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