All Downloads are FREE. Search and download functionalities are using the official Maven repository.

nz.co.gregs.dbvolution.internal.query.QueryDetails Maven / Gradle / Ivy

/*
 * Copyright 2014 gregorygraham.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package nz.co.gregs.dbvolution.internal.query;

import java.lang.reflect.Array;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLTimeoutException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import nz.co.gregs.dbvolution.DBQueryRow;
import nz.co.gregs.dbvolution.DBRow;
import nz.co.gregs.dbvolution.actions.DBQueryable;
import nz.co.gregs.dbvolution.columns.ColumnProvider;
import nz.co.gregs.dbvolution.columns.QueryColumn;
import nz.co.gregs.dbvolution.databases.DBDatabase;
import nz.co.gregs.dbvolution.databases.DBStatement;
import nz.co.gregs.dbvolution.databases.definitions.DBDefinition;
import nz.co.gregs.dbvolution.datatypes.QueryableDatatype;
import nz.co.gregs.dbvolution.exceptions.AccidentalBlankQueryException;
import nz.co.gregs.dbvolution.exceptions.AccidentalCartesianJoinException;
import nz.co.gregs.dbvolution.exceptions.UnableToInstantiateDBRowSubclassException;
import nz.co.gregs.dbvolution.exceptions.UnacceptableClassForAutoFillAnnotation;
import nz.co.gregs.dbvolution.expressions.BooleanExpression;
import nz.co.gregs.dbvolution.expressions.DBExpression;
import nz.co.gregs.dbvolution.internal.properties.PropertyWrapper;
import nz.co.gregs.dbvolution.internal.properties.PropertyWrapperDefinition;
import nz.co.gregs.dbvolution.internal.querygraph.QueryGraph;
import nz.co.gregs.dbvolution.query.RowDefinition;

/**
 *
 * 

Support DBvolution at * Patreon

* * @author gregorygraham */ public class QueryDetails implements DBQueryable { private static final int DEFAULT_TIMEOUT_MILLISECONDS = 10000; private Integer timeoutInMilliseconds = DEFAULT_TIMEOUT_MILLISECONDS; // private ScheduledFuture timeout; static final transient ScheduledExecutorService TIMER_SERVICE = Executors.newSingleThreadScheduledExecutor(); private final Map, DBRow> emptyRows = new HashMap<>(); private final List allQueryTables = new ArrayList<>(); private final List requiredQueryTables = new ArrayList<>(); private final List optionalQueryTables = new ArrayList<>(); private final List assumedQueryTables = new ArrayList<>(); // private final List intersectingQueries; private QueryOptions options = new QueryOptions(); private final List extraExamples = new ArrayList<>(); private final List conditions = new ArrayList<>(); private final Map> expressionColumns = new LinkedHashMap<>(); private final Map dbReportGroupByColumns = new LinkedHashMap<>(); private final Map, Map> existingInstances = new HashMap<>(); private boolean groupByRequiredByAggregator = false; // private DBDefinition databaseDefinition = null; private String selectSQLClause = null; private final ArrayList havingColumns = new ArrayList<>(); private String rawSQLClause = ""; private List results = new ArrayList<>(); private String resultSQL; private Integer resultsPageIndex = 0; private Integer resultsRowLimit = -1; private Long queryCount = null; private QueryGraph queryGraph; private ColumnProvider[] sortOrderColumns; private ArrayList sortOrder; private List currentPage; /** *

Support DBvolution at * Patreon

* * @return the allQueryTables */ public List getAllQueryTables() { return allQueryTables; } /** *

Support DBvolution at * Patreon

* * @return the requiredQueryTables */ public List getRequiredQueryTables() { return requiredQueryTables; } /** *

Support DBvolution at * Patreon

* * @return the optionalQueryTables */ public List getOptionalQueryTables() { return optionalQueryTables; } /** *

Support DBvolution at * Patreon

* * @return the assumedQueryTables */ public List getAssumedQueryTables() { return assumedQueryTables; } /** *

Support DBvolution at * Patreon

* * @return the options */ public synchronized QueryOptions getOptions() { return options; } /** *

Support DBvolution at * Patreon

* * @return the extraExamples */ public List getExtraExamples() { return extraExamples; } /** * Get all conditions involved in this query. * *

Support DBvolution at * Patreon

* * @param database * @return all conditions in the query */ public synchronized List getAllConditions(DBDatabase database) { List allConditions = new ArrayList<>(); for (DBRow entry : allQueryTables) { allConditions.addAll(entry.getWhereClauseExpressions(database.getDefinition(), true)); } return allConditions; } /** *

Support DBvolution at * Patreon

* * @return the conditions */ public List getConditions() { return conditions; } /** *

Support DBvolution at * Patreon

* * @return the expressionColumns */ public Map> getExpressionColumns() { return expressionColumns; } /** *

Support DBvolution at * Patreon

* * @return the dbReportGroupByColumns */ public Map getDBReportGroupByColumns() { return dbReportGroupByColumns; } /** *

Support DBvolution at * Patreon

* * @return the existingInstances */ public Map, Map> getExistingInstances() { return existingInstances; } /** * Set the requirement for a GROUP BY clause. * * @param b */ public synchronized void setGroupByRequiredByAggregator(boolean b) { this.groupByRequiredByAggregator = true; } private synchronized boolean getGroupByRequiredByAggregator() { return this.groupByRequiredByAggregator; } /** * Return the requirement for a GROUP BY clause. * *

Support DBvolution at * Patreon

* * @return TRUE if the GROUP BY clause is required, otherwise FALSE. */ public boolean isGroupedQuery() { return getDBReportGroupByColumns().size() > 0 || getGroupByRequiredByAggregator(); } /** * Define the SELECT clause used during the query. * * @param selectClause */ public synchronized void setSelectSQLClause(String selectClause) { this.selectSQLClause = selectClause; } /** * Get the SELECT clause used during the query. * *

Support DBvolution at * Patreon

* * @return the SELECT clause defined earlier */ public synchronized String getSelectSQLClause() { return selectSQLClause; } /** *

Support DBvolution at * Patreon

* * @return the havingColumns */ public synchronized BooleanExpression[] getHavingColumns() { return havingColumns.toArray(new BooleanExpression[]{}); } /** * @param havingColumns the havingColumns to set */ public synchronized void setHavingColumns(BooleanExpression... havingColumns) { Collections.addAll(this.havingColumns, havingColumns); } // public synchronized void setDefinition(DBDefinition database) { // this.databaseDefinition = database; // } public void setQueryType(QueryType queryType) { this.options.setQueryType(queryType); } public synchronized void setOptions(QueryOptions tempOptions) { this.options = tempOptions; } /** * @return the rawSQLClause */ public synchronized String getRawSQLClause() { return rawSQLClause; } /** * @param rawSQLClause the rawSQLClause to set */ public synchronized void setRawSQLClause(String rawSQLClause) { this.rawSQLClause = rawSQLClause; } /** * @return the results */ public synchronized List getResults() { return results; } /** * @param results the results to set */ public synchronized void setResults(List results) { this.results = results; } /** * @return the resultSQL */ public synchronized String getResultSQL() { return resultSQL; } /** * @param resultSQL the resultSQL to set */ public synchronized void setResultSQL(String resultSQL) { this.resultSQL = resultSQL; } /** * @return the resultsPageIndex */ public synchronized Integer getResultsPageIndex() { return resultsPageIndex; } /** * @param resultsPageIndex the resultsPageIndex to set */ public synchronized void setResultsPageIndex(Integer resultsPageIndex) { this.resultsPageIndex = resultsPageIndex; } /** * @return the resultsRowLimit */ public synchronized Integer getResultsRowLimit() { return resultsRowLimit; } /** * @param resultsRowLimit the resultsRowLimit to set */ public synchronized void setResultsRowLimit(Integer resultsRowLimit) { this.resultsRowLimit = resultsRowLimit; } public synchronized void clearResults() { setResults(new ArrayList()); setResultsRowLimit(options.getRowLimit()); setResultsPageIndex(options.getPageIndex()); setResultSQL(null); } public synchronized Long getCount() { return queryCount; } private synchronized void getResultSetCount(DBDatabase db, QueryDetails details) throws SQLException { long result = 0L; try (DBStatement dbStatement = db.getDBStatement()) { final String sqlForCount = details.getSQLForCount(db, details); try (ResultSet resultSet = dbStatement.executeQuery(sqlForCount)) { while (resultSet.next()) { result = resultSet.getLong(1); } } } queryCount = result; } private synchronized String getSQLForCount(DBDatabase database, QueryDetails details) { if (!database.getDefinition().supportsFullOuterJoinNatively()) { return "SELECT COUNT(*) FROM (" + getSQLForQuery(database, new QueryState(details), QueryType.SELECT, details.getOptions()) .replaceAll("; *$", "") + ") A" + database.getDefinition().endSQLStatement(); } else { return getSQLForQuery(database, new QueryState(details), QueryType.COUNT, details.getOptions()); } } public synchronized String getSQLForQuery(DBDatabase database, QueryState queryState, QueryType queryType, QueryOptions options) { String sqlString = ""; if (getAllQueryTables().size() > 0) { initialiseQueryGraph(); DBDefinition defn = database.getDefinition(); StringBuilder selectClause = new StringBuilder().append(defn.beginSelectStatement()); int columnIndex = 1; boolean groupByIsRequired = false; String groupByColumnIndex = defn.beginGroupByClause(); String groupByColumnIndexSeparator = ""; HashMap indexesOfSelectedColumns = new HashMap<>(); HashMap indexesOfSelectedExpressions = new HashMap<>(); StringBuilder fromClause = new StringBuilder().append(defn.beginFromClause()); List joinedTables = new ArrayList<>(); final String initialWhereClause = new StringBuilder().append(defn.beginWhereClause()).append(defn.getWhereClauseBeginningCondition(options)).toString(); StringBuilder whereClause = new StringBuilder(initialWhereClause); StringBuilder groupByClause = new StringBuilder().append(defn.beginGroupByClause()); String havingClause; String lineSep = System.getProperty("line.separator"); List sortedQueryTables = options.isCartesianJoinAllowed() ? queryGraph.toListIncludingCartesianReversable(queryType == QueryType.REVERSESELECT) : queryGraph.toListReversable(queryType == QueryType.REVERSESELECT); if (options.getRowLimit() > 0) { selectClause.append(defn.getLimitRowsSubClauseDuringSelectClause(options)); } String fromClauseTableSeparator = ""; String colSep = defn.getStartingSelectSubClauseSeparator(); String groupByColSep = ""; String tableName; for (DBRow tabRow : sortedQueryTables) { tableName = tabRow.getTableName(); List tabProps = tabRow.getSelectedProperties(); for (PropertyWrapper propWrapper : tabProps) { final QueryableDatatype qdt = propWrapper.getQueryableDatatype(); final List columnAspectsList = propWrapper.getColumnAspects(defn); for (PropertyWrapperDefinition.ColumnAspects columnAspects : columnAspectsList) { String selectableName = columnAspects.selectableName; String columnAlias = columnAspects.columnAlias; String selectColumn = defn.doColumnTransformForSelect(qdt, selectableName); selectClause.append(colSep).append(selectColumn).append(" ").append(columnAlias); colSep = defn.getSubsequentSelectSubClauseSeparator() + lineSep; // Now deal with the GROUP BY and ORDER BY clause requirements DBExpression expression = columnAspects.expression; if (expression != null && expression.isAggregator()) { setGroupByRequiredByAggregator(true); } if (expression == null || (!expression.isAggregator() && (!expression.isPurelyFunctional() || defn.supportsPurelyFunctionalGroupByColumns()))) { groupByIsRequired = true; groupByColumnIndex += groupByColumnIndexSeparator + columnIndex; groupByColumnIndexSeparator = defn.getSubsequentGroupBySubClauseSeparator(); if (expression != null) { groupByClause.append(groupByColSep).append(defn.transformToStorableType(expression).toSQLString(defn)); groupByColSep = defn.getSubsequentGroupBySubClauseSeparator() + lineSep; } else { groupByClause.append(groupByColSep).append(selectColumn); groupByColSep = defn.getSubsequentGroupBySubClauseSeparator() + lineSep; } indexesOfSelectedColumns.put(propWrapper.getPropertyWrapperDefinition(), columnIndex); } if (expression != null && expression.isComplexExpression()) { final boolean hasTablesAlready = !joinedTables.isEmpty(); String joiner = hasTablesAlready ? options.isUseANSISyntax() ? " join " : fromClauseTableSeparator : ""; // joiner = hasTablesAlready&& !options.isUseANSISyntax()?:joiner; fromClause .append(joiner) .append(fromClauseTableSeparator) .append(expression.createSQLForFromClause(database)) .append(options.isUseANSISyntax() ? " join " : ", "); fromClauseTableSeparator = ", " + lineSep; queryState.mayRequireOnClause = true; } columnIndex++; } } if (!options.isUseANSISyntax()) { fromClause.append(fromClauseTableSeparator).append(tableName); queryState.addedInnerJoinToQuery(); } else { fromClause.append(getANSIJoinClause(defn, queryState, tabRow, joinedTables, options)); } joinedTables.add(tabRow); if (!options.isUseANSISyntax()) { List tabRowCriteria = tabRow.getWhereClausesWithAliases(defn); if (tabRowCriteria != null && !tabRowCriteria.isEmpty()) { for (String clause : tabRowCriteria) { whereClause.append(lineSep).append(defn.beginConditionClauseLine(options)).append(clause); } } getNonANSIJoin(tabRow, whereClause, defn, joinedTables, lineSep, options); queryState.addedInnerJoinToQuery(); } fromClauseTableSeparator = ", " + lineSep; } //add conditions found during the ANSI Join creation final String conditionsAsSQLClause = mergeConditionsIntoSQLClause(queryState.getRequiredConditions(), defn, options); if (!conditionsAsSQLClause.isEmpty()) { whereClause.append(defn.beginConditionClauseLine(options)).append(conditionsAsSQLClause); } for (DBRow extra : getExtraExamples()) { List extraCriteria = extra.getWhereClausesWithAliases(defn); if (extraCriteria != null && !extraCriteria.isEmpty()) { for (String clause : extraCriteria) { whereClause.append(lineSep).append(defn.beginConditionClauseLine(options)).append(clause); } } } for (BooleanExpression expression : queryState.getRemainingExpressions()) { whereClause.append(lineSep).append(defn.beginConditionClauseLine(options)).append("(").append(expression.toSQLString(defn)).append(")"); queryState.consumeExpression(expression); } for (Map.Entry> entry : getExpressionColumns().entrySet()) { final Object key = entry.getKey(); final QueryableDatatype qdt = entry.getValue(); DBExpression[] expressions = qdt.getColumnExpression(); for (DBExpression expression : expressions) { selectClause.append(colSep).append(defn.transformToStorableType(expression).toSQLString(defn)).append(" ").append(defn.formatExpressionAlias(key)); colSep = defn.getSubsequentSelectSubClauseSeparator() + lineSep; if (expression.isAggregator()) { setGroupByRequiredByAggregator(true); } if (!expression.isAggregator() && (!expression.isPurelyFunctional() || defn.supportsPurelyFunctionalGroupByColumns())) { groupByIsRequired = true; groupByColumnIndex += groupByColumnIndexSeparator + columnIndex; groupByColumnIndexSeparator = defn.getSubsequentGroupBySubClauseSeparator(); groupByClause.append(groupByColSep).append(defn.transformToStorableType(expression).toSQLString(defn)); groupByColSep = defn.getSubsequentGroupBySubClauseSeparator() + lineSep; } if (expression.isComplexExpression()) { fromClause .append(options.isUseANSISyntax() ? " join " : fromClauseTableSeparator) .append(expression.createSQLForFromClause(database)); if (options.isUseANSISyntax() && defn.requiresOnClauseForAllJoins()) { fromClause .append(defn.beginOnClause()) .append(BooleanExpression.trueExpression().toSQLString(defn)) .append(defn.endOnClause()); } fromClauseTableSeparator = (options.isUseANSISyntax() ? " join " : ", ") + lineSep; queryState.mayRequireOnClause = true; } indexesOfSelectedExpressions.put(expression, columnIndex); columnIndex++; } } for (Map.Entry entry : getDBReportGroupByColumns().entrySet()) { final DBExpression expression = entry.getValue(); if ((!expression.isPurelyFunctional() || defn.supportsPurelyFunctionalGroupByColumns())) { groupByClause.append(groupByColSep).append(defn.transformToStorableType(expression).toSQLString(defn)); groupByColSep = defn.getSubsequentGroupBySubClauseSeparator() + lineSep; } } boolean useColumnIndexGroupBy = defn.prefersIndexBasedGroupByClause(); // tidy up the raw SQL provided String rawSQLClauseFinal = (getRawSQLClause().isEmpty() ? "" : getRawSQLClause() + lineSep); // Strip the unnecessary where clause if possible if (whereClause.toString().equals(initialWhereClause) && rawSQLClauseFinal.isEmpty()) { whereClause = new StringBuilder(""); } if (queryType == QueryType.SELECT || queryType == QueryType.REVERSESELECT) { if (getSelectSQLClause() == null) { setSelectSQLClause(selectClause.toString()); } if (queryType == QueryType.REVERSESELECT) { selectClause = new StringBuilder(getSelectSQLClause()); } String groupByClauseFinal = ""; if (isGroupedQuery() && groupByIsRequired) { if (useColumnIndexGroupBy) { groupByClauseFinal = groupByColumnIndex; } else { groupByClauseFinal = groupByClause.toString() + lineSep; } } String orderByClauseFinal = getOrderByClause(queryState, defn, indexesOfSelectedColumns, indexesOfSelectedExpressions); if (!orderByClauseFinal.trim().isEmpty()) { orderByClauseFinal += lineSep; queryState.setHasBeenOrdered(true); } havingClause = getHavingClause(database, options); if (!havingClause.trim().isEmpty()) { havingClause += lineSep; } sqlString = defn.doWrapQueryForPaging( selectClause.append(lineSep) .append(fromClause).append(lineSep) .append(whereClause).append(lineSep) .append(rawSQLClauseFinal) .append(groupByClauseFinal) .append(havingClause) .append(orderByClauseFinal) .append(options.getRowLimit() > 0 ? defn.getLimitRowsSubClauseAfterWhereClause(queryState, options) : "") .append(defn.endSQLStatement()) .toString(), options); } else if (queryType == QueryType.COUNT) { setSelectSQLClause(defn.countStarClause()); sqlString = defn.beginSelectStatement() + defn.countStarClause() + lineSep + fromClause + lineSep + whereClause + lineSep + rawSQLClauseFinal + lineSep + defn.endSQLStatement(); } if (options.isCreatingNativeQuery() && queryState.isFullOuterJoin() && !defn.supportsFullOuterJoinNatively()) { sqlString = getSQLForFakeFullOuterJoin(database, sqlString, queryState, this, options, queryType); } } return sqlString; } private synchronized void initialiseQueryGraph() { if (queryGraph == null) { queryGraph = new QueryGraph(getRequiredQueryTables(), getConditions()); queryGraph.addOptionalAndConnectToRelevant(getOptionalQueryTables(), getConditions()); } else { queryGraph.clear(); queryGraph.addAndConnectToRelevant(getRequiredQueryTables(), getConditions()); queryGraph.addOptionalAndConnectToRelevant(getOptionalQueryTables(), getConditions()); } } public synchronized String getANSIJoinClause(DBDefinition defn, QueryState queryState, DBRow newTable, List previousTables, QueryOptions options) { List joinClauses = new ArrayList<>(); List conditionClauses = new ArrayList<>(); String lineSep = System.getProperty("line.separator"); boolean isLeftOuterJoin = false; boolean isFullOuterJoin = false; final ArrayList preExistingTables = new ArrayList<>(); preExistingTables.addAll(previousTables); preExistingTables.addAll(getAssumedQueryTables()); List requiredTables = getRequiredQueryTables(); if (requiredTables.isEmpty() && getOptionalQueryTables().size() == getAllQueryTables().size()) { isFullOuterJoin = true; queryState.addedFullOuterJoinToQuery(); } else if (getOptionalQueryTables().contains(newTable)) { isLeftOuterJoin = true; queryState.addedLeftOuterJoinToQuery(); } else { queryState.addedInnerJoinToQuery(); } //Store the expressions from the new table in the QueryState for (DBRow otherTable : preExistingTables) { queryState.addAllToRemainingExpressions(newTable.getRelationshipsAsBooleanExpressions(otherTable)); } // Add new table's conditions List newTableConditions = newTable.getWhereClausesWithAliases(defn); if (requiredTables.contains(newTable)) { queryState.addRequiredConditions(newTableConditions); } else { conditionClauses.addAll(newTableConditions); } // Since the first table can not have a ON clause we need to add it's ON clause to the second table's. if (previousTables.size() == 1) { final DBRow firstTable = previousTables.get(0); if (!getRequiredQueryTables().contains(firstTable)) { List firstTableConditions = firstTable.getWhereClausesWithAliases(defn); conditionClauses.addAll(firstTableConditions); } } // Add all the expressions we can if (previousTables.size() > 0 || conditionClauses.size() > 0) { for (BooleanExpression expr : queryState.getRemainingExpressions()) { Set tablesInvolved = new HashSet<>(expr.getTablesInvolved()); if (tablesInvolved.contains(newTable)) { tablesInvolved.remove(newTable); } if (tablesInvolved.size() <= previousTables.size()) { if (previousTables.containsAll(tablesInvolved)) { if (expr.isRelationship()) { joinClauses.add(expr.toSQLString(defn)); } else { if (requiredTables.containsAll(tablesInvolved)) { queryState.addRequiredCondition(expr.toSQLString(defn)); } else { conditionClauses.add(expr.toSQLString(defn)); } } queryState.consumeExpression(expr); } } } } StringBuilder sqlToReturn = new StringBuilder(); if (previousTables.isEmpty()) { sqlToReturn.append(" ").append(defn.getFromClause(newTable)); // Handle the edge case where a complex query has added a table before the first and we need an ON clause. if (queryState.mayRequireOnClause && defn.requiresOnClauseForAllJoins()) { sqlToReturn .append(defn.beginOnClause()) .append(BooleanExpression.trueExpression().toSQLString(defn)) .append(defn.endOnClause()); } } else { if (isFullOuterJoin) { sqlToReturn.append(lineSep).append(defn.beginFullOuterJoin()); } else if (isLeftOuterJoin) { sqlToReturn.append(lineSep).append(defn.beginLeftOuterJoin()); } else { sqlToReturn.append(lineSep).append(defn.beginInnerJoin()); } sqlToReturn.append(defn.getFromClause(newTable)); sqlToReturn.append(defn.beginOnClause()); if (!conditionClauses.isEmpty()) { if (!joinClauses.isEmpty()) { sqlToReturn.append("("); } sqlToReturn.append(mergeConditionsIntoSQLClause(conditionClauses, defn, options)); } if (!joinClauses.isEmpty()) { if (!conditionClauses.isEmpty()) { sqlToReturn.append(")").append(defn.beginAndLine()).append("("); } String separator = ""; for (String join : joinClauses) { sqlToReturn.append(separator).append(join); separator = defn.beginJoinClauseLine(options); } if (!conditionClauses.isEmpty()) { sqlToReturn.append(")"); } } if (conditionClauses.isEmpty() && joinClauses.isEmpty()) { sqlToReturn.append(defn.getWhereClauseBeginningCondition(options)); } sqlToReturn.append(defn.endOnClause()); } return sqlToReturn.toString(); } private synchronized void getNonANSIJoin(DBRow tabRow, StringBuilder whereClause, DBDefinition defn, List otherTables, String lineSep, QueryOptions options) { for (DBRow otherTab : otherTables) { List otherTableFks = otherTab.getForeignKeyPropertyWrappers(); for (PropertyWrapper otherTableFk : otherTableFks) { Class fkReferencedClass = otherTableFk.referencedClass(); if (fkReferencedClass.isAssignableFrom(tabRow.getClass())) { String formattedForeignKey = defn.formatTableAliasAndColumnName( otherTab, otherTableFk.columnName()); String formattedReferencedColumn = defn.formatTableAliasAndColumnName( tabRow, otherTableFk.referencedColumnName()); whereClause .append(lineSep) .append(defn.beginConditionClauseLine(options)) .append("(") .append(formattedForeignKey) .append(defn.getEqualsComparator()) .append(formattedReferencedColumn) .append(")"); } } } } private synchronized String mergeConditionsIntoSQLClause(List conditionClauses, DBDefinition defn, QueryOptions options) { String separator = ""; StringBuilder sqlToReturn = new StringBuilder(); for (String cond : conditionClauses) { sqlToReturn.append(separator).append(cond); separator = defn.beginConditionClauseLine(options); } return sqlToReturn.toString(); } private synchronized String getOrderByClause(QueryState state, DBDefinition defn, Map indexesOfSelectedProperties, Map IndexesOfSelectedExpressions) { final boolean prefersIndexBasedOrderByClause = defn.prefersIndexBasedOrderByClause(); if (sortOrderColumns != null && sortOrderColumns.length > 0) { state.setHasBeenOrdered(true); StringBuilder orderByClause = new StringBuilder(defn.beginOrderByClause()); String sortSeparator = defn.getStartingOrderByClauseSeparator(); for (ColumnProvider column : sortOrderColumns) { if (column instanceof QueryColumn) { QueryColumn qc = (QueryColumn) column; final QueryableDatatype qdt = qc.getQueryableDatatypeForExpressionValue(); orderByClause.append(sortSeparator).append(qc.toSQLString(defn)).append(defn.getOrderByDirectionClause(qdt.getSortOrder())); } else { PropertyWrapper prop = column.getColumn().getPropertyWrapper(); QueryableDatatype qdt = prop.getQueryableDatatype(); PropertyWrapperDefinition propDefn = prop.getPropertyWrapperDefinition(); if (prefersIndexBasedOrderByClause) { Integer columnIndex = indexesOfSelectedProperties.get(propDefn); if (columnIndex == null) { columnIndex = IndexesOfSelectedExpressions.get(qdt); } if (columnIndex == null) { final DBExpression[] columnExpressions = qdt.getColumnExpression(); for (DBExpression columnExpression : columnExpressions) { columnIndex = IndexesOfSelectedExpressions.get(columnExpression); } } orderByClause.append(sortSeparator).append(columnIndex).append(defn.getOrderByDirectionClause(qdt.getSortOrder())); sortSeparator = defn.getSubsequentOrderByClauseSeparator(); } else { if (qdt.hasColumnExpression()) { final DBExpression[] columnExpressions = qdt.getColumnExpression(); for (DBExpression columnExpression : columnExpressions) { final String dbColumnName = defn.transformToStorableType(columnExpression).toSQLString(defn); if (dbColumnName != null) { orderByClause.append(sortSeparator).append(dbColumnName).append(defn.getOrderByDirectionClause(qdt.getSortOrder())); sortSeparator = defn.getSubsequentOrderByClauseSeparator(); } } } else { final RowDefinition possibleDBRow = prop.getRowDefinitionInstanceWrapper().adapteeRowDefinition(); if (possibleDBRow != null && DBRow.class.isAssignableFrom(possibleDBRow.getClass())) { final DBRow adapteeDBRow = (DBRow) possibleDBRow; final String dbColumnName = defn.formatTableAliasAndColumnName(adapteeDBRow, prop.columnName()); if (dbColumnName != null) { orderByClause.append(sortSeparator).append(dbColumnName).append(defn.getOrderByDirectionClause(qdt.getSortOrder())); sortSeparator = defn.getSubsequentOrderByClauseSeparator(); } } } } } } orderByClause.append(defn.endOrderByClause()); return orderByClause.toString(); } return ""; } private synchronized String getHavingClause(DBDatabase database, QueryOptions options) { BooleanExpression[] having = getHavingColumns(); final DBDefinition defn = database.getDefinition(); String havingClauseStart = defn.getHavingClauseStart(); if (having.length == 1) { return havingClauseStart + having[0].toSQLString(defn); } else if (having.length > 1) { String sep = ""; final String beginAndLine = defn.beginAndLine(); StringBuilder returnStr = new StringBuilder(havingClauseStart); for (BooleanExpression havingColumn : having) { returnStr.append(sep).append(havingColumn.toSQLString(defn)); sep = beginAndLine; } return returnStr.toString(); } else { return ""; } } /** * Adapts the query to work for a database that does not support full outer * join queries. * *

* Full outer join queries in this sense use a FULL OUTER join for ALL joins * in the query. * *

* The standard implementation replaces the query with a LEFT OUTER join query * UNIONed with a RIGHT OUTER join query. * * @param querySQL * @param options *

Support DBvolution at * Patreon

* @return a fake full outer join query for databases that don't support FULL * OUTER joins */ private synchronized String getSQLForFakeFullOuterJoin(DBDatabase database, String existingSQL, QueryState queryState, QueryDetails details, QueryOptions options, QueryType queryType) { String sqlForQuery; String unionOperator; DBDefinition defn = database.getDefinition(); if (defn.supportsUnionDistinct()) { unionOperator = defn.getUnionDistinctOperator(); } else { unionOperator = defn.getUnionOperator(); } if (defn.supportsRightOuterJoinNatively()) { // Fake the outer join by revering the left outer joins to right outer joins sqlForQuery = existingSQL.replaceAll("; *$", " ").replaceAll(defn.beginFullOuterJoin(), defn.beginLeftOuterJoin()); sqlForQuery += unionOperator + existingSQL.replaceAll(defn.beginFullOuterJoin(), defn.beginRightOuterJoin()); } else { // Watch out for the infinite loop options.setCreatingNativeQuery(false); String reversedQuery = getSQLForQuery(database, queryState, QueryType.REVERSESELECT, options); options.setCreatingNativeQuery(true); sqlForQuery = existingSQL.replaceAll("; *$", " ").replaceAll(defn.beginFullOuterJoin(), defn.beginLeftOuterJoin()); sqlForQuery += unionOperator; sqlForQuery += reversedQuery.replaceAll("; *$", " ").replaceAll(defn.beginFullOuterJoin(), defn.beginLeftOuterJoin()); } return sqlForQuery; } public synchronized void setSortOrder(ColumnProvider[] sortColumns) { blankResults(); sortOrderColumns = Arrays.copyOf(sortColumns, sortColumns.length); sortOrder = new ArrayList<>(); PropertyWrapper prop; for (ColumnProvider col : sortColumns) { if (col instanceof QueryColumn) { // System.out.println(""+((QueryColumn) col).toSQLString(new H2DBDefinition())); } else { prop = col.getColumn().getPropertyWrapper(); if (prop != null) { sortOrder.add(prop); } } } } public synchronized void blankResults() { setResults(null); setResultSQL(null); queryGraph = null; } public synchronized void addToSortOrder(ColumnProvider[] sortColumns) { if (sortColumns != null) { blankResults(); List sortOrderColumnsList = new LinkedList<>(); if (sortOrderColumns != null) { sortOrderColumnsList.addAll(Arrays.asList(sortOrderColumns)); } sortOrderColumnsList.addAll(Arrays.asList(sortColumns)); setSortOrder(sortOrderColumnsList.toArray(new ColumnProvider[]{})); } } public synchronized void clearSortOrder() { sortOrder = null; sortOrderColumns = null; } private synchronized void prepareForQuery(DBDatabase database, QueryOptions options) throws SQLException { clearResults(); setResultSQL(this.getSQLForQuery(database, new QueryState(this), QueryType.SELECT, options)); } public synchronized boolean needsResults(QueryOptions options) { final DBDatabase queryDatabase = options.getQueryDatabase(); return getResults() == null || queryDatabase == null || getResultSQL() == null || getResults().isEmpty() || !getResultsPageIndex().equals(options.getPageIndex()) || !getResultsRowLimit().equals(options.getRowLimit()) || !getResultSQL().equals(getSQLForQuery(queryDatabase, new QueryState(this), QueryType.SELECT, options)); } @Override public synchronized List getAllRows() throws SQLException, SQLTimeoutException, AccidentalBlankQueryException, AccidentalCartesianJoinException { final QueryOptions opts = getOptions(); if (this.needsResults(opts)) { getOptions().getQueryDatabase().executeDBQuery(this); } if (opts.getRowLimit() > 0 && getResults().size() > opts.getRowLimit()) { final int firstItemOfPage = opts.getPageIndex() * opts.getRowLimit(); final int firstItemOfNextPage = (opts.getPageIndex() + 1) * opts.getRowLimit(); return getResults().subList(firstItemOfPage, firstItemOfNextPage); } else { return getResults(); } } @Override public synchronized DBQueryable query(DBDatabase db) throws SQLException { getOptions().setQueryDatabase(db); final QueryType queryType = getOptions().getQueryType(); switch (queryType) { case COUNT: getResultSetCount(db, this); break; case ROWSFORPAGE: getAllRowsForPage(db, this); break; case GENERATESQLFORSELECT: this.setResultSQL(getSQLForQuery(db, new QueryState(this), QueryType.SELECT, getOptions())); break; case GENERATESQLFORCOUNT: this.setResultSQL(getSQLForCount(db, this)); break; case SELECT: fillResultSetInternal(db, this, getOptions()); break; default: throw new UnsupportedOperationException("Query Type Not Supported: " + queryType); } return this; } public synchronized void getAllRowsForPage(DBDatabase database, QueryDetails details) throws SQLException { final QueryOptions opts = getOptions(); int pageNumber = getResultsPageIndex(); final DBDefinition defn = database.getDefinition(); if (defn.supportsPagingNatively(opts)) { opts.setPageIndex(pageNumber); if (details.needsResults(opts)) { fillResultSetInternal(database, details, options); } setCurrentPage(getResults()); } else { if (defn.supportsRowLimitsNatively(opts)) { QueryOptions tempOptions = new QueryOptions(opts); tempOptions.setQueryType(QueryType.SELECT); tempOptions.setRowLimit((pageNumber + 1) * opts.getRowLimit()); if (details.needsResults(tempOptions) || tempOptions.getRowLimit() > getResults().size()) { details.setOptions(tempOptions); database.executeDBQuery(details); details.setOptions(opts); } } else { if (details.needsResults(opts)) { QueryOptions tempOptions = new QueryOptions(opts); tempOptions.setRowLimit(-1); tempOptions.setQueryType(QueryType.SELECT); details.setOptions(tempOptions); database.executeDBQuery(details); details.setOptions(opts); } } int rowLimit = opts.getRowLimit(); int startIndex = rowLimit * pageNumber; startIndex = (startIndex < 0 ? 0 : startIndex); int stopIndex = rowLimit * (pageNumber + 1); stopIndex = (stopIndex >= getResults().size() ? getResults().size() : stopIndex); if (stopIndex - startIndex < 1) { setCurrentPage(new ArrayList()); } else { setCurrentPage(getResults().subList(startIndex, stopIndex)); } } } protected synchronized void fillResultSetInternal(DBDatabase db, QueryDetails details, QueryOptions options) throws SQLException { prepareForQuery(db, options); final DBDefinition defn = db.getDefinition(); if (!options.isBlankQueryAllowed() && willCreateBlankQuery(db) && details.getRawSQLClause().isEmpty()) { throw new AccidentalBlankQueryException(options.isBlankQueryAllowed(), willCreateBlankQuery(db), details.getRawSQLClause().isEmpty()); } if (!options.isCartesianJoinAllowed() && (details.getRequiredQueryTables().size() + details.getOptionalQueryTables().size()) > 1 && queryGraph.willCreateCartesianJoin()) { throw new AccidentalCartesianJoinException(details.getResultSQL()); } fillResultSetFromSQL(db, details, defn, details.getResultSQL()); } protected synchronized void fillResultSetFromSQL(DBDatabase db, QueryDetails details, final DBDefinition defn, String sqlString) throws SQLException { DBQueryRow queryRow; try (DBStatement dbStatement = db.getDBStatement(); ResultSet resultSet = getResultSetForSQL(dbStatement, sqlString)) { while (resultSet.next()) { queryRow = new DBQueryRow(this); setExpressionColumns(defn, resultSet, queryRow); setQueryRowFromResultSet(defn, resultSet, details, queryRow, details.isGroupedQuery()); details.getResults().add(queryRow); } } for (DBQueryRow result : details.getResults()) { List rows = result.getAll(); for (DBRow row : rows) { if (row != null) { setAutoFilledFields(row); } } } } @SuppressWarnings("unchecked") synchronized void setAutoFilledFields(DBRow row) throws SQLException { boolean arrayRequired = false; boolean listRequired = false; try { List fields = row.getAutoFillingPropertyWrappers(); for (PropertyWrapper field : fields) { if (field.isAutoFilling()) { Class requiredClass = field.getRawJavaType(); if (requiredClass.isArray()) { requiredClass = requiredClass.getComponentType(); arrayRequired = true; } else if (Collection.class.isAssignableFrom(requiredClass)) { listRequired = true; requiredClass = field.getAutoFillingClass(); if (requiredClass.isAssignableFrom(DBRow.class)) { throw new nz.co.gregs.dbvolution.exceptions.UnacceptableClassForAutoFillAnnotation(field, requiredClass); } } if (DBRow.class.isAssignableFrom(requiredClass)) { DBRow fieldInstance; try { fieldInstance = (DBRow) requiredClass.newInstance(); } catch (InstantiationException | IllegalAccessException ex) { throw new UnableToInstantiateDBRowSubclassException((Class) requiredClass, ex); } List relatedInstancesFromQuery = getRelatedInstancesFromQuery(row, fieldInstance); if (arrayRequired) { Object newInstance = Array.newInstance(requiredClass, relatedInstancesFromQuery.size()); for (int index = 0; index < relatedInstancesFromQuery.size(); index++) { Array.set(newInstance, index, relatedInstancesFromQuery.get(index)); } field.setRawJavaValue(newInstance); } else if (listRequired) { field.setRawJavaValue(relatedInstancesFromQuery); } else if (relatedInstancesFromQuery.isEmpty()) { field.setRawJavaValue(null); } else { field.setRawJavaValue(relatedInstancesFromQuery.get(0)); } } } } } catch (UnacceptableClassForAutoFillAnnotation | UnableToInstantiateDBRowSubclassException | SQLException | NegativeArraySizeException | IllegalArgumentException | ArrayIndexOutOfBoundsException ex) { throw new RuntimeException("Unable To AutoFill Field", ex); } } /** * Finds all instances of {@code example} that share a {@link DBQueryRow} with * this instance. * * @param DBRow * @param row * @param example example *

Support DBvolution at * Patreon

* @return all instances of {@code example} that are connected to this * instance in the {@code query} 1 Database exceptions may be thrown * @throws java.sql.SQLException java.sql.SQLException */ public List getRelatedInstancesFromQuery(DBRow row, R example) throws SQLException { List instances = new ArrayList<>(); final List allRows = getAllRows(); for (DBQueryRow qrow : allRows) { DBRow versionOfThis = qrow.get(row); R versionOfThat = qrow.get(example); if (versionOfThis.equals(row) && versionOfThat != null) { instances.add(versionOfThat); } } return instances; } public synchronized boolean willCreateBlankQuery(DBDatabase db) { boolean willCreateBlankQuery = true; for (DBRow table : getAllQueryTables()) { willCreateBlankQuery = willCreateBlankQuery && table.willCreateBlankQuery(db.getDefinition()); } for (DBRow table : getExtraExamples()) { willCreateBlankQuery = willCreateBlankQuery && table.willCreateBlankQuery(db.getDefinition()); } willCreateBlankQuery = willCreateBlankQuery && getHavingColumns().length == 0; return willCreateBlankQuery && (getConditions().isEmpty()); } /** * Executes the query using the statement provided and returns the ResultSet * * @param statement dbStatement * @param sql sql *

Support DBvolution at * Patreon

* @return the ResultSet returned from the actual database. Database * exceptions may be thrown * @throws java.sql.SQLException java.sql.SQLException * @throws java.sql.SQLTimeoutException */ protected synchronized ResultSet getResultSetForSQL(final DBStatement statement, String sql) throws SQLException, SQLTimeoutException { final Integer timeoutTime = this.getTimeoutInMilliseconds(); ScheduledFuture cancelHandle = null; if (timeoutTime != null && timeoutTime > 0) { final Runnable canceller = new QueryCanceller(statement); cancelHandle = TIMER_SERVICE.schedule(canceller, timeoutTime, TimeUnit.MILLISECONDS); } final ResultSet queryResults = statement.executeQuery(sql); if (cancelHandle != null) { cancelHandle.cancel(true); } return queryResults; } private void setExpressionColumns(DBDefinition defn, ResultSet resultSet, DBQueryRow queryRow) throws SQLException { for (Map.Entry> entry : getExpressionColumns().entrySet()) { final Object key = entry.getKey(); final QueryableDatatype value = entry.getValue(); String expressionAlias = defn.formatExpressionAlias(key); QueryableDatatype expressionQDT = value.getQueryableDatatypeForExpressionValue(); expressionQDT.setFromResultSet(defn, resultSet, expressionAlias); queryRow.addExpressionColumnValue(key, expressionQDT); } } public synchronized void setQueryRowFromResultSet(DBDefinition defn, ResultSet resultSet, QueryDetails details, DBQueryRow queryRow, boolean isGroupedQuery) throws SQLException { for (DBRow tableRow : details.getAllQueryTables()) { DBRow newInstance = DBRow.getDBRow(tableRow.getClass()); setFieldsFromColumns(defn, tableRow, newInstance, resultSet); newInstance.setReturnFieldsBasedOn(tableRow); newInstance.setDefined(); // Actually came from the database so it is a defined row. Map existingInstancesOfThisTableRow = details.getExistingInstances().get(tableRow.getClass()); existingInstancesOfThisTableRow = setExistingInstancesForTable(existingInstancesOfThisTableRow, newInstance); final Class newInstanceClass = newInstance.getClass(); if (newInstance.isEmptyRow()) { DBRow emptyRow = emptyRows.get(newInstanceClass); if (emptyRow != null) { queryRow.put(newInstanceClass, emptyRow); } else { emptyRows.put(newInstanceClass, newInstance); queryRow.put(newInstanceClass, newInstance); } } else { final List> primaryKeys = newInstance.getPrimaryKeys(); boolean pksHaveBeenSet = true; for (QueryableDatatype pk : primaryKeys) { pksHaveBeenSet = pksHaveBeenSet && pk.hasBeenSet(); } if (isGroupedQuery || primaryKeys.isEmpty() || !pksHaveBeenSet) { queryRow.put(newInstanceClass, newInstance); } else { DBRow existingInstance = getOrSetExistingInstanceForRow(defn, newInstance, existingInstancesOfThisTableRow); queryRow.put(existingInstance.getClass(), existingInstance); } } } } /** * Based on the template provided by oldInstance, fill all the fields of * newInstance with data from the current row of the ResultSet. * *

* OldInstance is used to find the selected properties, newInstance is the * result, and restultSet contains the retrieved data. * * Database exceptions may be thrown * * @param defn * @param oldInstance oldInstance * @param newInstance newInstance * @param resultSet resultSet * @throws java.sql.SQLException java.sql.SQLException */ protected void setFieldsFromColumns(DBDefinition defn, DBRow oldInstance, DBRow newInstance, ResultSet resultSet) throws SQLException { List selectedProperties = oldInstance.getSelectedProperties(); List newProperties = newInstance.getColumnPropertyWrappers(); for (PropertyWrapper newProp : newProperties) { QueryableDatatype qdt = newProp.getQueryableDatatype(); for (PropertyWrapper propertyWrapper : selectedProperties) { if (propertyWrapper.getPropertyWrapperDefinition().equals(newProp.getPropertyWrapperDefinition())) { String resultSetColumnName = newProp.getColumnAlias(defn)[0]; //for (String resultSetColumnName : resultSetColumnNames) { qdt.setFromResultSet(defn, resultSet, resultSetColumnName); if (newInstance.isEmptyRow() && !qdt.isNull()) { newInstance.setEmptyRow(false); } //} } } // ensure field set when using type adaptors newProp.setQueryableDatatype(qdt); } } /** * Creates the list of already created rows for the DBRow class supplied. * * @param existingInstancesOfThisTableRow existingInstancesOfThisTableRow * @param newInstance newInstance *

Support DBvolution at * Patreon

* @return a list of existing rows. */ protected Map setExistingInstancesForTable(Map existingInstancesOfThisTableRow, DBRow newInstance) { Map hashMap = existingInstancesOfThisTableRow; if (hashMap == null) { hashMap = new HashMap<>(); } getExistingInstances().put(newInstance.getClass(), hashMap); return hashMap; } /** * Retrieves or sets the existing instance of the DBRow provided. * *

* Queries maintain a list of existing rows to avoid duplicating identical * rows. This method checks to see if the supplied row already exists and * returns the existing version. * *

* If the row is new then this method stores it, and returns it as the * existing instance. * * @param defn * @param newInstance newInstance * @param existingInstancesOfThisTableRow existingInstancesOfThisTableRow *

Support DBvolution at * Patreon

* @return the exisinting instance of the provided row, or the row itself if * none exists. */ protected DBRow getOrSetExistingInstanceForRow(DBDefinition defn, DBRow newInstance, Map existingInstancesOfThisTableRow) { DBRow existingInstance = newInstance; final List primaryKeys = newInstance.getPrimaryKeyPropertyWrappers(); for (PropertyWrapper primaryKey : primaryKeys) { if (primaryKey != null) { final QueryableDatatype qdt = primaryKey.getQueryableDatatype(); if (qdt != null) { existingInstance = existingInstancesOfThisTableRow.get(qdt.toSQLString(defn)); if (existingInstance == null) { existingInstance = newInstance; existingInstancesOfThisTableRow.put(qdt.toSQLString(defn), existingInstance); } } } } return existingInstance; } protected synchronized void setCurrentPage(List results) { currentPage = results; } public synchronized List getCurrentPage() { return currentPage; } public synchronized void clear() { requiredQueryTables.clear(); optionalQueryTables.clear(); allQueryTables.clear(); conditions.clear(); extraExamples.clear(); blankResults(); } public synchronized void setTimeoutInMilliseconds(Integer milliseconds) { this.timeoutInMilliseconds = milliseconds; } /** * @return the timeoutInMilliseconds */ public synchronized Integer getTimeoutInMilliseconds() { return timeoutInMilliseconds; } @Override public synchronized String toSQLString(DBDatabase db) { getOptions().setQueryDatabase(db); switch (getOptions().getQueryType()) { case COUNT: return getSQLForCount(db, this); default: return getSQLForQuery(db, new QueryState(this), QueryType.SELECT, getOptions()); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy