| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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.CharArrayReader; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.io.OutputStream; |
| 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.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import javax.sql.DataSource; |
| |
| import org.apache.commons.lang.StringUtils; |
| import org.apache.openjpa.jdbc.conf.JDBCConfiguration; |
| import org.apache.openjpa.jdbc.kernel.JDBCFetchConfiguration; |
| import org.apache.openjpa.jdbc.kernel.JDBCStore; |
| import org.apache.openjpa.jdbc.kernel.exps.ExpContext; |
| import org.apache.openjpa.jdbc.kernel.exps.ExpState; |
| import org.apache.openjpa.jdbc.kernel.exps.FilterValue; |
| import org.apache.openjpa.jdbc.kernel.exps.Null; |
| import org.apache.openjpa.jdbc.kernel.exps.Val; |
| 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.kernel.OpenJPAStateManager; |
| import org.apache.openjpa.kernel.Seq; |
| import org.apache.openjpa.kernel.exps.Path; |
| 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.lib.util.Localizer.Message; |
| import org.apache.openjpa.meta.FieldMetaData; |
| import org.apache.openjpa.meta.JavaTypes; |
| import org.apache.openjpa.meta.ValueStrategies; |
| import org.apache.openjpa.util.GeneralException; |
| import org.apache.openjpa.util.InternalException; |
| import org.apache.openjpa.util.InvalidStateException; |
| import org.apache.openjpa.util.LockException; |
| import org.apache.openjpa.util.ObjectExistsException; |
| import org.apache.openjpa.util.ObjectNotFoundException; |
| import org.apache.openjpa.util.OpenJPAException; |
| import org.apache.openjpa.util.OptimisticException; |
| 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"; |
| |
| public int blobBufferSize = 50000; |
| public int clobBufferSize = 50000; |
| |
| 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 RANGE_POST_LOCK = 3; |
| |
| 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; |
| |
| protected static final int UNLIMITED = -1; |
| protected static final int NO_BATCH = 0; |
| |
| 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 boolean supportsComments = false; |
| public String reservedWords = null; |
| public String systemSchemas = null; |
| public String systemTables = null; |
| public String selectWords = null; |
| public String fixedSizeTypeNames = null; |
| public String schemaCase = SCHEMA_CASE_UPPER; |
| public boolean setStringRightTruncationOn = true; |
| |
| // 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 requiresTargetForDelete = false; |
| public boolean allowsAliasInBulkClause = true; |
| public boolean supportsMultipleNontransactionalResultSets = true; |
| public boolean requiresSearchStringEscapeForLike = true; |
| public String searchStringEscape = "\\"; |
| public boolean requiresCastForMathFunctions = false; |
| public boolean requiresCastForComparisons = false; |
| public boolean supportsModOperator = false; |
| public boolean supportsXMLColumn = false; |
| public boolean reportsSuccessNoInfoOnBatchUpdates = false; |
| public boolean supportsCaseConversionForLob = false; |
| |
| /** |
| * Some Databases append whitespace after the schema name |
| */ |
| public boolean trimSchemaName = 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 inClauseLimit = -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"; |
| public String xmlTypeName = "XML"; |
| public String getStringVal = ""; |
| |
| // 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; |
| public String sequenceSQL = null; |
| public String sequenceSchemaSQL = null; |
| public String sequenceNameSQL = null; |
| // most native sequences can be run inside the business transaction |
| public int nativeSequenceType= Seq.TYPE_TRANSACTIONAL; |
| |
| protected JDBCConfiguration conf = null; |
| protected Log log = null; |
| protected boolean connected = false; |
| protected boolean isJDBC3 = 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(); |
| protected final Set typeModifierSet = new HashSet(); |
| |
| /** |
| * Some JDBC drivers - ie DB2 type 2 on Z/OS throw exceptions on |
| * setQueryTimeout when provided specific input values. |
| * To remain consistent with earlier versions of the driver we should ignore |
| * the exception. |
| * |
| * This variable will be removed in future releases when we can handle the |
| * exception properly. |
| */ |
| @Deprecated |
| public boolean ignoreSQLExceptionOnSetQueryTimeout = false; |
| |
| /** |
| * If a native query begins with any of the values found here then it will |
| * be treated as a select statement. |
| */ |
| protected final Set selectWordSet = 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; |
| |
| // batchLimit value: |
| // -1 = unlimited |
| // 0 = no batch |
| // any positive number = batch limit |
| public int batchLimit = NO_BATCH; |
| |
| public final Map<Integer,Set<String>> sqlStateCodes = |
| new HashMap<Integer, Set<String>>(); |
| |
| 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", |
| })); |
| |
| selectWordSet.add("SELECT"); |
| } |
| |
| /** |
| * 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) { |
| DatabaseMetaData metaData = null; |
| try { |
| metaData = conn.getMetaData(); |
| try { |
| // JDBC3-only method, so it might throw a |
| // AbstractMethodError |
| isJDBC3 = metaData.getJDBCMajorVersion() >= 3; |
| } catch (Throwable t) { |
| // ignore if not JDBC3 |
| } |
| } catch (Exception e) { |
| if (log.isTraceEnabled()) |
| log.trace(e.toString(), e); |
| } |
| |
| if (log.isTraceEnabled()) { |
| log.trace(DBDictionaryFactory.toString(metaData)); |
| |
| if (isJDBC3) |
| log.trace(_loc.get("connection-defaults", new Object[]{ |
| conn.getAutoCommit(), conn.getHoldability(), |
| conn.getTransactionIsolation()})); |
| } |
| } |
| 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); |
| } |
| |
| public InputStream getLOBStream(JDBCStore store, 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); |
| return (StringUtils.isEmpty(str)) ? 0 : 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 (StringUtils.isEmpty(str)) |
| 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, null, 0, 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, null, 0, 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, null, 0, 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 if (val instanceof Timestamp) |
| setTimestamp(stmnt, idx,(Timestamp) val, 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())) { |
| Message 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.NUMERIC); |
| 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 (!StringUtils.isEmpty(col.getTypeName())) |
| 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 Types.BOOLEAN: |
| 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>). |
| * |
| * <P>Some databases support "type modifiers" for example the unsigned |
| * "modifier" in MySQL. In these cases the size should go between the type |
| * and the "modifier", instead of after the modifier. For example |
| * CREATE table FOO ( myint INT (10) UNSIGNED . . .) instead of |
| * CREATE table FOO ( myint INT UNSIGNED (10) . . .). |
| * Type modifiers should be added to <code>typeModifierSet</code> in |
| * subclasses. |
| */ |
| 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(); |
| } |
| |
| return insertSize(typeName, size); |
| } |
| |
| /** |
| * Helper method that inserts a size clause for a given SQL type. |
| * |
| * @see appendSize |
| * |
| * @param typeName The SQL type ie INT |
| * @param size The size clause ie (10) |
| * @return The typeName + size clause. Usually the size clause will |
| * be appended to typeName. If the typeName contains a |
| * marker : {0} or if typeName contains a modifier the |
| * size clause will be inserted appropriately. |
| */ |
| protected String insertSize(String typeName, String size) { |
| if (StringUtils.isEmpty(size)) { |
| int idx = typeName.indexOf("{0}"); |
| if (idx != -1) { |
| return typeName.substring(0, idx); |
| } |
| return typeName; |
| } |
| |
| int idx = typeName.indexOf("{0}"); |
| if (idx != -1) { |
| // 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; |
| } |
| if (!typeModifierSet.isEmpty()) { |
| String s; |
| idx = typeName.length(); |
| int curIdx = -1; |
| for (Iterator i = typeModifierSet.iterator(); i.hasNext();) { |
| s = (String) i.next(); |
| if (typeName.toUpperCase().indexOf(s) != -1) { |
| curIdx = typeName.toUpperCase().indexOf(s); |
| if (curIdx != -1 && curIdx < idx) { |
| idx = curIdx; |
| } |
| } |
| } |
| if(idx != typeName.length()) { |
| String ret = typeName.substring(0, idx); |
| ret = ret + size; |
| ret = ret + ' ' + typeName.substring(idx); |
| return ret; |
| } |
| } |
| return typeName + size; |
| } |
| |
| /////////// |
| // 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 (!StringUtils.isEmpty(syntax)) |
| 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; |
| sel.addJoinClassConditions(); |
| 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(), true)); |
| 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, |
| Object[] params) { |
| return toBulkOperation(mapping, sel, null, 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 |
| */ |
| protected SQLBuffer toBulkOperation(ClassMapping mapping, Select sel, |
| JDBCStore store, Object[] params, Map updateParams) { |
| SQLBuffer sql = new SQLBuffer(this); |
| if (updateParams == null) { |
| if (requiresTargetForDelete) { |
| sql.append("DELETE "); |
| SQLBuffer deleteTargets = getDeleteTargets(sel); |
| sql.append(deleteTargets); |
| sql.append(" FROM "); |
| } else { |
| sql.append("DELETE FROM "); |
| } |
| } |
| else |
| sql.append("UPDATE "); |
| sel.addJoinClassConditions(); |
| |
| // 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; |
| } |
| |
| Table table = mapping.getTable(); |
| String tableName = getFullName(table, false); |
| |
| // only use a subselect if the where is not empty; otherwise |
| // an unqualified delete or update will work |
| if (sel.getWhere() == null || sel.getWhere().isEmpty()) { |
| sql.append(tableName); |
| appendUpdates(sel, store, sql, params, updateParams, false); |
| 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(); |
| 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 SQLBuffer getDeleteTargets(Select sel) { |
| SQLBuffer deleteTargets = new SQLBuffer(this); |
| Collection aliases = sel.getTableAliases(); |
| // Assumes aliases are of the form "TABLENAME t0" |
| for (Iterator itr = aliases.iterator(); itr.hasNext();) { |
| String tableAlias = itr.next().toString(); |
| int spaceIndex = tableAlias.indexOf(' '); |
| if (spaceIndex > 0 && spaceIndex < tableAlias.length() - 1) { |
| if (allowsAliasInBulkClause) { |
| deleteTargets.append(tableAlias.substring(spaceIndex + 1)); |
| } else { |
| deleteTargets.append(tableAlias.substring(0, spaceIndex)); |
| } |
| } else { |
| deleteTargets.append(tableAlias); |
| } |
| if (itr.hasNext()) |
| deleteTargets.append(", "); |
| } |
| return deleteTargets; |
| } |
| |
| 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 "); |
| ExpContext ctx = new ExpContext(store, params, |
| store.getFetchConfiguration()); |
| |
| // If the updates map contains any version fields, assume that the |
| // optimistic lock version data is being handled properly by the |
| // caller. Otherwise, give the version indicator an opportunity to |
| // add more update clauses as needed. |
| boolean augmentUpdates = true; |
| |
| for (Iterator i = updateParams.entrySet().iterator(); i.hasNext();) { |
| Map.Entry next = (Map.Entry) i.next(); |
| Path path = (Path) next.getKey(); |
| FieldMapping fmd = (FieldMapping) path.last(); |
| |
| if (fmd.isVersion()) |
| augmentUpdates = false; |
| |
| Val val = (Val) next.getValue(); |
| if (val == null) |
| val = new Null(); |
| Column col = fmd.getColumns()[0]; |
| if (allowAlias) { |
| sql.append(sel.getColumnAlias(col)); |
| } else { |
| sql.append(col.getName()); |
| } |
| sql.append(" = "); |
| |
| ExpState state = val.initialize(sel, ctx, 0); |
| // JDBC Paths are always PCPaths; PCPath implements Val |
| ExpState pathState = ((Val) path).initialize(sel, ctx, 0); |
| calculateValue(val, sel, ctx, state, path, pathState); |
| |
| // append the value with a null for the Select; i |
| // indicates that the |
| int length = val.length(sel, ctx, state); |
| for (int j = 0; j < length; j++) |
| val.appendTo((allowAlias) ? sel : null, ctx, state, sql, j); |
| |
| if (i.hasNext()) |
| sql.append(", "); |
| } |
| |
| if (augmentUpdates) { |
| Path path = (Path) updateParams.keySet().iterator().next(); |
| FieldMapping fm = (FieldMapping) path.last(); |
| ClassMapping meta = fm.getDeclaringMapping(); |
| Map updates = meta.getVersion().getBulkUpdateValues(); |
| for (Iterator iter = updates.entrySet().iterator(); |
| iter.hasNext(); ) { |
| Map.Entry e = (Map.Entry) iter.next(); |
| Column col = (Column) e.getKey(); |
| String val = (String) e.getValue(); |
| sql.append(", ").append(col.getName()) |
| .append(" = ").append(val); |
| } |
| } |
| } |
| |
| /** |
| * Create SQL to delete the contents of the specified tables. |
| * The default implementation drops all non-deferred RESTRICT foreign key |
| * constraints involving the specified tables, issues DELETE statements |
| * against the tables, and then adds the dropped constraints back in. |
| * Databases with more optimal ways of deleting the contents of several |
| * tables should override this method. |
| */ |
| public String[] getDeleteTableContentsSQL(Table[] tables) { |
| Collection sql = new ArrayList(); |
| |
| // collect and drop non-deferred physical restrict constraints, and |
| // collect the DELETE FROM statements |
| Collection deleteSQL = new ArrayList(tables.length); |
| Collection restrictConstraints = new LinkedHashSet(); |
| for (int i = 0; i < tables.length; i++) { |
| ForeignKey[] fks = tables[i].getForeignKeys(); |
| for (int j = 0; j < fks.length; j++) { |
| if (!fks[j].isLogical() && !fks[j].isDeferred() |
| && fks[j].getDeleteAction() == ForeignKey.ACTION_RESTRICT) |
| restrictConstraints.add(fks[j]); |
| String[] constraintSQL = getDropForeignKeySQL(fks[j]); |
| sql.addAll(Arrays.asList(constraintSQL)); |
| } |
| |
| deleteSQL.add("DELETE FROM " + tables[i].getFullName()); |
| } |
| |
| // add the delete statements after all the constraint mutations |
| sql.addAll(deleteSQL); |
| |
| // add the deleted constraints back to the schema |
| for (Iterator iter = restrictConstraints.iterator(); iter.hasNext(); ) { |
| String[] constraintSQL = |
| getAddForeignKeySQL((ForeignKey) iter.next()); |
| sql.addAll(Arrays.asList(constraintSQL)); |
| } |
| |
| return (String[]) sql.toArray(new String[sql.size()]); |
| } |
| |
| /** |
| * Create a SELECT statement in the proper join syntax for the given |
| * instance. |
| */ |
| public SQLBuffer toSelect(Select sel, boolean forUpdate, |
| JDBCFetchConfiguration fetch) { |
| sel.addJoinClassConditions(); |
| 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(), sel); |
| } |
| |
| /** |
| * 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, start, end, |
| getForUpdateClause(fetch, forUpdate, null)); |
| } |
| |
| /** |
| * 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, |
| boolean subselect) { |
| return toOperation(getSelectOperation(fetch), selects, from, where, |
| group, having, order, distinct, start, end, |
| getForUpdateClause(fetch, forUpdate, null), subselect); |
| } |
| |
| 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, |
| boolean subselect, boolean checkTableForUpdate) { |
| return toOperation(getSelectOperation(fetch), selects, from, where, |
| group, having, order, distinct, start, end, |
| getForUpdateClause(fetch, forUpdate, null), subselect, checkTableForUpdate); |
| } |
| |
| /** |
| * 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, |
| Select sel) { |
| return toOperation(getSelectOperation(fetch), selects, from, where, |
| group, having, order, distinct, start, end, |
| getForUpdateClause(fetch, forUpdate, sel)); |
| } |
| |
| /** |
| * Get the update clause for the query based on the |
| * updateClause and isolationLevel hints |
| */ |
| protected String getForUpdateClause(JDBCFetchConfiguration fetch, |
| boolean isForUpdate, Select sel) { |
| if (fetch != null && fetch.getIsolation() != -1) { |
| throw new InvalidStateException(_loc.get( |
| "isolation-level-config-not-supported", getClass().getName())); |
| } else if (isForUpdate && !simulateLocking) { |
| assertSupport(supportsSelectForUpdate, "SupportsSelectForUpdate"); |
| return forUpdateClause; |
| } else { |
| return null; |
| } |
| } |
| |
| /** |
| * Return the "SELECT" operation clause, adding any available hints, etc. |
| */ |
| public String getSelectOperation(JDBCFetchConfiguration fetch) { |
| return "SELECT"; |
| } |
| |
| /** |
| * Return the SQL for the given selecting operation. |
| */ |
| public SQLBuffer toOperation(String op, SQLBuffer selects, |
| SQLBuffer from, SQLBuffer where, SQLBuffer group, SQLBuffer having, |
| SQLBuffer order, boolean distinct, long start, long end, |
| String forUpdateClause) { |
| return toOperation(op, selects, from, where, group, having, order, |
| distinct, start, end, forUpdateClause, false); |
| } |
| |
| /** |
| * Return the SQL for the given selecting operation. |
| */ |
| public SQLBuffer toOperation(String op, SQLBuffer selects, |
| SQLBuffer from, SQLBuffer where, SQLBuffer group, SQLBuffer having, |
| SQLBuffer order, boolean distinct, long start, long end, |
| String forUpdateClause, boolean subselect) { |
| return toOperation(op, selects, from, where, group, having, order, |
| distinct, start, end, forUpdateClause, subselect, false); |
| } |
| |
| /** |
| * Return the SQL for the given selecting operation. |
| */ |
| private SQLBuffer toOperation(String op, SQLBuffer selects, |
| SQLBuffer from, SQLBuffer where, SQLBuffer group, SQLBuffer having, |
| SQLBuffer order, boolean distinct, long start, long end, |
| String forUpdateClause, boolean subselect, boolean checkTableForUpdate) { |
| 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, subselect); |
| if (distinct) |
| buf.append(" DISTINCT"); |
| if (range && rangePosition == RANGE_POST_DISTINCT) |
| appendSelectRange(buf, start, end, subselect); |
| |
| buf.append(" ").append(selects).append(" FROM ").append(from); |
| |
| if (checkTableForUpdate |
| && (StringUtils.isEmpty(forUpdateClause) && !StringUtils |
| .isEmpty(tableForUpdateClause))) { |
| buf.append(" ").append(tableForUpdateClause); |
| } |
| |
| 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, subselect); |
| if (forUpdateClause != null) |
| buf.append(" ").append(forUpdateClause); |
| if (range && rangePosition == RANGE_POST_LOCK) |
| appendSelectRange(buf, start, end, subselect); |
| 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, |
| boolean subselect) { |
| } |
| |
| /** |
| * 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 (int i = 0; i < aliases.size(); i++) { |
| alias = aliases.get(i); |
| appendSelect(selectSQL, alias, sel, i); |
| if (i < aliases.size() - 1) |
| selectSQL.append(", "); |
| } |
| return selectSQL; |
| } |
| |
| /** |
| * Append <code>elem</code> to <code>selectSQL</code>. |
| * @param selectSQL The SQLBuffer to append to. |
| * @param alias A {@link SQLBuffer} or a {@link String} to append. |
| * |
| * @since 1.1.0 |
| */ |
| protected void appendSelect(SQLBuffer selectSQL, Object elem, Select sel, |
| int idx) { |
| if (elem instanceof SQLBuffer) |
| selectSQL.append((SQLBuffer) elem); |
| else |
| selectSQL.append(elem.toString()); |
| } |
| |
| /** |
| * 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(", "); |
| if (start.getValue() instanceof Number) { |
| long startLong = toLong(start); |
| buf.append(Long.toString(startLong + 1)); |
| } else { |
| buf.append("("); |
| start.appendTo(buf); |
| buf.append(" + 1)"); |
| } |
| if (end != null) { |
| buf.append(", "); |
| if (start.getValue() instanceof Number |
| && end.getValue() instanceof Number) { |
| long startLong = toLong(start); |
| long endLong = toLong(end); |
| buf.append(Long.toString(endLong - startLong)); |
| } else { |
| end.appendTo(buf); |
| buf.append(" - ("); |
| start.appendTo(buf); |
| buf.append(")"); |
| } |
| } |
| buf.append(")"); |
| } |
| |
| long toLong(FilterValue litValue) { |
| return ((Number) litValue.getValue()).longValue(); |
| } |
| |
| /** |
| * 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 = Filters.wrap(lhs.getType()); |
| Class rc = Filters.wrap(rhs.getType()); |
| int type = 0; |
| if (requiresCastForMathFunctions && (lc != rc |
| || (lhs.isConstant() && rhs.isConstant()))) { |
| Class c = Filters.promote(lc, rc); |
| type = getJDBCType(JavaTypes.getTypeCode(c), false); |
| if (type != Types.VARBINARY && type != Types.BLOB) { |
| castlhs = (lhs.isConstant() && rhs.isConstant()) || lc != c; |
| castrhs = (lhs.isConstant() && rhs.isConstant()) || rc != c; |
| } |
| } |
| |
| 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 && !supportsModOperator) |
| 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 lhsxml = lhs.getXPath() != null; |
| boolean rhsxml = rhs.getXPath() != null; |
| if (lhsxml || rhsxml) { |
| appendXmlComparison(buf, op, lhs, rhs, lhsxml, rhsxml); |
| return; |
| } |
| boolean castlhs = false; |
| boolean castrhs = false; |
| Class lc = Filters.wrap(lhs.getType()); |
| Class rc = Filters.wrap(rhs.getType()); |
| int type = 0; |
| if (requiresCastForComparisons && (lc != rc |
| || (lhs.isConstant() && rhs.isConstant()))) { |
| Class c = Filters.promote(lc, rc); |
| type = getJDBCType(JavaTypes.getTypeCode(c), false); |
| if (type != Types.VARBINARY && type != Types.BLOB) { |
| castlhs = (lhs.isConstant() && rhs.isConstant()) || lc != c; |
| castrhs = (lhs.isConstant() && rhs.isConstant()) || rc != c; |
| } |
| } |
| |
| 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); |
| } |
| |
| /** |
| * If this dictionary supports XML type, |
| * use this method to append xml predicate. |
| */ |
| public void appendXmlComparison(SQLBuffer buf, String op, FilterValue lhs, |
| FilterValue rhs, boolean lhsxml, boolean rhsxml) { |
| assertSupport(supportsXMLColumn, "SupportsXMLColumn"); |
| } |
| |
| /** |
| * 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, Object 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); |
| if (val instanceof FilterValue) |
| ((FilterValue) val).appendTo(buf); |
| else if (val instanceof SQLBuffer) |
| buf.append(((SQLBuffer) val)); |
| else |
| buf.append(val.toString()); |
| buf.append(mid); |
| buf.append(getTypeName(type)); |
| appendLength(buf, type); |
| buf.append(post); |
| } |
| |
| protected void appendLength(SQLBuffer buf, int type) { |
| } |
| |
| |
| /** |
| * add CAST for a function operator where operand is a param |
| * @param func function name |
| * @param val |
| * @return updated func |
| */ |
| public String addCastAsType(String func, Val val) { |
| return null; |
| } |
| |
| |
| /////////// |
| // 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) |
| return seq.getName(); |
| if (".".equals(catalogSeparator)) |
| return seq.getFullName(); |
| return seq.getSchemaName() + catalogSeparator + seq.getName(); |
| } |
| |
| /** |
| * Make any necessary changes to the given table name to make it valid |
| * for the current DB. |
| */ |
| public String getValidTableName(String name, Schema schema) { |
| while (name.startsWith("_")) |
| name = name.substring(1); |
| return makeNameValid(name, schema.getSchemaGroup(), |
| maxTableNameLength, NAME_TABLE); |
| } |
| |
| /** |
| * Make any necessary changes to the given sequence name to make it valid |
| * for the current DB. |
| */ |
| public String getValidSequenceName(String name, Schema schema) { |
| while (name.startsWith("_")) |
| name = name.substring(1); |
| return makeNameValid("S_" + name, schema.getSchemaGroup(), |
| maxTableNameLength, NAME_SEQUENCE); |
| } |
| |
| /** |
| * Make any necessary changes to the given column name to make it valid |
| * for the current DB. The column name will be made unique for the |
| * specified table. |
| */ |
| public String getValidColumnName(String name, Table table) { |
| return getValidColumnName(name, table, true); |
| } |
| |
| /** |
| * Make any necessary changes to the given column name to make it valid |
| * for the current DB. If checkForUniqueness is true, the column name will |
| * be made unique for the specified table. |
| */ |
| public String getValidColumnName(String name, Table table, |
| boolean checkForUniqueness) { |
| while (name.startsWith("_")) |
| name = name.substring(1); |
| return makeNameValid(name, table, maxColumnNameLength, NAME_ANY, |
| checkForUniqueness); |
| } |
| |
| /** |
| * Make any necessary changes to the given primary key name to make it |
| * valid for the current DB. |
| */ |
| public String getValidPrimaryKeyName(String name, Table table) { |
| while (name.startsWith("_")) |
| name = name.substring(1); |
| return makeNameValid("P_" + name, table.getSchema().getSchemaGroup(), |
| maxConstraintNameLength, NAME_ANY); |
| } |
| |
| /** |
| * Make any necessary changes to the given foreign key name to make it |
| * valid for the current DB. |
| */ |
| public String getValidForeignKeyName(String name, Table table, |
| Table toTable) { |
| while (name.startsWith("_")) |
| name = name.substring(1); |
| String tableName = table.getName(); |
| int len = Math.min(tableName.length(), 7); |
| name = "F_" + shorten(tableName, len) + "_" + name; |
| return makeNameValid(name, table.getSchema().getSchemaGroup(), |
| maxConstraintNameLength, NAME_ANY); |
| } |
| |
| /** |
| * Make any necessary changes to the given index name to make it valid |
| * for the current DB. |
| */ |
| public String getValidIndexName(String name, Table table) { |
| while (name.startsWith("_")) |
| name = name.substring(1); |
| String tableName = table.getName(); |
| int len = Math.min(tableName.length(), 7); |
| name = "I_" + shorten(tableName, len) + "_" + name; |
| return makeNameValid(name, table.getSchema().getSchemaGroup(), |
| maxIndexNameLength, NAME_ANY); |
| } |
| |
| /** |
| * Make any necessary changes to the given unique constraint name to make |
| * it valid for the current DB. |
| */ |
| public String getValidUniqueName(String name, Table table) { |
| while (name.startsWith("_")) |
| name = name.substring(1); |
| String tableName = table.getName(); |
| int len = Math.min(tableName.length(), 7); |
| name = "U_" + shorten(tableName, len) + "_" + name; |
| return makeNameValid(name, table.getSchema().getSchemaGroup(), |
| maxConstraintNameLength, NAME_ANY); |
| } |
| |
| /** |
| * Shorten the specified name to the specified target name. This will |
| * be done by first stripping out the vowels, and then removing |
| * characters from the middle of the word until it reaches the target |
| * length. |
| */ |
| protected static String shorten(String name, int targetLength) { |
| if (name == null || name.length() <= targetLength) |
| return name; |
| |
| StringBuffer nm = new StringBuffer(name); |
| while (nm.length() > targetLength) { |
| if (!stripVowel(nm)) { |
| // cut out the middle char |
| nm.replace(nm.length() / 2, (nm.length() / 2) + 1, ""); |
| } |
| } |
| return nm.toString(); |
| } |
| |
| /** |
| * Remove vowels from the specified StringBuffer. |
| * |
| * @return true if any vowels have been removed |
| */ |
| private static boolean stripVowel(StringBuffer name) { |
| if (name == null || name.length() == 0) |
| return false; |
| |
| char[] vowels = { 'A', 'E', 'I', 'O', 'U', }; |
| for (int i = 0; i < vowels.length; i++) { |
| int index = name.toString().toUpperCase().indexOf(vowels[i]); |
| if (index != -1) { |
| name.replace(index, index + 1, ""); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Shortens the given name to the given maximum length, then checks that |
| * it is not a reserved word. If it is reserved, appends a "0". If |
| * the name conflicts with an existing schema component, the last |
| * character is replace with '0', then '1', etc. |
| * Note that the given max len may be 0 if the database metadata is |
| * incomplete. |
| */ |
| protected String makeNameValid(String name, NameSet set, int maxLen, |
| int nameType) { |
| return makeNameValid(name, set, maxLen, nameType, true); |
| } |
| |
| /** |
| * Shortens the given name to the given maximum length, then checks that |
| * it is not a reserved word. If it is reserved, appends a "0". If |
| * the name conflicts with an existing schema component and uniqueness |
| * checking is enabled, the last character is replace with '0', then |
| * '1', etc. |
| * Note that the given max len may be 0 if the database metadata is |
| * incomplete. |
| */ |
| protected String makeNameValid(String name, NameSet set, int maxLen, |
| int nameType, boolean checkForUniqueness) { |
| if (maxLen < 1) |
| maxLen = 255; |
| if (name.length() > maxLen) |
| name = name.substring(0, maxLen); |
| if (reservedWordSet.contains(name.toUpperCase())) { |
| if (name.length() == maxLen) |
| name = name.substring(0, name.length() - 1); |
| name += "0"; |
| } |
| |
| // now make sure the name is unique |
| if (set != null && checkForUniqueness) { |
| outer: |
| for (int version = 1, chars = 1; true; version++) { |
| // for table names, we check for the table itself in case the |
| // name set is lazy about schema reflection |
| switch (nameType) { |
| case NAME_TABLE: |
| if (!((SchemaGroup) set).isKnownTable(name)) |
| break outer; |
| break; |
| case NAME_SEQUENCE: |
| if (!((SchemaGroup) set).isKnownSequence(name)) |
| break outer; |
| break; |
| default: |
| if (!set.isNameTaken(name)) |
| break outer; |
| } |
| |
| // a single char for the version is probably enough, but might |
| // as well be general about it... |
| if (version > 1) |
| name = name.substring(0, name.length() - chars); |
| if (version >= Math.pow(10, chars)) |
| chars++; |
| if (name.length() + chars > maxLen) |
| name = name.substring(0, maxLen - chars); |
| name = name + version; |
| } |
| } |
| return name.toUpperCase(); |
| } |
| |
| /** |
| * Return a series of SQL statements to create the given table, complete |
| * with columns. Indexes and constraints will be created separately. |
| */ |
| public String[] getCreateTableSQL(Table table) { |
| StringBuffer buf = new StringBuffer(); |
| buf.append("CREATE TABLE ").append(getFullName(table, false)); |
| if (supportsComments && table.hasComment()) { |
| buf.append(" "); |
| comment(buf, table.getComment()); |
| buf.append("\n ("); |
| } else { |
| buf.append(" ("); |
| } |
| |
| // do this before getting the columns so we know how to handle |
| // the last comma |
| StringBuffer endBuf = new StringBuffer(); |
| PrimaryKey pk = table.getPrimaryKey(); |
| String pkStr; |
| if (pk != null) { |
| pkStr = getPrimaryKeyConstraintSQL(pk); |
| if (pkStr != null) |
| endBuf.append(pkStr); |
| } |
| |
| Unique[] unqs = table.getUniques(); |
| String unqStr; |
| for (int i = 0; i < unqs.length; i++) { |
| unqStr = getUniqueConstraintSQL(unqs[i]); |
| if (unqStr != null) { |
| if (endBuf.length() > 0) |
| endBuf.append(", "); |
| endBuf.append(unqStr); |
| } |
| } |
| |
| Column[] cols = table.getColumns(); |
| for (int i = 0; i < cols.length; i++) { |
| buf.append(getDeclareColumnSQL(cols[i], false)); |
| if (i < cols.length - 1 || endBuf.length() > 0) |
| buf.append(", "); |
| if (supportsComments && cols[i].hasComment()) { |
| comment(buf, cols[i].getComment()); |
| buf.append("\n "); |
| } |
| } |
| |
| buf.append(endBuf.toString()); |
| buf.append(")"); |
| return new String[]{ buf.toString() }; |
| } |
| |
| protected StringBuffer comment(StringBuffer buf, String comment) { |
| return buf.append("-- ").append(comment); |
| } |
| |
| /** |
| * Return a series of SQL statements to drop the given table. Indexes |
| * will be dropped separately. Returns |
| * <code>DROP TABLE <table name></code> by default. |
| */ |
| public String[] getDropTableSQL(Table table) { |
| String drop = MessageFormat.format(dropTableSQL, new Object[]{ |
| getFullName(table, false) }); |
| return new String[]{ drop }; |
| } |
| |
| /** |
| * Return a series of SQL statements to create the given sequence. Returns |
| * <code>CREATE SEQUENCE <sequence name>[ START WITH <start>] |
| * [ INCREMENT BY <increment>]</code> by default. |
| */ |
| public String[] getCreateSequenceSQL(Sequence seq) { |
| if (nextSequenceQuery == null) |
| return null; |
| |
| StringBuffer buf = new StringBuffer(); |
| buf.append("CREATE SEQUENCE "); |
| buf.append(getFullName(seq)); |
| if (seq.getInitialValue() != 0) |
| buf.append(" START WITH ").append(seq.getInitialValue()); |
| if (seq.getIncrement() > 1) |
| buf.append(" INCREMENT BY ").append(seq.getIncrement()); |
| return new String[]{ buf.toString() }; |
| } |
| |
| /** |
| * Return a series of SQL statements to drop the given sequence. Returns |
| * <code>DROP SEQUENCE <sequence name></code> by default. |
| */ |
| public String[] getDropSequenceSQL(Sequence seq) { |
| return new String[]{ "DROP SEQUENCE " + getFullName(seq) }; |
| } |
| |
| /** |
| * Return a series of SQL statements to create the given index. Returns |
| * <code>CREATE [UNIQUE] INDEX <index name> ON <table name> |
| * (<col list>)</code> by default. |
| */ |
| public String[] getCreateIndexSQL(Index index) { |
| StringBuffer buf = new StringBuffer(); |
| buf.append("CREATE "); |
| if (index.isUnique()) |
| buf.append("UNIQUE "); |
| buf.append("INDEX ").append(index.getName()); |
| |
| buf.append(" ON ").append(getFullName(index.getTable(), false)); |
| buf.append(" (").append(Strings.join(index.getColumns(), ", ")). |
| append(")"); |
| |
| return new String[]{ buf.toString() }; |
| } |
| |
| /** |
| * Return a series of SQL statements to drop the given index. Returns |
| * <code>DROP INDEX <index name></code> by default. |
| */ |
| public String[] getDropIndexSQL(Index index) { |
| return new String[]{ "DROP INDEX " + getFullName(index) }; |
| } |
| |
| /** |
| * Return a series of SQL statements to add the given column to |
| * its table. Return an empty array if operation not supported. Returns |
| * <code>ALTER TABLE <table name> ADD (<col dec>)</code> |
| * by default. |
| */ |
| public String[] getAddColumnSQL(Column column) { |
| if (!supportsAlterTableWithAddColumn) |
| return new String[0]; |
| |
| String dec = getDeclareColumnSQL(column, true); |
| if (dec == null) |
| return new String[0]; |
| return new String[]{ "ALTER TABLE " |
| + getFullName(column.getTable(), false) + " ADD " + dec }; |
| } |
| |
| /** |
| * Return a series of SQL statements to drop the given column from |
| * its table. Return an empty array if operation not supported. Returns |
| * <code>ALTER TABLE <table name> DROP COLUMN <col name></code> |
| * by default. |
| */ |
| public String[] getDropColumnSQL(Column column) { |
| if (!supportsAlterTableWithDropColumn) |
| return new String[0]; |
| return new String[]{ "ALTER TABLE " |
| + getFullName(column.getTable(), false) |
| + " DROP COLUMN " + column }; |
| } |
| |
| /** |
| * Return a series of SQL statements to add the given primary key to |
| * its table. Return an empty array if operation not supported. |
| * Returns <code>ALTER TABLE <table name> ADD |
| * <pk cons sql ></code> by default. |
| */ |
| public String[] getAddPrimaryKeySQL(PrimaryKey pk) { |
| String pksql = getPrimaryKeyConstraintSQL(pk); |
| if (pksql == null) |
| return new String[0]; |
| return new String[]{ "ALTER TABLE " |
| + getFullName(pk.getTable(), false) + " ADD " + pksql }; |
| } |
| |
| /** |
| * Return a series of SQL statements to drop the given primary key from |
| * its table. Return an empty array if operation not supported. |
| * Returns <code>ALTER TABLE <table name> DROP CONSTRAINT |
| * <pk name></code> by default. |
| */ |
| public String[] getDropPrimaryKeySQL(PrimaryKey pk) { |
| if (pk.getName() == null) |
| return new String[0]; |
| return new String[]{ "ALTER TABLE " |
| + getFullName(pk.getTable(), false) |
| + " DROP CONSTRAINT " + pk.getName() }; |
| } |
| |
| /** |
| * Return a series of SQL statements to add the given foreign key to |
| * its table. Return an empty array if operation not supported. |
| * Returns <code>ALTER TABLE <table name> ADD |
| * <fk cons sql ></code> by default. |
| */ |
| public String[] getAddForeignKeySQL(ForeignKey fk) { |
| String fkSQL = getForeignKeyConstraintSQL(fk); |
| if (fkSQL == null) |
| return new String[0]; |
| return new String[]{ "ALTER TABLE " |
| + getFullName(fk.getTable(), false) + " ADD " + fkSQL }; |
| } |
| |
| /** |
| * Return a series of SQL statements to drop the given foreign key from |
| * its table. Return an empty array if operation not supported. |
| * Returns <code>ALTER TABLE <table name> DROP CONSTRAINT |
| * <fk name></code> by default. |
| */ |
| public String[] getDropForeignKeySQL(ForeignKey fk) { |
| if (fk.getName() == null) |
| return new String[0]; |
| return new String[]{ "ALTER TABLE " |
| + getFullName(fk.getTable(), false) |
| + " DROP CONSTRAINT " + fk.getName() }; |
| } |
| |
| /** |
| * Return the declaration SQL for the given column. This method is used |
| * for each column from within {@link #getCreateTableSQL} and |
| * {@link #getAddColumnSQL}. |
| */ |
| protected String getDeclareColumnSQL(Column col, boolean alter) { |
| StringBuffer buf = new StringBuffer(); |
| buf.append(col).append(" "); |
| buf.append(getTypeName(col)); |
| |
| // can't add constraints to a column we're adding after table |
| // creation, cause some data might already be inserted |
| if (!alter) { |
| if (col.getDefaultString() != null && !col.isAutoAssigned()) |
| buf.append(" DEFAULT ").append(col.getDefaultString()); |
| if (col.isNotNull()) |
| buf.append(" NOT NULL"); |
| } |
| if (col.isAutoAssigned()) { |
| if (!supportsAutoAssign) |
| log.warn(_loc.get("invalid-autoassign", platform, col)); |
| else if (autoAssignClause != null) |
| buf.append(" ").append(autoAssignClause); |
| } |
| return buf.toString(); |
| } |
| |
| /** |
| * Return the declaration SQL for the given primary key. This method is |
| * used from within {@link #getCreateTableSQL} and |
| * {@link #getAddPrimaryKeySQL}. Returns |
| * <code>CONSTRAINT <pk name> PRIMARY KEY (<col list>)</code> |
| * by default. |
| */ |
| protected String getPrimaryKeyConstraintSQL(PrimaryKey pk) { |
| // if we have disabled the creation of primary keys, abort here |
| if (!createPrimaryKeys) |
| return null; |
| |
| String name = pk.getName(); |
| if (name != null && reservedWordSet.contains(name.toUpperCase())) |
| name = null; |
| |
| StringBuffer buf = new StringBuffer(); |
| if (name != null && CONS_NAME_BEFORE.equals(constraintNameMode)) |
| buf.append("CONSTRAINT ").append(name).append(" "); |
| buf.append("PRIMARY KEY "); |
| if (name != null && CONS_NAME_MID.equals(constraintNameMode)) |
| buf.append(name).append(" "); |
| buf.append("(").append(Strings.join(pk.getColumns(), ", ")). |
| append(")"); |
| if (name != null && CONS_NAME_AFTER.equals(constraintNameMode)) |
| buf.append(" CONSTRAINT ").append(name); |
| return buf.toString(); |
| } |
| |
| /** |
| * Return the declaration SQL for the given foreign key, or null if it is |
| * not supported. This method is used from within |
| * {@link #getCreateTableSQL} and {@link #getAddForeignKeySQL}. Returns |
| * <code>CONSTRAINT <cons name> FOREIGN KEY (<col list>) |
| * REFERENCES <foreign table> (<col list>) |
| * [ON DELETE <action>] [ON UPDATE <action>]</code> by default. |
| */ |
| protected String getForeignKeyConstraintSQL(ForeignKey fk) { |
| if (!supportsForeignKeys) |
| return null; |
| if (fk.getDeleteAction() == ForeignKey.ACTION_NONE) |
| return null; |
| if (fk.isDeferred() && !supportsDeferredForeignKeyConstraints()) |
| return null; |
| if (!supportsDeleteAction(fk.getDeleteAction()) |
| || !supportsUpdateAction(fk.getUpdateAction())) |
| return null; |
| |
| Column[] locals = fk.getColumns(); |
| Column[] foreigns = fk.getPrimaryKeyColumns(); |
| |
| int delActionId = fk.getDeleteAction(); |
| if (delActionId == ForeignKey.ACTION_NULL) { |
| for (int i = 0; i < locals.length; i++) { |
| if (locals[i].isNotNull()) |
| delActionId = ForeignKey.ACTION_NONE; |
| } |
| } |
| |
| String delAction = getActionName(delActionId); |
| String upAction = getActionName(fk.getUpdateAction()); |
| |
| StringBuffer buf = new StringBuffer(); |
| if (fk.getName() != null |
| && CONS_NAME_BEFORE.equals(constraintNameMode)) |
| buf.append("CONSTRAINT ").append(fk.getName()).append(" "); |
| buf.append("FOREIGN KEY "); |
| if (fk.getName() != null && CONS_NAME_MID.equals(constraintNameMode)) |
| buf.append(fk.getName()).append(" "); |
| buf.append("(").append(Strings.join(locals, ", ")).append(")"); |
| buf.append(" REFERENCES "); |
| buf.append(getFullName(foreigns[0].getTable(), false)); |
| buf.append(" (").append(Strings.join(foreigns, ", ")).append(")"); |
| if (delAction != null) |
| buf.append(" ON DELETE ").append(delAction); |
| if (upAction != null) |
| buf.append(" ON UPDATE ").append(upAction); |
| if (fk.isDeferred()) |
| buf.append(" INITIALLY DEFERRED"); |
| if (supportsDeferredForeignKeyConstraints()) |
| buf.append(" DEFERRABLE"); |
| if (fk.getName() != null |
| && CONS_NAME_AFTER.equals(constraintNameMode)) |
| buf.append(" CONSTRAINT ").append(fk.getName()); |
| return buf.toString(); |
| } |
| |
| /** |
| * Whether or not this dictionary supports deferred foreign key constraints. |
| * This implementation returns {@link #supportsUniqueConstraints}. |
| * |
| * @since 1.1.0 |
| */ |
| protected boolean supportsDeferredForeignKeyConstraints() { |
| return supportsDeferredConstraints; |
| } |
| |
| /** |
| * Return the name of the given foreign key action. |
| */ |
| private String getActionName(int action) { |
| switch (action) { |
| case ForeignKey.ACTION_CASCADE: |
| return "CASCADE"; |
| case ForeignKey.ACTION_NULL: |
| return "SET NULL"; |
| case ForeignKey.ACTION_DEFAULT: |
| return "SET DEFAULT"; |
| default: |
| return null; |
| } |
| } |
| |
| /** |
| * Whether this database supports the given foreign key delete action. |
| */ |
| public boolean supportsDeleteAction(int action) { |
| if (action == ForeignKey.ACTION_NONE) |
| return true; |
| if (!supportsForeignKeys) |
| return false; |
| switch (action) { |
| case ForeignKey.ACTION_RESTRICT: |
| return supportsRestrictDeleteAction; |
| case ForeignKey.ACTION_CASCADE: |
| return supportsCascadeDeleteAction; |
| case ForeignKey.ACTION_NULL: |
| return supportsNullDeleteAction; |
| case ForeignKey.ACTION_DEFAULT: |
| return supportsDefaultDeleteAction; |
| default: |
| return false; |
| } |
| } |
| |
| /** |
| * Whether this database supports the given foreign key update action. |
| */ |
| public boolean supportsUpdateAction(int action) { |
| if (action == ForeignKey.ACTION_NONE) |
| return true; |
| if (!supportsForeignKeys) |
| return false; |
| switch (action) { |
| case ForeignKey.ACTION_RESTRICT: |
| return supportsRestrictUpdateAction; |
| case ForeignKey.ACTION_CASCADE: |
| return supportsCascadeUpdateAction; |
| case ForeignKey.ACTION_NULL: |
| return supportsNullUpdateAction; |
| case ForeignKey.ACTION_DEFAULT: |
| return supportsDefaultUpdateAction; |
| default: |
| return false; |
| } |
| } |
| |
| /** |
| * Return the declaration SQL for the given unique constraint. This |
| * method is used from within {@link #getCreateTableSQL}. |
| * Returns <code>CONSTRAINT <name> UNIQUE (<col list>)</code> |
| * by default. |
| */ |
| protected String getUniqueConstraintSQL(Unique unq) { |
| if (!supportsUniqueConstraints |
| || (unq.isDeferred() && !supportsDeferredUniqueConstraints())) |
| return null; |
| |
| StringBuffer buf = new StringBuffer(); |
| if (unq.getName() != null |
| && CONS_NAME_BEFORE.equals(constraintNameMode)) |
| buf.append("CONSTRAINT ").append(unq.getName()).append(" "); |
| buf.append("UNIQUE "); |
| if (unq.getName() != null && CONS_NAME_MID.equals(constraintNameMode)) |
| buf.append(unq.getName()).append(" "); |
| buf.append("(").append(Strings.join(unq.getColumns(), ", ")). |
| append(")"); |
| if (unq.isDeferred()) |
| buf.append(" INITIALLY DEFERRED"); |
| if (supportsDeferredUniqueConstraints()) |
| buf.append(" DEFERRABLE"); |
| if (unq.getName() != null |
| && CONS_NAME_AFTER.equals(constraintNameMode)) |
| buf.append(" CONSTRAINT ").append(unq.getName()); |
| return buf.toString(); |
| } |
| |
| /** |
| * Whether or not this dictionary supports deferred unique constraints. |
| * This implementation returns {@link #supportsUniqueConstraints}. |
| * |
| * @since 1.1.0 |
| */ |
| protected boolean supportsDeferredUniqueConstraints() { |
| return supportsDeferredConstraints; |
| } |
| |
| ///////////////////// |
| // Database metadata |
| ///////////////////// |
| |
| /** |
| * This method is used to filter system tables from database metadata. |
| * Return true if the given table name represents a system table that |
| * should not appear in the schema definition. By default, returns |
| * true only if the given table is in the internal list of system tables, |
| * or if the given schema is in the list of system schemas and is not |
| * the target schema. |
| * |
| * @param name the table name |
| * @param schema the table schema; may be null |
| * @param targetSchema if true, then the given schema was listed by |
| * the user as one of his schemas |
| */ |
| public boolean isSystemTable(String name, String schema, |
| boolean targetSchema) { |
| if (systemTableSet.contains(name.toUpperCase())) |
| return true; |
| return !targetSchema && schema != null |
| && systemSchemaSet.contains(schema.toUpperCase()); |
| } |
| |
| /** |
| * This method is used to filter system indexes from database metadata. |
| * Return true if the given index name represents a system index that |
| * should not appear in the schema definition. Returns false by default. |
| * |
| * @param name the index name |
| * @param table the index table |
| */ |
| public boolean isSystemIndex(String name, Table table) { |
| return false; |
| } |
| |
| /** |
| * This method is used to filter system sequences from database metadata. |
| * Return true if the given sequence represents a system sequence that |
| * should not appear in the schema definition. Returns true if system |
| * schema by default. |
| * |
| * @param name the table name |
| * @param schema the table schema; may be null |
| * @param targetSchema if true, then the given schema was listed by |
| * the user as one of his schemas |
| */ |
| public boolean isSystemSequence(String name, String schema, |
| boolean targetSchema) { |
| return !targetSchema && schema != null |
| && systemSchemaSet.contains(schema.toUpperCase()); |
| } |
| |
| /** |
| * Reflect on the schema to find tables matching the given name pattern. |
| */ |
| public Table[] getTables(DatabaseMetaData meta, String catalog, |
| String schemaName, String tableName, Connection conn) |
| throws SQLException { |
| if (!supportsSchemaForGetTables) |
| schemaName = null; |
| else |
| schemaName = getSchemaNameForMetadata(schemaName); |
| |
| String[] types = Strings.split(tableTypes, ",", 0); |
| for (int i = 0; i < types.length; i++) |
| types[i] = types[i].trim(); |
| |
| beforeMetadataOperation(conn); |
| ResultSet tables = null; |
| try { |
| tables = meta.getTables(getCatalogNameForMetadata(catalog), |
| schemaName, getTableNameForMetadata(tableName), types); |
| List tableList = new ArrayList(); |
| while (tables != null && tables.next()) |
| tableList.add(newTable(tables)); |
| return (Table[]) tableList.toArray(new Table[tableList.size()]); |
| } finally { |
| if (tables != null) |
| try { |
| tables.close(); |
| } catch (Exception e) { |
| } |
| } |
| } |
| |
| /** |
| * Create a new table from the information in the schema metadata. |
| */ |
| protected Table newTable(ResultSet tableMeta) |
| throws SQLException { |
| Table t = new Table(); |
| t.setName(tableMeta.getString("TABLE_NAME")); |
| return t; |
| } |
| |
| /** |
| * Reflect on the schema to find sequences matching the given name pattern. |
| * Returns an empty array by default, as there is no standard way to |
| * retrieve a list of sequences. |
| */ |
| public Sequence[] getSequences(DatabaseMetaData meta, String catalog, |
| String schemaName, String sequenceName, Connection conn) |
| throws SQLException { |
| String str = getSequencesSQL(schemaName, sequenceName); |
| if (str == null) |
| return new Sequence[0]; |
| |
| PreparedStatement stmnt = prepareStatement(conn, str); |
| ResultSet rs = null; |
| try { |
| int idx = 1; |
| if (schemaName != null) |
| stmnt.setString(idx++, schemaName.toUpperCase()); |
| if (sequenceName != null) |
| stmnt.setString(idx++, sequenceName); |
| |
| rs = executeQuery(conn, stmnt, str); |
| return getSequence(rs); |
| } finally { |
| if (rs != null) |
| try { |
| rs.close(); |
| } catch (SQLException se) { |
| } |
| if (stmnt != null) |
| try { |
| stmnt.close(); |
| } catch (SQLException se) { |
| } |
| } |
| } |
| |
| /** |
| * Create a new sequence from the information in the schema metadata. |
| */ |
| protected Sequence newSequence(ResultSet sequenceMeta) |
| throws SQLException { |
| Sequence seq = new Sequence(); |
| seq.setSchemaName(sequenceMeta.getString("SEQUENCE_SCHEMA")); |
| seq.setName(sequenceMeta.getString("SEQUENCE_NAME")); |
| return seq; |
| } |
| |
| /** |
| * Return the SQL needed to select the list of sequences. |
| */ |
| protected String getSequencesSQL(String schemaName, String sequenceName) { |
| return null; |
| } |
| |
| /** |
| * Reflect on the schema to find columns matching the given table and |
| * column patterns. |
| */ |
| public Column[] getColumns(DatabaseMetaData meta, String catalog, |
| String schemaName, String tableName, String columnName, Connection conn) |
| throws SQLException { |
| if (tableName == null && !supportsNullTableForGetColumns) |
| return null; |
| |
| if (!supportsSchemaForGetColumns) |
| schemaName = null; |
| else |
| schemaName = getSchemaNameForMetadata(schemaName); |
| |
| beforeMetadataOperation(conn); |
| ResultSet cols = null; |
| try { |
| cols = meta.getColumns(getCatalogNameForMetadata(catalog), |
| schemaName, getTableNameForMetadata(tableName), |
| getColumnNameForMetadata(columnName)); |
| |
| List columnList = new ArrayList(); |
| while (cols != null && cols.next()) |
| columnList.add(newColumn(cols)); |
| return (Column[]) columnList.toArray |
| (new Column[columnList.size()]); |
| } finally { |
| if (cols != null) |
| try { |
| cols.close(); |
| } catch (Exception e) { |
| } |
| } |
| } |
| |
| /** |
| * Create a new column from the information in the schema metadata. |
| */ |
| protected Column newColumn(ResultSet colMeta) |
| throws SQLException { |
| Column c = new Column(); |
| c.setSchemaName(colMeta.getString("TABLE_SCHEM")); |
| c.setTableName(colMeta.getString("TABLE_NAME")); |
| c.setName(colMeta.getString("COLUMN_NAME")); |
| c.setType(colMeta.getInt("DATA_TYPE")); |
| c.setTypeName(colMeta.getString("TYPE_NAME")); |
| c.setSize(colMeta.getInt("COLUMN_SIZE")); |
| c.setDecimalDigits(colMeta.getInt("DECIMAL_DIGITS")); |
| c.setNotNull(colMeta.getInt("NULLABLE") |
| == DatabaseMetaData.columnNoNulls); |
| |
| String def = colMeta.getString("COLUMN_DEF"); |
| if (!StringUtils.isEmpty(def) && !"null".equalsIgnoreCase(def)) |
| c.setDefaultString(def); |
| return c; |
| } |
| |
| /** |
| * Reflect on the schema to find primary keys for the given table pattern. |
| */ |
| public PrimaryKey[] getPrimaryKeys(DatabaseMetaData meta, |
| String catalog, String schemaName, String tableName, Connection conn) |
| throws SQLException { |
| if (useGetBestRowIdentifierForPrimaryKeys) |
| return getPrimaryKeysFromBestRowIdentifier(meta, catalog, |
| schemaName, tableName, conn); |
| return getPrimaryKeysFromGetPrimaryKeys(meta, catalog, |
| schemaName, tableName, conn); |
| } |
| |
| /** |
| * Reflect on the schema to find primary keys for the given table pattern. |
| */ |
| protected PrimaryKey[] getPrimaryKeysFromGetPrimaryKeys |
| (DatabaseMetaData meta, String catalog, String schemaName, |
| String tableName, Connection conn) |
| throws SQLException { |
| if (tableName == null && !supportsNullTableForGetPrimaryKeys) |
| return null; |
| |
| beforeMetadataOperation(conn); |
| ResultSet pks = null; |
| try { |
| pks = meta.getPrimaryKeys(getCatalogNameForMetadata(catalog), |
| getSchemaNameForMetadata(schemaName), |
| getTableNameForMetadata(tableName)); |
| |
| List pkList = new ArrayList(); |
| while (pks != null && pks.next()) |
| pkList.add(newPrimaryKey(pks)); |
| return (PrimaryKey[]) pkList.toArray |
| (new PrimaryKey[pkList.size()]); |
| } finally { |
| if (pks != null) |
| try { |
| pks.close(); |
| } catch (Exception e) { |
| } |
| } |
| } |
| |
| /** |
| * Create a new primary key from the information in the schema metadata. |
| */ |
| protected PrimaryKey newPrimaryKey(ResultSet pkMeta) |
| throws SQLException { |
| PrimaryKey pk = new PrimaryKey(); |
| pk.setSchemaName(pkMeta.getString("TABLE_SCHEM")); |
| pk.setTableName(pkMeta.getString("TABLE_NAME")); |
| pk.setColumnName(pkMeta.getString("COLUMN_NAME")); |
| pk.setName(pkMeta.getString("PK_NAME")); |
| return pk; |
| } |
| |
| /** |
| * Reflect on the schema to find primary keys for the given table pattern. |
| */ |
| protected PrimaryKey[] getPrimaryKeysFromBestRowIdentifier |
| (DatabaseMetaData meta, String catalog, String schemaName, |
| String tableName, Connection conn) |
| throws SQLException { |
| if (tableName == null) |
| return null; |
| |
| beforeMetadataOperation(conn); |
| ResultSet pks = null; |
| try { |
| pks = meta.getBestRowIdentifier(catalog, schemaName, |
| tableName, 0, false); |
| |
| List pkList = new ArrayList(); |
| while (pks != null && pks.next()) { |
| PrimaryKey pk = new PrimaryKey(); |
| pk.setSchemaName(schemaName); |
| pk.setTableName(tableName); |
| pk.setColumnName(pks.getString("COLUMN_NAME")); |
| pkList.add(pk); |
| } |
| return (PrimaryKey[]) pkList.toArray |
| (new PrimaryKey[pkList.size()]); |
| } finally { |
| if (pks != null) |
| try { |
| pks.close(); |
| } catch (Exception e) { |
| } |
| } |
| } |
| |
| /** |
| * Reflect on the schema to find indexes matching the given table pattern. |
| */ |
| public Index[] getIndexInfo(DatabaseMetaData meta, String catalog, |
| String schemaName, String tableName, boolean unique, |
| boolean approx, Connection conn) |
| throws SQLException { |
| if (tableName == null && !supportsNullTableForGetIndexInfo) |
| return null; |
| |
| beforeMetadataOperation(conn); |
| ResultSet indexes = null; |
| try { |
| indexes = meta.getIndexInfo(getCatalogNameForMetadata(catalog), |
| getSchemaNameForMetadata(schemaName), |
| getTableNameForMetadata(tableName), unique, approx); |
| |
| List indexList = new ArrayList(); |
| while (indexes != null && indexes.next()) |
| indexList.add(newIndex(indexes)); |
| return (Index[]) indexList.toArray(new Index[indexList.size()]); |
| } finally { |
| if (indexes != null) |
| try { |
| indexes.close(); |
| } catch (Exception e) { |
| } |
| } |
| } |
| |
| /** |
| * Create a new index from the information in the schema metadata. |
| */ |
| protected Index newIndex(ResultSet idxMeta) |
| throws SQLException { |
| Index idx = new Index(); |
| idx.setSchemaName(idxMeta.getString("TABLE_SCHEM")); |
| idx.setTableName(idxMeta.getString("TABLE_NAME")); |
| idx.setColumnName(idxMeta.getString("COLUMN_NAME")); |
| idx.setName(idxMeta.getString("INDEX_NAME")); |
| idx.setUnique(!idxMeta.getBoolean("NON_UNIQUE")); |
| return idx; |
| } |
| |
| /** |
| * Reflect on the schema to return foreign keys imported by the given |
| * table pattern. |
| */ |
| public ForeignKey[] getImportedKeys(DatabaseMetaData meta, String catalog, |
| String schemaName, String tableName, Connection conn) |
| throws SQLException { |
| if (!supportsForeignKeys) |
| return null; |
| if (tableName == null && !supportsNullTableForGetImportedKeys) |
| return null; |
| |
| beforeMetadataOperation(conn); |
| ResultSet keys = null; |
| try { |
| keys = meta.getImportedKeys(getCatalogNameForMetadata(catalog), |
| getSchemaNameForMetadata(schemaName), |
| getTableNameForMetadata(tableName)); |
| |
| List importedKeyList = new ArrayList(); |
| while (keys != null && keys.next()) |
| importedKeyList.add(newForeignKey(keys)); |
| return (ForeignKey[]) importedKeyList.toArray |
| (new ForeignKey[importedKeyList.size()]); |
| } finally { |
| if (keys != null) |
| try { |
| keys.close(); |
| } catch (Exception e) { |
| } |
| } |
| } |
| |
| /** |
| * Create a new foreign key from the information in the schema metadata. |
| */ |
| protected ForeignKey newForeignKey(ResultSet fkMeta) |
| throws SQLException { |
| ForeignKey fk = new ForeignKey(); |
| fk.setSchemaName(fkMeta.getString("FKTABLE_SCHEM")); |
| fk.setTableName(fkMeta.getString("FKTABLE_NAME")); |
| fk.setColumnName(fkMeta.getString("FKCOLUMN_NAME")); |
| fk.setName(fkMeta.getString("FK_NAME")); |
| fk.setPrimaryKeySchemaName(fkMeta.getString("PKTABLE_SCHEM")); |
| fk.setPrimaryKeyTableName(fkMeta.getString("PKTABLE_NAME")); |
| fk.setPrimaryKeyColumnName(fkMeta.getString("PKCOLUMN_NAME")); |
| fk.setKeySequence(fkMeta.getShort("KEY_SEQ")); |
| fk.setDeferred(fkMeta.getShort("DEFERRABILITY") |
| == DatabaseMetaData.importedKeyInitiallyDeferred); |
| |
| int del = fkMeta.getShort("DELETE_RULE"); |
| switch (del) { |
| case DatabaseMetaData.importedKeySetNull: |
| fk.setDeleteAction(ForeignKey.ACTION_NULL); |
| break; |
| case DatabaseMetaData.importedKeySetDefault: |
| fk.setDeleteAction(ForeignKey.ACTION_DEFAULT); |
| break; |
| case DatabaseMetaData.importedKeyCascade: |
| fk.setDeleteAction(ForeignKey.ACTION_CASCADE); |
| break; |
| default: |
| fk.setDeleteAction(ForeignKey.ACTION_RESTRICT); |
| break; |
| } |
| return fk; |
| } |
| |
| /** |
| * Returns the table name that will be used for obtaining information |
| * from {@link DatabaseMetaData}. |
| */ |
| protected String getTableNameForMetadata(String tableName) { |
| return convertSchemaCase(tableName); |
| } |
| |
| /** |
| * Returns the schema name that will be used for obtaining information |
| * from {@link DatabaseMetaData}. |
| */ |
| protected String getSchemaNameForMetadata(String schemaName) { |
| if (schemaName == null) |
| schemaName = conf.getSchema(); |
| return convertSchemaCase(schemaName); |
| } |
| |
| /** |
| * Returns the catalog name that will be used for obtaining information |
| * from {@link DatabaseMetaData}. |
| */ |
| protected String getCatalogNameForMetadata(String catalogName) { |
| return convertSchemaCase(catalogName); |
| } |
| |
| /** |
| * Returns the column name that will be used for obtaining information |
| * from {@link DatabaseMetaData}. |
| */ |
| protected String getColumnNameForMetadata(String columnName) { |
| return convertSchemaCase(columnName); |
| } |
| |
| /** |
| * Convert the specified schema name to a name that the database will |
| * be able to understand. |
| */ |
| protected String convertSchemaCase(String objectName) { |
| if (objectName == null) |
| return null; |
| |
| String scase = getSchemaCase(); |
| if (SCHEMA_CASE_LOWER.equals(scase)) |
| return objectName.toLowerCase(); |
| if (SCHEMA_CASE_PRESERVE.equals(scase)) |
| return objectName; |
| return objectName.toUpperCase(); |
| } |
| |
| /** |
| * Return DB specific schemaCase |
| */ |
| public String getSchemaCase(){ |
| return schemaCase; |
| } |
| |
| /** |
| * Prepared the connection for metadata operations. |
| */ |
| private void beforeMetadataOperation(Connection c) { |
| if (requiresAutoCommitForMetaData) { |
| try { |
| c.rollback(); |
| } catch (SQLException sqle) { |
| } |
| try { |
| if (!c.getAutoCommit()) |
| c.setAutoCommit(true); |
| } catch (SQLException sqle) { |
| } |
| } |
| } |
| |
| ///////////////////////////// |
| // Sequences and Auto-Assign |
| ///////////////////////////// |
| |
| /** |
| * Return the last generated value for the given column. |
| * Throws an exception by default if {@link #lastGeneratedKeyQuery} is null. |
| */ |
| public Object getGeneratedKey(Column col, Connection conn) |
| throws SQLException { |
| if (lastGeneratedKeyQuery == null) |
| throw new StoreException(_loc.get("no-auto-assign")); |
| |
| // replace things like "SELECT MAX({0}) FROM {1}" |
| String query = lastGeneratedKeyQuery; |
| if (query.indexOf('{') != -1) // only if the token is in the string |
| { |
| query = MessageFormat.format(query, new Object[]{ |
| col.getName(), getFullName(col.getTable(), false), |
| getGeneratedKeySequenceName(col), |
| }); |
| } |
| |
| PreparedStatement stmnt = prepareStatement(conn, query); |
| ResultSet rs = null; |
| try { |
| rs = executeQuery(conn, stmnt, query); |
| return getKey(rs, col); |
| } finally { |
| if (rs != null) |
| try { rs.close(); } catch (SQLException se) {} |
| if (stmnt != null) |
| try { stmnt.close(); } catch (SQLException se) {} |
| } |
| } |
| |
| /** |
| * Return the sequence name used by databases for the given autoassigned |
| * column. This is only used by databases that require an explicit name |
| * to be used for auto-assign support. |
| */ |
| protected String getGeneratedKeySequenceName(Column col) { |
| String tname = col.getTableName(); |
| String cname = col.getName(); |
| int max = maxAutoAssignNameLength; |
| int extraChars = -max + tname.length() + 1 // <tname> + '_' |
| + cname.length() + 4; // <cname> + '_SEQ' |
| if (extraChars > 0) { |
| // this assumes that tname is longer than extraChars |
| tname = tname.substring(0, tname.length() - extraChars); |
| } |
| StringBuffer buf = new StringBuffer(max); |
| buf.append(tname).append("_").append(cname).append("_SEQ"); |
| return buf.toString(); |
| } |
| |
| /////////////////////////////// |
| // Configurable implementation |
| /////////////////////////////// |
| |
| public void setConfiguration(Configuration conf) { |
| this.conf = (JDBCConfiguration) conf; |
| this.log = this.conf.getLog(JDBCConfiguration.LOG_JDBC); |
| |
| // warn about unsupported dicts |
| if (log.isWarnEnabled() && !isSupported()) |
| log.warn(_loc.get("dict-not-supported", getClass())); |
| } |
| |
| private boolean isSupported() { |
| // if this is a custom dict, traverse to whatever openjpa dict it extends |
| Class c = getClass(); |
| while (!c.getName().startsWith("org.apache.openjpa.")) |
| c = c.getSuperclass(); |
| |
| // the generic dbdictionary is not considered a supported dict; all |
| // other concrete dictionaries are |
| if (c == DBDictionary.class) |
| return false; |
| return true; |
| } |
| |
| public void startConfiguration() { |
| } |
| |
| public void endConfiguration() { |
| // initialize the set of reserved SQL92 words from resource |
| InputStream in = DBDictionary.class.getResourceAsStream |
| ("sql-keywords.rsrc"); |
| try { |
| String keywords = new BufferedReader(new InputStreamReader(in)). |
| readLine(); |
| in.close(); |
| reservedWordSet.addAll(Arrays.asList(Strings.split |
| (keywords, ",", 0))); |
| } catch (IOException ioe) { |
| throw new GeneralException(ioe); |
| } finally { |
| try { in.close(); } catch (IOException e) {} |
| } |
| |
| // add additional reserved words set by user |
| if (reservedWords != null) |
| reservedWordSet.addAll(Arrays.asList(Strings.split |
| (reservedWords.toUpperCase(), ",", 0))); |
| |
| // add system schemas set by user |
| if (systemSchemas != null) |
| systemSchemaSet.addAll(Arrays.asList(Strings.split |
| (systemSchemas.toUpperCase(), ",", 0))); |
| |
| // add system tables set by user |
| if (systemTables != null) |
| systemTableSet.addAll(Arrays.asList(Strings.split |
| (systemTables.toUpperCase(), ",", 0))); |
| |
| // add fixed size type names set by the user |
| if (fixedSizeTypeNames != null) |
| fixedSizeTypeNameSet.addAll(Arrays.asList(Strings.split |
| (fixedSizeTypeNames.toUpperCase(), ",", 0))); |
| |
| // if user has unset sequence sql, null it out so we know sequences |
| // aren't supported |
| nextSequenceQuery = StringUtils.trimToNull(nextSequenceQuery); |
| |
| if (selectWords != null) |
| selectWordSet.addAll(Arrays.asList(Strings.split(selectWords |
| .toUpperCase(), ",", 0))); |
| |
| // initialize the error codes |
| SQLErrorCodeReader codeReader = new SQLErrorCodeReader(); |
| String rsrc = "sql-error-state-codes.xml"; |
| InputStream stream = getClass().getResourceAsStream(rsrc); |
| String dictionaryClassName = getClass().getName(); |
| if (stream == null) { // User supplied dictionary but no error codes xml |
| stream = DBDictionary.class.getResourceAsStream(rsrc); // use default |
| dictionaryClassName = getClass().getSuperclass().getName(); |
| } |
| codeReader.parse(stream, dictionaryClassName, this); |
| } |
| |
| public void addErrorCode(int errorType, String errorCode) { |
| if (errorCode == null || errorCode.trim().length() == 0) |
| return; |
| Set<String> codes = sqlStateCodes.get(errorType); |
| if (codes == null) { |
| codes = new HashSet<String>(); |
| codes.add(errorCode.trim()); |
| sqlStateCodes.put(errorType, codes); |
| } else { |
| codes.add(errorCode.trim()); |
| } |
| } |
| |
| ////////////////////////////////////// |
| // ConnectionDecorator implementation |
| ////////////////////////////////////// |
| |
| /** |
| * Decorate the given connection if needed. Some databases require special |
| * handling for JDBC bugs. This implementation issues any |
| * {@link #initializationSQL} that has been set for the dictionary but |
| * does not decorate the connection. |
| */ |
| public Connection decorate(Connection conn) |
| throws SQLException { |
| if (!connected) |
| connectedConfiguration(conn); |
| if (!StringUtils.isEmpty(initializationSQL)) { |
| PreparedStatement stmnt = null; |
| try { |
| stmnt = conn.prepareStatement(initializationSQL); |
| stmnt.execute(); |
| } catch (Exception e) { |
| if (log.isTraceEnabled()) |
| log.trace(e.toString(), e); |
| } finally { |
| if (stmnt != null) |
| try { |
| stmnt.close(); |
| } catch (SQLException se) { |
| } |
| } |
| } |
| return conn; |
| } |
| |
| /** |
| * Implementation of the |
| * {@link LoggingConnectionDecorator.SQLWarningHandler} interface |
| * that allows customization of the actions to perform when a |
| * {@link SQLWarning} occurs at any point on a {@link Connection}, |
| * {@link Statement}, or {@link ResultSet}. This method may |
| * be used determine those warnings the application wants to |
| * consider critical failures, and throw the warning in those |
| * cases. By default, this method does nothing. |
| * |
| * @see LoggingConnectionDecorator#setWarningAction |
| * @see LoggingConnectionDecorator#setWarningHandler |
| */ |
| public void handleWarning(SQLWarning warning) |
| throws SQLException { |
| } |
| |
| /** |
| * Return a new exception that wraps <code>causes</code>. |
| * However, the details of exactly what type of exception is returned can |
| * be determined by the implementation. This may take into account |
| * DB-specific exception information in <code>causes</code>. |
| */ |
| public OpenJPAException newStoreException(String msg, SQLException[] causes, |
| Object failed) { |
| if (causes != null && causes.length > 0) { |
| OpenJPAException ret = narrow(msg, causes[0]); |
| ret.setFailedObject(failed).setNestedThrowables(causes); |
| return ret; |
| } |
| return new StoreException(msg).setFailedObject(failed). |
| setNestedThrowables(causes); |
| } |
| |
| /** |
| * Gets the subtype of StoreException by matching the given SQLException's |
| * error state code to the list of error codes supplied by the dictionary. |
| * Returns -1 if no matching code can be found. |
| */ |
| OpenJPAException narrow(String msg, SQLException ex) { |
| String errorState = ex.getSQLState(); |
| int errorType = StoreException.GENERAL; |
| for (Integer type : sqlStateCodes.keySet()) { |
| Set<String> erroStates = sqlStateCodes.get(type); |
| if (erroStates != null && erroStates.contains(errorState)) { |
| errorType = type; |
| break; |
| } |
| } |
| switch (errorType) { |
| case StoreException.LOCK: |
| return new LockException(msg); |
| case StoreException.OBJECT_EXISTS: |
| return new ObjectExistsException(msg); |
| case StoreException.OBJECT_NOT_FOUND: |
| return new ObjectNotFoundException(msg); |
| case StoreException.OPTIMISTIC: |
| return new OptimisticException(msg); |
| case StoreException.REFERENTIAL_INTEGRITY: |
| return new ReferentialIntegrityException(msg); |
| default: |
| return new StoreException(msg); |
| } |
| } |
| |
| /** |
| * Closes the specified {@link DataSource} and releases any |
| * resources associated with it. |
| * |
| * @param dataSource the DataSource to close |
| */ |
| public void closeDataSource(DataSource dataSource) { |
| DataSourceFactory.closeDataSource(dataSource); |
| } |
| |
| /** |
| * Used by some mappings to represent data that has already been |
| * serialized so that we don't have to serialize multiple times. |
| */ |
| public static class SerializedData { |
| |
| public final byte[] bytes; |
| |
| public SerializedData(byte[] bytes) { |
| this.bytes = bytes; |
| } |
| } |
| |
| /** |
| * Return version column name |
| * @param column |
| * @param tableAlias : this is needed for platform specific version column |
| * @return |
| */ |
| public String getVersionColumn(Column column, String tableAlias) { |
| return column.toString(); |
| } |
| |
| public void insertBlobForStreamingLoad(Row row, Column col, |
| JDBCStore store, Object ob, Select sel) throws SQLException { |
| if (ob != null) { |
| row.setBinaryStream(col, |
| new ByteArrayInputStream(new byte[0]), 0); |
| } else { |
| row.setNull(col); |
| } |
| } |
| |
| public void insertClobForStreamingLoad(Row row, Column col, Object ob) |
| throws SQLException { |
| if (ob != null) { |
| row.setCharacterStream(col, |
| new CharArrayReader(new char[0]), 0); |
| } else { |
| row.setNull(col); |
| } |
| } |
| |
| public void updateBlob(Select sel, JDBCStore store, InputStream is) |
| throws SQLException { |
| SQLBuffer sql = sel.toSelect(true, store.getFetchConfiguration()); |
| ResultSet res = null; |
| Connection conn = store.getConnection(); |
| PreparedStatement stmnt = null; |
| try { |
| stmnt = sql.prepareStatement(conn, store.getFetchConfiguration(), |
| ResultSet.TYPE_SCROLL_SENSITIVE, |
| ResultSet.CONCUR_UPDATABLE); |
| res = stmnt.executeQuery(); |
| if (!res.next()) { |
| throw new InternalException(_loc.get("stream-exception")); |
| } |
| Blob blob = res.getBlob(1); |
| OutputStream os = blob.setBinaryStream(1); |
| copy(is, os); |
| os.close(); |
| res.updateBlob(1, blob); |
| res.updateRow(); |
| |
| } catch (IOException ioe) { |
| throw new StoreException(ioe); |
| } finally { |
| if (res != null) |
| try { res.close (); } catch (SQLException e) {} |
| if (stmnt != null) |
| try { stmnt.close (); } catch (SQLException e) {} |
| if (conn != null) |
| try { conn.close (); } catch (SQLException e) {} |
| } |
| } |
| |
| public void updateClob(Select sel, JDBCStore store, Reader reader) |
| throws SQLException { |
| SQLBuffer sql = sel.toSelect(true, store.getFetchConfiguration()); |
| ResultSet res = null; |
| Connection conn = store.getConnection(); |
| PreparedStatement stmnt = null; |
| try { |
| stmnt = sql.prepareStatement(conn, store.getFetchConfiguration(), |
| ResultSet.TYPE_SCROLL_SENSITIVE, |
| ResultSet.CONCUR_UPDATABLE); |
| res = stmnt.executeQuery(); |
| if (!res.next()) { |
| throw new InternalException(_loc.get("stream-exception")); |
| } |
| Clob clob = res.getClob(1); |
| Writer writer = clob.setCharacterStream(1); |
| copy(reader, writer); |
| writer.close(); |
| res.updateClob(1, clob); |
| res.updateRow(); |
| |
| } catch (IOException ioe) { |
| throw new StoreException(ioe); |
| } finally { |
| if (res != null) |
| try { res.close (); } catch (SQLException e) {} |
| if (stmnt != null) |
| try { stmnt.close (); } catch (SQLException e) {} |
| if (conn != null) |
| try { conn.close (); } catch (SQLException e) {} |
| } |
| } |
| |
| protected long copy(InputStream in, OutputStream out) throws IOException { |
| byte[] copyBuffer = new byte[blobBufferSize]; |
| long bytesCopied = 0; |
| int read = -1; |
| |
| while ((read = in.read(copyBuffer, 0, copyBuffer.length)) != -1) { |
| out.write(copyBuffer, 0, read); |
| bytesCopied += read; |
| } |
| return bytesCopied; |
| } |
| |
| protected long copy(Reader reader, Writer writer) throws IOException { |
| char[] copyBuffer = new char[clobBufferSize]; |
| long bytesCopied = 0; |
| int read = -1; |
| |
| while ((read = reader.read(copyBuffer, 0, copyBuffer.length)) != -1) { |
| writer.write(copyBuffer, 0, read); |
| bytesCopied += read; |
| } |
| |
| return bytesCopied; |
| } |
| |
| /** |
| * Attach CAST to the current function if necessary |
| * |
| * @param val operand value |
| * @parma func the sql function statement |
| * @return a String with the correct CAST function syntax |
| */ |
| public String getCastFunction(Val val, String func) { |
| return func; |
| } |
| |
| /** |
| * Return the correct CAST function syntax. This should be overriden by subclasses |
| * that need access to the Column information. |
| * |
| * @param val operand of cast |
| * @param func original string |
| * @param col database column |
| * @return a String with the correct CAST function syntax |
| */ |
| public String getCastFunction(Val val, String func, Column col) { |
| return getCastFunction (val, func); |
| } |
| |
| /** |
| * Create an index if necessary for some database tables |
| */ |
| public void createIndexIfNecessary(Schema schema, String table, |
| Column pkColumn) { |
| } |
| |
| /** |
| * Return the batchLimit |
| */ |
| public int getBatchLimit(){ |
| return batchLimit; |
| } |
| |
| /** |
| * Set the batchLimit value |
| */ |
| public void setBatchLimit(int limit){ |
| batchLimit = limit; |
| } |
| |
| /** |
| * Validate the batch process. In some cases, we can't batch the statements |
| * due to some restrictions. For example, if the GeneratedType=IDENTITY, |
| * we have to disable the batch process because we need to get the ID value |
| * right away for the in-memory entity to use. |
| */ |
| public boolean validateBatchProcess(RowImpl row, Column[] autoAssign, |
| OpenJPAStateManager sm, ClassMapping cmd ) { |
| boolean disableBatch = false; |
| if (getBatchLimit()== 0) return false; |
| if (autoAssign != null && sm != null) { |
| FieldMetaData[] fmd = cmd.getPrimaryKeyFields(); |
| int i = 0; |
| while (!disableBatch && i < fmd.length) { |
| if (fmd[i].getValueStrategy() == ValueStrategies.AUTOASSIGN) |
| disableBatch = true; |
| i++; |
| } |
| } |
| // go to each Dictionary to validate the batch capability |
| if (!disableBatch) |
| disableBatch = validateDBSpecificBatchProcess(disableBatch, row, |
| autoAssign, sm, cmd); |
| return disableBatch; |
| } |
| |
| /** |
| * Allow each Dictionary to validate its own batch process. |
| */ |
| public boolean validateDBSpecificBatchProcess (boolean disableBatch, |
| RowImpl row, Column[] autoAssign, |
| OpenJPAStateManager sm, ClassMapping cmd ) { |
| return disableBatch; |
| } |
| |
| /** |
| * This method is to provide override for non-JDBC or JDBC-like |
| * implementation of executing query. |
| */ |
| protected ResultSet executeQuery(Connection conn, PreparedStatement stmnt, String sql |
| ) throws SQLException { |
| return stmnt.executeQuery(); |
| } |
| |
| /** |
| * This method is to provide override for non-JDBC or JDBC-like |
| * implementation of preparing statement. |
| */ |
| protected PreparedStatement prepareStatement(Connection conn, String sql) |
| throws SQLException { |
| return conn.prepareStatement(sql); |
| } |
| |
| /** |
| * This method is to provide override for non-JDBC or JDBC-like |
| * implementation of getting sequence from the result set. |
| */ |
| protected Sequence[] getSequence(ResultSet rs) throws SQLException { |
| List seqList = new ArrayList(); |
| while (rs != null && rs.next()) |
| seqList.add(newSequence(rs)); |
| return (Sequence[]) seqList.toArray(new Sequence[seqList.size()]); |
| } |
| |
| /** |
| * This method is to provide override for non-JDBC or JDBC-like |
| * implementation of getting key from the result set. |
| */ |
| protected Object getKey (ResultSet rs, Column col) throws SQLException { |
| if (!rs.next()) |
| throw new StoreException(_loc.get("no-genkey")); |
| Object key = rs.getObject(1); |
| if (key == null) |
| log.warn(_loc.get("invalid-genkey", col)); |
| return key; |
| } |
| |
| /** |
| * This method is to provide override for non-JDBC or JDBC-like |
| * implementation of calculating value. |
| */ |
| protected void calculateValue(Val val, Select sel, ExpContext ctx, |
| ExpState state, Path path, ExpState pathState) { |
| val.calculateValue(sel, ctx, state, (Val) path, pathState); |
| } |
| |
| /** |
| * Determine whether the provided <code>sql</code> may be treated as a |
| * select statement on this database. |
| * |
| * @param sql A sql statement. |
| * |
| * @return true if <code>sql</code> represents a select statement. |
| */ |
| public boolean isSelect(String sql) { |
| Iterator i = selectWordSet.iterator(); |
| String cur; |
| while (i.hasNext()) { |
| cur = (String) i.next(); |
| if (sql.length() >= cur.length() |
| && sql.substring(0, cur.length()).equalsIgnoreCase(cur)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| public boolean needsToCreateIndex(Index idx, Table table) { |
| return true; |
| } |
| |
| /** |
| * Return batched statements update succes count |
| * @param ps A PreparedStatement |
| * @return return update count |
| */ |
| public int getBatchUpdateCount(PreparedStatement ps) throws SQLException { |
| return 0; |
| } |
| |
| public boolean getTrimSchemaName() { |
| return trimSchemaName; |
| } |
| |
| public void setTrimSchemaName(boolean trimSchemaName) { |
| this.trimSchemaName = trimSchemaName; |
| } |
| |
| public void deleteStream(JDBCStore store, Select sel) throws SQLException { |
| // Do nothing |
| } |
| } |