1 /* Copyright (c) 2001-2016, The HSQL Development Group
2  * All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are met:
6  *
7  * Redistributions of source code must retain the above copyright notice, this
8  * list of conditions and the following disclaimer.
9  *
10  * Redistributions in binary form must reproduce the above copyright notice,
11  * this list of conditions and the following disclaimer in the documentation
12  * and/or other materials provided with the distribution.
13  *
14  * Neither the name of the HSQL Development Group nor the names of its
15  * contributors may be used to endorse or promote products derived from this
16  * software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21  * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
22  * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
23  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (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  */
30 
31 
32 package org.hsqldb;
33 
34 import org.hsqldb.HsqlNameManager.HsqlName;
35 import org.hsqldb.error.Error;
36 import org.hsqldb.error.ErrorCode;
37 import org.hsqldb.index.Index;
38 import org.hsqldb.lib.ArrayUtil;
39 import org.hsqldb.map.ValuePool;
40 import org.hsqldb.navigator.RowIterator;
41 import org.hsqldb.persist.DataSpaceManager;
42 import org.hsqldb.persist.PersistentStore;
43 import org.hsqldb.types.Type;
44 
45 /**
46  * The  base of all HSQLDB table implementations.
47  *
48  * @author Fred Toussi (fredt@users dot sourceforge.net)
49  * @version 2.3.3
50  * @since 1.7.2
51  */
52 public class TableBase implements Cloneable {
53 
54     // types of table
55     public static final int INFO_SCHEMA_TABLE = 1;
56     public static final int SYSTEM_SUBQUERY   = 2;
57     public static final int TEMP_TABLE        = 3;
58     public static final int MEMORY_TABLE      = 4;
59     public static final int CACHED_TABLE      = 5;
60     public static final int TEMP_TEXT_TABLE   = 6;
61     public static final int TEXT_TABLE        = 7;
62     public static final int VIEW_TABLE        = 8;
63     public static final int RESULT_TABLE      = 9;
64     public static final int TRANSITION_TABLE  = 10;
65     public static final int FUNCTION_TABLE    = 11;
66     public static final int SYSTEM_TABLE      = 12;
67     public static final int CHANGE_SET_TABLE  = 13;
68 
69     //
70     public static final int SCOPE_ROUTINE     = 20;
71     public static final int SCOPE_STATEMENT   = 21;
72     public static final int SCOPE_TRANSACTION = 22;
73     public static final int SCOPE_SESSION     = 23;
74     public static final int SCOPE_FULL        = 24;
75 
76     //
77     public PersistentStore store;
78     public int             persistenceScope;
79     public long            persistenceId;
80     int                    tableSpace = DataSpaceManager.tableIdDefault;
81 
82     //
83     Index[]         indexList;                  // first index is the primary key index
84     public Database database;
85     int[]           bestRowIdentifierCols;      // column set for best index
86     boolean         bestRowIdentifierStrict;    // true if it has no nullable column
87     int[]           bestIndexForColumn;         // index of the 'best' index for each column
88     Index           bestIndex;                  // the best index overall - null if there is no user-defined index
89     Index         fullIndex;                    // index on all columns
90     boolean[]     colNotNull;                   // nullability
91     Type[]        colTypes;                     // types of columns
92     protected int columnCount;
93 
94     //
95     int               tableType;
96     protected boolean isReadOnly;
97     protected boolean isTemp;
98     protected boolean isCached;
99     protected boolean isText;
100     boolean           isView;
101     protected boolean isWithDataSource;
102     public boolean    isSessionBased;
103     protected boolean isSchemaBased;
104     protected boolean isLogged;
105     private boolean   isTransactional = true;
106     boolean           hasLobColumn;
107 
108     //
TableBase()109     TableBase() {}
110 
111     //
TableBase(Session session, Database database, int scope, int type, Type[] colTypes)112     public TableBase(Session session, Database database, int scope, int type,
113                      Type[] colTypes) {
114 
115         tableType        = type;
116         persistenceScope = scope;
117         isSessionBased   = true;
118         persistenceId    = database.persistentStoreCollection.getNextId();
119         this.database    = database;
120         this.colTypes    = colTypes;
121         columnCount      = colTypes.length;
122         indexList        = Index.emptyArray;
123 
124         createPrimaryIndex(ValuePool.emptyIntArray, Type.emptyArray, null);
125     }
126 
duplicate()127     public TableBase duplicate() {
128 
129         TableBase copy;
130 
131 
132         try {
133             copy = (TableBase) super.clone();
134         } catch (CloneNotSupportedException ex) {
135             throw Error.runtimeError(ErrorCode.U_S0500, "Expression");
136         }
137 
138         copy.persistenceId    = database.persistentStoreCollection.getNextId();
139 
140         return copy;
141     }
142 
getTableType()143     public final int getTableType() {
144         return tableType;
145     }
146 
getPersistenceId()147     public long getPersistenceId() {
148         return persistenceId;
149     }
150 
getSpaceID()151     public int getSpaceID() {
152         return tableSpace;
153     }
154 
setSpaceID(int id)155     public void setSpaceID(int id) {
156         tableSpace = id;
157     }
158 
getId()159     int getId() {
160         return 0;
161     }
162 
onCommitPreserve()163     public final boolean onCommitPreserve() {
164         return persistenceScope == TableBase.SCOPE_SESSION;
165     }
166 
rowIterator(Session session)167     public final RowIterator rowIterator(Session session) {
168 
169         PersistentStore store = getRowStore(session);
170 
171         return getPrimaryIndex().firstRow(session, store, 0, null);
172     }
173 
rowIterator(PersistentStore store)174     public final RowIterator rowIterator(PersistentStore store) {
175         return getPrimaryIndex().firstRow(store);
176     }
177 
getIndexCount()178     public final int getIndexCount() {
179         return indexList.length;
180     }
181 
getPrimaryIndex()182     public final Index getPrimaryIndex() {
183         return indexList.length > 0 ? indexList[0]
184                                     : null;
185     }
186 
getPrimaryKeyTypes()187     public final Type[] getPrimaryKeyTypes() {
188         return indexList[0].getColumnTypes();
189     }
190 
hasPrimaryKey()191     public final boolean hasPrimaryKey() {
192         return indexList[0].getColumnCount() > 0;
193     }
194 
getPrimaryKey()195     public final int[] getPrimaryKey() {
196         return indexList[0].getColumns();
197     }
198 
199     /**
200      *  Returns an array of Type indicating the SQL type of the columns
201      */
getColumnTypes()202     public final Type[] getColumnTypes() {
203         return colTypes;
204     }
205 
206     /**
207      *  Returns the Index object at the given index
208      */
getIndex(int i)209     public final Index getIndex(int i) {
210         return indexList[i];
211     }
212 
213     /**
214      *  Returns the indexes
215      */
getIndexList()216     public final Index[] getIndexList() {
217         return indexList;
218     }
219 
220     /**
221      * Returns empty boolean array.
222      */
getNewColumnCheckList()223     public final boolean[] getNewColumnCheckList() {
224         return new boolean[getColumnCount()];
225     }
226 
227     /**
228      *  Returns the count of all visible columns.
229      */
getColumnCount()230     public int getColumnCount() {
231         return columnCount;
232     }
233 
234     /**
235      *  Returns the count of all columns.
236      */
getDataColumnCount()237     public final int getDataColumnCount() {
238         return colTypes.length;
239     }
240 
isTransactional()241     public boolean isTransactional() {
242         return isTransactional;
243     }
244 
setTransactional(boolean value)245     public void setTransactional(boolean value) {
246         isTransactional = value;
247     }
248 
249     /**
250      * This method is called whenever there is a change to table structure and
251      * serves two purposes: (a) to reset the best set of columns that identify
252      * the rows of the table (b) to reset the best index that can be used
253      * to find rows of the table given a column value.
254      *
255      * (a) gives most weight to a primary key index, followed by a unique
256      * address with the lowest count of nullable columns. Otherwise there is
257      * no best row identifier.
258      *
259      * (b) finds for each column an index with a corresponding first column.
260      * It uses any type of visible index and accepts the one with the largest
261      * column count.
262      *
263      * bestIndex is the user defined, primary key, the first unique index, or
264      * the first non-unique index. NULL if there is no user-defined index.
265      *
266      */
setBestRowIdentifiers()267     public final void setBestRowIdentifiers() {
268 
269         int[]   briCols      = null;
270         int     briColsCount = 0;
271         boolean isStrict     = false;
272         int     nNullCount   = 0;
273 
274         // ignore if called prior to completion of primary key construction
275         if (colNotNull == null) {
276             return;
277         }
278 
279         bestIndex          = null;
280         bestIndexForColumn = new int[colTypes.length];
281 
282         ArrayUtil.fillArray(bestIndexForColumn, -1);
283 
284         for (int i = 0; i < indexList.length; i++) {
285             Index index     = indexList[i];
286             int[] cols      = index.getColumns();
287             int   colsCount = index.getColumnCount();
288 
289             if (colsCount == 0) {
290                 continue;
291             }
292 
293             if (i == 0) {
294                 isStrict = true;
295             }
296 
297             if (bestIndexForColumn[cols[0]] == -1) {
298                 bestIndexForColumn[cols[0]] = i;
299             } else {
300                 Index existing = indexList[bestIndexForColumn[cols[0]]];
301 
302                 if (colsCount > existing.getColumns().length) {
303                     bestIndexForColumn[cols[0]] = i;
304                 }
305             }
306 
307             if (!index.isUnique()) {
308                 if (bestIndex == null) {
309                     bestIndex = index;
310                 }
311 
312                 continue;
313             }
314 
315             int nnullc = 0;
316 
317             for (int j = 0; j < colsCount; j++) {
318                 if (colNotNull[cols[j]]) {
319                     nnullc++;
320                 }
321             }
322 
323             if (bestIndex != null) {
324                 bestIndex = index;
325             }
326 
327             if (nnullc == colsCount) {
328                 if (briCols == null || briColsCount != nNullCount
329                         || colsCount < briColsCount) {
330 
331                     //  nothing found before ||
332                     //  found but has null columns ||
333                     //  found but has more columns than this index
334                     briCols      = cols;
335                     briColsCount = colsCount;
336                     nNullCount   = colsCount;
337                     isStrict     = true;
338                 }
339 
340                 continue;
341             } else if (isStrict) {
342                 continue;
343             } else if (briCols == null || colsCount < briColsCount
344                        || nnullc > nNullCount) {
345 
346                 //  nothing found before ||
347                 //  found but has more columns than this index||
348                 //  found but has fewer not null columns than this index
349                 briCols      = cols;
350                 briColsCount = colsCount;
351                 nNullCount   = nnullc;
352             }
353         }
354 
355         if (briCols == null || briColsCount == briCols.length) {
356             bestRowIdentifierCols = briCols;
357         } else {
358             bestRowIdentifierCols = ArrayUtil.arraySlice(briCols, 0,
359                     briColsCount);
360         }
361 
362         bestRowIdentifierStrict = isStrict;
363 
364         if (indexList[0].getColumnCount() > 0) {
365             bestIndex = indexList[0];
366         }
367     }
368 
getColumnNotNull()369     public boolean[] getColumnNotNull() {
370         return this.colNotNull;
371     }
372 
createPrimaryIndex(int[] pkcols, Type[] pktypes, HsqlName name)373     public final void createPrimaryIndex(int[] pkcols, Type[] pktypes,
374                                          HsqlName name) {
375 
376         Index newIndex = getNewPrimaryIndex(pkcols, pktypes, name);
377 
378         addIndexStructure(newIndex);
379     }
380 
getNewPrimaryIndex(int[] pkcols, Type[] pktypes, HsqlName name)381     Index getNewPrimaryIndex(int[] pkcols, Type[] pktypes, HsqlName name) {
382 
383         long id = database.persistentStoreCollection.getNextId();
384 
385         return database.logger.newIndex(name, id, this, pkcols, null, null,
386                                         pktypes, true, pkcols.length > 0,
387                                         pkcols.length > 0, false);
388     }
389 
createAndAddIndexStructure(Session session, HsqlName name, int[] columns, boolean[] descending, boolean[] nullsLast, boolean unique, boolean constraint, boolean forward)390     public final Index createAndAddIndexStructure(Session session,
391             HsqlName name, int[] columns, boolean[] descending,
392             boolean[] nullsLast, boolean unique, boolean constraint,
393             boolean forward) {
394 
395         Index newindex = createIndexStructure(name, columns, descending,
396                                               nullsLast, unique, constraint,
397                                               forward);
398 
399         addIndex(session, newindex);
400 
401         return newindex;
402     }
403 
createIndexStructure(HsqlName name, int[] columns, boolean[] descending, boolean[] nullsLast, boolean unique, boolean constraint, boolean forward)404     final Index createIndexStructure(HsqlName name, int[] columns,
405                                      boolean[] descending,
406                                      boolean[] nullsLast, boolean unique,
407                                      boolean constraint, boolean forward) {
408 
409         int    s     = columns.length;
410         int[]  cols  = new int[s];
411         Type[] types = new Type[s];
412 
413         for (int j = 0; j < s; j++) {
414             cols[j]  = columns[j];
415             types[j] = colTypes[cols[j]];
416         }
417 
418         long id = database.persistentStoreCollection.getNextId();
419         Index newIndex = database.logger.newIndex(name, id, this, cols,
420             descending, nullsLast, types, false, unique, constraint, forward);
421 
422         return newIndex;
423     }
424 
425     /**
426      *  Performs Table structure modification and changes to the index nodes
427      *  to remove a given index from a MEMORY or TEXT table. Not for PK index.
428      *
429      */
dropIndex(Session session, int todrop)430     public void dropIndex(Session session, int todrop) {
431 
432         Index[] list = (Index[]) ArrayUtil.toAdjustedArray(indexList, null,
433             todrop, -1);
434 
435         for (int i = 0; i < list.length; i++) {
436             list[i].setPosition(i);
437         }
438 
439         resetAccessorKeys(session, list);
440 
441         indexList = list;
442 
443         setBestRowIdentifiers();
444     }
445 
addIndexStructure(Index index)446     final void addIndexStructure(Index index) {
447 
448         indexList = getNewIndexArray(index, indexList);
449 
450         setBestRowIdentifiers();
451     }
452 
getNewIndexArray(Index index, Index[] list)453     static Index[] getNewIndexArray(Index index, Index[] list) {
454 
455         int i = 0;
456 
457         for (; i < list.length; i++) {
458             Index current = list[i];
459             int order = index.getIndexOrderValue()
460                         - current.getIndexOrderValue();
461 
462             if (order < 0) {
463                 break;
464             }
465         }
466 
467         list = (Index[]) ArrayUtil.toAdjustedArray(list, index, i, 1);
468 
469         for (i = 0; i < list.length; i++) {
470             list[i].setPosition(i);
471         }
472 
473         return list;
474     }
475 
addIndex(Session session, Index index)476     final void addIndex(Session session, Index index) {
477 
478         Index[] list = getNewIndexArray(index, indexList);
479 
480         try {
481             resetAccessorKeys(session, list);
482         } catch (HsqlException e) {
483             for (int i = 0; i < indexList.length; i++) {
484                 indexList[i].setPosition(i);
485             }
486 
487             throw e;
488         }
489 
490         indexList = list;
491 
492         setBestRowIdentifiers();
493     }
494 
resetAccessorKeys(Session session, Index[] indexes)495     private void resetAccessorKeys(Session session, Index[] indexes) {
496 
497         if (store != null) {
498             store.resetAccessorKeys(session, indexes);
499 
500             return;
501         }
502 
503         switch (tableType) {
504 
505             case TableBase.INFO_SCHEMA_TABLE :
506             case TableBase.TEMP_TABLE : {
507 
508                 // session may be an unregistered sys session
509                 session.sessionData.persistentStoreCollection
510                     .resetAccessorKeys(session, (Table) this, indexes);
511 
512                 break;
513             }
514         }
515     }
516 
removeIndex(int position)517     final void removeIndex(int position) {
518         setBestRowIdentifiers();
519     }
520 
setIndexes(Index[] indexes)521     public final void setIndexes(Index[] indexes) {
522         this.indexList = indexes;
523     }
524 
getEmptyRowData()525     public final Object[] getEmptyRowData() {
526         return new Object[getDataColumnCount()];
527     }
528 
529     /**
530      *  Create new memory-resident index. For MEMORY and TEXT tables.
531      */
createIndex(Session session, HsqlName name, int[] columns, boolean[] descending, boolean[] nullsLast, boolean unique, boolean constraint, boolean forward)532     public final Index createIndex(Session session, HsqlName name,
533                                    int[] columns, boolean[] descending,
534                                    boolean[] nullsLast, boolean unique,
535                                    boolean constraint, boolean forward) {
536 
537         Index newIndex = createAndAddIndexStructure(session, name, columns,
538             descending, nullsLast, unique, constraint, forward);
539 
540         return newIndex;
541     }
542 
clearAllData(Session session)543     public void clearAllData(Session session) {
544 
545         PersistentStore store = getRowStore(session);
546 
547         store.removeAll();
548     }
549 
clearAllData(PersistentStore store)550     public void clearAllData(PersistentStore store) {
551         store.removeAll();
552     }
553 
554     /**
555      * @todo - this is not for general use, as it returns true when table has no
556      * rows, but not where it has rows that are not visible by session.
557      * current usage is fine.
558      */
559 
560     /**
561      *  Returns true if the table has any rows at all.
562      */
isEmpty(Session session)563     public final boolean isEmpty(Session session) {
564 
565         if (getIndexCount() == 0) {
566             return true;
567         }
568 
569         PersistentStore store = getRowStore(session);
570 
571         return getIndex(0).isEmpty(store);
572     }
573 
getRowStore(Session session)574     public PersistentStore getRowStore(Session session) {
575 
576         return store == null
577                ? session.sessionData.persistentStoreCollection.getStore(this)
578                : store;
579     }
580 }
581