Example #1
0
        /// <summary>
        /// Internal implementation of insert a document
        /// </summary>
        private void InsertDocument(Snapshot snapshot, BsonDocument doc, BsonAutoId autoId, IndexService indexer, DataService data)
        {
            // if no _id, use AutoId
            if (!doc.TryGetValue("_id", out var id))
            {
                doc["_id"] = id =
                    autoId == BsonAutoId.ObjectId ? new BsonValue(ObjectId.NewObjectId()) :
                    autoId == BsonAutoId.Guid ? new BsonValue(Guid.NewGuid()) :
                    this.GetSequence(snapshot, autoId);
            }
            else if (id.IsNumber)
            {
                // update memory sequence of numeric _id
                this.SetSequence(snapshot, id);
            }

            // test if _id is a valid type
            if (id.IsNull || id.IsMinValue || id.IsMaxValue)
            {
                throw LiteException.InvalidDataType("_id", id);
            }

            // storage in data pages - returns dataBlock address
            var dataBlock = data.Insert(doc);

            IndexNode last = null;

            // for each index, insert new IndexNode
            foreach (var index in snapshot.CollectionPage.GetCollectionIndexes())
            {
                // for each index, get all keys (supports multi-key) - gets distinct values only
                // if index are unique, get single key only
                var keys = index.BsonExpr.Execute(doc, _header.Pragmas.Collation);

                // do a loop with all keys (multi-key supported)
                foreach (var key in keys)
                {
                    // insert node
                    var node = indexer.AddNode(index, key, dataBlock, last);

                    last = node;
                }
            }
        }
Example #2
0
        /// <summary>
        /// Create a new index (or do nothing if already exists) to a collection/field
        /// </summary>
        public bool EnsureIndex(string collection, string name, BsonExpression expression, bool unique)
        {
            if (collection.IsNullOrWhiteSpace())
            {
                throw new ArgumentNullException(nameof(collection));
            }
            if (name.IsNullOrWhiteSpace())
            {
                throw new ArgumentNullException(nameof(name));
            }
            if (expression == null)
            {
                throw new ArgumentNullException(nameof(expression));
            }
            if (expression.IsIndexable == false)
            {
                throw new ArgumentException("Index expressions must contains at least one document field. Used methods must be immutable. Parameters are not supported.", nameof(expression));
            }

            if (name.Length > INDEX_NAME_MAX_LENGTH)
            {
                throw LiteException.InvalidIndexName(name, collection, "MaxLength = " + INDEX_NAME_MAX_LENGTH);
            }
            if (!name.IsWord())
            {
                throw LiteException.InvalidIndexName(name, collection, "Use only [a-Z$_]");
            }
            if (name.StartsWith("$"))
            {
                throw LiteException.InvalidIndexName(name, collection, "Index name can't starts with `$`");
            }

            if (name == "_id")
            {
                return(false);               // always exists
            }
            return(this.AutoTransaction(transaction =>
            {
                var snapshot = transaction.CreateSnapshot(LockMode.Write, collection, true);
                var col = snapshot.CollectionPage;
                var indexer = new IndexService(snapshot);
                var data = new DataService(snapshot);

                // check if index already exists
                var current = col.GetCollectionIndex(name);

                // if already exists, just exit
                if (current != null)
                {
                    // but if expression are different, throw error
                    if (current.Expression != expression.Source)
                    {
                        throw LiteException.IndexAlreadyExist(name);
                    }

                    return false;
                }

                LOG($"create index `{collection}.{name}`", "COMMAND");

                // create index head
                var index = indexer.CreateIndex(name, expression.Source, unique);
                var count = 0u;

                // read all objects (read from PK index)
                foreach (var pkNode in new IndexAll("_id", LiteDB.Query.Ascending).Run(col, indexer))
                {
                    using (var reader = new BufferReader(data.Read(pkNode.DataBlock)))
                    {
                        var doc = reader.ReadDocument(expression.Fields);

                        // first/last node in this document that will be added
                        IndexNode last = null;
                        IndexNode first = null;

                        // get values from expression in document
                        var keys = expression.Execute(doc);

                        // adding index node for each value
                        foreach (var key in keys)
                        {
                            // when index key is an array, get items inside array.
                            // valid only for first level (if this items are another array, this arrays will be indexed as array)
                            if (key.IsArray)
                            {
                                var arr = key.AsArray;

                                foreach (var itemKey in arr)
                                {
                                    // insert new index node
                                    var node = indexer.AddNode(index, itemKey, pkNode.DataBlock, last, _flipCoin);

                                    if (first == null)
                                    {
                                        first = node;
                                    }

                                    last = node;

                                    count++;
                                }
                            }
                            else
                            {
                                // insert new index node
                                var node = indexer.AddNode(index, key, pkNode.DataBlock, last, _flipCoin);

                                if (first == null)
                                {
                                    first = node;
                                }

                                last = node;

                                count++;
                            }
                        }

                        // fix single linked-list in pkNode
                        if (first != null)
                        {
                            last.SetNextNode(pkNode.NextNode);
                            pkNode.SetNextNode(first.Position);
                        }
                    }

                    transaction.Safepoint();
                }

                index.KeyCount = count;

                return true;
            }));
        }
Example #3
0
        /// <summary>
        /// Implement internal update document
        /// </summary>
        private bool UpdateDocument(Snapshot snapshot, CollectionPage col, BsonDocument doc, IndexService indexer, DataService data)
        {
            // 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);
            }

            // find indexNode from pk index
            var pkNode = indexer.Find(col.PK, id, false, LiteDB.Query.Ascending);

            // if not found document, no updates
            if (pkNode == null)
            {
                return(false);
            }

            // update data storage
            data.Update(col, pkNode.DataBlock, doc);

            // get all current non-pk index nodes from this data block (slot, key, nodePosition)
            var oldKeys = indexer.GetNodeList(pkNode.NextNode)
                          .Select(x => new Tuple <byte, BsonValue, PageAddress>(x.Slot, x.Key, x.Position))
                          .ToArray();

            // build a list of all new key index keys
            var newKeys = new List <Tuple <byte, BsonValue, string> >();

            foreach (var index in col.GetCollectionIndexes().Where(x => x.Name != "_id"))
            {
                // getting all keys from expression over document
                var keys = index.BsonExpr.Execute(doc, _header.Pragmas.Collation);

                foreach (var key in keys)
                {
                    newKeys.Add(new Tuple <byte, BsonValue, string>(index.Slot, key, index.Name));
                }
            }

            if (oldKeys.Length == 0 && newKeys.Count == 0)
            {
                return(true);
            }

            // get a list of all nodes that are in oldKeys but not in newKeys (must delete)
            var toDelete = new HashSet <PageAddress>(oldKeys
                                                     .Where(x => newKeys.Any(n => n.Item1 == x.Item1 && n.Item2 == x.Item2) == false)
                                                     .Select(x => x.Item3));

            // get a list of all keys that are not in oldKeys (must insert)
            var toInsert = newKeys
                           .Where(x => oldKeys.Any(o => o.Item1 == x.Item1 && o.Item2 == x.Item2) == false)
                           .ToArray();

            // if nothing to change, just exit
            if (toDelete.Count == 0 && toInsert.Length == 0)
            {
                return(true);
            }

            // delete nodes and return last keeped node in list
            var last = indexer.DeleteList(pkNode.Position, toDelete);

            // now, insert all new nodes
            foreach (var elem in toInsert)
            {
                var index = col.GetCollectionIndex(elem.Item3);

                last = indexer.AddNode(index, elem.Item2, pkNode.DataBlock, last);
            }

            return(true);
        }
Example #4
0
        /// <summary>
        /// Create a new index (or do nothing if already exists) to a collection/field
        /// </summary>
        public async Task <bool> EnsureIndexAsync(string collection, string name, BsonExpression expression, bool unique)
        {
            if (collection.IsNullOrWhiteSpace())
            {
                throw new ArgumentNullException(nameof(collection));
            }
            if (name.IsNullOrWhiteSpace())
            {
                throw new ArgumentNullException(nameof(name));
            }
            if (expression == null)
            {
                throw new ArgumentNullException(nameof(expression));
            }
            if (expression.IsIndexable == false)
            {
                throw new ArgumentException("Index expressions must contains at least one document field. Used methods must be immutable. Parameters are not supported.", nameof(expression));
            }

            if (name.Length > INDEX_NAME_MAX_LENGTH)
            {
                throw LiteException.InvalidIndexName(name, collection, "MaxLength = " + INDEX_NAME_MAX_LENGTH);
            }
            if (!name.IsWord())
            {
                throw LiteException.InvalidIndexName(name, collection, "Use only [a-Z$_]");
            }
            if (name.StartsWith("$"))
            {
                throw LiteException.InvalidIndexName(name, collection, "Index name can't starts with `$`");
            }
            if (expression.IsScalar == false && unique)
            {
                throw new LiteException(0, "Multikey index expression do not support unique option");
            }

            if (expression.Source == "$._id")
            {
                return(false);                              // always exists
            }
            return(await this.AutoTransaction(async transaction =>
            {
                var snapshot = await transaction.CreateSnapshot(LockMode.Write, collection, true);
                var collectionPage = snapshot.CollectionPage;
                var indexer = new IndexService(snapshot, _header.Pragmas.Collation);
                var data = new DataService(snapshot);

                // check if index already exists
                var current = collectionPage.GetCollectionIndex(name);

                // if already exists, just exit
                if (current != null)
                {
                    // but if expression are different, throw error
                    if (current.Expression != expression.Source)
                    {
                        throw LiteException.IndexAlreadyExist(name);
                    }

                    return false;
                }

                LOG($"create index `{collection}.{name}`", "COMMAND");

                // create index head
                var index = await indexer.CreateIndex(name, expression.Source, unique);
                var count = 0u;

                // read all objects (read from PK index)
                await foreach (var pkNode in new IndexAll("_id", LiteDB.Query.Ascending).Run(collectionPage, indexer))
                {
                    await using (var reader = await BufferReaderAsync.CreateAsync(data.Read(pkNode.DataBlock)))
                    {
                        var doc = await reader.ReadDocument(expression.Fields);

                        // first/last node in this document that will be added
                        IndexNode last = null;
                        IndexNode first = null;

                        // get values from expression in document
                        var keys = expression.GetIndexKeys(doc, _header.Pragmas.Collation);

                        // adding index node for each value
                        foreach (var key in keys)
                        {
                            // insert new index node
                            var node = await indexer.AddNode(index, key, pkNode.DataBlock, last);

                            if (first == null)
                            {
                                first = node;
                            }

                            last = node;

                            count++;
                        }

                        // fix single linked-list in pkNode
                        if (first != null)
                        {
                            last.SetNextNode(pkNode.NextNode);
                            pkNode.SetNextNode(first.Position);
                        }
                    }

                    await transaction.Safepoint();
                }

                return true;
            }));
        }