hibernate.hbm2ddl.auto="validate" works incorrectly with Postgres 9.1 sequences

Description

Hello,

We are in the progress of migrating from JDBC to Hibernate/JPA, as such we are using hibernate.hbm2ddl.auto="validate" to ensure our entities match the pre-existing database schema.

In our database we generally create columns with the "serial" data type (i.e. int4 with a sequence), which generally creates sequences with a name in this format; [tablename]_[columnname]_seq.

E.g. :

{{COLUMN_NAME DATA_TYPE DATA_LENGTH DATA_PRECISION DATA_SCALE NULLABLE DATA_DEFAULT
----------------------------------- --------- ----------- -------------- ---------- -------- -----------------------------------
id int4 NO nextval('country_id_seq'::regclass) YES
name varchar 128 }}

In our JPA entity class, we have annotated this column like so:
{{@Id
@SequenceGenerator(name = "country_id_seq", sequenceName = "country_id_seq", allocationSize = 1)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator="country_id_seq")
@Column(name = "id", updatable = false)
public int getId()
{
return id;
}}}

However, when deploying our application with hibernate.hbm2ddl.auto="validate", it fails to validate correctly:

{{
org.hibernate.HibernateException: Missing sequence or table: `country_id_seq`
at org.hibernate.cfg.Configuration.validateSchema(Configuration.java:1352)
at org.hibernate.tool.hbm2ddl.SchemaValidator.validate(SchemaValidator.java:175)
at org.hibernate.internal.SessionFactoryImpl.<init>(SessionFactoryImpl.java:525)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1859)
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl$4.perform(EntityManagerFactoryBuilderImpl.java:857)
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl$4.perform(EntityManagerFactoryBuilderImpl.java:850)
at org.hibernate.boot.registry.classloading.internal.ClassLoaderServiceImpl.withTccl(ClassLoaderServiceImpl.java:425)
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:849)
at org.hibernate.jpa.HibernatePersistenceProvider.createEntityManagerFactory(HibernatePersistenceProvider.java:75)
at org.hibernate.ejb.HibernatePersistence.createEntityManagerFactory(HibernatePersistence.java:54)
at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:55)
}}

When debugging the postgres logs, it seems the following query gets run for the validation:

{{
SELECT
NULL AS TABLE_CAT,
n.nspname AS TABLE_SCHEM,
c.relname AS TABLE_NAME,
c.relkind,
CASE n.nspname ~ '^pg_'
OR n.nspname = 'information_schema'
WHEN true
THEN
CASE
WHEN n.nspname = 'pg_catalog'
OR n.nspname = 'information_schema'
THEN
CASE c.relkind
WHEN 'r'
THEN 'SYSTEM TABLE'
WHEN 'v'
THEN 'SYSTEM VIEW'
WHEN 'i'
THEN 'SYSTEM INDEX'
ELSE NULL
END
WHEN n.nspname = 'pg_toast'
THEN
CASE c.relkind
WHEN 'r'
THEN 'SYSTEM TOAST TABLE'
WHEN 'i'
THEN 'SYSTEM TOAST INDEX'
ELSE NULL
END
ELSE
CASE c.relkind
WHEN 'r'
THEN 'TEMPORARY TABLE'
WHEN 'i'
THEN 'TEMPORARY INDEX'
WHEN 'S'
THEN 'TEMPORARY SEQUENCE'
WHEN 'v'
THEN 'TEMPORARY VIEW'
ELSE NULL
END
END
WHEN false
THEN
CASE c.relkind
WHEN 'r'
THEN 'TABLE'
WHEN 'i'
THEN 'INDEX'
WHEN 'S'
THEN 'SEQUENCE'
WHEN 'v'
THEN 'VIEW'
WHEN 'c'
THEN 'TYPE'
ELSE NULL
END
ELSE NULL
END AS TABLE_TYPE,
d.description AS REMARKS
FROM
pg_catalog.pg_namespace n,
pg_catalog.pg_class c
LEFT JOIN pg_catalog.pg_description d
ON (c.oid = d.objoid
AND d.objsubid = 0)
LEFT JOIN pg_catalog.pg_class dc
ON (d.classoid=dc.oid
AND dc.relname='pg_class')
LEFT JOIN pg_catalog.pg_namespace dn
ON (dn.oid=dc.relnamespace
AND dn.nspname='pg_catalog')
WHERE
c.relnamespace = n.oid
AND c.relname LIKE 'country_id_seq'
AND (false
OR ( c.relkind = 'r'
AND n.nspname !~ '^pg_'
AND n.nspname <> 'information_schema' )
OR ( c.relkind = 'v'
AND n.nspname <> 'pg_catalog'
AND n.nspname <> 'information_schema' ) )
ORDER BY
TABLE_TYPE,
TABLE_SCHEM,
TABLE_NAME
}}

(formatted)

This query looks completely incorrect to me, as sequences have a "pg_class.relkind" of "S", yet the validation query for sequences filters by the "r" and "v" relkinds?

Activity

Show:

Steve Ebersole October 28, 2015 at 3:26 AM

As part of verifying that this issue affects 5.0, please just set the "Affects version". Leave the "verify-affects-5.0" label and leave the issue in "Awaiting Response" status; these are critical for us to be able to track these verifications and triage them. Thanks.

Steve Ebersole October 27, 2015 at 7:14 PM

This bug report does not indicate that the reported issue affects version 5.x. Versions prior to 5.x are no longer maintained. It would be a great help to the Hibernate team and community for someone to verify that the reported issue still affects version 5.x. If so, please add the 5.x version that you verified with to the list of affected-versions and attach the (preferably SSCCE) test case you used to do the verification to the report; from there the issues will be looked at during our triage meetings.

For details, see http://in.relation.to/2015/10/27/great-jira-cleanup-2015/

Simon Bracegirdle August 5, 2015 at 6:44 AM
Edited

Thanks for the detailed response.

I can confirm that changing hibernate.globally_quoted_identifiers to false fixes the problem for us too. We had this turned on because we have some table names that are case sensitive due to camel-casing (for many-to-many tables), however it appears that manually quoting those individual tables e.g. `applePear` in the @Table annotation allows us to work with global quoting turned off.

If I have some spare time in the near future I'll see if I can contribute a fix for this.

Alex Tunick August 4, 2015 at 12:02 PM

I had exactly the same issue while using hibernate.globally_quoted_identifiers=true with Postgres9.
The workaround I have is to set hibernate.globally_quoted_identifiers=false.

If I understand it correctly the logic is next.

When globally_quoted_identifiers is set to false it runs org.hibernate.tool.hbm2ddl.DatabaseMetadata.initSequences method at line 169,
then it runs into line 171 String sql = dialect.getQuerySequencesString(); and returns query select relname from pg_class where relkind='S' which seems to be ok.
Based on the results of that query it populates sequences object with the values at line 181 in a loop sequences.add(rs.getString(1).toLowerCase(Locale.ROOT).trim());.
So sequences object is populated with values like [ users_seq, roles_seq ]

Then it runs into org.hibernate.cfg.Configuration.iterateGenerators method at line 918.
After that it runs into org.hibernate.cfg.Configuration.generateSchemaUpdateScriptList where the last step is to iterate through generators at line 1304.
And here where the issue starts: condition if ( !databaseMetadata.isSequence( key ) && !databaseMetadata.isTable( key ) ) does not work correctly in case of hibernate.globally_quoted_identifiers=true however it works fine for hibernate.globally_quoted_identifiers=false.
The key value is "app"."users_seq" and databaseMetadata.isSequence( key ) returns false while it has to return true like this would be without quotes - databaseMetadata.isSequence( "app.users_seq" ) instead of databaseMetadata.isSequence( "\"app\".\"users_seq\"" ).
As you can see the issue is with the quotes and may be related to another opened issue HHH-8574.
And it must not run into line 1309 since I already have the sequence with that name but it cannot be found due to quotes.

The query mentioned in description of this issue is related to databaseMetadata.isTable method invocation, that is why the query must not be fixed since it will break isTable method.
So I believe the query is fine and it runs as a fallback because it could not find sequence due to quotes bug.

In case of globally_quoted_identifiers is set to false it is able to find all sequences and does not run the query you want to fix.

So in my opinion the fix has to be done inside isSequence method of org.hibernate.tool.hbm2ddl.DatabaseMetadata class.
The logic of isSequence method has to be smart about quotes at lines 198 and 199:

org.hibernate.tool.hbm2ddl.DatabaseMetadata.java

String[] strings = StringHelper.split(".", (String) key); return sequences.contains( strings[strings.length-1].toLowerCase(Locale.ROOT));

Since hibernate.globally_quoted_identifiers=false works for me as a solution for now I'm not going to make any code changes as for now.
Anybody who needs quotes turned on please go ahead or you can wait for my contribution until I need it )))

Simon Bracegirdle July 22, 2015 at 7:16 AM

Happy to contribute if anyone can give pointers as to the relevant classes to be looking in for this?

Details

Assignee

Reporter

Labels

Affects versions

Priority

Created July 15, 2015 at 6:08 AM
Updated February 26, 2016 at 3:28 PM