Example #1
0
        /// <summary>
        /// Copy all file content to a steam
        /// </summary>
        public void Download(string id, Stream stream)
        {
            if (stream == null)
            {
                throw new ArgumentNullException("stream");
            }

            var file = this.FindById(id);

            if (file == null)
            {
                throw LiteException.FileNotFound(id);
            }

            file.CopyTo(stream);
        }
Example #2
0
File: Index.cs Project: Rizx/LiteDB
        /// <summary>
        /// Drop index and release slot for another index
        /// </summary>
        public bool DropIndex(string field)
        {
            if (string.IsNullOrEmpty(field))
            {
                throw new ArgumentNullException("field");
            }
            if (field == "_id")
            {
                throw LiteException.IndexDropId();
            }

            // start transaction
            this.Database.Transaction.Begin();

            try
            {
                var col = this.GetCollectionPage(false);

                // if collection not exists, no drop
                if (col == null)
                {
                    this.Database.Transaction.Abort();
                    return(false);
                }

                // search for index reference
                var index = col.GetIndex(field);

                // delete all data pages + indexes pages
                this.Database.Indexer.DropIndex(index);

                // clear index reference
                index.Clear();

                // save collection page
                col.IsDirty = true;

                this.Database.Transaction.Commit();

                return(true);
            }
            catch
            {
                this.Database.Transaction.Rollback();
                throw;
            }
        }
Example #3
0
        /// <summary>
        /// Drop an index from a collection
        /// </summary>
        public bool DropIndex(string collection, string field)
        {
            if (collection.IsNullOrWhiteSpace())
            {
                throw new ArgumentNullException("collection");
            }
            if (field.IsNullOrWhiteSpace())
            {
                throw new ArgumentNullException("field");
            }

            if (field == "_id")
            {
                throw LiteException.IndexDropId();
            }

            return(this.Transaction <bool>(collection, false, (col) =>
            {
                // no collection, no index
                if (col == null)
                {
                    return false;
                }

                // search for index reference
                var index = col.GetIndex(field);

                // no index, no drop
                if (index == null)
                {
                    return false;
                }

                _log.Write(LoggerWrap.COMMAND, "drop index on '{0}' :: '{1}'", collection, field);

                // delete all data pages + indexes pages
                _indexer.DropIndex(index);

                // clear index reference
                index.Clear();

                // mark collection page as dirty
                _pager.SetDirty(col);

                return true;
            }));
        }
Example #4
0
        /// <summary>
        /// Create a new permanent index in all documents inside this collections if index not exists already. Returns true if index was created or false if already exits
        /// </summary>
        /// <param name="field">Document field name (case sensitive)</param>
        /// <param name="unique">If is a unique index</param>
        public bool EnsureIndex(string field, bool unique = false)
        {
            if (string.IsNullOrEmpty(field))
            {
                throw new ArgumentNullException("field");
            }
            if (field == "_id")
            {
                return(false);                // always exists
            }
            if (!CollectionIndex.IndexPattern.IsMatch(field))
            {
                throw LiteException.InvalidFormat("IndexField", field);
            }

            return(_engine.Value.EnsureIndex(_name, field, unique));
        }
Example #5
0
        /// <summary>
        /// Get a field name passing mapper type and returns property type
        /// </summary>
        private string GetTypeField(Type type, string property, out Type propertyType)
        {
            // lets get mapping bettwen .NET class and BsonDocument
            var            map = _mapper.GetPropertyMapper(type);
            PropertyMapper prop;

            if (map.TryGetValue(property, out prop))
            {
                propertyType = prop.PropertyType;

                return(prop.FieldName);
            }
            else
            {
                throw LiteException.PropertyNotMapped(property);
            }
        }
Example #6
0
        /// <summary>
        /// Set datafile length
        /// </summary>
        public void SetLength(long fileSize)
        {
            // if readonly, do nothing
            if (_options.ReadOnly)
            {
                return;
            }

            // checks if new fileSize will exceed limit size
            if (fileSize > _options.LimitSize)
            {
                throw LiteException.FileSizeExceeded(_options.LimitSize);
            }

            // fileSize parameter tell me final size of data file - helpful to extend first datafile
            _stream.SetLength(fileSize);
        }
Example #7
0
        internal LiteFileInfo(LiteEngine engine, string id, string filename)
        {
            if (!IdPattern.IsMatch(id))
            {
                throw LiteException.InvalidFormat("FileId", id);
            }

            _engine = engine;

            this.Id         = id;
            this.Filename   = Path.GetFileName(filename);
            this.MimeType   = MimeTypeConverter.GetMimeType(this.Filename);
            this.Length     = 0;
            this.Chunks     = 0;
            this.UploadDate = DateTime.Now;
            this.Metadata   = new BsonDocument();
        }
Example #8
0
        /// <summary>
        /// Initialize LiteEngine using custom disk service implementation and full engine options
        /// </summary>
        public LiteEngine(IDiskService disk, string password = null, TimeSpan?timeout = null, int cacheSize = 5000, Logger log = null)
        {
            if (disk == null)
            {
                throw new ArgumentNullException("disk");
            }

            _timeout   = timeout ?? TimeSpan.FromMinutes(1);
            _cacheSize = cacheSize;
            _disk      = disk;
            _log       = log ?? new Logger();

            // initialize datafile (create) and set log instance
            _disk.Initialize(_log, password);

            // read header page
            var header = BasePage.ReadPage(_disk.ReadPage(0)) as HeaderPage;

            // hash password with sha1 or keep as empty byte[20]
            var sha1 = password == null ? new byte[20] : AesEncryption.HashSHA1(password);

            // compare header password with user password even if not passed password (datafile can have password)
            if (sha1.BinaryCompareTo(header.Password) != 0)
            {
                // explicit dispose
                _disk.Dispose();
                throw LiteException.DatabaseWrongPassword();
            }

            // initialize AES encryptor
            if (password != null)
            {
                _crypto = new AesEncryption(password, header.Salt);
            }

            // initialize all services
            this.InitializeServices();

            // try recovery data only if journal are enabled
            if (_disk.IsJournalEnabled)
            {
                // try recovery if has journal file
                _trans.Recovery();
            }
        }
Example #9
0
        /// <summary>
        /// Copy all file content to a steam
        /// </summary>
        public LiteFileInfo Download(string id, Stream stream)
        {
            if (stream == null)
            {
                throw new ArgumentNullException(nameof(stream));
            }

            var file = this.FindById(id);

            if (file == null)
            {
                throw LiteException.FileNotFound(id);
            }

            file.CopyTo(stream);

            return(file);
        }
Example #10
0
        /// <summary>
        /// Update a document in this collection. Returns false if not found document in collection
        /// </summary>
        public virtual bool Update(T document)
        {
            if (document == null)
            {
                throw new ArgumentNullException("document");
            }

            // get BsonDocument from object
            var doc = this.Database.Mapper.ToDocument(document);

            var id = doc["_id"];

            if (id.IsNull || id.IsMinValue || id.IsMaxValue)
            {
                throw LiteException.InvalidDataType("_id", id);
            }

            return(this.UpdateDoc(id, doc));
        }
Example #11
0
        /// <summary>
        /// Try execute a block of code until timeout when IO lock exception occurs
        /// </summary>
        public void TryExec(Action action)
        {
            var timeout = DateTime.Now.Add(_connectionString.Timeout);

            while (DateTime.Now < timeout)
            {
                try
                {
                    action();
                    return;
                }
                catch (IOException ex)
                {
                    ex.WaitIfLocked(250);
                }
            }

            throw LiteException.LockTimeout(_connectionString.Timeout);
        }
Example #12
0
        /// <summary>
        /// Rename a collection
        /// </summary>
        public bool RenameCollection(string collection, string newName)
        {
            if (collection.IsNullOrWhiteSpace())
            {
                throw new ArgumentNullException("collection");
            }
            if (newName.IsNullOrWhiteSpace())
            {
                throw new ArgumentNullException("newName");
            }

            return(this.Transaction <bool>(collection, false, (col) =>
            {
                if (col == null)
                {
                    return false;
                }

                _log.Write(LoggerWrap.COMMAND, "rename collection '{0}' -> '{1}'", collection, newName);

                // check if newName already exists
                if (this.GetCollectionNames().Contains(newName, StringComparer.OrdinalIgnoreCase))
                {
                    throw LiteException.AlreadyExistsCollectionName(newName);
                }

                // change collection name and save
                col.CollectionName = newName;

                // set collection page as dirty
                _pager.SetDirty(col);

                // update header collection reference
                var header = _pager.GetPage <HeaderPage>(0);

                header.CollectionPages.Remove(collection);
                header.CollectionPages.Add(newName, col.PageID);

                _pager.SetDirty(header);

                return true;
            }));
        }
Example #13
0
        /// <summary>
        /// Persist single page bytes to disk
        /// </summary>
        public virtual void WritePage(uint pageID, byte[] buffer)
        {
            if (_options.ReadOnly)
            {
                throw LiteException.ReadOnlyDatabase();
            }

            var position = BasePage.GetSizeOfPages(pageID);

            _log.Write(Logger.DISK, "write page #{0:0000} :: {1}", pageID, (PageType)buffer[PAGE_TYPE_POSITION]);

            // position cursor
            if (_stream.Position != position)
            {
                _stream.Seek(position, SeekOrigin.Begin);
            }

            _stream.Write(buffer, 0, BasePage.PAGE_SIZE);
        }
Example #14
0
        /// <summary>
        /// Test if cache still valid (if datafile was changed by another process reset cache)
        /// Returns true if file was changed
        /// [Thread Safe]
        /// </summary>
        private bool DetectDatabaseChanges()
        {
            // if disk are exclusive don't need check dirty read
            if (_disk.IsExclusive)
            {
                return(false);
            }

            // empty cache? just exit
            if (_cache.CleanUsed == 0)
            {
                return(false);
            }

            _log.Write(Logger.CACHE, "checking disk to detect database changes from another process");

            // get ChangeID from cache
            var header   = _cache.GetPage(0) as HeaderPage;
            var changeID = header == null ? 0 : header.ChangeID;

            // and get header from disk
            var disk = BasePage.ReadPage(_disk.ReadPage(0)) as HeaderPage;

            // if disk header are in recovery mode, throw exception to datafile re-open and recovery pages
            if (disk.Recovery)
            {
                _log.Write(Logger.ERROR, "datafile in recovery mode, need re-open database");

                throw LiteException.NeedRecover();
            }

            // if header change, clear cache and add new header to cache
            if (disk.ChangeID != changeID)
            {
                _log.Write(Logger.CACHE, "file changed from another process, cleaning all cache pages");

                _cache.ClearPages();
                _cache.AddPage(disk);
                return(true);
            }

            return(false);
        }
Example #15
0
        /// <summary>
        /// Create a new instance from a Type
        /// </summary>
        public static object CreateInstance(Type type)
        {
            try
            {
                CreateObject c = null;

                if (_cacheCtor.TryGetValue(type, out c))
                {
                    return(c());
                }
                else
                {
                    if (type.IsClass)
                    {
                        var dynMethod = new DynamicMethod("_", type, null);
                        var il        = dynMethod.GetILGenerator();
                        il.Emit(OpCodes.Newobj, type.GetConstructor(Type.EmptyTypes));
                        il.Emit(OpCodes.Ret);
                        c = (CreateObject)dynMethod.CreateDelegate(typeof(CreateObject));
                        _cacheCtor.Add(type, c);
                    }
                    else // structs
                    {
                        var dynMethod = new DynamicMethod("_", typeof(object), null);
                        var il        = dynMethod.GetILGenerator();
                        var lv        = il.DeclareLocal(type);
                        il.Emit(OpCodes.Ldloca_S, lv);
                        il.Emit(OpCodes.Initobj, type);
                        il.Emit(OpCodes.Ldloc_0);
                        il.Emit(OpCodes.Box, type);
                        il.Emit(OpCodes.Ret);
                        c = (CreateObject)dynMethod.CreateDelegate(typeof(CreateObject));
                        _cacheCtor.Add(type, c);
                    }

                    return(c());
                }
            }
            catch (Exception)
            {
                throw LiteException.InvalidCtor(type);
            }
        }
Example #16
0
        /// <summary>
        /// Try execute some action while has lock exception
        /// </summary>
        public static void TryExec(Action action, TimeSpan timeout)
        {
            var timer = DateTime.UtcNow.Add(timeout);

            do
            {
                try
                {
                    action();
                    return;
                }
                catch (IOException ex)
                {
                    ex.WaitIfLocked(25);
                }
            }while (DateTime.UtcNow < timer);

            throw LiteException.LockTimeout(timeout);
        }
Example #17
0
        private static PropertyInfo SelectProperty(IEnumerable <PropertyInfo> props, params Func <PropertyInfo, bool>[] predicates)
        {
            foreach (var predicate in predicates)
            {
                var prop = props.FirstOrDefault(predicate);

                if (prop != null)
                {
                    if (!prop.CanRead || !prop.CanWrite)
                    {
                        throw LiteException.PropertyReadWrite(prop);
                    }

                    return(prop);
                }
            }

            return(null);
        }
Example #18
0
        /// <summary>
        /// Add a new collection. Check if name the not exists
        /// </summary>
        public CollectionPage Add(string name)
        {
            if (string.IsNullOrEmpty(name))
            {
                throw new ArgumentNullException(nameof(name));
            }
            if (!CollectionPage.NamePattern.IsMatch(name))
            {
                throw LiteException.InvalidFormat(name);
            }

            _log.Write(Logger.COMMAND, "creating new collection '{0}'", name);

            // get header marked as dirty because I will use header after (and NewPage can get another header instance)
            var header = _pager.GetPage <HeaderPage>(0);

            // check limit count (8 bytes per collection = 4 to string length, 4 for uint pageID)
            if (header.CollectionPages.Sum(x => x.Key.Length + 8) + name.Length + 8 >= CollectionPage.MAX_COLLECTIONS_SIZE)
            {
                throw LiteException.CollectionLimitExceeded(CollectionPage.MAX_COLLECTIONS_SIZE);
            }

            // get new collection page (marked as dirty)
            var col = _pager.NewPage <CollectionPage>();

            // add this page to header page collection
            header.CollectionPages.Add(name, col.PageID);

            col.CollectionName = name;

            // set header page as dirty
            _pager.SetDirty(header);

            // create PK index
            var pk = _indexer.CreateIndex(col);

            pk.Field      = "_id";
            pk.Expression = "$._id";
            pk.Unique     = true;

            return(col);
        }
Example #19
0
        /// <summary>
        /// Override read page decrypting data from disk
        /// </summary>
        public override byte[] ReadPage(uint pageID)
        {
            var buffer = base.ReadPage(pageID);

            // when read header, checks passoword
            if (pageID == 0)
            {
                // I know, header page will be double read (it's the price for isolated concerns)
                var header = (HeaderPage)BasePage.ReadPage(buffer);

                if (header.DbParams.Password.BinaryCompareTo(_password) != 0)
                {
                    throw LiteException.DatabaseWrongPassword();
                }

                return(buffer);
            }

            return(_crypto.Decrypt(buffer));
        }
Example #20
0
        /// <summary>
        /// Lock datafile agains other process read/write
        /// </summary>
        public void Lock()
        {
#if NETFULL
            this.TryExec(() =>
            {
                _lockLength = _stream.Length;
                _log.Write(Logger.DISK, "lock datafile");
                _stream.Lock(0, _lockLength);
            });
#elif NETCORE
            bool locked = _lockSemaphore.Wait(_timeout);
            if (!locked)
            {
                LiteException.LockTimeout(_timeout);
            }

            _lockLength = _stream.Length;
            _log.Write(Logger.DISK, "lock datafile");
#endif
        }
Example #21
0
        public IBsonDataReader Execute()
        {
            var first = _tokenizer.ReadToken().Expect(TokenType.Word);

            LOG($"executing `{first.Value.ToUpper()}`", "SQL");

            switch (first.Value.ToUpper())
            {
            case "SELECT": return(this.ParseSelect(false));

            case "EXPLAIN":
                _tokenizer.ReadToken().Expect("SELECT");
                return(this.ParseSelect(true));

            case "INSERT": return(this.ParseInsert());

            case "DELETE": return(this.ParseDelete());

            case "UPDATE": return(this.ParseUpadate());

            case "DROP": return(this.ParseDrop());

            case "RENAME": return(this.ParseRename());

            case "CREATE": return(this.ParseCreate());

            case "ANALYZE": return(this.ParseAnalyze());

            case "CHECKPOINT": return(this.ParseCheckpoint());

            case "VACCUM": return(this.ParseVaccum());

            case "BEGIN": return(this.ParseBegin());

            case "ROLLBACK": return(this.ParseRollback());

            case "COMMIT": return(this.ParseCommit());

            default:  throw LiteException.UnexpectedToken(first);
            }
        }
Example #22
0
        /// <summary>
        ///     Create a new permanent index in all documents inside this collections if index not exists already. Returns true if
        ///     index was created or false if already exits
        /// </summary>
        /// <param name="field">Document field name (case sensitive)</param>
        /// <param name="options">All index options</param>
        public bool EnsureIndex(string field, IndexOptions options)
        {
            if (string.IsNullOrEmpty(field))
            {
                throw new ArgumentNullException("field");
            }
            if (options == null)
            {
                throw new ArgumentNullException("options");
            }
            if (field == "_id")
            {
                return(false);                // always exists
            }
            if (!CollectionIndex.IndexPattern.IsMatch(field))
            {
                throw LiteException.InvalidFormat("IndexField", field);
            }

            return(_engine.EnsureIndex(Name, field, options));
        }
Example #23
0
        /// <summary>
        /// Read a page with correct instance page object. Checks for pageType
        /// </summary>
        public static BasePage ReadPage(byte[] buffer)
        {
            var reader = new ByteReader(buffer);

            var pageID   = reader.ReadUInt32();
            var pageType = (PageType)reader.ReadByte();

            if (pageID == 0 && (byte)pageType > 5)
            {
                throw LiteException.InvalidDatabase();
            }

            var page = CreateInstance(pageID, pageType);

            page.ReadHeader(reader);
            page.ReadContent(reader);

            page.DiskData = buffer;

            return(page);
        }
        /// <summary>
        ///     Get a bson string field based on class PropertyInfo using BsonMapper class
        ///     Support Name1.Name2 dotted notation
        /// </summary>
        private string GetBsonField(string property)
        {
            var            parts   = property.Split('.');
            var            fields  = new string[parts.Length];
            var            type    = _type;
            var            isdbref = false;
            PropertyMapper prop;

            // loop "first.second.last"
            for (var i = 0; i < parts.Length; i++)
            {
                var map  = _mapper.GetPropertyMapper(type);
                var part = parts[i];

                if (map.TryGetValue(part, out prop))
                {
                    type = prop.PropertyType;

                    fields[i] = prop.FieldName;

                    if (prop.FieldName == "_id" && isdbref)
                    {
                        isdbref   = false;
                        fields[i] = "$id";
                    }

                    // if this property is DbRef, so if next property is _id, change to $id
                    if (prop.IsDbRef)
                    {
                        isdbref = true;
                    }
                }
                else
                {
                    throw LiteException.PropertyNotMapped(property);
                }
            }

            return(string.Join(".", fields));
        }
Example #25
0
        /// <summary>
        /// SHRINK
        /// </summary>
        private BsonDataReader ParseRebuild()
        {
            _tokenizer.ReadToken().Expect("REBUILD");

            var options = new RebuildOptions();

            // read <eol> or ;
            var next = _tokenizer.LookAhead();

            if (next.Type == TokenType.EOF || next.Type == TokenType.SemiColon)
            {
                options = null;

                _tokenizer.ReadToken();
            }
            else
            {
                var reader = new JsonReader(_tokenizer);
                var json   = reader.Deserialize();

                if (json.IsDocument == false)
                {
                    throw LiteException.UnexpectedToken(next);
                }

                if (json["password"].IsString)
                {
                    options.Password = json["password"];
                }

                if (json["collation"].IsString)
                {
                    options.Collation = new Collation(json["collation"].AsString);
                }
            }

            var diff = _engine.Rebuild(options);

            return(new BsonDataReader((int)diff));
        }
        /// <summary>
        /// Based on an expression, returns document field mapped from class Property.
        /// Support multi level dotted notation: x => x.Customer.Name
        /// Prefix is used on array expression like: x => x.Customers.Any(z => z.Name == "John") (prefix = "Customers."
        /// </summary>
        public string GetField(Expression expr, string prefix = "")
        {
            var property = prefix + expr.GetPath();
            var parts    = property.Split('.');
            var fields   = new string[parts.Length];
            var type     = _type;
            var isdbref  = false;

            // loop "first.second.last"
            for (var i = 0; i < parts.Length; i++)
            {
                var entity = _mapper.GetEntityMapper(type);
                var part   = parts[i];
                var prop   = entity.Members.Find(x => x.MemberName == part);

                if (prop == null)
                {
                    throw LiteException.PropertyNotMapped(property);
                }

                // if property is an IEnumerable, gets underlying type (otherwise, gets PropertyType)
                type = prop.UnderlyingType;

                fields[i] = prop.FieldName;

                if (prop.FieldName == "_id" && isdbref)
                {
                    isdbref   = false;
                    fields[i] = "$id";
                }

                // if this property is DbRef, so if next property is _id, change to $id
                if (prop.IsDbRef)
                {
                    isdbref = true;
                }
            }

            return(string.Join(".", fields));
        }
Example #27
0
        /// <summary>
        /// Enter in Exclusive lock mode
        /// </summary>
        public LockControl Write()
        {
            // if already in exclusive, do nothing
            if (_thread.IsWriteLockHeld)
            {
                return(new LockControl(() => { }));
            }

            // let's test if is not in read lock
            if (_thread.IsReadLockHeld)
            {
                throw new NotSupportedException("Not support Write lock inside a Read lock");
            }

            // try enter in write mode (thread)
            if (!_thread.TryEnterWriteLock(_timeout))
            {
                throw LiteException.LockTimeout(_timeout);
            }

            _log.Write(Logger.LOCK, "entered in write lock mode in thread #{0}", Thread.CurrentThread.ManagedThreadId);

            // try enter in exclusive mode in disk
            var position = _disk.Lock(LockState.Write, _timeout);

            // call avoid dirty only if not came from a shared mode
            this.DetectDatabaseChanges();

            return(new LockControl(() =>
            {
                // release disk write
                _disk.Unlock(LockState.Write, position);

                // release thread write
                _thread.ExitWriteLock();

                _log.Write(Logger.LOCK, "exited write lock mode in thread #{0}", Thread.CurrentThread.ManagedThreadId);
            }));
        }
Example #28
0
        protected override void ReadContent(ByteReader reader)
        {
            var info = reader.ReadString(HEADER_INFO.Length);
            var ver = reader.ReadByte();

            if (info != HEADER_INFO) throw LiteException.InvalidDatabase();
            if (ver != FILE_VERSION) throw LiteException.InvalidDatabaseVersion(ver);

            this.ChangeID = reader.ReadUInt16();
            this.FreeEmptyPageID = reader.ReadUInt32();
            this.LastPageID = reader.ReadUInt32();
            this.UserVersion = reader.ReadUInt16();
            this.Password = reader.ReadBytes(this.Password.Length);
            this.Salt = reader.ReadBytes(this.Salt.Length);

            // read page collections references (position on end of page)
            var cols = reader.ReadByte();
            for (var i = 0; i < cols; i++)
            {
                this.CollectionPages.Add(reader.ReadString(), reader.ReadUInt32());
            }
        }
Example #29
0
        /// <summary>
        /// Try run an operation over datafile - keep tring if locked
        /// </summary>
        private void TryExec(Action action)
        {
            var timer  = DateTime.UtcNow.Add(_options.Timeout);
            var locked = false;

            while (DateTime.UtcNow < timer)
            {
                try
                {
                    action();
                    return;
                }
                catch (UnauthorizedAccessException)
                {
                    // this exception can occurs when a single instance release file but Windows Filesystem are not fully released.
                    if (locked == false)
                    {
                        locked = true;
                        _log.Write(Logger.ERROR, "unauthorized file access, waiting for {0} timeout", _options.Timeout);
                    }

                    IOExceptionExtensions.WaitFor(250);
                }
                catch (IOException ex)
                {
                    if (locked == false)
                    {
                        locked = true;
                        _log.Write(Logger.ERROR, "locked file, waiting for {0} timeout", _options.Timeout);
                    }

                    ex.WaitIfLocked(250);
                }
            }

            _log.Write(Logger.ERROR, "timeout disk access after {0}", _options.Timeout);

            throw LiteException.LockTimeout(_options.Timeout);
        }
Example #30
0
        public override void ReadContent(BinaryReader reader)
        {
            var info = reader.ReadString();

            if (info != HEADER_INFO)
            {
                throw LiteException.InvalidDatabase(reader.BaseStream);
            }

            var ver = reader.ReadByte();

            if (ver != FILE_VERSION)
            {
                throw LiteException.InvalidDatabaseVersion(reader.BaseStream, ver);
            }

            this.ChangeID              = reader.ReadUInt16();
            this.FreeEmptyPageID       = reader.ReadUInt32();
            this.FirstCollectionPageID = reader.ReadUInt32();
            this.LastPageID            = reader.ReadUInt32();
            this.UserVersion           = reader.ReadInt32();
        }