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

org.hibernate.sql.Template Maven / Gradle / Ivy

There is a newer version: 6.6.2.Final
Show newest version
/*
 * 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