private unsafe void SaveProperties(IDictionary <string, object> props, string docID) { Db.InBatch(() => { var tricky = (C4Document *)LiteCoreBridge.Check(err => Native.c4doc_get(Db.c4db, docID, true, err)); var put = new C4DocPutRequest { docID = tricky->docID, history = &tricky->revID, historyCount = 1, save = true }; var enc = Native.c4db_getSharedFleeceEncoder(Db.c4db); props.FLEncode(enc); var body = NativeRaw.FLEncoder_Finish(enc, null); put.body = (C4Slice)body; LiteCoreBridge.Check(err => { var localPut = put; var retVal = Native.c4doc_put(Db.c4db, &localPut, null, err); Native.FLSliceResult_Free(body); return(retVal); }); }); }
private void CreateDocs(uint n, bool verbose = false) { _R = new Random(42); using (var t = new TransactionHelper(_db)) { for (int i = 0; i < n; i++) { var docID = "doc-" + i.ToString(); double lat0 = RandomLatitude(); double lon0 = RandomLongitude(); double lat1 = Math.Min(lat0 + 0.5, 90.0); double lon1 = Math.Min(lon0 + 0.5, 180.0); var body = String.Format("({0}, {1}, {2}, {3})", lon0, lat0, lon1, lat1); var rq = new C4DocPutRequest(); rq.docID = docID; rq.body = body; rq.save = true; C4Error error; var doc = Native.c4doc_put(_db, rq, null, &error); Assert.IsTrue(doc != null); if (verbose) { Console.WriteLine("Added {0} -> {1}", docID, body); } } } }
public void TestDeleteIndexedDoc() { RunTestVariants(() => { LiteCoreBridge.Check(err => Native.c4db_createIndex(Db, "test", Json5("[['length()', ['.name.first']]]"), C4IndexType.ValueIndex, null, err)); // Delete doc "0000015": LiteCoreBridge.Check(err => Native.c4db_beginTransaction(Db, err)); try { var doc = (C4Document *)LiteCoreBridge.Check(err => Native.c4doc_get(Db, "0000015", true, err)); var rq = new C4DocPutRequest { docID = FLSlice.Constant("0000015"), history = (FLSlice *)&doc->revID, historyCount = 1, revFlags = C4RevisionFlags.Deleted, save = true }; var updatedDoc = (C4Document *)LiteCoreBridge.Check(err => { var localRq = rq; return(Native.c4doc_put(Db, &localRq, null, err)); }); Native.c4doc_free(doc); Native.c4doc_free(updatedDoc); } finally { LiteCoreBridge.Check(err => Native.c4db_endTransaction(Db, true, err)); } // Now run a query that would have returned the deleted doc, if it weren't deleted: Compile(Json5("['=', ['length()', ['.name.first']], 9]")); Run().Should().Equal(new[] { "0000099" }, "because otherwise the query returned incorrect results"); }); }
public void TestInvalidDocID() { RunTestVariants(() => { NativePrivate.c4log_warnOnErrors(false); LiteCoreBridge.Check(err => Native.c4db_beginTransaction(Db, err)); try { Action <C4Slice> checkPutBadDocID = (C4Slice docID) => { C4Error e; var rq = new C4DocPutRequest(); rq.body = Body; rq.save = true; rq.docID = docID; ((long)Native.c4doc_put(Db, &rq, null, &e)).Should() .Be(0, "because the invalid doc ID should cause the put to fail"); e.domain.Should().Be(C4ErrorDomain.LiteCoreDomain); e.code.Should().Be((int)C4ErrorCode.BadDocID); }; checkPutBadDocID(C4Slice.Constant("")); string tooLong = new string(Enumerable.Repeat('x', 241).ToArray()); using (var tooLong_ = new C4String(tooLong)) { checkPutBadDocID(tooLong_.AsC4Slice()); } checkPutBadDocID(C4Slice.Constant("oops\x00oops")); // Bad UTF-8 checkPutBadDocID(C4Slice.Constant("oops\noops")); // Control characters } finally { NativePrivate.c4log_warnOnErrors(true); LiteCoreBridge.Check(err => Native.c4db_endTransaction(Db, true, err)); } }); }
protected void CreateRev(string docID, C4Slice revID, C4Slice body, bool isNew = true) { LiteCoreBridge.Check(err => Native.c4db_beginTransaction(Db, err)); try { var curDoc = (C4Document *)LiteCoreBridge.Check(err => Native.c4doc_get(Db, docID, false, err)); var history = new[] { revID, curDoc->revID }; fixed(C4Slice *h = history) { var rq = new C4DocPutRequest { existingRevision = true, docID = curDoc->docID, history = h, historyCount = curDoc->revID.buf != null ? 2UL : 1UL, body = body, deletion = body.buf == null, save = true }; var doc = (C4Document *)LiteCoreBridge.Check(err => { var localRq = rq; return(Native.c4doc_put(Db, &localRq, null, err)); }); Native.c4doc_free(doc); Native.c4doc_free(curDoc); } } finally { LiteCoreBridge.Check(err => Native.c4db_endTransaction(Db, true, err)); } }
internal void CreateRev(C4Database *db, string docID, C4Slice revID, C4Slice body, C4RevisionFlags flags = (C4RevisionFlags)0) { LiteCoreBridge.Check(err => Native.c4db_beginTransaction(db, err)); try { var curDoc = (C4Document *)LiteCoreBridge.Check(err => Native.c4doc_get(db, docID, false, err)); var history = new[] { revID, curDoc->revID }; fixed(C4Slice *h = history) { var rq = new C4DocPutRequest { existingRevision = true, docID = curDoc->docID, history = h, historyCount = curDoc->revID.buf != null ? 2UL : 1UL, body = body, revFlags = flags, save = true }; var doc = (C4Document *)LiteCoreBridge.Check(err => { var localRq = rq; return(Native.c4doc_put(db, &localRq, null, err)); }); Native.c4doc_free(doc); Native.c4doc_free(curDoc); } } finally { LiteCoreBridge.Check(err => Native.c4db_endTransaction(db, true, err)); } }
private void CreateConflictingRev(C4Database *db, FLSlice docID, FLSlice parentRevID, FLSlice newRevID, FLSlice body, C4RevisionFlags flags) { var history = new[] { newRevID, parentRevID }; fixed(FLSlice *h = history) { var rq = new C4DocPutRequest { existingRevision = true, allowConflict = true, docID = docID, history = h, historyCount = parentRevID.buf != null ? 2UL : 1UL, body = body, revFlags = flags, save = true }; var doc = (C4Document *)LiteCoreBridge.Check(err => { var localRq = rq; return(Native.c4doc_put(db, &localRq, null, err)); }); Native.c4doc_release(doc); } }
public void TestDuplicateRev() { RunTestVariants(() => { var docID = "mydoc"; var body = JSON2Fleece("{'key':'value'}"); var doc = PutDoc(docID, null, (FLSlice)body); var revID = doc->revID.CreateString(); Native.c4doc_release(doc); Native.FLSliceResult_Release(body); body = JSON2Fleece("{'key':'newvalue'}"); doc = PutDoc(docID, revID, (FLSlice)body); var revID2a = doc->revID.CreateString(); Native.c4doc_release(doc); LiteCoreBridge.Check(err => Native.c4db_beginTransaction(Db, err)); var success = false; try { using (var docID_ = new C4String(docID)) using (var revID_ = new C4String(revID)) { var history = new FLSlice[] { revID_.AsFLSlice() }; fixed(FLSlice * history_ = history) { var rq = new C4DocPutRequest { allowConflict = true, docID = docID_.AsFLSlice(), history = history_, historyCount = 1, body = (FLSlice)body, revFlags = 0, save = true }; doc = (C4Document *)LiteCoreBridge.Check(err => { var local = rq; return(Native.c4doc_put(Db, &local, null, err)); }); doc->docID.CreateString().Should().Be(docID); Native.FLSliceResult_Release(body); } } } finally { LiteCoreBridge.Check(err => Native.c4db_endTransaction(Db, success, err)); } var revID2b = doc->revID.CreateString(); Native.c4doc_release(doc); revID2b.Should().Be(revID2a, "because an identical revision was inserted"); }); }
internal void AddPersonInState(string docID, string state, string firstName = null) { LiteCoreBridge.Check(err => Native.c4db_beginTransaction(Db, err)); C4Error error; var enc = Native.c4db_getSharedFleeceEncoder(Db); try { Native.FLEncoder_BeginDict(enc, 3); Native.FLEncoder_WriteKey(enc, "custom"); Native.FLEncoder_WriteBool(enc, true); if (!string.IsNullOrEmpty(firstName)) { Native.FLEncoder_WriteKey(enc, "name"); Native.FLEncoder_BeginDict(enc, 2); Native.FLEncoder_WriteKey(enc, "first"); Native.FLEncoder_WriteString(enc, firstName); Native.FLEncoder_WriteKey(enc, "last"); Native.FLEncoder_WriteString(enc, "lastname"); Native.FLEncoder_EndDict(enc); } Native.FLEncoder_WriteKey(enc, "contact"); Native.FLEncoder_BeginDict(enc, 1); Native.FLEncoder_WriteKey(enc, "address"); Native.FLEncoder_BeginDict(enc, 1); Native.FLEncoder_WriteKey(enc, "state"); Native.FLEncoder_WriteString(enc, state); Native.FLEncoder_EndDict(enc); Native.FLEncoder_EndDict(enc); Native.FLEncoder_EndDict(enc); // Save document: FLSliceResult body = NativeRaw.FLEncoder_Finish(enc, null); //REQUIRE(body.buf); using (var docID_ = new C4String(docID)){ var rq = new C4DocPutRequest { allowConflict = false, docID = docID_.AsFLSlice(), allocedBody = body, save = true }; C4Document *doc; doc = Native.c4doc_put(Db, &rq, null, &error); ((long)doc).Should().NotBe(0, "because otherwise the put failed"); Native.c4doc_release(doc); Native.FLSliceResult_Release(body); } } finally { LiteCoreBridge.Check(err => Native.c4db_endTransaction(Db, true, err)); } }
private C4Document *PutDoc(C4Database *db, string docID, string revID, FLSlice body, C4RevisionFlags flags, C4Error *error = null) { LiteCoreBridge.Check(err => Native.c4db_beginTransaction(db, err)); var success = false; try { var encoded = EncodeBodyIfJSON(body); using (var docID_ = new C4String(docID)) using (var revID_ = new C4String(revID)) { var history = new FLSlice[] { revID_.AsFLSlice() }; fixed(FLSlice *history_ = history) { var rq = new C4DocPutRequest { allowConflict = false, docID = docID_.AsFLSlice(), history = revID == null ? null : history_, historyCount = revID == null ? 0UL : 1UL, body = encoded.buf == null ? body : (FLSlice)encoded, 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)); }); } Native.FLSliceResult_Release(encoded); success = true; return(doc); } } } finally { LiteCoreBridge.Check(err => Native.c4db_endTransaction(db, success, err)); } }
protected override void SetupVariant(int option) { base.SetupVariant(option); var c = new List <char>(Enumerable.Repeat('a', SizeOfDocument)); c.Add((char)0); var content = new string(c.ToArray()); C4Error err; Native.c4db_beginTransaction(Db, &err).Should().BeTrue("because starting a transaction should succeed"); var rng = new Random(); for (int i = 0; i < NumDocuments; i++) { using (var docID = new C4String($"doc-{rng.Next():D8}-{rng.Next():D8}-{rng.Next():D8}-{i:D4}")) using (var revID = new C4String("1-deadbeefcafebabe80081e50")) using (var json = new C4String("{{\"content\":\"{content}\"}}")) { var history = IsRevTrees() ? new C4String[1] { new C4String("1-deadbeefcafebabe80081e50") } : new C4String[1] { new C4String("1@deadbeefcafebabe80081e50") }; var rawHistory = history.Select(x => x.AsC4Slice()).ToArray(); fixed(C4Slice *rawHistory_ = rawHistory) { var rq = new C4DocPutRequest(); rq.existingRevision = true; rq.docID = docID.AsC4Slice(); rq.history = rawHistory_; rq.historyCount = 1; rq.body = json.AsC4Slice(); rq.save = true; var doc = Native.c4doc_put(Db, &rq, null, &err); ((long)doc).Should().NotBe(0, $"because otherwise the put failed"); Native.c4doc_free(doc); } } } Native.c4db_endTransaction(Db, true, &err).Should().BeTrue("because otherwise the transaction failed to end"); Console.WriteLine($"Created {NumDocuments} docs"); Native.c4db_getDocumentCount(Db).Should().Be(NumDocuments, "because the number of documents should be the number that was just inserted"); }
private C4Document *PutDoc(C4Database *db, string docID, string revID, string body, C4RevisionFlags flags, C4Error *error = null) { LiteCoreBridge.Check(err => Native.c4db_beginTransaction(db, err)); var success = false; try { using (var docID_ = new C4String(docID)) using (var revID_ = new C4String(revID)) using (var body_ = new C4String(body)) { var history = new C4Slice[] { revID_.AsC4Slice() }; fixed(C4Slice *history_ = history) { var rq = new C4DocPutRequest { allowConflict = false, docID = docID_.AsC4Slice(), history = revID == null ? null : history_, historyCount = revID == null ? 0UL : 1UL, 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 { LiteCoreBridge.Check(err => Native.c4db_endTransaction(db, success, err)); } }
public void TestPut() { C4Error error; using (var t = new TransactionHelper(_db)) { C4DocPutRequest rq = new C4DocPutRequest(); rq.docID = DOC_ID; rq.body = BODY; rq.save = true; var doc = Native.c4doc_put(_db, rq, null, &error); Assert.IsTrue(doc != null); Assert.IsTrue(doc->docID.Equals(DOC_ID)); const string expectedRevID = "1-c10c25442d9fe14fa3ca0db4322d7f1e43140fab"; Assert.IsTrue(doc->revID.Equals(expectedRevID)); Assert.AreEqual(C4DocumentFlags.Exists, doc->flags); Assert.IsTrue(doc->selectedRev.revID.Equals(expectedRevID)); Native.c4doc_free(doc); // Update doc: rq.body = "{\"ok\":\"go\"}"; rq.history = new[] { expectedRevID }; ulong commonAncestorIndex; doc = Native.c4doc_put(_db, rq, &commonAncestorIndex, &error); Assert.IsTrue(doc != null); Assert.AreEqual(1UL, commonAncestorIndex); const string expectedRev2ID = "2-32c711b29ea3297e27f3c28c8b066a68e1bb3f7b"; Assert.IsTrue(doc->revID.Equals(expectedRev2ID)); Assert.AreEqual(C4DocumentFlags.Exists, doc->flags); Assert.IsTrue(doc->selectedRev.revID.Equals(expectedRev2ID)); Native.c4doc_free(doc); // Insert existing rev: rq.body = "{\"from\":\"elsewhere\"}"; rq.existingRevision = true; rq.history = new[] { REV_2_ID, expectedRevID }; doc = Native.c4doc_put(_db, rq, &commonAncestorIndex, &error); Assert.IsTrue(doc != null); Assert.AreEqual(1UL, commonAncestorIndex); Assert.IsTrue(doc->revID.Equals(REV_2_ID)); Assert.AreEqual(C4DocumentFlags.Exists | C4DocumentFlags.Conflicted, doc->flags); Assert.IsTrue(doc->selectedRev.revID.Equals(REV_2_ID)); Native.c4doc_free(doc); } }
internal C4BlobKey[] AddDocWithAttachments(C4Slice docID, List <string> atts, string contentType) { var keys = new List <C4BlobKey>(); var json = new StringBuilder(); json.Append("{attached: ["); foreach (var att in atts) { var key = new C4BlobKey(); LiteCoreBridge.Check(err => { var localKey = key; var retVal = Native.c4blob_create(Native.c4db_getBlobStore(Db, null), Encoding.UTF8.GetBytes(att), null, &localKey, err); key = localKey; return(retVal); }); keys.Add(key); var keyStr = Native.c4blob_keyToString(key); json.Append( $"{{'{Constants.ObjectTypeProperty}': '{Constants.ObjectTypeBlob}', 'digest': '{keyStr}', length: {att.Length}, 'content_type': '{contentType}'}},"); } json.Append("]}"); var jsonStr = Native.FLJSON5_ToJSON(json.ToString(), null); using (var jsonStr_ = new C4String(jsonStr)) { C4Error error; var body = NativeRaw.c4db_encodeJSON(Db, jsonStr_.AsC4Slice(), &error); ((long)body.buf).Should().NotBe(0, "because otherwise the encode failed"); var rq = new C4DocPutRequest(); rq.docID = docID; rq.revFlags = C4RevisionFlags.HasAttachments; rq.body = (C4Slice)body; rq.save = true; var doc = Native.c4doc_put(Db, &rq, null, &error); Native.c4slice_free(body); ((long)doc).Should().NotBe(0, "because otherwise the put failed"); Native.c4doc_free(doc); return(keys.ToArray()); } }
private void CreateDocs(uint n, bool verbose = false) { var rng = new Random(42); LiteCoreBridge.Check(err => Native.c4db_beginTransaction(Db, err)); try { for (uint i = 0; i < n; ++i) { var docID = i.ToString(); var lat0 = RandomLat(rng); var lon0 = RandomLon(rng); var lat1 = Math.Min(lat0 + 0.5, 90.0); var lon1 = Math.Min(lon0 + 0.5, 180.0); var body = $"({lon0}, {lat0}, {lon1}, {lat1})"; using (var docID_ = new C4String(docID)) using (var body_ = new C4String(body)) { var rq = new C4DocPutRequest { docID = docID_.AsC4Slice(), body = body_.AsC4Slice(), save = true }; var doc = (C4Document *)LiteCoreBridge.Check(err => { var localRq = rq; return(Native.c4doc_put(Db, &localRq, null, err)); }); if (verbose) { Console.WriteLine($"Added {docID} --> {body}"); } Native.c4doc_free(doc); } } } finally { LiteCoreBridge.Check(err => Native.c4db_endTransaction(Db, true, err)); } }
public RevisionInternal PutRevision(string inDocId, RevisionID inPrevRevId, IDictionary<string, object> properties, bool deleting, bool allowConflict, Uri source, StoreValidation validationBlock) { if(_config.HasFlag(C4DatabaseFlags.ReadOnly)) { throw Misc.CreateExceptionAndLog(Log.To.Database, StatusCode.Forbidden, TAG, "Attempting to write to a readonly database (PutRevision)"); } var json = default(string); if(properties != null) { json = Manager.GetObjectMapper().WriteValueAsString(Database.StripDocumentJSON(properties), true); } else { json = "{}"; } if(inDocId == null) { inDocId = Misc.CreateGUID(); } C4Document* doc = null; var putRev = default(RevisionInternal); var change = default(DocumentChange); var success = RunInTransaction(() => { try { var docId = inDocId; var prevRevId = inPrevRevId; C4DocPutRequest rq = new C4DocPutRequest { body = json, docID = docId, deletion = deleting, hasAttachments = properties?.Get("_attachments") != null, existingRevision = false, allowConflict = allowConflict, history = prevRevId == null ? null : new[] { prevRevId.ToString() }, save = false }; UIntPtr commonAncestorIndex = UIntPtr.Zero; doc = (C4Document*)ForestDBBridge.Check(err => { UIntPtr tmp; var retVal = Native.c4doc_put(Forest, rq, &tmp, err); commonAncestorIndex = tmp; return retVal; }); if(docId == null) { docId = (string)doc->docID; } var newRevID = doc->selectedRev.revID.AsRevID(); Body body = null; if(properties != null) { properties.SetDocRevID(docId, newRevID); body = new Body(properties); } putRev = new RevisionInternal(docId, newRevID, deleting, body); if((uint)commonAncestorIndex == 0U) { return true; } if(validationBlock != null) { var prevRev = default(RevisionInternal); if(Native.c4doc_selectParentRevision(doc)) { prevRev = new ForestRevisionInternal(doc, false); } var status = validationBlock(putRev, prevRev, prevRev == null ? null : prevRev.RevID); if(status.IsError) { Log.To.Validation.I(TAG, "{0} ({1}) failed validation", new SecureLogString(docId, LogMessageSensitivity.PotentiallyInsecure), new SecureLogString(newRevID, LogMessageSensitivity.PotentiallyInsecure)); throw new CouchbaseLiteException("A document failed validation", status.Code); } } var isWinner = SaveDocument(doc, newRevID, properties); putRev.Sequence = (long)doc->sequence; change = ChangeWithNewRevision(putRev, isWinner, doc, null); return true; } finally { Native.c4doc_free(doc); } }); if(!success) { return null; } if(Delegate != null && change != null) { Delegate.DatabaseStorageChanged(change); } return putRev; }
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 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); } }); }
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); }); }
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 = 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)); } }
// 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); }
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 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 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 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)); } }); }
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)); } }