This commit is contained in:
Huan Du
2017-12-28 00:37:13 +08:00
commit 2f7b2c59bb
11 changed files with 778 additions and 0 deletions
+35
View File
@@ -0,0 +1,35 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
*.prof
# Intellij
*.iml
.idea/
# VS Code
debug
debug_test
# Mac
.DS_Store
+1
View File
@@ -0,0 +1 @@
language: go
+19
View File
@@ -0,0 +1,19 @@
Copyright (c) 2018 Huan Du
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+6
View File
@@ -0,0 +1,6 @@
# SQL builder for Go #
[![Build Status](https://travis-ci.org/huandu/go-sqlbuilder.svg?branch=master)](https://travis-ci.org/huandu/go-sqlbuilder)
[![GoDoc](https://godoc.org/github.com/huandu/go-sqlbuilder?status.svg)](https://godoc.org/github.com/huandu/go-sqlbuilder)
TBD
+89
View File
@@ -0,0 +1,89 @@
// Copyright 2018 Huan Du. All rights reserved.
// Licensed under the MIT license that can be found in the LICENSE file.
package sqlbuilder
import (
"bytes"
"fmt"
"strconv"
"strings"
)
// Escape replaces `$` with `$$` in ident.
func Escape(ident string) string {
return strings.Replace(ident, "$", "$$", -1)
}
// EscapeAll replaces `$` with `$$` in all strings of ident.
func EscapeAll(ident ...string) []string {
escaped := make([]string, 0, len(ident))
for _, i := range ident {
escaped = append(escaped, Escape(i))
}
return escaped
}
// Args stores arguments associated with a SQL.
type Args struct {
args []interface{}
}
// Add adds an arg to Args and returns a placeholder.
func (args *Args) Add(arg interface{}) string {
idx := len(args.args)
args.args = append(args.args, arg)
return fmt.Sprintf("$%v", idx)
}
// Compile analyzes builder's sql to standard sql and returns associated args.
//
// A builder uses `$N` to represent an argument in arguments.
// Unescape replaces `$N` to `?`, which is the placeholder supported by SQL driver,
// and then creates a new args associated with the placeholder.
func (args *Args) Compile(sql string) (query string, values []interface{}) {
buf := &bytes.Buffer{}
idx := strings.IndexRune(sql, '$')
values = make([]interface{}, 0, len(args.args))
for idx >= 0 {
if idx > 0 {
buf.WriteString(sql[:idx])
}
sql = sql[:idx+1]
// Should not happen.
if len(sql) == 0 {
break
}
if sql[0] == '$' {
buf.WriteRune('$')
sql = sql[1:]
} else {
i := 0
for ; i < len(sql) && '0' <= sql[i] && sql[i] <= '9'; i++ {
// Nothing.
}
if i > 0 {
digits := sql[:i]
sql = sql[i:]
if pointer, err := strconv.Atoi(digits); err == nil && pointer < len(args.args) {
buf.WriteRune('?')
values = append(values, args.args[pointer])
}
}
}
idx = strings.IndexRune(sql, '$')
}
query = buf.String()
return
}
+116
View File
@@ -0,0 +1,116 @@
// Copyright 2018 Huan Du. All rights reserved.
// Licensed under the MIT license that can be found in the LICENSE file.
package sqlbuilder
import (
"fmt"
"strings"
)
// Cond provides several helper methods to build conditions.
type Cond struct {
Args *Args
}
// Equal represents "field = value".
func (c *Cond) Equal(field string, value interface{}) string {
return fmt.Sprintf("%v = %v", Escape(field), c.Args.Add(value))
}
// NotEqual represents "field != value".
func (c *Cond) NotEqual(field string, value interface{}) string {
return fmt.Sprintf("%v != %v", Escape(field), c.Args.Add(value))
}
// GreaterThan represents "field > value".
func (c *Cond) GreaterThan(field string, value interface{}) string {
return fmt.Sprintf("%v > %v", Escape(field), c.Args.Add(value))
}
// G is an alias of GreaterThan.
func (c *Cond) G(field string, value interface{}) string {
return c.GreaterThan(field, value)
}
// GreaterEqualThan represents "field >= value".
func (c *Cond) GreaterEqualThan(field string, value interface{}) string {
return fmt.Sprintf("%v >= %v", Escape(field), c.Args.Add(value))
}
// GE is an alias of GreaterEqualThan.
func (c *Cond) GE(field string, value interface{}) string {
return c.GreaterEqualThan(field, value)
}
// LessThan represents "field < value".
func (c *Cond) LessThan(field string, value interface{}) string {
return fmt.Sprintf("%v < %v", Escape(field), c.Args.Add(value))
}
// L is an alias of LessThan.
func (c *Cond) L(field string, value interface{}) string {
return c.LessThan(field, value)
}
// LessEqualThan represents "field <= value".
func (c *Cond) LessEqualThan(field string, value interface{}) string {
return fmt.Sprintf("%v != %v", Escape(field), c.Args.Add(value))
}
// LE is an alias of LessEqualThan.
func (c *Cond) LE(field string, value interface{}) string {
return c.LessEqualThan(field, value)
}
// In represents "field IN (value...)".
func (c *Cond) In(field string, value ...interface{}) string {
vs := make([]string, 0, len(value))
for _, v := range value {
vs = append(vs, c.Args.Add(v))
}
return fmt.Sprintf("%v IN (%v)", Escape(field), strings.Join(vs, ", "))
}
// NotIn represents "field NOT IN (value...)".
func (c *Cond) NotIn(field string, value ...interface{}) string {
vs := make([]string, 0, len(value))
for _, v := range value {
vs = append(vs, c.Args.Add(v))
}
return fmt.Sprintf("%v NOT IN (%v)", Escape(field), strings.Join(vs, ", "))
}
// Like represents "field LIKE value".
func (c *Cond) Like(field string, value interface{}) string {
return fmt.Sprintf("%v LIKE %v", Escape(field), c.Args.Add(value))
}
// NotLike represents "field NOT LIKE value".
func (c *Cond) NotLike(field string, value interface{}) string {
return fmt.Sprintf("%v NOT LIKE %v", Escape(field), c.Args.Add(value))
}
// IsNull represents "field IS NULL".
func (c *Cond) IsNull(field string) string {
return fmt.Sprintf("%v IS NULL", Escape(field))
}
// IsNotNull represents "field IS NOT NULL".
func (c *Cond) IsNotNull(field string) string {
return fmt.Sprintf("%v IS NOT NULL", Escape(field))
}
// Between represents "field BETWEEN lower AND upper".
func (c *Cond) Between(field string, lower, upper interface{}) string {
return fmt.Sprintf("%v BETWEEN %v AND %v", Escape(field), c.Args.Add(lower), c.Args.Add(upper))
}
// Var returns a placeholder for value.
func (c *Cond) Var(value interface{}) string {
return c.Args.Add(value)
}
+83
View File
@@ -0,0 +1,83 @@
// Copyright 2018 Huan Du. All rights reserved.
// Licensed under the MIT license that can be found in the LICENSE file.
package sqlbuilder
import (
"bytes"
"strings"
)
// NewDeleteBuilder creates a new DELETE builder.
func NewDeleteBuilder() *DeleteBuilder {
args := &Args{}
return &DeleteBuilder{
Cond: Cond{
Args: args,
},
args: args,
}
}
// DeleteBuilder is a builder to build DELETE.
//
// Usage:
// db := NewDeleteBuilder()
// db.DeleteFrom("demo.user")
// .Where(
// db.GreaterThan("id", 1234),
// db.Like("name", "%Du"),
// db.Or(
// db.IsNull("id_card"),
// db.In("status", 1, 2, 5),
// ),
// "modified_at > created_at + " + db.Var(86400), // It's allowed to write arbitrary SQL.
// )
// sql, args := db.Build()
// fmt.Println(sql)
// fmt.Println(args)
//
// // Output:
// // DELETE FROM demo.user WHERE id > ? AND name LIKE ? AND (id_card IS NULL OR status IN (?, ?, ?)) AND modified_at > created_at + ?
// // [1234, %Du, 1, 2, 5, 86400]
type DeleteBuilder struct {
Cond
table string
whereExprs []string
args *Args
}
// DeleteFrom sets table name in DELETE.
func (db *DeleteBuilder) DeleteFrom(table string) *DeleteBuilder {
db.table = Escape(table)
return db
}
// Where sets expressions of WHERE in UPDATE.
func (db *DeleteBuilder) Where(andExpr ...string) *DeleteBuilder {
db.whereExprs = append(db.whereExprs, andExpr...)
return db
}
// String returns the compiled UPDATE string.
func (db *DeleteBuilder) String() string {
s, _ := db.Build()
return s
}
// Build returns compiled DELETE string and args.
// They can be used in `DB#Query` of package `database/sql` directly.
func (db *DeleteBuilder) Build() (sql string, args []interface{}) {
buf := &bytes.Buffer{}
buf.WriteString("DELETE FROM ")
buf.WriteString(db.table)
if len(db.whereExprs) > 0 {
buf.WriteString(" WHERE ")
buf.WriteString(strings.Join(db.whereExprs, " AND "))
}
return db.args.Compile(buf.String())
}
+5
View File
@@ -0,0 +1,5 @@
// Copyright 2018 Huan Du. All rights reserved.
// Licensed under the MIT license that can be found in the LICENSE file.
// Package sqlbuilder is a flexible and powerful tool to build SQL string and associated args.
package sqlbuilder
+95
View File
@@ -0,0 +1,95 @@
// Copyright 2018 Huan Du. All rights reserved.
// Licensed under the MIT license that can be found in the LICENSE file.
package sqlbuilder
import (
"bytes"
"fmt"
"strings"
)
// NewInsertBuilder creates a new INSERT builder.
func NewInsertBuilder() *InsertBuilder {
args := &Args{}
return &InsertBuilder{
args: args,
}
}
// InsertBuilder is a builder to build INSERT.
//
// Usage:
// ib := NewInsertBuilder()
// ib.InsertInto("demo.user")
// .Cols("id", "name", "status", "created_at")
// .Values(1, "Huan Du", 1, ib.Raw("UNIX_TIMESTAMP(NOW())"))
// .Values(2, "Charmy Liu", 1, 1234567890)
// sql, args := ib.Build()
// fmt.Println(sql)
// fmt.Println(args)
//
// // Output:
// // INSERT INTO demo.user (id, name, status, creasted_at) VALEUS (?, ?, ?, ?), (?, ?, ?, ?)
// // [1, Huan Du, 1, 2, Charmy Liu, 1, 1234567890]
type InsertBuilder struct {
table string
cols []string
values [][]string
args *Args
}
// InsertInto sets table name in INSERT.
func (ib *InsertBuilder) InsertInto(table string) *InsertBuilder {
ib.table = Escape(table)
return ib
}
// Cols sets columns in INSERT.
func (ib *InsertBuilder) Cols(col ...string) *InsertBuilder {
ib.cols = EscapeAll(col...)
return ib
}
// Values adds a list of values for a row in INSERT.
func (ib *InsertBuilder) Values(value ...interface{}) *InsertBuilder {
placeholders := make([]string, 0, len(value))
for _, v := range value {
placeholders = append(placeholders, ib.args.Add(v))
}
ib.values = append(ib.values, placeholders)
return ib
}
// String returns the compiled UPDATE string.
func (ib *InsertBuilder) String() string {
s, _ := ib.Build()
return s
}
// Build returns compiled INSERT string and args.
// They can be used in `DB#Query` of package `database/sql` directly.
func (ib *InsertBuilder) Build() (sql string, args []interface{}) {
buf := &bytes.Buffer{}
buf.WriteString("INSERT INTO ")
buf.WriteString(ib.table)
if len(ib.cols) > 0 {
buf.WriteString("(")
buf.WriteString(strings.Join(ib.cols, ", "))
buf.WriteString(")")
}
buf.WriteString(" VALUES ")
values := make([]string, 0, len(ib.values))
for _, v := range ib.values {
values = append(values, fmt.Sprintf("(%v)", strings.Join(v, ", ")))
}
buf.WriteString(strings.Join(values, ", "))
return ib.args.Compile(buf.String())
}
+190
View File
@@ -0,0 +1,190 @@
// Copyright 2018 Huan Du. All rights reserved.
// Licensed under the MIT license that can be found in the LICENSE file.
package sqlbuilder
import (
"bytes"
"fmt"
"strconv"
"strings"
)
// NewSelectBuilder creates a new SELECT builder.
func NewSelectBuilder() *SelectBuilder {
args := &Args{}
return &SelectBuilder{
Cond: Cond{
Args: args,
},
limit: -1,
offset: -1,
args: args,
}
}
// SelectBuilder is a builder to build SELECT.
//
// Usage:
// sb := NewSelectBuilder()
// sb.Distinct().Select("id", "name", sb.As("COUNT(*)", "t"))
// .From("demo.user")
// .Where(
// sb.GreaterThan("id", 1234),
// sb.Like("name", "%Du"),
// sb.Or(
// sb.IsNull("id_card"),
// sb.In("status", 1, 2, 5),
// ),
// "modified_at > created_at + " + sb.Var(86400), // It's allowed to write arbitrary SQL.
// )
// .GroupBy("status").Having(sb.NotIn("status", 4, 5))
// .OrderBy("modified_at").Asc()
// .Limit(10).Offset(5)
// sql, args := sb.Build()
// fmt.Println(sql)
// fmt.Println(args)
//
// // Output:
// // SELECT id, name, COUNT(*) AS t FROM demo.user WHERE id > ? AND name LIKE ? AND (id_card IS NULL OR status IN (?, ?, ?)) AND modified_at > created_at + ? GROUP BY status HAVING status NOT IN (4, 5) ORDER BY modified_at ASC LIMIT 10 OFFSET 5
// // [t, 1234, %Du, 1, 2, 5, 86400]
type SelectBuilder struct {
Cond
distinct bool
tables []string
selectCols []string
whereExprs []string
havingExprs []string
groupByCols []string
orderByCols []string
order string
limit int
offset int
args *Args
}
// Distinct marks this SELECT as DISTINCT.
func (sb *SelectBuilder) Distinct() *SelectBuilder {
sb.distinct = true
return sb
}
// Select sets columns in SELECT.
func (sb *SelectBuilder) Select(col ...string) *SelectBuilder {
sb.selectCols = EscapeAll(col...)
return sb
}
// From sets table names in SELECT.
func (sb *SelectBuilder) From(table ...string) *SelectBuilder {
sb.tables = EscapeAll(table...)
return sb
}
// Where sets expressions of WHERE in SELECT.
func (sb *SelectBuilder) Where(andExpr ...string) *SelectBuilder {
sb.whereExprs = append(sb.whereExprs, andExpr...)
return sb
}
// Having sets expressions of HAVING in SELECT.
func (sb *SelectBuilder) Having(andExpr ...string) *SelectBuilder {
sb.havingExprs = append(sb.havingExprs, andExpr...)
return sb
}
// GroupBy sets columns of GROUP BY in SELECT.
func (sb *SelectBuilder) GroupBy(col ...string) *SelectBuilder {
sb.groupByCols = EscapeAll(col...)
return sb
}
// OrderBy sets columns of ORDER BY in SELECT.
func (sb *SelectBuilder) OrderBy(col ...string) *SelectBuilder {
sb.orderByCols = EscapeAll(col...)
return sb
}
// Asc sets order of ORDER BY to ASC.
func (sb *SelectBuilder) Asc() *SelectBuilder {
sb.order = "ASC"
return sb
}
// Desc sets order of ORDER BY to DESC.
func (sb *SelectBuilder) Desc() *SelectBuilder {
sb.order = "DESC"
return sb
}
// Limit sets the LIMIT in SELECT.
func (sb *SelectBuilder) Limit(limit int) *SelectBuilder {
sb.limit = limit
return sb
}
// Offset sets the LIMIT offset in SELECT.
func (sb *SelectBuilder) Offset(offset int) *SelectBuilder {
sb.offset = offset
return sb
}
// As returns an AS expreesion.
func (sb *SelectBuilder) As(col, alias string) string {
return fmt.Sprintf("%v AS %v", Escape(col), Escape(alias))
}
// 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{}) {
buf := &bytes.Buffer{}
buf.WriteString("SELECT ")
buf.WriteString(strings.Join(sb.selectCols, ", "))
buf.WriteString(" FROM ")
buf.WriteString(strings.Join(sb.tables, ", "))
if len(sb.whereExprs) > 0 {
buf.WriteString(" WHERE ")
buf.WriteString(strings.Join(sb.whereExprs, " AND "))
}
if len(sb.groupByCols) > 0 {
buf.WriteString(" GROUP BY ")
buf.WriteString(strings.Join(sb.groupByCols, ", "))
if len(sb.havingExprs) > 0 {
buf.WriteString(" HAVING ")
buf.WriteString(strings.Join(sb.havingExprs, " AND "))
}
}
if len(sb.orderByCols) > 0 {
buf.WriteString(" ORDER BY ")
buf.WriteString(strings.Join(sb.orderByCols, ", "))
if sb.order != "" {
buf.WriteRune(' ')
buf.WriteString(sb.order)
}
}
if sb.limit >= 0 {
buf.WriteString(" LIMIT ")
buf.WriteString(strconv.Itoa(sb.limit))
if sb.offset >= 0 {
buf.WriteString(" OFFSET ")
buf.WriteString(strconv.Itoa(sb.offset))
}
}
return sb.Args.Compile(buf.String())
}
+139
View File
@@ -0,0 +1,139 @@
// Copyright 2018 Huan Du. All rights reserved.
// Licensed under the MIT license that can be found in the LICENSE file.
package sqlbuilder
import (
"bytes"
"fmt"
"strings"
)
// NewUpdateBuilder creates a new UPDATE builder.
func NewUpdateBuilder() *UpdateBuilder {
args := &Args{}
return &UpdateBuilder{
Cond: Cond{
Args: args,
},
args: args,
}
}
// UpdateBuilder is a builder to build UPDATE.
//
// Usage:
// ub := NewUpdateBuilder()
// ub.Update("demo.user")
// .Set(
// ub.Assign("type", "sys"),
// ub.Incr("credit"),
// "modified_at = UNIX_TIMESTAMP(NOW())", // It's allowed to write arbitrary SQL.
// )
// .Where(
// ub.GreaterThan("id", 1234),
// ub.Like("name", "%Du"),
// ub.Or(
// ub.IsNull("id_card"),
// ub.In("status", 1, 2, 5),
// ),
// "modified_at > created_at + " + ub.Var(86400), // It's allowed to write arbitrary SQL.
// )
// sql, args := ub.Build()
// fmt.Println(sql)
// fmt.Println(args)
//
// // Output:
// // UPDATE demo.user SET type = ?, credit = credit + 1, modified_at = UNIX_TIMESTAMP(NOW()) WHERE id > ? AND name LIKE ? AND (id_card IS NULL OR status IN (?, ?, ?)) AND modified_at > created_at + ?
// // [sys, 1234, %Du, 1, 2, 5, 86400]
type UpdateBuilder struct {
Cond
table string
assignments []string
whereExprs []string
args *Args
}
// Update sets table name in UPDATE.
func (ub *UpdateBuilder) Update(table string) *UpdateBuilder {
ub.table = Escape(table)
return ub
}
// Set sets the assignements in SET.
func (ub *UpdateBuilder) Set(assignment ...string) *UpdateBuilder {
ub.assignments = assignment
return ub
}
// Where sets expressions of WHERE in UPDATE.
func (ub *UpdateBuilder) Where(andExpr ...string) *UpdateBuilder {
ub.whereExprs = append(ub.whereExprs, andExpr...)
return ub
}
// Assign represents SET "field = value" in UPDATE.
func (ub *UpdateBuilder) Assign(field string, value interface{}) string {
return fmt.Sprintf("%v = %v", Escape(field), ub.args.Add(value))
}
// Incr represents SET "field = field + 1" in UPDATE.
func (ub *UpdateBuilder) Incr(field string) string {
f := Escape(field)
return fmt.Sprintf("%v = %v + 1", f, f)
}
// Decr represents SET "field = field - 1" in UPDATE.
func (ub *UpdateBuilder) Decr(field string) string {
f := Escape(field)
return fmt.Sprintf("%v = %v - 1", f, f)
}
// Add represents SET "field = field + value" in UPDATE.
func (ub *UpdateBuilder) Add(field string, value interface{}) string {
f := Escape(field)
return fmt.Sprintf("%v = %v + %v", f, f, ub.args.Add(value))
}
// Sub represents SET "field = field - value" in UPDATE.
func (ub *UpdateBuilder) Sub(field string, value interface{}) string {
f := Escape(field)
return fmt.Sprintf("%v = %v - %v", f, f, ub.args.Add(value))
}
// Mul represents SET "field = field * value" in UPDATE.
func (ub *UpdateBuilder) Mul(field string, value interface{}) string {
f := Escape(field)
return fmt.Sprintf("%v = %v * %v", f, f, ub.args.Add(value))
}
// Div represents SET "field = field / value" in UPDATE.
func (ub *UpdateBuilder) Div(field string, value interface{}) string {
f := Escape(field)
return fmt.Sprintf("%v = %v / %v", f, f, ub.args.Add(value))
}
// String returns the compiled UPDATE string.
func (ub *UpdateBuilder) String() string {
s, _ := ub.Build()
return s
}
// Build returns compiled UPDATE string and args.
// They can be used in `DB#Query` of package `database/sql` directly.
func (ub *UpdateBuilder) Build() (sql string, args []interface{}) {
buf := &bytes.Buffer{}
buf.WriteString("UPDATE ")
buf.WriteString(ub.table)
buf.WriteString(" SET ")
buf.WriteString(strings.Join(ub.assignments, ", "))
if len(ub.whereExprs) > 0 {
buf.WriteString(" WHERE ")
buf.WriteString(strings.Join(ub.whereExprs, " AND "))
}
return ub.args.Compile(buf.String())
}