/// <exception cref="Couchbase.Lite.CouchbaseLiteException"></exception> internal IList <QueryRow> ReducedQuery(Cursor cursor, Boolean group, Int32 groupLevel) { IList <object> keysToReduce = null; IList <object> valuesToReduce = null; object lastKey = null; var reduce = Reduce; // FIXME: If reduce is null, then so are keysToReduce and ValuesToReduce, which can throw an NRE below. if (reduce != null) { keysToReduce = new AList <Object>(ReduceBatchSize); valuesToReduce = new AList <Object>(ReduceBatchSize); } var rows = new AList <QueryRow>(); cursor.MoveToNext(); while (!cursor.IsAfterLast()) { var keyData = FromJSON(cursor.GetBlob(0)); var value = FromJSON(cursor.GetBlob(1)); System.Diagnostics.Debug.Assert((keyData != null)); if (group && !GroupTogether(keyData, lastKey, groupLevel)) { if (lastKey != null) { // This pair starts a new group, so reduce & record the last one: var reduced = (reduce != null) ? reduce(keysToReduce, valuesToReduce, false) : null; var key = GroupKey(lastKey, groupLevel); var row = new QueryRow(null, 0, key, reduced, null); row.Database = Database; rows.AddItem(row); // NOTE.ZJG: Change to `yield return row` to convert to a generator. keysToReduce.Clear(); valuesToReduce.Clear(); } lastKey = keyData; } keysToReduce.AddItem(keyData); valuesToReduce.AddItem(value); cursor.MoveToNext(); } // NOTE.ZJG: Need to handle grouping differently if switching this to a generator. if (keysToReduce.Count > 0) { // Finish the last group (or the entire list, if no grouping): var key = group ? GroupKey(lastKey, groupLevel) : null; var reduced = (reduce != null) ? reduce(keysToReduce, valuesToReduce, false) : null; var row = new QueryRow(null, 0, key, reduced, null); row.Database = Database; rows.AddItem(row); } return(rows); }
internal IList <QueryRow> ReducedQuery(Cursor cursor, bool group, int groupLevel) { IList <object> keysToReduce = null; IList <object> valuesToReduce = null; object lastKey = null; if (GetReduce() != null) { keysToReduce = new AList <object>(ReduceBatchSize); valuesToReduce = new AList <object>(ReduceBatchSize); } IList <QueryRow> rows = new AList <QueryRow>(); cursor.MoveToNext(); while (!cursor.IsAfterLast()) { JsonDocument keyDoc = new JsonDocument(cursor.GetBlob(0)); JsonDocument valueDoc = new JsonDocument(cursor.GetBlob(1)); System.Diagnostics.Debug.Assert((keyDoc != null)); object keyObject = keyDoc.JsonObject(); if (group && !GroupTogether(keyObject, lastKey, groupLevel)) { if (lastKey != null) { // This pair starts a new group, so reduce & record the last one: object reduced = (reduceBlock != null) ? reduceBlock.Reduce(keysToReduce, valuesToReduce , false) : null; object key = GroupKey(lastKey, groupLevel); QueryRow row = new QueryRow(null, 0, key, reduced, null); row.SetDatabase(database); rows.AddItem(row); keysToReduce.Clear(); valuesToReduce.Clear(); } lastKey = keyObject; } keysToReduce.AddItem(keyObject); valuesToReduce.AddItem(valueDoc.JsonObject()); cursor.MoveToNext(); } if (keysToReduce.Count > 0) { // Finish the last group (or the entire list, if no grouping): object key = group ? GroupKey(lastKey, groupLevel) : null; object reduced = (reduceBlock != null) ? reduceBlock.Reduce(keysToReduce, valuesToReduce , false) : null; QueryRow row = new QueryRow(null, 0, key, reduced, null); row.SetDatabase(database); rows.AddItem(row); } return(rows); }
public long GetLastSequenceIndexed() { string sql = "SELECT lastSequence FROM views WHERE name=?"; string[] args = new string[] { name }; Cursor cursor = null; long result = -1; try { cursor = database.GetDatabase().RawQuery(sql, args); if (cursor.MoveToNext()) { result = cursor.GetLong(0); } } catch (Exception e) { Log.E(Log.TagView, "Error getting last sequence indexed", e); } finally { if (cursor != null) { cursor.Close(); } } return(result); }
public int GetViewId() { if (viewId < 0) { string sql = "SELECT view_id FROM views WHERE name=?"; string[] args = new string[] { name }; Cursor cursor = null; try { cursor = database.GetDatabase().RawQuery(sql, args); if (cursor.MoveToNext()) { viewId = cursor.GetInt(0); } else { viewId = 0; } } catch (SQLException e) { Log.E(Log.TagView, "Error getting view id", e); viewId = 0; } finally { if (cursor != null) { cursor.Close(); } } } return(viewId); }
/// <summary> /// Defines the View's <see cref="Couchbase.Lite.MapDelegate"/> /// and <see cref="Couchbase.Lite.ReduceDelegate"/>. /// </summary> /// <remarks> /// Defines a view's functions. /// The view's definition is given as a class that conforms to the Mapper or /// Reducer interface (or null to delete the view). The body of the block /// should call the 'emit' object (passed in as a paramter) for every key/value pair /// it wants to write to the view. /// Since the function itself is obviously not stored in the database (only a unique /// string idenfitying it), you must re-define the view on every launch of the app! /// If the database needs to rebuild the view but the function hasn't been defined yet, /// it will fail and the view will be empty, causing weird problems later on. /// It is very important that this block be a law-abiding map function! As in other /// languages, it must be a "pure" function, with no side effects, that always emits /// the same values given the same input document. That means that it should not access /// or change any external state; be careful, since callbacks make that so easy that you /// might do it inadvertently! The callback may be called on any thread, or on /// multiple threads simultaneously. This won't be a problem if the code is "pure" as /// described above, since it will as a consequence also be thread-safe. /// </remarks> /// <returns> /// <c>true</c> if the <see cref="Couchbase.Lite.MapDelegate"/> /// and <see cref="Couchbase.Lite.ReduceDelegate"/> were set, otherwise <c>false</c>. /// If the values provided are identical to the values that are already set, /// then the values will not be updated and <c>false</c> will be returned. /// In addition, if <c>true</c> is returned, the index was deleted and /// will be rebuilt on the next <see cref="Couchbase.Lite.Query"/> execution. /// </returns> /// <param name="map">The <see cref="Couchbase.Lite.MapDelegate"/> to set.</param> /// <param name="reduce">The <see cref="Couchbase.Lite.ReduceDelegate"/> to set.</param> /// <param name="version"> /// The key of the property value to return. The value of this parameter must change /// when the <see cref="Couchbase.Lite.MapDelegate"/> and/or <see cref="Couchbase.Lite.ReduceDelegate"/> /// are changed in a way that will cause them to produce different results. /// </param> public Boolean SetMapReduce(MapDelegate map, ReduceDelegate reduce, String version) { System.Diagnostics.Debug.Assert(map != null); System.Diagnostics.Debug.Assert(version != null); // String.Empty is valid. Map = map; Reduce = reduce; if (!Database.Open()) { return(false); } // Update the version column in the database. This is a little weird looking // because we want to // avoid modifying the database if the version didn't change, and because the // row might not exist yet. var storageEngine = this.Database.StorageEngine; // Older Android doesnt have reliable insert or ignore, will to 2 step // FIXME review need for change to execSQL, manual call to changes() var sql = "SELECT name, version FROM views WHERE name=?"; // TODO: Convert to ADO params. var args = new [] { Name }; Cursor cursor = null; try { cursor = storageEngine.RawQuery(sql, args); if (!cursor.MoveToNext()) { // no such record, so insert var insertValues = new ContentValues(); insertValues["name"] = Name; insertValues["version"] = version; storageEngine.Insert("views", null, insertValues); return(true); } var updateValues = new ContentValues(); updateValues["version"] = version; updateValues["lastSequence"] = 0; var whereArgs = new [] { Name, version }; var rowsAffected = storageEngine.Update("views", updateValues, "name=? AND version!=?", whereArgs); return(rowsAffected > 0); } catch (SQLException e) { Log.E(Database.Tag, "Error setting map block", e); return(false); } finally { if (cursor != null) { cursor.Close(); } } }
public long GetLastSequenceIndexed() { string sql = "SELECT lastSequence FROM views WHERE name=?"; string[] args = new string[] { name }; Cursor cursor = null; long result = -1; try { Log.D(Database.TagSql, Sharpen.Thread.CurrentThread().GetName() + " start running query: " + sql); cursor = database.GetDatabase().RawQuery(sql, args); Log.D(Database.TagSql, Sharpen.Thread.CurrentThread().GetName() + " finish running query: " + sql); if (cursor.MoveToNext()) { result = cursor.GetLong(0); } } catch (Exception e) { Log.E(Database.Tag, "Error getting last sequence indexed", e); } finally { if (cursor != null) { cursor.Close(); } } return(result); }
public bool SetMapReduce(Mapper mapBlock, Reducer reduceBlock, string version) { System.Diagnostics.Debug.Assert((mapBlock != null)); System.Diagnostics.Debug.Assert((version != null)); this.mapBlock = mapBlock; this.reduceBlock = reduceBlock; if (!database.Open()) { return(false); } // Update the version column in the database. This is a little weird looking // because we want to // avoid modifying the database if the version didn't change, and because the // row might not exist yet. SQLiteStorageEngine storageEngine = this.database.GetDatabase(); // Older Android doesnt have reliable insert or ignore, will to 2 step // FIXME review need for change to execSQL, manual call to changes() string sql = "SELECT name, version FROM views WHERE name=?"; string[] args = new string[] { name }; Cursor cursor = null; try { cursor = storageEngine.RawQuery(sql, args); if (!cursor.MoveToNext()) { // no such record, so insert ContentValues insertValues = new ContentValues(); insertValues.Put("name", name); insertValues.Put("version", version); storageEngine.Insert("views", null, insertValues); return(true); } ContentValues updateValues = new ContentValues(); updateValues.Put("version", version); updateValues.Put("lastSequence", 0); string[] whereArgs = new string[] { name, version }; int rowsAffected = storageEngine.Update("views", updateValues, "name=? AND version!=?" , whereArgs); return(rowsAffected > 0); } catch (SQLException e) { Log.E(Log.TagView, "Error setting map block", e); return(false); } finally { if (cursor != null) { cursor.Close(); } } }
public IList <IDictionary <string, object> > Dump() { if (GetViewId() < 0) { return(null); } string[] selectArgs = new string[] { Sharpen.Extensions.ToString(GetViewId()) }; Cursor cursor = null; IList <IDictionary <string, object> > result = null; try { cursor = database.GetDatabase().RawQuery("SELECT sequence, key, value FROM maps WHERE view_id=? ORDER BY key" , selectArgs); cursor.MoveToNext(); result = new AList <IDictionary <string, object> >(); while (!cursor.IsAfterLast()) { IDictionary <string, object> row = new Dictionary <string, object>(); row.Put("seq", cursor.GetInt(0)); row.Put("key", cursor.GetString(1)); row.Put("value", cursor.GetString(2)); result.AddItem(row); cursor.MoveToNext(); } } catch (SQLException e) { Log.E(Log.TagView, "Error dumping view", e); return(null); } finally { if (cursor != null) { cursor.Close(); } } return(result); }
internal IList <IDictionary <string, object> > Dump() { if (Id < 0) { return(null); } var result = new List <IDictionary <string, object> >(); var selectArgs = new string[] { Id.ToString() }; Cursor cursor = null; try { cursor = Database.StorageEngine.RawQuery( "SELECT sequence, key, value FROM maps WHERE view_id=? ORDER BY key", selectArgs); while (cursor.MoveToNext()) { var row = new Dictionary <string, object>(); row.Put("seq", cursor.GetInt(0)); row.Put("key", cursor.GetString(1)); row.Put("value", cursor.GetString(2)); result.AddItem(row); } } catch (SQLException e) { Log.E(Tag, "Error dumping view", e); result = null; } finally { if (cursor != null) { cursor.Close(); } } return(result); }
/// <exception cref="System.Exception"></exception> public virtual void DumpTableRevs() { Cursor cursor = database.GetDatabase().RawQuery("SELECT * FROM revs", null); while (cursor.MoveToNext()) { int sequence = cursor.GetInt(0); int doc_id = cursor.GetInt(1); byte[] revid = cursor.GetBlob(2); string revIdStr = null; if (revid != null) { revIdStr = Sharpen.Runtime.GetStringForBytes(revid); } int parent = cursor.GetInt(3); int current = cursor.GetInt(4); int deleted = cursor.GetInt(5); Log.D(Tag, string.Format("Revs row seq: %s doc_id: %s, revIdStr: %s, parent: %s, current: %s, deleted: %s" , sequence, doc_id, revIdStr, parent, current, deleted)); } }
/// <exception cref="System.Exception"></exception> public virtual void DumpTableMaps() { Cursor cursor = database.GetDatabase().RawQuery("SELECT * FROM maps", null); while (cursor.MoveToNext()) { int viewId = cursor.GetInt(0); int sequence = cursor.GetInt(1); byte[] key = cursor.GetBlob(2); string keyStr = null; if (key != null) { keyStr = Sharpen.Runtime.GetStringForBytes(key); } byte[] value = cursor.GetBlob(3); string valueStr = null; if (value != null) { valueStr = Sharpen.Runtime.GetStringForBytes(value); } Log.D(Tag, string.Format("Maps row viewId: %s seq: %s, key: %s, val: %s", viewId, sequence, keyStr, valueStr)); } }
/// <exception cref="Couchbase.Lite.CouchbaseLiteException"></exception> internal IList<QueryRow> ReducedQuery(Cursor cursor, Boolean group, Int32 groupLevel) { IList<object> keysToReduce = null; IList<object> valuesToReduce = null; object lastKey = null; var reduce = Reduce; // FIXME: If reduce is null, then so are keysToReduce and ValuesToReduce, which can throw an NRE below. if (reduce != null) { keysToReduce = new AList<Object>(ReduceBatchSize); valuesToReduce = new AList<Object>(ReduceBatchSize); } var rows = new AList<QueryRow>(); cursor.MoveToNext(); while (!cursor.IsAfterLast()) { var keyData = FromJSON(cursor.GetBlob(0)); var value = FromJSON(cursor.GetBlob(1)); System.Diagnostics.Debug.Assert((keyData != null)); if (group && !GroupTogether(keyData, lastKey, groupLevel)) { if (lastKey != null) { // This pair starts a new group, so reduce & record the last one: var reduced = (reduce != null) ? reduce(keysToReduce, valuesToReduce, false) : null; var key = GroupKey(lastKey, groupLevel); var row = new QueryRow(null, 0, key, reduced, null); row.Database = Database; rows.AddItem(row); // NOTE.ZJG: Change to `yield return row` to convert to a generator. keysToReduce.Clear(); valuesToReduce.Clear(); } lastKey = keyData; } keysToReduce.AddItem(keyData); valuesToReduce.AddItem(value); cursor.MoveToNext(); } // NOTE.ZJG: Need to handle grouping differently if switching this to a generator. if (keysToReduce.Count > 0) { // Finish the last group (or the entire list, if no grouping): var key = group ? GroupKey(lastKey, groupLevel) : null; var reduced = (reduce != null) ? reduce(keysToReduce, valuesToReduce, false) : null; var row = new QueryRow(null, 0, key, reduced, null); row.Database = Database; rows.AddItem(row); } return rows; }
/// <summary>Queries the view.</summary> /// <remarks>Queries the view. Does NOT first update the index.</remarks> /// <param name="options">The options to use.</param> /// <returns>An array of QueryRow objects.</returns> /// <exception cref="Couchbase.Lite.CouchbaseLiteException"></exception> internal IEnumerable <QueryRow> QueryWithOptions(QueryOptions options) { if (options == null) { options = new QueryOptions(); } Cursor cursor = null; IList <QueryRow> rows = new List <QueryRow>(); try { cursor = ResultSetWithOptions(options); int groupLevel = options.GetGroupLevel(); var group = options.IsGroup() || (groupLevel > 0); var reduceBlock = Reduce; var reduce = GroupOrReduce(options); if (reduce && (reduceBlock == null) && !group) { var msg = "Cannot use reduce option in view " + Name + " which has no reduce block defined"; Log.W(Database.Tag, msg); throw new CouchbaseLiteException(StatusCode.BadRequest); } if (reduce || group) { // Reduced or grouped query: rows = ReducedQuery(cursor, group, groupLevel); } else { // regular query cursor.MoveToNext(); while (!cursor.IsAfterLast()) { var key = FromJSON(cursor.GetBlob(0)); var value = FromJSON(cursor.GetBlob(1)); var docId = cursor.GetString(2); var sequenceLong = cursor.GetLong(3); var sequence = Convert.ToInt32(sequenceLong); IDictionary <string, object> docContents = null; if (options.IsIncludeDocs()) { // http://wiki.apache.org/couchdb/Introduction_to_CouchDB_views#Linked_documents if (value is IDictionary <string, object> && ((IDictionary <string, object>)value).ContainsKey("_id")) { var linkedDocId = (string)((IDictionary <string, object>)value).Get("_id"); var linkedDoc = Database.GetDocumentWithIDAndRev(linkedDocId, null, DocumentContentOptions.None); docContents = linkedDoc.GetProperties(); } else { var revId = cursor.GetString(4); docContents = Database.DocumentPropertiesFromJSON(cursor.GetBlob(5), docId, revId, false, sequenceLong, options.GetContentOptions()); } } var row = new QueryRow(docId, sequence, key, value, docContents); row.Database = Database; rows.AddItem <QueryRow>(row); // NOTE.ZJG: Change to `yield return row` to convert to a generator. cursor.MoveToNext(); } } } catch (SQLException e) { var errMsg = string.Format("Error querying view: {0}", this); Log.E(Database.Tag, errMsg, e); throw new CouchbaseLiteException(errMsg, e, new Status(StatusCode.DbError)); } finally { if (cursor != null) { cursor.Close(); } } return(rows); }
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()); } } }
public IList <QueryRow> QueryWithOptions(QueryOptions options) { if (options == null) { options = new QueryOptions(); } Cursor cursor = null; IList <QueryRow> rows = new AList <QueryRow>(); try { cursor = ResultSetWithOptions(options); int groupLevel = options.GetGroupLevel(); bool group = options.IsGroup() || (groupLevel > 0); bool reduce = options.IsReduce() || group; if (reduce && (reduceBlock == null) && !group) { Log.W(Log.TagView, "Cannot use reduce option in view %s which has no reduce block defined" , name); throw new CouchbaseLiteException(new Status(Status.BadRequest)); } if (reduce || group) { // Reduced or grouped query: rows = ReducedQuery(cursor, group, groupLevel); } else { // regular query cursor.MoveToNext(); while (!cursor.IsAfterLast()) { JsonDocument keyDoc = new JsonDocument(cursor.GetBlob(0)); JsonDocument valueDoc = new JsonDocument(cursor.GetBlob(1)); string docId = cursor.GetString(2); int sequence = Sharpen.Extensions.ValueOf(cursor.GetString(3)); IDictionary <string, object> docContents = null; if (options.IsIncludeDocs()) { object valueObject = valueDoc.JsonObject(); // http://wiki.apache.org/couchdb/Introduction_to_CouchDB_views#Linked_documents if (valueObject is IDictionary && ((IDictionary)valueObject).ContainsKey("_id")) { string linkedDocId = (string)((IDictionary)valueObject).Get("_id"); RevisionInternal linkedDoc = database.GetDocumentWithIDAndRev(linkedDocId, null, EnumSet.NoneOf <Database.TDContentOptions>()); docContents = linkedDoc.GetProperties(); } else { docContents = database.DocumentPropertiesFromJSON(cursor.GetBlob(5), docId, cursor .GetString(4), false, cursor.GetLong(3), options.GetContentOptions()); } } QueryRow row = new QueryRow(docId, sequence, keyDoc.JsonObject(), valueDoc.JsonObject (), docContents); row.SetDatabase(database); rows.AddItem(row); cursor.MoveToNext(); } } } catch (SQLException e) { string errMsg = string.Format("Error querying view: %s", this); Log.E(Log.TagView, errMsg, e); throw new CouchbaseLiteException(errMsg, e, new Status(Status.DbError)); } finally { if (cursor != null) { cursor.Close(); } } return(rows); }
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(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 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()); } } }
internal IList<QueryRow> ReducedQuery(Cursor cursor, bool group, int groupLevel) { IList<object> keysToReduce = null; IList<object> valuesToReduce = null; object lastKey = null; if (GetReduce() != null) { keysToReduce = new AList<object>(ReduceBatchSize); valuesToReduce = new AList<object>(ReduceBatchSize); } IList<QueryRow> rows = new AList<QueryRow>(); cursor.MoveToNext(); while (!cursor.IsAfterLast()) { JsonDocument keyDoc = new JsonDocument(cursor.GetBlob(0)); JsonDocument valueDoc = new JsonDocument(cursor.GetBlob(1)); System.Diagnostics.Debug.Assert((keyDoc != null)); object keyObject = keyDoc.JsonObject(); if (group && !GroupTogether(keyObject, lastKey, groupLevel)) { if (lastKey != null) { // This pair starts a new group, so reduce & record the last one: object reduced = (reduceBlock != null) ? reduceBlock.Reduce(keysToReduce, valuesToReduce , false) : null; object key = GroupKey(lastKey, groupLevel); QueryRow row = new QueryRow(null, 0, key, reduced, null); row.SetDatabase(database); rows.AddItem(row); keysToReduce.Clear(); valuesToReduce.Clear(); } lastKey = keyObject; } keysToReduce.AddItem(keyObject); valuesToReduce.AddItem(valueDoc.JsonObject()); cursor.MoveToNext(); } if (keysToReduce.Count > 0) { // Finish the last group (or the entire list, if no grouping): object key = group ? GroupKey(lastKey, groupLevel) : null; object reduced = (reduceBlock != null) ? reduceBlock.Reduce(keysToReduce, valuesToReduce , false) : null; QueryRow row = new QueryRow(null, 0, key, reduced, null); row.SetDatabase(database); rows.AddItem(row); } return rows; }