diff --git a/docs/howto/engine-plugins.md b/docs/howto/engine-plugins.md new file mode 100644 index 0000000000..b2c79b3b5c --- /dev/null +++ b/docs/howto/engine-plugins.md @@ -0,0 +1,251 @@ +# Database Engine Plugins + +sqlc supports adding custom database backends through engine plugins. This allows you to use sqlc with databases that aren't natively supported (like MyDB, CockroachDB, or other SQL-compatible databases). + +## Overview + +Engine plugins are external programs that implement the sqlc engine interface: +- **Process plugins** (Go): Communicate via **Protocol Buffers** over stdin/stdout +- **WASM plugins** (any language): Communicate via **JSON** over stdin/stdout + +## Compatibility Guarantee + +For Go process plugins, compatibility is guaranteed at **compile time**: + +```go +import "github.com/sqlc-dev/sqlc/pkg/engine" +``` + +When you import this package: +- If your plugin compiles successfully → it's compatible with this version of sqlc +- If types change incompatibly → your plugin won't compile until you update it + +The Protocol Buffer schema ensures binary compatibility. No version negotiation needed. + +## Configuration + +### sqlc.yaml + +```yaml +version: "2" + +# Define engine plugins +engines: + - name: mydb + process: + cmd: sqlc-engine-mydb + env: + - MYDB_CONNECTION_STRING + +sql: + - engine: mydb # Use the MyDB engine + schema: "schema.sql" + queries: "queries.sql" + gen: + go: + package: db + out: db +``` + +### Configuration Options + +| Field | Description | +|-------|-------------| +| `name` | Unique name for the engine (used in `sql[].engine`) | +| `process.cmd` | Command to run (must be in PATH or absolute path) | +| `wasm.url` | URL to download WASM module (`file://` or `https://`) | +| `wasm.sha256` | SHA256 checksum of the WASM module | +| `env` | Environment variables to pass to the plugin | + +## Creating a Go Engine Plugin + +### 1. Import the SDK + +```go +import "github.com/sqlc-dev/sqlc/pkg/engine" +``` + +### 2. Implement the Handler + +```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, + GetCatalog: handleGetCatalog, + IsReservedKeyword: handleIsReservedKeyword, + GetCommentSyntax: handleGetCommentSyntax, + GetDialect: handleGetDialect, + }) +} +``` + +### 3. Implement Methods + +#### Parse + +Parses SQL text into statements with AST. + +```go +func handleParse(req *engine.ParseRequest) (*engine.ParseResponse, error) { + sql := req.GetSql() + // Parse SQL using your database's parser + + return &engine.ParseResponse{ + Statements: []*engine.Statement{ + { + RawSql: sql, + StmtLocation: 0, + StmtLen: int32(len(sql)), + AstJson: astJSON, // AST encoded as JSON bytes + }, + }, + }, nil +} +``` + +#### GetCatalog + +Returns the initial catalog with built-in types and functions. + +```go +func handleGetCatalog(req *engine.GetCatalogRequest) (*engine.GetCatalogResponse, error) { + return &engine.GetCatalogResponse{ + Catalog: &engine.Catalog{ + DefaultSchema: "public", + Name: "mydb", + Schemas: []*engine.Schema{ + { + Name: "public", + Functions: []*engine.Function{ + {Name: "now", ReturnType: &engine.DataType{Name: "timestamp"}}, + }, + }, + }, + }, + }, nil +} +``` + +#### IsReservedKeyword + +Checks if a string is a reserved keyword. + +```go +func handleIsReservedKeyword(req *engine.IsReservedKeywordRequest) (*engine.IsReservedKeywordResponse, error) { + reserved := map[string]bool{ + "select": true, "from": true, "where": true, + } + return &engine.IsReservedKeywordResponse{ + IsReserved: reserved[strings.ToLower(req.GetKeyword())], + }, nil +} +``` + +#### GetCommentSyntax + +Returns supported SQL comment syntax. + +```go +func handleGetCommentSyntax(req *engine.GetCommentSyntaxRequest) (*engine.GetCommentSyntaxResponse, error) { + return &engine.GetCommentSyntaxResponse{ + Dash: true, // -- comment + SlashStar: true, // /* comment */ + Hash: false, // # comment + }, nil +} +``` + +#### GetDialect + +Returns SQL dialect information for formatting. + +```go +func handleGetDialect(req *engine.GetDialectRequest) (*engine.GetDialectResponse, error) { + return &engine.GetDialectResponse{ + QuoteChar: "`", // Identifier quoting character + ParamStyle: "dollar", // $1, $2, ... + ParamPrefix: "$", // Parameter prefix + CastSyntax: "cast_function", // CAST(x AS type) or "double_colon" for :: + }, nil +} +``` + +### 4. Build and Install + +```bash +go build -o sqlc-engine-mydb . +mv sqlc-engine-mydb /usr/local/bin/ +``` + +## Protocol + +### Process Plugins (Go) + +Process plugins use **Protocol Buffers** for serialization: + +``` +sqlc → stdin (protobuf) → plugin → stdout (protobuf) → sqlc +``` + +The proto schema is published at `buf.build/sqlc/sqlc` in `engine/engine.proto`. + +Methods are invoked as command-line arguments: +```bash +sqlc-engine-mydb parse # stdin: ParseRequest, stdout: ParseResponse +sqlc-engine-mydb get_catalog # stdin: GetCatalogRequest, stdout: GetCatalogResponse +``` + +### WASM Plugins + +WASM plugins use **JSON** for broader language compatibility: + +``` +sqlc → stdin (JSON) → wasm module → stdout (JSON) → sqlc +``` + +## Full Example + +See `examples/plugin-based-codegen/` for a complete engine plugin implementation. + +## Architecture + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ sqlc generate │ +│ │ +│ 1. Read sqlc.yaml │ +│ 2. Find engine: mydb → look up in engines[] │ +│ 3. Run: sqlc-engine-mydb parse < schema.sql │ +│ 4. Get AST via protobuf on stdout │ +│ 5. Generate Go code │ +└─────────────────────────────────────────────────────────────────┘ + +Process Plugin Communication (Protobuf): + + sqlc sqlc-engine-mydb + ──── ──────────────── + │ │ + │──── spawn process ─────────────► │ + │ args: ["parse"] │ + │ │ + │──── protobuf on stdin ─────────► │ + │ ParseRequest{sql: "..."} │ + │ │ + │◄─── protobuf on stdout ───────── │ + │ ParseResponse{statements} │ + │ │ +``` + +## See Also + +- [Codegen Plugins](plugins.md) - For custom code generators +- [Configuration Reference](../reference/config.md) +- Proto schema: `protos/engine/engine.proto` diff --git a/examples/plugin-based-codegen/README.md b/examples/plugin-based-codegen/README.md new file mode 100644 index 0000000000..5f59c39951 --- /dev/null +++ b/examples/plugin-based-codegen/README.md @@ -0,0 +1,184 @@ +# Plugin-Based Code Generation Example + +This example demonstrates how to use **custom database engine plugins** and **custom code generation plugins** with sqlc. + +## Overview + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ sqlc generate │ +│ │ +│ 1. Read schema.sql & queries.sql │ +│ 2. Send to sqlc-engine-sqlite3 (custom DB engine) │ +│ 3. Get AST & catalog │ +│ 4. Send to sqlc-gen-rust (custom codegen) │ +│ 5. Get generated Rust code │ +└─────────────────────────────────────────────────────────────────┘ +``` + +## Structure + +``` +plugin-based-codegen/ +├── go.mod # This module depends on sqlc +├── sqlc.yaml # Configuration +├── schema.sql # Database schema (SQLite3) +├── queries.sql # SQL queries +├── plugin_test.go # Integration test +├── plugins/ +│ ├── sqlc-engine-sqlite3/ # Custom database engine plugin +│ │ └── main.go +│ └── sqlc-gen-rust/ # Custom code generator plugin +│ └── main.go +└── gen/ + └── rust/ + └── queries.rs # ✅ Generated Rust code +``` + +## Quick Start + +### 1. Build the plugins + +```bash +cd plugins/sqlc-engine-sqlite3 && go build -o sqlc-engine-sqlite3 . +cd ../sqlc-gen-rust && go build -o sqlc-gen-rust . +cd ../.. +``` + +### 2. Run tests + +```bash +go test -v ./... +``` + +### 3. Generate code (requires sqlc with plugin support) + +```bash +SQLCDEBUG=processplugins=1 sqlc generate +``` + +## How It Works + +### Database Engine Plugin (`sqlc-engine-sqlite3`) + +The engine plugin implements the `pkg/engine.Handler` interface: + +```go +import "github.com/sqlc-dev/sqlc/pkg/engine" + +func main() { + engine.Run(engine.Handler{ + Parse: handleParse, // Parse SQL + GetCatalog: handleGetCatalog, // Return initial catalog + IsReservedKeyword: handleIsReservedKeyword, + GetCommentSyntax: handleGetCommentSyntax, + GetDialect: handleGetDialect, + }) +} +``` + +Communication: **Protobuf over stdin/stdout** + +### Code Generation Plugin (`sqlc-gen-rust`) + +The codegen plugin uses the `pkg/plugin.Run` helper: + +```go +import "github.com/sqlc-dev/sqlc/pkg/plugin" + +func main() { + plugin.Run(func(req *plugin.GenerateRequest) (*plugin.GenerateResponse, error) { + // Generate Rust code from req.Queries and req.Catalog + return &plugin.GenerateResponse{ + Files: []*plugin.File{{Name: "queries.rs", Contents: rustCode}}, + }, nil + }) +} +``` + +Communication: **Protobuf over stdin/stdout** + +## Compatibility + +Both plugins import public packages from sqlc: + +- `github.com/sqlc-dev/sqlc/pkg/engine` - Engine plugin SDK +- `github.com/sqlc-dev/sqlc/pkg/plugin` - Codegen plugin SDK + +**Compile-time compatibility**: If the plugin compiles, it's compatible with this version of sqlc. + +## Configuration + +```yaml +version: "2" + +engines: + - name: sqlite3 + process: + cmd: ./plugins/sqlc-engine-sqlite3/sqlc-engine-sqlite3 + +plugins: + - name: rust + process: + cmd: ./plugins/sqlc-gen-rust/sqlc-gen-rust + +sql: + - engine: sqlite3 # Use custom engine + schema: "schema.sql" + queries: "queries.sql" + codegen: + - plugin: rust # Use custom codegen + out: gen/rust +``` + +## Generated Code Example + +The `sqlc-gen-rust` plugin generates type-safe Rust code from SQL: + +**Input (`queries.sql`):** +```sql +-- name: GetUser :one +SELECT * FROM users WHERE id = ?; + +-- name: CreateUser :exec +INSERT INTO users (id, name, email) VALUES (?, ?, ?); +``` + +**Output (`gen/rust/queries.rs`):** +```rust +use sqlx::{FromRow, SqlitePool}; +use anyhow::Result; + +#[derive(Debug, FromRow)] +pub struct Users { + pub id: i32, + pub name: String, + pub email: String, +} + +pub async fn get_user(pool: &SqlitePool, id: i32) -> Result> { + const QUERY: &str = "SELECT * FROM users WHERE id = ?"; + let row = sqlx::query_as(QUERY) + .bind(id) + .fetch_optional(pool) + .await?; + Ok(row) +} + +pub async fn create_user(pool: &SqlitePool, id: i32, name: String, email: String) -> Result<()> { + const QUERY: &str = "INSERT INTO users (id, name, email) VALUES (?, ?, ?)"; + sqlx::query(QUERY) + .bind(id) + .bind(name) + .bind(email) + .execute(pool) + .await?; + Ok(()) +} +``` + +## See Also + +- [Engine Plugins Documentation](../../docs/howto/engine-plugins.md) +- [Codegen Plugins Documentation](../../docs/howto/plugins.md) + diff --git a/examples/plugin-based-codegen/gen/rust/queries.rs b/examples/plugin-based-codegen/gen/rust/queries.rs new file mode 100644 index 0000000000..68240246af --- /dev/null +++ b/examples/plugin-based-codegen/gen/rust/queries.rs @@ -0,0 +1,69 @@ +// Code generated by sqlc-gen-rust. DO NOT EDIT. +// Engine: sqlite3 + +use sqlx::{FromRow, SqlitePool}; +use anyhow::Result; + +#[derive(Debug, FromRow)] +pub struct Users { +} + +#[derive(Debug, FromRow)] +pub struct Posts { +} + +/// GetUser +pub async fn get_user( + pool: &SqlitePool, +) -> Result<()> { + const QUERY: &str = "SELECT * FROM users WHERE id = ?;"; + let row = sqlx::query_as(QUERY) + .fetch_optional(pool) + .await?; + Ok(row) +} + +/// ListUsers +pub async fn list_users( + pool: &SqlitePool, +) -> Result<()> { + const QUERY: &str = "SELECT * FROM users ORDER BY name;"; + let rows = sqlx::query_as(QUERY) + .fetch_all(pool) + .await?; + Ok(rows) +} + +/// CreateUser +pub async fn create_user( + pool: &SqlitePool, +) -> Result<()> { + const QUERY: &str = "INSERT INTO users (id, name, email) VALUES (?, ?, ?);"; + sqlx::query(QUERY) + .execute(pool) + .await?; + Ok(()) +} + +/// GetUserPosts +pub async fn get_user_posts( + pool: &SqlitePool, +) -> Result<()> { + const QUERY: &str = "SELECT * FROM posts WHERE user_id = ? ORDER BY created_at DESC;"; + let rows = sqlx::query_as(QUERY) + .fetch_all(pool) + .await?; + Ok(rows) +} + +/// CreatePost +pub async fn create_post( + pool: &SqlitePool, +) -> Result<()> { + const QUERY: &str = "INSERT INTO posts (id, user_id, title, body) VALUES (?, ?, ?, ?);"; + sqlx::query(QUERY) + .execute(pool) + .await?; + Ok(()) +} + diff --git a/examples/plugin-based-codegen/plugin_test.go b/examples/plugin-based-codegen/plugin_test.go new file mode 100644 index 0000000000..333187b16a --- /dev/null +++ b/examples/plugin-based-codegen/plugin_test.go @@ -0,0 +1,130 @@ +package main + +import ( + "bytes" + "context" + "os" + "os/exec" + "path/filepath" + "testing" + + "github.com/sqlc-dev/sqlc/pkg/engine" + "google.golang.org/protobuf/proto" +) + +// TestEnginePlugin verifies that the SQLite3 engine plugin communicates correctly. +func TestEnginePlugin(t *testing.T) { + ctx := context.Background() + + // Build the engine plugin + pluginDir := filepath.Join("plugins", "sqlc-engine-sqlite3") + pluginBin := filepath.Join(pluginDir, "sqlc-engine-sqlite3") + + buildCmd := exec.Command("go", "build", "-o", "sqlc-engine-sqlite3", ".") + buildCmd.Dir = pluginDir + if output, err := buildCmd.CombinedOutput(); err != nil { + t.Fatalf("failed to build engine plugin: %v\n%s", err, output) + } + defer os.Remove(pluginBin) + + // Test Parse + t.Run("Parse", func(t *testing.T) { + req := &engine.ParseRequest{ + Sql: "SELECT * FROM users WHERE id = ?;", + } + resp := &engine.ParseResponse{} + if err := invokePlugin(ctx, pluginBin, "parse", req, resp); err != nil { + t.Fatal(err) + } + + if len(resp.Statements) != 1 { + t.Fatalf("expected 1 statement, got %d", len(resp.Statements)) + } + t.Logf("✓ Parse: %s", resp.Statements[0].RawSql) + }) + + // Test GetCatalog + t.Run("GetCatalog", func(t *testing.T) { + req := &engine.GetCatalogRequest{} + resp := &engine.GetCatalogResponse{} + if err := invokePlugin(ctx, pluginBin, "get_catalog", req, resp); err != nil { + t.Fatal(err) + } + + if resp.Catalog == nil || resp.Catalog.Name != "sqlite3" { + t.Fatalf("expected catalog 'sqlite3', got %v", resp.Catalog) + } + t.Logf("✓ GetCatalog: %s (schema: %s)", resp.Catalog.Name, resp.Catalog.DefaultSchema) + }) + + // Test IsReservedKeyword + t.Run("IsReservedKeyword", func(t *testing.T) { + tests := []struct { + keyword string + expected bool + }{ + {"SELECT", true}, + {"PRAGMA", true}, + {"users", false}, + } + + for _, tc := range tests { + req := &engine.IsReservedKeywordRequest{Keyword: tc.keyword} + resp := &engine.IsReservedKeywordResponse{} + if err := invokePlugin(ctx, pluginBin, "is_reserved_keyword", req, resp); err != nil { + t.Fatal(err) + } + if resp.IsReserved != tc.expected { + t.Errorf("IsReservedKeyword(%q) = %v, want %v", tc.keyword, resp.IsReserved, tc.expected) + } + } + t.Log("✓ IsReservedKeyword") + }) + + // Test GetDialect + t.Run("GetDialect", func(t *testing.T) { + req := &engine.GetDialectRequest{} + resp := &engine.GetDialectResponse{} + if err := invokePlugin(ctx, pluginBin, "get_dialect", req, resp); err != nil { + t.Fatal(err) + } + + if resp.ParamStyle != "question" { + t.Errorf("expected param_style 'question', got '%s'", resp.ParamStyle) + } + t.Logf("✓ GetDialect: quote=%s param=%s", resp.QuoteChar, resp.ParamStyle) + }) + + // Test GetCommentSyntax + t.Run("GetCommentSyntax", func(t *testing.T) { + req := &engine.GetCommentSyntaxRequest{} + resp := &engine.GetCommentSyntaxResponse{} + if err := invokePlugin(ctx, pluginBin, "get_comment_syntax", req, resp); err != nil { + t.Fatal(err) + } + + if !resp.Dash || !resp.SlashStar { + t.Errorf("expected dash and slash_star comments") + } + t.Logf("✓ GetCommentSyntax: dash=%v slash_star=%v", resp.Dash, resp.SlashStar) + }) +} + +func invokePlugin(ctx context.Context, bin, method string, req, resp proto.Message) error { + reqData, err := proto.Marshal(req) + if err != nil { + return err + } + + cmd := exec.CommandContext(ctx, bin, method) + cmd.Stdin = bytes.NewReader(reqData) + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + + if err := cmd.Run(); err != nil { + return err + } + + return proto.Unmarshal(stdout.Bytes(), resp) +} diff --git a/examples/plugin-based-codegen/plugins/sqlc-engine-sqlite3/main.go b/examples/plugin-based-codegen/plugins/sqlc-engine-sqlite3/main.go new file mode 100644 index 0000000000..393e5592a0 --- /dev/null +++ b/examples/plugin-based-codegen/plugins/sqlc-engine-sqlite3/main.go @@ -0,0 +1,177 @@ +// sqlc-engine-sqlite3 demonstrates a custom database engine plugin. +// +// This plugin provides SQLite3 SQL parsing for sqlc. It shows how external +// repositories can implement database support without modifying sqlc core. +// +// Build: go build -o sqlc-engine-sqlite3 . +package main + +import ( + "encoding/json" + "strings" + + "github.com/sqlc-dev/sqlc/pkg/engine" +) + +func main() { + engine.Run(engine.Handler{ + PluginName: "sqlite3", + PluginVersion: "1.0.0", + Parse: handleParse, + GetCatalog: handleGetCatalog, + IsReservedKeyword: handleIsReservedKeyword, + GetCommentSyntax: handleGetCommentSyntax, + GetDialect: handleGetDialect, + }) +} + +func handleParse(req *engine.ParseRequest) (*engine.ParseResponse, error) { + sql := req.GetSql() + statements := splitStatements(sql) + var result []*engine.Statement + + for _, stmt := range statements { + ast := map[string]interface{}{ + "node_type": detectStatementType(stmt), + "raw": stmt, + } + astJSON, _ := json.Marshal(ast) + + result = append(result, &engine.Statement{ + RawSql: stmt, + StmtLocation: int32(strings.Index(sql, stmt)), + StmtLen: int32(len(stmt)), + AstJson: astJSON, + }) + } + + return &engine.ParseResponse{Statements: result}, nil +} + +func handleGetCatalog(req *engine.GetCatalogRequest) (*engine.GetCatalogResponse, error) { + return &engine.GetCatalogResponse{ + Catalog: &engine.Catalog{ + DefaultSchema: "main", + Name: "sqlite3", + Schemas: []*engine.Schema{ + { + Name: "main", + Tables: []*engine.Table{}, + }, + }, + }, + }, nil +} + +func handleIsReservedKeyword(req *engine.IsReservedKeywordRequest) (*engine.IsReservedKeywordResponse, error) { + reserved := map[string]bool{ + "abort": true, "action": true, "add": true, "after": true, + "all": true, "alter": true, "analyze": true, "and": true, + "as": true, "asc": true, "attach": true, "autoincrement": true, + "before": true, "begin": true, "between": true, "by": true, + "cascade": true, "case": true, "cast": true, "check": true, + "collate": true, "column": true, "commit": true, "conflict": true, + "constraint": true, "create": true, "cross": true, "current_date": true, + "current_time": true, "current_timestamp": true, "database": true, + "default": true, "deferrable": true, "deferred": true, "delete": true, + "desc": true, "detach": true, "distinct": true, "drop": true, + "each": true, "else": true, "end": true, "escape": true, + "except": true, "exclusive": true, "exists": true, "explain": true, + "fail": true, "for": true, "foreign": true, "from": true, + "full": true, "glob": true, "group": true, "having": true, + "if": true, "ignore": true, "immediate": true, "in": true, + "index": true, "indexed": true, "initially": true, "inner": true, + "insert": true, "instead": true, "intersect": true, "into": true, + "is": true, "isnull": true, "join": true, "key": true, + "left": true, "like": true, "limit": true, "match": true, + "natural": true, "no": true, "not": true, "notnull": true, + "null": true, "of": true, "offset": true, "on": true, + "or": true, "order": true, "outer": true, "plan": true, + "pragma": true, "primary": true, "query": true, "raise": true, + "recursive": true, "references": true, "regexp": true, "reindex": true, + "release": true, "rename": true, "replace": true, "restrict": true, + "right": true, "rollback": true, "row": true, "savepoint": true, + "select": true, "set": true, "table": true, "temp": true, + "temporary": true, "then": true, "to": true, "transaction": true, + "trigger": true, "union": true, "unique": true, "update": true, + "using": true, "vacuum": true, "values": true, "view": true, + "virtual": true, "when": true, "where": true, "with": true, + "without": true, + } + return &engine.IsReservedKeywordResponse{ + IsReserved: reserved[strings.ToLower(req.GetKeyword())], + }, nil +} + +func handleGetCommentSyntax(req *engine.GetCommentSyntaxRequest) (*engine.GetCommentSyntaxResponse, error) { + return &engine.GetCommentSyntaxResponse{ + Dash: true, + SlashStar: true, + Hash: false, + }, nil +} + +func handleGetDialect(req *engine.GetDialectRequest) (*engine.GetDialectResponse, error) { + return &engine.GetDialectResponse{ + QuoteChar: `"`, + ParamStyle: "question", + ParamPrefix: "?", + CastSyntax: "cast_function", + }, nil +} + +func splitStatements(sql string) []string { + var statements []string + var current strings.Builder + + for _, line := range strings.Split(sql, "\n") { + trimmedLine := strings.TrimSpace(line) + if trimmedLine == "" { + continue + } + // Include sqlc metadata comments (-- name: ...) with the statement + if strings.HasPrefix(trimmedLine, "--") { + // Check if it's a sqlc query annotation + if strings.Contains(trimmedLine, "name:") { + current.WriteString(trimmedLine) + current.WriteString("\n") + } + // Skip other comments + continue + } + current.WriteString(trimmedLine) + current.WriteString(" ") + if strings.HasSuffix(trimmedLine, ";") { + stmt := strings.TrimSpace(current.String()) + if stmt != "" && stmt != ";" { + statements = append(statements, stmt) + } + current.Reset() + } + } + if current.Len() > 0 { + stmt := strings.TrimSpace(current.String()) + if stmt != "" { + statements = append(statements, stmt) + } + } + return statements +} + +func detectStatementType(sql string) string { + sql = strings.ToUpper(strings.TrimSpace(sql)) + switch { + case strings.HasPrefix(sql, "SELECT"): + return "SelectStmt" + case strings.HasPrefix(sql, "INSERT"): + return "InsertStmt" + case strings.HasPrefix(sql, "UPDATE"): + return "UpdateStmt" + case strings.HasPrefix(sql, "DELETE"): + return "DeleteStmt" + case strings.HasPrefix(sql, "CREATE TABLE"): + return "CreateTableStmt" + default: + return "Unknown" + } +} diff --git a/examples/plugin-based-codegen/plugins/sqlc-gen-rust/main.go b/examples/plugin-based-codegen/plugins/sqlc-gen-rust/main.go new file mode 100644 index 0000000000..6e385b512c --- /dev/null +++ b/examples/plugin-based-codegen/plugins/sqlc-gen-rust/main.go @@ -0,0 +1,190 @@ +// sqlc-gen-rust demonstrates a custom code generation plugin. +// +// This plugin generates Rust code from SQL queries. It shows how external +// repositories can implement language support without modifying sqlc core. +// +// Build: go build -o sqlc-gen-rust . +package main + +import ( + "fmt" + "strings" + + "github.com/sqlc-dev/sqlc/pkg/plugin" +) + +func main() { + plugin.Run(generate) +} + +func generate(req *plugin.GenerateRequest) (*plugin.GenerateResponse, error) { + var sb strings.Builder + + // Header + sb.WriteString("// Code generated by sqlc-gen-rust. DO NOT EDIT.\n") + sb.WriteString("// Engine: " + req.Settings.Engine + "\n\n") + + sb.WriteString("use sqlx::{FromRow, SqlitePool};\n") + sb.WriteString("use anyhow::Result;\n\n") + + // Generate structs from catalog + if req.Catalog != nil { + for _, schema := range req.Catalog.Schemas { + for _, table := range schema.Tables { + sb.WriteString("#[derive(Debug, FromRow)]\n") + sb.WriteString(fmt.Sprintf("pub struct %s {\n", pascalCase(table.Rel.Name))) + for _, col := range table.Columns { + rustType := mapToRustType(col.Type.Name, col.NotNull) + sb.WriteString(fmt.Sprintf(" pub %s: %s,\n", snakeCase(col.Name), rustType)) + } + sb.WriteString("}\n\n") + } + } + } + + // Generate query functions + for _, q := range req.Queries { + sb.WriteString(fmt.Sprintf("/// %s\n", q.Name)) + + // Function signature + sb.WriteString(fmt.Sprintf("pub async fn %s(\n", snakeCase(q.Name))) + sb.WriteString(" pool: &SqlitePool,\n") + + // Parameters + for _, p := range q.Params { + rustType := mapToRustType(p.Column.Type.Name, true) + sb.WriteString(fmt.Sprintf(" %s: %s,\n", snakeCase(p.Column.Name), rustType)) + } + + // Return type + sb.WriteString(")") + switch q.Cmd { + case ":one": + if len(q.Columns) > 0 { + sb.WriteString(fmt.Sprintf(" -> Result>", inferRustReturnType(q))) + } else { + sb.WriteString(" -> Result<()>") + } + case ":many": + if len(q.Columns) > 0 { + sb.WriteString(fmt.Sprintf(" -> Result>", inferRustReturnType(q))) + } else { + sb.WriteString(" -> Result<()>") + } + case ":exec": + sb.WriteString(" -> Result<()>") + default: + sb.WriteString(" -> Result<()>") + } + + sb.WriteString(" {\n") + + // SQL query + escapedSQL := strings.ReplaceAll(q.Text, "\n", " ") + escapedSQL = strings.ReplaceAll(escapedSQL, `"`, `\"`) + sb.WriteString(fmt.Sprintf(" const QUERY: &str = \"%s\";\n", escapedSQL)) + + // Query execution + switch q.Cmd { + case ":one": + sb.WriteString(" let row = sqlx::query_as(QUERY)\n") + for _, p := range q.Params { + sb.WriteString(fmt.Sprintf(" .bind(%s)\n", snakeCase(p.Column.Name))) + } + sb.WriteString(" .fetch_optional(pool)\n") + sb.WriteString(" .await?;\n") + sb.WriteString(" Ok(row)\n") + case ":many": + sb.WriteString(" let rows = sqlx::query_as(QUERY)\n") + for _, p := range q.Params { + sb.WriteString(fmt.Sprintf(" .bind(%s)\n", snakeCase(p.Column.Name))) + } + sb.WriteString(" .fetch_all(pool)\n") + sb.WriteString(" .await?;\n") + sb.WriteString(" Ok(rows)\n") + case ":exec": + sb.WriteString(" sqlx::query(QUERY)\n") + for _, p := range q.Params { + sb.WriteString(fmt.Sprintf(" .bind(%s)\n", snakeCase(p.Column.Name))) + } + sb.WriteString(" .execute(pool)\n") + sb.WriteString(" .await?;\n") + sb.WriteString(" Ok(())\n") + default: + sb.WriteString(" todo!()\n") + } + + sb.WriteString("}\n\n") + } + + return &plugin.GenerateResponse{ + Files: []*plugin.File{ + { + Name: "queries.rs", + Contents: []byte(sb.String()), + }, + }, + }, nil +} + +func pascalCase(s string) string { + if s == "" { + return s + } + words := strings.Split(s, "_") + for i, w := range words { + if len(w) > 0 { + words[i] = strings.ToUpper(w[:1]) + strings.ToLower(w[1:]) + } + } + return strings.Join(words, "") +} + +func snakeCase(s string) string { + var result strings.Builder + for i, r := range s { + if i > 0 && r >= 'A' && r <= 'Z' { + result.WriteRune('_') + } + result.WriteRune(r) + } + return strings.ToLower(result.String()) +} + +func mapToRustType(sqlType string, notNull bool) string { + var rustType string + switch strings.ToLower(sqlType) { + case "int", "integer", "int4": + rustType = "i32" + case "int8", "bigint": + rustType = "i64" + case "smallint", "int2": + rustType = "i16" + case "text", "varchar", "char", "string": + rustType = "String" + case "bool", "boolean": + rustType = "bool" + case "float", "real": + rustType = "f32" + case "double", "numeric", "decimal": + rustType = "f64" + case "blob", "bytea": + rustType = "Vec" + default: + rustType = "String" + } + if !notNull { + rustType = fmt.Sprintf("Option<%s>", rustType) + } + return rustType +} + +func inferRustReturnType(q *plugin.Query) string { + if q.InsertIntoTable != nil { + return pascalCase(q.InsertIntoTable.Name) + } + if len(q.Columns) == 1 { + return mapToRustType(q.Columns[0].Type.Name, q.Columns[0].NotNull) + } + return "Row" +} diff --git a/examples/plugin-based-codegen/queries.sql b/examples/plugin-based-codegen/queries.sql new file mode 100644 index 0000000000..bc09f51901 --- /dev/null +++ b/examples/plugin-based-codegen/queries.sql @@ -0,0 +1,16 @@ +-- name: GetUser :one +SELECT * FROM users WHERE id = ?; + +-- name: ListUsers :many +SELECT * FROM users ORDER BY name; + +-- name: CreateUser :exec +INSERT INTO users (id, name, email) VALUES (?, ?, ?); + +-- name: GetUserPosts :many +SELECT * FROM posts WHERE user_id = ? ORDER BY created_at DESC; + +-- name: CreatePost :exec +INSERT INTO posts (id, user_id, title, body) VALUES (?, ?, ?, ?); + + diff --git a/examples/plugin-based-codegen/schema.sql b/examples/plugin-based-codegen/schema.sql new file mode 100644 index 0000000000..b8f4c66385 --- /dev/null +++ b/examples/plugin-based-codegen/schema.sql @@ -0,0 +1,15 @@ +CREATE TABLE users ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL, + email TEXT NOT NULL +); + +CREATE TABLE posts ( + id INTEGER PRIMARY KEY, + user_id INTEGER NOT NULL REFERENCES users(id), + title TEXT NOT NULL, + body TEXT, + created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP +); + + diff --git a/examples/plugin-based-codegen/sqlc.yaml b/examples/plugin-based-codegen/sqlc.yaml new file mode 100644 index 0000000000..6236b3f34e --- /dev/null +++ b/examples/plugin-based-codegen/sqlc.yaml @@ -0,0 +1,23 @@ +version: "2" + +# Custom database engine plugin +engines: + - name: sqlite3 + process: + cmd: go run ./plugins/sqlc-engine-sqlite3 + +# Custom code generation plugin +plugins: + - name: rust + process: + cmd: go run ./plugins/sqlc-gen-rust + +sql: + - engine: sqlite3 + schema: "schema.sql" + queries: "queries.sql" + codegen: + - plugin: rust + out: gen/rust + + diff --git a/internal/cmd/generate.go b/internal/cmd/generate.go index 05b5445ebb..d78fff9d08 100644 --- a/internal/cmd/generate.go +++ b/internal/cmd/generate.go @@ -350,6 +350,7 @@ func codegen(ctx context.Context, combo config.CombinedSettings, sql OutputPair, case plug.Process != nil: handler = &process.Runner{ Cmd: plug.Process.Cmd, + Dir: combo.Dir, Env: plug.Env, Format: plug.Process.Format, } diff --git a/internal/cmd/process.go b/internal/cmd/process.go index 5003d113b8..ae7e76caff 100644 --- a/internal/cmd/process.go +++ b/internal/cmd/process.go @@ -69,6 +69,9 @@ func processQuerySets(ctx context.Context, rp ResultProcessor, conf *config.Conf grp.Go(func() error { combo := config.Combine(*conf, sql.SQL) + if dir != "" { + combo.Dir = dir + } if sql.Plugin != nil { combo.Codegen = *sql.Plugin } diff --git a/internal/cmd/vet.go b/internal/cmd/vet.go index 4dbd3c3b7b..dcec43eb14 100644 --- a/internal/cmd/vet.go +++ b/internal/cmd/vet.go @@ -465,6 +465,9 @@ func (c *checker) DSN(dsn string) (string, error) { func (c *checker) checkSQL(ctx context.Context, s config.SQL) error { // TODO: Create a separate function for this logic so we can combo := config.Combine(*c.Conf, s) + if c.Dir != "" { + combo.Dir = c.Dir + } // TODO: This feels like a hack that will bite us later joined := make([]string, 0, len(s.Schema)) diff --git a/internal/compiler/engine.go b/internal/compiler/engine.go index 64fdf3d5c7..cb08aad1b6 100644 --- a/internal/compiler/engine.go +++ b/internal/compiler/engine.go @@ -7,7 +7,9 @@ import ( "github.com/sqlc-dev/sqlc/internal/analyzer" "github.com/sqlc-dev/sqlc/internal/config" "github.com/sqlc-dev/sqlc/internal/dbmanager" + "github.com/sqlc-dev/sqlc/internal/engine" "github.com/sqlc-dev/sqlc/internal/engine/dolphin" + "github.com/sqlc-dev/sqlc/internal/engine/plugin" "github.com/sqlc-dev/sqlc/internal/engine/postgresql" pganalyze "github.com/sqlc-dev/sqlc/internal/engine/postgresql/analyzer" "github.com/sqlc-dev/sqlc/internal/engine/sqlite" @@ -112,11 +114,49 @@ func NewCompiler(conf config.SQL, combo config.CombinedSettings, parserOpts opts } } default: - return nil, fmt.Errorf("unknown engine: %s", conf.Engine) + // Check if this is a plugin engine + if enginePlugin, found := config.FindEnginePlugin(&combo.Global, string(conf.Engine)); found { + eng, err := createPluginEngine(enginePlugin, combo.Dir) + if err != nil { + return nil, err + } + c.parser = eng.Parser() + c.catalog = eng.Catalog() + sel := eng.Selector() + if sel != nil { + c.selector = &engineSelectorAdapter{sel} + } else { + c.selector = newDefaultSelector() + } + } else { + return nil, fmt.Errorf("unknown engine: %s\n\nTo use a custom database engine, add it to the 'engines' section of sqlc.yaml:\n\n engines:\n - name: %s\n process:\n cmd: sqlc-engine-%s\n\nThen install the plugin: go install github.com/example/sqlc-engine-%s@latest", + conf.Engine, conf.Engine, conf.Engine, conf.Engine) + } } return c, nil } +// createPluginEngine creates an engine from an engine plugin configuration. +func createPluginEngine(ep *config.EnginePlugin, dir string) (engine.Engine, error) { + switch { + case ep.Process != nil: + return plugin.NewPluginEngine(ep.Name, ep.Process.Cmd, dir, ep.Env), nil + case ep.WASM != nil: + return plugin.NewWASMPluginEngine(ep.Name, ep.WASM.URL, ep.WASM.SHA256, ep.Env), nil + default: + return nil, fmt.Errorf("engine plugin %s has no process or wasm configuration", ep.Name) + } +} + +// engineSelectorAdapter adapts engine.Selector to the compiler's selector interface. +type engineSelectorAdapter struct { + sel engine.Selector +} + +func (a *engineSelectorAdapter) ColumnExpr(name string, column *Column) string { + return a.sel.ColumnExpr(name, column.DataType) +} + func (c *Compiler) Catalog() *catalog.Catalog { return c.catalog } diff --git a/internal/config/config.go b/internal/config/config.go index d3e610ef05..7d6153f26b 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -63,10 +63,43 @@ type Config struct { SQL []SQL `json:"sql" yaml:"sql"` Overrides Overrides `json:"overrides,omitempty" yaml:"overrides"` Plugins []Plugin `json:"plugins" yaml:"plugins"` + Engines []EnginePlugin `json:"engines" yaml:"engines"` Rules []Rule `json:"rules" yaml:"rules"` Options map[string]yaml.Node `json:"options" yaml:"options"` } +// EnginePlugin defines a custom database engine plugin. +// Engine plugins allow external SQL parsers and database backends to be used with sqlc. +type EnginePlugin struct { + // Name is the unique name for this engine (used in sql[].engine field) + Name string `json:"name" yaml:"name"` + + // Env is a list of environment variable names to pass to the plugin + Env []string `json:"env" yaml:"env"` + + // Process defines an engine plugin that runs as an external process + Process *EnginePluginProcess `json:"process" yaml:"process"` + + // WASM defines an engine plugin that runs as a WASM module + WASM *EnginePluginWASM `json:"wasm" yaml:"wasm"` +} + +// EnginePluginProcess defines a process-based engine plugin. +type EnginePluginProcess struct { + // Cmd is the command to run (must be in PATH or an absolute path) + Cmd string `json:"cmd" yaml:"cmd"` +} + +// EnginePluginWASM defines a WASM-based engine plugin. +type EnginePluginWASM struct { + // URL is the URL to download the WASM module from + // Supports file:// and https:// schemes + URL string `json:"url" yaml:"url"` + + // SHA256 is the expected SHA256 checksum of the WASM module + SHA256 string `json:"sha256" yaml:"sha256"` +} + type Server struct { Name string `json:"name,omitempty" yaml:"name"` Engine Engine `json:"engine,omitempty" yaml:"engine"` @@ -125,8 +158,8 @@ type SQL struct { // AnalyzerDatabase represents the database analyzer setting. // It can be a boolean (true/false) or the string "only" for database-only mode. type AnalyzerDatabase struct { - value *bool // nil means not set, true/false for boolean values - isOnly bool // true when set to "only" + value *bool // nil means not set, true/false for boolean values + isOnly bool // true when set to "only" } // IsEnabled returns true if the database analyzer should be used. @@ -228,6 +261,14 @@ var ErrPluginNoType = errors.New("plugin: field `process` or `wasm` required") var ErrPluginBothTypes = errors.New("plugin: `process` and `wasm` cannot both be defined") var ErrPluginProcessNoCmd = errors.New("plugin: missing process command") +var ErrEnginePluginNoName = errors.New("engine plugin: missing name") +var ErrEnginePluginBuiltin = errors.New("engine plugin: cannot override built-in engine") +var ErrEnginePluginExists = errors.New("engine plugin: a plugin with that name already exists") +var ErrEnginePluginNoType = errors.New("engine plugin: field `process` or `wasm` required") +var ErrEnginePluginBothTypes = errors.New("engine plugin: `process` and `wasm` cannot both be defined") +var ErrEnginePluginProcessNoCmd = errors.New("engine plugin: missing process command") +var ErrEnginePluginWASMNoURL = errors.New("engine plugin: missing wasm url") + var ErrInvalidDatabase = errors.New("database must be managed or have a non-empty URI") var ErrManagedDatabaseNoProject = errors.New(`managed databases require a cloud project @@ -285,6 +326,9 @@ type CombinedSettings struct { // TODO: Combine these into a more usable type Codegen Codegen + + // Dir is the directory containing the config file (for resolving relative paths) + Dir string } func Combine(conf Config, pkg SQL) CombinedSettings { diff --git a/internal/config/validate.go b/internal/config/validate.go index fadef4fb3b..6587283ea3 100644 --- a/internal/config/validate.go +++ b/internal/config/validate.go @@ -1,6 +1,46 @@ package config +// builtinEngines contains the names of built-in database engines. +var builtinEngines = map[Engine]bool{ + EngineMySQL: true, + EnginePostgreSQL: true, + EngineSQLite: true, +} + +// IsBuiltinEngine returns true if the engine name is a built-in engine. +func IsBuiltinEngine(name Engine) bool { + return builtinEngines[name] +} + func Validate(c *Config) error { + // Validate engine plugins + engineNames := make(map[string]bool) + for _, ep := range c.Engines { + if ep.Name == "" { + return ErrEnginePluginNoName + } + if IsBuiltinEngine(Engine(ep.Name)) { + return ErrEnginePluginBuiltin + } + if engineNames[ep.Name] { + return ErrEnginePluginExists + } + engineNames[ep.Name] = true + + if ep.Process == nil && ep.WASM == nil { + return ErrEnginePluginNoType + } + if ep.Process != nil && ep.WASM != nil { + return ErrEnginePluginBothTypes + } + if ep.Process != nil && ep.Process.Cmd == "" { + return ErrEnginePluginProcessNoCmd + } + if ep.WASM != nil && ep.WASM.URL == "" { + return ErrEnginePluginWASMNoURL + } + } + for _, sql := range c.SQL { if sql.Database != nil { if sql.Database.URI == "" && !sql.Database.Managed { @@ -10,3 +50,13 @@ func Validate(c *Config) error { } return nil } + +// FindEnginePlugin finds an engine plugin by name. +func FindEnginePlugin(c *Config, name string) (*EnginePlugin, bool) { + for i := range c.Engines { + if c.Engines[i].Name == name { + return &c.Engines[i], true + } + } + return nil, false +} diff --git a/internal/endtoend/endtoend_test.go b/internal/endtoend/endtoend_test.go index 7634918446..085eb4a3d9 100644 --- a/internal/endtoend/endtoend_test.go +++ b/internal/endtoend/endtoend_test.go @@ -58,11 +58,11 @@ func TestExamples(t *testing.T) { t.Parallel() path := filepath.Join(examples, tc) var stderr bytes.Buffer - opts := &cmd.Options{ - Env: cmd.Env{}, + o := &cmd.Options{ + Env: cmd.Env{Debug: opts.DebugFromString("")}, Stderr: &stderr, } - output, err := cmd.Generate(ctx, path, "", opts) + output, err := cmd.Generate(ctx, path, "", o) if err != nil { t.Fatalf("sqlc generate failed: %s", stderr.String()) } @@ -311,7 +311,7 @@ func cmpDirectory(t *testing.T, dir string, actual map[string]string) { if file.IsDir() { return nil } - if !strings.HasSuffix(path, ".go") && !strings.HasSuffix(path, ".kt") && !strings.HasSuffix(path, ".py") && !strings.HasSuffix(path, ".json") && !strings.HasSuffix(path, ".txt") { + if !strings.HasSuffix(path, ".go") && !strings.HasSuffix(path, ".kt") && !strings.HasSuffix(path, ".py") && !strings.HasSuffix(path, ".json") && !strings.HasSuffix(path, ".txt") && !strings.HasSuffix(path, ".rs") { return nil } // TODO: Figure out a better way to ignore certain files @@ -330,6 +330,10 @@ func cmpDirectory(t *testing.T, dir string, actual map[string]string) { if strings.HasSuffix(path, "_test.go") || strings.Contains(path, "src/test/") { return nil } + // Skip plugin source files - they are not generated by sqlc + if strings.Contains(path, "/plugins/") { + return nil + } if strings.Contains(path, "/python/.venv") || strings.Contains(path, "/python/src/tests/") || strings.HasSuffix(path, "__init__.py") || strings.Contains(path, "/python/src/dbtest/") || strings.Contains(path, "/python/.mypy_cache") { diff --git a/internal/endtoend/testdata/bad_config/engine/stderr.txt b/internal/endtoend/testdata/bad_config/engine/stderr.txt index 9797244924..559868237b 100644 --- a/internal/endtoend/testdata/bad_config/engine/stderr.txt +++ b/internal/endtoend/testdata/bad_config/engine/stderr.txt @@ -1 +1,10 @@ -error creating compiler: unknown engine: bad_engine \ No newline at end of file +error creating compiler: unknown engine: bad_engine + +To use a custom database engine, add it to the 'engines' section of sqlc.yaml: + + engines: + - name: bad_engine + process: + cmd: sqlc-engine-bad_engine + +Then install the plugin: go install github.com/example/sqlc-engine-bad_engine@latest diff --git a/internal/endtoend/vet_test.go b/internal/endtoend/vet_test.go index 011c032c2e..db0ec987c1 100644 --- a/internal/endtoend/vet_test.go +++ b/internal/endtoend/vet_test.go @@ -12,6 +12,7 @@ import ( "testing" "github.com/sqlc-dev/sqlc/internal/cmd" + "github.com/sqlc-dev/sqlc/internal/opts" "github.com/sqlc-dev/sqlc/internal/sqltest" "github.com/sqlc-dev/sqlc/internal/sqltest/local" ) @@ -69,11 +70,11 @@ func TestExamplesVet(t *testing.T) { } var stderr bytes.Buffer - opts := &cmd.Options{ + o := &cmd.Options{ Stderr: &stderr, - Env: cmd.Env{}, + Env: cmd.Env{Debug: opts.DebugFromString("")}, } - err := cmd.Vet(ctx, path, "", opts) + err := cmd.Vet(ctx, path, "", o) if err != nil { t.Fatalf("sqlc vet failed: %s %s", err, stderr.String()) } diff --git a/internal/engine/dolphin/engine.go b/internal/engine/dolphin/engine.go new file mode 100644 index 0000000000..fb3ffc1825 --- /dev/null +++ b/internal/engine/dolphin/engine.go @@ -0,0 +1,43 @@ +package dolphin + +import ( + "github.com/sqlc-dev/sqlc/internal/engine" + "github.com/sqlc-dev/sqlc/internal/sql/catalog" +) + +// dolphinEngine implements the engine.Engine interface for MySQL. +type dolphinEngine struct { + parser *Parser +} + +// NewEngine creates a new MySQL engine. +func NewEngine() engine.Engine { + return &dolphinEngine{ + parser: NewParser(), + } +} + +// Name returns the engine name. +func (e *dolphinEngine) Name() string { + return "mysql" +} + +// Parser returns the MySQL parser. +func (e *dolphinEngine) Parser() engine.Parser { + return e.parser +} + +// Catalog returns a new MySQL catalog. +func (e *dolphinEngine) Catalog() *catalog.Catalog { + return NewCatalog() +} + +// Selector returns nil because MySQL uses the default selector. +func (e *dolphinEngine) Selector() engine.Selector { + return &engine.DefaultSelector{} +} + +// Dialect returns the parser which implements the Dialect interface. +func (e *dolphinEngine) Dialect() engine.Dialect { + return e.parser +} diff --git a/internal/engine/engine.go b/internal/engine/engine.go new file mode 100644 index 0000000000..713f8a0f4a --- /dev/null +++ b/internal/engine/engine.go @@ -0,0 +1,92 @@ +// Package engine provides the interface and registry for database engines. +// Engines are responsible for parsing SQL statements and providing database-specific +// functionality like catalog creation, keyword checking, and comment syntax. +package engine + +import ( + "io" + + "github.com/sqlc-dev/sqlc/internal/source" + "github.com/sqlc-dev/sqlc/internal/sql/ast" + "github.com/sqlc-dev/sqlc/internal/sql/catalog" +) + +// Parser is the interface that wraps the basic SQL parsing methods. +// All database engines must implement this interface. +type Parser interface { + // Parse parses SQL from the given reader and returns a slice of statements. + Parse(io.Reader) ([]ast.Statement, error) + + // CommentSyntax returns the comment syntax supported by this engine. + CommentSyntax() source.CommentSyntax + + // IsReservedKeyword returns true if the given string is a reserved keyword. + IsReservedKeyword(string) bool +} + +// Dialect provides database-specific formatting for SQL identifiers and expressions. +// This is used when reformatting queries for output. +type Dialect interface { + // QuoteIdent returns a quoted identifier if it needs quoting. + QuoteIdent(string) string + + // TypeName returns the SQL type name for the given namespace and name. + TypeName(ns, name string) string + + // Param returns the parameter placeholder for the given number. + // E.g., PostgreSQL uses $1, MySQL uses ?, etc. + Param(n int) string + + // NamedParam returns the named parameter placeholder for the given name. + NamedParam(name string) string + + // Cast returns a type cast expression. + Cast(arg, typeName string) string +} + +// Selector generates output expressions for SELECT and RETURNING statements. +// Different engines may need to wrap certain column types for proper output. +type Selector interface { + // ColumnExpr generates output to be used in a SELECT or RETURNING + // statement based on input column name and metadata. + ColumnExpr(name string, dataType string) string +} + +// Column represents column metadata for the Selector interface. +type Column struct { + DataType string +} + +// Engine is the main interface that database engines must implement. +// It provides factory methods for creating engine-specific components. +type Engine interface { + // Name returns the unique name of this engine (e.g., "postgresql", "mysql", "sqlite"). + Name() string + + // Parser returns a new Parser instance for this engine. + Parser() Parser + + // Catalog returns a new Catalog instance pre-populated with built-in types and schemas. + Catalog() *catalog.Catalog + + // Selector returns a Selector for generating column expressions. + // Returns nil if the engine uses the default selector. + Selector() Selector + + // Dialect returns the Dialect for this engine. + // Returns nil if the parser implements Dialect directly. + Dialect() Dialect +} + +// EngineFactory is a function that creates a new Engine instance. +type EngineFactory func() Engine + +// DefaultSelector is a selector implementation that does the simplest possible +// pass through when generating column expressions. Its use is suitable for all +// database engines not requiring additional customization. +type DefaultSelector struct{} + +// ColumnExpr returns the column name unchanged. +func (s *DefaultSelector) ColumnExpr(name string, dataType string) string { + return name +} diff --git a/internal/engine/plugin/process.go b/internal/engine/plugin/process.go new file mode 100644 index 0000000000..b2c20e76ae --- /dev/null +++ b/internal/engine/plugin/process.go @@ -0,0 +1,490 @@ +package plugin + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "os" + "os/exec" + "strings" + + "google.golang.org/protobuf/proto" + + "github.com/sqlc-dev/sqlc/internal/engine" + "github.com/sqlc-dev/sqlc/internal/info" + "github.com/sqlc-dev/sqlc/internal/source" + "github.com/sqlc-dev/sqlc/internal/sql/ast" + "github.com/sqlc-dev/sqlc/internal/sql/catalog" + pb "github.com/sqlc-dev/sqlc/pkg/engine" +) + +// ProcessRunner runs an engine plugin as an external process. +type ProcessRunner struct { + Cmd string + Dir string // Working directory for the plugin (config file directory) + Env []string + + // Cached responses + commentSyntax *pb.GetCommentSyntaxResponse + dialect *pb.GetDialectResponse +} + +// NewProcessRunner creates a new ProcessRunner. +func NewProcessRunner(cmd, dir string, env []string) *ProcessRunner { + return &ProcessRunner{ + Cmd: cmd, + Dir: dir, + Env: env, + } +} + +func (r *ProcessRunner) invoke(ctx context.Context, method string, req, resp proto.Message) error { + stdin, err := proto.Marshal(req) + if err != nil { + return fmt.Errorf("failed to encode request: %w", err) + } + + // Parse command string to support formats like "go run ./path" + cmdParts := strings.Fields(r.Cmd) + if len(cmdParts) == 0 { + return fmt.Errorf("engine plugin not found: %s\n\nMake sure the plugin is installed and available in PATH.\nInstall with: go install @latest", r.Cmd) + } + + path, err := exec.LookPath(cmdParts[0]) + if err != nil { + return fmt.Errorf("engine plugin not found: %s\n\nMake sure the plugin is installed and available in PATH.\nInstall with: go install @latest", r.Cmd) + } + + // Build arguments: rest of cmdParts + method + args := append(cmdParts[1:], method) + cmd := exec.CommandContext(ctx, path, args...) + cmd.Stdin = bytes.NewReader(stdin) + // Set working directory to config file directory for relative paths + if r.Dir != "" { + cmd.Dir = r.Dir + } + // Inherit the current environment and add SQLC_VERSION + cmd.Env = append(os.Environ(), fmt.Sprintf("SQLC_VERSION=%s", info.Version)) + + out, err := cmd.Output() + if err != nil { + stderr := err.Error() + var exit *exec.ExitError + if errors.As(err, &exit) { + stderr = string(exit.Stderr) + } + return fmt.Errorf("engine plugin error: %s", stderr) + } + + if err := proto.Unmarshal(out, resp); err != nil { + return fmt.Errorf("failed to decode response: %w", err) + } + + return nil +} + +// Parse implements engine.Parser. +func (r *ProcessRunner) Parse(reader io.Reader) ([]ast.Statement, error) { + sql, err := io.ReadAll(reader) + if err != nil { + return nil, err + } + + req := &pb.ParseRequest{Sql: string(sql)} + resp := &pb.ParseResponse{} + + if err := r.invoke(context.Background(), "parse", req, resp); err != nil { + return nil, err + } + + var stmts []ast.Statement + for _, s := range resp.Statements { + // Parse the AST JSON into an ast.Node + node, err := parseASTJSON(s.AstJson) + if err != nil { + return nil, fmt.Errorf("failed to parse AST: %w", err) + } + + stmts = append(stmts, ast.Statement{ + Raw: &ast.RawStmt{ + Stmt: node, + StmtLocation: int(s.StmtLocation), + StmtLen: int(s.StmtLen), + }, + }) + } + + return stmts, nil +} + +// CommentSyntax implements engine.Parser. +func (r *ProcessRunner) CommentSyntax() source.CommentSyntax { + if r.commentSyntax == nil { + req := &pb.GetCommentSyntaxRequest{} + resp := &pb.GetCommentSyntaxResponse{} + if err := r.invoke(context.Background(), "get_comment_syntax", req, resp); err != nil { + // Default to common SQL comment syntax + return source.CommentSyntax{ + Dash: true, + SlashStar: true, + } + } + r.commentSyntax = resp + } + + return source.CommentSyntax{ + Dash: r.commentSyntax.Dash, + SlashStar: r.commentSyntax.SlashStar, + Hash: r.commentSyntax.Hash, + } +} + +// IsReservedKeyword implements engine.Parser. +func (r *ProcessRunner) IsReservedKeyword(s string) bool { + req := &pb.IsReservedKeywordRequest{Keyword: s} + resp := &pb.IsReservedKeywordResponse{} + if err := r.invoke(context.Background(), "is_reserved_keyword", req, resp); err != nil { + return false + } + return resp.IsReserved +} + +// GetCatalog returns the initial catalog for this engine. +func (r *ProcessRunner) GetCatalog() (*catalog.Catalog, error) { + req := &pb.GetCatalogRequest{} + resp := &pb.GetCatalogResponse{} + if err := r.invoke(context.Background(), "get_catalog", req, resp); err != nil { + return nil, err + } + + return convertCatalog(resp.Catalog), nil +} + +// QuoteIdent implements engine.Dialect. +func (r *ProcessRunner) QuoteIdent(s string) string { + r.ensureDialect() + if r.IsReservedKeyword(s) && r.dialect.QuoteChar != "" { + return r.dialect.QuoteChar + s + r.dialect.QuoteChar + } + return s +} + +// TypeName implements engine.Dialect. +func (r *ProcessRunner) TypeName(ns, name string) string { + if ns != "" { + return ns + "." + name + } + return name +} + +// Param implements engine.Dialect. +func (r *ProcessRunner) Param(n int) string { + r.ensureDialect() + switch r.dialect.ParamStyle { + case "dollar": + return fmt.Sprintf("$%d", n) + case "question": + return "?" + case "at": + return fmt.Sprintf("@p%d", n) + default: + return fmt.Sprintf("$%d", n) + } +} + +// NamedParam implements engine.Dialect. +func (r *ProcessRunner) NamedParam(name string) string { + r.ensureDialect() + if r.dialect.ParamPrefix != "" { + return r.dialect.ParamPrefix + name + } + return "@" + name +} + +// Cast implements engine.Dialect. +func (r *ProcessRunner) Cast(arg, typeName string) string { + r.ensureDialect() + switch r.dialect.CastSyntax { + case "double_colon": + return arg + "::" + typeName + default: + return "CAST(" + arg + " AS " + typeName + ")" + } +} + +func (r *ProcessRunner) ensureDialect() { + if r.dialect == nil { + req := &pb.GetDialectRequest{} + resp := &pb.GetDialectResponse{} + if err := r.invoke(context.Background(), "get_dialect", req, resp); err != nil { + // Use defaults + r.dialect = &pb.GetDialectResponse{ + QuoteChar: `"`, + ParamStyle: "dollar", + ParamPrefix: "@", + CastSyntax: "cast_function", + } + } else { + r.dialect = resp + } + } +} + +// convertCatalog converts a protobuf Catalog to catalog.Catalog. +func convertCatalog(c *pb.Catalog) *catalog.Catalog { + if c == nil { + return catalog.New("") + } + + cat := catalog.New(c.DefaultSchema) + cat.Name = c.Name + cat.Comment = c.Comment + + // Clear default schemas and add from plugin + cat.Schemas = make([]*catalog.Schema, 0, len(c.Schemas)) + for _, s := range c.Schemas { + schema := &catalog.Schema{ + Name: s.Name, + Comment: s.Comment, + } + + for _, t := range s.Tables { + table := &catalog.Table{ + Rel: &ast.TableName{ + Catalog: t.Catalog, + Schema: t.Schema, + Name: t.Name, + }, + Comment: t.Comment, + } + for _, col := range t.Columns { + table.Columns = append(table.Columns, &catalog.Column{ + Name: col.Name, + Type: ast.TypeName{Name: col.DataType}, + IsNotNull: col.NotNull, + IsArray: col.IsArray, + ArrayDims: int(col.ArrayDims), + Comment: col.Comment, + Length: toPointer(int(col.Length)), + IsUnsigned: col.IsUnsigned, + }) + } + schema.Tables = append(schema.Tables, table) + } + + for _, e := range s.Enums { + enum := &catalog.Enum{ + Name: e.Name, + Comment: e.Comment, + } + enum.Vals = append(enum.Vals, e.Values...) + schema.Types = append(schema.Types, enum) + } + + for _, f := range s.Functions { + fn := &catalog.Function{ + Name: f.Name, + Comment: f.Comment, + ReturnType: &ast.TypeName{Schema: f.ReturnType.GetSchema(), Name: f.ReturnType.GetName()}, + } + for _, arg := range f.Args { + fn.Args = append(fn.Args, &catalog.Argument{ + Name: arg.Name, + Type: &ast.TypeName{Schema: arg.Type.GetSchema(), Name: arg.Type.GetName()}, + HasDefault: arg.HasDefault, + }) + } + schema.Funcs = append(schema.Funcs, fn) + } + + for _, t := range s.Types { + schema.Types = append(schema.Types, &catalog.CompositeType{ + Name: t.Name, + Comment: t.Comment, + }) + } + + cat.Schemas = append(cat.Schemas, schema) + } + + return cat +} + +func toPointer(n int) *int { + if n == 0 { + return nil + } + return &n +} + +// parseASTJSON parses AST JSON into an ast.Node. +// This is a placeholder - full implementation would require a JSON-to-AST converter. +func parseASTJSON(data []byte) (ast.Node, error) { + if len(data) == 0 { + return &ast.TODO{}, nil + } + + // Parse the JSON to determine the node type + var raw map[string]json.RawMessage + if err := json.Unmarshal(data, &raw); err != nil { + return nil, err + } + + // Check for node_type field + if nodeType, ok := raw["node_type"]; ok { + var typeName string + if err := json.Unmarshal(nodeType, &typeName); err != nil { + return nil, err + } + return parseNodeByType(typeName, data) + } + + // Default to TODO for unknown structures + return &ast.TODO{}, nil +} + +// parseNodeByType parses a node based on its type. +func parseNodeByType(nodeType string, data []byte) (ast.Node, error) { + switch strings.ToLower(nodeType) { + case "select", "selectstmt": + return parseSelectStmt(data) + case "insert", "insertstmt": + return parseInsertStmt(data) + case "update", "updatestmt": + return parseUpdateStmt(data) + case "delete", "deletestmt": + return parseDeleteStmt(data) + case "createtable", "createtablestmt": + return parseCreateTableStmt(data) + default: + return &ast.TODO{}, nil + } +} + +// Placeholder implementations for statement parsing +func parseSelectStmt(data []byte) (ast.Node, error) { + return &ast.SelectStmt{}, nil +} + +func parseInsertStmt(data []byte) (ast.Node, error) { + return &ast.InsertStmt{}, nil +} + +func parseUpdateStmt(data []byte) (ast.Node, error) { + return &ast.UpdateStmt{}, nil +} + +func parseDeleteStmt(data []byte) (ast.Node, error) { + return &ast.DeleteStmt{}, nil +} + +func parseCreateTableStmt(data []byte) (ast.Node, error) { + // Try to extract table name from JSON + var raw map[string]interface{} + if err := json.Unmarshal(data, &raw); err != nil { + return &ast.CreateTableStmt{}, nil + } + + stmt := &ast.CreateTableStmt{} + + // Check for table_name in JSON first + if tableName, ok := raw["table_name"].(string); ok && tableName != "" { + schema := "" + name := tableName + if parts := strings.SplitN(tableName, ".", 2); len(parts) == 2 { + schema = parts[0] + name = parts[1] + } + stmt.Name = &ast.TableName{Schema: schema, Name: name} + return stmt, nil + } + + // Try to extract from raw SQL + if rawSQL, ok := raw["raw"].(string); ok && rawSQL != "" { + if name := extractTableNameFromCreateSQL(rawSQL); name != "" { + stmt.Name = &ast.TableName{Name: name} + } + } + + return stmt, nil +} + +// extractTableNameFromCreateSQL extracts table name from CREATE TABLE statement +func extractTableNameFromCreateSQL(sql string) string { + sql = strings.TrimSpace(sql) + upper := strings.ToUpper(sql) + + // Handle CREATE TABLE [IF NOT EXISTS] name + idx := strings.Index(upper, "CREATE TABLE") + if idx == -1 { + return "" + } + sql = strings.TrimSpace(sql[idx+len("CREATE TABLE"):]) + upper = strings.ToUpper(sql) + + // Skip IF NOT EXISTS + if strings.HasPrefix(upper, "IF NOT EXISTS") { + sql = strings.TrimSpace(sql[len("IF NOT EXISTS"):]) + } + + // Extract table name (until space or parenthesis) + var name strings.Builder + for _, r := range sql { + if r == ' ' || r == '(' || r == '\t' || r == '\n' || r == '\r' { + break + } + name.WriteRune(r) + } + + result := name.String() + // Remove quotes if present + result = strings.Trim(result, `"'`+"`") + return result +} + +// PluginEngine wraps a ProcessRunner to implement engine.Engine. +type PluginEngine struct { + name string + runner *ProcessRunner +} + +// NewPluginEngine creates a new engine from a process plugin. +func NewPluginEngine(name, cmd, dir string, env []string) *PluginEngine { + return &PluginEngine{ + name: name, + runner: NewProcessRunner(cmd, dir, env), + } +} + +// Name implements engine.Engine. +func (e *PluginEngine) Name() string { + return e.name +} + +// Parser implements engine.Engine. +func (e *PluginEngine) Parser() engine.Parser { + return e.runner +} + +// Catalog implements engine.Engine. +func (e *PluginEngine) Catalog() *catalog.Catalog { + cat, err := e.runner.GetCatalog() + if err != nil { + // Return empty catalog on error + return catalog.New("") + } + return cat +} + +// Selector implements engine.Engine. +func (e *PluginEngine) Selector() engine.Selector { + return &engine.DefaultSelector{} +} + +// Dialect implements engine.Engine. +func (e *PluginEngine) Dialect() engine.Dialect { + return e.runner +} diff --git a/internal/engine/plugin/wasm.go b/internal/engine/plugin/wasm.go new file mode 100644 index 0000000000..c34fcaecc7 --- /dev/null +++ b/internal/engine/plugin/wasm.go @@ -0,0 +1,513 @@ +package plugin + +import ( + "bytes" + "context" + "crypto/sha256" + "encoding/json" + "errors" + "fmt" + "io" + "log/slog" + "net/http" + "os" + "path/filepath" + "runtime" + "strings" + + "github.com/tetratelabs/wazero" + "github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1" + "github.com/tetratelabs/wazero/sys" + "golang.org/x/sync/singleflight" + + "github.com/sqlc-dev/sqlc/internal/cache" + "github.com/sqlc-dev/sqlc/internal/engine" + "github.com/sqlc-dev/sqlc/internal/info" + "github.com/sqlc-dev/sqlc/internal/source" + "github.com/sqlc-dev/sqlc/internal/sql/ast" + "github.com/sqlc-dev/sqlc/internal/sql/catalog" +) + +var wasmFlight singleflight.Group + +type wasmRuntimeAndCode struct { + rt wazero.Runtime + code wazero.CompiledModule +} + +// WASMRunner runs an engine plugin as a WASM module. +type WASMRunner struct { + URL string + SHA256 string + Env []string + + // Cached responses + commentSyntax *WASMGetCommentSyntaxResponse + dialect *WASMGetDialectResponse +} + +// NewWASMRunner creates a new WASMRunner. +func NewWASMRunner(url, sha256 string, env []string) *WASMRunner { + return &WASMRunner{ + URL: url, + SHA256: sha256, + Env: env, + } +} + +func (r *WASMRunner) getChecksum(ctx context.Context) (string, error) { + if r.SHA256 != "" { + return r.SHA256, nil + } + _, sum, err := r.fetch(ctx, r.URL) + if err != nil { + return "", err + } + slog.Warn("fetching WASM binary to calculate sha256", "sha256", sum) + return sum, nil +} + +func (r *WASMRunner) fetch(ctx context.Context, uri string) ([]byte, string, error) { + var body io.ReadCloser + + switch { + case strings.HasPrefix(uri, "file://"): + file, err := os.Open(strings.TrimPrefix(uri, "file://")) + if err != nil { + return nil, "", fmt.Errorf("os.Open: %s %w", uri, err) + } + body = file + + case strings.HasPrefix(uri, "https://"): + req, err := http.NewRequestWithContext(ctx, "GET", uri, nil) + if err != nil { + return nil, "", fmt.Errorf("http.Get: %s %w", uri, err) + } + req.Header.Set("User-Agent", fmt.Sprintf("sqlc/%s Go/%s (%s %s)", info.Version, runtime.Version(), runtime.GOOS, runtime.GOARCH)) + resp, err := http.DefaultClient.Do(req) + if err != nil { + return nil, "", fmt.Errorf("http.Get: %s %w", r.URL, err) + } + body = resp.Body + + default: + return nil, "", fmt.Errorf("unknown scheme: %s", r.URL) + } + + defer body.Close() + + wmod, err := io.ReadAll(body) + if err != nil { + return nil, "", fmt.Errorf("readall: %w", err) + } + + sum := sha256.Sum256(wmod) + actual := fmt.Sprintf("%x", sum) + + return wmod, actual, nil +} + +func (r *WASMRunner) loadAndCompile(ctx context.Context) (*wasmRuntimeAndCode, error) { + expected, err := r.getChecksum(ctx) + if err != nil { + return nil, err + } + + cacheDir, err := cache.PluginsDir() + if err != nil { + return nil, err + } + + value, err, _ := wasmFlight.Do(expected, func() (interface{}, error) { + return r.loadAndCompileWASM(ctx, cacheDir, expected) + }) + if err != nil { + return nil, err + } + + data, ok := value.(*wasmRuntimeAndCode) + if !ok { + return nil, fmt.Errorf("returned value was not a compiled module") + } + return data, nil +} + +func (r *WASMRunner) loadAndCompileWASM(ctx context.Context, cacheDir string, expected string) (*wasmRuntimeAndCode, error) { + pluginDir := filepath.Join(cacheDir, expected) + pluginPath := filepath.Join(pluginDir, "engine.wasm") + _, staterr := os.Stat(pluginPath) + + uri := r.URL + if staterr == nil { + uri = "file://" + pluginPath + } + + wmod, actual, err := r.fetch(ctx, uri) + if err != nil { + return nil, err + } + + if expected != actual { + return nil, fmt.Errorf("invalid checksum: expected %s, got %s", expected, actual) + } + + if staterr != nil { + err := os.Mkdir(pluginDir, 0755) + if err != nil && !os.IsExist(err) { + return nil, fmt.Errorf("mkdirall: %w", err) + } + if err := os.WriteFile(pluginPath, wmod, 0444); err != nil { + return nil, fmt.Errorf("cache wasm: %w", err) + } + } + + wazeroCache, err := wazero.NewCompilationCacheWithDir(filepath.Join(cacheDir, "wazero")) + if err != nil { + return nil, fmt.Errorf("wazero.NewCompilationCacheWithDir: %w", err) + } + + config := wazero.NewRuntimeConfig().WithCompilationCache(wazeroCache) + rt := wazero.NewRuntimeWithConfig(ctx, config) + + if _, err := wasi_snapshot_preview1.Instantiate(ctx, rt); err != nil { + return nil, fmt.Errorf("wasi_snapshot_preview1 instantiate: %w", err) + } + + code, err := rt.CompileModule(ctx, wmod) + if err != nil { + return nil, fmt.Errorf("compile module: %w", err) + } + + return &wasmRuntimeAndCode{rt: rt, code: code}, nil +} + +func (r *WASMRunner) invoke(ctx context.Context, method string, req, resp any) error { + stdin, err := json.Marshal(req) + if err != nil { + return fmt.Errorf("failed to encode request: %w", err) + } + + runtimeAndCode, err := r.loadAndCompile(ctx) + if err != nil { + return fmt.Errorf("loadBytes: %w", err) + } + + var stderr, stdout bytes.Buffer + + conf := wazero.NewModuleConfig(). + WithName(""). + WithArgs("engine.wasm", method). + WithStdin(bytes.NewReader(stdin)). + WithStdout(&stdout). + WithStderr(&stderr). + WithEnv("SQLC_VERSION", info.Version) + for _, key := range r.Env { + conf = conf.WithEnv(key, os.Getenv(key)) + } + + result, err := runtimeAndCode.rt.InstantiateModule(ctx, runtimeAndCode.code, conf) + if err == nil { + defer result.Close(ctx) + } + if cerr := checkWASMError(err, stderr); cerr != nil { + return cerr + } + + if err := json.Unmarshal(stdout.Bytes(), resp); err != nil { + return fmt.Errorf("failed to decode response: %w", err) + } + + return nil +} + +func checkWASMError(err error, stderr bytes.Buffer) error { + if err == nil { + return err + } + + if exitErr, ok := err.(*sys.ExitError); ok { + if exitErr.ExitCode() == 0 { + return nil + } + } + + stderrBlob := stderr.String() + if len(stderrBlob) > 0 { + return errors.New(stderrBlob) + } + return fmt.Errorf("call: %w", err) +} + +// Parse implements engine.Parser. +func (r *WASMRunner) Parse(reader io.Reader) ([]ast.Statement, error) { + sql, err := io.ReadAll(reader) + if err != nil { + return nil, err + } + + req := &WASMParseRequest{SQL: string(sql)} + resp := &WASMParseResponse{} + + if err := r.invoke(context.Background(), "parse", req, resp); err != nil { + return nil, err + } + + var stmts []ast.Statement + for _, s := range resp.Statements { + node, err := parseASTJSON(s.ASTJSON) + if err != nil { + return nil, fmt.Errorf("failed to parse AST: %w", err) + } + + stmts = append(stmts, ast.Statement{ + Raw: &ast.RawStmt{ + Stmt: node, + StmtLocation: s.StmtLocation, + StmtLen: s.StmtLen, + }, + }) + } + + return stmts, nil +} + +// CommentSyntax implements engine.Parser. +func (r *WASMRunner) CommentSyntax() source.CommentSyntax { + if r.commentSyntax == nil { + req := &WASMGetCommentSyntaxRequest{} + resp := &WASMGetCommentSyntaxResponse{} + if err := r.invoke(context.Background(), "get_comment_syntax", req, resp); err != nil { + return source.CommentSyntax{ + Dash: true, + SlashStar: true, + } + } + r.commentSyntax = resp + } + + return source.CommentSyntax{ + Dash: r.commentSyntax.Dash, + SlashStar: r.commentSyntax.SlashStar, + Hash: r.commentSyntax.Hash, + } +} + +// IsReservedKeyword implements engine.Parser. +func (r *WASMRunner) IsReservedKeyword(s string) bool { + req := &WASMIsReservedKeywordRequest{Keyword: s} + resp := &WASMIsReservedKeywordResponse{} + if err := r.invoke(context.Background(), "is_reserved_keyword", req, resp); err != nil { + return false + } + return resp.IsReserved +} + +// GetCatalog returns the initial catalog for this engine. +func (r *WASMRunner) GetCatalog() (*catalog.Catalog, error) { + req := &WASMGetCatalogRequest{} + resp := &WASMGetCatalogResponse{} + if err := r.invoke(context.Background(), "get_catalog", req, resp); err != nil { + return nil, err + } + + return convertWASMCatalog(&resp.Catalog), nil +} + +// QuoteIdent implements engine.Dialect. +func (r *WASMRunner) QuoteIdent(s string) string { + r.ensureDialect() + if r.IsReservedKeyword(s) && r.dialect.QuoteChar != "" { + return r.dialect.QuoteChar + s + r.dialect.QuoteChar + } + return s +} + +// TypeName implements engine.Dialect. +func (r *WASMRunner) TypeName(ns, name string) string { + if ns != "" { + return ns + "." + name + } + return name +} + +// Param implements engine.Dialect. +func (r *WASMRunner) Param(n int) string { + r.ensureDialect() + switch r.dialect.ParamStyle { + case "dollar": + return fmt.Sprintf("$%d", n) + case "question": + return "?" + case "at": + return fmt.Sprintf("@p%d", n) + default: + return fmt.Sprintf("$%d", n) + } +} + +// NamedParam implements engine.Dialect. +func (r *WASMRunner) NamedParam(name string) string { + r.ensureDialect() + if r.dialect.ParamPrefix != "" { + return r.dialect.ParamPrefix + name + } + return "@" + name +} + +// Cast implements engine.Dialect. +func (r *WASMRunner) Cast(arg, typeName string) string { + r.ensureDialect() + switch r.dialect.CastSyntax { + case "double_colon": + return arg + "::" + typeName + default: + return "CAST(" + arg + " AS " + typeName + ")" + } +} + +func (r *WASMRunner) ensureDialect() { + if r.dialect == nil { + req := &WASMGetDialectRequest{} + resp := &WASMGetDialectResponse{} + if err := r.invoke(context.Background(), "get_dialect", req, resp); err != nil { + r.dialect = &WASMGetDialectResponse{ + QuoteChar: `"`, + ParamStyle: "dollar", + ParamPrefix: "@", + CastSyntax: "cast_function", + } + } else { + r.dialect = resp + } + } +} + +// convertWASMCatalog converts a WASM JSON Catalog to catalog.Catalog. +func convertWASMCatalog(c *WASMCatalog) *catalog.Catalog { + if c == nil { + return catalog.New("") + } + + cat := catalog.New(c.DefaultSchema) + cat.Name = c.Name + cat.Comment = c.Comment + cat.SearchPath = c.SearchPath + + cat.Schemas = make([]*catalog.Schema, 0, len(c.Schemas)) + for _, s := range c.Schemas { + schema := &catalog.Schema{ + Name: s.Name, + Comment: s.Comment, + } + + for _, t := range s.Tables { + table := &catalog.Table{ + Rel: &ast.TableName{ + Catalog: t.Catalog, + Schema: t.Schema, + Name: t.Name, + }, + Comment: t.Comment, + } + for _, col := range t.Columns { + table.Columns = append(table.Columns, &catalog.Column{ + Name: col.Name, + Type: ast.TypeName{Name: col.DataType}, + IsNotNull: col.NotNull, + IsArray: col.IsArray, + ArrayDims: col.ArrayDims, + Comment: col.Comment, + Length: toPointerWASM(col.Length), + IsUnsigned: col.IsUnsigned, + }) + } + schema.Tables = append(schema.Tables, table) + } + + for _, e := range s.Enums { + enum := &catalog.Enum{ + Name: e.Name, + Comment: e.Comment, + } + enum.Vals = append(enum.Vals, e.Values...) + schema.Types = append(schema.Types, enum) + } + + for _, f := range s.Functions { + fn := &catalog.Function{ + Name: f.Name, + Comment: f.Comment, + ReturnType: &ast.TypeName{Schema: f.ReturnType.Schema, Name: f.ReturnType.Name}, + } + for _, arg := range f.Args { + fn.Args = append(fn.Args, &catalog.Argument{ + Name: arg.Name, + Type: &ast.TypeName{Schema: arg.Type.Schema, Name: arg.Type.Name}, + HasDefault: arg.HasDefault, + }) + } + schema.Funcs = append(schema.Funcs, fn) + } + + for _, t := range s.Types { + schema.Types = append(schema.Types, &catalog.CompositeType{ + Name: t.Name, + Comment: t.Comment, + }) + } + + cat.Schemas = append(cat.Schemas, schema) + } + + return cat +} + +func toPointerWASM(n int) *int { + if n == 0 { + return nil + } + return &n +} + +// WASMPluginEngine wraps a WASMRunner to implement engine.Engine. +type WASMPluginEngine struct { + name string + runner *WASMRunner +} + +// NewWASMPluginEngine creates a new engine from a WASM plugin. +func NewWASMPluginEngine(name, url, sha256 string, env []string) *WASMPluginEngine { + return &WASMPluginEngine{ + name: name, + runner: NewWASMRunner(url, sha256, env), + } +} + +// Name implements engine.Engine. +func (e *WASMPluginEngine) Name() string { + return e.name +} + +// Parser implements engine.Engine. +func (e *WASMPluginEngine) Parser() engine.Parser { + return e.runner +} + +// Catalog implements engine.Engine. +func (e *WASMPluginEngine) Catalog() *catalog.Catalog { + cat, err := e.runner.GetCatalog() + if err != nil { + return catalog.New("") + } + return cat +} + +// Selector implements engine.Engine. +func (e *WASMPluginEngine) Selector() engine.Selector { + return &engine.DefaultSelector{} +} + +// Dialect implements engine.Engine. +func (e *WASMPluginEngine) Dialect() engine.Dialect { + return e.runner +} diff --git a/internal/engine/plugin/wasm_types.go b/internal/engine/plugin/wasm_types.go new file mode 100644 index 0000000000..a868475573 --- /dev/null +++ b/internal/engine/plugin/wasm_types.go @@ -0,0 +1,138 @@ +// Package plugin provides JSON types for WASM engine plugins. +// WASM plugins use JSON instead of Protobuf because they can be written in any language. +package plugin + +// WASMParseRequest is sent to the WASM plugin to parse SQL. +type WASMParseRequest struct { + SQL string `json:"sql"` +} + +// WASMParseResponse contains the parsed statements. +type WASMParseResponse struct { + Statements []WASMStatement `json:"statements"` +} + +// WASMStatement represents a parsed SQL statement. +type WASMStatement struct { + RawSQL string `json:"raw_sql"` + StmtLocation int `json:"stmt_location"` + StmtLen int `json:"stmt_len"` + ASTJSON []byte `json:"ast_json"` +} + +// WASMGetCatalogRequest is sent to get the initial catalog. +type WASMGetCatalogRequest struct{} + +// WASMGetCatalogResponse contains the initial catalog. +type WASMGetCatalogResponse struct { + Catalog WASMCatalog `json:"catalog"` +} + +// WASMCatalog represents the database catalog. +type WASMCatalog struct { + DefaultSchema string `json:"default_schema"` + Name string `json:"name"` + Comment string `json:"comment"` + Schemas []WASMSchema `json:"schemas"` + SearchPath []string `json:"search_path"` +} + +// WASMSchema represents a database schema. +type WASMSchema struct { + Name string `json:"name"` + Comment string `json:"comment"` + Tables []WASMTable `json:"tables"` + Enums []WASMEnum `json:"enums"` + Functions []WASMFunction `json:"functions"` + Types []WASMType `json:"types"` +} + +// WASMTable represents a database table. +type WASMTable struct { + Catalog string `json:"catalog"` + Schema string `json:"schema"` + Name string `json:"name"` + Columns []WASMColumn `json:"columns"` + Comment string `json:"comment"` +} + +// WASMColumn represents a table column. +type WASMColumn struct { + Name string `json:"name"` + DataType string `json:"data_type"` + NotNull bool `json:"not_null"` + IsArray bool `json:"is_array"` + ArrayDims int `json:"array_dims"` + Comment string `json:"comment"` + Length int `json:"length"` + IsUnsigned bool `json:"is_unsigned"` +} + +// WASMEnum represents an enum type. +type WASMEnum struct { + Schema string `json:"schema"` + Name string `json:"name"` + Values []string `json:"values"` + Comment string `json:"comment"` +} + +// WASMFunction represents a database function. +type WASMFunction struct { + Schema string `json:"schema"` + Name string `json:"name"` + Args []WASMFunctionArg `json:"args"` + ReturnType WASMDataType `json:"return_type"` + Comment string `json:"comment"` +} + +// WASMFunctionArg represents a function argument. +type WASMFunctionArg struct { + Name string `json:"name"` + Type WASMDataType `json:"type"` + HasDefault bool `json:"has_default"` +} + +// WASMDataType represents a SQL data type. +type WASMDataType struct { + Catalog string `json:"catalog"` + Schema string `json:"schema"` + Name string `json:"name"` +} + +// WASMType represents a composite or custom type. +type WASMType struct { + Schema string `json:"schema"` + Name string `json:"name"` + Comment string `json:"comment"` +} + +// WASMIsReservedKeywordRequest is sent to check if a keyword is reserved. +type WASMIsReservedKeywordRequest struct { + Keyword string `json:"keyword"` +} + +// WASMIsReservedKeywordResponse contains the result. +type WASMIsReservedKeywordResponse struct { + IsReserved bool `json:"is_reserved"` +} + +// WASMGetCommentSyntaxRequest is sent to get supported comment syntax. +type WASMGetCommentSyntaxRequest struct{} + +// WASMGetCommentSyntaxResponse contains supported comment syntax. +type WASMGetCommentSyntaxResponse struct { + Dash bool `json:"dash"` + SlashStar bool `json:"slash_star"` + Hash bool `json:"hash"` +} + +// WASMGetDialectRequest is sent to get dialect information. +type WASMGetDialectRequest struct{} + +// WASMGetDialectResponse contains dialect information. +type WASMGetDialectResponse struct { + QuoteChar string `json:"quote_char"` + ParamStyle string `json:"param_style"` + ParamPrefix string `json:"param_prefix"` + CastSyntax string `json:"cast_syntax"` +} diff --git a/internal/engine/postgresql/engine.go b/internal/engine/postgresql/engine.go new file mode 100644 index 0000000000..dfd2659ea8 --- /dev/null +++ b/internal/engine/postgresql/engine.go @@ -0,0 +1,43 @@ +package postgresql + +import ( + "github.com/sqlc-dev/sqlc/internal/engine" + "github.com/sqlc-dev/sqlc/internal/sql/catalog" +) + +// postgresqlEngine implements the engine.Engine interface for PostgreSQL. +type postgresqlEngine struct { + parser *Parser +} + +// NewEngine creates a new PostgreSQL engine. +func NewEngine() engine.Engine { + return &postgresqlEngine{ + parser: NewParser(), + } +} + +// Name returns the engine name. +func (e *postgresqlEngine) Name() string { + return "postgresql" +} + +// Parser returns the PostgreSQL parser. +func (e *postgresqlEngine) Parser() engine.Parser { + return e.parser +} + +// Catalog returns a new PostgreSQL catalog. +func (e *postgresqlEngine) Catalog() *catalog.Catalog { + return NewCatalog() +} + +// Selector returns nil because PostgreSQL uses the default selector. +func (e *postgresqlEngine) Selector() engine.Selector { + return &engine.DefaultSelector{} +} + +// Dialect returns the parser which implements the Dialect interface. +func (e *postgresqlEngine) Dialect() engine.Dialect { + return e.parser +} diff --git a/internal/engine/register.go b/internal/engine/register.go new file mode 100644 index 0000000000..6631587d80 --- /dev/null +++ b/internal/engine/register.go @@ -0,0 +1,18 @@ +package engine + +import ( + "sync" +) + +var registerOnce sync.Once + +// RegisterBuiltinEngines registers all built-in database engines. +// This function should be called once during application initialization. +// It is safe to call multiple times - subsequent calls are no-ops. +func RegisterBuiltinEngines(factories map[string]EngineFactory) { + registerOnce.Do(func() { + for name, factory := range factories { + Register(name, factory) + } + }) +} diff --git a/internal/engine/registry.go b/internal/engine/registry.go new file mode 100644 index 0000000000..37c8f0936a --- /dev/null +++ b/internal/engine/registry.go @@ -0,0 +1,101 @@ +package engine + +import ( + "fmt" + "sync" +) + +// Registry is a global registry of database engines. +// It allows both built-in and plugin engines to be registered and retrieved. +type Registry struct { + mu sync.RWMutex + engines map[string]EngineFactory +} + +// globalRegistry is the default engine registry used by the application. +var globalRegistry = &Registry{ + engines: make(map[string]EngineFactory), +} + +// Register adds a new engine factory to the global registry. +// It panics if an engine with the same name is already registered. +func Register(name string, factory EngineFactory) { + globalRegistry.Register(name, factory) +} + +// Get retrieves an engine by name from the global registry. +// It returns an error if the engine is not found. +func Get(name string) (Engine, error) { + return globalRegistry.Get(name) +} + +// List returns a list of all registered engine names. +func List() []string { + return globalRegistry.List() +} + +// IsRegistered returns true if an engine with the given name is registered. +func IsRegistered(name string) bool { + return globalRegistry.IsRegistered(name) +} + +// Register adds a new engine factory to this registry. +// It panics if an engine with the same name is already registered. +func (r *Registry) Register(name string, factory EngineFactory) { + r.mu.Lock() + defer r.mu.Unlock() + + if _, exists := r.engines[name]; exists { + panic(fmt.Sprintf("engine %q is already registered", name)) + } + r.engines[name] = factory +} + +// RegisterOrReplace adds or replaces an engine factory in this registry. +// This is useful for testing or for replacing built-in engines with plugins. +func (r *Registry) RegisterOrReplace(name string, factory EngineFactory) { + r.mu.Lock() + defer r.mu.Unlock() + r.engines[name] = factory +} + +// Get retrieves an engine by name from this registry. +// It returns an error if the engine is not found. +func (r *Registry) Get(name string) (Engine, error) { + r.mu.RLock() + defer r.mu.RUnlock() + + factory, ok := r.engines[name] + if !ok { + return nil, fmt.Errorf("unknown engine: %s", name) + } + return factory(), nil +} + +// List returns a list of all registered engine names. +func (r *Registry) List() []string { + r.mu.RLock() + defer r.mu.RUnlock() + + names := make([]string, 0, len(r.engines)) + for name := range r.engines { + names = append(names, name) + } + return names +} + +// IsRegistered returns true if an engine with the given name is registered. +func (r *Registry) IsRegistered(name string) bool { + r.mu.RLock() + defer r.mu.RUnlock() + _, ok := r.engines[name] + return ok +} + +// Unregister removes an engine from this registry. +// This is primarily useful for testing. +func (r *Registry) Unregister(name string) { + r.mu.Lock() + defer r.mu.Unlock() + delete(r.engines, name) +} diff --git a/internal/engine/sqlite/engine.go b/internal/engine/sqlite/engine.go new file mode 100644 index 0000000000..85b45f74d5 --- /dev/null +++ b/internal/engine/sqlite/engine.go @@ -0,0 +1,61 @@ +package sqlite + +import ( + "github.com/sqlc-dev/sqlc/internal/engine" + "github.com/sqlc-dev/sqlc/internal/sql/catalog" +) + +// sqliteEngine implements the engine.Engine interface for SQLite. +type sqliteEngine struct { + parser *Parser +} + +// NewEngine creates a new SQLite engine. +func NewEngine() engine.Engine { + return &sqliteEngine{ + parser: NewParser(), + } +} + +// Name returns the engine name. +func (e *sqliteEngine) Name() string { + return "sqlite" +} + +// Parser returns the SQLite parser. +func (e *sqliteEngine) Parser() engine.Parser { + return e.parser +} + +// Catalog returns a new SQLite catalog. +func (e *sqliteEngine) Catalog() *catalog.Catalog { + return NewCatalog() +} + +// Selector returns a SQLite-specific selector for handling jsonb columns. +func (e *sqliteEngine) Selector() engine.Selector { + return &sqliteSelector{} +} + +// Dialect returns the parser which implements the Dialect interface. +func (e *sqliteEngine) Dialect() engine.Dialect { + return e.parser +} + +// sqliteSelector wraps jsonb columns with json() for proper output. +type sqliteSelector struct{} + +// ColumnExpr wraps jsonb columns with json() function. +func (s *sqliteSelector) ColumnExpr(name string, dataType string) string { + // Under SQLite, neither json nor jsonb are real data types, and rather just + // of type blob, so database drivers just return whatever raw binary is + // stored as values. This is a problem for jsonb, which is considered an + // internal format to SQLite and no attempt should be made to parse it + // outside of the database itself. For jsonb columns in SQLite, wrap values + // in `json(col)` to coerce the internal binary format to JSON parsable by + // the user-space application. + if dataType == "jsonb" { + return "json(" + name + ")" + } + return name +} diff --git a/internal/ext/process/gen.go b/internal/ext/process/gen.go index b5720dbc33..8947133e01 100644 --- a/internal/ext/process/gen.go +++ b/internal/ext/process/gen.go @@ -7,6 +7,7 @@ import ( "fmt" "os" "os/exec" + "strings" "google.golang.org/grpc" "google.golang.org/grpc/codes" @@ -20,6 +21,7 @@ import ( type Runner struct { Cmd string + Dir string // Working directory for the plugin (config file directory) Format string Env []string } @@ -53,14 +55,27 @@ func (r *Runner) Invoke(ctx context.Context, method string, args any, reply any, return fmt.Errorf("unknown plugin format: %s", r.Format) } + // Parse command string to support formats like "go run ./path" + cmdParts := strings.Fields(r.Cmd) + if len(cmdParts) == 0 { + return fmt.Errorf("process: %s not found", r.Cmd) + } + // Check if the output plugin exists - path, err := exec.LookPath(r.Cmd) + path, err := exec.LookPath(cmdParts[0]) if err != nil { return fmt.Errorf("process: %s not found", r.Cmd) } - cmd := exec.CommandContext(ctx, path, method) + // Build arguments: rest of cmdParts + method + cmdArgs := append(cmdParts[1:], method) + cmd := exec.CommandContext(ctx, path, cmdArgs...) cmd.Stdin = bytes.NewReader(stdin) + // Set working directory to config file directory for relative paths + if r.Dir != "" { + cmd.Dir = r.Dir + } + // Pass only SQLC_VERSION and explicitly configured environment variables cmd.Env = []string{ fmt.Sprintf("SQLC_VERSION=%s", info.Version), } @@ -70,6 +85,20 @@ func (r *Runner) Invoke(ctx context.Context, method string, args any, reply any, } cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", key, os.Getenv(key))) } + // For "go run" commands, inherit PATH and Go-related environment + if len(cmdParts) > 1 && cmdParts[0] == "go" { + for _, env := range os.Environ() { + if strings.HasPrefix(env, "PATH=") || + strings.HasPrefix(env, "GOPATH=") || + strings.HasPrefix(env, "GOROOT=") || + strings.HasPrefix(env, "GOWORK=") || + strings.HasPrefix(env, "HOME=") || + strings.HasPrefix(env, "GOCACHE=") || + strings.HasPrefix(env, "GOMODCACHE=") { + cmd.Env = append(cmd.Env, env) + } + } + } out, err := cmd.Output() if err != nil { diff --git a/internal/sql/catalog/table.go b/internal/sql/catalog/table.go index dc30acfa1e..a9508e1f27 100644 --- a/internal/sql/catalog/table.go +++ b/internal/sql/catalog/table.go @@ -248,6 +248,9 @@ func (c *Catalog) alterTableSetSchema(stmt *ast.AlterTableSetSchemaStmt) error { } func (c *Catalog) createTable(stmt *ast.CreateTableStmt) error { + if stmt.Name == nil { + return fmt.Errorf("create table statement missing table name") + } ns := stmt.Name.Schema if ns == "" { ns = c.DefaultSchema diff --git a/pkg/engine/engine.pb.go b/pkg/engine/engine.pb.go new file mode 100644 index 0000000000..2782ba0e86 --- /dev/null +++ b/pkg/engine/engine.pb.go @@ -0,0 +1,1417 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc v6.32.1 +// source: engine/engine.proto + +package engine + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// ParseRequest contains the SQL to parse. +type ParseRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Sql string `protobuf:"bytes,1,opt,name=sql,proto3" json:"sql,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ParseRequest) Reset() { + *x = ParseRequest{} + mi := &file_engine_engine_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ParseRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ParseRequest) ProtoMessage() {} + +func (x *ParseRequest) ProtoReflect() protoreflect.Message { + mi := &file_engine_engine_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ParseRequest.ProtoReflect.Descriptor instead. +func (*ParseRequest) Descriptor() ([]byte, []int) { + return file_engine_engine_proto_rawDescGZIP(), []int{0} +} + +func (x *ParseRequest) GetSql() string { + if x != nil { + return x.Sql + } + return "" +} + +// ParseResponse contains the parsed statements. +type ParseResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Statements []*Statement `protobuf:"bytes,1,rep,name=statements,proto3" json:"statements,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ParseResponse) Reset() { + *x = ParseResponse{} + mi := &file_engine_engine_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ParseResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ParseResponse) ProtoMessage() {} + +func (x *ParseResponse) ProtoReflect() protoreflect.Message { + mi := &file_engine_engine_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ParseResponse.ProtoReflect.Descriptor instead. +func (*ParseResponse) Descriptor() ([]byte, []int) { + return file_engine_engine_proto_rawDescGZIP(), []int{1} +} + +func (x *ParseResponse) GetStatements() []*Statement { + if x != nil { + return x.Statements + } + return nil +} + +// Statement represents a parsed SQL statement. +type Statement struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The raw SQL text of the statement. + RawSql string `protobuf:"bytes,1,opt,name=raw_sql,json=rawSql,proto3" json:"raw_sql,omitempty"` + // The position in the input where this statement starts. + StmtLocation int32 `protobuf:"varint,2,opt,name=stmt_location,json=stmtLocation,proto3" json:"stmt_location,omitempty"` + // The length of the statement in bytes. + StmtLen int32 `protobuf:"varint,3,opt,name=stmt_len,json=stmtLen,proto3" json:"stmt_len,omitempty"` + // The AST of the statement encoded as JSON. + // The JSON structure follows the internal AST format. + AstJson []byte `protobuf:"bytes,4,opt,name=ast_json,json=astJson,proto3" json:"ast_json,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Statement) Reset() { + *x = Statement{} + mi := &file_engine_engine_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Statement) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Statement) ProtoMessage() {} + +func (x *Statement) ProtoReflect() protoreflect.Message { + mi := &file_engine_engine_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Statement.ProtoReflect.Descriptor instead. +func (*Statement) Descriptor() ([]byte, []int) { + return file_engine_engine_proto_rawDescGZIP(), []int{2} +} + +func (x *Statement) GetRawSql() string { + if x != nil { + return x.RawSql + } + return "" +} + +func (x *Statement) GetStmtLocation() int32 { + if x != nil { + return x.StmtLocation + } + return 0 +} + +func (x *Statement) GetStmtLen() int32 { + if x != nil { + return x.StmtLen + } + return 0 +} + +func (x *Statement) GetAstJson() []byte { + if x != nil { + return x.AstJson + } + return nil +} + +// GetCatalogRequest is empty for now. +type GetCatalogRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetCatalogRequest) Reset() { + *x = GetCatalogRequest{} + mi := &file_engine_engine_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetCatalogRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetCatalogRequest) ProtoMessage() {} + +func (x *GetCatalogRequest) ProtoReflect() protoreflect.Message { + mi := &file_engine_engine_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetCatalogRequest.ProtoReflect.Descriptor instead. +func (*GetCatalogRequest) Descriptor() ([]byte, []int) { + return file_engine_engine_proto_rawDescGZIP(), []int{3} +} + +// GetCatalogResponse contains the initial catalog. +type GetCatalogResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Catalog *Catalog `protobuf:"bytes,1,opt,name=catalog,proto3" json:"catalog,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetCatalogResponse) Reset() { + *x = GetCatalogResponse{} + mi := &file_engine_engine_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetCatalogResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetCatalogResponse) ProtoMessage() {} + +func (x *GetCatalogResponse) ProtoReflect() protoreflect.Message { + mi := &file_engine_engine_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetCatalogResponse.ProtoReflect.Descriptor instead. +func (*GetCatalogResponse) Descriptor() ([]byte, []int) { + return file_engine_engine_proto_rawDescGZIP(), []int{4} +} + +func (x *GetCatalogResponse) GetCatalog() *Catalog { + if x != nil { + return x.Catalog + } + return nil +} + +// Catalog represents the database catalog. +type Catalog struct { + state protoimpl.MessageState `protogen:"open.v1"` + Comment string `protobuf:"bytes,1,opt,name=comment,proto3" json:"comment,omitempty"` + DefaultSchema string `protobuf:"bytes,2,opt,name=default_schema,json=defaultSchema,proto3" json:"default_schema,omitempty"` + Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` + Schemas []*Schema `protobuf:"bytes,4,rep,name=schemas,proto3" json:"schemas,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Catalog) Reset() { + *x = Catalog{} + mi := &file_engine_engine_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Catalog) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Catalog) ProtoMessage() {} + +func (x *Catalog) ProtoReflect() protoreflect.Message { + mi := &file_engine_engine_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Catalog.ProtoReflect.Descriptor instead. +func (*Catalog) Descriptor() ([]byte, []int) { + return file_engine_engine_proto_rawDescGZIP(), []int{5} +} + +func (x *Catalog) GetComment() string { + if x != nil { + return x.Comment + } + return "" +} + +func (x *Catalog) GetDefaultSchema() string { + if x != nil { + return x.DefaultSchema + } + return "" +} + +func (x *Catalog) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Catalog) GetSchemas() []*Schema { + if x != nil { + return x.Schemas + } + return nil +} + +// Schema represents a database schema. +type Schema struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Comment string `protobuf:"bytes,2,opt,name=comment,proto3" json:"comment,omitempty"` + Tables []*Table `protobuf:"bytes,3,rep,name=tables,proto3" json:"tables,omitempty"` + Enums []*Enum `protobuf:"bytes,4,rep,name=enums,proto3" json:"enums,omitempty"` + Functions []*Function `protobuf:"bytes,5,rep,name=functions,proto3" json:"functions,omitempty"` + Types []*Type `protobuf:"bytes,6,rep,name=types,proto3" json:"types,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Schema) Reset() { + *x = Schema{} + mi := &file_engine_engine_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Schema) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Schema) ProtoMessage() {} + +func (x *Schema) ProtoReflect() protoreflect.Message { + mi := &file_engine_engine_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Schema.ProtoReflect.Descriptor instead. +func (*Schema) Descriptor() ([]byte, []int) { + return file_engine_engine_proto_rawDescGZIP(), []int{6} +} + +func (x *Schema) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Schema) GetComment() string { + if x != nil { + return x.Comment + } + return "" +} + +func (x *Schema) GetTables() []*Table { + if x != nil { + return x.Tables + } + return nil +} + +func (x *Schema) GetEnums() []*Enum { + if x != nil { + return x.Enums + } + return nil +} + +func (x *Schema) GetFunctions() []*Function { + if x != nil { + return x.Functions + } + return nil +} + +func (x *Schema) GetTypes() []*Type { + if x != nil { + return x.Types + } + return nil +} + +// Table represents a database table. +type Table struct { + state protoimpl.MessageState `protogen:"open.v1"` + Catalog string `protobuf:"bytes,1,opt,name=catalog,proto3" json:"catalog,omitempty"` + Schema string `protobuf:"bytes,2,opt,name=schema,proto3" json:"schema,omitempty"` + Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` + Columns []*Column `protobuf:"bytes,4,rep,name=columns,proto3" json:"columns,omitempty"` + Comment string `protobuf:"bytes,5,opt,name=comment,proto3" json:"comment,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Table) Reset() { + *x = Table{} + mi := &file_engine_engine_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Table) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Table) ProtoMessage() {} + +func (x *Table) ProtoReflect() protoreflect.Message { + mi := &file_engine_engine_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Table.ProtoReflect.Descriptor instead. +func (*Table) Descriptor() ([]byte, []int) { + return file_engine_engine_proto_rawDescGZIP(), []int{7} +} + +func (x *Table) GetCatalog() string { + if x != nil { + return x.Catalog + } + return "" +} + +func (x *Table) GetSchema() string { + if x != nil { + return x.Schema + } + return "" +} + +func (x *Table) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Table) GetColumns() []*Column { + if x != nil { + return x.Columns + } + return nil +} + +func (x *Table) GetComment() string { + if x != nil { + return x.Comment + } + return "" +} + +// Column represents a table column. +type Column struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + DataType string `protobuf:"bytes,2,opt,name=data_type,json=dataType,proto3" json:"data_type,omitempty"` + NotNull bool `protobuf:"varint,3,opt,name=not_null,json=notNull,proto3" json:"not_null,omitempty"` + IsArray bool `protobuf:"varint,4,opt,name=is_array,json=isArray,proto3" json:"is_array,omitempty"` + ArrayDims int32 `protobuf:"varint,5,opt,name=array_dims,json=arrayDims,proto3" json:"array_dims,omitempty"` + Comment string `protobuf:"bytes,6,opt,name=comment,proto3" json:"comment,omitempty"` + Length int32 `protobuf:"varint,7,opt,name=length,proto3" json:"length,omitempty"` + IsUnsigned bool `protobuf:"varint,8,opt,name=is_unsigned,json=isUnsigned,proto3" json:"is_unsigned,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Column) Reset() { + *x = Column{} + mi := &file_engine_engine_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Column) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Column) ProtoMessage() {} + +func (x *Column) ProtoReflect() protoreflect.Message { + mi := &file_engine_engine_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Column.ProtoReflect.Descriptor instead. +func (*Column) Descriptor() ([]byte, []int) { + return file_engine_engine_proto_rawDescGZIP(), []int{8} +} + +func (x *Column) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Column) GetDataType() string { + if x != nil { + return x.DataType + } + return "" +} + +func (x *Column) GetNotNull() bool { + if x != nil { + return x.NotNull + } + return false +} + +func (x *Column) GetIsArray() bool { + if x != nil { + return x.IsArray + } + return false +} + +func (x *Column) GetArrayDims() int32 { + if x != nil { + return x.ArrayDims + } + return 0 +} + +func (x *Column) GetComment() string { + if x != nil { + return x.Comment + } + return "" +} + +func (x *Column) GetLength() int32 { + if x != nil { + return x.Length + } + return 0 +} + +func (x *Column) GetIsUnsigned() bool { + if x != nil { + return x.IsUnsigned + } + return false +} + +// Enum represents an enum type. +type Enum struct { + state protoimpl.MessageState `protogen:"open.v1"` + Schema string `protobuf:"bytes,1,opt,name=schema,proto3" json:"schema,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Values []string `protobuf:"bytes,3,rep,name=values,proto3" json:"values,omitempty"` + Comment string `protobuf:"bytes,4,opt,name=comment,proto3" json:"comment,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Enum) Reset() { + *x = Enum{} + mi := &file_engine_engine_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Enum) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Enum) ProtoMessage() {} + +func (x *Enum) ProtoReflect() protoreflect.Message { + mi := &file_engine_engine_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Enum.ProtoReflect.Descriptor instead. +func (*Enum) Descriptor() ([]byte, []int) { + return file_engine_engine_proto_rawDescGZIP(), []int{9} +} + +func (x *Enum) GetSchema() string { + if x != nil { + return x.Schema + } + return "" +} + +func (x *Enum) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Enum) GetValues() []string { + if x != nil { + return x.Values + } + return nil +} + +func (x *Enum) GetComment() string { + if x != nil { + return x.Comment + } + return "" +} + +// Function represents a database function. +type Function struct { + state protoimpl.MessageState `protogen:"open.v1"` + Schema string `protobuf:"bytes,1,opt,name=schema,proto3" json:"schema,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Args []*FunctionArg `protobuf:"bytes,3,rep,name=args,proto3" json:"args,omitempty"` + ReturnType *DataType `protobuf:"bytes,4,opt,name=return_type,json=returnType,proto3" json:"return_type,omitempty"` + Comment string `protobuf:"bytes,5,opt,name=comment,proto3" json:"comment,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Function) Reset() { + *x = Function{} + mi := &file_engine_engine_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Function) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Function) ProtoMessage() {} + +func (x *Function) ProtoReflect() protoreflect.Message { + mi := &file_engine_engine_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Function.ProtoReflect.Descriptor instead. +func (*Function) Descriptor() ([]byte, []int) { + return file_engine_engine_proto_rawDescGZIP(), []int{10} +} + +func (x *Function) GetSchema() string { + if x != nil { + return x.Schema + } + return "" +} + +func (x *Function) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Function) GetArgs() []*FunctionArg { + if x != nil { + return x.Args + } + return nil +} + +func (x *Function) GetReturnType() *DataType { + if x != nil { + return x.ReturnType + } + return nil +} + +func (x *Function) GetComment() string { + if x != nil { + return x.Comment + } + return "" +} + +// FunctionArg represents a function argument. +type FunctionArg struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Type *DataType `protobuf:"bytes,2,opt,name=type,proto3" json:"type,omitempty"` + HasDefault bool `protobuf:"varint,3,opt,name=has_default,json=hasDefault,proto3" json:"has_default,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *FunctionArg) Reset() { + *x = FunctionArg{} + mi := &file_engine_engine_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *FunctionArg) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FunctionArg) ProtoMessage() {} + +func (x *FunctionArg) ProtoReflect() protoreflect.Message { + mi := &file_engine_engine_proto_msgTypes[11] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FunctionArg.ProtoReflect.Descriptor instead. +func (*FunctionArg) Descriptor() ([]byte, []int) { + return file_engine_engine_proto_rawDescGZIP(), []int{11} +} + +func (x *FunctionArg) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *FunctionArg) GetType() *DataType { + if x != nil { + return x.Type + } + return nil +} + +func (x *FunctionArg) GetHasDefault() bool { + if x != nil { + return x.HasDefault + } + return false +} + +// DataType represents a SQL data type. +type DataType struct { + state protoimpl.MessageState `protogen:"open.v1"` + Catalog string `protobuf:"bytes,1,opt,name=catalog,proto3" json:"catalog,omitempty"` + Schema string `protobuf:"bytes,2,opt,name=schema,proto3" json:"schema,omitempty"` + Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DataType) Reset() { + *x = DataType{} + mi := &file_engine_engine_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DataType) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DataType) ProtoMessage() {} + +func (x *DataType) ProtoReflect() protoreflect.Message { + mi := &file_engine_engine_proto_msgTypes[12] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DataType.ProtoReflect.Descriptor instead. +func (*DataType) Descriptor() ([]byte, []int) { + return file_engine_engine_proto_rawDescGZIP(), []int{12} +} + +func (x *DataType) GetCatalog() string { + if x != nil { + return x.Catalog + } + return "" +} + +func (x *DataType) GetSchema() string { + if x != nil { + return x.Schema + } + return "" +} + +func (x *DataType) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +// Type represents a composite or custom type. +type Type struct { + state protoimpl.MessageState `protogen:"open.v1"` + Schema string `protobuf:"bytes,1,opt,name=schema,proto3" json:"schema,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Comment string `protobuf:"bytes,3,opt,name=comment,proto3" json:"comment,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Type) Reset() { + *x = Type{} + mi := &file_engine_engine_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Type) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Type) ProtoMessage() {} + +func (x *Type) ProtoReflect() protoreflect.Message { + mi := &file_engine_engine_proto_msgTypes[13] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Type.ProtoReflect.Descriptor instead. +func (*Type) Descriptor() ([]byte, []int) { + return file_engine_engine_proto_rawDescGZIP(), []int{13} +} + +func (x *Type) GetSchema() string { + if x != nil { + return x.Schema + } + return "" +} + +func (x *Type) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Type) GetComment() string { + if x != nil { + return x.Comment + } + return "" +} + +// IsReservedKeywordRequest contains the keyword to check. +type IsReservedKeywordRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Keyword string `protobuf:"bytes,1,opt,name=keyword,proto3" json:"keyword,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *IsReservedKeywordRequest) Reset() { + *x = IsReservedKeywordRequest{} + mi := &file_engine_engine_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *IsReservedKeywordRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*IsReservedKeywordRequest) ProtoMessage() {} + +func (x *IsReservedKeywordRequest) ProtoReflect() protoreflect.Message { + mi := &file_engine_engine_proto_msgTypes[14] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use IsReservedKeywordRequest.ProtoReflect.Descriptor instead. +func (*IsReservedKeywordRequest) Descriptor() ([]byte, []int) { + return file_engine_engine_proto_rawDescGZIP(), []int{14} +} + +func (x *IsReservedKeywordRequest) GetKeyword() string { + if x != nil { + return x.Keyword + } + return "" +} + +// IsReservedKeywordResponse contains the result. +type IsReservedKeywordResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + IsReserved bool `protobuf:"varint,1,opt,name=is_reserved,json=isReserved,proto3" json:"is_reserved,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *IsReservedKeywordResponse) Reset() { + *x = IsReservedKeywordResponse{} + mi := &file_engine_engine_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *IsReservedKeywordResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*IsReservedKeywordResponse) ProtoMessage() {} + +func (x *IsReservedKeywordResponse) ProtoReflect() protoreflect.Message { + mi := &file_engine_engine_proto_msgTypes[15] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use IsReservedKeywordResponse.ProtoReflect.Descriptor instead. +func (*IsReservedKeywordResponse) Descriptor() ([]byte, []int) { + return file_engine_engine_proto_rawDescGZIP(), []int{15} +} + +func (x *IsReservedKeywordResponse) GetIsReserved() bool { + if x != nil { + return x.IsReserved + } + return false +} + +// GetCommentSyntaxRequest is empty. +type GetCommentSyntaxRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetCommentSyntaxRequest) Reset() { + *x = GetCommentSyntaxRequest{} + mi := &file_engine_engine_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetCommentSyntaxRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetCommentSyntaxRequest) ProtoMessage() {} + +func (x *GetCommentSyntaxRequest) ProtoReflect() protoreflect.Message { + mi := &file_engine_engine_proto_msgTypes[16] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetCommentSyntaxRequest.ProtoReflect.Descriptor instead. +func (*GetCommentSyntaxRequest) Descriptor() ([]byte, []int) { + return file_engine_engine_proto_rawDescGZIP(), []int{16} +} + +// GetCommentSyntaxResponse contains supported comment syntax. +type GetCommentSyntaxResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Dash bool `protobuf:"varint,1,opt,name=dash,proto3" json:"dash,omitempty"` // -- comment + SlashStar bool `protobuf:"varint,2,opt,name=slash_star,json=slashStar,proto3" json:"slash_star,omitempty"` // /* comment */ + Hash bool `protobuf:"varint,3,opt,name=hash,proto3" json:"hash,omitempty"` // # comment + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetCommentSyntaxResponse) Reset() { + *x = GetCommentSyntaxResponse{} + mi := &file_engine_engine_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetCommentSyntaxResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetCommentSyntaxResponse) ProtoMessage() {} + +func (x *GetCommentSyntaxResponse) ProtoReflect() protoreflect.Message { + mi := &file_engine_engine_proto_msgTypes[17] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetCommentSyntaxResponse.ProtoReflect.Descriptor instead. +func (*GetCommentSyntaxResponse) Descriptor() ([]byte, []int) { + return file_engine_engine_proto_rawDescGZIP(), []int{17} +} + +func (x *GetCommentSyntaxResponse) GetDash() bool { + if x != nil { + return x.Dash + } + return false +} + +func (x *GetCommentSyntaxResponse) GetSlashStar() bool { + if x != nil { + return x.SlashStar + } + return false +} + +func (x *GetCommentSyntaxResponse) GetHash() bool { + if x != nil { + return x.Hash + } + return false +} + +// GetDialectRequest is empty. +type GetDialectRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetDialectRequest) Reset() { + *x = GetDialectRequest{} + mi := &file_engine_engine_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetDialectRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetDialectRequest) ProtoMessage() {} + +func (x *GetDialectRequest) ProtoReflect() protoreflect.Message { + mi := &file_engine_engine_proto_msgTypes[18] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetDialectRequest.ProtoReflect.Descriptor instead. +func (*GetDialectRequest) Descriptor() ([]byte, []int) { + return file_engine_engine_proto_rawDescGZIP(), []int{18} +} + +// GetDialectResponse contains dialect information. +type GetDialectResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The character(s) used for quoting identifiers (e.g., ", `, [) + QuoteChar string `protobuf:"bytes,1,opt,name=quote_char,json=quoteChar,proto3" json:"quote_char,omitempty"` + // The parameter style: "positional" ($1, ?), "named" (@name, :name) + ParamStyle string `protobuf:"bytes,2,opt,name=param_style,json=paramStyle,proto3" json:"param_style,omitempty"` + // The parameter prefix (e.g., $, ?, @, :) + ParamPrefix string `protobuf:"bytes,3,opt,name=param_prefix,json=paramPrefix,proto3" json:"param_prefix,omitempty"` + // The cast syntax: "double_colon" (::), "cast_function" (CAST(x AS y)) + CastSyntax string `protobuf:"bytes,4,opt,name=cast_syntax,json=castSyntax,proto3" json:"cast_syntax,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetDialectResponse) Reset() { + *x = GetDialectResponse{} + mi := &file_engine_engine_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetDialectResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetDialectResponse) ProtoMessage() {} + +func (x *GetDialectResponse) ProtoReflect() protoreflect.Message { + mi := &file_engine_engine_proto_msgTypes[19] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetDialectResponse.ProtoReflect.Descriptor instead. +func (*GetDialectResponse) Descriptor() ([]byte, []int) { + return file_engine_engine_proto_rawDescGZIP(), []int{19} +} + +func (x *GetDialectResponse) GetQuoteChar() string { + if x != nil { + return x.QuoteChar + } + return "" +} + +func (x *GetDialectResponse) GetParamStyle() string { + if x != nil { + return x.ParamStyle + } + return "" +} + +func (x *GetDialectResponse) GetParamPrefix() string { + if x != nil { + return x.ParamPrefix + } + return "" +} + +func (x *GetDialectResponse) GetCastSyntax() string { + if x != nil { + return x.CastSyntax + } + return "" +} + +var File_engine_engine_proto protoreflect.FileDescriptor + +const file_engine_engine_proto_rawDesc = "" + + "\n" + + "\x13engine/engine.proto\x12\x06engine\" \n" + + "\fParseRequest\x12\x10\n" + + "\x03sql\x18\x01 \x01(\tR\x03sql\"B\n" + + "\rParseResponse\x121\n" + + "\n" + + "statements\x18\x01 \x03(\v2\x11.engine.StatementR\n" + + "statements\"\x7f\n" + + "\tStatement\x12\x17\n" + + "\araw_sql\x18\x01 \x01(\tR\x06rawSql\x12#\n" + + "\rstmt_location\x18\x02 \x01(\x05R\fstmtLocation\x12\x19\n" + + "\bstmt_len\x18\x03 \x01(\x05R\astmtLen\x12\x19\n" + + "\bast_json\x18\x04 \x01(\fR\aastJson\"\x13\n" + + "\x11GetCatalogRequest\"?\n" + + "\x12GetCatalogResponse\x12)\n" + + "\acatalog\x18\x01 \x01(\v2\x0f.engine.CatalogR\acatalog\"\x88\x01\n" + + "\aCatalog\x12\x18\n" + + "\acomment\x18\x01 \x01(\tR\acomment\x12%\n" + + "\x0edefault_schema\x18\x02 \x01(\tR\rdefaultSchema\x12\x12\n" + + "\x04name\x18\x03 \x01(\tR\x04name\x12(\n" + + "\aschemas\x18\x04 \x03(\v2\x0e.engine.SchemaR\aschemas\"\xd5\x01\n" + + "\x06Schema\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x18\n" + + "\acomment\x18\x02 \x01(\tR\acomment\x12%\n" + + "\x06tables\x18\x03 \x03(\v2\r.engine.TableR\x06tables\x12\"\n" + + "\x05enums\x18\x04 \x03(\v2\f.engine.EnumR\x05enums\x12.\n" + + "\tfunctions\x18\x05 \x03(\v2\x10.engine.FunctionR\tfunctions\x12\"\n" + + "\x05types\x18\x06 \x03(\v2\f.engine.TypeR\x05types\"\x91\x01\n" + + "\x05Table\x12\x18\n" + + "\acatalog\x18\x01 \x01(\tR\acatalog\x12\x16\n" + + "\x06schema\x18\x02 \x01(\tR\x06schema\x12\x12\n" + + "\x04name\x18\x03 \x01(\tR\x04name\x12(\n" + + "\acolumns\x18\x04 \x03(\v2\x0e.engine.ColumnR\acolumns\x12\x18\n" + + "\acomment\x18\x05 \x01(\tR\acomment\"\xe1\x01\n" + + "\x06Column\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x1b\n" + + "\tdata_type\x18\x02 \x01(\tR\bdataType\x12\x19\n" + + "\bnot_null\x18\x03 \x01(\bR\anotNull\x12\x19\n" + + "\bis_array\x18\x04 \x01(\bR\aisArray\x12\x1d\n" + + "\n" + + "array_dims\x18\x05 \x01(\x05R\tarrayDims\x12\x18\n" + + "\acomment\x18\x06 \x01(\tR\acomment\x12\x16\n" + + "\x06length\x18\a \x01(\x05R\x06length\x12\x1f\n" + + "\vis_unsigned\x18\b \x01(\bR\n" + + "isUnsigned\"d\n" + + "\x04Enum\x12\x16\n" + + "\x06schema\x18\x01 \x01(\tR\x06schema\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\x12\x16\n" + + "\x06values\x18\x03 \x03(\tR\x06values\x12\x18\n" + + "\acomment\x18\x04 \x01(\tR\acomment\"\xac\x01\n" + + "\bFunction\x12\x16\n" + + "\x06schema\x18\x01 \x01(\tR\x06schema\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\x12'\n" + + "\x04args\x18\x03 \x03(\v2\x13.engine.FunctionArgR\x04args\x121\n" + + "\vreturn_type\x18\x04 \x01(\v2\x10.engine.DataTypeR\n" + + "returnType\x12\x18\n" + + "\acomment\x18\x05 \x01(\tR\acomment\"h\n" + + "\vFunctionArg\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12$\n" + + "\x04type\x18\x02 \x01(\v2\x10.engine.DataTypeR\x04type\x12\x1f\n" + + "\vhas_default\x18\x03 \x01(\bR\n" + + "hasDefault\"P\n" + + "\bDataType\x12\x18\n" + + "\acatalog\x18\x01 \x01(\tR\acatalog\x12\x16\n" + + "\x06schema\x18\x02 \x01(\tR\x06schema\x12\x12\n" + + "\x04name\x18\x03 \x01(\tR\x04name\"L\n" + + "\x04Type\x12\x16\n" + + "\x06schema\x18\x01 \x01(\tR\x06schema\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\x12\x18\n" + + "\acomment\x18\x03 \x01(\tR\acomment\"4\n" + + "\x18IsReservedKeywordRequest\x12\x18\n" + + "\akeyword\x18\x01 \x01(\tR\akeyword\"<\n" + + "\x19IsReservedKeywordResponse\x12\x1f\n" + + "\vis_reserved\x18\x01 \x01(\bR\n" + + "isReserved\"\x19\n" + + "\x17GetCommentSyntaxRequest\"a\n" + + "\x18GetCommentSyntaxResponse\x12\x12\n" + + "\x04dash\x18\x01 \x01(\bR\x04dash\x12\x1d\n" + + "\n" + + "slash_star\x18\x02 \x01(\bR\tslashStar\x12\x12\n" + + "\x04hash\x18\x03 \x01(\bR\x04hash\"\x13\n" + + "\x11GetDialectRequest\"\x98\x01\n" + + "\x12GetDialectResponse\x12\x1d\n" + + "\n" + + "quote_char\x18\x01 \x01(\tR\tquoteChar\x12\x1f\n" + + "\vparam_style\x18\x02 \x01(\tR\n" + + "paramStyle\x12!\n" + + "\fparam_prefix\x18\x03 \x01(\tR\vparamPrefix\x12\x1f\n" + + "\vcast_syntax\x18\x04 \x01(\tR\n" + + "castSyntax2\x80\x03\n" + + "\rEngineService\x124\n" + + "\x05Parse\x12\x14.engine.ParseRequest\x1a\x15.engine.ParseResponse\x12C\n" + + "\n" + + "GetCatalog\x12\x19.engine.GetCatalogRequest\x1a\x1a.engine.GetCatalogResponse\x12X\n" + + "\x11IsReservedKeyword\x12 .engine.IsReservedKeywordRequest\x1a!.engine.IsReservedKeywordResponse\x12U\n" + + "\x10GetCommentSyntax\x12\x1f.engine.GetCommentSyntaxRequest\x1a .engine.GetCommentSyntaxResponse\x12C\n" + + "\n" + + "GetDialect\x12\x19.engine.GetDialectRequest\x1a\x1a.engine.GetDialectResponseB%Z#github.com/sqlc-dev/sqlc/pkg/engineb\x06proto3" + +var ( + file_engine_engine_proto_rawDescOnce sync.Once + file_engine_engine_proto_rawDescData []byte +) + +func file_engine_engine_proto_rawDescGZIP() []byte { + file_engine_engine_proto_rawDescOnce.Do(func() { + file_engine_engine_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_engine_engine_proto_rawDesc), len(file_engine_engine_proto_rawDesc))) + }) + return file_engine_engine_proto_rawDescData +} + +var file_engine_engine_proto_msgTypes = make([]protoimpl.MessageInfo, 20) +var file_engine_engine_proto_goTypes = []any{ + (*ParseRequest)(nil), // 0: engine.ParseRequest + (*ParseResponse)(nil), // 1: engine.ParseResponse + (*Statement)(nil), // 2: engine.Statement + (*GetCatalogRequest)(nil), // 3: engine.GetCatalogRequest + (*GetCatalogResponse)(nil), // 4: engine.GetCatalogResponse + (*Catalog)(nil), // 5: engine.Catalog + (*Schema)(nil), // 6: engine.Schema + (*Table)(nil), // 7: engine.Table + (*Column)(nil), // 8: engine.Column + (*Enum)(nil), // 9: engine.Enum + (*Function)(nil), // 10: engine.Function + (*FunctionArg)(nil), // 11: engine.FunctionArg + (*DataType)(nil), // 12: engine.DataType + (*Type)(nil), // 13: engine.Type + (*IsReservedKeywordRequest)(nil), // 14: engine.IsReservedKeywordRequest + (*IsReservedKeywordResponse)(nil), // 15: engine.IsReservedKeywordResponse + (*GetCommentSyntaxRequest)(nil), // 16: engine.GetCommentSyntaxRequest + (*GetCommentSyntaxResponse)(nil), // 17: engine.GetCommentSyntaxResponse + (*GetDialectRequest)(nil), // 18: engine.GetDialectRequest + (*GetDialectResponse)(nil), // 19: engine.GetDialectResponse +} +var file_engine_engine_proto_depIdxs = []int32{ + 2, // 0: engine.ParseResponse.statements:type_name -> engine.Statement + 5, // 1: engine.GetCatalogResponse.catalog:type_name -> engine.Catalog + 6, // 2: engine.Catalog.schemas:type_name -> engine.Schema + 7, // 3: engine.Schema.tables:type_name -> engine.Table + 9, // 4: engine.Schema.enums:type_name -> engine.Enum + 10, // 5: engine.Schema.functions:type_name -> engine.Function + 13, // 6: engine.Schema.types:type_name -> engine.Type + 8, // 7: engine.Table.columns:type_name -> engine.Column + 11, // 8: engine.Function.args:type_name -> engine.FunctionArg + 12, // 9: engine.Function.return_type:type_name -> engine.DataType + 12, // 10: engine.FunctionArg.type:type_name -> engine.DataType + 0, // 11: engine.EngineService.Parse:input_type -> engine.ParseRequest + 3, // 12: engine.EngineService.GetCatalog:input_type -> engine.GetCatalogRequest + 14, // 13: engine.EngineService.IsReservedKeyword:input_type -> engine.IsReservedKeywordRequest + 16, // 14: engine.EngineService.GetCommentSyntax:input_type -> engine.GetCommentSyntaxRequest + 18, // 15: engine.EngineService.GetDialect:input_type -> engine.GetDialectRequest + 1, // 16: engine.EngineService.Parse:output_type -> engine.ParseResponse + 4, // 17: engine.EngineService.GetCatalog:output_type -> engine.GetCatalogResponse + 15, // 18: engine.EngineService.IsReservedKeyword:output_type -> engine.IsReservedKeywordResponse + 17, // 19: engine.EngineService.GetCommentSyntax:output_type -> engine.GetCommentSyntaxResponse + 19, // 20: engine.EngineService.GetDialect:output_type -> engine.GetDialectResponse + 16, // [16:21] is the sub-list for method output_type + 11, // [11:16] is the sub-list for method input_type + 11, // [11:11] is the sub-list for extension type_name + 11, // [11:11] is the sub-list for extension extendee + 0, // [0:11] is the sub-list for field type_name +} + +func init() { file_engine_engine_proto_init() } +func file_engine_engine_proto_init() { + if File_engine_engine_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_engine_engine_proto_rawDesc), len(file_engine_engine_proto_rawDesc)), + NumEnums: 0, + NumMessages: 20, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_engine_engine_proto_goTypes, + DependencyIndexes: file_engine_engine_proto_depIdxs, + MessageInfos: file_engine_engine_proto_msgTypes, + }.Build() + File_engine_engine_proto = out.File + file_engine_engine_proto_goTypes = nil + file_engine_engine_proto_depIdxs = nil +} diff --git a/pkg/engine/engine_grpc.pb.go b/pkg/engine/engine_grpc.pb.go new file mode 100644 index 0000000000..fa21c02800 --- /dev/null +++ b/pkg/engine/engine_grpc.pb.go @@ -0,0 +1,291 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.6.0 +// - protoc v6.32.1 +// source: engine/engine.proto + +package engine + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + EngineService_Parse_FullMethodName = "/engine.EngineService/Parse" + EngineService_GetCatalog_FullMethodName = "/engine.EngineService/GetCatalog" + EngineService_IsReservedKeyword_FullMethodName = "/engine.EngineService/IsReservedKeyword" + EngineService_GetCommentSyntax_FullMethodName = "/engine.EngineService/GetCommentSyntax" + EngineService_GetDialect_FullMethodName = "/engine.EngineService/GetDialect" +) + +// EngineServiceClient is the client API for EngineService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +// +// EngineService defines the interface for database engine plugins. +// Engine plugins are responsible for parsing SQL statements and providing +// database-specific functionality. +type EngineServiceClient interface { + // Parse parses SQL statements from the input and returns parsed statements. + Parse(ctx context.Context, in *ParseRequest, opts ...grpc.CallOption) (*ParseResponse, error) + // GetCatalog returns the initial catalog with built-in types and schemas. + GetCatalog(ctx context.Context, in *GetCatalogRequest, opts ...grpc.CallOption) (*GetCatalogResponse, error) + // IsReservedKeyword checks if a string is a reserved keyword. + IsReservedKeyword(ctx context.Context, in *IsReservedKeywordRequest, opts ...grpc.CallOption) (*IsReservedKeywordResponse, error) + // GetCommentSyntax returns the comment syntax supported by this engine. + GetCommentSyntax(ctx context.Context, in *GetCommentSyntaxRequest, opts ...grpc.CallOption) (*GetCommentSyntaxResponse, error) + // GetDialect returns the SQL dialect information for formatting. + GetDialect(ctx context.Context, in *GetDialectRequest, opts ...grpc.CallOption) (*GetDialectResponse, error) +} + +type engineServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewEngineServiceClient(cc grpc.ClientConnInterface) EngineServiceClient { + return &engineServiceClient{cc} +} + +func (c *engineServiceClient) Parse(ctx context.Context, in *ParseRequest, opts ...grpc.CallOption) (*ParseResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ParseResponse) + err := c.cc.Invoke(ctx, EngineService_Parse_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *engineServiceClient) GetCatalog(ctx context.Context, in *GetCatalogRequest, opts ...grpc.CallOption) (*GetCatalogResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetCatalogResponse) + err := c.cc.Invoke(ctx, EngineService_GetCatalog_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *engineServiceClient) IsReservedKeyword(ctx context.Context, in *IsReservedKeywordRequest, opts ...grpc.CallOption) (*IsReservedKeywordResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(IsReservedKeywordResponse) + err := c.cc.Invoke(ctx, EngineService_IsReservedKeyword_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *engineServiceClient) GetCommentSyntax(ctx context.Context, in *GetCommentSyntaxRequest, opts ...grpc.CallOption) (*GetCommentSyntaxResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetCommentSyntaxResponse) + err := c.cc.Invoke(ctx, EngineService_GetCommentSyntax_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *engineServiceClient) GetDialect(ctx context.Context, in *GetDialectRequest, opts ...grpc.CallOption) (*GetDialectResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetDialectResponse) + err := c.cc.Invoke(ctx, EngineService_GetDialect_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// EngineServiceServer is the server API for EngineService service. +// All implementations must embed UnimplementedEngineServiceServer +// for forward compatibility. +// +// EngineService defines the interface for database engine plugins. +// Engine plugins are responsible for parsing SQL statements and providing +// database-specific functionality. +type EngineServiceServer interface { + // Parse parses SQL statements from the input and returns parsed statements. + Parse(context.Context, *ParseRequest) (*ParseResponse, error) + // GetCatalog returns the initial catalog with built-in types and schemas. + GetCatalog(context.Context, *GetCatalogRequest) (*GetCatalogResponse, error) + // IsReservedKeyword checks if a string is a reserved keyword. + IsReservedKeyword(context.Context, *IsReservedKeywordRequest) (*IsReservedKeywordResponse, error) + // GetCommentSyntax returns the comment syntax supported by this engine. + GetCommentSyntax(context.Context, *GetCommentSyntaxRequest) (*GetCommentSyntaxResponse, error) + // GetDialect returns the SQL dialect information for formatting. + GetDialect(context.Context, *GetDialectRequest) (*GetDialectResponse, error) + mustEmbedUnimplementedEngineServiceServer() +} + +// UnimplementedEngineServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedEngineServiceServer struct{} + +func (UnimplementedEngineServiceServer) Parse(context.Context, *ParseRequest) (*ParseResponse, error) { + return nil, status.Error(codes.Unimplemented, "method Parse not implemented") +} +func (UnimplementedEngineServiceServer) GetCatalog(context.Context, *GetCatalogRequest) (*GetCatalogResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetCatalog not implemented") +} +func (UnimplementedEngineServiceServer) IsReservedKeyword(context.Context, *IsReservedKeywordRequest) (*IsReservedKeywordResponse, error) { + return nil, status.Error(codes.Unimplemented, "method IsReservedKeyword not implemented") +} +func (UnimplementedEngineServiceServer) GetCommentSyntax(context.Context, *GetCommentSyntaxRequest) (*GetCommentSyntaxResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetCommentSyntax not implemented") +} +func (UnimplementedEngineServiceServer) GetDialect(context.Context, *GetDialectRequest) (*GetDialectResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetDialect not implemented") +} +func (UnimplementedEngineServiceServer) mustEmbedUnimplementedEngineServiceServer() {} +func (UnimplementedEngineServiceServer) testEmbeddedByValue() {} + +// UnsafeEngineServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to EngineServiceServer will +// result in compilation errors. +type UnsafeEngineServiceServer interface { + mustEmbedUnimplementedEngineServiceServer() +} + +func RegisterEngineServiceServer(s grpc.ServiceRegistrar, srv EngineServiceServer) { + // If the following call panics, it indicates UnimplementedEngineServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&EngineService_ServiceDesc, srv) +} + +func _EngineService_Parse_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ParseRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(EngineServiceServer).Parse(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: EngineService_Parse_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(EngineServiceServer).Parse(ctx, req.(*ParseRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _EngineService_GetCatalog_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetCatalogRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(EngineServiceServer).GetCatalog(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: EngineService_GetCatalog_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(EngineServiceServer).GetCatalog(ctx, req.(*GetCatalogRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _EngineService_IsReservedKeyword_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(IsReservedKeywordRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(EngineServiceServer).IsReservedKeyword(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: EngineService_IsReservedKeyword_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(EngineServiceServer).IsReservedKeyword(ctx, req.(*IsReservedKeywordRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _EngineService_GetCommentSyntax_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetCommentSyntaxRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(EngineServiceServer).GetCommentSyntax(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: EngineService_GetCommentSyntax_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(EngineServiceServer).GetCommentSyntax(ctx, req.(*GetCommentSyntaxRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _EngineService_GetDialect_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetDialectRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(EngineServiceServer).GetDialect(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: EngineService_GetDialect_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(EngineServiceServer).GetDialect(ctx, req.(*GetDialectRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// EngineService_ServiceDesc is the grpc.ServiceDesc for EngineService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var EngineService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "engine.EngineService", + HandlerType: (*EngineServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Parse", + Handler: _EngineService_Parse_Handler, + }, + { + MethodName: "GetCatalog", + Handler: _EngineService_GetCatalog_Handler, + }, + { + MethodName: "IsReservedKeyword", + Handler: _EngineService_IsReservedKeyword_Handler, + }, + { + MethodName: "GetCommentSyntax", + Handler: _EngineService_GetCommentSyntax_Handler, + }, + { + MethodName: "GetDialect", + Handler: _EngineService_GetDialect_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "engine/engine.proto", +} diff --git a/pkg/engine/sdk.go b/pkg/engine/sdk.go new file mode 100644 index 0000000000..0fef167e1a --- /dev/null +++ b/pkg/engine/sdk.go @@ -0,0 +1,143 @@ +// Package engine provides types and utilities for building sqlc database engine plugins. +// +// Engine plugins allow external database backends to be used with sqlc. +// Plugins communicate with sqlc via Protocol Buffers over stdin/stdout. +// +// # Compatibility +// +// Go plugins that import this package are guaranteed to be compatible with sqlc +// at compile time. If the types change incompatibly, the plugin simply won't +// compile until it's updated to match the new interface. +// +// The Protocol Buffer schema is published at buf.build/sqlc/sqlc and ensures +// binary compatibility between sqlc and plugins. +// +// Example plugin: +// +// package main +// +// import "github.com/sqlc-dev/sqlc/pkg/engine" +// +// func main() { +// engine.Run(engine.Handler{ +// PluginName: "my-plugin", +// PluginVersion: "1.0.0", +// Parse: handleParse, +// GetCatalog: handleGetCatalog, +// IsReservedKeyword: handleIsReservedKeyword, +// GetCommentSyntax: handleGetCommentSyntax, +// GetDialect: handleGetDialect, +// }) +// } +package engine + +import ( + "fmt" + "io" + "os" + + "google.golang.org/protobuf/proto" +) + +// Handler contains the functions that implement the engine plugin interface. +// All types used are Protocol Buffer messages defined in engine.proto. +type Handler struct { + PluginName string + PluginVersion string + + Parse func(*ParseRequest) (*ParseResponse, error) + GetCatalog func(*GetCatalogRequest) (*GetCatalogResponse, error) + IsReservedKeyword func(*IsReservedKeywordRequest) (*IsReservedKeywordResponse, error) + GetCommentSyntax func(*GetCommentSyntaxRequest) (*GetCommentSyntaxResponse, error) + GetDialect func(*GetDialectRequest) (*GetDialectResponse, error) +} + +// Run runs the engine plugin with the given handler. +// It reads a protobuf request from stdin and writes a protobuf response to stdout. +func Run(h Handler) { + if err := run(h, os.Args, os.Stdin, os.Stdout, os.Stderr); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} + +func run(h Handler, args []string, stdin io.Reader, stdout, stderr io.Writer) error { + if len(args) < 2 { + return fmt.Errorf("usage: %s ", args[0]) + } + + method := args[1] + input, err := io.ReadAll(stdin) + if err != nil { + return fmt.Errorf("reading stdin: %w", err) + } + + var output proto.Message + + switch method { + case "parse": + var req ParseRequest + if err := proto.Unmarshal(input, &req); err != nil { + return fmt.Errorf("parsing request: %w", err) + } + if h.Parse == nil { + return fmt.Errorf("parse not implemented") + } + output, err = h.Parse(&req) + + case "get_catalog": + var req GetCatalogRequest + if len(input) > 0 { + proto.Unmarshal(input, &req) + } + if h.GetCatalog == nil { + return fmt.Errorf("get_catalog not implemented") + } + output, err = h.GetCatalog(&req) + + case "is_reserved_keyword": + var req IsReservedKeywordRequest + if err := proto.Unmarshal(input, &req); err != nil { + return fmt.Errorf("parsing request: %w", err) + } + if h.IsReservedKeyword == nil { + return fmt.Errorf("is_reserved_keyword not implemented") + } + output, err = h.IsReservedKeyword(&req) + + case "get_comment_syntax": + var req GetCommentSyntaxRequest + if len(input) > 0 { + proto.Unmarshal(input, &req) + } + if h.GetCommentSyntax == nil { + return fmt.Errorf("get_comment_syntax not implemented") + } + output, err = h.GetCommentSyntax(&req) + + case "get_dialect": + var req GetDialectRequest + if len(input) > 0 { + proto.Unmarshal(input, &req) + } + if h.GetDialect == nil { + return fmt.Errorf("get_dialect not implemented") + } + output, err = h.GetDialect(&req) + + default: + return fmt.Errorf("unknown method: %s", method) + } + + if err != nil { + return err + } + + data, err := proto.Marshal(output) + if err != nil { + return fmt.Errorf("marshaling response: %w", err) + } + + _, err = stdout.Write(data) + return err +} diff --git a/pkg/plugin/codegen.pb.go b/pkg/plugin/codegen.pb.go new file mode 100644 index 0000000000..b742138f53 --- /dev/null +++ b/pkg/plugin/codegen.pb.go @@ -0,0 +1,1338 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc v6.32.1 +// source: plugin/codegen.proto + +package plugin + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type File struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Contents []byte `protobuf:"bytes,2,opt,name=contents,proto3" json:"contents,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *File) Reset() { + *x = File{} + mi := &file_plugin_codegen_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *File) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*File) ProtoMessage() {} + +func (x *File) ProtoReflect() protoreflect.Message { + mi := &file_plugin_codegen_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use File.ProtoReflect.Descriptor instead. +func (*File) Descriptor() ([]byte, []int) { + return file_plugin_codegen_proto_rawDescGZIP(), []int{0} +} + +func (x *File) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *File) GetContents() []byte { + if x != nil { + return x.Contents + } + return nil +} + +type Settings struct { + state protoimpl.MessageState `protogen:"open.v1"` + Version string `protobuf:"bytes,1,opt,name=version,proto3" json:"version,omitempty"` + Engine string `protobuf:"bytes,2,opt,name=engine,proto3" json:"engine,omitempty"` + Schema []string `protobuf:"bytes,3,rep,name=schema,proto3" json:"schema,omitempty"` + Queries []string `protobuf:"bytes,4,rep,name=queries,proto3" json:"queries,omitempty"` + Codegen *Codegen `protobuf:"bytes,12,opt,name=codegen,proto3" json:"codegen,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Settings) Reset() { + *x = Settings{} + mi := &file_plugin_codegen_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Settings) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Settings) ProtoMessage() {} + +func (x *Settings) ProtoReflect() protoreflect.Message { + mi := &file_plugin_codegen_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Settings.ProtoReflect.Descriptor instead. +func (*Settings) Descriptor() ([]byte, []int) { + return file_plugin_codegen_proto_rawDescGZIP(), []int{1} +} + +func (x *Settings) GetVersion() string { + if x != nil { + return x.Version + } + return "" +} + +func (x *Settings) GetEngine() string { + if x != nil { + return x.Engine + } + return "" +} + +func (x *Settings) GetSchema() []string { + if x != nil { + return x.Schema + } + return nil +} + +func (x *Settings) GetQueries() []string { + if x != nil { + return x.Queries + } + return nil +} + +func (x *Settings) GetCodegen() *Codegen { + if x != nil { + return x.Codegen + } + return nil +} + +type Codegen struct { + state protoimpl.MessageState `protogen:"open.v1"` + Out string `protobuf:"bytes,1,opt,name=out,proto3" json:"out,omitempty"` + Plugin string `protobuf:"bytes,2,opt,name=plugin,proto3" json:"plugin,omitempty"` + Options []byte `protobuf:"bytes,3,opt,name=options,proto3" json:"options,omitempty"` + Env []string `protobuf:"bytes,4,rep,name=env,proto3" json:"env,omitempty"` + Process *Codegen_Process `protobuf:"bytes,5,opt,name=process,proto3" json:"process,omitempty"` + Wasm *Codegen_WASM `protobuf:"bytes,6,opt,name=wasm,proto3" json:"wasm,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Codegen) Reset() { + *x = Codegen{} + mi := &file_plugin_codegen_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Codegen) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Codegen) ProtoMessage() {} + +func (x *Codegen) ProtoReflect() protoreflect.Message { + mi := &file_plugin_codegen_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Codegen.ProtoReflect.Descriptor instead. +func (*Codegen) Descriptor() ([]byte, []int) { + return file_plugin_codegen_proto_rawDescGZIP(), []int{2} +} + +func (x *Codegen) GetOut() string { + if x != nil { + return x.Out + } + return "" +} + +func (x *Codegen) GetPlugin() string { + if x != nil { + return x.Plugin + } + return "" +} + +func (x *Codegen) GetOptions() []byte { + if x != nil { + return x.Options + } + return nil +} + +func (x *Codegen) GetEnv() []string { + if x != nil { + return x.Env + } + return nil +} + +func (x *Codegen) GetProcess() *Codegen_Process { + if x != nil { + return x.Process + } + return nil +} + +func (x *Codegen) GetWasm() *Codegen_WASM { + if x != nil { + return x.Wasm + } + return nil +} + +type Catalog struct { + state protoimpl.MessageState `protogen:"open.v1"` + Comment string `protobuf:"bytes,1,opt,name=comment,proto3" json:"comment,omitempty"` + DefaultSchema string `protobuf:"bytes,2,opt,name=default_schema,json=defaultSchema,proto3" json:"default_schema,omitempty"` + Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` + Schemas []*Schema `protobuf:"bytes,4,rep,name=schemas,proto3" json:"schemas,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Catalog) Reset() { + *x = Catalog{} + mi := &file_plugin_codegen_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Catalog) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Catalog) ProtoMessage() {} + +func (x *Catalog) ProtoReflect() protoreflect.Message { + mi := &file_plugin_codegen_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Catalog.ProtoReflect.Descriptor instead. +func (*Catalog) Descriptor() ([]byte, []int) { + return file_plugin_codegen_proto_rawDescGZIP(), []int{3} +} + +func (x *Catalog) GetComment() string { + if x != nil { + return x.Comment + } + return "" +} + +func (x *Catalog) GetDefaultSchema() string { + if x != nil { + return x.DefaultSchema + } + return "" +} + +func (x *Catalog) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Catalog) GetSchemas() []*Schema { + if x != nil { + return x.Schemas + } + return nil +} + +type Schema struct { + state protoimpl.MessageState `protogen:"open.v1"` + Comment string `protobuf:"bytes,1,opt,name=comment,proto3" json:"comment,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Tables []*Table `protobuf:"bytes,3,rep,name=tables,proto3" json:"tables,omitempty"` + Enums []*Enum `protobuf:"bytes,4,rep,name=enums,proto3" json:"enums,omitempty"` + CompositeTypes []*CompositeType `protobuf:"bytes,5,rep,name=composite_types,json=compositeTypes,proto3" json:"composite_types,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Schema) Reset() { + *x = Schema{} + mi := &file_plugin_codegen_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Schema) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Schema) ProtoMessage() {} + +func (x *Schema) ProtoReflect() protoreflect.Message { + mi := &file_plugin_codegen_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Schema.ProtoReflect.Descriptor instead. +func (*Schema) Descriptor() ([]byte, []int) { + return file_plugin_codegen_proto_rawDescGZIP(), []int{4} +} + +func (x *Schema) GetComment() string { + if x != nil { + return x.Comment + } + return "" +} + +func (x *Schema) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Schema) GetTables() []*Table { + if x != nil { + return x.Tables + } + return nil +} + +func (x *Schema) GetEnums() []*Enum { + if x != nil { + return x.Enums + } + return nil +} + +func (x *Schema) GetCompositeTypes() []*CompositeType { + if x != nil { + return x.CompositeTypes + } + return nil +} + +type CompositeType struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Comment string `protobuf:"bytes,2,opt,name=comment,proto3" json:"comment,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CompositeType) Reset() { + *x = CompositeType{} + mi := &file_plugin_codegen_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CompositeType) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CompositeType) ProtoMessage() {} + +func (x *CompositeType) ProtoReflect() protoreflect.Message { + mi := &file_plugin_codegen_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CompositeType.ProtoReflect.Descriptor instead. +func (*CompositeType) Descriptor() ([]byte, []int) { + return file_plugin_codegen_proto_rawDescGZIP(), []int{5} +} + +func (x *CompositeType) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *CompositeType) GetComment() string { + if x != nil { + return x.Comment + } + return "" +} + +type Enum struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Vals []string `protobuf:"bytes,2,rep,name=vals,proto3" json:"vals,omitempty"` + Comment string `protobuf:"bytes,3,opt,name=comment,proto3" json:"comment,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Enum) Reset() { + *x = Enum{} + mi := &file_plugin_codegen_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Enum) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Enum) ProtoMessage() {} + +func (x *Enum) ProtoReflect() protoreflect.Message { + mi := &file_plugin_codegen_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Enum.ProtoReflect.Descriptor instead. +func (*Enum) Descriptor() ([]byte, []int) { + return file_plugin_codegen_proto_rawDescGZIP(), []int{6} +} + +func (x *Enum) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Enum) GetVals() []string { + if x != nil { + return x.Vals + } + return nil +} + +func (x *Enum) GetComment() string { + if x != nil { + return x.Comment + } + return "" +} + +type Table struct { + state protoimpl.MessageState `protogen:"open.v1"` + Rel *Identifier `protobuf:"bytes,1,opt,name=rel,proto3" json:"rel,omitempty"` + Columns []*Column `protobuf:"bytes,2,rep,name=columns,proto3" json:"columns,omitempty"` + Comment string `protobuf:"bytes,3,opt,name=comment,proto3" json:"comment,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Table) Reset() { + *x = Table{} + mi := &file_plugin_codegen_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Table) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Table) ProtoMessage() {} + +func (x *Table) ProtoReflect() protoreflect.Message { + mi := &file_plugin_codegen_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Table.ProtoReflect.Descriptor instead. +func (*Table) Descriptor() ([]byte, []int) { + return file_plugin_codegen_proto_rawDescGZIP(), []int{7} +} + +func (x *Table) GetRel() *Identifier { + if x != nil { + return x.Rel + } + return nil +} + +func (x *Table) GetColumns() []*Column { + if x != nil { + return x.Columns + } + return nil +} + +func (x *Table) GetComment() string { + if x != nil { + return x.Comment + } + return "" +} + +type Identifier struct { + state protoimpl.MessageState `protogen:"open.v1"` + Catalog string `protobuf:"bytes,1,opt,name=catalog,proto3" json:"catalog,omitempty"` + Schema string `protobuf:"bytes,2,opt,name=schema,proto3" json:"schema,omitempty"` + Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Identifier) Reset() { + *x = Identifier{} + mi := &file_plugin_codegen_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Identifier) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Identifier) ProtoMessage() {} + +func (x *Identifier) ProtoReflect() protoreflect.Message { + mi := &file_plugin_codegen_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Identifier.ProtoReflect.Descriptor instead. +func (*Identifier) Descriptor() ([]byte, []int) { + return file_plugin_codegen_proto_rawDescGZIP(), []int{8} +} + +func (x *Identifier) GetCatalog() string { + if x != nil { + return x.Catalog + } + return "" +} + +func (x *Identifier) GetSchema() string { + if x != nil { + return x.Schema + } + return "" +} + +func (x *Identifier) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type Column struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + NotNull bool `protobuf:"varint,3,opt,name=not_null,json=notNull,proto3" json:"not_null,omitempty"` + IsArray bool `protobuf:"varint,4,opt,name=is_array,json=isArray,proto3" json:"is_array,omitempty"` + Comment string `protobuf:"bytes,5,opt,name=comment,proto3" json:"comment,omitempty"` + Length int32 `protobuf:"varint,6,opt,name=length,proto3" json:"length,omitempty"` + IsNamedParam bool `protobuf:"varint,7,opt,name=is_named_param,json=isNamedParam,proto3" json:"is_named_param,omitempty"` + IsFuncCall bool `protobuf:"varint,8,opt,name=is_func_call,json=isFuncCall,proto3" json:"is_func_call,omitempty"` + // XXX: Figure out what PostgreSQL calls `foo.id` + Scope string `protobuf:"bytes,9,opt,name=scope,proto3" json:"scope,omitempty"` + Table *Identifier `protobuf:"bytes,10,opt,name=table,proto3" json:"table,omitempty"` + TableAlias string `protobuf:"bytes,11,opt,name=table_alias,json=tableAlias,proto3" json:"table_alias,omitempty"` + Type *Identifier `protobuf:"bytes,12,opt,name=type,proto3" json:"type,omitempty"` + IsSqlcSlice bool `protobuf:"varint,13,opt,name=is_sqlc_slice,json=isSqlcSlice,proto3" json:"is_sqlc_slice,omitempty"` + EmbedTable *Identifier `protobuf:"bytes,14,opt,name=embed_table,json=embedTable,proto3" json:"embed_table,omitempty"` + OriginalName string `protobuf:"bytes,15,opt,name=original_name,json=originalName,proto3" json:"original_name,omitempty"` + Unsigned bool `protobuf:"varint,16,opt,name=unsigned,proto3" json:"unsigned,omitempty"` + ArrayDims int32 `protobuf:"varint,17,opt,name=array_dims,json=arrayDims,proto3" json:"array_dims,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Column) Reset() { + *x = Column{} + mi := &file_plugin_codegen_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Column) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Column) ProtoMessage() {} + +func (x *Column) ProtoReflect() protoreflect.Message { + mi := &file_plugin_codegen_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Column.ProtoReflect.Descriptor instead. +func (*Column) Descriptor() ([]byte, []int) { + return file_plugin_codegen_proto_rawDescGZIP(), []int{9} +} + +func (x *Column) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Column) GetNotNull() bool { + if x != nil { + return x.NotNull + } + return false +} + +func (x *Column) GetIsArray() bool { + if x != nil { + return x.IsArray + } + return false +} + +func (x *Column) GetComment() string { + if x != nil { + return x.Comment + } + return "" +} + +func (x *Column) GetLength() int32 { + if x != nil { + return x.Length + } + return 0 +} + +func (x *Column) GetIsNamedParam() bool { + if x != nil { + return x.IsNamedParam + } + return false +} + +func (x *Column) GetIsFuncCall() bool { + if x != nil { + return x.IsFuncCall + } + return false +} + +func (x *Column) GetScope() string { + if x != nil { + return x.Scope + } + return "" +} + +func (x *Column) GetTable() *Identifier { + if x != nil { + return x.Table + } + return nil +} + +func (x *Column) GetTableAlias() string { + if x != nil { + return x.TableAlias + } + return "" +} + +func (x *Column) GetType() *Identifier { + if x != nil { + return x.Type + } + return nil +} + +func (x *Column) GetIsSqlcSlice() bool { + if x != nil { + return x.IsSqlcSlice + } + return false +} + +func (x *Column) GetEmbedTable() *Identifier { + if x != nil { + return x.EmbedTable + } + return nil +} + +func (x *Column) GetOriginalName() string { + if x != nil { + return x.OriginalName + } + return "" +} + +func (x *Column) GetUnsigned() bool { + if x != nil { + return x.Unsigned + } + return false +} + +func (x *Column) GetArrayDims() int32 { + if x != nil { + return x.ArrayDims + } + return 0 +} + +type Query struct { + state protoimpl.MessageState `protogen:"open.v1"` + Text string `protobuf:"bytes,1,opt,name=text,proto3" json:"text,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Cmd string `protobuf:"bytes,3,opt,name=cmd,proto3" json:"cmd,omitempty"` + Columns []*Column `protobuf:"bytes,4,rep,name=columns,proto3" json:"columns,omitempty"` + Params []*Parameter `protobuf:"bytes,5,rep,name=params,json=parameters,proto3" json:"params,omitempty"` + Comments []string `protobuf:"bytes,6,rep,name=comments,proto3" json:"comments,omitempty"` + Filename string `protobuf:"bytes,7,opt,name=filename,proto3" json:"filename,omitempty"` + InsertIntoTable *Identifier `protobuf:"bytes,8,opt,name=insert_into_table,proto3" json:"insert_into_table,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Query) Reset() { + *x = Query{} + mi := &file_plugin_codegen_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Query) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Query) ProtoMessage() {} + +func (x *Query) ProtoReflect() protoreflect.Message { + mi := &file_plugin_codegen_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Query.ProtoReflect.Descriptor instead. +func (*Query) Descriptor() ([]byte, []int) { + return file_plugin_codegen_proto_rawDescGZIP(), []int{10} +} + +func (x *Query) GetText() string { + if x != nil { + return x.Text + } + return "" +} + +func (x *Query) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Query) GetCmd() string { + if x != nil { + return x.Cmd + } + return "" +} + +func (x *Query) GetColumns() []*Column { + if x != nil { + return x.Columns + } + return nil +} + +func (x *Query) GetParams() []*Parameter { + if x != nil { + return x.Params + } + return nil +} + +func (x *Query) GetComments() []string { + if x != nil { + return x.Comments + } + return nil +} + +func (x *Query) GetFilename() string { + if x != nil { + return x.Filename + } + return "" +} + +func (x *Query) GetInsertIntoTable() *Identifier { + if x != nil { + return x.InsertIntoTable + } + return nil +} + +type Parameter struct { + state protoimpl.MessageState `protogen:"open.v1"` + Number int32 `protobuf:"varint,1,opt,name=number,proto3" json:"number,omitempty"` + Column *Column `protobuf:"bytes,2,opt,name=column,proto3" json:"column,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Parameter) Reset() { + *x = Parameter{} + mi := &file_plugin_codegen_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Parameter) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Parameter) ProtoMessage() {} + +func (x *Parameter) ProtoReflect() protoreflect.Message { + mi := &file_plugin_codegen_proto_msgTypes[11] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Parameter.ProtoReflect.Descriptor instead. +func (*Parameter) Descriptor() ([]byte, []int) { + return file_plugin_codegen_proto_rawDescGZIP(), []int{11} +} + +func (x *Parameter) GetNumber() int32 { + if x != nil { + return x.Number + } + return 0 +} + +func (x *Parameter) GetColumn() *Column { + if x != nil { + return x.Column + } + return nil +} + +type GenerateRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Settings *Settings `protobuf:"bytes,1,opt,name=settings,proto3" json:"settings,omitempty"` + Catalog *Catalog `protobuf:"bytes,2,opt,name=catalog,proto3" json:"catalog,omitempty"` + Queries []*Query `protobuf:"bytes,3,rep,name=queries,proto3" json:"queries,omitempty"` + SqlcVersion string `protobuf:"bytes,4,opt,name=sqlc_version,proto3" json:"sqlc_version,omitempty"` + PluginOptions []byte `protobuf:"bytes,5,opt,name=plugin_options,proto3" json:"plugin_options,omitempty"` + GlobalOptions []byte `protobuf:"bytes,6,opt,name=global_options,proto3" json:"global_options,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GenerateRequest) Reset() { + *x = GenerateRequest{} + mi := &file_plugin_codegen_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GenerateRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GenerateRequest) ProtoMessage() {} + +func (x *GenerateRequest) ProtoReflect() protoreflect.Message { + mi := &file_plugin_codegen_proto_msgTypes[12] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GenerateRequest.ProtoReflect.Descriptor instead. +func (*GenerateRequest) Descriptor() ([]byte, []int) { + return file_plugin_codegen_proto_rawDescGZIP(), []int{12} +} + +func (x *GenerateRequest) GetSettings() *Settings { + if x != nil { + return x.Settings + } + return nil +} + +func (x *GenerateRequest) GetCatalog() *Catalog { + if x != nil { + return x.Catalog + } + return nil +} + +func (x *GenerateRequest) GetQueries() []*Query { + if x != nil { + return x.Queries + } + return nil +} + +func (x *GenerateRequest) GetSqlcVersion() string { + if x != nil { + return x.SqlcVersion + } + return "" +} + +func (x *GenerateRequest) GetPluginOptions() []byte { + if x != nil { + return x.PluginOptions + } + return nil +} + +func (x *GenerateRequest) GetGlobalOptions() []byte { + if x != nil { + return x.GlobalOptions + } + return nil +} + +type GenerateResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Files []*File `protobuf:"bytes,1,rep,name=files,proto3" json:"files,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GenerateResponse) Reset() { + *x = GenerateResponse{} + mi := &file_plugin_codegen_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GenerateResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GenerateResponse) ProtoMessage() {} + +func (x *GenerateResponse) ProtoReflect() protoreflect.Message { + mi := &file_plugin_codegen_proto_msgTypes[13] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GenerateResponse.ProtoReflect.Descriptor instead. +func (*GenerateResponse) Descriptor() ([]byte, []int) { + return file_plugin_codegen_proto_rawDescGZIP(), []int{13} +} + +func (x *GenerateResponse) GetFiles() []*File { + if x != nil { + return x.Files + } + return nil +} + +type Codegen_Process struct { + state protoimpl.MessageState `protogen:"open.v1"` + Cmd string `protobuf:"bytes,1,opt,name=cmd,proto3" json:"cmd,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Codegen_Process) Reset() { + *x = Codegen_Process{} + mi := &file_plugin_codegen_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Codegen_Process) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Codegen_Process) ProtoMessage() {} + +func (x *Codegen_Process) ProtoReflect() protoreflect.Message { + mi := &file_plugin_codegen_proto_msgTypes[14] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Codegen_Process.ProtoReflect.Descriptor instead. +func (*Codegen_Process) Descriptor() ([]byte, []int) { + return file_plugin_codegen_proto_rawDescGZIP(), []int{2, 0} +} + +func (x *Codegen_Process) GetCmd() string { + if x != nil { + return x.Cmd + } + return "" +} + +type Codegen_WASM struct { + state protoimpl.MessageState `protogen:"open.v1"` + Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"` + Sha256 string `protobuf:"bytes,2,opt,name=sha256,proto3" json:"sha256,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Codegen_WASM) Reset() { + *x = Codegen_WASM{} + mi := &file_plugin_codegen_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Codegen_WASM) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Codegen_WASM) ProtoMessage() {} + +func (x *Codegen_WASM) ProtoReflect() protoreflect.Message { + mi := &file_plugin_codegen_proto_msgTypes[15] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Codegen_WASM.ProtoReflect.Descriptor instead. +func (*Codegen_WASM) Descriptor() ([]byte, []int) { + return file_plugin_codegen_proto_rawDescGZIP(), []int{2, 1} +} + +func (x *Codegen_WASM) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +func (x *Codegen_WASM) GetSha256() string { + if x != nil { + return x.Sha256 + } + return "" +} + +var File_plugin_codegen_proto protoreflect.FileDescriptor + +const file_plugin_codegen_proto_rawDesc = "" + + "\n" + + "\x14plugin/codegen.proto\x12\x06plugin\"6\n" + + "\x04File\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x1a\n" + + "\bcontents\x18\x02 \x01(\fR\bcontents\"\xb7\x01\n" + + "\bSettings\x12\x18\n" + + "\aversion\x18\x01 \x01(\tR\aversion\x12\x16\n" + + "\x06engine\x18\x02 \x01(\tR\x06engine\x12\x16\n" + + "\x06schema\x18\x03 \x03(\tR\x06schema\x12\x18\n" + + "\aqueries\x18\x04 \x03(\tR\aqueries\x12)\n" + + "\acodegen\x18\f \x01(\v2\x0f.plugin.CodegenR\acodegenJ\x04\b\x05\x10\x06J\x04\b\b\x10\tJ\x04\b\t\x10\n" + + "J\x04\b\n" + + "\x10\vJ\x04\b\v\x10\f\"\x8b\x02\n" + + "\aCodegen\x12\x10\n" + + "\x03out\x18\x01 \x01(\tR\x03out\x12\x16\n" + + "\x06plugin\x18\x02 \x01(\tR\x06plugin\x12\x18\n" + + "\aoptions\x18\x03 \x01(\fR\aoptions\x12\x10\n" + + "\x03env\x18\x04 \x03(\tR\x03env\x121\n" + + "\aprocess\x18\x05 \x01(\v2\x17.plugin.Codegen.ProcessR\aprocess\x12(\n" + + "\x04wasm\x18\x06 \x01(\v2\x14.plugin.Codegen.WASMR\x04wasm\x1a\x1b\n" + + "\aProcess\x12\x10\n" + + "\x03cmd\x18\x01 \x01(\tR\x03cmd\x1a0\n" + + "\x04WASM\x12\x10\n" + + "\x03url\x18\x01 \x01(\tR\x03url\x12\x16\n" + + "\x06sha256\x18\x02 \x01(\tR\x06sha256\"\x88\x01\n" + + "\aCatalog\x12\x18\n" + + "\acomment\x18\x01 \x01(\tR\acomment\x12%\n" + + "\x0edefault_schema\x18\x02 \x01(\tR\rdefaultSchema\x12\x12\n" + + "\x04name\x18\x03 \x01(\tR\x04name\x12(\n" + + "\aschemas\x18\x04 \x03(\v2\x0e.plugin.SchemaR\aschemas\"\xc1\x01\n" + + "\x06Schema\x12\x18\n" + + "\acomment\x18\x01 \x01(\tR\acomment\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\x12%\n" + + "\x06tables\x18\x03 \x03(\v2\r.plugin.TableR\x06tables\x12\"\n" + + "\x05enums\x18\x04 \x03(\v2\f.plugin.EnumR\x05enums\x12>\n" + + "\x0fcomposite_types\x18\x05 \x03(\v2\x15.plugin.CompositeTypeR\x0ecompositeTypes\"=\n" + + "\rCompositeType\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x18\n" + + "\acomment\x18\x02 \x01(\tR\acomment\"H\n" + + "\x04Enum\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x12\n" + + "\x04vals\x18\x02 \x03(\tR\x04vals\x12\x18\n" + + "\acomment\x18\x03 \x01(\tR\acomment\"q\n" + + "\x05Table\x12$\n" + + "\x03rel\x18\x01 \x01(\v2\x12.plugin.IdentifierR\x03rel\x12(\n" + + "\acolumns\x18\x02 \x03(\v2\x0e.plugin.ColumnR\acolumns\x12\x18\n" + + "\acomment\x18\x03 \x01(\tR\acomment\"R\n" + + "\n" + + "Identifier\x12\x18\n" + + "\acatalog\x18\x01 \x01(\tR\acatalog\x12\x16\n" + + "\x06schema\x18\x02 \x01(\tR\x06schema\x12\x12\n" + + "\x04name\x18\x03 \x01(\tR\x04name\"\x8e\x04\n" + + "\x06Column\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x19\n" + + "\bnot_null\x18\x03 \x01(\bR\anotNull\x12\x19\n" + + "\bis_array\x18\x04 \x01(\bR\aisArray\x12\x18\n" + + "\acomment\x18\x05 \x01(\tR\acomment\x12\x16\n" + + "\x06length\x18\x06 \x01(\x05R\x06length\x12$\n" + + "\x0eis_named_param\x18\a \x01(\bR\fisNamedParam\x12 \n" + + "\fis_func_call\x18\b \x01(\bR\n" + + "isFuncCall\x12\x14\n" + + "\x05scope\x18\t \x01(\tR\x05scope\x12(\n" + + "\x05table\x18\n" + + " \x01(\v2\x12.plugin.IdentifierR\x05table\x12\x1f\n" + + "\vtable_alias\x18\v \x01(\tR\n" + + "tableAlias\x12&\n" + + "\x04type\x18\f \x01(\v2\x12.plugin.IdentifierR\x04type\x12\"\n" + + "\ris_sqlc_slice\x18\r \x01(\bR\visSqlcSlice\x123\n" + + "\vembed_table\x18\x0e \x01(\v2\x12.plugin.IdentifierR\n" + + "embedTable\x12#\n" + + "\roriginal_name\x18\x0f \x01(\tR\foriginalName\x12\x1a\n" + + "\bunsigned\x18\x10 \x01(\bR\bunsigned\x12\x1d\n" + + "\n" + + "array_dims\x18\x11 \x01(\x05R\tarrayDims\"\x94\x02\n" + + "\x05Query\x12\x12\n" + + "\x04text\x18\x01 \x01(\tR\x04text\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\x12\x10\n" + + "\x03cmd\x18\x03 \x01(\tR\x03cmd\x12(\n" + + "\acolumns\x18\x04 \x03(\v2\x0e.plugin.ColumnR\acolumns\x12-\n" + + "\x06params\x18\x05 \x03(\v2\x11.plugin.ParameterR\n" + + "parameters\x12\x1a\n" + + "\bcomments\x18\x06 \x03(\tR\bcomments\x12\x1a\n" + + "\bfilename\x18\a \x01(\tR\bfilename\x12@\n" + + "\x11insert_into_table\x18\b \x01(\v2\x12.plugin.IdentifierR\x11insert_into_table\"K\n" + + "\tParameter\x12\x16\n" + + "\x06number\x18\x01 \x01(\x05R\x06number\x12&\n" + + "\x06column\x18\x02 \x01(\v2\x0e.plugin.ColumnR\x06column\"\x87\x02\n" + + "\x0fGenerateRequest\x12,\n" + + "\bsettings\x18\x01 \x01(\v2\x10.plugin.SettingsR\bsettings\x12)\n" + + "\acatalog\x18\x02 \x01(\v2\x0f.plugin.CatalogR\acatalog\x12'\n" + + "\aqueries\x18\x03 \x03(\v2\r.plugin.QueryR\aqueries\x12\"\n" + + "\fsqlc_version\x18\x04 \x01(\tR\fsqlc_version\x12&\n" + + "\x0eplugin_options\x18\x05 \x01(\fR\x0eplugin_options\x12&\n" + + "\x0eglobal_options\x18\x06 \x01(\fR\x0eglobal_options\"6\n" + + "\x10GenerateResponse\x12\"\n" + + "\x05files\x18\x01 \x03(\v2\f.plugin.FileR\x05files2O\n" + + "\x0eCodegenService\x12=\n" + + "\bGenerate\x12\x17.plugin.GenerateRequest\x1a\x18.plugin.GenerateResponseB%Z#github.com/sqlc-dev/sqlc/pkg/pluginb\x06proto3" + +var ( + file_plugin_codegen_proto_rawDescOnce sync.Once + file_plugin_codegen_proto_rawDescData []byte +) + +func file_plugin_codegen_proto_rawDescGZIP() []byte { + file_plugin_codegen_proto_rawDescOnce.Do(func() { + file_plugin_codegen_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_plugin_codegen_proto_rawDesc), len(file_plugin_codegen_proto_rawDesc))) + }) + return file_plugin_codegen_proto_rawDescData +} + +var file_plugin_codegen_proto_msgTypes = make([]protoimpl.MessageInfo, 16) +var file_plugin_codegen_proto_goTypes = []any{ + (*File)(nil), // 0: plugin.File + (*Settings)(nil), // 1: plugin.Settings + (*Codegen)(nil), // 2: plugin.Codegen + (*Catalog)(nil), // 3: plugin.Catalog + (*Schema)(nil), // 4: plugin.Schema + (*CompositeType)(nil), // 5: plugin.CompositeType + (*Enum)(nil), // 6: plugin.Enum + (*Table)(nil), // 7: plugin.Table + (*Identifier)(nil), // 8: plugin.Identifier + (*Column)(nil), // 9: plugin.Column + (*Query)(nil), // 10: plugin.Query + (*Parameter)(nil), // 11: plugin.Parameter + (*GenerateRequest)(nil), // 12: plugin.GenerateRequest + (*GenerateResponse)(nil), // 13: plugin.GenerateResponse + (*Codegen_Process)(nil), // 14: plugin.Codegen.Process + (*Codegen_WASM)(nil), // 15: plugin.Codegen.WASM +} +var file_plugin_codegen_proto_depIdxs = []int32{ + 2, // 0: plugin.Settings.codegen:type_name -> plugin.Codegen + 14, // 1: plugin.Codegen.process:type_name -> plugin.Codegen.Process + 15, // 2: plugin.Codegen.wasm:type_name -> plugin.Codegen.WASM + 4, // 3: plugin.Catalog.schemas:type_name -> plugin.Schema + 7, // 4: plugin.Schema.tables:type_name -> plugin.Table + 6, // 5: plugin.Schema.enums:type_name -> plugin.Enum + 5, // 6: plugin.Schema.composite_types:type_name -> plugin.CompositeType + 8, // 7: plugin.Table.rel:type_name -> plugin.Identifier + 9, // 8: plugin.Table.columns:type_name -> plugin.Column + 8, // 9: plugin.Column.table:type_name -> plugin.Identifier + 8, // 10: plugin.Column.type:type_name -> plugin.Identifier + 8, // 11: plugin.Column.embed_table:type_name -> plugin.Identifier + 9, // 12: plugin.Query.columns:type_name -> plugin.Column + 11, // 13: plugin.Query.params:type_name -> plugin.Parameter + 8, // 14: plugin.Query.insert_into_table:type_name -> plugin.Identifier + 9, // 15: plugin.Parameter.column:type_name -> plugin.Column + 1, // 16: plugin.GenerateRequest.settings:type_name -> plugin.Settings + 3, // 17: plugin.GenerateRequest.catalog:type_name -> plugin.Catalog + 10, // 18: plugin.GenerateRequest.queries:type_name -> plugin.Query + 0, // 19: plugin.GenerateResponse.files:type_name -> plugin.File + 12, // 20: plugin.CodegenService.Generate:input_type -> plugin.GenerateRequest + 13, // 21: plugin.CodegenService.Generate:output_type -> plugin.GenerateResponse + 21, // [21:22] is the sub-list for method output_type + 20, // [20:21] is the sub-list for method input_type + 20, // [20:20] is the sub-list for extension type_name + 20, // [20:20] is the sub-list for extension extendee + 0, // [0:20] is the sub-list for field type_name +} + +func init() { file_plugin_codegen_proto_init() } +func file_plugin_codegen_proto_init() { + if File_plugin_codegen_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_plugin_codegen_proto_rawDesc), len(file_plugin_codegen_proto_rawDesc)), + NumEnums: 0, + NumMessages: 16, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_plugin_codegen_proto_goTypes, + DependencyIndexes: file_plugin_codegen_proto_depIdxs, + MessageInfos: file_plugin_codegen_proto_msgTypes, + }.Build() + File_plugin_codegen_proto = out.File + file_plugin_codegen_proto_goTypes = nil + file_plugin_codegen_proto_depIdxs = nil +} diff --git a/pkg/plugin/sdk.go b/pkg/plugin/sdk.go new file mode 100644 index 0000000000..33da0c2cfc --- /dev/null +++ b/pkg/plugin/sdk.go @@ -0,0 +1,77 @@ +// Package plugin provides types and utilities for building sqlc codegen plugins. +// +// Codegen plugins allow generating code in custom languages from sqlc. +// Plugins communicate with sqlc via Protocol Buffers over stdin/stdout. +// +// # Compatibility +// +// Go plugins that import this package are guaranteed to be compatible with sqlc +// at compile time. If the types change incompatibly, the plugin simply won't +// compile until it's updated to match the new interface. +// +// Example plugin: +// +// package main +// +// import "github.com/sqlc-dev/sqlc/pkg/plugin" +// +// func main() { +// plugin.Run(func(req *plugin.GenerateRequest) (*plugin.GenerateResponse, error) { +// // Generate code from req.Queries and req.Catalog +// return &plugin.GenerateResponse{ +// Files: []*plugin.File{ +// {Name: "queries.txt", Contents: []byte("...")}, +// }, +// }, nil +// }) +// } +package plugin + +import ( + "bufio" + "fmt" + "io" + "os" + + "google.golang.org/protobuf/proto" +) + +// GenerateFunc is the function signature for code generation. +type GenerateFunc func(*GenerateRequest) (*GenerateResponse, error) + +// Run runs the codegen plugin with the given generate function. +// It reads a protobuf GenerateRequest from stdin and writes a GenerateResponse to stdout. +func Run(fn GenerateFunc) { + if err := run(fn, os.Stdin, os.Stdout, os.Stderr); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(2) + } +} + +func run(fn GenerateFunc, stdin io.Reader, stdout, stderr io.Writer) error { + reqBlob, err := io.ReadAll(stdin) + if err != nil { + return fmt.Errorf("reading stdin: %w", err) + } + + var req GenerateRequest + if err := proto.Unmarshal(reqBlob, &req); err != nil { + return fmt.Errorf("unmarshaling request: %w", err) + } + + resp, err := fn(&req) + if err != nil { + return fmt.Errorf("generating: %w", err) + } + + respBlob, err := proto.Marshal(resp) + if err != nil { + return fmt.Errorf("marshaling response: %w", err) + } + + w := bufio.NewWriter(stdout) + if _, err := w.Write(respBlob); err != nil { + return fmt.Errorf("writing response: %w", err) + } + return w.Flush() +} diff --git a/protos/engine/engine.proto b/protos/engine/engine.proto new file mode 100644 index 0000000000..553fbae7e6 --- /dev/null +++ b/protos/engine/engine.proto @@ -0,0 +1,176 @@ +syntax = "proto3"; + +package engine; + +// Go code is generated to pkg/engine for external plugin developers +option go_package = "github.com/sqlc-dev/sqlc/pkg/engine"; + +// EngineService defines the interface for database engine plugins. +// Engine plugins are responsible for parsing SQL statements and providing +// database-specific functionality. +service EngineService { + // Parse parses SQL statements from the input and returns parsed statements. + rpc Parse (ParseRequest) returns (ParseResponse); + + // GetCatalog returns the initial catalog with built-in types and schemas. + rpc GetCatalog (GetCatalogRequest) returns (GetCatalogResponse); + + // IsReservedKeyword checks if a string is a reserved keyword. + rpc IsReservedKeyword (IsReservedKeywordRequest) returns (IsReservedKeywordResponse); + + // GetCommentSyntax returns the comment syntax supported by this engine. + rpc GetCommentSyntax (GetCommentSyntaxRequest) returns (GetCommentSyntaxResponse); + + // GetDialect returns the SQL dialect information for formatting. + rpc GetDialect (GetDialectRequest) returns (GetDialectResponse); +} + +// ParseRequest contains the SQL to parse. +message ParseRequest { + string sql = 1; +} + +// ParseResponse contains the parsed statements. +message ParseResponse { + repeated Statement statements = 1; +} + +// Statement represents a parsed SQL statement. +message Statement { + // The raw SQL text of the statement. + string raw_sql = 1; + + // The position in the input where this statement starts. + int32 stmt_location = 2; + + // The length of the statement in bytes. + int32 stmt_len = 3; + + // The AST of the statement encoded as JSON. + // The JSON structure follows the internal AST format. + bytes ast_json = 4; +} + +// GetCatalogRequest is empty for now. +message GetCatalogRequest {} + +// GetCatalogResponse contains the initial catalog. +message GetCatalogResponse { + Catalog catalog = 1; +} + +// Catalog represents the database catalog. +message Catalog { + string comment = 1; + string default_schema = 2; + string name = 3; + repeated Schema schemas = 4; +} + +// Schema represents a database schema. +message Schema { + string name = 1; + string comment = 2; + repeated Table tables = 3; + repeated Enum enums = 4; + repeated Function functions = 5; + repeated Type types = 6; +} + +// Table represents a database table. +message Table { + string catalog = 1; + string schema = 2; + string name = 3; + repeated Column columns = 4; + string comment = 5; +} + +// Column represents a table column. +message Column { + string name = 1; + string data_type = 2; + bool not_null = 3; + bool is_array = 4; + int32 array_dims = 5; + string comment = 6; + int32 length = 7; + bool is_unsigned = 8; +} + +// Enum represents an enum type. +message Enum { + string schema = 1; + string name = 2; + repeated string values = 3; + string comment = 4; +} + +// Function represents a database function. +message Function { + string schema = 1; + string name = 2; + repeated FunctionArg args = 3; + DataType return_type = 4; + string comment = 5; +} + +// FunctionArg represents a function argument. +message FunctionArg { + string name = 1; + DataType type = 2; + bool has_default = 3; +} + +// DataType represents a SQL data type. +message DataType { + string catalog = 1; + string schema = 2; + string name = 3; +} + +// Type represents a composite or custom type. +message Type { + string schema = 1; + string name = 2; + string comment = 3; +} + +// IsReservedKeywordRequest contains the keyword to check. +message IsReservedKeywordRequest { + string keyword = 1; +} + +// IsReservedKeywordResponse contains the result. +message IsReservedKeywordResponse { + bool is_reserved = 1; +} + +// GetCommentSyntaxRequest is empty. +message GetCommentSyntaxRequest {} + +// GetCommentSyntaxResponse contains supported comment syntax. +message GetCommentSyntaxResponse { + bool dash = 1; // -- comment + bool slash_star = 2; // /* comment */ + bool hash = 3; // # comment +} + +// GetDialectRequest is empty. +message GetDialectRequest {} + +// GetDialectResponse contains dialect information. +message GetDialectResponse { + // The character(s) used for quoting identifiers (e.g., ", `, [) + string quote_char = 1; + + // The parameter style: "positional" ($1, ?), "named" (@name, :name) + string param_style = 2; + + // The parameter prefix (e.g., $, ?, @, :) + string param_prefix = 3; + + // The cast syntax: "double_colon" (::), "cast_function" (CAST(x AS y)) + string cast_syntax = 4; +} + diff --git a/protos/plugin/codegen.proto b/protos/plugin/codegen.proto index e6faf19bad..010b85f38d 100644 --- a/protos/plugin/codegen.proto +++ b/protos/plugin/codegen.proto @@ -2,6 +2,9 @@ syntax = "proto3"; package plugin; +// Go code is generated to pkg/plugin for external plugin developers +option go_package = "github.com/sqlc-dev/sqlc/pkg/plugin"; + service CodegenService { rpc Generate (GenerateRequest) returns (GenerateResponse); }