pkg/storage/querybuilder/select.go (198 lines of code) (raw):
// Copyright (c) 2019 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package querybuilder
import (
"bytes"
"fmt"
"strings"
"github.com/lann/builder"
)
type selectData struct {
PlaceholderFormat PlaceholderFormat
Distinct bool
Columns []Sqlizer
From Sqlizer
Joins []Sqlizer
WhereParts []Sqlizer
GroupBys []string
HavingParts []Sqlizer
OrderBys []string
Limit string
PageSize int
DisableAutoPage bool
PagingState []byte
Offset string
}
func (d *selectData) ToSQL() (sqlStr string, args []interface{}, err error) {
if len(d.Columns) == 0 {
err = fmt.Errorf("select statements must have at least one result column")
return
}
sql := &bytes.Buffer{}
sql.WriteString("SELECT ")
if d.Distinct {
sql.WriteString("DISTINCT ")
}
if len(d.Columns) > 0 {
args, err = appendToSQL(d.Columns, sql, ", ", args)
if err != nil {
return
}
}
if d.From != nil {
sql.WriteString(" FROM ")
args, err = appendToSQL([]Sqlizer{d.From}, sql, "", args)
if err != nil {
return
}
}
if len(d.Joins) > 0 {
sql.WriteString(" ")
args, err = appendToSQL(d.Joins, sql, " ", args)
}
if len(d.WhereParts) > 0 {
sql.WriteString(" WHERE ")
args, err = appendToSQL(d.WhereParts, sql, " AND ", args)
if err != nil {
return
}
}
if len(d.GroupBys) > 0 {
sql.WriteString(" GROUP BY ")
sql.WriteString(strings.Join(d.GroupBys, ", "))
}
if len(d.HavingParts) > 0 {
sql.WriteString(" HAVING ")
args, err = appendToSQL(d.HavingParts, sql, " AND ", args)
if err != nil {
return
}
}
if len(d.OrderBys) > 0 {
sql.WriteString(" ORDER BY ")
sql.WriteString(strings.Join(d.OrderBys, ", "))
}
if len(d.Limit) > 0 {
sql.WriteString(" LIMIT ")
sql.WriteString(d.Limit)
}
if len(d.Offset) > 0 {
sql.WriteString(" OFFSET ")
sql.WriteString(d.Offset)
}
sqlStr, err = d.PlaceholderFormat.ReplacePlaceholders(sql.String())
return
}
func (d selectData) GetResource() string {
res, _, _ := d.From.ToSQL()
return res
}
func (d selectData) GetWhereParts() []Sqlizer {
return d.WhereParts
}
func (d selectData) GetColumns() []Sqlizer {
return d.Columns
}
// Builder
// SelectBuilder builds SQL SELECT statements.
type SelectBuilder builder.Builder
func init() {
builder.Register(SelectBuilder{}, selectData{})
}
// Format methods
// PlaceholderFormat sets PlaceholderFormat (e.g. Question or Dollar) for the
// query.
func (b SelectBuilder) PlaceholderFormat(f PlaceholderFormat) SelectBuilder {
return builder.Set(b, "PlaceholderFormat", f).(SelectBuilder)
}
// SQL methods
// ToSQL builds the query into a SQL string and bound args.
func (b SelectBuilder) ToSQL() (string, []interface{}, error) {
data := builder.GetStruct(b).(selectData)
return data.ToSQL()
}
// ToUql builds the query into a UQL string and bound args.
// As an runtime optimization, it also returns query options
func (b SelectBuilder) ToUql() (query string, args []interface{},
options map[string]interface{}, err error) {
data := builder.GetStruct(b).(selectData)
query, args, err = data.ToSQL()
options = map[string]interface{}{
"PageSize": data.PageSize,
"DisableAutoPage": data.DisableAutoPage,
"PagingState": data.PagingState,
}
return
}
// StmtType returns type of the statement
func (b SelectBuilder) StmtType() StmtType {
return SelectStmtType
}
// IsCAS always return false
func (b SelectBuilder) IsCAS() bool {
return false
}
// GetData returns the underlying struct as an interface
func (b SelectBuilder) GetData() StatementAccessor {
return builder.GetStruct(b).(selectData)
}
// Distinct adds a DISTINCT clause to the query.
func (b SelectBuilder) Distinct() SelectBuilder {
return builder.Set(b, "Distinct", true).(SelectBuilder)
}
// Columns adds result columns to the query.
func (b SelectBuilder) Columns(columns ...string) SelectBuilder {
var parts []interface{}
for _, str := range columns {
parts = append(parts, newPart(str))
}
return builder.Extend(b, "Columns", parts).(SelectBuilder)
}
// Column adds a result column to the query.
// Unlike Columns, Column accepts args which will be bound to placeholders in
// the columns string, for example:
// Column("IF(col IN ("+squirrel.Placeholders(3)+"), 1, 0) as col", 1, 2, 3)
func (b SelectBuilder) Column(column interface{}, args ...interface{}) SelectBuilder {
return builder.Append(b, "Columns", newPart(column, args...)).(SelectBuilder)
}
// From sets the FROM clause of the query.
func (b SelectBuilder) From(from string) SelectBuilder {
return builder.Set(b, "From", newPart(from)).(SelectBuilder)
}
// FromSelect sets a subquery into the FROM clause of the query.
func (b SelectBuilder) FromSelect(from SelectBuilder, aliasString string) SelectBuilder {
return builder.Set(b, "From", alias(from, aliasString)).(SelectBuilder)
}
// JoinClause adds a join clause to the query.
func (b SelectBuilder) JoinClause(pred interface{}, args ...interface{}) SelectBuilder {
return builder.Append(b, "Joins", newPart(pred, args...)).(SelectBuilder)
}
// Join adds a JOIN clause to the query.
func (b SelectBuilder) Join(join string, rest ...interface{}) SelectBuilder {
return b.JoinClause("JOIN "+join, rest...)
}
// LeftJoin adds a LEFT JOIN clause to the query.
func (b SelectBuilder) LeftJoin(join string, rest ...interface{}) SelectBuilder {
return b.JoinClause("LEFT JOIN "+join, rest...)
}
// RightJoin adds a RIGHT JOIN clause to the query.
func (b SelectBuilder) RightJoin(join string, rest ...interface{}) SelectBuilder {
return b.JoinClause("RIGHT JOIN "+join, rest...)
}
// Where adds an expression to the WHERE clause of the query.
//
// Expressions are ANDed together in the generated SQL.
//
// Where accepts several types for its pred argument:
//
// nil OR "" - ignored.
//
// string - SQL expression.
// If the expression has SQL placeholders then a set of arguments must be passed
// as well, one for each placeholder.
//
// map[string]interface{} OR Eq - map of SQL expressions to values. Each key is
// transformed into an expression like "<key> = ?", with the corresponding value
// bound to the placeholder. If the value is nil, the expression will be "<key>
// IS NULL". If the value is an array or slice, the expression will be "<key> IN
// (?,?,...)", with one placeholder for each item in the value. These expressions
// are ANDed together.
//
// Where will panic if pred isn't any of the above types.
func (b SelectBuilder) Where(pred interface{}, args ...interface{}) SelectBuilder {
return builder.Append(b, "WhereParts", newWherePart(pred, args...)).(SelectBuilder)
}
// GroupBy adds GROUP BY expressions to the query.
func (b SelectBuilder) GroupBy(groupBys ...string) SelectBuilder {
return builder.Extend(b, "GroupBys", groupBys).(SelectBuilder)
}
// Having adds an expression to the HAVING clause of the query.
//
// See Where.
func (b SelectBuilder) Having(pred interface{}, rest ...interface{}) SelectBuilder {
return builder.Append(b, "HavingParts", newWherePart(pred, rest...)).(SelectBuilder)
}
// OrderBy adds ORDER BY expressions to the query.
func (b SelectBuilder) OrderBy(orderBys ...string) SelectBuilder {
return builder.Extend(b, "OrderBys", orderBys).(SelectBuilder)
}
// Limit sets a LIMIT clause on the query.
func (b SelectBuilder) Limit(limit uint64) SelectBuilder {
return builder.Set(b, "Limit", fmt.Sprintf("%d", limit)).(SelectBuilder)
}
// Offset sets a OFFSET clause on the query.
func (b SelectBuilder) Offset(offset uint64) SelectBuilder {
return builder.Set(b, "Offset", fmt.Sprintf("%d", offset)).(SelectBuilder)
}
// PagingState sets a continuation token on the query.
func (b SelectBuilder) disableAutoPaging() SelectBuilder {
return builder.Set(b, "DisableAutoPage", true).(SelectBuilder)
}
// PageSize sets size of rows the query should fetch a time
func (b SelectBuilder) PageSize(pageSize int) SelectBuilder {
return builder.Set(b, "PageSize", pageSize).(SelectBuilder)
}
// PagingState sets a continuation token on where the query will resume for the next page.
func (b SelectBuilder) PagingState(pagingState []byte) SelectBuilder {
ret := b.disableAutoPaging()
if len(pagingState) > 0 {
return builder.Set(ret, "PagingState", pagingState).(SelectBuilder)
}
return ret
}
// IsDisableAutoPaging returns true if the select statement disable auto-paging
func (b SelectBuilder) IsDisableAutoPaging() bool {
data := builder.GetStruct(b).(selectData)
return data.DisableAutoPage
}
// GetPagingState returns true the continuation token
func (b SelectBuilder) GetPagingState() []byte {
data := builder.GetStruct(b).(selectData)
return data.PagingState
}
// GetPageSize returns true the size of the page
func (b SelectBuilder) GetPageSize() int {
data := builder.GetStruct(b).(selectData)
return data.PageSize
}