Languages: English | 中文
xb is an AI-first SQL/JSON builder for relational + vector databases. One fluent API builds:
- SQL for
database/sql,sqlx,gorm, any raw driver - JSON for Qdrant (Recommend / Discover / Scroll) and other vector stores
- Hybrid pipelines that mix SQL tables with vector similarity
Everything flows through Custom() + Build() so the surface stays tiny even as capabilities grow.
Notes
- Persistence structs mirror the database schema, so numeric primary keys can stay as plain values.
- Request/filter DTOs should declare non-primary numeric and boolean fields as pointers to distinguish “unset” from “0/false” and to leverage autobypass logic.
- Need to bypass optimizations? Use
X("...")to inject raw SQL (the clause will never be auto-skipped), and pick explicit JOIN helpers (e.g.,JOIN(NON_JOIN)or custom builders) when you want to keep every JOIN even if it looks redundant. ForBuilderX, callWithoutOptimization()to disable the JOIN/CTE optimizer entirely.- For non-functional control flow inside fluent chains, use
Any(func(*BuilderX))to run loops or helper functions without breaking chaining, andBool(func() bool, func(*CondBuilder))to conditionally add blocks while reusing the auto-filtered DSL.
- Unified vector entry —
JsonOfSelect()now covers all Qdrant search/recommend/discover/scroll flows. LegacyToQdrant*JSON()methods were retired. - Composable SQL —
With/WithRecursiveandUNION(kind, fn)let you express ClickHouse-style analytics directly in Go. - Smart condition DSL — auto-filter nil/zero, guard rails via
InRequired, raw expressions viaX(), reusable subqueries viaCondBuilderX.Sub(), and inline conditional blocks. - Adaptive JOIN planner —
FromX+JOIN(kind)skip meaningless joins automatically (e.g., empty ON blocks), keeping SQL lean. - Observability-first —
Meta(func)plus interceptors carry TraceID/UserID across builder stages. - AI-assisted maintenance — code, tests, docs co-authored by AI and reviewed by humans every release.
package main
import "github.com/fndome/xb"
type Cat struct {
ID uint64 `db:"id"`
Name string `db:"name"`
Age *uint `db:"age"`
Price *float64 `db:"price"`
}
func main() {
built := xb.Of(&Cat{}).
Eq("status", 1).
Gte("age", 3).
Build()
sql, args, _ := built.SqlOfSelect()
// SELECT * FROM t_cat WHERE status = ? AND age >= ?
_ = sql
_ = args
}queryVector := xb.Vector{0.1, 0.2, 0.3}
json, err := xb.Of(&CodeVector{}).
Custom(
xb.NewQdrantBuilder().
Recommend(func(rb *xb.RecommendBuilder) {
rb.Positive(123, 456).Negative(789).Limit(20)
}).
Build()
).
Eq("language", "golang").
VectorSearch("embedding", queryVector, 10).
Build().
JsonOfSelect()
if err != nil {
panic(err)
}
// POST json to /collections/{name}/points/recommendreport := xb.Of("recent_orders").
With("recent_orders", func(sb *xb.BuilderX) {
sb.From("orders o").
Select("o.id", "o.user_id").
Gt("o.created_at", since30Days)
}).
WithRecursive("team_hierarchy", func(sb *xb.BuilderX) {
sb.From("users u").
Select("u.id", "u.manager_id").
Eq("u.active", true)
}).
UNION(xb.ALL, func(sb *xb.BuilderX) {
sb.From("archived_orders ao").
Select("ao.id", "ao.user_id")
}).
Meta(func(meta *interceptor.Metadata) {
meta.TraceID = traceID
meta.Set("source", "dashboard")
}).
Build()
sql, args, _ := report.SqlOfSelect()builder := xb.X().
Select("p.id", "p.weight").
FromX(func(fb *xb.FromBuilder) {
fb.Sub(func(sb *xb.BuilderX) {
sb.Select("id", "type").
From("t_pet").
Gt("id", 10000)
}).As("p").
JOIN(xb.INNER).Of("t_dog").As("d").On("d.pet_id = p.id").
JOIN(xb.LEFT).Of("t_cat").As("c").On("c.pet_id = p.id").
Cond(func(on *xb.ON) {
on.Gt("p.weight", 10)
})
}).
Ne("p.type", "PIG")
sql, args, _ := builder.Build().SqlOfSelect()- Configure via
NewQdrantBuilder().Recommend(...).Build()/Discover(...)/ScrollID(...). JsonOfSelect()inspects builder state and emits the correct JSON schema.- Compatible with diversity helpers (
WithHashDiversity,WithMinDistance) and standard filters.
- Register global
BeforeBuild/AfterBuildhooks (seexb/interceptor). Meta(func)injects metadata before hooks run — perfect for tracing, tenancy, or experiments.
- Dialects (
dialect.go) let you swap quoting rules, placeholder styles, and vendor-specific predicates without rewriting builders — seedoc/en/DIALECT_CUSTOM_DESIGN.md/doc/cn/DIALECT_CUSTOM_DESIGN.md. Custom()is the escape hatch for vector DBs and bespoke backends: plug inCustomimplementations, emit JSON viaJsonOfSelect(), or mix SQL + vector calls in one fluent chain. Deep dives live indoc/en/CUSTOM_VECTOR_DB_GUIDE.md/doc/cn/CUSTOM_VECTOR_DB_GUIDE.md.- Need Oracle/Milvus/other dialects? Implement a tiny interface
Custom, register it once, and the fluent chains instantly start outputting those drivers’ SQL/JSON schemas without forking the builder core.
| Topic | English | Chinese |
|---|---|---|
| Overview & Index | doc/en/README.md | doc/cn/README.md |
| Quickstart | doc/en/QUICKSTART.md | doc/cn/QUICKSTART.md |
| Qdrant Guide | doc/en/QDRANT_GUIDE.md | doc/cn/QDRANT_GUIDE.md |
| Vector Guide | doc/en/VECTOR_GUIDE.md | doc/cn/VECTOR_GUIDE.md |
| Custom Interface | doc/en/CUSTOM_INTERFACE.md | doc/cn/CUSTOM_INTERFACE.md |
| Auto-filter (nil/0 skip) | doc/en/ALL_FILTERING_MECHANISMS.md | doc/cn/FILTERING.md |
| Join optimization | doc/en/CUSTOM_JOINS_GUIDE.md | (coming soon) |
| AI Application Starter | doc/en/AI_APPLICATION.md | doc/cn/AI_APPLICATION.md |
We are migrating docs into
doc/en/+doc/cn/. Legacy files remain underdoc/until the move completes.
We welcome issues, discussions, PRs!
- Issues & features: GitHub Issues
- Roadmap & ideas: GitHub Discussions
- Contribution steps: CONTRIBUTING
- Vision & philosophy: VISION.md
Before opening a PR:
- Run
go test ./... - Update docs/tests related to your change
- Describe behavior changes clearly in the PR template
Apache License 2.0 — see LICENSE.