KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > triactive > jdo > store > DatabaseAdapter


1 /*
2  * Copyright 2004 (C) TJDO.
3  * All rights reserved.
4  *
5  * This software is distributed under the terms of the TJDO License version 1.0.
6  * See the terms of the TJDO License in the documentation provided with this software.
7  *
8  * $Id: DatabaseAdapter.java,v 1.30 2004/03/30 06:12:14 jackknifebarber Exp $
9  */

10
11 package com.triactive.jdo.store;
12
13 import com.triactive.jdo.model.FieldMetaData;
14 import java.lang.reflect.InvocationTargetException JavaDoc;
15 import java.sql.Connection JavaDoc;
16 import java.sql.DatabaseMetaData JavaDoc;
17 import java.sql.ResultSet JavaDoc;
18 import java.sql.SQLException JavaDoc;
19 import java.util.ArrayList JavaDoc;
20 import java.util.HashMap JavaDoc;
21 import java.util.HashSet JavaDoc;
22 import java.util.Set JavaDoc;
23 import java.util.StringTokenizer JavaDoc;
24 import javax.jdo.JDODataStoreException;
25 import javax.jdo.JDOException;
26 import javax.jdo.JDOFatalDataStoreException;
27 import javax.jdo.JDOFatalInternalException;
28 import javax.jdo.JDOUnsupportedOptionException;
29 import javax.jdo.JDOUserException;
30 import javax.sql.DataSource JavaDoc;
31 import org.apache.log4j.Category;
32
33
34 /**
35  * Provides methods for adapting SQL language elements to a specific vendor's
36  * database. A database adapter is primarily used to map generic JDBC data
37  * types and SQL identifiers to specific types/identifiers suitable for the
38  * database in use.
39  *
40  * <p>Each database adapter corresponds to a particular combination of database,
41  * database version, driver, and driver version, as provided by the driver's
42  * own metadata. Database adapters cannot be constructed directly, but must be
43  * obtained using the {@link #getInstance} method.
44  *
45  * @author <a HREF="mailto:mmartin5@austin.rr.com">Mike Martin</a>
46  * @author <a HREF="mailto:cwalk@triactive.com">Christopher Walk</a>
47  * @version $Revision: 1.30 $
48  *
49  * @see java.sql.DatabaseMetaData
50  */

51 public class DatabaseAdapter
52 {
53     private static final Category LOG = Category.getInstance(DatabaseAdapter.class);
54
55
56     /** The name of the underlying database. */
57     protected String JavaDoc databaseProductName;
58
59     /** The version number of the underlying database as a string. */
60     protected String JavaDoc databaseProductVersion;
61
62     /** The major version number of the underlying database. */
63     protected int databaseMajorVersion;
64
65     /** The minor version number of the underlying database. */
66     protected int databaseMinorVersion;
67
68     /** The maximum length to be used for a table name. */
69     protected int maxTableNameLength;
70
71     /** The maximum length to be used for a table constraint name. */
72     protected int maxConstraintNameLength;
73
74     /** The maximum length to be used for an index name. */
75     protected int maxIndexNameLength;
76
77     /** The maximum length to be used for a column name. */
78     protected int maxColumnNameLength;
79
80     /** <tt>true</tt> if the database stores all identifiers in lower-case. */
81     protected boolean storesLowerCaseIdentifiers;
82
83     /** <tt>true</tt> if the database stores all identifiers in upper-case. */
84     protected boolean storesUpperCaseIdentifiers;
85
86     /** The String used to quote SQL identifiers. */
87     protected String JavaDoc identifierQuoteString;
88
89     /** The set of SQL keywords for this DBMS, in upper-case. */
90     protected final HashSet JavaDoc keywords = new HashSet JavaDoc();
91
92     protected final HashMap JavaDoc typesByTypeNumber = new HashMap JavaDoc();
93     protected final HashMap JavaDoc typeMappings = new HashMap JavaDoc();
94
95     /**
96      * The cache of constructed database adapter objects.
97      */

98     private static HashMap JavaDoc adaptersByID = new HashMap JavaDoc();
99
100
101     /**
102      * Returns a <tt>DatabaseAdapter</tt> object appropriate for the database
103      * currently underlying the given {@link Connection}. Multiple calls to
104      * this method with connections having the same database/driver/version will
105      * return the same <tt>DatabaseAdapter</tt> object.
106      *
107      * @param conn An open database connection.
108      *
109      * @return a database adapter object for the database underlying the
110      * given connection.
111      *
112      * @exception SQLException
113      * If a database error occurs.
114      */

115     public static synchronized DatabaseAdapter getInstance(Connection JavaDoc conn) throws SQLException JavaDoc
116     {
117         DatabaseMetaData JavaDoc metadata = conn.getMetaData();
118
119         String JavaDoc id = metadata.getDatabaseProductName() + ", "
120                   + metadata.getDatabaseProductVersion() + ", "
121                   + metadata.getDriverName() + ", "
122                   + metadata.getDriverVersion();
123
124         DatabaseAdapter adapter = (DatabaseAdapter)adaptersByID.get(id);
125
126         if (adapter == null)
127         {
128             if (isCloudscape(metadata))
129                 adapter = new CloudscapeAdapter(metadata);
130             else if (isDB2J(metadata))
131                 adapter = new DB2JAdapter(metadata);
132             else if (isDB2(metadata))
133                 adapter = new DB2Adapter(metadata);
134             else if (isFirebird(metadata))
135                 adapter = new FirebirdAdapter(metadata);
136             else if (isHSQLDB(metadata))
137                 adapter = new HSQLDBAdapter(metadata);
138             else if (isMSSQLServer(metadata))
139                 adapter = new MSSQLServerAdapter(metadata);
140             else if (isMySQL(metadata))
141                 adapter = new MySQLAdapter(metadata);
142             else if (isOracle(metadata))
143             {
144                 /* NOTE: Reflection is used here as a temporary means of
145                  * eliminating the dependency between this class
146                  * and the OracleAdapter. OracleAdapater indirectly
147                  * utilizes classes that are shipped with the
148                  * Oracle drivers which may not be available to
149                  * all users.
150                  */

151                 try
152                 {
153                     Class JavaDoc classDefinition = Class.forName("com.triactive.jdo.store.OracleAdapter");
154                     
155                     adapter = (DatabaseAdapter) newInstance(classDefinition,
156                                                             new Class JavaDoc[] { DatabaseMetaData JavaDoc.class },
157                                                             new Object JavaDoc[] { metadata } );
158                 }
159                 catch (Exception JavaDoc e)
160                 {
161                     throw new JDODataStoreException("The Oracle adapter was not found.", e);
162                 }
163             }
164             else if (isPointBase(metadata))
165                 adapter = new PointBaseAdapter(metadata);
166             else if (isPostgreSQL(metadata))
167                 adapter = new PostgreSQLAdapter(metadata);
168             else if (isSAPDB(metadata))
169                 adapter = new SAPDBAdapter(metadata);
170             else
171                 adapter = new DatabaseAdapter(metadata);
172
173             adaptersByID.put(id, adapter);
174
175             LOG.info("Adapter initialized: " + adapter);
176         }
177
178         return adapter;
179     }
180
181     private static boolean productNameContains(DatabaseMetaData JavaDoc metadata, String JavaDoc name)
182     {
183         try
184         {
185             return metadata.getDatabaseProductName().toLowerCase().indexOf(name.toLowerCase()) >= 0;
186         }
187         catch (SQLException JavaDoc e)
188         {
189             throw new JDODataStoreException("Error accessing database metadata", e);
190         }
191     }
192
193     private static boolean isCloudscape(DatabaseMetaData JavaDoc metadata)
194     {
195         return productNameContains(metadata, "cloudscape");
196     }
197
198     private static boolean isDB2(DatabaseMetaData JavaDoc metadata)
199     {
200         return productNameContains(metadata, "db2") &&
201                !productNameContains(metadata, "db2j");
202     }
203
204     private static boolean isDB2J(DatabaseMetaData JavaDoc metadata)
205     {
206         return productNameContains(metadata, "db2j");
207     }
208
209     private static boolean isFirebird(DatabaseMetaData JavaDoc metadata)
210     {
211         return productNameContains(metadata, "firebird");
212     }
213
214     private static boolean isHSQLDB(DatabaseMetaData JavaDoc metadata)
215     {
216         return productNameContains(metadata, "hsql database engine") ||
217                productNameContains(metadata, "hsqldb");
218     }
219
220     private static boolean isMSSQLServer(DatabaseMetaData JavaDoc metadata)
221     {
222         return productNameContains(metadata, "sql server");
223     }
224
225     private static boolean isMySQL(DatabaseMetaData JavaDoc metadata)
226     {
227         return productNameContains(metadata, "mysql");
228     }
229
230     private static boolean isOracle(DatabaseMetaData JavaDoc metadata)
231     {
232         return productNameContains(metadata, "oracle");
233     }
234
235     private static boolean isPointBase(DatabaseMetaData JavaDoc metadata)
236     {
237         return productNameContains(metadata, "pointbase");
238     }
239
240     private static boolean isPostgreSQL(DatabaseMetaData JavaDoc metadata)
241     {
242         return productNameContains(metadata, "postgresql");
243     }
244
245     private static boolean isSAPDB(DatabaseMetaData JavaDoc metadata)
246     {
247         return productNameContains(metadata, "sapdb") ||
248                productNameContains(metadata, "sap db");
249     }
250
251
252     /**
253      * Constructs a database adapter based on the given JDBC metadata.
254      *
255      * @param metadata the database metadata.
256      */

257
258     protected DatabaseAdapter(DatabaseMetaData JavaDoc metadata)
259     {
260         keywords.addAll(parseKeywordList(SQL92Constants.RESERVED_WORDS));
261         keywords.addAll(parseKeywordList(SQL92Constants.NONRESERVED_WORDS));
262
263         try
264         {
265             keywords.addAll(parseKeywordList(metadata.getSQLKeywords()));
266
267             databaseProductName = metadata.getDatabaseProductName();
268             databaseProductVersion = metadata.getDatabaseProductVersion();
269
270             try
271             {
272                 Class JavaDoc mdc = metadata.getClass();
273
274                 databaseMajorVersion = ((Integer JavaDoc)mdc.getMethod("getDatabaseMajorVersion", null).invoke(metadata, null)).intValue();
275                 databaseMinorVersion = ((Integer JavaDoc)mdc.getMethod("getDatabaseMinorVersion", null).invoke(metadata, null)).intValue();
276             }
277             catch (Throwable JavaDoc t)
278             {
279                 /*
280                  * The driver doesn't support JDBC 3. Try to parse major and
281                  * minor version numbers out of the product version string.
282                  * We do this by stripping out everything but digits and periods
283                  * and hoping we get something that looks like <major>.<minor>.
284                  */

285                 StringBuffer JavaDoc stripped = new StringBuffer JavaDoc();
286
287                 for (int i = 0; i < databaseProductVersion.length(); ++i)
288                 {
289                     char c = databaseProductVersion.charAt(i);
290
291                     if (Character.isDigit(c) || c == '.')
292                         stripped.append(c);
293                 }
294
295                 StringTokenizer JavaDoc parts = new StringTokenizer JavaDoc(stripped.toString(), ".");
296
297                 if (parts.hasMoreTokens())
298                     databaseMajorVersion = Integer.parseInt(parts.nextToken());
299                 if (parts.hasMoreTokens())
300                     databaseMinorVersion = Integer.parseInt(parts.nextToken());
301             }
302
303             maxTableNameLength = metadata.getMaxTableNameLength();
304             /*
305              * The metadata object may return 0 for getMaxTableNameLength to
306              * indicate that there is no table name length. If that is the case,
307              * we will use SQL92Constants.MAX_IDENTIFIER_LENGTH for the
308              * maximum length.
309              */

310             if(maxTableNameLength == 0) {
311                 maxTableNameLength = SQL92Constants.MAX_IDENTIFIER_LENGTH;
312             }
313
314             // use maxTableNameLength for maxConstraintNameLength and maxIndexNameLength
315
maxConstraintNameLength = maxTableNameLength;
316             maxIndexNameLength = maxTableNameLength;
317
318             maxColumnNameLength = metadata.getMaxColumnNameLength();
319             /*
320              * The metadata object may return 0 for getMaxColumnNameLength to
321              * indicate that there is no column name length. If that is the case,
322              * we will use SQL92Constants.MAX_IDENTIFIER_LENGTH for the
323              * maximum length.
324              */

325             if(maxColumnNameLength == 0) {
326                 maxColumnNameLength = SQL92Constants.MAX_IDENTIFIER_LENGTH;
327             }
328
329             storesLowerCaseIdentifiers = metadata.storesLowerCaseIdentifiers();
330             storesUpperCaseIdentifiers = metadata.storesUpperCaseIdentifiers();
331             identifierQuoteString = metadata.getIdentifierQuoteString();
332
333             /*
334              * If this is null or an empty String, default to double-quote.
335              */

336             identifierQuoteString =
337                 ((null == identifierQuoteString) || (identifierQuoteString.trim().length() < 1))
338                 ? "\""
339                 : identifierQuoteString;
340
341             /*
342              * Create TypeInfo objects for all of the data types and index them
343              * in a HashMap by their JDBC type number.
344              */

345             createTypeInfo(metadata);
346
347         }
348         catch (SQLException JavaDoc e)
349         {
350             throw new JDODataStoreException("Error accessing database metadata", e);
351         }
352
353         typeMappings.put(boolean.class, BooleanMapping.class);
354         typeMappings.put(byte.class, ByteMapping.class);
355         typeMappings.put(byte[].class, ByteArrayMapping.class);
356         typeMappings.put(char.class, CharacterMapping.class);
357         typeMappings.put(short.class, ShortMapping.class);
358         typeMappings.put(int.class, IntegerMapping.class);
359         typeMappings.put(long.class, LongMapping.class);
360         typeMappings.put(float.class, FloatMapping.class);
361         typeMappings.put(double.class, DoubleMapping.class);
362         typeMappings.put(Boolean JavaDoc.class, BooleanMapping.class);
363         typeMappings.put(Byte JavaDoc.class, ByteMapping.class);
364         typeMappings.put(Character JavaDoc.class, CharacterMapping.class);
365         typeMappings.put(Short JavaDoc.class, ShortMapping.class);
366         typeMappings.put(Integer JavaDoc.class, IntegerMapping.class);
367         typeMappings.put(Long JavaDoc.class, LongMapping.class);
368         typeMappings.put(Float JavaDoc.class, FloatMapping.class);
369         typeMappings.put(Double JavaDoc.class, DoubleMapping.class);
370         typeMappings.put(String JavaDoc.class, StringMapping.class);
371         typeMappings.put(java.math.BigDecimal JavaDoc.class, BigDecimalMapping.class);
372         typeMappings.put(java.math.BigInteger JavaDoc.class, BigIntegerMapping.class);
373         typeMappings.put(java.util.Date JavaDoc.class, DateMapping.class);
374         typeMappings.put(java.util.Locale JavaDoc.class, UnsupportedMapping.class);
375         typeMappings.put(java.sql.Date JavaDoc.class, SqlDateMapping.class);
376         typeMappings.put(java.sql.Timestamp JavaDoc.class, SqlTimestampMapping.class);
377         typeMappings.put(OID.class, OIDMapping.class);
378
379         typeMappings.put(java.util.ArrayList JavaDoc.class, UnsupportedMapping.class);
380         typeMappings.put(java.util.Collection JavaDoc.class, SetMapping.class);
381         typeMappings.put(java.util.HashMap JavaDoc.class, MapMapping.class);
382         typeMappings.put(java.util.HashSet JavaDoc.class, SetMapping.class);
383         typeMappings.put(java.util.Hashtable JavaDoc.class, MapMapping.class);
384         typeMappings.put(java.util.LinkedList JavaDoc.class, UnsupportedMapping.class);
385         typeMappings.put(java.util.List JavaDoc.class, UnsupportedMapping.class);
386         typeMappings.put(java.util.Map JavaDoc.class, MapMapping.class);
387         typeMappings.put(java.util.Set JavaDoc.class, SetMapping.class);
388         typeMappings.put(java.util.TreeMap JavaDoc.class, MapMapping.class);
389         typeMappings.put(java.util.TreeSet JavaDoc.class, SetMapping.class);
390         typeMappings.put(java.util.Vector JavaDoc.class, UnsupportedMapping.class);
391     }
392
393
394     public String JavaDoc getVendorID()
395     {
396         return null;
397     }
398
399
400     public int getMaxTableNameLength()
401     {
402         return maxTableNameLength;
403     }
404
405
406     public int getMaxConstraintNameLength()
407     {
408         return maxConstraintNameLength;
409     }
410
411
412     public int getMaxIndexNameLength()
413     {
414         return maxIndexNameLength;
415     }
416
417
418     public int getMaxColumnNameLength()
419     {
420         return maxColumnNameLength;
421     }
422
423
424     public boolean storesLowerCaseIdentifiers()
425     {
426         return storesLowerCaseIdentifiers;
427     }
428
429
430     public boolean storesUpperCaseIdentifiers()
431     {
432         return storesUpperCaseIdentifiers;
433     }
434
435
436     /**
437      * Returns a SQLState object for the specified SQLException, if one is
438      * present and valid.
439      *
440      * @param se
441      * A caught SQL exception.
442      *
443      * @return
444      * A SQLState object, or <code>null</code> if <var>se</var> does not
445      * contain a valid 5-character SQLSTATE.
446      */

447
448     public SQLState getSQLState(SQLException JavaDoc se)
449     {
450         String JavaDoc state = se.getSQLState();
451
452         if (state == null)
453             return null;
454
455         try
456         {
457             return new SQLState(state);
458         }
459         catch (IllegalArgumentException JavaDoc e)
460         {
461             return null;
462         }
463     }
464
465
466     /**
467      * Create the appropriate <code>JDODataStoreException</code> or
468      * <code>JDOFatalDataStoreException</code> for the given
469      * <code>SQLException</code> based on whether the action causing
470      * the exception has aborted the current transaction.
471      * <p>
472      * For historical reasons, the design of this method is flawed.
473      * To conform correctly to the spec, if a JDOFatalDataStoreException is
474      * returned then there should be some coordination with the appropriate
475      * PersistenceManager and its Transaction to allow them to reflect the fact
476      * that a transaction is no longer active.
477      * At the least, that means that this method would have to be passed a
478      * reference to a PersistenceManager.
479      * <p>
480      * An outstanding question remains how we can reliably determine via JDBC
481      * whether or not a failed statement has aborted the current database
482      * transaction.
483      * Bottom line, this area is ripe for refactoring.
484      * <p>
485      * The current implementation in this class always returns a new
486      * JDODataStoreException and never a JDOFatalDataStoreException.
487      *
488      * @param message The message to include in the JDODataStoreException.
489      * @param e The SQLException to create a JDODataStoreException for.
490      *
491      * @return A <code>JDODataStoreException</code> or
492      * <code>JDOFatalDataStoreException</code> that wraps the
493      * given <code>SQLException</code>. A fatal exception is used
494      * to indicate that the active transaction has been aborted.
495      */

496
497     public JDOException newDataStoreException(String JavaDoc message, SQLException JavaDoc e)
498     {
499         return new JDODataStoreException(message, e);
500     }
501
502
503     /**
504      * Creates TypeInfo objects for all of the data types and indexes them
505      * in the typesByTypeNumber map by their JDBC data type number.
506      */

507
508     protected void createTypeInfo(DatabaseMetaData JavaDoc metadata) throws SQLException JavaDoc
509     {
510         ResultSet JavaDoc rs = metadata.getTypeInfo();
511
512         try
513         {
514             while (rs.next())
515             {
516                 TypeInfo ti = newTypeInfo(rs);
517
518                 if (ti != null)
519                 {
520                     Integer JavaDoc key = new Integer JavaDoc(ti.dataType);
521
522                     if (typesByTypeNumber.get(key) == null)
523                         typesByTypeNumber.put(key, ti);
524                 }
525             }
526         }
527         finally
528         {
529             rs.close();
530         }
531     }
532
533
534     /**
535      * A factory for TypeInfo objects. This method should always be used
536      * instead of directly constructing TypeInfo objects in order to give the
537      * DatabaseAdapter an opportunity to modify and/or correct the metadata
538      * obtained from the JDBC driver.
539      *
540      * The type information object is constructed from the current row of the
541      * given result set. The {@link ResultSet} object passed must have been
542      * obtained from a call to DatabaseMetaData.getTypeInfo().
543      *
544      * <p>The constructor only retrieves the values from the current row; the
545      * caller is required to advance to the next row with {@link ResultSet#next}.
546      *
547      * @param rs The result set returned from DatabaseMetaData.getTypeInfo().
548      *
549      * @return
550      * A TypeInfo object constructed from the current result set row, or
551      * <code>null</code> if the type indicated by this row should be
552      * excluded from use.
553      */

554
555     protected TypeInfo newTypeInfo(ResultSet JavaDoc rs)
556     {
557         return new TypeInfo(rs);
558     }
559
560
561     /**
562      * A factory for ColumnInfo objects. This method should always be used
563      * instead of directly constructing ColumnInfo objects in order to give the
564      * DatabaseAdapter an opportunity to modify and/or correct the metadata
565      * obtained from the JDBC driver.
566      *
567      * The column information object is constructed from the current row of the
568      * given result set. The {@link ResultSet} object passed must have been
569      * obtained from a call to DatabaseMetaData.getColumns().
570      *
571      * <p>The constructor only retrieves the values from the current row; the
572      * caller is required to advance to the next row with {@link ResultSet#next}.
573      *
574      * @param rs The result set returned from DatabaseMetaData.getColumns().
575      */

576
577     public ColumnInfo newColumnInfo(ResultSet JavaDoc rs)
578     {
579         return new ColumnInfo(rs);
580     }
581
582
583     /**
584      * A factory for ForeignKeyInfo objects. This method should always be used
585      * instead of directly constructing ForeignKeyInfo objects in order to give
586      * the DatabaseAdapter an opportunity to modify and/or correct the metadata
587      * obtained from the JDBC driver.
588      *
589      * The column information object is constructed from the current row of the
590      * given result set. The {@link ResultSet} object passed must have been
591      * obtained from a call to DatabaseMetaData.getImportedKeys() or
592      * DatabaseMetaData.getExportedKeys().
593      *
594      * <p>The constructor only retrieves the values from the current row; the
595      * caller is required to advance to the next row with {@link ResultSet#next}.
596      *
597      * @param rs The result set returned from DatabaseMetaData.get??portedKeys().
598      */

599
600     public ForeignKeyInfo newForeignKeyInfo(ResultSet JavaDoc rs)
601     {
602         return new ForeignKeyInfo(rs);
603     }
604
605
606     protected Set JavaDoc parseKeywordList(String JavaDoc list)
607     {
608         StringTokenizer JavaDoc tokens = new StringTokenizer JavaDoc(list, ",");
609         HashSet JavaDoc words = new HashSet JavaDoc();
610
611         while (tokens.hasMoreTokens())
612             words.add(tokens.nextToken().trim().toUpperCase());
613
614         return words;
615     }
616
617
618     /**
619      * Tests if a given string is a SQL key word.
620      * <p>
621      * The list of key words tested against is defined to contain all SQL/92
622      * key words, plus any additional key words reported by the JDBC driver
623      * for this adapter via <code>DatabaseMetaData.getSQLKeywords()</code>.
624      * <p>
625      * In general, use of a SQL key word as an identifier should be avoided.
626      * SQL/92 key words are divided into reserved and non-reserved words.
627      * If a reserved word is used as an identifier it must be quoted with double
628      * quotes.
629      * Strictly speaking, the same is not true of non-reserved words.
630      * However, as C.J. Date writes in <u>A Guide To The SQL Standard</u>:
631      * <blockquote>
632      * The rule by which it is determined within the standard that one key word
633      * needs to be reserved while another need not is not clear to this writer.
634      * In practice, it is probably wise to treat all key words as reserved.
635      * </blockquote>
636      *
637      * @param word The word to test.
638      *
639      * @return <code>true</code> if <var>word</var> is a SQL key word for this
640      * DBMS. The comparison is case-insensitive.
641      *
642      * @see SQL92Constants
643      */

644
645     public boolean isSQLKeyword(String JavaDoc word)
646     {
647         return keywords.contains(word.toUpperCase());
648     }
649
650
651     /**
652      * Returns type information for the database type that best implements the
653      * given JDBC type.
654      *
655      * @param dataType JDBC type number of the data type.
656      *
657      * @return type information for the best matching type.
658      */

659
660     public TypeInfo getTypeInfo(int dataType) throws UnsupportedDataTypeException
661     {
662         TypeInfo ti = (TypeInfo)typesByTypeNumber.get(new Integer JavaDoc(dataType));
663
664         if (ti == null)
665             throw new UnsupportedDataTypeException("JDBC type = " + TypeInfo.getJDBCTypeName(dataType));
666
667         return ti;
668     }
669
670
671     /**
672      * Returns type information for the first one of the given candidate JDBC
673      * data types supported by this database.
674      *
675      * @param candidateDataTypes
676      * array of JDBC type numbers of the data types to be checked
677      * in order of preference.
678      *
679      * @return type information for the first supported type.
680      */

681
682     public TypeInfo getTypeInfo(int[] candidateDataTypes) throws UnsupportedDataTypeException
683     {
684         for (int i = 0; i < candidateDataTypes.length; ++i)
685         {
686             TypeInfo ti = (TypeInfo)typesByTypeNumber.get(new Integer JavaDoc(candidateDataTypes[i]));
687
688             if (ti != null)
689                 return ti;
690         }
691
692         throw new UnsupportedDataTypeException("No JDBC types specified are supported");
693     }
694
695
696     /**
697      * Returns the precision value to be used when creating string columns of
698      * "unlimited" length. Usually, if this value is needed it is provided in
699      * the database metadata ({@link TypeInfo#precision}). However, for some
700      * types in some databases the value must be computed specially.
701      *
702      * @param typeInfo the typeInfo object for which the precision value is
703      * needed.
704      *
705      * @return the precision value to be used when creating the column, or -1
706      * if no value should be used.
707      */

708
709     public int getUnlimitedLengthPrecisionValue(TypeInfo typeInfo)
710     {
711         if (typeInfo.createParams != null && typeInfo.createParams.length() > 0)
712             return typeInfo.precision;
713         else
714             return -1;
715     }
716
717     public boolean isEmbeddedType(Class JavaDoc c)
718     {
719         return !OIDMapping.class.isAssignableFrom(getMappingClass(c));
720     }
721
722     public Mapping getMapping(Class JavaDoc c)
723     {
724         Class JavaDoc mc = getMappingClass(c);
725
726         try
727         {
728             return (Mapping)newInstance(mc,
729                                         new Class JavaDoc[] { DatabaseAdapter.class, Class JavaDoc.class },
730                                         new Object JavaDoc[] { this, c });
731         }
732         catch (NoSuchMethodException JavaDoc e)
733         {
734             String JavaDoc name = mc.getName();
735             name = name.substring(name.lastIndexOf('.') + 1);
736
737             throw new JDOFatalInternalException("Missing constructor " + mc.getName() + "(DatabaseAdapter, Class)");
738         }
739     }
740
741     public ColumnMapping getMapping(Column col)
742     {
743         Class JavaDoc mc = getMappingClass(col.getType());
744
745         try
746         {
747             return (ColumnMapping)newInstance(mc,
748                                               new Class JavaDoc[] { Column.class },
749                                               new Object JavaDoc[] { col });
750         }
751         catch (NoSuchMethodException JavaDoc e)
752         {
753             String JavaDoc name = mc.getName();
754             name = name.substring(name.lastIndexOf('.') + 1);
755
756             throw new JDOUserException(name + " can only be used with a persistence-capable field");
757         }
758     }
759
760     public Mapping getMapping(ClassBaseTable table, int relativeFieldNumber)
761     {
762         FieldMetaData fmd = table.getClassMetaData().getFieldRelative(relativeFieldNumber);
763         Class JavaDoc mc = getMappingClass(fmd.getType());
764
765         try
766         {
767             return (Mapping)newInstance(mc,
768                                         new Class JavaDoc[] { ClassBaseTable.class, int.class },
769                                         new Object JavaDoc[] { table, new Integer JavaDoc(relativeFieldNumber) });
770         }
771         catch (NoSuchMethodException JavaDoc e)
772         {
773             throw new JDOFatalInternalException("Missing constructor " + mc.getName() + "(ClassBaseTable, int)");
774         }
775     }
776
777     protected Class JavaDoc getMappingClass(Class JavaDoc c)
778     {
779         Class JavaDoc mappingClass = (Class JavaDoc)typeMappings.get(c);
780
781         if (mappingClass == UnsupportedMapping.class)
782             throw new JDOUnsupportedOptionException("Fields of type " + c.getName() + " not (yet) supported");
783
784         if (mappingClass == null)
785             mappingClass = PersistenceCapableMapping.class;
786
787         return mappingClass;
788     }
789
790     private static Object JavaDoc newInstance(Class JavaDoc clazz, Class JavaDoc[] ctorArgTypes, Object JavaDoc[] ctorArgs)
791         throws NoSuchMethodException JavaDoc
792     {
793         try
794         {
795             return clazz.getConstructor(ctorArgTypes).newInstance(ctorArgs);
796         }
797         catch (IllegalAccessException JavaDoc e)
798         {
799             throw new JDOFatalInternalException("Can't access constructor for mapping object", e);
800         }
801         catch (InstantiationException JavaDoc e)
802         {
803             throw new JDOFatalInternalException("Can't instantiate mapping object", e);
804         }
805         catch (InvocationTargetException JavaDoc e)
806         {
807             Throwable JavaDoc t = e.getTargetException();
808
809             if (t instanceof Error JavaDoc)
810                 throw (Error JavaDoc)t;
811             else if (t instanceof RuntimeException JavaDoc)
812                 throw (RuntimeException JavaDoc)t;
813             else
814                 throw new JDOFatalInternalException("Constructor for " + clazz.getName() + " failed", e);
815         }
816     }
817
818     public Connection JavaDoc getConnection(DataSource JavaDoc ds, String JavaDoc userName, String JavaDoc password, int isolationLevel) throws SQLException JavaDoc
819     {
820         Connection JavaDoc conn;
821         
822         if (userName == null)
823             conn = ds.getConnection();
824         else
825             conn = ds.getConnection(userName, password);
826
827         boolean succeeded = false;
828
829         try
830         {
831             if (isolationLevel == Connection.TRANSACTION_NONE)
832                 conn.setAutoCommit(true);
833             else
834             {
835                 conn.setAutoCommit(false);
836                 conn.setTransactionIsolation(isolationLevel);
837             }
838
839             succeeded = true;
840         }
841         finally
842         {
843             if (!succeeded)
844                 conn.close();
845         }
846
847         return conn;
848     }
849
850     public void closeConnection(Connection JavaDoc conn) throws SQLException JavaDoc
851     {
852         conn.close();
853     }
854
855     public String JavaDoc getSchemaName(Connection JavaDoc conn) throws SQLException JavaDoc
856     {
857         throw new UnsupportedOperationException JavaDoc("Don't know how to determine the current schema for this type of DBMS: " + databaseProductName + ' ' + databaseProductVersion);
858     }
859
860     public String JavaDoc getIdentifierQuoteString()
861     {
862         return identifierQuoteString;
863     }
864
865     public boolean createIndexesBeforeForeignKeys()
866     {
867         return false;
868     }
869
870     public boolean includeOrderByColumnsInSelect()
871     {
872         return true;
873     }
874
875     public boolean supportsAlterTableDropConstraint()
876     {
877         return true;
878     }
879
880     public boolean supportsDeferredConstraints()
881     {
882         return true;
883     }
884
885
886     /**
887      * Indicates whether or not two boolean expressions can be directly compared
888      * to each other using the = or &lt;&gt; operator.
889      * Some DBMS's allow you to say WHERE (X = 12) = (Y = 34), others don't.
890      * <p>
891      * The default value returned by the implementation in this class is
892      * <code>true</code>.
893      *
894      * @return
895      * <code>true</code> if boolean expressions can be compared,
896      * <code>false</code> otherwise.
897      */

898
899     public boolean supportsBooleanComparison()
900     {
901         return true;
902     }
903
904
905     public boolean supportsNullsInCandidateKeys()
906     {
907         return true;
908     }
909
910     public QueryStatement newQueryStatement(Table table)
911     {
912         return new QueryStatement(table);
913     }
914
915     public QueryStatement newQueryStatement(Table table, SQLIdentifier rangeVar)
916     {
917         return new QueryStatement(table, rangeVar);
918     }
919
920
921     /**
922      * Returns a new TableExpression object appropriate for this DBMS.
923      * This should be an instance of one of the three built-in styles of table
924      * expression:
925      * <ul>
926      * <li>TableExprAsJoins</li>
927      * <li>TableExprAsSubjoins</li>
928      * <li>TableExprAsSubquery</li>
929      * </ul>
930      * TableExprAsSubjoins is the default, which arguably produces the most
931      * readable SQL but doesn't work on all DBMS's. TableExprAsSubjoins
932      * should work anywhere, but may be less efficient.
933      *
934      * @param qs The query statement in which the table expression will
935      * be included.
936      * @param table The main table in the expression.
937      * @param rangeVar The SQL alias, or "range variable", to assign to the
938      * expression or to the main table.
939      */

940
941     public TableExpression newTableExpression(QueryStatement qs, Table table, SQLIdentifier rangeVar)
942     {
943         return new TableExprAsSubjoins(qs, table, rangeVar);
944     }
945
946
947     /**
948      * Returns the appropriate SQL to create the given table having the given
949      * columns. No column constraints or key definitions should be included.
950      * It should return something like:
951      * <p>
952      * <blockquote><pre>
953      * CREATE TABLE FOO ( BAR VARCHAR(30), BAZ INTEGER )
954      * </pre></blockquote>
955      *
956      * @param table The table to create.
957      * @param columns The columns of the table.
958      *
959      * @return The text of the SQL statement.
960      */

961
962     public String JavaDoc getCreateTableStatement(BaseTable table, Column[] columns)
963     {
964         StringBuffer JavaDoc createStmt = new StringBuffer JavaDoc();
965
966         createStmt.append("CREATE TABLE ").append(table.getName())
967                                           .append("\n(\n");
968         for (int i = 0; i < columns.length; ++i)
969         {
970             if (i > 0)
971                 createStmt.append(",\n");
972
973             createStmt.append(" ").append(columns[i].getSQLDefinition());
974         }
975
976         createStmt.append("\n)");
977
978         return createStmt.toString();
979     }
980
981
982     /**
983      * Returns the appropriate SQL to add a primary key to its table.
984      * It should return something like:
985      * <p>
986      * <blockquote><pre>
987      * ALTER TABLE FOO ADD CONSTRAINT FOO_PK PRIMARY KEY (BAR)
988      * </pre></blockquote>
989      *
990      * @param pkName The name of the primary key to add.
991      * @param pk An object describing the primary key.
992      *
993      * @return The text of the SQL statement.
994      */

995
996     public String JavaDoc getAddPrimaryKeyStatement(SQLIdentifier pkName, PrimaryKey pk)
997     {
998         return "ALTER TABLE " + pk.getTable().getName() + " ADD CONSTRAINT " + pkName + ' ' + pk;
999     }
1000
1001
1002    /**
1003     * Returns the appropriate SQL to add a candidate key to its table.
1004     * It should return something like:
1005     * <p>
1006     * <blockquote><pre>
1007     * ALTER TABLE FOO ADD CONSTRAINT FOO_CK UNIQUE (BAZ)
1008     * </pre></blockquote>
1009     *
1010     * @param ckName The name of the candidate key to add.
1011     * @param ck An object describing the candidate key.
1012     *
1013     * @return The text of the SQL statement.
1014     */

1015
1016    public String JavaDoc getAddCandidateKeyStatement(SQLIdentifier ckName, CandidateKey ck)
1017    {
1018        return "ALTER TABLE " + ck.getTable().getName() + " ADD CONSTRAINT " + ckName + ' ' + ck;
1019    }
1020
1021
1022    /**
1023     * Returns the appropriate SQL to add a foreign key to its table.
1024     * It should return something like:
1025     * <p>
1026     * <blockquote><pre>
1027     * ALTER TABLE FOO ADD CONSTRAINT FOO_FK1 FOREIGN KEY (BAR, BAZ) REFERENCES ABC (COL1, COL2)
1028     * </pre></blockquote>
1029     *
1030     * @param fkName The name of the foreign key to add.
1031     * @param fk An object describing the foreign key.
1032     *
1033     * @return The text of the SQL statement.
1034     */

1035
1036    public String JavaDoc getAddForeignKeyStatement(SQLIdentifier fkName, ForeignKey fk)
1037    {
1038        return "ALTER TABLE " + fk.getTable().getName() + " ADD CONSTRAINT " + fkName + ' ' + fk;
1039    }
1040
1041
1042    /**
1043     * Returns the appropriate SQL to add an index to its table.
1044     * It should return something like:
1045     * <p>
1046     * <blockquote><pre>
1047     * CREATE INDEX FOO_N1 ON FOO (BAR,BAZ)
1048     * CREATE UNIQUE INDEX FOO_U1 ON FOO (BAR,BAZ)
1049     * </pre></blockquote>
1050     *
1051     * @param idxName The name of the index to add.
1052     * @param idx An object describing the index.
1053     *
1054     * @return The text of the SQL statement.
1055     */

1056
1057    public String JavaDoc getCreateIndexStatement(SQLIdentifier idxName, Index idx)
1058    {
1059        return "CREATE " + (idx.getUnique() ? "UNIQUE " : "") + "INDEX " + idxName + " ON " + idx.getTable().getName() + ' ' + idx;
1060    }
1061
1062
1063    /**
1064     * Returns the appropriate SQL to drop the given table.
1065     * It should return something like:
1066     * <p>
1067     * <blockquote><pre>
1068     * DROP TABLE FOO CASCADE
1069     * </pre></blockquote>
1070     *
1071     * @param table The table to drop.
1072     *
1073     * @return The text of the SQL statement.
1074     */

1075
1076    public String JavaDoc getDropTableStatement(BaseTable table)
1077    {
1078        return "DROP TABLE " + table.getName() + " CASCADE";
1079    }
1080
1081
1082    /**
1083     * Returns the appropriate SQL to drop the given view.
1084     * It should return something like:
1085     * <p>
1086     * <blockquote><pre>
1087     * DROP VIEW FOO
1088     * </pre></blockquote>
1089     *
1090     * @param view The view to drop.
1091     *
1092     * @return The text of the SQL statement.
1093     */

1094
1095    public String JavaDoc getDropViewStatement(View view)
1096    {
1097        return "DROP VIEW " + view.getName();
1098    }
1099
1100
1101    /**
1102     * Returns the appropriate SQL expression for the JDOQL String.length()
1103     * method.
1104     * It should return something like:
1105     * <p>
1106     * <blockquote><pre>
1107     * CHAR_LENGTH(str)
1108     * </pre></blockquote>
1109     *
1110     * @param str The argument to the length() method.
1111     *
1112     * @return The text of the SQL expression.
1113     */

1114
1115    public NumericExpression lengthMethod(CharacterExpression str)
1116    {
1117        ArrayList JavaDoc args = new ArrayList JavaDoc();
1118        args.add(str);
1119
1120        return new NumericExpression("CHAR_LENGTH", args);
1121    }
1122
1123
1124    /**
1125     * Returns the appropriate SQL expression for the JDOQL
1126     * String.substring(str,begin) method.
1127     * It should return something like:
1128     * <p>
1129     * <blockquote><pre>
1130     * SUBSTRING(str FROM begin)
1131     * </pre></blockquote>
1132     * Note that the value of <var>begin</var> is base 0 (Java-style), while most
1133     * SQL string functions use base 1.
1134     *
1135     * @param str The first argument to the substring() method.
1136     * @param begin The second argument to the substring() method.
1137     *
1138     * @return The text of the SQL expression.
1139     */

1140
1141    public CharacterExpression substringMethod(CharacterExpression str,
1142                                               NumericExpression begin)
1143    {
1144        return new SubstringExpression(str, begin);
1145    }
1146
1147
1148    /**
1149     * Returns the appropriate SQL expression for the JDOQL
1150     * String.substring(str,begin,end) method.
1151     * It should return something like:
1152     * <p>
1153     * <blockquote><pre>
1154     * SUBSTRING(str FROM begin FOR len)
1155     * </pre></blockquote>
1156     * Note that the value of <var>begin</var> is base 0 (Java-style), while most
1157     * SQL string functions use base 1.
1158     * Note also that an end position is given, while most SQL substring
1159     * functions take a length.
1160     *
1161     * @param str The first argument to the substring() method.
1162     * @param begin The second argument to the substring() method.
1163     * @param end The third argument to the substring() method.
1164     *
1165     * @return The text of the SQL expression.
1166     */

1167
1168    public CharacterExpression substringMethod(CharacterExpression str,
1169                                               NumericExpression begin,
1170                                               NumericExpression end)
1171    {
1172        return new SubstringExpression(str, begin, end);
1173    }
1174
1175    public String JavaDoc toString()
1176    {
1177        String JavaDoc className = getClass().getName();
1178        String JavaDoc name = className.substring(className.lastIndexOf('.') + 1);
1179
1180        return name + ", " + databaseProductName
1181                    + " version " + databaseProductVersion
1182                    + " major " + databaseMajorVersion
1183                    + " minor " + databaseMinorVersion;
1184    }
1185
1186    private static class UnsupportedMapping
1187    {
1188        private UnsupportedMapping()
1189        {
1190        }
1191    }
1192}
1193
Popular Tags