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:
Steve Ebersole 2020-05-19 12:25:06 -05:00 committed by Sanne Grinovero
parent 00989d28d8
commit c1254cc205
15 changed files with 513 additions and 342 deletions

View File

@ -209,6 +209,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

View File

@ -0,0 +1,154 @@
header
{
package org.hibernate.tool.schema.ast;
import java.util.Iterator;
import java.util.List;
import java.util.LinkedList;
import org.hibernate.hql.internal.ast.ErrorReporter;
}
/**
* Lexer and parser used to extract single statements from import SQL script. Supports instructions/comments and quoted
* strings spread over multiple lines. Each statement must end with semicolon.
*
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
class GeneratedSqlScriptParser extends Parser;
options {
buildAST = false;
k=3;
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Semantic actions
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
{
protected void out(String stmt) {
// by default, nothing to do
}
protected void out(Token token) {
// by default, nothing to do
}
protected void statementStarted() {
// by default, nothing to do
}
protected void statementEnded() {
// by default, nothing to do
}
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Parser rules
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
script
: (statement)+ EOF
;
statement
: { statementStarted(); } (statementPart)* DELIMITER { statementEnded(); }
;
statementPart
: quotedString
| nonSkippedChar
;
quotedString
: q:QUOTED_TEXT {
out( q );
}
;
nonSkippedChar
: c:CHAR {
out( c );
}
;
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Lexer rules
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
class SqlScriptLexer extends Lexer;
options {
k = 2;
charVocabulary = '\u0000'..'\uFFFE';
}
DELIMITER : ';' ;
// 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_TEXT
: '`' ( ~('`') )* '`'
| '\'' ( (ESCqs) => ESCqs | ~('\'') )* '\''
// : '\'' ( ~('\'') )* '\''
;
protected
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
// 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 );
}
;
/**
* Note : this comes from the great Terrance Parr (author of Antlr) -
*
* https://theantlrguy.atlassian.net/wiki/spaces/ANTLR3/pages/2687360/How+do+I+match+multi-line+comments
*/
BLOCK_COMMENT
: "/*"
( /* '\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);}
;

View File

@ -1,152 +0,0 @@
header
{
package org.hibernate.hql.internal.antlr;
import java.util.Iterator;
import java.util.List;
import java.util.LinkedList;
import org.hibernate.hql.internal.ast.ErrorReporter;
}
/**
* Lexer and parser used to extract single statements from import SQL script. Supports instructions/comments and quoted
* strings spread over multiple lines. Each statement must end with semicolon.
*
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
class SqlStatementParser extends Parser;
options {
buildAST = false;
}
{
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) {
current.append( stmt );
}
protected void out(Token token) {
out( token.getText() );
}
public List<String> getStatementList() {
return statementList;
}
protected void statementEnd() {
statementList.add( current.toString().trim() );
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();
}
}
}
script
: ( statement )*
;
statement
: ( s:NOT_STMT_END { out( s ); } | q:QUOTED_STRING { out( q ); } )* STMT_END { statementEnd(); }
;
class SqlStatementLexer extends Lexer;
options {
k = 2;
charVocabulary = '\u0000'..'\uFFFE';
}
STMT_END
: ';' ( '\t' | ' ' | '\r' | '\n' )*
;
NOT_STMT_END
: ~( ';' )
;
QUOTED_STRING
: '\'' ( (ESCqs)=> ESCqs | ~'\'' )* '\''
;
protected
ESCqs
: '\'' '\''
;
LINE_COMMENT
: ( "//" | "--" ) ( ~('\n'|'\r') )* { $setType(Token.SKIP); }
;
MULTILINE_COMMENT
: "/*" ( options {greedy=false;} : . )* "*/" { $setType(Token.SKIP); }
;

View File

@ -55,6 +55,8 @@ public class GraphParser extends GeneratedGraphParser {
this.sessionFactory = sessionFactory; this.sessionFactory = sessionFactory;
} }
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Semantic actions
@Override @Override
protected void startAttribute(Token attributeNameToken) { protected void startAttribute(Token attributeNameToken) {
@ -164,4 +166,18 @@ public class GraphParser extends GeneratedGraphParser {
); );
} }
} }
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// trace logging hooks
@Override
public void traceIn(String s) throws TokenStreamException {
// nothing to do - this parser already does a good job with logging trace info
}
@Override
public void traceOut(String s) throws TokenStreamException {
// nothing to do - this parser already does a good job with logging trace info
}
} }

View File

@ -62,7 +62,8 @@ public final class HqlParser extends HqlBaseParser {
} }
// handle trace logging ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// trace logging hooks
private int traceDepth; private int traceDepth;
@ -90,6 +91,9 @@ public final class HqlParser extends HqlBaseParser {
LOG.trace( prefix + ruleName ); LOG.trace( prefix + ruleName );
} }
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// error handling hooks
@Override @Override
public void reportError(RecognitionException e) { public void reportError(RecognitionException e) {
parseErrorHandler.reportError( e ); // Use the delegate. parseErrorHandler.reportError( e ); // Use the delegate.
@ -110,6 +114,9 @@ public final class HqlParser extends HqlBaseParser {
} }
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Semantic actions
/** /**
* Overrides the base behavior to retry keywords as identifiers. * Overrides the base behavior to retry keywords as identifiers.
* *

View File

@ -45,6 +45,9 @@ public class OrderByFragmentParser extends GeneratedOrderByFragmentParser {
return columnReferences; return columnReferences;
} }
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// semantic actions
@Override @Override
protected AST quotedIdentifier(AST ident) { protected AST quotedIdentifier(AST ident) {
/* /*
@ -286,26 +289,37 @@ public class OrderByFragmentParser extends GeneratedOrderByFragmentParser {
} }
// trace logging ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// trace logging hooks
private int traceDepth = 0; private int traceDepth = 0;
@Override @Override
public void traceIn(String ruleName) { public void traceIn(String ruleName) {
if ( ! LOG.isTraceEnabled() ) {
return;
}
if ( inputState.guessing > 0 ) { if ( inputState.guessing > 0 ) {
return; return;
} }
String prefix = StringHelper.repeat( '-', ( traceDepth++ * 2 ) ) + "-> ";
final String prefix = StringHelper.repeat( '-', ( traceDepth++ * 2 ) ) + "-> ";
LOG.trace( prefix + ruleName ); LOG.trace( prefix + ruleName );
} }
@Override @Override
public void traceOut(String ruleName) { public void traceOut(String ruleName) {
if ( ! LOG.isTraceEnabled() ) {
return;
}
if ( inputState.guessing > 0 ) { if ( inputState.guessing > 0 ) {
return; return;
} }
String prefix = "<-" + StringHelper.repeat( '-', ( --traceDepth * 2 ) ) + " ";
final String prefix = "<-" + StringHelper.repeat( '-', ( --traceDepth * 2 ) ) + " ";
LOG.trace( prefix + ruleName ); LOG.trace( prefix + ruleName );
} }

View File

@ -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()] );
} }
} }

View File

@ -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();
}

View File

@ -0,0 +1,181 @@
/*
* 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) {
currentStatementBuffer.append( text );
}
/**
* Semantic action outputting a token to the current statement buffer
*/
@Override
protected void out(Token token) {
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();
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// error handling hooks
@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 );
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// trace logging hooks
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 );
}
}

View File

@ -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 );
}
}

View File

@ -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() );
}
}

View File

@ -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)" ) );
}
}
}
}

View File

@ -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;
}
} }

View File

@ -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

View File

@ -30,3 +30,5 @@ INSERT INTO test_data (id, text)
, NULL , NULL
); );
-- comment;
-- comment;