From cb6a3ce1011320f0b10776c8671a62caf939e3b9 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Thu, 10 Dec 2015 17:51:53 -0500 Subject: [PATCH] merged previous changes --- README.md | 254 ++++++++++++++++++++++++++++++++++----- builder.go | 4 +- builder_mssql_test.go | 2 +- builder_mysql_test.go | 2 +- builder_oci_test.go | 2 +- builder_pgsql_test.go | 2 +- builder_sqlite_test.go | 2 +- builder_standard_test.go | 4 +- db.go | 37 +++--- db_test.go | 2 +- query.go | 65 +++++++--- query_builder.go | 8 +- query_builder_test.go | 10 +- query_test.go | 4 +- select.go | 4 +- select_test.go | 6 +- 16 files changed, 316 insertions(+), 92 deletions(-) diff --git a/README.md b/README.md index 2c64398..56d5b6d 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,9 @@ ozzo-dbx is a Go package that enhances the standard `database/sql` package by pr as well as DB-agnostic query building capabilities. It has the following features: * Populating data into structs and NullString maps -* DB-agnostic query building, including SELECT queries, data manipulation queries, and schema manipulation queries +* Named parameter binding +* DB-agnostic query building methods, including SELECT queries, data manipulation queries, and schema manipulation queries +* Powerful query condition building * Open architecture allowing addition of new database support or customization of existing support * Logging executed SQL statements * Supporting major relational databases @@ -160,13 +162,14 @@ which will execute the query and populate the result into the specified variable For example, ```go -q := db.NewQuery("SELECT id, name FROM users LIMIT 10") +type User struct { + ID int + Name string +} var ( - users []struct { - ID int - Name string - } + users []User + user User row dbx.NullStringMap @@ -176,11 +179,19 @@ var ( err error ) -// populate all rows into users struct slice +q := db.NewQuery("SELECT id, name FROM users LIMIT 10") + +// populate all rows into a User slice err = q.All(&users) +fmt.Println(users[0].ID, users[0].Name) + +// populate the first row into a User struct +err = q.One(&user) +fmt.Println(user.ID, user.Name) // populate the first row into a NullString map err = q.One(&row) +fmt.Println(row["id"], row["name"]) // populate the first row into id and name err = q.Row(&id, &name) @@ -192,46 +203,233 @@ for rows.Next() { } ``` -When populating a struct, the data of a resulting column will be populated to an exported struct field if the name -of the field maps to that of the column according to the field mapping function specified by `Query.FieldMapper`. -For example, - -If a resulting column does not have a corresponding struct field, it will be skipped without error. The default -field mapping function separates words in a field name by underscores and turns them into lower case. For example, -a field name `FirstName` will be mapped to the column name `first_name`, and `MyID` to `my_id`. +When populating a struct, the following rules are used to determine which columns should go into which struct fields: -If a field has a `db` tag, the tag name will be used as the corresponding column name. +* Only exported struct fields can be populated. +* A field receives data if its name is mapped to a column according to the field mapping function `Query.FieldMapper`. + The default field mapping function separates words in a field name by underscores and turns them into lower case. + For example, a field name `FirstName` will be mapped to the column name `first_name`, and `MyID` to `my_id`. +* If a field has a `db` tag, the tag value will be used as the corresponding column name. If the `db` tag is a dash `-`, + it means the field should NOT be populated. +* For anonymous fields that are of struct type, they will be expanded and their component fields will be populated + according to the rules described above. +* For named fields that are of struct type, they will also be expanded. But their component fields will be prefixed + with the struct names when being populated. -Note that only exported fields can be populated with data. Unexported fields are ignored. Anonymous struct fields -will be expanded and populated according to the above rules. Named struct fields will also be expanded, but their -sub-fields will be prefixed with the struct names. In the following example, the `User` type can be used to populate -data from a column named `prof.age`. If the `Profile` field is anonymous, it would be able to receive data from -from a column named `age` without the prefix `prof.`. +The following example shows how fields are populated according to the rules above: ```go -type Profile struct { - Age int +type User struct { + id int + Type int `db:"-"` + MyName string `db:"name"` + Prof Profile } -type User struct { - Prof Profile +type Profile struct { + Age int } ``` +* `User.id`: not populated because the `db` tag is `-`; +* `User.Type`: not populated because the field is not exported; +* `User.MyName`: to be populated from the `name` column, according to the `db` tag; +* `Profile.Age`: to be populated from the `prof.age` column, since `Prof` is a named field of struct type + and its fields will be prefixed with `prof.`. +Note that if a column in the result does not have a corresponding struct field, it will be ignored. Similarly, +if a struct field does not have a corresponding column in the result, it will not be populated. ## Binding Parameters -## Building SELECT Queries +A SQL statement is usually parameterized with dynamic values. For example, you may want to select the user record +according to the user ID received from the client. Parameter binding should be used in this case, and it is almost +always preferred for security reason. Unlike `database/sql` which does anonymous parameter binding, `ozzo-dbx` uses +named parameter binding. For example, + +```go +q := db.NewQuery("SELECT id, name FROM users WHERE id={:id}") +q.Bind(dbx.Params{"id": 100}) +q.One(&user) +``` + +The above example will select the user record whose `id` is 100. The method `Query.Bind()` binds a set +of named parameters to a SQL statement which contains parameter placeholders in the format of `{:ParamName}`. + +If a SQL statement needs to be executed multiple times with different parameter values, it should be prepared +to improve the performance. For example, + +```go +q := db.NewQuery("SELECT id, name FROM users WHERE id={:id}") +q.Prepare() + +q.Bind(dbx.Params{"id": 100}) +q.One(&user) + +q.Bind(dbx.Params{"id": 200}) +q.One(&user) + +// ... +``` + +Note that anonymous parameter binding is not supported as it will mess up with named parameters. + +## Building Queries + +Instead of writing plain SQLs, `ozzo-dbx` allows you to build SQLs programmatically, which often leads to cleaner, +more secure, and DB-agnostic code. You can build three types of queries: the SELECT queries, the data manipulation +queries, and the schema manipulation queries. + +### Building SELECT Queries + +Building a SELECT query starts by calling `DB.Select()`. You can build different clauses of a SELECT query using +the corresponding query building methods. For example, + +```go +db, _ := dbx.Open("mysql", "user:pass@/example") +db.Select("id", "name"). + From("users"). + Where(dbx.HashExp{"id": 100}). + One(&user) +``` + +The above code will generate and execute the following SQL statement: + +```sql +SELECT `id`, `name` +FROM `users` +WHERE `id`={:p0} +``` -## Building Data Manipulation Queries +Notice how the table and column names are properly quoted according to the currently using database type. +And parameter binding is used to populate the value of `p0` in the `WHERE` clause. + +`dbx-ozzo` supports very flexible and powerful query condition building which can be used to build SQL clauses +such as `WHERE`, `HAVING`, etc. For example, + +```go +// id=100 +dbx.NewExp("id={:id}", Params{"id": 100}) + +// id=100 AND status=1 +dbx.HashExp{"id": 100, "status": 1} + +// status=1 OR age>30 +dbx.Or(dbx.HashExp{"status": 1}, dbx.NewExp("age>30")) + +// name LIKE '%admin%' AND name LIKE '%example%' +dbx.Like("name", "admin", "example") +``` + +### Building Data Manipulation Queries + +Data manipulation queries are those changing the data in the database, such as INSERT, UPDATE, DELETE statements. +Such queries can be built by calling the corresponding methods of `DB`. For example, + +```go +db, _ := dbx.Open("mysql", "user:pass@/example") -## Building Schema Manipulation Queries +// INSERT INTO `users` (`name`, `email`) VALUES ({:p0}, {:p1}) +db.Insert("users", dbx.Params{ + "name": "James", + "email": "james@example.com", +}).Execute() + +// UPDATE `users` SET `status`={:p0} WHERE `id`={:p1} +db.Update("users", dbx.Params{"status": 1}, dbx.HashExp{"id": 100}).Execute() + +// DELETE FROM `users` WHERE `status`={:p0} +db.Delete("users", dbx.HashExp{"status": 2}).Execute() +``` + +When building data manipulation queries, remember to call `Execute()` at the end to execute the queries. + +### Building Schema Manipulation Queries + +Schema manipulation queries are those changing the database schema, such as creating a new table, adding a new column. +These queries can be built by calling the corresponding methods of `DB`. For example, + +```go +db, _ := dbx.Open("mysql", "user:pass@/example") + +// CREATE TABLE `users` (`id` int primary key, `name` varchar(255)) +q := db.CreateTable("users", map[string]string{ + "id": "int primary key", + "name": "varchar(255)", +}) +q.Execute() +``` ## Using Transactions -## Handling Errors +You can use all aforementioned query execution and building methods with transaction. For example, + +```go +db, _ := dbx.Open("mysql", "user:pass@/example") + +tx, _ := db.Begin() + +_, err1 := tx.Insert("users", dbx.Params{ + "name": "user1", +}).Execute() +_, err2 := tx.Insert("users", dbx.Params{ + "name": "user2", +}).Execute() + +if err1 == nil && err2 == nil { + tx.Commit() +} else { + tx.Rollback() +} +``` ## Logging Executed SQL Statements +When `DB.LogFunc` is configured with a compatible log function, all SQL statements being executed will be logged. +The following example shows how to configure the logger using the standard `log` package: + +```go +import ( + "fmt" + "log" + "github.com/go-ozzo/ozzo-dbx" +) + +func main() { + db, _ := dbx.Open("mysql", "user:pass@/example") + db.LogFunc = log.Printf + + // ... +) +``` + +And the following example shows how to use the `ozzo-log` package which allows logging message severities and categories: + +```go +import ( + "fmt" + "github.com/go-ozzo/ozzo-dbx" + "github.com/go-ozzo/ozzo-log" +) + +func main() { + logger := log.NewLogger() + logger.Targets = []log.Target{log.NewConsoleTarget()} + logger.Open() + + db, _ := dbx.Open("mysql", "user:pass@/example") + db.LogFunc = logger.Info + + // ... +) +``` + ## Supporting New Databases + +While `ozzo-dbx` provides out-of-box support for most major relational databases, its open architecture +allows you to add support for new databases. The effort of adding support for a new database involves: + +* Create a struct that implements the `QueryBuilder` interface. You may use `BaseQueryBuilder` directly or extend it + via composition. +* Create a struct that implements the `Builder` interface. You may extend `BaseBuilder` via composition. +* Write an `init()` function to register the new builder in `dbx.BuilderFuncMap`. diff --git a/builder.go b/builder.go index 6c4cac8..746cb4e 100644 --- a/builder.go +++ b/builder.go @@ -230,10 +230,10 @@ func (b *BaseBuilder) CreateTable(table string, cols map[string]string, options columns := []string{} for _, name := range names { - columns = append(columns, "\t" + b.db.QuoteColumnName(name) + " " + cols[name]) + columns = append(columns, b.db.QuoteColumnName(name) + " " + cols[name]) } - sql := fmt.Sprintf("CREATE TABLE %v (\n%v\n)", b.db.QuoteTableName(table), strings.Join(columns, ",\n")) + sql := fmt.Sprintf("CREATE TABLE %v (%v)", b.db.QuoteTableName(table), strings.Join(columns, ", ")) for _, opt := range options { sql += " " + opt } diff --git a/builder_mssql_test.go b/builder_mssql_test.go index 17f12bc..2aa776f 100644 --- a/builder_mssql_test.go +++ b/builder_mssql_test.go @@ -65,7 +65,7 @@ func TestMssqlQueryBuilder_BuildOrderByAndLimit(t *testing.T) { func getMssqlBuilder() Builder { db := getDB() - b := NewMssqlBuilder(db, db.BaseDB) + b := NewMssqlBuilder(db, db.sqlDB) db.Builder = b return b } diff --git a/builder_mysql_test.go b/builder_mysql_test.go index 5404ba7..fb13b0e 100644 --- a/builder_mysql_test.go +++ b/builder_mysql_test.go @@ -45,7 +45,7 @@ func TestMysqlBuilder_DropForeignKey(t *testing.T) { func getMysqlBuilder() Builder { db := getDB() - b := NewMysqlBuilder(db, db.BaseDB) + b := NewMysqlBuilder(db, db.sqlDB) db.Builder = b return b } diff --git a/builder_oci_test.go b/builder_oci_test.go index 5ba438a..03f398f 100644 --- a/builder_oci_test.go +++ b/builder_oci_test.go @@ -48,7 +48,7 @@ func TestOciQueryBuilder_BuildOrderByAndLimit(t *testing.T) { func getOciBuilder() Builder { db := getDB() - b := NewOciBuilder(db, db.BaseDB) + b := NewOciBuilder(db, db.sqlDB) db.Builder = b return b } diff --git a/builder_pgsql_test.go b/builder_pgsql_test.go index e25812b..4786c05 100644 --- a/builder_pgsql_test.go +++ b/builder_pgsql_test.go @@ -28,7 +28,7 @@ func TestPgsqlBuilder_AlterColumn(t *testing.T) { func getPgsqlBuilder() Builder { db := getDB() - b := NewPgsqlBuilder(db, db.BaseDB) + b := NewPgsqlBuilder(db, db.sqlDB) db.Builder = b return b } diff --git a/builder_sqlite_test.go b/builder_sqlite_test.go index 2be21a8..e1f31a7 100644 --- a/builder_sqlite_test.go +++ b/builder_sqlite_test.go @@ -81,7 +81,7 @@ func TestSqliteBuilder_DropForeignKey(t *testing.T) { func getSqliteBuilder() Builder { db := getDB() - b := NewSqliteBuilder(db, db.BaseDB) + b := NewSqliteBuilder(db, db.sqlDB) db.Builder = b return b } diff --git a/builder_standard_test.go b/builder_standard_test.go index ecd9413..6fb9fb9 100644 --- a/builder_standard_test.go +++ b/builder_standard_test.go @@ -77,7 +77,7 @@ func TestStandardBuilder_CreateTable(t *testing.T) { "id": "int primary key", "name": "varchar(255)", }, "ON DELETE CASCADE") - assertEqual(t, q.SQL(), "CREATE TABLE \"users\" (\n\t\"id\" int primary key,\n\t\"name\" varchar(255)\n) ON DELETE CASCADE", "t1") + assertEqual(t, q.SQL(), "CREATE TABLE \"users\" (\"id\" int primary key, \"name\" varchar(255)) ON DELETE CASCADE", "t1") } func TestStandardBuilder_RenameTable(t *testing.T) { @@ -166,7 +166,7 @@ func TestStandardBuilder_DropIndex(t *testing.T) { func getStandardBuilder() Builder { db := getDB() - b := NewStandardBuilder(db, db.BaseDB) + b := NewStandardBuilder(db, db.sqlDB) db.Builder = b return b } diff --git a/db.go b/db.go index 0777c08..e40f184 100644 --- a/db.go +++ b/db.go @@ -11,27 +11,23 @@ import ( "regexp" ) -// Logger specifies the logger interface needed to log SQL statements being executed. -type Logger interface { - // Info logs a message for informational purpose. - // This method takes one or multiple parameters. If a single parameter - // is provided, it will be treated as the log message. If multiple parameters - // are provided, they will be passed to fmt.Sprintf() to generate the log message. - Info(format string, a ...interface{}) -} +// LogFunc logs a message for each SQL statement being executed. +// This method takes one or multiple parameters. If a single parameter +// is provided, it will be treated as the log message. If multiple parameters +// are provided, they will be passed to fmt.Sprintf() to generate the log message. +type LogFunc func(format string, a ...interface{}) // DB enhances sql.DB by providing a set of DB-agnostic query building methods. // DB allows easier query building and population of data into Go variables. type DB struct { Builder - // BaseDB stores the sql.DB instance that is encapsulated by DB. - BaseDB *sql.DB // FieldMapper maps struct fields to DB columns. Defaults to DefaultFieldMapFunc. FieldMapper FieldMapFunc - // Logger logs the SQL statements being executed. Defaults to nil, meaning no logging. - Logger Logger + // LogFunc logs the SQL statements being executed. Defaults to nil, meaning no logging. + LogFunc LogFunc + sqlDB *sql.DB driverName string } @@ -54,17 +50,17 @@ var BuilderFuncMap = map[string]BuilderFunc{ // Note that Open does not check if DSN is specified correctly. It doesn't try to establish a DB connection either. // Please refer to sql.Open() for more information. func Open(driverName, dsn string) (*DB, error) { - baseDB, err := sql.Open(driverName, dsn) + sqlDB, err := sql.Open(driverName, dsn) if err != nil { return nil, err } db := &DB{ driverName: driverName, - BaseDB: baseDB, + sqlDB: sqlDB, FieldMapper: DefaultFieldMapFunc, } - db.Builder = db.newBuilder(db.BaseDB) + db.Builder = db.newBuilder(db.sqlDB) return db, nil } @@ -76,22 +72,27 @@ func MustOpen(driverName, dsn string) (*DB, error) { if err != nil { return nil, err } - if err := db.BaseDB.Ping(); err != nil { + if err := db.sqlDB.Ping(); err != nil { return nil, err } return db, nil } +// DB returns the sql.DB instance encapsulated by dbx.DB. +func (db *DB) DB() *sql.DB { + return db.sqlDB +} + // Close closes the database, releasing any open resources. // It is rare to Close a DB, as the DB handle is meant to be // long-lived and shared between many goroutines. func (db *DB) Close() error { - return db.BaseDB.Close() + return db.sqlDB.Close() } // Begin starts a transaction. func (db *DB) Begin() (*Tx, error) { - tx, err := db.BaseDB.Begin() + tx, err := db.sqlDB.Begin() if err != nil { return nil, err } diff --git a/db_test.go b/db_test.go index d35a67f..4c494ae 100644 --- a/db_test.go +++ b/db_test.go @@ -28,7 +28,7 @@ func TestDB_Open(t *testing.T) { } db, _ := Open("mysql", TestDSN) - assertNotEqual(t, db.BaseDB, nil, "BaseDB") + assertNotEqual(t, db.sqlDB, nil, "BaseDB") assertNotEqual(t, db.FieldMapper, nil, "MapField") } diff --git a/query.go b/query.go index 331cf9d..cf72c15 100644 --- a/query.go +++ b/query.go @@ -9,6 +9,7 @@ import ( "errors" "strings" "fmt" + "time" ) // Params represents a list of parameter values to be bound to a SQL statement. @@ -39,8 +40,8 @@ type Query struct { FieldMapper FieldMapFunc // LastError contains the last error (if any) of the query. LastError error - // Logger is used to log the SQL statement being executed. - Logger Logger + // LogFunc is used to log the SQL statement being executed. + LogFunc LogFunc } // NewQuery creates a new Query with the given SQL statement. @@ -53,7 +54,7 @@ func NewQuery(db *DB, executor Executor, sql string) *Query { placeholders: placeholders, params: Params{}, FieldMapper: db.FieldMapper, - Logger: db.Logger, + LogFunc: db.LogFunc, } } @@ -64,25 +65,34 @@ func (q *Query) SQL() string { return q.sql } -// RawSQL returns the SQL statement passed to the underlying DB driver for execution. -func (q *Query) RawSQL() string { - return q.rawSQL -} - // logSQL returns the SQL statement with parameters being replaced with the actual values. // The result is only for logging purpose and should not be used to execute. func (q *Query) logSQL() string { s := q.sql for k, v := range q.params { - sv := fmt.Sprintf("%v", v) + var sv string if _, ok := v.(string); ok { - sv = "'" + strings.Replace(sv, "'", "''", -1) + "'" + sv = "'" + strings.Replace(v.(string), "'", "''", -1) + "'" + } else if _, ok := v.([]byte); ok { + sv = "'" + strings.Replace(string(v.([]byte)), "'", "''", -1) + "'" + } else { + sv = fmt.Sprintf("%v", v) } s = strings.Replace(s, "{:" + k + "}", sv, -1) } return s } +// log logs a message for the currently executed SQL statement. +func (q *Query) log(start time.Time, execute bool) { + t := float64(time.Now().Sub(start).Nanoseconds()) / 1e6 + if execute { + q.LogFunc("[%.2fms] Execute SQL: %v", t, q.logSQL()) + } else { + q.LogFunc("[%.2fms] Query SQL: %v", t, q.logSQL()) + } +} + // Params returns the parameters to be bound to the SQL statement represented by this query. func (q *Query) Params() Params { return q.params @@ -136,8 +146,8 @@ func (q *Query) Execute() (sql.Result, error) { return nil, err } - if q.Logger != nil { - q.Logger.Info("Execute SQL: %v", q.logSQL()) + if q.LogFunc != nil { + defer q.log(time.Now(), true) } var result sql.Result @@ -154,11 +164,16 @@ func (q *Query) Execute() (sql.Result, error) { // Refer to Rows.ScanStruct() and Rows.ScanMap() for more details on how to specify // the variable to be populated. func (q *Query) One(a interface{}) error { + if q.LastError != nil { + return q.LastError + } + rows, err := q.Rows() if err != nil { - return err + q.LastError = err + } else { + q.LastError = rows.one(a) } - q.LastError = rows.one(a) return q.LastError } @@ -166,22 +181,32 @@ func (q *Query) One(a interface{}) error { // The slice must be given as a pointer. The slice elements must be either structs or NullStringMap. // Refer to Rows.ScanStruct() and Rows.ScanMap() for more details on how each slice element can be. func (q *Query) All(slice interface{}) error { + if q.LastError != nil { + return q.LastError + } + rows, err := q.Rows() if err != nil { - return err + q.LastError = err + } else { + q.LastError = rows.all(slice) } - q.LastError = rows.all(slice) return q.LastError } // Row executes the SQL statement and populates the first row of the result into a list of variables. // Note that the number of the variables should match to that of the columns in the query result. func (q *Query) Row(a ...interface{}) error { + if q.LastError != nil { + return q.LastError + } + rows, err := q.Rows() if err != nil { - return err + q.LastError = err + } else { + q.LastError = rows.row(a...) } - q.LastError = rows.row(a...) return q.LastError } @@ -197,8 +222,8 @@ func (q *Query) Rows() (*Rows, error) { return nil, err } - if q.Logger != nil { - q.Logger.Info("Query SQL: %v", q.logSQL()) + if q.LogFunc != nil { + defer q.log(time.Now(), false) } var rows *sql.Rows diff --git a/query_builder.go b/query_builder.go index 6d20121..e8b5adb 100644 --- a/query_builder.go +++ b/query_builder.go @@ -109,7 +109,7 @@ func (q *BaseQueryBuilder) BuildJoin(joins []JoinInfo, params Params) string { } parts = append(parts, sql) } - return strings.Join(parts, "\n") + return strings.Join(parts, " ") } func (q *BaseQueryBuilder) BuildWhere(e Expression, params Params) string { @@ -147,10 +147,10 @@ func (q *BaseQueryBuilder) BuildGroupBy(cols []string) string { func (q *BaseQueryBuilder) BuildOrderByAndLimit(sql string, cols []string, limit int64, offset int64) string { if orderBy := q.BuildOrderBy(cols); orderBy != "" { - sql += "\n" + orderBy + sql += " " + orderBy } if limit := q.BuildLimit(limit, offset); limit != "" { - return sql + "\n" + limit + return sql + " " + limit } return sql } @@ -162,7 +162,7 @@ func (q *BaseQueryBuilder) BuildUnion(unions []UnionInfo, params Params) string sql := "" for i, union := range unions { if i > 0 { - sql += "\n" + sql += " " } for k, v := range union.Query.params { params[k] = v diff --git a/query_builder_test.go b/query_builder_test.go index 6f42577..4dfe247 100644 --- a/query_builder_test.go +++ b/query_builder_test.go @@ -163,7 +163,7 @@ func TestQB_BuildOrderByAndLimit(t *testing.T) { qb := getDB().QueryBuilder() sql := qb.BuildOrderByAndLimit("SELECT *", []string{"name"}, 10, 2) - expected := "SELECT *\nORDER BY `name`\nLIMIT 10 OFFSET 2" + expected := "SELECT * ORDER BY `name` LIMIT 10 OFFSET 2" assertEqual(t, sql, expected, "t1") sql = qb.BuildOrderByAndLimit("SELECT *", nil, -1, -1) @@ -171,11 +171,11 @@ func TestQB_BuildOrderByAndLimit(t *testing.T) { assertEqual(t, sql, expected, "t2") sql = qb.BuildOrderByAndLimit("SELECT *", []string{"name"}, -1, -1) - expected = "SELECT *\nORDER BY `name`" + expected = "SELECT * ORDER BY `name`" assertEqual(t, sql, expected, "t3") sql = qb.BuildOrderByAndLimit("SELECT *", nil, 10, -1) - expected = "SELECT *\nLIMIT 10" + expected = "SELECT * LIMIT 10" assertEqual(t, sql, expected, "t4") } @@ -203,7 +203,7 @@ func TestQB_BuildJoin(t *testing.T) { ji = JoinInfo{"INNER JOIN", "users", nil} ji2 := JoinInfo{"LEFT JOIN", "posts", nil} sql = qb.BuildJoin([]JoinInfo{ji, ji2}, nil) - expected = "INNER JOIN `users`\nLEFT JOIN `posts`" + expected = "INNER JOIN `users` LEFT JOIN `posts`" assertEqual(t, sql, expected, "BuildJoin@3") } @@ -232,6 +232,6 @@ func TestQB_BuildUnion(t *testing.T) { ui = UnionInfo{true, db.NewQuery("SELECT names")} ui2 := UnionInfo{false, db.NewQuery("SELECT ages")} sql = qb.BuildUnion([]UnionInfo{ui, ui2}, nil) - expected = "UNION ALL (SELECT names)\nUNION (SELECT ages)" + expected = "UNION ALL (SELECT names) UNION (SELECT ages)" assertEqual(t, sql, expected, "BuildUnion@4") } diff --git a/query_test.go b/query_test.go index 66aa9d0..5137583 100644 --- a/query_test.go +++ b/query_test.go @@ -17,9 +17,9 @@ type City struct { func TestNewQuery(t *testing.T) { db := getDB() sql := "SELECT * FROM users WHERE id={:id}" - q := NewQuery(db, db.BaseDB, sql) + q := NewQuery(db, db.sqlDB, sql) assertEqual(t, q.SQL(), sql, "q.SQL()") - assertEqual(t, q.RawSQL(), "SELECT * FROM users WHERE id=?", "q.RawSQL()") + assertEqual(t, q.rawSQL, "SELECT * FROM users WHERE id=?", "q.RawSQL()") assertEqual(t, len(q.Params()), 0, "len(q.Params())@1") q.Bind(Params{"id": 1}) diff --git a/select.go b/select.go index 6f92a39..af06d6b 100644 --- a/select.go +++ b/select.go @@ -252,13 +252,13 @@ func (s *SelectQuery) Build() *Query { if sql == "" { sql = clause } else { - sql += "\n" + clause + sql += " " + clause } } } sql = qb.BuildOrderByAndLimit(sql, s.orderBy, s.limit, s.offset) if union := qb.BuildUnion(s.union, params); union != "" { - sql = fmt.Sprintf("(%v)\n%v", sql, union) + sql = fmt.Sprintf("(%v) %v", sql, union) } return s.builder.NewQuery(sql).Bind(s.params) diff --git a/select_test.go b/select_test.go index 2c1a133..49424fa 100644 --- a/select_test.go +++ b/select_test.go @@ -13,7 +13,7 @@ func TestSelectQuery(t *testing.T) { // minimal select query q := db.Select().From("users").Build() - expected := "SELECT *\nFROM `users`" + expected := "SELECT * FROM `users`" assertEqual(t, q.SQL(), expected, "t1") assertEqual(t, len(q.Params()), 0, "t2") @@ -40,7 +40,7 @@ func TestSelectQuery(t *testing.T) { AndBind(Params{"age": 30}). Build() - expected = "SELECT DISTINCT CALC `id`, `name`, `age`\nFROM `users`\nINNER JOIN `profile` ON user.id=profile.id\nWHERE ((age>30) AND (status=1)) OR (type=2)\nGROUP BY `id`, `age`\nHAVING ((id>10) AND (id<20)) OR (type=3)\nORDER BY `age` DESC, `type`, `id`\nLIMIT 10 OFFSET 20" + expected = "SELECT DISTINCT CALC `id`, `name`, `age` FROM `users` INNER JOIN `profile` ON user.id=profile.id WHERE ((age>30) AND (status=1)) OR (type=2) GROUP BY `id`, `age` HAVING ((id>10) AND (id<20)) OR (type=3) ORDER BY `age` DESC, `type`, `id` LIMIT 10 OFFSET 20" assertEqual(t, q.SQL(), expected, "t3") assertEqual(t, len(q.Params()), 2, "t4") @@ -48,7 +48,7 @@ func TestSelectQuery(t *testing.T) { q1 := db.Select().From("users").Build() q2 := db.Select().From("posts").Build() q = db.Select().From("profiles").Union(q1).UnionAll(q2).Build() - expected = "(SELECT *\nFROM `profiles`)\nUNION (SELECT *\nFROM `users`)\nUNION ALL (SELECT *\nFROM `posts`)" + expected = "(SELECT * FROM `profiles`) UNION (SELECT * FROM `users`) UNION ALL (SELECT * FROM `posts`)" assertEqual(t, q.SQL(), expected, "t5") }