private bool UpdateDoc(BsonValue id, BsonDocument doc) { // serialize object var bytes = BsonSerializer.Serialize(doc); // start transaction this.Database.Transaction.Begin(); try { var col = this.GetCollectionPage(false); // if no collection, no updates if (col == null) { this.Database.Transaction.Abort(); return(false); } // normalize id before find var value = id.Normalize(col.PK.Options); // find indexNode from pk index var indexNode = this.Database.Indexer.Find(col.PK, value, false, Query.Ascending); // if not found document, no updates if (indexNode == null) { this.Database.Transaction.Abort(); return(false); } // update data storage var dataBlock = this.Database.Data.Update(col, indexNode.DataBlock, bytes); // delete/insert indexes - do not touch on PK foreach (var index in col.GetIndexes(false)) { var key = doc.Get(index.Field); var node = this.Database.Indexer.GetNode(dataBlock.IndexRef[index.Slot]); // check if my index node was changed if (node.Key.CompareTo(key) != 0) { // remove old index node this.Database.Indexer.Delete(index, node.Position); // and add a new one var newNode = this.Database.Indexer.AddNode(index, key); // point my index to data object newNode.DataBlock = dataBlock.Position; // point my dataBlock dataBlock.IndexRef[index.Slot] = newNode.Position; dataBlock.Page.IsDirty = true; } } this.Database.Transaction.Commit(); return(true); } catch { this.Database.Transaction.Rollback(); throw; } }
/// <summary> /// Implement internal update document /// </summary> private bool UpdateDocument(CollectionPage col, BsonDocument doc) { // normalize id before find var id = doc["_id"]; // validate id for null, min/max values if (id.IsNull || id.IsMinValue || id.IsMaxValue) { throw LiteException.InvalidDataType("_id", id); } _log.Write(Logger.COMMAND, "update document on '{0}' :: _id = {1}", col.CollectionName, id); // find indexNode from pk index var pkNode = _indexer.Find(col.PK, id, false, Query.Ascending); // if not found document, no updates if (pkNode == null) { return(false); } // serialize document in bytes var bytes = BsonSerializer.Serialize(doc); // update data storage var dataBlock = _data.Update(col, pkNode.DataBlock, bytes); // get all non-pk index nodes from this data block var allNodes = _indexer.GetNodeList(pkNode, false).ToArray(); // delete/insert indexes - do not touch on PK foreach (var index in col.GetIndexes(false)) { // using Distinct/ToArray because I will do many queries var keys = doc.GetValues(index.Field, true, index.Unique).ToArray(); // get a list of to delete nodes (using ToArray to resolve now) var toDelete = allNodes .Where(x => x.Slot == index.Slot && !keys.Any(k => k == x.Key)) .ToArray(); // get a list of to insert nodes (using ToArray to resolve now) var toInsert = keys .Where(x => !allNodes.Any(k => k.Slot == index.Slot && k.Key == x)) .ToArray(); // delete changed index nodes foreach (var node in toDelete) { _indexer.Delete(index, node.Position); } // insert new nodes foreach (var key in toInsert) { // and add a new one var node = _indexer.AddNode(index, key, pkNode); // link my node to data block node.DataBlock = dataBlock.Position; } } return(true); }
/// <summary> /// Internal implementation of insert a document /// </summary> private void InsertDocument(CollectionPage col, BsonDocument doc, BsonType autoId) { BsonValue id; // collection Sequence was created after release current datafile version. // In this case, Sequence will be 0 but already has documents. Let's fix this // ** this code can be removed when datafile change from 7 (HeaderPage.FILE_VERSION) ** if (col.Sequence == 0 && col.DocumentCount > 0) { var max = this.Max(col.CollectionName, "_id"); // if max value is a number, convert to Sequence last value // if not, just set sequence as document count col.Sequence = (max.IsInt32 || max.IsInt64 || max.IsDouble || max.IsDecimal) ? Convert.ToInt64(max.RawValue) : Convert.ToInt64(col.DocumentCount); } // increase collection sequence _id col.Sequence++; _pager.SetDirty(col); // if no _id, add one if (!doc.RawValue.TryGetValue("_id", out id)) { doc["_id"] = id = autoId == BsonType.ObjectId ? new BsonValue(ObjectId.NewObjectId()) : autoId == BsonType.Guid ? new BsonValue(Guid.NewGuid()) : autoId == BsonType.DateTime ? new BsonValue(DateTime.Now) : autoId == BsonType.Int32 ? new BsonValue((Int32)col.Sequence) : autoId == BsonType.Int64 ? new BsonValue(col.Sequence) : BsonValue.Null; } // create bubble in sequence number if _id is bigger than current sequence else if (autoId == BsonType.Int32 || autoId == BsonType.Int64) { var current = id.AsInt64; // if current id is bigger than sequence, jump sequence to this number. Other was, do not increse sequnce col.Sequence = current >= col.Sequence ? current : col.Sequence - 1; } // test if _id is a valid type if (id.IsNull || id.IsMinValue || id.IsMaxValue) { throw LiteException.InvalidDataType("_id", id); } _log.Write(Logger.COMMAND, "insert document on '{0}' :: _id = {1}", col.CollectionName, id.RawValue); // serialize object var bytes = BsonSerializer.Serialize(doc); // storage in data pages - returns dataBlock address var dataBlock = _data.Insert(col, bytes); // store id in a PK index [0 array] var pk = _indexer.AddNode(col.PK, id, null); // do link between index <-> data block pk.DataBlock = dataBlock.Position; // for each index, insert new IndexNode foreach (var index in col.GetIndexes(false)) { // for each index, get all keys (support now multi-key) - gets distinct values only // if index are unique, get single key only var expr = new BsonExpression(index.Expression); var keys = expr.Execute(doc, true); // do a loop with all keys (multi-key supported) foreach (var key in keys) { // insert node var node = _indexer.AddNode(index, key, pk); // link my index node to data block address node.DataBlock = dataBlock.Position; } } }
/// <summary> /// Implement update command to a document inside a collection /// </summary> public int Update(string colName, IEnumerable <BsonDocument> docs) { return(this.WriteTransaction <int>(colName, false, (col) => { // no collection, no updates if (col == null) { return 0; } var count = 0; foreach (var doc in docs) { // normalize id before find var id = doc["_id"].Normalize(col.PK.Options); // validate id for null, min/max values if (id.IsNull || id.IsMinValue || id.IsMaxValue) { throw LiteException.InvalidDataType("_id", id); } _log.Write(Logger.COMMAND, "update document on '{0}' :: _id = {1}", colName, id); // find indexNode from pk index var indexNode = _indexer.Find(col.PK, id, false, Query.Ascending); // if not found document, no updates if (indexNode == null) { continue; } // serialize document in bytes var bytes = BsonSerializer.Serialize(doc); // update data storage var dataBlock = _data.Update(col, indexNode.DataBlock, bytes); // delete/insert indexes - do not touch on PK foreach (var index in col.GetIndexes(false)) { var key = doc.Get(index.Field); var node = _indexer.GetNode(dataBlock.IndexRef[index.Slot]); // check if my index node was changed if (node.Key.CompareTo(key) != 0) { // remove old index node _indexer.Delete(index, node.Position); // and add a new one var newNode = _indexer.AddNode(index, key); // point my index to data object newNode.DataBlock = dataBlock.Position; // set my block page as dirty before change _pager.SetDirty(dataBlock.Page); // point my dataBlock dataBlock.IndexRef[index.Slot] = newNode.Position; } } _cache.CheckPoint(); count++; } return count; })); }
/// <summary> /// Insert a new document to this collection. Document Id must be a new value in collection /// </summary> public virtual void Insert(T doc) { if (doc == null) { throw new ArgumentNullException("doc"); } // gets document Id var id = BsonSerializer.GetIdValue(doc); if (id == null) { throw new LiteException("Document Id can't be null"); } // serialize object var bytes = BsonSerializer.Serialize(doc); _engine.Transaction.Begin(); try { var col = this.GetCollectionPage(true); // storage in data pages - returns dataBlock address var dataBlock = _engine.Data.Insert(col, new IndexKey(id), bytes); // store id in a PK index [0 array] var pk = _engine.Indexer.AddNode(col.PK, id); // do links between index <-> data block pk.DataBlock = dataBlock.Position; dataBlock.IndexRef[0] = pk.Position; // for each index, insert new IndexNode for (byte i = 1; i < col.Indexes.Length; i++) { var index = col.Indexes[i]; if (!index.IsEmpty) { var key = BsonSerializer.GetFieldValue(doc, index.Field); var node = _engine.Indexer.AddNode(index, key); // point my index to data object node.DataBlock = dataBlock.Position; // point my dataBlock dataBlock.IndexRef[i] = node.Position; } } _engine.Transaction.Commit(); } catch (Exception ex) { _engine.Transaction.Rollback(); throw ex; } }
/// <summary> /// Insert a new document to this collection. Document Id must be a new value in collection - Returns document Id /// </summary> public virtual BsonValue Insert(T document) { if (document == null) { throw new ArgumentNullException("document"); } // set an id value if document object needs this.Database.Mapper.SetAutoId(document, this.GetBsonCollection()); var doc = this.Database.Mapper.ToDocument(document); BsonValue id; // add ObjectId to _id if _id not found if (!doc.RawValue.TryGetValue("_id", out id)) { id = doc["_id"] = ObjectId.NewObjectId(); } // test if _id is a valid type if (id.IsNull || id.IsMinValue || id.IsMaxValue) { throw LiteException.InvalidDataType("_id", id); } // serialize object var bytes = BsonSerializer.Serialize(doc); this.Database.Transaction.Begin(); try { var col = this.GetCollectionPage(true); // storage in data pages - returns dataBlock address var dataBlock = this.Database.Data.Insert(col, bytes); // store id in a PK index [0 array] var pk = this.Database.Indexer.AddNode(col.PK, id); // do links between index <-> data block pk.DataBlock = dataBlock.Position; dataBlock.IndexRef[0] = pk.Position; // for each index, insert new IndexNode foreach (var index in col.GetIndexes(false)) { var key = doc.Get(index.Field); var node = this.Database.Indexer.AddNode(index, key); // point my index to data object node.DataBlock = dataBlock.Position; // point my dataBlock dataBlock.IndexRef[index.Slot] = node.Position; } this.Database.Transaction.Commit(); return(id); } catch { this.Database.Transaction.Rollback(); throw; } }