mirror of
https://github.com/DeBrosOfficial/network.git
synced 2026-01-30 14:13:04 +00:00
193 lines
4.6 KiB
Go
193 lines
4.6 KiB
Go
package rqlite
|
|
|
|
// query_builder.go implements a fluent SQL query builder for SELECT statements.
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// QueryBuilder implements a fluent SELECT builder with joins, where, etc.
|
|
type QueryBuilder struct {
|
|
exec executor
|
|
table string
|
|
alias string
|
|
selects []string
|
|
|
|
joins []joinClause
|
|
wheres []whereClause
|
|
|
|
groupBys []string
|
|
orderBys []string
|
|
limit *int
|
|
offset *int
|
|
}
|
|
|
|
// joinClause represents INNER/LEFT/etc joins.
|
|
type joinClause struct {
|
|
kind string // "INNER", "LEFT", "JOIN" (default)
|
|
table string
|
|
on string
|
|
}
|
|
|
|
// whereClause holds an expression and args with a conjunction.
|
|
type whereClause struct {
|
|
conj string // "AND" or "OR"
|
|
expr string
|
|
args []any
|
|
}
|
|
|
|
// newQueryBuilder creates a new QueryBuilder for the given table.
|
|
func newQueryBuilder(exec executor, table string) *QueryBuilder {
|
|
return &QueryBuilder{
|
|
exec: exec,
|
|
table: table,
|
|
}
|
|
}
|
|
|
|
// Select specifies columns to select.
|
|
func (qb *QueryBuilder) Select(cols ...string) *QueryBuilder {
|
|
qb.selects = append(qb.selects, cols...)
|
|
return qb
|
|
}
|
|
|
|
// Alias sets an alias for the main table.
|
|
func (qb *QueryBuilder) Alias(a string) *QueryBuilder {
|
|
qb.alias = a
|
|
return qb
|
|
}
|
|
|
|
// Where adds a WHERE clause (same as AndWhere).
|
|
func (qb *QueryBuilder) Where(expr string, args ...any) *QueryBuilder {
|
|
return qb.AndWhere(expr, args...)
|
|
}
|
|
|
|
// AndWhere adds an AND WHERE clause.
|
|
func (qb *QueryBuilder) AndWhere(expr string, args ...any) *QueryBuilder {
|
|
qb.wheres = append(qb.wheres, whereClause{conj: "AND", expr: expr, args: args})
|
|
return qb
|
|
}
|
|
|
|
// OrWhere adds an OR WHERE clause.
|
|
func (qb *QueryBuilder) OrWhere(expr string, args ...any) *QueryBuilder {
|
|
qb.wheres = append(qb.wheres, whereClause{conj: "OR", expr: expr, args: args})
|
|
return qb
|
|
}
|
|
|
|
// InnerJoin adds an INNER JOIN clause.
|
|
func (qb *QueryBuilder) InnerJoin(table string, on string) *QueryBuilder {
|
|
qb.joins = append(qb.joins, joinClause{kind: "INNER", table: table, on: on})
|
|
return qb
|
|
}
|
|
|
|
// LeftJoin adds a LEFT JOIN clause.
|
|
func (qb *QueryBuilder) LeftJoin(table string, on string) *QueryBuilder {
|
|
qb.joins = append(qb.joins, joinClause{kind: "LEFT", table: table, on: on})
|
|
return qb
|
|
}
|
|
|
|
// Join adds a JOIN clause.
|
|
func (qb *QueryBuilder) Join(table string, on string) *QueryBuilder {
|
|
qb.joins = append(qb.joins, joinClause{kind: "JOIN", table: table, on: on})
|
|
return qb
|
|
}
|
|
|
|
// GroupBy adds GROUP BY columns.
|
|
func (qb *QueryBuilder) GroupBy(cols ...string) *QueryBuilder {
|
|
qb.groupBys = append(qb.groupBys, cols...)
|
|
return qb
|
|
}
|
|
|
|
// OrderBy adds ORDER BY expressions.
|
|
func (qb *QueryBuilder) OrderBy(exprs ...string) *QueryBuilder {
|
|
qb.orderBys = append(qb.orderBys, exprs...)
|
|
return qb
|
|
}
|
|
|
|
// Limit sets the LIMIT clause.
|
|
func (qb *QueryBuilder) Limit(n int) *QueryBuilder {
|
|
qb.limit = &n
|
|
return qb
|
|
}
|
|
|
|
// Offset sets the OFFSET clause.
|
|
func (qb *QueryBuilder) Offset(n int) *QueryBuilder {
|
|
qb.offset = &n
|
|
return qb
|
|
}
|
|
|
|
// Build returns the SQL string and args for a SELECT.
|
|
func (qb *QueryBuilder) Build() (string, []any) {
|
|
cols := "*"
|
|
if len(qb.selects) > 0 {
|
|
cols = strings.Join(qb.selects, ", ")
|
|
}
|
|
base := fmt.Sprintf("SELECT %s FROM %s", cols, qb.table)
|
|
if qb.alias != "" {
|
|
base += " AS " + qb.alias
|
|
}
|
|
|
|
args := make([]any, 0, 16)
|
|
for _, j := range qb.joins {
|
|
base += fmt.Sprintf(" %s JOIN %s ON %s", j.kind, j.table, j.on)
|
|
}
|
|
|
|
if len(qb.wheres) > 0 {
|
|
base += " WHERE "
|
|
for i, w := range qb.wheres {
|
|
if i > 0 {
|
|
base += " " + w.conj + " "
|
|
}
|
|
base += "(" + w.expr + ")"
|
|
args = append(args, w.args...)
|
|
}
|
|
}
|
|
|
|
if len(qb.groupBys) > 0 {
|
|
base += " GROUP BY " + strings.Join(qb.groupBys, ", ")
|
|
}
|
|
if len(qb.orderBys) > 0 {
|
|
base += " ORDER BY " + strings.Join(qb.orderBys, ", ")
|
|
}
|
|
if qb.limit != nil {
|
|
base += fmt.Sprintf(" LIMIT %d", *qb.limit)
|
|
}
|
|
if qb.offset != nil {
|
|
base += fmt.Sprintf(" OFFSET %d", *qb.offset)
|
|
}
|
|
return base, args
|
|
}
|
|
|
|
// GetMany executes the built query and scans into dest (pointer to slice).
|
|
func (qb *QueryBuilder) GetMany(ctx context.Context, dest any) error {
|
|
sqlStr, args := qb.Build()
|
|
rows, err := qb.exec.QueryContext(ctx, sqlStr, args...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer rows.Close()
|
|
return scanIntoDest(rows, dest)
|
|
}
|
|
|
|
// GetOne executes the built query and scans into dest (pointer to struct or map) with LIMIT 1.
|
|
func (qb *QueryBuilder) GetOne(ctx context.Context, dest any) error {
|
|
limit := 1
|
|
if qb.limit == nil {
|
|
qb.limit = &limit
|
|
} else if qb.limit != nil && *qb.limit > 1 {
|
|
qb.limit = &limit
|
|
}
|
|
sqlStr, args := qb.Build()
|
|
rows, err := qb.exec.QueryContext(ctx, sqlStr, args...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer rows.Close()
|
|
if !rows.Next() {
|
|
return sql.ErrNoRows
|
|
}
|
|
return scanIntoSingle(rows, dest)
|
|
}
|