mirror of
https://github.com/kbenestad/mdcms.git
synced 2026-06-18 15:24:32 +00:00
286 lines
6.4 KiB
Markdown
286 lines
6.4 KiB
Markdown
---
|
|
title: Go SDK
|
|
sort: 120
|
|
section-id: client-sdks
|
|
keywords: Go, Golang, SDK, client, connection pool, query builder, pgx
|
|
description: The NeuralDB Go SDK — installation, connection pooling, and vector query builder
|
|
language: en
|
|
---
|
|
|
|
# Go SDK
|
|
|
|
The NeuralDB Go SDK provides idiomatic Go bindings built on top of `pgx`, the high-performance PostgreSQL driver for Go.
|
|
|
|
## Installation
|
|
|
|
```bash
|
|
go get github.com/neuraldb/neuraldb-go
|
|
```
|
|
|
|
Requires Go 1.21+.
|
|
|
|
## Connecting
|
|
|
|
### Single Connection
|
|
|
|
```go
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
|
|
"github.com/neuraldb/neuraldb-go"
|
|
)
|
|
|
|
func main() {
|
|
ctx := context.Background()
|
|
|
|
client, err := neuraldb.Connect(ctx, "postgresql://neuraldb:password@localhost:5432/mydb")
|
|
if err != nil {
|
|
log.Fatal("connection failed:", err)
|
|
}
|
|
defer client.Close(ctx)
|
|
|
|
var count int64
|
|
err = client.QueryRow(ctx, "SELECT COUNT(*) FROM documents").Scan(&count)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
fmt.Println("Documents:", count)
|
|
}
|
|
```
|
|
|
|
### Connection Pool (Recommended)
|
|
|
|
```go
|
|
import (
|
|
"github.com/neuraldb/neuraldb-go"
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
)
|
|
|
|
func NewPool(ctx context.Context) (*neuraldb.Pool, error) {
|
|
config, err := pgxpool.ParseConfig(os.Getenv("NEURALDB_URL"))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
config.MaxConns = 20
|
|
config.MinConns = 5
|
|
config.MaxConnIdleTime = 30 * time.Minute
|
|
config.MaxConnLifetime = time.Hour
|
|
|
|
return neuraldb.NewPool(ctx, config)
|
|
}
|
|
```
|
|
|
|
## Working with Vectors
|
|
|
|
### Defining Vector Types
|
|
|
|
```go
|
|
package main
|
|
|
|
import "github.com/neuraldb/neuraldb-go/types"
|
|
|
|
// Create a vector from a float32 slice
|
|
v := types.NewVector([]float32{0.023, -0.187, 0.412})
|
|
|
|
// Create from float64 (auto-converted)
|
|
v2 := types.NewVectorFromFloat64([]float64{0.023, -0.187, 0.412})
|
|
|
|
// Access the underlying data
|
|
floats := v.Slice() // []float32
|
|
dims := v.Dims() // int
|
|
```
|
|
|
|
### Inserting a Document with a Vector
|
|
|
|
```go
|
|
type Document struct {
|
|
ID string
|
|
Content string
|
|
Embedding types.Vector
|
|
}
|
|
|
|
func InsertDocument(ctx context.Context, pool *neuraldb.Pool, doc Document) error {
|
|
_, err := pool.Exec(ctx,
|
|
`INSERT INTO documents (id, content, embedding) VALUES ($1, $2, $3)`,
|
|
doc.ID, doc.Content, doc.Embedding,
|
|
)
|
|
return err
|
|
}
|
|
```
|
|
|
|
### Vector Similarity Search
|
|
|
|
```go
|
|
func SemanticSearch(ctx context.Context, pool *neuraldb.Pool, queryEmbedding []float32, limit int) ([]SearchResult, error) {
|
|
qv := types.NewVector(queryEmbedding)
|
|
|
|
rows, err := pool.Query(ctx, `
|
|
SELECT id, content, 1 - (embedding <=> $1) AS similarity
|
|
FROM documents
|
|
WHERE embedding IS NOT NULL
|
|
ORDER BY embedding <=> $1
|
|
LIMIT $2
|
|
`, qv, limit)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("query failed: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
var results []SearchResult
|
|
for rows.Next() {
|
|
var r SearchResult
|
|
err := rows.Scan(&r.ID, &r.Content, &r.Similarity)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
results = append(results, r)
|
|
}
|
|
|
|
return results, rows.Err()
|
|
}
|
|
```
|
|
|
|
## Query Builder
|
|
|
|
The SDK includes an optional query builder for type-safe query construction:
|
|
|
|
```go
|
|
import "github.com/neuraldb/neuraldb-go/qb"
|
|
|
|
// Build a hybrid query
|
|
query, args := qb.New().
|
|
Select("id", "name", "price").
|
|
Expr("1 - (embedding <=> $?) AS similarity", queryVector).
|
|
From("products").
|
|
Where(qb.Eq("category", "electronics")).
|
|
Where(qb.GTE("price", 50)).
|
|
Where(qb.LTE("price", 500)).
|
|
Where(qb.GT("stock", 0)).
|
|
OrderByExpr("embedding <=> $?", queryVector).
|
|
Limit(20).
|
|
Build()
|
|
|
|
rows, err := pool.Query(ctx, query, args...)
|
|
```
|
|
|
|
## Batch Operations
|
|
|
|
```go
|
|
import "github.com/jackc/pgx/v5"
|
|
|
|
func BatchInsert(ctx context.Context, pool *neuraldb.Pool, docs []Document) error {
|
|
batch := &pgx.Batch{}
|
|
|
|
for _, doc := range docs {
|
|
batch.Queue(
|
|
`INSERT INTO documents (content, embedding) VALUES ($1, $2)`,
|
|
doc.Content, doc.Embedding,
|
|
)
|
|
}
|
|
|
|
results := pool.SendBatch(ctx, batch)
|
|
defer results.Close()
|
|
|
|
for range docs {
|
|
_, err := results.Exec()
|
|
if err != nil {
|
|
return fmt.Errorf("batch insert error: %w", err)
|
|
}
|
|
}
|
|
|
|
return results.Close()
|
|
}
|
|
```
|
|
|
|
## Transactions
|
|
|
|
```go
|
|
func TransactionalInsert(ctx context.Context, pool *neuraldb.Pool, docs []Document) error {
|
|
return pool.BeginTxFunc(ctx, pgx.TxOptions{
|
|
IsoLevel: pgx.ReadCommitted,
|
|
}, func(tx pgx.Tx) error {
|
|
for _, doc := range docs {
|
|
_, err := tx.Exec(ctx,
|
|
`INSERT INTO documents (content, embedding) VALUES ($1, $2)`,
|
|
doc.Content, doc.Embedding,
|
|
)
|
|
if err != nil {
|
|
return err // auto-rolled back by BeginTxFunc
|
|
}
|
|
}
|
|
|
|
_, err := tx.Exec(ctx,
|
|
`UPDATE stats SET doc_count = doc_count + $1`,
|
|
len(docs),
|
|
)
|
|
return err
|
|
})
|
|
}
|
|
```
|
|
|
|
## Scanning Results into Structs
|
|
|
|
```go
|
|
import "github.com/jackc/pgx/v5/pgxscan"
|
|
|
|
type Product struct {
|
|
ID string `db:"id"`
|
|
Name string `db:"name"`
|
|
Price float64 `db:"price"`
|
|
Similarity float64 `db:"similarity"`
|
|
}
|
|
|
|
func SearchProducts(ctx context.Context, pool *neuraldb.Pool, qv types.Vector) ([]Product, error) {
|
|
var products []Product
|
|
err := pgxscan.Select(ctx, pool, &products, `
|
|
SELECT id, name, price, 1 - (embedding <=> $1) AS similarity
|
|
FROM products
|
|
WHERE available = true
|
|
ORDER BY embedding <=> $1
|
|
LIMIT 10
|
|
`, qv)
|
|
return products, err
|
|
}
|
|
```
|
|
|
|
## Context and Cancellation
|
|
|
|
All SDK methods accept a `context.Context`. Use it for timeout and cancellation:
|
|
|
|
```go
|
|
// Set a 5-second query deadline
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
|
|
rows, err := pool.Query(ctx, "SELECT ...", args...)
|
|
```
|
|
|
|
## Error Handling
|
|
|
|
```go
|
|
import (
|
|
"github.com/jackc/pgx/v5/pgconn"
|
|
"errors"
|
|
)
|
|
|
|
_, err := pool.Exec(ctx, "INSERT INTO ...", args...)
|
|
if err != nil {
|
|
var pgErr *pgconn.PgError
|
|
if errors.As(err, &pgErr) {
|
|
switch pgErr.Code {
|
|
case "23505": // unique_violation
|
|
return ErrDuplicate
|
|
case "23503": // foreign_key_violation
|
|
return ErrForeignKey
|
|
default:
|
|
return fmt.Errorf("database error %s: %s", pgErr.Code, pgErr.Message)
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
```
|