-
Notifications
You must be signed in to change notification settings - Fork 37
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
Question: User creation in a Flyway SQL migration #276
Comments
Hi Pavel, long time no see, I hope you're doing well. Regarding the first part of your question, users and roles in a PostgreSQL database are shared objects. So in certain cases where Flyway migrations need to be processed multiple times, this error can occur. To prevent this problem, it's necessary to use "CREATE ... IF NOT EXISTS" statements for shared objects. In cases where this type of statement is not supported, you need to use procedures, see the example below:
As for the second part of the question, what exactly do you mean by "loads of queries" appearing in In essence, This brings me to the question, how are Flyway migrations being executed? Through a Flyway bean as part of Spring context initialization or within the test itself? Are you using any annotations on the test class that generate SQL queries? For example, It would be good to look at these details more closely so we can better identify the cause of the problem. |
Hi Tomáš. It's been some time since we last met! Good to talk to you again and I hope you're well too. A little bit of praise - your framework is amazing; we love it and use it every day.
We run our integration tests using
The Record{thisId=connection4, methodName=prepareStatement, arguments=[Record{thisId=connection4, methodName=prepareStatement, arguments=[SELECT COUNT(*) FROM pg_namespace WHERE nspname=?], resultId=preparedStatement116}
I am wondering what would cause the Flyway script to be processed multiple times. Did you mean repeatable migrations? If so, we use repeatable migrations only for DML queries related to table content. In versioned migrations, we never use the I probably need to better understand how the database reset and the preparers work, to find out the root cause to implement a proper fix. |
Do you mean that
No, I'm referring to versioned Flyway migrations. This can simply occur when a specific test configuration leads to the initialization of multiple Spring contexts, where each context processes its own instance of the Flyway bean. The target databases for different Spring contexts are of course different, but shared objects (like users or roles) can pose a problem in such cases.
As I mentioned in my previous comment, at the start of the build during Spring context initialization, database operations are converted to core preparers. These are then collected after context initialization and used to prepare a template database from which independent databases for individual tests are then created. These preparers also serve as cache keys, so if different tests refer to the same core preparers, a database is created for them using the template database (a very fast operation - essentially binary cloning of databases supported by Postgres). If the preparers don't match for some reason (typically due to different Spring or Flyway configurations), it's necessary to create a new database from scratch for that test and process all Flyway migrations again, which may cause complications with shared objects and also takes much longer.
In case all tests have the same Spring and Flyway configuration and you perform database reset only using
This is definitely wrong. In such case, the library cannot function optimally because database caching doesn't work as it should. There are two things that are wrong here: 1. This type of preparer should under all circumstances be only in If not, for further investigation I would need you to enable trace logging for the package Another thing that would help me is adding a breakpoint to the 2. Presence of This is probably caused by not having I guess that fixing the second point will likely "solve" the first issue as well, as it would result in using a MigrateFlywayDatabasePreparer instead of the ReplayableDatabasePreparer. However, if this isn't caused by any custom database context handling in your project, it would still be valuable to investigate the root cause of the first issue, as it could potentially affect other users of the library. |
A few notes:
It seems it might be 100 times faster to jump on a live-session debugging call, because something seems to be way off. |
Although I expected you to send the entire log, it doesn't matter in the end. Because if there's only one The occurrence of the error org.postgresql.util.PSQLException: ERROR: role "a_new_user" already exists is related to how That's simply how To clarify what I mean, in your code you probably have Flyway migration execution implemented something like this: @Bean
public FlywayMigrationInitializer flywayInitializer(FlywayMigrationStrategy migrationStrategy) {
FluentConfiguration config = Flyway.configure();
// ...
Flyway flyway = config.load();
return new FlywayMigrationInitializer(flyway, migrationStrategy);
} While for the library to work optimally, the configuration should look like this. In other words, the Flyway instance must be exposed as a Spring bean (of course, there can be more than one Flyway bean if needed): @Bean
public Flyway flyway() {
FluentConfiguration config = Flyway.configure();
// ...
return config.load();
}
@Bean
public FlywayMigrationInitializer flywayInitializer(Flyway flyway, FlywayMigrationStrategy migrationStrategy) {
return new FlywayMigrationInitializer(flyway, migrationStrategy);
} I hope it's clear enough. If not, we can schedule an online meeting to discuss it further. However, I believe that if you follow the steps above, everything should start working properly. |
Thanks for the very detailed guide. However, our setup is different from what you expected. We have a single flyway bean exposed. This one manages tenant-agnostic migrations: @Bean
fun commonSqlFlyway(basicFluentConfiguration: FluentConfiguration): Flyway The @Component("commonFlywaySqlMigrationExecutor")
class CommonFlywaySqlMigrationExecutor(
private val commonSqlFlyway: Flyway,
) {
@PostConstruct
fun migrate() {
commonSqlFlyway.migrate()
}
} Once the common migrations are completed, the @Component
@DependsOn("commonFlywaySqlMigrationExecutor")
class TenantFlywaySqlMigrationExecutor(
private val tenantSqlFluentConfiguration: FluentConfiguration,
private val tenantRepository: TenantRepository,
) {
@PostConstruct
fun migrate() {
tenantRepository.findAll().forEach {
createConcreteTenantFlyway(it).migrate()
}
}
private fun createConcreteTenantFlyway(tenantEntity: TenantEntity): Flyway =
FluentConfiguration().configuration(tenantSqlFluentConfiguration)
.schemas(tenantEntity.schema)
.placeholders(mapOf(
"tenant_id" to tenantEntity.id.toString(),
etc.
))
.load()
} Given the above, I am not sure If we can achieve the setup you described. |
I think that in your case, it might be possible to use the following solution: @Component
@DependsOn("commonFlywaySqlMigrationExecutor")
class TenantFlywaySqlMigrationExecutor(
private val tenantSqlFluentConfiguration: FluentConfiguration,
private val tenantRepository: TenantRepository,
private val flywayDatabaseExtension: FlywayDatabaseExtension,
) {
@PostConstruct
fun migrate() {
tenantRepository.findAll().forEach {
flywayDatabaseExtension.postProcessBeforeInitialization(createConcreteTenantFlyway(it), null).migrate()
}
}
private fun createConcreteTenantFlyway(tenantEntity: TenantEntity): Flyway =
FluentConfiguration().configuration(tenantSqlFluentConfiguration)
.schemas(tenantEntity.schema)
.placeholders(mapOf(
"tenant_id" to tenantEntity.id.toString(),
etc.
))
.load()
} However, I've never tested it, so I'm not sure if it works, and it's a little hacky solution. |
Thanks a lot for all the details! I found the root cause but I'm not yet sure how to fix it other than in your framework. What happens in our app is that the I have created an own post processor and it behaves exactly the same way - it is registered after the @Component
class LoggingBeanPostProcessor : BeanPostProcessor {
private companion object : Logger()
init { log.info("!!!!!! LoggingBeanPostProcessor created") }
override fun postProcessBeforeInitialization(bean: Any, beanName: String): Any? {
if (bean is Flyway) log.info("!!!!!! Hooray - found a Flyway bean: '$beanName'")
return bean
}
} However, when the It's weird that the In the actuator, I can see that these beans depend on
Just for the record, we use Java 21, Spring Boot 3.3.0 along with Flyway 10. |
Implementing some form of ordering for processors certainly makes sense to me. I would prefer However, even without using the |
After some investigation, I have found out that we have a bean post-processor that auto-wires a JPA repository that enforces Flyway bean creation earlier than the The way I solved it was to mark the auto-wired repository with Thanks!!! |
#276 Add Ordered interface to internal bean post processors to ensure correct processing order
Thanks for your help, @tomix26! Much appreciated. |
The background
In our application, we have a versioned Flyway migration script that creates a user in all our environments using
When we run our integration tests using
all tests after the first one fail on
When debugging the
io.zonky.test.db.context.DefaultDatabaseContext#refreshDatabase
, thetestPreparers
contain loads of queries, with theCREATE USER a_new_user PASSWORD 'a_new_password'
being one of those.The question
Is it possible to somehow exclude user creation from becoming part of the template that is restored?
I am still unsure what the root cause is. I thought that the user in the template was restored to a new database where it can't conflict. Obviously, this is not the case.
The text was updated successfully, but these errors were encountered: