.sql file on disk. telnyx-edge storage sqldb migrations creates
those files, applies the ones that have not run yet, and records what it applied — in a table
inside the database, not in a state file beside your code. Three commands cover the whole
workflow:
--remote is required on list and apply. There is no local database to run against —
local execution is not supported, and there is no --local flag. create is the exception:
it only writes a file, so it needs no authentication and no network.A Worked Example
Start with a provisioned database namedlinks-db. Create the first migration:
list now reflects the new state:
apply again does nothing — it only ever runs what is pending:
The Ledger Lives in the Database
Applied state is tracked in a table calledsqldb_migrations, created inside the database on
the first apply:
nameis the filename, not the label — renaming a file after it has been applied makes it pending again.applied_atis an RFC 3339 UTC timestamp generated by the CLI at apply time, so it reflects the clock of the machine that ran the command.idis assigned by SQLite in apply order.
list never creates the table — it is read-only, and treats a missing sqldb_migrations as
“nothing applied yet”. Only apply creates it.
Apply Is Not All-or-Nothing
Each file is submitted as one script in one call: the migration body, then theINSERT
into sqldb_migrations that records it. A script is applied atomically: if any statement in it
fails, the whole script rolls back — a script that creates a table, inserts a row, and then hits
a bad statement leaves no table behind. So a migration file either applies completely and is
recorded, or changes nothing and is not recorded. There is no state in between for a single file.
Across the batch there is no atomicity at all. If the third of five migrations fails, the first
two stay applied and the command stops:
✗ line relays the API error verbatim, so the SQLite message — duplicate column name: owner here — arrives embedded in a longer transport string rather than on its own. The command
exits non-zero with an error that begins
migration 0003_add_owner.sql failed after applying 2 migration(s):, followed by the same
relayed API error. The database is left
partway through the batch — schema changes from 0001 and 0002 are live, and 0003 is not
recorded, so apply will try it again.
Recover by fixing forward:
- Run
migrations list <database> --remoteto see exactly where the batch stopped. - Correct the file that failed. It is still pending, and
applyruns pending files in numeric order, so the next run reaches it before anything numbered after it — a later migration cannot reconcile around it. Edit that file until it succeeds, or empty it out if the change is no longer wanted. - Run
apply --remoteagain. Only the still-pending files run, beginning with the one that failed. - Prefer statements that are safe to retry —
CREATE TABLE IF NOT EXISTS,CREATE INDEX IF NOT EXISTS,INSERT OR IGNORE— because a retry re-runs the failed file from the top.
applied and its new contents never run.
Nothing coordinates two apply runs against the same database. apply reads the ledger and
then submits each pending file as its own call, so two runs started at the same time — two CI
jobs, or a pipeline racing an operator — can both see a file as pending and run it twice. Run
migrations from one place, and write them so that a second run is harmless.
Foreign keys are on and enforced. A migration that rebuilds or drops a referenced table has to
order its statements so no statement leaves a dangling reference — a foreign-key violation
fails the file like any other error.
Files and Directories
create numbers files with a 4-digit zero-padded, auto-incrementing prefix
(0001_, 0002_, …), taking the next number from the highest prefix already in the
directory. The label is the name argument with every run of non-alphanumeric characters
collapsed to _, and leading or trailing runs dropped.
Files land in migrations/<database> by default, with the same collapsing applied to the
database argument — links-db becomes migrations/links_db:
app-db and app--db both resolve to
migrations/app_db. Give one of them an explicit --migrations-dir so their files stay apart.
Override the location with --migrations-dir:
.sql suffix is picked up, whatever
the digit count. Files run in numeric-prefix order, then filename order.
Machine-Readable Output
apply --json always emits the same shape, including on a no-op run ({"applied": []}), and
emits no JSON at all on failure. One wrinkle in the current CLI: the JSON — for apply and
list both — is written to stderr, not stdout, so capture it with 2>&1 >/dev/null
rather than piping stdout:
list --json emits one entry per local file:
Related
- CLI — the full
storage sqldbcommand surface - Quick Start — create a database, bind it, query it
- Limits — the ~4 MiB script ceiling and query duration
- How SQL Databases Work — one primary per database