/**********************************************************************
Copyright (c) 2002 Mike Martin (TJDO) 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:
2002 Kelly Grizzle (TJDO)
2002 Christopher Walk (TJDO)
2003 Andy Jefferson - commented and javadocs
2003 Andy Jefferson - added localiser
2003 Andy Jefferson - added sequence methods
2004 Erik Bengtson - added auto increment
2004 Erik Bengtson - added query operators, sql expressions
2004 Andy Jefferson - removed IndexMapping/OptimisticMapping
...
**********************************************************************/
package org.jpox.store.rdbms.adapter;
import java.math.BigInteger;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import javax.sql.DataSource;
import org.jpox.ClassLoaderResolver;
import org.jpox.UserTransaction;
import org.jpox.exceptions.JPOXDataStoreException;
import org.jpox.plugin.PluginManager;
import org.jpox.store.exceptions.UnsupportedDataTypeException;
import org.jpox.store.mapped.AbstractDatastoreAdapter;
import org.jpox.store.mapped.DatastoreContainerObject;
import org.jpox.store.mapped.DatastoreIdentifier;
import org.jpox.store.mapped.IdentifierFactory;
import org.jpox.store.mapped.expression.BooleanExpression;
import org.jpox.store.mapped.expression.CharacterExpression;
import org.jpox.store.mapped.expression.CharacterLiteral;
import org.jpox.store.mapped.expression.ConcatOperatorExpression;
import org.jpox.store.mapped.expression.Literal;
import org.jpox.store.mapped.expression.LogicSetExpression;
import org.jpox.store.mapped.expression.NumericExpression;
import org.jpox.store.mapped.expression.QueryExpression;
import org.jpox.store.mapped.expression.ScalarExpression;
import org.jpox.store.mapped.expression.SqlTemporalExpression;
import org.jpox.store.mapped.expression.StringExpression;
import org.jpox.store.mapped.expression.StringLiteral;
import org.jpox.store.mapped.expression.SubstringExpression;
import org.jpox.store.mapped.expression.TableExprAsSubjoins;
import org.jpox.store.mapped.expression.ScalarExpression.IllegalArgumentTypeException;
import org.jpox.store.mapped.mapping.JavaTypeMapping;
import org.jpox.store.mapped.mapping.MappingManager;
import org.jpox.store.rdbms.Column;
import org.jpox.store.rdbms.ConnectionProvider;
import org.jpox.store.rdbms.JDBCUtils;
import org.jpox.store.rdbms.RDBMSManager;
import org.jpox.store.rdbms.columninfo.ColumnInfo;
import org.jpox.store.rdbms.key.CandidateKey;
import org.jpox.store.rdbms.key.ForeignKey;
import org.jpox.store.rdbms.key.Index;
import org.jpox.store.rdbms.key.PrimaryKey;
import org.jpox.store.rdbms.mapping.RDBMSMappingManager;
import org.jpox.store.rdbms.query.QueryStatement;
import org.jpox.store.rdbms.table.Table;
import org.jpox.store.rdbms.table.TableImpl;
import org.jpox.store.rdbms.table.ViewImpl;
import org.jpox.store.rdbms.typeinfo.ExportedKeyInfo;
import org.jpox.store.rdbms.typeinfo.ForeignKeyInfo;
import org.jpox.store.rdbms.typeinfo.TypeInfo;
import org.jpox.transaction.TransactionUtils;
import org.jpox.util.JPOXLogger;
import org.jpox.util.Localiser;
import org.jpox.util.StringUtils;
/**
* Provides methods for adapting SQL language elements to a specific vendor's
* database. A database adapter is primarily used to map generic JDBC data
* types and SQL identifiers to specific types/identifiers suitable for the
* database in use.
*
* <p>Each database adapter corresponds to a particular combination of database,
* database version, driver, and driver version, as provided by the driver's
* own metadata. Database adapters cannot be constructed directly, but must be
* obtained using the {@link org.jpox.store.rdbms.adapter.RDBMSAdapterFactory} class.</p>
*
* @see RDBMSAdapterFactory
* @see java.sql.DatabaseMetaData
* @version $Revision: 1.141 $
*/
public class DatabaseAdapter extends AbstractDatastoreAdapter implements RDBMSAdapter
{
protected static final Localiser LOCALISER=Localiser.getInstance("org.jpox.store.rdbms.Localisation",
RDBMSManager.class.getClassLoader());
/** the JDBC driver name **/
protected String driverName;
/** the JDBC driver version **/
protected String driverVersion;
/** The major version number of the underlying driver. */
protected int driverMajorVersion;
/** The minor version number of the underlying driver. */
protected int driverMinorVersion;
/** The maximum length to be used for a table name. */
protected int maxTableNameLength;
/** The maximum length to be used for a table constraint name. */
protected int maxConstraintNameLength;
/** The maximum length to be used for an index name. */
protected int maxIndexNameLength;
/** The maximum length to be used for a column name. */
protected int maxColumnNameLength;
/** <tt>true</tt> if the database supports catalogs in table definition */
protected boolean supportsCatalogsInTableDefinitions;
/** <tt>true</tt> if the database supports schemas in table definition */
protected boolean supportsSchemasInTableDefinitions;
/** The String used to separate catalog and table name. */
protected String catalogSeparator;
/** JDBCTypeInfo, keyed by the type number. Allowing for overriding by RDBMS. */
protected final HashMap typesByTypeNumber = new HashMap();
/** Whether the adapter supports batching of statements. */
protected boolean supportsStatementBatching = false;
protected boolean storesLowerCaseIdentifiers = false;
protected boolean storesMixedCaseIdentifiers = false;
protected boolean storesUpperCaseIdentifiers = false;
protected boolean storesLowerCaseQuotedIdentifiers = false;
protected boolean storesMixedCaseQuotedIdentifiers = false;
protected boolean storesUpperCaseQuotedIdentifiers = false;
protected boolean storesMixedCaseSensitiveIdentifiers = false;
protected boolean storesMixedCaseQuotedSensitiveIdentifiers = false;
/**
* Constructs a database adapter based on the given JDBC metadata.
* @param metadata the database metadata.
*/
protected DatabaseAdapter(DatabaseMetaData metadata)
{
super();
reservedKeywords.addAll(parseKeywordList(SQLConstants.SQL92_RESERVED_WORDS));
reservedKeywords.addAll(parseKeywordList(SQLConstants.SQL99_RESERVED_WORDS));
reservedKeywords.addAll(parseKeywordList(SQLConstants.SQL2003_RESERVED_WORDS));
reservedKeywords.addAll(parseKeywordList(SQLConstants.NONRESERVED_WORDS));
try
{
reservedKeywords.addAll(parseKeywordList(metadata.getSQLKeywords()));
driverMinorVersion = metadata.getDriverMinorVersion();
driverMajorVersion = metadata.getDriverMajorVersion();
driverName = metadata.getDriverName();
driverVersion = metadata.getDriverVersion();
datastoreProductName = metadata.getDatabaseProductName();
datastoreProductVersion = metadata.getDatabaseProductVersion();
// Try to convert the "version" string into a W.X.Y.Z version string
StringBuffer strippedProductVersion=new StringBuffer();
char previousChar = ' ';
for (int i=0; i<datastoreProductVersion.length(); ++i)
{
char c = datastoreProductVersion.charAt(i);
if (Character.isDigit(c) || c == '.')
{
// Only update the stripped version when we have "X."
if (previousChar != ' ')
{
if (strippedProductVersion.length() == 0)
{
strippedProductVersion.append(previousChar);
}
strippedProductVersion.append(c);
}
previousChar = c;
}
else
{
previousChar = ' ';
}
}
try
{
Class mdc = metadata.getClass();
datastoreMajorVersion = ((Integer)mdc.getMethod("getDatabaseMajorVersion", null).invoke(metadata, null)).intValue();
datastoreMinorVersion = ((Integer)mdc.getMethod("getDatabaseMinorVersion", null).invoke(metadata, null)).intValue();
boolean noDBVersion = false;
if (datastoreMajorVersion <= 0 && datastoreMinorVersion <= 0)
{
// Check for the crap that they package with Websphere returning major/minor as 0.0
noDBVersion = true;
}
// Retrieve the Revision version if it is accessible
StringTokenizer parts = new StringTokenizer(strippedProductVersion.toString(), ".");
if (parts.hasMoreTokens())
{
// Major version
if (noDBVersion)
{
try
{
datastoreMajorVersion = Integer.parseInt(parts.nextToken());
}
catch (Exception e)
{
datastoreMajorVersion = -1; //unknown
}
}
else
{
// already have it, so ignore this
parts.nextToken();
}
}
if (parts.hasMoreTokens())
{
// Minor Version
if (noDBVersion)
{
try
{
datastoreMinorVersion = Integer.parseInt(parts.nextToken());
}
catch (Exception e)
{
datastoreMajorVersion = -1; //unknown
}
}
else
{
// already have it, so ignore this
parts.nextToken();
}
}
if (parts.hasMoreTokens())
{
// Revision Version
try
{
datastoreRevisionVersion = Integer.parseInt(parts.nextToken());
}
catch (Exception e)
{
datastoreRevisionVersion = -1; //unknown
}
}
}
catch (Throwable t)
{
/*
* The driver doesn't support JDBC 3. Try to parse major and
* minor version numbers out of the product version string.
* We do this by stripping out everything but digits and periods
* and hoping we get something that looks like <major>.<minor>.
*/
StringTokenizer parts = new StringTokenizer(strippedProductVersion.toString(), ".");
if (parts.hasMoreTokens())
{
try
{
datastoreMajorVersion = Integer.parseInt(parts.nextToken());
}
catch (Exception e)
{
datastoreMajorVersion = -1; //unknown
}
}
if (parts.hasMoreTokens())
{
try
{
datastoreMinorVersion = Integer.parseInt(parts.nextToken());
}
catch (Exception e)
{
datastoreMajorVersion = -1; //unknown
}
}
if (parts.hasMoreTokens())
{
try
{
datastoreRevisionVersion = Integer.parseInt(parts.nextToken());
}
catch (Exception e)
{
datastoreRevisionVersion = -1; //unknown
}
}
}
// Extract attributes of the Database adapter
maxTableNameLength = metadata.getMaxTableNameLength();
maxConstraintNameLength = metadata.getMaxTableNameLength();
maxIndexNameLength = metadata.getMaxTableNameLength();
maxColumnNameLength = metadata.getMaxColumnNameLength();
supportsCatalogsInTableDefinitions = metadata.supportsCatalogsInTableDefinitions();
supportsSchemasInTableDefinitions = metadata.supportsSchemasInTableDefinitions();
supportsStatementBatching = metadata.supportsBatchUpdates();
// Save the identifier cases available
storesLowerCaseIdentifiers = metadata.storesLowerCaseIdentifiers();
storesMixedCaseIdentifiers = metadata.storesMixedCaseIdentifiers();
storesUpperCaseIdentifiers = metadata.storesUpperCaseIdentifiers();
storesLowerCaseQuotedIdentifiers = metadata.storesLowerCaseQuotedIdentifiers();
storesMixedCaseQuotedIdentifiers = metadata.storesMixedCaseQuotedIdentifiers();
storesUpperCaseQuotedIdentifiers = metadata.storesUpperCaseQuotedIdentifiers();
storesMixedCaseSensitiveIdentifiers = metadata.supportsMixedCaseIdentifiers();
storesMixedCaseQuotedSensitiveIdentifiers = metadata.supportsMixedCaseQuotedIdentifiers();
// Retrieve the catalog separator string (default = ".")
catalogSeparator = metadata.getCatalogSeparator();
catalogSeparator =
((catalogSeparator == null) || (catalogSeparator.trim().length() < 1)) ? "." : catalogSeparator;
// Retrieve the identifier quote string (default = "")
identifierQuoteString = metadata.getIdentifierQuoteString();
identifierQuoteString =
((null == identifierQuoteString) || (identifierQuoteString.trim().length() < 1)) ?
"\"" : identifierQuoteString;
// Create TypeInfo objects for all of the data types and index them in a HashMap by their JDBC type number.
createTypeInfo(metadata);
}
catch (SQLException e)
{
throw new JPOXDataStoreException(LOCALISER.msg("051004"), e);
}
}
/**
* Convenience container for JDBC type information taken from the JDBC driver.
*/
public class JDBCTypeInfo
{
short jdbcType;
HashMap typeInfoByTypeName = new HashMap();
TypeInfo defaultTypeInfo;
public JDBCTypeInfo(short jdbcType)
{
this.jdbcType = jdbcType;
}
public void addTypeInfo(TypeInfo ti)
{
typeInfoByTypeName.put(ti.typeName, ti);
if (typeInfoByTypeName.size() == 1)
{
defaultTypeInfo = ti;
}
}
public void setDefault(TypeInfo ti)
{
defaultTypeInfo = ti;
}
public short getJdbcType()
{
return jdbcType;
}
public TypeInfo getDefault()
{
return defaultTypeInfo;
}
public String[] getTypeNames()
{
if (typeInfoByTypeName.size() == 0)
{
return null;
}
return (String[])typeInfoByTypeName.keySet().toArray(new String[typeInfoByTypeName.size()]);
}
}
/**
* Convenience method to log the configuration of this datastore adapter.
*/
public void logConfiguration()
{
JPOXLogger.DATASTORE.debug("Datastore Adapter : " + this.getClass().getName());
JPOXLogger.DATASTORE.debug("Datastore Details : name=\"" + datastoreProductName + "\" version=\"" + datastoreProductVersion +
"\" (major=" + datastoreMajorVersion + ", minor=" + datastoreMinorVersion + ", revision=" + datastoreRevisionVersion + ")");
JPOXLogger.DATASTORE.debug("Datastore Driver : name=\"" + driverName + "\" version=\"" + driverVersion +
"\" (major=" + driverMajorVersion + ", minor=" + driverMinorVersion + ")");
JPOXLogger.DATASTORE.debug("Supported Identifier Cases : " +
(storesLowerCaseIdentifiers ? "lowercase " : "") +
(storesLowerCaseQuotedIdentifiers ? "\"lowercase\" " : "") +
(storesMixedCaseIdentifiers ? "MixedCase " : "") +
(storesMixedCaseQuotedIdentifiers ? "\"MixedCase\" " : "") +
(storesUpperCaseIdentifiers ? "UPPERCASE " : "") +
(storesUpperCaseQuotedIdentifiers ? "\"UPPERCASE\" " : "") +
(storesMixedCaseSensitiveIdentifiers ? "MixedCase-Sensitive " : "") +
(storesMixedCaseQuotedSensitiveIdentifiers ? "\"MixedCase-Sensitive\" " : ""));
JPOXLogger.DATASTORE.debug("Supported Identifier Lengths (max) :" +
" Table=" + getMaxTableNameLength() +
" Column=" + getMaxColumnNameLength() +
" Constraint=" + getMaxConstraintNameLength() +
" Index=" + getMaxIndexNameLength() +
" Delimiter=" + getIdentifierQuoteString());
StringBuffer typeStr = new StringBuffer();
Collection types = typesByTypeNumber.values();
Iterator typesIter = types.iterator();
while (typesIter.hasNext())
{
JDBCTypeInfo info = (JDBCTypeInfo)typesIter.next();
typeStr.append(JDBCUtils.getNameForJDBCType(info.defaultTypeInfo.dataType));
if (typesIter.hasNext())
{
typeStr.append(", ");
}
}
JPOXLogger.DATASTORE.debug("Supported JDBC Types : " + typeStr);
JPOXLogger.DATASTORE.debug("Support for Identifiers in DDL : catalog=" + supportsCatalogsInTableDefinitions +
" schema=" + supportsSchemasInTableDefinitions);
JPOXLogger.DATASTORE.debug("Support Statement Batching : " + (supportsStatementBatching() ? "yes" : "no"));
}
/**
* Creates the auxiliary functions/procedures in the schema
* @param conn the connection to the datastore
*/
public void initialiseDatastore(Object conn)
{
}
/**
* Load the datastore mapping declared as Plug-in
* @param mgr the PluginManager
* @param clr the ClassLoaderResolver
*/
public void loadDatastoreMapping(PluginManager mgr, ClassLoaderResolver clr)
{
// Load the datastore mappings
super.loadDatastoreMapping(mgr, clr);
// Deregister all JDBC types that the adapter doesnt have a type for
int[] jdbcTypes = JDBCUtils.getJDBCTypes();
for (int i=0;i<jdbcTypes.length;i++)
{
if (typesByTypeNumber.get(new Integer(jdbcTypes[i])) == null)
{
// JDBC type not supported by adapter so deregister it
// This means that we dont need to add "excludes" definitions to plugin.xml
((RDBMSMappingManager)mappingManager).deregisterDatastoreMappingsForJDBCType(JDBCUtils.getNameForJDBCType(jdbcTypes[i]));
}
}
}
/**
* Accessor for the JDBC driver major version
* @return The driver major version
*/
public int getDriverMajorVersion()
{
return driverMajorVersion;
}
/**
* Accessor for the JDBC driver minor version
* @return The driver minor version
*/
public int getDriverMinorVersion()
{
return driverMinorVersion;
}
/**
* Accessor for the maximum table name length permitted on this
* datastore.
* @return Max table name length
**/
public int getMaxTableNameLength()
{
return maxTableNameLength;
}
/**
* Accessor for the maximum foreign keys by table permitted for this datastore.
* @return Max number of FKs for a table
**/
public int getMaxForeignKeys()
{
// TODO This is arbitrary. Should be relative to the RDBMS in use
return 9999;
}
/**
* Accessor for the maximum indexes by schema permitted for this datastore.
* @return Max number of indexes for a table
**/
public int getMaxIndexes()
{
// TODO This is arbitrary. Should be relative to the RDBMS in use
return 9999;
}
/**
* Accessor for the maximum constraint name length permitted on this
* datastore.
* @return Max constraint name length
**/
public int getMaxConstraintNameLength()
{
return maxConstraintNameLength;
}
/**
* Accessor for the maximum index name length permitted on this datastore.
* @return Max index name length
**/
public int getMaxIndexNameLength()
{
return maxIndexNameLength;
}
/**
* Accessor for the maximum column name length permitted on this datastore.
* @return Max column name length
**/
public int getMaxColumnNameLength()
{
return maxColumnNameLength;
}
/**
* Iterator for the TypeInfo objects constructed from the method DataBaseMetaData.getTypeInfo
* @return an Iterator with org.jpox.store.rdbms.typeinfo.TypeInfo
*/
public Iterator iteratorTypeInfo()
{
return typesByTypeNumber.values().iterator();
}
/**
* Iterator for the reserved words constructed from the method
* DataBaseMetaData.getSQLKeywords + standard SQL reserved words
* @return an Iterator with a set of reserved words
*/
public Iterator iteratorReservedWords()
{
return reservedKeywords.iterator();
}
/**
* Creates TypeInfo objects for all of the data types and indexes them
* in the typesByTypeNumber map by their JDBC data type number.
* @param metadata The MetaData for this datastore
* @throws SQLException Thrown if error occurs in the creation.
*/
private void createTypeInfo(DatabaseMetaData metadata)
throws SQLException
{
ResultSet rs = metadata.getTypeInfo();
try
{
while (rs.next())
{
TypeInfo ti = newTypeInfo(rs);
if (ti != null)
{
Integer key = new Integer(ti.dataType);
JDBCTypeInfo jti = (JDBCTypeInfo)typesByTypeNumber.get(key);
if (jti == null)
{
jti = new JDBCTypeInfo(ti.dataType);
typesByTypeNumber.put(key, jti);
}
jti.addTypeInfo(ti);
}
}
}
finally
{
rs.close();
}
}
/**
* Convenience method for use by overriding adapters to add their own fake types in.
* @param jdbcType The JDBC type
* @param ti The type info to use
*/
protected void addTypeInfo(short jdbcType, TypeInfo ti, boolean addIfNotPresent)
{
Integer key = new Integer(jdbcType);
JDBCTypeInfo jti = (JDBCTypeInfo)typesByTypeNumber.get(key);
if (jti != null && !addIfNotPresent)
{
// Adapter has this JDBC type so ignore
return;
}
if (jti == null)
{
jti = new JDBCTypeInfo(jdbcType);
typesByTypeNumber.put(key, jti);
}
jti.addTypeInfo(ti);
}
/**
* Convenience method to set the default database type name for a particular JDBC type.
* Sets the default to this if the type is found.
* @param jdbcType The JDBC type
* @param defaultTypeName The default database type name
*/
protected void setDefaultTypeInfoForJDBCType(int jdbcType, String defaultTypeName)
{
JDBCTypeInfo jti = (JDBCTypeInfo)typesByTypeNumber.get(new Integer(jdbcType));
if (jti != null)
{
Iterator iter = jti.typeInfoByTypeName.entrySet().iterator();
while (iter.hasNext())
{
Map.Entry entry = (Map.Entry)iter.next();
if (((String)entry.getKey()).equalsIgnoreCase(defaultTypeName))
{
jti.defaultTypeInfo = (TypeInfo)entry.getValue();
}
}
}
}
/**
* A factory for TypeInfo objects. This method should always be used
* instead of directly constructing TypeInfo objects in order to give the
* DatabaseAdapter an opportunity to modify and/or correct the metadata
* obtained from the JDBC driver.
*
* The type information object is constructed from the current row of the
* given result set. The {@link ResultSet} object passed must have been
* obtained from a call to DatabaseMetaData.getTypeInfo().
*
* <p>The constructor only retrieves the values from the current row; the
* caller is required to advance to the next row with {@link ResultSet#next}
*
* @param rs The result set returned from DatabaseMetaData.getTypeInfo().
*
* @return
* A TypeInfo object constructed from the current result set row, or
* <code>null</code> if the type indicated by this row should be
* excluded from use.
*/
protected TypeInfo newTypeInfo(ResultSet rs)
{
return new TypeInfo(rs);
}
/**
* A factory for ColumnInfo objects. This method should always be used
* instead of directly constructing ColumnInfo objects in order to give the
* DatabaseAdapter an opportunity to modify and/or correct the metadata
* obtained from the JDBC driver.
*
* The column information object is constructed from the current row of the
* given result set. The {@link ResultSet} object passed must have been
* obtained from a call to DatabaseMetaData.getColumns().
*
* <p>The constructor only retrieves the values from the current row; the
* caller is required to advance to the next row with {@link ResultSet#next}
*
* @param rs The result set returned from DatabaseMetaData.getColumns().
* @return The column info
*/
public ColumnInfo newColumnInfo(ResultSet rs)
{
return new ColumnInfo(rs);
}
/**
* A factory for ForeignKeyInfo objects. This method should always be used
* instead of directly constructing ForeignKeyInfo objects in order to give
* the DatabaseAdapter an opportunity to modify and/or correct the metadata
* obtained from the JDBC driver.
*
* The column information object is constructed from the current row of the
* given result set. The {@link ResultSet} object passed must have been
* obtained from a call to DatabaseMetaData.getImportedKeys() or
* DatabaseMetaData.getExportedKeys().
*
* <p>The constructor only retrieves the values from the current row; the
* caller is required to advance to the next row with {@link ResultSet#next}
*
* @param rs The result set returned from DatabaseMetaData.get??portedKeys()
* @return The foreign key info
*/
public ForeignKeyInfo newForeignKeyInfo(ResultSet rs)
{
return new ForeignKeyInfo(rs);
}
/**
* A factory for ForeignKeyInfo objects. This method should always be used
* instead of directly constructing ForeignKeyInfo objects in order to give
* the DatabaseAdapter an opportunity to modify and/or correct the metadata
* obtained from the JDBC driver.
*
* The column information object is constructed from the current row of the
* given result set. The {@link ResultSet} object passed must have been
* obtained from a call to DatabaseMetaData.getExportedKeys().
*
* <p>The constructor only retrieves the values from the current row; the
* caller is required to advance to the next row with {@link ResultSet#next}
*
* @param rs The result set returned from DatabaseMetaData.getExportedKeys()
* @return The ExportedKeyInfo
*/
public ExportedKeyInfo newExportedKeyInfo(ResultSet rs)
{
return new ExportedKeyInfo(rs);
}
/**
* Utility method to parse a list of keywords and split them out into
* words.
* @param list The comma-separated list of keywords.
* @return Set of keywords.
**/
protected Set parseKeywordList(String list)
{
StringTokenizer tokens = new StringTokenizer(list, ",");
HashSet words = new HashSet();
while (tokens.hasMoreTokens())
{
words.add(tokens.nextToken().trim().toUpperCase());
}
return words;
}
/**
* Tests if a given string is a SQL key word.
* <p>
* The list of key words tested against is defined to contain all SQL/92 key
* words, plus any additional key words reported by the JDBC driver for this
* adapter via <code>DatabaseMetaData.getSQLKeywords()</code>.
* <p>
* In general, use of a SQL key word as an identifier should be avoided.
* SQL/92 key words are divided into reserved and non-reserved words. If a
* reserved word is used as an identifier it must be quoted with double
* quotes. Strictly speaking, the same is not true of non-reserved words.
* However, as C.J. Date writes in <u>A Guide To The SQL Standard </u>:
* <blockquote>The rule by which it is determined within the standard that
* one key word needs to be reserved while another need not is not clear to
* this writer. In practice, it is probably wise to treat all key words as
* reserved. </blockquote>
* @param word The word to test.
* @return <code>true</code> if <var>word </var> is a SQL key word for
* this DBMS. The comparison is case-insensitive.
* @see SQLConstants
*/
public boolean isSQLKeyword(String word)
{
return isReservedKeyword(word.toUpperCase());
}
/**
* Returns type information for the database type that best implements the given JDBC type.
* TODO The JDBC adapter often provides several database types and we have them present here
* and so could return them all as TypeInfo[].
* @param dataType JDBC type number of the data type.
* @return type information for the best matching type.
* @throws UnsupportedDataTypeException Thrown if the specified datatype is not supported.
*/
public TypeInfo getTypeInfo(int dataType)
throws UnsupportedDataTypeException
{
JDBCTypeInfo jti = (JDBCTypeInfo)typesByTypeNumber.get(new Integer(dataType));
if (jti == null)
{
throw new UnsupportedDataTypeException(LOCALISER.msg("051005",
JDBCUtils.getNameForJDBCType(dataType)));
}
return jti.defaultTypeInfo;
}
/**
* Returns the precision value to be used when creating string columns of
* "unlimited" length. Usually, if this value is needed it is provided in
* the database metadata ({@link org.jpox.store.rdbms.typeinfo.TypeInfo#precision}). However, for some
* types in some databases the value must be computed specially.
*
* @param typeInfo the typeInfo object for which the precision value is needed.
* @return the precision value to be used when creating the column, or -1 if no value should be used.
*/
public int getUnlimitedLengthPrecisionValue(TypeInfo typeInfo)
{
if (typeInfo.createParams != null && typeInfo.createParams.length() > 0)
{
return typeInfo.precision;
}
else
{
return -1;
}
}
/**
* Method to return whether the specified JDBC type is valid for use in a PrimaryKey.
* @param datatype The JDBC type.
* @return Whether it is valid for use in the PK
*/
public boolean isValidPrimaryKeyType(int datatype)
{
// This is temporary since some RDBMS allow indexing of Blob/Clob/LongVarBinary
// TODO Transfer to individual adapters
if (datatype == Types.BLOB || datatype == Types.CLOB || datatype == Types.LONGVARBINARY)
{
return false;
}
return true;
}
/**
* Some databases, Oracle, treats an empty string (0 length) equals null
* @return whether the database treats an empty string as null
*/
public boolean isNullEqualsEmptyStrings()
{
return false;
}
/**
* Accessor for whether setting a BLOB value allows use of PreparedStatement.setString()
* @return Whether setString is allowed.
*/
public boolean supportsSettingBlobUsingSetString()
{
// Default to no, so we use setBytes or similar
return false;
}
/**
* Accessor for whether setting a CLOB value allows use of PreparedStatement.setString()
* @return Whether setString is allowed.
*/
public boolean supportsSettingClobUsingSetString()
{
// Default to no, so we use setBytes or similar
return false;
}
/**
* Some databases, Oracle, treats an empty string (0 length) equals null
* @return returns a surrogate to replace the empty string in the database
* otherwise it would be treated as null
*/
public String getSurrogateForEmptyStrings()
{
return null;
}
/**
* Some databases store character strings in CHAR(XX) columns and when read back in have been padded
* with spaces.
* @return whether this database pads char(XX) columns with spaces
*/
public boolean getCharColumnsPaddedWithSpaces()
{
return false;
}
/**
* Whether the database server supports persist of an unassigned character ("0x0").
* If not, any unassigned character will be replaced by " " (space) on persist.
* @return Whether we support persisting an unassigned char.
*/
public boolean getSupportsPersistOfUnassignedChar()
{
return true;
}
/**
* Accessor for a new Mapping Manager
* @return The mapping manager
*/
protected MappingManager getNewMappingManager()
{
return new RDBMSMappingManager();
}
/**
* Accessor for whether the adapter supports the transaction isolation level.
* @param isolationLevel the isolation level
* @return Whether the transaction isolation level setting is supported.
*/
public boolean supportsTransactionIsolationLevel(int isolationLevel)
{
return true;
}
/**
* Accessor for the transaction isolation level to use during schema creation.
* @return The transaction isolation level for schema generation process
*/
public int getTransactionIsolationForSchemaCreation()
{
return Connection.TRANSACTION_SERIALIZABLE;
}
/**
* Accessor for the "required" transaction isolation level if it has to be a certain value
* for this adapter.
* @return Transaction isolation level (-1 implies no restriction)
*/
public int getRequiredTransactionIsolationLevel()
{
return -1;
}
/**
* Accessor for a Connection to the datastore.
* TODO Remove this since it is embodied in ConnectionFactoryImpl now
* @param connProvider the ConnectionProvider
* @param ds The data source. Possible to have more than one datasource for failover
* @param isolationLevel The level of transaction isolation
* @return The Connection
* @throws SQLException Thrown when an error occurs in the creation.
**/
public Connection getConnection(ConnectionProvider connProvider, DataSource[] ds, int isolationLevel)
throws SQLException
{
int reqdIsolationLevel = isolationLevel;
if (getRequiredTransactionIsolationLevel() >= 0)
{
reqdIsolationLevel = getRequiredTransactionIsolationLevel();
}
Connection conn = connProvider.getConnection(ds);
boolean succeeded = false;
try
{
if (reqdIsolationLevel == UserTransaction.TRANSACTION_NONE)
{
if (!conn.getAutoCommit())
{
conn.setAutoCommit(true);
}
}
else
{
if (conn.getAutoCommit())
{
conn.setAutoCommit(false);
}
if (supportsTransactionIsolationLevel(reqdIsolationLevel))
{
int currentIsolationLevel = conn.getTransactionIsolation();
if (currentIsolationLevel != reqdIsolationLevel)
{
conn.setTransactionIsolation(reqdIsolationLevel);
}
}
else
{
JPOXLogger.DATASTORE.warn(LOCALISER.msg("051008",
reqdIsolationLevel));
}
}
succeeded = true;
if (JPOXLogger.DATASTORE.isDebugEnabled())
{
JPOXLogger.DATASTORE.debug(LOCALISER.msg("052002", conn.toString(),
TransactionUtils.getNameForTransactionIsolationLevel(reqdIsolationLevel)));
}
if (reqdIsolationLevel != isolationLevel && isolationLevel == UserTransaction.TRANSACTION_NONE)
{
// User asked for a level that implies auto-commit so make sure it has that
if (!conn.getAutoCommit())
{
conn.setAutoCommit(true);
}
}
}
finally
{
if (!succeeded)
{
conn.close();
}
}
return conn;
}
/**
* Method to close a Connection to the datastore.
* TODO Remove this. Use ConnectionFactoryImpl now
* @param conn The connection
* @throws SQLException Thrown if error occurs on the close.
**/
public void closeConnection(Connection conn)
throws SQLException
{
if (JPOXLogger.DATASTORE.isDebugEnabled())
{
JPOXLogger.DATASTORE.debug(LOCALISER.msg("052003", conn.toString()));
}
conn.close();
}
/**
* Accessor for the Catalog Name for this datastore.
* @param conn Connection to the datastore
* @return The catalog name
* @throws SQLException Thrown if error occurs in determining the
* catalog name.
**/
public String getCatalogName(Connection conn)
throws SQLException
{
throw new UnsupportedOperationException(LOCALISER.msg("051015",datastoreProductName,datastoreProductVersion));
}
/**
* Accessor for the Schema Name for this datastore.
* @param conn Connection to the datastore
* @return The schema name
* @throws SQLException Thrown if error occurs in determining the
* schema name.
**/
public String getSchemaName(Connection conn)
throws SQLException
{
throw new UnsupportedOperationException(LOCALISER.msg("051016",datastoreProductName,datastoreProductVersion));
}
/**
* Accessor for the catalog separator.
* @return Catalog separator string.
**/
public String getCatalogSeparator()
{
return catalogSeparator;
}
/**
* Whether this datastore supports the use of the escape expression
* in like predicates
* @return whether we can use the escape expression in like predicates
**/
public boolean supportsEscapeExpressionInLikePredicate()
{
return true;
}
/**
* Whether this datastore supports the use of the catalog name in SQL
* table definitions (DDL).
* @return whether we can use the catalog name in DDL
**/
public boolean supportsCatalogsInTableDefinitions()
{
return supportsCatalogsInTableDefinitions;
}
/**
* Whether this datastore supports the use of the schema name in SQL
* table definitions (DDL).
* @return whether we can use the schema name in DDL
**/
public boolean supportsSchemasInTableDefinitions()
{
return supportsSchemasInTableDefinitions;
}
/**
* Whether this datastore supports batching of statements.
* @return whether we can use statement batching
**/
public boolean supportsStatementBatching()
{
return supportsStatementBatching;
}
/**
* Whether the RDBMS supports use of UNION syntax.
* @return Whether UNION is supported.
*/
public boolean supportsUnionSyntax()
{
return true;
}
/**
* Whether the RDBMS supports use of EXISTS syntax.
* @return Whether EXISTS is supported.
*/
public boolean supportsExistsSyntax()
{
return true;
}
/**
* Accessor for whether the SQL extensions CUBE, ROLLUP are supported.
* @return Whether the SQL extensions CUBE, ROLLUP are supported.
*/
public boolean supportsAnalysisMethods()
{
return true;
}
/**
* The pattern string for representing one character.
* Most of databases will use the underscore character.
* @param patternExpression The expression that represents one character for a matcher/parser in the database
* @return the pattern string.
**/
public ScalarExpression getEscapedPatternExpression(ScalarExpression patternExpression)
{
if (patternExpression instanceof StringLiteral)
{
String value = (String) ((StringLiteral)patternExpression).getValue();
value = StringUtils.replaceAll(value,"\\","\\\\");
value = StringUtils.replaceAll(value,"%","\\%");
value = StringUtils.replaceAll(value,"_","\\_");
JavaTypeMapping m = getMapping(String.class, patternExpression);
return m.newLiteral(patternExpression.getQueryExpression(),value);
}
else
{
return patternExpression;
}
}
/**
* The pattern string for representing one character that is expanded in word searches.
* Most of databases will use the underscore character.
* @return the pattern string.
**/
public String getPatternExpressionAnyCharacter()
{
return "_";
}
/**
* The pattern string for representing zero or more characters that is expanded in word searches.
* Most of databases will use the percent sign character.
* @return the pattern string.
**/
public String getPatternExpressionZeroMoreCharacters()
{
return "%";
}
/**
* The character for escaping characters in pattern expressions.
* @return the character.
**/
public String getEscapePatternExpression()
{
return "ESCAPE '\\'";
}
/**
* The character for escaping characters in pattern expressions.
* @return the character.
**/
public String getEscapeCharacter()
{
return "\\";
}
/**
* Continuation string to use where the SQL statement goes over more than 1
* line. Some JDBC adapters (e.g DB2) don't do conversion.
* @return Continuation string.
**/
public String getContinuationString()
{
return "\n";
}
/**
* Whether to create indexes before foreign keys.
* @return Whether to create indexes before foreign keys
**/
public boolean createIndexesBeforeForeignKeys()
{
return false;
}
/**
* Whether to include ORDER BY columns in a SELECT.
* @return Whether to include ORDER BY in SELECTs
**/
public boolean includeOrderByColumnsInSelect()
{
return true;
}
/**
* Generates a expression that represents the cartesian product of two sets: <code>X</code> and <code>Y</code>.
* Actually, <code>X</code> is not generated to the expression.
* @param Y right hand set
* @return the cartesion product expression. if the cartesian product expression is "X x Y", the returned expression
* is " x Y". Note that the left hand set was not introduced to the expression
**/
public String cartersianProduct(LogicSetExpression Y)
{
StringBuffer sb = new StringBuffer();
sb.append(" , ");
sb.append(Y.toString());
return sb.toString();
}
/**
* Union combines the results of two or more queries into a single result
* set. Union include only distinct rows and Union all may include
* duplicates.
*
* When using the UNION statement, keep in mind that, by
* default, it performs the equivalent of a SELECT DISTINCT on the final
* result set. In other words, UNION takes the results of two like
* recordsets, combines them, and then performs a SELECT DISTINCT in order
* to eliminate any duplicate rows. This process occurs even if there are no
* duplicate records in the final recordset. If you know that there are
* duplicate records, and this presents a problem for your application, then
* by all means use the UNION statement to eliminate the duplicate rows. On
* the other hand, if you know that there will never be any duplicate rows,
* or if there are, and this presents no problem to your application, then
* you should use the UNION ALL statement instead of the UNION statement.
* The advantage of the UNION ALL is that is does not perform the SELECT
* DISTINCT function, which saves a lot of unnecessary SQL Server resources
* from being using.
*
* @return Whether to use UNION ALL instead of UNION
*/
public boolean useUnionAll()
{
return false;
}
/**
* Whether this datastore supports ALTER TABLE DROP constraints
* @return whether we support ALTER TABLE DROP constraints
**/
public boolean supportsAlterTableDropConstraint()
{
return true;
}
/**
* Whether this datastore supports ALTER TABLE DROP FOREIGN KEY constraints
* @return whether we support ALTER TABLE DROP FOREIGN KEY constraints
**/
public boolean supportsAlterTableDropForeignKeyConstraint()
{
return false;
}
/**
* Whether this datastore supports deferred constraints.
* @return whether we support deferred constraints.
**/
public boolean supportsDeferredConstraints()
{
return true;
}
/**
* Whether this datastore supports SELECT ... FOR UPDATE.
* @return whether we support SELECT ... FOR UPDATE.
**/
public boolean supportsLockWithSelectForUpdate()
{
return false;
}
/**
* Whether this datastore supports using DISTINCT when using SELECT ... FOR UPDATE.
* @return whether the datastore supports DISTINCT in same statement as FOR UPDATE
**/
public boolean supportsDistinctWithSelectForUpdate()
{
return true;
}
/**
* Whether this datastore supports "SELECT a.* FROM (SELECT * FROM TBL1 INNER JOIN TBL2 ON tbl1.x = tbl2.y ) a"
* If the database does not support the SQL statement generated is like
* "SELECT a.* FROM (TBL1 INNER JOIN TBL2 ON tbl1.x = tbl2.y ) a"
* @return whether we support project in subqueries joins
**/
public boolean supportsProjectionInTableReferenceJoins()
{
return false;
}
/**
* The option to specify in "SELECT ... FROM TABLE ... WITH (option)" to lock instances
* Null if not supported.
* @return The option to specify with "SELECT ... FROM TABLE ... WITH (option)"
**/
public String getSelectWithLockOption()
{
return null;
}
/**
* Determines whether the {@link #getSelectWithLockOption()} is to be placed right
* before the FROM clause, or at the end of the statement
* @return true if option has to be placed right after the from clause
*/
public boolean getPlaceWithOptionAfterFromClause()
{
return false;
}
/**
* Determines whether lock option has to be placed within JOIN clauses as well.
* @return true if option has to be placed right after the join clause
*/
public boolean getPlaceWithOptionWithinJoinClauses()
{
return false;
}
/**
* The function to creates a unique value of type uniqueidentifier.
* @return The function. e.g. "SELECT NEWID()"
**/
public String getSelectNewUUIDStmt()
{
return null;
}
/**
* The function to creates a unique value of type uniqueidentifier.
* @return The function. e.g. "NEWID()"
**/
public String getNewUUIDFunction()
{
return null;
}
/**
* Whether this datastore supports the specified foreign key update action
* @param action The update action
* @return Whether it is supported
*/
public boolean supportsForeignKeyUpdateAction(ForeignKey.FKAction action)
{
return true;
}
/**
* Whether this datastore supports the specified foreign key delete action
* @param action The delete action
* @return Whether it is supported
*/
public boolean supportsForeignKeyDeleteAction(ForeignKey.FKAction action)
{
return true;
}
/**
* Whether the datastore supports specification of the primary key in
* CREATE TABLE statements.
* @return Whetehr it allows "PRIMARY KEY ..."
*/
public boolean supportsPrimaryKeyInCreateStatements()
{
return false;
}
/**
* Whether this datastore supports the use of CHECK in CREATE TABLE
* statements (DDL).
* @return whether we can use CHECK in CREATE TABLE.
**/
public boolean supportsCheckInCreateStatements()
{
return true;
}
/**
* Whether this datastore supports the use of CHECK after the column
* definitions in CREATE TABLE statements (DDL).
* e.g.<PRE>
* CREATE TABLE XXX
* (
* COL_A int,
* COL_B char(1),
* PRIMARY KEY (COL_A),
* CHECK (COL_B IN ('Y','N'))
* )
* </PRE>
* @return whether we can use CHECK after the column
* definitions CREATE TABLE.
**/
public boolean supportsCheckConstraintsInEndCreateStatements()
{
return false;
}
/**
* Whether this datastore supports the use of UNIQUE after the column
* definitions in CREATE TABLE statements (DDL).
* e.g.<PRE>
* CREATE TABLE XXX
* (
* COL_A int,
* COL_B char(1),
* PRIMARY KEY (COL_A),
* UNIQUE (COL_B ...)
* )
* </PRE>
* @return whether we can use UNIQUE after the column definitions CREATE TABLE.
**/
public boolean supportsUniqueConstraintsInEndCreateStatements()
{
return false;
}
/**
* Whether the datastore supports {@link Statement#getGeneratedKeys()}
* @return true if it is supported
*/
public boolean supportsStatementGetGeneratedKeys()
{
return true;
}
/**
* Whether we support Boolean comparisons.
* @return whether we support Boolean comparisons.
**/
public boolean supportsBooleanComparison()
{
return true;
}
/**
* Whether we support NULLs in candidate keys.
* @return whether we support NULLs in candidate keys.
**/
public boolean supportsNullsInCandidateKeys()
{
return true;
}
/**
* Whether the database support NULLs in the column options for table creation.
* @return whether the database support NULLs in the column options for table creation.
**/
public boolean supportsNullsKeywordInColumnOptions()
{
return true;
}
/**
* Whether we support DEFAULT tag in CREATE TABLE statements
* @return whether we support the DEFAULT tag in CREATE TABLE
**/
public boolean supportsDefaultKeywordInColumnOptions()
{
return true;
}
/**
* Whether we support DEFAULT tag together with NOT NULL in CREATE TABLE statements.
* <pre>
* CREATE TABLE X ( MEMORY_SIZE BIGINT DEFAULT 0 NOT NULL )
* </pre>
* Some databases only support <i>DEFAULT {ConstantExpression | NULL}</i>
*
* @return whether we support the DEFAULT tag together with NOT NULL in CREATE TABLE
**/
public boolean supportsDefaultKeywordWithNotNullInColumnOptions()
{
return true;
}
/**
* Whether any DEFAULT tag will be before any NULL/NOT NULL in the column options.
* @return Whether to put DEFAULT before NULL
*/
public boolean supportsDefaultBeforeNullInColumnOptions()
{
return true;
}
/**
* Accessor for whether the RDBMS supports ANSI join syntax.
* @return Whether the RDBMS supports ANSI join syntax
*/
public boolean supportsAnsiJoinSyntax()
{
return true;
}
/**
* Accessor for the WHERE clause to add to provide an INNER JOIN
* using non-ANSI syntax.
* @param col1 The main datastore column
* @param col2 The secondary column to join to
* @return The Inner Join WHERE clause.
*/
public String getNonAnsiInnerJoinWhereClause(String col1, String col2)
{
return null;
}
/**
* Accessor for the WHERE clause to add to provide a LEFT OUTER JOIN
* using non-ANSI syntax.
* @param col1 The main datastore column
* @param col2 The secondary column to join to
* @return The Left Outer Join WHERE clause.
*/
public String getNonAnsiLeftOuterJoinWhereClause(String col1, String col2)
{
return null;
}
/**
* Accessor for the WHERE clause to add to provide a RIGHT OUTER JOIN
* using non-ANSI syntax.
* @param col1 The main datastore column
* @param col2 The secondary column to join to
* @return The Right Outer Join WHERE clause.
*/
public String getNonAnsiRightOuterJoinWhereClause(String col1, String col2)
{
return null;
}
// ---------------------------- AutoIncrement Support ----------------------
/**
* Accessor for the autoincrementing sql statement for this datastore.
* @param table Name of the table that the autoincrement is for
* @param columnName Name of the column that the autoincrement is for
* @return The statement for getting the latest autoincremented key
**/
public String getAutoIncrementStmt(Table table, String columnName)
{
throw new UnsupportedOperationException(LOCALISER.msg("051019"));
}
/**
* Accessor for the autoincrementing keyword for generating DDLs.
* (CREATE TABLEs...).
* @return The keyword for a column using autoincrement
**/
public String getAutoIncrementKeyword()
{
throw new UnsupportedOperationException(LOCALISER.msg("051019"));
}
/**
* Verifies if the given <code>typeName</code> is auto incremented by the datastore.
* @param typeName the datastore type name
* @return true when the <code>typeName</code> has values auto incremented by the datastore
**/
public boolean isIdentityFieldDataType(String typeName)
{
throw new UnsupportedOperationException(LOCALISER.msg("051019"));
}
/**
* Whether we support autoincrementing keys with nullability specification.
* @return whether we support autoincrementing keys with nullability spec.
**/
public boolean supportsAutoIncrementKeysNullSpecification()
{
return true;
}
/**
* Whether we support auto-increment/identity keys with column type specification.
* @return whether we support auto-increment keys with column type spec.
**/
public boolean supportsAutoIncrementColumnTypeSpecification()
{
return true;
}
/**
* Method to return the INSERT statement to use when inserting into a table that has no
* columns specified. This is the case when we have a single column in the table and that column
* is autoincrement/identity (and so is assigned automatically in the datastore).
* @param table The table
* @return The statement for the INSERT
*/
public String getInsertStatementForNoColumns(Table table)
{
return "INSERT INTO " + table.toString() + " () VALUES ()";
}
// ---------------------------- Sequence Support ---------------------------
/**
* Accessor for the sequence create statement for this datastore.
* TODO Change param types to int.
* @param sequence_name Name of the sequence
* @param min Minimum value for the sequence
* @param max Maximum value for the sequence
* @param start Start value for the sequence
* @param increment Increment value for the sequence
* @param cache_size Cache size for the sequence
* @return The statement for getting the next id from the sequence
**/
public String getSequenceCreateStmt(String sequence_name,
String min,String max,
String start,String increment,
String cache_size)
{
throw new UnsupportedOperationException(LOCALISER.msg("051020"));
}
/**
* Accessor for the sequence statement to get the next id for this
* datastore.
* @param sequence_name Name of the sequence
* @return The statement for getting the next id for the sequence
**/
public String getSequenceNextStmt(String sequence_name)
{
throw new UnsupportedOperationException(LOCALISER.msg("051020"));
}
/**
* Provide the existing indexes in the database for the table
* @param conn the JDBC connection
* @param dmd the DatabaseMetaData
* @param catalog the catalog name
* @param schema the schema name
* @param table the table name
* @return a ResultSet with the format @see DatabaseMetaData#getIndexInfo(java.lang.String, java.lang.String, java.lang.String, boolean, boolean)
* @throws SQLException
*/
public ResultSet getExistingIndexes(Connection conn, DatabaseMetaData dmd, String catalog, String schema, String table) throws SQLException
{
return dmd.getIndexInfo(catalog, schema, table, false, true);
}
// ------------------------------- Queries ---------------------------------
/**
* Accessor for a new query statement.
* @param table The table to query
* @param clr The ClassLoaderResolver
* @return The Query Statement
**/
public QueryExpression newQueryStatement(DatastoreContainerObject table, ClassLoaderResolver clr)
{
return new QueryStatement(table, clr);
}
/**
* Accessor for a new query statement.
* @param table The table to query
* @param rangeVar A range variable for the query
* @param clr The ClassLoaderResolver
* @return The Query Statement
**/
public QueryExpression newQueryStatement(DatastoreContainerObject table, DatastoreIdentifier rangeVar, ClassLoaderResolver clr)
{
return new QueryStatement(table, rangeVar, clr);
}
/**
* Returns a new TableExpression object appropriate for this DBMS.
* This should be an instance of one of the three built-in styles of table
* expression:
* <ul>
* <li>TableExprAsJoins</li>
* <li>TableExprAsSubjoins</li>
* <li>TableExprAsSubquery</li>
* </ul>
* TableExprAsSubjoins is the default, which arguably produces the most
* readable SQL but doesn't work on all DBMS's. TableExprAsSubjoins
* should work anywhere, but may be less efficient.
*
* @param qs The query statement in which the table expression will
* be included.
* @param table The main table in the expression.
* @param rangeVar The SQL alias, or "range variable", to assign to the
* expression or to the main table.
* @return The expression
*/
public LogicSetExpression newTableExpression(QueryExpression qs, DatastoreContainerObject table, DatastoreIdentifier rangeVar)
{
return new TableExprAsSubjoins(qs, table, rangeVar);
}
/**
* Returns the appropriate SQL to create the given table having the given
* columns. No column constraints or key definitions should be included.
* It should return something like:
* <p>
* <pre>
* CREATE TABLE FOO ( BAR VARCHAR(30), BAZ INTEGER )
* </pre>
*
* @param table The table to create.
* @param columns The columns of the table.
* @param props Properties for controlling the table creation
* @return The text of the SQL statement.
*/
public String getCreateTableStatement(TableImpl table, Column[] columns, Properties props)
{
StringBuffer createStmt = new StringBuffer();
// CREATE TABLE with column specifiers
createStmt.append("CREATE TABLE ").append(table.toString())
.append(getContinuationString())
.append("(")
.append(getContinuationString());
for (int i = 0; i < columns.length; ++i)
{
if (i > 0)
{
createStmt.append(",").append(getContinuationString());
}
createStmt.append(" ").append(columns[i].getSQLDefinition());
}
// PRIMARY KEY(col[,col])
if (supportsPrimaryKeyInCreateStatements())
{
PrimaryKey pk = table.getPrimaryKey();
if (pk != null && pk.size() > 0)
{
createStmt.append(",").append(getContinuationString());
createStmt.append(" ").append(pk.toString());
}
}
// UNIQUE( col [,col] )
if (supportsUniqueConstraintsInEndCreateStatements())
{
StringBuffer uniqueConstraintStmt = new StringBuffer();
for (int i = 0; i < columns.length; ++i)
{
if (columns[i].isUnique())
{
if (uniqueConstraintStmt.length() < 1)
{
uniqueConstraintStmt.append(",").append(getContinuationString());
uniqueConstraintStmt.append(" UNIQUE (");
}
else
{
uniqueConstraintStmt.append(",");
}
uniqueConstraintStmt.append(columns[i].getIdentifier().toString());
}
}
if (uniqueConstraintStmt.length() > 1)
{
uniqueConstraintStmt.append(")");
createStmt.append(uniqueConstraintStmt.toString());
}
}
// CHECK (column_identifier IN (literal[,literal]))
if (supportsCheckConstraintsInEndCreateStatements())
{
StringBuffer checkConstraintStmt = new StringBuffer();
for (int i = 0; i < columns.length; ++i)
{
if (columns[i].getConstraints() != null)
{
checkConstraintStmt.append(",").append(getContinuationString());
checkConstraintStmt.append(columns[i].getConstraints());
}
}
if (checkConstraintStmt.length() > 1)
{
createStmt.append(checkConstraintStmt.toString());
}
}
createStmt.append(getContinuationString()).append(")");
return createStmt.toString();
}
/**
* Returns the appropriate SQL to add a primary key to its table.
* It should return something like:
* <p>
* <pre>
* ALTER TABLE FOO ADD CONSTRAINT FOO_PK PRIMARY KEY (BAR)
* ALTER TABLE FOO ADD PRIMARY KEY (BAR)
* </pre>
*
* @param pk An object describing the primary key.
* @param factory Identifier factory
* @return The text of the SQL statement.
*/
public String getAddPrimaryKeyStatement(PrimaryKey pk, IdentifierFactory factory)
{
if (pk.getName() != null)
{
String identifier = factory.getIdentifierInAdapterCase(pk.getName());
return "ALTER TABLE " + pk.getDatastoreContainerObject().toString() + " ADD CONSTRAINT " + identifier + ' ' + pk;
}
else
{
return "ALTER TABLE " + pk.getDatastoreContainerObject().toString() + " ADD " + pk;
}
}
/**
* Returns the appropriate SQL to add a candidate key to its table.
* It should return something like:
* <p>
* <pre>
* ALTER TABLE FOO ADD CONSTRAINT FOO_CK UNIQUE (BAZ)
* ALTER TABLE FOO ADD UNIQUE (BAZ)
* </pre>
*
* @param ck An object describing the candidate key.
* @param factory Identifier factory
* @return The text of the SQL statement.
*/
public String getAddCandidateKeyStatement(CandidateKey ck, IdentifierFactory factory)
{
if (ck.getName() != null)
{
String identifier = factory.getIdentifierInAdapterCase(ck.getName());
return "ALTER TABLE " + ck.getDatastoreContainerObject().toString() + " ADD CONSTRAINT " + identifier + ' ' + ck;
}
else
{
return "ALTER TABLE " + ck.getDatastoreContainerObject().toString() + " ADD " + ck;
}
}
/**
* Returns the appropriate SQL to add a foreign key to its table.
* It should return something like:
* <p>
* <pre>
* ALTER TABLE FOO ADD CONSTRAINT FOO_FK1 FOREIGN KEY (BAR, BAZ) REFERENCES ABC (COL1, COL2)
* ALTER TABLE FOO ADD FOREIGN KEY (BAR, BAZ) REFERENCES ABC (COL1, COL2)
* </pre>
* @param fk An object describing the foreign key.
* @param factory Identifier factory
* @return The text of the SQL statement.
*/
public String getAddForeignKeyStatement(ForeignKey fk, IdentifierFactory factory)
{
if (fk.getName() != null)
{
String identifier = factory.getIdentifierInAdapterCase(fk.getName());
return "ALTER TABLE " + fk.getDatastoreContainerObject().toString() + " ADD CONSTRAINT " + identifier + ' ' + fk;
}
else
{
return "ALTER TABLE " + fk.getDatastoreContainerObject().toString() + " ADD " + fk;
}
}
/**
* Accessor for the SQL statement to add a column to a table.
* @param table The table
* @param col The column
* @return The SQL necessary to add the column
*/
public String getAddColumnStatement(DatastoreContainerObject table, Column col)
{
return "ALTER TABLE " + table.toString() + " ADD " + col.getSQLDefinition();
}
/**
* Returns the appropriate SQL to add an index to its table.
* It should return something like:
* <p>
* <pre>
* CREATE INDEX FOO_N1 ON FOO (BAR,BAZ) [Extended Settings]
* CREATE UNIQUE INDEX FOO_U1 ON FOO (BAR,BAZ) [Extended Settings]
* </pre>
*
* @param idx An object describing the index.
* @param factory Identifier factory
* @return The text of the SQL statement.
*/
public String getCreateIndexStatement(Index idx, IdentifierFactory factory)
{
String indexIdentifier = factory.getIdentifierInAdapterCase(idx.getName());
return "CREATE " + (idx.getUnique() ? "UNIQUE " : "") + "INDEX " + indexIdentifier + " ON " + idx.getDatastoreContainerObject().toString() + ' ' + idx + (idx.getExtendedIndexSettings() == null ? "" : " "+idx.getExtendedIndexSettings());
}
/**
* Creates a CHECK constraint definition based on the given values
* e.g.
* CHECK ( "COLUMN" IN ('VAL1','VAL2') OR "COLUMN" IS NULL )
* @param identifier
* @param values
* @param nullable whether the datastore identifier is null
* @return The check constraint
*/
public String getCheckConstraintForValues(DatastoreIdentifier identifier, ScalarExpression[] values, boolean nullable)
{
StringBuffer constraints = new StringBuffer("CHECK (");
constraints.append(identifier);
constraints.append(" IN (");
for (int i=0;i<values.length;i++)
{
if (i > 0)
{
constraints.append(",");
}
constraints.append(values[i].toStatementText(ScalarExpression.PROJECTION));
}
constraints.append(")");
if (nullable)
{
constraints.append(" OR " + identifier + " IS NULL");
}
constraints.append(")");
return constraints.toString();
}
/**
* Returns the appropriate SQL to drop the given table.
* It should return something like:
* <p>
* <pre>
* DROP TABLE FOO CASCADE
* </pre>
*
* @param table The table to drop.
* @return The text of the SQL statement.
*/
public String getDropTableStatement(DatastoreContainerObject table)
{
return "DROP TABLE " + table.toString() + " CASCADE";
}
/**
* Returns the appropriate SQL to drop the given view.
* It should return something like:
* <p>
* <pre>
* DROP VIEW FOO
* </pre>
*
* @param view The view to drop.
* @return The text of the SQL statement.
*/
public String getDropViewStatement(ViewImpl view)
{
return "DROP VIEW " + view.toString();
}
/**
* Method to return the SQL to append to the SELECT clause of a SELECT statement to handle
* restriction of ranges using the LIMIT keyword. Defaults to an empty
* string (not supported).
* SELECT {LIMIT} params
* @param offset The offset to return from
* @param count The number of items to return
* @return The SQL to append to allow for ranges using LIMIT.
*/
public String getRangeByLimitSelectClause(long offset, long count)
{
return "";
}
/**
* Method to return the SQL to append to the WHERE clause of a SELECT statement to handle
* restriction of ranges using the LIMIT keyword. Defaults to an empty
* string (not supported).
* SELECT param ... WHERE {LIMIT}
* @param offset The offset to return from
* @param count The number of items to return
* @return The SQL to append to allow for ranges using LIMIT.
*/
public String getRangeByLimitWhereClause(long offset, long count)
{
return "";
}
/**
* Method to return the column name to use when handling ranges via
* a rownumber on the select. Defaults to an empty string (not supported).
* @return The row number column.
*/
public String getRangeByRowNumberColumn()
{
return "";
}
/**
* Returns the appropriate expression for the <code>(int)'A'</code> expression.
* In SQL, it should compile something like:
* <p>
* <blockquote><pre>
* ASCII('A')
* </pre></blockquote>
* </p>
* @param expr The CharacterExpression
* @return The NumericExpression
*/
public NumericExpression toNumericExpression(CharacterExpression expr)
{
if (expr instanceof CharacterLiteral)
{
char c = ((Character)((CharacterLiteral)expr).getValue()).charValue();
BigInteger value = new BigInteger(""+(int)c);
return (NumericExpression) getMapping(value.getClass(), expr).newLiteral(expr.getQueryExpression(),value);
}
else if (expr instanceof Literal)
{
BigInteger value = new BigInteger((String)((Literal)expr).getValue());
return (NumericExpression) getMapping(value.getClass(), expr).newLiteral(expr.getQueryExpression(),value);
}
ArrayList args = new ArrayList();
args.add(expr);
return new NumericExpression("ASCII", args);
}
/**
* Method to translate all chars in this expression to the <code>fromExpr</code> which
* corresponds to <code>toExpr</code>.
* @return The expression.
**/
public StringExpression translateMethod(ScalarExpression expr, ScalarExpression toExpr, ScalarExpression fromExpr)
{
ArrayList args = new ArrayList();
args.add(expr);
args.add(fromExpr);
args.add(toExpr);
return new StringExpression("TRANSLATE", args);
}
/**
* Returns the appropriate SQL expression for the JDOQL Math.abs(EXPRESSION)
* method.
* It should return something like:
* <p>
* <blockquote><pre>
* ABS(str)
* </pre></blockquote>
*
* @param expr The argument to the abs() method.
* @return The text of the SQL expression.
*/
public NumericExpression absMethod(ScalarExpression expr)
{
ArrayList args = new ArrayList();
args.add(expr);
return new NumericExpression("ABS", args);
}
/**
* Returns the appropriate SQL expression for the JDOQL Math.sqrt(EXPRESSION)
* method. It should return something like:
* <p>
* <blockquote><pre>
* SQRT(str)
* </pre></blockquote>
*
* @param expr The argument to the sqrt() method.
* @return The text of the SQL expression.
*/
public NumericExpression sqrtMethod(ScalarExpression expr)
{
ArrayList args = new ArrayList();
args.add(expr);
return new NumericExpression("SQRT", args);
}
/**
* Returns the appropriate SQL expression for the JDOQL Math.cos(EXPRESSION)
* method.
* It should return something like:
* <p>
* <blockquote><pre>
* COS(str)
* </pre></blockquote>
*
* @param expr The argument to the cos() method.
* @return The text of the SQL expression.
*/
public NumericExpression cosMethod(ScalarExpression expr)
{
ArrayList args = new ArrayList();
args.add(expr);
return new NumericExpression("COS", args);
}
/**
* Returns the appropriate SQL expression for the JDOQL Math.sin(EXPRESSION)
* method.
* It should return something like:
* <p>
* <blockquote><pre>
* SIN(str)
* </pre></blockquote>
*
* @param expr The argument to the sin() method.
* @return The text of the SQL expression.
*/
public NumericExpression sinMethod(ScalarExpression expr)
{
ArrayList args = new ArrayList();
args.add(expr);
return new NumericExpression("SIN", args);
}
/**
* Returns the appropriate SQL expression for the JDOQL Math.tan(EXPRESSION)
* method.
* It should return something like:
* <p>
* <blockquote><pre>
* TAN(str)
* </pre></blockquote>
*
* @param expr The argument to the tan() method.
* @return The text of the SQL expression.
*/
public NumericExpression tanMethod(ScalarExpression expr)
{
ArrayList args = new ArrayList();
args.add(expr);
return new NumericExpression("TAN", args);
}
/**
* Returns the appropriate SQL expression for the JDOQL Math.acos(EXPRESSION)
* method.
* It should return something like:
* <p>
* <blockquote><pre>
* ACOS(str)
* </pre></blockquote>
*
* @param expr The argument to the cos() method.
* @return The text of the SQL expression.
*/
public NumericExpression acosMethod(ScalarExpression expr)
{
ArrayList args = new ArrayList();
args.add(expr);
return new NumericExpression("ACOS", args);
}
/**
* Returns the appropriate SQL expression for the JDOQL Math.asin(EXPRESSION)
* method.
* It should return something like:
* <p>
* <blockquote><pre>
* ASIN(str)
* </pre></blockquote>
*
* @param expr The argument to the asin() method.
* @return The text of the SQL expression.
*/
public NumericExpression asinMethod(ScalarExpression expr)
{
ArrayList args = new ArrayList();
args.add(expr);
return new NumericExpression("ASIN", args);
}
/**
* Returns the appropriate SQL expression for the JDOQL Math.atan(EXPRESSION)
* method.
* It should return something like:
* <p>
* <blockquote><pre>
* ATAN(str)
* </pre></blockquote>
*
* @param expr The argument to the atan() method.
* @return The text of the SQL expression.
*/
public NumericExpression atanMethod(ScalarExpression expr)
{
ArrayList args = new ArrayList();
args.add(expr);
return new NumericExpression("ATAN", args);
}
/**
* Returns whether this string ends with the specified string.
* @param leftOperand the source string
* @param rightOperand The string to compare against.
* @return Whether it ends with the string.
**/
public BooleanExpression endsWithMethod(ScalarExpression leftOperand, ScalarExpression rightOperand)
{
if (!(rightOperand instanceof StringExpression))
{
throw new IllegalArgumentTypeException(rightOperand);
}
StringLiteral pct = new StringLiteral(leftOperand.getQueryExpression(), leftOperand.getMapping(), '%');
return new BooleanExpression(leftOperand, ScalarExpression.OP_LIKE, pct.add(leftOperand.getQueryExpression().getStoreManager().getDatastoreAdapter().getEscapedPatternExpression(rightOperand)));
}
/**
* Returns the appropriate SQL expression for the JDOQL Math.exp(EXPRESSION)
* method.
* It should return something like:
* <p>
* <blockquote><pre>
* EXP(str)
* </pre></blockquote>
*
* @param expr The argument to the exp() method.
* @return The text of the SQL expression.
*/
public NumericExpression expMethod(ScalarExpression expr)
{
ArrayList args = new ArrayList();
args.add(expr);
return new NumericExpression("EXP", args);
}
/**
* Returns the appropriate SQL expression for the JDOQL Math.log(EXPRESSION)
* method.
* It should return something like:
* <p>
* <blockquote><pre>
* LOG(str)
* </pre></blockquote>
*
* @param expr The argument to the log() method.
* @return The text of the SQL expression.
*/
public NumericExpression logMethod(ScalarExpression expr)
{
ArrayList args = new ArrayList();
args.add(expr);
return new NumericExpression("LOG", args);
}
/**
* Returns the appropriate SQL expression for the JDOQL Math.floor(EXPRESSION)
* method.
* It should return something like:
* <p>
* <blockquote><pre>
* FLOOR(str)
* </pre></blockquote>
*
* @param expr The argument to the floor() method.
* @return The text of the SQL expression.
*/
public NumericExpression floorMethod(ScalarExpression expr)
{
ArrayList args = new ArrayList();
args.add(expr);
return new NumericExpression("FLOOR", args);
}
/**
* Returns the appropriate SQL expression for the JDOQL ceil(EXPRESSION)
* method.
* It should return something like:
* <p>
* <blockquote><pre>
* CEIL(str)
* </pre></blockquote>
*
* @param expr The argument to the ceil() method.
* @return The text of the SQL expression.
*/
public NumericExpression ceilMethod(ScalarExpression expr)
{
ArrayList args = new ArrayList();
args.add(expr);
return new NumericExpression("CEIL", args);
}
/**
* Returns the appropriate SQL expression for the JDOQL String.length()
* method.
* It should return something like:
* <p>
* <blockquote><pre>
* CHAR_LENGTH(str)
* </pre></blockquote>
*
* @param str The argument to the length() method.
* @return The text of the SQL expression.
*/
public NumericExpression lengthMethod(StringExpression str)
{
ArrayList args = new ArrayList();
args.add(str);
return new NumericExpression("CHAR_LENGTH", args);
}
/**
* Matches this to the argument expression pattern. Use "." to find any
* character and ".*" for wildcard matches. A global case-insensitive flag
* "(?i)" can be set for the pattern. If used, the global case-insensitive
* flag must prefix the pattern. The pattern passed to matches must be a
* literal or parameter.
* @param text The argument to the length() method.
* @param pattern The literal expression with the pattern.
* @return the match expression.
*/
public BooleanExpression matchesMethod(StringExpression text, StringExpression pattern)
{
return new BooleanExpression(text, ScalarExpression.OP_LIKE, pattern);
}
/**
* A String conversion that converts a numeric expression to string.
* If the expr argument represents a Literal value, converts to a Literal string.
* In SQL, it should compile something like:
* <p>
* <blockquote>
* <pre>
* CAST(999999 AS VARCHAR(4000))
* </pre>
* </blockquote>
* </p>
* @param expr The NumericExpression
* @return the StringExpression
*/
public StringExpression toStringExpression(NumericExpression expr)
{
if( expr instanceof Literal )
{
JavaTypeMapping m = getMapping(String.class, expr);
return new StringLiteral(expr.getQueryExpression(),m,((Literal)expr).getValue().toString());
}
List args = new ArrayList();
args.add(expr);
List types = new ArrayList();
types.add("VARCHAR(4000)");
return new StringExpression("CAST", args, types);
}
/**
* A String conversion that converts a String literal to String expression. It will allow
* the String to only be evaluated at runtime.
* In SQL, it should compile something like:
* <p>
* <blockquote>
* <pre>
* CAST(999999 AS VARCHAR(4000))
* </pre>
* </blockquote>
* </p>
* @param expr The NumericExpression
* @return the StringExpression
*/
public StringExpression toStringExpression(StringLiteral expr)
{
return expr;
}
/**
* Returns the appropriate SQL expression for the JDOQL
* String.substring(str,begin) method.
* It should return something like:
* <p>
* <pre>
* SUBSTRING(str FROM begin)
* </pre>
* Note that the value of <var>begin</var> is base 0(Java-style), while most
* SQL string functions use base 1.
*
* @param str The first argument to the substring() method.
* @param begin The second argument to the substring() method.
* @return The text of the SQL expression.
*/
public StringExpression substringMethod(StringExpression str,
NumericExpression begin)
{
return new SubstringExpression(str, begin);
}
/**
* Returns the appropriate SQL expression for a query "trim" method.
* It should return something like:
* <pre>TRIM(str)</pre>
* @param str The first argument to the trim() method.
* @param leading Whether to trim leading spaces
* @param trailing Whether to trim trailing spaces
* @return The text of the SQL expression.
*/
public StringExpression trimMethod(StringExpression str, boolean leading, boolean trailing)
{
ArrayList args = new ArrayList();
args.add(str);
if (leading && trailing)
{
return new StringExpression("TRIM", args);
}
else if (leading)
{
return new StringExpression("LTRIM", args);
}
else if (trailing)
{
return new StringExpression("RTRIM", args);
}
return str; // Nothing to trim
}
/**
* Returns the appropriate SQL expression for the JDOQL
* String.substring(str,begin,end) method.
* It should return something like:
* <p>
* <blockquote><pre>
* SUBSTRING(str FROM begin FOR len)
* </pre></blockquote>
* Note that the value of <var>begin</var> is base 0 (Java-style), while most
* SQL string functions use base 1.
* Note also that an end position is given, while most SQL substring
* functions take a length.
*
* @param str The first argument to the substring() method.
* @param begin The second argument to the substring() method.
* @param end The third argument to the substring() method.
* @return The text of the SQL expression.
*/
public StringExpression substringMethod(StringExpression str,
NumericExpression begin,
NumericExpression end)
{
return new SubstringExpression(str, begin, end);
}
/**
* Method to handle the starts with operation.
* Will return something like
* <PRE>
* source LIKE str%
* </PRE>
* @param source The expression with the searched string
* @param str The expression for the search string
* @return The expression.
**/
public BooleanExpression startsWithMethod(ScalarExpression source, ScalarExpression str)
{
ScalarExpression pct = getMapping(String.class, source).newLiteral(source.getQueryExpression(), "%");
return new BooleanExpression(source, ScalarExpression.OP_LIKE, getEscapedPatternExpression(str).add(pct));
}
/**
* Returns the appropriate SQL expression for the JDOQL String.indexOf() method.
* It should return something like:
* <p>
* <blockquote><pre>
* LOCATE(str, substr [,pos])-1
* </pre></blockquote>
* since LOCATE returns the first character as position 1. Similarly the "pos" is based on the first
* position being 1.
* </p>
* @param source The expression we want to search.
* @param str The argument to the indexOf() method.
* @param from The from position
* @return The text of the SQL expression.
*/
public NumericExpression indexOfMethod(ScalarExpression source, ScalarExpression str, NumericExpression from)
{
JavaTypeMapping m = getMapping(BigInteger.class, source);
ScalarExpression integerLiteral = m.newLiteral(source.getQueryExpression(), BigInteger.ONE);
ArrayList args = new ArrayList();
args.add(str);
args.add(source);
if (from != null)
{
// Add 1 to the passed in value so that it is of origin 1 to be compatible with LOCATE
args.add(from.add(integerLiteral));
}
NumericExpression locateExpr = new NumericExpression("LOCATE", args);
// Subtract 1 from the result of LOCATE to be consistent with Java strings
// TODO Would be nice to put this in parentheses
return new NumericExpression(locateExpr, ScalarExpression.OP_SUB, integerLiteral);
}
/**
* An operator in a string expression that concatenates two or more
* character or binary strings, columns, or a combination of strings and
* column names into one expression (a string operator).
*
* @return the operator SQL String
*/
public String getOperatorConcat()
{
return "||";
}
/**
* <p>
* If only one operand expression is of type String, then string conversion
* is performed on the other operand to produce a string at run time. The
* result is a reference to a String object (newly created, unless the
* expression is a compile-time constant expression (�15.28))that is the
* concatenation of the two operand strings. The characters of the left-hand
* operand precede the characters of the right-hand operand in the newly
* created string. If an operand of type String is null, then the string
* "null" is used instead of that operand. "null" is used instead of that
* operand.
* </p>
* <p>
* Concatenates two or more character or binary strings, columns, or a
* combination of strings and column names into one expression (a string
* operator).
* </p>
* @param operand1 the left expression
* @param operand2 the right expression
* @return The Expression for concatenation
*/
public ScalarExpression concatOperator(ScalarExpression operand1, ScalarExpression operand2)
{
return new ConcatOperatorExpression(operand1, operand2);
}
/**
* Returns the appropriate SQL expression for the JDOQL Date.getDay()
* method. It should return something like:
* <pre>DAY(date)</pre>
* @param date The date for the getDay() method.
* @return The text of the SQL expression.
*/
public NumericExpression getDayMethod(SqlTemporalExpression date)
{
ArrayList args = new ArrayList();
args.add(date);
return new NumericExpression("DAY", args);
}
/**
* Returns the appropriate SQL expression for the JDOQL Date.getMonth()
* method. It should return something like:
* <pre>MONTH(date)</pre>
* @param date The date for the getMonth() method.
* @return The text of the SQL expression.
*/
public NumericExpression getMonthMethod(SqlTemporalExpression date)
{
ArrayList args = new ArrayList();
args.add(date);
// Delete one from the SQL "month" (origin=1) to be compatible with Java month (origin=0)
JavaTypeMapping m = getMapping(BigInteger.class, date);
ScalarExpression integerLiteral = m.newLiteral(date.getQueryExpression(), BigInteger.ONE);
NumericExpression expr = new NumericExpression(new NumericExpression("MONTH", args), ScalarExpression.OP_SUB, integerLiteral);
expr.encloseWithInParentheses();
return expr;
}
/**
* Returns the appropriate SQL expression for the JDOQL Date.getYear()
* method. It should return something like:
* <pre>YEAR(date)</pre>
* @param date The date for the getYear() method.
* @return The text of the SQL expression.
*/
public NumericExpression getYearMethod(SqlTemporalExpression date)
{
ArrayList args = new ArrayList();
args.add(date);
return new NumericExpression("YEAR", args);
}
/**
* Returns the appropriate SQL expression for the JDOQL Time.getHour()
* method. It should return something like:
* <pre>HOUR(time)</pre>
* @param time The time for the getHour() method.
* @return The text of the SQL expression.
*/
public NumericExpression getHourMethod(SqlTemporalExpression time)
{
ArrayList args = new ArrayList();
args.add(time);
return new NumericExpression("HOUR", args);
}
/**
* Returns the appropriate SQL expression for the JDOQL Time.getMinute()
* method. It should return something like:
* <pre>MINUTE(time)</pre>
* @param time The time for the getMinute() method.
* @return The text of the SQL expression.
*/
public NumericExpression getMinuteMethod(SqlTemporalExpression time)
{
ArrayList args = new ArrayList();
args.add(time);
return new NumericExpression("MINUTE", args);
}
/**
* Returns the appropriate SQL expression for the JDOQL Time.getSecond()
* method. It should return something like:
* <pre>SECOND(time)</pre>
* @param time The time for the getSecond() method.
* @return The text of the SQL expression.
*/
public NumericExpression getSecondMethod(SqlTemporalExpression time)
{
ArrayList args = new ArrayList();
args.add(time);
return new NumericExpression("SECOND", args);
}
/**
* Accessor for table and column information for a catalog/schema in this datastore.
* @param conn Connection to use
* @param catalog The catalog (null if none)
* @param schema The schema (null if none)
* @param table The table (null if all)
* @return ResultSet containing the table/column information
* @throws SQLException Thrown if an error occurs
*/
public ResultSet getColumns(Connection conn, String catalog, String schema, String table)
throws SQLException
{
DatabaseMetaData dmd = conn.getMetaData();
return dmd.getColumns(catalog, schema, table, null);
}
/**
* Accessor for table information for a catalog/schema in this datastore.
* @param conn Connection to use
* @param catalog The catalog (null if none)
* @param schema The schema (null if none)
* @return ResultSet containing the table information
* @throws SQLException Thrown if an error occurs
*/
public ResultSet getTables(Connection conn, String catalog, String schema)
throws SQLException
{
DatabaseMetaData dmd = conn.getMetaData();
return dmd.getTables(catalog, schema, null, null);
}
/**
* Accessor for table and column information for a catalog/schema in this datastore.
* @param conn Connection to use
* @param catalog The catalog (null if none)
* @param schema The schema (null if none)
* @param table The table (null if all)
* @param columnNamePattern The column name (null if all)
* @return ResultSet containing the table/column information
* @throws SQLException Thrown if an error occurs
*/
public ResultSet getColumns(Connection conn, String catalog, String schema, String table, String columnNamePattern)
throws SQLException
{
DatabaseMetaData dmd = conn.getMetaData();
return dmd.getColumns(catalog, schema, table, columnNamePattern);
}
/**
* Accessor for whether the column defaults need initialising for this datastore
* @return Whether to initialise the column defaults
*/
public boolean requiresColumnDefaultsInitialising()
{
return true;
}
public boolean isStoresLowerCaseIdentifiers()
{
return storesLowerCaseIdentifiers;
}
public boolean isStoresLowerCaseQuotedIdentifiers()
{
return storesLowerCaseQuotedIdentifiers;
}
public boolean isStoresMixedCaseIdentifiers()
{
return storesMixedCaseIdentifiers || storesMixedCaseSensitiveIdentifiers;
}
public boolean isStoresMixedCaseQuotedIdentifiers()
{
return storesMixedCaseQuotedIdentifiers || storesMixedCaseQuotedSensitiveIdentifiers;
}
public boolean isStoresUpperCaseIdentifiers()
{
return storesUpperCaseIdentifiers;
}
public boolean isStoresUpperCaseQuotedIdentifiers()
{
return storesUpperCaseQuotedIdentifiers;
}
public String toString()
{
StringBuffer sb = new StringBuffer();
sb.append("================ DatabaseAdapter ==================");
sb.append("\n");
sb.append("Adapter : " + this.getClass().getName());
sb.append("\n");
sb.append("Datastore : name=\"" + datastoreProductName + "\" version=\"" + datastoreProductVersion +
"\" (major=" + datastoreMajorVersion + ", minor=" + datastoreMinorVersion + ", revision=" + datastoreRevisionVersion + ")");
sb.append("\n");
sb.append("Driver : name=\"" + driverName + "\" version=\"" + driverVersion +
"\" (major=" + driverMajorVersion + ", minor=" + driverMinorVersion + ")");
sb.append("\n");
sb.append("===================================================");
return sb.toString();
}
/**
* Accessor for a statement that will return the statement to use to get the datastore date.
* @return SQL statement to get the datastore date
*/
public String getDatastoreDateStatement()
{
return "SELECT CURRENT_TIMESTAMP";
}
/**
* Method to return an expression for the current date in the datastore.
* @param qs QueryExpression
* @return Current date expression for this datastore
*/
public ScalarExpression getCurrentDateMethod(QueryExpression qs)
{
return new SqlTemporalExpression("CURRENT_DATE", qs);
}
/**
* Method to return an expression for the current time in the datastore.
* @param qs QueryExpression
* @return Current time expression for this datastore
*/
public ScalarExpression getCurrentTimeMethod(QueryExpression qs)
{
return new SqlTemporalExpression("CURRENT_TIME", qs);
}
/**
* Method to return an expression for the current timestamp in the datastore.
* @param qs QueryExpression
* @return Current timestamp expression for this datastore
*/
public ScalarExpression getCurrentTimestampMethod(QueryExpression qs)
{
return new SqlTemporalExpression("CURRENT_TIMESTAMP", qs);
}
}