Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

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 {