Files
bobiqq c231be0094
build-and-push / detect (push) Successful in 7s
build-and-push / build (${{ fromJSON(needs.detect.outputs.services) }}) (push) Failing after 3m33s
services
2026-08-26 11:04:23 +03:00

391 lines
8.5 KiB
Go

package dbr
import (
"context"
"database/sql"
"strconv"
)
// SelectStmt builds `SELECT ...`.
type SelectStmt struct {
runner
EventReceiver
Dialect
raw
IsDistinct bool
Column []interface{}
Table interface{}
JoinTable []Builder
WhereCond []Builder
Group []Builder
HavingCond []Builder
Order []Builder
Suffixes []Builder
LimitCount int64
OffsetCount int64
comments Comments
}
type SelectBuilder = SelectStmt
func (b *SelectStmt) Build(d Dialect, buf Buffer) error {
if b.raw.Query != "" {
return b.raw.Build(d, buf)
}
if len(b.Column) == 0 {
return ErrColumnNotSpecified
}
err := b.comments.Build(d, buf)
if err != nil {
return err
}
buf.WriteString("SELECT ")
if b.IsDistinct {
buf.WriteString("DISTINCT ")
}
for i, col := range b.Column {
if i > 0 {
buf.WriteString(", ")
}
switch col := col.(type) {
case string:
// FIXME: no quote ident
buf.WriteString(col)
default:
buf.WriteString(placeholder)
buf.WriteValue(col)
}
}
if b.Table != nil {
buf.WriteString(" FROM ")
switch table := b.Table.(type) {
case string:
// FIXME: no quote ident
buf.WriteString(table)
default:
buf.WriteString(placeholder)
buf.WriteValue(table)
}
if len(b.JoinTable) > 0 {
for _, join := range b.JoinTable {
err := join.Build(d, buf)
if err != nil {
return err
}
}
}
}
if len(b.WhereCond) > 0 {
buf.WriteString(" WHERE ")
err := And(b.WhereCond...).Build(d, buf)
if err != nil {
return err
}
}
if len(b.Group) > 0 {
buf.WriteString(" GROUP BY ")
for i, group := range b.Group {
if i > 0 {
buf.WriteString(", ")
}
err := group.Build(d, buf)
if err != nil {
return err
}
}
}
if len(b.HavingCond) > 0 {
buf.WriteString(" HAVING ")
err := And(b.HavingCond...).Build(d, buf)
if err != nil {
return err
}
}
if len(b.Order) > 0 {
buf.WriteString(" ORDER BY ")
for i, order := range b.Order {
if i > 0 {
buf.WriteString(", ")
}
err := order.Build(d, buf)
if err != nil {
return err
}
}
}
if b.LimitCount >= 0 {
buf.WriteString(" LIMIT ")
buf.WriteString(strconv.FormatInt(b.LimitCount, 10))
}
if b.OffsetCount >= 0 {
buf.WriteString(" OFFSET ")
buf.WriteString(strconv.FormatInt(b.OffsetCount, 10))
}
if len(b.Suffixes) > 0 {
for _, suffix := range b.Suffixes {
buf.WriteString(" ")
err := suffix.Build(d, buf)
if err != nil {
return err
}
}
}
return nil
}
// Select creates a SelectStmt.
func Select(column ...interface{}) *SelectStmt {
return &SelectStmt{
Column: column,
LimitCount: -1,
OffsetCount: -1,
}
}
func prepareSelect(a []string) []interface{} {
b := make([]interface{}, len(a))
for i := range a {
b[i] = a[i]
}
return b
}
// Select creates a SelectStmt.
func (sess *Session) Select(column ...string) *SelectStmt {
b := Select(prepareSelect(column)...)
b.runner = sess
b.EventReceiver = sess.EventReceiver
b.Dialect = sess.Dialect
return b
}
// Select creates a SelectStmt.
func (tx *Tx) Select(column ...string) *SelectStmt {
b := Select(prepareSelect(column)...)
b.runner = tx
b.EventReceiver = tx.EventReceiver
b.Dialect = tx.Dialect
return b
}
// SelectBySql creates a SelectStmt from raw query.
func SelectBySql(query string, value ...interface{}) *SelectStmt {
return &SelectStmt{
raw: raw{
Query: query,
Value: value,
},
LimitCount: -1,
OffsetCount: -1,
}
}
// SelectBySql creates a SelectStmt from raw query.
func (sess *Session) SelectBySql(query string, value ...interface{}) *SelectStmt {
b := SelectBySql(query, value...)
b.runner = sess
b.EventReceiver = sess.EventReceiver
b.Dialect = sess.Dialect
return b
}
// SelectBySql creates a SelectStmt from raw query.
func (tx *Tx) SelectBySql(query string, value ...interface{}) *SelectStmt {
b := SelectBySql(query, value...)
b.runner = tx
b.EventReceiver = tx.EventReceiver
b.Dialect = tx.Dialect
return b
}
// From specifies table to select from.
// table can be Builder like SelectStmt, or string.
func (b *SelectStmt) From(table interface{}) *SelectStmt {
b.Table = table
return b
}
func (b *SelectStmt) Distinct() *SelectStmt {
b.IsDistinct = true
return b
}
// Where adds a where condition.
// query can be Builder or string. value is used only if query type is string.
func (b *SelectStmt) Where(query interface{}, value ...interface{}) *SelectStmt {
switch query := query.(type) {
case string:
b.WhereCond = append(b.WhereCond, Expr(query, value...))
case Builder:
b.WhereCond = append(b.WhereCond, query)
}
return b
}
// Having adds a having condition.
// query can be Builder or string. value is used only if query type is string.
func (b *SelectStmt) Having(query interface{}, value ...interface{}) *SelectStmt {
switch query := query.(type) {
case string:
b.HavingCond = append(b.HavingCond, Expr(query, value...))
case Builder:
b.HavingCond = append(b.HavingCond, query)
}
return b
}
// GroupBy specifies columns for grouping.
func (b *SelectStmt) GroupBy(col ...string) *SelectStmt {
for _, group := range col {
b.Group = append(b.Group, Expr(group))
}
return b
}
func (b *SelectStmt) OrderAsc(col string) *SelectStmt {
b.Order = append(b.Order, order(col, asc))
return b
}
func (b *SelectStmt) OrderDesc(col string) *SelectStmt {
b.Order = append(b.Order, order(col, desc))
return b
}
// OrderBy specifies columns for ordering.
func (b *SelectStmt) OrderBy(col string) *SelectStmt {
b.Order = append(b.Order, Expr(col))
return b
}
func (b *SelectStmt) Limit(n uint64) *SelectStmt {
b.LimitCount = int64(n)
return b
}
func (b *SelectStmt) Offset(n uint64) *SelectStmt {
b.OffsetCount = int64(n)
return b
}
// Suffix adds an expression to the end of the query. This is useful to add dialect-specific clauses like FOR UPDATE
func (b *SelectStmt) Suffix(suffix string, value ...interface{}) *SelectStmt {
b.Suffixes = append(b.Suffixes, Expr(suffix, value...))
return b
}
// Paginate fetches a page in a naive way for a small set of data.
func (b *SelectStmt) Paginate(page, perPage uint64) *SelectStmt {
b.Limit(perPage)
b.Offset((page - 1) * perPage)
return b
}
// OrderDir is a helper for OrderAsc and OrderDesc.
func (b *SelectStmt) OrderDir(col string, isAsc bool) *SelectStmt {
if isAsc {
b.OrderAsc(col)
} else {
b.OrderDesc(col)
}
return b
}
func (b *SelectStmt) Comment(comment string) *SelectStmt {
b.comments = b.comments.Append(comment)
return b
}
// Join add inner-join.
// on can be Builder or string.
func (b *SelectStmt) Join(table, on interface{}) *SelectStmt {
b.JoinTable = append(b.JoinTable, join(inner, table, on))
return b
}
// LeftJoin add left-join.
// on can be Builder or string.
func (b *SelectStmt) LeftJoin(table, on interface{}) *SelectStmt {
b.JoinTable = append(b.JoinTable, join(left, table, on))
return b
}
// RightJoin add right-join.
// on can be Builder or string.
func (b *SelectStmt) RightJoin(table, on interface{}) *SelectStmt {
b.JoinTable = append(b.JoinTable, join(right, table, on))
return b
}
// FullJoin add full-join.
// on can be Builder or string.
func (b *SelectStmt) FullJoin(table, on interface{}) *SelectStmt {
b.JoinTable = append(b.JoinTable, join(full, table, on))
return b
}
// As creates alias for select statement.
func (b *SelectStmt) As(alias string) Builder {
return as(b, alias)
}
// Rows executes the query and returns the rows returned, or any error encountered.
func (b *SelectStmt) Rows() (*sql.Rows, error) {
return b.RowsContext(context.Background())
}
func (b *SelectStmt) RowsContext(ctx context.Context) (*sql.Rows, error) {
_, rows, err := queryRows(ctx, b.runner, b.EventReceiver, b, b.Dialect)
return rows, err
}
func (b *SelectStmt) LoadOneContext(ctx context.Context, value interface{}) error {
count, err := query(ctx, b.runner, b.EventReceiver, b, b.Dialect, value)
if err != nil {
return err
}
if count == 0 {
return ErrNotFound
}
return nil
}
// LoadOne loads SQL result into go variable that is not a slice.
// Unlike Load, it returns ErrNotFound if the SQL result row count is 0.
//
// See https://godoc.org/github.com/gocraft/dbr#Load.
func (b *SelectStmt) LoadOne(value interface{}) error {
return b.LoadOneContext(context.Background(), value)
}
func (b *SelectStmt) LoadContext(ctx context.Context, value interface{}) (int, error) {
return query(ctx, b.runner, b.EventReceiver, b, b.Dialect, value)
}
// Load loads multi-row SQL result into a slice of go variables.
//
// See https://godoc.org/github.com/gocraft/dbr#Load.
func (b *SelectStmt) Load(value interface{}) (int, error) {
return b.LoadContext(context.Background(), value)
}