HHH-2403 - New configuration parameter, documentation

This commit is contained in:
Lukasz Antoniak 2011-11-09 00:23:41 +01:00 committed by Steve Ebersole
parent c6d616a8bf
commit 00e32f5e7f
15 changed files with 322 additions and 56 deletions

View File

@ -916,6 +916,20 @@ hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect</programlisting>
<literal>/humans.sql,/dogs.sql</literal> </para></entry>
</row>
<row>
<entry><literal>hibernate.hbm2ddl.import_files_sql_extractor</literal></entry>
<entry><para>The classname of a custom <interfacename>ImportSqlCommandExtractor</interfacename>
(defaults to the built-in <classname>SingleLineSqlCommandExtractor</classname>).
This is useful for implementing dedicated parser that extracts
single SQL statements from each import file. Hibernate provides
also <classname>MultipleLinesSqlCommandExtractor</classname> which
supports instructions/comments and quoted strings spread over
multiple lines (mandatory semicolon at the end of each statement).
</para><para> <emphasis role="strong">e.g.</emphasis>
<literal>classname.of.ImportSqlCommandExtractor</literal></para></entry>
</row>
<row>
<entry><literal>hibernate.bytecode.use_reflection_optimizer</literal></entry>

View File

@ -2,12 +2,15 @@ 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 single and multiple line
* instructions/comments and quoted strings. Each statement should end with semicolon.
* 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)
*/
@ -18,6 +21,29 @@ options {
}
{
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() ) {
throw new StatementParserException(errorHandler.getErrorMessage());
}
}
/** List of all SQL statements. */
private List<String> statementList = new LinkedList<String>();
@ -40,6 +66,45 @@ options {
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() {
StringBuffer buf = new StringBuffer();
for ( Iterator iterator = errorList.iterator(); iterator.hasNext(); ) {
buf.append( (String) iterator.next() );
if ( iterator.hasNext() ) {
buf.append( "\n" );
}
}
return buf.toString();
}
}
}
script

View File

@ -375,6 +375,14 @@ public interface AvailableSettings {
*/
public static final String HBM2DDL_IMPORT_FILES = "hibernate.hbm2ddl.import_files";
/**
* {@link String} reference to {@link org.hibernate.tool.hbm2ddl.ImportSqlCommandExtractor} implementation class.
* Referenced implementation is required to provide non-argument constructor.
*
* The default value is <tt>org.hibernate.tool.hbm2ddl.SingleLineSqlCommandExtractor</tt>.
*/
public static final String HBM2DDL_IMPORT_FILES_SQL_EXTRACTOR = "hibernate.hbm2ddl.import_files_sql_extractor";
/**
* The {@link org.hibernate.exception.spi.SQLExceptionConverter} to use for converting SQLExceptions
* to Hibernate's JDBCException hierarchy. The default is to use the configured

View File

@ -45,6 +45,7 @@ import org.hibernate.service.jmx.internal.JmxServiceInitiator;
import org.hibernate.service.jndi.internal.JndiServiceInitiator;
import org.hibernate.service.jta.platform.internal.JtaPlatformInitiator;
import org.hibernate.service.spi.BasicServiceInitiator;
import org.hibernate.tool.hbm2ddl.ImportSqlCommandExtractorInitiator;
/**
* Central definition of the standard set of service initiators defined by Hibernate.
@ -82,6 +83,8 @@ public class StandardServiceInitiators {
serviceInitiators.add( RegionFactoryInitiator.INSTANCE );
serviceInitiators.add( InstrumentationServiceInitiator.INSTANCE );
serviceInitiators.add( ImportSqlCommandExtractorInitiator.INSTANCE );
return Collections.unmodifiableList( serviceInitiators );
}

View File

@ -0,0 +1,18 @@
package org.hibernate.tool.hbm2ddl;
import java.io.Reader;
import org.hibernate.service.Service;
/**
* Contract for extracting statements from <i>import.sql</i> script.
*
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
public interface ImportSqlCommandExtractor extends Service {
/**
* @param reader Character stream reader of SQL script.
* @return List of single SQL statements. Each command may or may not contain semicolon at the end.
*/
public String[] extractCommands(Reader reader);
}

View File

@ -0,0 +1,48 @@
package org.hibernate.tool.hbm2ddl;
import java.util.Map;
import org.hibernate.HibernateException;
import org.hibernate.cfg.Environment;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.service.classloading.spi.ClassLoaderService;
import org.hibernate.service.spi.BasicServiceInitiator;
import org.hibernate.service.spi.ServiceRegistryImplementor;
/**
* Instantiates and configures an appropriate {@link ImportSqlCommandExtractor}. By default
* {@link SingleLineSqlCommandExtractor} is used.
*
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
public class ImportSqlCommandExtractorInitiator implements BasicServiceInitiator<ImportSqlCommandExtractor> {
private static final String DEFAULT_EXTRACTOR = SingleLineSqlCommandExtractor.class.getName();
public static final ImportSqlCommandExtractorInitiator INSTANCE = new ImportSqlCommandExtractorInitiator();
@Override
public ImportSqlCommandExtractor initiateService(Map configurationValues, ServiceRegistryImplementor registry) {
String extractorClassName = (String) configurationValues.get( Environment.HBM2DDL_IMPORT_FILES_SQL_EXTRACTOR );
if ( StringHelper.isEmpty( extractorClassName ) ) {
extractorClassName = DEFAULT_EXTRACTOR;
}
final ClassLoaderService classLoaderService = registry.getService( ClassLoaderService.class );
return instantiateExplicitCommandExtractor( extractorClassName, classLoaderService );
}
private ImportSqlCommandExtractor instantiateExplicitCommandExtractor(String extractorClassName,
ClassLoaderService classLoaderService) {
try {
return (ImportSqlCommandExtractor) classLoaderService.classForName( extractorClassName ).newInstance();
}
catch ( Exception e ) {
throw new HibernateException(
"Could not instantiate import sql command extractor [" + extractorClassName + "]", e
);
}
}
@Override
public Class<ImportSqlCommandExtractor> getServiceInitiated() {
return ImportSqlCommandExtractor.class;
}
}

View File

@ -1,31 +1,30 @@
package org.hibernate.tool.hbm2ddl;
import org.hibernate.hql.internal.antlr.SqlStatementLexer;
import org.hibernate.hql.internal.antlr.SqlStatementParser;
import java.io.Reader;
import java.util.List;
import org.hibernate.hql.internal.antlr.SqlStatementLexer;
import org.hibernate.hql.internal.antlr.SqlStatementParser;
/**
* Class responsible for extracting SQL statements from import script. Supports single and multiple line
* instructions/comments and quoted strings.
* 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)
*/
public class StatementExtractor {
/**
* @param reader Character stream reader of entire SQL script. Each statement should end with semicolon.
* @return List of single SQL statements (each without semicolon at the end).
*/
List<String> retrieveStatements(final Reader reader) {
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 );
}
return parser.getStatementList();
List<String> statementList = parser.getStatementList();
return statementList.toArray( new String[statementList.size()] );
}
}

View File

@ -60,6 +60,7 @@ import org.hibernate.internal.util.config.ConfigurationHelper;
import org.hibernate.metamodel.source.MetadataImplementor;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.service.ServiceRegistryBuilder;
import org.hibernate.service.classloading.spi.ClassLoaderService;
import org.hibernate.service.config.spi.ConfigurationService;
import org.hibernate.service.internal.StandardServiceRegistryImpl;
import org.hibernate.service.jdbc.connections.spi.ConnectionProvider;
@ -90,6 +91,7 @@ public class SchemaExport {
}
}
private final ServiceRegistry serviceRegistry;
private final ConnectionHelper connectionHelper;
private final SqlStatementLogger sqlStatementLogger;
private final SqlExceptionHelper sqlExceptionHelper;
@ -106,6 +108,7 @@ public class SchemaExport {
private boolean haltOnError = false;
public SchemaExport(ServiceRegistry serviceRegistry, Configuration configuration) {
this.serviceRegistry = serviceRegistry;
this.connectionHelper = new SuppliedConnectionProviderConnectionHelper(
serviceRegistry.getService( ConnectionProvider.class )
);
@ -125,7 +128,7 @@ public class SchemaExport {
}
public SchemaExport(MetadataImplementor metadata) {
ServiceRegistry serviceRegistry = metadata.getServiceRegistry();
this.serviceRegistry = metadata.getServiceRegistry();
this.connectionHelper = new SuppliedConnectionProviderConnectionHelper(
serviceRegistry.getService( ConnectionProvider.class )
);
@ -186,6 +189,7 @@ public class SchemaExport {
this.dropSQL = configuration.generateDropSchemaScript( dialect );
this.createSQL = configuration.generateSchemaCreationScript( dialect );
this.serviceRegistry = createServiceRegistry( props );
}
/**
@ -211,6 +215,7 @@ public class SchemaExport {
final Dialect dialect = Dialect.getDialect( configuration.getProperties() );
this.dropSQL = configuration.generateDropSchemaScript( dialect );
this.createSQL = configuration.generateSchemaCreationScript( dialect );
this.serviceRegistry = createServiceRegistry( configuration.getProperties() );
}
public SchemaExport(
@ -224,6 +229,7 @@ public class SchemaExport {
this.sqlStatementLogger = new SqlStatementLogger( false, true );
this.sqlExceptionHelper = new SqlExceptionHelper();
this.formatter = FormatStyle.DDL.getFormatter();
this.serviceRegistry = createServiceRegistry( new Properties() );
}
/**
@ -419,19 +425,27 @@ public class SchemaExport {
private void importScript(NamedReader namedReader, List<Exporter> exporters) throws Exception {
BufferedReader reader = new BufferedReader( namedReader.getReader() );
List<String> statementList = new StatementExtractor().retrieveStatements( reader );
for ( String statement : statementList ) {
if ( !StringHelper.isEmpty( statement ) ) {
try {
for ( Exporter exporter : exporters ) {
if ( exporter.acceptsImportScripts() ) {
exporter.export( statement );
String[] statements = serviceRegistry.getService( ImportSqlCommandExtractor.class ).extractCommands( reader );
if (statements != null) {
for ( String statement : statements ) {
if ( statement != null ) {
String trimmedSql = statement.trim();
if ( trimmedSql.endsWith( ";" )) {
trimmedSql = trimmedSql.substring( 0, statement.length() - 1 );
}
if ( !StringHelper.isEmpty( trimmedSql ) ) {
try {
for ( Exporter exporter : exporters ) {
if ( exporter.acceptsImportScripts() ) {
exporter.export( trimmedSql );
}
}
}
catch ( Exception e ) {
throw new ImportScriptException( "Error during statement execution (file: '" + namedReader.getName() + "'): " + trimmedSql, e );
}
}
}
catch ( Exception e ) {
throw new ImportScriptException( "Error during statement execution (file: '" + namedReader.getName() + "'): " + statement, e );
}
}
}
}

View File

@ -0,0 +1,43 @@
package org.hibernate.tool.hbm2ddl;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.util.LinkedList;
import java.util.List;
import org.hibernate.internal.util.StringHelper;
/**
* Class responsible for extracting SQL statements from import script. Treads each line as a complete SQL statement.
* Comment lines shall start with {@code --}, {@code //} or {@code /*} character sequence.
*
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
public class SingleLineSqlCommandExtractor implements ImportSqlCommandExtractor {
@Override
public String[] extractCommands(Reader reader) {
BufferedReader bufferedReader = new BufferedReader( reader );
List<String> statementList = new LinkedList<String>();
try {
for ( String sql = bufferedReader.readLine(); sql != null; sql = bufferedReader.readLine() ) {
String trimmedSql = sql.trim();
if ( StringHelper.isEmpty( trimmedSql ) || isComment( trimmedSql ) ) {
continue;
}
if ( trimmedSql.endsWith( ";" ) ) {
trimmedSql = trimmedSql.substring( 0, trimmedSql.length() - 1 );
}
statementList.add( trimmedSql );
}
return statementList.toArray( new String[statementList.size()] );
}
catch ( IOException e ) {
throw new ImportScriptException( "Error during import script parsing.", e );
}
}
private boolean isComment(final String line) {
return line.startsWith( "--" ) || line.startsWith( "//" ) || line.startsWith( "/*" );
}
}

View File

@ -0,0 +1,25 @@
package org.hibernate.test.importfile;
import org.hibernate.cfg.Configuration;
import org.hibernate.cfg.Environment;
import org.hibernate.service.ServiceRegistryBuilder;
import org.hibernate.testing.TestForIssue;
import org.hibernate.tool.hbm2ddl.ImportSqlCommandExtractor;
import org.hibernate.tool.hbm2ddl.MultipleLinesSqlCommandExtractor;
/**
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
@TestForIssue( jiraKey = "HHH-2403" )
public class CommandExtractorServiceTest extends MultiLineImportFileTest {
@Override
public void configure(Configuration cfg) {
cfg.setProperty( Environment.HBM2DDL_IMPORT_FILES, "/multiline-stmt.sql" );
}
@Override
protected void prepareBasicRegistryBuilder(ServiceRegistryBuilder serviceRegistryBuilder) {
super.prepareBasicRegistryBuilder( serviceRegistryBuilder );
serviceRegistryBuilder.addService( ImportSqlCommandExtractor.class, new MultipleLinesSqlCommandExtractor() );
}
}

View File

@ -0,0 +1,51 @@
package org.hibernate.test.importfile;
import java.math.BigInteger;
import org.junit.Test;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.hibernate.cfg.Environment;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.hibernate.tool.hbm2ddl.MultipleLinesSqlCommandExtractor;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
/**
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
@TestForIssue( jiraKey = "HHH-2403" )
public class MultiLineImportFileTest extends BaseCoreFunctionalTestCase {
@Override
public void configure(Configuration cfg) {
cfg.setProperty( Environment.HBM2DDL_IMPORT_FILES, "/multiline-stmt.sql" );
cfg.setProperty( Environment.HBM2DDL_IMPORT_FILES_SQL_EXTRACTOR, MultipleLinesSqlCommandExtractor.class.getName() );
}
@Override
public String[] getMappings() {
return NO_MAPPINGS;
}
@Test
public void testImportFile() throws Exception {
Session s = openSession();
final Transaction tx = s.beginTransaction();
BigInteger count = (BigInteger) s.createSQLQuery( "SELECT COUNT(*) FROM test_data" ).uniqueResult();
assertEquals( "incorrect row number", 3L, count.longValue() );
String multilineText = (String) s.createSQLQuery( "SELECT text FROM test_data WHERE id = 2" ).uniqueResult();
assertEquals( "multiline string inserted incorrectly", "Multiline comment line 1\r\n-- line 2'\r\n/* line 3 */", multilineText );
String empty = (String) s.createSQLQuery( "SELECT text FROM test_data WHERE id = 3" ).uniqueResult();
assertNull( "NULL value inserted incorrectly", empty );
tx.commit();
s.close();
}
}

View File

@ -22,7 +22,6 @@
* Boston, MA 02110-1301 USA
*/
package org.hibernate.test.importfile;
import java.math.BigInteger;
import java.util.List;
import org.junit.Test;
@ -34,16 +33,14 @@ import org.hibernate.cfg.Environment;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
/**
* @author Emmanuel Bernard
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
public class ImportFileTest extends BaseCoreFunctionalTestCase {
public class SingleLineImportFileTest extends BaseCoreFunctionalTestCase {
@Override
public void configure(Configuration cfg) {
cfg.setProperty( Environment.HBM2DDL_IMPORT_FILES, "/humans.sql,/dogs.sql,/multiline-stmt.sql" );
cfg.setProperty( Environment.HBM2DDL_IMPORT_FILES, "/humans.sql,/dogs.sql" );
}
@Override
@ -55,7 +52,7 @@ public class ImportFileTest extends BaseCoreFunctionalTestCase {
}
@Test
public void testSingleLineImportFile() throws Exception {
public void testImportFile() throws Exception {
Session s = openSession( );
final Transaction tx = s.beginTransaction();
final List<?> humans = s.createQuery( "from " + Human.class.getName() ).list();
@ -72,22 +69,4 @@ public class ImportFileTest extends BaseCoreFunctionalTestCase {
tx.commit();
s.close();
}
@Test
public void testMultipleLineImportFile() throws Exception {
Session s = openSession();
final Transaction tx = s.beginTransaction();
BigInteger count = (BigInteger) s.createSQLQuery( "SELECT COUNT(*) FROM test_data" ).uniqueResult();
assertEquals( "incorrect row number", 3L, count.longValue() );
String multilineText = (String) s.createSQLQuery( "SELECT text FROM test_data WHERE id = 2" ).uniqueResult();
assertEquals( "multiline string inserted incorrectly", "Multiline comment line 1\r\n-- line 2'\r\n/* line 3 */", multilineText );
String empty = (String) s.createSQLQuery( "SELECT text FROM test_data WHERE id = 3" ).uniqueResult();
assertNull( "NULL value inserted incorrectly", empty );
tx.commit();
s.close();
}
}

View File

@ -1,3 +1,3 @@
INSERT INTO dog (id, master_fk) VALUES (1,1);
INSERT INTO dog (id, master_fk) VALUES (2,2);
INSERT INTO dog (id, master_fk) VALUES (3,3);
INSERT INTO dog (id, master_fk) VALUES (1,1)
INSERT INTO dog (id, master_fk) VALUES (2,2)
INSERT INTO dog (id, master_fk) VALUES (3,3)

View File

@ -1,3 +1,3 @@
INSERT INTO human (id, fname, lname) VALUES (1,'Emmanuel','Bernard');
INSERT INTO human (id, fname, lname) VALUES (2,'Gavin','King');
INSERT INTO human (id, fname, lname) VALUES (3,'Max','Andersen');
INSERT INTO human (id, fname, lname) VALUES (1,'Emmanuel','Bernard')
INSERT INTO human (id, fname, lname) VALUES (2,'Gavin','King')
INSERT INTO human (id, fname, lname) VALUES (3,'Max','Andersen')

View File

@ -22,7 +22,6 @@ INSERT INTO test_data VALUES (2, 'Multiline comment line 1
-- INSERT INTO test_data VALUES (1, NULL);
INSERT INTO test_data VALUES (3 /* 'third record' */, NULL /* value */); -- with NULL value
INSERT INTO test_data (id, text)
VALUES
(