0001: /*
0002: * Copyright 2004 (C) TJDO.
0003: * All rights reserved.
0004: *
0005: * This software is distributed under the terms of the TJDO License version 1.0.
0006: * See the terms of the TJDO License in the documentation provided with this software.
0007: *
0008: * $Id: DatabaseAdapter.java,v 1.30 2004/03/30 06:12:14 jackknifebarber Exp $
0009: */
0010:
0011: package com.triactive.jdo.store;
0012:
0013: import com.triactive.jdo.model.FieldMetaData;
0014: import java.lang.reflect.InvocationTargetException;
0015: import java.sql.Connection;
0016: import java.sql.DatabaseMetaData;
0017: import java.sql.ResultSet;
0018: import java.sql.SQLException;
0019: import java.util.ArrayList;
0020: import java.util.HashMap;
0021: import java.util.HashSet;
0022: import java.util.Set;
0023: import java.util.StringTokenizer;
0024: import javax.jdo.JDODataStoreException;
0025: import javax.jdo.JDOException;
0026: import javax.jdo.JDOFatalDataStoreException;
0027: import javax.jdo.JDOFatalInternalException;
0028: import javax.jdo.JDOUnsupportedOptionException;
0029: import javax.jdo.JDOUserException;
0030: import javax.sql.DataSource;
0031: import org.apache.log4j.Category;
0032:
0033: /**
0034: * Provides methods for adapting SQL language elements to a specific vendor's
0035: * database. A database adapter is primarily used to map generic JDBC data
0036: * types and SQL identifiers to specific types/identifiers suitable for the
0037: * database in use.
0038: *
0039: * <p>Each database adapter corresponds to a particular combination of database,
0040: * database version, driver, and driver version, as provided by the driver's
0041: * own metadata. Database adapters cannot be constructed directly, but must be
0042: * obtained using the {@link #getInstance} method.
0043: *
0044: * @author <a href="mailto:mmartin5@austin.rr.com">Mike Martin</a>
0045: * @author <a href="mailto:cwalk@triactive.com">Christopher Walk</a>
0046: * @version $Revision: 1.30 $
0047: *
0048: * @see java.sql.DatabaseMetaData
0049: */
0050: public class DatabaseAdapter {
0051: private static final Category LOG = Category
0052: .getInstance(DatabaseAdapter.class);
0053:
0054: /** The name of the underlying database. */
0055: protected String databaseProductName;
0056:
0057: /** The version number of the underlying database as a string. */
0058: protected String databaseProductVersion;
0059:
0060: /** The major version number of the underlying database. */
0061: protected int databaseMajorVersion;
0062:
0063: /** The minor version number of the underlying database. */
0064: protected int databaseMinorVersion;
0065:
0066: /** The maximum length to be used for a table name. */
0067: protected int maxTableNameLength;
0068:
0069: /** The maximum length to be used for a table constraint name. */
0070: protected int maxConstraintNameLength;
0071:
0072: /** The maximum length to be used for an index name. */
0073: protected int maxIndexNameLength;
0074:
0075: /** The maximum length to be used for a column name. */
0076: protected int maxColumnNameLength;
0077:
0078: /** <tt>true</tt> if the database stores all identifiers in lower-case. */
0079: protected boolean storesLowerCaseIdentifiers;
0080:
0081: /** <tt>true</tt> if the database stores all identifiers in upper-case. */
0082: protected boolean storesUpperCaseIdentifiers;
0083:
0084: /** The String used to quote SQL identifiers. */
0085: protected String identifierQuoteString;
0086:
0087: /** The set of SQL keywords for this DBMS, in upper-case. */
0088: protected final HashSet keywords = new HashSet();
0089:
0090: protected final HashMap typesByTypeNumber = new HashMap();
0091: protected final HashMap typeMappings = new HashMap();
0092:
0093: /**
0094: * The cache of constructed database adapter objects.
0095: */
0096: private static HashMap adaptersByID = new HashMap();
0097:
0098: /**
0099: * Returns a <tt>DatabaseAdapter</tt> object appropriate for the database
0100: * currently underlying the given {@link Connection}. Multiple calls to
0101: * this method with connections having the same database/driver/version will
0102: * return the same <tt>DatabaseAdapter</tt> object.
0103: *
0104: * @param conn An open database connection.
0105: *
0106: * @return a database adapter object for the database underlying the
0107: * given connection.
0108: *
0109: * @exception SQLException
0110: * If a database error occurs.
0111: */
0112: public static synchronized DatabaseAdapter getInstance(
0113: Connection conn) throws SQLException {
0114: DatabaseMetaData metadata = conn.getMetaData();
0115:
0116: String id = metadata.getDatabaseProductName() + ", "
0117: + metadata.getDatabaseProductVersion() + ", "
0118: + metadata.getDriverName() + ", "
0119: + metadata.getDriverVersion();
0120:
0121: DatabaseAdapter adapter = (DatabaseAdapter) adaptersByID
0122: .get(id);
0123:
0124: if (adapter == null) {
0125: if (isCloudscape(metadata))
0126: adapter = new CloudscapeAdapter(metadata);
0127: else if (isDB2J(metadata))
0128: adapter = new DB2JAdapter(metadata);
0129: else if (isDB2(metadata))
0130: adapter = new DB2Adapter(metadata);
0131: else if (isFirebird(metadata))
0132: adapter = new FirebirdAdapter(metadata);
0133: else if (isHSQLDB(metadata))
0134: adapter = new HSQLDBAdapter(metadata);
0135: else if (isMSSQLServer(metadata))
0136: adapter = new MSSQLServerAdapter(metadata);
0137: else if (isMySQL(metadata))
0138: adapter = new MySQLAdapter(metadata);
0139: else if (isOracle(metadata)) {
0140: /* NOTE: Reflection is used here as a temporary means of
0141: * eliminating the dependency between this class
0142: * and the OracleAdapter. OracleAdapater indirectly
0143: * utilizes classes that are shipped with the
0144: * Oracle drivers which may not be available to
0145: * all users.
0146: */
0147: try {
0148: Class classDefinition = Class
0149: .forName("com.triactive.jdo.store.OracleAdapter");
0150:
0151: adapter = (DatabaseAdapter) newInstance(
0152: classDefinition,
0153: new Class[] { DatabaseMetaData.class },
0154: new Object[] { metadata });
0155: } catch (Exception e) {
0156: throw new JDODataStoreException(
0157: "The Oracle adapter was not found.", e);
0158: }
0159: } else if (isPointBase(metadata))
0160: adapter = new PointBaseAdapter(metadata);
0161: else if (isPostgreSQL(metadata))
0162: adapter = new PostgreSQLAdapter(metadata);
0163: else if (isSAPDB(metadata))
0164: adapter = new SAPDBAdapter(metadata);
0165: else
0166: adapter = new DatabaseAdapter(metadata);
0167:
0168: adaptersByID.put(id, adapter);
0169:
0170: LOG.info("Adapter initialized: " + adapter);
0171: }
0172:
0173: return adapter;
0174: }
0175:
0176: private static boolean productNameContains(
0177: DatabaseMetaData metadata, String name) {
0178: try {
0179: return metadata.getDatabaseProductName().toLowerCase()
0180: .indexOf(name.toLowerCase()) >= 0;
0181: } catch (SQLException e) {
0182: throw new JDODataStoreException(
0183: "Error accessing database metadata", e);
0184: }
0185: }
0186:
0187: private static boolean isCloudscape(DatabaseMetaData metadata) {
0188: return productNameContains(metadata, "cloudscape");
0189: }
0190:
0191: private static boolean isDB2(DatabaseMetaData metadata) {
0192: return productNameContains(metadata, "db2")
0193: && !productNameContains(metadata, "db2j");
0194: }
0195:
0196: private static boolean isDB2J(DatabaseMetaData metadata) {
0197: return productNameContains(metadata, "db2j");
0198: }
0199:
0200: private static boolean isFirebird(DatabaseMetaData metadata) {
0201: return productNameContains(metadata, "firebird");
0202: }
0203:
0204: private static boolean isHSQLDB(DatabaseMetaData metadata) {
0205: return productNameContains(metadata, "hsql database engine")
0206: || productNameContains(metadata, "hsqldb");
0207: }
0208:
0209: private static boolean isMSSQLServer(DatabaseMetaData metadata) {
0210: return productNameContains(metadata, "sql server");
0211: }
0212:
0213: private static boolean isMySQL(DatabaseMetaData metadata) {
0214: return productNameContains(metadata, "mysql");
0215: }
0216:
0217: private static boolean isOracle(DatabaseMetaData metadata) {
0218: return productNameContains(metadata, "oracle");
0219: }
0220:
0221: private static boolean isPointBase(DatabaseMetaData metadata) {
0222: return productNameContains(metadata, "pointbase");
0223: }
0224:
0225: private static boolean isPostgreSQL(DatabaseMetaData metadata) {
0226: return productNameContains(metadata, "postgresql");
0227: }
0228:
0229: private static boolean isSAPDB(DatabaseMetaData metadata) {
0230: return productNameContains(metadata, "sapdb")
0231: || productNameContains(metadata, "sap db");
0232: }
0233:
0234: /**
0235: * Constructs a database adapter based on the given JDBC metadata.
0236: *
0237: * @param metadata the database metadata.
0238: */
0239:
0240: protected DatabaseAdapter(DatabaseMetaData metadata) {
0241: keywords
0242: .addAll(parseKeywordList(SQL92Constants.RESERVED_WORDS));
0243: keywords
0244: .addAll(parseKeywordList(SQL92Constants.NONRESERVED_WORDS));
0245:
0246: try {
0247: keywords
0248: .addAll(parseKeywordList(metadata.getSQLKeywords()));
0249:
0250: databaseProductName = metadata.getDatabaseProductName();
0251: databaseProductVersion = metadata
0252: .getDatabaseProductVersion();
0253:
0254: try {
0255: Class mdc = metadata.getClass();
0256:
0257: databaseMajorVersion = ((Integer) mdc.getMethod(
0258: "getDatabaseMajorVersion", null).invoke(
0259: metadata, null)).intValue();
0260: databaseMinorVersion = ((Integer) mdc.getMethod(
0261: "getDatabaseMinorVersion", null).invoke(
0262: metadata, null)).intValue();
0263: } catch (Throwable t) {
0264: /*
0265: * The driver doesn't support JDBC 3. Try to parse major and
0266: * minor version numbers out of the product version string.
0267: * We do this by stripping out everything but digits and periods
0268: * and hoping we get something that looks like <major>.<minor>.
0269: */
0270: StringBuffer stripped = new StringBuffer();
0271:
0272: for (int i = 0; i < databaseProductVersion.length(); ++i) {
0273: char c = databaseProductVersion.charAt(i);
0274:
0275: if (Character.isDigit(c) || c == '.')
0276: stripped.append(c);
0277: }
0278:
0279: StringTokenizer parts = new StringTokenizer(stripped
0280: .toString(), ".");
0281:
0282: if (parts.hasMoreTokens())
0283: databaseMajorVersion = Integer.parseInt(parts
0284: .nextToken());
0285: if (parts.hasMoreTokens())
0286: databaseMinorVersion = Integer.parseInt(parts
0287: .nextToken());
0288: }
0289:
0290: maxTableNameLength = metadata.getMaxTableNameLength();
0291: /*
0292: * The metadata object may return 0 for getMaxTableNameLength to
0293: * indicate that there is no table name length. If that is the case,
0294: * we will use SQL92Constants.MAX_IDENTIFIER_LENGTH for the
0295: * maximum length.
0296: */
0297: if (maxTableNameLength == 0) {
0298: maxTableNameLength = SQL92Constants.MAX_IDENTIFIER_LENGTH;
0299: }
0300:
0301: // use maxTableNameLength for maxConstraintNameLength and maxIndexNameLength
0302: maxConstraintNameLength = maxTableNameLength;
0303: maxIndexNameLength = maxTableNameLength;
0304:
0305: maxColumnNameLength = metadata.getMaxColumnNameLength();
0306: /*
0307: * The metadata object may return 0 for getMaxColumnNameLength to
0308: * indicate that there is no column name length. If that is the case,
0309: * we will use SQL92Constants.MAX_IDENTIFIER_LENGTH for the
0310: * maximum length.
0311: */
0312: if (maxColumnNameLength == 0) {
0313: maxColumnNameLength = SQL92Constants.MAX_IDENTIFIER_LENGTH;
0314: }
0315:
0316: storesLowerCaseIdentifiers = metadata
0317: .storesLowerCaseIdentifiers();
0318: storesUpperCaseIdentifiers = metadata
0319: .storesUpperCaseIdentifiers();
0320: identifierQuoteString = metadata.getIdentifierQuoteString();
0321:
0322: /*
0323: * If this is null or an empty String, default to double-quote.
0324: */
0325: identifierQuoteString = ((null == identifierQuoteString) || (identifierQuoteString
0326: .trim().length() < 1)) ? "\""
0327: : identifierQuoteString;
0328:
0329: /*
0330: * Create TypeInfo objects for all of the data types and index them
0331: * in a HashMap by their JDBC type number.
0332: */
0333: createTypeInfo(metadata);
0334:
0335: } catch (SQLException e) {
0336: throw new JDODataStoreException(
0337: "Error accessing database metadata", e);
0338: }
0339:
0340: typeMappings.put(boolean.class, BooleanMapping.class);
0341: typeMappings.put(byte.class, ByteMapping.class);
0342: typeMappings.put(byte[].class, ByteArrayMapping.class);
0343: typeMappings.put(char.class, CharacterMapping.class);
0344: typeMappings.put(short.class, ShortMapping.class);
0345: typeMappings.put(int.class, IntegerMapping.class);
0346: typeMappings.put(long.class, LongMapping.class);
0347: typeMappings.put(float.class, FloatMapping.class);
0348: typeMappings.put(double.class, DoubleMapping.class);
0349: typeMappings.put(Boolean.class, BooleanMapping.class);
0350: typeMappings.put(Byte.class, ByteMapping.class);
0351: typeMappings.put(Character.class, CharacterMapping.class);
0352: typeMappings.put(Short.class, ShortMapping.class);
0353: typeMappings.put(Integer.class, IntegerMapping.class);
0354: typeMappings.put(Long.class, LongMapping.class);
0355: typeMappings.put(Float.class, FloatMapping.class);
0356: typeMappings.put(Double.class, DoubleMapping.class);
0357: typeMappings.put(String.class, StringMapping.class);
0358: typeMappings.put(java.math.BigDecimal.class,
0359: BigDecimalMapping.class);
0360: typeMappings.put(java.math.BigInteger.class,
0361: BigIntegerMapping.class);
0362: typeMappings.put(java.util.Date.class, DateMapping.class);
0363: typeMappings.put(java.util.Locale.class,
0364: UnsupportedMapping.class);
0365: typeMappings.put(java.sql.Date.class, SqlDateMapping.class);
0366: typeMappings.put(java.sql.Timestamp.class,
0367: SqlTimestampMapping.class);
0368: typeMappings.put(OID.class, OIDMapping.class);
0369:
0370: typeMappings.put(java.util.ArrayList.class,
0371: UnsupportedMapping.class);
0372: typeMappings.put(java.util.Collection.class, SetMapping.class);
0373: typeMappings.put(java.util.HashMap.class, MapMapping.class);
0374: typeMappings.put(java.util.HashSet.class, SetMapping.class);
0375: typeMappings.put(java.util.Hashtable.class, MapMapping.class);
0376: typeMappings.put(java.util.LinkedList.class,
0377: UnsupportedMapping.class);
0378: typeMappings
0379: .put(java.util.List.class, UnsupportedMapping.class);
0380: typeMappings.put(java.util.Map.class, MapMapping.class);
0381: typeMappings.put(java.util.Set.class, SetMapping.class);
0382: typeMappings.put(java.util.TreeMap.class, MapMapping.class);
0383: typeMappings.put(java.util.TreeSet.class, SetMapping.class);
0384: typeMappings.put(java.util.Vector.class,
0385: UnsupportedMapping.class);
0386: }
0387:
0388: public String getVendorID() {
0389: return null;
0390: }
0391:
0392: public int getMaxTableNameLength() {
0393: return maxTableNameLength;
0394: }
0395:
0396: public int getMaxConstraintNameLength() {
0397: return maxConstraintNameLength;
0398: }
0399:
0400: public int getMaxIndexNameLength() {
0401: return maxIndexNameLength;
0402: }
0403:
0404: public int getMaxColumnNameLength() {
0405: return maxColumnNameLength;
0406: }
0407:
0408: public boolean storesLowerCaseIdentifiers() {
0409: return storesLowerCaseIdentifiers;
0410: }
0411:
0412: public boolean storesUpperCaseIdentifiers() {
0413: return storesUpperCaseIdentifiers;
0414: }
0415:
0416: /**
0417: * Returns a SQLState object for the specified SQLException, if one is
0418: * present and valid.
0419: *
0420: * @param se
0421: * A caught SQL exception.
0422: *
0423: * @return
0424: * A SQLState object, or <code>null</code> if <var>se</var> does not
0425: * contain a valid 5-character SQLSTATE.
0426: */
0427:
0428: public SQLState getSQLState(SQLException se) {
0429: String state = se.getSQLState();
0430:
0431: if (state == null)
0432: return null;
0433:
0434: try {
0435: return new SQLState(state);
0436: } catch (IllegalArgumentException e) {
0437: return null;
0438: }
0439: }
0440:
0441: /**
0442: * Create the appropriate <code>JDODataStoreException</code> or
0443: * <code>JDOFatalDataStoreException</code> for the given
0444: * <code>SQLException</code> based on whether the action causing
0445: * the exception has aborted the current transaction.
0446: * <p>
0447: * For historical reasons, the design of this method is flawed.
0448: * To conform correctly to the spec, if a JDOFatalDataStoreException is
0449: * returned then there should be some coordination with the appropriate
0450: * PersistenceManager and its Transaction to allow them to reflect the fact
0451: * that a transaction is no longer active.
0452: * At the least, that means that this method would have to be passed a
0453: * reference to a PersistenceManager.
0454: * <p>
0455: * An outstanding question remains how we can reliably determine via JDBC
0456: * whether or not a failed statement has aborted the current database
0457: * transaction.
0458: * Bottom line, this area is ripe for refactoring.
0459: * <p>
0460: * The current implementation in this class always returns a new
0461: * JDODataStoreException and never a JDOFatalDataStoreException.
0462: *
0463: * @param message The message to include in the JDODataStoreException.
0464: * @param e The SQLException to create a JDODataStoreException for.
0465: *
0466: * @return A <code>JDODataStoreException</code> or
0467: * <code>JDOFatalDataStoreException</code> that wraps the
0468: * given <code>SQLException</code>. A fatal exception is used
0469: * to indicate that the active transaction has been aborted.
0470: */
0471:
0472: public JDOException newDataStoreException(String message,
0473: SQLException e) {
0474: return new JDODataStoreException(message, e);
0475: }
0476:
0477: /**
0478: * Creates TypeInfo objects for all of the data types and indexes them
0479: * in the typesByTypeNumber map by their JDBC data type number.
0480: */
0481:
0482: protected void createTypeInfo(DatabaseMetaData metadata)
0483: throws SQLException {
0484: ResultSet rs = metadata.getTypeInfo();
0485:
0486: try {
0487: while (rs.next()) {
0488: TypeInfo ti = newTypeInfo(rs);
0489:
0490: if (ti != null) {
0491: Integer key = new Integer(ti.dataType);
0492:
0493: if (typesByTypeNumber.get(key) == null)
0494: typesByTypeNumber.put(key, ti);
0495: }
0496: }
0497: } finally {
0498: rs.close();
0499: }
0500: }
0501:
0502: /**
0503: * A factory for TypeInfo objects. This method should always be used
0504: * instead of directly constructing TypeInfo objects in order to give the
0505: * DatabaseAdapter an opportunity to modify and/or correct the metadata
0506: * obtained from the JDBC driver.
0507: *
0508: * The type information object is constructed from the current row of the
0509: * given result set. The {@link ResultSet} object passed must have been
0510: * obtained from a call to DatabaseMetaData.getTypeInfo().
0511: *
0512: * <p>The constructor only retrieves the values from the current row; the
0513: * caller is required to advance to the next row with {@link ResultSet#next}.
0514: *
0515: * @param rs The result set returned from DatabaseMetaData.getTypeInfo().
0516: *
0517: * @return
0518: * A TypeInfo object constructed from the current result set row, or
0519: * <code>null</code> if the type indicated by this row should be
0520: * excluded from use.
0521: */
0522:
0523: protected TypeInfo newTypeInfo(ResultSet rs) {
0524: return new TypeInfo(rs);
0525: }
0526:
0527: /**
0528: * A factory for ColumnInfo objects. This method should always be used
0529: * instead of directly constructing ColumnInfo objects in order to give the
0530: * DatabaseAdapter an opportunity to modify and/or correct the metadata
0531: * obtained from the JDBC driver.
0532: *
0533: * The column information object is constructed from the current row of the
0534: * given result set. The {@link ResultSet} object passed must have been
0535: * obtained from a call to DatabaseMetaData.getColumns().
0536: *
0537: * <p>The constructor only retrieves the values from the current row; the
0538: * caller is required to advance to the next row with {@link ResultSet#next}.
0539: *
0540: * @param rs The result set returned from DatabaseMetaData.getColumns().
0541: */
0542:
0543: public ColumnInfo newColumnInfo(ResultSet rs) {
0544: return new ColumnInfo(rs);
0545: }
0546:
0547: /**
0548: * A factory for ForeignKeyInfo objects. This method should always be used
0549: * instead of directly constructing ForeignKeyInfo objects in order to give
0550: * the DatabaseAdapter an opportunity to modify and/or correct the metadata
0551: * obtained from the JDBC driver.
0552: *
0553: * The column information object is constructed from the current row of the
0554: * given result set. The {@link ResultSet} object passed must have been
0555: * obtained from a call to DatabaseMetaData.getImportedKeys() or
0556: * DatabaseMetaData.getExportedKeys().
0557: *
0558: * <p>The constructor only retrieves the values from the current row; the
0559: * caller is required to advance to the next row with {@link ResultSet#next}.
0560: *
0561: * @param rs The result set returned from DatabaseMetaData.get??portedKeys().
0562: */
0563:
0564: public ForeignKeyInfo newForeignKeyInfo(ResultSet rs) {
0565: return new ForeignKeyInfo(rs);
0566: }
0567:
0568: protected Set parseKeywordList(String list) {
0569: StringTokenizer tokens = new StringTokenizer(list, ",");
0570: HashSet words = new HashSet();
0571:
0572: while (tokens.hasMoreTokens())
0573: words.add(tokens.nextToken().trim().toUpperCase());
0574:
0575: return words;
0576: }
0577:
0578: /**
0579: * Tests if a given string is a SQL key word.
0580: * <p>
0581: * The list of key words tested against is defined to contain all SQL/92
0582: * key words, plus any additional key words reported by the JDBC driver
0583: * for this adapter via <code>DatabaseMetaData.getSQLKeywords()</code>.
0584: * <p>
0585: * In general, use of a SQL key word as an identifier should be avoided.
0586: * SQL/92 key words are divided into reserved and non-reserved words.
0587: * If a reserved word is used as an identifier it must be quoted with double
0588: * quotes.
0589: * Strictly speaking, the same is not true of non-reserved words.
0590: * However, as C.J. Date writes in <u>A Guide To The SQL Standard</u>:
0591: * <blockquote>
0592: * The rule by which it is determined within the standard that one key word
0593: * needs to be reserved while another need not is not clear to this writer.
0594: * In practice, it is probably wise to treat all key words as reserved.
0595: * </blockquote>
0596: *
0597: * @param word The word to test.
0598: *
0599: * @return <code>true</code> if <var>word</var> is a SQL key word for this
0600: * DBMS. The comparison is case-insensitive.
0601: *
0602: * @see SQL92Constants
0603: */
0604:
0605: public boolean isSQLKeyword(String word) {
0606: return keywords.contains(word.toUpperCase());
0607: }
0608:
0609: /**
0610: * Returns type information for the database type that best implements the
0611: * given JDBC type.
0612: *
0613: * @param dataType JDBC type number of the data type.
0614: *
0615: * @return type information for the best matching type.
0616: */
0617:
0618: public TypeInfo getTypeInfo(int dataType)
0619: throws UnsupportedDataTypeException {
0620: TypeInfo ti = (TypeInfo) typesByTypeNumber.get(new Integer(
0621: dataType));
0622:
0623: if (ti == null)
0624: throw new UnsupportedDataTypeException("JDBC type = "
0625: + TypeInfo.getJDBCTypeName(dataType));
0626:
0627: return ti;
0628: }
0629:
0630: /**
0631: * Returns type information for the first one of the given candidate JDBC
0632: * data types supported by this database.
0633: *
0634: * @param candidateDataTypes
0635: * array of JDBC type numbers of the data types to be checked
0636: * in order of preference.
0637: *
0638: * @return type information for the first supported type.
0639: */
0640:
0641: public TypeInfo getTypeInfo(int[] candidateDataTypes)
0642: throws UnsupportedDataTypeException {
0643: for (int i = 0; i < candidateDataTypes.length; ++i) {
0644: TypeInfo ti = (TypeInfo) typesByTypeNumber.get(new Integer(
0645: candidateDataTypes[i]));
0646:
0647: if (ti != null)
0648: return ti;
0649: }
0650:
0651: throw new UnsupportedDataTypeException(
0652: "No JDBC types specified are supported");
0653: }
0654:
0655: /**
0656: * Returns the precision value to be used when creating string columns of
0657: * "unlimited" length. Usually, if this value is needed it is provided in
0658: * the database metadata ({@link TypeInfo#precision}). However, for some
0659: * types in some databases the value must be computed specially.
0660: *
0661: * @param typeInfo the typeInfo object for which the precision value is
0662: * needed.
0663: *
0664: * @return the precision value to be used when creating the column, or -1
0665: * if no value should be used.
0666: */
0667:
0668: public int getUnlimitedLengthPrecisionValue(TypeInfo typeInfo) {
0669: if (typeInfo.createParams != null
0670: && typeInfo.createParams.length() > 0)
0671: return typeInfo.precision;
0672: else
0673: return -1;
0674: }
0675:
0676: public boolean isEmbeddedType(Class c) {
0677: return !OIDMapping.class.isAssignableFrom(getMappingClass(c));
0678: }
0679:
0680: public Mapping getMapping(Class c) {
0681: Class mc = getMappingClass(c);
0682:
0683: try {
0684: return (Mapping) newInstance(mc, new Class[] {
0685: DatabaseAdapter.class, Class.class }, new Object[] {
0686: this , c });
0687: } catch (NoSuchMethodException e) {
0688: String name = mc.getName();
0689: name = name.substring(name.lastIndexOf('.') + 1);
0690:
0691: throw new JDOFatalInternalException("Missing constructor "
0692: + mc.getName() + "(DatabaseAdapter, Class)");
0693: }
0694: }
0695:
0696: public ColumnMapping getMapping(Column col) {
0697: Class mc = getMappingClass(col.getType());
0698:
0699: try {
0700: return (ColumnMapping) newInstance(mc,
0701: new Class[] { Column.class }, new Object[] { col });
0702: } catch (NoSuchMethodException e) {
0703: String name = mc.getName();
0704: name = name.substring(name.lastIndexOf('.') + 1);
0705:
0706: throw new JDOUserException(
0707: name
0708: + " can only be used with a persistence-capable field");
0709: }
0710: }
0711:
0712: public Mapping getMapping(ClassBaseTable table,
0713: int relativeFieldNumber) {
0714: FieldMetaData fmd = table.getClassMetaData().getFieldRelative(
0715: relativeFieldNumber);
0716: Class mc = getMappingClass(fmd.getType());
0717:
0718: try {
0719: return (Mapping) newInstance(mc, new Class[] {
0720: ClassBaseTable.class, int.class }, new Object[] {
0721: table, new Integer(relativeFieldNumber) });
0722: } catch (NoSuchMethodException e) {
0723: throw new JDOFatalInternalException("Missing constructor "
0724: + mc.getName() + "(ClassBaseTable, int)");
0725: }
0726: }
0727:
0728: protected Class getMappingClass(Class c) {
0729: Class mappingClass = (Class) typeMappings.get(c);
0730:
0731: if (mappingClass == UnsupportedMapping.class)
0732: throw new JDOUnsupportedOptionException("Fields of type "
0733: + c.getName() + " not (yet) supported");
0734:
0735: if (mappingClass == null)
0736: mappingClass = PersistenceCapableMapping.class;
0737:
0738: return mappingClass;
0739: }
0740:
0741: private static Object newInstance(Class clazz,
0742: Class[] ctorArgTypes, Object[] ctorArgs)
0743: throws NoSuchMethodException {
0744: try {
0745: return clazz.getConstructor(ctorArgTypes).newInstance(
0746: ctorArgs);
0747: } catch (IllegalAccessException e) {
0748: throw new JDOFatalInternalException(
0749: "Can't access constructor for mapping object", e);
0750: } catch (InstantiationException e) {
0751: throw new JDOFatalInternalException(
0752: "Can't instantiate mapping object", e);
0753: } catch (InvocationTargetException e) {
0754: Throwable t = e.getTargetException();
0755:
0756: if (t instanceof Error)
0757: throw (Error) t;
0758: else if (t instanceof RuntimeException)
0759: throw (RuntimeException) t;
0760: else
0761: throw new JDOFatalInternalException("Constructor for "
0762: + clazz.getName() + " failed", e);
0763: }
0764: }
0765:
0766: public Connection getConnection(DataSource ds, String userName,
0767: String password, int isolationLevel) throws SQLException {
0768: Connection conn;
0769:
0770: if (userName == null)
0771: conn = ds.getConnection();
0772: else
0773: conn = ds.getConnection(userName, password);
0774:
0775: boolean succeeded = false;
0776:
0777: try {
0778: if (isolationLevel == Connection.TRANSACTION_NONE)
0779: conn.setAutoCommit(true);
0780: else {
0781: conn.setAutoCommit(false);
0782: conn.setTransactionIsolation(isolationLevel);
0783: }
0784:
0785: succeeded = true;
0786: } finally {
0787: if (!succeeded)
0788: conn.close();
0789: }
0790:
0791: return conn;
0792: }
0793:
0794: public void closeConnection(Connection conn) throws SQLException {
0795: conn.close();
0796: }
0797:
0798: public String getSchemaName(Connection conn) throws SQLException {
0799: throw new UnsupportedOperationException(
0800: "Don't know how to determine the current schema for this type of DBMS: "
0801: + databaseProductName + ' '
0802: + databaseProductVersion);
0803: }
0804:
0805: public String getIdentifierQuoteString() {
0806: return identifierQuoteString;
0807: }
0808:
0809: public boolean createIndexesBeforeForeignKeys() {
0810: return false;
0811: }
0812:
0813: public boolean includeOrderByColumnsInSelect() {
0814: return true;
0815: }
0816:
0817: public boolean supportsAlterTableDropConstraint() {
0818: return true;
0819: }
0820:
0821: public boolean supportsDeferredConstraints() {
0822: return true;
0823: }
0824:
0825: /**
0826: * Indicates whether or not two boolean expressions can be directly compared
0827: * to each other using the = or <> operator.
0828: * Some DBMS's allow you to say WHERE (X = 12) = (Y = 34), others don't.
0829: * <p>
0830: * The default value returned by the implementation in this class is
0831: * <code>true</code>.
0832: *
0833: * @return
0834: * <code>true</code> if boolean expressions can be compared,
0835: * <code>false</code> otherwise.
0836: */
0837:
0838: public boolean supportsBooleanComparison() {
0839: return true;
0840: }
0841:
0842: public boolean supportsNullsInCandidateKeys() {
0843: return true;
0844: }
0845:
0846: public QueryStatement newQueryStatement(Table table) {
0847: return new QueryStatement(table);
0848: }
0849:
0850: public QueryStatement newQueryStatement(Table table,
0851: SQLIdentifier rangeVar) {
0852: return new QueryStatement(table, rangeVar);
0853: }
0854:
0855: /**
0856: * Returns a new TableExpression object appropriate for this DBMS.
0857: * This should be an instance of one of the three built-in styles of table
0858: * expression:
0859: * <ul>
0860: * <li>TableExprAsJoins</li>
0861: * <li>TableExprAsSubjoins</li>
0862: * <li>TableExprAsSubquery</li>
0863: * </ul>
0864: * TableExprAsSubjoins is the default, which arguably produces the most
0865: * readable SQL but doesn't work on all DBMS's. TableExprAsSubjoins
0866: * should work anywhere, but may be less efficient.
0867: *
0868: * @param qs The query statement in which the table expression will
0869: * be included.
0870: * @param table The main table in the expression.
0871: * @param rangeVar The SQL alias, or "range variable", to assign to the
0872: * expression or to the main table.
0873: */
0874:
0875: public TableExpression newTableExpression(QueryStatement qs,
0876: Table table, SQLIdentifier rangeVar) {
0877: return new TableExprAsSubjoins(qs, table, rangeVar);
0878: }
0879:
0880: /**
0881: * Returns the appropriate SQL to create the given table having the given
0882: * columns. No column constraints or key definitions should be included.
0883: * It should return something like:
0884: * <p>
0885: * <blockquote><pre>
0886: * CREATE TABLE FOO ( BAR VARCHAR(30), BAZ INTEGER )
0887: * </pre></blockquote>
0888: *
0889: * @param table The table to create.
0890: * @param columns The columns of the table.
0891: *
0892: * @return The text of the SQL statement.
0893: */
0894:
0895: public String getCreateTableStatement(BaseTable table,
0896: Column[] columns) {
0897: StringBuffer createStmt = new StringBuffer();
0898:
0899: createStmt.append("CREATE TABLE ").append(table.getName())
0900: .append("\n(\n");
0901: for (int i = 0; i < columns.length; ++i) {
0902: if (i > 0)
0903: createStmt.append(",\n");
0904:
0905: createStmt.append(" ").append(
0906: columns[i].getSQLDefinition());
0907: }
0908:
0909: createStmt.append("\n)");
0910:
0911: return createStmt.toString();
0912: }
0913:
0914: /**
0915: * Returns the appropriate SQL to add a primary key to its table.
0916: * It should return something like:
0917: * <p>
0918: * <blockquote><pre>
0919: * ALTER TABLE FOO ADD CONSTRAINT FOO_PK PRIMARY KEY (BAR)
0920: * </pre></blockquote>
0921: *
0922: * @param pkName The name of the primary key to add.
0923: * @param pk An object describing the primary key.
0924: *
0925: * @return The text of the SQL statement.
0926: */
0927:
0928: public String getAddPrimaryKeyStatement(SQLIdentifier pkName,
0929: PrimaryKey pk) {
0930: return "ALTER TABLE " + pk.getTable().getName()
0931: + " ADD CONSTRAINT " + pkName + ' ' + pk;
0932: }
0933:
0934: /**
0935: * Returns the appropriate SQL to add a candidate key to its table.
0936: * It should return something like:
0937: * <p>
0938: * <blockquote><pre>
0939: * ALTER TABLE FOO ADD CONSTRAINT FOO_CK UNIQUE (BAZ)
0940: * </pre></blockquote>
0941: *
0942: * @param ckName The name of the candidate key to add.
0943: * @param ck An object describing the candidate key.
0944: *
0945: * @return The text of the SQL statement.
0946: */
0947:
0948: public String getAddCandidateKeyStatement(SQLIdentifier ckName,
0949: CandidateKey ck) {
0950: return "ALTER TABLE " + ck.getTable().getName()
0951: + " ADD CONSTRAINT " + ckName + ' ' + ck;
0952: }
0953:
0954: /**
0955: * Returns the appropriate SQL to add a foreign key to its table.
0956: * It should return something like:
0957: * <p>
0958: * <blockquote><pre>
0959: * ALTER TABLE FOO ADD CONSTRAINT FOO_FK1 FOREIGN KEY (BAR, BAZ) REFERENCES ABC (COL1, COL2)
0960: * </pre></blockquote>
0961: *
0962: * @param fkName The name of the foreign key to add.
0963: * @param fk An object describing the foreign key.
0964: *
0965: * @return The text of the SQL statement.
0966: */
0967:
0968: public String getAddForeignKeyStatement(SQLIdentifier fkName,
0969: ForeignKey fk) {
0970: return "ALTER TABLE " + fk.getTable().getName()
0971: + " ADD CONSTRAINT " + fkName + ' ' + fk;
0972: }
0973:
0974: /**
0975: * Returns the appropriate SQL to add an index to its table.
0976: * It should return something like:
0977: * <p>
0978: * <blockquote><pre>
0979: * CREATE INDEX FOO_N1 ON FOO (BAR,BAZ)
0980: * CREATE UNIQUE INDEX FOO_U1 ON FOO (BAR,BAZ)
0981: * </pre></blockquote>
0982: *
0983: * @param idxName The name of the index to add.
0984: * @param idx An object describing the index.
0985: *
0986: * @return The text of the SQL statement.
0987: */
0988:
0989: public String getCreateIndexStatement(SQLIdentifier idxName,
0990: Index idx) {
0991: return "CREATE " + (idx.getUnique() ? "UNIQUE " : "")
0992: + "INDEX " + idxName + " ON "
0993: + idx.getTable().getName() + ' ' + idx;
0994: }
0995:
0996: /**
0997: * Returns the appropriate SQL to drop the given table.
0998: * It should return something like:
0999: * <p>
1000: * <blockquote><pre>
1001: * DROP TABLE FOO CASCADE
1002: * </pre></blockquote>
1003: *
1004: * @param table The table to drop.
1005: *
1006: * @return The text of the SQL statement.
1007: */
1008:
1009: public String getDropTableStatement(BaseTable table) {
1010: return "DROP TABLE " + table.getName() + " CASCADE";
1011: }
1012:
1013: /**
1014: * Returns the appropriate SQL to drop the given view.
1015: * It should return something like:
1016: * <p>
1017: * <blockquote><pre>
1018: * DROP VIEW FOO
1019: * </pre></blockquote>
1020: *
1021: * @param view The view to drop.
1022: *
1023: * @return The text of the SQL statement.
1024: */
1025:
1026: public String getDropViewStatement(View view) {
1027: return "DROP VIEW " + view.getName();
1028: }
1029:
1030: /**
1031: * Returns the appropriate SQL expression for the JDOQL String.length()
1032: * method.
1033: * It should return something like:
1034: * <p>
1035: * <blockquote><pre>
1036: * CHAR_LENGTH(str)
1037: * </pre></blockquote>
1038: *
1039: * @param str The argument to the length() method.
1040: *
1041: * @return The text of the SQL expression.
1042: */
1043:
1044: public NumericExpression lengthMethod(CharacterExpression str) {
1045: ArrayList args = new ArrayList();
1046: args.add(str);
1047:
1048: return new NumericExpression("CHAR_LENGTH", args);
1049: }
1050:
1051: /**
1052: * Returns the appropriate SQL expression for the JDOQL
1053: * String.substring(str,begin) method.
1054: * It should return something like:
1055: * <p>
1056: * <blockquote><pre>
1057: * SUBSTRING(str FROM begin)
1058: * </pre></blockquote>
1059: * Note that the value of <var>begin</var> is base 0 (Java-style), while most
1060: * SQL string functions use base 1.
1061: *
1062: * @param str The first argument to the substring() method.
1063: * @param begin The second argument to the substring() method.
1064: *
1065: * @return The text of the SQL expression.
1066: */
1067:
1068: public CharacterExpression substringMethod(CharacterExpression str,
1069: NumericExpression begin) {
1070: return new SubstringExpression(str, begin);
1071: }
1072:
1073: /**
1074: * Returns the appropriate SQL expression for the JDOQL
1075: * String.substring(str,begin,end) method.
1076: * It should return something like:
1077: * <p>
1078: * <blockquote><pre>
1079: * SUBSTRING(str FROM begin FOR len)
1080: * </pre></blockquote>
1081: * Note that the value of <var>begin</var> is base 0 (Java-style), while most
1082: * SQL string functions use base 1.
1083: * Note also that an end position is given, while most SQL substring
1084: * functions take a length.
1085: *
1086: * @param str The first argument to the substring() method.
1087: * @param begin The second argument to the substring() method.
1088: * @param end The third argument to the substring() method.
1089: *
1090: * @return The text of the SQL expression.
1091: */
1092:
1093: public CharacterExpression substringMethod(CharacterExpression str,
1094: NumericExpression begin, NumericExpression end) {
1095: return new SubstringExpression(str, begin, end);
1096: }
1097:
1098: public String toString() {
1099: String className = getClass().getName();
1100: String name = className
1101: .substring(className.lastIndexOf('.') + 1);
1102:
1103: return name + ", " + databaseProductName + " version "
1104: + databaseProductVersion + " major "
1105: + databaseMajorVersion + " minor "
1106: + databaseMinorVersion;
1107: }
1108:
1109: private static class UnsupportedMapping {
1110: private UnsupportedMapping() {
1111: }
1112: }
1113: }
|