001: /*
002: * $Id: AlterTableCommand.java,v 1.12 2007/11/13 19:04:02 rwald Exp $
003: * =======================================================================
004: * Copyright (c) 2004 Axion Development Team. All rights reserved.
005: *
006: * Redistribution and use in source and binary forms, with or without
007: * modification, are permitted provided that the following conditions
008: * are met:
009: *
010: * 1. Redistributions of source code must retain the above
011: * copyright notice, this list of conditions and the following
012: * disclaimer.
013: *
014: * 2. Redistributions in binary form must reproduce the above copyright
015: * notice, this list of conditions and the following disclaimer in
016: * the documentation and/or other materials provided with the
017: * distribution.
018: *
019: * 3. The names "Tigris", "Axion", nor the names of its contributors may
020: * not be used to endorse or promote products derived from this
021: * software without specific prior written permission.
022: *
023: * 4. Products derived from this software may not be called "Axion", nor
024: * may "Tigris" or "Axion" appear in their names without specific prior
025: * written permission.
026: *
027: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
028: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
029: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
030: * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
031: * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
032: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
033: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
034: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
035: * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
036: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
037: * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
038: * =======================================================================
039: */
040: package org.axiondb.engine.commands;
041:
042: import java.util.ArrayList;
043: import java.util.Collection;
044: import java.util.Collections;
045: import java.util.Iterator;
046: import java.util.List;
047: import java.util.Properties;
048:
049: import org.axiondb.AxionCommand;
050: import org.axiondb.AxionException;
051: import org.axiondb.Column;
052: import org.axiondb.ColumnIdentifier;
053: import org.axiondb.Constraint;
054: import org.axiondb.Database;
055: import org.axiondb.ExternalTable;
056: import org.axiondb.Function;
057: import org.axiondb.Index;
058: import org.axiondb.IndexFactory;
059: import org.axiondb.Selectable;
060: import org.axiondb.Table;
061: import org.axiondb.constraints.BaseSelectableBasedConstraint;
062: import org.axiondb.constraints.CheckConstraint;
063: import org.axiondb.constraints.ForeignKeyConstraint;
064: import org.axiondb.engine.TransactableTableImpl;
065: import org.axiondb.engine.tables.BaseFlatfileTable;
066: import org.axiondb.engine.visitors.TableColumnsUsedInFunctionVisitor;
067: import org.axiondb.jdbc.AxionResultSet;
068:
069: /**
070: * A <tt>ALTER TABLE tableName ADD | DROP |ALTER column definition <tt> command.
071: *
072: * <p> NOTE: Identity column can't be altered.
073: * <p> NOTE: Renaming column require cascade to drop constraints and indexes and then rename
074: * <p> TODO: ADD [COLUMN] .... [BEFORE <existingcolumn>]
075: *
076: * @version $Revision: 1.12 $ $Date: 2007/11/13 19:04:02 $
077: * @author Ahimanikya Satapathy
078: */
079: public class AlterTableCommand extends BaseAxionCommand {
080:
081: public AlterTableCommand(String theTableName, boolean cascade) {
082: _tableName = theTableName;
083: _cascade = cascade;
084: _createTempTableCmd = new CreateTableCommand(generateName());
085: }
086:
087: public void addChildCommand(AxionCommand cmd) {
088: _childCommands.add(cmd);
089: }
090:
091: public void addColumn(String name, String type, String precision,
092: String scale, Selectable defaultValue, String generated) {
093: _createTempTableCmd.addColumn(name, type, precision, scale,
094: defaultValue, generated);
095: }
096:
097: public void alterColumn(String name, String newName,
098: Selectable newDefault, Boolean dropDefault) {
099: _createTempTableCmd.alterColumn(name, newName, newDefault,
100: dropDefault);
101: _alterColumn = (newName == null ? null : name);
102: }
103:
104: public void dropColumn(String colName) {
105: _createTempTableCmd.excludeColumn(colName);
106: }
107:
108: public boolean execute(Database db) throws AxionException {
109: executeUpdate(db);
110: return false;
111: }
112:
113: /** Unsupported */
114: public AxionResultSet executeQuery(Database database)
115: throws AxionException {
116: throw new UnsupportedOperationException("Use execute.");
117: }
118:
119: public int executeUpdate(Database db) throws AxionException {
120: assertNotReadOnly(db);
121:
122: Table table = db.getTable(_tableName);
123: if (null == table) {
124: throw new AxionException("Table " + _tableName
125: + " not found.");
126: }
127:
128: List depedentViews = db.getDependentViews(_tableName);
129: if (depedentViews.size() > 0) {
130: if (_cascade) {
131: db.dropDependentViews(depedentViews);
132: } else {
133: throw new AxionException("Can't Atlter Table/View: "
134: + _tableName
135: + " has reference in another View...");
136: }
137: }
138:
139: if (_newTableName != null) {
140: db.renameTable(_tableName, _newTableName);
141: setEffectedRowCount(0);
142: return 0;
143: }
144:
145: // create temp table using "create-as-subquery" command
146: _createTempTableCmd.setSourceTable(table);
147:
148: // 2. copy old table index and constraint information
149: List indexesToAdd = checkAndMigrateIndexes(db, table);
150: List constraintToAdd = checkAndMigrateConstraints(table);
151:
152: handleExternalTable(table);
153: _createTempTableCmd.execute(db);
154:
155: // 3. drop old table
156: table.shutdown();
157: db.dropTable(_tableName);
158:
159: // 4. rename new table to old table
160: db.renameTable(_createTempTableCmd.getObjectName(), _tableName);
161:
162: table = db.getTable(_tableName);
163:
164: // 5. restore and populate index and constraint for the new table
165: for (Iterator iter = indexesToAdd.iterator(); iter.hasNext();) {
166: db.addIndex(((Index) iter.next()), table, true);
167: }
168:
169: for (Iterator iter = constraintToAdd.iterator(); iter.hasNext();) {
170: table.addConstraint((Constraint) iter.next());
171: }
172:
173: for (Iterator iter = _childCommands.iterator(); iter.hasNext();) {
174: AxionCommand cmd = (AxionCommand) (iter.next());
175: cmd.execute(db);
176: }
177:
178: int rowcount = table.getRowCount();
179: setEffectedRowCount(rowcount);
180: return rowcount;
181: }
182:
183: public void setRenameTo(String newName) {
184: _newTableName = newName;
185: }
186:
187: private List checkAndMigrateConstraints(Table table)
188: throws AxionException {
189: // if column name has been changed/droped, check whether oldname has references
190: List constraintToAdd = new ArrayList();
191: for (Iterator iter = table.getConstraints(); iter.hasNext();) {
192: Constraint constraint = (Constraint) iter.next();
193: if (constraint instanceof ForeignKeyConstraint) {
194: ForeignKeyConstraint fk = ((ForeignKeyConstraint) constraint);
195: List cols = Collections.EMPTY_LIST;
196: if (_tableName.equals(fk.getParentTableName())) {
197: cols = fk.getParentTableColumns();
198: } else if (_tableName.equals(fk.getChildTableName())) {
199: cols = fk.getChildTableColumns();
200: }
201: checkColumnReference(constraintToAdd, fk, cols);
202:
203: } else if (constraint instanceof BaseSelectableBasedConstraint) {
204: BaseSelectableBasedConstraint c = (BaseSelectableBasedConstraint) constraint;
205: for (int k = 0, K = c.getSelectableCount(); k < K; k++) {
206: checkColumnReference(table, constraintToAdd, c, c
207: .getSelectable(k));
208: }
209:
210: } else if (constraint instanceof CheckConstraint) {
211: checkColumnReference(table, constraintToAdd,
212: constraint, ((CheckConstraint) constraint)
213: .getCondition());
214: } else if (_alterColumn != null && !_cascade) {
215: throw new AxionException("Can't drop/alter Column: "
216: + constraint.getName()
217: + " might have reference ");
218: }
219: }
220: return constraintToAdd;
221: }
222:
223: private List checkAndMigrateIndexes(Database db, Table table)
224: throws AxionException {
225: List<Index> indexesToAdd = new ArrayList<Index>();
226: for (Iterator iter = table.getIndices(); iter.hasNext();) {
227: Index i = (Index) iter.next();
228: Column col = i.getIndexedColumn();
229: if (_createTempTableCmd.isColumnEexcluded(col.getName())
230: && !_cascade) {
231: throw new AxionException(
232: "Can't drop Column: Index exist for "
233: + col.getName());
234: } else if (col.getName().equals(_alterColumn) && !_cascade) {
235: throw new AxionException(
236: "Can't alter Column: Index exist for "
237: + col.getName());
238: } else if (!_createTempTableCmd.isColumnEexcluded(col
239: .getName())
240: && !col.getName().equals(_alterColumn)) {
241: IndexFactory factory = db.getIndexFactory(i.getType());
242: Index index = factory.makeNewInstance(i.getName(), col,
243: i.isUnique(), db.getDBDirectory() == null);
244: indexesToAdd.add(index);
245: }
246: }
247: return indexesToAdd;
248: }
249:
250: private void checkColumnReference(List constraintToAdd,
251: Constraint c, Collection cols) throws AxionException {
252: for (Iterator i = cols.iterator(); i.hasNext();) {
253: checkColumnReference(constraintToAdd, c,
254: (ColumnIdentifier) i.next());
255: }
256: }
257:
258: private void checkColumnReference(List constraintToAdd,
259: Constraint c, ColumnIdentifier col) throws AxionException {
260: if (_createTempTableCmd.isColumnEexcluded(col.getName())
261: && !_cascade) {
262: throw new AxionException("Can't drop Column: "
263: + col.getName() + " used in constraint "
264: + c.getName());
265: } else if (col.getName().equals(_alterColumn) && !_cascade) {
266: throw new AxionException("Can't alter Column: "
267: + col.getName() + " used in constraint "
268: + c.getName());
269: } else if (!_createTempTableCmd
270: .isColumnEexcluded(col.getName())
271: && !col.getName().equals(_alterColumn)) {
272: constraintToAdd.add(c);
273: }
274: }
275:
276: private void checkColumnReference(Table table,
277: List constraintToAdd, Constraint c, Selectable sel)
278: throws AxionException {
279: if (sel instanceof ColumnIdentifier) {
280: checkColumnReference(constraintToAdd, c,
281: (ColumnIdentifier) sel);
282: } else if (sel instanceof Function) {
283: TableColumnsUsedInFunctionVisitor visitor = new TableColumnsUsedInFunctionVisitor();
284: visitor.visit((Function) sel, table);
285: checkColumnReference(constraintToAdd, c, visitor
286: .getColumnsUsedInFunction());
287: }
288: }
289:
290: private void handleExternalTable(Table table) {
291: if (table instanceof TransactableTableImpl) {
292: table = ((TransactableTableImpl) table).getTable();
293: }
294:
295: // carry the table properties for external table
296: if (table instanceof ExternalTable) {
297: Properties prop = ((ExternalTable) table)
298: .getTableProperties();
299: prop.remove(BaseFlatfileTable.PROP_FILENAME); // clear file
300: _createTempTableCmd.setProperties(prop);
301: _createTempTableCmd.setType("EXTERNAL");
302: }
303: }
304:
305: private static int _idCounter = 0;
306:
307: private static String generateName() {
308: return "TEMP_"
309: + Long.toHexString(System.currentTimeMillis())
310: .toUpperCase() + _idCounter++;
311: }
312:
313: private String _alterColumn;
314: private boolean _cascade = false;
315: private List _childCommands = new ArrayList(4);
316: private CreateTableCommand _createTempTableCmd;
317: private String _newTableName;
318: private String _tableName;
319: }
|