From fb85fff3e9af631b5e1251bd43de93ef09388d0c Mon Sep 17 00:00:00 2001 From: Michael Yan Date: Thu, 29 Jan 2026 00:19:23 +0800 Subject: [PATCH 1/4] Fix missing return false --- .../databasemigration/generator/MigrationGenerator.groovy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/grace-plugin-database-migration/src/main/groovy/org/grails/plugins/databasemigration/generator/MigrationGenerator.groovy b/grace-plugin-database-migration/src/main/groovy/org/grails/plugins/databasemigration/generator/MigrationGenerator.groovy index 9fb5c54eff..c0aa1f378a 100644 --- a/grace-plugin-database-migration/src/main/groovy/org/grails/plugins/databasemigration/generator/MigrationGenerator.groovy +++ b/grace-plugin-database-migration/src/main/groovy/org/grails/plugins/databasemigration/generator/MigrationGenerator.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2022-2025 the original author or authors. + * Copyright 2022-2026 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,7 +34,7 @@ class MigrationGenerator extends AbstractGenerator { boolean generate() { String[] args = commandLine.remainingArgs.toArray(new String[0]) if (args.size() < 2) { - return + return false } boolean overwrite = commandLine.hasOption('force') || commandLine.hasOption('f') From 479518c0b0fa309eb2d0c34ebd7b841a83305582 Mon Sep 17 00:00:00 2001 From: Michael Yan Date: Thu, 29 Jan 2026 00:33:22 +0800 Subject: [PATCH 2/4] Use the current datasource's username as the author of generated changeset --- .../databasemigration/generator/MigrationGenerator.groovy | 5 +++++ .../templates/generators/migration/Migration.groovy.tpl | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/grace-plugin-database-migration/src/main/groovy/org/grails/plugins/databasemigration/generator/MigrationGenerator.groovy b/grace-plugin-database-migration/src/main/groovy/org/grails/plugins/databasemigration/generator/MigrationGenerator.groovy index c0aa1f378a..5d4616f397 100644 --- a/grace-plugin-database-migration/src/main/groovy/org/grails/plugins/databasemigration/generator/MigrationGenerator.groovy +++ b/grace-plugin-database-migration/src/main/groovy/org/grails/plugins/databasemigration/generator/MigrationGenerator.groovy @@ -47,6 +47,7 @@ class MigrationGenerator extends AbstractGenerator { File changelogFile = new File(baseDir, 'db/migrations/changelog.groovy') Map model = new HashMap<>() model.put('id', System.currentTimeMillis().toString()) + model.put('author', getAuthor()) String migrationFile = 'db/migrations/' + migrationFileName createFile('Migration.groovy.tpl', migrationFile, model, overwrite) @@ -60,4 +61,8 @@ class MigrationGenerator extends AbstractGenerator { true } + private String getAuthor() { + loadApplicationConfig().getProperty('dataSource.username') ?: System.getProperty('user.name') + } + } diff --git a/grace-plugin-database-migration/src/main/resources/META-INF/templates/generators/migration/Migration.groovy.tpl b/grace-plugin-database-migration/src/main/resources/META-INF/templates/generators/migration/Migration.groovy.tpl index d1d5838dd3..a5d4f896d5 100644 --- a/grace-plugin-database-migration/src/main/resources/META-INF/templates/generators/migration/Migration.groovy.tpl +++ b/grace-plugin-database-migration/src/main/resources/META-INF/templates/generators/migration/Migration.groovy.tpl @@ -1,6 +1,6 @@ databaseChangeLog = { - changeSet(author: "dbm", id: "$id-1") { + changeSet(author: "$author", id: "$id-1") { } From 7092f4fe1e12fabea908f4afe3edf9b10074f92b Mon Sep 17 00:00:00 2001 From: Michael Yan Date: Thu, 29 Jan 2026 10:03:57 +0800 Subject: [PATCH 3/4] Support snake_case migration name --- .../databasemigration/generator/MigrationGenerator.groovy | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/grace-plugin-database-migration/src/main/groovy/org/grails/plugins/databasemigration/generator/MigrationGenerator.groovy b/grace-plugin-database-migration/src/main/groovy/org/grails/plugins/databasemigration/generator/MigrationGenerator.groovy index 5d4616f397..9b1b4132f3 100644 --- a/grace-plugin-database-migration/src/main/groovy/org/grails/plugins/databasemigration/generator/MigrationGenerator.groovy +++ b/grace-plugin-database-migration/src/main/groovy/org/grails/plugins/databasemigration/generator/MigrationGenerator.groovy @@ -22,6 +22,7 @@ import java.time.format.DateTimeFormatter import groovy.transform.CompileStatic import grails.cli.generator.AbstractGenerator +import grails.util.GrailsNameUtils /** * @author Michael Yan @@ -41,7 +42,7 @@ class MigrationGenerator extends AbstractGenerator { ZonedDateTime now = ZonedDateTime.now(ZoneId.of('UTC')) String migrationNumber = now.format(DateTimeFormatter.ofPattern('yyyyMMddHHmmss')) - String migrationName = args[1].uncapitalize() + String migrationName = GrailsNameUtils.getSnakeCaseName(GrailsNameUtils.getClassNameRepresentation(args[1])) String migrationFileName = [migrationNumber, migrationName].join('_') + '.groovy' File changelogFile = new File(baseDir, 'db/migrations/changelog.groovy') From 008cdeefdb5dbf3584d48ac3ae09c4509052d292 Mon Sep 17 00:00:00 2001 From: Michael Yan Date: Thu, 29 Jan 2026 23:51:39 +0800 Subject: [PATCH 4/4] Enhance `MigrationGenerator` - Provide various ways to create migrations based on conventions and additional arguments. --- .../generator/MigrationGenerator.groovy | 59 ++++++++++++++++++- .../generators/migration/Migration.groovy.tpl | 20 ++++++- 2 files changed, 77 insertions(+), 2 deletions(-) diff --git a/grace-plugin-database-migration/src/main/groovy/org/grails/plugins/databasemigration/generator/MigrationGenerator.groovy b/grace-plugin-database-migration/src/main/groovy/org/grails/plugins/databasemigration/generator/MigrationGenerator.groovy index 9b1b4132f3..288149972c 100644 --- a/grace-plugin-database-migration/src/main/groovy/org/grails/plugins/databasemigration/generator/MigrationGenerator.groovy +++ b/grace-plugin-database-migration/src/main/groovy/org/grails/plugins/databasemigration/generator/MigrationGenerator.groovy @@ -18,7 +18,10 @@ package org.grails.plugins.databasemigration.generator import java.time.ZonedDateTime import java.time.ZoneId import java.time.format.DateTimeFormatter +import java.util.regex.Matcher +import java.util.regex.Pattern +import groovy.transform.CompileDynamic import groovy.transform.CompileStatic import grails.cli.generator.AbstractGenerator @@ -31,10 +34,34 @@ import grails.util.GrailsNameUtils @CompileStatic class MigrationGenerator extends AbstractGenerator { + private static final Pattern ADD = Pattern.compile("^(add)_.*_to_(.*)") + private static final Pattern CREATE = Pattern.compile("^(create)_(.+)") + private static final Pattern REMOVE = Pattern.compile("^(remove)_.*?_from_(.*)") + private static final Pattern JOIN = Pattern.compile(".*_(join)_table_(.*)") + private static final Map DATA_TYPES = [ + 'string': 'varchar(50)', + 'String': 'varchar(50)', + 'int': 'int', + 'integer': 'int', + 'Integer': 'int', + 'long': 'bigint', + 'Long': 'bigint', + 'date': 'datetime', + 'boolean': 'boolean', + 'Boolean': 'boolean' + ] + @Override boolean generate() { String[] args = commandLine.remainingArgs.toArray(new String[0]) if (args.size() < 2) { + console.error('Missing the name of migration file!') + return false + } + + String filename = args[1] + if (!(filename ==~ /^[_a-z0-9]+$/)) { + console.error("Illegal name for migration file: $filename.") return false } @@ -42,13 +69,32 @@ class MigrationGenerator extends AbstractGenerator { ZonedDateTime now = ZonedDateTime.now(ZoneId.of('UTC')) String migrationNumber = now.format(DateTimeFormatter.ofPattern('yyyyMMddHHmmss')) - String migrationName = GrailsNameUtils.getSnakeCaseName(GrailsNameUtils.getClassNameRepresentation(args[1])) + String migrationName = GrailsNameUtils.getSnakeCaseName(GrailsNameUtils.getClassNameRepresentation(filename)) String migrationFileName = [migrationNumber, migrationName].join('_') + '.groovy' File changelogFile = new File(baseDir, 'db/migrations/changelog.groovy') Map model = new HashMap<>() model.put('id', System.currentTimeMillis().toString()) model.put('author', getAuthor()) + List matches = getTableAndActionName(migrationName) + if (matches) { + String tableName = matches[1] + String migrationAction = matches[0] + model.put('tableName', tableName) + model.put('migrationAction', migrationAction) + if (migrationAction == 'join') { + model.put('joinTables', args.size() > 3 ? args[2..-1] : tableName.toLowerCase().split('_')) + } + else { + Map tableColumns = new LinkedHashMap<>() + String[] columns = (args.size() >= 3 ? args[2..-1] : []) as String[] + columns.each { String item -> + String[] attr = (item.contains(':') ? item.split(':') : [item, 'string']) as String[] + tableColumns[attr[0]] = attr[1] + } + model['tableColumns'] = tableColumns + } + } String migrationFile = 'db/migrations/' + migrationFileName createFile('Migration.groovy.tpl', migrationFile, model, overwrite) @@ -66,4 +112,15 @@ class MigrationGenerator extends AbstractGenerator { loadApplicationConfig().getProperty('dataSource.username') ?: System.getProperty('user.name') } + @CompileDynamic + private List getTableAndActionName(String filename) { + for (Pattern it in [ADD, REMOVE, JOIN, CREATE]) { + Matcher matcher = it.matcher(filename) + if (matcher.matches()) { + return matcher[0][1..-1] + } + } + [] + } + } diff --git a/grace-plugin-database-migration/src/main/resources/META-INF/templates/generators/migration/Migration.groovy.tpl b/grace-plugin-database-migration/src/main/resources/META-INF/templates/generators/migration/Migration.groovy.tpl index a5d4f896d5..03bf171f39 100644 --- a/grace-plugin-database-migration/src/main/resources/META-INF/templates/generators/migration/Migration.groovy.tpl +++ b/grace-plugin-database-migration/src/main/resources/META-INF/templates/generators/migration/Migration.groovy.tpl @@ -1,7 +1,25 @@ databaseChangeLog = { changeSet(author: "$author", id: "$id-1") { - + <% if (migrationAction == 'create') { %>createTable(tableName: "$tableName") { +<% tableColumns.each { name, type -> %> + column(name: "$name", type: "$type") +<% } %> + }<% } else if (migrationAction == 'add') { %>addColumn(tableName: "$tableName") { +<% tableColumns.each { name, type -> %> + column(name: "$name", type: "$type") +<% } %> + }<% } else if (migrationAction == 'remove') { %>dropColumn(tableName: "$tableName") { +<% tableColumns.each { name, type -> %> + column(name: "$name") +<% } %> + }<% } else if (migrationAction == 'join') { %>createTable(tableName: "$tableName") { +<% joinTables.each { t -> %> + column(name: "${t}_id", type: "BIGINT") { + constraints(referencedTableName: "$t", referencedColumnNames: "id", foreignKeyName: "FK_${t}_id", nullable: "false") + } +<% } %> + }<% } %> } }