-
Notifications
You must be signed in to change notification settings - Fork 991
Add database engine plugins (external engines) #4247
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
asmyasnikov
wants to merge
26
commits into
sqlc-dev:main
Choose a base branch
from
ydb-platform:engine-plugin
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
+2,656
−13
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
asmyasnikov
commented
Dec 28, 2025
…c with new databases (in addition to PostgreSQL, Dolphin, sqlite)
fd040bc to
6c5b9a6
Compare
a5131b5 to
7609ebc
Compare
asmyasnikov
commented
Jan 27, 2026
asmyasnikov
commented
Jan 27, 2026
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
This PR resolve issue #4158 and adds support for database engine plugins: external processes that implement a single
ParseRPC and allow sqlc to work with databases that are not built-in (e.g. CockroachDB, TiDB, or custom SQL dialects). The plugin contract is deliberately minimal: no AST, no compiler in the middle, and a straight path from plugin output to codegen.Pipeline: built-in engine vs external plugin
Built-in engine (PostgreSQL, MySQL, SQLite)
flowchart LR subgraph input schema[schema.sql] queries[queries.sql] end subgraph sqlc_core parser[Parser] ast[(AST)] compiler[Compiler] catalog[(Catalog)] codegen_input[Queries + types] end subgraph output codegen[Codegen plugin] end schema --> parser queries --> parser parser --> ast ast --> compiler schema --> catalog catalog --> compiler compiler --> codegen_input codegen_input --> codegen*, validates against catalog, produces Queries.External engine plugin
flowchart LR subgraph input schema[schema.sql or connection] queries[queries.sql] end subgraph sqlc adapter[ProcessRunner] end subgraph plugin["Engine plugin (external process)"] parse[Parse] end subgraph plugin_output["Plugin returns"] sql[SQL text] params[parameters] cols[columns] end subgraph codegen_path["To codegen"] codegen_input[SQL + params + columns] codegen[Codegen plugin] end schema --> adapter queries --> adapter adapter -->|"ParseRequest{sql, schema_sql | connection_params}"| parse parse -->|"ParseResponse{sql, parameters, columns}"| plugin_output plugin_output --> codegen_input codegen_input --> codegen*expansion, and validation are the plugin’s job. sqlc does not run the built-in compiler on plugin output.[]ast.Statementfor compatibility; the useful payload issql+parameters+columns).So: for external engines, the pipeline is effectively schema + queries → engine plugin (Parse) → (sql, parameters, columns) → codegen, with no AST and no compiler in between.
Where and when the branch is taken
The choice between “built-in engine” and “external plugin” happens once per
sql[]block, when the compiler for that block is created.Call flow
internal/cmd/generate.goFor each entry in
sql[],parse()is called with that block’sconfig.SQL(which includesconf.Engine= value ofengine: ...).parse()callscompiler.NewCompiler(sql, combo, parserOpts)So every SQL block gets its own compiler, and the engine is selected inside
NewCompiler.internal/compiler/engine.go,NewCompiler(conf config.SQL, combo config.CombinedSettings, ...)This is where the branch is implemented:
conf.Engineis"sqlite","mysql", or"postgresql"→ the switch hits one of the first three cases; parser and catalog are the in-tree implementations.conf.Engineis any other string (e.g."mydb") → thedefaultcase runs;config.FindEnginePlugin(&combo.Global, string(conf.Engine))looks upcombo.Global.Engines(the top-levelengines:from sqlc.yaml) for an entry withName == conf.Engine. If found,createPluginEngine()builds either a process-based engine (plugin.NewPluginEngine(ep.Name, ep.Process.Cmd, dir, ep.Env)) or a WASM-based one; the compiler then useseng.Parser()andeng.Catalog()for that block. If the name is not inengines,FindEnginePluginreturnsfalseandNewCompilerreturns an error (“unknown engine”, with a hint to add it underengines).So: moment = creation of the compiler for one
sql[]block (NewCompiler); place =internal/compiler/engine.goin thatswitch conf.Engine+defaultwithconfig.FindEnginePlugin(defined ininternal/config/validate.go). The config is already loaded;combo.Globalholds the top-levelengines:list, andconf.Engineis theengine:field of the current block.No intermediate AST for external plugins
The plugin does not return an AST or “statements + AST”:
sql(possibly with*expanded),parameters,columns.The plugin is the single place that defines how the query is interpreted. sqlc does not parse or analyze that SQL again; it forwards the plugin’s
ParseResponsetoward codegen. Any internal use of[]ast.Statementfor the plugin path is a compatibility shim; the semantics are driven by the plugin’ssql/parameters/columns.No compiler for external plugins
The built-in compiler (catalog, type resolution, validation, expansion of
*) is not used for external engine plugins:SELECT *if desired.parametersandcolumnsthe codegen expects.What is sent to and returned from the plugin
Invocation: one RPC,
Parse, over stdin/stdout (protobuf).Example:
sqlc-engine-mydb parsewithParseRequeston stdin andParseResponseon stdout.Sent to the plugin (
ParseRequest)sqlqueries.sqlor the current batch).schema_sqlschema.sql.connection_paramsExactly one of
schema_sqlorconnection_paramsis used per request, depending on how the project is configured (see below).Returned from the plugin (
ParseResponse)sqlSELECT *expanded to explicit columns.parametersdata_type, nullable, is_array, array_dims.columnsdata_type, nullable, is_array, array_dims, optional table/schema.These three are enough for codegen to generate type-safe code without an AST or compiler step.
How the schema is passed into the plugin
Schema is provided to the plugin in one of two ways, via
ParseRequest.schema_source:Schema-based (files)
schema: "schema.sql") and passes their contents asschema_sql(a string) inParseRequest.CREATE TABLE ...) and uses it to resolve types, expand*, etc.Database-only
connection_params(DSN + optional extra options) inParseRequest.INFORMATION_SCHEMA/pg_catalog) to resolve types and columns.So: schema is either “schema.sql as text” or “connection params to the database”; the plugin chooses how to use it.
Changes in
sqlc.yamlNew top-level
enginesPlugins are declared under
enginesand referenced by name insql[].engine:engines: list of named engines. Each hasnameand eitherprocess.cmd(and optionallyenv) or a WASM config.sql[].engine: for that SQL block, use the engine namedmydb(which triggers the plugin) instead ofpostgresql/mysql/sqlite.So the only new concept in config is “define engines (including plugins) by name, then point
sql[].engineat them.” Schema and queries are still configured persql[]block as today.Who handles sqlc placeholders in queries
Support for sqlc-style placeholders (
sqlc.arg(),sqlc.narg(),sqlc.slice(),sqlc.embed(), etc.) is entirely up to the plugin:ParseRequest.sql.parameters(and, if needed, insqlor in how it uses schema). There is no separate “sqlc placeholder” pass in the core for the plugin path.So: the database engine plugin is responsible for understanding and handling sqlc placeholders for its engine.
Summary for maintainers
Parse(sql, schema_sql | connection_params) → (sql, parameters, columns).schema_sql(file contents) or asconnection_params(DSN) inParseRequest.engines[]+sql[].engine: <name>; existingschema/queries/codegenstay as-is.This keeps the plugin API small and leaves type resolution and dialect behavior inside the plugin, while still allowing sqlc to drive generation from a single, well-defined contract.