Skip to content
Anjunar/ DOCS

Required columns and backfills

A column becomes required only when no row holds NULL. The values for existing rows come from a backfill you register.

Required

Nullable first, NOT NULL after counting

A new required column in an existing table is added nullable. It becomes required with SET NOT NULL after the executor has counted under the lock that no row holds NULL. Any NULL left makes the migration fail and roll back.

So a new required column works in an empty table, and a column becomes required once its rows are filled. It becomes optional again with DROP NOT NULL; primary key and identity columns stay required.

Backfill

Values the application declares

The framework never invents a value. A backfill names its target column by ID, runs when the column becomes required and fills only the NULLs.

scala
import com.anjunar.hibernateddl.core.* val backfills = Vector( Backfill.fillNulls( id = "user-display-name-v1", target = SchemaId("7f3a9c21/4f5a6b7c"), when = BackfillTrigger.BecomesRequired, value = BackfillValue.coalesce( BackfillValue.concat( BackfillValue.column(SchemaId("7f3a9c21/1a2b3c4d")), BackfillValue.literal(" "), BackfillValue.column(SchemaId("7f3a9c21/2b3c4d5e")) ), BackfillValue.literal("Unknown") ) ) ) HibernateSchemaMigration.migrate(metadata, dataSource, backfills = backfills)
Output
UPDATE "public"."users" SET "display_name" = COALESCE(("first_name" || CAST(? AS varchar(255)) || "last_name"), CAST(? AS varchar(255))) WHERE "display_name" IS NULL
Values

What a backfill may use

Constants are always JDBC parameters. A constant must fit the column without conversion or rounding, a source column must have the target's type, or be text for text.

BackfillValue.literal(value)
A constant: text, numbers, boolean, UUID, date and time types.
BackfillValue.column(id)
Another column of the same row, by stable ID.
BackfillValue.coalesce(values*)
The first value that is not NULL.
BackfillValue.concat(values*)
Text joined together; NULL if any part is NULL.
Lifecycle

Registered once, run once

A backfill is recorded with its definition's checksum in __hibernate_ddl.backfill_history. The same ID with another definition blocks the start.

Rules
The integrator finds backfills through META-INF/services/com.anjunar.hibernateddl.integration.BackfillProvider.
The explicit call adds its own backfills to the providers'; an ID found twice is refused.
Keep the rules of every release a server may still skip.
A rule whose column does not become required stays pending in MigrationResult.pendingBackfills.
Backfills run inside the migration

They run in its transaction while its tables are locked exclusively. Large tables and deployments without downtime need a separate, stepwise data migration.