package net.sourceforge.mayfly.datastore.constraint; import net.sourceforge.mayfly.MayflyException; import net.sourceforge.mayfly.MayflyInternalException; import net.sourceforge.mayfly.datastore.Cell; import net.sourceforge.mayfly.datastore.ColumnNames; import net.sourceforge.mayfly.datastore.Columns; import net.sourceforge.mayfly.datastore.DataStore; import net.sourceforge.mayfly.datastore.Row; import net.sourceforge.mayfly.datastore.Rows; import net.sourceforge.mayfly.datastore.TableReference; import net.sourceforge.mayfly.parser.Location; import java.util.ArrayList; import java.util.Iterator; import java.util.List; public abstract class NotNullOrUnique extends Constraint { protected final ColumnNames names; /** * Here we take {@link Columns} as opposed to {@link ColumnNames} * merely as a way to express the concept that the names have * been verified to exist in the store. */ protected NotNullOrUnique(Columns columns, String constraintName) { this(ColumnNames.fromColumns(columns), constraintName); } protected NotNullOrUnique(ColumnNames columns, String constraintName) { super(constraintName); this.names = columns; if (names.size() == 0) { throw new MayflyInternalException("must have at least one column for a constraint"); } } @Override public void checkExistingRows(DataStore store, TableReference table) { Rows allRows = store.table(table).rows(); for (int i = 0; i < allRows.rowCount(); ++i) { check(allRows.subList(0, i), allRows.row(i), table, Location.UNKNOWN); } } @Override public void check(Rows existingRows, Row proposedRow, TableReference table, Location location) { List proposedValues = collectProposedValues(proposedRow); for (Iterator iter = existingRows.iterator(); iter.hasNext();) { Row row = (Row) iter.next(); List valuesForRow = valuesForRow(row); if (sqlEquals(proposedValues, valuesForRow, location)) { throw new MayflyException( constraintName(table) + ": duplicate value" + (valuesForRow.size() == 1 ? "" : "s") + " " + describeValues(valuesForRow)); } } } public static boolean sqlEquals(List left, List right, Location location) { if (left.size() != right.size()) { throw new MayflyInternalException( "meant to compare equal size lists but were " + left.size() + " and " + right.size()); } for (int i = 0; i < left.size(); ++i) { Cell leftCell = (Cell) left.get(i); Cell rightCell = (Cell) right.get(i); if (!leftCell.sqlEquals(rightCell, location)) { return false; } } return true; } private List valuesForRow(Row row) { List valuesForRow = new ArrayList(); for (Iterator iterator = names.iterator(); iterator.hasNext();) { String column = (String) iterator.next(); valuesForRow.add(row.cell(column)); } return valuesForRow; } static String describeValues(List valuesForRow) { StringBuilder message = new StringBuilder(); Iterator iter = valuesForRow.iterator(); message.append(((Cell) iter.next()).asString()); while (iter.hasNext()) { Cell cell = (Cell) iter.next(); message.append(","); message.append(cell.asString()); } return message.toString(); } private String constraintName(TableReference table) { StringBuilder message = new StringBuilder(); message.append(description()); if (constraintName != null) { message.append(" "); message.append(constraintName); message.append(" ("); } else { message.append(" in "); } message.append("table "); message.append(table.tableName()); message.append(", column"); if (names.size() != 1) { message.append("s"); } message.append(" "); Iterator iter = names.iterator(); String firstColumn = (String) iter.next(); message.append(firstColumn); while (iter.hasNext()) { String column = (String) iter.next(); message.append(","); message.append(column); } if (constraintName != null) { message.append(")"); } return message.toString(); } private List collectProposedValues(Row proposedRow) { List proposedValues = new ArrayList(); for (Iterator iter = names.iterator(); iter.hasNext();) { String column = (String) iter.next(); Cell proposedCell = proposedRow.cell(column); checkForNull(column, proposedCell); proposedValues.add(proposedCell); } return proposedValues; } protected abstract void checkForNull(String column, Cell proposedCell); protected abstract String description(); @Override public boolean checkDropColumn(TableReference table, String column) { if (names.hasColumn(column)) { if (names.size() > 1) { throw new MayflyException( "attempt to drop column " + column + " from multi-column " + constraintName(table)); } return false; } return true; } @Override public boolean canBeTargetOfForeignKey(String targetColumn) { return names.size() == 1 && names.hasColumn(targetColumn); } }