nkonev / r2dbc-migrate

R2DBC database migration library
https://nkonev.name/post/136
Apache License 2.0
99 stars 9 forks source link

Cannot use r2dbc.migrate with spring.sql.init #15

Closed wickedev closed 2 years ago

wickedev commented 2 years ago

Issue

I want to apply the initial schema using spring.sql.init on application.yml and then use r2dbc.migrate to do the migration. However, the default behavior is that R2dbcMigrateAutoConfiguration.R2dbcMigrateBlockingInvoker bean is initialized first, and then SqlR2dbcScriptDatabaseInitializer is initialized later. The call order is reversed and the application crashes.

application.yml


spring:
  sql:
    init:
      enabled: true
      mode: always
      schemaLocations: classpath:/db/schema.sql

r2dbc:
  migrate:
    resourcesPath: classpath:/db/migration/*.sql
# schema.sql
-- users
CREATE TABLE IF NOT EXISTS users (
    id BIGSERIAL PRIMARY KEY,
    email VARCHAR(191) UNIQUE NOT NULL,
    name VARCHAR(191),
    hash_salt VARCHAR(500) NOT NULL,
    roles VARCHAR(16)[]
);
# V2__alter_deleted_at_field_on_users,nontransactional.sql
ALTER TABLE users ADD COLUMN deleted_at TIMESTAMP NULL DEFAULT NULL;
Caused by: io.r2dbc.postgresql.ExceptionFactory$PostgresqlBadGrammarException: [42P01] relation "users" does not exist

Temp Solution

When initializing R2dbcMigrateBlockingInvoker, it tells SqlR2dbcScriptDatabaseInitializer that it needs a dependency (I don't know if this is best) so that SqlR2dbcScriptDatabaseInitializer is called first and R2dbcMigrateBlockingInvoker is called later. I also tried using @DependOn or @AutoConfigureAfter but it didn't work well.

repository

@SpringBootApplication(exclude = [R2dbcMigrateAutoConfiguration::class])
class Application

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(SpringBootR2dbcMigrateProperties::class)
class DatabaseConfiguration {

    @Bean(name = ["ar2dbcMigrate"], initMethod = "migrate")
    fun r2dbcMigrate(
        connectionFactory: ConnectionFactory,
        properties: SpringBootR2dbcMigrateProperties,
        @Autowired(required = false) maybeUserDialect: SqlQueries?,
        @Autowired(required = false) initializer: SqlR2dbcScriptDatabaseInitializer? // <--- THIS
    ): R2dbcMigrateAutoConfiguration.R2dbcMigrateBlockingInvoker {
        return R2dbcMigrateAutoConfiguration.R2dbcMigrateBlockingInvoker(
            connectionFactory,
            properties,
            maybeUserDialect
        )
    }
}

Expect

r2dbc.migrate and spring.sql.init to be used together by having SqlR2dbcScriptDatabaseInitializer called first and then R2dbcMigrateBlockingInvoker.

Thank you for your awesome project.

nkonev commented 2 years ago

Thank you for detailed report.

I've done by different way, try 1.8.1 when it will be available at Maven Central.