/**********************************************************************
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.jpox.store.rdbms.query;
import java.util.Map;
import org.jpox.ClassLoaderResolver;
import org.jpox.ObjectManager;
import org.jpox.exceptions.JPOXException;
import org.jpox.exceptions.JPOXUserException;
import org.jpox.jdo.exceptions.ClassNotPersistenceCapableException;
import org.jpox.metadata.AbstractClassMetaData;
import org.jpox.metadata.IdentityType;
import org.jpox.store.mapped.DatastoreAdapter;
import org.jpox.store.mapped.DatastoreClass;
import org.jpox.store.mapped.MappedStoreManager;
import org.jpox.store.mapped.mapping.DatastoreMapping;
import org.jpox.store.mapped.mapping.PersistenceCapableMapping;
import org.jpox.store.query.AbstractSQLQuery;
import org.jpox.store.query.Query;
import org.jpox.store.query.QueryCompiler;
import org.jpox.store.rdbms.RDBMSManager;
import org.jpox.util.Imports;
import org.jpox.util.Localiser;
import org.jpox.util.StringUtils;
/**
* Compiler for SQL-based queries.
* @version $Revision$
*/
public class SQLQueryCompiler extends QueryCompiler
{
/** Localiser for messages. */
protected static final Localiser LOCALISER_RDBMS=Localiser.getInstance("org.jpox.store.rdbms.Localisation",
RDBMSManager.class.getClassLoader());
/**
* Constructor for a compiler of SQL queries.
* @param query The query
* @param imports Import specification
* @param parameters Map of parameter values keyed by name
*/
public SQLQueryCompiler(AbstractSQLQuery query, Imports imports, Map parameters)
{
super(query, imports, parameters);
}
/**
* Method to compile the query.
* Returns the generated SQL when using "COMPILE_SYNTAX" and "COMPILE_EXECUTION".
* @param type Type of compilation
* @return compilation artifact (if any)
*/
public Object compile(int type)
{
switch (type)
{
case COMPILE_SYNTAX :
return generateQueryStatement();
case COMPILE_EXECUTION :
return generateQueryStatement();
case COMPILE_EXPLICIT_PARAMETERS :
throw new JPOXException(
"SQL Query Compiler doesnt allow input of explicit parameters so cant compile them");
case COMPILE_EXPLICIT_VARIABLES :
throw new JPOXException(
"SQL Query Compiler doesnt allow input of explicit variables so cant compile them");
default :
return super.compile(type);
}
}
/**
* Method to perform any necessary pre-processing on the users query statement
* before we execute it. SQL queries are not modified in any way, as per JDO2 spec [14.7].
*/
protected String generateQueryStatement()
{
// We're returning the users SQL direct with no substitution of params etc
String compiledSQL = ((AbstractSQLQuery)query).getInputSQL();
if (candidateClass != null && query.getType() == Query.SELECT)
{
// Perform any sanity checking of input for SELECT queries
ObjectManager om = query.getObjectManager();
MappedStoreManager storeMgr = (MappedStoreManager)om.getStoreManager();
ClassLoaderResolver clr = om.getClassLoaderResolver();
AbstractClassMetaData cmd = om.getMetaDataManager().getMetaDataForClass(candidateClass, clr);
if (cmd == null)
{
throw new ClassNotPersistenceCapableException(candidateClass.getName());
}
if (cmd.getPersistenceCapableSuperclass() != null)
{
// throw new PersistentSuperclassNotAllowedException(candidateClass.getName());
}
if (query.getResultClass() == null)
{
// Check the presence of the required columns (id, version, discriminator) in the candidate class
String selections = compiledSQL.trim().substring(7); // Skip "SELECT "
int fromStart = selections.indexOf("FROM");
if (fromStart == -1)
{
fromStart = selections.indexOf("from");
}
selections = selections.substring(0, fromStart).trim();
String[] selectedColumns = StringUtils.split(selections, ",");
if (selectedColumns == null || selectedColumns.length == 0)
{
throw new JPOXUserException(LOCALISER_RDBMS.msg("059003", compiledSQL));
}
if (selectedColumns.length == 1 && selectedColumns[0].trim().equals("*"))
{
// SQL Query using * so just return since all possible columns will be selected
return compiledSQL;
}
// Generate id column field information for later checking the id is present
DatastoreClass table = storeMgr.getDatastoreClass(candidateClass.getName(), clr);
PersistenceCapableMapping idMapping = (PersistenceCapableMapping)table.getIDMapping();
String[] idColNames = new String[idMapping.getNumberOfDatastoreFields()];
boolean[] idColMissing = new boolean[idMapping.getNumberOfDatastoreFields()];
for (int i=0;i<idMapping.getNumberOfDatastoreFields();i++)
{
DatastoreMapping m = idMapping.getDataStoreMapping(i);
idColNames[i] = m.getDatastoreField().getIdentifier().toString();
idColMissing[i] = true;
}
// Generate discriminator/version information for later checking they are present
String discriminatorColName = table.getDiscriminatorMapping(false) != null ?
table.getDiscriminatorMapping(false).getDataStoreMapping(0).getDatastoreField().getIdentifier().toString() : null;
String versionColName = table.getVersionMapping(false) != null ?
table.getVersionMapping(false).getDataStoreMapping(0).getDatastoreField().getIdentifier().toString() : null;
boolean discrimMissing = (discriminatorColName != null);
boolean versionMissing = true;
if (versionColName == null)
{
versionMissing = false;
}
// Go through the selected fields and check the existence of id, version, discriminator cols
DatastoreAdapter dba = storeMgr.getDatastoreAdapter();
final AbstractClassMetaData candidateCmd = om.getMetaDataManager().getMetaDataForClass(candidateClass, clr);
for (int i = 0; i < selectedColumns.length; i++)
{
String colName = selectedColumns[i].trim();
if (colName.indexOf(" AS ") > 0)
{
// Allow for user specification of "XX.YY AS ZZ"
colName = colName.substring(colName.indexOf(" AS ")+4).trim();
}
else if (colName.indexOf(" as ") > 0)
{
// Allow for user specification of "XX.YY as ZZ"
colName = colName.substring(colName.indexOf(" as ")+4).trim();
}
if (candidateCmd.getIdentityType() == IdentityType.DATASTORE)
{
// Check for existence of id column, allowing for any RDBMS using quoted identifiers
if (SQLQuery.columnNamesAreTheSame(dba, idColNames[0], colName))
{
idColMissing[0] = false;
}
}
else if (candidateCmd.getIdentityType() == IdentityType.APPLICATION)
{
for (int j=0; j<idColNames.length; j++)
{
// Check for existence of id column, allowing for any RDBMS using quoted identifiers
if (SQLQuery.columnNamesAreTheSame(dba, idColNames[j], colName))
{
idColMissing[j] = false;
}
}
}
if (discrimMissing && SQLQuery.columnNamesAreTheSame(dba, discriminatorColName, colName))
{
discrimMissing = false;
}
else if (versionMissing && SQLQuery.columnNamesAreTheSame(dba, versionColName, colName))
{
versionMissing = false;
}
}
if (discrimMissing)
{
throw new JPOXUserException(LOCALISER_RDBMS.msg("059014",
compiledSQL, candidateClass.getName(), discriminatorColName));
}
if (versionMissing)
{
throw new JPOXUserException(LOCALISER_RDBMS.msg("059015",
compiledSQL, candidateClass.getName(), versionColName));
}
for (int i = 0; i < idColMissing.length; i++)
{
if (idColMissing[i])
{
throw new JPOXUserException(LOCALISER_RDBMS.msg("059013",
compiledSQL, candidateClass.getName(), idColNames[i]));
}
}
}
}
return compiledSQL;
}
}