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

org.datanucleus.store.rdbms.sql.SQLStatement Maven / Gradle / Ivy

There is a newer version: 6.0.8
Show newest version
/**********************************************************************
Copyright (c) 2008 Andy Jefferson and others. All rights reserved.
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.

Contributors:
    ...
**********************************************************************/
package org.datanucleus.store.rdbms.sql;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.datanucleus.ClassLoaderResolver;
import org.datanucleus.exceptions.NucleusException;
import org.datanucleus.exceptions.NucleusUserException;
import org.datanucleus.store.rdbms.identifier.DatastoreIdentifier;
import org.datanucleus.store.rdbms.mapping.java.JavaTypeMapping;
import org.datanucleus.store.rdbms.RDBMSPropertyNames;
import org.datanucleus.store.rdbms.RDBMSStoreManager;
import org.datanucleus.store.rdbms.adapter.DatastoreAdapter;
import org.datanucleus.store.rdbms.query.QueryGenerator;
import org.datanucleus.store.rdbms.sql.SQLJoin.JoinType;
import org.datanucleus.store.rdbms.sql.expression.BooleanExpression;
import org.datanucleus.store.rdbms.sql.expression.BooleanLiteral;
import org.datanucleus.store.rdbms.sql.expression.SQLExpression;
import org.datanucleus.store.rdbms.sql.expression.SQLExpressionFactory;
import org.datanucleus.store.rdbms.table.Table;
import org.datanucleus.util.NucleusLogger;

/**
 * Class providing an API for generating SQL statements.
 * Caller should create the SQLStatement object and (optionally) call setClassLoaderResolver() to set any class loading restriction.
 * Then the caller builds up the statement using the various methods, and accesses the SQL statement using getSQLText(). 
 * 

* The generated SQL is cached. Any use of a mutating method, changing the composition of the statement * will clear the cached SQL, and it will be regenerated when

getSQLText
is called next. * *

Table Groups

* When tables are registered in the statement they are split into "table groups". A table group is, * in simple terms, an object in the query. If a table has a super-table and a field of the object * is selected that is in the super-table then the super-table is added to the table group. If there * is a join to a related object then the table of this object will be put in a new table group. * So the same datastore table can appear multiple times in the statement, each time for a different * object. * *

Table Aliases

* All methods that cause a new SQLTable to be created also allow specification of the table alias * in the statement. Where the alias is not provided then we use a table "namer" (definable on the * plugin-point "org.datanucleus.store.rdbms.sql_tablenamer"). The table namer can define names * simply based on the table number, or based on table group and the number of tables in the group * etc etc. * To select a particular table "namer", set the extension "table-naming-strategy" to the key of the namer plugin. * The default is "alpha-scheme" which bases table names on the group and number in that group. * * Note that this class is not intended to be thread-safe. It is used by a single ExecutionContext */ public abstract class SQLStatement { public static final String EXTENSION_SQL_TABLE_NAMING_STRATEGY = "table-naming-strategy"; public static final String EXTENSION_LOCK_FOR_UPDATE = "lock-for-update"; public static final String EXTENSION_LOCK_FOR_UPDATE_NOWAIT = "for-update-nowait"; /** Map of SQLTable naming instance keyed by the name of the naming scheme. */ protected static final Map tableNamerByName = new ConcurrentHashMap<>(); /** Cached SQL statement, generated by getSQLText(). */ protected SQLText sql = null; /** Manager for the RDBMS datastore. */ protected RDBMSStoreManager rdbmsMgr; /** ClassLoader resolver to use. Used by sub-expressions. Defaults to the loader resolver for the store manager. */ protected ClassLoaderResolver clr; /** Context of any query generation. */ protected QueryGenerator queryGenerator = null; protected SQLTableNamer namer = null; /** Name of class that this statement selects (optional, only typically for unioned statements). */ protected String candidateClassName = null; /** Map of extensions for use in generating the SQL, keyed by the extension name. */ protected Map extensions; /** Parent statement, if this is a subquery SELECT. Must be set at construction. */ protected SQLStatement parent = null; /** Primary table for this statement. */ protected SQLTable primaryTable; /** List of joins for this statement. */ protected List joins; protected boolean requiresJoinReorder = false; /** Map of tables referenced in this statement, keyed by their alias. */ protected Map tables; /** Map of table groups keyed by the group name. */ protected Map tableGroups = new HashMap<>(); /** Where clause. */ protected BooleanExpression where; /** * Constructor for an SQL statement that is a subquery of another statement. * @param parentStmt Parent statement * @param rdbmsMgr The datastore manager * @param table The primary table * @param alias Alias for this table * @param tableGroupName Name of candidate table-group (if any). Uses "Group0" if not provided * @param extensions Any extensions (optional) */ public SQLStatement(SQLStatement parentStmt, RDBMSStoreManager rdbmsMgr, Table table, DatastoreIdentifier alias, String tableGroupName, Map extensions) { this.parent = parentStmt; this.rdbmsMgr = rdbmsMgr; // Set the namer, using any override extension, otherwise the RDBMS default String namingStrategy = rdbmsMgr.getStringProperty(RDBMSPropertyNames.PROPERTY_RDBMS_SQL_TABLE_NAMING_STRATEGY); if (extensions != null && extensions.containsKey(EXTENSION_SQL_TABLE_NAMING_STRATEGY)) { namingStrategy = (String) extensions.get(EXTENSION_SQL_TABLE_NAMING_STRATEGY); } namer = getTableNamer(namingStrategy); String tableGrpName = (tableGroupName != null ? tableGroupName : "Group0"); if (alias == null) { // No alias provided so generate one alias = rdbmsMgr.getIdentifierFactory().newTableIdentifier(namer.getAliasForTable(this, table, tableGrpName)); } this.primaryTable = new SQLTable(this, table, alias, tableGrpName); putSQLTableInGroup(primaryTable, tableGrpName, null); if (parentStmt != null) { // Use same query generator queryGenerator = parentStmt.getQueryGenerator(); } } public RDBMSStoreManager getRDBMSManager() { return rdbmsMgr; } public void setClassLoaderResolver(ClassLoaderResolver clr) { this.clr = clr; } public ClassLoaderResolver getClassLoaderResolver() { if (clr == null) { clr = rdbmsMgr.getNucleusContext().getClassLoaderResolver(null); } return clr; } public void setCandidateClassName(String name) { this.candidateClassName = name; } public String getCandidateClassName() { return candidateClassName; } public QueryGenerator getQueryGenerator() { return queryGenerator; } public void setQueryGenerator(QueryGenerator gen) { this.queryGenerator = gen; } public SQLExpressionFactory getSQLExpressionFactory() { return rdbmsMgr.getSQLExpressionFactory(); } public DatastoreAdapter getDatastoreAdapter() { return rdbmsMgr.getDatastoreAdapter(); } public SQLStatement getParentStatement() { return parent; } /** * Convenience method to return if this statement is a child (inner) statement of the supplied * statement. * @param stmt The statement that may be parent, grandparent etc of this statement * @return Whether this is a child of the supplied statement */ public boolean isChildStatementOf(SQLStatement stmt) { if (stmt == null || parent == null) { return false; } if (stmt == parent) { return true; } return isChildStatementOf(parent); } /** * Method to define an extension for this query statement allowing control over its behaviour in generating a query. * @param key Extension key * @param value Value for the key */ public void addExtension(String key, Object value) { if (key == null) { return; } invalidateStatement(); if (key.equals(EXTENSION_SQL_TABLE_NAMING_STRATEGY)) { namer = getTableNamer((String) value); return; } if (extensions == null) { extensions = new HashMap(); } extensions.put(key, value); } /** * Accessor for the value for an extension. * @param key Key for the extension * @return Value for the extension (if any) */ public Object getValueForExtension(String key) { if (extensions == null) { return extensions; } return extensions.get(key); } // --------------------------------- FROM -------------------------------------- /** * Accessor for the primary table of the statement. * @return The primary table */ public SQLTable getPrimaryTable() { return primaryTable; } /** * Accessor for the SQLTable object with the specified alias (if defined for this statement). * @param alias Alias * @return The SQLTable */ public SQLTable getTable(String alias) { if (alias.equals(primaryTable.alias.getName())) { return primaryTable; } else if (tables != null) { return tables.get(alias); } return null; } /** * Convenience method to find a registered SQLTable that is for the specified table * @param table The table * @return The SQLTable (or null if not referenced) */ public SQLTable getTableForDatastoreContainer(Table table) { for (SQLTableGroup grp : tableGroups.values()) { SQLTable[] tbls = grp.getTables(); for (int i=0;i joinIter = joins.iterator(); while (joinIter.hasNext()) { SQLJoin join = joinIter.next(); if (join.getTable().equals(sqlTbl)) { return join.getType(); } } return null; } /** * Accessor for the type of join used for the specified table. * @param sqlTbl The table to check * @return The join type, or null if not joined in this statement */ public SQLJoin getJoinForTable(SQLTable sqlTbl) { if (joins == null) { return null; } Iterator joinIter = joins.iterator(); while (joinIter.hasNext()) { SQLJoin join = joinIter.next(); if (join.getTable().equals(sqlTbl)) { return join; } } return null; } /** * Method to remove a cross join for the specified table (if joined via cross join). * Also removes the table from the list of tables. * This is called where we have bound a variable via a CROSS JOIN (in the absence of better information) * and found out later it could become an INNER JOIN. * If the supplied table is not joined via a cross join then does nothing. * @param targetSqlTbl The table to drop the cross join for * @return The removed alias */ public String removeCrossJoin(SQLTable targetSqlTbl) { if (joins == null) { return null; } Iterator joinIter = joins.iterator(); while (joinIter.hasNext()) { SQLJoin join = joinIter.next(); if (join.getTable().equals(targetSqlTbl) && join.getType() == JoinType.CROSS_JOIN) { joinIter.remove(); requiresJoinReorder = true; tables.remove(join.getTable().alias.getName()); String removedAliasName = join.getTable().alias.getName(); return removedAliasName; } } return null; } /** * Convenience method to add the SQLTable to the specified group. * If the group doesn't yet exist then it adds it. * @param sqlTbl SQLTable to add * @param groupName The group * @param joinType type of join to start this table group */ protected void putSQLTableInGroup(SQLTable sqlTbl, String groupName, JoinType joinType) { SQLTableGroup tableGrp = tableGroups.get(groupName); if (tableGrp == null) { tableGrp = new SQLTableGroup(groupName, joinType); } tableGrp.addTable(sqlTbl); tableGroups.put(groupName, tableGrp); } /** * Internal method to form a join to the specified table using the provided mappings. * @param joinType Type of join (INNER, LEFT OUTER, RIGHT OUTER, CROSS, NON-ANSI) * @param sourceTable SQLTable to join from * @param targetTable SQLTable to join to * @param joinCondition Condition for the join */ protected void addJoin(SQLJoin.JoinType joinType, SQLTable sourceTable, SQLTable targetTable, BooleanExpression joinCondition) { if (tables == null) { throw new NucleusException("tables not set in statement!"); } if (tables.containsValue(targetTable)) { // Already have a join to this table // What if we have a cross join, and want to change to inner join? NucleusLogger.DATASTORE.debug("Attempt to join to " + targetTable + " but join already exists"); return; } if (joinType == JoinType.RIGHT_OUTER_JOIN && !rdbmsMgr.getDatastoreAdapter().supportsOption(DatastoreAdapter.RIGHT_OUTER_JOIN)) { throw new NucleusUserException("RIGHT OUTER JOIN is not supported by this datastore"); } // Add the table to the referenced tables for this statement tables.put(targetTable.alias.getName(), targetTable); if (rdbmsMgr.getDatastoreAdapter().supportsOption(DatastoreAdapter.ANSI_JOIN_SYNTAX)) { // "ANSI-92" style join SQLJoin join = new SQLJoin(joinType, targetTable, sourceTable, joinCondition); if (joins == null) { joins = new ArrayList<>(); } int position = -1; if (queryGenerator != null && queryGenerator.processingOnClause()) { // We are processing an ON condition, and this JOIN has been forced, so position it dependent on what it joins from if (primaryTable == sourceTable) { if (joins.size() > 0) { position = 0; } } else { int i=1; for (SQLJoin sqlJoin : joins) { if (sqlJoin.getJoinedTable() == sourceTable) { position = i; break; } i++; } } } if (position >= 0) { joins.add(position, join); } else { joins.add(join); } } else { // "ANSI-86" style join SQLJoin join = new SQLJoin(JoinType.NON_ANSI_JOIN, targetTable, sourceTable, null); if (joins == null) { joins = new ArrayList<>(); } joins.add(join); // Specify joinCondition in the WHERE clause since not allowed in FROM clause with ANSI-86 // TODO Cater for Oracle LEFT OUTER syntax "(+)" whereAnd(joinCondition, false); } } /** * Convenience method to generate the join condition between source and target tables for the supplied mappings. * @param sourceTable Source table * @param sourceMapping Mapping in source table * @param sourceParentMapping Optional parent of this source mapping (if joining an impl of an interface) * @param targetTable Target table * @param targetMapping Mapping in target table * @param targetParentMapping Optional parent of this target mapping (if joining an impl of an interface) * @param discrimValues Optional discriminator values to further restrict * @return The join condition */ protected BooleanExpression getJoinConditionForJoin( SQLTable sourceTable, JavaTypeMapping sourceMapping, JavaTypeMapping sourceParentMapping, SQLTable targetTable, JavaTypeMapping targetMapping, JavaTypeMapping targetParentMapping, Object[] discrimValues) { BooleanExpression joinCondition = null; if (sourceMapping != null && targetMapping != null) { // Join condition(s) - INNER, LEFT OUTER, RIGHT OUTER joins if (sourceMapping.getNumberOfDatastoreMappings() != targetMapping.getNumberOfDatastoreMappings()) { throw new NucleusException("Cannot join from " + sourceMapping + " to " + targetMapping + " since they have different numbers of datastore columns!"); } SQLExpressionFactory factory = rdbmsMgr.getSQLExpressionFactory(); // Set joinCondition to be "source = target" SQLExpression sourceExpr = null; if (sourceParentMapping == null) { sourceExpr = factory.newExpression(this, sourceTable != null ? sourceTable : primaryTable, sourceMapping); } else { sourceExpr = factory.newExpression(this, sourceTable != null ? sourceTable : primaryTable, sourceMapping, sourceParentMapping); } SQLExpression targetExpr = null; if (targetParentMapping == null) { targetExpr = factory.newExpression(this, targetTable, targetMapping); } else { targetExpr = factory.newExpression(this, targetTable, targetMapping, targetParentMapping); } joinCondition = sourceExpr.eq(targetExpr); // Process discriminator for any additional conditions JavaTypeMapping discrimMapping = targetTable.getTable().getDiscriminatorMapping(false); if (discrimMapping != null && discrimValues != null) { SQLExpression discrimExpr = factory.newExpression(this, targetTable, discrimMapping); BooleanExpression discrimCondition = null; for (int i=0;i-->


© 2015 - 2024 Weber Informatics LLC | Privacy Policy