Declare where a table's rows come from and how they should merge. SchemaQuench delivers the data in foreign-key order, across every platform, with no hand-rolled MERGE scripts.
By the SchemaSmith Team · Last reviewed
Declare how a table's data gets merged into the target database and let SchemaQuench handle the rest.
Lookup tables, configuration rows, the reference data every environment needs to run — that data is a schema-management problem as much as the tables themselves. The DataDelivery block declares how those rows land in the target database, alongside the table definition, so reference data travels with the schema it depends on.
Declare how a table's data gets merged into the target database and let SchemaQuench handle the rest.
Tables without a DataDelivery block are left alone. Tables that declare one are picked up automatically during the data delivery step — see the SchemaQuench Table Data Delivery section on your platform for the runtime behavior: SQL Server, PostgreSQL, MySQL.
Add a DataDelivery block to the table JSON, point it at a .tabledata file, tell it what a "match" looks like, and SchemaQuench delivers the data in foreign-key order — no hand-rolled merge scripts.
| Property | Type | Default | Description |
|---|---|---|---|
ContentFile |
string | Path to the row data, relative to the template root. Typically produced by DataTongs as a .tabledata file (raw JSON array). |
|
MergeType |
string | One of Insert, Insert/Update, Insert/Update/Delete. See MergeType below. |
|
MatchColumns |
string | Comma-separated column names that identify a row. Prefix a column with * for NULL-safe comparison on nullable keys. Matches the KeyColumns concept in DataTongs. |
|
MergeFilter |
string | "" |
Optional SQL WHERE clause (without the WHERE keyword). Scopes both the rows considered for matching and, when delete is enabled, the rows eligible for deletion. |
MergeDisableTriggers |
bool | false |
Wrap the merge with platform-appropriate trigger disable/enable. |
MergeDisableRules |
bool | false |
PostgreSQL. Disable rewrite rules on the table during the merge. |
MergeUpdateDescendents |
bool | false |
PostgreSQL. When true, the merge targets descendant partitions as well as the specified table. When false (the default), the merge uses ONLY so descendant tables are left untouched. |
| Value | Behavior |
|---|---|
Insert |
Missing rows inserted. Existing rows and extra rows left alone. The seed-data pattern. |
Insert/Update |
Missing rows inserted, changed rows updated. Extra rows left alone. Good for reference tables where environments can append local rows. |
Insert/Update/Delete |
Full sync. Missing rows inserted, changed rows updated, and target rows that don't exist in the source data are deleted. The demo products use this. When MergeFilter is set, deletes are scoped by the filter so rows outside it are never removed. |
The chosen idiom is platform-specific — MERGE on SQL Server and PostgreSQL, INSERT ... ON DUPLICATE KEY UPDATE with a conditional delete step on MySQL — but the declarative contract is the same on every platform.
On PostgreSQL, the delete branch of an Insert/Update/Delete merge adapts to the target's engine version. PostgreSQL 17 and later delete inside the MERGE itself with a WHEN NOT MATCHED BY SOURCE clause; PostgreSQL 15 and 16 run the MERGE for the inserts and updates, then a follow-on DELETE … WHERE NOT EXISTS keyed identically and honoring the same MergeFilter and NULL-safe matching. The end state is identical on every supported version. For the full version-adaptive matrix, see the SchemaQuench Engine Version Compatibility section on your platform: SQL Server, PostgreSQL, MySQL.
When multiple tables declare DataDelivery, SchemaQuench orders them by their declared foreign keys:
This is automatic. You don't order the tables yourself; SchemaQuench computes the dependency graph from the ForeignKeys arrays in the table JSON. A cycle among NOT NULL foreign keys fails delivery with a clear log message — break the cycle by making one side nullable, or separate the data load into explicit phases.
{
"Name": "[Employee]",
"Schema": "HumanResources",
"Columns": [
{ "Name": "[EmployeeID]", "DataType": "INT", "Identity": true, "Nullable": false },
{ "Name": "[ManagerID]", "DataType": "INT", "Nullable": true },
{ "Name": "[DepartmentID]", "DataType": "INT", "Nullable": false },
{ "Name": "[FullName]", "DataType": "NVARCHAR(100)", "Nullable": false }
],
"Indexes": [
{
"Name": "[PK_Employee]",
"PrimaryKey": true,
"Unique": true,
"IndexColumns": "[EmployeeID]"
}
],
"ForeignKeys": [
{
"Name": "[FK_Employee_Manager]",
"Columns": "[ManagerID]",
"RelatedTable": "[Employee]",
"RelatedColumns": "[EmployeeID]"
},
{
"Name": "[FK_Employee_Department]",
"Columns": "[DepartmentID]",
"RelatedTable": "[Department]",
"RelatedColumns": "[DepartmentID]"
}
],
"DataDelivery": {
"ContentFile": "data/HumanResources.Employee.tabledata",
"MergeType": "Insert/Update",
"MatchColumns": "[EmployeeID]",
"MergeDisableTriggers": true
}
}
The self-referential ManagerID is nullable, so pass 1 loads every employee with ManagerID = NULL, and pass 2 back-fills the manager chain once every row exists. The mandatory DepartmentID forces Department to deliver first.
You don't have to write these blocks by hand. Point DataTongs at a source database with --ConfigureDataDelivery and it writes the DataDelivery section into each table JSON, including the match columns and merge type — see the DataTongs --ConfigureDataDelivery reference for your platform: SQL Server, PostgreSQL, MySQL.
DataTongs writes both a merge script and a sibling .tabledata content file for every table, and the content files are what the declarative DataDelivery pipeline reads. Two ShouldCast flags govern that output, and both are on by default.
| Flag | Default | Behavior |
|---|---|---|
OutputContentFiles |
true |
Write the raw row data to sibling .tabledata content files. Set to false to skip the content files — and, transitively, to skip ConfigureDataDelivery, since the configurator needs a content-file path to record in the DataDelivery block. |
TokenizeScripts |
true |
SQL Server only. Replace the source database name with a script token in the generated merge scripts, matching SchemaTongs' tokenization behavior. Set to false to keep the literal database name. |
Schema templates need per-iteration data delivery — each tenant gets the same reference data, scaffolded from a canonical source schema. DataTongs extracts data scoped to a single source schema and writes content files and merge scripts that use {{SchemaName}} as the destination schema reference, so SchemaQuench can resolve it per iteration at deploy time. The common use: extract seed data from a canonical tenant schema so every future onboarding starts from the same known state. See Multi-Tenant Deployments for the end-to-end workflow, and Schema Templates for the Template.json field reference.
Schema-template mode is detected by two signals, and both must be present:
Template.json has a non-empty SchemaIdentificationScript field. DataTongs walks up from the content path to find the nearest Template.json, then reads it for this field.Source:Schema is set in DataTongs.settings.json, naming the single source schema DataTongs will extract data from.If the template is a schema template but Source:Schema is not set, DataTongs stops with an error asking you to set it (or to point at a regular template instead). If Source:Schema is set but the target is a regular template, DataTongs logs a warning and proceeds in regular extraction mode, ignoring the schema. Neither signal means ordinary extraction, unchanged.
Schema-template mode changes the name and content of every output file so the results drop straight into a schema template:
| Output | Regular mode | Schema-template mode |
|---|---|---|
| Merge script filename | Populate <schema>.<table>.sql |
Populate <table>.sql |
| Content file filename | <schema>.<table>.tabledata |
<table>.tabledata |
| Destination schema in merge body | Literal source schema name | {{SchemaName}} |
The unqualified filenames match the naming convention for table JSON files in a schema template. SchemaQuench resolves {{SchemaName}} to the active schema when it runs the merge script for each iteration — see how the per-iteration schema token substitutes across script slots. A minimal schema-template extraction adds Schema to the Source block:
{
"Source": {
"Server": "localhost",
"Database": "TenantSeedDB",
"Schema": "tenant_seed",
"Platform": "SqlServer"
},
"ContentPath": "./Templates/TenantBody/Table Data",
"ScriptPath": "./Templates/TenantBody/Table Data",
"Tables": [
{ "Name": "Customers", "KeyColumns": "CustomerID" },
{ "Name": "Plans" }
],
"ShouldCast": {
"OutputContentFiles": true,
"ConfigureDataDelivery": true
}
}
Each schema-template run targets exactly one source schema, and every entry in the Tables array is resolved against it — so table names must be unqualified, with the schema coming from Source:Schema rather than the individual entry. If a product's data spans more than one schema (per-tenant tables in {{SchemaName}} plus shared lookups in dbo), run DataTongs twice: once in schema-template mode for the per-tenant tables, once in regular mode for the shared ones.
Tokenization composes orthogonally. With TokenizeScripts on (SQL Server), both transformations apply in sequence — the database name becomes its token and the source schema becomes {{SchemaName}} — so the merge scripts carry both a database token and a per-iteration schema reference.
Build a DataDelivery block to declare reference-table merges, and let SchemaQuench deliver them in foreign-key order automatically.
Start the data delivery lab