001: /*
002: * Copyright 2004-2008 H2 Group. Licensed under the H2 License, Version 1.0
003: * (http://h2database.com/html/license.html).
004: * Initial Developer: H2 Group
005: */
006: package org.h2.command.ddl;
007:
008: import java.sql.SQLException;
009: import java.util.HashSet;
010:
011: import org.h2.constant.ErrorCode;
012: import org.h2.constraint.Constraint;
013: import org.h2.constraint.ConstraintCheck;
014: import org.h2.constraint.ConstraintReferential;
015: import org.h2.constraint.ConstraintUnique;
016: import org.h2.engine.Constants;
017: import org.h2.engine.Database;
018: import org.h2.engine.DbObject;
019: import org.h2.engine.Right;
020: import org.h2.engine.Session;
021: import org.h2.expression.Expression;
022: import org.h2.index.Index;
023: import org.h2.index.IndexType;
024: import org.h2.message.Message;
025: import org.h2.schema.Schema;
026: import org.h2.table.Column;
027: import org.h2.table.IndexColumn;
028: import org.h2.table.Table;
029: import org.h2.table.TableFilter;
030: import org.h2.util.ObjectArray;
031:
032: /**
033: * This class represents the statement
034: * ALTER TABLE ADD CONSTRAINT
035: */
036: public class AlterTableAddConstraint extends SchemaCommand {
037:
038: public static final int CHECK = 0, UNIQUE = 1, REFERENTIAL = 2,
039: PRIMARY_KEY = 3;
040: private int type;
041: private String constraintName;
042: private String tableName;
043: private IndexColumn[] indexColumns;
044: private int deleteAction;
045: private int updateAction;
046: private Schema refSchema;
047: private String refTableName;
048: private IndexColumn[] refIndexColumns;
049: private Expression checkExpression;
050: private Index index, refIndex;
051: private String comment;
052: private boolean checkExisting;
053: private boolean primaryKeyHash;
054:
055: public AlterTableAddConstraint(Session session, Schema schema) {
056: super (session, schema);
057: }
058:
059: private String generateConstraintName(DbObject obj, int id)
060: throws SQLException {
061: if (constraintName == null) {
062: constraintName = getSchema().getUniqueConstraintName(obj);
063: }
064: return constraintName;
065: }
066:
067: public int update() throws SQLException {
068: try {
069: return tryUpdate();
070: } finally {
071: getSchema().freeUniqueName(constraintName);
072: }
073: }
074:
075: /**
076: * Try to execute the statement.
077: *
078: * @return the update count
079: */
080: public int tryUpdate() throws SQLException {
081: session.commit(true);
082: Database db = session.getDatabase();
083: Table table = getSchema().getTableOrView(session, tableName);
084: if (getSchema().findConstraint(constraintName) != null) {
085: throw Message.getSQLException(
086: ErrorCode.CONSTRAINT_ALREADY_EXISTS_1,
087: constraintName);
088: }
089: session.getUser().checkRight(table, Right.ALL);
090: table.lock(session, true, true);
091: Constraint constraint;
092: switch (type) {
093: case PRIMARY_KEY: {
094: IndexColumn.mapColumns(indexColumns, table);
095: index = table.findPrimaryKey();
096: ObjectArray constraints = table.getConstraints();
097: for (int i = 0; constraints != null
098: && i < constraints.size(); i++) {
099: Constraint c = (Constraint) constraints.get(i);
100: if (Constraint.PRIMARY_KEY
101: .equals(c.getConstraintType())) {
102: throw Message
103: .getSQLException(ErrorCode.SECOND_PRIMARY_KEY);
104: }
105: }
106: if (index != null) {
107: // if there is an index, it must match with the one declared
108: // we don't test ascending / descending
109: IndexColumn[] pkCols = index.getIndexColumns();
110: if (pkCols.length != indexColumns.length) {
111: throw Message
112: .getSQLException(ErrorCode.SECOND_PRIMARY_KEY);
113: }
114: for (int i = 0; i < pkCols.length; i++) {
115: if (pkCols[i].column != indexColumns[i].column) {
116: throw Message
117: .getSQLException(ErrorCode.SECOND_PRIMARY_KEY);
118: }
119: }
120: }
121: if (index == null) {
122: IndexType indexType = IndexType.createPrimaryKey(table
123: .isPersistent(), primaryKeyHash);
124: String indexName = table.getSchema()
125: .getUniqueIndexName(table,
126: Constants.PREFIX_PRIMARY_KEY);
127: int id = getObjectId(true, false);
128: try {
129: index = table.addIndex(session, indexName, id,
130: indexColumns, indexType, Index.EMPTY_HEAD,
131: null);
132: } finally {
133: getSchema().freeUniqueName(indexName);
134: }
135: }
136: index.getIndexType().setBelongsToConstraint(true);
137: int constraintId = getObjectId(true, true);
138: String name = generateConstraintName(table, constraintId);
139: ConstraintUnique pk = new ConstraintUnique(getSchema(),
140: constraintId, name, table, true);
141: pk.setColumns(indexColumns);
142: pk.setIndex(index, true);
143: constraint = pk;
144: break;
145: }
146: case UNIQUE: {
147: IndexColumn.mapColumns(indexColumns, table);
148: boolean isOwner = false;
149: if (index != null
150: && canUseUniqueIndex(index, table, indexColumns)) {
151: isOwner = true;
152: index.getIndexType().setBelongsToConstraint(true);
153: } else {
154: index = getUniqueIndex(table, indexColumns);
155: if (index == null) {
156: index = createIndex(table, indexColumns, true);
157: isOwner = true;
158: }
159: }
160: int id = getObjectId(true, true);
161: String name = generateConstraintName(table, id);
162: ConstraintUnique unique = new ConstraintUnique(getSchema(),
163: id, name, table, false);
164: unique.setColumns(indexColumns);
165: unique.setIndex(index, isOwner);
166: constraint = unique;
167: break;
168: }
169: case CHECK: {
170: int id = getObjectId(true, true);
171: String name = generateConstraintName(table, id);
172: ConstraintCheck check = new ConstraintCheck(getSchema(),
173: id, name, table);
174: TableFilter filter = new TableFilter(session, table, null,
175: false, null);
176: checkExpression.mapColumns(filter, 0);
177: checkExpression = checkExpression.optimize(session);
178: check.setExpression(checkExpression);
179: check.setTableFilter(filter);
180: constraint = check;
181: if (checkExisting) {
182: check.checkExistingData(session);
183: }
184: break;
185: }
186: case REFERENTIAL: {
187: Table refTable = refSchema.getTableOrView(session,
188: refTableName);
189: session.getUser().checkRight(refTable, Right.ALL);
190: boolean isOwner = false;
191: IndexColumn.mapColumns(indexColumns, table);
192: if (index != null
193: && canUseIndex(index, table, indexColumns)) {
194: isOwner = true;
195: index.getIndexType().setBelongsToConstraint(true);
196: } else {
197: index = getIndex(table, indexColumns);
198: if (index == null) {
199: index = createIndex(table, indexColumns, false);
200: isOwner = true;
201: }
202: }
203: if (refIndexColumns == null) {
204: Index refIdx = refTable.getPrimaryKey();
205: refIndexColumns = refIdx.getIndexColumns();
206: } else {
207: IndexColumn.mapColumns(refIndexColumns, refTable);
208: }
209: if (refIndexColumns.length != indexColumns.length) {
210: throw Message
211: .getSQLException(ErrorCode.COLUMN_COUNT_DOES_NOT_MATCH);
212: }
213: boolean isRefOwner = false;
214: if (refIndex != null && refIndex.getTable() == refTable) {
215: isRefOwner = true;
216: refIndex.getIndexType().setBelongsToConstraint(true);
217: } else {
218: refIndex = null;
219: }
220: if (refIndex == null) {
221: refIndex = getUniqueIndex(refTable, refIndexColumns);
222: if (refIndex == null) {
223: refIndex = createIndex(refTable, refIndexColumns,
224: true);
225: isRefOwner = true;
226: }
227: }
228: int id = getObjectId(true, true);
229: String name = generateConstraintName(table, id);
230: ConstraintReferential ref = new ConstraintReferential(
231: getSchema(), id, name, table);
232: ref.setColumns(indexColumns);
233: ref.setIndex(index, isOwner);
234: ref.setRefTable(refTable);
235: ref.setRefColumns(refIndexColumns);
236: ref.setRefIndex(refIndex, isRefOwner);
237: if (checkExisting) {
238: ref.checkExistingData(session);
239: }
240: constraint = ref;
241: refTable.addConstraint(constraint);
242: ref.setDeleteAction(session, deleteAction);
243: ref.setUpdateAction(session, updateAction);
244: break;
245: }
246: default:
247: throw Message.getInternalError("type=" + type);
248: }
249: // parent relationship is already set with addConstraint
250: constraint.setComment(comment);
251: db.addSchemaObject(session, constraint);
252: table.addConstraint(constraint);
253: return 0;
254: }
255:
256: private Index createIndex(Table t, IndexColumn[] cols,
257: boolean unique) throws SQLException {
258: int indexId = getObjectId(true, false);
259: IndexType indexType;
260: if (unique) {
261: // TODO default index (hash or not; memory or not or same as table)
262: // for unique constraints
263: indexType = IndexType.createUnique(t.isPersistent(), false);
264: } else {
265: // TODO default index (memory or not or same as table) for unique
266: // constraints
267: indexType = IndexType.createNonUnique(t.isPersistent());
268: }
269: indexType.setBelongsToConstraint(true);
270: String prefix = constraintName == null ? "CONSTRAINT"
271: : constraintName;
272: String indexName = t.getSchema().getUniqueIndexName(t,
273: prefix + "_INDEX_");
274: Index idx;
275: try {
276: idx = t.addIndex(session, indexName, indexId, cols,
277: indexType, Index.EMPTY_HEAD, null);
278: } finally {
279: getSchema().freeUniqueName(indexName);
280: }
281: return idx;
282: }
283:
284: public void setDeleteAction(int action) {
285: this .deleteAction = action;
286: }
287:
288: public void setUpdateAction(int action) {
289: this .updateAction = action;
290: }
291:
292: private Index getUniqueIndex(Table t, IndexColumn[] cols) {
293: ObjectArray list = t.getIndexes();
294: for (int i = 0; i < list.size(); i++) {
295: Index index = (Index) list.get(i);
296: if (canUseUniqueIndex(index, t, cols)) {
297: return index;
298: }
299: }
300: return null;
301: }
302:
303: private Index getIndex(Table t, IndexColumn[] cols) {
304: ObjectArray list = t.getIndexes();
305: for (int i = 0; i < list.size(); i++) {
306: Index index = (Index) list.get(i);
307: if (canUseIndex(index, t, cols)) {
308: return index;
309: }
310: }
311: return null;
312: }
313:
314: private boolean canUseUniqueIndex(Index index, Table table,
315: IndexColumn[] cols) {
316: if (index.getTable() != table
317: || !index.getIndexType().isUnique()) {
318: return false;
319: }
320: Column[] indexCols = index.getColumns();
321: if (indexCols.length > cols.length) {
322: return false;
323: }
324: HashSet set = new HashSet();
325: for (int i = 0; i < cols.length; i++) {
326: set.add(cols[i].column);
327: }
328: for (int j = 0; j < indexCols.length; j++) {
329: // all columns of the index must be part of the list,
330: // but not all columns of the list need to be part of the index
331: if (!set.contains(indexCols[j])) {
332: return false;
333: }
334: }
335: return true;
336: }
337:
338: private boolean canUseIndex(Index index, Table table,
339: IndexColumn[] cols) {
340: if (index.getTable() != table || index.getCreateSQL() == null) {
341: // can't use the scan index or index of another table
342: return false;
343: }
344: Column[] indexCols = index.getColumns();
345: if (indexCols.length < cols.length) {
346: return false;
347: }
348: for (int j = 0; j < cols.length; j++) {
349: // all columns of the list must be part of the index,
350: // but not all columns of the index need to be part of the list
351: // holes are not allowed (index=a,b,c & list=a,b is ok; but list=a,c
352: // is not)
353: int idx = index.getColumnIndex(cols[j].column);
354: if (idx < 0 || idx >= cols.length) {
355: return false;
356: }
357: }
358: return true;
359: }
360:
361: public void setConstraintName(String constraintName) {
362: this .constraintName = constraintName;
363: }
364:
365: public void setType(int type) {
366: this .type = type;
367: }
368:
369: public int getType() {
370: return type;
371: }
372:
373: public void setCheckExpression(Expression expression) {
374: this .checkExpression = expression;
375: }
376:
377: public void setTableName(String tableName) {
378: this .tableName = tableName;
379: }
380:
381: public void setIndexColumns(IndexColumn[] indexColumns) {
382: this .indexColumns = indexColumns;
383: }
384:
385: public IndexColumn[] getIndexColumns() {
386: return indexColumns;
387: }
388:
389: /**
390: * Set the referenced table.
391: *
392: * @param refSchema the schema
393: * @param ref the table name
394: */
395: public void setRefTableName(Schema refSchema, String ref) {
396: this .refSchema = refSchema;
397: this .refTableName = ref;
398: }
399:
400: public void setRefIndexColumns(IndexColumn[] indexColumns) {
401: this .refIndexColumns = indexColumns;
402: }
403:
404: public void setIndex(Index index) {
405: this .index = index;
406: }
407:
408: public void setRefIndex(Index refIndex) {
409: this .refIndex = refIndex;
410: }
411:
412: public void setComment(String comment) {
413: this .comment = comment;
414: }
415:
416: public void setCheckExisting(boolean b) {
417: this .checkExisting = b;
418: }
419:
420: public void setPrimaryKeyHash(boolean b) {
421: this .primaryKeyHash = b;
422: }
423:
424: public boolean getPrimaryKeyHash() {
425: return primaryKeyHash;
426: }
427:
428: }
|