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 ...]