001: /**
002: * com.mckoi.database.interpret.AlterTable 14 Sep 2001
003: *
004: * Mckoi SQL Database ( http://www.mckoi.com/database )
005: * Copyright (C) 2000, 2001, 2002 Diehl and Associates, Inc.
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License
009: * Version 2 as published by the Free Software Foundation.
010: *
011: * This program is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014: * GNU General Public License Version 2 for more details.
015: *
016: * You should have received a copy of the GNU General Public License
017: * Version 2 along with this program; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
019: *
020: * Change Log:
021: *
022: *
023: */package com.mckoi.database.interpret;
024:
025: import com.mckoi.database.*;
026: import com.mckoi.util.IntegerVector;
027: import java.util.ArrayList;
028: import java.util.List;
029:
030: /**
031: * Logic for the ALTER TABLE SQL statement.
032: *
033: * @author Tobias Downer
034: */
035:
036: public class AlterTable extends Statement {
037:
038: /**
039: * The create statement that we use to alter the current table. This is
040: * only for compatibility reasons.
041: */
042: StatementTree create_statement;
043:
044: /**
045: * The name of the table we are altering.
046: */
047: String table_name;
048:
049: /**
050: * The list of actions to perform in this alter statement.
051: */
052: private ArrayList actions;
053:
054: /**
055: * The TableName object.
056: */
057: private TableName tname;
058:
059: /**
060: * The prepared create table statement.
061: */
062: CreateTable create_stmt;
063:
064: /**
065: * Adds an action to perform in this alter statement.
066: */
067: public void addAction(AlterTableAction action) {
068: if (actions == null) {
069: actions = new ArrayList();
070: }
071: actions.add(action);
072: }
073:
074: /**
075: * Returns true if the column names match. If the database is in case
076: * insensitive mode then the columns will match if the case insensitive
077: * search matches.
078: */
079: public boolean checkColumnNamesMatch(DatabaseConnection db,
080: String col1, String col2) {
081: if (db.isInCaseInsensitiveMode()) {
082: return col1.equalsIgnoreCase(col2);
083: }
084: return col1.equals(col2);
085: }
086:
087: private void checkColumnConstraint(String col_name, String[] cols,
088: TableName table, String constraint_name) {
089: for (int i = 0; i < cols.length; ++i) {
090: if (col_name.equals(cols[i])) {
091: throw new DatabaseConstraintViolationException(
092: DatabaseConstraintViolationException.DROP_COLUMN_VIOLATION,
093: "Constraint violation (" + constraint_name
094: + ") dropping column " + col_name
095: + " because of "
096: + "referential constraint in " + table);
097: }
098: }
099:
100: }
101:
102: // ---------- Implemented from Statement ----------
103:
104: public void prepare() throws DatabaseException {
105:
106: // Get variables from the model
107: table_name = (String) cmd.getObject("table_name");
108: addAction((AlterTableAction) cmd.getObject("alter_action"));
109: create_statement = (StatementTree) cmd
110: .getObject("create_statement");
111:
112: // ---
113:
114: if (create_statement != null) {
115: create_stmt = new CreateTable();
116: create_stmt.init(database, create_statement, null);
117: create_stmt.prepare();
118: this .table_name = create_stmt.table_name;
119: // create_statement.doPrepare(db, user);
120: } else {
121: // If we don't have a create statement, then this is an SQL alter
122: // command.
123: }
124:
125: // tname = TableName.resolve(db.getCurrentSchema(), table_name);
126: tname = resolveTableName(table_name, database);
127: if (tname.getName().indexOf('.') != -1) {
128: throw new DatabaseException(
129: "Table name can not contain '.' character.");
130: }
131:
132: }
133:
134: public Table evaluate() throws DatabaseException {
135:
136: DatabaseQueryContext context = new DatabaseQueryContext(
137: database);
138:
139: String schema_name = database.getCurrentSchema();
140:
141: // Does the user have privs to alter this tables?
142: if (!database.getDatabase().canUserAlterTableObject(context,
143: user, tname)) {
144: throw new UserAccessException(
145: "User not permitted to alter table: " + table_name);
146: }
147:
148: if (create_statement != null) {
149:
150: // Create the data table definition and tell the database to update it.
151: DataTableDef table_def = create_stmt.createDataTableDef();
152: TableName tname = table_def.getTableName();
153: // Is the table in the database already?
154: if (database.tableExists(tname)) {
155: // Drop any schema for this table,
156: database.dropAllConstraintsForTable(tname);
157: database.updateTable(table_def);
158: }
159: // If the table isn't in the database,
160: else {
161: database.createTable(table_def);
162: }
163:
164: // Setup the constraints
165: create_stmt.setupAllConstraints();
166:
167: // Return '0' if we created the table.
168: return FunctionTable.resultTable(context, 0);
169: } else {
170: // SQL alter command using the alter table actions,
171:
172: // Get the table definition for the table name,
173: DataTableDef table_def = database.getTable(tname)
174: .getDataTableDef();
175: String table_name = table_def.getName();
176: DataTableDef new_table = table_def.noColumnCopy();
177:
178: // Returns a ColumnChecker implementation for this table.
179: ColumnChecker checker = ColumnChecker
180: .standardColumnChecker(database, tname);
181:
182: // Set to true if the table topology is alter, or false if only
183: // the constraints are changed.
184: boolean table_altered = false;
185:
186: for (int n = 0; n < table_def.columnCount(); ++n) {
187: DataTableColumnDef column = new DataTableColumnDef(
188: table_def.columnAt(n));
189: String col_name = column.getName();
190: // Apply any actions to this column
191: boolean mark_dropped = false;
192: for (int i = 0; i < actions.size(); ++i) {
193: AlterTableAction action = (AlterTableAction) actions
194: .get(i);
195: if (action.getAction().equals("ALTERSET")
196: && checkColumnNamesMatch(database,
197: (String) action.getElement(0),
198: col_name)) {
199: Expression exp = (Expression) action
200: .getElement(1);
201: checker.checkExpression(exp);
202: column.setDefaultExpression(exp);
203: table_altered = true;
204: } else if (action.getAction().equals("DROPDEFAULT")
205: && checkColumnNamesMatch(database,
206: (String) action.getElement(0),
207: col_name)) {
208: column.setDefaultExpression(null);
209: table_altered = true;
210: } else if (action.getAction().equals("DROP")
211: && checkColumnNamesMatch(database,
212: (String) action.getElement(0),
213: col_name)) {
214: // Check there are no referential links to this column
215: Transaction.ColumnGroupReference[] refs = database
216: .queryTableImportedForeignKeyReferences(tname);
217: for (int p = 0; p < refs.length; ++p) {
218: checkColumnConstraint(col_name,
219: refs[p].ref_columns,
220: refs[p].ref_table_name,
221: refs[p].name);
222: }
223: // Or from it
224: refs = database
225: .queryTableForeignKeyReferences(tname);
226: for (int p = 0; p < refs.length; ++p) {
227: checkColumnConstraint(col_name,
228: refs[p].key_columns,
229: refs[p].key_table_name,
230: refs[p].name);
231: }
232: // Or that it's part of a primary key
233: Transaction.ColumnGroup primary_key = database
234: .queryTablePrimaryKeyGroup(tname);
235: if (primary_key != null) {
236: checkColumnConstraint(col_name,
237: primary_key.columns, tname,
238: primary_key.name);
239: }
240: // Or that it's part of a unique set
241: Transaction.ColumnGroup[] uniques = database
242: .queryTableUniqueGroups(tname);
243: for (int p = 0; p < uniques.length; ++p) {
244: checkColumnConstraint(col_name,
245: uniques[p].columns, tname,
246: uniques[p].name);
247: }
248:
249: mark_dropped = true;
250: table_altered = true;
251: }
252: }
253: // If not dropped then add to the new table definition.
254: if (!mark_dropped) {
255: new_table.addColumn(column);
256: }
257: }
258:
259: // Add any new columns,
260: for (int i = 0; i < actions.size(); ++i) {
261: AlterTableAction action = (AlterTableAction) actions
262: .get(i);
263: if (action.getAction().equals("ADD")) {
264: ColumnDef cdef = (ColumnDef) action.getElement(0);
265: if (cdef.isUnique() || cdef.isPrimaryKey()) {
266: throw new DatabaseException(
267: "Can not use UNIQUE or PRIMARY KEY "
268: + "column constraint when altering a column. Use "
269: + "ADD CONSTRAINT instead.");
270: }
271: // Convert to a DataTableColumnDef
272: DataTableColumnDef col = CreateTable
273: .convertColumnDef(cdef);
274:
275: checker
276: .checkExpression(col
277: .getDefaultExpression(database
278: .getSystem()));
279: String col_name = col.getName();
280: // If column name starts with [table_name]. then strip it off
281: col.setName(checker.stripTableName(table_name,
282: col_name));
283: new_table.addColumn(col);
284: table_altered = true;
285: }
286: }
287:
288: // Any constraints to drop...
289: for (int i = 0; i < actions.size(); ++i) {
290: AlterTableAction action = (AlterTableAction) actions
291: .get(i);
292: if (action.getAction().equals("DROP_CONSTRAINT")) {
293: String constraint_name = (String) action
294: .getElement(0);
295: int drop_count = database.dropNamedConstraint(
296: tname, constraint_name);
297: if (drop_count == 0) {
298: throw new DatabaseException(
299: "Named constraint to drop on table "
300: + tname + " was not found: "
301: + constraint_name);
302: }
303: } else if (action.getAction().equals(
304: "DROP_CONSTRAINT_PRIMARY_KEY")) {
305: boolean constraint_dropped = database
306: .dropPrimaryKeyConstraintForTable(tname,
307: null);
308: if (!constraint_dropped) {
309: throw new DatabaseException(
310: "No primary key to delete on table "
311: + tname);
312: }
313: }
314: }
315:
316: // Any constraints to add...
317: for (int i = 0; i < actions.size(); ++i) {
318: AlterTableAction action = (AlterTableAction) actions
319: .get(i);
320: if (action.getAction().equals("ADD_CONSTRAINT")) {
321: ConstraintDef constraint = (ConstraintDef) action
322: .getElement(0);
323: boolean foreign_constraint = (constraint.type == ConstraintDef.FOREIGN_KEY);
324: TableName ref_tname = null;
325: if (foreign_constraint) {
326: ref_tname = resolveTableName(
327: constraint.reference_table_name,
328: database);
329: if (database.isInCaseInsensitiveMode()) {
330: ref_tname = database
331: .tryResolveCase(ref_tname);
332: }
333: constraint.reference_table_name = ref_tname
334: .toString();
335: }
336:
337: checker.stripColumnList(table_name,
338: constraint.column_list);
339: checker.stripColumnList(
340: constraint.reference_table_name,
341: constraint.column_list2);
342: checker
343: .checkExpression(constraint.check_expression);
344: checker.checkColumnList(constraint.column_list);
345: if (foreign_constraint
346: && constraint.column_list2 != null) {
347: ColumnChecker referenced_checker = ColumnChecker
348: .standardColumnChecker(database,
349: ref_tname);
350: referenced_checker
351: .checkColumnList(constraint.column_list2);
352: }
353:
354: CreateTable.addSchemaConstraint(database, tname,
355: constraint);
356:
357: }
358: }
359:
360: // Alter the existing table to the new format...
361: if (table_altered) {
362: if (new_table.columnCount() == 0) {
363: throw new DatabaseException(
364: "Can not ALTER table to have 0 columns.");
365: }
366: database.updateTable(new_table);
367: } else {
368: // If the table wasn't physically altered, check the constraints.
369: // Calling this method will also make the transaction check all
370: // deferred constraints during the next commit.
371: database.checkAllConstraints(tname);
372: }
373:
374: // Return '0' if everything successful.
375: return FunctionTable.resultTable(context, 0);
376:
377: }
378:
379: }
380:
381: }
|