HHH-13229 - Sequences in MariaDB doesnt work on existing sequence

This commit is contained in:
Vlad Mihalcea 2019-01-25 10:48:59 +02:00
parent 0750716c87
commit d158762144
6 changed files with 373 additions and 14 deletions

View File

@ -59,7 +59,7 @@ ext {
'jdbc.url' : 'jdbc:mysql://127.0.0.1/hibernate_orm_test?useSSL=false'
],
mariadb : [
'db.dialect' : 'org.hibernate.dialect.MariaDB102Dialect',
'db.dialect' : 'org.hibernate.dialect.MariaDB103Dialect',
'jdbc.driver': 'org.mariadb.jdbc.Driver',
'jdbc.user' : 'hibernate_orm_test',
'jdbc.pass' : 'hibernate_orm_test',

View File

@ -6,10 +6,9 @@
*/
package org.hibernate.dialect;
import org.hibernate.LockOptions;
import org.hibernate.dialect.function.StandardSQLFunction;
import org.hibernate.tool.schema.extract.internal.SequenceNameExtractorImpl;
import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorMariaDBDatabaseImpl;
import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor;
import org.hibernate.type.StandardBasicTypes;
@ -63,8 +62,7 @@ public class MariaDB103Dialect extends MariaDB102Dialect {
@Override
public SequenceInformationExtractor getSequenceInformationExtractor() {
//TODO: Future improvement - https://hibernate.atlassian.net/browse/HHH-13008
return SequenceNameExtractorImpl.INSTANCE;
return SequenceInformationExtractorMariaDBDatabaseImpl.INSTANCE;
}
@Override

View File

@ -7,6 +7,7 @@
package org.hibernate.id.enhanced;
import java.io.Serializable;
import java.util.Objects;
import java.util.Properties;
import org.hibernate.HibernateException;
@ -568,14 +569,20 @@ public class SequenceStyleGenerator
* @return sequence increment value
*/
private Long getSequenceIncrementValue(JdbcEnvironment jdbcEnvironment, String sequenceName) {
return jdbcEnvironment.getExtractedDatabaseMetaData().getSequenceInformationList().stream().filter(
sequenceInformation -> {
Identifier catalog = sequenceInformation.getSequenceName().getCatalogName();
Identifier schema = sequenceInformation.getSequenceName().getSchemaName();
return sequenceName.equalsIgnoreCase( sequenceInformation.getSequenceName().getSequenceName().getText() ) &&
( catalog == null || catalog.equals( jdbcEnvironment.getCurrentCatalog() ) ) &&
( schema == null || schema.equals( jdbcEnvironment.getCurrentSchema() ) );
}
).map( SequenceInformation::getIncrementValue ).findFirst().orElse( null );
return jdbcEnvironment.getExtractedDatabaseMetaData().getSequenceInformationList()
.stream()
.filter(
sequenceInformation -> {
Identifier catalog = sequenceInformation.getSequenceName().getCatalogName();
Identifier schema = sequenceInformation.getSequenceName().getSchemaName();
return sequenceName.equalsIgnoreCase( sequenceInformation.getSequenceName().getSequenceName().getText() ) &&
( catalog == null || catalog.equals( jdbcEnvironment.getCurrentCatalog() ) ) &&
( schema == null || schema.equals( jdbcEnvironment.getCurrentSchema() ) );
}
)
.map( SequenceInformation::getIncrementValue )
.filter( Objects::nonNull )
.findFirst()
.orElse( null );
}
}

View File

@ -0,0 +1,115 @@
/*
* 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.extract.internal;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import org.hibernate.boot.model.relational.QualifiedSequenceName;
import org.hibernate.engine.jdbc.env.spi.IdentifierHelper;
import org.hibernate.tool.schema.extract.spi.ExtractionContext;
import org.hibernate.tool.schema.extract.spi.SequenceInformation;
/**
* @author Vlad Mihalcea, Magnus Hagström
*/
public class SequenceInformationExtractorMariaDBDatabaseImpl extends SequenceInformationExtractorLegacyImpl {
/**
* Singleton access
*/
public static final SequenceInformationExtractorMariaDBDatabaseImpl INSTANCE = new SequenceInformationExtractorMariaDBDatabaseImpl();
// SQL to get metadata from individual sequence
private static final String SQL_SEQUENCE_QUERY =
"SELECT '%1$s' as sequence_name, minimum_value, maximum_value, start_value, increment, cache_size FROM %1$s ";
private static final String UNION_ALL =
"UNION ALL ";
@Override
public Iterable<SequenceInformation> extractMetadata(ExtractionContext extractionContext) throws SQLException {
final String lookupSql = extractionContext.getJdbcEnvironment().getDialect().getQuerySequencesString();
// *should* never happen, but to be safe in the interest of performance...
if (lookupSql == null) {
return SequenceInformationExtractorNoOpImpl.INSTANCE.extractMetadata(extractionContext);
}
final IdentifierHelper identifierHelper = extractionContext.getJdbcEnvironment().getIdentifierHelper();
final List<SequenceInformation> sequenceInformationList = new ArrayList<>();
final List<String> sequenceNames = new ArrayList<>();
try (
final Statement statement = extractionContext.getJdbcConnection().createStatement();
final ResultSet resultSet = statement.executeQuery( lookupSql );
) {
while ( resultSet.next() ) {
sequenceNames.add( resultSetSequenceName( resultSet ) );
}
}
if ( !sequenceNames.isEmpty() ) {
StringBuilder sequenceInfoQueryBuilder = new StringBuilder();
for ( String sequenceName : sequenceNames ) {
if ( sequenceInfoQueryBuilder.length() > 0 ) {
sequenceInfoQueryBuilder.append( UNION_ALL );
}
sequenceInfoQueryBuilder.append( String.format( SQL_SEQUENCE_QUERY, sequenceName ) );
}
int index = 0;
try (
final Statement statement = extractionContext.getJdbcConnection().createStatement();
final ResultSet resultSet = statement.executeQuery( sequenceInfoQueryBuilder.toString() );
) {
while ( resultSet.next() ) {
SequenceInformation sequenceInformation = new SequenceInformationImpl(
new QualifiedSequenceName(
null,
null,
identifierHelper.toIdentifier(
resultSetSequenceName(resultSet)
)
),
resultSetStartValueSize(resultSet),
resultSetMinValue(resultSet),
resultSetMaxValue(resultSet),
resultSetIncrementValue(resultSet)
);
sequenceInformationList.add(sequenceInformation);
}
}
}
return sequenceInformationList;
}
protected String resultSetSequenceName(ResultSet resultSet) throws SQLException {
return resultSet.getString(1);
}
@Override
protected String sequenceCatalogColumn() {
return null;
}
@Override
protected String sequenceSchemaColumn() {
return null;
}
}

View File

@ -18,6 +18,7 @@ import org.hibernate.dialect.DB2Dialect;
import org.hibernate.dialect.DerbyDialect;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.HSQLDialect;
import org.hibernate.dialect.MariaDB103Dialect;
import org.hibernate.dialect.Oracle8iDialect;
import org.hibernate.dialect.SQLServer2012Dialect;
import org.hibernate.engine.spi.SessionImplementor;
@ -54,6 +55,10 @@ public class SequenceValueExtractor {
queryString = "select " + sequenceName + ".currval from sys.dummy";
}
else if ( dialect instanceof MariaDB103Dialect ) {
queryString = "select LASTVAL(" + sequenceName + ")";
}
else {
queryString = "select currval('" + sequenceName + "');";
}

View File

@ -0,0 +1,234 @@
/*
* 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.dialect.functional;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import org.hibernate.Session;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.boot.spi.MetadataImplementor;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.cfg.Environment;
import org.hibernate.dialect.MariaDB103Dialect;
import org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.tool.hbm2ddl.SchemaExport;
import org.hibernate.tool.schema.TargetType;
import org.hibernate.tool.schema.extract.spi.SequenceInformation;
import org.hibernate.testing.DialectChecks;
import org.hibernate.testing.RequiresDialect;
import org.hibernate.testing.RequiresDialectFeature;
import org.hibernate.testing.TestForIssue;
import org.junit.Test;
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
/**
* @author Vlad Mihalcea
*/
@TestForIssue(jiraKey = "HHH-12973")
@RequiresDialect(MariaDB103Dialect.class)
public class SequenceInformationMariaDBTest extends
BaseEntityManagerFunctionalTestCase {
private DriverManagerConnectionProviderImpl connectionProvider;
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class[] {
Book.class,
Author.class
};
}
@Override
public void buildEntityManagerFactory() {
connectionProvider = new DriverManagerConnectionProviderImpl();
connectionProvider.configure( Environment.getProperties() );
try(Connection connection = connectionProvider.getConnection();
Statement statement = connection.createStatement()) {
try {
statement.execute( "DROP SEQUENCE IF EXISTS book_sequence" );
statement.execute( "DROP SEQUENCE IF EXISTS author_sequence" );
}
catch (SQLException e) {
}
try {
statement.execute( "DROP TABLE TBL_BOOK" );
statement.execute( "DROP TABLE TBL_AUTHOR" );
}
catch (SQLException e) {
}
statement.execute( "CREATE TABLE `TBL_BOOK` ( " +
" `ID` int(11) NOT NULL, " +
" `TITLE` varchar(255) DEFAULT NULL, " +
" PRIMARY KEY (`ID`) " +
") ENGINE=InnoDB" );
statement.execute( "CREATE TABLE `TBL_AUTHOR` ( " +
" `ID` int(11) NOT NULL, " +
" `firstName` varchar(255) DEFAULT NULL, " +
" `lastName` varchar(255) DEFAULT NULL, " +
" PRIMARY KEY (`ID`) " +
") ENGINE=InnoDB" );
statement.execute( "CREATE SEQUENCE book_sequence " +
" START WITH 1 " +
" INCREMENT BY 1 " +
" MAXVALUE 2999999999 " +
" MINVALUE 0 " +
" CACHE 10" );
statement.execute( "CREATE SEQUENCE author_sequence " +
" START WITH 1 " +
" INCREMENT BY 1 " +
" MAXVALUE 2999999999 " +
" MINVALUE 0 " +
" CACHE 10" );
}
catch (SQLException e) {
fail(e.getMessage());
}
super.buildEntityManagerFactory();
}
@Override
public void releaseResources() {
super.releaseResources();
super.releaseResources();
try(Connection connection = connectionProvider.getConnection();
Statement statement = connection.createStatement()) {
try {
statement.execute( "DROP SEQUENCE book_sequence" );
statement.execute( "DROP SEQUENCE author_sequence" );
}
catch (SQLException e) {
}
try {
statement.execute( "DROP TABLE TBL_BOOK" );
statement.execute( "DROP TABLE TBL_AUTHOR" );
}
catch (SQLException e) {
}
}
catch (SQLException e) {
fail(e.getMessage());
}
if ( connectionProvider != null ) {
connectionProvider.stop();
}
}
@Override
protected void addMappings(Map settings) {
settings.put( AvailableSettings.HBM2DDL_AUTO, "none" );
}
@Test
public void test() {
doInJPA( this::entityManagerFactory, entityManager -> {
Book book = new Book();
book.setTitle("My Book");
entityManager.persist(book);
} );
}
@Entity
@Table(name = "TBL_BOOK")
public static class Book {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
@SequenceGenerator(
name = "book_sequence",
allocationSize = 10
)
@Column(name = "ID")
private Integer id;
@Column(name = "TITLE")
private String title;
public Book() {
}
public Book(String title) {
this.title = title;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
@Override
public String toString() {
return "Book{" +
"id=" + id +
", title='" + title + '\'' +
'}';
}
}
@Entity
@Table(name = "TBL_AUTHOR")
public static class Author {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
@SequenceGenerator(
name = "author_sequence",
allocationSize = 10
)
@Column(name = "ID")
private Integer id;
@Column(name = "FIRST_NAME")
private String firstName;
@Column(name = "LAST_NAME")
private String lastName;
}
}