File D:\maven-qcr-plugin-testsunitaires\qcr4java\h2-1.3.170-sources\target\site\qc\lecode\command\ddl\AlterTableAlterColumn.java.html

1 /*
2 * Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
3 * Version 1.0, and under the Eclipse Public License, Version 1.0
4 * (http://h2database.com/html/license.html).
5 * Initial Developer: H2 Group
6 */
7 package org.h2.command.ddl;
8
9 import java.util.ArrayList;
10 import java.util.HashSet;
11 import org.h2.command.CommandInterface;
12 import org.h2.command.Parser;
13 import org.h2.command.Prepared;
14 import org.h2.constant.ErrorCode;
15 import org.h2.constraint.Constraint;
16 import org.h2.constraint.ConstraintReferential;
17 import org.h2.engine.Database;
18 import org.h2.engine.DbObject;
19 import org.h2.engine.Right;
20 import org.h2.engine.Session;
21 import org.h2.expression.Expression;
22 import org.h2.expression.ExpressionVisitor;
23 import org.h2.index.Index;
24 import org.h2.index.IndexType;
25 import org.h2.message.DbException;
26 import org.h2.result.ResultInterface;
27 import org.h2.schema.Schema;
28 import org.h2.schema.SchemaObject;
29 import org.h2.schema.Sequence;
30 import org.h2.schema.TriggerObject;
31 import org.h2.table.Column;
32 import org.h2.table.Table;
33 import org.h2.table.TableView;
34 import org.h2.util.New;
35
36 /**
37 * This class represents the statements
38 * ALTER TABLE ADD,
39 * ALTER TABLE ADD IF NOT EXISTS,
40 * ALTER TABLE ALTER COLUMN,
41 * ALTER TABLE ALTER COLUMN RESTART,
42 * ALTER TABLE ALTER COLUMN SELECTIVITY,
43 * ALTER TABLE ALTER COLUMN SET DEFAULT,
44 * ALTER TABLE ALTER COLUMN SET NOT NULL,
45 * ALTER TABLE ALTER COLUMN SET NULL,
46 * ALTER TABLE DROP COLUMN
47 */
48 public class AlterTableAlterColumn extends SchemaCommand {
49
50 private Table table;
51 private Column oldColumn;
52 private Column newColumn;
53 private int type;
54 private Expression defaultExpression;
55 private Expression newSelectivity;
56 private String addBefore;
57 private boolean ifNotExists;
58 private ArrayList<Column> columnsToAdd;
59
60 public AlterTableAlterColumn(Session session, Schema schema) {
61 super(session, schema);
62 }
63
64 public void setTable(Table table) {
65 this.table = table;
66 }
67
68 public void setOldColumn(Column oldColumn) {
69 this.oldColumn = oldColumn;
70 }
71
72 public void setAddBefore(String before) {
73 this.addBefore = before;
74 }
75
76 public int update() {
77 session.commit(true);
78 Database db = session.getDatabase();
79 session.getUser().checkRight(table, Right.ALL);
80 table.checkSupportAlter();
81 table.lock(session, true, true);
82 Sequence sequence = oldColumn == null ? null : oldColumn.getSequence();
83 if (newColumn != null) {
84 checkDefaultReferencesTable(newColumn.getDefaultExpression());
85 }
86 if (columnsToAdd != null) {
87 for (Column column : columnsToAdd) {
88 checkDefaultReferencesTable(column.getDefaultExpression());
89 }
90 }
91 switch (type) {
92 case CommandInterface.ALTER_TABLE_ALTER_COLUMN_NOT_NULL: {
93 if (!oldColumn.isNullable()) {
94 // no change
95 break;
96 }
97 checkNoNullValues();
98 oldColumn.setNullable(false);
99 db.update(session, table);
100 break;
101 }
102 case CommandInterface.ALTER_TABLE_ALTER_COLUMN_NULL: {
103 if (oldColumn.isNullable()) {
104 // no change
105 break;
106 }
107 checkNullable();
108 oldColumn.setNullable(true);
109 db.update(session, table);
110 break;
111 }
112 case CommandInterface.ALTER_TABLE_ALTER_COLUMN_DEFAULT: {
113 checkDefaultReferencesTable(defaultExpression);
114 oldColumn.setSequence(null);
115 oldColumn.setDefaultExpression(session, defaultExpression);
116 removeSequence(sequence);
117 db.update(session, table);
118 break;
119 }
120 case CommandInterface.ALTER_TABLE_ALTER_COLUMN_CHANGE_TYPE: {
121 // if the change is only increasing the precision, then we don't
122 // need to copy the table because the length is only a constraint,
123 // and does not affect the storage structure.
124 if (oldColumn.isWideningConversion(newColumn)) {
125 convertAutoIncrementColumn(newColumn);
126 oldColumn.copy(newColumn);
127 db.update(session, table);
128 } else {
129 oldColumn.setSequence(null);
130 oldColumn.setDefaultExpression(session, null);
131 oldColumn.setConvertNullToDefault(false);
132 if (oldColumn.isNullable() && !newColumn.isNullable()) {
133 checkNoNullValues();
134 } else if (!oldColumn.isNullable() && newColumn.isNullable()) {
135 checkNullable();
136 }
137 convertAutoIncrementColumn(newColumn);
138 copyData();
139 }
140 break;
141 }
142 case CommandInterface.ALTER_TABLE_ADD_COLUMN: {
143 // ifNotExists only supported for single column add
144 if (ifNotExists && columnsToAdd.size() == 1 && table.doesColumnExist(columnsToAdd.get(0).getName())) {
145 break;
146 }
147 for (Column column : columnsToAdd) {
148 convertAutoIncrementColumn(column);
149 }
150 copyData();
151 break;
152 }
153 case CommandInterface.ALTER_TABLE_DROP_COLUMN: {
154 if (table.getColumns().length == 1) {
155 throw DbException.get(ErrorCode.CANNOT_DROP_LAST_COLUMN, oldColumn.getSQL());
156 }
157 table.dropSingleColumnConstraintsAndIndexes(session, oldColumn);
158 copyData();
159 break;
160 }
161 case CommandInterface.ALTER_TABLE_ALTER_COLUMN_SELECTIVITY: {
162 int value = newSelectivity.optimize(session).getValue(session).getInt();
163 oldColumn.setSelectivity(value);
164 db.update(session, table);
165 break;
166 }
167 default:
168 DbException.throwInternalError("type=" + type);
169 }
170 return 0;
171 }
172
173 private void checkDefaultReferencesTable(Expression defaultExpression) {
174 if (defaultExpression == null) {
175 return;
176 }
177 HashSet<DbObject> dependencies = New.hashSet();
178 ExpressionVisitor visitor = ExpressionVisitor.getDependenciesVisitor(dependencies);
179 defaultExpression.isEverything(visitor);
180 if (dependencies.contains(table)) {
181 throw DbException.get(ErrorCode.COLUMN_IS_REFERENCED_1, defaultExpression.getSQL());
182 }
183 }
184
185 private void convertAutoIncrementColumn(Column c) {
186 if (c.isAutoIncrement()) {
187 if (c.isPrimaryKey()) {
188 c.setOriginalSQL("IDENTITY");
189 } else {
190 int objId = getObjectId();
191 c.convertAutoIncrementToSequence(session, getSchema(), objId, table.isTemporary());
192 }
193 }
194 }
195
196 private void removeSequence(Sequence sequence) {
197 if (sequence != null) {
198 table.removeSequence(sequence);
199 sequence.setBelongsToTable(false);
200 Database db = session.getDatabase();
201 db.removeSchemaObject(session, sequence);
202 }
203 }
204
205 private void copyData() {
206 if (table.isTemporary()) {
207 throw DbException.getUnsupportedException("TEMP TABLE");
208 }
209 Database db = session.getDatabase();
210 String baseName = table.getName();
211 String tempName = db.getTempTableName(baseName, session);
212 Column[] columns = table.getColumns();
213 ArrayList<Column> newColumns = New.arrayList();
214 Table newTable = cloneTableStructure(columns, db, tempName, newColumns);
215 try {
216 // check if a view would become invalid
217 // (because the column to drop is referenced or so)
218 checkViews(table, newTable);
219 } catch (DbException e) {
220 execute("DROP TABLE " + newTable.getName(), true);
221 throw DbException.get(ErrorCode.VIEW_IS_INVALID_2, e, getSQL(), e.getMessage());
222 }
223 String tableName = table.getName();
224 ArrayList<TableView> views = table.getViews();
225 if (views != null) {
226 views = New.arrayList(views);
227 for (TableView view : views) {
228 table.removeView(view);
229 }
230 }
231 execute("DROP TABLE " + table.getSQL() + " IGNORE", true);
232 db.renameSchemaObject(session, newTable, tableName);
233 for (DbObject child : newTable.getChildren()) {
234 if (child instanceof Sequence) {
235 continue;
236 }
237 String name = child.getName();
238 if (name == null || child.getCreateSQL() == null) {
239 continue;
240 }
241 if (name.startsWith(tempName + "_")) {
242 name = name.substring(tempName.length() + 1);
243 SchemaObject so = (SchemaObject) child;
244 if (so instanceof Constraint) {
245 if (so.getSchema().findConstraint(session, name) != null) {
246 name = so.getSchema().getUniqueConstraintName(session, newTable);
247 }
248 } else if (so instanceof Index) {
249 if (so.getSchema().findIndex(session, name) != null) {
250 name = so.getSchema().getUniqueIndexName(session, newTable, name);
251 }
252 }
253 db.renameSchemaObject(session, so, name);
254 }
255 }
256 if (views != null) {
257 for (TableView view : views) {
258 String sql = view.getCreateSQL(true, true);
259 execute(sql, true);
260 }
261 }
262 }
263
264 private Table cloneTableStructure(Column[] columns, Database db, String tempName, ArrayList<Column> newColumns) {
265 for (Column col : columns) {
266 newColumns.add(col.getClone());
267 }
268 if (type == CommandInterface.ALTER_TABLE_DROP_COLUMN) {
269 int position = oldColumn.getColumnId();
270 newColumns.remove(position);
271 } else if (type == CommandInterface.ALTER_TABLE_ADD_COLUMN) {
272 int position;
273 if (addBefore == null) {
274 position = columns.length;
275 } else {
276 position = table.getColumn(addBefore).getColumnId();
277 }
278 for (Column column : columnsToAdd) {
279 newColumns.add(position++, column);
280 }
281 } else if (type == CommandInterface.ALTER_TABLE_ALTER_COLUMN_CHANGE_TYPE) {
282 int position = oldColumn.getColumnId();
283 newColumns.remove(position);
284 newColumns.add(position, newColumn);
285 }
286
287 // create a table object in order to get the SQL statement
288 // can't just use this table, because most column objects are 'shared'
289 // with the old table
290 // still need a new id because using 0 would mean: the new table tries
291 // to use the rows of the table 0 (the meta table)
292 int id = db.allocateObjectId();
293 CreateTableData data = new CreateTableData();
294 data.tableName = tempName;
295 data.id = id;
296 data.columns = newColumns;
297 data.temporary = table.isTemporary();
298 data.persistData = table.isPersistData();
299 data.persistIndexes = table.isPersistIndexes();
300 data.isHidden = table.isHidden();
301 data.create = true;
302 data.session = session;
303 Table newTable = getSchema().createTable(data);
304 newTable.setComment(table.getComment());
305 StringBuilder buff = new StringBuilder();
306 buff.append(newTable.getCreateSQL());
307 StringBuilder columnList = new StringBuilder();
308 for (Column nc : newColumns) {
309 if (columnList.length() > 0) {
310 columnList.append(", ");
311 }
312 if (type == CommandInterface.ALTER_TABLE_ADD_COLUMN && columnsToAdd.contains(nc)) {
313 Expression def = nc.getDefaultExpression();
314 columnList.append(def == null ? "NULL" : def.getSQL());
315 } else {
316 columnList.append(nc.getSQL());
317 }
318 }
319 buff.append(" AS SELECT ");
320 if (columnList.length() == 0) {
321 // special case: insert into test select * from
322 buff.append('*');
323 } else {
324 buff.append(columnList);
325 }
326 buff.append(" FROM ").append(table.getSQL());
327 String newTableSQL = buff.toString();
328 String newTableName = newTable.getName();
329 Schema newTableSchema = newTable.getSchema();
330 newTable.removeChildrenAndResources(session);
331
332 execute(newTableSQL, true);
333 newTable = newTableSchema.getTableOrView(session, newTableName);
334 ArrayList<String> triggers = New.arrayList();
335 for (DbObject child : table.getChildren()) {
336 if (child instanceof Sequence) {
337 continue;
338 } else if (child instanceof Index) {
339 Index idx = (Index) child;
340 if (idx.getIndexType().getBelongsToConstraint()) {
341 continue;
342 }
343 }
344 String createSQL = child.getCreateSQL();
345 if (createSQL == null) {
346 continue;
347 }
348 if (child instanceof TableView) {
349 continue;
350 } else if (child.getType() == DbObject.TABLE_OR_VIEW) {
351 DbException.throwInternalError();
352 }
353 String quotedName = Parser.quoteIdentifier(tempName + "_" + child.getName());
354 String sql = null;
355 if (child instanceof ConstraintReferential) {
356 ConstraintReferential r = (ConstraintReferential) child;
357 if (r.getTable() != table) {
358 sql = r.getCreateSQLForCopy(r.getTable(), newTable, quotedName, false);
359 }
360 }
361 if (sql == null) {
362 sql = child.getCreateSQLForCopy(newTable, quotedName);
363 }
364 if (sql != null) {
365 if (child instanceof TriggerObject) {
366 triggers.add(sql);
367 } else {
368 execute(sql, true);
369 }
370 }
371 }
372 table.setModified();
373 // remove the sequences from the columns (except dropped columns)
374 // otherwise the sequence is dropped if the table is dropped
375 for (Column col : newColumns) {
376 Sequence seq = col.getSequence();
377 if (seq != null) {
378 table.removeSequence(seq);
379 col.setSequence(null);
380 }
381 }
382 for (String sql : triggers) {
383 execute(sql, true);
384 }
385 return newTable;
386 }
387
388 /**
389 * Check that all views and other dependent objects.
390 */
391 private void checkViews(SchemaObject sourceTable, SchemaObject newTable) {
392 String sourceTableName = sourceTable.getName();
393 String newTableName = newTable.getName();
394 Database db = sourceTable.getDatabase();
395 // save the real table under a temporary name
396 String temp = db.getTempTableName(sourceTableName, session);
397 db.renameSchemaObject(session, sourceTable, temp);
398 try {
399 // have our new table impersonate the target table
400 db.renameSchemaObject(session, newTable, sourceTableName);
401 checkViewsAreValid(sourceTable);
402 } finally {
403 // always put the source tables back with their proper names
404 try {
405 db.renameSchemaObject(session, newTable, newTableName);
406 } finally {
407 db.renameSchemaObject(session, sourceTable, sourceTableName);
408 }
409 }
410 }
411
412 /**
413 * Check that a table or view is still valid.
414 *
415 * @param tableOrView the table or view to check
416 */
417 private void checkViewsAreValid(DbObject tableOrView) {
418 for (DbObject view : tableOrView.getChildren()) {
419 if (view instanceof TableView) {
420 String sql = ((TableView) view).getQuery();
421 // check if the query is still valid
422 // do not execute, not even with limit 1, because that could
423 // have side effects or take a very long time
424 session.prepare(sql);
425 checkViewsAreValid(view);
426 }
427 }
428 }
429
430 private void execute(String sql, boolean ddl) {
431 Prepared command = session.prepare(sql);
432 command.update();
433 if (ddl) {
434 session.commit(true);
435 }
436 }
437
438 private void checkNullable() {
439 for (Index index : table.getIndexes()) {
440 if (index.getColumnIndex(oldColumn) < 0) {
441 continue;
442 }
443 IndexType indexType = index.getIndexType();
444 if (indexType.isPrimaryKey() || indexType.isHash()) {
445 throw DbException.get(ErrorCode.COLUMN_IS_PART_OF_INDEX_1, index.getSQL());
446 }
447 }
448 }
449
450 private void checkNoNullValues() {
451 String sql = "SELECT COUNT(*) FROM " + table.getSQL() + " WHERE " + oldColumn.getSQL() + " IS NULL";
452 Prepared command = session.prepare(sql);
453 ResultInterface result = command.query(0);
454 result.next();
455 if (result.currentRow()[0].getInt() > 0) {
456 throw DbException.get(ErrorCode.COLUMN_CONTAINS_NULL_VALUES_1, oldColumn.getSQL());
457 }
458 }
459
460 public void setType(int type) {
461 this.type = type;
462 }
463
464 public void setSelectivity(Expression selectivity) {
465 newSelectivity = selectivity;
466 }
467
468 public void setDefaultExpression(Expression defaultExpression) {
469 this.defaultExpression = defaultExpression;
470 }
471
472 public void setNewColumn(Column newColumn) {
473 this.newColumn = newColumn;
474 }
475
476 public int getType() {
477 return type;
478 }
479
480 public void setIfNotExists(boolean ifNotExists) {
481 this.ifNotExists = ifNotExists;
482 }
483
484 public void setNewColumns(ArrayList<Column> columnsToAdd) {
485 this.columnsToAdd = columnsToAdd;
486 }
487 }
488