├── .gitignore ├── .travis.yml ├── examples ├── delete │ └── main.go ├── update │ └── main.go ├── insert │ └── main.go ├── transaction │ └── main.go ├── join │ └── main.go └── select │ └── main.go ├── driverManager.go ├── LICENSE ├── driverManager_test.go ├── driver └── mysql │ ├── database.go │ ├── queryBuilder_test.go │ └── queryBuilder.go └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.8.x 4 | - 1.9.x 5 | - 1.10.x 6 | - master 7 | before_install: 8 | - go get golang.org/x/tools/cmd/cover 9 | - go get github.com/mattn/goveralls 10 | script: 11 | - go test -v -covermode=count -coverprofile=coverage.out 12 | - $HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci 13 | -------------------------------------------------------------------------------- /examples/delete/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | _ "github.com/go-sql-driver/mysql" 7 | "github.com/xujiajun/godbal" 8 | "github.com/xujiajun/godbal/driver/mysql" 9 | ) 10 | 11 | func main() { 12 | database, err := godbal.NewMysql("root:123@tcp(127.0.0.1:3306)/test?charset=utf8").Open() 13 | 14 | if err != nil { 15 | panic(err) 16 | } 17 | 18 | err = database.Ping() 19 | if err != nil { 20 | panic(err) 21 | } 22 | 23 | queryBuilder := mysql.NewQueryBuilder(database) 24 | 25 | affect, _ := queryBuilder.Delete("userinfo").Where("uid=?").SetParam(8).PrepareAndExecute() 26 | 27 | fmt.Println(affect) 28 | } 29 | -------------------------------------------------------------------------------- /examples/update/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | _ "github.com/go-sql-driver/mysql" 7 | "github.com/xujiajun/godbal" 8 | "github.com/xujiajun/godbal/driver/mysql" 9 | ) 10 | 11 | func main() { 12 | database, err := godbal.NewMysql("root:123@tcp(127.0.0.1:3306)/test?charset=utf8").Open() 13 | 14 | if err != nil { 15 | panic(err) 16 | } 17 | 18 | err = database.Ping() 19 | if err != nil { 20 | panic(err) 21 | } 22 | 23 | queryBuilder := mysql.NewQueryBuilder(database) 24 | 25 | rowsAffected, _ := queryBuilder.Update("userinfo", "u").SetParam(4).Set("u.username", "joe11").Set("u.flag", 0).Set("price", 1110.01).Where("u.uid=?").PrepareAndExecute() 26 | fmt.Println(rowsAffected) 27 | } 28 | -------------------------------------------------------------------------------- /examples/insert/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | _ "github.com/go-sql-driver/mysql" 7 | "github.com/xujiajun/godbal" 8 | "github.com/xujiajun/godbal/driver/mysql" 9 | ) 10 | 11 | func main() { 12 | database, err := godbal.NewMysql("root:123@tcp(127.0.0.1:3306)/test?charset=utf8").Open() 13 | 14 | if err != nil { 15 | panic(err) 16 | } 17 | 18 | err = database.Ping() 19 | if err != nil { 20 | panic(err) 21 | } 22 | 23 | queryBuilder := mysql.NewQueryBuilder(database) 24 | 25 | sql := queryBuilder.Insert("userinfo").Value("username", "johnny").Value("departname", "tec").Value("created", "1521010136").GetSQL() 26 | 27 | fmt.Print(sql) 28 | 29 | lastInsertId, err := queryBuilder.PrepareAndExecute() 30 | 31 | if err != nil { 32 | fmt.Print(err) 33 | } 34 | 35 | fmt.Println(lastInsertId) 36 | } 37 | -------------------------------------------------------------------------------- /driverManager.go: -------------------------------------------------------------------------------- 1 | package godbal 2 | 3 | import "github.com/xujiajun/godbal/driver/mysql" 4 | 5 | // DriveManager records drivers 6 | type DriveManager struct { 7 | drivers map[string]string 8 | } 9 | 10 | // NewDriveManager returns a newly initialized NewDriveManager that implements DriveManager 11 | func NewDriveManager() *DriveManager { 12 | return &DriveManager{ 13 | drivers: map[string]string{"mysql": "mysql"}, 14 | } 15 | } 16 | 17 | // GetAvailableDrivers returns available drivers 18 | func (driverManager *DriveManager) GetAvailableDrivers() map[string]string { 19 | return driverManager.drivers 20 | } 21 | 22 | // GetMysqlDB returns mysql Database 23 | func (driverManager *DriveManager) GetMysqlDB(dataSourceName string) *mysql.Database { 24 | return NewMysql(dataSourceName) 25 | } 26 | 27 | // NewMysql is short for GetMysqlDB 28 | func NewMysql(dataSourceName string) *mysql.Database { 29 | return mysql.New(dataSourceName) 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 徐佳军 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/transaction/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | _ "github.com/go-sql-driver/mysql" 8 | "github.com/xujiajun/godbal" 9 | "github.com/xujiajun/godbal/driver/mysql" 10 | ) 11 | 12 | func foo() { 13 | panic("foo func error") 14 | } 15 | 16 | func main() { 17 | database, err := godbal.NewMysql("root:123@tcp(127.0.0.1:3306)/test?charset=utf8").Open() 18 | 19 | if err != nil { 20 | panic(err) 21 | } 22 | 23 | err = database.Ping() 24 | if err != nil { 25 | panic(err) 26 | } 27 | 28 | defer database.Close() 29 | 30 | tx, err := database.Begin() 31 | 32 | if err != nil { 33 | log.Fatalln(err) 34 | } 35 | 36 | defer tx.Rollback() 37 | 38 | queryBuilder := mysql.NewQueryBuilder(database) 39 | 40 | queryBuilder.Update("userinfo", "u").Set("u.username", "joe").Set("u.departname", "tecxx").Where("u.uid=?"). 41 | SetParam(4) 42 | 43 | res, err := tx.PrepareAndExecute(queryBuilder) 44 | 45 | if err != nil { 46 | log.Fatalln(err) 47 | } 48 | 49 | fmt.Print(res.RowsAffected()) 50 | 51 | foo() 52 | 53 | if err := tx.Commit(); err != nil { 54 | log.Fatalln(err) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /examples/join/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | 7 | _ "github.com/go-sql-driver/mysql" 8 | "github.com/xujiajun/godbal" 9 | "github.com/xujiajun/godbal/driver/mysql" 10 | ) 11 | 12 | func main() { 13 | database, err := godbal.NewMysql("root:123@tcp(127.0.0.1:3306)/test?charset=utf8").Open() 14 | 15 | if err != nil { 16 | panic(err) 17 | } 18 | 19 | err = database.Ping() 20 | if err != nil { 21 | panic(err) 22 | } 23 | 24 | //queryBuilder := mysql.NewQueryBuilder(database) 25 | //rows, _ := queryBuilder.Select("u.uid,u.username,p.address").From("userinfo", "u").SetFirstResult(0). 26 | // SetMaxResults(3).InnerJoin("profile", "p", "u.uid = p.uid").Query() 27 | // 28 | //fmt.Print(ToJson(rows)) 29 | 30 | queryBuilder2 := mysql.NewQueryBuilder(database) 31 | queryBuilder2 = queryBuilder2.Select("u.uid,u.username,p.address").From("userinfo", "u").SetFirstResult(0). 32 | SetMaxResults(3).RightJoin("profile", "p", "u.uid = p.uid") 33 | 34 | fmt.Println(queryBuilder2.GetSQL()) 35 | 36 | rows, _ := queryBuilder2.QueryAndGetMap() 37 | 38 | jsonString, _ := json.Marshal(&rows) 39 | 40 | fmt.Println(string(jsonString)) 41 | } 42 | -------------------------------------------------------------------------------- /driverManager_test.go: -------------------------------------------------------------------------------- 1 | package godbal_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/xujiajun/godbal" 7 | "gopkg.in/DATA-DOG/go-sqlmock.v1" 8 | ) 9 | 10 | const ( 11 | defaultDriver = "mysql" 12 | ) 13 | 14 | func TestNewMysql(t *testing.T) { 15 | database := godbal.NewMysql("") 16 | db, _, err := sqlmock.New() 17 | 18 | if err != nil { 19 | t.Fatalf("an error '%s' was not expected when opening a mysql database connection", err) 20 | } 21 | 22 | defer db.Close() 23 | 24 | database.SetDB(db) 25 | } 26 | 27 | func TestNewDriveManager(t *testing.T) { 28 | newDriveManager := godbal.NewDriveManager() 29 | drivers := newDriveManager.GetAvailableDrivers() 30 | 31 | if driver, ok := drivers[defaultDriver]; ok { 32 | if driver != defaultDriver { 33 | t.Errorf("returned unexpected driver: got %v want %v", driver, defaultDriver) 34 | } 35 | } else { 36 | t.Errorf("an error when TestNewDriveManager driver %s not found ", defaultDriver) 37 | } 38 | } 39 | 40 | func TestDriveManager_GetMysqlConnection(t *testing.T) { 41 | newDriveManager := godbal.NewDriveManager() 42 | mysqlDB := newDriveManager.GetMysqlDB("") 43 | 44 | db, _, err := sqlmock.New() 45 | 46 | if err != nil { 47 | t.Fatalf("an error '%s' was not expected when opening a mysql database connection", err) 48 | } 49 | 50 | defer db.Close() 51 | 52 | mysqlDB.SetDB(db) 53 | } 54 | -------------------------------------------------------------------------------- /examples/select/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | 7 | _ "github.com/go-sql-driver/mysql" 8 | "github.com/xujiajun/godbal" 9 | "github.com/xujiajun/godbal/driver/mysql" 10 | ) 11 | 12 | func main() { 13 | database, err := godbal.NewMysql("root:123@tcp(127.0.0.1:3306)/test?charset=utf8").Open() 14 | if err != nil { 15 | panic(err) 16 | } 17 | 18 | err = database.Ping() 19 | if err != nil { 20 | panic(err) 21 | } 22 | 23 | queryBuilder := mysql.NewQueryBuilder(database) 24 | sql := queryBuilder.Select("uid,username,price,flag").From("userinfo", "").SetFirstResult(0). 25 | SetMaxResults(5).OrderBy("uid", "DESC").OrderBy("username", "DESC").GetSQL() 26 | 27 | fmt.Println(sql) 28 | 29 | rows, _ := queryBuilder.QueryAndGetMap() 30 | 31 | jsonString, _ := json.Marshal(&rows) 32 | fmt.Println(string(jsonString)) 33 | queryBuilder2 := mysql.NewQueryBuilder(database) 34 | sql = queryBuilder2.Select("uid,username,created,textVal,price,name").From("userinfo", "").Where("username = ? AND departname = ?"). 35 | SetParam("johnny2").SetParam("tec").SetFirstResult(0). 36 | SetMaxResults(3).OrderBy("uid", "DESC").GetSQL() 37 | 38 | fmt.Println(sql) 39 | rows2, _ := queryBuilder2.QueryAndGetMap() 40 | jsonString2, _ := json.Marshal(&rows2) 41 | fmt.Println(string(jsonString2)) 42 | queryBuilder3 := mysql.NewQueryBuilder(database) 43 | rows3, _ := queryBuilder3.Select("uid,username,created,textVal,price,name").From("userinfo", "").Where("username = ?"). 44 | SetParam("johnny2").SetFirstResult(0). 45 | SetMaxResults(1).OrderBy("uid", "DESC").QueryAndGetMap() 46 | 47 | fmt.Println() 48 | 49 | jsonString3, _ := json.Marshal(&rows3) 50 | fmt.Println(string(jsonString3)) 51 | 52 | } 53 | -------------------------------------------------------------------------------- /driver/mysql/database.go: -------------------------------------------------------------------------------- 1 | package mysql 2 | 3 | import ( 4 | "database/sql" 5 | ) 6 | 7 | const ( 8 | driver = "mysql" 9 | ) 10 | 11 | // Database records dataSourceName 、db 12 | type Database struct { 13 | dataSourceName string 14 | db *sql.DB 15 | } 16 | 17 | // New returns a newly initialized Database that implements Database 18 | // dataSourceName ("user:password@tcp(ip:port)/database") 19 | func New(dataSourceName string) *Database { 20 | database := &Database{} 21 | 22 | if dataSourceName != "" { 23 | database.dataSourceName = dataSourceName 24 | } 25 | 26 | return database 27 | } 28 | 29 | // SetDB sets db 30 | func (database *Database) SetDB(db *sql.DB) { 31 | database.db = db 32 | } 33 | 34 | // GetDB returns db 35 | func (database *Database) GetDB() *sql.DB { 36 | return database.db 37 | } 38 | 39 | // Open returns mysql driver database 40 | func (database *Database) Open() (*Database, error) { 41 | db, err := sql.Open(driver, database.dataSourceName) 42 | database.db = db 43 | 44 | return database, err 45 | } 46 | 47 | // Ping verifies a connection to the database is still alive, 48 | // establishing a connection if necessary. 49 | func (database *Database) Ping() error { 50 | return database.db.Ping() 51 | } 52 | 53 | // Close closes the database, releasing any open resources. 54 | func (database *Database) Close() error { 55 | return database.db.Close() 56 | } 57 | 58 | // Prepare creates a prepared statement for later queries or executions. 59 | func (database *Database) Prepare(sql string) (*sql.Stmt, error) { 60 | stmt, err := database.db.Prepare(sql) 61 | 62 | return stmt, err 63 | } 64 | 65 | // Query executes a query that returns rows, typically a SELECT. 66 | // The args are for any placeholder parameters in the query. 67 | func (database *Database) Query(sql string, args ...interface{}) (*sql.Rows, error) { 68 | if args != nil { 69 | return database.db.Query(sql, args...) 70 | } 71 | return database.db.Query(sql) 72 | } 73 | 74 | // Fetch executes a query that is expected to return at most one row. 75 | // Fetch is wrapped for db.QueryRow 76 | func (database *Database) Fetch(sql string, args ...interface{}) *sql.Row { 77 | return database.db.QueryRow(sql, args) 78 | } 79 | 80 | // Transaction records Tx 81 | type Transaction struct { 82 | Tx *sql.Tx 83 | } 84 | 85 | // NewTx returns a newly initialized Transaction that implements Transaction 86 | func NewTx() *Transaction { 87 | return &Transaction{} 88 | } 89 | 90 | // Begin starts a transaction. The default isolation level is dependent on 91 | // the driver. 92 | func (database *Database) Begin() (*Transaction, error) { 93 | transaction, err := database.db.Begin() 94 | tx := NewTx() 95 | tx.Tx = transaction 96 | return tx, err 97 | } 98 | 99 | // Rollback aborts the transaction. 100 | func (transaction *Transaction) Rollback() error { 101 | return transaction.Tx.Rollback() 102 | } 103 | 104 | // Commit commits the transaction. 105 | func (transaction *Transaction) Commit() error { 106 | return transaction.Tx.Commit() 107 | } 108 | 109 | // PrepareAndExecute creates a prepared statement for later queries or executions. 110 | func (transaction *Transaction) PrepareAndExecute(queryBuilder *QueryBuilder) (sql.Result, error) { 111 | stmt, err := transaction.Tx.Prepare(queryBuilder.GetSQL()) 112 | 113 | if err != nil { 114 | panic(err) 115 | } 116 | return stmt.Exec(queryBuilder.GetParams()...) 117 | } 118 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # godbal [![GoDoc](https://godoc.org/github.com/xujiajun/godbal/driver/mysql?status.svg)](https://godoc.org/github.com/xujiajun/godbal/driver/mysql) [![GoDoc](https://godoc.org/github.com/xujiajun/godbal?status.svg)](https://godoc.org/github.com/xujiajun/godbal) [![Go Report Card](https://goreportcard.com/badge/github.com/xujiajun/godbal)](https://goreportcard.com/report/github.com/xujiajun/godbal) Build Status [![Coverage Status](https://coveralls.io/repos/github/xujiajun/godbal/badge.svg?branch=master)](https://coveralls.io/github/xujiajun/godbal?branch=master) [![License](http://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](https://raw.githubusercontent.com/xujiajun/godbal/master/LICENSE) [![Awesome](https://awesome.re/mentioned-badge.svg)](https://github.com/avelino/awesome-go#database) 2 | Database Abstraction Layer (dbal) for go (now only support mysql) 3 | 4 | ## Motivation 5 | 6 | I wanted a DBAL that ***No ORM***、***No Reflect***、***Concurrency Save***, support ***SQL builder*** following good practices and well tested code. 7 | 8 | ## Requirements 9 | 10 | Go 1.7 or above. 11 | 12 | ## Installation 13 | 14 | ``` 15 | go get github.com/xujiajun/godbal 16 | ``` 17 | 18 | ## Supported Databases 19 | 20 | * mysql 21 | 22 | 23 | ## Getting Started 24 | 25 | Godbal helps you build SQL queries from composable parts easily: 26 | 27 | ``` 28 | database, _ := godbal.NewMysql("root:123@tcp(127.0.0.1:3306)/test?charset=utf8").Open() 29 | queryBuilder := mysql.NewQueryBuilder(database) 30 | sql := queryBuilder.Select("uid,username,price,flag").From("userinfo", "").SetFirstResult(0). 31 | SetMaxResults(3).OrderBy("uid", "DESC").GetSQL() 32 | 33 | fmt.Println(sql) 34 | ``` 35 | 36 | Output: 37 | 38 | ``` 39 | SELECT uid,username,price,flag FROM userinfo ORDER BY uid DESC LIMIT 0,3 40 | ``` 41 | 42 | Godbal helps you get result easily: 43 | 44 | ``` 45 | rows, _ := queryBuilder.QueryAndGetMap() 46 | jsonString, _ := json.Marshal(&rows) 47 | fmt.Print(string(jsonString)) 48 | 49 | ``` 50 | 51 | Output like: 52 | 53 | ``` 54 | {"0":{"flag":"1","price":"111.00","uid":"6","username":"johnny2"},"1":{"flag":"1","price":"111.00","uid":"5","username":"johnny2"},"2":{"flag":"0","price":"123.99","uid":"4","username":"joe"}} 55 | ``` 56 | 57 | ### Full example: 58 | 59 | ``` 60 | package main 61 | 62 | import ( 63 | "encoding/json" 64 | "fmt" 65 | 66 | _ "github.com/go-sql-driver/mysql" 67 | "github.com/xujiajun/godbal" 68 | "github.com/xujiajun/godbal/driver/mysql" 69 | ) 70 | 71 | func main() { 72 | database, err := godbal.NewMysql("root:123@tcp(127.0.0.1:3306)/test?charset=utf8").Open() 73 | if err != nil { 74 | panic(err) 75 | } 76 | 77 | err = database.Ping() 78 | if err != nil { 79 | panic(err) 80 | } 81 | 82 | queryBuilder := mysql.NewQueryBuilder(database) 83 | sql := queryBuilder.Select("uid,username,price,flag").From("userinfo", "").SetFirstResult(0). 84 | SetMaxResults(3).OrderBy("uid", "DESC").GetSQL() 85 | 86 | fmt.Println(sql) // SELECT uid,username,price,flag FROM userinfo ORDER BY uid DESC LIMIT 0,3 87 | 88 | rows, _ := queryBuilder.QueryAndGetMap() 89 | 90 | jsonString, _ := json.Marshal(&rows) 91 | fmt.Print(string(jsonString)) 92 | // result like: {"0":{"flag":"1","price":"111.00","uid":"6","username":"johnny2"},"1":{"flag":"1","price":"111.00","uid":"5","username":"johnny2"},"2":{"flag":"0","price":"123.99","uid":"4","username":"joe"}} 93 | } 94 | 95 | ``` 96 | 97 | ## More examples 98 | 99 | * [select](https://github.com/xujiajun/godbal/blob/master/examples/select/main.go) 100 | 101 | * [insert](https://github.com/xujiajun/godbal/blob/master/examples/insert/main.go) 102 | 103 | * [delete](https://github.com/xujiajun/godbal/blob/master/examples/delete/main.go) 104 | 105 | * [update](https://github.com/xujiajun/godbal/blob/master/examples/update/main.go) 106 | 107 | * [join](https://github.com/xujiajun/godbal/blob/master/examples/join/main.go) 108 | 109 | * [transaction](https://github.com/xujiajun/godbal/blob/master/examples/transaction/main.go) 110 | 111 | ## Contributing 112 | 113 | If you'd like to help out with the project. You can put up a Pull Request. 114 | 115 | ## Author 116 | 117 | * [xujiajun](https://github.com/xujiajun) 118 | 119 | ## License 120 | 121 | The godbal is open-sourced software licensed under the [MIT Licensed](http://www.opensource.org/licenses/MIT) 122 | -------------------------------------------------------------------------------- /driver/mysql/queryBuilder_test.go: -------------------------------------------------------------------------------- 1 | package mysql_test 2 | 3 | import ( 4 | "testing" 5 | //"fmt" 6 | //"encoding/json" 7 | 8 | "github.com/xujiajun/godbal" 9 | "github.com/xujiajun/godbal/driver/mysql" 10 | "gopkg.in/DATA-DOG/go-sqlmock.v1" 11 | ) 12 | 13 | const ( 14 | INNER = "INNER" 15 | LEFT = "LEFT" 16 | RIGHT = "RIGHT" 17 | ) 18 | 19 | func TestQueryBuilder_Select(t *testing.T) { 20 | db, mock, err := sqlmock.New() 21 | 22 | if err != nil { 23 | t.Fatalf("an error '%s' was not expected when opening a mysql database connection", err) 24 | } 25 | 26 | defer db.Close() 27 | database := godbal.NewMysql("") 28 | database.SetDB(db) 29 | 30 | if err != nil { 31 | panic(err) 32 | } 33 | 34 | queryBuilder := mysql.NewQueryBuilder(database) 35 | sql := queryBuilder.Select("id, title").From("posts", "").GetSQL() 36 | 37 | expectedSql := "SELECT id, title FROM posts " 38 | 39 | if sql != expectedSql { 40 | t.Errorf("returned unexpected sql: got %v want %v", 41 | sql, expectedSql) 42 | } 43 | 44 | rows := sqlmock.NewRows([]string{"id", "title", "body"}). 45 | AddRow(1, "post 1", "hello"). 46 | AddRow(2, "post 2", "world") 47 | 48 | mock.ExpectQuery(sql).WillReturnRows(rows) 49 | 50 | _, err = queryBuilder.Query() 51 | 52 | if err != nil { 53 | t.Errorf("error '%s' was not expected, while SELECT a row", err) 54 | } 55 | 56 | // we make sure that all expectations were met 57 | if err := mock.ExpectationsWereMet(); err != nil { 58 | t.Errorf("there were unfulfilled expectations: %s", err) 59 | 60 | } 61 | } 62 | 63 | func testJoinCommon(t *testing.T, joinFlag string) { 64 | db, mock, err := sqlmock.New() 65 | 66 | if err != nil { 67 | t.Fatalf("an error '%s' was not expected when opening a mysql database connection", err) 68 | } 69 | 70 | defer db.Close() 71 | database := godbal.NewMysql("") 72 | database.SetDB(db) 73 | 74 | if err != nil { 75 | panic(err) 76 | } 77 | 78 | queryBuilder := mysql.NewQueryBuilder(database) 79 | 80 | sql, expectedSql := "", "" 81 | switch joinFlag { 82 | case INNER: 83 | sql = queryBuilder.Select("p.id, p.title").From("posts", "p").SetFirstResult(0). 84 | SetMaxResults(3).InnerJoin("user", "u", "u.uid = p.uid").GetSQL() 85 | expectedSql = "SELECT p.id, p.title FROM posts p INNER JOIN user u ON u.uid = p.uid LIMIT 0,3" 86 | case LEFT: 87 | sql = queryBuilder.Select("p.id, p.title").From("posts", "p").SetFirstResult(0). 88 | SetMaxResults(3).LeftJoin("user", "u", "u.uid = p.uid").GetSQL() 89 | expectedSql = "SELECT p.id, p.title FROM posts p LEFT JOIN user u ON u.uid = p.uid LIMIT 0,3" 90 | case RIGHT: 91 | sql = queryBuilder.Select("p.id, p.title").From("posts", "p").SetFirstResult(0). 92 | SetMaxResults(3).RightJoin("user", "u", "u.uid = p.uid").GetSQL() 93 | expectedSql = "SELECT p.id, p.title FROM posts p RIGHT JOIN user u ON u.uid = p.uid LIMIT 0,3" 94 | } 95 | 96 | if sql != expectedSql { 97 | t.Errorf("returned unexpected sql: got %v want %v", 98 | sql, expectedSql) 99 | } 100 | 101 | rows := sqlmock.NewRows([]string{"id", "title", "body"}). 102 | AddRow(1, "post 1", "hello"). 103 | AddRow(2, "post 2", "world") 104 | 105 | mock.ExpectQuery(sql).WillReturnRows(rows) 106 | 107 | //rows2, err := queryBuilder.Query() 108 | _, err = queryBuilder.Query() 109 | 110 | //jsonString, _ := json.Marshal(&rows2) 111 | //fmt.Print(string(jsonString)) 112 | 113 | if err != nil { 114 | t.Errorf("error '%s' was not expected, while SELECT a row", err) 115 | } 116 | 117 | // we make sure that all expectations were met 118 | if err := mock.ExpectationsWereMet(); err != nil { 119 | t.Errorf("there were unfulfilled expectations: %s", err) 120 | } 121 | } 122 | 123 | func TestQueryBuilder_InnerJoin(t *testing.T) { 124 | testJoinCommon(t, INNER) 125 | } 126 | 127 | func TestQueryBuilder_LeftJoin(t *testing.T) { 128 | testJoinCommon(t, LEFT) 129 | } 130 | 131 | func TestQueryBuilder_RightJoin(t *testing.T) { 132 | testJoinCommon(t, RIGHT) 133 | } 134 | 135 | func TestQueryBuilder_GetSQL(t *testing.T) { 136 | db, _, err := sqlmock.New() 137 | 138 | if err != nil { 139 | t.Fatalf("an error '%s' was not expected when opening a mysql database connection", err) 140 | } 141 | 142 | defer db.Close() 143 | database := godbal.NewMysql("") 144 | database.SetDB(db) 145 | 146 | if err != nil { 147 | panic(err) 148 | } 149 | 150 | queryBuilder := mysql.NewQueryBuilder(database) 151 | 152 | sql := queryBuilder.Select("uid,username,created,textVal,price,name").From("userinfo", "").Where("username = ? AND departname = ?"). 153 | SetParam("johnny2").SetParam("tec").SetFirstResult(0). 154 | SetMaxResults(3).OrderBy("uid", "DESC").GetSQL() 155 | 156 | expectedSql := "SELECT uid,username,created,textVal,price,name FROM userinfo WHERE username = ? AND departname = ? ORDER BY uid DESC LIMIT 0,3" 157 | 158 | if sql != expectedSql { 159 | t.Errorf("returned unexpected sql: got %v want %v", 160 | sql, expectedSql) 161 | } 162 | 163 | //GET 164 | queryBuilder.GetParameter() 165 | queryBuilder.GetMaxResults() 166 | queryBuilder.GetFirstResult() 167 | 168 | queryBuilder2 := mysql.NewQueryBuilder(database) 169 | sql = queryBuilder2.Select("u.uid,u.username,p.address,count(*) as num").From("userinfo", "u").SetFirstResult(0). 170 | SetMaxResults(3).RightJoin("profile", "p", "u.uid = p.uid").Having("num > 1").GetSQL() 171 | 172 | expectedSql2 := "SELECT u.uid,u.username,p.address,count(*) as num FROM userinfo u RIGHT JOIN profile p ON u.uid = p.uid HAVING num > 1 LIMIT 0,3" 173 | if sql != expectedSql2 { 174 | t.Errorf("returned unexpected sql: got %v want %v", 175 | sql, expectedSql2) 176 | } 177 | } 178 | 179 | func TestQueryBuilder_Transaction(t *testing.T) { 180 | // open database stub 181 | db, mock, err := sqlmock.New() 182 | if err != nil { 183 | t.Errorf("An error '%s' was not expected when opening a stub database connection", err) 184 | } 185 | defer db.Close() 186 | database := godbal.NewMysql("") 187 | database.SetDB(db) 188 | 189 | if err != nil { 190 | panic(err) 191 | } 192 | 193 | queryBuilder := mysql.NewQueryBuilder(database) 194 | 195 | sql := queryBuilder.Update("userinfo", "u").Set("u.username", "johnny").Where("u.uid=?"). 196 | SetParam(1).GetSQL() 197 | 198 | expectedSql := "UPDATE userinfo u SET u.username = ? WHERE u.uid=?" 199 | 200 | if sql != expectedSql { 201 | t.Errorf("returned unexpected sql: got %v want %v", 202 | sql, expectedSql) 203 | } 204 | 205 | queryBuilder2 := mysql.NewQueryBuilder(database) 206 | 207 | sql2 := queryBuilder2.Insert("userinfo").Value("username", "johnny3").Value("departname", "tec5").GetSQL() 208 | 209 | expectedSql2 := "INSERT INTO userinfo (username,departname) VALUES(?,?)" 210 | 211 | if sql2 != expectedSql2 { 212 | t.Errorf("returned unexpected sql: got %v want %v", 213 | sql2, expectedSql2) 214 | } 215 | 216 | // expect transaction begin 217 | mock.ExpectBegin() 218 | // expect user balance update 219 | mock.ExpectPrepare(sql).ExpectExec(). 220 | WithArgs("xujiajun", 1). 221 | WillReturnResult(sqlmock.NewResult(0, 1)) // no insert id, 1 affected row 222 | 223 | mock.ExpectPrepare("INSERT INTO userinfo ").ExpectExec(). 224 | WithArgs("tec", "xxx"). 225 | WillReturnResult(sqlmock.NewResult(1, 1)) // no insert id, 1 affected row 226 | 227 | // expect a transaction commit 228 | mock.ExpectCommit() 229 | 230 | if err != nil { 231 | t.Errorf("An error '%s' was not expected while queryBuilder PrepareAndExecute", err) 232 | } 233 | 234 | defer database.Close() 235 | 236 | transaction, err := database.Begin() 237 | 238 | if err != nil { 239 | t.Errorf("An error '%s' was not expected while database begin", err) 240 | } 241 | 242 | _, err = transaction.Tx.Exec(sql, "xujiajun", 1) 243 | _, err = transaction.Tx.Exec(sql2, "tec", "xxx") 244 | 245 | if err != nil { 246 | return 247 | } 248 | 249 | if err := transaction.Commit(); err != nil { 250 | return 251 | } 252 | } 253 | 254 | func TestQueryBuilder_Delete(t *testing.T) { 255 | db, _, err := sqlmock.New() 256 | 257 | if err != nil { 258 | t.Fatalf("an error '%s' was not expected when opening a mysql database connection", err) 259 | } 260 | 261 | defer db.Close() 262 | database := godbal.NewMysql("") 263 | database.SetDB(db) 264 | 265 | if err != nil { 266 | panic(err) 267 | } 268 | 269 | queryBuilder := mysql.NewQueryBuilder(database) 270 | 271 | sql := queryBuilder.Delete("userinfo").Where("uid=?").SetParam(7).GetSQL() 272 | 273 | expectedSql := "DELETE FROM userinfo WHERE uid=?" 274 | 275 | if sql != expectedSql { 276 | t.Errorf("returned unexpected sql: got %v want %v", 277 | sql, expectedSql) 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /driver/mysql/queryBuilder.go: -------------------------------------------------------------------------------- 1 | package mysql 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | // The query types. 11 | const ( 12 | SELECT = iota 13 | DELETE 14 | UPDATE 15 | INSERT 16 | ) 17 | 18 | // The query flags. 19 | const ( 20 | ISSORT = "isSort" 21 | ISJOIN = "isJoin" 22 | ISDEFAULT = "isDefault" 23 | INNER = "INNER" 24 | LEFT = "LEFT" 25 | RIGHT = "RIGHT" 26 | ) 27 | 28 | type ( 29 | // FromSqlParts records table and alias 30 | FromSqlParts struct { 31 | table, alias string 32 | } 33 | 34 | // OrderBySqlParts records sort and order 35 | OrderBySqlParts struct { 36 | sort, order string 37 | } 38 | 39 | // JoinSqlParts records joinType, joinTable, joinAlias, joinCondition 40 | JoinSqlParts struct { 41 | joinType, joinTable, joinAlias, joinCondition string 42 | } 43 | 44 | // ValuesSqlParts records key, val 45 | ValuesSqlParts struct { 46 | key string 47 | val interface{} 48 | } 49 | 50 | // SetSqlParts records key, val 51 | SetSqlParts struct { 52 | key string 53 | val interface{} 54 | } 55 | 56 | //QueryBuilder defined a SQL query builder. 57 | QueryBuilder struct { 58 | firstResult, maxResults, queryType int 59 | flag, sql, sqlPartsSelect, sqlPartsWhere, sqlPartsGroupBy, sqlPartsHaving string 60 | database *Database 61 | State *sql.Stmt 62 | params []interface{} 63 | sqlPartsFrom []FromSqlParts 64 | sqlPartsOrderBy []OrderBySqlParts 65 | sqlPartsValues []ValuesSqlParts 66 | sqlPartsSet []SetSqlParts 67 | sqlPartsJoin []JoinSqlParts 68 | } 69 | ) 70 | 71 | // NewQueryBuilder returns a newly initialized QueryBuilder that implements QueryBuilder 72 | func NewQueryBuilder(database *Database) *QueryBuilder { 73 | return &QueryBuilder{ 74 | firstResult: 0, 75 | maxResults: -1, 76 | queryType: SELECT, 77 | database: database, 78 | params: []interface{}{}, 79 | flag: ISDEFAULT, 80 | sql: "", 81 | sqlPartsSet: make([]SetSqlParts, 0), 82 | sqlPartsValues: make([]ValuesSqlParts, 0), 83 | sqlPartsFrom: make([]FromSqlParts, 0), 84 | sqlPartsOrderBy: make([]OrderBySqlParts, 0), 85 | sqlPartsJoin: make([]JoinSqlParts, 0), 86 | } 87 | } 88 | 89 | // GetParams returns queryBuilder params 90 | func (queryBuilder *QueryBuilder) GetParams() []interface{} { 91 | return queryBuilder.params 92 | } 93 | 94 | // Select returns QueryBuilder that Specifies an item that is to be returned in the query result. 95 | func (queryBuilder *QueryBuilder) Select(value string) *QueryBuilder { 96 | queryBuilder.queryType = SELECT 97 | queryBuilder.sqlPartsSelect = value 98 | 99 | return queryBuilder 100 | } 101 | 102 | // From returns QueryBuilder that creates and adds a query root corresponding to the table identified by the 103 | // given alias, forming a cartesian product with any existing query roots. 104 | func (queryBuilder *QueryBuilder) From(table string, alias string) *QueryBuilder { 105 | queryBuilder.setFromWrap(table, alias) 106 | 107 | return queryBuilder 108 | } 109 | 110 | // Update returns QueryBuilder that turns the query being built into a bulk update query that ranges over 111 | //a certain table 112 | func (queryBuilder *QueryBuilder) Update(table string, alias string) *QueryBuilder { 113 | queryBuilder.queryType = UPDATE 114 | queryBuilder.setFromWrap(table, alias) 115 | 116 | return queryBuilder 117 | } 118 | 119 | // Set returns QueryBuilder that sets a new value for a column in a bulk update query. 120 | func (queryBuilder *QueryBuilder) Set(key string, val interface{}) *QueryBuilder { 121 | queryBuilder.sqlPartsSet = append(queryBuilder.sqlPartsSet, SetSqlParts{key: key, val: val}) 122 | 123 | return queryBuilder 124 | } 125 | 126 | // Value returns QueryBuilder that sets a new value for a column in a bulk insert query. 127 | func (queryBuilder *QueryBuilder) Value(key string, val interface{}) *QueryBuilder { 128 | queryBuilder.sqlPartsValues = append(queryBuilder.sqlPartsValues, ValuesSqlParts{key: key, val: val}) 129 | 130 | return queryBuilder 131 | } 132 | 133 | // OrderBy returns QueryBuilder that specifies an ordering for the query results. 134 | func (queryBuilder *QueryBuilder) OrderBy(sort string, order string) *QueryBuilder { 135 | 136 | queryBuilder.flag = ISSORT 137 | if order == "" { 138 | order = "ASC" 139 | } 140 | 141 | queryBuilder.sqlPartsOrderBy = append(queryBuilder.sqlPartsOrderBy, OrderBySqlParts{sort, order}) 142 | 143 | return queryBuilder 144 | } 145 | 146 | // GroupBy returns QueryBuilder that specifies a grouping over the results of the query. 147 | func (queryBuilder *QueryBuilder) GroupBy(groupBy string) *QueryBuilder { 148 | if groupBy == "" { 149 | return queryBuilder 150 | } 151 | 152 | queryBuilder.sqlPartsGroupBy = groupBy 153 | 154 | return queryBuilder 155 | } 156 | 157 | // Having returns QueryBuilder that specifies a restriction over the groups of the query. 158 | func (queryBuilder *QueryBuilder) Having(having string) *QueryBuilder { 159 | queryBuilder.sqlPartsHaving = having 160 | 161 | return queryBuilder 162 | } 163 | 164 | // SetFirstResult returns QueryBuilder that sets the position of the first result to retrieve. 165 | func (queryBuilder *QueryBuilder) SetFirstResult(firstResult int) *QueryBuilder { 166 | queryBuilder.firstResult = firstResult 167 | 168 | return queryBuilder 169 | } 170 | 171 | // Where returns QueryBuilder that specifies one or more restrictions to the query result. 172 | func (queryBuilder *QueryBuilder) Where(condition string) *QueryBuilder { 173 | queryBuilder.sqlPartsWhere = condition 174 | 175 | return queryBuilder 176 | } 177 | 178 | // Join returns QueryBuilder that creates and adds a join to the query. 179 | func (queryBuilder *QueryBuilder) Join(join string, alias string, condition string) *QueryBuilder { 180 | return queryBuilder.InnerJoin(join, alias, condition) 181 | } 182 | 183 | // InnerJoin returns QueryBuilder that creates and adds a join to the query. 184 | func (queryBuilder *QueryBuilder) InnerJoin(join string, alias string, condition string) *QueryBuilder { 185 | queryBuilder.flag = ISJOIN 186 | queryBuilder.sqlPartsJoin = append(queryBuilder.sqlPartsJoin, JoinSqlParts{joinType: INNER, joinTable: join, joinAlias: alias, joinCondition: condition}) 187 | 188 | return queryBuilder 189 | } 190 | 191 | // LeftJoin returns QueryBuilder that creates and adds a left join to the query. 192 | func (queryBuilder *QueryBuilder) LeftJoin(join string, alias string, condition string) *QueryBuilder { 193 | queryBuilder.flag = ISJOIN 194 | queryBuilder.sqlPartsJoin = append(queryBuilder.sqlPartsJoin, JoinSqlParts{joinType: LEFT, joinTable: join, joinAlias: alias, joinCondition: condition}) 195 | 196 | return queryBuilder 197 | } 198 | 199 | // RightJoin returns QueryBuilder that creates and adds a right join to the query. 200 | func (queryBuilder *QueryBuilder) RightJoin(join string, alias string, condition string) *QueryBuilder { 201 | queryBuilder.flag = ISJOIN 202 | queryBuilder.sqlPartsJoin = append(queryBuilder.sqlPartsJoin, JoinSqlParts{joinType: RIGHT, joinTable: join, joinAlias: alias, joinCondition: condition}) 203 | 204 | return queryBuilder 205 | } 206 | 207 | // GetFirstResult gets the position of the first result the query object was set to retrieve. 208 | func (queryBuilder *QueryBuilder) GetFirstResult() int { 209 | return queryBuilder.firstResult 210 | } 211 | 212 | // SetMaxResults sets the maximum number of results to retrieve. 213 | func (queryBuilder *QueryBuilder) SetMaxResults(maxResults int) *QueryBuilder { 214 | queryBuilder.maxResults = maxResults 215 | return queryBuilder 216 | } 217 | 218 | // GetMaxResults gets the maximum number of results the query object was set to retrieve 219 | func (queryBuilder *QueryBuilder) GetMaxResults() int { 220 | return queryBuilder.maxResults 221 | } 222 | 223 | // SetParam sets a query parameter for the query being constructed. 224 | func (queryBuilder *QueryBuilder) SetParam(param interface{}) *QueryBuilder { 225 | queryBuilder.params = append(queryBuilder.params, param) 226 | return queryBuilder 227 | } 228 | 229 | // GetParameter gets all defined query parameters for the query being constructed indexed by parameter index or name. 230 | func (queryBuilder *QueryBuilder) GetParameter() []interface{} { 231 | return queryBuilder.params 232 | } 233 | 234 | // GetSQL gets the complete SQL string formed by the current specifications of this QueryBuilder. 235 | func (queryBuilder *QueryBuilder) GetSQL() string { 236 | sql := queryBuilder.sql 237 | 238 | if sql != "" { 239 | return sql 240 | } 241 | 242 | queryType := queryBuilder.queryType 243 | 244 | switch queryType { 245 | case INSERT: 246 | sql = queryBuilder.getSQLForInsert() 247 | case DELETE: 248 | sql = queryBuilder.getSQLForDelete() 249 | case UPDATE: 250 | sql = queryBuilder.getSQLForUpdate() 251 | case SELECT: 252 | sql = queryBuilder.getSQLForSelect() 253 | default: 254 | sql = queryBuilder.getSQLForSelect() 255 | } 256 | queryBuilder.sql = sql 257 | return sql 258 | } 259 | 260 | // getSQLForUpdate returns an update string in SQL. 261 | func (queryBuilder *QueryBuilder) getSQLForUpdate() string { 262 | sql := "UPDATE " 263 | 264 | table := "" 265 | for _, v := range queryBuilder.sqlPartsFrom { 266 | table = v.table + " " + v.alias 267 | } 268 | 269 | sortedKeys := make([]string, 0) 270 | 271 | paramsTemp := make([]interface{}, 0) 272 | 273 | sql += table + " SET " 274 | 275 | for _, v := range queryBuilder.sqlPartsSet { 276 | sortedKeys = append(sortedKeys, v.key) 277 | 278 | sql += v.key + " = ? ," 279 | 280 | paramsTemp = append(paramsTemp, v.val) 281 | } 282 | 283 | for _, v := range queryBuilder.params { 284 | paramsTemp = append(paramsTemp, v) 285 | } 286 | 287 | queryBuilder.params = paramsTemp 288 | 289 | sql = sql[:len(sql)-1] 290 | 291 | if whereStr := queryBuilder.sqlPartsWhere; whereStr != "" { 292 | sql += " WHERE " + whereStr 293 | } 294 | 295 | return sql 296 | 297 | } 298 | 299 | // getSQLForJoins returns an join string in SQL. 300 | func (queryBuilder *QueryBuilder) getSQLForJoins() string { 301 | sql := "" 302 | 303 | if queryBuilder.flag != ISJOIN { 304 | return "" 305 | } 306 | 307 | for _, v := range queryBuilder.sqlPartsJoin { 308 | joinType := v.joinType 309 | joinTable := v.joinTable 310 | joinAlias := v.joinAlias 311 | joinCondition := v.joinCondition 312 | 313 | sql += " " + joinType + " JOIN " + joinTable + " " + joinAlias + " ON " + joinCondition 314 | 315 | return sql 316 | } 317 | 318 | return sql 319 | } 320 | 321 | // getFromClauses returns table or join sql string 322 | func (queryBuilder *QueryBuilder) getFromClauses() string { 323 | tableSql := "" 324 | 325 | for _, v := range queryBuilder.sqlPartsFrom { 326 | tableSql = v.table + " " + v.alias 327 | return tableSql + queryBuilder.getSQLForJoins() 328 | } 329 | 330 | return tableSql 331 | } 332 | 333 | // getSQLForSelect returns an select string in SQL. 334 | func (queryBuilder *QueryBuilder) getSQLForSelect() string { 335 | sql := "SELECT " 336 | 337 | if selectStr := queryBuilder.sqlPartsSelect; selectStr != "" { 338 | sql += selectStr 339 | } 340 | 341 | sql += " FROM " + queryBuilder.getFromClauses() 342 | 343 | if whereStr := queryBuilder.sqlPartsWhere; whereStr != "" { 344 | sql += " WHERE " + whereStr 345 | } 346 | 347 | if groupByStr := queryBuilder.sqlPartsGroupBy; groupByStr != "" { 348 | sql += " GROUP BY " + groupByStr 349 | } 350 | 351 | if havingStr := queryBuilder.sqlPartsHaving; havingStr != "" { 352 | sql += " HAVING " + havingStr 353 | } 354 | 355 | if queryBuilder.flag == ISSORT { 356 | sql += " ORDER BY " 357 | 358 | for _, v := range queryBuilder.sqlPartsOrderBy { 359 | sql += v.sort + " " + v.order + "," 360 | } 361 | sql = sql[:len(sql)-1] 362 | } 363 | 364 | if queryBuilder.isLimitQuery() { 365 | sql += " LIMIT " + strconv.Itoa(queryBuilder.firstResult) + "," + strconv.Itoa(queryBuilder.maxResults) 366 | } 367 | 368 | return sql 369 | } 370 | 371 | // getSQLForDelete returns an delete string in SQL. 372 | func (queryBuilder *QueryBuilder) getSQLForDelete() string { 373 | sql := "DELETE " 374 | 375 | for _, v := range queryBuilder.sqlPartsFrom { 376 | sql += " FROM " + v.table 377 | if whereStr := queryBuilder.sqlPartsWhere; whereStr != "" { 378 | sql += " WHERE " + whereStr 379 | } 380 | 381 | return sql 382 | } 383 | 384 | return sql 385 | } 386 | 387 | // getSQLForInsert returns an insert string in SQL. 388 | func (queryBuilder *QueryBuilder) getSQLForInsert() string { 389 | sql := "INSERT INTO " 390 | 391 | for _, v := range queryBuilder.sqlPartsFrom { 392 | tableSql := v.table 393 | sql += tableSql + " (" 394 | 395 | sortedKeys := make([]string, 0) 396 | 397 | values := "" 398 | 399 | params := make([]interface{}, 0) 400 | 401 | for _, v := range queryBuilder.sqlPartsValues { 402 | sortedKeys = append(sortedKeys, v.key) 403 | 404 | sql += v.key + "," 405 | values += "?," 406 | 407 | params = append(params, v.val) 408 | 409 | } 410 | 411 | queryBuilder.params = params 412 | 413 | sql = sql[:len(sql)-1] 414 | values = values[:len(values)-1] 415 | sql += ") VALUES(" + values + ")" 416 | 417 | return sql 418 | } 419 | 420 | return sql 421 | } 422 | 423 | // isLimitQuery returns is a limited Query 424 | func (queryBuilder *QueryBuilder) isLimitQuery() bool { 425 | if queryBuilder.maxResults == -1 { 426 | return false 427 | } 428 | if queryBuilder.maxResults > 0 && queryBuilder.firstResult >= 0 { 429 | return true 430 | } 431 | return false 432 | } 433 | 434 | // executeQuery executes a query that returns rows 435 | func (queryBuilder *QueryBuilder) executeQuery(query string) (*sql.Rows, error) { 436 | if queryBuilder.params != nil { 437 | rows, err := queryBuilder.database.Query(query, queryBuilder.params...) 438 | 439 | return rows, err 440 | } 441 | 442 | rows, err := queryBuilder.database.Query(query, nil) 443 | 444 | return rows, err 445 | } 446 | 447 | // executeQueryAndGetRowsMap executes a query that returns rows map 448 | func (queryBuilder *QueryBuilder) executeQueryAndGetRowsMap(query string) (map[int]map[string]string, error) { 449 | if queryBuilder.params != nil { 450 | rows, err := queryBuilder.database.Query(query, queryBuilder.params...) 451 | 452 | return getRowsMap(rows), err 453 | } 454 | 455 | rows, err := queryBuilder.database.Query(query, nil) 456 | 457 | return getRowsMap(rows), err 458 | } 459 | 460 | // getRowsMap returns rows map 461 | func getRowsMap(rows *sql.Rows) map[int]map[string]string { 462 | columns, _ := rows.Columns() 463 | count := len(columns) 464 | values := make([]interface{}, count) 465 | columnPointers := make([]interface{}, count) 466 | 467 | resultId := 0 468 | result := map[int]map[string]string{} 469 | 470 | for rows.Next() { 471 | for i := range columns { 472 | columnPointers[i] = &values[i] 473 | } 474 | 475 | if err := rows.Scan(columnPointers...); err != nil { 476 | panic(err) 477 | } 478 | 479 | record := map[string]string{} 480 | 481 | for i, col := range columns { 482 | var v interface{} 483 | val := values[i] 484 | 485 | if str, ok := val.(string); ok { 486 | v = str 487 | } else { 488 | v = val 489 | switch v.(type) { 490 | case int, int8, int16, int32, int64: 491 | res := strings.Split(fmt.Sprintf("%s", v), "=") 492 | resTmp := res[1] 493 | 494 | v = resTmp[:len(resTmp)-1] 495 | } 496 | } 497 | 498 | record[col] = fmt.Sprintf("%s", v) 499 | } 500 | 501 | result[resultId] = record 502 | resultId++ 503 | } 504 | 505 | return result 506 | } 507 | 508 | // Query executes a query that returns rows 509 | func (queryBuilder *QueryBuilder) Query() (*sql.Rows, error) { 510 | if queryBuilder.queryType == SELECT { 511 | return queryBuilder.executeQuery(queryBuilder.GetSQL()) 512 | } 513 | return nil, nil 514 | } 515 | 516 | // QueryAndGetMap executes a query that returns rows map 517 | func (queryBuilder *QueryBuilder) QueryAndGetMap() (map[int]map[string]string, error) { 518 | if queryBuilder.queryType == SELECT { 519 | return queryBuilder.executeQueryAndGetRowsMap(queryBuilder.GetSQL()) 520 | } 521 | return nil, nil 522 | } 523 | 524 | // prepareAndExecute creates a prepared statement for later queries or executions. 525 | func (queryBuilder *QueryBuilder) prepareAndExecute() sql.Result { 526 | stmt, err := queryBuilder.database.Prepare(queryBuilder.GetSQL()) 527 | if err != nil { 528 | panic(err) 529 | } 530 | queryBuilder.State = stmt 531 | res, err := stmt.Exec(queryBuilder.params...) 532 | if err != nil { 533 | panic(err) 534 | } 535 | return res 536 | } 537 | 538 | // PrepareAndExecute creates a prepared statement for later queries or executions. 539 | func (queryBuilder *QueryBuilder) PrepareAndExecute() (int64, error) { 540 | if queryBuilder.queryType == INSERT { 541 | res := queryBuilder.prepareAndExecute() 542 | return res.LastInsertId() 543 | } 544 | 545 | if queryBuilder.queryType == DELETE { 546 | res := queryBuilder.prepareAndExecute() 547 | return res.RowsAffected() 548 | } 549 | 550 | if queryBuilder.queryType == UPDATE { 551 | res := queryBuilder.prepareAndExecute() 552 | return res.RowsAffected() 553 | } 554 | 555 | return -1, nil 556 | } 557 | 558 | // Insert turns the query being built into an insert query that inserts into 559 | func (queryBuilder *QueryBuilder) Insert(table string) *QueryBuilder { 560 | queryBuilder.queryType = INSERT 561 | queryBuilder.setFromWrap(table, "") 562 | 563 | return queryBuilder 564 | } 565 | 566 | // Delete turns the query being built into a bulk delete query that ranges over 567 | func (queryBuilder *QueryBuilder) Delete(table string) *QueryBuilder { 568 | queryBuilder.queryType = DELETE 569 | queryBuilder.setFromWrap(table, "") 570 | 571 | return queryBuilder 572 | } 573 | 574 | // setFromWrap wraps sqlParts `from` 575 | func (queryBuilder *QueryBuilder) setFromWrap(table string, alias string) { 576 | queryBuilder.sqlPartsFrom = append(queryBuilder.sqlPartsFrom, FromSqlParts{table, alias}) 577 | } 578 | --------------------------------------------------------------------------------