org.hibernate.sql.Template Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of beangle-hibernate-core Show documentation
Show all versions of beangle-hibernate-core Show documentation
Hibernate Orm Core Shade,Support Scala Collection
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or .
*/
package org.hibernate.sql;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.StringTokenizer;
import org.hibernate.HibernateException;
import org.hibernate.dialect.Dialect;
import org.hibernate.query.sqm.function.SqmFunctionDescriptor;
import org.hibernate.query.sqm.function.SqmFunctionRegistry;
import org.hibernate.type.spi.TypeConfiguration;
import static org.hibernate.internal.util.StringHelper.WHITESPACE;
/**
* Parses SQL fragments specified in mapping documents.
*
* @author Gavin King
*/
public final class Template {
private static final Set KEYWORDS = new HashSet<>();
private static final Set BEFORE_TABLE_KEYWORDS = new HashSet<>();
private static final Set FUNCTION_KEYWORDS = new HashSet<>();
private static final Set LITERAL_PREFIXES = new HashSet<>();
public static final String PUNCTUATION = "=> operands = new ArrayList<>();
StringBuilder builder = new StringBuilder();
boolean hasMoreOperands = true;
String operandToken = tokens.nextToken();
switch ( operandToken.toLowerCase( Locale.ROOT ) ) {
case "leading":
case "trailing":
case "both":
operands.add( operandToken );
if ( hasMoreOperands = tokens.hasMoreTokens() ) {
operandToken = tokens.nextToken();
}
break;
}
boolean quotedOperand = false;
int parenthesis = 0;
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 ( parenthesis != 0 ) {
builder.append( operandToken );
switch ( operandToken ) {
case "(":
parenthesis++;
break;
case ")":
parenthesis--;
break;
}
}
else {
builder.append( operandToken );
switch ( operandToken.toLowerCase( Locale.ROOT ) ) {
case "(":
parenthesis++;
break;
case ")":
parenthesis--;
break;
case "from":
if ( builder.length() != 0 ) {
operands.add( builder.substring( 0, builder.length() - 4 ) );
builder.setLength( 0 );
operands.add( operandToken );
}
break;
}
}
operandToken = tokens.nextToken();
hasMoreOperands = tokens.hasMoreTokens() && ( parenthesis != 0 || ! ")".equals( operandToken ) );
}
if ( builder.length() != 0 ) {
operands.add( builder.toString() );
}
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, typeConfiguration, 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 is specified
result.append( "from " );
}
result.append( renderWhereStringTemplate( trimOperands.trimSource, placeholder, dialect, typeConfiguration, 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;
}
result.append(token);
}
else if ( isNamedParameter(token) ) {
result.append(token);
}
else if ( isIdentifier(token)
&& !isFunctionOrKeyword(lcToken, nextToken, dialect, typeConfiguration, functionRegistry) ) {
result.append(placeholder)
.append('.')
.append( dialect.quote(token) );
}
else {
if ( BEFORE_TABLE_KEYWORDS.contains(lcToken) ) {
beforeTable = true;
inFromClause = true;
}
else if ( inFromClause && ",".equals(lcToken) ) {
beforeTable = true;
}
if ( isBoolean( token ) ) {
token = dialect.toBooleanValueString( Boolean.parseBoolean( token ) );
}
result.append(token);
}
//Yuck:
if ( inFromClause
&& KEYWORDS.contains( lcToken ) //"as" is not in KEYWORDS
&& !BEFORE_TABLE_KEYWORDS.contains( lcToken ) ) {
inFromClause = false;
}
}
return result.toString();
}
private enum TimeZoneTokens {
NONE,
WITH,
TIME,
ZONE;
static TimeZoneTokens getPossibleNextTokens(String lctoken) {
switch ( lctoken ) {
case "time":
case "timestamp":
return WITH;
default:
return NONE;
}
}
public TimeZoneTokens nextToken() {
if ( this == WITH ) {
return TIME;
}
else if ( this == TIME ) {
return ZONE;
}
else {
return NONE;
}
}
public boolean isToken(String token) {
return this != NONE && name().equalsIgnoreCase( token );
}
}
public static List collectColumnNames(
String sql,
Dialect dialect,
TypeConfiguration typeConfiguration,
SqmFunctionRegistry functionRegistry) {
return collectColumnNames( renderWhereStringTemplate( sql, dialect, typeConfiguration, functionRegistry ) );
}
public static List collectColumnNames(String template) {
final List names = new ArrayList<>();
int begin = 0;
int match;
while ( ( match = template.indexOf(TEMPLATE, begin) ) >= 0 ) {
int start = match + TEMPLATE.length() + 1;
for ( int loc = start;; loc++ ) {
if ( loc == template.length() - 1 ) {
names.add( template.substring( start ) );
begin = template.length();
break;
}
else {
char ch = template.charAt( loc );
if ( PUNCTUATION.indexOf(ch) >= 0 || WHITESPACE.indexOf(ch) >= 0 ) {
names.add( template.substring( start, loc ) );
begin = loc;
break;
}
}
}
}
return names;
}
// /**
// * 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 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( "=> operands = new ArrayList();
// 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 operands) {
final int size = operands.size();
if ( size == 1 ) {
trimSpec = null;
trimChar = null;
from = null;
trimSource = operands.get(0);
}
else if ( size == 4 ) {
trimSpec = operands.get(0);
trimChar = operands.get(1);
from = operands.get(2);
trimSource = operands.get(3);
}
else {
if ( size < 1 || size > 4 ) {
throw new HibernateException( "Unexpected number of trim function operands : " + size );
}
// trim-source will always be the last operand
trimSource = operands.get( size - 1 );
// ANSI SQL says that more than one operand means that the FROM is required
if ( ! "from".equals( operands.get( size - 2 ) ) ) {
throw new HibernateException( "Expecting FROM, found : " + operands.get( size - 2 ) );
}
from = operands.get( 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 ( 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();
}
private static boolean isNamedParameter(String token) {
return token.startsWith( ":" );
}
private static boolean isFunctionOrKeyword(
String lcToken,
String nextToken,
Dialect dialect,
TypeConfiguration typeConfiguration,
SqmFunctionRegistry functionRegistry) {
return "(".equals( nextToken ) ||
KEYWORDS.contains( lcToken ) ||
isType( lcToken, typeConfiguration ) ||
isFunction( lcToken, nextToken, functionRegistry ) ||
dialect.getKeywords().contains( lcToken ) ||
FUNCTION_KEYWORDS.contains( lcToken );
}
private static boolean isType(String lcToken, TypeConfiguration typeConfiguration) {
return typeConfiguration.getDdlTypeRegistry().isTypeNameRegistered( lcToken );
}
private static boolean isFunction(String lcToken, String nextToken, SqmFunctionRegistry functionRegistry) {
// checking for "(" is currently redundant because it is checked before getting here;
// doing the check anyhow, in case that earlier check goes away;
if ( "(".equals( nextToken ) ) {
return true;
}
final SqmFunctionDescriptor function = functionRegistry.findFunctionDescriptor( lcToken );
return function != null;
}
private static boolean isIdentifier(String token) {
if ( isBoolean( token ) ) {
return false;
}
return token.charAt( 0 ) == '`' || ( //allow any identifier quoted with backtick
Character.isLetter( token.charAt( 0 ) ) && //only recognizes identifiers beginning with a letter
token.indexOf( '.' ) < 0
);
}
private static boolean isBoolean(String token) {
return "true".equals( token ) || "false".equals( token );
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy