001/* 002 * (C) Copyright 2006-2011 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * Florent Guillaume 018 */ 019package org.nuxeo.ecm.core.storage.sql.jdbc; 020 021import java.io.IOException; 022import java.io.Serializable; 023import java.sql.Connection; 024import java.sql.ResultSet; 025import java.sql.SQLException; 026import java.util.ArrayList; 027import java.util.Arrays; 028import java.util.Collection; 029import java.util.Collections; 030import java.util.HashMap; 031import java.util.LinkedList; 032import java.util.List; 033import java.util.Map; 034import java.util.Map.Entry; 035import java.util.Set; 036 037import org.nuxeo.common.utils.StringUtils; 038import org.nuxeo.ecm.core.api.NuxeoException; 039import org.nuxeo.ecm.core.query.sql.NXQL; 040import org.nuxeo.ecm.core.storage.FulltextConfiguration; 041import org.nuxeo.ecm.core.storage.sql.ColumnType; 042import org.nuxeo.ecm.core.storage.sql.Mapper; 043import org.nuxeo.ecm.core.storage.sql.Model; 044import org.nuxeo.ecm.core.storage.sql.RepositoryDescriptor; 045import org.nuxeo.ecm.core.storage.sql.Selection; 046import org.nuxeo.ecm.core.storage.sql.SelectionType; 047import org.nuxeo.ecm.core.storage.sql.jdbc.db.Column; 048import org.nuxeo.ecm.core.storage.sql.jdbc.db.Database; 049import org.nuxeo.ecm.core.storage.sql.jdbc.db.Delete; 050import org.nuxeo.ecm.core.storage.sql.jdbc.db.Insert; 051import org.nuxeo.ecm.core.storage.sql.jdbc.db.Join; 052import org.nuxeo.ecm.core.storage.sql.jdbc.db.Select; 053import org.nuxeo.ecm.core.storage.sql.jdbc.db.Table; 054import org.nuxeo.ecm.core.storage.sql.jdbc.db.Update; 055import org.nuxeo.ecm.core.storage.sql.jdbc.db.Table.IndexType; 056import org.nuxeo.ecm.core.storage.sql.jdbc.dialect.Dialect; 057import org.nuxeo.ecm.core.storage.sql.jdbc.dialect.SQLStatement; 058import org.nuxeo.ecm.core.storage.sql.jdbc.dialect.SQLStatement.ListCollector; 059 060/** 061 * This singleton generates and holds the actual SQL DDL and DML statements for the operations needed by the 062 * {@link Mapper}, given a {@link Model}. 063 * <p> 064 * It is specific to one SQL dialect. 065 */ 066public class SQLInfo { 067 068 private static final String ORDER_DESC = "DESC"; 069 070 private static final String ORDER_ASC = "ASC"; 071 072 public final Database database; 073 074 public final Dialect dialect; 075 076 public final boolean softDeleteEnabled; 077 078 public final boolean proxiesEnabled; 079 080 private final Model model; 081 082 private String selectRootIdSql; 083 084 private Column selectRootIdWhatColumn; 085 086 private final Map<String, String> insertSqlMap; // statement 087 088 private final Map<String, List<Column>> insertColumnsMap; 089 090 private final Map<String, String> deleteSqlMap; // statement 091 092 private Map<SelectionType, SQLInfoSelection> selections; 093 094 private String selectChildrenIdsAndTypesSql; 095 096 private String selectComplexChildrenIdsAndTypesSql; 097 098 private List<Column> selectChildrenIdsAndTypesWhatColumns; 099 100 private String selectDescendantsInfoSql; 101 102 private List<Column> selectDescendantsInfoWhatColumns; 103 104 private final Map<String, String> copySqlMap; 105 106 private final Map<String, Column> copyIdColumnMap; 107 108 protected final Map<String, SQLInfoSelect> selectFragmentById; 109 110 protected String createClusterNodeSql; 111 112 protected List<Column> createClusterNodeColumns; 113 114 protected String deleteClusterNodeSql; 115 116 protected Column deleteClusterNodeColumn; 117 118 protected String deleteClusterInvalsSql; 119 120 protected Column deleteClusterInvalsColumn; 121 122 protected List<Column> clusterInvalidationsColumns; 123 124 protected Map<String, List<SQLStatement>> sqlStatements; 125 126 protected Map<String, Serializable> sqlStatementsProperties; 127 128 protected List<String> getBinariesSql; 129 130 protected List<Column> getBinariesColumns; 131 132 /** 133 * Generates and holds the needed SQL statements given a {@link Model} and a {@link Dialect}. 134 * 135 * @param model the model 136 * @param dialect the SQL dialect 137 */ 138 public SQLInfo(Model model, Dialect dialect) { 139 this.model = model; 140 this.dialect = dialect; 141 RepositoryDescriptor repositoryDescriptor = model.getRepositoryDescriptor(); 142 softDeleteEnabled = repositoryDescriptor.getSoftDeleteEnabled(); 143 proxiesEnabled = repositoryDescriptor.getProxiesEnabled(); 144 145 database = new Database(dialect); 146 147 selectRootIdSql = null; 148 selectRootIdWhatColumn = null; 149 150 selectFragmentById = new HashMap<String, SQLInfoSelect>(); 151 152 selections = new HashMap<SelectionType, SQLInfoSelection>(); 153 154 selectChildrenIdsAndTypesSql = null; 155 selectChildrenIdsAndTypesWhatColumns = null; 156 selectComplexChildrenIdsAndTypesSql = null; 157 158 insertSqlMap = new HashMap<String, String>(); 159 insertColumnsMap = new HashMap<String, List<Column>>(); 160 161 deleteSqlMap = new HashMap<String, String>(); 162 163 copySqlMap = new HashMap<String, String>(); 164 copyIdColumnMap = new HashMap<String, Column>(); 165 166 getBinariesSql = new ArrayList<String>(1); 167 getBinariesColumns = new ArrayList<Column>(1); 168 169 initSQL(); 170 initSelections(); 171 172 try { 173 initSQLStatements(JDBCMapper.testProps, repositoryDescriptor.sqlInitFiles); 174 } catch (IOException e) { 175 throw new NuxeoException(e); 176 } 177 } 178 179 public Database getDatabase() { 180 return database; 181 } 182 183 // ----- select ----- 184 185 public String getSelectRootIdSql() { 186 return selectRootIdSql; 187 } 188 189 public Column getSelectRootIdWhatColumn() { 190 return selectRootIdWhatColumn; 191 } 192 193 public String getInsertRootIdSql() { 194 return insertSqlMap.get(model.REPOINFO_TABLE_NAME); 195 } 196 197 public List<Column> getInsertRootIdColumns() { 198 return insertColumnsMap.get(model.REPOINFO_TABLE_NAME); 199 } 200 201 public SQLInfoSelection getSelection(SelectionType type) { 202 return selections.get(type); 203 204 } 205 206 public String getSelectChildrenIdsAndTypesSql(boolean onlyComplex) { 207 return onlyComplex ? selectComplexChildrenIdsAndTypesSql : selectChildrenIdsAndTypesSql; 208 } 209 210 public List<Column> getSelectChildrenIdsAndTypesWhatColumns() { 211 return selectChildrenIdsAndTypesWhatColumns; 212 } 213 214 public String getSelectDescendantsInfoSql() { 215 return selectDescendantsInfoSql; 216 } 217 218 public List<Column> getSelectDescendantsInfoWhatColumns() { 219 return selectDescendantsInfoWhatColumns; 220 } 221 222 // ----- cluster ----- 223 224 public String getCreateClusterNodeSql() { 225 return createClusterNodeSql; 226 } 227 228 public List<Column> getCreateClusterNodeColumns() { 229 return createClusterNodeColumns; 230 } 231 232 public String getDeleteClusterNodeSql() { 233 return deleteClusterNodeSql; 234 } 235 236 public Column getDeleteClusterNodeColumn() { 237 return deleteClusterNodeColumn; 238 } 239 240 public String getDeleteClusterInvalsSql() { 241 return deleteClusterInvalsSql; 242 } 243 244 public Column getDeleteClusterInvalsColumn() { 245 return deleteClusterInvalsColumn; 246 } 247 248 public int getClusterNodeIdType() { 249 return dialect.getJDBCTypeAndString(ColumnType.CLUSTERNODE).jdbcType; 250 } 251 252 public List<Column> getClusterInvalidationsColumns() { 253 return clusterInvalidationsColumns; 254 } 255 256 // ----- insert ----- 257 258 /** 259 * Returns the SQL {@code INSERT} to add a row. The columns that represent sequences that are implicitly 260 * auto-incremented aren't included. 261 * 262 * @param tableName the table name 263 * @return the SQL {@code INSERT} statement 264 */ 265 public String getInsertSql(String tableName) { 266 return insertSqlMap.get(tableName); 267 } 268 269 /** 270 * Returns the list of columns to use for an {@INSERT} statement {@link #getInsertSql}. 271 * 272 * @param tableName the table name 273 * @return the list of columns 274 */ 275 public List<Column> getInsertColumns(String tableName) { 276 return insertColumnsMap.get(tableName); 277 } 278 279 // ----- 280 281 /** 282 * Returns the clause used to match a given row by id in the given table. 283 * <p> 284 * Takes into account soft deletes. 285 * 286 * @param tableName the table name 287 * @return the clause, like {@code table.id = ?} 288 */ 289 public String getIdEqualsClause(String tableName) { 290 return database.getTable(tableName).getColumn(model.MAIN_KEY).getQuotedName() + " = ?" 291 + getSoftDeleteClause(tableName); 292 } 293 294 /** 295 * Returns {@code AND isdeleted IS NULL} if this is the hierarchy table and soft delete is activated. 296 * 297 * @param tableName the table name 298 * @return the clause 299 */ 300 public String getSoftDeleteClause(String tableName) { 301 if (model.HIER_TABLE_NAME.equals(tableName) && softDeleteEnabled) { 302 return " AND " + getSoftDeleteClause(); 303 } else { 304 return ""; 305 } 306 } 307 308 /** 309 * Returns null or {@code AND isdeleted IS NULL} if soft delete is activated. 310 * 311 * @return the clause, or null 312 */ 313 public String getSoftDeleteClause() { 314 if (softDeleteEnabled) { 315 return database.getTable(model.HIER_TABLE_NAME).getColumn(model.MAIN_IS_DELETED_KEY).getFullQuotedName() 316 + " IS NULL"; 317 } else { 318 return null; 319 } 320 } 321 322 // ----- update ----- 323 324 // TODO these two methods are redundant with one another 325 326 public SQLInfoSelect getUpdateById(String tableName, Collection<String> keys, Set<String> deltas) { 327 Table table = database.getTable(tableName); 328 List<Column> columns = new LinkedList<Column>(); 329 for (String key : keys) { 330 columns.add(table.getColumn(key)); 331 } 332 Update update = new Update(table); 333 update.setUpdatedColumns(columns, deltas); 334 update.setWhere(getIdEqualsClause(tableName)); 335 columns.add(table.getColumn(model.MAIN_KEY)); 336 return new SQLInfoSelect(update.getStatement(), columns, null, null); 337 } 338 339 public Update getUpdateByIdForKeys(String tableName, List<String> keys) { 340 Table table = database.getTable(tableName); 341 List<Column> columns = new LinkedList<Column>(); 342 for (String key : keys) { 343 columns.add(table.getColumn(key)); 344 } 345 Update update = new Update(table); 346 update.setUpdatedColumns(columns); 347 update.setWhere(getIdEqualsClause(tableName)); 348 return update; 349 } 350 351 /** 352 * Select by ids for all values of several fragments. 353 */ 354 public SQLInfoSelect getSelectFragmentsByIds(String tableName, int nids) { 355 return getSelectFragmentsByIds(tableName, nids, null, null); 356 } 357 358 /** 359 * Select by ids for all values of several fragments (maybe ordered along columns -- for collection fragments 360 * retrieval). 361 */ 362 public SQLInfoSelect getSelectFragmentsByIds(String tableName, int nids, String[] orderBys, Set<String> skipColumns) { 363 Table table = database.getTable(tableName); 364 List<Column> whatColumns = new LinkedList<Column>(); 365 List<String> whats = new LinkedList<String>(); 366 List<Column> opaqueColumns = new LinkedList<Column>(); 367 for (Column column : table.getColumns()) { 368 if (column.isOpaque()) { 369 opaqueColumns.add(column); 370 } else if (skipColumns == null || !skipColumns.contains(column.getKey())) { 371 whatColumns.add(column); 372 whats.add(column.getQuotedName()); 373 } 374 } 375 Column whereColumn = table.getColumn(model.MAIN_KEY); 376 StringBuilder wherebuf = new StringBuilder(whereColumn.getQuotedName()); 377 wherebuf.append(" IN ("); 378 for (int i = 0; i < nids; i++) { 379 if (i != 0) { 380 wherebuf.append(", "); 381 } 382 wherebuf.append('?'); 383 } 384 wherebuf.append(')'); 385 wherebuf.append(getSoftDeleteClause(tableName)); 386 Select select = new Select(table); 387 select.setWhat(StringUtils.join(whats, ", ")); 388 select.setFrom(table.getQuotedName()); 389 select.setWhere(wherebuf.toString()); 390 if (orderBys != null) { 391 List<String> orders = new LinkedList<String>(); 392 for (String orderBy : orderBys) { 393 orders.add(table.getColumn(orderBy).getQuotedName()); 394 } 395 select.setOrderBy(StringUtils.join(orders, ", ")); 396 } 397 return new SQLInfoSelect(select.getStatement(), whatColumns, Collections.singletonList(whereColumn), 398 opaqueColumns.isEmpty() ? null : opaqueColumns); 399 } 400 401 /** 402 * Select all ancestors ids for several fragments. 403 * <p> 404 * Fast alternative to the slowest iterative {@link #getSelectParentIds}. 405 * 406 * @return null if it's not possible in one call in this dialect 407 */ 408 public SQLInfoSelect getSelectAncestorsIds() { 409 String sql = dialect.getAncestorsIdsSql(); 410 if (sql == null) { 411 return null; 412 } 413 Table table = database.getTable(model.HIER_TABLE_NAME); 414 Column mainColumn = table.getColumn(model.MAIN_KEY); 415 // no soft-delete check needed, as ancestors of a non-deleted doc 416 // aren't deleted either 417 return new SQLInfoSelect(sql, Collections.singletonList(mainColumn), null, null); 418 } 419 420 /** 421 * Select parentid by ids for all values of several fragments. 422 */ 423 public SQLInfoSelect getSelectParentIds(int nids) { 424 Table table = database.getTable(model.HIER_TABLE_NAME); 425 Column whatColumn = table.getColumn(model.HIER_PARENT_KEY); 426 Column whereColumn = table.getColumn(model.MAIN_KEY); 427 StringBuilder wherebuf = new StringBuilder(whereColumn.getQuotedName()); 428 wherebuf.append(" IN ("); 429 for (int i = 0; i < nids; i++) { 430 if (i != 0) { 431 wherebuf.append(", "); 432 } 433 wherebuf.append('?'); 434 } 435 wherebuf.append(')'); 436 wherebuf.append(getSoftDeleteClause(model.HIER_TABLE_NAME)); 437 Select select = new Select(table); 438 select.setWhat("DISTINCT " + whatColumn.getQuotedName()); 439 select.setFrom(table.getQuotedName()); 440 select.setWhere(wherebuf.toString()); 441 return new SQLInfoSelect(select.getStatement(), Collections.singletonList(whatColumn), 442 Collections.singletonList(whereColumn), null); 443 } 444 445 /** 446 * Selects all children (not complex) for several parent ids. 447 */ 448 public SQLInfoSelect getSelectChildrenNodeInfos(int nids) { 449 Table hierTable = database.getTable(model.HIER_TABLE_NAME); 450 Column mainColumn = hierTable.getColumn(model.MAIN_KEY); 451 List<Column> whatColumns = new ArrayList<>(); 452 whatColumns.add(mainColumn); 453 whatColumns.add(hierTable.getColumn(model.HIER_PARENT_KEY)); 454 whatColumns.add(hierTable.getColumn(model.MAIN_PRIMARY_TYPE_KEY)); 455 Table proxyTable = null; 456 if (proxiesEnabled) { 457 proxyTable = database.getTable(model.PROXY_TABLE_NAME); 458 whatColumns.add(proxyTable.getColumn(model.PROXY_TARGET_KEY)); 459 whatColumns.add(proxyTable.getColumn(model.PROXY_VERSIONABLE_KEY)); 460 } 461 List<String> selectWhats = new ArrayList<>(whatColumns.size()); 462 for (Column col : whatColumns) { 463 selectWhats.add(col.getFullQuotedName()); 464 } 465 Select select = new Select(null); 466 select.setWhat(StringUtils.join(selectWhats, ", ")); 467 String from = hierTable.getQuotedName(); 468 if (proxiesEnabled) { 469 from += " LEFT JOIN " + proxyTable.getQuotedName() + " ON " + mainColumn.getFullQuotedName() + " = " 470 + proxyTable.getColumn(model.MAIN_KEY).getFullQuotedName(); 471 } 472 select.setFrom(from); 473 Column whereColumn = hierTable.getColumn(model.HIER_PARENT_KEY); 474 StringBuilder wherebuf = new StringBuilder(whereColumn.getFullQuotedName()); 475 if (nids == 1) { 476 wherebuf.append(" = ?"); 477 } else { 478 wherebuf.append(" IN ("); 479 for (int i = 0; i < nids; i++) { 480 if (i != 0) { 481 wherebuf.append(", "); 482 } 483 wherebuf.append('?'); 484 } 485 wherebuf.append(')'); 486 } 487 wherebuf.append(" AND "); 488 wherebuf.append(hierTable.getColumn(model.HIER_CHILD_ISPROPERTY_KEY).getFullQuotedName()); 489 wherebuf.append(" = " + dialect.toBooleanValueString(false)); // not complex 490 wherebuf.append(getSoftDeleteClause(model.HIER_TABLE_NAME)); 491 select.setWhere(wherebuf.toString()); 492 return new SQLInfoSelect(select.getStatement(), whatColumns, Collections.singletonList(whereColumn), null); 493 } 494 495 // ----- delete ----- 496 497 /** 498 * Returns the SQL {@code DELETE} to delete a row. The primary key columns are free parameters. 499 * 500 * @param tableName the table name 501 * @return the SQL {@code DELETE} statement 502 */ 503 public String getDeleteSql(String tableName) { 504 return deleteSqlMap.get(tableName); 505 } 506 507 /** 508 * Returns the SQL {@code DELETE} to delete several rows. The primary key columns are free parameters. 509 * 510 * @param tableName the table name 511 * @param n the number of rows to delete 512 * @return the SQL {@code DELETE} statement with a {@code IN} for the keys 513 */ 514 public String getDeleteSql(String tableName, int n) { 515 Table table = database.getTable(tableName); 516 Delete delete = new Delete(table); 517 String where = null; 518 for (Column column : table.getColumns()) { 519 if (column.getKey().equals(model.MAIN_KEY)) { 520 StringBuilder buf = new StringBuilder(); 521 buf.append(column.getQuotedName()); 522 if (n == 1) { 523 buf.append(" = ?"); 524 } else { 525 buf.append(" IN ("); 526 for (int i = 0; i < n; i++) { 527 if (i > 0) { 528 buf.append(", "); 529 } 530 buf.append("?"); 531 } 532 buf.append(")"); 533 } 534 where = buf.toString(); 535 } 536 } 537 delete.setWhere(where); 538 return delete.getStatement(); 539 } 540 541 /** 542 * Returns the SQL to soft-delete several rows. The array of ids and the time are free parameters. 543 * 544 * @return the SQL statement 545 */ 546 public String getSoftDeleteSql() { 547 return dialect.getSoftDeleteSql(); 548 } 549 550 /** 551 * Returns the SQL to clean (hard-delete) soft-deleted rows. The max and beforeTime are free parameters. 552 * 553 * @return the SQL statement 554 */ 555 public String getSoftDeleteCleanupSql() { 556 return dialect.getSoftDeleteCleanupSql(); 557 } 558 559 // ----- copy ----- 560 561 public SQLInfoSelect getCopyHier(boolean explicitName, boolean resetVersion) { 562 Table table = database.getTable(model.HIER_TABLE_NAME); 563 Collection<Column> columns = table.getColumns(); 564 List<String> selectWhats = new ArrayList<String>(columns.size()); 565 List<Column> selectWhatColumns = new ArrayList<Column>(5); 566 Insert insert = new Insert(table); 567 for (Column column : columns) { 568 if (column.isIdentity()) { 569 // identity column is never copied 570 continue; 571 } 572 insert.addColumn(column); 573 String quotedName = column.getQuotedName(); 574 String key = column.getKey(); 575 if (key.equals(model.MAIN_KEY) // 576 || key.equals(model.HIER_PARENT_KEY) // 577 || key.equals(model.MAIN_BASE_VERSION_KEY) // 578 || key.equals(model.MAIN_CHECKED_IN_KEY) // 579 || (key.equals(model.MAIN_MINOR_VERSION_KEY) && resetVersion) // 580 || (key.equals(model.MAIN_MAJOR_VERSION_KEY) && resetVersion) // 581 || (key.equals(model.HIER_CHILD_NAME_KEY) && explicitName)) { 582 // explicit value set 583 selectWhats.add("?"); 584 selectWhatColumns.add(column); 585 } else { 586 // otherwise copy value 587 selectWhats.add(quotedName); 588 } 589 } 590 Column whereColumn = table.getColumn(model.MAIN_KEY); 591 Select select = new Select(null); 592 select.setFrom(table.getQuotedName()); 593 select.setWhat(StringUtils.join(selectWhats, ", ")); 594 select.setWhere(whereColumn.getQuotedName() + " = ?"); 595 insert.setValues(select.getStatement()); 596 String sql = insert.getStatement(); 597 return new SQLInfoSelect(sql, selectWhatColumns, Collections.singletonList(whereColumn), null); 598 } 599 600 public String getCopySql(String tableName) { 601 return copySqlMap.get(tableName); 602 } 603 604 public Column getCopyIdColumn(String tableName) { 605 return copyIdColumnMap.get(tableName); 606 } 607 608 // ----- prepare everything ----- 609 610 /** 611 * Creates all the sql from the models. 612 */ 613 protected void initSQL() { 614 615 // structural tables 616 if (model.getRepositoryDescriptor().getClusteringEnabled()) { 617 if (!dialect.isClusteringSupported()) { 618 throw new NuxeoException("Clustering not supported for " + dialect.getClass().getSimpleName()); 619 } 620 initClusterSQL(); 621 } 622 initHierarchySQL(); 623 initRepositorySQL(); 624 if (dialect.supportsAncestorsTable()) { 625 initAncestorsSQL(); 626 } 627 628 for (String tableName : model.getFragmentNames()) { 629 if (tableName.equals(model.HIER_TABLE_NAME)) { 630 continue; 631 } 632 initFragmentSQL(tableName); 633 } 634 635 /* 636 * versions 637 */ 638 639 Table hierTable = database.getTable(model.HIER_TABLE_NAME); 640 Table versionTable = database.getTable(model.VERSION_TABLE_NAME); 641 hierTable.addIndex(model.MAIN_IS_VERSION_KEY); 642 versionTable.addIndex(model.VERSION_VERSIONABLE_KEY); 643 // don't index series+label, a simple label scan will suffice 644 645 /* 646 * proxies 647 */ 648 649 if (proxiesEnabled) { 650 Table proxyTable = database.getTable(model.PROXY_TABLE_NAME); 651 proxyTable.addIndex(model.PROXY_VERSIONABLE_KEY); 652 proxyTable.addIndex(model.PROXY_TARGET_KEY); 653 } 654 655 initSelectDescendantsSQL(); 656 657 /* 658 * fulltext 659 */ 660 if (!model.getRepositoryDescriptor().getFulltextDescriptor().getFulltextSearchDisabled()) { 661 Table table = database.getTable(model.FULLTEXT_TABLE_NAME); 662 FulltextConfiguration fulltextConfiguration = model.getFulltextConfiguration(); 663 if (fulltextConfiguration.indexNames.size() > 1 && !dialect.supportsMultipleFulltextIndexes()) { 664 String msg = String.format("SQL database supports only one fulltext index, but %d are configured: %s", 665 fulltextConfiguration.indexNames.size(), fulltextConfiguration.indexNames); 666 throw new NuxeoException(msg); 667 } 668 for (String indexName : fulltextConfiguration.indexNames) { 669 String suffix = model.getFulltextIndexSuffix(indexName); 670 int ftic = dialect.getFulltextIndexedColumns(); 671 if (ftic == 1) { 672 table.addIndex(indexName, IndexType.FULLTEXT, model.FULLTEXT_FULLTEXT_KEY + suffix); 673 } else if (ftic == 2) { 674 table.addIndex(indexName, IndexType.FULLTEXT, model.FULLTEXT_SIMPLETEXT_KEY + suffix, 675 model.FULLTEXT_BINARYTEXT_KEY + suffix); 676 } 677 } 678 } 679 680 /* 681 * binary columns for GC 682 */ 683 for (Entry<String, List<String>> e : model.getBinaryPropertyInfos().entrySet()) { 684 String tableName = e.getKey(); 685 Table table = database.getTable(tableName); 686 for (String key : e.getValue()) { 687 Select select = new Select(table); 688 Column col = table.getColumn(key); // key = name for now 689 select.setWhat("DISTINCT " + col.getQuotedName()); 690 select.setFrom(table.getQuotedName()); 691 getBinariesSql.add(select.getStatement()); 692 // in the result column we want the digest, not the binary 693 Column resCol = new Column(table, null, ColumnType.STRING, null); 694 getBinariesColumns.add(resCol); 695 } 696 } 697 } 698 699 protected void initClusterSQL() { 700 TableMaker maker = new TableMaker(model.CLUSTER_NODES_TABLE_NAME); 701 maker.newColumn(model.CLUSTER_NODES_NODEID_KEY, ColumnType.CLUSTERNODE); 702 maker.newColumn(model.CLUSTER_NODES_CREATED_KEY, ColumnType.TIMESTAMP); 703 maker.postProcessClusterNodes(); 704 705 maker = new TableMaker(model.CLUSTER_INVALS_TABLE_NAME); 706 maker.newColumn(model.CLUSTER_INVALS_NODEID_KEY, ColumnType.CLUSTERNODE); 707 maker.newColumn(model.CLUSTER_INVALS_ID_KEY, ColumnType.NODEVAL); 708 maker.newColumn(model.CLUSTER_INVALS_FRAGMENTS_KEY, ColumnType.CLUSTERFRAGS); 709 maker.newColumn(model.CLUSTER_INVALS_KIND_KEY, ColumnType.TINYINT); 710 maker.table.addIndex(model.CLUSTER_INVALS_NODEID_KEY); 711 maker.postProcessClusterInvalidations(); 712 } 713 714 /** 715 * Creates the SQL for the table holding global repository information. This includes the id of the hierarchy root 716 * node. 717 */ 718 protected void initRepositorySQL() { 719 TableMaker maker = new TableMaker(model.REPOINFO_TABLE_NAME); 720 maker.newColumn(model.MAIN_KEY, ColumnType.NODEIDFK); 721 maker.newColumn(model.REPOINFO_REPONAME_KEY, ColumnType.SYSNAME); 722 maker.postProcessRepository(); 723 } 724 725 /** 726 * Creates the SQL for the table holding hierarchy information. 727 */ 728 protected void initHierarchySQL() { 729 TableMaker maker = new TableMaker(model.HIER_TABLE_NAME); 730 // if (separateMainTable) 731 // maker.newColumn(model.MAIN_KEY, ColumnType.NODEIDFK); 732 maker.newColumn(model.MAIN_KEY, ColumnType.NODEID); 733 Column column = maker.newColumn(model.HIER_PARENT_KEY, ColumnType.NODEIDFKNULL); 734 maker.newColumn(model.HIER_CHILD_POS_KEY, ColumnType.INTEGER); 735 maker.newColumn(model.HIER_CHILD_NAME_KEY, ColumnType.STRING); 736 maker.newColumn(model.HIER_CHILD_ISPROPERTY_KEY, ColumnType.BOOLEAN); // notnull 737 // if (!separateMainTable) 738 maker.newFragmentFields(); 739 maker.postProcess(); 740 maker.postProcessHierarchy(); 741 // if (!separateMainTable) 742 // maker.postProcessIdGeneration(); 743 744 maker.table.addIndex(model.HIER_PARENT_KEY); 745 maker.table.addIndex(model.HIER_PARENT_KEY, model.HIER_CHILD_NAME_KEY); 746 // don't index parent+name+isprop, a simple isprop scan will suffice 747 maker.table.addIndex(model.MAIN_PRIMARY_TYPE_KEY); 748 749 if (model.getRepositoryDescriptor().getSoftDeleteEnabled()) { 750 maker.table.addIndex(model.MAIN_IS_DELETED_KEY); 751 } 752 } 753 754 protected void initSelectDescendantsSQL() { 755 Table hierTable = database.getTable(model.HIER_TABLE_NAME); 756 Table proxyTable = null; 757 if (proxiesEnabled) { 758 proxyTable = database.getTable(model.PROXY_TABLE_NAME); 759 } 760 Column mainColumn = hierTable.getColumn(model.MAIN_KEY); 761 List<Column> whatCols = new ArrayList<Column>(Arrays.asList(mainColumn, 762 hierTable.getColumn(model.HIER_PARENT_KEY), hierTable.getColumn(model.MAIN_PRIMARY_TYPE_KEY), 763 hierTable.getColumn(model.HIER_CHILD_ISPROPERTY_KEY))); 764 if (proxiesEnabled) { 765 whatCols.add(proxyTable.getColumn(model.PROXY_VERSIONABLE_KEY)); 766 whatCols.add(proxyTable.getColumn(model.PROXY_TARGET_KEY)); 767 } 768 // no mixins, not used to decide if we have a version or proxy 769 List<String> whats = new ArrayList<String>(6); 770 for (Column col : whatCols) { 771 whats.add(col.getFullQuotedName()); 772 } 773 Select select = new Select(null); 774 select.setWhat(StringUtils.join(whats, ", ")); 775 String from = hierTable.getQuotedName(); 776 if (proxiesEnabled) { 777 from += " LEFT JOIN " + proxyTable.getQuotedName() + " ON " + mainColumn.getFullQuotedName() + " = " 778 + proxyTable.getColumn(model.MAIN_KEY).getFullQuotedName(); 779 } 780 select.setFrom(from); 781 String where = dialect.getInTreeSql(mainColumn.getFullQuotedName(), null); 782 where += getSoftDeleteClause(model.HIER_TABLE_NAME); 783 select.setWhere(where); 784 selectDescendantsInfoSql = select.getStatement(); 785 selectDescendantsInfoWhatColumns = whatCols; 786 } 787 788 /** 789 * Creates the SQL for the table holding ancestors information. 790 * <p> 791 * This table holds trigger-updated information extracted from the recursive parent-child relationship in the 792 * hierarchy table. 793 */ 794 protected void initAncestorsSQL() { 795 TableMaker maker = new TableMaker(model.ANCESTORS_TABLE_NAME); 796 maker.newColumn(model.MAIN_KEY, ColumnType.NODEIDFKMUL); 797 maker.newColumn(model.ANCESTORS_ANCESTOR_KEY, ColumnType.NODEARRAY); 798 } 799 800 /** 801 * Creates the SQL for one fragment (simple or collection). 802 */ 803 protected void initFragmentSQL(String tableName) { 804 TableMaker maker = new TableMaker(tableName); 805 ColumnType type; 806 if (tableName.equals(model.HIER_TABLE_NAME)) { 807 type = ColumnType.NODEID; 808 } else if (tableName.equals(model.LOCK_TABLE_NAME)) { 809 type = ColumnType.NODEIDPK; // no foreign key to hierarchy 810 } else if (model.isCollectionFragment(tableName)) { 811 type = ColumnType.NODEIDFKMUL; 812 } else { 813 type = ColumnType.NODEIDFK; 814 } 815 maker.newColumn(model.MAIN_KEY, type); 816 maker.newFragmentFields(); 817 maker.postProcess(); 818 // if (isMain) 819 // maker.postProcessIdGeneration(); 820 } 821 822 protected void initSelections() { 823 for (SelectionType selType : SelectionType.values()) { 824 if (!proxiesEnabled && selType.tableName.equals(Model.PROXY_TABLE_NAME)) { 825 continue; 826 } 827 selections.put(selType, new SQLInfoSelection(selType)); 828 } 829 } 830 831 // ----- prepare one table ----- 832 833 protected class TableMaker { 834 835 private final String tableName; 836 837 private final Table table; 838 839 private final String orderBy; 840 841 protected TableMaker(String tableName) { 842 this.tableName = tableName; 843 table = database.addTable(tableName); 844 orderBy = model.getCollectionOrderBy(tableName); 845 } 846 847 protected void newFragmentFields() { 848 Map<String, ColumnType> keysType = model.getFragmentKeysType(tableName); 849 for (Entry<String, ColumnType> entry : keysType.entrySet()) { 850 newColumn(entry.getKey(), entry.getValue()); 851 } 852 } 853 854 protected Column newColumn(String key, ColumnType type) { 855 String columnName = key; 856 Column column = table.addColumn(columnName, type, key, model); 857 if (type == ColumnType.NODEID) { 858 // column.setIdentity(true); if idGenPolicy identity 859 column.setNullable(false); 860 column.setPrimary(true); 861 } 862 if (type == ColumnType.NODEIDFK || type == ColumnType.NODEIDPK) { 863 column.setNullable(false); 864 column.setPrimary(true); 865 } 866 if (type == ColumnType.NODEIDFKMUL) { 867 column.setNullable(false); 868 table.addIndex(key); 869 } 870 if (type == ColumnType.NODEIDFK || type == ColumnType.NODEIDFKNP || type == ColumnType.NODEIDFKNULL 871 || type == ColumnType.NODEIDFKMUL) { 872 column.setReferences(database.getTable(model.HIER_TABLE_NAME), model.MAIN_KEY); 873 } 874 return column; 875 } 876 877 // ----------------------- post processing ----------------------- 878 879 protected void postProcessClusterNodes() { 880 Collection<Column> columns = table.getColumns(); 881 List<Column> insertColumns = new ArrayList<Column>(columns.size()); 882 Insert insert = new Insert(table); 883 for (Column column : columns) { 884 insertColumns.add(column); 885 insert.addColumn(column); 886 } 887 createClusterNodeSql = insert.getStatement(); 888 createClusterNodeColumns = new ArrayList<>(columns); 889 890 Delete delete = new Delete(table); 891 Column column = table.getColumn(model.CLUSTER_NODES_NODEID_KEY); 892 delete.setWhere(column.getQuotedName() + " = ?"); 893 deleteClusterNodeSql = delete.getStatement(); 894 deleteClusterNodeColumn = column; 895 } 896 897 protected void postProcessClusterInvalidations() { 898 clusterInvalidationsColumns = Arrays.asList(table.getColumn(model.CLUSTER_INVALS_NODEID_KEY), 899 table.getColumn(model.CLUSTER_INVALS_ID_KEY), table.getColumn(model.CLUSTER_INVALS_FRAGMENTS_KEY), 900 table.getColumn(model.CLUSTER_INVALS_KIND_KEY)); 901 902 Delete delete = new Delete(table); 903 Column column = table.getColumn(model.CLUSTER_INVALS_NODEID_KEY); 904 delete.setWhere(column.getQuotedName() + " = ?"); 905 deleteClusterInvalsSql = delete.getStatement(); 906 deleteClusterInvalsColumn = column; 907 } 908 909 protected void postProcessRepository() { 910 postProcessRootIdSelect(); 911 postProcessInsert(); 912 } 913 914 protected void postProcessRootIdSelect() { 915 String what = null; 916 String where = null; 917 for (Column column : table.getColumns()) { 918 String key = column.getKey(); 919 String qname = column.getQuotedName(); 920 if (key.equals(model.MAIN_KEY)) { 921 what = qname; 922 selectRootIdWhatColumn = column; 923 } else if (key.equals(model.REPOINFO_REPONAME_KEY)) { 924 where = qname + " = ?"; 925 } else { 926 throw new RuntimeException(column.toString()); 927 } 928 } 929 Select select = new Select(table); 930 select.setWhat(what); 931 select.setFrom(table.getQuotedName()); 932 select.setWhere(where); 933 selectRootIdSql = select.getStatement(); 934 } 935 936 /** 937 * Precompute what we can from the information available for a regular schema table, or a collection table. 938 */ 939 protected void postProcess() { 940 postProcessSelectById(); 941 postProcessInsert(); 942 postProcessDelete(); 943 postProcessCopy(); 944 } 945 946 /** 947 * Additional SQL for the hierarchy table. 948 */ 949 protected void postProcessHierarchy() { 950 postProcessSelectChildrenIdsAndTypes(); 951 } 952 953 protected void postProcessSelectById() { 954 String[] orderBys = orderBy == null ? NO_ORDER_BY : new String[] { orderBy, ORDER_ASC }; 955 SQLInfoSelect select = makeSelect(table, orderBys, model.MAIN_KEY); 956 selectFragmentById.put(tableName, select); 957 } 958 959 protected void postProcessSelectChildrenIdsAndTypes() { 960 List<Column> whatColumns = new ArrayList<Column>(2); 961 List<String> whats = new ArrayList<String>(2); 962 Column column = table.getColumn(model.MAIN_KEY); 963 whatColumns.add(column); 964 whats.add(column.getQuotedName()); 965 column = table.getColumn(model.MAIN_PRIMARY_TYPE_KEY); 966 whatColumns.add(column); 967 whats.add(column.getQuotedName()); 968 column = table.getColumn(model.MAIN_MIXIN_TYPES_KEY); 969 whatColumns.add(column); 970 whats.add(column.getQuotedName()); 971 Select select = new Select(table); 972 select.setWhat(StringUtils.join(whats, ", ")); 973 select.setFrom(table.getQuotedName()); 974 String where = table.getColumn(model.HIER_PARENT_KEY).getQuotedName() + " = ?" 975 + getSoftDeleteClause(tableName); 976 select.setWhere(where); 977 selectChildrenIdsAndTypesSql = select.getStatement(); 978 selectChildrenIdsAndTypesWhatColumns = whatColumns; 979 // now only complex properties 980 where += " AND " + table.getColumn(model.HIER_CHILD_ISPROPERTY_KEY).getQuotedName() + " = " 981 + dialect.toBooleanValueString(true); 982 select.setWhere(where); 983 selectComplexChildrenIdsAndTypesSql = select.getStatement(); 984 } 985 986 // TODO optimize multiple inserts into one statement for collections 987 protected void postProcessInsert() { 988 // insert (implicitly auto-generated sequences not included) 989 Collection<Column> columns = table.getColumns(); 990 List<Column> insertColumns = new ArrayList<Column>(columns.size()); 991 Insert insert = new Insert(table); 992 for (Column column : columns) { 993 if (column.isIdentity()) { 994 // identity column is never inserted 995 continue; 996 } 997 insertColumns.add(column); 998 insert.addColumn(column); 999 } 1000 insertSqlMap.put(tableName, insert.getStatement()); 1001 insertColumnsMap.put(tableName, insertColumns); 1002 } 1003 1004 protected void postProcessDelete() { 1005 Delete delete = new Delete(table); 1006 List<String> wheres = new LinkedList<String>(); 1007 for (Column column : table.getColumns()) { 1008 if (column.getKey().equals(model.MAIN_KEY)) { 1009 wheres.add(column.getQuotedName() + " = ?"); 1010 } 1011 } 1012 delete.setWhere(StringUtils.join(wheres, " AND ")); 1013 deleteSqlMap.put(tableName, delete.getStatement()); 1014 } 1015 1016 // copy of a fragment 1017 // INSERT INTO foo (id, x, y) SELECT ?, x, y FROM foo WHERE id = ? 1018 protected void postProcessCopy() { 1019 Collection<Column> columns = table.getColumns(); 1020 List<String> selectWhats = new ArrayList<String>(columns.size()); 1021 Column copyIdColumn = table.getColumn(model.MAIN_KEY); 1022 Insert insert = new Insert(table); 1023 for (Column column : columns) { 1024 if (column.isIdentity()) { 1025 // identity column is never copied 1026 continue; 1027 } 1028 insert.addColumn(column); 1029 if (column == copyIdColumn) { 1030 // explicit value 1031 selectWhats.add("?"); 1032 } else { 1033 // otherwise copy value 1034 selectWhats.add(column.getQuotedName()); 1035 } 1036 } 1037 Select select = new Select(table); 1038 select.setWhat(StringUtils.join(selectWhats, ", ")); 1039 select.setFrom(table.getQuotedName()); 1040 select.setWhere(copyIdColumn.getQuotedName() + " = ?"); 1041 insert.setValues(select.getStatement()); 1042 copySqlMap.put(tableName, insert.getStatement()); 1043 copyIdColumnMap.put(tableName, copyIdColumn); 1044 } 1045 1046 } 1047 1048 public static class SQLInfoSelect { 1049 1050 public final String sql; 1051 1052 public final List<Column> whatColumns; 1053 1054 public final MapMaker mapMaker; 1055 1056 public final List<Column> whereColumns; 1057 1058 public final List<Column> opaqueColumns; 1059 1060 /** 1061 * Standard select for given columns. 1062 */ 1063 public SQLInfoSelect(String sql, List<Column> whatColumns, List<Column> whereColumns, List<Column> opaqueColumns) { 1064 this(sql, whatColumns, null, whereColumns, opaqueColumns); 1065 } 1066 1067 /** 1068 * Select where some column keys may be aliased, and some columns may be computed. The {@link MapMaker} is used 1069 * by the queryAndFetch() method. 1070 */ 1071 public SQLInfoSelect(String sql, MapMaker mapMaker) { 1072 this(sql, null, mapMaker, null, null); 1073 } 1074 1075 public SQLInfoSelect(String sql, List<Column> whatColumns, MapMaker mapMaker, List<Column> whereColumns, 1076 List<Column> opaqueColumns) { 1077 this.sql = sql; 1078 this.whatColumns = whatColumns; 1079 this.mapMaker = mapMaker; 1080 this.whereColumns = whereColumns == null ? null : new ArrayList<Column>(whereColumns); 1081 this.opaqueColumns = opaqueColumns == null ? null : new ArrayList<Column>(opaqueColumns); 1082 } 1083 } 1084 1085 /** 1086 * Info about how to do the query to get a {@link Selection}. 1087 */ 1088 public class SQLInfoSelection { 1089 1090 public final SelectionType type; 1091 1092 public final SQLInfoSelect selectAll; 1093 1094 public final SQLInfoSelect selectFiltered; 1095 1096 public SQLInfoSelection(SelectionType selType) { 1097 this.type = selType; 1098 Table table = database.getTable(selType.tableName); 1099 SQLInfoSelect selectAll; 1100 SQLInfoSelect selectFiltered; 1101 String from = table.getQuotedName(); 1102 List<String> clauses; 1103 if (selType.tableName.equals(model.HIER_TABLE_NAME)) { 1104 // clause already added by makeSelect 1105 clauses = null; 1106 } else { 1107 Table hierTable = database.getTable(model.HIER_TABLE_NAME); 1108 Join join = new Join(Join.INNER, hierTable.getQuotedName(), null, null, 1109 hierTable.getColumn(model.MAIN_KEY), table.getColumn(model.MAIN_KEY)); 1110 from += join.toSql(dialect); 1111 String clause = getSoftDeleteClause(); 1112 clauses = clause == null ? null : Collections.singletonList(clause); 1113 } 1114 if (selType.criterionKey == null) { 1115 selectAll = makeSelect(table, from, clauses, NO_ORDER_BY, selType.selKey); 1116 selectFiltered = makeSelect(table, from, clauses, NO_ORDER_BY, selType.selKey, selType.filterKey); 1117 } else { 1118 selectAll = makeSelect(table, from, clauses, NO_ORDER_BY, selType.selKey, selType.criterionKey); 1119 selectFiltered = makeSelect(table, from, clauses, NO_ORDER_BY, selType.selKey, selType.filterKey, 1120 selType.criterionKey); 1121 } 1122 this.selectAll = selectAll; 1123 this.selectFiltered = selectFiltered; 1124 } 1125 } 1126 1127 /** 1128 * Knows how to build a result map for a row given a {@link ResultSet}. This abstraction may be used to compute some 1129 * values on the fly. 1130 */ 1131 public interface MapMaker { 1132 Map<String, Serializable> makeMap(ResultSet rs) throws SQLException; 1133 } 1134 1135 /** 1136 * Builds the map from a result set given a list of columns and column keys. 1137 */ 1138 public static class ColumnMapMaker implements MapMaker { 1139 public final List<Column> columns; 1140 1141 public final List<String> keys; 1142 1143 public ColumnMapMaker(List<Column> columns) { 1144 this.columns = columns; 1145 keys = new ArrayList<String>(columns.size()); 1146 for (Column column : columns) { 1147 keys.add(column.getKey()); 1148 } 1149 } 1150 1151 public ColumnMapMaker(List<Column> columns, List<String> keys) { 1152 this.columns = columns; 1153 this.keys = keys; 1154 } 1155 1156 @Override 1157 public Map<String, Serializable> makeMap(ResultSet rs) throws SQLException { 1158 Map<String, Serializable> map = new HashMap<String, Serializable>(); 1159 int i = 1; 1160 for (Column column : columns) { 1161 String key = keys.get(i - 1); 1162 Serializable value = column.getFromResultSet(rs, i++); 1163 if (NXQL.ECM_UUID.equals(key) || NXQL.ECM_PARENTID.equals(key)) { 1164 value = String.valueOf(value); // idToString 1165 } 1166 map.put(key, value); 1167 } 1168 return map; 1169 } 1170 } 1171 1172 private static String[] NO_ORDER_BY = new String[0]; 1173 1174 /** 1175 * Basic SELECT x, y, z FROM table WHERE a = ? AND b = ? 1176 * <p> 1177 * with optional ORDER BY x, y DESC 1178 */ 1179 public SQLInfoSelect makeSelect(Table table, String[] orderBys, String... freeColumns) { 1180 return makeSelect(table, null, null, orderBys, freeColumns); 1181 } 1182 1183 /** 1184 * Same as above but the FROM can be passed in, to allow JOINs. 1185 */ 1186 public SQLInfoSelect makeSelect(Table table, String from, List<String> clauses, String[] orderBys, 1187 String... freeColumns) { 1188 boolean fullQuotedName = from != null; 1189 List<String> freeColumnsList = Arrays.asList(freeColumns); 1190 List<Column> whatColumns = new LinkedList<Column>(); 1191 List<Column> whereColumns = new LinkedList<Column>(); 1192 List<Column> opaqueColumns = new LinkedList<Column>(); 1193 List<String> whats = new LinkedList<String>(); 1194 List<String> wheres = new LinkedList<String>(); 1195 for (Column column : table.getColumns()) { 1196 String qname = fullQuotedName ? column.getFullQuotedName() : column.getQuotedName(); 1197 if (freeColumnsList.contains(column.getKey())) { 1198 whereColumns.add(column); 1199 wheres.add(qname + " = ?"); 1200 } else if (column.isOpaque()) { 1201 opaqueColumns.add(column); 1202 } else { 1203 whatColumns.add(column); 1204 whats.add(qname); 1205 } 1206 } 1207 if (whats.isEmpty()) { 1208 // only opaque columns, don't generate an illegal SELECT 1209 whats.add(table.getColumn(Model.MAIN_KEY).getQuotedName()); 1210 } 1211 if (clauses != null) { 1212 wheres.addAll(clauses); 1213 } 1214 Select select = new Select(table); 1215 select.setWhat(StringUtils.join(whats, ", ")); 1216 if (from == null) { 1217 from = table.getQuotedName(); 1218 } 1219 select.setFrom(from); 1220 String where = StringUtils.join(wheres, " AND ") + getSoftDeleteClause(table.getKey()); 1221 select.setWhere(where); 1222 List<String> orders = new LinkedList<String>(); 1223 for (int i = 0; i < orderBys.length; i++) { 1224 String name = orderBys[i++]; 1225 String ascdesc = orderBys[i].equals(ORDER_DESC) ? " " + ORDER_DESC : ""; 1226 Column col = table.getColumn(name); 1227 String qcol = fullQuotedName ? col.getFullQuotedName() : col.getQuotedName(); 1228 orders.add(qcol + ascdesc); 1229 } 1230 select.setOrderBy(StringUtils.join(orders, ", ")); 1231 return new SQLInfoSelect(select.getStatement(), whatColumns, whereColumns, opaqueColumns.isEmpty() ? null 1232 : opaqueColumns); 1233 } 1234 1235 public void initSQLStatements(Map<String, Serializable> testProps, List<String> sqlInitFiles) throws IOException { 1236 sqlStatements = new HashMap<String, List<SQLStatement>>(); 1237 SQLStatement.read(dialect.getSQLStatementsFilename(), sqlStatements); 1238 if (sqlInitFiles != null) { 1239 for (String filename : sqlInitFiles) { 1240 SQLStatement.read(filename, sqlStatements); 1241 } 1242 } 1243 if (!testProps.isEmpty()) { 1244 SQLStatement.read(dialect.getTestSQLStatementsFilename(), sqlStatements, true); // DDL time 1245 } 1246 sqlStatementsProperties = dialect.getSQLStatementsProperties(model, database); 1247 if (!testProps.isEmpty()) { 1248 sqlStatementsProperties.putAll(testProps); 1249 } 1250 } 1251 1252 /** 1253 * Executes the SQL statements for the given category. 1254 */ 1255 public void executeSQLStatements(String category, String ddlMode, Connection connection, JDBCLogger logger, 1256 ListCollector ddlCollector) throws SQLException { 1257 List<SQLStatement> statements = sqlStatements.get(category); 1258 if (statements != null) { 1259 SQLStatement.execute(statements, ddlMode, sqlStatementsProperties, dialect, connection, logger, 1260 ddlCollector); 1261 } 1262 } 1263 1264 public int getMaximumArgsForIn() { 1265 return dialect.getMaximumArgsForIn(); 1266 } 1267 1268}