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.table; |
7 | |
8 | import java.util.ArrayList; |
9 | import java.util.HashMap; |
10 | import java.util.HashSet; |
11 | import java.util.Set; |
12 | |
13 | import org.h2.api.ErrorCode; |
14 | import org.h2.command.Prepared; |
15 | import org.h2.constraint.Constraint; |
16 | import org.h2.engine.Constants; |
17 | import org.h2.engine.DbObject; |
18 | import org.h2.engine.Right; |
19 | import org.h2.engine.Session; |
20 | import org.h2.engine.UndoLogRecord; |
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.message.Trace; |
27 | import org.h2.result.Row; |
28 | import org.h2.result.RowList; |
29 | import org.h2.result.SearchRow; |
30 | import org.h2.result.SimpleRow; |
31 | import org.h2.result.SimpleRowValue; |
32 | import org.h2.result.SortOrder; |
33 | import org.h2.schema.Schema; |
34 | import org.h2.schema.SchemaObjectBase; |
35 | import org.h2.schema.Sequence; |
36 | import org.h2.schema.TriggerObject; |
37 | import org.h2.util.New; |
38 | import org.h2.value.CompareMode; |
39 | import org.h2.value.Value; |
40 | import org.h2.value.ValueNull; |
41 | |
42 | /** |
43 | * This is the base class for most tables. |
44 | * A table contains a list of columns and a list of rows. |
45 | */ |
46 | public abstract class Table extends SchemaObjectBase { |
47 | |
48 | /** |
49 | * The table type that means this table is a regular persistent table. |
50 | */ |
51 | public static final int TYPE_CACHED = 0; |
52 | |
53 | /** |
54 | * The table type that means this table is a regular persistent table. |
55 | */ |
56 | public static final int TYPE_MEMORY = 1; |
57 | |
58 | /** |
59 | * The table type name for linked tables. |
60 | */ |
61 | public static final String TABLE_LINK = "TABLE LINK"; |
62 | |
63 | /** |
64 | * The table type name for system tables. |
65 | */ |
66 | public static final String SYSTEM_TABLE = "SYSTEM TABLE"; |
67 | |
68 | /** |
69 | * The table type name for regular data tables. |
70 | */ |
71 | public static final String TABLE = "TABLE"; |
72 | |
73 | /** |
74 | * The table type name for views. |
75 | */ |
76 | public static final String VIEW = "VIEW"; |
77 | |
78 | /** |
79 | * The table type name for external table engines. |
80 | */ |
81 | public static final String EXTERNAL_TABLE_ENGINE = "EXTERNAL"; |
82 | |
83 | /** |
84 | * The columns of this table. |
85 | */ |
86 | protected Column[] columns; |
87 | |
88 | /** |
89 | * The compare mode used for this table. |
90 | */ |
91 | protected CompareMode compareMode; |
92 | |
93 | /** |
94 | * Protected tables are not listed in the meta data and are excluded when |
95 | * using the SCRIPT command. |
96 | */ |
97 | protected boolean isHidden; |
98 | |
99 | private final HashMap<String, Column> columnMap; |
100 | private final boolean persistIndexes; |
101 | private final boolean persistData; |
102 | private ArrayList<TriggerObject> triggers; |
103 | private ArrayList<Constraint> constraints; |
104 | private ArrayList<Sequence> sequences; |
105 | private ArrayList<TableView> views; |
106 | private boolean checkForeignKeyConstraints = true; |
107 | private boolean onCommitDrop, onCommitTruncate; |
108 | private Row nullRow; |
109 | |
110 | public Table(Schema schema, int id, String name, boolean persistIndexes, |
111 | boolean persistData) { |
112 | columnMap = schema.getDatabase().newStringMap(); |
113 | initSchemaObjectBase(schema, id, name, Trace.TABLE); |
114 | this.persistIndexes = persistIndexes; |
115 | this.persistData = persistData; |
116 | compareMode = schema.getDatabase().getCompareMode(); |
117 | } |
118 | |
119 | @Override |
120 | public void rename(String newName) { |
121 | super.rename(newName); |
122 | if (constraints != null) { |
123 | for (int i = 0, size = constraints.size(); i < size; i++) { |
124 | Constraint constraint = constraints.get(i); |
125 | constraint.rebuild(); |
126 | } |
127 | } |
128 | } |
129 | |
130 | /** |
131 | * Lock the table for the given session. |
132 | * This method waits until the lock is granted. |
133 | * |
134 | * @param session the session |
135 | * @param exclusive true for write locks, false for read locks |
136 | * @param forceLockEvenInMvcc lock even in the MVCC mode |
137 | * @return true if the table was already exclusively locked by this session. |
138 | * @throws DbException if a lock timeout occurred |
139 | */ |
140 | public abstract boolean lock(Session session, boolean exclusive, boolean forceLockEvenInMvcc); |
141 | |
142 | /** |
143 | * Close the table object and flush changes. |
144 | * |
145 | * @param session the session |
146 | */ |
147 | public abstract void close(Session session); |
148 | |
149 | /** |
150 | * Release the lock for this session. |
151 | * |
152 | * @param s the session |
153 | */ |
154 | public abstract void unlock(Session s); |
155 | |
156 | /** |
157 | * Create an index for this table |
158 | * |
159 | * @param session the session |
160 | * @param indexName the name of the index |
161 | * @param indexId the id |
162 | * @param cols the index columns |
163 | * @param indexType the index type |
164 | * @param create whether this is a new index |
165 | * @param indexComment the comment |
166 | * @return the index |
167 | */ |
168 | public abstract Index addIndex(Session session, String indexName, |
169 | int indexId, IndexColumn[] cols, IndexType indexType, |
170 | boolean create, String indexComment); |
171 | |
172 | /** |
173 | * Get the given row. |
174 | * |
175 | * @param session the session |
176 | * @param key the primary key |
177 | * @return the row |
178 | */ |
179 | public Row getRow(Session session, long key) { |
180 | return null; |
181 | } |
182 | |
183 | /** |
184 | * Remove a row from the table and all indexes. |
185 | * |
186 | * @param session the session |
187 | * @param row the row |
188 | */ |
189 | public abstract void removeRow(Session session, Row row); |
190 | |
191 | /** |
192 | * Remove all rows from the table and indexes. |
193 | * |
194 | * @param session the session |
195 | */ |
196 | public abstract void truncate(Session session); |
197 | |
198 | /** |
199 | * Add a row to the table and all indexes. |
200 | * |
201 | * @param session the session |
202 | * @param row the row |
203 | * @throws DbException if a constraint was violated |
204 | */ |
205 | public abstract void addRow(Session session, Row row); |
206 | |
207 | /** |
208 | * Commit an operation (when using multi-version concurrency). |
209 | * |
210 | * @param operation the operation |
211 | * @param row the row |
212 | */ |
213 | public void commit(short operation, Row row) { |
214 | // nothing to do |
215 | } |
216 | |
217 | /** |
218 | * Check if this table supports ALTER TABLE. |
219 | * |
220 | * @throws DbException if it is not supported |
221 | */ |
222 | public abstract void checkSupportAlter(); |
223 | |
224 | /** |
225 | * Get the table type name |
226 | * |
227 | * @return the table type name |
228 | */ |
229 | public abstract String getTableType(); |
230 | |
231 | /** |
232 | * Get the scan index to iterate through all rows. |
233 | * |
234 | * @param session the session |
235 | * @return the index |
236 | */ |
237 | public abstract Index getScanIndex(Session session); |
238 | |
239 | /** |
240 | * Get any unique index for this table if one exists. |
241 | * |
242 | * @return a unique index |
243 | */ |
244 | public abstract Index getUniqueIndex(); |
245 | |
246 | /** |
247 | * Get all indexes for this table. |
248 | * |
249 | * @return the list of indexes |
250 | */ |
251 | public abstract ArrayList<Index> getIndexes(); |
252 | |
253 | /** |
254 | * Check if this table is locked exclusively. |
255 | * |
256 | * @return true if it is. |
257 | */ |
258 | public abstract boolean isLockedExclusively(); |
259 | |
260 | /** |
261 | * Get the last data modification id. |
262 | * |
263 | * @return the modification id |
264 | */ |
265 | public abstract long getMaxDataModificationId(); |
266 | |
267 | /** |
268 | * Check if the table is deterministic. |
269 | * |
270 | * @return true if it is |
271 | */ |
272 | public abstract boolean isDeterministic(); |
273 | |
274 | /** |
275 | * Check if the row count can be retrieved quickly. |
276 | * |
277 | * @return true if it can |
278 | */ |
279 | public abstract boolean canGetRowCount(); |
280 | |
281 | /** |
282 | * Check if this table can be referenced. |
283 | * |
284 | * @return true if it can |
285 | */ |
286 | public boolean canReference() { |
287 | return true; |
288 | } |
289 | |
290 | /** |
291 | * Check if this table can be dropped. |
292 | * |
293 | * @return true if it can |
294 | */ |
295 | public abstract boolean canDrop(); |
296 | |
297 | /** |
298 | * Get the row count for this table. |
299 | * |
300 | * @param session the session |
301 | * @return the row count |
302 | */ |
303 | public abstract long getRowCount(Session session); |
304 | |
305 | /** |
306 | * Get the approximated row count for this table. |
307 | * |
308 | * @return the approximated row count |
309 | */ |
310 | public abstract long getRowCountApproximation(); |
311 | |
312 | public abstract long getDiskSpaceUsed(); |
313 | |
314 | /** |
315 | * Get the row id column if this table has one. |
316 | * |
317 | * @return the row id column, or null |
318 | */ |
319 | public Column getRowIdColumn() { |
320 | return null; |
321 | } |
322 | |
323 | @Override |
324 | public String getCreateSQLForCopy(Table table, String quotedName) { |
325 | throw DbException.throwInternalError(); |
326 | } |
327 | |
328 | /** |
329 | * Check whether the table (or view) contains no columns that prevent index |
330 | * conditions to be used. For example, a view that contains the ROWNUM() |
331 | * pseudo-column prevents this. |
332 | * |
333 | * @return true if the table contains no query-comparable column |
334 | */ |
335 | public boolean isQueryComparable() { |
336 | return true; |
337 | } |
338 | |
339 | /** |
340 | * Add all objects that this table depends on to the hash set. |
341 | * |
342 | * @param dependencies the current set of dependencies |
343 | */ |
344 | public void addDependencies(HashSet<DbObject> dependencies) { |
345 | if (dependencies.contains(this)) { |
346 | // avoid endless recursion |
347 | return; |
348 | } |
349 | if (sequences != null) { |
350 | for (Sequence s : sequences) { |
351 | dependencies.add(s); |
352 | } |
353 | } |
354 | ExpressionVisitor visitor = ExpressionVisitor.getDependenciesVisitor( |
355 | dependencies); |
356 | for (Column col : columns) { |
357 | col.isEverything(visitor); |
358 | } |
359 | if (constraints != null) { |
360 | for (Constraint c : constraints) { |
361 | c.isEverything(visitor); |
362 | } |
363 | } |
364 | dependencies.add(this); |
365 | } |
366 | |
367 | @Override |
368 | public ArrayList<DbObject> getChildren() { |
369 | ArrayList<DbObject> children = New.arrayList(); |
370 | ArrayList<Index> indexes = getIndexes(); |
371 | if (indexes != null) { |
372 | children.addAll(indexes); |
373 | } |
374 | if (constraints != null) { |
375 | children.addAll(constraints); |
376 | } |
377 | if (triggers != null) { |
378 | children.addAll(triggers); |
379 | } |
380 | if (sequences != null) { |
381 | children.addAll(sequences); |
382 | } |
383 | if (views != null) { |
384 | children.addAll(views); |
385 | } |
386 | ArrayList<Right> rights = database.getAllRights(); |
387 | for (Right right : rights) { |
388 | if (right.getGrantedTable() == this) { |
389 | children.add(right); |
390 | } |
391 | } |
392 | return children; |
393 | } |
394 | |
395 | protected void setColumns(Column[] columns) { |
396 | this.columns = columns; |
397 | if (columnMap.size() > 0) { |
398 | columnMap.clear(); |
399 | } |
400 | for (int i = 0; i < columns.length; i++) { |
401 | Column col = columns[i]; |
402 | int dataType = col.getType(); |
403 | if (dataType == Value.UNKNOWN) { |
404 | throw DbException.get( |
405 | ErrorCode.UNKNOWN_DATA_TYPE_1, col.getSQL()); |
406 | } |
407 | col.setTable(this, i); |
408 | String columnName = col.getName(); |
409 | if (columnMap.get(columnName) != null) { |
410 | throw DbException.get( |
411 | ErrorCode.DUPLICATE_COLUMN_NAME_1, columnName); |
412 | } |
413 | columnMap.put(columnName, col); |
414 | } |
415 | } |
416 | |
417 | /** |
418 | * Rename a column of this table. |
419 | * |
420 | * @param column the column to rename |
421 | * @param newName the new column name |
422 | */ |
423 | public void renameColumn(Column column, String newName) { |
424 | for (Column c : columns) { |
425 | if (c == column) { |
426 | continue; |
427 | } |
428 | if (c.getName().equals(newName)) { |
429 | throw DbException.get( |
430 | ErrorCode.DUPLICATE_COLUMN_NAME_1, newName); |
431 | } |
432 | } |
433 | columnMap.remove(column.getName()); |
434 | column.rename(newName); |
435 | columnMap.put(newName, column); |
436 | } |
437 | |
438 | /** |
439 | * Check if the table is exclusively locked by this session. |
440 | * |
441 | * @param session the session |
442 | * @return true if it is |
443 | */ |
444 | public boolean isLockedExclusivelyBy(Session session) { |
445 | return false; |
446 | } |
447 | |
448 | /** |
449 | * Update a list of rows in this table. |
450 | * |
451 | * @param prepared the prepared statement |
452 | * @param session the session |
453 | * @param rows a list of row pairs of the form old row, new row, old row, |
454 | * new row,... |
455 | */ |
456 | public void updateRows(Prepared prepared, Session session, RowList rows) { |
457 | // in case we need to undo the update |
458 | Session.Savepoint rollback = session.setSavepoint(); |
459 | // remove the old rows |
460 | int rowScanCount = 0; |
461 | for (rows.reset(); rows.hasNext();) { |
462 | if ((++rowScanCount & 127) == 0) { |
463 | prepared.checkCanceled(); |
464 | } |
465 | Row o = rows.next(); |
466 | rows.next(); |
467 | removeRow(session, o); |
468 | session.log(this, UndoLogRecord.DELETE, o); |
469 | } |
470 | // add the new rows |
471 | for (rows.reset(); rows.hasNext();) { |
472 | if ((++rowScanCount & 127) == 0) { |
473 | prepared.checkCanceled(); |
474 | } |
475 | rows.next(); |
476 | Row n = rows.next(); |
477 | try { |
478 | addRow(session, n); |
479 | } catch (DbException e) { |
480 | if (e.getErrorCode() == ErrorCode.CONCURRENT_UPDATE_1) { |
481 | session.rollbackTo(rollback, false); |
482 | session.startStatementWithinTransaction(); |
483 | rollback = session.setSavepoint(); |
484 | } |
485 | throw e; |
486 | } |
487 | session.log(this, UndoLogRecord.INSERT, n); |
488 | } |
489 | } |
490 | |
491 | public ArrayList<TableView> getViews() { |
492 | return views; |
493 | } |
494 | |
495 | @Override |
496 | public void removeChildrenAndResources(Session session) { |
497 | while (views != null && views.size() > 0) { |
498 | TableView view = views.get(0); |
499 | views.remove(0); |
500 | database.removeSchemaObject(session, view); |
501 | } |
502 | while (triggers != null && triggers.size() > 0) { |
503 | TriggerObject trigger = triggers.get(0); |
504 | triggers.remove(0); |
505 | database.removeSchemaObject(session, trigger); |
506 | } |
507 | while (constraints != null && constraints.size() > 0) { |
508 | Constraint constraint = constraints.get(0); |
509 | constraints.remove(0); |
510 | database.removeSchemaObject(session, constraint); |
511 | } |
512 | for (Right right : database.getAllRights()) { |
513 | if (right.getGrantedTable() == this) { |
514 | database.removeDatabaseObject(session, right); |
515 | } |
516 | } |
517 | database.removeMeta(session, getId()); |
518 | // must delete sequences later (in case there is a power failure |
519 | // before removing the table object) |
520 | while (sequences != null && sequences.size() > 0) { |
521 | Sequence sequence = sequences.get(0); |
522 | sequences.remove(0); |
523 | if (!isTemporary()) { |
524 | // only remove if no other table depends on this sequence |
525 | // this is possible when calling ALTER TABLE ALTER COLUMN |
526 | if (database.getDependentTable(sequence, this) == null) { |
527 | database.removeSchemaObject(session, sequence); |
528 | } |
529 | } |
530 | } |
531 | } |
532 | |
533 | /** |
534 | * Check that this column is not referenced by a multi-column constraint or |
535 | * multi-column index. If it is, an exception is thrown. Single-column |
536 | * references and indexes are dropped. |
537 | * |
538 | * @param session the session |
539 | * @param col the column |
540 | * @throws DbException if the column is referenced by multi-column |
541 | * constraints or indexes |
542 | */ |
543 | public void dropSingleColumnConstraintsAndIndexes(Session session, |
544 | Column col) { |
545 | ArrayList<Constraint> constraintsToDrop = New.arrayList(); |
546 | if (constraints != null) { |
547 | for (int i = 0, size = constraints.size(); i < size; i++) { |
548 | Constraint constraint = constraints.get(i); |
549 | HashSet<Column> columns = constraint.getReferencedColumns(this); |
550 | if (!columns.contains(col)) { |
551 | continue; |
552 | } |
553 | if (columns.size() == 1) { |
554 | constraintsToDrop.add(constraint); |
555 | } else { |
556 | throw DbException.get( |
557 | ErrorCode.COLUMN_IS_REFERENCED_1, constraint.getSQL()); |
558 | } |
559 | } |
560 | } |
561 | ArrayList<Index> indexesToDrop = New.arrayList(); |
562 | ArrayList<Index> indexes = getIndexes(); |
563 | if (indexes != null) { |
564 | for (int i = 0, size = indexes.size(); i < size; i++) { |
565 | Index index = indexes.get(i); |
566 | if (index.getCreateSQL() == null) { |
567 | continue; |
568 | } |
569 | if (index.getColumnIndex(col) < 0) { |
570 | continue; |
571 | } |
572 | if (index.getColumns().length == 1) { |
573 | indexesToDrop.add(index); |
574 | } else { |
575 | throw DbException.get( |
576 | ErrorCode.COLUMN_IS_REFERENCED_1, index.getSQL()); |
577 | } |
578 | } |
579 | } |
580 | for (Constraint c : constraintsToDrop) { |
581 | session.getDatabase().removeSchemaObject(session, c); |
582 | } |
583 | for (Index i : indexesToDrop) { |
584 | // the index may already have been dropped when dropping the |
585 | // constraint |
586 | if (getIndexes().contains(i)) { |
587 | session.getDatabase().removeSchemaObject(session, i); |
588 | } |
589 | } |
590 | } |
591 | |
592 | public Row getTemplateRow() { |
593 | return new Row(new Value[columns.length], Row.MEMORY_CALCULATE); |
594 | } |
595 | |
596 | /** |
597 | * Get a new simple row object. |
598 | * |
599 | * @param singleColumn if only one value need to be stored |
600 | * @return the simple row object |
601 | */ |
602 | public SearchRow getTemplateSimpleRow(boolean singleColumn) { |
603 | if (singleColumn) { |
604 | return new SimpleRowValue(columns.length); |
605 | } |
606 | return new SimpleRow(new Value[columns.length]); |
607 | } |
608 | |
609 | synchronized Row getNullRow() { |
610 | if (nullRow == null) { |
611 | nullRow = new Row(new Value[columns.length], 1); |
612 | for (int i = 0; i < columns.length; i++) { |
613 | nullRow.setValue(i, ValueNull.INSTANCE); |
614 | } |
615 | } |
616 | return nullRow; |
617 | } |
618 | |
619 | public Column[] getColumns() { |
620 | return columns; |
621 | } |
622 | |
623 | @Override |
624 | public int getType() { |
625 | return DbObject.TABLE_OR_VIEW; |
626 | } |
627 | |
628 | /** |
629 | * Get the column at the given index. |
630 | * |
631 | * @param index the column index (0, 1,...) |
632 | * @return the column |
633 | */ |
634 | public Column getColumn(int index) { |
635 | return columns[index]; |
636 | } |
637 | |
638 | /** |
639 | * Get the column with the given name. |
640 | * |
641 | * @param columnName the column name |
642 | * @return the column |
643 | * @throws DbException if the column was not found |
644 | */ |
645 | public Column getColumn(String columnName) { |
646 | Column column = columnMap.get(columnName); |
647 | if (column == null) { |
648 | throw DbException.get(ErrorCode.COLUMN_NOT_FOUND_1, columnName); |
649 | } |
650 | return column; |
651 | } |
652 | |
653 | /** |
654 | * Does the column with the given name exist? |
655 | * |
656 | * @param columnName the column name |
657 | * @return true if the column exists |
658 | */ |
659 | public boolean doesColumnExist(String columnName) { |
660 | return columnMap.containsKey(columnName); |
661 | } |
662 | |
663 | /** |
664 | * Get the best plan for the given search mask. |
665 | * |
666 | * @param session the session |
667 | * @param masks per-column comparison bit masks, null means 'always false', |
668 | * see constants in IndexCondition |
669 | * @param filter the table filter |
670 | * @param sortOrder the sort order |
671 | * @return the plan item |
672 | */ |
673 | public PlanItem getBestPlanItem(Session session, int[] masks, |
674 | TableFilter filter, SortOrder sortOrder) { |
675 | PlanItem item = new PlanItem(); |
676 | item.setIndex(getScanIndex(session)); |
677 | item.cost = item.getIndex().getCost(session, null, null, null); |
678 | ArrayList<Index> indexes = getIndexes(); |
679 | if (indexes != null && masks != null) { |
680 | for (int i = 1, size = indexes.size(); i < size; i++) { |
681 | Index index = indexes.get(i); |
682 | double cost = index.getCost(session, masks, filter, sortOrder); |
683 | if (cost < item.cost) { |
684 | item.cost = cost; |
685 | item.setIndex(index); |
686 | } |
687 | } |
688 | } |
689 | return item; |
690 | } |
691 | |
692 | /** |
693 | * Get the primary key index if there is one, or null if there is none. |
694 | * |
695 | * @return the primary key index or null |
696 | */ |
697 | public Index findPrimaryKey() { |
698 | ArrayList<Index> indexes = getIndexes(); |
699 | if (indexes != null) { |
700 | for (int i = 0, size = indexes.size(); i < size; i++) { |
701 | Index idx = indexes.get(i); |
702 | if (idx.getIndexType().isPrimaryKey()) { |
703 | return idx; |
704 | } |
705 | } |
706 | } |
707 | return null; |
708 | } |
709 | |
710 | public Index getPrimaryKey() { |
711 | Index index = findPrimaryKey(); |
712 | if (index != null) { |
713 | return index; |
714 | } |
715 | throw DbException.get(ErrorCode.INDEX_NOT_FOUND_1, |
716 | Constants.PREFIX_PRIMARY_KEY); |
717 | } |
718 | |
719 | /** |
720 | * Validate all values in this row, convert the values if required, and |
721 | * update the sequence values if required. This call will also set the |
722 | * default values if required and set the computed column if there are any. |
723 | * |
724 | * @param session the session |
725 | * @param row the row |
726 | */ |
727 | public void validateConvertUpdateSequence(Session session, Row row) { |
728 | for (int i = 0; i < columns.length; i++) { |
729 | Value value = row.getValue(i); |
730 | Column column = columns[i]; |
731 | Value v2; |
732 | if (column.getComputed()) { |
733 | // force updating the value |
734 | value = null; |
735 | v2 = column.computeValue(session, row); |
736 | } |
737 | v2 = column.validateConvertUpdateSequence(session, value); |
738 | if (v2 != value) { |
739 | row.setValue(i, v2); |
740 | } |
741 | } |
742 | } |
743 | |
744 | private static void remove(ArrayList<? extends DbObject> list, DbObject obj) { |
745 | if (list != null) { |
746 | int i = list.indexOf(obj); |
747 | if (i >= 0) { |
748 | list.remove(i); |
749 | } |
750 | } |
751 | } |
752 | |
753 | /** |
754 | * Remove the given index from the list. |
755 | * |
756 | * @param index the index to remove |
757 | */ |
758 | public void removeIndex(Index index) { |
759 | ArrayList<Index> indexes = getIndexes(); |
760 | if (indexes != null) { |
761 | remove(indexes, index); |
762 | if (index.getIndexType().isPrimaryKey()) { |
763 | for (Column col : index.getColumns()) { |
764 | col.setPrimaryKey(false); |
765 | } |
766 | } |
767 | } |
768 | } |
769 | |
770 | /** |
771 | * Remove the given view from the list. |
772 | * |
773 | * @param view the view to remove |
774 | */ |
775 | public void removeView(TableView view) { |
776 | remove(views, view); |
777 | } |
778 | |
779 | /** |
780 | * Remove the given constraint from the list. |
781 | * |
782 | * @param constraint the constraint to remove |
783 | */ |
784 | public void removeConstraint(Constraint constraint) { |
785 | remove(constraints, constraint); |
786 | } |
787 | |
788 | /** |
789 | * Remove a sequence from the table. Sequences are used as identity columns. |
790 | * |
791 | * @param sequence the sequence to remove |
792 | */ |
793 | public final void removeSequence(Sequence sequence) { |
794 | remove(sequences, sequence); |
795 | } |
796 | |
797 | /** |
798 | * Remove the given trigger from the list. |
799 | * |
800 | * @param trigger the trigger to remove |
801 | */ |
802 | public void removeTrigger(TriggerObject trigger) { |
803 | remove(triggers, trigger); |
804 | } |
805 | |
806 | /** |
807 | * Add a view to this table. |
808 | * |
809 | * @param view the view to add |
810 | */ |
811 | public void addView(TableView view) { |
812 | views = add(views, view); |
813 | } |
814 | |
815 | /** |
816 | * Add a constraint to the table. |
817 | * |
818 | * @param constraint the constraint to add |
819 | */ |
820 | public void addConstraint(Constraint constraint) { |
821 | if (constraints == null || constraints.indexOf(constraint) < 0) { |
822 | constraints = add(constraints, constraint); |
823 | } |
824 | } |
825 | |
826 | public ArrayList<Constraint> getConstraints() { |
827 | return constraints; |
828 | } |
829 | |
830 | /** |
831 | * Add a sequence to this table. |
832 | * |
833 | * @param sequence the sequence to add |
834 | */ |
835 | public void addSequence(Sequence sequence) { |
836 | sequences = add(sequences, sequence); |
837 | } |
838 | |
839 | /** |
840 | * Add a trigger to this table. |
841 | * |
842 | * @param trigger the trigger to add |
843 | */ |
844 | public void addTrigger(TriggerObject trigger) { |
845 | triggers = add(triggers, trigger); |
846 | } |
847 | |
848 | private static <T> ArrayList<T> add(ArrayList<T> list, T obj) { |
849 | if (list == null) { |
850 | list = New.arrayList(); |
851 | } |
852 | // self constraints are two entries in the list |
853 | list.add(obj); |
854 | return list; |
855 | } |
856 | |
857 | /** |
858 | * Fire the triggers for this table. |
859 | * |
860 | * @param session the session |
861 | * @param type the trigger type |
862 | * @param beforeAction whether 'before' triggers should be called |
863 | */ |
864 | public void fire(Session session, int type, boolean beforeAction) { |
865 | if (triggers != null) { |
866 | for (TriggerObject trigger : triggers) { |
867 | trigger.fire(session, type, beforeAction); |
868 | } |
869 | } |
870 | } |
871 | |
872 | /** |
873 | * Check whether this table has a select trigger. |
874 | * |
875 | * @return true if it has |
876 | */ |
877 | public boolean hasSelectTrigger() { |
878 | if (triggers != null) { |
879 | for (TriggerObject trigger : triggers) { |
880 | if (trigger.isSelectTrigger()) { |
881 | return true; |
882 | } |
883 | } |
884 | } |
885 | return false; |
886 | } |
887 | |
888 | /** |
889 | * Check if row based triggers or constraints are defined. |
890 | * In this case the fire after and before row methods need to be called. |
891 | * |
892 | * @return if there are any triggers or rows defined |
893 | */ |
894 | public boolean fireRow() { |
895 | return (constraints != null && constraints.size() > 0) || |
896 | (triggers != null && triggers.size() > 0); |
897 | } |
898 | |
899 | /** |
900 | * Fire all triggers that need to be called before a row is updated. |
901 | * |
902 | * @param session the session |
903 | * @param oldRow the old data or null for an insert |
904 | * @param newRow the new data or null for a delete |
905 | * @return true if no further action is required (for 'instead of' triggers) |
906 | */ |
907 | public boolean fireBeforeRow(Session session, Row oldRow, Row newRow) { |
908 | boolean done = fireRow(session, oldRow, newRow, true, false); |
909 | fireConstraints(session, oldRow, newRow, true); |
910 | return done; |
911 | } |
912 | |
913 | private void fireConstraints(Session session, Row oldRow, Row newRow, |
914 | boolean before) { |
915 | if (constraints != null) { |
916 | // don't use enhanced for loop to avoid creating objects |
917 | for (int i = 0, size = constraints.size(); i < size; i++) { |
918 | Constraint constraint = constraints.get(i); |
919 | if (constraint.isBefore() == before) { |
920 | constraint.checkRow(session, this, oldRow, newRow); |
921 | } |
922 | } |
923 | } |
924 | } |
925 | |
926 | /** |
927 | * Fire all triggers that need to be called after a row is updated. |
928 | * |
929 | * @param session the session |
930 | * @param oldRow the old data or null for an insert |
931 | * @param newRow the new data or null for a delete |
932 | * @param rollback when the operation occurred within a rollback |
933 | */ |
934 | public void fireAfterRow(Session session, Row oldRow, Row newRow, |
935 | boolean rollback) { |
936 | fireRow(session, oldRow, newRow, false, rollback); |
937 | if (!rollback) { |
938 | fireConstraints(session, oldRow, newRow, false); |
939 | } |
940 | } |
941 | |
942 | private boolean fireRow(Session session, Row oldRow, Row newRow, |
943 | boolean beforeAction, boolean rollback) { |
944 | if (triggers != null) { |
945 | for (TriggerObject trigger : triggers) { |
946 | boolean done = trigger.fireRow(session, oldRow, newRow, beforeAction, rollback); |
947 | if (done) { |
948 | return true; |
949 | } |
950 | } |
951 | } |
952 | return false; |
953 | } |
954 | |
955 | public boolean isGlobalTemporary() { |
956 | return false; |
957 | } |
958 | |
959 | /** |
960 | * Check if this table can be truncated. |
961 | * |
962 | * @return true if it can |
963 | */ |
964 | public boolean canTruncate() { |
965 | return false; |
966 | } |
967 | |
968 | /** |
969 | * Enable or disable foreign key constraint checking for this table. |
970 | * |
971 | * @param session the session |
972 | * @param enabled true if checking should be enabled |
973 | * @param checkExisting true if existing rows must be checked during this |
974 | * call |
975 | */ |
976 | public void setCheckForeignKeyConstraints(Session session, boolean enabled, |
977 | boolean checkExisting) { |
978 | if (enabled && checkExisting) { |
979 | if (constraints != null) { |
980 | for (Constraint c : constraints) { |
981 | c.checkExistingData(session); |
982 | } |
983 | } |
984 | } |
985 | checkForeignKeyConstraints = enabled; |
986 | } |
987 | |
988 | public boolean getCheckForeignKeyConstraints() { |
989 | return checkForeignKeyConstraints; |
990 | } |
991 | |
992 | /** |
993 | * Get the index that has the given column as the first element. |
994 | * This method returns null if no matching index is found. |
995 | * |
996 | * @param column the column |
997 | * @return the index or null |
998 | */ |
999 | public Index getIndexForColumn(Column column) { |
1000 | ArrayList<Index> indexes = getIndexes(); |
1001 | if (indexes != null) { |
1002 | for (int i = 1, size = indexes.size(); i < size; i++) { |
1003 | Index index = indexes.get(i); |
1004 | if (index.canGetFirstOrLast()) { |
1005 | int idx = index.getColumnIndex(column); |
1006 | if (idx == 0) { |
1007 | return index; |
1008 | } |
1009 | } |
1010 | } |
1011 | } |
1012 | return null; |
1013 | } |
1014 | |
1015 | public boolean getOnCommitDrop() { |
1016 | return onCommitDrop; |
1017 | } |
1018 | |
1019 | public void setOnCommitDrop(boolean onCommitDrop) { |
1020 | this.onCommitDrop = onCommitDrop; |
1021 | } |
1022 | |
1023 | public boolean getOnCommitTruncate() { |
1024 | return onCommitTruncate; |
1025 | } |
1026 | |
1027 | public void setOnCommitTruncate(boolean onCommitTruncate) { |
1028 | this.onCommitTruncate = onCommitTruncate; |
1029 | } |
1030 | |
1031 | /** |
1032 | * If the index is still required by a constraint, transfer the ownership to |
1033 | * it. Otherwise, the index is removed. |
1034 | * |
1035 | * @param session the session |
1036 | * @param index the index that is no longer required |
1037 | */ |
1038 | public void removeIndexOrTransferOwnership(Session session, Index index) { |
1039 | boolean stillNeeded = false; |
1040 | if (constraints != null) { |
1041 | for (Constraint cons : constraints) { |
1042 | if (cons.usesIndex(index)) { |
1043 | cons.setIndexOwner(index); |
1044 | database.updateMeta(session, cons); |
1045 | stillNeeded = true; |
1046 | } |
1047 | } |
1048 | } |
1049 | if (!stillNeeded) { |
1050 | database.removeSchemaObject(session, index); |
1051 | } |
1052 | } |
1053 | |
1054 | /** |
1055 | * Check if a deadlock occurred. This method is called recursively. There is |
1056 | * a circle if the session to be tested has already being visited. If this |
1057 | * session is part of the circle (if it is the clash session), the method |
1058 | * must return an empty object array. Once a deadlock has been detected, the |
1059 | * methods must add the session to the list. If this session is not part of |
1060 | * the circle, or if no deadlock is detected, this method returns null. |
1061 | * |
1062 | * @param session the session to be tested for |
1063 | * @param clash set with sessions already visited, and null when starting |
1064 | * verification |
1065 | * @param visited set with sessions already visited, and null when starting |
1066 | * verification |
1067 | * @return an object array with the sessions involved in the deadlock, or |
1068 | * null |
1069 | */ |
1070 | public ArrayList<Session> checkDeadlock(Session session, Session clash, |
1071 | Set<Session> visited) { |
1072 | return null; |
1073 | } |
1074 | |
1075 | public boolean isPersistIndexes() { |
1076 | return persistIndexes; |
1077 | } |
1078 | |
1079 | public boolean isPersistData() { |
1080 | return persistData; |
1081 | } |
1082 | |
1083 | /** |
1084 | * Compare two values with the current comparison mode. The values may be of |
1085 | * different type. |
1086 | * |
1087 | * @param a the first value |
1088 | * @param b the second value |
1089 | * @return 0 if both values are equal, -1 if the first value is smaller, and |
1090 | * 1 otherwise |
1091 | */ |
1092 | public int compareTypeSave(Value a, Value b) { |
1093 | if (a == b) { |
1094 | return 0; |
1095 | } |
1096 | int dataType = Value.getHigherOrder(a.getType(), b.getType()); |
1097 | a = a.convertTo(dataType); |
1098 | b = b.convertTo(dataType); |
1099 | return a.compareTypeSave(b, compareMode); |
1100 | } |
1101 | |
1102 | public CompareMode getCompareMode() { |
1103 | return compareMode; |
1104 | } |
1105 | |
1106 | /** |
1107 | * Tests if the table can be written. Usually, this depends on the |
1108 | * database.checkWritingAllowed method, but some tables (eg. TableLink) |
1109 | * overwrite this default behaviour. |
1110 | */ |
1111 | public void checkWritingAllowed() { |
1112 | database.checkWritingAllowed(); |
1113 | } |
1114 | |
1115 | /** |
1116 | * Get or generate a default value for the given column. |
1117 | * |
1118 | * @param session the session |
1119 | * @param column the column |
1120 | * @return the value |
1121 | */ |
1122 | public Value getDefaultValue(Session session, Column column) { |
1123 | Expression defaultExpr = column.getDefaultExpression(); |
1124 | Value v; |
1125 | if (defaultExpr == null) { |
1126 | v = column.validateConvertUpdateSequence(session, null); |
1127 | } else { |
1128 | v = defaultExpr.getValue(session); |
1129 | } |
1130 | return column.convert(v); |
1131 | } |
1132 | |
1133 | @Override |
1134 | public boolean isHidden() { |
1135 | return isHidden; |
1136 | } |
1137 | |
1138 | public void setHidden(boolean hidden) { |
1139 | this.isHidden = hidden; |
1140 | } |
1141 | |
1142 | public boolean isMVStore() { |
1143 | return false; |
1144 | } |
1145 | |
1146 | } |