private void Cursor10000Test() { //create database Hamster.Environment env = new Hamster.Environment(); env.Create("ntest.db"); Parameter[] param = new Parameter[1]; param[0] = new Parameter(); param[0].name = HamConst.HAM_PARAM_KEY_TYPE; param[0].value = HamConst.HAM_TYPE_UINT64; Database db = env.CreateDatabase(1, 0, param); //insert records for (ulong i = 0; i < 10000; i++) { byte[] key = BitConverter.GetBytes(i); byte[] record = new byte[20]; db.Insert(key, record); } //close database db.Close(); //reopen again db = env.OpenDatabase(1); Cursor cursor = new Cursor(db); cursor.MoveFirst(); ulong firstKey = BitConverter.ToUInt64(cursor.GetKey(), 0); Assert.AreEqual((ulong)0, firstKey); cursor.MoveLast(); ulong lastKey = BitConverter.ToUInt64(cursor.GetKey(), 0); Assert.AreEqual((ulong)9999, lastKey); //close database cursor.Close(); db.Close(); env.Close(); }
private void CreateStringIntIntParameterNeg() { Hamster.Environment env = new Hamster.Environment(); Database db = new Database(); Parameter[] param = new Parameter[1]; param[0] = new Parameter(); param[0].name = HamConst.HAM_PARAM_CACHESIZE; param[0].value = 1024; try { env.Create("ntest.db", HamConst.HAM_IN_MEMORY, 0644, param); env.Close(); } catch (DatabaseException e) { Assert.AreEqual(HamConst.HAM_INV_PARAMETER, e.ErrorCode); } }
private void CreateWithParameters() { using (Hamster.Environment env = new Hamster.Environment()) { env.Create("ntest.db"); Parameter[] param = new Parameter[] { new Parameter { name = HamConst.HAM_PARAM_KEYSIZE, value = 32 } }; using (Database db = env.CreateDatabase(13, 0, param)) { } } }
private static Parameter[] AppendNullParameter(Parameter[] parameters) { Parameter[] newArray = new Parameter[parameters.GetLength(0)+1]; for (int i = 0; i < parameters.GetLength(0); i++) newArray[i] = parameters[i]; return newArray; }
/// <summary> /// Opens an existing Environment /// </summary> /// <remarks> /// This method wraps the native ham_env_open function. /// </remarks> /// /// <param name="fileName">The file name of the Environment file.</param> /// <param name="flags">Optional flags for this operation, combined /// with bitwise OR. Possible flags are: /// <list type="bullet"> /// <item><see cref="HamConst.HAM_READ_ONLY" /> /// Opens the file for reading only. Operations which need /// write access (i.e. Database.Insert) /// will return <see cref="HamConst.HAM_WRITE_PROTECTED" />. /// </item><br /> /// <item><see cref="HamConst.HAM_ENABLE_FSYNC" /> /// Immediately write modified pages to the disk. This /// slows down all Database operations, but may save the /// Database integrity in case of a system crash.</item><br /> /// <item><see cref="HamConst.HAM_DISABLE_MMAP" /> /// Do not use memory mapped files for I/O. By default, /// hamsterdb checks if it can use mmap, since mmap is faster /// than read/write. For performance reasons, this flag should /// not be used.</item><br /> /// <item><see cref="HamConst.HAM_ENABLE_RECOVERY" /> /// Enables logging/recovery for this Database. Will return /// <see cref="HamConst.HAM_NEED_RECOVERY" />, if the /// Database is in an inconsistent state. Not allowed in /// combination with <see cref="HamConst.HAM_IN_MEMORY" />, /// <see cref="HamConst.HAM_DISABLE_FREELIST_FLUSH" /> and /// <see cref="HamConst.HAM_ENABLE_FSYNC" />.</item><br /> /// <item><see cref="HamConst.HAM_AUTO_RECOVERY" /> /// Automatically recover the Database, if necessary. This /// flag imples <see cref="HamConst.HAM_ENABLE_RECOVERY" />. /// </item><br /> /// <item><see cref="HamConst.HAM_ENABLE_TRANSACTIONS" /> /// Enables Transactions for this Database. This flag implies /// <see cref="HamConst.HAM_ENABLE_RECOVERY" />.</item><br /> /// </list> /// </param> /// <param name="parameters">An array of <see cref="Parameter" /> /// structures. The following parameters are available:<br /> /// <list type="bullet"> /// <item><see cref="HamConst.HAM_PARAM_CACHESIZE" /> /// The size of the Database cache, in bytes. The default size /// is defined in <i>src/config.h</i> as HAM_DEFAULT_CACHESIZE /// - usually 2 MB.</item><br /> /// </list> /// </param> /// <exception cref="DatabaseException"> /// <list type="bullet"> /// <item><see cref="HamConst.HAM_INV_PARAMETER"/> /// if an invalid combination of flags was specified</item> /// <item><see cref="HamConst.HAM_FILE_NOT_FOUND"/> /// if the file does not exist</item> /// <item><see cref="HamConst.HAM_IO_ERROR"/> /// if the file could not be opened or reading/writing failed</item> /// <item><see cref="HamConst.HAM_INV_FILE_VERSION"/> /// if the Database version is not compatible with the library /// version</item> /// <item><see cref="HamConst.HAM_OUT_OF_MEMORY"/> /// if memory could not be allocated</item> /// <item><see cref="HamConst.HAM_WOULD_BLOCK"/> /// if another process has locked the file</item> /// </list> /// </exception> public void Open(String fileName, int flags, Parameter[] parameters) { int st; handle = new IntPtr(0); if (parameters != null) parameters = AppendNullParameter(parameters); lock (this) { st = NativeMethods.EnvOpen(out handle, fileName, flags, parameters); } if (st != 0) throw new DatabaseException(st); }
/// <summary> /// Opens a Database in this Environment /// </summary> /// <remarks> /// This method wraps the native ham_env_open_db function. /// </remarks> /// <param name="name">The name of the Database. If a Database /// with this name does not exist, the function will throw /// <see cref="HamConst.HAM_DATABASE_NOT_FOUND"/>.</param> /// <param name="flags">Optional flags for this operation, combined /// with bitwise OR. Possible flags are: /// <list type="bullet"> /// <item><see cref="HamConst.HAM_DISABLE_VAR_KEYLEN" /> /// Do not allow the use of variable length keys. Inserting /// a key, which is larger than the B+Tree index key size, /// returns <see cref="HamConst.HAM_INV_KEYSIZE" />.</item> /// <item><see cref="HamConst.HAM_SORT_DUPLICATES" /> /// Sort duplicate keys for this Database. Only allowed in /// combination with HAM_ENABLE_DUPLICATES. A compare function /// can be set with <see cref="Database.SetDuplicateCompareFunc"/>. /// This flag is not persistent.</item><br /> /// </list> /// </param> /// <param name="parameters">An array of <see cref="Parameter" /> /// structures. The following parameters are available:<br /> /// <list type="bullet"> /// <item><see cref="HamConst.HAM_PARAM_DATA_ACCESS_MODE" /> /// Gives a hint regarding data access patterns. The default /// setting optimizes hamsterdb for random read/write access /// (<see cref="HamConst.HAM_DAM_RANDOM_WRITE"/>). /// Use <see cref="HamConst.HAM_DAM_SEQUENTIAL_INSERT"/> for /// sequential inserts (this is automatically set for /// record number Databases). This flag is not persistent.</item> /// </list> /// </param> /// <exception cref="DatabaseException"> /// <list type="bullet"> /// <item><see cref="HamConst.HAM_INV_PARAMETER"/> /// if an invalid combination of flags was specified</item> /// <item><see cref="HamConst.HAM_DATABASE_NOT_FOUND"/> /// if a Database with this name does not exist</item> /// <item><see cref="HamConst.HAM_DATABASE_ALREADY_OPEN"/> /// if this Database was already opened</item> /// <item><see cref="HamConst.HAM_OUT_OF_MEMORY"/> /// if memory could not be allocated</item> /// <item><see cref="HamConst.HAM_WOULD_BLOCK"/> /// if another process has locked the file</item> /// </list> /// </exception> /// <returns>The new Database object</returns> public Database OpenDatabase(short name, int flags, Parameter[] parameters) { int st; IntPtr dbh; if (parameters != null) parameters = AppendNullParameter(parameters); lock (this) { st = NativeMethods.NewDatabaseHandle(out dbh); if (st != 0) throw new DatabaseException(st); st = NativeMethods.EnvOpenDatabase(handle, dbh, name, flags, parameters); } if (st != 0) throw new DatabaseException(st); return new Database(dbh); }
public void CreateStringIntIntParameterNeg() { Hamster.Environment env = new Hamster.Environment(); Parameter[] param = new Parameter[1]; param[0] = new Parameter(); param[0].name = HamConst.HAM_PARAM_PAGESIZE; param[0].value = 777; try { env.Create("ntest.db", 0, 0644, param); env.Close(); } catch (DatabaseException e) { Assert.AreEqual(HamConst.HAM_INV_PAGESIZE, e.ErrorCode); } }
public static extern int EnvCreate(out IntPtr handle, String fileName, int flags, int mode, Parameter[] parameters);
public static extern int EnvOpen(out IntPtr handle, String fileName, int flags, Parameter[] parameters);
private Database CreateDatabase(string file) { List<Parameter> list = new List<Parameter>(); Parameter param1 = new Parameter(); param1.name = HamConst.HAM_PARAM_CACHESIZE; param1.value = 768 * 1024 * 1024; list.Add(param1); Parameter param2 = new Parameter(); param2.name = HamConst.HAM_PARAM_KEYSIZE; param2.value = 8; // sizeof(ulong); list.Add(param2); Database db = new Database(); db.Create(file, 0, 0, list.ToArray()); db.SetCompareFunc(new CompareFunc(NumericalCompareFunc)); return db; }
public void CreateInvalidParameter() { Database db = new Database(); Parameter[] param = new Parameter[3]; param[1] = new Parameter(); param[2] = new Parameter(); try { db.Create("ntest.db", 0, 0, param); db.Close(); } catch (DatabaseException e) { Assert.AreEqual(HamConst.HAM_INV_PARAMETER, e.ErrorCode); } }
public void CreateStringIntIntParameterNeg() { Database db = new Database(); Parameter[] param = new Parameter[1]; param[0] = new Parameter(); param[0].name = HamConst.HAM_PARAM_CACHESIZE; param[0].value = 1024; try { db.Create("ntest.db", HamConst.HAM_IN_MEMORY_DB, 0644, param); db.Close(); } catch (DatabaseException e) { Assert.AreEqual(HamConst.HAM_INV_PARAMETER, e.ErrorCode); } }
public void CreateStringIntIntParameter() { Database db = new Database(); Parameter[] param = new Parameter[1]; param[0] = new Parameter(); param[0].name = HamConst.HAM_PARAM_CACHESIZE; param[0].value = 1024; try { db.Create("ntest.db", 0, 0644, param); db.Close(); } catch (DatabaseException e) { Assert.Fail("Unexpected exception " + e); } }
/// <summary> /// Opens a Database in this Environment /// </summary> /// <remarks> /// This method wraps the native ham_env_open_db function. /// </remarks> /// <param name="name">The name of the Database. If a Database /// with this name does not exist, the function will throw /// <see cref="HamConst.HAM_DATABASE_NOT_FOUND"/>.</param> /// <param name="flags">Optional flags for this operation, combined /// with bitwise OR. Possible flags are: /// <list type="bullet"> /// <item><see cref="HamConst.HAM_READ_ONLY" /> /// Opens the database for reading.</item> /// </list> /// </param> /// <param name="parameters">An array of <see cref="Parameter" /> /// structures. The following parameters are available:<br /> /// <list type="bullet"> /// </list> /// </param> /// <exception cref="DatabaseException"> /// <list type="bullet"> /// <item><see cref="HamConst.HAM_INV_PARAMETER"/> /// if an invalid combination of flags was specified</item> /// <item><see cref="HamConst.HAM_DATABASE_NOT_FOUND"/> /// if a Database with this name does not exist</item> /// <item><see cref="HamConst.HAM_DATABASE_ALREADY_OPEN"/> /// if this Database was already opened</item> /// <item><see cref="HamConst.HAM_OUT_OF_MEMORY"/> /// if memory could not be allocated</item> /// <item><see cref="HamConst.HAM_WOULD_BLOCK"/> /// if another process has locked the file</item> /// </list> /// </exception> /// <returns>The new Database object</returns> public Database OpenDatabase(short name, int flags, Parameter[] parameters) { int st; IntPtr dbh = new IntPtr(0); if (parameters != null) parameters = AppendNullParameter(parameters); lock (this) { st = NativeMethods.EnvOpenDatabase(handle, out dbh, name, flags, parameters); } if (st != 0) throw new DatabaseException(st); Database db = new Database(dbh); databases.Add(db); return db; }
private Database OpenDatabase(string file) { List<Parameter> list = new List<Parameter>(); Parameter param1 = new Parameter(); param1.name = HamConst.HAM_PARAM_CACHESIZE; param1.value = 768 * 1024 * 1024; list.Add(param1); Hamster.Environment env = new Hamster.Environment(); Database db = new Database(); env.Open(file, 0, list.ToArray()); db = env.OpenDatabase(1); db.SetCompareFunc(new CompareFunc(NumericalCompareFunc)); return db; }
public static extern int EnvOpenDatabase(IntPtr handle, out IntPtr dbhandle, short name, int flags, Parameter[] parameters);
private void SetComparator() { Hamster.Environment env = new Hamster.Environment(); Database db = new Database(); byte[] k = new byte[5]; byte[] r = new byte[5]; Parameter[] param = new Parameter[1]; param[0] = new Parameter(); param[0].name = HamConst.HAM_PARAM_KEY_TYPE; param[0].value = HamConst.HAM_TYPE_CUSTOM; compareCounter = 0; try { env.Create("ntest.db"); db = env.CreateDatabase(1, 0, param); db.SetCompareFunc(new CompareFunc(MyCompareFunc)); db.Insert(k, r); k[0] = 1; db.Insert(k, r); db.Close(); env.Close(); } catch (DatabaseException e) { Assert.Fail("unexpected exception " + e); } Assert.AreEqual(1, compareCounter); }
private static Parameter GetCacheParam(long cacheSize) { Parameter param = new Parameter(); param.name = HamConst.HAM_PARAM_CACHESIZE; param.value = cacheSize; return param; }
public void CreateStringIntIntParameter() { Hamster.Environment env = new Hamster.Environment(); Parameter[] param = new Parameter[1]; param[0] = new Parameter(); param[0].name = HamConst.HAM_PARAM_MAX_ENV_DATABASES; param[0].value = 10; try { env.Create("ntest.db", 0, 0644, param); env.Close(); } catch (DatabaseException e) { Assert.Fail("Unexpected exception " + e); } }
private static Parameter GetKeySizeParam(long keySize) { Parameter param = new Parameter(); param.name = HamConst.HAM_PARAM_KEYSIZE; param.value = keySize; return param; }
public void OpenStringIntIntParameter() { Hamster.Environment env = new Hamster.Environment(); Parameter[] param = new Parameter[1]; param[0] = new Parameter(); param[0].name = HamConst.HAM_PARAM_CACHESIZE; param[0].value = 1024; try { env.Create("ntest.db", 0, 0644, param); env.Close(); env.Open("ntest.db", 0, param); env.Close(); } catch (DatabaseException e) { Assert.Fail("unexpected exception " + e); } }
private void CreateInvalidParameter() { Hamster.Environment env = new Hamster.Environment(); Database db = new Database(); Parameter[] param = new Parameter[3]; param[1] = new Parameter(); param[2] = new Parameter(); try { env.Create("ntest.db"); db = env.CreateDatabase(1, 0, param); db.Close(); env.Close(); } catch (DatabaseException e) { Assert.AreEqual(HamConst.HAM_INV_PARAMETER, e.ErrorCode); } }
/// <summary> /// Opens an existing Database /// </summary> /// <remarks> /// This method wraps the native ham_open_ex function. /// </remarks> /// <param name="fileName">The file name of the Database file.</param> /// <param name="flags">Optional flags for this operation, combined /// with bitwise OR. Possible flags are: /// <list type="bullet"> /// <item><see cref="HamConst.HAM_READ_ONLY" /> /// Opens the file for reading only. Operations which need /// write access (i.e. Database.Insert) /// will return <see cref="HamConst.HAM_DB_READ_ONLY" />. /// </item><br /> /// <item><see cref="HamConst.HAM_WRITE_THROUGH" /> /// Immediately write modified pages to the disk. This /// slows down all Database operations, but may save the /// Database integrity in case of a system crash.</item><br /> /// <item><see cref="HamConst.HAM_DISABLE_VAR_KEYLEN" /> /// Do not allow the use of variable length keys. Inserting /// a key, which is larger than the B+Tree index key size, /// returns <see cref="HamConst.HAM_INV_KEYSIZE" />.</item><br /> /// <item><see cref="HamConst.HAM_DISABLE_MMAP" /> /// Do not use memory mapped files for I/O. By default, /// hamsterdb checks if it can use mmap, since mmap is faster /// than read/write. For performance reasons, this flag should /// not be used.</item><br /> /// <item><see cref="HamConst.HAM_CACHE_STRICT" /> /// Do not allow the cache to grow larger than the size specified /// with <see cref="HamConst.HAM_PARAM_CACHESIZE" />. If a /// Database operation needs to resize the cache, it will /// fail and return <see cref="HamConst.HAM_CACHE_FULL" />. /// If the flag is not set, the cache is allowed to allocate /// more pages than the maximum cache size, but only if it's /// necessary and only for a short time.</item><br /> /// <item><see cref="HamConst.HAM_DISABLE_FREELIST_FLUSH" /> /// This flag is deprecated.</item><br /> /// <item><see cref="HamConst.HAM_LOCK_EXCLUSIVE" /> /// Place an exclusive lock on the file. Only one process /// may hold an exclusive lock for a given file at a given /// time. Deprecated - this is now the default.</item><br /> /// <item><see cref="HamConst.HAM_ENABLE_RECOVERY" /> /// Enables logging/recovery for this Database. Will return /// <see cref="HamConst.HAM_NEED_RECOVERY" />, if the /// Database is in an inconsistent state. Not allowed in /// combination with <see cref="HamConst.HAM_IN_MEMORY_DB" />, /// <see cref="HamConst.HAM_DISABLE_FREELIST_FLUSH" /> and /// <see cref="HamConst.HAM_WRITE_THROUGH" />.</item><br /> /// <item><see cref="HamConst.HAM_ENABLE_TRANSACTIONS" /> /// Enables Transactions for this Database. This flag implies /// <see cref="HamConst.HAM_ENABLE_RECOVERY" />.</item><br /> /// <item><see cref="HamConst.HAM_AUTO_RECOVERY" /> /// Automatically recover the Database, if necessary. This /// flag imples <see cref="HamConst.HAM_ENABLE_RECOVERY" />. /// </item><br /> /// <item><see cref="HamConst.HAM_SORT_DUPLICATES" /> /// Sort duplicate keys for this Database. Only allowed in /// combination with HAM_ENABLE_DUPLICATES. A compare function /// can be set with <see cref="Database.SetDuplicateCompareFunc"/>. /// This flag is not persistent.</item><br /> /// </list> /// </param> /// <param name="parameters">An array of <see cref="Parameter" /> /// structures. The following parameters are available:<br /> /// <list type="bullet"> /// <item><see cref="HamConst.HAM_PARAM_CACHESIZE" /> /// The size of the Database cache, in bytes. The default size /// is defined in <i>src/config.h</i> as HAM_DEFAULT_CACHESIZE /// - usually 2 MB.</item><br /> /// <item><see cref="HamConst.HAM_PARAM_DATA_ACCESS_MODE" /> /// Gives a hint regarding data access patterns. The default /// setting optimizes hamsterdb for random read/write access /// (<see cref="HamConst.HAM_DAM_RANDOM_WRITE"/>). /// Use <see cref="HamConst.HAM_DAM_SEQUENTIAL_INSERT"/> for /// sequential inserts (this is automatically set for /// record number Databases). This flag is not persistent.</item> /// </list> /// </param> /// <exception cref="DatabaseException"> /// <list type="bullet"> /// <item><see cref="HamConst.HAM_INV_PARAMETER"/> /// if an invalid combination of flags was specified</item> /// <item><see cref="HamConst.HAM_FILE_NOT_FOUND"/> /// if the file does not exist</item> /// <item><see cref="HamConst.HAM_IO_ERROR"/> /// if the file could not be opened or reading/writing failed</item> /// <item><see cref="HamConst.HAM_INV_FILE_VERSION"/> /// if the Database version is not compatible with the library /// version</item> /// <item><see cref="HamConst.HAM_OUT_OF_MEMORY"/> /// if memory could not be allocated</item> /// <item><see cref="HamConst.HAM_WOULD_BLOCK"/> /// if another process has locked the file</item> /// </list> /// </exception> public void Open(String fileName, int flags, Parameter[] parameters) { int st; if (parameters != null) parameters = AppendNullParameter(parameters); lock (this) { if (initialized == false) { st = NativeMethods.NewDatabaseHandle(out handle); if (st != 0) throw new DatabaseException(st); initialized = true; } st = NativeMethods.Open(handle, fileName, flags, parameters); } if (st != 0) throw new DatabaseException(st); }
/// <summary> /// Creates a new Database in this Environment /// </summary> /// <remarks>This method wraps the native ham_env_create_db function. /// </remarks> /// <returns>The new Database object</returns> /// <param name="name">The name of the Database. If a Database /// with this name already exists, /// <see cref="HamConst.HAM_DATABASE_ALREADY_EXISTS"/> is thrown. /// Database names from 0xf000 to 0xffff and 0 are reserved.</param> /// <param name="flags">Optional flags for creating the Database, /// combined with bitwise OR. Possible values are: /// <list> /// <item><see cref="HamConst.HAM_USE_BTREE" /> /// Use a B+Tree for the index structure. Currently enabled /// by default, but future releases of hamsterdb will offer /// additional index structures, i.e. hash tables.</item><br /> /// <item><see cref="HamConst.HAM_DISABLE_VAR_KEYLEN" /> /// Do not allow the use of variable length keys. Inserting /// a key, which is larger than the B+Tree index key size, /// returns <see cref="HamConst.HAM_INV_KEYSIZE" />.</item><br /> /// <item><see cref="HamConst.HAM_RECORD_NUMBER" /> /// Enable duplicate keys for this Database. By default, /// duplicate keys are disabled.</item><br /> /// <item><see cref="HamConst.HAM_ENABLE_DUPLICATES" /> /// Creates an "auto-increment" Database. Keys in Record /// Number Databases are automatically assigned an incrementing /// 64bit value.</item> /// <item><see cref="HamConst.HAM_SORT_DUPLICATES" /> /// Sort duplicate keys for this Database. Only allowed in /// combination with HAM_ENABLE_DUPLICATES. A compare function /// can be set with <see cref="Database.SetDuplicateCompareFunc"/>. /// This flag is not persistent.</item><br /> /// </list> /// </param> /// <param name="parameters">An array of <see cref="Parameter" /> /// structures. The following parameters are available:<br /> /// <list type="bullet"> /// <item><see cref="HamConst.HAM_PARAM_KEYSIZE" /> /// The size of the keys in the B+Tree index. The default size /// is 21 bytes.</item><br /> /// <item><see cref="HamConst.HAM_PARAM_DATA_ACCESS_MODE" /> /// Gives a hint regarding data access patterns. The default /// setting optimizes hamsterdb for random read/write access /// (<see cref="HamConst.HAM_DAM_RANDOM_WRITE"/>). /// Use <see cref="HamConst.HAM_DAM_SEQUENTIAL_INSERT"/> for /// sequential inserts (this is automatically set for /// record number Databases). This flag is not persistent.</item> /// </list> /// </param> /// <exception cref="DatabaseException"> /// <list type="bullet"> /// <item><see cref="HamConst.HAM_INV_PARAMETER"/> /// if an invalid combination of flags was specified</item> /// <item><see cref="HamConst.HAM_DATABASE_ALREADY_EXISTS"/> /// if a Database with this name already exists in this /// Environment</item> /// <item><see cref="HamConst.HAM_OUT_OF_MEMORY"/> /// if memory could not be allocated</item> /// <item><see cref="HamConst.HAM_INV_KEYSIZE"/> /// if the key size is too large (at least 4 keys must /// fit in a page)</item> /// <item><see cref="HamConst.HAM_LIMITS_REACHED"/> /// if the maximum number of Databases per Environment /// was already created</item> /// </list> /// </exception> /// <returns>The new Database object</returns> public Database CreateDatabase(short name, int flags, Parameter[] parameters) { int st; IntPtr dbh; lock (this) { st = NativeMethods.NewDatabaseHandle(out dbh); if (st != 0) throw new DatabaseException(st); st = NativeMethods.EnvCreateDatabase(handle, dbh, name, flags, parameters); } if (st != 0) throw new DatabaseException(st); return new Database(dbh); }