All Downloads are FREE. Search and download functionalities are using the official Maven repository.

schemacrawler.crawl.DatabaseInfoRetriever Maven / Gradle / Ivy

/*
 *
 * SchemaCrawler
 * http://www.schemacrawler.com
 * Copyright (c) 2000-2016, Sualeh Fatehi.
 *
 * This library is free software; you can redistribute it and/or modify it under the terms
 * of the GNU Lesser General Public License as published by the Free Software Foundation;
 * either version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License along with this
 * library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, MA 02111-1307, USA.
 *
 */

package schemacrawler.crawl;


import static sf.util.DatabaseUtility.executeSql;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.DriverPropertyInfo;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;

import schemacrawler.schema.ColumnDataType;
import schemacrawler.schema.Schema;
import schemacrawler.schema.SchemaReference;
import schemacrawler.schema.SearchableType;
import schemacrawler.schemacrawler.InformationSchemaViews;
import schemacrawler.schemacrawler.SchemaCrawlerException;
import schemacrawler.schemacrawler.SchemaCrawlerSQLException;
import sf.util.DatabaseUtility;
import sf.util.StringFormat;

final class DatabaseInfoRetriever
  extends AbstractRetriever
{

  private static final Logger LOGGER = Logger
    .getLogger(DatabaseInfoRetriever.class.getName());

  private static final List ignoreMethods = Arrays
    .asList("getDatabaseProductName",
            "getDatabaseProductVersion",
            "getURL",
            "getUserName",
            "getDriverName",
            "getDriverVersion");

  /**
   * Checks if a method is a result set method.
   *
   * @param method
   *        Method
   * @return Whether a method is a result set method
   */
  private static boolean isDatabasePropertiesResultSetMethod(final Method method)
  {
    final Class returnType = method.getReturnType();
    final boolean isPropertiesResultSetMethod = returnType
      .equals(ResultSet.class) && method.getParameterTypes().length == 0;
    return isPropertiesResultSetMethod;
  }

  /**
   * Checks if a method is a database property.
   *
   * @param method
   *        Method
   * @return Whether method is a database property
   */
  private static boolean isDatabasePropertyListMethod(final Method method)
  {
    final Class returnType = method.getReturnType();
    final boolean isDatabasePropertyListMethod = returnType
      .equals(String.class) && method.getName()
        .endsWith("s") && method.getParameterTypes().length == 0;
    return isDatabasePropertyListMethod;
  }

  /**
   * Checks if a method is a database property.
   *
   * @param method
   *        Method
   * @return Whether method is a database property
   */
  private static boolean isDatabasePropertyMethod(final Method method)
  {
    final Class returnType = method.getReturnType();
    final boolean notPropertyMethod = returnType.equals(ResultSet.class)
                                      || returnType.equals(Connection.class)
                                      || method.getParameterTypes().length > 0;
    return !notPropertyMethod;
  }

  /**
   * Checks if a method is a database property result set type.
   *
   * @param method
   *        Method
   * @return Whether a method is a database property result set type
   */
  private static boolean isDatabasePropertyResultSetType(final Method method)
  {
    final String[] databasePropertyResultSetTypes = new String[] {
                                                                   "deletesAreDetected",
                                                                   "insertsAreDetected",
                                                                   "updatesAreDetected",
                                                                   "othersDeletesAreVisible",
                                                                   "othersInsertsAreVisible",
                                                                   "othersUpdatesAreVisible",
                                                                   "ownDeletesAreVisible",
                                                                   "ownInsertsAreVisible",
                                                                   "ownUpdatesAreVisible",
                                                                   "supportsResultSetType" };
    final boolean isDatabasePropertyResultSetType = Arrays
      .binarySearch(databasePropertyResultSetTypes, method.getName()) >= 0;
    return isDatabasePropertyResultSetType;
  }

  private static ImmutableDatabaseProperty retrieveResultSetTypeProperty(final DatabaseMetaData dbMetaData,
                                                                         final Method method,
                                                                         final int resultSetType,
                                                                         final String resultSetTypeName)
                                                                           throws IllegalAccessException,
                                                                           InvocationTargetException
  {
    final String name = method.getName() + "For" + resultSetTypeName
                        + "ResultSets";
    final Boolean propertyValue = (Boolean) method
      .invoke(dbMetaData, Integer.valueOf(resultSetType));
    return new ImmutableDatabaseProperty(name, propertyValue);
  }

  DatabaseInfoRetriever(final RetrieverConnection retrieverConnection,
                        final MutableCatalog catalog)
                          throws SQLException
  {
    super(retrieverConnection, catalog);
  }

  /**
   * Provides additional information on the database.
   *
   * @throws SQLException
   *         On a SQL exception
   */
  void retrieveAdditionalDatabaseInfo()
    throws SQLException
  {
    final DatabaseMetaData dbMetaData = getMetaData();
    final MutableDatabaseInfo dbInfo = catalog.getDatabaseInfo();

    final Collection dbProperties = new ArrayList<>();

    final Method[] methods = DatabaseMetaData.class.getMethods();
    for (final Method method: methods)
    {
      try
      {
        if (ignoreMethods.contains(method.getName()))
        {
          continue;
        }
        if (isDatabasePropertyListMethod(method))
        {
          if (LOGGER.isLoggable(Level.FINE))
          {
            LOGGER.log(Level.FINER,
                       new StringFormat("Retrieving database property using method, %s",
                                        method));
          }
          final String value = (String) method.invoke(dbMetaData);
          final String[] list = value == null? new String[0]: value.split(",");
          dbProperties.add(new ImmutableDatabaseProperty(method.getName(),
                                                         Arrays.asList(list)));
        }
        else if (isDatabasePropertyMethod(method))
        {
          if (LOGGER.isLoggable(Level.FINE))
          {
            LOGGER.log(Level.FINER,
                       new StringFormat("Retrieving database property using method, %s",
                                        method));
          }
          final Object value = method.invoke(dbMetaData);
          dbProperties
            .add(new ImmutableDatabaseProperty(method.getName(), value));
        }
        else if (isDatabasePropertiesResultSetMethod(method))
        {
          if (LOGGER.isLoggable(Level.FINE))
          {
            LOGGER.log(Level.FINER,
                       new StringFormat("Retrieving database property using method, %s",
                                        method));
          }
          final ResultSet results = (ResultSet) method.invoke(dbMetaData);
          final List resultsList = DatabaseUtility
            .readResultsVector(results);
          dbProperties.add(new ImmutableDatabaseProperty(method
            .getName(), resultsList.toArray(new String[resultsList.size()])));
        }
        else if (isDatabasePropertyResultSetType(method))
        {
          if (LOGGER.isLoggable(Level.FINE))
          {
            LOGGER.log(Level.FINER,
                       new StringFormat("Retrieving database property using method, %s",
                                        method));
          }
          dbProperties.add(retrieveResultSetTypeProperty(dbMetaData,
                                                         method,
                                                         ResultSet.TYPE_FORWARD_ONLY,
                                                         "TYPE_FORWARD_ONLY"));
          dbProperties
            .add(retrieveResultSetTypeProperty(dbMetaData,
                                               method,
                                               ResultSet.TYPE_SCROLL_INSENSITIVE,
                                               "TYPE_SCROLL_INSENSITIVE"));
          dbProperties
            .add(retrieveResultSetTypeProperty(dbMetaData,
                                               method,
                                               ResultSet.TYPE_SCROLL_SENSITIVE,
                                               "TYPE_SCROLL_SENSITIVE"));
        }
      }
      catch (final IllegalAccessException | InvocationTargetException e)
      {
        LOGGER.log(Level.FINE,
                   e.getCause(),
                   new StringFormat("Could not execute method, %s", method));
      }
      catch (final AbstractMethodError | SQLFeatureNotSupportedException e)
      {
        logSQLFeatureNotSupported("JDBC driver does not support " + method, e);
      }
      catch (final SQLException e)
      {
        // HYC00 = Optional feature not implemented
        if ("HYC00".equalsIgnoreCase(e.getSQLState()))
        {
          logSQLFeatureNotSupported("JDBC driver does not support " + method,
                                    e);
        }
        else
        {
          throw new SchemaCrawlerSQLException("Could not execute " + method, e);
        }
      }

    }

    dbInfo.addAll(dbProperties);

  }

  /**
   * Provides information on the JDBC driver.
   *
   * @throws SQLException
   *         On a SQL exception
   */
  void retrieveAdditionalJdbcDriverInfo()
    throws SQLException
  {
    final DatabaseMetaData dbMetaData = getMetaData();
    final String url = dbMetaData.getURL();

    final MutableJdbcDriverInfo driverInfo = catalog.getJdbcDriverInfo();
    if (driverInfo != null)
    {
      try
      {
        final Driver jdbcDriver = DriverManager.getDriver(url);
        final DriverPropertyInfo[] propertyInfo = jdbcDriver
          .getPropertyInfo(url, new Properties());
        for (final DriverPropertyInfo driverPropertyInfo: propertyInfo)
        {
          driverInfo
            .addJdbcDriverProperty(new ImmutableJdbcDriverProperty(driverPropertyInfo));
        }
      }
      catch (final SQLException e)
      {
        LOGGER.log(Level.WARNING,
                   "Could not obtain JDBC driver information",
                   e);
      }
    }

  }

  void retrieveCrawlHeaderInfo(final String title)
  {
    catalog.setCrawlHeaderInfo(title);
  }

  /**
   * Provides information on the database.
   *
   * @throws SQLException
   *         On a SQL exception
   */
  void retrieveDatabaseInfo()
    throws SQLException
  {
    final DatabaseMetaData dbMetaData = getMetaData();

    final MutableDatabaseInfo dbInfo = catalog.getDatabaseInfo();

    dbInfo.setUserName(dbMetaData.getUserName());
    dbInfo.setProductName(dbMetaData.getDatabaseProductName());
    dbInfo.setProductVersion(dbMetaData.getDatabaseProductVersion());
  }

  /**
   * Provides information on the JDBC driver.
   *
   * @throws SQLException
   *         On a SQL exception
   */
  void retrieveJdbcDriverInfo()
    throws SQLException
  {
    final DatabaseMetaData dbMetaData = getMetaData();
    final String url = dbMetaData.getURL();

    final MutableJdbcDriverInfo driverInfo = catalog.getJdbcDriverInfo();
    if (driverInfo != null)
    {
      driverInfo.setDriverName(dbMetaData.getDriverName());
      driverInfo.setDriverVersion(dbMetaData.getDriverVersion());
      driverInfo.setConnectionUrl(url);
      final Driver jdbcDriver = DriverManager.getDriver(url);
      driverInfo.setJdbcDriverClassName(jdbcDriver.getClass().getName());
      driverInfo.setJdbcCompliant(jdbcDriver.jdbcCompliant());
    }

  }

  /**
   * Retrieves column data type metadata.
   *
   * @throws SQLException
   *         On a SQL exception
   * @throws SchemaCrawlerException
   */
  void retrieveSystemColumnDataTypes()
    throws SQLException, SchemaCrawlerException
  {
    final Schema systemSchema = new SchemaReference();

    final Statement statement;
    final MetadataResultSet results;

    final InformationSchemaViews informationSchemaViews = getRetrieverConnection()
      .getInformationSchemaViews();
    if (informationSchemaViews.hasOverrideTypeInfoSql())
    {
      final String typeInfoSql = informationSchemaViews
        .getOverrideTypeInfoSql();

      final Connection connection = getDatabaseConnection();
      statement = connection.createStatement();
      results = new MetadataResultSet(executeSql(statement, typeInfoSql));
    }
    else
    {
      statement = null;
      results = new MetadataResultSet(getMetaData().getTypeInfo());
    }

    try
    {
      while (results.next())
      {
        final String typeName = results.getString("TYPE_NAME");
        final int dataType = results.getInt("DATA_TYPE", 0);
        LOGGER.log(Level.FINER,
                   new StringFormat("Retrieving data type: %s (with type id %d)",
                                    typeName,
                                    dataType));
        final long precision = results.getLong("PRECISION", 0L);
        final String literalPrefix = results.getString("LITERAL_PREFIX");
        final String literalSuffix = results.getString("LITERAL_SUFFIX");
        final String createParameters = results.getString("CREATE_PARAMS");
        final boolean isNullable = results
          .getInt("NULLABLE",
                  DatabaseMetaData.typeNullableUnknown) == DatabaseMetaData.typeNullable;
        final boolean isCaseSensitive = results.getBoolean("CASE_SENSITIVE");
        final SearchableType searchable = SearchableType.valueOf(results
          .getInt("SEARCHABLE", SearchableType.unknown.ordinal()));
        final boolean isUnsigned = results.getBoolean("UNSIGNED_ATTRIBUTE");
        final boolean isFixedPrecisionScale = results
          .getBoolean("FIXED_PREC_SCALE");
        final boolean isAutoIncremented = results.getBoolean("AUTO_INCREMENT");
        final String localTypeName = results.getString("LOCAL_TYPE_NAME");
        final int minimumScale = results.getInt("MINIMUM_SCALE", 0);
        final int maximumScale = results.getInt("MAXIMUM_SCALE", 0);
        final int numPrecisionRadix = results.getInt("NUM_PREC_RADIX", 0);

        final MutableColumnDataType columnDataType = lookupOrCreateColumnDataType(systemSchema,
                                                                                  dataType,
                                                                                  typeName);
        // Set the Java SQL type code, but no mapped Java class is
        // available, so use the defaults
        columnDataType.setPrecision(precision);
        columnDataType.setLiteralPrefix(literalPrefix);
        columnDataType.setLiteralSuffix(literalSuffix);
        columnDataType.setCreateParameters(createParameters);
        columnDataType.setNullable(isNullable);
        columnDataType.setCaseSensitive(isCaseSensitive);
        columnDataType.setSearchable(searchable);
        columnDataType.setUnsigned(isUnsigned);
        columnDataType.setFixedPrecisionScale(isFixedPrecisionScale);
        columnDataType.setAutoIncrementable(isAutoIncremented);
        columnDataType.setLocalTypeName(localTypeName);
        columnDataType.setMinimumScale(minimumScale);
        columnDataType.setMaximumScale(maximumScale);
        columnDataType.setNumPrecisionRadix(numPrecisionRadix);

        columnDataType.addAttributes(results.getAttributes());

        catalog.addColumnDataType(columnDataType);
      }
    }
    finally
    {
      if (results != null)
      {
        results.close();
      }
      if (statement != null)
      {
        statement.close();
      }
    }
  }

  void retrieveUserDefinedColumnDataTypes(final String catalogName,
                                          final String schemaName)
                                            throws SQLException
  {

    try (final MetadataResultSet results = new MetadataResultSet(getMetaData()
      .getUDTs(catalogName, schemaName, "%", null));)
    {
      while (results.next())
      {
        // "TYPE_CAT", "TYPE_SCHEM"
        final String typeName = results.getString("TYPE_NAME");
        LOGGER.log(Level.FINER,
                   new StringFormat("Retrieving data type, %s", typeName));
        final int dataType = results.getInt("DATA_TYPE", 0);
        final String className = results.getString("CLASS_NAME");
        final String remarks = results.getString("REMARKS");
        final short baseTypeValue = results.getShort("BASE_TYPE", (short) 0);

        final Schema schema = new SchemaReference(catalogName, schemaName);
        final ColumnDataType baseType = catalog
          .lookupColumnDataTypeByType(baseTypeValue);
        final MutableColumnDataType columnDataType = lookupOrCreateColumnDataType(schema,
                                                                                  dataType,
                                                                                  typeName,
                                                                                  className);
        columnDataType.setUserDefined(true);
        columnDataType.setBaseType(baseType);
        columnDataType.setRemarks(remarks);

        columnDataType.addAttributes(results.getAttributes());

        catalog.addColumnDataType(columnDataType);
      }
    }

  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy