/* * This file is part of the HyperGraphDB source distribution. This is copyrighted software. For permitted * uses, licensing options and redistribution, please see the LicensingInformation file at the root level of * the distribution. * * Copyright (c) 2005-2010 Kobrix Software, Inc. All rights reserved. */ package org.hypergraphdb.storage.bje; import java.util.Comparator; import org.hypergraphdb.HGException; import org.hypergraphdb.HGRandomAccessResult; import org.hypergraphdb.HGSearchResult; import org.hypergraphdb.HGSortIndex; import org.hypergraphdb.storage.ByteArrayConverter; import org.hypergraphdb.storage.SearchResultWrapper; import org.hypergraphdb.transaction.HGTransaction; import org.hypergraphdb.transaction.HGTransactionManager; import org.hypergraphdb.transaction.VanillaTransaction; import com.sleepycat.je.BtreeStats; import com.sleepycat.je.Cursor; import com.sleepycat.je.CursorConfig; import com.sleepycat.je.Database; import com.sleepycat.je.DatabaseConfig; import com.sleepycat.je.DatabaseEntry; import com.sleepycat.je.DatabaseException; import com.sleepycat.je.LockMode; import com.sleepycat.je.OperationStatus; import com.sleepycat.je.Transaction; /** * <p> * A default index implementation. This implementation works by maintaining a separate DB, using a B-tree, * <code>byte []</code> lexicographical ordering on its keys. The keys are therefore assumed to by * <code>byte [] </code> instances. * </p> * * @author Borislav Iordanov */ @SuppressWarnings("unchecked") public class DefaultIndexImpl<KeyType, ValueType> implements HGSortIndex<KeyType, ValueType> { /** * Prefix of HyperGraph index DB filenames. */ public static final String DB_NAME_PREFIX = "hgstore_idx_"; protected BJEStorageImplementation storage; protected CursorConfig cursorConfig = new CursorConfig(); protected HGTransactionManager transactionManager; protected String name; protected Database db; private boolean owndb; protected Comparator<?> comparator; protected boolean sort_duplicates = true; protected ByteArrayConverter<KeyType> keyConverter; protected ByteArrayConverter<ValueType> valueConverter; protected void checkOpen() { if (!isOpen()) throw new HGException("Attempting to operate on index '" + name + "' while the index is being closed."); } protected TransactionBJEImpl txn() { HGTransaction tx = transactionManager.getContext().getCurrent(); if (tx == null || tx.getStorageTransaction() instanceof VanillaTransaction) return TransactionBJEImpl.nullTransaction(); else return (TransactionBJEImpl)tx.getStorageTransaction(); } // public DefaultIndexImpl(Environment env, // Database db, // HGTransactionManager transactionManager, // ByteArrayConverter<KeyType> keyConverter, // ByteArrayConverter<ValueType> valueConverter, // Comparator comparator) // { // this.db = db; // this.env = env; // this.transactionManager = transactionManager; // this.keyConverter = keyConverter; // this.valueConverter = valueConverter; // this.comparator = comparator; // owndb = false; // try { name = db.getDatabaseName(); } // catch (Exception ex) { throw new HGException(ex); } // // } public DefaultIndexImpl(String indexName, BJEStorageImplementation storage, HGTransactionManager transactionManager, ByteArrayConverter<KeyType> keyConverter, ByteArrayConverter<ValueType> valueConverter, Comparator<?> comparator) { this.name = indexName; this.storage = storage; this.transactionManager = transactionManager; this.keyConverter = keyConverter; this.valueConverter = valueConverter; this.comparator = comparator; owndb = true; } public String getName() { return name; } public String getDatabaseName() { return DB_NAME_PREFIX + name; } public Comparator<byte[]> getComparator() { try { if (comparator != null) { return (Comparator<byte[]>)comparator; } else { return db.getConfig().getBtreeComparator(); } } catch (DatabaseException ex) { throw new HGException(ex); } } public void open() { try { DatabaseConfig dbConfig = storage.getConfiguration().getDatabaseConfig().clone(); dbConfig.setSortedDuplicates(sort_duplicates); if (comparator != null) { dbConfig.setBtreeComparator((Comparator<byte[]>)comparator); } db = storage.getBerkleyEnvironment().openDatabase(null, DB_NAME_PREFIX + name, dbConfig); } catch (Throwable t) { throw new HGException("While attempting to open index ;" + name + "': " + t.toString(), t); } } public void close() { if (db == null || !owndb) return; try { db.close(); } catch (Throwable t) { throw new HGException(t); } finally { db = null; } } public boolean isOpen() { return db != null; } public HGRandomAccessResult<ValueType> scanValues() { checkOpen(); DatabaseEntry keyEntry = new DatabaseEntry(); DatabaseEntry value = new DatabaseEntry(); HGRandomAccessResult<ValueType> result = null; Cursor cursor = null; try { TransactionBJEImpl tx = txn(); cursor = db.openCursor(tx.getBJETransaction(), cursorConfig); OperationStatus status = cursor.getFirst(keyEntry, value, LockMode.DEFAULT); if (status == OperationStatus.SUCCESS /* && cursor.count() > 0 */) result = new KeyRangeForwardResultSet<ValueType>(tx.attachCursor(cursor), keyEntry, valueConverter); else { try { cursor.close(); } catch (Throwable t) { } result = (HGRandomAccessResult<ValueType>)HGSearchResult.EMPTY; } } catch (Throwable ex) { if (cursor != null) { try { cursor.close(); } catch (Throwable t) { } } throw new HGException("Failed to lookup index '" + name + "': " + ex.toString(), ex); } return result; } public HGRandomAccessResult<KeyType> scanKeys() { checkOpen(); DatabaseEntry keyEntry = new DatabaseEntry(); DatabaseEntry value = new DatabaseEntry(); HGRandomAccessResult<KeyType> result = null; Cursor cursor = null; try { TransactionBJEImpl tx = txn(); cursor = db.openCursor(tx.getBJETransaction(), cursorConfig); OperationStatus status = cursor.getFirst(keyEntry, value, LockMode.DEFAULT); if (status == OperationStatus.SUCCESS /* && cursor.count() > 0 */) result = new KeyScanResultSet<KeyType>(tx.attachCursor(cursor), keyEntry, keyConverter); else { try { cursor.close(); } catch (Throwable t) { } result = (HGRandomAccessResult<KeyType>)HGSearchResult.EMPTY; } } catch (Throwable ex) { if (cursor != null) { try { cursor.close(); } catch (Throwable t) { } } throw new HGException("Failed to lookup index '" + name + "': " + ex.toString(), ex); } return result; } public void addEntry(KeyType key, ValueType value) { checkOpen(); DatabaseEntry dbkey = new DatabaseEntry(keyConverter.toByteArray(key)); DatabaseEntry dbvalue = new DatabaseEntry(valueConverter.toByteArray(value)); try { OperationStatus result = db.putNoDupData(txn().getBJETransaction(), dbkey, dbvalue); if (result != OperationStatus.SUCCESS && result != OperationStatus.KEYEXIST) throw new Exception("OperationStatus: " + result); } catch (Exception ex) { throw new HGException("Failed to add entry to index '" + name + "': " + ex.toString(), ex); } } public void removeEntry(KeyType key, ValueType value) { checkOpen(); DatabaseEntry keyEntry = new DatabaseEntry(keyConverter.toByteArray(key)); DatabaseEntry valueEntry = new DatabaseEntry(valueConverter.toByteArray(value)); Cursor cursor = null; try { cursor = db.openCursor(txn().getBJETransaction(), cursorConfig); if (cursor.getSearchBoth(keyEntry, valueEntry, LockMode.DEFAULT) == OperationStatus.SUCCESS) cursor.delete(); } catch (Exception ex) { throw new HGException("Failed to lookup index '" + name + "': " + ex.toString(), ex); } finally { if (cursor != null) { try { cursor.close(); } catch (Throwable t) { } } } } public void removeAllEntries(KeyType key) { checkOpen(); DatabaseEntry dbkey = new DatabaseEntry(keyConverter.toByteArray(key)); try { db.delete(txn().getBJETransaction(), dbkey); } catch (Exception ex) { throw new HGException("Failed to delete entry from index '" + name + "': " + ex.toString(), ex); } } void ping(Transaction tx) { DatabaseEntry key = new DatabaseEntry(new byte[1]); DatabaseEntry data = new DatabaseEntry(); try { db.get(tx, key, data, LockMode.DEFAULT); } catch (Exception ex) { throw new HGException("Failed to ping index '" + name + "': " + ex.toString(), ex); } } public ValueType getData(KeyType key) { checkOpen(); DatabaseEntry keyEntry = new DatabaseEntry(keyConverter.toByteArray(key)); DatabaseEntry value = new DatabaseEntry(); ValueType result = null; try { OperationStatus status = db.get(txn().getBJETransaction(), keyEntry, value, LockMode.DEFAULT); if (status == OperationStatus.SUCCESS) result = valueConverter.fromByteArray(value.getData(), value.getOffset(), value.getSize()); } catch (Exception ex) { throw new HGException("Failed to lookup index '" + name + "': " + ex.toString(), ex); } return result; } public ValueType findFirst(KeyType key) { checkOpen(); DatabaseEntry keyEntry = new DatabaseEntry(keyConverter.toByteArray(key)); DatabaseEntry value = new DatabaseEntry(); ValueType result = null; Cursor cursor = null; try { cursor = db.openCursor(txn().getBJETransaction(), cursorConfig); OperationStatus status = cursor.getSearchKey(keyEntry, value, LockMode.DEFAULT); if (status == OperationStatus.SUCCESS) result = valueConverter.fromByteArray(value.getData(), value.getOffset(), value.getSize()); } catch (Exception ex) { throw new HGException("Failed to lookup index '" + name + "': " + ex.toString(), ex); } finally { if (cursor != null) { try { cursor.close(); } catch (Throwable t) { } } } return result; } /** * <p> * Find the last entry, assuming ordered duplicates, corresponding to the given key. * </p> * * @param key * The key whose last entry is sought. * @return The last (i.e. greatest, i.e. maximum) data value for that key or null if the set of entries for * the key is empty. */ public ValueType findLast(KeyType key) { checkOpen(); DatabaseEntry keyEntry = new DatabaseEntry(keyConverter.toByteArray(key)); DatabaseEntry value = new DatabaseEntry(); ValueType result = null; Cursor cursor = null; try { cursor = db.openCursor(txn().getBJETransaction(), cursorConfig); OperationStatus status = cursor.getLast(keyEntry, value, LockMode.DEFAULT); if (status == OperationStatus.SUCCESS) result = valueConverter.fromByteArray(value.getData(), value.getOffset(), value.getSize()); } catch (Exception ex) { throw new HGException("Failed to lookup index '" + name + "': " + ex.toString(), ex); } finally { if (cursor != null) { try { cursor.close(); } catch (Throwable t) { } } } return result; } public HGRandomAccessResult<ValueType> find(KeyType key) { checkOpen(); DatabaseEntry keyEntry = new DatabaseEntry(keyConverter.toByteArray(key)); DatabaseEntry value = new DatabaseEntry(); HGRandomAccessResult<ValueType> result = null; Cursor cursor = null; try { TransactionBJEImpl tx = txn(); cursor = db.openCursor(txn().getBJETransaction(), cursorConfig); OperationStatus status = cursor.getSearchKey(keyEntry, value, LockMode.DEFAULT); if (status == OperationStatus.SUCCESS /*&& cursor.count() > 0*/) { result = new SingleKeyResultSet<ValueType>(tx.attachCursor(cursor), keyEntry, valueConverter); } else { try { cursor.close(); } catch (Throwable t) { } result = (HGRandomAccessResult<ValueType>)HGSearchResult.EMPTY; } } catch (Throwable ex) { if (cursor != null) { try { cursor.close(); } catch (Throwable t) { } } throw new HGException("Failed to lookup index '" + name + "': " + ex.toString(), ex); } return result; } private HGSearchResult<ValueType> findOrdered(KeyType key, boolean lower_range, boolean compare_equals) { checkOpen(); /* * if (key == null) throw new HGException("Attempting to lookup index '" + name + "' with a null key."); */ byte[] keyAsBytes = keyConverter.toByteArray(key); DatabaseEntry keyEntry = new DatabaseEntry(keyAsBytes); DatabaseEntry value = new DatabaseEntry(); Cursor cursor = null; try { TransactionBJEImpl tx = txn(); cursor = db.openCursor(txn().getBJETransaction(), cursorConfig); OperationStatus status = cursor.getSearchKeyRange(keyEntry, value, LockMode.DEFAULT); if (status == OperationStatus.SUCCESS) { Comparator<byte[]> comparator = db.getConfig().getBtreeComparator(); if (!compare_equals) { // strict < or >? if (lower_range) { status = cursor.getPrev(keyEntry, value, LockMode.DEFAULT); } else if (comparator.compare(keyAsBytes, keyEntry.getData()) == 0) { status = cursor.getNextNoDup(keyEntry, value, LockMode.DEFAULT); } } // BDB cursor will position on the key or on the next element greater than the key // in the latter case we need to back up by one for < (or <=) query else if (lower_range && comparator.compare(keyAsBytes, keyEntry.getData()) != 0) { status = cursor.getPrev(keyEntry, value, LockMode.DEFAULT); } } else if (lower_range) { status = cursor.getLast(keyEntry, value, LockMode.DEFAULT); } else { status = cursor.getFirst(keyEntry, value, LockMode.DEFAULT); } if (status == OperationStatus.SUCCESS) { if (lower_range) { return new SearchResultWrapper<ValueType>(new KeyRangeBackwardResultSet<ValueType>(tx.attachCursor(cursor), keyEntry, valueConverter)); } else { return new SearchResultWrapper<ValueType>(new KeyRangeForwardResultSet<ValueType>(tx.attachCursor(cursor), keyEntry, valueConverter)); } } else { try { cursor.close(); } catch (Throwable t) { } } return (HGSearchResult<ValueType>)HGSearchResult.EMPTY; } catch (Throwable ex) { if (cursor != null) { try { cursor.close(); } catch (Throwable t) { } } throw new HGException("Failed to lookup index '" + name + "': " + ex.toString(), ex); } } public HGSearchResult<ValueType> findGT(KeyType key) { return findOrdered(key, false, false); } public HGSearchResult<ValueType> findGTE(KeyType key) { return findOrdered(key, false, true); } public HGSearchResult<ValueType> findLT(KeyType key) { return findOrdered(key, true, false); } public HGSearchResult<ValueType> findLTE(KeyType key) { return findOrdered(key, true, true); } protected void finalize() { if (isOpen()) try { close(); } catch (Throwable t) { } } public long count() { try { return ((BtreeStats)db.getStats(null)).getLeafNodeCount(); } catch (DatabaseException ex) { throw new HGException(ex); } } public long count(KeyType key) { Cursor cursor = null; try { cursor = db.openCursor(txn().getBJETransaction(), cursorConfig); DatabaseEntry keyEntry = new DatabaseEntry(keyConverter.toByteArray(key)); DatabaseEntry value = new DatabaseEntry(); OperationStatus status = cursor.getSearchKey(keyEntry, value, LockMode.DEFAULT); if (status == OperationStatus.SUCCESS) return cursor.count(); else return 0; } catch (DatabaseException ex) { throw new HGException(ex); } finally { if (cursor != null) { try { cursor.close(); } catch (Throwable t) { } } } } }