Example #1
0
        public void TestSession05_BothDbWithAttachMainTablesAndEmptySession()
        {
            // close replica we just created and attach db file to our primary db as "replica"
            dbReplica.Close();
            dbPrimary.AttachDB(dbReplicaPath, "replica");

            // create a session for "main" database
            //Assert.That(SQLiteSession.Create(dbPrimary, out session), Is.EqualTo(SQLite3.Result.OK));
            Assert.That(dbPrimary.CreateSession("main", out session), Is.EqualTo(SQLite3.Result.OK));

            // attach all tables [from "main"] to it
            Assert.That(SQLiteSession.AttachToTable(session, null), Is.EqualTo(SQLite3.Result.OK));

            // enable watching for changes with this session
            Assert.That(SQLiteSession.Enable(session, SQLiteSession.EnableState.EnableSession), Is.EqualTo(SQLiteSession.EnableState.EnableSession));

            // validate no changes have occurred
            Assert.That(SQLiteSession.IsEmptySession(session), Is.True);

            // remove secondary db
            dbPrimary.DetachDB("replica");

            // done with our session
            SQLiteSession.Delete(session);
        }
Example #2
0
        public void TestSession03_WithAttachAndEmptySession()
        {
            Assert.That(SQLiteSession.Create(dbPrimary, out session), Is.EqualTo(SQLite3.Result.OK));

            Assert.That(SQLiteSession.AttachToTable(session, nameof(TestTable1)), Is.EqualTo(SQLite3.Result.OK));
            Assert.That(SQLiteSession.AttachToTable(session, nameof(TestTable2)), Is.EqualTo(SQLite3.Result.OK));

            Assert.That(SQLiteSession.IsEmptySession(session), Is.True);

            SQLiteSession.Delete(session);
        }
Example #3
0
        public void TestSession04_WithAttachAllAndEmptySession()
        {
            // create a session for "main" database
            Assert.That(SQLiteSession.Create(dbPrimary, out session), Is.EqualTo(SQLite3.Result.OK));

            // attach all tables to it
            Assert.That(SQLiteSession.AttachToTable(session, null), Is.EqualTo(SQLite3.Result.OK));

            // enable watching for changes with this session
            Assert.That(SQLiteSession.Enable(session, SQLiteSession.EnableState.EnableSession), Is.EqualTo(SQLiteSession.EnableState.EnableSession));

            // validate no changes have occurred
            Assert.That(SQLiteSession.IsEmptySession(session), Is.True);

            // done with our session
            SQLiteSession.Delete(session);
        }
Example #4
0
        public void TestSession07_CreateTableDiffForSession()
        {
            // close replica we just created and attach db file to our primary db as "replica"
            dbReplica.Close(); dbReplica = null;
            dbPrimary.AttachDB(dbReplicaPath, "replica");

            // verify that "main" has rows in TestTable1 and "replica" currently empty
            var mainTestTable1Rows = dbPrimary.Query <TestTable1>("Select * FROM main.TestTable1;");

            Assert.That(mainTestTable1Rows.Count, Is.GreaterThan(0));

            var replicaTestTable1Rows = dbPrimary.Query <TestTable1>("Select * FROM replica.TestTable1;");

            Assert.That(replicaTestTable1Rows.Count, Is.EqualTo(0));

            // create a session for "replica" database  <== note replica not main
            Assert.That(dbPrimary.CreateSession("main", out session), Is.EqualTo(SQLite3.Result.OK));

            // attach all tables [from "main"] to it
            Assert.That(SQLiteSession.AttachToTable(session, null), Is.EqualTo(SQLite3.Result.OK));

            // Note: we have not enabled watching for changes

            // validate no changes have occurred
            Assert.That(SQLiteSession.IsEmptySession(session), Is.True);

            // see what it would take to make replica like primary
#pragma warning disable IDE0018 // Inline variable declaration
            string errMsg;
#pragma warning restore IDE0018 // Inline variable declaration
            Assert.That(SQLiteSession.AddTableDiffToSession(session, "replica", nameof(TestTable1), out errMsg), Is.EqualTo(SQLite3.Result.OK));
            Assert.That(SQLiteSession.AddTableDiffToSession(session, "replica", nameof(TestTable2), out errMsg), Is.EqualTo(SQLite3.Result.OK));

            // session should no longer be empty, as tables differ and session should have the diff
            Assert.That(SQLiteSession.IsEmptySession(session), Is.False);

            // remove secondary db
            dbPrimary.DetachDB("replica");

            // done with our session
            SQLiteSession.Delete(session);
        }
Example #5
0
        public void TestSession42_ReplicateSimple()
        {
            // close replica we just created and attach db file to our primary db as "replica"
            dbReplica.Close();
            dbPrimary.AttachDB(dbReplicaPath, "replica");

            // verify that "main" has rows in TestTable1 and "replica" currently empty
            var mainTestTable1Rows = dbPrimary.Query <TestTable1>("Select * FROM main.TestTable1;");

            Assert.That(mainTestTable1Rows.Count, Is.GreaterThan(0));

            var replicaTestTable1Rows = dbPrimary.Query <TestTable1>("Select * FROM replica.TestTable1;");

            Assert.That(replicaTestTable1Rows.Count, Is.EqualTo(0));

            // create a session for "main" database
            Assert.That(dbPrimary.CreateSession("main", out session), Is.EqualTo(SQLite3.Result.OK));

            // attach all tables [from "main"] to it
            Assert.That(SQLiteSession.AttachToTable(session, null), Is.EqualTo(SQLite3.Result.OK));

            // Note: we have not enabled watching for changes

            // validate no changes have occurred
            Assert.That(SQLiteSession.IsEmptySession(session), Is.True);

            // see what it would take to make replica.table like main.table
#pragma warning disable IDE0018 // Inline variable declaration
            string errMsg;
#pragma warning restore IDE0018 // Inline variable declaration
            Assert.That(SQLiteSession.AddTableDiffToSession(session, "replica", nameof(TestTable1), out errMsg), Is.EqualTo(SQLite3.Result.OK));
            Assert.That(SQLiteSession.AddTableDiffToSession(session, "replica", nameof(TestTable2), out errMsg), Is.EqualTo(SQLite3.Result.OK));

            // session should no longer be empty, as tables differ and session should have the diff
            Assert.That(SQLiteSession.IsEmptySession(session), Is.False);

            // create change set
            Assert.That(SQLiteSession.GenerateChangeSet(session, out SQLiteChangeSet changeSet), Is.EqualTo(SQLite3.Result.OK));

            // done with our session
            SQLiteSession.Delete(session);

            // remove secondary db
            dbPrimary.DetachDB("replica");

            // reopen dbReplica
            dbReplica = new SQLiteConnection(dbReplicaPath, storeDateTimeAsTicks: true);

            // validate reopened replica db has no rows
            replicaTestTable1Rows = dbReplica.Query <TestTable1>("Select * FROM TestTable1;");
            Assert.That(replicaTestTable1Rows.Count, Is.EqualTo(0));

            // apply change set to dbReplica
            // should be no conflicts so conflict handler not called
            Assert.That(dbReplica.ApplySessionChangeSet(changeSet, null, null, null), Is.EqualTo(SQLite3.Result.OK));

            // validate has contents we expect
            replicaTestTable1Rows = dbReplica.Query <TestTable1>("Select * FROM TestTable1;");
            Assert.That(replicaTestTable1Rows.Count, Is.GreaterThan(0));

            // generate inverse change set
            Assert.That(SQLiteSession.InvertChangeSet(changeSet, out SQLiteChangeSet inverseChangeSet), Is.EqualTo(SQLite3.Result.OK));
            using (inverseChangeSet)
            {
                // apply the inverse
                Assert.That(dbReplica.ApplySessionChangeSet(inverseChangeSet, null, null, null), Is.EqualTo(SQLite3.Result.OK));

                // validate replica db once again has no rows
                replicaTestTable1Rows = dbReplica.Query <TestTable1>("Select * FROM TestTable1;");
                Assert.That(replicaTestTable1Rows.Count, Is.EqualTo(0));
            }

            // reset
            CleanupTest();
            SetupTest();

            // insert differing row to dbReplica with same primary key, i.e. force conflict
            // add a row to table
            var newRow = new TestTable1
            {
                pk             = sampleData.pk,
                myString       = "test conflict", // <== only conflicting piece of data
                myInt          = sampleData.myInt,
                myDate         = sampleData.myDate,
                myTable2Object = sampleData.myTable2Object
            };
            Assert.That(dbReplica.Insert(newRow), Is.EqualTo(1));

            // apply change set to dbReplica
            // should now be a conflict so conflict handler is called
            Assert.That(dbReplica.ApplySessionChangeSet(changeSet, DummyFilterCallback /* null */, SQLiteSession.CallbackReplaceOnConflicts, null /*"my text context"*/), Is.EqualTo(SQLite3.Result.OK));

            // valid has contents we expect
            replicaTestTable1Rows = dbReplica.Query <TestTable1>("Select * FROM TestTable1;");
            Assert.That(replicaTestTable1Rows.Count, Is.GreaterThan(0));

            // release our change set buffer explicitly
            changeSet.Dispose();
        }
Example #6
0
        /// <summary>
        /// Perform one way synchronization where all changes in from db are replicated
        /// to current db with conflict resolution.  Once complete the current db will
        /// contain all changes made to syncFromDb (minus conflicting changes) and all
        /// changes to current db since last sync (minus conflicting changes).
        /// Note: dbToSyncFrom is not modified, i.e. changes within current db are not
        /// replicated back (hence the one way).
        /// On conflicting change only one or the other is used (obviously) - each
        /// table has its own conflict handler and that determines whether lastest
        /// change wins, current db wins, or user is asked.
        /// Uses active db connection for database to sync (db to update)
        /// </summary>
        /// <param name="dbToSyncFromFilename">filename (including path) to synchronize with</param>
        public void SyncOneWay(string dbToSyncFromFilename, string[] tablesToSync)
        {
            // todo fix properly
            _tablesToSync = tablesToSync;

            // session used during replication, Zero if no session currently open
            var session = IntPtr.Zero;

            try
            {
                logger.Info($"SyncOneWay with {dbToSyncFromFilename}");

                // open both database files in same db instance as 'main' and 'replica'
                db.AttachDB(dbToSyncFromFilename, "replica");

                // create a session, used to obtain differences
                // Note: we use reverse of desired to force each change to conflict, if we created session on "replica"
                // and diff to "main" below, then no conflicts will occur during applying of changset and when complete
                // main db will be identical to replica db - but we want a chance to evaluate each change before
                // applying, so we create the inverse changeset which forces a conflict for each change
                IfNotOKThenThrowReplicationException(db.CreateSession("main", out session), "Failed to create session");

                // do for each table that should be synchronized

                // attach all tables [from "main"] to it
                IfNotOKThenThrowReplicationException(SQLiteSession.AttachToTable(session, null), "Failed to attach tables");

                // Note: we have not enabled watching for changes

                // validate no changes have occurred
                if (!SQLiteSession.IsEmptySession(session))
                {
                    throw new ReplicationException("Session is not empty!");
                }

                // see what it would take to make main.table like replica.table
                foreach (var tableName in tablesToSync)
                {
                    IfNotOKThenThrowReplicationException(SQLiteSession.AddTableDiffToSession(session, "replica", tableName, out string errMsg), $"Unable to determine differences for table {tableName}: {errMsg}");
                }


                // if there are any changes then session will not be empty, it should contain the difference
                if (SQLiteSession.IsEmptySession(session))
                {
                    logger.Debug("Replication - no changes, session is empty after differencing.");
                }

                // create change set
                IfNotOKThenThrowReplicationException(SQLiteSession.GenerateChangeSet(session, out SQLiteChangeSet changeSet), "Failed to generate change set from session");
                using (changeSet) // ensure changeSet.Dispose() is called to release change set buffer
                {
                    // on conflicts our conflict handler callback uses our context to handle conflicts accordingly
                    IfNotOKThenThrowReplicationException(db.ApplySessionChangeSet(changeSet, null, CallbackConflictHandler, ctx: this), "Failed to apply replicated changes");
                }
            }
            finally
            {
                // ensure session is closed since we are done with our session,
                // Ensure attached databases and sessions are cleaned up.
                // May be called if error occurs or as part of normal cleanup
                // Note: we must ensure replica is still available for conflict handler so don't do earlier
                if (session != IntPtr.Zero)
                {
                    SQLiteSession.Delete(session);
                }
                session = IntPtr.Zero;

                // remove secondary db
                db?.DetachDB("replica");
            }
        }