diff --git a/docs/guide-rx/src/main/docs/gettingStarted/CRUD.adoc b/docs/guide-rx/src/main/docs/gettingStarted/CRUD.adoc
index c74d9e25ded..0bd824c0fec 100644
--- a/docs/guide-rx/src/main/docs/gettingStarted/CRUD.adoc
+++ b/docs/guide-rx/src/main/docs/gettingStarted/CRUD.adoc
@@ -60,7 +60,7 @@ Book.get(id)
}
----
-The `switchMap` transforms an `Observable` and converts the result into another `Observable`. However, this is not the most efficient way to perform updates as you can use https://gorm.grails.org/latest/hibernate/manual/index.html#whereQueries[where queries] to update an instance without retrieving it:
+The `switchMap` transforms an `Observable` and converts the result into another `Observable`. However, this is not the most efficient way to perform updates as you can use https://gorm.grails.org/latest/hibernate5/manual/index.html#whereQueries[where queries] to update an instance without retrieving it:
[source,groovy]
diff --git a/docs/guide-rx/src/main/docs/querying/dynamicFinders.adoc b/docs/guide-rx/src/main/docs/querying/dynamicFinders.adoc
index 2d4a4a7fdb6..c4ebe946d28 100644
--- a/docs/guide-rx/src/main/docs/querying/dynamicFinders.adoc
+++ b/docs/guide-rx/src/main/docs/querying/dynamicFinders.adoc
@@ -1,5 +1,5 @@
=== Dynamic Finders
-Although _Where queries_ are preferred, _Dynamic finders_ are another option for simple queries and are also very expressive. The https://gorm.grails.org/latest/hibernate/manual/index.html#finders[syntax for Dynamic finders is described in the GORM user guide]. The major difference in RxGORM is that all dynamic finders return an `rx.Observable`:
+Although _Where queries_ are preferred, _Dynamic finders_ are another option for simple queries and are also very expressive. The https://gorm.grails.org/latest/hibernate5/manual/index.html#finders[syntax for Dynamic finders is described in the GORM user guide]. The major difference in RxGORM is that all dynamic finders return an `rx.Observable`:
[source,groovy]
----
diff --git a/docs/guide-rx/src/main/docs/querying/index.adoc b/docs/guide-rx/src/main/docs/querying/index.adoc
index 9e7cbdd3362..b31bfe9c3b8 100644
--- a/docs/guide-rx/src/main/docs/querying/index.adoc
+++ b/docs/guide-rx/src/main/docs/querying/index.adoc
@@ -1,10 +1,10 @@
== Querying
RxGORM supports all the typical ways of querying that you are used to with GORM including:
-* https://gorm.grails.org/latest/hibernate/manual/index.html#finders[Dynamic finders]
-* https://gorm.grails.org/latest/hibernate/manual/index.html#whereQueries[Where queries]
-* https://gorm.grails.org/latest/hibernate/manual/index.html#detachedCriteria[Detached Criteria]
-* https://gorm.grails.org/latest/hibernate/manual/index.html#criteria[Criteria queries]
+* https://gorm.grails.org/latest/hibernate5/manual/index.html#finders[Dynamic finders]
+* https://gorm.grails.org/latest/hibernate5/manual/index.html#whereQueries[Where queries]
+* https://gorm.grails.org/latest/hibernate5/manual/index.html#detachedCriteria[Detached Criteria]
+* https://gorm.grails.org/latest/hibernate5/manual/index.html#criteria[Criteria queries]
The major difference is that all query operations return an `Observable`. In this section we will go through the various ways you can query for GORM objects.
diff --git a/docs/guide-rx/src/main/docs/querying/whereQueries.adoc b/docs/guide-rx/src/main/docs/querying/whereQueries.adoc
index b7d734aedbe..3d94c8d46ad 100644
--- a/docs/guide-rx/src/main/docs/querying/whereQueries.adoc
+++ b/docs/guide-rx/src/main/docs/querying/whereQueries.adoc
@@ -37,4 +37,4 @@ Book.where { title == 'The Stand' }
}
----
-For more information on the syntax of where queries see the https://gorm.grails.org/latest/hibernate/manual/index.html#whereQueries[relevant section in the GORM documentation].
+For more information on the syntax of where queries see the https://gorm.grails.org/latest/hibernate5/manual/index.html#whereQueries[relevant section in the GORM documentation].
diff --git a/gradle.properties b/gradle.properties
index aee53c6c69c..bd4aa553c7d 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,15 +1,26 @@
projectVersion=9.0.0-SNAPSHOT
-
grailsVersion=7.0.0-SNAPSHOT
javaVersion=17
asciidoctorVersion=4.0.4
+testcontainersMongodbVersion=1.20.1
commonsValidatorVersion=1.9.0
hibernateVersion=5.6.15.Final
+grailsSpringSecurityVersion=7.0.0-SNAPSHOT
+jbossTransactionApiVersion=2.0.0.Final
+liquibaseHibernate5Version=4.27.0
+yakworksHibernateGroovyProxyVersion=1.1
jakartaElVersion=4.0.0
jakartaElGlassfishImplVersion=4.0.2
javassistVersion=3.30.2-GA
+micronautPlatformVersion=4.6.3
+picocliVersion=4.7.6
+
+# This prevents the Grails Gradle Plugin from unnecessarily excluding slf4j-simple in the generated POMs
+# https://github.com/grails/grails-gradle-plugin/issues/222
+slf4jPreventExclusion=true
org.gradle.caching=true
org.gradle.parallel=true
org.gradle.daemon=true
+org.gradle.jvmargs=-Dfile.encoding=UTF-8 -Xmx1536M -XX:MaxMetaspaceSize=512M
diff --git a/gradle/documentation-config.gradle b/gradle/documentation-config.gradle
index 16436b758c3..abbc07ebdfd 100644
--- a/gradle/documentation-config.gradle
+++ b/gradle/documentation-config.gradle
@@ -56,10 +56,36 @@ tasks.register('data-mapping-groovydoc', Groovydoc) {
}
}
+tasks.register('copyMongodbDocs', Copy) {
+ dependsOn(':mongodb-docs:docs')
+
+ def mongodbSourceDir = project(':mongodb-docs').layout.buildDirectory.dir('docs')
+ def targetDir = rootProject.layout.buildDirectory.dir('docs/mongodb')
+
+ from(mongodbSourceDir)
+ into(targetDir)
+
+ inputs.dir(mongodbSourceDir)
+ outputs.dir(targetDir)
+}
+
+tasks.register('copyHibernate5Docs', Copy) {
+ dependsOn(':hibernate5-docs:docs')
+
+ def hibernate5SourceDir = project('::hibernate5-docs').layout.buildDirectory.dir('docs')
+ def targetDir = rootProject.layout.buildDirectory.dir('docs/hibernate5')
+
+ from(hibernate5SourceDir)
+ into(targetDir)
+
+ inputs.dir(hibernate5SourceDir)
+ outputs.dir(targetDir)
+}
+
tasks.register('docs') {
group = 'documentation'
dependsOn(':data-mapping-groovydoc')
- finalizedBy('copyRootWebsite','copyGuides')
+ finalizedBy('copyRootWebsite','copyGuides', 'copyMongodbDocs', 'copyHibernate5Docs')
}
tasks.register('copyRootWebsite', Copy) {
diff --git a/gradle/example-config.gradle b/gradle/example-config.gradle
new file mode 100644
index 00000000000..c94729090f4
--- /dev/null
+++ b/gradle/example-config.gradle
@@ -0,0 +1,3 @@
+tasks.withType(Groovydoc).configureEach {
+ enabled = false
+}
\ No newline at end of file
diff --git a/gradle/hibernate5-test-config.gradle b/gradle/hibernate5-test-config.gradle
new file mode 100644
index 00000000000..70a680c5a88
--- /dev/null
+++ b/gradle/hibernate5-test-config.gradle
@@ -0,0 +1,53 @@
+dependencies {
+ testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
+}
+
+tasks.withType(Test).configureEach {
+ onlyIf {
+ if (project.hasProperty('skipHibernate5Tests')) {
+ return false
+ }
+
+ if (project.hasProperty('onlyMongodbTests')) {
+ return false
+ }
+
+ if (project.hasProperty('onlyDatastoreTests')) {
+ return false
+ }
+
+ true
+ }
+
+ useJUnitPlatform()
+ systemProperty('hibernate5.gorm.suite', System.getProperty('hibernate5.gorm.suite') ?: true)
+ reports.html.required = !System.getenv('CI')
+ reports.junitXml.required = !System.getenv('CI')
+ testLogging {
+ events('passed', 'skipped', 'failed')
+ showExceptions = true
+ exceptionFormat = 'full'
+ showCauses = true
+ showStackTraces = true
+
+ // set options for log level DEBUG and INFO
+ debug {
+ events('started', 'passed', 'skipped', 'failed', 'standardOut', 'standardError')
+ exceptionFormat = 'full'
+ }
+ info.events = debug.events
+ info.exceptionFormat = debug.exceptionFormat
+ }
+ afterTest { desc, result ->
+ logger.quiet(' -- Executed test {} [{}] with result: {}', desc.name, desc.className, result.resultType)
+ }
+ afterSuite { desc, result ->
+ if (!desc.parent) { // will match the outermost suite
+ def output = "Results: ${result.resultType} (${result.testCount} tests, ${result.successfulTestCount} successes, ${result.failedTestCount} failures, ${result.skippedTestCount} skipped)"
+ def startItem = '| ', endItem = ' |'
+ def repeatLength = startItem.length() + output.length() + endItem.length()
+ def dashes = '-' * repeatLength
+ logger.quiet('\n{}\n{}{}{}\n{}', dashes, startItem, output, endItem, dashes)
+ }
+ }
+}
diff --git a/gradle/java-config.gradle b/gradle/java-config.gradle
index 7c2c0520fcc..b21e27e62aa 100644
--- a/gradle/java-config.gradle
+++ b/gradle/java-config.gradle
@@ -3,4 +3,12 @@ compileJava.options.release = javaVersion.toInteger()
java {
withSourcesJar()
withJavadocJar()
+}
+
+// work around for parallel builds due to https://github.com/bertramdev/asset-pipeline/issues/177
+if('assetCompile' in tasks.names) {
+ TaskProvider assetTask = tasks.named('assetCompile')
+ assetTask.configure { Task task ->
+ task.outputs.dir rootProject.layout.buildDirectory.dir('asset-serialize')
+ }
}
\ No newline at end of file
diff --git a/gradle/mongodb-test-config.gradle b/gradle/mongodb-test-config.gradle
new file mode 100644
index 00000000000..8fffc1c387a
--- /dev/null
+++ b/gradle/mongodb-test-config.gradle
@@ -0,0 +1,52 @@
+dependencies {
+ testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
+}
+
+tasks.named('compileTestGroovy', GroovyCompile) {
+ groovyOptions.forkOptions.jvmArgs = ['-Xmx768m']
+}
+
+tasks.withType(Test).configureEach {
+ onlyIf {
+ if (project.hasProperty('skipMongodbTests')) {
+ return false
+ }
+
+ if (project.hasProperty('onlyHibernate5Tests')) {
+ return false
+ }
+
+ if (project.hasProperty('onlyDatastoreTests')) {
+ return false
+ }
+
+ true
+ }
+
+ useJUnitPlatform()
+ maxParallelForks = 1
+ if (isCiBuild) {
+ forkEvery = 10
+ } else {
+ forkEvery = 30
+ }
+ jvmArgs = ['-Xmx1028M']
+ afterSuite {
+ System.out.print('.')
+ System.out.flush()
+ }
+ testLogging {
+ events('passed', 'skipped', 'failed')
+ exceptionFormat = 'full'
+ showCauses = true
+ showExceptions = true
+ showStackTraces = true
+ }
+ // Used in the TCK test to selectively enable/disable tests
+ systemProperty('mongodb.gorm.suite', 'true')
+}
+
+// Because the mongo tests do not use a container by default, the resource is exclusive and the tests must be exclusive
+tasks.withType(Test).configureEach { Task task ->
+ task.outputs.dir rootProject.layout.buildDirectory.dir('mongo-test-serialize')
+}
\ No newline at end of file
diff --git a/gradle/publish-config.gradle b/gradle/publish-config.gradle
index d4d9f4e0c23..1bdef059bac 100644
--- a/gradle/publish-config.gradle
+++ b/gradle/publish-config.gradle
@@ -1,5 +1,10 @@
import org.grails.gradle.plugin.publishing.GrailsPublishExtension
+if (project.hasProperty('snapshotPublishUrl')) {
+ ext.set('mavenPublishUrl', property('snapshotPublishUrl'))
+ logger.lifecycle('Configuring {}:{} snapshot publish repo: {}', group, findProperty('pomArtifactId') ?: name, mavenPublishUrl)
+}
+
extensions.configure(GrailsPublishExtension) {
// Explicit `it` is required here
it.githubSlug = 'grails/grails-data-mapping'
@@ -14,4 +19,6 @@ extensions.configure(GrailsPublishExtension) {
'puneetbehl': 'Puneet Behl',
'jamesfredley': 'James Fredley'
]
+ it.artifactId = findProperty('pomArtifactId') ?: name
+ it.publishTestSources = findProperty('pomPublishTestSources') ?: false
}
\ No newline at end of file
diff --git a/gradle/tck-config.gradle b/gradle/tck-config.gradle
index 994bc3ac401..e3389465926 100644
--- a/gradle/tck-config.gradle
+++ b/gradle/tck-config.gradle
@@ -1,53 +1,32 @@
dependencies {
+ if (!project.hasProperty('includeBaseTckClass') || project.findProperty('includeBaseTckClass')) {
+ testImplementation project(':grails-datastore-gorm-tck-base')
+ }
testImplementation project(':grails-datastore-gorm-tck')
+ testImplementation project(':grails-datastore-gorm-tck-domains')
}
-// We need to test against the TCK. Gradle cannot find/run tests from jars
-// without a lot of plumbing, so here we copy the class files from the TCK
-// project into this project's test classes dir so Gradle can find the test
-// classes and run them. See grails.gorm.tests.GormDatastoreSpec for on the TCK.
+// TODO: gradle will happily put the jar file on the classpath, but junit won't find the tests. Dynamic test discovery also won't find them.
+tasks.register('extractTckJar', Copy) {
+ dependsOn(configurations.testRuntimeClasspath)
+
+ Provider extractTarget = layout.buildDirectory.dir('extracted-tck-classes')
-// helper, used below.
-def toBaseClassRelativePathWithoutExtension = { String base, String classFile ->
- if (classFile.startsWith(base)) {
- def sansClass = classFile[0 .. classFile.size() - '.class'.size() - 1]
- def dollarIndex = sansClass.indexOf('$')
- def baseClass = dollarIndex > 0 ? sansClass[0..dollarIndex - 1] : sansClass
- def relative = baseClass - base - '/'
- relative
+ doFirst {
+ extractTarget.get().asFile.deleteDir()
}
- else {
- null
+
+ outputs.dir(extractTarget)
+ File tckJar = configurations.testRuntimeClasspath.resolve().find { it.name.startsWith("grails-datastore-gorm-tck-${projectVersion}") }
+ if (tckJar) {
+ from(zipTree(tckJar))
+ into(extractTarget)
}
}
-tasks.named('test') {
+tasks.withType(Test).configureEach {
+ dependsOn('extractTckJar')
doFirst {
- File tckClassesDir = findProject(':grails-datastore-gorm-tck').sourceSets.main.output.classesDirs.files.first()
- // surely there is a less hardcoded way to do this
- copy {
- from tckClassesDir
- into sourceSets.test.output.classesDirs.files.first()
- include '**/*.class'
- exclude { details ->
- // Do not copy across any TCK class (or nested classes of that class)
- // If there is a corresponding source file in the particular modules
- // test source tree. Allows a module to override a test/helper.
- if (!details.file.isFile()) {
- return false
- }
- def candidatePath = details.file.absolutePath
- def relativePath = toBaseClassRelativePathWithoutExtension(tckClassesDir.absolutePath, candidatePath)
- if (relativePath == null) {
- throw new IllegalStateException("$candidatePath does not appear to be in the TCK")
- }
- file("src/test/groovy/${relativePath}.groovy").exists()
- }
- }
+ testClassesDirs += files(layout.buildDirectory.dir('extracted-tck-classes'))
}
-}
-
-// Only enable to force snapshot dependency updates due to spring dependency management plugin snapshot caching behavior
-// configurations.all {
-// resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
-// }
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/gradle/test-config.gradle b/gradle/test-config.gradle
index 436ccecd72d..2903c645991 100644
--- a/gradle/test-config.gradle
+++ b/gradle/test-config.gradle
@@ -1,8 +1,28 @@
+dependencies {
+ testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
+}
+
tasks.named('compileTestGroovy', GroovyCompile) {
groovyOptions.forkOptions.jvmArgs = ['-Xmx768m']
}
tasks.withType(Test).configureEach {
+ onlyIf {
+ if (project.hasProperty('skipDatastoreTests')) {
+ return false
+ }
+
+ if (project.hasProperty('onlyMongodbTests')) {
+ return false
+ }
+
+ if (project.hasProperty('onlyHibernate5Tests')) {
+ return false
+ }
+
+ true
+ }
+
useJUnitPlatform()
testLogging {
events 'passed', 'skipped', 'failed'
diff --git a/grails-data-graphql/examples/spring-boot-app/build.gradle b/grails-data-graphql/examples/spring-boot-app/build.gradle
index 720f2c54a00..8d02f0297fc 100644
--- a/grails-data-graphql/examples/spring-boot-app/build.gradle
+++ b/grails-data-graphql/examples/spring-boot-app/build.gradle
@@ -1,12 +1,10 @@
buildscript {
- ext {
- springBootVersion = "2.1.1.RELEASE"
- }
repositories {
mavenCentral()
}
dependencies {
- classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
+ classpath platform("org.grails:grails-bom:$grailsVersion")
+ classpath("org.springframework.boot:spring-boot-gradle-plugin")
}
}
@@ -16,37 +14,34 @@ apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
dependencies {
+ implementation platform("org.grails:grails-bom:$grailsVersion")
+
implementation 'org.springframework.boot:spring-boot-starter'
implementation "org.springframework.boot:spring-boot-starter-web"
- implementation "org.grails:gorm-hibernate5-spring-boot:7.3.1"
+ implementation project(':hibernate5-boot-plugin')
implementation "org.hibernate:hibernate-core:$hibernateCoreVersion"
- implementation "org.hibernate:hibernate-ehcache:5.6.15.Final"
- implementation "org.grails:grails-core:5.3.2"
+ implementation "org.hibernate:hibernate-ehcache:$hibernateCoreVersion", {
+ // exclude javax variant of hibernate-core
+ exclude group: 'org.hibernate', module: 'hibernate-core'
+ }
+ implementation "org.grails:grails-core"
implementation project(":gorm-graphql")
- implementation 'org.codehaus.groovy:groovy-astbuilder:3.0.19'
- implementation 'jakarta.transaction:jakarta.transaction-api:2.0.1'
- implementation 'com.github.javaparser:javaparser-core:3.25.5'
+ //implementation 'org.codehaus.groovy:groovy-astbuilder:3.0.19'
+ implementation 'jakarta.transaction:jakarta.transaction-api'
+ implementation 'com.github.javaparser:javaparser-core'
implementation "com.graphql-java:graphql-java:$graphqlJavaVersion"
- implementation 'junit:junit:4.13.1'
- runtimeOnly 'com.h2database:h2:2.1.214'
- runtimeOnly 'org.apache.tomcat:tomcat-jdbc:10.1.9'
- runtimeOnly "org.apache.tomcat.embed:tomcat-embed-logging-log4j:8.5.0"
- implementation 'jakarta.persistence:jakarta.persistence-api:3.1.0'
+ implementation 'junit:junit'
+ runtimeOnly 'com.h2database:h2'
+ runtimeOnly 'org.apache.tomcat:tomcat-jdbc'
+ runtimeOnly "org.apache.tomcat.embed:tomcat-embed-logging-log4j"
+ implementation 'jakarta.persistence:jakarta.persistence-api'
- runtimeOnly "javax.el:javax.el-api:3.0.0"
- runtimeOnly "org.glassfish:javax.el:3.0.0"
+ runtimeOnly "javax.el:javax.el-api"
+ runtimeOnly "org.glassfish:javax.el"
testImplementation 'org.springframework.boot:spring-boot-starter-test'
- testImplementation 'org.spockframework:spock-core:2.3-groovy-3.0'
- testImplementation 'junit:junit:4.13.2'
-
-
-
-
-}
-
-repositories {
- mavenCentral()
+ testImplementation 'org.spockframework:spock-core'
+ testImplementation 'junit:junit'
}
tasks.withType(Test) {
diff --git a/grails-data-hibernate5/README.md b/grails-data-hibernate5/README.md
new file mode 100644
index 00000000000..8572b6441bc
--- /dev/null
+++ b/grails-data-hibernate5/README.md
@@ -0,0 +1,14 @@
+# GORM for Hibernate 5
+[](https://central.sonatype.com/artifact/org.grails/grails-datastore-gorm-hibernate5) [](https://github.com/grails/gorm-hibernate5/actions/workflows/gradle.yml)
+
+This project implements [GORM](https://gorm.grails.org) for the Hibernate 5.
+
+For more information see the following links:
+
+* [Documentation](https://gorm.grails.org/latest/hibernate/manual)
+* [API Reference](https://gorm.grails.org/latest/hibernate/api)
+
+For the current development version see the following links:
+
+* [Development Snapshot Documentation](https://gorm.grails.org/snapshot/hibernate/manual)
+* [Development Snapshot API Reference](https://gorm.grails.org/snapshot/hibernate/api)
\ No newline at end of file
diff --git a/grails-data-hibernate5/boot-plugin/build.gradle b/grails-data-hibernate5/boot-plugin/build.gradle
new file mode 100644
index 00000000000..4f00b8425f8
--- /dev/null
+++ b/grails-data-hibernate5/boot-plugin/build.gradle
@@ -0,0 +1,45 @@
+plugins {
+ id 'groovy'
+ id 'java-library'
+}
+
+version = projectVersion
+group = 'org.grails'
+
+ext {
+ apiDocs = true
+ snapshotPublishUrl = 'https://repo.grails.org/grails/libs-snapshots-local'
+ pomArtifactId = 'gorm-hibernate5-spring-boot'
+ pomDevelopers = [
+ 'graemerocher': 'Graeme Rocher',
+ 'jeffscottbrown': 'Jeff Brown',
+ 'burtbeckwith': 'Burt Beckwith',
+ 'puneetbehl': 'Puneet Behl',
+ ]
+}
+
+dependencies {
+ // TODO: Clarify and clean up dependencies
+ implementation platform("org.grails:grails-bom:$grailsVersion")
+
+ compileOnly "org.grails:grails-shell", {
+ exclude group:'org.apache.groovy', module:'groovy'
+ }
+ api "org.apache.groovy:groovy"
+ api "org.springframework.boot:spring-boot-autoconfigure"
+ api project(":hibernate5-gorm")
+
+ testImplementation "org.grails:grails-shell", {
+ exclude group:'org.apache.groovy', module:'groovy'
+ }
+ testImplementation "org.spockframework:spock-core"
+
+ testRuntimeOnly "org.apache.tomcat:tomcat-jdbc"
+ testRuntimeOnly "com.h2database:h2"
+}
+
+apply {
+ from rootProject.layout.projectDirectory.file('gradle/java-config.gradle')
+ from rootProject.layout.projectDirectory.file('gradle/hibernate5-test-config.gradle')
+ from rootProject.layout.projectDirectory.file('gradle/publish-config.gradle')
+}
diff --git a/grails-data-hibernate5/boot-plugin/src/main/groovy/org/grails/datastore/gorm/boot/autoconfigure/HibernateGormAutoConfiguration.groovy b/grails-data-hibernate5/boot-plugin/src/main/groovy/org/grails/datastore/gorm/boot/autoconfigure/HibernateGormAutoConfiguration.groovy
new file mode 100644
index 00000000000..392874122a4
--- /dev/null
+++ b/grails-data-hibernate5/boot-plugin/src/main/groovy/org/grails/datastore/gorm/boot/autoconfigure/HibernateGormAutoConfiguration.groovy
@@ -0,0 +1,140 @@
+/* Copyright (C) 2014 SpringSource
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.datastore.gorm.boot.autoconfigure
+
+import groovy.transform.CompileStatic
+import org.grails.datastore.gorm.events.ConfigurableApplicationContextEventPublisher
+import org.grails.datastore.mapping.services.Service
+import org.grails.orm.hibernate.HibernateDatastore
+import org.grails.orm.hibernate.cfg.HibernateMappingContextConfiguration
+import org.hibernate.SessionFactory
+import org.springframework.beans.BeansException
+import org.springframework.beans.factory.BeanFactory
+import org.springframework.beans.factory.BeanFactoryAware
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.beans.factory.config.ConfigurableBeanFactory
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory
+import org.springframework.beans.factory.support.BeanDefinitionRegistry
+import org.springframework.boot.autoconfigure.AutoConfigurationPackages
+import org.springframework.boot.autoconfigure.AutoConfigureAfter
+import org.springframework.boot.autoconfigure.AutoConfigureBefore
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
+import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
+import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration
+import org.springframework.context.ApplicationContext
+import org.springframework.context.ApplicationContextAware
+import org.springframework.context.ConfigurableApplicationContext
+import org.springframework.context.EnvironmentAware
+import org.springframework.context.ResourceLoaderAware
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.context.annotation.ImportBeanDefinitionRegistrar
+import org.springframework.core.env.Environment
+import org.springframework.core.io.ResourceLoader
+import org.springframework.core.type.AnnotationMetadata
+import org.springframework.transaction.PlatformTransactionManager
+
+import javax.sql.DataSource
+import java.beans.Introspector
+
+/**
+ * Auto configuration for GORM for Hibernate
+ *
+ * @author Graeme Rocher
+ * @since 1.0
+ */
+@CompileStatic
+@Configuration
+@ConditionalOnClass(HibernateMappingContextConfiguration)
+@ConditionalOnBean(DataSource)
+@ConditionalOnMissingBean(type = "grails.orm.bootstrap.HibernateDatastoreSpringInitializer")
+@AutoConfigureAfter(DataSourceAutoConfiguration)
+@AutoConfigureBefore([HibernateJpaAutoConfiguration])
+class HibernateGormAutoConfiguration implements ApplicationContextAware,BeanFactoryAware {
+
+ BeanFactory beanFactory
+
+ @Autowired(required = false)
+ DataSource dataSource
+
+ ConfigurableApplicationContext applicationContext
+
+ @Bean
+ HibernateDatastore hibernateDatastore() {
+ List packageNames = AutoConfigurationPackages.get(this.beanFactory)
+ List packages = []
+ for(name in packageNames) {
+ Package pkg = Package.getPackage(name)
+ if(pkg != null) {
+ packages.add(pkg)
+ }
+ }
+
+ ConfigurableListableBeanFactory beanFactory = applicationContext.beanFactory
+ HibernateDatastore datastore
+ if(dataSource == null) {
+ datastore = new HibernateDatastore(
+ applicationContext.getEnvironment(),
+ new ConfigurableApplicationContextEventPublisher(applicationContext),
+ packages as Package[]
+ )
+ beanFactory.registerSingleton("dataSource", datastore.getDataSource())
+ }
+ else {
+ datastore = new HibernateDatastore(
+ dataSource,
+ applicationContext.getEnvironment(),
+ new ConfigurableApplicationContextEventPublisher(applicationContext),
+ packages as Package[]
+ )
+ }
+
+ for(Service service in datastore.getServices()) {
+ Class serviceClass = service.getClass()
+ grails.gorm.services.Service ann = serviceClass.getAnnotation(grails.gorm.services.Service)
+ String serviceName = ann?.name()
+ if(serviceName == null) {
+ serviceName = Introspector.decapitalize(serviceClass.simpleName)
+ }
+ if(!applicationContext.containsBean(serviceName)) {
+ applicationContext.beanFactory.registerSingleton(
+ serviceName,
+ service
+ )
+ }
+ }
+ return datastore
+ }
+
+ @Bean
+ SessionFactory sessionFactory() {
+ hibernateDatastore().getSessionFactory()
+ }
+
+ @Bean
+ PlatformTransactionManager hibernateTransactionManager() {
+ hibernateDatastore().getTransactionManager()
+ }
+
+ @Override
+ void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+ if(!(applicationContext instanceof ConfigurableApplicationContext)) {
+ throw new IllegalArgumentException("Neo4jAutoConfiguration requires an instance of ConfigurableApplicationContext")
+ }
+ this.applicationContext = (ConfigurableApplicationContext)applicationContext
+ }
+}
diff --git a/grails-data-hibernate5/boot-plugin/src/main/groovy/org/grails/datastore/gorm/boot/compiler/GormCompilerAutoConfiguration.groovy b/grails-data-hibernate5/boot-plugin/src/main/groovy/org/grails/datastore/gorm/boot/compiler/GormCompilerAutoConfiguration.groovy
new file mode 100644
index 00000000000..384d27de067
--- /dev/null
+++ b/grails-data-hibernate5/boot-plugin/src/main/groovy/org/grails/datastore/gorm/boot/compiler/GormCompilerAutoConfiguration.groovy
@@ -0,0 +1,50 @@
+/* Copyright (C) 2014 SpringSource
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.datastore.gorm.boot.compiler
+
+import groovy.transform.CompileStatic
+import org.codehaus.groovy.ast.ClassNode
+import org.codehaus.groovy.control.CompilationFailedException
+import org.codehaus.groovy.control.customizers.ImportCustomizer
+import org.grails.cli.compiler.AstUtils
+import org.grails.cli.compiler.CompilerAutoConfiguration
+import org.grails.cli.compiler.DependencyCustomizer
+
+/**
+ * A compiler configuration that automatically adds the necessary imports
+ *
+ * @author Graeme Rocher
+ * @since 1.0
+ *
+ */
+@CompileStatic
+class GormCompilerAutoConfiguration extends CompilerAutoConfiguration{
+ @Override
+ boolean matches(ClassNode classNode) {
+ return AstUtils.hasAtLeastOneAnnotation(classNode, "grails.persistence.Entity", "grails.gorm.annotation.Entity" ,"Entity")
+ }
+
+ @Override
+ void applyDependencies(DependencyCustomizer dependencies) throws CompilationFailedException {
+ dependencies.ifAnyMissingClasses("grails.persistence.Entity", "grails.gorm.annotation.Entity")
+ .add("grails-datastore-gorm-hibernate5")
+ }
+
+ @Override
+ void applyImports(ImportCustomizer imports) throws CompilationFailedException {
+ imports.addStarImports("grails.gorm", "grails.gorm.annotation")
+ }
+}
\ No newline at end of file
diff --git a/grails-data-hibernate5/boot-plugin/src/main/resources/META-INF/services/org.grails.cli.compiler.CompilerAutoConfiguration b/grails-data-hibernate5/boot-plugin/src/main/resources/META-INF/services/org.grails.cli.compiler.CompilerAutoConfiguration
new file mode 100644
index 00000000000..2e0f07984fe
--- /dev/null
+++ b/grails-data-hibernate5/boot-plugin/src/main/resources/META-INF/services/org.grails.cli.compiler.CompilerAutoConfiguration
@@ -0,0 +1 @@
+org.grails.datastore.gorm.boot.compiler.GormCompilerAutoConfiguration
\ No newline at end of file
diff --git a/grails-data-hibernate5/boot-plugin/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/grails-data-hibernate5/boot-plugin/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 00000000000..d93153f929c
--- /dev/null
+++ b/grails-data-hibernate5/boot-plugin/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+org.grails.datastore.gorm.boot.autoconfigure.HibernateGormAutoConfiguration
diff --git a/grails-data-hibernate5/boot-plugin/src/test/groovy/org/grails/datastore/gorm/boot/autoconfigure/HibernateGormAutoConfigurationSpec.groovy b/grails-data-hibernate5/boot-plugin/src/test/groovy/org/grails/datastore/gorm/boot/autoconfigure/HibernateGormAutoConfigurationSpec.groovy
new file mode 100644
index 00000000000..c1998d38b04
--- /dev/null
+++ b/grails-data-hibernate5/boot-plugin/src/test/groovy/org/grails/datastore/gorm/boot/autoconfigure/HibernateGormAutoConfigurationSpec.groovy
@@ -0,0 +1,75 @@
+package org.grails.datastore.gorm.boot.autoconfigure
+
+import grails.gorm.annotation.Entity
+import org.springframework.beans.factory.support.DefaultListableBeanFactory
+import org.springframework.boot.autoconfigure.AutoConfigurationPackages
+import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration
+import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties
+import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration
+import org.springframework.context.annotation.AnnotationConfigApplicationContext
+import org.springframework.context.annotation.Configuration
+import org.springframework.context.annotation.Import
+import org.springframework.core.env.MapPropertySource
+import org.springframework.jdbc.datasource.DriverManagerDataSource
+import spock.lang.Ignore
+import spock.lang.Specification
+
+/**
+ * Created by graemerocher on 06/02/14.
+ */
+class HibernateGormAutoConfigurationSpec extends Specification{
+
+ protected AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
+
+ void cleanup() {
+ context.close()
+ }
+
+ void setup() {
+
+ AutoConfigurationPackages.register(context, "org.grails.datastore.gorm.boot.autoconfigure")
+ this.context.getEnvironment().getPropertySources().addFirst(new MapPropertySource("foo", ['hibernate.hbm2ddl.auto':'create']))
+ def beanFactory = this.context.defaultListableBeanFactory
+ beanFactory.registerSingleton("dataSource", new DriverManagerDataSource("jdbc:h2:mem:grailsDb1;LOCK_TIMEOUT=10000;DB_CLOSE_DELAY=-1", 'sa', ''))
+ this.context.register( TestConfiguration.class,
+ PropertyPlaceholderAutoConfiguration.class);
+ }
+
+ void 'Test that GORM is correctly configured'() {
+ when:"The context is refreshed"
+ context.refresh()
+
+ def result = Person.withTransaction {
+ Person.count()
+ }
+
+ then:"GORM queries work"
+ result == 0
+
+ when:"The addTo* methods are called"
+ def p = new Person()
+ p.addToChildren(firstName:"Bob")
+
+ then:"They work too"
+ p.children.size() == 1
+ }
+
+ @Configuration
+ @Import(HibernateGormAutoConfiguration)
+ static class TestConfiguration {
+ }
+
+}
+
+
+@Entity
+class Person {
+ String firstName
+ String lastName
+ Integer age = 18
+
+ Set children = []
+ static hasMany = [children: Person]
+}
+
+
diff --git a/grails-data-hibernate5/boot-plugin/src/test/groovy/org/springframework/bean/reader/GroovyBeanDefinitionReaderSpec.groovy b/grails-data-hibernate5/boot-plugin/src/test/groovy/org/springframework/bean/reader/GroovyBeanDefinitionReaderSpec.groovy
new file mode 100644
index 00000000000..298132c8021
--- /dev/null
+++ b/grails-data-hibernate5/boot-plugin/src/test/groovy/org/springframework/bean/reader/GroovyBeanDefinitionReaderSpec.groovy
@@ -0,0 +1,39 @@
+package org.springframework.bean.reader
+
+import org.springframework.beans.factory.InitializingBean
+import org.springframework.beans.factory.groovy.GroovyBeanDefinitionReader
+import org.springframework.context.annotation.AnnotationConfigApplicationContext
+import spock.lang.Specification
+
+/**
+ * Created by graemerocher on 06/02/14.
+ */
+class GroovyBeanDefinitionReaderSpec extends Specification{
+
+ protected AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
+
+ void setup() {
+ MyBean.blah = 'foo'
+ }
+
+ void "Test singletons are pre-instantiated with beans added by GroovyBeanDefinitionReader"() {
+ when:"The groovy reader is used"
+ def beanReader= new GroovyBeanDefinitionReader(context)
+ beanReader.beans {
+ myBean(MyBean)
+ }
+
+ context.refresh()
+
+ then:"The bean is pre instantiated"
+ MyBean.blah == 'created'
+ }
+}
+class MyBean implements InitializingBean{
+ static String blah = 'foo'
+
+ @Override
+ void afterPropertiesSet() throws Exception {
+ blah = 'created'
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/README.md b/grails-data-hibernate5/database-migration/README.md
new file mode 100644
index 00000000000..8a00f73f01f
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/README.md
@@ -0,0 +1,51 @@
+[](https://github.com/grails/grails-database-migration/actions/workflows/gradle.yml)
+
+# Grails Database Migration Plugin
+
+## Branches
+
+**9.0.x** Version of the plugin compatible with Grails 7 and Liquibase 4.27
+
+**5.0.x** Version of the plugin compatible with Grails 6 and Liquibase 4.19
+
+**4.0.x** Version of the plugin compatible with Grails 5 and Liquibase 4.6
+
+**3.x** Version of the plugin compatible with Grails 3 / 4 and Hibernate 5.
+
+**2.x**. Version of the plugin compatible with Grails 3 and Hibernate 4.
+
+**1.x** There is a 1.x branch for on-going maintenance of 1.x versions of the plugin compatible with Grails 2.
+
+Please submit any pull requests to the appropriate branch.
+
+Changes to the 1.x branch or 2.x branch will be merged into the master branch if appropriate.
+
+## Overview
+
+The Database Migration plugin helps you manage database changes while developing Grails applications. The plugin uses the Liquibase library. Using this plugin (and Liquibase in general) adds some structure and process to managing database changes. It will help avoid inconsistencies, communication issues, and other problems with ad-hoc approaches.
+
+Database migrations are represented in text form, either using a Groovy DSL or native Liquibase XML, in one or more changelog files. This approach makes it natural to maintain the changelog files in source control and also works well with branches. Changelog files can include other changelog files, so often developers create hierarchical files organized with various schemes.
+One popular approach is to have a root changelog named changlog.groovy (or changelog.xml) and to include a changelog per feature/branch that includes multiple smaller changelogs. Once the feature is finished and merged into the main development tree/trunk the changelog files can either stay as they are or be merged into one large file. Use whatever approach makes sense for your applications, but keep in mind that there are many options available for changelog management.
+
+## Versions
+* 1.x: Grails 2
+* 2.x: Grails 3 with Hibernate 4
+* 3.x: Grails 3 with Hibernate 5
+* 4.0.x Grails 5
+* 5.0.x Grails 6
+* 9.0.x Grails 7
+
+## Documentation
+* Latest https://gorm.grails.org/latest/hibernate/manual/#_grails_database_migration_plugin
+* Grails 2: https://grails.github.io/grails-database-migration/docs/manual/index.html
+* Grails 3 (Hibernate 4): https://grails.github.io/grails-database-migration/2.0.x/index.html
+* Grails 3/4 (Hibernate 5): https://grails.github.io/grails-database-migration/3.0.x/index.html
+* Grails 5 (Hibernate 5): https://grails.github.io/grails-database-migration/4.0.x/index.html
+* Grails 6 (Hibernate 5): https://grails.github.io/grails-database-migration/5.0.x/index.html
+* Grails 7 (Hibernate 5): https://gorm.grails.org/9.0.x/hibernate/manual/#_grails_database_migration_plugin
+* Snapshot: https://gorm.grails.org/snapshot/hibernate/manual/#_grails_database_migration_plugin
+
+
+## Package distribution
+
+Software is distributed on [Maven Central](https://mvnrepository.com/artifact/org.grails.plugins/database-migration)
diff --git a/grails-data-hibernate5/database-migration/build.gradle b/grails-data-hibernate5/database-migration/build.gradle
new file mode 100644
index 00000000000..9a86db88b5f
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/build.gradle
@@ -0,0 +1,74 @@
+plugins {
+ id 'java-library'
+ id 'org.grails.grails-plugin'
+}
+
+version = projectVersion
+group = 'org.grails.plugins'
+
+ext {
+ apiDocs = true
+ pomArtifactId = 'database-migration'
+ pomTitle = 'Grails Database Migration Plugin'
+ pomDescription = 'The Database Migration plugin helps you manage database changes, via Liquibase, while developing Grails applications'
+ pomDevelopers = [
+ 'kazukiyamamoto': 'Kazuki YAMAMOTO',
+ 'jamesfredley': 'James Fredley',
+ ]
+ snapshotPublishUrl = 'https://repo.grails.org/grails/plugins3-snapshots-local'
+}
+
+dependencies {
+ // TODO: Clarify and clean up dependencies
+ implementation platform("org.grails:grails-bom:$grailsVersion")
+
+ api("org.liquibase:liquibase-core:$liquibaseHibernate5Version")
+ api("org.liquibase.ext:liquibase-hibernate5:$liquibaseHibernate5Version") {
+ exclude group: 'org.hibernate', module: 'hibernate-core'
+ exclude group: 'org.hibernate', module: 'hibernate-entitymanager'
+ exclude group: 'org.hibernate', module: 'hibernate-envers'
+ exclude group: 'com.h2database', module: 'h2'
+ exclude group: 'org.liquibase', module: 'liquibase-commercial'
+ }
+
+ api('org.grails:grails-shell') {
+ exclude group: 'org.slf4j', module: 'slf4j-simple'
+ }
+
+ compileOnly "org.springframework.boot:spring-boot-starter-logging"
+ compileOnly "org.springframework.boot:spring-boot-autoconfigure"
+ compileOnly project(":hibernate5-grails-plugin")
+ compileOnly "org.grails:grails-core"
+ compileOnly "org.apache.groovy:groovy-sql"
+ compileOnly "org.apache.groovy:groovy-xml"
+
+ testImplementation "org.springframework.boot:spring-boot-starter-tomcat"
+ testImplementation project(":hibernate5-grails-plugin")
+ testImplementation "org.grails:grails-core"
+ testImplementation project(":grails-gorm-testing-support")
+ testImplementation "org.grails:grails-web-testing-support"
+ testImplementation "com.h2database:h2"
+}
+
+tasks.named('jar', Jar) {
+ exclude('testapp/**/**')
+}
+
+tasks.withType(Test).configureEach {
+ develocity.testRetry {
+ if (isCiBuild) {
+ maxRetries = 2
+ maxFailures = 20
+ failOnPassedAfterRetry = true
+ filter {
+ excludeClasses.add('*.GroovyChangeLogSpec')
+ }
+ }
+ }
+}
+
+apply {
+ from rootProject.layout.projectDirectory.file('gradle/java-config.gradle')
+ from rootProject.layout.projectDirectory.file('gradle/hibernate5-test-config.gradle')
+ from rootProject.layout.projectDirectory.file('gradle/publish-config.gradle')
+}
diff --git a/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmChangelogSyncCommand.groovy b/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmChangelogSyncCommand.groovy
new file mode 100644
index 00000000000..01dc3718940
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmChangelogSyncCommand.groovy
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2015 original authors
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration.command
+
+import grails.dev.commands.ApplicationCommand
+import groovy.transform.CompileStatic
+import liquibase.Liquibase
+
+@CompileStatic
+class DbmChangelogSyncCommand implements ApplicationCommand, ApplicationContextDatabaseMigrationCommand {
+
+ final String description = 'Mark all changes as executed in the database'
+
+ void handle() {
+ withLiquibase { Liquibase liquibase ->
+ liquibase.changeLogSync(contexts)
+ }
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmChangelogSyncSqlCommand.groovy b/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmChangelogSyncSqlCommand.groovy
new file mode 100644
index 00000000000..f1184f9e658
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmChangelogSyncSqlCommand.groovy
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2015 original authors
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration.command
+
+import grails.dev.commands.ApplicationCommand
+import groovy.transform.CompileStatic
+import liquibase.Liquibase
+
+@CompileStatic
+class DbmChangelogSyncSqlCommand implements ApplicationCommand, ApplicationContextDatabaseMigrationCommand {
+
+ final String description = 'Writes the SQL that will mark all changes as executed in the database to STDOUT or a file'
+
+ void handle() {
+ def filename = args[0]
+
+ withLiquibase { Liquibase liquibase ->
+ withFileOrSystemOutWriter(filename) { Writer writer ->
+ liquibase.changeLogSync(contexts, writer)
+ }
+ }
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmClearChecksumsCommand.groovy b/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmClearChecksumsCommand.groovy
new file mode 100644
index 00000000000..2f391a42bf8
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmClearChecksumsCommand.groovy
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2015 original authors
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration.command
+
+import grails.dev.commands.ApplicationCommand
+import groovy.transform.CompileStatic
+import liquibase.Liquibase
+
+@CompileStatic
+class DbmClearChecksumsCommand implements ApplicationCommand, ApplicationContextDatabaseMigrationCommand {
+
+ final String description = 'Removes current checksums from database. On next run checksums will be recomputed'
+
+ void handle() {
+ withLiquibase { Liquibase liquibase ->
+ liquibase.clearCheckSums()
+ }
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmDbDocCommand.groovy b/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmDbDocCommand.groovy
new file mode 100644
index 00000000000..516134099fd
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmDbDocCommand.groovy
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2015 original authors
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration.command
+
+import grails.dev.commands.ApplicationCommand
+import groovy.transform.CompileStatic
+import liquibase.Liquibase
+
+@CompileStatic
+class DbmDbDocCommand implements ApplicationCommand, ApplicationContextDatabaseMigrationCommand {
+
+ final String description = 'Generates Javadoc-like documentation based on current database and change log'
+
+ void handle() {
+ def destination = args[0] ?: config.getProperty((String) "${configPrefix}.dbDocLocation", String) ?: 'build/dbdoc'
+ withLiquibase { Liquibase liquibase ->
+ liquibase.generateDocumentation(destination, contexts)
+ }
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmDiffCommand.groovy b/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmDiffCommand.groovy
new file mode 100644
index 00000000000..e2022b93f03
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmDiffCommand.groovy
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2015 original authors
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration.command
+
+import grails.dev.commands.ApplicationCommand
+import grails.util.Environment
+import groovy.transform.CompileStatic
+import liquibase.database.Database
+import org.grails.plugins.databasemigration.DatabaseMigrationException
+
+@CompileStatic
+class DbmDiffCommand implements ApplicationCommand, ApplicationContextDatabaseMigrationCommand {
+
+ final String description = 'Compares two databases and creates a changelog that will make the changes required to bring them into sync'
+
+ void handle() {
+ def otherEnv = args[0]
+ if (!otherEnv) {
+ throw new DatabaseMigrationException('You must specify the environment to diff against')
+ }
+ if (Environment.getEnvironment(otherEnv) == Environment.current || otherEnv == Environment.current.name) {
+ throw new DatabaseMigrationException('You must specify a different environment than the one the command is running in')
+ }
+
+ def filename = args[1]
+
+ def outputChangeLogFile = resolveChangeLogFile(filename)
+ if (outputChangeLogFile) {
+ if (outputChangeLogFile.exists()) {
+ if (hasOption('force')) {
+ outputChangeLogFile.delete()
+ } else {
+ throw new DatabaseMigrationException("ChangeLogFile ${outputChangeLogFile} already exists!")
+ }
+ }
+ if (!outputChangeLogFile.parentFile.exists()) {
+ outputChangeLogFile.parentFile.mkdirs()
+ }
+ }
+
+ withDatabase { Database referenceDatabase ->
+ withDatabase(getDataSourceConfig(getEnvironmentConfig(otherEnv))) { Database targetDatabase ->
+ doDiffToChangeLog(outputChangeLogFile, referenceDatabase, targetDatabase)
+ }
+ }
+
+ if (outputChangeLogFile && hasOption('add')) {
+ appendToChangeLog(changeLogFile, outputChangeLogFile)
+ }
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmDropAllCommand.groovy b/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmDropAllCommand.groovy
new file mode 100644
index 00000000000..7db7704fb1e
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmDropAllCommand.groovy
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2015 original authors
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration.command
+
+import grails.dev.commands.ApplicationCommand
+import groovy.transform.CompileStatic
+import liquibase.CatalogAndSchema
+import liquibase.Liquibase
+
+@CompileStatic
+class DbmDropAllCommand implements ApplicationCommand, ApplicationContextDatabaseMigrationCommand {
+
+ final String description = 'Drops all database objects owned by the user'
+
+ void handle() {
+ def schemaNames = args[0]
+ def schemas = schemaNames?.split(',')?.collect { String schemaName -> new CatalogAndSchema(null, schemaName) }
+
+ withLiquibase { Liquibase liquibase ->
+ if (schemas) {
+ liquibase.dropAll(schemas as CatalogAndSchema[])
+ } else {
+ liquibase.dropAll()
+ }
+ }
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmFutureRollbackCountSqlCommand.groovy b/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmFutureRollbackCountSqlCommand.groovy
new file mode 100644
index 00000000000..8f0f8214676
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmFutureRollbackCountSqlCommand.groovy
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2015 original authors
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration.command
+
+import grails.dev.commands.ApplicationCommand
+import groovy.transform.CompileStatic
+import liquibase.Liquibase
+import org.grails.plugins.databasemigration.DatabaseMigrationException
+
+@CompileStatic
+class DbmFutureRollbackCountSqlCommand implements ApplicationCommand, ApplicationContextDatabaseMigrationCommand {
+
+ final String description = 'Writes SQL to roll back the database to the current state after changes in the changeslog have been applied'
+
+ @Override
+ void handle() {
+ def number = args[0]
+ if (!number) {
+ throw new DatabaseMigrationException("The $name command requires a change set number argument")
+ }
+ if (!number.isNumber()) {
+ throw new DatabaseMigrationException("The change set number argument '$number' isn't a number")
+ }
+
+ def filename = args[1]
+
+ withLiquibase { Liquibase liquibase ->
+ withFileOrSystemOutWriter(filename) { Writer writer ->
+ liquibase.futureRollbackSQL(number.toInteger(), contexts, writer)
+ }
+ }
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmFutureRollbackSqlCommand.groovy b/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmFutureRollbackSqlCommand.groovy
new file mode 100644
index 00000000000..078b7d74bc4
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmFutureRollbackSqlCommand.groovy
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2015 original authors
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration.command
+
+import grails.dev.commands.ApplicationCommand
+import groovy.transform.CompileStatic
+import liquibase.Liquibase
+
+@CompileStatic
+class DbmFutureRollbackSqlCommand implements ApplicationCommand, ApplicationContextDatabaseMigrationCommand {
+
+ final String description = 'Writes SQL to roll back the database to the current state after the changes in the changeslog have been applied'
+
+ @Override
+ void handle() {
+ def filename = args[0]
+
+ withLiquibase { Liquibase liquibase ->
+ withFileOrSystemOutWriter(filename) { Writer writer ->
+ liquibase.futureRollbackSQL(contexts, writer)
+ }
+ }
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmGenerateChangelogCommand.groovy b/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmGenerateChangelogCommand.groovy
new file mode 100644
index 00000000000..4995d79ef62
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmGenerateChangelogCommand.groovy
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2015 original authors
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration.command
+
+import grails.dev.commands.ApplicationCommand
+import groovy.transform.CompileStatic
+import liquibase.database.Database
+import org.grails.plugins.databasemigration.DatabaseMigrationException
+
+@CompileStatic
+class DbmGenerateChangelogCommand implements ApplicationCommand, ApplicationContextDatabaseMigrationCommand {
+
+ final String description = 'Generates an initial changelog XML or Groovy DSL file from the database'
+
+ void handle() {
+ def filename = args[0]
+
+ def outputChangeLogFile = resolveChangeLogFile(filename)
+ if (outputChangeLogFile) {
+ if (outputChangeLogFile.exists()) {
+ if (hasOption('force')) {
+ outputChangeLogFile.delete()
+ } else {
+ throw new DatabaseMigrationException("ChangeLogFile ${outputChangeLogFile} already exists!")
+ }
+ }
+ if (!outputChangeLogFile.parentFile.exists()) {
+ outputChangeLogFile.parentFile.mkdirs()
+ }
+ }
+
+ withDatabase { Database database ->
+ doGenerateChangeLog(outputChangeLogFile, database)
+ }
+
+ if (outputChangeLogFile && hasOption('add')) {
+ appendToChangeLog(changeLogFile, outputChangeLogFile)
+ }
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmGenerateGormChangelogCommand.groovy b/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmGenerateGormChangelogCommand.groovy
new file mode 100644
index 00000000000..355a6ac1151
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmGenerateGormChangelogCommand.groovy
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2015 original authors
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration.command
+
+import grails.dev.commands.ApplicationCommand
+import groovy.transform.CompileStatic
+import liquibase.database.Database
+import org.grails.plugins.databasemigration.DatabaseMigrationException
+
+@CompileStatic
+class DbmGenerateGormChangelogCommand implements ApplicationCommand, ApplicationContextDatabaseMigrationCommand {
+
+ final String description = 'Generates an initial changelog XML or Groovy DSL file from current GORM classes'
+
+ @Override
+ void handle() {
+ def filename = args[0]
+
+ def outputChangeLogFile = resolveChangeLogFile(filename)
+ if (outputChangeLogFile) {
+ if (outputChangeLogFile.exists()) {
+ if (hasOption('force')) {
+ outputChangeLogFile.delete()
+ } else {
+ throw new DatabaseMigrationException("ChangeLogFile ${outputChangeLogFile} already exists!")
+ }
+ }
+ if (!outputChangeLogFile.parentFile.exists()) {
+ outputChangeLogFile.parentFile.mkdirs()
+ }
+ }
+
+ withGormDatabase(applicationContext, dataSource) { Database database ->
+ doGenerateChangeLog(outputChangeLogFile, database)
+ }
+
+ if (outputChangeLogFile && hasOption('add')) {
+ appendToChangeLog(changeLogFile, outputChangeLogFile)
+ }
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmGormDiffCommand.groovy b/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmGormDiffCommand.groovy
new file mode 100644
index 00000000000..e35cd413418
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmGormDiffCommand.groovy
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2015 original authors
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration.command
+
+import grails.dev.commands.ApplicationCommand
+import groovy.transform.CompileStatic
+import liquibase.database.Database
+import org.grails.plugins.databasemigration.DatabaseMigrationException
+
+@CompileStatic
+class DbmGormDiffCommand implements ApplicationCommand, ApplicationContextDatabaseMigrationCommand {
+
+ final String description = 'Diffs GORM classes against a database and generates a changelog XML or Groovy DSL file'
+
+ @Override
+ void handle() {
+ def filename = args[0]
+ def outputChangeLogFile = resolveChangeLogFile(filename)
+ if (outputChangeLogFile) {
+ if (outputChangeLogFile.exists()) {
+ if (hasOption('force')) {
+ outputChangeLogFile.delete()
+ } else {
+ throw new DatabaseMigrationException("ChangeLogFile ${outputChangeLogFile} already exists!")
+ }
+ }
+ if (!outputChangeLogFile.parentFile.exists()) {
+ outputChangeLogFile.parentFile.mkdirs()
+ }
+ }
+
+ withGormDatabase(applicationContext, dataSource) { Database referenceDatabase ->
+ withDatabase { Database targetDatabase ->
+ doDiffToChangeLog(outputChangeLogFile, referenceDatabase, targetDatabase)
+ }
+ }
+
+ if (outputChangeLogFile && hasOption('add')) {
+ appendToChangeLog(changeLogFile, outputChangeLogFile)
+ }
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmListLocksCommand.groovy b/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmListLocksCommand.groovy
new file mode 100644
index 00000000000..1fef8c82281
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmListLocksCommand.groovy
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2015 original authors
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration.command
+
+import grails.dev.commands.ApplicationCommand
+import groovy.transform.CompileStatic
+import groovy.transform.stc.ClosureParams
+import groovy.transform.stc.SimpleType
+import liquibase.Liquibase
+
+@CompileStatic
+class DbmListLocksCommand implements ApplicationCommand, ApplicationContextDatabaseMigrationCommand {
+
+ final String description = 'Lists who currently has locks on the database changelog to STDOUT or a file'
+
+ void handle() {
+ def filename = args[0]
+
+ withLiquibase { Liquibase liquibase ->
+ withFilePrintStreamOrSystemOut(filename) { PrintStream printStream ->
+ liquibase.reportLocks(printStream)
+ }
+ }
+ }
+
+ private static void withFilePrintStreamOrSystemOut(String filename, @ClosureParams(value = SimpleType, options = "java.io.PrintStream") Closure closure) {
+ if (!filename) {
+ closure.call(System.out)
+ return
+ }
+
+ def outputFile = new File(filename)
+ if (!outputFile.parentFile.exists()) {
+ outputFile.parentFile.mkdirs()
+ }
+ outputFile.withOutputStream { OutputStream out ->
+ closure.call(new PrintStream(out))
+ }
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmMarkNextChangesetRanCommand.groovy b/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmMarkNextChangesetRanCommand.groovy
new file mode 100644
index 00000000000..bc667b1bb54
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmMarkNextChangesetRanCommand.groovy
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2015 original authors
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration.command
+
+import grails.dev.commands.ApplicationCommand
+import groovy.transform.CompileStatic
+import liquibase.Liquibase
+
+@CompileStatic
+class DbmMarkNextChangesetRanCommand implements ApplicationCommand, ApplicationContextDatabaseMigrationCommand {
+
+ final String description = 'Mark the next change set as executed in the database'
+
+ void handle() {
+ withLiquibase { Liquibase liquibase ->
+ liquibase.markNextChangeSetRan(contexts)
+ }
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmMarkNextChangesetRanSqlCommand.groovy b/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmMarkNextChangesetRanSqlCommand.groovy
new file mode 100644
index 00000000000..3927a5ce265
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmMarkNextChangesetRanSqlCommand.groovy
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2015 original authors
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration.command
+
+import grails.dev.commands.ApplicationCommand
+import groovy.transform.CompileStatic
+import liquibase.Liquibase
+
+@CompileStatic
+class DbmMarkNextChangesetRanSqlCommand implements ApplicationCommand, ApplicationContextDatabaseMigrationCommand {
+
+ final String description = 'Writes SQL to mark the next change as executed in the database to STDOUT or a file'
+
+ void handle() {
+ def filename = args[0]
+
+ withLiquibase { Liquibase liquibase ->
+ withFileOrSystemOutWriter(filename) { Writer writer ->
+ liquibase.markNextChangeSetRan(contexts, writer)
+ }
+ }
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmPreviousChangesetSqlCommand.groovy b/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmPreviousChangesetSqlCommand.groovy
new file mode 100644
index 00000000000..c92c25f9e4e
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmPreviousChangesetSqlCommand.groovy
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2015 original authors
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration.command
+
+import grails.dev.commands.ApplicationCommand
+import groovy.transform.CompileStatic
+import liquibase.Liquibase
+import liquibase.database.Database
+import org.grails.plugins.databasemigration.DatabaseMigrationException
+
+@CompileStatic
+class DbmPreviousChangesetSqlCommand implements ApplicationCommand, ApplicationContextDatabaseMigrationCommand {
+
+ final String description = 'Generates the SQL to apply the previous change sets'
+
+ @Override
+ void handle() {
+
+ String count = args[0]
+ if (!count) {
+ throw new DatabaseMigrationException("The $name command requires a change set number argument")
+ }
+ if (!count.isNumber()) {
+ throw new DatabaseMigrationException("The change set number argument '$count' isn't a number")
+ }
+
+ def filename = args[1]
+
+ String skip = optionValue('skip') ?: '0'
+
+ if (!skip.isNumber()) {
+ throw new DatabaseMigrationException("The change set skip argument '$count' isn't a number")
+ }
+
+ configureLiquibase()
+
+ withLiquibase { Liquibase liquibase ->
+ withDatabase { Database database ->
+ withFileOrSystemOutWriter(filename) { Writer output ->
+ doGeneratePreviousChangesetSql(output, database, liquibase, count, skip)
+ }
+ }
+ }
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmReleaseLocksCommand.groovy b/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmReleaseLocksCommand.groovy
new file mode 100644
index 00000000000..5ff12e2a5b0
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmReleaseLocksCommand.groovy
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2015 original authors
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration.command
+
+import grails.dev.commands.ApplicationCommand
+import groovy.transform.CompileStatic
+import liquibase.Liquibase
+
+@CompileStatic
+class DbmReleaseLocksCommand implements ApplicationCommand, ApplicationContextDatabaseMigrationCommand {
+
+ final String description = 'Releases all locks on the database changelog'
+
+ void handle() {
+ withLiquibase { Liquibase liquibase ->
+ liquibase.forceReleaseLocks()
+ }
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmRollbackCommand.groovy b/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmRollbackCommand.groovy
new file mode 100644
index 00000000000..97b1978ba22
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmRollbackCommand.groovy
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2015 original authors
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration.command
+
+import grails.dev.commands.ApplicationCommand
+import groovy.transform.CompileStatic
+import liquibase.Liquibase
+import org.grails.plugins.databasemigration.DatabaseMigrationException
+
+@CompileStatic
+class DbmRollbackCommand implements ApplicationCommand, ApplicationContextDatabaseMigrationCommand {
+
+ final String description = 'Rolls back the database to the state it was in when the tag was applied'
+
+ @Override
+ void handle() {
+ def tagName = args[0]
+ if (!tagName) {
+ throw new DatabaseMigrationException("The $name command requires a tag")
+ }
+
+ withLiquibase { Liquibase liquibase ->
+ liquibase.rollback(tagName, contexts)
+ }
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmRollbackCountCommand.groovy b/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmRollbackCountCommand.groovy
new file mode 100644
index 00000000000..f2d695c650d
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmRollbackCountCommand.groovy
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2015 original authors
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration.command
+
+import grails.dev.commands.ApplicationCommand
+import groovy.transform.CompileStatic
+import liquibase.Liquibase
+import org.grails.plugins.databasemigration.DatabaseMigrationException
+
+@CompileStatic
+class DbmRollbackCountCommand implements ApplicationCommand, ApplicationContextDatabaseMigrationCommand {
+
+ final String description = 'Rolls back the specified number of change sets'
+
+ @Override
+ void handle() {
+ def number = args[0]
+ if (!number) {
+ throw new DatabaseMigrationException("The $name command requires a change set number argument")
+ }
+ if (!number.isNumber()) {
+ throw new DatabaseMigrationException("The change set number argument '$number' isn't a number")
+ }
+
+ withLiquibase { Liquibase liquibase ->
+ liquibase.rollback(number.toInteger(), contexts)
+ }
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmRollbackCountSqlCommand.groovy b/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmRollbackCountSqlCommand.groovy
new file mode 100644
index 00000000000..862cb449ec2
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmRollbackCountSqlCommand.groovy
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2015 original authors
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration.command
+
+import grails.dev.commands.ApplicationCommand
+import groovy.transform.CompileStatic
+import liquibase.Liquibase
+import org.grails.plugins.databasemigration.DatabaseMigrationException
+
+@CompileStatic
+class DbmRollbackCountSqlCommand implements ApplicationCommand, ApplicationContextDatabaseMigrationCommand {
+
+ final String description = 'Writes the SQL to roll back the specified number of change sets to STDOUT or a file'
+
+ @Override
+ void handle() {
+ def number = args[0]
+ if (!number) {
+ throw new DatabaseMigrationException("The $name command requires a change set number argument")
+ }
+ if (!number.isNumber()) {
+ throw new DatabaseMigrationException("The change set number argument '$number' isn't a number")
+ }
+
+ def filename = args[1]
+
+ withLiquibase { Liquibase liquibase ->
+ withFileOrSystemOutWriter(filename) { Writer writer ->
+ liquibase.rollback(number.toInteger(), contexts, writer)
+ }
+ }
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmRollbackSqlCommand.groovy b/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmRollbackSqlCommand.groovy
new file mode 100644
index 00000000000..7e283155f2f
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmRollbackSqlCommand.groovy
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2015 original authors
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration.command
+
+import grails.dev.commands.ApplicationCommand
+import groovy.transform.CompileStatic
+import liquibase.Liquibase
+import org.grails.plugins.databasemigration.DatabaseMigrationException
+
+@CompileStatic
+class DbmRollbackSqlCommand implements ApplicationCommand, ApplicationContextDatabaseMigrationCommand {
+
+ final String description = 'Writes SQL to roll back the database to the state it was in when the tag was applied to STDOUT or a file'
+
+ @Override
+ void handle() {
+ def tagName = args[0]
+ if (!tagName) {
+ throw new DatabaseMigrationException("The $name command requires a tag")
+ }
+
+ def filename = args[1]
+
+ withLiquibase { Liquibase liquibase ->
+ withFileOrSystemOutWriter(filename) { Writer writer ->
+ liquibase.rollback(tagName, contexts, writer)
+ }
+ }
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmRollbackToDateCommand.groovy b/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmRollbackToDateCommand.groovy
new file mode 100644
index 00000000000..4e8ef908bb9
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmRollbackToDateCommand.groovy
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2015 original authors
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration.command
+
+import grails.dev.commands.ApplicationCommand
+import groovy.transform.CompileStatic
+import liquibase.Liquibase
+import org.grails.plugins.databasemigration.DatabaseMigrationException
+
+import java.text.ParseException
+
+@CompileStatic
+class DbmRollbackToDateCommand implements ApplicationCommand, ApplicationContextDatabaseMigrationCommand {
+
+ final String description = 'Rolls back the database to the state it was in at the given date/time'
+
+ @Override
+ void handle() {
+ def dateStr = args[0]
+ if (!dateStr) {
+ throw new DatabaseMigrationException('Date must be specified as two strings with the format "yyyy-MM-dd HH:mm:ss" or as one strings with the format "yyyy-MM-dd"')
+ }
+
+ def timeStr = args[1]
+
+ def date = null
+ try {
+ date = parseDateTime(dateStr, timeStr)
+ } catch (ParseException e) {
+ throw new DatabaseMigrationException("Problem parsing '$dateStr${timeStr ? " $timeStr" : ''}' as a Date: $e.message")
+ }
+
+ withLiquibase { Liquibase liquibase ->
+ liquibase.rollback(date, contexts)
+ }
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmRollbackToDateSqlCommand.groovy b/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmRollbackToDateSqlCommand.groovy
new file mode 100644
index 00000000000..09631aad51b
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmRollbackToDateSqlCommand.groovy
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2015 original authors
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration.command
+
+import grails.dev.commands.ApplicationCommand
+import groovy.transform.CompileStatic
+import liquibase.Liquibase
+import org.grails.plugins.databasemigration.DatabaseMigrationException
+
+import java.text.ParseException
+
+@CompileStatic
+class DbmRollbackToDateSqlCommand implements ApplicationCommand, ApplicationContextDatabaseMigrationCommand {
+
+ final String description = 'Writes SQL to roll back the database to the state it was in at the given date/time to STDOUT or a file'
+
+ @Override
+ void handle() {
+ def dateStr = args[0]
+ if (!dateStr) {
+ throw new DatabaseMigrationException('Date must be specified as two strings with the format "yyyy-MM-dd HH:mm:ss" or as one strings with the format "yyyy-MM-dd"')
+ }
+
+ String timeStr = null
+ String filename = null
+ if (args[1]) {
+ if (args.size() > 2 || isTimeFormat(args[1])) {
+ timeStr = args[1]
+ } else {
+ filename = args[1]
+ }
+ }
+
+ def date = null
+ try {
+ date = parseDateTime(dateStr, timeStr)
+ } catch (ParseException e) {
+ throw new DatabaseMigrationException("Problem parsing '$dateStr${timeStr ? " $timeStr" : ''}' as a Date: $e.message")
+ }
+
+ filename = filename ?: args[2]
+
+ withLiquibase { Liquibase liquibase ->
+ withFileOrSystemOutWriter(filename) { Writer writer ->
+ liquibase.rollback(date, contexts, writer)
+ }
+ }
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmStatusCommand.groovy b/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmStatusCommand.groovy
new file mode 100644
index 00000000000..a0e83856957
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmStatusCommand.groovy
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2015 original authors
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration.command
+
+import grails.dev.commands.ApplicationCommand
+import groovy.transform.CompileStatic
+import liquibase.Liquibase
+
+@CompileStatic
+class DbmStatusCommand implements ApplicationCommand, ApplicationContextDatabaseMigrationCommand {
+
+ final String description = 'Outputs count or list of unrun change sets to STDOUT or a file'
+
+ void handle() {
+ def filename = args[0]
+ def verbose = hasOption('verbose') ? Boolean.parseBoolean(optionValue('verbose')) as Boolean : true
+
+ withLiquibase { Liquibase liquibase ->
+ withFileOrSystemOutWriter(filename) { Writer writer ->
+ liquibase.reportStatus(verbose, contexts, writer)
+ }
+ }
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmTagCommand.groovy b/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmTagCommand.groovy
new file mode 100644
index 00000000000..4b21c995540
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmTagCommand.groovy
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2015 original authors
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration.command
+
+import grails.dev.commands.ApplicationCommand
+import groovy.transform.CompileStatic
+import liquibase.Liquibase
+import org.grails.plugins.databasemigration.DatabaseMigrationException
+
+@CompileStatic
+class DbmTagCommand implements ApplicationCommand, ApplicationContextDatabaseMigrationCommand {
+
+ final String description = 'Adds a tag to mark the current database state'
+
+ void handle() {
+ def tagName = args[0]
+ if (!tagName) {
+ throw new DatabaseMigrationException("The $name command requires a tag")
+ }
+
+ withLiquibase { Liquibase liquibase ->
+ liquibase.tag(tagName)
+ }
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmUpdateCommand.groovy b/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmUpdateCommand.groovy
new file mode 100644
index 00000000000..ebd3f1c0188
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmUpdateCommand.groovy
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2015 original authors
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration.command
+
+import grails.dev.commands.ApplicationCommand
+import groovy.transform.CompileStatic
+import liquibase.Liquibase
+
+@CompileStatic
+class DbmUpdateCommand implements ApplicationCommand, ApplicationContextDatabaseMigrationCommand {
+
+ final String description = 'Updates a database to the current version'
+
+ @Override
+ void handle() {
+ withLiquibase { Liquibase liquibase ->
+ withTransaction {
+ liquibase.update(contexts)
+ }
+ }
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmUpdateCountCommand.groovy b/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmUpdateCountCommand.groovy
new file mode 100644
index 00000000000..86670899933
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmUpdateCountCommand.groovy
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2015 original authors
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration.command
+
+import grails.dev.commands.ApplicationCommand
+import groovy.transform.CompileStatic
+import liquibase.Liquibase
+import org.grails.plugins.databasemigration.DatabaseMigrationException
+
+@CompileStatic
+class DbmUpdateCountCommand implements ApplicationCommand, ApplicationContextDatabaseMigrationCommand {
+
+ final String description = 'Applies next NUM changes to the database'
+
+ @Override
+ void handle() {
+ def number = args[0]
+ if (!number) {
+ throw new DatabaseMigrationException("The $name command requires a change set number argument")
+ }
+ if (!number.isNumber()) {
+ throw new DatabaseMigrationException("The change set number argument '$number' isn't a number")
+ }
+
+ withLiquibase { Liquibase liquibase ->
+ liquibase.update(number.toInteger(), contexts)
+ }
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmUpdateCountSqlCommand.groovy b/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmUpdateCountSqlCommand.groovy
new file mode 100644
index 00000000000..574c87ea538
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmUpdateCountSqlCommand.groovy
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2015 original authors
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration.command
+
+import grails.dev.commands.ApplicationCommand
+import groovy.transform.CompileStatic
+import liquibase.Liquibase
+import org.grails.plugins.databasemigration.DatabaseMigrationException
+
+@CompileStatic
+class DbmUpdateCountSqlCommand implements ApplicationCommand, ApplicationContextDatabaseMigrationCommand {
+
+ final String description = 'Writes the SQL that will partially update a database to STDOUT or a file'
+
+ @Override
+ void handle() {
+ def number = args[0]
+ if (!number) {
+ throw new DatabaseMigrationException("The $name command requires a change set number argument")
+ }
+ if (!number.isNumber()) {
+ throw new DatabaseMigrationException("The change set number argument '$number' isn't a number")
+ }
+
+ def filename = args[1]
+ withLiquibase { Liquibase liquibase ->
+ withFileOrSystemOutWriter(filename) { Writer writer ->
+ liquibase.update(number.toInteger(), contexts, writer)
+ }
+ }
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmUpdateSqlCommand.groovy b/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmUpdateSqlCommand.groovy
new file mode 100644
index 00000000000..10a0649ccf3
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmUpdateSqlCommand.groovy
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2015 original authors
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration.command
+
+import grails.dev.commands.ApplicationCommand
+import groovy.transform.CompileStatic
+import liquibase.Liquibase
+
+@CompileStatic
+class DbmUpdateSqlCommand implements ApplicationCommand, ApplicationContextDatabaseMigrationCommand {
+
+ final String description = 'Writes the SQL that will update the database to the current version to STDOUT or a file'
+
+ @Override
+ void handle() {
+ def filename = args[0]
+
+ withLiquibase { Liquibase liquibase ->
+ withFileOrSystemOutWriter(filename) { Writer writer ->
+ liquibase.update(contexts, writer)
+ }
+ }
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmValidateCommand.groovy b/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmValidateCommand.groovy
new file mode 100644
index 00000000000..dbd27eb02f7
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/grails-app/commands/org/grails/plugins/databasemigration/command/DbmValidateCommand.groovy
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2015 original authors
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration.command
+
+import grails.dev.commands.ApplicationCommand
+import groovy.transform.CompileStatic
+import liquibase.Liquibase
+
+@CompileStatic
+class DbmValidateCommand implements ApplicationCommand, ApplicationContextDatabaseMigrationCommand {
+
+ final String description = 'Checks the changelog for errors'
+
+ void handle() {
+ withLiquibase { Liquibase liquibase ->
+ liquibase.validate()
+ }
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/grails-app/conf/application.yml b/grails-data-hibernate5/database-migration/grails-app/conf/application.yml
new file mode 100644
index 00000000000..a30a35c88d5
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/grails-app/conf/application.yml
@@ -0,0 +1,13 @@
+grails:
+ profile: web-plugin
+ codegen:
+ defaultPackage: databasemigration
+info:
+ app:
+ name: '@info.app.name@'
+ version: '@info.app.version@'
+ grailsVersion: '@info.app.grailsVersion@'
+spring:
+ groovy:
+ template:
+ check-template-location: false
diff --git a/grails-data-hibernate5/database-migration/grails-app/conf/logback.groovy b/grails-data-hibernate5/database-migration/grails-app/conf/logback.groovy
new file mode 100644
index 00000000000..b835215c1e8
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/grails-app/conf/logback.groovy
@@ -0,0 +1,37 @@
+import grails.util.BuildSettings
+import grails.util.Environment
+import org.springframework.boot.logging.logback.ColorConverter
+import org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter
+
+import java.nio.charset.StandardCharsets
+
+conversionRule 'clr', ColorConverter
+conversionRule 'wex', WhitespaceThrowableProxyConverter
+
+// See http://logback.qos.ch/manual/groovy.html for details on configuration
+appender('STDOUT', ConsoleAppender) {
+ encoder(PatternLayoutEncoder) {
+ charset = StandardCharsets.UTF_8
+
+ pattern =
+ '%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} ' + // Date
+ '%clr(%5p) ' + // Log level
+ '%clr(---){faint} %clr([%15.15t]){faint} ' + // Thread
+ '%clr(%-40.40logger{39}){cyan} %clr(:){faint} ' + // Logger
+ '%m%n%wex' // Message
+ }
+}
+
+def targetDir = BuildSettings.TARGET_DIR
+if (Environment.isDevelopmentMode() && targetDir != null) {
+ appender("FULL_STACKTRACE", FileAppender) {
+ file = "${targetDir}/stacktrace.log"
+ append = true
+ encoder(PatternLayoutEncoder) {
+ charset = StandardCharsets.UTF_8
+ pattern = "%level %logger - %msg%n"
+ }
+ }
+ logger("StackTrace", ERROR, ['FULL_STACKTRACE'], false)
+}
+root(ERROR, ['STDOUT'])
diff --git a/grails-data-hibernate5/database-migration/grails-app/domain/testapp/Account.groovy b/grails-data-hibernate5/database-migration/grails-app/domain/testapp/Account.groovy
new file mode 100644
index 00000000000..e443824cb1b
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/grails-app/domain/testapp/Account.groovy
@@ -0,0 +1,6 @@
+package testapp
+
+class Account {
+ String name
+ String number
+}
\ No newline at end of file
diff --git a/grails-data-hibernate5/database-migration/grails-app/domain/testapp/Person.groovy b/grails-data-hibernate5/database-migration/grails-app/domain/testapp/Person.groovy
new file mode 100644
index 00000000000..9c0c49e99bb
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/grails-app/domain/testapp/Person.groovy
@@ -0,0 +1,11 @@
+package testapp
+
+class Person {
+ String firstName
+ String lastName
+ String gender
+ Integer age
+
+ String emailAddress
+ String cell
+}
\ No newline at end of file
diff --git a/grails-data-hibernate5/database-migration/grails-app/init/databasemigration/Application.groovy b/grails-data-hibernate5/database-migration/grails-app/init/databasemigration/Application.groovy
new file mode 100644
index 00000000000..debb892ba6a
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/grails-app/init/databasemigration/Application.groovy
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2015-2025 original authors
+ *
+ * Licensed 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 databasemigration
+
+import grails.boot.GrailsApp
+import grails.boot.config.GrailsAutoConfiguration
+import grails.plugins.metadata.PluginSource
+import groovy.transform.CompileStatic
+
+@PluginSource
+@CompileStatic
+class Application extends GrailsAutoConfiguration {
+ static void main(String[] args) {
+ GrailsApp.run(Application)
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/src/integration-test/groovy/org/grails/plugins/databasemigration/AutoRunWithMultipleDataSourceSpec.groovy b/grails-data-hibernate5/database-migration/src/integration-test/groovy/org/grails/plugins/databasemigration/AutoRunWithMultipleDataSourceSpec.groovy
new file mode 100644
index 00000000000..f904761ee6c
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/integration-test/groovy/org/grails/plugins/databasemigration/AutoRunWithMultipleDataSourceSpec.groovy
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2015 original authors
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration
+
+import grails.testing.mixin.integration.Integration
+import groovy.sql.Sql
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.test.context.ActiveProfiles
+import spock.lang.AutoCleanup
+import spock.lang.Specification
+
+import javax.sql.DataSource
+
+@Integration
+@ActiveProfiles('multiple-datasource')
+class AutoRunWithMultipleDataSourceSpec extends Specification {
+
+ @Autowired
+ DataSource dataSource
+
+ @Autowired
+ DataSource dataSource_second
+
+ @AutoCleanup
+ Sql sql
+
+ @AutoCleanup
+ Sql secondSql
+
+ def setup() {
+ sql = new Sql(dataSource)
+ secondSql = new Sql(dataSource_second)
+ }
+
+ def "runs app with a multiple datasource"() {
+ when:
+ def changeSetIds = sql.rows('SELECT id FROM DATABASECHANGELOG').collect { it.id }
+
+ then:
+ changeSetIds as Set == ['1', '2', '3', '4', '5'] as Set
+
+ when:
+ def secondChangeSetIds = secondSql.rows('SELECT id FROM DATABASECHANGELOG').collect { it.id }
+
+ then:
+ secondChangeSetIds as Set == ['second-1', 'second-2', 'second-3'] as Set
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/src/integration-test/groovy/org/grails/plugins/databasemigration/AutoRunWithSingleDataSourceSpec.groovy b/grails-data-hibernate5/database-migration/src/integration-test/groovy/org/grails/plugins/databasemigration/AutoRunWithSingleDataSourceSpec.groovy
new file mode 100644
index 00000000000..79b72d2868e
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/integration-test/groovy/org/grails/plugins/databasemigration/AutoRunWithSingleDataSourceSpec.groovy
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2015 original authors
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration
+
+import grails.testing.mixin.integration.Integration
+import groovy.sql.Sql
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.test.context.ActiveProfiles
+import spock.lang.AutoCleanup
+import spock.lang.Specification
+
+import javax.sql.DataSource
+
+@Integration
+@ActiveProfiles('single-datasource')
+class AutoRunWithSingleDataSourceSpec extends Specification {
+
+ @Autowired
+ DataSource dataSource
+
+ @AutoCleanup
+ Sql sql
+
+ def setup() {
+ sql = new Sql(dataSource)
+ //sql.executeUpdate("drop table AUTHOR")
+ }
+
+ def "runs app with a single datasource"() {
+ expect:
+ def changeSetIds = sql.rows('SELECT id FROM databasechangelog').collect { it.id }
+ changeSetIds as Set == ['1', '2', '3', '5'] as Set
+
+ and:
+ def authors = sql.rows('SELECT name FROM author').collect { it.name }
+ authors == ['Amelia']
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/src/integration-test/groovy/org/grails/plugins/databasemigration/DbUpdateCommandSpec.groovy b/grails-data-hibernate5/database-migration/src/integration-test/groovy/org/grails/plugins/databasemigration/DbUpdateCommandSpec.groovy
new file mode 100644
index 00000000000..b59d7b65a5c
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/integration-test/groovy/org/grails/plugins/databasemigration/DbUpdateCommandSpec.groovy
@@ -0,0 +1,69 @@
+package org.grails.plugins.databasemigration
+
+import grails.dev.commands.ApplicationCommand
+import grails.dev.commands.ExecutionContext
+import grails.testing.mixin.integration.Integration
+import grails.util.GrailsNameUtils
+import groovy.sql.Sql
+import liquibase.GlobalConfiguration
+import liquibase.Scope
+import liquibase.exception.LiquibaseException
+import org.grails.build.parsing.CommandLineParser
+import org.grails.plugins.databasemigration.command.DbmUpdateCommand
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.context.ApplicationContext
+import org.springframework.stereotype.Component
+import org.springframework.test.context.ActiveProfiles
+import spock.lang.AutoCleanup
+import spock.lang.Specification
+
+import javax.sql.DataSource
+
+@Integration
+@ActiveProfiles('transaction-datasource')
+@Component
+class DbUpdateCommandSpec extends Specification {
+
+ @Autowired
+ DataSource dataSource
+
+ @Autowired
+ ApplicationContext applicationContext
+
+ @AutoCleanup
+ Sql sql
+
+ def setup() {
+ sql = new Sql(dataSource)
+ }
+
+ void "test the transaction behaviour in the changeSet with grailsChange and GORM"() {
+
+ when:
+ Scope.child(GlobalConfiguration.DUPLICATE_FILE_MODE.getKey(), GlobalConfiguration.DuplicateFileMode.WARN, { ->
+ DbmUpdateCommand command = new DbmUpdateCommand()
+ command.applicationContext = applicationContext
+ command.setExecutionContext(getExecutionContext(DbmUpdateCommand))
+ command.handle()
+ } as Scope.ScopedRunner)
+
+ then:
+ def e = thrown(LiquibaseException)
+ e.cause instanceof LiquibaseException
+ sql.firstRow('SELECT COUNT(*) AS num FROM DATABASECHANGELOG WHERE id=?;', 'create-person-grails').num == 1
+ sql.firstRow('SELECT COUNT(*) AS num FROM person;').num == 1
+ sql.firstRow('SELECT COUNT(*) AS num FROM account;').num == 0
+
+ }
+
+ private ExecutionContext getExecutionContext(Class clazz, String... args) {
+ def commandClassName = GrailsNameUtils.getScriptName(GrailsNameUtils.getLogicalName(clazz.name, 'Command'))
+ new ExecutionContext(
+ new CommandLineParser().parse(([commandClassName] + args.toList()) as String[])
+ )
+ }
+}
+
+
+
+
diff --git a/grails-data-hibernate5/database-migration/src/integration-test/resources/application-multiple-datasource.yml b/grails-data-hibernate5/database-migration/src/integration-test/resources/application-multiple-datasource.yml
new file mode 100644
index 00000000000..c82462f34bc
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/integration-test/resources/application-multiple-datasource.yml
@@ -0,0 +1,29 @@
+grails:
+ plugin:
+ databasemigration:
+ updateOnStart: true
+ second:
+ updateOnStart: true
+---
+server:
+ port: 0
+---
+dataSource:
+ pooled: true
+ jmxExport: true
+ driverClassName: org.h2.Driver
+ username: sa
+ password:
+ dbCreate: none
+ url: jdbc:h2:file:./multipleFirstDb
+ logSql: true
+ formatSql: true
+dataSources:
+ second:
+ pooled: true
+ jmxExport: true
+ driverClassName: org.h2.Driver
+ username: sa
+ password:
+ dbCreate: none
+ url: jdbc:h2:file:./multipleSecondDb
\ No newline at end of file
diff --git a/grails-data-hibernate5/database-migration/src/integration-test/resources/application-single-datasource.yml b/grails-data-hibernate5/database-migration/src/integration-test/resources/application-single-datasource.yml
new file mode 100644
index 00000000000..e471a71df68
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/integration-test/resources/application-single-datasource.yml
@@ -0,0 +1,18 @@
+grails:
+ plugin:
+ databasemigration:
+ updateOnStart: true
+ updateOnStartContexts:
+ - test
+---
+server:
+ port: 0
+---
+dataSource:
+ pooled: true
+ jmxExport: true
+ driverClassName: org.h2.Driver
+ username: sa
+ password:
+ dbCreate: none
+ url: jdbc:h2:file:./singleDb
diff --git a/grails-data-hibernate5/database-migration/src/integration-test/resources/application-transaction-datasource.yml b/grails-data-hibernate5/database-migration/src/integration-test/resources/application-transaction-datasource.yml
new file mode 100644
index 00000000000..2be53f55289
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/integration-test/resources/application-transaction-datasource.yml
@@ -0,0 +1,29 @@
+grails:
+ plugin:
+ databasemigration:
+ changelogFileName: 'changelog-transaction.groovy'
+ changelogLocation: 'src/integration-test/resources'
+ updateOnStart: false
+ second:
+ updateOnStart: false
+---
+server:
+ port: 0
+---
+dataSource:
+ pooled: true
+ jmxExport: true
+ driverClassName: org.h2.Driver
+ username: sa
+ password:
+ dbCreate: none
+ url: jdbc:h2:file:./testDb
+dataSources:
+ other:
+ pooled: true
+ jmxExport: true
+ driverClassName: org.h2.Driver
+ username: sa
+ password:
+ dbCreate: none
+ url: jdbc:h2:file:./otherDb
diff --git a/grails-data-hibernate5/database-migration/src/integration-test/resources/changelog-account-person-init.groovy b/grails-data-hibernate5/database-migration/src/integration-test/resources/changelog-account-person-init.groovy
new file mode 100644
index 00000000000..5ad7e861a73
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/integration-test/resources/changelog-account-person-init.groovy
@@ -0,0 +1,57 @@
+databaseChangeLog = {
+ changeSet(id: "create-person-table", author: 'integration-test') {
+ createTable(tableName: "person") {
+ column(autoIncrement: "true", name: "id", type: "BIGINT") {
+ constraints(primaryKey: "true")
+ }
+
+ column(name: "version", type: "BIGINT") {
+ constraints(nullable: "false")
+ }
+
+ column(name: "first_name", type: "VARCHAR(255)") {
+ constraints(nullable: "false")
+ }
+
+ column(name: "age", type: "INT") {
+ constraints(nullable: "false")
+ }
+
+ column(name: "gender", type: "VARCHAR(255)") {
+ constraints(nullable: "false")
+ }
+
+ column(name: "last_name", type: "VARCHAR(255)") {
+ constraints(nullable: "false")
+ }
+
+ column(name: "cell", type: "VARCHAR(255)") {
+ constraints(nullable: "false")
+ }
+
+ column(name: "email_address", type: "VARCHAR(255)") {
+ constraints(nullable: "false")
+ }
+ }
+ }
+
+ changeSet(id: "create-account-table", author: 'integration-test') {
+ createTable(tableName: "account") {
+ column(autoIncrement: "true", name: "id", type: "BIGINT") {
+ constraints(primaryKey: "true", primaryKeyName: "accountPK")
+ }
+
+ column(name: "version", type: "BIGINT") {
+ constraints(nullable: "false")
+ }
+
+ column(name: "name", type: "VARCHAR(255)") {
+ constraints(nullable: "false")
+ }
+
+ column(name: "number", type: "VARCHAR(255)") {
+ constraints(nullable: "false")
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/grails-data-hibernate5/database-migration/src/integration-test/resources/changelog-account-sql.groovy b/grails-data-hibernate5/database-migration/src/integration-test/resources/changelog-account-sql.groovy
new file mode 100644
index 00000000000..db5c9618949
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/integration-test/resources/changelog-account-sql.groovy
@@ -0,0 +1,5 @@
+databaseChangeLog = {
+ changeSet(id: 'create-account-sql', author: 'integration-test') {
+ sql "INSERT INTO account (version, name) VALUES (0, 'Joseph');"
+ }
+}
\ No newline at end of file
diff --git a/grails-data-hibernate5/database-migration/src/integration-test/resources/changelog-person-grails.groovy b/grails-data-hibernate5/database-migration/src/integration-test/resources/changelog-person-grails.groovy
new file mode 100644
index 00000000000..42e3ea305c4
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/integration-test/resources/changelog-person-grails.groovy
@@ -0,0 +1,22 @@
+import testapp.Person
+
+databaseChangeLog = {
+ changeSet(id: 'create-person-grails', author: 'integration-test') {
+
+ grailsChange {
+ change {
+ Person person = new Person()
+ person.firstName = 'Joseph1'
+ person.lastName = 'Holmes'
+ person.age = 56
+ person.gender = 'male'
+ person.cell = '734-776-7738'
+ person.emailAddress = 'jhomes@example.com'
+ person.save(flush: true, failOnError: true)
+ }
+ rollback {
+ confirm('Done: Rollback person Jone')
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/grails-data-hibernate5/database-migration/src/integration-test/resources/changelog-second.groovy b/grails-data-hibernate5/database-migration/src/integration-test/resources/changelog-second.groovy
new file mode 100644
index 00000000000..40454db704f
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/integration-test/resources/changelog-second.groovy
@@ -0,0 +1,42 @@
+databaseChangeLog = {
+
+ changeSet(author: "John Smith", id: "second-1") {
+ createTable(tableName: "author") {
+ column(autoIncrement: "true", name: "id", type: "BIGINT") {
+ constraints(primaryKey: "true", primaryKeyName: "authorPK")
+ }
+
+ column(name: "version", type: "BIGINT") {
+ constraints(nullable: "false")
+ }
+
+ column(name: "name", type: "VARCHAR(255)") {
+ constraints(nullable: "false")
+ }
+ }
+ }
+
+ changeSet(author: "John Smith", id: "second-2") {
+ createTable(tableName: "book") {
+ column(autoIncrement: "true", name: "id", type: "BIGINT") {
+ constraints(primaryKey: "true", primaryKeyName: "bookPK")
+ }
+
+ column(name: "version", type: "BIGINT") {
+ constraints(nullable: "false")
+ }
+
+ column(name: "author_id", type: "BIGINT") {
+ constraints(nullable: "false")
+ }
+
+ column(name: "title", type: "VARCHAR(255)") {
+ constraints(nullable: "false")
+ }
+ }
+ }
+
+ changeSet(author: "John Smith", id: "second-3") {
+ addForeignKeyConstraint(baseColumnNames: "author_id", baseTableName: "book", constraintName: "FK_4sac2ubmnqva85r8bk8fxdvbf", deferrable: "false", initiallyDeferred: "false", referencedColumnNames: "id", referencedTableName: "author")
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/src/integration-test/resources/changelog-transaction.groovy b/grails-data-hibernate5/database-migration/src/integration-test/resources/changelog-transaction.groovy
new file mode 100644
index 00000000000..3ec56c30088
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/integration-test/resources/changelog-transaction.groovy
@@ -0,0 +1,5 @@
+databaseChangeLog = {
+ include file: 'changelog-account-person-init.groovy'
+ include file: 'changelog-person-grails.groovy'
+ include file: 'changelog-account-sql.groovy'
+}
\ No newline at end of file
diff --git a/grails-data-hibernate5/database-migration/src/integration-test/resources/changelog.groovy b/grails-data-hibernate5/database-migration/src/integration-test/resources/changelog.groovy
new file mode 100644
index 00000000000..c198af581fc
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/integration-test/resources/changelog.groovy
@@ -0,0 +1,56 @@
+databaseChangeLog = {
+
+ changeSet(author: "John Smith", id: "1") {
+ createTable(tableName: "author") {
+ column(autoIncrement: "true", name: "id", type: "BIGINT") {
+ constraints(primaryKey: "true", primaryKeyName: "authorPK")
+ }
+
+ column(name: "version", type: "BIGINT") {
+ constraints(nullable: "false")
+ }
+
+ column(name: "name", type: "VARCHAR(255)") {
+ constraints(nullable: "false")
+ }
+ }
+ }
+
+ changeSet(author: "John Smith", id: "2") {
+ createTable(tableName: "book") {
+ column(autoIncrement: "true", name: "id", type: "BIGINT") {
+ constraints(primaryKey: "true", primaryKeyName: "bookPK")
+ }
+
+ column(name: "version", type: "BIGINT") {
+ constraints(nullable: "false")
+ }
+
+ column(name: "author_id", type: "BIGINT") {
+ constraints(nullable: "false")
+ }
+
+ column(name: "title", type: "VARCHAR(255)") {
+ constraints(nullable: "false")
+ }
+ }
+ }
+
+ changeSet(author: "John Smith", id: "3") {
+ addForeignKeyConstraint(baseColumnNames: "author_id", baseTableName: "book", constraintName: "FK_4sac2ubmnqva85r8bk8fxdvbf", deferrable: "false", initiallyDeferred: "false", referencedColumnNames: "id", referencedTableName: "author")
+ }
+
+ changeSet(author: "John Smith", id: "4", context: "development") {
+ insert(tableName: "author") {
+ column(name: "name", value: "Mary")
+ column(name: "version", value: "0")
+ }
+ }
+
+ changeSet(author: "John Smith", id: "5", context: "test") {
+ insert(tableName: "author") {
+ column(name: "name", value: "Amelia")
+ column(name: "version", value: "0")
+ }
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/src/integration-test/resources/logback-test.xml b/grails-data-hibernate5/database-migration/src/integration-test/resources/logback-test.xml
new file mode 100644
index 00000000000..25794ab3e37
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/integration-test/resources/logback-test.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+ UTF-8
+ %clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n%wex
+
+
+
+
+
+
\ No newline at end of file
diff --git a/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/DatabaseMigrationException.groovy b/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/DatabaseMigrationException.groovy
new file mode 100644
index 00000000000..7692bbd6cdc
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/DatabaseMigrationException.groovy
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2015 original authors
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration
+
+import groovy.transform.InheritConstructors
+
+@InheritConstructors
+class DatabaseMigrationException extends RuntimeException {
+}
diff --git a/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/DatabaseMigrationGrailsPlugin.groovy b/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/DatabaseMigrationGrailsPlugin.groovy
new file mode 100644
index 00000000000..ac27e35bf0c
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/DatabaseMigrationGrailsPlugin.groovy
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2015 original authors
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration
+
+import grails.plugins.Plugin
+import liquibase.parser.ChangeLogParser
+import liquibase.parser.ChangeLogParserFactory
+import org.grails.plugins.databasemigration.liquibase.GrailsLiquibase
+import org.grails.plugins.databasemigration.liquibase.GrailsLiquibaseFactory
+import org.grails.plugins.databasemigration.liquibase.GroovyChangeLogParser
+import org.springframework.context.ApplicationContext
+
+import javax.sql.DataSource
+
+class DatabaseMigrationGrailsPlugin extends Plugin {
+
+ static final String CONFIG_MAIN_PREFIX = 'grails.plugin.databasemigration'
+
+ def grailsVersion = "7.0.0-SNAPSHOT > *"
+ def pluginExcludes = [
+ "**/testapp/**",
+ "grails-app/views/error.gsp"
+ ]
+
+ def title = "Grails Database Migration Plugin" // Headline display name of the plugin
+ def author = "Kazuki YAMAMOTO"
+ def authorEmail = ""
+ def description = 'Grails Database Migration Plugin'
+ def documentation = "http://grails.org/plugin/database-migration"
+ def license = "APACHE"
+ def scm = [url: "https://github.com/grails-plugins/grails-database-migration"]
+
+ @Override
+ Closure doWithSpring() {
+ configureLiquibase()
+ return { ->
+ grailsLiquibaseFactory(GrailsLiquibaseFactory, applicationContext)
+ }
+ }
+
+ @Override
+ void doWithApplicationContext() {
+ def mainClassName = deduceApplicationMainClassName()
+
+ def updateAllOnStart = config.getProperty("${CONFIG_MAIN_PREFIX}.updateAllOnStart", Boolean, false)
+
+ dataSourceNames.each { String dataSourceName ->
+ String configPrefix = isDefaultDataSource(dataSourceName) ? CONFIG_MAIN_PREFIX : "${CONFIG_MAIN_PREFIX}.${dataSourceName}"
+ def skipMainClasses = config.getProperty("${configPrefix}.skipUpdateOnStartMainClasses", List, ['grails.ui.command.GrailsApplicationContextCommandRunner'])
+ if (skipMainClasses.contains(mainClassName)) {
+ return
+ }
+
+ if(!updateAllOnStart) {
+ def updateOnStart = config.getProperty("${configPrefix}.updateOnStart", Boolean, false)
+ if (!updateOnStart) {
+ return
+ }
+ } else {
+ configPrefix = CONFIG_MAIN_PREFIX
+ }
+
+ new DatabaseMigrationTransactionManager(applicationContext, dataSourceName).withTransaction {
+ GrailsLiquibase gl = applicationContext.getBean('grailsLiquibaseFactory', GrailsLiquibase)
+ gl.dataSource = getDataSourceBean(applicationContext, dataSourceName)
+ gl.dropFirst = config.getProperty("${configPrefix}.dropOnStart", Boolean, false)
+ gl.changeLog = config.getProperty("${configPrefix}.updateOnStartFileName", String, isDefaultDataSource(dataSourceName) ? 'changelog.groovy' : "changelog-${dataSourceName}.groovy")
+ gl.contexts = config.getProperty("${configPrefix}.updateOnStartContexts", List, []).join(',')
+ gl.labels = config.getProperty("${configPrefix}.updateOnStartLabels", List, []).join(',')
+ gl.defaultSchema = config.getProperty("${configPrefix}.updateOnStartDefaultSchema", String)
+ gl.databaseChangeLogTableName = config.getProperty("${configPrefix}.databaseChangeLogTableName", String)
+ gl.databaseChangeLogLockTableName = config.getProperty("${configPrefix}.databaseChangeLogLockTableName", String)
+ gl.dataSourceName = getDataSourceName(dataSourceName)
+ gl.afterPropertiesSet()
+ }
+ }
+ }
+
+ private def getDataSourceBean(ApplicationContext applicationContext, String dataSourceName) {
+ applicationContext.getBean(getDataSourceName(dataSourceName), DataSource)
+ }
+
+ private void configureLiquibase() {
+ def groovyChangeLogParser = ChangeLogParserFactory.instance.parsers.find { ChangeLogParser changeLogParser -> changeLogParser instanceof GroovyChangeLogParser } as GroovyChangeLogParser
+ groovyChangeLogParser.applicationContext = applicationContext
+ groovyChangeLogParser.config = config
+ }
+
+ private Set getDataSourceNames() {
+ def dataSources = config.getProperty('dataSources', Map, [:])
+ if (!dataSources) {
+ return ['dataSource']
+ }
+ Set dataSourceNames = dataSources.keySet()
+ if (!dataSourceNames.contains('dataSource')) {
+ dataSourceNames = ['dataSource'] + dataSourceNames
+ }
+ dataSourceNames
+ }
+
+ private String deduceApplicationMainClassName() {
+ new RuntimeException().stackTrace.find { StackTraceElement stackTraceElement -> 'main' == stackTraceElement.methodName }?.className
+ }
+
+ static String getDataSourceName(String dataSourceName) {
+ isDefaultDataSource(dataSourceName) ? dataSourceName : "dataSource_$dataSourceName"
+ }
+
+ static Boolean isDefaultDataSource(String dataSourceName) {
+ !dataSourceName || 'dataSource' == dataSourceName
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/DatabaseMigrationTransactionManager.groovy b/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/DatabaseMigrationTransactionManager.groovy
new file mode 100644
index 00000000000..4283cb4763d
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/DatabaseMigrationTransactionManager.groovy
@@ -0,0 +1,125 @@
+package org.grails.plugins.databasemigration
+
+import grails.gorm.transactions.GrailsTransactionTemplate
+import org.springframework.context.ApplicationContext
+import org.springframework.transaction.PlatformTransactionManager
+import org.springframework.transaction.TransactionDefinition
+import org.springframework.transaction.support.DefaultTransactionDefinition
+import org.springframework.util.Assert
+
+/**
+ * Created by Jim on 7/15/2016.
+ */
+class DatabaseMigrationTransactionManager {
+
+ final String dataSource
+ final ApplicationContext applicationContext
+
+ DatabaseMigrationTransactionManager(ApplicationContext applicationContext, String dataSource) {
+ this.dataSource = dataSource
+ this.applicationContext = applicationContext
+ }
+
+ /**
+ *
+ * @return The transactionManager bean for the current dataSource
+ */
+ PlatformTransactionManager getTransactionManager() {
+ String dataSource = this.dataSource ?: "dataSource"
+ String beanName = "transactionManager"
+ if (dataSource != "dataSource") {
+ beanName += "_${dataSource}"
+ }
+ applicationContext.getBean(beanName, PlatformTransactionManager)
+ }
+
+ /**
+ * Executes the closure within the context of a transaction, creating one if none is present or joining
+ * an existing transaction if one is already present.
+ *
+ * @param callable The closure to call
+ * @return The result of the closure execution
+ * @see #withTransaction(Map, Closure)
+ * @see #withNewTransaction(Closure)
+ * @see #withNewTransaction(Map, Closure)
+ */
+ void withTransaction(Closure callable) {
+ withTransaction(new DefaultTransactionDefinition(), callable)
+ }
+
+ /**
+ * Executes the closure within the context of a new transaction
+ *
+ * @param callable The closure to call
+ * @return The result of the closure execution
+ * @see #withTransaction(Closure)
+ * @see #withTransaction(Map, Closure)
+ * @see #withNewTransaction(Map, Closure)
+ */
+ void withNewTransaction(Closure callable) {
+ withTransaction([propagationBehavior: TransactionDefinition.PROPAGATION_REQUIRES_NEW], callable)
+ }
+
+ /**
+ * Executes the closure within the context of a new transaction which is
+ * configured with the properties contained in transactionProperties.
+ * transactionProperties may contain any properties supported by
+ * {@link DefaultTransactionDefinition}. Note that if transactionProperties
+ * includes entries for propagationBehavior or propagationName, those values
+ * will be ignored. This method always sets the propagation level to
+ * TransactionDefinition.REQUIRES_NEW.
+ *
+ *
+ *
+ * @param transactionProperties properties to configure the transaction properties
+ * @param callable The closure to call
+ * @return The result of the closure execution
+ * @see DefaultTransactionDefinition
+ * @see #withNewTransaction(Closure)
+ * @see #withTransaction(Closure)
+ * @see #withTransaction(Map, Closure)
+ */
+ void withNewTransaction(Map transactionProperties, Closure callable) {
+ def props = new HashMap(transactionProperties)
+ props.remove 'propagationName'
+ props.propagationBehavior = TransactionDefinition.PROPAGATION_REQUIRES_NEW
+ withTransaction(props, callable)
+ }
+
+ void withTransaction(Map transactionProperties, Closure callable) {
+ def transactionDefinition = new DefaultTransactionDefinition()
+ transactionProperties.each { k, v ->
+ if(v instanceof CharSequence && !(v instanceof String)) {
+ v = v.toString()
+ }
+ try {
+ transactionDefinition[k as String] = v
+ } catch (MissingPropertyException mpe) {
+ throw new IllegalArgumentException("[${k}] is not a valid transaction property.", mpe)
+ }
+ }
+ withTransaction(transactionDefinition, callable)
+ }
+
+ /**
+ * Executes the closure within the context of a transaction for the given {@link TransactionDefinition}
+ *
+ * @param callable The closure to call
+ * @return The result of the closure execution
+ */
+ void withTransaction(TransactionDefinition definition, Closure callable) {
+ Assert.notNull transactionManager, "No transactionManager bean configured"
+
+ if (!callable) {
+ return
+ }
+
+ new GrailsTransactionTemplate(transactionManager, definition).execute(callable)
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/EnvironmentAwareCodeGenConfig.groovy b/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/EnvironmentAwareCodeGenConfig.groovy
new file mode 100644
index 00000000000..db16cafd039
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/EnvironmentAwareCodeGenConfig.groovy
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2015 original authors
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration
+
+import groovy.transform.CompileDynamic
+import groovy.transform.CompileStatic
+import org.grails.config.CodeGenConfig
+
+@CompileStatic
+class EnvironmentAwareCodeGenConfig extends CodeGenConfig {
+
+ EnvironmentAwareCodeGenConfig(CodeGenConfig copyOf, String environment) {
+ super(copyOf)
+ mergeEnvironmentConfig(copyOf, environment)
+ }
+
+ @CompileDynamic
+ private void mergeEnvironmentConfig(CodeGenConfig copyOf, String environment) {
+ mergeMap(copyOf.environments?."$environment" ?: [:])
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/NoopVisitor.groovy b/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/NoopVisitor.groovy
new file mode 100644
index 00000000000..744438182c2
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/NoopVisitor.groovy
@@ -0,0 +1,24 @@
+package org.grails.plugins.databasemigration
+
+import liquibase.changelog.ChangeSet
+import liquibase.changelog.DatabaseChangeLog
+import liquibase.changelog.filter.ChangeSetFilterResult
+import liquibase.changelog.visitor.ChangeSetVisitor
+import liquibase.database.Database
+import liquibase.exception.LiquibaseException
+
+class NoopVisitor implements ChangeSetVisitor {
+
+ protected Database database
+
+ NoopVisitor(Database database) {
+ this.database = database
+ }
+
+ Direction getDirection() { Direction.FORWARD }
+
+ @Override
+ void visit(ChangeSet changeSet, DatabaseChangeLog databaseChangeLog, Database database, Set filterResults) throws LiquibaseException {
+ changeSet.execute(databaseChangeLog, database)
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/PluginConstants.groovy b/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/PluginConstants.groovy
new file mode 100644
index 00000000000..420e7571f8b
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/PluginConstants.groovy
@@ -0,0 +1,7 @@
+package org.grails.plugins.databasemigration
+
+class PluginConstants {
+ static final String DATA_SOURCE_NAME_KEY = "dataSourceName"
+ static final String DEFAULT_DATASOURCE_NAME = 'dataSource'
+ static final String DEFAULT_CHANGE_LOG_LOCATION = 'grails-app/migrations'
+}
diff --git a/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/command/ApplicationContextDatabaseMigrationCommand.groovy b/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/command/ApplicationContextDatabaseMigrationCommand.groovy
new file mode 100644
index 00000000000..b0558b0e70a
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/command/ApplicationContextDatabaseMigrationCommand.groovy
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2015 original authors
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration.command
+
+import grails.config.ConfigMap
+import grails.core.GrailsApplication
+import grails.dev.commands.ExecutionContext
+import grails.util.Environment
+import groovy.transform.CompileStatic
+import groovy.transform.stc.ClosureParams
+import groovy.transform.stc.SimpleType
+import liquibase.database.Database
+import liquibase.parser.ChangeLogParser
+import liquibase.parser.ChangeLogParserFactory
+import org.grails.config.PropertySourcesConfig
+import org.grails.orm.hibernate.HibernateDatastore
+import org.grails.plugins.databasemigration.DatabaseMigrationTransactionManager
+import org.grails.plugins.databasemigration.liquibase.GormDatabase
+import org.grails.plugins.databasemigration.liquibase.GroovyChangeLogParser
+import org.hibernate.dialect.Dialect
+import org.hibernate.engine.jdbc.spi.JdbcServices
+import org.hibernate.engine.spi.SessionFactoryImplementor
+import org.springframework.context.ConfigurableApplicationContext
+
+import static org.grails.plugins.databasemigration.DatabaseMigrationGrailsPlugin.isDefaultDataSource
+import static org.grails.plugins.databasemigration.PluginConstants.DEFAULT_DATASOURCE_NAME
+
+@CompileStatic
+trait ApplicationContextDatabaseMigrationCommand implements DatabaseMigrationCommand {
+
+ ConfigurableApplicationContext applicationContext
+
+ Boolean skipBootstrap = true
+
+ boolean handle(ExecutionContext executionContext) {
+ this.executionContext = executionContext
+ handle()
+ return true
+ }
+
+ void setExecutionContext(ExecutionContext executionContext) {
+ this.commandLine = executionContext.commandLine
+ this.contexts = optionValue('contexts')
+ this.defaultSchema = optionValue('defaultSchema')
+ this.dataSource = optionValue('dataSource') ?: DEFAULT_DATASOURCE_NAME
+ }
+
+ abstract void handle()
+
+ @Override
+ ConfigMap getConfig() {
+ applicationContext.getBean(GrailsApplication).config
+ }
+
+ void withGormDatabase(ConfigurableApplicationContext applicationContext, String dataSource,
+ @ClosureParams(value = SimpleType, options = 'liquibase.database.Database') Closure closure) {
+ def database = null
+ try {
+ database = createGormDatabase(applicationContext, dataSource)
+ closure.call(database)
+ } finally {
+ database?.close()
+ }
+ }
+
+ private Database createGormDatabase(ConfigurableApplicationContext applicationContext, String dataSource) {
+ String sessionFactoryName = "sessionFactory"
+ if (!isDefaultDataSource(dataSource)) {
+ sessionFactoryName = sessionFactoryName + '_' + dataSource
+ }
+
+ def serviceRegistry = applicationContext.getBean(sessionFactoryName, SessionFactoryImplementor).serviceRegistry.parentServiceRegistry
+
+ Dialect dialect = serviceRegistry.getService(JdbcServices.class).dialect
+
+ HibernateDatastore hibernateDatastore = applicationContext.getBean("hibernateDatastore", HibernateDatastore)
+ hibernateDatastore = hibernateDatastore.getDatastoreForConnection(dataSource)
+
+ Database database = new GormDatabase(dialect, serviceRegistry, hibernateDatastore)
+ configureDatabase(database)
+
+ return database
+ }
+
+ ConfigMap getEnvironmentConfig(String environment) {
+ return (ConfigMap) environmentWith(environment) {
+ new PropertySourcesConfig(((PropertySourcesConfig) config).getPropertySources())
+ }
+ }
+
+ private Object environmentWith(String environment, Closure closure) {
+ def originalEnvironment = Environment.current
+ System.setProperty(Environment.KEY, environment)
+ try {
+ return closure.call()
+ } finally {
+ System.setProperty(Environment.KEY, originalEnvironment.name)
+ }
+ }
+
+ void withTransaction(Closure callable) {
+ new DatabaseMigrationTransactionManager(this.applicationContext, this.dataSource).withTransaction(callable)
+ }
+
+ void configureLiquibase() {
+ def groovyChangeLogParser = ChangeLogParserFactory.instance.parsers.find { ChangeLogParser changeLogParser -> changeLogParser instanceof GroovyChangeLogParser } as GroovyChangeLogParser
+ groovyChangeLogParser.applicationContext = applicationContext
+ groovyChangeLogParser.config = config
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/command/DatabaseMigrationCommand.groovy b/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/command/DatabaseMigrationCommand.groovy
new file mode 100644
index 00000000000..9209981d191
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/command/DatabaseMigrationCommand.groovy
@@ -0,0 +1,424 @@
+/*
+ * Copyright 2015 original authors
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration.command
+
+import grails.config.ConfigMap
+import groovy.transform.CompileDynamic
+import groovy.transform.CompileStatic
+import groovy.transform.stc.ClosureParams
+import groovy.transform.stc.SimpleType
+import liquibase.Contexts
+import liquibase.LabelExpression
+import liquibase.Liquibase
+import liquibase.RuntimeEnvironment
+import liquibase.Scope
+import liquibase.changelog.ChangeLogIterator
+import liquibase.changelog.DatabaseChangeLog
+import liquibase.changelog.filter.ContextChangeSetFilter
+import liquibase.changelog.filter.CountChangeSetFilter
+import liquibase.changelog.filter.DbmsChangeSetFilter
+import liquibase.command.CommandScope
+import liquibase.command.core.DiffChangelogCommandStep
+import liquibase.command.core.DiffCommandStep
+import liquibase.command.core.GenerateChangelogCommandStep
+import liquibase.command.core.helpers.AbstractChangelogCommandStep
+import liquibase.command.core.helpers.DbUrlConnectionArgumentsCommandStep
+import liquibase.command.core.helpers.DbUrlConnectionCommandStep
+import liquibase.command.core.helpers.DiffOutputControlCommandStep
+import liquibase.command.core.helpers.PreCompareCommandStep
+import liquibase.command.core.helpers.ReferenceDbUrlConnectionCommandStep
+import liquibase.database.Database
+import liquibase.database.DatabaseConnection
+import liquibase.database.DatabaseFactory
+import liquibase.database.core.MSSQLDatabase
+import liquibase.database.core.OracleDatabase
+import liquibase.diff.compare.CompareControl
+import liquibase.diff.output.DiffOutputControl
+import liquibase.diff.output.StandardObjectChangeFilter
+import liquibase.exception.DatabaseException
+import liquibase.exception.LiquibaseException
+import liquibase.exception.LockException
+import liquibase.executor.Executor
+import liquibase.executor.ExecutorService
+import liquibase.executor.LoggingExecutor
+import liquibase.lockservice.LockService
+import liquibase.lockservice.LockServiceFactory
+import liquibase.parser.ChangeLogParserFactory
+import liquibase.resource.ClassLoaderResourceAccessor
+import liquibase.resource.CompositeResourceAccessor
+import liquibase.resource.FileSystemResourceAccessor
+import liquibase.resource.ResourceAccessor
+import liquibase.statement.core.RawSqlStatement
+import liquibase.structure.core.Catalog
+import liquibase.util.LiquibaseUtil
+import liquibase.util.StreamUtil
+import org.grails.build.parsing.CommandLine
+import org.grails.plugins.databasemigration.DatabaseMigrationException
+import org.grails.plugins.databasemigration.NoopVisitor
+
+import java.nio.file.Path
+import java.text.DateFormat
+import java.text.ParseException
+import java.text.SimpleDateFormat
+
+import static org.grails.plugins.databasemigration.DatabaseMigrationGrailsPlugin.getDataSourceName
+import static org.grails.plugins.databasemigration.DatabaseMigrationGrailsPlugin.isDefaultDataSource
+import static org.grails.plugins.databasemigration.PluginConstants.DATA_SOURCE_NAME_KEY
+import static org.grails.plugins.databasemigration.PluginConstants.DEFAULT_CHANGE_LOG_LOCATION
+
+@CompileStatic
+trait DatabaseMigrationCommand {
+
+ CommandLine commandLine
+
+ String defaultSchema
+ String dataSource
+ String contexts
+
+ abstract ConfigMap getConfig()
+
+ String optionValue(String name) {
+ commandLine.optionValue(name)?.toString()
+ }
+
+ boolean hasOption(String name) {
+ commandLine.hasOption(name)
+ }
+
+ String getContexts() {
+ if (contexts) {
+ return contexts
+ }
+ return config.getProperty("${configPrefix}.contexts".toString(), List)?.join(',')
+ }
+
+ List getArgs() {
+ commandLine.remainingArgs
+ }
+
+ File getChangeLogLocation() {
+ new File(config.getProperty("${configPrefix}.changelogLocation".toString(), String) ?: DEFAULT_CHANGE_LOG_LOCATION)
+ }
+
+ File getChangeLogFile() {
+ new File(changeLogLocation, changeLogFileName)
+ }
+
+ String getChangeLogFileName() {
+ def changelogFileName = config.getProperty("${configPrefix}.changelogFileName".toString(), String)
+ if (changelogFileName) {
+ return changelogFileName
+ }
+ return isDefaultDataSource(dataSource) ? 'changelog.groovy' : "changelog-${dataSource}.groovy"
+ }
+
+ File resolveChangeLogFile(String filename) {
+ if (!filename) {
+ return null
+ }
+ if (getExtension(filename)) {
+ return new File(changeLogLocation, filename)
+ }
+ if (dataSource) {
+ return new File(changeLogLocation, "${filename}-${dataSource}.groovy")
+ }
+ return new File(changeLogLocation, "${filename}.groovy")
+ }
+
+ Map getDataSourceConfig(ConfigMap config = this.config) {
+ def dataSourceName = dataSource ?: 'dataSource'
+
+ if (dataSourceName == 'dataSource' && config.containsKey(dataSourceName)) {
+ return (Map) (config.getProperty(dataSourceName, Map) ?: [:])
+ }
+
+ def dataSources = config.getProperty('dataSources', Map) ?: [:]
+ if (!dataSources) {
+ def defaultDataSource = config.getProperty('dataSource', Map)
+ if (defaultDataSource) {
+ dataSources['dataSource'] = defaultDataSource
+ }
+ }
+ return (Map) dataSources.get(dataSourceName)
+ }
+
+ void withFileOrSystemOutWriter(String filename, @ClosureParams(value = SimpleType, options = "java.io.Writer") Closure closure) {
+ if (!filename) {
+ closure.call(new PrintWriter(System.out))
+ return
+ }
+
+ def outputFile = new File(filename)
+ if (outputFile.parentFile && !outputFile.parentFile.exists()) {
+ outputFile.parentFile.mkdirs()
+ }
+ outputFile.withWriter { BufferedWriter writer ->
+ closure.call(writer)
+ }
+ }
+
+ boolean isTimeFormat(String time) {
+ time ==~ /\d{2}:\d{2}:\d{2}/
+ }
+
+ Date parseDateTime(String date, String time) throws ParseException {
+ time = time ?: '00:00:00'
+ DateFormat formatter = new SimpleDateFormat('yyyy-MM-dd HH:mm:ss')
+ formatter.parse("$date $time")
+ }
+
+ void withLiquibase(@ClosureParams(value = SimpleType, options = 'liquibase.Liquibase') Closure closure) {
+ def resourceAccessor = createResourceAccessor()
+
+ Path changeLogLocationPath = changeLogLocation.toPath()
+ Path changeLogFilePath = changeLogFile.toPath()
+ String relativePath = changeLogLocationPath.relativize(changeLogFilePath).toString()
+
+ withDatabase { Database database ->
+ Liquibase liquibase = new Liquibase(relativePath, resourceAccessor, database)
+ liquibase.changeLogParameters.set(DATA_SOURCE_NAME_KEY, getDataSourceName(dataSource))
+ closure.call(liquibase)
+ }
+ }
+
+ ResourceAccessor createResourceAccessor() {
+ new CompositeResourceAccessor(
+ new FileSystemResourceAccessor(changeLogLocation),
+ new ClassLoaderResourceAccessor())
+
+ }
+
+ void withDatabase(Map dataSourceConfig = null, @ClosureParams(value = SimpleType, options = 'liquibase.database.Database') Closure closure) {
+ def database = null
+ try {
+ database = createDatabase(defaultSchema, dataSource, dataSourceConfig ?: getDataSourceConfig())
+ closure.call(database)
+ } finally {
+ database?.close()
+ }
+ }
+
+ @CompileDynamic
+ Database createDatabase(String defaultSchema, String dataSource, Map dataSourceConfig) {
+ String password = dataSourceConfig.password ?: null
+
+ if (password && dataSourceConfig.passwordEncryptionCodec) {
+ def clazz = Class.forName(dataSourceConfig.passwordEncryptionCodec)
+ password = clazz.decode(password)
+ }
+
+ Database database = DatabaseFactory.getInstance().openDatabase(
+ dataSourceConfig.url,
+ dataSourceConfig.username ?: null,
+ password,
+ dataSourceConfig.driverClassName,
+ null,
+ null,
+ null,
+ new ClassLoaderResourceAccessor(Thread.currentThread().contextClassLoader)
+ )
+ configureDatabase(database)
+ return database
+ }
+
+ void configureDatabase(Database database) {
+ database.defaultSchemaName = defaultSchema
+ if (!database.supportsSchemas() && defaultSchema) {
+ database.defaultCatalogName = defaultSchema
+ }
+ database.databaseChangeLogTableName = config.getProperty("${configPrefix}.databaseChangeLogTableName".toString(), String)
+ database.databaseChangeLogLockTableName = config.getProperty("${configPrefix}.databaseChangeLogLockTableName".toString(), String)
+ }
+
+ void doGenerateChangeLog(File changeLogFile, Database originalDatabase) {
+ def changeLogFilePath = changeLogFile?.path
+ def compareControl = new CompareControl([] as CompareControl.SchemaComparison[], null as String)
+ DiffOutputControl diffOutputControl = createDiffOutputControl()
+
+ final CommandScope command = new CommandScope("groovyGenerateChangeLog");
+ command
+ .addArgumentValue(ReferenceDbUrlConnectionCommandStep.REFERENCE_DATABASE_ARG, originalDatabase)
+ .addArgumentValue(DbUrlConnectionArgumentsCommandStep.DATABASE_ARG, originalDatabase)
+ .addArgumentValue(PreCompareCommandStep.SNAPSHOT_TYPES_ARG, DiffCommandStep.parseSnapshotTypes(null as String))
+ .addArgumentValue(PreCompareCommandStep.COMPARE_CONTROL_ARG, compareControl)
+ .addArgumentValue(DiffChangelogCommandStep.CHANGELOG_FILE_ARG, changeLogFilePath)
+ .addArgumentValue(DiffOutputControlCommandStep.INCLUDE_CATALOG_ARG, diffOutputControl.getIncludeCatalog())
+ .addArgumentValue(DiffOutputControlCommandStep.INCLUDE_SCHEMA_ARG, diffOutputControl.getIncludeSchema())
+ .addArgumentValue(DiffOutputControlCommandStep.INCLUDE_TABLESPACE_ARG, diffOutputControl.getIncludeTablespace())
+ .addArgumentValue(GenerateChangelogCommandStep.OVERWRITE_OUTPUT_FILE_ARG, GenerateChangelogCommandStep.OVERWRITE_OUTPUT_FILE_ARG.getDefaultValue())
+ .addArgumentValue(GenerateChangelogCommandStep.RUN_ON_CHANGE_TYPES_ARG, AbstractChangelogCommandStep.RUN_ON_CHANGE_TYPES_ARG.getDefaultValue())
+ .addArgumentValue(GenerateChangelogCommandStep.REPLACE_IF_EXISTS_TYPES_ARG, AbstractChangelogCommandStep.REPLACE_IF_EXISTS_TYPES_ARG.getDefaultValue());
+
+
+ if(diffOutputControl.isReplaceIfExistsSet()) {
+ command.addArgumentValue(GenerateChangelogCommandStep.USE_OR_REPLACE_OPTION, true)
+ }
+ command.setOutput(System.out)
+ command.execute()
+ }
+
+ void doDiffToChangeLog(File changeLogFile, Database referenceDatabase, Database targetDatabase) {
+ def changeLogFilePath = changeLogFile?.path
+ def compareControl = new CompareControl([] as CompareControl.SchemaComparison[], null as String)
+ DiffOutputControl diffOutputControl = createDiffOutputControl()
+
+ final CommandScope command = new CommandScope("groovyDiffChangelog");
+ command
+ .addArgumentValue(ReferenceDbUrlConnectionCommandStep.REFERENCE_DATABASE_ARG, referenceDatabase)
+ .addArgumentValue(DbUrlConnectionArgumentsCommandStep.DATABASE_ARG, targetDatabase)
+ .addArgumentValue(PreCompareCommandStep.SNAPSHOT_TYPES_ARG, DiffCommandStep.parseSnapshotTypes(null as String))
+ .addArgumentValue(PreCompareCommandStep.COMPARE_CONTROL_ARG, compareControl)
+ .addArgumentValue(PreCompareCommandStep.OBJECT_CHANGE_FILTER_ARG, diffOutputControl.objectChangeFilter)
+ .addArgumentValue(DiffChangelogCommandStep.CHANGELOG_FILE_ARG, changeLogFilePath)
+ .addArgumentValue(DiffOutputControlCommandStep.INCLUDE_CATALOG_ARG, diffOutputControl.getIncludeCatalog())
+ .addArgumentValue(DiffOutputControlCommandStep.INCLUDE_SCHEMA_ARG, diffOutputControl.getIncludeSchema())
+ .addArgumentValue(DiffOutputControlCommandStep.INCLUDE_TABLESPACE_ARG, diffOutputControl.getIncludeTablespace())
+ .addArgumentValue(GenerateChangelogCommandStep.RUN_ON_CHANGE_TYPES_ARG, AbstractChangelogCommandStep.RUN_ON_CHANGE_TYPES_ARG.getDefaultValue())
+ .addArgumentValue(GenerateChangelogCommandStep.REPLACE_IF_EXISTS_TYPES_ARG, AbstractChangelogCommandStep.REPLACE_IF_EXISTS_TYPES_ARG.getDefaultValue());
+
+ if(diffOutputControl.isReplaceIfExistsSet()) {
+ command.addArgumentValue(GenerateChangelogCommandStep.USE_OR_REPLACE_OPTION, true)
+ }
+ command.setOutput(System.out)
+ command.execute()
+
+ }
+
+ void doGeneratePreviousChangesetSql(Writer output, Database database, Liquibase liquibase, String count, String skip) {
+ Contexts contexts = new Contexts(contexts)
+ LabelExpression labelExpression = liquibase.changeLogParameters.labels
+ liquibase.changeLogParameters.setContexts(contexts)
+
+ final ExecutorService executorService = Scope.getCurrentScope().getSingleton(ExecutorService.class)
+ final Executor oldTemplate = executorService.getExecutor("jdbc", database)
+ final LoggingExecutor outputTemplate = new LoggingExecutor(oldTemplate, output, database)
+ executorService.setExecutor("jdbc", database, outputTemplate)
+
+ outputHeader(outputTemplate, (String) "Previous $count SQL Changeset(s) Skipping $skip Script", liquibase, database)
+
+ LockService lockService = LockServiceFactory.getInstance().getLockService(database)
+ lockService.waitForLock()
+
+ try {
+ def parser = ChangeLogParserFactory.instance.getParser(liquibase.changeLogFile, liquibase.resourceAccessor)
+ DatabaseChangeLog changeLog = parser.parse(liquibase.changeLogFile, liquibase.changeLogParameters, liquibase.resourceAccessor)
+ liquibase.checkLiquibaseTables(true, changeLog, contexts, labelExpression)
+ changeLog.validate(database, contexts, labelExpression)
+ changeLog.changeSets.reverse(true)
+ skip.toInteger().times { changeLog.changeSets.remove(0) }
+
+ ChangeLogIterator logIterator = new ChangeLogIterator(changeLog,
+ new ContextChangeSetFilter(contexts),
+ new DbmsChangeSetFilter(database),
+ new CountChangeSetFilter(count.toInteger()))
+
+ logIterator.run(new NoopVisitor(database), new RuntimeEnvironment(database, contexts, labelExpression))
+
+ output.flush()
+ } finally {
+ try {
+ lockService.releaseLock()
+ executorService.setExecutor("jdbc", database, oldTemplate)
+ } catch (LockException e) {
+ throw new LiquibaseException(e.message, e.cause)
+ }
+ }
+ }
+
+ void outputHeader(Executor executor, String message, Liquibase liquibase, Database database) throws DatabaseException {
+ executor.comment("*********************************************************************")
+ executor.comment(message)
+ executor.comment("*********************************************************************")
+ executor.comment("Change Log: " + liquibase.changeLogFile)
+ executor.comment("Ran at: " + DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT).format(new Date()))
+ DatabaseConnection connection = liquibase.getDatabase().getConnection()
+ if (connection != null) {
+ executor.comment("Against: " + connection.getConnectionUserName() + "@" + connection.getURL())
+ }
+ executor.comment("Liquibase version: " + LiquibaseUtil.getBuildVersion())
+ executor.comment("*********************************************************************" + StreamUtil.getLineSeparator())
+
+ if (database instanceof OracleDatabase) {
+ executor.execute(new RawSqlStatement("SET DEFINE OFF;"))
+ }
+ if (database instanceof MSSQLDatabase && database.getDefaultCatalogName() != null) {
+ executor.execute(new RawSqlStatement("USE " + database.escapeObjectName(database.getDefaultCatalogName(), Catalog.class) + ";"))
+ }
+ }
+
+ private DiffOutputControl createDiffOutputControl() {
+ def diffOutputControl = new DiffOutputControl(false, false, false)
+
+ String excludeObjects = config.getProperty("${configPrefix}.excludeObjects".toString(), String)
+ String includeObjects = config.getProperty("${configPrefix}.includeObjects".toString(), String)
+ if (excludeObjects && includeObjects) {
+ throw new DatabaseMigrationException("Cannot specify both excludeObjects and includeObjects")
+ }
+ if (excludeObjects) {
+ diffOutputControl.objectChangeFilter = new StandardObjectChangeFilter(StandardObjectChangeFilter.FilterType.EXCLUDE, excludeObjects)
+ }
+ if (includeObjects) {
+ diffOutputControl.objectChangeFilter = new StandardObjectChangeFilter(StandardObjectChangeFilter.FilterType.INCLUDE, includeObjects)
+ }
+
+ diffOutputControl
+ }
+
+ void appendToChangeLog(File srcChangeLogFile, File destChangeLogFile) {
+ if (!srcChangeLogFile.exists() || srcChangeLogFile == destChangeLogFile) {
+ return
+ }
+
+ def relativePath = changeLogLocation.toPath().relativize(destChangeLogFile.toPath()).toString()
+ def extension = getExtension(srcChangeLogFile.name)?.toLowerCase()
+
+ switch (extension) {
+ case ['yaml', 'yml']:
+ srcChangeLogFile << """
+ |- include:
+ | file: ${relativePath}
+ """.stripMargin().trim()
+ break
+ case ['xml']:
+ def text = srcChangeLogFile.text
+ if (text =~ ']*/>') {
+ srcChangeLogFile.write(text.replaceFirst('(]*)/>', "\$1>\n \n"))
+ } else {
+ srcChangeLogFile.write(text.replaceFirst('', " \n\$0"))
+ }
+ break
+ case ['groovy']:
+ def text = srcChangeLogFile.text
+ srcChangeLogFile.write(text.replaceFirst('}.*$', " include file: '$relativePath'\n\$0"))
+ break
+ }
+ }
+
+ String getConfigPrefix() {
+ return isDefaultDataSource(dataSource) ?
+ 'grails.plugin.databasemigration' : "grails.plugin.databasemigration.${dataSource}"
+ }
+
+ private String getExtension(String fileName) {
+ String extension = ""
+
+ int i = fileName.lastIndexOf('.')
+ if (i > 0) {
+ extension = fileName.substring(i+1)
+ }
+ extension
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/command/DbmChangelogToGroovy.groovy b/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/command/DbmChangelogToGroovy.groovy
new file mode 100644
index 00000000000..c313f2c8e0c
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/command/DbmChangelogToGroovy.groovy
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2015 original authors
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration.command
+
+import groovy.transform.CompileStatic
+import groovy.transform.stc.ClosureParams
+import groovy.transform.stc.SimpleType
+import liquibase.parser.ChangeLogParserFactory
+import liquibase.serializer.ChangeLogSerializerFactory
+import org.grails.plugins.databasemigration.DatabaseMigrationException
+
+@CompileStatic
+class DbmChangelogToGroovy implements ScriptDatabaseMigrationCommand {
+
+ @Override
+ void handle() {
+ def srcFilename = args[0]
+ if (!srcFilename) {
+ throw new DatabaseMigrationException("The $name command requires a source filename")
+ }
+
+ def resourceAccessor = createResourceAccessor()
+
+ def parser = ChangeLogParserFactory.instance.getParser(srcFilename, resourceAccessor)
+ def databaseChangeLog = parser.parse(srcFilename, null, resourceAccessor)
+
+ def destFilename = args[1]
+ def destChangeLogFile = resolveChangeLogFile(destFilename)
+ if (destChangeLogFile) {
+ if (!destChangeLogFile.path.endsWith('.groovy')) {
+ throw new DatabaseMigrationException("Destination ChangeLogFile ${destChangeLogFile} must be a Groovy file")
+ }
+ if (destChangeLogFile.exists()) {
+ if (hasOption('force')) {
+ destChangeLogFile.delete()
+ } else {
+ throw new DatabaseMigrationException("ChangeLogFile ${destChangeLogFile} already exists!")
+ }
+ }
+ }
+
+ def serializer = ChangeLogSerializerFactory.instance.getSerializer('groovy')
+ withFileOrSystemOutputStream(destChangeLogFile) { OutputStream out ->
+ serializer.write(databaseChangeLog.changeSets, out)
+ }
+
+ if (destChangeLogFile && hasOption('add')) {
+ appendToChangeLog(changeLogFile, destChangeLogFile)
+ }
+ }
+
+ private static void withFileOrSystemOutputStream(File file, @ClosureParams(value = SimpleType, options = "java.io.OutputStream") Closure closure) {
+ if (!file) {
+ closure.call(System.out)
+ return
+ }
+
+ if (!file.parentFile.exists()) {
+ file.parentFile.mkdirs()
+ }
+ file.withOutputStream { OutputStream out ->
+ closure.call(out)
+ }
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/command/DbmCreateChangelog.groovy b/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/command/DbmCreateChangelog.groovy
new file mode 100644
index 00000000000..e7181826dfc
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/command/DbmCreateChangelog.groovy
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2015 original authors
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration.command
+
+import groovy.transform.CompileStatic
+import liquibase.serializer.ChangeLogSerializer
+import liquibase.serializer.ChangeLogSerializerFactory
+import org.grails.plugins.databasemigration.DatabaseMigrationException
+
+@CompileStatic
+class DbmCreateChangelog implements ScriptDatabaseMigrationCommand {
+
+ @Override
+ void handle() {
+ def filename = args[0]
+ if (!filename) {
+ throw new DatabaseMigrationException("The $name command requires a filename")
+ }
+
+ def outputChangeLogFile = resolveChangeLogFile(filename)
+ if (outputChangeLogFile.exists()) {
+ if (hasOption('force')) {
+ outputChangeLogFile.delete()
+ } else {
+ throw new DatabaseMigrationException("ChangeLogFile ${outputChangeLogFile} already exists!")
+ }
+ }
+ if (!outputChangeLogFile.parentFile.exists()) {
+ outputChangeLogFile.parentFile.mkdirs()
+ }
+
+ ChangeLogSerializer serializer = ChangeLogSerializerFactory.instance.getSerializer(filename)
+
+ outputChangeLogFile.withOutputStream { OutputStream out ->
+ serializer.write([], out)
+ }
+
+ if (hasOption('add')) {
+ appendToChangeLog(changeLogFile, outputChangeLogFile)
+ }
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/command/ScriptDatabaseMigrationCommand.groovy b/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/command/ScriptDatabaseMigrationCommand.groovy
new file mode 100644
index 00000000000..61e90f90af5
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/command/ScriptDatabaseMigrationCommand.groovy
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2015 original authors
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration.command
+
+import grails.config.ConfigMap
+import grails.util.Environment
+import grails.util.GrailsNameUtils
+import groovy.transform.CompileStatic
+import liquibase.parser.ChangeLogParser
+import liquibase.parser.ChangeLogParserFactory
+import org.grails.cli.profile.ExecutionContext
+import org.grails.config.CodeGenConfig
+import org.grails.plugins.databasemigration.EnvironmentAwareCodeGenConfig
+import org.grails.plugins.databasemigration.liquibase.GroovyChangeLogParser
+
+import static org.grails.plugins.databasemigration.PluginConstants.DEFAULT_DATASOURCE_NAME
+
+@CompileStatic
+trait ScriptDatabaseMigrationCommand implements DatabaseMigrationCommand {
+
+ ConfigMap config
+ ConfigMap sourceConfig
+ ExecutionContext executionContext
+
+ void handle(ExecutionContext executionContext) {
+ this.executionContext = executionContext
+ setConfig(executionContext.config)
+
+ this.commandLine = executionContext.commandLine
+ this.contexts = optionValue('contexts')
+ this.defaultSchema = optionValue('defaultSchema')
+ this.dataSource = optionValue('dataSource') ?: DEFAULT_DATASOURCE_NAME
+
+ configureLiquibase()
+ handle()
+ }
+
+ void configureLiquibase() {
+ GroovyChangeLogParser groovyChangeLogParser = ChangeLogParserFactory.instance.parsers.find { ChangeLogParser changeLogParser -> changeLogParser instanceof GroovyChangeLogParser } as GroovyChangeLogParser
+ groovyChangeLogParser.config = config
+ }
+
+ abstract void handle()
+
+ String getName() {
+ return GrailsNameUtils.getScriptName(GrailsNameUtils.getLogicalName(getClass().getName(), "Command"))
+ }
+
+ void setConfig(ConfigMap config) {
+ this.sourceConfig = config
+ this.config = new EnvironmentAwareCodeGenConfig(sourceConfig as CodeGenConfig, Environment.current.name)
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/liquibase/ChangelogXml2Groovy.groovy b/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/liquibase/ChangelogXml2Groovy.groovy
new file mode 100644
index 00000000000..d09df99e73b
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/liquibase/ChangelogXml2Groovy.groovy
@@ -0,0 +1,107 @@
+/* Copyright 2010-2013 SpringSource.
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration.liquibase
+
+import groovy.transform.CompileStatic
+import groovy.xml.XmlParser
+
+/**
+ * Generates a Groovy DSL version of a Liquibase XML changelog.
+ *
+ * @author Burt Beckwith
+ * @author Kazuki YAMAMOTO
+ */
+@CompileStatic
+class ChangelogXml2Groovy {
+
+ protected static final String NEWLINE = System.getProperty('line.separator')
+
+ /**
+ * Convert a Liquibase XML changelog to Groovy DSL format.
+ * @param xml the XML
+ * @return DSL format
+ */
+ static String convert(String xml) {
+ def groovy = new StringBuilder('databaseChangeLog = {')
+ groovy.append NEWLINE
+
+ new XmlParser(false, false).parseText(xml).each { Node node ->
+ convertNode(node, groovy, 1)
+ }
+ groovy.append '}'
+ groovy.append NEWLINE
+ groovy.toString()
+ }
+
+ protected static void convertNode(Node node, StringBuilder groovy, int indentLevel) {
+
+ groovy.append NEWLINE
+ appendWithIndent indentLevel, groovy, (String) node.name()
+
+ String mixedText
+ def children = []
+ for (child in node.children()) {
+ if (child instanceof String) {
+ mixedText = child
+ } else {
+ children << child
+ }
+ }
+
+ appendAttrs groovy, node, mixedText
+
+ if (children) {
+ groovy.append ' {'
+ for (child in children) {
+ convertNode((Node) child, groovy, indentLevel + 1)
+ }
+ appendWithIndent indentLevel, groovy, '}'
+ groovy.append NEWLINE
+ } else {
+ groovy.append NEWLINE
+ }
+ }
+
+ protected static void appendAttrs(StringBuilder groovy, Node node, String text) {
+ def local = new StringBuilder()
+
+ String delimiter = ''
+
+ if (text) {
+ local.append '"""'
+ local.append text.replaceAll(/(\$|\\)/, /\\$1/)
+ local.append '"""'
+ delimiter = ', '
+ }
+
+ node.attributes().each { name, value ->
+ local.append delimiter
+ local.append name
+ local.append(': "').append(((String) value).replaceAll(/(\$|\\|\\n)/, /\\$1/)).append('"')
+ delimiter = ', '
+ }
+
+ if (local.length()) {
+ groovy.append '('
+ groovy.append local.toString()
+ groovy.append ')'
+ }
+ }
+
+ protected static void appendWithIndent(int indentLevel, StringBuilder groovy, String s) {
+ indentLevel.times { groovy.append ' ' }
+ groovy.append s
+ }
+}
\ No newline at end of file
diff --git a/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/liquibase/DatabaseChangeLogBuilder.groovy b/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/liquibase/DatabaseChangeLogBuilder.groovy
new file mode 100644
index 00000000000..5ec0988d20b
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/liquibase/DatabaseChangeLogBuilder.groovy
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2015 original authors
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration.liquibase
+
+import groovy.transform.CompileStatic
+import liquibase.parser.core.ParsedNode
+import org.codehaus.groovy.runtime.InvokerHelper
+import org.grails.plugins.databasemigration.DatabaseMigrationException
+import org.springframework.context.ApplicationContext
+
+import static org.grails.plugins.databasemigration.PluginConstants.DATA_SOURCE_NAME_KEY
+
+@CompileStatic
+class DatabaseChangeLogBuilder extends BuilderSupport {
+
+ ApplicationContext applicationContext
+
+ String dataSourceName
+
+ @Override
+ protected void setParent(Object parent, Object child) {
+ }
+
+ @Override
+ protected Object createNode(Object name) {
+ def node = new ParsedNode(null, (String) name)
+ if (name == 'grailsChange' || name == 'grailsPrecondition') {
+ node.addChild(null, 'applicationContext', applicationContext)
+ node.addChild(null, DATA_SOURCE_NAME_KEY, dataSourceName)
+ }
+ if (currentNode) {
+ currentNode.addChild(node)
+ }
+ node
+ }
+
+ @Override
+ protected Object createNode(Object name, Object value) {
+ def node = new ParsedNode(null, (String) name)
+ node.value = value
+ if (currentNode) {
+ currentNode.addChild(node)
+ }
+ node
+ }
+
+ @Override
+ protected Object createNode(Object name, Map attributes) {
+ def node = new ParsedNode(null, (String) name)
+ attributes.each { Object key, Object value ->
+ node.addChild(null, (String) key, value)
+ }
+ currentNode.addChild(node)
+ node
+ }
+
+ @Override
+ protected Object createNode(Object name, Map attributes, Object value) {
+ def node = new ParsedNode(null, (String) name)
+ attributes.each { Object key, Object attrValue ->
+ node.addChild(null, (String) key, attrValue)
+ }
+ node.value = value
+ currentNode.addChild(node)
+ node
+ }
+
+ private ParsedNode getCurrentNode() {
+ (ParsedNode) current
+ }
+
+ @Override
+ Object invokeMethod(String methodName, Object args) {
+ if (currentNode?.name == 'grailsChange') {
+ processGrailsChangeProperty(methodName, args)
+ return null
+ } else if (currentNode?.name == 'grailsPrecondition') {
+ processGrailsPreconditionProperty(methodName, args)
+ return null
+ } else {
+ return super.invokeMethod(methodName, args)
+ }
+ }
+
+ protected void processGrailsChangeProperty(String methodName, Object args) {
+ def name = methodName.toLowerCase()
+ def arg = InvokerHelper.asList(args)[0]
+ if (name == 'init' && arg instanceof Closure) {
+ currentNode.addChild(null, 'init', arg)
+ } else if (name == 'validate' && arg instanceof Closure) {
+ currentNode.addChild(null, 'validate', arg)
+ } else if (name == 'change' && arg instanceof Closure) {
+ currentNode.addChild(null, 'change', arg)
+ } else if (name == 'rollback' && arg instanceof Closure) {
+ currentNode.addChild(null, 'rollback', arg)
+ } else if (name == 'confirm' && arg instanceof CharSequence) {
+ currentNode.addChild(null, 'confirm', arg)
+ } else if (name == 'checksum' && arg instanceof CharSequence) {
+ currentNode.addChild(null, 'checksum', arg)
+ } else {
+ throw new DatabaseMigrationException("Unknown method name: ${methodName}")
+ }
+ }
+
+ protected boolean processGrailsPreconditionProperty(String methodName, args) {
+ def name = methodName.toLowerCase()
+ def arg = InvokerHelper.asList(args)[0]
+ if (name == 'check' && arg instanceof Closure) {
+ currentNode.addChild(null, 'check', arg)
+ } else {
+ throw new DatabaseMigrationException("Unknown method name: ${methodName}")
+ }
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/liquibase/EmbeddedJarPathHandler.groovy b/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/liquibase/EmbeddedJarPathHandler.groovy
new file mode 100644
index 00000000000..0cc052eaa77
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/liquibase/EmbeddedJarPathHandler.groovy
@@ -0,0 +1,75 @@
+package org.grails.plugins.databasemigration.liquibase
+
+import groovy.transform.CompileStatic
+import liquibase.resource.AbstractPathResourceAccessor
+import liquibase.resource.PathResource
+import liquibase.resource.Resource
+import liquibase.resource.ResourceAccessor
+import liquibase.resource.ZipPathHandler
+
+import java.nio.file.FileSystem
+import java.nio.file.FileSystems
+import java.nio.file.Path
+import java.nio.file.Paths
+
+@CompileStatic
+class EmbeddedJarPathHandler extends ZipPathHandler {
+ @Override
+ int getPriority(String root) {
+ if (root.startsWith("jar:file:") && root.endsWith("!/")) { //only can handle `jar:` urls for the entire jar
+ if (parseJarPath(root).contains('!')) {
+ return PRIORITY_SPECIALIZED
+ }
+ }
+ PRIORITY_NOT_APPLICABLE
+ }
+
+ private String parseJarPath(String root) {
+ root.substring(9, root.lastIndexOf("!"))
+ }
+
+ @Override
+ ResourceAccessor getResourceAccessor(String root) throws FileNotFoundException {
+ String jarPath = parseJarPath(root)
+ new EmbeddedJarResourceAccessor(jarPath.split('!').toList())
+ }
+}
+
+@CompileStatic
+class EmbeddedJarResourceAccessor extends AbstractPathResourceAccessor {
+ private FileSystem fileSystem
+
+ EmbeddedJarResourceAccessor(List jarPaths) {
+ try {
+ Path firstPath = Paths.get(jarPaths.pop())
+ fileSystem = FileSystems.newFileSystem(firstPath, null as ClassLoader)
+
+ while(jarPaths) {
+ Path innerPath = fileSystem.getPath(jarPaths.pop())
+ fileSystem = FileSystems.newFileSystem(innerPath, null as ClassLoader)
+ }
+ } catch (e) {
+ throw new IllegalArgumentException(e.getMessage(), e)
+ }
+ }
+
+ @Override
+ void close() throws Exception {
+ //can't close the filesystem because they often get reused and/or are being used by other things
+ }
+
+ @Override
+ protected Path getRootPath() {
+ return this.fileSystem.getPath("/")
+ }
+
+ @Override
+ protected Resource createResource(Path file, String pathToAdd) {
+ return new PathResource(pathToAdd, file)
+ }
+
+ @Override
+ List describeLocations() {
+ return Collections.singletonList(fileSystem.toString())
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/liquibase/GormDatabase.groovy b/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/liquibase/GormDatabase.groovy
new file mode 100644
index 00000000000..5479aa05c2e
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/liquibase/GormDatabase.groovy
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2015 original authors
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration.liquibase
+
+import groovy.transform.CompileStatic
+import liquibase.database.DatabaseConnection
+import liquibase.database.OfflineConnection
+import liquibase.exception.DatabaseException
+import liquibase.ext.hibernate.database.HibernateDatabase
+import liquibase.snapshot.DatabaseSnapshot
+import liquibase.snapshot.JdbcDatabaseSnapshot
+import liquibase.snapshot.SnapshotControl
+import liquibase.structure.DatabaseObject
+import org.grails.orm.hibernate.HibernateDatastore
+import org.hibernate.boot.Metadata
+import org.hibernate.boot.MetadataSources
+import org.hibernate.dialect.Dialect
+import org.hibernate.service.ServiceRegistry
+
+@CompileStatic
+class GormDatabase extends HibernateDatabase {
+
+ final String shortName = 'GORM'
+ final String DefaultDatabaseProductName = 'getDefaultDatabaseProductName'
+
+ private Dialect dialect
+ private Metadata metadata
+ DatabaseConnection connection
+
+ GormDatabase() {
+ }
+
+ GormDatabase(Dialect dialect, ServiceRegistry serviceRegistry, HibernateDatastore hibernateDatastore) {
+ this.dialect = dialect
+ this.metadata = hibernateDatastore.getMetadata()
+ SnapshotControl snapshotControl = new SnapshotControl(this, null, null)
+ GormDatabase database = this
+ OfflineConnection connection = new OfflineConnection("offline:gorm", null) {
+ DatabaseSnapshot getSnapshot(DatabaseObject[] examples) {
+ new JdbcDatabaseSnapshot(examples, database, snapshotControl)
+ }
+ }
+ this.connection = connection
+ }
+
+ @Override
+ Dialect getDialect() {
+ dialect
+ }
+
+ /**
+ * Return the hibernate {@link Metadata} used by this database.
+ */
+ @Override
+ public Metadata getMetadata() {
+ metadata
+ }
+
+ @Override
+ protected void configureSources(MetadataSources sources) throws DatabaseException {
+ //no op
+ }
+
+
+ @Override
+ boolean isCorrectDatabaseImplementation(DatabaseConnection conn) throws DatabaseException {
+ return false
+ }
+
+}
+
+
+
+
+
diff --git a/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/liquibase/GrailsLiquibase.groovy b/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/liquibase/GrailsLiquibase.groovy
new file mode 100644
index 00000000000..70e8afae37f
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/liquibase/GrailsLiquibase.groovy
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2015 original authors
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration.liquibase
+
+import groovy.transform.CompileStatic
+import liquibase.Liquibase
+import liquibase.database.Database
+import liquibase.exception.DatabaseException
+import liquibase.exception.LiquibaseException
+import liquibase.integration.spring.SpringLiquibase
+import liquibase.resource.ResourceAccessor
+import org.springframework.context.ApplicationContext
+import org.springframework.core.io.DefaultResourceLoader
+
+import java.sql.Connection
+
+import static org.grails.plugins.databasemigration.PluginConstants.DATA_SOURCE_NAME_KEY
+
+@CompileStatic
+class GrailsLiquibase extends SpringLiquibase {
+
+ private ApplicationContext applicationContext
+
+ String dataSourceName
+
+ String databaseChangeLogTableName
+
+ String databaseChangeLogLockTableName
+
+ GrailsLiquibase(ApplicationContext applicationContext) {
+ this.applicationContext = applicationContext
+ this.resourceLoader = new DefaultResourceLoader()
+ }
+
+ @Override
+ protected Liquibase createLiquibase(Connection connection) throws LiquibaseException {
+ Liquibase liquibase = new Liquibase(getChangeLog(), createResourceOpener(), createDatabase (connection, null))
+ if (parameters != null) {
+ for (Map.Entry entry : parameters.entrySet()) {
+ liquibase.setChangeLogParameter(entry.getKey(), entry.getValue())
+ }
+ }
+ liquibase.setChangeLogParameter(DATA_SOURCE_NAME_KEY, dataSourceName)
+ if (isDropFirst()) {
+ liquibase.dropAll()
+ }
+
+ return liquibase
+ }
+
+
+ @Override
+ protected Database createDatabase(Connection connection, ResourceAccessor accessor) throws DatabaseException {
+ Database database = super.createDatabase(connection, accessor)
+
+ if (databaseChangeLogTableName) {
+ database.databaseChangeLogTableName = databaseChangeLogTableName
+ }
+ if (databaseChangeLogLockTableName) {
+ database.databaseChangeLogLockTableName = databaseChangeLogLockTableName
+ }
+
+ database
+ }
+
+ @Override
+ protected void performUpdate(Liquibase liquibase) throws LiquibaseException {
+ if (!applicationContext.containsBean('migrationCallbacks')) {
+ super.performUpdate(liquibase)
+ return
+ }
+
+ def database = liquibase.database
+ def migrationCallbacks = applicationContext.getBean('migrationCallbacks')
+
+ if (migrationCallbacks.metaClass.respondsTo(migrationCallbacks, 'beforeStartMigration')) {
+ migrationCallbacks.invokeMethod('beforeStartMigration', [database] as Object[])
+ }
+ if (migrationCallbacks.metaClass.respondsTo(migrationCallbacks, 'onStartMigration')) {
+ migrationCallbacks.invokeMethod('onStartMigration', [database, liquibase, changeLog] as Object[])
+ }
+
+ super.performUpdate(liquibase)
+
+ if (migrationCallbacks.metaClass.respondsTo(migrationCallbacks, 'afterMigrations')) {
+ migrationCallbacks.invokeMethod('afterMigrations', [database] as Object[])
+ }
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/liquibase/GrailsLiquibaseFactory.groovy b/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/liquibase/GrailsLiquibaseFactory.groovy
new file mode 100644
index 00000000000..48155967d27
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/liquibase/GrailsLiquibaseFactory.groovy
@@ -0,0 +1,24 @@
+package org.grails.plugins.databasemigration.liquibase
+
+import org.springframework.beans.factory.config.AbstractFactoryBean
+import org.springframework.context.ApplicationContext
+
+class GrailsLiquibaseFactory extends AbstractFactoryBean {
+
+ private final ApplicationContext applicationContext
+
+ GrailsLiquibaseFactory(ApplicationContext applicationContext) {
+ setSingleton(false)
+ this.applicationContext = applicationContext
+ }
+
+ @Override
+ Class> getObjectType() {
+ return GrailsLiquibase
+ }
+
+ @Override
+ protected GrailsLiquibase createInstance() throws Exception {
+ return new GrailsLiquibase(applicationContext)
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/liquibase/GroovyChange.groovy b/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/liquibase/GroovyChange.groovy
new file mode 100644
index 00000000000..86438085d91
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/liquibase/GroovyChange.groovy
@@ -0,0 +1,326 @@
+/*
+ * Copyright 2010-2013 SpringSource.
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration.liquibase
+
+import grails.config.Config
+import grails.core.GrailsApplication
+import groovy.sql.Sql
+import groovy.transform.CompileStatic
+import liquibase.Scope
+import liquibase.change.AbstractChange
+import liquibase.change.ChangeMetaData
+import liquibase.change.CheckSum
+import liquibase.change.DatabaseChange
+import liquibase.database.Database
+import liquibase.database.DatabaseConnection
+import liquibase.database.jvm.JdbcConnection
+import liquibase.exception.RollbackImpossibleException
+import liquibase.exception.SetupException
+import liquibase.exception.ValidationErrors
+import liquibase.exception.Warnings
+import liquibase.executor.ExecutorService
+import liquibase.executor.LoggingExecutor
+import liquibase.parser.core.ParsedNode
+import liquibase.parser.core.ParsedNodeException
+import liquibase.resource.ResourceAccessor
+import liquibase.statement.SqlStatement
+import org.grails.plugins.databasemigration.DatabaseMigrationTransactionManager
+import org.springframework.context.ApplicationContext
+
+import java.sql.Connection
+
+import static org.grails.plugins.databasemigration.PluginConstants.DATA_SOURCE_NAME_KEY
+
+/**
+ * Custom Groovy-based change.
+ *
+ * @author Burt Beckwith
+ * @author Kazuki YAMAMOTO
+ */
+@CompileStatic
+@DatabaseChange(name = "grailsChange", description = "Executes groovy code to apply a database change.", priority = ChangeMetaData.PRIORITY_DEFAULT)
+class GroovyChange extends AbstractChange {
+
+ ApplicationContext ctx
+
+ String dataSourceName
+
+ Closure initClosure
+
+ Closure validateClosure
+
+ Closure changeClosure
+
+ Closure rollbackClosure
+
+ String confirmationMessage
+
+ String checksumString
+
+ Database database
+
+ Sql sql
+
+ ValidationErrors validationErrors = new ValidationErrors()
+
+ Warnings warnings = new Warnings()
+
+ List allStatements = []
+
+ boolean initClosureCalled
+
+ boolean validateClosureCalled
+
+ boolean changeClosureCalled
+
+ @Override
+ void load(ParsedNode parsedNode, ResourceAccessor resourceAccessor) throws ParsedNodeException {
+ ctx = parsedNode.getChildValue(null, 'applicationContext', ApplicationContext)
+ dataSourceName = parsedNode.getChildValue(null, DATA_SOURCE_NAME_KEY, String)
+ if (dataSourceName?.startsWith("dataSource_")) {
+ dataSourceName = dataSourceName.substring("dataSource_".length())
+ }
+
+ initClosure = parsedNode.getChildValue(null, 'init', Closure)
+ initClosure?.setResolveStrategy(Closure.DELEGATE_FIRST)
+
+ validateClosure = parsedNode.getChildValue(null, 'validate', Closure)
+ validateClosure?.setResolveStrategy(Closure.DELEGATE_FIRST)
+
+ changeClosure = parsedNode.getChildValue(null, 'change', Closure)
+ changeClosure?.setResolveStrategy(Closure.DELEGATE_FIRST)
+
+ rollbackClosure = parsedNode.getChildValue(null, 'rollback', Closure)
+ rollbackClosure?.setResolveStrategy(Closure.DELEGATE_FIRST)
+
+ confirmationMessage = parsedNode.getChildValue(null, 'confirm', String)
+ checksumString = parsedNode.getChildValue(null, 'checksum', String)
+ }
+
+ @Override
+ void finishInitialization() throws SetupException {
+ if (!initClosure || initClosureCalled) {
+ return
+ }
+
+ initClosure.delegate = this
+ try {
+ initClosure()
+ } catch (Exception e) {
+ throw new SetupException(e)
+ } finally {
+ initClosureCalled = true
+ }
+ }
+
+ @Override
+ ValidationErrors validate(Database database) {
+ this.database = database
+
+ if (!validateClosure || validateClosureCalled || !shouldRun()) {
+ return validationErrors
+ }
+
+ validateClosure.delegate = this
+ try {
+ validateClosure()
+ } finally {
+ validateClosureCalled = true
+ }
+
+ return validationErrors
+ }
+
+ @Override
+ Warnings warn(Database database) {
+ validate(database)
+ warnings
+ }
+
+ @Override
+ SqlStatement[] generateStatements(Database database) {
+ this.database = database
+
+ if (shouldRun() && changeClosure) {
+ changeClosure.delegate = this
+ try {
+ if(!changeClosureCalled) {
+ withNewTransaction(changeClosure)
+ }
+ } finally {
+ changeClosureCalled = true
+ }
+ }
+
+ allStatements as SqlStatement[]
+ }
+
+ @Override
+ SqlStatement[] generateRollbackStatements(Database database) throws RollbackImpossibleException {
+ this.database = database
+
+ if (shouldRun() && rollbackClosure) {
+ rollbackClosure.delegate = this
+ rollbackClosure()
+ }
+
+ allStatements as SqlStatement[]
+ }
+
+ @Override
+ String getConfirmationMessage() {
+ confirmationMessage ?: 'Executed GrailsChange'
+ }
+
+ @Override
+ CheckSum generateCheckSum() {
+ CheckSum.compute checksumString ?: 'Grails Change'
+ }
+
+ @Override
+ boolean supportsRollback(Database database) {
+ this.database = database
+ shouldRun()
+ }
+
+ /**
+ * Called by the validate closure. Adds a validation error.
+ *
+ * @param message the error message
+ */
+ void error(String message) {
+ validationErrors.addError message
+ }
+
+ /**
+ * Called by the validate closure. Adds a warning message.
+ *
+ * @param warning the warning message
+ */
+ void warn(String warning) {
+ warnings.addWarning warning
+ }
+
+ /**
+ * Called by the change or rollback closure. Adds a statement to be executed.
+ *
+ * @param statement the statement
+ */
+ void sqlStatement(SqlStatement statement) {
+ if (statement) {
+ allStatements << statement
+ }
+ }
+
+ /**
+ * Called by the change or rollback closure. Adds multiple statements to be executed.
+ *
+ * @param statement the statement
+ */
+ void sqlStatements(List statements) {
+ if (statements) {
+ allStatements.addAll(statements as List)
+ }
+ }
+
+ /**
+ * Called by the change or rollback closure. Overrides the confirmation message.
+ *
+ * @param message the confirmation message
+ */
+ void confirm(String message) {
+ confirmationMessage = message
+ }
+
+ /**
+ * Called from the change or rollback closure. Creates a Sql instance from the current connection.
+ *
+ * @return the sql instance
+ */
+ Sql getSql() {
+ if (!connection) {
+ return null
+ }
+
+ if (!sql) {
+ sql = new Sql(connection) {
+ protected void closeResources(Connection c) {
+ // do nothing, let Liquibase close the connection
+ }
+ }
+ }
+
+ sql
+ }
+
+ /**
+ * Called from the change or rollback closure. Shortcut to get the (wrapper) database connection.
+ *
+ * @return the connection or null if the database isn't set yet
+ */
+ DatabaseConnection getDatabaseConnection() {
+ database?.connection
+ }
+
+ /**
+ * Called from the change or rollback closure. Shortcut to get the real database connection.
+ *
+ * @return the connection or null if the database isn't set yet
+ */
+ Connection getConnection() {
+ if (databaseConnection instanceof JdbcConnection) {
+ return ((JdbcConnection) database.connection).underlyingConnection
+ }
+ return null
+ }
+
+ /**
+ * Called from the change or rollback closure. Shortcut for the current application.
+ *
+ * @return the application
+ */
+ GrailsApplication getApplication() {
+ ctx.getBean(GrailsApplication)
+ }
+
+ /**
+ * Called from the change or rollback closure. Shortcut for the current config.
+ *
+ * @return the config
+ */
+ Config getConfig() {
+ application.config
+ }
+
+ /**
+ *
+ * @return Whether the database executor is instance of LoggingExecutor
+ */
+ protected boolean shouldRun() {
+ !(Scope.getCurrentScope().getSingleton(ExecutorService.class).getExecutor("jdbc", database) instanceof LoggingExecutor)
+ }
+
+ /**
+ * Executes the grailsChange>change block within the context of a new transaction
+ *
+ * @param callable The changeClosure to call
+ * @return The result of the closure execution
+ */
+ protected void withNewTransaction(Closure callable) {
+ new DatabaseMigrationTransactionManager(ctx, dataSourceName)
+ .withNewTransaction callable
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/liquibase/GroovyChangeLogParser.groovy b/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/liquibase/GroovyChangeLogParser.groovy
new file mode 100644
index 00000000000..12d8e4e37a6
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/liquibase/GroovyChangeLogParser.groovy
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2015 original authors
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration.liquibase
+
+import grails.config.ConfigMap
+import grails.io.IOUtils
+import groovy.transform.CompileDynamic
+import groovy.transform.CompileStatic
+import liquibase.changelog.ChangeLogParameters
+import liquibase.exception.ChangeLogParseException
+import liquibase.parser.core.ParsedNode
+import liquibase.parser.core.xml.AbstractChangeLogParser
+import liquibase.resource.ResourceAccessor
+import org.codehaus.groovy.control.CompilerConfiguration
+import org.springframework.context.ApplicationContext
+
+import static org.grails.plugins.databasemigration.PluginConstants.DATA_SOURCE_NAME_KEY
+
+@CompileStatic
+class GroovyChangeLogParser extends AbstractChangeLogParser {
+
+ final int priority = PRIORITY_DEFAULT
+
+ ApplicationContext applicationContext
+
+ ConfigMap config
+
+ @Override
+ @CompileDynamic
+ protected ParsedNode parseToNode(String physicalChangeLogLocation, ChangeLogParameters changeLogParameters, ResourceAccessor resourceAccessor) throws ChangeLogParseException {
+ def inputStream = null
+ def changeLogText = null
+ try {
+ inputStream = resourceAccessor.openStreams(null, physicalChangeLogLocation).first()
+ changeLogText = inputStream?.text
+ } finally {
+ IOUtils.closeQuietly(inputStream)
+ }
+
+ CompilerConfiguration compilerConfiguration = new CompilerConfiguration(CompilerConfiguration.DEFAULT)
+ if (compilerConfiguration.metaClass.respondsTo(compilerConfiguration, 'setDisabledGlobalASTTransformations')) {
+ Set disabled = compilerConfiguration.disabledGlobalASTTransformations ?: []
+ disabled << 'org.grails.datastore.gorm.query.transform.GlobalDetachedCriteriaASTTransformation'
+ compilerConfiguration.disabledGlobalASTTransformations = disabled
+ }
+
+ def changeLogProperties = config.getProperty('changelogProperties', Map) ?: [:]
+
+ try {
+ GroovyClassLoader classLoader = new GroovyClassLoader(Thread.currentThread().contextClassLoader, compilerConfiguration, false)
+ Script script = new GroovyShell(classLoader, new Binding(changeLogProperties), compilerConfiguration).parse(changeLogText as String)
+ script.run()
+
+ setChangeLogProperties(changeLogProperties, changeLogParameters)
+
+ Closure databaseChangeLogBlock = script.getProperty('databaseChangeLog') as Closure
+
+ DatabaseChangeLogBuilder builder = new DatabaseChangeLogBuilder()
+ builder.dataSourceName = changeLogParameters.getValue(DATA_SOURCE_NAME_KEY, null)
+ builder.applicationContext = applicationContext
+ builder.databaseChangeLog(databaseChangeLogBlock) as ParsedNode
+ } catch (Exception e) {
+ throw new ChangeLogParseException(e)
+ }
+ }
+
+ @Override
+ boolean supports(String changeLogFile, ResourceAccessor resourceAccessor) {
+ changeLogFile.endsWith('.groovy')
+ }
+
+ @CompileDynamic
+ protected void setChangeLogProperties(Map changeLogProperties, ChangeLogParameters changeLogParameters) {
+ changeLogProperties.each { name, value ->
+ String contexts = null
+ String labels = null
+ String databases = null
+ if (value instanceof Map) {
+ contexts = value.contexts
+ labels = value.labels
+ databases = value.databases
+ value = value.value
+ }
+ changeLogParameters.set(name as String, value as String, contexts as String, labels, databases, true, null)
+ }
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/liquibase/GroovyChangeLogSerializer.groovy b/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/liquibase/GroovyChangeLogSerializer.groovy
new file mode 100644
index 00000000000..4a9ca326081
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/liquibase/GroovyChangeLogSerializer.groovy
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2015 original authors
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration.liquibase
+
+import groovy.transform.CompileStatic
+import liquibase.changelog.ChangeLogChild
+import liquibase.changelog.ChangeSet
+import liquibase.serializer.ChangeLogSerializer
+import liquibase.serializer.LiquibaseSerializable
+import liquibase.serializer.core.xml.XMLChangeLogSerializer
+
+@CompileStatic
+class GroovyChangeLogSerializer implements ChangeLogSerializer {
+
+ private XMLChangeLogSerializer xmlChangeLogSerializer = new XMLChangeLogSerializer()
+
+ @Override
+ def void write(List changesets, OutputStream out) throws IOException {
+ def xmlOutputStrem = new ByteArrayOutputStream()
+ xmlChangeLogSerializer.write(changesets, xmlOutputStrem)
+ out << ChangelogXml2Groovy.convert(xmlOutputStrem.toString())
+ }
+
+ @Override
+ void append(ChangeSet changeSet, File changeLogFile) throws IOException {
+ throw new UnsupportedOperationException()
+ }
+
+ @Override
+ String[] getValidFileExtensions() {
+ ['groovy'] as String[]
+ }
+
+ @Override
+ String serialize(LiquibaseSerializable object, boolean pretty) {
+ throw new UnsupportedOperationException()
+ }
+
+ @Override
+ int getPriority() {
+ return 0
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/liquibase/GroovyDiffToChangeLogCommandStep.groovy b/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/liquibase/GroovyDiffToChangeLogCommandStep.groovy
new file mode 100644
index 00000000000..e995918350d
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/liquibase/GroovyDiffToChangeLogCommandStep.groovy
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2015 original authors
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration.liquibase
+
+import groovy.transform.CompileStatic
+import liquibase.command.CommandResultsBuilder
+import liquibase.command.CommandScope
+import liquibase.command.core.DiffChangelogCommandStep
+import liquibase.command.core.DiffCommandStep
+import liquibase.command.core.InternalSnapshotCommandStep
+import liquibase.command.core.helpers.DiffOutputControlCommandStep
+import liquibase.command.core.helpers.ReferenceDbUrlConnectionCommandStep
+import liquibase.database.Database
+import liquibase.database.ObjectQuotingStrategy
+import liquibase.diff.DiffResult
+import liquibase.diff.output.DiffOutputControl
+import liquibase.serializer.ChangeLogSerializerFactory
+import grails.util.GrailsStringUtils
+
+@CompileStatic
+class GroovyDiffToChangeLogCommandStep extends DiffChangelogCommandStep {
+
+ public static final String[] COMMAND_NAME = new String[] {"groovyDiffChangelog"}
+
+ @Override
+ void run(CommandResultsBuilder resultsBuilder) {
+ CommandScope commandScope = resultsBuilder.getCommandScope()
+ Database referenceDatabase = commandScope.getArgumentValue(ReferenceDbUrlConnectionCommandStep.REFERENCE_DATABASE_ARG);
+ String changeLogFile = commandScope.getArgumentValue(CHANGELOG_FILE_ARG);
+
+ InternalSnapshotCommandStep.logUnsupportedDatabase(referenceDatabase, this.getClass());
+
+ DiffCommandStep diffCommandStep = new DiffCommandStep()
+
+ DiffResult diffResult = diffCommandStep.createDiffResult(resultsBuilder);
+
+ PrintStream outputStream = new PrintStream(resultsBuilder.getOutputStream());
+
+ ObjectQuotingStrategy originalStrategy = referenceDatabase.getObjectQuotingStrategy();
+
+ DiffOutputControl diffOutputControl = (DiffOutputControl) resultsBuilder.getResult(DiffOutputControlCommandStep.DIFF_OUTPUT_CONTROL.getName())
+
+ try {
+ referenceDatabase.setObjectQuotingStrategy(ObjectQuotingStrategy.QUOTE_ALL_OBJECTS);
+ if (GrailsStringUtils.trimToNull(changeLogFile) == null) {
+ createDiffToChangeLogObject(diffResult, diffOutputControl, false).print(outputStream, ChangeLogSerializerFactory.instance.getSerializer('groovy'))
+ } else {
+ createDiffToChangeLogObject(diffResult, diffOutputControl, false).print(changeLogFile, ChangeLogSerializerFactory.instance.getSerializer(changeLogFile))
+ }
+ }
+ finally {
+ referenceDatabase.setObjectQuotingStrategy(originalStrategy);
+ outputStream.flush();
+ }
+ resultsBuilder.addResult("statusCode", 0);
+
+ }
+
+ @Override
+ String[][] defineCommandNames() {
+ return new String[][] { COMMAND_NAME }
+ }
+
+}
diff --git a/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/liquibase/GroovyGenerateChangeLogCommandStep.groovy b/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/liquibase/GroovyGenerateChangeLogCommandStep.groovy
new file mode 100644
index 00000000000..a46547c0cd4
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/liquibase/GroovyGenerateChangeLogCommandStep.groovy
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2015 original authors
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration.liquibase
+
+import groovy.transform.CompileStatic
+import liquibase.Scope
+import liquibase.command.CommandResultsBuilder
+import liquibase.command.CommandScope
+import liquibase.command.core.DiffCommandStep
+import liquibase.command.core.GenerateChangelogCommandStep
+import liquibase.command.core.InternalSnapshotCommandStep
+import liquibase.command.core.helpers.DiffOutputControlCommandStep
+import liquibase.command.core.helpers.ReferenceDbUrlConnectionCommandStep
+import liquibase.database.Database
+import liquibase.database.ObjectQuotingStrategy
+import liquibase.diff.DiffResult
+import liquibase.diff.output.DiffOutputControl
+import liquibase.diff.output.changelog.DiffToChangeLog
+import liquibase.serializer.ChangeLogSerializerFactory
+import grails.util.GrailsStringUtils
+
+@CompileStatic
+class GroovyGenerateChangeLogCommandStep extends GenerateChangelogCommandStep {
+
+ public static final String[] COMMAND_NAME = new String[] {"groovyGenerateChangeLog"};
+
+ private static final String INFO_MESSAGE =
+ "When generating formatted SQL changelogs, it is important to decide if batched statements\n" +
+ "should be split or not. For storedlogic objects, the default behavior is 'splitStatements:false'\n." +
+ "All other objects default to 'splitStatements:true'. See https://docs.liquibase.org for additional information.";
+
+ @Override
+ void run(CommandResultsBuilder resultsBuilder) throws Exception {
+ CommandScope commandScope = resultsBuilder.getCommandScope();
+
+ String changeLogFile = GrailsStringUtils.trimToNull(commandScope.getArgumentValue(CHANGELOG_FILE_ARG));
+ if (changeLogFile != null && changeLogFile.toLowerCase().endsWith(".sql")) {
+ Scope.getCurrentScope().getUI().sendMessage("\n" + INFO_MESSAGE + "\n");
+ Scope.getCurrentScope().getLog(getClass()).info("\n" + INFO_MESSAGE + "\n");
+ }
+
+ final Database referenceDatabase = commandScope.getArgumentValue(ReferenceDbUrlConnectionCommandStep.REFERENCE_DATABASE_ARG);
+
+ InternalSnapshotCommandStep.logUnsupportedDatabase(referenceDatabase, this.getClass());
+
+ DiffCommandStep diffCommandStep = new DiffCommandStep()
+
+ DiffResult diffResult = diffCommandStep.createDiffResult(resultsBuilder);
+
+ DiffOutputControl diffOutputControl = (DiffOutputControl) resultsBuilder.getResult(DiffOutputControlCommandStep.DIFF_OUTPUT_CONTROL.getName())
+
+ DiffToChangeLog changeLogWriter = new DiffToChangeLog(diffResult, diffOutputControl);
+
+ changeLogWriter.setChangeSetAuthor(commandScope.getArgumentValue(AUTHOR_ARG));
+ changeLogWriter.setChangeSetContext(commandScope.getArgumentValue(CONTEXT_ARG));
+ changeLogWriter.setChangeSetPath(changeLogFile);
+
+ ObjectQuotingStrategy originalStrategy = referenceDatabase.getObjectQuotingStrategy();
+ try {
+ referenceDatabase.setObjectQuotingStrategy(ObjectQuotingStrategy.QUOTE_ALL_OBJECTS);
+ if (GrailsStringUtils.trimToNull(changeLogFile) != null) {
+ changeLogWriter.print(changeLogFile, ChangeLogSerializerFactory.instance.getSerializer(changeLogFile))
+ } else {
+ PrintStream outputStream = new PrintStream(resultsBuilder.getOutputStream());
+ try {
+ changeLogWriter.print(outputStream, ChangeLogSerializerFactory.instance.getSerializer('groovy'))
+ } finally {
+ outputStream.flush()
+ }
+
+ }
+ if (GrailsStringUtils.trimToNull(changeLogFile) != null) {
+ Scope.getCurrentScope().getUI().sendMessage("Generated changelog written to " + new File(changeLogFile).getAbsolutePath());
+ }
+ } finally {
+ referenceDatabase.setObjectQuotingStrategy(originalStrategy);
+ }
+ }
+
+ @Override
+ String[][] defineCommandNames() {
+ return new String[][] { COMMAND_NAME }
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/liquibase/GroovyPrecondition.groovy b/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/liquibase/GroovyPrecondition.groovy
new file mode 100644
index 00000000000..a5cb0d7dd55
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/main/groovy/org/grails/plugins/databasemigration/liquibase/GroovyPrecondition.groovy
@@ -0,0 +1,196 @@
+/*
+ * Copyright 2010-2013 SpringSource.
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration.liquibase
+
+import grails.config.Config
+import grails.core.GrailsApplication
+import groovy.sql.Sql
+import groovy.transform.CompileStatic
+import liquibase.CatalogAndSchema
+import liquibase.changelog.ChangeSet
+import liquibase.changelog.DatabaseChangeLog
+import liquibase.changelog.visitor.ChangeExecListener
+import liquibase.database.Database
+import liquibase.database.DatabaseConnection
+import liquibase.database.jvm.JdbcConnection
+import liquibase.exception.DatabaseException
+import liquibase.exception.PreconditionErrorException
+import liquibase.exception.PreconditionFailedException
+import liquibase.exception.ValidationErrors
+import liquibase.exception.Warnings
+import liquibase.parser.core.ParsedNode
+import liquibase.parser.core.ParsedNodeException
+import liquibase.precondition.AbstractPrecondition
+import liquibase.resource.ResourceAccessor
+import liquibase.snapshot.DatabaseSnapshot
+import liquibase.snapshot.SnapshotControl
+import liquibase.snapshot.SnapshotGeneratorFactory
+import org.springframework.context.ApplicationContext
+
+import java.sql.Connection
+
+/**
+ * Custom Groovy-based precondition.
+ *
+ * @author Burt Beckwith
+ * @author Kazuki YAMAMOTO
+ */
+@CompileStatic
+class GroovyPrecondition extends AbstractPrecondition {
+
+ final String serializedObjectNamespace = STANDARD_CHANGELOG_NAMESPACE
+
+ final String name = 'grailsPrecondition'
+
+ Closure checkClosure
+
+ Database database
+
+ DatabaseChangeLog changeLog
+
+ ChangeSet changeSet
+
+ ResourceAccessor resourceAccessor
+
+ ApplicationContext ctx
+
+ Sql sql
+
+ @Override
+ void load(ParsedNode parsedNode, ResourceAccessor resourceAccessor) throws ParsedNodeException {
+ this.resourceAccessor = resourceAccessor
+
+ ctx = parsedNode.getChildValue(null, 'applicationContext', ApplicationContext)
+ checkClosure = parsedNode.getChildValue(null, 'check', Closure)
+ checkClosure?.setResolveStrategy(Closure.DELEGATE_FIRST)
+ }
+
+ @Override
+ Warnings warn(Database database) {
+ new Warnings()
+ }
+
+ @Override
+ ValidationErrors validate(Database database) {
+ new ValidationErrors()
+ }
+
+ @Override
+ void check(Database database, DatabaseChangeLog changeLog, ChangeSet changeSet, ChangeExecListener changeExecListener) throws PreconditionFailedException, PreconditionErrorException {
+ this.database = database
+ this.changeLog = changeLog
+ this.changeSet = changeSet
+
+ if (!checkClosure) {
+ return
+ }
+
+ checkClosure.delegate = this
+
+ try {
+ checkClosure()
+ } catch (PreconditionFailedException e) {
+ throw e
+ } catch (AssertionError e) {
+ throw new PreconditionFailedException(e.message, changeLog, this)
+ } catch (Exception e) {
+ throw new PreconditionErrorException(e, changeLog, this)
+ }
+ }
+
+ /**
+ * Called from the change or rollback closure. Creates a Sql instance from the current connection.
+ *
+ * @return the sql instance
+ */
+ Sql getSql() {
+ if (!connection) {
+ return null
+ }
+
+ if (!sql) {
+ sql = new Sql(connection) {
+ protected void closeResources(Connection c) {
+ // do nothing, let Liquibase close the connection
+ }
+ }
+ }
+
+ sql
+ }
+
+ /**
+ * Called from the change or rollback closure. Shortcut to get the (wrapper) database connection.
+ *
+ * @return the connection or null if the database isn't set yet
+ */
+ DatabaseConnection getDatabaseConnection() {
+ database?.connection
+ }
+
+ /**
+ * Called from the change or rollback closure. Shortcut to get the real database connection.
+ *
+ * @return the connection or null if the database isn't set yet
+ */
+ Connection getConnection() {
+ if (databaseConnection instanceof JdbcConnection) {
+ return ((JdbcConnection) database.connection).underlyingConnection
+ }
+ return null
+ }
+
+ /**
+ * Called from the change or rollback closure. Shortcut for the current application.
+ *
+ * @return the application
+ */
+ GrailsApplication getApplication() {
+ ctx.getBean(GrailsApplication)
+ }
+
+ /**
+ * Called from the change or rollback closure. Shortcut for the current config.
+ *
+ * @return the config
+ */
+ Config getConfig() {
+ application.config
+ }
+
+ /**
+ * Called from the check closure as a shortcut to throw a PreconditionFailedException.
+ *
+ * @param message the failure message
+ */
+ void fail(String message) {
+ throw new PreconditionFailedException(message, changeLog, this)
+ }
+
+ /**
+ * Called from the check closure.
+ *
+ * @param schemaName the schema name
+ * @return a snapshot for the current database and schema name
+ */
+ DatabaseSnapshot createDatabaseSnapshot(String schemaName = null) {
+ try {
+ return SnapshotGeneratorFactory.instance.createSnapshot(new CatalogAndSchema(null, schemaName), database, new SnapshotControl(database))
+ } catch (DatabaseException e) {
+ throw new PreconditionErrorException(e, changeLog, this)
+ }
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/src/main/resources/META-INF/services/liquibase.change.Change b/grails-data-hibernate5/database-migration/src/main/resources/META-INF/services/liquibase.change.Change
new file mode 100644
index 00000000000..28032a072dd
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/main/resources/META-INF/services/liquibase.change.Change
@@ -0,0 +1 @@
+org.grails.plugins.databasemigration.liquibase.GroovyChange
\ No newline at end of file
diff --git a/grails-data-hibernate5/database-migration/src/main/resources/META-INF/services/liquibase.command.CommandStep b/grails-data-hibernate5/database-migration/src/main/resources/META-INF/services/liquibase.command.CommandStep
new file mode 100644
index 00000000000..57807cf02f8
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/main/resources/META-INF/services/liquibase.command.CommandStep
@@ -0,0 +1,2 @@
+org.grails.plugins.databasemigration.liquibase.GroovyDiffToChangeLogCommandStep
+org.grails.plugins.databasemigration.liquibase.GroovyGenerateChangeLogCommandStep
\ No newline at end of file
diff --git a/grails-data-hibernate5/database-migration/src/main/resources/META-INF/services/liquibase.database.Database b/grails-data-hibernate5/database-migration/src/main/resources/META-INF/services/liquibase.database.Database
new file mode 100644
index 00000000000..ac4dce94511
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/main/resources/META-INF/services/liquibase.database.Database
@@ -0,0 +1 @@
+org.grails.plugins.databasemigration.liquibase.GormDatabase
\ No newline at end of file
diff --git a/grails-data-hibernate5/database-migration/src/main/resources/META-INF/services/liquibase.parser.ChangeLogParser b/grails-data-hibernate5/database-migration/src/main/resources/META-INF/services/liquibase.parser.ChangeLogParser
new file mode 100644
index 00000000000..34140176c3e
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/main/resources/META-INF/services/liquibase.parser.ChangeLogParser
@@ -0,0 +1 @@
+org.grails.plugins.databasemigration.liquibase.GroovyChangeLogParser
\ No newline at end of file
diff --git a/grails-data-hibernate5/database-migration/src/main/resources/META-INF/services/liquibase.precondition.Precondition b/grails-data-hibernate5/database-migration/src/main/resources/META-INF/services/liquibase.precondition.Precondition
new file mode 100644
index 00000000000..fef65c4e6ce
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/main/resources/META-INF/services/liquibase.precondition.Precondition
@@ -0,0 +1 @@
+org.grails.plugins.databasemigration.liquibase.GroovyPrecondition
\ No newline at end of file
diff --git a/grails-data-hibernate5/database-migration/src/main/resources/META-INF/services/liquibase.resource.PathHandler b/grails-data-hibernate5/database-migration/src/main/resources/META-INF/services/liquibase.resource.PathHandler
new file mode 100644
index 00000000000..4675c5fdc95
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/main/resources/META-INF/services/liquibase.resource.PathHandler
@@ -0,0 +1 @@
+org.grails.plugins.databasemigration.liquibase.EmbeddedJarPathHandler
\ No newline at end of file
diff --git a/grails-data-hibernate5/database-migration/src/main/resources/META-INF/services/liquibase.serializer.ChangeLogSerializer b/grails-data-hibernate5/database-migration/src/main/resources/META-INF/services/liquibase.serializer.ChangeLogSerializer
new file mode 100644
index 00000000000..ea14cdd7d56
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/main/resources/META-INF/services/liquibase.serializer.ChangeLogSerializer
@@ -0,0 +1 @@
+org.grails.plugins.databasemigration.liquibase.GroovyChangeLogSerializer
\ No newline at end of file
diff --git a/grails-data-hibernate5/database-migration/src/main/resources/migration.gdsl b/grails-data-hibernate5/database-migration/src/main/resources/migration.gdsl
new file mode 100644
index 00000000000..8622d7049ca
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/main/resources/migration.gdsl
@@ -0,0 +1,686 @@
+migrationDir = ".*/grails-app/migrations/.*"
+
+final String STRING = String.name
+
+contributor(context(pathRegexp: migrationDir, scope: scriptScope())) {
+ property name: "databaseChangeLog", type: {}
+}
+
+def changelogBody = context(scope: closureScope())
+contributor([changelogBody]) {
+ method name: "changeSet", params: [
+ args: [
+ parameter(name: 'id', type: STRING),
+ parameter(name: 'author', type: STRING),
+ parameter(name: 'dbms', type: STRING),
+ parameter(name: 'runAlways', type: STRING),
+ parameter(name: 'runOnChange', type: STRING),
+ parameter(name: 'context', type: STRING),
+ parameter(name: 'runInTransaction', type: STRING),
+ parameter(name: 'failOnError', type: STRING),
+ parameter(name: 'description', type: STRING)
+ ],
+ body: {}
+
+ ], type: void
+
+ method name: "include", params: [
+ args: [parameter(name: 'file', type: STRING)]
+ ], type: void
+}
+
+void provideChildsOf(String parentMethod, Closure callback) {
+ provideChildsOf(parentMethod, true, callback)
+}
+
+void provideChildsOf(String parentMethod, boolean isArg, Closure callback) {
+ def c = context(scope: closureScope(isArg: isArg), pathRegexp: migrationDir)
+
+ contributor([c]) {
+ if (enclosingCall(parentMethod)) {
+ Closure cloned = callback.clone()
+ cloned.delegate = delegate
+ cloned.call()
+ }
+ }
+}
+
+//Grails changes
+provideChildsOf("changeSet") {
+ method name: "grailsChange", params: [:], body: {}
+}
+
+provideChildsOf("grailsChange") {
+ method name: "init", params: [:], body: {}, type: void
+ method name: "validate", params: [:], body: {}, type: void
+ method name: "change", params: [:], body: {}, type: void
+ method name: "rollback", params: [:], body: {}, type: void
+ method name: "confirm", params: [:], body: {}, type: void
+ method name: "checkSum", params: [:], body: {}, type: void
+}
+
+provideChildsOf("change") {
+ property name: "changeSet", type: "liquibase.changelog.ChangeSet"
+ property name: "resourceAccessor", type: "liquibase.resource.ResourceAccessor"
+ property name: "ctx", type: "org.springframework.context.ApplicationContext"
+ property name: "application", type: "org.codehaus.groovy.grails.commons.GrailsApplication"
+ property name: "database", type: "liquibase.database.Database"
+ property name: "databaseConnection", type: "liquibase.database.DatabaseConnection"
+ property name: "connection", type: "java.sql.Connection"
+ property name: "sql", type: "groovy.sql.Sql"
+}
+
+provideChildsOf("rollback") {
+ property name: "database", type: "liquibase.database.Database"
+ property name: "databaseConnection", type: "liquibase.database.DatabaseConnection"
+ property name: "connection", type: "java.sql.Connection"
+ property name: "sql", type: "groovy.sql.Sql"
+}
+
+
+provideChildsOf("changeSet") {
+ method name: "sql", params: [query: "java.lang.String"], type: void
+}
+
+provideChildsOf("changeSet") {
+ method name: "sqlFile", params: [
+ args: [
+ parameter(name: "dbms", type: STRING),
+ parameter(name: "encoding", type: STRING),
+ parameter(name: "endDelimiter", type: STRING),
+ parameter(name: "path", type: STRING),
+ parameter(name: "relativeToChangelogFile", type: STRING),
+ parameter(name: "splitStatements", type: STRING),
+ parameter(name: "stripComments", type: STRING),
+ ]
+ ], type: void
+}
+
+provideChildsOf("changeSet") {
+ method name: "createSequence", params: [
+ args: [
+ parameter(name: "catalogName", type: STRING),
+ parameter(name: "cycle", type: STRING),
+ parameter(name: "incrementBy", type: STRING),
+ parameter(name: "maxValue", type: STRING),
+ parameter(name: "minValue", type: STRING),
+ parameter(name: "ordered", type: STRING),
+ parameter(name: "schemaName", type: STRING),
+ parameter(name: "sequenceName", type: STRING),
+ parameter(name: "startValue", type: STRING)
+ ]
+ ], type: void
+}
+
+//liquibase changes
+List columnArgs = [
+ parameter(name: 'name', type: STRING),
+ parameter(name: 'type', type: STRING),
+ parameter(name: 'value', type: STRING),
+ parameter(name: 'valueNumeric', type: STRING),
+ parameter(name: 'valueBoolean', type: STRING),
+ parameter(name: 'valueDate', type: STRING),
+ parameter(name: 'defaultValue', type: STRING),
+ parameter(name: 'defaultValueNumeric', type: STRING),
+ parameter(name: 'defaultValueBoolean', type: STRING),
+ parameter(name: 'defaultValueDate', type: STRING),
+ parameter(name: 'autoIncrement', type: STRING),
+ parameter(name: 'beforeColumn', type: STRING),
+ parameter(name: 'afterColumn', type: STRING),
+ parameter(name: 'position', type: STRING),
+ parameter(name: 'descending', type: STRING),
+
+]
+
+columnType = {
+ method name: "column", params: [
+ args: columnArgs,
+ body: {}
+ ], type: void
+}
+
+columnTypeNoBody = {
+ method name: "column", params: [
+ args: columnArgs
+ ], type: void
+}
+
+List param = [
+ parameter(name: 'name', type: STRING),
+ parameter(name: 'value', type: STRING)
+]
+
+provideChildsOf("changeSet") {
+ method name: "createTable", params: [
+ args: [
+ parameter(name: 'catalogName', type: STRING),
+ parameter(name: 'remarks', type: STRING),
+ parameter(name: 'schemaName', type: STRING),
+ parameter(name: 'tableName', type: STRING),
+ parameter(name: 'tablespace', type: STRING),
+ ],
+ body: {}
+ ], type: void
+
+}
+provideChildsOf("createTable", columnType)
+provideChildsOf("createTable", columnTypeNoBody)
+
+provideChildsOf("changeSet") {
+ method name: "createView", params: [
+ args: [
+ parameter(name: 'catalogName', type: STRING),
+ parameter(name: 'replaceIfExists', type: STRING),
+ parameter(name: 'schemaName', type: STRING),
+ parameter(name: 'selectQuery', type: STRING),
+ parameter(name: 'viewName', type: STRING)
+ ]
+ ], type: void
+
+}
+
+provideChildsOf("column") {
+ method name: "constraints", params: [
+ args: [
+ parameter(name: 'nullable', type: STRING),
+ parameter(name: 'primaryKey', type: STRING),
+ parameter(name: 'primaryKeyName', type: STRING),
+ parameter(name: 'unique', type: STRING),
+ parameter(name: 'uniqueConstraintName', type: STRING),
+ parameter(name: 'references', type: STRING),
+ parameter(name: 'foreignKeyName', type: STRING),
+ parameter(name: 'deleteCascade', type: STRING),
+ parameter(name: 'deferrable', type: STRING),
+ parameter(name: 'initiallyDeferred', type: STRING)
+ ]
+ ], type: void
+}
+
+
+provideChildsOf("changeSet") {
+ method name: "addAutoIncrement", params: [
+ args: [
+ parameter(name: 'catalogName', type: STRING),
+ parameter(name: 'columnDataType', type: STRING),
+ parameter(name: 'columnName', type: STRING),
+ parameter(name: 'incrementBy', type: STRING),
+ parameter(name: 'schemaName', type: STRING),
+ parameter(name: 'startWith', type: STRING),
+ parameter(name: 'tableName', type: STRING)
+ ]
+ ], type: void
+
+}
+
+provideChildsOf("changeSet") {
+ method name: "addColumn", params: [
+ args: [
+ parameter(name: 'catalogName', type: STRING),
+ parameter(name: 'schemaName', type: STRING),
+ parameter(name: 'tableName', type: STRING)
+ ],
+ body: {}
+ ], type: void
+}
+provideChildsOf("addColumn", columnType)
+provideChildsOf("addColumn", columnTypeNoBody)
+
+provideChildsOf("changeSet") {
+ method name: "addDefaultValue", params: [
+ args: [
+ parameter(name: 'catalogName', type: STRING),
+ parameter(name: 'columnDataType', type: STRING),
+ parameter(name: 'columnName', type: STRING),
+ parameter(name: 'defaultValue', type: STRING),
+ parameter(name: 'defaultValueBoolean', type: STRING),
+ parameter(name: 'defaultValueComputed', type: STRING),
+ parameter(name: 'defaultValueDate', type: STRING),
+ parameter(name: 'defaultValueNumeric', type: STRING),
+ parameter(name: 'defaultValueSequenceNext', type: STRING),
+ parameter(name: 'schemaName', type: STRING),
+ parameter(name: 'tableName', type: STRING)
+
+ ]
+ ], type: void
+}
+
+provideChildsOf("changeSet") {
+ method name: "addForeignKeyConstraint", params: [
+ args: [
+ parameter(name: 'baseColumnNames', type: STRING),
+ parameter(name: 'baseTableCatalogName', type: STRING),
+ parameter(name: 'baseTableName', type: STRING),
+ parameter(name: 'baseTableSchemaName', type: STRING),
+ parameter(name: 'constraintName', type: STRING),
+ parameter(name: 'deferrable', type: STRING),
+ parameter(name: 'initiallyDeferred', type: STRING),
+ parameter(name: 'onDelete', type: STRING),
+ parameter(name: 'onUpdate', type: STRING),
+ parameter(name: 'referencedColumnNames', type: STRING),
+ parameter(name: 'referencedTableCatalogName', type: STRING),
+ parameter(name: 'referencedTableName', type: STRING),
+ parameter(name: 'referencedTableSchemaName', type: STRING),
+ parameter(name: 'referencesUniqueColumn', type: STRING),
+ ]
+ ], type: void
+}
+
+provideChildsOf("changeSet") {
+ method name: "addNotNullConstraint", params: [
+ args: [
+ parameter(name: 'catalogName', type: STRING),
+ parameter(name: 'columnDataType', type: STRING),
+ parameter(name: 'columnName', type: STRING),
+ parameter(name: 'defaultNullValue', type: STRING),
+ parameter(name: 'schemaName', type: STRING),
+ parameter(name: 'tableName', type: STRING)
+ ]
+ ], type: void
+}
+
+provideChildsOf("changeSet") {
+ method name: "addPrimaryKey", params: [
+ args: [
+ parameter(name: 'catalogName', type: STRING),
+ parameter(name: 'columnNames', type: STRING),
+ parameter(name: 'constraintName', type: STRING),
+ parameter(name: 'schemaName', type: STRING),
+ parameter(name: 'tableName', type: STRING),
+ parameter(name: 'tablespace', type: STRING)
+ ]
+ ], type: void
+}
+
+provideChildsOf("changeSet") {
+ method name: "addUniqueConstraint", params: [
+ args: [
+ parameter(name: 'catalogName', type: STRING),
+ parameter(name: 'columnNames', type: STRING),
+ parameter(name: 'constraintName', type: STRING),
+ parameter(name: 'deferrable', type: STRING),
+ parameter(name: 'disabled', type: STRING),
+ parameter(name: 'initiallyDeferred', type: STRING),
+ parameter(name: 'schemaName', type: STRING),
+ parameter(name: 'tableName', type: STRING),
+ parameter(name: 'tablespace', type: STRING)
+ ]
+ ], type: void
+}
+
+provideChildsOf("changeSet") {
+ method name: "createIndex", params: [
+ args: [
+ parameter(name: 'catalogName', type: STRING),
+ parameter(name: 'indexName', type: STRING),
+ parameter(name: 'schemaName', type: STRING),
+ parameter(name: 'tableName', type: STRING),
+ parameter(name: 'tablespace', type: STRING),
+ parameter(name: 'unique', type: STRING),
+ ],
+ body: {}
+ ], type: void
+}
+provideChildsOf("createIndex", columnType)
+provideChildsOf("createIndex", columnTypeNoBody)
+
+provideChildsOf("changeSet") {
+ method name: "delete", params: [
+ args: [
+ parameter(name: 'catalogName', type: STRING),
+ parameter(name: 'schemaName', type: STRING),
+ parameter(name: 'tableName', type: STRING),
+ parameter(name: 'where', type: STRING)
+ ]
+ ], type: void
+}
+
+provideChildsOf("changeSet") {
+ method name: "dropAllForeignKeyConstraints", params: [
+ args: [
+ parameter(name: 'baseTableCatalogName', type: STRING),
+ parameter(name: 'baseTableName', type: STRING),
+ parameter(name: 'baseTableSchemaName', type: STRING)
+ ]
+ ], type: void
+}
+
+provideChildsOf("changeSet") {
+ method name: "dropColumn", params: [
+ args: [
+ parameter(name: 'catalogName', type: STRING),
+ parameter(name: 'columnName', type: STRING),
+ parameter(name: 'schemaName', type: STRING),
+ parameter(name: 'tableName', type: STRING)
+ ]
+ ], type: void
+}
+
+provideChildsOf("changeSet") {
+ method name: "dropDefaultValue", params: [
+ args: [
+ parameter(name: 'catalogName', type: STRING),
+ parameter(name: 'columnDataType', type: STRING),
+ parameter(name: 'columnName', type: STRING),
+ parameter(name: 'schemaName', type: STRING),
+ parameter(name: 'tableName', type: STRING)
+ ]
+ ], type: void
+}
+
+provideChildsOf("changeSet") {
+ method name: "dropForeignKeyConstraint", params: [
+ args: [
+ parameter(name: 'baseTableCatalogName', type: STRING),
+ parameter(name: 'baseTableName', type: STRING),
+ parameter(name: 'baseTableSchemaName', type: STRING),
+ parameter(name: 'constraintName', type: STRING)
+ ]
+ ], type: void
+}
+
+provideChildsOf("changeSet") {
+ method name: "dropIndex", params: [
+ args: [
+ parameter(name: 'catalogName', type: STRING),
+ parameter(name: 'indexName', type: STRING),
+ parameter(name: 'schemaName', type: STRING),
+ parameter(name: 'tableName', type: STRING)
+ ]
+ ], type: void
+}
+
+provideChildsOf("changeSet") {
+ method name: "dropNotNullConstraint", params: [
+ args: [
+ parameter(name: 'catalogName', type: STRING),
+ parameter(name: 'columnDataType', type: STRING),
+ parameter(name: 'columnName', type: STRING),
+ parameter(name: 'schemaName', type: STRING),
+ parameter(name: 'tableName', type: STRING)
+ ]
+ ], type: void
+}
+
+provideChildsOf("changeSet") {
+ method name: "dropPrimaryKey", params: [
+ args: [
+ parameter(name: 'catalogName', type: STRING),
+ parameter(name: 'constraintName', type: STRING),
+ parameter(name: 'schemaName', type: STRING),
+ parameter(name: 'tableName', type: STRING)
+ ]
+ ], type: void
+}
+
+provideChildsOf("changeSet") {
+ method name: "dropTable", params: [
+ args: [
+ parameter(name: 'cascadeConstraints', type: STRING),
+ parameter(name: 'catalogName', type: STRING),
+ parameter(name: 'schemaName', type: STRING),
+ parameter(name: 'tableName', type: STRING)
+ ]
+ ], type: void
+}
+
+provideChildsOf("changeSet") {
+ method name: "dropUniqueConstraint", params: [
+ args: [
+ parameter(name: 'catalogName', type: STRING),
+ parameter(name: 'constraintName', type: STRING),
+ parameter(name: 'schemaName', type: STRING),
+ parameter(name: 'tableName', type: STRING),
+ parameter(name: 'uniqueColumns', type: STRING)
+ ]
+ ], type: void
+}
+
+provideChildsOf("changeSet") {
+ method name: "dropView", params: [
+ args: [
+ parameter(name: 'catalogName', type: STRING),
+ parameter(name: 'schemaName', type: STRING),
+ parameter(name: 'viewName', type: STRING)
+ ]
+ ], type: void
+}
+
+provideChildsOf("changeSet") {
+ method name: "modifyDataType", params: [
+ args: [
+ parameter(name: 'catalogName', type: STRING),
+ parameter(name: 'columnName', type: STRING),
+ parameter(name: 'newDataType', type: STRING),
+ parameter(name: 'schemaName', type: STRING),
+ parameter(name: 'tableName', type: STRING)
+
+ ]
+ ], type: void
+}
+
+provideChildsOf("changeSet") {
+ method name: "renameColumn", params: [
+ args: [
+ parameter(name: 'catalogName', type: STRING),
+ parameter(name: 'columnDataType', type: STRING),
+ parameter(name: 'newColumnName', type: STRING),
+ parameter(name: 'oldColumnName', type: STRING),
+ parameter(name: 'remarks', type: STRING),
+ parameter(name: 'schemaName', type: STRING),
+ parameter(name: 'tableName', type: STRING)
+ ]
+ ], type: void
+}
+
+provideChildsOf("changeSet") {
+ method name: "renameTable", params: [
+ args: [
+ parameter(name: 'catalogName', type: STRING),
+ parameter(name: 'newTableName', type: STRING),
+ parameter(name: 'oldTableName', type: STRING),
+ parameter(name: 'schemaName', type: STRING)
+ ]
+ ], type: void
+}
+
+provideChildsOf("changeSet") {
+ method name: "renameView", params: [
+ args: [
+ parameter(name: 'catalogName', type: STRING),
+ parameter(name: 'newViewName', type: STRING),
+ parameter(name: 'oldViewName', type: STRING),
+ parameter(name: 'schemaName', type: STRING)
+ ]
+ ], type: void
+}
+
+provideChildsOf("changeSet") {
+ method name: "addLookupTable", params: [
+ args: [
+ parameter(name: 'constraintName', type: STRING),
+ parameter(name: 'existingColumnName', type: STRING),
+ parameter(name: 'existingTableCatalogName', type: STRING),
+ parameter(name: 'existingTableName', type: STRING),
+ parameter(name: 'existingTableSchemaName', type: STRING),
+ parameter(name: 'newColumnDataType', type: STRING),
+ parameter(name: 'newColumnName', type: STRING),
+ parameter(name: 'newTableCatalogName', type: STRING),
+ parameter(name: 'newTableName', type: STRING),
+ parameter(name: 'newTableSchemaName', type: STRING)
+ ]
+ ], type: void
+}
+
+provideChildsOf("changeSet") {
+ method name: "alterSequence", params: [
+ args: [
+ parameter(name: 'catalogName', type: STRING),
+ parameter(name: 'incrementBy', type: STRING),
+ parameter(name: 'maxValue', type: STRING),
+ parameter(name: 'minValue', type: STRING),
+ parameter(name: 'ordered', type: STRING),
+ parameter(name: 'schemaName', type: STRING),
+ parameter(name: 'sequenceName', type: STRING),
+ ]
+ ], type: void
+}
+
+provideChildsOf("changeSet") {
+ method name: "createProcedure", params: [
+ args: [
+ parameter(name: 'catalogName', type: STRING),
+ parameter(name: 'comments', type: STRING),
+ parameter(name: 'dbms', type: STRING),
+ parameter(name: 'encoding', type: STRING),
+ parameter(name: 'path', type: STRING),
+ parameter(name: 'procedureName', type: STRING),
+ parameter(name: 'procedureText', type: STRING),
+ parameter(name: 'relativeToChangelogFile', type: STRING),
+ parameter(name: 'schemaName', type: STRING)
+ ]
+ ], type: void
+}
+
+provideChildsOf("changeSet") {
+ method name: "customChange", params: [
+ args: [
+ parameter(name: 'class', type: STRING),
+ ],
+ body: {}
+ ], type: void
+}
+
+provideChildsOf("customChange") {
+ method name: "param", params: [
+ args: [
+ parameter(name: 'id', type: STRING),
+ parameter(name: 'value', type: STRING)
+ ]
+ ], type: void
+}
+
+provideChildsOf("changeSet") {
+ method name: "dropProcedure", params: [
+ args: [
+ parameter(name: 'catalogName', type: STRING),
+ parameter(name: 'procedureName', type: STRING),
+ parameter(name: 'schemaName', type: STRING)
+ ]
+ ], type: void
+}
+
+provideChildsOf("changeSet") {
+ method name: "dropSequence", params: [
+ args: [
+ parameter(name: 'catalogName', type: STRING),
+ parameter(name: 'schemaName', type: STRING),
+ parameter(name: 'sequenceName', type: STRING)
+ ]
+ ], type: void
+}
+
+provideChildsOf("changeSet") {
+ method name: "empty", params: [], type: void
+}
+
+provideChildsOf("changeSet") {
+ method name: "executeCommand", params: [
+ args: [
+ parameter(name: 'executable', type: STRING)
+ ]
+ ], type: void
+}
+
+provideChildsOf("changeSet") {
+ method name: "insert", params: [
+ args: [
+ parameter(name: 'catalogName', type: STRING),
+ parameter(name: 'dbms', type: STRING),
+ parameter(name: 'schemaName', type: STRING),
+ parameter(name: 'tableName', type: STRING)
+ ],
+ body: {}
+ ], type: void
+}
+provideChildsOf("insert", columnType)
+provideChildsOf("insert", columnTypeNoBody)
+
+provideChildsOf("changeSet") {
+ method name: "loadData", params: [
+ args: [
+ parameter(name: 'catalogName', type: STRING),
+ parameter(name: 'encoding', type: STRING),
+ parameter(name: 'file', type: STRING),
+ parameter(name: 'quotchar', type: STRING),
+ parameter(name: 'schemaName', type: STRING),
+ parameter(name: 'separator', type: STRING),
+ parameter(name: 'tableName', type: STRING)
+ ],
+ body: {}
+ ], type: void
+}
+provideChildsOf("loadData", columnType)
+provideChildsOf("loadData", columnTypeNoBody)
+
+provideChildsOf("changeSet") {
+ method name: "loadUpdateData", params: [
+ args: [
+ parameter(name: 'catalogName', type: STRING),
+ parameter(name: 'encoding', type: STRING),
+ parameter(name: 'file', type: STRING),
+ parameter(name: 'primaryKey', type: STRING),
+ parameter(name: 'quotchar', type: STRING),
+ parameter(name: 'schemaName', type: STRING),
+ parameter(name: 'separator', type: STRING),
+ parameter(name: 'tableName', type: STRING)
+ ],
+ body: {}
+ ], type: void
+}
+provideChildsOf("loadUpdateData", columnType)
+provideChildsOf("loadUpdateData", columnTypeNoBody)
+
+provideChildsOf("changeSet") {
+ method name: "mergeColumns", params: [
+ args: [
+ parameter(name: 'catalogName', type: STRING),
+ parameter(name: 'column1Name', type: STRING),
+ parameter(name: 'column2Name', type: STRING),
+ parameter(name: 'finalColumnName', type: STRING),
+ parameter(name: 'finalColumnType', type: STRING),
+ parameter(name: 'joinString', type: STRING),
+ parameter(name: 'schemaName', type: STRING),
+ parameter(name: 'tableName', type: STRING)
+ ]
+ ], type: void
+}
+
+provideChildsOf("changeSet") {
+ method name: "stop", params: [
+ args: [
+ parameter(name: 'message', type: STRING)
+ ]
+ ], type: void
+}
+
+provideChildsOf("changeSet") {
+ method name: "tagDatabase", params: [
+ args: [
+ parameter(name: 'tag', type: STRING)
+ ]
+ ], type: void
+}
+
+provideChildsOf("changeSet") {
+ method name: "update", params: [
+ args: [
+ parameter(name: 'catalogName', type: STRING),
+ parameter(name: 'schemaName', type: STRING),
+ parameter(name: 'tableName', type: STRING),
+ parameter(name: 'where', type: STRING)
+ ],
+ body: {}
+ ], type: void
+}
+provideChildsOf("update", columnType)
+provideChildsOf("update", columnTypeNoBody)
\ No newline at end of file
diff --git a/grails-data-hibernate5/database-migration/src/main/scripts/dbm-changelog-to-groovy.groovy b/grails-data-hibernate5/database-migration/src/main/scripts/dbm-changelog-to-groovy.groovy
new file mode 100644
index 00000000000..101c8e48e91
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/main/scripts/dbm-changelog-to-groovy.groovy
@@ -0,0 +1,17 @@
+import org.grails.plugins.databasemigration.DatabaseMigrationException
+import org.grails.plugins.databasemigration.command.DbmChangelogToGroovy
+
+description('Converts a changelog file to a Groovy DSL file') {
+ usage 'grails [environment] dbm-changelog-to-groovy [src_file_name] [dest_file_name]'
+ flag name: 'src_file_name', description: 'The name and path of the changelog file to convert'
+ flag name: 'dest_file_name', description: 'The name and path of the Groovy file'
+ flag name: 'dataSource', description: 'if provided will run the script for the specified dataSource creating a file named changelog-dataSource.groovy if a filename is not given. Not needed for the default dataSource'
+ flag name: 'force', description: 'Whether to overwrite existing files'
+ flag name: 'add', description: 'if provided will run the script for the specified dataSource. Not needed for the default dataSource.'
+}
+
+try {
+ new DbmChangelogToGroovy().handle(executionContext)
+} catch (DatabaseMigrationException e) {
+ error e.message, e
+}
diff --git a/grails-data-hibernate5/database-migration/src/main/scripts/dbm-create-changelog.groovy b/grails-data-hibernate5/database-migration/src/main/scripts/dbm-create-changelog.groovy
new file mode 100644
index 00000000000..252a5b7a986
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/main/scripts/dbm-create-changelog.groovy
@@ -0,0 +1,16 @@
+import org.grails.plugins.databasemigration.DatabaseMigrationException
+import org.grails.plugins.databasemigration.command.DbmCreateChangelog
+
+description('Creates an empty changelog file') {
+ usage 'grails [environment] dbm-create-changelog [filename]'
+ flag name: 'filename', description: 'The path to the output file to write to'
+ flag name: 'dataSource', description: 'if provided will run the script for the specified dataSource creating a file named changelog-dataSource.groovy if a filename is not given. Not needed for the default dataSource'
+ flag name: 'force', description: 'Whether to overwrite existing files'
+ flag name: 'add', description: 'if provided will run the script for the specified dataSource. Not needed for the default dataSource.'
+}
+
+try {
+ new DbmCreateChangelog().handle(executionContext)
+} catch (DatabaseMigrationException e) {
+ error e.message, e
+}
diff --git a/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/ApplicationContextDatabaseMigrationCommandSpec.groovy b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/ApplicationContextDatabaseMigrationCommandSpec.groovy
new file mode 100644
index 00000000000..a1566b495c8
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/ApplicationContextDatabaseMigrationCommandSpec.groovy
@@ -0,0 +1,108 @@
+package org.grails.plugins.databasemigration.command
+
+import grails.config.Config
+import grails.core.DefaultGrailsApplication
+import grails.core.GrailsApplication
+import grails.core.support.GrailsApplicationAware
+import grails.dev.commands.ApplicationCommand
+import grails.dev.commands.ExecutionContext
+import grails.orm.bootstrap.HibernateDatastoreSpringInitializer
+import grails.persistence.Entity
+import grails.util.GrailsNameUtils
+import liquibase.parser.ChangeLogParser
+import liquibase.parser.ChangeLogParserFactory
+import org.grails.build.parsing.CommandLineParser
+import org.grails.config.PropertySourcesConfig
+import org.grails.plugins.databasemigration.liquibase.GroovyChangeLogParser
+import org.h2.Driver
+import org.springframework.context.support.GenericApplicationContext
+import org.springframework.core.env.MapPropertySource
+import org.springframework.core.env.MutablePropertySources
+import spock.lang.AutoCleanup
+
+abstract class ApplicationContextDatabaseMigrationCommandSpec extends DatabaseMigrationCommandSpec implements GrailsApplicationAware {
+
+ GrailsApplication grailsApplication
+
+ @AutoCleanup
+ GenericApplicationContext applicationContext
+
+ ApplicationCommand command
+
+ Config config
+
+ def setup() {
+ applicationContext = new GenericApplicationContext()
+
+ applicationContext.beanFactory.registerSingleton('dataSource', dataSource)
+ applicationContext.beanFactory.registerSingleton(GrailsApplication.APPLICATION_ID, new DefaultGrailsApplication())
+
+ def mutablePropertySources = new MutablePropertySources()
+ mutablePropertySources.addFirst(new MapPropertySource('TestConfig', [
+ 'grails.plugin.databasemigration.changelogLocation': changeLogLocation.canonicalPath,
+ 'dataSource.dbCreate' : '',
+ 'environments.test.dataSource.url' : 'jdbc:h2:mem:testDb',
+ 'dataSource.username' : 'sa',
+ 'dataSource.password' : '',
+ 'dataSource.driverClassName' : Driver.name,
+ 'environments.other.dataSource.url' : 'jdbc:h2:mem:otherDb',
+ ]))
+ config = new PropertySourcesConfig(mutablePropertySources)
+
+ def datastoreInitializer = new HibernateDatastoreSpringInitializer(config, domainClasses)
+ datastoreInitializer.configureForBeanDefinitionRegistry(applicationContext)
+
+ applicationContext.refresh()
+
+ def grailsApplication = applicationContext.getBean(GrailsApplication)
+ grailsApplication.config = config
+
+ def groovyChangeLogParser = ChangeLogParserFactory.instance.parsers.find { ChangeLogParser changeLogParser -> changeLogParser instanceof GroovyChangeLogParser } as GroovyChangeLogParser
+ groovyChangeLogParser.applicationContext = applicationContext
+ groovyChangeLogParser.config = config
+
+ if (commandClass != null) {
+ command = createCommand(commandClass)
+ }
+ }
+
+ protected ApplicationCommand createCommand(Class applicationCommand) {
+ def command = applicationCommand.getDeclaredConstructor().newInstance()
+ command.applicationContext = applicationContext
+ command.changeLogFile.parentFile.mkdirs()
+ return command
+ }
+
+ protected Class[] getDomainClasses() {
+ [] as Class[]
+ }
+
+ protected Class getCommandClass() {
+ null
+ }
+
+ protected ExecutionContext getExecutionContext(Class clazz = commandClass, String... args) {
+ def commandClassName = GrailsNameUtils.getScriptName(GrailsNameUtils.getLogicalName(clazz.name, 'Command'))
+ new ExecutionContext(
+ new CommandLineParser().parse(([commandClassName] + args.toList()) as String[])
+ )
+ }
+
+ void cleanup() {
+
+ }
+
+
+}
+
+@Entity
+class Book {
+ String title
+ Author author
+}
+
+@Entity
+class Author {
+ String name
+ static hasMany = [books: Book]
+}
diff --git a/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DatabaseMigrationCommandConfigSpec.groovy b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DatabaseMigrationCommandConfigSpec.groovy
new file mode 100644
index 00000000000..ab8ba4ec357
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DatabaseMigrationCommandConfigSpec.groovy
@@ -0,0 +1,94 @@
+package org.grails.plugins.databasemigration.command
+
+import org.h2.Driver
+import spock.lang.Specification
+import org.grails.testing.GrailsUnitTest
+
+class DatabaseMigrationCommandConfigSpec extends Specification implements DatabaseMigrationCommand, GrailsUnitTest {
+
+ void cleanup() {
+ config.remove('dataSource')
+ config.remove('dataSources')
+ }
+
+ void "test getDataSourceConfig with single dataSource"() {
+
+ when:
+ config.dataSource = [
+ 'dbCreate' : '',
+ 'url' : 'jdbc:h2:mem:testDb',
+ 'username' : 'sa',
+ 'password' : '',
+ 'driverClassName': Driver.name
+ ]
+
+ then:
+ getDataSourceConfig(config) == [
+ 'dbCreate' : '',
+ 'url' : 'jdbc:h2:mem:testDb',
+ 'username' : 'sa',
+ 'password' : '',
+ 'driverClassName': Driver.name
+ ]
+
+ }
+
+ void "test getDataSourceConfig with no dataSource config"() {
+ expect:
+ getDataSourceConfig(config) == null
+ }
+
+ void "test getDataSourceConfig should return config when default is defined in dataSources"() {
+ when:
+ config.dataSources = [
+ dataSource: [
+ 'dbCreate' : '',
+ 'url' : 'jdbc:h2:mem:testDb',
+ 'username' : 'sa',
+ 'password' : '',
+ 'driverClassName': Driver.name
+ ]
+ ]
+
+ then:
+ getDataSourceConfig(config) == [
+ 'dbCreate' : '',
+ 'url' : 'jdbc:h2:mem:testDb',
+ 'username' : 'sa',
+ 'password' : '',
+ 'driverClassName': Driver.name,
+ ]
+
+ }
+
+ void "test getDataSourceConfig should return config when both dataSource and dataSources exists"() {
+ when:
+ config.dataSource = [
+ 'dbCreate' : '',
+ 'url' : 'jdbc:h2:mem:testDb',
+ 'username' : 'sa',
+ 'password' : '',
+ 'driverClassName': Driver.name
+ ]
+ config.dataSources = [
+ other: [
+ 'dbCreate' : '',
+ 'url' : 'jdbc:h2:mem:otherDb',
+ 'username' : 'sa',
+ 'password' : '',
+ 'driverClassName': Driver.name
+ ]
+ ]
+
+ then:
+ getDataSourceConfig(config) == [
+ 'dbCreate' : '',
+ 'url' : 'jdbc:h2:mem:testDb',
+ 'username' : 'sa',
+ 'password' : '',
+ 'driverClassName': Driver.name,
+ ]
+
+ }
+
+}
diff --git a/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DatabaseMigrationCommandSpec.groovy b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DatabaseMigrationCommandSpec.groovy
new file mode 100644
index 00000000000..6d4d8ebdc1a
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DatabaseMigrationCommandSpec.groovy
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2015 original authors
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration.command
+
+import groovy.sql.Sql
+import org.grails.plugins.databasemigration.testing.annotation.OutputCapture
+import org.h2.Driver
+import org.springframework.jdbc.datasource.DriverManagerDataSource
+import spock.lang.AutoCleanup
+import spock.lang.Specification
+
+import javax.sql.DataSource
+import java.sql.Connection
+
+abstract class DatabaseMigrationCommandSpec extends Specification {
+
+ @OutputCapture Object output
+
+ DataSource dataSource
+
+ @AutoCleanup
+ Connection connection
+
+ @AutoCleanup
+ Sql sql
+
+ @AutoCleanup('deleteDir')
+ File changeLogLocation
+
+ def setup() {
+ dataSource = new DriverManagerDataSource('jdbc:h2:mem:testDb', 'sa', '')
+ dataSource.driverClassName = Driver.name
+ connection = dataSource.connection
+ sql = new Sql(connection)
+
+ changeLogLocation = File.createTempDir()
+ }
+
+
+ protected static extractOutput(Object output){
+ String out = output.toString()
+ out.getAt(out.indexOf("databaseChangeLog")..-1)?.replaceAll(/\s/,"")
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmChangelogSyncCommandSpec.groovy b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmChangelogSyncCommandSpec.groovy
new file mode 100644
index 00000000000..c7945a5d526
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmChangelogSyncCommandSpec.groovy
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2015 original authors
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration.command
+
+import grails.dev.commands.ApplicationCommand
+
+class DbmChangelogSyncCommandSpec extends ApplicationContextDatabaseMigrationCommandSpec {
+
+ @Override
+ protected Class getCommandClass() {
+ return DbmChangelogSyncCommand
+ }
+
+ def "marks all changes as executed in the database"() {
+ given:
+ command.changeLogFile << CHANGE_LOG_CONTENT
+
+ when:
+ command.handle(getExecutionContext())
+
+ then:
+ def rows = sql.rows('SELECT id FROM databasechangelog').collect { it.id }
+ rows == ['changeSet1', 'changeSet2']
+ }
+
+ static final String CHANGE_LOG_CONTENT = '''
+databaseChangeLog = {
+ changeSet(author: "John Smith", id: "changeSet1") {
+ }
+ changeSet(author: "John Smith", id: "changeSet2") {
+ }
+}
+'''
+}
diff --git a/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmChangelogSyncCommandSqlSpec.groovy b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmChangelogSyncCommandSqlSpec.groovy
new file mode 100644
index 00000000000..5e3c3a9491b
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmChangelogSyncCommandSqlSpec.groovy
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2015 original authors
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration.command
+
+import grails.dev.commands.ApplicationCommand
+import spock.lang.AutoCleanup
+
+class DbmChangelogSyncCommandSqlSpec extends ApplicationContextDatabaseMigrationCommandSpec {
+
+ @Override
+ protected Class getCommandClass() {
+ return DbmChangelogSyncSqlCommand
+ }
+
+ @AutoCleanup('delete')
+ File outputFile = File.createTempFile('sync', 'sql')
+
+ def "writes SQL to mark all changes as executed in the database to STDOUT"() {
+ given:
+ command.changeLogFile << CHANGE_LOG_CONTENT
+
+ when:
+ command.handle(getExecutionContext())
+
+ then:
+ def outputString = output.toString()
+ outputString =~ /INSERT INTO .+'changeSet1'/
+ outputString =~ /INSERT INTO .+'changeSet2'/
+ }
+
+ def "writes SQL to mark all changes as executed in the database to a file given as arguments"() {
+ given:
+ command.changeLogFile << CHANGE_LOG_CONTENT
+
+ when:
+ command.handle(getExecutionContext(outputFile.canonicalPath))
+
+ then:
+ def outputString = outputFile.text
+ outputString =~ /INSERT INTO .+'changeSet1'/
+ outputString =~ /INSERT INTO .+'changeSet2'/
+ }
+
+ static final String CHANGE_LOG_CONTENT = '''
+databaseChangeLog = {
+ changeSet(author: "John Smith", id: "changeSet1") {
+ }
+ changeSet(author: "John Smith", id: "changeSet2") {
+ }
+}
+'''
+}
diff --git a/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmClearChecksumsCommandSpec.groovy b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmClearChecksumsCommandSpec.groovy
new file mode 100644
index 00000000000..50a9ec2f302
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmClearChecksumsCommandSpec.groovy
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2015 original authors
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration.command
+
+import grails.dev.commands.ApplicationCommand
+
+class DbmClearChecksumsCommandSpec extends ApplicationContextDatabaseMigrationCommandSpec {
+
+ @Override
+ protected Class getCommandClass() {
+ return DbmClearChecksumsCommand
+ }
+
+ def "removes all saved checksums from database log"() {
+ given:
+ sql.executeUpdate '''
+CREATE TABLE DATABASECHANGELOG (ID VARCHAR(255) NOT NULL, AUTHOR VARCHAR(255) NOT NULL, FILENAME VARCHAR(255) NOT NULL, DATEEXECUTED TIMESTAMP NOT NULL, ORDEREXECUTED INT NOT NULL, EXECTYPE VARCHAR(10) NOT NULL, MD5SUM VARCHAR(35), DESCRIPTION VARCHAR(255), COMMENTS VARCHAR(255), TAG VARCHAR(255), LIQUIBASE VARCHAR(20));
+INSERT INTO DATABASECHANGELOG (ID, AUTHOR, FILENAME, DATEEXECUTED, ORDEREXECUTED, MD5SUM, DESCRIPTION, COMMENTS, EXECTYPE, LIQUIBASE) VALUES ('changeSet1', 'John Smith', 'changelog.yml', NOW(), 1, '7:d41d8cd98f00b204e9800998ecf8427e', 'Empty', '', 'EXECUTED', '3.3.2');
+INSERT INTO DATABASECHANGELOG (ID, AUTHOR, FILENAME, DATEEXECUTED, ORDEREXECUTED, MD5SUM, DESCRIPTION, COMMENTS, EXECTYPE, LIQUIBASE) VALUES ('changeSet2', 'John Smith', 'changelog.yml', NOW(), 2, '7:d41d8cd98f00b204e9800998ecf8427e', 'Empty', '', 'EXECUTED', '3.3.2');
+'''
+
+ when:
+ command.handle(getExecutionContext())
+
+ then:
+ def rows = sql.rows('select id, md5sum from databasechangelog')
+ rows.size() == 2
+ rows.every { it.md5sum == null }
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmDiffCommandSpec.groovy b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmDiffCommandSpec.groovy
new file mode 100644
index 00000000000..37bcfc59c45
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmDiffCommandSpec.groovy
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2015-2024 original authors
+ *
+ * Licensed 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 org.grails.plugins.databasemigration.command
+
+import grails.dev.commands.ApplicationCommand
+import groovy.sql.Sql
+import org.grails.plugins.databasemigration.DatabaseMigrationException
+import org.h2.Driver
+import org.springframework.jdbc.datasource.DriverManagerDataSource
+import spock.lang.AutoCleanup
+
+import java.sql.Connection
+
+class DbmDiffCommandSpec extends ApplicationContextDatabaseMigrationCommandSpec {
+
+ @Override
+ protected Class getCommandClass() {
+ return DbmDiffCommand
+ }
+
+ @AutoCleanup
+ Connection otherDbConnection
+
+ @AutoCleanup
+ Sql otherDbSql
+
+ def setup() {
+ def otherDbDataSource = new DriverManagerDataSource('jdbc:h2:mem:otherDb', 'sa', '')
+ otherDbDataSource.driverClassName = Driver.name
+ otherDbConnection = otherDbDataSource.connection
+ otherDbSql = new Sql(otherDbConnection)
+ otherDbSql.executeUpdate '''
+CREATE TABLE book (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, title VARCHAR(255) NOT NULL, CONSTRAINT PK_BOOK PRIMARY KEY (id))
+'''
+ sql.executeUpdate '''
+CREATE TABLE book (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, title VARCHAR(255) NOT NULL, price INT NOT NULL, CONSTRAINT PK_BOOK PRIMARY KEY (id));
+CREATE TABLE author (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, CONSTRAINT PK_AUTHOR PRIMARY KEY (id));
+'''
+ }
+
+ def "writes Change Log to update the database to STDOUT"() {
+ when:
+ command.handle(getExecutionContext('other'))
+
+ then:
+ String expected = extractOutput(output)
+ expected =~ '''
+databaseChangeLog = \\{
+
+ changeSet\\(author: ".+?", id: ".+?"\\) \\{
+ createTable\\(tableName: "AUTHOR"\\) \\{
+ column\\(autoIncrement: "true", name: "ID", type: "INT"\\) \\{
+ constraints\\(nullable:"false", primaryKey: "true", primaryKeyName: "PK_AUTHOR"\\)
+ \\}
+
+ column\\(name: "NAME", type: "VARCHAR\\(255\\)"\\) \\{
+ constraints\\(nullable: "false"\\)
+ \\}
+ \\}
+ \\}
+
+ changeSet\\(author: ".+?", id: ".+?"\\) \\{
+ addColumn\\(tableName: "BOOK"\\) \\{
+ column\\(name: "PRICE", type: "INTEGER"\\) \\{
+ constraints\\(nullable: "false"\\)
+ \\}
+ \\}
+ \\}
+\\}
+'''.replaceAll(/\s/,"")
+ }
+
+ def "writes Change Log to update the database to a file given as arguments"() {
+ given:
+ def outputChangeLog = new File(changeLogLocation, 'diff.groovy')
+
+ when:
+ command.handle(getExecutionContext('other', outputChangeLog.name))
+
+ then:
+ outputChangeLog.text?.replaceAll(/\s/,"") =~ '''
+databaseChangeLog = \\{
+
+ changeSet\\(author: ".+?", id: ".+?"\\) \\{
+ createTable\\(tableName: "AUTHOR"\\) \\{
+ column\\(autoIncrement: "true", name: "ID", type: "INT"\\) \\{
+ constraints\\(nullable:"false", primaryKey: "true", primaryKeyName: "PK_AUTHOR"\\)
+ \\}
+
+ column\\(name: "NAME", type: "VARCHAR\\(255\\)"\\) \\{
+ constraints\\(nullable: "false"\\)
+ \\}
+ \\}
+ \\}
+
+ changeSet\\(author: ".+?", id: ".+?"\\) \\{
+ addColumn\\(tableName: "BOOK"\\) \\{
+ column\\(name: "PRICE", type: "INTEGER"\\) \\{
+ constraints\\(nullable: "false"\\)
+ \\}
+ \\}
+ \\}
+\\}
+'''.replaceAll(/\s/,"")
+ }
+
+ def "an error occurs if the otherEnv parameter is not specified"() {
+ when:
+ command.handle(getExecutionContext())
+
+ then:
+ def e = thrown(DatabaseMigrationException)
+ e.message == 'You must specify the environment to diff against'
+ }
+
+ def "an error occurs if other environment and current environment is same"() {
+ when:
+ command.handle(getExecutionContext('test'))
+
+ then:
+ def e = thrown(DatabaseMigrationException)
+ e.message == 'You must specify a different environment than the one the command is running in'
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmDropAllCommandSpec.groovy b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmDropAllCommandSpec.groovy
new file mode 100644
index 00000000000..285e84d3d1a
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmDropAllCommandSpec.groovy
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2015-2024 original authors
+ *
+ * Licensed 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 org.grails.plugins.databasemigration.command
+
+import grails.dev.commands.ApplicationCommand
+
+class DbmDropAllCommandSpec extends ApplicationContextDatabaseMigrationCommandSpec {
+
+ @Override
+ protected Class getCommandClass() {
+ return DbmDropAllCommand
+ }
+
+ def "drops all database objects"() {
+ given:
+ sql.executeUpdate 'CREATE TABLE book (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, title VARCHAR(255) NOT NULL, CONSTRAINT PK_BOOK PRIMARY KEY (id))'
+
+ expect:
+ sql.rows('SELECT table_name FROM information_schema.tables WHERE table_class = \'org.h2.mvstore.db.MVTable\'')
+
+ when:
+ command.handle(getExecutionContext())
+
+ then:
+ !sql.rows('SELECT table_name FROM information_schema.tables WHERE table_class = \'org.h2.mvstore.db.MVTable\'')
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmFutureRollbackCountSqlCommandSpec.groovy b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmFutureRollbackCountSqlCommandSpec.groovy
new file mode 100644
index 00000000000..acaf4bb9394
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmFutureRollbackCountSqlCommandSpec.groovy
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2015 original authors
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration.command
+
+import grails.dev.commands.ApplicationCommand
+import org.grails.plugins.databasemigration.DatabaseMigrationException
+import spock.lang.AutoCleanup
+
+class DbmFutureRollbackCountSqlCommandSpec extends ApplicationContextDatabaseMigrationCommandSpec {
+
+ @Override
+ protected Class getCommandClass() {
+ return DbmFutureRollbackCountSqlCommand
+ }
+
+ @AutoCleanup('delete')
+ File outputFile = File.createTempFile('rollback', 'sql')
+
+ def "writes SQL to roll back the database to STDOUT"() {
+ given:
+ command.changeLogFile << CHANGE_LOG_CONTENT
+
+ when:
+ command.handle(getExecutionContext('1'))
+
+ then:
+ def output = output.toString()
+ output.contains('DROP TABLE PUBLIC.author;')
+ !output.contains('DROP TABLE PUBLIC.book;')
+ }
+
+ def "writes SQL to roll back the database to a file given as arguments"() {
+ given:
+ command.changeLogFile << CHANGE_LOG_CONTENT
+
+ when:
+ command.handle(getExecutionContext('1', outputFile.canonicalPath))
+
+ then:
+ def output = outputFile.text
+ output.contains('DROP TABLE PUBLIC.author;')
+ !output.contains('DROP TABLE PUBLIC.book;')
+ }
+
+ def "an error occurs if the count parameter is not specified"() {
+ when:
+ command.handle(getExecutionContext())
+
+ then:
+ def e = thrown(DatabaseMigrationException)
+ e.message == "The ${command.name} command requires a change set number argument"
+ }
+
+ def "an error occurs if the count parameter is not number"() {
+ when:
+ command.handle(getExecutionContext('one'))
+
+ then:
+ def e = thrown(DatabaseMigrationException)
+ e.message == 'The change set number argument \'one\' isn\'t a number'
+ }
+
+ static final String CHANGE_LOG_CONTENT = '''
+databaseChangeLog = {
+
+ changeSet(author: "John Smith", id: "1") {
+ createTable(tableName: "author") {
+ column(autoIncrement: "true", name: "id", type: "BIGINT") {
+ constraints(primaryKey: "true", primaryKeyName: "authorPK")
+ }
+
+ column(name: "version", type: "BIGINT") {
+ constraints(nullable: "false")
+ }
+
+ column(name: "name", type: "VARCHAR(255)") {
+ constraints(nullable: "false")
+ }
+ }
+ }
+
+ changeSet(author: "John Smith", id: "2") {
+ createTable(tableName: "book") {
+ column(autoIncrement: "true", name: "id", type: "BIGINT") {
+ constraints(primaryKey: "true", primaryKeyName: "bookPK")
+ }
+
+ column(name: "version", type: "BIGINT") {
+ constraints(nullable: "false")
+ }
+
+ column(name: "author_id", type: "BIGINT") {
+ constraints(nullable: "false")
+ }
+
+ column(name: "title", type: "VARCHAR(255)") {
+ constraints(nullable: "false")
+ }
+ }
+ }
+
+ changeSet(author: "John Smith", id: "3") {
+ addForeignKeyConstraint(baseColumnNames: "author_id", baseTableName: "book", constraintName: "FK_4sac2ubmnqva85r8bk8fxdvbf", deferrable: "false", initiallyDeferred: "false", referencedColumnNames: "id", referencedTableName: "author")
+ }
+
+ changeSet(author: "John Smith", id: "4", context: "development") {
+ insert(tableName: "author") {
+ column(name: "name", value: "Mary")
+ }
+ }
+
+ changeSet(author: "John Smith", id: "5", context: "test") {
+ insert(tableName: "author") {
+ column(name: "name", value: "Amelia")
+ }
+ }
+}
+'''
+}
diff --git a/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmFutureRollbackSqlCommandSpec.groovy b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmFutureRollbackSqlCommandSpec.groovy
new file mode 100644
index 00000000000..94a9ab8384d
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmFutureRollbackSqlCommandSpec.groovy
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2015 original authors
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration.command
+
+import grails.dev.commands.ApplicationCommand
+import spock.lang.AutoCleanup
+
+class DbmFutureRollbackSqlCommandSpec extends ApplicationContextDatabaseMigrationCommandSpec {
+
+ @Override
+ protected Class getCommandClass() {
+ return DbmFutureRollbackSqlCommand
+ }
+
+ @AutoCleanup('delete')
+ File outputFile = File.createTempFile('rollback', 'sql')
+
+ def "writes SQL to roll back the database to STDOUT"() {
+ given:
+ command.changeLogFile << CHANGE_LOG_CONTENT
+
+ when:
+ command.handle(getExecutionContext())
+
+ then:
+ def output = output.toString()
+ output.contains('ALTER TABLE PUBLIC.book DROP CONSTRAINT FK_4sac2ubmnqva85r8bk8fxdvbf')
+ output.contains('DROP TABLE PUBLIC.author;')
+ output.contains('DROP TABLE PUBLIC.book;')
+ }
+
+ def "writes SQL to roll back the database to a file given as arguments"() {
+ given:
+ command.changeLogFile << CHANGE_LOG_CONTENT
+
+ when:
+ command.handle(getExecutionContext(outputFile.canonicalPath))
+
+ then:
+ def output = outputFile.text
+ output.contains('ALTER TABLE PUBLIC.book DROP CONSTRAINT FK_4sac2ubmnqva85r8bk8fxdvbf')
+ output.contains('DROP TABLE PUBLIC.author;')
+ output.contains('DROP TABLE PUBLIC.book;')
+ }
+
+ static final String CHANGE_LOG_CONTENT = '''
+databaseChangeLog = {
+
+ changeSet(author: "John Smith", id: "1") {
+ createTable(tableName: "author") {
+ column(autoIncrement: "true", name: "id", type: "BIGINT") {
+ constraints(primaryKey: "true", primaryKeyName: "authorPK")
+ }
+
+ column(name: "version", type: "BIGINT") {
+ constraints(nullable: "false")
+ }
+
+ column(name: "name", type: "VARCHAR(255)") {
+ constraints(nullable: "false")
+ }
+ }
+ }
+
+ changeSet(author: "John Smith", id: "2") {
+ createTable(tableName: "book") {
+ column(autoIncrement: "true", name: "id", type: "BIGINT") {
+ constraints(primaryKey: "true", primaryKeyName: "bookPK")
+ }
+
+ column(name: "version", type: "BIGINT") {
+ constraints(nullable: "false")
+ }
+
+ column(name: "author_id", type: "BIGINT") {
+ constraints(nullable: "false")
+ }
+
+ column(name: "title", type: "VARCHAR(255)") {
+ constraints(nullable: "false")
+ }
+ }
+ }
+
+ changeSet(author: "John Smith", id: "3") {
+ addForeignKeyConstraint(baseColumnNames: "author_id", baseTableName: "book", constraintName: "FK_4sac2ubmnqva85r8bk8fxdvbf", deferrable: "false", initiallyDeferred: "false", referencedColumnNames: "id", referencedTableName: "author")
+ }
+}
+'''
+}
diff --git a/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmGenerateChangelogCommandSpec.groovy b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmGenerateChangelogCommandSpec.groovy
new file mode 100644
index 00000000000..ad20e179288
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmGenerateChangelogCommandSpec.groovy
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2015-2024 original authors
+ *
+ * Licensed 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 org.grails.plugins.databasemigration.command
+
+import grails.dev.commands.ApplicationCommand
+
+class DbmGenerateChangelogCommandSpec extends ApplicationContextDatabaseMigrationCommandSpec {
+
+ @Override
+ protected Class getCommandClass() {
+ return DbmGenerateChangelogCommand
+ }
+
+ def setup() {
+ sql.executeUpdate '''
+CREATE TABLE book (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, title VARCHAR(255) NOT NULL, price INT NOT NULL, CONSTRAINT PK_BOOK PRIMARY KEY (id));
+CREATE TABLE author (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, CONSTRAINT PK_AUTHOR PRIMARY KEY (id));
+'''
+ }
+
+ def "generates an initial changelog from the database to STDOUT"() {
+ when:
+ command.handle(getExecutionContext())
+
+ then:
+ extractOutput(output) =~ '''
+databaseChangeLog = \\{
+
+ changeSet\\(author: ".*?", id: ".*?"\\) \\{
+ createTable\\(tableName: "AUTHOR"\\) \\{
+ column\\(autoIncrement: "true", name: "ID", type: "INT"\\) \\{
+ constraints\\(nullable: "false", primaryKey: "true", primaryKeyName: "PK_AUTHOR"\\)
+ \\}
+
+ column\\(name: "NAME", type: "VARCHAR\\(255\\)"\\) \\{
+ constraints\\(nullable: "false"\\)
+ \\}
+ \\}
+ \\}
+
+ changeSet\\(author: ".*?", id: ".*?"\\) \\{
+ createTable\\(tableName: "BOOK"\\) \\{
+ column\\(autoIncrement: "true", name: "ID", type: "INT"\\) \\{
+ constraints\\(nullable: "false", primaryKey: "true", primaryKeyName: "PK_BOOK"\\)
+ \\}
+
+ column\\(name: "TITLE", type: "VARCHAR\\(255\\)"\\) \\{
+ constraints\\(nullable: "false"\\)
+ \\}
+
+ column\\(name: "PRICE", type: "INT"\\) \\{
+ constraints\\(nullable: "false"\\)
+ \\}
+ \\}
+ \\}
+\\}
+'''.replaceAll(/\s/, "")
+ }
+
+ def "generates an initial changelog from the database to a file given as arguments"() {
+ given:
+ def outputChangeLog = new File(changeLogLocation, 'changelog.groovy')
+
+ when:
+ command.handle(getExecutionContext(outputChangeLog.name))
+
+ then:
+ outputChangeLog.text?.replaceAll(/\s/, "") =~ '''
+databaseChangeLog = \\{
+
+ changeSet\\(author: ".*?", id: ".*?"\\) \\{
+ createTable\\(tableName: "AUTHOR"\\) \\{
+ column\\(autoIncrement: "true", name: "ID", type: "INT"\\) \\{
+ constraints\\(nullable: "false", primaryKey: "true", primaryKeyName: "PK_AUTHOR"\\)
+ \\}
+
+ column\\(name: "NAME", type: "VARCHAR\\(255\\)"\\) \\{
+ constraints\\(nullable: "false"\\)
+ \\}
+ \\}
+ \\}
+
+ changeSet\\(author: ".*?", id: ".*?"\\) \\{
+ createTable\\(tableName: "BOOK"\\) \\{
+ column\\(autoIncrement: "true", name: "ID", type: "INT"\\) \\{
+ constraints\\(nullable: "false", primaryKey: "true", primaryKeyName: "PK_BOOK"\\)
+ \\}
+
+ column\\(name: "TITLE", type: "VARCHAR\\(255\\)"\\) \\{
+ constraints\\(nullable: "false"\\)
+ \\}
+
+ column\\(name: "PRICE", type: "INT"\\) \\{
+ constraints\\(nullable: "false"\\)
+ \\}
+ \\}
+ \\}
+\\}
+'''.replaceAll(/\s/, "")
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmGenerateGormChangelogCommandSpec.groovy b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmGenerateGormChangelogCommandSpec.groovy
new file mode 100644
index 00000000000..3fdaabad087
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmGenerateGormChangelogCommandSpec.groovy
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2015 original authors
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration.command
+
+import grails.dev.commands.ApplicationCommand
+import org.grails.plugins.databasemigration.DatabaseMigrationException
+
+class DbmGenerateGormChangelogCommandSpec extends ApplicationContextDatabaseMigrationCommandSpec {
+
+ @Override
+ protected Class getCommandClass() {
+ return DbmGenerateGormChangelogCommand
+ }
+
+ def "writes Change Log to copy the current state of the database to STDOUT"() {
+ when:
+ command.handle(getExecutionContext())
+
+ then:
+ extractOutput(output) =~ '''
+databaseChangeLog = \\{
+
+ changeSet\\(author: ".+?", id: ".+?"\\) \\{
+ createTable\\(tableName: "author"\\) \\{
+ column\\(autoIncrement: "true", name: "id", type: "BIGINT"\\) \\{
+ constraints\\(nullable: "false", primaryKey: "true", primaryKeyName: "authorPK"\\)
+ \\}
+
+ column\\(name: "version", type: "BIGINT"\\) \\{
+ constraints\\(nullable: "false"\\)
+ \\}
+
+ column\\(name: "name", type: "VARCHAR\\(255\\)"\\) \\{
+ constraints\\(nullable: "false"\\)
+ \\}
+ \\}
+ \\}
+
+ changeSet\\(author: ".+?", id: ".+?"\\) \\{
+ createTable\\(tableName: "book"\\) \\{
+ column\\(autoIncrement: "true", name: "id", type: "BIGINT"\\) \\{
+ constraints\\(nullable: "false", primaryKey: "true", primaryKeyName: "bookPK"\\)
+ \\}
+
+ column\\(name: "version", type: "BIGINT"\\) \\{
+ constraints\\(nullable: "false"\\)
+ \\}
+
+ column\\(name: "title", type: "VARCHAR\\(255\\)"\\) \\{
+ constraints\\(nullable: "false"\\)
+ \\}
+
+ column\\(name: "author_id", type: "BIGINT"\\) \\{
+ constraints\\(nullable: "false"\\)
+ \\}
+ \\}
+ \\}
+
+ changeSet\\(author: ".+?", id: ".+?"\\) \\{
+ addForeignKeyConstraint\\(baseColumnNames: "author_id", baseTableName: "book", constraintName: "FK.+?", deferrable: "false", initiallyDeferred: "false", referencedColumnNames: "id", referencedTableName: "author", validate: "true"\\)
+ \\}
+\\}
+'''.replaceAll(/\s/,"")
+ }
+
+ def "writes Change Log to copy the current state of the database to a file given as arguments"() {
+ given:
+ def filename = 'changelog.groovy'
+
+ when:
+ command.handle(getExecutionContext(filename))
+
+ then:
+ def output = new File(changeLogLocation, filename).text?.replaceAll(/\s/, "")
+ output =~ '''
+databaseChangeLog = \\{
+
+ changeSet\\(author: ".+?", id: ".+?"\\) \\{
+ createTable\\(tableName: "author"\\) \\{
+ column\\(autoIncrement: "true", name: "id", type: "BIGINT"\\) \\{
+ constraints\\(nullable: "false", primaryKey: "true", primaryKeyName: "authorPK"\\)
+ \\}
+
+ column\\(name: "version", type: "BIGINT"\\) \\{
+ constraints\\(nullable: "false"\\)
+ \\}
+
+ column\\(name: "name", type: "VARCHAR\\(255\\)"\\) \\{
+ constraints\\(nullable: "false"\\)
+ \\}
+ \\}
+ \\}
+
+ changeSet\\(author: ".+?", id: ".+?"\\) \\{
+ createTable\\(tableName: "book"\\) \\{
+ column\\(autoIncrement: "true", name: "id", type: "BIGINT"\\) \\{
+ constraints\\(nullable: "false", primaryKey: "true", primaryKeyName: "bookPK"\\)
+ \\}
+
+ column\\(name: "version", type: "BIGINT"\\) \\{
+ constraints\\(nullable: "false"\\)
+ \\}
+
+ column\\(name: "title", type: "VARCHAR\\(255\\)"\\) \\{
+ constraints\\(nullable: "false"\\)
+ \\}
+
+ column\\(name: "author_id", type: "BIGINT"\\) \\{
+ constraints\\(nullable: "false"\\)
+ \\}
+ \\}
+ \\}
+
+ changeSet\\(author: ".+?", id: ".+?"\\) \\{
+ addForeignKeyConstraint\\(baseColumnNames: "author_id", baseTableName: "book", constraintName: "FK.+?", deferrable: "false", initiallyDeferred: "false", referencedColumnNames: "id", referencedTableName: "author", validate: "true"\\)
+ \\}
+\\}
+'''.replaceAll(/\s/, "")
+ }
+
+ def "an error occurs if changeLogFile already exists"() {
+ given:
+ def filename = 'changelog.yml'
+ def changeLogFile = new File(changeLogLocation, filename)
+ assert changeLogFile.createNewFile()
+
+ when:
+ command.handle(getExecutionContext(filename))
+
+ then:
+ def e = thrown(DatabaseMigrationException)
+ e.message == "ChangeLogFile ${changeLogFile.canonicalPath} already exists!"
+ }
+
+ @Override
+ protected Class[] getDomainClasses() {
+ [Book, Author] as Class[]
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmGormDiffCommandSpec.groovy b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmGormDiffCommandSpec.groovy
new file mode 100644
index 00000000000..7735332dcd6
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmGormDiffCommandSpec.groovy
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2015-2024 original authors
+ *
+ * Licensed 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 org.grails.plugins.databasemigration.command
+
+import grails.dev.commands.ApplicationCommand
+import org.grails.plugins.databasemigration.DatabaseMigrationException
+
+class DbmGormDiffCommandSpec extends ApplicationContextDatabaseMigrationCommandSpec {
+
+ @Override
+ protected Class getCommandClass() {
+ return DbmGormDiffCommand
+ }
+
+ def setup() {
+ sql.executeUpdate 'CREATE TABLE PUBLIC.author (id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, version BIGINT NOT NULL, name VARCHAR(255) NOT NULL, CONSTRAINT authorPK PRIMARY KEY (id));'
+ }
+
+ def "diffs GORM classes against a database and generates a changelog to STDOUT"() {
+ when:
+ command.handle(getExecutionContext())
+
+ then:
+ extractOutput(output) =~ '''
+databaseChangeLog = \\{
+
+ changeSet\\(author: ".+?", id: ".+?"\\) \\{
+ createTable\\(tableName: "book"\\) \\{
+ column\\(autoIncrement: "true", name: "id", type: "BIGINT"\\) \\{
+ constraints\\(nullable: "false", primaryKey: "true", primaryKeyName: "bookPK"\\)
+ \\}
+
+ column\\(name: "version", type: "BIGINT"\\) \\{
+ constraints\\(nullable: "false"\\)
+ \\}
+
+ column\\(name: "title", type: "VARCHAR\\(255\\)"\\) \\{
+ constraints\\(nullable: "false"\\)
+ \\}
+
+ column\\(name: "author_id", type: "BIGINT"\\) \\{
+ constraints\\(nullable: "false"\\)
+ \\}
+ \\}
+ \\}
+
+ changeSet\\(author: ".+?", id: ".+?"\\) \\{
+ addForeignKeyConstraint\\(baseColumnNames: "author_id", baseTableName: "book", constraintName: "FK.+?", deferrable: "false", initiallyDeferred: "false", referencedColumnNames: "id", referencedTableName: "author", validate: "true"\\)
+ \\}
+\\}
+'''.replaceAll(/\s/,"")
+ }
+
+ def "diffs GORM classes against a database and generates a changelog to a file given as arguments"() {
+ given:
+ def filename = 'changelog.groovy'
+
+ when:
+ command.handle(getExecutionContext(filename))
+
+ then:
+ def output = new File(changeLogLocation, filename).text?.replaceAll(/\s/,"")
+ output =~ '''
+databaseChangeLog = \\{
+
+ changeSet\\(author: ".+?", id: ".+?"\\) \\{
+ createTable\\(tableName: "book"\\) \\{
+ column\\(autoIncrement: "true", name: "id", type: "BIGINT"\\) \\{
+ constraints\\(nullable:"false", primaryKey: "true", primaryKeyName: "bookPK"\\)
+ \\}
+
+ column\\(name: "version", type: "BIGINT"\\) \\{
+ constraints\\(nullable: "false"\\)
+ \\}
+
+ column\\(name: "title", type: "VARCHAR\\(255\\)"\\) \\{
+ constraints\\(nullable: "false"\\)
+ \\}
+
+ column\\(name: "author_id", type: "BIGINT"\\) \\{
+ constraints\\(nullable: "false"\\)
+ \\}
+ \\}
+ \\}
+
+ changeSet\\(author: ".+?", id: ".+?"\\) \\{
+ addForeignKeyConstraint\\(baseColumnNames: "author_id", baseTableName: "book", constraintName: "FK.+?", deferrable: "false", initiallyDeferred: "false", referencedColumnNames: "id", referencedTableName: "author", validate: "true"\\)
+ \\}
+\\}
+'''.replaceAll(/\s/,"")
+ }
+
+ def "an error occurs if changeLogFile already exists"() {
+ given:
+ def filename = 'changelog.yml'
+ def changeLogFile = new File(changeLogLocation, filename)
+ assert changeLogFile.createNewFile()
+
+ when:
+ command.handle(getExecutionContext(filename))
+
+ then:
+ def e = thrown(DatabaseMigrationException)
+ e.message == "ChangeLogFile ${changeLogFile.canonicalPath} already exists!"
+ }
+
+ @Override
+ protected Class[] getDomainClasses() {
+ [Book, Author] as Class[]
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmListLocksCommandSpec.groovy b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmListLocksCommandSpec.groovy
new file mode 100644
index 00000000000..676e35e0b9f
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmListLocksCommandSpec.groovy
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2015 original authors
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration.command
+
+import grails.dev.commands.ApplicationCommand
+import spock.lang.AutoCleanup
+
+class DbmListLocksCommandSpec extends ApplicationContextDatabaseMigrationCommandSpec {
+
+ @Override
+ protected Class getCommandClass() {
+ return DbmListLocksCommand
+ }
+
+ @AutoCleanup('delete')
+ File outputFile = File.createTempFile('locks', 'txt')
+
+ def "lists locks on the database changelog when the lock does not exist"() {
+ when:
+ command.handle(getExecutionContext())
+
+ then:
+ output.toString().contains '- No locks'
+ }
+
+ def "lists locks on the database changelog when the lock exists"() {
+ given:
+ sql.executeUpdate('CREATE TABLE PUBLIC.DATABASECHANGELOGLOCK (ID INT NOT NULL, LOCKED BOOLEAN NOT NULL, LOCKGRANTED TIMESTAMP, LOCKEDBY VARCHAR(255), CONSTRAINT PK_DATABASECHANGELOGLOCK PRIMARY KEY (ID))')
+ sql.executeUpdate('INSERT INTO PUBLIC.DATABASECHANGELOGLOCK (ID, LOCKED, LOCKGRANTED, LOCKEDBY) VALUES (1, TRUE, NOW(), \'John Smith\')')
+
+ when:
+ command.handle(getExecutionContext())
+
+ then:
+ output.toString() =~ '- John Smith at .+?'
+ }
+
+ def "lists locks to a file given as arguments"() {
+ when:
+ command.handle(getExecutionContext(outputFile.canonicalPath))
+
+ then:
+ outputFile.text.contains '- No locks'
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmMarkNextChangesetRanCommandSpec.groovy b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmMarkNextChangesetRanCommandSpec.groovy
new file mode 100644
index 00000000000..18c773bf3ee
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmMarkNextChangesetRanCommandSpec.groovy
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2015 original authors
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration.command
+
+import grails.dev.commands.ApplicationCommand
+
+class DbmMarkNextChangesetRanCommandSpec extends ApplicationContextDatabaseMigrationCommandSpec {
+
+ @Override
+ protected Class getCommandClass() {
+ return DbmMarkNextChangesetRanCommand
+ }
+
+ def "marks the next change changes as executed in the database"() {
+ given:
+ command.changeLogFile << CHANGE_LOG_CONTENT
+
+ when:
+ command.handle(getExecutionContext())
+
+ then:
+ def rows = sql.rows('SELECT id FROM databasechangelog').collect { it.id }
+ rows == ['changeSet1']
+ }
+
+ static final String CHANGE_LOG_CONTENT = '''
+databaseChangeLog = {
+ changeSet(author: "John Smith", id: "changeSet1") {
+ }
+ changeSet(author: "John Smith", id: "changeSet2") {
+ }
+}
+'''
+}
+
diff --git a/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmMarkNextChangesetRanSqlCommandSpec.groovy b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmMarkNextChangesetRanSqlCommandSpec.groovy
new file mode 100644
index 00000000000..c8e689bbb14
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmMarkNextChangesetRanSqlCommandSpec.groovy
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2015 original authors
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration.command
+
+import grails.dev.commands.ApplicationCommand
+import spock.lang.AutoCleanup
+
+class DbmMarkNextChangesetRanSqlCommandSpec extends ApplicationContextDatabaseMigrationCommandSpec {
+
+ @Override
+ protected Class getCommandClass() {
+ return DbmMarkNextChangesetRanSqlCommand
+ }
+
+ @AutoCleanup('delete')
+ File outputFile = File.createTempFile('update', 'sql')
+
+ def "writes SQL to mark the next change as executed in the database to STDOUT"() {
+ given:
+ command.changeLogFile << CHANGE_LOG_CONTENT
+
+ when:
+ command.handle(getExecutionContext())
+
+ then:
+ def output = output.toString()
+ output =~ /INSERT INTO .+'changeSet1'/
+ !(output =~ /INSERT INTO .+'changeSet2'/)
+ }
+
+ def "writes SQL to mark the next change as executed in the database to a file given as arguments"() {
+ given:
+ command.changeLogFile << CHANGE_LOG_CONTENT
+
+ when:
+ command.handle(getExecutionContext(outputFile.canonicalPath))
+
+ then:
+ def output = outputFile.text
+ output =~ /INSERT INTO .+'changeSet1'/
+ !(output =~ /INSERT INTO .+'changeSet2'/)
+ }
+
+ static final String CHANGE_LOG_CONTENT = '''
+databaseChangeLog = {
+ changeSet(author: "John Smith", id: "changeSet1") {
+ }
+ changeSet(author: "John Smith", id: "changeSet2") {
+ }
+}
+'''
+}
diff --git a/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmPreviousChangesetSqlCommandSpec.groovy b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmPreviousChangesetSqlCommandSpec.groovy
new file mode 100644
index 00000000000..1a67c8c6659
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmPreviousChangesetSqlCommandSpec.groovy
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2015-2024 original authors
+ *
+ * Licensed 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 org.grails.plugins.databasemigration.command
+
+import grails.dev.commands.ApplicationCommand
+import org.grails.plugins.databasemigration.DatabaseMigrationException
+import spock.lang.AutoCleanup
+
+class DbmPreviousChangesetSqlCommandSpec extends ApplicationContextDatabaseMigrationCommandSpec {
+
+ @Override
+ protected Class getCommandClass() {
+ return DbmPreviousChangesetSqlCommand
+ }
+
+ @AutoCleanup('delete')
+ File outputFile = File.createTempFile('previous', 'sql')
+
+
+ def setup() {
+ command.changeLogFile << CHANGE_LOG_CONTENT
+
+ new DbmUpdateCommand(applicationContext: applicationContext).handle(getExecutionContext())
+
+ def tables = sql.rows('SELECT table_name FROM information_schema.tables WHERE table_class = \'org.h2.mvstore.db.MVTable\'').collect { it.table_name.toLowerCase() }
+ assert tables as Set == ['book', 'author', 'databasechangeloglock', 'databasechangelog'] as Set
+ }
+
+
+ void "The last SQL change sets to STDOUT"() {
+ when:
+ command.handle(getExecutionContext('1'))
+
+ then:
+ def output = output.toString()
+ output.contains('CREATE TABLE PUBLIC.book (id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, version BIGINT NOT NULL, author_id BIGINT NOT NULL, title VARCHAR(255) NOT NULL, CONSTRAINT bookPK PRIMARY KEY (id));')
+ }
+
+ void "The last SQL change sets to a file given as arguments"() {
+ when:
+ command.handle(getExecutionContext('1', outputFile.canonicalPath))
+
+ then:
+ def output = outputFile.text
+ output.contains('CREATE TABLE PUBLIC.book (id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, version BIGINT NOT NULL, author_id BIGINT NOT NULL, title VARCHAR(255) NOT NULL, CONSTRAINT bookPK PRIMARY KEY (id));')
+
+ }
+
+ void "The second last SQL change sets to a file given as arguments"() {
+ when:
+ command.handle(getExecutionContext('1', outputFile.canonicalPath, "--skip=1"))
+
+ then:
+ def output = outputFile.text
+ output.contains('CREATE TABLE PUBLIC.author (id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, version BIGINT NOT NULL, name VARCHAR(255) NOT NULL, CONSTRAINT authorPK PRIMARY KEY (id));')
+
+ }
+
+ void "an error occurs if the count parameter is not specified"() {
+ when:
+ command.handle(getExecutionContext())
+
+ then:
+ def e = thrown(DatabaseMigrationException)
+ e.message == "The ${command.name} command requires a change set number argument"
+ }
+
+ void "an error occurs if the count parameter is not number"() {
+ when:
+ command.handle(getExecutionContext('one'))
+
+ then:
+ def e = thrown(DatabaseMigrationException)
+ e.message == 'The change set number argument \'one\' isn\'t a number'
+ }
+
+
+ static final String CHANGE_LOG_CONTENT = '''
+databaseChangeLog = {
+
+ changeSet(author: "John Smith", id: "1") {
+ createTable(tableName: "author") {
+ column(autoIncrement: "true", name: "id", type: "BIGINT") {
+ constraints(primaryKey: "true", primaryKeyName: "authorPK")
+ }
+
+ column(name: "version", type: "BIGINT") {
+ constraints(nullable: "false")
+ }
+
+ column(name: "name", type: "VARCHAR(255)") {
+ constraints(nullable: "false")
+ }
+ }
+ }
+
+ changeSet(author: "John Smith", id: "2") {
+ createTable(tableName: "book") {
+ column(autoIncrement: "true", name: "id", type: "BIGINT") {
+ constraints(primaryKey: "true", primaryKeyName: "bookPK")
+ }
+
+ column(name: "version", type: "BIGINT") {
+ constraints(nullable: "false")
+ }
+
+ column(name: "author_id", type: "BIGINT") {
+ constraints(nullable: "false")
+ }
+
+ column(name: "title", type: "VARCHAR(255)") {
+ constraints(nullable: "false")
+ }
+ }
+ }
+}
+'''
+}
diff --git a/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmReleaseLocksCommandSpec.groovy b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmReleaseLocksCommandSpec.groovy
new file mode 100644
index 00000000000..807921a944e
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmReleaseLocksCommandSpec.groovy
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2015 original authors
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration.command
+
+import grails.dev.commands.ApplicationCommand
+
+class DbmReleaseLocksCommandSpec extends ApplicationContextDatabaseMigrationCommandSpec {
+
+ @Override
+ protected Class getCommandClass() {
+ return DbmReleaseLocksCommand
+ }
+
+ def "releases all locks on the database changelog"() {
+ given:
+ sql.executeUpdate('CREATE TABLE PUBLIC.DATABASECHANGELOGLOCK (ID INT NOT NULL, LOCKED BOOLEAN NOT NULL, LOCKGRANTED TIMESTAMP, LOCKEDBY VARCHAR(255), CONSTRAINT PK_DATABASECHANGELOGLOCK PRIMARY KEY (ID))')
+ sql.executeUpdate('INSERT INTO PUBLIC.DATABASECHANGELOGLOCK (ID, LOCKED, LOCKGRANTED, LOCKEDBY) VALUES (1, TRUE, NOW(), \'John Smith\')')
+
+ when:
+ command.handle(getExecutionContext())
+
+ then:
+ sql.rows('SELECT * FROM PUBLIC.DATABASECHANGELOGLOCK ')
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmRollbackCommandSpec.groovy b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmRollbackCommandSpec.groovy
new file mode 100644
index 00000000000..f0dbb3b06f7
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmRollbackCommandSpec.groovy
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2015-2024 original authors
+ *
+ * Licensed 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 org.grails.plugins.databasemigration.command
+
+import grails.dev.commands.ApplicationCommand
+import org.grails.plugins.databasemigration.DatabaseMigrationException
+
+class DbmRollbackCommandSpec extends ApplicationContextDatabaseMigrationCommandSpec {
+
+ @Override
+ protected Class getCommandClass() {
+ return DbmRollbackCommand
+ }
+
+ def setup() {
+ command.changeLogFile << CHANGE_LOG_CONTENT
+
+ new DbmUpdateCountCommand(applicationContext: applicationContext).handle(getExecutionContext('1'))
+ new DbmTagCommand(applicationContext: applicationContext).handle(getExecutionContext('test-tag'))
+ new DbmUpdateCommand(applicationContext: applicationContext).handle(getExecutionContext())
+
+ def tables = sql.rows('SELECT table_name FROM information_schema.tables WHERE table_class = \'org.h2.mvstore.db.MVTable\'').collect { it.table_name.toLowerCase() }
+ assert tables as Set == ['book', 'author', 'databasechangeloglock', 'databasechangelog'] as Set
+ }
+
+ def "rolls back the database to the state it was in when the tag was applied"() {
+ when:
+ command.handle(getExecutionContext('test-tag'))
+
+ then:
+ def tables = sql.rows('SELECT table_name FROM information_schema.tables WHERE table_class = \'org.h2.mvstore.db.MVTable\'').collect { it.table_name.toLowerCase() }
+ tables as Set == ['author', 'databasechangeloglock', 'databasechangelog'] as Set
+ }
+
+ def "an error occurs if tagName parameter is not specified"() {
+ when:
+ command.handle(getExecutionContext())
+
+ then:
+ def e = thrown(DatabaseMigrationException)
+ e.message == "The ${command.name} command requires a tag"
+ }
+
+ static final String CHANGE_LOG_CONTENT = '''
+databaseChangeLog = {
+
+ changeSet(author: "John Smith", id: "1") {
+ createTable(tableName: "author") {
+ column(autoIncrement: "true", name: "id", type: "BIGINT") {
+ constraints(primaryKey: "true", primaryKeyName: "authorPK")
+ }
+
+ column(name: "version", type: "BIGINT") {
+ constraints(nullable: "false")
+ }
+
+ column(name: "name", type: "VARCHAR(255)") {
+ constraints(nullable: "false")
+ }
+ }
+ }
+
+ changeSet(author: "John Smith", id: "2") {
+ createTable(tableName: "book") {
+ column(autoIncrement: "true", name: "id", type: "BIGINT") {
+ constraints(primaryKey: "true", primaryKeyName: "bookPK")
+ }
+
+ column(name: "version", type: "BIGINT") {
+ constraints(nullable: "false")
+ }
+
+ column(name: "author_id", type: "BIGINT") {
+ constraints(nullable: "false")
+ }
+
+ column(name: "title", type: "VARCHAR(255)") {
+ constraints(nullable: "false")
+ }
+ }
+ }
+}
+'''
+}
diff --git a/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmRollbackCountCommandSpec.groovy b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmRollbackCountCommandSpec.groovy
new file mode 100644
index 00000000000..5bce4dbfaad
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmRollbackCountCommandSpec.groovy
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2015-2024 original authors
+ *
+ * Licensed 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 org.grails.plugins.databasemigration.command
+
+import grails.dev.commands.ApplicationCommand
+import org.grails.plugins.databasemigration.DatabaseMigrationException
+
+class DbmRollbackCountCommandSpec extends ApplicationContextDatabaseMigrationCommandSpec {
+
+ @Override
+ protected Class getCommandClass() {
+ return DbmRollbackCountCommand
+ }
+
+ def setup() {
+ command.changeLogFile << CHANGE_LOG_CONTENT
+
+ new DbmUpdateCommand(applicationContext: applicationContext).handle(getExecutionContext())
+
+ def tables = sql.rows('SELECT table_name FROM information_schema.tables WHERE table_class = \'org.h2.mvstore.db.MVTable\'').collect { it.table_name.toLowerCase() }
+ assert tables as Set == ['book', 'author', 'databasechangeloglock', 'databasechangelog'] as Set
+ }
+
+ def "rolls back the specified number of change sets"() {
+ when:
+ command.handle(getExecutionContext('1'))
+
+ then:
+ def tables = sql.rows('SELECT table_name FROM information_schema.tables WHERE table_class = \'org.h2.mvstore.db.MVTable\'').collect { it.table_name.toLowerCase() }
+ tables as Set == ['author', 'databasechangeloglock', 'databasechangelog'] as Set
+ }
+
+ def "an error occurs if the count parameter is not specified"() {
+ when:
+ command.handle(getExecutionContext())
+
+ then:
+ def e = thrown(DatabaseMigrationException)
+ e.message == "The ${command.name} command requires a change set number argument"
+ }
+
+ def "an error occurs if the count parameter is not number"() {
+ when:
+ command.handle(getExecutionContext('one'))
+
+ then:
+ def e = thrown(DatabaseMigrationException)
+ e.message == 'The change set number argument \'one\' isn\'t a number'
+ }
+
+ static final String CHANGE_LOG_CONTENT = '''
+databaseChangeLog = {
+
+ changeSet(author: "John Smith", id: "1") {
+ createTable(tableName: "author") {
+ column(autoIncrement: "true", name: "id", type: "BIGINT") {
+ constraints(primaryKey: "true", primaryKeyName: "authorPK")
+ }
+
+ column(name: "version", type: "BIGINT") {
+ constraints(nullable: "false")
+ }
+
+ column(name: "name", type: "VARCHAR(255)") {
+ constraints(nullable: "false")
+ }
+ }
+ }
+
+ changeSet(author: "John Smith", id: "2") {
+ createTable(tableName: "book") {
+ column(autoIncrement: "true", name: "id", type: "BIGINT") {
+ constraints(primaryKey: "true", primaryKeyName: "bookPK")
+ }
+
+ column(name: "version", type: "BIGINT") {
+ constraints(nullable: "false")
+ }
+
+ column(name: "author_id", type: "BIGINT") {
+ constraints(nullable: "false")
+ }
+
+ column(name: "title", type: "VARCHAR(255)") {
+ constraints(nullable: "false")
+ }
+ }
+ }
+}
+'''
+}
diff --git a/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmRollbackCountSqlCommandSpec.groovy b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmRollbackCountSqlCommandSpec.groovy
new file mode 100644
index 00000000000..09b55acd880
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmRollbackCountSqlCommandSpec.groovy
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2015-2024 original authors
+ *
+ * Licensed 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 org.grails.plugins.databasemigration.command
+
+import grails.dev.commands.ApplicationCommand
+import org.grails.plugins.databasemigration.DatabaseMigrationException
+import spock.lang.AutoCleanup
+
+class DbmRollbackCountSqlCommandSpec extends ApplicationContextDatabaseMigrationCommandSpec {
+
+ @Override
+ protected Class getCommandClass() {
+ return DbmRollbackCountSqlCommand
+ }
+
+ @AutoCleanup('delete')
+ File outputFile = File.createTempFile('rollback', 'sql')
+
+ def setup() {
+ command.changeLogFile << CHANGE_LOG_CONTENT
+
+ new DbmUpdateCommand(applicationContext: applicationContext).handle(getExecutionContext())
+
+ def tables = sql.rows('SELECT table_name FROM information_schema.tables WHERE table_class = \'org.h2.mvstore.db.MVTable\'').collect { it.table_name.toLowerCase() }
+ assert tables as Set == ['book', 'author', 'databasechangeloglock', 'databasechangelog'] as Set
+ }
+
+ def "writes the SQL to roll back the specified number of change sets to STDOUT"() {
+ when:
+ command.handle(getExecutionContext('1'))
+
+ then:
+ def output = output.toString()
+ output.contains('DROP TABLE PUBLIC.book;')
+ !output.contains('DROP TABLE PUBLIC.author;')
+ }
+
+ def "writes the SQL to roll back the specified number of change sets to a file given as arguments"() {
+ when:
+ command.handle(getExecutionContext('1', outputFile.canonicalPath))
+
+ then:
+ def output = outputFile.text
+ output.contains('DROP TABLE PUBLIC.book;')
+ !output.contains('DROP TABLE PUBLIC.author;')
+
+ }
+
+ def "an error occurs if the count parameter is not specified"() {
+ when:
+ command.handle(getExecutionContext())
+
+ then:
+ def e = thrown(DatabaseMigrationException)
+ e.message == "The ${command.name} command requires a change set number argument"
+ }
+
+ def "an error occurs if the count parameter is not number"() {
+ when:
+ command.handle(getExecutionContext('one'))
+
+ then:
+ def e = thrown(DatabaseMigrationException)
+ e.message == 'The change set number argument \'one\' isn\'t a number'
+ }
+
+ static final String CHANGE_LOG_CONTENT = '''
+databaseChangeLog = {
+
+ changeSet(author: "John Smith", id: "1") {
+ createTable(tableName: "author") {
+ column(autoIncrement: "true", name: "id", type: "BIGINT") {
+ constraints(primaryKey: "true", primaryKeyName: "authorPK")
+ }
+
+ column(name: "version", type: "BIGINT") {
+ constraints(nullable: "false")
+ }
+
+ column(name: "name", type: "VARCHAR(255)") {
+ constraints(nullable: "false")
+ }
+ }
+ }
+
+ changeSet(author: "John Smith", id: "2") {
+ createTable(tableName: "book") {
+ column(autoIncrement: "true", name: "id", type: "BIGINT") {
+ constraints(primaryKey: "true", primaryKeyName: "bookPK")
+ }
+
+ column(name: "version", type: "BIGINT") {
+ constraints(nullable: "false")
+ }
+
+ column(name: "author_id", type: "BIGINT") {
+ constraints(nullable: "false")
+ }
+
+ column(name: "title", type: "VARCHAR(255)") {
+ constraints(nullable: "false")
+ }
+ }
+ }
+}
+'''
+}
diff --git a/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmRollbackSqlCommandSpec.groovy b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmRollbackSqlCommandSpec.groovy
new file mode 100644
index 00000000000..2b376e50c98
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmRollbackSqlCommandSpec.groovy
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2015-2024 original authors
+ *
+ * Licensed 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 org.grails.plugins.databasemigration.command
+
+import grails.dev.commands.ApplicationCommand
+import org.grails.plugins.databasemigration.DatabaseMigrationException
+import spock.lang.AutoCleanup
+
+class DbmRollbackSqlCommandSpec extends ApplicationContextDatabaseMigrationCommandSpec {
+
+ @Override
+ protected Class getCommandClass() {
+ return DbmRollbackSqlCommand
+ }
+
+ @AutoCleanup('delete')
+ File outputFile = File.createTempFile('rollback', 'sql')
+
+ def setup() {
+ command.changeLogFile << CHANGE_LOG_CONTENT
+
+ new DbmUpdateCountCommand(applicationContext: applicationContext).handle(getExecutionContext('1'))
+ new DbmTagCommand(applicationContext: applicationContext).handle(getExecutionContext('test-tag'))
+ new DbmUpdateCommand(applicationContext: applicationContext).handle(getExecutionContext())
+
+ def tables = sql.rows('SELECT table_name FROM information_schema.tables WHERE table_class = \'org.h2.mvstore.db.MVTable\'').collect { it.table_name.toLowerCase() }
+ assert tables as Set == ['book', 'author', 'databasechangeloglock', 'databasechangelog'] as Set
+ }
+
+ def "writes SQL to roll back the database to the state it was in when the tag was applied to STDOUT"() {
+ when:
+ command.handle(getExecutionContext('test-tag'))
+
+ then:
+ def output = output.toString()
+ output.contains('DROP TABLE PUBLIC.book;')
+ !output.contains('DROP TABLE PUBLIC.author;')
+ }
+
+ def "writes SQL to roll back the database to the state it was in when the tag was applied to a file given as arguments"() {
+ when:
+ command.handle(getExecutionContext('test-tag', outputFile.canonicalPath))
+
+ then:
+ def output = outputFile.text
+ output.contains('DROP TABLE PUBLIC.book;')
+ !output.contains('DROP TABLE PUBLIC.author;')
+
+ }
+
+ def "an error occurs if tagName parameter is not specified"() {
+ when:
+ command.handle(getExecutionContext())
+
+ then:
+ def e = thrown(DatabaseMigrationException)
+ e.message == "The ${command.name} command requires a tag"
+ }
+
+ static final String CHANGE_LOG_CONTENT = '''
+databaseChangeLog = {
+
+ changeSet(author: "John Smith", id: "1") {
+ createTable(tableName: "author") {
+ column(autoIncrement: "true", name: "id", type: "BIGINT") {
+ constraints(primaryKey: "true", primaryKeyName: "authorPK")
+ }
+
+ column(name: "version", type: "BIGINT") {
+ constraints(nullable: "false")
+ }
+
+ column(name: "name", type: "VARCHAR(255)") {
+ constraints(nullable: "false")
+ }
+ }
+ }
+
+ changeSet(author: "John Smith", id: "2") {
+ createTable(tableName: "book") {
+ column(autoIncrement: "true", name: "id", type: "BIGINT") {
+ constraints(primaryKey: "true", primaryKeyName: "bookPK")
+ }
+
+ column(name: "version", type: "BIGINT") {
+ constraints(nullable: "false")
+ }
+
+ column(name: "author_id", type: "BIGINT") {
+ constraints(nullable: "false")
+ }
+
+ column(name: "title", type: "VARCHAR(255)") {
+ constraints(nullable: "false")
+ }
+ }
+ }
+}
+'''
+}
diff --git a/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmRollbackToDateCommandSpec.groovy b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmRollbackToDateCommandSpec.groovy
new file mode 100644
index 00000000000..040ac6419cc
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmRollbackToDateCommandSpec.groovy
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2015-2024 original authors
+ *
+ * Licensed 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 org.grails.plugins.databasemigration.command
+
+import grails.dev.commands.ApplicationCommand
+import org.grails.plugins.databasemigration.DatabaseMigrationException
+
+class DbmRollbackToDateCommandSpec extends ApplicationContextDatabaseMigrationCommandSpec {
+
+ @Override
+ protected Class getCommandClass() {
+ return DbmRollbackToDateCommand
+ }
+
+ def setup() {
+ command.changeLogFile << CHANGE_LOG_CONTENT
+
+ new DbmUpdateCommand(applicationContext: applicationContext).handle(getExecutionContext())
+ sql.executeUpdate('UPDATE PUBLIC.DATABASECHANGELOG SET DATEEXECUTED = \'2015-01-02 12:00:00\' WHERE ID = \'1\'')
+
+ def tables = sql.rows('SELECT table_name FROM information_schema.tables WHERE table_class = \'org.h2.mvstore.db.MVTable\'').collect { it.table_name.toLowerCase() }
+ assert tables as Set == ['book', 'author', 'databasechangeloglock', 'databasechangelog'] as Set
+ }
+
+ def "rolls back the database to the state it was in at the given date/time"() {
+ when:
+ command.handle(getExecutionContext(args as String[]))
+
+ then:
+ def tables = sql.rows('SELECT table_name FROM information_schema.tables WHERE table_class = \'org.h2.mvstore.db.MVTable\'').collect { it.table_name.toLowerCase() }
+ tables as Set == ['author', 'databasechangeloglock', 'databasechangelog'] as Set
+
+ where:
+ args << [['2015-01-03'], ['2015-01-02', '13:00:00']]
+ }
+
+ def "an error occurs if the date parameter is not specified"() {
+ when:
+ command.handle(getExecutionContext())
+
+ then:
+ def e = thrown(DatabaseMigrationException)
+ e.message == 'Date must be specified as two strings with the format "yyyy-MM-dd HH:mm:ss" or as one strings with the format "yyyy-MM-dd"'
+ }
+
+ def "an error occurs if the date parameter is invalid format"() {
+ when:
+ command.handle(getExecutionContext(args as String[]))
+
+ then:
+ def e = thrown(DatabaseMigrationException)
+ e.message.startsWith("Problem parsing '${args.join(' ')}' as a Date")
+
+ where:
+ args << [['XXXX-01-03'], ['XXXX-01-02', '13:00:00']]
+ }
+
+ static final String CHANGE_LOG_CONTENT = '''
+databaseChangeLog = {
+
+ changeSet(author: "John Smith", id: "1") {
+ createTable(tableName: "author") {
+ column(autoIncrement: "true", name: "id", type: "BIGINT") {
+ constraints(primaryKey: "true", primaryKeyName: "authorPK")
+ }
+
+ column(name: "version", type: "BIGINT") {
+ constraints(nullable: "false")
+ }
+
+ column(name: "name", type: "VARCHAR(255)") {
+ constraints(nullable: "false")
+ }
+ }
+ }
+
+ changeSet(author: "John Smith", id: "2") {
+ createTable(tableName: "book") {
+ column(autoIncrement: "true", name: "id", type: "BIGINT") {
+ constraints(primaryKey: "true", primaryKeyName: "bookPK")
+ }
+
+ column(name: "version", type: "BIGINT") {
+ constraints(nullable: "false")
+ }
+
+ column(name: "author_id", type: "BIGINT") {
+ constraints(nullable: "false")
+ }
+
+ column(name: "title", type: "VARCHAR(255)") {
+ constraints(nullable: "false")
+ }
+ }
+ }
+}
+'''
+}
diff --git a/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmRollbackToDateSqlCommandSpec.groovy b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmRollbackToDateSqlCommandSpec.groovy
new file mode 100644
index 00000000000..1da64ebcf49
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmRollbackToDateSqlCommandSpec.groovy
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2015-2024 original authors
+ *
+ * Licensed 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 org.grails.plugins.databasemigration.command
+
+import grails.dev.commands.ApplicationCommand
+import org.grails.plugins.databasemigration.DatabaseMigrationException
+import spock.lang.AutoCleanup
+
+class DbmRollbackToDateSqlCommandSpec extends ApplicationContextDatabaseMigrationCommandSpec {
+
+ @Override
+ protected Class getCommandClass() {
+ return DbmRollbackToDateSqlCommand
+ }
+
+ @AutoCleanup('delete')
+ File outputFile = File.createTempFile('rollback', 'sql')
+
+ def setup() {
+ command.changeLogFile << CHANGE_LOG_CONTENT
+
+ new DbmUpdateCommand(applicationContext: applicationContext).handle(getExecutionContext())
+ sql.executeUpdate('UPDATE PUBLIC.DATABASECHANGELOG SET DATEEXECUTED = \'2015-01-02 12:00:00\' WHERE ID = \'1\'')
+
+ def tables = sql.rows('SELECT table_name FROM information_schema.tables WHERE table_class = \'org.h2.mvstore.db.MVTable\'').collect { it.table_name.toLowerCase() }
+ assert tables as Set == ['book', 'author', 'databasechangeloglock', 'databasechangelog'] as Set
+ }
+
+ def "writes SQL to roll back the database to the state it was in when the tag was applied to STDOUT"() {
+ when:
+ command.handle(getExecutionContext(args as String[]))
+
+ then:
+ def output = output.toString()
+ output.contains('DROP TABLE PUBLIC.book;')
+ !output.contains('DROP TABLE PUBLIC.author;')
+
+ where:
+ args << [['2015-01-03'], ['2015-01-02', '13:00:00']]
+ }
+
+ def "writes SQL to roll back the database to the state it was in when the tag was applied to a file given as arguments"() {
+ when:
+ command.handle(getExecutionContext(((args << outputFile.canonicalPath) as String[])))
+
+ then:
+ def output = outputFile.text
+ output.contains('DROP TABLE PUBLIC.book;')
+ !output.contains('DROP TABLE PUBLIC.author;')
+
+ where:
+ args << [['2015-01-03'], ['2015-01-02', '13:00:00']]
+ }
+
+ def "an error occurs if the date parameter is not specified"() {
+ when:
+ command.handle(getExecutionContext())
+
+ then:
+ def e = thrown(DatabaseMigrationException)
+ e.message == 'Date must be specified as two strings with the format "yyyy-MM-dd HH:mm:ss" or as one strings with the format "yyyy-MM-dd"'
+ }
+
+ def "an error occurs if the date parameter is invalid format"() {
+ when:
+ command.handle(getExecutionContext(args as String[]))
+
+ then:
+ def e = thrown(DatabaseMigrationException)
+ e.message.startsWith("Problem parsing '${args.join(' ')}' as a Date")
+
+ where:
+ args << [['XXXX-01-03'], ['XXXX-01-02', '13:00:00']]
+ }
+
+ static final String CHANGE_LOG_CONTENT = '''
+databaseChangeLog = {
+
+ changeSet(author: "John Smith", id: "1") {
+ createTable(tableName: "author") {
+ column(autoIncrement: "true", name: "id", type: "BIGINT") {
+ constraints(primaryKey: "true", primaryKeyName: "authorPK")
+ }
+
+ column(name: "version", type: "BIGINT") {
+ constraints(nullable: "false")
+ }
+
+ column(name: "name", type: "VARCHAR(255)") {
+ constraints(nullable: "false")
+ }
+ }
+ }
+
+ changeSet(author: "John Smith", id: "2") {
+ createTable(tableName: "book") {
+ column(autoIncrement: "true", name: "id", type: "BIGINT") {
+ constraints(primaryKey: "true", primaryKeyName: "bookPK")
+ }
+
+ column(name: "version", type: "BIGINT") {
+ constraints(nullable: "false")
+ }
+
+ column(name: "author_id", type: "BIGINT") {
+ constraints(nullable: "false")
+ }
+
+ column(name: "title", type: "VARCHAR(255)") {
+ constraints(nullable: "false")
+ }
+ }
+ }
+}
+'''
+}
diff --git a/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmStatusCommandSpec.groovy b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmStatusCommandSpec.groovy
new file mode 100644
index 00000000000..1b2a5691e28
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmStatusCommandSpec.groovy
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2015 original authors
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration.command
+
+import grails.dev.commands.ApplicationCommand
+import spock.lang.AutoCleanup
+
+class DbmStatusCommandSpec extends ApplicationContextDatabaseMigrationCommandSpec {
+
+ @Override
+ protected Class getCommandClass() {
+ return DbmStatusCommand
+ }
+
+ @AutoCleanup('delete')
+ File outputFile = File.createTempFile('update', 'sql')
+
+ def "outputs count or list of unrun change sets to STDOUT"() {
+ given:
+ command.changeLogFile << CHANGE_LOG_CONTENT
+
+ when:
+ command.handle(getExecutionContext())
+
+ then:
+ output.toString().contains('2 changesets have not been applied')
+ }
+
+ def "outputs count or list of unrun change sets to a file given as arguments"() {
+ given:
+ command.changeLogFile << CHANGE_LOG_CONTENT
+
+ when:
+ command.handle(getExecutionContext(outputFile.canonicalPath))
+
+ then:
+ outputFile.text.contains('2 changesets have not been applied')
+ }
+
+ static final String CHANGE_LOG_CONTENT = '''
+databaseChangeLog = {
+ changeSet(author: "John Smith", id: "changeSet1") {
+ }
+ changeSet(author: "John Smith", id: "changeSet2") {
+ }
+}
+'''
+}
diff --git a/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmUpdateCommandSpec.groovy b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmUpdateCommandSpec.groovy
new file mode 100644
index 00000000000..b264b70a09e
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmUpdateCommandSpec.groovy
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2015-2024 original authors
+ *
+ * Licensed 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 org.grails.plugins.databasemigration.command
+
+import grails.dev.commands.ApplicationCommand
+
+class DbmUpdateCommandSpec extends ApplicationContextDatabaseMigrationCommandSpec {
+
+ @Override
+ protected Class getCommandClass() {
+ return DbmUpdateCommand
+ }
+
+ def "updates database to current version"() {
+ given:
+ command.changeLogFile << CHANGE_LOG_CONTENT
+
+ when:
+ command.handle(getExecutionContext())
+
+ then:
+ def tables = sql.rows('SELECT table_name FROM information_schema.tables WHERE table_class = \'org.h2.mvstore.db.MVTable\'').collect { it.table_name.toLowerCase() }
+ tables as Set == ['book', 'author', 'databasechangeloglock', 'databasechangelog'] as Set
+
+ and:
+ def authors = sql.rows('SELECT name FROM author').collect { it.name }
+ authors as Set == ['Mary', 'Amelia'] as Set
+ }
+
+ def "updates database to current version with contexts"() {
+ given:
+ command.changeLogFile << CHANGE_LOG_CONTENT
+
+ when:
+ command.handle(getExecutionContext('--contexts=test'))
+
+ then:
+ def tables = sql.rows('SELECT table_name FROM information_schema.tables WHERE table_class = \'org.h2.mvstore.db.MVTable\'').collect { it.table_name.toLowerCase() }
+ tables as Set == ['book', 'author', 'databasechangeloglock', 'databasechangelog'] as Set
+
+ and:
+ def authors = sql.rows('SELECT name FROM author').collect { it.name }
+ authors as Set == ['Amelia'] as Set
+ }
+
+ static final String CHANGE_LOG_CONTENT = '''
+databaseChangeLog = {
+
+ changeSet(author: "John Smith", id: "1") {
+ createTable(tableName: "author") {
+ column(autoIncrement: "true", name: "id", type: "BIGINT") {
+ constraints(primaryKey: "true", primaryKeyName: "authorPK")
+ }
+
+ column(name: "version", type: "BIGINT") {
+ constraints(nullable: "false")
+ }
+
+ column(name: "name", type: "VARCHAR(255)") {
+ constraints(nullable: "false")
+ }
+ }
+ }
+
+ changeSet(author: "John Smith", id: "2") {
+ createTable(tableName: "book") {
+ column(autoIncrement: "true", name: "id", type: "BIGINT") {
+ constraints(primaryKey: "true", primaryKeyName: "bookPK")
+ }
+
+ column(name: "version", type: "BIGINT") {
+ constraints(nullable: "false")
+ }
+
+ column(name: "author_id", type: "BIGINT") {
+ constraints(nullable: "false")
+ }
+
+ column(name: "title", type: "VARCHAR(255)") {
+ constraints(nullable: "false")
+ }
+ }
+ }
+
+ changeSet(author: "John Smith", id: "3") {
+ addForeignKeyConstraint(baseColumnNames: "author_id", baseTableName: "book", constraintName: "FK_4sac2ubmnqva85r8bk8fxdvbf", deferrable: "false", initiallyDeferred: "false", referencedColumnNames: "id", referencedTableName: "author")
+ }
+
+ changeSet(author: "John Smith", id: "4", context: "development") {
+ insert(tableName: "author") {
+ column(name: "name", value: "Mary")
+ column(name: "version", value: "0")
+ }
+ }
+
+ changeSet(author: "John Smith", id: "5", context: "test") {
+ insert(tableName: "author") {
+ column(name: "name", value: "Amelia")
+ column(name: "version", value: "0")
+ }
+ }
+}
+'''
+}
diff --git a/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmUpdateCountCommandSpec.groovy b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmUpdateCountCommandSpec.groovy
new file mode 100644
index 00000000000..e5e068472bc
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmUpdateCountCommandSpec.groovy
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2015-2024 original authors
+ *
+ * Licensed 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 org.grails.plugins.databasemigration.command
+
+import grails.dev.commands.ApplicationCommand
+import org.grails.plugins.databasemigration.DatabaseMigrationException
+
+class DbmUpdateCountCommandSpec extends ApplicationContextDatabaseMigrationCommandSpec {
+
+ @Override
+ protected Class getCommandClass() {
+ return DbmUpdateCountCommand
+ }
+
+ def "applies next NUM changes to the database"() {
+ given:
+ command.changeLogFile << CHANGE_LOG_CONTENT
+
+ when:
+ command.handle(getExecutionContext('1'))
+
+ then:
+ def tables = sql.rows('SELECT table_name FROM information_schema.tables WHERE table_class = \'org.h2.mvstore.db.MVTable\'').collect { it.table_name.toLowerCase() }
+ tables as Set == ['author', 'databasechangeloglock', 'databasechangelog'] as Set
+ }
+
+ def "an error occurs if the count parameter is not specified"() {
+ when:
+ command.handle(getExecutionContext())
+
+ then:
+ def e = thrown(DatabaseMigrationException)
+ e.message == "The ${command.name} command requires a change set number argument"
+ }
+
+ def "an error occurs if the count parameter is not number"() {
+ when:
+ command.handle(getExecutionContext('one'))
+
+ then:
+ def e = thrown(DatabaseMigrationException)
+ e.message == 'The change set number argument \'one\' isn\'t a number'
+ }
+
+ static final String CHANGE_LOG_CONTENT = '''
+databaseChangeLog = {
+
+ changeSet(author: "John Smith", id: "1") {
+ createTable(tableName: "author") {
+ column(autoIncrement: "true", name: "id", type: "BIGINT") {
+ constraints(primaryKey: "true", primaryKeyName: "authorPK")
+ }
+
+ column(name: "version", type: "BIGINT") {
+ constraints(nullable: "false")
+ }
+
+ column(name: "name", type: "VARCHAR(255)") {
+ constraints(nullable: "false")
+ }
+ }
+ }
+
+ changeSet(author: "John Smith", id: "2") {
+ createTable(tableName: "book") {
+ column(autoIncrement: "true", name: "id", type: "BIGINT") {
+ constraints(primaryKey: "true", primaryKeyName: "bookPK")
+ }
+
+ column(name: "version", type: "BIGINT") {
+ constraints(nullable: "false")
+ }
+
+ column(name: "author_id", type: "BIGINT") {
+ constraints(nullable: "false")
+ }
+
+ column(name: "title", type: "VARCHAR(255)") {
+ constraints(nullable: "false")
+ }
+ }
+ }
+}
+'''
+}
diff --git a/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmUpdateCountSqlCommandSpec.groovy b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmUpdateCountSqlCommandSpec.groovy
new file mode 100644
index 00000000000..a519a4b4ed5
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmUpdateCountSqlCommandSpec.groovy
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2015-2024 original authors
+ *
+ * Licensed 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 org.grails.plugins.databasemigration.command
+
+import grails.dev.commands.ApplicationCommand
+import org.grails.plugins.databasemigration.DatabaseMigrationException
+import spock.lang.AutoCleanup
+
+class DbmUpdateCountSqlCommandSpec extends ApplicationContextDatabaseMigrationCommandSpec {
+
+ @Override
+ protected Class getCommandClass() {
+ return DbmUpdateCountSqlCommand
+ }
+
+ @AutoCleanup('delete')
+ File outputFile = File.createTempFile('update', 'sql')
+
+ def "writes SQL to apply next NUM changes to the database to STDOUT"() {
+ given:
+ command.changeLogFile << CHANGE_LOG_CONTENT
+
+ when:
+ command.handle(getExecutionContext('1'))
+
+ then:
+ def output = output.toString()
+ output.contains('CREATE TABLE PUBLIC.author (id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, version BIGINT NOT NULL, name VARCHAR(255) NOT NULL, CONSTRAINT authorPK PRIMARY KEY (id));')
+ !output.contains('CREATE TABLE PUBLIC.book (id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, version BIGINT NOT NULL, author_id BIGINT NOT NULL, title VARCHAR(255) NOT NULL, CONSTRAINT bookPK PRIMARY KEY (id));')
+ }
+
+ def "writes SQL to apply next NUM changes to the database to a file given as arguments"() {
+ given:
+ command.changeLogFile << CHANGE_LOG_CONTENT
+
+ when:
+ command.handle(getExecutionContext('1', outputFile.canonicalPath))
+
+ then:
+ def output = outputFile.text
+ output.contains('CREATE TABLE PUBLIC.author (id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, version BIGINT NOT NULL, name VARCHAR(255) NOT NULL, CONSTRAINT authorPK PRIMARY KEY (id));')
+ !output.contains('CREATE TABLE PUBLIC.book (id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, version BIGINT NOT NULL, author_id BIGINT NOT NULL, title VARCHAR(255) NOT NULL, CONSTRAINT bookPK PRIMARY KEY (id));')
+ }
+
+ def "an error occurs if the count parameter is not specified"() {
+ when:
+ command.handle(getExecutionContext())
+
+ then:
+ def e = thrown(DatabaseMigrationException)
+ e.message == "The ${command.name} command requires a change set number argument"
+ }
+
+ def "an error occurs if the count parameter is not number"() {
+ when:
+ command.handle(getExecutionContext('one'))
+
+ then:
+ def e = thrown(DatabaseMigrationException)
+ e.message == 'The change set number argument \'one\' isn\'t a number'
+ }
+
+ static final String CHANGE_LOG_CONTENT = '''
+databaseChangeLog = {
+
+ changeSet(author: "John Smith", id: "1") {
+ createTable(tableName: "author") {
+ column(autoIncrement: "true", name: "id", type: "BIGINT") {
+ constraints(primaryKey: "true", primaryKeyName: "authorPK")
+ }
+
+ column(name: "version", type: "BIGINT") {
+ constraints(nullable: "false")
+ }
+
+ column(name: "name", type: "VARCHAR(255)") {
+ constraints(nullable: "false")
+ }
+ }
+ }
+
+ changeSet(author: "John Smith", id: "2") {
+ createTable(tableName: "book") {
+ column(autoIncrement: "true", name: "id", type: "BIGINT") {
+ constraints(primaryKey: "true", primaryKeyName: "bookPK")
+ }
+
+ column(name: "version", type: "BIGINT") {
+ constraints(nullable: "false")
+ }
+
+ column(name: "author_id", type: "BIGINT") {
+ constraints(nullable: "false")
+ }
+
+ column(name: "title", type: "VARCHAR(255)") {
+ constraints(nullable: "false")
+ }
+ }
+ }
+
+ changeSet(author: "John Smith", id: "3") {
+ addForeignKeyConstraint(baseColumnNames: "author_id", baseTableName: "book", constraintName: "FK_4sac2ubmnqva85r8bk8fxdvbf", deferrable: "false", initiallyDeferred: "false", referencedColumnNames: "id", referencedTableName: "author")
+ }
+
+ changeSet(author: "John Smith", id: "4", context: "development") {
+ insert(tableName: "author") {
+ column(name: "name", value: "Mary")
+ column(name: "version", value: "0")
+ }
+ }
+
+ changeSet(author: "John Smith", id: "5", context: "test") {
+ insert(tableName: "author") {
+ column(name: "name", value: "Amelia")
+ column(name: "version", value: "0")
+ }
+ }
+}
+'''
+}
diff --git a/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmUpdateSqlCommandSpec.groovy b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmUpdateSqlCommandSpec.groovy
new file mode 100644
index 00000000000..ce0ad4d8053
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmUpdateSqlCommandSpec.groovy
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2015-2024 original authors
+ *
+ * Licensed 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 org.grails.plugins.databasemigration.command
+
+import grails.dev.commands.ApplicationCommand
+import spock.lang.AutoCleanup
+
+class DbmUpdateSqlCommandSpec extends ApplicationContextDatabaseMigrationCommandSpec {
+
+ @Override
+ protected Class getCommandClass() {
+ return DbmUpdateSqlCommand
+ }
+
+ @AutoCleanup('delete')
+ File outputFile = File.createTempFile('update', 'sql')
+
+ def "writes SQL to update database to STDOUT"() {
+ given:
+ command.changeLogFile << CHANGE_LOG_CONTENT
+
+ when:
+ command.handle(getExecutionContext())
+
+ then:
+ def output = output.toString()
+ output.contains('CREATE TABLE PUBLIC.author (id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, version BIGINT NOT NULL, name VARCHAR(255) NOT NULL, CONSTRAINT authorPK PRIMARY KEY (id));')
+ output.contains('CREATE TABLE PUBLIC.book (id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, version BIGINT NOT NULL, author_id BIGINT NOT NULL, title VARCHAR(255) NOT NULL, CONSTRAINT bookPK PRIMARY KEY (id));')
+ output.contains('ALTER TABLE PUBLIC.book ADD CONSTRAINT FK_4sac2ubmnqva85r8bk8fxdvbf FOREIGN KEY (author_id) REFERENCES PUBLIC.author (id);')
+ output.contains('INSERT INTO PUBLIC.author (name, version) VALUES (\'Mary\', \'0\');')
+ output.contains('INSERT INTO PUBLIC.author (name, version) VALUES (\'Amelia\', \'0\');')
+ }
+
+ def "writes SQL to update database with contexts"() {
+ given:
+ command.changeLogFile << CHANGE_LOG_CONTENT
+
+ when:
+ command.handle(getExecutionContext('--contexts=test'))
+
+ then:
+ def output = output.toString()
+ output.contains('CREATE TABLE PUBLIC.author (id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, version BIGINT NOT NULL, name VARCHAR(255) NOT NULL, CONSTRAINT authorPK PRIMARY KEY (id));')
+ output.contains('CREATE TABLE PUBLIC.book (id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, version BIGINT NOT NULL, author_id BIGINT NOT NULL, title VARCHAR(255) NOT NULL, CONSTRAINT bookPK PRIMARY KEY (id));')
+ output.contains('ALTER TABLE PUBLIC.book ADD CONSTRAINT FK_4sac2ubmnqva85r8bk8fxdvbf FOREIGN KEY (author_id) REFERENCES PUBLIC.author (id);')
+ !output.contains('INSERT INTO PUBLIC.author (name, version) VALUES (\'Mary\', \'0\');')
+ output.contains('INSERT INTO PUBLIC.author (name, version) VALUES (\'Amelia\', \'0\');')
+ }
+
+ def "writes SQL to update database to a file given as arguments"() {
+ given:
+ command.changeLogFile << CHANGE_LOG_CONTENT
+
+ when:
+ command.handle(getExecutionContext(outputFile.canonicalPath))
+
+ then:
+ def output = outputFile.text
+ output.contains('CREATE TABLE PUBLIC.author (id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, version BIGINT NOT NULL, name VARCHAR(255) NOT NULL, CONSTRAINT authorPK PRIMARY KEY (id));')
+ output.contains('CREATE TABLE PUBLIC.book (id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, version BIGINT NOT NULL, author_id BIGINT NOT NULL, title VARCHAR(255) NOT NULL, CONSTRAINT bookPK PRIMARY KEY (id));')
+ output.contains('ALTER TABLE PUBLIC.book ADD CONSTRAINT FK_4sac2ubmnqva85r8bk8fxdvbf FOREIGN KEY (author_id) REFERENCES PUBLIC.author (id);')
+ output.contains('INSERT INTO PUBLIC.author (name, version) VALUES (\'Mary\', \'0\');')
+ output.contains('INSERT INTO PUBLIC.author (name, version) VALUES (\'Amelia\', \'0\');')
+ }
+
+ static final String CHANGE_LOG_CONTENT = '''
+databaseChangeLog = {
+
+ changeSet(author: "John Smith", id: "1") {
+ createTable(tableName: "author") {
+ column(autoIncrement: "true", name: "id", type: "BIGINT") {
+ constraints(primaryKey: "true", primaryKeyName: "authorPK")
+ }
+
+ column(name: "version", type: "BIGINT") {
+ constraints(nullable: "false")
+ }
+
+ column(name: "name", type: "VARCHAR(255)") {
+ constraints(nullable: "false")
+ }
+ }
+ }
+
+ changeSet(author: "John Smith", id: "2") {
+ createTable(tableName: "book") {
+ column(autoIncrement: "true", name: "id", type: "BIGINT") {
+ constraints(primaryKey: "true", primaryKeyName: "bookPK")
+ }
+
+ column(name: "version", type: "BIGINT") {
+ constraints(nullable: "false")
+ }
+
+ column(name: "author_id", type: "BIGINT") {
+ constraints(nullable: "false")
+ }
+
+ column(name: "title", type: "VARCHAR(255)") {
+ constraints(nullable: "false")
+ }
+ }
+ }
+
+ changeSet(author: "John Smith", id: "3") {
+ addForeignKeyConstraint(baseColumnNames: "author_id", baseTableName: "book", constraintName: "FK_4sac2ubmnqva85r8bk8fxdvbf", deferrable: "false", initiallyDeferred: "false", referencedColumnNames: "id", referencedTableName: "author")
+ }
+
+ changeSet(author: "John Smith", id: "4", context: "development") {
+ insert(tableName: "author") {
+ column(name: "name", value: "Mary")
+ column(name: "version", value: "0")
+ }
+ }
+
+ changeSet(author: "John Smith", id: "5", context: "test") {
+ insert(tableName: "author") {
+ column(name: "name", value: "Amelia")
+ column(name: "version", value: "0")
+ }
+ }
+}
+'''
+}
diff --git a/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmValidateCommandSpec.groovy b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmValidateCommandSpec.groovy
new file mode 100644
index 00000000000..cb6e2c54fca
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/DbmValidateCommandSpec.groovy
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2015 original authors
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration.command
+
+import grails.dev.commands.ApplicationCommand
+import liquibase.exception.CommandExecutionException
+
+class DbmValidateCommandSpec extends ApplicationContextDatabaseMigrationCommandSpec {
+
+ @Override
+ protected Class getCommandClass() {
+ return DbmValidateCommand
+ }
+
+ def "checks the valid changelog"() {
+ given:
+ command.changeLogFile << CHANGE_LOG_CONTENT
+
+ expect:
+ command.handle(getExecutionContext())
+ }
+
+ def "checks the invalid changelog"() {
+ given:
+ command.changeLogFile << 'xxx'
+
+ when:
+ command.handle(getExecutionContext())
+
+ then:
+ thrown(CommandExecutionException)
+ }
+
+ static final String CHANGE_LOG_CONTENT = '''
+databaseChangeLog = {
+
+ changeSet(author: "John Smith", id: "1") {
+ createTable(tableName: "author") {
+ column(autoIncrement: "true", name: "id", type: "BIGINT") {
+ constraints(primaryKey: "true", primaryKeyName: "authorPK")
+ }
+
+ column(name: "version", type: "BIGINT") {
+ constraints(nullable: "false")
+ }
+
+ column(name: "name", type: "VARCHAR(255)") {
+ constraints(nullable: "false")
+ }
+ }
+ }
+
+ changeSet(author: "John Smith", id: "2") {
+ createTable(tableName: "book") {
+ column(autoIncrement: "true", name: "id", type: "BIGINT") {
+ constraints(primaryKey: "true", primaryKeyName: "bookPK")
+ }
+
+ column(name: "version", type: "BIGINT") {
+ constraints(nullable: "false")
+ }
+
+ column(name: "author_id", type: "BIGINT") {
+ constraints(nullable: "false")
+ }
+
+ column(name: "title", type: "VARCHAR(255)") {
+ constraints(nullable: "false")
+ }
+ }
+ }
+}
+'''
+}
diff --git a/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/ScriptDatabaseMigrationCommandSpec.groovy b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/ScriptDatabaseMigrationCommandSpec.groovy
new file mode 100644
index 00000000000..398dc1afaac
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/command/ScriptDatabaseMigrationCommandSpec.groovy
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2015 original authors
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration.command
+
+import grails.util.GrailsNameUtils
+import org.grails.build.parsing.CommandLineParser
+import org.grails.cli.GrailsCli
+import org.grails.cli.profile.ExecutionContext
+import org.grails.config.CodeGenConfig
+import org.h2.Driver
+
+abstract class ScriptDatabaseMigrationCommandSpec extends DatabaseMigrationCommandSpec {
+
+ ScriptDatabaseMigrationCommand command
+
+ CodeGenConfig config
+
+ def setup() {
+ def configMap = [
+ 'grails.plugin.databasemigration.changelogLocation': changeLogLocation.canonicalPath,
+ 'dataSource.url' : 'jdbc:h2:mem:testDb',
+ 'dataSource.username' : 'sa',
+ 'dataSource.password' : '',
+ 'dataSource.driverClassName' : Driver.name,
+ 'environments.other.dataSource.url' : 'jdbc:h2:mem:otherDb',
+ ]
+ config = new CodeGenConfig()
+ config.mergeMap(configMap)
+ config.mergeMap(configMap, true)
+
+ command = commandClass.newInstance()
+ command.config = config
+ command.changeLogFile.parentFile.mkdirs()
+ }
+
+ abstract protected Class getCommandClass()
+
+ protected ExecutionContext getExecutionContext(String... args) {
+ def executionContext = new GrailsCli.ExecutionContextImpl(config)
+ executionContext.commandLine = new CommandLineParser().parse(([GrailsNameUtils.getScriptName(GrailsNameUtils.getLogicalName(commandClass.name, 'Command'))] + args.toList()) as String[])
+ executionContext
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/liquibase/GroovyChangeLogSpec.groovy b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/liquibase/GroovyChangeLogSpec.groovy
new file mode 100644
index 00000000000..4d22e84f5a3
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/liquibase/GroovyChangeLogSpec.groovy
@@ -0,0 +1,222 @@
+package org.grails.plugins.databasemigration.liquibase
+
+import grails.core.GrailsApplication
+import liquibase.exception.CommandExecutionException
+import org.grails.plugins.databasemigration.command.ApplicationContextDatabaseMigrationCommandSpec
+import org.grails.plugins.databasemigration.command.DbmChangelogSyncCommand
+import org.grails.plugins.databasemigration.command.DbmRollbackCommand
+import org.grails.plugins.databasemigration.command.DbmTagCommand
+import org.grails.plugins.databasemigration.command.DbmUpdateCommand
+import org.grails.plugins.databasemigration.command.DbmUpdateCountCommand
+
+class GroovyChangeLogSpec extends ApplicationContextDatabaseMigrationCommandSpec {
+
+ static List calledBlocks
+
+ def setup() {
+ calledBlocks = []
+ Locale.setDefault(new Locale("en", "US"))
+ }
+
+ def "updates a database with Groovy Change"() {
+ given:
+ def command = createCommand(DbmUpdateCommand)
+ command.changeLogFile << """
+databaseChangeLog = {
+ changeSet(author: "John Smith", id: "1") {
+ grailsChange {
+ init { ${GroovyChangeLogSpec.name}.calledBlocks << 'init' }
+ validate { ${GroovyChangeLogSpec.name}.calledBlocks << 'validate' }
+ change { ${GroovyChangeLogSpec.name}.calledBlocks << 'change' }
+ rollback { ${GroovyChangeLogSpec.name}.calledBlocks << 'rollback' }
+ confirm 'confirmation message'
+ checkSum 'override value for checksum'
+ }
+ }
+}
+"""
+ when:
+ command.handle(getExecutionContext(DbmUpdateCommand))
+
+ then:
+ calledBlocks == ['init', 'validate', 'change']
+ output.toString().contains('confirmation message')
+ }
+
+
+ def "outputs a warning message by calling the warn method"() {
+ given:
+ def command = createCommand(DbmUpdateCommand)
+ command.changeLogFile << """
+databaseChangeLog = {
+ changeSet(author: "John Smith", id: "2") {
+ grailsChange {
+ validate {
+ ${GroovyChangeLogSpec.name}.calledBlocks << 'validate'
+ warn('warn message')
+ }
+ change {
+ ${GroovyChangeLogSpec.name}.calledBlocks << 'change'
+ }
+ }
+ }
+}
+"""
+ when:
+ command.handle(getExecutionContext(DbmUpdateCommand))
+
+ then:
+ output.toString().contains('warn message')
+ calledBlocks == ['validate', 'change']
+ }
+
+ def "stops processing by calling the error method"() {
+ given:
+ DbmUpdateCommand command = (DbmUpdateCommand) createCommand(DbmUpdateCommand)
+ command.changeLogFile << """
+databaseChangeLog = {
+ changeSet(author: "John Smith", id: "1") {
+ grailsChange {
+ validate {
+ ${GroovyChangeLogSpec.name}.calledBlocks << 'validate'
+ error('error message')
+ }
+ change {
+ ${GroovyChangeLogSpec.name}.calledBlocks << 'change'
+ }
+ }
+ }
+}
+"""
+ when:
+ command.handle(getExecutionContext(DbmUpdateCommand))
+
+ then:
+ def e = thrown(CommandExecutionException)
+
+ e.message.contains('1 changes have validation failures')
+ e.message.contains('error message, changelog.groovy::1::John Smith')
+ calledBlocks == ['validate']
+ }
+
+
+ def "can use bind variables in the change block"() {
+ given:
+ def command = createCommand(DbmUpdateCommand)
+ command.changeLogFile << """
+databaseChangeLog = {
+ changeSet(author: "John Smith", id: "4") {
+ grailsChange {
+ change {
+ assert changeSet.id == '4'
+ assert resourceAccessor.toString().startsWith('CompositeResourceAccessor{')
+ assert ctx.hashCode() == ${applicationContext.hashCode()}
+ assert application.hashCode() == ${applicationContext.getBean(GrailsApplication).hashCode()}
+ ${GroovyChangeLogSpec.name}.calledBlocks << 'change'
+ }
+ }
+ }
+}
+"""
+ when:
+ command.handle(getExecutionContext(DbmUpdateCommand))
+
+ then:
+ calledBlocks == ['change']
+ }
+
+
+ def "executes sql statements in the change block"() {
+ given:
+ def command = createCommand(DbmUpdateCommand)
+ command.changeLogFile << """
+import groovy.sql.Sql
+import liquibase.statement.core.InsertStatement
+
+databaseChangeLog = {
+ changeSet(author: "John Smith", id: "5") {
+ grailsChange {
+ change {
+ new Sql(database.connection.underlyingConnection).executeUpdate('CREATE TABLE book (id INT)')
+ new Sql(databaseConnection.underlyingConnection).executeUpdate('INSERT INTO book (id) VALUES (1)')
+ new Sql(connection).executeUpdate('INSERT INTO book (id) VALUES (2)')
+ sqlStatement(new InsertStatement(null, null, 'book').addColumnValue('id', 3))
+ sqlStatements([new InsertStatement(null, null, 'book').addColumnValue('id', 4), new InsertStatement(null, null, 'book').addColumnValue('id', 5)])
+ }
+ }
+ }
+}
+"""
+
+ when:
+ command.handle(getExecutionContext(DbmUpdateCommand))
+
+ then:
+ sql.rows('SELECT id FROM book').collect { it.id } as Set == [1, 2, 3, 4, 5] as Set
+ }
+
+
+ def "rolls back a database with Groovy Change"() {
+ given:
+ def command = createCommand(DbmRollbackCommand)
+ command.changeLogFile << """
+databaseChangeLog = {
+ changeSet(author: "John Smith", id: "6") {
+ }
+ changeSet(author: "John Smith", id: "7") {
+ grailsChange {
+ init { ${GroovyChangeLogSpec.name}.calledBlocks << 'init' }
+ validate { ${GroovyChangeLogSpec.name}.calledBlocks << 'validate' }
+ change { ${GroovyChangeLogSpec.name}.calledBlocks << 'change' }
+ rollback { ${GroovyChangeLogSpec.name}.calledBlocks << 'rollback' }
+ confirm 'confirmation message'
+ checkSum 'override value for checksum'
+ }
+ }
+}
+"""
+ createCommand(DbmUpdateCountCommand).handle(getExecutionContext(DbmUpdateCountCommand, '1'))
+ createCommand(DbmTagCommand).handle(getExecutionContext(DbmTagCommand, 'test tag'))
+ createCommand(DbmChangelogSyncCommand).handle(getExecutionContext(DbmChangelogSyncCommand))
+ calledBlocks = []
+
+ when:
+ command.handle(getExecutionContext(DbmRollbackCommand, 'test tag'))
+
+ then:
+ calledBlocks == ['init', 'change', 'rollback', 'rollback']
+ }
+
+
+ def "can use bind variables in the rollback block"() {
+ given:
+ def command = createCommand(DbmRollbackCommand)
+ command.changeLogFile << """
+databaseChangeLog = {
+ changeSet(author: "John Smith", id: "8") {
+ }
+ changeSet(author: "John Smith", id: "9") {
+ grailsChange {
+ rollback {
+ assert changeSet.id == '9'
+ assert resourceAccessor.toString().startsWith('CompositeResourceAccessor{')
+ assert ctx.hashCode() == ${applicationContext.hashCode()}
+ assert application.hashCode() == ${applicationContext.getBean(GrailsApplication).hashCode()}
+ ${GroovyChangeLogSpec.name}.calledBlocks << 'rollback'
+ }
+ }
+ }
+}
+"""
+ createCommand(DbmUpdateCountCommand).handle(getExecutionContext(DbmUpdateCountCommand, '1'))
+ createCommand(DbmTagCommand).handle(getExecutionContext(DbmTagCommand, 'test tag'))
+ createCommand(DbmChangelogSyncCommand).handle(getExecutionContext(DbmChangelogSyncCommand))
+ calledBlocks = []
+
+ when:
+ command.handle(getExecutionContext(DbmRollbackCommand, 'test tag'))
+
+ then:
+ calledBlocks == ['rollback', 'rollback']
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/liquibase/GroovyPreconditionSpec.groovy b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/liquibase/GroovyPreconditionSpec.groovy
new file mode 100644
index 00000000000..fd65a0036bf
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/liquibase/GroovyPreconditionSpec.groovy
@@ -0,0 +1,282 @@
+/*
+ * Copyright 2015 original authors
+ *
+ * Licensed 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
+ *
+ * http://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 org.grails.plugins.databasemigration.liquibase
+
+import liquibase.exception.CommandExecutionException
+import org.grails.plugins.databasemigration.command.ApplicationContextDatabaseMigrationCommandSpec
+import org.grails.plugins.databasemigration.command.DbmUpdateCommand
+
+class GroovyPreconditionSpec extends ApplicationContextDatabaseMigrationCommandSpec {
+
+
+ static List executedChangeSets
+
+ def setup() {
+ executedChangeSets = []
+ }
+ def cleanup() {
+ executedChangeSets.clear()
+ }
+
+ def "changeSet precondition is satisfied"() {
+ given:
+ def command = createCommand(DbmUpdateCommand)
+ command.changeLogFile << """
+databaseChangeLog = {
+ changeSet(author: 'John Smith', id: '1') {
+ preConditions {
+ grailsPrecondition {
+ check {
+ assert true
+ }
+ }
+ }
+ grailsChange {
+ change {
+ ${GroovyPreconditionSpec.name}.executedChangeSets << changeSet.id
+ }
+ }
+ }
+}
+"""
+ when:
+ command.handle(getExecutionContext(DbmUpdateCommand))
+
+ then:
+ executedChangeSets == ['1']
+ }
+
+ def "changeSet precondition is not satisfied by using a simple assertion"() {
+ given:
+ def command = createCommand(DbmUpdateCommand)
+ command.changeLogFile << """
+databaseChangeLog = {
+ changeSet(author: 'John Smith', id: '1') {
+ preConditions(onFail: 'CONTINUE') {
+ grailsPrecondition {
+ check {
+ assert false
+ }
+ }
+ }
+ grailsChange {
+ change {
+ ${GroovyPreconditionSpec.name}.executedChangeSets << changeSet.id
+ }
+ }
+ }
+ changeSet(author: 'John Smith', id: '2') {
+ grailsChange {
+ change {
+ ${GroovyPreconditionSpec.name}.executedChangeSets << changeSet.id
+ }
+ }
+ }
+}
+"""
+ when:
+ command.handle(getExecutionContext(DbmUpdateCommand))
+
+ then:
+ executedChangeSets == ['2']
+ }
+
+ def "changeSet precondition is not satisfied by using an assertion with a message"() {
+ given:
+ def command = createCommand(DbmUpdateCommand)
+ command.changeLogFile << """
+databaseChangeLog = {
+ changeSet(author: 'John Smith', id: '1') {
+ preConditions(onFail: 'CONTINUE') {
+ grailsPrecondition {
+ check {
+ assert false: 'precondition is not satisfied'
+ }
+ }
+ }
+ grailsChange {
+ change {
+ ${GroovyPreconditionSpec.name}.executedChangeSets << changeSet.id
+ }
+ }
+ }
+ changeSet(author: 'John Smith', id: '2') {
+ grailsChange {
+ change {
+ ${GroovyPreconditionSpec.name}.executedChangeSets << changeSet.id
+ }
+ }
+ }
+}
+"""
+ when:
+ command.handle(getExecutionContext(DbmUpdateCommand))
+
+ then:
+ executedChangeSets == ['2']
+ }
+
+ def "changeSet precondition is not satisfied by calling the fail method"() {
+ given:
+ def command = createCommand(DbmUpdateCommand)
+ command.changeLogFile << """
+databaseChangeLog = {
+ changeSet(author: 'John Smith', id: '1') {
+ preConditions(onFail: 'CONTINUE') {
+ grailsPrecondition {
+ check {
+ fail('precondition is not satisfied')
+ }
+ }
+ }
+ grailsChange {
+ change {
+ ${GroovyPreconditionSpec.name}.executedChangeSets << changeSet.id
+ }
+ }
+ }
+ changeSet(author: 'John Smith', id: '2') {
+ grailsChange {
+ change {
+ ${GroovyPreconditionSpec.name}.executedChangeSets << changeSet.id
+ }
+ }
+ }
+}
+"""
+ when:
+ command.handle(getExecutionContext(DbmUpdateCommand))
+
+ then:
+ executedChangeSets == ['2']
+ }
+
+ def "changeSet precondition is not satisfied by throwing an exception"() {
+ given:
+ def command = createCommand(DbmUpdateCommand)
+ command.changeLogFile << """
+databaseChangeLog = {
+ changeSet(author: 'John Smith', id: '1') {
+ preConditions(onError: 'CONTINUE') {
+ grailsPrecondition {
+ check {
+ throw new RuntimeException('precondition is not satisfied')
+ }
+ }
+ }
+ grailsChange {
+ change {
+ ${GroovyPreconditionSpec.name}.executedChangeSets << changeSet.id
+ }
+ }
+ }
+ changeSet(author: 'John Smith', id: '2') {
+ grailsChange {
+ change {
+ ${GroovyPreconditionSpec.name}.executedChangeSets << changeSet.id
+ }
+ }
+ }
+}
+"""
+ when:
+ command.handle(getExecutionContext(DbmUpdateCommand))
+
+ then:
+ executedChangeSets == ['2']
+ }
+
+ def "databaseChangeLog precondition is not satisfied"() {
+ given:
+ def command = createCommand(DbmUpdateCommand)
+ command.changeLogFile << """
+databaseChangeLog = {
+ preConditions {
+ grailsPrecondition {
+ check {
+ assert false
+ }
+ }
+ }
+ changeSet(author: 'John Smith', id: '1') {
+ grailsChange {
+ change {
+ ${GroovyPreconditionSpec.name}.executedChangeSets << changeSet.id
+ }
+ }
+ }
+ changeSet(author: 'John Smith', id: '2') {
+ grailsChange {
+ change {
+ ${GroovyPreconditionSpec.name}.executedChangeSets << changeSet.id
+ }
+ }
+ }
+}
+"""
+ when:
+ command.handle(getExecutionContext(DbmUpdateCommand))
+
+ then:
+ def e = thrown(CommandExecutionException)
+ e.message.contains('1 preconditions failed')
+ executedChangeSets == []
+ }
+
+ def "checks the available variables"() {
+ given:
+ def command = createCommand(DbmUpdateCommand)
+ command.changeLogFile << """
+databaseChangeLog = {
+ changeSet(author: 'John Smith', id: '1') {
+ preConditions(onError: 'CONTINUE') {
+ grailsPrecondition {
+ check {
+ assert database
+ assert databaseConnection
+ assert connection
+ assert sql
+ assert resourceAccessor
+ assert ctx
+ assert application
+ assert changeSet
+ assert changeLog
+ }
+ }
+ }
+ grailsChange {
+ change {
+ ${GroovyPreconditionSpec.name}.executedChangeSets << changeSet.id
+ }
+ }
+ }
+ changeSet(author: 'John Smith', id: '2') {
+ grailsChange {
+ change {
+ ${GroovyPreconditionSpec.name}.executedChangeSets << changeSet.id
+ }
+ }
+ }
+}
+"""
+ when:
+ command.handle(getExecutionContext(DbmUpdateCommand))
+
+ then:
+ executedChangeSets == ['1','2']
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/testing/OutputCaptureExtension.groovy b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/testing/OutputCaptureExtension.groovy
new file mode 100644
index 00000000000..07af114b225
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/testing/OutputCaptureExtension.groovy
@@ -0,0 +1,93 @@
+package org.grails.plugins.databasemigration.testing
+
+import groovy.transform.TupleConstructor
+import org.grails.plugins.databasemigration.testing.annotation.OutputCapture
+import org.spockframework.runtime.IStandardStreamsListener
+import org.spockframework.runtime.InvalidSpecException
+import org.spockframework.runtime.StandardStreamsCapturer
+import org.spockframework.runtime.extension.IAnnotationDrivenExtension
+import org.spockframework.runtime.extension.IMethodInvocation
+import org.spockframework.runtime.model.FieldInfo
+import org.spockframework.runtime.model.SpecInfo
+
+class OutputCaptureExtension implements IAnnotationDrivenExtension {
+
+ private final Map fieldBuffers = new HashMap(1);
+
+ @Override
+ void visitFieldAnnotation(OutputCapture annotation, FieldInfo field) {
+ if (!field.type.isAssignableFrom(Object.class)) {
+ throw new InvalidSpecException("""Wrong type for field %s.
+ |@OutputCapture can only be placed on fields assignableFrom Object.
+ |For example
+ |@OutputCapture Object output
+ |""".stripMargin()).withArgs(field.name)
+ }
+ this.fieldBuffers[field] = new ByteArrayOutputStream()
+ }
+
+
+ @Override
+ void visitSpec(SpecInfo spec) {
+ def capturer = new StandardStreamsCapturer()
+ capturer.addStandardStreamsListener(new Listener(fieldBuffers))
+ capturer.start()
+ spec.addSharedInitializerInterceptor({ IMethodInvocation invocation ->
+ fieldBuffers.keySet().each { field ->
+ if (field.shared) {
+ fieldBuffers[field] = new ByteArrayOutputStream()
+ invocation.instance.metaClass.setProperty(invocation.instance, field.reflection.name, createNewOutput(fieldBuffers[field]))
+ }
+ }
+ invocation.proceed()
+ })
+ spec.addInitializerInterceptor({ IMethodInvocation invocation ->
+ fieldBuffers.keySet().each { field ->
+ if (!field.shared) {
+ fieldBuffers[field] = new ByteArrayOutputStream()
+ invocation.instance.metaClass.setProperty(invocation.instance, field.reflection.name, createNewOutput(fieldBuffers[field]))
+ }
+ }
+ invocation.proceed()
+ })
+ spec.addCleanupSpecInterceptor({ IMethodInvocation invocation ->
+ capturer.stop()
+ invocation.proceed()
+ })
+ }
+
+ private Object createNewOutput(ByteArrayOutputStream baos) {
+ new Object() {
+
+ boolean contains(CharSequence s) {
+ this.toString().contains(s)
+ }
+
+ @Override
+ String toString() {
+ return baos.toString("UTF-8")
+ }
+ }
+ }
+
+ @TupleConstructor(includeFields = true)
+ static class Listener implements IStandardStreamsListener {
+
+ private Map fieldBuffers
+
+ @Override
+ void standardOut(String message) {
+ fieldBuffers.values().each { baos ->
+ new PrintStream(baos).append(message)
+ }
+ }
+
+ @Override
+ void standardErr(String message) {
+ fieldBuffers.values().each { baos ->
+ new PrintStream(baos).append(message)
+ }
+ }
+
+ }
+}
diff --git a/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/testing/annotation/OutputCapture.groovy b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/testing/annotation/OutputCapture.groovy
new file mode 100644
index 00000000000..59720905c85
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/test/groovy/org/grails/plugins/databasemigration/testing/annotation/OutputCapture.groovy
@@ -0,0 +1,16 @@
+package org.grails.plugins.databasemigration.testing.annotation
+
+import org.grails.plugins.databasemigration.testing.OutputCaptureExtension
+import org.spockframework.runtime.extension.ExtensionAnnotation
+
+import java.lang.annotation.ElementType
+import java.lang.annotation.Retention
+import java.lang.annotation.RetentionPolicy
+import java.lang.annotation.Target
+
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.RUNTIME)
+@ExtensionAnnotation(OutputCaptureExtension)
+@interface OutputCapture {
+
+}
\ No newline at end of file
diff --git a/grails-data-hibernate5/database-migration/src/test/resources/logback.groovy b/grails-data-hibernate5/database-migration/src/test/resources/logback.groovy
new file mode 100644
index 00000000000..326e0ce1ca6
--- /dev/null
+++ b/grails-data-hibernate5/database-migration/src/test/resources/logback.groovy
@@ -0,0 +1,20 @@
+// See http://logback.qos.ch/manual/groovy.html for details on configuration
+def CONSOLE_LOG_PATTERN = '%d{HH:mm:ss.SSS} [%t] %highlight(%p) %cyan(\\(%logger{39}\\)) %m%n'
+
+appender('STDOUT', ConsoleAppender) {
+ withJansi = true
+ encoder(PatternLayoutEncoder) {
+ pattern = CONSOLE_LOG_PATTERN
+ }
+}
+root(ERROR, ['STDOUT'])
+
+//logger("org.grails", DEBUG, ['STDOUT'], false)
+logger("liquibase", DEBUG, ['STDOUT'], false)
+//logger("groovy.sql", DEBUG, ['STDOUT'], false)
+//logger("org.hibernate.SQL", DEBUG, ["STDOUT"], false)
+logger("org.grails.datastore.gorm.GormEnhancer", INFO, ['STDOUT'], false)
+logger("org.grails.plugin.datasource.TomcatJDBCPoolMBeanExporter", WARN, ['STDOUT'], false)
+
+
+
diff --git a/grails-data-hibernate5/docs/build.gradle b/grails-data-hibernate5/docs/build.gradle
new file mode 100644
index 00000000000..b39a687b898
--- /dev/null
+++ b/grails-data-hibernate5/docs/build.gradle
@@ -0,0 +1,128 @@
+import org.asciidoctor.gradle.jvm.AsciidoctorTask
+
+plugins {
+ id 'groovy'
+ id 'org.asciidoctor.jvm.convert' version "${asciidoctorVersion}"
+}
+
+version = projectVersion
+
+ext {
+ isReleaseVersion = !projectVersion.endsWith('-SNAPSHOT')
+ coreProjects = ['grails-datastore-core', 'grails-datastore-gorm']
+}
+
+configurations {
+ documentation {
+ attributes {
+ attribute(Bundling.BUNDLING_ATTRIBUTE, (Bundling) (objects.named(Bundling, 'external')))
+ }
+ }
+}
+
+dependencies {
+ documentation platform("org.grails:grails-bom:$grailsVersion")
+ documentation 'com.github.javaparser:javaparser-core'
+ documentation "info.picocli:picocli:$picocliVersion"
+ documentation 'org.apache.groovy:groovy-dateutil'
+ documentation 'org.fusesource.jansi:jansi'
+ documentation 'org.grails:grails-bootstrap'
+ documentation 'org.grails:grails-core'
+ documentation 'org.grails:grails-spring'
+ documentation "org.hibernate:hibernate-core-jakarta:$hibernateVersion"
+ coreProjects.each {
+ documentation "org.grails:$it"
+ }
+ rootProject.subprojects
+ .findAll { it.findProperty('apiDocs') }
+ .each { documentation project(":$it.name") }
+}
+
+tasks.named('asciidoctor', AsciidoctorTask) {
+ inputs.dir layout.projectDirectory.dir('src/docs')
+ outputs.dir layout.buildDirectory.dir('docs')
+ baseDirFollowsSourceDir()
+ resources {
+ from("$project.projectDir/src/docs/asciidoc/images")
+ into './images'
+ }
+
+ attributes(
+ 'experimental' : 'true',
+ 'compat-mode' : 'true',
+ 'toc' : 'left',
+ 'icons' : 'font',
+ 'reproducible' : '',
+ 'version' : projectVersion,
+ 'pluginVersion' : projectVersion,
+ 'groupId' : project.group,
+ 'artifactId' : project.name,
+ 'sourcedir' : "$project.rootDir",
+ 'migrationPluginExamplesDir' : rootProject.layout.projectDirectory.dir('grails-database-migration/src/integration-test/resources').asFile.absolutePath,
+ 'migrationPluginGroupId' : rootProject.findProject(':hibernate5-database-migration').group,
+ 'migrationPluginArtifactId' : rootProject.findProject(':hibernate5-database-migration').name,
+ 'liquibaseHibernate5Version': liquibaseHibernate5Version
+ )
+ jvm {
+ jvmArgs('--add-opens', 'java.base/sun.nio.ch=ALL-UNNAMED', '--add-opens', 'java.base/java.io=ALL-UNNAMED')
+ }
+}
+
+tasks.register('copyDocs', Copy) {
+ group = 'documentation'
+ dependsOn('asciidoctor')
+ mustRunAfter('asciidoctor', 'groovydoc', 'copyResources')
+ finalizedBy('cleanAsciidoc')
+ from(layout.buildDirectory.dir('docs/asciidoc'))
+ into(layout.buildDirectory.dir('docs/manual'))
+}
+
+tasks.register('cleanAsciidoc', Delete) {
+ group = 'documentation'
+ delete(layout.buildDirectory.dir('docs/asciidoc'))
+}
+
+tasks.withType(Groovydoc).configureEach {
+ docTitle = "GORM for Hibernate 5 - $project.version"
+
+ def sourceFiles = coreProjects.collect {
+ rootProject.layout.projectDirectory.files("$it/src/main/groovy")
+ }.sum()
+
+ rootProject.subprojects
+ .findAll { it.findProperty('apiDocs') }
+ .each { sourceFiles += it.files('src/main/groovy') }
+
+ source = sourceFiles
+ destinationDir = layout.buildDirectory.dir('docs/api').get().asFile
+ access = GroovydocAccess.PROTECTED
+ processScripts = false
+ includeMainForScripts = false
+ includeAuthor = false
+ classpath = configurations.documentation
+ groovyClasspath += configurations.documentation
+}
+
+tasks.register('copyResources', Copy) {
+ group = 'documentation'
+ from(layout.projectDirectory.dir('src/docs/resources'))
+ into(layout.buildDirectory.dir('docs'))
+}
+
+tasks.register('docs') {
+ group = 'documentation'
+ dependsOn('asciidoctor', 'copyDocs', 'cleanAsciidoc', 'groovydoc', 'copyResources',
+ rootProject.subprojects
+ .findAll { it.findProperty('apiDocs') }
+ .collect { ":${it.name}:groovydoc" }
+ )
+ finalizedBy('assembleDocsDist')
+}
+
+tasks.register('assembleDocsDist', Zip) {
+ group = 'documentation'
+ dependsOn('docs', 'copyDocs')
+ from(layout.buildDirectory.dir('docs'))
+ archiveFileName = "${project.name}-${project.version}.zip"
+ destinationDirectory = project.layout.buildDirectory.dir('distributions')
+}
\ No newline at end of file
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/advancedGORMFeatures.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/advancedGORMFeatures.adoc
new file mode 100644
index 00000000000..70291809a68
--- /dev/null
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/advancedGORMFeatures.adoc
@@ -0,0 +1 @@
+The following sections cover more advanced usages of GORM including caching, custom mapping and events.
\ No newline at end of file
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/advancedGORMFeatures/defaultSortOrder.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/advancedGORMFeatures/defaultSortOrder.adoc
new file mode 100644
index 00000000000..b8e979a4b0a
--- /dev/null
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/advancedGORMFeatures/defaultSortOrder.adoc
@@ -0,0 +1,48 @@
+You can sort objects using query arguments such as those found in the <> method:
+
+[source,java]
+----
+def airports = Airport.list(sort:'name')
+----
+
+However, you can also declare the default sort order for a collection in the mapping:
+
+[source,java]
+----
+class Airport {
+ ...
+ static mapping = {
+ sort "name"
+ }
+}
+----
+
+The above means that all collections of `Airport` instances will by default be sorted by the airport name. If you also want to change the sort _order_, use this syntax:
+
+[source,java]
+----
+class Airport {
+ ...
+ static mapping = {
+ sort name: "desc"
+ }
+}
+----
+
+Finally, you can configure sorting at the association level:
+
+[source,java]
+----
+class Airport {
+ ...
+ static hasMany = [flights: Flight]
+
+ static mapping = {
+ flights sort: 'number', order: 'desc'
+ }
+}
+----
+
+In this case, the `flights` collection will always be sorted in descending order of flight number.
+
+WARNING: These mappings will not work for default unidirectional one-to-many or many-to-many relationships because they involve a join table. See <> for more details. Consider using a `SortedSet` or queries with sort parameters to fetch the data you need.
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/advancedGORMFeatures/eventsAutoTimestamping.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/advancedGORMFeatures/eventsAutoTimestamping.adoc
new file mode 100644
index 00000000000..94cf526a068
--- /dev/null
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/advancedGORMFeatures/eventsAutoTimestamping.adoc
@@ -0,0 +1,391 @@
+GORM supports the registration of events as methods that get fired when certain events occurs such as deletes, inserts and updates. The following is a list of supported events:
+
+* `beforeInsert` - Executed before an object is initially persisted to the database. If you return false, the insert will be cancelled.
+* `beforeUpdate` - Executed before an object is updated. If you return false, the update will be cancelled.
+* `beforeDelete` - Executed before an object is deleted. If you return false, the operation delete will be cancelled.
+* `beforeValidate` - Executed before an object is validated
+* `afterInsert` - Executed after an object is persisted to the database
+* `afterUpdate` - Executed after an object has been updated
+* `afterDelete` - Executed after an object has been deleted
+* `onLoad` - Executed when an object is loaded from the database
+
+To add an event simply register the relevant method with your domain class.
+
+WARNING: Do not attempt to flush the session within an event (such as with obj.save(flush:true)). Since events are fired during flushing this will cause a StackOverflowError.
+
+
+==== The beforeInsert event
+
+
+Fired before an object is saved to the database
+
+[source,java]
+----
+class Person {
+ private static final Date NULL_DATE = new Date(0)
+
+ String firstName
+ String lastName
+ Date signupDate = NULL_DATE
+
+ def beforeInsert() {
+ if (signupDate == NULL_DATE) {
+ signupDate = new Date()
+ }
+ }
+}
+----
+
+
+==== The beforeUpdate event
+
+
+Fired before an existing object is updated
+
+[source,java]
+----
+class Person {
+
+ def securityService
+
+ String firstName
+ String lastName
+ String lastUpdatedBy
+
+ static constraints = {
+ lastUpdatedBy nullable: true
+ }
+
+ static mapping = {
+ autowire true
+ }
+
+ def beforeUpdate() {
+ lastUpdatedBy = securityService.currentAuthenticatedUsername()
+ }
+}
+----
+
+Notice the usage of `autowire true` above. This is required for the bean `securityService` to be injected.
+
+
+==== The beforeDelete event
+
+
+Fired before an object is deleted.
+
+[source,java]
+----
+class Person {
+ String name
+
+ def beforeDelete() {
+ ActivityTrace.withNewSession {
+ new ActivityTrace(eventName: "Person Deleted", data: name).save()
+ }
+ }
+}
+----
+
+Notice the usage of `withNewSession` method above. Since events are triggered whilst Hibernate is flushing using persistence methods like `save()` and `delete()` won't result in objects being saved unless you run your operations with a new `Session`.
+
+Fortunately the `withNewSession` method lets you share the same transactional JDBC connection even though you're using a different underlying `Session`.
+
+
+==== The beforeValidate event
+
+
+Fired before an object is validated.
+
+[source,java]
+----
+class Person {
+ String name
+
+ static constraints = {
+ name size: 5..45
+ }
+
+ def beforeValidate() {
+ name = name?.trim()
+ }
+}
+----
+
+The `beforeValidate` method is run before any validators are run.
+
+NOTE: Validation may run more often than you think. It is triggered by the `validate()` and `save()` methods as you'd expect, but it is also typically triggered just before the view is rendered as well. So when writing `beforeValidate()` implementations, make sure that they can handle being called multiple times with the same property values.
+
+GORM supports an overloaded version of `beforeValidate` which accepts a `List` parameter which may include
+the names of the properties which are about to be validated. This version of `beforeValidate` will be called
+when the `validate` method has been invoked and passed a `List` of property names as an argument.
+
+[source,java]
+----
+class Person {
+ String name
+ String town
+ Integer age
+
+ static constraints = {
+ name size: 5..45
+ age range: 4..99
+ }
+
+ def beforeValidate(List propertiesBeingValidated) {
+ // do pre validation work based on propertiesBeingValidated
+ }
+}
+
+def p = new Person(name: 'Jacob Brown', age: 10)
+p.validate(['age', 'name'])
+----
+
+NOTE: Note that when `validate` is triggered indirectly because of a call to the `save` method that
+the `validate` method is being invoked with no arguments, not a `List` that includes all of
+the property names.
+
+Either or both versions of `beforeValidate` may be defined in a domain class. GORM will
+prefer the `List` version if a `List` is passed to `validate` but will fall back on the
+no-arg version if the `List` version does not exist. Likewise, GORM will prefer the
+no-arg version if no arguments are passed to `validate` but will fall back on the
+`List` version if the no-arg version does not exist. In that case, `null` is passed to `beforeValidate`.
+
+
+==== The onLoad/beforeLoad event
+
+
+Fired immediately before an object is loaded from the database:
+
+[source,java]
+----
+class Person {
+ String name
+ Date dateCreated
+ Date lastUpdated
+
+ def onLoad() {
+ log.debug "Loading ${id}"
+ }
+}
+----
+
+`beforeLoad()` is effectively a synonym for `onLoad()`, so only declare one or the other.
+
+
+==== The afterLoad event
+
+
+Fired immediately after an object is loaded from the database:
+
+[source,java]
+----
+class Person {
+ String name
+ Date dateCreated
+ Date lastUpdated
+
+ def afterLoad() {
+ name = "I'm loaded"
+ }
+}
+----
+
+
+==== Custom Event Listeners
+
+To register a custom event listener you need to subclass `AbstractPersistenceEventListener` (in package _org.grails.datastore.mapping.engine.event_) and implement the methods `onPersistenceEvent` and `supportsEventType`. You also must provide a reference to the datastore to the listener. The simplest possible implementation can be seen below:
+
+[source,groovy]
+----
+public MyPersistenceListener(final Datastore datastore) {
+ super(datastore)
+}
+
+@Override
+protected void onPersistenceEvent(final AbstractPersistenceEvent event) {
+ switch(event.eventType) {
+ case PreInsert:
+ println "PRE INSERT \${event.entityObject}"
+ break
+ case PostInsert:
+ println "POST INSERT \${event.entityObject}"
+ break
+ case PreUpdate:
+ println "PRE UPDATE \${event.entityObject}"
+ break;
+ case PostUpdate:
+ println "POST UPDATE \${event.entityObject}"
+ break;
+ case PreDelete:
+ println "PRE DELETE \${event.entityObject}"
+ break;
+ case PostDelete:
+ println "POST DELETE \${event.entityObject}"
+ break;
+ case PreLoad:
+ println "PRE LOAD \${event.entityObject}"
+ break;
+ case PostLoad:
+ println "POST LOAD \${event.entityObject}"
+ break;
+ }
+}
+
+@Override
+public boolean supportsEventType(Class extends ApplicationEvent> eventType) {
+ return true
+}
+----
+
+The `AbstractPersistenceEvent` class has many subclasses (`PreInsertEvent`, `PostInsertEvent` etc.) that provide further information specific to the event. A `cancel()` method is also provided on the event which allows you to veto an insert, update or delete operation.
+
+Once you have created your event listener you need to register it. If you are using Spring this can be done via the `ApplicationContext`:
+
+[source,groovy]
+----
+HibernateDatastore datastore = applicationContext.getBean(HibernateDatastore)
+applicationContext.addApplicationListener new MyPersistenceListener(datastore)
+----
+
+If you are not using Spring then you can register the event listener using the `getApplicationEventPublisher()` method:
+
+[source,groovy]
+----
+HibernateDatastore datastore = ... // get a reference to the datastore
+datastore.getApplicationEventPublisher()
+ .addApplicationListener new MyPersistenceListener(datastore)
+----
+
+
+==== Hibernate Events
+
+
+It is generally encouraged to use the non-Hibernate specific API described above, but if you need access to more detailed Hibernate events then you can define custom Hibernate-specific event listeners.
+
+You can also register event handler classes in an application's `grails-app/conf/spring/resources.groovy` or in the `doWithSpring` closure in a plugin descriptor by registering a Spring bean named `hibernateEventListeners`. This bean has one property, `listenerMap` which specifies the listeners to register for various Hibernate events.
+
+The values of the Map are instances of classes that implement one or more Hibernate listener interfaces. You can use one class that implements all of the required interfaces, or one concrete class per interface, or any combination. The valid Map keys and corresponding interfaces are listed here:
+
+[format="csv", options="header"]
+|===
+
+*Name*,*Interface*
+auto-flush,https://docs.jboss.org/hibernate/orm/5.6/javadocs/org/hibernate/event/spi/AutoFlushEventListener.html[AutoFlushEventListener]
+merge,https://docs.jboss.org/hibernate/orm/5.6/javadocs/org/hibernate/event/spi/MergeEventListener.html[MergeEventListener]
+create,https://docs.jboss.org/hibernate/orm/5.6/javadocs/org/hibernate/event/spi/PersistEventListener.html[PersistEventListener]
+create-onflush,https://docs.jboss.org/hibernate/orm/5.6/javadocs/org/hibernate/event/spi/PersistEventListener.html[PersistEventListener]
+delete,https://docs.jboss.org/hibernate/orm/5.6/javadocs/org/hibernate/event/spi/DeleteEventListener.html[DeleteEventListener]
+dirty-check,https://docs.jboss.org/hibernate/orm/5.6/javadocs/org/hibernate/event/spi/DirtyCheckEventListener.html[DirtyCheckEventListener]
+evict,https://docs.jboss.org/hibernate/orm/5.6/javadocs/org/hibernate/event/spi/EvictEventListener.html[EvictEventListener]
+flush,https://docs.jboss.org/hibernate/orm/5.6/javadocs/org/hibernate/event/spi/FlushEventListener.html[FlushEventListener]
+flush-entity,https://docs.jboss.org/hibernate/orm/5.6/javadocs/org/hibernate/event/spi/FlushEntityEventListener.html[FlushEntityEventListener]
+load,https://docs.jboss.org/hibernate/orm/5.6/javadocs/org/hibernate/event/spi/LoadEventListener.html[LoadEventListener]
+load-collection,https://docs.jboss.org/hibernate/orm/5.6/javadocs/org/hibernate/event/spi/InitializeCollectionEventListener.html[InitializeCollectionEventListener]
+lock,https://docs.jboss.org/hibernate/orm/5.6/javadocs/org/hibernate/event/spi/LockEventListener.html[LockEventListener]
+refresh,https://docs.jboss.org/hibernate/orm/5.6/javadocs/org/hibernate/event/spi/RefreshEventListener.html[RefreshEventListener]
+replicate,https://docs.jboss.org/hibernate/orm/5.6/javadocs/org/hibernate/event/spi/ReplicateEventListener.html[ReplicateEventListener]
+save-update,https://docs.jboss.org/hibernate/orm/5.6/javadocs/org/hibernate/event/spi/SaveOrUpdateEventListener.html[SaveOrUpdateEventListener]
+save,https://docs.jboss.org/hibernate/orm/5.6/javadocs/org/hibernate/event/spi/SaveOrUpdateEventListener.html[SaveOrUpdateEventListener]
+update,https://docs.jboss.org/hibernate/orm/5.6/javadocs/org/hibernate/event/spi/SaveOrUpdateEventListener.html[SaveOrUpdateEventListener]
+pre-load,https://docs.jboss.org/hibernate/orm/5.6/javadocs/org/hibernate/event/spi/PreLoadEventListener.html[PreLoadEventListener]
+pre-update,https://docs.jboss.org/hibernate/orm/5.6/javadocs/org/hibernate/event/spi/PreUpdateEventListener.html[PreUpdateEventListener]
+pre-delete,https://docs.jboss.org/hibernate/orm/5.6/javadocs/org/hibernate/event/spi/PreDeleteEventListener.html[PreDeleteEventListener]
+pre-insert,https://docs.jboss.org/hibernate/orm/5.6/javadocs/org/hibernate/event/spi/PreInsertEventListener.html[PreInsertEventListener]
+pre-collection-recreate,https://docs.jboss.org/hibernate/orm/5.6/javadocs/org/hibernate/event/spi/PreCollectionRecreateEventListener.html[PreCollectionRecreateEventListener]
+pre-collection-remove,https://docs.jboss.org/hibernate/orm/5.6/javadocs/org/hibernate/event/spi/PreCollectionRemoveEventListener.html[PreCollectionRemoveEventListener]
+pre-collection-update,https://docs.jboss.org/hibernate/orm/5.6/javadocs/org/hibernate/event/spi/PreCollectionUpdateEventListener.html[PreCollectionUpdateEventListener]
+post-load,https://docs.jboss.org/hibernate/orm/5.6/javadocs/org/hibernate/event/spi/PostLoadEventListener.html[PostLoadEventListener]
+post-update,https://docs.jboss.org/hibernate/orm/5.6/javadocs/org/hibernate/event/spi/PostUpdateEventListener.html[PostUpdateEventListener]
+post-delete,https://docs.jboss.org/hibernate/orm/5.6/javadocs/org/hibernate/event/spi/PostDeleteEventListener.html[PostDeleteEventListener]
+post-insert,https://docs.jboss.org/hibernate/orm/5.6/javadocs/org/hibernate/event/spi/PostInsertEventListener.html[PostInsertEventListener]
+post-commit-update,https://docs.jboss.org/hibernate/orm/5.6/javadocs/org/hibernate/event/spi/PostUpdateEventListener.html[PostUpdateEventListener]
+post-commit-delete,https://docs.jboss.org/hibernate/orm/5.6/javadocs/org/hibernate/event/spi/PostDeleteEventListener.html[PostDeleteEventListener]
+post-commit-insert,https://docs.jboss.org/hibernate/orm/5.6/javadocs/org/hibernate/event/spi/PostInsertEventListener.html[PostInsertEventListener]
+post-collection-recreate,https://docs.jboss.org/hibernate/orm/5.6/javadocs/org/hibernate/event/spi/PostCollectionRecreateEventListener.html[PostCollectionRecreateEventListener]
+post-collection-remove,https://docs.jboss.org/hibernate/orm/5.6/javadocs/org/hibernate/event/spi/PostCollectionRemoveEventListener.html[PostCollectionRemoveEventListener]
+post-collection-update,https://docs.jboss.org/hibernate/orm/5.6/javadocs/org/hibernate/event/spi/PostCollectionUpdateEventListener.html[PostCollectionUpdateEventListener]
+|===
+
+For example, you could register a class `AuditEventListener` which implements `PostInsertEventListener`, `PostUpdateEventListener`, and `PostDeleteEventListener` using the following in an application:
+
+[source,groovy]
+----
+beans = {
+
+ auditListener(AuditEventListener)
+
+ hibernateEventListeners(HibernateEventListeners) {
+ listenerMap = ['post-insert': auditListener,
+ 'post-update': auditListener,
+ 'post-delete': auditListener]
+ }
+}
+----
+
+or use this in a plugin:
+
+[source,groovy]
+----
+def doWithSpring = {
+
+ auditListener(AuditEventListener)
+
+ hibernateEventListeners(HibernateEventListeners) {
+ listenerMap = ['post-insert': auditListener,
+ 'post-update': auditListener,
+ 'post-delete': auditListener]
+ }
+}
+----
+
+
+==== Automatic timestamping
+
+
+If you define a `dateCreated` property it will be set to the current date for you when you create new instances. Likewise, if you define a `lastUpdated` property it will be automatically be updated for you when you change persistent instances.
+
+If this is not the behaviour you want you can disable this feature with:
+
+[source,java]
+----
+class Person {
+ Date dateCreated
+ Date lastUpdated
+ static mapping = {
+ autoTimestamp false
+ }
+}
+----
+
+WARNING: If you have `nullable: false` constraints on either `dateCreated` or `lastUpdated`, your domain instances will fail validation - probably not what you want. Omit constraints from these properties unless you disable automatic timestamping.
+
+It is also possible to disable the automatic timestamping temporarily. This is most typically done in the case of a test where you need to define values for the `dateCreated` or `lastUpdated` in the past. It may also be useful for importing old data from other systems where you would like to keep the current values of the timestamps.
+
+Timestamps can be temporarily disabled for all domains, a specified list of domains, or a single domain. To get started, you need to get a reference to the `AutoTimestampEventListener`. If you already have access to the datastore, you can execute the `getAutoTimestampEventListener` method. If you don't have access to the datastore, inject the `autoTimestampEventListener` bean.
+
+Once you have a reference to the event listener, you can execute `withoutDateCreated`, `withoutLastUpdated`, or `withoutTimestamps`. The `withoutTimestamps` method will temporarily disable both `dateCreated` and `lastUpdated`.
+
+Example:
+
+[source,groovy]
+----
+//Only the dateCreated property handling will be disabled for only the Foo domain
+autoTimestampEventListener.withoutDateCreated(Foo) {
+ new Foo(dateCreated: new Date() - 1).save(flush: true)
+}
+
+//Only the lastUpdated property handling will be disabled for only the Foo and Bar domains
+autoTimestampEventListener.withoutLastUpdated(Foo, Bar) {
+ new Foo(lastUpdated: new Date() - 1, bar: new Bar(lastUpdated: new Date() + 1)).save(flush: true)
+}
+
+//All timestamp property handling will be disabled for all domains
+autoTimestampEventListener.withoutTimestamps {
+ new Foo(dateCreated: new Date() - 2, lastUpdated: new Date() - 1).save(flush: true)
+ new Bar(dateCreated: new Date() - 2, lastUpdated: new Date() - 1).save(flush: true)
+ new FooBar(dateCreated: new Date() - 2, lastUpdated: new Date() - 1).save(flush: true)
+}
+----
+
+WARNING: Because the timestamp handling is only disabled for the duration of the closure, you must flush the session during the closure execution!
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/advancedGORMFeatures/ormdsl.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/advancedGORMFeatures/ormdsl.adoc
new file mode 100644
index 00000000000..15e4100a131
--- /dev/null
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/advancedGORMFeatures/ormdsl.adoc
@@ -0,0 +1,28 @@
+GORM domain classes can be mapped onto many legacy schemas with an Object Relational Mapping DSL (domain specific language). The following sections takes you through what is possible with the ORM DSL.
+
+NOTE: None of this is necessary if you are happy to stick to the conventions defined by GORM for table names, column names and so on. You only needs this functionality if you need to tailor the way GORM maps onto legacy schemas or configures caching
+
+Custom mappings are defined using a static `mapping` block defined within your domain class:
+
+[source,java]
+----
+class Person {
+ ...
+ static mapping = {
+ version false
+ autoTimestamp false
+ }
+}
+----
+
+You can also configure global mappings in `application.groovy` (or an external config file) using this setting:
+
+[source,java]
+----
+grails.gorm.default.mapping = {
+ version false
+ autoTimestamp false
+}
+----
+
+It has the same syntax as the standard `mapping` block but it applies to all your domain classes! You can then override these defaults within the `mapping` block of a domain class.
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/advancedGORMFeatures/ormdsl/caching.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/advancedGORMFeatures/ormdsl/caching.adoc
new file mode 100644
index 00000000000..fefb59ee1cd
--- /dev/null
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/advancedGORMFeatures/ormdsl/caching.adoc
@@ -0,0 +1,136 @@
+===== Setting up caching
+
+
+https://www.hibernate.org/[Hibernate] features a second-level cache with a customizable cache provider. This needs to be configured in the `grails-app/conf/application.yml` file as follows:
+
+[source,groovy]
+----
+hibernate:
+ cache:
+ use_second_level_cache: true
+ provider_class: net.sf.ehcache.hibernate.EhCacheProvider
+ region:
+ factory_class: org.hibernate.cache.ehcache.EhCacheRegionFactory
+----
+
+You can customize any of these settings, for example to use a distributed caching mechanism.
+
+NOTE: For further reading on caching and in particular Hibernate's second-level cache, refer to the https://docs.jboss.org/hibernate/orm/current/userguide/html_single/Hibernate_User_Guide.html#caching[Hibernate documentation] on the subject.
+
+
+===== Caching instances
+
+
+Call the `cache` method in your mapping block to enable caching with the default settings:
+
+[source,java]
+----
+class Person {
+ ...
+ static mapping = {
+ table 'people'
+ cache true
+ }
+}
+----
+
+This will configure a 'read-write' cache that includes both lazy and non-lazy properties. You can customize this further:
+
+[source,java]
+----
+class Person {
+ ...
+ static mapping = {
+ table 'people'
+ cache usage: 'read-only', include: 'non-lazy'
+ }
+}
+----
+
+
+===== Caching associations
+
+
+As well as the ability to use Hibernate's second level cache to cache instances you can also cache collections (associations) of objects. For example:
+
+[source,java]
+----
+class Person {
+
+ String firstName
+
+ static hasMany = [addresses: Address]
+
+ static mapping = {
+ table 'people'
+ version false
+ addresses column: 'Address', cache: true
+ }
+}
+----
+
+[source,java]
+----
+class Address {
+ String number
+ String postCode
+}
+----
+
+This will enable a 'read-write' caching mechanism on the `addresses` collection. You can also use:
+
+[source,java]
+----
+cache: 'read-write' // or 'read-only' or 'transactional'
+----
+
+to further configure the cache usage.
+
+
+===== Caching Queries
+
+In order for the results of queries to be cached, you must enable caching in your mapping:
+
+[source,groovy]
+----
+hibernate:
+ cache:
+ use_query_cache: true
+----
+
+To enable query caching for all queries created by dynamic finders, GORM etc. you can specify:
+
+[source,groovy]
+----
+hibernate:
+ cache:
+ queries: true # This implicitly sets `use_query_cache=true`
+----
+
+You can cache queries such as dynamic finders and criteria. To do so using a dynamic finder you can pass the `cache` argument:
+
+[source,java]
+----
+def person = Person.findByFirstName("Fred", [cache: true])
+----
+
+You can also cache criteria queries:
+
+[source,java]
+----
+def people = Person.withCriteria {
+ like('firstName', 'Fr%')
+ cache true
+}
+----
+
+
+===== Cache usages
+
+
+Below is a description of the different cache settings and their usages:
+
+* `read-only` - If your application needs to read but never modify instances of a persistent class, a read-only cache may be used.
+* `read-write` - If the application needs to update data, a read-write cache might be appropriate.
+* `nonstrict-read-write` - If the application only occasionally needs to update data (i.e. if it is very unlikely that two transactions would try to update the same item simultaneously) and strict transaction isolation is not required, a `nonstrict-read-write` cache might be appropriate.
+* `transactional` - The `transactional` cache strategy provides support for fully transactional cache providers such as JBoss TreeCache. Such a cache may only be used in a JTA environment and you must specify `hibernate.transaction.manager_lookup_class` in the `grails-app/conf/application.groovy` file's `hibernate` config.
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/advancedGORMFeatures/ormdsl/compositePrimaryKeys.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/advancedGORMFeatures/ormdsl/compositePrimaryKeys.adoc
new file mode 100644
index 00000000000..0dcd1ebfcb1
--- /dev/null
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/advancedGORMFeatures/ormdsl/compositePrimaryKeys.adoc
@@ -0,0 +1,69 @@
+GORM supports the concept of composite identifiers (identifiers composed from 2 or more properties). It is not an approach we recommend, but is available to you if you need it:
+
+[source,groovy]
+----
+import org.apache.commons.lang.builder.HashCodeBuilder
+
+class Person implements Serializable {
+
+ String firstName
+ String lastName
+
+ boolean equals(other) {
+ if (!(other instanceof Person)) {
+ return false
+ }
+
+ other.firstName == firstName && other.lastName == lastName
+ }
+
+ int hashCode() {
+ def builder = new HashCodeBuilder()
+ builder.append firstName
+ builder.append lastName
+ builder.toHashCode()
+ }
+
+ static mapping = {
+ id composite: ['firstName', 'lastName']
+ }
+}
+----
+
+The above will create a composite id of the `firstName` and `lastName` properties of the Person class. To retrieve an instance by id you use a prototype of the object itself:
+
+[source,java]
+----
+def p = Person.get(new Person(firstName: "Fred", lastName: "Flintstone"))
+println p.firstName
+----
+
+Domain classes mapped with composite primary keys must implement the `Serializable` interface and override the `equals` and `hashCode` methods, using the properties in the composite key for the calculations. The example above uses a `HashCodeBuilder` for convenience but it's fine to implement it yourself.
+
+Another important consideration when using composite primary keys is associations. If for example you have a many-to-one association where the foreign keys are stored in the associated table then 2 columns will be present in the associated table.
+
+For example consider the following domain class:
+
+[source,groovy]
+----
+class Address {
+ Person person
+}
+----
+
+In this case the `address` table will have an additional two columns called `person_first_name` and `person_last_name`. If you wish the change the mapping of these columns then you can do so using the following technique:
+
+[source,groovy]
+----
+class Address {
+ Person person
+ static mapping = {
+ columns {
+ person {
+ column name: "FirstName"
+ column name: "LastName"
+ }
+ }
+ }
+}
+----
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/advancedGORMFeatures/ormdsl/customCascadeBehaviour.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/advancedGORMFeatures/ormdsl/customCascadeBehaviour.adoc
new file mode 100644
index 00000000000..98a674dbadb
--- /dev/null
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/advancedGORMFeatures/ormdsl/customCascadeBehaviour.adoc
@@ -0,0 +1,39 @@
+As described in the section on <>, the primary mechanism to control the way updates and deletes cascade from one association to another is the static <> property.
+
+However, the ORM DSL gives you complete access to Hibernate's https://docs.jboss.org/hibernate/orm/current/userguide/html_single/Hibernate_User_Guide.html#associations[transitive persistence] capabilities using the `cascade` attribute.
+
+Valid settings for the cascade attribute include:
+
+* `merge` - merges the state of a detached association
+* `save-update` - cascades only saves and updates to an association
+* `delete` - cascades only deletes to an association
+* `lock` - useful if a pessimistic lock should be cascaded to its associations
+* `refresh` - cascades refreshes to an association
+* `evict` - cascades evictions (equivalent to `discard()` in GORM) to associations if set
+* `all` - cascade _all_ operations to associations
+* `all-delete-orphan` - Applies only to one-to-many associations and indicates that when a child is removed from an association then it should be automatically deleted. Children are also deleted when the parent is.
+
+
+To specify the cascade attribute simply define one or more (comma-separated) of the aforementioned settings as its value:
+
+[source,java]
+----
+class Person {
+
+ String firstName
+
+ static hasMany = [addresses: Address]
+
+ static mapping = {
+ addresses cascade: "all-delete-orphan"
+ }
+}
+----
+
+[source,java]
+----
+class Address {
+ String street
+ String postCode
+}
+----
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/advancedGORMFeatures/ormdsl/customHibernateTypes.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/advancedGORMFeatures/ormdsl/customHibernateTypes.adoc
new file mode 100644
index 00000000000..eaaf7e82143
--- /dev/null
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/advancedGORMFeatures/ormdsl/customHibernateTypes.adoc
@@ -0,0 +1,70 @@
+You saw in an earlier section that you can use composition (with the `embedded` property) to break a table into multiple objects. You can achieve a similar effect with Hibernate's custom user types. These are not domain classes themselves, but plain Java or Groovy classes. Each of these types also has a corresponding "meta-type" class that implements https://docs.jboss.org/hibernate/orm/5.6/javadocs/org/hibernate/usertype/UserType.html[org.hibernate.usertype.UserType].
+
+The https://docs.jboss.org/hibernate/orm/current/userguide/html_single/Hibernate_User_Guide.html#_custom_type[Hibernate reference manual] has some information on custom types, but here we will focus on how to map them in GORM. Let's start by taking a look at a simple domain class that uses an old-fashioned (pre-Java 1.5) type-safe enum class:
+
+[source,groovy]
+----
+class Book {
+
+ String title
+ String author
+ Rating rating
+
+ static mapping = {
+ rating type: RatingUserType
+ }
+}
+----
+
+All we have done is declare the `rating` field the enum type and set the property's type in the custom mapping to the corresponding `UserType` implementation. That's all you have to do to start using your custom type. If you want, you can also use the other column settings such as "column" to change the column name and "index" to add it to an index.
+
+Custom types aren't limited to just a single column - they can be mapped to as many columns as you want. In such cases you explicitly define in the mapping what columns to use, since Hibernate can only use the property name for a single column. Fortunately, GORM lets you map multiple columns to a property using this syntax:
+
+[source,java]
+----
+class Book {
+
+ String title
+ Name author
+ Rating rating
+
+ static mapping = {
+ author type: NameUserType, {
+ column name: "first_name"
+ column name: "last_name"
+ }
+ rating type: RatingUserType
+ }
+}
+----
+
+The above example will create "first_name" and "last_name" columns for the `author` property. You'll be pleased to know that you can also use some of the normal column/property mapping attributes in the column definitions. For example:
+
+[source,java]
+----
+column name: "first_name", index: "my_idx", unique: true
+----
+
+The column definitions do _not_ support the following attributes: `type`, `cascade`, `lazy`, `cache`, and `joinTable`.
+
+One thing to bear in mind with custom types is that they define the _SQL types_ for the corresponding database columns. That helps take the burden of configuring them yourself, but what happens if you have a legacy database that uses a different SQL type for one of the columns? In that case, override the column's SQL type using the `sqlType` attribute:
+
+[source,java]
+----
+class Book {
+
+ String title
+ Name author
+ Rating rating
+
+ static mapping = {
+ author type: NameUserType, {
+ column name: "first_name", sqlType: "text"
+ column name: "last_name", sqlType: "text"
+ }
+ rating type: RatingUserType, sqlType: "text"
+ }
+}
+----
+
+Mind you, the SQL type you specify needs to still work with the custom type. So overriding a default of "varchar" with "text" is fine, but overriding "text" with "yes_no" isn't going to work.
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/advancedGORMFeatures/ormdsl/customNamingStrategy.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/advancedGORMFeatures/ormdsl/customNamingStrategy.adoc
new file mode 100644
index 00000000000..69c67687e52
--- /dev/null
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/advancedGORMFeatures/ormdsl/customNamingStrategy.adoc
@@ -0,0 +1,62 @@
+By default GORM uses Hibernate's `ImprovedNamingStrategy` to convert domain class Class and field names to SQL table and column names by converting from camel-cased Strings to ones that use underscores as word separators. You can customize these on a per-class basis in the `mapping` closure but if there's a consistent pattern you can specify a different `NamingStrategy` class to use.
+
+Configure the class name to be used in `grails-app/conf/application.groovy` in the `hibernate` section, e.g.
+
+[source,java]
+----
+dataSource {
+ pooled = true
+ dbCreate = "create-drop"
+ ...
+}
+
+hibernate {
+ cache.use_second_level_cache = true
+ ...
+ naming_strategy = com.myco.myproj.CustomNamingStrategy
+}
+----
+
+You can also specify the name of the class and it will be loaded for you:
+
+[source,java]
+----
+hibernate {
+ ...
+ naming_strategy = 'com.myco.myproj.CustomNamingStrategy'
+}
+----
+
+A third option is to provide an instance if there is some configuration required beyond calling the default constructor:
+
+[source,java]
+----
+hibernate {
+ ...
+ def strategy = new com.myco.myproj.CustomNamingStrategy()
+ // configure as needed
+ naming_strategy = strategy
+}
+----
+
+You can use an existing class or write your own, for example one that prefixes table names and column names:
+
+[source,java]
+----
+package com.myco.myproj
+
+import org.hibernate.cfg.ImprovedNamingStrategy
+import org.hibernate.util.StringHelper
+
+class CustomNamingStrategy extends ImprovedNamingStrategy {
+
+ String classToTableName(String className) {
+ "table_" + StringHelper.unqualify(className)
+ }
+
+ String propertyToColumnName(String propertyName) {
+ "col_" + StringHelper.unqualify(propertyName)
+ }
+}
+----
+
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/advancedGORMFeatures/ormdsl/databaseIndices.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/advancedGORMFeatures/ormdsl/databaseIndices.adoc
new file mode 100644
index 00000000000..49e9ae7623e
--- /dev/null
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/advancedGORMFeatures/ormdsl/databaseIndices.adoc
@@ -0,0 +1,18 @@
+To get the best performance out of your queries it is often necessary to tailor the table index definitions. How you tailor them is domain specific and a matter of monitoring usage patterns of your queries. With GORM's DSL you can specify which columns are used in which indexes:
+
+[source,java]
+----
+class Person {
+ String firstName
+ String address
+ static mapping = {
+ table 'people'
+ version false
+ id column: 'person_id'
+ firstName column: 'First_Name', index: 'Name_Idx'
+ address column: 'Address', index: 'Name_Idx,Address_Index'
+ }
+}
+----
+
+Note that you cannot have any spaces in the value of the `index` attribute; in this example `index:'Name_Idx, Address_Index'` will cause an error.
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/advancedGORMFeatures/ormdsl/derivedProperties.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/advancedGORMFeatures/ormdsl/derivedProperties.adoc
new file mode 100644
index 00000000000..e298dc19c87
--- /dev/null
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/advancedGORMFeatures/ormdsl/derivedProperties.adoc
@@ -0,0 +1,77 @@
+A derived property is one that takes its value from a SQL expression, often but not necessarily based on the value of one or more other persistent properties. Consider a Product class like this:
+
+[source,java]
+----
+class Product {
+ Float price
+ Float taxRate
+ Float tax
+}
+----
+
+If the `tax` property is derived based on the value of `price` and `taxRate` properties then is probably no need to persist the `tax` property. The SQL used to derive the value of a derived property may be expressed in the ORM DSL like this:
+
+[source,java]
+----
+class Product {
+ Float price
+ Float taxRate
+ Float tax
+
+ static mapping = {
+ tax formula: 'PRICE * TAX_RATE'
+ }
+}
+----
+
+Note that the formula expressed in the ORM DSL is SQL so references to other properties should relate to the persistence model not the object model, which is why the example refers to `PRICE` and `TAX_RATE` instead of `price` and `taxRate`.
+
+With that in place, when a Product is retrieved with something like `Product.get(42)`, the SQL that is generated to support that will look something like this:
+
+[source,groovy]
+----
+select
+ product0_.id as id1_0_,
+ product0_.version as version1_0_,
+ product0_.price as price1_0_,
+ product0_.tax_rate as tax4_1_0_,
+ product0_.PRICE * product0_.TAX_RATE as formula1_0_
+from
+ product product0_
+where
+ product0_.id=?
+----
+
+Since the `tax` property is derived at runtime and not stored in the database it might seem that the same effect could be achieved by adding a method like `getTax()` to the `Product` class that simply returns the product of the `taxRate` and `price` properties. With an approach like that you would give up the ability query the database based on the value of the `tax` property. Using a derived property allows exactly that. To retrieve all `Product` objects that have a `tax` value greater than 21.12 you could execute a query like this:
+
+[source,java]
+----
+Product.findAllByTaxGreaterThan(21.12)
+----
+
+Derived properties may be referenced in the Criteria API:
+
+[source,java]
+----
+Product.withCriteria {
+ gt 'tax', 21.12f
+}
+----
+
+The SQL that is generated to support either of those would look something like this:
+
+[source,groovy]
+----
+select
+ this_.id as id1_0_,
+ this_.version as version1_0_,
+ this_.price as price1_0_,
+ this_.tax_rate as tax4_1_0_,
+ this_.PRICE * this_.TAX_RATE as formula1_0_
+from
+ product this_
+where
+ this_.PRICE * this_.TAX_RATE>?
+----
+
+NOTE: Because the value of a derived property is generated in the database and depends on the execution of SQL code, derived properties may not have GORM constraints applied to them. If constraints are specified for a derived property, they will be ignored.
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/advancedGORMFeatures/ormdsl/fetchingDSL.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/advancedGORMFeatures/ormdsl/fetchingDSL.adoc
new file mode 100644
index 00000000000..4f1c2059a52
--- /dev/null
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/advancedGORMFeatures/ormdsl/fetchingDSL.adoc
@@ -0,0 +1,176 @@
+
+===== Lazy Collections
+
+
+As discussed in the section on <>, GORM collections are lazily loaded by default but you can change this behaviour with the ORM DSL. There are several options available to you, but the most common ones are:
+
+* lazy: false
+* fetch: 'join'
+
+and they're used like this:
+
+[source,java]
+----
+class Person {
+
+ String firstName
+ Pet pet
+
+ static hasMany = [addresses: Address]
+
+ static mapping = {
+ addresses lazy: false
+ pet fetch: 'join'
+ }
+}
+----
+
+[source,java]
+----
+class Address {
+ String street
+ String postCode
+}
+----
+
+[source,java]
+----
+class Pet {
+ String name
+}
+----
+
+The first option, `lazy: false` , ensures that when a `Person` instance is loaded, its `addresses` collection is loaded at the same time with a second SELECT. The second option is basically the same, except the collection is loaded with a JOIN rather than another SELECT. Typically you want to reduce the number of queries, so `fetch: 'join'` is the more appropriate option. On the other hand, it could feasibly be the more expensive approach if your domain model and data result in more and larger results than would otherwise be necessary.
+
+For more advanced users, the other settings available are:
+
+* `batchSize: N`
+* `lazy: false, batchSize: N`
+
+where N is an integer. These let you fetch results in batches, with one query per batch. As a simple example, consider this mapping for `Person`:
+
+[source,groovy]
+----
+class Person {
+
+ String firstName
+ Pet pet
+
+ static mapping = {
+ pet batchSize: 5
+ }
+}
+----
+If a query returns multiple `Person` instances, then when we access the first `pet` property, Hibernate will fetch that `Pet` plus the four next ones. You can get the same behaviour with eager loading by combining `batchSize` with the `lazy: false` option.
+
+You can find out more about these options in the https://docs.jboss.org/hibernate/orm/current/userguide/html_single/Hibernate_User_Guide.html#fetching[Hibernate user guide]. Note that ORM DSL does not currently support the "subselect" fetching strategy.
+
+
+===== Lazy Single-Ended Associations
+
+
+In GORM, one-to-one and many-to-one associations are by default lazy. Non-lazy single ended associations can be problematic when you load many entities because each non-lazy association will result in an extra SELECT statement. If the associated entities also have non-lazy associations, the number of queries grows significantly!
+
+Use the same technique as for lazy collections to make a one-to-one or many-to-one association non-lazy/eager:
+
+[source,java]
+----
+class Person {
+ String firstName
+}
+----
+
+[source,java]
+----
+class Address {
+
+ String street
+ String postCode
+
+ static belongsTo = [person: Person]
+
+ static mapping = {
+ person lazy: false
+ }
+}
+----
+
+Here we configure GORM to load the associated `Person` instance (through the `person` property) whenever an `Address` is loaded.
+
+
+===== Lazy Associations and Proxies
+
+
+Hibernate uses runtime-generated proxies to facilitate single-ended lazy associations; Hibernate dynamically subclasses the entity class to create the proxy.
+
+Consider the previous example but with a lazily-loaded `person` association: Hibernate will set the `person` property to a proxy that is a subclass of `Person`. When you call any of the getters (except for the `id` property) or setters on that proxy, Hibernate will load the entity from the database.
+
+Unfortunately this technique can produce surprising results. Consider the following example classes:
+
+[source,java]
+----
+class Pet {
+ String name
+}
+----
+
+[source,java]
+----
+class Dog extends Pet {
+}
+----
+
+[source,java]
+----
+class Person {
+ String name
+ Pet pet
+}
+----
+
+Proxies can have confusing behavior when combined with inheritance. Because the proxy is only a subclass of the parent class, any attempt to cast or access data on the subclass will fail. Assuming we have a single `Person` instance with a `Dog` as the `pet`.
+
+The code below will not fail because directly querying the `Pet` table does not require the resulting objects to be proxies because they are not lazy.
+
+[source,groovy]
+----
+def pet = Pet.get(1)
+assert pet instanceof Dog
+----
+
+The following code will fail because the association is lazy and the `pet` instance is a proxy.
+
+[source,groovy]
+----
+def person = Person.get(1)
+assert person.pet instanceof Dog
+----
+
+If the only goal is to check if the proxy is an instance of a class, there is one helper method available to do so that works with proxies. Take special care in using it though because it does cause a call to the database to retrieve the association data.
+
+[source,groovy]
+----
+def person = Person.get(1)
+assert person.pet.instanceOf(Dog)
+----
+
+There are a couple of ways to approach this issue. The first rule of thumb is that if it is known ahead of time that the association data is required, join the data in the query of the `Person`. For example, the following assertion is true.
+
+[source,groovy]
+----
+def person = Person.where { id == 1 }.join("pet").get()
+assert person.pet instanceof Dog
+----
+
+In the above example the `pet` association is no longer lazy because it is being retrieved along with the `Person` and thus no proxies are necessary. There are cases when it makes sense for a proxy to be returned, mostly in the case where its impossible to know if the data will be used or not. For those cases in order to access properties of the subclasses, the proxy must be unwrapped. To unwrap a proxy inject an instance of link:../api/org/grails/datastore/mapping/proxy/ProxyHandler.html[ProxyHandler] and pass the proxy to the `unwrap` method.
+
+[source,groovy]
+----
+def person = Person.get(1)
+assert proxyHandler.unwrap(person.pet) instanceof Dog
+----
+
+For cases where dependency injection is impractical or not available, a helper method link:../api/org/grails/orm/hibernate/cfg/GrailsHibernateUtil.html#unwrapIfProxy(java.lang.Object)[GrailsHibernateUtil.unwrapIfProxy(Object)] can be used instead.
+
+Unwrapping a proxy is different than initializing it. Initializing a proxy simply populates the underlying instance with data from the database, however unwrapping a returns the inner target.
+
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/advancedGORMFeatures/ormdsl/identity.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/advancedGORMFeatures/ormdsl/identity.adoc
new file mode 100644
index 00000000000..be43f13d2cd
--- /dev/null
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/advancedGORMFeatures/ormdsl/identity.adoc
@@ -0,0 +1,36 @@
+You can customize how GORM generates identifiers for the database using the DSL. By default GORM relies on the native database mechanism for generating ids. This is by far the best approach, but there are still many schemas that have different approaches to identity.
+
+To deal with this Hibernate defines the concept of an id generator. You can customize the id generator and the column it maps to as follows:
+
+[source,java]
+----
+class Person {
+ ...
+ static mapping = {
+ table 'people'
+ version false
+ id generator: 'hilo',
+ params: [table: 'hi_value',
+ column: 'next_value',
+ max_lo: 100]
+ }
+}
+----
+
+In this case we're using one of Hibernate's built in 'hilo' generators that uses a separate table to generate ids.
+
+NOTE: For more information on the different Hibernate generators refer to the https://docs.jboss.org/hibernate/orm/current/userguide/html_single/Hibernate_User_Guide.html#identifiers-generators[Hibernate reference documentation]
+
+Although you don't typically specify the `id` field (GORM adds it for you) you can still configure its mapping like the other properties. For example to customise the column for the id property you can do:
+
+[source,java]
+----
+class Person {
+ ...
+ static mapping = {
+ table 'people'
+ version false
+ id column: 'person_id'
+ }
+}
+----
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/advancedGORMFeatures/ormdsl/inheritanceStrategies.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/advancedGORMFeatures/ormdsl/inheritanceStrategies.adoc
new file mode 100644
index 00000000000..4882e623de8
--- /dev/null
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/advancedGORMFeatures/ormdsl/inheritanceStrategies.adoc
@@ -0,0 +1,18 @@
+By default GORM classes use `table-per-hierarchy` inheritance mapping. This has the disadvantage that columns cannot have a `NOT-NULL` constraint applied to them at the database level. If you would prefer to use a `table-per-subclass` inheritance strategy you can do so as follows:
+
+[source,java]
+----
+class Payment {
+ Integer amount
+
+ static mapping = {
+ tablePerHierarchy false
+ }
+}
+
+class CreditCardPayment extends Payment {
+ String cardNumber
+}
+----
+
+The mapping of the root `Payment` class specifies that it will not be using `table-per-hierarchy` mapping for all child classes.
\ No newline at end of file
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/advancedGORMFeatures/ormdsl/optimisticLockingAndVersioning.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/advancedGORMFeatures/ormdsl/optimisticLockingAndVersioning.adoc
new file mode 100644
index 00000000000..b8797c72c0b
--- /dev/null
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/advancedGORMFeatures/ormdsl/optimisticLockingAndVersioning.adoc
@@ -0,0 +1,39 @@
+As discussed in the section on <>, by default GORM uses optimistic locking and automatically injects a `version` property into every class which is in turn mapped to a `version` column at the database level.
+
+If you're mapping to a legacy schema that doesn't have version columns (or there's some other reason why you don't want/need this feature) you can disable this with the `version` method:
+
+[source,java]
+----
+class Person {
+ ...
+ static mapping = {
+ table 'people'
+ version false
+ }
+}
+----
+
+NOTE: If you disable optimistic locking you are essentially on your own with regards to concurrent updates and are open to the risk of users losing data (due to data overriding) unless you use <>
+
+
+===== Version columns types
+
+
+By default GORM maps the `version` property as a `Long` that gets incremented by one each time an instance is updated. But Hibernate also supports using a `Timestamp`, for example:
+
+[source,java]
+----
+import java.sql.Timestamp
+
+class Person {
+
+ ...
+ Timestamp version
+
+ static mapping = {
+ table 'people'
+ }
+}
+----
+
+There's a slight risk that two updates occurring at nearly the same time on a fast server can end up with the same timestamp value but this risk is very low. One benefit of using a `Timestamp` instead of a `Long` is that you combine the optimistic locking and last-updated semantics into a single column.
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/advancedGORMFeatures/ormdsl/tableAndColumnNames.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/advancedGORMFeatures/ormdsl/tableAndColumnNames.adoc
new file mode 100644
index 00000000000..9628de8a606
--- /dev/null
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/advancedGORMFeatures/ormdsl/tableAndColumnNames.adoc
@@ -0,0 +1,200 @@
+
+===== Table names
+
+
+The database table name which the class maps to can be customized using the `table` method:
+
+[source,java]
+----
+class Person {
+ ...
+ static mapping = {
+ table 'people'
+ }
+}
+----
+
+In this case the class would be mapped to a table called `people` instead of the default name of `person`.
+
+
+===== Column names
+
+
+It is also possible to customize the mapping for individual columns onto the database. For example to change the name you can do:
+
+[source,java]
+----
+class Person {
+
+ String firstName
+
+ static mapping = {
+ table 'people'
+ firstName column: 'First_Name'
+ }
+}
+----
+
+Here `firstName` is a dynamic method within the `mapping` Closure that has a single Map parameter. Since its name corresponds to a domain class persistent field, the parameter values (in this case just `"column"`) are used to configure the mapping for that property.
+
+
+===== Column type
+
+
+GORM supports configuration of Hibernate types with the DSL using the type attribute. This includes specifying user types that implement the Hibernate https://docs.jboss.org/hibernate/orm/5.6/javadocs/org/hibernate/usertype/UserType.html[org.hibernate.usertype.UserType] interface, which allows complete customization of how a type is persisted. As an example if you had a `PostCodeType` you could use it as follows:
+
+[source,java]
+----
+class Address {
+
+ String number
+ String postCode
+
+ static mapping = {
+ postCode type: PostCodeType
+ }
+}
+----
+
+Alternatively if you just wanted to map it to one of Hibernate's basic types other than the default chosen by GORM you could use:
+
+[source,java]
+----
+class Address {
+
+ String number
+ String postCode
+
+ static mapping = {
+ postCode type: 'text'
+ }
+}
+----
+
+This would make the `postCode` column map to the default large-text type for the database you're using (for example TEXT or CLOB).
+
+See the Hibernate documentation regarding https://docs.jboss.org/hibernate/orm/current/userguide/html_single/Hibernate_User_Guide.html#basic-explicit[Basic Types] for further information.
+
+
+===== Many-to-One/One-to-One Mappings
+
+
+In the case of associations it is also possible to configure the foreign keys used to map associations. In the case of a many-to-one or one-to-one association this is exactly the same as any regular column. For example consider the following:
+
+[source,java]
+----
+class Person {
+
+ String firstName
+ Address address
+
+ static mapping = {
+ table 'people'
+ firstName column: 'First_Name'
+ address column: 'Person_Address_Id'
+ }
+}
+----
+
+By default the `address` association would map to a foreign key column called `address_id`. By using the above mapping we have changed the name of the foreign key column to `Person_Adress_Id`.
+
+===== One-to-Many Mapping
+
+With a bidirectional one-to-many you can change the foreign key column used by changing the column name on the many side of the association as per the example in the previous section on one-to-one associations. However, with unidirectional associations the foreign key needs to be specified on the association itself. For example given a unidirectional one-to-many relationship between `Person` and `Address` the following code will change the foreign key in the `address` table:
+
+[source,java]
+----
+class Person {
+
+ String firstName
+
+ static hasMany = [addresses: Address]
+
+ static mapping = {
+ table 'people'
+ firstName column: 'First_Name'
+ addresses column: 'Person_Address_Id'
+ }
+}
+----
+
+If you don't want the column to be in the `address` table, but instead some intermediate join table you can use the `joinTable` parameter:
+
+[source,java]
+----
+class Person {
+
+ String firstName
+
+ static hasMany = [addresses: Address]
+
+ static mapping = {
+ table 'people'
+ firstName column: 'First_Name'
+ addresses joinTable: [name: 'Person_Addresses',
+ key: 'Person_Id',
+ column: 'Address_Id']
+ }
+}
+----
+
+
+===== Many-to-Many Mapping
+
+
+GORM, by default maps a many-to-many association using a join table. For example consider this many-to-many association:
+
+[source,java]
+----
+class Group {
+ ...
+ static hasMany = [people: Person]
+}
+----
+
+[source,java]
+----
+class Person {
+ ...
+ static belongsTo = Group
+ static hasMany = [groups: Group]
+}
+----
+
+In this case GORM will create a join table called `group_person` containing foreign keys called `person_id` and `group_id` referencing the `person` and `group` tables. To change the column names you can specify a column within the mappings for each class.
+
+[source,java]
+----
+class Group {
+ ...
+ static mapping = {
+ people column: 'Group_Person_Id'
+ }
+}
+class Person {
+ ...
+ static mapping = {
+ groups column: 'Group_Group_Id'
+ }
+}
+----
+
+You can also specify the name of the join table to use:
+
+[source,java]
+----
+class Group {
+ ...
+ static mapping = {
+ people column: 'Group_Person_Id',
+ joinTable: 'PERSON_GROUP_ASSOCIATIONS'
+ }
+}
+class Person {
+ ...
+ static mapping = {
+ groups column: 'Group_Group_Id',
+ joinTable: 'PERSON_GROUP_ASSOCIATIONS'
+ }
+}
+----
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/configuration/configurationDefaults.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/configuration/configurationDefaults.adoc
new file mode 100644
index 00000000000..614e4b4a0a8
--- /dev/null
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/configuration/configurationDefaults.adoc
@@ -0,0 +1,40 @@
+The `grails.gorm.default.mapping` and `grails.gorm.default.constraints` settings deserve special mention. These define the default <> and the default <> used by each entity.
+
+==== Altering the Default Database Mapping
+
+You may have reason to want to change how all domain classes map to the database. For example, by default GORM uses the `native` id generation strategy of the database, whether that be an auto-increment column or a sequence.
+
+If you wish to globally change all domain classes to use a `uuid` strategy then you can specify that in the default mapping:
+
+[source,groovy]
+.grails-app/conf/application.groovy
+----
+grails.gorm.default.mapping = {
+ cache true
+ id generator:'uuid'
+}
+----
+
+As you can see you can assign a closure that is equivalent to the `mapping` block used to <>.
+
+NOTE: Because the setting is Groovy configuration it must go into a Groovy-aware configuration format. This can be `grails-app/conf/application.groovy` in Grails, or `src/main/resources/application.groovy` in Spring Boot.
+
+==== Altering the Default Constraints
+
+For validation, GORM applies a default set of <> to all domain classes.
+
+For example, by default all properties of GORM classes are not nullable by default. This means a value has to be supplied for each property, otherwise you will get a validation error.
+
+In most cases this is what you want, but if you are dealing with a large number of columns, it may prove inconvinient.
+
+You can alter the default constraints using Groovy configuration using the `grails.gorm.default.constraints` setting:
+
+[source,groovy]
+.grails-app/conf/application.groovy
+----
+grails.gorm.default.constraints = {
+ '*'(nullable: true, size: 1..20)
+}
+----
+
+In the above example, all properties are allowed to be `nullable` by default, but limited to a size of between 1 and 20.
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/configuration/configurationReference.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/configuration/configurationReference.adoc
new file mode 100644
index 00000000000..d3724ac7aa1
--- /dev/null
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/configuration/configurationReference.adoc
@@ -0,0 +1,48 @@
+You can refer to the link:../api/org/grails/orm/hibernate/connections/HibernateConnectionSourceSettings.html[HibernateConnectionSourceSettings] class for all available configuration options, but below is a table of the common ones:
+
+[format="csv", options="header"]
+|===
+name,description,default value
+`grails.gorm.flushMode`, The flush mode to use, `COMMIT`
+`grails.gorm.failOnError`, Whether to throw an exception on validation error, `false`
+`grails.gorm.default.mapping`,The default mapping to apply to all classes, `null`
+`grails.gorm.default.constraints`,The default constraints to apply to all classes, `null`
+`grails.gorm.multiTenancy.mode`,The multi tenancy mode, `NONE`
+|===
+
+The following are common configuration options for the SQL connection:
+
+[format="csv", options="header"]
+|===
+name,description,default value
+`dataSource.url`, The JDBC url, `jdbc:h2:mem:grailsDB`
+`dataSource.driverClassName`, The class of the JDBC driver, detected from URL
+`dataSource.username`, The JDBC username, `null`
+`dataSource.password`, The JDBC password, `null`
+`dataSource.jndiName`, The name of the JNDI resource for the `DataSource`, `null`
+`dataSource.pooled`, Whether the connection is pooled, `true`
+`dataSource.lazy`, Whether a `LazyConnectionDataSourceProxy` should be used, `true`
+`dataSource.transactionAware`, Whether a `TransactionAwareDataSourceProxy` should be used, `true`
+`dataSource.readOnly`, Whether the DataSource is read-only, `false`
+`dataSource.options`, A map of options to pass to the underlying JDBC driver, `null`
+|===
+
+And the following are common configuration options for Hibernate:
+
+[format="csv", options="header"]
+|===
+name,description,default value
+`hibernate.dialect`, The hibernate dialect to use, detected automatically from DataSource
+`hibernate.readOnly`, Whether Hibernate should be read-only, `false`
+`hibernate.configClass`, The configuration class to use, `HibernateMappingContextConfiguration`
+`hibernate.hbm2ddl.auto`, Whether to create the tables on startup, `none`
+`hibernate.use_second_level_cache`, Whether to use the second level cache, `true`
+`hibernate.cache.queries`, Whether to cache queries (see Caching Queries), `false`
+`hibernate.cache.use_query_cache`, Enables the query cache, `false`
+`hibernate.configLocations`, Location of additional Hibernate XML configuration files
+`hibernate.packagesToScan`, Specify packages to search for autodetection of your entity classes in the classpath
+|===
+
+In addition, any additional settings that start with `hibernate.` are passed through to Hibernate, so if there is any specific feature of Hibernate you wish to configure that is possible.
+
+TIP: The above table covers the common configuration options. For all configuration refer to properties of the link:../api/org/grails/orm/hibernate/connections/HibernateConnectionSourceSettings.html[HibernateConnectionSourceSettings] class.
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/configuration/hibernateCustomization.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/configuration/hibernateCustomization.adoc
new file mode 100644
index 00000000000..1e6f71ab08d
--- /dev/null
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/configuration/hibernateCustomization.adoc
@@ -0,0 +1,29 @@
+If you want to hook into GORM and customize how Hibernate is configured there are a variety of ways to achieve that when using GORM.
+
+Firstly, as mentioned previously, any configuration you specify when configuring GORM for Hibernate will be passed through to Hibernate so you can configure any setting of Hibernate itself.
+
+For more advanced configuration you may want to configure or supply a new link:../api/org/grails/orm/hibernate/connections/HibernateConnectionSourceFactory.html[HibernateConnectionSourceFactory] instance or a link:../api/org/grails/orm/hibernate/cfg/HibernateMappingContextConfiguration.html[HibernateMappingContextConfiguration] or both.
+
+==== The HibernateConnectionSourceFactory
+
+The `HibernateConnectionSourceFactory` is used to create a new Hibernate `SessionFactory` on startup.
+
+If you are using Spring, it is registered as a Spring bean using the name `hibernateConnectionSourceFactory` and therefore can be overridden.
+
+If you are not using Spring it can be passed to the constructor of the `HibernateDatastore` class on instantiation.
+
+The `HibernateConnectionSourceFactory` has a few useful setters that allow you to specify a Hibernate https://docs.jboss.org/hibernate/orm/5.6/javadocs/org/hibernate/Interceptor.html[Interceptor] or https://docs.jboss.org/hibernate/orm/5.6/javadocs/org/hibernate/boot/spi/MetadataContributor.html[MetadataContributor] (Hibernate 5+ only).
+
+==== The HibernateMappingContextConfiguration
+
+link:../api/org/grails/orm/hibernate/cfg/HibernateMappingContextConfiguration.html[HibernateMappingContextConfiguration] is built by the `HibernateConnectionSourceFactory`, but a customized version can be specified using the `hibernate.configClass` setting in your configuration:
+
+[source,yaml]
+.grails-app/conf/application.yml
+----
+hibernate:
+ configClass: com.example.MyHibernateMappingContextConfiguration
+----
+
+The customized version should extend `HibernateMappingContextConfiguration` and using this class you can add additional classes, packages, `hbm.cfg.xml` files and so on.
+
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/configuration/index.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/configuration/index.adoc
new file mode 100644
index 00000000000..5544946e157
--- /dev/null
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/configuration/index.adoc
@@ -0,0 +1,40 @@
+GORM for Hibernate can be configured with the `grails-app/conf/application.yml` file when using Grails, the `src/main/resources/application.yml` file when using Spring Boot or by passing a `Map` or instanceof the `PropertyResolver` interface to the `org.grails.orm.hibernate.HibernateDatastore` class when used standalone.
+
+All configuration options are read and materialized into an instance of link:../api/org/grails/orm/hibernate/connections/HibernateConnectionSourceSettings.html[HibernateConnectionSourceSettings].
+
+=== Configuration Example
+
+If you are using Grails or Spring Boot, the following is an example of configuration specified in `application.yml`:
+
+[source,yaml]
+----
+dataSource:
+ pooled: true
+ dbCreate: create-drop
+ url: jdbc:h2:mem:devDb
+ driverClassName: org.h2.Driver
+ username: sa
+ password:
+hibernate:
+ cache:
+ queries: false
+ use_second_level_cache: true
+ use_query_cache: false
+ region.factory_class: org.hibernate.cache.ehcache.EhCacheRegionFactory
+----
+
+Each one of the settings under the `dataSource` block is set on the link:../api/org/grails/datastore/gorm/jdbc/connections/DataSourceSettings.html[DataSourceSettings] property of `HibernateConnectionSourceSettings`.
+
+Whilst each setting under the `hibernate` block is set on the link:../api/org/grails/orm/hibernate/connections/HibernateConnectionSourceSettings.HibernateSettings.html[HibernateSettings] property.
+
+=== Configuration Reference
+
+include::configurationReference.adoc[]
+
+=== The Default Mapping & Constraints
+
+include::configurationDefaults.adoc[]
+
+=== Hibernate Customization
+
+include::hibernateCustomization.adoc[]
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/constraints/applyingConstraints.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/constraints/applyingConstraints.adoc
new file mode 100644
index 00000000000..259d7a11200
--- /dev/null
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/constraints/applyingConstraints.adoc
@@ -0,0 +1,135 @@
+Within a domain class constraints are defined with the constraints property that is assigned a code block:
+
+[source,java]
+----
+class User {
+ String login
+ String password
+ String email
+ Integer age
+
+ static constraints = {
+ ...
+ }
+}
+----
+
+You then use method calls that match the property name for which the constraint applies in combination with named parameters to specify constraints:
+
+[source,java]
+----
+class User {
+ ...
+
+ static constraints = {
+ login size: 5..15, blank: false, unique: true
+ password size: 5..15, blank: false
+ email email: true, blank: false
+ age min: 18
+ }
+}
+----
+
+In this example we've declared that the `login` property must be between 5 and 15 characters long, it cannot be blank and must be unique. We've also applied other constraints to the `password`, `email` and `age` properties.
+
+NOTE: By default, all domain class properties are not nullable (i.e. they have an implicit `nullable: false` constraint).
+
+Note that constraints are only evaluated once which may be relevant for a constraint that relies on a value like an instance of `java.util.Date`.
+
+[source,java]
+----
+class User {
+ ...
+
+ static constraints = {
+ // this Date object is created when the constraints are evaluated, not
+ // each time an instance of the User class is validated.
+ birthDate max: new Date()
+ }
+}
+----
+
+
+=== Referencing Instances in Constraints
+
+
+It's very easy to attempt to reference instance variables from the static constraints block, but this isn't legal in Groovy (or Java). If you do so, you will get a `MissingPropertyException` for your trouble. For example, you may try the following:
+
+[source,groovy]
+----
+class Response {
+ Survey survey
+ Answer answer
+
+ static constraints = {
+ survey blank: false
+ answer blank: false, inList: survey.answers
+ }
+}
+----
+
+See how the `inList` constraint references the instance property `survey`? That won't work. Instead, use a custom `validator` constraint:
+
+[source,groovy]
+----
+class Response {
+ ...
+ static constraints = {
+ survey blank: false
+ answer blank: false, validator: { val, Response obj -> val in obj.survey.answers }
+ }
+}
+----
+
+In this example, the `obj` argument to the custom validator is the domain _instance_ that is being validated, so we can access its `survey` property and return a boolean to indicate whether the new value for the `answer` property, `val`, is valid.
+
+=== Cascade constraints validation
+
+If GORM entity references some other entities, then during its constraints evaluation (validation) the constraints of the referenced entity could be
+evaluated also, if needed. There is a special parameter `cascadeValidate` in the entity mappings section, which manage the way of this _cascaded_ validation happens.
+
+[source,groovy]
+----
+class Author {
+ Publisher publisher
+
+ static mapping = {
+ publisher(cascadeValidate: "dirty")
+ }
+}
+
+class Publisher {
+ String name
+
+ static constraints = {
+ name blank: false
+ }
+}
+----
+
+The following table presents all options, which can be used:
+[cols="1,2"]
+|===
+|Option |Description
+
+|none
+|Will not do any cascade validation at all for the association.
+
+|default
+|The DEFAULT option. GORM performs cascade validation in some cases.
+
+|dirty
+|Only cascade validation if the referenced object is dirty via the `DirtyCheckable` trait. If the object doesn't implement DirtyCheckable, this will fall back to `default`.
+
+|owned
+|Only cascade validation if the entity <> the referenced object.
+|===
+
+It is possible to set the global option for the `cascadeValidate`:
+[source,groovy]
+.grails-app/conf/application.groovy
+----
+grails.gorm.default.mapping = {
+ '*'(cascadeValidate: 'dirty')
+}
+----
\ No newline at end of file
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/constraints/constraintReference.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/constraints/constraintReference.adoc
new file mode 100644
index 00000000000..703a0e8513a
--- /dev/null
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/constraints/constraintReference.adoc
@@ -0,0 +1,24 @@
+The following table summarizes the available constraints with a brief example:
+
+[format="csv", options="header"]
+|===
+
+Constraint,Description,Example
+blank,Validates that a String value is not blank,`login(blank:false)`
+creditCard,Validates that a String value is a valid credit card number,`cardNumber(creditCard: true)`
+email,Validates that a String value is a valid email address.,`homeEmail(email: true)`
+inList,Validates that a value is within a range or collection of constrained values.,`name(inList: ["Joe"])`
+matches,Validates that a String value matches a given regular expression.,`login(matches: "[a-zA-Z]+")`
+max,Validates that a value does not exceed the given maximum value.,`age(max: new Date())` `price(max: 999F)`
+maxSize,Validates that a value's size does not exceed the given maximum value.,`children(maxSize: 25)`
+min,Validates that a value does not fall below the given minimum value.,`age(min: new Date())` `price(min: 0F)`
+minSize,Validates that a value's size does not fall below the given minimum value.,`children(minSize: 25)`
+notEqual,Validates that that a property is not equal to the specified value,`login(notEqual: "Bob")`
+nullable,Allows a property to be set to `null` - defaults to `false`.,`age(nullable: true)`
+range,Uses a Groovy range to ensure that a property's value occurs within a specified range,`age(range: 18..65)`
+scale,Set to the desired scale for floating point numbers (i.e. the number of digits to the right of the decimal point).,`salary(scale: 2)`
+size,Uses a Groovy range to restrict the size of a collection or number or the length of a String.,`children(size: 5..15)`
+unique,Constrains a property as unique at the database level,`login(unique: true)`
+url,Validates that a String value is a valid URL.,`homePage(url: true)`
+validator,Adds custom validation to a field.,See documentation
+|===
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/constraints/gormConstraints.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/constraints/gormConstraints.adoc
new file mode 100644
index 00000000000..25842bb9b75
--- /dev/null
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/constraints/gormConstraints.adoc
@@ -0,0 +1,112 @@
+Although constraints are primarily for validation, it is important to understand that constraints can affect the way in which the database schema is generated.
+
+Where feasible, GORM uses a domain class's constraints to influence the database columns generated for the corresponding domain class properties.
+
+Consider the following example. Suppose we have a domain model with the following properties:
+
+[source,groovy]
+----
+String name
+String description
+----
+
+By default, in MySQL, GORM would define these columns as
+
+[format="csv", options="header"]
+|===
+
+Column,Data Type
+name,varchar(255)
+description,varchar(255)
+|===
+
+But perhaps the business rules for this domain class state that a description can be up to 1000 characters in length. If that were the case, we would likely define the column as follows _if_ we were creating the table with an SQL script.
+
+[format="csv", options="header"]
+|===
+
+Column,Data Type
+description,TEXT
+|===
+
+Chances are we would also want to have some application-based validation to make sure we don't exceed that 1000 character limit _before_ we persist any records. In GORM, we achieve this validation with <>. We would add the following constraint declaration to the domain class.
+
+[source,groovy]
+----
+static constraints = {
+ description maxSize: 1000
+}
+----
+
+This constraint would provide both the application-based validation we want and it would also cause the schema to be generated as shown above. Below is a description of the other constraints that influence schema generation.
+
+
+==== Constraints Affecting String Properties
+
+
+* `inList`
+* `maxSize`
+* `size`
+
+If either the `maxSize` or the `size` constraint is defined, Grails sets the maximum column length based on the constraint value.
+
+In general, it's not advisable to use both constraints on the same domain class property. However, if both the `maxSize` constraint and the `size` constraint are defined, then GORM sets the column length to the minimum of the `maxSize` constraint and the upper bound of the size constraint. (GORM uses the minimum of the two, because any length that exceeds that minimum will result in a validation error.)
+
+If the `inList` constraint is defined (and the `maxSize` and the `size` constraints are not defined), then GORM sets the maximum column length based on the length of the longest string in the list of valid values. For example, given a list including values "Java", "Groovy", and "C++", GORM would set the column length to 6 (i.e., the number of characters in the string "Groovy").
+
+
+==== Constraints Affecting Numeric Properties
+
+
+* `min`
+* `max`
+* `range`
+
+If the `max`, `min`, or `range` constraint is defined, GORM attempts to set the column precision based on the constraint value. (The success of this attempted influence is largely dependent on how Hibernate interacts with the underlying DBMS.)
+
+In general, it's not advisable to combine the pair `min`/`max` and `range` constraints together on the same domain class property. However, if both of these constraints is defined, then GORM uses the minimum precision value from the constraints. (GORM uses the minimum of the two, because any length that exceeds that minimum precision will result in a validation error.)
+
+* `scale`
+
+If the scale constraint is defined, then GORM attempts to set the column <> based on the constraint value. This rule only applies to floating point numbers (i.e., `java.lang.Float`, `java.Lang.Double`, `java.lang.BigDecimal`, or subclasses of `java.lang.BigDecimal`). The success of this attempted influence is largely dependent on how Hibernate interacts with the underlying DBMS.
+
+The constraints define the minimum/maximum numeric values, and GORM derives the maximum number of digits for use in the precision. Keep in mind that specifying only one of `min`/`max` constraints will not affect schema generation (since there could be large negative value of property with max:100, for example), unless the specified constraint value requires more digits than default Hibernate column precision is (19 at the moment). For example:
+
+[source,groovy]
+----
+someFloatValue max: 1000000, scale: 3
+----
+
+would yield:
+
+[source,groovy]
+----
+someFloatValue DECIMAL(19, 3) // precision is default
+----
+
+but
+
+[source,groovy]
+----
+someFloatValue max: 12345678901234567890, scale: 5
+----
+
+would yield:
+[source,groovy]
+----
+someFloatValue DECIMAL(25, 5) // precision = digits in max + scale
+----
+
+and
+
+[source,groovy]
+----
+someFloatValue max: 100, min: -100000
+----
+
+would yield:
+
+[source,groovy]
+----
+someFloatValue DECIMAL(8, 2) // precision = digits in min + default scale
+----
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/constraints/index.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/constraints/index.adoc
new file mode 100644
index 00000000000..46aa1a5f5bf
--- /dev/null
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/constraints/index.adoc
@@ -0,0 +1,13 @@
+Constraints are how you define validation when using GORM entities.
+
+=== Applying Constraints
+
+include::applyingConstraints.adoc[]
+
+=== Constraints Reference
+
+include::constraintReference.adoc[]
+
+=== Constraints and Database Mapping
+
+include::gormConstraints.adoc[]
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/configuration.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/configuration.adoc
new file mode 100644
index 00000000000..422c2cd4fb5
--- /dev/null
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/configuration.adoc
@@ -0,0 +1,49 @@
+=== Configuration
+
+There are a few configuration options for the plugin. All configurations are prefixed with `grails.plugin.databasemigration`:
+
+[options="header"]
+|==================================
+|Property |Default |Meaning
+|changelogLocation |`grails-app/migrations` |the folder containing the main changelog file (which can include one or more other files)
+|changelogFileName |`changelog.groovy` |the name of the main changelog file
+|changelogProperties |none |a map of properties to use for property substitution in Groovy DSL changelogs
+|contexts |none |A comma-delimited list of http://www.liquibase.org/manual/contexts[context] names. If specified, only changesets tagged with one of the context names will be run
+|dbDocLocation |`target/dbdoc` |the directory where the output from the <> script is written
+|dbDocController.enabled |`true` in dev mode |whether the /dbdoc/ url is accessible at runtime
+|dropOnStart |`false` |if `true` then drops all tables before auto-running migrations (if updateOnStart is true)
+|updateOnStart |`false` |if `true` then changesets from the specified list of names will be run at startup
+|updateOnStartFileName |none |the file name (relative to `changelogLocation`) to run at startup if `updateOnStart` is `true`
+|updateOnStartDefaultSchema |none |the default schema to use when running auto-migrate on start
+|updateOnStartContexts |none |A comma-delimited list of http://www.liquibase.org/manual/contexts[context] names. If specified, only changesets tagged with one of the context names will be run
+|updateAllOnStart |false |if `true` then changesets from the specified list of names will be run at startup for all dataSources. Useful for Grails Multitenancy with Multiple Databases (same db schema)
+|autoMigrateScripts |['RunApp'] |the scripts when running auto-migrate. Useful to run auto-migrate during test phase with: ['RunApp', 'TestApp']
+|excludeObjects |none |A comma-delimited list of database object names to ignore while performing a dbm-gorm-diff or dbm-generate-gorm-changelog
+|includeObjects |none |A comma-delimited list of database object names to look for while performing a dbm-gorm-diff or dbm-generate-gorm-changelog
+|databaseChangeLogTableName |'databasechangelog' |the Liquibase changelog record table name
+|databaseChangeLogLockTableName |'databasechangeloglock' |the Liquibase lock table name
+|==================================
+
+NOTE: All the above configs can be used for multiple datasources
+
+
+*Multiple DataSource Example:*
+
+If secondary dataSource named "second" is configured in application.yml
+[source,yaml]
+----
+include::{migrationPluginExamplesDir}/application-multiple-datasource.yml[lines=11..29]
+----
+
+The configuration for this data source would be:
+[source,groovy]
+----
+grails.plugin.databasemigration.reports.updateOnStart = true
+grails.plugin.databasemigration.reports.changelogFileName = changelog-second.groovy
+----
+The configuration for all data sources with same db schema would be:
+[source,groovy]
+----
+grails.plugin.databasemigration.updateAllOnStart = true
+grails.plugin.databasemigration.changelogFileName = changelog.groovy
+----
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/dbdoc.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/dbdoc.adoc
new file mode 100644
index 00000000000..1eb77962c3f
--- /dev/null
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/dbdoc.adoc
@@ -0,0 +1,23 @@
+=== DbDoc Controller
+
+You can use the <> script to generate static HTML files to view changelog information, but another option is to use the `DbDocController` at runtime. By default this controller is mapped to `/appname/dbdoc/` but this can be customized with `UrlMappings` like any controller.
+
+You probably don't want to expose this information to all of your application's users so by default the controller is only enabled in the development environment. But you can enable or disable it for any environment in `application.groovy` with the `dbDocController.enabled` config option. For example to enable for all environments (be sure to guard the URL with a security plugin in prod):
+
+[source,groovy]
+----
+grails.plugin.databasemigration.dbDocController.enabled = true
+----
+
+or to enable in the production environment:
+
+[source,groovy]
+----
+environments {
+ production {
+ grails.plugin.databasemigration.dbDocController.enabled = true
+ }
+ ...
+}
+----
+
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/generalUsage.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/generalUsage.adoc
new file mode 100644
index 00000000000..c37b746c930
--- /dev/null
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/generalUsage.adoc
@@ -0,0 +1,95 @@
+=== General Usage
+
+After creating the initial changelog, the typical workflow will be along the lines of:
+
+* make domain class changes that affect the schema
+* add changes to the changelog for them
+* backup your database in case something goes wrong
+* run `grails dbm-update` to update your development environment (or wherever you're applying the changes)
+* check the updated domain class(es) and changelog(s) into source control
+
+[WARNING]
+=====
+1. When running migration scripts on non-development databases, it's important that you backup the database before running the migration in case anything goes wrong. You could also make a copy of the database and run the script against that, and if there's a problem the real database will be unaffected.
+2. Setting the dbCreate setting to "none" is recommended when executing the dbm migration commands. Otherwise you might run into troubles and the commands could not be executed.
+=====
+
+To create the changelog additions, you can either manually create the changes or with the <> script (you can also use the <> script but it's far less convenient and requires a 2nd temporary database).
+
+You have a few options with `dbm-gorm-diff`:
+
+* `dbm-gorm-diff` will dump to the console if no filename is specified, so you can copy/paste from there
+* if you include the `--add` parameter when running the script with a filename it will register an include for the filename in the main changelog for you
+
+Regardless of which approach you use, be sure to inspect generated changes and adjust as necessary.
+
+
+==== Autorun on start
+
+
+Since Liquibase maintains a record of changes that have been applied, you can avoid manually updating the database by taking advantage of the plugin's auto-run feature. By default this is disabled, but you can enable it by adding
+
+[source,groovy]
+----
+grails.plugin.databasemigration.updateOnStart = true
+----
+
+to application.groovy. In addition you must specify the file containing changes; specify the name using the `updateOnStartFileName` property, e.g.:
+
+[source,groovy]
+----
+grails.plugin.databasemigration.updateOnStartFileName = 'changelog.groovy'
+----
+
+Since changelogs can contain changelogs you'll most often just specify the root changelog, changelog.groovy by convention. Any changes that haven't been executed (in the specified file(s) or files included by them) will be run in the order specified.
+
+You may optionally limit the plugin's auto-run feature to run only specific contexts. If this configuration parameter is empty or omitted, all contexts will be run.
+
+[source,groovy]
+----
+grails.plugin.databasemigration.updateOnStartContexts = ['context1,context2']
+----
+
+You can be notified when migration are run (for example to do some work before and/or after the migrations execute) by registering a "callback" class as a Spring bean. The class can have any name and package and doesn't have to implement any interface since its methods will be called using Groovy duck-typing.
+
+The bean name is "migrationCallbacks" and there are currently three callback methods supported (all are optional):
+
+* `beforeStartMigration` will be called (if it exists) for each datasource before any migrations have run; the method will be passed a single argument, the Liquibase `Database` for that datasource
+* `onStartMigration` will be called (if it exists) for each migration script; the method will be passed three arguments, the Liquibase `Database`, the `Liquibase` instance, and the changelog file name
+* `afterMigrations` will be called (if it exists) for each datasource after all migrations have run; the method will be passed a single argument, the Liquibase `Database` for that datasource
+
+An example class will look like this:
+
+[source,groovy]
+----
+package com.mycompany.myapp
+
+import liquibase.Liquibase
+import liquibase.database.Database
+
+class MigrationCallbacks {
+
+ void beforeStartMigration(Database Database) {
+ ...
+ }
+
+ void onStartMigration(Database database, Liquibase liquibase, String changelogName) {
+ ...
+ }
+
+ void afterMigrations(Database Database) {
+ ...
+ }
+}
+----
+
+Register it in resources.groovy:
+
+[source,groovy]
+----
+import com.mycompany.myapp.MigrationCallbacks
+
+beans = {
+ migrationCallbacks(MigrationCallbacks)
+}
+----
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/gettingStarted.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/gettingStarted.adoc
new file mode 100644
index 00000000000..3d9e6c2bc21
--- /dev/null
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/gettingStarted.adoc
@@ -0,0 +1,110 @@
+=== Getting Started
+
+*The first step is to add a dependency for the plugin in `build.gradle`:*
+
+[source,groovy,subs="attributes"]
+----
+buildscript {
+ dependencies {
+ ...
+ classpath '{migrationPluginGroupId}:{migrationPluginArtifactId}:{version}'
+ }
+}
+
+dependencies {
+ ...
+ implementation '{migrationPluginGroupId}:{migrationPluginArtifactId}:{version}'
+}
+----
+
+It is also recommended to add a direct dependency to liquibase because Spring Boot overrides the one provided by this plugin
+
+[source,groovy,subs="attributes"]
+----
+dependencies {
+ ...
+ implementation "org.liquibase:liquibase-core:{liquibaseHibernate5Version}"
+ implementation "org.liquibase.ext:liquibase-hibernate5:{liquibaseHibernate5Version}"
+}
+----
+
+You should also tell Gradle about the migrations folder. If using Grails 4 or above, make sure the configuration below is BEFORE the
+`dependencies` configuration, so that the `sourceSets` declaration takes effect.
+
+[source,groovy,subs="attributes"]
+----
+sourceSets {
+ main {
+ resources {
+ srcDir 'grails-app/migrations'
+ }
+ }
+}
+----
+
+*Typical initial workflow*
+
+Next you'll need to create an initial changelog. You can use Liquibase XML or the plugin's Groovy DSL for individual files. You can even mix and match; Groovy files can include other Groovy files and Liquibase XML files (but XML files can't include Groovy files).
+
+Depending on the state of your database and code, you have two options; either create a changelog from the database or create it from your domain classes. The decision tends to be based on whether you prefer to design the database and adjust the domain classes to work with it, or to design your domain classes and use Hibernate to create the corresponding database structure.
+
+To create a changelog from the database, use the <> script:
+[source,groovy]
+----
+grails dbm-generate-changelog changelog.groovy
+----
+
+or
+
+[source,groovy]
+----
+grails dbm-generate-changelog changelog.xml
+----
+
+depending on whether you prefer the Groovy DSL or XML. The filename is relative to the changelog base folder, which defaults to `grails-app/migrations`.
+
+NOTE: If you use the XML format (or use a non-default Groovy filename), be sure to change the name of the file in `application.groovy` so `dbm-update` and other scripts find the file:
+[source,groovy]
+----
+grails.plugin.databasemigration.changelogFileName = 'changelog.xml'
+----
+
+Since the database is already correct, run the <> script to record that the changes have already been applied:
+[source,groovy]
+----
+grails dbm-changelog-sync
+----
+
+Running this script is primarily a no-op except that it records the execution(s) in the Liquibase DATABASECHANGELOG table.
+
+To create a changelog from your domain classes, use the <> script:
+
+[source,groovy]
+----
+grails dbm-generate-gorm-changelog changelog.groovy
+----
+
+or
+
+[source,groovy]
+----
+grails dbm-generate-gorm-changelog changelog.xml
+----
+
+If you haven't created the database yet, run the <> script to create the corresponding tables:
+
+[source,groovy]
+----
+grails dbm-update
+----
+
+or the <> script if the database is already in sync with your code:
+
+[source,groovy]
+----
+grails dbm-changelog-sync
+----
+
+*Source control*
+
+Now you can commit the changelog and the corresponding application code to source control. Other developers can then update and synchronize their databases, and start doing migrations themselves.
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/gorm.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/gorm.adoc
new file mode 100644
index 00000000000..ef0090812a3
--- /dev/null
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/gorm.adoc
@@ -0,0 +1,25 @@
+=== GORM Support
+
+The plugin's support for GORM is one feature that differentiates it from using Liquibase directly. Typically, when using Liquibase you make changes to a database yourself, and then create changesets manually, or use a diff script to compare your updated database to one that hasn't been updated yet. This is a decent amount of work and is rather error-prone. It's easy to forget some changes that aren't required but help performance, for example creating an index on a foreign key when using MySQL.
+
+*create-drop, create, and update*
+
+On the other end of the spectrum, Hibernate's `create-drop` mode (or `create`) will create a database that matches your domain model, but it's destructive since all previous data is lost when it runs. This works well in the very early stages of development but gets frustrating quickly. Unfortunately Hibernate's `update` mode seems like a good compromise since it only makes changes to your existing schema, but it's very limited in what it will do. It's very pessimistic and won't make any changes that could lose data. So it will add new tables and columns, but won't drop anything. If you remove a not-null domain class property you'll find you can't insert anymore since the column is still there. And it will create not-null columns as nullable since otherwise existing data would be invalid. It won't even widen a column e.g. from `VARCHAR(100)` to `VARCHAR(200)`.
+
+*dbm-gorm-diff*
+
+The plugin provides a script that will compare your GORM current domain model with a database that you specify, and the result is a Liquibase changeset - `dbm-gorm-diff`. This is the same changeset you would get if you exported your domain model to a scratch database and diffed it with the other database, but it's more convenient.
+
+So a good workflow would be:
+
+* make whatever domain class changes you need (add new ones, delete unneeded ones, add/change/remove properties, etc.)
+* once your tests pass, and you're ready to commit your changes to source control, run the script to generate the changeset that will bring your database back in line with your code
+* add the changeset to an existing changelog file, or use the `include` tag to include the whole file
+* run the changeset on your functional test database
+* assuming your functional tests pass, check everything in as one commit
+* the other members of your team will get both the code and database changes when they next update, and will know to run the update script to sync their database with the latest code
+* once you're ready to deploy to QA for testing (or staging or production), you can run all the un-run changes since the last deployment
+
+*dbm-generate-gorm-changelog*
+
+The <> script is useful for when you want to switch from `create-drop` mode to doing proper migrations. It's not very useful if you already have a database that's in sync with your code, since you can just use the <> script that creates a changelog from your database.
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/groovyChanges.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/groovyChanges.adoc
new file mode 100644
index 00000000000..2394dcd9090
--- /dev/null
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/groovyChanges.adoc
@@ -0,0 +1,91 @@
+=== Groovy Changes
+
+In addition to the built-in Liquibase changes (see http://www.liquibase.org/manual/home[the documentation] for what's available) you can also make database changes using Groovy code (as long as you're using the Groovy DSL file format). These changes use the `grailsChange` tag name and are contained in a `changeSet` tag like standard built-in tags.
+
+There are four supported inner tags and two callable methods (to override the default confirmation message and checksum value).
+
+==== General format
+
+This is the general format of a Groovy-based change; all inner tags and methods are optional:
+
+[source,groovy]
+----
+databaseChangeLog = {
+
+ changeSet(author: '...', id: '...') {
+
+ grailsChange {
+ init {
+ // arbitrary initialization code; note that no
+ // database or connection is available
+ }
+
+ validate {
+ // can call warn(String message) to log a warning
+ // or error(String message) to stop processing
+ }
+
+ change {
+ // arbitrary code; make changes directly and/or return a
+ // SqlStatement using the sqlStatement(SqlStatement sqlStatement)
+ // method or multiple with sqlStatements(List sqlStatements)
+
+ confirm 'change confirmation message'
+ }
+
+ rollback {
+ // arbitrary code; make rollback changes directly and/or
+ // return a SqlStatement using the sqlStatement(SqlStatement sqlStatement)
+ // method or multiple with sqlStatements(List sqlStatements)
+
+ confirm 'rollback confirmation message'
+ }
+
+ confirm 'confirmation message'
+
+ checkSum 'override value for checksum'
+ }
+
+ }
+}
+----
+
+==== Available variables
+
+These variables are available throughout the change closure:
+
+* `changeSet` - the current Liquibase `ChangeSet` instance
+* `resourceAccessor` - the current Liquibase `ResourceAccessor` instance
+* `ctx` - the Spring `ApplicationContext`
+* `application` - the `GrailsApplication`
+
+The `change` and `rollback` closures also have the following available:
+
+* `database` - the current Liquibase `Database` instance
+* `databaseConnection` - the current Liquibase `DatabaseConnection` instance, which is a wrapper around the JDBC `Connection` (but doesn't implement the `Connection` interface)
+* `connection` - the real JDBC `Connection` instance (a shortcut for `database.connection.wrappedConnection`)
+* `sql` - a `groovy.sql.Sql` instance which uses the current `connection` and can be used for arbitrary queries and updates
+
+*init*
+
+This is where any optional initialization should happen. You can't access the database from this closure.
+
+*validate*
+
+If there are any necessary validation checks before executing changes or rollbacks they should be done here. You can log warnings by calling `warn(String message)` and stop processing by calling `error(String message)`. It may make more sense to use one or more ++preCondition++s instead of directly validating here.
+
+*change*
+
+All migration changes are done in the `change` closure. You can make changes directly (using the `sql` instance or the `connection`) and/or return one or more ++SqlStatement++s. You can call `sqlStatement(SqlStatement statement)` multiple times to register instances to be run. You can also call the `sqlStatements(statements)` method with an array or list of instances to be run.
+
+*rollback*
+
+All rollback changes are done in the `rollback` closure. You can make changes directly (using the `sql` instance or the `connection`) and/or return one or more ++SqlStatement++s. You can call `sqlStatement(SqlStatement statement)` multiple times to register instances to be run. You can also call the `sqlStatements(statements)` method with an array or list of instances to be run.
+
+*confirm*
+
+The `confirm(String message)` method is used to specify the confirmation message to be shown. The default is "Executed GrailsChange" and it can be overridden in the `change` or `rollback` closures to allow phase-specific messages or outside of both closures to use the same message for the update and rollback phase.
+
+*checkSum*
+
+The checksum for the change will be generated automatically, but if you want to override the value that gets hashed you can specify it with the `checkSum(String value)` method.
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/groovyPreconditions.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/groovyPreconditions.adoc
new file mode 100644
index 00000000000..8e6d66ac883
--- /dev/null
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/groovyPreconditions.adoc
@@ -0,0 +1,68 @@
+=== Groovy Preconditions
+
+In addition to the built-in Liquibase preconditions (see http://www.liquibase.org/manual/preconditions[the documentation] for what's available) you can also specify preconditions using Groovy code (as long as you're using the Groovy DSL file format). These changes use the `grailsPrecondition` tag name and are contained in the `databaseChangeLog` tag or in a `changeSet` tag like standard built-in tags.
+
+==== General format
+
+This is the general format of a Groovy-based precondition:
+
+[source,groovy]
+----
+databaseChangeLog = {
+
+ changeSet(author: '...', id: '...') {
+
+ preConditions {
+
+ grailsPrecondition {
+
+ check {
+
+ // use an assertion
+ assert x == x
+
+ // use an assertion with an error message
+ assert y == y : 'value cannot be 237'
+
+ // call the fail method
+ if (x != x) {
+ fail 'x != x'
+ }
+
+ // throw an exception (the fail method is preferred)
+ if (y != y) {
+ throw new RuntimeException('y != y')
+ }
+ }
+
+ }
+
+ }
+ }
+}
+----
+
+As you can see there are a few ways to indicate that a precondition wasn't met:
+
+* use a simple assertion
+* use an assertion with a message
+* call the `fail(String message)` method (throws a `PreconditionFailedException`)
+* throw an exception (shouldn't be necessary - use `assert` or `fail()` instead)
+
+==== Available variables
+
+* `database` - the current Liquibase `Database` instance
+* `databaseConnection` - the current Liquibase `DatabaseConnection` instance, which is a wrapper around the JDBC `Connection` (but doesn't implement the `Connection` interface)
+* `connection` - the real JDBC `Connection` instance (a shortcut for `database.connection.wrappedConnection`)
+* `sql` - a `groovy.sql.Sql` instance which uses the current `connection` and can be used for arbitrary queries and updates
+* `resourceAccessor` - the current Liquibase `ResourceAccessor` instance
+* `ctx` - the Spring `ApplicationContext`
+* `application` - the `GrailsApplication`
+* `changeSet` - the current Liquibase `ChangeSet` instance
+* `changeLog` - the current Liquibase `DatabaseChangeLog` instance
+
+==== Utility methods
+
+* `createDatabaseSnapshotGenerator()` - retrieves the `DatabaseSnapshotGenerator` for the current `Database`
+* `createDatabaseSnapshot(String schemaName = null)` - creates a `DatabaseSnapshot` for the current `Database` (and schema if specified)
+
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/index.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/index.adoc
new file mode 100644
index 00000000000..f46f5e1c4bc
--- /dev/null
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/index.adoc
@@ -0,0 +1,134 @@
+[[introduction]]
+include::introduction.adoc[]
+
+[[gettingStarted]]
+include::gettingStarted.adoc[]
+
+[[configuration]]
+include::configuration.adoc[]
+
+[[generalUsage]]
+include::generalUsage.adoc[]
+
+[[groovyChanges]]
+include::groovyChanges.adoc[]
+
+[[groovyPreconditions]]
+include::groovyPreconditions.adoc[]
+
+[[gorm]]
+include::gorm.adoc[]
+
+[[dbdoc]]
+include::dbdoc.adoc[]
+
+[[reference]]
+=== Reference
+
+[[ref-diff-scripts]]
+==== Diff Scripts
+
+[[ref-diff-scripts-dbm-diff]]
+include::ref/Diff Scripts/dbm-diff.adoc[]
+
+[[ref-diff-scripts-dbm-gorm-diff]]
+include::ref/Diff Scripts/dbm-gorm-diff.adoc[]
+
+[[ref-documentation-scripts]]
+==== Documentation Scripts
+
+[[ref-documentation-scripts-dbm-db-doc]]
+include::ref/Documentation Scripts/dbm-db-doc.adoc[]
+
+[[ref-maintenance-scripts]]
+==== Maintenance Scripts
+
+[[ref-maintenance-scripts-dbm-add-migration]]
+include::ref/Maintenance Scripts/dbm-add-migration.adoc[]
+
+[[ref-maintenance-scripts-dbm-changelog-sync-sql]]
+include::ref/Maintenance Scripts/dbm-changelog-sync-sql.adoc[]
+
+[[ref-maintenance-scripts-dbm-changelog-sync]]
+include::ref/Maintenance Scripts/dbm-changelog-sync.adoc[]
+
+[[ref-maintenance-scripts-dbm-changelog-to-groovy]]
+include::ref/Maintenance Scripts/dbm-changelog-to-groovy.adoc[]
+
+[[ref-maintenance-scripts-dbm-clear-checksums]]
+include::ref/Maintenance Scripts/dbm-clear-checksums.adoc[]
+
+[[ref-maintenance-scripts-dbm-create-changelog]]
+include::ref/Maintenance Scripts/dbm-create-changelog.adoc[]
+
+[[ref-maintenance-scripts-dbm-drop-all]]
+include::ref/Maintenance Scripts/dbm-drop-all.adoc[]
+
+[[ref-maintenance-scripts-dbm-list-locks]]
+include::ref/Maintenance Scripts/dbm-list-locks.adoc[]
+
+[[ref-maintenance-scripts-dbm-list-tags]]
+include::ref/Maintenance Scripts/dbm-list-tags.adoc[]
+
+[[ref-maintenance-scripts-dbm-mark-next-changeset-ran]]
+include::ref/Maintenance Scripts/dbm-mark-next-changeset-ran.adoc[]
+
+[[ref-maintenance-scripts-dbm-release-locks]]
+include::ref/Maintenance Scripts/dbm-release-locks.adoc[]
+
+[[ref-maintenance-scripts-dbm-status]]
+include::ref/Maintenance Scripts/dbm-status.adoc[]
+
+[[ref-maintenance-scripts-dbm-tag]]
+include::ref/Maintenance Scripts/dbm-tag.adoc[]
+
+[[ref-maintenance-scripts-dbm-validate]]
+include::ref/Maintenance Scripts/dbm-validate.adoc[]
+
+[[ref-rollback-scripts]]
+=== Rollback Scripts
+
+[[ref-rollback-scripts-dbm-future-rollback-sql]]
+include::ref/Rollback Scripts/dbm-future-rollback-sql.adoc[]
+
+[[ref-rollback-scripts-dbm-generate-changelog]]
+include::ref/Rollback Scripts/dbm-generate-changelog.adoc[]
+
+[[ref-rollback-scripts-dbm-generate-gorm-changelog]]
+include::ref/Rollback Scripts/dbm-generate-gorm-changelog.adoc[]
+
+[[ref-rollback-scripts-dbm-rollback-count-sql]]
+include::ref/Rollback Scripts/dbm-rollback-count-sql.adoc[]
+
+[[ref-rollback-scripts-dbm-rollback-count]]
+include::ref/Rollback Scripts/dbm-rollback-count.adoc[]
+
+[[ref-rollback-scripts-dbm-rollback-sql]]
+include::ref/Rollback Scripts/dbm-rollback-sql.adoc[]
+
+[[ref-rollback-scripts-dbm-rollback-to-date-sql]]
+include::ref/Rollback Scripts/dbm-rollback-to-date-sql.adoc[]
+
+[[ref-rollback-scripts-dbm-rollback-to-date]]
+include::ref/Rollback Scripts/dbm-rollback-to-date.adoc[]
+
+[[ref-rollback-scripts-dbm-rollback]]
+include::ref/Rollback Scripts/dbm-rollback.adoc[]
+
+[[ref-update-scripts]]
+==== Update Scripts
+
+[[ref-update-scripts-dbm-previous-changeset-sql]]
+include::ref/Update Scripts/dbm-previous-changeset-sql.adoc[]
+
+[[ref-update-scripts-dbm-update-count-sql]]
+include::ref/Update Scripts/dbm-update-count-sql.adoc[]
+
+[[ref-update-scripts-dbm-update-count]]
+include::ref/Update Scripts/dbm-update-count.adoc[]
+
+[[ref-update-scripts-dbm-update-sql]]
+include::ref/Update Scripts/dbm-update-sql.adoc[]
+
+[[ref-update-scripts-dbm-update]]
+include::ref/Update Scripts/dbm-update.adoc[]
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/introduction.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/introduction.adoc
new file mode 100644
index 00000000000..5e3720e3c0e
--- /dev/null
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/introduction.adoc
@@ -0,0 +1,17 @@
+=== Introduction
+
+The Database Migration plugin helps you manage database changes while developing Grails applications. The plugin uses the http://www.liquibase.org/[Liquibase] library.
+
+Using this plugin (and Liquibase in general) adds some structure and process to managing database changes. It will help avoid inconsistencies, communication issues, and other problems with ad-hoc approaches.
+
+Database migrations are represented in text form, either using a Groovy DSL or native Liquibase XML, in one or more changelog files. This approach makes it natural to maintain the changelog files in source control and also works well with branches. Changelog files can include other changelog files, so often developers create hierarchical files organized with various schemes. One popular approach is to have a root changelog named changlog.groovy (or changelog.xml) and to include a changelog per feature/branch that includes multiple smaller changelogs. Once the feature is finished and merged into the main development tree/trunk the changelog files can either stay as they are or be merged into one large file. Use whatever approach makes sense for your applications, but keep in mind that there are many options available for changelog management.
+
+Individual changes have an ID that should be globally unique, although they also include the username of the user making the change, making the combination of ID and username unique (although technically the ID, username, and changelog location are the "unique key").
+
+As you make changes in your code (typically domain classes) that require changes in the database, you add a new change set to the changelog. Commit the code changes along with the changelog additions, and the other developers on your team will get both when they update from source control. Once they apply the new changes their code and development database will be in sync with your changes. Likewise when you deploy to a QA, a staging server, or production, you'll run the un-run changes that correspond to the code updates to being that environment's database in sync. Liquibase keeps track of previously executed changes so there's no need to think about what has and hasn't been run yet.
+
+*Scripts*
+
+Your primary interaction with the plugin will be using the provided scripts. For the most part these correspond to the many Liquibase commands that are typically executed directly from the commandline or with its Ant targets, but there are also a few Grails-specific scripts that take advantage of the information available from the GORM mappings.
+
+All the scripts start with `dbm-` to ensure that they're unique and don't clash with scripts from Grails or other plugins.
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Diff Scripts/dbm-diff.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Diff Scripts/dbm-diff.adoc
new file mode 100644
index 00000000000..1c114719c3e
--- /dev/null
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Diff Scripts/dbm-diff.adoc
@@ -0,0 +1,43 @@
+===== dbm-diff
+
+====== Purpose
+
+Compares two databases and creates a changelog that will make the changes required to bring them into sync.
+
+====== Description
+
+Executes against the database configured in `application.[yml|groovy]` for the current environment (defaults to `dev`) and another configured datasource in `application.[yml|groovy]`.
+
+If a filename parameter is specified then the output will be written to the named file, otherwise it will be written to the console. If the filename ends with .groovy a Groovy DSL file will be created, otherwise a standard XML file will be created.
+
+File are written to the migrations folder, so specify the filename relative to the migrations folder (`grails-app/migrations` by default).
+
+Usage:
+[source,java]
+----
+grails <> dbm-diff <> <> --defaultSchema=<> --dataSource=<> --add
+----
+
+Required arguments:
+
+* `otherEnv` - The name of the environment to compare to
+
+Optional arguments:
+
+* `filename` - The path to the output file to write to. If not specified output is written to the console
+* `defaultSchema` - The default schema name to use
+* `add` - If specified add an include in the root changelog file referencing the new file
+* `dataSource` - If provided will run the script for the specified dataSource. Not needed for the default dataSource.
+
+NOTE: Note that the `defaultSchema` and `dataSource` parameter name and value must be quoted if executed in Windows, e.g.
+[source,groovy]
+----
+grails dbm-diff "--defaultSchema=<>" "--dataSource=<>"
+----
+
+NOTE: For the `dataSource` parameter, if the data source is configured as `reports` underneath the `dataSources` key in `application.[yml|groovy]`, the value should be `reports`.
+
+[source,groovy]
+----
+--dataSource=reports
+----
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Diff Scripts/dbm-gorm-diff.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Diff Scripts/dbm-gorm-diff.adoc
new file mode 100644
index 00000000000..1e5c376000e
--- /dev/null
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Diff Scripts/dbm-gorm-diff.adoc
@@ -0,0 +1,45 @@
+===== dbm-gorm-diff
+
+====== Purpose
+
+Diffs GORM classes against a database and generates a changelog XML or Groovy DSL file.
+
+====== Description
+
+Creates a Groovy DSL file if the filename is specified and it ends with .groovy. If another extension is specified it creates a standard Liquibase XML file, and if no filename is specified it writes to the console.
+
+File are written to the migrations folder, so specify the filename relative to the migrations folder (`grails-app/migrations` by default).
+
+Similar to <> but diffs the current configuration based on the application's domain classes with the database configured in `application.[yml|groovy]` for the current environment (defaults to `dev`).
+
+Doesn't modify any existing files - you need to manually merge the output into the changeset along with any necessary modifications.
+
+You can configure database objects to be ignored by this script - either in the GORM classes or in the target database. For example you may want domain objects that are transient, or you may have externally-managed tables, keys, etc. that you want left alone by the diff script. The configuration name for these ignored objects is `grails.plugin.databasemigration.ignoredObjects`, whose value is a list of strings.
+
+Usage:
+[source,java]
+----
+grails <> dbm-gorm-diff <> --defaultSchema=<> --dataSource=<> --add
+----
+
+Required arguments: _none_ .
+
+Optional arguments:
+
+* `filename` - The path to the output file to write to. If not specified output is written to the console
+* `defaultSchema` - The default schema name to use
+* `add` - if specified add an include in the root changelog file referencing the new file
+* `dataSource` - if provided will run the script for the specified dataSource. Not needed for the default dataSource.
+
+NOTE: Note that the `defaultSchema` and `dataSource` parameter name and value must be quoted if executed in Windows, e.g.
+[source,groovy]
+----
+grails dbm-gorm-diff "--defaultSchema=<>" "--dataSource=<>"
+----
+
+NOTE: For the `dataSource` parameter, if the data source is configured as `reports` underneath the `dataSources` key in `application.[yml|groovy]`, the value should be `reports`.
+
+[source,groovy]
+----
+--dataSource=reports
+----
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Documentation Scripts/dbm-db-doc.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Documentation Scripts/dbm-db-doc.adoc
new file mode 100644
index 00000000000..f827c2a42a8
--- /dev/null
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Documentation Scripts/dbm-db-doc.adoc
@@ -0,0 +1,36 @@
+===== dbm-db-doc
+
+====== Purpose
+
+Generates Javadoc-like documentation based on current database and change log.
+
+====== Description
+
+Writes to the folder specified by the `destination` parameter, or to the `grails.plugin.databasemigration.dbDocLocation` configuration option (defaults to `target/dbdoc`).
+
+Usage:
+[source,java]
+----
+grails <> dbm-db-doc <> --contexts=<> --dataSource=<>
+----
+
+Required arguments: _none_ .
+
+Optional arguments:
+
+* `destination` - The path to write to
+* `contexts` - A comma-delimited list of http://www.liquibase.org/manual/contexts[context] names. If specified, only changesets tagged with one of the context names will be included
+* `dataSource` - if provided will run the script for the specified dataSource. Not needed for the default dataSource.
+
+NOTE: Note that the `contexts` and `dataSource` parameter name and value must be quoted if executed in Windows, e.g.
+[source,groovy]
+----
+grails dbm-db-doc "--contexts=<>" "--dataSource=<>"
+----
+
+NOTE: For the `dataSource` parameter, if the data source is configured as `reports` underneath the `dataSources` key in `application.[yml|groovy]`, the value should be `reports`.
+
+[source,groovy]
+----
+--dataSource=reports
+----
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Maintenance Scripts/dbm-add-migration.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Maintenance Scripts/dbm-add-migration.adoc
new file mode 100644
index 00000000000..cee4bb5e387
--- /dev/null
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Maintenance Scripts/dbm-add-migration.adoc
@@ -0,0 +1,22 @@
+===== dbm-add-migration
+
+====== Purpose
+
+Adds a template migration file to your project and to the changelog file.
+
+====== Description
+
+This script provides a template in which to place your migration behaviour code, whether
+Grails code or raw SQL.
+
+Usage:
+[source,java]
+----
+grails <> dbm-add-migration <>
+----
+
+Required arguments:
+
+* `migrationName` - The name of the migration - will be used as a filename and the default migration id.
+
+NOTE: This script only supports .groovy-style migrations at the moment.
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Maintenance Scripts/dbm-changelog-sync-sql.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Maintenance Scripts/dbm-changelog-sync-sql.adoc
new file mode 100644
index 00000000000..463bb3fdc36
--- /dev/null
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Maintenance Scripts/dbm-changelog-sync-sql.adoc
@@ -0,0 +1,37 @@
+===== dbm-changelog-sync-sql
+
+====== Purpose
+
+Writes the SQL that will mark all changes as executed in the database to STDOUT or a file.
+
+====== Description
+
+Generates the SQL statements for the Liquibase `DATABASECHANGELOG` control table.
+
+Usage:
+[source,java]
+----
+grails <> dbm-changelog-sync-sql <> --contexts=<> --defaultSchema=<> --dataSource=<>
+----
+
+Required arguments: __none__.
+
+Optional arguments:
+
+* `filename` - The path to the output file to write to. If not specified output is written to the console
+* `contexts` - A comma-delimited list of http://www.liquibase.org/manual/contexts[context] names. If specified, only changesets tagged with one of the context names will be included
+* `defaultSchema` - The default schema name to use
+* `dataSource` - if provided will run the script for the specified dataSource. Not needed for the default dataSource.
+
+NOTE: Note that the `contexts`, `defaultSchema`, and `dataSource` parameter name and value must be quoted if executed in Windows, e.g.
+[source,groovy]
+----
+grails dbm-changelog-sync "--contexts=<>" "--defaultSchema=<>" "--dataSource=<>"
+----
+
+NOTE: For the `dataSource` parameter if the data source is configured as `reports` underneath the `dataSources` key in `application.[yml|groovy]`
+the suffix of `reports` will be used as the parameter value.
+[source,groovy]
+----
+--dataSource=reports
+----
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Maintenance Scripts/dbm-changelog-sync.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Maintenance Scripts/dbm-changelog-sync.adoc
new file mode 100644
index 00000000000..f06be1c7540
--- /dev/null
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Maintenance Scripts/dbm-changelog-sync.adoc
@@ -0,0 +1,36 @@
+===== dbm-changelog-sync
+
+====== Purpose
+
+Mark all changes as executed in the database.
+
+====== Description
+
+Registers all changesets as having been run in the Liquibase control table. This is useful when the changes have already been applied, for example if you've just created a changelog from your database using the <> script.
+
+Usage:
+[source,java]
+----
+grails <> dbm-changelog-sync --contexts=<> --defaultSchema=<> --dataSource=<>
+----
+
+Required arguments: __none__.
+
+Optional arguments:
+
+* `contexts` - A comma-delimited list of http://www.liquibase.org/manual/contexts[context] names. If specified, only changesets tagged with one of the context names will be included
+* `defaultSchema` - The default schema name to use
+* `dataSource` - If provided will run the script for the specified dataSource. Not needed for the default dataSource.
+
+NOTE: Note that the `contexts`, `defaultSchema`, and `dataSource` parameter name and value must be quoted if executed in Windows, e.g.
+[source,groovy]
+----
+grails dbm-changelog-sync "--contexts=<>" "--defaultSchema=<>" "--dataSource=<>"
+----
+
+NOTE: For the `dataSource` parameter, if the data source is configured as `reports` underneath the `dataSources` key in `application.[yml|groovy]`, the value should be `reports`.
+
+[source,groovy]
+----
+--dataSource=reports
+----
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Maintenance Scripts/dbm-changelog-to-groovy.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Maintenance Scripts/dbm-changelog-to-groovy.adoc
new file mode 100644
index 00000000000..4c1fdb1ad8e
--- /dev/null
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Maintenance Scripts/dbm-changelog-to-groovy.adoc
@@ -0,0 +1,23 @@
+===== dbm-changelog-to-groovy
+
+====== Purpose
+
+Converts a Liquibase XML changelog file to a Groovy DSL file.
+
+====== Description
+
+If the Groovy file name isn't specified the name and location will be the same as the original XML file with a .groovy extension.
+
+Usage:
+[source,java]
+----
+grails <> dbm-changelog-to-groovy [xml_file_name] [groovy_file_name]
+----
+
+Required arguments:
+
+* `xml_file_name` - The name and path of the XML file to convert
+
+Optional arguments:
+
+* `groovy_file_name` - The name and path of the Groovy file
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Maintenance Scripts/dbm-clear-checksums.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Maintenance Scripts/dbm-clear-checksums.adoc
new file mode 100644
index 00000000000..e5d7caf76a1
--- /dev/null
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Maintenance Scripts/dbm-clear-checksums.adoc
@@ -0,0 +1,32 @@
+===== dbm-clear-checksums
+
+====== Purpose
+
+Removes current checksums from database. On next run checksums will be recomputed.
+
+====== Description
+
+Usage:
+[source,java]
+----
+grails <> dbm-clear-checksums --dataSource=<>
+----
+
+Required arguments: __none__.
+
+Optional arguments:
+
+* `dataSource` - if provided will run the script for the specified dataSource. Not needed for the default dataSource.
+
+NOTE: Note that the `dataSource` parameter name and value must be quoted if executed in Windows, e.g.
+[source,groovy]
+----
+grails dbm-clear-checksums "--dataSource=<>"
+----
+
+NOTE: For the `dataSource` parameter, if the data source is configured as `reports` underneath the `dataSources` key in `application.[yml|groovy]`, the value should be `reports`.
+
+[source,groovy]
+----
+--dataSource=reports
+----
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Maintenance Scripts/dbm-create-changelog.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Maintenance Scripts/dbm-create-changelog.adoc
new file mode 100644
index 00000000000..3214b53ac5c
--- /dev/null
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Maintenance Scripts/dbm-create-changelog.adoc
@@ -0,0 +1,36 @@
+===== dbm-create-changelog
+
+====== Purpose
+
+Creates an empty changelog file.
+
+====== Description
+
+Creates a new empty file instead of generating the file from the database (using <>) or your GORM classes (using <>).
+
+Usage:
+[source,java]
+----
+grails <> dbm-create-changelog <> --dataSource=<>
+----
+
+Required arguments:
+
+* `filename` - The path to the output file to write to
+
+Optional arguments:
+
+* `dataSource` - if provided will run the script for the specified dataSource creating a file named `changelog-<>.groovy` if a `filename` is not given. Not needed for the default dataSource.
+
+NOTE: Note that the `dataSource` parameter name and value must be quoted if executed in Windows, e.g.
+[source,groovy]
+----
+grails dbm-create-changelog "--dataSource=<>"
+----
+
+NOTE: For the `dataSource` parameter, if the data source is configured as `reports` underneath the `dataSources` key in `application.[yml|groovy]`, the value should be `reports`.
+
+[source,groovy]
+----
+--dataSource=reports
+----
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Maintenance Scripts/dbm-drop-all.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Maintenance Scripts/dbm-drop-all.adoc
new file mode 100644
index 00000000000..a8f421a29d7
--- /dev/null
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Maintenance Scripts/dbm-drop-all.adoc
@@ -0,0 +1,34 @@
+===== dbm-drop-all
+
+====== Purpose
+
+Drops all database objects owned by the user.
+
+====== Description
+
+Usage:
+[source,java]
+----
+grails <> dbm-drop-all <> --defaultSchema=<> --dataSource=<>
+----
+
+Required arguments: __none__.
+
+Optional arguments:
+
+* `schemaNames` - A comma-delimited list of schema names to use
+* `defaultSchema` - The default schema name to use if the `schemaNames` parameter isn't present
+* `dataSource` - if provided will run the script for the specified dataSource. Not needed for the default dataSource.
+
+NOTE: Note that the `defaultSchema` and `dataSource` parameter name and value must be quoted if executed in Windows, e.g.
+[source,groovy]
+----
+grails dbm-drop-all "--defaultSchema=<>" "--dataSource=<>"
+----
+
+NOTE: For the `dataSource` parameter, if the data source is configured as `reports` underneath the `dataSources` key in `application.[yml|groovy]`, the value should be `reports`.
+
+[source,groovy]
+----
+--dataSource=reports
+----
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Maintenance Scripts/dbm-list-locks.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Maintenance Scripts/dbm-list-locks.adoc
new file mode 100644
index 00000000000..49f76f4f70e
--- /dev/null
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Maintenance Scripts/dbm-list-locks.adoc
@@ -0,0 +1,34 @@
+===== dbm-list-locks
+
+====== Purpose
+
+Lists who currently has locks on the database changelog to STDOUT or a file.
+
+====== Description
+
+Usage:
+[source,java]
+----
+grails <> dbm-list-locks <> --defaultSchema=<> --dataSource=<>
+----
+
+Required arguments: __none__.
+
+Optional arguments:
+
+* `filename` - The path to the output file to write to. If not specified output is written to the console
+* `defaultSchema` - The default schema name to use
+* `dataSource` - if provided will run the script for the specified dataSource. Not needed for the default dataSource.
+
+NOTE: Note that the `defaultSchema` and `dataSource` parameter name and value must be quoted if executed in Windows, e.g.
+[source,groovy]
+----
+grails dbm-list-locks "--defaultSchema=<>" "--dataSource=<>"
+----
+
+NOTE: For the `dataSource` parameter, if the data source is configured as `reports` underneath the `dataSources` key in `application.[yml|groovy]`, the value should be `reports`.
+
+[source,groovy]
+----
+--dataSource=reports
+----
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Maintenance Scripts/dbm-list-tags.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Maintenance Scripts/dbm-list-tags.adoc
new file mode 100644
index 00000000000..e6057665f4c
--- /dev/null
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Maintenance Scripts/dbm-list-tags.adoc
@@ -0,0 +1,27 @@
+===== dbm-list-tags
+
+====== Purpose
+
+Lists the tags in the current database.
+
+====== Description
+
+Usage:
+[source,java]
+----
+grails <> dbm-list-tags --defaultSchema=<>
+----
+
+Required arguments:
+
+Required arguments: __none__.
+
+Optional arguments:
+
+* `defaultSchema` - The default schema name to use
+
+NOTE: Note that the `defaultSchema` parameter name and value must be quoted if executed in Windows, e.g.
+[source,groovy]
+----
+grails dbm-tag "--defaultSchema=<>"
+----
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Maintenance Scripts/dbm-mark-next-changeset-ran.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Maintenance Scripts/dbm-mark-next-changeset-ran.adoc
new file mode 100644
index 00000000000..1b9d9481292
--- /dev/null
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Maintenance Scripts/dbm-mark-next-changeset-ran.adoc
@@ -0,0 +1,37 @@
+===== dbm-mark-next-changeset-ran
+
+====== Purpose
+
+Mark the next change set as executed in the database.
+
+====== Description
+
+If a filename is specified, writes the SQL that will perform the update that file but doesn't update.
+
+Usage:
+[source,java]
+----
+grails <> dbm-mark-next-changeset-ran <> --contexts=<> --defaultSchema=<> --dataSource=<>
+----
+
+Required arguments: __none__.
+
+Optional arguments:
+
+* `filename` - The path to the output file to write to
+* `contexts` - A comma-delimited list of http://www.liquibase.org/manual/contexts[context] names. If specified, only changesets tagged with one of the context names will be run
+* `defaultSchema` - The default schema name to use
+* `dataSource` - if provided will run the script for the specified dataSource. Not needed for the default dataSource.
+
+NOTE: Note that the `contexts`, `defaultSchema`, and `dataSource` parameter name and value must be quoted if executed in Windows, e.g.
+[source,groovy]
+----
+grails dbm-mark-next-changeset-ran "--contexts=<>" "--defaultSchema=<>" "--dataSource=<>"
+----
+
+NOTE: For the `dataSource` parameter, if the data source is configured as `reports` underneath the `dataSources` key in `application.[yml|groovy]`, the value should be `reports`.
+
+[source,groovy]
+----
+--dataSource=reports
+----
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Maintenance Scripts/dbm-release-locks.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Maintenance Scripts/dbm-release-locks.adoc
new file mode 100644
index 00000000000..a303ccea3e6
--- /dev/null
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Maintenance Scripts/dbm-release-locks.adoc
@@ -0,0 +1,33 @@
+===== dbm-release-locks
+
+====== Purpose
+
+Releases all locks on the database changelog.
+
+====== Description
+
+Usage:
+[source,java]
+----
+grails <> dbm-release-locks --defaultSchema=<> --dataSource=<>
+----
+
+Required arguments: __none__.
+
+Optional arguments:
+
+* `defaultSchema` - The default schema name to use
+* `dataSource` - if provided will run the script for the specified dataSource. Not needed for the default dataSource.
+
+NOTE: Note that the `defaultSchema` and `dataSource` parameter name and value must be quoted if executed in Windows, e.g.
+[source,groovy]
+----
+grails dbm-release-locks "--defaultSchema=<>" "--dataSource=<>"
+----
+
+NOTE: For the `dataSource` parameter, if the data source is configured as `reports` underneath the `dataSources` key in `application.[yml|groovy]`, the value should be `reports`.
+
+[source,groovy]
+----
+--dataSource=reports
+----
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Maintenance Scripts/dbm-status.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Maintenance Scripts/dbm-status.adoc
new file mode 100644
index 00000000000..167556516ae
--- /dev/null
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Maintenance Scripts/dbm-status.adoc
@@ -0,0 +1,36 @@
+===== dbm-status
+
+====== Purpose
+
+Outputs count or list of unrun change sets to STDOUT or a file.
+
+====== Description
+
+Usage:
+[source,java]
+----
+grails <> dbm-status <> --verbose=<> --contexts=<> --defaultSchema=<> --dataSource=<>
+----
+
+Required arguments: __none__.
+
+Optional arguments:
+
+* `filename` - The path to the output file to write to. If not specified output is written to the console
+* `verbose` - If `true` (the default) the changesets are listed; if `false` only the count is displayed
+* `contexts` - A comma-delimited list of http://www.liquibase.org/manual/contexts[context] names. If specified, only changesets tagged with one of the context names will be included
+* `defaultSchema` - The default schema name to use
+* `dataSource` - if provided will run the script for the specified dataSource. Not needed for the default dataSource.
+
+NOTE: Note that the `verbose`, `contexts`, `defaultSchema` and `dataSource` parameter name and value must be quoted if executed in Windows, e.g.
+[source,groovy]
+----
+grails dbm-status "--verbose=<>" "--contexts=<>" "--defaultSchema=<>" "--dataSource=<>"
+----
+
+NOTE: For the `dataSource` parameter, if the data source is configured as `reports` underneath the `dataSources` key in `application.[yml|groovy]`, the value should be `reports`.
+
+[source,groovy]
+----
+--dataSource=reports
+----
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Maintenance Scripts/dbm-tag.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Maintenance Scripts/dbm-tag.adoc
new file mode 100644
index 00000000000..c1db05471dd
--- /dev/null
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Maintenance Scripts/dbm-tag.adoc
@@ -0,0 +1,37 @@
+===== dbm-tag
+
+====== Purpose
+
+Adds a tag to mark the current database state.
+
+====== Description
+
+Useful for future rollbacks to a specific tag (e.g. using the <> script).
+
+Usage:
+[source,java]
+----
+grails <> dbm-tag <> --defaultSchema=<> --dataSource=<>
+----
+
+Required arguments:
+
+* `tagName` - The name of the tag to use
+
+Optional arguments:
+
+* `defaultSchema` - The default schema name to use
+* `dataSource` - if provided will run the script for the specified dataSource. Not needed for the default dataSource.
+
+NOTE: Note that the `defaultSchema` and `dataSource` parameter name and value must be quoted if executed in Windows, e.g.
+[source,groovy]
+----
+grails dbm-tag "--defaultSchema=<>" "--dataSource=<>"
+----
+
+NOTE: For the `dataSource` parameter, if the data source is configured as `reports` underneath the `dataSources` key in `application.[yml|groovy]`, the value should be `reports`.
+
+[source,groovy]
+----
+--dataSource=reports
+----
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Maintenance Scripts/dbm-validate.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Maintenance Scripts/dbm-validate.adoc
new file mode 100644
index 00000000000..c64bb1398eb
--- /dev/null
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Maintenance Scripts/dbm-validate.adoc
@@ -0,0 +1,34 @@
+===== dbm-validate
+
+====== Purpose
+
+Checks the changelog for errors.
+
+====== Description
+
+Prints any validation messages to the console.
+
+Usage:
+[source,java]
+----
+grails <> dbm-validate --dataSource=<>
+----
+
+Required arguments: __none__.
+
+Optional arguments:
+
+* `dataSource` - if provided will run the script for the specified dataSource. Not needed for the default dataSource.
+
+NOTE: Note that the `dataSource` parameter name and value must be quoted if executed in Windows, e.g.
+[source,groovy]
+----
+grails dbm-validate "--dataSource=<>"
+----
+
+NOTE: For the `dataSource` parameter, if the data source is configured as `reports` underneath the `dataSources` key in `application.[yml|groovy]`, the value should be `reports`.
+
+[source,groovy]
+----
+--dataSource=reports
+----
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Rollback Scripts/dbm-future-rollback-sql.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Rollback Scripts/dbm-future-rollback-sql.adoc
new file mode 100644
index 00000000000..22f7ed42819
--- /dev/null
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Rollback Scripts/dbm-future-rollback-sql.adoc
@@ -0,0 +1,35 @@
+===== dbm-future-rollback-sql
+
+====== Purpose
+
+Writes SQL to roll back the database to the current state after the changes in the changeslog have been applied to STDOUT or a file.
+
+====== Description
+
+Usage:
+[source,java]
+----
+grails <> dbm-future-rollback-sql <> --contexts=<> --defaultSchema=<> --dataSource=<>
+----
+
+Required arguments: _none_ .
+
+Optional arguments:
+
+* `filename` - The path to the output file to write to. If not specified output is written to the console
+* `contexts` - A comma-delimited list of http://www.liquibase.org/manual/contexts[context] names. If specified, only changesets tagged with one of the context names will be included
+* `defaultSchema` - The default schema name to use
+* `dataSource` - if provided will run the script for the specified dataSource. Not needed for the default dataSource.
+
+NOTE: Note that the `contexts`, `defaultSchema`, and `dataSource` parameter name and value must be quoted if executed in Windows, e.g.
+[source,groovy]
+----
+grails dbm-future-rollback-sql "--contexts=<>" "--defaultSchema=<>" "--dataSource=<>"
+----
+
+NOTE: For the `dataSource` parameter, if the data source is configured as `reports` underneath the `dataSources` key in `application.[yml|groovy]`, the value should be `reports`.
+
+[source,groovy]
+----
+--dataSource=reports
+----
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Rollback Scripts/dbm-generate-changelog.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Rollback Scripts/dbm-generate-changelog.adoc
new file mode 100644
index 00000000000..55a7c69389a
--- /dev/null
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Rollback Scripts/dbm-generate-changelog.adoc
@@ -0,0 +1,42 @@
+===== dbm-generate-changelog
+
+====== Purpose
+
+Generates an initial changelog XML or Groovy DSL file from the database.
+
+====== Description
+
+Creates a Groovy DSL file if the filename is specified and it ends with .groovy. If another extension is specified it creates a standard Liquibase XML file, and if no filename is specified it writes to the console.
+
+File are written to the migrations folder, so specify the filename relative to the migrations folder (`grails-app/migrations` by default).
+
+Executes against the database configured in `application.[yml|groovy]` for the current environment (defaults to `dev`).
+
+Usage:
+[source,java]
+----
+grails <> dbm-generate-changelog <> --diffTypes=<> --defaultSchema=<> --dataSource=<> --add
+----
+
+Required arguments: _none_ .
+
+Optional arguments:
+
+* `filename` - The path to the output file to write to. If not specified output is written to the console
+* `diffTypes` - A comma-delimited list of change types to include - see http://www.liquibase.org/manual/diff#controlling_checks_since_1.8[the documentation] for what types are available
+* `defaultSchema` - The default schema name to use
+* `dataSource` - if provided will run the script for the specified dataSource. Not needed for the default dataSource.
+* `add` - if specified add an include in the root changelog file referencing the new file
+
+NOTE: Note that the `diffTypes`, `defaultSchema`, and `dataSource` parameter name and value must be quoted if executed in Windows, e.g.
+[source,groovy]
+----
+grails dbm-generate-changelog "--diffTypes=<>" "--defaultSchema=<>" "--dataSource=<>"
+----
+
+NOTE: For the `dataSource` parameter, if the data source is configured as `reports` underneath the `dataSources` key in `application.[yml|groovy]`, the value should be `reports`.
+
+[source,groovy]
+----
+--dataSource=reports
+----
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Rollback Scripts/dbm-generate-gorm-changelog.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Rollback Scripts/dbm-generate-gorm-changelog.adoc
new file mode 100644
index 00000000000..f7c2cdba500
--- /dev/null
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Rollback Scripts/dbm-generate-gorm-changelog.adoc
@@ -0,0 +1,40 @@
+===== dbm-generate-gorm-changelog
+
+====== Purpose
+
+Generates an initial changelog XML or Groovy DSL file from current GORM classes.
+
+====== Description
+
+Creates a Groovy DSL file if the filename is specified and it ends with .groovy. If another extension is specified it creates a standard Liquibase XML file, and if no filename is specified it writes to the console.
+
+File are written to the migrations folder, so specify the filename relative to the migrations folder (`grails-app/migrations` by default).
+
+Executes against the database configured in `DataSource.groovy` for the current environment (defaults to `dev`).
+
+Usage:
+[source,java]
+----
+grails <> dbm-generate-gorm-changelog <> --dataSource=<> --add
+----
+
+Required arguments: _none_ .
+
+Optional arguments:
+
+* `filename` - The path to the output file to write to. If not specified output is written to the console
+* `dataSource` - if provided will run the script for the specified dataSource. Not needed for the default dataSource.
+* `add` - if specified add an include in the root changelog file referencing the new file
+
+NOTE: Note that the `dataSource` parameter name and value must be quoted if executed in Windows, e.g.
+[source,groovy]
+----
+grails dbm-generate-gorm-changelog "--dataSource=<>"
+----
+
+NOTE: For the `dataSource` parameter, if the data source is configured as `reports` underneath the `dataSources` key in `application.[yml|groovy]`, the value should be `reports`.
+
+[source,groovy]
+----
+--dataSource=reports
+----
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Rollback Scripts/dbm-rollback-count-sql.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Rollback Scripts/dbm-rollback-count-sql.adoc
new file mode 100644
index 00000000000..ac6379a699a
--- /dev/null
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Rollback Scripts/dbm-rollback-count-sql.adoc
@@ -0,0 +1,38 @@
+===== dbm-rollback-count-sql
+
+====== Purpose
+
+Writes the SQL to roll back the specified number of change sets to STDOUT or a file.
+
+====== Description
+
+
+Usage:
+[source,java]
+----
+grails <> dbm-rollback-count-sql <> <> --contexts=<> --defaultSchema=<> --dataSource=<>
+----
+
+Required arguments:
+
+* `number` - The number of changesets to roll back
+
+Optional arguments:
+
+* `filename` - The path to the output file to write to. If not specified output is written to the console
+* `contexts` - A comma-delimited list of http://www.liquibase.org/manual/contexts[context] names. If specified, only changesets tagged with one of the context names will be included
+* `defaultSchema` - The default schema name to use
+* `dataSource` - if provided will run the script for the specified dataSource. Not needed for the default dataSource.
+
+NOTE: Note that the `contexts`, `defaultSchema`, and `dataSource` parameter name and value must be quoted if executed in Windows, e.g.
+[source,groovy]
+----
+grails dbm-rollback-count-sql "--contexts=<>" "--defaultSchema=<>" "--dataSource=<>"
+----
+
+NOTE: For the `dataSource` parameter, if the data source is configured as `reports` underneath the `dataSources` key in `application.[yml|groovy]`, the value should be `reports`.
+
+[source,groovy]
+----
+--dataSource=reports
+----
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Rollback Scripts/dbm-rollback-count.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Rollback Scripts/dbm-rollback-count.adoc
new file mode 100644
index 00000000000..5e3169621f7
--- /dev/null
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Rollback Scripts/dbm-rollback-count.adoc
@@ -0,0 +1,37 @@
+===== dbm-rollback-count
+
+====== Purpose
+
+Rolls back the specified number of change sets
+
+====== Description
+
+
+Usage:
+[source,java]
+----
+grails <> dbm-rollback-count <> --contexts=<> --defaultSchema=<> --dataSource=<>
+----
+
+Required arguments:
+
+* `number` - The number of changesets to roll back
+
+Optional arguments:
+
+* `contexts` - A comma-delimited list of http://www.liquibase.org/manual/contexts[context] names. If specified, only changesets tagged with one of the context names will be run
+* `defaultSchema` - The default schema name to use
+* `dataSource` - if provided will run the script for the specified dataSource. Not needed for the default dataSource.
+
+NOTE: Note that the `contexts`, `defaultSchema`, and `dataSource` parameter name and value must be quoted if executed in Windows, e.g.
+[source,groovy]
+----
+grails dbm-rollback-count "--contexts=<>" "--defaultSchema=<>" "--dataSource=<>"
+----
+
+NOTE: For the `dataSource` parameter, if the data source is configured as `reports` underneath the `dataSources` key in `application.[yml|groovy]`, the value should be `reports`.
+
+[source,groovy]
+----
+--dataSource=reports
+----
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Rollback Scripts/dbm-rollback-sql.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Rollback Scripts/dbm-rollback-sql.adoc
new file mode 100644
index 00000000000..138fc653067
--- /dev/null
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Rollback Scripts/dbm-rollback-sql.adoc
@@ -0,0 +1,39 @@
+===== dbm-rollback-sql
+
+====== Purpose
+
+Writes SQL to roll back the database to the state it was in when the tag was applied to STDOUT or a file.
+
+====== Description
+
+Requires that the named tag exists. You can create tags with the <> script.
+
+Usage:
+[source,java]
+----
+grails <> dbm-rollback-sql <> <> --contexts=<> --defaultSchema=<> --dataSource=<>
+----
+
+Required arguments:
+
+* `tagName` - The name of the tag to use
+
+Optional arguments:
+
+* `filename` - The path to the output file to write to. If not specified output is written to the console
+* `contexts` - A comma-delimited list of http://www.liquibase.org/manual/contexts[context] names. If specified, only changesets tagged with one of the context names will be included
+* `defaultSchema` - The default schema name to use
+* `dataSource` - if provided will run the script for the specified dataSource. Not needed for the default dataSource.
+
+NOTE: Note that the `contexts`, `defaultSchema`, and `dataSource` parameter name and value must be quoted if executed in Windows, e.g.
+[source,groovy]
+----
+grails dbm-rollback-sql "--contexts=<>" "--defaultSchema=<>" --dataSource=<>
+----
+
+NOTE: For the `dataSource` parameter, if the data source is configured as `reports` underneath the `dataSources` key in `application.[yml|groovy]`, the value should be `reports`.
+
+[source,groovy]
+----
+--dataSource=reports
+----
diff --git a/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Rollback Scripts/dbm-rollback-to-date-sql.adoc b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Rollback Scripts/dbm-rollback-to-date-sql.adoc
new file mode 100644
index 00000000000..bbb30f64bfd
--- /dev/null
+++ b/grails-data-hibernate5/docs/src/docs/asciidoc/databaseMigration/ref/Rollback Scripts/dbm-rollback-to-date-sql.adoc
@@ -0,0 +1,41 @@
+===== dbm-rollback-to-date-sql
+
+====== Purpose
+
+Writes SQL to roll back the database to the state it was in at the given date/time to STDOUT or a file.
+
+====== Description
+
+You can specify just the date, or the date and time. The date format must be `yyyy-MM-dd` and the time format must be `HH:mm:ss`.
+
+Usage:
+[source,java]
+----
+grails <> dbm-rollback-to-date-sql <> <