package net.sourceforge.mayfly.datastore;
import net.sourceforge.mayfly.MayflyException;
import net.sourceforge.mayfly.Options;
import net.sourceforge.mayfly.datastore.constraint.Constraint;
import net.sourceforge.mayfly.datastore.constraint.Constraints;
import net.sourceforge.mayfly.evaluation.Checker;
import net.sourceforge.mayfly.evaluation.ValueList;
import net.sourceforge.mayfly.evaluation.command.UpdateSchema;
import net.sourceforge.mayfly.evaluation.command.UpdateTable;
import net.sourceforge.mayfly.evaluation.condition.Condition;
import net.sourceforge.mayfly.parser.Location;
import net.sourceforge.mayfly.util.ImmutableList;
import net.sourceforge.mayfly.util.ImmutableMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class Schema {
private final ImmutableMap<String, TableData> tables;
public Schema() {
this(new ImmutableMap());
}
private Schema(ImmutableMap<String, TableData> tables) {
this.tables = tables;
}
public Schema createTable(String table, Iterable<String> columnNames) {
return createTable(
table,
Columns.fromColumnNames(columnNames),
new Constraints(),
new ImmutableList());
}
public Schema createTable(String table, String... columnNames) {
return createTable(
table,
Columns.fromColumnNames(columnNames),
new Constraints(),
new ImmutableList());
}
public Schema createTable(
String table, Columns columns, Constraints constraints,
ImmutableList indexes) {
assertNoTable(table);
return new Schema(tables.with(table,
new TableData(columns, constraints, indexes)));
}
public Schema addColumn(String table, Column newColumn, Position position) {
TableData oldTable = table(table);
return new Schema(tables.with(table,
oldTable.addColumn(newColumn, position)));
}
public Schema dropColumn(TableReference table, String column) {
TableData oldTable = table(table.tableName());
return new Schema(
tables.with(
table.tableName(),
oldTable.dropColumn(table, column)
));
}
public void checkDropColumn(TableReference table, String column) {
for (Iterator iter = tables.values().iterator(); iter.hasNext();) {
TableData potentialReferencer = (TableData) iter.next();
potentialReferencer.checkDropColumn(table, column);
}
}
public Schema modifyColumn(String tableName, Column newColumn) {
TableData oldTable = table(tableName);
return new Schema(
tables.with(tableName, oldTable.modifyColumn(newColumn)));
}
public Schema renameColumn(String table, String oldName, String newName) {
TableData oldTable = table(table);
return new Schema(
tables.with(table, oldTable.renameColumn(oldName, newName)));
}
public Schema renameTable(TableReference existing, String newName) {
String oldName = existing.tableName();
Map<String, TableData> result = new LinkedHashMap<String, TableData>();
for (String table : tables.keySet()) {
if (table.equalsIgnoreCase(oldName)) {
result.put(newName, table(oldName).renameTable(oldName, newName));
}
else if (table.equalsIgnoreCase(newName)) {
throw new MayflyException(
"table " + newName + " already exists; " +
"cannot rename " + oldName + " to " + newName);
}
else {
result.put(table, table(table).renameTable(oldName, newName));
}
}
return new Schema(new ImmutableMap(result));
}
private void assertNoTable(String table) {
String existingTable = lookUpTableOrNull(table);
if (existingTable != null) {
throw new MayflyException("table " + existingTable + " already exists");
}
}
public boolean hasTable(String table) {
return lookUpTableOrNull(table) != null;
}
public Schema dropTable(Checker checker, String table) {
String canonicalTableName = lookUpTable(table);
checker.checkDropTable();
return new Schema(tables.without(canonicalTableName));
}
public TableData table(String table) {
String canonicalTableName = lookUpTable(table);
return tables.get(canonicalTableName);
}
public String lookUpTable(String target) {
return lookUpTable(target, Location.UNKNOWN);
}
public String lookUpTable(String target, Location location) {
return lookUpTable(target, location, new Options());
}
public String lookUpTable(String target, Location location, Options options) {
for (String canonicalTable : tables.keySet()) {
if (canonicalTable.equalsIgnoreCase(target)) {
if (options.tableNamesCaseSensitive()
&& !canonicalTable.equals(target)) {
throw new MayflyException(
"attempt to refer to table " + canonicalTable +
" as " + target +
" (with case sensitive table names enabled)",
location);
}
else {
return canonicalTable;
}
}
}
throw new MayflyException("no table " + target, location);
}
private String lookUpTableOrNull(String target) {
for (String canonicalTable : tables.keySet()) {
if (canonicalTable.equalsIgnoreCase(target)) {
return canonicalTable;
}
}
return null;
}
public Set tables() {
return tables.keySet();
}
public Schema addRow(Checker checker, TableReference table,
ImmutableList columnNames, ValueList values) {
return new Schema(tables.with(lookUpTable(table.tableName()),
table(table.tableName()).addRow(checker, table, columnNames, values)));
}
public Schema addRow(String table, ImmutableList columnNames, ValueList values) {
return addRow(new NullChecker(),
new TableReference(DataStore.ANONYMOUS_SCHEMA_NAME, table),
columnNames, values);
}
public UpdateSchema update(Checker checker, TableReference table,
List setClauses, Condition where) {
UpdateTable result =
table(table.tableName())
.update(checker, setClauses, where, table);
return replaceTable(table.tableName(), result);
}
public UpdateSchema delete(String table, Condition where, Checker checker) {
UpdateTable result = table(table).delete(where, checker, table);
ImmutableMap tablesAfterChecking =
checker.store().schema(checker.schema()).tables;
/**
* Here we merge the tables: the one corresponding to table was returned
* by delete, and the rest come in via the checker.
* This way the checker is the only thing which operates across
* tables - the regular code just affects the one.
*/
Schema schema = new Schema(
tablesAfterChecking.with(lookUpTable(table), result.table())
);
return new UpdateSchema(schema, result.rowsAffected());
}
private UpdateSchema replaceTable(String table, UpdateTable result) {
Schema schema = replaceTable(table, result.table());
return new UpdateSchema(schema, result.rowsAffected());
}
private Schema replaceTable(String tableName, TableData table) {
return new Schema(tables.with(lookUpTable(tableName), table));
}
public DataStore checkDelete(
DataStore store,
String schema, String table, Row rowToDelete, Row replacementRow) {
for (Iterator iter = tables.values().iterator(); iter.hasNext();) {
TableData potentialReferencer = (TableData) iter.next();
store = potentialReferencer.checkDelete(
store,
schema, table, rowToDelete, replacementRow
);
}
return store;
}
public void checkDropTable(DataStore store, String schema, String table) {
for (TableData potentialReferencer : tables.values()) {
potentialReferencer.checkDropTable(store, schema, table);
}
}
public Schema dropForeignKey(String table, String constraintName) {
return replaceTable(table, table(table).dropForeignKey(constraintName));
}
public Schema dropConstraint(String table, String constraintName) {
return replaceTable(table, table(table).dropConstraint(constraintName));
}
public Schema addConstraint(String table, Constraint constraint) {
return replaceTable(table, table(table).addConstraint(constraint));
}
public Schema addIndex(String table, Index index) {
String foundTable = findTableForIndex(index.name());
if (foundTable != null) {
throw new MayflyException(
"table " + foundTable +
" already has an index " + index.name());
}
return replaceTable(table, table(table).addIndex(index));
}
public Schema dropIndex(String table, String indexName) {
if (table == null) {
throw new NullPointerException("table expected");
}
String foundTable = findTableForIndex(indexName);
if (table.equalsIgnoreCase(foundTable)) {
return replaceTable(table, table(table).dropIndex(indexName));
}
else {
throw new MayflyException(
"attempt to drop index " + indexName + " from table " +
table + " although the index is on table " + foundTable);
}
}
public Schema dropIndex(String indexName) {
String table = findTableForIndex(indexName);
if (table == null) {
throw new MayflyException("no index " + indexName);
}
return replaceTable(table, table(table).dropIndex(indexName));
}
public String findTableForIndex(String indexName) {
for (Map.Entry<String, TableData> entry : tables.entrySet()) {
if (entry.getValue().indexes.hasIndex(indexName)) {
return entry.getKey();
}
}
return null;
}
}