/*- * See the file LICENSE for redistribution information. * * Copyright (c) 2009 Oracle. All rights reserved. * */ using System; using System.Collections.Generic; using System.Runtime.InteropServices; using System.Text; using BerkeleyDB.Internal; namespace BerkeleyDB { /// /// A class representing a BTreeDatabase. The Btree format is a /// representation of a sorted, balanced tree structure. /// public class BTreeDatabase : Database { private BTreeCompressDelegate compressHandler; private BTreeDecompressDelegate decompressHandler; private EntryComparisonDelegate compareHandler, prefixCompareHandler; private EntryComparisonDelegate dupCompareHandler; private BDB_CompareDelegate doCompareRef; private BDB_CompareDelegate doPrefixCompareRef; private BDB_CompareDelegate doDupCompareRef; private BDB_CompressDelegate doCompressRef; private BDB_DecompressDelegate doDecompressRef; #region Constructors private BTreeDatabase(DatabaseEnvironment env, uint flags) : base(env, flags) { } internal BTreeDatabase(BaseDatabase clone) : base(clone) { } private void Config(BTreeDatabaseConfig cfg) { base.Config(cfg); /* * Database.Config calls set_flags, but that doesn't get the BTree * specific flags. No harm in calling it again. */ db.set_flags(cfg.flags); if (cfg.BTreeCompare != null) Compare = cfg.BTreeCompare; if (cfg.BTreePrefixCompare != null) PrefixCompare = cfg.BTreePrefixCompare; // The duplicate comparison function cannot change. if (cfg.DuplicateCompare != null) DupCompare = cfg.DuplicateCompare; if (cfg.minkeysIsSet) db.set_bt_minkey(cfg.MinKeysPerPage); if (cfg.compressionIsSet) { Compress = cfg.Compress; Decompress = cfg.Decompress; if (Compress == null) doCompressRef = null; else doCompressRef = new BDB_CompressDelegate(doCompress); if (Decompress == null) doDecompressRef = null; else doDecompressRef = new BDB_DecompressDelegate(doDecompress); db.set_bt_compress(doCompressRef, doDecompressRef); } } /// /// Instantiate a new BTreeDatabase object and open the database /// represented by . /// /// /// /// If is null, the database is strictly /// temporary and cannot be opened by any other thread of control, thus /// the database can only be accessed by sharing the single database /// object that created it, in circumstances where doing so is safe. /// /// /// If is set, the operation /// will be implicitly transaction protected. Note that transactionally /// protected operations on a datbase object requires the object itself /// be transactionally protected during its open. /// /// /// /// The name of an underlying file that will be used to back the /// database. In-memory databases never intended to be preserved on disk /// may be created by setting this parameter to null. /// /// The database's configuration /// A new, open database object public static BTreeDatabase Open( string Filename, BTreeDatabaseConfig cfg) { return Open(Filename, null, cfg, null); } /// /// Instantiate a new BTreeDatabase object and open the database /// represented by and /// . /// /// /// /// If both and /// are null, the database is strictly /// temporary and cannot be opened by any other thread of control, thus /// the database can only be accessed by sharing the single database /// object that created it, in circumstances where doing so is safe. If /// is null and /// is non-null, the database can be /// opened by other threads of control and will be replicated to client /// sites in any replication group. /// /// /// If is set, the operation /// will be implicitly transaction protected. Note that transactionally /// protected operations on a datbase object requires the object itself /// be transactionally protected during its open. /// /// /// /// The name of an underlying file that will be used to back the /// database. In-memory databases never intended to be preserved on disk /// may be created by setting this parameter to null. /// /// /// This parameter allows applications to have multiple databases in a /// single file. Although no DatabaseName needs to be specified, it is /// an error to attempt to open a second database in a file that was not /// initially created using a database name. /// /// The database's configuration /// A new, open database object public static BTreeDatabase Open( string Filename, string DatabaseName, BTreeDatabaseConfig cfg) { return Open(Filename, DatabaseName, cfg, null); } /// /// Instantiate a new BTreeDatabase object and open the database /// represented by . /// /// /// /// If is null, the database is strictly /// temporary and cannot be opened by any other thread of control, thus /// the database can only be accessed by sharing the single database /// object that created it, in circumstances where doing so is safe. /// /// /// If is null, but /// is set, the operation will /// be implicitly transaction protected. Note that transactionally /// protected operations on a datbase object requires the object itself /// be transactionally protected during its open. Also note that the /// transaction must be committed before the object is closed. /// /// /// /// The name of an underlying file that will be used to back the /// database. In-memory databases never intended to be preserved on disk /// may be created by setting this parameter to null. /// /// The database's configuration /// /// If the operation is part of an application-specified transaction, /// is a Transaction object returned from /// ; if /// the operation is part of a Berkeley DB Concurrent Data Store group, /// is a handle returned from /// ; otherwise null. /// /// A new, open database object public static BTreeDatabase Open( string Filename, BTreeDatabaseConfig cfg, Transaction txn) { return Open(Filename, null, cfg, txn); } /// /// Instantiate a new BTreeDatabase object and open the database /// represented by and /// . /// /// /// /// If both and /// are null, the database is strictly /// temporary and cannot be opened by any other thread of control, thus /// the database can only be accessed by sharing the single database /// object that created it, in circumstances where doing so is safe. If /// is null and /// is non-null, the database can be /// opened by other threads of control and will be replicated to client /// sites in any replication group. /// /// /// If is null, but /// is set, the operation will /// be implicitly transaction protected. Note that transactionally /// protected operations on a datbase object requires the object itself /// be transactionally protected during its open. Also note that the /// transaction must be committed before the object is closed. /// /// /// /// The name of an underlying file that will be used to back the /// database. In-memory databases never intended to be preserved on disk /// may be created by setting this parameter to null. /// /// /// This parameter allows applications to have multiple databases in a /// single file. Although no DatabaseName needs to be specified, it is /// an error to attempt to open a second database in a file that was not /// initially created using a database name. /// /// The database's configuration /// /// If the operation is part of an application-specified transaction, /// is a Transaction object returned from /// ; if /// the operation is part of a Berkeley DB Concurrent Data Store group, /// is a handle returned from /// ; otherwise null. /// /// A new, open database object public static BTreeDatabase Open(string Filename, string DatabaseName, BTreeDatabaseConfig cfg, Transaction txn) { BTreeDatabase ret = new BTreeDatabase(cfg.Env, 0); ret.Config(cfg); ret.db.open(Transaction.getDB_TXN(txn), Filename, DatabaseName, DBTYPE.DB_BTREE, cfg.openFlags, 0); ret.isOpen = true; return ret; } #endregion Constructors #region Callbacks private static int doCompare(IntPtr dbp, IntPtr dbtp1, IntPtr dbtp2) { DB db = new DB(dbp, false); DBT dbt1 = new DBT(dbtp1, false); DBT dbt2 = new DBT(dbtp2, false); BTreeDatabase btdb = (BTreeDatabase)(db.api_internal); return btdb.Compare( DatabaseEntry.fromDBT(dbt1), DatabaseEntry.fromDBT(dbt2)); } private static int doCompress(IntPtr dbp, IntPtr prevKeyp, IntPtr prevDatap, IntPtr keyp, IntPtr datap, IntPtr destp) { DB db = new DB(dbp, false); DatabaseEntry prevKey = DatabaseEntry.fromDBT(new DBT(prevKeyp, false)); DatabaseEntry prevData = DatabaseEntry.fromDBT(new DBT(prevDatap, false)); DatabaseEntry key = DatabaseEntry.fromDBT(new DBT(keyp, false)); DatabaseEntry data = DatabaseEntry.fromDBT(new DBT(datap, false)); DBT dest = new DBT(destp, false); BTreeDatabase btdb = (BTreeDatabase)(db.api_internal); byte[] arr = new byte[(int)dest.ulen]; int len; try { if (btdb.Compress(prevKey, prevData, key, data, ref arr, out len)) { Marshal.Copy(arr, 0, dest.dataPtr, len); dest.size = (uint)len; return 0; } else { return DbConstants.DB_BUFFER_SMALL; } } catch (Exception) { return -1; } } private static int doDecompress(IntPtr dbp, IntPtr prevKeyp, IntPtr prevDatap, IntPtr cmpp, IntPtr destKeyp, IntPtr destDatap) { DB db = new DB(dbp, false); DatabaseEntry prevKey = DatabaseEntry.fromDBT(new DBT(prevKeyp, false)); DatabaseEntry prevData = DatabaseEntry.fromDBT(new DBT(prevDatap, false)); DBT compressed = new DBT(cmpp, false); DBT destKey = new DBT(destKeyp, false); DBT destData = new DBT(destDatap, false); BTreeDatabase btdb = (BTreeDatabase)(db.api_internal); uint size; try { KeyValuePair kvp = btdb.Decompress(prevKey, prevData, compressed.data, out size); int keylen = kvp.Key.Data.Length; int datalen = kvp.Value.Data.Length; destKey.size = (uint)keylen; destData.size = (uint)datalen; if (keylen > destKey.ulen || datalen > destData.ulen) return DbConstants.DB_BUFFER_SMALL; Marshal.Copy(kvp.Key.Data, 0, destKey.dataPtr, keylen); Marshal.Copy(kvp.Value.Data, 0, destData.dataPtr, datalen); compressed.size = size; return 0; } catch (Exception) { return -1; } } private static int doDupCompare( IntPtr dbp, IntPtr dbt1p, IntPtr dbt2p) { DB db = new DB(dbp, false); DBT dbt1 = new DBT(dbt1p, false); DBT dbt2 = new DBT(dbt2p, false); BTreeDatabase btdb = (BTreeDatabase)(db.api_internal); return btdb.DupCompare( DatabaseEntry.fromDBT(dbt1), DatabaseEntry.fromDBT(dbt2)); } private static int doPrefixCompare( IntPtr dbp, IntPtr dbtp1, IntPtr dbtp2) { DB db = new DB(dbp, false); DBT dbt1 = new DBT(dbtp1, false); DBT dbt2 = new DBT(dbtp2, false); BTreeDatabase btdb = (BTreeDatabase)(db.api_internal); return btdb.PrefixCompare( DatabaseEntry.fromDBT(dbt1), DatabaseEntry.fromDBT(dbt2)); } #endregion Callbacks #region Properties // Sorted alpha by property name /// /// The Btree key comparison function. The comparison function is called /// whenever it is necessary to compare a key specified by the /// application with a key currently stored in the tree. /// public EntryComparisonDelegate Compare { get { return compareHandler; } private set { if (value == null) db.set_bt_compare(null); else if (compareHandler == null) { if (doCompareRef == null) doCompareRef = new BDB_CompareDelegate(doCompare); db.set_bt_compare(doCompareRef); } compareHandler = value; } } /// /// The compression function used to store key/data pairs in the /// database. /// public BTreeCompressDelegate Compress { get { return compressHandler; } private set { compressHandler = value; } } /// /// The decompression function used to retrieve key/data pairs from the /// database. /// public BTreeDecompressDelegate Decompress { get { return decompressHandler; } private set { decompressHandler = value; } } /// /// The duplicate data item comparison function. /// public EntryComparisonDelegate DupCompare { get { return dupCompareHandler; } private set { /* Cannot be called after open. */ if (value == null) db.set_dup_compare(null); else if (dupCompareHandler == null) { if (doDupCompareRef == null) doDupCompareRef = new BDB_CompareDelegate(doDupCompare); db.set_dup_compare(doDupCompareRef); } dupCompareHandler = value; } } /// /// Whether the insertion of duplicate data items in the database is /// permitted, and whether duplicates items are sorted. /// public DuplicatesPolicy Duplicates { get { uint flags = 0; db.get_flags(ref flags); if ((flags & DbConstants.DB_DUPSORT) != 0) return DuplicatesPolicy.SORTED; else if ((flags & DbConstants.DB_DUP) != 0) return DuplicatesPolicy.UNSORTED; else return DuplicatesPolicy.NONE; } } /// /// The minimum number of key/data pairs intended to be stored on any /// single Btree leaf page. /// public uint MinKeysPerPage { get { uint ret = 0; db.get_bt_minkey(ref ret); return ret; } } /// /// The Btree prefix function. The prefix function is used to determine /// the amount by which keys stored on the Btree internal pages can be /// safely truncated without losing their uniqueness. /// public EntryComparisonDelegate PrefixCompare { get { return prefixCompareHandler; } private set { if (value == null) db.set_bt_prefix(null); else if (prefixCompareHandler == null) { if (doPrefixCompareRef == null) doPrefixCompareRef = new BDB_CompareDelegate(doPrefixCompare); db.set_bt_prefix(doPrefixCompareRef); } prefixCompareHandler = value; } } /// /// If true, this object supports retrieval from the Btree using record /// numbers. /// public bool RecordNumbers { get { uint flags = 0; db.get_flags(ref flags); return (flags & DbConstants.DB_RECNUM) != 0; } } /// /// If false, empty pages will not be coalesced into higher-level pages. /// public bool ReverseSplit { get { uint flags = 0; db.get_flags(ref flags); return (flags & DbConstants.DB_REVSPLITOFF) == 0; } } #endregion Properties #region Methods // Sorted alpha by method name /// /// Compact the database, and optionally return unused database pages to /// the underlying filesystem. /// /// /// If the operation occurs in a transactional database, the operation /// will be implicitly transaction protected using multiple /// transactions. These transactions will be periodically committed to /// avoid locking large sections of the tree. Any deadlocks encountered /// cause the compaction operation to be retried from the point of the /// last transaction commit. /// /// Compact configuration parameters /// Compact operation statistics public CompactData Compact(CompactConfig cdata) { return Compact(cdata, null); } /// /// Compact the database, and optionally return unused database pages to /// the underlying filesystem. /// /// /// /// If is non-null, then the operation is /// performed using that transaction. In this event, large sections of /// the tree may be locked during the course of the transaction. /// /// /// If is null, but the operation occurs in a /// transactional database, the operation will be implicitly transaction /// protected using multiple transactions. These transactions will be /// periodically committed to avoid locking large sections of the tree. /// Any deadlocks encountered cause the compaction operation to be /// retried from the point of the last transaction commit. /// /// /// Compact configuration parameters /// /// If the operation is part of an application-specified transaction, /// is a Transaction object returned from /// ; if /// the operation is part of a Berkeley DB Concurrent Data Store group, /// is a handle returned from /// ; otherwise null. /// /// Compact operation statistics public CompactData Compact(CompactConfig cdata, Transaction txn) { DatabaseEntry end = null; if (cdata.returnEnd) end = new DatabaseEntry(); db.compact(Transaction.getDB_TXN(txn), cdata.start, cdata.stop, CompactConfig.getDB_COMPACT(cdata), cdata.flags, end); return new CompactData(CompactConfig.getDB_COMPACT(cdata), end); } /// /// Create a database cursor. /// /// A newly created cursor public new BTreeCursor Cursor() { return Cursor(new CursorConfig(), null); } /// /// Create a database cursor with the given configuration. /// /// /// The configuration properties for the cursor. /// /// A newly created cursor public new BTreeCursor Cursor(CursorConfig cfg) { return Cursor(cfg, null); } /// /// Create a transactionally protected database cursor. /// /// /// The transaction context in which the cursor may be used. /// /// A newly created cursor public new BTreeCursor Cursor(Transaction txn) { return Cursor(new CursorConfig(), txn); } /// /// Create a transactionally protected database cursor with the given /// configuration. /// /// /// The configuration properties for the cursor. /// /// /// The transaction context in which the cursor may be used. /// /// A newly created cursor public new BTreeCursor Cursor(CursorConfig cfg, Transaction txn) { return new BTreeCursor( db.cursor(Transaction.getDB_TXN(txn), cfg.flags), Pagesize); } /// /// Return the database statistical information which does not require /// traversal of the database. /// /// /// The database statistical information which does not require /// traversal of the database. /// public BTreeStats FastStats() { return Stats(null, true, Isolation.DEGREE_THREE); } /// /// Return the database statistical information which does not require /// traversal of the database. /// /// /// If the operation is part of an application-specified transaction, /// is a Transaction object returned from /// ; if /// the operation is part of a Berkeley DB Concurrent Data Store group, /// is a handle returned from /// ; otherwise null. /// /// /// The database statistical information which does not require /// traversal of the database. /// public BTreeStats FastStats(Transaction txn) { return Stats(txn, true, Isolation.DEGREE_THREE); } /// /// Return the database statistical information which does not require /// traversal of the database. /// /// /// /// Among other things, this method makes it possible for applications /// to request key and record counts without incurring the performance /// penalty of traversing the entire database. /// /// /// The statistical information is described by the /// , , /// , and classes. /// /// /// /// If the operation is part of an application-specified transaction, /// is a Transaction object returned from /// ; if /// the operation is part of a Berkeley DB Concurrent Data Store group, /// is a handle returned from /// ; otherwise null. /// /// /// The level of isolation for database reads. /// will be silently ignored for /// databases which did not specify /// . /// /// /// The database statistical information which does not require /// traversal of the database. /// public BTreeStats FastStats(Transaction txn, Isolation isoDegree) { return Stats(txn, true, isoDegree); } /// /// Retrieve a specific numbered key/data pair from the database. /// /// /// The record number of the record to be retrieved. /// /// /// A whose Key /// parameter is and whose Value parameter is the /// retrieved data. /// public KeyValuePair Get(uint recno) { return Get(recno, null, null); } /// /// Retrieve a specific numbered key/data pair from the database. /// /// /// The record number of the record to be retrieved. /// /// /// is a Transaction object returned from /// ; if /// the operation is part of a Berkeley DB Concurrent Data Store group, /// is a handle returned from /// ; otherwise null. /// /// /// A whose Key /// parameter is and whose Value parameter is the /// retrieved data. /// public KeyValuePair Get( uint recno, Transaction txn) { return Get(recno, txn, null); } /// /// Retrieve a specific numbered key/data pair from the database. /// /// /// The record number of the record to be retrieved. /// /// /// is a Transaction object returned from /// ; if /// the operation is part of a Berkeley DB Concurrent Data Store group, /// is a handle returned from /// ; otherwise null. /// /// The locking behavior to use. /// /// A whose Key /// parameter is and whose Value parameter is the /// retrieved data. /// public KeyValuePair Get( uint recno, Transaction txn, LockingInfo info) { DatabaseEntry key = new DatabaseEntry(); key.Data = BitConverter.GetBytes(recno); return Get(key, null, txn, info, DbConstants.DB_SET_RECNO); } public KeyValuePair GetMultiple( uint recno) { return GetMultiple(recno, (int)Pagesize, null, null); } public KeyValuePair GetMultiple( uint recno, int BufferSize) { return GetMultiple(recno, BufferSize, null, null); } public KeyValuePair GetMultiple( uint recno, int BufferSize, Transaction txn) { return GetMultiple(recno, BufferSize, txn, null); } public KeyValuePair GetMultiple( uint recno, int BufferSize, Transaction txn, LockingInfo info) { KeyValuePair kvp; DatabaseEntry key = new DatabaseEntry(); key.Data = BitConverter.GetBytes(recno); DatabaseEntry data = new DatabaseEntry(); for (; ; ) { data.UserData = new byte[BufferSize]; try { kvp = Get(key, data, txn, info, DbConstants.DB_MULTIPLE | DbConstants.DB_SET_RECNO); break; } catch (MemoryException) { int sz = (int)data.size; if (sz > BufferSize) BufferSize = sz; else BufferSize *= 2; } } MultipleDatabaseEntry dbe = new MultipleDatabaseEntry(kvp.Value); return new KeyValuePair( kvp.Key, dbe); } /// /// Return an estimate of the proportion of keys that are less than, /// equal to, and greater than the specified key. /// /// The key to search for /// /// An estimate of the proportion of keys that are less than, equal to, /// and greater than the specified key. /// public KeyRange KeyRange(DatabaseEntry key) { return KeyRange(key, null); } /// /// Return an estimate of the proportion of keys that are less than, /// equal to, and greater than the specified key. /// /// The key to search for /// /// If the operation is part of an application-specified transaction, /// is a Transaction object returned from /// ; if /// the operation is part of a Berkeley DB Concurrent Data Store group, /// is a handle returned from /// ; otherwise null. /// /// /// An estimate of the proportion of keys that are less than, equal to, /// and greater than the specified key. /// public KeyRange KeyRange(DatabaseEntry key, Transaction txn) { DB_KEY_RANGE range = new DB_KEY_RANGE(); db.key_range(Transaction.getDB_TXN(txn), key, range, 0); return new KeyRange(range); } /// /// Store the key/data pair in the database only if it does not already /// appear in the database. /// /// The key to store in the database /// The data item to store in the database public void PutNoDuplicate(DatabaseEntry key, DatabaseEntry data) { PutNoDuplicate(key, data, null); } /// /// Store the key/data pair in the database only if it does not already /// appear in the database. /// /// The key to store in the database /// The data item to store in the database /// /// If the operation is part of an application-specified transaction, /// is a Transaction object returned from /// ; if /// the operation is part of a Berkeley DB Concurrent Data Store group, /// is a handle returned from /// ; otherwise null. /// public void PutNoDuplicate( DatabaseEntry key, DatabaseEntry data, Transaction txn) { Put(key, data, txn, DbConstants.DB_NODUPDATA); } /// /// Return the database statistical information for this database. /// /// Database statistical information. public BTreeStats Stats() { return Stats(null, false, Isolation.DEGREE_THREE); } /// /// Return the database statistical information for this database. /// /// /// If the operation is part of an application-specified transaction, /// is a Transaction object returned from /// ; if /// the operation is part of a Berkeley DB Concurrent Data Store group, /// is a handle returned from /// ; otherwise null. /// /// Database statistical information. public BTreeStats Stats(Transaction txn) { return Stats(txn, false, Isolation.DEGREE_THREE); } /// /// Return the database statistical information for this database. /// /// /// The statistical information is described by /// . /// /// /// If the operation is part of an application-specified transaction, /// is a Transaction object returned from /// ; if /// the operation is part of a Berkeley DB Concurrent Data Store group, /// is a handle returned from /// ; otherwise null. /// /// /// The level of isolation for database reads. /// will be silently ignored for /// databases which did not specify /// . /// /// Database statistical information. public BTreeStats Stats(Transaction txn, Isolation isoDegree) { return Stats(txn, false, isoDegree); } private BTreeStats Stats( Transaction txn, bool fast, Isolation isoDegree) { uint flags = 0; flags |= fast ? DbConstants.DB_FAST_STAT : 0; switch (isoDegree) { case Isolation.DEGREE_ONE: flags |= DbConstants.DB_READ_UNCOMMITTED; break; case Isolation.DEGREE_TWO: flags |= DbConstants.DB_READ_COMMITTED; break; } BTreeStatStruct st = db.stat_bt(Transaction.getDB_TXN(txn), flags); return new BTreeStats(st); } /// /// Return pages to the filesystem that are already free and at the end /// of the file. /// /// /// The number of database pages returned to the filesystem /// public uint TruncateUnusedPages() { return TruncateUnusedPages(null); } /// /// Return pages to the filesystem that are already free and at the end /// of the file. /// /// /// If the operation is part of an application-specified transaction, /// is a Transaction object returned from /// ; if /// the operation is part of a Berkeley DB Concurrent Data Store group, /// is a handle returned from /// ; otherwise null. /// /// /// The number of database pages returned to the filesystem /// public uint TruncateUnusedPages(Transaction txn) { DB_COMPACT cdata = new DB_COMPACT(); db.compact(Transaction.getDB_TXN(txn), null, null, cdata, DbConstants.DB_FREELIST_ONLY, null); return cdata.compact_pages_truncated; } #endregion Methods } }