解放數(shù)據(jù)庫查詢, 寫一個 go 的查詢構造器

前言

數(shù)據(jù)庫查詢

在 go 開發(fā)中, 查詢數(shù)據(jù)庫一般有兩種選擇:

  • 使用 orm (gorm\xorm 等)
  • 直接寫 SQL

直接編寫 SQL 語義清晰, 不易出錯, 但是遇到多個可變條件時顯得不靈活

ORM 有模型關系, 記錄預加載 (sql 生成優(yōu)化) 等功能, 但是 sql 語句對開發(fā)人員相對透明, 管了太多數(shù)據(jù)庫相關的東西, 相對封閉, 語法晦澀語義不明確, 想要操作 db 連接斯稳、構造復雜 SQL 很繁瑣

查詢構造器

對于查詢場景少巍糯、查詢條件相對固定的系統(tǒng), 直接寫 SQL 無疑是一種好的選擇梯醒。那么, 對于 SQL 多變的場景而又不想使用 orm 的開發(fā)者, 如何能快速開發(fā)數(shù)據(jù)層呢?

go 的官方包已經提供了好用的 database/sql 工具, 也有各個數(shù)據(jù)庫的驅動包, 屏蔽了底層驅動差異, 使數(shù)據(jù)庫查詢變得簡單, 只需提供 SQL 語句和占位符參數(shù)即可快速查詢, 也無需考慮 SQL 注入等問題癌别。那么, 只要解決了 SQL 語句和占位符參數(shù)的構造問題, 就解決了直接寫 SQL 的靈活性問題棒假。

為了解決 SQL 語句和占位符參數(shù)的構造問題, 我們需要查詢構造器 (Query Builder)。簡而言之, 查詢構造器就是利用 database/sql 的優(yōu)勢, 提供了一種 orm 和 raw sql 之間的中間方案囤捻。有了查詢構造器, 你可以在遇到不定 SQL 時動態(tài)構造 SQL, 遇到復雜確定 SQL 時直接寫原生 SQL, 使數(shù)據(jù)查詢更加靈活可控食听。

思路

做什么

查詢構造器, 顧名思義, 最主要的就是構造籍救。構造什么? 查詢語句。查詢語句本身就是一個滿足標準 SQL 規(guī)范的字符串, 所以我們要做查詢構造器, 主要的任務就是構造字符串渠抹。

拆解 SQL

在構造一條 SQL 之前, 不妨看看一條 SQL 是什么樣的吧蝙昙。

SELECT `name`,`age`,`school` FROM `test` WHERE `name` = 'jack'

復雜點的, 帶聯(lián)合查詢、分組梧却、排序奇颠、分頁

SELECT `t1`.`name`,`t1`.`age`,`t2`.`teacher`,`t3`.`address` FROM `test` as t1 LEFT JOIN `test2` as `t2` ON `t1`.`class` = `t2`.`class` INNER JOIN `test3` as t3 ON `t1`.`school` = `t3`.`school` WHERE `t1`.`age` >= 20 GROUP BY `t1`.`age` HAVING COUNT(`t1`.`age`) > 2 ORDER BY `t1`.`age` DESC LIMIT 10 OFFSET 0

當然, 標準 SQL 還有很多語法規(guī)定, 這里就不一一舉例。而對于規(guī)范中最常用的語法, 我們的查詢構造器必須要有構造它們的能力

一個標準的查詢語句結構如下:

SELECT [字段] FROM [表名] [JOIN 子句] [WHERE 子句] [GROUP BY 子句] [HAVING 子句] [ORDER BY 子句] [LIMIT 子句]

其中 JOIN 子句放航、WHERE 子句烈拒、 HAVING 子句和 LIMIT 子句會用到占位符參數(shù)

再看 INSERT、UPDATE广鳍、DELETE 操作的結構:

INSERT

INSERT INTO [表名] ([字段名]) VALUES ([要插入的值])

要插入的值會用到占位符參數(shù)

UPDATE

UPDATE [表名] [SET 子句] [WHERE 子句] 

SET 子句和 WHERE 子句會用到占位符參數(shù)

DELETE

DELETE FROM [表名] [WHERE 子句] 

WHERE 子句會用到占位符參數(shù)

OK, 拆解后是不是覺得 SQL 語句的基本結構很簡單? 要實現(xiàn)查詢構造器, 只需按照這些語句的結構構造出相應的字符串, 并保存需要的占位符參數(shù)即可荆几。

實現(xiàn)

有了思路, 實現(xiàn)起來就簡單了。

參考其他語言的查詢構造器, 方法名直接體現(xiàn) SQL 語法, 多為鏈式調用:

$db.table("`test`").
    where("a", ">", 20).
    where("b", "=", "aaa").
    get()

要實現(xiàn)查詢構造器, 這是一個好的示范赊时。

話不多說, 開寫!

首先定義我們的 SQLBuilder 類型:

type SQLBuilder struct {
    _select       string // select 子句字符串
    _insert       string // insert 子句字符串
    _update       string // update 子句字符串
    _delete       string // delete 子句字符串
    _table        string // 表名
    _join         string // join 子句字符串
    _where        string // where 子句字符串
    _groupBy      string // group by 子句字符串
    _having       string // having 子句字符串
    _orderBy      string // order by 子句字符串
    _limit        string // limit 子句字符串
    _insertParams []interface{} // insert 插入值需要的占位符參數(shù)
    _updateParams []interface{} // update SET 子句需要的占位符參數(shù)
    _whereParams  []interface{} // where 子句需要的占位符參數(shù)
    _havingParams []interface{} // having 子句需要的占位符參數(shù)
    _limitParams  []interface{} // limit 子句需要的占位符參數(shù)
    _joinParams   []interface{} // join 子句需要的占位符參數(shù)
}

SQLBuilder 的構造函數(shù):

func NewSQLBuilder() *SQLBuilder {
    return &SQLBuilder{}
}

獲取 SQL 字符串

獲取字符串很簡單, 只要按照 SQL 的規(guī)定將各個子句組合即可吨铸。

獲取 QuerySQL:

var ErrTableEmpty = errors.New("table empty")

func (sb *SQLBuilder) GetQuerySQL() (string, error) {
    if sb._table == "" {
        return "", ErrTableEmpty
    }
    var buf strings.Builder

    buf.WriteString("SELECT ")
    if sb._select != "" {
        buf.WriteString(sb._select)
    } else {
        buf.WriteString("*")
    }
    buf.WriteString(" FROM ")
    buf.WriteString(sb._table)
    if sb._join != "" {
        buf.WriteString(" ")
        buf.WriteString(sb._join)
    }
    if sb._where != "" {
        buf.WriteString(" ")
        buf.WriteString(sb._where)
    }
    if sb._groupBy != "" {
        buf.WriteString(" ")
        buf.WriteString(sb._groupBy)
    }
    if sb._having != "" {
        buf.WriteString(" ")
        buf.WriteString(sb._having)
    }
    if sb._orderBy != "" {
        buf.WriteString(" ")
        buf.WriteString(sb._orderBy)
    }
    if sb._limit != "" {
        buf.WriteString(" ")
        buf.WriteString(sb._limit)
    }

    return buf.String(), nil
}

tips: 上述代碼使用 strings.Builder 包來拼接字符串。當然構造查詢語句本身不是一個高頻操作, 不考慮效率使用 + 來拼接也是可以的

獲取 InsertSQL:

var ErrInsertEmpty = errors.New("insert content empty")

func (sb *SQLBuilder) GetInsertSQL() (string, error) {
    if sb._table == "" {
        return "", ErrTableEmpty
    }
    if sb._insert == "" {
        return "", ErrInsertEmpty
    }

    var buf strings.Builder

    buf.WriteString("INSERT INTO ")
    buf.WriteString(sb._table)
    buf.WriteString(" ")
    buf.WriteString(sb._insert)

    return buf.String(), nil
}

獲取 UpdateSQL:

var ErrUpdateEmpty = errors.New("update content empty")

func (sb *SQLBuilder) GetUpdateSQL() (string, error) {
    if sb._table == "" {
        return "", ErrTableEmpty
    }

    if sb._update == "" {
        return "", ErrUpdateEmpty
    }

    var buf strings.Builder

    buf.WriteString("UPDATE ")
    buf.WriteString(sb._table)
    buf.WriteString(" ")
    buf.WriteString(sb._update)
    if sb._where != "" {
        buf.WriteString(" ")
        buf.WriteString(sb._where)
    }

    return buf.String(), nil
}

獲取 DeteleSQL:

func (sb *SQLBuilder) GetDeleteSQL() (string, error) {
    if sb._table == "" {
        return "", ErrTableEmpty
    }

    var buf strings.Builder

    buf.WriteString("DELETE FROM ")
    buf.WriteString(sb._table)
    if sb._where != "" {
        buf.WriteString(" ")
        buf.WriteString(sb._where)
    }

    return buf.String(), nil
}

獲取占位符參數(shù)

同樣, 我們要填充占位符 "?" 的參數(shù)也需要獲得, query祖秒、insert诞吱、update、delete 擁有的參數(shù)類型都有差別, 也都有著不同的順序


func (sb *SQLBuilder) GetQueryParams() []interface{} {
    params := []interface{}{}
    params = append(params, sb._joinParams...)
    params = append(params, sb._whereParams...)
    params = append(params, sb._havingParams...)
    params = append(params, sb._limitParams...)
    return params
}

func (sb *SQLBuilder) GetInsertParams() []interface{} {
    params := []interface{}{}
    params = append(params, sb._insertParams...)
    return params
}

func (sb *SQLBuilder) GetUpdateParams() []interface{} {
    params := []interface{}{}
    params = append(params, sb._updateParams...)
    params = append(params, sb._whereParams...)
    return params
}

func (sb *SQLBuilder) GetDeleteParams() []interface{} {
    params := []interface{}{}
    params = append(params, sb._whereParams...)
    return params
}

表名設置

設置表名, 這里我們設置完成后返回 SQLBuilder 指針自己, 可以完成鏈式調用竭缝。之后大部分方法都會使用這種方式房维。

func (sb *SQLBuilder) Table(table string) *SQLBuilder {

    sb._table = table

    return sb
}

用例:

package main

import (
    "github.com/wazsmwazsm/QueryBuilder/builder"
    "log"
)

func main() {
    sb := builder.NewSQLBuilder()

    sql, err := sb.Table("`test`").
        Select("*").
        GetQuerySQL()
    if err != nil {
        log.Fatal(err)
    }

    params := sb.GetQueryParams()

    log.Println(sql)    // SELECT * FROM `test`
    log.Println(params) // []
}

select 子句

設置 select 子句, 支持多個參數(shù)用逗號隔開, 注意最后一個逗號要去掉

func (sb *SQLBuilder) Select(cols ...string) *SQLBuilder {
    var buf strings.Builder

    for k, col := range cols {

        buf.WriteString(col)

        if k != len(cols)-1 {
            buf.WriteString(",")
        }
    }

    sb._select = buf.String()

    return sb
}

用例:

package main

import (
    "github.com/wazsmwazsm/QueryBuilder/builder"
    "log"
)

func main() {
    sb := builder.NewSQLBuilder()

    sql, err := sb.Table("`test`").
        Select("`age`", "COUNT(age)").
        GetQuerySQL()
    if err != nil {
        log.Fatal(err)
    }

    params := sb.GetQueryParams()

    log.Println(sql)    // SELECT `age`,COUNT(age) FROM `test`
    log.Println(params) // []
}

where 子句

where

對于 where 子句, 第一個 where 條件需要 WHERE 關鍵字, 再有其它條件, 會通過 AND 和 OR 來連接, 那么我們可以增加 Where() 和 OrWhere() 方法, 兩個方法公共邏輯可以提出來:

func (sb *SQLBuilder) Where(field string, condition string, value interface{}) *SQLBuilder {
    return sb.where("AND", condition, field, value)
}

func (sb *SQLBuilder) OrWhere(field string, condition string, value interface{}) *SQLBuilder {
    return sb.where("OR", condition, field, value)
}

func (sb *SQLBuilder) where(operator string, condition string, field string, value interface{}) *SQLBuilder {
    var buf strings.Builder

    buf.WriteString(sb._where) // 載入之前的 where 子句

    if buf.Len() == 0 { // where 子句還沒設置
        buf.WriteString("WHERE ")
    } else { // 已經設置, 拼接 OR 或 AND 操作符
        buf.WriteString(" ")
        buf.WriteString(operator)
        buf.WriteString(" ")
    }

    buf.WriteString(field) // 拼接字段

    buf.WriteString(" ")
    buf.WriteString(condition) // 拼接條件 =、!=抬纸、<咙俩、>、like 等
    buf.WriteString(" ")
    buf.WriteString("?") // 拼接占位符

    sb._where = buf.String() // 寫字符串

    sb._whereParams = append(sb._whereParams, value) // push 占位符參數(shù)到數(shù)組

    return sb
}

用例:

package main

import (
    "github.com/wazsmwazsm/QueryBuilder/builder"
    "log"
)

func main() {
    sb := builder.NewSQLBuilder()

    sql, err := sb.Table("`test`").
        Select("`name`", "`age`", "`school`").
        Where("`name`", "=", "jack").
        Where("`age`", ">=", 18).
        OrWhere("`name`", "like", "%admin%").
        GetQuerySQL()
    if err != nil {
        log.Fatal(err)
    }

    params := sb.GetQueryParams()

    log.Println(sql)    // SELECT `name`,`age`,`school` FROM `test` WHERE `name` = ? AND `age` >= ? OR `name` like ?
    log.Println(params) // [jack 18 %admin%]
}

上述代碼可以解決簡單的條件子句, 如果遇到 WHERE a = ? AND (b = ? OR c = ?) 這樣的復雜子句, 該如何構造呢? 面對這種場景, 我們需要提供書寫原生 where 子句的能力, 增加 WhereRaw() 和 OrWhereRaw() 方法:

func (sb *SQLBuilder) WhereRaw(s string, values ...interface{}) *SQLBuilder {
    return sb.whereRaw("AND", s, values)
}

func (sb *SQLBuilder) OrWhereRaw(s string, values ...interface{}) *SQLBuilder {
    return sb.whereRaw("OR", s, values)
}

func (sb *SQLBuilder) whereRaw(operator string, s string, values []interface{}) *SQLBuilder {
    var buf strings.Builder

    buf.WriteString(sb._where) // append

    if buf.Len() == 0 {
        buf.WriteString("WHERE ")
    } else {
        buf.WriteString(" ")
        buf.WriteString(operator)
        buf.WriteString(" ")
    }

    buf.WriteString(s) // 直接使用 raw SQL 字符串
    sb._where = buf.String()

    for _, value := range values {
        sb._whereParams = append(sb._whereParams, value)
    }

    return sb
}

用例:

package main

import (
    "github.com/wazsmwazsm/QueryBuilder/builder"
    "log"
)

func main() {
    sb := builder.NewSQLBuilder()

    sql, err := sb.Table("`test`").
        Select("`name`", "`age`", "`school`").
        WhereRaw("`title` = ?", "hello").
        Where("`name`", "=", "jack").
        OrWhereRaw("(`age` = ? OR `age` = ?) AND `class` = ?", 22, 25, "2-3").
        GetQuerySQL()
    if err != nil {
        log.Fatal(err)
    }

    params := sb.GetQueryParams()

    log.Println(sql)    // SELECT `name`,`age`,`school` FROM `test` WHERE `title` = ? AND `name` = ? OR (`age` = ? OR `age` = ?) AND `class` = ?
    log.Println(params) // [hello jack 22 25 2-3]
}

where in

where in 也是常見的 where 子句, where in 子句分為 where in湿故、or where in暴浦、where not in溅话、or where not in 四種模式, 占位符數(shù)量等于 where in 的集合數(shù)量。

我們希望構造 where in 子句的方法入?yún)⑹且粋€ slice, 占位符的數(shù)量等于 slice 的長度, 那么我們需要封裝一個生成占位符的函數(shù):

func GenPlaceholders(n int) string {
    var buf strings.Builder

    for i := 0; i < n-1; i++ {
        buf.WriteString("?,") // 生成 n-1 個 "?" 占位符
    }

    if n > 0 {
        buf.WriteString("?") // 生成最后一個占位符, 如果 n <= 0 則不生成任何占位符
    }

    return buf.String()
}

按照 where in 子句的四種模式, 增加 WhereIn() OrWhereIn() WhereNotIn() OrWhereNotIn() 方法:

func (sb *SQLBuilder) WhereIn(field string, values ...interface{}) *SQLBuilder {
    return sb.whereIn("AND", "IN", field, values)
}

func (sb *SQLBuilder) OrWhereIn(field string, values ...interface{}) *SQLBuilder {
    return sb.whereIn("OR", "IN", field, values)
}

func (sb *SQLBuilder) WhereNotIn(field string, values ...interface{}) *SQLBuilder {
    return sb.whereIn("AND", "NOT IN", field, values)
}

func (sb *SQLBuilder) OrWhereNotIn(field string, values ...interface{}) *SQLBuilder {
    return sb.whereIn("OR", "NOT IN", field, values)
}

func (sb *SQLBuilder) whereIn(operator string, condition string, field string, values []interface{}) *SQLBuilder {
    var buf strings.Builder

    buf.WriteString(sb._where) // append

    if buf.Len() == 0 {
        buf.WriteString("WHERE ")
    } else {
        buf.WriteString(" ")
        buf.WriteString(operator)
        buf.WriteString(" ")
    }

    buf.WriteString(field)

    plhs := GenPlaceholders(len(values)) // 生成占位符
    buf.WriteString(" ")
    buf.WriteString(condition)
    buf.WriteString(" ")
    buf.WriteString("(")
    buf.WriteString(plhs) // 拼接占位符
    buf.WriteString(")")

    sb._where = buf.String()

    for _, value := range values { 
        sb._whereParams = append(sb._whereParams, value) // push 占位符參數(shù)
    }

    return sb
}

用例:

package main

import (
    "github.com/wazsmwazsm/QueryBuilder/builder"
    "log"
)

func main() {
    sb := builder.NewSQLBuilder()

    sql, err := sb.Table("`test`").
        Select("`name`", "`age`", "`school`").
        WhereIn("`id`", 1, 2, 3).
        OrWhereNotIn("`uid`", 2, 4).
        GetQuerySQL()
    if err != nil {
        log.Fatal(err)
    }

    params := sb.GetQueryParams()

    log.Println(sql)    // SELECT `name`,`age`,`school` FROM `test` WHERE `id` IN (?,?,?) OR `uid` NOT IN (?,?)
    log.Println(params) // [1 2 3 2 4]
}

group by 子句

group by 子句可以根據(jù)多個字段分組:

func (sb *SQLBuilder) GroupBy(fields ...string) *SQLBuilder {
    var buf strings.Builder

    buf.WriteString("GROUP BY ")

    for k, field := range fields {

        buf.WriteString(field)

        if k != len(fields)-1 {
            buf.WriteString(",")
        }
    }

    sb._groupBy = buf.String()

    return sb
}

having 子句和 where 子句基本相同, 這里就不費篇幅說明了, 詳細見 QueryBuilder/builder/builder.go

用例:

package main

import (
    "github.com/wazsmwazsm/QueryBuilder/builder"
    "log"
)

func main() {
    sb := builder.NewSQLBuilder()

    sql, err := sb.Table("`test`").
        Select("`school`", "`class`", "COUNT(*) as `ct`").
        GroupBy("`school`", "`class`").
        Having("`ct`", ">", "2").
        GetQuerySQL()
    if err != nil {
        log.Fatal(err)
    }

    params := sb.GetQueryParams()

    log.Println(sql)    // SELECT `school`,`class`,COUNT(*) as `ct` FROM `test` GROUP BY `school`,`class` HAVING `ct` > ?
    log.Println(params) // [2]
}

order by 子句和 limit 子句

order by 子句可以根據(jù)多個字段來排序:

func (sb *SQLBuilder) OrderBy(operator string, fields ...string) *SQLBuilder {
    var buf strings.Builder

    buf.WriteString("ORDER BY ")

    for k, field := range fields {

        buf.WriteString(field)

        if k != len(fields)-1 {
            buf.WriteString(",")
        }
    }

    buf.WriteString(" ")
    buf.WriteString(operator) // DESC 或 ASC

    sb._orderBy = buf.String()

    return sb
}

limit 來限制查詢的結果, 這里我們使用 LIMIT OFFSET 語法, 這個語法是標準 SQL 規(guī)定的, LIMIT x,x 這個形式只有 mysql 支持

func (sb *SQLBuilder) Limit(offset, num interface{}) *SQLBuilder {
    var buf strings.Builder

    buf.WriteString("LIMIT ? OFFSET ?")

    sb._limit = buf.String()

    sb._limitParams = append(sb._limitParams, num, offset)

    return sb
}

用例:

package main

import (
    "github.com/wazsmwazsm/QueryBuilder/builder"
    "log"
)

func main() {
    sb := builder.NewSQLBuilder()

    sql, err := sb.Table("`test`").
        Select("`name`", "`age`", "`school`").
        Where("`name`", "=", "jack").
        Where("`age`", ">=", 18).
        OrderBy("DESC", "`age`", "`class`").
        Limit(1, 10).
        GetQuerySQL()
    if err != nil {
        log.Fatal(err)
    }

    params := sb.GetQueryParams()

    log.Println(sql)    // SELECT `name`,`age`,`school` FROM `test` WHERE `name` = ? AND `age` >= ? ORDER BY `age`,`class` DESC LIMIT ? OFFSET ?
    log.Println(params) // [jack 18 10 1]
}

join 子句

使用 join 子句后, SQL 變得復雜歌焦。標準 SQL join 有 left join飞几、right join、inner join独撇、full join 幾種模式 join 子句的 on 條件類似 where 子句, 連表后需要給表起別名用來區(qū)分字段所屬...面對這樣靈活多變的語法, 我們這里較好的方式就是提供 raw sql 的形式來處理 join 操作:

func (sb *SQLBuilder) JoinRaw(join string, values ...interface{}) *SQLBuilder {
    var buf strings.Builder

    buf.WriteString(sb._join)
    if buf.Len() != 0 {
        buf.WriteString(" ")
    }
    buf.WriteString(join) // 拼接 raw join sql

    sb._join = buf.String()

    for _, value := range values {
        sb._joinParams = append(sb._joinParams, value)
    }

    return sb
}

用例 (構造一個復雜的查詢):

package main

import (
    "github.com/wazsmwazsm/QueryBuilder/builder"
    "log"
)

func main() {
    sb := builder.NewSQLBuilder()

    sql, err := sb.Table("`test` as t1").
        Select("`t1`.`name`", "`t1`.`age`", "`t2`.`teacher`", "`t3`.`address`").
        JoinRaw("LEFT JOIN `test2` as `t2` ON `t1`.`class` = `t2`.`class`").
        JoinRaw("INNER JOIN `test3` as t3 ON `t1`.`school` = `t3`.`school`").
        Where("`t1`.`age`", ">=", 18).
        GroupBy("`t1`.`age`").
        Having("COUNT(`t1`.`age`)", ">", 2).
        OrderBy("DESC", "`t1`.`age`").
        Limit(1, 10).
        GetQuerySQL()
    if err != nil {
        log.Fatal(err)
    }

    params := sb.GetQueryParams()

    log.Println(sql)    // SELECT `t1`.`name`,`t1`.`age`,`t2`.`teacher`,`t3`.`address` FROM `test` as t1 LEFT JOIN `test2` as `t2` ON `t1`.`class` = `t2`.`class` INNER JOIN `test3` as t3 ON `t1`.`school` = `t3`.`school` WHERE `t1`.`age` >= ? GROUP BY `t1`.`age` HAVING COUNT(`t1`.`age`) > ? ORDER BY `t1`.`age` DESC LIMIT ? OFFSET ?
    log.Println(params) // [18 2 10 1]
}

insert

insert SQL 構建:

func (sb *SQLBuilder) Insert(cols []string, values ...interface{}) *SQLBuilder {
    var buf strings.Builder

    // 拼接字段
    buf.WriteString("(")
    for k, col := range cols {

        buf.WriteString(col)

        if k != len(cols)-1 {
            buf.WriteString(",")
        }
    }
    buf.WriteString(") VALUES (")

    // 拼接占位符
    for k := range cols {
        buf.WriteString("?")
        if k != len(cols)-1 {
            buf.WriteString(",")
        }
    }
    buf.WriteString(")")

    sb._insert = buf.String()

    for _, value := range values { // push 占位符參數(shù)
        sb._insertParams = append(sb._insertParams, value)
    }

    return sb
}

用例:

package main

import (
    "github.com/wazsmwazsm/QueryBuilder/builder"
    "log"
)

func main() {
    sb := builder.NewSQLBuilder()

    sql, err := sb.Table("`test`").
        Insert([]string{"`name`", "`age`"}, "jack", 18).
        GetInsertSQL()
    if err != nil {
        log.Fatal(err)
    }

    params := sb.GetInsertParams()

    log.Println(sql)    // INSERT INTO `test` (`name`,`age`) VALUES (?,?)
    log.Println(params) // [jack 18]
}

update

update SQL 構建:

package main

import (
    "github.com/wazsmwazsm/QueryBuilder/builder"
    "log"
)

func main() {
    sb := builder.NewSQLBuilder()

    sql, err := sb.Table("`test`").
        Update([]string{"`name`", "`age`"}, "jack", 18).
        Where("`id`", "=", 11).
        GetUpdateSQL()
    if err != nil {
        log.Fatal(err)
    }

    params := sb.GetUpdateParams()

    log.Println(sql)    // UPDATE `test` SET `name` = ?,`age` = ? WHERE `id` = ?
    log.Println(params) // [jack 18 11]
}

delete

delete SQL 構建:

package main

import (
    "github.com/wazsmwazsm/QueryBuilder/builder"
    "log"
)

func main() {
    sb := builder.NewSQLBuilder()

    sql, err := sb.Table("`test`").
        Where("`id`", "=", 11).
        GetDeleteSQL()
    if err != nil {
        log.Fatal(err)
    }

    params := sb.GetDeleteParams()

    log.Println(sql)    // DELETE FROM `test` WHERE `id` = ?
    log.Println(params) // [11]
}

OK, 查詢構造器的實現(xiàn)到此結束, 是不是很簡單呢?

使用

查詢構造器實現(xiàn)了, 那么就結合 database/sql 用用吧!

以 mysql 為例:

package main

import (
    "database/sql"
    "fmt"
    _ "github.com/go-sql-driver/mysql"
    "github.com/wazsmwazsm/QueryBuilder/builder"
    "log"
)

// Info 定義一個數(shù)據(jù)模型, 用于接收查詢數(shù)據(jù)
type Info struct {
    Age      int
    AgeCount int
}

func main() {
    // 創(chuàng)建 mysql 連接
    dataSource := fmt.Sprintf("%s:%s@tcp(%s:%v)/%s?charset=utf8",
        "test", "test", "127.0.0.1", 3306, "test")
    mysqlConn, err := sql.Open("mysql", dataSource)
    if err != nil {
        log.Panic("Db connect failed!" + err.Error())
    }

    // 創(chuàng)建查詢構造器實例
    sb := builder.NewSQLBuilder()

    querySQL, err := sb.Table("`test`").
        Select("`age`", "COUNT(age)").
        GroupBy("`age`").
        GetQuerySQL()
    if err != nil {
        log.Fatal(err)
    }

    params := sb.GetQueryParams()

    // 執(zhí)行查詢
    rows, err := mysqlConn.Query(querySQL, params...)
    if err != nil {
        log.Panic(err)
    }
    defer rows.Close()

    // 查詢數(shù)據(jù)綁定到 info 結構中
    infos := []*Info{}
    for rows.Next() {
        info := new(Info)
        if err := rows.Scan(
            &info.Age,
            &info.AgeCount,
        ); err != nil {
            log.Panicln(err)
        }
        infos = append(infos, info)
    }

    for _, info := range infos {
        fmt.Println(info)
    }

}

源碼地址

該項目的全部源碼詳見 QueryBuilder, 單元測試已 100% 覆蓋

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末屑墨,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子纷铣,更是在濱河造成了極大的恐慌卵史,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件搜立,死亡現(xiàn)場離奇詭異以躯,居然都是意外死亡,警方通過查閱死者的電腦和手機啄踊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進店門忧设,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人颠通,你說我怎么就攤上這事址晕。” “怎么了顿锰?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵谨垃,是天一觀的道長。 經常有香客問我硼控,道長刘陶,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任牢撼,我火速辦了婚禮易核,結果婚禮上,老公的妹妹穿的比我還像新娘浪默。我一直安慰自己牡直,他們只是感情好,可當我...
    茶點故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布纳决。 她就那樣靜靜地躺著碰逸,像睡著了一般。 火紅的嫁衣襯著肌膚如雪阔加。 梳的紋絲不亂的頭發(fā)上饵史,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天,我揣著相機與錄音,去河邊找鬼胳喷。 笑死湃番,一個胖子當著我的面吹牛,可吹牛的內容都是我干的吭露。 我是一名探鬼主播吠撮,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼讲竿!你這毒婦竟也來了泥兰?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤题禀,失蹤者是張志新(化名)和其女友劉穎鞋诗,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體迈嘹,經...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡削彬,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了秀仲。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片融痛。...
    茶點故事閱讀 39,965評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖啄育,靈堂內的尸體忽然破棺而出酌心,到底是詐尸還是另有隱情拌消,我是刑警寧澤挑豌,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站墩崩,受9級特大地震影響氓英,放射性物質發(fā)生泄漏。R本人自食惡果不足惜鹦筹,卻給世界環(huán)境...
    茶點故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一铝阐、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧铐拐,春花似錦徘键、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至虚青,卻和暖如春它呀,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工纵穿, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留下隧,地道東北人。 一個月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓谓媒,卻偏偏與公主長得像淆院,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子篙耗,可洞房花燭夜當晚...
    茶點故事閱讀 44,914評論 2 355

推薦閱讀更多精彩內容

  • 每個人內心都有急于想向他人證明自己是獨特的心理傾向迫筑。 所以神學女孩也不例外。 8月12日晚宗弯,2號線換乘1號線來到閘...
    yc辰苑閱讀 322評論 0 0
  • 我愿徜徉在獨身一人的世界里 那里沒有高樓大廈 只有奇形怪狀的樹樁 伴隨著高的離譜的芭蕉 它揮舞著全身的綠葉 像是朝...
    如果你是H閱讀 349評論 4 3
  • 一脯燃、每日記錄之重要的事: 嘗試訓練自己記錄發(fā)生了“什么”,是“如何”發(fā)生的蒙保,而不僅僅是事后自以為是地去解釋“為什么...
    空靈一月閱讀 490評論 0 0
  • 山東光福華夏新能源有限公司辕棚,晶科,晶澳邓厕,國家電網獨家代理逝嚎,全國136家加盟商,竭誠為您服務详恼。15554131615
    kxk1986閱讀 157評論 0 0
  • 今天還是很冷补君,天又陰沉,太陽小小的露了一下臉就又縮回去了昧互,一天再沒有見到挽铁。 突然很懷念太陽光芒萬丈的溫暖。 今天跟...
    如煙_f580閱讀 279評論 2 5