package net.sourceforge.mayfly.datastore; import net.sourceforge.mayfly.MayflyException; import net.sourceforge.mayfly.MayflyInternalException; import net.sourceforge.mayfly.Options; import net.sourceforge.mayfly.datastore.constraint.Constraint; import net.sourceforge.mayfly.evaluation.Checker; import net.sourceforge.mayfly.evaluation.RealChecker; import net.sourceforge.mayfly.evaluation.ValueList; import net.sourceforge.mayfly.evaluation.command.UnresolvedTableReference; import net.sourceforge.mayfly.evaluation.command.UpdateSchema; import net.sourceforge.mayfly.evaluation.command.UpdateStore; import net.sourceforge.mayfly.evaluation.condition.Condition; import net.sourceforge.mayfly.parser.Location; import net.sourceforge.mayfly.util.CaseInsensitiveString; import net.sourceforge.mayfly.util.ImmutableList; import net.sourceforge.mayfly.util.ImmutableMap; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.TreeSet; /** * A data store is an immutable object containing data * and metadata for a set of tables. * * <p>You typically get one via {@link net.sourceforge.mayfly.Database#dataStore()} * and then pass it to {@link net.sourceforge.mayfly.Database#Database(DataStore)}.</p> * * @internal * As with the rest of our immutable objects, all fields should * be final and have types which are immutable (like String, * Double, ImmutableList, long, etc). */ public class DataStore { public static final String ANONYMOUS_SCHEMA_NAME = ""; public static final CaseInsensitiveString ANONYMOUS_SCHEMA = new CaseInsensitiveString(ANONYMOUS_SCHEMA_NAME); private final ImmutableMap schemas; public DataStore() { this(new Schema()); } public DataStore(Schema anonymousSchema) { this(new ImmutableMap().with(ANONYMOUS_SCHEMA, anonymousSchema)); } private DataStore(ImmutableMap namedSchemas) { this.schemas = namedSchemas; } public DataStore addSchema(String newSchemaName, Schema newSchema) { if (schemaExists(newSchemaName)) { throw new MayflyException("schema " + newSchemaName + " already exists"); } ImmutableMap newSchemas = schemas.with(new CaseInsensitiveString(newSchemaName), newSchema); return new DataStore(newSchemas); } public DataStore replace(String newSchemaName, Schema newSchema) { return replace(schemas, newSchemaName, newSchema); } private DataStore replace(ImmutableMap existingSchemas, String newSchemaName, Schema newSchema) { if (schemaExists(newSchemaName)) { ImmutableMap newSchemas = existingSchemas.with( new CaseInsensitiveString(newSchemaName), newSchema); return new DataStore(newSchemas); } else { throw new MayflyInternalException("no schema " + newSchemaName); } } private boolean schemaExists(String newSchemaName) { return schemas.containsKey(new CaseInsensitiveString(newSchemaName)); } public Schema schema(String schema) { if (schemaExists(schema)) { return (Schema) schemas.get(new CaseInsensitiveString(schema)); } throw new MayflyException("no schema " + schema); } public Schema anonymousSchema() { return (Schema) schemas.get(ANONYMOUS_SCHEMA); } public DataStore dropTable(TableReference table) { Checker checker = new RealChecker(this, table); return replace(table.schema(), schema(table.schema()).dropTable(checker, table.tableName())); } public TableData table(String schema, String table) { return schema(schema).table(table); } public TableData table(TableReference table) { return schema(table.schema()).table(table.tableName()); } public TableData table(String table) { return anonymousSchema().table(table); } public Set tables(String schema) { return schema(schema).tables(); } public DataStore addRow(TableReference table, ImmutableList columnNames, ValueList values, Checker checker) { if (columnNames == null) { throw new NullPointerException("should have looked up column names by now"); } return replace(table.schema(), schema(table.schema()) .addRow(checker, table, columnNames, values)); } public Set schemas() { Set names = new TreeSet(); for (Iterator iter = schemas.keySet().iterator(); iter.hasNext();) { CaseInsensitiveString key = (CaseInsensitiveString) iter.next(); if (!key.equals(ANONYMOUS_SCHEMA)) { names.add(key.getString()); } } return names; } public UpdateStore update(String schema, String table, List setClauses, Condition where, Options options) { TableReference tableReference = new TableReference(schema, table); Checker checker = new RealChecker(this, schema, table, Location.UNKNOWN, options); UpdateSchema result = schema(schema).update(checker, tableReference, setClauses, where); return replaceSchema(schema, result); } public UpdateStore delete(String schema, String table, Condition where, Options options) { Checker checker = new RealChecker(this, schema, table, Location.UNKNOWN, options); UpdateSchema result = schema(schema).delete(table, where, checker); /** * Here we merge the schemas: the one corresponding to schema * was returned * by delete, and the rest come in via the checker. * This way the checker is the only thing which operates across * schemas - the regular code just affects the one. */ ImmutableMap schemas = checker.store().schemas; DataStore newStore = replace(schemas, schema, result.schema()); return new UpdateStore( newStore, result.rowsAffected() ); } private UpdateStore replaceSchema(String schema, UpdateSchema result) { return new UpdateStore( replace(schema, result.schema()), result.rowsAffected() ); } public DataStore checkDelete(String schema, String table, Row rowToDelete, Row replacementRow) { DataStore store = this; for (Iterator iter = schemas.values().iterator(); iter.hasNext();) { Schema potentialReferencer = (Schema) iter.next(); store = potentialReferencer.checkDelete( store, schema, table, rowToDelete, replacementRow); } return store; } public void checkDropTable(String schema, String table) { for (Iterator iter = schemas.values().iterator(); iter.hasNext();) { Schema potentialReferencer = (Schema) iter.next(); potentialReferencer.checkDropTable(this, schema, table); } } public UpdateStore dropColumn(TableReference table, String column) { checkForReferencesToColumn(table, column); Schema existing = schema(table.schema()); Schema updatedSchema = existing.dropColumn(table, column); return new UpdateStore(replace(table.schema(), updatedSchema), 0); } private void checkForReferencesToColumn(TableReference table, String column) { for (Iterator iter = schemas.values().iterator(); iter.hasNext();) { Schema potentialReferencer = (Schema) iter.next(); potentialReferencer.checkDropColumn(table, column); } } public UpdateStore modifyColumn(TableReference table, Column newColumn) { Schema existing = schema(table.schema()); Schema updatedSchema = existing.modifyColumn(table.tableName(), newColumn); return new UpdateStore(replace(table.schema(), updatedSchema), 0); } public UpdateStore renameTable(TableReference existing, String newName) { return new UpdateStore( replace(existing.schema(), schema(existing.schema()).renameTable(existing, newName) ), 0); } public DataStore renameColumn(TableReference table, String oldName, String newName) { checkForReferencesToColumn(table, oldName); Schema existing = schema(table.schema()); Schema updatedSchema = existing.renameColumn( table.tableName(), oldName, newName); return replace(table.schema(), updatedSchema); } public DataStore dropForeignKey(TableReference table, String constraintName) { Schema existing = schema(table.schema()); return replace( table.schema(), existing.dropForeignKey(table.tableName(), constraintName) ); } public DataStore dropConstraint(TableReference table, String constraintName) { Schema existing = schema(table.schema()); return replace( table.schema(), existing.dropConstraint(table.tableName(), constraintName) ); } public DataStore addConstraint(TableReference table, Constraint constraint) { Schema existing = schema(table.schema()); return replace( table.schema(), existing.addConstraint(table.tableName(), constraint) ); } public boolean hasTable( UnresolvedTableReference table, String defaultSchema) { Schema schema = schema(table.schema(defaultSchema)); return schema.hasTable(table.tableName()); } public DataStore addIndex(TableReference table, Index index) { Schema existing = schema(table.schema()); return replace( table.schema(), existing.addIndex(table.tableName(), index) ); } public DataStore dropIndex(TableReference table, String indexName) { Schema existing = schema(table.schema()); return replace( table.schema(), existing.dropIndex(table.tableName(), indexName) ); } public DataStore dropIndex(String schema, String indexName) { Schema existing = schema(schema); return replace( schema, existing.dropIndex(indexName) ); } }