// Read a file that contains a JSON document per line. Every line becomes a document. protected uint ImportJSONLines(string path, TimeSpan timeout, bool verbose) { if (verbose) { WriteLine($"Reading {path}..."); } var st = Stopwatch.StartNew(); uint numDocs = 0; LiteCoreBridge.Check(err => Native.c4db_beginTransaction(Db, err)); try { var encoder = Native.FLEncoder_New(); ReadFileByLines(path, line => { FLError error; NativeRaw.FLEncoder_ConvertJSON(encoder, line); var body = NativeRaw.FLEncoder_Finish(encoder, &error); ((long)body.buf).Should().NotBe(0, "because otherwise the encode failed"); Native.FLEncoder_Reset(encoder); var docID = (numDocs + 1).ToString("D7"); // Save document: using (var docID_ = new C4String(docID)) { var rq = new C4DocPutRequest { docID = docID_.AsC4Slice(), body = (C4Slice)body, save = true }; var doc = (C4Document *)LiteCoreBridge.Check(err => { var localRq = rq; return(Native.c4doc_put(Db, &localRq, null, err)); }); Native.c4doc_free(doc); } Native.FLSliceResult_Free(body); ++numDocs; if (numDocs % 1000 == 0 && st.Elapsed >= timeout) { Console.Write($"Stopping JSON import after {st.Elapsed.TotalSeconds:F3} sec "); return(false); } if (verbose && numDocs % 10000 == 0) { Console.Write($"{numDocs} "); } return(true); }); if (verbose) { WriteLine("Committing..."); } } finally { LiteCoreBridge.Check(err => Native.c4db_endTransaction(Db, true, err)); } if (verbose) { st.PrintReport("Importing", numDocs, "doc", _output); } return(numDocs); }
public void TestQueryFullTextIndex() { RunTestVariants(() => { CreateFullTextIndex(3); // Search for "somewhere": var e = (C4QueryEnumerator *)LiteCoreBridge.Check(err => Native.c4view_fullTextQuery(_view, "somewhere", null, null, err)); C4Error error; Native.c4queryenum_next(e, &error).Should().BeTrue("because otherwise the full text query failed"); e->docID.Equals(C4Slice.Constant("doc-001")).Should().BeTrue("because doc-001 contains the text"); e->docSequence.Should().Be(1, "because the enumerator should have the correct sequence for the current doc"); e->fullTextTermCount.Should().Be(1, "because the full text information should be correct"); e->fullTextTerms[0].termIndex.Should().Be(0, "because the full text information should be correct"); e->fullTextTerms[0].start.Should().Be(8, "because the full text information should be correct"); e->fullTextTerms[0].length.Should().Be(9, "because the full text information should be correct"); Native.c4queryenum_next(e, &error).Should().BeFalse("beacuse the query only has one row"); error.Code.Should().Be(0, "because otherwise an error occurred somewhere"); Native.c4queryenum_free(e); // Search for "cat": e = (C4QueryEnumerator *)LiteCoreBridge.Check(err => Native.c4view_fullTextQuery(_view, "cat", null, null, err)); int i = 0; while (Native.c4queryenum_next(e, &error)) { ++i; e->fullTextTermCount.Should().Be(1, "because the full text information should be correct"); e->fullTextTerms[0].termIndex.Should().Be(0, "because the full text information should be correct"); if (e->docSequence == 1) { e->fullTextTerms[0].start.Should().Be(20, "because the full text information should be correct"); e->fullTextTerms[0].length.Should().Be(4, "because the full text information should be correct"); } else { e->docSequence.Should().Be(3, "because the correct document should be indexed"); e->fullTextTerms[0].start.Should().Be(4, "because the full text information should be correct"); e->fullTextTerms[0].length.Should().Be(3, "because the full text information should be correct"); } } Native.c4queryenum_free(e); error.Code.Should().Be(0, "because otherwise an error occurred somewhere"); i.Should().Be(2, "because there are two documents valid for the query"); // Search for "cat bark": e = (C4QueryEnumerator *)LiteCoreBridge.Check(err => Native.c4view_fullTextQuery(_view, "cat bark", null, null, err)); Native.c4queryenum_next(e, &error).Should().BeTrue("because otherwise the full text query failed"); e->docID.Equals(C4Slice.Constant("doc-001")).Should().BeTrue("because doc-001 contains the text"); e->docSequence.Should().Be(1, "because the enumerator should have the correct sequence for the current doc"); e->fullTextTermCount.Should().Be(2, "because the full text information should be correct"); e->fullTextTerms[0].termIndex.Should().Be(0, "because the full text information should be correct"); e->fullTextTerms[0].start.Should().Be(20, "because the full text information should be correct"); e->fullTextTerms[0].length.Should().Be(4, "because the full text information should be correct"); e->fullTextTerms[1].termIndex.Should().Be(1, "because the full text information should be correct"); e->fullTextTerms[1].start.Should().Be(29, "because the full text information should be correct"); e->fullTextTerms[1].length.Should().Be(7, "because the full text information should be correct"); Native.c4queryenum_next(e, &error).Should().BeFalse("beacuse the query only has one row"); error.Code.Should().Be(0, "because otherwise an error occurred somewhere"); Native.c4queryenum_free(e); }); }
private C4Document *ForceInsert(C4Database *db, string docID, string[] history, FLSlice body, C4RevisionFlags flags, C4Error *error = null) { LiteCoreBridge.Check(err => Native.c4db_beginTransaction(db, err)); var c4History = new C4String[history.Length]; var success = false; try { var i = 0; var sliceHistory = new FLSlice[history.Length]; foreach (var entry in history) { var c4Str = new C4String(entry); c4History[i] = c4Str; sliceHistory[i++] = c4Str.AsFLSlice(); } using (var docID_ = new C4String(docID)) { fixed(FLSlice *sliceHistory_ = sliceHistory) { var rq = new C4DocPutRequest { docID = docID_.AsFLSlice(), existingRevision = true, allowConflict = true, history = sliceHistory_, historyCount = (ulong)history.Length, body = body, revFlags = flags, remoteDBID = _remoteDocID, save = true }; C4Document *doc; if (error != null) { var local = rq; doc = Native.c4doc_put(db, &local, null, error); } else { doc = (C4Document *)LiteCoreBridge.Check(err => { var local = rq; return(Native.c4doc_put(db, &local, null, err)); }); } success = true; return(doc); } } } finally { foreach (var entry in c4History) { entry.Dispose(); } LiteCoreBridge.Check(err => Native.c4db_endTransaction(db, success, err)); } }
public void TestRevTree() { RunTestVariants(() => { if (!IsRevTrees(Db)) { return; } // TODO: Observer var docID = "MyDocID"; var body = JSON2Fleece("{'message':'hi'}"); var history = new[] { "4-4444", "3-3333", "2-2222", "1-1111" }; ForceInsert(docID, history, (FLSlice)body); Native.c4db_getDocumentCount(Db).Should().Be(1UL); var doc = GetDoc(docID, C4DocContentLevel.DocGetAll); VerifyRev(doc, history, (FLSlice)body); Native.c4doc_release(doc); var lastSeq = Native.c4db_getLastSequence(Db); ForceInsert(docID, history, (FLSlice)body); Native.c4db_getLastSequence(Db).Should().Be(lastSeq, "because the last operation should have been a no-op"); _remoteDocID = 1; var conflictHistory = new[] { "5-5555", "4-4545", "3-3030", "2-2222", "1-1111" }; var conflictBody = JSON2Fleece("{'message':'yo'}"); ForceInsert(docID, conflictHistory, (FLSlice)conflictBody); _remoteDocID = 0; // Conflicts are a bit different than CBL 1.x here. A conflicted revision is marked with the Conflict flag, // and such revisions can never be current. So in other words, the oldest revision always wins the conflict; // it has nothing to do with revIDs Native.c4db_getDocumentCount(Db).Should().Be(1UL); doc = GetDoc(docID, C4DocContentLevel.DocGetAll); VerifyRev(doc, history, (FLSlice)body); Native.c4doc_release(doc); // TODO: Conflict check var otherDocID = "AnotherDocID"; var otherBody = JSON2Fleece("{'language':'jp'}"); var otherHistory = new[] { "1-1010" }; ForceInsert(otherDocID, otherHistory, (FLSlice)otherBody); doc = GetDoc(docID, C4DocContentLevel.DocGetAll); LiteCoreBridge.Check(err => Native.c4doc_selectRevision(doc, "2-2222", false, err)); doc->selectedRev.flags.Should().NotHaveFlag(C4RevisionFlags.KeepBody); NativeRaw.c4doc_getRevisionBody(doc).CreateString().Should().BeNull(); Native.c4doc_release(doc); doc = GetDoc(otherDocID, C4DocContentLevel.DocGetAll); C4Error error; Native.c4doc_selectRevision(doc, "666-6666", false, &error).Should().BeFalse(); error.domain.Should().Be(C4ErrorDomain.LiteCoreDomain); error.code.Should().Be((int)C4ErrorCode.NotFound); Native.c4doc_release(doc); Native.c4db_getLastSequence(Db).Should().Be(3UL, "because duplicate inserted rows should not advance the last sequence"); doc = GetDoc(docID); doc->revID.CreateString().Should().Be(history[0], "because the earlier revision should win the conflict"); doc->selectedRev.revID.CreateString().Should().Be(history[0]); Native.c4doc_release(doc); doc = GetDoc(docID, C4DocContentLevel.DocGetAll); var conflictingRevs = GetRevisionHistory(doc, true, true); conflictingRevs.Count.Should().Be(2); conflictingRevs.Should().Equal(history[0], conflictHistory[0]); Native.c4doc_release(doc); var e = (C4DocEnumerator *)LiteCoreBridge.Check(err => { var options = C4EnumeratorOptions.Default; return(Native.c4db_enumerateChanges(Db, 0, &options, err)); }); var counter = 0; while (Native.c4enum_next(e, &error)) { C4DocumentInfo docInfo; Native.c4enum_getDocumentInfo(e, &docInfo); if (counter == 0) { docInfo.docID.CreateString().Should().Be(docID); docInfo.revID.CreateString().Should().Be(history[0]); } else if (counter == 1) { docInfo.docID.CreateString().Should().Be(otherDocID); docInfo.revID.CreateString().Should().Be(otherHistory[0]); } counter++; } Native.c4enum_free(e); counter.Should().Be(2, "because only two documents are present"); e = (C4DocEnumerator *)LiteCoreBridge.Check(err => { var options = C4EnumeratorOptions.Default; options.flags |= C4EnumeratorFlags.IncludeDeleted; return(Native.c4db_enumerateChanges(Db, 0, &options, err)); }); counter = 0; while (Native.c4enum_next(e, &error)) { doc = Native.c4enum_getDocument(e, &error); if (doc == null) { break; } do { if (counter == 0) { doc->docID.CreateString().Should().Be(docID); doc->selectedRev.revID.CreateString().Should().Be(history[0]); } else if (counter == 1) { doc->docID.CreateString().Should().Be(docID); doc->selectedRev.revID.CreateString().Should().Be(conflictHistory[0]); } else if (counter == 2) { doc->docID.CreateString().Should().Be(otherDocID); doc->selectedRev.revID.CreateString().Should().Be(otherHistory[0]); } counter++; } while (Native.c4doc_selectNextLeafRevision(doc, true, false, &error)); Native.c4doc_release(doc); } Native.c4enum_free(e); counter.Should().Be(3, "because only two documents are present, but one has two conflicting revisions"); doc = PutDoc(docID, conflictHistory[0], FLSlice.Null, C4RevisionFlags.Deleted); Native.c4doc_release(doc); doc = GetDoc(docID); //TODO: Uncomment once https://github.com/couchbase/couchbase-lite-core/issues/57 is fixed //doc->revID.CreateString().Should().Be(history[0]); //doc->selectedRev.revID.CreateString().Should().Be(history[0]); //VerifyRev(doc, history, (FLSlice)body); Native.c4doc_release(doc); doc = PutDoc(docID, history[0], FLSlice.Null, C4RevisionFlags.Deleted); Native.c4doc_release(doc); Native.FLSliceResult_Release(body); Native.FLSliceResult_Release(otherBody); Native.FLSliceResult_Release(conflictBody); // TODO: Need to implement following tests }); }
public void TestDatabaseBlobStore() { RunTestVariants(() => { LiteCoreBridge.Check(err => Native.c4db_getBlobStore(Db, err)); }); }
private uint InsertDocs(FLArray *docs) { var typeKey = NativeRaw.FLDictKey_Init(FLSlice.Constant("Track Type"), true); var idKey = NativeRaw.FLDictKey_Init(FLSlice.Constant("Persistent ID"), true); var nameKey = NativeRaw.FLDictKey_Init(FLSlice.Constant("Name"), true); var albumKey = NativeRaw.FLDictKey_Init(FLSlice.Constant("Album"), true); var artistKey = NativeRaw.FLDictKey_Init(FLSlice.Constant("Artist"), true); var timeKey = NativeRaw.FLDictKey_Init(FLSlice.Constant("Total Time"), true); var genreKey = NativeRaw.FLDictKey_Init(FLSlice.Constant("Genre"), true); var yearKey = NativeRaw.FLDictKey_Init(FLSlice.Constant("Year"), true); var trackNoKey = NativeRaw.FLDictKey_Init(FLSlice.Constant("Track Number"), true); var compKey = NativeRaw.FLDictKey_Init(FLSlice.Constant("Compilation"), true); LiteCoreBridge.Check(err => Native.c4db_beginTransaction(Db, err)); try { var enc = Native.FLEncoder_New(); FLArrayIterator iter; Native.FLArrayIterator_Begin(docs, &iter); uint numDocs = 0; while (Native.FLArrayIterator_Next(&iter)) { // Check that track is correct type: var track = Native.FLValue_AsDict(Native.FLArrayIterator_GetValue(&iter)); var trackType = NativeRaw.FLValue_AsString(Native.FLDict_GetWithKey(track, &typeKey)); if (!trackType.Equals(FLSlice.Constant("File")) && !trackType.Equals(FLSlice.Constant("Remote"))) { continue; } var trackID = NativeRaw.FLValue_AsString(Native.FLDict_GetWithKey(track, &idKey)); ((long)trackID.buf).Should().NotBe(0, "because otherwise the data was not read correctly"); // Encode doc body: Native.FLEncoder_BeginDict(enc, 0); CopyValue(track, &nameKey, enc).Should().BeTrue("because otherwise the copy failed"); CopyValue(track, &albumKey, enc); CopyValue(track, &artistKey, enc); CopyValue(track, &timeKey, enc); CopyValue(track, &genreKey, enc); CopyValue(track, &yearKey, enc); CopyValue(track, &trackNoKey, enc); CopyValue(track, &compKey, enc); Native.FLEncoder_EndDict(enc); FLError err; var body = NativeRaw.FLEncoder_Finish(enc, &err); body.Should().NotBeNull("because otherwise the encoding process failed"); Native.FLEncoder_Reset(enc); // Save Document: var rq = new C4DocPutRequest(); rq.docID = (C4Slice)trackID; rq.body = body; rq.save = true; var doc = (C4Document *)LiteCoreBridge.Check(c4err => { var localRq = rq; return(Native.c4doc_put(Db, &localRq, null, c4err)); }); Native.c4doc_free(doc); ++numDocs; } Native.FLEncoder_Free(enc); return(numDocs); } finally { LiteCoreBridge.Check(err => Native.c4db_endTransaction(Db, true, err)); } }
public void TestUpdate() { RunTestVariants(() => { WriteLine("Begin test"); C4Document *doc = null; LiteCoreBridge.Check(err => Native.c4db_beginTransaction(Db, err)); try { WriteLine("Begin create"); doc = (C4Document *)LiteCoreBridge.Check(err => NativeRaw.c4doc_create(Db, DocID, FleeceBody, 0, err)); } finally { LiteCoreBridge.Check(err => Native.c4db_endTransaction(Db, true, err)); } WriteLine("After save"); var expectedRevID = IsRevTrees() ? FLSlice.Constant("1-d41ffed6be0529153fd3d27b3218d9052e1c2b40") : FLSlice.Constant("1@*"); doc->revID.Equals(expectedRevID).Should().BeTrue(); doc->flags.Should().Be(C4DocumentFlags.DocExists, "because the document was saved"); doc->selectedRev.revID.Equals(expectedRevID).Should().BeTrue(); doc->docID.Equals(DocID).Should().BeTrue("because that is the document ID that it was saved with"); // Read the doc into another C4Document var doc2 = (C4Document *)LiteCoreBridge.Check(err => NativeRaw.c4doc_get(Db, DocID, false, err)); doc->revID.Equals(expectedRevID).Should() .BeTrue("because the other reference should have the same rev ID"); LiteCoreBridge.Check(err => Native.c4db_beginTransaction(Db, err)); try { WriteLine("Begin 2nd save"); var body = JSON2Fleece("{\"ok\":\"go\"}"); var updatedDoc = (C4Document *)LiteCoreBridge.Check( err => NativeRaw.c4doc_update(doc, (FLSlice)body, 0, err)); doc->selectedRev.revID.Equals(expectedRevID).Should().BeTrue(); doc->revID.Equals(expectedRevID).Should().BeTrue(); Native.c4doc_free(doc); doc = updatedDoc; Native.FLSliceResult_Release(body); } finally { LiteCoreBridge.Check(err => Native.c4db_endTransaction(Db, true, err)); } WriteLine("After 2nd save"); var expectedRev2ID = IsRevTrees() ? FLSlice.Constant("2-e388dff9126ba5a0d93c7af05bc72f3cdf450598") : FLSlice.Constant("2@*"); doc->revID.Equals(expectedRev2ID).Should().BeTrue(); doc->selectedRev.revID.Equals(expectedRev2ID).Should().BeTrue(); LiteCoreBridge.Check(err => Native.c4db_beginTransaction(Db, err)); try { WriteLine("Begin conflicting save"); C4Error error; var body = JSON2Fleece("{\"ok\":\"no way\"}"); ((long)NativeRaw.c4doc_update(doc2, (FLSlice)body, 0, &error)).Should().Be(0, "because this is a conflict"); error.code.Should().Be((int)C4ErrorCode.Conflict); error.domain.Should().Be(C4ErrorDomain.LiteCoreDomain); Native.FLSliceResult_Release(body); } finally { LiteCoreBridge.Check(err => Native.c4db_endTransaction(Db, true, err)); } LiteCoreBridge.Check(err => Native.c4db_beginTransaction(Db, err)); try { WriteLine("Begin conflicting create"); C4Error error; var body = JSON2Fleece("{\"ok\":\"no way\"}"); ((long)NativeRaw.c4doc_create(Db, DocID, (FLSlice)body, 0, &error)).Should().Be(0, "because this is a conflict"); error.code.Should().Be((int)C4ErrorCode.Conflict); error.domain.Should().Be(C4ErrorDomain.LiteCoreDomain); Native.FLSliceResult_Release(body); } finally { LiteCoreBridge.Check(err => Native.c4db_endTransaction(Db, true, err)); } Native.c4doc_free(doc); Native.c4doc_free(doc2); }); }
public void TestExpired() { RunTestVariants(() => { const string docID = "expire_me"; CreateRev(docID, RevID, Body); var expire = DateTimeOffset.UtcNow.Add(TimeSpan.FromSeconds(1)).ToUnixTimeSeconds(); LiteCoreBridge.Check(err => Native.c4doc_setExpiration(Db, docID, (ulong)expire, err)); expire = DateTimeOffset.UtcNow.Add(TimeSpan.FromSeconds(2)).ToUnixTimeSeconds(); // Make sure setting it to the same is also true LiteCoreBridge.Check(err => Native.c4doc_setExpiration(Db, docID, (ulong)expire, err)); LiteCoreBridge.Check(err => Native.c4doc_setExpiration(Db, docID, (ulong)expire, err)); const string docID2 = "expire_me_too"; CreateRev(docID2, RevID, Body); LiteCoreBridge.Check(err => Native.c4doc_setExpiration(Db, docID2, (ulong)expire, err)); const string docID3 = "dont_expire_me"; CreateRev(docID3, RevID, Body); WriteLine("---- Wait till expiration time..."); Task.Delay(TimeSpan.FromSeconds(2)).Wait(); WriteLine("---- Scan expired docs (#1)"); var e = (C4ExpiryEnumerator *)LiteCoreBridge.Check(err => Native.c4db_enumerateExpired(Db, err)); int expiredCount = 0; while (Native.c4exp_next(e, null)) { var existingDocID = Native.c4exp_getDocID(e); existingDocID.Should().NotBe(docID3, "because the last document is not scheduled for expiration"); expiredCount++; } Native.c4exp_free(e); expiredCount.Should().Be(2, "because 2 documents were scheduled for expiration"); Native.c4doc_getExpiration(Db, docID).Should().Be((ulong)expire, "because that was what was set as the expiration"); Native.c4doc_getExpiration(Db, docID2).Should().Be((ulong)expire, "because that was what was set as the expiration"); Native.c4db_nextDocExpiration(Db).Should().Be((ulong)expire, "because that is the closest expiration date"); WriteLine("---- Scan expired docs (#2)"); e = (C4ExpiryEnumerator *)LiteCoreBridge.Check(err => Native.c4db_enumerateExpired(Db, err)); expiredCount = 0; while (Native.c4exp_next(e, null)) { var existingDocID = Native.c4exp_getDocID(e); existingDocID.Should().NotBe(docID3, "because the last document is not scheduled for expiration"); expiredCount++; } WriteLine("---- Purge expired docs"); LiteCoreBridge.Check(err => Native.c4exp_purgeExpired(e, err)); Native.c4exp_free(e); expiredCount.Should().Be(2, "because 2 documents were scheduled for expiration"); WriteLine("---- Scan expired docs (#3)"); e = (C4ExpiryEnumerator *)LiteCoreBridge.Check(err => Native.c4db_enumerateExpired(Db, err)); expiredCount = 0; while (Native.c4exp_next(e, null)) { expiredCount++; } Write("---- Purge expired docs (again"); LiteCoreBridge.Check(err => Native.c4exp_purgeExpired(e, err)); Native.c4exp_free(e); expiredCount.Should().Be(0, "because no more documents were scheduled for expiration"); }); }
public void TestFullTextMultipleProperties() { RunTestVariants(() => { LiteCoreBridge.Check(err => Native.c4db_createIndex(Db, "byAddress", "[[\".contact.address.street\"],[\".contact.address.city\"],[\".contact.address.state\"]]", C4IndexType.FullTextIndex, null, err)); Compile(Json5("['MATCH', 'byAddress', 'Santa']")); var expected = new[] { new C4FullTextMatch(15, 1, 0, 0, 5), new C4FullTextMatch(44, 0, 0, 3, 5), new C4FullTextMatch(68, 0, 0, 3, 5), new C4FullTextMatch(72, 1, 0, 0, 5) }; int index = 0; foreach (var result in RunFTS()) { foreach (var match in result) { match.Should().Be(expected[index++]); } } Compile(Json5("['MATCH', 'byAddress', 'contact.address.street:Santa']")); expected = new[] { new C4FullTextMatch(44, 0, 0, 3, 5), new C4FullTextMatch(68, 0, 0, 3, 5) }; index = 0; foreach (var result in RunFTS()) { foreach (var match in result) { match.Should().Be(expected[index++]); } } Compile(Json5("['MATCH', 'byAddress', 'contact.address.street:Santa Saint']")); expected = new[] { new C4FullTextMatch(68, 0, 0, 3, 5), new C4FullTextMatch(68, 1, 1, 0, 5) }; index = 0; foreach (var result in RunFTS()) { foreach (var match in result) { match.Should().Be(expected[index++]); } } Compile(Json5("['MATCH', 'byAddress', 'contact.address.street:Santa OR Saint']")); expected = new[] { new C4FullTextMatch(20, 1, 1, 0, 5), new C4FullTextMatch(44, 0, 0, 3, 5), new C4FullTextMatch(68, 0, 0, 3, 5), new C4FullTextMatch(68, 1, 1, 0, 5), new C4FullTextMatch(77, 1, 1, 0, 5) }; index = 0; foreach (var result in RunFTS()) { foreach (var match in result) { match.Should().Be(expected[index++]); } } }); }
public void TestAllDocs() { RunTestVariants(() => { SetupAllDocs(); Native.c4db_getDocumentCount(Db).Should().Be(99UL, "because there are 99 non-deleted documents"); // No start or end ID: var options = C4EnumeratorOptions.Default; options.flags &= ~C4EnumeratorFlags.IncludeBodies; var e = (C4DocEnumerator *)LiteCoreBridge.Check(err => { var localOpts = options; return(Native.c4db_enumerateAllDocs(Db, null, null, &localOpts, err)); }); int i = 1; C4Error error; while (Native.c4enum_next(e, &error)) { var doc = (C4Document *)LiteCoreBridge.Check(err => Native.c4enum_getDocument(e, err)); var docID = $"doc-{i:D3}"; doc->docID.CreateString().Should().Be(docID, "because the doc should have the correct doc ID"); doc->revID.Equals(RevID).Should().BeTrue("because the doc should have the current revID"); doc->selectedRev.revID.Equals(RevID).Should().BeTrue("because the selected rev should have the correct rev ID"); doc->selectedRev.sequence.Should().Be((ulong)i, "because the sequences should come in order"); doc->selectedRev.body.Equals(C4Slice.Null).Should().BeTrue("because the body is not loaded yet"); LiteCoreBridge.Check(err => Native.c4doc_loadRevisionBody(doc, err)); doc->selectedRev.body.Equals(Body).Should().BeTrue("because the loaded body should be correct"); C4DocumentInfo info; Native.c4enum_getDocumentInfo(e, &info).Should().BeTrue("because otherwise the doc info load failed"); info.docID.CreateString().Should().Be(docID, "because the doc info should have the correct doc ID"); info.revID.Equals(RevID).Should().BeTrue("because the doc info should have the correct rev ID"); Native.c4doc_free(doc); i++; } Native.c4enum_free(e); i.Should().Be(100); // Start and end ID: e = (C4DocEnumerator *)LiteCoreBridge.Check(err => Native.c4db_enumerateAllDocs(Db, "doc-007", "doc-090", null, err)); i = 7; while (Native.c4enum_next(e, &error)) { error.code.Should().Be(0, "because otherwise an enumeration error occurred"); var doc = (C4Document *)LiteCoreBridge.Check(err => Native.c4enum_getDocument(e, err)); var docID = $"doc-{i:D3}"; doc->docID.CreateString().Should().Be(docID, "because the doc should have the correct doc ID"); Native.c4doc_free(doc); i++; } Native.c4enum_free(e); i.Should().Be(91, "because that is how many documents fall in the given range"); // Some docs, by ID: options = C4EnumeratorOptions.Default; options.flags |= C4EnumeratorFlags.IncludeDeleted; var docIDs = new[] { "doc-042", "doc-007", "bogus", "doc-001" }; e = (C4DocEnumerator *)LiteCoreBridge.Check(err => { var localOpts = options; return(Native.c4db_enumerateSomeDocs(Db, docIDs, &localOpts, err)); }); i = 0; while (Native.c4enum_next(e, &error)) { error.code.Should().Be(0, "because otherwise an enumeration error occurred"); var doc = (C4Document *)LiteCoreBridge.Check(err => Native.c4enum_getDocument(e, err)); doc->docID.CreateString().Should().Be(docIDs[i], "because the doc should have the correct sorted doc ID"); if (doc->sequence != 0) { i.Should().NotBe(2, "because no document exists with the 'bogus' key"); } Native.c4doc_free(doc); i++; } Native.c4enum_free(e); i.Should().Be(4, "because four document IDs were specified"); }); }
public void TestUpdate() { RunTestVariants(() => { WriteLine("Begin test"); C4Document *doc = null; LiteCoreBridge.Check(err => Native.c4db_beginTransaction(Db, err)); try { WriteLine("Begin create"); doc = (C4Document *)LiteCoreBridge.Check(err => NativeRaw.c4doc_create(Db, DocID, Body, 0, err)); } finally { LiteCoreBridge.Check(err => Native.c4db_endTransaction(Db, true, err)); } WriteLine("After save"); var expectedRevID = IsRevTrees() ? C4Slice.Constant("1-c10c25442d9fe14fa3ca0db4322d7f1e43140fab") : C4Slice.Constant("1@*"); doc->revID.Equals(expectedRevID).Should().BeTrue(); doc->flags.Should().Be(C4DocumentFlags.DocExists, "because the document was saved"); doc->selectedRev.revID.Equals(expectedRevID).Should().BeTrue(); doc->docID.Equals(DocID).Should().BeTrue("because that is the document ID that it was saved with"); // Read the doc into another C4Document var doc2 = (C4Document *)LiteCoreBridge.Check(err => NativeRaw.c4doc_get(Db, DocID, false, err)); doc->revID.Equals(expectedRevID).Should() .BeTrue("because the other reference should have the same rev ID"); LiteCoreBridge.Check(err => Native.c4db_beginTransaction(Db, err)); try { WriteLine("Begin 2nd save"); var updatedDoc = (C4Document *)LiteCoreBridge.Check( err => Native.c4doc_update(doc, Encoding.UTF8.GetBytes("{\"ok\":\"go\"}"), 0, err)); doc->selectedRev.revID.Equals(expectedRevID).Should().BeTrue(); doc->revID.Equals(expectedRevID).Should().BeTrue(); Native.c4doc_free(doc); doc = updatedDoc; } finally { LiteCoreBridge.Check(err => Native.c4db_endTransaction(Db, true, err)); } WriteLine("After 2nd save"); var expectedRev2ID = IsRevTrees() ? C4Slice.Constant("2-32c711b29ea3297e27f3c28c8b066a68e1bb3f7b") : C4Slice.Constant("2@*"); doc->revID.Equals(expectedRev2ID).Should().BeTrue(); doc->selectedRev.revID.Equals(expectedRev2ID).Should().BeTrue(); LiteCoreBridge.Check(err => Native.c4db_beginTransaction(Db, err)); try { WriteLine("Begin conflicting save"); C4Error error; ((long)Native.c4doc_update(doc2, Encoding.UTF8.GetBytes("{\"ok\":\"no way\"}"), 0, &error)).Should().Be(0, "because this is a conflict"); error.code.Should().Be((int)C4ErrorCode.Conflict); error.domain.Should().Be(C4ErrorDomain.LiteCoreDomain); } finally { LiteCoreBridge.Check(err => Native.c4db_endTransaction(Db, true, err)); } LiteCoreBridge.Check(err => Native.c4db_beginTransaction(Db, err)); try { WriteLine("Begin conflicting create"); C4Error error; ((long)NativeRaw.c4doc_create(Db, DocID, C4Slice.Constant("{\"ok\":\"no way\"}"), 0, &error)).Should().Be(0, "because this is a conflict"); error.code.Should().Be((int)C4ErrorCode.Conflict); error.domain.Should().Be(C4ErrorDomain.LiteCoreDomain); } finally { LiteCoreBridge.Check(err => Native.c4db_endTransaction(Db, true, err)); } Native.c4doc_free(doc); Native.c4doc_free(doc2); }); }
public void TestPut() { RunTestVariants(() => { LiteCoreBridge.Check(err => Native.c4db_beginTransaction(Db, err)); try { // Creating doc given ID: var rq = new C4DocPutRequest { docID = DocID, body = Body, save = true }; var doc = (C4Document *)LiteCoreBridge.Check(err => { var localRq = rq; return(Native.c4doc_put(Db, &localRq, null, err)); }); doc->docID.Equals(DocID).Should().BeTrue("because the doc should have the correct doc ID"); var expectedRevID = IsRevTrees() ? C4Slice.Constant("1-c10c25442d9fe14fa3ca0db4322d7f1e43140fab") : C4Slice.Constant("1@*"); doc->revID.Equals(expectedRevID).Should().BeTrue("because the doc should have the correct rev ID"); doc->flags.Should().Be(C4DocumentFlags.DocExists, "because the document exists"); doc->selectedRev.revID.Equals(expectedRevID).Should().BeTrue("because the selected rev should have the correct rev ID"); Native.c4doc_free(doc); // Update doc: var tmp = new[] { expectedRevID }; rq.body = C4Slice.Constant("{\"ok\":\"go\"}"); rq.historyCount = 1; ulong commonAncestorIndex = 0UL; fixed(C4Slice * history = tmp) { rq.history = history; doc = (C4Document *)LiteCoreBridge.Check(err => { var localRq = rq; ulong cai; var retVal = Native.c4doc_put(Db, &localRq, &cai, err); commonAncestorIndex = cai; return(retVal); }); } commonAncestorIndex.Should().Be(0UL, "because there are no common ancestors"); var expectedRev2ID = IsRevTrees() ? C4Slice.Constant("2-32c711b29ea3297e27f3c28c8b066a68e1bb3f7b") : C4Slice.Constant("2@*"); doc->revID.Equals(expectedRev2ID).Should().BeTrue("because the doc should have the updated rev ID"); doc->flags.Should().Be(C4DocumentFlags.DocExists, "because the document exists"); doc->selectedRev.revID.Equals(expectedRev2ID).Should().BeTrue("because the selected rev should have the correct rev ID"); Native.c4doc_free(doc); // Insert existing rev that conflicts: rq.body = C4Slice.Constant("{\"from\":\"elsewhere\"}"); rq.existingRevision = true; var conflictRevID = IsRevTrees() ? C4Slice.Constant("2-deadbeef") : C4Slice.Constant("1@binky"); tmp = new[] { conflictRevID, expectedRevID }; rq.historyCount = 2; fixed(C4Slice * history = tmp) { rq.history = history; doc = (C4Document *)LiteCoreBridge.Check(err => { var localRq = rq; ulong cai; var retVal = Native.c4doc_put(Db, &localRq, &cai, err); commonAncestorIndex = cai; return(retVal); }); } commonAncestorIndex.Should().Be(1UL, "because the common ancestor is at sequence 1"); doc->flags.Should().Be(C4DocumentFlags.DocExists | C4DocumentFlags.DocConflicted, "because the document exists"); doc->selectedRev.revID.Equals(conflictRevID).Should().BeTrue("because the selected rev should have the correct rev ID"); doc->revID.Equals(expectedRev2ID).Should().BeTrue("because the conflicting rev should never be the default"); Native.c4doc_free(doc); } finally { LiteCoreBridge.Check(err => Native.c4db_endTransaction(Db, true, err)); } }); }
public void TestGetForPut() { RunTestVariants(() => { LiteCoreBridge.Check(err => Native.c4db_beginTransaction(Db, err)); try { // Creating doc given ID: var doc = (C4Document *)LiteCoreBridge.Check(err => NativeRawPrivate.c4doc_getForPut(Db, DocID, C4Slice.Null, false, false, err)); doc->docID.Equals(DocID).Should().BeTrue("because the doc should have the correct doc ID"); doc->revID.Equals(C4Slice.Null).Should().BeTrue("because a rev ID has not been assigned yet"); ((int)doc->flags).Should().Be(0, "because the document has no flags yet"); doc->selectedRev.revID.Equals(C4Slice.Null).Should().BeTrue("because no rev ID has been assigned yet"); Native.c4doc_free(doc); // Creating doc, no ID: doc = (C4Document *)LiteCoreBridge.Check(err => NativeRawPrivate.c4doc_getForPut(Db, C4Slice.Null, C4Slice.Null, false, false, err)); doc->docID.size.Should().BeGreaterOrEqualTo(20, "because the document should be assigned a random ID"); doc->revID.Equals(C4Slice.Null).Should().BeTrue("because the doc doesn't have a rev ID yet"); ((int)doc->flags).Should().Be(0, "because the document has no flags yet"); doc->selectedRev.revID.Equals(C4Slice.Null).Should().BeTrue("because no rev ID has been assigned yet"); Native.c4doc_free(doc); // Delete with no revID given C4Error error; doc = NativeRawPrivate.c4doc_getForPut(Db, C4Slice.Null, C4Slice.Null, true, false, &error); ((long)doc).Should().Be(0, "because the document does not exist"); error.code.Should().Be((int)C4ErrorCode.NotFound, "because the correct error should be returned"); // Adding new rev of nonexistent doc: doc = NativeRawPrivate.c4doc_getForPut(Db, DocID, RevID, false, false, &error); ((long)doc).Should().Be(0, "because the document does not exist"); error.code.Should().Be((int)C4ErrorCode.NotFound, "because the correct error should be returned"); // Adding new rev of existing doc: CreateRev(DocID.CreateString(), RevID, Body); doc = (C4Document *)LiteCoreBridge.Check(err => NativeRawPrivate.c4doc_getForPut(Db, DocID, RevID, false, false, err)); doc->docID.Equals(DocID).Should().BeTrue("because the doc should have the correct doc ID"); doc->revID.Equals(RevID).Should().BeTrue("because the doc should have the correct rev ID"); doc->flags.Should().Be(C4DocumentFlags.DocExists, "because the document has no flags yet"); doc->selectedRev.revID.Equals(RevID).Should().BeTrue("because the selected rev should have the correct rev ID"); Native.c4doc_free(doc); // Adding new rev, with nonexistent parent doc = NativeRawPrivate.c4doc_getForPut(Db, DocID, Rev2ID, false, false, &error); ((long)doc).Should().Be(0, "because the document does not exist"); error.code.Should().Be((int)C4ErrorCode.Conflict, "because the correct error should be returned"); // Conflict -- try & fail to update non-current rev: var body2 = C4Slice.Constant("{\"ok\":\"go\"}"); CreateRev(DocID.CreateString(), Rev2ID, body2); doc = NativeRawPrivate.c4doc_getForPut(Db, DocID, RevID, false, false, &error); ((long)doc).Should().Be(0, "because the document does not exist"); error.code.Should().Be((int)C4ErrorCode.Conflict, "because the correct error should be returned"); if (Versioning == C4DocumentVersioning.RevisionTrees) { // Conflict -- force an update of non-current rev: doc = (C4Document *)LiteCoreBridge.Check(err => NativeRawPrivate.c4doc_getForPut(Db, DocID, RevID, false, true, err)); doc->docID.Equals(DocID).Should().BeTrue("because the doc should have the correct doc ID"); doc->selectedRev.revID.Equals(RevID).Should().BeTrue("because the doc should have the correct rev ID"); Native.c4doc_free(doc); } // Deleting the doc: doc = (C4Document *)LiteCoreBridge.Check(err => NativeRawPrivate.c4doc_getForPut(Db, DocID, Rev2ID, true, false, err)); doc->docID.Equals(DocID).Should().BeTrue("because the doc should have the correct doc ID"); doc->selectedRev.revID.Equals(Rev2ID).Should().BeTrue("because the doc should have the correct rev ID"); Native.c4doc_free(doc); // Actually delete it: CreateRev(DocID.CreateString(), Rev3ID, C4Slice.Null, C4RevisionFlags.Deleted); LiteCoreBridge.Check(err => Native.c4db_endTransaction(Db, true, err)); LiteCoreBridge.Check(err => Native.c4db_beginTransaction(Db, err)); // Re-creating the doc (no revID given): doc = (C4Document *)LiteCoreBridge.Check(err => NativeRawPrivate.c4doc_getForPut(Db, DocID, C4Slice.Null, false, false, err)); doc->docID.Equals(DocID).Should().BeTrue("because the doc should have the correct doc ID"); doc->selectedRev.revID.Equals(Rev3ID).Should().BeTrue("because the doc should have the correct rev ID"); doc->flags.Should().Be(C4DocumentFlags.DocExists | C4DocumentFlags.DocDeleted, "because the document was deleted"); doc->selectedRev.revID.Equals(Rev3ID).Should().BeTrue("because the doc should have the correct rev ID"); Native.c4doc_free(doc); } finally { LiteCoreBridge.Check(err => Native.c4db_endTransaction(Db, true, err)); } }); }
private Document Save([NotNull] Document document, bool deletion) { if (document.IsInvalidated) { throw new CouchbaseLiteException(StatusCode.NotAllowed, "Cannot save or delete a MutableDocument that has already been used to save or delete"); } if (deletion && document.RevID == null) { throw new CouchbaseLiteException(StatusCode.NotAllowed, "Cannot delete a document that has not yet been saved"); } var docID = document.Id; var doc = document; Document baseDoc = null, otherDoc = null; C4Document *newDoc = null; Document retVal = null; while (true) { var resolve = false; retVal = ThreadSafety.DoLocked(() => { VerifyDB(doc); LiteCoreBridge.Check(err => Native.c4db_beginTransaction(_c4db, err)); try { if (deletion) { // Check for no-op case if the document does not exist var curDoc = (C4Document *)NativeHandler.Create().AllowError(new C4Error(C4ErrorCode.NotFound)) .Execute(err => Native.c4doc_get(_c4db, docID, true, err)); if (curDoc == null) { (document as MutableDocument)?.MarkAsInvalidated(); return(null); } Native.c4doc_free(curDoc); } var newDocOther = newDoc; Save(doc, &newDocOther, baseDoc?.c4Doc?.HasValue == true ? baseDoc.c4Doc.RawDoc : null, deletion); if (newDocOther != null) { // Save succeeded, so commit newDoc = newDocOther; LiteCoreBridge.Check(err => { var success = Native.c4db_endTransaction(_c4db, true, err); if (!success) { Native.c4doc_free(newDoc); } return(success); }); (document as MutableDocument)?.MarkAsInvalidated(); baseDoc?.Dispose(); return(new Document(this, docID, new C4DocumentWrapper(newDoc))); } // There was a conflict if (deletion && !doc.IsDeleted) { var deletedDoc = doc.ToMutable(); deletedDoc.MarkAsDeleted(); doc = deletedDoc; } if (doc.c4Doc != null) { baseDoc = new Document(this, docID, doc.c4Doc.Retain <C4DocumentWrapper>()); } otherDoc = new Document(this, docID); if (!otherDoc.Exists) { LiteCoreBridge.Check(err => Native.c4db_endTransaction(_c4db, false, err)); return(null); } } catch (Exception) { baseDoc?.Dispose(); otherDoc?.Dispose(); LiteCoreBridge.Check(err => Native.c4db_endTransaction(_c4db, false, err)); throw; } resolve = true; LiteCoreBridge.Check(err => Native.c4db_endTransaction(_c4db, false, err)); return(null); }); if (!resolve) { return(retVal); } // Resolve Conflict Document resolved = null; try { var resolver = Config.ConflictResolver; var conflict = new Conflict(doc, otherDoc, baseDoc); resolved = resolver.Resolve(conflict); if (resolved == null) { throw new LiteCoreException(new C4Error(C4ErrorCode.Conflict)); } } finally { baseDoc?.Dispose(); if (!ReferenceEquals(resolved, otherDoc)) { otherDoc?.Dispose(); } } retVal = ThreadSafety.DoLocked(() => { var current = new Document(this, docID); if (resolved.RevID == current.RevID) { (document as MutableDocument)?.MarkAsInvalidated(); current.Dispose(); return(resolved); // Same as current } // For saving doc = resolved; baseDoc = current; deletion = resolved.IsDeleted; return(null); }); if (retVal != null) { return(retVal); } } }
public void TestConflict() { RunTestVariants(() => { if (!IsRevTrees()) { return; } var body2 = JSON2Fleece("{\"ok\":\"go\"}"); var body3 = JSON2Fleece("{\"ubu\":\"roi\"}"); CreateRev(DocID.CreateString(), RevID, FleeceBody); CreateRev(DocID.CreateString(), Rev2ID, (FLSlice)body2, C4RevisionFlags.KeepBody); CreateRev(DocID.CreateString(), FLSlice.Constant("3-aaaaaa"), (FLSlice)body3); LiteCoreBridge.Check(err => Native.c4db_beginTransaction(Db, err)); try { // "Pull" a conflicting revision: var history = new FLSlice[] { FLSlice.Constant("4-dddd"), FLSlice.Constant("3-ababab"), Rev2ID }; fixed(FLSlice * history_ = history) { var rq = new C4DocPutRequest { existingRevision = true, docID = DocID, history = history_, historyCount = 3, allowConflict = true, body = (FLSlice)body3, save = true, remoteDBID = 1 }; C4Error error; var doc = Native.c4doc_put(Db, &rq, null, &error); ((IntPtr)doc).Should().NotBe(IntPtr.Zero); Native.FLSliceResult_Release(body2); Native.FLSliceResult_Release(body3); Native.c4doc_selectCommonAncestorRevision(doc, "3-aaaaaa", "4-dddd").Should().BeTrue(); doc->selectedRev.revID.CreateString().Should().Be(Rev2ID.CreateString()); Native.c4doc_selectCommonAncestorRevision(doc, "4-dddd", "3-aaaaaa").Should().BeTrue(); doc->selectedRev.revID.CreateString().Should().Be(Rev2ID.CreateString()); Native.c4doc_selectCommonAncestorRevision(doc, "3-ababab", "3-aaaaaa").Should().BeTrue(); doc->selectedRev.revID.CreateString().Should().Be(Rev2ID.CreateString()); Native.c4doc_selectCommonAncestorRevision(doc, "3-aaaaaa", "3-ababab").Should().BeTrue(); doc->selectedRev.revID.CreateString().Should().Be(Rev2ID.CreateString()); Native.c4doc_selectCommonAncestorRevision(doc, Rev2ID.CreateString(), "3-aaaaaa").Should().BeTrue(); doc->selectedRev.revID.CreateString().Should().Be(Rev2ID.CreateString()); Native.c4doc_selectCommonAncestorRevision(doc, "3-aaaaaa", Rev2ID.CreateString()).Should().BeTrue(); doc->selectedRev.revID.CreateString().Should().Be(Rev2ID.CreateString()); NativeRaw.c4doc_selectCommonAncestorRevision(doc, Rev2ID, Rev2ID).Should().BeTrue(); doc->selectedRev.revID.CreateString().Should().Be(Rev2ID.CreateString()); } } finally { LiteCoreBridge.Check(err => Native.c4db_endTransaction(Db, true, err)); } LiteCoreBridge.Check(err => Native.c4db_beginTransaction(Db, err)); try { var doc = (C4Document *)LiteCoreBridge.Check(err => Native.c4doc_get(Db, DocID.CreateString(), true, err)); LiteCoreBridge.Check(err => Native.c4doc_resolveConflict(doc, "4-dddd", "3-aaaaaa", Encoding.UTF8.GetBytes("{\"merged\":true}"), 0, err)); Native.c4doc_selectCurrentRevision(doc); doc->selectedRev.revID.CreateString().Should().Be("5-940fe7e020dbf8db0f82a5d764870c4b6c88ae99"); doc->selectedRev.body.CreateString().Should().Be("{\"merged\":true}"); Native.c4doc_selectParentRevision(doc); doc->selectedRev.revID.CreateString().Should().Be("4-dddd"); } finally { LiteCoreBridge.Check(err => Native.c4db_endTransaction(Db, false, err)); } LiteCoreBridge.Check(err => Native.c4db_beginTransaction(Db, err)); try { var doc = (C4Document *)LiteCoreBridge.Check(err => Native.c4doc_get(Db, DocID.CreateString(), true, err)); LiteCoreBridge.Check(err => Native.c4doc_resolveConflict(doc, "3-aaaaaa", "4-dddd", Encoding.UTF8.GetBytes("{\"merged\":true}"), 0, err)); Native.c4doc_selectCurrentRevision(doc); doc->selectedRev.revID.CreateString().Should().Be("4-333ee0677b5f1e1e5064b050d417a31d2455dc30"); doc->selectedRev.body.CreateString().Should().Be("{\"merged\":true}"); Native.c4doc_selectParentRevision(doc); doc->selectedRev.revID.CreateString().Should().Be("3-aaaaaa"); } finally { LiteCoreBridge.Check(err => Native.c4db_endTransaction(Db, false, err)); } }); }
internal void ResolveConflict([NotNull] string docID, [NotNull] IConflictResolver resolver) { Debug.Assert(docID != null); Document doc = null, otherDoc = null, baseDoc = null; var inConflict = true; while (inConflict) { ThreadSafety.DoLocked(() => { LiteCoreBridge.Check(err => Native.c4db_beginTransaction(_c4db, err)); try { doc = new Document(this, docID); if (!doc.Exists) { doc.Dispose(); return; } otherDoc = new Document(this, docID); if (!otherDoc.Exists) { doc.Dispose(); otherDoc.Dispose(); return; } otherDoc.SelectConflictingRevision(); baseDoc = new Document(this, docID); if (!baseDoc.SelectCommonAncestor(doc, otherDoc) || baseDoc.ToDictionary() == null) { baseDoc.Dispose(); baseDoc = null; } LiteCoreBridge.Check(err => Native.c4db_endTransaction(_c4db, true, err)); } catch (Exception) { doc?.Dispose(); otherDoc?.Dispose(); baseDoc?.Dispose(); LiteCoreBridge.Check(err => Native.c4db_endTransaction(_c4db, false, err)); } }); var conflict = new Conflict(doc, otherDoc, baseDoc); var logID = new SecureLogString(doc.Id, LogMessageSensitivity.PotentiallyInsecure); Log.To.Database.I(Tag, $"Resolving doc '{logID}' with {resolver.GetType().Name} (mine={doc.RevID}, theirs={otherDoc.RevID}, base={baseDoc?.RevID})"); Document resolved = null; try { resolved = resolver.Resolve(conflict); if (resolved == null) { throw new LiteCoreException(new C4Error(C4ErrorCode.Conflict)); } SaveResolvedDocument(resolved, conflict); inConflict = false; } catch (LiteCoreException e) { if (e.Error.domain == C4ErrorDomain.LiteCoreDomain && e.Error.code == (int)C4ErrorCode.Conflict) { continue; } throw; } finally { resolved?.Dispose(); if (resolved != doc) { doc?.Dispose(); } if (resolved != otherDoc) { otherDoc?.Dispose(); } if (resolved != baseDoc) { baseDoc?.Dispose(); } } } }
public void TestCreateVersionedDoc() { RunTestVariants(() => { // Try reading doc with mustExist=true, which should fail: C4Error error; C4Document *doc = NativeRaw.c4doc_get(Db, DocID, true, &error); ((long)doc).Should().Be(0, "because the document does not exist"); error.domain.Should().Be(C4ErrorDomain.LiteCoreDomain); error.code.Should().Be((int)C4ErrorCode.NotFound); Native.c4doc_free(doc); // Now get the doc with mustExist=false, which returns an empty doc: doc = (C4Document *)LiteCoreBridge.Check(err => NativeRaw.c4doc_get(Db, DocID, false, err)); ((int)doc->flags).Should().Be(0, "because the document is empty"); doc->docID.Equals(DocID).Should().BeTrue("because the doc ID should match what was stored"); ((long)doc->revID.buf).Should().Be(0, "because the doc has no revision ID yet"); ((long)doc->selectedRev.revID.buf).Should().Be(0, "because the doc has no revision ID yet"); Native.c4doc_free(doc); LiteCoreBridge.Check(err => Native.c4db_beginTransaction(Db, err)); try { var tmp = RevID; var rq = new C4DocPutRequest { existingRevision = true, docID = DocID, history = &tmp, historyCount = 1, body = FleeceBody, save = true }; doc = (C4Document *)LiteCoreBridge.Check(err => { var localRq = rq; return(Native.c4doc_put(Db, &localRq, null, err)); }); doc->revID.Equals(RevID).Should().BeTrue("because the doc should have the stored revID"); doc->selectedRev.revID.Equals(RevID).Should().BeTrue("because the doc should have the stored revID"); doc->selectedRev.flags.Should().Be(C4RevisionFlags.Leaf, "because this is a leaf revision"); doc->selectedRev.body.Equals(FleeceBody).Should().BeTrue("because the body should be stored correctly"); Native.c4doc_free(doc); } finally { LiteCoreBridge.Check(err => Native.c4db_endTransaction(Db, true, err)); } // Reload the doc: doc = (C4Document *)LiteCoreBridge.Check(err => NativeRaw.c4doc_get(Db, DocID, true, err)); doc->flags.Should().Be(C4DocumentFlags.DocExists, "because this is an existing document"); doc->docID.Equals(DocID).Should().BeTrue("because the doc should have the stored doc ID"); doc->revID.Equals(RevID).Should().BeTrue("because the doc should have the stored rev ID"); doc->selectedRev.revID.Equals(RevID).Should().BeTrue("because the doc should have the stored rev ID"); doc->selectedRev.sequence.Should().Be(1, "because it is the first stored document"); doc->selectedRev.body.Equals(FleeceBody).Should().BeTrue("because the doc should have the stored body"); Native.c4doc_free(doc); // Get the doc by its sequence doc = (C4Document *)LiteCoreBridge.Check(err => Native.c4doc_getBySequence(Db, 1, err)); doc->flags.Should().Be(C4DocumentFlags.DocExists, "because this is an existing document"); doc->docID.Equals(DocID).Should().BeTrue("because the doc should have the stored doc ID"); doc->revID.Equals(RevID).Should().BeTrue("because the doc should have the stored rev ID"); doc->selectedRev.revID.Equals(RevID).Should().BeTrue("because the doc should have the stored rev ID"); doc->selectedRev.sequence.Should().Be(1, "because it is the first stored document"); doc->selectedRev.body.Equals(FleeceBody).Should().BeTrue("because the doc should have the stored body"); Native.c4doc_free(doc); }); }
public void TestDatabaseCopy() { RunTestVariants(() => { var doc1ID = "doc001"; var doc2ID = "doc002"; CreateRev(doc1ID, RevID, Body); CreateRev(doc2ID, RevID, Body); var srcPath = Native.c4db_getPath(Db); var destPath = Path.Combine(Path.GetTempPath(), $"nudb.cblite2{Path.DirectorySeparatorChar}"); C4Error error; var config = *(Native.c4db_getConfig(Db)); if (!Native.c4db_deleteAtPath(destPath, &error)) { error.code.Should().Be(0); } LiteCoreBridge.Check(err => { var localConfig = config; return(Native.c4db_copy(srcPath, destPath, &localConfig, err)); }); var nudb = (C4Database *)LiteCoreBridge.Check(err => { var localConfig = config; return(Native.c4db_open(destPath, &localConfig, err)); }); try { Native.c4db_getDocumentCount(nudb).Should().Be(2L, "because the database was seeded"); LiteCoreBridge.Check(err => Native.c4db_delete(nudb, err)); } finally { Native.c4db_free(nudb); } nudb = (C4Database *)LiteCoreBridge.Check(err => { var localConfig = config; return(Native.c4db_open(destPath, &localConfig, err)); }); try { CreateRev(nudb, doc1ID, RevID, Body); Native.c4db_getDocumentCount(nudb).Should().Be(1L, "because a document was inserted"); } finally { Native.c4db_free(nudb); } var originalDest = destPath; destPath = Path.Combine(Path.GetTempPath(), "bogus", $"nudb.cblite2{Path.DirectorySeparatorChar}"); Action a = () => LiteCoreBridge.Check(err => { var localConfig = config; return(Native.c4db_copy(srcPath, destPath, &localConfig, err)); }); a.ShouldThrow <CouchbaseLiteException>().Where(e => e.Error == CouchbaseLiteError.NotFound && e.Domain == CouchbaseLiteErrorType.CouchbaseLite); nudb = (C4Database *)LiteCoreBridge.Check(err => { var localConfig = config; return(Native.c4db_open(originalDest, &localConfig, err)); }); try { Native.c4db_getDocumentCount(nudb).Should().Be(1L, "because the original database should remain"); } finally { Native.c4db_free(nudb); } var originalSrc = srcPath; srcPath = $"{srcPath}bogus{Path.DirectorySeparatorChar}"; destPath = originalDest; a.ShouldThrow <CouchbaseLiteException>().Where(e => e.Error == CouchbaseLiteError.NotFound && e.Domain == CouchbaseLiteErrorType.CouchbaseLite); nudb = (C4Database *)LiteCoreBridge.Check(err => { var localConfig = config; return(Native.c4db_open(destPath, &localConfig, err)); }); try { Native.c4db_getDocumentCount(nudb).Should().Be(1L, "because the original database should remain"); } finally { Native.c4db_free(nudb); } srcPath = originalSrc; a.ShouldThrow <CouchbasePosixException>().Where(e => e.Error == PosixBase.GetCode(nameof(PosixBase.EEXIST)) && e.Domain == CouchbaseLiteErrorType.POSIX); nudb = (C4Database *)LiteCoreBridge.Check(err => { var localConfig = config; return(Native.c4db_open(destPath, &localConfig, err)); }); try { Native.c4db_getDocumentCount(nudb).Should().Be(1L, "because the database copy failed"); LiteCoreBridge.Check(err => Native.c4db_delete(nudb, err)); } finally { Native.c4db_free(nudb); } }); }
public void TestPurge() { RunTestVariants(() => { var body2 = JSON2Fleece("{\"ok\":\"go\"}"); var body3 = JSON2Fleece("{\"ubu\":\"roi\"}"); CreateRev(DocID.CreateString(), RevID, FleeceBody); CreateRev(DocID.CreateString(), Rev2ID, (FLSlice)body2); CreateRev(DocID.CreateString(), Rev3ID, (FLSlice)body3); var history = new[] { FLSlice.Constant("3-ababab"), Rev2ID }; fixed(FLSlice * history_ = history) { var rq = new C4DocPutRequest { existingRevision = true, docID = DocID, history = history_, historyCount = 2, allowConflict = true, body = (FLSlice)body3, save = true }; C4Error error; C4Document *doc = null; LiteCoreBridge.Check(err => Native.c4db_beginTransaction(Db, err)); try { doc = Native.c4doc_put(Db, &rq, null, &error); ((IntPtr)doc).Should().NotBe(IntPtr.Zero); Native.c4doc_free(doc); } finally { LiteCoreBridge.Check(err => Native.c4db_endTransaction(Db, true, err)); } LiteCoreBridge.Check(err => Native.c4db_beginTransaction(Db, err)); try { LiteCoreBridge.Check(err => NativeRaw.c4db_purgeDoc(Db, DocID, err)); } finally { LiteCoreBridge.Check(err => Native.c4db_endTransaction(Db, true, err)); } Native.c4db_getDocumentCount(Db).Should().Be(0UL); CreateRev(DocID.CreateString(), RevID, FleeceBody); CreateRev(DocID.CreateString(), Rev2ID, (FLSlice)body2); CreateRev(DocID.CreateString(), Rev3ID, (FLSlice)body3); LiteCoreBridge.Check(err => Native.c4db_beginTransaction(Db, err)); try { doc = Native.c4doc_put(Db, &rq, null, &error); ((IntPtr)doc).Should().NotBe(IntPtr.Zero); } finally { LiteCoreBridge.Check(err => Native.c4db_endTransaction(Db, true, err)); } LiteCoreBridge.Check(err => Native.c4db_beginTransaction(Db, err)); try { LiteCoreBridge.Check(err => Native.c4doc_purgeRevision(doc, null, err)); LiteCoreBridge.Check(err => Native.c4doc_save(doc, 0, err)); } finally { Native.c4doc_free(doc); LiteCoreBridge.Check(err => Native.c4db_endTransaction(Db, true, err)); } Native.c4db_getDocumentCount(Db).Should().Be(0UL); Native.FLSliceResult_Release(body2); Native.FLSliceResult_Release(body3); } }); }
public void TestImportNames() { // Docs look like: // {"name":{"first":"Travis","last":"Mutchler"},"gender":"female","birthday":"1990-12-21","contact":{"address":{"street":"22 Kansas Cir","zip":"45384","city":"Wilberforce","state":"OH"},"email":["*****@*****.**","*****@*****.**"],"region":"937","phone":["937-3512486"]},"likes":["travelling"],"memberSince":"2010-01-01"} RunTestVariants(() => { var numDocs = ImportJSONLines("/Couchbase/example-datasets-master/RandomUsers/names_300000.json", TimeSpan.FromSeconds(15), true); var complete = numDocs == 300000; #if !DEBUG numDocs.Should().Be(300000, "because otherwise the operation was too slow"); #endif { var st = Stopwatch.StartNew(); var totalLikes = IndexLikesView(); Console.WriteLine($"Total of {totalLikes} likes"); st.PrintReport("Indexing Likes view", numDocs, "doc"); if (complete) { totalLikes.Should().Be(345986, "because otherwise the index missed data set objects"); } } { var st = Stopwatch.StartNew(); var context = new CountContext(); using (var reduce = new C4ManagedReduceFunction(CountAccumulate, CountReduce, context)) { var numLikes = QueryGrouped(_likesView, reduce.Native, true); st.PrintReport("Querying all likes", numLikes, "like"); if (complete) { numLikes.Should().Be(15, "because that is the number of likes in the data set"); } } } { var st = Stopwatch.StartNew(); var total = IndexStatesView(); st.PrintReport("Indexing States view", numDocs, "doc"); if (complete) { total.Should().Be(300000, "because otherwise the index missed some dataset objects"); } } { var options = C4QueryOptions.Default; var key = Native.c4key_new(); NativeRaw.c4key_addString(key, C4Slice.Constant("WA")); options.startKey = options.endKey = key; var st = Stopwatch.StartNew(); var total = RunQuery(_statesView, options); Native.c4key_free(key); st.PrintReport("Querying States view", total, "row"); if (complete) { total.Should().Be(5053, "because that is the number of states in the data set"); } } { if (Storage == C4StorageEngine.SQLite && !IsRevTrees()) { for (int pass = 0; pass < 2; ++pass) { var st = Stopwatch.StartNew(); var n = QueryWhere("{\"contact.address.state\": \"WA\"}"); st.PrintReport("SQL query of state", n, "doc"); if (complete) { n.Should().Be(5053, "because that is the number of states in the data set"); } if (pass == 0) { var st2 = Stopwatch.StartNew(); LiteCoreBridge.Check(err => Native.c4db_createIndex(Db, "contact.address.state", err)); st2.PrintReport("Creating SQL index of state", 1, "index"); } } } } }); }
public void TestMaxRevTreeDepth() { RunTestVariants(() => { if (IsRevTrees()) { Native.c4db_getMaxRevTreeDepth(Db).Should().Be(20, "because that is the default"); Native.c4db_setMaxRevTreeDepth(Db, 30U); Native.c4db_getMaxRevTreeDepth(Db).Should().Be(30); ReopenDB(); Native.c4db_getMaxRevTreeDepth(Db).Should().Be(30, "because the value should be persistent"); } const uint NumRevs = 10000; var st = Stopwatch.StartNew(); var doc = (C4Document *)LiteCoreBridge.Check(err => NativeRaw.c4doc_get(Db, DocID, false, err)); LiteCoreBridge.Check(err => Native.c4db_beginTransaction(Db, err)); try { for (uint i = 0; i < NumRevs; i++) { var rq = new C4DocPutRequest(); rq.docID = doc->docID; rq.history = (FLSlice *)&doc->revID; rq.historyCount = 1; rq.body = FleeceBody; rq.save = true; var savedDoc = (C4Document *)LiteCoreBridge.Check(err => { var localPut = rq; return(Native.c4doc_put(Db, &localPut, null, err)); }); Native.c4doc_free(doc); doc = savedDoc; } } finally { LiteCoreBridge.Check(err => Native.c4db_endTransaction(Db, true, err)); } st.Stop(); WriteLine($"Created {NumRevs} revisions in {st.ElapsedMilliseconds} ms"); uint nRevs = 0; Native.c4doc_selectCurrentRevision(doc); do { if (IsRevTrees()) { NativeRaw.c4rev_getGeneration(doc->selectedRev.revID).Should() .Be(NumRevs - nRevs, "because the tree should be pruned"); } ++nRevs; } while (Native.c4doc_selectParentRevision(doc)); WriteLine($"Document rev tree depth is {nRevs}"); if (IsRevTrees()) { nRevs.Should().Be(30, "because the tree should be pruned"); } Native.c4doc_free(doc); }); }
private void IndexViews() { var nameKey = Native.FLDictKey_Init("Name", true); var albumKey = Native.FLDictKey_Init("Album", true); var artistKey = Native.FLDictKey_Init("Artist", true); var timeKey = Native.FLDictKey_Init("Total Time", true); var trackNoKey = Native.FLDictKey_Init("Track Number", true); var compKey = Native.FLDictKey_Init("Compilation", true); var enc = Native.FLEncoder_New(); var key = Native.c4key_new(); C4Error error; if (_artistsView == null) { var config = Native.c4db_getConfig(Db); _artistsView = (C4View *)LiteCoreBridge.Check(err => Native.c4view_open(Db, null, "Artists", "1", Native.c4db_getConfig(Db), err)); } if (_albumsView == null) { _albumsView = (C4View *)LiteCoreBridge.Check(err => Native.c4view_open(Db, null, "Albums", "1", Native.c4db_getConfig(Db), err)); } var views = new[] { _artistsView, _albumsView }; var indexer = (C4Indexer *)LiteCoreBridge.Check(err => Native.c4indexer_begin(Db, views, err)); var e = (C4DocEnumerator *)LiteCoreBridge.Check(err => Native.c4indexer_enumerateDocuments(indexer, err)); while (Native.c4enum_next(e, &error)) { var doc = Native.c4enum_getDocument(e, &error); var body = Native.FLValue_AsDict(NativeRaw.FLValue_FromTrustedData((FLSlice)doc->selectedRev.body)); ((long)body).Should().NotBe(0, "because otherwise the data got corrupted somehow"); FLSlice artist; if (Native.FLValue_AsBool(Native.FLDict_GetWithKey(body, &compKey))) { artist = FLSlice.Constant("-Compilations-"); } else { artist = NativeRaw.FLValue_AsString(Native.FLDict_GetWithKey(body, &artistKey)); } var name = NativeRaw.FLValue_AsString(Native.FLDict_GetWithKey(body, &nameKey)); var album = Native.FLValue_AsString(Native.FLDict_GetWithKey(body, &albumKey)); var trackNo = Native.FLValue_AsInt(Native.FLDict_GetWithKey(body, &trackNoKey)); var time = Native.FLDict_GetWithKey(body, &timeKey); // Generate Value: Native.FLEncoder_WriteValue(enc, time); FLError flError; var fval = NativeRaw.FLEncoder_Finish(enc, &flError); Native.FLEncoder_Reset(enc); ((long)fval.buf).Should().NotBe(0, "because otherwise the encoding failed"); var value = (C4Slice)fval; // Emit to artists view: uint nKeys = 0; if (!artist.Equals(FLSlice.Null) && !name.Equals(FLSlice.Null)) { nKeys = 1; // Generate key: Native.c4key_beginArray(key); NativeRaw.c4key_addString(key, (C4Slice)artist); if (album != null) { Native.c4key_addString(key, album); } else { Native.c4key_addNull(key); } Native.c4key_addNumber(key, trackNo); NativeRaw.c4key_addString(key, (C4Slice)name); Native.c4key_addNumber(key, 1.0); Native.c4key_endArray(key); } Native.c4indexer_emit(indexer, doc, 0, nKeys, new[] { key }, new[] { value }, &error).Should() .BeTrue("because otherwise the emit to the artists view failed"); Native.c4key_reset(key); // Emit to albums view: nKeys = 0; if (album != null) { nKeys = 1; Native.c4key_beginArray(key); Native.c4key_addString(key, album); if (!artist.Equals(FLSlice.Null)) { NativeRaw.c4key_addString(key, (C4Slice)artist); } else { Native.c4key_addNull(key); } Native.c4key_addNumber(key, trackNo); if (name.buf == null) { name = FLSlice.Constant(""); } NativeRaw.c4key_addString(key, (C4Slice)name); Native.c4key_addNumber(key, 1.0); Native.c4key_endArray(key); } Native.c4indexer_emit(indexer, doc, 1, nKeys, new[] { key }, new[] { value }, &error).Should() .BeTrue("because otherwise the emit to the artists view failed"); Native.c4key_reset(key); Native.FLSliceResult_Free(fval); Native.c4doc_free(doc); } Native.c4enum_free(e); error.Code.Should().Be(0, "because otherwise an error occurred"); Native.c4indexer_end(indexer, true, &error).Should().BeTrue("because otherwise the indexer failed to end"); Native.FLEncoder_Free(enc); Native.c4key_free(key); }
public void TestPut() { RunTestVariants(() => { LiteCoreBridge.Check(err => Native.c4db_beginTransaction(Db, err)); try { // Creating doc given ID: var rq = new C4DocPutRequest { docID = DocID, body = FleeceBody, save = true }; var doc = (C4Document *)LiteCoreBridge.Check(err => { var localRq = rq; return(Native.c4doc_put(Db, &localRq, null, err)); }); doc->docID.Equals(DocID).Should().BeTrue("because the doc should have the correct doc ID"); var expectedRevID = IsRevTrees() ? FLSlice.Constant("1-042ca1d3a1d16fd5ab2f87efc7ebbf50b7498032") : FLSlice.Constant("1@*"); doc->revID.Equals(expectedRevID).Should().BeTrue("because the doc should have the correct rev ID"); doc->flags.Should().Be(C4DocumentFlags.DocExists, "because the document exists"); doc->selectedRev.revID.Equals(expectedRevID).Should().BeTrue("because the selected rev should have the correct rev ID"); Native.c4doc_free(doc); // Update doc: var tmp = new[] { expectedRevID }; var body = JSON2Fleece("{\"ok\":\"go\"}"); rq.body = (FLSlice)body; rq.historyCount = 1; ulong commonAncestorIndex = 0UL; fixed(FLSlice * history = tmp) { rq.history = history; doc = (C4Document *)LiteCoreBridge.Check(err => { var localRq = rq; ulong cai; var retVal = Native.c4doc_put(Db, &localRq, &cai, err); commonAncestorIndex = cai; return(retVal); }); } commonAncestorIndex.Should().Be(0UL, "because there are no common ancestors"); var expectedRev2ID = IsRevTrees() ? FLSlice.Constant("2-201796aeeaa6ddbb746d6cab141440f23412ac51") : FLSlice.Constant("2@*"); doc->revID.Equals(expectedRev2ID).Should().BeTrue("because the doc should have the updated rev ID"); doc->flags.Should().Be(C4DocumentFlags.DocExists, "because the document exists"); doc->selectedRev.revID.Equals(expectedRev2ID).Should().BeTrue("because the selected rev should have the correct rev ID"); Native.c4doc_free(doc); // Insert existing rev that conflicts: Native.FLSliceResult_Release(body); body = JSON2Fleece("{\"from\":\"elsewhere\"}"); rq.body = (FLSlice)body; rq.existingRevision = true; rq.remoteDBID = 1; var conflictRevID = IsRevTrees() ? FLSlice.Constant("2-deadbeef") : FLSlice.Constant("1@binky"); tmp = new[] { conflictRevID, expectedRevID }; rq.historyCount = 2; rq.allowConflict = true; fixed(FLSlice * history = tmp) { rq.history = history; doc = (C4Document *)LiteCoreBridge.Check(err => { var localRq = rq; ulong cai; var retVal = Native.c4doc_put(Db, &localRq, &cai, err); commonAncestorIndex = cai; return(retVal); }); } commonAncestorIndex.Should().Be(1UL, "because the common ancestor is at sequence 1"); doc->flags.Should().Be(C4DocumentFlags.DocExists | C4DocumentFlags.DocConflicted, "because the document exists"); doc->selectedRev.revID.Equals(conflictRevID).Should().BeTrue("because the selected rev should have the correct rev ID"); doc->revID.Equals(expectedRev2ID).Should().BeTrue("because the conflicting rev should never be the default"); Native.FLSliceResult_Release(body); Native.c4doc_free(doc); } finally { LiteCoreBridge.Check(err => Native.c4db_endTransaction(Db, true, err)); } }); }
public void TestCRUD() { RunTestVariants(() => { if (!IsRevTrees(Db)) { return; } var body = JSON2Fleece("{'foo':1, 'bar':false}"); var updatedBody = JSON2Fleece("{'foo':1, 'bar':false, 'status': 'updated!'}"); // TODO: Observer C4Error error; var doc = Native.c4db_getDoc(Db, "nonexistent", true, C4DocContentLevel.DocGetAll, &error); ((IntPtr)doc).Should().Be(IntPtr.Zero, "because it does not exist"); error.domain.Should().Be(C4ErrorDomain.LiteCoreDomain); error.code.Should().Be((int)C4ErrorCode.NotFound); // KeepBody => Revision's body should not be discarded when non-leaf doc = PutDoc(null, null, (FLSlice)body, C4RevisionFlags.KeepBody); doc->docID.size.Should().BeGreaterOrEqualTo(10, "because otherwise no docID was created"); var docID = doc->docID.CreateString(); var revID1 = doc->revID.CreateString(); revID1.Should().StartWith("1-", "because otherwise the generation is invalid"); Native.c4doc_release(doc); doc = (C4Document *)LiteCoreBridge.Check(err => Native.c4db_getDoc(Db, docID, true, C4DocContentLevel.DocGetAll, err)); doc->docID.CreateString().Should().Be(docID); doc->selectedRev.revID.CreateString().Should().Be(revID1); Native.FLSlice_Equal(NativeRaw.c4doc_getRevisionBody(doc), (FLSlice)body).Should().BeTrue(); Native.c4doc_release(doc); doc = PutDoc(docID, revID1, (FLSlice)updatedBody, C4RevisionFlags.KeepBody); doc->docID.CreateString().Should().Be(docID); Native.FLSlice_Equal(NativeRaw.c4doc_getRevisionBody(doc), (FLSlice)updatedBody).Should().BeTrue(); var revID2 = doc->revID.CreateString(); revID2.Should().StartWith("2-", "because otherwise the generation is invalid"); Native.c4doc_release(doc); Native.FLSliceResult_Release(body); error = new C4Error(C4ErrorCode.Conflict); PutDocMustFail(docID, revID1, (FLSlice)updatedBody, C4RevisionFlags.KeepBody, error); var e = (C4DocEnumerator *)LiteCoreBridge.Check(err => { var options = C4EnumeratorOptions.Default; return(Native.c4db_enumerateChanges(Db, 0, &options, err)); }); var seq = 2UL; while (null != (doc = c4enum_nextDocument(e, &error))) { doc->selectedRev.sequence.Should().Be(seq); doc->selectedRev.revID.CreateString().Should().Be(revID2); doc->docID.CreateString().Should().Be(docID); Native.c4doc_release(doc); seq++; } seq.Should().Be(3UL); Native.c4enum_free(e); Native.FLSliceResult_Release(updatedBody); // NOTE: Filter is out of LiteCore scope error = new C4Error(C4ErrorCode.InvalidParameter); PutDocMustFail(docID, null, FLSlice.Null, C4RevisionFlags.Deleted, error); doc = PutDoc(docID, revID2, FLSlice.Null, C4RevisionFlags.Deleted); doc->flags.Should().Be(C4DocumentFlags.DocExists | C4DocumentFlags.DocDeleted); doc->docID.CreateString().Should().Be(docID); var revID3 = doc->revID.CreateString(); revID3.Should().StartWith("3-", "because otherwise the generation is invalid"); Native.c4doc_release(doc); doc = (C4Document *)LiteCoreBridge.Check(err => Native.c4db_getDoc(Db, docID, true, C4DocContentLevel.DocGetAll, err)); doc->docID.CreateString().Should().Be(docID); doc->revID.CreateString().Should().Be(revID3); doc->flags.Should().Be(C4DocumentFlags.DocExists | C4DocumentFlags.DocDeleted); doc->selectedRev.revID.CreateString().Should().Be(revID3); NativeRaw.c4doc_getRevisionBody(doc).CreateString().Should().NotBeNull("because a valid revision should have a valid body"); doc->selectedRev.flags.Should().Be(C4RevisionFlags.Leaf | C4RevisionFlags.Deleted); Native.c4doc_release(doc); PutDocMustFail("fake", null, FLSlice.Null, C4RevisionFlags.Deleted, error); e = (C4DocEnumerator *)LiteCoreBridge.Check(err => { var options = C4EnumeratorOptions.Default; return(Native.c4db_enumerateChanges(Db, 0, &options, err)); }); seq = 3UL; while (null != (doc = c4enum_nextDocument(e, &error))) { Native.c4doc_release(doc); seq++; } seq.Should().Be(3UL, "because deleted documents were not included"); Native.c4enum_free(e); e = (C4DocEnumerator *)LiteCoreBridge.Check(err => { var options = C4EnumeratorOptions.Default; options.flags |= C4EnumeratorFlags.IncludeDeleted; return(Native.c4db_enumerateChanges(Db, 0, &options, err)); }); seq = 3UL; while (null != (doc = c4enum_nextDocument(e, &error))) { doc->selectedRev.sequence.Should().Be(seq); doc->selectedRev.revID.CreateString().Should().Be(revID3); doc->docID.CreateString().Should().Be(docID); Native.c4doc_release(doc); seq++; } seq.Should().Be(4UL, "because deleted documents were included"); Native.c4enum_free(e); doc = (C4Document *)LiteCoreBridge.Check(err => Native.c4db_getDoc(Db, docID, true, C4DocContentLevel.DocGetAll, err)); var latest = 3; do { switch (latest) { case 3: doc->selectedRev.revID.CreateString().Should().Be(revID3); break; case 2: doc->selectedRev.revID.CreateString().Should().Be(revID2); break; case 1: doc->selectedRev.revID.CreateString().Should().Be(revID1); break; default: throw new InvalidOperationException("Invalid switch portion reached"); } latest--; } while (Native.c4doc_selectParentRevision(doc)); latest.Should().Be(0, "because otherwise the history is not valid"); Native.c4doc_release(doc); doc = (C4Document *)LiteCoreBridge.Check(err => Native.c4db_getDoc(Db, docID, true, C4DocContentLevel.DocGetAll, err)); LiteCoreBridge.Check(err => Native.c4doc_selectRevision(doc, revID2, true, err)); doc->selectedRev.revID.CreateString().Should().Be(revID2); Native.c4doc_release(doc); LiteCoreBridge.Check(err => Native.c4db_maintenance(Db, C4MaintenanceType.Compact, err)); doc = (C4Document *)LiteCoreBridge.Check(err => Native.c4db_getDoc(Db, docID, true, C4DocContentLevel.DocGetAll, err)); LiteCoreBridge.Check(err => Native.c4doc_selectRevision(doc, revID2, true, err)); doc->selectedRev.revID.CreateString().Should().Be(revID2); // doc->selectedRev.body.CreateString().Should().BeNull("because the database was compacted"); Native.c4doc_release(doc); // Check history again after compaction doc = (C4Document *)LiteCoreBridge.Check(err => Native.c4db_getDoc(Db, docID, true, C4DocContentLevel.DocGetAll, err)); latest = 3; do { switch (latest) { case 3: doc->selectedRev.revID.CreateString().Should().Be(revID3); break; case 2: doc->selectedRev.revID.CreateString().Should().Be(revID2); break; case 1: doc->selectedRev.revID.CreateString().Should().Be(revID1); break; default: throw new InvalidOperationException("Invalid switch portion reached"); } latest--; } while (Native.c4doc_selectParentRevision(doc)); latest.Should().Be(0, "because otherwise the history is not valid"); Native.c4doc_release(doc); }); }
public void TestUpdate() { RunTestVariants(() => { WriteLine("Begin test"); C4Document *doc = null; LiteCoreBridge.Check(err => Native.c4db_beginTransaction(Db, err)); try { WriteLine("Begin create"); doc = (C4Document *)LiteCoreBridge.Check(err => NativeRaw.c4doc_create(Db, DocID, FleeceBody, 0, err)); } finally { LiteCoreBridge.Check(err => Native.c4db_endTransaction(Db, true, err)); } WriteLine("After save"); var expectedRevID = IsRevTrees() ? FLSlice.Constant("1-042ca1d3a1d16fd5ab2f87efc7ebbf50b7498032") : FLSlice.Constant("1@*"); doc->revID.Equals(expectedRevID).Should().BeTrue(); doc->flags.Should().Be(C4DocumentFlags.DocExists, "because the document was saved"); doc->selectedRev.revID.Equals(expectedRevID).Should().BeTrue(); doc->docID.Equals(DocID).Should().BeTrue("because that is the document ID that it was saved with"); // Read the doc into another C4Document var doc2 = (C4Document *)LiteCoreBridge.Check(err => NativeRaw.c4doc_get(Db, DocID, false, err)); doc->revID.Equals(expectedRevID).Should() .BeTrue("because the other reference should have the same rev ID"); for (int i = 2; i <= 5; i++) { LiteCoreBridge.Check(err => Native.c4db_beginTransaction(Db, err)); try { WriteLine($"Begin save #{i}"); var body = JSON2Fleece("{\"ok\":\"go\"}"); var oldRevID = doc->revID; var updatedDoc = (C4Document *)LiteCoreBridge.Check( err => NativeRaw.c4doc_update(doc, (FLSlice)body, 0, err)); doc->selectedRev.revID.Equals(oldRevID).Should().BeTrue(); doc->revID.Equals(oldRevID).Should().BeTrue(); Native.c4doc_free(doc); doc = updatedDoc; Native.FLSliceResult_Release(body); } finally { LiteCoreBridge.Check(err => Native.c4db_endTransaction(Db, true, err)); } } WriteLine("After multiple updates"); var expectedRev2ID = IsRevTrees() ? FLSlice.Constant("5-a452899fa8e69b06d936a5034018f6fff0a8f906") : FLSlice.Constant("5@*"); doc->revID.Equals(expectedRev2ID).Should().BeTrue(); doc->selectedRev.revID.Equals(expectedRev2ID).Should().BeTrue(); LiteCoreBridge.Check(err => Native.c4db_beginTransaction(Db, err)); try { WriteLine("Begin conflicting save"); C4Error error; var body = JSON2Fleece("{\"ok\":\"no way\"}"); ((long)NativeRaw.c4doc_update(doc2, (FLSlice)body, 0, &error)).Should().Be(0, "because this is a conflict"); error.code.Should().Be((int)C4ErrorCode.Conflict); error.domain.Should().Be(C4ErrorDomain.LiteCoreDomain); Native.FLSliceResult_Release(body); } finally { LiteCoreBridge.Check(err => Native.c4db_endTransaction(Db, true, err)); } LiteCoreBridge.Check(err => Native.c4db_beginTransaction(Db, err)); try { WriteLine("Begin conflicting create"); C4Error error; var body = JSON2Fleece("{\"ok\":\"no way\"}"); ((long)NativeRaw.c4doc_create(Db, DocID, (FLSlice)body, 0, &error)).Should().Be(0, "because this is a conflict"); error.code.Should().Be((int)C4ErrorCode.Conflict); error.domain.Should().Be(C4ErrorDomain.LiteCoreDomain); Native.FLSliceResult_Release(body); } finally { LiteCoreBridge.Check(err => Native.c4db_endTransaction(Db, true, err)); } Native.c4doc_free(doc); Native.c4doc_free(doc2); }); }
protected override void TeardownVariant(int options) { LiteCoreBridge.Check(err => Native.c4blob_deleteStore(_store, err)); }
public void TestConflict() { RunTestVariants(() => { if (!IsRevTrees()) { return; } var body2 = JSON2Fleece("{\"ok\":\"go\"}"); var body3 = JSON2Fleece("{\"ubu\":\"roi\"}"); CreateRev(DocID.CreateString(), RevID, FleeceBody); CreateRev(DocID.CreateString(), Rev2ID, (FLSlice)body2, C4RevisionFlags.KeepBody); CreateRev(DocID.CreateString(), FLSlice.Constant("3-aaaaaa"), (FLSlice)body3); LiteCoreBridge.Check(err => Native.c4db_beginTransaction(Db, err)); try { // "Pull" a conflicting revision: var history = new FLSlice[] { FLSlice.Constant("4-dddd"), FLSlice.Constant("3-ababab"), Rev2ID }; fixed(FLSlice * history_ = history) { var rq = new C4DocPutRequest { existingRevision = true, docID = DocID, history = history_, historyCount = 3, allowConflict = true, body = (FLSlice)body3, save = true, remoteDBID = 1 }; C4Error error; var doc = Native.c4doc_put(Db, &rq, null, &error); ((IntPtr)doc).Should().NotBe(IntPtr.Zero); Native.FLSliceResult_Release(body2); Native.FLSliceResult_Release(body3); Native.c4doc_selectCommonAncestorRevision(doc, "3-aaaaaa", "4-dddd").Should().BeTrue(); doc->selectedRev.revID.CreateString().Should().Be(Rev2ID.CreateString()); Native.c4doc_selectCommonAncestorRevision(doc, "4-dddd", "3-aaaaaa").Should().BeTrue(); doc->selectedRev.revID.CreateString().Should().Be(Rev2ID.CreateString()); Native.c4doc_selectCommonAncestorRevision(doc, "3-ababab", "3-aaaaaa").Should().BeTrue(); doc->selectedRev.revID.CreateString().Should().Be(Rev2ID.CreateString()); Native.c4doc_selectCommonAncestorRevision(doc, "3-aaaaaa", "3-ababab").Should().BeTrue(); doc->selectedRev.revID.CreateString().Should().Be(Rev2ID.CreateString()); Native.c4doc_selectCommonAncestorRevision(doc, Rev2ID.CreateString(), "3-aaaaaa").Should().BeTrue(); doc->selectedRev.revID.CreateString().Should().Be(Rev2ID.CreateString()); Native.c4doc_selectCommonAncestorRevision(doc, "3-aaaaaa", Rev2ID.CreateString()).Should().BeTrue(); doc->selectedRev.revID.CreateString().Should().Be(Rev2ID.CreateString()); NativeRaw.c4doc_selectCommonAncestorRevision(doc, Rev2ID, Rev2ID).Should().BeTrue(); doc->selectedRev.revID.CreateString().Should().Be(Rev2ID.CreateString()); } } finally { LiteCoreBridge.Check(err => Native.c4db_endTransaction(Db, true, err)); } var mergedBody = JSON2Fleece("{\"merged\":true}"); LiteCoreBridge.Check(err => Native.c4db_beginTransaction(Db, err)); try { var doc = (C4Document *)LiteCoreBridge.Check(err => Native.c4doc_get(Db, DocID.CreateString(), true, err)); LiteCoreBridge.Check(err => NativeRaw.c4doc_resolveConflict(doc, FLSlice.Constant("4-dddd"), FLSlice.Constant("3-aaaaaa"), (FLSlice)mergedBody, 0, err)); Native.c4doc_selectCurrentRevision(doc); doc->selectedRev.revID.CreateString().Should().Be("5-79b2ecd897d65887a18c46cc39db6f0a3f7b38c4"); doc->selectedRev.body.Equals(mergedBody).Should().BeTrue(); Native.c4doc_selectParentRevision(doc); doc->selectedRev.revID.CreateString().Should().Be("4-dddd"); } finally { LiteCoreBridge.Check(err => Native.c4db_endTransaction(Db, false, err)); } LiteCoreBridge.Check(err => Native.c4db_beginTransaction(Db, err)); try { var doc = (C4Document *)LiteCoreBridge.Check(err => Native.c4doc_get(Db, DocID.CreateString(), true, err)); LiteCoreBridge.Check(err => NativeRaw.c4doc_resolveConflict(doc, FLSlice.Constant("3-aaaaaa"), FLSlice.Constant("4-dddd"), (FLSlice)mergedBody, 0, err)); Native.c4doc_selectCurrentRevision(doc); doc->selectedRev.revID.CreateString().Should().Be("4-1fa2dbcb66b5e0456f6d6fc4a90918d42f3dd302"); doc->selectedRev.body.Equals(mergedBody).Should().BeTrue(); Native.c4doc_selectParentRevision(doc); doc->selectedRev.revID.CreateString().Should().Be("3-aaaaaa"); } finally { LiteCoreBridge.Check(err => Native.c4db_endTransaction(Db, false, err)); } }); }
protected uint ImportJSONFile(string path, string idPrefix, TimeSpan timeout, bool verbose) { WriteLine($"Reading {path} ..."); var st = Stopwatch.StartNew(); #if WINDOWS_UWP var url = $"ms-appx:///Assets/{path}"; var file = Windows.Storage.StorageFile.GetFileFromApplicationUriAsync(new Uri(url)) .AsTask() .ConfigureAwait(false) .GetAwaiter() .GetResult(); var buffer = Windows.Storage.FileIO.ReadBufferAsync(file).AsTask().ConfigureAwait(false).GetAwaiter() .GetResult(); var jsonData = System.Runtime.InteropServices.WindowsRuntime.WindowsRuntimeBufferExtensions.ToArray(buffer); #elif __ANDROID__ var ctx = global::Couchbase.Lite.Tests.Android.MainActivity.ActivityContext; byte[] jsonData; using (var stream = ctx.Assets.Open(path)) using (var ms = new MemoryStream()) { stream.CopyTo(ms); jsonData = ms.ToArray(); } #elif __IOS__ var bundlePath = Foundation.NSBundle.MainBundle.PathForResource(Path.GetFileNameWithoutExtension(path), Path.GetExtension(path)); byte[] jsonData; using (var stream = File.Open(bundlePath, FileMode.Open, FileAccess.Read)) using (var ms = new MemoryStream()) { stream.CopyTo(ms); jsonData = ms.ToArray(); } #else var jsonData = File.ReadAllBytes(path); #endif FLError error; FLSliceResult fleeceData; fixed(byte *jsonData_ = jsonData) { fleeceData = NativeRaw.FLData_ConvertJSON(new FLSlice(jsonData_, (ulong)jsonData.Length), &error); } ((long)fleeceData.buf).Should().NotBe(0, "because otherwise the conversion failed"); var root = Native.FLValue_AsArray(NativeRaw.FLValue_FromTrustedData((FLSlice)fleeceData)); ((long)root).Should().NotBe(0, "because otherwise the value is not of the expected type"); LiteCoreBridge.Check(err => Native.c4db_beginTransaction(Db, err)); try { FLArrayIterator iter; FLValue * item; uint numDocs = 0; for (Native.FLArrayIterator_Begin(root, &iter); null != (item = Native.FLArrayIterator_GetValue(&iter)); Native.FLArrayIterator_Next(&iter)) { var docID = idPrefix != null ? $"{idPrefix}doc{numDocs + 1:D7}" : $"doc{numDocs + 1:D7}"; var enc = Native.c4db_createFleeceEncoder(Db); Native.FLEncoder_WriteValue(enc, item); var body = NativeRaw.FLEncoder_Finish(enc, &error); var rq = new C4DocPutRequest { docID = C4Slice.Allocate(docID), body = (C4Slice)body, save = true }; var doc = (C4Document *)LiteCoreBridge.Check(err => { var localPut = rq; return(Native.c4doc_put(Db, &localPut, null, err)); }); Native.c4doc_free(doc); Native.FLSliceResult_Free(body); C4Slice.Free(rq.docID); ++numDocs; if ((numDocs % 1000) == 0 && st.Elapsed > timeout) { WriteLine($"WARNING: Stopping JSON import after {st.Elapsed}"); return(numDocs); } if (verbose && (numDocs % 100000) == 0) { WriteLine($"{numDocs} "); } } if (verbose) { st.PrintReport("Importing", numDocs, "doc", _output); } return(numDocs); } finally { Native.FLSliceResult_Free(fleeceData); LiteCoreBridge.Check(err => Native.c4db_endTransaction(Db, true, err)); } }
private void UpdateIndex(C4Database *updateDB, C4View *view) { var oldLastSeqIndexed = Native.c4view_getLastSequenceIndexed(view); var lastSeq = oldLastSeqIndexed; var ind = (C4Indexer *)LiteCoreBridge.Check(err => Native.c4indexer_begin(updateDB, new[] { view }, err)); var e = (C4DocEnumerator *)LiteCoreBridge.Check(err => Native.c4indexer_enumerateDocuments(ind, err)); if (e == null) { LiteCoreBridge.Check(err => Native.c4indexer_end(ind, true, err)); return; } if (Log) { Console.Write("<< "); } C4Document *doc; C4Error error; while (null != (doc = Native.c4enum_nextDocument(e, &error))) { // Index 'doc': if (Log) { Console.Write($"(#{doc->sequence}) "); } if (lastSeq > 0) { doc->sequence.Should().Be(lastSeq + 1, "because the sequences should be ordered"); } lastSeq = doc->sequence; var keys = new C4Key *[1]; var values = new C4Slice[1]; keys[0] = Native.c4key_new(); NativeRaw.c4key_addString(keys[0], doc->docID); values[0] = C4Slice.Constant("1234"); LiteCoreBridge.Check(err => Native.c4indexer_emit(ind, doc, 0, keys, values, err)); Native.c4key_free(keys[0]); Native.c4doc_free(doc); } error.Code.Should().Be(0, "because otherwise an error occurred somewhere"); Native.c4enum_free(e); if (Log) { Console.Write($">>indexed_to:{lastSeq} "); } LiteCoreBridge.Check(err => Native.c4indexer_end(ind, true, err)); var newLastSeqIndexed = Native.c4view_getLastSequenceIndexed(view); if (newLastSeqIndexed != lastSeq) { if (Log) { Console.Write($"BUT view.lastSequenceIndexed={newLastSeqIndexed}! (Started at {oldLastSeqIndexed})"); } } newLastSeqIndexed.Should().Be(lastSeq, "because the last sequence in the loop should be current"); Native.c4view_getLastSequenceChangedAt(view).Should().Be(lastSeq); }