Documentation
¶
Overview ¶
Read/write pool separation for the HelixKnowledge skill graph system.
ReadWritePool wraps two *sql.DB pools — a primary for writes and an optional replica for reads. When no replica is configured, all operations are routed to the primary, ensuring the system works identically in single-node deployments.
Usage:
pool, err := db.NewReadWritePool(cfg.Database)
if err != nil {
log.Fatal(err)
}
defer pool.Close()
// Writes go to primary.
_, err = pool.Write().ExecContext(ctx, "INSERT INTO skills ...")
// Reads go to replica (or primary if no replica).
row := pool.Read().QueryRowContext(ctx, "SELECT ...")
Package db provides PostgreSQL connectivity, migration management, vector search helpers, audit logging, and embedding providers for the HelixKnowledge skill graph system.
All functions accept context.Context as the first parameter and use zap for structured logging. Errors are wrapped with descriptive messages.
Index ¶
- Constants
- func AssertEmbeddingDimension(ctx context.Context, pool *Pool, table, column string, wantDim int) error
- func BatchEmbedAllSkills(ctx context.Context, pool *Pool, provider EmbeddingProvider, ...) error
- func BatchEmbedSkills(ctx context.Context, pool *Pool, skillIDs []uuid.UUID, ...) error
- func ClearSkillEmbedding(ctx context.Context, pool *Pool, skillID uuid.UUID) error
- func CurrentMigrationVersion(ctx context.Context, pool *Pool) (int64, error)
- func EmbedAsync(ctx context.Context, embedder Embedder, texts []string, maxConcurrency int) <-chan AsyncEmbedResult
- func EmbedBatch(ctx context.Context, embedder Embedder, texts []string, batchSize int) ([][]float32, error)
- func LogDependencyChange(ctx context.Context, pool *Pool, event string, skillID uuid.UUID, ...) error
- func LogEvent(ctx context.Context, pool *Pool, event string, skillID *uuid.UUID, ...) error
- func LogEventWithDetails(ctx context.Context, pool *Pool, event string, skillID *uuid.UUID, ...) error
- func LogEvidenceChange(ctx context.Context, pool *Pool, event string, skillID uuid.UUID, ...) error
- func LogSkillChange(ctx context.Context, pool *Pool, event string, skillID uuid.UUID, ...) error
- func LogSystemEvent(ctx context.Context, pool *Pool, event string, details map[string]interface{}) error
- func Migrate(ctx context.Context, pool *Pool, migrationsDir string) error
- func MigrateDown(ctx context.Context, pool *Pool, migrationsDir string, n int) error
- func MigrateDownFS(ctx context.Context, pool *Pool, fsys fs.FS, n int) error
- func MigrateFS(ctx context.Context, pool *Pool, fsys fs.FS) error
- func PruneAuditLog(ctx context.Context, pool *Pool, olderThan time.Duration) (int64, error)
- func QueryColumnVectorDimension(ctx context.Context, pool *Pool, table, column string) (int, error)
- func StoreEvidenceEmbedding(ctx context.Context, pool *Pool, evidenceID uuid.UUID, ...) error
- func StoreSkillEmbedding(ctx context.Context, pool *Pool, skillID uuid.UUID, embedding pgvector.Vector) error
- func VectorIndexStats(ctx context.Context, pool *Pool, table string) (indexedCount int64, indexSizeBytes int64, err error)
- func WaitForVectorIndexReady(ctx context.Context, pool *Pool, table string, timeout time.Duration) error
- type AsyncEmbedResult
- type AuditLogEntry
- func AuditLogForEvent(ctx context.Context, pool *Pool, event string, limit int) ([]AuditLogEntry, error)
- func AuditLogForSkill(ctx context.Context, pool *Pool, skillID uuid.UUID, limit int) ([]AuditLogEntry, error)
- func RecentAuditLog(ctx context.Context, pool *Pool, limit int) ([]AuditLogEntry, error)
- type BatchEmbedConfig
- type Embedder
- type EmbeddingProgress
- type EmbeddingProvider
- type LocalEmbedder
- type OpenAIEmbedder
- type Pool
- func (p *Pool) Close()
- func (p *Pool) Exec(ctx context.Context, sql string, args ...any) (pgconn.CommandTag, error)
- func (p *Pool) Health(ctx context.Context) error
- func (p *Pool) Inner() *pgxpool.Pool
- func (p *Pool) Query(ctx context.Context, sql string, args ...any) (pgx.Rows, error)
- func (p *Pool) QueryRow(ctx context.Context, sql string, args ...any) pgx.Row
- func (p *Pool) Stats() pgxpool.Stat
- func (p *Pool) StdlibDB() *sql.DB
- func (p *Pool) WithTx(ctx context.Context, fn TxFn) error
- type PoolStat
- type PoolStats
- type ReadWritePool
- type TxFn
- type VectorSearchResult
- func FindSimilarEvidences(ctx context.Context, pool *Pool, embedding pgvector.Vector, limit int) ([]VectorSearchResult, error)
- func FindSimilarSkills(ctx context.Context, pool *Pool, embedding pgvector.Vector, limit int) ([]VectorSearchResult, error)
- func HybridSearch(ctx context.Context, pool *Pool, table string, embedding pgvector.Vector, ...) ([]VectorSearchResult, error)
- func VectorSearch(ctx context.Context, pool *Pool, table string, embedding pgvector.Vector, ...) ([]VectorSearchResult, error)
- func VectorSearchFiltered(ctx context.Context, pool *Pool, table string, embedding pgvector.Vector, ...) ([]VectorSearchResult, error)
Constants ¶
const ( AuditEventSkillCreated = "skill.created" AuditEventSkillUpdated = "skill.updated" AuditEventSkillDeleted = "skill.deleted" AuditEventSkillValidated = "skill.validated" AuditEventSkillActivated = "skill.activated" AuditEventSkillDeprecated = "skill.deprecated" AuditEventDepAdded = "dependency.added" AuditEventDepRemoved = "dependency.removed" AuditEventEvidenceAdded = "evidence.added" AuditEventEvidenceValidated = "evidence.validated" AuditEventResourceAdded = "resource.added" AuditEventResourceFetched = "resource.fetched" AuditEventExpansionStarted = "expansion.started" AuditEventExpansionCompleted = "expansion.completed" AuditEventExpansionFailed = "expansion.failed" AuditEventLearningStarted = "learning.started" AuditEventLearningCompleted = "learning.completed" AuditEventMigrationApplied = "migration.applied" AuditEventMigrationRolledBack = "migration.rolled_back" )
Common audit event names used throughout the skill graph system.
Variables ¶
This section is empty.
Functions ¶
func AssertEmbeddingDimension ¶
func AssertEmbeddingDimension(ctx context.Context, pool *Pool, table, column string, wantDim int) error
AssertEmbeddingDimension is the G10 boot-time safety guard (§11.4.201): it reads the REAL pgvector dimension declared on table.column from the live database (via QueryColumnVectorDimension — the authoritative source) and asserts it equals wantDim, the configured embedder's OWN declared/output dimension (Embedder.Dimensions()). A mismatch returns a FAIL-CLOSED error naming BOTH dimensions and the source of each, so a misconfigured embedder can never silently corrupt every StoreSkillEmbedding/StoreEvidenceEmbedding insert and every KNN search (VectorSearch/HybridSearch) against a column sized for a different provider. Callers (cmd/server, cmd/worker) MUST treat a non-nil return as fatal — this is a hard stop, never a warn-and-continue (research/g10_embedding_provider_design.md §2.2 point 3).
func BatchEmbedAllSkills ¶
func BatchEmbedAllSkills( ctx context.Context, pool *Pool, provider EmbeddingProvider, cfg BatchEmbedConfig, ) error
BatchEmbedAllSkills finds all skills without embeddings and generates them using the provided provider. This is useful for initial population or after a provider/model change.
func BatchEmbedSkills ¶
func BatchEmbedSkills( ctx context.Context, pool *Pool, skillIDs []uuid.UUID, provider EmbeddingProvider, cfg BatchEmbedConfig, ) error
BatchEmbedSkills generates and stores embeddings for the given skill IDs. Skills are processed in configurable batches with rate limiting to avoid overwhelming the embedding provider.
Partial failures are tolerated: individual skill embedding errors are logged and the function continues with remaining skills. The returned error is non-nil only if the entire operation is aborted (e.g. context cancellation).
The provider is called with each skill's content (name + description + content concatenated) to generate the embedding vector.
func ClearSkillEmbedding ¶
ClearSkillEmbedding sets a skill's embedding column to NULL. This is the honest-degrade counterpart to StoreSkillEmbedding, used by internal/skill.Store's embedWriteThrough (§G59 F3, code-review remediation) on ALL FOUR of its embed failure/skip paths -- empty embed text, an Embed() error, Embed() returning an empty/unusable vector, and (F3 round-2 remediation) Embed() succeeding but the subsequent StoreSkillEmbedding call itself failing (e.g. a dimension mismatch between the returned vector and this table's `embedding vector(768)` column) -- so a re-embed attempted during an ON CONFLICT (name) DO UPDATE never leaves a STALE vector -- one computed from the skill's PREVIOUS content -- silently serving vector-KNN matches against content that no longer exists. "A failed store means the clear would fail too" does NOT generally hold: this UPDATE writes a literal NULL, which has no dimension (or other content-shaped constraint) to violate, so it succeeds independently of why the preceding store failed. Called unconditionally on every one of those four paths regardless of whether the skill was freshly inserted or updated: on a fresh insert the column is already NULL by column default, so this is a harmless no-op there.
func CurrentMigrationVersion ¶
CurrentMigrationVersion returns the highest applied migration version, or -1 if no migrations have been applied.
func EmbedAsync ¶
func EmbedAsync( ctx context.Context, embedder Embedder, texts []string, maxConcurrency int, ) <-chan AsyncEmbedResult
EmbedAsync embeds each text in a separate goroutine (up to maxConcurrency at a time) and returns the results via a channel. The channel is closed when all work is complete.
Callers should range over the returned channel until closed.
func EmbedBatch ¶
func EmbedBatch( ctx context.Context, embedder Embedder, texts []string, batchSize int, ) ([][]float32, error)
EmbedBatch breaks a large text slice into smaller batches and embeds them sequentially. If a single batch fails the entire operation fails.
func LogDependencyChange ¶
func LogDependencyChange( ctx context.Context, pool *Pool, event string, skillID uuid.UUID, dependsOn uuid.UUID, relationType string, ) error
LogDependencyChange records a dependency addition or removal.
func LogEvent ¶
func LogEvent( ctx context.Context, pool *Pool, event string, skillID *uuid.UUID, details json.RawMessage, ) error
LogEvent inserts a single event into the audit_log table.
event should be one of the AuditEvent* constants. skillID may be nil for system-level events. details may be nil if no extra JSON data is associated with the event.
func LogEventWithDetails ¶
func LogEventWithDetails( ctx context.Context, pool *Pool, event string, skillID *uuid.UUID, details map[string]interface{}, ) error
LogEventWithDetails is a convenience helper that marshals a details map into JSON and then calls LogEvent.
func LogEvidenceChange ¶
func LogEvidenceChange( ctx context.Context, pool *Pool, event string, skillID uuid.UUID, evidenceID uuid.UUID, extra map[string]interface{}, ) error
LogEvidenceChange records evidence addition or validation.
func LogSkillChange ¶
func LogSkillChange( ctx context.Context, pool *Pool, event string, skillID uuid.UUID, changes map[string]interface{}, ) error
LogSkillChange records a skill create/update/delete event.
func LogSystemEvent ¶
func LogSystemEvent( ctx context.Context, pool *Pool, event string, details map[string]interface{}, ) error
LogSystemEvent records a system-level event with no associated skill.
func Migrate ¶
Migrate runs all pending "up" migrations found in migrationsDir. It tracks applied versions in the schema_migrations table (created automatically).
Migration files must be named as NNN_description.up.sql and NNN_description.down.sql where NNN is a zero-padded version number. Migrations are executed in version order inside transactions.
Migrate is a thin wrapper over MigrateFS that reads the migrations from an on-disk directory (os.DirFS). Production startup (cmd/server) uses MigrateFS with the embedded migrations FS so migrations apply independently of the process working directory (§G23); this string-path form is retained for the test suites that drive a controlled on-disk migration subset.
func MigrateDown ¶
MigrateDown rolls back the last n applied migrations. Use with caution; in production prefer targeted down migrations during maintenance windows.
Like Migrate, this is a thin wrapper over MigrateDownFS reading from an on-disk directory (os.DirFS); the string-path form is retained for the test suites.
func MigrateDownFS ¶
MigrateDownFS rolls back the last n applied migrations, reading the down migrations from fsys (an io/fs.FS rooted at the migrations directory). See MigrateFS for the FS-shape contract.
func MigrateFS ¶
MigrateFS runs all pending "up" migrations discovered in fsys (an io/fs.FS). Both an embed.FS (the compiled-in migrations, cwd-independent) and os.DirFS(dir) (an on-disk directory) satisfy fs.FS, so the same discovery and apply logic serves the production embedded path AND the on-disk test path.
fsys MUST be rooted at the migrations directory itself, so its entries are the bare NNN_description.{up,down}.sql filenames (skillsystem.MigrationsFS and os.DirFS(dir) both satisfy this).
It tracks applied versions in the schema_migrations table (created automatically) and applies each pending migration in version order inside a transaction. Any discovery, read, or apply failure is returned to the caller UNMODIFIED-in-severity: it is a hard error, never swallowed — cmd/server treats it as fatal and refuses to bind the port (§G23 fail-closed).
func PruneAuditLog ¶
PruneAuditLog deletes audit log entries older than the given duration.
func QueryColumnVectorDimension ¶
QueryColumnVectorDimension reads the ACTUAL pgvector dimension declared on table.column from the live database's system catalog (pg_attribute joined to pg_class, rendered via format_type) — the single authoritative source of truth for "what dimension is this column really sized for" (§11.4.201). It is NEVER derived from a hardcoded Go constant or from the embedding provider's own configuration.
Returns an error if the column does not exist (or has been dropped), or if it exists but is not a dimensioned pgvector column: a bare "vector" with no typmod accepts a vector of ANY length, which would defeat the entire purpose of a boot-time dimension assertion, so that case is rejected rather than silently treated as "no constraint" (§11.4.201: an unresolvable/ambiguous signal refuses, it never assumes OK).
func StoreEvidenceEmbedding ¶
func StoreEvidenceEmbedding( ctx context.Context, pool *Pool, evidenceID uuid.UUID, embedding pgvector.Vector, ) error
StoreEvidenceEmbedding updates the embedding vector for a specific evidence record.
func StoreSkillEmbedding ¶
func StoreSkillEmbedding( ctx context.Context, pool *Pool, skillID uuid.UUID, embedding pgvector.Vector, ) error
StoreEmbedding updates the embedding vector for a specific skill.
func VectorIndexStats ¶
func VectorIndexStats( ctx context.Context, pool *Pool, table string, ) (indexedCount int64, indexSizeBytes int64, err error)
VectorIndexStats returns the number of indexed vectors and index size for the given table's embedding column.
func WaitForVectorIndexReady ¶
func WaitForVectorIndexReady( ctx context.Context, pool *Pool, table string, timeout time.Duration, ) error
WaitForVectorIndexReady polls until the pgvector HNSW index for the given table is ready (indisvalid = true), or until the timeout.
Error handling distinguishes the two cases the previous swallow-everything `continue` conflated (§11.4.201 — a guard must assert the real condition, not a proxy that any failure satisfies): pgx.ErrNoRows means the index does not exist yet and polling should continue, whereas any OTHER query error means the query itself (or the connection) is broken and is surfaced immediately rather than silently spun on until the deadline. A per-iteration query cancelled by this function's own deadline is not a broken query — it is deferred to the ctx.Done() branch so the timeout is reported once, with its clearer message.
Types ¶
type AsyncEmbedResult ¶
AsyncEmbedResult holds the result of a single async embedding job.
type AuditLogEntry ¶
type AuditLogEntry struct {
Timestamp time.Time `db:"ts"`
Event string `db:"event"`
SkillID *uuid.UUID `db:"skill_id"`
Details json.RawMessage `db:"details"`
}
AuditLogEntry mirrors models.AuditLogEntry for database scanning.
func AuditLogForEvent ¶
func AuditLogForEvent( ctx context.Context, pool *Pool, event string, limit int, ) ([]AuditLogEntry, error)
AuditLogForEvent returns audit entries filtered to a specific event type.
func AuditLogForSkill ¶
func AuditLogForSkill( ctx context.Context, pool *Pool, skillID uuid.UUID, limit int, ) ([]AuditLogEntry, error)
AuditLogForSkill returns audit entries filtered to a specific skill.
func RecentAuditLog ¶
RecentAuditLog returns the most recent audit log entries, up to limit.
type BatchEmbedConfig ¶
type BatchEmbedConfig struct {
// BatchSize is the number of skills to embed per provider call.
// A value <= 0 defaults to 100.
BatchSize int
// RequestsPerSecond caps the rate of embedding-provider API calls.
// A value <= 0 defaults to 10 (conservative).
RequestsPerSecond float64
// OnProgress is an optional callback invoked after each batch.
OnProgress EmbeddingProgress
}
BatchEmbedConfig controls batch-embedding behaviour.
type Embedder ¶
type Embedder interface {
// Embed returns a vector for each input text. The returned slice has
// the same length as texts. Each inner slice has Dimensions() elements.
Embed(ctx context.Context, texts []string) ([][]float32, error)
// Dimensions returns the embedding vector size (e.g. 768, 1536).
Dimensions() int
}
Embedder converts text into dense vector embeddings suitable for storage in pgvector columns and similarity search.
func NewEmbedderFromConfig ¶
func NewEmbedderFromConfig(cfg config.EmbeddingConfig) (Embedder, error)
NewEmbedderFromConfig creates the appropriate Embedder implementation based on the embedding provider configured in the application config.
Supported providers:
- "openai" -> OpenAIEmbedder
- "local" -> LocalEmbedder
NewEmbedderFromConfig is fail-closed on an incompletely-specified provider: it errors rather than construct an Embedder that is guaranteed to fail its first real request. This is the SINGLE source of truth for "is this provider fully configured" -- callers (e.g. internal/mcp.NewMCPServer's §G29 hybrid-search wiring) determine whether to attach an embedder solely from this function's error return, never by re-deriving the same per-provider policy a second time (Fable code-review remediation, finding 6a: a duplicated "is it configured" check is a second source of truth that can silently drift from this one).
A positive cfg.Dimensions is a CONSTRUCTION INVARIANT enforced here, once, for every provider (Fable round-2 finding F4): both OpenAIEmbedder.Embed and LocalEmbedder.Embed unconditionally reject any response whose embedding length does not equal e.dimensions -- a zero (or negative) e.dimensions would make every single Embed call fail that check, since a real provider response is never zero-length. config.Load()'s own validate() already rejects embedding.dimensions <= 0 before a *Config ever reaches this factory, but this factory is the SOLE production constructor (see above) and MUST NOT rely solely on that upstream, call-site-external check to hold an invariant its own returned Embedder depends on to ever succeed -- the check belongs at the point of construction.
type EmbeddingProgress ¶
type EmbeddingProgress func(succeeded, failed, total int)
EmbeddingProgress is called after each batch completes to report progress. succeeded is the cumulative number of skills embedded so far; failed is the cumulative number of failures; total is the total number of skills requested.
type EmbeddingProvider ¶
type EmbeddingProvider = Embedder
EmbeddingProvider is an alias for the Embedder interface in this package, used to clarify the batch-embedding contract.
type LocalEmbedder ¶
type LocalEmbedder struct {
// contains filtered or unexported fields
}
LocalEmbedder calls a local embedding model server via HTTP. The expected endpoint accepts a JSON POST body { "texts": [...] } and returns { "embeddings": [[...], ...] }.
func NewLocalEmbedder ¶
func NewLocalEmbedder(cfg config.EmbeddingConfig) *LocalEmbedder
NewLocalEmbedder creates a local embedder from configuration.
func (*LocalEmbedder) Dimensions ¶
func (e *LocalEmbedder) Dimensions() int
Dimensions returns the configured embedding dimensionality.
func (*LocalEmbedder) SetHTTPClient ¶
func (e *LocalEmbedder) SetHTTPClient(client *http.Client)
SetHTTPClient replaces the default HTTP client.
type OpenAIEmbedder ¶
type OpenAIEmbedder struct {
// contains filtered or unexported fields
}
OpenAIEmbedder calls the OpenAI Embeddings API.
func NewOpenAIEmbedder ¶
func NewOpenAIEmbedder(cfg config.EmbeddingConfig) *OpenAIEmbedder
NewOpenAIEmbedder creates an OpenAI embedder from configuration.
func (*OpenAIEmbedder) Dimensions ¶
func (e *OpenAIEmbedder) Dimensions() int
Dimensions returns the configured embedding dimensionality.
func (*OpenAIEmbedder) SetHTTPClient ¶
func (e *OpenAIEmbedder) SetHTTPClient(client *http.Client)
SetHTTPClient replaces the default HTTP client (useful for tests or custom transport).
type Pool ¶
type Pool struct {
// contains filtered or unexported fields
}
Pool wraps *pgxpool.Pool with pgvector type support and convenience methods for the skill graph application.
func New ¶
func New(cfg config.DatabaseConfig) (*Pool, error)
New creates a new PostgreSQL connection pool from the provided configuration. It registers the pgvector type with pgx and verifies connectivity with a ping before returning.
The caller is responsible for calling Close() when the pool is no longer needed.
func (*Pool) Exec ¶
Exec is a thin wrapper around pgxpool.Pool.Exec. It returns the command tag (which carries the number of rows affected) alongside any error, mirroring the pgx-native signature so callers can inspect RowsAffected.
func (*Pool) Health ¶
Health performs a connectivity check on the pool. Returns nil if the database is reachable, otherwise an error.
type PoolStat ¶
type PoolStat struct {
MaxOpenConnections int `json:"max_open_connections"`
OpenConnections int `json:"open_connections"`
InUse int `json:"in_use"`
Idle int `json:"idle"`
WaitCount int64 `json:"wait_count"`
WaitDuration time.Duration `json:"wait_duration"`
MaxIdleClosed int64 `json:"max_idle_closed"`
MaxLifetimeClosed int64 `json:"max_lifetime_closed"`
}
PoolStat holds the statistics for a single database pool.
type PoolStats ¶
type PoolStats struct {
Primary PoolStat `json:"primary"`
Replica PoolStat `json:"replica"`
HasReplica bool `json:"has_replica"`
}
PoolStats reports the state of both the primary and replica pools.
type ReadWritePool ¶
type ReadWritePool struct {
// contains filtered or unexported fields
}
ReadWritePool separates read and write traffic across two database connection pools. The primary pool handles all write operations and serves as fallback when no replica is configured or when the replica is unhealthy.
func NewReadWritePool ¶
func NewReadWritePool(cfg config.DatabaseConfig) (*ReadWritePool, error)
NewReadWritePool creates a ReadWritePool from the provided database configuration. The primary pool is always created. If cfg.Replica.DSN is non-empty, a second pool is opened for the replica.
Both pools are pinged before returning. If the replica ping fails, the system logs a warning and falls back to primary-only mode — it never fails closed on a missing replica.
func (*ReadWritePool) Close ¶
func (p *ReadWritePool) Close() error
Close shuts down both connection pools.
func (*ReadWritePool) Health ¶
func (p *ReadWritePool) Health(ctx context.Context) error
Health checks the primary pool. If a replica is configured, it also checks replica health and replication lag. When the replica is unhealthy, reads are routed to the primary until the next successful health check.
func (*ReadWritePool) Read ¶
func (p *ReadWritePool) Read() *sql.DB
Read returns the database connection for read operations. If a replica is configured and healthy, reads go to the replica. Otherwise they go to the primary.
func (*ReadWritePool) Stats ¶
func (p *ReadWritePool) Stats() PoolStats
Stats returns connection-pool statistics for both pools. If a pool is nil (e.g. in tests), the corresponding stats are zero-valued.
func (*ReadWritePool) Write ¶
func (p *ReadWritePool) Write() *sql.DB
Write returns the primary database connection for write operations. All writes always go to the primary.
type VectorSearchResult ¶
type VectorSearchResult struct {
ID uuid.UUID
Score float64 // cosine similarity (higher = more similar)
}
VectorSearchResult holds a single result from vector similarity search.
func FindSimilarEvidences ¶
func FindSimilarEvidences( ctx context.Context, pool *Pool, embedding pgvector.Vector, limit int, ) ([]VectorSearchResult, error)
FindSimilarEvidences performs vector search against the evidences table.
func FindSimilarSkills ¶
func FindSimilarSkills( ctx context.Context, pool *Pool, embedding pgvector.Vector, limit int, ) ([]VectorSearchResult, error)
FindSimilarSkills performs vector search against the skills table and returns the matching skill IDs with scores.
func HybridSearch ¶
func HybridSearch( ctx context.Context, pool *Pool, table string, embedding pgvector.Vector, keywords string, limit int, ) ([]VectorSearchResult, error)
HybridSearch combines vector similarity and full-text keyword search using Reciprocal Rank Fusion (RRF). The final results are ordered by the RRF score descending.
The keywords string is split on whitespace and used in a tsvector match against the name, title, and description columns.
func VectorSearch ¶
func VectorSearch( ctx context.Context, pool *Pool, table string, embedding pgvector.Vector, limit int, ) ([]VectorSearchResult, error)
VectorSearch performs cosine-similarity search against the embedding column of the given table. Returns up to limit results ordered by similarity descending.
The table must have an embedding column of type vector(N) and an HNSW index for acceptable performance at scale.