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) }