├── .travis.yml ├── LICENSE ├── README.md ├── alter.go ├── alter_test.go ├── column.go ├── column_test.go ├── condition.go ├── condition_test.go ├── create.go ├── create_test.go ├── delete.go ├── delete_test.go ├── dialects ├── mysql.go ├── postgresql.go └── sqlite3.go ├── drop.go ├── drop_test.go ├── insert.go ├── insert_test.go ├── integration_test ├── doc.go ├── main_test.go └── scenario_test.go ├── literal.go ├── literal_test.go ├── select.go ├── select_test.go ├── sqlbuilder.go ├── sqlbuilder_test.go ├── sqlfunc.go ├── sqlfunc_test.go ├── table.go ├── table_test.go ├── testutils_test.go ├── update.go └── update_test.go /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.4 5 | - tip 6 | 7 | addons: 8 | postgresql: "9.3" 9 | 10 | before_install: 11 | - go get github.com/axw/gocov/gocov 12 | - go get github.com/mattn/goveralls 13 | - go get golang.org/x/tools/cmd/cover 14 | 15 | before_script: 16 | - mysql -e 'create database go_sqlbuilder_test1;' 17 | - mysql -e 'create database go_sqlbuilder_test2;' 18 | - psql -c 'create database go_sqlbuilder_test' -U postgres 19 | 20 | script: 21 | - $HOME/gopath/bin/goveralls -repotoken IgwKyLgyhaPzKKNdryx4T3swQIiqfO1Rb 22 | - go test -v ./integration_test 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 umisama 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # umisama/go-sqlbuilder 2 | **go-sqlbuilder** is a SQL-query builder for golang. This supports you using relational database with more readable and flexible code than raw SQL query string. 3 | 4 | [![Build Status](https://travis-ci.org/umisama/go-sqlbuilder.svg?branch=master)](https://travis-ci.org/umisama/go-sqlbuilder) 5 | [![Coverage Status](https://coveralls.io/repos/umisama/go-sqlbuilder/badge.svg)](https://coveralls.io/r/umisama/go-sqlbuilder) 6 | 7 | ## Support 8 | * Generate SQL query programmatically. 9 | * fluent flexibility! yeah!! 10 | * Basic SQL statements 11 | * SELECT/INSERT/UPDATE/DELETE/DROP/CREATE TABLE/CREATE INDEX 12 | * Strict error checking 13 | * Some database server 14 | * Sqlite3([mattn/go-sqlite3](https://github.com/mattn/go-sqlite3)) 15 | * MySQL([ziutek/mymysql](https://github.com/ziutek/mymysql)) 16 | * MySQL([go-sql-driver/mysql](https://github.com/go-sql-driver/mysql)) 17 | * PostgresSQL([lib/pq](https://github.com/lib/pq)) 18 | * Subquery in SELECT FROM clause 19 | 20 | ## TODO 21 | * Support UNION clause 22 | * Support LOCK clause 23 | 24 | ## Quick usage 25 | 26 | ```go 27 | import ( 28 | sb "github.com/umisama/go-sqlbuilder" 29 | "github.com/umisama/go-sqlbuilder/dialects" 30 | ) 31 | 32 | db, err := sql.Open("sqlite3", ":memory:") 33 | if err != nil { 34 | fmt.Println(err.Error()) 35 | return 36 | } 37 | 38 | // Set dialect first 39 | // dialects are in github.com/umisama/go-sqlbuilder/dialects 40 | sb.SetDialect(TestDialect{}) 41 | 42 | // Define a table 43 | tbl_person := sb.NewTable( 44 | "PERSON", 45 | &sb.TableOption{}, 46 | sb.IntColumn("id", &sb.ColumnOption{ 47 | PrimaryKey: true, 48 | }), 49 | sb.StringColumn("name", &sb.ColumnOption{ 50 | Unique: true, 51 | Size: 255, 52 | Default: "no_name", 53 | }), 54 | sb.DateColumn("birth", nil), 55 | ) 56 | 57 | // Create Table 58 | query, args, err := sb.CreateTable(tbl_person).ToSql() 59 | if err != nil { 60 | fmt.Println(err.Error()) 61 | return 62 | } 63 | _, err = db.Exec(query, args...) 64 | if err != nil { 65 | fmt.Println(err.Error()) 66 | return 67 | } 68 | 69 | // Insert data 70 | // (Table).C function returns a column object. 71 | query, args, err = sb.Insert(tbl_person). 72 | Set(tbl_person.C("name"), "Kurisu Makise"). 73 | Set(tbl_person.C("birth"), time.Date(1992, time.July, 25, 0, 0, 0, 0, time.UTC)). 74 | ToSql() 75 | _, err = db.Exec(query, args...) 76 | if err != nil { 77 | fmt.Println(err.Error()) 78 | return 79 | } 80 | 81 | // Query 82 | // (Column).Eq returns a condition object for equal(=) operator. See 83 | var birth time.Time 84 | query, args, err = sb.Select(tbl_person).Columns( 85 | tbl_person.C("birth"), 86 | ).Where( 87 | tbl_person.C("name").Eq("Kurisu Makise"), 88 | ).ToSql() 89 | err = db.QueryRow(query, args...).Scan(&birth) 90 | if err != nil { 91 | fmt.Println(err.Error()) 92 | return 93 | } 94 | 95 | fmt.Printf("Kurisu's birthday is %s,%d %d", birth.Month().String(), birth.Day(), birth.Year()) 96 | 97 | // Output: 98 | // Kurisu's birthday is July,25 1992 99 | ``` 100 | 101 | ## Examples 102 | ### Initialize 103 | off course, go getable. 104 | 105 | ```shell-script 106 | $ go get github.com/umisama/go-sqlbuilder 107 | ``` 108 | 109 | I recomended to set "sb" as sqlbuilder's shorthand. 110 | 111 | ```go 112 | import sb "github.com/umisama/go-sqlbuilder" 113 | 114 | // First, you set dialect for your DB 115 | func init ( 116 | sb.SetDialect(sb.SqliteDialect{}) 117 | ) 118 | ``` 119 | 120 | ### Define a table 121 | Sqlbuilder needs table definition to strict query generating. Any statement checks column type and constraints. 122 | 123 | ```go 124 | tbl_person := sb.NewTable( 125 | "PERSON", 126 | &sb.TableOption{}, 127 | sb.IntColumn("id", &sb.ColumnOption{ 128 | PrimaryKey: true, 129 | }), 130 | sb.StringColumn("name", &sb.ColumnOption{ 131 | Unique: true, 132 | Size: 255, 133 | Default: "no_name", 134 | }), 135 | sb.DateColumn("birth", nil), 136 | ) 137 | ``` 138 | 139 | #### Table Options 140 | ##### Unique [][]string 141 | Sets UNIQUE options to table. 142 | 143 | example: 144 | 145 | ```go 146 | &sb.TableOption{ 147 | Unique: [][]string{ 148 | {"hoge", "piyo"}, 149 | {"fuga"}, 150 | } 151 | } 152 | ``` 153 | 154 | ``` 155 | CREATE TABLE PERSON ( "id" integer, ~~~, UNIQUE("hoge", "piyo"), UNIQUE("fuga")) 156 | ``` 157 | 158 | #### Column Options 159 | ##### PrimaryKey bool 160 | `true` for add primary key option. 161 | 162 | ##### NotNull bool 163 | `true` for add UNIQUE option. 164 | 165 | ##### Unique bool 166 | `true` for add UNIQUE option to column. 167 | 168 | example: 169 | 170 | ```go 171 | IntColumn("test", &sb.ColumnOption{ 172 | Unique: true, 173 | }) 174 | ``` 175 | 176 | ``` 177 | "test" INTEGER UNIQUE 178 | ``` 179 | 180 | ##### AutoIncrement bool 181 | `true` for add AutoIncrement option to column. 182 | 183 | ##### Size int 184 | Sets size for string column. 185 | example: 186 | 187 | ```go 188 | StringColumn("test", &sb.ColumnOption{ 189 | Size: 255, 190 | }) 191 | ``` 192 | 193 | ``` 194 | "test" VARCHAR(255) 195 | ``` 196 | 197 | ##### SqlType string 198 | Sets type for column on AnyColumn. 199 | 200 | ```go 201 | AnyColumn("test", &sb.ColumnOption{ 202 | ColumnType: "BLOB", 203 | }) 204 | ``` 205 | 206 | ``` 207 | "test" BLOB 208 | ``` 209 | 210 | ##### Default interface{} 211 | Sets default value. Default's type need to be same as column. 212 | 213 | 214 | ```go 215 | StringColumn("test", &sb.ColumnOption{ 216 | Size: 255, 217 | Default: "empty" 218 | }) 219 | ``` 220 | 221 | ``` 222 | "test" VARCHAR(255) DEFAILT "empty" 223 | ``` 224 | 225 | ### CRATE TABLE statement 226 | Sqlbuilder has a `Statement` object generating CREATE TABLE statement from table object. 227 | `Statement` objects have `ToSql()` method. it returns query(string), placeholder arguments([]interface{}) and error. 228 | 229 | ```go 230 | query, args, err := sb.CreateTable(tbl_person).ToSql() 231 | if err != nil { 232 | panic(err) 233 | } 234 | // query == `CREATE TABLE "PERSON" ( "id" INTEGER PRIMARY KEY, "value" INTEGER );` 235 | // args == []interface{}{} 236 | // err == nil 237 | ``` 238 | 239 | You can exec with ```database/sql``` package or Table-struct mapper(for example, gorp). 240 | here is example, 241 | 242 | ```go 243 | db, err := sql.Open("sqlite3", ":memory:") 244 | if err != nil { 245 | panic(err) 246 | } 247 | _, err = db.Exec(query, args...) 248 | if err != nil { 249 | panic(err) 250 | } 251 | ``` 252 | 253 | ### INSERT statement 254 | Sqlbuilder can generate INSERT statement. You can checkout a column with `Table.C([column_name])` method. 255 | 256 | ```go 257 | query, args, err := sb.Insert(table1). 258 | Columns(table1.C("id"), table1.C("value")). 259 | Values(1, 10). 260 | ToSql() 261 | // query == `INSERT INTO "TABLE_A" ( "id", "value" ) VALUES ( ?, ? );` 262 | // args == []interface{}{1, 10} 263 | // err == nil 264 | ``` 265 | 266 | Or, can use `Set()` method. 267 | 268 | ```go 269 | query, args, err := sb.Insert(table1). 270 | Set(table1.C("id"), 1). 271 | Set(table1.C("value"), 10). 272 | ToSql() 273 | // query == `INSERT INTO "TABLE_A" ( "id", "value" ) VALUES ( ?, ? );` 274 | // args == []interface{}{1, 10} 275 | // err == nil 276 | ``` 277 | 278 | ### SELECT statement 279 | Sqlbuilder can generate SELECT statement with readable interfaces. Condition object is generated from column object. 280 | 281 | ```go 282 | query, args, err := sb.Select(table1.C("id"), table1.C("value")). 283 | From(table1). 284 | Where( 285 | table1.C("id").Eq(10), 286 | ). 287 | Limit(1).OrderBy(false, table1.C("id")). 288 | ToSql() 289 | // query == `SELECT "TABLE_A"."id", "TABLE_A"."value" FROM "TABLE_A" WHERE "TABLE_A"."id"=? ORDER BY "TABLE_A"."id" ASC LIMIT ?;` 290 | // args == []interface{}{10, 1} 291 | // err == nil 292 | ``` 293 | 294 | See [godoc.org](http://godoc.org/github.com/umisama/go-sqlbuilder#SelectStatement) for more options 295 | 296 | ### Condition 297 | You can define condition with Condition objects. Condition object create from ```Column```'s method. 298 | 299 | | example operation | output example | 300 | |:-------------------------------------:|:--------------------------:| 301 | |```table1.C("id").Eq(10)``` | "TABLE1"."id"=10 | 302 | |```table1.C("id").Eq(table2.C("id"))``` | "TABLE1"."id"="TABLE2"."id"| 303 | 304 | More than one condition can combine with AND & OR operator. 305 | 306 | | example operation | output example | 307 | |:-------------------------------------:|:--------------------------:| 308 | |```And(table1.C("id").Eq(1), table2.C("id").Eq(2)``` | "TABLE1"."id"=1 AND "TABLE2"."id"=1 | 309 | |```Or(table1.C("id").Eq(1), table2.C("id").Eq(2)``` | "TABLE1"."id"=1 OR "TABLE2"."id"=1 | 310 | 311 | Sqlbuilder is supporting most common condition operators. 312 | Here is supporting: 313 | 314 | | columns method | means | SQL operator | example | 315 | |:---------------------:|:----------------------:|:-------------:|:--------------------:| 316 | |Eq(Column or value) |EQUAL TO | ```=``` | "TABLE"."id" = 10 | 317 | |NotEq(Column or value) |NOT EQUAL TO | ```<>``` | "TABLE"."id" <> 10 | 318 | |Gt(Column or value) |GRATER-THAN | ```>``` | "TABLE"."id" > 10 | 319 | |GtEq(Column or value) |GRATER-THAN OR EQUAL TO | ```>=``` | "TABLE"."id" >= 10 | 320 | |Lt(Column or value) |LESS-THAN | ```<``` | "TABLE"."id" < 10 | 321 | |LtEq(Column or value) |LESS-THAN OR EQUAL TO | ```<=``` | "TABLE"."id" <= 10 | 322 | |Like(string) |LIKE | ```LIKE``` | "TABLE"."id" LIKE "%hoge%" | 323 | |In(values array) |IN | ```IN``` | "TABLE"."id" IN ( 1, 2, 3 ) | 324 | |Between(loewer, higher int) |BETWEEN | ```BETWEEN``` | "TABLE"."id" BETWEEN 10 AND 20)| 325 | 326 | Document for all: [godoc(Column)](http://godoc.org/github.com/umisama/go-sqlbuilder#Column) 327 | 328 | ## More documents 329 | [godoc.org](http://godoc.org/github.com/umisama/go-sqlbuilder) 330 | 331 | ## License 332 | under the MIT license 333 | -------------------------------------------------------------------------------- /alter.go: -------------------------------------------------------------------------------- 1 | package sqlbuilder 2 | 3 | type AlterTableStatement struct { 4 | table *table 5 | rename_to string 6 | add_columns []*alterTableAddColumn 7 | drop_columns []Column 8 | change_columns []*alterTableChangeColumn 9 | 10 | err error 11 | } 12 | 13 | func AlterTable(tbl Table) *AlterTableStatement { 14 | if tbl == nil { 15 | return &AlterTableStatement{ 16 | err: newError("table is nil."), 17 | } 18 | } 19 | 20 | t, ok := tbl.(*table) 21 | if !ok { 22 | return &AlterTableStatement{ 23 | err: newError("AlterTable can use only natural table."), 24 | } 25 | } 26 | return &AlterTableStatement{ 27 | table: t, 28 | add_columns: make([]*alterTableAddColumn, 0), 29 | change_columns: make([]*alterTableChangeColumn, 0), 30 | } 31 | } 32 | 33 | func (b *AlterTableStatement) RenameTo(name string) *AlterTableStatement { 34 | if b.err != nil { 35 | return b 36 | } 37 | 38 | b.rename_to = name 39 | return b 40 | } 41 | 42 | func (b *AlterTableStatement) AddColumn(col ColumnConfig) *AlterTableStatement { 43 | if b.err != nil { 44 | return b 45 | } 46 | 47 | b.add_columns = append(b.add_columns, &alterTableAddColumn{ 48 | table: b.table, 49 | column: col, 50 | first: false, 51 | after: nil, 52 | }) 53 | return b 54 | } 55 | 56 | func (b *AlterTableStatement) AddColumnAfter(col ColumnConfig, after Column) *AlterTableStatement { 57 | if b.err != nil { 58 | return b 59 | } 60 | 61 | b.add_columns = append(b.add_columns, &alterTableAddColumn{ 62 | table: b.table, 63 | column: col, 64 | first: false, 65 | after: after, 66 | }) 67 | return b 68 | } 69 | 70 | func (b *AlterTableStatement) AddColumnFirst(col ColumnConfig) *AlterTableStatement { 71 | if b.err != nil { 72 | return b 73 | } 74 | 75 | b.add_columns = append(b.add_columns, &alterTableAddColumn{ 76 | table: b.table, 77 | column: col, 78 | first: true, 79 | after: nil, 80 | }) 81 | return b 82 | } 83 | 84 | func (b *AlterTableStatement) DropColumn(col Column) *AlterTableStatement { 85 | if b.err != nil { 86 | return b 87 | } 88 | 89 | b.drop_columns = append(b.drop_columns, col) 90 | return b 91 | } 92 | 93 | func (b *AlterTableStatement) ChangeColumn(old_column Column, new_column ColumnConfig) *AlterTableStatement { 94 | if b.err != nil { 95 | return b 96 | } 97 | 98 | b.change_columns = append(b.change_columns, &alterTableChangeColumn{ 99 | table: b.table, 100 | old_column: old_column, 101 | new_column: new_column, 102 | first: false, 103 | after: nil, 104 | }) 105 | return b 106 | } 107 | 108 | func (b *AlterTableStatement) ChangeColumnAfter(old_column Column, new_column ColumnConfig, after Column) *AlterTableStatement { 109 | if b.err != nil { 110 | return b 111 | } 112 | 113 | b.change_columns = append(b.change_columns, &alterTableChangeColumn{ 114 | table: b.table, 115 | old_column: old_column, 116 | new_column: new_column, 117 | first: false, 118 | after: after, 119 | }) 120 | return b 121 | } 122 | 123 | func (b *AlterTableStatement) ChangeColumnFirst(old_column Column, new_column ColumnConfig) *AlterTableStatement { 124 | if b.err != nil { 125 | return b 126 | } 127 | 128 | b.change_columns = append(b.change_columns, &alterTableChangeColumn{ 129 | table: b.table, 130 | old_column: old_column, 131 | new_column: new_column, 132 | first: true, 133 | after: nil, 134 | }) 135 | return b 136 | } 137 | 138 | func (b *AlterTableStatement) ToSql() (query string, args []interface{}, err error) { 139 | bldr := newBuilder() 140 | defer func() { 141 | query, args, err = bldr.Query(), bldr.Args(), bldr.Err() 142 | }() 143 | if b.err != nil { 144 | bldr.SetError(b.err) 145 | return 146 | } 147 | 148 | bldr.Append("ALTER TABLE ") 149 | bldr.AppendItem(b.table) 150 | bldr.Append(" ") 151 | 152 | first := true 153 | for _, add_column := range b.add_columns { 154 | if first { 155 | first = false 156 | } else { 157 | bldr.Append(", ") 158 | } 159 | bldr.AppendItem(add_column) 160 | } 161 | for _, change_column := range b.change_columns { 162 | if first { 163 | first = false 164 | } else { 165 | bldr.Append(", ") 166 | } 167 | bldr.AppendItem(change_column) 168 | } 169 | for _, drop_column := range b.drop_columns { 170 | if first { 171 | first = false 172 | } else { 173 | bldr.Append(", ") 174 | } 175 | bldr.Append("DROP COLUMN ") 176 | if colname := drop_column.column_name(); len(colname) != 0 { 177 | bldr.Append(dialect().QuoteField(colname)) 178 | } else { 179 | bldr.AppendItem(drop_column) 180 | } 181 | } 182 | if len(b.rename_to) != 0 { 183 | if first { 184 | first = false 185 | } else { 186 | bldr.Append(", ") 187 | } 188 | bldr.Append("RENAME TO ") 189 | bldr.Append(dialect().QuoteField(b.rename_to)) 190 | } 191 | 192 | return "", nil, nil 193 | } 194 | 195 | func (b *AlterTableStatement) ApplyToTable() error { 196 | for _, add_column := range b.add_columns { 197 | err := add_column.applyToTable() 198 | if err != nil { 199 | return err 200 | } 201 | } 202 | for _, change_column := range b.change_columns { 203 | err := change_column.applyToTable() 204 | if err != nil { 205 | return err 206 | } 207 | } 208 | for _, drop_column := range b.drop_columns { 209 | err := b.table.DropColumn(drop_column) 210 | if err != nil { 211 | return err 212 | } 213 | } 214 | if len(b.rename_to) != 0 { 215 | b.table.SetName(b.rename_to) 216 | } 217 | return nil 218 | } 219 | 220 | type alterTableAddColumn struct { 221 | table *table 222 | column ColumnConfig 223 | first bool 224 | after Column 225 | } 226 | 227 | func (b *alterTableAddColumn) serialize(bldr *builder) { 228 | bldr.Append("ADD COLUMN ") 229 | bldr.AppendItem(b.column) 230 | 231 | // SQL data name 232 | typ, err := dialect().ColumnTypeToString(b.column) 233 | if err != nil { 234 | bldr.SetError(err) 235 | } else if len(typ) == 0 { 236 | bldr.SetError(newError("column type is required.(maybe, a bug is in implements of dialect.)")) 237 | } else { 238 | bldr.Append(" ") 239 | bldr.Append(typ) 240 | } 241 | 242 | opt, err := dialect().ColumnOptionToString(b.column.Option()) 243 | if err != nil { 244 | bldr.SetError(err) 245 | } else if len(opt) != 0 { 246 | bldr.Append(" ") 247 | bldr.Append(opt) 248 | } 249 | 250 | if b.first { 251 | bldr.Append(" FIRST") 252 | } else if b.after != nil { 253 | bldr.Append(" AFTER ") 254 | if colname := b.after.column_name(); len(colname) != 0 { 255 | bldr.Append(dialect().QuoteField(colname)) 256 | } else { 257 | bldr.AppendItem(b.after) 258 | } 259 | } 260 | } 261 | 262 | func (b *alterTableAddColumn) applyToTable() error { 263 | if b.first { 264 | return b.table.AddColumnFirst(b.column) 265 | } 266 | if b.after != nil { 267 | return b.table.AddColumnAfter(b.column, b.after) 268 | } 269 | return b.table.AddColumnLast(b.column) 270 | } 271 | 272 | type alterTableChangeColumn struct { 273 | table *table 274 | old_column Column 275 | new_column ColumnConfig 276 | first bool 277 | after Column 278 | } 279 | 280 | func (b *alterTableChangeColumn) serialize(bldr *builder) { 281 | bldr.Append("CHANGE COLUMN ") 282 | if colname := b.old_column.column_name(); len(colname) != 0 { 283 | bldr.Append(dialect().QuoteField(colname)) 284 | } else { 285 | bldr.AppendItem(b.old_column) 286 | } 287 | bldr.Append(" ") 288 | bldr.AppendItem(b.new_column) 289 | 290 | typ, err := dialect().ColumnTypeToString(b.new_column) 291 | if err != nil { 292 | bldr.SetError(err) 293 | } else if len(typ) == 0 { 294 | bldr.SetError(newError("column type is required.(maybe, a bug is in implements of dialect.)")) 295 | } else { 296 | bldr.Append(" ") 297 | bldr.Append(typ) 298 | } 299 | 300 | opt, err := dialect().ColumnOptionToString(b.new_column.Option()) 301 | if err != nil { 302 | bldr.SetError(err) 303 | } else if len(opt) != 0 { 304 | bldr.Append(" ") 305 | bldr.Append(opt) 306 | } 307 | 308 | if b.first { 309 | bldr.Append(" FIRST") 310 | } else if b.after != nil { 311 | bldr.Append(" AFTER ") 312 | if colname := b.after.column_name(); len(colname) != 0 { 313 | bldr.Append(dialect().QuoteField(colname)) 314 | } else { 315 | bldr.AppendItem(b.after) 316 | } 317 | } 318 | } 319 | 320 | func (b *alterTableChangeColumn) applyToTable() error { 321 | if b.first { 322 | return b.table.ChangeColumnFirst(b.old_column, b.new_column) 323 | } 324 | if b.after != nil { 325 | return b.table.ChangeColumnAfter(b.old_column, b.new_column, b.after) 326 | } 327 | return b.table.ChangeColumn(b.old_column, b.new_column) 328 | } 329 | -------------------------------------------------------------------------------- /alter_test.go: -------------------------------------------------------------------------------- 1 | package sqlbuilder 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestAlterTable(t *testing.T) { 8 | table1 := NewTable( 9 | "TABLE_A", 10 | &TableOption{}, 11 | IntColumn("id", &ColumnOption{ 12 | PrimaryKey: true, 13 | }), 14 | IntColumn("test1", nil), 15 | IntColumn("test2", nil), 16 | ) 17 | table2 := NewTable( 18 | "TABLE_B", 19 | &TableOption{}, 20 | IntColumn("id", &ColumnOption{ 21 | PrimaryKey: true, 22 | }), 23 | ) 24 | tableJoined := table1.InnerJoin(table2, table1.C("test1").Eq(table2.C("id"))) 25 | 26 | var cases = []statementTestCase{{ 27 | stmt: AlterTable(table1). 28 | RenameTo("TABLE_AAA"). 29 | AddColumn(IntColumn("test3", nil)). 30 | AddColumn(IntColumn("test4", nil)). 31 | ChangeColumn(table1.C("test1"), IntColumn("test1a", nil)). 32 | DropColumn(table1.C("test1")), 33 | query: `ALTER TABLE "TABLE_A" ADD COLUMN "test3" INTEGER, ADD COLUMN "test4" INTEGER, CHANGE COLUMN "test1" "test1a" INTEGER, DROP COLUMN "test1", RENAME TO "TABLE_AAA";`, 34 | args: []interface{}{}, 35 | errmsg: "", 36 | }, { 37 | stmt: AlterTable(table1).RenameTo("TABLE_AAA"), 38 | query: `ALTER TABLE "TABLE_A" RENAME TO "TABLE_AAA";`, 39 | args: []interface{}{}, 40 | errmsg: "", 41 | }, { 42 | stmt: AlterTable(table1).AddColumn(IntColumn("test3", &ColumnOption{ 43 | Unique: true, 44 | })), 45 | query: `ALTER TABLE "TABLE_A" ADD COLUMN "test3" INTEGER UNIQUE;`, 46 | args: []interface{}{}, 47 | errmsg: "", 48 | }, { 49 | stmt: AlterTable(table1).AddColumnAfter(IntColumn("test0", nil), table1.C("id")), 50 | query: `ALTER TABLE "TABLE_A" ADD COLUMN "test0" INTEGER AFTER "id";`, 51 | args: []interface{}{}, 52 | errmsg: "", 53 | }, { 54 | stmt: AlterTable(table1).AddColumnFirst(IntColumn("test0", nil)), 55 | query: `ALTER TABLE "TABLE_A" ADD COLUMN "test0" INTEGER FIRST;`, 56 | args: []interface{}{}, 57 | errmsg: "", 58 | }, { 59 | stmt: AlterTable(table1).ChangeColumn(table1.C("test1"), IntColumn("test1a", &ColumnOption{ 60 | Unique: true, 61 | })), 62 | query: `ALTER TABLE "TABLE_A" CHANGE COLUMN "test1" "test1a" INTEGER UNIQUE;`, 63 | args: []interface{}{}, 64 | errmsg: "", 65 | }, { 66 | stmt: AlterTable(table1).ChangeColumnFirst(table1.C("test1"), IntColumn("test1a", nil)), 67 | query: `ALTER TABLE "TABLE_A" CHANGE COLUMN "test1" "test1a" INTEGER FIRST;`, 68 | args: []interface{}{}, 69 | errmsg: "", 70 | }, { 71 | stmt: AlterTable(table1).ChangeColumnAfter(table1.C("test1"), IntColumn("test1a", nil), table1.C("test2")), 72 | query: `ALTER TABLE "TABLE_A" CHANGE COLUMN "test1" "test1a" INTEGER AFTER "test2";`, 73 | args: []interface{}{}, 74 | errmsg: "", 75 | }, { 76 | stmt: AlterTable(table1).DropColumn(table1.C("test1")), 77 | query: `ALTER TABLE "TABLE_A" DROP COLUMN "test1";`, 78 | args: []interface{}{}, 79 | errmsg: "", 80 | }, { 81 | stmt: AlterTable(table1).DropColumn(table1.C("invalid")), 82 | query: ``, 83 | args: []interface{}{}, 84 | errmsg: "sqlbuilder: column TABLE_A.invalid was not found.", 85 | }, { 86 | stmt: AlterTable(table1).ChangeColumnAfter(table1.C("invalid"), IntColumn("test1a", nil), table1.C("test2")), 87 | query: ``, 88 | args: []interface{}{}, 89 | errmsg: "sqlbuilder: column TABLE_A.invalid was not found.", 90 | }, { 91 | stmt: AlterTable(table1).ChangeColumnAfter(table1.C("test1"), IntColumn("test1a", nil), table1.C("invalid")), 92 | query: ``, 93 | args: []interface{}{}, 94 | errmsg: "sqlbuilder: column TABLE_A.invalid was not found.", 95 | }, { 96 | stmt: AlterTable(table1).AddColumnAfter(IntColumn("test0", nil), table1.C("invalid")), 97 | query: ``, 98 | args: []interface{}{}, 99 | errmsg: "sqlbuilder: column TABLE_A.invalid was not found.", 100 | }, { 101 | stmt: AlterTable(nil).DropColumn(table1.C("invalid")), 102 | query: ``, 103 | args: []interface{}{}, 104 | errmsg: "sqlbuilder: table is nil.", 105 | }, { 106 | stmt: AlterTable(tableJoined).DropColumn(table1.C("id")), 107 | query: ``, 108 | args: []interface{}{}, 109 | errmsg: "sqlbuilder: AlterTable can use only natural table.", 110 | }} 111 | 112 | for num, c := range cases { 113 | mes, args, ok := c.Run() 114 | if !ok { 115 | t.Errorf(mes+" (case no.%d)", append(args, num)...) 116 | } 117 | } 118 | } 119 | 120 | func TestAlterTableApplyToTable(t *testing.T) { 121 | var cases = []struct { 122 | stmt func(Table) *AlterTableStatement 123 | expect_columns []string 124 | expect_name string 125 | }{{ 126 | stmt: func(t Table) *AlterTableStatement { 127 | return AlterTable(t). 128 | RenameTo("TABLE_AAA"). 129 | AddColumn(IntColumn("test3", nil)). 130 | AddColumnFirst(IntColumn("test4", nil)). 131 | AddColumnAfter(IntColumn("test5", nil), t.C("id")). 132 | ChangeColumn(t.C("test1"), IntColumn("test1a", nil)). 133 | ChangeColumnFirst(t.C("test2"), IntColumn("test2a", nil)). 134 | DropColumn(t.C("id")) 135 | }, 136 | expect_columns: []string{"test2a", "test4", "test5", "test1a", "test3"}, 137 | expect_name: "TABLE_AAA", 138 | }, { 139 | stmt: func(t Table) *AlterTableStatement { 140 | return AlterTable(t). 141 | ChangeColumnAfter(t.C("test1"), IntColumn("test1a", nil), t.C("test2")) 142 | }, 143 | expect_columns: []string{"id", "test2", "test1a"}, 144 | expect_name: "TABLE_A", 145 | }} 146 | 147 | for num, c := range cases { 148 | table1 := NewTable( 149 | "TABLE_A", 150 | &TableOption{}, 151 | IntColumn("id", &ColumnOption{ 152 | PrimaryKey: true, 153 | }), 154 | IntColumn("test1", nil), 155 | IntColumn("test2", nil), 156 | ) 157 | 158 | err := c.stmt(table1).ApplyToTable() 159 | if err != nil { 160 | t.Errorf("failed on %d", num) 161 | } 162 | if len(table1.Columns()) != len(c.expect_columns) { 163 | t.Errorf("failed on %d", num) 164 | } 165 | for i, col := range table1.Columns() { 166 | if c.expect_columns[i] != col.column_name() { 167 | t.Errorf("failed on %d", num) 168 | break 169 | } 170 | } 171 | if table1.Name() != c.expect_name { 172 | t.Errorf("failed on %d", num) 173 | } 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /column.go: -------------------------------------------------------------------------------- 1 | package sqlbuilder 2 | 3 | import ( 4 | sqldriver "database/sql/driver" 5 | "reflect" 6 | "time" 7 | ) 8 | 9 | // ColumnConfig represents a config for table's column. 10 | // This has a name, data type and some options. 11 | type ColumnConfig interface { 12 | serializable 13 | 14 | toColumn(Table) Column 15 | Name() string 16 | Type() ColumnType 17 | Option() *ColumnOption 18 | } 19 | 20 | // ColumnType reprecents a type of column. 21 | // Dialects handle this for know column options. 22 | type ColumnType int 23 | 24 | const ( 25 | ColumnTypeAny ColumnType = iota 26 | ColumnTypeInt 27 | ColumnTypeString 28 | ColumnTypeDate 29 | ColumnTypeFloat 30 | ColumnTypeBool 31 | ColumnTypeBytes 32 | ) 33 | 34 | func (t ColumnType) String() string { 35 | switch t { 36 | case ColumnTypeInt: 37 | return "int" 38 | case ColumnTypeString: 39 | return "string" 40 | case ColumnTypeDate: 41 | return "date" 42 | case ColumnTypeFloat: 43 | return "float" 44 | case ColumnTypeBool: 45 | return "bool" 46 | case ColumnTypeBytes: 47 | return "bytes" 48 | case ColumnTypeAny: 49 | return "any" 50 | } 51 | panic(newError("unknown columnType")) 52 | } 53 | 54 | func (t ColumnType) CapableTypes() []reflect.Type { 55 | switch t { 56 | case ColumnTypeInt: 57 | return []reflect.Type{ 58 | reflect.TypeOf(int(0)), 59 | reflect.TypeOf(int8(0)), 60 | reflect.TypeOf(int16(0)), 61 | reflect.TypeOf(int32(0)), 62 | reflect.TypeOf(int64(0)), 63 | reflect.TypeOf(uint(0)), 64 | reflect.TypeOf(uint8(0)), 65 | reflect.TypeOf(uint16(0)), 66 | reflect.TypeOf(uint32(0)), 67 | reflect.TypeOf(uint64(0)), 68 | } 69 | case ColumnTypeString: 70 | return []reflect.Type{ 71 | reflect.TypeOf(""), 72 | } 73 | case ColumnTypeDate: 74 | return []reflect.Type{ 75 | reflect.TypeOf(time.Time{}), 76 | } 77 | case ColumnTypeFloat: 78 | return []reflect.Type{ 79 | reflect.TypeOf(float32(0)), 80 | reflect.TypeOf(float64(0)), 81 | } 82 | case ColumnTypeBool: 83 | return []reflect.Type{ 84 | reflect.TypeOf(bool(true)), 85 | } 86 | case ColumnTypeBytes: 87 | return []reflect.Type{ 88 | reflect.TypeOf([]byte{}), 89 | } 90 | case ColumnTypeAny: 91 | return []reflect.Type{} // but accept all types 92 | } 93 | return []reflect.Type{} 94 | } 95 | 96 | // ColumnOption represents option for a column. ex: primary key. 97 | type ColumnOption struct { 98 | PrimaryKey bool 99 | NotNull bool 100 | Unique bool 101 | AutoIncrement bool 102 | Size int 103 | SqlType string 104 | Default interface{} 105 | } 106 | 107 | // ColumnList represents list of Column. 108 | type ColumnList []Column 109 | 110 | // Column represents a table column. 111 | type Column interface { 112 | serializable 113 | 114 | column_name() string 115 | config() ColumnConfig 116 | acceptType(interface{}) bool 117 | 118 | // As creates Column alias. 119 | As(alias string) Column 120 | 121 | // Eq creates Condition for "column==right". Type for right is column's one or other Column. 122 | Eq(right interface{}) Condition 123 | 124 | // NotEq creates Condition for "column<>right". Type for right is column's one or other Column. 125 | NotEq(right interface{}) Condition 126 | 127 | // GtEq creates Condition for "column>right". Type for right is column's one or other Column. 128 | Gt(right interface{}) Condition 129 | 130 | // GtEq creates Condition for "column>=right". Type for right is column's one or other Column. 131 | GtEq(right interface{}) Condition 132 | 133 | // Lt creates Condition for "column") 292 | } 293 | 294 | func (left *columnImpl) Gt(right interface{}) Condition { 295 | return newBinaryOperationCondition(left, right, ">") 296 | } 297 | 298 | func (left *columnImpl) GtEq(right interface{}) Condition { 299 | return newBinaryOperationCondition(left, right, ">=") 300 | } 301 | 302 | func (left *columnImpl) Lt(right interface{}) Condition { 303 | return newBinaryOperationCondition(left, right, "<") 304 | } 305 | 306 | func (left *columnImpl) LtEq(right interface{}) Condition { 307 | return newBinaryOperationCondition(left, right, "<=") 308 | } 309 | 310 | func (left *columnImpl) Like(right string) Condition { 311 | return newBinaryOperationCondition(left, right, " LIKE ") 312 | } 313 | 314 | func (left *columnImpl) Between(lower, higher interface{}) Condition { 315 | return newBetweenCondition(left, lower, higher) 316 | } 317 | 318 | func (left *columnImpl) In(val ...interface{}) Condition { 319 | return newInCondition(left, val...) 320 | } 321 | 322 | func (b ColumnList) serialize(bldr *builder) { 323 | first := true 324 | for _, column := range b { 325 | if column == nil { 326 | bldr.SetError(newError("column is not found.")) 327 | return 328 | } 329 | if first { 330 | first = false 331 | } else { 332 | bldr.Append(", ") 333 | } 334 | bldr.Append(dialect().QuoteField(column.column_name())) 335 | } 336 | return 337 | } 338 | 339 | type errorColumn struct { 340 | err error 341 | } 342 | 343 | func newErrorColumn(err error) Column { 344 | return &errorColumn{ 345 | err: err, 346 | } 347 | } 348 | 349 | func (m *errorColumn) column_name() string { 350 | return "" 351 | } 352 | 353 | func (m *errorColumn) config() ColumnConfig { 354 | return nil 355 | } 356 | 357 | func (m *errorColumn) acceptType(interface{}) bool { 358 | return false 359 | } 360 | 361 | func (m *errorColumn) serialize(bldr *builder) { 362 | bldr.SetError(m.err) 363 | return 364 | } 365 | 366 | func (m *errorColumn) As(string) Column { 367 | return m 368 | } 369 | 370 | func (left *errorColumn) Eq(right interface{}) Condition { 371 | return newBinaryOperationCondition(left, right, "=") 372 | } 373 | 374 | func (left *errorColumn) NotEq(right interface{}) Condition { 375 | return newBinaryOperationCondition(left, right, "<>") 376 | } 377 | 378 | func (left *errorColumn) Gt(right interface{}) Condition { 379 | return newBinaryOperationCondition(left, right, ">") 380 | } 381 | 382 | func (left *errorColumn) GtEq(right interface{}) Condition { 383 | return newBinaryOperationCondition(left, right, ">=") 384 | } 385 | 386 | func (left *errorColumn) Lt(right interface{}) Condition { 387 | return newBinaryOperationCondition(left, right, "<") 388 | } 389 | 390 | func (left *errorColumn) LtEq(right interface{}) Condition { 391 | return newBinaryOperationCondition(left, right, "<=") 392 | } 393 | 394 | func (left *errorColumn) Like(right string) Condition { 395 | return newBinaryOperationCondition(left, right, " LIKE ") 396 | } 397 | 398 | func (left *errorColumn) Between(lower, higher interface{}) Condition { 399 | return newBetweenCondition(left, lower, higher) 400 | } 401 | 402 | func (left *errorColumn) In(val ...interface{}) Condition { 403 | return newInCondition(left, val...) 404 | } 405 | 406 | type aliasColumn struct { 407 | column Column 408 | alias string 409 | } 410 | 411 | func (m *aliasColumn) column_name() string { 412 | return m.alias 413 | } 414 | 415 | func (m *aliasColumn) config() ColumnConfig { 416 | return m.column.config() 417 | } 418 | 419 | func (m *aliasColumn) acceptType(val interface{}) bool { 420 | return m.column.acceptType(val) 421 | } 422 | 423 | func (m *aliasColumn) As(alias string) Column { 424 | return &aliasColumn{ 425 | column: m, 426 | alias: alias, 427 | } 428 | } 429 | 430 | func (m *aliasColumn) serialize(bldr *builder) { 431 | bldr.Append(dialect().QuoteField(m.alias)) 432 | return 433 | } 434 | 435 | func (m *aliasColumn) column_alias() string { 436 | return m.alias 437 | } 438 | 439 | func (m *aliasColumn) source() Column { 440 | return m.column 441 | } 442 | 443 | func (left *aliasColumn) Eq(right interface{}) Condition { 444 | return newBinaryOperationCondition(left, right, "=") 445 | } 446 | 447 | func (left *aliasColumn) NotEq(right interface{}) Condition { 448 | return newBinaryOperationCondition(left, right, "<>") 449 | } 450 | 451 | func (left *aliasColumn) Gt(right interface{}) Condition { 452 | return newBinaryOperationCondition(left, right, ">") 453 | } 454 | 455 | func (left *aliasColumn) GtEq(right interface{}) Condition { 456 | return newBinaryOperationCondition(left, right, ">=") 457 | } 458 | 459 | func (left *aliasColumn) Lt(right interface{}) Condition { 460 | return newBinaryOperationCondition(left, right, "<") 461 | } 462 | 463 | func (left *aliasColumn) LtEq(right interface{}) Condition { 464 | return newBinaryOperationCondition(left, right, "<=") 465 | } 466 | 467 | func (left *aliasColumn) Like(right string) Condition { 468 | return newBinaryOperationCondition(left, right, " LIKE ") 469 | } 470 | 471 | func (left *aliasColumn) Between(lower, higher interface{}) Condition { 472 | return newBetweenCondition(left, lower, higher) 473 | } 474 | 475 | func (left *aliasColumn) In(val ...interface{}) Condition { 476 | return newInCondition(left, val...) 477 | } 478 | -------------------------------------------------------------------------------- /column_test.go: -------------------------------------------------------------------------------- 1 | package sqlbuilder 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestColumnImplements(t *testing.T) { 9 | fnImplColumn := func(i interface{}) bool { 10 | return reflect.TypeOf(i).Implements(reflect.TypeOf(new(Column)).Elem()) 11 | } 12 | if !fnImplColumn(&columnImpl{}) { 13 | t.Errorf("fail") 14 | } 15 | if !fnImplColumn(&errorColumn{}) { 16 | t.Errorf("fail") 17 | } 18 | if !fnImplColumn(&aliasColumn{}) { 19 | t.Errorf("fail") 20 | } 21 | } 22 | 23 | func TestColumnOptionImpl(t *testing.T) { 24 | if !reflect.DeepEqual(&columnConfigImpl{ 25 | name: "name", 26 | typ: ColumnTypeBytes, 27 | opt: &ColumnOption{ 28 | Unique: true, 29 | }}, newColumnConfigImpl("name", ColumnTypeBytes, &ColumnOption{Unique: true})) { 30 | t.Errorf("fail") 31 | } 32 | if !reflect.DeepEqual(&columnConfigImpl{ 33 | name: "name", 34 | typ: ColumnTypeBytes, 35 | opt: &ColumnOption{}, 36 | }, newColumnConfigImpl("name", ColumnTypeBytes, nil)) { 37 | t.Errorf("fail") 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /condition.go: -------------------------------------------------------------------------------- 1 | package sqlbuilder 2 | 3 | // Condition represents a condition for WHERE clause and other. 4 | type Condition interface { 5 | serializable 6 | 7 | columns() []Column 8 | } 9 | 10 | type connectCondition struct { 11 | connector string 12 | conds []Condition 13 | } 14 | 15 | func (c *connectCondition) serialize(bldr *builder) { 16 | first := true 17 | for _, cond := range c.conds { 18 | if first { 19 | first = false 20 | } else { 21 | bldr.Append(" " + c.connector + " ") 22 | } 23 | if _, ok := cond.(*connectCondition); ok { 24 | // if condition is AND or OR 25 | bldr.Append("( ") 26 | bldr.AppendItem(cond) 27 | bldr.Append(" )") 28 | } else { 29 | bldr.AppendItem(cond) 30 | } 31 | } 32 | return 33 | } 34 | 35 | func (c *connectCondition) columns() []Column { 36 | list := make([]Column, 0) 37 | for _, cond := range c.conds { 38 | list = append(list, cond.columns()...) 39 | } 40 | return list 41 | } 42 | 43 | // And creates a combined condition with "AND" operator. 44 | func And(conds ...Condition) Condition { 45 | return &connectCondition{ 46 | connector: "AND", 47 | conds: conds, 48 | } 49 | } 50 | 51 | // And creates a combined condition with "OR" operator. 52 | func Or(conds ...Condition) Condition { 53 | return &connectCondition{ 54 | connector: "OR", 55 | conds: conds, 56 | } 57 | } 58 | 59 | type binaryOperationCondition struct { 60 | left serializable 61 | right serializable 62 | operator string 63 | err error 64 | } 65 | 66 | func newBinaryOperationCondition(left, right interface{}, operator string) *binaryOperationCondition { 67 | cond := &binaryOperationCondition{ 68 | operator: operator, 69 | } 70 | column_exist := false 71 | switch t := left.(type) { 72 | case Column: 73 | column_exist = true 74 | cond.left = t 75 | case nil: 76 | cond.err = newError("left-hand side of binary operator is null.") 77 | default: 78 | cond.left = toLiteral(t) 79 | } 80 | switch t := right.(type) { 81 | case Column: 82 | column_exist = true 83 | cond.right = t 84 | default: 85 | cond.right = toLiteral(t) 86 | } 87 | if !column_exist { 88 | cond.err = newError("binary operation is need column.") 89 | } 90 | 91 | return cond 92 | } 93 | 94 | func newBetweenCondition(left Column, low, high interface{}) Condition { 95 | low_literal := toLiteral(low) 96 | high_literal := toLiteral(high) 97 | 98 | return &betweenCondition{ 99 | left: left, 100 | lower: low_literal, 101 | higher: high_literal, 102 | } 103 | } 104 | 105 | func (c *binaryOperationCondition) serialize(bldr *builder) { 106 | bldr.AppendItem(c.left) 107 | 108 | switch t := c.right.(type) { 109 | case literal: 110 | if t.IsNil() { 111 | switch c.operator { 112 | case "=": 113 | bldr.Append(" IS ") 114 | case "<>": 115 | bldr.Append(" IS NOT ") 116 | default: 117 | bldr.SetError(newError("NULL can not be used with %s operator.", c.operator)) 118 | } 119 | bldr.Append("NULL") 120 | } else { 121 | bldr.Append(c.operator) 122 | bldr.AppendItem(c.right) 123 | } 124 | default: 125 | bldr.Append(c.operator) 126 | bldr.AppendItem(c.right) 127 | } 128 | return 129 | } 130 | 131 | func (c *binaryOperationCondition) columns() []Column { 132 | list := make([]Column, 0) 133 | if col, ok := c.left.(Column); ok { 134 | list = append(list, col) 135 | } 136 | if col, ok := c.right.(Column); ok { 137 | list = append(list, col) 138 | } 139 | return list 140 | } 141 | 142 | type betweenCondition struct { 143 | left serializable 144 | lower serializable 145 | higher serializable 146 | } 147 | 148 | func (c *betweenCondition) serialize(bldr *builder) { 149 | bldr.AppendItem(c.left) 150 | bldr.Append(" BETWEEN ") 151 | bldr.AppendItem(c.lower) 152 | bldr.Append(" AND ") 153 | bldr.AppendItem(c.higher) 154 | return 155 | } 156 | 157 | func (c *betweenCondition) columns() []Column { 158 | list := make([]Column, 0) 159 | if col, ok := c.left.(Column); ok { 160 | list = append(list, col) 161 | } 162 | if col, ok := c.lower.(Column); ok { 163 | list = append(list, col) 164 | } 165 | if col, ok := c.higher.(Column); ok { 166 | list = append(list, col) 167 | } 168 | return list 169 | } 170 | 171 | type inCondition struct { 172 | left serializable 173 | in []serializable 174 | } 175 | 176 | func newInCondition(left Column, list ...interface{}) Condition { 177 | m := &inCondition{ 178 | left: left, 179 | in: make([]serializable, 0, len(list)), 180 | } 181 | for _, item := range list { 182 | if c, ok := item.(Column); ok { 183 | m.in = append(m.in, c) 184 | } else { 185 | m.in = append(m.in, toLiteral(item)) 186 | } 187 | } 188 | return m 189 | } 190 | 191 | func (c *inCondition) serialize(bldr *builder) { 192 | bldr.AppendItem(c.left) 193 | bldr.Append(" IN ( ") 194 | bldr.AppendItems(c.in, ", ") 195 | bldr.Append(" )") 196 | } 197 | 198 | func (c *inCondition) columns() []Column { 199 | list := make([]Column, 0) 200 | if col, ok := c.left.(Column); ok { 201 | list = append(list, col) 202 | } 203 | for _, in := range c.in { 204 | if col, ok := in.(Column); ok { 205 | list = append(list, col) 206 | } 207 | } 208 | return list 209 | } 210 | -------------------------------------------------------------------------------- /condition_test.go: -------------------------------------------------------------------------------- 1 | package sqlbuilder 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestBinaryCondition(t *testing.T) { 8 | table1 := NewTable( 9 | "TABLE_A", 10 | &TableOption{}, 11 | IntColumn("id", &ColumnOption{ 12 | PrimaryKey: true, 13 | }), 14 | IntColumn("test1", nil), 15 | IntColumn("test2", nil), 16 | ) 17 | var cases = []conditionTestCase{ 18 | { 19 | cond: table1.C("id").Eq(table1.C("test1")), 20 | query: `"TABLE_A"."id"="TABLE_A"."test1"`, 21 | args: []interface{}{}, 22 | errmsg: "", 23 | }, { 24 | cond: table1.C("id").Eq(1), 25 | query: `"TABLE_A"."id"=?`, 26 | args: []interface{}{int64(1)}, 27 | errmsg: "", 28 | }, { 29 | cond: table1.C("id").NotEq(1), 30 | query: `"TABLE_A"."id"<>?`, 31 | args: []interface{}{int64(1)}, 32 | errmsg: "", 33 | }, { 34 | cond: table1.C("id").Gt(1), 35 | query: `"TABLE_A"."id">?`, 36 | args: []interface{}{int64(1)}, 37 | errmsg: "", 38 | }, { 39 | cond: table1.C("id").GtEq(1), 40 | query: `"TABLE_A"."id">=?`, 41 | args: []interface{}{int64(1)}, 42 | errmsg: "", 43 | }, { 44 | cond: table1.C("id").Lt(1), 45 | query: `"TABLE_A"."id" operator.", 83 | }, { 84 | // case for fail 85 | cond: table1.C("id").In(NewTable("DUMMY TABLE", &TableOption{}, StringColumn("id", nil))), 86 | query: `"TABLE_A"."id" IN ( `, 87 | args: []interface{}{}, 88 | errmsg: "sqlbuilder: got sqlbuilder.table type, but literal is not supporting this.", 89 | }, 90 | } 91 | 92 | for num, c := range cases { 93 | mes, args, ok := c.Run() 94 | if !ok { 95 | t.Errorf(mes+" (case no.%d)", append(args, num)...) 96 | } 97 | } 98 | } 99 | 100 | func TestBinaryConditionForSqlFunctions(t *testing.T) { 101 | table1 := NewTable( 102 | "TABLE_A", 103 | &TableOption{}, 104 | IntColumn("id", &ColumnOption{ 105 | PrimaryKey: true, 106 | }), 107 | IntColumn("test1", nil), 108 | IntColumn("test2", nil), 109 | ) 110 | var cases = []conditionTestCase{ 111 | { 112 | cond: Func("count", table1.C("id")).Eq(table1.C("test1")), 113 | query: `count("TABLE_A"."id")="TABLE_A"."test1"`, 114 | args: []interface{}{}, 115 | errmsg: "", 116 | }, { 117 | cond: Func("count", table1.C("id")).Eq(1), 118 | query: `count("TABLE_A"."id")=?`, 119 | args: []interface{}{int64(1)}, 120 | errmsg: "", 121 | }, { 122 | cond: Func("count", table1.C("id")).NotEq(1), 123 | query: `count("TABLE_A"."id")<>?`, 124 | args: []interface{}{int64(1)}, 125 | errmsg: "", 126 | }, { 127 | cond: Func("count", table1.C("id")).Gt(1), 128 | query: `count("TABLE_A"."id")>?`, 129 | args: []interface{}{int64(1)}, 130 | errmsg: "", 131 | }, { 132 | cond: Func("count", table1.C("id")).GtEq(1), 133 | query: `count("TABLE_A"."id")>=?`, 134 | args: []interface{}{int64(1)}, 135 | errmsg: "", 136 | }, { 137 | cond: Func("count", table1.C("id")).Lt(1), 138 | query: `count("TABLE_A"."id")") 73 | } 74 | 75 | func (left *sqlFuncImpl) Gt(right interface{}) Condition { 76 | return newBinaryOperationCondition(left, right, ">") 77 | } 78 | 79 | func (left *sqlFuncImpl) GtEq(right interface{}) Condition { 80 | return newBinaryOperationCondition(left, right, ">=") 81 | } 82 | 83 | func (left *sqlFuncImpl) Lt(right interface{}) Condition { 84 | return newBinaryOperationCondition(left, right, "<") 85 | } 86 | 87 | func (left *sqlFuncImpl) LtEq(right interface{}) Condition { 88 | return newBinaryOperationCondition(left, right, "<=") 89 | } 90 | 91 | func (left *sqlFuncImpl) Like(right string) Condition { 92 | return newBinaryOperationCondition(left, right, " LIKE ") 93 | } 94 | 95 | func (left *sqlFuncImpl) Between(lower, higher interface{}) Condition { 96 | return newBetweenCondition(left, lower, higher) 97 | } 98 | 99 | func (left *sqlFuncImpl) In(vals ...interface{}) Condition { 100 | return newInCondition(left, vals...) 101 | } 102 | 103 | func (m *sqlFuncImpl) columns() []Column { 104 | return m.args 105 | } 106 | -------------------------------------------------------------------------------- /sqlfunc_test.go: -------------------------------------------------------------------------------- 1 | package sqlbuilder 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestSqlFuncImplements(t *testing.T) { 9 | fnImplColumn := func(i interface{}) bool { 10 | return reflect.TypeOf(i).Implements(reflect.TypeOf(new(Column)).Elem()) 11 | } 12 | fnImplColumn(&columnImpl{}) 13 | } 14 | 15 | func TestSqlFunc(t *testing.T) { 16 | b := newBuilder() 17 | table1 := NewTable( 18 | "TABLE_A", 19 | &TableOption{}, 20 | IntColumn("id", &ColumnOption{ 21 | PrimaryKey: true, 22 | }), 23 | IntColumn("test1", nil), 24 | IntColumn("test2", nil), 25 | ) 26 | 27 | Func("funcname", table1.C("id")).serialize(b) 28 | if `funcname("TABLE_A"."id")` != b.query.String() { 29 | t.Errorf("failed") 30 | } 31 | if len(b.Args()) != 0 { 32 | t.Errorf("failed") 33 | } 34 | if b.Err() != nil { 35 | t.Errorf("failed") 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /table.go: -------------------------------------------------------------------------------- 1 | package sqlbuilder 2 | 3 | type joinType int 4 | 5 | const ( 6 | inner_join joinType = iota 7 | left_outer_join 8 | right_outer_join 9 | full_outer_join 10 | ) 11 | 12 | type table struct { 13 | name string 14 | option *TableOption 15 | columns []Column 16 | } 17 | 18 | // TableOption reprecents constraint of a table. 19 | type TableOption struct { 20 | Unique [][]string 21 | //ForeignKey map[string]Column // will implement future 22 | } 23 | 24 | type joinTable struct { 25 | typ joinType 26 | left Table 27 | right Table 28 | on Condition 29 | } 30 | 31 | // Table represents a table. 32 | type Table interface { 33 | serializable 34 | 35 | // C returns table's column by the name. 36 | C(name string) Column 37 | 38 | // Name returns table' name. 39 | // returns empty if it is joined table or subquery. 40 | Name() string 41 | 42 | // Option returns table's option(table constraint). 43 | // returns nil if it is joined table or subquery. 44 | Option() *TableOption 45 | 46 | // Columns returns all columns. 47 | Columns() []Column 48 | 49 | // InnerJoin returns a joined table use with "INNER JOIN" clause. 50 | // The joined table can be handled in same way as single table. 51 | InnerJoin(Table, Condition) Table 52 | 53 | // LeftOuterJoin returns a joined table use with "LEFT OUTER JOIN" clause. 54 | // The joined table can be handled in same way as single table. 55 | LeftOuterJoin(Table, Condition) Table 56 | 57 | // RightOuterJoin returns a joined table use with "RIGHT OUTER JOIN" clause. 58 | // The joined table can be handled in same way as single table. 59 | RightOuterJoin(Table, Condition) Table 60 | 61 | // FullOuterJoin returns a joined table use with "FULL OUTER JOIN" clause. 62 | // The joined table can be handled in same way as single table. 63 | FullOuterJoin(Table, Condition) Table 64 | 65 | hasColumn(column Column) bool 66 | } 67 | 68 | // NewTable returns a new table named by the name. Specify table columns by the column_config. 69 | // Panic if column is empty. 70 | func NewTable(name string, option *TableOption, column_configs ...ColumnConfig) Table { 71 | if len(column_configs) == 0 { 72 | panic(newError("column is needed.")) 73 | } 74 | if option == nil { 75 | option = &TableOption{} 76 | } 77 | 78 | t := &table{ 79 | name: name, 80 | option: option, 81 | columns: make([]Column, 0, len(column_configs)), 82 | } 83 | 84 | for _, column_config := range column_configs { 85 | err := t.AddColumnLast(column_config) 86 | if err != nil { 87 | panic(err) 88 | } 89 | } 90 | 91 | return t 92 | } 93 | 94 | func (m *table) serialize(bldr *builder) { 95 | bldr.Append(dialect().QuoteField(m.name)) 96 | return 97 | } 98 | 99 | func (m *table) C(name string) Column { 100 | for _, column := range m.columns { 101 | if column.column_name() == name { 102 | return column 103 | } 104 | } 105 | 106 | return newErrorColumn(newError("column %s.%s was not found.", m.name, name)) 107 | } 108 | 109 | func (m *table) Name() string { 110 | return m.name 111 | } 112 | 113 | func (m *table) SetName(name string) { 114 | m.name = name 115 | } 116 | 117 | func (m *table) Columns() []Column { 118 | return m.columns 119 | } 120 | 121 | func (m *table) Option() *TableOption { 122 | return m.option 123 | } 124 | 125 | func (m *table) AddColumnLast(cc ColumnConfig) error { 126 | return m.addColumn(cc, len(m.columns)) 127 | } 128 | 129 | func (m *table) AddColumnFirst(cc ColumnConfig) error { 130 | return m.addColumn(cc, 0) 131 | } 132 | 133 | func (m *table) AddColumnAfter(cc ColumnConfig, after Column) error { 134 | for i := range m.columns { 135 | if m.columns[i] == after { 136 | return m.addColumn(cc, i+1) 137 | } 138 | } 139 | return newError("column not found.") 140 | } 141 | 142 | func (m *table) ChangeColumn(trg Column, cc ColumnConfig) error { 143 | for i := range m.columns { 144 | if m.columns[i] == trg { 145 | err := m.dropColumn(i) 146 | if err != nil { 147 | return err 148 | } 149 | err = m.addColumn(cc, i) 150 | if err != nil { 151 | return err 152 | } 153 | return nil 154 | } 155 | } 156 | return newError("column not found.") 157 | } 158 | 159 | func (m *table) ChangeColumnFirst(trg Column, cc ColumnConfig) error { 160 | for i := range m.columns { 161 | if m.columns[i] == trg { 162 | err := m.dropColumn(i) 163 | if err != nil { 164 | return err 165 | } 166 | err = m.addColumn(cc, 0) 167 | if err != nil { 168 | return err 169 | } 170 | return nil 171 | } 172 | } 173 | return newError("column not found.") 174 | } 175 | 176 | func (m *table) ChangeColumnAfter(trg Column, cc ColumnConfig, after Column) error { 177 | backup := make([]Column, len(m.columns)) 178 | copy(backup, m.columns) 179 | found := false 180 | for i := range m.columns { 181 | if m.columns[i] == trg { 182 | err := m.dropColumn(i) 183 | if err != nil { 184 | m.columns = backup 185 | return err 186 | } 187 | found = true 188 | break 189 | } 190 | } 191 | if !found { 192 | return newError("column not found.") 193 | } 194 | for i := range m.columns { 195 | if m.columns[i] == after { 196 | err := m.addColumn(cc, i+1) 197 | if err != nil { 198 | m.columns = backup 199 | return err 200 | } 201 | return nil 202 | } 203 | } 204 | m.columns = backup 205 | return newError("column not found.") 206 | } 207 | 208 | func (m *table) addColumn(cc ColumnConfig, pos int) error { 209 | if len(m.columns) < pos || pos < 0 { 210 | return newError("Invalid position.") 211 | } 212 | 213 | var ( 214 | u = make([]Column, pos) 215 | p = make([]Column, len(m.columns)-pos) 216 | ) 217 | copy(u, m.columns[:pos]) 218 | copy(p, m.columns[pos:]) 219 | c := cc.toColumn(m) 220 | m.columns = append(u, c) 221 | m.columns = append(m.columns, p...) 222 | return nil 223 | } 224 | 225 | func (m *table) DropColumn(col Column) error { 226 | for i := range m.columns { 227 | if m.columns[i] == col { 228 | return m.dropColumn(i) 229 | } 230 | } 231 | return newError("column not found.") 232 | } 233 | 234 | func (m *table) dropColumn(pos int) error { 235 | if len(m.columns) < pos || pos < 0 { 236 | return newError("Invalid position.") 237 | } 238 | var ( 239 | u = make([]Column, pos) 240 | p = make([]Column, len(m.columns)-pos-1) 241 | ) 242 | copy(u, m.columns[:pos]) 243 | if len(m.columns) > pos+1 { 244 | copy(p, m.columns[pos+1:]) 245 | } 246 | m.columns = append(u, p...) 247 | return nil 248 | } 249 | 250 | func (m *table) InnerJoin(right Table, on Condition) Table { 251 | return &joinTable{ 252 | left: m, 253 | right: right, 254 | typ: inner_join, 255 | on: on, 256 | } 257 | } 258 | 259 | func (m *table) LeftOuterJoin(right Table, on Condition) Table { 260 | return &joinTable{ 261 | left: m, 262 | right: right, 263 | typ: left_outer_join, 264 | on: on, 265 | } 266 | } 267 | 268 | func (m *table) RightOuterJoin(right Table, on Condition) Table { 269 | return &joinTable{ 270 | left: m, 271 | right: right, 272 | typ: right_outer_join, 273 | on: on, 274 | } 275 | } 276 | 277 | func (m *table) FullOuterJoin(right Table, on Condition) Table { 278 | return &joinTable{ 279 | left: m, 280 | right: right, 281 | typ: full_outer_join, 282 | on: on, 283 | } 284 | } 285 | 286 | func (m *table) hasColumn(trg Column) bool { 287 | if cimpl, ok := trg.(*columnImpl); ok { 288 | if trg == Star { 289 | return true 290 | } 291 | for _, col := range m.columns { 292 | if col == cimpl { 293 | return true 294 | } 295 | } 296 | return false 297 | } 298 | if acol, ok := trg.(*aliasColumn); ok { 299 | for _, col := range m.columns { 300 | if col == acol.column { 301 | return true 302 | } 303 | } 304 | return false 305 | } 306 | if sqlfn, ok := trg.(*sqlFuncImpl); ok { 307 | for _, fncol := range sqlfn.columns() { 308 | find := false 309 | for _, col := range m.columns { 310 | if col == fncol { 311 | find = true 312 | } 313 | } 314 | if !find { 315 | return false 316 | } 317 | } 318 | return true 319 | } 320 | return false 321 | } 322 | 323 | func (m *joinTable) C(name string) Column { 324 | l_col := m.left.C(name) 325 | r_col := m.right.C(name) 326 | 327 | _, l_err := l_col.(*errorColumn) 328 | _, r_err := r_col.(*errorColumn) 329 | 330 | switch { 331 | case l_err && r_err: 332 | return newErrorColumn(newError("column %s was not found.", name)) 333 | case l_err && !r_err: 334 | return r_col 335 | case !l_err && r_err: 336 | return l_col 337 | default: 338 | return newErrorColumn(newError("column %s was duplicated.", name)) 339 | } 340 | } 341 | 342 | func (m *joinTable) Name() string { 343 | return "" 344 | } 345 | 346 | func (m *joinTable) Columns() []Column { 347 | return append(m.left.Columns(), m.right.Columns()...) 348 | } 349 | 350 | func (m *joinTable) Option() *TableOption { 351 | return nil 352 | } 353 | 354 | func (m *joinTable) InnerJoin(right Table, on Condition) Table { 355 | return &joinTable{ 356 | left: m, 357 | right: right, 358 | typ: inner_join, 359 | on: on, 360 | } 361 | } 362 | 363 | func (m *joinTable) LeftOuterJoin(right Table, on Condition) Table { 364 | return &joinTable{ 365 | left: m, 366 | right: right, 367 | typ: left_outer_join, 368 | on: on, 369 | } 370 | } 371 | 372 | func (m *joinTable) RightOuterJoin(right Table, on Condition) Table { 373 | return &joinTable{ 374 | left: m, 375 | right: right, 376 | typ: right_outer_join, 377 | on: on, 378 | } 379 | } 380 | 381 | func (m *joinTable) FullOuterJoin(right Table, on Condition) Table { 382 | return &joinTable{ 383 | left: m, 384 | right: right, 385 | typ: full_outer_join, 386 | on: on, 387 | } 388 | } 389 | 390 | func (m *joinTable) serialize(bldr *builder) { 391 | bldr.AppendItem(m.left) 392 | switch m.typ { 393 | case inner_join: 394 | bldr.Append(" INNER JOIN ") 395 | case left_outer_join: 396 | bldr.Append(" LEFT OUTER JOIN ") 397 | case right_outer_join: 398 | bldr.Append(" RIGHT OUTER JOIN ") 399 | case full_outer_join: 400 | bldr.Append(" FULL OUTER JOIN ") 401 | } 402 | bldr.AppendItem(m.right) 403 | bldr.Append(" ON ") 404 | bldr.AppendItem(m.on) 405 | return 406 | } 407 | 408 | func (m *joinTable) hasColumn(trg Column) bool { 409 | if m.left.hasColumn(trg) { 410 | return true 411 | } 412 | if m.right.hasColumn(trg) { 413 | return true 414 | } 415 | return false 416 | } 417 | -------------------------------------------------------------------------------- /table_test.go: -------------------------------------------------------------------------------- 1 | package sqlbuilder 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestTable(t *testing.T) { 9 | var table1 Table 10 | var fnPanic = func(fn func()) (ok bool) { 11 | defer func() { 12 | if r := recover(); r != nil { 13 | ok = true 14 | } else { 15 | ok = false 16 | } 17 | }() 18 | fn() 19 | return 20 | } 21 | 22 | // be panic 23 | if !fnPanic(func() { 24 | table1 = NewTable( 25 | "TABLE_NAME", 26 | &TableOption{}, 27 | ) 28 | }) { 29 | t.Errorf("failed") 30 | } 31 | if table1 != nil { 32 | t.Errorf("failed") 33 | } 34 | 35 | // not panic 36 | if fnPanic(func() { 37 | table1 = NewTable( 38 | "TABLE_NAME", 39 | &TableOption{}, 40 | IntColumn("id", nil), 41 | ) 42 | }) { 43 | t.Errorf("failed") 44 | } 45 | if table1 == nil { 46 | t.Errorf("failed") 47 | } 48 | 49 | // not panic 50 | if fnPanic(func() { 51 | table1 = NewTable( 52 | "TABLE_NAME", 53 | nil, 54 | IntColumn("id", nil), 55 | ) 56 | }) { 57 | t.Errorf("failed") 58 | } 59 | if table1 == nil { 60 | t.Errorf("failed") 61 | } 62 | 63 | // not panic 64 | if fnPanic(func() { 65 | table1 = NewTable( 66 | "TABLE_NAME", 67 | nil, 68 | IntColumn("id", nil), 69 | ) 70 | }) { 71 | t.Errorf("failed") 72 | } 73 | if table1 == nil { 74 | t.Errorf("failed") 75 | } 76 | } 77 | 78 | func TestJoinTable(t *testing.T) { 79 | l_table := NewTable( 80 | "LEFT_TABLE", 81 | &TableOption{}, 82 | IntColumn("id", &ColumnOption{ 83 | PrimaryKey: true, 84 | }), 85 | IntColumn("right_id", nil), 86 | ) 87 | r_table := NewTable( 88 | "RIGHT_TABLE", 89 | &TableOption{}, 90 | IntColumn("id", &ColumnOption{ 91 | PrimaryKey: true, 92 | }), 93 | IntColumn("value", nil), 94 | ) 95 | rr_table := NewTable( 96 | "RIGHTRIGHT_TABLE", 97 | &TableOption{}, 98 | IntColumn("id", &ColumnOption{ 99 | PrimaryKey: true, 100 | }), 101 | ) 102 | 103 | // inner join 104 | b := newBuilder() 105 | joinedTable := l_table.InnerJoin(r_table, l_table.C("right_id").Eq(r_table.C("id"))) 106 | joinedTable.serialize(b) 107 | if `"LEFT_TABLE" INNER JOIN "RIGHT_TABLE" ON "LEFT_TABLE"."right_id"="RIGHT_TABLE"."id"` != b.query.String() { 108 | t.Error("failed") 109 | } 110 | if b.err != nil { 111 | t.Error("failed") 112 | } 113 | if len(b.args) != 0 { 114 | t.Error("failed") 115 | } 116 | 117 | // left outer join 118 | b = newBuilder() 119 | joinedTable = l_table.LeftOuterJoin(r_table, l_table.C("right_id").Eq(r_table.C("id"))) 120 | joinedTable.serialize(b) 121 | if `"LEFT_TABLE" LEFT OUTER JOIN "RIGHT_TABLE" ON "LEFT_TABLE"."right_id"="RIGHT_TABLE"."id"` != b.query.String() { 122 | t.Error("failed") 123 | } 124 | if b.err != nil { 125 | t.Error("failed") 126 | } 127 | if len(b.args) != 0 { 128 | t.Error("failed") 129 | } 130 | 131 | // right outer join 132 | b = newBuilder() 133 | joinedTable = l_table.RightOuterJoin(r_table, l_table.C("right_id").Eq(r_table.C("id"))) 134 | joinedTable.serialize(b) 135 | if `"LEFT_TABLE" RIGHT OUTER JOIN "RIGHT_TABLE" ON "LEFT_TABLE"."right_id"="RIGHT_TABLE"."id"` != b.query.String() { 136 | t.Error("failed") 137 | } 138 | if b.err != nil { 139 | t.Error("failed") 140 | } 141 | if len(b.args) != 0 { 142 | t.Error("failed") 143 | } 144 | 145 | // full outer join 146 | b = newBuilder() 147 | joinedTable = l_table.FullOuterJoin(r_table, l_table.C("right_id").Eq(r_table.C("id"))) 148 | joinedTable.serialize(b) 149 | if `"LEFT_TABLE" FULL OUTER JOIN "RIGHT_TABLE" ON "LEFT_TABLE"."right_id"="RIGHT_TABLE"."id"` != b.query.String() { 150 | t.Error("failed") 151 | } 152 | if b.err != nil { 153 | t.Error("failed") 154 | } 155 | if len(b.args) != 0 { 156 | t.Error("failed") 157 | } 158 | 159 | // joined table column 160 | if !reflect.DeepEqual(l_table.C("right_id"), joinedTable.C("right_id")) { 161 | t.Error("failed") 162 | } 163 | if !reflect.DeepEqual(r_table.C("value"), joinedTable.C("value")) { 164 | t.Error("failed") 165 | } 166 | if _, ok := joinedTable.C("not_exist_column").(*errorColumn); !ok { 167 | t.Error("failed") 168 | } 169 | if _, ok := joinedTable.C("id").(*errorColumn); !ok { 170 | t.Error("failed") 171 | } 172 | 173 | // combination 174 | b = newBuilder() 175 | joinedTable = l_table.InnerJoin(r_table, l_table.C("right_id").Eq(r_table.C("id"))).InnerJoin(rr_table, l_table.C("right_id").Eq(rr_table.C("id"))) 176 | joinedTable.serialize(b) 177 | if `"LEFT_TABLE" INNER JOIN "RIGHT_TABLE" ON "LEFT_TABLE"."right_id"="RIGHT_TABLE"."id" INNER JOIN "RIGHTRIGHT_TABLE" ON "LEFT_TABLE"."right_id"="RIGHTRIGHT_TABLE"."id"` != b.query.String() { 178 | t.Error("failed") 179 | } 180 | if b.err != nil { 181 | t.Error("failed") 182 | } 183 | if len(b.args) != 0 { 184 | t.Error("failed") 185 | } 186 | } 187 | 188 | func TestTableColumnOperation(t *testing.T) { 189 | var fnEqualColumnName = func(cols []Column, expect []string) bool { 190 | if len(cols) != len(expect) { 191 | return false 192 | } 193 | for i, col := range cols { 194 | if col.column_name() != expect[i] { 195 | return false 196 | } 197 | } 198 | return true 199 | } 200 | 201 | table1 := NewTable( 202 | "TABLE_NAME", 203 | nil, 204 | IntColumn("id", nil), 205 | ).(*table) 206 | 207 | // initial check 208 | if !fnEqualColumnName(table1.Columns(), []string{"id"}) { 209 | t.Error("failed") 210 | } 211 | 212 | // AddColumnLast 213 | err := table1.AddColumnLast(IntColumn("test1", nil)) 214 | if err != nil { 215 | t.Error("failed") 216 | } 217 | if !fnEqualColumnName(table1.Columns(), []string{"id", "test1"}) { 218 | t.Error("failed") 219 | } 220 | 221 | // AddColumnFirst 222 | err = table1.AddColumnFirst(IntColumn("first", nil)) 223 | if err != nil { 224 | t.Error("failed") 225 | } 226 | if !fnEqualColumnName(table1.Columns(), []string{"first", "id", "test1"}) { 227 | t.Error("failed") 228 | } 229 | 230 | // AddColumnAfter 231 | err = table1.AddColumnAfter(IntColumn("second", nil), table1.C("first")) 232 | if err != nil { 233 | t.Error("failed") 234 | } 235 | err = table1.AddColumnAfter(IntColumn("aaa", nil), table1.C("invalid")) 236 | if err == nil { 237 | t.Error("failed") 238 | } 239 | if !fnEqualColumnName(table1.Columns(), []string{"first", "second", "id", "test1"}) { 240 | t.Error("failed") 241 | } 242 | 243 | // ChangeColumn 244 | err = table1.ChangeColumn(table1.C("id"), IntColumn("third", nil)) 245 | if err != nil { 246 | t.Error("failed") 247 | } 248 | err = table1.ChangeColumn(table1.C("invalid"), IntColumn("third", nil)) 249 | if err == nil { 250 | t.Error("failed") 251 | } 252 | if !fnEqualColumnName(table1.Columns(), []string{"first", "second", "third", "test1"}) { 253 | t.Error("failed") 254 | } 255 | 256 | // ChangeColumnFirst 257 | err = table1.ChangeColumnFirst(table1.C("test1"), IntColumn("new_first", nil)) 258 | if err != nil { 259 | t.Error("failed") 260 | } 261 | err = table1.ChangeColumnFirst(table1.C("invalid"), IntColumn("new_first", nil)) 262 | if err == nil { 263 | t.Error("failed") 264 | } 265 | if !fnEqualColumnName(table1.Columns(), []string{"new_first", "first", "second", "third"}) { 266 | t.Error("failed") 267 | } 268 | 269 | // ChangeColumnAfter 270 | err = table1.ChangeColumnAfter(table1.C("new_first"), IntColumn("fourth", nil), table1.C("third")) 271 | if err != nil { 272 | t.Error("failed") 273 | } 274 | err = table1.ChangeColumnAfter(table1.C("invalid"), IntColumn("fourth", nil), table1.C("third")) 275 | if err == nil { 276 | t.Error("failed") 277 | } 278 | err = table1.ChangeColumnAfter(table1.C("second"), IntColumn("fourth", nil), table1.C("invalid")) 279 | if err == nil { 280 | t.Error("failed") 281 | } 282 | if !fnEqualColumnName(table1.Columns(), []string{"first", "second", "third", "fourth"}) { 283 | t.Error("failed") 284 | } 285 | 286 | // ChangeColumnAfter 287 | err = table1.DropColumn(table1.C("fourth")) 288 | if err != nil { 289 | t.Error("failed") 290 | } 291 | err = table1.DropColumn(table1.C("invalid")) 292 | if err == nil { 293 | t.Error("failed") 294 | } 295 | if !fnEqualColumnName(table1.Columns(), []string{"first", "second", "third"}) { 296 | t.Error("failed") 297 | } 298 | 299 | // SetName 300 | table1.SetName("TABLE_MODIFIED") 301 | if "TABLE_MODIFIED" != table1.Name() { 302 | t.Error("failed") 303 | } 304 | return 305 | } 306 | -------------------------------------------------------------------------------- /testutils_test.go: -------------------------------------------------------------------------------- 1 | package sqlbuilder 2 | 3 | import ( 4 | "reflect" 5 | ) 6 | 7 | type statementTestCase struct { 8 | stmt Statement 9 | query string 10 | args []interface{} 11 | errmsg string 12 | } 13 | 14 | func (testCase statementTestCase) Run() (message string, args []interface{}, ok bool) { 15 | query, args, err := testCase.stmt.ToSql() 16 | if len(testCase.errmsg) != 0 { 17 | if err == nil { 18 | return "error: expect returns error but got nil.", []interface{}{}, false 19 | } 20 | if err.Error() != testCase.errmsg { 21 | return "error: expect error message is '%s' but got '%s'.", []interface{}{err.Error(), testCase.errmsg}, false 22 | } 23 | } else { 24 | if err != nil { 25 | return "error: expect returns no error got %s.", []interface{}{err.Error()}, false 26 | } 27 | } 28 | if testCase.query != query { 29 | return "expect returns query \n%s \nbut got\n%s.", []interface{}{testCase.query, query}, false 30 | } 31 | if !reflect.DeepEqual(testCase.args, args) { 32 | return "expect returns arguments \n%s \nbut got\n%s.", []interface{}{testCase.args, args}, false 33 | } 34 | return "", nil, true 35 | } 36 | 37 | type conditionTestCase struct { 38 | cond Condition 39 | query string 40 | args []interface{} 41 | errmsg string 42 | } 43 | 44 | func (testCase conditionTestCase) Run() (message string, args []interface{}, ok bool) { 45 | bldr := newBuilder() 46 | testCase.cond.serialize(bldr) 47 | if len(testCase.errmsg) != 0 { 48 | if bldr.err == nil { 49 | return "error: expect returns error but got nil.", []interface{}{}, false 50 | } 51 | if bldr.err.Error() != testCase.errmsg { 52 | return "error: expect error message is '%s' but got '%s'.", []interface{}{bldr.err.Error(), testCase.errmsg}, false 53 | } 54 | } else { 55 | if bldr.err != nil { 56 | return "error: expect returns no error got %s.", []interface{}{bldr.err.Error()}, false 57 | } 58 | } 59 | if bldr.query.String() != testCase.query { 60 | return "expect returns query \n%s \nbut got\n%s.", []interface{}{testCase.query, bldr.query.String()}, false 61 | } 62 | if !reflect.DeepEqual(bldr.args, testCase.args) { 63 | return "expect returns arguments \n%s \nbut got\n%s.", []interface{}{testCase.args, args}, false 64 | } 65 | return "", nil, true 66 | } 67 | -------------------------------------------------------------------------------- /update.go: -------------------------------------------------------------------------------- 1 | package sqlbuilder 2 | 3 | // UpdateStatement represents a UPDATE statement. 4 | type UpdateStatement struct { 5 | table Table 6 | set []serializable 7 | where Condition 8 | orderBy []serializable 9 | limit int 10 | offset int 11 | 12 | err error 13 | } 14 | 15 | // Update returns new UPDATE statement. The table is Table object to update. 16 | func Update(tbl Table) *UpdateStatement { 17 | if tbl == nil { 18 | return &UpdateStatement{ 19 | err: newError("table is nil."), 20 | } 21 | } 22 | return &UpdateStatement{ 23 | table: tbl, 24 | set: make([]serializable, 0), 25 | } 26 | } 27 | 28 | // Set sets SETS clause like col=val. Call many time for update multi columns. 29 | func (b *UpdateStatement) Set(col Column, val interface{}) *UpdateStatement { 30 | if b.err != nil { 31 | return b 32 | } 33 | if !b.table.hasColumn(col) { 34 | b.err = newError("column not found in FROM.") 35 | return b 36 | } 37 | b.set = append(b.set, newUpdateValue(col, val)) 38 | return b 39 | } 40 | 41 | // Where sets WHERE clause. The cond is filter condition. 42 | func (b *UpdateStatement) Where(cond Condition) *UpdateStatement { 43 | if b.err != nil { 44 | return b 45 | } 46 | b.where = cond 47 | return b 48 | } 49 | 50 | // Limit sets LIMIT clause. 51 | func (b *UpdateStatement) Limit(limit int) *UpdateStatement { 52 | if b.err != nil { 53 | return b 54 | } 55 | b.limit = limit 56 | return b 57 | } 58 | 59 | // Limit sets OFFSET clause. 60 | func (b *UpdateStatement) Offset(offset int) *UpdateStatement { 61 | if b.err != nil { 62 | return b 63 | } 64 | b.offset = offset 65 | return b 66 | } 67 | 68 | // OrderBy sets "ORDER BY" clause. Use descending order if the desc is true, by the columns. 69 | func (b *UpdateStatement) OrderBy(desc bool, columns ...Column) *UpdateStatement { 70 | if b.err != nil { 71 | return b 72 | } 73 | if b.orderBy == nil { 74 | b.orderBy = make([]serializable, 0) 75 | } 76 | 77 | for _, c := range columns { 78 | b.orderBy = append(b.orderBy, newOrderBy(desc, c)) 79 | } 80 | return b 81 | } 82 | 83 | // ToSql generates query string, placeholder arguments, and returns err on errors. 84 | func (b *UpdateStatement) ToSql() (query string, args []interface{}, err error) { 85 | bldr := newBuilder() 86 | defer func() { 87 | query, args, err = bldr.Query(), bldr.Args(), bldr.Err() 88 | }() 89 | if b.err != nil { 90 | bldr.SetError(b.err) 91 | return 92 | } 93 | 94 | // UPDATE TABLE SET (COLUMN=VALUE) 95 | bldr.Append("UPDATE ") 96 | bldr.AppendItem(b.table) 97 | 98 | bldr.Append(" SET ") 99 | if len(b.set) != 0 { 100 | bldr.AppendItems(b.set, ", ") 101 | } else { 102 | bldr.SetError(newError("length of sets is 0.")) 103 | } 104 | 105 | // WHERE 106 | if b.where != nil { 107 | bldr.Append(" WHERE ") 108 | bldr.AppendItem(b.where) 109 | } 110 | 111 | // ORDER BY 112 | if b.orderBy != nil { 113 | bldr.Append(" ORDER BY ") 114 | bldr.AppendItems(b.orderBy, ", ") 115 | } 116 | 117 | // LIMIT 118 | if b.limit != 0 { 119 | bldr.Append(" LIMIT ") 120 | bldr.AppendValue(b.limit) 121 | } 122 | 123 | // Offset 124 | if b.offset != 0 { 125 | bldr.Append(" OFFSET ") 126 | bldr.AppendValue(b.offset) 127 | } 128 | return 129 | } 130 | 131 | type updateValue struct { 132 | col Column 133 | val literal 134 | } 135 | 136 | func newUpdateValue(col Column, val interface{}) updateValue { 137 | return updateValue{ 138 | col: col, 139 | val: toLiteral(val), 140 | } 141 | } 142 | 143 | func (m updateValue) serialize(bldr *builder) { 144 | if !m.col.acceptType(m.val) { 145 | bldr.SetError(newError("%s column not accept %T.", 146 | m.col.config().Type().String(), 147 | m.val.Raw(), 148 | )) 149 | return 150 | } 151 | 152 | bldr.Append(dialect().QuoteField(m.col.column_name())) 153 | bldr.Append("=") 154 | bldr.AppendItem(m.val) 155 | } 156 | -------------------------------------------------------------------------------- /update_test.go: -------------------------------------------------------------------------------- 1 | package sqlbuilder 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestUpdate(t *testing.T) { 8 | table1 := NewTable( 9 | "TABLE_A", 10 | &TableOption{}, 11 | IntColumn("id", &ColumnOption{ 12 | PrimaryKey: true, 13 | }), 14 | IntColumn("test1", nil), 15 | IntColumn("test2", nil), 16 | ) 17 | table2 := NewTable( 18 | "TABLE_B", 19 | &TableOption{}, 20 | IntColumn("id", &ColumnOption{ 21 | PrimaryKey: true, 22 | }), 23 | ) 24 | tableJoined := table1.InnerJoin(table2, table1.C("test1").Eq(table2.C("id"))) 25 | 26 | var cases = []statementTestCase{{ 27 | stmt: Update(table1).Where(table1.C("id").Eq(1)). 28 | Set(table1.C("test1"), 10). 29 | Set(table1.C("test2"), 20). 30 | OrderBy(true, table1.C("test1")). 31 | Limit(1). 32 | Offset(2), 33 | query: `UPDATE "TABLE_A" SET "test1"=?, "test2"=? WHERE "TABLE_A"."id"=? ORDER BY "TABLE_A"."test1" DESC LIMIT ? OFFSET ?;`, 34 | args: []interface{}{int64(10), int64(20), int64(1), 1, 2}, 35 | errmsg: "", 36 | }, { 37 | stmt: Update(table1).Where(table1.C("id").Eq(1)). 38 | Set(table1.C("test1"), 10). 39 | Set(table1.C("test2"), 20), 40 | query: `UPDATE "TABLE_A" SET "test1"=?, "test2"=? WHERE "TABLE_A"."id"=?;`, 41 | args: []interface{}{int64(10), int64(20), int64(1)}, 42 | errmsg: "", 43 | }, { 44 | stmt: Update(nil).Where(table1.C("id").Eq(1)). 45 | Set(table1.C("test1"), 10). 46 | Set(table1.C("test2"), 20), 47 | query: ``, 48 | args: []interface{}{}, 49 | errmsg: "sqlbuilder: table is nil.", 50 | }, { 51 | stmt: Update(table1).Where(table1.C("id").Eq(1)), 52 | query: ``, 53 | args: []interface{}{}, 54 | errmsg: "sqlbuilder: length of sets is 0.", 55 | }, { 56 | stmt: Update(table1).Where(table1.C("id").Eq(1)). 57 | Set(table1.C("test1"), "foo"), 58 | query: ``, 59 | args: []interface{}{}, 60 | errmsg: "sqlbuilder: int column not accept string.", 61 | }, { 62 | stmt: Update(tableJoined), 63 | query: ``, 64 | args: []interface{}{}, 65 | errmsg: "sqlbuilder: length of sets is 0.", 66 | }} 67 | for num, c := range cases { 68 | mes, args, ok := c.Run() 69 | if !ok { 70 | t.Errorf(mes+" (case no.%d)", append(args, num)...) 71 | } 72 | } 73 | } 74 | --------------------------------------------------------------------------------