-
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
Open
asmyasnikov
wants to merge
46
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.
Open
Changes from all commits
Commits
Show all changes
46 commits
Select commit
Hold shift + click to select a range
b6aa62d
Added support for the database engine plugin system for extending sql…
asmyasnikov 5336821
Fix of endtoend tests
asmyasnikov 2b88994
added install plugin-based-codegen's
asmyasnikov b1d156d
remove tmp file
asmyasnikov 9f65d4f
removed go.{mod,sum}
asmyasnikov 74b621f
SQLCDEBUG=processplugins=1
asmyasnikov cede5d3
Fix
asmyasnikov 15b240d
Fix
asmyasnikov 0b3b165
Apply suggestions from code review
asmyasnikov 6c5b9a6
revert Combine
asmyasnikov 7609ebc
.gitignore + README
asmyasnikov 2c74313
simplified engine API
asmyasnikov 88e6082
Apply suggestions from code review
asmyasnikov f39ae4a
Delete protos/engine/engine_grpc.pb.go
asmyasnikov 18f5368
Delete protos/engine/engine.pb.go
asmyasnikov 8eaef3c
Delete pkg/plugin/sdk.go
asmyasnikov fbaf6ba
Delete pkg/engine/engine.pb.go
asmyasnikov ce385ae
Delete pkg/plugin/codegen.pb.go
asmyasnikov a024d3e
Delete examples/plugin-based-codegen/README.md
asmyasnikov fbd5b43
Delete examples/plugin-based-codegen/gen/rust/queries.rs
asmyasnikov e6a730a
docs
asmyasnikov c8831c7
removed example
asmyasnikov 6d5770f
fix
asmyasnikov d2417e8
Update .gitignore
asmyasnikov c50e9c7
pb.go
asmyasnikov e9cc264
fix comments
asmyasnikov ad7bf6c
simplified plugin engine code
asmyasnikov 5d4c8dd
sourceFiles
asmyasnikov 131d7bb
fix
asmyasnikov 048a64d
Apply suggestions from code review
asmyasnikov 79621b0
removed temp file
asmyasnikov d9df83b
Apply suggestions from code review
asmyasnikov 55760fc
Apply suggestions from code review
asmyasnikov 96dfabd
Apply suggestions from code review
asmyasnikov 85475e2
removed engine interface
asmyasnikov 0f81f5d
merge files
asmyasnikov 7800a42
move md doc
asmyasnikov f6b34f0
Apply suggestions from code review
asmyasnikov 830767e
revert changes
asmyasnikov e4667d2
revert
asmyasnikov 9b9b3ed
docs
asmyasnikov a8fec25
fix
asmyasnikov 778b45c
fixes and tests
asmyasnikov fb7e9a6
change ParseResponse - returns multiple statements from single call
asmyasnikov 13fc9f3
Merge branch 'sqlc-dev:main' into engine-plugin
asmyasnikov 12ffdbb
fix
asmyasnikov File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,210 @@ | ||
| # External Database Engines (Engine Plugins) | ||
|
|
||
| Engine plugins let you use sqlc with databases that are not built-in. You can add support for other SQL-compatible systems (e.g. CockroachDB, TiDB, or custom engines) by implementing a small external program that parses SQL and returns parameters and result columns. | ||
|
|
||
| ## Why use an engine plugin? | ||
|
|
||
| - Use sqlc with a database that doesn't have native support. | ||
| - Reuse an existing SQL parser or dialect in a separate binary. | ||
| - Keep engine-specific logic outside the sqlc core. | ||
|
|
||
| Data returned by the engine plugin (SQL text, parameters, columns) is passed through to [codegen plugins](plugins.md) without an extra compiler/AST step. The plugin is the single place that defines how queries are interpreted for that engine. | ||
|
|
||
| **Limitation:** `sqlc vet` does not support plugin engines. Use vet only with built-in engines (postgresql, mysql, sqlite). | ||
|
|
||
| ## Overview | ||
|
|
||
| An engine plugin is an external process that implements one RPC: | ||
|
|
||
| - **Parse** — accepts the **entire contents** of one query file (e.g. `query.sql`) and either schema SQL or connection parameters; returns **one Statement per query block** in that file (each with sql, parameters, columns, and name/cmd). | ||
|
|
||
| Process plugins (e.g. written in Go) talk to sqlc over **stdin/stdout** using **Protocol Buffers**. The protocol is defined in `protos/engine/engine.proto`. | ||
|
|
||
| ## Compatibility | ||
|
|
||
| For Go plugins, compatibility is enforced at **compile time** by importing the engine package: | ||
|
|
||
| ```go | ||
| import "github.com/sqlc-dev/sqlc/pkg/engine" | ||
| ``` | ||
|
|
||
| - If the plugin builds, it matches this version of the engine API. | ||
| - If the API changes in a breaking way, the plugin stops compiling until it's updated. | ||
|
|
||
| No version handshake is required; the proto schema defines the contract. | ||
|
|
||
| ## Configuration | ||
|
|
||
| ### sqlc.yaml | ||
|
|
||
| ```yaml | ||
| version: "2" | ||
|
|
||
| engines: | ||
| - name: mydb | ||
| process: | ||
| cmd: sqlc-engine-mydb | ||
| env: | ||
| - MYDB_DSN | ||
|
|
||
| sql: | ||
| - engine: mydb | ||
| schema: "schema.sql" | ||
| queries: "queries.sql" | ||
| codegen: | ||
| - plugin: go | ||
| out: db | ||
| ``` | ||
| ### Engine options | ||
| | Field | Description | | ||
| |-------|-------------| | ||
| | `name` | Engine name used in `sql[].engine` | | ||
| | `process.cmd` | Command to run (PATH or absolute path) | | ||
| | `env` | Environment variable names passed to the plugin | | ||
|
|
||
| Each engine must define either `process` (with `cmd`) or `wasm` (with `url` and `sha256`). See [Configuration reference](../reference/config.md) for the full `engines` schema. | ||
|
|
||
| ### How sqlc finds the process plugin | ||
|
|
||
| For an engine with `process.cmd`, sqlc resolves and runs the plugin as follows: | ||
|
|
||
| 1. **Command parsing** — `process.cmd` is split on whitespace. The first token is the executable; any further tokens are passed as arguments, and sqlc appends the RPC method name (`parse`) when invoking the plugin. | ||
|
|
||
| 2. **Executable lookup** — The first token is resolved the same way as in the shell: | ||
| - If it contains a path separator (e.g. `/usr/bin/sqlc-engine-mydb` or `./bin/sqlc-engine-mydb`), it is treated as a path. Absolute paths are used as-is; relative paths are taken relative to the **current working directory of the process running sqlc**. | ||
| - If it has no path separator, the executable is looked up in the **PATH** of the process running sqlc. The plugin binary must be on PATH (e.g. after `go install` or adding its directory to PATH) or `process.cmd` must be an absolute path. | ||
|
|
||
| 3. **Working directory** — The plugin process is started with its working directory set to the **directory containing the sqlc config file**. That directory is used for resolving relative paths inside the plugin, not for resolving `process.cmd` itself. | ||
|
|
||
| If the executable cannot be found or `process.cmd` is empty, sqlc reports an error and refers to this documentation. | ||
|
|
||
| ## Implementing an engine plugin (Go) | ||
|
|
||
| ### 1. Dependencies and entrypoint | ||
|
|
||
| ```go | ||
| package main | ||
| import "github.com/sqlc-dev/sqlc/pkg/engine" | ||
| func main() { | ||
| engine.Run(engine.Handler{ | ||
| PluginName: "mydb", | ||
| PluginVersion: "1.0.0", | ||
| Parse: handleParse, | ||
| }) | ||
| } | ||
| ``` | ||
|
|
||
| The engine API exposes only **Parse**. There are no separate methods for catalog, keywords, comment syntax, or dialect. | ||
|
|
||
| ### 2. Parse | ||
|
|
||
| sqlc calls Parse **once per query file** (e.g. once for `query.sql`). The plugin receives the full file contents and returns one **Statement** per query block in that file. sqlc then passes each statement to the codegen plugin as a separate query. | ||
|
|
||
| **Request** | ||
|
|
||
| - `sql` — The **entire contents** of one query file (all query blocks, with `-- name: X :one`-style comments). | ||
| - `schema_source` — One of: | ||
| - `schema_sql`: full schema as in schema.sql (for schema-based parsing). | ||
| - `connection_params`: DSN and options for database-only mode. | ||
|
|
||
| **Response** | ||
|
|
||
| Return `statements`: one `Statement` per query block. Each `Statement` has: | ||
|
|
||
| - `name` — Query name (from `-- name: GetUser` etc.). | ||
| - `cmd` — Command/type: use the `Cmd` enum (`engine.Cmd_CMD_ONE`, `engine.Cmd_CMD_MANY`, `engine.Cmd_CMD_EXEC`, etc.). See `protos/engine/engine.proto` for the full list. | ||
| - `sql` — Processed SQL for that block (as-is or with `*` expanded using schema). | ||
| - `parameters` — Parameters for this statement. | ||
| - `columns` — Result columns (names, types, nullability, etc.) for this statement. | ||
|
|
||
| The engine package provides helpers (optional) to split `query.sql` and parse `"-- name: X :cmd"` lines in the same way as the built-in engines: | ||
|
|
||
| - `engine.CommentSyntax` — Which comment styles to accept (`Dash`, `SlashStar`, `Hash`). | ||
| - `engine.ParseNameAndCmd(line, syntax)` — Parses a single line like `"-- name: ListAuthors :many"` → `(name, cmd, ok)`. `cmd` is `engine.Cmd`. | ||
| - `engine.QueryBlocks(content, syntax)` — Splits file content into `[]engine.QueryBlock` (each has `Name`, `Cmd`, `SQL`). | ||
| - `engine.StatementMeta(name, cmd, sql)` — Builds a `*engine.Statement` with name/cmd/sql set; you add parameters and columns. | ||
|
|
||
| Example handler using helpers: | ||
|
|
||
| ```go | ||
| func handleParse(req *engine.ParseRequest) (*engine.ParseResponse, error) { | ||
| queryFileContent := req.GetSql() | ||
| syntax := engine.CommentSyntax{Dash: true, SlashStar: true, Hash: true} | ||
| var schema *SchemaInfo | ||
| if s := req.GetSchemaSql(); s != "" { | ||
| schema = parseSchema(s) | ||
| } | ||
| // Or use req.GetConnectionParams() for database-only mode. | ||
| blocks, _ := engine.QueryBlocks(queryFileContent, syntax) | ||
| var statements []*engine.Statement | ||
| for _, b := range blocks { | ||
| st := engine.StatementMeta(b.Name, b.Cmd, processSQL(b.SQL, schema)) | ||
| st.Parameters = extractParameters(b.SQL) | ||
| st.Columns = extractColumns(b.SQL, schema) | ||
| statements = append(statements, st) | ||
| } | ||
| return &engine.ParseResponse{Statements: statements}, nil | ||
| } | ||
| ``` | ||
|
|
||
| Parameter and column types use the `Parameter` and `Column` messages in `engine.proto` (name, position, data_type, nullable, is_array, array_dims; for columns, table_name and schema_name are optional). | ||
|
|
||
| Support for sqlc placeholders (`sqlc.arg()`, `sqlc.narg()`, `sqlc.slice()`, `sqlc.embed()`) is up to the plugin: it can parse and map them into `parameters` (and schema usage) as needed. | ||
|
|
||
| ### 3. Build and run | ||
|
|
||
| ```bash | ||
| go build -o sqlc-engine-mydb . | ||
| # Ensure sqlc-engine-mydb is on PATH or use an absolute path in process.cmd | ||
| ``` | ||
|
|
||
| ## Protocol | ||
|
|
||
| Process plugins use Protocol Buffers on stdin/stdout: | ||
|
|
||
| ``` | ||
| sqlc → stdin (protobuf) → plugin → stdout (protobuf) → sqlc | ||
| ``` | ||
|
|
||
| Invocation: | ||
|
|
||
| ```bash | ||
| sqlc-engine-mydb parse # stdin: ParseRequest, stdout: ParseResponse | ||
| ``` | ||
|
|
||
| The definition lives in `protos/engine/engine.proto` (generated Go in `pkg/engine`). After editing the proto, run `make proto-engine-plugin` to regenerate the Go code. | ||
|
|
||
| ## Example | ||
|
|
||
| The protocol and Go SDK are in this repository: `protos/engine/engine.proto` and `pkg/engine/` (including `sdk.go` with `engine.Run` and `engine.Handler`). Use them to build a binary that implements the Parse RPC; register it under `engines` in sqlc.yaml as shown above. | ||
|
|
||
| ## Architecture | ||
|
|
||
| For each `sql[]` block, `sqlc generate` branches on the configured engine: built-in (postgresql, mysql, sqlite) use the compiler and catalog; any engine listed under `engines:` in sqlc.yaml uses the plugin path (no compiler). For the plugin path, sqlc calls Parse **once per query file**, sending the full file contents and schema (or connection params). The plugin returns **N statements** (one per query block); sqlc passes each statement to codegen as a separate query. | ||
|
|
||
| ``` | ||
| ┌─────────────────────────────────────────────────────────────────┐ | ||
| │ sqlc generate (plugin engine) │ | ||
| │ 1. Per query file: one Parse(schema_sql|connection_params, │ | ||
| │ full query file content) │ | ||
| │ 2. ParseResponse.statements = one Statement per query block │ | ||
| │ 3. Each statement → one codegen query (N helpers) │ | ||
| └─────────────────────────────────────────────────────────────────┘ | ||
| sqlc sqlc-engine-mydb | ||
| │──── spawn, args: ["parse"] ──────────────────────────────► │ | ||
| │──── stdin: ParseRequest{sql=full query.sql, schema_sql|…} ► │ | ||
| │◄─── stdout: ParseResponse{statements: [stmt1, stmt2, …]} ── │ | ||
| ``` | ||
|
|
||
| ## See also | ||
|
|
||
| - [Codegen plugins](plugins.md) — Custom code generators that consume engine output. | ||
| - [Configuration reference](../reference/config.md) | ||
| - Proto schema: `protos/engine/engine.proto` |
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
Oops, something went wrong.
Oops, something went wrong.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.