Adding upstream version 1.11.0.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
40df376a7f
commit
02cacc5b45
37 changed files with 7317 additions and 0 deletions
244
query_builder.go
Normal file
244
query_builder.go
Normal file
|
@ -0,0 +1,244 @@
|
|||
// Copyright 2016 Qiang Xue. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package dbx
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// QueryBuilder builds different clauses for a SELECT SQL statement.
|
||||
type QueryBuilder interface {
|
||||
// BuildSelect generates a SELECT clause from the given selected column names.
|
||||
BuildSelect(cols []string, distinct bool, option string) string
|
||||
// BuildFrom generates a FROM clause from the given tables.
|
||||
BuildFrom(tables []string) string
|
||||
// BuildGroupBy generates a GROUP BY clause from the given group-by columns.
|
||||
BuildGroupBy(cols []string) string
|
||||
// BuildJoin generates a JOIN clause from the given join information.
|
||||
BuildJoin([]JoinInfo, Params) string
|
||||
// BuildWhere generates a WHERE clause from the given expression.
|
||||
BuildWhere(Expression, Params) string
|
||||
// BuildHaving generates a HAVING clause from the given expression.
|
||||
BuildHaving(Expression, Params) string
|
||||
// BuildOrderByAndLimit generates the ORDER BY and LIMIT clauses.
|
||||
BuildOrderByAndLimit(string, []string, int64, int64) string
|
||||
// BuildUnion generates a UNION clause from the given union information.
|
||||
BuildUnion([]UnionInfo, Params) string
|
||||
}
|
||||
|
||||
// BaseQueryBuilder provides a basic implementation of QueryBuilder.
|
||||
type BaseQueryBuilder struct {
|
||||
db *DB
|
||||
}
|
||||
|
||||
var _ QueryBuilder = &BaseQueryBuilder{}
|
||||
|
||||
// NewBaseQueryBuilder creates a new BaseQueryBuilder instance.
|
||||
func NewBaseQueryBuilder(db *DB) *BaseQueryBuilder {
|
||||
return &BaseQueryBuilder{db}
|
||||
}
|
||||
|
||||
// DB returns the DB instance associated with the query builder.
|
||||
func (q *BaseQueryBuilder) DB() *DB {
|
||||
return q.db
|
||||
}
|
||||
|
||||
// the regexp for columns and tables.
|
||||
var selectRegex = regexp.MustCompile(`(?i:\s+as\s+|\s+)([\w\-_\.]+)$`)
|
||||
|
||||
// BuildSelect generates a SELECT clause from the given selected column names.
|
||||
func (q *BaseQueryBuilder) BuildSelect(cols []string, distinct bool, option string) string {
|
||||
var s bytes.Buffer
|
||||
s.WriteString("SELECT ")
|
||||
if distinct {
|
||||
s.WriteString("DISTINCT ")
|
||||
}
|
||||
if option != "" {
|
||||
s.WriteString(option)
|
||||
s.WriteString(" ")
|
||||
}
|
||||
if len(cols) == 0 {
|
||||
s.WriteString("*")
|
||||
return s.String()
|
||||
}
|
||||
|
||||
for i, col := range cols {
|
||||
if i > 0 {
|
||||
s.WriteString(", ")
|
||||
}
|
||||
matches := selectRegex.FindStringSubmatch(col)
|
||||
if len(matches) == 0 {
|
||||
s.WriteString(q.db.QuoteColumnName(col))
|
||||
} else {
|
||||
col := col[:len(col)-len(matches[0])]
|
||||
alias := matches[1]
|
||||
s.WriteString(q.db.QuoteColumnName(col) + " AS " + q.db.QuoteSimpleColumnName(alias))
|
||||
}
|
||||
}
|
||||
|
||||
return s.String()
|
||||
}
|
||||
|
||||
// BuildFrom generates a FROM clause from the given tables.
|
||||
func (q *BaseQueryBuilder) BuildFrom(tables []string) string {
|
||||
if len(tables) == 0 {
|
||||
return ""
|
||||
}
|
||||
s := ""
|
||||
for _, table := range tables {
|
||||
table = q.quoteTableNameAndAlias(table)
|
||||
if s == "" {
|
||||
s = table
|
||||
} else {
|
||||
s += ", " + table
|
||||
}
|
||||
}
|
||||
return "FROM " + s
|
||||
}
|
||||
|
||||
// BuildJoin generates a JOIN clause from the given join information.
|
||||
func (q *BaseQueryBuilder) BuildJoin(joins []JoinInfo, params Params) string {
|
||||
if len(joins) == 0 {
|
||||
return ""
|
||||
}
|
||||
parts := []string{}
|
||||
for _, join := range joins {
|
||||
sql := join.Join + " " + q.quoteTableNameAndAlias(join.Table)
|
||||
on := ""
|
||||
if join.On != nil {
|
||||
on = join.On.Build(q.db, params)
|
||||
}
|
||||
if on != "" {
|
||||
sql += " ON " + on
|
||||
}
|
||||
parts = append(parts, sql)
|
||||
}
|
||||
return strings.Join(parts, " ")
|
||||
}
|
||||
|
||||
// BuildWhere generates a WHERE clause from the given expression.
|
||||
func (q *BaseQueryBuilder) BuildWhere(e Expression, params Params) string {
|
||||
if e != nil {
|
||||
if c := e.Build(q.db, params); c != "" {
|
||||
return "WHERE " + c
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// BuildHaving generates a HAVING clause from the given expression.
|
||||
func (q *BaseQueryBuilder) BuildHaving(e Expression, params Params) string {
|
||||
if e != nil {
|
||||
if c := e.Build(q.db, params); c != "" {
|
||||
return "HAVING " + c
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// BuildGroupBy generates a GROUP BY clause from the given group-by columns.
|
||||
func (q *BaseQueryBuilder) BuildGroupBy(cols []string) string {
|
||||
if len(cols) == 0 {
|
||||
return ""
|
||||
}
|
||||
s := ""
|
||||
for i, col := range cols {
|
||||
if i == 0 {
|
||||
s = q.db.QuoteColumnName(col)
|
||||
} else {
|
||||
s += ", " + q.db.QuoteColumnName(col)
|
||||
}
|
||||
}
|
||||
return "GROUP BY " + s
|
||||
}
|
||||
|
||||
// BuildOrderByAndLimit generates the ORDER BY and LIMIT clauses.
|
||||
func (q *BaseQueryBuilder) BuildOrderByAndLimit(sql string, cols []string, limit int64, offset int64) string {
|
||||
if orderBy := q.BuildOrderBy(cols); orderBy != "" {
|
||||
sql += " " + orderBy
|
||||
}
|
||||
if limit := q.BuildLimit(limit, offset); limit != "" {
|
||||
return sql + " " + limit
|
||||
}
|
||||
return sql
|
||||
}
|
||||
|
||||
// BuildUnion generates a UNION clause from the given union information.
|
||||
func (q *BaseQueryBuilder) BuildUnion(unions []UnionInfo, params Params) string {
|
||||
if len(unions) == 0 {
|
||||
return ""
|
||||
}
|
||||
sql := ""
|
||||
for i, union := range unions {
|
||||
if i > 0 {
|
||||
sql += " "
|
||||
}
|
||||
for k, v := range union.Query.params {
|
||||
params[k] = v
|
||||
}
|
||||
u := "UNION"
|
||||
if union.All {
|
||||
u = "UNION ALL"
|
||||
}
|
||||
sql += fmt.Sprintf("%v (%v)", u, union.Query.sql)
|
||||
}
|
||||
return sql
|
||||
}
|
||||
|
||||
var orderRegex = regexp.MustCompile(`\s+((?i)ASC|DESC)$`)
|
||||
|
||||
// BuildOrderBy generates the ORDER BY clause.
|
||||
func (q *BaseQueryBuilder) BuildOrderBy(cols []string) string {
|
||||
if len(cols) == 0 {
|
||||
return ""
|
||||
}
|
||||
s := ""
|
||||
for i, col := range cols {
|
||||
if i > 0 {
|
||||
s += ", "
|
||||
}
|
||||
matches := orderRegex.FindStringSubmatch(col)
|
||||
if len(matches) == 0 {
|
||||
s += q.db.QuoteColumnName(col)
|
||||
} else {
|
||||
col := col[:len(col)-len(matches[0])]
|
||||
dir := matches[1]
|
||||
s += q.db.QuoteColumnName(col) + " " + dir
|
||||
}
|
||||
}
|
||||
return "ORDER BY " + s
|
||||
}
|
||||
|
||||
// BuildLimit generates the LIMIT clause.
|
||||
func (q *BaseQueryBuilder) BuildLimit(limit int64, offset int64) string {
|
||||
if limit < 0 && offset > 0 {
|
||||
// most DBMS requires LIMIT when OFFSET is present
|
||||
limit = 9223372036854775807 // 2^63 - 1
|
||||
}
|
||||
|
||||
sql := ""
|
||||
if limit >= 0 {
|
||||
sql = fmt.Sprintf("LIMIT %v", limit)
|
||||
}
|
||||
if offset <= 0 {
|
||||
return sql
|
||||
}
|
||||
if sql != "" {
|
||||
sql += " "
|
||||
}
|
||||
return sql + fmt.Sprintf("OFFSET %v", offset)
|
||||
}
|
||||
|
||||
func (q *BaseQueryBuilder) quoteTableNameAndAlias(table string) string {
|
||||
matches := selectRegex.FindStringSubmatch(table)
|
||||
if len(matches) == 0 {
|
||||
return q.db.QuoteTableName(table)
|
||||
}
|
||||
table = table[:len(table)-len(matches[0])]
|
||||
return q.db.QuoteTableName(table) + " " + q.db.QuoteSimpleTableName(matches[1])
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue