Skip to content

Commit

Permalink
HHH-14033 - SQL script parsing problem with multi-line comments
Browse files Browse the repository at this point in the history
- Better handling of multi-line comments
- Restructured some internal classes to consolidate packages
- Added "system"-style SchemaToolingLogging
  • Loading branch information
sebersole committed May 19, 2020
1 parent e1b1207 commit b658e90
Show file tree
Hide file tree
Showing 11 changed files with 411 additions and 285 deletions.
3 changes: 3 additions & 0 deletions hibernate-core/hibernate-core.gradle
Expand Up @@ -203,6 +203,9 @@ xjc {
}
}

generateGrammarSource {
arguments += "-traceParser"
}

//sourceSets.main.sourceGeneratorsTask.dependsOn xjc
//sourceSets.main.sourceGeneratorsTask.dependsOn generateGrammarSource
Expand Down
195 changes: 96 additions & 99 deletions hibernate-core/src/main/antlr/sql-stmt.g
@@ -1,6 +1,6 @@
header
{
package org.hibernate.hql.internal.antlr;
package org.hibernate.tool.schema.ast;

import java.util.Iterator;
import java.util.List;
Expand All @@ -14,139 +14,136 @@ import org.hibernate.hql.internal.ast.ErrorReporter;
*
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
class SqlStatementParser extends Parser;
class GeneratedSqlScriptParser extends Parser;

options {
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) {
current.append( stmt );
// by default, nothing to do
}

protected void out(Token token) {
out( token.getText() );
// by default, nothing to do
}

public List<String> getStatementList() {
return statementList;
protected void statementStarted() {
// by default, nothing to do
}

protected void statementEnd() {
statementList.add( current.toString().trim() );
current = new StringBuilder();
protected void statementEnded() {
// by default, nothing to do
}
}

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
: ( statement )*
: (statement)+ EOF
;

statement
: ( s:NOT_STMT_END { out( s ); } | q:QUOTED_STRING { out( q ); } )* STMT_END { statementEnd(); }
;
: { statementStarted(); } (statementPart)* DELIMITER { statementEnded(); }
;

statementPart
: quotedString
| nonSkippedChar
;

quotedString
: q:QUOTED_TEXT {
out( q );
}
;

nonSkippedChar
: c:CHAR {
out( c );
}
;


class SqlStatementLexer extends Lexer;
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Lexer rules
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

class SqlScriptLexer extends Lexer;

options {
k = 2;
charVocabulary = '\u0000'..'\uFFFE';
}

STMT_END
: ';' ( '\t' | ' ' | '\r' | '\n' )*
;

NOT_STMT_END
: ~( ';' )
;
DELIMITER : ';' ;

QUOTED_STRING
: '\'' ( (ESCqs)=> ESCqs | ~'\'' )* '\''
// 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
: '\'' '\''
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
: ( "//" | "--" ) ( ~('\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
: "/*" ( options {greedy=false;} : . )* "*/" { $setType(Token.SKIP); }
;
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);}
;
Expand Up @@ -9,28 +9,20 @@
import java.io.Reader;
import java.util.List;

import org.hibernate.hql.internal.antlr.SqlStatementLexer;
import org.hibernate.hql.internal.antlr.SqlStatementParser;
import org.hibernate.tool.schema.ast.SqlScriptParser;

/**
* 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.
*
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
* @author Steve Ebersole
*/
public class MultipleLinesSqlCommandExtractor implements ImportSqlCommandExtractor {
@Override
public String[] extractCommands(Reader reader) {
final SqlStatementLexer lexer = new SqlStatementLexer( reader );
final SqlStatementParser parser = new SqlStatementParser( lexer );
try {
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()] );
final List<String> commands = SqlScriptParser.extractCommands( reader );

return commands.toArray( new String[0] );
}
}
@@ -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 comments on commit b658e90

Please sign in to comment.