You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openjpa.apache.org by pc...@apache.org on 2006/07/19 23:35:07 UTC
svn commit: r423615 [30/44] - in /incubator/openjpa/trunk: ./
openjpa-jdbc-5/ openjpa-jdbc-5/src/ openjpa-jdbc-5/src/main/
openjpa-jdbc-5/src/main/java/ openjpa-jdbc-5/src/main/java/org/
openjpa-jdbc-5/src/main/java/org/apache/ openjpa-jdbc-5/src/main/...
Added: incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DBDictionary.java
URL: http://svn.apache.org/viewvc/incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DBDictionary.java?rev=423615&view=auto
==============================================================================
--- incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DBDictionary.java (added)
+++ incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DBDictionary.java Wed Jul 19 14:34:44 2006
@@ -0,0 +1,3721 @@
+/*
+ * Copyright 2006 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.openjpa.jdbc.sql;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.Writer;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.sql.Array;
+import java.sql.Blob;
+import java.sql.Clob;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.PreparedStatement;
+import java.sql.Ref;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.SQLWarning;
+import java.sql.Statement;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.sql.Types;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import javax.sql.DataSource;
+
+import org.apache.openjpa.jdbc.conf.JDBCConfiguration;
+import org.apache.openjpa.jdbc.kernel.JDBCFetchConfiguration;
+import org.apache.openjpa.jdbc.kernel.JDBCFetchState;
+import org.apache.openjpa.jdbc.kernel.JDBCStore;
+import org.apache.openjpa.jdbc.kernel.exps.FilterValue;
+import org.apache.openjpa.jdbc.meta.ClassMapping;
+import org.apache.openjpa.jdbc.meta.FieldMapping;
+import org.apache.openjpa.jdbc.meta.JavaSQLTypes;
+import org.apache.openjpa.jdbc.schema.Column;
+import org.apache.openjpa.jdbc.schema.DataSourceFactory;
+import org.apache.openjpa.jdbc.schema.ForeignKey;
+import org.apache.openjpa.jdbc.schema.Index;
+import org.apache.openjpa.jdbc.schema.NameSet;
+import org.apache.openjpa.jdbc.schema.PrimaryKey;
+import org.apache.openjpa.jdbc.schema.Schema;
+import org.apache.openjpa.jdbc.schema.SchemaGroup;
+import org.apache.openjpa.jdbc.schema.Sequence;
+import org.apache.openjpa.jdbc.schema.Table;
+import org.apache.openjpa.jdbc.schema.Unique;
+import org.apache.openjpa.kernel.Filters;
+import org.apache.openjpa.lib.conf.Configurable;
+import org.apache.openjpa.lib.conf.Configuration;
+import org.apache.openjpa.lib.jdbc.ConnectionDecorator;
+import org.apache.openjpa.lib.jdbc.LoggingConnectionDecorator;
+import org.apache.openjpa.lib.log.Log;
+import org.apache.openjpa.lib.util.Localizer;
+import org.apache.openjpa.meta.FieldMetaData;
+import org.apache.openjpa.meta.JavaTypes;
+import org.apache.openjpa.util.GeneralException;
+import org.apache.openjpa.util.OpenJPAException;
+import org.apache.openjpa.util.ReferentialIntegrityException;
+import org.apache.openjpa.util.Serialization;
+import org.apache.openjpa.util.StoreException;
+import org.apache.openjpa.util.UnsupportedException;
+import org.apache.openjpa.util.UserException;
+import serp.util.Numbers;
+import serp.util.Strings;
+
+/**
+ * Class which allows the creation of SQL dynamically, in a
+ * database agnostic fashion. Subclass for the nuances of different data stores.
+ */
+public class DBDictionary
+ implements Configurable, ConnectionDecorator, JoinSyntaxes,
+ LoggingConnectionDecorator.SQLWarningHandler {
+
+ public static final String VENDOR_OTHER = "other";
+ public static final String VENDOR_DATADIRECT = "datadirect";
+
+ public static final String SCHEMA_CASE_UPPER = "upper";
+ public static final String SCHEMA_CASE_LOWER = "lower";
+ public static final String SCHEMA_CASE_PRESERVE = "preserve";
+
+ public static final String CONS_NAME_BEFORE = "before";
+ public static final String CONS_NAME_MID = "mid";
+ public static final String CONS_NAME_AFTER = "after";
+
+ protected static final int RANGE_POST_SELECT = 0;
+ protected static final int RANGE_PRE_DISTINCT = 1;
+ protected static final int RANGE_POST_DISTINCT = 2;
+
+ protected static final int NANO = 1;
+ protected static final int MICRO = NANO * 1000;
+ protected static final int MILLI = MICRO * 1000;
+ protected static final int CENTI = MILLI * 10;
+ protected static final int DECI = MILLI * 100;
+ protected static final int SEC = MILLI * 1000;
+
+ protected static final int NAME_ANY = 0;
+ protected static final int NAME_TABLE = 1;
+ protected static final int NAME_SEQUENCE = 2;
+
+ private static final String ZERO_DATE_STR =
+ "'" + new java.sql.Date(0) + "'";
+ private static final String ZERO_TIME_STR = "'" + new Time(0) + "'";
+ private static final String ZERO_TIMESTAMP_STR =
+ "'" + new Timestamp(0) + "'";
+
+ private static final Localizer _loc = Localizer.forPackage
+ (DBDictionary.class);
+
+ // schema data
+ public String platform = "Generic";
+ public String driverVendor = null;
+ public String catalogSeparator = ".";
+ public boolean createPrimaryKeys = true;
+ public String constraintNameMode = CONS_NAME_BEFORE;
+ public int maxTableNameLength = 128;
+ public int maxColumnNameLength = 128;
+ public int maxConstraintNameLength = 128;
+ public int maxIndexNameLength = 128;
+ public int maxIndexesPerTable = Integer.MAX_VALUE;
+ public boolean supportsForeignKeys = true;
+ public boolean supportsUniqueConstraints = true;
+ public boolean supportsDeferredConstraints = true;
+ public boolean supportsRestrictDeleteAction = true;
+ public boolean supportsCascadeDeleteAction = true;
+ public boolean supportsNullDeleteAction = true;
+ public boolean supportsDefaultDeleteAction = true;
+ public boolean supportsRestrictUpdateAction = true;
+ public boolean supportsCascadeUpdateAction = true;
+ public boolean supportsNullUpdateAction = true;
+ public boolean supportsDefaultUpdateAction = true;
+ public boolean supportsAlterTableWithAddColumn = true;
+ public boolean supportsAlterTableWithDropColumn = true;
+ public String reservedWords = null;
+ public String systemSchemas = null;
+ public String systemTables = null;
+ public String fixedSizeTypeNames = null;
+ public String schemaCase = SCHEMA_CASE_UPPER;
+
+ // sql
+ public String validationSQL = null;
+ public String closePoolSQL = null;
+ public String initializationSQL = null;
+ public int joinSyntax = SYNTAX_SQL92;
+ public String outerJoinClause = "LEFT OUTER JOIN";
+ public String innerJoinClause = "INNER JOIN";
+ public String crossJoinClause = "CROSS JOIN";
+ public boolean requiresConditionForCrossJoin = false;
+ public String forUpdateClause = "FOR UPDATE";
+ public String tableForUpdateClause = null;
+ public String distinctCountColumnSeparator = null;
+ public boolean supportsSelectForUpdate = true;
+ public boolean supportsLockingWithDistinctClause = true;
+ public boolean supportsLockingWithMultipleTables = true;
+ public boolean supportsLockingWithOrderClause = true;
+ public boolean supportsLockingWithOuterJoin = true;
+ public boolean supportsLockingWithInnerJoin = true;
+ public boolean supportsLockingWithSelectRange = true;
+ public boolean supportsQueryTimeout = true;
+ public boolean simulateLocking = false;
+ public boolean supportsSubselect = true;
+ public boolean supportsCorrelatedSubselect = true;
+ public boolean supportsHaving = true;
+ public boolean supportsSelectStartIndex = false;
+ public boolean supportsSelectEndIndex = false;
+ public int rangePosition = RANGE_POST_SELECT;
+ public boolean requiresAliasForSubselect = false;
+ public boolean allowsAliasInBulkClause = true;
+ public boolean supportsMultipleNontransactionalResultSets = true;
+ public String searchStringEscape = "\\";
+ public boolean requiresCastForMathFunctions = false;
+ public boolean requiresCastForComparisons = false;
+ public boolean supportsModOperator = false;
+
+ // functions
+ public String castFunction = "CAST({0} AS {1})";
+ public String toLowerCaseFunction = "LOWER({0})";
+ public String toUpperCaseFunction = "UPPER({0})";
+ public String stringLengthFunction = "CHAR_LENGTH({0})";
+ public String bitLengthFunction = "(OCTET_LENGTH({0}) * 8)";
+ public String trimLeadingFunction = "TRIM(LEADING '{1}' FROM {0})";
+ public String trimTrailingFunction = "TRIM(TRAILING '{1}' FROM {0})";
+ public String trimBothFunction = "TRIM(BOTH '{1}' FROM {0})";
+ public String concatenateFunction = "({0}||{1})";
+ public String concatenateDelimiter = "'OPENJPATOKEN'";
+ public String substringFunctionName = "SUBSTRING";
+ public String currentDateFunction = "CURRENT_DATE";
+ public String currentTimeFunction = "CURRENT_TIME";
+ public String currentTimestampFunction = "CURRENT_TIMESTAMP";
+ public String dropTableSQL = "DROP TABLE {0}";
+
+ // types
+ public boolean storageLimitationsFatal = false;
+ public boolean storeLargeNumbersAsStrings = false;
+ public boolean storeCharsAsNumbers = true;
+ public boolean useGetBytesForBlobs = false;
+ public boolean useSetBytesForBlobs = false;
+ public boolean useGetObjectForBlobs = false;
+ public boolean useGetStringForClobs = false;
+ public boolean useSetStringForClobs = false;
+ public int maxEmbeddedBlobSize = -1;
+ public int maxEmbeddedClobSize = -1;
+ public int datePrecision = MILLI;
+ public int characterColumnSize = 255;
+ public String arrayTypeName = "ARRAY";
+ public String bigintTypeName = "BIGINT";
+ public String binaryTypeName = "BINARY";
+ public String bitTypeName = "BIT";
+ public String blobTypeName = "BLOB";
+ public String booleanTypeName = "BOOLEAN";
+ public String charTypeName = "CHAR";
+ public String clobTypeName = "CLOB";
+ public String dateTypeName = "DATE";
+ public String decimalTypeName = "DECIMAL";
+ public String distinctTypeName = "DISTINCT";
+ public String doubleTypeName = "DOUBLE";
+ public String floatTypeName = "FLOAT";
+ public String integerTypeName = "INTEGER";
+ public String javaObjectTypeName = "JAVA_OBJECT";
+ public String longVarbinaryTypeName = "LONGVARBINARY";
+ public String longVarcharTypeName = "LONGVARCHAR";
+ public String nullTypeName = "NULL";
+ public String numericTypeName = "NUMERIC";
+ public String otherTypeName = "OTHER";
+ public String realTypeName = "REAL";
+ public String refTypeName = "REF";
+ public String smallintTypeName = "SMALLINT";
+ public String structTypeName = "STRUCT";
+ public String timeTypeName = "TIME";
+ public String timestampTypeName = "TIMESTAMP";
+ public String tinyintTypeName = "TINYINT";
+ public String varbinaryTypeName = "VARBINARY";
+ public String varcharTypeName = "VARCHAR";
+
+ // schema metadata
+ public boolean useSchemaName = true;
+ public String tableTypes = "TABLE";
+ public boolean supportsSchemaForGetTables = true;
+ public boolean supportsSchemaForGetColumns = true;
+ public boolean supportsNullTableForGetColumns = true;
+ public boolean supportsNullTableForGetPrimaryKeys = false;
+ public boolean supportsNullTableForGetIndexInfo = false;
+ public boolean supportsNullTableForGetImportedKeys = false;
+ public boolean useGetBestRowIdentifierForPrimaryKeys = false;
+ public boolean requiresAutoCommitForMetaData = false;
+
+ // auto-increment
+ public int maxAutoAssignNameLength = 31;
+ public String autoAssignClause = null;
+ public String autoAssignTypeName = null;
+ public boolean supportsAutoAssign = false;
+ public String lastGeneratedKeyQuery = null;
+ public String nextSequenceQuery = null;
+
+ protected JDBCConfiguration conf = null;
+ protected Log log = null;
+ protected boolean connected = false;
+ protected final Set reservedWordSet = new HashSet();
+ protected final Set systemSchemaSet = new HashSet();
+ protected final Set systemTableSet = new HashSet();
+ protected final Set fixedSizeTypeNameSet = new HashSet();
+
+ // when we store values that lose precion, track the types so that the
+ // first time it happens we can warn the user
+ private Set _precisionWarnedTypes = null;
+
+ // cache lob methods
+ private Method _setBytes = null;
+ private Method _setString = null;
+ private Method _setCharStream = null;
+
+ public DBDictionary() {
+ fixedSizeTypeNameSet.addAll(Arrays.asList(new String[]{
+ "BIGINT", "BIT", "BLOB", "CLOB", "DATE", "DECIMAL", "DISTINCT",
+ "DOUBLE", "FLOAT", "INTEGER", "JAVA_OBJECT", "NULL", "NUMERIC",
+ "OTHER", "REAL", "REF", "SMALLINT", "STRUCT", "TIME", "TIMESTAMP",
+ "TINYINT",
+ }));
+ }
+
+ /**
+ * This method is called when the dictionary first sees any connection.
+ * It is used to initialize dictionary metadata if needed. If you
+ * override this method, be sure to call
+ * <code>super.connectedConfiguration</code>.
+ */
+ public void connectedConfiguration(Connection conn)
+ throws SQLException {
+ if (!connected) {
+ try {
+ if (log.isTraceEnabled())
+ log.trace(DBDictionaryFactory.toString
+ (conn.getMetaData()));
+ } catch (Exception e) {
+ log.trace(e.toString(), e);
+ }
+ }
+ connected = true;
+ }
+
+ //////////////////////
+ // ResultSet wrappers
+ //////////////////////
+
+ /**
+ * Convert the specified column of the SQL ResultSet to the proper
+ * java type.
+ */
+ public Array getArray(ResultSet rs, int column)
+ throws SQLException {
+ return rs.getArray(column);
+ }
+
+ /**
+ * Convert the specified column of the SQL ResultSet to the proper
+ * java type.
+ */
+ public InputStream getAsciiStream(ResultSet rs, int column)
+ throws SQLException {
+ return rs.getAsciiStream(column);
+ }
+
+ /**
+ * Convert the specified column of the SQL ResultSet to the proper
+ * java type.
+ */
+ public BigDecimal getBigDecimal(ResultSet rs, int column)
+ throws SQLException {
+ if (storeLargeNumbersAsStrings) {
+ String str = getString(rs, column);
+ return (str == null) ? null : new BigDecimal(str);
+ }
+ return rs.getBigDecimal(column);
+ }
+
+ /**
+ * Returns the specified column value as an unknown numeric type;
+ * we try from the most generic to the least generic.
+ */
+ public Number getNumber(ResultSet rs, int column)
+ throws SQLException {
+ // try from the most generic, and if errors occur, try
+ // less generic types; this enables us to handle values
+ // like Double.NaN without having to introspect on the
+ // ResultSetMetaData (bug #1053). note that we handle
+ // generic exceptions, since some drivers may throw
+ // NumberFormatExceptions, whereas others may throw SQLExceptions
+ try {
+ return getBigDecimal(rs, column);
+ } catch (Exception e1) {
+ try {
+ return new Double(getDouble(rs, column));
+ } catch (Exception e2) {
+ try {
+ return new Float(getFloat(rs, column));
+ } catch (Exception e3) {
+ try {
+ return Numbers.valueOf(getLong(rs, column));
+ } catch (Exception e4) {
+ try {
+ return Numbers.valueOf(getInt(rs, column));
+ } catch (Exception e5) {
+ }
+ }
+ }
+ }
+
+ if (e1 instanceof RuntimeException)
+ throw(RuntimeException) e1;
+ if (e1 instanceof SQLException)
+ throw(SQLException) e1;
+ }
+
+ return null;
+ }
+
+ /**
+ * Convert the specified column of the SQL ResultSet to the proper
+ * java type.
+ */
+ public BigInteger getBigInteger(ResultSet rs, int column)
+ throws SQLException {
+ if (storeLargeNumbersAsStrings) {
+ String str = getString(rs, column);
+ return (str == null) ? null : new BigDecimal(str).toBigInteger();
+ }
+ BigDecimal bd = getBigDecimal(rs, column);
+ return (bd == null) ? null : bd.toBigInteger();
+ }
+
+ /**
+ * Convert the specified column of the SQL ResultSet to the proper
+ * java type.
+ */
+ public InputStream getBinaryStream(ResultSet rs, int column)
+ throws SQLException {
+ return rs.getBinaryStream(column);
+ }
+
+ /**
+ * Convert the specified column of the SQL ResultSet to the proper
+ * java type.
+ */
+ public Blob getBlob(ResultSet rs, int column)
+ throws SQLException {
+ return rs.getBlob(column);
+ }
+
+ /**
+ * Convert the specified column of the SQL ResultSet to the proper
+ * java type.
+ */
+ public Object getBlobObject(ResultSet rs, int column, JDBCStore store)
+ throws SQLException {
+ InputStream in = null;
+ if (useGetBytesForBlobs || useGetObjectForBlobs) {
+ byte[] bytes = getBytes(rs, column);
+ if (bytes != null && bytes.length > 0)
+ in = new ByteArrayInputStream(bytes);
+ } else {
+ Blob blob = getBlob(rs, column);
+ if (blob != null && blob.length() > 0)
+ in = blob.getBinaryStream();
+ }
+ if (in == null)
+ return null;
+
+ try {
+ if (store == null)
+ return Serialization.deserialize(in, null);
+ return Serialization.deserialize(in, store.getContext());
+ } finally {
+ try {
+ in.close();
+ } catch (IOException ioe) {
+ }
+ }
+ }
+
+ /**
+ * Convert the specified column of the SQL ResultSet to the proper
+ * java type.
+ */
+ public boolean getBoolean(ResultSet rs, int column)
+ throws SQLException {
+ return rs.getBoolean(column);
+ }
+
+ /**
+ * Convert the specified column of the SQL ResultSet to the proper
+ * java type.
+ */
+ public byte getByte(ResultSet rs, int column)
+ throws SQLException {
+ return rs.getByte(column);
+ }
+
+ /**
+ * Convert the specified column of the SQL ResultSet to the proper
+ * java type.
+ */
+ public byte[] getBytes(ResultSet rs, int column)
+ throws SQLException {
+ if (useGetBytesForBlobs)
+ return rs.getBytes(column);
+ if (useGetObjectForBlobs)
+ return (byte[]) rs.getObject(column);
+
+ Blob blob = getBlob(rs, column);
+ if (blob == null)
+ return null;
+ int length = (int) blob.length();
+ if (length == 0)
+ return null;
+ return blob.getBytes(1, length);
+ }
+
+ /**
+ * Convert the specified column of the SQL ResultSet to the proper
+ * java type. Converts the date from a {@link Timestamp} by default.
+ */
+ public Calendar getCalendar(ResultSet rs, int column)
+ throws SQLException {
+ Date d = getDate(rs, column);
+ if (d == null)
+ return null;
+
+ Calendar cal = Calendar.getInstance();
+ cal.setTime(d);
+ return cal;
+ }
+
+ /**
+ * Convert the specified column of the SQL ResultSet to the proper
+ * java type.
+ */
+ public char getChar(ResultSet rs, int column)
+ throws SQLException {
+ if (storeCharsAsNumbers)
+ return (char) getInt(rs, column);
+
+ String str = getString(rs, column);
+ if (str == null || str.length() == 0)
+ return 0;
+ return str.charAt(0);
+ }
+
+ /**
+ * Convert the specified column of the SQL ResultSet to the proper
+ * java type.
+ */
+ public Reader getCharacterStream(ResultSet rs, int column)
+ throws SQLException {
+ return rs.getCharacterStream(column);
+ }
+
+ /**
+ * Convert the specified column of the SQL ResultSet to the proper
+ * java type.
+ */
+ public Clob getClob(ResultSet rs, int column)
+ throws SQLException {
+ return rs.getClob(column);
+ }
+
+ /**
+ * Convert the specified column of the SQL ResultSet to the proper
+ * java type.
+ */
+ public String getClobString(ResultSet rs, int column)
+ throws SQLException {
+ if (useGetStringForClobs)
+ return rs.getString(column);
+
+ Clob clob = getClob(rs, column);
+ if (clob == null)
+ return null;
+ if (clob.length() == 0)
+ return "";
+
+ // unlikely that we'll have strings over Integer.MAX_VALUE chars
+ return clob.getSubString(1, (int) clob.length());
+ }
+
+ /**
+ * Convert the specified column of the SQL ResultSet to the proper
+ * java type. Converts the date from a {@link Timestamp} by default.
+ */
+ public Date getDate(ResultSet rs, int column)
+ throws SQLException {
+ Timestamp tstamp = getTimestamp(rs, column, null);
+ if (tstamp == null)
+ return null;
+
+ // get the fractional seconds component, rounding away anything beyond
+ // milliseconds
+ int fractional = (int) Math.round(tstamp.getNanos() / (double) MILLI);
+
+ // get the millis component; some JDBC drivers round this to the
+ // nearest second, while others do not
+ long millis = (tstamp.getTime() / 1000L) * 1000L;
+ return new Date(millis + fractional);
+ }
+
+ /**
+ * Convert the specified column of the SQL ResultSet to the proper
+ * java type.
+ */
+ public java.sql.Date getDate(ResultSet rs, int column, Calendar cal)
+ throws SQLException {
+ if (cal == null)
+ return rs.getDate(column);
+ return rs.getDate(column, cal);
+ }
+
+ /**
+ * Convert the specified column of the SQL ResultSet to the proper
+ * java type.
+ */
+ public double getDouble(ResultSet rs, int column)
+ throws SQLException {
+ return rs.getDouble(column);
+ }
+
+ /**
+ * Convert the specified column of the SQL ResultSet to the proper
+ * java type.
+ */
+ public float getFloat(ResultSet rs, int column)
+ throws SQLException {
+ return rs.getFloat(column);
+ }
+
+ /**
+ * Convert the specified column of the SQL ResultSet to the proper
+ * java type.
+ */
+ public int getInt(ResultSet rs, int column)
+ throws SQLException {
+ return rs.getInt(column);
+ }
+
+ /**
+ * Convert the specified column of the SQL ResultSet to the proper
+ * java type.
+ */
+ public Locale getLocale(ResultSet rs, int column)
+ throws SQLException {
+ String str = getString(rs, column);
+ if (str == null || str.length() == 0)
+ return null;
+
+ String[] params = Strings.split(str, "_", 3);
+ if (params.length < 3)
+ return null;
+ return new Locale(params[0], params[1], params[2]);
+ }
+
+ /**
+ * Convert the specified column of the SQL ResultSet to the proper
+ * java type.
+ */
+ public long getLong(ResultSet rs, int column)
+ throws SQLException {
+ return rs.getLong(column);
+ }
+
+ /**
+ * Convert the specified column of the SQL ResultSet to the proper
+ * java type.
+ */
+ public Object getObject(ResultSet rs, int column, Map map)
+ throws SQLException {
+ if (map == null)
+ return rs.getObject(column);
+ return rs.getObject(column, map);
+ }
+
+ /**
+ * Convert the specified column of the SQL ResultSet to the proper
+ * java type.
+ */
+ public Ref getRef(ResultSet rs, int column, Map map)
+ throws SQLException {
+ return rs.getRef(column);
+ }
+
+ /**
+ * Convert the specified column of the SQL ResultSet to the proper
+ * java type.
+ */
+ public short getShort(ResultSet rs, int column)
+ throws SQLException {
+ return rs.getShort(column);
+ }
+
+ /**
+ * Convert the specified column of the SQL ResultSet to the proper
+ * java type.
+ */
+ public String getString(ResultSet rs, int column)
+ throws SQLException {
+ return rs.getString(column);
+ }
+
+ /**
+ * Convert the specified column of the SQL ResultSet to the proper
+ * java type.
+ */
+ public Time getTime(ResultSet rs, int column, Calendar cal)
+ throws SQLException {
+ if (cal == null)
+ return rs.getTime(column);
+ return rs.getTime(column, cal);
+ }
+
+ /**
+ * Convert the specified column of the SQL ResultSet to the proper
+ * java type.
+ */
+ public Timestamp getTimestamp(ResultSet rs, int column, Calendar cal)
+ throws SQLException {
+ if (cal == null)
+ return rs.getTimestamp(column);
+ return rs.getTimestamp(column, cal);
+ }
+
+ //////////////////////////////
+ // PreparedStatement wrappers
+ //////////////////////////////
+
+ /**
+ * Set the given value as a parameter to the statement.
+ */
+ public void setArray(PreparedStatement stmnt, int idx, Array val,
+ Column col)
+ throws SQLException {
+ stmnt.setArray(idx, val);
+ }
+
+ /**
+ * Set the given value as a parameter to the statement.
+ */
+ public void setAsciiStream(PreparedStatement stmnt, int idx,
+ InputStream val, int length, Column col)
+ throws SQLException {
+ stmnt.setAsciiStream(idx, val, length);
+ }
+
+ /**
+ * Set the given value as a parameter to the statement.
+ */
+ public void setBigDecimal(PreparedStatement stmnt, int idx, BigDecimal val,
+ Column col)
+ throws SQLException {
+ if ((col != null && col.isCompatible(Types.VARCHAR, 0))
+ || (col == null && storeLargeNumbersAsStrings))
+ setString(stmnt, idx, val.toString(), col);
+ else
+ stmnt.setBigDecimal(idx, val);
+ }
+
+ /**
+ * Set the given value as a parameter to the statement.
+ */
+ public void setBigInteger(PreparedStatement stmnt, int idx, BigInteger val,
+ Column col)
+ throws SQLException {
+ if ((col != null && col.isCompatible(Types.VARCHAR, 0))
+ || (col == null && storeLargeNumbersAsStrings))
+ setString(stmnt, idx, val.toString(), col);
+ else
+ setBigDecimal(stmnt, idx, new BigDecimal(val), col);
+ }
+
+ /**
+ * Set the given value as a parameter to the statement.
+ */
+ public void setBinaryStream(PreparedStatement stmnt, int idx,
+ InputStream val, int length, Column col)
+ throws SQLException {
+ stmnt.setBinaryStream(idx, val, length);
+ }
+
+ /**
+ * Set the given value as a parameter to the statement.
+ */
+ public void setBlob(PreparedStatement stmnt, int idx, Blob val, Column col)
+ throws SQLException {
+ stmnt.setBlob(idx, val);
+ }
+
+ /**
+ * Set the given value as a parameter to the statement. Uses the
+ * {@link #serialize} method to serialize the value.
+ */
+ public void setBlobObject(PreparedStatement stmnt, int idx, Object val,
+ Column col, JDBCStore store)
+ throws SQLException {
+ setBytes(stmnt, idx, serialize(val, store), col);
+ }
+
+ /**
+ * Set the given value as a parameter to the statement.
+ */
+ public void setBoolean(PreparedStatement stmnt, int idx, boolean val,
+ Column col)
+ throws SQLException {
+ stmnt.setInt(idx, (val) ? 1 : 0);
+ }
+
+ /**
+ * Set the given value as a parameter to the statement.
+ */
+ public void setByte(PreparedStatement stmnt, int idx, byte val, Column col)
+ throws SQLException {
+ stmnt.setByte(idx, val);
+ }
+
+ /**
+ * Set the given value as a parameter to the statement.
+ */
+ public void setBytes(PreparedStatement stmnt, int idx, byte[] val,
+ Column col)
+ throws SQLException {
+ if (useSetBytesForBlobs)
+ stmnt.setBytes(idx, val);
+ else
+ setBinaryStream(stmnt, idx, new ByteArrayInputStream(val),
+ val.length, col);
+ }
+
+ /**
+ * Set the given value as a parameter to the statement.
+ */
+ public void setChar(PreparedStatement stmnt, int idx, char val, Column col)
+ throws SQLException {
+ if ((col != null && col.isCompatible(Types.INTEGER, 0))
+ || (col == null && storeCharsAsNumbers))
+ setInt(stmnt, idx, (int) val, col);
+ else
+ setString(stmnt, idx, String.valueOf(val), col);
+ }
+
+ /**
+ * Set the given value as a parameter to the statement.
+ */
+ public void setCharacterStream(PreparedStatement stmnt, int idx,
+ Reader val, int length, Column col)
+ throws SQLException {
+ stmnt.setCharacterStream(idx, val, length);
+ }
+
+ /**
+ * Set the given value as a parameter to the statement.
+ */
+ public void setClob(PreparedStatement stmnt, int idx, Clob val, Column col)
+ throws SQLException {
+ stmnt.setClob(idx, val);
+ }
+
+ /**
+ * Set the given value as a parameter to the statement.
+ */
+ public void setClobString(PreparedStatement stmnt, int idx, String val,
+ Column col)
+ throws SQLException {
+ if (useSetStringForClobs)
+ stmnt.setString(idx, val);
+ else {
+ // set reader from string
+ StringReader in = new StringReader(val);
+ setCharacterStream(stmnt, idx, in, val.length(), col);
+ }
+ }
+
+ /**
+ * Set the given value as a parameter to the statement.
+ */
+ public void setDate(PreparedStatement stmnt, int idx, Date val, Column col)
+ throws SQLException {
+ if (col != null && col.getType() == Types.DATE)
+ setDate(stmnt, idx, new java.sql.Date(val.getTime()), null, col);
+ else if (col != null && col.getType() == Types.TIME)
+ setTime(stmnt, idx, new Time(val.getTime()), null, col);
+ else
+ setTimestamp(stmnt, idx, new Timestamp(val.getTime()), null, col);
+ }
+
+ /**
+ * Set the given value as a parameter to the statement.
+ */
+ public void setDate(PreparedStatement stmnt, int idx, java.sql.Date val,
+ Calendar cal, Column col)
+ throws SQLException {
+ if (cal == null)
+ stmnt.setDate(idx, val);
+ else
+ stmnt.setDate(idx, val, cal);
+ }
+
+ /**
+ * Set the given value as a parameter to the statement.
+ */
+ public void setCalendar(PreparedStatement stmnt, int idx, Calendar val,
+ Column col)
+ throws SQLException {
+ // by default we merely delegate the the Date parameter
+ setDate(stmnt, idx, val.getTime(), col);
+ }
+
+ /**
+ * Set the given value as a parameter to the statement.
+ */
+ public void setDouble(PreparedStatement stmnt, int idx, double val,
+ Column col)
+ throws SQLException {
+ stmnt.setDouble(idx, val);
+ }
+
+ /**
+ * Set the given value as a parameter to the statement.
+ */
+ public void setFloat(PreparedStatement stmnt, int idx, float val,
+ Column col)
+ throws SQLException {
+ stmnt.setFloat(idx, val);
+ }
+
+ /**
+ * Set the given value as a parameter to the statement.
+ */
+ public void setInt(PreparedStatement stmnt, int idx, int val, Column col)
+ throws SQLException {
+ stmnt.setInt(idx, val);
+ }
+
+ /**
+ * Set the given value as a parameter to the statement.
+ */
+ public void setLong(PreparedStatement stmnt, int idx, long val, Column col)
+ throws SQLException {
+ stmnt.setLong(idx, val);
+ }
+
+ /**
+ * Set the given value as a parameter to the statement.
+ */
+ public void setLocale(PreparedStatement stmnt, int idx, Locale val,
+ Column col)
+ throws SQLException {
+ setString(stmnt, idx, val.getLanguage() + "_" + val.getCountry()
+ + "_" + val.getVariant(), col);
+ }
+
+ /**
+ * Set the given value as a parameters to the statement. The column
+ * type will come from {@link Types}.
+ */
+ public void setNull(PreparedStatement stmnt, int idx, int colType,
+ Column col)
+ throws SQLException {
+ stmnt.setNull(idx, colType);
+ }
+
+ /**
+ * Set the given value as a parameter to the statement.
+ */
+ public void setNumber(PreparedStatement stmnt, int idx, Number num,
+ Column col)
+ throws SQLException {
+ // check for known floating point types to give driver a chance to
+ // handle special numbers like NaN and infinity; bug #1053
+ if (num instanceof Double)
+ setDouble(stmnt, idx, ((Double) num).doubleValue(), col);
+ else if (num instanceof Float)
+ setFloat(stmnt, idx, ((Float) num).floatValue(), col);
+ else
+ setBigDecimal(stmnt, idx, new BigDecimal(num.toString()), col);
+ }
+
+ /**
+ * Set the given value as a parameters to the statement. The column
+ * type will come from {@link Types}.
+ */
+ public void setObject(PreparedStatement stmnt, int idx, Object val,
+ int colType, Column col)
+ throws SQLException {
+ if (colType == -1 || colType == Types.OTHER)
+ stmnt.setObject(idx, val);
+ else
+ stmnt.setObject(idx, val, colType);
+ }
+
+ /**
+ * Set the given value as a parameter to the statement.
+ */
+ public void setRef(PreparedStatement stmnt, int idx, Ref val, Column col)
+ throws SQLException {
+ stmnt.setRef(idx, val);
+ }
+
+ /**
+ * Set the given value as a parameter to the statement.
+ */
+ public void setShort(PreparedStatement stmnt, int idx, short val,
+ Column col)
+ throws SQLException {
+ stmnt.setShort(idx, val);
+ }
+
+ /**
+ * Set the given value as a parameter to the statement.
+ */
+ public void setString(PreparedStatement stmnt, int idx, String val,
+ Column col)
+ throws SQLException {
+ stmnt.setString(idx, val);
+ }
+
+ /**
+ * Set the given value as a parameter to the statement.
+ */
+ public void setTime(PreparedStatement stmnt, int idx, Time val,
+ Calendar cal, Column col)
+ throws SQLException {
+ if (cal == null)
+ stmnt.setTime(idx, val);
+ else
+ stmnt.setTime(idx, val, cal);
+ }
+
+ /**
+ * Set the given value as a parameter to the statement.
+ */
+ public void setTimestamp(PreparedStatement stmnt, int idx,
+ Timestamp val, Calendar cal, Column col)
+ throws SQLException {
+ // ensure that we do not insert dates at a greater precision than
+ // that at which they will be returned by a SELECT
+ int rounded = (int) Math.round(val.getNanos() /
+ (double) datePrecision);
+ int nanos = rounded * datePrecision;
+ if (nanos > 999999999) {
+ // rollover to next second
+ val.setTime(val.getTime() + 1000);
+ nanos = 0;
+ }
+ val.setNanos(nanos);
+
+ if (cal == null)
+ stmnt.setTimestamp(idx, val);
+ else
+ stmnt.setTimestamp(idx, val, cal);
+ }
+
+ /**
+ * Set a column value into a prepared statement.
+ *
+ * @param stmnt the prepared statement to parameterize
+ * @param idx the index of the parameter in the prepared statement
+ * @param val the value of the column
+ * @param col the column being set
+ * @param type the field mapping type code for the value
+ * @param store the store manager for the current context
+ */
+ public void setTyped(PreparedStatement stmnt, int idx, Object val,
+ Column col, int type, JDBCStore store)
+ throws SQLException {
+ if (val == null) {
+ setNull(stmnt, idx, (col == null) ? Types.OTHER : col.getType(),
+ col);
+ return;
+ }
+
+ Sized s;
+ Calendard c;
+ switch (type) {
+ case JavaTypes.BOOLEAN:
+ case JavaTypes.BOOLEAN_OBJ:
+ setBoolean(stmnt, idx, ((Boolean) val).booleanValue(), col);
+ break;
+ case JavaTypes.BYTE:
+ case JavaTypes.BYTE_OBJ:
+ setByte(stmnt, idx, ((Number) val).byteValue(), col);
+ break;
+ case JavaTypes.CHAR:
+ case JavaTypes.CHAR_OBJ:
+ setChar(stmnt, idx, ((Character) val).charValue(), col);
+ break;
+ case JavaTypes.DOUBLE:
+ case JavaTypes.DOUBLE_OBJ:
+ setDouble(stmnt, idx, ((Number) val).doubleValue(), col);
+ break;
+ case JavaTypes.FLOAT:
+ case JavaTypes.FLOAT_OBJ:
+ setFloat(stmnt, idx, ((Number) val).floatValue(), col);
+ break;
+ case JavaTypes.INT:
+ case JavaTypes.INT_OBJ:
+ setInt(stmnt, idx, ((Number) val).intValue(), col);
+ break;
+ case JavaTypes.LONG:
+ case JavaTypes.LONG_OBJ:
+ setLong(stmnt, idx, ((Number) val).longValue(), col);
+ break;
+ case JavaTypes.SHORT:
+ case JavaTypes.SHORT_OBJ:
+ setShort(stmnt, idx, ((Number) val).shortValue(), col);
+ break;
+ case JavaTypes.STRING:
+ if (col != null && (col.getType() == Types.CLOB
+ || col.getType() == Types.LONGVARCHAR))
+ setClobString(stmnt, idx, (String) val, col);
+ else
+ setString(stmnt, idx, (String) val, col);
+ break;
+ case JavaTypes.OBJECT:
+ setBlobObject(stmnt, idx, val, col, store);
+ break;
+ case JavaTypes.DATE:
+ setDate(stmnt, idx, (Date) val, col);
+ break;
+ case JavaTypes.CALENDAR:
+ setCalendar(stmnt, idx, (Calendar) val, col);
+ break;
+ case JavaTypes.BIGDECIMAL:
+ setBigDecimal(stmnt, idx, (BigDecimal) val, col);
+ break;
+ case JavaTypes.BIGINTEGER:
+ setBigInteger(stmnt, idx, (BigInteger) val, col);
+ break;
+ case JavaTypes.NUMBER:
+ setNumber(stmnt, idx, (Number) val, col);
+ break;
+ case JavaTypes.LOCALE:
+ setLocale(stmnt, idx, (Locale) val, col);
+ break;
+ case JavaSQLTypes.SQL_ARRAY:
+ setArray(stmnt, idx, (Array) val, col);
+ break;
+ case JavaSQLTypes.ASCII_STREAM:
+ s = (Sized) val;
+ setAsciiStream(stmnt, idx, (InputStream) s.value, s.size, col);
+ break;
+ case JavaSQLTypes.BINARY_STREAM:
+ s = (Sized) val;
+ setBinaryStream(stmnt, idx, (InputStream) s.value, s.size, col);
+ break;
+ case JavaSQLTypes.BLOB:
+ setBlob(stmnt, idx, (Blob) val, col);
+ break;
+ case JavaSQLTypes.BYTES:
+ setBytes(stmnt, idx, (byte[]) val, col);
+ break;
+ case JavaSQLTypes.CHAR_STREAM:
+ s = (Sized) val;
+ setCharacterStream(stmnt, idx, (Reader) s.value, s.size, col);
+ break;
+ case JavaSQLTypes.CLOB:
+ setClob(stmnt, idx, (Clob) val, col);
+ break;
+ case JavaSQLTypes.SQL_DATE:
+ if (val instanceof Calendard) {
+ c = (Calendard) val;
+ setDate(stmnt, idx, (java.sql.Date) c.value, c.calendar,
+ col);
+ } else
+ setDate(stmnt, idx, (java.sql.Date) val, null, col);
+ break;
+ case JavaSQLTypes.REF:
+ setRef(stmnt, idx, (Ref) val, col);
+ break;
+ case JavaSQLTypes.TIME:
+ if (val instanceof Calendard) {
+ c = (Calendard) val;
+ setTime(stmnt, idx, (Time) c.value, c.calendar, col);
+ } else
+ setTime(stmnt, idx, (Time) val, null, col);
+ break;
+ case JavaSQLTypes.TIMESTAMP:
+ if (val instanceof Calendard) {
+ c = (Calendard) val;
+ setTimestamp(stmnt, idx, (Timestamp) c.value, c.calendar,
+ col);
+ } else
+ setTimestamp(stmnt, idx, (Timestamp) val, null, col);
+ break;
+ default:
+ if (col != null && (col.getType() == Types.BLOB
+ || col.getType() == Types.VARBINARY))
+ setBlobObject(stmnt, idx, val, col, store);
+ else
+ setObject(stmnt, idx, val, col.getType(), col);
+ }
+ }
+
+ /**
+ * Set a completely unknown parameter into a prepared statement.
+ */
+ public void setUnknown(PreparedStatement stmnt, int idx, Object val,
+ Column col)
+ throws SQLException {
+ Sized sized = null;
+ Calendard cald = null;
+ if (val instanceof Sized) {
+ sized = (Sized) val;
+ val = sized.value;
+ } else if (val instanceof Calendard) {
+ cald = (Calendard) val;
+ val = cald.value;
+ }
+
+ if (val == null)
+ setNull(stmnt, idx, (col == null) ? Types.OTHER : col.getType(),
+ col);
+ else if (val instanceof String)
+ setString(stmnt, idx, val.toString(), col);
+ else if (val instanceof Integer)
+ setInt(stmnt, idx, ((Integer) val).intValue(), col);
+ else if (val instanceof Boolean)
+ setBoolean(stmnt, idx, ((Boolean) val).booleanValue(), col);
+ else if (val instanceof Long)
+ setLong(stmnt, idx, ((Long) val).longValue(), col);
+ else if (val instanceof Float)
+ setFloat(stmnt, idx, ((Float) val).floatValue(), col);
+ else if (val instanceof Double)
+ setDouble(stmnt, idx, ((Double) val).doubleValue(), col);
+ else if (val instanceof Byte)
+ setByte(stmnt, idx, ((Byte) val).byteValue(), col);
+ else if (val instanceof Character)
+ setChar(stmnt, idx, ((Character) val).charValue(), col);
+ else if (val instanceof Short)
+ setShort(stmnt, idx, ((Short) val).shortValue(), col);
+ else if (val instanceof Locale)
+ setLocale(stmnt, idx, (Locale) val, col);
+ else if (val instanceof BigDecimal)
+ setBigDecimal(stmnt, idx, (BigDecimal) val, col);
+ else if (val instanceof BigInteger)
+ setBigInteger(stmnt, idx, (BigInteger) val, col);
+ else if (val instanceof Array)
+ setArray(stmnt, idx, (Array) val, col);
+ else if (val instanceof Blob)
+ setBlob(stmnt, idx, (Blob) val, col);
+ else if (val instanceof byte[])
+ setBytes(stmnt, idx, (byte[]) val, col);
+ else if (val instanceof Clob)
+ setClob(stmnt, idx, (Clob) val, col);
+ else if (val instanceof Ref)
+ setRef(stmnt, idx, (Ref) val, col);
+ else if (val instanceof java.sql.Date)
+ setDate(stmnt, idx, (java.sql.Date) val,
+ (cald == null) ? null : cald.calendar, col);
+ else if (val instanceof Timestamp)
+ setTimestamp(stmnt, idx, (Timestamp) val,
+ (cald == null) ? null : cald.calendar, col);
+ else if (val instanceof Time)
+ setTime(stmnt, idx, (Time) val,
+ (cald == null) ? null : cald.calendar, col);
+ else if (val instanceof Date)
+ setDate(stmnt, idx, (Date) val, col);
+ else if (val instanceof Calendar)
+ setDate(stmnt, idx, ((Calendar) val).getTime(), col);
+ else if (val instanceof Reader)
+ setCharacterStream(stmnt, idx, (Reader) val,
+ (sized == null) ? 0 : sized.size, col);
+ else
+ throw new UserException(_loc.get("bad-param", val.getClass()));
+ }
+
+ /**
+ * Return the serialized bytes for the given object.
+ */
+ public byte[] serialize(Object val, JDBCStore store)
+ throws SQLException {
+ if (val == null)
+ return null;
+ if (val instanceof SerializedData)
+ return ((SerializedData) val).bytes;
+ return Serialization.serialize(val, store.getContext());
+ }
+
+ /**
+ * Invoke the JDK 1.4 <code>setBytes</code> method on the given BLOB object.
+ */
+ public void putBytes(Object blob, byte[] data)
+ throws SQLException {
+ if (_setBytes == null) {
+ try {
+ _setBytes = blob.getClass().getMethod("setBytes",
+ new Class[]{ long.class, byte[].class });
+ } catch (Exception e) {
+ throw new StoreException(e);
+ }
+ }
+ invokePutLobMethod(_setBytes, blob,
+ new Object[]{ Numbers.valueOf(1L), data });
+ }
+
+ /**
+ * Invoke the JDK 1.4 <code>setString</code> method on the given CLOB
+ * object.
+ */
+ public void putString(Object clob, String data)
+ throws SQLException {
+ if (_setString == null) {
+ try {
+ _setString = clob.getClass().getMethod("setString",
+ new Class[]{ long.class, String.class });
+ } catch (Exception e) {
+ throw new StoreException(e);
+ }
+ }
+ invokePutLobMethod(_setString, clob,
+ new Object[]{ Numbers.valueOf(1L), data });
+ }
+
+ /**
+ * Invoke the JDK 1.4 <code>setCharacterStream</code> method on the given
+ * CLOB object.
+ */
+ public void putChars(Object clob, char[] data)
+ throws SQLException {
+ if (_setCharStream == null) {
+ try {
+ _setCharStream = clob.getClass().getMethod
+ ("setCharacterStream", new Class[]{ long.class });
+ } catch (Exception e) {
+ throw new StoreException(e);
+ }
+ }
+
+ Writer writer = (Writer) invokePutLobMethod(_setCharStream, clob,
+ new Object[]{ Numbers.valueOf(1L) });
+ try {
+ writer.write(data);
+ writer.flush();
+ } catch (IOException ioe) {
+ throw new SQLException(ioe.toString());
+ }
+ }
+
+ /**
+ * Invoke the given LOB method on the given target with the given data.
+ */
+ private static Object invokePutLobMethod(Method method, Object target,
+ Object[] args)
+ throws SQLException {
+ try {
+ return method.invoke(target, args);
+ } catch (InvocationTargetException ite) {
+ Throwable t = ite.getTargetException();
+ if (t instanceof SQLException)
+ throw(SQLException) t;
+ throw new StoreException(t);
+ } catch (Exception e) {
+ throw new StoreException(e);
+ }
+ }
+
+ /**
+ * Warn that a particular value could not be stored precisely.
+ * After the first warning for a particular type, messages
+ * will be turned into trace messages.
+ */
+ protected void storageWarning(Object orig, Object converted) {
+ boolean warn;
+ synchronized (this) {
+ if (_precisionWarnedTypes == null)
+ _precisionWarnedTypes = new HashSet();
+ warn = _precisionWarnedTypes.add(orig.getClass());
+ }
+
+ if (storageLimitationsFatal || (warn && log.isWarnEnabled())
+ || (!warn && log.isTraceEnabled())) {
+ String msg = _loc.get("storage-restriction", new Object[]{
+ platform,
+ orig,
+ orig.getClass().getName(),
+ converted,
+ });
+
+ if (storageLimitationsFatal)
+ throw new StoreException(msg);
+
+ if (warn)
+ log.warn(msg);
+ else
+ log.trace(msg);
+ }
+ }
+
+ /////////
+ // Types
+ /////////
+
+ /**
+ * Return the preferred {@link Types} constant for the given
+ * {@link JavaTypes} or {@link JavaSQLTypes} constant.
+ */
+ public int getJDBCType(int metaTypeCode, boolean lob) {
+ if (lob) {
+ switch (metaTypeCode) {
+ case JavaTypes.STRING:
+ case JavaSQLTypes.ASCII_STREAM:
+ case JavaSQLTypes.CHAR_STREAM:
+ return getPreferredType(Types.CLOB);
+ default:
+ return getPreferredType(Types.BLOB);
+ }
+ }
+
+ switch (metaTypeCode) {
+ case JavaTypes.BOOLEAN:
+ case JavaTypes.BOOLEAN_OBJ:
+ return getPreferredType(Types.BIT);
+ case JavaTypes.BYTE:
+ case JavaTypes.BYTE_OBJ:
+ return getPreferredType(Types.TINYINT);
+ case JavaTypes.CHAR:
+ case JavaTypes.CHAR_OBJ:
+ if (storeCharsAsNumbers)
+ return getPreferredType(Types.INTEGER);
+ return getPreferredType(Types.CHAR);
+ case JavaTypes.DOUBLE:
+ case JavaTypes.DOUBLE_OBJ:
+ return getPreferredType(Types.DOUBLE);
+ case JavaTypes.FLOAT:
+ case JavaTypes.FLOAT_OBJ:
+ return getPreferredType(Types.REAL);
+ case JavaTypes.INT:
+ case JavaTypes.INT_OBJ:
+ return getPreferredType(Types.INTEGER);
+ case JavaTypes.LONG:
+ case JavaTypes.LONG_OBJ:
+ return getPreferredType(Types.BIGINT);
+ case JavaTypes.SHORT:
+ case JavaTypes.SHORT_OBJ:
+ return getPreferredType(Types.SMALLINT);
+ case JavaTypes.STRING:
+ case JavaTypes.LOCALE:
+ case JavaSQLTypes.ASCII_STREAM:
+ case JavaSQLTypes.CHAR_STREAM:
+ return getPreferredType(Types.VARCHAR);
+ case JavaTypes.BIGINTEGER:
+ if (storeLargeNumbersAsStrings)
+ return getPreferredType(Types.VARCHAR);
+ return getPreferredType(Types.BIGINT);
+ case JavaTypes.BIGDECIMAL:
+ if (storeLargeNumbersAsStrings)
+ return getPreferredType(Types.VARCHAR);
+ return getPreferredType(Types.DOUBLE);
+ case JavaTypes.NUMBER:
+ if (storeLargeNumbersAsStrings)
+ return getPreferredType(Types.VARCHAR);
+ return getPreferredType(Types.NUMERIC);
+ case JavaTypes.CALENDAR:
+ case JavaTypes.DATE:
+ return getPreferredType(Types.TIMESTAMP);
+ case JavaSQLTypes.SQL_ARRAY:
+ return getPreferredType(Types.ARRAY);
+ case JavaSQLTypes.BINARY_STREAM:
+ case JavaSQLTypes.BLOB:
+ case JavaSQLTypes.BYTES:
+ return getPreferredType(Types.BLOB);
+ case JavaSQLTypes.CLOB:
+ return getPreferredType(Types.CLOB);
+ case JavaSQLTypes.SQL_DATE:
+ return getPreferredType(Types.DATE);
+ case JavaSQLTypes.TIME:
+ return getPreferredType(Types.TIME);
+ case JavaSQLTypes.TIMESTAMP:
+ return getPreferredType(Types.TIMESTAMP);
+ default:
+ return getPreferredType(Types.BLOB);
+ }
+ }
+
+ /**
+ * Return the preferred {@link Types} type for the given one. Returns
+ * the given type by default.
+ */
+ public int getPreferredType(int type) {
+ return type;
+ }
+
+ /**
+ * Return the preferred database type name for the given column's type
+ * from {@link Types}.
+ */
+ public String getTypeName(Column col) {
+ if (col.getTypeName() != null && col.getTypeName().length() > 0)
+ return appendSize(col, col.getTypeName());
+
+ if (col.isAutoAssigned() && autoAssignTypeName != null)
+ return appendSize(col, autoAssignTypeName);
+
+ return appendSize(col, getTypeName(col.getType()));
+ }
+
+ /**
+ * Returns the type name for the specific constant as defined
+ * by {@link java.sql.Types}.
+ *
+ * @param type the type
+ * @return the name for the type
+ */
+ public String getTypeName(int type) {
+ switch (type) {
+ case Types.ARRAY:
+ return arrayTypeName;
+ case Types.BIGINT:
+ return bigintTypeName;
+ case Types.BINARY:
+ return binaryTypeName;
+ case Types.BIT:
+ return bitTypeName;
+ case Types.BLOB:
+ return blobTypeName;
+ case 16: // JDK 1.4 introduces Types.BOOLEAN, whose value is 16
+ return booleanTypeName;
+ case Types.CHAR:
+ return charTypeName;
+ case Types.CLOB:
+ return clobTypeName;
+ case Types.DATE:
+ return dateTypeName;
+ case Types.DECIMAL:
+ return decimalTypeName;
+ case Types.DISTINCT:
+ return distinctTypeName;
+ case Types.DOUBLE:
+ return doubleTypeName;
+ case Types.FLOAT:
+ return floatTypeName;
+ case Types.INTEGER:
+ return integerTypeName;
+ case Types.JAVA_OBJECT:
+ return javaObjectTypeName;
+ case Types.LONGVARBINARY:
+ return longVarbinaryTypeName;
+ case Types.LONGVARCHAR:
+ return longVarcharTypeName;
+ case Types.NULL:
+ return nullTypeName;
+ case Types.NUMERIC:
+ return numericTypeName;
+ case Types.OTHER:
+ return otherTypeName;
+ case Types.REAL:
+ return realTypeName;
+ case Types.REF:
+ return refTypeName;
+ case Types.SMALLINT:
+ return smallintTypeName;
+ case Types.STRUCT:
+ return structTypeName;
+ case Types.TIME:
+ return timeTypeName;
+ case Types.TIMESTAMP:
+ return timestampTypeName;
+ case Types.TINYINT:
+ return tinyintTypeName;
+ case Types.VARBINARY:
+ return varbinaryTypeName;
+ case Types.VARCHAR:
+ return varcharTypeName;
+ default:
+ return otherTypeName;
+ }
+ }
+
+ /**
+ * Helper method to add size properties to the specified type.
+ * If present, the string "{0}" will be replaced with the size definition;
+ * otherwise the size definition will be appended to the type name.
+ * If your database has column types that don't allow size definitions,
+ * override this method to return the unaltered type name for columns of
+ * those types (or add the type names to the
+ * <code>fixedSizeTypeNameSet</code>).
+ */
+ protected String appendSize(Column col, String typeName) {
+ if (fixedSizeTypeNameSet.contains(typeName.toUpperCase()))
+ return typeName;
+ if (typeName.indexOf('(') != -1)
+ return typeName;
+
+ String size = null;
+ if (col.getSize() > 0) {
+ StringBuffer buf = new StringBuffer(10);
+ buf.append("(").append(col.getSize());
+ if (col.getDecimalDigits() > 0)
+ buf.append(", ").append(col.getDecimalDigits());
+ buf.append(")");
+ size = buf.toString();
+ }
+
+ int idx = typeName.indexOf("{0}");
+ if (idx == -1 && size != null)
+ return typeName + size;
+ if (idx == -1)
+ return typeName;
+
+ // replace '{0}' with size
+ String ret = typeName.substring(0, idx);
+ if (size != null)
+ ret = ret + size;
+ if (typeName.length() > idx + 3)
+ ret = ret + typeName.substring(idx + 3);
+ return ret;
+ }
+
+ ///////////
+ // Selects
+ ///////////
+
+ /**
+ * Set the name of the join syntax to use: sql92, traditional, database
+ */
+ public void setJoinSyntax(String syntax) {
+ if ("sql92".equals(syntax))
+ joinSyntax = SYNTAX_SQL92;
+ else if ("traditional".equals(syntax))
+ joinSyntax = SYNTAX_TRADITIONAL;
+ else if ("database".equals(syntax))
+ joinSyntax = SYNTAX_DATABASE;
+ else if (syntax != null && syntax.length() > 0)
+ throw new IllegalArgumentException(syntax);
+ }
+
+ /**
+ * Return a SQL string to act as a placeholder for the given column.
+ */
+ public String getPlaceholderValueString(Column col) {
+ switch (col.getType()) {
+ case Types.BIGINT:
+ case Types.BIT:
+ case Types.INTEGER:
+ case Types.NUMERIC:
+ case Types.SMALLINT:
+ case Types.TINYINT:
+ return "0";
+ case Types.CHAR:
+ return (storeCharsAsNumbers) ? "0" : "' '";
+ case Types.CLOB:
+ case Types.LONGVARCHAR:
+ case Types.VARCHAR:
+ return "''";
+ case Types.DATE:
+ return ZERO_DATE_STR;
+ case Types.DECIMAL:
+ case Types.DOUBLE:
+ case Types.FLOAT:
+ case Types.REAL:
+ return "0.0";
+ case Types.TIME:
+ return ZERO_TIME_STR;
+ case Types.TIMESTAMP:
+ return ZERO_TIMESTAMP_STR;
+ default:
+ return "NULL";
+ }
+ }
+
+ /**
+ * Create a SELECT COUNT statement in the proper join syntax for the
+ * given instance.
+ */
+ public SQLBuffer toSelectCount(Select sel) {
+ SQLBuffer selectSQL = new SQLBuffer(this);
+ SQLBuffer from;
+ if (sel.getFromSelect() != null)
+ from = getFromSelect(sel, false);
+ else
+ from = getFrom(sel, false);
+ SQLBuffer where = getWhere(sel, false);
+
+ // if no grouping and no range, we might be able to get by without
+ // a subselect
+ if (sel.getGrouping() == null && sel.getStartIndex() == 0
+ && sel.getEndIndex() == Long.MAX_VALUE) {
+ // if the select has no identifier cols, use COUNT(*)
+ List aliases = (!sel.isDistinct()) ? Collections.EMPTY_LIST
+ : sel.getIdentifierAliases();
+ if (aliases.isEmpty()) {
+ selectSQL.append("COUNT(*)");
+ return toSelect(selectSQL, null, from, where, null, null, null,
+ false, false, 0, Long.MAX_VALUE);
+ }
+
+ // if there is a single distinct col, use COUNT(DISTINCT col)
+ if (aliases.size() == 1) {
+ selectSQL.append("COUNT(DISTINCT ").
+ append(aliases.get(0).toString()).append(")");
+ return toSelect(selectSQL, null, from, where, null, null, null,
+ false, false, 0, Long.MAX_VALUE);
+ }
+
+ // can we combine distinct cols?
+ if (distinctCountColumnSeparator != null) {
+ selectSQL.append("COUNT(DISTINCT ");
+ for (int i = 0; i < aliases.size(); i++) {
+ if (i > 0) {
+ selectSQL.append(" ");
+ selectSQL.append(distinctCountColumnSeparator);
+ selectSQL.append(" ");
+ }
+ selectSQL.append(aliases.get(i).toString());
+ }
+ selectSQL.append(")");
+ return toSelect(selectSQL, null, from, where, null, null, null,
+ false, false, 0, Long.MAX_VALUE);
+ }
+ }
+
+ // since we can't combine distinct cols, we have to perform an outer
+ // COUNT(*) select using the original select as a subselect in the
+ // FROM clause
+ assertSupport(supportsSubselect, "SupportsSubselect");
+
+ SQLBuffer subSelect = getSelects(sel, true, false);
+ SQLBuffer subFrom = from;
+ from = new SQLBuffer(this);
+ from.append("(");
+ from.append(toSelect(subSelect, null, subFrom, where,
+ sel.getGrouping(), sel.getHaving(), null, sel.isDistinct(),
+ false, sel.getStartIndex(), sel.getEndIndex()));
+ from.append(")");
+ if (requiresAliasForSubselect)
+ from.append(" ").append(Select.FROM_SELECT_ALIAS);
+
+ selectSQL.append("COUNT(*)");
+ return toSelect(selectSQL, null, from, null, null, null, null,
+ false, false, 0, Long.MAX_VALUE);
+ }
+
+ /**
+ * Create a DELETE statement for the specified Select. If the
+ * database does not support the bulk delete statement (such as
+ * cases where a subselect is required and the database doesn't support
+ * subselects), this method should return null.
+ */
+ public SQLBuffer toDelete(ClassMapping mapping, Select sel,
+ JDBCStore store, Object[] params) {
+ return toBulkOperation(mapping, sel, store, params, null);
+ }
+
+ public SQLBuffer toUpdate(ClassMapping mapping, Select sel,
+ JDBCStore store, Object[] params, Map updates) {
+ return toBulkOperation(mapping, sel, store, params, updates);
+ }
+
+ /**
+ * Returns the SQL for a bulk operation, either a DELETE or an UPDATE.
+ *
+ * @param mapping the mappng against which we are operating
+ * @param sel the Select that will constitute the WHERE clause
+ * @param store the current store
+ * @param updateParams the Map that holds the update parameters; a null
+ * value indicates that this is a delete operation
+ * @return the SQLBuffer for the update, or <em>null</em> if it is not
+ * possible to perform the bulk update
+ */
+ public SQLBuffer toBulkOperation(ClassMapping mapping, Select sel,
+ JDBCStore store, Object[] params, Map updateParams) {
+ SQLBuffer sql = new SQLBuffer(this);
+ if (updateParams == null)
+ sql.append("DELETE FROM ");
+ else
+ sql.append("UPDATE ");
+
+ // if there is only a single table in the select, then we can
+ // just issue a single DELETE FROM TABLE WHERE <conditions>
+ // statement; otherwise, since SQL doesn't allow deleting
+ // from one of a multi-table select, we need to issue a subselect
+ // like DELETE FROM TABLE WHERE EXISTS
+ // (SELECT 1 FROM TABLE t0 WHERE t0.ID = TABLE.ID); also, some
+ // databases do not allow aliases in delete statements, which
+ // also causes us to use a subselect
+ if (sel.getTableAliases().size() == 1 && supportsSubselect
+ && allowsAliasInBulkClause) {
+ SQLBuffer from;
+ if (sel.getFromSelect() != null)
+ from = getFromSelect(sel, false);
+ else
+ from = getFrom(sel, false);
+
+ sql.append(from);
+ appendUpdates(sel, store, sql, params, updateParams,
+ allowsAliasInBulkClause);
+
+ SQLBuffer where = sel.getWhere();
+ if (where != null && !where.isEmpty()) {
+ sql.append(" WHERE ");
+ sql.append(where);
+ }
+ return sql;
+ }
+
+ // we need to use a subselect if we are to bulk delete where
+ // the select includes multiple tables; if the database
+ // doesn't support it, then we need to sigal this by returning null
+ if (!supportsSubselect || !supportsCorrelatedSubselect)
+ return null;
+
+ Column[] pks = mapping.getPrimaryKeyColumns();
+ Table table = mapping.getTable();
+ String tableName = getFullName(table, false);
+ sel.clearSelects();
+ sel.setDistinct(true);
+
+ // if we have only a single PK, we can use a non-correlated
+ // subquery (using an IN statement), which is much faster than
+ // a correlated subquery (since a correlated subquery needs
+ // to be executed once for each row in the table)
+ if (pks.length == 1) {
+ sel.select(pks[0]);
+ sql.append(tableName);
+ appendUpdates(sel, store, sql, params, updateParams, false);
+ sql.append(" WHERE ").
+ append(pks[0]).append(" IN (").
+ append(sel.toSelect(false, null)).append(")");
+ } else {
+ sel.clearSelects();
+ sel.setDistinct(false);
+
+ // since the select is using a correlated subquery, we
+ // only need to select a bogus virtual column
+ sel.select("1", null);
+
+ // add in the joins to the table
+ Column[] cols = table.getPrimaryKey().getColumns();
+ SQLBuffer buf = new SQLBuffer(this);
+ buf.append("(");
+ for (int i = 0; i < cols.length; i++) {
+ if (i > 0)
+ buf.append(" AND ");
+
+ // add in "t0.PK = MYTABLE.PK"
+ buf.append(sel.getColumnAlias(cols[i], null)).append(" = ").
+ append(table).append(catalogSeparator).append(cols[i]);
+ }
+ buf.append(")");
+ sel.where(buf, null);
+
+ sql.append(tableName);
+ appendUpdates(sel, store, sql, params, updateParams, false);
+ sql.append(" WHERE EXISTS (").
+ append(sel.toSelect(false, null)).append(")");
+ }
+ return sql;
+ }
+
+ protected void appendUpdates(Select sel, JDBCStore store, SQLBuffer sql,
+ Object[] params, Map updateParams, boolean allowAlias) {
+ if (updateParams == null || updateParams.size() == 0)
+ return;
+
+ // manually build up the SET clause for the UPDATE statement
+ sql.append(" SET ");
+ for (Iterator i = updateParams.entrySet().iterator(); i.hasNext();) {
+ Map.Entry next = (Map.Entry) i.next();
+ FieldMetaData fmd = (FieldMetaData) next.getKey();
+ org.apache.openjpa.jdbc.kernel.exps.Val val =
+ (org.apache.openjpa.jdbc.kernel.exps.Val) next.getValue();
+
+ Column col = ((FieldMapping) fmd).getColumns()[0];
+ sql.append(col.getName());
+ sql.append(" = ");
+ val.initialize(sel, store, false);
+ JDBCFetchState fetchState = (JDBCFetchState) store.
+ getFetchConfiguration().newFetchState();
+ val.calculateValue(sel, store, params, null, fetchState);
+
+ // append the value with a null for the Select; i
+ // indicates that the
+ for (int j = 0; j < val.length(); j++)
+ val.appendTo(sql, j, allowAlias ? sel : null,
+ store, params, fetchState);
+
+ if (i.hasNext())
+ sql.append(", ");
+ }
+ }
+
+ /**
+ * Create a SELECT statement in the proper join syntax for the given
+ * instance.
+ */
+ public SQLBuffer toSelect(Select sel, boolean forUpdate,
+ JDBCFetchConfiguration fetch) {
+ boolean update = forUpdate && sel.getFromSelect() == null;
+ SQLBuffer select = getSelects(sel, false, update);
+ SQLBuffer ordering = null;
+ if (!sel.isAggregate() || sel.getGrouping() != null)
+ ordering = sel.getOrdering();
+ SQLBuffer from;
+ if (sel.getFromSelect() != null)
+ from = getFromSelect(sel, forUpdate);
+ else
+ from = getFrom(sel, update);
+ SQLBuffer where = getWhere(sel, update);
+ return toSelect(select, fetch, from, where, sel.getGrouping(),
+ sel.getHaving(), ordering, sel.isDistinct(), forUpdate,
+ sel.getStartIndex(), sel.getEndIndex());
+ }
+
+ /**
+ * Return the portion of the select statement between the FROM keyword
+ * and the WHERE keyword.
+ */
+ protected SQLBuffer getFrom(Select sel, boolean forUpdate) {
+ SQLBuffer fromSQL = new SQLBuffer(this);
+ Collection aliases = sel.getTableAliases();
+ if (aliases.size() < 2 || sel.getJoinSyntax() != SYNTAX_SQL92) {
+ for (Iterator itr = aliases.iterator(); itr.hasNext();) {
+ fromSQL.append(itr.next().toString());
+ if (forUpdate && tableForUpdateClause != null)
+ fromSQL.append(" ").append(tableForUpdateClause);
+
+ if (itr.hasNext())
+ fromSQL.append(", ");
+ }
+ } else {
+ Iterator itr = sel.getJoinIterator();
+ boolean first = true;
+ while (itr.hasNext()) {
+ fromSQL.append(toSQL92Join((Join) itr.next(), forUpdate,
+ first));
+ first = false;
+ }
+ }
+ return fromSQL;
+ }
+
+ /**
+ * Return the FROM clause for a select that selects from a tmp table
+ * created by an inner select.
+ */
+ protected SQLBuffer getFromSelect(Select sel, boolean forUpdate) {
+ SQLBuffer fromSQL = new SQLBuffer(this);
+ fromSQL.append("(");
+ fromSQL.append(toSelect(sel.getFromSelect(), forUpdate, null));
+ fromSQL.append(")");
+ if (requiresAliasForSubselect)
+ fromSQL.append(" ").append(Select.FROM_SELECT_ALIAS);
+ return fromSQL;
+ }
+
+ /**
+ * Return the WHERE portion of the select statement, or null if no where
+ * conditions.
+ */
+ protected SQLBuffer getWhere(Select sel, boolean forUpdate) {
+ Joins joins = sel.getJoins();
+ if (sel.getJoinSyntax() == SYNTAX_SQL92
+ || joins == null || joins.isEmpty())
+ return sel.getWhere();
+
+ SQLBuffer where = new SQLBuffer(this);
+ if (sel.getWhere() != null)
+ where.append(sel.getWhere());
+ if (joins != null)
+ sel.append(where, joins);
+ return where;
+ }
+
+ /**
+ * Use the given join instance to create SQL joining its tables in
+ * the traditional style.
+ */
+ public SQLBuffer toTraditionalJoin(Join join) {
+ ForeignKey fk = join.getForeignKey();
+ if (fk == null)
+ return null;
+
+ boolean inverse = join.isForeignKeyInversed();
+ Column[] from = (inverse) ? fk.getPrimaryKeyColumns()
+ : fk.getColumns();
+ Column[] to = (inverse) ? fk.getColumns()
+ : fk.getPrimaryKeyColumns();
+
+ // do column joins
+ SQLBuffer buf = new SQLBuffer(this);
+ int count = 0;
+ for (int i = 0; i < from.length; i++, count++) {
+ if (count > 0)
+ buf.append(" AND ");
+ buf.append(join.getAlias1()).append(".").append(from[i]);
+ buf.append(" = ");
+ buf.append(join.getAlias2()).append(".").append(to[i]);
+ }
+
+ // do constant joins
+ Column[] constCols = fk.getConstantColumns();
+ for (int i = 0; i < constCols.length; i++, count++) {
+ if (count > 0)
+ buf.append(" AND ");
+ if (inverse)
+ buf.appendValue(fk.getConstant(constCols[i]), constCols[i]);
+ else
+ buf.append(join.getAlias1()).append(".").
+ append(constCols[i]);
+ buf.append(" = ");
+
+ if (inverse)
+ buf.append(join.getAlias2()).append(".").
+ append(constCols[i]);
+ else
+ buf.appendValue(fk.getConstant(constCols[i]), constCols[i]);
+ }
+
+ Column[] constColsPK = fk.getConstantPrimaryKeyColumns();
+ for (int i = 0; i < constColsPK.length; i++, count++) {
+ if (count > 0)
+ buf.append(" AND ");
+ if (inverse)
+ buf.append(join.getAlias1()).append(".").
+ append(constColsPK[i]);
+ else
+ buf.appendValue(fk.getPrimaryKeyConstant(constColsPK[i]),
+ constColsPK[i]);
+ buf.append(" = ");
+
+ if (inverse)
+ buf.appendValue(fk.getPrimaryKeyConstant(constColsPK[i]),
+ constColsPK[i]);
+ else
+ buf.append(join.getAlias2()).append(".").
+ append(constColsPK[i]);
+ }
+ return buf;
+ }
+
+ /**
+ * Use the given join instance to create SQL joining its tables in
+ * the SQL92 style.
+ */
+ public SQLBuffer toSQL92Join(Join join, boolean forUpdate, boolean first) {
+ SQLBuffer buf = new SQLBuffer(this);
+ if (first) {
+ buf.append(join.getTable1()).append(" ").
+ append(join.getAlias1());
+ if (forUpdate && tableForUpdateClause != null)
+ buf.append(" ").append(tableForUpdateClause);
+ }
+
+ buf.append(" ");
+ if (join.getType() == Join.TYPE_OUTER)
+ buf.append(outerJoinClause);
+ else if (join.getType() == Join.TYPE_INNER)
+ buf.append(innerJoinClause);
+ else // cross
+ buf.append(crossJoinClause);
+ buf.append(" ");
+
+ buf.append(join.getTable2()).append(" ").append(join.getAlias2());
+ if (forUpdate && tableForUpdateClause != null)
+ buf.append(" ").append(tableForUpdateClause);
+
+ if (join.getForeignKey() != null)
+ buf.append(" ON ").append(toTraditionalJoin(join));
+ else if (requiresConditionForCrossJoin &&
+ join.getType() == Join.TYPE_CROSS)
+ buf.append(" ON (1 = 1)");
+
+ return buf;
+ }
+
+ /**
+ * Use the given join instance to create SQL joining its tables in
+ * the database's native syntax. Throws an exception by default.
+ */
+ public SQLBuffer toNativeJoin(Join join) {
+ throw new UnsupportedException();
+ }
+
+ /**
+ * Returns if the given foreign key can be eagerly loaded using other joins.
+ */
+ public boolean canOuterJoin(int syntax, ForeignKey fk) {
+ return syntax != SYNTAX_TRADITIONAL;
+ }
+
+ /**
+ * Combine the given components into a SELECT statement.
+ */
+ public SQLBuffer toSelect(SQLBuffer selects, JDBCFetchConfiguration fetch,
+ SQLBuffer from, SQLBuffer where, SQLBuffer group,
+ SQLBuffer having, SQLBuffer order,
+ boolean distinct, boolean forUpdate, long start, long end) {
+ return toOperation(getSelectOperation(fetch), selects, from, where,
+ group, having, order, distinct, forUpdate, start, end);
+ }
+
+ public String getSelectOperation(JDBCFetchConfiguration fetch) {
+ return "SELECT";
+ }
+
+ public SQLBuffer toOperation(String op, SQLBuffer selects, SQLBuffer from,
+ SQLBuffer where, SQLBuffer group, SQLBuffer having, SQLBuffer order,
+ boolean distinct, boolean forUpdate, long start, long end) {
+ SQLBuffer buf = new SQLBuffer(this);
+ buf.append(op);
+
+ boolean range = start != 0 || end != Long.MAX_VALUE;
+ if (range && rangePosition == RANGE_PRE_DISTINCT)
+ appendSelectRange(buf, start, end);
+ if (distinct)
+ buf.append(" DISTINCT");
+ if (range && rangePosition == RANGE_POST_DISTINCT)
+ appendSelectRange(buf, start, end);
+
+ buf.append(" ").append(selects).append(" FROM ").append(from);
+
+ if (where != null && !where.isEmpty())
+ buf.append(" WHERE ").append(where);
+ if (group != null && !group.isEmpty())
+ buf.append(" GROUP BY ").append(group);
+ if (having != null && !having.isEmpty()) {
+ assertSupport(supportsHaving, "SupportsHaving");
+ buf.append(" HAVING ").append(having);
+ }
+ if (order != null && !order.isEmpty())
+ buf.append(" ORDER BY ").append(order);
+ if (range && rangePosition == RANGE_POST_SELECT)
+ appendSelectRange(buf, start, end);
+
+ if (forUpdate && !simulateLocking) {
+ assertSupport(supportsSelectForUpdate, "SupportsSelectForUpdate");
+ if (forUpdateClause != null)
+ buf.append(" ").append(forUpdateClause);
+ }
+ return buf;
+ }
+
+ /**
+ * If this dictionary can select ranges,
+ * use this method to append the range SQL.
+ */
+ protected void appendSelectRange(SQLBuffer buf, long start, long end) {
+ }
+
+ /**
+ * Return the portion of the select statement between the SELECT keyword
+ * and the FROM keyword.
+ */
+ protected SQLBuffer getSelects(Select sel, boolean distinctIdentifiers,
+ boolean forUpdate) {
+ // append the aliases for all the columns
+ SQLBuffer selectSQL = new SQLBuffer(this);
+ List aliases;
+ if (distinctIdentifiers)
+ aliases = sel.getIdentifierAliases();
+ else
+ aliases = sel.getSelectAliases();
+
+ Object alias;
+ for (Iterator itr = aliases.iterator(); itr.hasNext();) {
+ alias = itr.next();
+ if (alias instanceof SQLBuffer)
+ selectSQL.append((SQLBuffer) alias);
+ else
+ selectSQL.append(alias.toString());
+ if (itr.hasNext())
+ selectSQL.append(", ");
+ }
+ return selectSQL;
+ }
+
+ /**
+ * Returns true if a "FOR UPDATE" clause can be used for the specified
+ * Select object.
+ */
+ public boolean supportsLocking(Select sel) {
+ if (sel.isAggregate())
+ return false;
+ if (!supportsSelectForUpdate)
+ return false;
+ if (!supportsLockingWithSelectRange && (sel.getStartIndex() != 0
+ || sel.getEndIndex() != Long.MAX_VALUE))
+ return false;
+
+ // only inner select is locked
+ if (sel.getFromSelect() != null)
+ sel = sel.getFromSelect();
+
+ if (!supportsLockingWithDistinctClause && sel.isDistinct())
+ return false;
+ if (!supportsLockingWithMultipleTables
+ && sel.getTableAliases().size() > 1)
+ return false;
+ if (!supportsLockingWithOrderClause && sel.getOrdering() != null)
+ return false;
+ if (!supportsLockingWithOuterJoin || !supportsLockingWithInnerJoin) {
+ for (Iterator itr = sel.getJoinIterator(); itr.hasNext();) {
+ Join join = (Join) itr.next();
+ if (!supportsLockingWithOuterJoin
+ && join.getType() == Join.TYPE_OUTER)
+ return false;
+ if (!supportsLockingWithInnerJoin
+ && join.getType() == Join.TYPE_INNER)
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Return false if the given select requires a forward-only result set.
+ */
+ public boolean supportsRandomAccessResultSet(Select sel,
+ boolean forUpdate) {
+ return !sel.isAggregate();
+ }
+
+ /**
+ * Assert that the given dictionary flag is true. If it is not true,
+ * throw an error saying that the given setting needs to return true for
+ * the current operation to work.
+ */
+ public void assertSupport(boolean feature, String property) {
+ if (!feature)
+ throw new UnsupportedException(_loc.get("feature-not-supported",
+ getClass(), property));
+ }
+
+ ////////////////////
+ // Query functions
+ ////////////////////
+
+ /**
+ * Invoke this database's substring function.
+ *
+ * @param buf the SQL buffer to write the substring invocation to
+ * @param str a query value representing the target string
+ * @param start a query value representing the start index
+ * @param end a query value representing the end index, or null for none
+ */
+ public void substring(SQLBuffer buf, FilterValue str, FilterValue start,
+ FilterValue end) {
+ buf.append(substringFunctionName).append("((");
+ str.appendTo(buf);
+ buf.append("), (");
+ start.appendTo(buf);
+ buf.append(" + 1)");
+ if (end != null) {
+ buf.append(", (");
+ end.appendTo(buf);
+ buf.append(" - (");
+ start.appendTo(buf);
+ buf.append("))");
+ }
+ buf.append(")");
+ }
+
+ /**
+ * Invoke this database's indexOf function.
+ *
+ * @param buf the SQL buffer to write the indexOf invocation to
+ * @param str a query value representing the target string
+ * @param find a query value representing the search string
+ * @param start a query value representing the start index, or null
+ * to start at the beginning
+ */
+ public void indexOf(SQLBuffer buf, FilterValue str, FilterValue find,
+ FilterValue start) {
+ buf.append("(INSTR((");
+ if (start != null)
+ substring(buf, str, start, null);
+ else
+ str.appendTo(buf);
+ buf.append("), (");
+ find.appendTo(buf);
+ buf.append(")) - 1");
+ if (start != null) {
+ buf.append(" + ");
+ start.appendTo(buf);
+ }
+ buf.append(")");
+ }
+
+ /**
+ * Append the numeric parts of a mathematical function.
+ *
+ * @param buf the SQL buffer to write the math function
+ * @param op the mathematical operation to perform
+ * @param lhs the left hand side of the math function
+ * @param rhs the right hand side of the math function
+ */
+ public void mathFunction(SQLBuffer buf, String op, FilterValue lhs,
+ FilterValue rhs) {
+ boolean castlhs = false;
+ boolean castrhs = false;
+ Class lc = lhs.getType();
+ Class rc = rhs.getType();
+ int type = 0;
+ if (requiresCastForMathFunctions && (lc != rc
+ || (lhs.isConstant() && rhs.isConstant()))) {
+ Class c = Filters.promote(lhs.getType(), rhs.getType());
+ castlhs = (lhs.isConstant() && rhs.isConstant())
+ || Filters.wrap(lhs.getType()) != c;
+ castrhs = (lhs.isConstant() && rhs.isConstant())
+ || Filters.wrap(rhs.getType()) != c;
+ type = getJDBCType(JavaTypes.getTypeCode(c), false);
+ }
+
+ boolean mod = "MOD".equals(op);
+ if (mod) {
+ if (supportsModOperator)
+ op = "%";
+ else
+ buf.append(op);
+ }
+ buf.append("(");
+
+ if (castlhs)
+ appendCast(buf, lhs, type);
+ else
+ lhs.appendTo(buf);
+
+ if (mod)
+ buf.append(", ");
+ else
+ buf.append(" ").append(op).append(" ");
+
+ if (castrhs)
+ appendCast(buf, rhs, type);
+ else
+ rhs.appendTo(buf);
+
+ buf.append(")");
+ }
+
+ /**
+ * Append a comparison.
+ *
+ * @param buf the SQL buffer to write the comparison
+ * @param op the comparison operation to perform
+ * @param lhs the left hand side of the comparison
+ * @param rhs the right hand side of the comparison
+ */
+ public void comparison(SQLBuffer buf, String op, FilterValue lhs,
+ FilterValue rhs) {
+ boolean castlhs = false;
+ boolean castrhs = false;
+ Class lc = lhs.getType();
+ Class rc = rhs.getType();
+ int type = 0;
+ if (requiresCastForComparisons && (lc != rc
+ || (lhs.isConstant() && rhs.isConstant()))) {
+ Class c = Filters.promote(lhs.getType(), rhs.getType());
+ castlhs = (lhs.isConstant() && rhs.isConstant())
+ || Filters.wrap(lhs.getType()) != c;
+ castrhs = (lhs.isConstant() && rhs.isConstant())
+ || Filters.wrap(rhs.getType()) != c;
+ type = getJDBCType(JavaTypes.getTypeCode(c), false);
+ }
+
+ if (castlhs)
+ appendCast(buf, lhs, type);
+ else
+ lhs.appendTo(buf);
+
+ buf.append(" ").append(op).append(" ");
+
+ if (castrhs)
+ appendCast(buf, rhs, type);
+ else
+ rhs.appendTo(buf);
+ }
+
+ /**
+ * Append SQL for the given numeric value to the buffer, casting as needed.
+ */
+ protected void appendNumericCast(SQLBuffer buf, FilterValue val) {
+ if (val.isConstant())
+ appendCast(buf, val, Types.NUMERIC);
+ else
+ val.appendTo(buf);
+ }
+
+ /**
+ * Cast the specified value to the specified type.
+ *
+ * @param buf the buffer to append the cast to
+ * @param val the value to cast
+ * @param type the type of the case, e.g. {@link Types#NUMERIC}
+ */
+ public void appendCast(SQLBuffer buf, FilterValue val, int type) {
+ // Convert the cast function: "CAST({0} AS {1})"
+ int firstParam = castFunction.indexOf("{0}");
+ String pre = castFunction.substring(0, firstParam); // "CAST("
+ String mid = castFunction.substring(firstParam + 3);
+ int secondParam = mid.indexOf("{1}");
+ String post;
+ if (secondParam > -1) {
+ post = mid.substring(secondParam + 3); // ")"
+ mid = mid.substring(0, secondParam); // " AS "
+ } else
+ post = "";
+
+ buf.append(pre);
+ val.appendTo(buf);
+ buf.append(mid);
+ buf.append(getTypeName(type));
+ buf.append(post);
+ }
+
+ ///////////
+ // DDL SQL
+ ///////////
+
+ /**
+ * Increment the reference count of any table components that this
+ * dictionary adds that are not used by mappings. Does nothing by default.
+ */
+ public void refSchemaComponents(Table table) {
+ }
+
+ /**
+ * Returns the full name of the table, including the schema (delimited
+ * by {@link #catalogSeparator}).
+ */
+ public String getFullName(Table table, boolean logical) {
+ if (!useSchemaName || table.getSchemaName() == null)
+ return table.getName();
+ if (logical || ".".equals(catalogSeparator))
+ return table.getFullName();
+ return table.getSchemaName() + catalogSeparator + table.getName();
+ }
+
+ /**
+ * Returns the full name of the index, including the schema (delimited
+ * by the result of {@link #catalogSeparator}).
+ */
+ public String getFullName(Index index) {
+ if (!useSchemaName || index.getSchemaName() == null)
+ return index.getName();
+ if (".".equals(catalogSeparator))
+ return index.getFullName();
+ return index.getSchemaName() + catalogSeparator + index.getName();
+ }
+
+ /**
+ * Returns the full name of the sequence, including the schema (delimited
+ * by the result of {@link #catalogSeparator}).
+ */
+ public String getFullName(Sequence seq) {
+ if (!useSchemaName || seq.getSchemaName() == null)
[... 1282 lines stripped ...]