0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one
0003: * or more contributor license agreements. See the NOTICE file
0004: * distributed with this work for additional information
0005: * regarding copyright ownership. The ASF licenses this file
0006: * to you under the Apache License, Version 2.0 (the
0007: * "License"); you may not use this file except in compliance
0008: * with the License. You may obtain a copy of the License at
0009: *
0010: * http://www.apache.org/licenses/LICENSE-2.0
0011: *
0012: * Unless required by applicable law or agreed to in writing,
0013: * software distributed under the License is distributed on an
0014: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
0015: * KIND, either express or implied. See the License for the
0016: * specific language governing permissions and limitations
0017: * under the License.
0018: */
0019: package org.apache.openjpa.jdbc.sql;
0020:
0021: import java.io.BufferedReader;
0022: import java.io.ByteArrayInputStream;
0023: import java.io.CharArrayReader;
0024: import java.io.IOException;
0025: import java.io.InputStream;
0026: import java.io.InputStreamReader;
0027: import java.io.OutputStream;
0028: import java.io.Reader;
0029: import java.io.StringReader;
0030: import java.io.Writer;
0031: import java.lang.reflect.InvocationTargetException;
0032: import java.lang.reflect.Method;
0033: import java.math.BigDecimal;
0034: import java.math.BigInteger;
0035: import java.sql.Array;
0036: import java.sql.Blob;
0037: import java.sql.Clob;
0038: import java.sql.Connection;
0039: import java.sql.DatabaseMetaData;
0040: import java.sql.PreparedStatement;
0041: import java.sql.Ref;
0042: import java.sql.ResultSet;
0043: import java.sql.SQLException;
0044: import java.sql.SQLWarning;
0045: import java.sql.Statement;
0046: import java.sql.Time;
0047: import java.sql.Timestamp;
0048: import java.sql.Types;
0049: import java.text.MessageFormat;
0050: import java.util.ArrayList;
0051: import java.util.Arrays;
0052: import java.util.Calendar;
0053: import java.util.Collection;
0054: import java.util.Collections;
0055: import java.util.Date;
0056: import java.util.HashSet;
0057: import java.util.Iterator;
0058: import java.util.LinkedHashSet;
0059: import java.util.List;
0060: import java.util.Locale;
0061: import java.util.Map;
0062: import java.util.Set;
0063: import javax.sql.DataSource;
0064:
0065: import org.apache.commons.lang.StringUtils;
0066: import org.apache.openjpa.jdbc.conf.JDBCConfiguration;
0067: import org.apache.openjpa.jdbc.kernel.JDBCFetchConfiguration;
0068: import org.apache.openjpa.jdbc.kernel.JDBCStore;
0069: import org.apache.openjpa.jdbc.kernel.exps.ExpContext;
0070: import org.apache.openjpa.jdbc.kernel.exps.ExpState;
0071: import org.apache.openjpa.jdbc.kernel.exps.FilterValue;
0072: import org.apache.openjpa.jdbc.kernel.exps.Val;
0073: import org.apache.openjpa.jdbc.meta.ClassMapping;
0074: import org.apache.openjpa.jdbc.meta.FieldMapping;
0075: import org.apache.openjpa.jdbc.meta.JavaSQLTypes;
0076: import org.apache.openjpa.jdbc.schema.Column;
0077: import org.apache.openjpa.jdbc.schema.DataSourceFactory;
0078: import org.apache.openjpa.jdbc.schema.ForeignKey;
0079: import org.apache.openjpa.jdbc.schema.Index;
0080: import org.apache.openjpa.jdbc.schema.NameSet;
0081: import org.apache.openjpa.jdbc.schema.PrimaryKey;
0082: import org.apache.openjpa.jdbc.schema.Schema;
0083: import org.apache.openjpa.jdbc.schema.SchemaGroup;
0084: import org.apache.openjpa.jdbc.schema.Sequence;
0085: import org.apache.openjpa.jdbc.schema.Table;
0086: import org.apache.openjpa.jdbc.schema.Unique;
0087: import org.apache.openjpa.kernel.Filters;
0088: import org.apache.openjpa.kernel.OpenJPAStateManager;
0089: import org.apache.openjpa.kernel.exps.Path;
0090: import org.apache.openjpa.lib.conf.Configurable;
0091: import org.apache.openjpa.lib.conf.Configuration;
0092: import org.apache.openjpa.lib.jdbc.ConnectionDecorator;
0093: import org.apache.openjpa.lib.jdbc.LoggingConnectionDecorator;
0094: import org.apache.openjpa.lib.log.Log;
0095: import org.apache.openjpa.lib.util.Localizer;
0096: import org.apache.openjpa.lib.util.Localizer.Message;
0097: import org.apache.openjpa.meta.FieldMetaData;
0098: import org.apache.openjpa.meta.JavaTypes;
0099: import org.apache.openjpa.meta.ValueStrategies;
0100: import org.apache.openjpa.util.GeneralException;
0101: import org.apache.openjpa.util.InternalException;
0102: import org.apache.openjpa.util.InvalidStateException;
0103: import org.apache.openjpa.util.OpenJPAException;
0104: import org.apache.openjpa.util.Serialization;
0105: import org.apache.openjpa.util.StoreException;
0106: import org.apache.openjpa.util.UnsupportedException;
0107: import org.apache.openjpa.util.UserException;
0108: import serp.util.Numbers;
0109: import serp.util.Strings;
0110:
0111: /**
0112: * Class which allows the creation of SQL dynamically, in a
0113: * database agnostic fashion. Subclass for the nuances of different data stores.
0114: */
0115: public class DBDictionary implements Configurable, ConnectionDecorator,
0116: JoinSyntaxes, LoggingConnectionDecorator.SQLWarningHandler {
0117:
0118: public static final String VENDOR_OTHER = "other";
0119: public static final String VENDOR_DATADIRECT = "datadirect";
0120:
0121: public static final String SCHEMA_CASE_UPPER = "upper";
0122: public static final String SCHEMA_CASE_LOWER = "lower";
0123: public static final String SCHEMA_CASE_PRESERVE = "preserve";
0124:
0125: public static final String CONS_NAME_BEFORE = "before";
0126: public static final String CONS_NAME_MID = "mid";
0127: public static final String CONS_NAME_AFTER = "after";
0128:
0129: public int blobBufferSize = 50;
0130: public int clobBufferSize = 50;
0131:
0132: protected static final int RANGE_POST_SELECT = 0;
0133: protected static final int RANGE_PRE_DISTINCT = 1;
0134: protected static final int RANGE_POST_DISTINCT = 2;
0135: protected static final int RANGE_POST_LOCK = 3;
0136:
0137: protected static final int NANO = 1;
0138: protected static final int MICRO = NANO * 1000;
0139: protected static final int MILLI = MICRO * 1000;
0140: protected static final int CENTI = MILLI * 10;
0141: protected static final int DECI = MILLI * 100;
0142: protected static final int SEC = MILLI * 1000;
0143:
0144: protected static final int NAME_ANY = 0;
0145: protected static final int NAME_TABLE = 1;
0146: protected static final int NAME_SEQUENCE = 2;
0147:
0148: protected static final int UNLIMITED = -1;
0149: protected static final int NO_BATCH = 0;
0150:
0151: private static final String ZERO_DATE_STR = "'"
0152: + new java.sql.Date(0) + "'";
0153: private static final String ZERO_TIME_STR = "'" + new Time(0) + "'";
0154: private static final String ZERO_TIMESTAMP_STR = "'"
0155: + new Timestamp(0) + "'";
0156:
0157: public static final List EMPTY_STRING_LIST = Arrays
0158: .asList(new String[] {});
0159: public static final List[] SQL_STATE_CODES = { EMPTY_STRING_LIST, // 0: Default
0160: Arrays.asList(new String[] { "41000" }), // 1: LOCK
0161: EMPTY_STRING_LIST, // 2: OBJECT_NOT_FOUND
0162: EMPTY_STRING_LIST, // 3: OPTIMISTIC
0163: Arrays.asList(new String[] { "23000" }), // 4: REFERENTIAL_INTEGRITY
0164: EMPTY_STRING_LIST // 5: OBJECT_EXISTS
0165: };
0166:
0167: private static final Localizer _loc = Localizer
0168: .forPackage(DBDictionary.class);
0169:
0170: // schema data
0171: public String platform = "Generic";
0172: public String driverVendor = null;
0173: public String catalogSeparator = ".";
0174: public boolean createPrimaryKeys = true;
0175: public String constraintNameMode = CONS_NAME_BEFORE;
0176: public int maxTableNameLength = 128;
0177: public int maxColumnNameLength = 128;
0178: public int maxConstraintNameLength = 128;
0179: public int maxIndexNameLength = 128;
0180: public int maxIndexesPerTable = Integer.MAX_VALUE;
0181: public boolean supportsForeignKeys = true;
0182: public boolean supportsTimestampNanos = true;
0183: public boolean supportsUniqueConstraints = true;
0184: public boolean supportsDeferredConstraints = true;
0185: public boolean supportsRestrictDeleteAction = true;
0186: public boolean supportsCascadeDeleteAction = true;
0187: public boolean supportsNullDeleteAction = true;
0188: public boolean supportsDefaultDeleteAction = true;
0189: public boolean supportsRestrictUpdateAction = true;
0190: public boolean supportsCascadeUpdateAction = true;
0191: public boolean supportsNullUpdateAction = true;
0192: public boolean supportsDefaultUpdateAction = true;
0193: public boolean supportsAlterTableWithAddColumn = true;
0194: public boolean supportsAlterTableWithDropColumn = true;
0195: public boolean supportsComments = false;
0196: public String reservedWords = null;
0197: public String systemSchemas = null;
0198: public String systemTables = null;
0199: public String selectWords = null;
0200: public String fixedSizeTypeNames = null;
0201: public String schemaCase = SCHEMA_CASE_UPPER;
0202:
0203: // sql
0204: public String validationSQL = null;
0205: public String closePoolSQL = null;
0206: public String initializationSQL = null;
0207: public int joinSyntax = SYNTAX_SQL92;
0208: public String outerJoinClause = "LEFT OUTER JOIN";
0209: public String innerJoinClause = "INNER JOIN";
0210: public String crossJoinClause = "CROSS JOIN";
0211: public boolean requiresConditionForCrossJoin = false;
0212: public String forUpdateClause = "FOR UPDATE";
0213: public String tableForUpdateClause = null;
0214: public String distinctCountColumnSeparator = null;
0215: public boolean supportsSelectForUpdate = true;
0216: public boolean supportsLockingWithDistinctClause = true;
0217: public boolean supportsLockingWithMultipleTables = true;
0218: public boolean supportsLockingWithOrderClause = true;
0219: public boolean supportsLockingWithOuterJoin = true;
0220: public boolean supportsLockingWithInnerJoin = true;
0221: public boolean supportsLockingWithSelectRange = true;
0222: public boolean supportsQueryTimeout = true;
0223: public boolean simulateLocking = false;
0224: public boolean supportsSubselect = true;
0225: public boolean supportsCorrelatedSubselect = true;
0226: public boolean supportsHaving = true;
0227: public boolean supportsSelectStartIndex = false;
0228: public boolean supportsSelectEndIndex = false;
0229: public int rangePosition = RANGE_POST_SELECT;
0230: public boolean requiresAliasForSubselect = false;
0231: public boolean allowsAliasInBulkClause = true;
0232: public boolean supportsMultipleNontransactionalResultSets = true;
0233: public String searchStringEscape = "\\";
0234: public boolean requiresCastForMathFunctions = false;
0235: public boolean requiresCastForComparisons = false;
0236: public boolean supportsModOperator = false;
0237: public boolean supportsXMLColumn = false;
0238:
0239: // functions
0240: public String castFunction = "CAST({0} AS {1})";
0241: public String toLowerCaseFunction = "LOWER({0})";
0242: public String toUpperCaseFunction = "UPPER({0})";
0243: public String stringLengthFunction = "CHAR_LENGTH({0})";
0244: public String bitLengthFunction = "(OCTET_LENGTH({0}) * 8)";
0245: public String trimLeadingFunction = "TRIM(LEADING {1} FROM {0})";
0246: public String trimTrailingFunction = "TRIM(TRAILING {1} FROM {0})";
0247: public String trimBothFunction = "TRIM(BOTH {1} FROM {0})";
0248: public String concatenateFunction = "({0}||{1})";
0249: public String concatenateDelimiter = "'OPENJPATOKEN'";
0250: public String substringFunctionName = "SUBSTRING";
0251: public String currentDateFunction = "CURRENT_DATE";
0252: public String currentTimeFunction = "CURRENT_TIME";
0253: public String currentTimestampFunction = "CURRENT_TIMESTAMP";
0254: public String dropTableSQL = "DROP TABLE {0}";
0255:
0256: // types
0257: public boolean storageLimitationsFatal = false;
0258: public boolean storeLargeNumbersAsStrings = false;
0259: public boolean storeCharsAsNumbers = true;
0260: public boolean useGetBytesForBlobs = false;
0261: public boolean useSetBytesForBlobs = false;
0262: public boolean useGetObjectForBlobs = false;
0263: public boolean useGetStringForClobs = false;
0264: public boolean useSetStringForClobs = false;
0265: public int maxEmbeddedBlobSize = -1;
0266: public int maxEmbeddedClobSize = -1;
0267: public int inClauseLimit = -1;
0268: public int datePrecision = MILLI;
0269: public int characterColumnSize = 255;
0270: public String arrayTypeName = "ARRAY";
0271: public String bigintTypeName = "BIGINT";
0272: public String binaryTypeName = "BINARY";
0273: public String bitTypeName = "BIT";
0274: public String blobTypeName = "BLOB";
0275: public String booleanTypeName = "BOOLEAN";
0276: public String charTypeName = "CHAR";
0277: public String clobTypeName = "CLOB";
0278: public String dateTypeName = "DATE";
0279: public String decimalTypeName = "DECIMAL";
0280: public String distinctTypeName = "DISTINCT";
0281: public String doubleTypeName = "DOUBLE";
0282: public String floatTypeName = "FLOAT";
0283: public String integerTypeName = "INTEGER";
0284: public String javaObjectTypeName = "JAVA_OBJECT";
0285: public String longVarbinaryTypeName = "LONGVARBINARY";
0286: public String longVarcharTypeName = "LONGVARCHAR";
0287: public String nullTypeName = "NULL";
0288: public String numericTypeName = "NUMERIC";
0289: public String otherTypeName = "OTHER";
0290: public String realTypeName = "REAL";
0291: public String refTypeName = "REF";
0292: public String smallintTypeName = "SMALLINT";
0293: public String structTypeName = "STRUCT";
0294: public String timeTypeName = "TIME";
0295: public String timestampTypeName = "TIMESTAMP";
0296: public String tinyintTypeName = "TINYINT";
0297: public String varbinaryTypeName = "VARBINARY";
0298: public String varcharTypeName = "VARCHAR";
0299: public String xmlTypeName = "XML";
0300: public String getStringVal = "";
0301:
0302: // schema metadata
0303: public boolean useSchemaName = true;
0304: public String tableTypes = "TABLE";
0305: public boolean supportsSchemaForGetTables = true;
0306: public boolean supportsSchemaForGetColumns = true;
0307: public boolean supportsNullTableForGetColumns = true;
0308: public boolean supportsNullTableForGetPrimaryKeys = false;
0309: public boolean supportsNullTableForGetIndexInfo = false;
0310: public boolean supportsNullTableForGetImportedKeys = false;
0311: public boolean useGetBestRowIdentifierForPrimaryKeys = false;
0312: public boolean requiresAutoCommitForMetaData = false;
0313:
0314: // auto-increment
0315: public int maxAutoAssignNameLength = 31;
0316: public String autoAssignClause = null;
0317: public String autoAssignTypeName = null;
0318: public boolean supportsAutoAssign = false;
0319: public String lastGeneratedKeyQuery = null;
0320: public String nextSequenceQuery = null;
0321: public String sequenceSQL = null;
0322: public String sequenceSchemaSQL = null;
0323: public String sequenceNameSQL = null;
0324:
0325: protected JDBCConfiguration conf = null;
0326: protected Log log = null;
0327: protected boolean connected = false;
0328: protected final Set reservedWordSet = new HashSet();
0329: protected final Set systemSchemaSet = new HashSet();
0330: protected final Set systemTableSet = new HashSet();
0331: protected final Set fixedSizeTypeNameSet = new HashSet();
0332: protected final Set typeModifierSet = new HashSet();
0333:
0334: /**
0335: * If a native query begins with any of the values found here then it will
0336: * be treated as a select statement.
0337: */
0338: protected final Set selectWordSet = new HashSet();
0339:
0340: // when we store values that lose precion, track the types so that the
0341: // first time it happens we can warn the user
0342: private Set _precisionWarnedTypes = null;
0343:
0344: // cache lob methods
0345: private Method _setBytes = null;
0346: private Method _setString = null;
0347: private Method _setCharStream = null;
0348:
0349: // batchLimit value:
0350: // -1 = unlimited
0351: // 0 = no batch
0352: // any positive number = batch limit
0353: public int batchLimit = NO_BATCH;
0354:
0355: public DBDictionary() {
0356: fixedSizeTypeNameSet.addAll(Arrays.asList(new String[] {
0357: "BIGINT", "BIT", "BLOB", "CLOB", "DATE", "DECIMAL",
0358: "DISTINCT", "DOUBLE", "FLOAT", "INTEGER",
0359: "JAVA_OBJECT", "NULL", "NUMERIC", "OTHER", "REAL",
0360: "REF", "SMALLINT", "STRUCT", "TIME", "TIMESTAMP",
0361: "TINYINT", }));
0362:
0363: selectWordSet.add("SELECT");
0364: }
0365:
0366: /**
0367: * This method is called when the dictionary first sees any connection.
0368: * It is used to initialize dictionary metadata if needed. If you
0369: * override this method, be sure to call
0370: * <code>super.connectedConfiguration</code>.
0371: */
0372: public void connectedConfiguration(Connection conn)
0373: throws SQLException {
0374: if (!connected) {
0375: try {
0376: if (log.isTraceEnabled())
0377: log.trace(DBDictionaryFactory.toString(conn
0378: .getMetaData()));
0379: } catch (Exception e) {
0380: log.trace(e.toString(), e);
0381: }
0382: }
0383: connected = true;
0384: }
0385:
0386: //////////////////////
0387: // ResultSet wrappers
0388: //////////////////////
0389:
0390: /**
0391: * Convert the specified column of the SQL ResultSet to the proper
0392: * java type.
0393: */
0394: public Array getArray(ResultSet rs, int column) throws SQLException {
0395: return rs.getArray(column);
0396: }
0397:
0398: /**
0399: * Convert the specified column of the SQL ResultSet to the proper
0400: * java type.
0401: */
0402: public InputStream getAsciiStream(ResultSet rs, int column)
0403: throws SQLException {
0404: return rs.getAsciiStream(column);
0405: }
0406:
0407: /**
0408: * Convert the specified column of the SQL ResultSet to the proper
0409: * java type.
0410: */
0411: public BigDecimal getBigDecimal(ResultSet rs, int column)
0412: throws SQLException {
0413: if (storeLargeNumbersAsStrings) {
0414: String str = getString(rs, column);
0415: return (str == null) ? null : new BigDecimal(str);
0416: }
0417: return rs.getBigDecimal(column);
0418: }
0419:
0420: /**
0421: * Returns the specified column value as an unknown numeric type;
0422: * we try from the most generic to the least generic.
0423: */
0424: public Number getNumber(ResultSet rs, int column)
0425: throws SQLException {
0426: // try from the most generic, and if errors occur, try
0427: // less generic types; this enables us to handle values
0428: // like Double.NaN without having to introspect on the
0429: // ResultSetMetaData (bug #1053). note that we handle
0430: // generic exceptions, since some drivers may throw
0431: // NumberFormatExceptions, whereas others may throw SQLExceptions
0432: try {
0433: return getBigDecimal(rs, column);
0434: } catch (Exception e1) {
0435: try {
0436: return new Double(getDouble(rs, column));
0437: } catch (Exception e2) {
0438: try {
0439: return new Float(getFloat(rs, column));
0440: } catch (Exception e3) {
0441: try {
0442: return Numbers.valueOf(getLong(rs, column));
0443: } catch (Exception e4) {
0444: try {
0445: return Numbers.valueOf(getInt(rs, column));
0446: } catch (Exception e5) {
0447: }
0448: }
0449: }
0450: }
0451:
0452: if (e1 instanceof RuntimeException)
0453: throw (RuntimeException) e1;
0454: if (e1 instanceof SQLException)
0455: throw (SQLException) e1;
0456: }
0457:
0458: return null;
0459: }
0460:
0461: /**
0462: * Convert the specified column of the SQL ResultSet to the proper
0463: * java type.
0464: */
0465: public BigInteger getBigInteger(ResultSet rs, int column)
0466: throws SQLException {
0467: if (storeLargeNumbersAsStrings) {
0468: String str = getString(rs, column);
0469: return (str == null) ? null : new BigDecimal(str)
0470: .toBigInteger();
0471: }
0472: BigDecimal bd = getBigDecimal(rs, column);
0473: return (bd == null) ? null : bd.toBigInteger();
0474: }
0475:
0476: /**
0477: * Convert the specified column of the SQL ResultSet to the proper
0478: * java type.
0479: */
0480: public InputStream getBinaryStream(ResultSet rs, int column)
0481: throws SQLException {
0482: return rs.getBinaryStream(column);
0483: }
0484:
0485: /**
0486: * Convert the specified column of the SQL ResultSet to the proper
0487: * java type.
0488: */
0489: public Blob getBlob(ResultSet rs, int column) throws SQLException {
0490: return rs.getBlob(column);
0491: }
0492:
0493: /**
0494: * Convert the specified column of the SQL ResultSet to the proper
0495: * java type.
0496: */
0497: public Object getBlobObject(ResultSet rs, int column,
0498: JDBCStore store) throws SQLException {
0499: InputStream in = null;
0500: if (useGetBytesForBlobs || useGetObjectForBlobs) {
0501: byte[] bytes = getBytes(rs, column);
0502: if (bytes != null && bytes.length > 0)
0503: in = new ByteArrayInputStream(bytes);
0504: } else {
0505: Blob blob = getBlob(rs, column);
0506: if (blob != null && blob.length() > 0)
0507: in = blob.getBinaryStream();
0508: }
0509: if (in == null)
0510: return null;
0511:
0512: try {
0513: if (store == null)
0514: return Serialization.deserialize(in, null);
0515: return Serialization.deserialize(in, store.getContext());
0516: } finally {
0517: try {
0518: in.close();
0519: } catch (IOException ioe) {
0520: }
0521: }
0522: }
0523:
0524: /**
0525: * Convert the specified column of the SQL ResultSet to the proper
0526: * java type.
0527: */
0528: public boolean getBoolean(ResultSet rs, int column)
0529: throws SQLException {
0530: return rs.getBoolean(column);
0531: }
0532:
0533: /**
0534: * Convert the specified column of the SQL ResultSet to the proper
0535: * java type.
0536: */
0537: public byte getByte(ResultSet rs, int column) throws SQLException {
0538: return rs.getByte(column);
0539: }
0540:
0541: /**
0542: * Convert the specified column of the SQL ResultSet to the proper
0543: * java type.
0544: */
0545: public byte[] getBytes(ResultSet rs, int column)
0546: throws SQLException {
0547: if (useGetBytesForBlobs)
0548: return rs.getBytes(column);
0549: if (useGetObjectForBlobs)
0550: return (byte[]) rs.getObject(column);
0551:
0552: Blob blob = getBlob(rs, column);
0553: if (blob == null)
0554: return null;
0555: int length = (int) blob.length();
0556: if (length == 0)
0557: return null;
0558: return blob.getBytes(1, length);
0559: }
0560:
0561: /**
0562: * Convert the specified column of the SQL ResultSet to the proper
0563: * java type. Converts the date from a {@link Timestamp} by default.
0564: */
0565: public Calendar getCalendar(ResultSet rs, int column)
0566: throws SQLException {
0567: Date d = getDate(rs, column);
0568: if (d == null)
0569: return null;
0570:
0571: Calendar cal = Calendar.getInstance();
0572: cal.setTime(d);
0573: return cal;
0574: }
0575:
0576: /**
0577: * Convert the specified column of the SQL ResultSet to the proper
0578: * java type.
0579: */
0580: public char getChar(ResultSet rs, int column) throws SQLException {
0581: if (storeCharsAsNumbers)
0582: return (char) getInt(rs, column);
0583:
0584: String str = getString(rs, column);
0585: return (StringUtils.isEmpty(str)) ? 0 : str.charAt(0);
0586: }
0587:
0588: /**
0589: * Convert the specified column of the SQL ResultSet to the proper
0590: * java type.
0591: */
0592: public Reader getCharacterStream(ResultSet rs, int column)
0593: throws SQLException {
0594: return rs.getCharacterStream(column);
0595: }
0596:
0597: /**
0598: * Convert the specified column of the SQL ResultSet to the proper
0599: * java type.
0600: */
0601: public Clob getClob(ResultSet rs, int column) throws SQLException {
0602: return rs.getClob(column);
0603: }
0604:
0605: /**
0606: * Convert the specified column of the SQL ResultSet to the proper
0607: * java type.
0608: */
0609: public String getClobString(ResultSet rs, int column)
0610: throws SQLException {
0611: if (useGetStringForClobs)
0612: return rs.getString(column);
0613:
0614: Clob clob = getClob(rs, column);
0615: if (clob == null)
0616: return null;
0617: if (clob.length() == 0)
0618: return "";
0619:
0620: // unlikely that we'll have strings over Integer.MAX_VALUE chars
0621: return clob.getSubString(1, (int) clob.length());
0622: }
0623:
0624: /**
0625: * Convert the specified column of the SQL ResultSet to the proper
0626: * java type. Converts the date from a {@link Timestamp} by default.
0627: */
0628: public Date getDate(ResultSet rs, int column) throws SQLException {
0629: Timestamp tstamp = getTimestamp(rs, column, null);
0630: if (tstamp == null)
0631: return null;
0632:
0633: // get the fractional seconds component, rounding away anything beyond
0634: // milliseconds
0635: int fractional = (int) Math.round(tstamp.getNanos()
0636: / (double) MILLI);
0637:
0638: // get the millis component; some JDBC drivers round this to the
0639: // nearest second, while others do not
0640: long millis = (tstamp.getTime() / 1000L) * 1000L;
0641: return new Date(millis + fractional);
0642: }
0643:
0644: /**
0645: * Convert the specified column of the SQL ResultSet to the proper
0646: * java type.
0647: */
0648: public java.sql.Date getDate(ResultSet rs, int column, Calendar cal)
0649: throws SQLException {
0650: if (cal == null)
0651: return rs.getDate(column);
0652: return rs.getDate(column, cal);
0653: }
0654:
0655: /**
0656: * Convert the specified column of the SQL ResultSet to the proper
0657: * java type.
0658: */
0659: public double getDouble(ResultSet rs, int column)
0660: throws SQLException {
0661: return rs.getDouble(column);
0662: }
0663:
0664: /**
0665: * Convert the specified column of the SQL ResultSet to the proper
0666: * java type.
0667: */
0668: public float getFloat(ResultSet rs, int column) throws SQLException {
0669: return rs.getFloat(column);
0670: }
0671:
0672: /**
0673: * Convert the specified column of the SQL ResultSet to the proper
0674: * java type.
0675: */
0676: public int getInt(ResultSet rs, int column) throws SQLException {
0677: return rs.getInt(column);
0678: }
0679:
0680: /**
0681: * Convert the specified column of the SQL ResultSet to the proper
0682: * java type.
0683: */
0684: public Locale getLocale(ResultSet rs, int column)
0685: throws SQLException {
0686: String str = getString(rs, column);
0687: if (StringUtils.isEmpty(str))
0688: return null;
0689:
0690: String[] params = Strings.split(str, "_", 3);
0691: if (params.length < 3)
0692: return null;
0693: return new Locale(params[0], params[1], params[2]);
0694: }
0695:
0696: /**
0697: * Convert the specified column of the SQL ResultSet to the proper
0698: * java type.
0699: */
0700: public long getLong(ResultSet rs, int column) throws SQLException {
0701: return rs.getLong(column);
0702: }
0703:
0704: /**
0705: * Convert the specified column of the SQL ResultSet to the proper
0706: * java type.
0707: */
0708: public Object getObject(ResultSet rs, int column, Map map)
0709: throws SQLException {
0710: if (map == null)
0711: return rs.getObject(column);
0712: return rs.getObject(column, map);
0713: }
0714:
0715: /**
0716: * Convert the specified column of the SQL ResultSet to the proper
0717: * java type.
0718: */
0719: public Ref getRef(ResultSet rs, int column, Map map)
0720: throws SQLException {
0721: return rs.getRef(column);
0722: }
0723:
0724: /**
0725: * Convert the specified column of the SQL ResultSet to the proper
0726: * java type.
0727: */
0728: public short getShort(ResultSet rs, int column) throws SQLException {
0729: return rs.getShort(column);
0730: }
0731:
0732: /**
0733: * Convert the specified column of the SQL ResultSet to the proper
0734: * java type.
0735: */
0736: public String getString(ResultSet rs, int column)
0737: throws SQLException {
0738: return rs.getString(column);
0739: }
0740:
0741: /**
0742: * Convert the specified column of the SQL ResultSet to the proper
0743: * java type.
0744: */
0745: public Time getTime(ResultSet rs, int column, Calendar cal)
0746: throws SQLException {
0747: if (cal == null)
0748: return rs.getTime(column);
0749: return rs.getTime(column, cal);
0750: }
0751:
0752: /**
0753: * Convert the specified column of the SQL ResultSet to the proper
0754: * java type.
0755: */
0756: public Timestamp getTimestamp(ResultSet rs, int column, Calendar cal)
0757: throws SQLException {
0758: if (cal == null)
0759: return rs.getTimestamp(column);
0760: return rs.getTimestamp(column, cal);
0761: }
0762:
0763: //////////////////////////////
0764: // PreparedStatement wrappers
0765: //////////////////////////////
0766:
0767: /**
0768: * Set the given value as a parameter to the statement.
0769: */
0770: public void setArray(PreparedStatement stmnt, int idx, Array val,
0771: Column col) throws SQLException {
0772: stmnt.setArray(idx, val);
0773: }
0774:
0775: /**
0776: * Set the given value as a parameter to the statement.
0777: */
0778: public void setAsciiStream(PreparedStatement stmnt, int idx,
0779: InputStream val, int length, Column col)
0780: throws SQLException {
0781: stmnt.setAsciiStream(idx, val, length);
0782: }
0783:
0784: /**
0785: * Set the given value as a parameter to the statement.
0786: */
0787: public void setBigDecimal(PreparedStatement stmnt, int idx,
0788: BigDecimal val, Column col) throws SQLException {
0789: if ((col != null && col.isCompatible(Types.VARCHAR, null, 0, 0))
0790: || (col == null && storeLargeNumbersAsStrings))
0791: setString(stmnt, idx, val.toString(), col);
0792: else
0793: stmnt.setBigDecimal(idx, val);
0794: }
0795:
0796: /**
0797: * Set the given value as a parameter to the statement.
0798: */
0799: public void setBigInteger(PreparedStatement stmnt, int idx,
0800: BigInteger val, Column col) throws SQLException {
0801: if ((col != null && col.isCompatible(Types.VARCHAR, null, 0, 0))
0802: || (col == null && storeLargeNumbersAsStrings))
0803: setString(stmnt, idx, val.toString(), col);
0804: else
0805: setBigDecimal(stmnt, idx, new BigDecimal(val), col);
0806: }
0807:
0808: /**
0809: * Set the given value as a parameter to the statement.
0810: */
0811: public void setBinaryStream(PreparedStatement stmnt, int idx,
0812: InputStream val, int length, Column col)
0813: throws SQLException {
0814: stmnt.setBinaryStream(idx, val, length);
0815: }
0816:
0817: /**
0818: * Set the given value as a parameter to the statement.
0819: */
0820: public void setBlob(PreparedStatement stmnt, int idx, Blob val,
0821: Column col) throws SQLException {
0822: stmnt.setBlob(idx, val);
0823: }
0824:
0825: /**
0826: * Set the given value as a parameter to the statement. Uses the
0827: * {@link #serialize} method to serialize the value.
0828: */
0829: public void setBlobObject(PreparedStatement stmnt, int idx,
0830: Object val, Column col, JDBCStore store)
0831: throws SQLException {
0832: setBytes(stmnt, idx, serialize(val, store), col);
0833: }
0834:
0835: /**
0836: * Set the given value as a parameter to the statement.
0837: */
0838: public void setBoolean(PreparedStatement stmnt, int idx,
0839: boolean val, Column col) throws SQLException {
0840: stmnt.setInt(idx, (val) ? 1 : 0);
0841: }
0842:
0843: /**
0844: * Set the given value as a parameter to the statement.
0845: */
0846: public void setByte(PreparedStatement stmnt, int idx, byte val,
0847: Column col) throws SQLException {
0848: stmnt.setByte(idx, val);
0849: }
0850:
0851: /**
0852: * Set the given value as a parameter to the statement.
0853: */
0854: public void setBytes(PreparedStatement stmnt, int idx, byte[] val,
0855: Column col) throws SQLException {
0856: if (useSetBytesForBlobs)
0857: stmnt.setBytes(idx, val);
0858: else
0859: setBinaryStream(stmnt, idx, new ByteArrayInputStream(val),
0860: val.length, col);
0861: }
0862:
0863: /**
0864: * Set the given value as a parameter to the statement.
0865: */
0866: public void setChar(PreparedStatement stmnt, int idx, char val,
0867: Column col) throws SQLException {
0868: if ((col != null && col.isCompatible(Types.INTEGER, null, 0, 0))
0869: || (col == null && storeCharsAsNumbers))
0870: setInt(stmnt, idx, (int) val, col);
0871: else
0872: setString(stmnt, idx, String.valueOf(val), col);
0873: }
0874:
0875: /**
0876: * Set the given value as a parameter to the statement.
0877: */
0878: public void setCharacterStream(PreparedStatement stmnt, int idx,
0879: Reader val, int length, Column col) throws SQLException {
0880: stmnt.setCharacterStream(idx, val, length);
0881: }
0882:
0883: /**
0884: * Set the given value as a parameter to the statement.
0885: */
0886: public void setClob(PreparedStatement stmnt, int idx, Clob val,
0887: Column col) throws SQLException {
0888: stmnt.setClob(idx, val);
0889: }
0890:
0891: /**
0892: * Set the given value as a parameter to the statement.
0893: */
0894: public void setClobString(PreparedStatement stmnt, int idx,
0895: String val, Column col) throws SQLException {
0896: if (useSetStringForClobs)
0897: stmnt.setString(idx, val);
0898: else {
0899: // set reader from string
0900: StringReader in = new StringReader(val);
0901: setCharacterStream(stmnt, idx, in, val.length(), col);
0902: }
0903: }
0904:
0905: /**
0906: * Set the given value as a parameter to the statement.
0907: */
0908: public void setDate(PreparedStatement stmnt, int idx, Date val,
0909: Column col) throws SQLException {
0910: if (col != null && col.getType() == Types.DATE)
0911: setDate(stmnt, idx, new java.sql.Date(val.getTime()), null,
0912: col);
0913: else if (col != null && col.getType() == Types.TIME)
0914: setTime(stmnt, idx, new Time(val.getTime()), null, col);
0915: else if (val instanceof Timestamp)
0916: setTimestamp(stmnt, idx, (Timestamp) val, null, col);
0917: else
0918: setTimestamp(stmnt, idx, new Timestamp(val.getTime()),
0919: null, col);
0920: }
0921:
0922: /**
0923: * Set the given value as a parameter to the statement.
0924: */
0925: public void setDate(PreparedStatement stmnt, int idx,
0926: java.sql.Date val, Calendar cal, Column col)
0927: throws SQLException {
0928: if (cal == null)
0929: stmnt.setDate(idx, val);
0930: else
0931: stmnt.setDate(idx, val, cal);
0932: }
0933:
0934: /**
0935: * Set the given value as a parameter to the statement.
0936: */
0937: public void setCalendar(PreparedStatement stmnt, int idx,
0938: Calendar val, Column col) throws SQLException {
0939: // by default we merely delegate the the Date parameter
0940: setDate(stmnt, idx, val.getTime(), col);
0941: }
0942:
0943: /**
0944: * Set the given value as a parameter to the statement.
0945: */
0946: public void setDouble(PreparedStatement stmnt, int idx, double val,
0947: Column col) throws SQLException {
0948: stmnt.setDouble(idx, val);
0949: }
0950:
0951: /**
0952: * Set the given value as a parameter to the statement.
0953: */
0954: public void setFloat(PreparedStatement stmnt, int idx, float val,
0955: Column col) throws SQLException {
0956: stmnt.setFloat(idx, val);
0957: }
0958:
0959: /**
0960: * Set the given value as a parameter to the statement.
0961: */
0962: public void setInt(PreparedStatement stmnt, int idx, int val,
0963: Column col) throws SQLException {
0964: stmnt.setInt(idx, val);
0965: }
0966:
0967: /**
0968: * Set the given value as a parameter to the statement.
0969: */
0970: public void setLong(PreparedStatement stmnt, int idx, long val,
0971: Column col) throws SQLException {
0972: stmnt.setLong(idx, val);
0973: }
0974:
0975: /**
0976: * Set the given value as a parameter to the statement.
0977: */
0978: public void setLocale(PreparedStatement stmnt, int idx, Locale val,
0979: Column col) throws SQLException {
0980: setString(stmnt, idx, val.getLanguage() + "_"
0981: + val.getCountry() + "_" + val.getVariant(), col);
0982: }
0983:
0984: /**
0985: * Set the given value as a parameters to the statement. The column
0986: * type will come from {@link Types}.
0987: */
0988: public void setNull(PreparedStatement stmnt, int idx, int colType,
0989: Column col) throws SQLException {
0990: stmnt.setNull(idx, colType);
0991: }
0992:
0993: /**
0994: * Set the given value as a parameter to the statement.
0995: */
0996: public void setNumber(PreparedStatement stmnt, int idx, Number num,
0997: Column col) throws SQLException {
0998: // check for known floating point types to give driver a chance to
0999: // handle special numbers like NaN and infinity; bug #1053
1000: if (num instanceof Double)
1001: setDouble(stmnt, idx, ((Double) num).doubleValue(), col);
1002: else if (num instanceof Float)
1003: setFloat(stmnt, idx, ((Float) num).floatValue(), col);
1004: else
1005: setBigDecimal(stmnt, idx, new BigDecimal(num.toString()),
1006: col);
1007: }
1008:
1009: /**
1010: * Set the given value as a parameters to the statement. The column
1011: * type will come from {@link Types}.
1012: */
1013: public void setObject(PreparedStatement stmnt, int idx, Object val,
1014: int colType, Column col) throws SQLException {
1015: if (colType == -1 || colType == Types.OTHER)
1016: stmnt.setObject(idx, val);
1017: else
1018: stmnt.setObject(idx, val, colType);
1019: }
1020:
1021: /**
1022: * Set the given value as a parameter to the statement.
1023: */
1024: public void setRef(PreparedStatement stmnt, int idx, Ref val,
1025: Column col) throws SQLException {
1026: stmnt.setRef(idx, val);
1027: }
1028:
1029: /**
1030: * Set the given value as a parameter to the statement.
1031: */
1032: public void setShort(PreparedStatement stmnt, int idx, short val,
1033: Column col) throws SQLException {
1034: stmnt.setShort(idx, val);
1035: }
1036:
1037: /**
1038: * Set the given value as a parameter to the statement.
1039: */
1040: public void setString(PreparedStatement stmnt, int idx, String val,
1041: Column col) throws SQLException {
1042: stmnt.setString(idx, val);
1043: }
1044:
1045: /**
1046: * Set the given value as a parameter to the statement.
1047: */
1048: public void setTime(PreparedStatement stmnt, int idx, Time val,
1049: Calendar cal, Column col) throws SQLException {
1050: if (cal == null)
1051: stmnt.setTime(idx, val);
1052: else
1053: stmnt.setTime(idx, val, cal);
1054: }
1055:
1056: /**
1057: * Set the given value as a parameter to the statement.
1058: */
1059: public void setTimestamp(PreparedStatement stmnt, int idx,
1060: Timestamp val, Calendar cal, Column col)
1061: throws SQLException {
1062: // ensure that we do not insert dates at a greater precision than
1063: // that at which they will be returned by a SELECT
1064: int rounded = (int) Math.round(val.getNanos()
1065: / (double) datePrecision);
1066: int nanos = rounded * datePrecision;
1067: if (nanos > 999999999) {
1068: // rollover to next second
1069: val.setTime(val.getTime() + 1000);
1070: nanos = 0;
1071: }
1072:
1073: if (supportsTimestampNanos)
1074: val.setNanos(nanos);
1075: else
1076: val.setNanos(0);
1077:
1078: if (cal == null)
1079: stmnt.setTimestamp(idx, val);
1080: else
1081: stmnt.setTimestamp(idx, val, cal);
1082: }
1083:
1084: /**
1085: * Set a column value into a prepared statement.
1086: *
1087: * @param stmnt the prepared statement to parameterize
1088: * @param idx the index of the parameter in the prepared statement
1089: * @param val the value of the column
1090: * @param col the column being set
1091: * @param type the field mapping type code for the value
1092: * @param store the store manager for the current context
1093: */
1094: public void setTyped(PreparedStatement stmnt, int idx, Object val,
1095: Column col, int type, JDBCStore store) throws SQLException {
1096: if (val == null) {
1097: setNull(stmnt, idx, (col == null) ? Types.OTHER : col
1098: .getType(), col);
1099: return;
1100: }
1101:
1102: Sized s;
1103: Calendard c;
1104: switch (type) {
1105: case JavaTypes.BOOLEAN:
1106: case JavaTypes.BOOLEAN_OBJ:
1107: setBoolean(stmnt, idx, ((Boolean) val).booleanValue(), col);
1108: break;
1109: case JavaTypes.BYTE:
1110: case JavaTypes.BYTE_OBJ:
1111: setByte(stmnt, idx, ((Number) val).byteValue(), col);
1112: break;
1113: case JavaTypes.CHAR:
1114: case JavaTypes.CHAR_OBJ:
1115: setChar(stmnt, idx, ((Character) val).charValue(), col);
1116: break;
1117: case JavaTypes.DOUBLE:
1118: case JavaTypes.DOUBLE_OBJ:
1119: setDouble(stmnt, idx, ((Number) val).doubleValue(), col);
1120: break;
1121: case JavaTypes.FLOAT:
1122: case JavaTypes.FLOAT_OBJ:
1123: setFloat(stmnt, idx, ((Number) val).floatValue(), col);
1124: break;
1125: case JavaTypes.INT:
1126: case JavaTypes.INT_OBJ:
1127: setInt(stmnt, idx, ((Number) val).intValue(), col);
1128: break;
1129: case JavaTypes.LONG:
1130: case JavaTypes.LONG_OBJ:
1131: setLong(stmnt, idx, ((Number) val).longValue(), col);
1132: break;
1133: case JavaTypes.SHORT:
1134: case JavaTypes.SHORT_OBJ:
1135: setShort(stmnt, idx, ((Number) val).shortValue(), col);
1136: break;
1137: case JavaTypes.STRING:
1138: if (col != null
1139: && (col.getType() == Types.CLOB || col.getType() == Types.LONGVARCHAR))
1140: setClobString(stmnt, idx, (String) val, col);
1141: else
1142: setString(stmnt, idx, (String) val, col);
1143: break;
1144: case JavaTypes.OBJECT:
1145: setBlobObject(stmnt, idx, val, col, store);
1146: break;
1147: case JavaTypes.DATE:
1148: setDate(stmnt, idx, (Date) val, col);
1149: break;
1150: case JavaTypes.CALENDAR:
1151: setCalendar(stmnt, idx, (Calendar) val, col);
1152: break;
1153: case JavaTypes.BIGDECIMAL:
1154: setBigDecimal(stmnt, idx, (BigDecimal) val, col);
1155: break;
1156: case JavaTypes.BIGINTEGER:
1157: setBigInteger(stmnt, idx, (BigInteger) val, col);
1158: break;
1159: case JavaTypes.NUMBER:
1160: setNumber(stmnt, idx, (Number) val, col);
1161: break;
1162: case JavaTypes.LOCALE:
1163: setLocale(stmnt, idx, (Locale) val, col);
1164: break;
1165: case JavaSQLTypes.SQL_ARRAY:
1166: setArray(stmnt, idx, (Array) val, col);
1167: break;
1168: case JavaSQLTypes.ASCII_STREAM:
1169: s = (Sized) val;
1170: setAsciiStream(stmnt, idx, (InputStream) s.value, s.size,
1171: col);
1172: break;
1173: case JavaSQLTypes.BINARY_STREAM:
1174: s = (Sized) val;
1175: setBinaryStream(stmnt, idx, (InputStream) s.value, s.size,
1176: col);
1177: break;
1178: case JavaSQLTypes.BLOB:
1179: setBlob(stmnt, idx, (Blob) val, col);
1180: break;
1181: case JavaSQLTypes.BYTES:
1182: setBytes(stmnt, idx, (byte[]) val, col);
1183: break;
1184: case JavaSQLTypes.CHAR_STREAM:
1185: s = (Sized) val;
1186: setCharacterStream(stmnt, idx, (Reader) s.value, s.size,
1187: col);
1188: break;
1189: case JavaSQLTypes.CLOB:
1190: setClob(stmnt, idx, (Clob) val, col);
1191: break;
1192: case JavaSQLTypes.SQL_DATE:
1193: if (val instanceof Calendard) {
1194: c = (Calendard) val;
1195: setDate(stmnt, idx, (java.sql.Date) c.value,
1196: c.calendar, col);
1197: } else
1198: setDate(stmnt, idx, (java.sql.Date) val, null, col);
1199: break;
1200: case JavaSQLTypes.REF:
1201: setRef(stmnt, idx, (Ref) val, col);
1202: break;
1203: case JavaSQLTypes.TIME:
1204: if (val instanceof Calendard) {
1205: c = (Calendard) val;
1206: setTime(stmnt, idx, (Time) c.value, c.calendar, col);
1207: } else
1208: setTime(stmnt, idx, (Time) val, null, col);
1209: break;
1210: case JavaSQLTypes.TIMESTAMP:
1211: if (val instanceof Calendard) {
1212: c = (Calendard) val;
1213: setTimestamp(stmnt, idx, (Timestamp) c.value,
1214: c.calendar, col);
1215: } else
1216: setTimestamp(stmnt, idx, (Timestamp) val, null, col);
1217: break;
1218: default:
1219: if (col != null
1220: && (col.getType() == Types.BLOB || col.getType() == Types.VARBINARY))
1221: setBlobObject(stmnt, idx, val, col, store);
1222: else
1223: setObject(stmnt, idx, val, col.getType(), col);
1224: }
1225: }
1226:
1227: /**
1228: * Set a completely unknown parameter into a prepared statement.
1229: */
1230: public void setUnknown(PreparedStatement stmnt, int idx,
1231: Object val, Column col) throws SQLException {
1232: Sized sized = null;
1233: Calendard cald = null;
1234: if (val instanceof Sized) {
1235: sized = (Sized) val;
1236: val = sized.value;
1237: } else if (val instanceof Calendard) {
1238: cald = (Calendard) val;
1239: val = cald.value;
1240: }
1241:
1242: if (val == null)
1243: setNull(stmnt, idx, (col == null) ? Types.OTHER : col
1244: .getType(), col);
1245: else if (val instanceof String)
1246: setString(stmnt, idx, val.toString(), col);
1247: else if (val instanceof Integer)
1248: setInt(stmnt, idx, ((Integer) val).intValue(), col);
1249: else if (val instanceof Boolean)
1250: setBoolean(stmnt, idx, ((Boolean) val).booleanValue(), col);
1251: else if (val instanceof Long)
1252: setLong(stmnt, idx, ((Long) val).longValue(), col);
1253: else if (val instanceof Float)
1254: setFloat(stmnt, idx, ((Float) val).floatValue(), col);
1255: else if (val instanceof Double)
1256: setDouble(stmnt, idx, ((Double) val).doubleValue(), col);
1257: else if (val instanceof Byte)
1258: setByte(stmnt, idx, ((Byte) val).byteValue(), col);
1259: else if (val instanceof Character)
1260: setChar(stmnt, idx, ((Character) val).charValue(), col);
1261: else if (val instanceof Short)
1262: setShort(stmnt, idx, ((Short) val).shortValue(), col);
1263: else if (val instanceof Locale)
1264: setLocale(stmnt, idx, (Locale) val, col);
1265: else if (val instanceof BigDecimal)
1266: setBigDecimal(stmnt, idx, (BigDecimal) val, col);
1267: else if (val instanceof BigInteger)
1268: setBigInteger(stmnt, idx, (BigInteger) val, col);
1269: else if (val instanceof Array)
1270: setArray(stmnt, idx, (Array) val, col);
1271: else if (val instanceof Blob)
1272: setBlob(stmnt, idx, (Blob) val, col);
1273: else if (val instanceof byte[])
1274: setBytes(stmnt, idx, (byte[]) val, col);
1275: else if (val instanceof Clob)
1276: setClob(stmnt, idx, (Clob) val, col);
1277: else if (val instanceof Ref)
1278: setRef(stmnt, idx, (Ref) val, col);
1279: else if (val instanceof java.sql.Date)
1280: setDate(stmnt, idx, (java.sql.Date) val,
1281: (cald == null) ? null : cald.calendar, col);
1282: else if (val instanceof Timestamp)
1283: setTimestamp(stmnt, idx, (Timestamp) val,
1284: (cald == null) ? null : cald.calendar, col);
1285: else if (val instanceof Time)
1286: setTime(stmnt, idx, (Time) val, (cald == null) ? null
1287: : cald.calendar, col);
1288: else if (val instanceof Date)
1289: setDate(stmnt, idx, (Date) val, col);
1290: else if (val instanceof Calendar)
1291: setDate(stmnt, idx, ((Calendar) val).getTime(), col);
1292: else if (val instanceof Reader)
1293: setCharacterStream(stmnt, idx, (Reader) val,
1294: (sized == null) ? 0 : sized.size, col);
1295: else
1296: throw new UserException(_loc.get("bad-param", val
1297: .getClass()));
1298: }
1299:
1300: /**
1301: * Return the serialized bytes for the given object.
1302: */
1303: public byte[] serialize(Object val, JDBCStore store)
1304: throws SQLException {
1305: if (val == null)
1306: return null;
1307: if (val instanceof SerializedData)
1308: return ((SerializedData) val).bytes;
1309: return Serialization.serialize(val, store.getContext());
1310: }
1311:
1312: /**
1313: * Invoke the JDK 1.4 <code>setBytes</code> method on the given BLOB object.
1314: */
1315: public void putBytes(Object blob, byte[] data) throws SQLException {
1316: if (_setBytes == null) {
1317: try {
1318: _setBytes = blob.getClass().getMethod("setBytes",
1319: new Class[] { long.class, byte[].class });
1320: } catch (Exception e) {
1321: throw new StoreException(e);
1322: }
1323: }
1324: invokePutLobMethod(_setBytes, blob, new Object[] {
1325: Numbers.valueOf(1L), data });
1326: }
1327:
1328: /**
1329: * Invoke the JDK 1.4 <code>setString</code> method on the given CLOB
1330: * object.
1331: */
1332: public void putString(Object clob, String data) throws SQLException {
1333: if (_setString == null) {
1334: try {
1335: _setString = clob.getClass().getMethod("setString",
1336: new Class[] { long.class, String.class });
1337: } catch (Exception e) {
1338: throw new StoreException(e);
1339: }
1340: }
1341: invokePutLobMethod(_setString, clob, new Object[] {
1342: Numbers.valueOf(1L), data });
1343: }
1344:
1345: /**
1346: * Invoke the JDK 1.4 <code>setCharacterStream</code> method on the given
1347: * CLOB object.
1348: */
1349: public void putChars(Object clob, char[] data) throws SQLException {
1350: if (_setCharStream == null) {
1351: try {
1352: _setCharStream = clob.getClass().getMethod(
1353: "setCharacterStream",
1354: new Class[] { long.class });
1355: } catch (Exception e) {
1356: throw new StoreException(e);
1357: }
1358: }
1359:
1360: Writer writer = (Writer) invokePutLobMethod(_setCharStream,
1361: clob, new Object[] { Numbers.valueOf(1L) });
1362: try {
1363: writer.write(data);
1364: writer.flush();
1365: } catch (IOException ioe) {
1366: throw new SQLException(ioe.toString());
1367: }
1368: }
1369:
1370: /**
1371: * Invoke the given LOB method on the given target with the given data.
1372: */
1373: private static Object invokePutLobMethod(Method method,
1374: Object target, Object[] args) throws SQLException {
1375: try {
1376: return method.invoke(target, args);
1377: } catch (InvocationTargetException ite) {
1378: Throwable t = ite.getTargetException();
1379: if (t instanceof SQLException)
1380: throw (SQLException) t;
1381: throw new StoreException(t);
1382: } catch (Exception e) {
1383: throw new StoreException(e);
1384: }
1385: }
1386:
1387: /**
1388: * Warn that a particular value could not be stored precisely.
1389: * After the first warning for a particular type, messages
1390: * will be turned into trace messages.
1391: */
1392: protected void storageWarning(Object orig, Object converted) {
1393: boolean warn;
1394: synchronized (this ) {
1395: if (_precisionWarnedTypes == null)
1396: _precisionWarnedTypes = new HashSet();
1397: warn = _precisionWarnedTypes.add(orig.getClass());
1398: }
1399:
1400: if (storageLimitationsFatal || (warn && log.isWarnEnabled())
1401: || (!warn && log.isTraceEnabled())) {
1402: Message msg = _loc.get("storage-restriction", new Object[] {
1403: platform, orig, orig.getClass().getName(),
1404: converted, });
1405:
1406: if (storageLimitationsFatal)
1407: throw new StoreException(msg);
1408:
1409: if (warn)
1410: log.warn(msg);
1411: else
1412: log.trace(msg);
1413: }
1414: }
1415:
1416: /////////
1417: // Types
1418: /////////
1419:
1420: /**
1421: * Return the preferred {@link Types} constant for the given
1422: * {@link JavaTypes} or {@link JavaSQLTypes} constant.
1423: */
1424: public int getJDBCType(int metaTypeCode, boolean lob) {
1425: if (lob) {
1426: switch (metaTypeCode) {
1427: case JavaTypes.STRING:
1428: case JavaSQLTypes.ASCII_STREAM:
1429: case JavaSQLTypes.CHAR_STREAM:
1430: return getPreferredType(Types.CLOB);
1431: default:
1432: return getPreferredType(Types.BLOB);
1433: }
1434: }
1435:
1436: switch (metaTypeCode) {
1437: case JavaTypes.BOOLEAN:
1438: case JavaTypes.BOOLEAN_OBJ:
1439: return getPreferredType(Types.BIT);
1440: case JavaTypes.BYTE:
1441: case JavaTypes.BYTE_OBJ:
1442: return getPreferredType(Types.TINYINT);
1443: case JavaTypes.CHAR:
1444: case JavaTypes.CHAR_OBJ:
1445: if (storeCharsAsNumbers)
1446: return getPreferredType(Types.INTEGER);
1447: return getPreferredType(Types.CHAR);
1448: case JavaTypes.DOUBLE:
1449: case JavaTypes.DOUBLE_OBJ:
1450: return getPreferredType(Types.DOUBLE);
1451: case JavaTypes.FLOAT:
1452: case JavaTypes.FLOAT_OBJ:
1453: return getPreferredType(Types.REAL);
1454: case JavaTypes.INT:
1455: case JavaTypes.INT_OBJ:
1456: return getPreferredType(Types.INTEGER);
1457: case JavaTypes.LONG:
1458: case JavaTypes.LONG_OBJ:
1459: return getPreferredType(Types.BIGINT);
1460: case JavaTypes.SHORT:
1461: case JavaTypes.SHORT_OBJ:
1462: return getPreferredType(Types.SMALLINT);
1463: case JavaTypes.STRING:
1464: case JavaTypes.LOCALE:
1465: case JavaSQLTypes.ASCII_STREAM:
1466: case JavaSQLTypes.CHAR_STREAM:
1467: return getPreferredType(Types.VARCHAR);
1468: case JavaTypes.BIGINTEGER:
1469: if (storeLargeNumbersAsStrings)
1470: return getPreferredType(Types.VARCHAR);
1471: return getPreferredType(Types.BIGINT);
1472: case JavaTypes.BIGDECIMAL:
1473: if (storeLargeNumbersAsStrings)
1474: return getPreferredType(Types.VARCHAR);
1475: return getPreferredType(Types.DOUBLE);
1476: case JavaTypes.NUMBER:
1477: if (storeLargeNumbersAsStrings)
1478: return getPreferredType(Types.VARCHAR);
1479: return getPreferredType(Types.NUMERIC);
1480: case JavaTypes.CALENDAR:
1481: case JavaTypes.DATE:
1482: return getPreferredType(Types.TIMESTAMP);
1483: case JavaSQLTypes.SQL_ARRAY:
1484: return getPreferredType(Types.ARRAY);
1485: case JavaSQLTypes.BINARY_STREAM:
1486: case JavaSQLTypes.BLOB:
1487: case JavaSQLTypes.BYTES:
1488: return getPreferredType(Types.BLOB);
1489: case JavaSQLTypes.CLOB:
1490: return getPreferredType(Types.CLOB);
1491: case JavaSQLTypes.SQL_DATE:
1492: return getPreferredType(Types.DATE);
1493: case JavaSQLTypes.TIME:
1494: return getPreferredType(Types.TIME);
1495: case JavaSQLTypes.TIMESTAMP:
1496: return getPreferredType(Types.TIMESTAMP);
1497: default:
1498: return getPreferredType(Types.BLOB);
1499: }
1500: }
1501:
1502: /**
1503: * Return the preferred {@link Types} type for the given one. Returns
1504: * the given type by default.
1505: */
1506: public int getPreferredType(int type) {
1507: return type;
1508: }
1509:
1510: /**
1511: * Return the preferred database type name for the given column's type
1512: * from {@link Types}.
1513: */
1514: public String getTypeName(Column col) {
1515: if (!StringUtils.isEmpty(col.getTypeName()))
1516: return appendSize(col, col.getTypeName());
1517:
1518: if (col.isAutoAssigned() && autoAssignTypeName != null)
1519: return appendSize(col, autoAssignTypeName);
1520:
1521: return appendSize(col, getTypeName(col.getType()));
1522: }
1523:
1524: /**
1525: * Returns the type name for the specific constant as defined
1526: * by {@link java.sql.Types}.
1527: *
1528: * @param type the type
1529: * @return the name for the type
1530: */
1531: public String getTypeName(int type) {
1532: switch (type) {
1533: case Types.ARRAY:
1534: return arrayTypeName;
1535: case Types.BIGINT:
1536: return bigintTypeName;
1537: case Types.BINARY:
1538: return binaryTypeName;
1539: case Types.BIT:
1540: return bitTypeName;
1541: case Types.BLOB:
1542: return blobTypeName;
1543: case Types.BOOLEAN:
1544: return booleanTypeName;
1545: case Types.CHAR:
1546: return charTypeName;
1547: case Types.CLOB:
1548: return clobTypeName;
1549: case Types.DATE:
1550: return dateTypeName;
1551: case Types.DECIMAL:
1552: return decimalTypeName;
1553: case Types.DISTINCT:
1554: return distinctTypeName;
1555: case Types.DOUBLE:
1556: return doubleTypeName;
1557: case Types.FLOAT:
1558: return floatTypeName;
1559: case Types.INTEGER:
1560: return integerTypeName;
1561: case Types.JAVA_OBJECT:
1562: return javaObjectTypeName;
1563: case Types.LONGVARBINARY:
1564: return longVarbinaryTypeName;
1565: case Types.LONGVARCHAR:
1566: return longVarcharTypeName;
1567: case Types.NULL:
1568: return nullTypeName;
1569: case Types.NUMERIC:
1570: return numericTypeName;
1571: case Types.OTHER:
1572: return otherTypeName;
1573: case Types.REAL:
1574: return realTypeName;
1575: case Types.REF:
1576: return refTypeName;
1577: case Types.SMALLINT:
1578: return smallintTypeName;
1579: case Types.STRUCT:
1580: return structTypeName;
1581: case Types.TIME:
1582: return timeTypeName;
1583: case Types.TIMESTAMP:
1584: return timestampTypeName;
1585: case Types.TINYINT:
1586: return tinyintTypeName;
1587: case Types.VARBINARY:
1588: return varbinaryTypeName;
1589: case Types.VARCHAR:
1590: return varcharTypeName;
1591: default:
1592: return otherTypeName;
1593: }
1594: }
1595:
1596: /**
1597: * Helper method to add size properties to the specified type.
1598: * If present, the string "{0}" will be replaced with the size definition;
1599: * otherwise the size definition will be appended to the type name.
1600: * If your database has column types that don't allow size definitions,
1601: * override this method to return the unaltered type name for columns of
1602: * those types (or add the type names to the
1603: * <code>fixedSizeTypeNameSet</code>).
1604: *
1605: * <P>Some databases support "type modifiers" for example the unsigned
1606: * "modifier" in MySQL. In these cases the size should go between the type
1607: * and the "modifier", instead of after the modifier. For example
1608: * CREATE table FOO ( myint INT (10) UNSIGNED . . .) instead of
1609: * CREATE table FOO ( myint INT UNSIGNED (10) . . .).
1610: * Type modifiers should be added to <code>typeModifierSet</code> in
1611: * subclasses.
1612: */
1613: protected String appendSize(Column col, String typeName) {
1614: if (fixedSizeTypeNameSet.contains(typeName.toUpperCase()))
1615: return typeName;
1616: if (typeName.indexOf('(') != -1)
1617: return typeName;
1618:
1619: String size = null;
1620: if (col.getSize() > 0) {
1621: StringBuffer buf = new StringBuffer(10);
1622: buf.append("(").append(col.getSize());
1623: if (col.getDecimalDigits() > 0)
1624: buf.append(", ").append(col.getDecimalDigits());
1625: buf.append(")");
1626: size = buf.toString();
1627: }
1628:
1629: return insertSize(typeName, size);
1630: }
1631:
1632: /**
1633: * Helper method that inserts a size clause for a given SQL type.
1634: *
1635: * @see appendSize
1636: *
1637: * @param typeName The SQL type ie INT
1638: * @param size The size clause ie (10)
1639: * @return The typeName + size clause. Usually the size clause will
1640: * be appended to typeName. If the typeName contains a
1641: * marker : {0} or if typeName contains a modifier the
1642: * size clause will be inserted appropriately.
1643: */
1644: protected String insertSize(String typeName, String size) {
1645: if (StringUtils.isEmpty(size)) {
1646: int idx = typeName.indexOf("{0}");
1647: if (idx != -1) {
1648: return typeName.substring(0, idx);
1649: }
1650: return typeName;
1651: }
1652:
1653: int idx = typeName.indexOf("{0}");
1654: if (idx != -1) {
1655: // replace '{0}' with size
1656: String ret = typeName.substring(0, idx);
1657: if (size != null)
1658: ret = ret + size;
1659: if (typeName.length() > idx + 3)
1660: ret = ret + typeName.substring(idx + 3);
1661: return ret;
1662: }
1663: if (!typeModifierSet.isEmpty()) {
1664: String s;
1665: idx = typeName.length();
1666: int curIdx = -1;
1667: for (Iterator i = typeModifierSet.iterator(); i.hasNext();) {
1668: s = (String) i.next();
1669: if (typeName.toUpperCase().indexOf(s) != -1) {
1670: curIdx = typeName.toUpperCase().indexOf(s);
1671: if (curIdx != -1 && curIdx < idx) {
1672: idx = curIdx;
1673: }
1674: }
1675: }
1676: if (idx != typeName.length()) {
1677: String ret = typeName.substring(0, idx);
1678: ret = ret + size;
1679: ret = ret + ' ' + typeName.substring(idx);
1680: return ret;
1681: }
1682: }
1683: return typeName + size;
1684: }
1685:
1686: ///////////
1687: // Selects
1688: ///////////
1689:
1690: /**
1691: * Set the name of the join syntax to use: sql92, traditional, database
1692: */
1693: public void setJoinSyntax(String syntax) {
1694: if ("sql92".equals(syntax))
1695: joinSyntax = SYNTAX_SQL92;
1696: else if ("traditional".equals(syntax))
1697: joinSyntax = SYNTAX_TRADITIONAL;
1698: else if ("database".equals(syntax))
1699: joinSyntax = SYNTAX_DATABASE;
1700: else if (!StringUtils.isEmpty(syntax))
1701: throw new IllegalArgumentException(syntax);
1702: }
1703:
1704: /**
1705: * Return a SQL string to act as a placeholder for the given column.
1706: */
1707: public String getPlaceholderValueString(Column col) {
1708: switch (col.getType()) {
1709: case Types.BIGINT:
1710: case Types.BIT:
1711: case Types.INTEGER:
1712: case Types.NUMERIC:
1713: case Types.SMALLINT:
1714: case Types.TINYINT:
1715: return "0";
1716: case Types.CHAR:
1717: return (storeCharsAsNumbers) ? "0" : "' '";
1718: case Types.CLOB:
1719: case Types.LONGVARCHAR:
1720: case Types.VARCHAR:
1721: return "''";
1722: case Types.DATE:
1723: return ZERO_DATE_STR;
1724: case Types.DECIMAL:
1725: case Types.DOUBLE:
1726: case Types.FLOAT:
1727: case Types.REAL:
1728: return "0.0";
1729: case Types.TIME:
1730: return ZERO_TIME_STR;
1731: case Types.TIMESTAMP:
1732: return ZERO_TIMESTAMP_STR;
1733: default:
1734: return "NULL";
1735: }
1736: }
1737:
1738: /**
1739: * Create a SELECT COUNT statement in the proper join syntax for the
1740: * given instance.
1741: */
1742: public SQLBuffer toSelectCount(Select sel) {
1743: SQLBuffer selectSQL = new SQLBuffer(this );
1744: SQLBuffer from;
1745: sel.addJoinClassConditions();
1746: if (sel.getFromSelect() != null)
1747: from = getFromSelect(sel, false);
1748: else
1749: from = getFrom(sel, false);
1750: SQLBuffer where = getWhere(sel, false);
1751:
1752: // if no grouping and no range, we might be able to get by without
1753: // a subselect
1754: if (sel.getGrouping() == null && sel.getStartIndex() == 0
1755: && sel.getEndIndex() == Long.MAX_VALUE) {
1756: // if the select has no identifier cols, use COUNT(*)
1757: List aliases = (!sel.isDistinct()) ? Collections.EMPTY_LIST
1758: : sel.getIdentifierAliases();
1759: if (aliases.isEmpty()) {
1760: selectSQL.append("COUNT(*)");
1761: return toSelect(selectSQL, null, from, where, null,
1762: null, null, false, false, 0, Long.MAX_VALUE);
1763: }
1764:
1765: // if there is a single distinct col, use COUNT(DISTINCT col)
1766: if (aliases.size() == 1) {
1767: selectSQL.append("COUNT(DISTINCT ").append(
1768: aliases.get(0).toString()).append(")");
1769: return toSelect(selectSQL, null, from, where, null,
1770: null, null, false, false, 0, Long.MAX_VALUE);
1771: }
1772:
1773: // can we combine distinct cols?
1774: if (distinctCountColumnSeparator != null) {
1775: selectSQL.append("COUNT(DISTINCT ");
1776: for (int i = 0; i < aliases.size(); i++) {
1777: if (i > 0) {
1778: selectSQL.append(" ");
1779: selectSQL.append(distinctCountColumnSeparator);
1780: selectSQL.append(" ");
1781: }
1782: selectSQL.append(aliases.get(i).toString());
1783: }
1784: selectSQL.append(")");
1785: return toSelect(selectSQL, null, from, where, null,
1786: null, null, false, false, 0, Long.MAX_VALUE);
1787: }
1788: }
1789:
1790: // since we can't combine distinct cols, we have to perform an outer
1791: // COUNT(*) select using the original select as a subselect in the
1792: // FROM clause
1793: assertSupport(supportsSubselect, "SupportsSubselect");
1794:
1795: SQLBuffer subSelect = getSelects(sel, true, false);
1796: SQLBuffer subFrom = from;
1797: from = new SQLBuffer(this );
1798: from.append("(");
1799: from.append(toSelect(subSelect, null, subFrom, where, sel
1800: .getGrouping(), sel.getHaving(), null,
1801: sel.isDistinct(), false, sel.getStartIndex(), sel
1802: .getEndIndex(), true));
1803: from.append(")");
1804: if (requiresAliasForSubselect)
1805: from.append(" ").append(Select.FROM_SELECT_ALIAS);
1806:
1807: selectSQL.append("COUNT(*)");
1808: return toSelect(selectSQL, null, from, null, null, null, null,
1809: false, false, 0, Long.MAX_VALUE);
1810: }
1811:
1812: /**
1813: * Create a DELETE statement for the specified Select. If the
1814: * database does not support the bulk delete statement (such as
1815: * cases where a subselect is required and the database doesn't support
1816: * subselects), this method should return null.
1817: */
1818: public SQLBuffer toDelete(ClassMapping mapping, Select sel,
1819: Object[] params) {
1820: return toBulkOperation(mapping, sel, null, params, null);
1821: }
1822:
1823: public SQLBuffer toUpdate(ClassMapping mapping, Select sel,
1824: JDBCStore store, Object[] params, Map updates) {
1825: return toBulkOperation(mapping, sel, store, params, updates);
1826: }
1827:
1828: /**
1829: * Returns the SQL for a bulk operation, either a DELETE or an UPDATE.
1830: *
1831: * @param mapping the mappng against which we are operating
1832: * @param sel the Select that will constitute the WHERE clause
1833: * @param store the current store
1834: * @param updateParams the Map that holds the update parameters; a null
1835: * value indicates that this is a delete operation
1836: * @return the SQLBuffer for the update, or <em>null</em> if it is not
1837: * possible to perform the bulk update
1838: */
1839: protected SQLBuffer toBulkOperation(ClassMapping mapping,
1840: Select sel, JDBCStore store, Object[] params,
1841: Map updateParams) {
1842: SQLBuffer sql = new SQLBuffer(this );
1843: if (updateParams == null)
1844: sql.append("DELETE FROM ");
1845: else
1846: sql.append("UPDATE ");
1847: sel.addJoinClassConditions();
1848:
1849: // if there is only a single table in the select, then we can
1850: // just issue a single DELETE FROM TABLE WHERE <conditions>
1851: // statement; otherwise, since SQL doesn't allow deleting
1852: // from one of a multi-table select, we need to issue a subselect
1853: // like DELETE FROM TABLE WHERE EXISTS
1854: // (SELECT 1 FROM TABLE t0 WHERE t0.ID = TABLE.ID); also, some
1855: // databases do not allow aliases in delete statements, which
1856: // also causes us to use a subselect
1857: if (sel.getTableAliases().size() == 1 && supportsSubselect
1858: && allowsAliasInBulkClause) {
1859: SQLBuffer from;
1860: if (sel.getFromSelect() != null)
1861: from = getFromSelect(sel, false);
1862: else
1863: from = getFrom(sel, false);
1864:
1865: sql.append(from);
1866: appendUpdates(sel, store, sql, params, updateParams,
1867: allowsAliasInBulkClause);
1868:
1869: SQLBuffer where = sel.getWhere();
1870: if (where != null && !where.isEmpty()) {
1871: sql.append(" WHERE ");
1872: sql.append(where);
1873: }
1874: return sql;
1875: }
1876:
1877: Table table = mapping.getTable();
1878: String tableName = getFullName(table, false);
1879:
1880: // only use a subselect if the where is not empty; otherwise
1881: // an unqualified delete or update will work
1882: if (sel.getWhere() == null || sel.getWhere().isEmpty()) {
1883: sql.append(tableName);
1884: appendUpdates(sel, store, sql, params, updateParams, false);
1885: return sql;
1886: }
1887:
1888: // we need to use a subselect if we are to bulk delete where
1889: // the select includes multiple tables; if the database
1890: // doesn't support it, then we need to sigal this by returning null
1891: if (!supportsSubselect || !supportsCorrelatedSubselect)
1892: return null;
1893:
1894: Column[] pks = mapping.getPrimaryKeyColumns();
1895: sel.clearSelects();
1896: sel.setDistinct(true);
1897:
1898: // if we have only a single PK, we can use a non-correlated
1899: // subquery (using an IN statement), which is much faster than
1900: // a correlated subquery (since a correlated subquery needs
1901: // to be executed once for each row in the table)
1902: if (pks.length == 1) {
1903: sel.select(pks[0]);
1904: sql.append(tableName);
1905: appendUpdates(sel, store, sql, params, updateParams, false);
1906: sql.append(" WHERE ").append(pks[0]).append(" IN (")
1907: .append(sel.toSelect(false, null)).append(")");
1908: } else {
1909: sel.clearSelects();
1910: sel.setDistinct(false);
1911:
1912: // since the select is using a correlated subquery, we
1913: // only need to select a bogus virtual column
1914: sel.select("1", null);
1915:
1916: // add in the joins to the table
1917: Column[] cols = table.getPrimaryKey().getColumns();
1918: SQLBuffer buf = new SQLBuffer(this );
1919: buf.append("(");
1920: for (int i = 0; i < cols.length; i++) {
1921: if (i > 0)
1922: buf.append(" AND ");
1923:
1924: // add in "t0.PK = MYTABLE.PK"
1925: buf.append(sel.getColumnAlias(cols[i], null)).append(
1926: " = ").append(table).append(catalogSeparator)
1927: .append(cols[i]);
1928: }
1929: buf.append(")");
1930: sel.where(buf, null);
1931:
1932: sql.append(tableName);
1933: appendUpdates(sel, store, sql, params, updateParams, false);
1934: sql.append(" WHERE EXISTS (").append(
1935: sel.toSelect(false, null)).append(")");
1936: }
1937: return sql;
1938: }
1939:
1940: protected void appendUpdates(Select sel, JDBCStore store,
1941: SQLBuffer sql, Object[] params, Map updateParams,
1942: boolean allowAlias) {
1943: if (updateParams == null || updateParams.size() == 0)
1944: return;
1945:
1946: // manually build up the SET clause for the UPDATE statement
1947: sql.append(" SET ");
1948: ExpContext ctx = new ExpContext(store, params, store
1949: .getFetchConfiguration());
1950:
1951: // If the updates map contains any version fields, assume that the
1952: // optimistic lock version data is being handled properly by the
1953: // caller. Otherwise, give the version indicator an opportunity to
1954: // add more update clauses as needed.
1955: boolean augmentUpdates = true;
1956:
1957: for (Iterator i = updateParams.entrySet().iterator(); i
1958: .hasNext();) {
1959: Map.Entry next = (Map.Entry) i.next();
1960: Path path = (Path) next.getKey();
1961: FieldMapping fmd = (FieldMapping) path.last();
1962:
1963: if (fmd.isVersion())
1964: augmentUpdates = false;
1965:
1966: Val val = (Val) next.getValue();
1967:
1968: Column col = fmd.getColumns()[0];
1969: sql.append(col.getName());
1970: sql.append(" = ");
1971:
1972: ExpState state = val.initialize(sel, ctx, 0);
1973: // JDBC Paths are always PCPaths; PCPath implements Val
1974: ExpState pathState = ((Val) path).initialize(sel, ctx, 0);
1975: calculateValue(val, sel, ctx, state, path, pathState);
1976:
1977: // append the value with a null for the Select; i
1978: // indicates that the
1979: int length = val.length(sel, ctx, state);
1980: for (int j = 0; j < length; j++)
1981: val.appendTo((allowAlias) ? sel : null, ctx, state,
1982: sql, j);
1983:
1984: if (i.hasNext())
1985: sql.append(", ");
1986: }
1987:
1988: if (augmentUpdates) {
1989: Path path = (Path) updateParams.keySet().iterator().next();
1990: FieldMapping fm = (FieldMapping) path.last();
1991: ClassMapping meta = fm.getDeclaringMapping();
1992: Map updates = meta.getVersion().getBulkUpdateValues();
1993: for (Iterator iter = updates.entrySet().iterator(); iter
1994: .hasNext();) {
1995: Map.Entry e = (Map.Entry) iter.next();
1996: Column col = (Column) e.getKey();
1997: String val = (String) e.getValue();
1998: sql.append(", ").append(col.getName()).append(" = ")
1999: .append(val);
2000: }
2001: }
2002: }
2003:
2004: /**
2005: * Create SQL to delete the contents of the specified tables.
2006: * The default implementation drops all non-deferred RESTRICT foreign key
2007: * constraints involving the specified tables, issues DELETE statements
2008: * against the tables, and then adds the dropped constraints back in.
2009: * Databases with more optimal ways of deleting the contents of several
2010: * tables should override this method.
2011: */
2012: public String[] getDeleteTableContentsSQL(Table[] tables) {
2013: Collection sql = new ArrayList();
2014:
2015: // collect and drop non-deferred physical restrict constraints, and
2016: // collect the DELETE FROM statements
2017: Collection deleteSQL = new ArrayList(tables.length);
2018: Collection restrictConstraints = new LinkedHashSet();
2019: for (int i = 0; i < tables.length; i++) {
2020: ForeignKey[] fks = tables[i].getForeignKeys();
2021: for (int j = 0; j < fks.length; j++) {
2022: if (!fks[j].isLogical()
2023: && !fks[j].isDeferred()
2024: && fks[j].getDeleteAction() == ForeignKey.ACTION_RESTRICT)
2025: restrictConstraints.add(fks[j]);
2026: String[] constraintSQL = getDropForeignKeySQL(fks[j]);
2027: sql.addAll(Arrays.asList(constraintSQL));
2028: }
2029:
2030: deleteSQL.add("DELETE FROM " + tables[i].getFullName());
2031: }
2032:
2033: // add the delete statements after all the constraint mutations
2034: sql.addAll(deleteSQL);
2035:
2036: // add the deleted constraints back to the schema
2037: for (Iterator iter = restrictConstraints.iterator(); iter
2038: .hasNext();) {
2039: String[] constraintSQL = getAddForeignKeySQL((ForeignKey) iter
2040: .next());
2041: sql.addAll(Arrays.asList(constraintSQL));
2042: }
2043:
2044: return (String[]) sql.toArray(new String[sql.size()]);
2045: }
2046:
2047: /**
2048: * Create a SELECT statement in the proper join syntax for the given
2049: * instance.
2050: */
2051: public SQLBuffer toSelect(Select sel, boolean forUpdate,
2052: JDBCFetchConfiguration fetch) {
2053: sel.addJoinClassConditions();
2054: boolean update = forUpdate && sel.getFromSelect() == null;
2055: SQLBuffer select = getSelects(sel, false, update);
2056: SQLBuffer ordering = null;
2057: if (!sel.isAggregate() || sel.getGrouping() != null)
2058: ordering = sel.getOrdering();
2059: SQLBuffer from;
2060: if (sel.getFromSelect() != null)
2061: from = getFromSelect(sel, forUpdate);
2062: else
2063: from = getFrom(sel, update);
2064: SQLBuffer where = getWhere(sel, update);
2065: return toSelect(select, fetch, from, where, sel.getGrouping(),
2066: sel.getHaving(), ordering, sel.isDistinct(), forUpdate,
2067: sel.getStartIndex(), sel.getEndIndex(), sel);
2068: }
2069:
2070: /**
2071: * Return the portion of the select statement between the FROM keyword
2072: * and the WHERE keyword.
2073: */
2074: protected SQLBuffer getFrom(Select sel, boolean forUpdate) {
2075: SQLBuffer fromSQL = new SQLBuffer(this );
2076: Collection aliases = sel.getTableAliases();
2077: if (aliases.size() < 2 || sel.getJoinSyntax() != SYNTAX_SQL92) {
2078: for (Iterator itr = aliases.iterator(); itr.hasNext();) {
2079: fromSQL.append(itr.next().toString());
2080: if (forUpdate && tableForUpdateClause != null)
2081: fromSQL.append(" ").append(tableForUpdateClause);
2082: if (itr.hasNext())
2083: fromSQL.append(", ");
2084: }
2085: } else {
2086: Iterator itr = sel.getJoinIterator();
2087: boolean first = true;
2088: while (itr.hasNext()) {
2089: fromSQL.append(toSQL92Join((Join) itr.next(),
2090: forUpdate, first));
2091: first = false;
2092: }
2093: }
2094: return fromSQL;
2095: }
2096:
2097: /**
2098: * Return the FROM clause for a select that selects from a tmp table
2099: * created by an inner select.
2100: */
2101: protected SQLBuffer getFromSelect(Select sel, boolean forUpdate) {
2102: SQLBuffer fromSQL = new SQLBuffer(this );
2103: fromSQL.append("(");
2104: fromSQL.append(toSelect(sel.getFromSelect(), forUpdate, null));
2105: fromSQL.append(")");
2106: if (requiresAliasForSubselect)
2107: fromSQL.append(" ").append(Select.FROM_SELECT_ALIAS);
2108: return fromSQL;
2109: }
2110:
2111: /**
2112: * Return the WHERE portion of the select statement, or null if no where
2113: * conditions.
2114: */
2115: protected SQLBuffer getWhere(Select sel, boolean forUpdate) {
2116: Joins joins = sel.getJoins();
2117: if (sel.getJoinSyntax() == SYNTAX_SQL92 || joins == null
2118: || joins.isEmpty())
2119: return sel.getWhere();
2120:
2121: SQLBuffer where = new SQLBuffer(this );
2122: if (sel.getWhere() != null)
2123: where.append(sel.getWhere());
2124: if (joins != null)
2125: sel.append(where, joins);
2126: return where;
2127: }
2128:
2129: /**
2130: * Use the given join instance to create SQL joining its tables in
2131: * the traditional style.
2132: */
2133: public SQLBuffer toTraditionalJoin(Join join) {
2134: ForeignKey fk = join.getForeignKey();
2135: if (fk == null)
2136: return null;
2137:
2138: boolean inverse = join.isForeignKeyInversed();
2139: Column[] from = (inverse) ? fk.getPrimaryKeyColumns() : fk
2140: .getColumns();
2141: Column[] to = (inverse) ? fk.getColumns() : fk
2142: .getPrimaryKeyColumns();
2143:
2144: // do column joins
2145: SQLBuffer buf = new SQLBuffer(this );
2146: int count = 0;
2147: for (int i = 0; i < from.length; i++, count++) {
2148: if (count > 0)
2149: buf.append(" AND ");
2150: buf.append(join.getAlias1()).append(".").append(from[i]);
2151: buf.append(" = ");
2152: buf.append(join.getAlias2()).append(".").append(to[i]);
2153: }
2154:
2155: // do constant joins
2156: Column[] constCols = fk.getConstantColumns();
2157: for (int i = 0; i < constCols.length; i++, count++) {
2158: if (count > 0)
2159: buf.append(" AND ");
2160: if (inverse)
2161: buf.appendValue(fk.getConstant(constCols[i]),
2162: constCols[i]);
2163: else
2164: buf.append(join.getAlias1()).append(".").append(
2165: constCols[i]);
2166: buf.append(" = ");
2167:
2168: if (inverse)
2169: buf.append(join.getAlias2()).append(".").append(
2170: constCols[i]);
2171: else
2172: buf.appendValue(fk.getConstant(constCols[i]),
2173: constCols[i]);
2174: }
2175:
2176: Column[] constColsPK = fk.getConstantPrimaryKeyColumns();
2177: for (int i = 0; i < constColsPK.length; i++, count++) {
2178: if (count > 0)
2179: buf.append(" AND ");
2180: if (inverse)
2181: buf.append(join.getAlias1()).append(".").append(
2182: constColsPK[i]);
2183: else
2184: buf.appendValue(fk
2185: .getPrimaryKeyConstant(constColsPK[i]),
2186: constColsPK[i]);
2187: buf.append(" = ");
2188:
2189: if (inverse)
2190: buf.appendValue(fk
2191: .getPrimaryKeyConstant(constColsPK[i]),
2192: constColsPK[i]);
2193: else
2194: buf.append(join.getAlias2()).append(".").append(
2195: constColsPK[i]);
2196: }
2197: return buf;
2198: }
2199:
2200: /**
2201: * Use the given join instance to create SQL joining its tables in
2202: * the SQL92 style.
2203: */
2204: public SQLBuffer toSQL92Join(Join join, boolean forUpdate,
2205: boolean first) {
2206: SQLBuffer buf = new SQLBuffer(this );
2207: if (first) {
2208: buf.append(join.getTable1()).append(" ").append(
2209: join.getAlias1());
2210: if (forUpdate && tableForUpdateClause != null)
2211: buf.append(" ").append(tableForUpdateClause);
2212: }
2213:
2214: buf.append(" ");
2215: if (join.getType() == Join.TYPE_OUTER)
2216: buf.append(outerJoinClause);
2217: else if (join.getType() == Join.TYPE_INNER)
2218: buf.append(innerJoinClause);
2219: else
2220: // cross
2221: buf.append(crossJoinClause);
2222: buf.append(" ");
2223:
2224: buf.append(join.getTable2()).append(" ").append(
2225: join.getAlias2());
2226: if (forUpdate && tableForUpdateClause != null)
2227: buf.append(" ").append(tableForUpdateClause);
2228:
2229: if (join.getForeignKey() != null)
2230: buf.append(" ON ").append(toTraditionalJoin(join));
2231: else if (requiresConditionForCrossJoin
2232: && join.getType() == Join.TYPE_CROSS)
2233: buf.append(" ON (1 = 1)");
2234:
2235: return buf;
2236: }
2237:
2238: /**
2239: * Use the given join instance to create SQL joining its tables in
2240: * the database's native syntax. Throws an exception by default.
2241: */
2242: public SQLBuffer toNativeJoin(Join join) {
2243: throw new UnsupportedException();
2244: }
2245:
2246: /**
2247: * Returns if the given foreign key can be eagerly loaded using other joins.
2248: */
2249: public boolean canOuterJoin(int syntax, ForeignKey fk) {
2250: return syntax != SYNTAX_TRADITIONAL;
2251: }
2252:
2253: /**
2254: * Combine the given components into a SELECT statement.
2255: */
2256: public SQLBuffer toSelect(SQLBuffer selects,
2257: JDBCFetchConfiguration fetch, SQLBuffer from,
2258: SQLBuffer where, SQLBuffer group, SQLBuffer having,
2259: SQLBuffer order, boolean distinct, boolean forUpdate,
2260: long start, long end) {
2261: return toOperation(getSelectOperation(fetch), selects, from,
2262: where, group, having, order, distinct, start, end,
2263: getForUpdateClause(fetch, forUpdate, null));
2264: }
2265:
2266: /**
2267: * Combine the given components into a SELECT statement.
2268: */
2269: public SQLBuffer toSelect(SQLBuffer selects,
2270: JDBCFetchConfiguration fetch, SQLBuffer from,
2271: SQLBuffer where, SQLBuffer group, SQLBuffer having,
2272: SQLBuffer order, boolean distinct, boolean forUpdate,
2273: long start, long end, boolean subselect) {
2274: return toOperation(getSelectOperation(fetch), selects, from,
2275: where, group, having, order, distinct, start, end,
2276: getForUpdateClause(fetch, forUpdate, null), subselect);
2277: }
2278:
2279: public SQLBuffer toSelect(SQLBuffer selects,
2280: JDBCFetchConfiguration fetch, SQLBuffer from,
2281: SQLBuffer where, SQLBuffer group, SQLBuffer having,
2282: SQLBuffer order, boolean distinct, boolean forUpdate,
2283: long start, long end, boolean subselect,
2284: boolean checkTableForUpdate) {
2285: return toOperation(getSelectOperation(fetch), selects, from,
2286: where, group, having, order, distinct, start, end,
2287: getForUpdateClause(fetch, forUpdate, null), subselect,
2288: checkTableForUpdate);
2289: }
2290:
2291: /**
2292: * Combine the given components into a SELECT statement.
2293: */
2294: public SQLBuffer toSelect(SQLBuffer selects,
2295: JDBCFetchConfiguration fetch, SQLBuffer from,
2296: SQLBuffer where, SQLBuffer group, SQLBuffer having,
2297: SQLBuffer order, boolean distinct, boolean forUpdate,
2298: long start, long end, Select sel) {
2299: return toOperation(getSelectOperation(fetch), selects, from,
2300: where, group, having, order, distinct, start, end,
2301: getForUpdateClause(fetch, forUpdate, sel));
2302: }
2303:
2304: /**
2305: * Get the update clause for the query based on the
2306: * updateClause and isolationLevel hints
2307: */
2308: protected String getForUpdateClause(JDBCFetchConfiguration fetch,
2309: boolean isForUpdate, Select sel) {
2310: if (fetch != null && fetch.getIsolation() != -1) {
2311: throw new InvalidStateException(_loc.get(
2312: "isolation-level-config-not-supported", getClass()
2313: .getName()));
2314: } else if (isForUpdate && !simulateLocking) {
2315: assertSupport(supportsSelectForUpdate,
2316: "SupportsSelectForUpdate");
2317: return forUpdateClause;
2318: } else {
2319: return null;
2320: }
2321: }
2322:
2323: /**
2324: * Return the "SELECT" operation clause, adding any available hints, etc.
2325: */
2326: public String getSelectOperation(JDBCFetchConfiguration fetch) {
2327: return "SELECT";
2328: }
2329:
2330: /**
2331: * Return the SQL for the given selecting operation.
2332: */
2333: public SQLBuffer toOperation(String op, SQLBuffer selects,
2334: SQLBuffer from, SQLBuffer where, SQLBuffer group,
2335: SQLBuffer having, SQLBuffer order, boolean distinct,
2336: long start, long end, String forUpdateClause) {
2337: return toOperation(op, selects, from, where, group, having,
2338: order, distinct, start, end, forUpdateClause, false);
2339: }
2340:
2341: /**
2342: * Return the SQL for the given selecting operation.
2343: */
2344: public SQLBuffer toOperation(String op, SQLBuffer selects,
2345: SQLBuffer from, SQLBuffer where, SQLBuffer group,
2346: SQLBuffer having, SQLBuffer order, boolean distinct,
2347: long start, long end, String forUpdateClause,
2348: boolean subselect) {
2349: return toOperation(op, selects, from, where, group, having,
2350: order, distinct, start, end, forUpdateClause,
2351: subselect, false);
2352: }
2353:
2354: /**
2355: * Return the SQL for the given selecting operation.
2356: */
2357: private SQLBuffer toOperation(String op, SQLBuffer selects,
2358: SQLBuffer from, SQLBuffer where, SQLBuffer group,
2359: SQLBuffer having, SQLBuffer order, boolean distinct,
2360: long start, long end, String forUpdateClause,
2361: boolean subselect, boolean checkTableForUpdate) {
2362: SQLBuffer buf = new SQLBuffer(this );
2363: buf.append(op);
2364:
2365: boolean range = start != 0 || end != Long.MAX_VALUE;
2366: if (range && rangePosition == RANGE_PRE_DISTINCT)
2367: appendSelectRange(buf, start, end, subselect);
2368: if (distinct)
2369: buf.append(" DISTINCT");
2370: if (range && rangePosition == RANGE_POST_DISTINCT)
2371: appendSelectRange(buf, start, end, subselect);
2372:
2373: buf.append(" ").append(selects).append(" FROM ").append(from);
2374:
2375: if (checkTableForUpdate
2376: && (StringUtils.isEmpty(forUpdateClause) && !StringUtils
2377: .isEmpty(tableForUpdateClause))) {
2378: buf.append(" ").append(tableForUpdateClause);
2379: }
2380:
2381: if (where != null && !where.isEmpty())
2382: buf.append(" WHERE ").append(where);
2383: if (group != null && !group.isEmpty())
2384: buf.append(" GROUP BY ").append(group);
2385: if (having != null && !having.isEmpty()) {
2386: assertSupport(supportsHaving, "SupportsHaving");
2387: buf.append(" HAVING ").append(having);
2388: }
2389: if (order != null && !order.isEmpty())
2390: buf.append(" ORDER BY ").append(order);
2391: if (range && rangePosition == RANGE_POST_SELECT)
2392: appendSelectRange(buf, start, end, subselect);
2393: if (forUpdateClause != null)
2394: buf.append(" ").append(forUpdateClause);
2395: if (range && rangePosition == RANGE_POST_LOCK)
2396: appendSelectRange(buf, start, end, subselect);
2397: return buf;
2398: }
2399:
2400: /**
2401: * If this dictionary can select ranges,
2402: * use this method to append the range SQL.
2403: */
2404: protected void appendSelectRange(SQLBuffer buf, long start,
2405: long end, boolean subselect) {
2406: }
2407:
2408: /**
2409: * Return the portion of the select statement between the SELECT keyword
2410: * and the FROM keyword.
2411: */
2412: protected SQLBuffer getSelects(Select sel,
2413: boolean distinctIdentifiers, boolean forUpdate) {
2414: // append the aliases for all the columns
2415: SQLBuffer selectSQL = new SQLBuffer(this );
2416: List aliases;
2417: if (distinctIdentifiers)
2418: aliases = sel.getIdentifierAliases();
2419: else
2420: aliases = sel.getSelectAliases();
2421:
2422: Object alias;
2423: for (int i = 0; i < aliases.size(); i++) {
2424: alias = aliases.get(i);
2425: appendSelect(selectSQL, alias, sel, i);
2426: if (i < aliases.size() - 1)
2427: selectSQL.append(", ");
2428: }
2429: return selectSQL;
2430: }
2431:
2432: /**
2433: * Append <code>elem</code> to <code>selectSQL</code>.
2434: * @param selectSQL The SQLBuffer to append to.
2435: * @param alias A {@link SQLBuffer} or a {@link String} to append.
2436: *
2437: * @since 1.1.0
2438: */
2439: protected void appendSelect(SQLBuffer selectSQL, Object elem,
2440: Select sel, int idx) {
2441: if (elem instanceof SQLBuffer)
2442: selectSQL.append((SQLBuffer) elem);
2443: else
2444: selectSQL.append(elem.toString());
2445: }
2446:
2447: /**
2448: * Returns true if a "FOR UPDATE" clause can be used for the specified
2449: * Select object.
2450: */
2451: public boolean supportsLocking(Select sel) {
2452: if (sel.isAggregate())
2453: return false;
2454: if (!supportsSelectForUpdate)
2455: return false;
2456: if (!supportsLockingWithSelectRange
2457: && (sel.getStartIndex() != 0 || sel.getEndIndex() != Long.MAX_VALUE))
2458: return false;
2459:
2460: // only inner select is locked
2461: if (sel.getFromSelect() != null)
2462: sel = sel.getFromSelect();
2463:
2464: if (!supportsLockingWithDistinctClause && sel.isDistinct())
2465: return false;
2466: if (!supportsLockingWithMultipleTables
2467: && sel.getTableAliases().size() > 1)
2468: return false;
2469: if (!supportsLockingWithOrderClause
2470: && sel.getOrdering() != null)
2471: return false;
2472: if (!supportsLockingWithOuterJoin
2473: || !supportsLockingWithInnerJoin) {
2474: for (Iterator itr = sel.getJoinIterator(); itr.hasNext();) {
2475: Join join = (Join) itr.next();
2476: if (!supportsLockingWithOuterJoin
2477: && join.getType() == Join.TYPE_OUTER)
2478: return false;
2479: if (!supportsLockingWithInnerJoin
2480: && join.getType() == Join.TYPE_INNER)
2481: return false;
2482: }
2483: }
2484: return true;
2485: }
2486:
2487: /**
2488: * Return false if the given select requires a forward-only result set.
2489: */
2490: public boolean supportsRandomAccessResultSet(Select sel,
2491: boolean forUpdate) {
2492: return !sel.isAggregate();
2493: }
2494:
2495: /**
2496: * Assert that the given dictionary flag is true. If it is not true,
2497: * throw an error saying that the given setting needs to return true for
2498: * the current operation to work.
2499: */
2500: public void assertSupport(boolean feature, String property) {
2501: if (!feature)
2502: throw new UnsupportedException(_loc.get(
2503: "feature-not-supported", getClass(), property));
2504: }
2505:
2506: ////////////////////
2507: // Query functions
2508: ////////////////////
2509:
2510: /**
2511: * Invoke this database's substring function.
2512: *
2513: * @param buf the SQL buffer to write the substring invocation to
2514: * @param str a query value representing the target string
2515: * @param start a query value representing the start index
2516: * @param end a query value representing the end index, or null for none
2517: */
2518: public void substring(SQLBuffer buf, FilterValue str,
2519: FilterValue start, FilterValue end) {
2520: buf.append(substringFunctionName).append("(");
2521: str.appendTo(buf);
2522: buf.append(", ");
2523: if (start.getValue() instanceof Number) {
2524: long startLong = toLong(start);
2525: buf.append(Long.toString(startLong + 1));
2526: } else {
2527: buf.append("(");
2528: start.appendTo(buf);
2529: buf.append(" + 1)");
2530: }
2531: if (end != null) {
2532: buf.append(", ");
2533: if (start.getValue() instanceof Number
2534: && end.getValue() instanceof Number) {
2535: long startLong = toLong(start);
2536: long endLong = toLong(end);
2537: buf.append(Long.toString(endLong - startLong));
2538: } else {
2539: end.appendTo(buf);
2540: buf.append(" - (");
2541: start.appendTo(buf);
2542: buf.append(")");
2543: }
2544: }
2545: buf.append(")");
2546: }
2547:
2548: long toLong(FilterValue litValue) {
2549: return ((Number) litValue.getValue()).longValue();
2550: }
2551:
2552: /**
2553: * Invoke this database's indexOf function.
2554: *
2555: * @param buf the SQL buffer to write the indexOf invocation to
2556: * @param str a query value representing the target string
2557: * @param find a query value representing the search string
2558: * @param start a query value representing the start index, or null
2559: * to start at the beginning
2560: */
2561: public void indexOf(SQLBuffer buf, FilterValue str,
2562: FilterValue find, FilterValue start) {
2563: buf.append("(INSTR((");
2564: if (start != null)
2565: substring(buf, str, start, null);
2566: else
2567: str.appendTo(buf);
2568: buf.append("), (");
2569: find.appendTo(buf);
2570: buf.append(")) - 1");
2571: if (start != null) {
2572: buf.append(" + ");
2573: start.appendTo(buf);
2574: }
2575: buf.append(")");
2576: }
2577:
2578: /**
2579: * Append the numeric parts of a mathematical function.
2580: *
2581: * @param buf the SQL buffer to write the math function
2582: * @param op the mathematical operation to perform
2583: * @param lhs the left hand side of the math function
2584: * @param rhs the right hand side of the math function
2585: */
2586: public void mathFunction(SQLBuffer buf, String op, FilterValue lhs,
2587: FilterValue rhs) {
2588: boolean castlhs = false;
2589: boolean castrhs = false;
2590: Class lc = Filters.wrap(lhs.getType());
2591: Class rc = Filters.wrap(rhs.getType());
2592: int type = 0;
2593: if (requiresCastForMathFunctions
2594: && (lc != rc || (lhs.isConstant() && rhs.isConstant()))) {
2595: Class c = Filters.promote(lc, rc);
2596: type = getJDBCType(JavaTypes.getTypeCode(c), false);
2597: if (type != Types.VARBINARY && type != Types.BLOB) {
2598: castlhs = (lhs.isConstant() && rhs.isConstant())
2599: || lc != c;
2600: castrhs = (lhs.isConstant() && rhs.isConstant())
2601: || rc != c;
2602: }
2603: }
2604:
2605: boolean mod = "MOD".equals(op);
2606: if (mod) {
2607: if (supportsModOperator)
2608: op = "%";
2609: else
2610: buf.append(op);
2611: }
2612: buf.append("(");
2613:
2614: if (castlhs)
2615: appendCast(buf, lhs, type);
2616: else
2617: lhs.appendTo(buf);
2618:
2619: if (mod && !supportsModOperator)
2620: buf.append(", ");
2621: else
2622: buf.append(" ").append(op).append(" ");
2623:
2624: if (castrhs)
2625: appendCast(buf, rhs, type);
2626: else
2627: rhs.appendTo(buf);
2628:
2629: buf.append(")");
2630: }
2631:
2632: /**
2633: * Append a comparison.
2634: *
2635: * @param buf the SQL buffer to write the comparison
2636: * @param op the comparison operation to perform
2637: * @param lhs the left hand side of the comparison
2638: * @param rhs the right hand side of the comparison
2639: */
2640: public void comparison(SQLBuffer buf, String op, FilterValue lhs,
2641: FilterValue rhs) {
2642: boolean lhsxml = lhs.getXPath() != null;
2643: boolean rhsxml = rhs.getXPath() != null;
2644: if (lhsxml || rhsxml) {
2645: appendXmlComparison(buf, op, lhs, rhs, lhsxml, rhsxml);
2646: return;
2647: }
2648: boolean castlhs = false;
2649: boolean castrhs = false;
2650: Class lc = Filters.wrap(lhs.getType());
2651: Class rc = Filters.wrap(rhs.getType());
2652: int type = 0;
2653: if (requiresCastForComparisons
2654: && (lc != rc || (lhs.isConstant() && rhs.isConstant()))) {
2655: Class c = Filters.promote(lc, rc);
2656: type = getJDBCType(JavaTypes.getTypeCode(c), false);
2657: if (type != Types.VARBINARY && type != Types.BLOB) {
2658: castlhs = (lhs.isConstant() && rhs.isConstant())
2659: || lc != c;
2660: castrhs = (lhs.isConstant() && rhs.isConstant())
2661: || rc != c;
2662: }
2663: }
2664:
2665: if (castlhs)
2666: appendCast(buf, lhs, type);
2667: else
2668: lhs.appendTo(buf);
2669:
2670: buf.append(" ").append(op).append(" ");
2671:
2672: if (castrhs)
2673: appendCast(buf, rhs, type);
2674: else
2675: rhs.appendTo(buf);
2676: }
2677:
2678: /**
2679: * If this dictionary supports XML type,
2680: * use this method to append xml predicate.
2681: */
2682: public void appendXmlComparison(SQLBuffer buf, String op,
2683: FilterValue lhs, FilterValue rhs, boolean lhsxml,
2684: boolean rhsxml) {
2685: assertSupport(supportsXMLColumn, "SupportsXMLColumn");
2686: }
2687:
2688: /**
2689: * Append SQL for the given numeric value to the buffer, casting as needed.
2690: */
2691: protected void appendNumericCast(SQLBuffer buf, FilterValue val) {
2692: if (val.isConstant())
2693: appendCast(buf, val, Types.NUMERIC);
2694: else
2695: val.appendTo(buf);
2696: }
2697:
2698: /**
2699: * Cast the specified value to the specified type.
2700: *
2701: * @param buf the buffer to append the cast to
2702: * @param val the value to cast
2703: * @param type the type of the case, e.g. {@link Types#NUMERIC}
2704: */
2705: public void appendCast(SQLBuffer buf, Object val, int type) {
2706: // Convert the cast function: "CAST({0} AS {1})"
2707: int firstParam = castFunction.indexOf("{0}");
2708: String pre = castFunction.substring(0, firstParam); // "CAST("
2709: String mid = castFunction.substring(firstParam + 3);
2710: int secondParam = mid.indexOf("{1}");
2711: String post;
2712: if (secondParam > -1) {
2713: post = mid.substring(secondParam + 3); // ")"
2714: mid = mid.substring(0, secondParam); // " AS "
2715: } else
2716: post = "";
2717:
2718: buf.append(pre);
2719: if (val instanceof FilterValue)
2720: ((FilterValue) val).appendTo(buf);
2721: else if (val instanceof SQLBuffer)
2722: buf.append(((SQLBuffer) val));
2723: else
2724: buf.append(val.toString());
2725: buf.append(mid);
2726: buf.append(getTypeName(type));
2727: appendLength(buf, type);
2728: buf.append(post);
2729: }
2730:
2731: protected void appendLength(SQLBuffer buf, int type) {
2732: }
2733:
2734: /**
2735: * add CAST for a function operator where operand is a param
2736: * @param func function name
2737: * @param val
2738: * @return updated func
2739: */
2740: public String addCastAsType(String func, Val val) {
2741: return null;
2742: }
2743:
2744: ///////////
2745: // DDL SQL
2746: ///////////
2747:
2748: /**
2749: * Increment the reference count of any table components that this
2750: * dictionary adds that are not used by mappings. Does nothing by default.
2751: */
2752: public void refSchemaComponents(Table table) {
2753: }
2754:
2755: /**
2756: * Returns the full name of the table, including the schema (delimited
2757: * by {@link #catalogSeparator}).
2758: */
2759: public String getFullName(Table table, boolean logical) {
2760: if (!useSchemaName || table.getSchemaName() == null)
2761: return table.getName();
2762: if (logical || ".".equals(catalogSeparator))
2763: return table.getFullName();
2764: return table.getSchemaName() + catalogSeparator
2765: + table.getName();
2766: }
2767:
2768: /**
2769: * Returns the full name of the index, including the schema (delimited
2770: * by the result of {@link #catalogSeparator}).
2771: */
2772: public String getFullName(Index index) {
2773: if (!useSchemaName || index.getSchemaName() == null)
2774: return index.getName();
2775: if (".".equals(catalogSeparator))
2776: return index.getFullName();
2777: return index.getSchemaName() + catalogSeparator
2778: + index.getName();
2779: }
2780:
2781: /**
2782: * Returns the full name of the sequence, including the schema (delimited
2783: * by the result of {@link #catalogSeparator}).
2784: */
2785: public String getFullName(Sequence seq) {
2786: if (!useSchemaName || seq.getSchemaName() == null)
2787: return seq.getName();
2788: if (".".equals(catalogSeparator))
2789: return seq.getFullName();
2790: return seq.getSchemaName() + catalogSeparator + seq.getName();
2791: }
2792:
2793: /**
2794: * Make any necessary changes to the given table name to make it valid
2795: * for the current DB.
2796: */
2797: public String getValidTableName(String name, Schema schema) {
2798: while (name.startsWith("_"))
2799: name = name.substring(1);
2800: return makeNameValid(name, schema.getSchemaGroup(),
2801: maxTableNameLength, NAME_TABLE);
2802: }
2803:
2804: /**
2805: * Make any necessary changes to the given sequence name to make it valid
2806: * for the current DB.
2807: */
2808: public String getValidSequenceName(String name, Schema schema) {
2809: while (name.startsWith("_"))
2810: name = name.substring(1);
2811: return makeNameValid("S_" + name, schema.getSchemaGroup(),
2812: maxTableNameLength, NAME_SEQUENCE);
2813: }
2814:
2815: /**
2816: * Make any necessary changes to the given column name to make it valid
2817: * for the current DB.
2818: */
2819: public String getValidColumnName(String name, Table table) {
2820: while (name.startsWith("_"))
2821: name = name.substring(1);
2822: return makeNameValid(name, table, maxColumnNameLength, NAME_ANY);
2823: }
2824:
2825: /**
2826: * Make any necessary changes to the given primary key name to make it
2827: * valid for the current DB.
2828: */
2829: public String getValidPrimaryKeyName(String name, Table table) {
2830: while (name.startsWith("_"))
2831: name = name.substring(1);
2832: return makeNameValid("P_" + name, table.getSchema()
2833: .getSchemaGroup(), maxConstraintNameLength, NAME_ANY);
2834: }
2835:
2836: /**
2837: * Make any necessary changes to the given foreign key name to make it
2838: * valid for the current DB.
2839: */
2840: public String getValidForeignKeyName(String name, Table table,
2841: Table toTable) {
2842: while (name.startsWith("_"))
2843: name = name.substring(1);
2844: String tableName = table.getName();
2845: int len = Math.min(tableName.length(), 7);
2846: name = "F_" + shorten(tableName, len) + "_" + name;
2847: return makeNameValid(name, table.getSchema().getSchemaGroup(),
2848: maxConstraintNameLength, NAME_ANY);
2849: }
2850:
2851: /**
2852: * Make any necessary changes to the given index name to make it valid
2853: * for the current DB.
2854: */
2855: public String getValidIndexName(String name, Table table) {
2856: while (name.startsWith("_"))
2857: name = name.substring(1);
2858: String tableName = table.getName();
2859: int len = Math.min(tableName.length(), 7);
2860: name = "I_" + shorten(tableName, len) + "_" + name;
2861: return makeNameValid(name, table.getSchema().getSchemaGroup(),
2862: maxIndexNameLength, NAME_ANY);
2863: }
2864:
2865: /**
2866: * Make any necessary changes to the given unique constraint name to make
2867: * it valid for the current DB.
2868: */
2869: public String getValidUniqueName(String name, Table table) {
2870: while (name.startsWith("_"))
2871: name = name.substring(1);
2872: String tableName = table.getName();
2873: int len = Math.min(tableName.length(), 7);
2874: name = "U_" + shorten(tableName, len) + "_" + name;
2875: return makeNameValid(name, table.getSchema().getSchemaGroup(),
2876: maxConstraintNameLength, NAME_ANY);
2877: }
2878:
2879: /**
2880: * Shorten the specified name to the specified target name. This will
2881: * be done by first stripping out the vowels, and then removing
2882: * characters from the middle of the word until it reaches the target
2883: * length.
2884: */
2885: protected static String shorten(String name, int targetLength) {
2886: if (name == null || name.length() <= targetLength)
2887: return name;
2888:
2889: StringBuffer nm = new StringBuffer(name);
2890: while (nm.length() > targetLength) {
2891: if (!stripVowel(nm)) {
2892: // cut out the middle char
2893: nm.replace(nm.length() / 2, (nm.length() / 2) + 1, "");
2894: }
2895: }
2896: return nm.toString();
2897: }
2898:
2899: /**
2900: * Remove vowels from the specified StringBuffer.
2901: *
2902: * @return true if any vowels have been removed
2903: */
2904: private static boolean stripVowel(StringBuffer name) {
2905: if (name == null || name.length() == 0)
2906: return false;
2907:
2908: char[] vowels = { 'A', 'E', 'I', 'O', 'U', };
2909: for (int i = 0; i < vowels.length; i++) {
2910: int index = name.toString().toUpperCase()
2911: .indexOf(vowels[i]);
2912: if (index != -1) {
2913: name.replace(index, index + 1, "");
2914: return true;
2915: }
2916: }
2917: return false;
2918: }
2919:
2920: /**
2921: * Shortens the given name to the given maximum length, then checks that
2922: * it is not a reserved word. If it is reserved, appends a "0". If
2923: * the name conflicts with an existing schema component, the last
2924: * character is replace with '0', then '1', etc.
2925: * Note that the given max len may be 0 if the database metadata is
2926: * incomplete.
2927: */
2928: protected String makeNameValid(String name, NameSet set,
2929: int maxLen, int nameType) {
2930: if (maxLen < 1)
2931: maxLen = 255;
2932: if (name.length() > maxLen)
2933: name = name.substring(0, maxLen);
2934: if (reservedWordSet.contains(name.toUpperCase())) {
2935: if (name.length() == maxLen)
2936: name = name.substring(0, name.length() - 1);
2937: name += "0";
2938: }
2939:
2940: // now make sure the name is unique
2941: if (set != null) {
2942: outer: for (int version = 1, chars = 1; true; version++) {
2943: // for table names, we check for the table itself in case the
2944: // name set is lazy about schema reflection
2945: switch (nameType) {
2946: case NAME_TABLE:
2947: if (!((SchemaGroup) set).isKnownTable(name))
2948: break outer;
2949: break;
2950: case NAME_SEQUENCE:
2951: if (!((SchemaGroup) set).isKnownSequence(name))
2952: break outer;
2953: break;
2954: default:
2955: if (!set.isNameTaken(name))
2956: break outer;
2957: }
2958:
2959: // a single char for the version is probably enough, but might
2960: // as well be general about it...
2961: if (version > 1)
2962: name = name.substring(0, name.length() - chars);
2963: if (version >= Math.pow(10, chars))
2964: chars++;
2965: if (name.length() + chars > maxLen)
2966: name = name.substring(0, maxLen - chars);
2967: name = name + version;
2968: }
2969: }
2970: return name.toUpperCase();
2971: }
2972:
2973: /**
2974: * Return a series of SQL statements to create the given table, complete
2975: * with columns. Indexes and constraints will be created separately.
2976: */
2977: public String[] getCreateTableSQL(Table table) {
2978: StringBuffer buf = new StringBuffer();
2979: buf.append("CREATE TABLE ").append(getFullName(table, false));
2980: if (supportsComments && table.hasComment()) {
2981: buf.append(" ");
2982: comment(buf, table.getComment());
2983: buf.append("\n (");
2984: } else {
2985: buf.append(" (");
2986: }
2987:
2988: // do this before getting the columns so we know how to handle
2989: // the last comma
2990: StringBuffer endBuf = new StringBuffer();
2991: PrimaryKey pk = table.getPrimaryKey();
2992: String pkStr;
2993: if (pk != null) {
2994: pkStr = getPrimaryKeyConstraintSQL(pk);
2995: if (pkStr != null)
2996: endBuf.append(pkStr);
2997: }
2998:
2999: Unique[] unqs = table.getUniques();
3000: String unqStr;
3001: for (int i = 0; i < unqs.length; i++) {
3002: unqStr = getUniqueConstraintSQL(unqs[i]);
3003: if (unqStr != null) {
3004: if (endBuf.length() > 0)
3005: endBuf.append(", ");
3006: endBuf.append(unqStr);
3007: }
3008: }
3009:
3010: Column[] cols = table.getColumns();
3011: for (int i = 0; i < cols.length; i++) {
3012: buf.append(getDeclareColumnSQL(cols[i], false));
3013: if (i < cols.length - 1 || endBuf.length() > 0)
3014: buf.append(", ");
3015: if (supportsComments && cols[i].hasComment()) {
3016: comment(buf, cols[i].getComment());
3017: buf.append("\n ");
3018: }
3019: }
3020:
3021: buf.append(endBuf.toString());
3022: buf.append(")");
3023: return new String[] { buf.toString() };
3024: }
3025:
3026: protected StringBuffer comment(StringBuffer buf, String comment) {
3027: return buf.append("-- ").append(comment);
3028: }
3029:
3030: /**
3031: * Return a series of SQL statements to drop the given table. Indexes
3032: * will be dropped separately. Returns
3033: * <code>DROP TABLE <table name></code> by default.
3034: */
3035: public String[] getDropTableSQL(Table table) {
3036: String drop = MessageFormat.format(dropTableSQL,
3037: new Object[] { getFullName(table, false) });
3038: return new String[] { drop };
3039: }
3040:
3041: /**
3042: * Return a series of SQL statements to create the given sequence. Returns
3043: * <code>CREATE SEQUENCE <sequence name>[ START WITH <start>]
3044: * [ INCREMENT BY <increment>]</code> by default.
3045: */
3046: public String[] getCreateSequenceSQL(Sequence seq) {
3047: if (nextSequenceQuery == null)
3048: return null;
3049:
3050: StringBuffer buf = new StringBuffer();
3051: buf.append("CREATE SEQUENCE ");
3052: buf.append(getFullName(seq));
3053: if (seq.getInitialValue() != 0)
3054: buf.append(" START WITH ").append(seq.getInitialValue());
3055: if (seq.getIncrement() > 1)
3056: buf.append(" INCREMENT BY ").append(seq.getIncrement());
3057: return new String[] { buf.toString() };
3058: }
3059:
3060: /**
3061: * Return a series of SQL statements to drop the given sequence. Returns
3062: * <code>DROP SEQUENCE <sequence name></code> by default.
3063: */
3064: public String[] getDropSequenceSQL(Sequence seq) {
3065: return new String[] { "DROP SEQUENCE " + getFullName(seq) };
3066: }
3067:
3068: /**
3069: * Return a series of SQL statements to create the given index. Returns
3070: * <code>CREATE [UNIQUE] INDEX <index name> ON <table name>
3071: * (<col list>)</code> by default.
3072: */
3073: public String[] getCreateIndexSQL(Index index) {
3074: StringBuffer buf = new StringBuffer();
3075: buf.append("CREATE ");
3076: if (index.isUnique())
3077: buf.append("UNIQUE ");
3078: buf.append("INDEX ").append(index.getName());
3079:
3080: buf.append(" ON ").append(getFullName(index.getTable(), false));
3081: buf.append(" (").append(Strings.join(index.getColumns(), ", "))
3082: .append(")");
3083:
3084: return new String[] { buf.toString() };
3085: }
3086:
3087: /**
3088: * Return a series of SQL statements to drop the given index. Returns
3089: * <code>DROP INDEX <index name></code> by default.
3090: */
3091: public String[] getDropIndexSQL(Index index) {
3092: return new String[] { "DROP INDEX " + getFullName(index) };
3093: }
3094:
3095: /**
3096: * Return a series of SQL statements to add the given column to
3097: * its table. Return an empty array if operation not supported. Returns
3098: * <code>ALTER TABLE <table name> ADD (<col dec>)</code>
3099: * by default.
3100: */
3101: public String[] getAddColumnSQL(Column column) {
3102: if (!supportsAlterTableWithAddColumn)
3103: return new String[0];
3104:
3105: String dec = getDeclareColumnSQL(column, true);
3106: if (dec == null)
3107: return new String[0];
3108: return new String[] { "ALTER TABLE "
3109: + getFullName(column.getTable(), false) + " ADD " + dec };
3110: }
3111:
3112: /**
3113: * Return a series of SQL statements to drop the given column from
3114: * its table. Return an empty array if operation not supported. Returns
3115: * <code>ALTER TABLE <table name> DROP COLUMN <col name></code>
3116: * by default.
3117: */
3118: public String[] getDropColumnSQL(Column column) {
3119: if (!supportsAlterTableWithDropColumn)
3120: return new String[0];
3121: return new String[] { "ALTER TABLE "
3122: + getFullName(column.getTable(), false)
3123: + " DROP COLUMN " + column };
3124: }
3125:
3126: /**
3127: * Return a series of SQL statements to add the given primary key to
3128: * its table. Return an empty array if operation not supported.
3129: * Returns <code>ALTER TABLE <table name> ADD
3130: * <pk cons sql ></code> by default.
3131: */
3132: public String[] getAddPrimaryKeySQL(PrimaryKey pk) {
3133: String pksql = getPrimaryKeyConstraintSQL(pk);
3134: if (pksql == null)
3135: return new String[0];
3136: return new String[] { "ALTER TABLE "
3137: + getFullName(pk.getTable(), false) + " ADD " + pksql };
3138: }
3139:
3140: /**
3141: * Return a series of SQL statements to drop the given primary key from
3142: * its table. Return an empty array if operation not supported.
3143: * Returns <code>ALTER TABLE <table name> DROP CONSTRAINT
3144: * <pk name></code> by default.
3145: */
3146: public String[] getDropPrimaryKeySQL(PrimaryKey pk) {
3147: if (pk.getName() == null)
3148: return new String[0];
3149: return new String[] { "ALTER TABLE "
3150: + getFullName(pk.getTable(), false)
3151: + " DROP CONSTRAINT " + pk.getName() };
3152: }
3153:
3154: /**
3155: * Return a series of SQL statements to add the given foreign key to
3156: * its table. Return an empty array if operation not supported.
3157: * Returns <code>ALTER TABLE <table name> ADD
3158: * <fk cons sql ></code> by default.
3159: */
3160: public String[] getAddForeignKeySQL(ForeignKey fk) {
3161: String fkSQL = getForeignKeyConstraintSQL(fk);
3162: if (fkSQL == null)
3163: return new String[0];
3164: return new String[] { "ALTER TABLE "
3165: + getFullName(fk.getTable(), false) + " ADD " + fkSQL };
3166: }
3167:
3168: /**
3169: * Return a series of SQL statements to drop the given foreign key from
3170: * its table. Return an empty array if operation not supported.
3171: * Returns <code>ALTER TABLE <table name> DROP CONSTRAINT
3172: * <fk name></code> by default.
3173: */
3174: public String[] getDropForeignKeySQL(ForeignKey fk) {
3175: if (fk.getName() == null)
3176: return new String[0];
3177: return new String[] { "ALTER TABLE "
3178: + getFullName(fk.getTable(), false)
3179: + " DROP CONSTRAINT " + fk.getName() };
3180: }
3181:
3182: /**
3183: * Return the declaration SQL for the given column. This method is used
3184: * for each column from within {@link #getCreateTableSQL} and
3185: * {@link #getAddColumnSQL}.
3186: */
3187: protected String getDeclareColumnSQL(Column col, boolean alter) {
3188: StringBuffer buf = new StringBuffer();
3189: buf.append(col).append(" ");
3190: buf.append(getTypeName(col));
3191:
3192: // can't add constraints to a column we're adding after table
3193: // creation, cause some data might already be inserted
3194: if (!alter) {
3195: if (col.getDefaultString() != null && !col.isAutoAssigned())
3196: buf.append(" DEFAULT ").append(col.getDefaultString());
3197: if (col.isNotNull())
3198: buf.append(" NOT NULL");
3199: }
3200: if (col.isAutoAssigned()) {
3201: if (!supportsAutoAssign)
3202: log.warn(_loc.get("invalid-autoassign", platform, col));
3203: else if (autoAssignClause != null)
3204: buf.append(" ").append(autoAssignClause);
3205: }
3206: return buf.toString();
3207: }
3208:
3209: /**
3210: * Return the declaration SQL for the given primary key. This method is
3211: * used from within {@link #getCreateTableSQL} and
3212: * {@link #getAddPrimaryKeySQL}. Returns
3213: * <code>CONSTRAINT <pk name> PRIMARY KEY (<col list>)</code>
3214: * by default.
3215: */
3216: protected String getPrimaryKeyConstraintSQL(PrimaryKey pk) {
3217: // if we have disabled the creation of primary keys, abort here
3218: if (!createPrimaryKeys)
3219: return null;
3220:
3221: String name = pk.getName();
3222: if (name != null
3223: && reservedWordSet.contains(name.toUpperCase()))
3224: name = null;
3225:
3226: StringBuffer buf = new StringBuffer();
3227: if (name != null && CONS_NAME_BEFORE.equals(constraintNameMode))
3228: buf.append("CONSTRAINT ").append(name).append(" ");
3229: buf.append("PRIMARY KEY ");
3230: if (name != null && CONS_NAME_MID.equals(constraintNameMode))
3231: buf.append(name).append(" ");
3232: buf.append("(").append(Strings.join(pk.getColumns(), ", "))
3233: .append(")");
3234: if (name != null && CONS_NAME_AFTER.equals(constraintNameMode))
3235: buf.append(" CONSTRAINT ").append(name);
3236: return buf.toString();
3237: }
3238:
3239: /**
3240: * Return the declaration SQL for the given foreign key, or null if it is
3241: * not supported. This method is used from within
3242: * {@link #getCreateTableSQL} and {@link #getAddForeignKeySQL}. Returns
3243: * <code>CONSTRAINT <cons name> FOREIGN KEY (<col list>)
3244: * REFERENCES <foreign table> (<col list>)
3245: * [ON DELETE <action>] [ON UPDATE <action>]</code> by default.
3246: */
3247: protected String getForeignKeyConstraintSQL(ForeignKey fk) {
3248: if (!supportsForeignKeys)
3249: return null;
3250: if (fk.getDeleteAction() == ForeignKey.ACTION_NONE)
3251: return null;
3252: if (fk.isDeferred() && !supportsDeferredForeignKeyConstraints())
3253: return null;
3254: if (!supportsDeleteAction(fk.getDeleteAction())
3255: || !supportsUpdateAction(fk.getUpdateAction()))
3256: return null;
3257:
3258: Column[] locals = fk.getColumns();
3259: Column[] foreigns = fk.getPrimaryKeyColumns();
3260:
3261: int delActionId = fk.getDeleteAction();
3262: if (delActionId == ForeignKey.ACTION_NULL) {
3263: for (int i = 0; i < locals.length; i++) {
3264: if (locals[i].isNotNull())
3265: delActionId = ForeignKey.ACTION_NONE;
3266: }
3267: }
3268:
3269: String delAction = getActionName(delActionId);
3270: String upAction = getActionName(fk.getUpdateAction());
3271:
3272: StringBuffer buf = new StringBuffer();
3273: if (fk.getName() != null
3274: && CONS_NAME_BEFORE.equals(constraintNameMode))
3275: buf.append("CONSTRAINT ").append(fk.getName()).append(" ");
3276: buf.append("FOREIGN KEY ");
3277: if (fk.getName() != null
3278: && CONS_NAME_MID.equals(constraintNameMode))
3279: buf.append(fk.getName()).append(" ");
3280: buf.append("(").append(Strings.join(locals, ", ")).append(")");
3281: buf.append(" REFERENCES ");
3282: buf.append(getFullName(foreigns[0].getTable(), false));
3283: buf.append(" (").append(Strings.join(foreigns, ", ")).append(
3284: ")");
3285: if (delAction != null)
3286: buf.append(" ON DELETE ").append(delAction);
3287: if (upAction != null)
3288: buf.append(" ON UPDATE ").append(upAction);
3289: if (fk.isDeferred())
3290: buf.append(" INITIALLY DEFERRED");
3291: if (supportsDeferredForeignKeyConstraints())
3292: buf.append(" DEFERRABLE");
3293: if (fk.getName() != null
3294: && CONS_NAME_AFTER.equals(constraintNameMode))
3295: buf.append(" CONSTRAINT ").append(fk.getName());
3296: return buf.toString();
3297: }
3298:
3299: /**
3300: * Whether or not this dictionary supports deferred foreign key constraints.
3301: * This implementation returns {@link #supportsUniqueConstraints}.
3302: *
3303: * @since 1.1.0
3304: */
3305: protected boolean supportsDeferredForeignKeyConstraints() {
3306: return supportsDeferredConstraints;
3307: }
3308:
3309: /**
3310: * Return the name of the given foreign key action.
3311: */
3312: private String getActionName(int action) {
3313: switch (action) {
3314: case ForeignKey.ACTION_CASCADE:
3315: return "CASCADE";
3316: case ForeignKey.ACTION_NULL:
3317: return "SET NULL";
3318: case ForeignKey.ACTION_DEFAULT:
3319: return "SET DEFAULT";
3320: default:
3321: return null;
3322: }
3323: }
3324:
3325: /**
3326: * Whether this database supports the given foreign key delete action.
3327: */
3328: public boolean supportsDeleteAction(int action) {
3329: if (action == ForeignKey.ACTION_NONE)
3330: return true;
3331: if (!supportsForeignKeys)
3332: return false;
3333: switch (action) {
3334: case ForeignKey.ACTION_RESTRICT:
3335: return supportsRestrictDeleteAction;
3336: case ForeignKey.ACTION_CASCADE:
3337: return supportsCascadeDeleteAction;
3338: case ForeignKey.ACTION_NULL:
3339: return supportsNullDeleteAction;
3340: case ForeignKey.ACTION_DEFAULT:
3341: return supportsDefaultDeleteAction;
3342: default:
3343: return false;
3344: }
3345: }
3346:
3347: /**
3348: * Whether this database supports the given foreign key update action.
3349: */
3350: public boolean supportsUpdateAction(int action) {
3351: if (action == ForeignKey.ACTION_NONE)
3352: return true;
3353: if (!supportsForeignKeys)
3354: return false;
3355: switch (action) {
3356: case ForeignKey.ACTION_RESTRICT:
3357: return supportsRestrictUpdateAction;
3358: case ForeignKey.ACTION_CASCADE:
3359: return supportsCascadeUpdateAction;
3360: case ForeignKey.ACTION_NULL:
3361: return supportsNullUpdateAction;
3362: case ForeignKey.ACTION_DEFAULT:
3363: return supportsDefaultUpdateAction;
3364: default:
3365: return false;
3366: }
3367: }
3368:
3369: /**
3370: * Return the declaration SQL for the given unique constraint. This
3371: * method is used from within {@link #getCreateTableSQL}.
3372: * Returns <code>CONSTRAINT <name> UNIQUE (<col list>)</code>
3373: * by default.
3374: */
3375: protected String getUniqueConstraintSQL(Unique unq) {
3376: if (!supportsUniqueConstraints
3377: || (unq.isDeferred() && !supportsDeferredUniqueConstraints()))
3378: return null;
3379:
3380: StringBuffer buf = new StringBuffer();
3381: if (unq.getName() != null
3382: && CONS_NAME_BEFORE.equals(constraintNameMode))
3383: buf.append("CONSTRAINT ").append(unq.getName()).append(" ");
3384: buf.append("UNIQUE ");
3385: if (unq.getName() != null
3386: && CONS_NAME_MID.equals(constraintNameMode))
3387: buf.append(unq.getName()).append(" ");
3388: buf.append("(").append(Strings.join(unq.getColumns(), ", "))
3389: .append(")");
3390: if (unq.isDeferred())
3391: buf.append(" INITIALLY DEFERRED");
3392: if (supportsDeferredUniqueConstraints())
3393: buf.append(" DEFERRABLE");
3394: if (unq.getName() != null
3395: && CONS_NAME_AFTER.equals(constraintNameMode))
3396: buf.append(" CONSTRAINT ").append(unq.getName());
3397: return buf.toString();
3398: }
3399:
3400: /**
3401: * Whether or not this dictionary supports deferred unique constraints.
3402: * This implementation returns {@link #supportsUniqueConstraints}.
3403: *
3404: * @since 1.1.0
3405: */
3406: protected boolean supportsDeferredUniqueConstraints() {
3407: return supportsDeferredConstraints;
3408: }
3409:
3410: /////////////////////
3411: // Database metadata
3412: /////////////////////
3413:
3414: /**
3415: * This method is used to filter system tables from database metadata.
3416: * Return true if the given table name represents a system table that
3417: * should not appear in the schema definition. By default, returns
3418: * true only if the given table is in the internal list of system tables,
3419: * or if the given schema is in the list of system schemas and is not
3420: * the target schema.
3421: *
3422: * @param name the table name
3423: * @param schema the table schema; may be null
3424: * @param targetSchema if true, then the given schema was listed by
3425: * the user as one of his schemas
3426: */
3427: public boolean isSystemTable(String name, String schema,
3428: boolean targetSchema) {
3429: if (systemTableSet.contains(name.toUpperCase()))
3430: return true;
3431: return !targetSchema && schema != null
3432: && systemSchemaSet.contains(schema.toUpperCase());
3433: }
3434:
3435: /**
3436: * This method is used to filter system indexes from database metadata.
3437: * Return true if the given index name represents a system index that
3438: * should not appear in the schema definition. Returns false by default.
3439: *
3440: * @param name the index name
3441: * @param table the index table
3442: */
3443: public boolean isSystemIndex(String name, Table table) {
3444: return false;
3445: }
3446:
3447: /**
3448: * This method is used to filter system sequences from database metadata.
3449: * Return true if the given sequence represents a system sequence that
3450: * should not appear in the schema definition. Returns true if system
3451: * schema by default.
3452: *
3453: * @param name the table name
3454: * @param schema the table schema; may be null
3455: * @param targetSchema if true, then the given schema was listed by
3456: * the user as one of his schemas
3457: */
3458: public boolean isSystemSequence(String name, String schema,
3459: boolean targetSchema) {
3460: return !targetSchema && schema != null
3461: && systemSchemaSet.contains(schema.toUpperCase());
3462: }
3463:
3464: /**
3465: * Reflect on the schema to find tables matching the given name pattern.
3466: */
3467: public Table[] getTables(DatabaseMetaData meta, String catalog,
3468: String schemaName, String tableName, Connection conn)
3469: throws SQLException {
3470: if (!supportsSchemaForGetTables)
3471: schemaName = null;
3472: else
3473: schemaName = getSchemaNameForMetadata(schemaName);
3474:
3475: String[] types = Strings.split(tableTypes, ",", 0);
3476: for (int i = 0; i < types.length; i++)
3477: types[i] = types[i].trim();
3478:
3479: beforeMetadataOperation(conn);
3480: ResultSet tables = null;
3481: try {
3482: tables = meta.getTables(getCatalogNameForMetadata(catalog),
3483: schemaName, getTableNameForMetadata(tableName),
3484: types);
3485: List tableList = new ArrayList();
3486: while (tables != null && tables.next())
3487: tableList.add(newTable(tables));
3488: return (Table[]) tableList.toArray(new Table[tableList
3489: .size()]);
3490: } finally {
3491: if (tables != null)
3492: try {
3493: tables.close();
3494: } catch (Exception e) {
3495: }
3496: }
3497: }
3498:
3499: /**
3500: * Create a new table from the information in the schema metadata.
3501: */
3502: protected Table newTable(ResultSet tableMeta) throws SQLException {
3503: Table t = new Table();
3504: t.setName(tableMeta.getString("TABLE_NAME"));
3505: return t;
3506: }
3507:
3508: /**
3509: * Reflect on the schema to find sequences matching the given name pattern.
3510: * Returns an empty array by default, as there is no standard way to
3511: * retrieve a list of sequences.
3512: */
3513: public Sequence[] getSequences(DatabaseMetaData meta,
3514: String catalog, String schemaName, String sequenceName,
3515: Connection conn) throws SQLException {
3516: String str = getSequencesSQL(schemaName, sequenceName);
3517: if (str == null)
3518: return new Sequence[0];
3519:
3520: PreparedStatement stmnt = prepareStatement(conn, str);
3521: ResultSet rs = null;
3522: try {
3523: int idx = 1;
3524: if (schemaName != null)
3525: stmnt.setString(idx++, schemaName.toUpperCase());
3526: if (sequenceName != null)
3527: stmnt.setString(idx++, sequenceName);
3528:
3529: rs = executeQuery(conn, stmnt, str);
3530: return getSequence(rs);
3531: } finally {
3532: if (rs != null)
3533: try {
3534: rs.close();
3535: } catch (SQLException se) {
3536: }
3537: if (stmnt != null)
3538: try {
3539: stmnt.close();
3540: } catch (SQLException se) {
3541: }
3542: }
3543: }
3544:
3545: /**
3546: * Create a new sequence from the information in the schema metadata.
3547: */
3548: protected Sequence newSequence(ResultSet sequenceMeta)
3549: throws SQLException {
3550: Sequence seq = new Sequence();
3551: seq.setSchemaName(sequenceMeta.getString("SEQUENCE_SCHEMA"));
3552: seq.setName(sequenceMeta.getString("SEQUENCE_NAME"));
3553: return seq;
3554: }
3555:
3556: /**
3557: * Return the SQL needed to select the list of sequences.
3558: */
3559: protected String getSequencesSQL(String schemaName,
3560: String sequenceName) {
3561: return null;
3562: }
3563:
3564: /**
3565: * Reflect on the schema to find columns matching the given table and
3566: * column patterns.
3567: */
3568: public Column[] getColumns(DatabaseMetaData meta, String catalog,
3569: String schemaName, String tableName, String columnName,
3570: Connection conn) throws SQLException {
3571: if (tableName == null && !supportsNullTableForGetColumns)
3572: return null;
3573:
3574: if (!supportsSchemaForGetColumns)
3575: schemaName = null;
3576: else
3577: schemaName = getSchemaNameForMetadata(schemaName);
3578:
3579: beforeMetadataOperation(conn);
3580: ResultSet cols = null;
3581: try {
3582: cols = meta.getColumns(getCatalogNameForMetadata(catalog),
3583: schemaName, getTableNameForMetadata(tableName),
3584: getColumnNameForMetadata(columnName));
3585:
3586: List columnList = new ArrayList();
3587: while (cols != null && cols.next())
3588: columnList.add(newColumn(cols));
3589: return (Column[]) columnList.toArray(new Column[columnList
3590: .size()]);
3591: } finally {
3592: if (cols != null)
3593: try {
3594: cols.close();
3595: } catch (Exception e) {
3596: }
3597: }
3598: }
3599:
3600: /**
3601: * Create a new column from the information in the schema metadata.
3602: */
3603: protected Column newColumn(ResultSet colMeta) throws SQLException {
3604: Column c = new Column();
3605: c.setSchemaName(colMeta.getString("TABLE_SCHEM"));
3606: c.setTableName(colMeta.getString("TABLE_NAME"));
3607: c.setName(colMeta.getString("COLUMN_NAME"));
3608: c.setType(colMeta.getInt("DATA_TYPE"));
3609: c.setTypeName(colMeta.getString("TYPE_NAME"));
3610: c.setSize(colMeta.getInt("COLUMN_SIZE"));
3611: c.setDecimalDigits(colMeta.getInt("DECIMAL_DIGITS"));
3612: c
3613: .setNotNull(colMeta.getInt("NULLABLE") == DatabaseMetaData.columnNoNulls);
3614:
3615: String def = colMeta.getString("COLUMN_DEF");
3616: if (!StringUtils.isEmpty(def) && !"null".equalsIgnoreCase(def))
3617: c.setDefaultString(def);
3618: return c;
3619: }
3620:
3621: /**
3622: * Reflect on the schema to find primary keys for the given table pattern.
3623: */
3624: public PrimaryKey[] getPrimaryKeys(DatabaseMetaData meta,
3625: String catalog, String schemaName, String tableName,
3626: Connection conn) throws SQLException {
3627: if (useGetBestRowIdentifierForPrimaryKeys)
3628: return getPrimaryKeysFromBestRowIdentifier(meta, catalog,
3629: schemaName, tableName, conn);
3630: return getPrimaryKeysFromGetPrimaryKeys(meta, catalog,
3631: schemaName, tableName, conn);
3632: }
3633:
3634: /**
3635: * Reflect on the schema to find primary keys for the given table pattern.
3636: */
3637: protected PrimaryKey[] getPrimaryKeysFromGetPrimaryKeys(
3638: DatabaseMetaData meta, String catalog, String schemaName,
3639: String tableName, Connection conn) throws SQLException {
3640: if (tableName == null && !supportsNullTableForGetPrimaryKeys)
3641: return null;
3642:
3643: beforeMetadataOperation(conn);
3644: ResultSet pks = null;
3645: try {
3646: pks = meta.getPrimaryKeys(
3647: getCatalogNameForMetadata(catalog),
3648: getSchemaNameForMetadata(schemaName),
3649: getTableNameForMetadata(tableName));
3650:
3651: List pkList = new ArrayList();
3652: while (pks != null && pks.next())
3653: pkList.add(newPrimaryKey(pks));
3654: return (PrimaryKey[]) pkList.toArray(new PrimaryKey[pkList
3655: .size()]);
3656: } finally {
3657: if (pks != null)
3658: try {
3659: pks.close();
3660: } catch (Exception e) {
3661: }
3662: }
3663: }
3664:
3665: /**
3666: * Create a new primary key from the information in the schema metadata.
3667: */
3668: protected PrimaryKey newPrimaryKey(ResultSet pkMeta)
3669: throws SQLException {
3670: PrimaryKey pk = new PrimaryKey();
3671: pk.setSchemaName(pkMeta.getString("TABLE_SCHEM"));
3672: pk.setTableName(pkMeta.getString("TABLE_NAME"));
3673: pk.setColumnName(pkMeta.getString("COLUMN_NAME"));
3674: pk.setName(pkMeta.getString("PK_NAME"));
3675: return pk;
3676: }
3677:
3678: /**
3679: * Reflect on the schema to find primary keys for the given table pattern.
3680: */
3681: protected PrimaryKey[] getPrimaryKeysFromBestRowIdentifier(
3682: DatabaseMetaData meta, String catalog, String schemaName,
3683: String tableName, Connection conn) throws SQLException {
3684: if (tableName == null)
3685: return null;
3686:
3687: beforeMetadataOperation(conn);
3688: ResultSet pks = null;
3689: try {
3690: pks = meta.getBestRowIdentifier(catalog, schemaName,
3691: tableName, 0, false);
3692:
3693: List pkList = new ArrayList();
3694: while (pks != null && pks.next()) {
3695: PrimaryKey pk = new PrimaryKey();
3696: pk.setSchemaName(schemaName);
3697: pk.setTableName(tableName);
3698: pk.setColumnName(pks.getString("COLUMN_NAME"));
3699: pkList.add(pk);
3700: }
3701: return (PrimaryKey[]) pkList.toArray(new PrimaryKey[pkList
3702: .size()]);
3703: } finally {
3704: if (pks != null)
3705: try {
3706: pks.close();
3707: } catch (Exception e) {
3708: }
3709: }
3710: }
3711:
3712: /**
3713: * Reflect on the schema to find indexes matching the given table pattern.
3714: */
3715: public Index[] getIndexInfo(DatabaseMetaData meta, String catalog,
3716: String schemaName, String tableName, boolean unique,
3717: boolean approx, Connection conn) throws SQLException {
3718: if (tableName == null && !supportsNullTableForGetIndexInfo)
3719: return null;
3720:
3721: beforeMetadataOperation(conn);
3722: ResultSet indexes = null;
3723: try {
3724: indexes = meta.getIndexInfo(
3725: getCatalogNameForMetadata(catalog),
3726: getSchemaNameForMetadata(schemaName),
3727: getTableNameForMetadata(tableName), unique, approx);
3728:
3729: List indexList = new ArrayList();
3730: while (indexes != null && indexes.next())
3731: indexList.add(newIndex(indexes));
3732: return (Index[]) indexList.toArray(new Index[indexList
3733: .size()]);
3734: } finally {
3735: if (indexes != null)
3736: try {
3737: indexes.close();
3738: } catch (Exception e) {
3739: }
3740: }
3741: }
3742:
3743: /**
3744: * Create a new index from the information in the schema metadata.
3745: */
3746: protected Index newIndex(ResultSet idxMeta) throws SQLException {
3747: Index idx = new Index();
3748: idx.setSchemaName(idxMeta.getString("TABLE_SCHEM"));
3749: idx.setTableName(idxMeta.getString("TABLE_NAME"));
3750: idx.setColumnName(idxMeta.getString("COLUMN_NAME"));
3751: idx.setName(idxMeta.getString("INDEX_NAME"));
3752: idx.setUnique(!idxMeta.getBoolean("NON_UNIQUE"));
3753: return idx;
3754: }
3755:
3756: /**
3757: * Reflect on the schema to return foreign keys imported by the given
3758: * table pattern.
3759: */
3760: public ForeignKey[] getImportedKeys(DatabaseMetaData meta,
3761: String catalog, String schemaName, String tableName,
3762: Connection conn) throws SQLException {
3763: if (!supportsForeignKeys)
3764: return null;
3765: if (tableName == null && !supportsNullTableForGetImportedKeys)
3766: return null;
3767:
3768: beforeMetadataOperation(conn);
3769: ResultSet keys = null;
3770: try {
3771: keys = meta.getImportedKeys(
3772: getCatalogNameForMetadata(catalog),
3773: getSchemaNameForMetadata(schemaName),
3774: getTableNameForMetadata(tableName));
3775:
3776: List importedKeyList = new ArrayList();
3777: while (keys != null && keys.next())
3778: importedKeyList.add(newForeignKey(keys));
3779: return (ForeignKey[]) importedKeyList
3780: .toArray(new ForeignKey[importedKeyList.size()]);
3781: } finally {
3782: if (keys != null)
3783: try {
3784: keys.close();
3785: } catch (Exception e) {
3786: }
3787: }
3788: }
3789:
3790: /**
3791: * Create a new foreign key from the information in the schema metadata.
3792: */
3793: protected ForeignKey newForeignKey(ResultSet fkMeta)
3794: throws SQLException {
3795: ForeignKey fk = new ForeignKey();
3796: fk.setSchemaName(fkMeta.getString("FKTABLE_SCHEM"));
3797: fk.setTableName(fkMeta.getString("FKTABLE_NAME"));
3798: fk.setColumnName(fkMeta.getString("FKCOLUMN_NAME"));
3799: fk.setName(fkMeta.getString("FK_NAME"));
3800: fk.setPrimaryKeySchemaName(fkMeta.getString("PKTABLE_SCHEM"));
3801: fk.setPrimaryKeyTableName(fkMeta.getString("PKTABLE_NAME"));
3802: fk.setPrimaryKeyColumnName(fkMeta.getString("PKCOLUMN_NAME"));
3803: fk.setKeySequence(fkMeta.getShort("KEY_SEQ"));
3804: fk
3805: .setDeferred(fkMeta.getShort("DEFERRABILITY") == DatabaseMetaData.importedKeyInitiallyDeferred);
3806:
3807: int del = fkMeta.getShort("DELETE_RULE");
3808: switch (del) {
3809: case DatabaseMetaData.importedKeySetNull:
3810: fk.setDeleteAction(ForeignKey.ACTION_NULL);
3811: break;
3812: case DatabaseMetaData.importedKeySetDefault:
3813: fk.setDeleteAction(ForeignKey.ACTION_DEFAULT);
3814: break;
3815: case DatabaseMetaData.importedKeyCascade:
3816: fk.setDeleteAction(ForeignKey.ACTION_CASCADE);
3817: break;
3818: default:
3819: fk.setDeleteAction(ForeignKey.ACTION_RESTRICT);
3820: break;
3821: }
3822: return fk;
3823: }
3824:
3825: /**
3826: * Returns the table name that will be used for obtaining information
3827: * from {@link DatabaseMetaData}.
3828: */
3829: protected String getTableNameForMetadata(String tableName) {
3830: return convertSchemaCase(tableName);
3831: }
3832:
3833: /**
3834: * Returns the schema name that will be used for obtaining information
3835: * from {@link DatabaseMetaData}.
3836: */
3837: protected String getSchemaNameForMetadata(String schemaName) {
3838: if (schemaName == null)
3839: schemaName = conf.getSchema();
3840: return convertSchemaCase(schemaName);
3841: }
3842:
3843: /**
3844: * Returns the catalog name that will be used for obtaining information
3845: * from {@link DatabaseMetaData}.
3846: */
3847: protected String getCatalogNameForMetadata(String catalogName) {
3848: return convertSchemaCase(catalogName);
3849: }
3850:
3851: /**
3852: * Returns the column name that will be used for obtaining information
3853: * from {@link DatabaseMetaData}.
3854: */
3855: protected String getColumnNameForMetadata(String columnName) {
3856: return convertSchemaCase(columnName);
3857: }
3858:
3859: /**
3860: * Convert the specified schema name to a name that the database will
3861: * be able to understand.
3862: */
3863: protected String convertSchemaCase(String objectName) {
3864: if (objectName == null)
3865: return null;
3866:
3867: String scase = getSchemaCase();
3868: if (SCHEMA_CASE_LOWER.equals(scase))
3869: return objectName.toLowerCase();
3870: if (SCHEMA_CASE_PRESERVE.equals(scase))
3871: return objectName;
3872: return objectName.toUpperCase();
3873: }
3874:
3875: /**
3876: * Return DB specific schemaCase
3877: */
3878: public String getSchemaCase() {
3879: return schemaCase;
3880: }
3881:
3882: /**
3883: * Prepared the connection for metadata operations.
3884: */
3885: private void beforeMetadataOperation(Connection c) {
3886: if (requiresAutoCommitForMetaData) {
3887: try {
3888: c.rollback();
3889: } catch (SQLException sqle) {
3890: }
3891: try {
3892: if (!c.getAutoCommit())
3893: c.setAutoCommit(true);
3894: } catch (SQLException sqle) {
3895: }
3896: }
3897: }
3898:
3899: /////////////////////////////
3900: // Sequences and Auto-Assign
3901: /////////////////////////////
3902:
3903: /**
3904: * Return the last generated value for the given column.
3905: * Throws an exception by default if {@link #lastGeneratedKeyQuery} is null.
3906: */
3907: public Object getGeneratedKey(Column col, Connection conn)
3908: throws SQLException {
3909: if (lastGeneratedKeyQuery == null)
3910: throw new StoreException(_loc.get("no-auto-assign"));
3911:
3912: // replace things like "SELECT MAX({0}) FROM {1}"
3913: String query = lastGeneratedKeyQuery;
3914: if (query.indexOf('{') != -1) // only if the token is in the string
3915: {
3916: query = MessageFormat.format(query, new Object[] {
3917: col.getName(), getFullName(col.getTable(), false),
3918: getGeneratedKeySequenceName(col), });
3919: }
3920:
3921: PreparedStatement stmnt = prepareStatement(conn, query);
3922: ResultSet rs = null;
3923: try {
3924: rs = executeQuery(conn, stmnt, query);
3925: return getKey(rs, col);
3926: } finally {
3927: if (rs != null)
3928: try {
3929: rs.close();
3930: } catch (SQLException se) {
3931: }
3932: if (stmnt != null)
3933: try {
3934: stmnt.close();
3935: } catch (SQLException se) {
3936: }
3937: }
3938: }
3939:
3940: /**
3941: * Return the sequence name used by databases for the given autoassigned
3942: * column. This is only used by databases that require an explicit name
3943: * to be used for auto-assign support.
3944: */
3945: protected String getGeneratedKeySequenceName(Column col) {
3946: String tname = col.getTableName();
3947: String cname = col.getName();
3948: int max = maxAutoAssignNameLength;
3949: int extraChars = -max + tname.length() + 1 // <tname> + '_'
3950: + cname.length() + 4; // <cname> + '_SEQ'
3951: if (extraChars > 0) {
3952: // this assumes that tname is longer than extraChars
3953: tname = tname.substring(0, tname.length() - extraChars);
3954: }
3955: StringBuffer buf = new StringBuffer(max);
3956: buf.append(tname).append("_").append(cname).append("_SEQ");
3957: return buf.toString();
3958: }
3959:
3960: ///////////////////////////////
3961: // Configurable implementation
3962: ///////////////////////////////
3963:
3964: public void setConfiguration(Configuration conf) {
3965: this .conf = (JDBCConfiguration) conf;
3966: this .log = this .conf.getLog(JDBCConfiguration.LOG_JDBC);
3967:
3968: // warn about unsupported dicts
3969: if (log.isWarnEnabled() && !isSupported())
3970: log.warn(_loc.get("dict-not-supported", getClass()));
3971: }
3972:
3973: private boolean isSupported() {
3974: // if this is a custom dict, traverse to whatever openjpa dict it extends
3975: Class c = getClass();
3976: while (!c.getName().startsWith("org.apache.openjpa."))
3977: c = c.getSuperclass();
3978:
3979: // the generic dbdictionary is not considered a supported dict; all
3980: // other concrete dictionaries are
3981: if (c == DBDictionary.class)
3982: return false;
3983: return true;
3984: }
3985:
3986: public void startConfiguration() {
3987: }
3988:
3989: public void endConfiguration() {
3990: // initialize the set of reserved SQL92 words from resource
3991: InputStream in = DBDictionary.class
3992: .getResourceAsStream("sql-keywords.rsrc");
3993: try {
3994: String keywords = new BufferedReader(new InputStreamReader(
3995: in)).readLine();
3996: in.close();
3997: reservedWordSet.addAll(Arrays.asList(Strings.split(
3998: keywords, ",", 0)));
3999: } catch (IOException ioe) {
4000: throw new GeneralException(ioe);
4001: } finally {
4002: try {
4003: in.close();
4004: } catch (IOException e) {
4005: }
4006: }
4007:
4008: // add additional reserved words set by user
4009: if (reservedWords != null)
4010: reservedWordSet.addAll(Arrays.asList(Strings.split(
4011: reservedWords.toUpperCase(), ",", 0)));
4012:
4013: // add system schemas set by user
4014: if (systemSchemas != null)
4015: systemSchemaSet.addAll(Arrays.asList(Strings.split(
4016: systemSchemas.toUpperCase(), ",", 0)));
4017:
4018: // add system tables set by user
4019: if (systemTables != null)
4020: systemTableSet.addAll(Arrays.asList(Strings.split(
4021: systemTables.toUpperCase(), ",", 0)));
4022:
4023: // add fixed size type names set by the user
4024: if (fixedSizeTypeNames != null)
4025: fixedSizeTypeNameSet.addAll(Arrays.asList(Strings.split(
4026: fixedSizeTypeNames.toUpperCase(), ",", 0)));
4027:
4028: // if user has unset sequence sql, null it out so we know sequences
4029: // aren't supported
4030: nextSequenceQuery = StringUtils.trimToNull(nextSequenceQuery);
4031:
4032: if (selectWords != null)
4033: selectWordSet.addAll(Arrays.asList(Strings.split(
4034: selectWords.toUpperCase(), ",", 0)));
4035: }
4036:
4037: //////////////////////////////////////
4038: // ConnectionDecorator implementation
4039: //////////////////////////////////////
4040:
4041: /**
4042: * Decorate the given connection if needed. Some databases require special
4043: * handling for JDBC bugs. This implementation issues any
4044: * {@link #initializationSQL} that has been set for the dictionary but
4045: * does not decoreate the connection.
4046: */
4047: public Connection decorate(Connection conn) throws SQLException {
4048: if (!connected)
4049: connectedConfiguration(conn);
4050: if (!StringUtils.isEmpty(initializationSQL)) {
4051: PreparedStatement stmnt = null;
4052: try {
4053: stmnt = conn.prepareStatement(initializationSQL);
4054: stmnt.execute();
4055: } catch (Exception e) {
4056: if (log.isTraceEnabled())
4057: log.trace(e.toString(), e);
4058: } finally {
4059: if (stmnt != null)
4060: try {
4061: stmnt.close();
4062: } catch (SQLException se) {
4063: }
4064: }
4065: }
4066: return conn;
4067: }
4068:
4069: /**
4070: * Implementation of the
4071: * {@link LoggingConnectionDecorator.SQLWarningHandler} interface
4072: * that allows customization of the actions to perform when a
4073: * {@link SQLWarning} occurs at any point on a {@link Connection},
4074: * {@link Statement}, or {@link ResultSet}. This method may
4075: * be used determine those warnings the application wants to
4076: * consider critical failures, and throw the warning in those
4077: * cases. By default, this method does nothing.
4078: *
4079: * @see LoggingConnectionDecorator#setWarningAction
4080: * @see LoggingConnectionDecorator#setWarningHandler
4081: */
4082: public void handleWarning(SQLWarning warning) throws SQLException {
4083: }
4084:
4085: /**
4086: * Return a new exception that wraps <code>causes</code>.
4087: * However, the details of exactly what type of exception is returned can
4088: * be determined by the implementation. This may take into account
4089: * DB-specific exception information in <code>causes</code>.
4090: */
4091: public OpenJPAException newStoreException(String msg,
4092: SQLException[] causes, Object failed) {
4093: if (causes != null && causes.length > 0) {
4094: OpenJPAException ret = SQLExceptions.narrow(msg, causes[0],
4095: this );
4096: ret.setFailedObject(failed).setNestedThrowables(causes);
4097: return ret;
4098: }
4099: return new StoreException(msg).setFailedObject(failed)
4100: .setNestedThrowables(causes);
4101: }
4102:
4103: /**
4104: * Gets the list of String, each represents an error that can help
4105: * to narrow down a SQL exception to specific type of StoreException.<br>
4106: * For example, error code <code>"23000"</code> represents referential
4107: * integrity violation and hence can be narrowed down to
4108: * {@link ReferentialIntegrityException} rather than more general
4109: * {@link StoreException}.<br>
4110: * JDBC Drivers are not uniform in return values of SQLState for the same
4111: * error and hence each database specific Dictionary can specialize.<br>
4112: *
4113: *
4114: * @return an <em>unmodifiable</em> list of Strings representing supposedly
4115: * uniform SQL States for a given type of StoreException.
4116: * Default behavior is to return an empty list.
4117: */
4118: public List/*<String>*/getSQLStates(int exceptionType) {
4119: if (exceptionType >= 0
4120: && exceptionType < SQL_STATE_CODES.length)
4121: return SQL_STATE_CODES[exceptionType];
4122: return EMPTY_STRING_LIST;
4123: }
4124:
4125: /**
4126: * Closes the specified {@link DataSource} and releases any
4127: * resources associated with it.
4128: *
4129: * @param dataSource the DataSource to close
4130: */
4131: public void closeDataSource(DataSource dataSource) {
4132: DataSourceFactory.closeDataSource(dataSource);
4133: }
4134:
4135: /**
4136: * Used by some mappings to represent data that has already been
4137: * serialized so that we don't have to serialize multiple times.
4138: */
4139: public static class SerializedData {
4140:
4141: public final byte[] bytes;
4142:
4143: public SerializedData(byte[] bytes) {
4144: this .bytes = bytes;
4145: }
4146: }
4147:
4148: /**
4149: * Return version column name
4150: * @param column
4151: * @param tableAlias : this is needed for platform specific version column
4152: * @return
4153: */
4154: public String getVersionColumn(Column column, String tableAlias) {
4155: return column.toString();
4156: }
4157:
4158: public void insertBlobForStreamingLoad(Row row, Column col)
4159: throws SQLException {
4160: row.setBinaryStream(col, new ByteArrayInputStream(new byte[0]),
4161: 0);
4162: }
4163:
4164: public void insertClobForStreamingLoad(Row row, Column col)
4165: throws SQLException {
4166: row
4167: .setCharacterStream(col, new CharArrayReader(
4168: new char[0]), 0);
4169: }
4170:
4171: public void updateBlob(Select sel, JDBCStore store, InputStream is)
4172: throws SQLException {
4173: SQLBuffer sql = sel.toSelect(true, store
4174: .getFetchConfiguration());
4175: ResultSet res = null;
4176: Connection conn = store.getConnection();
4177: PreparedStatement stmnt = null;
4178: try {
4179: stmnt = sql.prepareStatement(conn, store
4180: .getFetchConfiguration(),
4181: ResultSet.TYPE_SCROLL_SENSITIVE,
4182: ResultSet.CONCUR_UPDATABLE);
4183: res = stmnt.executeQuery();
4184: if (!res.next()) {
4185: throw new InternalException(_loc
4186: .get("stream-exception"));
4187: }
4188: Blob blob = res.getBlob(1);
4189: OutputStream os = blob.setBinaryStream(1);
4190: copy(is, os);
4191: os.close();
4192: res.updateBlob(1, blob);
4193: res.updateRow();
4194:
4195: } catch (IOException ioe) {
4196: throw new StoreException(ioe);
4197: } finally {
4198: if (res != null)
4199: try {
4200: res.close();
4201: } catch (SQLException e) {
4202: }
4203: if (stmnt != null)
4204: try {
4205: stmnt.close();
4206: } catch (SQLException e) {
4207: }
4208: if (conn != null)
4209: try {
4210: conn.close();
4211: } catch (SQLException e) {
4212: }
4213: }
4214: }
4215:
4216: public void updateClob(Select sel, JDBCStore store, Reader reader)
4217: throws SQLException {
4218: SQLBuffer sql = sel.toSelect(true, store
4219: .getFetchConfiguration());
4220: ResultSet res = null;
4221: Connection conn = store.getConnection();
4222: PreparedStatement stmnt = null;
4223: try {
4224: stmnt = sql.prepareStatement(conn, store
4225: .getFetchConfiguration(),
4226: ResultSet.TYPE_SCROLL_SENSITIVE,
4227: ResultSet.CONCUR_UPDATABLE);
4228: res = stmnt.executeQuery();
4229: if (!res.next()) {
4230: throw new InternalException(_loc
4231: .get("stream-exception"));
4232: }
4233: Clob clob = res.getClob(1);
4234: Writer writer = clob.setCharacterStream(1);
4235: copy(reader, writer);
4236: writer.close();
4237: res.updateClob(1, clob);
4238: res.updateRow();
4239:
4240: } catch (IOException ioe) {
4241: throw new StoreException(ioe);
4242: } finally {
4243: if (res != null)
4244: try {
4245: res.close();
4246: } catch (SQLException e) {
4247: }
4248: if (stmnt != null)
4249: try {
4250: stmnt.close();
4251: } catch (SQLException e) {
4252: }
4253: if (conn != null)
4254: try {
4255: conn.close();
4256: } catch (SQLException e) {
4257: }
4258: }
4259: }
4260:
4261: protected long copy(InputStream in, OutputStream out)
4262: throws IOException {
4263: byte[] copyBuffer = new byte[blobBufferSize];
4264: long bytesCopied = 0;
4265: int read = -1;
4266:
4267: while ((read = in.read(copyBuffer, 0, copyBuffer.length)) != -1) {
4268: out.write(copyBuffer, 0, read);
4269: bytesCopied += read;
4270: }
4271: return bytesCopied;
4272: }
4273:
4274: protected long copy(Reader reader, Writer writer)
4275: throws IOException {
4276: char[] copyBuffer = new char[clobBufferSize];
4277: long bytesCopied = 0;
4278: int read = -1;
4279:
4280: while ((read = reader.read(copyBuffer, 0, copyBuffer.length)) != -1) {
4281: writer.write(copyBuffer, 0, read);
4282: bytesCopied += read;
4283: }
4284:
4285: return bytesCopied;
4286: }
4287:
4288: /**
4289: * Attach CAST to the current function if necessary
4290: *
4291: * @param val operand value
4292: * @parma func the sql function statement
4293: * @return a String with the correct CAST function syntax
4294: */
4295: public String getCastFunction(Val val, String func) {
4296: return func;
4297: }
4298:
4299: /**
4300: * Create an index if necessary for some database tables
4301: */
4302: public void createIndexIfNecessary(Schema schema, String table,
4303: Column pkColumn) {
4304: }
4305:
4306: /**
4307: * Return the batchLimit
4308: */
4309: public int getBatchLimit() {
4310: return batchLimit;
4311: }
4312:
4313: /**
4314: * Set the batchLimit value
4315: */
4316: public void setBatchLimit(int limit) {
4317: batchLimit = limit;
4318: }
4319:
4320: /**
4321: * Validate the batch process. In some cases, we can't batch the statements
4322: * due to some restrictions. For example, if the GeneratedType=IDENTITY,
4323: * we have to disable the batch process because we need to get the ID value
4324: * right away for the in-memory entity to use.
4325: */
4326: public boolean validateBatchProcess(RowImpl row,
4327: Column[] autoAssign, OpenJPAStateManager sm,
4328: ClassMapping cmd) {
4329: boolean disableBatch = false;
4330: if (getBatchLimit() == 0)
4331: return false;
4332: if (autoAssign != null && sm != null) {
4333: FieldMetaData[] fmd = cmd.getPrimaryKeyFields();
4334: int i = 0;
4335: while (!disableBatch && i < fmd.length) {
4336: if (fmd[i].getValueStrategy() == ValueStrategies.AUTOASSIGN)
4337: disableBatch = true;
4338: i++;
4339: }
4340: }
4341: // go to each Dictionary to validate the batch capability
4342: if (!disableBatch)
4343: disableBatch = validateDBSpecificBatchProcess(disableBatch,
4344: row, autoAssign, sm, cmd);
4345: return disableBatch;
4346: }
4347:
4348: /**
4349: * Allow each Dictionary to validate its own batch process.
4350: */
4351: public boolean validateDBSpecificBatchProcess(boolean disableBatch,
4352: RowImpl row, Column[] autoAssign, OpenJPAStateManager sm,
4353: ClassMapping cmd) {
4354: return disableBatch;
4355: }
4356:
4357: /**
4358: * This method is to provide override for non-JDBC or JDBC-like
4359: * implementation of executing query.
4360: */
4361: protected ResultSet executeQuery(Connection conn,
4362: PreparedStatement stmnt, String sql) throws SQLException {
4363: return stmnt.executeQuery();
4364: }
4365:
4366: /**
4367: * This method is to provide override for non-JDBC or JDBC-like
4368: * implementation of preparing statement.
4369: */
4370: protected PreparedStatement prepareStatement(Connection conn,
4371: String sql) throws SQLException {
4372: return conn.prepareStatement(sql);
4373: }
4374:
4375: /**
4376: * This method is to provide override for non-JDBC or JDBC-like
4377: * implementation of getting sequence from the result set.
4378: */
4379: protected Sequence[] getSequence(ResultSet rs) throws SQLException {
4380: List seqList = new ArrayList();
4381: while (rs != null && rs.next())
4382: seqList.add(newSequence(rs));
4383: return (Sequence[]) seqList
4384: .toArray(new Sequence[seqList.size()]);
4385: }
4386:
4387: /**
4388: * This method is to provide override for non-JDBC or JDBC-like
4389: * implementation of getting key from the result set.
4390: */
4391: protected Object getKey(ResultSet rs, Column col)
4392: throws SQLException {
4393: if (!rs.next())
4394: throw new StoreException(_loc.get("no-genkey"));
4395: Object key = rs.getObject(1);
4396: if (key == null)
4397: log.warn(_loc.get("invalid-genkey", col));
4398: return key;
4399: }
4400:
4401: /**
4402: * This method is to provide override for non-JDBC or JDBC-like
4403: * implementation of calculating value.
4404: */
4405: protected void calculateValue(Val val, Select sel, ExpContext ctx,
4406: ExpState state, Path path, ExpState pathState) {
4407: val.calculateValue(sel, ctx, state, (Val) path, pathState);
4408: }
4409:
4410: /**
4411: * Determine whether the provided <code>sql</code> may be treated as a
4412: * select statement on this database.
4413: *
4414: * @param sql A sql statement.
4415: *
4416: * @return true if <code>sql</code> represents a select statement.
4417: */
4418: public boolean isSelect(String sql) {
4419: Iterator i = selectWordSet.iterator();
4420: String cur;
4421: while (i.hasNext()) {
4422: cur = (String) i.next();
4423: if (sql.length() >= cur.length()
4424: && sql.substring(0, cur.length()).equalsIgnoreCase(
4425: cur)) {
4426: return true;
4427: }
4428: }
4429: return false;
4430: }
4431: }
|