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}