HHH-14033 - SQL script parsing problem with multi-line comments
- Better handling of multi-line comments - Restructured some internal classes to consolidate packages - Added "system"-style SchemaToolingLogging
This commit is contained in:
parent
e1b1207d30
commit
b658e903d7
|
@ -203,6 +203,9 @@ xjc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
generateGrammarSource {
|
||||||
|
arguments += "-traceParser"
|
||||||
|
}
|
||||||
|
|
||||||
//sourceSets.main.sourceGeneratorsTask.dependsOn xjc
|
//sourceSets.main.sourceGeneratorsTask.dependsOn xjc
|
||||||
//sourceSets.main.sourceGeneratorsTask.dependsOn generateGrammarSource
|
//sourceSets.main.sourceGeneratorsTask.dependsOn generateGrammarSource
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
header
|
header
|
||||||
{
|
{
|
||||||
package org.hibernate.hql.internal.antlr;
|
package org.hibernate.tool.schema.ast;
|
||||||
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -14,139 +14,136 @@ import org.hibernate.hql.internal.ast.ErrorReporter;
|
||||||
*
|
*
|
||||||
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
|
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
|
||||||
*/
|
*/
|
||||||
class SqlStatementParser extends Parser;
|
class GeneratedSqlScriptParser extends Parser;
|
||||||
|
|
||||||
options {
|
options {
|
||||||
buildAST = false;
|
buildAST = false;
|
||||||
|
k=3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
// Semantic actions
|
||||||
|
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
{
|
{
|
||||||
private ErrorHandler errorHandler = new ErrorHandler();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void reportError(RecognitionException e) {
|
|
||||||
errorHandler.reportError( e );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void reportError(String s) {
|
|
||||||
errorHandler.reportError( s );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void reportWarning(String s) {
|
|
||||||
errorHandler.reportWarning( s );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void throwExceptionIfErrorOccurred() {
|
|
||||||
if ( errorHandler.hasErrors() ) {
|
|
||||||
String errorMessage = errorHandler.getErrorMessage();
|
|
||||||
if(errorMessage.contains("expecting STMT_END")) {
|
|
||||||
throw new StatementParserException( "Import script Sql statements must terminate with a ';' char" );
|
|
||||||
}
|
|
||||||
throw new StatementParserException( errorHandler.getErrorMessage() );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** List of all SQL statements. */
|
|
||||||
private List<String> statementList = new LinkedList<String>();
|
|
||||||
|
|
||||||
/** Currently processing SQL statement. */
|
|
||||||
private StringBuilder current = new StringBuilder();
|
|
||||||
|
|
||||||
protected void out(String stmt) {
|
protected void out(String stmt) {
|
||||||
current.append( stmt );
|
// by default, nothing to do
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void out(Token token) {
|
protected void out(Token token) {
|
||||||
out( token.getText() );
|
// by default, nothing to do
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<String> getStatementList() {
|
protected void statementStarted() {
|
||||||
return statementList;
|
// by default, nothing to do
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void statementEnd() {
|
protected void statementEnded() {
|
||||||
statementList.add( current.toString().trim() );
|
// by default, nothing to do
|
||||||
current = new StringBuilder();
|
|
||||||
}
|
|
||||||
|
|
||||||
public class StatementParserException extends RuntimeException {
|
|
||||||
public StatementParserException(String message) {
|
|
||||||
super( message );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ErrorHandler implements ErrorReporter {
|
|
||||||
private List<String> errorList = new LinkedList<String>();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void reportError(RecognitionException e) {
|
|
||||||
reportError( e.toString() );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void reportError(String s) {
|
|
||||||
errorList.add( s );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void reportWarning(String s) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasErrors() {
|
|
||||||
return !errorList.isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getErrorMessage() {
|
|
||||||
StringBuilder buf = new StringBuilder();
|
|
||||||
for ( Iterator iterator = errorList.iterator(); iterator.hasNext(); ) {
|
|
||||||
buf.append( (String) iterator.next() );
|
|
||||||
if ( iterator.hasNext() ) {
|
|
||||||
buf.append( "\n" );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return buf.toString();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
// Parser rules
|
||||||
|
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
script
|
script
|
||||||
: ( statement )*
|
: (statement)+ EOF
|
||||||
;
|
;
|
||||||
|
|
||||||
statement
|
statement
|
||||||
: ( s:NOT_STMT_END { out( s ); } | q:QUOTED_STRING { out( q ); } )* STMT_END { statementEnd(); }
|
: { statementStarted(); } (statementPart)* DELIMITER { statementEnded(); }
|
||||||
;
|
;
|
||||||
|
|
||||||
class SqlStatementLexer extends Lexer;
|
statementPart
|
||||||
|
: quotedString
|
||||||
|
| nonSkippedChar
|
||||||
|
;
|
||||||
|
|
||||||
|
quotedString
|
||||||
|
: q:QUOTED_TEXT {
|
||||||
|
out( q );
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
|
nonSkippedChar
|
||||||
|
: c:CHAR {
|
||||||
|
out( c );
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
|
|
||||||
|
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
// Lexer rules
|
||||||
|
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
class SqlScriptLexer extends Lexer;
|
||||||
|
|
||||||
options {
|
options {
|
||||||
k = 2;
|
k = 2;
|
||||||
charVocabulary = '\u0000'..'\uFFFE';
|
charVocabulary = '\u0000'..'\uFFFE';
|
||||||
}
|
}
|
||||||
|
|
||||||
STMT_END
|
DELIMITER : ';' ;
|
||||||
: ';' ( '\t' | ' ' | '\r' | '\n' )*
|
|
||||||
;
|
|
||||||
|
|
||||||
NOT_STMT_END
|
// NOTE : The `ESCqs` part in the match is meant to an escaped quote (two single-quotes) and
|
||||||
: ~( ';' )
|
// add it to the recognized text. The `(ESCqs) => ESCqs` syntax is a syntactic predicate.
|
||||||
;
|
// We basically give precedence to the two single-quotes as a group as opposed to the first of them
|
||||||
|
// matching the terminal single-quote. Both single-quotes end up in the quoted text
|
||||||
QUOTED_STRING
|
QUOTED_TEXT
|
||||||
: '\'' ( (ESCqs)=> ESCqs | ~'\'' )* '\''
|
: '`' ( ~('`') )* '`'
|
||||||
|
| '\'' ( (ESCqs) => ESCqs | ~('\'') )* '\''
|
||||||
|
// : '\'' ( ~('\'') )* '\''
|
||||||
;
|
;
|
||||||
|
|
||||||
protected
|
protected
|
||||||
ESCqs
|
ESCqs : '\'' '\'' ;
|
||||||
: '\'' '\''
|
|
||||||
|
CHAR
|
||||||
|
: ( ' ' | '\t' ) => ( ' ' | '\t' )
|
||||||
|
| ~( ';' | '\n' | '\r' )
|
||||||
|
;
|
||||||
|
|
||||||
|
NEWLINE
|
||||||
|
: ( '\r' | '\n' | '\r''\n' ) {
|
||||||
|
newline();
|
||||||
|
// skip the entire match from the lexer stream
|
||||||
|
$setType( Token.SKIP );
|
||||||
|
}
|
||||||
;
|
;
|
||||||
|
|
||||||
LINE_COMMENT
|
LINE_COMMENT
|
||||||
: ( "//" | "--" ) ( ~('\n'|'\r') )* { $setType(Token.SKIP); }
|
// match `//` or `--` followed by anything other than \n or \r until NEWLINE
|
||||||
|
: ("//" | "--") ( ~('\n'|'\r') )* {
|
||||||
|
// skip the entire match from the lexer stream
|
||||||
|
$setType( Token.SKIP );
|
||||||
|
}
|
||||||
;
|
;
|
||||||
|
|
||||||
MULTILINE_COMMENT
|
BLOCK_COMMENT
|
||||||
: "/*" ( options {greedy=false;} : . )* "*/" { $setType(Token.SKIP); }
|
: "/*"
|
||||||
;
|
( /* '\r' '\n' can be matched in one alternative or by matching
|
||||||
|
'\r' in one iteration and '\n' in another. I am trying to
|
||||||
|
handle any flavor of newline that comes in, but the language
|
||||||
|
that allows both "\r\n" and "\r" and "\n" to all be valid
|
||||||
|
newline is ambiguous. Consequently, the resulting grammar
|
||||||
|
must be ambiguous. I'm shutting this warning off.
|
||||||
|
*/
|
||||||
|
options {
|
||||||
|
generateAmbigWarnings=false;
|
||||||
|
}
|
||||||
|
: { LA(2)!='/' }? '*'
|
||||||
|
| '\r' '\n' {newline();}
|
||||||
|
| '\r' {newline();}
|
||||||
|
| '\n' {newline();}
|
||||||
|
| ~('*'|'\n'|'\r')
|
||||||
|
)*
|
||||||
|
"*/"
|
||||||
|
{$setType(Token.SKIP);}
|
||||||
|
;
|
|
@ -9,28 +9,20 @@ package org.hibernate.tool.hbm2ddl;
|
||||||
import java.io.Reader;
|
import java.io.Reader;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.hibernate.hql.internal.antlr.SqlStatementLexer;
|
import org.hibernate.tool.schema.ast.SqlScriptParser;
|
||||||
import org.hibernate.hql.internal.antlr.SqlStatementParser;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class responsible for extracting SQL statements from import script. Supports instructions/comments and quoted
|
* Class responsible for extracting SQL statements from import script. Supports instructions/comments and quoted
|
||||||
* strings spread over multiple lines. Each statement must end with semicolon.
|
* strings spread over multiple lines. Each statement must end with semicolon.
|
||||||
*
|
*
|
||||||
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
|
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
|
||||||
|
* @author Steve Ebersole
|
||||||
*/
|
*/
|
||||||
public class MultipleLinesSqlCommandExtractor implements ImportSqlCommandExtractor {
|
public class MultipleLinesSqlCommandExtractor implements ImportSqlCommandExtractor {
|
||||||
@Override
|
@Override
|
||||||
public String[] extractCommands(Reader reader) {
|
public String[] extractCommands(Reader reader) {
|
||||||
final SqlStatementLexer lexer = new SqlStatementLexer( reader );
|
final List<String> commands = SqlScriptParser.extractCommands( reader );
|
||||||
final SqlStatementParser parser = new SqlStatementParser( lexer );
|
|
||||||
try {
|
return commands.toArray( new String[0] );
|
||||||
parser.script(); // Parse script.
|
|
||||||
parser.throwExceptionIfErrorOccurred();
|
|
||||||
}
|
|
||||||
catch ( Exception e ) {
|
|
||||||
throw new ImportScriptException( "Error during import script parsing.", e );
|
|
||||||
}
|
|
||||||
List<String> statementList = parser.getStatementList();
|
|
||||||
return statementList.toArray( new String[statementList.size()] );
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* 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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||||
|
*/
|
||||||
|
package org.hibernate.tool.schema;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
public class SchemaToolingLogging {
|
||||||
|
public static final String LOGGER_NAME = "org.hibernate.orm.tooling.schema";
|
||||||
|
public static final Logger LOGGER = Logger.getLogger( LOGGER_NAME );
|
||||||
|
|
||||||
|
public static final String AST_LOGGER_NAME = LOGGER_NAME + ".AST";
|
||||||
|
public static final Logger AST_LOGGER = Logger.getLogger( AST_LOGGER_NAME );
|
||||||
|
|
||||||
|
public static final boolean TRACE_ENABLED = LOGGER.isTraceEnabled();
|
||||||
|
public static final boolean DEBUG_ENABLED = LOGGER.isDebugEnabled();
|
||||||
|
|
||||||
|
public static final boolean AST_TRACE_ENABLED = AST_LOGGER.isTraceEnabled();
|
||||||
|
}
|
|
@ -0,0 +1,178 @@
|
||||||
|
/*
|
||||||
|
* 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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||||
|
*/
|
||||||
|
package org.hibernate.tool.schema.ast;
|
||||||
|
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import org.hibernate.hql.internal.ast.util.ASTUtil;
|
||||||
|
import org.hibernate.internal.util.StringHelper;
|
||||||
|
import org.hibernate.tool.schema.SchemaToolingLogging;
|
||||||
|
|
||||||
|
import antlr.RecognitionException;
|
||||||
|
import antlr.Token;
|
||||||
|
import antlr.TokenStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
public class SqlScriptParser extends GeneratedSqlScriptParser {
|
||||||
|
private static String[] TOKEN_NAMES = ASTUtil.generateTokenNameCache( GeneratedSqlScriptParserTokenTypes .class );
|
||||||
|
|
||||||
|
public static List<String> extractCommands(Reader reader) {
|
||||||
|
final List<String> statementList = new ArrayList<>();
|
||||||
|
|
||||||
|
final SqlScriptLexer lexer = new SqlScriptLexer( reader );
|
||||||
|
final SqlScriptParser parser = new SqlScriptParser( statementList::add, lexer );
|
||||||
|
|
||||||
|
parser.parseScript();
|
||||||
|
|
||||||
|
return statementList;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final List<String> errorList = new LinkedList<>();
|
||||||
|
private final Consumer<String> commandConsumer;
|
||||||
|
|
||||||
|
private StringBuilder currentStatementBuffer;
|
||||||
|
|
||||||
|
public SqlScriptParser(Consumer<String> commandConsumer, TokenStream lexer) {
|
||||||
|
super( lexer );
|
||||||
|
this.commandConsumer = commandConsumer;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseScript() {
|
||||||
|
try {
|
||||||
|
// trigger the top-level grammar rule
|
||||||
|
script();
|
||||||
|
}
|
||||||
|
catch ( Exception e ) {
|
||||||
|
throw new SqlScriptParserException( "Error during import script parsing.", e );
|
||||||
|
}
|
||||||
|
|
||||||
|
failIfAnyErrors();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Semantic action outputting text to the current statement buffer
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void out(String text) {
|
||||||
|
SchemaToolingLogging.AST_LOGGER.tracef( "Buffering text : %s", text );
|
||||||
|
currentStatementBuffer.append( text );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Semantic action outputting a token to the current statement buffer
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void out(Token token) {
|
||||||
|
SchemaToolingLogging.AST_LOGGER.tracef( "out( %s(%s) )", TOKEN_NAMES[ token.getType() ], token.getText() );
|
||||||
|
currentStatementBuffer.append( token.getText() );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void statementStarted() {
|
||||||
|
if ( currentStatementBuffer != null ) {
|
||||||
|
SchemaToolingLogging.LOGGER.debugf( "`#currentStatementBuffer` was not null at `#statementStart`" );
|
||||||
|
}
|
||||||
|
currentStatementBuffer = new StringBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Semantic action signifying the end of a statement (delimiter recognized)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void statementEnded() {
|
||||||
|
final String statementText = currentStatementBuffer.toString().trim();
|
||||||
|
SchemaToolingLogging.LOGGER.debugf( "Import statement : %s", statementText );
|
||||||
|
commandConsumer.accept( statementText );
|
||||||
|
|
||||||
|
currentStatementBuffer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void failIfAnyErrors() {
|
||||||
|
if ( errorList.isEmpty() ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new SqlScriptParserException( buildErrorMessage() );
|
||||||
|
}
|
||||||
|
|
||||||
|
public String buildErrorMessage() {
|
||||||
|
final StringBuilder buf = new StringBuilder();
|
||||||
|
for ( int i = 0; i < errorList.size(); i++ ) {
|
||||||
|
buf.append( errorList.get( i ) );
|
||||||
|
if ( i < errorList.size() - 1 ) {
|
||||||
|
buf.append( System.lineSeparator() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buf.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reportError(RecognitionException e) {
|
||||||
|
final String textBase = "RecognitionException(@" + e.getLine() + ":" + e.getColumn() + ")";
|
||||||
|
|
||||||
|
String message = e.toString();
|
||||||
|
if ( message.contains( "expecting DELIMITER" ) ) {
|
||||||
|
message = "Import script Sql statements must terminate with a ';' char";
|
||||||
|
}
|
||||||
|
|
||||||
|
errorList.add( textBase + " : " + message );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reportError(String message) {
|
||||||
|
if ( message.contains( "expecting DELIMITER" ) ) {
|
||||||
|
message = "Import script Sql statements must terminate with a ';' char";
|
||||||
|
}
|
||||||
|
|
||||||
|
errorList.add( message );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reportWarning(String message) {
|
||||||
|
SchemaToolingLogging.LOGGER.debugf( "SqlScriptParser recognition warning : " + message );
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle trace logging ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
private final int depthIndent = 2;
|
||||||
|
private int traceDepth;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void traceIn(String ruleName) {
|
||||||
|
if ( ! SchemaToolingLogging.AST_TRACE_ENABLED ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( inputState.guessing > 0 ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String prefix = StringHelper.repeat( '-', ( traceDepth++ * depthIndent ) );
|
||||||
|
SchemaToolingLogging.AST_LOGGER.tracef( "%s-> %s", prefix, ruleName );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void traceOut(String ruleName) {
|
||||||
|
if ( ! SchemaToolingLogging.AST_TRACE_ENABLED ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( inputState.guessing > 0 ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String prefix = StringHelper.repeat( '-', ( --traceDepth * depthIndent ) );
|
||||||
|
SchemaToolingLogging.AST_LOGGER.tracef( "<-%s %s", prefix, ruleName );
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
/*
|
||||||
|
* 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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||||
|
*/
|
||||||
|
package org.hibernate.tool.schema.ast;
|
||||||
|
|
||||||
|
import org.hibernate.HibernateException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
public class SqlScriptParserException extends HibernateException {
|
||||||
|
public SqlScriptParserException(String message) {
|
||||||
|
super( message );
|
||||||
|
}
|
||||||
|
|
||||||
|
public SqlScriptParserException(String message, Throwable cause) {
|
||||||
|
super( message, cause );
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,38 +0,0 @@
|
||||||
/*
|
|
||||||
* 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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
|
||||||
*/
|
|
||||||
package org.hibernate.test.fileimport;
|
|
||||||
|
|
||||||
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
|
|
||||||
import org.hibernate.cfg.Configuration;
|
|
||||||
import org.hibernate.cfg.Environment;
|
|
||||||
import org.hibernate.dialect.H2Dialect;
|
|
||||||
import org.hibernate.tool.hbm2ddl.ImportSqlCommandExtractor;
|
|
||||||
import org.hibernate.tool.hbm2ddl.MultipleLinesSqlCommandExtractor;
|
|
||||||
|
|
||||||
import org.hibernate.testing.RequiresDialect;
|
|
||||||
import org.hibernate.testing.TestForIssue;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
|
|
||||||
*/
|
|
||||||
@TestForIssue( jiraKey = "HHH-2403" )
|
|
||||||
@RequiresDialect(value = H2Dialect.class,
|
|
||||||
jiraKey = "HHH-6286",
|
|
||||||
comment = "Only running the tests against H2, because the sql statements in the import file are not generic. " +
|
|
||||||
"This test should actually not test directly against the db")
|
|
||||||
public class CommandExtractorServiceTest extends MultiLineImportFileTest {
|
|
||||||
@Override
|
|
||||||
public void configure(Configuration cfg) {
|
|
||||||
cfg.setProperty( Environment.HBM2DDL_IMPORT_FILES, "/org/hibernate/test/fileimport/multi-line-statements.sql" );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void prepareBasicRegistryBuilder(StandardServiceRegistryBuilder serviceRegistryBuilder) {
|
|
||||||
super.prepareBasicRegistryBuilder( serviceRegistryBuilder );
|
|
||||||
serviceRegistryBuilder.addService( ImportSqlCommandExtractor.class, new MultipleLinesSqlCommandExtractor() );
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
/*
|
||||||
|
* 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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||||
|
*/
|
||||||
|
package org.hibernate.test.fileimport;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
|
||||||
|
import org.hibernate.tool.hbm2ddl.MultipleLinesSqlCommandExtractor;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.hamcrest.CoreMatchers.containsString;
|
||||||
|
import static org.hamcrest.CoreMatchers.is;
|
||||||
|
import static org.hamcrest.CoreMatchers.not;
|
||||||
|
import static org.hamcrest.CoreMatchers.notNullValue;
|
||||||
|
import static org.hamcrest.CoreMatchers.startsWith;
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
public class MultiLineImportExtractorTest {
|
||||||
|
public static final String IMPORT_FILE = "org/hibernate/test/fileimport/multi-line-statements.sql";
|
||||||
|
|
||||||
|
private final MultipleLinesSqlCommandExtractor extractor = new MultipleLinesSqlCommandExtractor();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExtraction() throws IOException {
|
||||||
|
final ClassLoader classLoader = ClassLoader.getSystemClassLoader();
|
||||||
|
|
||||||
|
try ( final InputStream stream = classLoader.getResourceAsStream( IMPORT_FILE ) ) {
|
||||||
|
assertThat( stream, notNullValue() );
|
||||||
|
try ( final InputStreamReader reader = new InputStreamReader( stream ) ) {
|
||||||
|
final String[] commands = extractor.extractCommands( reader );
|
||||||
|
assertThat( commands, notNullValue() );
|
||||||
|
assertThat( commands.length, is( 6 ) );
|
||||||
|
|
||||||
|
assertThat( commands[0], startsWith( "CREATE TABLE test_data" ) );
|
||||||
|
|
||||||
|
assertThat( commands[1], is( "INSERT INTO test_data VALUES (1, 'sample')" ) );
|
||||||
|
|
||||||
|
assertThat( commands[2], is( "DELETE FROM test_data" ) );
|
||||||
|
|
||||||
|
assertThat( commands[3], startsWith( "INSERT INTO test_data VALUES (2," ) );
|
||||||
|
assertThat( commands[3], containsString( "-- line 2" ) );
|
||||||
|
|
||||||
|
assertThat( commands[4], startsWith( "INSERT INTO test_data VALUES (3" ) );
|
||||||
|
assertThat( commands[4], not( containsString( "third record" ) ) );
|
||||||
|
|
||||||
|
assertThat( commands[5], containsString( "INSERT INTO test_data (id, text)" ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,46 +6,22 @@
|
||||||
*/
|
*/
|
||||||
package org.hibernate.test.fileimport;
|
package org.hibernate.test.fileimport;
|
||||||
|
|
||||||
import java.util.EnumSet;
|
import java.io.IOException;
|
||||||
import java.util.Map;
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
|
||||||
import org.hibernate.boot.Metadata;
|
|
||||||
import org.hibernate.boot.MetadataSources;
|
|
||||||
import org.hibernate.boot.registry.StandardServiceRegistry;
|
|
||||||
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
|
|
||||||
import org.hibernate.cfg.AvailableSettings;
|
|
||||||
import org.hibernate.cfg.Environment;
|
|
||||||
import org.hibernate.dialect.H2Dialect;
|
import org.hibernate.dialect.H2Dialect;
|
||||||
import org.hibernate.engine.config.spi.ConfigurationService;
|
|
||||||
import org.hibernate.hql.internal.antlr.SqlStatementParser;
|
|
||||||
import org.hibernate.resource.transaction.backend.jta.internal.JtaTransactionCoordinatorBuilderImpl;
|
|
||||||
import org.hibernate.resource.transaction.spi.TransactionCoordinatorBuilder;
|
|
||||||
import org.hibernate.tool.hbm2ddl.ImportScriptException;
|
|
||||||
import org.hibernate.tool.hbm2ddl.MultipleLinesSqlCommandExtractor;
|
import org.hibernate.tool.hbm2ddl.MultipleLinesSqlCommandExtractor;
|
||||||
import org.hibernate.tool.schema.SourceType;
|
import org.hibernate.tool.schema.ast.SqlScriptParserException;
|
||||||
import org.hibernate.tool.schema.TargetType;
|
|
||||||
import org.hibernate.tool.schema.internal.ExceptionHandlerLoggedImpl;
|
|
||||||
import org.hibernate.tool.schema.internal.SchemaCreatorImpl;
|
|
||||||
import org.hibernate.tool.schema.spi.ExceptionHandler;
|
|
||||||
import org.hibernate.tool.schema.spi.ExecutionOptions;
|
|
||||||
import org.hibernate.tool.schema.spi.SchemaCreator;
|
|
||||||
import org.hibernate.tool.schema.spi.ScriptSourceInput;
|
|
||||||
import org.hibernate.tool.schema.spi.ScriptTargetOutput;
|
|
||||||
import org.hibernate.tool.schema.spi.SourceDescriptor;
|
|
||||||
import org.hibernate.tool.schema.spi.TargetDescriptor;
|
|
||||||
|
|
||||||
import org.hibernate.testing.RequiresDialect;
|
import org.hibernate.testing.RequiresDialect;
|
||||||
import org.hibernate.testing.TestForIssue;
|
import org.hibernate.testing.TestForIssue;
|
||||||
import org.hibernate.testing.jta.TestingJtaBootstrap;
|
|
||||||
import org.hibernate.testing.junit4.BaseUnitTestCase;
|
import org.hibernate.testing.junit4.BaseUnitTestCase;
|
||||||
import org.hibernate.test.schemaupdate.CommentGenerationTest;
|
|
||||||
import org.junit.After;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import static org.hamcrest.CoreMatchers.instanceOf;
|
import static org.hamcrest.CoreMatchers.endsWith;
|
||||||
|
import static org.hamcrest.CoreMatchers.notNullValue;
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.hamcrest.core.Is.is;
|
|
||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -56,120 +32,27 @@ import static org.junit.Assert.fail;
|
||||||
jiraKey = "HHH-6286",
|
jiraKey = "HHH-6286",
|
||||||
comment = "Only running the tests against H2, because the sql statements in the import file are not generic. " +
|
comment = "Only running the tests against H2, because the sql statements in the import file are not generic. " +
|
||||||
"This test should actually not test directly against the db")
|
"This test should actually not test directly against the db")
|
||||||
public class StatementsWithoutTerminalCharsImportFileTest extends BaseUnitTestCase implements ExecutionOptions {
|
public class StatementsWithoutTerminalCharsImportFileTest extends BaseUnitTestCase {
|
||||||
|
|
||||||
|
private static final String IMPORT_FILE = "org/hibernate/test/fileimport/statements-without-terminal-chars.sql";
|
||||||
|
|
||||||
private StandardServiceRegistry ssr;
|
|
||||||
private static final String EXPECTED_ERROR_MESSAGE = "Import script Sql statements must terminate with a ';' char";
|
private static final String EXPECTED_ERROR_MESSAGE = "Import script Sql statements must terminate with a ';' char";
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() {
|
|
||||||
ssr = new StandardServiceRegistryBuilder()
|
|
||||||
.applySetting( Environment.HBM2DDL_AUTO, "none" )
|
|
||||||
.applySetting( Environment.DIALECT, CommentGenerationTest.SupportCommentDialect.class.getName() )
|
|
||||||
.applySetting(
|
|
||||||
Environment.HBM2DDL_IMPORT_FILES,
|
|
||||||
"/org/hibernate/test/fileimport/statements-without-terminal-chars.sql"
|
|
||||||
).applySetting( AvailableSettings.HBM2DDL_HALT_ON_ERROR, "true" )
|
|
||||||
.applySetting(
|
|
||||||
Environment.HBM2DDL_IMPORT_FILES_SQL_EXTRACTOR,
|
|
||||||
MultipleLinesSqlCommandExtractor.class.getName()
|
|
||||||
)
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testImportFile() {
|
public void testImportFile() throws IOException {
|
||||||
try {
|
final ClassLoader classLoader = ClassLoader.getSystemClassLoader();
|
||||||
final SchemaCreator schemaCreator = new SchemaCreatorImpl( ssr );
|
final MultipleLinesSqlCommandExtractor extractor = new MultipleLinesSqlCommandExtractor();
|
||||||
|
|
||||||
schemaCreator.doCreation(
|
try ( final InputStream stream = classLoader.getResourceAsStream( IMPORT_FILE ) ) {
|
||||||
buildMappings( ssr ),
|
assertThat( stream, notNullValue() );
|
||||||
this,
|
try (final InputStreamReader reader = new InputStreamReader( stream )) {
|
||||||
SourceDescriptorImpl.INSTANCE,
|
extractor.extractCommands( reader );
|
||||||
TargetDescriptorImpl.INSTANCE
|
}
|
||||||
);
|
|
||||||
|
|
||||||
fail( "ImportScriptException expected" );
|
fail( "ImportScriptException expected" );
|
||||||
}
|
}
|
||||||
catch (ImportScriptException e) {
|
catch (SqlScriptParserException e) {
|
||||||
final Throwable cause = e.getCause();
|
assertThat( e.getMessage(), endsWith( EXPECTED_ERROR_MESSAGE ) );
|
||||||
|
|
||||||
assertThat( cause, instanceOf( SqlStatementParser.StatementParserException.class ) );
|
|
||||||
|
|
||||||
assertThat( cause.getMessage(), is( EXPECTED_ERROR_MESSAGE ) );
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
|
||||||
public void tearDown() {
|
|
||||||
if ( ssr != null ) {
|
|
||||||
StandardServiceRegistryBuilder.destroy( ssr );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map getConfigurationValues() {
|
|
||||||
return ssr.getService( ConfigurationService.class ).getSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean shouldManageNamespaces() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ExceptionHandler getExceptionHandler() {
|
|
||||||
return ExceptionHandlerLoggedImpl.INSTANCE;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class SourceDescriptorImpl implements SourceDescriptor {
|
|
||||||
/**
|
|
||||||
* Singleton access
|
|
||||||
*/
|
|
||||||
public static final SourceDescriptorImpl INSTANCE = new SourceDescriptorImpl();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public SourceType getSourceType() {
|
|
||||||
return SourceType.METADATA;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ScriptSourceInput getScriptSourceInput() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class TargetDescriptorImpl implements TargetDescriptor {
|
|
||||||
/**
|
|
||||||
* Singleton access
|
|
||||||
*/
|
|
||||||
public static final TargetDescriptorImpl INSTANCE = new TargetDescriptorImpl();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public EnumSet<TargetType> getTargetTypes() {
|
|
||||||
return EnumSet.of( TargetType.DATABASE );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ScriptTargetOutput getScriptTargetOutput() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private Metadata buildMappings(StandardServiceRegistry registry) {
|
|
||||||
return new MetadataSources( registry )
|
|
||||||
.buildMetadata();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected StandardServiceRegistry buildJtaStandardServiceRegistry() {
|
|
||||||
StandardServiceRegistry registry = TestingJtaBootstrap.prepare().build();
|
|
||||||
assertThat(
|
|
||||||
registry.getService( TransactionCoordinatorBuilder.class ),
|
|
||||||
instanceOf( JtaTransactionCoordinatorBuilderImpl.class )
|
|
||||||
);
|
|
||||||
return registry;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,9 @@ log4j.rootLogger=info, stdout
|
||||||
|
|
||||||
log4j.logger.org.hibernate.orm.graph=debug
|
log4j.logger.org.hibernate.orm.graph=debug
|
||||||
|
|
||||||
|
#log4j.logger.org.hibernate.orm.tooling.schema=trace
|
||||||
|
## the AST logger gets very verbose at trace
|
||||||
|
#log4j.logger.org.hibernate.orm.tooling.schema.AST=debug
|
||||||
|
|
||||||
log4j.logger.org.hibernate.tool.hbm2ddl=trace
|
log4j.logger.org.hibernate.tool.hbm2ddl=trace
|
||||||
log4j.logger.org.hibernate.testing.cache=debug
|
log4j.logger.org.hibernate.testing.cache=debug
|
||||||
|
|
|
@ -30,3 +30,5 @@ INSERT INTO test_data (id, text)
|
||||||
, NULL
|
, NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
|
-- comment;
|
||||||
|
-- comment;
|
||||||
|
|
Loading…
Reference in New Issue