HHH-7472 - Introduce a "schema management" service

This commit is contained in:
Steve Ebersole 2012-07-28 00:04:20 -05:00
parent f43c8bab1b
commit 47f6360225
12 changed files with 564 additions and 405 deletions

View File

@ -23,17 +23,25 @@
*/
package org.hibernate.engine.jdbc.env.internal;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.env.spi.IdentifierHelper;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
import org.hibernate.engine.jdbc.env.spi.SchemaCatalogSupport;
import org.hibernate.engine.jdbc.spi.SchemaNameResolver;
import org.hibernate.engine.jdbc.env.spi.StandardSchemaCatalogSupportImpl;
import org.hibernate.engine.jdbc.spi.SqlExceptionHelper;
import org.hibernate.exception.internal.SQLExceptionTypeDelegate;
import org.hibernate.exception.internal.SQLStateConversionDelegate;
import org.hibernate.exception.internal.StandardSQLExceptionConverter;
import org.hibernate.exception.spi.SQLExceptionConverter;
import org.hibernate.metamodel.spi.relational.Identifier;
import org.hibernate.service.schema.spi.ExistingSequenceMetadataExtractor;
/**
@ -41,24 +49,58 @@ import org.hibernate.service.schema.spi.ExistingSequenceMetadataExtractor;
*/
public class JdbcEnvironmentImpl implements JdbcEnvironment {
private final Dialect dialect;
private final IdentifierHelper identifierHelper;
private final Identifier currentCatalog;
private final Identifier currentSchema;
private final SchemaCatalogSupport schemaCatalogSupport;
private final SchemaNameResolver schemaNameResolver;
private ExistingSequenceMetadataExtractor sequenceMetadataExtractor;
private final Set<String> reservedWords;
private final SqlExceptionHelper sqlExceptionHelper;
public JdbcEnvironmentImpl(
Dialect dialect,
SchemaCatalogSupport schemaCatalogSupport,
SchemaNameResolver schemaNameResolver,
ExistingSequenceMetadataExtractor sequenceMetadataExtractor,
Set<String> reservedWords) {
public JdbcEnvironmentImpl(DatabaseMetaData dbmd, Dialect dialect, Map properties) throws SQLException {
this.dialect = dialect;
this.schemaCatalogSupport = schemaCatalogSupport;
this.schemaNameResolver = schemaNameResolver;
this.sequenceMetadataExtractor = sequenceMetadataExtractor;
Set<String> reservedWords = new HashSet<String>();
reservedWords.addAll( dialect.getKeywords() );
// todo : do we need to explicitly handle SQL:2003 keywords?
reservedWords.addAll( Arrays.asList( dbmd.getSQLKeywords().split( "," ) ) );
this.reservedWords = reservedWords;
this.identifierHelper = new NormalizingIdentifierHelperImpl(
this,
dbmd.storesMixedCaseQuotedIdentifiers(),
dbmd.storesLowerCaseQuotedIdentifiers(),
dbmd.storesUpperCaseQuotedIdentifiers(),
dbmd.storesUpperCaseIdentifiers(),
dbmd.storesLowerCaseIdentifiers()
);
String currentCatalogName = dbmd.getConnection().getCatalog();
if ( currentCatalogName != null ) {
// intentionally using fromMetaDataObjectName rather than fromMetaDataCatalogName !!!
currentCatalog = identifierHelper.fromMetaDataObjectName( currentCatalogName );
}
else {
currentCatalogName = (String) properties.get( AvailableSettings.DEFAULT_CATALOG );
currentCatalog = Identifier.toIdentifier( currentCatalogName );
}
String currentSchemaName = TemporarySchemaNameResolver.INSTANCE.resolveSchemaName( dbmd.getConnection() );
if ( currentSchemaName != null ) {
// intentionally using fromMetaDataObjectName rather than fromMetaDataSchemaName !!!
currentSchema = identifierHelper.fromMetaDataObjectName( currentSchemaName );
}
else {
currentSchemaName = (String) properties.get( AvailableSettings.DEFAULT_SCHEMA );
currentSchema = Identifier.toIdentifier( currentSchemaName );
}
schemaCatalogSupport = new StandardSchemaCatalogSupportImpl(
dbmd.getCatalogSeparator(),
dbmd.isCatalogAtStart(),
dialect.openQuote(),
dialect.closeQuote()
);
SQLExceptionConverter sqlExceptionConverter = dialect.buildSQLExceptionConverter();
if ( sqlExceptionConverter == null ) {
@ -66,11 +108,56 @@ public class JdbcEnvironmentImpl implements JdbcEnvironment {
sqlExceptionConverter = converter;
converter.addDelegate( dialect.buildSQLExceptionConversionDelegate() );
converter.addDelegate( new SQLExceptionTypeDelegate( dialect ) );
// todo : vary this based on extractedMetaDataSupport.getSqlStateType()
converter.addDelegate( new SQLStateConversionDelegate( dialect ) );
}
this.sqlExceptionHelper = new SqlExceptionHelper( sqlExceptionConverter );
this.sequenceMetadataExtractor = new TemporaryExistingSequenceMetadataExtractor( this );
}
public JdbcEnvironmentImpl(Dialect dialect, Map properties) {
this.dialect = dialect;
Set<String> reservedWords = new HashSet<String>();
reservedWords.addAll( dialect.getKeywords() );
// todo : do we need to explicitly handle SQL:2003 keywords?
this.reservedWords = reservedWords;
// again, a simple temporary impl that works on H2
this.identifierHelper = new NormalizingIdentifierHelperImpl(
this,
true, // storesMixedCaseQuotedIdentifiers
false, // storesLowerCaseQuotedIdentifiers
false, // storesUpperCaseQuotedIdentifiers
true, // storesUpperCaseIdentifiers
false // storesLowerCaseIdentifiers
);
String currentCatalogName = (String) properties.get( AvailableSettings.DEFAULT_CATALOG );
currentCatalog = Identifier.toIdentifier( currentCatalogName );
String currentSchemaName = (String) properties.get( AvailableSettings.DEFAULT_SCHEMA );
currentSchema = Identifier.toIdentifier( currentSchemaName );
// again, a simple temporary impl that works on H2
schemaCatalogSupport = new StandardSchemaCatalogSupportImpl(
".",
true,
dialect.openQuote(),
dialect.closeQuote()
);
SQLExceptionConverter sqlExceptionConverter = dialect.buildSQLExceptionConverter();
if ( sqlExceptionConverter == null ) {
final StandardSQLExceptionConverter converter = new StandardSQLExceptionConverter();
sqlExceptionConverter = converter;
converter.addDelegate( dialect.buildSQLExceptionConversionDelegate() );
converter.addDelegate( new SQLExceptionTypeDelegate( dialect ) );
converter.addDelegate( new SQLStateConversionDelegate( dialect ) );
}
this.sqlExceptionHelper = new SqlExceptionHelper( sqlExceptionConverter );
this.sequenceMetadataExtractor = new TemporaryExistingSequenceMetadataExtractor( this );
}
@Override
@ -78,14 +165,24 @@ public class JdbcEnvironmentImpl implements JdbcEnvironment {
return dialect;
}
@Override
public Identifier getCurrentCatalog() {
return currentCatalog;
}
@Override
public Identifier getCurrentSchema() {
return currentSchema;
}
@Override
public SchemaCatalogSupport getSchemaCatalogSupport() {
return schemaCatalogSupport;
}
@Override
public SchemaNameResolver getSchemaNameResolver() {
return schemaNameResolver;
public IdentifierHelper getIdentifierHelper() {
return identifierHelper;
}
@Override

View File

@ -0,0 +1,199 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2012, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* 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.hibernate.engine.jdbc.env.internal;
import org.jboss.logging.Logger;
import org.hibernate.engine.jdbc.env.spi.IdentifierHelper;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.metamodel.spi.relational.Identifier;
/**
* @author Steve Ebersole
*/
public class NormalizingIdentifierHelperImpl implements IdentifierHelper {
private static final Logger log = Logger.getLogger( NormalizingIdentifierHelperImpl.class );
private final JdbcEnvironment jdbcEnvironment;
private final boolean storesMixedCaseQuotedIdentifiers;
private final boolean storesLowerCaseQuotedIdentifiers;
private final boolean storesUpperCaseQuotedIdentifiers;
private final boolean storesUpperCaseIdentifiers;
private final boolean storesLowerCaseIdentifiers;
public NormalizingIdentifierHelperImpl(
JdbcEnvironment jdbcEnvironment, boolean storesMixedCaseQuotedIdentifiers,
boolean storesLowerCaseQuotedIdentifiers,
boolean storesUpperCaseQuotedIdentifiers,
boolean storesUpperCaseIdentifiers,
boolean storesLowerCaseIdentifiers) {
this.jdbcEnvironment = jdbcEnvironment;
this.storesMixedCaseQuotedIdentifiers = storesMixedCaseQuotedIdentifiers;
this.storesLowerCaseQuotedIdentifiers = storesLowerCaseQuotedIdentifiers;
this.storesUpperCaseQuotedIdentifiers = storesUpperCaseQuotedIdentifiers;
this.storesUpperCaseIdentifiers = storesUpperCaseIdentifiers;
this.storesLowerCaseIdentifiers = storesLowerCaseIdentifiers;
if ( storesMixedCaseQuotedIdentifiers && storesLowerCaseQuotedIdentifiers && storesUpperCaseQuotedIdentifiers ) {
log.warn( "JDBC Driver reports it stores quoted identifiers in mixed, upper and lower case" );
}
else if ( storesMixedCaseQuotedIdentifiers && storesUpperCaseQuotedIdentifiers ) {
log.warn( "JDBC Driver reports it stores quoted identifiers in both mixed and upper case" );
}
else if ( storesMixedCaseQuotedIdentifiers && storesLowerCaseQuotedIdentifiers ) {
log.warn( "JDBC Driver reports it stores quoted identifiers in both mixed and lower case" );
}
if ( storesUpperCaseIdentifiers && storesLowerCaseIdentifiers ) {
log.warn( "JDBC Driver reports it stores non-quoted identifiers in both upper and lower case" );
}
if ( storesUpperCaseIdentifiers && storesUpperCaseQuotedIdentifiers ) {
log.warn( "JDBC Driver reports it stores both quoted and non-quoted identifiers in upper case" );
}
if ( storesLowerCaseIdentifiers && storesLowerCaseQuotedIdentifiers ) {
log.warn( "JDBC Driver reports it stores both quoted and non-quoted identifiers in lower case" );
}
}
// In the DatabaseMetaData method params for catalog and schema name have the following meaning:
// 1) <""> means to match things "without a catalog/schema"
// 2) <null> means to not limit results based on this field
//
// todo : not sure how "without a catalog/schema" is interpreted. Current?
@Override
public String toMetaDataCatalogName(Identifier identifier) {
if ( identifier == null ) {
// todo : not sure if this is interpreted as <""> or <currentCatalog>
return jdbcEnvironment.getCurrentCatalog().getText();
}
return toText( identifier );
}
private String toText(Identifier identifier) {
if ( identifier == null ) {
throw new IllegalArgumentException( "Identifier cannot be null; bad usage" );
}
if ( identifier.isQuoted() && storesMixedCaseQuotedIdentifiers ) {
return identifier.getText();
}
else if ( ( identifier.isQuoted() && storesUpperCaseQuotedIdentifiers )
|| ( !identifier.isQuoted() && storesUpperCaseIdentifiers ) ) {
return StringHelper.toUpperCase( identifier.getText() );
}
else if ( ( identifier.isQuoted() && storesLowerCaseQuotedIdentifiers )
|| ( !identifier.isQuoted() && storesLowerCaseIdentifiers ) ) {
return StringHelper.toLowerCase( identifier.getText() );
}
return identifier.getText();
}
@Override
public String toMetaDataSchemaName(Identifier identifier) {
if ( identifier == null ) {
// todo : not sure if this is interpreted as <""> or <currentSchema>
return jdbcEnvironment.getCurrentSchema().getText();
}
return toText( identifier );
}
@Override
public String toMetaDataObjectName(Identifier identifier) {
if ( identifier == null ) {
// if this method was called, the value is needed
throw new IllegalArgumentException( );
}
return toText( identifier );
}
@Override
public Identifier fromMetaDataCatalogName(String catalogName) {
if ( catalogName == null ) {
return null;
}
if ( catalogName.equals( jdbcEnvironment.getCurrentCatalog().getText() ) ) {
return null;
}
return toIdentifier( catalogName );
// note really sure the best way to know (can you?) whether the identifier is quoted
}
private Identifier toIdentifier(String incomingName) {
// lovely decipher of whether the incoming value represents a quoted identifier...
final boolean isUpperCase = incomingName.toUpperCase().equals( incomingName );
final boolean isLowerCase = incomingName.toLowerCase().equals( incomingName );
final boolean isMixedCase = ! isLowerCase && ! isUpperCase;
if ( jdbcEnvironment.getReservedWords().contains( incomingName ) ) {
// unequivocally it needs to be quoted...
return Identifier.toIdentifier( incomingName, true );
}
if ( storesMixedCaseQuotedIdentifiers && isMixedCase ) {
return Identifier.toIdentifier( incomingName, true );
}
if ( storesLowerCaseQuotedIdentifiers && isLowerCase ) {
return Identifier.toIdentifier( incomingName, true );
}
if ( storesUpperCaseQuotedIdentifiers && isUpperCase ) {
return Identifier.toIdentifier( incomingName, true );
}
return Identifier.toIdentifier( incomingName );
}
@Override
public Identifier fromMetaDataSchemaName(String schemaName) {
if ( schemaName == null ) {
return null;
}
if ( schemaName.equals( jdbcEnvironment.getCurrentSchema().getText() ) ) {
return null;
}
return toIdentifier( schemaName );
}
@Override
public Identifier fromMetaDataObjectName(String objectName) {
if ( objectName == null ) {
return null;
}
return toIdentifier( objectName );
}
}

View File

@ -0,0 +1,101 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2012, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* 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.hibernate.engine.jdbc.env.internal;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import org.hibernate.metamodel.spi.relational.ObjectName;
import org.hibernate.service.schema.internal.ExistingSequenceMetadataImpl;
import org.hibernate.service.schema.spi.ExistingSequenceMetadata;
import org.hibernate.service.schema.spi.ExistingSequenceMetadataExtractor;
/**
* Temporary implementation that works for H2.
*
* @author Steve Ebersole
*/
public class TemporaryExistingSequenceMetadataExtractor implements ExistingSequenceMetadataExtractor {
private final JdbcEnvironmentImpl jdbcEnvironment;
public TemporaryExistingSequenceMetadataExtractor(JdbcEnvironmentImpl jdbcEnvironment) {
this.jdbcEnvironment = jdbcEnvironment;
}
@Override
public Iterable<ExistingSequenceMetadata> extractMetadata(DatabaseMetaData databaseMetaData) throws SQLException {
Statement statement = databaseMetaData.getConnection().createStatement();
try {
ResultSet resultSet = statement.executeQuery(
"select SEQUENCE_CATALOG, SEQUENCE_SCHEMA, SEQUENCE_NAME " +
"from information_schema.sequences"
);
try {
final List<ExistingSequenceMetadata> sequenceMetadataList = new ArrayList<ExistingSequenceMetadata>();
while ( resultSet.next() ) {
sequenceMetadataList.add(
new ExistingSequenceMetadataImpl(
new ObjectName(
jdbcEnvironment.getIdentifierHelper().fromMetaDataCatalogName(
resultSet.getString(
"SEQUENCE_CATALOG"
)
),
jdbcEnvironment.getIdentifierHelper().fromMetaDataSchemaName(
resultSet.getString(
"SEQUENCE_SCHEMA"
)
),
jdbcEnvironment.getIdentifierHelper().fromMetaDataCatalogName(
resultSet.getString(
"SEQUENCE_NAME"
)
)
)
)
);
}
return sequenceMetadataList;
}
finally {
try {
resultSet.close();
}
catch (SQLException ignore) {
}
}
}
finally {
try {
statement.close();
}
catch (SQLException ignore) {
}
}
}
}

View File

@ -0,0 +1,69 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2012, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* 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.hibernate.engine.jdbc.env.internal;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import org.hibernate.engine.jdbc.env.spi.SchemaNameResolver;
/**
* Temporary implementation that works for H2.
*
* @author Steve Ebersole
*/
public class TemporarySchemaNameResolver implements SchemaNameResolver {
public static final TemporarySchemaNameResolver INSTANCE = new TemporarySchemaNameResolver();
@Override
public String resolveSchemaName(Connection connection) throws SQLException {
// the H2 variant...
Statement statement = connection.createStatement();
try {
ResultSet resultSet = statement.executeQuery( "call schema()" );
try {
if ( ! resultSet.next() ) {
return null;
}
return resultSet.getString( 1 );
}
finally {
try {
resultSet.close();
}
catch (SQLException ignore) {
}
}
}
finally {
try {
statement.close();
}
catch (SQLException ignore) {
}
}
}
}

View File

@ -21,7 +21,7 @@
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.service.schema.spi;
package org.hibernate.engine.jdbc.env.spi;
import org.hibernate.metamodel.spi.relational.Identifier;

View File

@ -26,8 +26,8 @@ package org.hibernate.engine.jdbc.env.spi;
import java.util.Set;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.spi.SchemaNameResolver;
import org.hibernate.engine.jdbc.spi.SqlExceptionHelper;
import org.hibernate.metamodel.spi.relational.Identifier;
import org.hibernate.service.schema.spi.ExistingSequenceMetadataExtractor;
/**
@ -39,9 +39,13 @@ import org.hibernate.service.schema.spi.ExistingSequenceMetadataExtractor;
public interface JdbcEnvironment {
public Dialect getDialect();
public Identifier getCurrentCatalog();
public Identifier getCurrentSchema();
public SchemaCatalogSupport getSchemaCatalogSupport();
public SchemaNameResolver getSchemaNameResolver();
public IdentifierHelper getIdentifierHelper();
public Set<String> getReservedWords();

View File

@ -21,7 +21,7 @@
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.engine.jdbc.spi;
package org.hibernate.engine.jdbc.env.spi;
import java.sql.Connection;
import java.sql.SQLException;

View File

@ -1,168 +0,0 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2012, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* 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.hibernate.engine.jdbc.env.spi;
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.HashSet;
import java.util.List;
import java.util.Set;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentImpl;
import org.hibernate.engine.jdbc.spi.SchemaNameResolver;
import org.hibernate.metamodel.spi.relational.ObjectName;
import org.hibernate.service.schema.internal.ExistingSequenceMetadataImpl;
import org.hibernate.service.schema.spi.ExistingSequenceMetadata;
import org.hibernate.service.schema.spi.ExistingSequenceMetadataExtractor;
import org.hibernate.service.schema.spi.IdentifierHelper;
/**
* @author Steve Ebersole
*/
public class StandardJdbcEnvironmentBuilder {
public static final StandardJdbcEnvironmentBuilder INSTANCE = new StandardJdbcEnvironmentBuilder();
private static final SchemaNameResolver FOR_NOW_SCHEMA_NAME_RESOLVER = new SchemaNameResolver() {
@Override
public String resolveSchemaName(Connection connection) throws SQLException {
// the H2 variant...
Statement statement = connection.createStatement();
try {
ResultSet resultSet = statement.executeQuery( "call schema()" );
try {
if ( ! resultSet.next() ) {
return null;
}
return resultSet.getString( 1 );
}
finally {
try {
resultSet.close();
}
catch (SQLException ignore) {
}
}
}
finally {
try {
statement.close();
}
catch (SQLException ignore) {
}
}
}
};
private static final ExistingSequenceMetadataExtractor FOR_NOW_SEQ_META_EXTRACTOR = new ExistingSequenceMetadataExtractor() {
@Override
public Iterable<ExistingSequenceMetadata> extractMetadata(
DatabaseMetaData databaseMetaData,
IdentifierHelper identifierHelper) throws SQLException {
// again, the H2 variant...
Statement statement = databaseMetaData.getConnection().createStatement();
try {
ResultSet resultSet = statement.executeQuery(
"select SEQUENCE_CATALOG, SEQUENCE_SCHEMA, SEQUENCE_NAME " +
"from information_schema.sequences"
);
try {
final List<ExistingSequenceMetadata> sequenceMetadataList = new ArrayList<ExistingSequenceMetadata>();
while ( resultSet.next() ) {
sequenceMetadataList.add(
new ExistingSequenceMetadataImpl(
new ObjectName(
identifierHelper.fromMetaDataCatalogName(
resultSet.getString(
"SEQUENCE_CATALOG"
)
),
identifierHelper.fromMetaDataSchemaName( resultSet.getString( "SEQUENCE_SCHEMA" ) ),
identifierHelper.fromMetaDataCatalogName( resultSet.getString( "SEQUENCE_NAME" ) )
)
)
);
}
return sequenceMetadataList;
}
finally {
try {
resultSet.close();
}
catch (SQLException ignore) {
}
}
}
finally {
try {
statement.close();
}
catch (SQLException ignore) {
}
}
}
};
public JdbcEnvironment buildJdbcEnvironment(DatabaseMetaData dbmd, Dialect dialect) throws SQLException {
SchemaCatalogSupport schemaCatalogSupport = new StandardSchemaCatalogSupportImpl(
dbmd.getCatalogSeparator(),
dbmd.isCatalogAtStart(),
dialect.openQuote(),
dialect.closeQuote()
);
Set<String> reservedWords = new HashSet<String>();
reservedWords.addAll( dialect.getKeywords() );
// todo : do we need to explicitly handle SQL:2003 keywords?
reservedWords.addAll( Arrays.asList( dbmd.getSQLKeywords().split( "," ) ) );
return new JdbcEnvironmentImpl(
dialect,
schemaCatalogSupport,
FOR_NOW_SCHEMA_NAME_RESOLVER,
FOR_NOW_SEQ_META_EXTRACTOR,
reservedWords
);
}
public JdbcEnvironment buildJdbcEnvironment(Dialect dialect) throws SQLException {
SchemaCatalogSupport schemaCatalogSupport = new StandardSchemaCatalogSupportImpl( dialect );
Set<String> reservedWords = new HashSet<String>();
reservedWords.addAll( dialect.getKeywords() );
return new JdbcEnvironmentImpl(
dialect,
schemaCatalogSupport,
FOR_NOW_SCHEMA_NAME_RESOLVER,
FOR_NOW_SEQ_META_EXTRACTOR,
reservedWords
);
}
}

View File

@ -46,7 +46,7 @@ import org.hibernate.engine.jdbc.spi.ExtractedDatabaseMetaData;
import org.hibernate.engine.jdbc.spi.JdbcConnectionAccess;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.jdbc.spi.ResultSetWrapper;
import org.hibernate.engine.jdbc.spi.SchemaNameResolver;
import org.hibernate.engine.jdbc.env.spi.SchemaNameResolver;
import org.hibernate.engine.jdbc.spi.SqlExceptionHelper;
import org.hibernate.engine.jdbc.spi.SqlStatementLogger;
import org.hibernate.exception.internal.SQLExceptionTypeDelegate;

View File

@ -30,11 +30,9 @@ import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;
import org.jboss.logging.Logger;
import org.hibernate.TruthValue;
import org.hibernate.engine.jdbc.env.spi.IdentifierHelper;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.metamodel.spi.relational.Identifier;
import org.hibernate.metamodel.spi.relational.ObjectName;
import org.hibernate.metamodel.spi.relational.Schema;
@ -42,19 +40,14 @@ import org.hibernate.service.schema.spi.ExistingColumnMetadata;
import org.hibernate.service.schema.spi.ExistingDatabaseMetaData;
import org.hibernate.service.schema.spi.ExistingSequenceMetadata;
import org.hibernate.service.schema.spi.ExistingTableMetadata;
import org.hibernate.service.schema.spi.IdentifierHelper;
/**
* @author Steve Ebersole
*/
public class ExistingDatabaseMetaDataImpl implements ExistingDatabaseMetaData {
private static final Logger log = Logger.getLogger( ExistingDatabaseMetaDataImpl.class );
private final JdbcEnvironment jdbcEnvironment;
private final DatabaseMetaData databaseMetaData;
private final IdentifierHelperImpl identifierHelper;
private final Map<ObjectName,ExistingTableMetadataImpl> tables = new HashMap<ObjectName, ExistingTableMetadataImpl>();
private final Map<ObjectName,ExistingSequenceMetadata> sequences;
@ -70,34 +63,28 @@ public class ExistingDatabaseMetaDataImpl implements ExistingDatabaseMetaData {
private ExistingDatabaseMetaDataImpl(JdbcEnvironment jdbcEnvironment, DatabaseMetaData databaseMetaData) throws SQLException {
this.jdbcEnvironment = jdbcEnvironment;
this.databaseMetaData = databaseMetaData;
this.identifierHelper = new IdentifierHelperImpl(
databaseMetaData.getConnection().getCatalog(),
jdbcEnvironment.getSchemaNameResolver().resolveSchemaName( databaseMetaData.getConnection() ),
databaseMetaData.storesMixedCaseQuotedIdentifiers(),
databaseMetaData.storesLowerCaseQuotedIdentifiers(),
databaseMetaData.storesUpperCaseQuotedIdentifiers(),
databaseMetaData.storesUpperCaseIdentifiers(),
databaseMetaData.storesLowerCaseIdentifiers()
);
this.sequences = loadSequenceMetadataMap();
}
sequences = loadSequenceMetadataMap();
public IdentifierHelper identifierHelper() {
return jdbcEnvironment.getIdentifierHelper();
}
private static final String[] TABLE_TYPES = new String[] { "TABLE", "VIEW" };
private void loadTableMetadata(ResultSet resultSet) throws SQLException {
while ( resultSet.next() ) {
final Identifier catalogIdentifier = identifierHelper.fromMetaDataCatalogName(
final Identifier catalogIdentifier = identifierHelper().fromMetaDataCatalogName(
resultSet.getString(
"TABLE_CAT"
)
);
final Identifier schemaIdentifier = identifierHelper.fromMetaDataSchemaName(
final Identifier schemaIdentifier = identifierHelper().fromMetaDataSchemaName(
resultSet.getString(
"TABLE_SCHEM"
)
);
final Identifier tableIdentifier = identifierHelper.fromMetaDataObjectName(
final Identifier tableIdentifier = identifierHelper().fromMetaDataObjectName(
resultSet.getString(
"TABLE_NAME"
)
@ -110,20 +97,17 @@ public class ExistingDatabaseMetaDataImpl implements ExistingDatabaseMetaData {
}
tableMetadata = new ExistingTableMetadataImpl( this, tableName );
tables.put( tableName, tableMetadata );
tables.put( toMapKey( tableName ), tableMetadata );
}
}
private Map<ObjectName,ExistingSequenceMetadata> loadSequenceMetadataMap() throws SQLException {
Map<ObjectName,ExistingSequenceMetadata> sequences = new HashMap<ObjectName, ExistingSequenceMetadata>();
final Iterable<ExistingSequenceMetadata> sequenceMetadatas =
jdbcEnvironment.getExistingSequenceMetadataExtractor().extractMetadata(
databaseMetaData,
identifierHelper
);
jdbcEnvironment.getExistingSequenceMetadataExtractor().extractMetadata( databaseMetaData );
if ( sequenceMetadatas != null ) {
for ( ExistingSequenceMetadata sequenceMetadata :sequenceMetadatas ) {
sequences.put( sequenceMetadata.getSequenceName(), sequenceMetadata );
sequences.put( toMapKey( sequenceMetadata.getSequenceName() ), sequenceMetadata );
}
}
return sequences;
@ -131,12 +115,37 @@ public class ExistingDatabaseMetaDataImpl implements ExistingDatabaseMetaData {
@Override
public ExistingTableMetadata getTableMetadata(ObjectName tableName) {
return tables.get( tableName );
return tables.get( toMapKey( tableName ) );
}
public static ObjectName toMapKey(ObjectName qualifiedName) {
Identifier catalog = qualifiedName.getCatalog();
if ( catalog != null ) {
if ( ! catalog.isQuoted() ) {
catalog = Identifier.toIdentifier( catalog.getText().toUpperCase() );
}
}
Identifier schema = qualifiedName.getSchema();
if ( schema != null ) {
if ( ! schema.isQuoted() ) {
schema = Identifier.toIdentifier( schema.getText().toUpperCase() );
}
}
Identifier name = qualifiedName.getName();
if ( name != null ) {
if ( ! name.isQuoted() ) {
name = Identifier.toIdentifier( name.getText().toUpperCase() );
}
}
return new ObjectName( catalog, schema, name );
}
@Override
public ExistingSequenceMetadata getSequenceMetadata(ObjectName sequenceName) {
return sequences.get( sequenceName );
return sequences.get( toMapKey( sequenceName ) );
}
public Map<Identifier, ExistingColumnMetadata> getColumnMetadata(ExistingTableMetadata tableMetadata) {
@ -144,9 +153,9 @@ public class ExistingDatabaseMetaDataImpl implements ExistingDatabaseMetaData {
try {
ResultSet resultSet = databaseMetaData.getColumns(
identifierHelper.toMetaDataCatalogName( tableMetadata.getName().getCatalog() ),
identifierHelper.toMetaDataSchemaName( tableMetadata.getName().getSchema() ),
identifierHelper.toMetaDataObjectName( tableMetadata.getName().getName() ),
identifierHelper().toMetaDataCatalogName( tableMetadata.getName().getCatalog() ),
identifierHelper().toMetaDataSchemaName( tableMetadata.getName().getSchema() ),
identifierHelper().toMetaDataObjectName( tableMetadata.getName().getName() ),
"%"
);
@ -194,9 +203,9 @@ public class ExistingDatabaseMetaDataImpl implements ExistingDatabaseMetaData {
try {
ResultSet resultSet = databaseMetaData.getImportedKeys(
identifierHelper.toMetaDataCatalogName( tableMetadata.getName().getCatalog() ),
identifierHelper.toMetaDataSchemaName( tableMetadata.getName().getSchema() ),
identifierHelper.toMetaDataObjectName( tableMetadata.getName().getName() )
identifierHelper().toMetaDataCatalogName( tableMetadata.getName().getCatalog() ),
identifierHelper().toMetaDataSchemaName( tableMetadata.getName().getSchema() ),
identifierHelper().toMetaDataObjectName( tableMetadata.getName().getName() )
);
// todo : need to account for getCrossReference() as well...
@ -271,172 +280,6 @@ public class ExistingDatabaseMetaDataImpl implements ExistingDatabaseMetaData {
return TruthValue.UNKNOWN;
}
public class IdentifierHelperImpl implements IdentifierHelper {
private final String currentCatalog;
private final String currentSchema;
private final boolean storesMixedCaseQuotedIdentifiers;
private final boolean storesLowerCaseQuotedIdentifiers;
private final boolean storesUpperCaseQuotedIdentifiers;
private final boolean storesUpperCaseIdentifiers;
private final boolean storesLowerCaseIdentifiers;
public IdentifierHelperImpl(
String currentCatalog,
String currentSchema,
boolean storesMixedCaseQuotedIdentifiers,
boolean storesLowerCaseQuotedIdentifiers,
boolean storesUpperCaseQuotedIdentifiers,
boolean storesUpperCaseIdentifiers,
boolean storesLowerCaseIdentifiers) {
this.currentCatalog = currentCatalog;
this.currentSchema = currentSchema;
this.storesMixedCaseQuotedIdentifiers = storesMixedCaseQuotedIdentifiers;
this.storesLowerCaseQuotedIdentifiers = storesLowerCaseQuotedIdentifiers;
this.storesUpperCaseQuotedIdentifiers = storesUpperCaseQuotedIdentifiers;
this.storesUpperCaseIdentifiers = storesUpperCaseIdentifiers;
this.storesLowerCaseIdentifiers = storesLowerCaseIdentifiers;
if ( storesMixedCaseQuotedIdentifiers && storesLowerCaseQuotedIdentifiers && storesUpperCaseQuotedIdentifiers ) {
log.warn( "JDBC Driver reports it stores quoted identifiers in mixed, upper and lower case" );
}
else if ( storesMixedCaseQuotedIdentifiers && storesUpperCaseQuotedIdentifiers ) {
log.warn( "JDBC Driver reports it stores quoted identifiers in both mixed and upper case" );
}
else if ( storesMixedCaseQuotedIdentifiers && storesLowerCaseQuotedIdentifiers ) {
log.warn( "JDBC Driver reports it stores quoted identifiers in both mixed and lower case" );
}
if ( storesUpperCaseIdentifiers && storesLowerCaseIdentifiers ) {
log.warn( "JDBC Driver reports it stores non-quoted identifiers in both upper and lower case" );
}
if ( storesUpperCaseIdentifiers && storesUpperCaseQuotedIdentifiers ) {
log.warn( "JDBC Driver reports it stores both quoted and non-quoted identifiers in upper case" );
}
if ( storesLowerCaseIdentifiers && storesLowerCaseQuotedIdentifiers ) {
log.warn( "JDBC Driver reports it stores both quoted and non-quoted identifiers in lower case" );
}
}
// In the DatabaseMetaData method params for catalog and schema name have the following meaning:
// 1) <""> means to match things "without a catalog/schema"
// 2) <null> means to not limit results based on this field
//
// todo : not sure how "without a catalog/schema" is interpreted. Current?
@Override
public String toMetaDataCatalogName(Identifier identifier) {
if ( identifier == null ) {
// todo : not sure if this is interpreted as <""> or <currentCatalog>
return currentCatalog;
}
return toText( identifier );
}
private String toText(Identifier identifier) {
if ( identifier == null ) {
throw new IllegalArgumentException( "Identifier cannot be null; bad usage" );
}
if ( identifier.isQuoted() && storesMixedCaseQuotedIdentifiers ) {
return identifier.getText();
}
else if ( ( identifier.isQuoted() && storesUpperCaseQuotedIdentifiers )
|| ( !identifier.isQuoted() && storesUpperCaseIdentifiers ) ) {
return StringHelper.toUpperCase( identifier.getText() );
}
else if ( ( identifier.isQuoted() && storesLowerCaseQuotedIdentifiers )
|| ( !identifier.isQuoted() && storesLowerCaseIdentifiers ) ) {
return StringHelper.toLowerCase( identifier.getText() );
}
return identifier.getText();
}
@Override
public String toMetaDataSchemaName(Identifier identifier) {
if ( identifier == null ) {
// todo : not sure if this is interpreted as <""> or <currentSchema>
return currentSchema;
}
return toText( identifier );
}
@Override
public String toMetaDataObjectName(Identifier identifier) {
if ( identifier == null ) {
// if this method was called, the value is needed
throw new IllegalArgumentException( );
}
return toText( identifier );
}
@Override
public Identifier fromMetaDataCatalogName(String catalogName) {
if ( catalogName == null ) {
return null;
}
if ( catalogName.equals( currentCatalog ) ) {
return null;
}
return toIdentifier( catalogName );
// note really sure the best way to know (can you?) whether the identifier is quoted
}
private Identifier toIdentifier(String incomingName) {
// lovely decipher of whether the incoming value represents a quoted identifier...
final boolean isUpperCase = incomingName.toUpperCase().equals( incomingName );
final boolean isLowerCase = incomingName.toLowerCase().equals( incomingName );
final boolean isMixedCase = ! isLowerCase && ! isUpperCase;
if ( jdbcEnvironment.getReservedWords().contains( incomingName ) ) {
// unequivocally it needs to be quoted...
return Identifier.toIdentifier( incomingName, true );
}
if ( storesMixedCaseQuotedIdentifiers && isMixedCase ) {
return Identifier.toIdentifier( incomingName, true );
}
if ( storesLowerCaseQuotedIdentifiers && isLowerCase ) {
return Identifier.toIdentifier( incomingName, true );
}
if ( storesUpperCaseQuotedIdentifiers && isUpperCase ) {
return Identifier.toIdentifier( incomingName, true );
}
return Identifier.toIdentifier( incomingName );
}
@Override
public Identifier fromMetaDataSchemaName(String schemaName) {
if ( schemaName == null ) {
return null;
}
if ( schemaName.equals( currentSchema ) ) {
return null;
}
return toIdentifier( schemaName );
}
@Override
public Identifier fromMetaDataObjectName(String objectName) {
if ( objectName == null ) {
return null;
}
return toIdentifier( objectName );
}
}
public static interface Builder {
public Builder prepareAll();
public Builder prepareCatalogAndSchema(Schema.Name schemaName);
@ -487,8 +330,8 @@ public class ExistingDatabaseMetaDataImpl implements ExistingDatabaseMetaData {
@Override
public Builder prepareCatalogAndSchema(Schema.Name schemaName) {
prepare(
it.identifierHelper.toMetaDataCatalogName( schemaName.getCatalog() ),
it.identifierHelper.toMetaDataSchemaName( schemaName.getSchema() )
it.identifierHelper().toMetaDataCatalogName( schemaName.getCatalog() ),
it.identifierHelper().toMetaDataSchemaName( schemaName.getSchema() )
);
return this;
}
@ -496,7 +339,7 @@ public class ExistingDatabaseMetaDataImpl implements ExistingDatabaseMetaData {
@Override
public Builder prepareCatalog(Identifier catalog) {
prepare(
it.identifierHelper.toMetaDataCatalogName( catalog ),
it.identifierHelper().toMetaDataCatalogName( catalog ),
null
);
return this;
@ -506,7 +349,7 @@ public class ExistingDatabaseMetaDataImpl implements ExistingDatabaseMetaData {
public Builder prepareSchema(Identifier schema) {
prepare(
null,
it.identifierHelper.toMetaDataSchemaName( schema )
it.identifierHelper().toMetaDataSchemaName( schema )
);
return this;
}

View File

@ -31,7 +31,5 @@ import java.sql.SQLException;
* @author Steve Ebersole
*/
public interface ExistingSequenceMetadataExtractor {
public Iterable<ExistingSequenceMetadata> extractMetadata(
DatabaseMetaData databaseMetaData,
IdentifierHelper identifierHelper) throws SQLException;
public Iterable<ExistingSequenceMetadata> extractMetadata(DatabaseMetaData databaseMetaData) throws SQLException;
}

View File

@ -30,8 +30,9 @@ import java.util.Properties;
import org.hibernate.cfg.Environment;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentImpl;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
import org.hibernate.engine.jdbc.env.spi.StandardJdbcEnvironmentBuilder;
import org.hibernate.metamodel.spi.relational.ObjectName;
import org.hibernate.service.schema.internal.ExistingDatabaseMetaDataImpl;
import org.hibernate.service.schema.spi.ExistingDatabaseMetaData;
@ -41,6 +42,8 @@ import org.junit.Test;
import org.hibernate.testing.junit4.BaseUnitTestCase;
import static org.junit.Assert.assertNotNull;
/**
* @author Steve Ebersole
*/
@ -56,18 +59,15 @@ public class ExistingDatabaseMetaDataImplTest extends BaseUnitTestCase {
props.getProperty( Environment.USER ),
props.getProperty( Environment.PASS )
);
connection.createStatement().execute( "CREATE SCHEMA \"another_schema\"" );
connection.createStatement().execute( "CREATE SCHEMA another_schema" );
connection.createStatement().execute( "CREATE TABLE t1 (name varchar)" );
connection.createStatement().execute( "CREATE TABLE db1.\"another_schema\".t2 (name varchar)" );
connection.createStatement().execute( "CREATE TABLE another_schema.t2 (name varchar)" );
connection.createStatement().execute( "CREATE SEQUENCE seq1" );
connection.createStatement().execute( "CREATE SEQUENCE db1.\"another_schema\".seq1" );
connection.createStatement().execute( "CREATE SEQUENCE db1.another_schema.seq2" );
jdbcEnvironment = StandardJdbcEnvironmentBuilder.INSTANCE.buildJdbcEnvironment(
connection.getMetaData(),
Dialect.getDialect( props )
);
jdbcEnvironment = new JdbcEnvironmentImpl( connection.getMetaData(), Dialect.getDialect( props ), props );
}
@After
@ -81,10 +81,26 @@ public class ExistingDatabaseMetaDataImplTest extends BaseUnitTestCase {
}
}
// @Test
@Test
public void testGetTableMetadata() throws Exception {
ExistingDatabaseMetaData databaseMetaData =
ExistingDatabaseMetaDataImpl.builder( jdbcEnvironment, connection.getMetaData() ).prepareAll().build();
databaseMetaData.toString();
ObjectName name = new ObjectName( null, null, "t1" );
assertNotNull( databaseMetaData.getTableMetadata( name ) );
name = new ObjectName( null, "another_schema", "t2" );
assertNotNull( databaseMetaData.getTableMetadata( name ) );
name = new ObjectName( null, null, "seq1" );
assertNotNull( databaseMetaData.getSequenceMetadata( name ) );
name = new ObjectName( null, "another_schema", "seq2" );
assertNotNull( databaseMetaData.getSequenceMetadata( name ) );
// knowing if identifiers coming back from the database are quoted is all dicked up...
// see org.hibernate.engine.jdbc.env.internal.NormalizingIdentifierHelperImpl
//
// surely JDBC has a better way to determine this right?
}
}