Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -927,6 +927,7 @@ public String translateExtractField(TemporalUnit unit) {
public IdentifierHelper buildIdentifierHelper(IdentifierHelperBuilder builder, DatabaseMetaData dbMetaData)
throws SQLException {
builder.setAutoQuoteInitialUnderscore(true);
builder.setAutoQuoteDollar(true);
return super.buildIdentifierHelper(builder, dbMetaData);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package org.hibernate.boot.model.naming;

import java.util.Locale;
import java.util.Objects;

import org.hibernate.dialect.Dialect;

Expand Down Expand Up @@ -67,19 +68,19 @@ public static Identifier toIdentifier(String text, boolean quote) {
/**
* Means to generate an {@link Identifier} instance from its simple text form.
* <p>
* If passed text is {@code null}, {@code null} is returned.
* If passed {@code text} is {@code null}, {@code null} is returned.
* <p>
* If passed text is surrounded in quote markers, the generated Identifier
* is considered quoted. Quote markers include back-ticks (`),
* double-quotes (") and brackets ([ and ]).
* If passed {@code text} is surrounded in quote markers, the returned Identifier
* is considered quoted. Quote markers include back-ticks (`), double-quotes ("),
* and brackets ([ and ]).
*
* @param text The text form
* @param quote Whether to quote unquoted text forms
* @param quoteOnNonIdentifierChar Controls whether to treat the result as quoted if text contains characters that are invalid for identifiers
* @param autoquote Whether to quote the result if it contains special characters
*
* @return The identifier form, or {@code null} if text was {@code null}
*/
public static Identifier toIdentifier(String text, boolean quote, boolean quoteOnNonIdentifierChar) {
public static Identifier toIdentifier(String text, boolean quote, boolean autoquote) {
if ( isBlank( text ) ) {
return null;
}
Expand All @@ -102,24 +103,40 @@ public static Identifier toIdentifier(String text, boolean quote, boolean quoteO
end--;
quote = true;
}
else if ( quoteOnNonIdentifierChar && !quote ) {
// Check the letters to determine if we must quote the text
char c = text.charAt( start );
if ( !isLetter( c ) && c != '_' ) {
// SQL identifiers must begin with a letter or underscore
quote = true;
}
else {
for ( int i = start + 1; i < end; i++ ) {
c = text.charAt( i );
if ( !isLetterOrDigit( c ) && c != '_' ) {
quote = true;
break;
}
else if ( autoquote && !quote ) {
quote = autoquote( text, start, end );
}
return new Identifier( text.substring( start, end ), quote );
}

private static boolean autoquote(String text, int start, int end) {
// Check the letters to determine if we must quote the text
if ( !isLegalFirstChar( text.charAt( start ) ) ) {
// SQL identifiers must begin with a letter or underscore
return true;
}
else {
for ( int i = start + 1; i < end; i++ ) {
if ( !isLegalChar( text.charAt( i ) ) ) {
return true;
}
}
}
return new Identifier( text.substring( start, end ), quote );
return false;
}

private static boolean isLegalChar(char current) {
return isLetterOrDigit( current )
// every database also allows _ here
|| current == '_'
// every database except HSQLDB also allows $ here
|| current == '$';
}

private static boolean isLegalFirstChar(char first) {
return isLetter( first )
// many databases also allow _ here
|| first == '_';
}

/**
Expand All @@ -141,21 +158,22 @@ public static boolean isQuoted(String name) {

public static boolean isQuoted(String name, int start, int end) {
if ( start + 2 < end ) {
switch ( name.charAt( start ) ) {
case '`':
return name.charAt( end - 1 ) == '`';
case '[':
return name.charAt( end - 1 ) == ']';
case '"':
return name.charAt( end - 1 ) == '"';
}
final char first = name.charAt( start );
final char last = name.charAt( end - 1 );
return switch ( first ) {
case '`' -> last == '`';
case '[' -> last == ']';
case '"' -> last == '"';
default -> false;
};
}
else {
return false;
}
return false;
}

public static String unQuote(String name) {
assert isQuoted( name );

return name.substring( 1, name.length() - 1 );
}

Expand Down Expand Up @@ -236,11 +254,9 @@ public String toString() {
}

@Override
public boolean equals(Object o) {
if ( !(o instanceof Identifier that) ) {
return false;
}
return getCanonicalName().equals( that.getCanonicalName() );
public boolean equals(Object object) {
return object instanceof Identifier that
&& getCanonicalName().equals( that.getCanonicalName() );
}

public boolean matches(String name) {
Expand All @@ -251,16 +267,13 @@ public boolean matches(String name) {

@Override
public int hashCode() {
return isQuoted ? text.hashCode() : text.toLowerCase( Locale.ENGLISH ).hashCode();
return isQuoted
? text.hashCode()
: text.toLowerCase( Locale.ENGLISH ).hashCode();
}

public static boolean areEqual(Identifier id1, Identifier id2) {
if ( id1 == null ) {
return id2 == null;
}
else {
return id1.equals( id2 );
}
return Objects.equals( id1, id2 );
}

public static Identifier quote(Identifier identifier) {
Expand All @@ -270,7 +283,7 @@ public static Identifier quote(Identifier identifier) {
}

@Override
public int compareTo(Identifier o) {
return getCanonicalName().compareTo( o.getCanonicalName() );
public int compareTo(Identifier identifier) {
return getCanonicalName().compareTo( identifier.getCanonicalName() );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -712,6 +712,7 @@ public String translateExtractField(TemporalUnit unit) {
public IdentifierHelper buildIdentifierHelper(IdentifierHelperBuilder builder, DatabaseMetaData dbMetaData)
throws SQLException {
builder.setAutoQuoteInitialUnderscore( true );
builder.setAutoQuoteDollar( true );
return super.buildIdentifierHelper( builder, dbMetaData );
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,10 @@
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
import org.hibernate.engine.jdbc.env.spi.NameQualifierSupport;

import org.jboss.logging.Logger;

/**
* @author Steve Ebersole
*/
public class NormalizingIdentifierHelperImpl implements IdentifierHelper {
private static final Logger log = Logger.getLogger( NormalizingIdentifierHelperImpl.class );

private final JdbcEnvironment jdbcEnvironment;

Expand All @@ -30,6 +27,7 @@ public class NormalizingIdentifierHelperImpl implements IdentifierHelper {
private final boolean globallyQuoteIdentifiersSkipColumnDefinitions;
private final boolean autoQuoteKeywords;
private final boolean autoQuoteInitialUnderscore;
private final boolean autoQuoteDollar;
private final TreeSet<String> reservedWords;
private final IdentifierCaseStrategy unquotedCaseStrategy;
private final IdentifierCaseStrategy quotedCaseStrategy;
Expand All @@ -41,6 +39,7 @@ public NormalizingIdentifierHelperImpl(
boolean globallyQuoteIdentifiersSkipColumnDefinitions,
boolean autoQuoteKeywords,
boolean autoQuoteInitialUnderscore,
boolean autoQuoteDollar,
TreeSet<String> reservedWords, //careful, we intentionally omit making a defensive copy to not waste memory
IdentifierCaseStrategy unquotedCaseStrategy,
IdentifierCaseStrategy quotedCaseStrategy) {
Expand All @@ -50,39 +49,33 @@ public NormalizingIdentifierHelperImpl(
this.globallyQuoteIdentifiersSkipColumnDefinitions = globallyQuoteIdentifiersSkipColumnDefinitions;
this.autoQuoteKeywords = autoQuoteKeywords;
this.autoQuoteInitialUnderscore = autoQuoteInitialUnderscore;
this.autoQuoteDollar = autoQuoteDollar;
this.reservedWords = reservedWords;
this.unquotedCaseStrategy = unquotedCaseStrategy == null ? IdentifierCaseStrategy.UPPER : unquotedCaseStrategy;
this.quotedCaseStrategy = quotedCaseStrategy == null ? IdentifierCaseStrategy.MIXED : quotedCaseStrategy;
}

@Override
public Identifier normalizeQuoting(Identifier identifier) {
log.tracef( "Normalizing identifier quoting [%s]", identifier );

if ( identifier == null ) {
return null;
}

if ( identifier.isQuoted() ) {
else if ( identifier.isQuoted() ) {
return identifier;
}

if ( globallyQuoteIdentifiers ) {
log.tracef( "Forcing identifier [%s] to quoted for global quoting", identifier );
else if ( mustQuote( identifier ) ) {
return Identifier.toIdentifier( identifier.getText(), true );
}

if ( autoQuoteKeywords && isReservedWord( identifier.getText() ) ) {
log.tracef( "Forcing identifier [%s] to quoted as recognized reserved word", identifier );
return Identifier.toIdentifier( identifier.getText(), true );
}

if ( autoQuoteInitialUnderscore && identifier.getText().startsWith("_") ) {
log.tracef( "Forcing identifier [%s] to quoted due to initial underscore", identifier );
return Identifier.toIdentifier( identifier.getText(), true );
else {
return identifier;
}
}

return identifier;
private boolean mustQuote(Identifier identifier) {
return globallyQuoteIdentifiers
|| autoQuoteKeywords && isReservedWord( identifier.getText() )
|| autoQuoteInitialUnderscore && identifier.getText().startsWith( "_" )
|| autoQuoteDollar && identifier.getText().contains( "$" );
}

@Override
Expand Down Expand Up @@ -110,10 +103,7 @@ public boolean isReservedWord(String word) {

@Override
public String toMetaDataCatalogName(Identifier identifier) {
log.tracef( "Normalizing identifier quoting for catalog name [%s]", identifier );

if ( !nameQualifierSupport.supportsCatalogs() ) {
log.trace( "Environment does not support catalogs; returning null" );
// null is used to tell DatabaseMetaData to not limit results based on catalog.
return null;
}
Expand All @@ -133,53 +123,30 @@ private String toMetaDataText(Identifier identifier) {
throw new IllegalArgumentException( "Identifier cannot be null; bad usage" );
}

final String text = identifier.getText();
if ( identifier instanceof DatabaseIdentifier ) {
return identifier.getText();
return text;
}

if ( identifier.isQuoted() ) {
switch ( quotedCaseStrategy ) {
case UPPER: {
log.tracef( "Rendering quoted identifier [%s] in upper case for use in DatabaseMetaData", identifier );
return identifier.getText().toUpperCase( Locale.ROOT );
}
case LOWER: {
log.tracef( "Rendering quoted identifier [%s] in lower case for use in DatabaseMetaData", identifier );
return identifier.getText().toLowerCase( Locale.ROOT );
}
default: {
// default is mixed case
log.tracef( "Rendering quoted identifier [%s] in mixed case for use in DatabaseMetaData", identifier );
return identifier.getText();
}
}
else if ( identifier.isQuoted() ) {
return switch ( quotedCaseStrategy ) {
case UPPER -> text.toUpperCase( Locale.ROOT );
case LOWER -> text.toLowerCase( Locale.ROOT );
case MIXED -> text; // default
};
}
else {
switch ( unquotedCaseStrategy ) {
case MIXED: {
log.tracef( "Rendering unquoted identifier [%s] in mixed case for use in DatabaseMetaData", identifier );
return identifier.getText();
}
case LOWER: {
log.tracef( "Rendering unquoted identifier [%s] in lower case for use in DatabaseMetaData", identifier );
return identifier.getText().toLowerCase( Locale.ROOT );
}
default: {
// default is upper case
log.tracef( "Rendering unquoted identifier [%s] in upper case for use in DatabaseMetaData", identifier );
return identifier.getText().toUpperCase( Locale.ROOT );
}
}
return switch ( unquotedCaseStrategy ) {
case MIXED -> text;
case LOWER -> text.toLowerCase( Locale.ROOT );
case UPPER -> text.toUpperCase( Locale.ROOT ); // default
};
}
}

@Override
public String toMetaDataSchemaName(Identifier identifier) {
log.tracef( "Normalizing identifier quoting for schema name [%s]", identifier );

if ( !nameQualifierSupport.supportsSchemas() ) {
// null is used to tell DatabaseMetaData to not limit results based on schema.
log.trace( "Environment does not support catalogs; returning null" );
return null;
}

Expand All @@ -195,8 +162,6 @@ public String toMetaDataSchemaName(Identifier identifier) {

@Override
public String toMetaDataObjectName(Identifier identifier) {
log.tracef( "Normalizing identifier quoting for object name [%s]", identifier );

if ( identifier == null ) {
// if this method was called, the value is needed
throw new IllegalArgumentException( "null was passed as an object name" );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public class IdentifierHelperBuilder {
private boolean skipGlobalQuotingForColumnDefinitions = false;
private boolean autoQuoteKeywords = true;
private boolean autoQuoteInitialUnderscore = false;
private boolean autoQuoteDollar = false;
private IdentifierCaseStrategy unquotedCaseStrategy = IdentifierCaseStrategy.UPPER;
private IdentifierCaseStrategy quotedCaseStrategy = IdentifierCaseStrategy.MIXED;

Expand Down Expand Up @@ -150,6 +151,10 @@ public void setAutoQuoteInitialUnderscore(boolean autoQuoteInitialUnderscore) {
this.autoQuoteInitialUnderscore = autoQuoteInitialUnderscore;
}

public void setAutoQuoteDollar(boolean autoQuoteDollar) {
this.autoQuoteDollar = autoQuoteDollar;
}

public NameQualifierSupport getNameQualifierSupport() {
return nameQualifierSupport;
}
Expand Down Expand Up @@ -215,6 +220,7 @@ public IdentifierHelper build() {
skipGlobalQuotingForColumnDefinitions,
autoQuoteKeywords,
autoQuoteInitialUnderscore,
autoQuoteDollar,
reservedWords,
unquotedCaseStrategy,
quotedCaseStrategy
Expand Down
Loading