com.sleepycat.persist.EntityJoin Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of je Show documentation
Show all versions of je Show documentation
Berkley Database Java Edition - build and runtime support.
/*-
* See the file LICENSE for redistribution information.
*
* Copyright (c) 2002-2010 Oracle. All rights reserved.
*
* $Id: EntityJoin.java,v 1.16 2010/01/04 15:50:55 cwl Exp $
*/
package com.sleepycat.persist;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import com.sleepycat.bind.EntityBinding;
import com.sleepycat.bind.EntryBinding;
import com.sleepycat.je.Cursor;
import com.sleepycat.je.CursorConfig;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.JoinCursor;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.OperationStatus;
import com.sleepycat.je.Transaction;
/**
* Performs an equality join on two or more secondary keys.
*
* {@code EntityJoin} objects are thread-safe. Multiple threads may safely
* call the methods of a shared {@code EntityJoin} object.
*
* An equality join is a match on all entities in a given primary index that
* have two or more specific secondary key values. Note that key ranges may
* not be matched by an equality join, only exact keys are matched.
*
* For example:
*
* // Index declarations -- see {@link package summary example}.
* //
* {@literal PrimaryIndex personBySsn;}
* {@literal SecondaryIndex personByParentSsn;}
* {@literal SecondaryIndex personByEmployerIds;}
* Employer employer = ...;
*
* // Match on all Person objects having parentSsn "111-11-1111" and also
* // containing an employerId of employer.id. In other words, match on all
* // of Bob's children that work for a given employer.
* //
* {@literal EntityJoin join = new EntityJoin(personBySsn);}
* join.addCondition(personByParentSsn, "111-11-1111");
* join.addCondition(personByEmployerIds, employer.id);
*
* // Perform the join operation by traversing the results with a cursor.
* //
* {@literal ForwardCursor results = join.entities();}
* try {
* for (Person person : results) {
* System.out.println(person.ssn + ' ' + person.name);
* }
* } finally {
* results.close();
* }
*
* @author Mark Hayes
*/
public class EntityJoin {
private PrimaryIndex primary;
private List conditions;
/**
* Creates a join object for a given primary index.
*
* @param index the primary index on which the join will operate.
*/
public EntityJoin(PrimaryIndex index) {
primary = index;
conditions = new ArrayList();
}
/**
* Adds a secondary key condition to the equality join. Only entities
* having the given key value in the given secondary index will be returned
* by the join operation.
*
* @param index the secondary index containing the given key value.
*
* @param key the key value to match during the join.
*/
public void addCondition(SecondaryIndex index, SK key) {
/* Make key entry. */
DatabaseEntry keyEntry = new DatabaseEntry();
index.getKeyBinding().objectToEntry(key, keyEntry);
/* Use keys database if available. */
Database db = index.getKeysDatabase();
if (db == null) {
db = index.getDatabase();
}
/* Add condition. */
conditions.add(new Condition(db, keyEntry));
}
/**
* Opens a cursor that returns the entities qualifying for the join. The
* join operation is performed as the returned cursor is accessed.
*
* The operations performed with the cursor will not be transaction
* protected, and {@link CursorConfig#DEFAULT} is used implicitly.
*
* @return the cursor.
*
*
* @throws OperationFailureException if one of the Read Operation
* Failures occurs.
*
* @throws EnvironmentFailureException if an unexpected, internal or
* environment-wide failure occurs.
*
*
* @throws IllegalStateException if less than two conditions were added.
*
* @throws DatabaseException the base class for all BDB exceptions.
*/
public ForwardCursor entities()
throws DatabaseException {
return entities(null, null);
}
/**
* Opens a cursor that returns the entities qualifying for the join. The
* join operation is performed as the returned cursor is accessed.
*
* @param txn the transaction used to protect all operations performed with
* the cursor, or null if the operations should not be transaction
* protected. If the store is non-transactional, null must be specified.
* For a transactional store the transaction is optional for read-only
* access and required for read-write access.
*
* @param config the cursor configuration that determines the default lock
* mode used for all cursor operations, or null to implicitly use {@link
* CursorConfig#DEFAULT}.
*
* @return the cursor.
*
*
* @throws OperationFailureException if one of the Read Operation
* Failures occurs.
*
* @throws EnvironmentFailureException if an unexpected, internal or
* environment-wide failure occurs.
*
*
* @throws IllegalStateException if less than two conditions were added.
*
* @throws DatabaseException the base class for all BDB exceptions.
*/
public ForwardCursor entities(Transaction txn, CursorConfig config)
throws DatabaseException {
return new JoinForwardCursor(txn, config, false);
}
/**
* Opens a cursor that returns the primary keys of entities qualifying for
* the join. The join operation is performed as the returned cursor is
* accessed.
*
* The operations performed with the cursor will not be transaction
* protected, and {@link CursorConfig#DEFAULT} is used implicitly.
*
* @return the cursor.
*
*
* @throws OperationFailureException if one of the Read Operation
* Failures occurs.
*
* @throws EnvironmentFailureException if an unexpected, internal or
* environment-wide failure occurs.
*
*
* @throws IllegalStateException if less than two conditions were added.
*
* @throws DatabaseException the base class for all BDB exceptions.
*/
public ForwardCursor keys()
throws DatabaseException {
return keys(null, null);
}
/**
* Opens a cursor that returns the primary keys of entities qualifying for
* the join. The join operation is performed as the returned cursor is
* accessed.
*
* @param txn the transaction used to protect all operations performed with
* the cursor, or null if the operations should not be transaction
* protected. If the store is non-transactional, null must be specified.
* For a transactional store the transaction is optional for read-only
* access and required for read-write access.
*
* @param config the cursor configuration that determines the default lock
* mode used for all cursor operations, or null to implicitly use {@link
* CursorConfig#DEFAULT}.
*
* @return the cursor.
*
*
* @throws OperationFailureException if one of the Read Operation
* Failures occurs.
*
* @throws EnvironmentFailureException if an unexpected, internal or
* environment-wide failure occurs.
*
*
* @throws IllegalStateException if less than two conditions were added.
*
* @throws DatabaseException the base class for all BDB exceptions.
*/
public ForwardCursor keys(Transaction txn, CursorConfig config)
throws DatabaseException {
return new JoinForwardCursor(txn, config, true);
}
private static class Condition {
private Database db;
private DatabaseEntry key;
Condition(Database db, DatabaseEntry key) {
this.db = db;
this.key = key;
}
Cursor openCursor(Transaction txn, CursorConfig config)
throws DatabaseException {
OperationStatus status;
Cursor cursor = db.openCursor(txn, config);
try {
DatabaseEntry data = BasicIndex.NO_RETURN_ENTRY;
status = cursor.getSearchKey(key, data, null);
} catch (DatabaseException e) {
try {
cursor.close();
} catch (DatabaseException ignored) {}
throw e;
}
if (status == OperationStatus.SUCCESS) {
return cursor;
} else {
cursor.close();
return null;
}
}
}
private class JoinForwardCursor implements ForwardCursor {
private Cursor[] cursors;
private JoinCursor joinCursor;
private boolean doKeys;
JoinForwardCursor(Transaction txn, CursorConfig config, boolean doKeys)
throws DatabaseException {
this.doKeys = doKeys;
try {
cursors = new Cursor[conditions.size()];
for (int i = 0; i < cursors.length; i += 1) {
Condition cond = conditions.get(i);
Cursor cursor = cond.openCursor(txn, config);
if (cursor == null) {
/* Leave joinCursor null. */
doClose(null);
return;
}
cursors[i] = cursor;
}
joinCursor = primary.getDatabase().join(cursors, null);
} catch (DatabaseException e) {
/* doClose will throw e. */
doClose(e);
}
}
public V next()
throws DatabaseException {
return next(null);
}
public V next(LockMode lockMode)
throws DatabaseException {
if (joinCursor == null) {
return null;
}
if (doKeys) {
DatabaseEntry key = new DatabaseEntry();
OperationStatus status = joinCursor.getNext(key, lockMode);
if (status == OperationStatus.SUCCESS) {
EntryBinding binding = primary.getKeyBinding();
return (V) binding.entryToObject(key);
}
} else {
DatabaseEntry key = new DatabaseEntry();
DatabaseEntry data = new DatabaseEntry();
OperationStatus status =
joinCursor.getNext(key, data, lockMode);
if (status == OperationStatus.SUCCESS) {
EntityBinding binding = primary.getEntityBinding();
return (V) binding.entryToObject(key, data);
}
}
return null;
}
public Iterator iterator() {
return iterator(null);
}
public Iterator iterator(LockMode lockMode) {
return new BasicIterator(this, lockMode);
}
public void close()
throws DatabaseException {
doClose(null);
}
private void doClose(DatabaseException firstException)
throws DatabaseException {
if (joinCursor != null) {
try {
joinCursor.close();
joinCursor = null;
} catch (DatabaseException e) {
if (firstException == null) {
firstException = e;
}
}
}
for (int i = 0; i < cursors.length; i += 1) {
Cursor cursor = cursors[i];
if (cursor != null) {
try {
cursor.close();
cursors[i] = null;
} catch (DatabaseException e) {
if (firstException == null) {
firstException = e;
}
}
}
}
if (firstException != null) {
throw firstException;
}
}
}
}