1 | /* |
2 | * Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0, |
3 | * and the EPL 1.0 (http://h2database.com/html/license.html). |
4 | * Initial Developer: H2 Group |
5 | */ |
6 | package org.h2.command.ddl; |
7 | |
8 | import java.util.ArrayList; |
9 | import java.util.HashSet; |
10 | |
11 | import org.h2.api.ErrorCode; |
12 | import org.h2.command.CommandInterface; |
13 | import org.h2.command.dml.Insert; |
14 | import org.h2.command.dml.Query; |
15 | import org.h2.engine.Database; |
16 | import org.h2.engine.DbObject; |
17 | import org.h2.engine.Session; |
18 | import org.h2.expression.Expression; |
19 | import org.h2.message.DbException; |
20 | import org.h2.schema.Schema; |
21 | import org.h2.schema.Sequence; |
22 | import org.h2.table.Column; |
23 | import org.h2.table.IndexColumn; |
24 | import org.h2.table.Table; |
25 | import org.h2.util.New; |
26 | import org.h2.value.DataType; |
27 | |
28 | /** |
29 | * This class represents the statement |
30 | * CREATE TABLE |
31 | */ |
32 | public class CreateTable extends SchemaCommand { |
33 | |
34 | private final CreateTableData data = new CreateTableData(); |
35 | private final ArrayList<DefineCommand> constraintCommands = New.arrayList(); |
36 | private IndexColumn[] pkColumns; |
37 | private boolean ifNotExists; |
38 | private boolean onCommitDrop; |
39 | private boolean onCommitTruncate; |
40 | private Query asQuery; |
41 | private String comment; |
42 | private boolean sortedInsertMode; |
43 | |
44 | public CreateTable(Session session, Schema schema) { |
45 | super(session, schema); |
46 | data.persistIndexes = true; |
47 | data.persistData = true; |
48 | } |
49 | |
50 | public void setQuery(Query query) { |
51 | this.asQuery = query; |
52 | } |
53 | |
54 | public void setTemporary(boolean temporary) { |
55 | data.temporary = temporary; |
56 | } |
57 | |
58 | public void setTableName(String tableName) { |
59 | data.tableName = tableName; |
60 | } |
61 | |
62 | /** |
63 | * Add a column to this table. |
64 | * |
65 | * @param column the column to add |
66 | */ |
67 | public void addColumn(Column column) { |
68 | data.columns.add(column); |
69 | } |
70 | |
71 | /** |
72 | * Add a constraint statement to this statement. |
73 | * The primary key definition is one possible constraint statement. |
74 | * |
75 | * @param command the statement to add |
76 | */ |
77 | public void addConstraintCommand(DefineCommand command) { |
78 | if (command instanceof CreateIndex) { |
79 | constraintCommands.add(command); |
80 | } else { |
81 | AlterTableAddConstraint con = (AlterTableAddConstraint) command; |
82 | boolean alreadySet; |
83 | if (con.getType() == CommandInterface.ALTER_TABLE_ADD_CONSTRAINT_PRIMARY_KEY) { |
84 | alreadySet = setPrimaryKeyColumns(con.getIndexColumns()); |
85 | } else { |
86 | alreadySet = false; |
87 | } |
88 | if (!alreadySet) { |
89 | constraintCommands.add(command); |
90 | } |
91 | } |
92 | } |
93 | |
94 | public void setIfNotExists(boolean ifNotExists) { |
95 | this.ifNotExists = ifNotExists; |
96 | } |
97 | |
98 | @Override |
99 | public int update() { |
100 | if (!transactional) { |
101 | session.commit(true); |
102 | } |
103 | Database db = session.getDatabase(); |
104 | if (!db.isPersistent()) { |
105 | data.persistIndexes = false; |
106 | } |
107 | if (getSchema().findTableOrView(session, data.tableName) != null) { |
108 | if (ifNotExists) { |
109 | return 0; |
110 | } |
111 | throw DbException.get(ErrorCode.TABLE_OR_VIEW_ALREADY_EXISTS_1, data.tableName); |
112 | } |
113 | if (asQuery != null) { |
114 | asQuery.prepare(); |
115 | if (data.columns.size() == 0) { |
116 | generateColumnsFromQuery(); |
117 | } else if (data.columns.size() != asQuery.getColumnCount()) { |
118 | throw DbException.get(ErrorCode.COLUMN_COUNT_DOES_NOT_MATCH); |
119 | } |
120 | } |
121 | if (pkColumns != null) { |
122 | for (Column c : data.columns) { |
123 | for (IndexColumn idxCol : pkColumns) { |
124 | if (c.getName().equals(idxCol.columnName)) { |
125 | c.setNullable(false); |
126 | } |
127 | } |
128 | } |
129 | } |
130 | data.id = getObjectId(); |
131 | data.create = create; |
132 | data.session = session; |
133 | boolean isSessionTemporary = data.temporary && !data.globalTemporary; |
134 | if (!isSessionTemporary) { |
135 | db.lockMeta(session); |
136 | } |
137 | Table table = getSchema().createTable(data); |
138 | ArrayList<Sequence> sequences = New.arrayList(); |
139 | for (Column c : data.columns) { |
140 | if (c.isAutoIncrement()) { |
141 | int objId = getObjectId(); |
142 | c.convertAutoIncrementToSequence(session, getSchema(), objId, data.temporary); |
143 | } |
144 | Sequence seq = c.getSequence(); |
145 | if (seq != null) { |
146 | sequences.add(seq); |
147 | } |
148 | } |
149 | table.setComment(comment); |
150 | if (isSessionTemporary) { |
151 | if (onCommitDrop) { |
152 | table.setOnCommitDrop(true); |
153 | } |
154 | if (onCommitTruncate) { |
155 | table.setOnCommitTruncate(true); |
156 | } |
157 | session.addLocalTempTable(table); |
158 | } else { |
159 | db.lockMeta(session); |
160 | db.addSchemaObject(session, table); |
161 | } |
162 | try { |
163 | for (Column c : data.columns) { |
164 | c.prepareExpression(session); |
165 | } |
166 | for (Sequence sequence : sequences) { |
167 | table.addSequence(sequence); |
168 | } |
169 | for (DefineCommand command : constraintCommands) { |
170 | command.setTransactional(transactional); |
171 | command.update(); |
172 | } |
173 | if (asQuery != null) { |
174 | boolean old = session.isUndoLogEnabled(); |
175 | try { |
176 | session.setUndoLogEnabled(false); |
177 | session.startStatementWithinTransaction(); |
178 | Insert insert = null; |
179 | insert = new Insert(session); |
180 | insert.setSortedInsertMode(sortedInsertMode); |
181 | insert.setQuery(asQuery); |
182 | insert.setTable(table); |
183 | insert.setInsertFromSelect(true); |
184 | insert.prepare(); |
185 | insert.update(); |
186 | } finally { |
187 | session.setUndoLogEnabled(old); |
188 | } |
189 | } |
190 | HashSet<DbObject> set = New.hashSet(); |
191 | set.clear(); |
192 | table.addDependencies(set); |
193 | for (DbObject obj : set) { |
194 | if (obj == table) { |
195 | continue; |
196 | } |
197 | if (obj.getType() == DbObject.TABLE_OR_VIEW) { |
198 | if (obj instanceof Table) { |
199 | Table t = (Table) obj; |
200 | if (t.getId() > table.getId()) { |
201 | throw DbException.get( |
202 | ErrorCode.FEATURE_NOT_SUPPORTED_1, |
203 | "Table depends on another table " + |
204 | "with a higher ID: " + t + |
205 | ", this is currently not supported, " + |
206 | "as it would prevent the database from " + |
207 | "being re-opened"); |
208 | } |
209 | } |
210 | } |
211 | } |
212 | } catch (DbException e) { |
213 | db.checkPowerOff(); |
214 | db.removeSchemaObject(session, table); |
215 | if (!transactional) { |
216 | session.commit(true); |
217 | } |
218 | throw e; |
219 | } |
220 | return 0; |
221 | } |
222 | |
223 | private void generateColumnsFromQuery() { |
224 | int columnCount = asQuery.getColumnCount(); |
225 | ArrayList<Expression> expressions = asQuery.getExpressions(); |
226 | for (int i = 0; i < columnCount; i++) { |
227 | Expression expr = expressions.get(i); |
228 | int type = expr.getType(); |
229 | String name = expr.getAlias(); |
230 | long precision = expr.getPrecision(); |
231 | int displaySize = expr.getDisplaySize(); |
232 | DataType dt = DataType.getDataType(type); |
233 | if (precision > 0 && (dt.defaultPrecision == 0 || |
234 | (dt.defaultPrecision > precision && dt.defaultPrecision < Byte.MAX_VALUE))) { |
235 | // dont' set precision to MAX_VALUE if this is the default |
236 | precision = dt.defaultPrecision; |
237 | } |
238 | int scale = expr.getScale(); |
239 | if (scale > 0 && (dt.defaultScale == 0 || |
240 | (dt.defaultScale > scale && dt.defaultScale < precision))) { |
241 | scale = dt.defaultScale; |
242 | } |
243 | if (scale > precision) { |
244 | precision = scale; |
245 | } |
246 | Column col = new Column(name, type, precision, scale, displaySize); |
247 | addColumn(col); |
248 | } |
249 | } |
250 | |
251 | /** |
252 | * Sets the primary key columns, but also check if a primary key |
253 | * with different columns is already defined. |
254 | * |
255 | * @param columns the primary key columns |
256 | * @return true if the same primary key columns where already set |
257 | */ |
258 | private boolean setPrimaryKeyColumns(IndexColumn[] columns) { |
259 | if (pkColumns != null) { |
260 | int len = columns.length; |
261 | if (len != pkColumns.length) { |
262 | throw DbException.get(ErrorCode.SECOND_PRIMARY_KEY); |
263 | } |
264 | for (int i = 0; i < len; i++) { |
265 | if (!columns[i].columnName.equals(pkColumns[i].columnName)) { |
266 | throw DbException.get(ErrorCode.SECOND_PRIMARY_KEY); |
267 | } |
268 | } |
269 | return true; |
270 | } |
271 | this.pkColumns = columns; |
272 | return false; |
273 | } |
274 | |
275 | public void setPersistIndexes(boolean persistIndexes) { |
276 | data.persistIndexes = persistIndexes; |
277 | } |
278 | |
279 | public void setGlobalTemporary(boolean globalTemporary) { |
280 | data.globalTemporary = globalTemporary; |
281 | } |
282 | |
283 | /** |
284 | * This temporary table is dropped on commit. |
285 | */ |
286 | public void setOnCommitDrop() { |
287 | this.onCommitDrop = true; |
288 | } |
289 | |
290 | /** |
291 | * This temporary table is truncated on commit. |
292 | */ |
293 | public void setOnCommitTruncate() { |
294 | this.onCommitTruncate = true; |
295 | } |
296 | |
297 | public void setComment(String comment) { |
298 | this.comment = comment; |
299 | } |
300 | |
301 | public void setPersistData(boolean persistData) { |
302 | data.persistData = persistData; |
303 | if (!persistData) { |
304 | data.persistIndexes = false; |
305 | } |
306 | } |
307 | |
308 | public void setSortedInsertMode(boolean sortedInsertMode) { |
309 | this.sortedInsertMode = sortedInsertMode; |
310 | } |
311 | |
312 | public void setTableEngine(String tableEngine) { |
313 | data.tableEngine = tableEngine; |
314 | } |
315 | |
316 | public void setTableEngineParams(ArrayList<String> tableEngineParams) { |
317 | data.tableEngineParams = tableEngineParams; |
318 | } |
319 | |
320 | public void setHidden(boolean isHidden) { |
321 | data.isHidden = isHidden; |
322 | } |
323 | |
324 | @Override |
325 | public int getType() { |
326 | return CommandInterface.CREATE_TABLE; |
327 | } |
328 | |
329 | } |