package org.reldb.rel.v0.storage.tables; import com.sleepycat.je.*; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.NoSuchElementException; import org.reldb.rel.exceptions.*; import org.reldb.rel.v0.generator.Generator; import org.reldb.rel.v0.generator.SelectAttributes; import org.reldb.rel.v0.storage.RelDatabase; import org.reldb.rel.v0.storage.TransactionRunner; import org.reldb.rel.v0.storage.relvars.RelvarHeading; import org.reldb.rel.v0.storage.temporary.TempTable; import org.reldb.rel.v0.storage.temporary.TempTableImplementation; import org.reldb.rel.v0.types.AttributeMap; import org.reldb.rel.v0.types.Heading; import org.reldb.rel.v0.types.Type; import org.reldb.rel.v0.values.*; import org.reldb.rel.v0.vm.Context; import org.reldb.rel.v0.vm.Instruction; import org.reldb.rel.v0.vm.VirtualMachine; /** An updatable collection of ValueTupleS; a wrapper around Berkeley DB's "Database". */ public abstract class Table { private RelDatabase database; private RelvarHeading headingDefinition; private AttributeMap[] keyMaps; public Table(RelDatabase database, RelvarHeading headingDefinition) { this.database = database; this.headingDefinition = headingDefinition; keyMaps = new AttributeMap[headingDefinition.getKeyCount()]; for (int keyNumber=0; keyNumber<headingDefinition.getKeyCount(); keyNumber++) { SelectAttributes keyAttributes = headingDefinition.getKey(keyNumber); Heading sourceHeading = headingDefinition.getHeading(); Heading targetHeading = sourceHeading.project(keyAttributes); keyMaps[keyNumber] = new AttributeMap(targetHeading, sourceHeading); } } public RelvarHeading getHeadingDefinition() { return headingDefinition; } protected abstract Storage getStorage(Transaction txn) throws DatabaseException; public RelDatabase getDatabase() { return database; } private DatabaseEntry getKeyValueFromTuple(Generator generator, ValueTuple tuple, int keyNumber) { DatabaseEntry theKey = new DatabaseEntry(); if (headingDefinition.getKeyCount() == 0) database.getTupleBinding().objectToEntry(tuple, theKey); else { ValueTuple keyTuple = keyMaps[keyNumber].project(generator, tuple); database.getTupleBinding().objectToEntry(keyTuple, theKey); } return theKey; } public boolean insertTupleNoDuplicates(Generator generator, Storage table, Transaction txn, ValueTuple tuple, String description) throws DatabaseException { DatabaseEntry theData = new DatabaseEntry(); database.getTupleBinding().objectToEntry(tuple, theData); // Put it in the database. for (int i=0; i<table.size(); i++) { Database tab = table.getDatabase(i); DatabaseEntry entry = (i == 0) ? theData : database.getKeyTableEntry(); if (tab.putNoOverwrite(txn, getKeyValueFromTuple(generator, tuple, i), entry) == OperationStatus.KEYEXIST) throw new ExceptionSemantic("RS0232: " + description + " tuple would violate uniqueness constraint of KEY {" + headingDefinition.getKey(i) + "}"); } return true; } boolean insertTuple(Generator generator, Storage table, Transaction txn, ValueTuple tuple, String description) throws DatabaseException { DatabaseEntry theData = new DatabaseEntry(); database.getTupleBinding().objectToEntry(tuple, theData); // Put it in the database. Skip it silently if duplicate. for (int i=0; i<table.size(); i++) { Database tab = table.getDatabase(i); DatabaseEntry entry = (i == 0) ? theData : database.getKeyTableEntry(); if (tab.putNoOverwrite(txn, getKeyValueFromTuple(generator, tuple, i), entry) == OperationStatus.KEYEXIST) return false; } return true; } public void insert(final Generator generator, final ValueTuple tuple) { try { (new TransactionRunner() { public Object run(Transaction txn) throws Throwable { insertTupleNoDuplicates(generator, getStorage(txn), txn, (ValueTuple)tuple.getSerializableClone(), "Inserting"); return null; } }).execute(database); } catch (ExceptionSemantic se) { throw se; } catch (Throwable t) { throw new ExceptionSemantic("RS0233: insert tuple failed: " + t.getMessage()); } } private static abstract class Inserter { abstract boolean insert(Generator generator, Storage table, Transaction txn, ValueTuple tuple, String comment); } private long insert(final Generator generator, final ValueRelation relation, final Inserter inserter) { try { return ((Long)(new TransactionRunner() { public Object run(Transaction txn) throws Throwable { // use of temporary storage prevents problems with deadlock or infinite iteration if we insert a relvar into itself // TODO - improve this so we only use temporary storage if there's the potential of inserting relvar 'x' (or a query involving relvar 'x') into itself. TempTable tmp = new TempTableImplementation(database); long insertCount = 0; try { Storage table = getStorage(txn); TupleIterator iterator = relation.iterator(); try { while (iterator.hasNext()) { ValueTuple tuple = (ValueTuple)iterator.next().getSerializableClone(); tmp.put(tuple); } } finally { iterator.close(); } iterator = tmp.values(); try { while (iterator.hasNext()) { ValueTuple tuple = iterator.next(); if (inserter.insert(generator, table, txn, tuple, "Inserting")) insertCount++; } } finally { iterator.close(); } } finally { tmp.close(); } return new Long(insertCount); } }).execute(database)).longValue(); } catch (ExceptionSemantic se) { throw se; } catch (Throwable t) { t.printStackTrace(); throw new ExceptionSemantic("RS0234: insert relation failed: " + t.getMessage()); } } public long insert(final Generator generator, final ValueRelation relation) { return insert(generator, relation, new Inserter() { boolean insert(Generator generator, Storage table, Transaction txn, ValueTuple tuple, String comment) { return insertTuple(generator, table, txn, tuple, comment); } }); } public long insertNoDuplicates(Generator generator, ValueRelation relation) { return insert(generator, relation, new Inserter() { boolean insert(Generator generator, Storage table, Transaction txn, ValueTuple tuple, String comment) { return insertTupleNoDuplicates(generator, table, txn, tuple, comment); } }); } public long getCardinality() { try { return ((Long)(new TransactionRunner() { public Object run(Transaction txn) throws Throwable { return new Long(getStorage(txn).getDatabase(0).count()); } }).execute(database)).longValue(); } catch (ExceptionSemantic se) { throw se; } catch (Throwable de) { de.printStackTrace(); throw new ExceptionFatal("RS0370: getCardinality failed: " + de.getMessage()); } } /** Obtain tuple value given a key. Return null if not found. */ public ValueTuple getTupleForKey(final Generator generator, final ValueTuple tuple) { try { return ((ValueTuple)(new TransactionRunner() { public Object run(Transaction txn) throws Throwable { DatabaseEntry foundData = new DatabaseEntry(); Storage storage = getStorage(txn); if (storage == null) return null; if (storage.getDatabase(0).get(txn, getKeyValueFromTuple(generator, tuple, 0), foundData, LockMode.READ_COMMITTED) == OperationStatus.SUCCESS) return (ValueTuple)database.getTupleBinding().entryToObject(foundData); return null; } }).execute(database)); } catch (ExceptionSemantic se) { throw se; } catch (Throwable t) { t.printStackTrace(); throw new ExceptionFatal("RS0371: getTupleForKey failed: " + t.getMessage()); } } public boolean contains(final Generator generator, final ValueTuple tuple) { try { return ((Boolean)(new TransactionRunner() { public Object run(Transaction txn) throws Throwable { DatabaseEntry foundData = new DatabaseEntry(); DatabaseEntry keyData = getKeyValueFromTuple(generator, tuple, 0); return Boolean.valueOf(getStorage(txn).getDatabase(0).get(txn, keyData, foundData, LockMode.READ_COMMITTED) == OperationStatus.SUCCESS); } }).execute(database)).booleanValue(); } catch (ExceptionSemantic se) { throw se; } catch (Throwable t) { t.printStackTrace(); throw new ExceptionFatal("RS0372: contains failed: " + t.getMessage()); } } // Delete all tuples private void purge(Transaction txn) throws DatabaseException { Storage tables = getStorage(txn); for (int i=0; i<tables.size(); i++) { Cursor cursor = tables.getDatabase(i).openCursor(txn, null); try { DatabaseEntry foundKey = new DatabaseEntry(); DatabaseEntry foundData = new DatabaseEntry(); while (cursor.getNext(foundKey, foundData, LockMode.RMW) == OperationStatus.SUCCESS) cursor.delete(); } finally { cursor.close(); } } } // Delete all tuples public void purge() { try { (new TransactionRunner() { public Object run(Transaction txn) throws Throwable { purge(txn); return null; } }).execute(database); } catch (ExceptionSemantic se) { throw se; } catch (Throwable t) { t.printStackTrace(); throw new ExceptionFatal("RS0373: purge failed: " + t.getMessage()); } } // Delete given tuple. public void delete(final Generator generator, final ValueTuple tuple) { try { (new TransactionRunner() { public Object run(Transaction txn) throws Throwable { Storage tables = getStorage(txn); for (int i=0; i<tables.size(); i++) tables.getDatabase(i).delete(txn, getKeyValueFromTuple(generator, tuple, i)); return null; } }).execute(database); } catch (ExceptionSemantic se) { throw se; } catch (Throwable t) { t.printStackTrace(); throw new ExceptionFatal("RS0374: delete tuple failed: " + t.getMessage()); } } // Delete selected tuples public long delete(final Generator generator, final TupleFilter filter) { try { return ((Long)(new TransactionRunner() { public Object run(Transaction txn) throws Throwable { Storage tables = getStorage(txn); Cursor cursor = tables.getDatabase(0).openCursor(txn, null); long deleteCount = 0; try { DatabaseEntry foundKey = new DatabaseEntry(); DatabaseEntry foundData = new DatabaseEntry(); while (cursor.getNext(foundKey, foundData, LockMode.RMW) == OperationStatus.SUCCESS) { ValueTuple tuple = (ValueTuple)database.getTupleBinding().entryToObject(foundData); tuple.loaded(generator); if (filter.filter(tuple)) { cursor.delete(); for (int i=1; i<tables.size(); i++) tables.getDatabase(i).delete(txn, getKeyValueFromTuple(generator, tuple, i)); deleteCount++; } } } finally { cursor.close(); } return new Long(deleteCount); } }).execute(database)).longValue(); } catch (ExceptionSemantic se) { throw se; } catch (Throwable t) { t.printStackTrace(); throw new ExceptionFatal("RS0375: delete tuples failed: " + t.getMessage()); } } // Delete specified tuples. If there are tuplesToDelete not found in this Relvar, and errorIfNotIncluded is true, throw an error. public long delete(Context context, ValueRelation tuplesToDelete, boolean errorIfNotIncluded) { final HashMap<ValueTuple, Boolean> toDelete = new HashMap<ValueTuple, Boolean>(); Generator generator = context.getGenerator(); // index the tuplesToDelete into the toDelete index, which uses each tupleToDelete as a key and a Boolean as the value. TupleIterator iterator = tuplesToDelete.iterator(); try { while (iterator.hasNext()) toDelete.put(iterator.next(), Boolean.FALSE); } finally { iterator.close(); } if (errorIfNotIncluded) { // make sure every tuple in toDelete is found in this relvar (table) at least once. TupleIterator relvarTupleIterator = iterator(generator); try { while (relvarTupleIterator.hasNext()) { ValueTuple keyTuple = relvarTupleIterator.next(); if (toDelete.containsKey(keyTuple)) toDelete.put(keyTuple, Boolean.TRUE); } } finally { relvarTupleIterator.close(); } // make sure every entry in index is TRUE, i.e., has been found at least once Collection<Boolean>values = toDelete.values(); Iterator<Boolean> valueIterator = values.iterator(); while (valueIterator.hasNext()) if (!valueIterator.next().booleanValue()) throw new ExceptionSemantic("RS0235: In I_DELETE, one or more specified tuples are not included in the relvar."); } return delete(generator, new TupleFilter() { public boolean filter(ValueTuple tuple) { return toDelete.containsKey(tuple); } }); } // Update selected tuples using a given TupleMap public long update(final Generator generator, final TupleFilter whereFilter, final TupleMap updateMap) { try { return ((Long)(new TransactionRunner() { public Object run(Transaction txn) throws Throwable { TempTable insertionTemporaryTable = new TempTableImplementation(database); long updateCount = 0; try { Storage tables = getStorage(txn); DatabaseEntry foundKey = new DatabaseEntry(); DatabaseEntry foundData = new DatabaseEntry(); Cursor cursor = tables.getDatabase(0).openCursor(txn, null); try { while (cursor.getNext(foundKey, foundData, LockMode.RMW) == OperationStatus.SUCCESS) { ValueTuple tuple = (ValueTuple)database.getTupleBinding().entryToObject(foundData); tuple.loaded(generator); if (whereFilter.filter(tuple)) { ValueTuple newTuple = updateMap.map(tuple); cursor.delete(); for (int i=1; i<tables.size(); i++) tables.getDatabase(i).delete(txn, getKeyValueFromTuple(generator, tuple, i)); ValueTuple data = (ValueTuple)newTuple.getSerializableClone(); insertionTemporaryTable.put(data); updateCount++; } } } finally { cursor.close(); } TupleIterator iterator = insertionTemporaryTable.values(); try { while (iterator.hasNext()) insertTupleNoDuplicates(generator, tables, txn, iterator.next(), "Updating"); } finally { iterator.close(); } } finally { insertionTemporaryTable.close(); } return new Long(updateCount); } }).execute(database)).longValue(); } catch (ExceptionSemantic se) { throw se; } catch (Throwable t) { t.printStackTrace(); throw new ExceptionFatal("RS0376: update failed: " + t.getMessage()); } } // Update all tuples using a given TupleMap public long update(final Generator generator, final TupleMap map) { return update(generator, new TupleFilter() { public boolean filter(ValueTuple tuple) { return true; } }, map); } // Get a TupleIterator public TupleIterator iterator(final Generator generator) { return new RegisteredTupleIterator(database) { DatabaseEntry foundKey = new DatabaseEntry(); DatabaseEntry foundData = new DatabaseEntry(); ValueTuple current = null; boolean atEnd = false; public boolean hasNext() { if (current != null) return true; if (atEnd) return false; try { if (cursor == null) { txn = database.beginTransaction(); cursor = getStorage(txn.getTransaction()).getDatabase(0).openCursor(txn.getTransaction(), null); } if (cursor.getNext(foundKey, foundData, LockMode.DEFAULT) == OperationStatus.SUCCESS) { current = (ValueTuple)database.getTupleBinding().entryToObject(foundData); current.loaded(generator); return true; } else atEnd = true; } catch (DatabaseException exp) { exp.printStackTrace(); throw new ExceptionFatal("RS0377: Unable to get next tuple: " + exp.getMessage()); } return false; } public ValueTuple next() { if (hasNext()) try { return current; } finally { current = null; } throw new NoSuchElementException(); } }; } // Alter every tuple to include the rightTuple public void expandTuples(Transaction txn, ValueTuple rightTuple) { Storage tables = getStorage(txn); DatabaseEntry foundKey = new DatabaseEntry(); DatabaseEntry foundData = new DatabaseEntry(); Cursor cursor = tables.getDatabase(0).openCursor(txn, null); try { while (cursor.getNext(foundKey, foundData, LockMode.RMW) == OperationStatus.SUCCESS) { ValueTuple oldTuple = (ValueTuple)database.getTupleBinding().entryToObject(foundData); ValueTuple newTuple = oldTuple.joinDisjoint(rightTuple); DatabaseEntry theData = new DatabaseEntry(); database.getTupleBinding().objectToEntry(newTuple, theData); cursor.putCurrent(theData); } } finally { cursor.close(); } } // Alter every tuple to drop the attribute with the specified index public void shrinkTuples(Transaction txn, int attributeIndex) { Storage tables = getStorage(txn); DatabaseEntry foundKey = new DatabaseEntry(); DatabaseEntry foundData = new DatabaseEntry(); Cursor cursor = tables.getDatabase(0).openCursor(txn, null); try { while (cursor.getNext(foundKey, foundData, LockMode.RMW) == OperationStatus.SUCCESS) { ValueTuple oldTuple = (ValueTuple)database.getTupleBinding().entryToObject(foundData); ValueTuple newTuple = oldTuple.shrink(attributeIndex); DatabaseEntry theData = new DatabaseEntry(); database.getTupleBinding().objectToEntry(newTuple, theData); cursor.putCurrent(theData); } } finally { cursor.close(); } } // Alter every tuple to set attribute at newAttributeIndex to the result of invoking selector newAttributeSelector // with an argument of the attribute at oldAttributeIndex. public void convertTuples(Generator generator, Transaction txn, Type oldType, int oldAttributeIndex, int newAttributeIndex, Instruction selector) { Storage tables = getStorage(txn); DatabaseEntry foundKey = new DatabaseEntry(); DatabaseEntry foundData = new DatabaseEntry(); Cursor cursor = tables.getDatabase(0).openCursor(txn, null); VirtualMachine vm = new VirtualMachine(generator, database, System.out); Context context = new Context(generator, vm); try { while (cursor.getNext(foundKey, foundData, LockMode.RMW) == OperationStatus.SUCCESS) { ValueTuple oldTuple = (ValueTuple)database.getTupleBinding().entryToObject(foundData); Value oldValue = ValueCharacter.select(generator, oldTuple.getValues()[oldAttributeIndex].toParsableString(oldType)); context.push(oldValue); selector.execute(context); Value newValue = context.pop(); oldTuple.getValues()[newAttributeIndex] = newValue; DatabaseEntry theData = new DatabaseEntry(); database.getTupleBinding().objectToEntry(oldTuple, theData); cursor.putCurrent(theData); } } finally { cursor.close(); } } }