Some schema objects can be declared as JSON instead of scripted in SQL. SchemaSmith compares a declared object against the server and converges it — and each kind has a boundary where that convergence stops.
By the SchemaSmith Team · Last reviewed
You have a domain called positive_amount that must reject negative values. In your template's Domain Types/ folder you write it as a guarded CREATE DOMAIN in a .sql file, and it deploys correctly the first time.
Later you tighten the CHECK constraint and redeploy. The run reports success, but the server is still enforcing the old rule. Your edit changed nothing, and it will keep changing nothing on every future deploy, because the guard skips an object that already exists — and there is no CREATE OR REPLACE DOMAIN to fall back on.
A declared object is the same object written as a .json file instead. SchemaSmith reads it, compares it against the server, and changes only what differs — whether or not the object is already there, so the edit takes effect on the next deploy. Declared, positive_amount looks like this:
{
"Name": "positive_amount",
"Schema": "public",
"DataType": "numeric(10,2)",
"NotNull": true,
"Default": "0",
"CheckConstraints": [
{ "Name": "positive_amount_nonneg", "Expression": "VALUE >= 0" }
]
}
Both forms live side by side in the same folder. An existing package full of .sql objects keeps working unchanged, and you can mix declared files into it without migrating anything.
Declared objects include sequences, enum types and domain types on PostgreSQL, and scheduled events on MySQL and MariaDB. On SQL Server, user-defined types live in the DataTypes/ folder, which takes scripts.
Declaring an object does not hand SchemaSmith total control of it. Each one has a boundary where convergence deliberately stops, and the engine draws it — usually because the alternative would destroy data or dependent objects.
| Object | Where convergence stops | Why |
|---|---|---|
| Sequence | The current position is never managed | Position is data, not schema. Resetting it would re-issue keys already handed out. |
| Enum type | A removed value is reported, not performed | PostgreSQL cannot remove a value without recreating the type, which would mean dropping every column that uses it. |
| Domain type | A base-type change is refused | PostgreSQL has no ALTER DOMAIN … TYPE. |
| Scheduled event | Removal reaches only events SchemaSmith created | One made by hand, or by a scripted file, is never removed — and removal happens at all only with DropEventsRemovedFromProduct on. |
A declared sequence carries its type, bounds, increment, cache and cycle behavior, and SchemaSmith compares it against the server and converges what differs. Start applies when the sequence is created and never afterwards; SchemaSmith never issues RESTART. A sequence the engine owns, generated by a serial or IDENTITY column, belongs to that column's declaration and is excluded from extraction. Sequences live in the Sequences/ directory of each template, which accepts both .json and .sql.
{
"Name": "invoice_number_seq",
"Schema": "public",
"DataType": "bigint",
"Start": 1000,
"Increment": 1,
"Cache": 1,
"Cycle": false
}
| Property | Type | Default | Description |
|---|---|---|---|
Name | string | Sequence name. Required. | |
Schema | string | "public" | Schema name. |
DataType | string | "bigint" | smallint, integer or bigint. |
Start | long | null | The value the sequence starts from when it is created. Not the current value. |
Increment | long | 1 | Step between values. Negative for a descending sequence. |
MinValue | long | null | Omit for the type's natural minimum. |
MaxValue | long | null | Omit for the type's natural maximum. |
Cache | long | 1 | Values pre-allocated per session. Higher is faster but leaves larger gaps after a crash. |
Cycle | bool | false | Wrap to MinValue after MaxValue instead of erroring. |
A declared enum has its value list compared against the server, and missing values are added. Order is part of the definition: PostgreSQL sorts and compares enum values by declared position rather than alphabetically, so a value added in the middle of the list is added in the middle of the type, not appended. Enum types live in the Enum Types/ directory of each template, which accepts both forms.
A value the package no longer lists is left in place and named in the deploy log and the change manifest. Removing it would mean recreating the type and dropping every column that uses it, so the divergence is made visible instead of acted on.
{
"Name": "order_status",
"Schema": "public",
"Values": [ "draft", "submitted", "shipped", "cancelled" ]
}
| Property | Type | Default | Description |
|---|---|---|---|
Name | string | Type name. Required. | |
Schema | string | "public" | Schema name. |
Values | array of string | The labels, in order. Required. |
A domain is a base type carrying its own constraints, and a declared one converges its NotNull, Default and named CheckConstraints in place through ALTER DOMAIN — without dropping the type or touching the columns that use it. Domain types live in the Domain Types/ directory of each template, which accepts both forms.
The base type is create-time only. A declared change to it is refused rather than attempted, because PostgreSQL offers no ALTER DOMAIN … TYPE and reaching the same end state would mean dropping the domain and everything declared against it.
{
"Name": "positive_amount",
"Schema": "public",
"DataType": "numeric(10,2)",
"NotNull": true,
"Default": "0",
"CheckConstraints": [
{ "Name": "positive_amount_nonneg", "Expression": "VALUE >= 0" }
]
}
| Property | Type | Default | Description |
|---|---|---|---|
Name | string | Domain name. Required. | |
Schema | string | "public" | Schema name. |
DataType | string | The underlying type, with its modifier. Required, and applied at create only. | |
NotNull | bool | false | Converges in place via ALTER DOMAIN … SET/DROP NOT NULL. |
Default | string | null | Default applied to a column of this domain that declares no default of its own. |
CheckConstraints | array | [] | Named CHECK constraints, each a Name and an Expression. |
A declared event is compared against the server and converges when it differs. A scripted one re-runs on every deploy, is never compared, and is never removed by absence. Scheduled events live in the Events/ directory of each template, which accepts both forms.
Removal is the narrower half of the contract. An event that leaves the package is dropped only when DropEventsRemovedFromProduct is on, and only when SchemaSmith created it — one made by hand, or by a scripted file in the same folder, is left alone.
{
"Name": "nightly_purge",
"ScheduleType": "EVERY",
"Interval": "1 DAY",
"Status": "ENABLE",
"Preserve": false,
"Definition": "DELETE FROM audit_log WHERE created_at < NOW() - INTERVAL 90 DAY"
}
| Property | Type | Default | Description |
|---|---|---|---|
Name | string | Event name. Required. | |
Definition | string | The body after DO. A multi-statement body must be wrapped in BEGIN … END exactly as it would be in hand-written DDL. Required. | |
ScheduleType | string | "EVERY" | "EVERY" for a recurring event or "AT" for a one-shot. |
Interval | string | null | For "EVERY": the interval as a value and a unit, e.g. "1 DAY" or "30 MINUTE". |
ExecuteAt | string | null | For "AT": when the event runs, once. |
Starts | string | null | Optional start of the recurrence window. Omit it and the server's own start time is left alone. |
Ends | string | null | Optional end of the recurrence window. |
Status | string | "ENABLE" | "ENABLE", "DISABLE" or "DISABLE ON SLAVE". |
Preserve | bool | false | When true the event survives its last run instead of dropping itself. |
Comment | string | null | Event comment. |
Not every object earns a declared form. A PostgreSQL extension is database-scoped and belongs to no table, so SchemaSmith has no property for one and does not need one — an extension deploys the way schemas and collations do, as a scripted file in a folder you declare. Give that folder a QuenchSlot of Objects and it runs before the tables, which is what a column whose type comes from the extension requires.
The distinction is ownership rather than syntax. The objects above have a boundary SchemaSmith can describe and hold; where no such boundary exists, a script is the honest answer.