/** * */ package edu.washington.escience.myria.accessmethod; import java.io.Serializable; import java.util.Iterator; import java.util.List; import org.apache.commons.lang.builder.HashCodeBuilder; import com.fasterxml.jackson.annotation.JsonProperty; import edu.washington.escience.myria.DbException; import edu.washington.escience.myria.MyriaConstants; import edu.washington.escience.myria.RelationKey; import edu.washington.escience.myria.Schema; import edu.washington.escience.myria.storage.TupleBatch; /** * Interface for Database Access Methods. * * @author valmeida * */ public abstract class AccessMethod { /** * Factory. * * @param dbms the DBMS * @param connectionInfo the connection info * @param readOnly the flag for read-only * @return the object of type AccessMethod * @throws DbException if anything goes wrong on connecting */ public static AccessMethod of( final String dbms, final ConnectionInfo connectionInfo, final Boolean readOnly) throws DbException { switch (dbms) { case MyriaConstants.STORAGE_SYSTEM_SQLITE: return new SQLiteAccessMethod((SQLiteInfo) connectionInfo, readOnly); case MyriaConstants.STORAGE_SYSTEM_MONETDB: case MyriaConstants.STORAGE_SYSTEM_MYSQL: case MyriaConstants.STORAGE_SYSTEM_POSTGRESQL: return new JdbcAccessMethod((JdbcInfo) connectionInfo, readOnly); } return null; } /** * Connects with the database. * * @param connectionInfo connection information * @param readOnly whether read-only connection or not * @throws DbException if there is an error making the connection. */ abstract void connect(final ConnectionInfo connectionInfo, final Boolean readOnly) throws DbException; /** * Sets the connection to be read-only or writable. * * @param readOnly whether read-only connection or not * @throws DbException if there is an error making the connection. */ abstract void setReadOnly(final Boolean readOnly) throws DbException; /** * Insert the tuples in this TupleBatch into the database. * * @param relationKey the table to insert into. * @param tupleBatch the tupleBatch to be inserted. * * @throws DbException if there is an error inserting the tuples. */ public abstract void tupleBatchInsert(final RelationKey relationKey, final TupleBatch tupleBatch) throws DbException; /** * Runs a query and expose the results as an Iterator<TupleBatch>. * * @param queryString the query * @param schema the output schema (with SQLite we are not able to reconstruct the schema from the API) * @return an Iterator<TupleBatch> containing the results. * @throws DbException if there is an error getting tuples. */ public abstract Iterator<TupleBatch> tupleBatchIteratorFromQuery( final String queryString, final Schema schema) throws DbException; /** * Executes a DDL command. * * @param ddlCommand the DDL command * @throws DbException if there is an error in the database. */ abstract void execute(final String ddlCommand) throws DbException; /** * Closes the database connection. * * @throws DbException if there is an error in the database. */ public abstract void close() throws DbException; /** * Perform some initialization steps for the specific database system. * * @throws DbException if there is an error in the database. */ public void init() throws DbException {} /** * Generates the insert statement string for a relation in the database. * * @param schema the relation schema * @param relationKey the relation name * @return the insert statement string */ public abstract String insertStatementFromSchema(Schema schema, RelationKey relationKey); /** * Generates the create table statement string for a relation in the database. * * @param schema the relation schema * @param relationKey the relation name * @return the create table statement string */ public abstract String createIfNotExistsStatementFromSchema( Schema schema, RelationKey relationKey); /** * Creates a table in the database, if it does not already exist. * * @param relationKey the relation name * @param schema the relation schema * @throws DbException if anything goes wrong */ public abstract void createTableIfNotExists(RelationKey relationKey, Schema schema) throws DbException; /** * Overwrite <code>oldRelation</code> with <code>newRelation</code> by dropping <code>oldRelation</code> if it exists * and renaming <code>newRelation</code> to <code>oldRelation</code>. * * @param oldRelation the table to be overwritten. * @param newRelation the table replacing <code>old</code>. * @throws DbException if there is an error during this operation. */ public abstract void dropAndRenameTables(RelationKey oldRelation, RelationKey newRelation) throws DbException; /** * Drop the specified table, if it exists. * * @param relationKey the table to be dropped. * @throws DbException if there is an error dropping the table. */ public abstract void dropTableIfExists(RelationKey relationKey) throws DbException; /** * @param relationKey the relation to drop * @throws DbException if there is an error dropping the table. */ public abstract void dropTableIfExistsCascade(RelationKey relationKey) throws DbException; /** * Creates the specified indexes on the provided temporary table, but uses the real table name for their names. * * @param relationKey the table on which the indexes will be created. * @param schema the Schema of the data in the table. * @param indexes a list of indexes to be created; each entry is a list of column indices. * @throws DbException if there is an error in the DBMS. */ public abstract void createIndexes( final RelationKey relationKey, final Schema schema, final List<List<IndexRef>> indexes) throws DbException; /** * Creates an indexes on the provided temporary table only when it doesn't exist. * * @param relationKey the table on which the indexes will be created. * @param schema the Schema of the data in the table. * @param index the index to be created; each entry is a list of column indices. * @throws DbException if there is an error in the DBMS. */ public abstract void createIndexIfNotExists( final RelationKey relationKey, final Schema schema, final List<IndexRef> index) throws DbException; /** * Creates a view * * @param viewName * @param viewDefinition * @throws DbException */ public abstract void createView(String viewName, String viewDefinition) throws DbException; /** * Creates a materialized view * * @param viewName * @param viewDefinition * @throws DbException */ public abstract void createMaterializedView(String viewName, String viewDefinition) throws DbException; /** * Executes a command on the underlying database * * @param command * @throws DbException */ public abstract void runCommand(String command) throws DbException; /** * Holds a reference to a column and whether it is ascending or descending. */ public static final class IndexRef implements Serializable { /** Required for Java serialization. */ private static final long serialVersionUID = 1L; /** Which column should be hashed. */ @JsonProperty private final int column; /** True if the column should be hashed in ascending order. */ @JsonProperty private final boolean ascending; /** * This is not really unused, it's used automagically by Jackson deserialization. */ private IndexRef() { column = -1; ascending = true; } /** * Constructs a new IndexRef. * * @param column which column should be hashed. * @param ascending true if the column should be hashed in ascending order. */ private IndexRef(final int column, final boolean ascending) { this.column = column; this.ascending = ascending; } /** * Constructs a new IndexRef. * * @param column which column should be hashed. */ private IndexRef(final int column) { this(column, true); } /** * Factory method for IndexRef. * * @param column which column should be hashed. * @return an IndexRef representing ascending order over that column. */ public static IndexRef of(final int column) { return IndexRef.of(column, true); } /** * Factory method for IndexRef. * * @param schema the schema * @param columnName the column name * @return the index ref to the column */ public static IndexRef of(final Schema schema, final String columnName) { return IndexRef.of(schema.columnNameToIndex(columnName)); } /** * Factory method for IndexRef. * * @param schema the schema. * @param columnName the column name. * @param ascending true if the column should be hashed in ascending order. * @return the index ref to the column */ public static IndexRef of( final Schema schema, final String columnName, final boolean ascending) { return IndexRef.of(schema.columnNameToIndex(columnName), ascending); } /** * Factory method for IndexRef. * * @param column which column should be hashed. * @param ascending true if the column should be hashed in ascending order. * @return an IndexRef representing the specified order over the specified column. */ public static IndexRef of(final int column, final boolean ascending) { return new IndexRef(column, ascending); } /** * @return which column should be hashed. */ public int getColumn() { return column; } /** * @return true if the column should be hashed in ascending order. */ public boolean isAscending() { return ascending; } @Override public int hashCode() { HashCodeBuilder hb = new HashCodeBuilder(); hb.append(ascending).append(column); return hb.toHashCode(); } @Override public boolean equals(final Object o) { if (o == null) { return false; } if (!(o instanceof IndexRef)) { return false; } IndexRef other = (IndexRef) o; return ascending == other.ascending && column == other.column; } } }