NL2SQL 0.6.0
Phase 2: every built-in adapter is now tested against real PostgreSQL, MySQL and SQL Server servers, adapters are built on SDK v2 with a conformance kit, storage has four pluggable stores, LLM and embeddings providers take any OpenAI-compatible server, and policies gain allow and deny globs. Several config keys and interfaces change: read Upgrading before you install it.
Released 2026-10-15.
Highlights
- Tested on real databases. A test bed runs PostgreSQL with Pagila, MySQL with Sakila and SQL Server with AdventureWorksLT, plus a generated schema of 1,200 tables. It runs in CI on every PR that touches database code, and on a GCP VM on demand. The VM run found three bugs that CI had hidden, all fixed in this release.
- Adapter SDK v2 and a conformance kit. Each adapter has a typed connection model, so a misspelt key gets "did you mean". You can choose which schemas and tables to index (
IndexScope). Row counts come from the database catalog. A table that cannot be read becomes an issue in the index build instead of a crash. Read-only sessions and statement timeouts are enforced where the database allows it and reported where it does not, and a failed connection test names the stage that failed. The SQLAlchemy base is public innl2sql-sdk[sqlalchemy], andnl2sql-sdk[testing]is the kit that all five built-in adapters pass. - Four storage slots.
storagehasindex,state,tracesandresults, each a built-in or a plugin store. The index's build pointer is now the commit, so a failed build leaves the previous index live. Each embedding model gets its own namespace. Traces and results are deleted after a set number of days. - Providers and policies.
openai_compatiblewith abase_urlworks with any OpenAI-style server, withopenrouterandollamaas presets of it. Each agent can setstructured_output,max_tokens,timeout_s,max_retriesandheaders, and a malformed answer is retried with the error. Policies useallowanddenyglobs ondatasource.schema.table, the planner never sees a denied table, and aPolicyCheckplugin can add your own access rules.engine.datasources.types(),test()andadd()are the methods a Connect-a-database form needs.
Real-database results
- The CI
realdbjob passed on every PR that touched database code. - The run on the GCP VM on 2026-10-14 (
python scripts/testbed.py run -- pytest -m realdb): 110 passed, 4 skipped, 0 failed. The four skips are expected. SQL Server does not declareENFORCES_READ_ONLY(2 tests), and the 60-second target for reading the wide schema is measured on PostgreSQL only (2 tests). - Runs on the real VM found and fixed three problems. MySQL 8 logins needed
cryptography, and SQL Server used a hard-coded ODBC driver (#303). The test bed also had Windows problems (#299, #301, #302, #304).
What's included
Features
- Embeddings providers v2:
openai_compatible,base_urlon the hosted providers, a knowndimensionfor each model, and one probe call when the dimension is unknown (#281). - Store interfaces in
nl2sql_sdk.stores, the fourstorageslots and thestoreregistry kind (#283). - LLM providers v2:
openai_compatible, withopenrouterandollamaas its presets, and the agent fieldsstructured_output,max_tokens,timeout_s,max_retriesandheaders. Header values are never printed (#284). - Policy
allowanddenyglobs, oneScopewherever access is decided, and denied tables hidden from the planner (#285). - The real-database test bed: a Docker Compose file, the
realdbpytest marker and a CI workflow (#286). - A malformed structured answer is sent back to the model with the error, up to
parse_retriestimes. After that the run fails withMODEL_OUTPUT_INVALID(#287). - The
PolicyCheckextension point. Its grants are intersected with the YAML roles, and it denies if it fails (#288). - The index store: one namespace per embedding model, the build pointer as the commit, and
nl2sql index prune(#289). scripts/testbed.pystarts, uses and stops the GCP test-bed VM through IAP (#290).- Adapter SDK v2:
DatasourceAdapter, strict connection models with "did you mean", andIndexScope(#291). - Trace and result stores with
max_age_daysretention,engine.storage.info()andpurge(), andnl2sql storage purge(#292). - The
sqlitestate store: snapshots that are never changed, migrations that adopt a 0.5state.db, and a lock during index builds (#293). - The SQLAlchemy base in
nl2sql-sdk[sqlalchemy]. It reads the schema once, takes row estimates from the catalog, collects sample values only when asked, and reports index issues (#295). engine.datasources.types(),test(Datasource)without registering it, andadd()checking the index scope (#296).- Read-only sessions and statement timeouts on every adapter that can enforce them, and connection tests that name the failed stage (#297).
- The adapter conformance kit,
nl2sql-sdk[testing], run on all five built-in adapters (#298).
Fixes
- The SDK docs page no longer has leftover merge-conflict markers, and CI now fails on any it finds (#294).
- DuckDB schemas are read from DuckDB's own catalog, so indexing works on SQLAlchemy 2.1 (#300).
nl2sql-engine[mysql]installscryptographyfor MySQL 8 logins. SQL Server uses the newest installed ODBC driver, with a clear message when none is installed (#303).- Fixes to the test bed on Windows and on the VM: passwords, Docker start-up, SQL Server start-up, and stopping cleanly (#299, #301, #302, #304).
Docs
- The Phase 2 design: test bed, storage, providers, policies and adapter SDK v2 (#279). The test bed, storage, policies and adapter SDK pages are written or rewritten in the PRs above.
Known limits
- SQL Server does not make the session read-only.
check()warns about it, so connect with a login that can only read. - The built-in stores are for a single replica. A PostgreSQL state store and a pgvector index, for several replicas, come later.
- There is no built-in S3 or ADLS results store. Write one as a
ResultStoreplugin; the Artifact Store page has an S3 example. - Policies go down to tables. Column denies, row filters and user attributes come later.
engine.datasources.types(),test()andadd()are Python only. The REST routes and the Connect-a-database form come in Phase 4.- There are no Snowflake or BigQuery adapters yet.
- The 60-second target for reading the 1,200-table schema is measured on PostgreSQL only. Retrieval limits as settings and incremental re-indexing come after 0.6.
Upgrading
Adapter, store and policy-check authors: pip install "nl2sql-sdk==0.6.0" (add [sqlalchemy] for the SQLAlchemy base, [testing] for the conformance kit).
Breaking changes: several. Each needs a look before you upgrade.
- Storage sub-blocks and a new index namespace (#283, #289, #292).
storagehasindex,state,tracesandresultssub-blocks.pathon its own is still valid. The index now lives in one namespace per embedding model, and 0.6 never reads 0.5'snl2sql_storecollection. Traces are deleted after 14 days and results after 7 by default. What to do: runnl2sql indexafter upgrading, thennl2sql index pruneto drop the 0.5 collection. Setmax_age_daysontracesorresultsif you want them kept longer. A 0.5state.dbis adopted as it is. - Policy patterns (#285).
allowanddenyreplaceallowed_datasourcesandallowed_tables. A pattern is*,datasourceordatasource.schema.table. A two-part pattern such assales.ordersis rejected when the config loads, and matching ignores case. What to do: rewrite each role, for examplesales.ordersassales.<schema>.ordersorsales.<schema>.*. - Denied tables are hidden from the planner (#285). A table a role cannot read never reaches the model. A user who asks about one sees "can't be answered from the connected data". A plan that still names a hidden table is refused by the validator. What to do: check that each role allows the tables its users need.
sample_valuesis off by default (#295). Column values are no longer collected into the index and prompts unlessindex.sample_valuesis on. The demo keeps them on. What to do: setindex: {sample_values: true}on a datasource whose answers need its values, then runnl2sql index.- Strict connection keys (#291). An unknown key in a datasource's
connectionblock is now an error with a "did you mean" hint. It used to be ignored. What to do: runnl2sql config checkand fix what it reports. - Adapter interface version 2 (#291, #295).
DatasourceAdapterreplacesDatasourceAdapterProtocol, with no alias, and a version 1 adapter is refused when it loads. What to do: port your adapter toDatasourceAdapter, ideally onnl2sql_sdk.sqlalchemy.SQLAlchemyAdapter, and run the conformance kit. See Adapter development. - LLM and embeddings provider interface version 2 (#281, #284). A version 1 LLM or embeddings provider is refused when it loads. What to do: add
structured_outputto an LLM provider anddescribe()to an embeddings provider, and setinterface_version = 2. - Deleted index arguments and setting (#289).
index.build(full=..., switch_guard=...),nl2sql index --fullandsettings.schema.version_mismatchare gone. What to do: drop them. A failed build now leaves the previous build live, so there is nothing to guard. engine.policies.allowed()is nowengine.policies.scope()(#285). A flat table list cannot express a deny. What to do: callscope(user_context, datasource_id)and ask itallows(schema, table).- S3 and ADLS results only through a plugin (#292). What to do: implement the
ResultStoreinterface and set it as theresultsstore. - SQL Server always connects with
Encrypt=yes(#303). ODBC Driver 17 did not encrypt by default. What to do: for a server with a self-signed certificate, settrust_server_certificate: truein itsconnectionblock. nl2sql-engine[mysql]installspymysql[rsa](#303), which brings incryptography. What to do: nothing, unless you pin dependencies, in which case allowcryptography.
What's next
Next: Phase 3, hooks, observers and evals.
Full changelog: v0.5.0...v0.6.0