package net.sourceforge.mayfly.datastore;
import net.sourceforge.mayfly.MayflyException;
import net.sourceforge.mayfly.MayflyInternalException;
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.Value;
import net.sourceforge.mayfly.evaluation.ValueList;
import net.sourceforge.mayfly.evaluation.command.SetClause;
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.CaseInsensitiveString;
import net.sourceforge.mayfly.util.ImmutableList;
import net.sourceforge.mayfly.util.L;
import java.util.Iterator;
import java.util.List;
public class TableData {
private final Columns columns;
private final Rows rows;
public final Constraints constraints;
public final Indexes indexes;
public TableData(Columns columns, Constraints constraints,
ImmutableList indexes) {
this(columns, constraints, new Rows(), new Indexes(indexes));
}
TableData(Columns columns, Constraints constraints, Rows rows,
Indexes indexes) {
this.constraints = constraints;
if (constraints == null) {
throw new NullPointerException("constraints is required");
}
this.columns = columns;
this.rows = rows;
this.indexes = indexes;
}
public TableData addRow(Checker checker, TableReference table,
ImmutableList<String> columnNames, ValueList values) {
checkColumnCount(columnNames, values);
TupleMapper tuple = new TupleMapper();
Columns newColumns = columns;
for (int i = 0; i < values.size(); ++i) {
String columnName = columnNames.get(i);
Column column = columns.columnFromName(columnName);
newColumns =
addColumn(newColumns, tuple, column, checker, values.value(i));
}
Value defaultMarker = new Value(null, values.location);
for (int i = 0; i < columns.columnCount(); ++i) {
Column column = columns.column(i);
CaseInsensitiveString name = column.columnName;
if (!tuple.hasColumn(name)) {
newColumns =
addColumn(newColumns, tuple, column, checker, defaultMarker);
}
}
Row newRow = tuple.asRow();
constraints.check(rows, newRow, table, values.location);
indexes.check(rows, newRow, table, values.location);
checker.checkInsert(constraints, newRow);
return new TableData(
newColumns, constraints, rows.with(newRow), indexes);
}
public void checkColumnCount(ImmutableList<String> columnNames,
ValueList values) {
if (columnNames.size() != values.size()) {
if (values.size() > columnNames.size()) {
throw makeException("Too many values.\n", columnNames, values);
} else {
throw makeException("Too few values.\n", columnNames, values);
}
}
}
private MayflyException makeException(String message,
ImmutableList<String> columnNames, ValueList values) {
return new MayflyException(
message + describeNamesAndValues(columnNames, values),
values.location);
}
private String describeNamesAndValues(
ImmutableList<String> columns, ValueList values) {
StringBuilder result = new StringBuilder();
result.append("Columns and values were:\n");
Iterator<String> nameIterator = columns.iterator();
Iterator<Cell> valueIterator = values.asCells().iterator();
while (nameIterator.hasNext() || valueIterator.hasNext()) {
if (nameIterator.hasNext()) {
String columnName = nameIterator.next();
result.append(columnName);
} else {
result.append("(none)");
}
result.append(' ');
if (valueIterator.hasNext()) {
Cell value = valueIterator.next();
result.append(value.toString());
} else {
result.append("(none)");
}
result.append('\n');
}
return result.toString();
}
private Columns addColumn(Columns newColumns, TupleMapper tuple, Column column, Checker checker, Value value) {
boolean isDefault = value.value == null;
Cell cell = column.coerce(
isDefault ? column.defaultValue() : value.value,
value.location);
tuple.add(column.columnName, cell);
Column newColumn = column.afterAutoIncrement(checker, cell, isDefault);
if (newColumn != null) {
newColumns = newColumns.replace(newColumn);
}
return newColumns;
}
public UpdateTable update(Checker checker, List setClauses,
Condition where, TableReference table) {
checker.evaluate(where, dummyRow(), table.tableName());
Rows newRows = new Rows();
int rowsAffected = 0;
for (Iterator iter = rows.iterator(); iter.hasNext();) {
Row row = (Row) iter.next();
if (checker.evaluate(where, row, table.tableName())) {
Row newRow = newRow(setClauses, row, table.tableName());
constraints.check(newRows, newRow, table, Location.UNKNOWN);
checker.checkInsert(constraints, newRow);
checker.checkDelete(row, newRow);
newRows = newRows.with(newRow);
++rowsAffected;
}
else {
constraints.check(newRows, row, table, Location.UNKNOWN);
newRows = newRows.with(row);
}
}
TableData newTable = new TableData(
columns, constraints, newRows, indexes);
return new UpdateTable(newTable, rowsAffected);
}
private Row newRow(List setClauses, Row row, String table) {
TupleMapper mapper = new TupleMapper(row);
for (Iterator iterator = setClauses.iterator(); iterator.hasNext();) {
SetClause setClause = (SetClause) iterator.next();
Column column = setClause.column(columns);
mapper.put(column, setClause.value(row, table, column));
}
setOnUpdateColumns(mapper);
Row newRow = mapper.asRow();
return newRow;
}
private void setOnUpdateColumns(TupleMapper mapper) {
for (Iterator iter = columns.iterator(); iter.hasNext();) {
Column column = (Column) iter.next();
if (column.hasOnUpdateValue()) {
mapper.put(column, column.getOnUpdateValue());
}
}
}
public UpdateTable delete(Condition where, Checker checker, String tableName) {
checker.evaluate(where, dummyRow(), tableName);
Rows newRows = new Rows();
int rowsAffected = 0;
for (Iterator iter = rows.iterator(); iter.hasNext();) {
Row row = (Row) iter.next();
if (checker.evaluate(where, row, tableName)) {
++rowsAffected;
checker.checkDelete(row, null);
}
else {
newRows = newRows.with(row);
}
}
TableData newTable = new TableData(
columns, constraints, newRows, indexes);
return new UpdateTable(newTable, rowsAffected);
}
public Columns findColumns(List columnNames) {
L columnList = new L();
for (Iterator iter = columnNames.iterator(); iter.hasNext();) {
String name = (String) iter.next();
columnList.add(findColumn(name));
}
Columns specified = new Columns(columnList.asImmutable());
return specified;
}
public Row dummyRow() {
TupleBuilder tuple = new TupleBuilder();
for (int i = 0; i < columns.columnCount(); ++i) {
tuple.append(
columns.column(i),
NullCell.INSTANCE
);
}
return tuple.asRow();
}
public Column findColumn(String columnName) {
return columns.columnFromName(columnName);
}
public ImmutableList<String> columnNames() {
return columns.asNames();
}
public Columns columns() {
return columns;
}
public int rowCount() {
return rows.rowCount();
}
public Row row(int index) {
return rows.row(index);
}
public Rows rows() {
return rows;
}
public boolean hasValue(String column, Cell value) {
for (Iterator iter = rows.iterator(); iter.hasNext();) {
Row row = (Row) iter.next();
if (row.cell(column).sqlEquals(value)) {
return true;
}
}
return false;
}
private boolean hasNull(Column column) {
for (Iterator iter = rows.iterator(); iter.hasNext();) {
Row row = (Row) iter.next();
if (row.cell(column.columnName()) instanceof NullCell) {
return true;
}
}
return false;
}
public DataStore checkDelete(
DataStore store,
String schema, String table, Row rowToDelete, Row replacementRow) {
return constraints.checkDelete(store, schema, table,
rowToDelete, replacementRow);
}
public void checkDropTable(DataStore store, String schema, String table) {
constraints.checkDropTable(store, schema, table);
}
public TableData addColumn(Column newColumn, Position position) {
if (columns.hasColumn(newColumn.columnName())) {
throw new MayflyException(
"column " + newColumn.columnName() + " already exists");
}
return new TableData(
columns.with(newColumn, position),
constraints,
rows.addColumn(newColumn),
indexes
);
}
public TableData dropColumn(TableReference table, String column) {
// TODO: what if column is mentioned in indexes?
return new TableData(
columns.without(column),
constraints.dropColumn(table, column),
rows.dropColumn(column),
indexes
);
}
/**
* @internal
* Check for whether this table has any foreign keys which
* reference the other column specified by the parameters.
* If so, throw an exception.
*/
public void checkDropColumn(TableReference table, String column) {
constraints.checkDropColumn(table, column);
}
public TableData modifyColumn(Column newColumn) {
Column oldColumn = columns.columnFromName(newColumn.columnName());
if (!oldColumn.isNotNull && newColumn.isNotNull) {
// Check the constraint that we're adding
if (hasNull(oldColumn)) {
throw new MayflyException(
"cannot make column " + newColumn.columnName() +
" NOT NULL because it contains null values");
}
}
if (!oldColumn.isAutoIncrement() && newColumn.isAutoIncrement()) {
if (rows.rowCount() > 0) {
newColumn =
newColumn.withIncrementedDefault(
highest(newColumn.columnName()));
}
}
return new TableData(
columns.replace(newColumn),
constraints,
rows,
indexes
);
}
public TableData renameColumn(String oldName, String newName) {
Column oldColumn = columns.columnFromName(oldName);
Column newColumn = oldColumn.withName(newName);
return new TableData(
columns.replace(oldName, newColumn),
constraints.renameColumn(oldName, newName),
rows.renameColumn(oldName, newName),
indexes.renameColumn(oldName, newName)
);
}
public TableData renameTable(String oldName, String newName) {
return new TableData(
columns,
constraints.renameTable(oldName, newName),
rows,
indexes
);
}
Cell highest(String column) {
Iterator iter = rows.iterator();
if (!iter.hasNext()) {
throw new MayflyInternalException("no rows");
}
Row firstRow = (Row) iter.next();
Cell best = firstRow.cell(column);
while (iter.hasNext()) {
Row row = (Row) iter.next();
Cell value = row.cell(column);
if (value.compareTo(best) > 0) {
best = value;
}
}
return best;
}
public TableData dropForeignKey(String constraintName) {
return new TableData(
columns,
constraints.dropForeignKey(constraintName),
rows,
indexes
);
}
public TableData dropConstraint(String constraintName) {
return new TableData(
columns,
constraints.dropConstraint(constraintName),
rows,
indexes
);
}
public TableData addConstraint(Constraint key) {
return new TableData(
columns,
constraints.addConstraint(key),
rows,
indexes
);
}
public TableData addIndex(Index index) {
return new TableData(
columns,
constraints,
rows,
indexes.with(index));
}
public TableData dropIndex(String indexName) {
return new TableData(
columns,
constraints,
rows,
indexes.without(indexName));
}
public boolean canBeTargetOfForeignKey(String targetColumn) {
return constraints.canBeTargetOfForeignKey(targetColumn);
}
public boolean hasPrimaryKey() {
return constraints.hasPrimaryKey();
}
}