diff --git a/.travis.yml b/.travis.yml index a652bc84..8b68e7b4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,58 +1,48 @@ +dist: xenial + sudo: required +language: go + +env: + global: + - GOARCH=amd64 + - DB_HOST=127.0.0.1 + matrix: + - TEST_CMD="make databases test" POSTGRESQL_VERSION=9 MYSQL_VERSION=5.7 MONGO_VERSION=3.2 MSSQL_VERSION=2017-GA-ubuntu + - TEST_CMD="make databases test" POSTGRESQL_VERSION=10 MYSQL_VERSION=5.7 MONGO_VERSION=3.6 MSSQL_VERSION=2017-GDR-ubuntu + - TEST_CMD="make databases test" POSTGRESQL_VERSION=11 MYSQL_VERSION=5 MONGO_VERSION=3 MSSQL_VERSION=latest + - TEST_CMD="make benchmark" + notifications: email: false -language: go - go: - - "1.8" - "1.9" -# - "tip" + - "1.10" + - "1.11" services: - docker - - mongodb - - postgresql addons: - postgresql: 9.6 apt: - sources: - - mongodb-3.0-precise packages: - freetds-bin - - mongodb-org-server - - mongodb-org-shell before_install: - - sudo service mysql stop - - docker pull microsoft/mssql-server-linux:latest - - docker pull mysql:5.7 - - docker run -d -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=my$Password' --name mssql -p 1433:1433 -d microsoft/mssql-server-linux:latest - - docker run -d -e "MYSQL_USER=upperio_tests" -e "MYSQL_PASSWORD=upperio_secret" -e "MYSQL_ALLOW_EMPTY_PASSWORD=1" -e "MYSQL_DATABASE=upperio_tests" -p 3306:3306 --name mysql mysql:5.7 - - docker ps -a - - sleep 30 - -env: - global: - - MAKEFLAGS="-j4" - - GOARCH=amd64 - - DB_HOST=127.0.0.1 - matrix: - - TEST_CMD="make benchmark test-main" - - TEST_CMD="make test-adapters" + - sudo service mysql stop & + - sudo service postgresql stop & + - sudo apt-get install -y parallel & + - wait install: - mkdir -p $GOPATH/src/upper.io - - mv $PWD $GOPATH/src/upper.io/db.v3 - - cd $GOPATH/src/upper.io/db.v3 + - export TRAVIS_BUILD_DIR=$GOPATH/src/upper.io/db.v3 + - mv $PWD $TRAVIS_BUILD_DIR + - cd $TRAVIS_BUILD_DIR - go get -t -v -d ./... - go get -v github.com/cznic/ql/ql - - export TRAVIS_BUILD_DIR=$GOPATH/src/upper.io/db.v3 - -before_script: - - docker exec -it mysql bash -c 'mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root mysql' script: - - ${TEST_CMD} + - bash -c "$TEST_CMD" diff --git a/Makefile b/Makefile index c33e1033..d92ba531 100644 --- a/Makefile +++ b/Makefile @@ -1,13 +1,42 @@ -SHELL := /bin/bash +SHELL := /bin/bash -WAPPER ?= all -DB_HOST ?= 127.0.0.1 +POSTGRESQL_IMAGE ?= postgres +POSTGRESQL_VERSION ?= 11 +POSTGRESQL_CONTAINER ?= upper-postgresql +POSTGRESQL_PORT ?= 5432 -TEST_FLAGS ?= +MYSQL_IMAGE ?= mysql +MYSQL_VERSION ?= 5 +MYSQL_CONTAINER ?= upper-mysql +MYSQL_PORT ?= 3306 + +MONGO_IMAGE ?= mongo +MONGO_VERSION ?= 3 +MONGO_CONTAINER ?= upper-mongo +MONGO_PORT ?= 27017 + +MSSQL_IMAGE ?= mcr.microsoft.com/mssql/server +MSSQL_VERSION ?= latest +MSSQL_CONTAINER ?= upper-mssql +MSSQL_PORT ?= 1433 + +DB_USERNAME ?= upperio_tests +DB_PASSWORD ?= upperio_secret + +BIND_HOST ?= 0.0.0.0 + +WRAPPER ?= all +DB_HOST ?= 127.0.0.1 + +TEST_FLAGS ?= + +PARALLEL_FLAGS ?= --halt 2 -v export DB_HOST export WRAPPER +test: reset-db test-libs test-main test-adapters + benchmark-lib: go test -v -benchtime=500ms -bench=. ./lib/... @@ -22,22 +51,97 @@ test-lib: test-internal: go test -v ./internal/... -test-libs: test-lib test-internal +test-libs: + parallel $(PARALLEL_FLAGS) -u \ + "$(MAKE) test-{}" ::: \ + lib \ + internal -test-adapters: test-adapter-postgresql test-adapter-mysql test-adapter-sqlite test-adapter-mssql test-adapter-ql test-adapter-mongo +test-adapters: + parallel $(PARALLEL_FLAGS) -u \ + "$(MAKE) test-adapter-{}" ::: \ + postgresql \ + mysql \ + sqlite \ + mssql \ + ql \ + mongo -reset-db: - $(MAKE) -C postgresql reset-db && \ - $(MAKE) -C mysql reset-db && \ - $(MAKE) -C sqlite reset-db && \ - $(MAKE) -C mssql reset-db && \ - $(MAKE) -C ql reset-db && \ - $(MAKE) -C mongo reset-db - -test-main: reset-db +test-main: go test $(TEST_FLAGS) -v ./tests/... -test: test-adapters test-libs test-main +reset-db: + parallel $(PARALLEL_FLAGS) \ + "$(MAKE) reset-db-{}" ::: \ + postgresql \ + mysql \ + sqlite \ + mssql \ + ql \ + mongo test-adapter-%: - $(MAKE) -C $* test || exit 1; + ($(MAKE) -C $* test || exit 1) + +reset-db-%: + ($(MAKE) -C $* reset-db || exit 1) + +databases: + parallel $(PARALLEL_FLAGS) -u \ + "$(MAKE) docker-{}" ::: \ + postgresql \ + mysql \ + mssql \ + mongo && \ + sleep 30 + +docker-mongo: + docker pull $(MONGO_IMAGE):$(MONGO_VERSION) && \ + (docker rm -f $(MONGO_CONTAINER) || exit 0) && \ + docker run \ + -d \ + --rm \ + -e "MONGO_USER=$(DB_USERNAME)" \ + -e "MONGO_PASSWORD=$(DB_PASSWORD)" \ + -e "MONGO_DATABASE=$(DB_USERNAME)" \ + -p $(BIND_HOST):$(MONGO_PORT):27017 \ + --name $(MONGO_CONTAINER) \ + $(MONGO_IMAGE):$(MONGO_VERSION) + +docker-mysql: + docker pull $(MYSQL_IMAGE):$(MYSQL_VERSION) && \ + (docker rm -f $(MYSQL_CONTAINER) || exit 0) && \ + docker run \ + -d \ + --rm \ + -e "MYSQL_USER=$(DB_USERNAME)" \ + -e "MYSQL_PASSWORD=$(DB_PASSWORD)" \ + -e "MYSQL_ALLOW_EMPTY_PASSWORD=1" \ + -e "MYSQL_DATABASE=$(DB_USERNAME)" \ + -p $(BIND_HOST):$(MYSQL_PORT):3306 \ + --name $(MYSQL_CONTAINER) \ + $(MYSQL_IMAGE):$(MYSQL_VERSION) + +docker-mssql: + docker pull $(MSSQL_IMAGE):$(MSSQL_VERSION) && \ + (docker rm -f $(MSSQL_CONTAINER) || exit 0) && \ + docker run \ + -d \ + --rm \ + -e "ACCEPT_EULA=Y" \ + -e 'SA_PASSWORD=my$$Password' \ + -p $(BIND_HOST):$(MSSQL_PORT):1433 \ + --name $(MSSQL_CONTAINER) \ + $(MSSQL_IMAGE):$(MSSQL_VERSION) + +docker-postgresql: + docker pull $(POSTGRESQL_IMAGE):$(POSTGRESQL_VERSION) && \ + (docker rm -f $(POSTGRESQL_CONTAINER) || exit 0) && \ + docker run \ + -d \ + --rm \ + -e "POSTGRES_USER=$(DB_USERNAME)" \ + -e "POSTGRES_PASSWORD=$(DB_PASSWORD)" \ + -p $(BIND_HOST):$(POSTGRESQL_PORT):5432 \ + --name $(POSTGRESQL_CONTAINER) \ + $(POSTGRESQL_IMAGE):$(POSTGRESQL_VERSION) diff --git a/internal/cache/cache.go b/internal/cache/cache.go index 0c65b468..36c2bd93 100644 --- a/internal/cache/cache.go +++ b/internal/cache/cache.go @@ -133,7 +133,7 @@ func (c *Cache) Clear() { func Hash(v interface{}) string { q, err := hashstructure.Hash(v, nil) if err != nil { - panic(fmt.Sprintf("Could not hash struct: ", err.Error())) + panic(fmt.Sprintf("Could not hash struct: %v", err.Error())) } return strconv.FormatUint(q, 10) } diff --git a/internal/sqladapter/exql/column.go b/internal/sqladapter/exql/column.go index e27566da..4140a442 100644 --- a/internal/sqladapter/exql/column.go +++ b/internal/sqladapter/exql/column.go @@ -56,14 +56,14 @@ func (c *Column) Compile(layout *Template) (compiled string, err error) { if nameChunks[i] == "*" { continue } - nameChunks[i] = mustParse(layout.IdentifierQuote, Raw{Value: nameChunks[i]}) + nameChunks[i] = layout.MustCompile(layout.IdentifierQuote, Raw{Value: nameChunks[i]}) } compiled = strings.Join(nameChunks, layout.ColumnSeparator) if len(chunks) > 1 { alias = trimString(chunks[1]) - alias = mustParse(layout.IdentifierQuote, Raw{Value: alias}) + alias = layout.MustCompile(layout.IdentifierQuote, Raw{Value: alias}) } case Raw: compiled = value.String() @@ -72,7 +72,7 @@ func (c *Column) Compile(layout *Template) (compiled string, err error) { } if alias != "" { - compiled = mustParse(layout.ColumnAliasLayout, columnT{compiled, alias}) + compiled = layout.MustCompile(layout.ColumnAliasLayout, columnT{compiled, alias}) } layout.Write(c, compiled) diff --git a/internal/sqladapter/exql/column_value.go b/internal/sqladapter/exql/column_value.go index 39b62b8d..7b692c0d 100644 --- a/internal/sqladapter/exql/column_value.go +++ b/internal/sqladapter/exql/column_value.go @@ -48,7 +48,7 @@ func (c *ColumnValue) Compile(layout *Template) (compiled string, err error) { } } - compiled = strings.TrimSpace(mustParse(layout.ColumnValue, data)) + compiled = strings.TrimSpace(layout.MustCompile(layout.ColumnValue, data)) layout.Write(c, compiled) diff --git a/internal/sqladapter/exql/columns.go b/internal/sqladapter/exql/columns.go index 54a52d30..d85e8e4a 100644 --- a/internal/sqladapter/exql/columns.go +++ b/internal/sqladapter/exql/columns.go @@ -32,11 +32,20 @@ func UsingColumns(columns ...Fragment) *Using { return &Using{Columns: columns} } +// Append func (c *Columns) Append(a *Columns) *Columns { c.Columns = append(c.Columns, a.Columns...) return c } +// IsEmpty +func (c *Columns) IsEmpty() bool { + if c == nil || len(c.Columns) < 1 { + return true + } + return false +} + // Compile transforms the Columns into an equivalent SQL representation. func (c *Columns) Compile(layout *Template) (compiled string, err error) { @@ -57,6 +66,8 @@ func (c *Columns) Compile(layout *Template) (compiled string, err error) { } compiled = strings.Join(out, layout.IdentifierSeparator) + } else { + compiled = "*" } layout.Write(c, compiled) diff --git a/internal/sqladapter/exql/database.go b/internal/sqladapter/exql/database.go index 01cb66f5..1603607e 100644 --- a/internal/sqladapter/exql/database.go +++ b/internal/sqladapter/exql/database.go @@ -24,7 +24,7 @@ func (d *Database) Compile(layout *Template) (compiled string, err error) { return c, nil } - compiled = mustParse(layout.IdentifierQuote, Raw{Value: d.Name}) + compiled = layout.MustCompile(layout.IdentifierQuote, Raw{Value: d.Name}) layout.Write(d, compiled) return diff --git a/internal/sqladapter/exql/default.go b/internal/sqladapter/exql/default.go index 6916d590..38022166 100644 --- a/internal/sqladapter/exql/default.go +++ b/internal/sqladapter/exql/default.go @@ -69,22 +69,22 @@ const ( {{end}} {{if .Columns}} - {{.Columns}} + {{.Columns | compile}} {{else}} * {{end}} - {{if .Table}} - FROM {{.Table}} + {{if defined .Table}} + FROM {{.Table | compile}} {{end}} - {{.Joins}} + {{.Joins | compile}} - {{.Where}} + {{.Where | compile}} - {{.GroupBy}} + {{.GroupBy | compile}} - {{.OrderBy}} + {{.OrderBy | compile}} {{if .Limit}} LIMIT {{.Limit}} @@ -96,8 +96,8 @@ const ( ` defaultDeleteLayout = ` DELETE - FROM {{.Table}} - {{.Where}} + FROM {{.Table | compile}} + {{.Where | compile}} {{if .Limit}} LIMIT {{.Limit}} {{end}} @@ -107,19 +107,19 @@ const ( ` defaultUpdateLayout = ` UPDATE - {{.Table}} - SET {{.ColumnValues}} - {{ .Where }} + {{.Table | compile}} + SET {{.ColumnValues | compile}} + {{.Where | compile}} ` defaultCountLayout = ` SELECT COUNT(1) AS _t - FROM {{.Table}} - {{.Where}} + FROM {{.Table | compile}} + {{.Where | compile}} {{if .Limit}} - LIMIT {{.Limit}} + LIMIT {{.Limit | compile}} {{end}} {{if .Offset}} @@ -128,25 +128,25 @@ const ( ` defaultInsertLayout = ` - INSERT INTO {{.Table}} - {{if .Columns }}({{.Columns}}){{end}} + INSERT INTO {{.Table | compile}} + {{if .Columns }}({{.Columns | compile}}){{end}} VALUES - {{.Values}} + {{.Values | compile}} {{if .Returning}} - RETURNING {{.Returning}} + RETURNING {{.Returning | compile}} {{end}} ` defaultTruncateLayout = ` - TRUNCATE TABLE {{.Table}} + TRUNCATE TABLE {{.Table | compile}} ` defaultDropDatabaseLayout = ` - DROP DATABASE {{.Database}} + DROP DATABASE {{.Database | compile}} ` defaultDropTableLayout = ` - DROP TABLE {{.Table}} + DROP TABLE {{.Table | compile}} ` defaultGroupByColumnLayout = `{{.Column}}` diff --git a/internal/sqladapter/exql/group_by.go b/internal/sqladapter/exql/group_by.go index 1c18a164..4f0132c7 100644 --- a/internal/sqladapter/exql/group_by.go +++ b/internal/sqladapter/exql/group_by.go @@ -22,6 +22,13 @@ func GroupByColumns(columns ...Fragment) *GroupBy { return &GroupBy{Columns: JoinColumns(columns...)} } +func (g *GroupBy) IsEmpty() bool { + if g == nil || g.Columns == nil { + return true + } + return g.Columns.(hasIsEmpty).IsEmpty() +} + // Compile transforms the GroupBy into an equivalent SQL representation. func (g *GroupBy) Compile(layout *Template) (compiled string, err error) { @@ -34,11 +41,11 @@ func (g *GroupBy) Compile(layout *Template) (compiled string, err error) { if err != nil { return "", err } + data := groupByT{ GroupColumns: columns, } - - compiled = mustParse(layout.GroupByLayout, data) + compiled = layout.MustCompile(layout.GroupByLayout, data) } layout.Write(g, compiled) diff --git a/internal/sqladapter/exql/interfaces.go b/internal/sqladapter/exql/interfaces.go index f450484f..b871ef8c 100644 --- a/internal/sqladapter/exql/interfaces.go +++ b/internal/sqladapter/exql/interfaces.go @@ -7,9 +7,14 @@ import ( // Fragment is any interface that can be both cached and compiled. type Fragment interface { cache.Hashable + compilable } type compilable interface { Compile(*Template) (string, error) } + +type hasIsEmpty interface { + IsEmpty() bool +} diff --git a/internal/sqladapter/exql/join.go b/internal/sqladapter/exql/join.go index 9dd0f1f9..ba982f7c 100644 --- a/internal/sqladapter/exql/join.go +++ b/internal/sqladapter/exql/join.go @@ -108,7 +108,7 @@ func (j *Join) Compile(layout *Template) (compiled string, err error) { Using: using, } - compiled = mustParse(layout.JoinLayout, data) + compiled = layout.MustCompile(layout.JoinLayout, data) layout.Write(j, compiled) return } @@ -129,13 +129,13 @@ func (o *On) Compile(layout *Template) (compiled string, err error) { return c, nil } - grouped, err := groupCondition(layout, o.Conditions, mustParse(layout.ClauseOperator, layout.AndKeyword)) + grouped, err := groupCondition(layout, o.Conditions, layout.MustCompile(layout.ClauseOperator, layout.AndKeyword)) if err != nil { return "", err } if grouped != "" { - compiled = mustParse(layout.OnLayout, conds{grouped}) + compiled = layout.MustCompile(layout.OnLayout, conds{grouped}) } layout.Write(o, compiled) @@ -173,7 +173,7 @@ func (u *Using) Compile(layout *Template) (compiled string, err error) { return "", err } data := usingT{Columns: columns} - compiled = mustParse(layout.UsingLayout, data) + compiled = layout.MustCompile(layout.UsingLayout, data) } layout.Write(u, compiled) diff --git a/internal/sqladapter/exql/join_test.go b/internal/sqladapter/exql/join_test.go index c6a6d913..055f013d 100644 --- a/internal/sqladapter/exql/join_test.go +++ b/internal/sqladapter/exql/join_test.go @@ -253,7 +253,7 @@ func BenchmarkCompileJoinNoCache(b *testing.B) { func BenchmarkCompileJoinNoCache2(b *testing.B) { for i := 0; i < b.N; i++ { j := JoinConditions(&Join{ - Table: TableWithName(fmt.Sprintf("countries c", i)), + Table: TableWithName(fmt.Sprintf("countries c%d", i)), On: OnConditions( &ColumnValue{ Column: &Column{Name: "p.country_id"}, diff --git a/internal/sqladapter/exql/order_by.go b/internal/sqladapter/exql/order_by.go index b8fbd319..8ee9f464 100644 --- a/internal/sqladapter/exql/order_by.go +++ b/internal/sqladapter/exql/order_by.go @@ -85,7 +85,7 @@ func (s *SortColumn) Compile(layout *Template) (compiled string, err error) { data := sortColumnT{Column: column, Order: orderBy} - compiled = mustParse(layout.SortByColumnLayout, data) + compiled = layout.MustCompile(layout.SortByColumnLayout, data) layout.Write(s, compiled) @@ -139,7 +139,7 @@ func (s *OrderBy) Compile(layout *Template) (compiled string, err error) { data := orderByT{ SortColumns: sortColumns, } - compiled = mustParse(layout.OrderByLayout, data) + compiled = layout.MustCompile(layout.OrderByLayout, data) } layout.Write(s, compiled) diff --git a/internal/sqladapter/exql/statement.go b/internal/sqladapter/exql/statement.go index d1f1ea3c..8cf4c774 100644 --- a/internal/sqladapter/exql/statement.go +++ b/internal/sqladapter/exql/statement.go @@ -10,7 +10,7 @@ import ( var errUnknownTemplateType = errors.New("Unknown template type") -// Statement represents different kinds of SQL statements. +// represents different kinds of SQL statements. type Statement struct { Type Table Fragment @@ -34,22 +34,6 @@ type Statement struct { amendFn func(string) string } -type statementT struct { - Table string - Database string - Columns string - Values string - Distinct bool - ColumnValues string - OrderBy string - GroupBy string - Where string - Joins string - Returning string - Limit - Offset -} - func (layout *Template) doCompile(c Fragment) (string, error) { if c != nil && !reflect.ValueOf(c).IsNil() { return c.Compile(layout) @@ -80,6 +64,29 @@ func (s *Statement) Amend(in string) string { return s.amendFn(in) } +func (s *Statement) template(layout *Template) (string, error) { + switch s.Type { + case Truncate: + return layout.TruncateLayout, nil + case DropTable: + return layout.DropTableLayout, nil + case DropDatabase: + return layout.DropDatabaseLayout, nil + case Count: + return layout.CountLayout, nil + case Select: + return layout.SelectLayout, nil + case Delete: + return layout.DeleteLayout, nil + case Update: + return layout.UpdateLayout, nil + case Insert: + return layout.InsertLayout, nil + default: + return "", errUnknownTemplateType + } +} + // Compile transforms the Statement into an equivalent SQL query. func (s *Statement) Compile(layout *Template) (compiled string, err error) { if s.Type == SQL { @@ -91,82 +98,12 @@ func (s *Statement) Compile(layout *Template) (compiled string, err error) { return s.Amend(z), nil } - data := statementT{ - Limit: s.Limit, - Offset: s.Offset, - Distinct: s.Distinct, - } - - data.Table, err = layout.doCompile(s.Table) + tpl, err := s.template(layout) if err != nil { return "", err } - data.Database, err = layout.doCompile(s.Database) - if err != nil { - return "", err - } - - data.Columns, err = layout.doCompile(s.Columns) - if err != nil { - return "", err - } - - data.Values, err = layout.doCompile(s.Values) - if err != nil { - return "", err - } - - data.ColumnValues, err = layout.doCompile(s.ColumnValues) - if err != nil { - return "", err - } - - data.OrderBy, err = layout.doCompile(s.OrderBy) - if err != nil { - return "", err - } - - data.GroupBy, err = layout.doCompile(s.GroupBy) - if err != nil { - return "", err - } - - data.Where, err = layout.doCompile(s.Where) - if err != nil { - return "", err - } - - data.Returning, err = layout.doCompile(s.Returning) - if err != nil { - return "", err - } - - data.Joins, err = layout.doCompile(s.Joins) - if err != nil { - return "", err - } - - switch s.Type { - case Truncate: - compiled = mustParse(layout.TruncateLayout, data) - case DropTable: - compiled = mustParse(layout.DropTableLayout, data) - case DropDatabase: - compiled = mustParse(layout.DropDatabaseLayout, data) - case Count: - compiled = mustParse(layout.CountLayout, data) - case Select: - compiled = mustParse(layout.SelectLayout, data) - case Delete: - compiled = mustParse(layout.DeleteLayout, data) - case Update: - compiled = mustParse(layout.UpdateLayout, data) - case Insert: - compiled = mustParse(layout.InsertLayout, data) - default: - return "", errUnknownTemplateType - } + compiled = layout.MustCompile(tpl, s) compiled = strings.TrimSpace(compiled) layout.Write(s, compiled) diff --git a/internal/sqladapter/exql/table.go b/internal/sqladapter/exql/table.go index fc7f48b9..5c0c8f83 100644 --- a/internal/sqladapter/exql/table.go +++ b/internal/sqladapter/exql/table.go @@ -35,7 +35,7 @@ func quotedTableName(layout *Template, input string) string { for i := range nameChunks { // nameChunks[i] = strings.TrimSpace(nameChunks[i]) nameChunks[i] = trimString(nameChunks[i]) - nameChunks[i] = mustParse(layout.IdentifierQuote, Raw{Value: nameChunks[i]}) + nameChunks[i] = layout.MustCompile(layout.IdentifierQuote, Raw{Value: nameChunks[i]}) } name = strings.Join(nameChunks, layout.ColumnSeparator) @@ -45,10 +45,10 @@ func quotedTableName(layout *Template, input string) string { if len(chunks) > 1 { // alias = strings.TrimSpace(chunks[1]) alias = trimString(chunks[1]) - alias = mustParse(layout.IdentifierQuote, Raw{Value: alias}) + alias = layout.MustCompile(layout.IdentifierQuote, Raw{Value: alias}) } - return mustParse(layout.TableAliasLayout, tableT{name, alias}) + return layout.MustCompile(layout.TableAliasLayout, tableT{name, alias}) } // TableWithName creates an returns a Table with the given name. diff --git a/internal/sqladapter/exql/template.go b/internal/sqladapter/exql/template.go index 8886e4ad..4758b49b 100644 --- a/internal/sqladapter/exql/template.go +++ b/internal/sqladapter/exql/template.go @@ -2,6 +2,7 @@ package exql import ( "bytes" + "reflect" "sync" "text/template" @@ -35,10 +36,6 @@ type ( Offset int ) -var ( - templateCache = templateMap{M: make(map[string]*template.Template)} -) - // Template is an SQL template. type Template struct { AndKeyword string @@ -74,40 +71,66 @@ type Template struct { ComparisonOperator map[db.ComparisonOperator]string + templateMutex sync.RWMutex + templateMap map[string]*template.Template + *cache.Cache } -func mustParse(text string, data interface{}) string { +func (layout *Template) MustCompile(templateText string, data interface{}) string { var b bytes.Buffer - var ok bool - v, ok := templateCache.Get(text) - if !ok { - v = template.Must(template.New("").Parse(text)) - templateCache.Set(text, v) + v, ok := layout.getTemplate(templateText) + if !ok || true { + v = template. + Must(template.New(""). + Funcs(map[string]interface{}{ + "defined": func(in Fragment) bool { + if in == nil || reflect.ValueOf(in).IsNil() { + return false + } + if check, ok := in.(hasIsEmpty); ok { + if check.IsEmpty() { + return false + } + } + return true + }, + "compile": func(in Fragment) (string, error) { + s, err := layout.doCompile(in) + if err != nil { + return "", err + } + return s, nil + }, + }). + Parse(templateText)) + + layout.setTemplate(templateText, v) } if err := v.Execute(&b, data); err != nil { - panic("There was an error compiling the following template:\n" + text + "\nError was: " + err.Error()) + panic("There was an error compiling the following template:\n" + templateText + "\nError was: " + err.Error()) } return b.String() } -type templateMap struct { - sync.RWMutex - M map[string]*template.Template -} +func (t *Template) getTemplate(k string) (*template.Template, bool) { + t.templateMutex.RLock() + defer t.templateMutex.RUnlock() -func (m *templateMap) Get(k string) (*template.Template, bool) { - m.RLock() - defer m.RUnlock() - v, ok := m.M[k] + if t.templateMap == nil { + t.templateMap = make(map[string]*template.Template) + } + + v, ok := t.templateMap[k] return v, ok } -func (m *templateMap) Set(k string, v *template.Template) { - m.Lock() - defer m.Unlock() - m.M[k] = v +func (t *Template) setTemplate(k string, v *template.Template) { + t.templateMutex.Lock() + defer t.templateMutex.Unlock() + + t.templateMap[k] = v } diff --git a/internal/sqladapter/exql/value.go b/internal/sqladapter/exql/value.go index 390469ad..49b22aa9 100644 --- a/internal/sqladapter/exql/value.go +++ b/internal/sqladapter/exql/value.go @@ -11,6 +11,18 @@ type ValueGroups struct { hash hash } +func (vg *ValueGroups) IsEmpty() bool { + if vg == nil || len(vg.Values) < 1 { + return true + } + for i := range vg.Values { + if !vg.Values[i].IsEmpty() { + return false + } + } + return true +} + var _ = Fragment(&ValueGroups{}) // Values represents an array of Value. @@ -19,6 +31,13 @@ type Values struct { hash hash } +func (vs *Values) IsEmpty() bool { + if vs == nil || len(vs.Values) < 1 { + return true + } + return false +} + var _ = Fragment(&Values{}) // Value represents an escaped SQL value. @@ -44,6 +63,10 @@ func (v *Value) Hash() string { return v.hash.Hash(v) } +func (v *Value) IsEmpty() bool { + return false +} + // Compile transforms the Value into an equivalent SQL representation. func (v *Value) Compile(layout *Template) (compiled string, err error) { @@ -63,7 +86,7 @@ func (v *Value) Compile(layout *Template) (compiled string, err error) { return "", err } default: - compiled = mustParse(layout.ValueQuote, RawValue(fmt.Sprintf(`%v`, v.V))) + compiled = layout.MustCompile(layout.ValueQuote, RawValue(fmt.Sprintf(`%v`, v.V))) } layout.Write(v, compiled) @@ -92,7 +115,7 @@ func (vs *Values) Compile(layout *Template) (compiled string, err error) { } chunks = append(chunks, chunk) } - compiled = mustParse(layout.ClauseGroup, strings.Join(chunks, layout.ValueSeparator)) + compiled = layout.MustCompile(layout.ClauseGroup, strings.Join(chunks, layout.ValueSeparator)) } layout.Write(vs, compiled) return diff --git a/internal/sqladapter/exql/where.go b/internal/sqladapter/exql/where.go index 14a98a5a..3e77e005 100644 --- a/internal/sqladapter/exql/where.go +++ b/internal/sqladapter/exql/where.go @@ -68,7 +68,7 @@ func (o *Or) Compile(layout *Template) (compiled string, err error) { return z, nil } - compiled, err = groupCondition(layout, o.Conditions, mustParse(layout.ClauseOperator, layout.OrKeyword)) + compiled, err = groupCondition(layout, o.Conditions, layout.MustCompile(layout.ClauseOperator, layout.OrKeyword)) if err != nil { return "", err } @@ -84,7 +84,7 @@ func (a *And) Compile(layout *Template) (compiled string, err error) { return c, nil } - compiled, err = groupCondition(layout, a.Conditions, mustParse(layout.ClauseOperator, layout.AndKeyword)) + compiled, err = groupCondition(layout, a.Conditions, layout.MustCompile(layout.ClauseOperator, layout.AndKeyword)) if err != nil { return "", err } @@ -100,13 +100,13 @@ func (w *Where) Compile(layout *Template) (compiled string, err error) { return c, nil } - grouped, err := groupCondition(layout, w.Conditions, mustParse(layout.ClauseOperator, layout.AndKeyword)) + grouped, err := groupCondition(layout, w.Conditions, layout.MustCompile(layout.ClauseOperator, layout.AndKeyword)) if err != nil { return "", err } if grouped != "" { - compiled = mustParse(layout.WhereLayout, conds{grouped}) + compiled = layout.MustCompile(layout.WhereLayout, conds{grouped}) } layout.Write(w, compiled) @@ -130,7 +130,7 @@ func groupCondition(layout *Template, terms []Fragment, joinKeyword string) (str } if len(chunks) > 0 { - return mustParse(layout.ClauseGroup, strings.Join(chunks, joinKeyword)), nil + return layout.MustCompile(layout.ClauseGroup, strings.Join(chunks, joinKeyword)), nil } return "", nil diff --git a/lib/sqlbuilder/template_test.go b/lib/sqlbuilder/template_test.go index 3563f84f..a81db9e0 100644 --- a/lib/sqlbuilder/template_test.go +++ b/lib/sqlbuilder/template_test.go @@ -69,23 +69,25 @@ const ( DISTINCT {{end}} - {{if .Columns}} - {{.Columns}} + {{if defined .Columns}} + {{.Columns | compile}} {{else}} * {{end}} - {{if .Table}} - FROM {{.Table}} + {{if defined .Table}} + FROM {{.Table | compile}} {{end}} - {{.Joins}} + {{.Joins | compile}} - {{.Where}} + {{.Where | compile}} - {{.GroupBy}} + {{if defined .GroupBy}} + {{.GroupBy | compile}} + {{end}} - {{.OrderBy}} + {{.OrderBy | compile}} {{if .Limit}} LIMIT {{.Limit}} @@ -97,21 +99,21 @@ const ( ` defaultDeleteLayout = ` DELETE - FROM {{.Table}} - {{.Where}} + FROM {{.Table | compile}} + {{.Where | compile}} ` defaultUpdateLayout = ` UPDATE - {{.Table}} - SET {{.ColumnValues}} - {{ .Where }} + {{.Table | compile}} + SET {{.ColumnValues | compile}} + {{.Where | compile}} ` defaultCountLayout = ` SELECT COUNT(1) AS _t - FROM {{.Table}} - {{.Where}} + FROM {{.Table | compile}} + {{.Where | compile}} {{if .Limit}} LIMIT {{.Limit}} @@ -123,29 +125,29 @@ const ( ` defaultInsertLayout = ` - INSERT INTO {{.Table}} - {{if .Columns }}({{.Columns}}){{end}} + INSERT INTO {{.Table | compile}} + {{if defined .Columns }}({{.Columns | compile}}){{end}} VALUES - {{if .Values}} - {{.Values}} + {{if defined .Values}} + {{.Values | compile}} {{else}} (default) {{end}} - {{if .Returning}} - RETURNING {{.Returning}} + {{if defined .Returning}} + RETURNING {{.Returning | compile}} {{end}} ` defaultTruncateLayout = ` - TRUNCATE TABLE {{.Table}} + TRUNCATE TABLE {{.Table | compile}} ` defaultDropDatabaseLayout = ` - DROP DATABASE {{.Database}} + DROP DATABASE {{.Database | compile}} ` defaultDropTableLayout = ` - DROP TABLE {{.Table}} + DROP TABLE {{.Table | compile}} ` defaultGroupByColumnLayout = `{{.Column}}` diff --git a/mssql/collection.go b/mssql/collection.go index a1bfe447..c14ee1dd 100644 --- a/mssql/collection.go +++ b/mssql/collection.go @@ -22,8 +22,6 @@ package mssql import ( - "database/sql" - "upper.io/db.v3" "upper.io/db.v3/internal/sqladapter" "upper.io/db.v3/lib/sqlbuilder" @@ -118,28 +116,26 @@ func (t *table) Insert(item interface{}) (interface{}, error) { Columns(columnNames...). Values(columnValues...) - var res sql.Result - if res, err = q.Exec(); err != nil { - return nil, err + if len(pKey) < 1 { + _, err = q.Exec() + if err != nil { + return nil, err + } + return nil, nil } - if len(pKey) <= 1 { - // Attempt to use LastInsertId() (probably won't work, but the Exec() - // succeeded, so we can safely ignore the error from LastInsertId()). - lastID, _ := res.LastInsertId() + q = q.Returning(pKey...) - return lastID, nil + var keyMap db.Cond + if err = q.Iterator().One(&keyMap); err != nil { + return nil, err } - keyMap := db.Cond{} - - for i := range columnNames { - for j := 0; j < len(pKey); j++ { - if pKey[j] == columnNames[i] { - keyMap[pKey[j]] = columnValues[i] - } - } + // The IDSetter interface does not match, look for another interface match. + if len(keyMap) == 1 { + return keyMap[pKey[0]], nil } + // This was a compound key and no interface matched it, let's return a map. return keyMap, nil } diff --git a/mssql/template.go b/mssql/template.go index 4d371afe..a682ef0d 100644 --- a/mssql/template.go +++ b/mssql/template.go @@ -81,93 +81,99 @@ const ( ` adapterSelectLayout = ` - {{if or .Limit .Offset}} - SELECT __q0.* FROM ( - SELECT TOP 100 PERCENT __q1.*, - ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS rnum FROM - ( - {{end}} + {{if or .Limit .Offset}} + SELECT __q0.* FROM ( + SELECT TOP 100 PERCENT __q1.*, + ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS rnum FROM + ( + {{end}} - SELECT + SELECT - {{if .Distinct}} - DISTINCT - {{end}} + {{if .Distinct}} + DISTINCT + {{end}} - {{if or .Limit .Offset}} - {{if gt .Limit 0 }} - TOP ({{.Limit}} + {{if gt .Offset 0}}{{.Offset}}{{else}}0{{end}}) - {{else}} - TOP 100 PERCENT - {{end}} - {{end}} + {{if or .Limit .Offset}} + {{if gt .Limit 0 }} + TOP ({{.Limit}} + {{if gt .Offset 0}}{{.Offset}}{{else}}0{{end}}) + {{else}} + TOP 100 PERCENT + {{end}} + {{end}} - {{if .Columns}} - {{.Columns}} - {{else}} - * - {{end}} + {{if defined .Columns}} + {{.Columns | compile}} + {{else}} + * + {{end}} - {{if .Table}} - FROM {{.Table}} - {{end}} + {{if defined .Table}} + FROM {{.Table | compile}} + {{end}} - {{.Joins}} + {{.Joins | compile}} - {{.Where}} + {{.Where | compile}} - {{.GroupBy}} + {{if defined .GroupBy}} + {{.GroupBy | compile}} + {{end}} - {{.OrderBy}} + {{.OrderBy | compile}} - {{if or .Limit .Offset}} - ) __q1) __q0 WHERE rnum > {{if gt .Offset 0}}{{.Offset}}{{else}}0{{end}} - {{end}} + {{if or .Limit .Offset}} + ) __q1) __q0 WHERE rnum > {{if gt .Offset 0}}{{.Offset}}{{else}}0{{end}} + {{end}} ` adapterDeleteLayout = ` DELETE - FROM {{.Table}} - {{.Where}} + FROM {{.Table | compile}} + {{.Where | compile}} ` adapterUpdateLayout = ` UPDATE - {{.Table}} - SET {{.ColumnValues}} - {{ .Where }} + {{.Table | compile}} + SET {{.ColumnValues | compile}} + {{.Where | compile}} ` adapterSelectCountLayout = ` SELECT COUNT(1) AS _t - FROM {{.Table}} - {{.Where}} + FROM {{.Table | compile}} + {{.Where | compile}}} ` adapterInsertLayout = ` - INSERT INTO {{.Table}} - {{if .Columns }}({{.Columns}}){{end}} + INSERT INTO {{.Table | compile}} + {{if .Columns }}({{.Columns | compile}}){{end}} + {{if .Returning }} + OUTPUT + {{range $key, $value := .Returning.Columns.Columns}} + {{- if $key}},{{end}} + [inserted].{{ $value | compile }} + {{end}} + {{end}} VALUES - {{if .Values}} - {{.Values}} + {{if defined .Values}} + {{.Values | compile}} {{else}} (DEFAULT) {{end}} - {{if .Returning}} - RETURNING {{.Returning}} - {{end}} ` adapterTruncateLayout = ` - TRUNCATE TABLE {{.Table}} + TRUNCATE TABLE {{.Table | compile}} ` adapterDropDatabaseLayout = ` - DROP DATABASE {{.Database}} + DROP DATABASE {{.Database | compile}} ` adapterDropTableLayout = ` - DROP TABLE {{.Table}} + DROP TABLE {{.Table | compile}} ` adapterGroupByLayout = ` diff --git a/mssql/template_test.go b/mssql/template_test.go index 8f54e771..6f780806 100644 --- a/mssql/template_test.go +++ b/mssql/template_test.go @@ -165,7 +165,7 @@ func TestTemplateInsert(t *testing.T) { ) assert.Equal( - "INSERT INTO [artist] ([id], [name]) VALUES ($1, $2) RETURNING [id]", + "INSERT INTO [artist] ([id], [name]) OUTPUT [inserted].[id] VALUES ($1, $2)", b.InsertInto("artist").Values(map[string]string{"id": "12", "name": "Chavela Vargas"}).Returning("id").String(), ) diff --git a/mysql/template.go b/mysql/template.go index e4f6a3f0..265d0df9 100644 --- a/mysql/template.go +++ b/mysql/template.go @@ -92,28 +92,30 @@ const ( DISTINCT {{end}} - {{if .Columns}} - {{.Columns}} + {{if defined .Columns}} + {{.Columns | compile}} {{else}} * {{end}} - {{if .Table}} - FROM {{.Table}} + {{if defined .Table}} + FROM {{.Table | compile}} {{end}} - {{.Joins}} + {{.Joins | compile}} - {{.Where}} + {{.Where | compile}} - {{.GroupBy}} + {{if defined .GroupBy}} + {{.GroupBy | compile}} + {{end}} - {{.OrderBy}} + {{.OrderBy | compile}} {{if .Limit}} LIMIT {{.Limit}} {{end}} - ` + + ` + // The argument for LIMIT when only OFFSET is specified is a pretty odd magic // number; this comes directly from MySQL's manual, see: // https://dev.mysql.com/doc/refman/5.7/en/select.html @@ -126,55 +128,55 @@ const ( // ¯\_(ツ)_/¯ ` {{if .Offset}} - {{if not .Limit}} - LIMIT 18446744073709551615 - {{end}} + {{if not .Limit}} + LIMIT 18446744073709551615 + {{end}} OFFSET {{.Offset}} {{end}} ` adapterDeleteLayout = ` DELETE - FROM {{.Table}} - {{.Where}} + FROM {{.Table | compile}} + {{.Where | compile}} ` adapterUpdateLayout = ` UPDATE - {{.Table}} - SET {{.ColumnValues}} - {{ .Where }} + {{.Table | compile}} + SET {{.ColumnValues | compile}} + {{.Where | compile}} ` adapterSelectCountLayout = ` SELECT COUNT(1) AS _t - FROM {{.Table}} - {{.Where}} + FROM {{.Table | compile}} + {{.Where | compile}} ` adapterInsertLayout = ` - INSERT INTO {{.Table}} - {{if .Columns }}({{.Columns}}){{end}} + INSERT INTO {{.Table | compile}} + {{if defined .Columns}}({{.Columns | compile}}){{end}} VALUES - {{if .Values}} - {{.Values}} + {{if defined .Values}} + {{.Values | compile}} {{else}} () {{end}} - {{if .Returning}} - RETURNING {{.Returning}} + {{if defined .Returning}} + RETURNING {{.Returning | compile}} {{end}} ` adapterTruncateLayout = ` - TRUNCATE TABLE {{.Table}} + TRUNCATE TABLE {{.Table | compile}} ` adapterDropDatabaseLayout = ` - DROP DATABASE {{.Database}} + DROP DATABASE {{.Database | compile}} ` adapterDropTableLayout = ` - DROP TABLE {{.Table}} + DROP TABLE {{.Table | compile}} ` adapterGroupByLayout = ` diff --git a/postgresql/Makefile b/postgresql/Makefile index 44f9c400..acdffc26 100644 --- a/postgresql/Makefile +++ b/postgresql/Makefile @@ -35,12 +35,7 @@ reset-db: require-client SQL+="CREATE USER $(DB_USERNAME) WITH PASSWORD '$(DB_PASSWORD)';" && \ SQL+="CREATE DATABASE $(DB_NAME) ENCODING 'UTF-8' LC_COLLATE='en_US.UTF-8' LC_CTYPE='en_US.UTF-8' TEMPLATE template0;" && \ SQL+="GRANT ALL PRIVILEGES ON DATABASE $(DB_NAME) TO $(DB_USERNAME);" && \ - if [ ! -z "$$CI" ]; then \ - psql -Upostgres -h$(DB_HOST) -p$(DB_PORT) <<< $$SQL; \ - else \ - PGPASSWORD="$(DB_PASSWORD)" \ - psql -U$(DB_USERNAME) -h$(DB_HOST) -p$(DB_PORT) <<< $$SQL; \ - fi + PGPASSWORD="$(DB_PASSWORD)" psql -U$(DB_USERNAME) -h$(DB_HOST) -p$(DB_PORT) <<< $$SQL; test: reset-db generate #go test -tags generated -v -race # race: limit on 8192 simultaneously alive goroutines is exceeded, dying diff --git a/postgresql/collection.go b/postgresql/collection.go index 5772b054..b07451d8 100644 --- a/postgresql/collection.go +++ b/postgresql/collection.go @@ -77,8 +77,10 @@ func (c *collection) Insert(item interface{}) (interface{}, error) { // Attempt to use LastInsertId() (probably won't work, but the Exec() // succeeded, so we can safely ignore the error from LastInsertId()). - lastID, _ := res.LastInsertId() - + lastID, err := res.LastInsertId() + if err != nil { + return 0, nil + } return lastID, nil } diff --git a/postgresql/template.go b/postgresql/template.go index ec01e157..0a11d3fa 100644 --- a/postgresql/template.go +++ b/postgresql/template.go @@ -91,23 +91,25 @@ const ( DISTINCT {{end}} - {{if .Columns}} - {{.Columns}} + {{if defined .Columns}} + {{.Columns | compile}} {{else}} * {{end}} - {{if .Table}} - FROM {{.Table}} + {{if defined .Table}} + FROM {{.Table | compile}} {{end}} - {{.Joins}} + {{.Joins | compile}} - {{.Where}} + {{.Where | compile}} - {{.GroupBy}} + {{if defined .GroupBy}} + {{.GroupBy | compile}} + {{end}} - {{.OrderBy}} + {{.OrderBy | compile}} {{if .Limit}} LIMIT {{.Limit}} @@ -119,47 +121,47 @@ const ( ` adapterDeleteLayout = ` DELETE - FROM {{.Table}} - {{.Where}} + FROM {{.Table | compile}} + {{.Where | compile}} ` adapterUpdateLayout = ` UPDATE - {{.Table}} - SET {{.ColumnValues}} - {{ .Where }} + {{.Table | compile}} + SET {{.ColumnValues | compile}} + {{.Where | compile}} ` adapterSelectCountLayout = ` SELECT COUNT(1) AS _t - FROM {{.Table}} - {{.Where}} + FROM {{.Table | compile}} + {{.Where | compile}} ` adapterInsertLayout = ` - INSERT INTO {{.Table}} - {{if .Columns }}({{.Columns}}){{end}} + INSERT INTO {{.Table | compile}} + {{if defined .Columns}}({{.Columns | compile}}){{end}} VALUES - {{if .Values}} - {{.Values}} + {{if defined .Values}} + {{.Values | compile}} {{else}} (default) {{end}} - {{if .Returning}} - RETURNING {{.Returning}} + {{if defined .Returning}} + RETURNING {{.Returning | compile}} {{end}} ` adapterTruncateLayout = ` - TRUNCATE TABLE {{.Table}} RESTART IDENTITY + TRUNCATE TABLE {{.Table | compile}} RESTART IDENTITY ` adapterDropDatabaseLayout = ` - DROP DATABASE {{.Database}} + DROP DATABASE {{.Database | compile}} ` adapterDropTableLayout = ` - DROP TABLE {{.Table}} + DROP TABLE {{.Table | compile}} ` adapterGroupByLayout = ` diff --git a/ql/collection.go b/ql/collection.go index d9960f38..a564cb03 100644 --- a/ql/collection.go +++ b/ql/collection.go @@ -126,11 +126,7 @@ func (t *table) Insert(item interface{}) (interface{}, error) { } if len(pKey) <= 1 { - // Attempt to use LastInsertId() (probably won't work, but the Exec() - // succeeded, so we can safely ignore the error from LastInsertId()). - lastID, _ := res.LastInsertId() - - return lastID, nil + return res.LastInsertId() } keyMap := db.Cond{} diff --git a/ql/template.go b/ql/template.go index 3a9e01e5..bf00758d 100644 --- a/ql/template.go +++ b/ql/template.go @@ -87,29 +87,31 @@ const ( DISTINCT {{end}} - {{if .Columns}} - {{.Columns}} + {{if defined .Columns}} + {{.Columns | compile}} {{else}} * {{end}} - {{if .Table}} - FROM {{.Table}} + {{if defined .Table}} + FROM {{.Table | compile}} {{end}} - {{.Joins}} + {{.Joins | compile}} - {{.Where}} + {{.Where | compile}} - {{.GroupBy}} + {{if defined .GroupBy}} + {{.GroupBy | compile}} + {{end}} - {{if .OrderBy}} - {{.OrderBy}} - {{else}} - {{if .Table}} - ORDER BY id() ASC - {{end}} - {{end}} + {{if defined .OrderBy}} + {{.OrderBy | compile}} + {{else}} + {{if defined .Table}} + ORDER BY id() ASC + {{end}} + {{end}} {{if .Limit}} LIMIT {{.Limit}} @@ -121,47 +123,47 @@ const ( ` adapterDeleteLayout = ` DELETE - FROM {{.Table}} - {{.Where}} + FROM {{.Table | compile}} + {{.Where | compile}} ` adapterUpdateLayout = ` UPDATE - {{.Table}} - SET {{.ColumnValues}} - {{ .Where }} + {{.Table | compile}} + SET {{.ColumnValues | compile}} + {{.Where | compile}} ` adapterSelectCountLayout = ` SELECT count(1) AS total - FROM {{.Table}} - {{.Where}} + FROM {{.Table | compile}} + {{.Where | compile}} ` adapterInsertLayout = ` - INSERT INTO {{.Table}} - {{if .Columns }}({{.Columns}}){{end}} - {{if .Values}} + INSERT INTO {{.Table | compile}} + {{if defined .Columns }}({{.Columns | compile}}){{end}} + {{if defined .Values}} VALUES - {{.Values}} + {{.Values | compile}} {{else}} DEFAULT VALUES {{end}} - {{if .Returning}} - RETURNING {{.Returning}} + {{if defined .Returning}} + RETURNING {{.Returning | compile}} {{end}} ` adapterTruncateLayout = ` - TRUNCATE TABLE {{.Table}} + TRUNCATE TABLE {{.Table | compile}} ` adapterDropDatabaseLayout = ` - DROP DATABASE {{.Database}} + DROP DATABASE {{.Database | compile}} ` adapterDropTableLayout = ` - DROP TABLE {{.Table}} + DROP TABLE {{.Table | compile}} ` adapterGroupByLayout = ` diff --git a/sqlite/collection.go b/sqlite/collection.go index c1c5342d..76d90660 100644 --- a/sqlite/collection.go +++ b/sqlite/collection.go @@ -79,11 +79,7 @@ func (t *table) Insert(item interface{}) (interface{}, error) { } if len(pKey) <= 1 { - // Attempt to use LastInsertId() (probably won't work, but the Exec() - // succeeded, so we can safely ignore the error from LastInsertId()). - lastID, _ := res.LastInsertId() - - return lastID, nil + return res.LastInsertId() } keyMap := db.Cond{} diff --git a/sqlite/template.go b/sqlite/template.go index e8b93423..7bb7e0d2 100644 --- a/sqlite/template.go +++ b/sqlite/template.go @@ -92,23 +92,25 @@ const ( DISTINCT {{end}} - {{if .Columns}} - {{.Columns}} + {{if defined .Columns}} + {{.Columns | compile}} {{else}} * {{end}} - {{if .Table}} - FROM {{.Table}} + {{if defined .Table}} + FROM {{.Table | compile}} {{end}} - {{.Joins}} + {{.Joins | compile}} - {{.Where}} + {{.Where | compile}} - {{.GroupBy}} + {{if defined .GroupBy}} + {{.GroupBy | compile}} + {{end}} - {{.OrderBy}} + {{.OrderBy | compile}} {{if .Limit}} LIMIT {{.Limit}} @@ -123,47 +125,47 @@ const ( ` adapterDeleteLayout = ` DELETE - FROM {{.Table}} - {{.Where}} + FROM {{.Table | compile}} + {{.Where | compile}} ` adapterUpdateLayout = ` UPDATE - {{.Table}} - SET {{.ColumnValues}} - {{ .Where }} + {{.Table | compile}} + SET {{.ColumnValues | compile}} + {{.Where | compile}} ` adapterSelectCountLayout = ` SELECT COUNT(1) AS _t - FROM {{.Table}} - {{.Where}} + FROM {{.Table | compile}} + {{.Where | compile}} ` adapterInsertLayout = ` - INSERT INTO {{.Table}} - {{if .Columns }}({{.Columns}}){{end}} - {{if .Values}} + INSERT INTO {{.Table | compile}} + {{if .Columns }}({{.Columns | compile}}){{end}} + {{if defined .Values}} VALUES - {{.Values}} + {{.Values | compile}} {{else}} DEFAULT VALUES {{end}} - {{if .Returning}} - RETURNING {{.Returning}} + {{if defined .Returning}} + RETURNING {{.Returning | compile}} {{end}} ` adapterTruncateLayout = ` - DELETE FROM {{.Table}} + DELETE FROM {{.Table | compile}} ` adapterDropDatabaseLayout = ` - DROP DATABASE {{.Database}} + DROP DATABASE {{.Database | compile}} ` adapterDropTableLayout = ` - DROP TABLE {{.Table}} + DROP TABLE {{.Table | compile}} ` adapterGroupByLayout = ` diff --git a/tests/db_test.go b/tests/db_test.go index f06a6e3f..8ec02255 100644 --- a/tests/db_test.go +++ b/tests/db_test.go @@ -270,7 +270,7 @@ var setupFn = map[string]func(driver interface{}) error{ return err } _, err = sqld.Exec(`CREATE TABLE [birthdays] ( - id BIGINT PRIMARY KEY NOT NULL IDENTITY(1,1), + id BIGINT IDENTITY(1, 1) PRIMARY KEY NOT NULL, name NVARCHAR(50), born DATETIME, born_ut BIGINT