Documentation
¶
Index ¶
- Constants
- type Builder
- func (b *Builder) Count() (int64, error)
- func (b *Builder) OrderBy(field, direction string) *Builder
- func (b *Builder) Page(page int) *Builder
- func (b *Builder) Paginate() (*Result, error)
- func (b *Builder) PerPage(perPage int) *Builder
- func (b *Builder) Query(query string) *Builder
- func (b *Builder) Search() (*Result, error)
- func (b *Builder) Where(field string, value any) *Builder
- type DatabaseDriver
- type Document
- type Driver
- type ElasticsearchConfig
- type ElasticsearchDriver
- func (d *ElasticsearchDriver) BulkIndex(indexName string, docs []Document) error
- func (d *ElasticsearchDriver) Index(indexName string, id any, data core.Data) error
- func (d *ElasticsearchDriver) Remove(indexName string, id any) error
- func (d *ElasticsearchDriver) Search(req Request) (*Result, error)
- type Engine
- type Filter
- type Hit
- type Order
- type Request
- type Result
- type Searchable
Constants ¶
const ( Asc = "asc" Desc = "desc" )
Order direction constants
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Builder ¶
type Builder struct {
// contains filtered or unexported fields
}
Builder provides a fluent API for constructing and executing search queries, inspired by Laravel Scout's query builder.
result, err := engine.For(models.User{}).
Query("john doe").
Where("status", "active").
OrderBy("created_at", search.Desc).
Page(2).PerPage(10).
Search()
func (*Builder) Count ¶
Count returns only the total number of matching documents without fetching any result data. Useful for existence checks or badge counts.
func (*Builder) Paginate ¶
Paginate is a convenience method equivalent to Search. It is provided so call-sites can express intent clearly.
result, err := builder.Page(p).PerPage(20).Paginate()
type DatabaseDriver ¶
type DatabaseDriver struct {
// CaseSensitive switches from ILIKE to LIKE when true.
// Default is false (case-insensitive).
CaseSensitive bool
// IDField is the primary-key column name used in the SELECT and COUNT
// queries. Defaults to "id".
IDField string
}
DatabaseDriver implements Driver using the existing PostgreSQL database. It builds ILIKE (case-insensitive LIKE) conditions across the supplied fields and executes them via the mb query builder.
No separate index is maintained – the source of truth is the database itself. Index/Remove/BulkIndex are no-ops for this driver.
Usage:
driver := search.NewDatabaseDriver()
engine := search.New(driver)
result, err := engine.For(models.User{}).
Query("alice").
Where("status", "active").
Page(1).PerPage(20).
Search()
func NewDatabaseDriver ¶
func NewDatabaseDriver() *DatabaseDriver
NewDatabaseDriver creates a DatabaseDriver with sensible defaults.
func (*DatabaseDriver) BulkIndex ¶
func (d *DatabaseDriver) BulkIndex(_ string, _ []Document) error
BulkIndex is a no-op for the database driver.
func (*DatabaseDriver) Remove ¶
func (d *DatabaseDriver) Remove(_ string, _ any) error
Remove is a no-op for the database driver.
func (*DatabaseDriver) Search ¶
func (d *DatabaseDriver) Search(req Request) (*Result, error)
Search builds and executes two SQL queries:
- A COUNT query to obtain the total number of matching rows.
- A SELECT <id> query to obtain the paginated primary-key list.
The caller can then hydrate full models using mb.GetModelByID[T].
type Driver ¶
type Driver interface {
// Search executes a query and returns paginated hits together with
// the total number of matching documents.
Search(req Request) (*Result, error)
// Index adds or replaces a single document in the search index.
// For the database driver this is a no-op – the source of truth is
// already the database.
Index(indexName string, id any, data core.Data) error
// Remove deletes a document from the search index.
Remove(indexName string, id any) error
// BulkIndex adds or replaces multiple documents in one operation.
BulkIndex(indexName string, docs []Document) error
}
Driver is the interface every search backend must satisfy.
Implementations are provided for PostgreSQL (DatabaseDriver) and Elasticsearch (ElasticsearchDriver). Custom drivers (e.g. Meilisearch, Typesense, Algolia) can be added by satisfying this interface.
type ElasticsearchConfig ¶
type ElasticsearchConfig struct {
// Host is the base URL of the Elasticsearch node, e.g. "http://localhost:9200".
Host string
// Username and Password are used for HTTP Basic Auth (optional).
Username string
Password string // #nosec G117 -- Legitimate password field for Elasticsearch HTTP Basic Auth config
// Timeout for individual HTTP requests (default: 10 s).
Timeout time.Duration
}
ElasticsearchConfig holds connection settings for the Elasticsearch cluster.
type ElasticsearchDriver ¶
type ElasticsearchDriver struct {
// contains filtered or unexported fields
}
ElasticsearchDriver implements Driver against an Elasticsearch 7.x/8.x cluster using plain HTTP requests – no external client library required.
Indexing workflow (mirrors Laravel Scout):
- After creating/updating a model call engine.IndexModel(model).
- After deleting a model call engine.RemoveModel(model).
- To re-index a whole collection use engine.BulkIndex(models).
Search uses a multi-match bool query with optional term filters and simple field sorting.
Usage:
cfg := search.ElasticsearchConfig{Host: "http://localhost:9200"}
engine := search.New(search.NewElasticsearchDriver(cfg))
// Index a model
engine.IndexModel(user)
// Search
result, err := engine.For(models.User{}).
Query("alice").
Where("status", "active").
Page(1).PerPage(20).
Search()
func NewElasticsearchDriver ¶
func NewElasticsearchDriver(cfg ElasticsearchConfig) *ElasticsearchDriver
NewElasticsearchDriver creates an ElasticsearchDriver with the supplied configuration. The Host field is normalised so bare addresses like "localhost:9200" or "es:9200" are automatically prefixed with "http://".
func (*ElasticsearchDriver) BulkIndex ¶
func (d *ElasticsearchDriver) BulkIndex(indexName string, docs []Document) error
BulkIndex uses the ES _bulk API for efficient multi-document indexing.
type Engine ¶
type Engine struct {
// contains filtered or unexported fields
}
Engine wraps a Driver and is the main entry point for all search operations. Create one Engine per driver (database, Elasticsearch, …) and reuse it throughout the application.
Basic usage:
engine := search.New(search.NewDatabaseDriver())
result, err := engine.For(models.User{}).
Query("alice").
Where("status", "active").
Page(1).PerPage(20).
Search()
func (*Engine) BulkIndex ¶
func (e *Engine) BulkIndex(models []Searchable) error
BulkIndex indexes a slice of Searchable models in a single operation. All models must share the same index (i.e. be of the same type).
func (*Engine) For ¶
func (e *Engine) For(s Searchable) *Builder
For returns a Builder pre-configured from a Searchable model. The index name and searchable fields are derived from the model automatically.
engine.For(models.User{}).Query("alice").Search()
func (*Engine) Index ¶
Index returns a Builder scoped to the given index name. Pass the searchable fields explicitly when you cannot (or prefer not to) create a full Searchable implementation.
engine.Index("users", "fullname", "email").Query("bob").Search()
func (*Engine) IndexModel ¶
func (e *Engine) IndexModel(s Searchable) error
IndexModel adds or updates a single Searchable model in the search index. For the database driver this is a no-op.
func (*Engine) RemoveModel ¶
func (e *Engine) RemoveModel(s Searchable) error
RemoveModel removes a single Searchable model from the search index.
type Hit ¶
type Hit struct {
// ID is the primary key of the matched document.
ID any
// Score is the relevance score. The database driver always sets this
// to 1.0; the Elasticsearch driver uses the native _score value.
Score float64
// Data contains the indexed document fields as returned by
// Elasticsearch. The database driver leaves this nil – callers should
// hydrate full models from the database using the ID.
Data core.Data
}
Hit is a single document returned by a search operation.
type Request ¶
type Request struct {
// Index is the table name (DatabaseDriver) or ES index name
// (ElasticsearchDriver).
Index string
// Fields lists the columns / document fields to match the Query against.
// Used by the DatabaseDriver for ILIKE conditions and by the
// ElasticsearchDriver for multi-match queries.
Fields []string
// Query is the full-text search string supplied by the caller.
Query string
// Filters are additional equality conditions applied after the
// full-text match.
Filters []Filter
// Order controls result sorting.
Order Order
// Page is the 1-based page number.
Page int
// PerPage is the maximum number of results to return per page.
PerPage int
}
Request carries every parameter needed to execute a single search operation.
type Result ¶
type Result struct {
// Total is the number of matching documents before pagination.
Total int64
// Page is the current page number (mirrors the request).
Page int
// PerPage is the page size (mirrors the request).
PerPage int
// Hits contains the matched documents for the current page.
Hits []Hit
}
Result is the complete response for one search operation.
type Searchable ¶
type Searchable interface {
// SearchIndex returns the logical name of the search index.
// For the DatabaseDriver this is the table name.
// For the ElasticsearchDriver this is the ES index name.
SearchIndex() string
// SearchKey returns the unique identifier used to address the document
// inside the index (primary key of the model).
SearchKey() any
// SearchableFields returns the list of column / field names that the
// DatabaseDriver will apply ILIKE conditions against when a Query
// string is present in the Request.
SearchableFields() []string
// ToSearchDocument returns a flat map of the data that should be
// written to the Elasticsearch index when indexing this model.
ToSearchDocument() core.Data
}
Searchable is the interface a model must implement to participate in search operations. It is analogous to Laravel Scout's Searchable trait.
Implement it on any domain model to make it indexable and searchable:
func (u User) SearchIndex() string { return "users" }
func (u User) SearchKey() any { return u.ID }
func (u User) SearchableFields() []string { return []string{"fullname", "email", "phone"} }
func (u User) ToSearchDocument() core.Data {
return core.Data{
"id": u.ID,
"fullname": u.Fullname,
"email": u.Email,
"phone": u.Phone,
"status": string(u.Status),
}
}