Source code: org/hsqldb/Table.java
1 /* Copyrights and Licenses
2 *
3 * This product includes Hypersonic SQL.
4 * Originally developed by Thomas Mueller and the Hypersonic SQL Group.
5 *
6 * Copyright (c) 1995-2000 by the Hypersonic SQL Group. All rights reserved.
7 * Redistribution and use in source and binary forms, with or without modification, are permitted
8 * provided that the following conditions are met:
9 * - Redistributions of source code must retain the above copyright notice, this list of conditions
10 * and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright notice, this list of
12 * conditions and the following disclaimer in the documentation and/or other materials
13 * provided with the distribution.
14 * - All advertising materials mentioning features or use of this software must display the
15 * following acknowledgment: "This product includes Hypersonic SQL."
16 * - Products derived from this software may not be called "Hypersonic SQL" nor may
17 * "Hypersonic SQL" appear in their names without prior written permission of the
18 * Hypersonic SQL Group.
19 * - Redistributions of any form whatsoever must retain the following acknowledgment: "This
20 * product includes Hypersonic SQL."
21 * This software is provided "as is" and any expressed or implied warranties, including, but
22 * not limited to, the implied warranties of merchantability and fitness for a particular purpose are
23 * disclaimed. In no event shall the Hypersonic SQL Group or its contributors be liable for any
24 * direct, indirect, incidental, special, exemplary, or consequential damages (including, but
25 * not limited to, procurement of substitute goods or services; loss of use, data, or profits;
26 * or business interruption). However caused any on any theory of liability, whether in contract,
27 * strict liability, or tort (including negligence or otherwise) arising in any way out of the use of this
28 * software, even if advised of the possibility of such damage.
29 * This software consists of voluntary contributions made by many individuals on behalf of the
30 * Hypersonic SQL Group.
31 *
32 *
33 * For work added by the HSQL Development Group:
34 *
35 * Copyright (c) 2001-2002, The HSQL Development Group
36 * All rights reserved.
37 *
38 * Redistribution and use in source and binary forms, with or without
39 * modification, are permitted provided that the following conditions are met:
40 *
41 * Redistributions of source code must retain the above copyright notice, this
42 * list of conditions and the following disclaimer, including earlier
43 * license statements (above) and comply with all above license conditions.
44 *
45 * Redistributions in binary form must reproduce the above copyright notice,
46 * this list of conditions and the following disclaimer in the documentation
47 * and/or other materials provided with the distribution, including earlier
48 * license statements (above) and comply with all above license conditions.
49 *
50 * Neither the name of the HSQL Development Group nor the names of its
51 * contributors may be used to endorse or promote products derived from this
52 * software without specific prior written permission.
53 *
54 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
55 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
56 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
57 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
58 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
59 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
60 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
61 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
62 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
63 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
64 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
65 */
66
67
68 package org.hsqldb;
69
70 import org.hsqldb.lib.ArrayUtil;
71 import org.hsqldb.lib.StringUtil;
72 import java.sql.SQLException;
73 import java.sql.Types;
74 import java.util.Vector;
75 import java.util.Hashtable;
76
77 // fredt@users 20020405 - patch 1.7.0 by fredt - quoted identifiers
78 // for sql standard quoted identifiers for column and table names and aliases
79 // applied to different places
80 // fredt@users 20020225 - patch 1.7.0 - restructuring
81 // some methods moved from Database.java, some rewritten
82 // changes to several methods
83 // fredt@users 20020225 - patch 1.7.0 - CASCADING DELETES
84 // fredt@users 20020225 - patch 1.7.0 - named constraints
85 // boucherb@users 20020225 - patch 1.7.0 - multi-column primary keys
86 // fredt@users 20020221 - patch 513005 by sqlbob@users (RMP)
87 // tony_lai@users 20020820 - patch 595099 by tlai@users - user defined PK name
88 // tony_lai@users 20020820 - patch 595172 by tlai@users - drop constraint fix
89
90 /**
91 * Holds the data structures and methods for creation of a database table.
92 *
93 *
94 * @version 1.7.0
95 */
96 class Table {
97
98 // types of table
99 static final int SYSTEM_TABLE = 0;
100 static final int TEMP_TABLE = 1;
101 static final int MEMORY_TABLE = 2;
102 static final int CACHED_TABLE = 3;
103 static final int TEMP_TEXT_TABLE = 4;
104 static final int TEXT_TABLE = 5;
105 static final int VIEW = 6;
106
107 // name of the column added to tables without primary key
108 static final String DEFAULT_PK = "";
109
110 // main properties
111 private Vector vColumn; // columns in table
112 private Vector vIndex; // vIndex(0) is the primary key index
113 private int[] iPrimaryKey; // column numbers for primary key
114 private int iIndexCount; // size of vIndex
115 private int iIdentityColumn; // -1 means no such row
116 private int iIdentityId; // next value of identity column
117 Vector vConstraint; // constrainst for the table
118 Vector vTrigs[]; // array of trigger Vectors
119 private int[] colTypes; // fredt - types of columns
120 private boolean isSystem;
121 private boolean isText;
122 private boolean isView;
123
124 // properties for subclasses
125 protected int iColumnCount; // inclusive the hidden primary key
126 protected int iVisibleColumns; // exclusive of hidden primary key
127 protected Database dDatabase;
128 protected Cache cCache;
129 protected HsqlName tableName; // SQL name
130 protected int tableType;
131 protected Session ownerSession; // fredt - set for temp tables only
132 protected boolean isReadOnly;
133 protected boolean isTemp;
134 protected boolean isCached;
135 protected int indexType; // fredt - type of index used
136
137 /**
138 * Constructor declaration
139 *
140 * @param db
141 * @param isTemp
142 * @param name
143 * @param cached
144 * @param nameQuoted Description of the Parameter
145 * @exception SQLException Description of the Exception
146 */
147 Table(Database db, HsqlName name, int type,
148 Session session) throws SQLException {
149
150 dDatabase = db;
151
152 if (type == SYSTEM_TABLE) {
153 isTemp = true;
154 } else if (type == TEMP_TABLE) {
155 Trace.doAssert(session != null);
156
157 isTemp = true;
158 ownerSession = session;
159 } else if (type == CACHED_TABLE) {
160 cCache = db.logger.getCache();
161
162 if (cCache != null) {
163 isCached = true;
164 } else {
165 type = MEMORY_TABLE;
166 }
167 } else if (type == TEMP_TEXT_TABLE) {
168 Trace.doAssert(session != null);
169
170 if (!db.logger.hasLog()) {
171 throw Trace.error(Trace.DATABASE_IS_MEMORY_ONLY);
172 }
173
174 isTemp = true;
175 isText = true;
176 isReadOnly = true;
177 isCached = true;
178 ownerSession = session;
179 } else if (type == TEXT_TABLE) {
180 if (!db.logger.hasLog()) {
181 throw Trace.error(Trace.DATABASE_IS_MEMORY_ONLY);
182 }
183
184 isText = true;
185 isCached = true;
186 } else if (type == VIEW) {
187 isView = true;
188 }
189
190 if (isText) {
191 indexType = Index.POINTER_INDEX;
192 } else if (isCached) {
193 indexType = Index.DISK_INDEX;
194 }
195
196 // type may have changed above for CACHED tables
197 tableType = type;
198 tableName = name;
199 iPrimaryKey = null;
200 iIdentityColumn = -1;
201 vColumn = new Vector();
202 vIndex = new Vector();
203 vConstraint = new Vector();
204 vTrigs = new Vector[TriggerDef.numTrigs()];
205
206 for (int vi = 0; vi < TriggerDef.numTrigs(); vi++) {
207 vTrigs[vi] = new Vector();
208 }
209 }
210
211 boolean equals(String other, Session c) {
212
213 if (isTemp && c.getId() != ownerSession.getId()) {
214 return false;
215 }
216
217 return (tableName.name.equals(other));
218 }
219
220 boolean equals(String other) {
221 return (tableName.name.equals(other));
222 }
223
224 final boolean isText() {
225 return isText;
226 }
227
228 final boolean isTemp() {
229 return isTemp;
230 }
231
232 final boolean isView() {
233 return isView;
234 }
235
236 final int getIndexType() {
237 return indexType;
238 }
239
240 final boolean isDataReadOnly() {
241 return isReadOnly;
242 }
243
244 void setDataReadOnly(boolean value) throws SQLException {
245 isReadOnly = value;
246 }
247
248 Session getOwnerSession() {
249 return ownerSession;
250 }
251
252 protected void setDataSource(String source, boolean isDesc,
253 Session s) throws SQLException {
254
255 // Same exception as setIndexRoots.
256 throw (Trace.error(Trace.TABLE_NOT_FOUND));
257 }
258
259 protected String getDataSource() throws SQLException {
260 return null;
261 }
262
263 protected boolean isDescDataSource() throws SQLException {
264 return (false);
265 }
266
267 /**
268 * Method declaration
269 *
270 * @param c
271 */
272 void addConstraint(Constraint c) {
273 vConstraint.addElement(c);
274 }
275
276 /**
277 * Method declaration
278 *
279 * @return
280 */
281 Vector getConstraints() {
282 return vConstraint;
283 }
284
285 /**
286 * Get the index supporting a constraint that can be used as an index
287 * of the given type and index column signature.
288 *
289 * @param col column list array
290 * @param unique for the index
291 * @return
292 */
293 Index getConstraintIndexForColumns(int[] col, boolean unique) {
294
295 Index currentIndex = getPrimaryIndex();
296
297 if (ArrayUtil.haveEquality(currentIndex.getColumns(), col,
298 col.length, unique)) {
299 return currentIndex;
300 }
301
302 for (int i = 0; i < vConstraint.size(); i++) {
303 Constraint c = (Constraint) vConstraint.elementAt(i);
304
305 currentIndex = c.getMainIndex();
306
307 if (ArrayUtil.haveEquality(currentIndex.getColumns(), col,
308 col.length, unique)) {
309 return currentIndex;
310 }
311 }
312
313 return null;
314 }
315
316 /**
317 * Method declaration
318 *
319 * @param from
320 * @param type
321 * @return
322 */
323 int getNextConstraintIndex(int from, int type) {
324
325 for (int i = from; i < vConstraint.size(); i++) {
326 Constraint c = (Constraint) vConstraint.elementAt(i);
327
328 if (c.getType() == type) {
329 return i;
330 }
331 }
332
333 return -1;
334 }
335
336 /**
337 * Method declaration
338 *
339 * @param name
340 * @param type
341 * @throws SQLException
342 */
343 void addColumn(String name, int type) throws SQLException {
344
345 Column column = new Column(new HsqlName(name, false), true, type, 0,
346 0, false, false, null);
347
348 addColumn(column);
349 }
350
351 // fredt@users 20020220 - patch 475199 - duplicate column
352
353 /**
354 * Performs the table level checks and adds a column to the table at the
355 * DDL level.
356 *
357 * @param column new column to add
358 * @throws SQLException when table level checks fail
359 */
360 void addColumn(Column column) throws SQLException {
361
362 if (searchColumn(column.columnName.name) >= 0) {
363 throw Trace.error(Trace.COLUMN_ALREADY_EXISTS);
364 }
365
366 if (column.isIdentity()) {
367 Trace.check(column.getType() == Types.INTEGER,
368 Trace.WRONG_DATA_TYPE, column.columnName.name);
369 Trace.check(iIdentityColumn == -1, Trace.SECOND_PRIMARY_KEY,
370 column.columnName.name);
371
372 iIdentityColumn = iColumnCount;
373 }
374
375 Trace.doAssert(iPrimaryKey == null, "Table.addColumn");
376 vColumn.addElement(column);
377
378 iColumnCount++;
379 }
380
381 /**
382 * Method declaration
383 *
384 * @param result
385 * @throws SQLException
386 */
387 void addColumns(Result result) throws SQLException {
388
389 for (int i = 0; i < result.getColumnCount(); i++) {
390 Column column = new Column(
391 new HsqlName(result.sLabel[i], result.isLabelQuoted[i]),
392 true, result.colType[i], result.colSize[i],
393 result.colScale[i], false, false, null);
394
395 addColumn(column);
396 }
397 }
398
399 /**
400 * Method declaration
401 *
402 * @return
403 */
404 HsqlName getName() {
405 return tableName;
406 }
407
408 /**
409 * Changes table name. Used by 'alter table rename to'
410 *
411 * @param name
412 * @param isquoted
413 * @throws SQLException
414 */
415 void setName(String name, boolean isquoted) {
416
417 tableName.rename(name, isquoted);
418
419 if (HsqlName.isReservedName(getPrimaryIndex().getName().name)) {
420 getPrimaryIndex().getName().rename("SYS_PK", name, isquoted);
421 }
422 }
423
424 /**
425 * Method declaration
426 *
427 * @return
428 */
429 int getInternalColumnCount() {
430
431 // todo: this is a temporary solution;
432 // the the hidden column is not really required
433 return iColumnCount;
434 }
435
436 protected Table duplicate() throws SQLException {
437
438 Table t = (new Table(dDatabase, tableName, tableType, ownerSession));
439
440 return t;
441 }
442
443 /**
444 * Match two columns arrays for length and type of coluns
445 *
446 * @param col column array from this Table
447 * @param other the other Table object
448 * @param othercol column array from the other Table
449 * @throws SQLException if there is a mismatch
450 */
451 void checkColumnsMatch(int[] col, Table other,
452 int[] othercol) throws SQLException {
453
454 if (col.length != othercol.length) {
455 throw Trace.error(Trace.COLUMN_COUNT_DOES_NOT_MATCH);
456 }
457
458 for (int i = 0; i < col.length; i++) {
459
460 // integrity check - should not throw in normal operation
461 if (col[i] >= iColumnCount || othercol[i] >= other.iColumnCount) {
462 throw Trace.error(Trace.COLUMN_COUNT_DOES_NOT_MATCH);
463 }
464
465 if (getColumn(col[i]).getType()
466 != other.getColumn(othercol[i]).getType()) {
467 throw Trace.error(Trace.COLUMN_TYPE_MISMATCH);
468 }
469 }
470 }
471
472 // fredt@users 20020405 - patch 1.7.0 by fredt - DROP and CREATE INDEX bug
473
474 /**
475 * DROP INDEX and CREATE INDEX on non empty tables both recreate the table
476 * and the data to reflect the new indexing structure. The new structure
477 * should be reflected in the DDL script, otherwise if a
478 * SHUTDOWN IMMEDIATE occures, the following will happen:<br>
479 * If the table is cached, the index roots will be different from what
480 * is specified in SET INDEX ROOTS. <br>
481 * If the table is memory, the old index will be used until the script
482 * reaches drop index etc. and data is recreated again.<b>
483 *
484 * The fix avoids scripting the row insert and delete ops.
485 *
486 * Constraints that need removing are removed outside this (fredt@users)
487 * @param withoutindex
488 * @param newcolumn
489 * @param colindex
490 * @param adjust -1 or 0 or +1
491 * @return
492 * @throws SQLException
493 */
494 Table moveDefinition(String withoutindex, Column newcolumn, int colindex,
495 int adjust) throws SQLException {
496
497 Table tn = duplicate();
498
499 for (int i = 0; i < iVisibleColumns + 1; i++) {
500 if (i == colindex) {
501 if (adjust > 0) {
502 tn.addColumn(newcolumn);
503 } else if (adjust < 0) {
504 continue;
505 }
506 }
507
508 if (i == iVisibleColumns) {
509 break;
510 }
511
512 tn.addColumn(getColumn(i));
513 }
514
515 // treat it the same as new table creation and
516 // take account of the a hidden column
517 int[] primarykey = (iPrimaryKey[0] == iVisibleColumns) ? null
518 : iPrimaryKey;
519
520 if (primarykey != null) {
521 int[] newpk = ArrayUtil.toAdjustedColumnArray(primarykey,
522 colindex, adjust);
523
524 // fredt - we don't drop pk column
525 // in future we can drop signle column pk wih no fk reference
526 if (primarykey.length != newpk.length) {
527 throw Trace.error(Trace.DROP_PRIMARY_KEY);
528 } else {
529 primarykey = newpk;
530 }
531 }
532
533 // tony_lai@users - 20020820 - patch 595099 - primary key names
534 tn.createPrimaryKey(getIndex(0).getName(), primarykey);
535
536 tn.vConstraint = vConstraint;
537
538 for (int i = 1; i < getIndexCount(); i++) {
539 Index idx = getIndex(i);
540
541 if (withoutindex != null
542 && idx.getName().name.equals(withoutindex)) {
543 continue;
544 }
545
546 Index newidx = tn.createAdjustedIndex(idx, colindex, adjust);
547
548 if (newidx == null) {
549
550 // fredt - todo - better error message
551 throw Trace.error(Trace.INDEX_ALREADY_EXISTS);
552 }
553 }
554
555 return tn;
556 }
557
558 void updateConstraints(Table to, int colindex,
559 int adjust) throws SQLException {
560
561 for (int j = 0; j < vConstraint.size(); j++) {
562 Constraint c = (Constraint) vConstraint.elementAt(j);
563
564 c.replaceTable(to, this, colindex, adjust);
565 }
566 }
567
568 /**
569 * Method declaration
570 *
571 * @return
572 */
573 int getColumnCount() {
574 return iVisibleColumns;
575 }
576
577 /**
578 * Method declaration
579 *
580 * @return
581 */
582 int getIndexCount() {
583 return iIndexCount;
584 }
585
586 /**
587 * Method declaration
588 *
589 * @return
590 */
591 int getIdentityColumn() {
592 return iIdentityColumn;
593 }
594
595 /**
596 * Method declaration
597 *
598 * @param c
599 * @return
600 * @throws SQLException
601 */
602 int getColumnNr(String c) throws SQLException {
603
604 int i = searchColumn(c);
605
606 if (i == -1) {
607 throw Trace.error(Trace.COLUMN_NOT_FOUND, c);
608 }
609
610 return i;
611 }
612
613 /**
614 * Method declaration
615 *
616 * @param c
617 * @return
618 */
619 int searchColumn(String c) {
620
621 for (int i = 0; i < iColumnCount; i++) {
622 if (c.equals(((Column) vColumn.elementAt(i)).columnName.name)) {
623 return i;
624 }
625 }
626
627 return -1;
628 }
629
630 /**
631 * Method declaration
632 *
633 * @return
634 * @throws SQLException
635 */
636 Index getPrimaryIndex() {
637
638 if (iPrimaryKey == null) {
639 return null;
640 }
641
642 return getIndex(0);
643 }
644
645 /**
646 * Method declaration
647 *
648 * @param column
649 * @return
650 * @throws SQLException
651 */
652 Index getIndexForColumn(int column) throws SQLException {
653
654 for (int i = 0; i < iIndexCount; i++) {
655 Index h = getIndex(i);
656
657 if (h.getColumns()[0] == column) {
658 return h;
659 }
660 }
661
662 return null;
663 }
664
665 /**
666 * Finds an existing index for a foreign key column group
667 *
668 * @param col
669 * @return
670 * @throws SQLException
671 */
672 Index getIndexForColumns(int col[], boolean unique) throws SQLException {
673
674 for (int i = 0; i < iIndexCount; i++) {
675 Index currentindex = getIndex(i);
676 int indexcol[] = currentindex.getColumns();
677
678 if (ArrayUtil.haveEquality(indexcol, col, col.length, unique)) {
679 if (!unique || currentindex.isUnique()) {
680 return currentindex;
681 }
682 }
683 }
684
685 return null;
686 }
687
688 /**
689 * Return the list of file pointers to root nodes for this table's
690 * indexes.
691 */
692 int[] getIndexRootsArray() throws SQLException {
693
694 int[] roots = new int[iIndexCount];
695
696 for (int i = 0; i < iIndexCount; i++) {
697 Node f = getIndex(i).getRoot();
698
699 roots[i] = (f != null) ? f.getKey()
700 : -1;
701 }
702
703 return roots;
704 }
705
706 /**
707 * Method declaration
708 *
709 * @return
710 * @throws SQLException
711 */
712 String getIndexRoots() throws SQLException {
713
714 Trace.doAssert(isCached, "Table.getIndexRootData");
715
716 String roots = StringUtil.getList(getIndexRootsArray(), " ", "");
717 StringBuffer s = new StringBuffer(roots);
718
719 s.append(' ');
720 s.append(iIdentityId);
721
722 return s.toString();
723 }
724
725 /**
726 * Method declaration
727 *
728 * @param s
729 * @throws SQLException
730 */
731 void setIndexRoots(int[] roots) throws SQLException {
732
733 Trace.check(isCached, Trace.TABLE_NOT_FOUND);
734
735 for (int i = 0; i < iIndexCount; i++) {
736 int p = roots[i];
737 Row r = null;
738
739 if (p != -1) {
740 r = cCache.getRow(p, this);
741 }
742
743 Node f = null;
744
745 if (r != null) {
746 f = r.getNode(i);
747 }
748
749 getIndex(i).setRoot(f);
750 }
751 }
752
753 /**
754 * Method declaration
755 *
756 * @param s
757 * @throws SQLException
758 */
759 void setIndexRoots(String s) throws SQLException {
760
761 // the user may try to set this; this is not only internal problem
762 Trace.check(isCached, Trace.TABLE_NOT_FOUND);
763
764 int[] roots = new int[iIndexCount];
765 int j = 0;
766
767 for (int i = 0; i < iIndexCount; i++) {
768 int n = s.indexOf(' ', j);
769 int p = Integer.parseInt(s.substring(j, n));
770
771 roots[i] = p;
772 j = n + 1;
773 }
774
775 setIndexRoots(roots);
776
777 iIdentityId = Integer.parseInt(s.substring(j));
778 }
779
780 /**
781 * Method declaration
782 *
783 * @param index
784 * @return
785 */
786 Index getNextIndex(Index index) {
787
788 int i = 0;
789
790 if (index != null) {
791 for (; i < iIndexCount && getIndex(i) != index; i++) {
792 ;
793 }
794
795 i++;
796 }
797
798 if (i < iIndexCount) {
799 return getIndex(i);
800 }
801
802 return null; // no more indexes
803 }
804
805 /**
806 * Shortcut for creating default PK's
807 *
808 * @throws SQLException
809 */
810 void createPrimaryKey() throws SQLException {
811
812 // tony_lai@users 20020820 - patch 595099
813 createPrimaryKey(null, null);
814 }
815
816 /**
817 * Adds the SYSTEM_ID column if no primary key is specified in DDL.
818 * Creates a single or multi-column primary key and index. sets the
819 * colTypes array. Finalises the creation of the table. (fredt@users)
820 *
821 * @param columns primary key column(s) or null if no primary key in DDL
822 * @throws SQLException
823 */
824
825 // tony_lai@users 20020820 - patch 595099
826 void createPrimaryKey(HsqlName pkName,
827 int[] columns) throws SQLException {
828
829 Trace.doAssert(iPrimaryKey == null, "Table.createPrimaryKey(column)");
830
831 iVisibleColumns = iColumnCount;
832
833 if (columns == null) {
834 columns = new int[]{ iColumnCount };
835
836 Column column = new Column(new HsqlName(DEFAULT_PK, false),
837 false, Types.INTEGER, 0, 0, true,
838 true, null);
839
840 addColumn(column);
841 } else {
842 for (int i = 0; i < columns.length; i++) {
843 getColumn(columns[i]).setNullable(false);
844 getColumn(columns[i]).setPrimaryKey(true);
845 }
846 }
847
848 iPrimaryKey = columns;
849
850 // tony_lai@users 20020820 - patch 595099
851 HsqlName name = pkName != null ? pkName
852 : new HsqlName("SYS_PK",
853 tableName.name,
854 tableName.isNameQuoted);
855
856 createIndexPrivate(columns, name, true);
857
858 colTypes = new int[iColumnCount];
859
860 for (int i = 0; i < iColumnCount; i++) {
861 colTypes[i] = getColumn(i).getType();
862 }
863 }
864
865 /**
866 * Create new index taking into account removal or addition a column of
867 * the table.
868 *
869 * @param index
870 * @param colindex
871 * @param ajdust -1 or 0 or 1
872 * @return new index or null if a column is removed from index
873 * @throws SQLException
874 */
875 private Index createAdjustedIndex(Index index, int colindex,
876 int adjust) throws SQLException {
877
878 int[] colarr = ArrayUtil.getAdjustedColumnArray(index.getColumns(),
879 index.getVisibleColumns(), colindex, adjust);
880
881 if (colarr.length != index.getVisibleColumns()) {
882 return null;
883 }
884
885 return createIndexPrivate(colarr, index.getName(), index.isUnique());
886 }
887
888 /**
889 * Method declaration
890 *
891 * @param column
892 * @param name
893 * @param unique
894 * @return Description of the Return Value
895 * @throws SQLException
896 */
897 Index createIndexPrivate(int column[], HsqlName name,
898 boolean unique) throws SQLException {
899
900 Trace.doAssert(iPrimaryKey != null, "createIndex");
901
902 int s = column.length;
903 int t = iPrimaryKey.length;
904
905 // The primary key field is added for non-unique indexes
906 // making all indexes unique
907 int col[] = new int[unique ? s
908 : s + t];
909 int type[] = new int[unique ? s
910 : s + t];
911
912 for (int j = 0; j < s; j++) {
913 col[j] = column[j];
914 type[j] = getColumn(col[j]).getType();
915 }
916
917 if (!unique) {
918 for (int j = 0; j < t; j++) {
919 col[s + j] = iPrimaryKey[j];
920 type[s + j] = getColumn(iPrimaryKey[j]).getType();
921 }
922 }
923
924 // fredt - visible columns of index is 0 for system generated PK
925 if (col[0] == iVisibleColumns) {
926 s = 0;
927 }
928
929 Index newindex = new Index(name, this, col, type, unique, s);
930
931 // fredt@users 20020225 - comment
932 // in future we can avoid duplicate indexes
933 /*
934 for (int i = 0; i < iIndexCount; i++) {
935 if ( newindex.isEquivalent(getIndex(i))){
936 return;
937 }
938 }
939 */
940 Trace.doAssert(isEmpty(), "createIndex");
941 vIndex.addElement(newindex);
942
943 iIndexCount++;
944
945 return newindex;
946 }
947
948 // fredt@users 20020315 - patch 1.7.0 - drop index bug
949 // don't drop an index used for a foreign key
950
951 /**
952 * Checks for use of a named index in table constraints
953 *
954 * @param indexname
955 * @param ignore null or a set of constraints that should be ignored in checks
956 * @throws SQLException if index is used in a constraint
957 */
958 void checkDropIndex(String indexname,
959 Hashtable ignore) throws SQLException {
960
961 Index index = this.getIndex(indexname);
962
963 if (index == null) {
964 throw Trace.error(Trace.INDEX_NOT_FOUND, indexname);
965 }
966
967 if (index.equals(getIndex(0))) {
968 throw Trace.error(Trace.DROP_PRIMARY_KEY, indexname);
969 }
970
971 for (int i = 0; i < vConstraint.size(); i++) {
972 Constraint c = (Constraint) vConstraint.elementAt(i);
973
974 if (ignore.get(c) != null) {
975 continue;
976 }
977
978 if (c.isIndexFK(index)) {
979 throw Trace.error(Trace.DROP_FK_INDEX, indexname);
980 }
981
982 if (c.isIndexUnique(index)) {
983 throw Trace.error(Trace.SYSTEM_INDEX, indexname);
984 }
985 }
986
987 return;
988 }
989
990 /**
991 * Method declaration
992 *
993 * @return
994 */
995 boolean isEmpty() {
996
997 if (iIndexCount == 0) {
998 return true;
999 }
1000
1001 return getIndex(0).getRoot() == null;
1002 }
1003
1004 /**
1005 * Method declaration
1006 *
1007 * @return
1008 */
1009 Object[] getNewRow() {
1010 return new Object[iColumnCount];
1011 }
1012
1013 /**
1014 * Method declaration
1015 *
1016 * @param from
1017 * @param colindex index of the column that was added or removed
1018 * @throws SQLException normally for lack of resources
1019 */
1020 void moveData(Table from, int colindex, int adjust) throws SQLException {
1021
1022 Object colvalue = null;
1023
1024 if (adjust > 0) {
1025 Column column = getColumn(colindex);
1026
1027 colvalue = Column.convertObject(column.getDefaultString(),
1028 column.getType());
1029 }
1030
1031 Index index = from.getPrimaryIndex();
1032 Node n = index.first();
1033
1034 while (n != null) {
1035 if (Trace.STOP) {
1036 Trace.stop();
1037 }
1038
1039 Object o[] = n.getData();
1040 Object newrow[] = this.getNewRow();
1041
1042 ArrayUtil.copyAdjustArray(o, newrow, colvalue, colindex, adjust);
1043 insertNoCheck(newrow, null, false);
1044
1045 n = index.next(n);
1046 }
1047
1048 index = from.getPrimaryIndex();
1049 n = index.first();
1050
1051 while (n != null) {
1052 if (Trace.STOP) {
1053 Trace.stop();
1054 }
1055
1056 Node nextnode = index.next(n);
1057 Object o[] = n.getData();
1058
1059 from.deleteNoCheck(o, null, false);
1060
1061 n = nextnode;
1062 }
1063 }
1064
1065 /**
1066 * Method declaration
1067 *
1068 * @param col
1069 * @param deleted
1070 * @param inserted
1071 * @throws SQLException
1072 */
1073 void checkUpdate(int col[], Result deleted,
1074 Result inserted) throws SQLException {
1075
1076 Trace.check(!isReadOnly, Trace.DATA_IS_READONLY);
1077
1078 if (dDatabase.isReferentialIntegrity()) {
1079 for (int i = 0; i < vConstraint.size(); i++) {
1080 Constraint v = (Constraint) vConstraint.elementAt(i);
1081
1082 v.checkUpdate(col, deleted, inserted);
1083 }
1084 }
1085 }
1086
1087 /**
1088 * Method declaration
1089 *
1090 * @param result
1091 * @param c
1092 * @throws SQLException
1093 */
1094 void insert(Result result, Session c) throws SQLException {
1095
1096 // if violation of constraints can occur, insert must be rolled back
1097 // outside of this function!
1098 Record r = result.rRoot;
1099 int len = result.getColumnCount();
1100
1101 while (r != null) {
1102 Object row[] = getNewRow();
1103
1104 for (int i = 0; i < len; i++) {
1105 row[i] = r.data[i];
1106 }
1107
1108 insert(row, c);
1109
1110 r = r.next;
1111 }
1112 }
1113
1114 /**
1115 * Method declaration
1116 *
1117 * @param row
1118 * @param c
1119 * @throws SQLException
1120 */
1121 void insert(Object row[], Session c) throws SQLException {
1122
1123 Trace.check(!isReadOnly, Trace.DATA_IS_READONLY);
1124 fireAll(TriggerDef.INSERT_BEFORE, row);
1125
1126 if (dDatabase.isReferentialIntegrity()) {
1127 for (int i = 0; i < vConstraint.size(); i++) {
1128 ((Constraint) vConstraint.elementAt(i)).checkInsert(row);
1129 }
1130 }
1131
1132 insertNoCheck(row, c, true);
1133 fireAll(TriggerDef.INSERT_AFTER, row);
1134 }
1135
1136 /**
1137 * Method declaration
1138 *
1139 * @param row
1140 * @param c
1141 * @param log
1142 * @throws SQLException
1143 */
1144 void insertNoCheck(Object row[], Session c,
1145 boolean log) throws SQLException {
1146
1147 for (int i = 0; i < iColumnCount; i++) {
1148 if (row[i] == null) {
1149 Column col = getColumn(i);
1150 boolean nullOK = col.isNullable() || col.isIdentity();
1151
1152 if (!nullOK) {
1153 throw Trace.error(Trace.TRY_TO_INSERT_NULL);
1154 }
1155 }
1156 }
1157
1158 int nextId = iIdentityId;
1159
1160 if (iIdentityColumn != -1) {
1161 Number id = (Number) row[iIdentityColumn];
1162
1163 if (id == null) {
1164 row[iIdentityColumn] = new Integer(iIdentityId);
1165 } else {
1166 int columnId = id.intValue();
1167
1168 if (iIdentityId < columnId) {
1169 iIdentityId = nextId = columnId;
1170 }
1171 }
1172 }
1173
1174 Row r = Row.newRow(this, row);
1175
1176 if (isText) {
1177
1178 //-- Always inserted at end of file.
1179 nextId = ((CachedRow) r).iPos + ((CachedRow) r).storageSize;
1180 } else {
1181 nextId++;
1182 }
1183
1184 indexRow(r, true);
1185
1186 if (c != null) {
1187 c.setLastIdentity(iIdentityId);
1188 c.addTransactionInsert(this, row);
1189 }
1190
1191 iIdentityId = nextId;
1192
1193 if (log &&!isTemp &&!isReadOnly && dDatabase.logger.hasLog()) {
1194 dDatabase.logger.writeToLog(c, getInsertStatement(row));
1195 }
1196 }
1197
1198 /**
1199 * Method declaration
1200 *
1201 * @param trigVecIndx
1202 * @param row
1203 */
1204 void fireAll(int trigVecIndx, Object row[]) {
1205
1206 if (!dDatabase.isReferentialIntegrity()) { // reloading db
1207 return;
1208 }
1209
1210 Vector trigVec = vTrigs[trigVecIndx];
1211 int trCount = trigVec.size();
1212
1213 for (int i = 0; i < trCount; i++) {
1214 TriggerDef td = (TriggerDef) trigVec.elementAt(i);
1215
1216 td.push(row); // tell the trigger thread to fire with this row
1217 }
1218 }
1219
1220// statement-level triggers
1221
1222 /**
1223 * Method declaration
1224 *
1225 * @param trigVecIndx
1226 */
1227 void fireAll(int trigVecIndx) {
1228
1229 Object row[] = new Object[1];
1230
1231 row[0] = new String("Statement-level");
1232
1233 fireAll(trigVecIndx, row);
1234 }
1235
1236 /**
1237 * Method declaration
1238 *
1239 * @param trigDef
1240 */
1241 void addTrigger(TriggerDef trigDef) {
1242
1243 if (Trace.TRACE) {
1244 Trace.trace("Trigger added "
1245 + String.valueOf(trigDef.vectorIndx));
1246 }
1247
1248 vTrigs[trigDef.vectorIndx].addElement(trigDef);
1249 }
1250
1251// fredt@users 20020225 - patch 1.7.0 - CASCADING DELETES
1252
1253 /**
1254 * Method is called recursively on a tree of tables from the current one
1255 * until no referring foreign-key table is left. In the process, if a
1256 * non-cascading foreign-key referring table contains data, an exception
1257 * is thrown. Parameter delete indicates whether to delete refering rows.
1258 * The method is called first to check if the row can be deleted, then to
1259 * delete the row and all the refering rows. (fredt@users)
1260 *
1261 * @param row
1262 * @param session
1263 * @param delete
1264 * @throws SQLException
1265 */
1266 void checkCascadeDelete(Object[] row, Session session,
1267 boolean delete) throws SQLException {
1268
1269 for (int i = 0; i < vConstraint.size(); i++) {
1270 Constraint c = (Constraint) vConstraint.elementAt(i);
1271
1272 if (c.getType() != Constraint.MAIN || c.getRef() == null) {
1273 continue;
1274 }
1275
1276 Node refnode = c.findFkRef(row);
1277
1278 if (refnode == null) {
1279
1280 // no referencing row found
1281 continue;
1282 }
1283
1284 Table reftable = c.getRef();
1285
1286 // shortcut when deltable has no imported constraint
1287 boolean hasref =
1288 reftable.getNextConstraintIndex(0, Constraint.MAIN) != -1;
1289
1290 if (delete == false && hasref == false) {
1291 return;
1292 }
1293
1294 Index refindex = c.getRefIndex();
1295 int maincolumns[] = c.getMainColumns();
1296 Object[] mainobjects = new Object[maincolumns.length];
1297
1298 ArrayUtil.copyColumnValues(row, maincolumns, mainobjects);
1299
1300 // walk the index for all the nodes that reference delnode
1301 for (Node n = refnode;
1302 refindex.comparePartialRowNonUnique(
1303 mainobjects, n.getData()) == 0; ) {
1304
1305 // get the next node before n is deleted
1306 Node nextn = refindex.next(n);
1307
1308 if (hasref) {
1309 reftable.checkCascadeDelete(n.getData(), session, delete);
1310 }
1311
1312 if (delete) {
1313 reftable.deleteNoRefCheck(n.getData(), session);
1314
1315 // foreign key referencing own table
1316 if (reftable == this) {
1317 nextn = c.findFkRef(row);
1318 }
1319 }
1320
1321 if (nextn == null) {
1322 break;
1323 }
1324
1325 n = nextn;
1326 }
1327 }
1328 }
1329
1330 /**
1331 * Method declaration
1332 *
1333 * @param row
1334 * @param session Description of the Parameter
1335 * @throws SQLException
1336 */
1337 void delete(Object row[], Session session) throws SQLException {
1338
1339 fireAll(TriggerDef.DELETE_BEFORE_ROW, row);
1340
1341 if (dDatabase.isReferentialIntegrity()) {
1342 checkCascadeDelete(row, session, false);
1343 checkCascadeDelete(row, session, true);
1344 }
1345
1346 deleteNoCheck(row, session, true);
1347
1348 // fire the delete after statement trigger
1349 fireAll(TriggerDef.DELETE_AFTER_ROW, row);
1350 }
1351
1352 /**
1353 * Method declaration
1354 *
1355 * @param row
1356 * @param session Description of the Parameter
1357 * @throws SQLException
1358 */
1359 private void deleteNoRefCheck(Object row[],
1360 Session session) throws SQLException {
1361
1362 fireAll(TriggerDef.DELETE_BEFORE_ROW, row);
1363 deleteNoCheck(row, session, true);
1364
1365 // fire the delete after statement trigger
1366 fireAll(TriggerDef.DELETE_AFTER_ROW, row);
1367 }
1368
1369 /**
1370 * Method declaration
1371 *
1372 * @param row
1373 * @param c
1374 * @param log
1375 * @throws SQLException
1376 */
1377 void deleteNoCheck(Object row[], Session c,
1378 boolean log) throws SQLException {
1379
1380 for (int i = 1; i < iIndexCount; i++) {
1381 getIndex(i).delete(row, false);
1382 }
1383
1384 // must delete data last
1385 getIndex(0).delete(row, true);
1386
1387 if (c != null) {
1388 c.addTransactionDelete(this, row);
1389 }
1390
1391 if (log &&!isTemp &&!isReadOnly && dDatabase.logger.hasLog()) {
1392 dDatabase.logger.writeToLog(c, getDeleteStatement(row));
1393 }
1394 }
1395
1396 /**
1397 * Method declaration
1398 *
1399 * @param row
1400 * @return
1401 * @throws SQLException
1402 */
1403 String getInsertStatement(Object row[]) throws SQLException {
1404
1405 StringBuffer a = new StringBuffer(128);
1406
1407 a.append("INSERT INTO ");
1408 a.append(tableName.statementName);
1409 a.append(" VALUES(");
1410
1411 for (int i = 0; i < iVisibleColumns; i++) {
1412 a.append(Column.createSQLString(row[i], getColumn(i).getType()));
1413 a.append(',');
1414 }
1415
1416 a.setCharAt(a.length() - 1, ')');
1417
1418 return a.toString();
1419 }
1420
1421 /**
1422 * Method declaration
1423 *
1424 * @return
1425 */
1426 boolean isCached() {
1427 return isCached;
1428 }
1429
1430 /**
1431 * Method declaration
1432 *
1433 * @return
1434 */
1435 boolean isIndexCached() {
1436 return isCached;
1437 }
1438
1439 /**
1440 * Method declaration
1441 *
1442 * @param s
1443 * @return
1444 */
1445 Index getIndex(String s) {
1446
1447 for (int i = 0; i < iIndexCount; i++) {
1448 Index h = getIndex(i);
1449
1450 if (s.equals(h.getName().name)) {
1451 return h;
1452 }
1453 }
1454
1455 // no such index
1456 return null;
1457 }
1458
1459 /**
1460 * Return the position of the constraint within the list
1461 *
1462 * @param s
1463 * @return
1464 */
1465 int getConstraintIndex(String s) {
1466
1467 for (int j = 0; j < vConstraint.size(); j++) {
1468 Constraint tempc = (Constraint) vConstraint.elementAt(j);
1469
1470 if (tempc.getName().name.equals(s)) {
1471 return j;
1472 }
1473 }
1474
1475 return -1;
1476 }
1477
1478 /**
1479 * return the named constriant
1480 *
1481 * @param s
1482 * @return
1483 */
1484 Constraint getConstraint(String s) {
1485
1486 int j = getConstraintIndex(s);
1487
1488 if (j >= 0) {
1489 return (Constraint) vConstraint.elementAt(j);
1490 } else {
1491 return null;
1492 }
1493 }
1494
1495 /**
1496 * Method declaration
1497 *
1498 * @param i
1499 * @return
1500 */
1501 Column getColumn(int i) {
1502 return (Column) vColumn.elementAt(i);
1503 }
1504
1505 /**
1506 * Method declaration
1507 *
1508 * @return
1509 */
1510 int[] getColumnTypes() {
1511 return colTypes;
1512 }
1513
1514 /**
1515 * Method declaration
1516 *
1517 * @param i
1518 * @return
1519 */
1520 protected Index getIndex(int i) {
1521 return (Index) vIndex.elementAt(i);
1522 }
1523
1524 /**
1525 * Method declaration
1526 *
1527 * @param row
1528 * @return
1529 * @throws SQLException
1530 */
1531 private String getDeleteStatement(Object row[]) throws SQLException {
1532
1533 StringBuffer a = new StringBuffer(128);
1534
1535 a.append("DELETE FROM ");
1536 a.append(tableName.statementName);
1537 a.append(" WHERE ");
1538
1539 if (iVisibleColumns < iColumnCount) {
1540 for (int i = 0; i < iVisibleColumns; i++) {
1541 Column c = getColumn(i);
1542
1543 a.append(c.columnName.statementName);
1544 a.append('=');
1545 a.append(Column.createSQLString(row[i], c.getType()));
1546
1547 if (i < iVisibleColumns - 1) {
1548 a.append(" AND ");
1549 }
1550 }
1551 } else {
1552 for (int i = 0; i < iPrimaryKey.length; i++) {
1553 Column c = getColumn(iPrimaryKey[i]);
1554
1555 a.append(c.columnName.statementName);
1556 a.append('=');
1557 a.append(Column.createSQLString(row[iPrimaryKey[i]],
1558 c.getType()));
1559
1560 if (i < iPrimaryKey.length - 1) {
1561 a.append(" AND ");
1562 }
1563 }
1564 }
1565
1566 return a.toString();
1567 }
1568
1569 /**
1570 * Method declaration
1571 *
1572 * @param pos
1573 * @return
1574 * @throws SQLException
1575 */
1576 Row getRow(int pos) throws SQLException {
1577
1578 if (isCached) {
1579 return (cCache.getRow(pos, this));
1580 }
1581
1582 return null;
1583 }
1584
1585 void putRow(CachedRow r) throws SQLException {
1586
1587 int size = 0;
1588
1589 if (cCache != null) {
1590 cCache.add(r);
1591 }
1592 }
1593
1594 void removeRow(CachedRow r) throws SQLException {
1595
1596 if (cCache != null) {
1597 cCache.free(r);
1598 }
1599 }
1600
1601 void cleanUp() throws SQLException {
1602
1603 if (cCache != null) {
1604 cCache.cleanUp();
1605 }
1606 }
1607
1608 void indexRow(Row r, boolean inserted) throws SQLException {
1609
1610 if (inserted) {
1611 int i = 0;
1612
1613 try {