Skip to content

Commit d4ccb4d

Browse files
committed
Catalog from engine plugin
1 parent bd1f56e commit d4ccb4d

File tree

6 files changed

+556
-38
lines changed

6 files changed

+556
-38
lines changed

docs/guides/engine-plugins.md

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,8 @@ Return `statements`: one `Statement` per query block. Each `Statement` has:
123123
- `parameters` — Parameters for this statement.
124124
- `columns` — Result columns (names, types, nullability, etc.) for this statement.
125125

126+
You may also return **`catalog`** (optional). When present, sqlc passes it to the codegen plugin so codegen can emit model structs from the schema (tables/columns), not only per-query row types. Build an `engine.Catalog` from your schema (e.g. from `schema_sql` or DB metadata): one or more **CatalogSchema**, each with **CatalogTable** (rel = Identifier, columns = **CatalogColumn**). See `protos/engine/engine.proto` for the `Catalog`, `CatalogSchema`, `CatalogTable`, `CatalogColumn`, and `Identifier` messages.
127+
126128
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:
127129

128130
- `engine.CommentSyntax` — Which comment styles to accept (`Dash`, `SlashStar`, `Hash`).
@@ -151,14 +153,22 @@ func handleParse(req *engine.ParseRequest) (*engine.ParseResponse, error) {
151153
st.Columns = extractColumns(b.SQL, schema)
152154
statements = append(statements, st)
153155
}
154-
return &engine.ParseResponse{Statements: statements}, nil
156+
resp := &engine.ParseResponse{Statements: statements}
157+
if cat := buildCatalogFromSchema(schema); cat != nil {
158+
resp.Catalog = cat
159+
}
160+
return resp, nil
155161
}
156162
```
157163

158-
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).
164+
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). If you return a **Catalog** (tables/columns from schema or DB), sqlc forwards it to the codegen plugin so generated code can include model types from the schema.
159165

160166
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.
161167

168+
#### Catalog (optional)
169+
170+
If the plugin returns a non-empty **`catalog`** in `ParseResponse`, sqlc converts it to the codegen plugin’s Catalog and passes it in the GenerateRequest. Codegen plugins can then emit model structs from the schema (e.g. `type Author struct`) in addition to per-query row types. Build the catalog from `schema_sql` or from live DB metadata when using `connection_params`. The `engine.proto` defines `Catalog`, `CatalogSchema`, `CatalogTable`, `CatalogColumn`, and `Identifier` for this purpose.
171+
162172
### 3. Build and run
163173

164174
```bash
@@ -188,7 +198,7 @@ The protocol and Go SDK are in this repository: `protos/engine/engine.proto` and
188198

189199
## Architecture
190200

191-
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.
201+
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) and optionally a **catalog** (tables/columns from schema or DB). sqlc passes each statement to codegen as a separate query and, when present, passes the catalog so codegen can emit model types from the schema.
192202

193203
```
194204
┌─────────────────────────────────────────────────────────────────┐

internal/cmd/plugin_engine.go

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"github.com/sqlc-dev/sqlc/internal/config"
1818
"github.com/sqlc-dev/sqlc/internal/metadata"
1919
"github.com/sqlc-dev/sqlc/internal/multierr"
20+
"github.com/sqlc-dev/sqlc/internal/plugin"
2021
"github.com/sqlc-dev/sqlc/internal/sql/ast"
2122
"github.com/sqlc-dev/sqlc/internal/sql/catalog"
2223
"github.com/sqlc-dev/sqlc/internal/sql/sqlpath"
@@ -164,6 +165,7 @@ func runPluginQuerySet(ctx context.Context, rp resultProcessor, name, dir string
164165
}
165166

166167
var queries []*compiler.Query
168+
var pluginCatalog *plugin.Catalog
167169
merr := multierr.New()
168170
set := map[string]struct{}{}
169171

@@ -179,6 +181,9 @@ func runPluginQuerySet(ctx context.Context, rp resultProcessor, name, dir string
179181
merr.Add(filename, queryContent, 0, err)
180182
continue
181183
}
184+
if pluginCatalog == nil && resp.GetCatalog() != nil {
185+
pluginCatalog = engineCatalogToPlugin(resp.GetCatalog())
186+
}
182187
baseName := filepath.Base(filename)
183188
stmts := resp.GetStatements()
184189
for _, st := range stmts {
@@ -204,8 +209,9 @@ func runPluginQuerySet(ctx context.Context, rp resultProcessor, name, dir string
204209
}
205210

206211
result := &compiler.Result{
207-
Catalog: catalog.New(""),
208-
Queries: queries,
212+
Catalog: catalog.New(""),
213+
Queries: queries,
214+
PluginCatalog: pluginCatalog,
209215
}
210216
return rp.ProcessResult(ctx, combo, sql, result)
211217
}
@@ -231,6 +237,57 @@ func loadSchemaSQL(schemaPaths []string, readFile func(string) ([]byte, error))
231237
return strings.Join(parts, "\n"), nil
232238
}
233239

240+
// engineCatalogToPlugin converts engine.Catalog (from ParseResponse) to plugin.Catalog for codegen.
241+
func engineCatalogToPlugin(engineCat *pb.Catalog) *plugin.Catalog {
242+
if engineCat == nil {
243+
return nil
244+
}
245+
var schemas []*plugin.Schema
246+
for _, es := range engineCat.GetSchemas() {
247+
var tables []*plugin.Table
248+
for _, et := range es.GetTables() {
249+
rel := et.GetRel()
250+
if rel == nil {
251+
continue
252+
}
253+
var cols []*plugin.Column
254+
for _, col := range et.GetColumns() {
255+
cols = append(cols, &plugin.Column{
256+
Name: col.GetName(),
257+
NotNull: col.GetNotNull(),
258+
IsArray: col.GetIsArray(),
259+
ArrayDims: col.GetArrayDims(),
260+
Type: engineIDToPlugin(col.GetType()),
261+
Table: engineIDToPlugin(rel),
262+
})
263+
}
264+
tables = append(tables, &plugin.Table{
265+
Rel: engineIDToPlugin(rel),
266+
Columns: cols,
267+
Comment: et.GetComment(),
268+
})
269+
}
270+
schemas = append(schemas, &plugin.Schema{
271+
Name: es.GetName(),
272+
Tables: tables,
273+
Comment: es.GetComment(),
274+
})
275+
}
276+
return &plugin.Catalog{
277+
Comment: engineCat.GetComment(),
278+
DefaultSchema: engineCat.GetDefaultSchema(),
279+
Name: engineCat.GetName(),
280+
Schemas: schemas,
281+
}
282+
}
283+
284+
func engineIDToPlugin(e *pb.Identifier) *plugin.Identifier {
285+
if e == nil {
286+
return nil
287+
}
288+
return &plugin.Identifier{Catalog: e.GetCatalog(), Schema: e.GetSchema(), Name: e.GetName()}
289+
}
290+
234291
// statementToCompilerQuery converts one engine.Statement from the plugin into a compiler.Query.
235292
func statementToCompilerQuery(st *pb.Statement, filename string) *compiler.Query {
236293
if st == nil {

internal/cmd/shim.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,9 +224,13 @@ func pluginQueryParam(p compiler.Parameter) *plugin.Parameter {
224224
}
225225

226226
func codeGenRequest(r *compiler.Result, settings config.CombinedSettings) *plugin.GenerateRequest {
227+
cat := pluginCatalog(r.Catalog)
228+
if r.PluginCatalog != nil {
229+
cat = r.PluginCatalog
230+
}
227231
return &plugin.GenerateRequest{
228232
Settings: pluginSettings(r, settings),
229-
Catalog: pluginCatalog(r.Catalog),
233+
Catalog: cat,
230234
Queries: pluginQueries(r),
231235
SqlcVersion: info.Version,
232236
}

internal/compiler/result.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
package compiler
22

33
import (
4+
"github.com/sqlc-dev/sqlc/internal/plugin"
45
"github.com/sqlc-dev/sqlc/internal/sql/catalog"
56
)
67

78
type Result struct {
8-
Catalog *catalog.Catalog
9-
Queries []*Query
9+
Catalog *catalog.Catalog
10+
Queries []*Query
11+
// PluginCatalog is set by plugin engines (e.g. sqlc-engine-ydb) from ParseResponse.Catalog.
12+
// When non-nil, it is passed to codegen instead of converting Catalog.
13+
PluginCatalog *plugin.Catalog
1014
}

0 commit comments

Comments
 (0)