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