public void TestDBQueryWhatReturningObject() { RunTestVariants(() => { var expectedFirst = new[] { "Cleveland", "Georgetta", "Margaretta" }; var expectedLast = new[] { "Bejcek", "Kolding", "Ogwynn" }; CompileSelect(Json5( "{WHAT: ['.name'], WHERE: ['>=', ['length()', ['.name.first']], 9], ORDER_BY: [['.name.first']]}")); Native.c4query_columnCount(_query).Should().Be(1); var e = (C4QueryEnumerator *)LiteCoreBridge.Check(err => { var opts = C4QueryOptions.Default; return(Native.c4query_run(_query, &opts, null, err)); }); var i = 0; C4Error error; while (Native.c4queryenum_next(e, &error)) { var col = Native.FLArrayIterator_GetValueAt(&e->columns, 0); Native.FLValue_GetType(col).Should().Be(FLValueType.Dict); var name = Native.FLValue_AsDict(col); WriteLine(Native.FLValue_ToJSONX(col, false, false)); Native.FLValue_AsString(Native.FLDict_Get(name, Encoding.UTF8.GetBytes("first"))) .Should().Be(expectedFirst[i]); Native.FLValue_AsString(Native.FLDict_Get(name, Encoding.UTF8.GetBytes("last"))) .Should().Be(expectedLast[i]); ++i; } error.code.Should().Be(0, "because otherwise an error occurred during enumeration"); i.Should().Be(3, "because that is the number of expected rows"); Native.c4queryenum_release(e); }); }
protected IList <string> Run(ulong skip = 0, ulong limit = ulong.MaxValue, string bindings = null) { ((long)_query).Should().NotBe(0, "because otherwise what are we testing?"); var docIDs = new List <string>(); var options = C4QueryOptions.Default; options.skip = skip; options.limit = limit; var e = (C4QueryEnumerator *)LiteCoreBridge.Check(err => { var localOptions = options; return(Native.c4query_run(_query, &localOptions, bindings, err)); }); C4Error error; while (Native.c4queryenum_next(e, &error)) { docIDs.Add(e->docID.CreateString()); } error.code.Should().Be(0, "because otherwise an error occurred during enumeration"); Native.c4queryenum_free(e); return(docIDs); }
public void TestWriteManyBlobSizes() { var sizes = new[] { 0, 1, 15, 16, 17, 4095, 4096, 4097, 4096 + 15, 4096 + 16, 4096 + 17, 8191, 8192, 8193 }; var chars = Encoding.UTF8.GetBytes("ABCDEFGHIJKLMNOPQRSTUVWXY"); // The interesting sizes for encrypted blobs are right around the file block size (4096) // and the cipher block size (16). RunTestVariants(() => { foreach (var size in sizes) { WriteLine($"Testing {size}-byte blob"); // Write the blob: var stream = (C4WriteStream *)LiteCoreBridge.Check(err => Native.c4blob_openWriteStream(_store, err)); for (int i = 0; i < size; i++) { var c = i % chars.Length; LiteCoreBridge.Check(err => Native.c4stream_write(stream, new[] { chars[c] }, err)); } // Get the blob key, and install it: var key = Native.c4stream_computeBlobKey(stream); LiteCoreBridge.Check(err => Native.c4stream_install(stream, null, err)); Native.c4stream_closeWriter(stream); // Read it back using the key: C4Error error; var contents = Native.c4blob_getContents(_store, key, &error); contents.Should().HaveCount(size, "because that was the size that was stored"); for (int i = 0; i < size; i++) { contents[i].Should().Be(chars[i % chars.Length], "because the data should not change"); } } }); }
public void TestAllDocsIncludeDeleted() { RunTestVariants(() => { SetupAllDocs(); var options = C4EnumeratorOptions.Default; options.flags |= C4EnumeratorFlags.IncludeDeleted; var e = (C4DocEnumerator *)LiteCoreBridge.Check(err => { var localOpts = options; return(Native.c4db_enumerateAllDocs(Db, "doc-004", "doc-007", &localOpts, err)); }); int i = 4; C4Error error; while (Native.c4enum_next(e, &error)) { var doc = (C4Document *)LiteCoreBridge.Check(err => Native.c4enum_getDocument(e, err)); var docID = default(string); if (i == 6) { docID = "doc-005DEL"; } else { var docNum = i >= 6 ? i - 1 : i; docID = $"doc-{docNum: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(9, "because that is the last ID suffix in the given range"); }); }
public void TestDBQueryAggregate() { RunTestVariants(() => { Compile(Json5("{WHAT: [['min()', ['.name.last']], ['max()', ['.name.last']]]}")); var e = (C4QueryEnumerator *)LiteCoreBridge.Check(err => { var opts = C4QueryOptions.Default; return(Native.c4query_run(_query, &opts, null, err)); }); var i = 0; C4Error error; while (Native.c4queryenum_next(e, &error)) { var customColumns = Native.c4queryenum_customColumns(e); GetColumn(customColumns, 0).Should().Be("Aerni", "because otherwise the query returned incorrect results"); GetColumn(customColumns, 1).Should().Be("Zirk", "because otherwise the query returned incorrect results"); ++i; } error.code.Should().Be(0, "because otherwise an error occurred during enumeration"); i.Should().Be(1, "because there is only one result for the query"); Native.c4queryenum_free(e); }); }
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 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)); } }); }
public void TestPurge() { RunTestVariants(() => { var body2 = C4Slice.Constant("{\"ok\":\"go\"}"); var body3 = C4Slice.Constant("{\"ubu\":\"roi\"}"); CreateRev(DocID.CreateString(), RevID, Body); CreateRev(DocID.CreateString(), Rev2ID, body2); CreateRev(DocID.CreateString(), Rev3ID, body3); var history = new[] { C4Slice.Constant("3-ababab"), Rev2ID }; fixed(C4Slice * history_ = history) { var rq = new C4DocPutRequest { existingRevision = true, docID = DocID, history = history_, historyCount = 2, body = 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, Body); CreateRev(DocID.CreateString(), Rev2ID, body2); CreateRev(DocID.CreateString(), Rev3ID, 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); } }); }
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 = ios::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}{numDocs + 1:D7}" : $"doc{numDocs + 1:D7}"; var enc = Native.c4db_getSharedFleeceEncoder(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)); } }
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"); info.bodySize.Should().BeGreaterOrEqualTo(11).And .BeLessOrEqualTo(40, "because the body should have some data"); 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); error.code.Should().Be(0, "because otherwise an error occurred"); 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); error.code.Should().Be(0, "because otherwise an error occurred"); i.Should().Be(4, "because four document IDs were specified"); }); }
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"); }); }
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 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"); } } } } }); }
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(); } } } }
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 TestRevTree() { RunTestVariants(() => { if (!IsRevTrees()) { return; } // TODO: Observer var docID = "MyDocID"; var body = "{\"message\":\"hi\"}"; var history = new[] { "4-4444", "3-3333", "2-2222", "1-1111" }; ForceInsert(docID, history, body); Native.c4db_getDocumentCount(Db).Should().Be(1UL); var doc = GetDoc(docID); VerifyRev(doc, history, body); Native.c4doc_free(doc); var lastSeq = Native.c4db_getLastSequence(Db); ForceInsert(docID, history, body); Native.c4db_getLastSequence(Db).Should().Be(lastSeq, "because the last operation should have been a no-op"); var conflictHistory = new[] { "5-5555", "4-4545", "3-3030", "2-2222", "1-1111" }; var conflictBody = "{\"message\":\"yo\"}"; ForceInsert(docID, conflictHistory, conflictBody); // 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); VerifyRev(doc, history, body); Native.c4doc_free(doc); // TODO: Conflict check var otherDocID = "AnotherDocID"; var otherBody = "{\"language\":\"jp\"}"; var otherHistory = new[] { "1-1010" }; ForceInsert(otherDocID, otherHistory, otherBody); doc = GetDoc(docID); LiteCoreBridge.Check(err => Native.c4doc_selectRevision(doc, "2-2222", false, err)); doc->selectedRev.flags.Should().NotHaveFlag(C4RevisionFlags.KeepBody); doc->selectedRev.body.CreateString().Should().BeNull(); Native.c4doc_free(doc); doc = GetDoc(otherDocID); 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_free(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_free(doc); doc = GetDoc(docID); var conflictingRevs = GetRevisionHistory(doc, true, true); conflictingRevs.Count.Should().Be(2); conflictingRevs.Should().Equal(history[0], conflictHistory[0]); Native.c4doc_free(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_free(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], null, C4RevisionFlags.Deleted); Native.c4doc_free(doc); doc = GetDoc(docID); doc->revID.CreateString().Should().Be(history[0]); doc->selectedRev.revID.CreateString().Should().Be(history[0]); VerifyRev(doc, history, body); Native.c4doc_free(doc); doc = PutDoc(docID, history[0], null, C4RevisionFlags.Deleted); Native.c4doc_free(doc); // 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 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)); var configCopy = config; if (!Native.c4db_deleteAtPath(destPath, &configCopy, &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 <LiteCoreException>().Which.Error.Should().Be(new C4Error(C4ErrorCode.NotFound)); 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 <LiteCoreException>().Which.Error.Should().Be(new C4Error(C4ErrorCode.NotFound)); 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 <LiteCoreException>().Which.Error.Should().Be(new C4Error(C4ErrorDomain.POSIXDomain, (int)PosixStatus.EXIST)); 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 TestQueryObserver() { RunTestVariants(() => { var handle = GCHandle.Alloc(this); try { Compile(Json5("['=', ['.', 'contact', 'address', 'state'], 'CA']")); C4Error error; /** Creates a new query observer, with a callback that will be invoked when the query * results change, with an enumerator containing the new results. * \note The callback isn't invoked immediately after a change, and won't be invoked after * every change, to avoid performance problems. Instead, there's a brief delay so multiple * changes can be coalesced. * \note The new observer needs to be enabled by calling \ref c4queryobs_setEnabled. */ _queryObserver = Native.c4queryobs_create(_query, QueryCallback, GCHandle.ToIntPtr(handle).ToPointer()); /** Enables a query observer so its callback can be called, or disables it to stop callbacks. */ Native.c4queryobs_setEnabled(_queryObserver, true); WriteLine("---- Waiting for query observer..."); Thread.Sleep(2000); WriteLine("Checking query observer..."); _queryCallbackCalls.Should().Be(1, "because we should have received a callback"); var e = (C4QueryEnumerator *)LiteCoreBridge.Check(err => { /** Returns the current query results, if any. * When the observer is created, the results are initially NULL until the query finishes * running in the background. * Once the observer callback is called, the results are available. * \note You are responsible for releasing the returned reference. * @param obs The query observer. * @param forget If true, the observer will not hold onto the enumerator, and subsequent calls * will return NULL until the next time the observer notifies you. This can help * conserve memory, since the query result data will be freed as soon as you * release the enumerator. * @param error If the last evaluation of the query failed, the error will be stored here. * @return The current query results, or NULL if the query hasn't run or has failed. */ return(Native.c4queryobs_getEnumerator(_queryObserver, true, err)); }); //REQUIRE(e); //CHECK(c4queryobs_getEnumerator(state.obs, true, &error) == nullptr); //CHECK(error.code == 0); Native.c4queryenum_getRowCount(e, &error).Should().Be(8); AddPersonInState("after1", "AL"); WriteLine("---- Checking that query observer doesn't fire..."); Thread.Sleep(TimeSpan.FromSeconds(1)); _queryCallbackCalls.Should().Be(1); WriteLine("---- Changing a doc in the query"); LiteCoreBridge.Check(err => Native.c4db_beginTransaction(Db, err)); try { AddPersonInState("after2", "CA"); // wait, to make sure the observer doesn't try to run the query before the commit Thread.Sleep(TimeSpan.FromSeconds(1)); WriteLine("---- Commiting changes"); } finally { LiteCoreBridge.Check(err => Native.c4db_endTransaction(Db, true, err)); } WriteLine("---- Waiting for 2nd call of query observer..."); Thread.Sleep(2000); WriteLine("---- Checking query observer again..."); _queryCallbackCalls.Should().Be(2, "because we should have received a callback"); var e2 = (C4QueryEnumerator *)LiteCoreBridge.Check(err => { return(Native.c4queryobs_getEnumerator(_queryObserver, false, err)); }); //REQUIRE(e2); //CHECK(e2 != e); var e3 = (C4QueryEnumerator *)LiteCoreBridge.Check(err => { return(Native.c4queryobs_getEnumerator(_queryObserver, false, err)); }); //CHECK(e3 == e2); Native.c4queryenum_getRowCount(e2, &error).Should().Be(9); // Testing with purged document: WriteLine("---- Purging a document..."); LiteCoreBridge.Check(err => Native.c4db_beginTransaction(Db, err)); try { Native.c4db_purgeDoc(Db, "after2", &error); WriteLine("---- Commiting changes"); } finally { LiteCoreBridge.Check(err => Native.c4db_endTransaction(Db, true, err)); } WriteLine("---- Waiting for 3rd call of query observer..."); Thread.Sleep(2000); WriteLine("---- Checking query observer again..."); _queryCallbackCalls.Should().Be(3, "because we should have received a callback"); e2 = (C4QueryEnumerator *)LiteCoreBridge.Check(err => { return(Native.c4queryobs_getEnumerator(_queryObserver, true, err)); }); //REQUIRE(e2); //CHECK(e2 != e); Native.c4queryenum_getRowCount(e2, &error).Should().Be(8); } finally { handle.Free(); } }); }
// 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 { ReadFileByLines(path, line => { C4Error error; var body = NativeRaw.c4db_encodeJSON(Db, (C4Slice)line, &error); ((long)body.buf).Should().NotBe(0, "because otherwise the encode failed"); 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.c4slice_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 TestMultipleQueryObservers() { RunTestVariants(() => { var handle = GCHandle.Alloc(this); try { Compile(Json5("['=', ['.', 'contact', 'address', 'state'], 'CA']")); C4Error error; _queryObserver = Native.c4queryobs_create(_query, QueryCallback, GCHandle.ToIntPtr(handle).ToPointer()); //CHECK(_queryObserver); Native.c4queryobs_setEnabled(_queryObserver, true); _queryObserver2 = Native.c4queryobs_create(_query, QueryCallback, GCHandle.ToIntPtr(handle).ToPointer()); //CHECK(_queryObserver2); Native.c4queryobs_setEnabled(_queryObserver2, true); WriteLine("---- Waiting for query observers..."); Thread.Sleep(2000); WriteLine("Checking query observers..."); _queryCallbackCalls.Should().Be(1, "because we should have received a callback"); var e1 = (C4QueryEnumerator *)LiteCoreBridge.Check(err => { return(Native.c4queryobs_getEnumerator(_queryObserver, true, err)); }); //REQUIRE(e1); //CHECK(error.code == 0); Native.c4queryenum_getRowCount(e1, &error).Should().Be(8); _queryCallbackCalls2.Should().Be(1, "because we should have received a callback"); var e2 = (C4QueryEnumerator *)LiteCoreBridge.Check(err => { return(Native.c4queryobs_getEnumerator(_queryObserver2, true, err)); }); //REQUIRE(e2); //CHECK(error.code == 0); //CHECK(e2 != e1); Native.c4queryenum_getRowCount(e2, &error).Should().Be(8); _queryObserver3 = Native.c4queryobs_create(_query, QueryCallback, GCHandle.ToIntPtr(handle).ToPointer()); //CHECK(_queryObserver3); Native.c4queryobs_setEnabled(_queryObserver3, true); WriteLine("---- Waiting for a new query observer..."); Thread.Sleep(2000); WriteLine("Checking a new query observer..."); _queryCallbackCalls3.Should().Be(1, "because we should have received a callback"); var e3 = (C4QueryEnumerator *)LiteCoreBridge.Check(err => { return(Native.c4queryobs_getEnumerator(_queryObserver3, true, err)); }); //REQUIRE(e3); //CHECK(error.code == 0); //CHECK(e3 != e2); Native.c4queryenum_getRowCount(e3, &error).Should().Be(8); WriteLine("Iterating all query results..."); int count = 0; while (Native.c4queryenum_next(e1, null) && Native.c4queryenum_next(e2, null) && Native.c4queryenum_next(e3, null)) { ++count; FLArrayIterator col1 = e1->columns; FLArrayIterator col2 = e2->columns; FLArrayIterator col3 = e3->columns; var c = Native.FLArrayIterator_GetCount(&col1); c.Should().Be(Native.FLArrayIterator_GetCount(&col2)); c.Should().Be(Native.FLArrayIterator_GetCount(&col3)); for (uint i = 0; i < c; ++i) { var v1 = Native.FLArrayIterator_GetValueAt(&col1, i); var v2 = Native.FLArrayIterator_GetValueAt(&col2, i); var v3 = Native.FLArrayIterator_GetValueAt(&col3, i); Native.FLValue_IsEqual(v1, v2).Should().BeTrue(); Native.FLValue_IsEqual(v2, v3).Should().BeTrue(); } } count.Should().Be(8); } finally { handle.Free(); } }); }
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 = Body, 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(Body).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(Body).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(Body).Should().BeTrue("because the doc should have the stored body"); Native.c4doc_free(doc); }); }
protected override void TeardownVariant(int options) { LiteCoreBridge.Check(err => Native.c4blob_deleteStore(_store, err)); }
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 = &doc->revID; rq.historyCount = 1; rq.body = Body; 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); }); }
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 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)); } }); }
private C4Document *ForceInsert(C4Database *db, string docID, string[] history, string 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 C4Slice[history.Length]; foreach (var entry in history) { var c4Str = new C4String(entry); c4History[i] = c4Str; sliceHistory[i++] = c4Str.AsC4Slice(); } using (var docID_ = new C4String(docID)) using (var body_ = new C4String(body)) { fixed(C4Slice *sliceHistory_ = sliceHistory) { var rq = new C4DocPutRequest { docID = docID_.AsC4Slice(), existingRevision = true, allowConflict = true, history = sliceHistory_, historyCount = (ulong)history.Length, body = body_.AsC4Slice(), revFlags = flags, 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 TestConflict() { RunTestVariants(() => { if (!IsRevTrees()) { return; } var body2 = C4Slice.Constant("{\"ok\":\"go\"}"); var body3 = C4Slice.Constant("{\"ubu\":\"roi\"}"); CreateRev(DocID.CreateString(), RevID, Body); CreateRev(DocID.CreateString(), Rev2ID, body2, C4RevisionFlags.KeepBody); CreateRev(DocID.CreateString(), C4Slice.Constant("3-aaaaaa"), body3); LiteCoreBridge.Check(err => Native.c4db_beginTransaction(Db, err)); try { // "Pull" a conflicting revision: var history = new C4Slice[] { C4Slice.Constant("4-dddd"), C4Slice.Constant("3-ababab"), Rev2ID }; fixed(C4Slice * history_ = history) { var rq = new C4DocPutRequest { existingRevision = true, docID = DocID, history = history_, historyCount = 3, body = body3, save = true }; C4Error error; var doc = Native.c4doc_put(Db, &rq, null, &error); ((IntPtr)doc).Should().NotBe(IntPtr.Zero); 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)); } }); }
public void TestCRUD() { RunTestVariants(() => { if (!IsRevTrees()) { return; } var body = "{\"foo\":1, \"bar\":false}"; var updatedBody = "{\"foo\":1, \"bar\":false, \"status\": \"updated!\"}"; // TODO: Observer C4Error error; var doc = Native.c4doc_get(Db, "nonexistent", true, &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, 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_free(doc); doc = (C4Document *)LiteCoreBridge.Check(err => Native.c4doc_get(Db, docID, true, err)); doc->docID.CreateString().Should().Be(docID); doc->selectedRev.revID.CreateString().Should().Be(revID1); doc->selectedRev.body.CreateString().Should().Be(body); Native.c4doc_free(doc); doc = PutDoc(docID, revID1, updatedBody, C4RevisionFlags.KeepBody); doc->docID.CreateString().Should().Be(docID); doc->selectedRev.body.CreateString().Should().Be(updatedBody); var revID2 = doc->revID.CreateString(); revID2.Should().StartWith("2-", "because otherwise the generation is invalid"); Native.c4doc_free(doc); error = new C4Error(C4ErrorCode.Conflict); PutDocMustFail(docID, revID1, 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_free(doc); seq++; } seq.Should().Be(3UL); Native.c4enum_free(e); // NOTE: Filter is out of LiteCore scope error = new C4Error(C4ErrorCode.InvalidParameter); PutDocMustFail(docID, null, null, C4RevisionFlags.Deleted, error); doc = PutDoc(docID, revID2, 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_free(doc); doc = (C4Document *)LiteCoreBridge.Check(err => Native.c4doc_get(Db, docID, true, 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); doc->selectedRev.body.CreateString().Should().NotBeNull("because a valid revision should have a valid body"); doc->selectedRev.flags.Should().Be(C4RevisionFlags.Leaf | C4RevisionFlags.Deleted); Native.c4doc_free(doc); PutDocMustFail("fake", null, 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_free(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_free(doc); seq++; } seq.Should().Be(4UL, "because deleted documents were included"); Native.c4enum_free(e); doc = (C4Document *)LiteCoreBridge.Check(err => Native.c4doc_get(Db, docID, true, 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_free(doc); doc = (C4Document *)LiteCoreBridge.Check(err => Native.c4doc_get(Db, docID, true, err)); LiteCoreBridge.Check(err => Native.c4doc_selectRevision(doc, revID2, true, err)); doc->selectedRev.revID.CreateString().Should().Be(revID2); doc->selectedRev.body.CreateString().Should().Be(updatedBody); Native.c4doc_free(doc); LiteCoreBridge.Check(err => Native.c4db_compact(Db, err)); doc = (C4Document *)LiteCoreBridge.Check(err => Native.c4doc_get(Db, docID, true, 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_free(doc); // Check history again after compaction doc = (C4Document *)LiteCoreBridge.Check(err => Native.c4doc_get(Db, docID, true, 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_free(doc); }); }