SchemaSmith Concept

Data Fixes

A deliberately partial schema package that corrects one specific issue in production — without touching anything the package doesn't mention. Here's when to reach for one and how to run it safely.

By the SchemaSmith Team · Last reviewed

A single glowing fix being placed into a production database while surrounding structures remain untouched

A data fix is a deployment of a deliberately partial schema package — usually a handful of migration scripts that correct a specific production issue — rather than a full release.

What is a data fix

A data fix is a deployment of a deliberately partial schema package — usually a handful of migration scripts that correct a specific production issue — rather than a full release. SchemaQuench supports this as a first-class mode through four configuration flags that flip together, called the datafix profile.

The distinction matters because several SchemaQuench behaviors are correct for a full release and wrong for a partial package. A full release treats the package as the complete truth of what the database should look like: tables not in the package get dropped, migration tracking records with no matching script get pruned, helper infrastructure gets redeployed. A data fix does the opposite — it runs only what's in the partial package and leaves everything else alone.

When to reach for a data fix

Data fixes are for narrow, between-release corrections where the full schema-management cycle isn't warranted or isn't allowed. Common scenarios:

Data backfills

Populate a new NOT NULL column's values, backfill a computed column, correct values that shipped wrong in a prior release.

Compliance scrubs

Redact or delete data to meet a GDPR, HIPAA, or PCI deadline without waiting for the next release cadence.

Emergency indexes

Add an index to stop a query from timing out in production when you can't wait for the next full release cycle.

Reference re-seeds

Restore a reference table that got clobbered by a bad manual change, or re-apply a lookup-table delta that was missed.

Targeted repairs

Fix a specific row, reset a stuck workflow state, correct a foreign-key orphan, recover from a partial manual edit.

Permissions-limited

Run data-only changes in an environment where the deployment user has read/write access but no DDL modification ability.

How a data fix differs from a regular release

Where a full release treats the schema package as the complete truth of what the database should look like, a data fix treats it as a surgical slice. Eight dimensions separate the two:

Dimension Regular release Data fix
Package content Full product (tables, objects, DataDelivery, all migration scripts) Partial (usually just migration scripts)
Package semantics Complete truth — database reconciles to match Surgical — only what's in the package executes
Table changes Added, altered, dropped to match the package No structural changes
Migration tracking Recorded and pruned to match the package Not recorded; prior tracking preserved
Infrastructure KindleTheForge runs to sync helper procedures Skipped — existing infrastructure assumed correct
Rerun-ability Tracked scripts skip; [ALWAYS] always runs Every script runs on every invocation
Permissions needed DDL + data Data-only in most cases
Typical cadence Scheduled release train On-demand, between releases

The datafix profile

The datafix profile is four SchemaQuench settings that flip together whenever you're deploying a partial package. Each addresses a specific assumption that full-release mode makes and a data fix has to turn off. The four flags are a profile, not a menu — mixing partial-package intent with full-release reconciliation is how tracking records get corrupted or tables get dropped by mistake. Flip all four together.

{
  "KindleTheForge": false,
  "UpdateTables": false,
  "DropTablesRemovedFromProduct": false,
  "TrackRunOnceMigrations": false
}

KindleTheForge: false

Skip redeployment of SchemaSmith's helper procedures and tracking table. The infrastructure is already in place from the most recent full release; a data fix doesn't need to touch it. Also reduces the DDL permissions the deployment user needs, which matters when the fix is running under a data-only service account.

UpdateTables: false

Skip the table-quench phase entirely. The partial package doesn't contain table JSON; this flag stops SchemaQuench from interpreting their absence as "drop everything." Combined with DropTablesRemovedFromProduct: false, it closes both paths to unintended structural changes.

DropTablesRemovedFromProduct: false

Tables not in the partial package must not be dropped. A datafix package with two migration scripts and no table JSON would otherwise signal "the product has no tables" and trigger a mass drop. This flag is the last line of defense against a silent catastrophic mistake.

TrackRunOnceMigrations: false

Don't record migration script execution in CompletedMigrationScripts, and treat every script as if it carried [ALWAYS]. Data fixes often need to run more than once — the first run didn't quite land, the fix needs to be re-applied after data drift — and tracking would prevent that. This also forces PruneObsoleteMigrationTracking off regardless of its configured value, which protects the tracking records from prior full releases from being deleted by a partial package's pruning pass.

Scoped deployment account

Under the datafix profile above, SchemaSmith performs no structural DDL of its own — it runs the migration scripts you provide. Those scripts often still need targeted rights beyond basic reader/writer access, the most common being CREATE TABLE for the rollback-backup tables a careful fix writes before it changes anything. The safe way to grant that is to give the deploy account its own schema — conventionally named datafix — to create backup tables in: creating a table in a schema you own needs no rights over the product's own tables, so the account can back up and fix data without any power to alter or drop the schema it's deploying into.

The grant sets below are a recommended starting point for a datafix_user account on each engine, scoped to about the minimum a representative multi-tenant fix needs. Treat them as a baseline to tighten per environment — a production account should carry only the grants the specific fix has been proven to need.

Verify what "create a table" actually grants

Granting CREATE TABLE authorizes the statement, but the new table still has to land somewhere. Creating it in the product's own schema would additionally require a schema-alter grant — and on SQL Server that grant (ALTER ON SCHEMA::dbo) also lets the account drop and alter the product's tables, a structural power a datafix account should never hold. Giving the account a schema it owns sidesteps that entirely. So it pays to check what was actually handed over: ask a DBA for “rights to create a table” and you may be given ALTER ON SCHEMA — a drop capability in disguise.

Per-engine grant baselines

Each engine reaches the same intent — reader/writer on the product data, an owned schema for backups, execute on ancillary routines, and temporary-table space — through its own privilege model. Substitute a strong password and repeat the per-database block for each target database.

SQL Server separates server identity from database identity: one LOGIN authenticates at the instance, and a USER maps it inside each database. The reader/writer grant on dbo carries no structural rights; tempdb access is implicit for any authenticated login, so temporary tables need no explicit grant.

-- Server-level login (run against master)
CREATE LOGIN datafix_user WITH PASSWORD = 'YourStrongPassword';

-- Repeat the block below in each target database
USE shop_tenant_a;
GO
CREATE USER datafix_user FOR LOGIN datafix_user;
GO

-- Dedicated schema the deploy user OWNS: backup tables land here
CREATE SCHEMA datafix AUTHORIZATION datafix_user;
GO

-- Reader/writer on the product data (dbo); no structural rights on dbo
GRANT SELECT, INSERT, UPDATE ON SCHEMA::dbo TO datafix_user;

-- Create rollback-backup tables; they land in the owned 'datafix' schema
GRANT CREATE TABLE TO datafix_user;

-- Ancillary stored procedures and functions the fix may call
GRANT EXECUTE ON SCHEMA::dbo TO datafix_user;
GO

PostgreSQL security is built around cluster-level roles rather than per-database logins: create one ROLE with LOGIN, then grant it access to each database. The role reads and writes the existing public tables but is given no CREATE on public, so its backup table lives in an owned datafix schema instead. One reason that matters: a GRANT ... ON ALL TABLES only covers tables that exist at grant time, so leaning on a public grant for new tables would be fragile as well as over-privileged.

-- Cluster-level role (run on a maintenance database)
CREATE ROLE datafix_user LOGIN PASSWORD 'YourStrongPassword';

-- Repeat the block below for each target database (\connect in psql)
\connect shop_tenant_a

-- Allow the role to open a connection to this database
GRANT CONNECT   ON DATABASE shop_tenant_a TO datafix_user;

-- Temp space: allows CREATE TEMPORARY TABLE within a session
GRANT TEMPORARY ON DATABASE shop_tenant_a TO datafix_user;

-- Read/write the product data, but no CREATE in public
GRANT USAGE ON SCHEMA public TO datafix_user;
GRANT SELECT, INSERT, UPDATE ON ALL TABLES IN SCHEMA public TO datafix_user;

-- Dedicated schema the deploy user OWNS: backup tables go here
CREATE SCHEMA datafix AUTHORIZATION datafix_user;

-- Ancillary functions and procedures the fix may call
GRANT EXECUTE ON ALL FUNCTIONS  IN SCHEMA public TO datafix_user;
GRANT EXECUTE ON ALL PROCEDURES IN SCHEMA public TO datafix_user;

In MySQL a schema and a database are the same construct, so there is no separate schema-level grant layer — tenant isolation maps directly to per-database grants. The CREATE privilege on db.* covers permanent backup tables; temporary tables need CREATE TEMPORARY TABLES, which is a distinct privilege from CREATE.

-- User account (% = any host; tighten the host specifier in production)
CREATE USER IF NOT EXISTS 'datafix_user'@'%' IDENTIFIED BY 'YourStrongPassword';

-- Repeat for each target database
GRANT SELECT, INSERT, UPDATE, CREATE ON `shop_tenant_a`.* TO 'datafix_user'@'%';
GRANT CREATE TEMPORARY TABLES        ON `shop_tenant_a`.* TO 'datafix_user'@'%';
GRANT EXECUTE                        ON `shop_tenant_a`.* TO 'datafix_user'@'%';

FLUSH PRIVILEGES;

Privilege summary across engines

Capability SQL Server PostgreSQL MySQL
Reader/writer on data GRANT SELECT, INSERT, UPDATE ON SCHEMA::dbo GRANT SELECT, INSERT, UPDATE ON ALL TABLES IN SCHEMA public GRANT SELECT, INSERT, UPDATE ON db.*
Backup-table creation GRANT CREATE TABLE + owned datafix schema Owned datafix schema (no CREATE on public) GRANT CREATE ON db.*
Temp space Implicit for authenticated logins GRANT TEMPORARY ON DATABASE GRANT CREATE TEMPORARY TABLES ON db.*
Execute ancillary routines GRANT EXECUTE ON SCHEMA::dbo GRANT EXECUTE ON ALL FUNCTIONS / PROCEDURES IN SCHEMA public GRANT EXECUTE ON db.*

A baseline, not a mandate

These grants are the minimal starting point a representative multi-tenant fix exercises, not a fixed requirement. Certify them against your own fix and environment, and remove any grant a specific datafix doesn't actually use.

Patterns that pair well with data fixes

Two SchemaSmith features show up repeatedly in data-fix packages. Neither is required, but each fits the partial-package shape naturally.

Checkpoint and resume

When a data fix touches a large dataset and may need to be retried after a connection drop, server restart, or mid-deployment timeout, enable resume so the fix picks up where it left off instead of re-running completed work. See the Checkpoint and Resume section of the SchemaQuench reference for your platform: SQL Server, PostgreSQL, MySQL.

Slot choice

Even in a partial package, a migration script's slot determines when in the deployment sequence it runs. Before, BetweenTablesAndKeys, AfterTablesScripts, and After are the usual homes for data fixes. The slot is a namespacing and timing signal — the fact that a data fix typically has no tables to run between doesn't change the ordering contract. See Migration scripts for the full slot reference.

Note

A data fix should not carry DataDelivery blocks or table JSON. If your fix is "re-seed this reference table," the right shape is usually a migration script that does the seeding imperatively (or calls a stored procedure that does), not a DataDelivery block in what would then stop being a partial package.

Hands-on lab

Practice shipping a corrective data fix as a one-time, hand-authored script that SchemaSmith tracks and runs exactly once.

Start the hand-rolled lab