Class Transaction

java.lang.Object
io.permazen.core.Transaction
Direct Known Subclasses:
DetachedTransaction

@ThreadSafe public class Transaction extends Object
A Permazen Database transaction.

Note: this is the lower level, core API for Permazen. In most cases this API will only be used indirectly through the higher level Permazen, PermazenTransaction, and PermazenObject APIs.

Methods in this class can be divided into the following categories:

Transaction Meta-Data

Transaction Lifecycle

Object Schema

Object Lifecycle

Object Access

Field Access

Field Change Notifications

Reference Paths

  • followReferencePath() - Find all objects referred to by any element in a given set of starting objects through a specified reference path
  • invertReferencePath() - Find all objects that refer to any element in a given set of target objects through a specified reference path

Listener Sets

All methods returning a set of values return a NavigableSet. The NavigableSets utility class provides methods for the efficient intersection, union, difference, and symmetric difference of NavigableSets containing the same elements and ordering, thereby providing the equivalent of traditional database joins.

Instances of this class are thread safe.

  • Field Details

    • log

      protected final Logger log
  • Method Details

    • getDatabase

      public Database getDatabase()
      Get the database with which this transaction is associated.
      Returns:
      associated database
    • getSchemaBundle

      public SchemaBundle getSchemaBundle()
      Get the database's schema bundle

      This returns all of the schemas currently recorded in the database as seen by this transaction.

      Returns:
      database schema bundle
    • getSchema

      public Schema getSchema()
      Get the database schema associated with this transaction.

      This is the target schema for newly created and migrated objects.

      Returns:
      this transaction's schema
    • getKVTransaction

      public KVTransaction getKVTransaction()
      Get the underlying key/value store transaction.

      Warning: making changes directly to the key/value store directly is not supported. If changes are made, future behavior is undefined.

      Returns:
      the associated key/value transaction
    • addSchema

      public boolean addSchema(SchemaModel schemaModel)
      Manually add the given schema to the database.

      If successful, this transaction's schema bundle will be updated.

      This method is dangerous and should normally not be used except by low-level tools.

      Returns:
      true if the schema was added, false if it was already in the schema bundle
      Throws:
      InvalidSchemaException - if the given schema is invalid (i.e., does not pass validation checks)
      SchemaMismatchException - if the given schema has any explicit storage ID assignments that conflict with other schemas already recorded in the database
      StaleTransactionException - if this transaction is no longer usable
      IllegalArgumentException - if schemaModel is null
    • removeSchema

      public boolean removeSchema(SchemaId schemaId)
      Manually remove the given schema from the database.

      If successful, this transaction's schema bundle will be updated.

      This method is dangerous and should normally not be used except by low-level tools.

      Returns:
      true if the schema was removed, false if it was not found
      Throws:
      StaleTransactionException - if this transaction is no longer usable
      IllegalArgumentException - if schemaId is null
    • commit

      public void commit()
      Commit this transaction.
      Throws:
      StaleTransactionException - if this transaction is no longer usable
      RetryTransactionException - from KVTransaction.commit()
      RollbackOnlyTransactionException - if this instance has been marked rollback only; this instance will be automatically rolled back
    • rollback

      public void rollback()
      Roll back this transaction.

      This method may be invoked at any time, even after a previous invocation of commit() or rollback(), in which case the invocation will be ignored.

    • isOpen

      public boolean isOpen()
      Determine whether this transaction is still open.

      In other words, other methods in this class won't throw StaleTransactionException.

      Returns:
      true if this instance is still usable
    • isReadOnly

      public boolean isReadOnly()
      Determine whether this transaction is read-only.

      This method just invokes KVTransaction.isReadOnly() on the underlying key/value transaction.

      Returns:
      true if this instance is read-only
      Throws:
      StaleTransactionException - if this transaction is no longer usable
    • setReadOnly

      public void setReadOnly(boolean readOnly)
      Enable or disable read-only mode.

      Read-only transactions allow mutations, but all changes are discarded on commit(). Registered Transaction.Callbacks are still processed normally.

      This method just invokes KVTransaction.setReadOnly(boolean) on the underlying key/value transaction.

      Parameters:
      readOnly - read-only setting
      Throws:
      StaleTransactionException - if this transaction is no longer usable
    • isRollbackOnly

      public boolean isRollbackOnly()
      Determine whether this transaction is marked rollback only.
      Returns:
      true if this instance is marked for rollback only
    • setRollbackOnly

      public void setRollbackOnly()
      Mark this transaction for rollback only.

      Once a transaction is marked rollback only, any subsequent commit() attempt will throw an exception.

    • setTimeout

      public void setTimeout(long timeout)
      Change the timeout for this transaction from its default value (optional operation).

      This method just invokes KVTransaction.setTimeout(long) on the underlying key/value transaction.

      Parameters:
      timeout - transaction timeout in milliseconds, or zero for unlimited
      Throws:
      UnsupportedOperationException - if this transaction does not support timeouts
      IllegalArgumentException - if timeout is negative
      StaleTransactionException - if this transaction is no longer usable
    • addCallback

      public void addCallback(Transaction.Callback callback)
      Register a transaction Transaction.Callback to be invoked when this transaction completes.

      Callbacks will be invoked in the order they are registered, but duplicate registrations are ignored (based on comparison via Object.equals(java.lang.Object)).

      Note: if you are using Spring for transaction demarcation (via PermazenTransactionManager), then you may also use Spring's TransactionSynchronizationManager.registerSynchronization() instead of this method.

      Parameters:
      callback - callback to invoke
      Throws:
      IllegalArgumentException - if callback is null
      StaleTransactionException - if this transaction is no longer usable
    • createDetachedTransaction

      public DetachedTransaction createDetachedTransaction()
      Create an in-memory detached transaction.

      The detached transaction will be initialized with the same schema meta-data as this instance but will be otherwise empty (i.e., contain no objects). It can be used as a destination for in-memory copies of objects made via copy().

      The returned DetachedTransaction does not support commit() or rollback(). It can be used indefinitely after this transaction closes, but it must be close()'d when no longer needed to release any associated resources.

      Returns:
      empty in-memory detached transaction with compatible schema information
      See Also:
    • createSnapshotTransaction

      public DetachedTransaction createSnapshotTransaction()
      Create a detached transaction pre-populated with a snapshot of this transaction.

      The returned transaction will have the same schema meta-data and object content as this instance. It will be a mutable transaction, but being detached, changes can't be committed.

      This method requires the underlying key/value transaction to support KVTransaction.readOnlySnapshot(). As with any other information extracted from this transaction, the returned content is not guaranteed to be valid until this transaction has been successfully committed.

      The returned DetachedTransaction does not support commit() or rollback(). It can be used indefinitely after this transaction closes, but it must be close()'d when no longer needed to release any associated resources.

      Returns:
      in-memory copy of this transaction
      Throws:
      UnsupportedOperationException - if they underlying key/value transaction doesn't support KVTransaction.readOnlySnapshot()
    • isDetached

      public boolean isDetached()
      Determine whether this instance is a DetachedTransaction.
      Returns:
      true if this instance is a DetachedTransaction, otherwise false
    • withWeakConsistency

      public void withWeakConsistency(Runnable action)
      Apply weaker transaction consistency while performing the given action, if supported.

      Some key/value implementations support reads with weaker consistency guarantees, where reads generate fewer transaction conflicts in exchange for returning possibly out-of-date information.

      Depending on the key/value implementation, in this mode writes may not be supported; instead, they would generate a IllegalStateException or just be ignored.

      The weaker consistency is only applied for the current thread, and it ends when this method returns.

      This method is for experts only; inappropriate use can result in a corrupted database. In general, after this method returns you should not make any changes to the database that are based on any information read by the action.

      Parameters:
      action - the action to perform
      Throws:
      IllegalArgumentException - if action is null
    • create

      public boolean create(ObjId id)
      Create a new object with the given object ID, if it doesn't already exist.

      If the object already exists, nothing happens.

      If the object doesn't already exist, all fields are set to their default values and the object's schema is set to the schema associated with this transaction.

      Parameters:
      id - object ID
      Returns:
      true if the object did not exist and was created, false if the object already existed
      Throws:
      UnknownTypeException - if id does not correspond to an object type in this transaction's schema
      IllegalArgumentException - if id is null
      StaleTransactionException - if this transaction is no longer usable
    • create

      public boolean create(ObjId id, SchemaId schemaId)
      Create a new object with the given object ID, if it doesn't already exist. If it does exist, nothing happens.

      If the object doesn't already exist, the object's schema is set to the specified schema and all fields are set to their default values.

      Parameters:
      id - object ID
      schemaId - the schema to use for the newly created object
      Returns:
      true if the object did not exist and was created, false if the object already existed
      Throws:
      UnknownTypeException - if id does not correspond to a known object type in the specified schema
      InvalidSchemaException - if schemaId is invalid
      IllegalArgumentException - if id or schemaId is null
      StaleTransactionException - if this transaction is no longer usable
    • create

      public ObjId create(String typeName)
      Create a new object with a randomly assigned object ID and having the given type.

      All fields will be set to their default values. The object's schema will be set to the associated with this transaction.

      Parameters:
      typeName - object type name
      Returns:
      object id of newly created object
      Throws:
      UnknownTypeException - if typeName does not correspond to a known object type in this transaction's schema
      IllegalArgumentException - if typeName is null
      StaleTransactionException - if this transaction is no longer usable
    • create

      public ObjId create(String typeName, SchemaId schemaId)
      Create a new object with a randomly assigned object ID and having the given type and schema.

      All fields will be set to their default values. The object's schema will be set to the specified schema.

      Parameters:
      typeName - object type name
      schemaId - ID of the schema to use for the newly created object
      Returns:
      object id of newly created object
      Throws:
      UnknownTypeException - if typeName does not correspond to a known object type in the specified schema
      InvalidSchemaException - if schemaId is invalid
      IllegalArgumentException - if id or schemaId is null
      StaleTransactionException - if this transaction is no longer usable
    • generateId

      public ObjId generateId(String typeName)
      Generate a random, unused ObjId for the given object type.
      Parameters:
      typeName - object type name
      Returns:
      random unassigned object id
      Throws:
      UnknownTypeException - if typeName does not correspond to any known object type
      IllegalArgumentException - if typeName is null
      StaleTransactionException - if this transaction is no longer usable
    • delete

      public boolean delete(ObjId id)
      Delete an object. Does nothing if object does not exist (e.g., has already been deleted).

      This method does not change the object's schema if it is different from this transaction's schema.

      Secondary Deletions

      Deleting an object can trigger additional secondary deletions. Specifically, (a) if the object contains reference fields with forward delete cascade enabled, any objects referred to through those fields will also be deleted, and (b) if the object is referred to by any other objects through fields configured for DeleteAction.DELETE, those referring objects will be deleted.

      In any case, deletions occur one at a time, and only when an object is actually deleted are any associated secondary deletions added to an internal deletion queue. However, the order in which objects on this deletion queue are processed is unspecified. For an example of where this ordering matters, consider an object A referring to objects B and C with delete cascading references, where B also refers to C with a DeleteAction.EXCEPTION reference. Then if A is deleted, it's indeterminate whether a ReferencedObjectException will be thrown, as that depends on whether B or C is deleted first (with the answer being, respectively, no and yes).

      Parameters:
      id - object ID of the object to delete
      Returns:
      true if object was found and deleted, false if object was not found, or if id specifies an unknown object type
      Throws:
      ReferencedObjectException - if the object is referenced by some other object through a reference field configured for DeleteAction.EXCEPTION
      IllegalArgumentException - if id is null
      StaleTransactionException - if this transaction is no longer usable
    • exists

      public boolean exists(ObjId id)
      Determine if an object exists.

      This method does not change the object's schema if it exists and is different from this transaction's schema.

      Parameters:
      id - object ID of the object to find
      Returns:
      true if object was found, false if object was not found, or if id specifies an unknown object type
      Throws:
      StaleTransactionException - if this transaction is no longer usable
      IllegalArgumentException - if id is null
    • copy

      public boolean copy(ObjId source, Transaction dest, boolean migrateSchema, boolean notifyListeners, ObjIdMap<ReferenceField> deletedAssignments, ObjIdMap<ObjId> objectIdMap)
      Copy an object into a (possibly different) transaction.

      This copies the object, including all of its field data, to dest. If the object already exists in dest, the existing copy is completely replaced, otherwise it will be created automatically.

      Object Schemas

      In order to perform the copy, source's schema must also already exist in dest. If it does not, a SchemaMismatchException is thrown.

      But first, if migrateSchema(io.permazen.core.ObjId) is true, source's schema is first migrated to match this transaction, if needed.

      Notifications

      CreateListeners in the destination transaction will be notified if the target object must be created, and field change listeners in the destination transaction will be notified for non-trivial changes to the target object's fields as each field is copied. These notifications may be disabled by setting notifyListeners to false.

      SchemaChangeListeners in this transaction are always notified if/when source is migrated due to migrateSchema(io.permazen.core.ObjId) being true.

      Deleted Assignments

      If a reference field configured to disallow deleted assignments is copied, but the referenced object does not exist in dest, then a DeletedObjectException is thrown and no copy is performed. However, this presents an impossible chicken-and-egg situation when multiple objects need to be copied and there are cycles in the graph of references between objects.

      To handle that situation, if deletedAssignments is non-null, then instead of triggering an exception, illegal references to deleted objects are collected in deletedAssignments; each entry maps a deleted object to (some) referring field in the copied object. This lets the caller to decide what to do about them.

      Object ID Remapping

      By default, the ObjId of source is also the ObjId of the target object in dest, and all reference fields are copied as-is. The optional objectIdMap allows the caller to remap these ObjIds arbitrarily, as long as the implied object types are the same. If objectIdMap maps an ObjId to null, then a new, unused ObjId in dest will be chosen and updated in objectIdMap.

      Return Value

      If dest is this instance, and the source is not remapped, no fields are changed and false is returned, otherwise true is returned. Even if false is returned, a schema migration can still occur (if migrateSchema is true), and deleted assignment checks are still applied.

      Deadlock Avoidance

      If two threads attempt to copy objects between the same two transactions at the same time but in opposite directions, deadlock can result.

      Parameters:
      source - object ID of the source object in this transaction
      dest - destination for the copy of source (possibly this transaction)
      migrateSchema - whether to migrate source's schema (if necessary) to match this transaction prior to the copy
      notifyListeners - whether to notify CreateListeners and field change listeners in dest
      deletedAssignments - if not null, where to collect assignments to deleted objects instead of throwing DeletedObjectExceptions; the map key is the deleted object and the map value is some referring field
      objectIdMap - if not null, a remapping of object ID's in this transaction to object ID's in dest
      Returns:
      false if the target object already existed in dest, true if it was newly created
      Throws:
      DeletedObjectException - if no object with ID equal to source exists in this transaction
      DeletedObjectException - if deletedAssignments is null, and a non-null reference field in source that disallows deleted assignments contains a reference to an object that does not exist in dest
      UnknownTypeException - if source or an ObjId in objectIdMap specifies an unknown object type
      IllegalArgumentException - if objectIdMap maps source to a different object type
      IllegalArgumentException - if objectIdMap maps the value of a reference field to an incompatible object type
      IllegalArgumentException - if any parameter is null
      StaleTransactionException - if this transaction or dest is no longer usable
      SchemaMismatchException - if source's schema does not exist in dest
      SchemaMismatchException - if the object's ID in dest does not match the assigned storage ID for its type
      TypeNotInSchemaException - migrateSchema is true and the object's schema could not be migrated because the object's type does not exist in this transaction's schema
    • addCreateListener

      public void addCreateListener(CreateListener listener)
      Add a CreateListener to this transaction.
      Parameters:
      listener - the listener to add
      Throws:
      IllegalArgumentException - if listener is null
      StaleTransactionException - if this transaction is no longer usable
      UnsupportedOperationException - if setListeners() has been invoked on this instance
    • removeCreateListener

      public void removeCreateListener(CreateListener listener)
      Remove an CreateListener from this transaction.
      Parameters:
      listener - the listener to remove
      Throws:
      StaleTransactionException - if this transaction is no longer usable
      IllegalArgumentException - if listener is null
      UnsupportedOperationException - if setListeners() has been invoked on this instance
    • addDeleteListener

      public void addDeleteListener(DeleteListener listener)
      Add a DeleteListener to this transaction.
      Parameters:
      listener - the listener to add
      Throws:
      IllegalArgumentException - if listener is null
      StaleTransactionException - if this transaction is no longer usable
      UnsupportedOperationException - if setListeners() has been invoked on this instance
    • removeDeleteListener

      public void removeDeleteListener(DeleteListener listener)
      Remove an DeleteListener from this transaction.
      Parameters:
      listener - the listener to remove
      Throws:
      StaleTransactionException - if this transaction is no longer usable
      IllegalArgumentException - if listener is null
      UnsupportedOperationException - if setListeners() has been invoked on this instance
    • getObjType

      public ObjType getObjType(ObjId id)
      Get the given object's ObjType.
      Parameters:
      id - object id
      Returns:
      object's object type
      Throws:
      DeletedObjectException - if no such object exists
      UnknownTypeException - if id specifies an unknown object type
      StaleTransactionException - if this transaction is no longer usable
      IllegalArgumentException - if id is null
    • getTypeName

      public String getTypeName(int storageId)
      Get the object type assigned to the given storage ID.
      Parameters:
      storageId - object type storage ID
      Returns:
      the corresponding object type name
      Throws:
      UnknownTypeException - if storageId specifies an unknown object type
    • migrateSchema

      public boolean migrateSchema(ObjId id)
      Migrate the specified object, if necessary, to the schema associated with this transaction.

      If a schema change occurs, any registered SchemaChangeListeners will be notified prior to this method returning.

      Parameters:
      id - object ID of the object to migrate
      Returns:
      true if the object's schema was migrated, false if it's schema already matched
      Throws:
      StaleTransactionException - if this transaction is no longer usable
      DeletedObjectException - if no object with ID equal to id is found
      IllegalArgumentException - if id is null
      TypeNotInSchemaException - if the object's type is not defined in this transaction's schema
    • querySchemaIndex

      public CoreIndex1<Integer,ObjId> querySchemaIndex()
      Query objects by schema.

      This returns all objects in the database grouped by Schema. The keys in the returned map are schema indexes; use getSchemaBundle() and then SchemaBundle.getSchemasBySchemaIndex() to access the corresponding Schemas.

      Returns:
      read-only, real-time view of all database objects grouped by schema
      Throws:
      StaleTransactionException - if this transaction is no longer usable
    • addSchemaChangeListener

      public void addSchemaChangeListener(SchemaChangeListener listener)
      Add an SchemaChangeListener to this transaction.
      Parameters:
      listener - the listener to add
      Throws:
      IllegalArgumentException - if listener is null
      StaleTransactionException - if this transaction is no longer usable
      UnsupportedOperationException - if setListeners() has been invoked on this instance
    • removeSchemaChangeListener

      public void removeSchemaChangeListener(SchemaChangeListener listener)
      Remove an SchemaChangeListener from this transaction.
      Parameters:
      listener - the listener to remove
      Throws:
      StaleTransactionException - if this transaction is no longer usable
      IllegalArgumentException - if listener is null
      UnsupportedOperationException - if setListeners() has been invoked on this instance
    • getAll

      public NavigableSet<ObjId> getAll()
      Get all objects in the database.

      The returned set includes objects from all schemas. Use querySchemaIndex() to access objects with a specific schema.

      The returned set is mutable, with the exception that add() is not supported. Deleting an element results in deleting the corresponding object.

      Returns:
      a live view of all database objects
      Throws:
      StaleTransactionException - if this transaction is no longer usable
      See Also:
    • getAll

      public NavigableSet<ObjId> getAll(String typeName)
      Get all objects whose object type has the specified name.

      The returned set includes objects of the specified type from all schemas in the database. The returned set is mutable, with the exception that add() is not supported. Deleting an element results in deleting the corresponding object.

      Parameters:
      typeName - object type name
      Returns:
      a live view of all database objects having the specified type
      Throws:
      UnknownTypeException - if typeName does not correspond to any known object type
      IllegalArgumentException - if typeName is null
      StaleTransactionException - if this transaction is no longer usable
      See Also:
    • readSimpleField

      public Object readSimpleField(ObjId id, String name, boolean migrateSchema)
      Read the value of a SimpleField from an object, optionally migrating the object's schema.

      If migrateSchema is true, the object will be automatically migrated to match the schema associated with this transaction, if necessary, prior to reading the field.

      Parameters:
      id - object ID of the object
      name - field name
      migrateSchema - true to first automatically migrate the object's schema, false to not change it
      Returns:
      value of the field in the object
      Throws:
      StaleTransactionException - if this transaction is no longer usable
      DeletedObjectException - if no object with ID equal to id is found
      UnknownTypeException - if id specifies an unknown object type
      UnknownFieldException - if no SimpleField corresponding to name exists in the object
      IllegalArgumentException - if id is null
      TypeNotInSchemaException - migrateSchema is true and the object's schema could not be migrated because the object's type does not exist in this transaction's schema
    • writeSimpleField

      public void writeSimpleField(ObjId id, String name, Object value, boolean migrateSchema)
      Change the value of a SimpleField in an object, optionally updating the object's schema.

      If migrateSchema is true, the object will be automatically migrated to match the schema associated with this transaction, if necessary, prior to writing the field.

      Parameters:
      id - object ID of the object
      name - field name
      value - new value for the field
      migrateSchema - true to first automatically migrate the object's schema, false to not change it
      Throws:
      StaleTransactionException - if this transaction is no longer usable
      DeletedObjectException - if no object with ID equal to id is found
      UnknownTypeException - if id specifies an unknown object type
      UnknownFieldException - if no SimpleField corresponding to name exists in the object
      TypeNotInSchemaException - migrateSchema is true and the object's schema could not be migrated because the object's type does not exist in this transaction's schema
      IllegalArgumentException - if value is not an appropriate value for the field
      IllegalArgumentException - if id is null
    • getTypeDescription

      public String getTypeDescription(ObjId id)
      Get a description of the given object's type.
      Parameters:
      id - object ID
      Returns:
      textual description of the specified object's type
      Throws:
      IllegalArgumentException - if id is null
    • getObjDescription

      public String getObjDescription(ObjId id)
      Get a description of the given object.
      Parameters:
      id - object ID
      Returns:
      textual description of the specified object
      Throws:
      IllegalArgumentException - if id is null
    • getFieldDescription

      public String getFieldDescription(ObjId id, String name)
      Get a description of the given object field.
      Parameters:
      id - object ID
      name - field's name
      Returns:
      textual description of the specified object field
      Throws:
      IllegalArgumentException - if id or name is null
    • readCounterField

      public long readCounterField(ObjId id, String name, boolean migrateSchema)
      Read the value of a CounterField from an object, optionally updating the object's schema.

      If migrateSchema is true, the object will be automatically migrated to match the schema associated with this transaction, if necessary, prior to reading the field.

      Parameters:
      id - object ID of the object
      name - field name
      migrateSchema - true to first automatically migrate the object's schema, false to not change it
      Returns:
      value of the counter in the object
      Throws:
      StaleTransactionException - if this transaction is no longer usable
      DeletedObjectException - if no object with ID equal to id is found
      UnknownTypeException - if id specifies an unknown object type
      UnknownFieldException - if no CounterField corresponding to name exists in the object
      TypeNotInSchemaException - migrateSchema is true and the object's schema could not be migrated because the object's type does not exist in this transaction's schema
      IllegalArgumentException - if id is null
    • writeCounterField

      public void writeCounterField(ObjId id, String name, long value, boolean migrateSchema)
      Set the value of a CounterField in an object, optionally updating the object's schema.

      If migrateSchema is true, the object will be automatically migrated to match the schema associated with this transaction, if necessary, prior to writing the field.

      Parameters:
      id - object ID of the object
      name - field name
      value - new counter value
      migrateSchema - true to first automatically migrate the object's schema, false to not change it
      Throws:
      StaleTransactionException - if this transaction is no longer usable
      DeletedObjectException - if no object with ID equal to id is found
      UnknownTypeException - if id specifies an unknown object type
      UnknownFieldException - if no CounterField corresponding to name exists in the object
      TypeNotInSchemaException - migrateSchema is true and the object's schema could not be migrated because the object's type does not exist in this transaction's schema
      IllegalArgumentException - if id is null
    • adjustCounterField

      public void adjustCounterField(ObjId id, String name, long offset, boolean migrateSchema)
      Adjust the value of a CounterField in an object by some amount, optionally updating the object's schema.

      If migrateSchema is true, the object will be automatically migrated to match the schema associated with this transaction, if necessary, prior to adjusting the field.

      Parameters:
      id - object ID of the object
      name - field name
      offset - offset value to add to counter value
      migrateSchema - true to first automatically migrate the object's schema, false to not change it
      Throws:
      StaleTransactionException - if this transaction is no longer usable
      DeletedObjectException - if no object with ID equal to id is found
      UnknownTypeException - if id specifies an unknown object type
      UnknownFieldException - if no CounterField corresponding to name exists in the object
      TypeNotInSchemaException - migrateSchema is true and the object's schema could not be migrated because the object's type does not exist in this transaction's schema
      IllegalArgumentException - if id is null
    • readSetField

      public NavigableSet<?> readSetField(ObjId id, String name, boolean migrateSchema)
      Access a SetField associated with an object, optionally updating the object's schema.

      If migrateSchema is true, the object will be automatically migrated to match the schema associated with this transaction, if necessary.

      Parameters:
      id - object ID of the object
      name - field name
      migrateSchema - true to first automatically migrate the object's schema, false to not change it
      Returns:
      set field value
      Throws:
      StaleTransactionException - if this transaction is no longer usable
      DeletedObjectException - if no object with ID equal to id is found
      UnknownTypeException - if id specifies an unknown object type
      UnknownFieldException - if no SetField corresponding to name exists in the object
      TypeNotInSchemaException - migrateSchema is true and the object's schema could not be migrated because the object's type does not exist in this transaction's schema
      IllegalArgumentException - if id is null
    • readListField

      public List<?> readListField(ObjId id, String name, boolean migrateSchema)
      Access a ListField associated with an object, optionally updating the object's schema.

      If migrateSchema is true, the object will be automatically migrated to match the schema associated with this transaction, if necessary.

      Parameters:
      id - object ID of the object
      name - field name
      migrateSchema - true to first automatically migrate the object's schema, false to not change it
      Returns:
      list field value
      Throws:
      StaleTransactionException - if this transaction is no longer usable
      DeletedObjectException - if no object with ID equal to id is found
      UnknownTypeException - if id specifies an unknown object type
      UnknownFieldException - if no ListField corresponding to name exists in the object
      TypeNotInSchemaException - migrateSchema is true and the object's schema could not be migrated because the object's type does not exist in this transaction's schema
      IllegalArgumentException - if id is null
    • readMapField

      public NavigableMap<?,?> readMapField(ObjId id, String name, boolean migrateSchema)
      Access a MapField associated with an object, optionally updating the object's schema.

      If migrateSchema is true, the object will be automatically migrated to match the schema associated with this transaction, if necessary.

      Parameters:
      id - object ID of the object
      name - field name
      migrateSchema - true to first automatically migrate the object's schema, false to not change it
      Returns:
      map field value
      Throws:
      StaleTransactionException - if this transaction is no longer usable
      DeletedObjectException - if no object with ID equal to id is found
      UnknownTypeException - if id specifies an unknown object type
      UnknownFieldException - if no MapField corresponding to name exists in the object
      TypeNotInSchemaException - migrateSchema is true and the object's schema could not be migrated because the object's type does not exist in this transaction's schema
      IllegalArgumentException - if id is null
    • getKey

      public byte[] getKey(ObjId id)
      Get the byte[] key prefix in the underlying key/value store corresponding to the specified object.

      Notes:

      • This method does not check whether the object actually exists.
      • Objects utilize multiple keys; the return value is the common prefix of all such keys.
      • The KVDatabase should not be modified directly, otherwise behavior is undefined
      Parameters:
      id - object ID
      Returns:
      the KVDatabase key corresponding to id
      Throws:
      UnknownTypeException - if id specifies an unknown object type
      IllegalArgumentException - if id is null
      See Also:
    • addSimpleFieldChangeListener

      public void addSimpleFieldChangeListener(int storageId, int[] path, KeyRanges[] filters, SimpleFieldChangeListener listener)
      Monitor for changes within this transaction of the value of the given field, as seen through a path of references.

      When the specified field is changed in some object T, a listener notification will be delivered for each object R that refers to object T through the specified path of reference fields (if path is empty, then R = T). Notifications are delivered at the end of the mutation operation just prior to returning to the caller. If the listener method performs additional mutation(s) which are themselves being listened for, those notifications will also be delivered prior to the returning to the original caller. Therefore, care must be taken to avoid change notification dependency loops when listeners can themselves mutate fields, to avoid infinite loops.

      The filters, if any, are applied to ObjId's at the corresponding steps in the path: filters[0] is applied to the starting objects R, filters[1] is applied to the objects reachable from R via path[0], etc., up to filters[path.length], which applies to the target objects T. filters or any element therein may be null to indicate no restriction.

      A referring object R may refer to the changed object T through more than one sequence of objects matching path; if so, R will still appear only once in the NavigableSet provided to the listener (this is of course required by set semantics).

      Although the reference back-tracking algorithm does consolidate multiple paths between the same two objects, be careful to avoid an explosion of notifications when objects in the path have a high degree of fan-in.

      When a ReferenceField in path also happens to be the field being changed, then there is ambiguity about how the set of referring objects is calculated: does it use the field's value before or after the change? This API guarantees that the answer is "after the change"; however, if another listener on the same field is invoked before listener and mutates any reference field(s) in path, then whether that additional change is also be included in the calculation is undefined.

      Therefore, for consistency, avoid changing any ReferenceField from within a listener callback when that field is also in some other listener's reference path, and both listeners are watching the same field.

      Permazen allows a field's type to change across schemas, therefore some schema may exist in which the field associated with storageId is not a SimpleField. In such cases, listener will receive notifications about those changes if it also happens to implement the other listener interface. In other words, this method delegates directly to addFieldChangeListener().

      Parameters:
      storageId - storage ID of the field to monitor
      path - path of reference fields (represented by storage IDs) through which to monitor field; negated values denote inverse traversal of the field
      filters - if not null, an array of length path.length + 1 containing optional filters to be applied to object ID's after the corresponding steps in the path
      listener - callback for notifications on changes in value
      Throws:
      UnknownFieldException - if path contains a storage ID that does not correspond to a ReferenceField
      IllegalArgumentException - if path or listener is null
      StaleTransactionException - if this transaction is no longer usable
      UnsupportedOperationException - if setListeners() has been invoked on this instance
    • addSetFieldChangeListener

      public void addSetFieldChangeListener(int storageId, int[] path, KeyRanges[] filters, SetFieldChangeListener listener)
      Monitor for changes within this transaction to the specified SetField as seen through a path of references.

      See addSimpleFieldChangeListener() for details on how notifications are delivered.

      Permazen allows a field's type to change across schemas, therefore some schema may exist in which the field associated with storageId is not a SetField. In such cases, listener will receive notifications about those changes if it also happens to implement the other listener interface. In other words, this method delegates directly to addFieldChangeListener().

      Parameters:
      storageId - storage ID of the field to monitor
      path - path of reference fields (represented by storage IDs) through which to monitor field; negated values denote inverse traversal of the field
      filters - if not null, an array of length path.length + 1 containing optional filters to be applied to object ID's after the corresponding steps in the path
      listener - callback for notifications on changes in value
      Throws:
      UnknownFieldException - if path contains a storage ID that does not correspond to a ReferenceField
      IllegalArgumentException - if path or listener is null
      StaleTransactionException - if this transaction is no longer usable
      UnsupportedOperationException - if setListeners() has been invoked on this instance
    • addListFieldChangeListener

      public void addListFieldChangeListener(int storageId, int[] path, KeyRanges[] filters, ListFieldChangeListener listener)
      Monitor for changes within this transaction to the specified ListField as seen through a path of references.

      See addSimpleFieldChangeListener() for details on how notifications are delivered.

      Permazen allows a field's type to change across schemas, therefore some schema may exist in which the field associated with storageId is not a ListField. In such cases, listener will receive notifications about those changes if it also happens to implement the other listener interface. In other words, this method delegates directly to addFieldChangeListener().

      Parameters:
      storageId - storage ID of the field to monitor
      path - path of reference fields (represented by storage IDs) through which to monitor field; negated values denote inverse traversal of the field
      filters - if not null, an array of length path.length + 1 containing optional filters to be applied to object ID's after the corresponding steps in the path
      listener - callback for notifications on changes in value
      Throws:
      UnknownFieldException - if path contains a storage ID that does not correspond to a ReferenceField
      IllegalArgumentException - if path or listener is null
      StaleTransactionException - if this transaction is no longer usable
      UnsupportedOperationException - if setListeners() has been invoked on this instance
    • addMapFieldChangeListener

      public void addMapFieldChangeListener(int storageId, int[] path, KeyRanges[] filters, MapFieldChangeListener listener)
      Monitor for changes within this transaction to the specified MapField as seen through a path of references.

      See addSimpleFieldChangeListener() for details on how notifications are delivered.

      Permazen allows a field's type to change across schemas, therefore some schema may exist in which the field associated with storageId is not a MapField. In such cases, listener will receive notifications about those changes if it also happens to implement the other listener interface. In other words, this method delegates directly to addFieldChangeListener().

      Parameters:
      storageId - storage ID of the field to monitor
      path - path of reference fields (represented by storage IDs) through which to monitor field; negated values denote inverse traversal of the field
      filters - if not null, an array of length path.length + 1 containing optional filters to be applied to object ID's after the corresponding steps in the path
      listener - callback for notifications on changes in value
      Throws:
      UnknownFieldException - if path contains a storage ID that does not correspond to a ReferenceField
      IllegalArgumentException - if path or listener is null
      StaleTransactionException - if this transaction is no longer usable
      UnsupportedOperationException - if setListeners() has been invoked on this instance
    • addFieldChangeListener

      public void addFieldChangeListener(int storageId, int[] path, KeyRanges[] filters, Object listener)
      Monitor for changes within this transaction to the specified Field as seen through a path of references.

      See addSimpleFieldChangeListener() for details on how notifications are delivered.

      Permazen allows a field's type to change across schemas, therefore in different schemas the specified field may have different types. The listener will receive notifications about a field change if it implements the interface appropriate for the field's current type (i.e., SimpleFieldChangeListener, ListFieldChangeListener, SetFieldChangeListener, or MapFieldChangeListener) at the time of the change.

      Parameters:
      storageId - storage ID of the field to monitor
      path - path of reference fields (represented by storage IDs) through which to monitor field; negated values denote inverse traversal of the field
      filters - if not null, an array of length path.length + 1 containing optional filters to be applied to object ID's after the corresponding steps in the path
      listener - callback for notifications on changes in value
      Throws:
      UnknownFieldException - if path contains a storage ID that does not correspond to a ReferenceField
      IllegalArgumentException - if path or listener is null
      StaleTransactionException - if this transaction is no longer usable
      UnsupportedOperationException - if setListeners() has been invoked on this instance
    • removeSimpleFieldChangeListener

      public void removeSimpleFieldChangeListener(int storageId, int[] path, KeyRanges[] filters, SimpleFieldChangeListener listener)
      Remove a field monitor previously added via addSimpleFieldChangeListener() (or addFieldChangeListener()).
      Parameters:
      storageId - storage ID of the field to no longer monitor
      path - path of reference fields (represented by storage IDs) through which to monitor field; negated values denote inverse traversal of the field
      filters - if not null, an array of length path.length + 1 containing optional filters to be applied to object ID's after the corresponding steps in the path
      listener - callback for notifications on changes in value
      Throws:
      UnknownFieldException - if path contains a storage ID that does not correspond to a ReferenceField
      IllegalArgumentException - if path or listener is null
      StaleTransactionException - if this transaction is no longer usable
      UnsupportedOperationException - if setListeners() has been invoked on this instance
    • removeSetFieldChangeListener

      public void removeSetFieldChangeListener(int storageId, int[] path, KeyRanges[] filters, SetFieldChangeListener listener)
      Remove a field monitor previously added via addSetFieldChangeListener() (or addFieldChangeListener()).
      Parameters:
      storageId - storage ID of the field to no longer monitor
      path - path of reference fields (represented by storage IDs) through which to monitor field; negated values denote inverse traversal of the field
      filters - if not null, an array of length path.length + 1 containing optional filters to be applied to object ID's after the corresponding steps in the path
      listener - callback for notifications on changes in value
      Throws:
      UnknownFieldException - if path contains a storage ID that does not correspond to a ReferenceField
      IllegalArgumentException - if path or listener is null
      StaleTransactionException - if this transaction is no longer usable
      UnsupportedOperationException - if setListeners() has been invoked on this instance
    • removeListFieldChangeListener

      public void removeListFieldChangeListener(int storageId, int[] path, KeyRanges[] filters, ListFieldChangeListener listener)
      Remove a field monitor previously added via addListFieldChangeListener() (or addFieldChangeListener()).
      Parameters:
      storageId - storage ID of the field to no longer monitor
      path - path of reference fields (represented by storage IDs) through which to monitor field; negated values denote inverse traversal of the field
      filters - if not null, an array of length path.length + 1 containing optional filters to be applied to object ID's after the corresponding steps in the path
      listener - callback for notifications on changes in value
      Throws:
      UnknownFieldException - if path contains a storage ID that does not correspond to a ReferenceField
      IllegalArgumentException - if path or listener is null
      StaleTransactionException - if this transaction is no longer usable
      UnsupportedOperationException - if setListeners() has been invoked on this instance
    • removeMapFieldChangeListener

      public void removeMapFieldChangeListener(int storageId, int[] path, KeyRanges[] filters, MapFieldChangeListener listener)
      Remove a field monitor previously added via addMapFieldChangeListener() (or addFieldChangeListener()).
      Parameters:
      storageId - storage ID of the field to no longer monitor
      path - path of reference fields (represented by storage IDs) through which to monitor field; negated values denote inverse traversal of the field
      filters - if not null, an array of length path.length + 1 containing optional filters to be applied to object ID's after the corresponding steps in the path
      listener - callback for notifications on changes in value
      Throws:
      UnknownFieldException - if path contains a storage ID that does not correspond to a ReferenceField
      IllegalArgumentException - if path or listener is null
      StaleTransactionException - if this transaction is no longer usable
      UnsupportedOperationException - if setListeners() has been invoked on this instance
    • removeFieldChangeListener

      public void removeFieldChangeListener(int storageId, int[] path, KeyRanges[] filters, Object listener)
      Parameters:
      storageId - storage ID of the field to no longer monitor
      path - path of reference fields (represented by storage IDs) through which to monitor field; negated values denote inverse traversal of the field
      filters - if not null, an array of length path.length + 1 containing optional filters to be applied to object ID's after the corresponding steps in the path
      listener - callback for notifications on changes in value
      Throws:
      UnknownFieldException - if path contains a storage ID that does not correspond to a ReferenceField
      IllegalArgumentException - if path or listener is null
      StaleTransactionException - if this transaction is no longer usable
      UnsupportedOperationException - if setListeners() has been invoked on this instance
    • followReferencePath

      public NavigableSet<ObjId> followReferencePath(Stream<? extends ObjId> startObjects, int[] path, KeyRanges[] filters)
      Find all objects referred to by any object in the given start set through the specified path of references.

      Each value in path represents a reference field traversed in the path to some target object(s); if a value in path is negated, then the field is traversed in the inverse direction.

      If path is empty, then the contents of startObjects is returned.

      The filters, if any, are applied to ObjId's at the corresponding steps in the path: filters[0] is applied to startObjects, filters[1] is applied to the objects reachable from startObjects via path[0], etc., up to filters[path.length], which applies to the final target objects. filters or any element therein may be null to indicate no restriction.

      Parameters:
      startObjects - starting objects
      path - path of zero or more reference fields (represented by storage IDs) through which to reach the target objects; negated values denote an inverse traversal of the corresponding reference field
      filters - if not null, an array of length path.length + 1 containing optional filters to be applied to object ID's after the corresponding steps in the path
      Returns:
      read-only set of objects referred to by the startObjects via path restricted by filters
      Throws:
      UnknownFieldException - if path contains a storage ID that does not correspond to a ReferenceField
      IllegalArgumentException - if startObjects or path is null
      IllegalArgumentException - if filters is not null and does not have length path.length + 1
      StaleTransactionException - if this transaction is no longer usable
    • invertReferencePath

      public NavigableSet<ObjId> invertReferencePath(int[] path, KeyRanges[] filters, Stream<? extends ObjId> targetObjects)
      Find all objects that refer to any object in the given target set through the specified path of references.

      Each value in path represents a reference field traversed in the path to the target object(s); if a value in path is negated, then the field is traversed in the inverse direction.

      If path is empty, then the contents of targetObjects is returned.

      The filters, if any, are applied to ObjId's at the corresponding steps in the path: filters[path.length] is applied to targetObjects, filters[path.length - 1] is applied to the objects referring to targetObjects via path[path.length - 1], etc., down to filters[0], which applies to the objects at the start of the path being inverted. filters or any element therein may be null to indicate no restriction.

      Parameters:
      path - path of zero or more reference fields (represented by storage IDs) through which to reach the target objects; negated values denote an inverse traversal of the corresponding reference field
      filters - if not null, an array of length path.length + 1 containing optional filters to be applied to object ID's after the corresponding steps in the path
      targetObjects - target objects
      Returns:
      read-only set of objects that refer to the targetObjects via path restricted by filters
      Throws:
      UnknownFieldException - if path contains a storage ID that does not correspond to a ReferenceField
      IllegalArgumentException - if targetObjects or path is null
      IllegalArgumentException - if filters is not null and does not have length path.length + 1
      StaleTransactionException - if this transaction is no longer usable
    • queryIndex

      public AbstractCoreIndex<ObjId> queryIndex(int storageId)
      Query any simple or composite index.

      The returned view will have type CoreIndex1, CoreIndex2, CoreIndex3, etc., corresponding to the number of fields in the index.

      Parameters:
      storageId - the storage ID associated with the field (if simple) or composite index
      Returns:
      read-only, real-time view of the index
      Throws:
      UnknownIndexException - if no such index exists
      StaleTransactionException - if this transaction is no longer usable
    • querySimpleIndex

      public CoreIndex1<?,ObjId> querySimpleIndex(int storageId)
      Query a SimpleIndex to find all values stored in some field and, for each value, the set of all objects having that value in the field.

      Use this method to acquire a plain CoreIndex1 on complex sub-fields.

      Parameters:
      storageId - the storage ID associated with the field
      Returns:
      read-only, real-time view of the index
      Throws:
      UnknownIndexException - if no such index exists
      StaleTransactionException - if this transaction is no longer usable
    • queryListElementIndex

      public CoreIndex2<?,ObjId,Integer> queryListElementIndex(int storageId)
      Query a ListElementIndex to find all values stored in some list field and, for each value, the set of all objects having that value as an element in the list and the corresponding list index.
      Parameters:
      storageId - the storage ID associated with the field
      Returns:
      read-only, real-time view of the index
      Throws:
      UnknownIndexException - if no such index exists
      StaleTransactionException - if this transaction is no longer usable
    • queryMapValueIndex

      public CoreIndex2<?,ObjId,?> queryMapValueIndex(int storageId)
      Query a MapValueIndex to find all values stored in some map field and, for each value, the set of all objects having that value as a value in the map and the corresponding key.
      Parameters:
      storageId - the storage ID associated with the field
      Returns:
      read-only, real-time view of the index
      Throws:
      UnknownIndexException - if no such index exists
      StaleTransactionException - if this transaction is no longer usable
    • queryCompositeIndex2

      public CoreIndex2<?,?,ObjId> queryCompositeIndex2(int storageId)
      Query a CompositeIndex on two fields to find all value tuples stored in the corresponding field tuple and, for each value tuple, the set of all objects having those values in those fields.
      Parameters:
      storageId - the storage ID associated with the composite index
      Returns:
      read-only, real-time view of the index
      Throws:
      UnknownIndexException - if no such index exists
      UnknownIndexException - if the index is not on two fields
      StaleTransactionException - if this transaction is no longer usable
    • queryCompositeIndex3

      public CoreIndex3<?,?,?,ObjId> queryCompositeIndex3(int storageId)
      Query a CompositeIndex on three fields to find all value tuples stored in the corresponding field tuple and, for each value tuple, the set of all objects having those values in those fields.
      Parameters:
      storageId - the storage ID associated with the composite index
      Returns:
      read-only, real-time view of the index
      Throws:
      UnknownIndexException - if no such index exists
      UnknownIndexException - if the index is not on three fields
      StaleTransactionException - if this transaction is no longer usable
    • queryCompositeIndex4

      public CoreIndex4<?,?,?,?,ObjId> queryCompositeIndex4(int storageId)
      Query a CompositeIndex on four fields to find all value tuples stored in the corresponding field tuple and, for each value tuple, the set of all objects having those values in those fields.
      Parameters:
      storageId - the storage ID associated with the composite index
      Returns:
      read-only, real-time view of the index
      Throws:
      UnknownIndexException - if no such index exists
      UnknownIndexException - if the index is not on four fields
      StaleTransactionException - if this transaction is no longer usable
    • snapshotListeners

      public Transaction.ListenerSet snapshotListeners()
      Create a read-only snapshot of all (CreateListeners, DeleteListeners, SchemaChangeListeners, SimpleFieldChangeListeners, SetFieldChangeListeners, ListFieldChangeListeners, and MapFieldChangeListeners currently registered on this instance.

      The snapshot can be applied to other transactions having compatible schemas via setListeners(). Use of a Transaction.ListenerSet also allows certain internal optimizations.

      Returns:
      snapshot of listeners associated with this instance
      See Also:
    • setListeners

      public void setListeners(Transaction.ListenerSet listeners)
      Apply a snapshot created via snapshotListeners() to this instance.

      Any currently registered listeners are unregistered and replaced by the listeners in listeners. This method may be invoked multiple times; however, once this method has been invoked, any subsequent attempts to register or unregister individual listeners will result in an UnsupportedOperationException.

      Parameters:
      listeners - listener set created by snapshotListeners()
      Throws:
      IllegalArgumentException - if listeners was created from a transaction with an incompatible schema
      IllegalArgumentException - if listeners is null
    • setUserObject

      public void setUserObject(Object obj)
      Associate an arbitrary object with this instance.
      Parameters:
      obj - user object
    • getUserObject

      public Object getUserObject()
      Get the object with this instance by setUserObject(), if any.
      Returns:
      the associated user object, or null if none has been set