Phase 7g.6: Go SDK sqlrite.Ask() / AskRun() / AskConfig#68
Merged
Conversation
cgo wrapper for natural-language → SQL via the engine's
`sqlrite::ask` module. Same three-layer config precedence as
Python (7g.4) and Node (7g.5), with idiomatic Go shape: free
functions on `*sql.DB` plus context-aware variants.
## Public surface
```go
import (
"database/sql"
sqlrite "github.com/joaoh82/rust_sqlite/sdk/go"
)
db, _ := sql.Open("sqlrite", "foo.sqlrite")
// Path 1: nil cfg → reads SQLRITE_LLM_API_KEY etc. from env
resp, err := sqlrite.Ask(db, "How many users are over 30?", nil)
// Path 2: explicit per-call config
cfg := &sqlrite.AskConfig{
APIKey: "sk-ant-...",
Model: "claude-haiku-4-5",
MaxTokens: 512,
CacheTTL: "1h", // "5m" (default) | "1h" | "off"
}
resp, _ := sqlrite.Ask(db, "list users", cfg)
// Convenience: generate + execute
rows, _ := sqlrite.AskRun(db, "list users", nil)
defer rows.Close()
for rows.Next() {
var id int64; var name string
rows.Scan(&id, &name)
}
// Context-aware variants for connection-pool acquisition control
resp, err := sqlrite.AskContext(ctx, db, "...", cfg)
rows, err := sqlrite.AskRunContext(ctx, db, "...", cfg)
```
## What's new
* `sqlrite.AskConfig` struct — Provider / APIKey / Model /
MaxTokens / CacheTTL / BaseURL. JSON tags match the FFI's
snake_case ABI for direct serialization.
* `sqlrite.AskConfigFromEnv()` — reads SQLRITE_LLM_*. Builds a
config callers can inspect / log / mutate before passing to
Ask. Returns error on invalid SQLRITE_LLM_MAX_TOKENS.
* `(*AskConfig).String()` — debug-friendly repr that **omits the
API key value** (shows `<set>` or `<unset>`). Lets callers
`fmt.Println(cfg)` in logs without leaking the secret.
* `sqlrite.Ask(db, question, *AskConfig) (*AskResponse, error)`
— generates SQL, does NOT execute. Plus `AskContext(ctx, ...)`.
* `sqlrite.AskRun(db, question, *AskConfig) (*sql.Rows, error)`
— generates AND executes; returns rows for iteration. Plus
`AskRunContext(ctx, ...)`. Throws on empty SQL response (model
declined) with the model's explanation in the error.
* `AskResponse { SQL, Explanation, Usage }` + `AskUsage { Input,
Output, CacheCreation, CacheRead Tokens }`. The API key is
never carried through here.
## C ABI: one new FFI function
`sqlrite_ask(conn, question, config_json, *out)` accepts the
AskConfig as a **JSON string** rather than 6+ separate FFI
parameters. Smaller surface, more extensible (adding fields later
doesn't break the C ABI for existing bindings) — matches how the
Python and Node SDKs ended up structuring config too.
The response is also JSON: `{"sql":..., "explanation":...,
"usage":{...}}`. Caller frees with `sqlrite_free_string`.
`build_ask_config(json)` on the Rust side starts from
`AskConfig::from_env()` and applies overrides — so a Go caller
passing `{"model": "claude-haiku-4-5"}` still picks up
`SQLRITE_LLM_API_KEY` from the environment without having to
re-read it Go-side.
## Plumbing through `database/sql`
The Go SDK's existing pattern is `sql.Open("sqlrite", path) →
*sql.DB`. The underlying `*conn` (which holds the opaque
`*C.SqlriteConnection`) is reachable via `db.Conn(ctx).Raw(...)`.
That's how `Ask` gets at the FFI handle — abstracted in `ask.go`'s
internal helper. Callers just see the natural Go API.
## Tests — 11 new tests pass
11 new tests in `sdk/go/ask_test.go`:
* **AskConfig + AskConfigFromEnv (4)**: defaults, env overrides,
invalid MAX_TOKENS, String() doesn't leak the API key.
* **Error paths (3)**: nil db, missing API key, closed db.
* **Happy path (3)**: Ask round-trip, AskRun executes the
generated SQL and returns rows, AskRun on empty SQL response
surfaces the model's explanation as an error.
* **API error surfacing (1)**: 400 with structured Anthropic
error → Go error carrying status + inner type+message.
`httptest.Server` runs on a Go runtime goroutine, so unlike the
Python (GIL) and Node (event-loop) SDKs there's no deadlock
concern with synchronous cgo calls. Test setup is the cleanest
of the three SDKs.
Plus all 9 pre-existing Go SDK tests still pass.
## Docs
* `sdk/go/README.md` — "coming soon" preview replaced with full
reference: 3-layer precedence, context-aware variants, errors,
AskResponse type signatures, no-key-in-String guarantee.
* `docs/phase-7-plan.md` — 7g.6 marked ✅ with the JSON-config-
over-cgo design note and the test-setup-is-cleanest insight.
* `docs/roadmap.md` — 7g bullet updated.
cargo fmt clean. cargo workspace tests 301/301 still green (FFI
gained 200 LOC; engine + sqlrite-ask unchanged). go vet clean.
go test passes (11 new + 9 existing). Generated `sqlrite.h`
header now declares `sqlrite_ask` for downstream C / FFI consumers.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
cgo wrapper for natural-language → SQL via the engine's
sqlrite::askmodule. Same three-layer config precedence as Python (7g.4) and Node (7g.5), idiomatic Go shape: free functions on*sql.DBplus context-aware variants.What's new
AskConfigstructAskConfigFromEnv()SQLRITE_LLM_*. Errors on invalidMAX_TOKENS.(*AskConfig).String()<set>or<unset>).Ask(db, q, *AskConfig)*AskResponse.AskContext(ctx, db, q, *AskConfig)db.Conn(ctx).AskRun(db, q, *AskConfig)*sql.Rows. Errors on empty SQL response (model declined) with the model's explanation.AskRunContext(ctx, db, q, *AskConfig)AskResponse { SQL, Explanation, Usage }Askreturns.AskUsage { InputTokens, OutputTokens, CacheCreationInputTokens, CacheReadInputTokens }CacheReadInputTokensto verify caching is working.C ABI: one new FFI function
sqlrite_ask(conn, question, config_json, *out)accepts the AskConfig as a JSON string rather than 6+ separate FFI parameters. Smaller surface, more extensible (adding fields later doesn't break the C ABI for existing bindings).The response is also JSON:
{"sql":..., "explanation":..., "usage":{...}}. Caller frees withsqlrite_free_string.build_ask_config(json)on the Rust side starts fromAskConfig::from_env()and applies overrides — so a Go caller passing{"model": "claude-haiku-4-5"}still picks upSQLRITE_LLM_API_KEYfrom the environment without having to re-read it Go-side.Plumbing through
database/sqlThe existing pattern is
sql.Open("sqlrite", path) → *sql.DB. The underlying*conn(which holds the opaque*C.SqlriteConnection) is reachable viadb.Conn(ctx).Raw(...). That's howAskgets at the FFI handle — abstracted inask.go's internal helper. Callers just see the natural Go API.Tests — 11/11 new pass
MAX_TOKENS,String()doesn't leak the API key.httptest.Serverruns on a Go runtime goroutine, so unlike Python (GIL) and Node (event-loop), there's no deadlock concern with synchronous cgo calls. Cleanest test setup of the three SDKs.Plus all 9 pre-existing Go SDK tests still pass.
Test plan
cargo fmt --all -- --check— cleancargo build -p sqlrite-ffi --release— clean (regeneratessqlrite.hwith new function)cargo test --workspace --exclude sqlrite-desktop --exclude sqlrite-python --exclude sqlrite-nodejs— 301/301 pass (engine + sqlrite-ask unchanged; FFI extended)cd sdk/go && go vet ./...— cleancd sdk/go && go test— 11 new + 9 existing passexport SQLRITE_LLM_API_KEY=...→cd sdk/go && go run -tags ./...against a small test programDocs
sdk/go/README.md— "coming soon" preview replaced with full reference.docs/phase-7-plan.md— 7g.6 marked ✅ with the JSON-config-over-cgo design note.docs/roadmap.md— 7g bullet updated.Next up after merge
release-pr.ymlatv0.1.23→sdk/go/v0.1.23tag goes live;go get github.com/joaoh82/rust_sqlite/sdk/go@v0.1.23carries the newAsk/AskRunsurface.sdk/wasm/README.mdshowing browser → backend proxy → LLM provider → response back to WASM.asktool (after 7h lays the MCP server framework).🤖 Generated with Claude Code