package net.sourceforge.mayfly.datastore; import net.sourceforge.mayfly.MayflyException; import net.sourceforge.mayfly.MayflyInternalException; import net.sourceforge.mayfly.datastore.types.DataType; import net.sourceforge.mayfly.datastore.types.FakeDataType; import net.sourceforge.mayfly.evaluation.Checker; import net.sourceforge.mayfly.evaluation.Expression; import net.sourceforge.mayfly.evaluation.ResultRow; import net.sourceforge.mayfly.evaluation.Value; import net.sourceforge.mayfly.evaluation.expression.DefaultValue; import net.sourceforge.mayfly.evaluation.expression.SpecifiedDefaultValue; import net.sourceforge.mayfly.parser.Location; import net.sourceforge.mayfly.util.CaseInsensitiveString; public class Column { public final CaseInsensitiveString columnName; private final DefaultValue defaultValue; private final Expression onUpdateValue; private final boolean isAutoIncrement; private final boolean isSequence; public final DataType type; /** If not-null contraints are managed (e.g. in ALTER TABLE) * along with the column, rather than via named constraints, * it is more natural to define them here than in a subclass of * {@link net.sourceforge.mayfly.datastore.constraint.Constraint} */ public final boolean isNotNull; public Column(CaseInsensitiveString name, DefaultValue defaultValue, Expression onUpdateValue, boolean isAutoIncrement, boolean isSequence, DataType type, boolean isNotNull) { this.columnName = name; this.defaultValue = defaultValue; this.onUpdateValue = onUpdateValue; this.isAutoIncrement = isAutoIncrement; this.isSequence = isSequence; this.type = type; this.isNotNull = isNotNull; if (isAutoIncrement && isSequence) { throw new MayflyInternalException( "column " + name + " is both auto increment and sequence"); } } public Column(String name, DefaultValue defaultValue, Expression onUpdateValue, boolean isAutoIncrement, boolean isSequence, DataType type, boolean isNotNull) { this(new CaseInsensitiveString(name), defaultValue, onUpdateValue, isAutoIncrement, isSequence, type, isNotNull); } /** * Create a column with most of the fields defaulted. I think this is * just called from tests; most code will want to look up an existing * column, rather than create one. */ public Column(String columnName) { this(columnName, DefaultValue.NOT_SPECIFIED, null, false, false, new FakeDataType(), false); } public String columnName() { return columnName.getString(); } public boolean matches(String target) { if (target.indexOf('.') != -1) { throw new MayflyException( "column name " + target + " should not contain a period"); } return columnName.getString().equalsIgnoreCase(target); } @Override public String toString() { return columnName(); } public static String displayName(String tableOrAlias, String column) { if (tableOrAlias == null) { return column; } else { return tableOrAlias + "." + column; } } public Cell defaultValue() { return defaultValue.cell(); } public String defaultValueAsSql() { return defaultValue.asSql(); } /** * Mostly called by old code from before when we distinguished * between {@link #isSequence()} and {@link #isAutoIncrement()}. */ public boolean isSequenceOrAutoIncrement() { return isAutoIncrement || isSequence; } public boolean isAutoIncrement() { return isAutoIncrement; } public boolean isSequence() { return isSequence; } public Column afterAutoIncrement(Checker checker, Cell valueInserted, boolean isDefault) { Cell previousValue; if (isDefault && isSequenceOrAutoIncrement()) { previousValue = defaultValue(); } else if (isAutoIncrement) { if (valueInserted.compareTo(defaultValue()) >= 0) { previousValue = valueInserted; } else { return null; } } else { return null; } checker.setIdentityValue(previousValue); return withIncrementedDefault(previousValue); } public Column withIncrementedDefault(Cell previousValue) { DefaultValue newDefault = new SpecifiedDefaultValue( new LongCell(previousValue.asLong() + 1L)); return new Column(columnName, newDefault, onUpdateValue, isAutoIncrement, isSequence, type, isNotNull); } public Column withName(String name) { return new Column(name, defaultValue, onUpdateValue, isAutoIncrement, isSequence, type, isNotNull); } /** * Coerce to the type of this column. */ public Cell coerce(Cell value, Location location) { if (isNotNull && value instanceof NullCell) { throw new MayflyException( "column " + columnName() + " cannot be null", location); } return type.coerce(new Value(value, location), columnName()); } public Cell newColumnValue() { if (isNotNull && !defaultValue.isSpecified()) { throw new MayflyException( "no default value for column " + columnName(), Location.UNKNOWN); } return defaultValue(); } public boolean hasOnUpdateValue() { return onUpdateValue != null; } public Cell getOnUpdateValue() { if (hasOnUpdateValue()) { return onUpdateValue.evaluate((ResultRow)null); } else { throw new MayflyInternalException( "Column " + columnName + " does not have ON UPDATE value"); } } public String onUpdateValueAsSql() { return onUpdateValue.asSql(); } public boolean hasDefault() { if (!defaultValue.isSpecified()) { return false; } if (isSequenceOrAutoIncrement()) { /* We implement the next value to be assigned as a default value. At least for now, we dump it that way too. But don't dump it if leaving it out has the same meaning (that is, next value is 1). */ if (defaultValue.sqlEquals(new LongCell(1))) { return false; } } return true; } }