Skip to content

Switch engines

Engine migration

Most of AskDB is engine-agnostic. The dialect is the part that knows your SQL flavor — change the dialect id, and the same schema-author workflow and the same ask() call work against a different engine.

  • The schema artifact format. Same schema.json, same tables/*.md, same enrichment workflow.
  • The ask() API. Same options, same return shape.
  • The SQL checks: must start with SELECT or WITH, single statement (with a SQL Server caveat), no comments, no write or DDL keywords, no locking reads, no calls to known side-effecting functions, and the heuristic tenant and sensitive-column checks. Per engine, only the detail changes: SQL is split into tokens the way that engine reads strings, quoted identifiers, and comments, and each engine adds its own entries to the keyword denylist, the function denylist, or both. The locking reads rejected follow each engine’s syntax too, such as LOCK IN SHARE MODE on MySQL and WITH (UPDLOCK) on SQL Server. See Safety boundaries.
  • The privacy model. The model sees schema, not rows, regardless of engine.
  • The engine package you install.
  • The dialect. A direct ask() call passes it; the CLI and @askdb/client infer it from the schema artifact (see step 3).
  • The dialect’s prompt brief, which tells the model the engine’s SQL (ILIKE and ::type on Postgres, TOP on SQL Server), and the read-only check’s rules for the engine (extra forbidden keywords and blocked functions).
  • How the prompt ask() sends quotes names. A schema, table or column whose name is one of the engine’s reserved words is listed quoted the engine’s way, so the model copies it quoted: billing."order" on Postgres, billing.`order` on MySQL and MariaDB, billing.[order] on SQL Server, "order" on SQLite. On Postgres, which folds unquoted names to lowercase, a name with capitals is quoted too, so a Prisma table Post is listed as public."Post". Where tables are listed with their schema, the prompt also tells the model that when it quotes a qualified name, it quotes each part separately ("billing"."order", not "billing.order").
  • The driver your app uses to execute (pg, mysql2, better-sqlite3, mssql).
  • How the CLI introspects the database (connection-string format and catalog queries differ per engine).
EngineEngine packagedialect valueSuggested driver
PostgreSQL@askdb/postgres"postgres"pg
MySQL@askdb/mysql"mysql"mysql2
MariaDB@askdb/mysql"mariadb"mysql2
SQLite@askdb/sqlite"sqlite"better-sqlite3
SQL Server@askdb/sqlserver"sqlserver"mssql
Terminal window
npm install @askdb/postgres ai

If you plan to run live introspection, also add the driver:

Terminal window
npm install pg

You can keep the old engine package installed during a transition. Each engine package is self-contained. Database drivers are optional peers used only by live introspection or by your own execution layer.

2. Introspect the new database (if the schema differs)

Section titled “2. Introspect the new database (if the schema differs)”

If you are pointing at a different database instance — one that may have a different set of tables or columns — run introspection against it to regenerate the physical layer of the schema artifact:

Terminal window
npx askdb introspect \
--engine postgres \
--url "postgresql://user:pass@host/dbname" \
--out my-app.schema \
--schema-id my-app

On Postgres and SQL Server, introspection.schemas lists schemas (the default is every non-system schema). On MySQL and MariaDB it lists databases, as above. SQLite has a single namespace and ignores it. askdb introspect --schemas app,sales does the same for one run and overrides the configured list.

The connection string format follows each engine’s convention. The engine itself comes from --engine (or introspection.provider in askdb.config.ts) — the CLI does not infer it from the URL. The artifact records which engine produced it, and askdb ask infers the dialect from the artifact.

For one-off live introspection without installing askdb in the project, include the driver in the same ephemeral command:

Terminal window
pnpm dlx -p askdb -p mysql2 askdb introspect --engine mysql --url "$MYSQL_URL"
npx -p askdb -p mssql askdb introspect --engine sqlserver --url "$SQLSERVER_URL"

If the logical schema is identical and you are only swapping the engine package, the existing artifact is already valid — skip this step and go straight to step 3.

3. Update the dialect value in ask() calls

Section titled “3. Update the dialect value in ask() calls”

The schema artifact records which engine it was introspected from. The CLI (askdb ask) and @askdb/client use it to infer the dialect, so those entry points need no update. A dialect set in askdb.config.ts takes precedence over the artifact’s engine: remove it, or change it, when you switch.

This holds for Prisma-introspected artifacts too: the connector reads the datasource.provider from your .prisma file (postgresql → postgres, mysql, sqlite, sqlserver, cockroachdb) and records it, so askdb ask still knows the dialect without a live database.

When using @askdb/client with no dialect in the config, the dialect resolves from the schema artifact’s recorded provider, the same inference the CLI uses. No code change is needed after a dialect switch if the new artifact declares the correct provider.

To force a specific dialect (for example, if the artifact’s provider doesn’t match your target engine), pass it as a per-call override:

// askdb initialized at startup with createAskDb() — see Embed in a Node app.
const { sql } = await askdb.ask(question, {
dialect: "mysql", // overrides auto-resolution
});

All six dialect specs (postgres, cockroachdb, mysql, mariadb, sqlite, sqlserver) ship inside @askdb/core. The engine packages (@askdb/mysql, @askdb/postgres, …) supply the introspection connector for that engine (and re-export its dialect constant for convenience); you need them to introspect, not to generate.

Swap the driver library for the one that matches the new engine. The execution layer is yours — AskDB doesn’t open the connection — so this is a straight library swap.

import { Pool } from "pg";
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
statement_timeout: 5000,
max: 10,
});
const { rows } = await pool.query(sql);

Postgres is the reference dialect — features ship there first and the other engines follow. For most analytical questions, the engines this guide covers generate equivalent SQL; for engine-specific features (JSON operators, window function syntax, regex variations), behavior follows what each engine supports natively.

If you hit a dialect difference that surprises you, that’s a bug — file it on the project issues.

Nothing stops you from querying two schema artifacts against different dialects in the same process. With @askdb/client, pass the schema and dialect as per-call overrides:

// askdb initialized at startup with createAskDb() — see Embed in a Node app.
const fromOrders = await askdb.ask(question, {
schema: { path: "./orders.schema" },
dialect: "postgres",
});
const fromAnalytics = await askdb.ask(question, {
schema: { path: "./analytics.schema" },
dialect: "mysql",
});