HHH-5135 - "Ambiguous column" exception thrown with columns having the same name as a function registered with the dialect (e.g. to_date, floor)
git-svn-id: https://svn.jboss.org/repos/hibernate/core/trunk@19411 1b8cb986-b30d-0410-93ca-fae66ebed9b2
This commit is contained in:
parent
8f4401349f
commit
cb51ef1a64
|
@ -61,7 +61,8 @@ public class Column implements Selectable, Serializable, Cloneable {
|
|||
private String customWrite;
|
||||
private String customRead;
|
||||
|
||||
public Column() { };
|
||||
public Column() {
|
||||
}
|
||||
|
||||
public Column(String columnName) {
|
||||
setName(columnName);
|
||||
|
@ -262,12 +263,17 @@ public class Column implements Selectable, Serializable, Cloneable {
|
|||
}
|
||||
|
||||
public String getTemplate(Dialect dialect, SQLFunctionRegistry functionRegistry) {
|
||||
String expr = getReadExpr(dialect);
|
||||
return Template.renderWhereStringTemplate(expr, dialect, functionRegistry);
|
||||
return hasCustomRead()
|
||||
? Template.renderWhereStringTemplate( customRead, dialect, functionRegistry )
|
||||
: Template.TEMPLATE + '.' + getQuotedName( dialect );
|
||||
}
|
||||
|
||||
public boolean hasCustomRead() {
|
||||
return ( customRead != null && customRead.length() > 0 );
|
||||
}
|
||||
|
||||
public String getReadExpr(Dialect dialect) {
|
||||
return ( customRead != null && customRead.length() > 0 ) ? customRead : getQuotedName(dialect);
|
||||
return hasCustomRead() ? customRead : getQuotedName( dialect );
|
||||
}
|
||||
|
||||
public String getWriteExpr() {
|
||||
|
|
|
@ -24,9 +24,13 @@
|
|||
*/
|
||||
package org.hibernate.sql;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.dialect.Dialect;
|
||||
import org.hibernate.dialect.function.SQLFunction;
|
||||
import org.hibernate.dialect.function.SQLFunctionRegistry;
|
||||
|
@ -43,9 +47,9 @@ import org.hibernate.engine.SessionFactoryImplementor;
|
|||
*/
|
||||
public final class Template {
|
||||
|
||||
private static final java.util.Set KEYWORDS = new HashSet();
|
||||
private static final java.util.Set BEFORE_TABLE_KEYWORDS = new HashSet();
|
||||
private static final java.util.Set FUNCTION_KEYWORDS = new HashSet();
|
||||
private static final Set<String> KEYWORDS = new HashSet<String>();
|
||||
private static final Set<String> BEFORE_TABLE_KEYWORDS = new HashSet<String>();
|
||||
private static final Set<String> FUNCTION_KEYWORDS = new HashSet<String>();
|
||||
static {
|
||||
KEYWORDS.add("and");
|
||||
KEYWORDS.add("or");
|
||||
|
@ -109,13 +113,14 @@ public final class Template {
|
|||
*
|
||||
* @deprecated Only intended for annotations usage; use {@link #renderWhereStringTemplate(String, String, Dialect, SQLFunctionRegistry)} instead
|
||||
*/
|
||||
@SuppressWarnings({ "JavaDoc" })
|
||||
public static String renderWhereStringTemplate(String sqlWhereString, String placeholder, Dialect dialect) {
|
||||
return renderWhereStringTemplate( sqlWhereString, placeholder, dialect, new SQLFunctionRegistry( dialect, java.util.Collections.EMPTY_MAP ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes the where condition provided in the mapping attribute and interpolates the alias.
|
||||
* Handles subselects, quoted identifiers, quoted strings, expressions, SQL functions,
|
||||
* Takes the where condition provided in the mapping attribute and interpolates the alias.
|
||||
* Handles sub-selects, quoted identifiers, quoted strings, expressions, SQL functions,
|
||||
* named parameters.
|
||||
*
|
||||
* @param sqlWhereString The string into which to interpolate the placeholder value
|
||||
|
@ -125,51 +130,55 @@ public final class Template {
|
|||
* @return The rendered sql fragment
|
||||
*/
|
||||
public static String renderWhereStringTemplate(String sqlWhereString, String placeholder, Dialect dialect, SQLFunctionRegistry functionRegistry ) {
|
||||
//TODO: make this a bit nicer
|
||||
|
||||
// IMPL NOTE : The basic process here is to tokenize the incoming string and to iterate over each token
|
||||
// in turn. As we process each token, we set a series of flags used to indicate the type of context in
|
||||
// which the tokens occur. Depending on the state of those flags we decide whether we need to qualify
|
||||
// identifier references.
|
||||
|
||||
String symbols = new StringBuffer()
|
||||
.append("=><!+-*/()',|&`")
|
||||
.append(StringHelper.WHITESPACE)
|
||||
.append( dialect.openQuote() )
|
||||
.append( dialect.closeQuote() )
|
||||
.toString();
|
||||
StringTokenizer tokens = new StringTokenizer(sqlWhereString, symbols, true);
|
||||
|
||||
.append( "=><!+-*/()',|&`" )
|
||||
.append( StringHelper.WHITESPACE )
|
||||
.append( dialect.openQuote() )
|
||||
.append( dialect.closeQuote() )
|
||||
.toString();
|
||||
StringTokenizer tokens = new StringTokenizer( sqlWhereString, symbols, true );
|
||||
StringBuffer result = new StringBuffer();
|
||||
|
||||
boolean quoted = false;
|
||||
boolean quotedIdentifier = false;
|
||||
boolean beforeTable = false;
|
||||
boolean inFromClause = false;
|
||||
boolean afterFromTable = false;
|
||||
|
||||
|
||||
boolean hasMore = tokens.hasMoreTokens();
|
||||
String nextToken = hasMore ? tokens.nextToken() : null;
|
||||
while (hasMore) {
|
||||
while ( hasMore ) {
|
||||
String token = nextToken;
|
||||
String lcToken = token.toLowerCase();
|
||||
hasMore = tokens.hasMoreTokens();
|
||||
nextToken = hasMore ? tokens.nextToken() : null;
|
||||
|
||||
|
||||
boolean isQuoteCharacter = false;
|
||||
|
||||
|
||||
if ( !quotedIdentifier && "'".equals(token) ) {
|
||||
quoted = !quoted;
|
||||
isQuoteCharacter = true;
|
||||
}
|
||||
|
||||
|
||||
if ( !quoted ) {
|
||||
|
||||
boolean isOpenQuote;
|
||||
if ( "`".equals(token) ) {
|
||||
isOpenQuote = !quotedIdentifier;
|
||||
token = lcToken = isOpenQuote ?
|
||||
new Character( dialect.openQuote() ).toString() :
|
||||
new Character( dialect.closeQuote() ).toString();
|
||||
quotedIdentifier = isOpenQuote;
|
||||
token = lcToken = isOpenQuote
|
||||
? Character.toString( dialect.openQuote() )
|
||||
: Character.toString( dialect.closeQuote() );
|
||||
quotedIdentifier = isOpenQuote;
|
||||
isQuoteCharacter = true;
|
||||
}
|
||||
else if ( !quotedIdentifier && ( dialect.openQuote()==token.charAt(0) ) ) {
|
||||
isOpenQuote = true;
|
||||
quotedIdentifier = true;
|
||||
quotedIdentifier = true;
|
||||
isQuoteCharacter = true;
|
||||
}
|
||||
else if ( quotedIdentifier && ( dialect.closeQuote()==token.charAt(0) ) ) {
|
||||
|
@ -180,40 +189,120 @@ public final class Template {
|
|||
else {
|
||||
isOpenQuote = false;
|
||||
}
|
||||
|
||||
if (isOpenQuote) {
|
||||
result.append(placeholder).append('.');
|
||||
|
||||
if ( isOpenQuote ) {
|
||||
result.append( placeholder ).append( '.' );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
boolean quotedOrWhitespace = quoted ||
|
||||
quotedIdentifier ||
|
||||
isQuoteCharacter ||
|
||||
Character.isWhitespace( token.charAt(0) );
|
||||
|
||||
if (quotedOrWhitespace) {
|
||||
result.append(token);
|
||||
|
||||
// Special processing for ANSI SQL EXTRACT function
|
||||
if ( "extract".equals( lcToken ) && "(".equals( nextToken ) ) {
|
||||
final String field = extractUntil( tokens, "from" );
|
||||
final String source = renderWhereStringTemplate(
|
||||
extractUntil( tokens, ")" ),
|
||||
placeholder,
|
||||
dialect,
|
||||
functionRegistry
|
||||
);
|
||||
result.append( "extract(" ).append( field ).append( " from " ).append( source ).append( ')' );
|
||||
|
||||
hasMore = tokens.hasMoreTokens();
|
||||
nextToken = hasMore ? tokens.nextToken() : null;
|
||||
|
||||
continue;
|
||||
}
|
||||
else if (beforeTable) {
|
||||
result.append(token);
|
||||
|
||||
// Special processing for ANSI SQL TRIM function
|
||||
if ( "trim".equals( lcToken ) && "(".equals( nextToken ) ) {
|
||||
List<String> operands = new ArrayList<String>();
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
boolean hasMoreOperands = true;
|
||||
String operandToken = tokens.nextToken();
|
||||
boolean quotedOperand = false;
|
||||
while ( hasMoreOperands ) {
|
||||
final boolean isQuote = "'".equals( operandToken );
|
||||
if ( isQuote ) {
|
||||
quotedOperand = !quotedOperand;
|
||||
if ( !quotedOperand ) {
|
||||
operands.add( builder.append( '\'' ).toString() );
|
||||
builder.setLength( 0 );
|
||||
}
|
||||
else {
|
||||
builder.append( '\'' );
|
||||
}
|
||||
}
|
||||
else if ( quotedOperand ) {
|
||||
builder.append( operandToken );
|
||||
}
|
||||
else if ( operandToken.length() == 1 && Character.isWhitespace( operandToken.charAt( 0 ) ) ) {
|
||||
// do nothing
|
||||
}
|
||||
else {
|
||||
operands.add( operandToken );
|
||||
}
|
||||
operandToken = tokens.nextToken();
|
||||
hasMoreOperands = tokens.hasMoreTokens() && ! ")".equals( operandToken );
|
||||
}
|
||||
|
||||
TrimOperands trimOperands = new TrimOperands( operands );
|
||||
result.append( "trim(" );
|
||||
if ( trimOperands.trimSpec != null ) {
|
||||
result.append( trimOperands.trimSpec ).append( ' ' );
|
||||
}
|
||||
if ( trimOperands.trimChar != null ) {
|
||||
if ( trimOperands.trimChar.startsWith( "'" ) && trimOperands.trimChar.endsWith( "'" ) ) {
|
||||
result.append( trimOperands.trimChar );
|
||||
}
|
||||
else {
|
||||
result.append(
|
||||
renderWhereStringTemplate( trimOperands.trimSpec, placeholder, dialect, functionRegistry )
|
||||
);
|
||||
}
|
||||
result.append( ' ' );
|
||||
}
|
||||
if ( trimOperands.from != null ) {
|
||||
result.append( trimOperands.from ).append( ' ' );
|
||||
}
|
||||
else if ( trimOperands.trimSpec != null || trimOperands.trimChar != null ) {
|
||||
// I think ANSI SQL says that the 'from' is not optional if either trim-spec or trim-char are specified
|
||||
result.append( "from " );
|
||||
}
|
||||
|
||||
result.append( renderWhereStringTemplate( trimOperands.trimSource, placeholder, dialect, functionRegistry ) )
|
||||
.append( ')' );
|
||||
|
||||
hasMore = tokens.hasMoreTokens();
|
||||
nextToken = hasMore ? tokens.nextToken() : null;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
boolean quotedOrWhitespace = quoted || quotedIdentifier || isQuoteCharacter
|
||||
|| Character.isWhitespace( token.charAt(0) );
|
||||
|
||||
if ( quotedOrWhitespace ) {
|
||||
result.append( token );
|
||||
}
|
||||
else if ( beforeTable ) {
|
||||
result.append( token );
|
||||
beforeTable = false;
|
||||
afterFromTable = true;
|
||||
}
|
||||
else if (afterFromTable) {
|
||||
if ( !"as".equals(lcToken) ) afterFromTable = false;
|
||||
else if ( afterFromTable ) {
|
||||
if ( !"as".equals(lcToken) ) {
|
||||
afterFromTable = false;
|
||||
}
|
||||
result.append(token);
|
||||
}
|
||||
else if ( isNamedParameter(token) ) {
|
||||
result.append(token);
|
||||
}
|
||||
else if (
|
||||
isIdentifier(token, dialect) &&
|
||||
!isFunctionOrKeyword(lcToken, nextToken, dialect , functionRegistry)
|
||||
) {
|
||||
else if ( isIdentifier(token, dialect)
|
||||
&& !isFunctionOrKeyword(lcToken, nextToken, dialect , functionRegistry) ) {
|
||||
result.append(placeholder)
|
||||
.append('.')
|
||||
.append( dialect.quote(token) );
|
||||
.append('.')
|
||||
.append( dialect.quote(token) );
|
||||
}
|
||||
else {
|
||||
if ( BEFORE_TABLE_KEYWORDS.contains(lcToken) ) {
|
||||
|
@ -225,19 +314,313 @@ public final class Template {
|
|||
}
|
||||
result.append(token);
|
||||
}
|
||||
|
||||
if ( //Yuck:
|
||||
inFromClause &&
|
||||
KEYWORDS.contains(lcToken) && //"as" is not in KEYWORDS
|
||||
!BEFORE_TABLE_KEYWORDS.contains(lcToken)
|
||||
) {
|
||||
|
||||
//Yuck:
|
||||
if ( inFromClause
|
||||
&& KEYWORDS.contains( lcToken ) //"as" is not in KEYWORDS
|
||||
&& !BEFORE_TABLE_KEYWORDS.contains( lcToken ) ) {
|
||||
inFromClause = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Takes the where condition provided in the mapping attribute and interpolates the alias.
|
||||
// * Handles sub-selects, quoted identifiers, quoted strings, expressions, SQL functions,
|
||||
// * named parameters.
|
||||
// *
|
||||
// * @param sqlWhereString The string into which to interpolate the placeholder value
|
||||
// * @param placeholder The value to be interpolated into the the sqlWhereString
|
||||
// * @param dialect The dialect to apply
|
||||
// * @param functionRegistry The registry of all sql functions
|
||||
// *
|
||||
// * @return The rendered sql fragment
|
||||
// */
|
||||
// public static String renderWhereStringTemplate(
|
||||
// String sqlWhereString,
|
||||
// String placeholder,
|
||||
// Dialect dialect,
|
||||
// SQLFunctionRegistry functionRegistry) {
|
||||
//
|
||||
// // IMPL NOTE : The basic process here is to tokenize the incoming string and to iterate over each token
|
||||
// // in turn. As we process each token, we set a series of flags used to indicate the type of context in
|
||||
// // which the tokens occur. Depending on the state of those flags we decide whether we need to qualify
|
||||
// // identifier references.
|
||||
//
|
||||
// final String dialectOpenQuote = Character.toString( dialect.openQuote() );
|
||||
// final String dialectCloseQuote = Character.toString( dialect.closeQuote() );
|
||||
//
|
||||
// String symbols = new StringBuilder()
|
||||
// .append( "=><!+-*/()',|&`" )
|
||||
// .append( StringHelper.WHITESPACE )
|
||||
// .append( dialect.openQuote() )
|
||||
// .append( dialect.closeQuote() )
|
||||
// .toString();
|
||||
// StringTokenizer tokens = new StringTokenizer( sqlWhereString, symbols, true );
|
||||
// ProcessingState state = new ProcessingState();
|
||||
//
|
||||
// StringBuilder quotedBuffer = new StringBuilder();
|
||||
// StringBuilder result = new StringBuilder();
|
||||
//
|
||||
// boolean hasMore = tokens.hasMoreTokens();
|
||||
// String nextToken = hasMore ? tokens.nextToken() : null;
|
||||
// while ( hasMore ) {
|
||||
// String token = nextToken;
|
||||
// String lcToken = token.toLowerCase();
|
||||
// hasMore = tokens.hasMoreTokens();
|
||||
// nextToken = hasMore ? tokens.nextToken() : null;
|
||||
//
|
||||
// // First, determine quoting which might be based on either:
|
||||
// // 1) back-tick
|
||||
// // 2) single quote (ANSI SQL standard)
|
||||
// // 3) or dialect defined quote character(s)
|
||||
// QuotingCharacterDisposition quotingCharacterDisposition = QuotingCharacterDisposition.NONE;
|
||||
// if ( "`".equals( token ) ) {
|
||||
// state.quoted = !state.quoted;
|
||||
// quotingCharacterDisposition = state.quoted
|
||||
// ? QuotingCharacterDisposition.OPEN
|
||||
// : QuotingCharacterDisposition.CLOSE;
|
||||
// // replace token with the appropriate dialect quoting char
|
||||
// token = lcToken = ( quotingCharacterDisposition == QuotingCharacterDisposition.OPEN )
|
||||
// ? dialectOpenQuote
|
||||
// : dialectCloseQuote;
|
||||
// }
|
||||
// else if ( "'".equals( token ) ) {
|
||||
// state.quoted = !state.quoted;
|
||||
// quotingCharacterDisposition = state.quoted
|
||||
// ? QuotingCharacterDisposition.OPEN
|
||||
// : QuotingCharacterDisposition.CLOSE;
|
||||
// }
|
||||
// else if ( !state.quoted && dialectOpenQuote.equals( token ) ) {
|
||||
// state.quoted = true;
|
||||
// quotingCharacterDisposition = QuotingCharacterDisposition.OPEN;
|
||||
// }
|
||||
// else if ( state.quoted && dialectCloseQuote.equals( token ) ) {
|
||||
// state.quoted = false;
|
||||
// quotingCharacterDisposition = QuotingCharacterDisposition.CLOSE;
|
||||
// }
|
||||
//
|
||||
// if ( state.quoted ) {
|
||||
// quotedBuffer.append( token );
|
||||
// continue;
|
||||
// }
|
||||
//
|
||||
// // if we were previously processing quoted state and just encountered the close quote, then handle that
|
||||
// // quoted text
|
||||
// if ( quotingCharacterDisposition == QuotingCharacterDisposition.CLOSE ) {
|
||||
// token = quotedBuffer.toString();
|
||||
// quotedBuffer.setLength( 0 );
|
||||
// result.append( placeholder ).append( '.' )
|
||||
// .append( dialectOpenQuote ).append( token ).append( dialectCloseQuote );
|
||||
// continue;
|
||||
// }
|
||||
//
|
||||
// // Special processing for ANSI SQL EXTRACT function
|
||||
// if ( "extract".equals( lcToken ) && "(".equals( nextToken ) ) {
|
||||
// final String field = extractUntil( tokens, "from" );
|
||||
// final String source = renderWhereStringTemplate(
|
||||
// extractUntil( tokens, ")" ),
|
||||
// placeholder,
|
||||
// dialect,
|
||||
// functionRegistry
|
||||
// );
|
||||
// result.append( "extract(" ).append( field ).append( " from " ).append( source ).append( ')' );
|
||||
//
|
||||
// hasMore = tokens.hasMoreTokens();
|
||||
// nextToken = hasMore ? tokens.nextToken() : null;
|
||||
//
|
||||
// continue;
|
||||
// }
|
||||
//
|
||||
// // Special processing for ANSI SQL TRIM function
|
||||
// if ( "trim".equals( lcToken ) && "(".equals( nextToken ) ) {
|
||||
// List<String> operands = new ArrayList<String>();
|
||||
// StringBuilder builder = new StringBuilder();
|
||||
//
|
||||
// boolean hasMoreOperands = true;
|
||||
// String operandToken = tokens.nextToken();
|
||||
// boolean quoted = false;
|
||||
// while ( hasMoreOperands ) {
|
||||
// final boolean isQuote = "'".equals( operandToken );
|
||||
// if ( isQuote ) {
|
||||
// quoted = !quoted;
|
||||
// if ( !quoted ) {
|
||||
// operands.add( builder.append( '\'' ).toString() );
|
||||
// builder.setLength( 0 );
|
||||
// }
|
||||
// else {
|
||||
// builder.append( '\'' );
|
||||
// }
|
||||
// }
|
||||
// else if ( quoted ) {
|
||||
// builder.append( operandToken );
|
||||
// }
|
||||
// else if ( operandToken.length() == 1 && Character.isWhitespace( operandToken.charAt( 0 ) ) ) {
|
||||
// // do nothing
|
||||
// }
|
||||
// else {
|
||||
// operands.add( operandToken );
|
||||
// }
|
||||
// operandToken = tokens.nextToken();
|
||||
// hasMoreOperands = tokens.hasMoreTokens() && ! ")".equals( operandToken );
|
||||
// }
|
||||
//
|
||||
// TrimOperands trimOperands = new TrimOperands( operands );
|
||||
// result.append( "trim(" );
|
||||
// if ( trimOperands.trimSpec != null ) {
|
||||
// result.append( trimOperands.trimSpec ).append( ' ' );
|
||||
// }
|
||||
// if ( trimOperands.trimChar != null ) {
|
||||
// if ( trimOperands.trimChar.startsWith( "'" ) && trimOperands.trimChar.endsWith( "'" ) ) {
|
||||
// result.append( trimOperands.trimChar );
|
||||
// }
|
||||
// else {
|
||||
// result.append(
|
||||
// renderWhereStringTemplate( trimOperands.trimSpec, placeholder, dialect, functionRegistry )
|
||||
// );
|
||||
// }
|
||||
// result.append( ' ' );
|
||||
// }
|
||||
// if ( trimOperands.from != null ) {
|
||||
// result.append( trimOperands.from ).append( ' ' );
|
||||
// }
|
||||
// else if ( trimOperands.trimSpec != null || trimOperands.trimChar != null ) {
|
||||
// // I think ANSI SQL says that the 'from' is not optional if either trim-spec or trim-char are specified
|
||||
// result.append( "from " );
|
||||
// }
|
||||
//
|
||||
// result.append( renderWhereStringTemplate( trimOperands.trimSource, placeholder, dialect, functionRegistry ) )
|
||||
// .append( ')' );
|
||||
//
|
||||
// hasMore = tokens.hasMoreTokens();
|
||||
// nextToken = hasMore ? tokens.nextToken() : null;
|
||||
//
|
||||
// continue;
|
||||
// }
|
||||
//
|
||||
//
|
||||
// // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
// if ( Character.isWhitespace( token.charAt( 0 ) ) ) {
|
||||
// result.append( token );
|
||||
// }
|
||||
// else if ( state.beforeTable ) {
|
||||
// result.append( token );
|
||||
// state.beforeTable = false;
|
||||
// state.afterFromTable = true;
|
||||
// }
|
||||
// else if ( state.afterFromTable ) {
|
||||
// if ( !"as".equals(lcToken) ) {
|
||||
// state.afterFromTable = false;
|
||||
// }
|
||||
// result.append(token);
|
||||
// }
|
||||
// else if ( isNamedParameter(token) ) {
|
||||
// result.append(token);
|
||||
// }
|
||||
// else if ( isIdentifier(token, dialect)
|
||||
// && !isFunctionOrKeyword(lcToken, nextToken, dialect , functionRegistry) ) {
|
||||
// result.append(placeholder)
|
||||
// .append('.')
|
||||
// .append( dialect.quote(token) );
|
||||
// }
|
||||
// else {
|
||||
// if ( BEFORE_TABLE_KEYWORDS.contains(lcToken) ) {
|
||||
// state.beforeTable = true;
|
||||
// state.inFromClause = true;
|
||||
// }
|
||||
// else if ( state.inFromClause && ",".equals(lcToken) ) {
|
||||
// state.beforeTable = true;
|
||||
// }
|
||||
// result.append(token);
|
||||
// }
|
||||
//
|
||||
// //Yuck:
|
||||
// if ( state.inFromClause
|
||||
// && KEYWORDS.contains( lcToken ) //"as" is not in KEYWORDS
|
||||
// && !BEFORE_TABLE_KEYWORDS.contains( lcToken ) ) {
|
||||
// state.inFromClause = false;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return result.toString();
|
||||
// }
|
||||
//
|
||||
// private static class ProcessingState {
|
||||
// boolean quoted = false;
|
||||
// boolean quotedIdentifier = false;
|
||||
// boolean beforeTable = false;
|
||||
// boolean inFromClause = false;
|
||||
// boolean afterFromTable = false;
|
||||
// }
|
||||
//
|
||||
// private static enum QuotingCharacterDisposition { NONE, OPEN, CLOSE }
|
||||
|
||||
private static class TrimOperands {
|
||||
private final String trimSpec;
|
||||
private final String trimChar;
|
||||
private final String from;
|
||||
private final String trimSource;
|
||||
|
||||
private TrimOperands(List<String> operands) {
|
||||
if ( operands.size() == 1 ) {
|
||||
trimSpec = null;
|
||||
trimChar = null;
|
||||
from = null;
|
||||
trimSource = operands.get(0);
|
||||
}
|
||||
else if ( operands.size() == 4 ) {
|
||||
trimSpec = operands.get(0);
|
||||
trimChar = operands.get(1);
|
||||
from = operands.get(2);
|
||||
trimSource = operands.get(3);
|
||||
}
|
||||
else {
|
||||
if ( operands.size() < 1 || operands.size() > 4 ) {
|
||||
throw new HibernateException( "Unexpected number of trim function operands : " + operands.size() );
|
||||
}
|
||||
|
||||
// trim-source will always be the last operand
|
||||
trimSource = operands.get( operands.size() - 1 );
|
||||
|
||||
// ANSI SQL says that more than one operand means that the FROM is required
|
||||
if ( ! "from".equals( operands.get( operands.size() - 2 ) ) ) {
|
||||
throw new HibernateException( "Expecting FROM, found : " + operands.get( operands.size() - 2 ) );
|
||||
}
|
||||
from = operands.get( operands.size() - 2 );
|
||||
|
||||
// trim-spec, if there is one will always be the first operand
|
||||
if ( "leading".equalsIgnoreCase( operands.get(0) )
|
||||
|| "trailing".equalsIgnoreCase( operands.get(0) )
|
||||
|| "both".equalsIgnoreCase( operands.get(0) ) ) {
|
||||
trimSpec = operands.get(0);
|
||||
trimChar = null;
|
||||
}
|
||||
else {
|
||||
trimSpec = null;
|
||||
if ( operands.size() - 2 == 0 ) {
|
||||
trimChar = null;
|
||||
}
|
||||
else {
|
||||
trimChar = operands.get( 0 );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String extractUntil(StringTokenizer tokens, String delimiter) {
|
||||
StringBuilder valueBuilder = new StringBuilder();
|
||||
String token = tokens.nextToken();
|
||||
while ( ! delimiter.equalsIgnoreCase( token ) ) {
|
||||
valueBuilder.append( token );
|
||||
token = tokens.nextToken();
|
||||
}
|
||||
return valueBuilder.toString().trim();
|
||||
}
|
||||
|
||||
public static class NoOpColumnMapper implements ColumnMapper {
|
||||
public static final NoOpColumnMapper INSTANCE = new NoOpColumnMapper();
|
||||
public String[] map(String reference) {
|
||||
|
@ -254,8 +637,8 @@ public final class Template {
|
|||
* @param functionRegistry The SQL function registry
|
||||
*
|
||||
* @return The rendered <tt>ORDER BY</tt> template.
|
||||
*
|
||||
* @see #renderOrderByStringTemplate(String,ColumnMapper,SessionFactoryImplementor,Dialect,SQLFunctionRegistry)
|
||||
*
|
||||
* @deprecated Use {@link #renderOrderByStringTemplate(String,ColumnMapper,SessionFactoryImplementor,Dialect,SQLFunctionRegistry)} instead
|
||||
*/
|
||||
public static String renderOrderByStringTemplate(
|
||||
String orderByFragment,
|
||||
|
@ -348,5 +731,4 @@ public final class Template {
|
|||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -85,6 +85,43 @@ public class TemplateTest extends TestCase {
|
|||
|
||||
private static final SQLFunctionRegistry FUNCTION_REGISTRY = new SQLFunctionRegistry( DIALECT, Collections.EMPTY_MAP );
|
||||
|
||||
public void testSqlExtractFunction() {
|
||||
String fragment = "extract( year from col )";
|
||||
String template = Template.renderWhereStringTemplate( fragment, Template.TEMPLATE, DIALECT, FUNCTION_REGISTRY );
|
||||
|
||||
assertEquals( "extract(year from " + Template.TEMPLATE + ".col)", template );
|
||||
}
|
||||
|
||||
public void testSqlTrimFunction() {
|
||||
String fragment = "trim( col )";
|
||||
String template = Template.renderWhereStringTemplate( fragment, Template.TEMPLATE, DIALECT, FUNCTION_REGISTRY );
|
||||
assertEquals( "trim(" + Template.TEMPLATE + ".col)", template );
|
||||
|
||||
fragment = "trim( from col )";
|
||||
template = Template.renderWhereStringTemplate( fragment, Template.TEMPLATE, DIALECT, FUNCTION_REGISTRY );
|
||||
assertEquals( "trim(from " + Template.TEMPLATE + ".col)", template );
|
||||
|
||||
fragment = "trim( both from col )";
|
||||
template = Template.renderWhereStringTemplate( fragment, Template.TEMPLATE, DIALECT, FUNCTION_REGISTRY );
|
||||
assertEquals( "trim(both from " + Template.TEMPLATE + ".col)", template );
|
||||
|
||||
fragment = "trim( leading from col )";
|
||||
template = Template.renderWhereStringTemplate( fragment, Template.TEMPLATE, DIALECT, FUNCTION_REGISTRY );
|
||||
assertEquals( "trim(leading from " + Template.TEMPLATE + ".col)", template );
|
||||
|
||||
fragment = "trim( TRAILING from col )";
|
||||
template = Template.renderWhereStringTemplate( fragment, Template.TEMPLATE, DIALECT, FUNCTION_REGISTRY );
|
||||
assertEquals( "trim(TRAILING from " + Template.TEMPLATE + ".col)", template );
|
||||
|
||||
fragment = "trim( 'b' from col )";
|
||||
template = Template.renderWhereStringTemplate( fragment, Template.TEMPLATE, DIALECT, FUNCTION_REGISTRY );
|
||||
assertEquals( "trim('b' from " + Template.TEMPLATE + ".col)", template );
|
||||
|
||||
fragment = "trim( both 'b' from col )";
|
||||
template = Template.renderWhereStringTemplate( fragment, Template.TEMPLATE, DIALECT, FUNCTION_REGISTRY );
|
||||
assertEquals( "trim(both 'b' from " + Template.TEMPLATE + ".col)", template );
|
||||
}
|
||||
|
||||
public void testSQLReferences() {
|
||||
String fragment = "sql asc, sql desc";
|
||||
String template = doStandardRendering( fragment );
|
||||
|
|
Loading…
Reference in New Issue