Text to SQL in Go: a safe natural-language query library
QueryForge is an open-source Go library that turns a user’s sentence into a query you can run. The model never writes the SQL: it fills in a typed plan that is validated against your config, and Go code compiles the plan into a parameterized statement. The same plan can also compile to MongoDB and Elasticsearch, so one integration covers every database you query.
Install
go get github.com/awsaman-ai/queryforge
You need a config file that describes your data and, unless you use a local model, an API key in your environment. The config builder creates the file in your browser.
Describe your data in a config
The config is the vocabulary the model is allowed to use. Anything not listed here cannot appear in a query. This is a minimal one for an orders table, using a local model through Ollama:
{
"entity": "Order",
"model": { "provider": "ollama", "model": "qwen2.5" },
"backends": { "sql": { "table": "orders" } },
"fields": [
{ "name": "status", "type": "enum",
"values": ["PLACED", "DELIVERED", "CANCELLED", "REFUNDED"],
"operators": ["equals", "notEquals", "in", "notIn"],
"mapping": { "sql": "status" } },
{ "name": "amount", "type": "number", "mapping": { "sql": "amount" } }
]
}
Every key is explained in the configuration documentation.
A working example
cfg, _ := qf.LoadConfig("orders.config.json")
engine := qf.New(cfg)
res, _ := engine.Translate(ctx,
"cancelled orders over 200 dollars", "sql", nil)
fmt.Println(res.Query.SQL)
// SELECT … WHERE (status = $1 AND amount > $2) …
fmt.Println(res.Query.Args) // [CANCELLED 200]
The library returns the SQL and its arguments. It never opens a connection, so you run the query with the driver you already use. With database/sql that is one line:
rows, err := db.QueryContext(ctx, res.Query.SQL, res.Query.Args...)
The result also carries the validated plan and a plain-language explanation of it, so you can show the user what was understood before you run anything.
Keep every query inside the caller’s tenant
Some filters are not the user’s to choose. Pass them as a scope map and they are AND-ed onto the query after the model answers, so the model never learns the field exists:
res, err := engine.Translate(ctx, "delivered orders over 500 dollars", "sql", qf.Scope{
"subscriptionId": session.SubscriptionID, // "SUB-42"
"userId": session.UserID, // 9
"enterpriseId": session.EnterpriseIDs, // []string{"E-1", "E-2"}
})
SELECT … FROM orders
WHERE (enterpriseId IN ($1, $2) AND subscriptionId = $3 AND userId = $4
AND status = $5 AND amount > $6)
-- args: ["E-1", "E-2", "SUB-42", 9, "DELIVERED", 500]
Scope values are bound parameters like any other value, so an injection payload stays an argument. Set policy.requiredScope in the config and a query without the tenant filter is refused.
Choose your model
The default provider speaks any OpenAI-compatible /chat/completions endpoint. Others speak Anthropic’s native Messages API and Amazon Bedrock’s Converse API. Built-in presets include OpenAI, Anthropic, Gemini, Mistral, Groq and Ollama, and switching is a config change, not a code change.
Why not just prompt an LLM for SQL?
- There is no query text to inject into. The model fills a typed plan; code writes the statement.
- Invented columns are caught early. A field that is not in your config fails validation before anything compiles.
- Writes cannot be expressed. The plan has no mutation node, so every output is a read.
- Behaviour is testable offline. The compile step is plain Go with no network, so you can unit-test it.
The reasoning is covered in more depth in Safe text-to-SQL.
What you get
- Five backends. PostgreSQL, MySQL, MongoDB, Elasticsearch and OpenSearch.
- Read-only by construction, with hidden fields excluded from the projection.
- Observability. An optional callback reports model latency, tokens and outcomes. The library never writes a log line itself, and never logs the question, scope values or config contents.
Other languages
The same engine is available for Python (pip install queryforge-ai) and Java, and as an MCP server for AI assistants. See the install guide.
Next steps
The Go API is documented on pkg.go.dev, and the source is on GitHub under the Apache-2.0 license.