Declare which table components apply where. ShouldApplyExpression evaluates per target database; one table file carries every environment variant with no branching pipeline.
By the SchemaSmith Team · Last reviewed
ShouldApplyExpression is the universal opt-in/opt-out switch on every component that supports it.
Dev uses synthetic data and a lean index set. Staging mirrors production's footprint. Production carries the full reporting stack and the regulated columns. Without a declarative answer, those differences turn into per-environment file copies, branching pipelines, or hand-maintained deploy scripts — the kind of drift that's fine until a column gets forgotten. ShouldApplyExpression is that declarative answer: a SQL fragment attached to a component that decides, at deployment time, whether the component should apply to the current target.
The same property is available on:
The expression is a SQL fragment that returns a single scalar value. When SchemaQuench evaluates it before deploying that component, it considers the result false if it's 0, the literal string false, or empty/null. Anything else means apply the component normally.
On a table component you write it as a bare boolean predicate — the example below is simply '{{Table.Environment}}' = 'Production'. A script folder gate is written as a SELECT that returns a boolean; see Folder-level gates below.
Tokens inside the expression — {{Table.Environment}} in the example below — are substituted through the Script Tokens system before the expression runs. See Script token mechanics for the token sources, substitution rules, and case-insensitive matching behavior.
A reporting index that should only materialize in production, scoped by an Extensions-driven Custom Property:
{
"Name": "[Orders]",
"Extensions": { "Environment": "Production" },
"Indexes": [
{
"Name": "[IX_Orders_Reporting]",
"IndexColumns": "[ReportingDate]",
"ShouldApplyExpression": "'{{Table.Environment}}' = 'Production'"
}
]
}
The Orders table declares a table-level Custom Property Extensions.Environment set to Production. The IX_Orders_Reporting index's ShouldApplyExpression reads that property through {{Table.Environment}} at deployment. The expression is true on every target where the property resolves to Production, so the index applies there and is skipped everywhere else — one index definition, one table file, no per-environment forks.
ShouldApplyExpression works at more than one level, and it helps to keep them straight:
MariaDB/ variant gated on @@version, a Jobs/ folder skipped on Azure SQL, or TableData/TestData/ kept out of production. Its tokens are resolved before evaluation, like every other gate. See Folder-level gates below.Recommendation: to vary a table's structure by target, prefer component-level variants inside a single table file (or give the structurally different tables distinct names). Two separate same-named whole-table variant files will deploy correctly, but SchemaTongs normalizes a table to one file per name on extraction, so a multi-file same-named-table layout isn't reproduced when you re-extract.
The same primitive works one level up: a script folder can carry a ShouldApplyExpression too. Put it on any product- or template-level folder definition, alongside ServerToQuench / QuenchSlot. Blank deploys the folder as always; a non-blank expression is evaluated against the target and the folder's scripts deploy only when it returns true — false skips the entire folder (and its sub-folders), logged so you can see why.
A folder expression is run as a scalar query against the target, so write it as a SELECT that returns a boolean (or 1/0) — for example SELECT CASE WHEN @@version LIKE '%MariaDB%' THEN 1 ELSE 0 END. It can read SERVERPROPERTY / @@version, call an environment-type function your team already has, query a control table, or reference resolved tokens (including {{SchemaName}} on schema templates).
This turns "different folders for different flavors of a target" into a declarative property instead of pipeline branching: a MariaDB/ folder gated on a @@version check beside a MySQL/ folder gated on the negation, a Jobs/ folder skipped on Azure SQL, or a TableData/TestData/ folder kept out of production by your environment predicate.
A product-folder expression runs against the server's admin connection (the platform's init database — master / postgres / information_schema), because product-level scripts are server-scoped. Use server-scoped predicates there (server properties, version, edition). A template-folder expression runs against the actual target database (and schema for schema templates), so it can also query target-database state. Product folders are evaluated per server; template folders are evaluated per database — and per schema for schema templates.
A folder's ShouldApplyExpression must return a boolean. If it errors — a SQL mistake, a missing function — the deployment fails with a clear message naming the folder, rather than silently skipping it. A gate that quietly dropped schema folders would be the dangerous failure mode, so the engine fails closed.
When a component carries several same-named variants gated by mutually exclusive expressions, give each one a VariantName. The expression says when a variant applies; the name says why it exists — and that name is how a human (or a downstream tool) tells two complex, SQL-gated variants apart at a glance.
"Indexes": [
{
"Name": "[IX_Orders_Region]",
"VariantName": "Modern engines",
"IndexColumns": "[Region]",
"FilterExpression": "[Region] IS NOT NULL",
"ShouldApplyExpression": "SERVERPROPERTY('ProductMajorVersion') >= 16"
},
{
"Name": "[IX_Orders_Region]",
"VariantName": "Legacy engines",
"IndexColumns": "[Region]",
"ShouldApplyExpression": "SERVERPROPERTY('ProductMajorVersion') < 16"
}
]
Both variants share a name and target the same column, but only one matches any given server. When the matching variant deploys, its name rides along in the log:
Creating index dbo.Orders.IX_Orders_Region (variant: Modern engines)
The same (variant: ...) suffix appears in WhatIf output, so a dry run tells you exactly which variant would be applied to each target — before you commit to it. VariantName is metadata only: it has no effect on what gets deployed, it just makes the deployment legible. It's an optional label, up to 128 characters, and it round-trips through SchemaTongs re-extraction alongside the rest of the variant set.
Full-text indexes on SQL Server take variants one step further: declare an array of full-text variants on a table — each with its own catalog and ShouldApplyExpression — and each target deploys only the variant that matches it. See the table definition reference for the array field shape.
Sometimes the skip decision can't be expressed as a static expression in the package at all — it depends on something only the target server can answer at the moment the script runs. Is this database on a replica? Did a prior script's data migration land correctly? Is this a tenant that hasn't opted into a feature yet? ShouldApplyExpression covers those cases when the answer is a SQL query. But if the logic is inside the script itself — reading row counts, calling a stored procedure, checking a role membership, branching on a version+edition combination — you need the script to decide at runtime.
Raise the sentinel error and SchemaQuench treats the script as an intentional skip, not a failure:
-- SQL Server
IF SERVERPROPERTY('EngineEdition') NOT IN (5, 8)
RAISERROR('SCHEMASMITH: SHOULD NOT APPLY', 16, 1);
SchemaQuench recognizes the exact message SCHEMASMITH: SHOULD NOT APPLY (trimmed, case-insensitive, matched as the entire message). It logs the skip and moves on — the deployment succeeds. Any other error still surfaces as a real failure.
| Platform | Raise form |
|---|---|
| SQL Server | RAISERROR('SCHEMASMITH: SHOULD NOT APPLY', 16, 1) |
| PostgreSQL | RAISE EXCEPTION 'SCHEMASMITH: SHOULD NOT APPLY' |
| MySQL | SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'SCHEMASMITH: SHOULD NOT APPLY' |
SQL Server severity matters. RAISERROR at severity ≤ 10 is an informational message, not an error — SchemaQuench never sees it and the script continues executing. Use severity ≥ 11 (16 is the conventional choice) so the raise is an abort-level error that SchemaQuench can catch.
Any batch may carry the sentinel — not just the top of the script. Earlier batches that already ran are committed (the engine does not wrap the script in a transaction). The sentinel stops the rest of the script; the work those earlier batches did is preserved. "Do real setup in early batches, then decide later batches shouldn't apply" is fully supported — you own the partial-work semantics.
A migration script that raises the sentinel is recorded in CompletedMigrationScripts just like a script that ran normally — it will not be retried on the next deployment. The skip decision is per-database, so a database with different state re-evaluates independently.
| Surface | Sentinel honored |
|---|---|
| Before / After scripts | Yes |
| Object scripts (procedures, views, functions) | Yes |
| Migration scripts | Yes |
[ALWAYS] scripts | Yes |
| Validation scripts | No — express N/A through conditional logic inside the validation |
| Tool-generated SQL | No — use ShouldApplyExpression on the component |
Think of the sentinel as the ShouldApplyExpression for logic that can only run inside the script. Use ShouldApplyExpression when a single SQL expression makes the call; use the sentinel when the script needs to inspect, branch, or call procedures before it can decide.
A ShouldApplyExpression evaluates against metadata your team defines. Custom Properties are the mechanism: an open Extensions object on every schema object where you attach environment labels, data-classification tags, ownership markers, retention policies, or any other team-defined metadata. Those values become {{TokenName}} substitutions inside ShouldApplyExpression and every other expression field SchemaQuench evaluates.
Combined with Custom Properties and the rest of the Script Tokens feature surface, ShouldApplyExpression lets you express deployment-time decisions declaratively without ever writing a separate per-environment script file. See Custom Properties for the Extensions carrier shape, token promotion rules (bare names at table scope, Table. prefix from child components), and nested-object flattening.
Write a ShouldApplyExpression to gate an index, column, or table to a specific environment, without per-environment file copies.
Try conditional application