/// <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.RawValue); // 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 = _bsonWriter.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)) { var expr = new BsonExpression(index.Expression); // getting all keys do check var keys = expr.Execute(doc).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); }
private const int MAX_SORT_PAGES = 5000; // ~ 20Mb? /// <summary> /// EXPERIMENTAL Find with sort operation - use memory or disk (temp file) to sort /// </summary> public List <BsonDocument> FindSort(string collection, Query query, string orderBy, int order = Query.Ascending, int skip = 0, int limit = int.MaxValue) { if (collection.IsNullOrWhiteSpace()) { throw new ArgumentNullException(nameof(collection)); } if (query == null) { throw new ArgumentNullException(nameof(query)); } _log.Write(Logger.COMMAND, "query-sort documents in '{0}' => {1}", collection, query); // evaluate orderBy path/expression var expr = new BsonExpression(orderBy); // lock database for read access using (_locker.Read()) { var last = order == Query.Ascending ? BsonValue.MaxValue : BsonValue.MinValue; var total = limit == int.MaxValue ? int.MaxValue : skip + limit; var indexCounter = 0; var disk = new TempDiskService(); // create memory database using (var engine = new LiteEngine(disk)) { // get collection page var col = this.GetCollectionPage(collection, false); if (col == null) { return(new List <BsonDocument>()); } // create a temp collection in new memory database var tmp = engine._collections.Add("tmp"); // create index pointer var index = engine._indexer.CreateIndex(tmp); // get head/tail index node var head = engine._indexer.GetNode(index.HeadNode); var tail = engine._indexer.GetNode(index.TailNode); // first lets works only with index in query var nodes = query.Run(col, _indexer); foreach (var node in nodes) { var buffer = _data.Read(node.DataBlock); var doc = _bsonReader.Deserialize(buffer).AsDocument; // if needs use filter if (query.UseFilter && query.FilterDocument(doc) == false) { continue; } // get key to be sorted var key = expr.Execute(doc, true).First(); var diff = key.CompareTo(last); // add to list only if lower than last space if ((order == Query.Ascending && diff < 1) || (order == Query.Descending && diff > -1)) { var tmpNode = engine._indexer.AddNode(index, key, null); tmpNode.DataBlock = node.DataBlock; tmpNode.CacheDocument = doc; indexCounter++; // exceeded limit if (indexCounter > total) { var exceeded = (order == Query.Ascending) ? tail.Prev[0] : head.Next[0]; engine._indexer.Delete(index, exceeded); var lnode = (order == Query.Ascending) ? tail.Prev[0] : head.Next[0]; last = engine._indexer.GetNode(lnode).Key; indexCounter--; } // if memory pages excedded limit size, flush to disk if (engine._cache.DirtyUsed > MAX_SORT_PAGES) { engine._trans.PersistDirtyPages(); engine._trans.CheckPoint(); } } } var result = new List <BsonDocument>(); // if skip is lower than limit, take nodes from skip from begin // if skip is higher than limit, take nodes from end and revert order (avoid lots of skip) var find = skip < limit? engine._indexer.FindAll(index, order).Skip(skip).Take(limit) : // get from original order engine._indexer.FindAll(index, -order).Take(limit).Reverse(); // avoid long skips, take from end and revert // --- foreach (var node in engine._indexer.FindAll(index, order).Skip(skip).Take(limit)) foreach (var node in find) { // if document are in cache, use it. if not, get from disk again var doc = node.CacheDocument; if (doc == null) { var buffer = _data.Read(node.DataBlock); doc = _bsonReader.Deserialize(buffer).AsDocument; } result.Add(doc); } return(result); } } }