internal void UpdateIndex() { Log.V(Database.Tag, "Re-indexing view " + Name + " ..."); System.Diagnostics.Debug.Assert((Map != null)); if (Id < 0) { var msg = string.Format("View.Id < 0"); throw new CouchbaseLiteException(msg, new Status(StatusCode.NotFound)); } Database.BeginTransaction(); var result = new Status(StatusCode.InternalServerError); Cursor cursor = null; try { var lastSequence = LastSequenceIndexed; var dbMaxSequence = Database.LastSequenceNumber; if (lastSequence == dbMaxSequence) { // nothing to do (eg, kCBLStatusNotModified) var msg = String.Format("lastSequence ({0}) == dbMaxSequence ({1}), nothing to do", lastSequence, dbMaxSequence); Log.D(Database.Tag, msg); result.SetCode(StatusCode.Ok); return; } // First remove obsolete emitted results from the 'maps' table: var sequence = lastSequence; if (lastSequence < 0) { var msg = string.Format("lastSequence < 0 ({0})", lastSequence); throw new CouchbaseLiteException(msg, new Status(StatusCode.InternalServerError)); } if (lastSequence == 0) { // If the lastSequence has been reset to 0, make sure to remove // any leftover rows: var whereArgs = new string[] { Sharpen.Extensions.ToString(Id) }; Database.StorageEngine.Delete("maps", "view_id=@", whereArgs); } else { // Delete all obsolete map results (ones from since-replaced // revisions): var args = new [] { Id.ToString(), lastSequence.ToString(), lastSequence.ToString() }; Database.StorageEngine.ExecSQL( "DELETE FROM maps WHERE view_id=@ AND sequence IN (" + "SELECT parent FROM revs WHERE sequence>@ " + "AND parent>0 AND parent<=@)", args); } var deleted = 0; cursor = Database.StorageEngine.RawQuery("SELECT changes()", null); // TODO: Convert to ADO params. cursor.MoveToNext(); deleted = cursor.GetInt(0); cursor.Close(); // find a better way to propagate this back // Now scan every revision added since the last time the view was // indexed: var selectArgs = new[] { Convert.ToString(lastSequence) }; cursor = Database.StorageEngine.RawQuery("SELECT revs.doc_id, sequence, docid, revid, json FROM revs, docs " + "WHERE sequence>@ AND current!=0 AND deleted=0 " + "AND revs.doc_id = docs.doc_id " + "ORDER BY revs.doc_id, revid DESC", CommandBehavior.SequentialAccess, selectArgs); cursor.MoveToNext(); var lastDocID = 0L; while (!cursor.IsAfterLast()) { long docID = cursor.GetLong(0); if (docID != lastDocID) { // Only look at the first-iterated revision of any document, // because this is the // one with the highest revid, hence the "winning" revision // of a conflict. lastDocID = docID; // Reconstitute the document as a dictionary: sequence = cursor.GetLong(1); string docId = cursor.GetString(2); if (docId.StartsWith("_design/", StringCompare.IgnoreCase)) { // design docs don't get indexed! cursor.MoveToNext(); continue; } var revId = cursor.GetString(3); var json = cursor.GetBlob(4); var properties = Database.DocumentPropertiesFromJSON( json, docId, revId, false, sequence, EnumSet.NoneOf<TDContentOptions>() ); if (properties != null) { // Call the user-defined map() to emit new key/value // pairs from this revision: Log.V(Database.Tag, " call map for sequence=" + System.Convert.ToString(sequence )); // This is the emit() block, which gets called from within the // user-defined map() block // that's called down below. var enclosingView = this; var thisSequence = sequence; var map = Map; if (map == null) throw new CouchbaseLiteException("Map function is missing."); EmitDelegate emitBlock = (key, value) => { // TODO: Do we need to do any null checks on key or value? try { var keyJson = Manager.GetObjectMapper().WriteValueAsString(key); var valueJson = value == null ? null : Manager.GetObjectMapper().WriteValueAsString(value) ; Log.V(Database.Tag, String.Format(" emit({0}, {1})", keyJson, valueJson)); var insertValues = new ContentValues(); insertValues.Put("view_id", enclosingView.Id); insertValues["sequence"] = thisSequence; insertValues["key"] = keyJson; insertValues["value"] = valueJson; enclosingView.Database.StorageEngine.Insert("maps", null, insertValues); } catch (Exception e) { Log.E(Database.Tag, "Error emitting", e); } }; map(properties, emitBlock); } } cursor.MoveToNext(); } // Finally, record the last revision sequence number that was // indexed: ContentValues updateValues = new ContentValues(); updateValues["lastSequence"] = dbMaxSequence; var whereArgs_1 = new string[] { Sharpen.Extensions.ToString(Id) }; Database.StorageEngine.Update("views", updateValues, "view_id=@", whereArgs_1); // FIXME actually count number added :) Log.V(Database.Tag, "...Finished re-indexing view " + Name + " up to sequence " + System.Convert.ToString(dbMaxSequence) + " (deleted " + deleted + " added " + "?" + ")"); result.SetCode(StatusCode.Ok); } catch (SQLException e) { throw new CouchbaseLiteException(e, new Status(StatusCode.DbError)); } finally { if (cursor != null) { cursor.Close(); } if (!result.IsSuccessful()) { Log.W(Database.Tag, "Failed to rebuild view " + Name + ": " + result.GetCode()); } if (Database != null) { Database.EndTransaction(result.IsSuccessful()); } } }
/// <summary>Stores a new (or initial) revision of a document.</summary> /// <remarks> /// Stores a new (or initial) revision of a document. /// This is what's invoked by a PUT or POST. As with those, the previous revision ID must be supplied when necessary and the call will fail if it doesn't match. /// </remarks> /// <param name="oldRev">The revision to add. If the docID is null, a new UUID will be assigned. Its revID must be null. It must have a JSON body. /// </param> /// <param name="prevRevId">The ID of the revision to replace (same as the "?rev=" parameter to a PUT), or null if this is a new document. /// </param> /// <param name="allowConflict">If false, an error status 409 will be returned if the insertion would create a conflict, i.e. if the previous revision already has a child. /// </param> /// <param name="resultStatus">On return, an HTTP status code indicating success or failure. /// </param> /// <returns>A new RevisionInternal with the docID, revID and sequence filled in (but no body). /// </returns> /// <exception cref="Couchbase.Lite.CouchbaseLiteException"></exception> internal RevisionInternal PutRevision(RevisionInternal oldRev, String prevRevId, Boolean allowConflict, Status resultStatus) { // prevRevId is the rev ID being replaced, or nil if an insert var docId = oldRev.GetDocId(); var deleted = oldRev.IsDeleted(); if ((oldRev == null) || ((prevRevId != null) && (docId == null)) || (deleted && (docId == null)) || ((docId != null) && !IsValidDocumentId(docId))) { throw new CouchbaseLiteException(StatusCode.BadRequest); } BeginTransaction(); Cursor cursor = null; var inConflict = false; RevisionInternal winningRev = null; RevisionInternal newRev = null; // PART I: In which are performed lookups and validations prior to the insert... var docNumericID = (docId != null) ? GetDocNumericID(docId) : 0; var parentSequence = 0L; string oldWinningRevID = null; try { var oldWinnerWasDeletion = false; var wasConflicted = false; if (docNumericID > 0) { var outIsDeleted = new AList<bool>(); var outIsConflict = new AList<bool>(); try { oldWinningRevID = WinningRevIDOfDoc(docNumericID, outIsDeleted, outIsConflict); oldWinnerWasDeletion |= outIsDeleted.Count > 0; wasConflicted |= outIsConflict.Count > 0; } catch (Exception e) { Sharpen.Runtime.PrintStackTrace(e); } } if (prevRevId != null) { // Replacing: make sure given prevRevID is current & find its sequence number: if (docNumericID <= 0) { var msg = string.Format("No existing revision found with doc id: {0}", docId); throw new CouchbaseLiteException(msg, StatusCode.NotFound); } parentSequence = GetSequenceOfDocument(docNumericID, prevRevId, !allowConflict); if (parentSequence == 0) { // Not found: either a 404 or a 409, depending on whether there is any current revision if (!allowConflict && ExistsDocumentWithIDAndRev(docId, null)) { var msg = string.Format("Conflicts not allowed and there is already an existing doc with id: {0}", docId); throw new CouchbaseLiteException(msg, StatusCode.Conflict); } else { var msg = string.Format("No existing revision found with doc id: {0}", docId); throw new CouchbaseLiteException(msg, StatusCode.NotFound); } } if (_validations != null && _validations.Count > 0) { // Fetch the previous revision and validate the new one against it: var oldRevCopy = oldRev.CopyWithDocID(oldRev.GetDocId(), null); var prevRev = new RevisionInternal(docId, prevRevId, false, this); ValidateRevision(oldRevCopy, prevRev, prevRevId); } } else { // Inserting first revision. if (deleted && (docId != null)) { // Didn't specify a revision to delete: 404 or a 409, depending if (ExistsDocumentWithIDAndRev(docId, null)) { throw new CouchbaseLiteException(StatusCode.Conflict); } else { throw new CouchbaseLiteException(StatusCode.NotFound); } } // Validate: ValidateRevision(oldRev, null, null); if (docId != null) { // Inserting first revision, with docID given (PUT): if (docNumericID <= 0) { // Doc doesn't exist at all; create it: docNumericID = InsertDocumentID(docId); if (docNumericID <= 0) { return null; } } else { // Doc ID exists; check whether current winning revision is deleted: if (oldWinnerWasDeletion) { prevRevId = oldWinningRevID; parentSequence = GetSequenceOfDocument(docNumericID, prevRevId, false); } else { if (oldWinningRevID != null) { // The current winning revision is not deleted, so this is a conflict throw new CouchbaseLiteException(StatusCode.Conflict); } } } } else { // Inserting first revision, with no docID given (POST): generate a unique docID: docId = Database.GenerateDocumentId(); docNumericID = InsertDocumentID(docId); if (docNumericID <= 0) { return null; } } } // There may be a conflict if (a) the document was already in conflict, or // (b) a conflict is created by adding a non-deletion child of a non-winning rev. inConflict = wasConflicted || (!deleted && prevRevId != null && oldWinningRevID != null && !prevRevId.Equals(oldWinningRevID)); // PART II: In which we prepare for insertion... // Get the attachments: var attachments = GetAttachmentsFromRevision(oldRev); // Bump the revID and update the JSON: IList<byte> json = null; if(!oldRev.IsDeleted()) //oldRev.GetProperties() != null && oldRev.GetProperties().Any()) { json = EncodeDocumentJSON(oldRev).ToList(); if (json == null) { // bad or missing json throw new CouchbaseLiteException(StatusCode.BadRequest); } if (json.Count() == 2 && json[0] == '{' && json[1] == '}') { json = null; } } else { json = Encoding.UTF8.GetBytes("{}"); // NOTE.ZJG: Confirm w/ Traun. This prevents a null reference exception in call to InsertRevision below. } var newRevId = GenerateIDForRevision(oldRev, json, attachments, prevRevId); newRev = oldRev.CopyWithDocID(docId, newRevId); StubOutAttachmentsInRevision(attachments, newRev); // Now insert the rev itself: var newSequence = InsertRevision(newRev, docNumericID, parentSequence, true, (attachments.Count > 0), json); if (newSequence == 0) { return null; } // Make replaced rev non-current: try { var args = new ContentValues(); args["current"] = 0; StorageEngine.Update("revs", args, "sequence=?", new[] { parentSequence.ToString() }); } catch (SQLException e) { Log.E(Database.Tag, "Error setting parent rev non-current", e); throw new CouchbaseLiteException(StatusCode.InternalServerError); } // Store any attachments: if (attachments != null) { ProcessAttachmentsForRevision(attachments, newRev, parentSequence); } // Figure out what the new winning rev ID is: winningRev = Winner(docNumericID, oldWinningRevID, oldWinnerWasDeletion, newRev); // Success! if (deleted) { resultStatus.SetCode(StatusCode.Ok); } else { resultStatus.SetCode(StatusCode.Created); } } catch (SQLException e1) { Log.E(Tag, "Error putting revision", e1); return null; } finally { if (cursor != null) { cursor.Close(); } EndTransaction(resultStatus.IsSuccessful); if (!string.IsNullOrEmpty(docId)) { UnsavedRevisionDocumentCache.Remove(docId); } } // EPILOGUE: A change notification is sent... NotifyChange(newRev, winningRev, null, inConflict); return newRev; }
/// <exception cref="Couchbase.Lite.CouchbaseLiteException"></exception> internal RevisionInternal LoadRevisionBody(RevisionInternal rev, DocumentContentOptions contentOptions) { if (rev.GetBody() != null && contentOptions == DocumentContentOptions.None && rev.GetSequence() != 0) { return rev; } if ((rev.GetDocId() == null) || (rev.GetRevId() == null)) { Log.E(Database.Tag, "Error loading revision body"); throw new CouchbaseLiteException(StatusCode.PreconditionFailed); } Cursor cursor = null; var result = new Status(StatusCode.NotFound); try { // TODO: on ios this query is: // TODO: "SELECT sequence, json FROM revs WHERE doc_id=@ AND revid=@ LIMIT 1" var sql = "SELECT sequence, json FROM revs, docs WHERE revid=? AND docs.docid=? AND revs.doc_id=docs.doc_id LIMIT 1"; var args = new [] { rev.GetRevId(), rev.GetDocId() }; cursor = StorageEngine.RawQuery(sql, CommandBehavior.SequentialAccess, args); if (cursor.MoveToNext()) { result.SetCode(StatusCode.Ok); rev.SetSequence(cursor.GetLong(0)); ExpandStoredJSONIntoRevisionWithAttachments(cursor.GetBlob(1), rev, contentOptions); } } catch (SQLException e) { Log.E(Tag, "Error loading revision body", e); throw new CouchbaseLiteException(StatusCode.InternalServerError); } finally { if (cursor != null) { cursor.Close(); } } if (result.GetCode() == StatusCode.NotFound) { throw new CouchbaseLiteException(result.GetCode()); } return rev; }
internal void UpdateIndex() { Log.I(Database.Tag, "Re-indexing view {0} ...", Name); System.Diagnostics.Debug.Assert((Map != null)); if (Id <= 0) { var msg = string.Format("View.Id <= 0"); throw new CouchbaseLiteException(msg, new Status(StatusCode.NotFound)); } var result = new Status(StatusCode.InternalServerError); Cursor cursor = null; Cursor cursor2 = null; try { Database.RunInTransaction(() => { var lastSequence = LastSequenceIndexed; var dbMaxSequence = Database.LastSequenceNumber; if (lastSequence >= dbMaxSequence) { // nothing to do (eg, kCBLStatusNotModified) Log.V(Database.Tag, "lastSequence ({0}) == dbMaxSequence ({1}), nothing to do", lastSequence, dbMaxSequence); result.SetCode(StatusCode.NotModified); return(false); } // First remove obsolete emitted results from the 'maps' table: var sequence = lastSequence; if (lastSequence < 0) { var msg = string.Format("lastSequence < 0 ({0})", lastSequence); throw new CouchbaseLiteException(msg, new Status(StatusCode.InternalServerError)); } if (lastSequence == 0) { // If the lastSequence has been reset to 0, make sure to remove // any leftover rows: var whereArgs = new string[] { Id.ToString() }; Database.StorageEngine.Delete("maps", "view_id=?", whereArgs); } else { Database.OptimizeSQLIndexes(); // Delete all obsolete map results (ones from since-replaced // revisions): var args = new [] { Id.ToString(), lastSequence.ToString(), lastSequence.ToString() }; Database.StorageEngine.ExecSQL( "DELETE FROM maps WHERE view_id=? AND sequence IN (" + "SELECT parent FROM revs WHERE sequence>? " + "AND +parent>0 AND +parent<=?)", args); } var deleted = 0; cursor = Database.StorageEngine.IntransactionRawQuery("SELECT changes()"); cursor.MoveToNext(); deleted = cursor.GetInt(0); cursor.Close(); // Find a better way to propagate this back // Now scan every revision added since the last time the view was indexed: var selectArgs = new[] { lastSequence.ToString(), dbMaxSequence.ToString() }; cursor = Database.StorageEngine.IntransactionRawQuery("SELECT revs.doc_id, sequence, docid, revid, json, no_attachments FROM revs, docs " + "WHERE sequence>? AND sequence<=? AND current!=0 AND deleted=0 " + "AND revs.doc_id = docs.doc_id " + "ORDER BY revs.doc_id, revid DESC", selectArgs); var lastDocID = 0L; var keepGoing = cursor.MoveToNext(); while (keepGoing) { long docID = cursor.GetLong(0); if (docID != lastDocID) { // Only look at the first-iterated revision of any document, // because this is the // one with the highest revid, hence the "winning" revision // of a conflict. lastDocID = docID; // Reconstitute the document as a dictionary: sequence = cursor.GetLong(1); string docId = cursor.GetString(2); if (docId.StartsWith("_design/", StringComparison.InvariantCultureIgnoreCase)) { // design docs don't get indexed! keepGoing = cursor.MoveToNext(); continue; } var revId = cursor.GetString(3); var json = cursor.GetBlob(4); var noAttachments = cursor.GetInt(5) > 0; // Skip rows with the same doc_id -- these are losing conflicts. while ((keepGoing = cursor.MoveToNext()) && cursor.GetLong(0) == docID) { } if (lastSequence > 0) { // Find conflicts with documents from previous indexings. var selectArgs2 = new[] { Convert.ToString(docID), Convert.ToString(lastSequence) }; cursor2 = Database.StorageEngine.IntransactionRawQuery("SELECT revid, sequence FROM revs " + "WHERE doc_id=? AND sequence<=? AND current!=0 AND deleted=0 " + "ORDER BY revID DESC " + "LIMIT 1", selectArgs2); if (cursor2.MoveToNext()) { var oldRevId = cursor2.GetString(0); // This is the revision that used to be the 'winner'. // Remove its emitted rows: var oldSequence = cursor2.GetLong(1); var args = new[] { Sharpen.Extensions.ToString(Id), Convert.ToString(oldSequence) }; Database.StorageEngine.ExecSQL("DELETE FROM maps WHERE view_id=? AND sequence=?", args); if (RevisionInternal.CBLCompareRevIDs(oldRevId, revId) > 0) { // It still 'wins' the conflict, so it's the one that // should be mapped [again], not the current revision! revId = oldRevId; sequence = oldSequence; var selectArgs3 = new[] { Convert.ToString(sequence) }; json = Misc.ByteArrayResultForQuery( Database.StorageEngine, "SELECT json FROM revs WHERE sequence=?", selectArgs3 ); } } cursor2.Close(); cursor2 = null; } // Get the document properties, to pass to the map function: var contentOptions = DocumentContentOptions.None; if (noAttachments) { contentOptions |= DocumentContentOptions.NoAttachments; } var properties = Database.DocumentPropertiesFromJSON( json, docId, revId, false, sequence, DocumentContentOptions.None ); if (properties != null) { // Call the user-defined map() to emit new key/value // pairs from this revision: // This is the emit() block, which gets called from within the // user-defined map() block // that's called down below. var enclosingView = this; var thisSequence = sequence; var map = Map; if (map == null) { throw new CouchbaseLiteException("Map function is missing."); } EmitDelegate emitBlock = (key, value) => { // TODO: Do we need to do any null checks on key or value? try { var keyJson = Manager.GetObjectMapper().WriteValueAsString(key); var valueJson = value == null ? null : Manager.GetObjectMapper().WriteValueAsString(value); var insertValues = new ContentValues(); insertValues.Put("view_id", enclosingView.Id); insertValues["sequence"] = thisSequence; insertValues["key"] = keyJson; insertValues["value"] = valueJson; enclosingView.Database.StorageEngine.Insert("maps", null, insertValues); } catch (Exception e) { Log.E(Database.Tag, "Error emitting", e); } }; map(properties, emitBlock); } } else { keepGoing = cursor.MoveToNext(); } } // Finally, record the last revision sequence number that was // indexed: var updateValues = new ContentValues(); updateValues["lastSequence"] = dbMaxSequence; var whereArgs_1 = new string[] { Id.ToString() }; Database.StorageEngine.Update("views", updateValues, "view_id=?", whereArgs_1); // FIXME actually count number added :) Log.V(Database.Tag, "...Finished re-indexing view {0} up to sequence {1} (deleted {2} added ?)", Name, Convert.ToString(dbMaxSequence), deleted); result.SetCode(StatusCode.Ok); return(true); }); } catch (Exception e) { throw new CouchbaseLiteException(e, new Status(StatusCode.DbError)); } finally { if (cursor2 != null) { cursor2.Close(); } if (cursor != null) { cursor.Close(); } if (!result.IsSuccessful) { Log.W(Database.Tag, "Failed to rebuild view {0}:{1}", Name, result.GetCode()); } } }
internal Status DeleteViewNamed(String name) { var result = new Status(StatusCode.InternalServerError); try { var whereArgs = new [] { name }; var rowsAffected = StorageEngine.Delete("views", "name=?", whereArgs); if (rowsAffected > 0) { result.SetCode(StatusCode.Ok); } else { result.SetCode(StatusCode.NotFound); } } catch (SQLException e) { Log.E(Tag, "Error deleting view", e); } return result; }
public Status DeleteViewNamed(string name) { Status result = new Status(Status.InternalServerError); try { string[] whereArgs = new string[] { name }; int rowsAffected = database.Delete("views", "name=?", whereArgs); if (rowsAffected > 0) { result.SetCode(Status.Ok); } else { result.SetCode(Status.NotFound); } } catch (SQLException e) { Log.E(Database.Tag, "Error deleting view", e); } return result; }
public RevisionInternal PutRevision(RevisionInternal oldRev, string prevRevId, bool allowConflict, Status resultStatus) { // prevRevId is the rev ID being replaced, or nil if an insert string docId = oldRev.GetDocId(); bool deleted = oldRev.IsDeleted(); if ((oldRev == null) || ((prevRevId != null) && (docId == null)) || (deleted && ( docId == null)) || ((docId != null) && !IsValidDocumentId(docId))) { throw new CouchbaseLiteException(Status.BadRequest); } BeginTransaction(); Cursor cursor = null; bool inConflict = false; RevisionInternal winningRev = null; RevisionInternal newRev = null; //// PART I: In which are performed lookups and validations prior to the insert... long docNumericID = (docId != null) ? GetDocNumericID(docId) : 0; long parentSequence = 0; string oldWinningRevID = null; try { bool oldWinnerWasDeletion = false; bool wasConflicted = false; if (docNumericID > 0) { IList<bool> outIsDeleted = new AList<bool>(); IList<bool> outIsConflict = new AList<bool>(); try { oldWinningRevID = WinningRevIDOfDoc(docNumericID, outIsDeleted, outIsConflict); if (outIsDeleted.Count > 0) { oldWinnerWasDeletion = true; } if (outIsConflict.Count > 0) { wasConflicted = true; } } catch (Exception e) { Sharpen.Runtime.PrintStackTrace(e); } } if (prevRevId != null) { // Replacing: make sure given prevRevID is current & find its sequence number: if (docNumericID <= 0) { string msg = string.Format("No existing revision found with doc id: %s", docId); throw new CouchbaseLiteException(msg, Status.NotFound); } string[] args = new string[] { System.Convert.ToString(docNumericID), prevRevId }; string additionalWhereClause = string.Empty; if (!allowConflict) { additionalWhereClause = "AND current=1"; } cursor = database.RawQuery("SELECT sequence FROM revs WHERE doc_id=? AND revid=? " + additionalWhereClause + " LIMIT 1", args); if (cursor.MoveToNext()) { parentSequence = cursor.GetLong(0); } if (parentSequence == 0) { // Not found: either a 404 or a 409, depending on whether there is any current revision if (!allowConflict && ExistsDocumentWithIDAndRev(docId, null)) { string msg = string.Format("Conflicts not allowed and there is already an existing doc with id: %s" , docId); throw new CouchbaseLiteException(msg, Status.Conflict); } else { string msg = string.Format("No existing revision found with doc id: %s", docId); throw new CouchbaseLiteException(msg, Status.NotFound); } } if (validations != null && validations.Count > 0) { // Fetch the previous revision and validate the new one against it: RevisionInternal prevRev = new RevisionInternal(docId, prevRevId, false, this); ValidateRevision(oldRev, prevRev); } // Make replaced rev non-current: ContentValues updateContent = new ContentValues(); updateContent.Put("current", 0); database.Update("revs", updateContent, "sequence=" + parentSequence, null); } else { // Inserting first revision. if (deleted && (docId != null)) { // Didn't specify a revision to delete: 404 or a 409, depending if (ExistsDocumentWithIDAndRev(docId, null)) { throw new CouchbaseLiteException(Status.Conflict); } else { throw new CouchbaseLiteException(Status.NotFound); } } // Validate: ValidateRevision(oldRev, null); if (docId != null) { // Inserting first revision, with docID given (PUT): if (docNumericID <= 0) { // Doc doesn't exist at all; create it: docNumericID = InsertDocumentID(docId); if (docNumericID <= 0) { return null; } } else { // Doc ID exists; check whether current winning revision is deleted: if (oldWinnerWasDeletion == true) { prevRevId = oldWinningRevID; parentSequence = GetSequenceOfDocument(docNumericID, prevRevId, false); } else { if (oldWinningRevID != null) { // The current winning revision is not deleted, so this is a conflict throw new CouchbaseLiteException(Status.Conflict); } } } } else { // Inserting first revision, with no docID given (POST): generate a unique docID: docId = Database.GenerateDocumentId(); docNumericID = InsertDocumentID(docId); if (docNumericID <= 0) { return null; } } } // There may be a conflict if (a) the document was already in conflict, or // (b) a conflict is created by adding a non-deletion child of a non-winning rev. inConflict = wasConflicted || (!deleted && prevRevId != null && oldWinningRevID != null && !prevRevId.Equals(oldWinningRevID)); //// PART II: In which insertion occurs... // Get the attachments: IDictionary<string, AttachmentInternal> attachments = GetAttachmentsFromRevision( oldRev); // Bump the revID and update the JSON: string newRevId = GenerateNextRevisionID(prevRevId); byte[] data = null; if (!oldRev.IsDeleted()) { data = EncodeDocumentJSON(oldRev); if (data == null) { // bad or missing json throw new CouchbaseLiteException(Status.BadRequest); } } newRev = oldRev.CopyWithDocID(docId, newRevId); StubOutAttachmentsInRevision(attachments, newRev); // Now insert the rev itself: long newSequence = InsertRevision(newRev, docNumericID, parentSequence, true, data ); if (newSequence == 0) { return null; } // Store any attachments: if (attachments != null) { ProcessAttachmentsForRevision(attachments, newRev, parentSequence); } // Figure out what the new winning rev ID is: winningRev = Winner(docNumericID, oldWinningRevID, oldWinnerWasDeletion, newRev); // Success! if (deleted) { resultStatus.SetCode(Status.Ok); } else { resultStatus.SetCode(Status.Created); } } catch (SQLException e1) { Log.E(Database.Tag, "Error putting revision", e1); return null; } finally { if (cursor != null) { cursor.Close(); } EndTransaction(resultStatus.IsSuccessful()); } //// EPILOGUE: A change notification is sent... NotifyChange(newRev, winningRev, null, inConflict); return newRev; }
public void UpdateIndex() { Log.V(Log.TagView, "Re-indexing view: %s", name); System.Diagnostics.Debug.Assert((mapBlock != null)); if (GetViewId() <= 0) { string msg = string.Format("getViewId() < 0"); throw new CouchbaseLiteException(msg, new Status(Status.NotFound)); } database.BeginTransaction(); Status result = new Status(Status.InternalServerError); Cursor cursor = null; try { long lastSequence = GetLastSequenceIndexed(); long dbMaxSequence = database.GetLastSequenceNumber(); if (lastSequence == dbMaxSequence) { // nothing to do (eg, kCBLStatusNotModified) Log.V(Log.TagView, "lastSequence (%s) == dbMaxSequence (%s), nothing to do", lastSequence , dbMaxSequence); result.SetCode(Status.NotModified); return; } // First remove obsolete emitted results from the 'maps' table: long sequence = lastSequence; if (lastSequence < 0) { string msg = string.Format("lastSequence < 0 (%s)", lastSequence); throw new CouchbaseLiteException(msg, new Status(Status.InternalServerError)); } if (lastSequence == 0) { // If the lastSequence has been reset to 0, make sure to remove // any leftover rows: string[] whereArgs = new string[] { Sharpen.Extensions.ToString(GetViewId()) }; database.GetDatabase().Delete("maps", "view_id=?", whereArgs); } else { // Delete all obsolete map results (ones from since-replaced // revisions): string[] args = new string[] { Sharpen.Extensions.ToString(GetViewId()), System.Convert.ToString (lastSequence), System.Convert.ToString(lastSequence) }; database.GetDatabase().ExecSQL("DELETE FROM maps WHERE view_id=? AND sequence IN (" + "SELECT parent FROM revs WHERE sequence>? " + "AND parent>0 AND parent<=?)", args); } int deleted = 0; cursor = database.GetDatabase().RawQuery("SELECT changes()", null); cursor.MoveToNext(); deleted = cursor.GetInt(0); cursor.Close(); // This is the emit() block, which gets called from within the // user-defined map() block // that's called down below. AbstractTouchMapEmitBlock emitBlock = new _AbstractTouchMapEmitBlock_428(this); //Log.v(Log.TAG_VIEW, " emit(" + keyJson + ", " // + valueJson + ")"); // find a better way to propagate this back // Now scan every revision added since the last time the view was // indexed: string[] selectArgs = new string[] { System.Convert.ToString(lastSequence) }; cursor = database.GetDatabase().RawQuery("SELECT revs.doc_id, sequence, docid, revid, json, no_attachments FROM revs, docs " + "WHERE sequence>? AND current!=0 AND deleted=0 " + "AND revs.doc_id = docs.doc_id " + "ORDER BY revs.doc_id, revid DESC", selectArgs); long lastDocID = 0; bool keepGoing = cursor.MoveToNext(); while (keepGoing) { long docID = cursor.GetLong(0); if (docID != lastDocID) { // Only look at the first-iterated revision of any document, // because this is the // one with the highest revid, hence the "winning" revision // of a conflict. lastDocID = docID; // Reconstitute the document as a dictionary: sequence = cursor.GetLong(1); string docId = cursor.GetString(2); if (docId.StartsWith("_design/")) { // design docs don't get indexed! keepGoing = cursor.MoveToNext(); continue; } string revId = cursor.GetString(3); byte[] json = cursor.GetBlob(4); bool noAttachments = cursor.GetInt(5) > 0; while ((keepGoing = cursor.MoveToNext()) && cursor.GetLong(0) == docID) { } // Skip rows with the same doc_id -- these are losing conflicts. if (lastSequence > 0) { // Find conflicts with documents from previous indexings. string[] selectArgs2 = new string[] { System.Convert.ToString(docID), System.Convert.ToString (lastSequence) }; Cursor cursor2 = database.GetDatabase().RawQuery("SELECT revid, sequence FROM revs " + "WHERE doc_id=? AND sequence<=? AND current!=0 AND deleted=0 " + "ORDER BY revID DESC " + "LIMIT 1", selectArgs2); if (cursor2.MoveToNext()) { string oldRevId = cursor2.GetString(0); // This is the revision that used to be the 'winner'. // Remove its emitted rows: long oldSequence = cursor2.GetLong(1); string[] args = new string[] { Sharpen.Extensions.ToString(GetViewId()), System.Convert.ToString (oldSequence) }; database.GetDatabase().ExecSQL("DELETE FROM maps WHERE view_id=? AND sequence=?", args); if (RevisionInternal.CBLCompareRevIDs(oldRevId, revId) > 0) { // It still 'wins' the conflict, so it's the one that // should be mapped [again], not the current revision! revId = oldRevId; sequence = oldSequence; string[] selectArgs3 = new string[] { System.Convert.ToString(sequence) }; json = Utils.ByteArrayResultForQuery(database.GetDatabase(), "SELECT json FROM revs WHERE sequence=?" , selectArgs3); } } } // Get the document properties, to pass to the map function: EnumSet <Database.TDContentOptions> contentOptions = EnumSet.NoneOf <Database.TDContentOptions >(); if (noAttachments) { contentOptions.AddItem(Database.TDContentOptions.TDNoAttachments); } IDictionary <string, object> properties = database.DocumentPropertiesFromJSON(json , docId, revId, false, sequence, contentOptions); if (properties != null) { // Call the user-defined map() to emit new key/value // pairs from this revision: emitBlock.SetSequence(sequence); mapBlock.Map(properties, emitBlock); } } } // Finally, record the last revision sequence number that was // indexed: ContentValues updateValues = new ContentValues(); updateValues.Put("lastSequence", dbMaxSequence); string[] whereArgs_1 = new string[] { Sharpen.Extensions.ToString(GetViewId()) }; database.GetDatabase().Update("views", updateValues, "view_id=?", whereArgs_1); // FIXME actually count number added :) Log.V(Log.TagView, "Finished re-indexing view: %s " + " up to sequence %s" + " (deleted %s added ?)" , name, dbMaxSequence, deleted); result.SetCode(Status.Ok); } catch (SQLException e) { throw new CouchbaseLiteException(e, new Status(Status.DbError)); } finally { if (cursor != null) { cursor.Close(); } if (!result.IsSuccessful()) { Log.W(Log.TagView, "Failed to rebuild view %s. Result code: %d", name, result.GetCode ()); } if (database != null) { database.EndTransaction(result.IsSuccessful()); } } }
public RevisionInternal LoadRevisionBody(RevisionInternal rev, EnumSet<Database.TDContentOptions > contentOptions) { if (rev.GetBody() != null && contentOptions == EnumSet.NoneOf<Database.TDContentOptions >() && rev.GetSequence() != 0) { return rev; } System.Diagnostics.Debug.Assert(((rev.GetDocId() != null) && (rev.GetRevId() != null ))); Cursor cursor = null; Status result = new Status(Status.NotFound); try { // TODO: on ios this query is: // TODO: "SELECT sequence, json FROM revs WHERE doc_id=? AND revid=? LIMIT 1" string sql = "SELECT sequence, json FROM revs, docs WHERE revid=? AND docs.docid=? AND revs.doc_id=docs.doc_id LIMIT 1"; string[] args = new string[] { rev.GetRevId(), rev.GetDocId() }; cursor = database.RawQuery(sql, args); if (cursor.MoveToNext()) { result.SetCode(Status.Ok); rev.SetSequence(cursor.GetLong(0)); ExpandStoredJSONIntoRevisionWithAttachments(cursor.GetBlob(1), rev, contentOptions ); } } catch (SQLException e) { Log.E(Database.Tag, "Error loading revision body", e); throw new CouchbaseLiteException(Status.InternalServerError); } finally { if (cursor != null) { cursor.Close(); } } if (result.GetCode() == Status.NotFound) { throw new CouchbaseLiteException(result); } return rev; }
internal void UpdateIndex() { Log.V(Database.Tag, "Re-indexing view {0} ...", Name); System.Diagnostics.Debug.Assert((Map != null)); if (Id <= 0) { var msg = string.Format("View.Id <= 0"); throw new CouchbaseLiteException(msg, new Status(StatusCode.NotFound)); } Database.BeginTransaction(); var result = new Status(StatusCode.InternalServerError); Cursor cursor = null; try { var lastSequence = LastSequenceIndexed; var dbMaxSequence = Database.LastSequenceNumber; if (lastSequence == dbMaxSequence) { // nothing to do (eg, kCBLStatusNotModified) Log.V(Database.Tag, "lastSequence ({0}) == dbMaxSequence ({1}), nothing to do", lastSequence, dbMaxSequence); result.SetCode(StatusCode.NotModified); return; } // First remove obsolete emitted results from the 'maps' table: var sequence = lastSequence; if (lastSequence < 0) { var msg = string.Format("lastSequence < 0 ({0})", lastSequence); throw new CouchbaseLiteException(msg, new Status(StatusCode.InternalServerError)); } if (lastSequence == 0) { // If the lastSequence has been reset to 0, make sure to remove // any leftover rows: var whereArgs = new string[] { Id.ToString() }; Database.StorageEngine.Delete("maps", "view_id=?", whereArgs); } else { // Delete all obsolete map results (ones from since-replaced // revisions): var args = new [] { Id.ToString(), lastSequence.ToString(), lastSequence.ToString() }; Database.StorageEngine.ExecSQL( "DELETE FROM maps WHERE view_id=? AND sequence IN (" + "SELECT parent FROM revs WHERE sequence>? " + "AND parent>0 AND parent<=?)", args); } var deleted = 0; cursor = Database.StorageEngine.RawQuery("SELECT changes()"); cursor.MoveToNext(); deleted = cursor.GetInt(0); cursor.Close(); // Find a better way to propagate this back // Now scan every revision added since the last time the view was indexed: var selectArgs = new[] { lastSequence.ToString() }; cursor = Database.StorageEngine.RawQuery("SELECT revs.doc_id, sequence, docid, revid, json, no_attachments FROM revs, docs " + "WHERE sequence>? AND current!=0 AND deleted=0 " + "AND revs.doc_id = docs.doc_id " + "ORDER BY revs.doc_id, revid DESC", CommandBehavior.SequentialAccess, selectArgs); var lastDocID = 0L; var keepGoing = cursor.MoveToNext(); while (keepGoing) { long docID = cursor.GetLong(0); if (docID != lastDocID) { // Only look at the first-iterated revision of any document, // because this is the // one with the highest revid, hence the "winning" revision // of a conflict. lastDocID = docID; // Reconstitute the document as a dictionary: sequence = cursor.GetLong(1); string docId = cursor.GetString(2); if (docId.StartsWith("_design/", StringComparison.InvariantCultureIgnoreCase)) { // design docs don't get indexed! keepGoing = cursor.MoveToNext(); continue; } var revId = cursor.GetString(3); var json = cursor.GetBlob(4); var noAttachments = cursor.GetInt(5) > 0; // Skip rows with the same doc_id -- these are losing conflicts. while ((keepGoing = cursor.MoveToNext()) && cursor.GetLong(0) == docID) { } if (lastSequence > 0) { // Find conflicts with documents from previous indexings. var selectArgs2 = new[] { Convert.ToString(docID), Convert.ToString(lastSequence) }; var cursor2 = Database.StorageEngine.RawQuery("SELECT revid, sequence FROM revs " + "WHERE doc_id=? AND sequence<=? AND current!=0 AND deleted=0 " + "ORDER BY revID DESC " + "LIMIT 1", selectArgs2); if (cursor2.MoveToNext()) { var oldRevId = cursor2.GetString(0); // This is the revision that used to be the 'winner'. // Remove its emitted rows: var oldSequence = cursor2.GetLong(1); var args = new[] { Sharpen.Extensions.ToString(Id), Convert.ToString(oldSequence) }; Database.StorageEngine.ExecSQL("DELETE FROM maps WHERE view_id=? AND sequence=?", args); if (RevisionInternal.CBLCompareRevIDs(oldRevId, revId) > 0) { // It still 'wins' the conflict, so it's the one that // should be mapped [again], not the current revision! revId = oldRevId; sequence = oldSequence; var selectArgs3 = new[] { Convert.ToString(sequence) }; json = Misc.ByteArrayResultForQuery( Database.StorageEngine, "SELECT json FROM revs WHERE sequence=?", selectArgs3 ); } } } // Get the document properties, to pass to the map function: var contentOptions = DocumentContentOptions.None; if (noAttachments) { contentOptions |= DocumentContentOptions.NoAttachments; } var properties = Database.DocumentPropertiesFromJSON( json, docId, revId, false, sequence, DocumentContentOptions.None ); if (properties != null) { // Call the user-defined map() to emit new key/value // pairs from this revision: // This is the emit() block, which gets called from within the // user-defined map() block // that's called down below. var enclosingView = this; var thisSequence = sequence; var map = Map; if (map == null) throw new CouchbaseLiteException("Map function is missing."); EmitDelegate emitBlock = (key, value) => { // TODO: Do we need to do any null checks on key or value? try { var keyJson = Manager.GetObjectMapper().WriteValueAsString(key); var valueJson = value == null ? null : Manager.GetObjectMapper().WriteValueAsString(value) ; var insertValues = new ContentValues(); insertValues.Put("view_id", enclosingView.Id); insertValues["sequence"] = thisSequence; insertValues["key"] = keyJson; insertValues["value"] = valueJson; enclosingView.Database.StorageEngine.Insert("maps", null, insertValues); // // According to the issue #81, it is possible that there will be another // thread inserting a new revision to the database at the same time that // the UpdateIndex operation is running. This event should be guarded by // the database transaction that the code begun but apparently it was not. // As a result, it is possible that dbMaxSequence will be out of date at // this point and could cause the last indexed sequence to be out of track // from the obsolete map entry cleanup operation, which eventually results // to duplicated documents in the indexed map. // // To prevent the issue above, as a workaroubd, we need to make sure that // we have the current max sequence of the indexed documents updated. // This diverts from the CBL's Android code which doesn't have the same issue // as the Android doesn't allow multiple thread to interact with the database // at the same time. if (thisSequence > dbMaxSequence) { dbMaxSequence = thisSequence; } } catch (Exception e) { Log.E(Database.Tag, "Error emitting", e); } }; map(properties, emitBlock); } } } // Finally, record the last revision sequence number that was // indexed: var updateValues = new ContentValues(); updateValues["lastSequence"] = dbMaxSequence; var whereArgs_1 = new string[] { Id.ToString() }; Database.StorageEngine.Update("views", updateValues, "view_id=?", whereArgs_1); // FIXME actually count number added :) Log.V(Database.Tag, "...Finished re-indexing view {0} up to sequence {1} (deleted {2} added ?)", Name, Convert.ToString(dbMaxSequence), deleted); result.SetCode(StatusCode.Ok); } catch (Exception e) { throw new CouchbaseLiteException(e, new Status(StatusCode.DbError)); } finally { if (cursor != null) { cursor.Close(); } if (!result.IsSuccessful) { Log.W(Database.Tag, "Failed to rebuild view {0}:{1}", Name, result.GetCode()); } if (Database != null) { Database.EndTransaction(result.IsSuccessful); } } }
public void UpdateIndex() { Log.V(Log.TagView, "Re-indexing view: %s", name); System.Diagnostics.Debug.Assert((mapBlock != null)); if (GetViewId() <= 0) { string msg = string.Format("getViewId() < 0"); throw new CouchbaseLiteException(msg, new Status(Status.NotFound)); } database.BeginTransaction(); Status result = new Status(Status.InternalServerError); Cursor cursor = null; try { long lastSequence = GetLastSequenceIndexed(); long dbMaxSequence = database.GetLastSequenceNumber(); if (lastSequence == dbMaxSequence) { // nothing to do (eg, kCBLStatusNotModified) Log.V(Log.TagView, "lastSequence (%s) == dbMaxSequence (%s), nothing to do", lastSequence , dbMaxSequence); result.SetCode(Status.NotModified); return; } // First remove obsolete emitted results from the 'maps' table: long sequence = lastSequence; if (lastSequence < 0) { string msg = string.Format("lastSequence < 0 (%s)", lastSequence); throw new CouchbaseLiteException(msg, new Status(Status.InternalServerError)); } if (lastSequence == 0) { // If the lastSequence has been reset to 0, make sure to remove // any leftover rows: string[] whereArgs = new string[] { Sharpen.Extensions.ToString(GetViewId()) }; database.GetDatabase().Delete("maps", "view_id=?", whereArgs); } else { // Delete all obsolete map results (ones from since-replaced // revisions): string[] args = new string[] { Sharpen.Extensions.ToString(GetViewId()), System.Convert.ToString (lastSequence), System.Convert.ToString(lastSequence) }; database.GetDatabase().ExecSQL("DELETE FROM maps WHERE view_id=? AND sequence IN (" + "SELECT parent FROM revs WHERE sequence>? " + "AND parent>0 AND parent<=?)", args); } int deleted = 0; cursor = database.GetDatabase().RawQuery("SELECT changes()", null); cursor.MoveToNext(); deleted = cursor.GetInt(0); cursor.Close(); // This is the emit() block, which gets called from within the // user-defined map() block // that's called down below. AbstractTouchMapEmitBlock emitBlock = new _AbstractTouchMapEmitBlock_428(this); //Log.v(Log.TAG_VIEW, " emit(" + keyJson + ", " // + valueJson + ")"); // find a better way to propagate this back // Now scan every revision added since the last time the view was // indexed: string[] selectArgs = new string[] { System.Convert.ToString(lastSequence) }; cursor = database.GetDatabase().RawQuery("SELECT revs.doc_id, sequence, docid, revid, json, no_attachments FROM revs, docs " + "WHERE sequence>? AND current!=0 AND deleted=0 " + "AND revs.doc_id = docs.doc_id " + "ORDER BY revs.doc_id, revid DESC", selectArgs); long lastDocID = 0; bool keepGoing = cursor.MoveToNext(); while (keepGoing) { long docID = cursor.GetLong(0); if (docID != lastDocID) { // Only look at the first-iterated revision of any document, // because this is the // one with the highest revid, hence the "winning" revision // of a conflict. lastDocID = docID; // Reconstitute the document as a dictionary: sequence = cursor.GetLong(1); string docId = cursor.GetString(2); if (docId.StartsWith("_design/")) { // design docs don't get indexed! keepGoing = cursor.MoveToNext(); continue; } string revId = cursor.GetString(3); byte[] json = cursor.GetBlob(4); bool noAttachments = cursor.GetInt(5) > 0; while ((keepGoing = cursor.MoveToNext()) && cursor.GetLong(0) == docID) { } // Skip rows with the same doc_id -- these are losing conflicts. if (lastSequence > 0) { // Find conflicts with documents from previous indexings. string[] selectArgs2 = new string[] { System.Convert.ToString(docID), System.Convert.ToString (lastSequence) }; Cursor cursor2 = database.GetDatabase().RawQuery("SELECT revid, sequence FROM revs " + "WHERE doc_id=? AND sequence<=? AND current!=0 AND deleted=0 " + "ORDER BY revID DESC " + "LIMIT 1", selectArgs2); if (cursor2.MoveToNext()) { string oldRevId = cursor2.GetString(0); // This is the revision that used to be the 'winner'. // Remove its emitted rows: long oldSequence = cursor2.GetLong(1); string[] args = new string[] { Sharpen.Extensions.ToString(GetViewId()), System.Convert.ToString (oldSequence) }; database.GetDatabase().ExecSQL("DELETE FROM maps WHERE view_id=? AND sequence=?", args); if (RevisionInternal.CBLCompareRevIDs(oldRevId, revId) > 0) { // It still 'wins' the conflict, so it's the one that // should be mapped [again], not the current revision! revId = oldRevId; sequence = oldSequence; string[] selectArgs3 = new string[] { System.Convert.ToString(sequence) }; json = Utils.ByteArrayResultForQuery(database.GetDatabase(), "SELECT json FROM revs WHERE sequence=?" , selectArgs3); } } } // Get the document properties, to pass to the map function: EnumSet<Database.TDContentOptions> contentOptions = EnumSet.NoneOf<Database.TDContentOptions >(); if (noAttachments) { contentOptions.AddItem(Database.TDContentOptions.TDNoAttachments); } IDictionary<string, object> properties = database.DocumentPropertiesFromJSON(json , docId, revId, false, sequence, contentOptions); if (properties != null) { // Call the user-defined map() to emit new key/value // pairs from this revision: emitBlock.SetSequence(sequence); mapBlock.Map(properties, emitBlock); } } } // Finally, record the last revision sequence number that was // indexed: ContentValues updateValues = new ContentValues(); updateValues.Put("lastSequence", dbMaxSequence); string[] whereArgs_1 = new string[] { Sharpen.Extensions.ToString(GetViewId()) }; database.GetDatabase().Update("views", updateValues, "view_id=?", whereArgs_1); // FIXME actually count number added :) Log.V(Log.TagView, "Finished re-indexing view: %s " + " up to sequence %s" + " (deleted %s added ?)" , name, dbMaxSequence, deleted); result.SetCode(Status.Ok); } catch (SQLException e) { throw new CouchbaseLiteException(e, new Status(Status.DbError)); } finally { if (cursor != null) { cursor.Close(); } if (!result.IsSuccessful()) { Log.W(Log.TagView, "Failed to rebuild view %s. Result code: %d", name, result.GetCode ()); } if (database != null) { database.EndTransaction(result.IsSuccessful()); } } }
internal void UpdateIndex() { Log.V(Database.Tag, "Re-indexing view " + Name + " ..."); System.Diagnostics.Debug.Assert((Map != null)); if (Id < 0) { var msg = string.Format("View.Id < 0"); throw new CouchbaseLiteException(msg, new Status(StatusCode.NotFound)); } Database.BeginTransaction(); var result = new Status(StatusCode.InternalServerError); Cursor cursor = null; try { var lastSequence = LastSequenceIndexed; var dbMaxSequence = Database.LastSequenceNumber; if (lastSequence == dbMaxSequence) { // nothing to do (eg, kCBLStatusNotModified) var msg = String.Format("lastSequence ({0}) == dbMaxSequence ({1}), nothing to do", lastSequence, dbMaxSequence); Log.D(Database.Tag, msg); result.SetCode(StatusCode.Ok); return; } // First remove obsolete emitted results from the 'maps' table: var sequence = lastSequence; if (lastSequence < 0) { var msg = string.Format("lastSequence < 0 ({0})", lastSequence); throw new CouchbaseLiteException(msg, new Status(StatusCode.InternalServerError)); } if (lastSequence == 0) { // If the lastSequence has been reset to 0, make sure to remove // any leftover rows: var whereArgs = new string[] { Sharpen.Extensions.ToString(Id) }; Database.StorageEngine.Delete("maps", "view_id=@", whereArgs); } else { // Delete all obsolete map results (ones from since-replaced // revisions): var args = new [] { Id.ToString(), lastSequence.ToString(), lastSequence.ToString() }; Database.StorageEngine.ExecSQL( "DELETE FROM maps WHERE view_id=@ AND sequence IN (" + "SELECT parent FROM revs WHERE sequence>@ " + "AND parent>0 AND parent<=@)", args); } var deleted = 0; cursor = Database.StorageEngine.RawQuery("SELECT changes()", null); // TODO: Convert to ADO params. cursor.MoveToNext(); deleted = cursor.GetInt(0); cursor.Close(); // find a better way to propagate this back // Now scan every revision added since the last time the view was // indexed: var selectArgs = new[] { Convert.ToString(lastSequence) }; cursor = Database.StorageEngine.RawQuery("SELECT revs.doc_id, sequence, docid, revid, json FROM revs, docs " + "WHERE sequence>@ AND current!=0 AND deleted=0 " + "AND revs.doc_id = docs.doc_id " + "ORDER BY revs.doc_id, revid DESC", CommandBehavior.SequentialAccess, selectArgs); cursor.MoveToNext(); var lastDocID = 0L; while (!cursor.IsAfterLast()) { long docID = cursor.GetLong(0); if (docID != lastDocID) { // Only look at the first-iterated revision of any document, // because this is the // one with the highest revid, hence the "winning" revision // of a conflict. lastDocID = docID; // Reconstitute the document as a dictionary: sequence = cursor.GetLong(1); string docId = cursor.GetString(2); if (docId.StartsWith("_design/", StringComparison.InvariantCultureIgnoreCase)) { // design docs don't get indexed! cursor.MoveToNext(); continue; } var revId = cursor.GetString(3); var json = cursor.GetBlob(4); var properties = Database.DocumentPropertiesFromJSON( json, docId, revId, false, sequence, EnumSet.NoneOf <TDContentOptions>() ); if (properties != null) { // Call the user-defined map() to emit new key/value // pairs from this revision: Log.V(Database.Tag, " call map for sequence=" + System.Convert.ToString(sequence )); // This is the emit() block, which gets called from within the // user-defined map() block // that's called down below. var enclosingView = this; var thisSequence = sequence; var map = Map; if (map == null) { throw new CouchbaseLiteException("Map function is missing."); } EmitDelegate emitBlock = (key, value) => { // TODO: Do we need to do any null checks on key or value? try { var keyJson = Manager.GetObjectMapper().WriteValueAsString(key); var valueJson = value == null ? null : Manager.GetObjectMapper().WriteValueAsString(value); Log.V(Database.Tag, String.Format(" emit({0}, {1})", keyJson, valueJson)); var insertValues = new ContentValues(); insertValues.Put("view_id", enclosingView.Id); insertValues["sequence"] = thisSequence; insertValues["key"] = keyJson; insertValues["value"] = valueJson; enclosingView.Database.StorageEngine.Insert("maps", null, insertValues); } catch (Exception e) { Log.E(Database.Tag, "Error emitting", e); } }; map(properties, emitBlock); } } cursor.MoveToNext(); } // Finally, record the last revision sequence number that was // indexed: ContentValues updateValues = new ContentValues(); updateValues["lastSequence"] = dbMaxSequence; var whereArgs_1 = new string[] { Sharpen.Extensions.ToString(Id) }; Database.StorageEngine.Update("views", updateValues, "view_id=@", whereArgs_1); // FIXME actually count number added :) Log.V(Database.Tag, "...Finished re-indexing view " + Name + " up to sequence " + System.Convert.ToString(dbMaxSequence) + " (deleted " + deleted + " added " + "?" + ")"); result.SetCode(StatusCode.Ok); } catch (SQLException e) { throw new CouchbaseLiteException(e, new Status(StatusCode.DbError)); } finally { if (cursor != null) { cursor.Close(); } if (!result.IsSuccessful()) { Log.W(Database.Tag, "Failed to rebuild view " + Name + ": " + result.GetCode()); } if (Database != null) { Database.EndTransaction(result.IsSuccessful()); } } }
public void UpdateIndex() { Log.V(Database.Tag, "Re-indexing view " + name + " ..."); System.Diagnostics.Debug.Assert((mapBlock != null)); if (GetViewId() < 0) { string msg = string.Format("getViewId() < 0"); throw new CouchbaseLiteException(msg, new Status(Status.NotFound)); } database.BeginTransaction(); Status result = new Status(Status.InternalServerError); Cursor cursor = null; try { long lastSequence = GetLastSequenceIndexed(); long dbMaxSequence = database.GetLastSequenceNumber(); if (lastSequence == dbMaxSequence) { // nothing to do (eg, kCBLStatusNotModified) string msg = string.Format("lastSequence (%d) == dbMaxSequence (%d), nothing to do" , lastSequence, dbMaxSequence); Log.D(Database.Tag, msg); result.SetCode(Status.Ok); return; } // First remove obsolete emitted results from the 'maps' table: long sequence = lastSequence; if (lastSequence < 0) { string msg = string.Format("lastSequence < 0 (%s)", lastSequence); throw new CouchbaseLiteException(msg, new Status(Status.InternalServerError)); } if (lastSequence == 0) { // If the lastSequence has been reset to 0, make sure to remove // any leftover rows: string[] whereArgs = new string[] { Sharpen.Extensions.ToString(GetViewId()) }; database.GetDatabase().Delete("maps", "view_id=?", whereArgs); } else { // Delete all obsolete map results (ones from since-replaced // revisions): string[] args = new string[] { Sharpen.Extensions.ToString(GetViewId()), System.Convert.ToString (lastSequence), System.Convert.ToString(lastSequence) }; database.GetDatabase().ExecSQL("DELETE FROM maps WHERE view_id=? AND sequence IN (" + "SELECT parent FROM revs WHERE sequence>? " + "AND parent>0 AND parent<=?)", args); } int deleted = 0; cursor = database.GetDatabase().RawQuery("SELECT changes()", null); cursor.MoveToNext(); deleted = cursor.GetInt(0); cursor.Close(); // This is the emit() block, which gets called from within the // user-defined map() block // that's called down below. AbstractTouchMapEmitBlock emitBlock = new _AbstractTouchMapEmitBlock_446(this); // find a better way to propagate this back // Now scan every revision added since the last time the view was // indexed: string[] selectArgs = new string[] { System.Convert.ToString(lastSequence) }; cursor = database.GetDatabase().RawQuery("SELECT revs.doc_id, sequence, docid, revid, json FROM revs, docs " + "WHERE sequence>? AND current!=0 AND deleted=0 " + "AND revs.doc_id = docs.doc_id " + "ORDER BY revs.doc_id, revid DESC", selectArgs); cursor.MoveToNext(); long lastDocID = 0; while (!cursor.IsAfterLast()) { long docID = cursor.GetLong(0); if (docID != lastDocID) { // Only look at the first-iterated revision of any document, // because this is the // one with the highest revid, hence the "winning" revision // of a conflict. lastDocID = docID; // Reconstitute the document as a dictionary: sequence = cursor.GetLong(1); string docId = cursor.GetString(2); if (docId.StartsWith("_design/")) { // design docs don't get indexed! cursor.MoveToNext(); continue; } string revId = cursor.GetString(3); byte[] json = cursor.GetBlob(4); IDictionary<string, object> properties = database.DocumentPropertiesFromJSON(json , docId, revId, false, sequence, EnumSet.NoneOf<Database.TDContentOptions>()); if (properties != null) { // Call the user-defined map() to emit new key/value // pairs from this revision: Log.V(Database.Tag, " call map for sequence=" + System.Convert.ToString(sequence )); emitBlock.SetSequence(sequence); mapBlock.Map(properties, emitBlock); } } cursor.MoveToNext(); } // Finally, record the last revision sequence number that was // indexed: ContentValues updateValues = new ContentValues(); updateValues.Put("lastSequence", dbMaxSequence); string[] whereArgs_1 = new string[] { Sharpen.Extensions.ToString(GetViewId()) }; database.GetDatabase().Update("views", updateValues, "view_id=?", whereArgs_1); // FIXME actually count number added :) Log.V(Database.Tag, "...Finished re-indexing view " + name + " up to sequence " + System.Convert.ToString(dbMaxSequence) + " (deleted " + deleted + " added " + "?" + ")"); result.SetCode(Status.Ok); } catch (SQLException e) { throw new CouchbaseLiteException(e, new Status(Status.DbError)); } finally { if (cursor != null) { cursor.Close(); } if (!result.IsSuccessful()) { Log.W(Database.Tag, "Failed to rebuild view " + name + ": " + result.GetCode()); } if (database != null) { database.EndTransaction(result.IsSuccessful()); } } }
public void UpdateIndex() { Log.V(Database.Tag, "Re-indexing view " + name + " ..."); System.Diagnostics.Debug.Assert((mapBlock != null)); if (GetViewId() < 0) { string msg = string.Format("getViewId() < 0"); throw new CouchbaseLiteException(msg, new Status(Status.NotFound)); } database.BeginTransaction(); Status result = new Status(Status.InternalServerError); Cursor cursor = null; try { long lastSequence = GetLastSequenceIndexed(); long dbMaxSequence = database.GetLastSequenceNumber(); if (lastSequence == dbMaxSequence) { // nothing to do (eg, kCBLStatusNotModified) string msg = string.Format("lastSequence (%d) == dbMaxSequence (%d), nothing to do" , lastSequence, dbMaxSequence); Log.D(Database.Tag, msg); result.SetCode(Status.Ok); return; } // First remove obsolete emitted results from the 'maps' table: long sequence = lastSequence; if (lastSequence < 0) { string msg = string.Format("lastSequence < 0 (%s)", lastSequence); throw new CouchbaseLiteException(msg, new Status(Status.InternalServerError)); } if (lastSequence == 0) { // If the lastSequence has been reset to 0, make sure to remove // any leftover rows: string[] whereArgs = new string[] { Sharpen.Extensions.ToString(GetViewId()) }; database.GetDatabase().Delete("maps", "view_id=?", whereArgs); } else { // Delete all obsolete map results (ones from since-replaced // revisions): string[] args = new string[] { Sharpen.Extensions.ToString(GetViewId()), System.Convert.ToString (lastSequence), System.Convert.ToString(lastSequence) }; database.GetDatabase().ExecSQL("DELETE FROM maps WHERE view_id=? AND sequence IN (" + "SELECT parent FROM revs WHERE sequence>? " + "AND parent>0 AND parent<=?)", args); } int deleted = 0; cursor = database.GetDatabase().RawQuery("SELECT changes()", null); cursor.MoveToNext(); deleted = cursor.GetInt(0); cursor.Close(); // This is the emit() block, which gets called from within the // user-defined map() block // that's called down below. AbstractTouchMapEmitBlock emitBlock = new _AbstractTouchMapEmitBlock_446(this); // find a better way to propagate this back // Now scan every revision added since the last time the view was // indexed: string[] selectArgs = new string[] { System.Convert.ToString(lastSequence) }; cursor = database.GetDatabase().RawQuery("SELECT revs.doc_id, sequence, docid, revid, json FROM revs, docs " + "WHERE sequence>? AND current!=0 AND deleted=0 " + "AND revs.doc_id = docs.doc_id " + "ORDER BY revs.doc_id, revid DESC", selectArgs); cursor.MoveToNext(); long lastDocID = 0; while (!cursor.IsAfterLast()) { long docID = cursor.GetLong(0); if (docID != lastDocID) { // Only look at the first-iterated revision of any document, // because this is the // one with the highest revid, hence the "winning" revision // of a conflict. lastDocID = docID; // Reconstitute the document as a dictionary: sequence = cursor.GetLong(1); string docId = cursor.GetString(2); if (docId.StartsWith("_design/")) { // design docs don't get indexed! cursor.MoveToNext(); continue; } string revId = cursor.GetString(3); byte[] json = cursor.GetBlob(4); IDictionary <string, object> properties = database.DocumentPropertiesFromJSON(json , docId, revId, false, sequence, EnumSet.NoneOf <Database.TDContentOptions>()); if (properties != null) { // Call the user-defined map() to emit new key/value // pairs from this revision: Log.V(Database.Tag, " call map for sequence=" + System.Convert.ToString(sequence )); emitBlock.SetSequence(sequence); mapBlock.Map(properties, emitBlock); } } cursor.MoveToNext(); } // Finally, record the last revision sequence number that was // indexed: ContentValues updateValues = new ContentValues(); updateValues.Put("lastSequence", dbMaxSequence); string[] whereArgs_1 = new string[] { Sharpen.Extensions.ToString(GetViewId()) }; database.GetDatabase().Update("views", updateValues, "view_id=?", whereArgs_1); // FIXME actually count number added :) Log.V(Database.Tag, "...Finished re-indexing view " + name + " up to sequence " + System.Convert.ToString(dbMaxSequence) + " (deleted " + deleted + " added " + "?" + ")"); result.SetCode(Status.Ok); } catch (SQLException e) { throw new CouchbaseLiteException(e, new Status(Status.DbError)); } finally { if (cursor != null) { cursor.Close(); } if (!result.IsSuccessful()) { Log.W(Database.Tag, "Failed to rebuild view " + name + ": " + result.GetCode()); } if (database != null) { database.EndTransaction(result.IsSuccessful()); } } }