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.RangeVariable.RangeIteratorBase; 36 import org.hsqldb.error.Error; 37 import org.hsqldb.error.ErrorCode; 38 import org.hsqldb.index.Index; 39 import org.hsqldb.lib.ArrayUtil; 40 import org.hsqldb.lib.OrderedHashSet; 41 import org.hsqldb.navigator.RowIterator; 42 import org.hsqldb.persist.PersistentStore; 43 import org.hsqldb.result.Result; 44 import org.hsqldb.rights.Grantee; 45 import org.hsqldb.types.Type; 46 47 /** 48 * Implementation of a table constraint with references to the indexes used 49 * by the constraint.<p> 50 * 51 * @author Fred Toussi (fredt@users dot sourceforge.net) 52 * @version 2.3.4 53 * @since 1.6.0 54 */ 55 public final class Constraint implements SchemaObject { 56 57 ConstraintCore core; 58 private HsqlName name; 59 int constType; 60 boolean isForward; 61 62 // 63 Expression check; 64 private boolean isNotNull; 65 int notNullColumnIndex; 66 RangeVariable rangeVariable; 67 68 // for temp constraints only 69 OrderedHashSet mainColSet; 70 OrderedHashSet refColSet; 71 boolean isSimpleIdentityPK; 72 73 // 74 public static final Constraint[] emptyArray = new Constraint[]{}; 75 Constraint()76 private Constraint() {} 77 78 /** 79 * Constructor declaration for PK and UNIQUE 80 */ Constraint(HsqlName name, Table t, Index index, int type)81 public Constraint(HsqlName name, Table t, Index index, int type) { 82 83 this.name = name; 84 constType = type; 85 core = new ConstraintCore(); 86 core.mainTable = t; 87 core.mainIndex = index; 88 core.mainCols = index.getColumns(); 89 90 for (int i = 0; i < core.mainCols.length; i++) { 91 Type dataType = t.getColumn(core.mainCols[i]).getDataType(); 92 93 if (dataType.isLobType()) { 94 throw Error.error(ErrorCode.X_42534); 95 } 96 } 97 } 98 Constraint(HsqlName name, Table table, int[] cols, int type)99 public Constraint(HsqlName name, Table table, int[] cols, int type) { 100 101 this.name = name; 102 constType = type; 103 core = new ConstraintCore(); 104 core.mainTable = table; 105 core.mainCols = cols; 106 } 107 108 /** 109 * Constructor for main constraints (foreign key references in PK table) 110 */ Constraint(HsqlName name, Constraint fkconstraint)111 public Constraint(HsqlName name, Constraint fkconstraint) { 112 113 this.name = name; 114 constType = SchemaObject.ConstraintTypes.MAIN; 115 core = fkconstraint.core; 116 } 117 118 /** 119 * General constructor for foreign key constraints. 120 * 121 * @param name name of constraint 122 * @param refCols list of referencing columns 123 * @param mainTableName referenced table 124 * @param mainCols list of referenced columns 125 * @param type constraint type 126 * @param deleteAction triggered action on delete 127 * @param updateAction triggered action on update 128 * 129 */ Constraint(HsqlName name, HsqlName refTableName, OrderedHashSet refCols, HsqlName mainTableName, OrderedHashSet mainCols, int type, int deleteAction, int updateAction, int matchType)130 public Constraint(HsqlName name, HsqlName refTableName, 131 OrderedHashSet refCols, HsqlName mainTableName, 132 OrderedHashSet mainCols, int type, int deleteAction, 133 int updateAction, int matchType) { 134 135 this.name = name; 136 constType = type; 137 mainColSet = mainCols; 138 refColSet = refCols; 139 core = new ConstraintCore(); 140 core.refTableName = refTableName; 141 core.mainTableName = mainTableName; 142 core.deleteAction = deleteAction; 143 core.updateAction = updateAction; 144 core.matchType = matchType; 145 146 switch (core.deleteAction) { 147 148 case SchemaObject.ReferentialAction.CASCADE : 149 case SchemaObject.ReferentialAction.SET_DEFAULT : 150 case SchemaObject.ReferentialAction.SET_NULL : 151 core.hasDeleteAction = true; 152 break; 153 154 default : 155 } 156 157 switch (core.updateAction) { 158 159 case SchemaObject.ReferentialAction.CASCADE : 160 case SchemaObject.ReferentialAction.SET_DEFAULT : 161 case SchemaObject.ReferentialAction.SET_NULL : 162 core.hasUpdateAction = true; 163 break; 164 165 default : 166 } 167 } 168 Constraint(HsqlName name, OrderedHashSet mainCols, int type)169 public Constraint(HsqlName name, OrderedHashSet mainCols, int type) { 170 171 this.name = name; 172 constType = type; 173 mainColSet = mainCols; 174 core = new ConstraintCore(); 175 } 176 Constraint(HsqlName uniqueName, HsqlName mainName, HsqlName refName, Table mainTable, Table refTable, int[] mainCols, int[] refCols, Index mainIndex, Index refIndex, int deleteAction, int updateAction)177 public Constraint(HsqlName uniqueName, HsqlName mainName, 178 HsqlName refName, Table mainTable, Table refTable, 179 int[] mainCols, int[] refCols, Index mainIndex, 180 Index refIndex, int deleteAction, int updateAction) { 181 182 this.name = refName; 183 constType = SchemaObject.ConstraintTypes.FOREIGN_KEY; 184 core = new ConstraintCore(); 185 core.uniqueName = uniqueName; 186 core.mainName = mainName; 187 core.refName = refName; 188 core.mainTable = mainTable; 189 core.refTable = refTable; 190 core.mainCols = mainCols; 191 core.refCols = refCols; 192 core.mainIndex = mainIndex; 193 core.refIndex = refIndex; 194 core.deleteAction = deleteAction; 195 core.updateAction = updateAction; 196 } 197 duplicate()198 Constraint duplicate() { 199 200 Constraint copy = new Constraint(); 201 202 copy.core = core.duplicate(); 203 copy.name = name; 204 copy.constType = constType; 205 copy.isForward = isForward; 206 207 // 208 copy.check = check; 209 copy.isNotNull = isNotNull; 210 copy.notNullColumnIndex = notNullColumnIndex; 211 copy.rangeVariable = rangeVariable; 212 213 return copy; 214 } 215 setSimpleIdentityPK()216 void setSimpleIdentityPK() { 217 isSimpleIdentityPK = true; 218 } 219 setColumnsIndexes(Table table)220 void setColumnsIndexes(Table table) { 221 222 if (constType == SchemaObject.ConstraintTypes.FOREIGN_KEY) { 223 if (mainColSet == null) { 224 core.mainCols = core.mainTable.getPrimaryKey(); 225 226 if (core.mainCols == null) { 227 throw Error.error(ErrorCode.X_42581); 228 } 229 } else if (core.mainCols == null) { 230 core.mainCols = core.mainTable.getColumnIndexes(mainColSet); 231 } 232 233 if (core.refCols == null) { 234 core.refCols = table.getColumnIndexes(refColSet); 235 } 236 237 for (int i = 0; i < core.refCols.length; i++) { 238 Type dataType = table.getColumn(core.refCols[i]).getDataType(); 239 240 if (dataType.isLobType()) { 241 throw Error.error(ErrorCode.X_42534); 242 } 243 } 244 245 if (core.mainCols.length != core.refCols.length) { 246 throw Error.error(ErrorCode.X_42593); 247 } 248 } else if (mainColSet != null) { 249 core.mainCols = table.getColumnIndexes(mainColSet); 250 251 for (int i = 0; i < core.mainCols.length; i++) { 252 Type dataType = 253 table.getColumn(core.mainCols[i]).getDataType(); 254 255 if (dataType.isLobType()) { 256 throw Error.error(ErrorCode.X_42534); 257 } 258 } 259 } 260 } 261 getType()262 public int getType() { 263 return SchemaObject.CONSTRAINT; 264 } 265 266 /** 267 * Returns the HsqlName. 268 */ getName()269 public HsqlName getName() { 270 return name; 271 } 272 getCatalogName()273 public HsqlName getCatalogName() { 274 return name.schema.schema; 275 } 276 getSchemaName()277 public HsqlName getSchemaName() { 278 return name.schema; 279 } 280 getOwner()281 public Grantee getOwner() { 282 return name.schema.owner; 283 } 284 getReferences()285 public OrderedHashSet getReferences() { 286 287 switch (constType) { 288 289 case SchemaObject.ConstraintTypes.CHECK : 290 OrderedHashSet refs = new OrderedHashSet(); 291 292 check.collectObjectNames(refs); 293 294 for (int j = refs.size() - 1; j >= 0; j--) { 295 HsqlName name = (HsqlName) refs.get(j); 296 297 if (name.type == SchemaObject.COLUMN 298 || name.type == SchemaObject.TABLE) { 299 refs.remove(j); 300 } 301 } 302 303 return refs; 304 305 case SchemaObject.ConstraintTypes.FOREIGN_KEY : 306 OrderedHashSet set = new OrderedHashSet(); 307 308 set.add(core.uniqueName); 309 310 return set; 311 } 312 313 return new OrderedHashSet(); 314 } 315 getComponents()316 public OrderedHashSet getComponents() { 317 return null; 318 } 319 compile(Session session, SchemaObject parentObject)320 public void compile(Session session, SchemaObject parentObject) {} 321 getSQL()322 public String getSQL() { 323 324 StringBuffer sb = new StringBuffer(); 325 326 switch (getConstraintType()) { 327 328 case SchemaObject.ConstraintTypes.PRIMARY_KEY : 329 if (getMainColumns().length > 1 330 || (getMainColumns().length == 1 331 && !getName().isReservedName())) { 332 if (!getName().isReservedName()) { 333 sb.append(Tokens.T_CONSTRAINT).append(' '); 334 sb.append(getName().statementName).append(' '); 335 } 336 337 sb.append(Tokens.T_PRIMARY).append(' ').append( 338 Tokens.T_KEY); 339 sb.append( 340 getMain().getColumnListSQL( 341 getMainColumns(), getMainColumns().length)); 342 } 343 break; 344 345 case SchemaObject.ConstraintTypes.UNIQUE : 346 if (!getName().isReservedName()) { 347 sb.append(Tokens.T_CONSTRAINT).append(' '); 348 sb.append(getName().statementName); 349 sb.append(' '); 350 } 351 352 sb.append(Tokens.T_UNIQUE); 353 354 int[] col = getMainColumns(); 355 356 sb.append(getMain().getColumnListSQL(col, col.length)); 357 break; 358 359 case SchemaObject.ConstraintTypes.FOREIGN_KEY : 360 if (isForward) { 361 sb.append(Tokens.T_ALTER).append(' ').append( 362 Tokens.T_TABLE).append(' '); 363 sb.append( 364 getRef().getName().getSchemaQualifiedStatementName()); 365 sb.append(' ').append(Tokens.T_ADD).append(' '); 366 getFKStatement(sb); 367 } else { 368 getFKStatement(sb); 369 } 370 break; 371 372 case SchemaObject.ConstraintTypes.CHECK : 373 if (isNotNull()) { 374 break; 375 } 376 377 if (!getName().isReservedName()) { 378 sb.append(Tokens.T_CONSTRAINT).append(' '); 379 sb.append(getName().statementName).append(' '); 380 } 381 382 sb.append(Tokens.T_CHECK).append('('); 383 sb.append(check.getSQL()); 384 sb.append(')'); 385 386 // should not throw as it is already tested OK 387 break; 388 389 default : 390 } 391 392 return sb.toString(); 393 } 394 getChangeTimestamp()395 public long getChangeTimestamp() { 396 return 0; 397 } 398 399 /** 400 * Generates the foreign key declaration for a given Constraint object. 401 */ getFKStatement(StringBuffer sb)402 private void getFKStatement(StringBuffer sb) { 403 404 if (!getName().isReservedName()) { 405 sb.append(Tokens.T_CONSTRAINT).append(' '); 406 sb.append(getName().statementName); 407 sb.append(' '); 408 } 409 410 sb.append(Tokens.T_FOREIGN).append(' ').append(Tokens.T_KEY); 411 412 int[] col = getRefColumns(); 413 414 sb.append(getRef().getColumnListSQL(col, col.length)); 415 sb.append(' ').append(Tokens.T_REFERENCES).append(' '); 416 sb.append(getMain().getName().getSchemaQualifiedStatementName()); 417 418 col = getMainColumns(); 419 420 sb.append(getMain().getColumnListSQL(col, col.length)); 421 422 if (getDeleteAction() != SchemaObject.ReferentialAction.NO_ACTION) { 423 sb.append(' ').append(Tokens.T_ON).append(' ').append( 424 Tokens.T_DELETE).append(' '); 425 sb.append(getDeleteActionString()); 426 } 427 428 if (getUpdateAction() != SchemaObject.ReferentialAction.NO_ACTION) { 429 sb.append(' ').append(Tokens.T_ON).append(' ').append( 430 Tokens.T_UPDATE).append(' '); 431 sb.append(getUpdateActionString()); 432 } 433 } 434 getMainTableName()435 public HsqlName getMainTableName() { 436 return core.mainTableName; 437 } 438 getMainName()439 public HsqlName getMainName() { 440 return core.mainName; 441 } 442 getRefName()443 public HsqlName getRefName() { 444 return core.refName; 445 } 446 getUniqueName()447 public HsqlName getUniqueName() { 448 return core.uniqueName; 449 } 450 451 /** 452 * Returns the type of constraint 453 */ getConstraintType()454 public int getConstraintType() { 455 return constType; 456 } 457 isUniqueOrPK()458 public boolean isUniqueOrPK() { 459 return constType == SchemaObject.ConstraintTypes.UNIQUE 460 || constType == SchemaObject.ConstraintTypes.PRIMARY_KEY; 461 } 462 463 /** 464 * Returns the main table 465 */ getMain()466 public Table getMain() { 467 return core.mainTable; 468 } 469 470 /** 471 * Returns the main index 472 */ getMainIndex()473 Index getMainIndex() { 474 return core.mainIndex; 475 } 476 477 /** 478 * Returns the reference table 479 */ getRef()480 public Table getRef() { 481 return core.refTable; 482 } 483 484 /** 485 * Returns the reference index 486 */ getRefIndex()487 Index getRefIndex() { 488 return core.refIndex; 489 } 490 491 /** 492 * Returns the foreign key action rule. 493 */ getActionString(int action)494 private static String getActionString(int action) { 495 496 switch (action) { 497 498 case SchemaObject.ReferentialAction.RESTRICT : 499 return Tokens.T_RESTRICT; 500 501 case SchemaObject.ReferentialAction.CASCADE : 502 return Tokens.T_CASCADE; 503 504 case SchemaObject.ReferentialAction.SET_DEFAULT : 505 return Tokens.T_SET + ' ' + Tokens.T_DEFAULT; 506 507 case SchemaObject.ReferentialAction.SET_NULL : 508 return Tokens.T_SET + ' ' + Tokens.T_NULL; 509 510 default : 511 return Tokens.T_NO + ' ' + Tokens.T_ACTION; 512 } 513 } 514 515 /** 516 * The ON DELETE triggered action of (foreign key) constraint 517 */ getDeleteAction()518 public int getDeleteAction() { 519 return core.deleteAction; 520 } 521 getDeleteActionString()522 public String getDeleteActionString() { 523 return getActionString(core.deleteAction); 524 } 525 526 /** 527 * The ON UPDATE triggered action of (foreign key) constraint 528 */ getUpdateAction()529 public int getUpdateAction() { 530 return core.updateAction; 531 } 532 getUpdateActionString()533 public String getUpdateActionString() { 534 return getActionString(core.updateAction); 535 } 536 hasTriggeredAction()537 public boolean hasTriggeredAction() { 538 539 if (constType == SchemaObject.ConstraintTypes.FOREIGN_KEY) { 540 return hasCoreTriggeredAction(); 541 } 542 543 return false; 544 } 545 hasCoreTriggeredAction()546 public boolean hasCoreTriggeredAction() { 547 548 switch (core.deleteAction) { 549 550 case SchemaObject.ReferentialAction.CASCADE : 551 case SchemaObject.ReferentialAction.SET_DEFAULT : 552 case SchemaObject.ReferentialAction.SET_NULL : 553 return true; 554 } 555 556 switch (core.updateAction) { 557 558 case SchemaObject.ReferentialAction.CASCADE : 559 case SchemaObject.ReferentialAction.SET_DEFAULT : 560 case SchemaObject.ReferentialAction.SET_NULL : 561 return true; 562 } 563 564 return false; 565 } 566 getDeferability()567 public int getDeferability() { 568 return SchemaObject.Deferable.NOT_DEFERRABLE; 569 } 570 571 /** 572 * Returns the main table column index array 573 */ getMainColumns()574 public int[] getMainColumns() { 575 return core.mainCols; 576 } 577 578 /** 579 * Returns the reference table column index array 580 */ getRefColumns()581 public int[] getRefColumns() { 582 return core.refCols; 583 } 584 585 /** 586 * Returns the SQL for the expression in CHECK clause 587 */ getCheckSQL()588 public String getCheckSQL() { 589 return check.getSQL(); 590 } 591 592 /** 593 * Returns true if the expression in CHECK is a simple IS NOT NULL 594 */ isNotNull()595 public boolean isNotNull() { 596 return isNotNull; 597 } 598 hasColumnOnly(int colIndex)599 boolean hasColumnOnly(int colIndex) { 600 601 switch (constType) { 602 603 case SchemaObject.ConstraintTypes.CHECK : 604 return rangeVariable.usedColumns[colIndex] && ArrayUtil 605 .countTrueElements(rangeVariable.usedColumns) == 1; 606 607 case SchemaObject.ConstraintTypes.PRIMARY_KEY : 608 case SchemaObject.ConstraintTypes.UNIQUE : 609 return core.mainCols.length == 1 610 && core.mainCols[0] == colIndex; 611 612 case SchemaObject.ConstraintTypes.MAIN : 613 return false; 614 615 case SchemaObject.ConstraintTypes.FOREIGN_KEY : 616 return core.refCols.length == 1 && core.refCols[0] == colIndex; 617 618 default : 619 throw Error.runtimeError(ErrorCode.U_S0500, "Constraint"); 620 } 621 } 622 hasColumnPlus(int colIndex)623 boolean hasColumnPlus(int colIndex) { 624 625 switch (constType) { 626 627 case SchemaObject.ConstraintTypes.CHECK : 628 return rangeVariable.usedColumns[colIndex] && ArrayUtil 629 .countTrueElements(rangeVariable.usedColumns) > 1; 630 631 case SchemaObject.ConstraintTypes.PRIMARY_KEY : 632 case SchemaObject.ConstraintTypes.UNIQUE : 633 return core.mainCols.length != 1 634 && ArrayUtil.find(core.mainCols, colIndex) != -1; 635 636 case SchemaObject.ConstraintTypes.MAIN : 637 return ArrayUtil.find(core.mainCols, colIndex) != -1; 638 639 case SchemaObject.ConstraintTypes.FOREIGN_KEY : 640 return core.refCols.length != 1 641 && ArrayUtil.find(core.refCols, colIndex) != -1; 642 643 default : 644 throw Error.runtimeError(ErrorCode.U_S0500, "Constraint"); 645 } 646 } 647 hasColumn(int colIndex)648 boolean hasColumn(int colIndex) { 649 650 switch (constType) { 651 652 case SchemaObject.ConstraintTypes.CHECK : 653 return rangeVariable.usedColumns[colIndex]; 654 655 case SchemaObject.ConstraintTypes.PRIMARY_KEY : 656 case SchemaObject.ConstraintTypes.UNIQUE : 657 case SchemaObject.ConstraintTypes.MAIN : 658 return ArrayUtil.find(core.mainCols, colIndex) != -1; 659 660 case SchemaObject.ConstraintTypes.FOREIGN_KEY : 661 return ArrayUtil.find(core.refCols, colIndex) != -1; 662 663 default : 664 throw Error.runtimeError(ErrorCode.U_S0500, "Constraint"); 665 } 666 } 667 668 /** 669 * Compares this with another constraint column set. This is used only for 670 * UNIQUE constraints. 671 */ isUniqueWithColumns(int[] cols)672 boolean isUniqueWithColumns(int[] cols) { 673 674 switch (constType) { 675 676 case SchemaObject.ConstraintTypes.PRIMARY_KEY : 677 case SchemaObject.ConstraintTypes.UNIQUE : 678 if (core.mainCols.length == cols.length) { 679 return ArrayUtil.haveEqualSets(core.mainCols, cols, 680 cols.length); 681 } 682 } 683 684 return false; 685 } 686 687 /** 688 * Compares this with another constraint column set. This implementation 689 * only checks FOREIGN KEY constraints. 690 */ isEquivalent(Table mainTable, int[] mainCols, Table refTable, int[] refCols)691 boolean isEquivalent(Table mainTable, int[] mainCols, Table refTable, 692 int[] refCols) { 693 694 switch (constType) { 695 696 case SchemaObject.ConstraintTypes.MAIN : 697 case SchemaObject.ConstraintTypes.FOREIGN_KEY : 698 if (mainTable != core.mainTable || refTable != core.refTable) { 699 return false; 700 } 701 702 if (core.mainCols.length == mainCols.length 703 && core.refCols.length == refCols.length) { 704 return ArrayUtil.areEqualSets(core.mainCols, mainCols) 705 && ArrayUtil.areEqualSets(core.refCols, refCols); 706 } 707 } 708 709 return false; 710 } 711 712 /** 713 * Used to update constrains to reflect structural changes in a table. Prior 714 * checks must ensure that this method does not throw. 715 * 716 * @param session Session 717 * @param oldTable reference to the old version of the table 718 * @param newTable reference to the new version of the table 719 * @param colIndex index at which table column is added or removed 720 * @param adjust -1, 0, +1 to indicate if column is added or removed 721 */ updateTable(Session session, Table oldTable, Table newTable, int colIndex, int adjust)722 void updateTable(Session session, Table oldTable, Table newTable, 723 int colIndex, int adjust) { 724 725 if (oldTable == core.mainTable) { 726 core.mainTable = newTable; 727 728 if (core.mainIndex != null) { 729 core.mainIndex = 730 core.mainTable.getIndex(core.mainIndex.getName().name); 731 core.mainCols = ArrayUtil.toAdjustedColumnArray(core.mainCols, 732 colIndex, adjust); 733 } 734 } 735 736 if (oldTable == core.refTable) { 737 core.refTable = newTable; 738 739 if (core.refIndex != null) { 740 core.refIndex = 741 core.refTable.getIndex(core.refIndex.getName().name); 742 core.refCols = ArrayUtil.toAdjustedColumnArray(core.refCols, 743 colIndex, adjust); 744 } 745 } 746 747 // CHECK 748 if (constType == SchemaObject.ConstraintTypes.CHECK) { 749 recompile(session, newTable); 750 } 751 } 752 753 /** 754 * Checks for foreign key or check constraint violation when 755 * inserting a row into the child table. 756 */ checkInsert(Session session, Table table, Object[] data, boolean isNew)757 void checkInsert(Session session, Table table, Object[] data, 758 boolean isNew) { 759 760 switch (constType) { 761 762 case SchemaObject.ConstraintTypes.CHECK : 763 if (!isNotNull) { 764 checkCheckConstraint(session, table, data); 765 } 766 767 return; 768 769 case SchemaObject.ConstraintTypes.FOREIGN_KEY : 770 PersistentStore store = core.mainTable.getRowStore(session); 771 772 if (ArrayUtil.hasNull(data, core.refCols)) { 773 if (core.matchType == OpTypes.MATCH_SIMPLE) { 774 return; 775 } 776 777 if (core.refCols.length == 1) { 778 return; 779 } 780 781 if (ArrayUtil.hasAllNull(data, core.refCols)) { 782 return; 783 } 784 785 // core.matchType == OpTypes.MATCH_FULL 786 } else if (core.mainIndex.existsParent(session, store, data, 787 core.refCols)) { 788 return; 789 } 790 791 throw getException(data); 792 } 793 } 794 795 /* 796 * Tests a row against this CHECK constraint. 797 */ checkCheckConstraint(Session session, Table table, Object[] data)798 void checkCheckConstraint(Session session, Table table, Object[] data) { 799 800 RangeIteratorBase it = 801 session.sessionContext.getCheckIterator(rangeVariable); 802 803 it.setCurrent(data); 804 805 boolean nomatch = Boolean.FALSE.equals(check.getValue(session)); 806 807 it.setCurrent(null); 808 809 if (nomatch) { 810 String[] info = new String[] { 811 name.name, table.getName().name 812 }; 813 814 throw Error.error(null, ErrorCode.X_23513, ErrorCode.CONSTRAINT, 815 info); 816 } 817 } 818 checkCheckConstraint(Session session, Table table, ColumnSchema column, Object data)819 void checkCheckConstraint(Session session, Table table, 820 ColumnSchema column, Object data) { 821 822 session.sessionData.currentValue = data; 823 824 boolean nomatch = Boolean.FALSE.equals(check.getValue(session)); 825 826 session.sessionData.currentValue = null; 827 828 if (nomatch) { 829 String[] info = new String[] { 830 name.statementName, 831 table == null ? "" 832 : table.getName().statementName, 833 column == null ? "" 834 : column.getName().statementName, 835 }; 836 837 throw Error.error(null, ErrorCode.X_23513, 838 ErrorCode.COLUMN_CONSTRAINT, info); 839 } 840 } 841 getException(Object[] data)842 public HsqlException getException(Object[] data) { 843 844 switch (this.constType) { 845 846 case SchemaObject.ConstraintTypes.CHECK : { 847 String[] info = new String[]{ name.statementName }; 848 849 return Error.error(null, ErrorCode.X_23513, 850 ErrorCode.CONSTRAINT, info); 851 } 852 case SchemaObject.ConstraintTypes.FOREIGN_KEY : { 853 StringBuffer sb = new StringBuffer(); 854 855 for (int i = 0; i < core.refCols.length; i++) { 856 Object o = data[core.refCols[i]]; 857 858 sb.append(core.refTable.getColumnTypes()[core.refCols[i]] 859 .convertToString(o)); 860 sb.append(','); 861 } 862 863 String[] info = new String[] { 864 name.statementName, core.refTable.getName().statementName, 865 sb.toString() 866 }; 867 868 return Error.error(null, ErrorCode.X_23503, 869 ErrorCode.CONSTRAINT, info); 870 } 871 case SchemaObject.ConstraintTypes.PRIMARY_KEY : 872 case SchemaObject.ConstraintTypes.UNIQUE : { 873 StringBuffer sb = new StringBuffer(); 874 875 for (int i = 0; i < core.mainCols.length; i++) { 876 Object o = data[core.mainCols[i]]; 877 878 sb.append(core.mainTable.colTypes[core.mainCols[i]] 879 .convertToString(o)); 880 sb.append(','); 881 } 882 883 return Error.error(null, ErrorCode.X_23505, 884 ErrorCode.CONSTRAINT, new String[] { 885 name.statementName, core.mainTable.getName().statementName, 886 sb.toString() 887 }); 888 } 889 default : 890 throw Error.runtimeError(ErrorCode.U_S0500, "Constraint"); 891 } 892 } 893 894 // fredt@users 20020225 - patch 1.7.0 - cascading deletes 895 896 /** 897 * New method to find any referencing row for a foreign key (finds row in 898 * child table). If ON DELETE CASCADE is specified for this constraint, then 899 * the method finds the first row among the rows of the table ordered by the 900 * index and doesn't throw. Without ON DELETE CASCADE, the method attempts 901 * to finds any row that exists. If no 902 * row is found, null is returned. (fredt@users) 903 * 904 * @param session Session 905 * @param row array of objects for a database row 906 * @return iterator 907 */ findFkRef(Session session, Object[] row)908 RowIterator findFkRef(Session session, Object[] row) { 909 910 if (row == null || ArrayUtil.hasNull(row, core.mainCols)) { 911 return core.refIndex.emptyIterator(); 912 } 913 914 PersistentStore store = core.refTable.getRowStore(session); 915 916 return core.refIndex.findFirstRow(session, store, row, core.mainCols); 917 } 918 919 /** 920 * Finds a row matching the values in UNIQUE columns. 921 */ findUniqueRows(Session session, Object[] row)922 RowIterator findUniqueRows(Session session, Object[] row) { 923 924 if (row == null || ArrayUtil.hasNull(row, core.mainCols)) { 925 return core.mainIndex.emptyIterator(); 926 } 927 928 PersistentStore store = core.mainTable.getRowStore(session); 929 930 return core.mainIndex.findFirstRow(session, store, row, core.mainCols); 931 } 932 933 /** 934 * Check used before creating a new foreign key cosntraint, this method 935 * checks all rows of a table to ensure they all have a corresponding 936 * row in the main table. 937 */ checkReferencedRows(Session session, Table table)938 void checkReferencedRows(Session session, Table table) { 939 940 RowIterator it = table.rowIterator(session); 941 942 while (true) { 943 Row row = it.getNextRow(); 944 945 if (row == null) { 946 break; 947 } 948 949 Object[] rowData = row.getData(); 950 951 checkInsert(session, table, rowData, false); 952 } 953 } 954 getCheckExpression()955 public Expression getCheckExpression() { 956 return check; 957 } 958 getCheckColumnExpressions()959 public OrderedHashSet getCheckColumnExpressions() { 960 961 OrderedHashSet set = new OrderedHashSet(); 962 963 check.collectAllExpressions(set, Expression.columnExpressionSet, 964 Expression.emptyExpressionSet); 965 966 return set; 967 } 968 recompile(Session session, Table newTable)969 void recompile(Session session, Table newTable) { 970 971 check = getNewCheckExpression(session); 972 973 // this workaround is here to stop LIKE optimisation (for proper scripting) 974 QuerySpecification checkSelect = Expression.getCheckSelect(session, 975 newTable, check); 976 977 rangeVariable = checkSelect.rangeVariables[0]; 978 979 rangeVariable.setForCheckConstraint(); 980 } 981 getNewCheckExpression(Session session)982 private Expression getNewCheckExpression(Session session) { 983 984 String ddl = check.getSQL(); 985 Scanner scanner = new Scanner(session, ddl); 986 ParserDQL parser = new ParserDQL(session, scanner, null); 987 988 parser.compileContext.setNextRangeVarIndex(0); 989 parser.read(); 990 991 parser.isCheckOrTriggerCondition = true; 992 993 Expression condition = parser.XreadBooleanValueExpression(); 994 995 return condition; 996 } 997 prepareCheckConstraint(Session session, Table table)998 void prepareCheckConstraint(Session session, Table table) { 999 1000 // to ensure no subselects etc. are in condition 1001 check.checkValidCheckConstraint(); 1002 1003 if (table == null) { 1004 check.resolveTypes(session, null); 1005 } else { 1006 QuerySpecification checkSelect = Expression.getCheckSelect(session, 1007 table, check); 1008 1009 rangeVariable = checkSelect.rangeVariables[0]; 1010 1011 // removes reference to the Index object in range variable 1012 rangeVariable.setForCheckConstraint(); 1013 } 1014 1015 if (check.getType() == OpTypes.NOT 1016 && check.getLeftNode().getType() == OpTypes.IS_NULL 1017 && check.getLeftNode().getLeftNode().getType() 1018 == OpTypes.COLUMN) { 1019 notNullColumnIndex = 1020 check.getLeftNode().getLeftNode().getColumnIndex(); 1021 isNotNull = true; 1022 } 1023 } 1024 checkCheckConstraint(Session session, Table table)1025 void checkCheckConstraint(Session session, Table table) { 1026 1027 if (table.getRowStore(session).elementCount() > 0) { 1028 Expression newCheck = getNewCheckExpression(session); 1029 QuerySpecification checkSelect = Expression.getCheckSelect(session, 1030 table, newCheck); 1031 Result r = checkSelect.getResult(session, 1); 1032 1033 if (r.getNavigator().getSize() != 0) { 1034 String[] info = new String[] { 1035 name.statementName, table.getName().statementName 1036 }; 1037 1038 throw Error.error(null, ErrorCode.X_23513, 1039 ErrorCode.CONSTRAINT, info); 1040 } 1041 } 1042 } 1043 } 1044