Files
zitadel/cmd/setup/transactional_tables.go
T
SilvanandGitHub 6d90a120a6 feat: allow transactional table setup step to recreate the whole schema (#11833)
## Problem description

- Setup currently creates/updates relational tables, but there is no
built-in way to fully reset the relational schema during iterative
development.
- Re-running setup after schema/projection changes can leave stale
relational objects and projection state behind, which makes local/dev
validation harder.
- There is no explicit, configurable switch in setup steps for
destructive schema recreation behavior.

## How the Problems Are Solved

- Adds a new setup step configuration section for relational tables with
a `ShouldRecreateSchema` flag (default `false`).
- Wires the new config flag into the transactional tables setup step.
- Extends the transactional tables execution logic to optionally:
  - Drop the `zitadel` schema with `CASCADE`
- Clean related projection state entries for relational tables in
`projections.current_states`
  - Recreate tables through the existing setup flow afterward
- Wraps the destructive operations in a transaction and keeps
error/rollback handling plus logging for visibility.

## Additional Changes

- Adds an explicit warning in step configuration that schema recreation
is intended for development and not production use.
- Keeps behavior fully backward-compatible by default
(`ShouldRecreateSchema: false`), so existing setups are unchanged unless
the flag is enabled.

## Additional Context

- Follow-up for relational setup/dev workflow improvements.
- PR: #11833

## How to use it

### Env `export ZITADEL_RELATIONALTABLES_SHOULDRECREATESCHEMA=true`

### Config

Add the following to your custom setup steps config:

```yaml
RelationalTables:
  ShouldRecreateSchema: true
```
2026-03-18 03:53:57 +00:00

65 lines
1.6 KiB
Go

package setup
import (
"context"
_ "embed"
"github.com/zitadel/zitadel/backend/v3/instrumentation/logging"
"github.com/zitadel/zitadel/backend/v3/storage/database/dialect/postgres"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/eventstore"
)
type TransactionalTables struct {
dbClient *database.DB
ShouldRecreateSchema bool
}
func (mig *TransactionalTables) Execute(ctx context.Context, _ eventstore.Event) error {
// TODO(adlerhurst): revert changes made in https://github.com/zitadel/zitadel/pull/11833 before v5 release.
if mig.ShouldRecreateSchema {
logging.Info(ctx, "dropping schema of relational tables")
if err := mig.dropSchema(ctx); err != nil {
return err
}
}
config := &postgres.Config{Pool: mig.dbClient.Pool}
pool, err := config.Connect(ctx)
if err != nil {
return err
}
return pool.Migrate(ctx)
}
func (mig *TransactionalTables) String() string {
return "repeatable_transactional_tables"
}
func (mig *TransactionalTables) Check(lastRun map[string]interface{}) bool {
return true
}
func (mig *TransactionalTables) dropSchema(ctx context.Context) (err error) {
tx, err := mig.dbClient.BeginTx(ctx, nil)
if err != nil {
return err
}
defer func() {
if err != nil {
rollbackErr := tx.Rollback()
logging.OnError(ctx, rollbackErr).Debug("rollback failed")
return
}
err = tx.Commit()
}()
_, err = tx.ExecContext(ctx, "DROP SCHEMA IF EXISTS zitadel CASCADE")
if err != nil {
return err
}
_, err = tx.ExecContext(ctx, "DELETE FROM projections.current_states WHERE projection_name LIKE $1 || '%' OR projection_name = $2", "zitadel.", "relational_tables")
return err
}