Skip to content

feat: EXPOSED-755 Introduce Exposed Migration Gradle Plugin #2451

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

nomisRev
Copy link

@nomisRev nomisRev commented Mar 25, 2025

Introduces the Exposed Migration Gradle Plugin to simplify SQL migration generation using Exposed table definitions. The plugin supports TestContainers for database testing, Flyway for integration, and a highly configurable extension. Includes unit tests for plugin functionality and task behavior.

Description

Summary of the change: Introduces the Exposed Migration Gradle Plugin to simplify SQL migration generation using Exposed table definitions. The plugin supports TestContainers for database testing, Flyway for integration, and a highly configurable extension. Includes unit tests for plugin functionality and task behavior.

Detailed description:

  • What: This PR introduces a new module with a Gradle plugin that wraps the migration module.
  • Why: Automatic migration script generation through Gradle with 0 boilerplate.
  • How: New functionality, no impact on the existing codebase or features.

Type of Change

Please mark the relevant options with an "X":

  • New feature

Affected databases:

  • MariaDB
  • Mysql5
  • Mysql8
  • Oracle
  • Postgres
  • SqlServer
  • H2
  • SQLite

Checklist

  • Unit tests are in place
  • The build is green (including the Detekt check)
  • All public methods affected by my PR has up to date API docs
  • Documentation for my change is up to date

Related Issues

EXPOSED-755

@nomisRev nomisRev changed the title Add Exposed Migration Gradle Plugin feat: EXPOSED-165 Introduce Exposed Migration Gradle Plugin Mar 26, 2025
Introduces the Exposed Migration Gradle Plugin to simplify SQL migration generation using Exposed table definitions. The plugin supports TestContainers for database testing, Flyway for integration, and a highly configurable extension. Includes unit tests for plugin functionality and task behavior.
@nomisRev nomisRev force-pushed the migration-gradle-plugin branch from 834b96a to 6da76a3 Compare March 26, 2025 11:33
@bog-walk bog-walk requested review from bog-walk and e5l March 26, 2025 13:04
Copy link
Member

@e5l e5l left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @nomisRev, thank you for the PR!
Looks nice!

Could you drop the migration word from the plugin name? I think we also can have the same versioning and include bom, but we can do this later

…to generalize for adding future functionality.
@bog-walk bog-walk changed the title feat: EXPOSED-165 Introduce Exposed Migration Gradle Plugin feat: EXPOSED-755 Introduce Exposed Migration Gradle Plugin Apr 1, 2025
Copy link
Member

@bog-walk bog-walk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks so much for putting all the work into this @nomisRev ! Left some comments.

Two extra thoughts I had when reviewing:

  • I saw that the original module name was changed to drop 'migration'. Is the plan to do the same for the package -> 'org.jetbrains.exposed.plugin'? Or swap the order?
  • We might need to adjust the tests. Exposed is setup to run all tests on at least 1 db, so I believe that all your tests are being ignored right now by test tasks. And looking on TC, I can't find any. Depending on the approach, it most likely needs to be setup at minimum like spring-transaction (to ensure all new tests run, but only once & only during H2_V2 task):
    • testImplementation() needs to be swapped out
    • Then in TestDbDsl.kt/TestDb, the new module needs to be filtered out from any task that isn't running H2_V2. Similar to how val ignoresSpringTests is handled.

Comment on lines +141 to +148
// Complex ALTER TABLE with multiple constraints
assertEquals(
"ALTER_TABLE_USERS_ADD_CONSTRAINT_USERS_EMAIL_UNIQUE",
"""
ALTER TABLE users
ADD CONSTRAINT users_email_unique UNIQUE (email)
""".trimIndent().statementToFileName()
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just mentioning that this assert is identical to the simple one in test ALTER TABLE statements above, in case you meant to include more complex constraints like the comment states.

Comment on lines +46 to +50
// CREATE INDEX statements
normalizedStatement.startsWith("CREATE INDEX", ignoreCase = true) -> {
val indexInfo = extractIndexInfo(normalizedStatement)
"CREATE_INDEX_$indexInfo"
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some edge cases to mention:

If the goal is to cover the base DDL syntax generated out-of-the-box by Exposed, there are 2 cases that won't be caught here, resulting in CREATE_STATEMENT_hash names.

  1. Unique index + function/expression generates CREATE UNIQUE INDEX syntax in supported db (PostgreSQL, Oracle, MySQL, SQL Server)
  2. Typed index generates for example CREATE CLUSTERED INDEX (SQL Server only)

I can of course log an issue to cover these cases later or add as-needed. Just let me know

Comment on lines +34 to +38
// CREATE SEQUENCE statements
normalizedStatement.startsWith("CREATE SEQUENCE", ignoreCase = true) -> {
val sequenceName = extractSequenceName(normalizedStatement)
"CREATE_SEQUENCE_$sequenceName"
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another (maybe) edge case I was wondering about:

What if the generated Exposed migration statement(s) include creating a table that requires a sequence to be created first? So CREATE TABLE statement would be preceded by CREATE SEQUENCE in db that support it.

Imo the better descriptive migration name would still be CREATE_TABLE_name, but script will instead be named CREATE_SEQUENCE_name because it comes first.

I get that we can't cover every potential case and I'm not even sure if this may be common enough to warrant a special look-forward to catch the start of the (potential) second statement. Just thinking out loud.

Comment on lines +207 to +208
private fun URLClassLoader.getClassesInPackage(packageName: String): Sequence<KClass<*>> =
getResources(packageName.replace('.', '/')).asSequence().flatMap { resource ->
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This made me think of our current configuration options in the spring boot starter database initializer, which potentially accepts a package to exclude (when searching for table objects). Do you think there could be a case when a user might prefer a way to exclude/ignore a specific (sub)package from generating migrations? And is this something you think we could feasibly cover later on if requested?

Comment on lines +115 to +118
// Check for VX__ format
val matcher = versionPattern.matcher(fileName)
if (matcher.matches()) {
hasXYFormat = false
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please confirm that I understand correctly the logic for when VX_Y__ format is used or not used?

Is it correct that this logic is based on the assumption that the user has (potentially) existing migration scripts that are all using the same format?
But that if there are different formats, the VX__ format will take priority (even if only 1 matches and it isn't the last)?
So if the existing scripts look like this:

- V1__text.sql
- V2__text.sql
- V3_0__text.sql
- V3_1__text.sql

The next script will be V4__text.sql?

Comment on lines +151 to +157
require(
parameters.databaseUrl == null ||
parameters.databaseUser == null ||
parameters.databasePassword == null
) {
"Database properties (url, user, password) must be provided when not using TestContainers"
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this probably is meant to be an if block or condition needs to change to != null && ...?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants