# Database URLs and dev databases

Accepted database URL formats, and the difference between the target, dev, shadow, and throwaway databases.

Source: https://docs.ptah.run/v0.25.0/concepts/database-urls-and-dev-databases/

Every Ptah command that touches a database takes a URL, and the URL's scheme
selects the engine. The same syntax names databases in four roles: the target
you are changing, and up to three kinds of disposable databases that exist so
mistakes happen somewhere harmless. This page defines all four; other pages
link here instead of redefining them.

## URL formats

| Engine | Example |
| --- | --- |
| PostgreSQL | `postgres://user:pass@localhost:5432/app` |
| MySQL | `mysql://user:pass@localhost:3306/app` (Go-driver form `mysql://user:pass@tcp(localhost:3306)/app` is also accepted) |
| MariaDB | `mariadb://user:pass@localhost:3306/app` (the Atlas CLI spelling `maria://user:pass@localhost:3306/app` is also accepted) |
| SQLite | `sqlite://relative.db`, `sqlite:///absolute/path/app.db`, `sqlite:file:C:/absolute/windows/path/app.db`, `sqlite:///:memory:`, `sqlite:file:memdb1?mode=memory&cache=shared` |
| SQL Server | `sqlserver://sa:pass@localhost:1433?database=app` (plus a Ptah-only `schema` parameter — see [SQL Server](../../databases/sqlserver/)) |
| ClickHouse | `clickhouse://user:pass@localhost:9000/app` |
| CockroachDB | `cockroachdb://user:pass@localhost:26257/app` |
| YugabyteDB | `yugabytedb://user:pass@localhost:5433/app` |
| Oracle | `oracle://user:pass@localhost:1521/service` (renders, plans, reads a live catalog, and runs versioned migrations) |
| YDB, through its provider | `ydb://localhost:2136/local`, or `ydbs://` over TLS (see [YDB](../../extend/providers/ydb/usage/#connecting)) |
| Spanner's PostgreSQL interface, through its provider | `spanner://localhost:5432/app`, with `cloudspanner://` and `google-spanner://` as the same address (see [Spanner](../../extend/providers/spanner/usage/#connecting)) |

Scheme aliases normalize to the canonical dialect (`postgresql://`,
`sqlite3://`, `mssql://`, `crdb://`, `ysql://`, `ch://`, and more) — the full
alias list is on the
[Database support matrix](../../databases/support-matrix/). A URL with an
unrecognized scheme fails with `unsupported database dialect`.

A MySQL or MariaDB server behind a Unix socket takes the Atlas CLI socket
form, `mysql+unix://user:pass@/run/mysqld/mysqld.sock?database=app`, or
`mariadb+unix://` and `maria+unix://`: the path is the socket, `database`
names the database, and a host is ignored. The Go-driver form
`mysql://user:pass@unix(/run/mysqld/mysqld.sock)/app` reaches the same socket.

A MySQL or MariaDB URL naming no database is the whole server, as in the
Atlas CLI; as a dev database it is a
[dev server](../../atlas/migrate-commands/#a-whole-dev-server).

A password holding a character a URL reserves, such as `@ : / ? # %`, is
percent-encoded in a URL. The Go-driver form takes it as written, because that
driver reads a password up to the last `@` before the last `/`. A URL written with
such a character raw is refused, and the error says so.

Ptah never repeats a password. Output, errors and reports show an address with
its password as `***` and the value of a secret parameter, such as `sslpassword`,
`authToken` or `TOKEN`, as `redacted`, in every form an engine takes: a URL, a
Go-driver DSN with or without a scheme, and a keyword, ADO or ODBC string. The
rest of the address stays, so the line still says which database it means.

## The four database roles

**The target database** is the one a command reads or changes: `--db-url` on
native commands, `--url` on Atlas-compatible ones. It is the only database
whose state matters after the command exits.

**A dev database** (`--dev-url`) is a disposable replay target used for
validation:

- `ptah migrations validate` and `ptah migrations lint` clean it and replay the
  migration directory on it to prove the SQL executes;
- `schema apply` rehearses its plan on it before touching the target;
- `schema diff` replays a migration directory on it, and creates a `--from`
  schema file on it when `--to` is a database or migration directory;
- Atlas-compatible verbs use it for planning, linting, and rollback
  verification.

A dev database must hold nothing the reset would drop when a command starts.
Ptah refuses a table before anything is dropped, as the Atlas CLI does, and
any other such object too. Ptah then cleans the
replay realm before migration execution and after the replay, failed or not.
Commands read the replayed state before the final cleanup, on the same session.
No fixed time limit applies to a cleanup: it takes as long as emptying the
realm takes. After an interrupt, the cleanup has 30 seconds to finish; a second
interrupt stops the process at once.

Everything a dev database runs stays inside it. Where a schema names a database
rather than a namespace — MySQL, MariaDB, and ClickHouse — a plan carrying the
target's schema name is re-scoped onto the dev database before it is rehearsed,
and a statement naming some third database is refused instead of run.

`mysql://`, `mariadb://` and `maria://` open one driver, and the server's
version banner, not the scheme, says which engine answers. A dev URL may spell
the family differently from its target, and two spellings of one database are
one database. `schema apply` and `migrate diff` refuse a MySQL dev database for
a MariaDB target, and the reverse.

**A shadow database** (`--shadow-db`) is a disposable verification target for
commands that write or record migrations: `ptah migrations generate` replays
the directory — including the new migration, up, down, and up again — before
keeping any files, and `ptah migrations checkpoint` and
`ptah migrations baseline` use it to verify that migrations reproduce the
expected schema before anything is recorded. `ptah migrations down` uses it to
verify the rollback plan before changing the target.

The shadow database must meet the same rule and must not be the target's live
database realm. Ptah checks both before changing either database, and empties
the shadow database again after each run.

**A throwaway test database** is what `ptah migrations test` and
`ptah schema test` run cases against: by default a fresh ephemeral SQLite
database per case, or the database passed with `--db-url` when tests must
exercise a real server dialect — see
[Test migrations and schemas](../../testing/migrations-and-schema/). On YDB the
provider runs the cases in a dev realm in that database.

## Consequences

- **Disposable means Ptah may drop everything it supports.** Dev and shadow
  database workflows clean user objects, and test seed steps bypass the
  seeder's protected-environment guards. Point these flags at scratch databases
  only, never at a real environment. Rollback verification, `schema apply`,
  `schema diff`, and `migrate diff` refuse, before any reset, a dev or shadow
  URL naming a database they read, by URL or by the realm each server reports.
  Equal network database names fail closed across different endpoints because
  DNS aliases and replicated members cannot be proven independent before
  destructive cleanup. Cleanup rejects system, template and metadata databases,
  and a server's default database unless the run owns it.
- **Migration-diff scope does not reduce the replay realm.** Repeated
  `--schema` values select which schemas `migrate diff` compares and emits. They do not
  limit which schemas a migration may create or which user schemas final
  cleanup removes. PostgreSQL extensions remain in both scoped projections:
  an extension is a database-wide identity, and its schema records installation
  placement rather than ownership by that schema. An extension declared on
  both sides remains synced even when installed outside the named schemas; an
  extension the replay created and the desired schema omits remains an explicit
  global removal. One the dev database held when the command started is its
  environment: a comparison leaves it out unless the desired schema declares
  it, so no migration drops it.
- **The replay realm follows the database engine.** PostgreSQL, CockroachDB,
  and YugabyteDB cleanup treats all user schemas and user-installed extensions
  in the selected database as one dependency graph. The row-security policies
  of its tables go before any relation: a policy holds back each relation it
  reads, so tables whose policies read each other's table are dropped too.
  Extensions the dev
  database held when the run started stay installed, with everything they own,
  so a migration may use a type from one without creating it, as under Atlas;
  a schema one is installed in is emptied in place. A dev URL that selects a
  schema with `search_path` also keeps the database's other schemas as they
  were, `public` included. An extension the run created is dropped with the
  schemas and tables it owns; TimescaleDB's catalog schemas are this shape.
  So are PostgreSQL and YugabyteDB default privileges: the global ones, and
  with `search_path` those set in the selected schema. A default the run set is
  taken back, so a Supabase image works as a dev database.
  MySQL, MariaDB, and ClickHouse cleanup owns the selected database. SQL
  Server cleanup owns all supported user schemas in the selected database.
  SQLite cleanup owns `main` on one pinned session. YDB has no SQL that
  creates a database, so its provider gives a run a dev realm instead: a
  directory under `ptah_dev` in the database its dev, shadow or test URL names,
  which the run treats as its whole database and the provider removes at the
  end. A run against the database itself leaves `ptah_dev` out, so the URL may
  name the target; see
  [YDB](../../extend/providers/ydb/usage/#dev-shadow-and-scratch-databases).
- **PostgreSQL cleanup gives back the schema it empties.** The schema the dev
  URL selects, `public` when it selects none, comes back after the cleanup
  with the owner, grants and comment it had when the run started. A role
  created on the server afterwards can use `public` as it could before. A run
  that revokes `USAGE` on `public` or comments it does not hand the change to
  the next run, and neither does one whose dev database keeps an extension in
  `public`, where the schema is emptied in place.
- **MySQL-family cleanup needs global catalog visibility.** MySQL cleanup
  credentials require global `SELECT`, `DROP`, `ALTER`, `ALTER ROUTINE`,
  `EVENT`, `LOCK TABLES`, and `PROCESS`. MySQL also requires global `TRIGGER`
  and, on MySQL 8.0.20 and newer, `SHOW_ROUTINE`; MariaDB requires global
  `SHOW VIEW`. Ptah checks these privileges before destructive DDL. It fails
  closed when another user database contains a routine, event, or trigger
  because stored-program bodies can reference the cleanup realm without a
  catalog dependency. Use dedicated server instances and credentials only for
  disposable dev databases.
- **ClickHouse realm cleanup requires 24.11 or newer.** Ptah uses `CHECK GRANT`
  with global `SHOW DATABASES` and `SHOW TABLES` to prove complete catalog
  visibility before dropping objects. ClickHouse does not expose ordinary-view
  dependencies, so Ptah fails closed when another user database contains a
  view, materialized view, live/window view, dictionary, or `Buffer`,
  `Distributed`, or `Merge` table. Older servers fail before cleanup because
  role-aware visibility cannot be proven safely.
- **PostgreSQL-family cleanup keeps the database-scoped artifacts it found.**
  Publications, subscriptions, logical replication slots, event triggers, and
  non-extension foreign-data wrappers, servers, or user mappings the dev
  database held when the command started are its environment, as under Atlas:
  the realm cleanup leaves them and checks they are still there. PostgreSQL
  and YugabyteDB reject one the run created before DDL, since the cleanup
  cannot remove it. An object the server created when it built the
  database template is not counted: YugabyteDB 2026.1.2 puts the foreign
  server `yb_global_views_server` into every database, and the cleanup leaves
  it, and the extensions it depends on, in place. PostgreSQL also removes and
  verifies database large objects transactionally. YugabyteDB does not run
  that PostgreSQL-specific large-object operation.
- **A docker block's starting point is kept whole.** A PostgreSQL dev
  database an `atlas.hcl`
  [`docker` block](../../atlas/project-config/#docker-dev-database-block)
  provisions starts from what its image and baseline leave. Cleanup returns
  that database to the starting point instead of emptying the realm, and
  refuses when the run dropped part of it or changed what no statement
  restores.
- **SQL Server cleanup rejects replication state.** A replication-enabled
  database or replicated table fails before DDL, along with other unsupported
  database-scoped artifacts. Remove replication configuration or use a
  dedicated disposable database before replay.
- **Cross-realm operations fail before execution.** Ptah rejects direct
  statements that switch or mutate another database, protected namespace,
  server, cluster, another session's temporary namespace, external file, or attached SQLite
  database. It also rejects statement forms whose nested SQL cannot be
  confined safely during replay.
- **Replay cleanup is serialized by realm.** PostgreSQL, YugabyteDB, MySQL,
  MariaDB, and SQL Server use database advisory locks, and the YDB provider a
  semaphore on the coordination node `ptah_locks`, keyed by the database and
  the dev realm.
  SQLite, ClickHouse, and
  CockroachDB use an operating-system file lock keyed by the normalized
  database identity. That file lock coordinates only Ptah processes that
  resolve the same temporary lock path and can access it, normally processes
  running as the same operating-system user with the same temporary-directory
  configuration. Different users, different temporary directories, other
  hosts, and non-Ptah clients are not coordinated. Cross-host ClickHouse and
  CockroachDB replay is unsupported because neither engine provides the
  required effective session advisory lock.
- **Match the target engine.** Replay verification proves the SQL executes on
  the engine it ran against, so a dev or shadow database should run the same
  engine (and ideally the same version) as the target.
- **Every flag has an environment variable.** `PTAH_DB_URL`, `PTAH_DEV_URL`,
  and `PTAH_SHADOW_DB` set the corresponding flags, which keeps credentials
  out of CI command lines. A malformed non-empty value fails before command
  execution. See [Configuration](../../reference/configuration/).

### Statement forms that fail closed

Replay rejects SQL sublanguages and storage mechanisms whose effects cannot be
proven to stay inside the disposable realm:

- PostgreSQL-family `DO`, `CALL`, routine creation or alteration, foreign-table
  creation or alteration, foreign servers, `IMPORT FOREIGN SCHEMA`,
  `SET search_path`, `SELECT INTO` protected namespaces, and CockroachDB's
  `ALTER DEFAULT PRIVILEGES FOR ALL ROLES` without `IN SCHEMA`.
- MySQL and MariaDB executable comments, `CALL`, events, triggers, routines,
  `LOAD DATA`/`LOAD XML`, and externally backed `FEDERATED` or `CONNECT`
  tables.
- SQL Server `EXEC`/`EXECUTE`, procedure/function/trigger creation or
  alteration, synonym/external-table creation, `BULK INSERT`, `BACKUP`,
  `RESTORE`, and server-level maintenance statements.
- ClickHouse remote, distributed, replicated, and unknown table engines,
  external dictionary sources, and `FREEZE`/`UNFREEZE`. Tables and standalone
  materialized views must select an explicitly allowlisted engine.
- SQLite `ATTACH`, `DETACH`, temporary objects, and non-restorable pragmas.
- YDB, through its provider's replay guard, writes whose target is an absolute
  path, climbs out of the dev realm
  with `..`, is named through a `$` expression or names a cluster,
  `PRAGMA TablePathPrefix`, `DEFINE ACTION`, `DO`, `EVALUATE`, users, groups,
  `GRANT`, `REVOKE`, secrets, resource pools, backups, `ALTER DATABASE`,
  topics, external data sources and tables, async replication, transfers, and
  streaming queries.

Replay runs every migration of a directory on one session, as `migrate apply`
runs the migrations of one run, so a PostgreSQL `SET` or `RESET` that changes
the session would carry into the migrations after it, and is refused. A
setting that ends with its transaction is accepted:

- `SET LOCAL` and `SET CONSTRAINTS`;
- `set_config(name, value, true)`, with the name written as a string and `true`
  written as the keyword.

PostgreSQL `CREATE TEMP TABLE` and `CREATE TEMPORARY TABLE` are accepted on the
replay's pinned physical session. Later statements and migrations can use those
tables, including through `pg_temp`. Ptah discards that session after success,
failure, or cancellation. Explicit `pg_temp_<number>` targets remain protected;
the replay cannot take ownership of another session's temporary tables.

A PostgreSQL-family `COMMENT ON SCHEMA` is accepted when the dev URL pins no
schema. The reset that ends the replay returns each schema it keeps to the comment
it held, and drops the others. A URL pinned to one schema is reset without reading
that state, so there the comment is refused. A privilege on a schema is refused in
every replay, because it depends on the server's roles, and so is a comment on a
protected namespace such as `pg_catalog`. The pinned community binary v1.3.0
replays all of these, so `ptah-compat` is stricter here, deliberately.

Replay runs each migration the way `migrations up` does, in one transaction
unless the file opts out with `-- +ptah no_transaction` or
`-- atlas:txmode none`. Such a setting holds for the rest of its migration and
is gone before the next. In a file that opts out, PostgreSQL ignores a
`SET LOCAL` with a warning, on replay and on apply alike.

A PostgreSQL migration file that opens and closes its own transaction replays
as written. Histories written for golang-migrate have files that begin with
`BEGIN` and end with `COMMIT`. Replay reads such a file as a whole before it
runs a statement and runs it without a transaction of its own. A file that fails
is rolled back on the pinned session before the dev database is cleaned. The
whole file is refused, with the statement named, when it:

- leaves a transaction open, closes one it did not open, or opens one inside
  another;
- uses `ROLLBACK`, `ABORT`, `SAVEPOINT`, `RELEASE` or `ROLLBACK TO`;
- writes `AND CHAIN`, `PREPARE TRANSACTION`, `COMMIT PREPARED`, or a `BEGIN`
  mode other than an isolation level, access mode or `DEFERRABLE`
  (`COMMIT AND NO CHAIN` is a plain `COMMIT`);
- asks for `-- atlas:txmode file` and opens its own transaction.

This holds for PostgreSQL only, and only where Ptah replays an existing
migration directory. Elsewhere in a replay each of these statements is refused
on its own. In a golang-migrate directory, and in one that `--runner
golang-migrate` or `migration.runner` names, replay runs a file the way
`ptah-compat migrate apply` runs it, which is how golang-migrate's default mode
runs it: a file that opens its own transaction runs in the transactions the
file sent as one query runs in, and a file of one statement runs without one.
The reading there also accepts what golang-migrate applies: a `COMMIT` outside
a block, a `BEGIN` inside one, and `SAVEPOINT`, `RELEASE` and `ROLLBACK TO`
inside one; see
[Transactions in a golang-migrate directory](../../atlas/migrate-commands/#transactions-in-a-golang-migrate-directory).
`LOCK TABLE` is accepted for the same reason as `SET LOCAL`; a lock on a table in
a protected namespace is refused.

Three parameters stay refused even for one transaction. `search_path` (and its
alias `SET SCHEMA`) decides where an unqualified name lands, which would let a
`CREATE FUNCTION` reach `pg_catalog`. `role` and `session_authorization` change
who the rest of the migration runs as.

The rejection happens while Ptah validates the whole migration, before its
first statement executes. Realm-local removals Ptah can classify without
reading a routine body, such as `DROP FUNCTION`, `DROP FOREIGN TABLE`,
`DROP SYNONYM` and `DROP EXTERNAL TABLE`, stay allowed.

### A server Ptah provisions

A `docker://` or
[`docker+<driver>://`](../../atlas/migrate-commands/#a-dev-database-from-a-named-image)
dev URL starts a server for one command and removes it afterwards, so the
server holds only what the run put there. On such a server,
replay also runs the statements it refuses elsewhere only because their effect
reaches past the dev database:

- PostgreSQL-family `DO`, `CALL`, routine creation and alteration,
  `CREATE` and `DROP` of a role, user, group or database, `ALTER ROLE`
  without `SET` or `RESET`, role membership, privileges on a role, database,
  schema, language or parameter, CockroachDB's
  `ALTER DEFAULT PRIVILEGES FOR ALL ROLES` without `IN SCHEMA`, `DROP OWNED`,
  `REASSIGN OWNED`, and `COMMENT ON` a role, a database, or an extension or
  schema of the dev database. A server declared disposable lifts all of these
  except the comment on an extension or a schema, because that server outlives
  the run. A [rehearsal](#the-baseline-a-rehearsal-writes) allows a schema's own
  comment and privileges where its reset returns them.
- MySQL and MariaDB routines, triggers, `CALL`, `GRANT`, `REVOKE`, `CREATE`
  and `DROP` of a user, role or database, and writes to another database.
- YDB writes anywhere in the server, `PRAGMA TablePathPrefix`, actions, users,
  groups, permissions, secrets, resource pools, backups and topics, on
  `docker://ydb/<tag>`, which the YDB provider starts. YDB gets no dev realm
  there: the database is the dev database.

So a migration directory that creates its role in a `DO` block or defines a
function replays on `docker://postgres/18/dev`.

The rest of the list above still fails closed on a provisioned server. Those
statements change the replay session or the sessions the rest of the command
opens (`SET search_path`, `ALTER ROLE ... SET`, any `ALTER DATABASE`,
transaction control), change how the command's later SQL runs (`ALTER SYSTEM`,
event triggers, casts, languages), reach past the container (foreign servers,
subscriptions, `dblink`, an external `COPY`), or mutate a protected namespace
such as `pg_catalog`. A MySQL or MariaDB event stays refused because the
scheduler runs it during the rest of the command.

A YDB external data source or table, async replication, a transfer and a
streaming query reach outside the container and stay refused. local-ydb takes
every connection without a credential, so the YDB provider refuses
`docker://ydb` when the container runtime is on another machine, where its port
would be published on every interface of that host.

A `docker://<engine>` URL whose engine belongs to a selected
[executable provider](../../extend/executable-providers/) goes to that provider
instead. The provider picks the image, starts the server and removes it when the
command ends. Ptah records the address it gets back as a server the run owns, as
for a server it starts itself. A provider that will not provision answers with
a refusal that says whether it does not serve the request or its runtime is
unavailable, and the command stops with that reason.

The decision reads what Ptah recorded when it started the server, not the
URL's spelling. A named server keeps the whole list, since it may hold
databases and roles that are not the run's. Its refusal names the two ways to
this realm: a docker URL and the declaration below.

### A server declared disposable

A server Ptah did not start can be as disposable: a CI service container
the job throws away, or a container the operator runs. `PTAH_DEV_SERVER_DISPOSABLE=1` declares that the server
`--dev-url` names is the run's own. Replay then runs the statements listed
above on it, as on a server Ptah provisions, except a comment on an extension
or a schema of the dev database, and refuses the same rest.

```bash
PTAH_DEV_SERVER_DISPOSABLE=1 ptah migrations validate --dir migrations --dev-url "$DEV_URL"
```

The declaration covers the whole server, its default database (`postgres`)
included. After a replay Ptah cleans the dev database. On PostgreSQL,
CockroachDB and YugabyteDB it also drops every role the server did not hold
when the command claimed the dev database. On a server nothing else uses, those
roles are the run's, so a later command that replays the same directory creates
them again. A role the server already held stays, whoever created it. A YDB dev
URL declared this way gets no dev realm: its database is the dev database, and
it has to be empty. A database a replay creates, and a role or a user on any
other engine, stays on the server until the container is removed. A later
command that replays the same directory on the same server meets it, so a
migration that creates one without checking for it first fails the second
time. Declare only a server that nothing else uses.

Every command that replays a migration directory or rehearses a plan on a dev
database reads the variable, on both binaries, and refuses a non-boolean value
before any work, whether or not it replays. Strict Atlas
compatibility keeps the variable, because the pinned community binary runs
these statements on any dev database.

### The baseline a rehearsal writes

Before `schema apply` rehearses its plan, it recreates the target's current
schema in the dev database. Every statement of that baseline is checked
against the same rules as a replay before the first one runs. A routine or
trigger body can write outside the dev database when it runs, and a role, a
user or a privilege the cleanup does not remove outlives it, so a target that
holds one is rehearsed only on a server the run owns: one Ptah provisions from
a docker URL, or one declared with `PTAH_DEV_SERVER_DISPOSABLE=1`. The refusal
names the baseline statement and these two remedies.

Comments follow a narrower rule. A comment on a table or a column is written.
The comment an extension's control file gives is not written, because `CREATE
EXTENSION` sets it; Ptah reads that comment from the dev server. A comment the
target gave an extension itself is written only on a server Ptah provisions
from a docker URL, because the cleanup does not restore it and a server declared
disposable keeps it for the next run.

A schema's own comment and the privileges on it are written on any server,
because they end with the run. A target often holds them, on `public` or on a
schema of its own, and a dev database whose `public` was dropped and created
again has neither. On PostgreSQL, CockroachDB and YugabyteDB, the claim records
the owner, access list and comment of the schemas a reset keeps, and every reset
returns those schemas to them and drops the others. A dev URL that pins one schema
with `search_path` is the exception: its cleanup leaves that schema as it finds
it, so they are refused there, with the remedies above. A privilege for a role the
dev server does not hold fails on that server, as it does on a table.

The plan is held to the same rules, before the first statement of the
baseline or the plan runs. The plan comes from the desired schema, so a role,
a routine or trigger body, or a comment the cleanup keeps is rehearsed only
on a server the run owns. On a server the operator named, the rehearsal
refuses, names the plan statement and the remedies, and leaves the target and
the dev server as they were. Before this check, a role the rehearsal created
outlived the cleanup on the dev server, and the real apply then failed on it
when the target was on the same server.

In a YDB dev realm, the baseline leaves out the permissions the target holds
on its database root. The realm's root would stand in for the target's, but a
reset in the middle of a run keeps the realm's root and its permissions, so a
grant written there would reach the next target rehearsed in the realm.

## Where it appears

- Replay validation with a dev database: [Integrity and safety](../../versioned/integrity-and-safety/).
- Linting against a dev database: [Lint and gate unsafe SQL](../../versioned/lint/).
- Shadow-verified generation and baselining: [Generate migrations](../../versioned/generate/) and [Adopt an existing database](../../start/adopt-an-existing-database/).
- Engine-specific URL behavior: [Database support matrix](../../databases/support-matrix/).
