This is an automated email from the ASF dual-hosted git repository. borinquenkid pushed a commit to branch 8.0.x-hibernate7-dev in repository https://gitbox.apache.org/repos/asf/grails-core.git
commit 8b444d90bccd15c92da54daf84f7899e59c76be5 Author: Walter Duque de Estrada <[email protected]> AuthorDate: Wed Mar 11 14:12:37 2026 -0500 dbmigration: added specs for Generators --- grails-data-hibernate7/dbmigration/ISSUES.md | 41 +++++++++++ grails-data-hibernate7/dbmigration/build.gradle | 5 ++ .../GroovyDiffToChangeLogCommandStep.groovy | 2 +- .../snapshot/ForeignKeySnapshotGenerator.java | 64 +++++++---------- .../snapshot/HibernateSnapshotGenerator.java | 31 +++++++-- .../ext/hibernate/snapshot/AuctionEntities.groovy | 63 +++++++++++++++++ .../snapshot/CatalogSnapshotGeneratorSpec.groovy | 41 +++++++++++ .../ForeignKeySnapshotGeneratorSpec.groovy | 44 ++++++++++++ .../snapshot/HibernateSnapshotGeneratorSpec.groovy | 39 +++++++++++ .../HibernateSnapshotIntegrationSpec.groovy | 81 ++++++++++++++++++++++ .../snapshot/IndexSnapshotGeneratorSpec.groovy | 55 +++++++++++++++ .../PrimaryKeySnapshotGeneratorSpec.groovy | 45 ++++++++++++ .../snapshot/SchemaSnapshotGeneratorSpec.groovy | 41 +++++++++++ .../snapshot/SequenceSnapshotGeneratorSpec.groovy | 54 +++++++++++++++ .../snapshot/TableSnapshotGeneratorSpec.groovy | 56 +++++++++++++++ .../UniqueConstraintSnapshotGeneratorSpec.groovy | 54 +++++++++++++++ .../snapshot/ViewSnapshotGeneratorSpec.groovy | 41 +++++++++++ .../TableGeneratorSnapshotGeneratorSpec.groovy | 58 ++++++++++++++++ .../grails/rest/web/RespondMethodSpec.groovy | 1 + 19 files changed, 773 insertions(+), 43 deletions(-) diff --git a/grails-data-hibernate7/dbmigration/ISSUES.md b/grails-data-hibernate7/dbmigration/ISSUES.md new file mode 100644 index 0000000000..873d73651e --- /dev/null +++ b/grails-data-hibernate7/dbmigration/ISSUES.md @@ -0,0 +1,41 @@ +# Database Migration Issues (Hibernate 7) + +This document tracks technical challenges, API incompatibilities, and known test failures discovered during the development of the `grails-data-hibernate7-dbmigration` module. + +--- + +## ✅ RESOLVED + +### Snapshot Generator Specs — GORM Entity Recognition +- **Affected Tests:** `ForeignKeySnapshotGeneratorSpec`, `PrimaryKeySnapshotGeneratorSpec`, `TableSnapshotGeneratorSpec`, `IndexSnapshotGeneratorSpec`, `UniqueConstraintSnapshotGeneratorSpec`, `SequenceSnapshotGeneratorSpec` +- **Root Cause:** `HibernateMappingContext.createPersistentEntity()` only creates persistent entities for classes where `GormEntity.class.isAssignableFrom(javaClass)`. The original test entities were plain Java/Groovy classes annotated with `@jakarta.persistence.Entity`, which are silently skipped by GORM — resulting in empty Hibernate metadata (no table mappings). +- **Fix:** Replaced all inner-static-class entities (Jakarta `@Entity`) and `com.example.ejb3.auction` Java imports with top-level GORM `@grails.gorm.annotation.Entity` classes defined in the same `.groovy` spec file. All 6 tests now pass. + +### Envers Initialization +- **Issue:** `org.hibernate.HibernateException: Expecting EnversService to have been initialized prior to call to EnversIntegrator#integrate`. +- **Fix:** Explicitly disabled Envers in test config: `hibernate.integration.envers.enabled: false`. + +--- + +### `TableGeneratorSnapshotGeneratorSpec` — Real GORM Entity via `HibernateSnapshotIntegrationSpec` +- **Issue:** `TableGenerator.getTableName()` in Hibernate 7 is not interceptable by Spock's `Stub()`/`GroovyStub()` — the method accesses `this.qualifiedTableName` which is only set after `configure()` + `registerExportables()`. `BasicValue.getGenerator()` was also removed in Hibernate 7, breaking the original approach. +- **Fix:** Extended `HibernateSnapshotIntegrationSpec`, added a top-level GORM `@Entity TableGeneratorEntity` with `id generator: 'table'`, and retrieved the real `GrailsTableGenerator` via `datastore.sessionFactory.getMappingMetamodel().getEntityDescriptor(TableGeneratorEntity.name).getGenerator()`. Assertions use `tableGenerator.getTableName()` / `tableGenerator.getSegmentColumnName()` / `tableGenerator.getValueColumnName()` so they remain correct regardless of default param values. + +--- + +## ℹ️ BACKGROUND / OTHER NOTES + +### `BasicValue.getGenerator()` Removal (Hibernate 7) +- `org.hibernate.mapping.BasicValue.getGenerator()` was removed in Hibernate 7. +- Any code that previously extracted a generator from mapping metadata this way must be rewritten using `createGenerator(Dialect, RootClass, ...)` or by iterating namespace exportables. + +### `JdbcDatabaseSnapshot` Constructor +- Liquibase 4.x requires `JdbcDatabaseSnapshot(DatabaseObject[] examples, Database database)`. Tests use the correct constructor. + +### Testcontainers / Docker Dependency +- Integration tests require Docker (PostgreSQL container). Tests use `@Requires({ isDockerAvailable() })` to skip when Docker is absent. +- Checks both `~/.docker/run/docker.sock` and `/var/run/docker.sock`. + +### `GormDatabase` Service Loading +- Liquibase's `DatabaseFactory` tries to instantiate all registered `Database` implementations via no-arg constructor. `GormDatabase` requires a `HibernateDatastore`, so service-loader registration may cause issues in CLI contexts. Worked around in tests by manual instantiation. + diff --git a/grails-data-hibernate7/dbmigration/build.gradle b/grails-data-hibernate7/dbmigration/build.gradle index 3d4d094733..61a6e94bc1 100644 --- a/grails-data-hibernate7/dbmigration/build.gradle +++ b/grails-data-hibernate7/dbmigration/build.gradle @@ -78,10 +78,15 @@ dependencies { compileOnly 'org.springframework:spring-orm' testImplementation 'org.springframework.boot:spring-boot-starter-tomcat' + testImplementation project(':grails-data-hibernate7-core') testImplementation project(':grails-data-hibernate7') testImplementation project(':grails-core') testImplementation project(':grails-testing-support-datamapping') testImplementation project(':grails-testing-support-web') + testImplementation platform('org.testcontainers:testcontainers-bom:2.0.3') + testImplementation 'org.testcontainers:testcontainers' + testImplementation 'org.testcontainers:postgresql' + testImplementation 'org.testcontainers:spock' testImplementation 'com.h2database:h2' testImplementation 'org.hsqldb:hsqldb' testImplementation 'org.postgresql:postgresql' diff --git a/grails-data-hibernate7/dbmigration/src/main/groovy/org/grails/plugins/databasemigration/liquibase/GroovyDiffToChangeLogCommandStep.groovy b/grails-data-hibernate7/dbmigration/src/main/groovy/org/grails/plugins/databasemigration/liquibase/GroovyDiffToChangeLogCommandStep.groovy index f9c46a7fbe..84c7b7027e 100644 --- a/grails-data-hibernate7/dbmigration/src/main/groovy/org/grails/plugins/databasemigration/liquibase/GroovyDiffToChangeLogCommandStep.groovy +++ b/grails-data-hibernate7/dbmigration/src/main/groovy/org/grails/plugins/databasemigration/liquibase/GroovyDiffToChangeLogCommandStep.groovy @@ -79,7 +79,7 @@ class GroovyDiffToChangeLogCommandStep extends DiffChangelogCommandStep { return new String[][] { COMMAND_NAME } } - protected static DiffCommandStep createDiffCommandStep() { + protected DiffCommandStep createDiffCommandStep() { return new DiffCommandStep() } diff --git a/grails-data-hibernate7/dbmigration/src/main/java/liquibase/ext/hibernate/snapshot/ForeignKeySnapshotGenerator.java b/grails-data-hibernate7/dbmigration/src/main/java/liquibase/ext/hibernate/snapshot/ForeignKeySnapshotGenerator.java index 502ea80374..01f2882090 100644 --- a/grails-data-hibernate7/dbmigration/src/main/java/liquibase/ext/hibernate/snapshot/ForeignKeySnapshotGenerator.java +++ b/grails-data-hibernate7/dbmigration/src/main/java/liquibase/ext/hibernate/snapshot/ForeignKeySnapshotGenerator.java @@ -1,8 +1,5 @@ package liquibase.ext.hibernate.snapshot; -import java.util.Collection; - -import liquibase.diff.compare.DatabaseObjectComparatorFactory; import liquibase.exception.DatabaseException; import liquibase.ext.hibernate.database.HibernateDatabase; import liquibase.snapshot.DatabaseSnapshot; @@ -33,54 +30,45 @@ public class ForeignKeySnapshotGenerator extends HibernateSnapshotGenerator { return; } if (foundObject instanceof Table table) { - HibernateDatabase database = (HibernateDatabase) snapshot.getDatabase(); - MetadataImplementor metadata = (MetadataImplementor) database.getMetadata(); - - Collection<org.hibernate.mapping.Table> tmapp = metadata.collectTableMappings(); - for (org.hibernate.mapping.Table hibernateTable : tmapp) { - for (org.hibernate.mapping.ForeignKey hibernateForeignKey : hibernateTable.getForeignKeyCollection()) { - Table currentTable = new Table().setName(hibernateTable.getName()); - currentTable.setSchema(hibernateTable.getCatalog(), hibernateTable.getSchema()); + org.hibernate.mapping.Table hibernateTable = findHibernateTable(table, snapshot); + if (hibernateTable == null) { + return; + } + for (org.hibernate.mapping.ForeignKey hibernateForeignKey : hibernateTable.getForeignKeyCollection()) { + if (hibernateForeignKey.isCreationEnabled()) { org.hibernate.mapping.Table hibernateReferencedTable = hibernateForeignKey.getReferencedTable(); + Table referencedTable = new Table().setName(hibernateReferencedTable.getName()); referencedTable.setSchema( hibernateReferencedTable.getCatalog(), hibernateReferencedTable.getSchema()); - if (hibernateForeignKey.isCreationEnabled() && hibernateForeignKey.isPhysicalConstraint()) { - ForeignKey fk = new ForeignKey(); - fk.setName(hibernateForeignKey.getName()); - fk.setPrimaryKeyTable(referencedTable); - fk.setForeignKeyTable(currentTable); - for (Column column : hibernateForeignKey.getColumns()) { - fk.addForeignKeyColumn(new liquibase.structure.core.Column(column.getName())); - } - for (Column column : hibernateForeignKey.getReferencedColumns()) { - fk.addPrimaryKeyColumn(new liquibase.structure.core.Column(column.getName())); - } - if (fk.getPrimaryKeyColumns() == null - || fk.getPrimaryKeyColumns().isEmpty()) { + ForeignKey fk = new ForeignKey(); + fk.setName(hibernateForeignKey.getName()); + fk.setPrimaryKeyTable(referencedTable); + fk.setForeignKeyTable(table); + for (Column column : hibernateForeignKey.getColumns()) { + fk.addForeignKeyColumn(new liquibase.structure.core.Column(column.getName())); + } + for (Column column : hibernateForeignKey.getReferencedColumns()) { + fk.addPrimaryKeyColumn(new liquibase.structure.core.Column(column.getName())); + } + if (fk.getPrimaryKeyColumns() == null + || fk.getPrimaryKeyColumns().isEmpty()) { + if (hibernateReferencedTable.getPrimaryKey() != null) { for (Column column : hibernateReferencedTable.getPrimaryKey().getColumns()) { fk.addPrimaryKeyColumn(new liquibase.structure.core.Column(column.getName())); } } + } - fk.setDeferrable(false); - fk.setInitiallyDeferred(false); - - // Index index = new Index(); - // index.setName("IX_" + fk.getName()); - // index.setTable(fk.getForeignKeyTable()); - // index.setColumns(fk.getForeignKeyColumns()); - // fk.setBackingIndex(index); - // table.getIndexes().add(index); + fk.setDeferrable(false); + fk.setInitiallyDeferred(false); - if (DatabaseObjectComparatorFactory.getInstance() - .isSameObject(currentTable, table, null, database)) { - table.getOutgoingForeignKeys().add(fk); - table.getSchema().addDatabaseObject(fk); - } + table.getOutgoingForeignKeys().add(fk); + if (table.getSchema() != null) { + table.getSchema().addDatabaseObject(fk); } } } diff --git a/grails-data-hibernate7/dbmigration/src/main/java/liquibase/ext/hibernate/snapshot/HibernateSnapshotGenerator.java b/grails-data-hibernate7/dbmigration/src/main/java/liquibase/ext/hibernate/snapshot/HibernateSnapshotGenerator.java index bfc40d256a..676026e8d7 100644 --- a/grails-data-hibernate7/dbmigration/src/main/java/liquibase/ext/hibernate/snapshot/HibernateSnapshotGenerator.java +++ b/grails-data-hibernate7/dbmigration/src/main/java/liquibase/ext/hibernate/snapshot/HibernateSnapshotGenerator.java @@ -8,6 +8,7 @@ import liquibase.snapshot.InvalidExampleException; import liquibase.snapshot.SnapshotGenerator; import liquibase.snapshot.SnapshotGeneratorChain; import liquibase.structure.DatabaseObject; +import org.hibernate.boot.Metadata; import org.hibernate.boot.spi.MetadataImplementor; /** @@ -87,16 +88,38 @@ public abstract class HibernateSnapshotGenerator implements SnapshotGenerator { throws DatabaseException, InvalidExampleException; protected org.hibernate.mapping.Table findHibernateTable(DatabaseObject example, DatabaseSnapshot snapshot) { - var database = (HibernateDatabase) snapshot.getDatabase(); - var metadata = (MetadataImplementor) database.getMetadata(); + Metadata metadata = null; + Database database = snapshot.getDatabase(); + if (database instanceof HibernateDatabase hibernateDatabase) { + metadata = hibernateDatabase.getMetadata(); + } else { + try { + metadata = (Metadata) database.getClass().getMethod("getMetadata").invoke(database); + } catch (Exception e) { + // not a metadata-bearing database + } + } + + if (metadata == null) { + return null; + } - var tmapp = metadata.collectTableMappings(); + MetadataImplementor metadataImplementor = (MetadataImplementor) metadata; - for (var hibernateTable : tmapp) { + for (var hibernateTable : metadataImplementor.collectTableMappings()) { if (hibernateTable.getName().equalsIgnoreCase(example.getName())) { return hibernateTable; } } + + for (var namespace : metadataImplementor.getDatabase().getNamespaces()) { + for (var hibernateTable : namespace.getTables()) { + if (hibernateTable.getName().equalsIgnoreCase(example.getName())) { + return hibernateTable; + } + } + } + return null; } } diff --git a/grails-data-hibernate7/dbmigration/src/test/groovy/liquibase/ext/hibernate/snapshot/AuctionEntities.groovy b/grails-data-hibernate7/dbmigration/src/test/groovy/liquibase/ext/hibernate/snapshot/AuctionEntities.groovy new file mode 100644 index 0000000000..fbfbd8b6f5 --- /dev/null +++ b/grails-data-hibernate7/dbmigration/src/test/groovy/liquibase/ext/hibernate/snapshot/AuctionEntities.groovy @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package liquibase.ext.hibernate.snapshot + +import grails.gorm.annotation.Entity + +@Entity +class AuctionItem { + String description + String shortDescription + Date ends + Integer condition + + static hasMany = [bids: Bid] + + static constraints = { + description nullable: true, maxSize: 1000 + shortDescription nullable: true, maxSize: 200 + ends nullable: true + condition nullable: true + } +} + +@Entity +class Bid { + Float amount + Date datetime + + static belongsTo = [item: AuctionItem, bidder: AuctionUser] + + static constraints = { + datetime nullable: false + } +} + +@Entity +class AuctionUser { + String userName + String email + + static hasMany = [bids: Bid] + + static constraints = { + userName nullable: true + email nullable: true + } +} diff --git a/grails-data-hibernate7/dbmigration/src/test/groovy/liquibase/ext/hibernate/snapshot/CatalogSnapshotGeneratorSpec.groovy b/grails-data-hibernate7/dbmigration/src/test/groovy/liquibase/ext/hibernate/snapshot/CatalogSnapshotGeneratorSpec.groovy new file mode 100644 index 0000000000..f7a082446c --- /dev/null +++ b/grails-data-hibernate7/dbmigration/src/test/groovy/liquibase/ext/hibernate/snapshot/CatalogSnapshotGeneratorSpec.groovy @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package liquibase.ext.hibernate.snapshot + +import com.example.ejb3.auction.AuctionItem +import liquibase.structure.core.Catalog + +class CatalogSnapshotGeneratorSpec extends HibernateSnapshotIntegrationSpec { + + CatalogSnapshotGenerator generator = new CatalogSnapshotGenerator() + + @Override + List<Class> getEntityClasses() { + return [AuctionItem] + } + + def "snapshotObject returns default catalog"() { + when: + def result = generator.snapshotObject(new Catalog(), snapshot) + + then: + result instanceof Catalog + result.isDefault() + } +} diff --git a/grails-data-hibernate7/dbmigration/src/test/groovy/liquibase/ext/hibernate/snapshot/ForeignKeySnapshotGeneratorSpec.groovy b/grails-data-hibernate7/dbmigration/src/test/groovy/liquibase/ext/hibernate/snapshot/ForeignKeySnapshotGeneratorSpec.groovy new file mode 100644 index 0000000000..399f3b90c6 --- /dev/null +++ b/grails-data-hibernate7/dbmigration/src/test/groovy/liquibase/ext/hibernate/snapshot/ForeignKeySnapshotGeneratorSpec.groovy @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package liquibase.ext.hibernate.snapshot + +import liquibase.structure.core.ForeignKey +import liquibase.structure.core.Table + +class ForeignKeySnapshotGeneratorSpec extends HibernateSnapshotIntegrationSpec { + + ForeignKeySnapshotGenerator generator = new ForeignKeySnapshotGenerator() + + @Override + List<Class> getEntityClasses() { + return [AuctionItem, Bid, AuctionUser] + } + + def "addTo adds foreign keys to table"() { + given: + Table table = new Table(name: "Bid") + snapshot.getSnapshotControl().shouldInclude(ForeignKey) >> true + + when: + generator.addTo(table, snapshot) + + then: + table.getOutgoingForeignKeys().any { it.foreignKeyTable.name.equalsIgnoreCase("Bid") } + } +} diff --git a/grails-data-hibernate7/dbmigration/src/test/groovy/liquibase/ext/hibernate/snapshot/HibernateSnapshotGeneratorSpec.groovy b/grails-data-hibernate7/dbmigration/src/test/groovy/liquibase/ext/hibernate/snapshot/HibernateSnapshotGeneratorSpec.groovy new file mode 100644 index 0000000000..885901aaf4 --- /dev/null +++ b/grails-data-hibernate7/dbmigration/src/test/groovy/liquibase/ext/hibernate/snapshot/HibernateSnapshotGeneratorSpec.groovy @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package liquibase.ext.hibernate.snapshot + +import liquibase.ext.hibernate.database.HibernateDatabase +import liquibase.snapshot.DatabaseSnapshot +import liquibase.snapshot.SnapshotControl +import org.hibernate.boot.spi.MetadataImplementor +import spock.lang.Specification + +abstract class HibernateSnapshotGeneratorSpec extends Specification { + + DatabaseSnapshot snapshot = Mock() + HibernateDatabase database = Mock() + MetadataImplementor metadata = Mock() + SnapshotControl snapshotControl = Mock() + + def setup() { + snapshot.getDatabase() >> database + snapshot.getSnapshotControl() >> snapshotControl + database.getMetadata() >> metadata + } +} diff --git a/grails-data-hibernate7/dbmigration/src/test/groovy/liquibase/ext/hibernate/snapshot/HibernateSnapshotIntegrationSpec.groovy b/grails-data-hibernate7/dbmigration/src/test/groovy/liquibase/ext/hibernate/snapshot/HibernateSnapshotIntegrationSpec.groovy new file mode 100644 index 0000000000..0dab1a71d4 --- /dev/null +++ b/grails-data-hibernate7/dbmigration/src/test/groovy/liquibase/ext/hibernate/snapshot/HibernateSnapshotIntegrationSpec.groovy @@ -0,0 +1,81 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package liquibase.ext.hibernate.snapshot + +import liquibase.CatalogAndSchema +import liquibase.ext.hibernate.database.HibernateDatabase +import liquibase.snapshot.DatabaseSnapshot +import liquibase.snapshot.JdbcDatabaseSnapshot +import liquibase.structure.DatabaseObject +import org.grails.orm.hibernate.HibernateDatastore +import org.grails.plugins.databasemigration.liquibase.GormDatabase +import org.hibernate.boot.Metadata +import org.hibernate.dialect.PostgreSQLDialect +import org.testcontainers.containers.PostgreSQLContainer +import org.testcontainers.spock.Testcontainers +import spock.lang.AutoCleanup +import spock.lang.Requires +import spock.lang.Shared +import spock.lang.Specification + +@Testcontainers +@Requires({ isDockerAvailable() }) +abstract class HibernateSnapshotIntegrationSpec extends Specification { + + @Shared PostgreSQLContainer postgres = new PostgreSQLContainer("postgres:16") + + @AutoCleanup + HibernateDatastore datastore + HibernateDatabase database + DatabaseSnapshot snapshot + Metadata metadata + + def setup() { + Map config = [ + 'hibernate.dialect' : PostgreSQLDialect.class.getName(), + 'dataSource.url' : postgres.jdbcUrl, + 'dataSource.driverClassName' : postgres.driverClassName, + 'dataSource.username' : postgres.username, + 'dataSource.password' : postgres.password, + 'hibernate.hbm2ddl.auto' : 'create-drop', + 'hibernate.integration.envers.enabled': false + ] + + datastore = new HibernateDatastore(config, getEntityClasses() as Class[]) + metadata = datastore.getMetadata() + + database = new GormDatabase(new PostgreSQLDialect(), datastore) + + snapshot = new JdbcDatabaseSnapshot([] as DatabaseObject[], database) + } + + abstract List<Class> getEntityClasses() + + /** + * Returns true when a Docker daemon is reachable on this machine. + */ + static boolean isDockerAvailable() { + def candidates = [ + System.getProperty('user.home') + '/.docker/run/docker.sock', + '/var/run/docker.sock', + System.getenv('DOCKER_HOST') ?: '' + ] + candidates.any { it && new File(it).exists() } + } +} diff --git a/grails-data-hibernate7/dbmigration/src/test/groovy/liquibase/ext/hibernate/snapshot/IndexSnapshotGeneratorSpec.groovy b/grails-data-hibernate7/dbmigration/src/test/groovy/liquibase/ext/hibernate/snapshot/IndexSnapshotGeneratorSpec.groovy new file mode 100644 index 0000000000..f5039090a4 --- /dev/null +++ b/grails-data-hibernate7/dbmigration/src/test/groovy/liquibase/ext/hibernate/snapshot/IndexSnapshotGeneratorSpec.groovy @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package liquibase.ext.hibernate.snapshot + +import grails.gorm.annotation.Entity +import liquibase.structure.core.Index as LiquibaseIndex +import liquibase.structure.core.Table as LiquibaseTable + +class IndexSnapshotGeneratorSpec extends HibernateSnapshotIntegrationSpec { + + IndexSnapshotGenerator generator = new IndexSnapshotGenerator() + + @Override + List<Class> getEntityClasses() { + return [IndexedEntity] + } + + def "addTo adds indexes to table"() { + given: + LiquibaseTable table = new LiquibaseTable(name: "indexed_entity") + snapshot.getSnapshotControl().shouldInclude(LiquibaseIndex) >> true + + when: + generator.addTo(table, snapshot) + + then: + table.getIndexes().any { it.name.equalsIgnoreCase("idx_code") } + } +} + +@Entity +class IndexedEntity { + String code + + static mapping = { + table 'indexed_entity' + code index: 'idx_code' + } +} diff --git a/grails-data-hibernate7/dbmigration/src/test/groovy/liquibase/ext/hibernate/snapshot/PrimaryKeySnapshotGeneratorSpec.groovy b/grails-data-hibernate7/dbmigration/src/test/groovy/liquibase/ext/hibernate/snapshot/PrimaryKeySnapshotGeneratorSpec.groovy new file mode 100644 index 0000000000..87ae95410b --- /dev/null +++ b/grails-data-hibernate7/dbmigration/src/test/groovy/liquibase/ext/hibernate/snapshot/PrimaryKeySnapshotGeneratorSpec.groovy @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package liquibase.ext.hibernate.snapshot + +import liquibase.structure.core.PrimaryKey +import liquibase.structure.core.Table + +class PrimaryKeySnapshotGeneratorSpec extends HibernateSnapshotIntegrationSpec { + + PrimaryKeySnapshotGenerator generator = new PrimaryKeySnapshotGenerator() + + @Override + List<Class> getEntityClasses() { + return [AuctionItem, Bid, AuctionUser] + } + + def "addTo adds primary key to table"() { + given: + Table table = new Table(name: "auction_item") + snapshot.getSnapshotControl().shouldInclude(PrimaryKey) >> true + + when: + generator.addTo(table, snapshot) + + then: + table.primaryKey != null + table.primaryKey.columns*.name.contains("id") + } +} diff --git a/grails-data-hibernate7/dbmigration/src/test/groovy/liquibase/ext/hibernate/snapshot/SchemaSnapshotGeneratorSpec.groovy b/grails-data-hibernate7/dbmigration/src/test/groovy/liquibase/ext/hibernate/snapshot/SchemaSnapshotGeneratorSpec.groovy new file mode 100644 index 0000000000..bc9158e57b --- /dev/null +++ b/grails-data-hibernate7/dbmigration/src/test/groovy/liquibase/ext/hibernate/snapshot/SchemaSnapshotGeneratorSpec.groovy @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package liquibase.ext.hibernate.snapshot + +import com.example.ejb3.auction.AuctionItem +import liquibase.structure.core.Schema + +class SchemaSnapshotGeneratorSpec extends HibernateSnapshotIntegrationSpec { + + SchemaSnapshotGenerator generator = new SchemaSnapshotGenerator() + + @Override + List<Class> getEntityClasses() { + return [AuctionItem] + } + + def "snapshotObject returns default schema"() { + when: + def result = generator.snapshotObject(new Schema(), snapshot) + + then: + result instanceof Schema + result.isDefault() + } +} diff --git a/grails-data-hibernate7/dbmigration/src/test/groovy/liquibase/ext/hibernate/snapshot/SequenceSnapshotGeneratorSpec.groovy b/grails-data-hibernate7/dbmigration/src/test/groovy/liquibase/ext/hibernate/snapshot/SequenceSnapshotGeneratorSpec.groovy new file mode 100644 index 0000000000..d1a0755c5d --- /dev/null +++ b/grails-data-hibernate7/dbmigration/src/test/groovy/liquibase/ext/hibernate/snapshot/SequenceSnapshotGeneratorSpec.groovy @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package liquibase.ext.hibernate.snapshot + +import grails.gorm.annotation.Entity +import liquibase.structure.core.Schema +import liquibase.structure.core.Sequence as LiquibaseSequence + +class SequenceSnapshotGeneratorSpec extends HibernateSnapshotIntegrationSpec { + + SequenceSnapshotGenerator generator = new SequenceSnapshotGenerator() + + @Override + List<Class> getEntityClasses() { + return [SequenceEntity] + } + + def "addTo adds sequences from namespaces"() { + given: + Schema schema = new Schema() + snapshot.getSnapshotControl().shouldInclude(LiquibaseSequence) >> true + + when: + generator.addTo(schema, snapshot) + + then: + schema.getDatabaseObjects(LiquibaseSequence).any { it.name.equalsIgnoreCase("test_sequence") } + } +} + +@Entity +class SequenceEntity { + Long id + + static mapping = { + id generator: 'sequence', params: [sequence_name: 'test_sequence', allocationSize: 50] + } +} diff --git a/grails-data-hibernate7/dbmigration/src/test/groovy/liquibase/ext/hibernate/snapshot/TableSnapshotGeneratorSpec.groovy b/grails-data-hibernate7/dbmigration/src/test/groovy/liquibase/ext/hibernate/snapshot/TableSnapshotGeneratorSpec.groovy new file mode 100644 index 0000000000..d7fe2ab294 --- /dev/null +++ b/grails-data-hibernate7/dbmigration/src/test/groovy/liquibase/ext/hibernate/snapshot/TableSnapshotGeneratorSpec.groovy @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package liquibase.ext.hibernate.snapshot + +import liquibase.structure.core.Schema +import liquibase.structure.core.Table + +class TableSnapshotGeneratorSpec extends HibernateSnapshotIntegrationSpec { + + TableSnapshotGenerator generator = new TableSnapshotGenerator() + + @Override + List<Class> getEntityClasses() { + return [AuctionItem, Bid, AuctionUser] + } + + def "snapshotObject returns table with name"() { + given: + Table example = new Table(name: "auction_item") + + when: + def result = generator.snapshotObject(example, snapshot) + + then: + result instanceof Table + result.name == "auction_item" + } + + def "addTo adds tables to schema"() { + given: + Schema schema = new Schema() + snapshot.getSnapshotControl().shouldInclude(Table) >> true + + when: + generator.addTo(schema, snapshot) + + then: + schema.getDatabaseObjects(Table).any { it.name == "auction_item" } + } +} diff --git a/grails-data-hibernate7/dbmigration/src/test/groovy/liquibase/ext/hibernate/snapshot/UniqueConstraintSnapshotGeneratorSpec.groovy b/grails-data-hibernate7/dbmigration/src/test/groovy/liquibase/ext/hibernate/snapshot/UniqueConstraintSnapshotGeneratorSpec.groovy new file mode 100644 index 0000000000..5e9ae89c57 --- /dev/null +++ b/grails-data-hibernate7/dbmigration/src/test/groovy/liquibase/ext/hibernate/snapshot/UniqueConstraintSnapshotGeneratorSpec.groovy @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package liquibase.ext.hibernate.snapshot + +import grails.gorm.annotation.Entity +import liquibase.structure.core.Table +import liquibase.structure.core.UniqueConstraint + +class UniqueConstraintSnapshotGeneratorSpec extends HibernateSnapshotIntegrationSpec { + + UniqueConstraintSnapshotGenerator generator = new UniqueConstraintSnapshotGenerator() + + @Override + List<Class> getEntityClasses() { + return [UniqueEntity] + } + + def "addTo adds unique constraints to table"() { + given: + Table table = new Table(name: "unique_entity") + snapshot.getSnapshotControl().shouldInclude(UniqueConstraint) >> true + + when: + generator.addTo(table, snapshot) + + then: + table.getUniqueConstraints().any { it.columnNames.contains("code") } + } +} + +@Entity +class UniqueEntity { + String code + + static constraints = { + code unique: true + } +} diff --git a/grails-data-hibernate7/dbmigration/src/test/groovy/liquibase/ext/hibernate/snapshot/ViewSnapshotGeneratorSpec.groovy b/grails-data-hibernate7/dbmigration/src/test/groovy/liquibase/ext/hibernate/snapshot/ViewSnapshotGeneratorSpec.groovy new file mode 100644 index 0000000000..768618698e --- /dev/null +++ b/grails-data-hibernate7/dbmigration/src/test/groovy/liquibase/ext/hibernate/snapshot/ViewSnapshotGeneratorSpec.groovy @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package liquibase.ext.hibernate.snapshot + +import com.example.ejb3.auction.AuctionItem +import liquibase.exception.DatabaseException +import liquibase.structure.core.View + +class ViewSnapshotGeneratorSpec extends HibernateSnapshotIntegrationSpec { + + ViewSnapshotGenerator generator = new ViewSnapshotGenerator() + + @Override + List<Class> getEntityClasses() { + return [AuctionItem] + } + + def "snapshotObject throws exception as views are not supported"() { + when: + generator.snapshotObject(new View(), snapshot) + + then: + thrown(DatabaseException) + } +} diff --git a/grails-data-hibernate7/dbmigration/src/test/groovy/liquibase/ext/hibernate/snapshot/extension/TableGeneratorSnapshotGeneratorSpec.groovy b/grails-data-hibernate7/dbmigration/src/test/groovy/liquibase/ext/hibernate/snapshot/extension/TableGeneratorSnapshotGeneratorSpec.groovy new file mode 100644 index 0000000000..2d9e022482 --- /dev/null +++ b/grails-data-hibernate7/dbmigration/src/test/groovy/liquibase/ext/hibernate/snapshot/extension/TableGeneratorSnapshotGeneratorSpec.groovy @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package liquibase.ext.hibernate.snapshot.extension + +import grails.gorm.annotation.Entity +import liquibase.ext.hibernate.snapshot.HibernateSnapshotIntegrationSpec +import liquibase.structure.core.Table +import org.hibernate.id.enhanced.TableGenerator + +class TableGeneratorSnapshotGeneratorSpec extends HibernateSnapshotIntegrationSpec { + + TableGeneratorSnapshotGenerator generator = new TableGeneratorSnapshotGenerator() + + @Override + List<Class> getEntityClasses() { + return [TableGeneratorEntity] + } + + def "snapshot returns table with generator details"() { + given: + def persister = datastore.sessionFactory.getMappingMetamodel().getEntityDescriptor(TableGeneratorEntity.name) + def tableGenerator = persister.getGenerator() as TableGenerator + + when: + Table table = generator.snapshot(tableGenerator) + + then: + table.name == tableGenerator.getTableName() + table.getColumn(tableGenerator.getSegmentColumnName()) != null + table.getColumn(tableGenerator.getValueColumnName()) != null + table.primaryKey != null + } +} + +@Entity +class TableGeneratorEntity { + Long id + + static mapping = { + id generator: 'table' + } +} diff --git a/grails-test-suite-web/src/test/groovy/grails/rest/web/RespondMethodSpec.groovy b/grails-test-suite-web/src/test/groovy/grails/rest/web/RespondMethodSpec.groovy index 3a51e20b4a..0d45e8f5e3 100644 --- a/grails-test-suite-web/src/test/groovy/grails/rest/web/RespondMethodSpec.groovy +++ b/grails-test-suite-web/src/test/groovy/grails/rest/web/RespondMethodSpec.groovy @@ -292,6 +292,7 @@ class BookController { } @Entity class Book { + Long id String title static constraints = {
