Documentation
¶
Overview ¶
Package quark provides a modern, type-safe ORM for Go. It supports multiple SQL dialects and is designed to be framework-agnostic.
Index ¶
- Variables
- func Call(ctx context.Context, provider ClientProvider, procedure string, args ...any) error
- func Notify(ctx context.Context, provider ClientProvider, channel, payload string) error
- func RegisterDialect(name string, d Dialect)
- func RegisterTypeMapper(t reflect.Type, m TypeMapper)
- type AfterCreateHook
- type AfterDeleteHook
- type AfterUpdateHook
- type BaseMiddleware
- type BaseQuery
- type BeforeCreateHook
- type BeforeDeleteHook
- type BeforeUpdateHook
- type CacheConfig
- type CacheStore
- type Client
- func (c *Client) AddForeignKey(ctx context.Context, table, constraintName string, columns []string, ...) error
- func (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error)
- func (c *Client) Close() error
- func (c *Client) CreateIndex(ctx context.Context, table, indexName string, columns []string, unique bool) error
- func (c *Client) Dialect() Dialect
- func (c *Client) Exec(ctx context.Context, query string, args ...any) error
- func (c *Client) GetClient(ctx context.Context) (*Client, error)
- func (c *Client) Migrate(ctx context.Context, models ...any) error
- func (c *Client) Raw() *sql.DB
- func (c *Client) RawQuery(ctx context.Context, query string, args ...any) (*sql.Rows, error)
- func (c *Client) Sync(ctx context.Context, opts SyncOptions, models ...any) error
- func (c *Client) Tx(ctx context.Context, fn func(tx *Tx) error) error
- func (c *Client) Validate(ctx context.Context, model any) error
- func (c *Client) WithOptions(opts ...any) (*Client, error)
- type ClientProvider
- type Cursor
- type Dialect
- type EventBus
- type EventListener
- type EventPayload
- type ExecFunc
- type Executor
- type Expr
- func And(parts ...Expr) Expr
- func Cmp(lhs Expr, op string, rhs Expr) Expr
- func Col(name string) Expr
- func DenseRank() Expr
- func Eq(lhs, rhs Expr) Expr
- func Exists(s *Subquery) Expr
- func Func(name string, args ...Expr) Expr
- func Gt(lhs, rhs Expr) Expr
- func Gte(lhs, rhs Expr) Expr
- func In(lhs Expr, values ...Expr) Expr
- func InSub(lhs Expr, s *Subquery) Expr
- func Lag(col Expr, offset int) Expr
- func Lead(col Expr, offset int) Expr
- func Lit(v any) Expr
- func Lt(lhs, rhs Expr) Expr
- func Lte(lhs, rhs Expr) Expr
- func Ne(lhs, rhs Expr) Expr
- func Not(e Expr) Expr
- func NotExists(s *Subquery) Expr
- func NotIn(lhs Expr, values ...Expr) Expr
- func NotInSub(lhs Expr, s *Subquery) Expr
- func Or(parts ...Expr) Expr
- func Over(inner Expr, w *Window) Expr
- func Rank() Expr
- func RowNumber() Expr
- func Sub(s *Subquery) Expr
- type FieldMeta
- type JSON
- type JoinBuilder
- type Limits
- type LockMode
- type LockOptions
- type MSSQLDialect
- func (m *MSSQLDialect) AlterTableAddColumn(table, column, dataType string) string
- func (m *MSSQLDialect) AlterTableAlterColumn(table, column, newDataType string) string
- func (m *MSSQLDialect) AlterTableDropColumn(table, column string) string
- func (m *MSSQLDialect) BuildProcedureCall(procedure string, argCount int) string
- func (m *MSSQLDialect) BuildRoutineQuery(routine string, argCount int) string
- func (m *MSSQLDialect) CurrentTimestamp() string
- func (m *MSSQLDialect) JSONExtract(column, path string) (string, []any, error)
- func (m *MSSQLDialect) LastInsertIDQuery(table, pkColumn string) string
- func (m *MSSQLDialect) LimitOffset(limit, offset int) string
- func (m *MSSQLDialect) LockSuffix(opts LockOptions) (string, string, error)
- func (d *MSSQLDialect) Name() string
- func (m *MSSQLDialect) Placeholder(index int) string
- func (m *MSSQLDialect) Placeholders(n int) []string
- func (m *MSSQLDialect) Quote(identifier string) string
- func (m *MSSQLDialect) RenameColumn(table, oldName, newName string) string
- func (m *MSSQLDialect) RenameTable(oldName, newName string) string
- func (m *MSSQLDialect) Returning(columns ...string) string
- func (m *MSSQLDialect) SupportsLastInsertID() bool
- func (m *MSSQLDialect) SupportsReturning() bool
- func (m *MSSQLDialect) SupportsTransactionalDDL() bool
- func (m *MSSQLDialect) UpsertSQL(conflictCols, updateCols []string, _ int) string
- type MariaDBDialect
- func (m *MariaDBDialect) AlterTableAlterColumn(table, column, newDataType string) string
- func (m *MariaDBDialect) CreateSequence(name string, start, increment int64) string
- func (m *MariaDBDialect) CreateSystemVersionedTable(table string, columnDefs string) string
- func (m *MariaDBDialect) HistoryBetween(table, from, to string) string
- func (m *MariaDBDialect) HistoryQuery(table string) string
- func (m *MariaDBDialect) JSONExtract(column, path string) (string, []any, error)
- func (m *MariaDBDialect) JSONTable(source, path string, columns ...string) string
- func (m *MariaDBDialect) LastInsertIDQuery(table, pkColumn string) string
- func (m *MariaDBDialect) LimitOffset(limit, offset int) string
- func (m *MariaDBDialect) LockSuffix(opts LockOptions) (string, string, error)
- func (d *MariaDBDialect) Name() string
- func (m *MariaDBDialect) NextVal(sequenceName string) string
- func (m *MariaDBDialect) RenameColumn(table, oldName, newName string) string
- func (m *MariaDBDialect) Returning(columns ...string) string
- func (m *MariaDBDialect) SupportsLastInsertID() bool
- func (m *MariaDBDialect) SupportsReturning() bool
- func (m *MariaDBDialect) SupportsTransactionalDDL() bool
- func (m *MariaDBDialect) UpsertSQL(conflictCols, updateCols []string, argOffset int) string
- type Middleware
- type ModelMeta
- type MySQLDialect
- func (m *MySQLDialect) AlterTableAddColumn(table, column, dataType string) string
- func (m *MySQLDialect) AlterTableAlterColumn(table, column, newDataType string) string
- func (m *MySQLDialect) AlterTableDropColumn(table, column string) string
- func (m *MySQLDialect) BuildProcedureCall(procedure string, argCount int) string
- func (m *MySQLDialect) BuildRoutineQuery(routine string, argCount int) string
- func (m *MySQLDialect) CurrentTimestamp() string
- func (m *MySQLDialect) JSONExtract(column, path string) (string, []any, error)
- func (m *MySQLDialect) LastInsertIDQuery(table, pkColumn string) string
- func (m *MySQLDialect) LimitOffset(limit, offset int) string
- func (m *MySQLDialect) LockSuffix(opts LockOptions) (string, string, error)
- func (d *MySQLDialect) Name() string
- func (m *MySQLDialect) Placeholder(index int) string
- func (m *MySQLDialect) Placeholders(n int) []string
- func (m *MySQLDialect) Quote(identifier string) string
- func (m *MySQLDialect) RenameColumn(table, oldName, newName string) string
- func (m *MySQLDialect) RenameTable(oldName, newName string) string
- func (m *MySQLDialect) Returning(columns ...string) string
- func (m *MySQLDialect) SupportsLastInsertID() bool
- func (m *MySQLDialect) SupportsReturning() bool
- func (m *MySQLDialect) SupportsTransactionalDDL() bool
- func (m *MySQLDialect) UpsertSQL(conflictCols, updateCols []string, _ int) string
- type Nullable
- type Option
- type OracleDialect
- func (o *OracleDialect) AlterTableAddColumn(table, column, dataType string) string
- func (o *OracleDialect) AlterTableAlterColumn(table, column, newDataType string) string
- func (o *OracleDialect) AlterTableDropColumn(table, column string) string
- func (o *OracleDialect) BuildProcedureCall(procedure string, argCount int) string
- func (o *OracleDialect) BuildRoutineQuery(routine string, argCount int) string
- func (o *OracleDialect) CurrentTimestamp() string
- func (o *OracleDialect) JSONExtract(column, path string) (string, []any, error)
- func (o *OracleDialect) LastInsertIDQuery(table, pkColumn string) string
- func (o *OracleDialect) LimitOffset(limit, offset int) string
- func (o *OracleDialect) LockSuffix(opts LockOptions) (string, string, error)
- func (d *OracleDialect) Name() string
- func (o *OracleDialect) Placeholder(index int) string
- func (o *OracleDialect) Placeholders(n int) []string
- func (o *OracleDialect) Quote(identifier string) string
- func (o *OracleDialect) RenameColumn(table, oldName, newName string) string
- func (o *OracleDialect) RenameTable(oldName, newName string) string
- func (o *OracleDialect) Returning(columns ...string) string
- func (o *OracleDialect) SupportsLastInsertID() bool
- func (o *OracleDialect) SupportsReturning() bool
- func (o *OracleDialect) SupportsTransactionalDDL() bool
- func (o *OracleDialect) UpsertSQL(conflictCols, updateCols []string, _ int) string
- type Page
- type PoolOption
- type PostgresDialect
- func (p *PostgresDialect) AlterTableAddColumn(table, column, dataType string) string
- func (p *PostgresDialect) AlterTableAlterColumn(table, column, newDataType string) string
- func (p *PostgresDialect) AlterTableDropColumn(table, column string) string
- func (p *PostgresDialect) BuildProcedureCall(procedure string, argCount int) string
- func (p *PostgresDialect) BuildRoutineQuery(routine string, argCount int) string
- func (p *PostgresDialect) CurrentTimestamp() string
- func (p *PostgresDialect) JSONExtract(column, path string) (string, []any, error)
- func (p *PostgresDialect) LastInsertIDQuery(table, pkColumn string) string
- func (p *PostgresDialect) LimitOffset(limit, offset int) string
- func (p *PostgresDialect) LockSuffix(opts LockOptions) (string, string, error)
- func (d *PostgresDialect) Name() string
- func (p *PostgresDialect) Placeholder(index int) string
- func (p *PostgresDialect) Placeholders(n int) []string
- func (p *PostgresDialect) Quote(identifier string) string
- func (p *PostgresDialect) RenameColumn(table, oldName, newName string) string
- func (p *PostgresDialect) RenameTable(oldName, newName string) string
- func (p *PostgresDialect) Returning(columns ...string) string
- func (p *PostgresDialect) SupportsLastInsertID() bool
- func (p *PostgresDialect) SupportsReturning() bool
- func (p *PostgresDialect) SupportsTransactionalDDL() bool
- func (p *PostgresDialect) UpsertSQL(conflictCols, updateCols []string, _ int) string
- type Query
- func (q *Query[T]) Apply(scopes ...Scope[T]) *Query[T]
- func (q *Query[T]) AsSubquery() (*Subquery, error)
- func (q *Query[T]) Avg(column string) (float64, error)
- func (q *Query[T]) Cache(ttl time.Duration, tags ...string) *Query[T]
- func (q *Query[T]) Count() (int64, error)
- func (q *Query[T]) Create(entity *T) error
- func (q *Query[T]) CreateBatch(entities []*T) error
- func (q *Query[T]) Cursor() (*Cursor[T], error)
- func (q *Query[T]) Delete(entity *T) (int64, error)
- func (q *Query[T]) DeleteBatch(ids []any) (int64, error)
- func (q *Query[T]) DeleteBy() (int64, error)
- func (q *Query[T]) Distinct() *Query[T]
- func (q *Query[T]) Except(other *Query[T]) *Query[T]
- func (q *Query[T]) Find(id any) (T, error)
- func (q *Query[T]) First() (T, error)
- func (q *Query[T]) ForShare() *Query[T]
- func (q *Query[T]) ForUpdate() *Query[T]
- func (q *Query[T]) GroupBy(columns ...string) *Query[T]
- func (q *Query[T]) HardDelete(entity *T) (int64, error)
- func (q *Query[T]) Having(column string, operator string, value any) *Query[T]
- func (q *Query[T]) HavingAggregate(fn, column, operator string, value any) *Query[T]
- func (q *Query[T]) HavingExpr(e Expr) *Query[T]
- func (q *Query[T]) Intersect(other *Query[T]) *Query[T]
- func (q *Query[T]) Iter(fn func(T) error) error
- func (q *Query[T]) Join(table string) *JoinBuilder[T]
- func (q *Query[T]) LeftJoin(table string) *JoinBuilder[T]
- func (q *Query[T]) Limit(n int) *Query[T]
- func (q *Query[T]) List() ([]T, error)
- func (q *Query[T]) Max(column string) (float64, error)
- func (q *Query[T]) Min(column string) (float64, error)
- func (q *Query[T]) MustAsSubquery() *Subquery
- func (q *Query[T]) NoWait() *Query[T]
- func (q *Query[T]) Offset(n int) *Query[T]
- func (q *Query[T]) OnlyTrashed() *Query[T]
- func (q *Query[T]) Or(fn func(*Query[T]) *Query[T]) *Query[T]
- func (q *Query[T]) OrderBy(column string, direction string) *Query[T]
- func (q *Query[T]) Paginate(pageSize, page int) (*Page[T], error)
- func (q *Query[T]) Preload(relations ...string) *Query[T]
- func (q *Query[T]) Restore(entity *T) (int64, error)
- func (q *Query[T]) RightJoin(table string) *JoinBuilder[T]
- func (q *Query[T]) Select(columns ...string) *Query[T]
- func (q *Query[T]) SelectExpr(alias string, e Expr) *Query[T]
- func (q *Query[T]) SkipLocked() *Query[T]
- func (q *Query[T]) Sum(column string) (float64, error)
- func (q *Query[T]) Track() *TrackedQuery[T]
- func (q *Query[T]) Union(other *Query[T]) *Query[T]
- func (q *Query[T]) UnionAll(other *Query[T]) *Query[T]
- func (q *Query[T]) Unscoped() *Query[T]
- func (q *Query[T]) Update(entity *T) (int64, error)
- func (q *Query[T]) UpdateBatch(entities []*T) error
- func (q *Query[T]) UpdateFields(entity *T, fields ...string) (int64, error)
- func (q *Query[T]) UpdateMap(data map[string]any) (int64, error)
- func (q *Query[T]) Upsert(entity *T, conflictCols []string, updateCols []string) error
- func (q *Query[T]) UpsertBatch(entities []*T, conflictCols []string, updateCols []string) error
- func (q *Query[T]) Where(column string, operator string, value any) *Query[T]
- func (q *Query[T]) WhereBetween(column string, start, end any) *Query[T]
- func (q *Query[T]) WhereExpr(e Expr) *Query[T]
- func (q *Query[T]) WhereIn(column string, values []any) *Query[T]
- func (q *Query[T]) WhereJSON(column, path, operator string, value any) *Query[T]
- func (q *Query[T]) WhereNot(column string, operator string, value any) *Query[T]
- func (q *Query[T]) WhereSubquery(column, operator, subquery string) *Query[T]
- func (q *Query[T]) With(name string, sub *Subquery) *Query[T]
- func (q *Query[T]) WithRecursive(name string, sub *Subquery) *Query[T]
- func (q *Query[T]) WithTrashed() *Query[T]
- type QueryEvent
- type QueryFunc
- type QueryObserver
- type QueryRowFunc
- type RelationMeta
- type Routine
- type SQLGuard
- type SQLiteDialect
- func (s *SQLiteDialect) AlterTableAddColumn(table, column, dataType string) string
- func (s *SQLiteDialect) AlterTableAlterColumn(table, column, newDataType string) string
- func (s *SQLiteDialect) AlterTableDropColumn(table, column string) string
- func (s *SQLiteDialect) BuildProcedureCall(procedure string, argCount int) string
- func (s *SQLiteDialect) BuildRoutineQuery(routine string, argCount int) string
- func (s *SQLiteDialect) CurrentTimestamp() string
- func (s *SQLiteDialect) JSONExtract(column, path string) (string, []any, error)
- func (s *SQLiteDialect) LastInsertIDQuery(table, pkColumn string) string
- func (s *SQLiteDialect) LimitOffset(limit, offset int) string
- func (s *SQLiteDialect) LockSuffix(opts LockOptions) (string, string, error)
- func (d *SQLiteDialect) Name() string
- func (s *SQLiteDialect) Placeholder(index int) string
- func (s *SQLiteDialect) Placeholders(n int) []string
- func (s *SQLiteDialect) Quote(identifier string) string
- func (s *SQLiteDialect) RenameColumn(table, oldName, newName string) string
- func (s *SQLiteDialect) RenameTable(oldName, newName string) string
- func (s *SQLiteDialect) Returning(columns ...string) string
- func (s *SQLiteDialect) SupportsLastInsertID() bool
- func (s *SQLiteDialect) SupportsReturning() bool
- func (s *SQLiteDialect) SupportsTransactionalDDL() bool
- func (s *SQLiteDialect) UpsertSQL(conflictCols, updateCols []string, _ int) string
- type Scope
- type Subquery
- type SyncOptions
- type TenantConfig
- type TenantRouter
- type TenantStrategy
- type Tracked
- type TrackedQuery
- type Tx
- type TypeMapper
- type TypeOptions
- type Window
Constants ¶
This section is empty.
Variables ¶
var ( // ErrNotFound indicates that no record was found for the given criteria. ErrNotFound = errors.New("record not found") // ErrInvalidModel indicates that the provided model is invalid or not registered. ErrInvalidModel = errors.New("invalid model") // ErrInvalidQuery indicates that the query is malformed or invalid. ErrInvalidQuery = errors.New("invalid query") // ErrInvalidIdentifier indicates that a table or column identifier is invalid. ErrInvalidIdentifier = errors.New("invalid identifier") // ErrInvalidJSONPath indicates that a JSON path passed to WhereJSON is malformed // or contains characters that could enable SQL injection. Quark accepts dotted // paths shaped like "user.name"; see guard.ValidateJSONPath for the grammar. // Array indexes and engine-specific syntax are out of scope for WhereJSON; // use RawQuery for those. ErrInvalidJSONPath = errors.New("invalid JSON path") // ErrInvalidJoin indicates that a JOIN ... ON clause passed to Join, // LeftJoin, or RightJoin does not match the minimal identifier-only // grammar Quark accepts while a structured Join().On() builder is pending // (Phase 2 AST). See guard.ValidateJoinOn for the grammar; use a // structured Join (when available) or RawQuery for shapes outside it. ErrInvalidJoin = errors.New("invalid JOIN ON clause") // ErrStaleEntity indicates that an optimistic-locking update failed // because the row's version column had been bumped by another writer // since the entity was loaded. The caller should reload the row, replay // the change against the fresh state, and retry — or surface the // conflict to the user. Returned by Update / UpdateFields / Tracked.Save // when the model carries a quark:"version" field. ErrStaleEntity = errors.New("stale entity (optimistic-locking conflict)") // ErrUnsupportedFeature indicates that a feature is not supported by the // active database dialect. Returned by builder methods (e.g. ForUpdate // on SQLite) so callers can branch by dialect or fall back to a different // strategy. The error message includes the dialect name and the feature // being requested. ErrUnsupportedFeature = errors.New("feature not supported by dialect") // ErrDialectNotSupported indicates that the database dialect is not supported. ErrDialectNotSupported = errors.New("dialect not supported") // ErrConnection indicates a database connection error. ErrConnection = errors.New("database connection error") // ErrTimeout indicates that a query timed out. ErrTimeout = errors.New("query timeout") // ErrConstraintViolation indicates a database constraint violation. ErrConstraintViolation = errors.New("constraint violation") )
Common errors returned by quark operations.
var HasPlaceholders = guard.HasPlaceholders
HasPlaceholders checks if a query string contains parameter placeholders.
Functions ¶
func Call ¶
Call executes a stored procedure that does not return a result set, but may modify OUT parameters.
func Notify ¶
func Notify(ctx context.Context, provider ClientProvider, channel, payload string) error
Notify is a helper to trigger a database event (e.g., NOTIFY in Postgres).
func RegisterDialect ¶
RegisterDialect allows developers to register custom database dialects. This enables support for proprietary or non-standard databases.
Example:
quark.RegisterDialect("cockroach", myCockroachDialect)
The registered dialect can then be used with:
client, err := quark.New(db, quark.WithDialect(quark.DetectDialectByName("cockroach")))
func RegisterTypeMapper ¶ added in v0.3.0
func RegisterTypeMapper(t reflect.Type, m TypeMapper)
RegisterTypeMapper registers a custom Go-type → SQL-type mapping for the migration / sync layer. Pointer types are stripped before registration: registering for time.Duration also covers *time.Duration. Re-registering the same type overwrites the previous mapper.
Example: storing google/uuid.UUID as native UUID on Postgres and as a 36-char string on the rest:
quark.RegisterTypeMapper(reflect.TypeOf(uuid.UUID{}), func(d string, _ quark.TypeOptions) string {
if d == "postgres" {
return "UUID"
}
return "VARCHAR(36)"
})
The mapper is consulted by client.Migrate and client.Sync. database/sql's Scanner / driver.Valuer interfaces still apply to the read/write side — a type registered here must also implement those interfaces (or be already supported by the underlying driver) for round-trip to work.
Types ¶
type AfterCreateHook ¶
AfterCreateHook is executed after an entity is created.
type AfterDeleteHook ¶
AfterDeleteHook is executed after an entity is deleted.
type AfterUpdateHook ¶
AfterUpdateHook is executed after an entity is updated.
type BaseMiddleware ¶
type BaseMiddleware struct{}
BaseMiddleware provides default implementations that pass through to the next handler. Embed this in your middleware so you only need to override the methods you care about.
func (BaseMiddleware) WrapExec ¶
func (BaseMiddleware) WrapExec(next ExecFunc) ExecFunc
func (BaseMiddleware) WrapQuery ¶
func (BaseMiddleware) WrapQuery(next QueryFunc) QueryFunc
func (BaseMiddleware) WrapQueryRow ¶
func (BaseMiddleware) WrapQueryRow(next QueryRowFunc) QueryRowFunc
type BaseQuery ¶
type BaseQuery struct {
// contains filtered or unexported fields
}
BaseQuery holds the non-generic state of a database query.
type BeforeCreateHook ¶
BeforeCreateHook is executed before an entity is created.
type BeforeDeleteHook ¶
BeforeDeleteHook is executed before an entity is deleted.
type BeforeUpdateHook ¶
BeforeUpdateHook is executed before an entity is updated.
type CacheConfig ¶
CacheConfig holds the caching parameters for a specific query.
type CacheStore ¶
type CacheStore interface {
// Get retrieves a value from the cache.
Get(ctx context.Context, key string) ([]byte, error)
// Set stores a value in the cache with a specific TTL and associated tags.
Set(ctx context.Context, key string, val []byte, ttl time.Duration, tags ...string) error
// Delete removes a specific key.
Delete(ctx context.Context, key string) error
// InvalidateTags removes all entries associated with the given tags (usually table names).
InvalidateTags(ctx context.Context, tags ...string) error
}
CacheStore defines the contract for any caching backend. Implementations should be provided in separate packages (e.g., quark/cache/redis).
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client is the main entry point for quark ORM operations. It wraps a database connection and provides type-safe query building.
func New ¶
New creates a new quark Client with the given driver name and data source.
Example:
client, err := quark.New("sqlite", "example.db",
quark.WithMaxOpenConns(25),
quark.WithMaxIdleConns(5),
)
For PostgreSQL:
client, err := quark.New("pgx", "postgres://user:pass@localhost/db",
quark.WithMaxOpenConns(25),
)
The dialect is auto-detected from the driver name. You can override it with WithDialect().
func (*Client) AddForeignKey ¶
func (c *Client) AddForeignKey(ctx context.Context, table, constraintName string, columns []string, refTable string, refColumns []string, onDelete, onUpdate string) error
AddForeignKey adds a FOREIGN KEY constraint to an existing table. constraintName is the constraint identifier; refTable is the referenced table; columns and refColumns are matched by position.
Example:
client.AddForeignKey(ctx, "orders", "fk_orders_user", []string{"user_id"}, "users", []string{"id"}, "CASCADE", "SET NULL")
func (*Client) BeginTx ¶
BeginTx starts a new database transaction with the given options.
Example:
tx, err := client.BeginTx(ctx, nil)
if err != nil { log.Fatal(err) }
defer tx.Rollback()
quark.ForTx[User](ctx, tx).Create(&user)
tx.Commit()
func (*Client) CreateIndex ¶
func (c *Client) CreateIndex(ctx context.Context, table, indexName string, columns []string, unique bool) error
CreateIndex creates an index on the given table and columns. If unique is true, a UNIQUE INDEX is created. If the index already exists the error is silently ignored for compatible dialects.
Example:
client.CreateIndex(ctx, "users", "idx_users_email", []string{"email"}, true)
func (*Client) Exec ¶
Exec executes a raw SQL statement (INSERT, UPDATE, DELETE, DDL). This is primarily used for migrations and schema changes.
func (*Client) Migrate ¶
Migrate creates tables for the given models if they don't exist. This is a simplistic auto-migration tool for development. It uses the "db" and "pk" tags to generate CREATE TABLE statements. It also creates join tables for many-to-many relations.
func (*Client) Raw ¶
Raw returns the underlying *sql.DB for advanced operations. Use with caution - this bypasses quark's safety features.
func (*Client) RawQuery ¶
RawQuery executes a raw SQL query with the given arguments. By default, this requires placeholders to prevent SQL injection. Enable with WithLimits(Limits{AllowRawQueries: true}).
func (*Client) Sync ¶
Sync synchronizes the database schema with the provided models. It detects missing columns, renames, and can drop columns if safe mode is disabled.
func (*Client) Tx ¶
Tx executes fn within a transaction. If fn returns nil, the transaction is committed. If fn returns an error or panics, the transaction is rolled back.
Example:
err := client.Tx(ctx, func(tx *quark.Tx) error {
quark.ForTx[User](ctx, tx).Create(&user)
quark.ForTx[Order](ctx, tx).Create(&order)
return nil // auto-commit
})
type ClientProvider ¶
ClientProvider is an interface that provides a database client. Both *Client and *TenantRouter implement this.
type Cursor ¶
type Cursor[T any] struct { // contains filtered or unexported fields }
Cursor provides manual iteration over query results. Similar to sql.Rows but type-safe for model T.
type Dialect ¶
type Dialect interface {
// Name returns the dialect name (e.g., "postgres", "mysql", "sqlite").
Name() string
// Placeholder returns the placeholder for the given parameter index.
// PostgreSQL: $1, $2, etc.
// MySQL/SQLite: ?
// MSSQL: @p1, @p2, etc.
// Oracle: :1, :2, etc.
Placeholder(index int) string
// Quote returns a quoted identifier (table/column name).
// PostgreSQL: "identifier"
// MySQL: `identifier`
// MSSQL: [identifier]
// SQLite/Oracle: "identifier"
Quote(identifier string) string
// Placeholders returns a slice of placeholders for n parameters.
Placeholders(n int) []string
// LimitOffset returns the LIMIT/OFFSET clause for the given parameters.
LimitOffset(limit, offset int) string
// SupportsReturning indicates if the dialect supports RETURNING clause.
SupportsReturning() bool
// Returning returns the RETURNING clause for the given columns.
// Returns empty string if not supported.
Returning(columns ...string) string
// SupportsLastInsertID indicates if the dialect supports LastInsertId().
SupportsLastInsertID() bool
// LastInsertIDQuery returns the query to get the last insert ID.
// Used for dialects that don't support RETURNING.
LastInsertIDQuery(table, pkColumn string) string
// CurrentTimestamp returns the SQL function for current timestamp.
CurrentTimestamp() string
// BuildRoutineQuery returns the SQL for a table-valued function or routine returning rows.
// E.g., Postgres: SELECT * FROM func($1, $2)
BuildRoutineQuery(routine string, argCount int) string
// BuildProcedureCall returns the SQL for calling a procedure (pure logic / OUT params).
// E.g., MySQL: CALL proc(?, ?)
BuildProcedureCall(procedure string, argCount int) string
// JSONExtract returns the SQL expression to extract a value from a JSON column,
// the bind args required by that expression, or an error if the path is
// malformed.
//
// The returned SQL fragment uses literal '?' as a neutral bind marker; the
// caller (typically buildWhereClause) substitutes each '?' for the dialect's
// placeholder syntax (`$N`, `?`, `@pN`, `:N`) at the appropriate arg index.
//
// The path is validated and passed as a bind parameter — never interpolated
// into the SQL surface. This closes the SQL-injection vector that existed
// while the path was concatenated with fmt.Sprintf.
//
// Example outputs (with column "data" and path "user.name"):
// Postgres: jsonb_extract_path_text(("data")::jsonb, ?, ?) / args=["user","name"]
// MySQL: JSON_EXTRACT(`data`, ?) / args=["$.user.name"]
// SQLite: JSON_EXTRACT("data", ?) / args=["$.user.name"]
// MSSQL: JSON_VALUE([data], ?) / args=["$.user.name"]
// Oracle: JSON_VALUE("DATA", ?) / args=["$.user.name"]
JSONExtract(column, path string) (sql string, args []any, err error)
// AlterTableAddColumn returns SQL to add a column to a table.
// E.g., PostgreSQL: ALTER TABLE "users" ADD COLUMN "email" VARCHAR(255)
AlterTableAddColumn(table, column, dataType string) string
// AlterTableDropColumn returns SQL to drop a column from a table.
// E.g., PostgreSQL: ALTER TABLE "users" DROP COLUMN "email"
AlterTableDropColumn(table, column string) string
// AlterTableAlterColumn returns SQL to alter a column's type.
// E.g., PostgreSQL: ALTER TABLE "users" ALTER COLUMN "email" TYPE VARCHAR(255)
AlterTableAlterColumn(table, column, newDataType string) string
// RenameColumn returns SQL to rename a column.
// E.g., PostgreSQL: ALTER TABLE "users" RENAME COLUMN "old_name" TO "new_name"
RenameColumn(table, oldName, newName string) string
// RenameTable returns SQL to rename a table.
// E.g., PostgreSQL: ALTER TABLE "users" RENAME TO "accounts"
RenameTable(oldName, newName string) string
// SupportsTransactionalDDL indicates if the dialect supports DDL in transactions.
SupportsTransactionalDDL() bool
// LockSuffix returns the SQL fragments needed to attach a pessimistic
// lock to a SELECT.
//
// - tableHint is appended after the FROM clause's table name. MSSQL
// uses this slot for `WITH (UPDLOCK, ROWLOCK)`-style hints; the
// row-level locking dialects return "" here.
// - suffix is appended at the very end of the SELECT (after ORDER BY
// and LIMIT/OFFSET) — `FOR UPDATE [SKIP LOCKED|NOWAIT]` for the
// PG/MySQL/Oracle/MariaDB family.
//
// Returning ErrUnsupportedFeature signals "this dialect doesn't speak
// pessimistic locks at this level" — SQLite is the canonical case.
// LockOptions.IsZero() input must always return ("", "", nil).
LockSuffix(opts LockOptions) (tableHint, suffix string, err error)
// UpsertSQL returns the dialect-specific upsert (INSERT … ON CONFLICT … DO UPDATE)
// fragment that is appended after the VALUES clause.
// conflictCols: columns that define the conflict target (e.g. primary key or unique index).
// updateCols: columns to update on conflict; if empty defaults to all non-conflict columns.
// argOffset: current placeholder index (1-based) so positional dialects stay in sync.
// Returns the SQL fragment and the additional argument list (for the SET clause values).
UpsertSQL(conflictCols, updateCols []string, argOffset int) string
}
Dialect defines the interface for database-specific SQL generation. Each supported database (PostgreSQL, MySQL, SQLite, etc.) implements this interface.
func DetectDialect ¶
DetectDialect attempts to auto-detect the dialect from a driver name.
func DetectDialectByName ¶
DetectDialectByName attempts to get a dialect by name from all registered dialects including custom ones. This is useful when you know the exact dialect name.
type EventBus ¶
type EventBus struct {
// contains filtered or unexported fields
}
EventBus provides a dialect-agnostic factory for creating EventListeners. Since not all databases support PubSub natively (e.g., SQLite), this may return ErrNotSupported for certain dialects.
func NewEventBus ¶
NewEventBus creates a new EventBus for the given client.
func (*EventBus) CreateListener ¶
func (eb *EventBus) CreateListener() (EventListener, error)
CreateListener creates an EventListener based on the dialect.
NOTE: EventBus is experimental in V1. Native LISTEN/NOTIFY (PostgreSQL) requires a dedicated connection with a driver-specific implementation (e.g., github.com/lib/pq). This will be completed in a future release.
type EventListener ¶
type EventListener interface {
// Listen subscribes to a specific channel.
Listen(ctx context.Context, channel string) error
// Unlisten unsubscribes from a channel.
Unlisten(ctx context.Context, channel string) error
// Receive blocks until an event is received, returning the payload or an error.
Receive(ctx context.Context) (EventPayload, error)
// Close terminates the listener connection.
Close() error
}
EventListener defines an interface for listening to database events. This is typically implemented via PubSub mechanisms like PostgreSQL's LISTEN/NOTIFY.
type EventPayload ¶
EventPayload represents a message received from a database event channel.
type ExecFunc ¶
type ExecFunc func(ctx context.Context, exec Executor, sqlStr string, args []any) (sql.Result, error)
ExecFunc is the signature for SQL execution functions used by middleware.
type Executor ¶
type Executor interface {
QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)
QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row
ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error)
}
Executor is the common interface for *sql.DB and *sql.Tx. It allows Query[T] to execute against either a raw connection or a transaction.
type Expr ¶ added in v0.4.0
Expr is the composable expression node Phase 2's query builder rests on. Each node implements ToSQL, returning a SQL fragment with `?` as a neutral bind marker plus the args that fill those markers.
Callers (WhereExpr, HavingExpr) hand the rendered fragment to the existing buildWhereClause / substitutePathMarkers pipeline that already rewrites `?` to the dialect's placeholder syntax at the correct arg index. That keeps the AST dialect-agnostic at construction time and lets us compose deep trees without juggling indices.
The exported constructors below (Col, Lit, And, Or, Not, Cmp, Eq, Ne, Lt, Gt, Lte, Gte, In, NotIn, Func) are the v0.4 AST surface. Subqueries, Cast, and Exists arrive in the subqueries-and-CTE PR.
func And ¶ added in v0.4.0
And composes two or more expressions with logical AND. Empty And is a no-op (renders to ""). Single-element And renders the inner expression without parentheses; two or more get wrapped so precedence is explicit.
func Cmp ¶ added in v0.4.0
Cmp is the general comparison constructor. Operator goes through SQLGuard.ValidateOperator so the AST cannot smuggle arbitrary tokens into the SQL surface.
func Col ¶ added in v0.4.0
Col references a column by name. The name is validated through SQLGuard.ValidateIdentifier — the AST inherits the same identifier safety the rest of the builder enforces. The wildcard "*" is accepted as-is for use inside aggregate calls (e.g. Func("COUNT", Col("*"))).
func Eq ¶ added in v0.4.0
Eq, Ne, Lt, Gt, Lte, Gte are the syntactic shortcuts for Cmp with the most common operators. Built on top of Lit / Col for the typical "Col(x) = Lit(v)" shape.
func Exists ¶ added in v0.4.0
Exists renders `EXISTS (<subquery>)`. Typically used as a top-level WhereExpr predicate.
Example:
hasOrders, _ := quark.For[Order](ctx, client).
Where("user_id", "=", quark.Col("users.id")).
Select("1").
AsSubquery()
q := quark.For[User](ctx, client).WhereExpr(quark.Exists(hasOrders))
(Correlated subqueries require the inner query to reference the outer table by qualified name; see the JoinOn grammar for what's accepted.)
func Func ¶ added in v0.4.0
Func calls a SQL function. The name is normalised to upper-case and matched against a whitelist; unknown names return ErrInvalidQuery rather than reaching the SQL surface. Empty argument list is allowed — emit a bare "FUN()" — for COUNT(*), use Col("*") explicitly.
func In ¶ added in v0.4.0
In renders "lhs IN (v1, v2, …)". Empty values list is a logic error and returns ErrInvalidQuery — `WHERE x IN ()` is non-portable across dialects (Postgres errors, MySQL silently matches nothing) so we refuse to emit it. Use a no-rows query instead.
func InSub ¶ added in v0.4.0
InSub renders `lhs IN (<subquery>)`. Useful for the common `WHERE x IN (SELECT id FROM ...)` shape; the inner Subquery should `Select("…")` exactly one column for the comparison.
func Lag ¶ added in v0.4.0
Lag renders `LAG(<col>, <offset>)`. The offset is bound as a parameter so the path is uniform with the rest of the AST — no SQL-surface integers, no per-dialect numeric formatting concerns.
func Lit ¶ added in v0.4.0
Lit binds a Go value as a SQL parameter. The value never reaches the SQL surface — it always travels through args, regardless of how nested the expression tree is.
func Not ¶ added in v0.4.0
Not negates an expression. Renders as "NOT (<inner>)" so precedence is explicit.
func Or ¶ added in v0.4.0
Or composes two or more expressions with logical OR. Same parenthesis rules as And.
func Over ¶ added in v0.4.0
Over wraps an inner Expr with a Window: `<inner> OVER (<window>)`. Typical use is wrapping a window-function leaf (RowNumber, Rank, DenseRank, Lag, Lead) but any aggregate function from the AST whitelist (`COUNT`, `SUM`, etc.) is also valid as the inner — the SQL spec defines them all as windowable.
type JSON ¶ added in v0.3.0
type JSON[T any] struct { V T }
JSON[T] wraps a Go value so it round-trips through a SQL JSON column via encoding/json. Use it when you want a typed view of a JSON-shaped column without writing scan / value code by hand.
JSON implements database/sql.Scanner and database/sql/driver.Valuer, so every driver Quark supports handles the round-trip with no extra reflect in Quark's hot paths. The migrate layer detects JSON[T] and emits the dialect-native JSON column type:
Postgres → JSONB MySQL / MariaDB → JSON SQLite → TEXT (with json_* functions available at query time) SQL Server → NVARCHAR(MAX) Oracle → CLOB
Example:
type Settings struct{ Theme string `json:"theme"`; Volume int `json:"volume"` }
type Profile struct {
ID int64 `db:"id" pk:"true"`
Settings quark.JSON[Settings] `db:"settings"`
}
p := Profile{Settings: quark.JSON[Settings]{V: Settings{Theme: "dark", Volume: 7}}}
_ = client.Migrate(ctx, &Profile{})
_ = quark.For[Profile](ctx, client).Create(&p)
func (*JSON[T]) Scan ¶ added in v0.3.0
Scan implements sql.Scanner. Accepts []byte and string sources (the two forms drivers return for JSON columns). NULL clears V to its zero value rather than erroring; pair with quark.Nullable[JSON[T]] when you want to distinguish NULL from "valid but empty" payloads.
type JoinBuilder ¶ added in v0.4.0
type JoinBuilder[T any] struct { // contains filtered or unexported fields }
JoinBuilder is the structured form returned by Join, LeftJoin, and RightJoin. Complete the JOIN by chaining `.On(left, op, right)` (the typed identifier form) or `.OnRaw(onClause)` (the legacy free-form string for ON clauses outside the simple binary grammar). Both chain-terminate by returning *Query[T] so subsequent builder calls pick up where the JOIN left off.
JoinBuilder values are immutable; the underlying *Query[T] is cloned before the JOIN is appended, matching the rest of the builder's thread-safety contract.
func (*JoinBuilder[T]) On ¶ added in v0.4.0
func (b *JoinBuilder[T]) On(left, op, right string) *Query[T]
On appends an INNER/LEFT/RIGHT JOIN with a single binary identifier comparison as the ON clause: `<left> <op> <right>`. The three arguments are concatenated as `left + " " + op + " " + right` and the resulting clause is validated as a whole against `guard.ValidateJoinOn` at exec time, surfacing `ErrInvalidJoin` for any shape outside the identifier-only grammar (literal RHS, function calls, parens, comments, mismatched operators). The grammar accepts the binary comparison operators `=`, `!=`, `<>`, `<`, `<=`, `>`, `>=` and AND-chained compound clauses.
Most JOINs need only this form — the typical `users.id = orders.user_id` shape. For multi-condition ON clauses or any expression the ValidateJoinOn grammar accepts (AND-chained identifier comparisons), use `OnRaw`.
Example:
quark.For[Order](ctx, client).
Join("users").On("users.id", "=", "orders.user_id").
List()
func (*JoinBuilder[T]) OnRaw ¶ added in v0.4.0
func (b *JoinBuilder[T]) OnRaw(onClause string) *Query[T]
OnRaw appends the JOIN with a free-form ON clause string. The clause must match the minimal identifier-only grammar that `guard.ValidateJoinOn` enforces (AND-chained binary comparisons of qualified identifiers, e.g. `users.id = orders.user_id AND users.tenant_id = orders.tenant_id`). Literals, function calls, subqueries, and parentheses are rejected. Drop down to `RawQuery` for shapes outside this grammar.
OnRaw is the migration path for callers of the v0.3.x string-raw `Join(table, onClause)`: rewrite as `Join(table).OnRaw(onClause)`.
type Limits ¶
type Limits struct {
MaxQueryLength int
MaxResults int
MaxJoins int
MaxWhereConditions int
QueryTimeout time.Duration
AllowRawQueries bool
SafeMigrations bool
}
Limits defines security and performance limits for queries.
type LockMode ¶ added in v0.4.0
type LockMode int
LockMode is the kind of pessimistic lock requested for a SELECT.
const ( // LockNone means no lock clause is emitted (the default). LockNone LockMode = iota // LockForUpdate locks the rows for update; other transactions cannot // read-with-lock or write the matching rows until the current // transaction commits or rolls back. Most engines support it. LockForUpdate // read-with-lock but not write. Supported on PG / MySQL 8+ / MariaDB; // not on SQLite. MSSQL approximates with HOLDLOCK. LockForShare )
type LockOptions ¶ added in v0.4.0
LockOptions describes the pessimistic-lock behaviour for a SELECT. The zero value (LockMode == LockNone) emits nothing — callers opt in via ForUpdate / ForShare on Query[T].
func (LockOptions) IsZero ¶ added in v0.4.0
func (o LockOptions) IsZero() bool
IsZero reports whether the options request no lock at all. Used by dialects to short-circuit their LockSuffix implementations.
type MSSQLDialect ¶
type MSSQLDialect struct {
// contains filtered or unexported fields
}
MSSQLDialect implements the Microsoft SQL Server dialect.
func (*MSSQLDialect) AlterTableAddColumn ¶
func (m *MSSQLDialect) AlterTableAddColumn(table, column, dataType string) string
func (*MSSQLDialect) AlterTableAlterColumn ¶
func (m *MSSQLDialect) AlterTableAlterColumn(table, column, newDataType string) string
func (*MSSQLDialect) AlterTableDropColumn ¶
func (m *MSSQLDialect) AlterTableDropColumn(table, column string) string
func (*MSSQLDialect) BuildProcedureCall ¶
func (m *MSSQLDialect) BuildProcedureCall(procedure string, argCount int) string
func (*MSSQLDialect) BuildRoutineQuery ¶
func (m *MSSQLDialect) BuildRoutineQuery(routine string, argCount int) string
func (*MSSQLDialect) CurrentTimestamp ¶
func (m *MSSQLDialect) CurrentTimestamp() string
func (*MSSQLDialect) JSONExtract ¶
func (m *MSSQLDialect) JSONExtract(column, path string) (string, []any, error)
func (*MSSQLDialect) LastInsertIDQuery ¶
func (m *MSSQLDialect) LastInsertIDQuery(table, pkColumn string) string
func (*MSSQLDialect) LimitOffset ¶
func (m *MSSQLDialect) LimitOffset(limit, offset int) string
func (*MSSQLDialect) LockSuffix ¶ added in v0.4.0
func (m *MSSQLDialect) LockSuffix(opts LockOptions) (string, string, error)
MSSQL uses table hints attached to the FROM clause (not a SELECT suffix). Single-row pessimistic-style locks come from (UPDLOCK, ROWLOCK) for ForUpdate and (HOLDLOCK, ROWLOCK) for ForShare. SkipLocked maps to READPAST; NoWait has no direct hint, so it errors out rather than silently blocking.
func (*MSSQLDialect) Placeholder ¶
func (m *MSSQLDialect) Placeholder(index int) string
func (*MSSQLDialect) Placeholders ¶
func (m *MSSQLDialect) Placeholders(n int) []string
func (*MSSQLDialect) Quote ¶
func (m *MSSQLDialect) Quote(identifier string) string
func (*MSSQLDialect) RenameColumn ¶
func (m *MSSQLDialect) RenameColumn(table, oldName, newName string) string
func (*MSSQLDialect) RenameTable ¶
func (m *MSSQLDialect) RenameTable(oldName, newName string) string
func (*MSSQLDialect) Returning ¶
func (m *MSSQLDialect) Returning(columns ...string) string
func (*MSSQLDialect) SupportsLastInsertID ¶
func (m *MSSQLDialect) SupportsLastInsertID() bool
func (*MSSQLDialect) SupportsReturning ¶
func (m *MSSQLDialect) SupportsReturning() bool
func (*MSSQLDialect) SupportsTransactionalDDL ¶
func (m *MSSQLDialect) SupportsTransactionalDDL() bool
func (*MSSQLDialect) UpsertSQL ¶
func (m *MSSQLDialect) UpsertSQL(conflictCols, updateCols []string, _ int) string
UpsertSQL for MSSQL: uses MERGE statement appended as a WITH-style hint. MSSQL requires MERGE syntax which cannot be appended to a plain INSERT, so we return a marker that buildUpsert handles specially.
type MariaDBDialect ¶
type MariaDBDialect struct {
MySQLDialect // embed MySQL — identical wire protocol and driver
}
MariaDBDialect implements the MariaDB dialect. MariaDB is a fork of MySQL with significant additions:
- RETURNING clause in INSERT/DELETE/UPDATE (10.5+)
- Native sequences via CREATE SEQUENCE (10.3+)
- Temporal tables / system-versioned tables (10.3.4+)
- JSON_TABLE support (10.6+)
- INTERSECT / EXCEPT set operations (10.3+)
- Descending indexes (10.6+)
- UUID() and UUID_SHORT() built-ins
- IGNORE INDEX / USE INDEX hints identical to MySQL
func (*MariaDBDialect) AlterTableAlterColumn ¶
func (m *MariaDBDialect) AlterTableAlterColumn(table, column, newDataType string) string
AlterTableAlterColumn uses MODIFY COLUMN (same as MySQL).
func (*MariaDBDialect) CreateSequence ¶
func (m *MariaDBDialect) CreateSequence(name string, start, increment int64) string
CreateSequence returns the DDL to create a named sequence (MariaDB 10.3+).
func (*MariaDBDialect) CreateSystemVersionedTable ¶
func (m *MariaDBDialect) CreateSystemVersionedTable(table string, columnDefs string) string
CreateSystemVersionedTable returns the DDL for a system-versioned (temporal) table. Requires MariaDB 10.3.4+.
func (*MariaDBDialect) HistoryBetween ¶
func (m *MariaDBDialect) HistoryBetween(table, from, to string) string
HistoryBetween returns SELECT … FOR SYSTEM_TIME BETWEEN for a time range.
func (*MariaDBDialect) HistoryQuery ¶
func (m *MariaDBDialect) HistoryQuery(table string) string
HistoryQuery returns SELECT … FOR SYSTEM_TIME ALL to query full row history.
func (*MariaDBDialect) JSONExtract ¶
func (m *MariaDBDialect) JSONExtract(column, path string) (string, []any, error)
JSONExtract uses the MariaDB / MySQL JSON_VALUE syntax (10.2.3+). MariaDB also accepts the arrow operator col->>'$.key' from 10.4.3+.
func (*MariaDBDialect) JSONTable ¶
func (m *MariaDBDialect) JSONTable(source, path string, columns ...string) string
JSONTable returns a JSON_TABLE expression (MariaDB 10.6+). source: SQL expression producing JSON; path: root path e.g. '$[*]'; columns: column definitions e.g. "id INT PATH '$.id'".
The path is validated against guard.ValidateJSONTablePath (JSONPath grammar rooted at "$"). source and columns must be trusted strings — the JSON_TABLE row syntax intermixes column types and PATH literals, so binding it as a parameter is not possible. Callers MUST NOT pass user-controlled values for source or columns. If invalid, the returned SQL embeds an obvious sentinel that fails parsing at execution time, surfacing the misuse rather than silently producing executable injection.
TODO(public-api): when JSONTable graduates from internal-only to a public builder, change the signature to return (string, error) so callers can detect validation failure with errors.Is rather than scanning the SQL for JSON_TABLE_PATH_INVALID.
func (*MariaDBDialect) LastInsertIDQuery ¶
func (m *MariaDBDialect) LastInsertIDQuery(table, pkColumn string) string
LastInsertIDQuery is kept as fallback for engines older than 10.5.
func (*MariaDBDialect) LimitOffset ¶
func (m *MariaDBDialect) LimitOffset(limit, offset int) string
LimitOffset for MariaDB uses standard LIMIT … OFFSET … syntax (unlike MySQL which uses LIMIT offset, count).
func (*MariaDBDialect) LockSuffix ¶ added in v0.4.0
func (m *MariaDBDialect) LockSuffix(opts LockOptions) (string, string, error)
MariaDB 10.6+ supports SKIP LOCKED and NOWAIT in the same shape as MySQL.
func (*MariaDBDialect) NextVal ¶
func (m *MariaDBDialect) NextVal(sequenceName string) string
NextVal returns the SQL expression that reads the next value from a sequence.
func (*MariaDBDialect) RenameColumn ¶
func (m *MariaDBDialect) RenameColumn(table, oldName, newName string) string
RenameColumn uses the standard SQL syntax supported since MariaDB 10.4.2.
func (*MariaDBDialect) Returning ¶
func (m *MariaDBDialect) Returning(columns ...string) string
Returning generates a RETURNING clause compatible with MariaDB 10.5+.
func (*MariaDBDialect) SupportsLastInsertID ¶
func (m *MariaDBDialect) SupportsLastInsertID() bool
SupportsLastInsertID returns false when RETURNING is used. The ORM prefers RETURNING over LAST_INSERT_ID() for MariaDB.
func (*MariaDBDialect) SupportsReturning ¶
func (m *MariaDBDialect) SupportsReturning() bool
SupportsReturning returns true: MariaDB 10.5+ supports RETURNING in INSERT … RETURNING, DELETE … RETURNING and UPDATE … RETURNING.
func (*MariaDBDialect) SupportsTransactionalDDL ¶
func (m *MariaDBDialect) SupportsTransactionalDDL() bool
SupportsTransactionalDDL returns false — MariaDB (like MySQL) performs implicit commits around DDL statements.
type Middleware ¶
type Middleware interface {
WrapExec(next ExecFunc) ExecFunc
WrapQuery(next QueryFunc) QueryFunc
WrapQueryRow(next QueryRowFunc) QueryRowFunc
}
Middleware wraps query execution for cross-cutting concerns like logging, retry logic, caching, rate limiting, etc. It intercepts all types of database interactions (Exec, Query, QueryRow).
type ModelMeta ¶
ModelMeta is the cached metadata for a model struct.
func GetModelMeta ¶
GetModelMeta returns the cached metadata for model type T.
func GetModelMetaByType ¶
GetModelMetaByType returns the cached metadata for a reflect.Type.
type MySQLDialect ¶
type MySQLDialect struct {
// contains filtered or unexported fields
}
MySQLDialect implements the MySQL dialect.
func (*MySQLDialect) AlterTableAddColumn ¶
func (m *MySQLDialect) AlterTableAddColumn(table, column, dataType string) string
func (*MySQLDialect) AlterTableAlterColumn ¶
func (m *MySQLDialect) AlterTableAlterColumn(table, column, newDataType string) string
func (*MySQLDialect) AlterTableDropColumn ¶
func (m *MySQLDialect) AlterTableDropColumn(table, column string) string
func (*MySQLDialect) BuildProcedureCall ¶
func (m *MySQLDialect) BuildProcedureCall(procedure string, argCount int) string
func (*MySQLDialect) BuildRoutineQuery ¶
func (m *MySQLDialect) BuildRoutineQuery(routine string, argCount int) string
func (*MySQLDialect) CurrentTimestamp ¶
func (m *MySQLDialect) CurrentTimestamp() string
func (*MySQLDialect) JSONExtract ¶
func (m *MySQLDialect) JSONExtract(column, path string) (string, []any, error)
func (*MySQLDialect) LastInsertIDQuery ¶
func (m *MySQLDialect) LastInsertIDQuery(table, pkColumn string) string
func (*MySQLDialect) LimitOffset ¶
func (m *MySQLDialect) LimitOffset(limit, offset int) string
func (*MySQLDialect) LockSuffix ¶ added in v0.4.0
func (m *MySQLDialect) LockSuffix(opts LockOptions) (string, string, error)
MySQL 8.0+ supports SKIP LOCKED and NOWAIT. Older 5.x ignores those modifiers (driver does not error; lock still taken).
func (*MySQLDialect) Placeholder ¶
func (m *MySQLDialect) Placeholder(index int) string
func (*MySQLDialect) Placeholders ¶
func (m *MySQLDialect) Placeholders(n int) []string
func (*MySQLDialect) Quote ¶
func (m *MySQLDialect) Quote(identifier string) string
func (*MySQLDialect) RenameColumn ¶
func (m *MySQLDialect) RenameColumn(table, oldName, newName string) string
func (*MySQLDialect) RenameTable ¶
func (m *MySQLDialect) RenameTable(oldName, newName string) string
func (*MySQLDialect) Returning ¶
func (m *MySQLDialect) Returning(columns ...string) string
func (*MySQLDialect) SupportsLastInsertID ¶
func (m *MySQLDialect) SupportsLastInsertID() bool
func (*MySQLDialect) SupportsReturning ¶
func (m *MySQLDialect) SupportsReturning() bool
func (*MySQLDialect) SupportsTransactionalDDL ¶
func (m *MySQLDialect) SupportsTransactionalDDL() bool
type Nullable ¶ added in v0.3.0
Nullable[T] is a generic wrapper for a column value that may be SQL NULL. It is a thin alias of database/sql.Null[T] (Go 1.22+) so a Nullable[T] already implements both database/sql.Scanner and database/sql/driver.Valuer — drivers handle the round-trip through their existing fast paths and Quark's reflect-based scan / write code does not need to special-case it.
Replace the long-standing pointer-as-nullable idiom (e.g. *time.Time, *string) with Nullable[T] when you want explicit "is set" semantics without a heap allocation per field. The migrate layer recognises the type and emits the SQL type for T (no NOT NULL, since the column is nullable by definition).
Example:
type Profile struct {
ID int64 `db:"id" pk:"true"`
Bio quark.Nullable[string] `db:"bio"`
Born quark.Nullable[time.Time] `db:"born"`
}
p := Profile{
Bio: quark.SomeOf("hello"),
Born: quark.NullOf[time.Time](),
}
type Option ¶
type Option func(*Client)
Option configures a Client.
func WithCacheStore ¶
func WithCacheStore(s CacheStore) Option
WithCacheStore sets the caching backend for the client.
func WithDialect ¶
WithDialect sets the SQL dialect for the client. If not set, the dialect will be auto-detected from the database driver.
func WithLimits ¶
WithLimits sets the security and performance limits.
func WithLogger ¶
WithLogger sets the logger for the client. If not set, a no-op logger will be used.
func WithMiddleware ¶
func WithMiddleware(m Middleware) Option
WithMiddleware adds middleware to the query execution chain. Middleware is applied in the order they are added.
func WithQueryObserver ¶
func WithQueryObserver(o QueryObserver) Option
WithQueryObserver adds a query observer to the client. Multiple observers can be added and will be called in order.
type OracleDialect ¶
type OracleDialect struct {
// contains filtered or unexported fields
}
OracleDialect implements the Oracle Database dialect.
func (*OracleDialect) AlterTableAddColumn ¶
func (o *OracleDialect) AlterTableAddColumn(table, column, dataType string) string
func (*OracleDialect) AlterTableAlterColumn ¶
func (o *OracleDialect) AlterTableAlterColumn(table, column, newDataType string) string
func (*OracleDialect) AlterTableDropColumn ¶
func (o *OracleDialect) AlterTableDropColumn(table, column string) string
func (*OracleDialect) BuildProcedureCall ¶
func (o *OracleDialect) BuildProcedureCall(procedure string, argCount int) string
func (*OracleDialect) BuildRoutineQuery ¶
func (o *OracleDialect) BuildRoutineQuery(routine string, argCount int) string
func (*OracleDialect) CurrentTimestamp ¶
func (o *OracleDialect) CurrentTimestamp() string
func (*OracleDialect) JSONExtract ¶
func (o *OracleDialect) JSONExtract(column, path string) (string, []any, error)
func (*OracleDialect) LastInsertIDQuery ¶
func (o *OracleDialect) LastInsertIDQuery(table, pkColumn string) string
func (*OracleDialect) LimitOffset ¶
func (o *OracleDialect) LimitOffset(limit, offset int) string
func (*OracleDialect) LockSuffix ¶ added in v0.4.0
func (o *OracleDialect) LockSuffix(opts LockOptions) (string, string, error)
Oracle pessimistic locking. SKIP LOCKED supported on 12c+.
FOR UPDATE [NOWAIT|SKIP LOCKED]
Oracle does not have a FOR SHARE; map LockForShare → ErrUnsupportedFeature rather than emitting an unsafe approximation.
func (*OracleDialect) Placeholder ¶
func (o *OracleDialect) Placeholder(index int) string
func (*OracleDialect) Placeholders ¶
func (o *OracleDialect) Placeholders(n int) []string
func (*OracleDialect) Quote ¶
func (o *OracleDialect) Quote(identifier string) string
func (*OracleDialect) RenameColumn ¶
func (o *OracleDialect) RenameColumn(table, oldName, newName string) string
func (*OracleDialect) RenameTable ¶
func (o *OracleDialect) RenameTable(oldName, newName string) string
func (*OracleDialect) Returning ¶
func (o *OracleDialect) Returning(columns ...string) string
func (*OracleDialect) SupportsLastInsertID ¶
func (o *OracleDialect) SupportsLastInsertID() bool
func (*OracleDialect) SupportsReturning ¶
func (o *OracleDialect) SupportsReturning() bool
func (*OracleDialect) SupportsTransactionalDDL ¶
func (o *OracleDialect) SupportsTransactionalDDL() bool
type Page ¶
type Page[T any] struct { Items []T // The items for current page Total int64 // Total count (if available) Page int // Current page number (0-indexed) PageSize int // Items per page TotalPages int64 // Calculated total pages }
Page represents a paginated result set.
type PoolOption ¶ added in v0.3.0
type PoolOption interface {
// contains filtered or unexported methods
}
PoolOption is a configuration option for the database connection pool. These are applied to the *sql.DB before creating the Client.
func WithConnMaxIdleTime ¶ added in v0.3.0
func WithConnMaxIdleTime(d time.Duration) PoolOption
WithConnMaxIdleTime sets the maximum amount of time a connection may be idle.
func WithConnMaxLifetime ¶ added in v0.3.0
func WithConnMaxLifetime(d time.Duration) PoolOption
WithConnMaxLifetime sets the maximum amount of time a connection may be reused.
func WithMaxIdleConns ¶ added in v0.3.0
func WithMaxIdleConns(n int) PoolOption
WithMaxIdleConns sets the maximum number of idle connections in the pool.
func WithMaxOpenConns ¶ added in v0.3.0
func WithMaxOpenConns(n int) PoolOption
WithMaxOpenConns sets the maximum number of open connections to the database.
type PostgresDialect ¶
type PostgresDialect struct {
// contains filtered or unexported fields
}
PostgresDialect implements the PostgreSQL dialect.
func (*PostgresDialect) AlterTableAddColumn ¶
func (p *PostgresDialect) AlterTableAddColumn(table, column, dataType string) string
func (*PostgresDialect) AlterTableAlterColumn ¶
func (p *PostgresDialect) AlterTableAlterColumn(table, column, newDataType string) string
func (*PostgresDialect) AlterTableDropColumn ¶
func (p *PostgresDialect) AlterTableDropColumn(table, column string) string
func (*PostgresDialect) BuildProcedureCall ¶
func (p *PostgresDialect) BuildProcedureCall(procedure string, argCount int) string
func (*PostgresDialect) BuildRoutineQuery ¶
func (p *PostgresDialect) BuildRoutineQuery(routine string, argCount int) string
func (*PostgresDialect) CurrentTimestamp ¶
func (p *PostgresDialect) CurrentTimestamp() string
func (*PostgresDialect) JSONExtract ¶
func (p *PostgresDialect) JSONExtract(column, path string) (string, []any, error)
func (*PostgresDialect) LastInsertIDQuery ¶
func (p *PostgresDialect) LastInsertIDQuery(table, pkColumn string) string
func (*PostgresDialect) LimitOffset ¶
func (p *PostgresDialect) LimitOffset(limit, offset int) string
func (*PostgresDialect) LockSuffix ¶ added in v0.4.0
func (p *PostgresDialect) LockSuffix(opts LockOptions) (string, string, error)
PostgreSQL pessimistic locking (PG 9.5+ for SKIP LOCKED).
FOR UPDATE [SKIP LOCKED|NOWAIT] FOR SHARE [SKIP LOCKED|NOWAIT]
func (*PostgresDialect) Placeholder ¶
func (p *PostgresDialect) Placeholder(index int) string
func (*PostgresDialect) Placeholders ¶
func (p *PostgresDialect) Placeholders(n int) []string
func (*PostgresDialect) Quote ¶
func (p *PostgresDialect) Quote(identifier string) string
func (*PostgresDialect) RenameColumn ¶
func (p *PostgresDialect) RenameColumn(table, oldName, newName string) string
func (*PostgresDialect) RenameTable ¶
func (p *PostgresDialect) RenameTable(oldName, newName string) string
func (*PostgresDialect) Returning ¶
func (p *PostgresDialect) Returning(columns ...string) string
func (*PostgresDialect) SupportsLastInsertID ¶
func (p *PostgresDialect) SupportsLastInsertID() bool
func (*PostgresDialect) SupportsReturning ¶
func (p *PostgresDialect) SupportsReturning() bool
func (*PostgresDialect) SupportsTransactionalDDL ¶
func (p *PostgresDialect) SupportsTransactionalDDL() bool
type Query ¶
Query represents a type-safe database query builder for model T. All builder methods return a new Query (immutable/clone pattern) for thread-safety. Execution methods are in query_exec.go and query_crud.go
func For ¶
func For[T any](ctx context.Context, provider ClientProvider) *Query[T]
For creates a Query builder for the given model type. This is the primary entry point for type-safe database operations.
Example:
type User struct {
ID int64 `db:"id"`
Name string `db:"name"`
}
user, err := quark.For[User](ctx, client).Find(1)
users, err := quark.For[User](ctx, client).Where("active", "=", true).List()
func ForTx ¶
ForTx creates a Query builder for the given model type bound to a transaction. This is the transactional counterpart of For[T]().
Example:
err := client.Tx(ctx, func(tx *quark.Tx) error {
return quark.ForTx[User](ctx, tx).Create(&user)
})
func (*Query[T]) Apply ¶
Apply applies one or more Scope functions to the query. Scopes are composable, reusable query fragments.
Example:
activeUsers := func(q *quark.Query[User]) *quark.Query[User] {
return q.Where("active", "=", true)
}
users, _ := quark.For[User](ctx, client).Apply(activeUsers).List()
func (*Query[T]) AsSubquery ¶ added in v0.4.0
AsSubquery captures the current Query[T] as a renderable Subquery. The SELECT is rendered eagerly: identifier validation, soft-delete and tenant predicates, JOINs, GROUP BY, HAVING, ORDER BY, LIMIT all run at AsSubquery time.
The captured SQL uses '?' as the bind marker; the outer query swaps it for the dialect's placeholder when the wrapping Expr is rendered. The SELECT cols can be customised via the standard Select() before capture — typical use is `Select("id")` for an `IN (subquery)` shape.
Pessimistic lock options (`ForUpdate` / `ForShare` / `SkipLocked` / `NoWait`) are explicitly rejected on the inner query — MSSQL emits table hints (`WITH (UPDLOCK, ROWLOCK)`) inline in the FROM clause that are not legal inside a subquery context, and PG / MySQL / Oracle's `FOR UPDATE` suffix on a subquery is technically valid but misleading when the outer caller already drives row locking. Acquire locks on the outer query instead.
func (*Query[T]) Create ¶
Create inserts a new record. The entity must have a db tag on fields to be persisted. Returns with the ID set from the database. Create inserts a new record and recursively saves associations.
func (*Query[T]) CreateBatch ¶
CreateBatch inserts multiple records in a single SQL statement using bulk VALUES. Each entity gets its PK populated when the dialect supports RETURNING; otherwise PKs are left at their zero value (callers can re-query if needed).
Example:
users := []*User{{Name: "Alice"}, {Name: "Bob"}}
err := quark.For[User](ctx, client).CreateBatch(users)
func (*Query[T]) Cursor ¶
Cursor returns a Cursor for manual iteration over large result sets. The Cursor must be closed after use (defer cursor.Close()).
Example:
cursor, err := quark.For[User](ctx, client).Where("active", "=", true).Cursor()
if err != nil { log.Fatal(err) }
defer cursor.Close()
for cursor.Next() {
var user User
if err := cursor.Scan(&user); err != nil { break }
process(user)
}
func (*Query[T]) Delete ¶
Delete performs a soft delete by setting deleted_at = NOW(). If the model doesn't have deleted_at field, performs hard delete. Returns the number of rows affected.
func (*Query[T]) DeleteBatch ¶
DeleteBatch deletes multiple records by their primary key values using DELETE … WHERE pk IN (…) statements, chunked to batchChunkSize to stay within every supported dialect's placeholder limit (Oracle: 1000, MSSQL: ~2100, others: larger).
Example:
affected, err := quark.For[User](ctx, client).DeleteBatch([]any{1, 2, 3})
func (*Query[T]) DeleteBy ¶
DeleteBy performs a hard delete with WHERE conditions. Requires Where clause for safety.
func (*Query[T]) Except ¶ added in v0.4.0
Except renders `EXCEPT` (or `MINUS` on Oracle). Not supported on MySQL / MariaDB.
func (*Query[T]) ForShare ¶ added in v0.4.0
ForShare marks the query so the emitted SELECT acquires a shared read lock. Composes with SkipLocked / NoWait. Not supported on SQLite; MSSQL approximates with HOLDLOCK; PG / MySQL 8+ / MariaDB / Oracle support it natively.
func (*Query[T]) ForUpdate ¶ added in v0.4.0
ForUpdate marks the query so the emitted SELECT acquires a row-level FOR UPDATE lock. Composes with SkipLocked / NoWait. Returns the receiver (no error) so it chains naturally; SQL surface failures (unsupported dialect, invalid combination) surface at execution time.
rows, err := quark.For[Order](ctx, client).
Where("status", "=", "pending").
ForUpdate().
Limit(50).
List()
func (*Query[T]) HardDelete ¶
HardDelete permanently deletes the entity by its primary key.
func (*Query[T]) Having ¶
Having adds a HAVING condition (used together with GroupBy).
The column argument is validated as a plain identifier — no parentheses, function calls, or expressions. To filter on aggregates such as COUNT(*) or SUM(col), use HavingAggregate instead.
func (*Query[T]) HavingAggregate ¶ added in v0.4.0
HavingAggregate adds a HAVING condition over an aggregate function.
fn must be one of COUNT, SUM, AVG, MIN, MAX (case-insensitive). column is either a regular column name (validated through SQLGuard) or "*" — only accepted with COUNT, since "SUM(*)" / "AVG(*)" / etc. are not valid SQL. operator goes through the same whitelist Where uses (=, !=, <>, <, <=, >, >=, IN, NOT IN, BETWEEN, IS [NOT] NULL, LIKE, ILIKE).
Example:
groups, err := quark.For[Order](ctx, client).
GroupBy("status").
HavingAggregate("COUNT", "*", ">", 5).
List()
// emitted: ... GROUP BY "status" HAVING COUNT(*) > $1
This closes the historic Having(column, op, value) limitation where the column went through ValidateIdentifier and aggregates therefore could not be expressed without RawQuery. The structured-AST form Having(Func("count", Col("*")), ">", 5) arrives with the full Phase 2 AST; HavingAggregate is the focused, type-safe shortcut for the overwhelmingly common case.
func (*Query[T]) HavingExpr ¶ added in v0.4.0
HavingExpr adds a HAVING condition built from the Expr AST. Same rendering pipeline as WhereExpr; useful for aggregate predicates that need the full composition surface (Func("COUNT", Col("*")) > Lit(5), and so on).
func (*Query[T]) Intersect ¶ added in v0.4.0
Intersect renders `INTERSECT` between the base and the operand. Not supported on MySQL / MariaDB — those return ErrUnsupportedFeature from setOpKeyword at render time.
func (*Query[T]) Iter ¶
Iter executes the query and iterates over results one by one. Uses streaming to handle large datasets without loading all into memory.
Example:
err := quark.For[User](ctx, client).Where("active", "=", true).Iter(func(user User) error {
process(user)
return nil
})
func (*Query[T]) Join ¶
func (q *Query[T]) Join(table string) *JoinBuilder[T]
Join opens an INNER JOIN against `table`. Complete the JOIN with `.On(left, op, right)` (typed binary identifier comparison) or `.OnRaw(onClause)` (free-form, validated through the same identifier grammar as `On`).
The structured form replaces the v0.3.x string-raw `Join(table, on)` signature; see `MIGRATION_v0.4.0.md` for the mechanical rewrite.
func (*Query[T]) LeftJoin ¶
func (q *Query[T]) LeftJoin(table string) *JoinBuilder[T]
LeftJoin opens a LEFT JOIN. See Join for ON-clause grammar.
func (*Query[T]) List ¶
List executes the query and returns all matching rows. If Limit() is not called, uses a safe default (100) to prevent OOM. Use Iter() for unbounded streaming or Paginate() for large datasets.
func (*Query[T]) MustAsSubquery ¶ added in v0.4.0
MustAsSubquery is the panic-on-error variant of AsSubquery for use in expression composition where errors would otherwise have to be threaded through the AST. The error is realistic (invalid identifier, etc.) only when the inner query is malformed; for well-formed inputs it never triggers.
func (*Query[T]) NoWait ¶ added in v0.4.0
NoWait tells the database to fail immediately if any matching row is already locked by another transaction. Combine with ForUpdate / ForShare. Implementation-defined per dialect.
func (*Query[T]) OnlyTrashed ¶ added in v0.3.0
OnlyTrashed returns a query that filters down to soft-deleted rows (deleted_at IS NOT NULL) so callers can list, restore, or hard-delete the trash. A no-op when the model has no deleted_at column.
func (*Query[T]) Or ¶
Or adds an OR condition group. The callback receives a fresh Query to build conditions. All conditions within the callback are grouped with AND and joined to the outer query with OR.
Example:
quark.For[User](ctx, client).
Where("active", "=", true).
Or(func(q *Query[User]) *Query[User] {
return q.Where("role", "=", "admin").Where("role", "=", "superadmin")
}).List()
Generates: WHERE "active" = $1 OR ("role" = $2 AND "role" = $3)
Under the RowLevelSecurity tenant strategy the OR group inherits the parent's tenant_id predicate so it cannot escape isolation via SQL operator precedence.
func (*Query[T]) Paginate ¶
Paginate executes the query with pagination. Returns current page, total count, and error.
Example:
page, err := quark.For[User](ctx, client).Paginate(100, 0) // 100 per page, page 0
page, err := quark.For[User](ctx, client).Where("active", "=", true).Paginate(50, 2)
func (*Query[T]) Restore ¶ added in v0.3.0
Restore clears the deleted_at column on the row identified by entity's primary key, "untrashing" it. Returns the number of rows affected.
Restore implicitly scopes to currently-trashed rows (deleted_at IS NOT NULL): a Restore on a row that was never deleted is a 0-row no-op rather than a corrupting NULL-write. Useful as the inverse of Delete in admin flows.
Phase-1 F1-5. Returns ErrInvalidModel when the model has no deleted_at.
func (*Query[T]) RightJoin ¶
func (q *Query[T]) RightJoin(table string) *JoinBuilder[T]
RightJoin opens a RIGHT JOIN. See Join for ON-clause grammar.
func (*Query[T]) SelectExpr ¶ added in v0.4.0
SelectExpr adds an AST projection to the SELECT list, aliased as `alias`. Use it for window functions, scalar subqueries, or any expression the plain `Select(cols...)` API can't model:
q := quark.For[Order](ctx, client).
SelectExpr("rank", quark.Over(quark.Rank(),
quark.NewWindow().
PartitionBy(quark.Col("status")).
OrderBy(quark.Col("amount"), true))).
SelectExpr("running_total", quark.Over(
quark.Func("SUM", quark.Col("amount")),
quark.NewWindow().OrderBy(quark.Col("id"), false)))
The expression is rendered against a `qmark`-emitting dialect at SelectExpr time, so the inner '?' markers are reindexed to the outer dialect's placeholder syntax when buildSelect runs. The args land in the args slice between any CTE args and the WHERE args — matching the SQL-surface order of the SELECT projection.
Composing SelectExpr with the plain Select(cols...) is allowed: the regular columns render first, the AST projections after, comma- separated. If neither is set, the SELECT defaults to '*'.
func (*Query[T]) SkipLocked ¶ added in v0.4.0
SkipLocked tells the database to skip rows that are already locked by another transaction instead of blocking on them. Combine with ForUpdate / ForShare. Implementation-defined per dialect.
func (*Query[T]) Track ¶ added in v0.3.0
func (q *Query[T]) Track() *TrackedQuery[T]
Track returns a TrackedQuery whose Find/First/List yield *Tracked[T] values carrying a column-value snapshot. Call Save on the result to emit an UPDATE that touches only the columns whose values changed since load — the permanent fix for the zero-value trap (P0-4).
Track is opt-in. Existing Find/First/List remain unchanged.
func (*Query[T]) Union ¶ added in v0.4.0
Union appends a UNION (DISTINCT) operand to the query. The combined statement renders flat — `SELECT ... UNION SELECT ...` — without parentheses around the operands, since SQLite's compound-select grammar rejects parenthesised operands. The flat form is the portable shape across all six target dialects.
Identifier validation runs eagerly on the operand so a malformed other-query surfaces at attach time, not at the outer query's exec time. Outer-query `OrderBy` / `Limit` apply to the combined result (the SQL standard binding); the operand cannot have its own ORDER BY / LIMIT (rejected with `ErrUnsupportedFeature`). See attachSetOp for the full operand restriction list.
func (*Query[T]) UnionAll ¶ added in v0.4.0
UnionAll is the multiset variant: `UNION ALL` keeps duplicate rows.
func (*Query[T]) Unscoped ¶
Unscoped ignores soft-delete filters for the query, returning both trashed and non-trashed rows. Equivalent to WithTrashed; kept for backward compatibility.
func (*Query[T]) Update ¶
Update updates the entity by its primary key with partial-update semantics: only fields whose value is non-zero for their type are written.
CAUTION — zero-value trap (P0-4 — pending dirty tracking in Phase 1): because zero values are skipped, calling Update cannot write false to a bool, 0 to an integer, "" to a string, or nil to a pointer/slice/map. To write a zero value explicitly, use UpdateFields or UpdateMap. When Update detects skipped zero-value fields it logs a WARN line so callers notice the silent skip.
Any Where() conditions are merged into the WHERE clause alongside the PK. Returns the number of rows affected. Recursively saves associations.
func (*Query[T]) UpdateBatch ¶
UpdateBatch updates multiple records by their primary keys within a single transaction. Each entity undergoes a partial update: zero-value fields are skipped (same semantics as Update). A transaction is used to guarantee atomicity across all rows.
Example:
err := quark.For[User](ctx, client).UpdateBatch(users)
func (*Query[T]) UpdateFields ¶ added in v0.3.0
UpdateFields updates only the named fields on the entity, bypassing the zero-value filter that Update applies. This is the recommended API when you need to write false / 0 / "" / nil to a column — values that Update would silently skip.
fields are matched against struct field db tags only — the same identifier resolution as Update and Find. Listing a struct field name without a db tag returns ErrInvalidQuery: there is one canonical name per column and we don't accept aliases here, to keep the resolution unambiguous.
The primary key is never overwritten; listing a PK column returns an error. If the client is configured with the RowLevelSecurity tenant strategy, the tenant column is injected before the SET clause is built; callers do not need to (and should not) list it explicitly.
Example:
user := User{ID: 42, Active: false}
rows, err := quark.For[User](ctx, client).UpdateFields(&user, "active")
// emitted: UPDATE "users" SET "active" = $1 WHERE "id" = $2 args=[false, 42]
Returns the number of rows affected.
func (*Query[T]) UpdateMap ¶
UpdateMap updates fields using a map (for partial updates without full entity). Requires Where clause for safety. Returns the number of rows affected.
func (*Query[T]) Upsert ¶
Upsert inserts or updates a record depending on whether a conflict occurs on conflictCols. updateCols specifies which columns to update on conflict; if empty, all non-conflict columns are updated.
Example:
quark.For[User](ctx, client).Upsert(&user, []string{"email"}, []string{"name", "updated_at"})
func (*Query[T]) UpsertBatch ¶
UpsertBatch inserts or updates multiple records in a single batch operation. conflictCols defines uniqueness (e.g. primary key or unique index columns). updateCols defines which columns to update on conflict; empty = all non-conflict columns.
Dialect strategies:
- Postgres / SQLite / MySQL / MariaDB: multi-row INSERT … ON CONFLICT / ON DUPLICATE KEY
- MSSQL: single MERGE … USING (VALUES …) AS src(…)
- Oracle: N individual MERGE statements (Oracle IDENTITY restriction prevents bulk MERGE)
Example:
err := quark.For[User](ctx, client).UpsertBatch(users, []string{"email"}, []string{"name"})
func (*Query[T]) WhereBetween ¶
WhereBetween adds a WHERE ... BETWEEN condition.
func (*Query[T]) WhereExpr ¶ added in v0.4.0
WhereExpr adds a WHERE condition built from a composable Expr AST.
The AST is rendered against the active dialect at call time, producing a fragment with '?' bind markers plus the args. Storage and execution reuse the existing raw-fragment slot in condition: buildWhereClause substitutes each '?' for the dialect placeholder at the correct argIndex, so the AST stays dialect-agnostic at construction time and integrates cleanly with WhereJSON, Or, and the rest of the builder.
Errors raised during ToSQL — unknown function names, invalid identifiers, invalid operators, empty IN lists — are stashed on the query and surface at execution time wrapping ErrInvalidQuery.
Example:
q := quark.For[User](ctx, client).WhereExpr(
quark.Or(
quark.Eq(quark.Col("role"), quark.Lit("admin")),
quark.And(
quark.Gt(quark.Col("logins"), quark.Lit(10)),
quark.Eq(quark.Col("verified"), quark.Lit(true)),
),
),
)
func (*Query[T]) WhereJSON ¶
WhereJSON adds a WHERE condition for a JSON field. column is the JSON column name, path is a dotted key path within the JSON object (e.g. "user.name"). The path is validated and bound as a parameter — never interpolated into the SQL surface — so it cannot carry SQL injection. See guard.ValidateJSONPath for the accepted grammar.
On invalid path the error is stashed on the query and surfaces at execution time (List, First, etc.), wrapping ErrInvalidJSONPath.
func (*Query[T]) WhereNot ¶
WhereNot adds a WHERE NOT condition with AND logic.
Example:
quark.For[User](ctx, client).WhereNot("active", "=", false).List()
Generates: WHERE NOT ("active" = $1)
func (*Query[T]) WhereSubquery ¶
WhereSubquery adds a WHERE column operator (subquery) condition. The subquery is a raw SQL string. Use this only when AllowRawQueries is enabled.
Example:
sub := "SELECT MAX(id) FROM orders WHERE status = 'open'"
quark.For[User](ctx, client).WhereSubquery("id", "IN", sub).List()
func (*Query[T]) With ¶ added in v0.4.0
With attaches a non-recursive CTE to the query. The CTE renders as `WITH <name> AS (<inner>)` before the outer SELECT, and the outer query can reference the CTE by name in JOIN clauses.
Example:
topOrders, _ := quark.For[Order](ctx, client).
Where("amount", ">", 100).
Select("user_id", "amount").
AsSubquery()
users, err := quark.For[User](ctx, client).
With("top_orders", topOrders).
Join("top_orders", "users.id = top_orders.user_id").
Limit(50).
List()
Multiple With calls compose: the entries render comma-separated in the order they were added. If any entry is recursive, the prefix becomes `WITH RECURSIVE ...`.
func (*Query[T]) WithRecursive ¶ added in v0.4.0
WithRecursive is the recursive form. Emits `WITH RECURSIVE` (or just promotes the prefix when at least one of the previously-added entries is recursive). The inner Subquery is responsible for shaping the recursive body — typically a `UNION ALL` between a base case and a step that references the CTE name. quark's typed Subquery surface doesn't yet model UNION (F2-set), so practical recursive use today is limited to engines/cases where the Subquery body can be constructed from a single SELECT — full recursive support is the motivating use case for F2-set.
func (*Query[T]) WithTrashed ¶ added in v0.3.0
WithTrashed returns a query that includes both soft-deleted (trashed) and live rows — the same effect as Unscoped, named for parity with the scope-driven idiom that other ORMs use. Only meaningful when the model carries a deleted_at column.
type QueryEvent ¶
type QueryEvent struct {
SQL string
Args []any
Duration time.Duration
Rows int64
Error error
Table string
Operation string // "SELECT", "INSERT", "UPDATE", "DELETE"
}
QueryEvent represents a executed query.
type QueryFunc ¶
type QueryFunc func(ctx context.Context, exec Executor, sqlStr string, args []any) (*sql.Rows, error)
QueryFunc is the signature for SQL query functions used by middleware.
type QueryObserver ¶
type QueryObserver interface {
ObserveQuery(event QueryEvent)
}
QueryObserver is called after each query execution. Use this for logging, metrics, auditing, etc.
type QueryRowFunc ¶
QueryRowFunc is the signature for SQL single-row query functions used by middleware.
type RelationMeta ¶
type RelationMeta = schema.RelationMeta
RelationMeta is the metadata for a model relation.
type Routine ¶
type Routine[T any] struct { // contains filtered or unexported fields }
Routine is a builder for executing database functions and stored procedures that return results (table-valued functions or scalar functions).
func NewRoutine ¶
func NewRoutine[T any](ctx context.Context, provider ClientProvider, routine string, args ...any) *Routine[T]
NewRoutine creates a new Routine builder for the given procedure/function.
type SQLGuard ¶
SQLGuard re-exports the internal guard.SQLGuard. It provides SQL injection prevention utilities for Quark ORM.
func NewSQLGuard ¶
func NewSQLGuard() *SQLGuard
NewSQLGuard creates a new SQLGuard with default settings.
type SQLiteDialect ¶
type SQLiteDialect struct {
// contains filtered or unexported fields
}
SQLiteDialect implements the SQLite dialect.
func (*SQLiteDialect) AlterTableAddColumn ¶
func (s *SQLiteDialect) AlterTableAddColumn(table, column, dataType string) string
func (*SQLiteDialect) AlterTableAlterColumn ¶
func (s *SQLiteDialect) AlterTableAlterColumn(table, column, newDataType string) string
func (*SQLiteDialect) AlterTableDropColumn ¶
func (s *SQLiteDialect) AlterTableDropColumn(table, column string) string
func (*SQLiteDialect) BuildProcedureCall ¶
func (s *SQLiteDialect) BuildProcedureCall(procedure string, argCount int) string
func (*SQLiteDialect) BuildRoutineQuery ¶
func (s *SQLiteDialect) BuildRoutineQuery(routine string, argCount int) string
func (*SQLiteDialect) CurrentTimestamp ¶
func (s *SQLiteDialect) CurrentTimestamp() string
func (*SQLiteDialect) JSONExtract ¶
func (s *SQLiteDialect) JSONExtract(column, path string) (string, []any, error)
func (*SQLiteDialect) LastInsertIDQuery ¶
func (s *SQLiteDialect) LastInsertIDQuery(table, pkColumn string) string
func (*SQLiteDialect) LimitOffset ¶
func (s *SQLiteDialect) LimitOffset(limit, offset int) string
func (*SQLiteDialect) LockSuffix ¶ added in v0.4.0
func (s *SQLiteDialect) LockSuffix(opts LockOptions) (string, string, error)
SQLite has no row-level pessimistic-lock primitive — locking is transaction-scoped via BEGIN IMMEDIATE / EXCLUSIVE. Return ErrUnsupportedFeature so callers can branch by dialect or fall back.
func (*SQLiteDialect) Placeholder ¶
func (s *SQLiteDialect) Placeholder(index int) string
func (*SQLiteDialect) Placeholders ¶
func (s *SQLiteDialect) Placeholders(n int) []string
func (*SQLiteDialect) Quote ¶
func (s *SQLiteDialect) Quote(identifier string) string
func (*SQLiteDialect) RenameColumn ¶
func (s *SQLiteDialect) RenameColumn(table, oldName, newName string) string
func (*SQLiteDialect) RenameTable ¶
func (s *SQLiteDialect) RenameTable(oldName, newName string) string
func (*SQLiteDialect) Returning ¶
func (s *SQLiteDialect) Returning(columns ...string) string
func (*SQLiteDialect) SupportsLastInsertID ¶
func (s *SQLiteDialect) SupportsLastInsertID() bool
func (*SQLiteDialect) SupportsReturning ¶
func (s *SQLiteDialect) SupportsReturning() bool
func (*SQLiteDialect) SupportsTransactionalDDL ¶
func (s *SQLiteDialect) SupportsTransactionalDDL() bool
type Scope ¶
Scope is a reusable query modifier — a function that receives and returns a *Query[T]. Scopes can be composed via Apply().
type Subquery ¶ added in v0.4.0
type Subquery struct {
// contains filtered or unexported fields
}
Subquery is a rendered SELECT that can be embedded inside another query through the Expr AST (Sub, Exists, NotExists, InSub, NotInSub).
A subquery is built like any other query — `For[T](...).Where(...)` — and then captured with `AsSubquery()`. The capture eagerly renders the SELECT using the active dialect's identifier quoting but with `?` as the bind marker, so the outer query's `buildWhereClause` can swap each `?` for the dialect's placeholder syntax at the correct arg index when the AST is rendered.
This contract matches the rest of the AST: leaves emit `?`, `substitutePathMarkers` does the placeholder rewrite, args are threaded through `condition.extraArgs`. So a subquery is just another Expr leaf.
type SyncOptions ¶
type SyncOptions struct {
DryRun bool // If true, logs the SQL but doesn't execute it.
NoTransaction bool // If true, doesn't wrap the sync in a transaction.
}
SyncOptions configures the behavior of the Sync operation.
type TenantConfig ¶
type TenantConfig struct {
Strategy TenantStrategy
MaxCachedPools int // Maximum number of DB connection pools to keep open (for DatabasePerTenant)
BaseClient *Client // Used for SchemaPerTenant and RowLevelSecurity
TenantColumn string // Column name for RowLevelSecurity, default is "tenant_id"
}
TenantConfig configures the TenantRouter.
func DefaultTenantConfig ¶
func DefaultTenantConfig() TenantConfig
DefaultTenantConfig provides sensible defaults.
type TenantRouter ¶
type TenantRouter struct {
// contains filtered or unexported fields
}
TenantRouter manages dynamic database connections or queries for different tenants.
func NewTenantRouter ¶
func NewTenantRouter( config TenantConfig, resolver func(ctx context.Context) string, factory func(tenantID string) (*Client, error), ) *TenantRouter
NewTenantRouter creates a new router for multi-tenant database access.
func (*TenantRouter) ActiveTenants ¶
func (r *TenantRouter) ActiveTenants() []string
ActiveTenants returns a list of active tenant connections in the cache.
func (*TenantRouter) GetClient ¶
func (r *TenantRouter) GetClient(ctx context.Context) (*Client, error)
GetClient resolves the tenant ID from the context and returns the corresponding Client. It implements the ClientProvider interface so it can be used with For[T].
func (*TenantRouter) ResolveTenant ¶
func (r *TenantRouter) ResolveTenant(ctx context.Context) (string, error)
ResolveTenant returns the tenant ID for the context.
type TenantStrategy ¶
type TenantStrategy int
TenantStrategy defines how multi-tenancy is handled.
const ( // DatabasePerTenant uses a separate database connection pool per tenant. // This requires an LRU cache to prevent connection exhaustion. DatabasePerTenant TenantStrategy = iota // SchemaPerTenant uses a single database connection pool but prefixes // the table name with the tenant ID (e.g. "tenant_acme.users"). SchemaPerTenant // RowLevelSecurity uses a single database connection pool and injects // a "WHERE tenant_id = ?" condition to every query. RowLevelSecurity )
type Tracked ¶ added in v0.3.0
type Tracked[T any] struct { // Entity is the loaded value. Mutate fields on it directly; Save will // detect the changes and write only those columns. Entity *T // contains filtered or unexported fields }
Tracked wraps a loaded entity with a snapshot of its column values, so a later Save can emit an UPDATE limited to the fields that actually changed.
Active Record + dirty tracking ligero (Phase 1): the snapshot lives on the wrapper, not in the Client, so there is no shared map to grow or evict. Each Tracked carries the metadata it needs (client, table, dialect, pk, meta) to run Save without the caller threading state.
Tracked is the permanent fix for P0-4: a bool / int / string / pointer can be set to its zero value and Save will write it because the diff is taken against the snapshot, not against "is this field non-zero?".
func (*Tracked[T]) Changed ¶ added in v0.3.0
Changed reports the names (db tags) of columns whose value differs between Entity now and the snapshot taken at load time. Useful for tests and observability; Save uses the same comparison internally.
func (*Tracked[T]) Save ¶ added in v0.3.0
Save updates the row identified by Entity's primary key, writing only the columns whose value differs from the load-time snapshot. If nothing changed, Save returns (0, nil) without touching the database.
Returns the number of rows affected. Refreshes the internal snapshot on success so subsequent Save calls diff against the new state.
type TrackedQuery ¶ added in v0.3.0
type TrackedQuery[T any] struct { // contains filtered or unexported fields }
TrackedQuery is the lightweight wrapper Query[T].Track() returns. It re-issues Find/First/List on the underlying query and wraps each loaded entity with a snapshot for later dirty-tracked Save.
func (*TrackedQuery[T]) Find ¶ added in v0.3.0
func (tq *TrackedQuery[T]) Find(id any) (*Tracked[T], error)
Find loads a single entity by primary key and wraps it in a Tracked.
func (*TrackedQuery[T]) First ¶ added in v0.3.0
func (tq *TrackedQuery[T]) First() (*Tracked[T], error)
First returns the first matching entity wrapped in a Tracked.
func (*TrackedQuery[T]) List ¶ added in v0.3.0
func (tq *TrackedQuery[T]) List() ([]*Tracked[T], error)
List loads all matching entities wrapped in Tracked values.
type Tx ¶
type Tx struct {
// contains filtered or unexported fields
}
Tx wraps *sql.Tx and provides transactional query execution. It shares dialect, guard, observers, and limits from the parent Client.
func (*Tx) ReleaseSavepoint ¶
ReleaseSavepoint releases the named savepoint.
func (*Tx) RollbackTo ¶
RollbackTo rolls back to the named savepoint.
type TypeMapper ¶ added in v0.3.0
type TypeMapper = migrate.TypeMapper
TypeMapper produces a dialect-specific SQL type for a Go type. The caller supplies the dialect name (lower-case: "postgres", "mysql", "mariadb", "sqlite", "mssql", "oracle") and the sizing hints from the field's tag.
type TypeOptions ¶ added in v0.3.0
type TypeOptions = migrate.TypeOptions
TypeOptions carries the SQL-type sizing hints parsed from a struct's db tag — `size=N`, `precision=N`, `scale=N` — plus a flag indicating whether the column is the primary key. A zero value for any field means "use the dialect default for the Go type".
type Window ¶ added in v0.4.0
type Window struct {
// contains filtered or unexported fields
}
Window models the `OVER (...)` clause for a window-function expression. Build with NewWindow() and chain PartitionBy / OrderBy. The chain is immutable: each method returns a fresh copy so a Window definition can be reused across multiple Over() calls without aliasing.
func NewWindow ¶ added in v0.4.0
func NewWindow() *Window
NewWindow returns an empty Window. An empty Window renders as `OVER ()` — sometimes legitimate (e.g. `COUNT(*) OVER ()` for a running grand total), so it's not an error.
func (*Window) OrderBy ¶ added in v0.4.0
OrderBy adds an order entry. Set desc=true for descending; the second argument is the conventional bool toggle to keep the API tight (no "ASC"/"DESC" stringly-typed argument).
func (*Window) PartitionBy ¶ added in v0.4.0
PartitionBy adds one or more partition expressions. Identifiers go through SQLGuard at render time — pass `Col("status")`, not the raw column string.
Source Files
¶
- cache.go
- client.go
- cte.go
- cursor.go
- db_errors.go
- dialect.go
- dialect_lock.go
- dirty_track.go
- errors.go
- events.go
- expr.go
- hooks.go
- json_field.go
- locking.go
- migrator.go
- model.go
- nullable.go
- optimistic_locking.go
- option.go
- page.go
- preload_loaders.go
- preload_tree.go
- query_builder.go
- query_crud.go
- query_exec.go
- routine_builder.go
- security.go
- setop.go
- soft_delete.go
- subquery.go
- sync.go
- tenant_router.go
- tx.go
- type_mapper.go
- validator.go
- window.go
Directories
¶
| Path | Synopsis |
|---|---|
|
cache
|
|
|
examples
|
|
|
mssql
command
|
|
|
mysql
command
|
|
|
oracle
command
|
|
|
postgres
command
|
|
|
sqlite
command
|
|
|
internal
|
|
|
guard
Package guard provides SQL injection prevention utilities for Quark ORM.
|
Package guard provides SQL injection prevention utilities for Quark ORM. |
|
migrate
Package migrate provides internal utilities for database schema migrations.
|
Package migrate provides internal utilities for database schema migrations. |
|
schema
Package schema provides struct reflection and model metadata caching for Quark ORM.
|
Package schema provides struct reflection and model metadata caching for Quark ORM. |