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

org.eobjects.metamodel.jdbc.JdbcDataContext Maven / Gradle / Ivy

/**
 * eobjects.org MetaModel
 * Copyright (C) 2010 eobjects.org
 *
 * This copyrighted material is made available to anyone wishing to use, modify,
 * copy, or redistribute it subject to the terms and conditions of the GNU
 * Lesser General Public License, as published by the Free Software Foundation.
 *
 * This program 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 distribution; if not, write to:
 * Free Software Foundation, Inc.
 * 51 Franklin Street, Fifth Floor
 * Boston, MA  02110-1301  USA
 */
package org.eobjects.metamodel.jdbc;

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;

import javax.sql.DataSource;

import org.eobjects.metamodel.AbstractDataContext;
import org.eobjects.metamodel.MetaModelException;
import org.eobjects.metamodel.UpdateScript;
import org.eobjects.metamodel.UpdateableDataContext;
import org.eobjects.metamodel.data.DataSet;
import org.eobjects.metamodel.data.FirstRowDataSet;
import org.eobjects.metamodel.data.MaxRowsDataSet;
import org.eobjects.metamodel.jdbc.dialects.DB2QueryRewriter;
import org.eobjects.metamodel.jdbc.dialects.DefaultQueryRewriter;
import org.eobjects.metamodel.jdbc.dialects.IQueryRewriter;
import org.eobjects.metamodel.jdbc.dialects.MysqlQueryRewriter;
import org.eobjects.metamodel.jdbc.dialects.PostgresqlQueryRewriter;
import org.eobjects.metamodel.jdbc.dialects.SQLServerQueryRewriter;
import org.eobjects.metamodel.query.Query;
import org.eobjects.metamodel.schema.Column;
import org.eobjects.metamodel.schema.ColumnType;
import org.eobjects.metamodel.schema.MutableColumn;
import org.eobjects.metamodel.schema.MutableRelationship;
import org.eobjects.metamodel.schema.Relationship;
import org.eobjects.metamodel.schema.Schema;
import org.eobjects.metamodel.schema.Table;
import org.eobjects.metamodel.schema.TableType;
import org.eobjects.metamodel.util.FileHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * DataContextStrategy to use for JDBC-compliant databases
 */
public class JdbcDataContext extends AbstractDataContext implements
		UpdateableDataContext {

	public static final String DATABASE_PRODUCT_POSTGRESQL = "PostgreSQL";
	public static final String DATABASE_PRODUCT_MYSQL = "MySQL";
	public static final String DATABASE_PRODUCT_HSQLDB = "HSQL Database Engine";
	public static final String DATABASE_PRODUCT_SQLSERVER = "Microsoft SQL Server";
	public static final String DATABASE_PRODUCT_DB2 = "DB2";

	private static final Logger logger = LoggerFactory
			.getLogger(JdbcDataContext.class);

	private final FetchSizeCalculator _fetchSizeCalculator;
	private final Connection _connection;
	private final DataSource _dataSource;
	private final TableType[] _tableTypes;
	private final String _catalogName;
	private final boolean _singleConnection;

	/**
	 * Defines the way that queries are written once dispatched to the database
	 */
	private IQueryRewriter _queryRewriter;
	private String _databaseProductName;

	/**
	 * There are some confusion as to the definition of catalogs and schemas.
	 * Some databases seperate "groups of tables" by using schemas, others by
	 * catalogs. This variable indicates whether a MetaModel schema really
	 * represents a catalog.
	 */
	private boolean _usesCatalogsAsSchemas;
	private String _identifierQuoteString;
	private boolean _supportsBatchUpdates;
	private boolean _isDefaultAutoCommit;

	/**
	 * Creates the strategy based on a data source, some table types and an
	 * optional catalogName
	 * 
	 * @param dataSource
	 *            the datasource objcet to use for making connections
	 * @param tableTypes
	 *            the types of tables to include
	 * @param catalogName
	 *            a catalog name to use, can be null
	 */
	public JdbcDataContext(DataSource dataSource, TableType[] tableTypes,
			String catalogName) {
		this(dataSource, null, tableTypes, catalogName);
	}

	/**
	 * Creates the strategy based on a {@link Connection}, some table types and
	 * an optional catalogName
	 * 
	 * @param connection
	 *            the database connection
	 * @param tableTypes
	 *            the types of tables to include
	 * @param catalogName
	 *            a catalog name to use, can be null
	 */
	public JdbcDataContext(Connection connection, TableType[] tableTypes,
			String catalogName) {
		this(null, connection, tableTypes, catalogName);
	}

	/**
	 * Creates the strategy based on a {@link DataSource}, some table types and
	 * an optional catalogName
	 * 
	 * @param dataSource
	 *            the data source
	 * @param tableTypes
	 *            the types of tables to include
	 * @param catalogName
	 *            a catalog name to use, can be null
	 */
	private JdbcDataContext(DataSource dataSource, Connection connection,
			TableType[] tableTypes, String catalogName) {
		_dataSource = dataSource;
		_connection = connection;
		_tableTypes = tableTypes;
		_catalogName = catalogName;

		if (_dataSource == null) {
			_singleConnection = true;
		} else {
			_singleConnection = false;
		}

		// available memory for fetching is so far fixed at 16 megs.
		_fetchSizeCalculator = new FetchSizeCalculator(16 * 1024 * 1024);
		initialize();
	}

	/**
	 * Creates the strategy based on a {@link Connection}
	 * 
	 * @param connection
	 *            the database connection
	 */
	public JdbcDataContext(Connection connection) {
		this(connection, TableType.DEFAULT_TABLE_TYPES, null);
	}

	/**
	 * Creates the strategy based on a {@link DataSource}
	 * 
	 * @param dataSource
	 *            the data source
	 */
	public JdbcDataContext(DataSource dataSource) {
		this(dataSource, TableType.DEFAULT_TABLE_TYPES, null);
	}

	/**
	 * Initializes the data context with global metadata about database type and
	 * query escaping rules
	 */
	private void initialize() {
		Connection connection = getConnection();

		try {
			_isDefaultAutoCommit = connection.getAutoCommit();
		} catch (SQLException e) {
			throw JdbcUtils.wrapException(e, "determine auto-commit behaviour");
		}

		_supportsBatchUpdates = false;
		try {
			DatabaseMetaData metaData = connection.getMetaData();

			_supportsBatchUpdates = supportsBatchUpdates(metaData);

			try {
				_identifierQuoteString = metaData.getIdentifierQuoteString();
				if (_identifierQuoteString != null) {
					_identifierQuoteString = _identifierQuoteString.trim();
				}
			} catch (SQLException e) {
				logger.warn(
						"could not retrieve identifier quote string from database metadata",
						e);
			}

			_usesCatalogsAsSchemas = usesCatalogsAsSchemas(metaData);
			try {
				_databaseProductName = metaData.getDatabaseProductName();
				logger.debug("Database product name: {}", _databaseProductName);
				if (DATABASE_PRODUCT_MYSQL.equals(_databaseProductName)) {
					setQueryRewriter(new MysqlQueryRewriter(this));
				} else if (DATABASE_PRODUCT_POSTGRESQL
						.equals(_databaseProductName)) {
					setQueryRewriter(new PostgresqlQueryRewriter(this));
				} else if (DATABASE_PRODUCT_SQLSERVER
						.equals(_databaseProductName)) {
					setQueryRewriter(new SQLServerQueryRewriter(this));
				} else if (DATABASE_PRODUCT_DB2.equals(_databaseProductName)) {
					setQueryRewriter(new DB2QueryRewriter(this));
				} else {
					setQueryRewriter(new DefaultQueryRewriter(this));
				}

			} catch (SQLException e) {
				logger.warn("Could not retrieve database product name: "
						+ e.getMessage());
			}
		} catch (SQLException e) {
			logger.debug("initialize() threw exception", e);
		} finally {
			closeIfNescesary(connection);
		}
	}

	private boolean supportsBatchUpdates(DatabaseMetaData metaData) {
		try {
			return metaData.supportsBatchUpdates();
		} catch (Exception e) {
			logger.warn(
					"Could not determine if driver support batch updates, returning false",
					e);
			return false;
		}
	}

	private boolean usesCatalogsAsSchemas(DatabaseMetaData metaData) {
		boolean result = true;
		ResultSet rs = null;
		try {
			rs = metaData.getSchemas();
			while (rs.next() && result) {
				result = false;
			}
		} catch (SQLException e) {
			throw JdbcUtils.wrapException(e,
					"retrieve schema and catalog metadata");
		} finally {
			close(null, rs, null);
		}
		return result;
	}

	protected void loadTables(JdbcSchema schema) {
		Connection connection = getConnection();
		try {
			DatabaseMetaData metaData = connection.getMetaData();

			// Creates string array to represent the table types
			String[] types = getTableTypesAsStrings();
			loadTables(schema, metaData, types);
		} catch (SQLException e) {
			throw JdbcUtils.wrapException(e, "retrieve table metadata for "
					+ schema.getName());
		} finally {
			closeIfNescesary(connection);
		}
	}

	private void loadTables(JdbcSchema schema, DatabaseMetaData metaData,
			String[] types) {
		ResultSet rs = null;
		try {
			if (logger.isInfoEnabled()) {
				logger.info("Querying for table types "
						+ Arrays.toString(types) + " in catalog: "
						+ _catalogName + ", schema: " + schema.getName());
			}
			if (_usesCatalogsAsSchemas) {
				rs = metaData.getTables(schema.getName(), null, null, types);
			} else {
				rs = metaData.getTables(_catalogName, schema.getName(), null,
						types);
			}
			while (rs.next()) {
				String tableCatalog = rs.getString(1);
				String tableSchema = rs.getString(2);
				String tableName = rs.getString(3);
				String tableTypeName = rs.getString(4);
				TableType tableType = TableType.getTableType(tableTypeName);
				String tableRemarks = rs.getString(5);

				if (logger.isDebugEnabled()) {
					logger.debug("Found table: tableCatalog=" + tableCatalog
							+ ",tableSchema=" + tableSchema + ",tableName="
							+ tableName);
				}

				if (tableSchema == null) {
					tableSchema = tableCatalog;
				}

				JdbcTable table = new JdbcTable(tableName, tableType, schema,
						this);
				table.setRemarks(tableRemarks);
				table.setQuote(_identifierQuoteString);
				schema.addTable(table);
			}
		} catch (SQLException e) {
			throw JdbcUtils.wrapException(e, "retrieve table metadata for "
					+ schema.getName());
		} finally {
			close(null, rs, null);
		}
	}

	/**
	 * Loads index metadata for a table
	 * 
	 * @param table
	 */
	protected void loadIndexes(JdbcTable table) throws MetaModelException {
		Connection connection = getConnection();
		try {
			DatabaseMetaData metaData = connection.getMetaData();
			loadIndexes(table, metaData);
		} catch (Exception e) {
			logger.error("Error loading indexes", e);
			throw new MetaModelException(e);
		} finally {
			closeIfNescesary(connection);
		}
	}

	private void loadIndexes(Table table, DatabaseMetaData metaData) {
		Schema schema = table.getSchema();
		ResultSet rs = null;
		// Ticket #170: IndexInfo is nice-to-have, not need-to-have, so
		// we will do a nice failover on SQLExceptions
		try {
			if (_usesCatalogsAsSchemas) {
				rs = metaData.getIndexInfo(schema.getName(), null,
						table.getName(), false, true);
			} else {
				rs = metaData.getIndexInfo(_catalogName, schema.getName(),
						table.getName(), false, true);
			}
			while (rs.next()) {
				String columnName = rs.getString(9);
				if (columnName != null) {
					MutableColumn column = (MutableColumn) table
							.getColumnByName(columnName);
					if (column != null) {
						column.setIndexed(true);
					} else {
						logger.error(
								"Indexed column \"{}\" could not be found in table: {}",
								columnName, table);
					}
				}
			}
		} catch (SQLException e) {
			throw JdbcUtils.wrapException(e, "retrieve index information for "
					+ table.getName());
		} finally {
			close(null, rs, null);
		}
	}

	/**
	 * Loads column metadata (no indexes though) for a table
	 * 
	 * @param table
	 */
	protected void loadColumns(JdbcTable table) {
		Connection connection = getConnection();
		try {
			DatabaseMetaData metaData = connection.getMetaData();
			loadColumns(table, metaData);
		} catch (Exception e) {
			logger.error("Could not load columns for table: " + table, e);
		} finally {
			closeIfNescesary(connection);
		}
	}

	private void loadColumns(JdbcTable table, DatabaseMetaData metaData) {
		Schema schema = table.getSchema();
		ResultSet rs = null;
		try {
			if (logger.isInfoEnabled()) {
				logger.info("Querying for columns in table: " + table.getName());
			}
			int columnNumber = -1;
			if (_usesCatalogsAsSchemas) {
				rs = metaData.getColumns(schema.getName(), null,
						table.getName(), null);
			} else {
				rs = metaData.getColumns(_catalogName, schema.getName(),
						table.getName(), null);
			}
			while (rs.next()) {
				columnNumber++;
				String columnName = rs.getString(4);
				if (_identifierQuoteString == null
						&& new StringTokenizer(columnName).countTokens() > 1) {
					logger.warn("column name contains whitespace: \""
							+ columnName + "\".");
				}

				int jdbcType = rs.getInt(5);
				ColumnType columnType = ColumnType.convertColumnType(jdbcType);

				String nativeType = rs.getString(6);
				Integer columnSize = rs.getInt(7);

				int jdbcNullable = rs.getInt(11);
				Boolean nullable = null;
				if (jdbcNullable == DatabaseMetaData.columnNullable) {
					nullable = true;
				} else if (jdbcNullable == DatabaseMetaData.columnNoNulls) {
					nullable = false;
				}

				String remarks = rs.getString(12);

				JdbcColumn column = new JdbcColumn(columnName, columnType,
						table, columnNumber, nullable);
				column.setRemarks(remarks);
				column.setNativeType(nativeType);
				column.setColumnSize(columnSize);
				column.setQuote(_identifierQuoteString);
				table.addColumn(column);
			}
		} catch (SQLException e) {
			throw JdbcUtils.wrapException(e, "retrieve table metadata for "
					+ table.getName());
		} finally {
			close(null, rs, null);
		}
	}

	protected void loadRelations(Schema schema) {
		Connection connection = getConnection();
		try {
			Table[] tables = schema.getTables();
			DatabaseMetaData metaData = connection.getMetaData();
			for (Table table : tables) {
				loadRelations(table, metaData);
			}
		} catch (Exception e) {
			logger.error("Could not load relations for schema: " + schema, e);
		} finally {
			closeIfNescesary(connection);
		}
	}

	private void loadRelations(Table table, DatabaseMetaData metaData) {
		Schema schema = table.getSchema();
		ResultSet rs = null;
		try {
			if (_usesCatalogsAsSchemas) {
				rs = metaData.getImportedKeys(schema.getName(), null,
						table.getName());
			} else {
				rs = metaData.getImportedKeys(_catalogName, schema.getName(),
						table.getName());
			}
			loadRelations(rs, schema);
		} catch (SQLException e) {
			throw JdbcUtils.wrapException(e, "retrieve imported keys for "
					+ table.getName());
		} finally {
			close(null, rs, null);
		}
	}

	private void loadRelations(ResultSet rs, Schema schema) throws SQLException {
		while (rs.next()) {
			String pkTableName = rs.getString(3);
			String pkColumnName = rs.getString(4);

			Column pkColumn = null;
			Table pkTable = schema.getTableByName(pkTableName);
			if (pkTable != null) {
				pkColumn = pkTable.getColumnByName(pkColumnName);
			}
			if (logger.isDebugEnabled()) {
				logger.debug("Found primary key relation: tableName="
						+ pkTableName + ",columnName=" + pkColumnName
						+ ", matching column: " + pkColumn);
			}

			String fkTableName = rs.getString(7);
			String fkColumnName = rs.getString(8);
			Column fkColumn = null;
			Table fkTable = schema.getTableByName(fkTableName);
			if (fkTable != null) {
				fkColumn = fkTable.getColumnByName(fkColumnName);
			}
			if (logger.isDebugEnabled()) {
				logger.debug("Found foreign key relation: tableName="
						+ fkTableName + ",columnName=" + fkColumnName
						+ ", matching column: " + fkColumn);
			}

			if (pkColumn == null || fkColumn == null) {
				logger.error(
						"Could not find relation columns: pkTableName={},pkColumnName={},fkTableName={},fkColumnName={}",
						new Object[] { pkTableName, pkColumnName, fkTableName,
								fkColumnName });
				logger.error("pkColumn={}", pkColumn);
				logger.error("fkColumn={}", fkColumn);
			} else {
				Relationship[] relations = pkTable.getRelationships(fkTable);
				boolean exists = false;
				for (Relationship relation : relations) {
					if (relation.containsColumnPair(pkColumn, fkColumn)) {
						exists = true;
						break;
					}
				}
				if (!exists) {
					MutableRelationship.createRelationship(
							new Column[] { pkColumn },
							new Column[] { fkColumn });
				}
			}
		}
	}

	public DataSet executeQuery(Query query) throws MetaModelException {
		final Connection connection = getConnection();
		ResultSet resultSet = null;

		boolean postProcessFirstRow = false;
		final Integer firstRow = query.getFirstRow();
		if (firstRow != null) {
			if (_queryRewriter.isFirstRowSupported()) {
				logger.debug("First row property will be treated by query rewriter");
			} else {
				postProcessFirstRow = true;
			}
		}

		final Statement statement;
		try {
			statement = connection.createStatement(ResultSet.TYPE_FORWARD_ONLY,
					ResultSet.CONCUR_READ_ONLY);
		} catch (SQLException e) {
			throw JdbcUtils.wrapException(e, "create statement for query");
		}

		boolean postProcessMaxRows = false;
		Integer maxRows = query.getMaxRows();
		if (maxRows != null) {
			if (postProcessFirstRow) {
				// if First row is being post processed, we need to
				// increment the "Max rows" accordingly
				maxRows = maxRows + firstRow;
				query = query.clone().setMaxRows(maxRows);

				logger.info(
						"Setting Max rows to {} because of post processing strategy of First row.",
						maxRows);
			}

			if (_queryRewriter.isMaxRowsSupported()) {
				logger.debug("Max rows property will be treated by query rewriter");
			} else {
				try {
					statement.setMaxRows(maxRows);
				} catch (SQLException e) {
					if (logger.isInfoEnabled()) {
						logger.info(
								"setMaxRows(" + maxRows + ") was rejected.", e);
					}
					postProcessMaxRows = true;
				}
			}
		}

		final String queryString = _queryRewriter.rewriteQuery(query);

		try {
			final int fetchSize = _fetchSizeCalculator.getFetchSize(query);
			logger.info("Applying fetch_size={}", fetchSize);
			try {
				statement.setFetchSize(fetchSize);
			} catch (Exception e) {
				// Ticket #372: Sometimes an exception is thrown here even
				// though it's contrary to the jdbc spec. We'll proceed without
				// doing anything about it though.
				logger.warn("Could not setFetchSize on statement: {}",
						e.getMessage());
			}

			logger.info("Executing query: {}", queryString);
			resultSet = statement.executeQuery(queryString);

			DataSet dataSet = new JdbcDataSet(query, this, connection,
					statement, resultSet);

			if (postProcessFirstRow) {
				dataSet = new FirstRowDataSet(dataSet, firstRow);
			}
			if (postProcessMaxRows) {
				dataSet = new MaxRowsDataSet(dataSet, maxRows);
			}
			return dataSet;
		} catch (SQLException e) {
			// only close in case of an error - the JdbcDataSet will close
			// otherwise
			close(connection, resultSet, statement);
			throw JdbcUtils.wrapException(e, "execute query");
		}
	}

	/**
	 * Quietly closes any of the parameterized JDBC objects
	 * 
	 * @param connection
	 * 
	 * @param rs
	 * @param st
	 */
	public void close(Connection connection, ResultSet rs, Statement st) {
		closeIfNescesary(connection);
		FileHelper.safeClose(rs, st);
	}

	/**
	 * Convenience method to get the available catalogNames using this
	 * connection.
	 * 
	 * @return a String-array with the names of the available catalogs.
	 */
	public String[] getCatalogNames() {
		Connection connection = getConnection();

		// Retrieve metadata
		DatabaseMetaData metaData = null;
		ResultSet rs = null;
		try {
			metaData = connection.getMetaData();
		} catch (SQLException e) {
			throw JdbcUtils.wrapException(e, "retrieve metadata");
		}

		// Retrieve catalogs
		logger.debug("Retrieving catalogs");
		List catalogs = new ArrayList();
		try {
			rs = metaData.getCatalogs();
			while (rs.next()) {
				String catalogName = rs.getString(1);
				logger.debug("Found catalogName: " + catalogName);
				catalogs.add(catalogName);
			}
		} catch (SQLException e) {
			logger.error("Error retrieving catalog metadata", e);
		} finally {
			close(connection, rs, null);
			logger.info("Retrieved " + catalogs.size() + " catalogs");
		}
		return catalogs.toArray(new String[catalogs.size()]);
	}

	/**
	 * Gets the delegate from the JDBC API (ie. Connection or DataSource) that
	 * is being used to perform database interactions.
	 * 
	 * @return either a DataSource or a Connection, depending on the
	 *         configuration of the DataContext.
	 */
	public Object getDelegate() {
		if (_dataSource == null) {
			return _connection;
		}
		return _dataSource;
	}

	/**
	 * Gets an appropriate connection object to use - either a dedicated
	 * connection or a new connection from the datasource object.
	 * 
	 * Hint: Use the {@link #close(Connection, ResultSet, Statement)} method to
	 * close the connection (and any ResultSet or Statements involved).
	 */
	public Connection getConnection() {
		if (_dataSource == null) {
			return _connection;
		}
		try {
			return _dataSource.getConnection();
		} catch (SQLException e) {
			throw JdbcUtils.wrapException(e, "establish connection");
		}
	}

	private void closeIfNescesary(Connection con) {
		if (con != null) {
			if (_dataSource != null) {
				// closing connections after individual usage is only nescesary
				// when they are being pulled from a DataSource.
				FileHelper.safeClose(con);
			}
		}
	}

	public String getDefaultSchemaName() {
		// Use a boolean to check if the result has been
		// found, because a schema name can actually be
		// null (for example in the case of Firebird
		// databases).
		boolean found = false;
		String result = null;
		String[] schemaNames = getSchemaNames();

		// First strategy: If there's only one schema available, that must
		// be it
		if (schemaNames.length == 1) {
			result = schemaNames[0];
			found = true;
		}

		if (!found) {
			Connection connection = getConnection();
			try {
				DatabaseMetaData metaData = connection.getMetaData();

				// Second strategy: Find default schema name by examining the
				// URL
				if (!found) {
					String url = metaData.getURL();
					if (url != null && url.length() > 0) {
						if (schemaNames.length > 0) {
							StringTokenizer st = new StringTokenizer(url,
									"/\\:");
							int tokenCount = st.countTokens();
							if (tokenCount > 0) {
								for (int i = 1; i < tokenCount; i++) {
									st.nextToken();
								}
								String lastToken = st.nextToken();

								for (int i = 0; i < schemaNames.length
										&& !found; i++) {
									String schemaName = schemaNames[i];
									if (lastToken.indexOf(schemaName) != -1) {
										result = schemaName;
										found = true;
									}
								}
							}
						}
					}
				}

				// Third strategy: Check for schema equal to username
				if (!found) {
					String username = metaData.getUserName();
					if (username != null) {
						for (int i = 0; i < schemaNames.length && !found; i++) {
							if (username.equalsIgnoreCase(schemaNames[i])) {
								result = schemaNames[i];
								found = true;
							}
						}
					}
				}

			} catch (SQLException e) {
				throw JdbcUtils.wrapException(e,
						"determine default schema name");
			} finally {
				closeIfNescesary(connection);
			}

			// Fourth strategy: Find default schema name by vendor-specific
			// hacks
			if (!found) {
				if (DATABASE_PRODUCT_POSTGRESQL
						.equalsIgnoreCase(_databaseProductName)) {
					if (_catalogName == null) {
						result = "public";
					} else {
						result = _catalogName;
					}
					found = true;
				}
				if (DATABASE_PRODUCT_HSQLDB
						.equalsIgnoreCase(_databaseProductName)) {
					for (int i = 0; i < schemaNames.length && !found; i++) {
						String schemaName = schemaNames[i];
						if ("PUBLIC".equals(schemaName)) {
							result = schemaName;
							found = true;
							break;
						}
					}
				}
				if (DATABASE_PRODUCT_SQLSERVER.equals(_databaseProductName)) {
					for (int i = 0; i < schemaNames.length && !found; i++) {
						String schemaName = schemaNames[i];
						if ("dbo".equals(schemaName)) {
							result = schemaName;
							found = true;
							break;
						}
					}
				}
			}
		}
		return result;
	}

	/**
	 * Microsoft SQL Server returns users instead of schemas when calling
	 * metadata.getSchemas() This is a simple workaround.
	 * 
	 * @return
	 * @throws SQLException
	 */
	private Set getSchemaSQLServerNames(DatabaseMetaData metaData)
			throws SQLException {
		// Distinct schema names. metaData.getTables() is a denormalized
		// resultset
		Set schemas = new HashSet();
		ResultSet rs = metaData.getTables(_catalogName, null, null,
				getTableTypesAsStrings());
		while (rs.next()) {
			schemas.add(rs.getString("TABLE_SCHEM"));
		}
		return schemas;
	}

	private String[] getTableTypesAsStrings() {
		String[] types = new String[_tableTypes.length];
		for (int i = 0; i < types.length; i++) {
			if (_tableTypes[i] == TableType.OTHER) {
				// if the OTHER type has been selected, don't use a table
				// pattern (ie. include all types)
				types = null;
				break;
			}
			types[i] = _tableTypes[i].toString();
		}
		return types;
	}

	public JdbcDataContext setQueryRewriter(IQueryRewriter queryRewriter) {
		if (queryRewriter == null) {
			throw new IllegalArgumentException("Query rewriter cannot be null");
		}
		_queryRewriter = queryRewriter;
		return this;
	}

	public IQueryRewriter getQueryRewriter() {
		return _queryRewriter;
	}

	public String getIdentifierQuoteString() {
		return _identifierQuoteString;
	}

	@Override
	protected String[] getSchemaNamesInternal() {
		Connection connection = getConnection();
		try {
			DatabaseMetaData metaData = connection.getMetaData();
			Collection result = new ArrayList();

			if (DATABASE_PRODUCT_SQLSERVER.equals(_databaseProductName)) {
				result = getSchemaSQLServerNames(metaData);
			} else if (_usesCatalogsAsSchemas) {
				String[] catalogNames = getCatalogNames();
				for (String name : catalogNames) {
					logger.info("Found catalogName: {}", name);
					result.add(name);
				}
			} else {
				ResultSet rs = metaData.getSchemas();
				while (rs.next()) {
					String schemaName = rs.getString(1);
					logger.info("Found schemaName: {}", schemaName);
					result.add(schemaName);
				}
				rs.close();
			}

			if (DATABASE_PRODUCT_MYSQL.equals(_databaseProductName)) {
				result.remove("information_schema");
			}

			// If still no schemas are found, add a schema with a null-name
			if (result.isEmpty()) {
				logger.info("No schemas or catalogs found. Creating unnamed schema.");
				result.add(null);
			}
			return result.toArray(new String[result.size()]);
		} catch (SQLException e) {
			throw JdbcUtils.wrapException(e, "get schema names");
		} finally {
			closeIfNescesary(connection);
		}
	}

	@Override
	protected Schema getSchemaByNameInternal(String name) {
		JdbcSchema schema = new JdbcSchema(name, this);
		loadTables(schema);
		return schema;
	}

	public FetchSizeCalculator getFetchSizeCalculator() {
		return _fetchSizeCalculator;
	}

	@Override
	public void executeUpdate(final UpdateScript update) {
		final JdbcUpdateCallback updateCallback;

		if (_supportsBatchUpdates) {
			updateCallback = new JdbcBatchUpdateCallback(this);
		} else {
			updateCallback = new JdbcSimpleUpdateCallback(this);
		}

		try {
			if (isSingleConnection() && isDefaultAutoCommit()) {
				// if auto-commit is going to be switched off and on during
				// updates, then the update needs to be synchronized, to avoid
				// race-conditions when switching off and on.
				synchronized (_connection) {
					update.run(updateCallback);
				}
			} else {
				update.run(updateCallback);
			}
			updateCallback.close(true);
		} catch (RuntimeException e) {
			updateCallback.close(false);
			throw e;
		}
	}

	protected boolean isSingleConnection() {
		return _singleConnection;
	}

	protected boolean isDefaultAutoCommit() {
		return _isDefaultAutoCommit;
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy