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.schema; |
7 | |
8 | import java.util.ArrayList; |
9 | import java.util.HashMap; |
10 | import java.util.HashSet; |
11 | |
12 | import org.h2.api.ErrorCode; |
13 | import org.h2.api.TableEngine; |
14 | import org.h2.command.ddl.CreateTableData; |
15 | import org.h2.constraint.Constraint; |
16 | import org.h2.engine.Database; |
17 | import org.h2.engine.DbObject; |
18 | import org.h2.engine.DbObjectBase; |
19 | import org.h2.engine.FunctionAlias; |
20 | import org.h2.engine.Session; |
21 | import org.h2.engine.SysProperties; |
22 | import org.h2.engine.User; |
23 | import org.h2.index.Index; |
24 | import org.h2.message.DbException; |
25 | import org.h2.message.Trace; |
26 | import org.h2.mvstore.db.MVTableEngine; |
27 | import org.h2.table.RegularTable; |
28 | import org.h2.table.Table; |
29 | import org.h2.table.TableLink; |
30 | import org.h2.util.JdbcUtils; |
31 | import org.h2.util.New; |
32 | |
33 | /** |
34 | * A schema as created by the SQL statement |
35 | * CREATE SCHEMA |
36 | */ |
37 | public class Schema extends DbObjectBase { |
38 | |
39 | private User owner; |
40 | private final boolean system; |
41 | |
42 | private final HashMap<String, Table> tablesAndViews; |
43 | private final HashMap<String, Index> indexes; |
44 | private final HashMap<String, Sequence> sequences; |
45 | private final HashMap<String, TriggerObject> triggers; |
46 | private final HashMap<String, Constraint> constraints; |
47 | private final HashMap<String, Constant> constants; |
48 | private final HashMap<String, FunctionAlias> functions; |
49 | |
50 | /** |
51 | * The set of returned unique names that are not yet stored. It is used to |
52 | * avoid returning the same unique name twice when multiple threads |
53 | * concurrently create objects. |
54 | */ |
55 | private final HashSet<String> temporaryUniqueNames = New.hashSet(); |
56 | |
57 | /** |
58 | * Create a new schema object. |
59 | * |
60 | * @param database the database |
61 | * @param id the object id |
62 | * @param schemaName the schema name |
63 | * @param owner the owner of the schema |
64 | * @param system if this is a system schema (such a schema can not be |
65 | * dropped) |
66 | */ |
67 | public Schema(Database database, int id, String schemaName, User owner, |
68 | boolean system) { |
69 | tablesAndViews = database.newStringMap(); |
70 | indexes = database.newStringMap(); |
71 | sequences = database.newStringMap(); |
72 | triggers = database.newStringMap(); |
73 | constraints = database.newStringMap(); |
74 | constants = database.newStringMap(); |
75 | functions = database.newStringMap(); |
76 | initDbObjectBase(database, id, schemaName, Trace.SCHEMA); |
77 | this.owner = owner; |
78 | this.system = system; |
79 | } |
80 | |
81 | /** |
82 | * Check if this schema can be dropped. System schemas can not be dropped. |
83 | * |
84 | * @return true if it can be dropped |
85 | */ |
86 | public boolean canDrop() { |
87 | return !system; |
88 | } |
89 | |
90 | @Override |
91 | public String getCreateSQLForCopy(Table table, String quotedName) { |
92 | throw DbException.throwInternalError(); |
93 | } |
94 | |
95 | @Override |
96 | public String getDropSQL() { |
97 | return null; |
98 | } |
99 | |
100 | @Override |
101 | public String getCreateSQL() { |
102 | if (system) { |
103 | return null; |
104 | } |
105 | return "CREATE SCHEMA IF NOT EXISTS " + |
106 | getSQL() + " AUTHORIZATION " + owner.getSQL(); |
107 | } |
108 | |
109 | @Override |
110 | public int getType() { |
111 | return DbObject.SCHEMA; |
112 | } |
113 | |
114 | @Override |
115 | public void removeChildrenAndResources(Session session) { |
116 | while (triggers != null && triggers.size() > 0) { |
117 | TriggerObject obj = (TriggerObject) triggers.values().toArray()[0]; |
118 | database.removeSchemaObject(session, obj); |
119 | } |
120 | while (constraints != null && constraints.size() > 0) { |
121 | Constraint obj = (Constraint) constraints.values().toArray()[0]; |
122 | database.removeSchemaObject(session, obj); |
123 | } |
124 | // There can be dependencies between tables e.g. using computed columns, |
125 | // so we might need to loop over them multiple times. |
126 | boolean runLoopAgain = false; |
127 | do { |
128 | runLoopAgain = false; |
129 | if (tablesAndViews != null) { |
130 | // Loop over a copy because the map is modified underneath us. |
131 | for (Table obj : New.arrayList(tablesAndViews.values())) { |
132 | // Check for null because multiple tables might be deleted |
133 | // in one go underneath us. |
134 | if (obj.getName() != null) { |
135 | if (database.getDependentTable(obj, obj) == null) { |
136 | database.removeSchemaObject(session, obj); |
137 | } else { |
138 | runLoopAgain = true; |
139 | } |
140 | } |
141 | } |
142 | } |
143 | } while (runLoopAgain); |
144 | while (indexes != null && indexes.size() > 0) { |
145 | Index obj = (Index) indexes.values().toArray()[0]; |
146 | database.removeSchemaObject(session, obj); |
147 | } |
148 | while (sequences != null && sequences.size() > 0) { |
149 | Sequence obj = (Sequence) sequences.values().toArray()[0]; |
150 | database.removeSchemaObject(session, obj); |
151 | } |
152 | while (constants != null && constants.size() > 0) { |
153 | Constant obj = (Constant) constants.values().toArray()[0]; |
154 | database.removeSchemaObject(session, obj); |
155 | } |
156 | while (functions != null && functions.size() > 0) { |
157 | FunctionAlias obj = (FunctionAlias) functions.values().toArray()[0]; |
158 | database.removeSchemaObject(session, obj); |
159 | } |
160 | database.removeMeta(session, getId()); |
161 | owner = null; |
162 | invalidate(); |
163 | } |
164 | |
165 | @Override |
166 | public void checkRename() { |
167 | // ok |
168 | } |
169 | |
170 | /** |
171 | * Get the owner of this schema. |
172 | * |
173 | * @return the owner |
174 | */ |
175 | public User getOwner() { |
176 | return owner; |
177 | } |
178 | |
179 | @SuppressWarnings("unchecked") |
180 | private HashMap<String, SchemaObject> getMap(int type) { |
181 | HashMap<String, ? extends SchemaObject> result; |
182 | switch (type) { |
183 | case DbObject.TABLE_OR_VIEW: |
184 | result = tablesAndViews; |
185 | break; |
186 | case DbObject.SEQUENCE: |
187 | result = sequences; |
188 | break; |
189 | case DbObject.INDEX: |
190 | result = indexes; |
191 | break; |
192 | case DbObject.TRIGGER: |
193 | result = triggers; |
194 | break; |
195 | case DbObject.CONSTRAINT: |
196 | result = constraints; |
197 | break; |
198 | case DbObject.CONSTANT: |
199 | result = constants; |
200 | break; |
201 | case DbObject.FUNCTION_ALIAS: |
202 | result = functions; |
203 | break; |
204 | default: |
205 | throw DbException.throwInternalError("type=" + type); |
206 | } |
207 | return (HashMap<String, SchemaObject>) result; |
208 | } |
209 | |
210 | /** |
211 | * Add an object to this schema. |
212 | * This method must not be called within CreateSchemaObject; |
213 | * use Database.addSchemaObject() instead |
214 | * |
215 | * @param obj the object to add |
216 | */ |
217 | public void add(SchemaObject obj) { |
218 | if (SysProperties.CHECK && obj.getSchema() != this) { |
219 | DbException.throwInternalError("wrong schema"); |
220 | } |
221 | String name = obj.getName(); |
222 | HashMap<String, SchemaObject> map = getMap(obj.getType()); |
223 | if (SysProperties.CHECK && map.get(name) != null) { |
224 | DbException.throwInternalError("object already exists: " + name); |
225 | } |
226 | map.put(name, obj); |
227 | freeUniqueName(name); |
228 | } |
229 | |
230 | /** |
231 | * Rename an object. |
232 | * |
233 | * @param obj the object to rename |
234 | * @param newName the new name |
235 | */ |
236 | public void rename(SchemaObject obj, String newName) { |
237 | int type = obj.getType(); |
238 | HashMap<String, SchemaObject> map = getMap(type); |
239 | if (SysProperties.CHECK) { |
240 | if (!map.containsKey(obj.getName())) { |
241 | DbException.throwInternalError("not found: " + obj.getName()); |
242 | } |
243 | if (obj.getName().equals(newName) || map.containsKey(newName)) { |
244 | DbException.throwInternalError("object already exists: " + newName); |
245 | } |
246 | } |
247 | obj.checkRename(); |
248 | map.remove(obj.getName()); |
249 | freeUniqueName(obj.getName()); |
250 | obj.rename(newName); |
251 | map.put(newName, obj); |
252 | freeUniqueName(newName); |
253 | } |
254 | |
255 | /** |
256 | * Try to find a table or view with this name. This method returns null if |
257 | * no object with this name exists. Local temporary tables are also |
258 | * returned. |
259 | * |
260 | * @param session the session |
261 | * @param name the object name |
262 | * @return the object or null |
263 | */ |
264 | public Table findTableOrView(Session session, String name) { |
265 | Table table = tablesAndViews.get(name); |
266 | if (table == null && session != null) { |
267 | table = session.findLocalTempTable(name); |
268 | } |
269 | return table; |
270 | } |
271 | |
272 | /** |
273 | * Try to find an index with this name. This method returns null if |
274 | * no object with this name exists. |
275 | * |
276 | * @param session the session |
277 | * @param name the object name |
278 | * @return the object or null |
279 | */ |
280 | public Index findIndex(Session session, String name) { |
281 | Index index = indexes.get(name); |
282 | if (index == null) { |
283 | index = session.findLocalTempTableIndex(name); |
284 | } |
285 | return index; |
286 | } |
287 | |
288 | /** |
289 | * Try to find a trigger with this name. This method returns null if |
290 | * no object with this name exists. |
291 | * |
292 | * @param name the object name |
293 | * @return the object or null |
294 | */ |
295 | public TriggerObject findTrigger(String name) { |
296 | return triggers.get(name); |
297 | } |
298 | |
299 | /** |
300 | * Try to find a sequence with this name. This method returns null if |
301 | * no object with this name exists. |
302 | * |
303 | * @param sequenceName the object name |
304 | * @return the object or null |
305 | */ |
306 | public Sequence findSequence(String sequenceName) { |
307 | return sequences.get(sequenceName); |
308 | } |
309 | |
310 | /** |
311 | * Try to find a constraint with this name. This method returns null if no |
312 | * object with this name exists. |
313 | * |
314 | * @param session the session |
315 | * @param name the object name |
316 | * @return the object or null |
317 | */ |
318 | public Constraint findConstraint(Session session, String name) { |
319 | Constraint constraint = constraints.get(name); |
320 | if (constraint == null) { |
321 | constraint = session.findLocalTempTableConstraint(name); |
322 | } |
323 | return constraint; |
324 | } |
325 | |
326 | /** |
327 | * Try to find a user defined constant with this name. This method returns |
328 | * null if no object with this name exists. |
329 | * |
330 | * @param constantName the object name |
331 | * @return the object or null |
332 | */ |
333 | public Constant findConstant(String constantName) { |
334 | return constants.get(constantName); |
335 | } |
336 | |
337 | /** |
338 | * Try to find a user defined function with this name. This method returns |
339 | * null if no object with this name exists. |
340 | * |
341 | * @param functionAlias the object name |
342 | * @return the object or null |
343 | */ |
344 | public FunctionAlias findFunction(String functionAlias) { |
345 | return functions.get(functionAlias); |
346 | } |
347 | |
348 | /** |
349 | * Release a unique object name. |
350 | * |
351 | * @param name the object name |
352 | */ |
353 | public void freeUniqueName(String name) { |
354 | if (name != null) { |
355 | synchronized (temporaryUniqueNames) { |
356 | temporaryUniqueNames.remove(name); |
357 | } |
358 | } |
359 | } |
360 | |
361 | private String getUniqueName(DbObject obj, |
362 | HashMap<String, ? extends SchemaObject> map, String prefix) { |
363 | String hash = Integer.toHexString(obj.getName().hashCode()).toUpperCase(); |
364 | String name = null; |
365 | synchronized (temporaryUniqueNames) { |
366 | for (int i = 1, len = hash.length(); i < len; i++) { |
367 | name = prefix + hash.substring(0, i); |
368 | if (!map.containsKey(name) && !temporaryUniqueNames.contains(name)) { |
369 | break; |
370 | } |
371 | name = null; |
372 | } |
373 | if (name == null) { |
374 | prefix = prefix + hash + "_"; |
375 | for (int i = 0;; i++) { |
376 | name = prefix + i; |
377 | if (!map.containsKey(name) && !temporaryUniqueNames.contains(name)) { |
378 | break; |
379 | } |
380 | } |
381 | } |
382 | temporaryUniqueNames.add(name); |
383 | } |
384 | return name; |
385 | } |
386 | |
387 | /** |
388 | * Create a unique constraint name. |
389 | * |
390 | * @param session the session |
391 | * @param table the constraint table |
392 | * @return the unique name |
393 | */ |
394 | public String getUniqueConstraintName(Session session, Table table) { |
395 | HashMap<String, Constraint> tableConstraints; |
396 | if (table.isTemporary() && !table.isGlobalTemporary()) { |
397 | tableConstraints = session.getLocalTempTableConstraints(); |
398 | } else { |
399 | tableConstraints = constraints; |
400 | } |
401 | return getUniqueName(table, tableConstraints, "CONSTRAINT_"); |
402 | } |
403 | |
404 | /** |
405 | * Create a unique index name. |
406 | * |
407 | * @param session the session |
408 | * @param table the indexed table |
409 | * @param prefix the index name prefix |
410 | * @return the unique name |
411 | */ |
412 | public String getUniqueIndexName(Session session, Table table, String prefix) { |
413 | HashMap<String, Index> tableIndexes; |
414 | if (table.isTemporary() && !table.isGlobalTemporary()) { |
415 | tableIndexes = session.getLocalTempTableIndexes(); |
416 | } else { |
417 | tableIndexes = indexes; |
418 | } |
419 | return getUniqueName(table, tableIndexes, prefix); |
420 | } |
421 | |
422 | /** |
423 | * Get the table or view with the given name. |
424 | * Local temporary tables are also returned. |
425 | * |
426 | * @param session the session |
427 | * @param name the table or view name |
428 | * @return the table or view |
429 | * @throws DbException if no such object exists |
430 | */ |
431 | public Table getTableOrView(Session session, String name) { |
432 | Table table = tablesAndViews.get(name); |
433 | if (table == null) { |
434 | if (session != null) { |
435 | table = session.findLocalTempTable(name); |
436 | } |
437 | if (table == null) { |
438 | throw DbException.get(ErrorCode.TABLE_OR_VIEW_NOT_FOUND_1, name); |
439 | } |
440 | } |
441 | return table; |
442 | } |
443 | |
444 | /** |
445 | * Get the index with the given name. |
446 | * |
447 | * @param name the index name |
448 | * @return the index |
449 | * @throws DbException if no such object exists |
450 | */ |
451 | public Index getIndex(String name) { |
452 | Index index = indexes.get(name); |
453 | if (index == null) { |
454 | throw DbException.get(ErrorCode.INDEX_NOT_FOUND_1, name); |
455 | } |
456 | return index; |
457 | } |
458 | |
459 | /** |
460 | * Get the constraint with the given name. |
461 | * |
462 | * @param name the constraint name |
463 | * @return the constraint |
464 | * @throws DbException if no such object exists |
465 | */ |
466 | public Constraint getConstraint(String name) { |
467 | Constraint constraint = constraints.get(name); |
468 | if (constraint == null) { |
469 | throw DbException.get(ErrorCode.CONSTRAINT_NOT_FOUND_1, name); |
470 | } |
471 | return constraint; |
472 | } |
473 | |
474 | /** |
475 | * Get the user defined constant with the given name. |
476 | * |
477 | * @param constantName the constant name |
478 | * @return the constant |
479 | * @throws DbException if no such object exists |
480 | */ |
481 | public Constant getConstant(String constantName) { |
482 | Constant constant = constants.get(constantName); |
483 | if (constant == null) { |
484 | throw DbException.get(ErrorCode.CONSTANT_NOT_FOUND_1, constantName); |
485 | } |
486 | return constant; |
487 | } |
488 | |
489 | /** |
490 | * Get the sequence with the given name. |
491 | * |
492 | * @param sequenceName the sequence name |
493 | * @return the sequence |
494 | * @throws DbException if no such object exists |
495 | */ |
496 | public Sequence getSequence(String sequenceName) { |
497 | Sequence sequence = sequences.get(sequenceName); |
498 | if (sequence == null) { |
499 | throw DbException.get(ErrorCode.SEQUENCE_NOT_FOUND_1, sequenceName); |
500 | } |
501 | return sequence; |
502 | } |
503 | |
504 | /** |
505 | * Get all objects. |
506 | * |
507 | * @return a (possible empty) list of all objects |
508 | */ |
509 | public ArrayList<SchemaObject> getAll() { |
510 | ArrayList<SchemaObject> all = New.arrayList(); |
511 | all.addAll(getMap(DbObject.TABLE_OR_VIEW).values()); |
512 | all.addAll(getMap(DbObject.SEQUENCE).values()); |
513 | all.addAll(getMap(DbObject.INDEX).values()); |
514 | all.addAll(getMap(DbObject.TRIGGER).values()); |
515 | all.addAll(getMap(DbObject.CONSTRAINT).values()); |
516 | all.addAll(getMap(DbObject.CONSTANT).values()); |
517 | all.addAll(getMap(DbObject.FUNCTION_ALIAS).values()); |
518 | return all; |
519 | } |
520 | |
521 | /** |
522 | * Get all objects of the given type. |
523 | * |
524 | * @param type the object type |
525 | * @return a (possible empty) list of all objects |
526 | */ |
527 | public ArrayList<SchemaObject> getAll(int type) { |
528 | HashMap<String, SchemaObject> map = getMap(type); |
529 | return New.arrayList(map.values()); |
530 | } |
531 | |
532 | /** |
533 | * Get all tables and views. |
534 | * |
535 | * @return a (possible empty) list of all objects |
536 | */ |
537 | public ArrayList<Table> getAllTablesAndViews() { |
538 | synchronized (database) { |
539 | return New.arrayList(tablesAndViews.values()); |
540 | } |
541 | } |
542 | |
543 | /** |
544 | * Remove an object from this schema. |
545 | * |
546 | * @param obj the object to remove |
547 | */ |
548 | public void remove(SchemaObject obj) { |
549 | String objName = obj.getName(); |
550 | HashMap<String, SchemaObject> map = getMap(obj.getType()); |
551 | if (SysProperties.CHECK && !map.containsKey(objName)) { |
552 | DbException.throwInternalError("not found: " + objName); |
553 | } |
554 | map.remove(objName); |
555 | freeUniqueName(objName); |
556 | } |
557 | |
558 | /** |
559 | * Add a table to the schema. |
560 | * |
561 | * @param data the create table information |
562 | * @return the created {@link Table} object |
563 | */ |
564 | public Table createTable(CreateTableData data) { |
565 | synchronized (database) { |
566 | if (!data.temporary || data.globalTemporary) { |
567 | database.lockMeta(data.session); |
568 | } |
569 | data.schema = this; |
570 | if (data.tableEngine == null) { |
571 | if (database.getSettings().mvStore) { |
572 | data.tableEngine = MVTableEngine.class.getName(); |
573 | } |
574 | } |
575 | if (data.tableEngine != null) { |
576 | TableEngine engine; |
577 | try { |
578 | engine = (TableEngine) JdbcUtils.loadUserClass(data.tableEngine).newInstance(); |
579 | } catch (Exception e) { |
580 | throw DbException.convert(e); |
581 | } |
582 | return engine.createTable(data); |
583 | } |
584 | return new RegularTable(data); |
585 | } |
586 | } |
587 | |
588 | /** |
589 | * Add a linked table to the schema. |
590 | * |
591 | * @param id the object id |
592 | * @param tableName the table name of the alias |
593 | * @param driver the driver class name |
594 | * @param url the database URL |
595 | * @param user the user name |
596 | * @param password the password |
597 | * @param originalSchema the schema name of the target table |
598 | * @param originalTable the table name of the target table |
599 | * @param emitUpdates if updates should be emitted instead of delete/insert |
600 | * @param force create the object even if the database can not be accessed |
601 | * @return the {@link TableLink} object |
602 | */ |
603 | public TableLink createTableLink(int id, String tableName, String driver, |
604 | String url, String user, String password, String originalSchema, |
605 | String originalTable, boolean emitUpdates, boolean force) { |
606 | synchronized (database) { |
607 | return new TableLink(this, id, tableName, |
608 | driver, url, user, password, |
609 | originalSchema, originalTable, emitUpdates, force); |
610 | } |
611 | } |
612 | |
613 | } |