public void TestAttachments()
        {
            var testAttachmentName = "test_attachment";
            var attachments = database.Attachments;
            Assert.AreEqual(0, attachments.Count());
            Assert.AreEqual(0, attachments.AllKeys().Count());
            
            var rev1Properties = new Dictionary<string, object>();
            rev1Properties["foo"] = 1;
            rev1Properties["bar"] = false;

            var status = new Status();
            var rev1 = database.PutRevision(
                new RevisionInternal(rev1Properties, database), null, false, status);
            Assert.AreEqual(StatusCode.Created, status.GetCode());

            var attach1 = Runtime.GetBytesForString(
                "This is the body of attach1").ToArray();
            database.InsertAttachmentForSequenceWithNameAndType(
                new ByteArrayInputStream(attach1), 
                rev1.GetSequence(), 
                testAttachmentName, 
                "text/plain", 
                rev1.GetGeneration());

            //We must set the no_attachments column for the rev to false, as we are using an internal
            //private API call above (database.insertAttachmentForSequenceWithNameAndType) which does
            //not set the no_attachments column on revs table
            try
            {
                var args = new ContentValues();
                args.Put("no_attachments", false);
                database.StorageEngine.Update(
                    "revs", 
                    args, 
                    "sequence=?", 
                    new[] { rev1.GetSequence().ToString() }
                );
            }
            catch (SQLException e)
            {
                Log.E(Tag, "Error setting rev1 no_attachments to false", e);
                throw new CouchbaseLiteException(StatusCode.InternalServerError);
            }

            var attachment = database.GetAttachmentForSequence(
                rev1.GetSequence(), 
                testAttachmentName
            );
            Assert.AreEqual("text/plain", attachment.ContentType);
            var data = attachment.Content.ToArray();
            Assert.IsTrue(Arrays.Equals(attach1, data));

            // Workaround :
            // Not closing the content stream will cause Sharing Violation
            // Exception when trying to get the same attachment going forward.
            attachment.ContentStream.Close();

            var innerDict = new Dictionary<string, object>();
            innerDict["content_type"] = "text/plain";
            innerDict["digest"] = "sha1-gOHUOBmIMoDCrMuGyaLWzf1hQTE=";
            innerDict["length"] = 27;
            innerDict["stub"] = true;
            innerDict["revpos"] = 1;

            var attachmentDict = new Dictionary<string, object>();
            attachmentDict[testAttachmentName] = innerDict;
            var attachmentDictForSequence = database.GetAttachmentsDictForSequenceWithContent(rev1.GetSequence(), DocumentContentOptions.None);
            Assert.AreEqual(new SortedDictionary<string,object>(attachmentDict), new SortedDictionary<string,object>(attachmentDictForSequence));//Assert.AreEqual(1, attachmentDictForSequence.Count);
            var gotRev1 = database.GetDocumentWithIDAndRev(rev1.GetDocId(), 
                rev1.GetRevId(), DocumentContentOptions.IncludeAttachments);
            var gotAttachmentDict = gotRev1.GetProperties()
                .Get("_attachments")
                .AsDictionary<string,object>();
            Assert.AreEqual(attachmentDict.Select(kvp => kvp.Key).OrderBy(k => k), gotAttachmentDict.Select(kvp => kvp.Key).OrderBy(k => k));

            // Check the attachment dict, with attachments included:
            innerDict.Remove("stub");
            innerDict.Put("data", Convert.ToBase64String(attach1));
            attachmentDictForSequence = database.GetAttachmentsDictForSequenceWithContent(
                rev1.GetSequence(), DocumentContentOptions.IncludeAttachments);
            Assert.AreEqual(new SortedDictionary<string,object>(attachmentDict[testAttachmentName].AsDictionary<string,object>()), new SortedDictionary<string,object>(attachmentDictForSequence[testAttachmentName].AsDictionary<string,object>()));

            gotRev1 = database.GetDocumentWithIDAndRev(
                rev1.GetDocId(), rev1.GetRevId(), DocumentContentOptions.IncludeAttachments);
            gotAttachmentDict = gotRev1.GetProperties()
                .Get("_attachments")
                .AsDictionary<string, object>()
                .Get(testAttachmentName)
                .AsDictionary<string,object>();
            Assert.AreEqual(innerDict.Select(kvp => kvp.Key).OrderBy(k => k), gotAttachmentDict.Select(kvp => kvp.Key).OrderBy(k => k));

            // Add a second revision that doesn't update the attachment:
            var rev2Properties = new Dictionary<string, object>();
            rev2Properties.Put("_id", rev1.GetDocId());
            rev2Properties["foo"] = 2;
            rev2Properties["bazz"] = false;
            var rev2 = database.PutRevision(new RevisionInternal(rev2Properties, 
                database), rev1.GetRevId(), false, status);
            Assert.AreEqual(StatusCode.Created, status.GetCode());

            database.CopyAttachmentNamedFromSequenceToSequence(
                testAttachmentName, rev1.GetSequence(), rev2.GetSequence());           
            // Add a third revision of the same document:
            var rev3Properties = new Dictionary<string, object>();
            rev3Properties.Put("_id", rev2.GetDocId());
            rev3Properties["foo"] = 2;
            rev3Properties["bazz"] = false;
            var rev3 = database.PutRevision(new RevisionInternal(
                rev3Properties, database), rev2.GetRevId(), false, status);
            Assert.AreEqual(StatusCode.Created, status.GetCode());

            var attach2 = Runtime.GetBytesForString("<html>And this is attach2</html>").ToArray();
            database.InsertAttachmentForSequenceWithNameAndType(
                new ByteArrayInputStream(attach2), rev3.GetSequence(), 
                testAttachmentName, "text/html", rev2.GetGeneration());
            // Check the 2nd revision's attachment:
            var attachment2 = database.GetAttachmentForSequence(rev2.GetSequence(), testAttachmentName);
            Assert.AreEqual("text/plain", attachment2.ContentType);

            data = attachment2.Content.ToArray();
            Assert.IsTrue(Arrays.Equals(attach1, data));

            // Workaround :
            // Not closing the content stream will cause Sharing Violation
            // Exception when trying to get the same attachment going forward.
            attachment2.ContentStream.Close();

            // Check the 3rd revision's attachment:
            var attachment3 = database.GetAttachmentForSequence(rev3.GetSequence(), testAttachmentName);
            Assert.AreEqual("text/html", attachment3.ContentType);

            data = attachment3.Content.ToArray();
            Assert.IsTrue(Arrays.Equals(attach2, data));

            var attachmentDictForRev3 = database.GetAttachmentsDictForSequenceWithContent(rev3.GetSequence(), DocumentContentOptions.None)
                .Get(testAttachmentName)
                .AsDictionary<string,object>();
            if (attachmentDictForRev3.ContainsKey("follows"))
            {
                if (((bool)attachmentDictForRev3.Get("follows")) == true)
                {
                    throw new RuntimeException("Did not expected attachment dict 'follows' key to be true"
                    );
                }
                else
                {
                    throw new RuntimeException("Did not expected attachment dict to have 'follows' key"
                    );
                }
            }

            // Workaround :
            // Not closing the content stream will cause Sharing Violation
            // Exception when trying to get the same attachment going forward.
            attachment3.ContentStream.Close();

            // Examine the attachment store:
            Assert.AreEqual(2, attachments.Count());

            var expected = new HashSet<BlobKey>();
            expected.AddItem(BlobStore.KeyForBlob(attach1));
            expected.AddItem(BlobStore.KeyForBlob(attach2));
            Assert.AreEqual(expected.Count, attachments.AllKeys().Count());

            foreach(var key in attachments.AllKeys()) {
                Assert.IsTrue(expected.Contains(key));
            }

            database.Compact();

            // This clears the body of the first revision
            Assert.AreEqual(1, attachments.Count());

            var expected2 = new HashSet<BlobKey>();
            expected2.AddItem(BlobStore.KeyForBlob(attach2));
            Assert.AreEqual(expected2.Count, attachments.AllKeys().Count());

            foreach(var key in attachments.AllKeys()) {
                Assert.IsTrue(expected2.Contains(key));
            }
        }
Пример #2
0
        internal RevisionInternal PutLocalRevision(RevisionInternal revision, string prevRevID)
        {
            var docID = revision.GetDocId();
            if (!docID.StartsWith ("_local/", StringComparison.InvariantCultureIgnoreCase))
            {
                throw new CouchbaseLiteException(StatusCode.BadRequest);
            }

            if (!revision.IsDeleted())
            {
                // PUT:
                string newRevID;
                var json = EncodeDocumentJSON(revision);

                if (prevRevID != null)
                {
                    var generation = RevisionInternal.GenerationFromRevID(prevRevID);
                    if (generation == 0)
                    {
                        throw new CouchbaseLiteException(StatusCode.BadRequest);
                    }
                    newRevID = Sharpen.Extensions.ToString(++generation) + "-local";

                    var values = new ContentValues();
                    values["revid"] = newRevID;
                    values["json"] = json;

                    var whereArgs = new [] { docID, prevRevID };
                    try
                    {
                        var rowsUpdated = StorageEngine.Update("localdocs", values, "docid=? AND revid=?", whereArgs);
                        if (rowsUpdated == 0)
                        {
                            throw new CouchbaseLiteException(StatusCode.Conflict);
                        }
                    }
                    catch (SQLException e)
                    {
                        throw new CouchbaseLiteException(e, StatusCode.InternalServerError);
                    }
                }
                else
                {
                    newRevID = "1-local";

                    var values = new ContentValues();
                    values["docid"] = docID;
                    values["revid"] = newRevID;
                    values["json"] = json;

                    try
                    {
                        StorageEngine.InsertWithOnConflict("localdocs", null, values, ConflictResolutionStrategy.Ignore);
                    }
                    catch (SQLException e)
                    {
                        throw new CouchbaseLiteException(e, StatusCode.InternalServerError);
                    }
                }
                return revision.CopyWithDocID(docID, newRevID);
            }
            else
            {
                // DELETE:
                DeleteLocalDocument(docID, prevRevID);
                return revision;
            }
        }
Пример #3
0
        /// <summary>Defines a view's functions.</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>
        public Boolean SetMapReduce(MapDelegate map, ReduceDelegate reduce, String version) { 
            System.Diagnostics.Debug.Assert((map != null));
            System.Diagnostics.Debug.Assert(!String.IsNullOrWhiteSpace(version));

            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();
                }
            }
        }
Пример #4
0
        /// <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;
        }
Пример #5
0
        internal Int64 InsertRevision(RevisionInternal rev, long docNumericID, long parentSequence, bool current, bool hasAttachments, IEnumerable<byte> data)
        {
            var rowId = 0L;
            try
            {
                var args = new ContentValues();
                args["doc_id"] = docNumericID;
                args.Put("revid", rev.GetRevId());
                if (parentSequence != 0)
                {
                    args["parent"] = parentSequence;
                }

                args["current"] = current;
                args["deleted"] = rev.IsDeleted();
                args["no_attachments"] = !hasAttachments;
                if (data != null)
                {
                    args["json"] = data.ToArray();
                }

                rowId = StorageEngine.Insert("revs", null, args);
                rev.SetSequence(rowId);
            }
            catch (Exception e)
            {
                Log.E(Tag, "Error inserting revision", e);
            }
            return rowId;
        }
Пример #6
0
		/// <summary>Creates a set of values copied from the given set</summary>
		/// <param name="from">the values to copy</param>
		public ContentValues(ContentValues from)
		{
			mValues = new Dictionary<string, object>(from.mValues);
		}
Пример #7
0
 internal Boolean SetLastSequence(String lastSequence, String checkpointId, Boolean push)
 {
     var values = new ContentValues();
     values.Put("remote", checkpointId);
     values["push"] = push;
     values["last_sequence"] = lastSequence;
     var newId = StorageEngine.InsertWithOnConflict("replicators", null, values, ConflictResolutionStrategy.Replace);
     Log.D(Tag, "Set Last Sequence: {0}: {1} / {2}".Fmt(lastSequence, checkpointId, newId));
     return (newId == -1);
 }
Пример #8
0
		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;
		}
Пример #9
0
		public bool SetLastSequence(string lastSequence, string checkpointId, bool push)
		{
			ContentValues values = new ContentValues();
			values.Put("remote", checkpointId);
			values.Put("push", push);
			values.Put("last_sequence", lastSequence);
			long newId = database.InsertWithOnConflict("replicators", null, values, SQLiteStorageEngine
				.ConflictReplace);
			return (newId == -1);
		}
Пример #10
0
		public long InsertDocumentID(string docId)
		{
			long rowId = -1;
			try
			{
				ContentValues args = new ContentValues();
				args.Put("docid", docId);
				rowId = database.Insert("docs", null, args);
			}
			catch (Exception e)
			{
				Log.E(Database.Tag, "Error inserting document id", e);
			}
			return rowId;
		}
Пример #11
0
		public long InsertRevision(RevisionInternal rev, long docNumericID, long parentSequence
			, bool current, byte[] data)
		{
			long rowId = 0;
			try
			{
				ContentValues args = new ContentValues();
				args.Put("doc_id", docNumericID);
				args.Put("revid", rev.GetRevId());
				if (parentSequence != 0)
				{
					args.Put("parent", parentSequence);
				}
				args.Put("current", current);
				args.Put("deleted", rev.IsDeleted());
				args.Put("json", data);
				rowId = database.Insert("revs", null, args);
				rev.SetSequence(rowId);
			}
			catch (Exception e)
			{
				Log.E(Database.Tag, "Error inserting revision", e);
			}
			return rowId;
		}
Пример #12
0
		public void Compact()
		{
			// Can't delete any rows because that would lose revision tree history.
			// But we can remove the JSON of non-current revisions, which is most of the space.
			try
			{
				Log.V(Couchbase.Lite.Database.Tag, "Pruning old revisions...");
				PruneRevsToMaxDepth(0);
				Log.V(Couchbase.Lite.Database.Tag, "Deleting JSON of old revisions...");
				ContentValues args = new ContentValues();
				args.Put("json", (string)null);
				database.Update("revs", args, "current=0", null);
			}
			catch (SQLException e)
			{
				Log.E(Couchbase.Lite.Database.Tag, "Error compacting", e);
				throw new CouchbaseLiteException(Status.InternalServerError);
			}
			Log.V(Couchbase.Lite.Database.Tag, "Deleting old attachments...");
			Status result = GarbageCollectAttachments();
			if (!result.IsSuccessful())
			{
				throw new CouchbaseLiteException(result);
			}
			Log.V(Couchbase.Lite.Database.Tag, "Vacuuming SQLite sqliteDb...");
			try
			{
				database.ExecSQL("VACUUM");
			}
			catch (SQLException e)
			{
				Log.E(Couchbase.Lite.Database.Tag, "Error vacuuming sqliteDb", e);
				throw new CouchbaseLiteException(Status.InternalServerError);
			}
		}
Пример #13
0
		public void InsertAttachmentForSequenceWithNameAndType(long sequence, string name
			, string contentType, int revpos, BlobKey key)
		{
			try
			{
				ContentValues args = new ContentValues();
				args.Put("sequence", sequence);
				args.Put("filename", name);
				if (key != null)
				{
					args.Put("key", key.GetBytes());
					args.Put("length", attachments.GetSizeOfBlob(key));
				}
				args.Put("type", contentType);
				args.Put("revpos", revpos);
				long result = database.Insert("attachments", null, args);
				if (result == -1)
				{
					string msg = "Insert attachment failed (returned -1)";
					Log.E(Database.Tag, msg);
					throw new CouchbaseLiteException(msg, Status.InternalServerError);
				}
			}
			catch (SQLException e)
			{
				Log.E(Database.Tag, "Error inserting attachment", e);
				throw new CouchbaseLiteException(e, Status.InternalServerError);
			}
		}
Пример #14
0
        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 abstract long InsertWithOnConflict(string table, string nullColumnHack, ContentValues initialValues, ConflictResolutionStrategy conflictResolutionStrategy);
Пример #16
0
		public RevisionInternal PutLocalRevision(RevisionInternal revision, string prevRevID
			)
		{
			string docID = revision.GetDocId();
			if (!docID.StartsWith("_local/"))
			{
				throw new CouchbaseLiteException(Status.BadRequest);
			}
			if (!revision.IsDeleted())
			{
				// PUT:
				byte[] json = EncodeDocumentJSON(revision);
				string newRevID;
				if (prevRevID != null)
				{
					int generation = RevisionInternal.GenerationFromRevID(prevRevID);
					if (generation == 0)
					{
						throw new CouchbaseLiteException(Status.BadRequest);
					}
					newRevID = Sharpen.Extensions.ToString(++generation) + "-local";
					ContentValues values = new ContentValues();
					values.Put("revid", newRevID);
					values.Put("json", json);
					string[] whereArgs = new string[] { docID, prevRevID };
					try
					{
						int rowsUpdated = database.Update("localdocs", values, "docid=? AND revid=?", whereArgs
							);
						if (rowsUpdated == 0)
						{
							throw new CouchbaseLiteException(Status.Conflict);
						}
					}
					catch (SQLException e)
					{
						throw new CouchbaseLiteException(e, Status.InternalServerError);
					}
				}
				else
				{
					newRevID = "1-local";
					ContentValues values = new ContentValues();
					values.Put("docid", docID);
					values.Put("revid", newRevID);
					values.Put("json", json);
					try
					{
						database.InsertWithOnConflict("localdocs", null, values, SQLiteStorageEngine.ConflictIgnore
							);
					}
					catch (SQLException e)
					{
						throw new CouchbaseLiteException(e, Status.InternalServerError);
					}
				}
				return revision.CopyWithDocID(docID, newRevID);
			}
			else
			{
				// DELETE:
				DeleteLocalDocument(docID, prevRevID);
				return revision;
			}
		}
 public abstract int Update(string table, ContentValues values, string whereClause, params String[] whereArgs);
 public override long Insert (String table, String nullColumnHack, ContentValues values)
 {
     return InsertWithOnConflict(table, null, values, ConflictResolutionStrategy.None);
 }
Пример #19
0
        /// <summary>Inserts an already-existing revision replicated from a remote sqliteDb.</summary>
        /// <remarks>
        /// Inserts an already-existing revision replicated from a remote sqliteDb.
        /// It must already have a revision ID. This may create a conflict! The revision's history must be given; ancestor revision IDs that don't already exist locally will create phantom revisions with no content.
        /// </remarks>
        /// <exception cref="Couchbase.Lite.CouchbaseLiteException"></exception>
        internal void ForceInsert(RevisionInternal rev, IList<string> revHistory, Uri source)
        {
            var inConflict = false;
            var docId = rev.GetDocId();
            var revId = rev.GetRevId();
            if (!IsValidDocumentId(docId) || (revId == null))
            {
                throw new CouchbaseLiteException(StatusCode.BadRequest);
            }
            int historyCount = 0;
            if (revHistory != null)
            {
                historyCount = revHistory.Count;
            }
            if (historyCount == 0)
            {
                revHistory = new AList<string>();
                revHistory.AddItem(revId);
                historyCount = 1;
            }
            else
            {
                if (!revHistory[0].Equals(rev.GetRevId()))
                {
                    throw new CouchbaseLiteException(StatusCode.BadRequest);
                }
            }
            bool success = false;
            BeginTransaction();
            try
            {
                // First look up all locally-known revisions of this document:
                long docNumericID = GetOrInsertDocNumericID(docId);
                RevisionList localRevs = GetAllRevisionsOfDocumentID(docId, docNumericID, false);
                if (localRevs == null)
                {
                    throw new CouchbaseLiteException(StatusCode.InternalServerError);
                }

                IList<bool> outIsDeleted = new AList<bool>();
                IList<bool> outIsConflict = new AList<bool>();
                bool oldWinnerWasDeletion = false;
                string oldWinningRevID = WinningRevIDOfDoc(docNumericID, outIsDeleted, outIsConflict
                );
                if (outIsDeleted.Count > 0)
                {
                    oldWinnerWasDeletion = true;
                }
                if (outIsConflict.Count > 0)
                {
                    inConflict = true;
                }

                // Walk through the remote history in chronological order, matching each revision ID to
                // a local revision. When the list diverges, start creating blank local revisions to fill
                // in the local history:
                long sequence = 0;
                long localParentSequence = 0;
                for (int i = revHistory.Count - 1; i >= 0; --i)
                {
                    revId = revHistory[i];
                    RevisionInternal localRev = localRevs.RevWithDocIdAndRevId(docId, revId);
                    if (localRev != null)
                    {
                        // This revision is known locally. Remember its sequence as the parent of the next one:
                        sequence = localRev.GetSequence();
                        Debug.Assert((sequence > 0));
                        localParentSequence = sequence;
                    }
                    else
                    {
                        // This revision isn't known, so add it:
                        RevisionInternal newRev;
                        IEnumerable<Byte> data = null;
                        bool current = false;
                        if (i == 0)
                        {
                            // Hey, this is the leaf revision we're inserting:
                            newRev = rev;
                            if (!rev.IsDeleted())
                            {
                                data = EncodeDocumentJSON(rev);
                                if (data == null)
                                {
                                    throw new CouchbaseLiteException(StatusCode.BadRequest);
                                }
                            }
                            current = true;
                        }
                        else
                        {
                            // It's an intermediate parent, so insert a stub:
                            newRev = new RevisionInternal(docId, revId, false, this);
                        }

                        // Insert it:
                        sequence = InsertRevision(newRev, docNumericID, sequence, current, (GetAttachmentsFromRevision(newRev).Count > 0), data);
                        if (sequence <= 0)
                        {
                            throw new CouchbaseLiteException(StatusCode.InternalServerError);
                        }
                        if (i == 0)
                        {
                            // Write any changed attachments for the new revision. As the parent sequence use
                            // the latest local revision (this is to copy attachments from):
                            var attachments = GetAttachmentsFromRevision(rev);
                            if (attachments != null)
                            {
                                ProcessAttachmentsForRevision(attachments, rev, localParentSequence);
                                StubOutAttachmentsInRevision(attachments, rev);
                            }
                        }
                    }
                }
                // Mark the latest local rev as no longer current:
                if (localParentSequence > 0 && localParentSequence != sequence)
                {
                    ContentValues args = new ContentValues();
                    args["current"] = 0;
                    string[] whereArgs = new string[] { Convert.ToString(localParentSequence) };
                    try
                    {
                        var numRowsChanged = StorageEngine.Update("revs", args, "sequence=?", whereArgs);
                        if (numRowsChanged == 0)
                        {
                            inConflict = true;
                        }
                    }
                    catch (Exception)
                    {
                        throw new CouchbaseLiteException(StatusCode.InternalServerError);
                    }
                }

                var winningRev = Winner(docNumericID, oldWinningRevID, oldWinnerWasDeletion, rev);
                success = true;
                NotifyChange(rev, winningRev, source, inConflict);
            }
            catch (SQLException)
            {
                throw new CouchbaseLiteException(StatusCode.InternalServerError);
            }
            finally
            {
                EndTransaction(success);
            }
        }
        public override long InsertWithOnConflict (String table, String nullColumnHack, ContentValues initialValues, ConflictResolutionStrategy conflictResolutionStrategy)
        {
            if (!String.IsNullOrWhiteSpace(nullColumnHack)) {
                var e = new InvalidOperationException("{0} does not support the 'nullColumnHack'.".Fmt(Tag));
                Log.E(Tag, "Unsupported use of nullColumnHack", e);
                throw e;
            }

            var command = GetInsertCommand(table, initialValues, conflictResolutionStrategy);

            var lastInsertedId = -1L;
            try {
                command.ExecuteNonQuery();

                // Get the new row's id.
                // TODO.ZJG: This query should ultimately be replaced with a call to sqlite3_last_insert_rowid.
                var lastInsertedIndexCommand = new SqliteCommand("select last_insert_rowid()", Connection, currentTransaction);
                lastInsertedId = (Int64)lastInsertedIndexCommand.ExecuteScalar();
                lastInsertedIndexCommand.Dispose();
                if (lastInsertedId == -1L) {
                    Log.E(Tag, "Error inserting " + initialValues + " using " + command.CommandText);
                } else {
                    Log.V(Tag, "Inserting row " + lastInsertedId + " from " + initialValues + " using " + command.CommandText);
                }
            } catch (Exception ex) {
                Log.E(Tag, "Error inserting into table " + table, ex);
            } finally {
                command.Dispose();
            }
            return lastInsertedId;
        }
Пример #21
0
        //Methods

        /// <summary>
        /// Compacts the <see cref="Couchbase.Lite.Database" /> file by purging non-current 
        /// <see cref="Couchbase.Lite.Revision" />s and deleting unused <see cref="Couchbase.Lite.Attachment" />s.
        /// </summary>
        /// <exception cref="Couchbase.Lite.CouchbaseLiteException">thrown if an issue occurs while 
        /// compacting the <see cref="Couchbase.Lite.Database" /></exception>
        public void Compact()
        {
            // Can't delete any rows because that would lose revision tree history.
            // But we can remove the JSON of non-current revisions, which is most of the space.
            try
            {
                Log.V(Tag, "Deleting JSON of old revisions...");
                PruneRevsToMaxDepth(0);
                Log.V(Tag, "Deleting JSON of old revisions...");

                var args = new ContentValues();
                args["json"] = null;
                StorageEngine.Update("revs", args, "current=0 AND json IS NOT NULL", null);
            }
            catch (SQLException e)
            {
                Log.E(Tag, "Error compacting", e);
                throw new CouchbaseLiteException(StatusCode.InternalServerError);
            }

            Log.V(Tag, "Deleting old attachments...");
            var result = GarbageCollectAttachments();
            if (!result.IsSuccessful)
            {
                throw new CouchbaseLiteException(result.GetCode());
            }

            try
            {
                Log.V(Tag, "Vacuuming SQLite sqliteDb..." + result);
                StorageEngine.ExecSQL("VACUUM");
            }
            catch (SQLException e)
            {
                Log.E(Tag, "Error vacuuming sqliteDb", e);
                throw new CouchbaseLiteException(StatusCode.InternalServerError);
            }
        }
        public override int Update (String table, ContentValues values, String whereClause, params String[] whereArgs)
        {
            Debug.Assert(!table.IsEmpty());
            Debug.Assert(values != null);

            var builder = new SqliteCommandBuilder();
            builder.SetAllValues = false;

            var command = GetUpdateCommand(table, values, whereClause, whereArgs);

            var resultCount = -1;
            try {
                resultCount = (Int32)command.ExecuteNonQuery ();
            } catch (Exception ex) {
                Log.E(Tag, "Error updating table " + table, ex);
            }
            return resultCount;
        }
Пример #23
0
 /// <exception cref="Couchbase.Lite.CouchbaseLiteException"></exception>
 internal void InsertAttachmentForSequenceWithNameAndType(long sequence, string name, string contentType, int revpos, BlobKey key)
 {
     try
     {
         var args = new ContentValues(); // TODO: Create Add override and refactor to use initializer syntax.
         args["sequence"] = sequence;
         args["filename"] = name;
         if (key != null)
         {
             args.Put("key", key.GetBytes());
             args.Put("length", Attachments.GetSizeOfBlob(key));
         }
         args["type"] = contentType;
         args["revpos"] = revpos;
         var result = StorageEngine.Insert("attachments", null, args);
         if (result == -1)
         {
             var msg = "Insert attachment failed (returned -1)";
             Log.E(Tag, msg);
             throw new CouchbaseLiteException(msg, StatusCode.InternalServerError);
         }
     }
     catch (SQLException e)
     {
         Log.E(Tag, "Error inserting attachment", e);
         throw new CouchbaseLiteException(StatusCode.InternalServerError);
     }
 }
        /// <summary>
        /// Avoids the additional database trip that using SqliteCommandBuilder requires.
        /// </summary>
        /// <returns>The update command.</returns>
        /// <param name="table">Table.</param>
        /// <param name="values">Values.</param>
        /// <param name="whereClause">Where clause.</param>
        /// <param name="whereArgs">Where arguments.</param>
        SqliteCommand GetUpdateCommand (string table, ContentValues values, string whereClause, string[] whereArgs)
        {
            var builder = new StringBuilder("UPDATE ");

            builder.Append(table);
            builder.Append(" SET ");

            // Append our content column names and create our SQL parameters.
            var valueSet = values.ValueSet();
            var valueSetLength = valueSet.Count();

            var whereArgsLength = (whereArgs != null ? whereArgs.Length : 0);
            var sqlParams = new List<SqliteParameter>(valueSetLength + whereArgsLength);

            foreach(var column in valueSet)
            {
                if (sqlParams.Count > 0) {
                    builder.Append(",");
                }
                builder.AppendFormat( "{0} = @{0}", column.Key);
                sqlParams.Add(new SqliteParameter(column.Key, column.Value));
            }

            if (!whereClause.IsEmpty()) {
                builder.Append(" WHERE ");
                builder.Append(whereClause.ReplacePositionalParams());
            }

            if (whereArgsLength > 0)
                sqlParams.AddRange(whereArgs.ToSqliteParameters());

            var sql = builder.ToString();
            var command = new SqliteCommand(sql, Connection, currentTransaction);
            command.Parameters.Clear();
            command.Parameters.AddRange(sqlParams.ToArray());

            return command;
        }
Пример #25
0
 internal Int64 InsertDocumentID(String docId)
 {
     var rowId = -1L;
     try
     {
         ContentValues args = new ContentValues();
         args["docid"] = docId;
         rowId = StorageEngine.Insert("docs", null, args);
     }
     catch (Exception e)
     {
         Log.E(Tag, "Error inserting document id", e);
     }
     return rowId;
 }
        /// <summary>
        /// Avoids the additional database trip that using SqliteCommandBuilder requires.
        /// </summary>
        /// <returns>The insert command.</returns>
        /// <param name="table">Table.</param>
        /// <param name="values">Values.</param>
        /// <param name="conflictResolutionStrategy">Conflict resolution strategy.</param>
        SqliteCommand GetInsertCommand (String table, ContentValues values, ConflictResolutionStrategy conflictResolutionStrategy)
        {
            var builder = new StringBuilder("INSERT");

            if (conflictResolutionStrategy != ConflictResolutionStrategy.None) {
                builder.Append(" OR ");
                builder.Append(conflictResolutionStrategy);
            }

            builder.Append(" INTO ");
            builder.Append(table);
            builder.Append(" (");

            // Append our content column names and create our SQL parameters.
            var valueSet = values.ValueSet();
            var sqlParams = new SqliteParameter[valueSet.LongCount()];
            var valueBuilder = new StringBuilder();
            var index = 0L;

            foreach(var column in valueSet)
            {
                if (index > 0) {
                         builder.Append(",");
                    valueBuilder.Append(",");
                }

                     builder.AppendFormat( "{0}", column.Key);
                valueBuilder.AppendFormat("@{0}", column.Key);

                sqlParams[index++] = new SqliteParameter(column.Key, column.Value);
            }

            builder.Append(") VALUES (");
            builder.Append(valueBuilder);
            builder.Append(")");

            var sql = builder.ToString();
            var command = new SqliteCommand(sql, Connection, currentTransaction);
            command.Parameters.Clear();
            command.Parameters.AddRange(sqlParams);

            return command;
        }
Пример #27
0
        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());
                }
            }
        }
		public abstract long Insert(string table, string nullColumnHack, ContentValues values);
Пример #29
0
        /// <summary>
        /// Deletes the <see cref="Couchbase.Lite.View"/>'s persistent index.  The index is regenerated on the next <see cref="Couchbase.Lite.Query"/> execution.
        /// </summary>
        public void DeleteIndex()
        {
            if (Id < 0)
                return;

            var success = false;

            try
            {
                Database.BeginTransaction();

                var whereArgs = new string[] { Sharpen.Extensions.ToString(Id) };
                Database.StorageEngine.Delete("maps", "view_id=@", whereArgs);

                var updateValues = new ContentValues();
                updateValues["lastSequence"] = 0;

                Database.StorageEngine.Update("views", updateValues, "view_id=@", whereArgs); // TODO: Convert to ADO params.

                success = true;
            }
            catch (SQLException e)
            {
                Log.E(Database.Tag, "Error removing index", e);
            }
            finally
            {
                Database.EndTransaction(success);
            }
        }
		/// <summary>
		/// Avoids the additional database trip that using SqliteCommandBuilder requires.
		/// </summary>
		/// <returns>The insert command.</returns>
		/// <param name="table">Table.</param>
		/// <param name="values">Values.</param>
		/// <param name="conflictResolutionStrategy">Conflict resolution strategy.</param>
		ISQLiteStatement GetInsertCommand (String table, ContentValues values, ConflictResolutionStrategy conflictResolutionStrategy)
		{
			var builder = new StringBuilder("INSERT");

			if (conflictResolutionStrategy != ConflictResolutionStrategy.None) {
				builder.Append(" OR ");
				builder.Append(conflictResolutionStrategy);
			}

			builder.Append(" INTO ");
			builder.Append(table);
			builder.Append(" (");

			// Append our content column names and create our SQL parameters.
			var valueSet = values.ValueSet();
			var sqlParams = new Dictionary<string, object> (); 
			var valueBuilder = new StringBuilder();
			var index = 0L;

			foreach(var column in valueSet)
			{
				if (index > 0) {
					builder.Append(",");
					valueBuilder.Append(",");
				}

				builder.AppendFormat( "{0}", column.Key);
				valueBuilder.AppendFormat("@{0}", column.Key);

				index++;
				sqlParams.Add (column.Key, column.Value);
			}

			builder.Append(") VALUES (");
			builder.Append(valueBuilder);
			builder.Append(")");

			var stmt = connection.Prepare (builder.ToString ());
			foreach (var p in sqlParams)
				stmt.Bind (p.Key, p.Value);

			return stmt;
		}