mirror of
https://github.com/hanzoai/sqlbuilder.git
synced 2026-10-01 00:37:42 +00:00
652 lines
17 KiB
Go
652 lines
17 KiB
Go
// Copyright 2018 Huan Du. All rights reserved.
|
|
// Licensed under the MIT license that can be found in the LICENSE file.
|
|
|
|
package sqlbuilder
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
|
|
"github.com/huandu/go-clone"
|
|
)
|
|
|
|
const (
|
|
selectMarkerInit injectionMarker = iota
|
|
selectMarkerAfterWith
|
|
selectMarkerAfterSelect
|
|
selectMarkerAfterFrom
|
|
selectMarkerAfterJoin
|
|
selectMarkerAfterWhere
|
|
selectMarkerAfterGroupBy
|
|
selectMarkerAfterOrderBy
|
|
selectMarkerAfterLimit
|
|
selectMarkerAfterFor
|
|
)
|
|
|
|
// JoinOption is the option in JOIN.
|
|
type JoinOption string
|
|
|
|
// Join options.
|
|
const (
|
|
FullJoin JoinOption = "FULL"
|
|
FullOuterJoin JoinOption = "FULL OUTER"
|
|
InnerJoin JoinOption = "INNER"
|
|
LeftJoin JoinOption = "LEFT"
|
|
LeftOuterJoin JoinOption = "LEFT OUTER"
|
|
RightJoin JoinOption = "RIGHT"
|
|
RightOuterJoin JoinOption = "RIGHT OUTER"
|
|
)
|
|
|
|
// NewSelectBuilder creates a new SELECT builder.
|
|
func NewSelectBuilder() *SelectBuilder {
|
|
return DefaultFlavor.NewSelectBuilder()
|
|
}
|
|
|
|
func newSelectBuilder() *SelectBuilder {
|
|
args := &Args{}
|
|
proxy := &whereClauseProxy{}
|
|
return &SelectBuilder{
|
|
whereClauseProxy: proxy,
|
|
whereClauseExpr: args.Add(proxy),
|
|
|
|
Cond: Cond{
|
|
Args: args,
|
|
},
|
|
args: args,
|
|
injection: newInjection(),
|
|
}
|
|
}
|
|
|
|
// Clone returns a deep copy of SelectBuilder.
|
|
// It's useful when you want to create a base builder and clone it to build similar queries.
|
|
func (sb *SelectBuilder) Clone() *SelectBuilder {
|
|
return clone.Clone(sb).(*SelectBuilder)
|
|
}
|
|
|
|
func init() {
|
|
t := reflect.TypeOf(SelectBuilder{})
|
|
clone.SetCustomFunc(t, func(allocator *clone.Allocator, old, new reflect.Value) {
|
|
cloned := allocator.CloneSlowly(old)
|
|
new.Set(cloned)
|
|
|
|
sb := cloned.Addr().Interface().(*SelectBuilder)
|
|
sb.args.Replace(sb.whereClauseExpr, sb.whereClauseProxy)
|
|
sb.args.Replace(sb.cteBuilderVar, sb.cteBuilder)
|
|
})
|
|
}
|
|
|
|
// SelectBuilder is a builder to build SELECT.
|
|
type SelectBuilder struct {
|
|
*WhereClause
|
|
Cond
|
|
|
|
whereClauseProxy *whereClauseProxy
|
|
whereClauseExpr string
|
|
|
|
cteBuilderVar string
|
|
cteBuilder *CTEBuilder
|
|
|
|
distinct bool
|
|
tables []string
|
|
selectCols []string
|
|
joinOptions []JoinOption
|
|
joinTables []string
|
|
joinExprs [][]string
|
|
havingExprs []string
|
|
groupByCols []string
|
|
orderByCols []string
|
|
order string
|
|
limitVar string
|
|
offsetVar string
|
|
forWhat string
|
|
|
|
args *Args
|
|
|
|
injection *injection
|
|
marker injectionMarker
|
|
}
|
|
|
|
var _ Builder = new(SelectBuilder)
|
|
|
|
// Select sets columns in SELECT.
|
|
func Select(col ...string) *SelectBuilder {
|
|
return DefaultFlavor.NewSelectBuilder().Select(col...)
|
|
}
|
|
|
|
// TableNames returns all table names in this SELECT statement.
|
|
func (sb *SelectBuilder) TableNames() []string {
|
|
var additionalTableNames []string
|
|
|
|
if sb.cteBuilder != nil {
|
|
additionalTableNames = sb.cteBuilder.tableNamesForFrom()
|
|
}
|
|
|
|
var tableNames []string
|
|
|
|
if len(sb.tables) > 0 && len(additionalTableNames) > 0 {
|
|
tableNames = make([]string, len(sb.tables)+len(additionalTableNames))
|
|
copy(tableNames, sb.tables)
|
|
copy(tableNames[len(sb.tables):], additionalTableNames)
|
|
} else if len(sb.tables) > 0 {
|
|
tableNames = sb.tables
|
|
} else if len(additionalTableNames) > 0 {
|
|
tableNames = additionalTableNames
|
|
}
|
|
|
|
return tableNames
|
|
}
|
|
|
|
// With sets WITH clause (the Common Table Expression) before SELECT.
|
|
func (sb *SelectBuilder) With(builder *CTEBuilder) *SelectBuilder {
|
|
sb.marker = selectMarkerAfterWith
|
|
sb.cteBuilderVar = sb.Var(builder)
|
|
sb.cteBuilder = builder
|
|
return sb
|
|
}
|
|
|
|
// Select sets columns in SELECT.
|
|
func (sb *SelectBuilder) Select(col ...string) *SelectBuilder {
|
|
sb.selectCols = col
|
|
sb.marker = selectMarkerAfterSelect
|
|
return sb
|
|
}
|
|
|
|
// SelectMore adds more columns in SELECT.
|
|
func (sb *SelectBuilder) SelectMore(col ...string) *SelectBuilder {
|
|
sb.selectCols = append(sb.selectCols, col...)
|
|
sb.marker = selectMarkerAfterSelect
|
|
return sb
|
|
}
|
|
|
|
// Distinct marks this SELECT as DISTINCT.
|
|
func (sb *SelectBuilder) Distinct() *SelectBuilder {
|
|
sb.distinct = true
|
|
sb.marker = selectMarkerAfterSelect
|
|
return sb
|
|
}
|
|
|
|
// From sets table names in SELECT.
|
|
func (sb *SelectBuilder) From(table ...string) *SelectBuilder {
|
|
sb.tables = table
|
|
sb.marker = selectMarkerAfterFrom
|
|
return sb
|
|
}
|
|
|
|
// Join sets expressions of JOIN in SELECT.
|
|
//
|
|
// It builds a JOIN expression like
|
|
//
|
|
// JOIN table ON onExpr[0] AND onExpr[1] ...
|
|
func (sb *SelectBuilder) Join(table string, onExpr ...string) *SelectBuilder {
|
|
sb.marker = selectMarkerAfterJoin
|
|
return sb.JoinWithOption("", table, onExpr...)
|
|
}
|
|
|
|
// JoinWithOption sets expressions of JOIN with an option.
|
|
//
|
|
// It builds a JOIN expression like
|
|
//
|
|
// option JOIN table ON onExpr[0] AND onExpr[1] ...
|
|
//
|
|
// Here is a list of supported options.
|
|
// - FullJoin: FULL JOIN
|
|
// - FullOuterJoin: FULL OUTER JOIN
|
|
// - InnerJoin: INNER JOIN
|
|
// - LeftJoin: LEFT JOIN
|
|
// - LeftOuterJoin: LEFT OUTER JOIN
|
|
// - RightJoin: RIGHT JOIN
|
|
// - RightOuterJoin: RIGHT OUTER JOIN
|
|
func (sb *SelectBuilder) JoinWithOption(option JoinOption, table string, onExpr ...string) *SelectBuilder {
|
|
sb.joinOptions = append(sb.joinOptions, option)
|
|
sb.joinTables = append(sb.joinTables, table)
|
|
sb.joinExprs = append(sb.joinExprs, onExpr)
|
|
sb.marker = selectMarkerAfterJoin
|
|
return sb
|
|
}
|
|
|
|
// Where adds expressions to the WHERE clause in SELECT.
|
|
//
|
|
// Multiple calls to Where will join expressions with AND.
|
|
// To reset the WHERE clause, set the WhereClause field to nil.
|
|
func (sb *SelectBuilder) Where(andExpr ...string) *SelectBuilder {
|
|
if len(andExpr) == 0 || estimateStringsBytes(andExpr) == 0 {
|
|
return sb
|
|
}
|
|
|
|
if sb.WhereClause == nil {
|
|
sb.WhereClause = NewWhereClause()
|
|
}
|
|
|
|
sb.WhereClause.AddWhereExpr(sb.args, andExpr...)
|
|
sb.marker = selectMarkerAfterWhere
|
|
return sb
|
|
}
|
|
|
|
// AddWhereClause adds all clauses in the whereClause to SELECT.
|
|
func (sb *SelectBuilder) AddWhereClause(whereClause *WhereClause) *SelectBuilder {
|
|
if sb.WhereClause == nil {
|
|
sb.WhereClause = NewWhereClause()
|
|
}
|
|
|
|
sb.WhereClause.AddWhereClause(whereClause)
|
|
return sb
|
|
}
|
|
|
|
// Having sets expressions of HAVING in SELECT.
|
|
func (sb *SelectBuilder) Having(andExpr ...string) *SelectBuilder {
|
|
sb.havingExprs = append(sb.havingExprs, andExpr...)
|
|
sb.marker = selectMarkerAfterGroupBy
|
|
return sb
|
|
}
|
|
|
|
// GroupBy sets columns of GROUP BY in SELECT.
|
|
func (sb *SelectBuilder) GroupBy(col ...string) *SelectBuilder {
|
|
sb.groupByCols = append(sb.groupByCols, col...)
|
|
sb.marker = selectMarkerAfterGroupBy
|
|
return sb
|
|
}
|
|
|
|
// OrderBy sets columns of ORDER BY in SELECT.
|
|
//
|
|
// It's recommended to use OrderByAsc or OrderByDesc instead for better support of multiple ORDER BY columns with different directions.
|
|
// OrderBy combined with Asc/Desc only supports a single direction for all columns.
|
|
func (sb *SelectBuilder) OrderBy(col ...string) *SelectBuilder {
|
|
sb.orderByCols = append(sb.orderByCols, col...)
|
|
sb.marker = selectMarkerAfterOrderBy
|
|
return sb
|
|
}
|
|
|
|
// OrderByAsc sets a column of ORDER BY in SELECT with ASC order.
|
|
// It supports chaining multiple calls to add multiple ORDER BY columns with different directions.
|
|
//
|
|
// sb.OrderByAsc("name").OrderByDesc("id")
|
|
// // Generates: ORDER BY name ASC, id DESC
|
|
func (sb *SelectBuilder) OrderByAsc(col string) *SelectBuilder {
|
|
sb.orderByCols = append(sb.orderByCols, col+" ASC")
|
|
sb.marker = selectMarkerAfterOrderBy
|
|
return sb
|
|
}
|
|
|
|
// OrderByDesc sets a column of ORDER BY in SELECT with DESC order.
|
|
// It supports chaining multiple calls to add multiple ORDER BY columns with different directions.
|
|
//
|
|
// sb.OrderByDesc("id").OrderByAsc("name")
|
|
// // Generates: ORDER BY id DESC, name ASC
|
|
func (sb *SelectBuilder) OrderByDesc(col string) *SelectBuilder {
|
|
sb.orderByCols = append(sb.orderByCols, col+" DESC")
|
|
sb.marker = selectMarkerAfterOrderBy
|
|
return sb
|
|
}
|
|
|
|
// Asc sets order of ORDER BY to ASC.
|
|
//
|
|
// Deprecated: Use OrderByAsc instead. Asc only supports a single direction for all ORDER BY columns.
|
|
func (sb *SelectBuilder) Asc() *SelectBuilder {
|
|
sb.order = "ASC"
|
|
sb.marker = selectMarkerAfterOrderBy
|
|
return sb
|
|
}
|
|
|
|
// Desc sets order of ORDER BY to DESC.
|
|
//
|
|
// Deprecated: Use OrderByDesc instead. Desc only supports a single direction for all ORDER BY columns.
|
|
func (sb *SelectBuilder) Desc() *SelectBuilder {
|
|
sb.order = "DESC"
|
|
sb.marker = selectMarkerAfterOrderBy
|
|
return sb
|
|
}
|
|
|
|
// Limit sets the LIMIT in SELECT.
|
|
func (sb *SelectBuilder) Limit(limit int) *SelectBuilder {
|
|
if limit < 0 {
|
|
sb.limitVar = ""
|
|
return sb
|
|
}
|
|
|
|
sb.limitVar = sb.Var(limit)
|
|
sb.marker = selectMarkerAfterLimit
|
|
return sb
|
|
}
|
|
|
|
// Offset sets the LIMIT offset in SELECT.
|
|
func (sb *SelectBuilder) Offset(offset int) *SelectBuilder {
|
|
if offset < 0 {
|
|
sb.offsetVar = ""
|
|
return sb
|
|
}
|
|
|
|
sb.offsetVar = sb.Var(offset)
|
|
sb.marker = selectMarkerAfterLimit
|
|
return sb
|
|
}
|
|
|
|
// ForUpdate adds FOR UPDATE at the end of SELECT statement.
|
|
func (sb *SelectBuilder) ForUpdate() *SelectBuilder {
|
|
sb.forWhat = "UPDATE"
|
|
sb.marker = selectMarkerAfterFor
|
|
return sb
|
|
}
|
|
|
|
// ForShare adds FOR SHARE at the end of SELECT statement.
|
|
func (sb *SelectBuilder) ForShare() *SelectBuilder {
|
|
sb.forWhat = "SHARE"
|
|
sb.marker = selectMarkerAfterFor
|
|
return sb
|
|
}
|
|
|
|
// As returns an AS expression.
|
|
func (sb *SelectBuilder) As(name, alias string) string {
|
|
return fmt.Sprintf("%s AS %s", name, alias)
|
|
}
|
|
|
|
// BuilderAs returns an AS expression wrapping a complex SQL.
|
|
// According to SQL syntax, SQL built by builder is surrounded by parens.
|
|
func (sb *SelectBuilder) BuilderAs(builder Builder, alias string) string {
|
|
return fmt.Sprintf("(%s) AS %s", sb.Var(builder), alias)
|
|
}
|
|
|
|
// LateralAs returns a LATERAL derived table expression wrapping a complex SQL.
|
|
func (sb *SelectBuilder) LateralAs(builder Builder, alias string) string {
|
|
return fmt.Sprintf("LATERAL (%s) AS %s", sb.Var(builder), alias)
|
|
}
|
|
|
|
// NumCol returns the number of columns to select.
|
|
func (sb *SelectBuilder) NumCol() int {
|
|
return len(sb.selectCols)
|
|
}
|
|
|
|
// String returns the compiled SELECT string.
|
|
func (sb *SelectBuilder) String() string {
|
|
s, _ := sb.Build()
|
|
return s
|
|
}
|
|
|
|
// Build returns compiled SELECT string and args.
|
|
// They can be used in `DB#Query` of package `database/sql` directly.
|
|
func (sb *SelectBuilder) Build() (sql string, args []interface{}) {
|
|
return sb.BuildWithFlavor(sb.args.Flavor)
|
|
}
|
|
|
|
// BuildWithFlavor returns compiled SELECT string and args with flavor and initial args.
|
|
// They can be used in `DB#Query` of package `database/sql` directly.
|
|
func (sb *SelectBuilder) BuildWithFlavor(flavor Flavor, initialArg ...interface{}) (sql string, args []interface{}) {
|
|
buf := newStringBuilder()
|
|
sb.injection.WriteTo(buf, selectMarkerInit)
|
|
|
|
oraclePage := flavor == Oracle && (len(sb.limitVar) > 0 || len(sb.offsetVar) > 0)
|
|
|
|
if sb.cteBuilderVar != "" {
|
|
buf.WriteLeadingString(sb.cteBuilderVar)
|
|
sb.injection.WriteTo(buf, selectMarkerAfterWith)
|
|
}
|
|
|
|
if len(sb.selectCols) > 0 {
|
|
buf.WriteLeadingString("SELECT ")
|
|
|
|
if sb.distinct {
|
|
buf.WriteString("DISTINCT ")
|
|
}
|
|
|
|
if oraclePage {
|
|
var selectCols = make([]string, 0, len(sb.selectCols))
|
|
for i := range sb.selectCols {
|
|
cols := strings.SplitN(sb.selectCols[i], ".", 2)
|
|
|
|
if len(cols) == 1 {
|
|
selectCols = append(selectCols, cols[0])
|
|
} else {
|
|
selectCols = append(selectCols, cols[1])
|
|
}
|
|
}
|
|
buf.WriteStrings(selectCols, ", ")
|
|
} else {
|
|
buf.WriteStrings(sb.selectCols, ", ")
|
|
}
|
|
}
|
|
|
|
sb.injection.WriteTo(buf, selectMarkerAfterSelect)
|
|
|
|
if oraclePage {
|
|
if len(sb.selectCols) > 0 {
|
|
buf.WriteLeadingString("FROM (SELECT ")
|
|
|
|
if sb.distinct {
|
|
buf.WriteString("DISTINCT ")
|
|
}
|
|
|
|
var selectCols = make([]string, 0, len(sb.selectCols)+1)
|
|
selectCols = append(selectCols, "ROWNUM r")
|
|
|
|
for i := range sb.selectCols {
|
|
cols := strings.SplitN(sb.selectCols[i], ".", 2)
|
|
if len(cols) == 1 {
|
|
selectCols = append(selectCols, cols[0])
|
|
} else {
|
|
selectCols = append(selectCols, cols[1])
|
|
}
|
|
}
|
|
|
|
buf.WriteStrings(selectCols, ", ")
|
|
buf.WriteLeadingString("FROM (SELECT ")
|
|
buf.WriteStrings(sb.selectCols, ", ")
|
|
}
|
|
}
|
|
|
|
tableNames := sb.TableNames()
|
|
|
|
if len(tableNames) > 0 {
|
|
buf.WriteLeadingString("FROM ")
|
|
buf.WriteStrings(tableNames, ", ")
|
|
}
|
|
|
|
sb.injection.WriteTo(buf, selectMarkerAfterFrom)
|
|
|
|
for i := range sb.joinTables {
|
|
if option := sb.joinOptions[i]; option != "" {
|
|
buf.WriteLeadingString(string(option))
|
|
}
|
|
|
|
buf.WriteLeadingString("JOIN ")
|
|
buf.WriteString(sb.joinTables[i])
|
|
|
|
if exprs := filterEmptyStrings(sb.joinExprs[i]); len(exprs) > 0 {
|
|
buf.WriteString(" ON ")
|
|
buf.WriteStrings(exprs, " AND ")
|
|
}
|
|
}
|
|
|
|
if len(sb.joinTables) > 0 {
|
|
sb.injection.WriteTo(buf, selectMarkerAfterJoin)
|
|
}
|
|
|
|
if sb.WhereClause != nil {
|
|
sb.whereClauseProxy.WhereClause = sb.WhereClause
|
|
defer func() {
|
|
sb.whereClauseProxy.WhereClause = nil
|
|
}()
|
|
|
|
buf.WriteLeadingString(sb.whereClauseExpr)
|
|
sb.injection.WriteTo(buf, selectMarkerAfterWhere)
|
|
}
|
|
|
|
if len(sb.groupByCols) > 0 {
|
|
buf.WriteLeadingString("GROUP BY ")
|
|
buf.WriteStrings(sb.groupByCols, ", ")
|
|
|
|
if havingExprs := filterEmptyStrings(sb.havingExprs); len(havingExprs) > 0 {
|
|
buf.WriteString(" HAVING ")
|
|
buf.WriteStrings(havingExprs, " AND ")
|
|
}
|
|
|
|
sb.injection.WriteTo(buf, selectMarkerAfterGroupBy)
|
|
}
|
|
|
|
if len(sb.orderByCols) > 0 {
|
|
buf.WriteLeadingString("ORDER BY ")
|
|
buf.WriteStrings(sb.orderByCols, ", ")
|
|
|
|
if sb.order != "" {
|
|
buf.WriteRune(' ')
|
|
buf.WriteString(sb.order)
|
|
}
|
|
|
|
sb.injection.WriteTo(buf, selectMarkerAfterOrderBy)
|
|
}
|
|
|
|
switch flavor {
|
|
case MySQL, SQLite, ClickHouse:
|
|
if len(sb.limitVar) > 0 {
|
|
buf.WriteLeadingString("LIMIT ")
|
|
buf.WriteString(sb.limitVar)
|
|
|
|
if len(sb.offsetVar) > 0 {
|
|
buf.WriteLeadingString("OFFSET ")
|
|
buf.WriteString(sb.offsetVar)
|
|
}
|
|
}
|
|
|
|
case CQL:
|
|
if len(sb.limitVar) > 0 {
|
|
buf.WriteLeadingString("LIMIT ")
|
|
buf.WriteString(sb.limitVar)
|
|
}
|
|
|
|
case PostgreSQL:
|
|
if len(sb.limitVar) > 0 {
|
|
buf.WriteLeadingString("LIMIT ")
|
|
buf.WriteString(sb.limitVar)
|
|
}
|
|
|
|
if len(sb.offsetVar) > 0 {
|
|
buf.WriteLeadingString("OFFSET ")
|
|
buf.WriteString(sb.offsetVar)
|
|
}
|
|
|
|
case Presto:
|
|
// There might be a hidden constraint in Presto requiring offset to be set before limit.
|
|
// The select statement documentation (https://prestodb.io/docs/current/sql/select.html)
|
|
// puts offset before limit, and Trino, which is based on Presto, seems
|
|
// to require this specific order.
|
|
if len(sb.offsetVar) > 0 {
|
|
buf.WriteLeadingString("OFFSET ")
|
|
buf.WriteString(sb.offsetVar)
|
|
}
|
|
|
|
if len(sb.limitVar) > 0 {
|
|
buf.WriteLeadingString("LIMIT ")
|
|
buf.WriteString(sb.limitVar)
|
|
}
|
|
|
|
case SQLServer:
|
|
// If ORDER BY is not set, sort column #1 by default.
|
|
// It's required to make OFFSET...FETCH work.
|
|
if len(sb.orderByCols) == 0 && (len(sb.limitVar) > 0 || len(sb.offsetVar) > 0) {
|
|
buf.WriteLeadingString("ORDER BY 1")
|
|
}
|
|
|
|
if len(sb.offsetVar) > 0 {
|
|
buf.WriteLeadingString("OFFSET ")
|
|
buf.WriteString(sb.offsetVar)
|
|
buf.WriteString(" ROWS")
|
|
}
|
|
|
|
if len(sb.limitVar) > 0 {
|
|
if len(sb.offsetVar) == 0 {
|
|
buf.WriteLeadingString("OFFSET 0 ROWS")
|
|
}
|
|
|
|
buf.WriteLeadingString("FETCH NEXT ")
|
|
buf.WriteString(sb.limitVar)
|
|
buf.WriteString(" ROWS ONLY")
|
|
}
|
|
|
|
case Oracle:
|
|
if oraclePage {
|
|
buf.WriteString(") ")
|
|
|
|
if len(sb.tables) > 0 {
|
|
buf.WriteStrings(sb.tables, ", ")
|
|
}
|
|
|
|
buf.WriteString(") WHERE ")
|
|
|
|
if len(sb.limitVar) > 0 {
|
|
buf.WriteString("r BETWEEN ")
|
|
|
|
if len(sb.offsetVar) > 0 {
|
|
buf.WriteString(sb.offsetVar)
|
|
buf.WriteString(" + 1 AND ")
|
|
buf.WriteString(sb.limitVar)
|
|
buf.WriteString(" + ")
|
|
buf.WriteString(sb.offsetVar)
|
|
} else {
|
|
buf.WriteString("1 AND ")
|
|
buf.WriteString(sb.limitVar)
|
|
}
|
|
} else {
|
|
// As oraclePage is true, sb.offsetVar must not be empty.
|
|
buf.WriteString("r >= ")
|
|
buf.WriteString(sb.offsetVar)
|
|
buf.WriteString(" + 1")
|
|
}
|
|
}
|
|
|
|
case Informix:
|
|
// [SKIP N] FIRST M
|
|
// M must be greater than 0
|
|
if len(sb.limitVar) > 0 {
|
|
if len(sb.offsetVar) > 0 {
|
|
buf.WriteLeadingString("SKIP ")
|
|
buf.WriteString(sb.offsetVar)
|
|
}
|
|
|
|
buf.WriteLeadingString("FIRST ")
|
|
buf.WriteString(sb.limitVar)
|
|
}
|
|
|
|
case Doris:
|
|
// #192: Doris doesn't support ? in OFFSET and LIMIT.
|
|
if len(sb.limitVar) > 0 {
|
|
buf.WriteLeadingString("LIMIT ")
|
|
buf.WriteString(fmt.Sprint(sb.args.Value(sb.limitVar)))
|
|
|
|
if len(sb.offsetVar) > 0 {
|
|
buf.WriteLeadingString("OFFSET ")
|
|
buf.WriteString(fmt.Sprint(sb.args.Value(sb.offsetVar)))
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(sb.limitVar) > 0 {
|
|
sb.injection.WriteTo(buf, selectMarkerAfterLimit)
|
|
}
|
|
|
|
if sb.forWhat != "" {
|
|
buf.WriteLeadingString("FOR ")
|
|
buf.WriteString(sb.forWhat)
|
|
|
|
sb.injection.WriteTo(buf, selectMarkerAfterFor)
|
|
}
|
|
|
|
return sb.args.CompileWithFlavor(buf.String(), flavor, initialArg...)
|
|
}
|
|
|
|
// SetFlavor sets the flavor of compiled sql.
|
|
func (sb *SelectBuilder) SetFlavor(flavor Flavor) (old Flavor) {
|
|
old = sb.args.Flavor
|
|
sb.args.Flavor = flavor
|
|
return
|
|
}
|
|
|
|
// Flavor returns flavor of builder
|
|
func (sb *SelectBuilder) Flavor() Flavor {
|
|
return sb.args.Flavor
|
|
}
|
|
|
|
// SQL adds an arbitrary sql to current position.
|
|
func (sb *SelectBuilder) SQL(sql string) *SelectBuilder {
|
|
sb.injection.SQL(sb.marker, sql)
|
|
return sb
|
|
}
|