public AtomicAction ActionToChangeEncryptionKey(SymmetricKey newKey) { var retVal = new AtomicAction(() => ForestDBBridge.Check(err => { var newc4key = default(C4EncryptionKey); if (newKey != null) { newc4key = new C4EncryptionKey(newKey.KeyData); } return(Native.c4db_rekey(Forest, &newc4key, err)); }), null, null); foreach (var viewName in GetAllViews()) { var store = GetViewStorage(viewName, false) as ForestDBViewStore; if (store == null) { continue; } retVal.AddLogic(store.ActionToChangeEncryptionKey(newKey)); } return(retVal); }
private void WithC4Document(string docId, string revId, bool withBody, bool create, C4DocumentActionDelegate block) { var doc = default(C4Document *); try { doc = (C4Document *)ForestDBBridge.Check(err => Native.c4doc_get(Forest, docId, !create, err)); if (revId != null) { ForestDBBridge.Check(err => Native.c4doc_selectRevision(doc, revId, withBody, err)); } if (withBody) { ForestDBBridge.Check(err => Native.c4doc_loadRevisionBody(doc, err)); } } catch (CBForestException e) { var is404 = e.Domain == C4ErrorDomain.ForestDB && e.Code == (int)ForestDBStatus.KeyNotFound; is404 |= e.Domain == C4ErrorDomain.HTTP && e.Code == 404; var is410 = e.Domain == C4ErrorDomain.HTTP && e.Code == 410; // Body compacted if (!is404 && !is410) { throw; } Native.c4doc_free(doc); // In case the failure was in selectRevision doc = null; } try { block(doc); } finally { Native.c4doc_free(doc); } }
public void ForceInsert(RevisionInternal inRev, IList <string> revHistory, StoreValidation validationBlock, Uri source) { if (_config.HasFlag(C4DatabaseFlags.ReadOnly)) { throw new CouchbaseLiteException("Attempting to write to a readonly database", StatusCode.Forbidden); } var json = Manager.GetObjectMapper().WriteValueAsString(inRev.GetProperties(), true); var change = default(DocumentChange); RunInTransaction(() => { // First get the CBForest doc: WithC4Document(inRev.GetDocId(), null, false, true, doc => { ForestDBBridge.Check(err => Native.c4doc_insertRevisionWithHistory(doc, json, inRev.IsDeleted(), inRev.GetAttachments() != null, revHistory.ToArray(), err)); // Save updated doc back to the database: var isWinner = SaveDocument(doc, revHistory[0], inRev.GetProperties()); inRev.SetSequence((long)doc->sequence); change = ChangeWithNewRevision(inRev, isWinner, doc, source); }); return(true); }); if (change != null && Delegate != null) { Delegate.DatabaseStorageChanged(change); } }
private IDictionary <string, object> GetAllDocsEntry(string docId) { var value = default(IDictionary <string, object>); var existingDoc = default(C4Document *); try { existingDoc = (C4Document *)ForestDBBridge.Check(err => Native.c4doc_get(Forest, docId, true, err)); if (existingDoc != null) { value = new NonNullDictionary <string, object> { { "rev", (string)existingDoc->revID }, { "deleted", true } }; } } catch (CBForestException e) { if (e.Domain != C4ErrorDomain.ForestDB || e.Code != (int)ForestDBStatus.KeyNotFound) { throw; } } finally { Native.c4doc_free(existingDoc); } return(value); }
public bool RunInTransaction(RunInTransactionDelegate block) { Log.D(TAG, "BEGIN transaction..."); ForestDBBridge.Check(err => Native.c4db_beginTransaction(Forest, err)); var success = false; try { success = block(); } catch (CouchbaseLiteException) { Log.W(TAG, "Failed to run transaction"); success = false; throw; } catch (Exception e) { success = false; throw new CouchbaseLiteException("Error running transaction", e) { Code = StatusCode.Exception }; } finally { Log.D(TAG, "END transaction (success={0})", success); ForestDBBridge.Check(err => Native.c4db_endTransaction(Forest, success, err)); if (!InTransaction && Delegate != null) { Delegate.StorageExitedTransaction(success); } } return(success); }
private C4View *OpenIndexWithOptions(C4DatabaseFlags options, bool dryRun = false) { if (_indexDB == null) { _indexDB = (C4View *)ForestDBBridge.Check(err => { var encryptionKey = default(C4EncryptionKey); if (_dbStorage.EncryptionKey != null) { encryptionKey = new C4EncryptionKey(_dbStorage.EncryptionKey.KeyData); } return(Native.c4view_open(_dbStorage.Forest, _path, Name, dryRun ? "0" : Delegate.MapVersion, options, &encryptionKey, err)); }); if (dryRun) { ForestDBBridge.Check(err => Native.c4view_close(_indexDB, err)); _indexDB = null; } } return(_indexDB); }
private void CloseIndex() { var indexDB = _indexDB; _indexDB = null; if (indexDB != null) { Log.D(TAG, "Closing index"); ForestDBBridge.Check(err => Native.c4view_close(indexDB, err)); } }
public void Close() { IsOpen = false; var connections = _fdbConnections; _fdbConnections = new ConcurrentDictionary <int, IntPtr>(); foreach (var ptr in connections) { ForestDBBridge.Check(err => Native.c4db_close((C4Database *)ptr.Value.ToPointer(), err)); } }
public RevisionInternal PutLocalRevision(RevisionInternal revision, string prevRevId, bool obeyMVCC) { var docId = revision.GetDocId(); if (!docId.StartsWith("_local/")) { throw new CouchbaseLiteException("Local revision IDs must start with _local/", StatusCode.BadId); } if (revision.IsDeleted()) { DeleteLocalRevision(docId, prevRevId, obeyMVCC); return(revision); } var result = default(RevisionInternal); RunInTransaction(() => { var json = Manager.GetObjectMapper().WriteValueAsString(revision.GetProperties(), true); WithC4Raw(docId, "_local", doc => { var generation = RevisionInternal.GenerationFromRevID(prevRevId); if (obeyMVCC) { if (prevRevId != null) { if (prevRevId != (doc != null ? (string)doc->meta : null)) { throw new CouchbaseLiteException(StatusCode.Conflict); } if (generation == 0) { throw new CouchbaseLiteException(StatusCode.BadId); } } else if (doc != null) { throw new CouchbaseLiteException(StatusCode.Conflict); } } var newRevId = String.Format("{0}-local", ++generation); ForestDBBridge.Check(err => Native.c4raw_put(Forest, "_local", docId, newRevId, json, err)); result = revision.CopyWithDocID(docId, newRevId); }); return(true); }); return(result); }
public AtomicAction ActionToChangeEncryptionKey(SymmetricKey newKey) { return(new AtomicAction(() => ForestDBBridge.Check(err => { var newc4key = default(C4EncryptionKey); if (newKey != null) { newc4key = new C4EncryptionKey(newKey.KeyData); } return Native.c4view_rekey(_indexDB, &newc4key, err); }), null, null)); }
private bool SaveDocument(C4Document *doc, string revId, IDictionary <string, object> properties) { // Is the new revision the winner? Native.c4doc_selectCurrentRevision(doc); bool isWinner = (string)doc->selectedRev.revID == revId; // Update the documentType: if (isWinner) { var type = properties == null ? null : properties.GetCast <string>("type"); ForestDBBridge.Check(err => Native.c4doc_setType(doc, type, err)); } // Save: ForestDBBridge.Check(err => Native.c4doc_save(doc, (uint)MaxRevTreeDepth, err)); return(isWinner); }
public ICollection <BlobKey> FindAllAttachmentKeys() { var keys = new HashSet <BlobKey>(); var options = C4EnumeratorOptions.DEFAULT; options.flags &= ~C4EnumeratorFlags.IncludeBodies; options.flags |= C4EnumeratorFlags.IncludeDeleted; var e = new CBForestDocEnumerator(Forest, null, null, options); foreach (var next in e) { var docInfo = next.DocumentInfo; if (!docInfo->HasAttachments || (docInfo->IsDeleted && !docInfo->IsConflicted)) { continue; } var doc = next.GetDocument(); // Since db is assumed to have just been compacted, we know that non-current revisions // won't have any bodies. So only scan the current revs. do { if (doc->selectedRev.IsActive && doc->selectedRev.HasAttachments) { ForestDBBridge.Check(err => Native.c4doc_loadRevisionBody(doc, err)); var body = doc->selectedRev.body; if (body.size > 0) { var rev = Manager.GetObjectMapper().ReadValue <IDictionary <string, object> >(body); foreach (var entry in rev.Get("_attachments").AsDictionary <string, IDictionary <string, object> >()) { try { var key = new BlobKey(entry.Value.GetCast <string>("digest")); keys.Add(key); } catch (Exception) { Log.W(TAG, "Invalid digest {0}; skipping", entry.Value.GetCast <string>("digest")); } } } } } while(Native.c4doc_selectNextLeafRevision(doc, true, true, null)); } return(keys); }
private void WithC4Document(string docId, long sequence, C4DocumentActionDelegate block) { var doc = default(C4Document *); try { doc = (C4Document *)ForestDBBridge.Check(err => Native.c4doc_getBySequence(Forest, (ulong)sequence, err)); } catch (CBForestException e) { if (e.Domain != C4ErrorDomain.ForestDB && (ForestDBStatus)e.Code != ForestDBStatus.KeyNotFound) { throw; } } try { block(doc); } finally { Native.c4doc_free(doc); } }
private void WithC4Raw(string docId, string storeName, C4RawDocumentActionDelegate block) { var doc = default(C4RawDocument *); try { doc = (C4RawDocument *)ForestDBBridge.Check(err => Native.c4raw_get(Forest, storeName, docId, err)); } catch (CBForestException e) { if (e.Domain != C4ErrorDomain.ForestDB && (ForestDBStatus)e.Code != ForestDBStatus.KeyNotFound) { throw; } } try { block(doc); } finally { Native.c4raw_free(doc); } }
public RevisionInternal GetParentRevision(RevisionInternal rev) { var retVal = default(RevisionInternal); WithC4Document(rev.GetDocId(), rev.GetRevId(), false, false, doc => { if (!Native.c4doc_selectParentRevision(doc)) { return; } ForestDBBridge.Check(err => Native.c4doc_loadRevisionBody(doc, err)); retVal = new RevisionInternal((string)doc->docID, (string)doc->selectedRev.revID, doc->selectedRev.IsDeleted); retVal.SetSequence((long)doc->selectedRev.sequence); retVal.SetBody(new Body(doc->selectedRev.body)); }); return(retVal); }
private CBForestQueryEnumerator QueryEnumeratorWithOptions(QueryOptions options) { Debug.Assert(_indexDB != null); var enumerator = default(C4QueryEnumerator *); using (var startkeydocid_ = new C4String(options.StartKeyDocId)) using (var endkeydocid_ = new C4String(options.EndKeyDocId)) { WithC4Keys(new object[] { options.StartKey, options.EndKey }, false, startEndKey => WithC4Keys(options.Keys == null ? null : options.Keys.ToArray(), true, c4keys => { var opts = C4QueryOptions.DEFAULT; opts.descending = options.Descending; opts.endKey = startEndKey[1]; opts.endKeyDocID = endkeydocid_.AsC4Slice(); opts.inclusiveEnd = options.InclusiveEnd; opts.inclusiveStart = options.InclusiveStart; if (c4keys != null) { opts.keysCount = (uint)c4keys.Length; } opts.limit = (ulong)options.Limit; opts.skip = (ulong)options.Skip; opts.startKey = startEndKey[0]; opts.startKeyDocID = startkeydocid_.AsC4Slice(); fixed(C4Key * *keysPtr = c4keys) { opts.keys = keysPtr; enumerator = (C4QueryEnumerator *)ForestDBBridge.Check(err => { var localOpts = opts; return(Native.c4view_query(_indexDB, &localOpts, err)); }); } }) ); } return(new CBForestQueryEnumerator(enumerator)); }
private void DeleteLocalRevision(string docId, string revId, bool obeyMVCC) { if (!docId.StartsWith("_local/")) { throw new CouchbaseLiteException("Local revision IDs must start with _local/", StatusCode.BadId); } if (obeyMVCC && revId == null) { // Didn't specify a revision to delete: NotFound or a Conflict, depending var gotLocalDoc = GetLocalDocument(docId, null); if (gotLocalDoc == null) { throw new CouchbaseLiteException(StatusCode.NotFound); } throw new CouchbaseLiteException(StatusCode.Conflict); } RunInTransaction(() => { WithC4Raw(docId, "_local", doc => { if (doc == null) { throw new CouchbaseLiteException(StatusCode.NotFound); } if (obeyMVCC && (revId != (string)doc->meta)) { throw new CouchbaseLiteException(StatusCode.Conflict); } ForestDBBridge.Check(err => Native.c4raw_put(Forest, "_local", docId, null, null, err)); }); return(true); }); }
private CBForestHistoryEnumerator GetHistoryEnumerator(RevisionInternal rev, int generation) { if (generation <= 1) { return(null); } var doc = default(C4Document *); try { doc = (C4Document *)ForestDBBridge.Check(err => Native.c4doc_get(Forest, rev.GetDocId(), true, err)); ForestDBBridge.Check(err => Native.c4doc_selectCurrentRevision(doc)); } catch (CBForestException e) { if (e.Domain == C4ErrorDomain.ForestDB && e.Code == (int)ForestDBStatus.KeyNotFound) { return(null); } throw; } return(new CBForestHistoryEnumerator(doc, false, true)); }
private C4Database *Reopen() { var forestPath = Path.Combine(Directory, DB_FILENAME); try { return((C4Database *)ForestDBBridge.Check(err => { var nativeKey = default(C4EncryptionKey); if (_encryptionKey != null) { nativeKey = new C4EncryptionKey(_encryptionKey.KeyData); } return Native.c4db_open(forestPath, _config, &nativeKey, err); })); } catch (CBForestException e) { if (e.Domain == C4ErrorDomain.ForestDB && e.Code == (int)ForestDBStatus.NoDbHeaders) { throw new CouchbaseLiteException(StatusCode.Unauthorized); } throw; } }
public IDictionary <string, object> PurgeRevisions(IDictionary <string, IList <string> > docsToRev) { // <http://wiki.apache.org/couchdb/Purge_Documents> IDictionary <string, object> result = new Dictionary <string, object>(); if (docsToRev.Count == 0) { return(result); } Log.D(TAG, "Purging {0} docs...", docsToRev.Count); RunInTransaction(() => { foreach (var docRevPair in docsToRev) { var docID = docRevPair.Key; WithC4Document(docID, null, false, false, doc => {; if (!doc->Exists) { throw new CouchbaseLiteException(StatusCode.NotFound); } var revsPurged = default(IList <string>); var revIDs = docRevPair.Value; if (revIDs.Count == 0) { revsPurged = new List <string>(); } else if (revIDs.Contains("*")) { // Delete all revisions if magic "*" revision ID is given: ForestDBBridge.Check(err => Native.c4db_purgeDoc(Forest, doc->docID, err)); revsPurged = new List <string> { "*" }; Log.D(TAG, "Purged document '{0}'", docID); } else { var purged = new List <string>(); foreach (var revID in revIDs) { if (Native.c4doc_purgeRevision(doc, revID, null) > 0) { purged.Add(revID); } } if (purged.Count > 0) { ForestDBBridge.Check(err => Native.c4doc_save(doc, (uint)MaxRevTreeDepth, err)); Log.D(TAG, "Purged doc '{0}' revs {1}", docID, Manager.GetObjectMapper().WriteValueAsString(revIDs)); } revsPurged = purged; } result[docID] = revsPurged; }); } return(true); }); return(result); }
public void DeleteIndex() { ForestDBBridge.Check(err => Native.c4view_eraseIndex(_indexDB, err)); }
public void Compact() { ForestDBBridge.Check(err => Native.c4db_compact(Forest, err)); }
public void DeleteView() { _dbStorage.ForgetViewStorage(Name); ForestDBBridge.Check(err => Native.c4view_delete(_indexDB, err)); }
public bool UpdateIndexes(IEnumerable <IViewStore> views) { Log.D(TAG, "Checking indexes of ({0}) for {1}", ViewNames(views), Name); // Creates an array of tuples -> [[view1, view1 last sequence, view1 native handle], // [view2, view2 last sequence, view2 native handle], ...] var viewsArray = views.Cast <ForestDBViewStore>().ToArray(); var viewInfo = viewsArray.Select(x => Tuple.Create(x, x.LastSequenceIndexed)).ToArray(); var nativeViews = new C4View *[viewsArray.Length]; for (int i = 0; i < viewsArray.Length; i++) { nativeViews[i] = viewsArray[i]._indexDB; } var indexer = (C4Indexer *)ForestDBBridge.Check(err => Native.c4indexer_begin(_dbStorage.Forest, nativeViews, err)); var enumerator = new CBForestDocEnumerator(indexer); var commit = false; try { foreach (var next in enumerator) { var seq = next.SelectedRev.sequence; for (int i = 0; i < viewInfo.Length; i++) { var info = viewInfo[i]; if (seq <= (ulong)info.Item2) { continue; // This view has already indexed this sequence } var viewDelegate = info.Item1.Delegate; if (viewDelegate == null || viewDelegate.Map == null) { Log.V(TAG, " {0} has no map block; skipping it", info.Item1.Name); continue; } var rev = new RevisionInternal(next, true); var keys = new List <object>(); var values = new List <string>(); try { viewDelegate.Map(rev.GetProperties(), (key, value) => { keys.Add(key); values.Add(Manager.GetObjectMapper().WriteValueAsString(value)); }); } catch (Exception e) { Log.W(TAG, String.Format("Exception thrown in map function of {0}", info.Item1.Name), e); continue; } WithC4Keys(keys.ToArray(), true, c4keys => ForestDBBridge.Check(err => Native.c4indexer_emit(indexer, next.Document, (uint)i, c4keys, values.ToArray(), err)) ); } } commit = true; } catch (Exception e) { Log.W(TAG, "Error updates indexes", e); } finally { ForestDBBridge.Check(err => Native.c4indexer_end(indexer, commit, err)); } return(true); }
public void SetInfo(string key, string info) { ForestDBBridge.Check(err => Native.c4raw_put(Forest, "info", key, null, info, err)); }
private void LoadRevisionBody(CBForestDocStatus status) { ForestDBBridge.Check(err => Native.c4doc_loadRevisionBody(status.GetDocument(), err)); }
private void SelectCurrentRevision(CBForestDocStatus status) { ForestDBBridge.Check(err => Native.c4doc_selectCurrentRevision(status.GetDocument())); }
public RevisionInternal PutRevision(string inDocId, string inPrevRevId, IDictionary <string, object> properties, bool deleting, bool allowConflict, StoreValidation validationBlock) { if (_config.HasFlag(C4DatabaseFlags.ReadOnly)) { throw new CouchbaseLiteException("Attempting to write to a readonly database", StatusCode.Forbidden); } var json = default(string); if (properties != null) { json = Manager.GetObjectMapper().WriteValueAsString(Database.StripDocumentJSON(properties), true); } else { json = "{}"; } if (inDocId == null) { inDocId = Misc.CreateGUID(); } var putRev = default(RevisionInternal); var change = default(DocumentChange); var success = RunInTransaction(() => { var docId = inDocId; var prevRevId = inPrevRevId; var transactionSuccess = false; WithC4Document(docId, null, false, true, doc => { if (prevRevId != null) { // Updating an existing revision; make sure it exists and is a leaf: ForestDBBridge.Check(err => Native.c4doc_selectRevision(doc, prevRevId, false, err)); if (!allowConflict && !doc->selectedRev.IsLeaf) { throw new CouchbaseLiteException(StatusCode.Conflict); } } else { // No parent revision given: if (deleting) { // Didn't specify a revision to delete: NotFound or a Conflict, depending throw new CouchbaseLiteException(doc->Exists ? StatusCode.Conflict : StatusCode.NotFound); } // If doc exists, current rev must be in a deleted state or there will be a conflict: if (Native.c4doc_selectCurrentRevision(doc)) { if (doc->selectedRev.IsDeleted) { // New rev will be child of the tombstone: prevRevId = (string)doc->revID; } else { throw new CouchbaseLiteException(StatusCode.Conflict); } } } // Compute the new revID. (Can't be done earlier because prevRevID may have changed.) var newRevID = Delegate != null ? Delegate.GenerateRevID(Encoding.UTF8.GetBytes(json), deleting, prevRevId) : null; if (newRevID == null) { throw new CouchbaseLiteException(StatusCode.BadId); } putRev = new RevisionInternal(docId, newRevID, deleting); if (properties != null) { properties["_id"] = docId; properties["_rev"] = newRevID; putRev.SetProperties(properties); } // Run any validation blocks: if (validationBlock != null) { var prevRev = default(RevisionInternal); if (prevRevId != null) { prevRev = new RevisionInternal(docId, prevRevId, doc->selectedRev.IsDeleted); } var status = validationBlock(putRev, prevRev, prevRevId); if (status.IsError) { throw new CouchbaseLiteException(String.Format("{0} failed validation", putRev), status.Code); } } // Add the revision to the database: ForestDBBridge.Check(err => Native.c4doc_insertRevision(doc, newRevID, json, deleting, putRev.GetAttachments() != null, allowConflict, err)); var isWinner = SaveDocument(doc, newRevID, properties); putRev.SetSequence((long)doc->sequence); change = ChangeWithNewRevision(putRev, isWinner, doc, null); transactionSuccess = true; }); return(transactionSuccess); }); if (!success) { return(null); } if (Delegate != null && change != null) { Delegate.DatabaseStorageChanged(change); } return(putRev); }