public ArrayList<DbObject> getChildren() { ArrayList<DbObject> children = New.arrayList(); ArrayList<Index> indexes = getIndexes(); if (indexes != null) { children.addAll(indexes); } if (constraints != null) { children.addAll(constraints); } if (triggers != null) { children.addAll(triggers); } if (sequences != null) { children.addAll(sequences); } if (views != null) { children.addAll(views); } ArrayList<Right> rights = database.getAllRights(); for (Right right : rights) { if (right.getGrantedTable() == this) { children.add(right); } } return children; }
private static <T> ArrayList<T> add(ArrayList<T> list, T obj) { if (list == null) { list = New.arrayList(); } // self constraints are two entries in the list list.add(obj); return list; }
/** * Check that this column is not referenced by a multi-column constraint or multi-column index. If * it is, an exception is thrown. Single-column references and indexes are dropped. * * @param session the session * @param col the column * @throws DbException if the column is referenced by multi-column constraints or indexes */ public void dropSingleColumnConstraintsAndIndexes(Session session, Column col) { ArrayList<Constraint> constraintsToDrop = New.arrayList(); if (constraints != null) { for (int i = 0, size = constraints.size(); i < size; i++) { Constraint constraint = constraints.get(i); HashSet<Column> columns = constraint.getReferencedColumns(this); if (!columns.contains(col)) { continue; } if (columns.size() == 1) { constraintsToDrop.add(constraint); } else { throw DbException.get(ErrorCode.COLUMN_IS_REFERENCED_1, constraint.getSQL()); } } } ArrayList<Index> indexesToDrop = New.arrayList(); ArrayList<Index> indexes = getIndexes(); if (indexes != null) { for (int i = 0, size = indexes.size(); i < size; i++) { Index index = indexes.get(i); if (index.getCreateSQL() == null) { continue; } if (index.getColumnIndex(col) < 0) { continue; } if (index.getColumns().length == 1) { indexesToDrop.add(index); } else { throw DbException.get(ErrorCode.COLUMN_IS_REFERENCED_1, index.getSQL()); } } } for (Constraint c : constraintsToDrop) { session.getDatabase().removeSchemaObject(session, c); } for (Index i : indexesToDrop) { // the index may already have been dropped when dropping the constraint if (getIndexes().contains(i)) { session.getDatabase().removeSchemaObject(session, i); } } }
/** This class represents the statement MERGE */ public class Merge extends Prepared { protected Table table; protected Column[] columns; protected Column[] keys; protected final ArrayList<Expression[]> list = New.arrayList(); protected Query query; protected Prepared update; public Merge(Session session) { super(session); } public void setCommand(Command command) { super.setCommand(command); if (query != null) { query.setCommand(command); } } public void setTable(Table table) { this.table = table; } public void setColumns(Column[] columns) { this.columns = columns; } public void setKeys(Column[] keys) { this.keys = keys; } public void setQuery(Query query) { this.query = query; } /** * Add a row to this merge statement. * * @param expr the list of values */ public void addRow(Expression[] expr) { list.add(expr); } public int update() { int count; session.getUser().checkRight(table, Right.INSERT); session.getUser().checkRight(table, Right.UPDATE); setCurrentRowNumber(0); if (list.size() > 0) { count = 0; for (int x = 0, size = list.size(); x < size; x++) { Expression[] expr = list.get(x); Row newRow; try { newRow = createRow(expr, x); if (newRow == null) { continue; } } catch (DbException ex) { throw setRow(ex, count + 1, getSQL(expr)); } setCurrentRowNumber(++count); merge(newRow); } } else { ResultInterface rows = query.query(0); count = 0; table.fire(session, Trigger.UPDATE | Trigger.INSERT, true); table.lock(session, true, false); while (rows.next()) { Value[] values = rows.currentRow(); Row newRow; try { newRow = createRow(values); if (newRow == null) { continue; } } catch (DbException ex) { throw setRow(ex, count + 1, getSQL(values)); } setCurrentRowNumber(++count); merge(newRow); } rows.close(); table.fire(session, Trigger.UPDATE | Trigger.INSERT, false); } return count; } protected Row createRow(Expression[] expr, int rowId) { Row newRow = table.getTemplateRow(); for (int i = 0, len = columns.length; i < len; i++) { Column c = columns[i]; int index = c.getColumnId(); Expression e = expr[i]; if (e != null) { // e can be null (DEFAULT) try { Value v = c.convert(e.getValue(session)); newRow.setValue(index, v); } catch (DbException ex) { throw setRow(ex, rowId, getSQL(expr)); } } } return newRow; } protected Row createRow(Value[] values) { Row newRow = table.getTemplateRow(); for (int j = 0; j < columns.length; j++) { Column c = columns[j]; int index = c.getColumnId(); Value v = c.convert(values[j]); newRow.setValue(index, v); } return newRow; } private void merge(Row row) { ArrayList<Parameter> k = update.getParameters(); for (int i = 0; i < columns.length; i++) { Column col = columns[i]; Value v = row.getValue(col.getColumnId()); Parameter p = k.get(i); p.setValue(v); } for (int i = 0; i < keys.length; i++) { Column col = keys[i]; Value v = row.getValue(col.getColumnId()); if (v == null) { throw DbException.get(ErrorCode.COLUMN_CONTAINS_NULL_VALUES_1, col.getSQL()); } Parameter p = k.get(columns.length + i); p.setValue(v); } int count = update.update(); if (count == 0) { try { table.validateConvertUpdateSequence(session, row); boolean done = table.fireBeforeRow(session, null, row); if (!done) { table.lock(session, true, false); table.addRow(session, row); session.log(table, UndoLogRecord.INSERT, row); table.fireAfterRow(session, null, row, false); } } catch (DbException e) { if (e.getErrorCode() == ErrorCode.DUPLICATE_KEY_1) { // possibly a concurrent merge or insert Index index = (Index) e.getSource(); if (index != null) { // verify the index columns match the key Column[] indexColumns = index.getColumns(); boolean indexMatchesKeys = false; if (indexColumns.length <= keys.length) { for (int i = 0; i < indexColumns.length; i++) { if (indexColumns[i] != keys[i]) { indexMatchesKeys = false; break; } } } if (indexMatchesKeys) { throw DbException.get(ErrorCode.CONCURRENT_UPDATE_1, table.getName()); } } } throw e; } } else if (count != 1) { throw DbException.get(ErrorCode.DUPLICATE_KEY_1, table.getSQL()); } } public String getPlanSQL() { StatementBuilder buff = new StatementBuilder("MERGE INTO "); buff.append(table.getSQL()).append('('); for (Column c : columns) { buff.appendExceptFirst(", "); buff.append(c.getSQL()); } buff.append(')'); if (keys != null) { buff.append(" KEY("); buff.resetCount(); for (Column c : keys) { buff.appendExceptFirst(", "); buff.append(c.getSQL()); } buff.append(')'); } buff.append('\n'); if (list.size() > 0) { buff.append("VALUES "); int row = 0; for (Expression[] expr : list) { if (row++ > 0) { buff.append(", "); } buff.append('('); buff.resetCount(); for (Expression e : expr) { buff.appendExceptFirst(", "); if (e == null) { buff.append("DEFAULT"); } else { buff.append(e.getSQL()); } } buff.append(')'); } } else { buff.append(query.getPlanSQL()); } return buff.toString(); } public void prepare() { if (columns == null) { if (list.size() > 0 && list.get(0).length == 0) { // special case where table is used as a sequence columns = new Column[0]; } else { columns = table.getColumns(); } } if (list.size() > 0) { for (Expression[] expr : list) { if (expr.length != columns.length) { throw DbException.get(ErrorCode.COLUMN_COUNT_DOES_NOT_MATCH); } for (int i = 0; i < expr.length; i++) { Expression e = expr[i]; if (e != null) { expr[i] = e.optimize(session); } } } } else { query.prepare(); if (query.getColumnCount() != columns.length) { throw DbException.get(ErrorCode.COLUMN_COUNT_DOES_NOT_MATCH); } } if (keys == null) { Index idx = table.getPrimaryKey(); if (idx == null) { throw DbException.get(ErrorCode.CONSTRAINT_NOT_FOUND_1, "PRIMARY KEY"); } keys = idx.getColumns(); } StatementBuilder buff = new StatementBuilder("UPDATE "); buff.append(table.getSQL()).append(" SET "); for (Column c : columns) { buff.appendExceptFirst(", "); buff.append(c.getSQL()).append("=?"); } buff.append(" WHERE "); buff.resetCount(); for (Column c : keys) { buff.appendExceptFirst(" AND "); buff.append(c.getSQL()).append("=?"); } String sql = buff.toString(); update = session.prepare(sql); } public boolean isTransactional() { return true; } public ResultInterface queryMeta() { return null; } public int getType() { return CommandInterface.MERGE; } public boolean isCacheable() { return true; } }
public ArrayList<CacheObject> getAllChanged() { ArrayList<CacheObject> changed = New.arrayList(); changed.addAll(lru.getAllChanged()); changed.addAll(fifo.getAllChanged()); return changed; }
/** This class represents the statement ALTER TABLE ADD CONSTRAINT */ public class AlterTableAddConstraint extends SchemaCommand { private int type; private String constraintName; private String tableName; private IndexColumn[] indexColumns; private int deleteAction; private int updateAction; private Schema refSchema; private String refTableName; private IndexColumn[] refIndexColumns; private Expression checkExpression; private Index index, refIndex; private String comment; private boolean checkExisting; private boolean primaryKeyHash; private final boolean ifNotExists; private final ArrayList<Index> createdIndexes = New.arrayList(); public AlterTableAddConstraint(Session session, Schema schema, boolean ifNotExists) { super(session, schema); this.ifNotExists = ifNotExists; } private String generateConstraintName(Table table) { if (constraintName == null) { constraintName = getSchema().getUniqueConstraintName(session, table); } return constraintName; } @Override public int update() { try { return tryUpdate(); } catch (DbException e) { for (Index index : createdIndexes) { session.getDatabase().removeSchemaObject(session, index); } throw e; } finally { getSchema().freeUniqueName(constraintName); } } /** * Try to execute the statement. * * @return the update count */ private int tryUpdate() { if (!transactional) { session.commit(true); } Database db = session.getDatabase(); Table table = getSchema().getTableOrView(session, tableName); if (getSchema().findConstraint(session, constraintName) != null) { if (ifNotExists) { return 0; } throw DbException.get(ErrorCode.CONSTRAINT_ALREADY_EXISTS_1, constraintName); } session.getUser().checkRight(table, Right.ALL); db.lockMeta(session); table.lock(session, true, true); Constraint constraint; switch (type) { case CommandInterface.ALTER_TABLE_ADD_CONSTRAINT_PRIMARY_KEY: { IndexColumn.mapColumns(indexColumns, table); index = table.findPrimaryKey(); ArrayList<Constraint> constraints = table.getConstraints(); for (int i = 0; constraints != null && i < constraints.size(); i++) { Constraint c = constraints.get(i); if (Constraint.PRIMARY_KEY.equals(c.getConstraintType())) { throw DbException.get(ErrorCode.SECOND_PRIMARY_KEY); } } if (index != null) { // if there is an index, it must match with the one declared // we don't test ascending / descending IndexColumn[] pkCols = index.getIndexColumns(); if (pkCols.length != indexColumns.length) { throw DbException.get(ErrorCode.SECOND_PRIMARY_KEY); } for (int i = 0; i < pkCols.length; i++) { if (pkCols[i].column != indexColumns[i].column) { throw DbException.get(ErrorCode.SECOND_PRIMARY_KEY); } } } if (index == null) { IndexType indexType = IndexType.createPrimaryKey(table.isPersistIndexes(), primaryKeyHash); String indexName = table.getSchema().getUniqueIndexName(session, table, Constants.PREFIX_PRIMARY_KEY); int id = getObjectId(); try { index = table.addIndex(session, indexName, id, indexColumns, indexType, true, null); } finally { getSchema().freeUniqueName(indexName); } } index.getIndexType().setBelongsToConstraint(true); int constraintId = getObjectId(); String name = generateConstraintName(table); ConstraintUnique pk = new ConstraintUnique(getSchema(), constraintId, name, table, true); pk.setColumns(indexColumns); pk.setIndex(index, true); constraint = pk; break; } case CommandInterface.ALTER_TABLE_ADD_CONSTRAINT_UNIQUE: { IndexColumn.mapColumns(indexColumns, table); boolean isOwner = false; if (index != null && canUseUniqueIndex(index, table, indexColumns)) { isOwner = true; index.getIndexType().setBelongsToConstraint(true); } else { index = getUniqueIndex(table, indexColumns); if (index == null) { index = createIndex(table, indexColumns, true); isOwner = true; } } int id = getObjectId(); String name = generateConstraintName(table); ConstraintUnique unique = new ConstraintUnique(getSchema(), id, name, table, false); unique.setColumns(indexColumns); unique.setIndex(index, isOwner); constraint = unique; break; } case CommandInterface.ALTER_TABLE_ADD_CONSTRAINT_CHECK: { int id = getObjectId(); String name = generateConstraintName(table); ConstraintCheck check = new ConstraintCheck(getSchema(), id, name, table); TableFilter filter = new TableFilter(session, table, null, false, null); checkExpression.mapColumns(filter, 0); checkExpression = checkExpression.optimize(session); check.setExpression(checkExpression); check.setTableFilter(filter); constraint = check; if (checkExisting) { check.checkExistingData(session); } break; } case CommandInterface.ALTER_TABLE_ADD_CONSTRAINT_REFERENTIAL: { Table refTable = refSchema.getTableOrView(session, refTableName); session.getUser().checkRight(refTable, Right.ALL); if (!refTable.canReference()) { throw DbException.getUnsupportedException("Reference " + refTable.getSQL()); } boolean isOwner = false; IndexColumn.mapColumns(indexColumns, table); if (index != null && canUseIndex(index, table, indexColumns, false)) { isOwner = true; index.getIndexType().setBelongsToConstraint(true); } else { index = getIndex(table, indexColumns, true); if (index == null) { index = createIndex(table, indexColumns, false); isOwner = true; } } if (refIndexColumns == null) { Index refIdx = refTable.getPrimaryKey(); refIndexColumns = refIdx.getIndexColumns(); } else { IndexColumn.mapColumns(refIndexColumns, refTable); } if (refIndexColumns.length != indexColumns.length) { throw DbException.get(ErrorCode.COLUMN_COUNT_DOES_NOT_MATCH); } boolean isRefOwner = false; if (refIndex != null && refIndex.getTable() == refTable && canUseIndex(refIndex, refTable, refIndexColumns, false)) { isRefOwner = true; refIndex.getIndexType().setBelongsToConstraint(true); } else { refIndex = null; } if (refIndex == null) { refIndex = getIndex(refTable, refIndexColumns, false); if (refIndex == null) { refIndex = createIndex(refTable, refIndexColumns, true); isRefOwner = true; } } int id = getObjectId(); String name = generateConstraintName(table); ConstraintReferential ref = new ConstraintReferential(getSchema(), id, name, table); ref.setColumns(indexColumns); ref.setIndex(index, isOwner); ref.setRefTable(refTable); ref.setRefColumns(refIndexColumns); ref.setRefIndex(refIndex, isRefOwner); if (checkExisting) { ref.checkExistingData(session); } constraint = ref; refTable.addConstraint(constraint); ref.setDeleteAction(deleteAction); ref.setUpdateAction(updateAction); break; } default: throw DbException.throwInternalError("type=" + type); } // parent relationship is already set with addConstraint constraint.setComment(comment); if (table.isTemporary() && !table.isGlobalTemporary()) { session.addLocalTempTableConstraint(constraint); } else { db.addSchemaObject(session, constraint); } table.addConstraint(constraint); return 0; } private Index createIndex(Table t, IndexColumn[] cols, boolean unique) { int indexId = getObjectId(); IndexType indexType; if (unique) { // for unique constraints indexType = IndexType.createUnique(t.isPersistIndexes(), false); } else { // constraints indexType = IndexType.createNonUnique(t.isPersistIndexes()); } indexType.setBelongsToConstraint(true); String prefix = constraintName == null ? "CONSTRAINT" : constraintName; String indexName = t.getSchema().getUniqueIndexName(session, t, prefix + "_INDEX_"); try { Index index = t.addIndex(session, indexName, indexId, cols, indexType, true, null); createdIndexes.add(index); return index; } finally { getSchema().freeUniqueName(indexName); } } public void setDeleteAction(int action) { this.deleteAction = action; } public void setUpdateAction(int action) { this.updateAction = action; } private static Index getUniqueIndex(Table t, IndexColumn[] cols) { for (Index idx : t.getIndexes()) { if (canUseUniqueIndex(idx, t, cols)) { return idx; } } return null; } private static Index getIndex(Table t, IndexColumn[] cols, boolean moreColumnOk) { for (Index idx : t.getIndexes()) { if (canUseIndex(idx, t, cols, moreColumnOk)) { return idx; } } return null; } private static boolean canUseUniqueIndex(Index idx, Table table, IndexColumn[] cols) { if (idx.getTable() != table || !idx.getIndexType().isUnique()) { return false; } Column[] indexCols = idx.getColumns(); if (indexCols.length > cols.length) { return false; } HashSet<Column> set = New.hashSet(); for (IndexColumn c : cols) { set.add(c.column); } for (Column c : indexCols) { // all columns of the index must be part of the list, // but not all columns of the list need to be part of the index if (!set.contains(c)) { return false; } } return true; } private static boolean canUseIndex( Index existingIndex, Table table, IndexColumn[] cols, boolean moreColumnsOk) { if (existingIndex.getTable() != table || existingIndex.getCreateSQL() == null) { // can't use the scan index or index of another table return false; } Column[] indexCols = existingIndex.getColumns(); if (moreColumnsOk) { if (indexCols.length < cols.length) { return false; } for (IndexColumn col : cols) { // all columns of the list must be part of the index, // but not all columns of the index need to be part of the list // holes are not allowed (index=a,b,c & list=a,b is ok; // but list=a,c is not) int idx = existingIndex.getColumnIndex(col.column); if (idx < 0 || idx >= cols.length) { return false; } } } else { if (indexCols.length != cols.length) { return false; } for (IndexColumn col : cols) { // all columns of the list must be part of the index int idx = existingIndex.getColumnIndex(col.column); if (idx < 0) { return false; } } } return true; } public void setConstraintName(String constraintName) { this.constraintName = constraintName; } public void setType(int type) { this.type = type; } @Override public int getType() { return type; } public void setCheckExpression(Expression expression) { this.checkExpression = expression; } public void setTableName(String tableName) { this.tableName = tableName; } public void setIndexColumns(IndexColumn[] indexColumns) { this.indexColumns = indexColumns; } public IndexColumn[] getIndexColumns() { return indexColumns; } /** * Set the referenced table. * * @param refSchema the schema * @param ref the table name */ public void setRefTableName(Schema refSchema, String ref) { this.refSchema = refSchema; this.refTableName = ref; } public void setRefIndexColumns(IndexColumn[] indexColumns) { this.refIndexColumns = indexColumns; } public void setIndex(Index index) { this.index = index; } public void setRefIndex(Index refIndex) { this.refIndex = refIndex; } public void setComment(String comment) { this.comment = comment; } public void setCheckExisting(boolean b) { this.checkExisting = b; } public void setPrimaryKeyHash(boolean b) { this.primaryKeyHash = b; } }