Documentation
¶
Overview ¶
Package dbctx compiles a PostgreSQL database into a compact, queryable context index for text-to-SQL systems, AI agents, and database-aware applications.
dbctx connects to PostgreSQL, extracts schema metadata, analyzes field statistics from pg_stats, discovers JSONB structure via sampling, and builds a full-text search index — all without requiring an LLM or external services. The result is a Index that answers natural-language queries about which tables, columns, values, and relationships are relevant to a given question.
The index can be stored on disk as a portable .dtx file (SQLite) or kept entirely in memory for ephemeral use. It is safe for concurrent access from multiple goroutines.
Quick start ¶
Build an in-memory index and query it:
idx, err := dbctx.Build(ctx, "postgres://localhost/mydb", nil)
if err != nil {
log.Fatal(err)
}
defer idx.Close()
result, err := idx.Query("failed reviews last month")
if err != nil {
log.Fatal(err)
}
fmt.Println(result.Matched().Text())
The Selection.Text output is a compact, notation-annotated schema ready to pass to an LLM or text-to-SQL system:
--- notation ---
PK: primary key col → table foreign key
...
reviews (score: 15.24)
PK: id
org_id → orgs
status character varying(50) [state]
{completed, failed, created, in_progress}
metadata jsonb
$.provider string {github, gitlab}
Persisting the index ¶
Save the index to a .dtx file for later reuse — no PostgreSQL needed:
idx, err := dbctx.Build(ctx, dsn, &dbctx.Options{Path: "mydb.dtx"})
// ...later...
idx, err = dbctx.Open("mydb.dtx")
Non-blocking startup ¶
For applications that need the index available without blocking startup, use BuildAsync. Queries made before the build completes will block automatically:
idx, ready, err := dbctx.BuildAsync(ctx, dsn, nil)
if err != nil {
log.Fatal(err)
}
defer idx.Close()
// Register idx with your application immediately...
<-ready // or: <-idx.Ready()
Selection API ¶
Query results can be filtered and rendered in several ways:
result, _ := idx.Query("failed reviews")
result.Matched().Text() // matched tables with legend
result.Matched().TextRaw() // matched tables, no legend
result.All().Text() // all tables including FK-expanded
result.Include("reviews", "orgs").Text() // specific tables
result.Matched().Exclude("migrations").Text() // matched minus exclusions
Use cases ¶
dbctx is designed for any system that needs to understand a PostgreSQL database at query time: text-to-SQL generation, natural-language analytics, AI agents, database explorers, BI tools, and developer assistants. It replaces repeated full-schema dumps with a deterministic, queryable index.
Example ¶
This example demonstrates building an in-memory index from PostgreSQL and querying it with natural language. The output is a compact, notation-annotated schema ready for an LLM or text-to-SQL system.
package main
import (
"context"
"fmt"
"log"
"github.com/shrsv/dbctx"
)
func main() {
ctx := context.Background()
idx, err := dbctx.Build(ctx, "postgres://localhost/mydb", nil)
if err != nil {
log.Fatal(err)
}
defer idx.Close()
result, err := idx.Query("failed reviews last month")
if err != nil {
log.Fatal(err)
}
// Matched() returns only tables with score > 0.
// Text() prepends a notation legend explaining every symbol.
fmt.Println(result.Matched().Text())
}
Output:
Example (BuildAsync) ¶
This example demonstrates non-blocking startup with BuildAsync. The index builds in a background goroutine while the application continues setup. Queries block automatically until the index is ready.
package main
import (
"context"
"fmt"
"log"
"github.com/shrsv/dbctx"
)
func main() {
ctx := context.Background()
dsn := "postgres://localhost/mydb"
idx, ready, err := dbctx.BuildAsync(ctx, dsn, nil)
if err != nil {
log.Fatal(err)
}
defer idx.Close()
// Register idx with your application immediately.
go func() {
<-ready
log.Println("dbctx index is ready")
}()
// This call blocks automatically if the index isn't ready yet.
result, err := idx.Query("active users")
if err != nil {
log.Fatal(err)
}
fmt.Println(result.Matched().Text())
}
Output:
Example (Persist) ¶
This example demonstrates saving an index to a .dtx file and opening it later without a PostgreSQL connection.
package main
import (
"context"
"fmt"
"log"
"github.com/shrsv/dbctx"
)
func main() {
ctx := context.Background()
dsn := "postgres://localhost/mydb"
// Build and save to disk.
idx, err := dbctx.Build(ctx, dsn, &dbctx.Options{Path: "mydb.dtx"})
if err != nil {
log.Fatal(err)
}
idx.Close()
// Reopen without PostgreSQL.
idx, err = dbctx.Open("mydb.dtx")
if err != nil {
log.Fatal(err)
}
defer idx.Close()
tables, _ := idx.Tables()
fmt.Printf("Index has %d tables\n", len(tables))
}
Output:
Example (Selection) ¶
This example demonstrates the Selection API for filtering and rendering query results in different ways.
package main
import (
"context"
"fmt"
"log"
"github.com/shrsv/dbctx"
)
func main() {
ctx := context.Background()
idx, err := dbctx.Build(ctx, "postgres://localhost/mydb", nil)
if err != nil {
log.Fatal(err)
}
defer idx.Close()
result, err := idx.Query("failed reviews")
if err != nil {
log.Fatal(err)
}
// Only matched tables, with notation legend.
fmt.Println(result.Matched().Text())
// Without legend (tighter token budget).
fmt.Println(result.Matched().TextRaw())
// All tables including FK-expanded.
fmt.Println(result.All().TextRaw())
// Specific tables by name.
fmt.Println(result.Include("reviews", "orgs").TextRaw())
// Matched minus a table.
fmt.Println(result.Matched().Exclude("migrations").TextRaw())
}
Output:
Index ¶
- type ColumnDetail
- type ColumnInfo
- type FKInfo
- type Index
- func (idx *Index) Close() error
- func (idx *Index) Err() error
- func (idx *Index) Query(query string) (*ResultSet, error)
- func (idx *Index) Ready() <-chan struct{}
- func (idx *Index) Report(w io.Writer) error
- func (idx *Index) Stats() (*Stats, error)
- func (idx *Index) TableDetail(name string) (*TableDetail, error)
- func (idx *Index) Tables() ([]TableSummary, error)
- type JSONBPathInfo
- type Options
- type ResultSet
- type Selection
- type Stats
- type TableContext
- type TableDetail
- type TableSummary
- type ValueInfo
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type ColumnDetail ¶
type ColumnDetail struct {
Name string `json:"name"`
Type string `json:"type"`
Nullable bool `json:"nullable"`
IsPK bool `json:"is_pk"`
FKTarget string `json:"fk_target,omitempty"`
Distinct int `json:"distinct"`
IsState bool `json:"is_state"`
IsCategoric bool `json:"is_categoric"`
Values []ValueInfo `json:"values,omitempty"`
JSONBPaths []JSONBPathInfo `json:"jsonb_paths,omitempty"`
}
ColumnDetail describes a column in a table detail response. It includes distinct count, state/categorical flags, representative values, and JSONB paths.
type ColumnInfo ¶
type ColumnInfo struct {
Name string `json:"name"`
Type string `json:"type"`
Nullable bool `json:"nullable"`
IsPK bool `json:"is_pk"`
FKTarget string `json:"fk_target,omitempty"`
IsState bool `json:"is_state"`
IsCategoric bool `json:"is_categoric"`
Values []ValueInfo `json:"values,omitempty"`
JSONBPaths []JSONBPathInfo `json:"jsonb_paths,omitempty"`
}
ColumnInfo describes a column in a query result, including its type, flags (PK, nullable, state, categorical), representative values, and JSONB paths if applicable.
type FKInfo ¶
type FKInfo struct {
SrcColumns string `json:"src_columns"`
RefTable string `json:"ref_table"`
DstColumns string `json:"dst_columns"`
}
FKInfo describes a foreign key relationship between tables.
type Index ¶
type Index struct {
// contains filtered or unexported fields
}
Index is a compiled database context index. It provides methods to query the database structure, relationships, field semantics, and representative values extracted from PostgreSQL.
An Index is safe for concurrent use by multiple goroutines. Create one with Build, BuildAsync, or Open.
func Build ¶
Build connects to PostgreSQL and builds a complete database context index.
It extracts schema, analyzes field statistics from pg_stats, discovers JSONB structure via sampling, and builds a full-text search index. The resulting index is ready for queries immediately upon return.
If opts is nil or opts.Path is empty, the index is stored in memory. Pass opts.Path to persist the index as a .dtx file on disk.
The caller must call Close on the returned Index when done.
func BuildAsync ¶
BuildAsync starts building the index in a background goroutine and returns immediately. The returned channel is closed when the build completes.
This is useful for non-blocking application startup. The returned Index can be registered with your application immediately. Any calls to Index.Query, Index.Tables, or other methods will block until the build completes.
Example:
idx, ready, err := dbctx.BuildAsync(ctx, dsn, nil)
if err != nil {
log.Fatal(err)
}
defer idx.Close()
// Register idx with your app immediately...
// Wait for readiness:
<-ready
If the build fails, Index.Err returns the error and Query/Tables/etc will return that error.
func Open ¶
Open opens an existing .dtx file for querying. The file must exist and contain a valid dbctx index created by Build or the `dbctx build` CLI.
The caller must call Close on the returned Index when done.
Example ¶
This example demonstrates opening a persisted .dtx file and listing all tables with summary information.
package main
import (
"fmt"
"log"
"github.com/shrsv/dbctx"
)
func main() {
idx, err := dbctx.Open("mydb.dtx")
if err != nil {
log.Fatal(err)
}
defer idx.Close()
tables, err := idx.Tables()
if err != nil {
log.Fatal(err)
}
for _, t := range tables {
fmt.Printf("%-30s %6.0f rows %d cols %d FKs\n",
t.Name, t.RowEstimate, t.ColCount, t.FKCount)
}
}
Output:
func (*Index) Close ¶
Close releases all resources held by the index, including the underlying SQLite database connection. After Close, no other methods may be called.
func (*Index) Err ¶
Err returns the build error if an async build failed. Returns nil if the build succeeded or is still in progress. Check Index.Ready first to know when the build is done.
func (*Index) Query ¶
Query searches the index for tables matching the given natural language query. It combines full-text search, fuzzy table name matching, value matching, and foreign-key expansion to find relevant tables and their context.
If the index was created with BuildAsync and the build is still in progress, Query blocks until the build completes.
Returns a ResultSet that can be filtered and converted to compact text:
result, _ := idx.Query("failed reviews last month")
text := result.Matched().Text() // only matched tables
text := result.All().Text() // all tables including FK-expanded
text := result.Include("reviews").Text() // specific tables
func (*Index) Ready ¶
func (idx *Index) Ready() <-chan struct{}
Ready returns a channel that is closed when the index is ready for queries. For synchronous builds created with Build, the channel is already closed. For async builds created with BuildAsync, the channel closes when the background build completes.
func (*Index) Report ¶
Report writes a human-readable report of the entire index to w. The report includes schema, state fields, categorical fields, JSONB structure, relationships, and summary statistics.
Blocks until the index is ready if an async build is in progress.
func (*Index) Stats ¶
Stats returns summary statistics about the index, including counts of tables, columns, foreign keys, state fields, categorical fields, JSONB paths, and field values.
Blocks until the index is ready if an async build is in progress.
Example ¶
This example demonstrates getting summary statistics about the index.
package main
import (
"context"
"fmt"
"log"
"github.com/shrsv/dbctx"
)
func main() {
ctx := context.Background()
idx, err := dbctx.Build(ctx, "postgres://localhost/mydb", nil)
if err != nil {
log.Fatal(err)
}
defer idx.Close()
stats, err := idx.Stats()
if err != nil {
log.Fatal(err)
}
fmt.Printf("Tables: %d\n", stats.Tables)
fmt.Printf("Columns: %d\n", stats.Columns)
fmt.Printf("Foreign keys: %d\n", stats.ForeignKeys)
fmt.Printf("State fields: %d\n", stats.StateFields)
fmt.Printf("Categorical fields: %d\n", stats.CategoricalFields)
fmt.Printf("JSONB paths: %d\n", stats.JSONBPaths)
}
Output:
func (*Index) TableDetail ¶
func (idx *Index) TableDetail(name string) (*TableDetail, error)
TableDetail returns detailed information about a specific table, including columns with types, PK/FK tags, value distributions, JSONB paths, and foreign key relationships.
Returns nil and no error if the table is not found. Blocks until the index is ready if an async build is in progress.
Example ¶
This example demonstrates getting detailed information about a single table including columns, types, primary keys, foreign keys, and representative values for state-like fields.
package main
import (
"context"
"fmt"
"log"
"strings"
"github.com/shrsv/dbctx"
)
func main() {
ctx := context.Background()
idx, err := dbctx.Build(ctx, "postgres://localhost/mydb", nil)
if err != nil {
log.Fatal(err)
}
defer idx.Close()
detail, err := idx.TableDetail("reviews")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Table: %s\n", detail.Name)
fmt.Printf("Primary key: %s\n", strings.Join(detail.PrimaryKey, ", "))
fmt.Printf("Columns: %d\n", len(detail.Columns))
for _, col := range detail.Columns {
flags := ""
if col.IsPK {
flags += " PK"
}
if col.IsState {
flags += " [state]"
}
if col.FKTarget != "" {
flags += " -> " + col.FKTarget
}
fmt.Printf(" %-20s %-20s%s\n", col.Name, col.Type, flags)
}
}
Output:
func (*Index) Tables ¶
func (idx *Index) Tables() ([]TableSummary, error)
Tables returns a summary of all tables in the index. Each entry includes the table name, schema, row estimate, column count, and FK count.
Blocks until the index is ready if an async build is in progress.
type JSONBPathInfo ¶
type JSONBPathInfo struct {
Path string `json:"path"`
InferredType string `json:"inferred_type"`
SampleValues string `json:"sample_values,omitempty"`
}
JSONBPathInfo describes a path within a JSONB column, including its inferred type and sample values.
type Options ¶
type Options struct {
// Path is the file path for the .dtx file. If empty, an in-memory
// SQLite database is used (no file created). In-memory indexes are
// faster but must be rebuilt on each process start.
Path string
// Schemas is a comma-separated list of PostgreSQL schemas to extract.
// Defaults to "public" if empty.
Schemas string
// MaxConns is the maximum number of concurrent PostgreSQL connections
// in the connection pool. Higher values allow more parallel JSONB
// analysis. Defaults to 4 if zero.
MaxConns int
// Logger receives progress messages during build. If nil, os.Stderr is used.
Logger io.Writer
}
Options configures how a database context index is built.
type ResultSet ¶
type ResultSet struct {
// Query is the original query string.
Query string `json:"query"`
// Tables contains all tables in the result, including both directly
// matched tables (score > 0) and FK-expanded tables (score = 0).
Tables []TableContext `json:"tables"`
}
ResultSet holds the results of a query and provides methods to select subsets of matched tables and render them as compact text.
The typical flow is:
result, _ := idx.Query("failed reviews")
text := result.Matched().Text() // compact schema of matched tables only
func (*ResultSet) All ¶
All returns a Selection containing all tables in the result set, including FK-expanded tables that were not directly matched.
func (*ResultSet) Include ¶
Include returns a Selection containing only the named tables. Tables not found in the result set are silently ignored.
func (*ResultSet) Matched ¶
Matched returns a Selection containing only tables with a match score > 0. These are the tables most relevant to the query.
func (*ResultSet) TableMap ¶
func (rs *ResultSet) TableMap() map[string]TableContext
TableMap returns a map of table name to TableContext for quick lookup.
type Selection ¶
type Selection struct {
// contains filtered or unexported fields
}
Selection represents a subset of tables from a ResultSet. It provides methods to refine the selection and render it as compact text suitable for passing to an LLM or text-to-SQL system.
func (*Selection) Include ¶
Include adds the named tables to the selection. Tables not in the result set are silently ignored.
func (*Selection) Tables ¶
func (s *Selection) Tables() []TableContext
Tables returns the TableContext objects in this selection, in the same order they appear in the original result set.
func (*Selection) Text ¶
Text renders the selected tables as compact, human-readable text with a notation legend at the top. The legend explains every symbol and annotation used in the output so that an LLM (or human) can interpret the schema without external documentation.
Use Selection.TextRaw to omit the legend.
func (*Selection) TextRaw ¶
TextRaw renders the selected tables as compact, human-readable text without the notation legend. Use this when the caller already knows the notation, or when token budget is tight and the legend would be wasted context.
The output includes table names, scores, primary keys, foreign keys, columns with type/flags, state/categorical values, and JSONB paths.
type Stats ¶
type Stats struct {
Tables int `json:"tables"`
Columns int `json:"columns"`
ForeignKeys int `json:"foreign_keys"`
StateFields int `json:"state_fields"`
CategoricalFields int `json:"categorical_fields"`
JSONBPaths int `json:"jsonb_paths"`
FieldValues int `json:"field_values"`
}
Stats contains summary statistics about a database context index.
type TableContext ¶
type TableContext struct {
TableName string `json:"table_name"`
Schema string `json:"schema"`
Columns []ColumnInfo `json:"columns"`
PrimaryKey []string `json:"primary_key"`
ForeignKeys []FKInfo `json:"foreign_keys"`
IsMatch bool `json:"is_match"`
MatchScore float64 `json:"match_score"`
}
TableContext represents a table in a query result with its relevance score and full context (columns, values, relationships, JSONB paths).
type TableDetail ¶
type TableDetail struct {
TableSummary
PrimaryKey []string `json:"primary_key"`
ForeignKeys []FKInfo `json:"foreign_keys"`
Columns []ColumnDetail `json:"columns"`
}
TableDetail contains complete information about a table, including columns with types, flags, values, JSONB paths, and all relationships.
type TableSummary ¶
type TableSummary struct {
ID int `json:"id"`
Schema string `json:"schema"`
Name string `json:"name"`
RowEstimate float64 `json:"row_estimate"`
ColCount int `json:"columns"`
FKCount int `json:"fk_count"`
}
TableSummary is a lightweight table descriptor returned by Index.Tables.

Table details
JSONB expansion
State & categorical values
Query interface