Main Page | Packages | Class Hierarchy | Alphabetical List | Class List | File List | Class Members | Related Pages

DatabaseSQLMetaData.java

00001 /**
00002  * C-JDBC: Clustered JDBC.
00003  * Copyright (C) 2002-2004 French National Institute For Research In Computer
00004  * Science And Control (INRIA).
00005  * Contact: c-jdbc@objectweb.org
00006  * 
00007  * This library is free software; you can redistribute it and/or modify it
00008  * under the terms of the GNU Lesser General Public License as published by the
00009  * Free Software Foundation; either version 2.1 of the License, or any later
00010  * version.
00011  * 
00012  * This library is distributed in the hope that it will be useful, but WITHOUT
00013  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
00014  * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
00015  * for more details.
00016  * 
00017  * You should have received a copy of the GNU Lesser General Public License
00018  * along with this library; if not, write to the Free Software Foundation,
00019  * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
00020  *
00021  * Initial developer(s): Nicolas Modrzyk.
00022  * Contributor(s): ______________________.
00023  */
00024 
00025 package org.objectweb.cjdbc.common.sql.schema;
00026 
00027 import java.sql.Connection;
00028 import java.sql.DatabaseMetaData;
00029 import java.sql.ResultSet;
00030 import java.sql.SQLException;
00031 
00032 import org.objectweb.cjdbc.common.i18n.Translate;
00033 import org.objectweb.cjdbc.common.log.Trace;
00034 import org.objectweb.cjdbc.controller.backend.DatabaseBackendSchemaConstants;
00035 
00036 /**
00037  * This class defines a DatabaseSQLMetaData. It is used to collect metadata from
00038  * a live connection to a database
00039  * 
00040  * @author <a href="mailto:Nicolas.Modrzyk@inrialpes.fr">Nicolas Modrzyk </a>
00041  * @version 1.0
00042  */
00043 public class DatabaseSQLMetaData
00044 {
00045   Trace      logger;
00046   Connection connection;
00047   int        dynamicPrecision;
00048   boolean    gatherSystemTables;
00049   String     schemaPattern;
00050 
00051   /**
00052    * Creates a new <code>MetaData</code> object
00053    * 
00054    * @param logger the log4j logger to output to
00055    * @param connection a jdbc connection to a database
00056    * @param dynamicPrecision precision used to create the schema
00057    * @param gatherSystemTables should we gather system tables
00058    */
00059   public DatabaseSQLMetaData(Trace logger, Connection connection,
00060       int dynamicPrecision, boolean gatherSystemTables, String schemaPattern)
00061   {
00062     super();
00063     this.logger = logger;
00064     this.connection = connection;
00065     this.dynamicPrecision = dynamicPrecision;
00066     this.gatherSystemTables = gatherSystemTables;
00067     this.schemaPattern = schemaPattern;
00068   }
00069 
00070   /**
00071    * Create a database schema from the given connection
00072    * 
00073    * @return <code>DataSchema</code> contructed from the information collected
00074    *         through jdbc
00075    * @throws SQLException if an error occurs with the given connection
00076    */
00077   public final DatabaseSchema createDatabaseSchema() throws SQLException
00078   {
00079     ResultSet rs = null;
00080 
00081     connection.setAutoCommit(false); // Needed
00082     // for
00083     // Derby
00084     // Get DatabaseMetaData
00085     DatabaseMetaData metaData = connection.getMetaData();
00086     if (metaData == null)
00087     {
00088       logger.warn(Translate.get("backend.meta.received.null"));
00089       return null;
00090     }
00091 
00092     DatabaseSchema databaseSchema = new DatabaseSchema();
00093 
00094     // Check if we should get system tables or not
00095     String[] types;
00096     if (gatherSystemTables)
00097     {
00098       schemaPattern = null;
00099       types = new String[]{"TABLE", "VIEW", "SYSTEM TABLE", "SYSTEM VIEW"};
00100     }
00101     else
00102       types = new String[]{"TABLE", "VIEW"};
00103 
00104     // Get tables meta data
00105     // getTables() gets a description of tables matching the catalog, schema,
00106     // table name pattern and type. Sending in null for catalog and schema
00107     // drops them from the selection criteria. The table name pattern "%"
00108     // means match any substring of 0 or more characters.
00109     // Last argument allows to obtain only database tables
00110     try
00111     {
00112       rs = metaData.getTables(null, schemaPattern, "%", types);
00113     }
00114     catch (Exception e)
00115     {
00116       // VIEWS cannot be retrieved with this backend
00117       logger.error(Translate.get("backend.meta.view.not.supported"), e);
00118       if (gatherSystemTables)
00119         types = new String[]{"TABLE", "SYSTEM TABLE",};
00120       else
00121         types = new String[]{"TABLE"};
00122       rs = metaData.getTables(null, schemaPattern, "%", types);
00123     }
00124 
00125     if (rs == null)
00126     {
00127       logger.warn(Translate.get("backend.meta.received.null"));
00128       connection.commit();
00129       return null;
00130     }
00131 
00132     String tableName;
00133     DatabaseTable table = null;
00134     while (rs.next())
00135     {
00136       // 1 is table catalog, 2 is table schema, 3 is table name, 4 is type
00137       tableName = rs.getString(3);
00138       if (logger.isDebugEnabled())
00139         logger.debug(Translate.get("backend.meta.found.table", tableName));
00140 
00141       // Create a new table and add it to the database schema
00142       table = new DatabaseTable(tableName);
00143       databaseSchema.addTable(table);
00144 
00145       if (dynamicPrecision >= DatabaseBackendSchemaConstants.DynamicPrecisionColumn)
00146       {
00147         // Get information about this table columns
00148         getColumns(metaData, table);
00149         // Get information about this table primary keys
00150         getPrimaryKeys(metaData, table);
00151       }
00152     }
00153 
00154     // Get Procedures for this database
00155     if (dynamicPrecision >= DatabaseBackendSchemaConstants.DynamicPrecisionProcedures)
00156       getProcedures(metaData, databaseSchema);
00157 
00158     try
00159     {
00160       rs.close();
00161     }
00162     catch (Exception ignore)
00163     {
00164     }
00165 
00166     try
00167     {
00168       connection.commit();
00169     }
00170     catch (Exception ignore)
00171     {
00172       // This was a read-only transaction
00173     }
00174 
00175     try
00176     {
00177       // restore connection
00178       connection.setAutoCommit(true);
00179     }
00180     catch (SQLException e1)
00181     {
00182       // ignore, transaction is no more valid
00183     }
00184 
00185     return databaseSchema;
00186   }
00187 
00188   /**
00189    * @see java.sql.DatabaseMetaData#getProcedures
00190    * @see java.sql.DatabaseMetaData#getProcedureColumns
00191    */
00192   private void getProcedures(DatabaseMetaData metaData, DatabaseSchema schema)
00193   {
00194     if (logger.isDebugEnabled())
00195       logger.debug(Translate.get("backend.meta.get.procedures"));
00196     ResultSet rs = null;
00197     ResultSet rs2 = null;
00198     try
00199     {
00200       // Get Procedures meta data
00201       rs = metaData.getProcedures(null, null, "%");
00202 
00203       if (rs == null)
00204       {
00205         logger.warn(Translate.get("backend.meta.get.procedures.failed",
00206             metaData.getConnection().getCatalog()));
00207         return;
00208       }
00209 
00210       while (rs.next())
00211       {
00212         // Each row is a procedure description
00213         // 3 = PROCEDURE_NAME
00214         // 7 = REMARKS
00215         // 8 = PROCEDURE_TYPE
00216         DatabaseProcedure procedure = new DatabaseProcedure(rs.getString(3), rs
00217             .getString(7), rs.getShort(8));
00218 
00219         if (schema.getProcedure(procedure.getName()) != null)
00220         {
00221           if (logger.isDebugEnabled())
00222             logger.debug(Translate
00223                 .get("backend.meta.procedure.already.in.schema", procedure
00224                     .getName()));
00225           continue;
00226         }
00227         else
00228         {
00229           if (logger.isDebugEnabled())
00230             logger.debug(Translate.get("backend.meta.found.procedure",
00231                 procedure.getName()));
00232         }
00233 
00234         // TODO: Current limitation is that we don't distinguish 2 procedures
00235         // with the same name but different signatures
00236 
00237         if (dynamicPrecision < DatabaseBackendSchemaConstants.DynamicPrecisionProcedures)
00238           continue;
00239         // This is a new stored procedure, get the column information
00240         rs2 = metaData
00241             .getProcedureColumns(null, null, procedure.getName(), "%");
00242         if (rs2 == null)
00243           logger.warn(Translate.get("backend.meta.get.procedure.params.failed",
00244               procedure.getName()));
00245         else
00246         {
00247           while (rs2.next())
00248           {
00249             // Each row is a parameter description for the current procedure
00250             // 4 = COLUMN_NAME
00251             // 5 = COLUMN_TYPE
00252             // 6 = DATA_TYPE
00253             // 7 = TYPE_NAME
00254             // 8 = PRECISION
00255             // 9 = LENGTH
00256             // 10 = SCALE
00257             // 11 = RADIX
00258             // 12 = NULLABLE
00259             // 13 = REMARKS
00260             DatabaseProcedureParameter param = new DatabaseProcedureParameter(
00261                 rs2.getString(4), rs2.getInt(5), rs2.getInt(6), rs2
00262                     .getString(7), rs2.getFloat(8), rs2.getInt(9), rs2
00263                     .getInt(10), rs2.getInt(11), rs2.getInt(12), rs2
00264                     .getString(13));
00265             // TO CHECK:
00266             // This cannot happen since we don't allow 2 procedures with the
00267             // same name.
00268             // It seems useless to test if a parameter already exist since it
00269             // should never happen.
00270             // We will need to reuse some pieces of this code when we will
00271             // implement support for
00272             // stored procedures of the same name with different signatures.
00273             //              ArrayList parameters = procedure.getParameters();
00274             //              for (int i = 0; i < parameters.size(); i++)
00275             //              {
00276             //                String name =
00277             //                  ((DatabaseProcedureParameter) parameters.get(i)).getName();
00278             //                if (name.equals(param.getName()))
00279             //                  break;
00280             //              }
00281             procedure.addParameter(param);
00282           }
00283           rs2.close();
00284         }
00285 
00286         schema.addProcedure(procedure);
00287       }
00288     }
00289     catch (Exception e)
00290     {
00291       logger.error(Translate.get("backend.meta.get.procedures.failed", e
00292           .getMessage()));
00293     }
00294     finally
00295     {
00296       try
00297       {
00298         rs.close();
00299       }
00300       catch (Exception ignore)
00301       {
00302       }
00303       try
00304       {
00305         rs2.close();
00306       }
00307       catch (Exception ignoreAsWell)
00308       {
00309       }
00310     }
00311   }
00312 
00313   /**
00314    * Gets the list of columns of a given database table. The caller must ensure
00315    * that the parameters are not <code>null</code>.
00316    * 
00317    * @param metaData the database meta data
00318    * @param table the database table
00319    * @exception SQLException if an error occurs
00320    */
00321   private void getColumns(DatabaseMetaData metaData, DatabaseTable table)
00322       throws SQLException
00323   {
00324     ResultSet rs = null;
00325     try
00326     {
00327       // Get columns meta data
00328       // getColumns() gets a description of columns matching the catalog,
00329       // schema, table name and column name pattern. Sending in null for
00330       // catalog and schema drops them from the selection criteria. The
00331       // column pattern "%" allows to obtain all columns.
00332       rs = metaData.getColumns(null, null, table.getName(), "%");
00333 
00334       if (rs == null)
00335       {
00336         logger.warn(Translate.get("backend.meta.get.columns.failed", table
00337             .getName()));
00338         return;
00339       }
00340 
00341       DatabaseColumn column = null;
00342       int type;
00343       while (rs.next())
00344       {
00345         // 1 is table catalog, 2 is table schema, 3 is table name,
00346         // 4 is column name, 5 is data type
00347         type = rs.getShort(5);
00348         column = new DatabaseColumn(rs.getString(4), false, type);
00349         table.addColumn(column);
00350 
00351         if (logger.isDebugEnabled())
00352           logger.debug(Translate.get("backend.meta.found.column", rs
00353               .getString(4)));
00354       }
00355     }
00356     catch (SQLException e)
00357     {
00358       throw new SQLException(Translate.get("backend.meta.get.columns.failed",
00359           table.getName()));
00360     }
00361     finally
00362     {
00363       try
00364       {
00365         rs.close();
00366       }
00367       catch (Exception ignore)
00368       {
00369       }
00370     }
00371   }
00372 
00373   /**
00374    * Gets the primary keys of a given database table. The caller must ensure
00375    * that the parameters are not <code>null</code>.
00376    * 
00377    * @param metaData the database meta data
00378    * @param table the database table
00379    * @exception SQLException if an error occurs
00380    */
00381   private void getPrimaryKeys(DatabaseMetaData metaData, DatabaseTable table)
00382       throws SQLException
00383   {
00384     ResultSet rs = null;
00385     try
00386     {
00387       // Get primary keys meta data
00388       // getPrimaryKeys() gets a description of primary keys matching the
00389       // catalog, schema, and table name. Sending in null for catalog and
00390       // schema drop them from the selection criteria.
00391 
00392       rs = metaData.getPrimaryKeys(null, null, table.getName());
00393 
00394       if (rs == null)
00395       {
00396         logger.warn(Translate.get("backend.meta.get.primary.keys.failed", table
00397             .getName()));
00398         return;
00399       }
00400 
00401       String columnName = null;
00402       while (rs.next())
00403       {
00404 
00405         // 1 is table catalog, 2 is table schema, 3 is table name, 4 is column
00406         // name
00407         columnName = rs.getString(4);
00408         if (columnName == null)
00409           continue;
00410         if (logger.isDebugEnabled())
00411           logger.debug(Translate.get("backend.meta.found.primary.key",
00412               columnName));
00413 
00414         // Set the column to unique
00415         table.getColumn(columnName).setIsUnique(true);
00416       }
00417     }
00418     catch (SQLException e)
00419     {
00420       throw new SQLException(Translate.get(
00421           "backend.meta.get.primary.keys.failed", table.getName()));
00422     }
00423     finally
00424     {
00425       try
00426       {
00427         rs.close();
00428       }
00429       catch (Exception ignore)
00430       {
00431       }
00432     }
00433   }
00434 
00435 }

Generated on Mon Apr 11 22:01:31 2005 for C-JDBC by  doxygen 1.3.9.1