xuzhiheng 5 жил өмнө
parent
commit
0dbb7d312e
100 өөрчлөгдсөн 15627 нэмэгдсэн , 37 устгасан
  1. BIN
      .DS_Store
  2. 0 11
      Cadmin-server-go/order/router.go
  3. BIN
      src/.DS_Store
  4. 8 0
      src/config.json
  5. 20 0
      src/config/config.go
  6. BIN
      src/github.com/baiy/.DS_Store
  7. BIN
      src/github.com/baiy/Cadmin-server-go/.DS_Store
  8. 0 0
      src/github.com/baiy/Cadmin-server-go/LICENSE
  9. 0 0
      src/github.com/baiy/Cadmin-server-go/admin/context.go
  10. 0 0
      src/github.com/baiy/Cadmin-server-go/admin/db.go
  11. 5 16
      src/github.com/baiy/Cadmin-server-go/admin/dispatch.go
  12. 0 0
      src/github.com/baiy/Cadmin-server-go/admin/log.go
  13. 0 0
      src/github.com/baiy/Cadmin-server-go/admin/password.go
  14. 0 0
      src/github.com/baiy/Cadmin-server-go/admin/password_test.go
  15. 0 0
      src/github.com/baiy/Cadmin-server-go/admin/response.go
  16. 0 0
      src/github.com/baiy/Cadmin-server-go/go.mod
  17. 0 0
      src/github.com/baiy/Cadmin-server-go/go.sum
  18. 0 0
      src/github.com/baiy/Cadmin-server-go/main.go
  19. 0 0
      src/github.com/baiy/Cadmin-server-go/models/auth/auth.go
  20. 0 0
      src/github.com/baiy/Cadmin-server-go/models/menu/menu.go
  21. 0 0
      src/github.com/baiy/Cadmin-server-go/models/menuRelate/menuRelate.go
  22. 0 0
      src/github.com/baiy/Cadmin-server-go/models/model.go
  23. 0 0
      src/github.com/baiy/Cadmin-server-go/models/request/request.go
  24. 0 0
      src/github.com/baiy/Cadmin-server-go/models/requestRelate/requestRelate.go
  25. 0 0
      src/github.com/baiy/Cadmin-server-go/models/token/token.go
  26. 0 0
      src/github.com/baiy/Cadmin-server-go/models/user/user.go
  27. 0 0
      src/github.com/baiy/Cadmin-server-go/models/userGroup/userGroup.go
  28. 0 0
      src/github.com/baiy/Cadmin-server-go/models/userGroupRelate/userGroupRelate.go
  29. 0 0
      src/github.com/baiy/Cadmin-server-go/models/userRelate/userRelate.go
  30. 0 0
      src/github.com/baiy/Cadmin-server-go/models/utils/time.go
  31. 0 0
      src/github.com/baiy/Cadmin-server-go/system/auth/auth.go
  32. 0 0
      src/github.com/baiy/Cadmin-server-go/system/index/index.go
  33. 0 0
      src/github.com/baiy/Cadmin-server-go/system/menu/menu.go
  34. 3 3
      src/github.com/baiy/Cadmin-server-go/system/request/request.go
  35. 7 7
      src/github.com/baiy/Cadmin-server-go/system/router.go
  36. 0 0
      src/github.com/baiy/Cadmin-server-go/system/user/user.go
  37. 0 0
      src/github.com/baiy/Cadmin-server-go/system/userGroup/userGroup.go
  38. 0 0
      src/github.com/baiy/Cadmin-server-go/system/utils/page.go
  39. 0 0
      src/github.com/baiy/Cadmin-server-go/utils/set/set.go
  40. 0 0
      src/github.com/baiy/Cadmin-server-go/utils/set/set_test.go
  41. 22 0
      src/github.com/deckarep/golang-set/LICENSE
  42. 95 0
      src/github.com/deckarep/golang-set/README.md
  43. 674 0
      src/github.com/deckarep/golang-set/bench_test.go
  44. 58 0
      src/github.com/deckarep/golang-set/iterator.go
  45. 32 0
      src/github.com/deckarep/golang-set/iterator_example_test.go
  46. 217 0
      src/github.com/deckarep/golang-set/set.go
  47. 1200 0
      src/github.com/deckarep/golang-set/set_test.go
  48. 283 0
      src/github.com/deckarep/golang-set/threadsafe.go
  49. 524 0
      src/github.com/deckarep/golang-set/threadsafe_test.go
  50. 340 0
      src/github.com/deckarep/golang-set/threadunsafe.go
  51. 46 0
      src/github.com/doug-martin/goqu/CODE_OF_CONDUCT.md
  52. 31 0
      src/github.com/doug-martin/goqu/CONTRIBUTING.md
  53. 344 0
      src/github.com/doug-martin/goqu/HISTORY.md
  54. 21 0
      src/github.com/doug-martin/goqu/LICENSE
  55. 6 0
      src/github.com/doug-martin/goqu/Makefile
  56. 310 0
      src/github.com/doug-martin/goqu/README.md
  57. 1 0
      src/github.com/doug-martin/goqu/_config.yml
  58. 3 0
      src/github.com/doug-martin/goqu/codecov.yml
  59. 632 0
      src/github.com/doug-martin/goqu/database.go
  60. 146 0
      src/github.com/doug-martin/goqu/database_example_test.go
  61. 700 0
      src/github.com/doug-martin/goqu/database_test.go
  62. 242 0
      src/github.com/doug-martin/goqu/delete_dataset.go
  63. 312 0
      src/github.com/doug-martin/goqu/delete_dataset_example_test.go
  64. 497 0
      src/github.com/doug-martin/goqu/delete_dataset_test.go
  65. 78 0
      src/github.com/doug-martin/goqu/dialect/mysql/mysql.go
  66. 185 0
      src/github.com/doug-martin/goqu/dialect/mysql/mysql_dialect_test.go
  67. 544 0
      src/github.com/doug-martin/goqu/dialect/mysql/mysql_test.go
  68. 16 0
      src/github.com/doug-martin/goqu/dialect/postgres/postgres.go
  69. 556 0
      src/github.com/doug-martin/goqu/dialect/postgres/postgres_test.go
  70. 65 0
      src/github.com/doug-martin/goqu/dialect/sqlite3/sqlite3.go
  71. 199 0
      src/github.com/doug-martin/goqu/dialect/sqlite3/sqlite3_dialect_test.go
  72. 461 0
      src/github.com/doug-martin/goqu/dialect/sqlite3/sqlite3_test.go
  73. 90 0
      src/github.com/doug-martin/goqu/dialect/sqlserver/sqlserver.go
  74. 535 0
      src/github.com/doug-martin/goqu/dialect/sqlserver/sqlserver_test.go
  75. 62 0
      src/github.com/doug-martin/goqu/docker-compose.yml
  76. 77 0
      src/github.com/doug-martin/goqu/docs/database.md
  77. 314 0
      src/github.com/doug-martin/goqu/docs/deleting.md
  78. 195 0
      src/github.com/doug-martin/goqu/docs/dialect.md
  79. 447 0
      src/github.com/doug-martin/goqu/docs/expressions.md
  80. 496 0
      src/github.com/doug-martin/goqu/docs/inserting.md
  81. 56 0
      src/github.com/doug-martin/goqu/docs/interpolation.md
  82. 1255 0
      src/github.com/doug-martin/goqu/docs/selecting.md
  83. 62 0
      src/github.com/doug-martin/goqu/docs/time.md
  84. 517 0
      src/github.com/doug-martin/goqu/docs/updating.md
  85. 212 0
      src/github.com/doug-martin/goqu/docs/version_migration.md
  86. 282 0
      src/github.com/doug-martin/goqu/exec/query_executor.go
  87. 1184 0
      src/github.com/doug-martin/goqu/exec/query_executor_test.go
  88. 35 0
      src/github.com/doug-martin/goqu/exec/query_factory.go
  89. 108 0
      src/github.com/doug-martin/goqu/exec/scanner.go
  90. 59 0
      src/github.com/doug-martin/goqu/exp/alias.go
  91. 68 0
      src/github.com/doug-martin/goqu/exp/alias_test.go
  92. 180 0
      src/github.com/doug-martin/goqu/exp/bool.go
  93. 75 0
      src/github.com/doug-martin/goqu/exp/case.go
  94. 88 0
      src/github.com/doug-martin/goqu/exp/case_test.go
  95. 56 0
      src/github.com/doug-martin/goqu/exp/cast.go
  96. 79 0
      src/github.com/doug-martin/goqu/exp/cast_test.go
  97. 84 0
      src/github.com/doug-martin/goqu/exp/col.go
  98. 19 0
      src/github.com/doug-martin/goqu/exp/compound.go
  99. 86 0
      src/github.com/doug-martin/goqu/exp/conflict.go
  100. 23 0
      src/github.com/doug-martin/goqu/exp/cte.go

BIN
.DS_Store


+ 0 - 11
Cadmin-server-go/order/router.go

@@ -1,11 +0,0 @@
-package order
-
-import (
-	"github.com/baiy/Cadmin-server-go/admin"
-)
-
-func init() {
-	admin.RegisterDefaultDispatcherHandleMethod(map[string]admin.DefaultDispatcherHandleMethod{
-		"Baiy.Cadmin.Order.dataExportLists": DataExportLists,
-	}, "order")
-}

BIN
src/.DS_Store


+ 8 - 0
src/config.json

@@ -0,0 +1,8 @@
+{
+	"web_port": ":8001",
+	"mysql_username": "root",
+	"mysql_password": "994520",
+	"mysql_addr": "127.0.0.1:3306",
+	"mysql_db_name": "jianyu",
+	"domain": "http://kf-xzh.jianyu360.cn" 
+}

+ 20 - 0
src/config/config.go

@@ -0,0 +1,20 @@
+package config
+
+import (
+	"qfw/util"
+)
+
+type SysConfig struct {
+	WebPort       string `json:"web_port"`
+	MysqlUserName string `json:"mysql_username"`
+	MysqlPassWord string `json:"mysql_password"`
+	MysqlDbName   string `json:"mysql_db_name"`
+	MysqlAddr     string `json:"mysql_addr"`
+	Domain        string `json:"domain"`
+}
+
+var SysConfigs SysConfig
+
+func init() {
+	util.ReadConfig("./config.json", &SysConfigs)
+}

BIN
src/github.com/baiy/.DS_Store


BIN
src/github.com/baiy/Cadmin-server-go/.DS_Store


+ 0 - 0
Cadmin-server-go/LICENSE → src/github.com/baiy/Cadmin-server-go/LICENSE


+ 0 - 0
Cadmin-server-go/admin/context.go → src/github.com/baiy/Cadmin-server-go/admin/context.go


+ 0 - 0
Cadmin-server-go/admin/db.go → src/github.com/baiy/Cadmin-server-go/admin/db.go


+ 5 - 16
Cadmin-server-go/admin/dispatch.go → src/github.com/baiy/Cadmin-server-go/admin/dispatch.go

@@ -20,12 +20,8 @@ type Dispatch interface {
 
 var dispatchers = make(map[string]Dispatch)
 
-func RegisterDispatch(dispatcher Dispatch, typ string) {
-	if typ != "" {
-		dispatchers[strings.ToLower(typ)] = dispatcher
-	} else {
-		dispatchers[strings.ToLower(dispatcher.Key())] = dispatcher
-	}
+func RegisterDispatch(dispatcher Dispatch) {
+	dispatchers[strings.ToLower(dispatcher.Key())] = dispatcher
 }
 
 func GetDispatcher(type_ string) (Dispatch, error) {
@@ -86,19 +82,12 @@ func (d *defaultDispatcher) Register(methods map[string]DefaultDispatcherHandleM
 type DefaultDispatcherHandleMethod func(*Context) (interface{}, error)
 
 var DefaultDispatcher = &defaultDispatcher{HandleMethod: make(map[string]DefaultDispatcherHandleMethod)}
-var OrderDispatcher = &defaultDispatcher{HandleMethod: make(map[string]DefaultDispatcherHandleMethod)}
 
 // 注册默认调度器请求处理方法
-func RegisterDefaultDispatcherHandleMethod(methods map[string]DefaultDispatcherHandleMethod, typ string) {
-	switch typ {
-	case "order":
-		OrderDispatcher.Register(methods)
-	case "":
-		DefaultDispatcher.Register(methods)
-	}
+func RegisterDefaultDispatcherHandleMethod(methods map[string]DefaultDispatcherHandleMethod) {
+	DefaultDispatcher.Register(methods)
 }
 
 func init() {
-	RegisterDispatch(DefaultDispatcher, "")
-	RegisterDispatch(OrderDispatcher, "order")
+	RegisterDispatch(DefaultDispatcher)
 }

+ 0 - 0
Cadmin-server-go/admin/log.go → src/github.com/baiy/Cadmin-server-go/admin/log.go


+ 0 - 0
Cadmin-server-go/admin/password.go → src/github.com/baiy/Cadmin-server-go/admin/password.go


+ 0 - 0
Cadmin-server-go/admin/password_test.go → src/github.com/baiy/Cadmin-server-go/admin/password_test.go


+ 0 - 0
Cadmin-server-go/admin/response.go → src/github.com/baiy/Cadmin-server-go/admin/response.go


+ 0 - 0
Cadmin-server-go/go.mod → src/github.com/baiy/Cadmin-server-go/go.mod


+ 0 - 0
Cadmin-server-go/go.sum → src/github.com/baiy/Cadmin-server-go/go.sum


+ 0 - 0
Cadmin-server-go/main.go → src/github.com/baiy/Cadmin-server-go/main.go


+ 0 - 0
Cadmin-server-go/models/auth/auth.go → src/github.com/baiy/Cadmin-server-go/models/auth/auth.go


+ 0 - 0
Cadmin-server-go/models/menu/menu.go → src/github.com/baiy/Cadmin-server-go/models/menu/menu.go


+ 0 - 0
Cadmin-server-go/models/menuRelate/menuRelate.go → src/github.com/baiy/Cadmin-server-go/models/menuRelate/menuRelate.go


+ 0 - 0
Cadmin-server-go/models/model.go → src/github.com/baiy/Cadmin-server-go/models/model.go


+ 0 - 0
Cadmin-server-go/models/request/request.go → src/github.com/baiy/Cadmin-server-go/models/request/request.go


+ 0 - 0
Cadmin-server-go/models/requestRelate/requestRelate.go → src/github.com/baiy/Cadmin-server-go/models/requestRelate/requestRelate.go


+ 0 - 0
Cadmin-server-go/models/token/token.go → src/github.com/baiy/Cadmin-server-go/models/token/token.go


+ 0 - 0
Cadmin-server-go/models/user/user.go → src/github.com/baiy/Cadmin-server-go/models/user/user.go


+ 0 - 0
Cadmin-server-go/models/userGroup/userGroup.go → src/github.com/baiy/Cadmin-server-go/models/userGroup/userGroup.go


+ 0 - 0
Cadmin-server-go/models/userGroupRelate/userGroupRelate.go → src/github.com/baiy/Cadmin-server-go/models/userGroupRelate/userGroupRelate.go


+ 0 - 0
Cadmin-server-go/models/userRelate/userRelate.go → src/github.com/baiy/Cadmin-server-go/models/userRelate/userRelate.go


+ 0 - 0
Cadmin-server-go/models/utils/time.go → src/github.com/baiy/Cadmin-server-go/models/utils/time.go


+ 0 - 0
Cadmin-server-go/system/auth/auth.go → src/github.com/baiy/Cadmin-server-go/system/auth/auth.go


+ 0 - 0
Cadmin-server-go/system/index/index.go → src/github.com/baiy/Cadmin-server-go/system/index/index.go


+ 0 - 0
Cadmin-server-go/system/menu/menu.go → src/github.com/baiy/Cadmin-server-go/system/menu/menu.go


+ 3 - 3
Cadmin-server-go/system/request/request.go → src/github.com/baiy/Cadmin-server-go/system/request/request.go

@@ -83,11 +83,11 @@ func Remove(context *admin.Context) (interface{}, error) {
 func Type(context *admin.Context) (interface{}, error) {
 	lists := make([]DispatchItem, admin.AllDispatcherLength())
 	i := 0
-	for type_, _ := range admin.AllDispatcher() {
+	for type_, value := range admin.AllDispatcher() {
 		lists[i] = DispatchItem{
 			Type:        type_,
-			Name:        type_,
-			Description: type_ + "请求调度器",
+			Name:        value.Name(),
+			Description: value.Description(),
 		}
 		i++
 	}

+ 7 - 7
Cadmin-server-go/system/router.go → src/github.com/baiy/Cadmin-server-go/system/router.go

@@ -3,12 +3,12 @@
 package system
 
 import (
-	"Cadmin-server-go/system/auth"
-	"Cadmin-server-go/system/index"
-	"Cadmin-server-go/system/menu"
-	"Cadmin-server-go/system/request"
-	"Cadmin-server-go/system/user"
-	"Cadmin-server-go/system/userGroup"
+	"github.com/baiy/Cadmin-server-go/system/auth"
+	"github.com/baiy/Cadmin-server-go/system/index"
+	"github.com/baiy/Cadmin-server-go/system/menu"
+	"github.com/baiy/Cadmin-server-go/system/request"
+	"github.com/baiy/Cadmin-server-go/system/user"
+	"github.com/baiy/Cadmin-server-go/system/userGroup"
 
 	"github.com/baiy/Cadmin-server-go/admin"
 )
@@ -47,5 +47,5 @@ func init() {
 		"Baiy.Cadmin.System.Auth.removeUserGroup": auth.RemoveUserGroup,
 		"Baiy.Cadmin.System.Auth.getMenu":         auth.GetMenu,
 		"Baiy.Cadmin.System.Auth.assignMenu":      auth.AssignMenu,
-	}, "")
+	})
 }

+ 0 - 0
Cadmin-server-go/system/user/user.go → src/github.com/baiy/Cadmin-server-go/system/user/user.go


+ 0 - 0
Cadmin-server-go/system/userGroup/userGroup.go → src/github.com/baiy/Cadmin-server-go/system/userGroup/userGroup.go


+ 0 - 0
Cadmin-server-go/system/utils/page.go → src/github.com/baiy/Cadmin-server-go/system/utils/page.go


+ 0 - 0
Cadmin-server-go/utils/set/set.go → src/github.com/baiy/Cadmin-server-go/utils/set/set.go


+ 0 - 0
Cadmin-server-go/utils/set/set_test.go → src/github.com/baiy/Cadmin-server-go/utils/set/set_test.go


+ 22 - 0
src/github.com/deckarep/golang-set/LICENSE

@@ -0,0 +1,22 @@
+Open Source Initiative OSI - The MIT License (MIT):Licensing
+
+The MIT License (MIT)
+Copyright (c) 2013 Ralph Caraveo (deckarep@gmail.com)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 95 - 0
src/github.com/deckarep/golang-set/README.md

@@ -0,0 +1,95 @@
+[![Build Status](https://travis-ci.org/deckarep/golang-set.svg?branch=master)](https://travis-ci.org/deckarep/golang-set)
+[![Go Report Card](https://goreportcard.com/badge/github.com/deckarep/golang-set)](https://goreportcard.com/report/github.com/deckarep/golang-set)
+[![GoDoc](https://godoc.org/github.com/deckarep/golang-set?status.svg)](http://godoc.org/github.com/deckarep/golang-set)
+
+## golang-set
+
+
+The missing set collection for the Go language.  Until Go has sets built-in...use this.
+
+Coming from Python one of the things I miss is the superbly wonderful set collection.  This is my attempt to mimic the primary features of the set from Python.
+You can of course argue that there is no need for a set in Go, otherwise the creators would have added one to the standard library.  To those I say simply ignore this repository
+and carry-on and to the rest that find this useful please contribute in helping me make it better by:
+
+* Helping to make more idiomatic improvements to the code.
+* Helping to increase the performance of it. ~~(So far, no attempt has been made, but since it uses a map internally, I expect it to be mostly performant.)~~
+* Helping to make the unit-tests more robust and kick-ass.
+* Helping to fill in the [documentation.](http://godoc.org/github.com/deckarep/golang-set)
+* Simply offering feedback and suggestions.  (Positive, constructive feedback is appreciated.)
+
+I have to give some credit for helping seed the idea with this post on [stackoverflow.](http://programmers.stackexchange.com/questions/177428/sets-data-structure-in-golang)
+
+*Update* - as of 3/9/2014, you can use a compile-time generic version of this package in the [gen](http://clipperhouse.github.io/gen/) framework.  This framework allows you to use the golang-set in a completely generic and type-safe way by allowing you to generate a supporting .go file based on your custom types.
+
+## Features (as of 9/22/2014)
+
+* a CartesianProduct() method has been added with unit-tests: [Read more about the cartesian product](http://en.wikipedia.org/wiki/Cartesian_product)
+
+## Features (as of 9/15/2014)
+
+* a PowerSet() method has been added with unit-tests: [Read more about the Power set](http://en.wikipedia.org/wiki/Power_set)
+
+## Features (as of 4/22/2014)
+
+* One common interface to both implementations
+* Two set implementations to choose from
+  * a thread-safe implementation designed for concurrent use
+  * a non-thread-safe implementation designed for performance
+* 75 benchmarks for both implementations
+* 35 unit tests for both implementations
+* 14 concurrent tests for the thread-safe implementation
+
+
+
+Please see the unit test file for additional usage examples.  The Python set documentation will also do a better job than I can of explaining how a set typically [works.](http://docs.python.org/2/library/sets.html)    Please keep in mind
+however that the Python set is a built-in type and supports additional features and syntax that make it awesome.
+
+## Examples but not exhaustive:
+
+```go
+requiredClasses := mapset.NewSet()
+requiredClasses.Add("Cooking")
+requiredClasses.Add("English")
+requiredClasses.Add("Math")
+requiredClasses.Add("Biology")
+
+scienceSlice := []interface{}{"Biology", "Chemistry"}
+scienceClasses := mapset.NewSetFromSlice(scienceSlice)
+
+electiveClasses := mapset.NewSet()
+electiveClasses.Add("Welding")
+electiveClasses.Add("Music")
+electiveClasses.Add("Automotive")
+
+bonusClasses := mapset.NewSet()
+bonusClasses.Add("Go Programming")
+bonusClasses.Add("Python Programming")
+
+//Show me all the available classes I can take
+allClasses := requiredClasses.Union(scienceClasses).Union(electiveClasses).Union(bonusClasses)
+fmt.Println(allClasses) //Set{Cooking, English, Math, Chemistry, Welding, Biology, Music, Automotive, Go Programming, Python Programming}
+
+
+//Is cooking considered a science class?
+fmt.Println(scienceClasses.Contains("Cooking")) //false
+
+//Show me all classes that are not science classes, since I hate science.
+fmt.Println(allClasses.Difference(scienceClasses)) //Set{Music, Automotive, Go Programming, Python Programming, Cooking, English, Math, Welding}
+
+//Which science classes are also required classes?
+fmt.Println(scienceClasses.Intersect(requiredClasses)) //Set{Biology}
+
+//How many bonus classes do you offer?
+fmt.Println(bonusClasses.Cardinality()) //2
+
+//Do you have the following classes? Welding, Automotive and English?
+fmt.Println(allClasses.IsSuperset(mapset.NewSetFromSlice([]interface{}{"Welding", "Automotive", "English"}))) //true
+```
+
+Thanks!
+
+-Ralph
+
+[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/deckarep/golang-set/trend.png)](https://bitdeli.com/free "Bitdeli Badge")
+
+[![Analytics](https://ga-beacon.appspot.com/UA-42584447-2/deckarep/golang-set)](https://github.com/igrigorik/ga-beacon)

+ 674 - 0
src/github.com/deckarep/golang-set/bench_test.go

@@ -0,0 +1,674 @@
+package mapset
+
+import (
+	"math/rand"
+	"testing"
+)
+
+func nrand(n int) []int {
+	i := make([]int, n)
+	for ind := range i {
+		i[ind] = rand.Int()
+	}
+	return i
+}
+
+func toInterfaces(i []int) []interface{} {
+	ifs := make([]interface{}, len(i))
+	for ind, v := range i {
+		ifs[ind] = v
+	}
+	return ifs
+}
+
+func benchAdd(b *testing.B, s Set) {
+	nums := nrand(b.N)
+	b.ResetTimer()
+	for _, v := range nums {
+		s.Add(v)
+	}
+}
+
+func BenchmarkAddSafe(b *testing.B) {
+	benchAdd(b, NewSet())
+}
+
+func BenchmarkAddUnsafe(b *testing.B) {
+	benchAdd(b, NewThreadUnsafeSet())
+}
+
+func benchRemove(b *testing.B, s Set) {
+	nums := nrand(b.N)
+	for _, v := range nums {
+		s.Add(v)
+	}
+
+	b.ResetTimer()
+	for _, v := range nums {
+		s.Remove(v)
+	}
+}
+
+func BenchmarkRemoveSafe(b *testing.B) {
+	benchRemove(b, NewSet())
+}
+
+func BenchmarkRemoveUnsafe(b *testing.B) {
+	benchRemove(b, NewThreadUnsafeSet())
+}
+
+func benchCardinality(b *testing.B, s Set) {
+	for i := 0; i < b.N; i++ {
+		s.Cardinality()
+	}
+}
+
+func BenchmarkCardinalitySafe(b *testing.B) {
+	benchCardinality(b, NewSet())
+}
+
+func BenchmarkCardinalityUnsafe(b *testing.B) {
+	benchCardinality(b, NewThreadUnsafeSet())
+}
+
+func benchClear(b *testing.B, s Set) {
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		s.Clear()
+	}
+}
+
+func BenchmarkClearSafe(b *testing.B) {
+	benchClear(b, NewSet())
+}
+
+func BenchmarkClearUnsafe(b *testing.B) {
+	benchClear(b, NewThreadUnsafeSet())
+}
+
+func benchClone(b *testing.B, n int, s Set) {
+	nums := toInterfaces(nrand(n))
+	for _, v := range nums {
+		s.Add(v)
+	}
+
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		s.Clone()
+	}
+}
+
+func BenchmarkClone1Safe(b *testing.B) {
+	benchClone(b, 1, NewSet())
+}
+
+func BenchmarkClone1Unsafe(b *testing.B) {
+	benchClone(b, 1, NewThreadUnsafeSet())
+}
+
+func BenchmarkClone10Safe(b *testing.B) {
+	benchClone(b, 10, NewSet())
+}
+
+func BenchmarkClone10Unsafe(b *testing.B) {
+	benchClone(b, 10, NewThreadUnsafeSet())
+}
+
+func BenchmarkClone100Safe(b *testing.B) {
+	benchClone(b, 100, NewSet())
+}
+
+func BenchmarkClone100Unsafe(b *testing.B) {
+	benchClone(b, 100, NewThreadUnsafeSet())
+}
+
+func benchContains(b *testing.B, n int, s Set) {
+	nums := toInterfaces(nrand(n))
+	for _, v := range nums {
+		s.Add(v)
+	}
+
+	nums[n-1] = -1 // Definitely not in s
+
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		s.Contains(nums...)
+	}
+}
+
+func BenchmarkContains1Safe(b *testing.B) {
+	benchContains(b, 1, NewSet())
+}
+
+func BenchmarkContains1Unsafe(b *testing.B) {
+	benchContains(b, 1, NewThreadUnsafeSet())
+}
+
+func BenchmarkContains10Safe(b *testing.B) {
+	benchContains(b, 10, NewSet())
+}
+
+func BenchmarkContains10Unsafe(b *testing.B) {
+	benchContains(b, 10, NewThreadUnsafeSet())
+}
+
+func BenchmarkContains100Safe(b *testing.B) {
+	benchContains(b, 100, NewSet())
+}
+
+func BenchmarkContains100Unsafe(b *testing.B) {
+	benchContains(b, 100, NewThreadUnsafeSet())
+}
+
+func benchEqual(b *testing.B, n int, s, t Set) {
+	nums := nrand(n)
+	for _, v := range nums {
+		s.Add(v)
+		t.Add(v)
+	}
+
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		s.Equal(t)
+	}
+}
+
+func BenchmarkEqual1Safe(b *testing.B) {
+	benchEqual(b, 1, NewSet(), NewSet())
+}
+
+func BenchmarkEqual1Unsafe(b *testing.B) {
+	benchEqual(b, 1, NewThreadUnsafeSet(), NewThreadUnsafeSet())
+}
+
+func BenchmarkEqual10Safe(b *testing.B) {
+	benchEqual(b, 10, NewSet(), NewSet())
+}
+
+func BenchmarkEqual10Unsafe(b *testing.B) {
+	benchEqual(b, 10, NewThreadUnsafeSet(), NewThreadUnsafeSet())
+}
+
+func BenchmarkEqual100Safe(b *testing.B) {
+	benchEqual(b, 100, NewSet(), NewSet())
+}
+
+func BenchmarkEqual100Unsafe(b *testing.B) {
+	benchEqual(b, 100, NewThreadUnsafeSet(), NewThreadUnsafeSet())
+}
+
+func benchDifference(b *testing.B, n int, s, t Set) {
+	nums := nrand(n)
+	for _, v := range nums {
+		s.Add(v)
+	}
+	for _, v := range nums[:n/2] {
+		t.Add(v)
+	}
+
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		s.Difference(t)
+	}
+}
+
+func benchIsSubset(b *testing.B, n int, s, t Set) {
+	nums := nrand(n)
+	for _, v := range nums {
+		s.Add(v)
+		t.Add(v)
+	}
+
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		s.IsSubset(t)
+	}
+}
+
+func BenchmarkIsSubset1Safe(b *testing.B) {
+	benchIsSubset(b, 1, NewSet(), NewSet())
+}
+
+func BenchmarkIsSubset1Unsafe(b *testing.B) {
+	benchIsSubset(b, 1, NewThreadUnsafeSet(), NewThreadUnsafeSet())
+}
+
+func BenchmarkIsSubset10Safe(b *testing.B) {
+	benchIsSubset(b, 10, NewSet(), NewSet())
+}
+
+func BenchmarkIsSubset10Unsafe(b *testing.B) {
+	benchIsSubset(b, 10, NewThreadUnsafeSet(), NewThreadUnsafeSet())
+}
+
+func BenchmarkIsSubset100Safe(b *testing.B) {
+	benchIsSubset(b, 100, NewSet(), NewSet())
+}
+
+func BenchmarkIsSubset100Unsafe(b *testing.B) {
+	benchIsSubset(b, 100, NewThreadUnsafeSet(), NewThreadUnsafeSet())
+}
+
+func benchIsSuperset(b *testing.B, n int, s, t Set) {
+	nums := nrand(n)
+	for _, v := range nums {
+		s.Add(v)
+		t.Add(v)
+	}
+
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		s.IsSuperset(t)
+	}
+}
+
+func BenchmarkIsSuperset1Safe(b *testing.B) {
+	benchIsSuperset(b, 1, NewSet(), NewSet())
+}
+
+func BenchmarkIsSuperset1Unsafe(b *testing.B) {
+	benchIsSuperset(b, 1, NewThreadUnsafeSet(), NewThreadUnsafeSet())
+}
+
+func BenchmarkIsSuperset10Safe(b *testing.B) {
+	benchIsSuperset(b, 10, NewSet(), NewSet())
+}
+
+func BenchmarkIsSuperset10Unsafe(b *testing.B) {
+	benchIsSuperset(b, 10, NewThreadUnsafeSet(), NewThreadUnsafeSet())
+}
+
+func BenchmarkIsSuperset100Safe(b *testing.B) {
+	benchIsSuperset(b, 100, NewSet(), NewSet())
+}
+
+func BenchmarkIsSuperset100Unsafe(b *testing.B) {
+	benchIsSuperset(b, 100, NewThreadUnsafeSet(), NewThreadUnsafeSet())
+}
+
+func benchIsProperSubset(b *testing.B, n int, s, t Set) {
+	nums := nrand(n)
+	for _, v := range nums {
+		s.Add(v)
+		t.Add(v)
+	}
+
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		s.IsProperSubset(t)
+	}
+}
+
+func BenchmarkIsProperSubset1Safe(b *testing.B) {
+	benchIsProperSubset(b, 1, NewSet(), NewSet())
+}
+
+func BenchmarkIsProperSubset1Unsafe(b *testing.B) {
+	benchIsProperSubset(b, 1, NewThreadUnsafeSet(), NewThreadUnsafeSet())
+}
+
+func BenchmarkIsProperSubset10Safe(b *testing.B) {
+	benchIsProperSubset(b, 10, NewSet(), NewSet())
+}
+
+func BenchmarkIsProperSubset10Unsafe(b *testing.B) {
+	benchIsProperSubset(b, 10, NewThreadUnsafeSet(), NewThreadUnsafeSet())
+}
+
+func BenchmarkIsProperSubset100Safe(b *testing.B) {
+	benchIsProperSubset(b, 100, NewSet(), NewSet())
+}
+
+func BenchmarkIsProperSubset100Unsafe(b *testing.B) {
+	benchIsProperSubset(b, 100, NewThreadUnsafeSet(), NewThreadUnsafeSet())
+}
+
+func benchIsProperSuperset(b *testing.B, n int, s, t Set) {
+	nums := nrand(n)
+	for _, v := range nums {
+		s.Add(v)
+		t.Add(v)
+	}
+
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		s.IsProperSuperset(t)
+	}
+}
+
+func BenchmarkIsProperSuperset1Safe(b *testing.B) {
+	benchIsProperSuperset(b, 1, NewSet(), NewSet())
+}
+
+func BenchmarkIsProperSuperset1Unsafe(b *testing.B) {
+	benchIsProperSuperset(b, 1, NewThreadUnsafeSet(), NewThreadUnsafeSet())
+}
+
+func BenchmarkIsProperSuperset10Safe(b *testing.B) {
+	benchIsProperSuperset(b, 10, NewSet(), NewSet())
+}
+
+func BenchmarkIsProperSuperset10Unsafe(b *testing.B) {
+	benchIsProperSuperset(b, 10, NewThreadUnsafeSet(), NewThreadUnsafeSet())
+}
+
+func BenchmarkIsProperSuperset100Safe(b *testing.B) {
+	benchIsProperSuperset(b, 100, NewSet(), NewSet())
+}
+
+func BenchmarkIsProperSuperset100Unsafe(b *testing.B) {
+	benchIsProperSuperset(b, 100, NewThreadUnsafeSet(), NewThreadUnsafeSet())
+}
+
+func BenchmarkDifference1Safe(b *testing.B) {
+	benchDifference(b, 1, NewSet(), NewSet())
+}
+
+func BenchmarkDifference1Unsafe(b *testing.B) {
+	benchDifference(b, 1, NewThreadUnsafeSet(), NewThreadUnsafeSet())
+}
+
+func BenchmarkDifference10Safe(b *testing.B) {
+	benchDifference(b, 10, NewSet(), NewSet())
+}
+
+func BenchmarkDifference10Unsafe(b *testing.B) {
+	benchDifference(b, 10, NewThreadUnsafeSet(), NewThreadUnsafeSet())
+}
+
+func BenchmarkDifference100Safe(b *testing.B) {
+	benchDifference(b, 100, NewSet(), NewSet())
+}
+
+func BenchmarkDifference100Unsafe(b *testing.B) {
+	benchDifference(b, 100, NewThreadUnsafeSet(), NewThreadUnsafeSet())
+}
+
+func benchIntersect(b *testing.B, n int, s, t Set) {
+	nums := nrand(int(float64(n) * float64(1.5)))
+	for _, v := range nums[:n] {
+		s.Add(v)
+	}
+	for _, v := range nums[n/2:] {
+		t.Add(v)
+	}
+
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		s.Intersect(t)
+	}
+}
+
+func BenchmarkIntersect1Safe(b *testing.B) {
+	benchIntersect(b, 1, NewSet(), NewSet())
+}
+
+func BenchmarkIntersect1Unsafe(b *testing.B) {
+	benchIntersect(b, 1, NewThreadUnsafeSet(), NewThreadUnsafeSet())
+}
+
+func BenchmarkIntersect10Safe(b *testing.B) {
+	benchIntersect(b, 10, NewSet(), NewSet())
+}
+
+func BenchmarkIntersect10Unsafe(b *testing.B) {
+	benchIntersect(b, 10, NewThreadUnsafeSet(), NewThreadUnsafeSet())
+}
+
+func BenchmarkIntersect100Safe(b *testing.B) {
+	benchIntersect(b, 100, NewSet(), NewSet())
+}
+
+func BenchmarkIntersect100Unsafe(b *testing.B) {
+	benchIntersect(b, 100, NewThreadUnsafeSet(), NewThreadUnsafeSet())
+}
+
+func benchSymmetricDifference(b *testing.B, n int, s, t Set) {
+	nums := nrand(int(float64(n) * float64(1.5)))
+	for _, v := range nums[:n] {
+		s.Add(v)
+	}
+	for _, v := range nums[n/2:] {
+		t.Add(v)
+	}
+
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		s.SymmetricDifference(t)
+	}
+}
+
+func BenchmarkSymmetricDifference1Safe(b *testing.B) {
+	benchSymmetricDifference(b, 1, NewSet(), NewSet())
+}
+
+func BenchmarkSymmetricDifference1Unsafe(b *testing.B) {
+	benchSymmetricDifference(b, 1, NewThreadUnsafeSet(), NewThreadUnsafeSet())
+}
+
+func BenchmarkSymmetricDifference10Safe(b *testing.B) {
+	benchSymmetricDifference(b, 10, NewSet(), NewSet())
+}
+
+func BenchmarkSymmetricDifference10Unsafe(b *testing.B) {
+	benchSymmetricDifference(b, 10, NewThreadUnsafeSet(), NewThreadUnsafeSet())
+}
+
+func BenchmarkSymmetricDifference100Safe(b *testing.B) {
+	benchSymmetricDifference(b, 100, NewSet(), NewSet())
+}
+
+func BenchmarkSymmetricDifference100Unsafe(b *testing.B) {
+	benchSymmetricDifference(b, 100, NewThreadUnsafeSet(), NewThreadUnsafeSet())
+}
+
+func benchUnion(b *testing.B, n int, s, t Set) {
+	nums := nrand(n)
+	for _, v := range nums[:n/2] {
+		s.Add(v)
+	}
+	for _, v := range nums[n/2:] {
+		t.Add(v)
+	}
+
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		s.Union(t)
+	}
+}
+
+func BenchmarkUnion1Safe(b *testing.B) {
+	benchUnion(b, 1, NewSet(), NewSet())
+}
+
+func BenchmarkUnion1Unsafe(b *testing.B) {
+	benchUnion(b, 1, NewThreadUnsafeSet(), NewThreadUnsafeSet())
+}
+
+func BenchmarkUnion10Safe(b *testing.B) {
+	benchUnion(b, 10, NewSet(), NewSet())
+}
+
+func BenchmarkUnion10Unsafe(b *testing.B) {
+	benchUnion(b, 10, NewThreadUnsafeSet(), NewThreadUnsafeSet())
+}
+
+func BenchmarkUnion100Safe(b *testing.B) {
+	benchUnion(b, 100, NewSet(), NewSet())
+}
+
+func BenchmarkUnion100Unsafe(b *testing.B) {
+	benchUnion(b, 100, NewThreadUnsafeSet(), NewThreadUnsafeSet())
+}
+
+func benchEach(b *testing.B, n int, s Set) {
+	nums := nrand(n)
+	for _, v := range nums {
+		s.Add(v)
+	}
+
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		s.Each(func(elem interface{}) bool {
+			return false
+		})
+	}
+}
+
+func BenchmarkEach1Safe(b *testing.B) {
+	benchEach(b, 1, NewSet())
+}
+
+func BenchmarkEach1Unsafe(b *testing.B) {
+	benchEach(b, 1, NewThreadUnsafeSet())
+}
+
+func BenchmarkEach10Safe(b *testing.B) {
+	benchEach(b, 10, NewSet())
+}
+
+func BenchmarkEach10Unsafe(b *testing.B) {
+	benchEach(b, 10, NewThreadUnsafeSet())
+}
+
+func BenchmarkEach100Safe(b *testing.B) {
+	benchEach(b, 100, NewSet())
+}
+
+func BenchmarkEach100Unsafe(b *testing.B) {
+	benchEach(b, 100, NewThreadUnsafeSet())
+}
+
+func benchIter(b *testing.B, n int, s Set) {
+	nums := nrand(n)
+	for _, v := range nums {
+		s.Add(v)
+	}
+
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		c := s.Iter()
+		for range c {
+
+		}
+	}
+}
+
+func BenchmarkIter1Safe(b *testing.B) {
+	benchIter(b, 1, NewSet())
+}
+
+func BenchmarkIter1Unsafe(b *testing.B) {
+	benchIter(b, 1, NewThreadUnsafeSet())
+}
+
+func BenchmarkIter10Safe(b *testing.B) {
+	benchIter(b, 10, NewSet())
+}
+
+func BenchmarkIter10Unsafe(b *testing.B) {
+	benchIter(b, 10, NewThreadUnsafeSet())
+}
+
+func BenchmarkIter100Safe(b *testing.B) {
+	benchIter(b, 100, NewSet())
+}
+
+func BenchmarkIter100Unsafe(b *testing.B) {
+	benchIter(b, 100, NewThreadUnsafeSet())
+}
+
+func benchIterator(b *testing.B, n int, s Set) {
+	nums := nrand(n)
+	for _, v := range nums {
+		s.Add(v)
+	}
+
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		c := s.Iterator().C
+		for range c {
+
+		}
+	}
+}
+
+func BenchmarkIterator1Safe(b *testing.B) {
+	benchIterator(b, 1, NewSet())
+}
+
+func BenchmarkIterator1Unsafe(b *testing.B) {
+	benchIterator(b, 1, NewThreadUnsafeSet())
+}
+
+func BenchmarkIterator10Safe(b *testing.B) {
+	benchIterator(b, 10, NewSet())
+}
+
+func BenchmarkIterator10Unsafe(b *testing.B) {
+	benchIterator(b, 10, NewThreadUnsafeSet())
+}
+
+func BenchmarkIterator100Safe(b *testing.B) {
+	benchIterator(b, 100, NewSet())
+}
+
+func BenchmarkIterator100Unsafe(b *testing.B) {
+	benchIterator(b, 100, NewThreadUnsafeSet())
+}
+
+func benchString(b *testing.B, n int, s Set) {
+	nums := nrand(n)
+	for _, v := range nums {
+		s.Add(v)
+	}
+
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		_ = s.String()
+	}
+}
+
+func BenchmarkString1Safe(b *testing.B) {
+	benchString(b, 1, NewSet())
+}
+
+func BenchmarkString1Unsafe(b *testing.B) {
+	benchString(b, 1, NewThreadUnsafeSet())
+}
+
+func BenchmarkString10Safe(b *testing.B) {
+	benchString(b, 10, NewSet())
+}
+
+func BenchmarkString10Unsafe(b *testing.B) {
+	benchString(b, 10, NewThreadUnsafeSet())
+}
+
+func BenchmarkString100Safe(b *testing.B) {
+	benchString(b, 100, NewSet())
+}
+
+func BenchmarkString100Unsafe(b *testing.B) {
+	benchString(b, 100, NewThreadUnsafeSet())
+}
+
+func benchToSlice(b *testing.B, s Set) {
+	nums := nrand(b.N)
+	for _, v := range nums {
+		s.Add(v)
+	}
+
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		s.ToSlice()
+	}
+}
+
+func BenchmarkToSliceSafe(b *testing.B) {
+	benchToSlice(b, NewSet())
+}
+
+func BenchmarkToSliceUnsafe(b *testing.B) {
+	benchToSlice(b, NewThreadUnsafeSet())
+}

+ 58 - 0
src/github.com/deckarep/golang-set/iterator.go

@@ -0,0 +1,58 @@
+/*
+Open Source Initiative OSI - The MIT License (MIT):Licensing
+
+The MIT License (MIT)
+Copyright (c) 2013 Ralph Caraveo (deckarep@gmail.com)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+package mapset
+
+// Iterator defines an iterator over a Set, its C channel can be used to range over the Set's
+// elements.
+type Iterator struct {
+	C    <-chan interface{}
+	stop chan struct{}
+}
+
+// Stop stops the Iterator, no further elements will be received on C, C will be closed.
+func (i *Iterator) Stop() {
+	// Allows for Stop() to be called multiple times
+	// (close() panics when called on already closed channel)
+	defer func() {
+		recover()
+	}()
+
+	close(i.stop)
+
+	// Exhaust any remaining elements.
+	for range i.C {
+	}
+}
+
+// newIterator returns a new Iterator instance together with its item and stop channels.
+func newIterator() (*Iterator, chan<- interface{}, <-chan struct{}) {
+	itemChan := make(chan interface{})
+	stopChan := make(chan struct{})
+	return &Iterator{
+		C:    itemChan,
+		stop: stopChan,
+	}, itemChan, stopChan
+}

+ 32 - 0
src/github.com/deckarep/golang-set/iterator_example_test.go

@@ -0,0 +1,32 @@
+package mapset
+
+import (
+	"fmt"
+)
+
+type YourType struct {
+	Name string
+}
+
+func ExampleIterator() {
+	set := NewSetFromSlice([]interface{}{
+		&YourType{Name: "Alise"},
+		&YourType{Name: "Bob"},
+		&YourType{Name: "John"},
+		&YourType{Name: "Nick"},
+	})
+
+	var found *YourType
+	it := set.Iterator()
+
+	for elem := range it.C {
+		if elem.(*YourType).Name == "John" {
+			found = elem.(*YourType)
+			it.Stop()
+		}
+	}
+
+	fmt.Printf("Found %+v\n", found)
+
+	// Output: Found &{Name:John}
+}

+ 217 - 0
src/github.com/deckarep/golang-set/set.go

@@ -0,0 +1,217 @@
+/*
+Open Source Initiative OSI - The MIT License (MIT):Licensing
+
+The MIT License (MIT)
+Copyright (c) 2013 Ralph Caraveo (deckarep@gmail.com)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+// Package mapset implements a simple and generic set collection.
+// Items stored within it are unordered and unique. It supports
+// typical set operations: membership testing, intersection, union,
+// difference, symmetric difference and cloning.
+//
+// Package mapset provides two implementations of the Set
+// interface. The default implementation is safe for concurrent
+// access, but a non-thread-safe implementation is also provided for
+// programs that can benefit from the slight speed improvement and
+// that can enforce mutual exclusion through other means.
+package mapset
+
+// Set is the primary interface provided by the mapset package.  It
+// represents an unordered set of data and a large number of
+// operations that can be applied to that set.
+type Set interface {
+	// Adds an element to the set. Returns whether
+	// the item was added.
+	Add(i interface{}) bool
+
+	// Returns the number of elements in the set.
+	Cardinality() int
+
+	// Removes all elements from the set, leaving
+	// the empty set.
+	Clear()
+
+	// Returns a clone of the set using the same
+	// implementation, duplicating all keys.
+	Clone() Set
+
+	// Returns whether the given items
+	// are all in the set.
+	Contains(i ...interface{}) bool
+
+	// Returns the difference between this set
+	// and other. The returned set will contain
+	// all elements of this set that are not also
+	// elements of other.
+	//
+	// Note that the argument to Difference
+	// must be of the same type as the receiver
+	// of the method. Otherwise, Difference will
+	// panic.
+	Difference(other Set) Set
+
+	// Determines if two sets are equal to each
+	// other. If they have the same cardinality
+	// and contain the same elements, they are
+	// considered equal. The order in which
+	// the elements were added is irrelevant.
+	//
+	// Note that the argument to Equal must be
+	// of the same type as the receiver of the
+	// method. Otherwise, Equal will panic.
+	Equal(other Set) bool
+
+	// Returns a new set containing only the elements
+	// that exist only in both sets.
+	//
+	// Note that the argument to Intersect
+	// must be of the same type as the receiver
+	// of the method. Otherwise, Intersect will
+	// panic.
+	Intersect(other Set) Set
+
+	// Determines if every element in this set is in
+	// the other set but the two sets are not equal.
+	//
+	// Note that the argument to IsProperSubset
+	// must be of the same type as the receiver
+	// of the method. Otherwise, IsProperSubset
+	// will panic.
+	IsProperSubset(other Set) bool
+
+	// Determines if every element in the other set
+	// is in this set but the two sets are not
+	// equal.
+	//
+	// Note that the argument to IsSuperset
+	// must be of the same type as the receiver
+	// of the method. Otherwise, IsSuperset will
+	// panic.
+	IsProperSuperset(other Set) bool
+
+	// Determines if every element in this set is in
+	// the other set.
+	//
+	// Note that the argument to IsSubset
+	// must be of the same type as the receiver
+	// of the method. Otherwise, IsSubset will
+	// panic.
+	IsSubset(other Set) bool
+
+	// Determines if every element in the other set
+	// is in this set.
+	//
+	// Note that the argument to IsSuperset
+	// must be of the same type as the receiver
+	// of the method. Otherwise, IsSuperset will
+	// panic.
+	IsSuperset(other Set) bool
+
+	// Iterates over elements and executes the passed func against each element.
+	// If passed func returns true, stop iteration at the time.
+	Each(func(interface{}) bool)
+
+	// Returns a channel of elements that you can
+	// range over.
+	Iter() <-chan interface{}
+
+	// Returns an Iterator object that you can
+	// use to range over the set.
+	Iterator() *Iterator
+
+	// Remove a single element from the set.
+	Remove(i interface{})
+
+	// Provides a convenient string representation
+	// of the current state of the set.
+	String() string
+
+	// Returns a new set with all elements which are
+	// in either this set or the other set but not in both.
+	//
+	// Note that the argument to SymmetricDifference
+	// must be of the same type as the receiver
+	// of the method. Otherwise, SymmetricDifference
+	// will panic.
+	SymmetricDifference(other Set) Set
+
+	// Returns a new set with all elements in both sets.
+	//
+	// Note that the argument to Union must be of the
+
+	// same type as the receiver of the method.
+	// Otherwise, IsSuperset will panic.
+	Union(other Set) Set
+
+	// Pop removes and returns an arbitrary item from the set.
+	Pop() interface{}
+
+	// Returns all subsets of a given set (Power Set).
+	PowerSet() Set
+
+	// Returns the Cartesian Product of two sets.
+	CartesianProduct(other Set) Set
+
+	// Returns the members of the set as a slice.
+	ToSlice() []interface{}
+}
+
+// NewSet creates and returns a reference to an empty set.  Operations
+// on the resulting set are thread-safe.
+func NewSet(s ...interface{}) Set {
+	set := newThreadSafeSet()
+	for _, item := range s {
+		set.Add(item)
+	}
+	return &set
+}
+
+// NewSetWith creates and returns a new set with the given elements.
+// Operations on the resulting set are thread-safe.
+func NewSetWith(elts ...interface{}) Set {
+	return NewSetFromSlice(elts)
+}
+
+// NewSetFromSlice creates and returns a reference to a set from an
+// existing slice.  Operations on the resulting set are thread-safe.
+func NewSetFromSlice(s []interface{}) Set {
+	a := NewSet(s...)
+	return a
+}
+
+// NewThreadUnsafeSet creates and returns a reference to an empty set.
+// Operations on the resulting set are not thread-safe.
+func NewThreadUnsafeSet() Set {
+	set := newThreadUnsafeSet()
+	return &set
+}
+
+// NewThreadUnsafeSetFromSlice creates and returns a reference to a
+// set from an existing slice.  Operations on the resulting set are
+// not thread-safe.
+func NewThreadUnsafeSetFromSlice(s []interface{}) Set {
+	a := NewThreadUnsafeSet()
+	for _, item := range s {
+		a.Add(item)
+	}
+	return a
+}

+ 1200 - 0
src/github.com/deckarep/golang-set/set_test.go

@@ -0,0 +1,1200 @@
+/*
+Open Source Initiative OSI - The MIT License (MIT):Licensing
+
+The MIT License (MIT)
+Copyright (c) 2013 Ralph Caraveo (deckarep@gmail.com)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+package mapset
+
+import "testing"
+
+func makeSet(ints []int) Set {
+	set := NewSet()
+	for _, i := range ints {
+		set.Add(i)
+	}
+	return set
+}
+
+func makeUnsafeSet(ints []int) Set {
+	set := NewThreadUnsafeSet()
+	for _, i := range ints {
+		set.Add(i)
+	}
+	return set
+}
+
+func assertEqual(a, b Set, t *testing.T) {
+	if !a.Equal(b) {
+		t.Errorf("%v != %v\n", a, b)
+	}
+}
+
+func Test_NewSet(t *testing.T) {
+	a := NewSet()
+	if a.Cardinality() != 0 {
+		t.Error("NewSet should start out as an empty set")
+	}
+
+	assertEqual(NewSetFromSlice([]interface{}{}), NewSet(), t)
+	assertEqual(NewSetFromSlice([]interface{}{1}), NewSet(1), t)
+	assertEqual(NewSetFromSlice([]interface{}{1, 2}), NewSet(1, 2), t)
+	assertEqual(NewSetFromSlice([]interface{}{"a"}), NewSet("a"), t)
+	assertEqual(NewSetFromSlice([]interface{}{"a", "b"}), NewSet("a", "b"), t)
+}
+
+func Test_NewUnsafeSet(t *testing.T) {
+	a := NewThreadUnsafeSet()
+
+	if a.Cardinality() != 0 {
+		t.Error("NewSet should start out as an empty set")
+	}
+}
+
+func Test_AddSet(t *testing.T) {
+	a := makeSet([]int{1, 2, 3})
+
+	if a.Cardinality() != 3 {
+		t.Error("AddSet does not have a size of 3 even though 3 items were added to a new set")
+	}
+}
+
+func Test_AddUnsafeSet(t *testing.T) {
+	a := makeUnsafeSet([]int{1, 2, 3})
+
+	if a.Cardinality() != 3 {
+		t.Error("AddSet does not have a size of 3 even though 3 items were added to a new set")
+	}
+}
+
+func Test_AddSetNoDuplicate(t *testing.T) {
+	a := makeSet([]int{7, 5, 3, 7})
+
+	if a.Cardinality() != 3 {
+		t.Error("AddSetNoDuplicate set should have 3 elements since 7 is a duplicate")
+	}
+
+	if !(a.Contains(7) && a.Contains(5) && a.Contains(3)) {
+		t.Error("AddSetNoDuplicate set should have a 7, 5, and 3 in it.")
+	}
+}
+
+func Test_AddUnsafeSetNoDuplicate(t *testing.T) {
+	a := makeUnsafeSet([]int{7, 5, 3, 7})
+
+	if a.Cardinality() != 3 {
+		t.Error("AddSetNoDuplicate set should have 3 elements since 7 is a duplicate")
+	}
+
+	if !(a.Contains(7) && a.Contains(5) && a.Contains(3)) {
+		t.Error("AddSetNoDuplicate set should have a 7, 5, and 3 in it.")
+	}
+}
+
+func Test_RemoveSet(t *testing.T) {
+	a := makeSet([]int{6, 3, 1})
+
+	a.Remove(3)
+
+	if a.Cardinality() != 2 {
+		t.Error("RemoveSet should only have 2 items in the set")
+	}
+
+	if !(a.Contains(6) && a.Contains(1)) {
+		t.Error("RemoveSet should have only items 6 and 1 in the set")
+	}
+
+	a.Remove(6)
+	a.Remove(1)
+
+	if a.Cardinality() != 0 {
+		t.Error("RemoveSet should be an empty set after removing 6 and 1")
+	}
+}
+
+func Test_RemoveUnsafeSet(t *testing.T) {
+	a := makeUnsafeSet([]int{6, 3, 1})
+
+	a.Remove(3)
+
+	if a.Cardinality() != 2 {
+		t.Error("RemoveSet should only have 2 items in the set")
+	}
+
+	if !(a.Contains(6) && a.Contains(1)) {
+		t.Error("RemoveSet should have only items 6 and 1 in the set")
+	}
+
+	a.Remove(6)
+	a.Remove(1)
+
+	if a.Cardinality() != 0 {
+		t.Error("RemoveSet should be an empty set after removing 6 and 1")
+	}
+}
+
+func Test_ContainsSet(t *testing.T) {
+	a := NewSet()
+
+	a.Add(71)
+
+	if !a.Contains(71) {
+		t.Error("ContainsSet should contain 71")
+	}
+
+	a.Remove(71)
+
+	if a.Contains(71) {
+		t.Error("ContainsSet should not contain 71")
+	}
+
+	a.Add(13)
+	a.Add(7)
+	a.Add(1)
+
+	if !(a.Contains(13) && a.Contains(7) && a.Contains(1)) {
+		t.Error("ContainsSet should contain 13, 7, 1")
+	}
+}
+
+func Test_ContainsUnsafeSet(t *testing.T) {
+	a := NewThreadUnsafeSet()
+
+	a.Add(71)
+
+	if !a.Contains(71) {
+		t.Error("ContainsSet should contain 71")
+	}
+
+	a.Remove(71)
+
+	if a.Contains(71) {
+		t.Error("ContainsSet should not contain 71")
+	}
+
+	a.Add(13)
+	a.Add(7)
+	a.Add(1)
+
+	if !(a.Contains(13) && a.Contains(7) && a.Contains(1)) {
+		t.Error("ContainsSet should contain 13, 7, 1")
+	}
+}
+
+func Test_ContainsMultipleSet(t *testing.T) {
+	a := makeSet([]int{8, 6, 7, 5, 3, 0, 9})
+
+	if !a.Contains(8, 6, 7, 5, 3, 0, 9) {
+		t.Error("ContainsAll should contain Jenny's phone number")
+	}
+
+	if a.Contains(8, 6, 11, 5, 3, 0, 9) {
+		t.Error("ContainsAll should not have all of these numbers")
+	}
+}
+
+func Test_ContainsMultipleUnsafeSet(t *testing.T) {
+	a := makeUnsafeSet([]int{8, 6, 7, 5, 3, 0, 9})
+
+	if !a.Contains(8, 6, 7, 5, 3, 0, 9) {
+		t.Error("ContainsAll should contain Jenny's phone number")
+	}
+
+	if a.Contains(8, 6, 11, 5, 3, 0, 9) {
+		t.Error("ContainsAll should not have all of these numbers")
+	}
+}
+
+func Test_ClearSet(t *testing.T) {
+	a := makeSet([]int{2, 5, 9, 10})
+
+	a.Clear()
+
+	if a.Cardinality() != 0 {
+		t.Error("ClearSet should be an empty set")
+	}
+}
+
+func Test_ClearUnsafeSet(t *testing.T) {
+	a := makeUnsafeSet([]int{2, 5, 9, 10})
+
+	a.Clear()
+
+	if a.Cardinality() != 0 {
+		t.Error("ClearSet should be an empty set")
+	}
+}
+
+func Test_CardinalitySet(t *testing.T) {
+	a := NewSet()
+
+	if a.Cardinality() != 0 {
+		t.Error("set should be an empty set")
+	}
+
+	a.Add(1)
+
+	if a.Cardinality() != 1 {
+		t.Error("set should have a size of 1")
+	}
+
+	a.Remove(1)
+
+	if a.Cardinality() != 0 {
+		t.Error("set should be an empty set")
+	}
+
+	a.Add(9)
+
+	if a.Cardinality() != 1 {
+		t.Error("set should have a size of 1")
+	}
+
+	a.Clear()
+
+	if a.Cardinality() != 0 {
+		t.Error("set should have a size of 1")
+	}
+}
+
+func Test_CardinalityUnsafeSet(t *testing.T) {
+	a := NewThreadUnsafeSet()
+
+	if a.Cardinality() != 0 {
+		t.Error("set should be an empty set")
+	}
+
+	a.Add(1)
+
+	if a.Cardinality() != 1 {
+		t.Error("set should have a size of 1")
+	}
+
+	a.Remove(1)
+
+	if a.Cardinality() != 0 {
+		t.Error("set should be an empty set")
+	}
+
+	a.Add(9)
+
+	if a.Cardinality() != 1 {
+		t.Error("set should have a size of 1")
+	}
+
+	a.Clear()
+
+	if a.Cardinality() != 0 {
+		t.Error("set should have a size of 1")
+	}
+}
+
+func Test_SetIsSubset(t *testing.T) {
+	a := makeSet([]int{1, 2, 3, 5, 7})
+
+	b := NewSet()
+	b.Add(3)
+	b.Add(5)
+	b.Add(7)
+
+	if !b.IsSubset(a) {
+		t.Error("set b should be a subset of set a")
+	}
+
+	b.Add(72)
+
+	if b.IsSubset(a) {
+		t.Error("set b should not be a subset of set a because it contains 72 which is not in the set of a")
+	}
+}
+
+func Test_SetIsProperSubset(t *testing.T) {
+	a := makeSet([]int{1, 2, 3, 5, 7})
+	b := makeSet([]int{7, 5, 3, 2, 1})
+
+	if !a.IsSubset(b) {
+		t.Error("set a should be a subset of set b")
+	}
+	if a.IsProperSubset(b) {
+		t.Error("set a should not be a proper subset of set b (they're equal)")
+	}
+
+	b.Add(72)
+
+	if !a.IsSubset(b) {
+		t.Error("set a should be a subset of set b")
+	}
+	if !a.IsProperSubset(b) {
+		t.Error("set a should be a proper subset of set b")
+	}
+}
+
+func Test_UnsafeSetIsSubset(t *testing.T) {
+	a := makeUnsafeSet([]int{1, 2, 3, 5, 7})
+
+	b := NewThreadUnsafeSet()
+	b.Add(3)
+	b.Add(5)
+	b.Add(7)
+
+	if !b.IsSubset(a) {
+		t.Error("set b should be a subset of set a")
+	}
+
+	b.Add(72)
+
+	if b.IsSubset(a) {
+		t.Error("set b should not be a subset of set a because it contains 72 which is not in the set of a")
+	}
+}
+
+func Test_UnsafeSetIsProperSubset(t *testing.T) {
+	a := makeUnsafeSet([]int{1, 2, 3, 5, 7})
+	b := NewThreadUnsafeSet()
+	b.Add(7)
+	b.Add(1)
+	b.Add(5)
+	b.Add(3)
+	b.Add(2)
+
+	if !a.IsSubset(b) {
+		t.Error("set a should be a subset of set b")
+	}
+	if a.IsProperSubset(b) {
+		t.Error("set a should not be a proper subset of set b (they're equal)")
+	}
+
+	b.Add(72)
+
+	if !a.IsSubset(b) {
+		t.Error("set a should be a subset of set b")
+	}
+	if !a.IsProperSubset(b) {
+		t.Error("set a should be a proper subset of set b because set b has 72")
+	}
+}
+
+func Test_SetIsSuperset(t *testing.T) {
+	a := NewSet()
+	a.Add(9)
+	a.Add(5)
+	a.Add(2)
+	a.Add(1)
+	a.Add(11)
+
+	b := NewSet()
+	b.Add(5)
+	b.Add(2)
+	b.Add(11)
+
+	if !a.IsSuperset(b) {
+		t.Error("set a should be a superset of set b")
+	}
+
+	b.Add(42)
+
+	if a.IsSuperset(b) {
+		t.Error("set a should not be a superset of set b because set b has a 42")
+	}
+}
+
+func Test_SetIsProperSuperset(t *testing.T) {
+	a := NewSet()
+	a.Add(5)
+	a.Add(2)
+	a.Add(11)
+
+	b := NewSet()
+	b.Add(2)
+	b.Add(5)
+	b.Add(11)
+
+	if !a.IsSuperset(b) {
+		t.Error("set a should be a superset of set b")
+	}
+	if a.IsProperSuperset(b) {
+		t.Error("set a should not be a proper superset of set b (they're equal)")
+	}
+
+	a.Add(9)
+
+	if !a.IsSuperset(b) {
+		t.Error("set a should be a superset of set b")
+	}
+	if !a.IsProperSuperset(b) {
+		t.Error("set a not be a proper superset of set b because set a has a 9")
+	}
+
+	b.Add(42)
+
+	if a.IsSuperset(b) {
+		t.Error("set a should not be a superset of set b because set b has a 42")
+	}
+	if a.IsProperSuperset(b) {
+		t.Error("set a should not be a proper superset of set b because set b has a 42")
+	}
+}
+
+func Test_UnsafeSetIsSuperset(t *testing.T) {
+	a := NewThreadUnsafeSet()
+	a.Add(9)
+	a.Add(5)
+	a.Add(2)
+	a.Add(1)
+	a.Add(11)
+
+	b := NewThreadUnsafeSet()
+	b.Add(5)
+	b.Add(2)
+	b.Add(11)
+
+	if !a.IsSuperset(b) {
+		t.Error("set a should be a superset of set b")
+	}
+
+	b.Add(42)
+
+	if a.IsSuperset(b) {
+		t.Error("set a should not be a superset of set b because set a has a 42")
+	}
+}
+
+func Test_UnsafeSetIsProperSuperset(t *testing.T) {
+	a := NewThreadUnsafeSet()
+	a.Add(5)
+	a.Add(2)
+	a.Add(11)
+
+	b := NewThreadUnsafeSet()
+	b.Add(2)
+	b.Add(5)
+	b.Add(11)
+
+	if !a.IsSuperset(b) {
+		t.Error("set a should be a superset of set b")
+	}
+	if a.IsProperSuperset(b) {
+		t.Error("set a should not be a proper superset of set b (they're equal)")
+	}
+
+	a.Add(9)
+
+	if !a.IsSuperset(b) {
+		t.Error("set a should be a superset of set b")
+	}
+	if !a.IsProperSuperset(b) {
+		t.Error("set a not be a proper superset of set b because set a has a 9")
+	}
+
+	b.Add(42)
+
+	if a.IsSuperset(b) {
+		t.Error("set a should not be a superset of set b because set b has a 42")
+	}
+	if a.IsProperSuperset(b) {
+		t.Error("set a should not be a proper superset of set b because set b has a 42")
+	}
+}
+
+func Test_SetUnion(t *testing.T) {
+	a := NewSet()
+
+	b := NewSet()
+	b.Add(1)
+	b.Add(2)
+	b.Add(3)
+	b.Add(4)
+	b.Add(5)
+
+	c := a.Union(b)
+
+	if c.Cardinality() != 5 {
+		t.Error("set c is unioned with an empty set and therefore should have 5 elements in it")
+	}
+
+	d := NewSet()
+	d.Add(10)
+	d.Add(14)
+	d.Add(0)
+
+	e := c.Union(d)
+	if e.Cardinality() != 8 {
+		t.Error("set e should should have 8 elements in it after being unioned with set c to d")
+	}
+
+	f := NewSet()
+	f.Add(14)
+	f.Add(3)
+
+	g := f.Union(e)
+	if g.Cardinality() != 8 {
+		t.Error("set g should still have 8 elements in it after being unioned with set f that has duplicates")
+	}
+}
+
+func Test_UnsafeSetUnion(t *testing.T) {
+	a := NewThreadUnsafeSet()
+
+	b := NewThreadUnsafeSet()
+	b.Add(1)
+	b.Add(2)
+	b.Add(3)
+	b.Add(4)
+	b.Add(5)
+
+	c := a.Union(b)
+
+	if c.Cardinality() != 5 {
+		t.Error("set c is unioned with an empty set and therefore should have 5 elements in it")
+	}
+
+	d := NewThreadUnsafeSet()
+	d.Add(10)
+	d.Add(14)
+	d.Add(0)
+
+	e := c.Union(d)
+	if e.Cardinality() != 8 {
+		t.Error("set e should should have 8 elements in it after being unioned with set c to d")
+	}
+
+	f := NewThreadUnsafeSet()
+	f.Add(14)
+	f.Add(3)
+
+	g := f.Union(e)
+	if g.Cardinality() != 8 {
+		t.Error("set g should still have 8 elements in it after being unioned with set f that has duplicates")
+	}
+}
+
+func Test_SetIntersect(t *testing.T) {
+	a := NewSet()
+	a.Add(1)
+	a.Add(3)
+	a.Add(5)
+
+	b := NewSet()
+	a.Add(2)
+	a.Add(4)
+	a.Add(6)
+
+	c := a.Intersect(b)
+
+	if c.Cardinality() != 0 {
+		t.Error("set c should be the empty set because there is no common items to intersect")
+	}
+
+	a.Add(10)
+	b.Add(10)
+
+	d := a.Intersect(b)
+
+	if !(d.Cardinality() == 1 && d.Contains(10)) {
+		t.Error("set d should have a size of 1 and contain the item 10")
+	}
+}
+
+func Test_UnsafeSetIntersect(t *testing.T) {
+	a := NewThreadUnsafeSet()
+	a.Add(1)
+	a.Add(3)
+	a.Add(5)
+
+	b := NewThreadUnsafeSet()
+	a.Add(2)
+	a.Add(4)
+	a.Add(6)
+
+	c := a.Intersect(b)
+
+	if c.Cardinality() != 0 {
+		t.Error("set c should be the empty set because there is no common items to intersect")
+	}
+
+	a.Add(10)
+	b.Add(10)
+
+	d := a.Intersect(b)
+
+	if !(d.Cardinality() == 1 && d.Contains(10)) {
+		t.Error("set d should have a size of 1 and contain the item 10")
+	}
+}
+
+func Test_SetDifference(t *testing.T) {
+	a := NewSet()
+	a.Add(1)
+	a.Add(2)
+	a.Add(3)
+
+	b := NewSet()
+	b.Add(1)
+	b.Add(3)
+	b.Add(4)
+	b.Add(5)
+	b.Add(6)
+	b.Add(99)
+
+	c := a.Difference(b)
+
+	if !(c.Cardinality() == 1 && c.Contains(2)) {
+		t.Error("the difference of set a to b is the set of 1 item: 2")
+	}
+}
+
+func Test_UnsafeSetDifference(t *testing.T) {
+	a := NewThreadUnsafeSet()
+	a.Add(1)
+	a.Add(2)
+	a.Add(3)
+
+	b := NewThreadUnsafeSet()
+	b.Add(1)
+	b.Add(3)
+	b.Add(4)
+	b.Add(5)
+	b.Add(6)
+	b.Add(99)
+
+	c := a.Difference(b)
+
+	if !(c.Cardinality() == 1 && c.Contains(2)) {
+		t.Error("the difference of set a to b is the set of 1 item: 2")
+	}
+}
+
+func Test_SetSymmetricDifference(t *testing.T) {
+	a := NewSet()
+	a.Add(1)
+	a.Add(2)
+	a.Add(3)
+	a.Add(45)
+
+	b := NewSet()
+	b.Add(1)
+	b.Add(3)
+	b.Add(4)
+	b.Add(5)
+	b.Add(6)
+	b.Add(99)
+
+	c := a.SymmetricDifference(b)
+
+	if !(c.Cardinality() == 6 && c.Contains(2) && c.Contains(45) && c.Contains(4) && c.Contains(5) && c.Contains(6) && c.Contains(99)) {
+		t.Error("the symmetric difference of set a to b is the set of 6 items: 2, 45, 4, 5, 6, 99")
+	}
+}
+
+func Test_UnsafeSetSymmetricDifference(t *testing.T) {
+	a := NewThreadUnsafeSet()
+	a.Add(1)
+	a.Add(2)
+	a.Add(3)
+	a.Add(45)
+
+	b := NewThreadUnsafeSet()
+	b.Add(1)
+	b.Add(3)
+	b.Add(4)
+	b.Add(5)
+	b.Add(6)
+	b.Add(99)
+
+	c := a.SymmetricDifference(b)
+
+	if !(c.Cardinality() == 6 && c.Contains(2) && c.Contains(45) && c.Contains(4) && c.Contains(5) && c.Contains(6) && c.Contains(99)) {
+		t.Error("the symmetric difference of set a to b is the set of 6 items: 2, 45, 4, 5, 6, 99")
+	}
+}
+
+func Test_SetEqual(t *testing.T) {
+	a := NewSet()
+	b := NewSet()
+
+	if !a.Equal(b) {
+		t.Error("Both a and b are empty sets, and should be equal")
+	}
+
+	a.Add(10)
+
+	if a.Equal(b) {
+		t.Error("a should not be equal to b because b is empty and a has item 1 in it")
+	}
+
+	b.Add(10)
+
+	if !a.Equal(b) {
+		t.Error("a is now equal again to b because both have the item 10 in them")
+	}
+
+	b.Add(8)
+	b.Add(3)
+	b.Add(47)
+
+	if a.Equal(b) {
+		t.Error("b has 3 more elements in it so therefore should not be equal to a")
+	}
+
+	a.Add(8)
+	a.Add(3)
+	a.Add(47)
+
+	if !a.Equal(b) {
+		t.Error("a and b should be equal with the same number of elements")
+	}
+}
+
+func Test_UnsafeSetEqual(t *testing.T) {
+	a := NewThreadUnsafeSet()
+	b := NewThreadUnsafeSet()
+
+	if !a.Equal(b) {
+		t.Error("Both a and b are empty sets, and should be equal")
+	}
+
+	a.Add(10)
+
+	if a.Equal(b) {
+		t.Error("a should not be equal to b because b is empty and a has item 1 in it")
+	}
+
+	b.Add(10)
+
+	if !a.Equal(b) {
+		t.Error("a is now equal again to b because both have the item 10 in them")
+	}
+
+	b.Add(8)
+	b.Add(3)
+	b.Add(47)
+
+	if a.Equal(b) {
+		t.Error("b has 3 more elements in it so therefore should not be equal to a")
+	}
+
+	a.Add(8)
+	a.Add(3)
+	a.Add(47)
+
+	if !a.Equal(b) {
+		t.Error("a and b should be equal with the same number of elements")
+	}
+}
+
+func Test_SetClone(t *testing.T) {
+	a := NewSet()
+	a.Add(1)
+	a.Add(2)
+
+	b := a.Clone()
+
+	if !a.Equal(b) {
+		t.Error("Clones should be equal")
+	}
+
+	a.Add(3)
+	if a.Equal(b) {
+		t.Error("a contains one more element, they should not be equal")
+	}
+
+	c := a.Clone()
+	c.Remove(1)
+
+	if a.Equal(c) {
+		t.Error("C contains one element less, they should not be equal")
+	}
+}
+
+func Test_UnsafeSetClone(t *testing.T) {
+	a := NewThreadUnsafeSet()
+	a.Add(1)
+	a.Add(2)
+
+	b := a.Clone()
+
+	if !a.Equal(b) {
+		t.Error("Clones should be equal")
+	}
+
+	a.Add(3)
+	if a.Equal(b) {
+		t.Error("a contains one more element, they should not be equal")
+	}
+
+	c := a.Clone()
+	c.Remove(1)
+
+	if a.Equal(c) {
+		t.Error("C contains one element less, they should not be equal")
+	}
+}
+
+func Test_Each(t *testing.T) {
+	a := NewSet()
+
+	a.Add("Z")
+	a.Add("Y")
+	a.Add("X")
+	a.Add("W")
+
+	b := NewSet()
+	a.Each(func(elem interface{}) bool {
+		b.Add(elem)
+		return false
+	})
+
+	if !a.Equal(b) {
+		t.Error("The sets are not equal after iterating (Each) through the first set")
+	}
+
+	var count int
+	a.Each(func(elem interface{}) bool {
+		if count == 2 {
+			return true
+		}
+		count++
+		return false
+	})
+	if count != 2 {
+		t.Error("Iteration should stop on the way")
+	}
+}
+
+func Test_Iter(t *testing.T) {
+	a := NewSet()
+
+	a.Add("Z")
+	a.Add("Y")
+	a.Add("X")
+	a.Add("W")
+
+	b := NewSet()
+	for val := range a.Iter() {
+		b.Add(val)
+	}
+
+	if !a.Equal(b) {
+		t.Error("The sets are not equal after iterating (Iter) through the first set")
+	}
+}
+
+func Test_UnsafeIter(t *testing.T) {
+	a := NewThreadUnsafeSet()
+
+	a.Add("Z")
+	a.Add("Y")
+	a.Add("X")
+	a.Add("W")
+
+	b := NewThreadUnsafeSet()
+	for val := range a.Iter() {
+		b.Add(val)
+	}
+
+	if !a.Equal(b) {
+		t.Error("The sets are not equal after iterating (Iter) through the first set")
+	}
+}
+
+func Test_Iterator(t *testing.T) {
+	a := NewSet()
+
+	a.Add("Z")
+	a.Add("Y")
+	a.Add("X")
+	a.Add("W")
+
+	b := NewSet()
+	for val := range a.Iterator().C {
+		b.Add(val)
+	}
+
+	if !a.Equal(b) {
+		t.Error("The sets are not equal after iterating (Iterator) through the first set")
+	}
+}
+
+func Test_UnsafeIterator(t *testing.T) {
+	a := NewThreadUnsafeSet()
+
+	a.Add("Z")
+	a.Add("Y")
+	a.Add("X")
+	a.Add("W")
+
+	b := NewThreadUnsafeSet()
+	for val := range a.Iterator().C {
+		b.Add(val)
+	}
+
+	if !a.Equal(b) {
+		t.Error("The sets are not equal after iterating (Iterator) through the first set")
+	}
+}
+
+func Test_IteratorStop(t *testing.T) {
+	a := NewSet()
+
+	a.Add("Z")
+	a.Add("Y")
+	a.Add("X")
+	a.Add("W")
+
+	it := a.Iterator()
+	it.Stop()
+	for range it.C {
+		t.Error("The iterating (Iterator) did not stop after Stop() has been called")
+	}
+}
+
+func Test_PopSafe(t *testing.T) {
+	a := NewSet()
+
+	a.Add("a")
+	a.Add("b")
+	a.Add("c")
+	a.Add("d")
+
+	captureSet := NewSet()
+	captureSet.Add(a.Pop())
+	captureSet.Add(a.Pop())
+	captureSet.Add(a.Pop())
+	captureSet.Add(a.Pop())
+	finalNil := a.Pop()
+
+	if captureSet.Cardinality() != 4 {
+		t.Error("unexpected captureSet cardinality; should be 4")
+	}
+
+	if a.Cardinality() != 0 {
+		t.Error("unepxected a cardinality; should be zero")
+	}
+
+	if !captureSet.Contains("c", "a", "d", "b") {
+		t.Error("unexpected result set; should be a,b,c,d (any order is fine")
+	}
+
+	if finalNil != nil {
+		t.Error("when original set is empty, further pops should result in nil")
+	}
+}
+
+func Test_PopUnsafe(t *testing.T) {
+	a := NewThreadUnsafeSet()
+
+	a.Add("a")
+	a.Add("b")
+	a.Add("c")
+	a.Add("d")
+
+	captureSet := NewThreadUnsafeSet()
+	captureSet.Add(a.Pop())
+	captureSet.Add(a.Pop())
+	captureSet.Add(a.Pop())
+	captureSet.Add(a.Pop())
+	finalNil := a.Pop()
+
+	if captureSet.Cardinality() != 4 {
+		t.Error("unexpected captureSet cardinality; should be 4")
+	}
+
+	if a.Cardinality() != 0 {
+		t.Error("unepxected a cardinality; should be zero")
+	}
+
+	if !captureSet.Contains("c", "a", "d", "b") {
+		t.Error("unexpected result set; should be a,b,c,d (any order is fine")
+	}
+
+	if finalNil != nil {
+		t.Error("when original set is empty, further pops should result in nil")
+	}
+}
+
+func Test_PowerSet(t *testing.T) {
+	a := NewThreadUnsafeSet()
+
+	a.Add(1)
+	a.Add("delta")
+	a.Add("chi")
+	a.Add(4)
+
+	b := a.PowerSet()
+	if b.Cardinality() != 16 {
+		t.Error("unexpected PowerSet cardinality")
+	}
+}
+
+func Test_PowerSetThreadSafe(t *testing.T) {
+	set := NewSet().PowerSet()
+	_, setIsThreadSafe := set.(*threadSafeSet)
+	if !setIsThreadSafe {
+		t.Error("result of PowerSet should be thread safe")
+	}
+
+	subset := set.Pop()
+	_, subsetIsThreadSafe := subset.(*threadSafeSet)
+	if !subsetIsThreadSafe {
+		t.Error("subsets in PowerSet result should be thread safe")
+	}
+}
+
+func Test_EmptySetProperties(t *testing.T) {
+	empty := NewSet()
+
+	a := NewSet()
+	a.Add(1)
+	a.Add("foo")
+	a.Add("bar")
+
+	b := NewSet()
+	b.Add("one")
+	b.Add("two")
+	b.Add(3)
+	b.Add(4)
+
+	if !empty.IsSubset(a) || !empty.IsSubset(b) {
+		t.Error("The empty set is supposed to be a subset of all sets")
+	}
+
+	if !a.IsSuperset(empty) || !b.IsSuperset(empty) {
+		t.Error("All sets are supposed to be a superset of the empty set")
+	}
+
+	if !empty.IsSubset(empty) || !empty.IsSuperset(empty) {
+		t.Error("The empty set is supposed to be a subset and a superset of itself")
+	}
+
+	c := a.Union(empty)
+	if !c.Equal(a) {
+		t.Error("The union of any set with the empty set is supposed to be equal to itself")
+	}
+
+	c = a.Intersect(empty)
+	if !c.Equal(empty) {
+		t.Error("The intesection of any set with the empty set is supposed to be the empty set")
+	}
+
+	c = a.CartesianProduct(empty)
+	if c.Cardinality() != 0 {
+		t.Error("Cartesian product of any set and the empty set must be the empty set")
+	}
+
+	if empty.Cardinality() != 0 {
+		t.Error("Cardinality of the empty set is supposed to be zero")
+	}
+
+	c = empty.PowerSet()
+	if c.Cardinality() != 1 {
+		t.Error("Cardinality of the power set of the empty set is supposed to be one { {} }")
+	}
+}
+
+func Test_CartesianProduct(t *testing.T) {
+	a := NewThreadUnsafeSet()
+	b := NewThreadUnsafeSet()
+	empty := NewThreadUnsafeSet()
+
+	a.Add(1)
+	a.Add(2)
+	a.Add(3)
+
+	b.Add("one")
+	b.Add("two")
+	b.Add("three")
+	b.Add("alpha")
+	b.Add("gamma")
+
+	c := a.CartesianProduct(b)
+	d := b.CartesianProduct(a)
+
+	if c.Cardinality() != d.Cardinality() {
+		t.Error("Cardinality of AxB must be equal to BxA")
+	}
+
+	if c.Cardinality() != (a.Cardinality() * b.Cardinality()) {
+		t.Error("Unexpected cardinality for cartesian product set")
+	}
+
+	c = a.CartesianProduct(empty)
+	d = empty.CartesianProduct(b)
+
+	if c.Cardinality() != 0 || d.Cardinality() != 0 {
+		t.Error("Cartesian product of any set and the empty set Ax0 || 0xA must be the empty set")
+	}
+}
+
+func Test_ToSliceUnthreadsafe(t *testing.T) {
+	s := makeUnsafeSet([]int{1, 2, 3})
+	setAsSlice := s.ToSlice()
+	if len(setAsSlice) != s.Cardinality() {
+		t.Errorf("Set length is incorrect: %v", len(setAsSlice))
+	}
+
+	for _, i := range setAsSlice {
+		if !s.Contains(i) {
+			t.Errorf("Set is missing element: %v", i)
+		}
+	}
+}
+
+func Test_Example(t *testing.T) {
+	/*
+	   requiredClasses := NewSet()
+	   requiredClasses.Add("Cooking")
+	   requiredClasses.Add("English")
+	   requiredClasses.Add("Math")
+	   requiredClasses.Add("Biology")
+
+	   scienceSlice := []interface{}{"Biology", "Chemistry"}
+	   scienceClasses := NewSetFromSlice(scienceSlice)
+
+	   electiveClasses := NewSet()
+	   electiveClasses.Add("Welding")
+	   electiveClasses.Add("Music")
+	   electiveClasses.Add("Automotive")
+
+	   bonusClasses := NewSet()
+	   bonusClasses.Add("Go Programming")
+	   bonusClasses.Add("Python Programming")
+
+	   //Show me all the available classes I can take
+	   allClasses := requiredClasses.Union(scienceClasses).Union(electiveClasses).Union(bonusClasses)
+	   fmt.Println(allClasses) //Set{English, Chemistry, Automotive, Cooking, Math, Biology, Welding, Music, Go Programming}
+
+	   //Is cooking considered a science class?
+	   fmt.Println(scienceClasses.Contains("Cooking")) //false
+
+	   //Show me all classes that are not science classes, since I hate science.
+	   fmt.Println(allClasses.Difference(scienceClasses)) //Set{English, Automotive, Cooking, Math, Welding, Music, Go Programming}
+
+	   //Which science classes are also required classes?
+	   fmt.Println(scienceClasses.Intersect(requiredClasses)) //Set{Biology}
+
+	   //How many bonus classes do you offer?
+	   fmt.Println(bonusClasses.Cardinality()) //2
+
+	   //Do you have the following classes? Welding, Automotive and English?
+	   fmt.Println(allClasses.ContainsAll("Welding", "Automotive", "English"))
+	*/
+}

+ 283 - 0
src/github.com/deckarep/golang-set/threadsafe.go

@@ -0,0 +1,283 @@
+/*
+Open Source Initiative OSI - The MIT License (MIT):Licensing
+
+The MIT License (MIT)
+Copyright (c) 2013 Ralph Caraveo (deckarep@gmail.com)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+package mapset
+
+import "sync"
+
+type threadSafeSet struct {
+	s threadUnsafeSet
+	sync.RWMutex
+}
+
+func newThreadSafeSet() threadSafeSet {
+	return threadSafeSet{s: newThreadUnsafeSet()}
+}
+
+func (set *threadSafeSet) Add(i interface{}) bool {
+	set.Lock()
+	ret := set.s.Add(i)
+	set.Unlock()
+	return ret
+}
+
+func (set *threadSafeSet) Contains(i ...interface{}) bool {
+	set.RLock()
+	ret := set.s.Contains(i...)
+	set.RUnlock()
+	return ret
+}
+
+func (set *threadSafeSet) IsSubset(other Set) bool {
+	o := other.(*threadSafeSet)
+
+	set.RLock()
+	o.RLock()
+
+	ret := set.s.IsSubset(&o.s)
+	set.RUnlock()
+	o.RUnlock()
+	return ret
+}
+
+func (set *threadSafeSet) IsProperSubset(other Set) bool {
+	o := other.(*threadSafeSet)
+
+	set.RLock()
+	defer set.RUnlock()
+	o.RLock()
+	defer o.RUnlock()
+
+	return set.s.IsProperSubset(&o.s)
+}
+
+func (set *threadSafeSet) IsSuperset(other Set) bool {
+	return other.IsSubset(set)
+}
+
+func (set *threadSafeSet) IsProperSuperset(other Set) bool {
+	return other.IsProperSubset(set)
+}
+
+func (set *threadSafeSet) Union(other Set) Set {
+	o := other.(*threadSafeSet)
+
+	set.RLock()
+	o.RLock()
+
+	unsafeUnion := set.s.Union(&o.s).(*threadUnsafeSet)
+	ret := &threadSafeSet{s: *unsafeUnion}
+	set.RUnlock()
+	o.RUnlock()
+	return ret
+}
+
+func (set *threadSafeSet) Intersect(other Set) Set {
+	o := other.(*threadSafeSet)
+
+	set.RLock()
+	o.RLock()
+
+	unsafeIntersection := set.s.Intersect(&o.s).(*threadUnsafeSet)
+	ret := &threadSafeSet{s: *unsafeIntersection}
+	set.RUnlock()
+	o.RUnlock()
+	return ret
+}
+
+func (set *threadSafeSet) Difference(other Set) Set {
+	o := other.(*threadSafeSet)
+
+	set.RLock()
+	o.RLock()
+
+	unsafeDifference := set.s.Difference(&o.s).(*threadUnsafeSet)
+	ret := &threadSafeSet{s: *unsafeDifference}
+	set.RUnlock()
+	o.RUnlock()
+	return ret
+}
+
+func (set *threadSafeSet) SymmetricDifference(other Set) Set {
+	o := other.(*threadSafeSet)
+
+	set.RLock()
+	o.RLock()
+
+	unsafeDifference := set.s.SymmetricDifference(&o.s).(*threadUnsafeSet)
+	ret := &threadSafeSet{s: *unsafeDifference}
+	set.RUnlock()
+	o.RUnlock()
+	return ret
+}
+
+func (set *threadSafeSet) Clear() {
+	set.Lock()
+	set.s = newThreadUnsafeSet()
+	set.Unlock()
+}
+
+func (set *threadSafeSet) Remove(i interface{}) {
+	set.Lock()
+	delete(set.s, i)
+	set.Unlock()
+}
+
+func (set *threadSafeSet) Cardinality() int {
+	set.RLock()
+	defer set.RUnlock()
+	return len(set.s)
+}
+
+func (set *threadSafeSet) Each(cb func(interface{}) bool) {
+	set.RLock()
+	for elem := range set.s {
+		if cb(elem) {
+			break
+		}
+	}
+	set.RUnlock()
+}
+
+func (set *threadSafeSet) Iter() <-chan interface{} {
+	ch := make(chan interface{})
+	go func() {
+		set.RLock()
+
+		for elem := range set.s {
+			ch <- elem
+		}
+		close(ch)
+		set.RUnlock()
+	}()
+
+	return ch
+}
+
+func (set *threadSafeSet) Iterator() *Iterator {
+	iterator, ch, stopCh := newIterator()
+
+	go func() {
+		set.RLock()
+	L:
+		for elem := range set.s {
+			select {
+			case <-stopCh:
+				break L
+			case ch <- elem:
+			}
+		}
+		close(ch)
+		set.RUnlock()
+	}()
+
+	return iterator
+}
+
+func (set *threadSafeSet) Equal(other Set) bool {
+	o := other.(*threadSafeSet)
+
+	set.RLock()
+	o.RLock()
+
+	ret := set.s.Equal(&o.s)
+	set.RUnlock()
+	o.RUnlock()
+	return ret
+}
+
+func (set *threadSafeSet) Clone() Set {
+	set.RLock()
+
+	unsafeClone := set.s.Clone().(*threadUnsafeSet)
+	ret := &threadSafeSet{s: *unsafeClone}
+	set.RUnlock()
+	return ret
+}
+
+func (set *threadSafeSet) String() string {
+	set.RLock()
+	ret := set.s.String()
+	set.RUnlock()
+	return ret
+}
+
+func (set *threadSafeSet) PowerSet() Set {
+	set.RLock()
+	unsafePowerSet := set.s.PowerSet().(*threadUnsafeSet)
+	set.RUnlock()
+
+	ret := &threadSafeSet{s: newThreadUnsafeSet()}
+	for subset := range unsafePowerSet.Iter() {
+		unsafeSubset := subset.(*threadUnsafeSet)
+		ret.Add(&threadSafeSet{s: *unsafeSubset})
+	}
+	return ret
+}
+
+func (set *threadSafeSet) Pop() interface{} {
+	set.Lock()
+	defer set.Unlock()
+	return set.s.Pop()
+}
+
+func (set *threadSafeSet) CartesianProduct(other Set) Set {
+	o := other.(*threadSafeSet)
+
+	set.RLock()
+	o.RLock()
+
+	unsafeCartProduct := set.s.CartesianProduct(&o.s).(*threadUnsafeSet)
+	ret := &threadSafeSet{s: *unsafeCartProduct}
+	set.RUnlock()
+	o.RUnlock()
+	return ret
+}
+
+func (set *threadSafeSet) ToSlice() []interface{} {
+	keys := make([]interface{}, 0, set.Cardinality())
+	set.RLock()
+	for elem := range set.s {
+		keys = append(keys, elem)
+	}
+	set.RUnlock()
+	return keys
+}
+
+func (set *threadSafeSet) MarshalJSON() ([]byte, error) {
+	set.RLock()
+	b, err := set.s.MarshalJSON()
+	set.RUnlock()
+
+	return b, err
+}
+
+func (set *threadSafeSet) UnmarshalJSON(p []byte) error {
+	set.RLock()
+	err := set.s.UnmarshalJSON(p)
+	set.RUnlock()
+
+	return err
+}

+ 524 - 0
src/github.com/deckarep/golang-set/threadsafe_test.go

@@ -0,0 +1,524 @@
+/*
+Open Source Initiative OSI - The MIT License (MIT):Licensing
+
+The MIT License (MIT)
+Copyright (c) 2013 Ralph Caraveo (deckarep@gmail.com)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+package mapset
+
+import (
+	"encoding/json"
+	"math/rand"
+	"runtime"
+	"sync"
+	"sync/atomic"
+	"testing"
+)
+
+const N = 1000
+
+func Test_AddConcurrent(t *testing.T) {
+	runtime.GOMAXPROCS(2)
+
+	s := NewSet()
+	ints := rand.Perm(N)
+
+	var wg sync.WaitGroup
+	wg.Add(len(ints))
+	for i := 0; i < len(ints); i++ {
+		go func(i int) {
+			s.Add(i)
+			wg.Done()
+		}(i)
+	}
+
+	wg.Wait()
+	for _, i := range ints {
+		if !s.Contains(i) {
+			t.Errorf("Set is missing element: %v", i)
+		}
+	}
+}
+
+func Test_CardinalityConcurrent(t *testing.T) {
+	runtime.GOMAXPROCS(2)
+
+	s := NewSet()
+
+	var wg sync.WaitGroup
+	wg.Add(1)
+	go func() {
+		elems := s.Cardinality()
+		for i := 0; i < N; i++ {
+			newElems := s.Cardinality()
+			if newElems < elems {
+				t.Errorf("Cardinality shrunk from %v to %v", elems, newElems)
+			}
+		}
+		wg.Done()
+	}()
+
+	for i := 0; i < N; i++ {
+		s.Add(rand.Int())
+	}
+	wg.Wait()
+}
+
+func Test_ClearConcurrent(t *testing.T) {
+	runtime.GOMAXPROCS(2)
+
+	s := NewSet()
+	ints := rand.Perm(N)
+
+	var wg sync.WaitGroup
+	wg.Add(len(ints))
+	for i := 0; i < len(ints); i++ {
+		go func() {
+			s.Clear()
+			wg.Done()
+		}()
+		go func(i int) {
+			s.Add(i)
+		}(i)
+	}
+
+	wg.Wait()
+}
+
+func Test_CloneConcurrent(t *testing.T) {
+	runtime.GOMAXPROCS(2)
+
+	s := NewSet()
+	ints := rand.Perm(N)
+
+	for _, v := range ints {
+		s.Add(v)
+	}
+
+	var wg sync.WaitGroup
+	wg.Add(len(ints))
+	for i := range ints {
+		go func(i int) {
+			s.Remove(i)
+			wg.Done()
+		}(i)
+	}
+
+	s.Clone()
+}
+
+func Test_ContainsConcurrent(t *testing.T) {
+	runtime.GOMAXPROCS(2)
+
+	s := NewSet()
+	ints := rand.Perm(N)
+	interfaces := make([]interface{}, 0)
+	for _, v := range ints {
+		s.Add(v)
+		interfaces = append(interfaces, v)
+	}
+
+	var wg sync.WaitGroup
+	for range ints {
+		wg.Add(1)
+		go func() {
+			s.Contains(interfaces...)
+			wg.Done()
+		}()
+	}
+	wg.Wait()
+}
+
+func Test_DifferenceConcurrent(t *testing.T) {
+	runtime.GOMAXPROCS(2)
+
+	s, ss := NewSet(), NewSet()
+	ints := rand.Perm(N)
+	for _, v := range ints {
+		s.Add(v)
+		ss.Add(v)
+	}
+
+	var wg sync.WaitGroup
+	for range ints {
+		wg.Add(1)
+		go func() {
+			s.Difference(ss)
+			wg.Done()
+		}()
+	}
+	wg.Wait()
+}
+
+func Test_EqualConcurrent(t *testing.T) {
+	runtime.GOMAXPROCS(2)
+
+	s, ss := NewSet(), NewSet()
+	ints := rand.Perm(N)
+	for _, v := range ints {
+		s.Add(v)
+		ss.Add(v)
+	}
+
+	var wg sync.WaitGroup
+	for range ints {
+		wg.Add(1)
+		go func() {
+			s.Equal(ss)
+			wg.Done()
+		}()
+	}
+	wg.Wait()
+}
+
+func Test_IntersectConcurrent(t *testing.T) {
+	runtime.GOMAXPROCS(2)
+
+	s, ss := NewSet(), NewSet()
+	ints := rand.Perm(N)
+	for _, v := range ints {
+		s.Add(v)
+		ss.Add(v)
+	}
+
+	var wg sync.WaitGroup
+	for range ints {
+		wg.Add(1)
+		go func() {
+			s.Intersect(ss)
+			wg.Done()
+		}()
+	}
+	wg.Wait()
+}
+
+func Test_IsSubsetConcurrent(t *testing.T) {
+	runtime.GOMAXPROCS(2)
+
+	s, ss := NewSet(), NewSet()
+	ints := rand.Perm(N)
+	for _, v := range ints {
+		s.Add(v)
+		ss.Add(v)
+	}
+
+	var wg sync.WaitGroup
+	for range ints {
+		wg.Add(1)
+		go func() {
+			s.IsSubset(ss)
+			wg.Done()
+		}()
+	}
+	wg.Wait()
+}
+
+func Test_IsProperSubsetConcurrent(t *testing.T) {
+	runtime.GOMAXPROCS(2)
+
+	s, ss := NewSet(), NewSet()
+	ints := rand.Perm(N)
+	for _, v := range ints {
+		s.Add(v)
+		ss.Add(v)
+	}
+
+	var wg sync.WaitGroup
+	for range ints {
+		wg.Add(1)
+		go func() {
+			s.IsProperSubset(ss)
+			wg.Done()
+		}()
+	}
+	wg.Wait()
+}
+
+func Test_IsSupersetConcurrent(t *testing.T) {
+	runtime.GOMAXPROCS(2)
+
+	s, ss := NewSet(), NewSet()
+	ints := rand.Perm(N)
+	for _, v := range ints {
+		s.Add(v)
+		ss.Add(v)
+	}
+
+	var wg sync.WaitGroup
+	for range ints {
+		wg.Add(1)
+		go func() {
+			s.IsSuperset(ss)
+			wg.Done()
+		}()
+	}
+	wg.Wait()
+}
+
+func Test_IsProperSupersetConcurrent(t *testing.T) {
+	runtime.GOMAXPROCS(2)
+
+	s, ss := NewSet(), NewSet()
+	ints := rand.Perm(N)
+	for _, v := range ints {
+		s.Add(v)
+		ss.Add(v)
+	}
+
+	var wg sync.WaitGroup
+	for range ints {
+		wg.Add(1)
+		go func() {
+			s.IsProperSuperset(ss)
+			wg.Done()
+		}()
+	}
+	wg.Wait()
+}
+
+func Test_EachConcurrent(t *testing.T) {
+	runtime.GOMAXPROCS(2)
+	concurrent := 10
+
+	s := NewSet()
+	ints := rand.Perm(N)
+	for _, v := range ints {
+		s.Add(v)
+	}
+
+	var count int64
+	wg := new(sync.WaitGroup)
+	wg.Add(concurrent)
+	for n := 0; n < concurrent; n++ {
+		go func() {
+			defer wg.Done()
+			s.Each(func(elem interface{}) bool {
+				atomic.AddInt64(&count, 1)
+				return false
+			})
+		}()
+	}
+	wg.Wait()
+
+	if count != int64(N*concurrent) {
+		t.Errorf("%v != %v", count, int64(N*concurrent))
+	}
+}
+
+func Test_IterConcurrent(t *testing.T) {
+	runtime.GOMAXPROCS(2)
+
+	s := NewSet()
+	ints := rand.Perm(N)
+	for _, v := range ints {
+		s.Add(v)
+	}
+
+	cs := make([]<-chan interface{}, 0)
+	for range ints {
+		cs = append(cs, s.Iter())
+	}
+
+	c := make(chan interface{})
+	go func() {
+		for n := 0; n < len(ints)*N; {
+			for _, d := range cs {
+				select {
+				case <-d:
+					n++
+					c <- nil
+				default:
+				}
+			}
+		}
+		close(c)
+	}()
+
+	for range c {
+	}
+}
+
+func Test_RemoveConcurrent(t *testing.T) {
+	runtime.GOMAXPROCS(2)
+
+	s := NewSet()
+	ints := rand.Perm(N)
+	for _, v := range ints {
+		s.Add(v)
+	}
+
+	var wg sync.WaitGroup
+	wg.Add(len(ints))
+	for _, v := range ints {
+		go func(i int) {
+			s.Remove(i)
+			wg.Done()
+		}(v)
+	}
+	wg.Wait()
+
+	if s.Cardinality() != 0 {
+		t.Errorf("Expected cardinality 0; got %v", s.Cardinality())
+	}
+}
+
+func Test_StringConcurrent(t *testing.T) {
+	runtime.GOMAXPROCS(2)
+
+	s := NewSet()
+	ints := rand.Perm(N)
+	for _, v := range ints {
+		s.Add(v)
+	}
+
+	var wg sync.WaitGroup
+	wg.Add(len(ints))
+	for range ints {
+		go func() {
+			_ = s.String()
+			wg.Done()
+		}()
+	}
+	wg.Wait()
+}
+
+func Test_SymmetricDifferenceConcurrent(t *testing.T) {
+	runtime.GOMAXPROCS(2)
+
+	s, ss := NewSet(), NewSet()
+	ints := rand.Perm(N)
+	for _, v := range ints {
+		s.Add(v)
+		ss.Add(v)
+	}
+
+	var wg sync.WaitGroup
+	for range ints {
+		wg.Add(1)
+		go func() {
+			s.SymmetricDifference(ss)
+			wg.Done()
+		}()
+	}
+	wg.Wait()
+}
+
+func Test_ToSlice(t *testing.T) {
+	runtime.GOMAXPROCS(2)
+
+	s := NewSet()
+	ints := rand.Perm(N)
+
+	var wg sync.WaitGroup
+	wg.Add(len(ints))
+	for i := 0; i < len(ints); i++ {
+		go func(i int) {
+			s.Add(i)
+			wg.Done()
+		}(i)
+	}
+
+	wg.Wait()
+	setAsSlice := s.ToSlice()
+	if len(setAsSlice) != s.Cardinality() {
+		t.Errorf("Set length is incorrect: %v", len(setAsSlice))
+	}
+
+	for _, i := range setAsSlice {
+		if !s.Contains(i) {
+			t.Errorf("Set is missing element: %v", i)
+		}
+	}
+}
+
+// Test_ToSliceDeadlock - fixes issue: https://github.com/deckarep/golang-set/issues/36
+// This code reveals the deadlock however it doesn't happen consistently.
+func Test_ToSliceDeadlock(t *testing.T) {
+	runtime.GOMAXPROCS(2)
+
+	var wg sync.WaitGroup
+	set := NewSet()
+	workers := 10
+	wg.Add(workers)
+	for i := 1; i <= workers; i++ {
+		go func() {
+			for j := 0; j < 1000; j++ {
+				set.Add(1)
+				set.ToSlice()
+			}
+			wg.Done()
+		}()
+	}
+	wg.Wait()
+}
+
+func Test_UnmarshalJSON(t *testing.T) {
+	s := []byte(`["test", 1, 2, 3, ["4,5,6"]]`)
+	expected := NewSetFromSlice(
+		[]interface{}{
+			json.Number("1"),
+			json.Number("2"),
+			json.Number("3"),
+			"test",
+		},
+	)
+	actual := NewSet()
+	err := json.Unmarshal(s, actual)
+	if err != nil {
+		t.Errorf("Error should be nil: %v", err)
+	}
+
+	if !expected.Equal(actual) {
+		t.Errorf("Expected no difference, got: %v", expected.Difference(actual))
+	}
+}
+
+func Test_MarshalJSON(t *testing.T) {
+	expected := NewSetFromSlice(
+		[]interface{}{
+			json.Number("1"),
+			"test",
+		},
+	)
+
+	b, err := json.Marshal(
+		NewSetFromSlice(
+			[]interface{}{
+				1,
+				"test",
+			},
+		),
+	)
+	if err != nil {
+		t.Errorf("Error should be nil: %v", err)
+	}
+
+	actual := NewSet()
+	err = json.Unmarshal(b, actual)
+	if err != nil {
+		t.Errorf("Error should be nil: %v", err)
+	}
+
+	if !expected.Equal(actual) {
+		t.Errorf("Expected no difference, got: %v", expected.Difference(actual))
+	}
+}

+ 340 - 0
src/github.com/deckarep/golang-set/threadunsafe.go

@@ -0,0 +1,340 @@
+/*
+Open Source Initiative OSI - The MIT License (MIT):Licensing
+
+The MIT License (MIT)
+Copyright (c) 2013 Ralph Caraveo (deckarep@gmail.com)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+package mapset
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"reflect"
+	"strings"
+)
+
+type threadUnsafeSet map[interface{}]struct{}
+
+// An OrderedPair represents a 2-tuple of values.
+type OrderedPair struct {
+	First  interface{}
+	Second interface{}
+}
+
+func newThreadUnsafeSet() threadUnsafeSet {
+	return make(threadUnsafeSet)
+}
+
+// Equal says whether two 2-tuples contain the same values in the same order.
+func (pair *OrderedPair) Equal(other OrderedPair) bool {
+	if pair.First == other.First &&
+		pair.Second == other.Second {
+		return true
+	}
+
+	return false
+}
+
+func (set *threadUnsafeSet) Add(i interface{}) bool {
+	_, found := (*set)[i]
+	if found {
+		return false //False if it existed already
+	}
+
+	(*set)[i] = struct{}{}
+	return true
+}
+
+func (set *threadUnsafeSet) Contains(i ...interface{}) bool {
+	for _, val := range i {
+		if _, ok := (*set)[val]; !ok {
+			return false
+		}
+	}
+	return true
+}
+
+func (set *threadUnsafeSet) IsSubset(other Set) bool {
+	_ = other.(*threadUnsafeSet)
+	if set.Cardinality() > other.Cardinality() {
+		return false
+	}
+	for elem := range *set {
+		if !other.Contains(elem) {
+			return false
+		}
+	}
+	return true
+}
+
+func (set *threadUnsafeSet) IsProperSubset(other Set) bool {
+	return set.IsSubset(other) && !set.Equal(other)
+}
+
+func (set *threadUnsafeSet) IsSuperset(other Set) bool {
+	return other.IsSubset(set)
+}
+
+func (set *threadUnsafeSet) IsProperSuperset(other Set) bool {
+	return set.IsSuperset(other) && !set.Equal(other)
+}
+
+func (set *threadUnsafeSet) Union(other Set) Set {
+	o := other.(*threadUnsafeSet)
+
+	unionedSet := newThreadUnsafeSet()
+
+	for elem := range *set {
+		unionedSet.Add(elem)
+	}
+	for elem := range *o {
+		unionedSet.Add(elem)
+	}
+	return &unionedSet
+}
+
+func (set *threadUnsafeSet) Intersect(other Set) Set {
+	o := other.(*threadUnsafeSet)
+
+	intersection := newThreadUnsafeSet()
+	// loop over smaller set
+	if set.Cardinality() < other.Cardinality() {
+		for elem := range *set {
+			if other.Contains(elem) {
+				intersection.Add(elem)
+			}
+		}
+	} else {
+		for elem := range *o {
+			if set.Contains(elem) {
+				intersection.Add(elem)
+			}
+		}
+	}
+	return &intersection
+}
+
+func (set *threadUnsafeSet) Difference(other Set) Set {
+	_ = other.(*threadUnsafeSet)
+
+	difference := newThreadUnsafeSet()
+	for elem := range *set {
+		if !other.Contains(elem) {
+			difference.Add(elem)
+		}
+	}
+	return &difference
+}
+
+func (set *threadUnsafeSet) SymmetricDifference(other Set) Set {
+	_ = other.(*threadUnsafeSet)
+
+	aDiff := set.Difference(other)
+	bDiff := other.Difference(set)
+	return aDiff.Union(bDiff)
+}
+
+func (set *threadUnsafeSet) Clear() {
+	*set = newThreadUnsafeSet()
+}
+
+func (set *threadUnsafeSet) Remove(i interface{}) {
+	delete(*set, i)
+}
+
+func (set *threadUnsafeSet) Cardinality() int {
+	return len(*set)
+}
+
+func (set *threadUnsafeSet) Each(cb func(interface{}) bool) {
+	for elem := range *set {
+		if cb(elem) {
+			break
+		}
+	}
+}
+
+func (set *threadUnsafeSet) Iter() <-chan interface{} {
+	ch := make(chan interface{})
+	go func() {
+		for elem := range *set {
+			ch <- elem
+		}
+		close(ch)
+	}()
+
+	return ch
+}
+
+func (set *threadUnsafeSet) Iterator() *Iterator {
+	iterator, ch, stopCh := newIterator()
+
+	go func() {
+	L:
+		for elem := range *set {
+			select {
+			case <-stopCh:
+				break L
+			case ch <- elem:
+			}
+		}
+		close(ch)
+	}()
+
+	return iterator
+}
+
+func (set *threadUnsafeSet) Equal(other Set) bool {
+	_ = other.(*threadUnsafeSet)
+
+	if set.Cardinality() != other.Cardinality() {
+		return false
+	}
+	for elem := range *set {
+		if !other.Contains(elem) {
+			return false
+		}
+	}
+	return true
+}
+
+func (set *threadUnsafeSet) Clone() Set {
+	clonedSet := newThreadUnsafeSet()
+	for elem := range *set {
+		clonedSet.Add(elem)
+	}
+	return &clonedSet
+}
+
+func (set *threadUnsafeSet) String() string {
+	items := make([]string, 0, len(*set))
+
+	for elem := range *set {
+		items = append(items, fmt.Sprintf("%v", elem))
+	}
+	return fmt.Sprintf("Set{%s}", strings.Join(items, ", "))
+}
+
+// String outputs a 2-tuple in the form "(A, B)".
+func (pair OrderedPair) String() string {
+	return fmt.Sprintf("(%v, %v)", pair.First, pair.Second)
+}
+
+func (set *threadUnsafeSet) Pop() interface{} {
+	for item := range *set {
+		delete(*set, item)
+		return item
+	}
+	return nil
+}
+
+func (set *threadUnsafeSet) PowerSet() Set {
+	powSet := NewThreadUnsafeSet()
+	nullset := newThreadUnsafeSet()
+	powSet.Add(&nullset)
+
+	for es := range *set {
+		u := newThreadUnsafeSet()
+		j := powSet.Iter()
+		for er := range j {
+			p := newThreadUnsafeSet()
+			if reflect.TypeOf(er).Name() == "" {
+				k := er.(*threadUnsafeSet)
+				for ek := range *(k) {
+					p.Add(ek)
+				}
+			} else {
+				p.Add(er)
+			}
+			p.Add(es)
+			u.Add(&p)
+		}
+
+		powSet = powSet.Union(&u)
+	}
+
+	return powSet
+}
+
+func (set *threadUnsafeSet) CartesianProduct(other Set) Set {
+	o := other.(*threadUnsafeSet)
+	cartProduct := NewThreadUnsafeSet()
+
+	for i := range *set {
+		for j := range *o {
+			elem := OrderedPair{First: i, Second: j}
+			cartProduct.Add(elem)
+		}
+	}
+
+	return cartProduct
+}
+
+func (set *threadUnsafeSet) ToSlice() []interface{} {
+	keys := make([]interface{}, 0, set.Cardinality())
+	for elem := range *set {
+		keys = append(keys, elem)
+	}
+
+	return keys
+}
+
+// MarshalJSON creates a JSON array from the set, it marshals all elements
+func (set *threadUnsafeSet) MarshalJSON() ([]byte, error) {
+	items := make([]string, 0, set.Cardinality())
+
+	for elem := range *set {
+		b, err := json.Marshal(elem)
+		if err != nil {
+			return nil, err
+		}
+
+		items = append(items, string(b))
+	}
+
+	return []byte(fmt.Sprintf("[%s]", strings.Join(items, ","))), nil
+}
+
+// UnmarshalJSON recreates a set from a JSON array, it only decodes
+// primitive types. Numbers are decoded as json.Number.
+func (set *threadUnsafeSet) UnmarshalJSON(b []byte) error {
+	var i []interface{}
+
+	d := json.NewDecoder(bytes.NewReader(b))
+	d.UseNumber()
+	err := d.Decode(&i)
+	if err != nil {
+		return err
+	}
+
+	for _, v := range i {
+		switch t := v.(type) {
+		case []interface{}, map[string]interface{}:
+			continue
+		default:
+			set.Add(t)
+		}
+	}
+
+	return nil
+}

+ 46 - 0
src/github.com/doug-martin/goqu/CODE_OF_CONDUCT.md

@@ -0,0 +1,46 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at doug@dougamartin.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
+
+[homepage]: http://contributor-covenant.org
+[version]: http://contributor-covenant.org/version/1/4/

+ 31 - 0
src/github.com/doug-martin/goqu/CONTRIBUTING.md

@@ -0,0 +1,31 @@
+## Contributions
+
+I am always welcoming contributions of any type. Please open an issue or create a PR if you find an issue with any of the following.
+
+* An issue with Documentation
+* You found the documentation lacking in some way
+
+If you have an issue with the package please include the following
+
+* The dialect you are using
+* A description of the problem
+* A short example of how to reproduce (if applicable)
+
+Without those basics it can be difficult to reproduce your issue locally. You may be asked for more information but that is a good starting point.
+
+### New Features
+
+New features and/or enhancements are great and I encourage you to either submit a PR or create an issue. In both cases include the following as the need/requirement may not be readily apparent.
+
+1. The use case
+2. A short example
+
+If you are issuing a PR also also include the following
+
+1. Tests - otherwise the PR will not be merged
+2. Documentation - otherwise the PR will not be merged
+3. Examples - [If applicable] see example_test.go for examples
+
+If you find an issue you want to work on please comment on it letting other people know you are looking at it and I will assign the issue to you.
+
+If want to work on an issue but dont know where to start just leave a comment and I'll be more than happy to point you in the right direction.

+ 344 - 0
src/github.com/doug-martin/goqu/HISTORY.md

@@ -0,0 +1,344 @@
+# v9.8.0
+
+* [ADDED] Support for ANY and ALL operators. [#196](https://github.com/doug-martin/goqu/issues/196)
+* [ADDED] Support for CASE statements [#193](https://github.com/doug-martin/goqu/issues/193) 
+* [ADDED] Support for getting column identifiers from AliasExpressions. [#203](https://github.com/doug-martin/goqu/issues/203)  
+
+# v9.7.1
+
+* Fix all formatting for golangci-lint
+* Move to golangci-lint github action
+
+# v9.7.0
+
+* [ADDED] Support for sqlserver dialect [#197](https://github.com/doug-martin/goqu/issues/197),[#205](https://github.com/doug-martin/goqu/issues/205) - [@vlanse](https://github.com/vlanse)
+
+# v9.6.0
+
+* [ADDED] Support for Lateral queries [#182](https://github.com/doug-martin/goqu/issues/182)
+
+# v9.5.1
+
+* [FIXED] WITH clause without a RETURNING clause will panic [#177](https://github.com/doug-martin/goqu/issues/177)
+* [FIXED] SQlite dialect escapes single quotes wrong, leads to SQL syntax error [#178](https://github.com/doug-martin/goqu/issues/178)
+* [FIXED] Fix ReturnsColumns() nil pointer panic [#181](https://github.com/doug-martin/goqu/issues/181) - [@yeaha](https://github.com/yeaha)
+* [FIXED] SelectDataset From with Error [#183](https://github.com/doug-martin/goqu/issues/183)
+* [FIXED] Unable to execute union with order by expression [#185](https://github.com/doug-martin/goqu/issues/185)
+
+# v9.5.0
+
+* [ADDED] Ability to use regexp like, ilike, notlike, and notilike without a regexp [#172](https://github.com/doug-martin/goqu/issues/172)
+
+# v9.4.0
+
+* [ADDED] Ability to scan into struct fields from multiple tables [#160](https://github.com/doug-martin/goqu/issues/160)
+
+# v9.3.0
+
+* [ADDED] Using Update, Insert, or Delete datasets in sub selects and CTEs [#164](https://github.com/doug-martin/goqu/issues/164)
+
+# v9.2.0
+
+* [ADDED] exec.Scanner: New exposed scanner supports iterative scanning [#157](https://github.com/doug-martin/goqu/pull/157) - [@akarl](https://github.com/akarl)
+
+# v9.1.0
+
+* [FIXED] ExampleDoUpdate does't work in postgres [#156](https://github.com/doug-martin/goqu/issues/156)
+* [FIXED] Issue with timezone being lost [#163](https://github.com/doug-martin/goqu/issues/163) 
+
+# v9.0.1
+
+* [FIXED] Issue where `NULL`, `TRUE` and `FALSE` are interpolated when using an `IS` clause. [#165](https://github.com/doug-martin/goqu/issues/165)
+    
+# v9.0.0
+
+* Changed `NULL`, `TRUE`, `FALSE` to not be interpolated when creating prepared statements. [#132](https://github.com/doug-martin/goqu/pull/132), [#158](https://github.com/doug-martin/goqu/pull/158) - [@marshallmcmullen](https://github.com/marshallmcmullen)
+* Updated dependencies
+    * `github.com/lib/pq v1.1.1 -> v1.2.0`
+    * `github.com/mattn/go-sqlite3 v1.10.0 -> v1.11.0`
+    * `github.com/stretchr/testify v1.3.0 -> v1.4.0`
+
+## v8.6.0
+
+* [ADDED] `SetError()` and `Error()` to all datasets. [#152](https://github.com/doug-martin/goqu/pull/152) and [#150](https://github.com/doug-martin/goqu/pull/150) - [@marshallmcmullen](https://github.com/marshallmcmullen)
+
+## v8.5.0
+
+* [ADDED] Window Function support [#128](https://github.com/doug-martin/goqu/issues/128) - [@Xuyuanp](https://github.com/Xuyuanp)
+
+## v8.4.1
+
+* [FIXED] Returning func be able to handle nil [#140](https://github.com/doug-martin/goqu/issues/140)
+
+## v8.4.0
+
+* Created new `sqlgen` module to encapsulate sql generation
+    * Broke SQLDialect inti new SQL generators for each statement type.
+* Test refactor
+    * Moved to a test case pattern to allow for quickly adding new test cases.
+    
+## v8.3.2
+
+* [FIXED] Data race during query factory initialization [#133](https://github.com/doug-martin/goqu/issues/133) and [#136](https://github.com/doug-martin/goqu/issues/136) - [@o1egl](https://github.com/o1egl)    
+
+## v8.3.1
+
+* [FIXED] InsertDataset.WithDialect return old dataset [#126](https://github.com/doug-martin/goqu/issues/126) - [@chen56](https://github.com/chen56)
+* Test clean up and more testing pattern consistency
+    * Changed to use assertion methods off of suite
+    * Updated Equals assertions to have expected output first 
+* Increase overall test coverage.
+
+## v8.3.0
+
+* [Added] Support for `DISTINCT ON` clauses [#119](https://github.com/doug-martin/goqu/issues/119)
+
+## v8.2.2
+
+* [FIX] Scanner errors on pointers to primitive values [#122](https://github.com/doug-martin/goqu/issues/122)
+
+## v8.2.1
+
+* [FIX] Return an error when an empty identifier is encountered [#115](https://github.com/doug-martin/goqu/issues/115)
+
+## v8.2.0
+
+* [FIX] Fix reflection errors related to nil pointers and unexported fields [#118](https://github.com/doug-martin/goqu/issues/118)
+    * Unexported fields are ignored when creating a columnMap
+    * Nil embedded pointers will no longer cause a panic
+    * Fields on nil embedded pointers will be ignored when creating update or insert statements.
+* [ADDED] You can now ingore embedded structs and their fields by using `db:"-"` tag on the embedded struct.
+
+## v8.1.0
+
+* [ADDED] Support column DEFAULT when inserting/updating via struct [#27](https://github.com/doug-martin/goqu/issues/27)
+
+## v8.0.1
+
+* [ADDED] Multi table update support for `mysql` and `postgres` [#60](https://github.com/doug-martin/goqu/issues/60)
+* [ADDED] `goqu.V` so values can be used on the LHS of expressions [#104](https://github.com/doug-martin/goqu/issues/104)
+
+## v8.0.0
+
+A major change the the API was made in `v8` to seperate concerns between the different SQL statement types. 
+
+**Why the change?**
+
+1. There were feature requests that could not be cleanly implemented with everything in a single dataset. 
+2. Too much functionality was encapsulated in a single datastructure.
+    * It was unclear what methods could be used for each SQL statement type.
+    * Changing a feature for one statement type had the possiblity of breaking another statement type.
+    * Test coverage was decent but was almost solely concerned about SELECT statements, breaking them up allowed for focused testing on each statement type.
+    * Most the SQL generation methods (`ToInsertSQL`, `ToUpdateSQL` etc.) took arguments which lead to an ugly API that was not uniform for each statement type, and proved to be inflexible.
+
+**What Changed**
+
+There are now five dataset types, `SelectDataset`, `InsertDataset`, `UpdateDataset`, `DeleteDataset` and `TruncateDataset`
+
+Each dataset type has its own entry point.
+
+* `goqu.From`, `Database#From`, `DialectWrapper#From` - Create SELECT
+* `goqu.Insert`, `Database#Insert`, `DialectWrapper#Insert` - Create INSERT
+* `goqu.Update`, `Database#db.Update`, `DialectWrapper#Update` - Create UPDATE
+* `goqu.Delete`, `Database#Delete`, `DialectWrapper#Delete` - Create DELETE
+* `goqu.Truncate`, `Database#Truncate`, `DialectWrapper#Truncate` - Create TRUNCATE
+  
+`ToInsertSQL`, `ToUpdateSQL`, `ToDeleteSQL`, and `ToTruncateSQL` (and variations of them) methods have been removed from the `SelectDataset`. Instead use the `ToSQL` methods on each dataset type.
+
+Each dataset type will have an `Executor` and `ToSQL` method so a common interface can be created for each type.
+
+
+## v7.4.0
+
+* [FIXED] literalTime use t.UTC() , This behavior is different from the original sql.DB [#106](https://github.com/doug-martin/goqu/issues/106) - [chen56](https://github.com/chen56)
+* [ADDED] Add new method WithTx for Database [#108](https://github.com/doug-martin/goqu/issues/108) - [Xuyuanp](https://github.com/Xuyuanp)
+
+## v7.3.1
+
+* [ADDED] Exposed `goqu.NewTx` to allow creating a goqu tx directly from a `sql.Tx` instead of using `goqu.Database#Begin` [#95](https://github.com/doug-martin/goqu/issues/95)
+* [ADDED] `goqu.Database.BeginTx` [#98](https://github.com/doug-martin/goqu/issues/98)
+
+## v7.3.0
+
+* [ADDED] UPDATE and INSERT should use struct Field name if db tag is not specified [#57](https://github.com/doug-martin/goqu/issues/57)
+* [CHANGE] Changed goqu.Database to accept a SQLDatabase interface to allow using goqu.Database with other libraries such as `sqlx` [#95](https://github.com/doug-martin/goqu/issues/95)
+
+## v7.2.0
+
+* [FIXED] Sqlite3 does not accept SELECT * UNION (SELECT *) [#79](https://github.com/doug-martin/goqu/issues/79)
+* [FIXED] Where(Ex{}) causes panics [mysql] [#49](https://github.com/doug-martin/goqu/issues/49)
+* [ADDED] Support for OrderPrepend [#61](https://github.com/doug-martin/goqu/issues/61)
+* [DOCS] Added new section about loading a dialect and using it to build SQL [#44](https://github.com/doug-martin/goqu/issues/44)
+
+## v7.1.0
+
+* [FIXED] Embedded pointers with property names that duplicate parent struct properties. [#23](https://github.com/doug-martin/goqu/issues/23)
+* [FIXED] Can't scan values using []byte or []string [#90](https://github.com/doug-martin/goqu/issues/90)
+    * When a slice that is `*sql.RawBytes`, `*[]byte` or `sql.Scanner` no errors will be returned. 
+
+## v7.0.1
+
+* Fix issue where structs with pointer fields where not set properly [#86](https://github.com/doug-martin/goqu/pull/86) and [#89](https://github.com/doug-martin/goqu/pull/89) - [@efureev](https://github.com/efureev)
+
+## v7.0.0
+
+**Linting**
+* Add linting checks and fixed errors 
+    * Renamed all snake_case variables to be camelCase.     
+    * Fixed examples to always map to a defined method
+* Renamed `adapters` to `dialect` to more closely match their intended purpose.
+
+**API Changes**
+* Updated all sql generations methods to from `Sql` to `SQL`
+    * `ToSql` -> `ToSQL`
+    * `ToInsertSql` -> `ToInsertSQL`
+    * `ToUpdateSql` -> `ToUpdateSQL`
+    * `ToDeleteSql` -> `ToDeleteSQL`
+    * `ToTruncateSql` -> `ToTruncateSQL`
+* Abstracted out `dialect_options` from the adapter to make the dialect self contained.
+    * This also removed the dataset<->adapter co dependency making the dialect self contained.
+* Refactored the `goqu.I` method.
+    * Added new `goqu.S`, `goqu.T` and `goqu.C` methods to clarify why type of identifier you are using.
+    * `goqu.I` should only be used when you have a qualified identifier (e.g. `goqu.I("my_schema.my_table.my_col")
+* Added new `goqu.Dialect` method to make using `goqu` as an SQL builder easier.
+
+**Internal Changes**
+* Pulled expressions into their own package
+    * Broke up expressions.go into multiple files to make working with and defining them easier.
+    * Moved the user facing methods into the main `goqu` to keep the same API as before.
+* Added more examples
+* Moved non-user facing structs and interfaces to internal modules to clean up API.
+* Increased test coverage.
+ 
+
+## v6.1.0
+
+* Handle nil *time.Time Literal [#73](https://github.com/doug-martin/goqu/pull/73) and [#52](https://github.com/doug-martin/goqu/pull/52) - [@RoarkeRandall](https://github.com/RoarkeRandall) and [@quetz](https://github.com/quetz)
+* Add ability to change column rename function [#66](https://github.com/doug-martin/goqu/pull/66) - [@blainehansen](https://github.com/blainehansen)
+
+## v6.0.0
+
+* Updated go support to `1.10`, `1.11` and `1.12`
+* Change testify dependency from c2fo/testify back to stretchr/testify.
+* Add support for "FOR UPDATE" and "SKIP LOCKED" [#62](https://github.com/doug-martin/goqu/pull/62) - [@btubbs](https://github.com/btubbs)
+* Changed to use go modules
+
+## v5.0.0
+
+* Drop go 1.6 support, supported versions are `1.8`, `1.9` and latest
+* Add context support [#64](https://github.com/doug-martin/goqu/pull/64) - [@cmoad](https://github.com/cmoad)
+
+## v4.2.0
+
+* Add support for ON CONFLICT when using a dataset [#55](https://github.com/doug-martin/goqu/pull/55) - [@bobrnor](https://github.com/bobrnor)
+
+## v4.1.0
+
+* Support for defining WITH clauses for Common Table Expressions (CTE) [#39](https://github.com/doug-martin/goqu/pull/39) - [@Oscil8](https://github.com/Oscil8)
+
+## v4.0
+
+* Prepared(true) issues when using IS NULL comparisson operation [#33](https://github.com/doug-martin/goqu/pull/33) - [@danielfbm](https://github.com/danielfbm)
+
+## v3.3
+
+* Add `upsert` support via `InsertIgnore` and `InsertConflict` methods - [#25](https://github.com/doug-martin/goqu/pull/28) - [@aheuermann](https://github.com/aheuermann)
+* Adding vendor dependencies and updating tests to run in docker containers [#29](https://github.com/doug-martin/goqu/pull/29) - [@aheuermann](https://github.com/aheuermann)
+
+## v3.2
+
+* Add range clauses ([NOT] BETWEEN) support - [#25](https://github.com/doug-martin/goqu/pull/25) - [@denisvm](https://github.com/denisvm)
+* Readmefix [#26](https://github.com/doug-martin/goqu/pull/26) - [@tiagopotencia](https://github.com/tiagopotencia)
+
+## v3.1.3
+
+* Bugfix for chained Where() [#20](https://github.com/doug-martin/goqu/pull/20) - [@Emreu](https://github.com/Emreu)
+
+
+## v3.1.2
+
+* Fixing ScanStruct issue with embedded pointers in crud_exec [#20](https://github.com/doug-martin/goqu/pull/20) - [@ruzz311](https://github.com/ruzz311)
+
+## v3.1.1
+
+* Fixing race condition with struct_map_cache in crud_exec [#18](https://github.com/doug-martin/goqu/pull/18) - [@andymoon](https://github.com/andymoon), [@aheuermann](https://github.com/aheuermann)
+
+## v3.1.0
+
+* Version 3.1 [#14](https://github.com/doug-martin/goqu/pull/14) - [@andymoon](https://github.com/andymoon)
+    * Fix an issue with a nil pointer access on the inserts and updates.
+    * Allowing ScanStructs to take a struct with an embedded pointer to a struct.
+    * Change to check if struct is Anonymous when recursing through an embedded struct.
+    * Updated to use the latest version of github.com/DATA-DOG/go-sqlmock.
+
+## v3.0.1
+
+* Add literal bytes and update to c2fo testify [#15](https://github.com/doug-martin/goqu/pull/15) - [@TechnotronicOz](https://github.com/TechnotronicOz)
+
+## v3.0.0
+
+* Added support for embedded structs when inserting or updating. [#13](https://github.com/doug-martin/goqu/pull/13) - [@andymoon](https://github.com/andymoon)
+
+## v2.0.3
+
+* Fixed issue with transient columns and the auto select of columns.
+
+## v2.0.2
+
+* Changed references to "github.com/doug-martin/goqu" to "gopkg.in/doug-martin/goqu.v2"
+
+## v2.0.1
+
+* Fixed issue when `ScanStruct(s)` was used with `SelectDistinct` and caused a panic.
+
+## v2.0.0
+
+* When scanning a struct or slice of structs, the struct(s) will be parsed for the column names to select. [#9](https://github.com/doug-martin/goqu/pull/9) - [@technotronicoz](https://github.com/TechnotronicOz)
+
+## v1.0.0
+
+* You can now passed an IdentiferExpression to `As` [#8](https://github.com/doug-martin/goqu/pull/8) - [@croachrose](https://github.com/croachrose)
+* Added info about installation through [gopkg.in](http://labix.org/gopkg.in)
+
+## v0.3.1
+
+* Fixed issue setting Logger when starting a new transaction.
+
+## v0.3.0
+
+* Changed sql generation methods to use a common naming convention. `To(Sql|Insert|Update|Delete)`
+   * Also changed to have common return values `string, []interface{}, error)`
+* Added `Dataset.Prepared` which allows a user to specify whether or not SQL should be interpolated. [#7](https://github.com/doug-martin/goqu/issues/7)
+* Updated Docs
+    * More examples
+* Increased test coverage.
+
+## v0.2.0
+
+* Changed `CrudExec` to not wrap driver errors in a GoquError [#2](https://github.com/doug-martin/goqu/issues/2)
+* Added ability to use a dataset in an `Ex` map or `Eq` expression without having to use `In` [#3](https://github.com/doug-martin/goqu/issues/3)
+   * `db.From("test").Where(goqu.Ex{"a": db.From("test").Select("b")})`
+* Updated readme with links to [`DefaultAdapter`](https://godoc.org/github.com/doug-martin/goqu#DefaultAdapter)
+
+## v0.1.1
+
+* Added SQLite3 adapter [#1](https://github.com/doug-martin/goqu/pull/1) - [@mattn](https://github.com/mattn)
+
+## v0.1.0
+
+* Added:
+    * [`Ex`](https://godoc.org/github.com/doug-martin/goqu#Ex)
+    * [`ExOr`](https://godoc.org/github.com/doug-martin/goqu#ExOr)
+    * [`Op`](https://godoc.org/github.com/doug-martin/goqu#Op)
+* More tests and examples
+* Added CONTRIBUTING.md
+* Added LICENSE information
+* Removed godoc introduction in favor of just maintaining the README.
+
+## v0.0.2
+
+* Fixed issue with goqu.New not returning a pointer to a Database
+
+## v0.0.1
+
+* Initial release

+ 21 - 0
src/github.com/doug-martin/goqu/LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2017 Doug Martin
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 6 - 0
src/github.com/doug-martin/goqu/Makefile

@@ -0,0 +1,6 @@
+#phony dependency task that does nothing
+#"make executable" does not run if there is a ./executable directory, unless the task has a dependency
+phony:
+
+lint:
+	docker run --rm -v ${CURDIR}:/app -w /app golangci/golangci-lint:v1.23.8 golangci-lint run -v

+ 310 - 0
src/github.com/doug-martin/goqu/README.md

@@ -0,0 +1,310 @@
+```
+  __ _  ___   __ _ _   _
+ / _` |/ _ \ / _` | | | |
+| (_| | (_) | (_| | |_| |
+ \__, |\___/ \__, |\__,_|
+ |___/          |_|
+```
+[![GitHub tag](https://img.shields.io/github/tag/doug-martin/goqu.svg?style=flat)](https://github.com/doug-martin/goqu/releases)
+[![Test](https://github.com/doug-martin/goqu/workflows/Test/badge.svg?branch=master&event=push)](https://github.com/doug-martin/goqu/actions?query=workflow%3ATest+and+branch%3Amaster+)
+[![GoDoc](https://godoc.org/github.com/doug-martin/goqu?status.png)](http://godoc.org/github.com/doug-martin/goqu)
+[![codecov](https://codecov.io/gh/doug-martin/goqu/branch/master/graph/badge.svg)](https://codecov.io/gh/doug-martin/goqu)
+[![Go Report Card](https://goreportcard.com/badge/github.com/doug-martin/goqu/v9)](https://goreportcard.com/report/github.com/doug-martin/goqu/v9)
+
+`goqu` is an expressive SQL builder and executor
+    
+If you are upgrading from an older version please read the [Migrating Between Versions](./docs/version_migration.md) docs.
+
+
+## Installation
+
+If using go modules.
+
+```sh
+go get -u github.com/doug-martin/goqu/v9
+```
+
+If you are not using go modules...
+
+**NOTE** You should still be able to use this package if you are using go version `>v1.10` but, you will need to drop the version from the package. `import "github.com/doug-martin/goqu/v9` -> `import "github.com/doug-martin/goqu"`
+
+```sh
+go get -u github.com/doug-martin/goqu
+```
+
+### [Migrating Between Versions](./docs/version_migration.md)
+
+## Features
+
+`goqu` comes with many features but here are a few of the more notable ones
+
+* Query Builder
+* Parameter interpolation (e.g `SELECT * FROM "items" WHERE "id" = ?` -> `SELECT * FROM "items" WHERE "id" = 1`)
+* Built from the ground up with multiple dialects in mind
+* Insert, Multi Insert, Update, and Delete support
+* Scanning of rows to struct[s] or primitive value[s]
+
+While goqu may support the scanning of rows into structs it is not intended to be used as an ORM if you are looking for common ORM features like associations,
+or hooks I would recommend looking at some of the great ORM libraries such as:
+
+* [gorm](https://github.com/jinzhu/gorm)
+* [hood](https://github.com/eaigner/hood)
+
+## Why?
+
+We tried a few other sql builders but each was a thin wrapper around sql fragments that we found error prone. `goqu` was built with the following goals in mind:
+
+* Make the generation of SQL easy and enjoyable
+* Create an expressive DSL that would find common errors with SQL at compile time.
+* Provide a DSL that accounts for the common SQL expressions, NOT every nuance for each database.
+* Provide developers the ability to:
+  * Use SQL when desired
+  * Easily scan results into primitive values and structs
+  * Use the native sql.Db methods when desired
+
+## Docs
+
+* [Dialect](./docs/dialect.md) - Introduction to different dialects (`mysql`, `postgres`, `sqlite3`, `sqlserver` etc) 
+* [Expressions](./docs/expressions.md) - Introduction to `goqu` expressions and common examples.
+* [Select Dataset](./docs/selecting.md) - Docs and examples about creating and executing SELECT sql statements.
+* [Insert Dataset](./docs/inserting.md) - Docs and examples about creating and executing INSERT sql statements.
+* [Update Dataset](./docs/updating.md) - Docs and examples about creating and executing UPDATE sql statements.
+* [Delete Dataset](./docs/deleting.md) - Docs and examples about creating and executing DELETE sql statements.
+* [Prepared Statements](./docs/interpolation.md) - Docs about interpolation and prepared statements in `goqu`.
+* [Database](./docs/database.md) - Docs and examples of using a Database to execute queries in `goqu`
+* [Working with time.Time](./docs/time.md) - Docs on how to use alternate time locations.
+
+## Quick Examples
+
+### Select
+
+See the [select dataset](./docs/selecting.md) docs for more in depth examples
+
+```go
+sql, _, _ := goqu.From("test").ToSQL()
+fmt.Println(sql)
+```
+
+Output:
+
+```
+SELECT * FROM "test"
+```
+
+```go
+sql, _, _ := goqu.From("test").Where(goqu.Ex{
+	"d": []string{"a", "b", "c"},
+}).ToSQL()
+fmt.Println(sql)
+```
+
+Output:
+
+```
+SELECT * FROM "test" WHERE ("d" IN ('a', 'b', 'c'))
+```
+
+### Insert
+
+See the [insert dataset](./docs/inserting.md) docs for more in depth examples
+
+```go
+ds := goqu.Insert("user").
+	Cols("first_name", "last_name").
+	Vals(
+		goqu.Vals{"Greg", "Farley"},
+		goqu.Vals{"Jimmy", "Stewart"},
+		goqu.Vals{"Jeff", "Jeffers"},
+	)
+insertSQL, args, _ := ds.ToSQL()
+fmt.Println(insertSQL, args)
+```
+
+Output: 
+```sql
+INSERT INTO "user" ("first_name", "last_name") VALUES ('Greg', 'Farley'), ('Jimmy', 'Stewart'), ('Jeff', 'Jeffers') []
+```
+
+```go
+ds := goqu.Insert("user").Rows(
+	goqu.Record{"first_name": "Greg", "last_name": "Farley"},
+	goqu.Record{"first_name": "Jimmy", "last_name": "Stewart"},
+	goqu.Record{"first_name": "Jeff", "last_name": "Jeffers"},
+)
+insertSQL, args, _ := ds.ToSQL()
+fmt.Println(insertSQL, args)
+```
+
+Output:
+```
+INSERT INTO "user" ("first_name", "last_name") VALUES ('Greg', 'Farley'), ('Jimmy', 'Stewart'), ('Jeff', 'Jeffers') []
+```
+
+
+```go
+type User struct {
+	FirstName string `db:"first_name"`
+	LastName  string `db:"last_name"`
+}
+ds := goqu.Insert("user").Rows(
+	User{FirstName: "Greg", LastName: "Farley"},
+	User{FirstName: "Jimmy", LastName: "Stewart"},
+	User{FirstName: "Jeff", LastName: "Jeffers"},
+)
+insertSQL, args, _ := ds.ToSQL()
+fmt.Println(insertSQL, args)
+```
+
+Output:
+```
+INSERT INTO "user" ("first_name", "last_name") VALUES ('Greg', 'Farley'), ('Jimmy', 'Stewart'), ('Jeff', 'Jeffers') []
+```
+
+```go
+ds := goqu.Insert("user").Prepared(true).
+	FromQuery(goqu.From("other_table"))
+insertSQL, args, _ := ds.ToSQL()
+fmt.Println(insertSQL, args)
+```
+
+Output:
+```
+INSERT INTO "user" SELECT * FROM "other_table" []
+```
+
+```go
+ds := goqu.Insert("user").Prepared(true).
+	Cols("first_name", "last_name").
+	FromQuery(goqu.From("other_table").Select("fn", "ln"))
+insertSQL, args, _ := ds.ToSQL()
+fmt.Println(insertSQL, args)
+```
+
+Output:
+```
+INSERT INTO "user" ("first_name", "last_name") SELECT "fn", "ln" FROM "other_table" []
+```
+
+### Update
+
+See the [update dataset](./docs/updating.md) docs for more in depth examples
+
+```go
+sql, args, _ := goqu.Update("items").Set(
+	goqu.Record{"name": "Test", "address": "111 Test Addr"},
+).ToSQL()
+fmt.Println(sql, args)
+```
+
+Output:
+```
+UPDATE "items" SET "address"='111 Test Addr',"name"='Test' []
+```
+
+```go
+type item struct {
+	Address string `db:"address"`
+	Name    string `db:"name" goqu:"skipupdate"`
+}
+sql, args, _ := goqu.Update("items").Set(
+	item{Name: "Test", Address: "111 Test Addr"},
+).ToSQL()
+fmt.Println(sql, args)
+```
+
+Output:
+```
+UPDATE "items" SET "address"='111 Test Addr' []
+```
+
+```go
+sql, _, _ := goqu.Update("test").
+	Set(goqu.Record{"foo": "bar"}).
+	Where(goqu.Ex{
+		"a": goqu.Op{"gt": 10}
+	}).ToSQL()
+fmt.Println(sql)
+```
+
+Output:
+```
+UPDATE "test" SET "foo"='bar' WHERE ("a" > 10)
+```
+
+### Delete
+
+See the [delete dataset](./docs/deleting.md) docs for more in depth examples
+
+```go
+ds := goqu.Delete("items")
+
+sql, args, _ := ds.ToSQL()
+fmt.Println(sql, args)
+```
+
+```go
+sql, _, _ := goqu.Delete("test").Where(goqu.Ex{
+		"c": nil
+	}).ToSQL()
+fmt.Println(sql)
+```
+
+Output:
+```
+DELETE FROM "test" WHERE ("c" IS NULL)
+```
+
+<a name="contributions"></a>
+## Contributions
+
+I am always welcoming contributions of any type. Please open an issue or create a PR if you find an issue with any of the following.
+
+* An issue with Documentation
+* You found the documentation lacking in some way
+
+If you have an issue with the package please include the following
+
+* The dialect you are using
+* A description of the problem
+* A short example of how to reproduce (if applicable)
+
+Without those basics it can be difficult to reproduce your issue locally. You may be asked for more information but that is a good starting point.
+
+### New Features
+
+New features and/or enhancements are great and I encourage you to either submit a PR or create an issue. In both cases include the following as the need/requirement may not be readily apparent.
+
+1. The use case
+2. A short example
+
+If you are issuing a PR also also include the following
+
+1. Tests - otherwise the PR will not be merged
+2. Documentation - otherwise the PR will not be merged
+3. Examples - [If applicable] see example_test.go for examples
+
+If you find an issue you want to work on please comment on it letting other people know you are looking at it and I will assign the issue to you.
+
+If want to work on an issue but dont know where to start just leave a comment and I'll be more than happy to point you in the right direction.
+
+### Running tests
+The test suite requires a postgres, mysql and sqlserver databases. You can override the connection strings with the [`MYSQL_URI`, `PG_URI`, `SQLSERVER_URI` environment variables](https://github.com/doug-martin/goqu/blob/2fe3349/docker-compose.yml#L26)*
+
+```sh
+go test -v -race ./...
+```
+
+You can also run the tests in a container using [docker-compose](https://docs.docker.com/compose/).
+
+```sh
+GO_VERSION=latest docker-compose run goqu
+```
+
+## License
+
+`goqu` is released under the [MIT License](http://www.opensource.org/licenses/MIT).
+
+
+
+
+

+ 1 - 0
src/github.com/doug-martin/goqu/_config.yml

@@ -0,0 +1 @@
+theme: jekyll-theme-cayman

+ 3 - 0
src/github.com/doug-martin/goqu/codecov.yml

@@ -0,0 +1,3 @@
+ignore:
+  - "**/mocks/**"         # glob accepted
+  - "mocks/**"         # glob accepted

+ 632 - 0
src/github.com/doug-martin/goqu/database.go

@@ -0,0 +1,632 @@
+package goqu
+
+import (
+	"context"
+	"database/sql"
+	"sync"
+
+	"github.com/doug-martin/goqu/v9/exec"
+)
+
+type (
+	Logger interface {
+		Printf(format string, v ...interface{})
+	}
+	// Interface for sql.DB, an interface is used so you can use with other
+	// libraries such as sqlx instead of the native sql.DB
+	SQLDatabase interface {
+		Begin() (*sql.Tx, error)
+		BeginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error)
+		ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error)
+		PrepareContext(ctx context.Context, query string) (*sql.Stmt, error)
+		QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error)
+		QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row
+	}
+	// This struct is the wrapper for a Db. The struct delegates most calls to either an Exec instance or to the Db
+	// passed into the constructor.
+	Database struct {
+		logger  Logger
+		dialect string
+		Db      SQLDatabase
+		qf      exec.QueryFactory
+		qfOnce  sync.Once
+	}
+)
+
+// This is the common entry point into goqu.
+//
+// dialect: This is the adapter dialect, you should see your database adapter for the string to use. Built in adapters
+// can be found at https://github.com/doug-martin/goqu/tree/master/adapters
+//
+// db: A sql.Db to use for querying the database
+//      import (
+//          "database/sql"
+//          "fmt"
+//          "github.com/doug-martin/goqu/v9"
+//          _ "github.com/doug-martin/goqu/v9/adapters/postgres"
+//          _ "github.com/lib/pq"
+//      )
+//
+//      func main() {
+//          sqlDb, err := sql.Open("postgres", "user=postgres dbname=goqupostgres sslmode=disable ")
+//          if err != nil {
+//              panic(err.Error())
+//          }
+//          db := goqu.New("postgres", sqlDb)
+//      }
+// The most commonly used Database method is From, which creates a new Dataset that uses the correct adapter and
+// supports queries.
+//          var ids []uint32
+//          if err := db.From("items").Where(goqu.I("id").Gt(10)).Pluck("id", &ids); err != nil {
+//              panic(err.Error())
+//          }
+//          fmt.Printf("%+v", ids)
+func newDatabase(dialect string, db SQLDatabase) *Database {
+	return &Database{dialect: dialect, Db: db}
+}
+
+// returns this databases dialect
+func (d *Database) Dialect() string {
+	return d.dialect
+}
+
+// Starts a new Transaction.
+func (d *Database) Begin() (*TxDatabase, error) {
+	sqlTx, err := d.Db.Begin()
+	if err != nil {
+		return nil, err
+	}
+	tx := NewTx(d.dialect, sqlTx)
+	tx.Logger(d.logger)
+	return tx, nil
+}
+
+// Starts a new Transaction. See sql.DB#BeginTx for option description
+func (d *Database) BeginTx(ctx context.Context, opts *sql.TxOptions) (*TxDatabase, error) {
+	sqlTx, err := d.Db.BeginTx(ctx, opts)
+	if err != nil {
+		return nil, err
+	}
+	tx := NewTx(d.dialect, sqlTx)
+	tx.Logger(d.logger)
+	return tx, nil
+}
+
+// WithTx starts a new transaction and executes it in Wrap method
+func (d *Database) WithTx(fn func(*TxDatabase) error) error {
+	tx, err := d.Begin()
+	if err != nil {
+		return err
+	}
+	return tx.Wrap(func() error { return fn(tx) })
+}
+
+// Creates a new Dataset that uses the correct adapter and supports queries.
+//          var ids []uint32
+//          if err := db.From("items").Where(goqu.I("id").Gt(10)).Pluck("id", &ids); err != nil {
+//              panic(err.Error())
+//          }
+//          fmt.Printf("%+v", ids)
+//
+// from...: Sources for you dataset, could be table names (strings), a goqu.Literal or another goqu.Dataset
+func (d *Database) From(from ...interface{}) *SelectDataset {
+	return newDataset(d.dialect, d.queryFactory()).From(from...)
+}
+
+func (d *Database) Select(cols ...interface{}) *SelectDataset {
+	return newDataset(d.dialect, d.queryFactory()).Select(cols...)
+}
+
+func (d *Database) Update(table interface{}) *UpdateDataset {
+	return newUpdateDataset(d.dialect, d.queryFactory()).Table(table)
+}
+
+func (d *Database) Insert(table interface{}) *InsertDataset {
+	return newInsertDataset(d.dialect, d.queryFactory()).Into(table)
+}
+
+func (d *Database) Delete(table interface{}) *DeleteDataset {
+	return newDeleteDataset(d.dialect, d.queryFactory()).From(table)
+}
+
+func (d *Database) Truncate(table ...interface{}) *TruncateDataset {
+	return newTruncateDataset(d.dialect, d.queryFactory()).Table(table...)
+}
+
+// Sets the logger for to use when logging queries
+func (d *Database) Logger(logger Logger) {
+	d.logger = logger
+}
+
+// Logs a given operation with the specified sql and arguments
+func (d *Database) Trace(op, sqlString string, args ...interface{}) {
+	if d.logger != nil {
+		if sqlString != "" {
+			if len(args) != 0 {
+				d.logger.Printf("[goqu] %s [query:=`%s` args:=%+v]", op, sqlString, args)
+			} else {
+				d.logger.Printf("[goqu] %s [query:=`%s`]", op, sqlString)
+			}
+		} else {
+			d.logger.Printf("[goqu] %s", op)
+		}
+	}
+}
+
+// Uses the db to Execute the query with arguments and return the sql.Result
+//
+// query: The SQL to execute
+//
+// args...: for any placeholder parameters in the query
+func (d *Database) Exec(query string, args ...interface{}) (sql.Result, error) {
+	return d.ExecContext(context.Background(), query, args...)
+}
+
+// Uses the db to Execute the query with arguments and return the sql.Result
+//
+// query: The SQL to execute
+//
+// args...: for any placeholder parameters in the query
+func (d *Database) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) {
+	d.Trace("EXEC", query, args...)
+	return d.Db.ExecContext(ctx, query, args...)
+}
+
+// Can be used to prepare a query.
+//
+// You can use this in tandem with a dataset by doing the following.
+//    sql, args, err := db.From("items").Where(goqu.I("id").Gt(10)).ToSQL(true)
+//    if err != nil{
+//        panic(err.Error()) //you could gracefully handle the error also
+//    }
+//    stmt, err := db.Prepare(sql)
+//    if err != nil{
+//        panic(err.Error()) //you could gracefully handle the error also
+//    }
+//    defer stmt.Close()
+//    rows, err := stmt.Query(args)
+//    if err != nil{
+//        panic(err.Error()) //you could gracefully handle the error also
+//    }
+//    defer rows.Close()
+//    for rows.Next(){
+//              //scan your rows
+//    }
+//    if rows.Err() != nil{
+//        panic(err.Error()) //you could gracefully handle the error also
+//    }
+//
+// query: The SQL statement to prepare.
+func (d *Database) Prepare(query string) (*sql.Stmt, error) {
+	return d.PrepareContext(context.Background(), query)
+}
+
+// Can be used to prepare a query.
+//
+// You can use this in tandem with a dataset by doing the following.
+//    sql, args, err := db.From("items").Where(goqu.I("id").Gt(10)).ToSQL(true)
+//    if err != nil{
+//        panic(err.Error()) //you could gracefully handle the error also
+//    }
+//    stmt, err := db.Prepare(sql)
+//    if err != nil{
+//        panic(err.Error()) //you could gracefully handle the error also
+//    }
+//    defer stmt.Close()
+//    rows, err := stmt.QueryContext(ctx, args)
+//    if err != nil{
+//        panic(err.Error()) //you could gracefully handle the error also
+//    }
+//    defer rows.Close()
+//    for rows.Next(){
+//              //scan your rows
+//    }
+//    if rows.Err() != nil{
+//        panic(err.Error()) //you could gracefully handle the error also
+//    }
+//
+// query: The SQL statement to prepare.
+func (d *Database) PrepareContext(ctx context.Context, query string) (*sql.Stmt, error) {
+	d.Trace("PREPARE", query)
+	return d.Db.PrepareContext(ctx, query)
+}
+
+// Used to query for multiple rows.
+//
+// You can use this in tandem with a dataset by doing the following.
+//    sql, err := db.From("items").Where(goqu.I("id").Gt(10)).ToSQL()
+//    if err != nil{
+//        panic(err.Error()) //you could gracefully handle the error also
+//    }
+//    rows, err := stmt.Query(args)
+//    if err != nil{
+//        panic(err.Error()) //you could gracefully handle the error also
+//    }
+//    defer rows.Close()
+//    for rows.Next(){
+//              //scan your rows
+//    }
+//    if rows.Err() != nil{
+//        panic(err.Error()) //you could gracefully handle the error also
+//    }
+//
+// query: The SQL to execute
+//
+// args...: for any placeholder parameters in the query
+func (d *Database) Query(query string, args ...interface{}) (*sql.Rows, error) {
+	return d.QueryContext(context.Background(), query, args...)
+}
+
+// Used to query for multiple rows.
+//
+// You can use this in tandem with a dataset by doing the following.
+//    sql, err := db.From("items").Where(goqu.I("id").Gt(10)).ToSQL()
+//    if err != nil{
+//        panic(err.Error()) //you could gracefully handle the error also
+//    }
+//    rows, err := stmt.QueryContext(ctx, args)
+//    if err != nil{
+//        panic(err.Error()) //you could gracefully handle the error also
+//    }
+//    defer rows.Close()
+//    for rows.Next(){
+//              //scan your rows
+//    }
+//    if rows.Err() != nil{
+//        panic(err.Error()) //you could gracefully handle the error also
+//    }
+//
+// query: The SQL to execute
+//
+// args...: for any placeholder parameters in the query
+func (d *Database) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) {
+	d.Trace("QUERY", query, args...)
+	return d.Db.QueryContext(ctx, query, args...)
+}
+
+// Used to query for a single row.
+//
+// You can use this in tandem with a dataset by doing the following.
+//    sql, err := db.From("items").Where(goqu.I("id").Gt(10)).Limit(1).ToSQL()
+//    if err != nil{
+//        panic(err.Error()) //you could gracefully handle the error also
+//    }
+//    rows, err := stmt.QueryRow(args)
+//    if err != nil{
+//        panic(err.Error()) //you could gracefully handle the error also
+//    }
+//    //scan your row
+//
+// query: The SQL to execute
+//
+// args...: for any placeholder parameters in the query
+func (d *Database) QueryRow(query string, args ...interface{}) *sql.Row {
+	return d.QueryRowContext(context.Background(), query, args...)
+}
+
+// Used to query for a single row.
+//
+// You can use this in tandem with a dataset by doing the following.
+//    sql, err := db.From("items").Where(goqu.I("id").Gt(10)).Limit(1).ToSQL()
+//    if err != nil{
+//        panic(err.Error()) //you could gracefully handle the error also
+//    }
+//    rows, err := stmt.QueryRowContext(ctx, args)
+//    if err != nil{
+//        panic(err.Error()) //you could gracefully handle the error also
+//    }
+//    //scan your row
+//
+// query: The SQL to execute
+//
+// args...: for any placeholder parameters in the query
+func (d *Database) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row {
+	d.Trace("QUERY ROW", query, args...)
+	return d.Db.QueryRowContext(ctx, query, args...)
+}
+
+func (d *Database) queryFactory() exec.QueryFactory {
+	d.qfOnce.Do(func() {
+		d.qf = exec.NewQueryFactory(d)
+	})
+	return d.qf
+}
+
+// Queries the database using the supplied query, and args and uses CrudExec.ScanStructs to scan the results into a
+// slice of structs
+//
+// i: A pointer to a slice of structs
+//
+// query: The SQL to execute
+//
+// args...: for any placeholder parameters in the query
+func (d *Database) ScanStructs(i interface{}, query string, args ...interface{}) error {
+	return d.ScanStructsContext(context.Background(), i, query, args...)
+}
+
+// Queries the database using the supplied context, query, and args and uses CrudExec.ScanStructsContext to scan the
+// results into a slice of structs
+//
+// i: A pointer to a slice of structs
+//
+// query: The SQL to execute
+//
+// args...: for any placeholder parameters in the query
+func (d *Database) ScanStructsContext(ctx context.Context, i interface{}, query string, args ...interface{}) error {
+	return d.queryFactory().FromSQL(query, args...).ScanStructsContext(ctx, i)
+}
+
+// Queries the database using the supplied query, and args and uses CrudExec.ScanStruct to scan the results into a
+// struct
+//
+// i: A pointer to a struct
+//
+// query: The SQL to execute
+//
+// args...: for any placeholder parameters in the query
+func (d *Database) ScanStruct(i interface{}, query string, args ...interface{}) (bool, error) {
+	return d.ScanStructContext(context.Background(), i, query, args...)
+}
+
+// Queries the database using the supplied context, query, and args and uses CrudExec.ScanStructContext to scan the
+// results into a struct
+//
+// i: A pointer to a struct
+//
+// query: The SQL to execute
+//
+// args...: for any placeholder parameters in the query
+func (d *Database) ScanStructContext(ctx context.Context, i interface{}, query string, args ...interface{}) (bool, error) {
+	return d.queryFactory().FromSQL(query, args...).ScanStructContext(ctx, i)
+}
+
+// Queries the database using the supplied query, and args and uses CrudExec.ScanVals to scan the results into a slice
+// of primitive values
+//
+// i: A pointer to a slice of primitive values
+//
+// query: The SQL to execute
+//
+// args...: for any placeholder parameters in the query
+func (d *Database) ScanVals(i interface{}, query string, args ...interface{}) error {
+	return d.ScanValsContext(context.Background(), i, query, args...)
+}
+
+// Queries the database using the supplied context, query, and args and uses CrudExec.ScanValsContext to scan the
+// results into a slice of primitive values
+//
+// i: A pointer to a slice of primitive values
+//
+// query: The SQL to execute
+//
+// args...: for any placeholder parameters in the query
+func (d *Database) ScanValsContext(ctx context.Context, i interface{}, query string, args ...interface{}) error {
+	return d.queryFactory().FromSQL(query, args...).ScanValsContext(ctx, i)
+}
+
+// Queries the database using the supplied query, and args and uses CrudExec.ScanVal to scan the results into a
+// primitive value
+//
+// i: A pointer to a primitive value
+//
+// query: The SQL to execute
+//
+// args...: for any placeholder parameters in the query
+func (d *Database) ScanVal(i interface{}, query string, args ...interface{}) (bool, error) {
+	return d.ScanValContext(context.Background(), i, query, args...)
+}
+
+// Queries the database using the supplied context, query, and args and uses CrudExec.ScanValContext to scan the
+// results into a primitive value
+//
+// i: A pointer to a primitive value
+//
+// query: The SQL to execute
+//
+// args...: for any placeholder parameters in the query
+func (d *Database) ScanValContext(ctx context.Context, i interface{}, query string, args ...interface{}) (bool, error) {
+	return d.queryFactory().FromSQL(query, args...).ScanValContext(ctx, i)
+}
+
+// A wrapper around a sql.Tx and works the same way as Database
+type (
+	// Interface for sql.Tx, an interface is used so you can use with other
+	// libraries such as sqlx instead of the native sql.DB
+	SQLTx interface {
+		ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error)
+		PrepareContext(ctx context.Context, query string) (*sql.Stmt, error)
+		QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error)
+		QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row
+		Commit() error
+		Rollback() error
+	}
+	TxDatabase struct {
+		logger  Logger
+		dialect string
+		Tx      SQLTx
+		qf      exec.QueryFactory
+		qfOnce  sync.Once
+	}
+)
+
+// Creates a new TxDatabase
+func NewTx(dialect string, tx SQLTx) *TxDatabase {
+	return &TxDatabase{dialect: dialect, Tx: tx}
+}
+
+// returns this databases dialect
+func (td *TxDatabase) Dialect() string {
+	return td.dialect
+}
+
+// Creates a new Dataset for querying a Database.
+func (td *TxDatabase) From(cols ...interface{}) *SelectDataset {
+	return newDataset(td.dialect, td.queryFactory()).From(cols...)
+}
+
+func (td *TxDatabase) Select(cols ...interface{}) *SelectDataset {
+	return newDataset(td.dialect, td.queryFactory()).Select(cols...)
+}
+
+func (td *TxDatabase) Update(table interface{}) *UpdateDataset {
+	return newUpdateDataset(td.dialect, td.queryFactory()).Table(table)
+}
+
+func (td *TxDatabase) Insert(table interface{}) *InsertDataset {
+	return newInsertDataset(td.dialect, td.queryFactory()).Into(table)
+}
+
+func (td *TxDatabase) Delete(table interface{}) *DeleteDataset {
+	return newDeleteDataset(td.dialect, td.queryFactory()).From(table)
+}
+
+func (td *TxDatabase) Truncate(table ...interface{}) *TruncateDataset {
+	return newTruncateDataset(td.dialect, td.queryFactory()).Table(table...)
+}
+
+// Sets the logger
+func (td *TxDatabase) Logger(logger Logger) {
+	td.logger = logger
+}
+
+func (td *TxDatabase) Trace(op, sqlString string, args ...interface{}) {
+	if td.logger != nil {
+		if sqlString != "" {
+			if len(args) != 0 {
+				td.logger.Printf("[goqu - transaction] %s [query:=`%s` args:=%+v] ", op, sqlString, args)
+			} else {
+				td.logger.Printf("[goqu - transaction] %s [query:=`%s`] ", op, sqlString)
+			}
+		} else {
+			td.logger.Printf("[goqu - transaction] %s", op)
+		}
+	}
+}
+
+// See Database#Exec
+func (td *TxDatabase) Exec(query string, args ...interface{}) (sql.Result, error) {
+	return td.ExecContext(context.Background(), query, args...)
+}
+
+// See Database#ExecContext
+func (td *TxDatabase) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) {
+	td.Trace("EXEC", query, args...)
+	return td.Tx.ExecContext(ctx, query, args...)
+}
+
+// See Database#Prepare
+func (td *TxDatabase) Prepare(query string) (*sql.Stmt, error) {
+	return td.PrepareContext(context.Background(), query)
+}
+
+// See Database#PrepareContext
+func (td *TxDatabase) PrepareContext(ctx context.Context, query string) (*sql.Stmt, error) {
+	td.Trace("PREPARE", query)
+	return td.Tx.PrepareContext(ctx, query)
+}
+
+// See Database#Query
+func (td *TxDatabase) Query(query string, args ...interface{}) (*sql.Rows, error) {
+	return td.QueryContext(context.Background(), query, args...)
+}
+
+// See Database#QueryContext
+func (td *TxDatabase) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) {
+	td.Trace("QUERY", query, args...)
+	return td.Tx.QueryContext(ctx, query, args...)
+}
+
+// See Database#QueryRow
+func (td *TxDatabase) QueryRow(query string, args ...interface{}) *sql.Row {
+	return td.QueryRowContext(context.Background(), query, args...)
+}
+
+// See Database#QueryRowContext
+func (td *TxDatabase) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row {
+	td.Trace("QUERY ROW", query, args...)
+	return td.Tx.QueryRowContext(ctx, query, args...)
+}
+
+func (td *TxDatabase) queryFactory() exec.QueryFactory {
+	td.qfOnce.Do(func() {
+		td.qf = exec.NewQueryFactory(td)
+	})
+	return td.qf
+}
+
+// See Database#ScanStructs
+func (td *TxDatabase) ScanStructs(i interface{}, query string, args ...interface{}) error {
+	return td.ScanStructsContext(context.Background(), i, query, args...)
+}
+
+// See Database#ScanStructsContext
+func (td *TxDatabase) ScanStructsContext(ctx context.Context, i interface{}, query string, args ...interface{}) error {
+	return td.queryFactory().FromSQL(query, args...).ScanStructsContext(ctx, i)
+}
+
+// See Database#ScanStruct
+func (td *TxDatabase) ScanStruct(i interface{}, query string, args ...interface{}) (bool, error) {
+	return td.ScanStructContext(context.Background(), i, query, args...)
+}
+
+// See Database#ScanStructContext
+func (td *TxDatabase) ScanStructContext(ctx context.Context, i interface{}, query string, args ...interface{}) (bool, error) {
+	return td.queryFactory().FromSQL(query, args...).ScanStructContext(ctx, i)
+}
+
+// See Database#ScanVals
+func (td *TxDatabase) ScanVals(i interface{}, query string, args ...interface{}) error {
+	return td.ScanValsContext(context.Background(), i, query, args...)
+}
+
+// See Database#ScanValsContext
+func (td *TxDatabase) ScanValsContext(ctx context.Context, i interface{}, query string, args ...interface{}) error {
+	return td.queryFactory().FromSQL(query, args...).ScanValsContext(ctx, i)
+}
+
+// See Database#ScanVal
+func (td *TxDatabase) ScanVal(i interface{}, query string, args ...interface{}) (bool, error) {
+	return td.ScanValContext(context.Background(), i, query, args...)
+}
+
+// See Database#ScanValContext
+func (td *TxDatabase) ScanValContext(ctx context.Context, i interface{}, query string, args ...interface{}) (bool, error) {
+	return td.queryFactory().FromSQL(query, args...).ScanValContext(ctx, i)
+}
+
+// COMMIT the transaction
+func (td *TxDatabase) Commit() error {
+	td.Trace("COMMIT", "")
+	return td.Tx.Commit()
+}
+
+// ROLLBACK the transaction
+func (td *TxDatabase) Rollback() error {
+	td.Trace("ROLLBACK", "")
+	return td.Tx.Rollback()
+}
+
+// A helper method that will automatically COMMIT or ROLLBACK once the  supplied function is done executing
+//
+//      tx, err := db.Begin()
+//      if err != nil{
+//           panic(err.Error()) // you could gracefully handle the error also
+//      }
+//      if err := tx.Wrap(func() error{
+//          if _, err := tx.From("test").Insert(Record{"a":1, "b": "b"}).Exec(){
+//              // this error will be the return error from the Wrap call
+//              return err
+//          }
+//          return nil
+//      }); err != nil{
+//           panic(err.Error()) // you could gracefully handle the error also
+//      }
+func (td *TxDatabase) Wrap(fn func() error) error {
+	if err := fn(); err != nil {
+		if rollbackErr := td.Rollback(); rollbackErr != nil {
+			return rollbackErr
+		}
+		return err
+	}
+	return td.Commit()
+}

+ 146 - 0
src/github.com/doug-martin/goqu/database_example_test.go

@@ -0,0 +1,146 @@
+package goqu_test
+
+import (
+	"context"
+	"database/sql"
+	"fmt"
+	"time"
+
+	"github.com/doug-martin/goqu/v9"
+)
+
+func ExampleDatabase_Begin() {
+	db := getDb()
+
+	tx, err := db.Begin()
+	if err != nil {
+		fmt.Println("Error starting transaction", err.Error())
+	}
+
+	// use tx.From to get a dataset that will execute within this transaction
+	update := tx.Update("goqu_user").
+		Set(goqu.Record{"last_name": "Ucon"}).
+		Where(goqu.Ex{"last_name": "Yukon"}).
+		Returning("id").
+		Executor()
+
+	var ids []int64
+	if err := update.ScanVals(&ids); err != nil {
+		if rErr := tx.Rollback(); rErr != nil {
+			fmt.Println("An error occurred while issuing ROLLBACK\n\t", rErr.Error())
+		} else {
+			fmt.Println("An error occurred while updating users ROLLBACK transaction\n\t", err.Error())
+		}
+		return
+	}
+	if err := tx.Commit(); err != nil {
+		fmt.Println("An error occurred while issuing COMMIT\n\t", err.Error())
+	} else {
+		fmt.Printf("Updated users in transaction [ids:=%+v]", ids)
+	}
+	// Output:
+	// Updated users in transaction [ids:=[1 2 3]]
+}
+
+func ExampleDatabase_BeginTx() {
+	db := getDb()
+
+	ctx := context.Background()
+	tx, err := db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelReadCommitted})
+	if err != nil {
+		fmt.Println("Error starting transaction", err.Error())
+	}
+
+	// use tx.From to get a dataset that will execute within this transaction
+	update := tx.Update("goqu_user").
+		Set(goqu.Record{"last_name": "Ucon"}).
+		Where(goqu.Ex{"last_name": "Yukon"}).
+		Returning("id").
+		Executor()
+
+	var ids []int64
+	if err := update.ScanVals(&ids); err != nil {
+		if rErr := tx.Rollback(); rErr != nil {
+			fmt.Println("An error occurred while issuing ROLLBACK\n\t", rErr.Error())
+		} else {
+			fmt.Println("An error occurred while updating users ROLLBACK transaction\n\t", err.Error())
+		}
+		return
+	}
+	if err := tx.Commit(); err != nil {
+		fmt.Println("An error occurred while issuing COMMIT\n\t", err.Error())
+	} else {
+		fmt.Printf("Updated users in transaction [ids:=%+v]", ids)
+	}
+	// Output:
+	// Updated users in transaction [ids:=[1 2 3]]
+}
+
+func ExampleDatabase_WithTx() {
+	db := getDb()
+	var ids []int64
+	if err := db.WithTx(func(tx *goqu.TxDatabase) error {
+		// use tx.From to get a dataset that will execute within this transaction
+		update := tx.Update("goqu_user").
+			Where(goqu.Ex{"last_name": "Yukon"}).
+			Returning("id").
+			Set(goqu.Record{"last_name": "Ucon"}).
+			Executor()
+
+		return update.ScanVals(&ids)
+	}); err != nil {
+		fmt.Println("An error occurred in transaction\n\t", err.Error())
+	} else {
+		fmt.Printf("Updated users in transaction [ids:=%+v]", ids)
+	}
+	// Output:
+	// Updated users in transaction [ids:=[1 2 3]]
+}
+
+func ExampleDatabase_Dialect() {
+	db := getDb()
+
+	fmt.Println(db.Dialect())
+
+	// Output:
+	// postgres
+}
+
+func ExampleDatabase_Exec() {
+	db := getDb()
+
+	_, err := db.Exec(`DROP TABLE "user_role"; DROP TABLE "goqu_user"`)
+	if err != nil {
+		fmt.Println("Error occurred while dropping tables", err.Error())
+	}
+	fmt.Println("Dropped tables user_role and goqu_user")
+	// Output:
+	// Dropped tables user_role and goqu_user
+}
+
+func ExampleDatabase_ExecContext() {
+	db := getDb()
+	d := time.Now().Add(50 * time.Millisecond)
+	ctx, cancel := context.WithDeadline(context.Background(), d)
+	defer cancel()
+	_, err := db.ExecContext(ctx, `DROP TABLE "user_role"; DROP TABLE "goqu_user"`)
+	if err != nil {
+		fmt.Println("Error occurred while dropping tables", err.Error())
+	}
+	fmt.Println("Dropped tables user_role and goqu_user")
+	// Output:
+	// Dropped tables user_role and goqu_user
+}
+
+func ExampleDatabase_From() {
+	db := getDb()
+	var names []string
+
+	if err := db.From("goqu_user").Select("first_name").ScanVals(&names); err != nil {
+		fmt.Println(err.Error())
+	} else {
+		fmt.Println("Fetched Users names:", names)
+	}
+	// Output:
+	// Fetched Users names: [Bob Sally Vinita John]
+}

+ 700 - 0
src/github.com/doug-martin/goqu/database_test.go

@@ -0,0 +1,700 @@
+package goqu
+
+import (
+	"context"
+	"fmt"
+	"sync"
+	"testing"
+
+	"github.com/DATA-DOG/go-sqlmock"
+	"github.com/doug-martin/goqu/v9/internal/errors"
+	"github.com/stretchr/testify/suite"
+)
+
+type testActionItem struct {
+	Address string `db:"address"`
+	Name    string `db:"name"`
+}
+
+type dbTestMockLogger struct {
+	Messages []string
+}
+
+func (dtml *dbTestMockLogger) Printf(format string, v ...interface{}) {
+	dtml.Messages = append(dtml.Messages, fmt.Sprintf(format, v...))
+}
+
+func (dtml *dbTestMockLogger) Reset() {
+	dtml.Messages = dtml.Messages[0:0]
+}
+
+type databaseSuite struct {
+	suite.Suite
+}
+
+func (ds *databaseSuite) TestLogger() {
+	mDb, mock, err := sqlmock.New()
+	ds.NoError(err)
+	mock.ExpectQuery(`SELECT \* FROM "items"`).
+		WithArgs().
+		WillReturnRows(sqlmock.NewRows([]string{"address", "name"}).
+			FromCSVString("111 Test Addr,Test1\n211 Test Addr,Test2"))
+
+	mock.ExpectExec(`SELECT \* FROM "items" WHERE "id" = ?`).
+		WithArgs(1).
+		WillReturnResult(sqlmock.NewResult(0, 0))
+
+	db := New("db-mock", mDb)
+	logger := new(dbTestMockLogger)
+	db.Logger(logger)
+	var items []testActionItem
+	ds.NoError(db.ScanStructs(&items, `SELECT * FROM "items"`))
+	_, err = db.Exec(`SELECT * FROM "items" WHERE "id" = ?`, 1)
+	ds.NoError(err)
+	db.Trace("TEST", "")
+	ds.Equal([]string{
+		"[goqu] QUERY [query:=`SELECT * FROM \"items\"`]",
+		"[goqu] EXEC [query:=`SELECT * FROM \"items\" WHERE \"id\" = ?` args:=[1]]",
+		"[goqu] TEST",
+	}, logger.Messages)
+}
+
+func (ds *databaseSuite) TestScanStructs() {
+	mDb, mock, err := sqlmock.New()
+	ds.NoError(err)
+	mock.ExpectQuery(`SELECT \* FROM "items"`).
+		WithArgs().
+		WillReturnRows(sqlmock.NewRows([]string{"address", "name"}).
+			FromCSVString("111 Test Addr,Test1\n211 Test Addr,Test2"))
+
+	mock.ExpectQuery(`SELECT "test" FROM "items"`).
+		WithArgs().
+		WillReturnRows(sqlmock.NewRows([]string{"test"}).FromCSVString("test1\ntest2"))
+
+	db := New("db-mock", mDb)
+	var items []testActionItem
+	ds.NoError(db.ScanStructs(&items, `SELECT * FROM "items"`))
+	ds.Len(items, 2)
+	ds.Equal("111 Test Addr", items[0].Address)
+	ds.Equal("Test1", items[0].Name)
+
+	ds.Equal("211 Test Addr", items[1].Address)
+	ds.Equal("Test2", items[1].Name)
+
+	items = items[0:0]
+	ds.EqualError(db.ScanStructs(items, `SELECT * FROM "items"`),
+		"goqu: type must be a pointer to a slice when scanning into structs")
+	ds.EqualError(db.ScanStructs(&testActionItem{}, `SELECT * FROM "items"`),
+		"goqu: type must be a pointer to a slice when scanning into structs")
+	ds.EqualError(db.ScanStructs(&items, `SELECT "test" FROM "items"`),
+		`goqu: unable to find corresponding field to column "test" returned by query`)
+}
+
+func (ds *databaseSuite) TestScanStruct() {
+	mDb, mock, err := sqlmock.New()
+	ds.NoError(err)
+	mock.ExpectQuery(`SELECT \* FROM "items" LIMIT 1`).
+		WithArgs().
+		WillReturnRows(sqlmock.NewRows([]string{"address", "name"}).FromCSVString("111 Test Addr,Test1"))
+
+	mock.ExpectQuery(`SELECT "test" FROM "items" LIMIT 1`).
+		WithArgs().
+		WillReturnRows(sqlmock.NewRows([]string{"test"}).FromCSVString("test1\ntest2"))
+
+	db := New("mock", mDb)
+	var item testActionItem
+	found, err := db.ScanStruct(&item, `SELECT * FROM "items" LIMIT 1`)
+	ds.NoError(err)
+	ds.True(found)
+	ds.Equal("111 Test Addr", item.Address)
+	ds.Equal("Test1", item.Name)
+
+	_, err = db.ScanStruct(item, `SELECT * FROM "items" LIMIT 1`)
+	ds.EqualError(err, "goqu: type must be a pointer to a struct when scanning into a struct")
+	_, err = db.ScanStruct([]testActionItem{}, `SELECT * FROM "items" LIMIT 1`)
+	ds.EqualError(err, "goqu: type must be a pointer to a struct when scanning into a struct")
+	_, err = db.ScanStruct(&item, `SELECT "test" FROM "items" LIMIT 1`)
+	ds.EqualError(err, `goqu: unable to find corresponding field to column "test" returned by query`)
+}
+
+func (ds *databaseSuite) TestScanVals() {
+	mDb, mock, err := sqlmock.New()
+	ds.NoError(err)
+	mock.ExpectQuery(`SELECT "id" FROM "items"`).
+		WithArgs().
+		WillReturnRows(sqlmock.NewRows([]string{"id"}).FromCSVString("1\n2\n3\n4\n5"))
+
+	db := New("mock", mDb)
+	var ids []uint32
+	ds.NoError(db.ScanVals(&ids, `SELECT "id" FROM "items"`))
+	ds.Len(ids, 5)
+
+	ds.EqualError(db.ScanVals([]uint32{}, `SELECT "id" FROM "items"`),
+		"goqu: type must be a pointer to a slice when scanning into vals")
+	ds.EqualError(db.ScanVals(testActionItem{}, `SELECT "id" FROM "items"`),
+		"goqu: type must be a pointer to a slice when scanning into vals")
+}
+
+func (ds *databaseSuite) TestScanVal() {
+	mDb, mock, err := sqlmock.New()
+	ds.NoError(err)
+	mock.ExpectQuery(`SELECT "id" FROM "items"`).
+		WithArgs().
+		WillReturnRows(sqlmock.NewRows([]string{"id"}).FromCSVString("10"))
+
+	db := New("mock", mDb)
+	var id int64
+	found, err := db.ScanVal(&id, `SELECT "id" FROM "items"`)
+	ds.NoError(err)
+	ds.Equal(int64(10), id)
+	ds.True(found)
+
+	found, err = db.ScanVal([]int64{}, `SELECT "id" FROM "items"`)
+	ds.False(found)
+	ds.EqualError(err, "goqu: type must be a pointer when scanning into val")
+	found, err = db.ScanVal(10, `SELECT "id" FROM "items"`)
+	ds.False(found)
+	ds.EqualError(err, "goqu: type must be a pointer when scanning into val")
+}
+
+func (ds *databaseSuite) TestExec() {
+	mDb, mock, err := sqlmock.New()
+	ds.NoError(err)
+	mock.ExpectExec(`UPDATE "items" SET "address"='111 Test Addr',"name"='Test1' WHERE \("name" IS NULL\)`).
+		WithArgs().
+		WillReturnResult(sqlmock.NewResult(0, 0))
+
+	mock.ExpectExec(`UPDATE "items" SET "address"='111 Test Addr',"name"='Test1' WHERE \("name" IS NULL\)`).
+		WithArgs().
+		WillReturnError(errors.New("mock error"))
+
+	db := New("mock", mDb)
+	_, err = db.Exec(`UPDATE "items" SET "address"='111 Test Addr',"name"='Test1' WHERE ("name" IS NULL)`)
+	ds.NoError(err)
+	_, err = db.Exec(`UPDATE "items" SET "address"='111 Test Addr',"name"='Test1' WHERE ("name" IS NULL)`)
+	ds.EqualError(err, "goqu: mock error")
+}
+
+func (ds *databaseSuite) TestQuery() {
+	mDb, mock, err := sqlmock.New()
+	ds.NoError(err)
+	mock.ExpectQuery(`SELECT \* FROM "items"`).
+		WithArgs().
+		WillReturnRows(sqlmock.NewRows([]string{"address", "name"}).
+			FromCSVString("111 Test Addr,Test1\n211 Test Addr,Test2"))
+
+	mock.ExpectQuery(`SELECT \* FROM "items"`).
+		WithArgs().
+		WillReturnError(errors.New("mock error"))
+
+	db := New("mock", mDb)
+	_, err = db.Query(`SELECT * FROM "items"`)
+	ds.NoError(err, "goqu - mock error")
+
+	_, err = db.Query(`SELECT * FROM "items"`)
+	ds.EqualError(err, "goqu: mock error")
+}
+
+func (ds *databaseSuite) TestQueryRow() {
+	mDb, mock, err := sqlmock.New()
+	ds.NoError(err)
+	mock.ExpectQuery(`SELECT \* FROM "items"`).
+		WithArgs().
+		WillReturnRows(sqlmock.NewRows([]string{"address", "name"}).
+			FromCSVString("111 Test Addr,Test1\n211 Test Addr,Test2"))
+
+	mock.ExpectQuery(`SELECT \* FROM "items"`).
+		WithArgs().
+		WillReturnError(errors.New("mock error"))
+
+	db := New("mock", mDb)
+	rows := db.QueryRow(`SELECT * FROM "items"`)
+	var address string
+	var name string
+	ds.NoError(rows.Scan(&address, &name))
+
+	rows = db.QueryRow(`SELECT * FROM "items"`)
+	ds.EqualError(rows.Scan(&address, &name), "goqu: mock error")
+}
+
+func (ds *databaseSuite) TestPrepare() {
+	mDb, mock, err := sqlmock.New()
+	ds.NoError(err)
+	mock.ExpectPrepare("SELECT \\* FROM test WHERE id = \\?")
+	db := New("mock", mDb)
+	stmt, err := db.Prepare("SELECT * FROM test WHERE id = ?")
+	ds.NoError(err)
+	ds.NotNil(stmt)
+}
+
+func (ds *databaseSuite) TestBegin() {
+	mDb, mock, err := sqlmock.New()
+	ds.NoError(err)
+	mock.ExpectBegin()
+	mock.ExpectBegin().WillReturnError(errors.New("transaction error"))
+	db := New("mock", mDb)
+	tx, err := db.Begin()
+	ds.NoError(err)
+	ds.Equal("mock", tx.Dialect())
+
+	_, err = db.Begin()
+	ds.EqualError(err, "goqu: transaction error")
+}
+
+func (ds *databaseSuite) TestBeginTx() {
+	ctx := context.Background()
+	mDb, mock, err := sqlmock.New()
+	ds.NoError(err)
+	mock.ExpectBegin()
+	mock.ExpectBegin().WillReturnError(errors.New("transaction error"))
+	db := New("mock", mDb)
+	tx, err := db.BeginTx(ctx, nil)
+	ds.NoError(err)
+	ds.Equal("mock", tx.Dialect())
+
+	_, err = db.BeginTx(ctx, nil)
+	ds.EqualError(err, "goqu: transaction error")
+}
+
+func (ds *databaseSuite) TestWithTx() {
+	mDb, mock, err := sqlmock.New()
+	ds.NoError(err)
+
+	db := newDatabase("mock", mDb)
+
+	cases := []struct {
+		expectf func(sqlmock.Sqlmock)
+		f       func(*TxDatabase) error
+		wantErr bool
+		errStr  string
+	}{
+		{
+			expectf: func(mock sqlmock.Sqlmock) {
+				mock.ExpectBegin()
+				mock.ExpectCommit()
+			},
+			f:       func(_ *TxDatabase) error { return nil },
+			wantErr: false,
+		},
+		{
+			expectf: func(mock sqlmock.Sqlmock) {
+				mock.ExpectBegin().WillReturnError(errors.New("transaction begin error"))
+			},
+			f:       func(_ *TxDatabase) error { return nil },
+			wantErr: true,
+			errStr:  "goqu: transaction begin error",
+		},
+		{
+			expectf: func(mock sqlmock.Sqlmock) {
+				mock.ExpectBegin()
+				mock.ExpectRollback()
+			},
+			f:       func(_ *TxDatabase) error { return errors.New("transaction error") },
+			wantErr: true,
+			errStr:  "goqu: transaction error",
+		},
+		{
+			expectf: func(mock sqlmock.Sqlmock) {
+				mock.ExpectBegin()
+				mock.ExpectRollback().WillReturnError(errors.New("transaction rollback error"))
+			},
+			f:       func(_ *TxDatabase) error { return errors.New("something wrong") },
+			wantErr: true,
+			errStr:  "goqu: transaction rollback error",
+		},
+	}
+	for _, c := range cases {
+		c.expectf(mock)
+		err := db.WithTx(c.f)
+		if c.wantErr {
+			ds.EqualError(err, c.errStr)
+		} else {
+			ds.NoError(err)
+		}
+	}
+}
+
+func (ds *databaseSuite) TestDataRace() {
+	mDb, mock, err := sqlmock.New()
+	ds.NoError(err)
+	db := newDatabase("mock", mDb)
+
+	const concurrency = 10
+
+	for i := 0; i < concurrency; i++ {
+		mock.ExpectQuery(`SELECT "address", "name" FROM "items"`).
+			WithArgs().
+			WillReturnRows(sqlmock.NewRows([]string{"address", "name"}).
+				FromCSVString("111 Test Addr,Test1\n211 Test Addr,Test2"))
+	}
+
+	wg := sync.WaitGroup{}
+	for i := 0; i < concurrency; i++ {
+		wg.Add(1)
+		go func() {
+			defer wg.Done()
+
+			sql := db.From("items").Limit(1)
+			var item testActionItem
+			found, err := sql.ScanStruct(&item)
+			ds.NoError(err)
+			ds.True(found)
+			ds.Equal(item.Address, "111 Test Addr")
+			ds.Equal(item.Name, "Test1")
+		}()
+	}
+
+	wg.Wait()
+}
+
+func TestDatabaseSuite(t *testing.T) {
+	suite.Run(t, new(databaseSuite))
+}
+
+type txdatabaseSuite struct {
+	suite.Suite
+}
+
+func (tds *txdatabaseSuite) TestLogger() {
+	mDb, mock, err := sqlmock.New()
+	tds.NoError(err)
+	mock.ExpectBegin()
+	mock.ExpectQuery(`SELECT \* FROM "items"`).
+		WithArgs().
+		WillReturnRows(sqlmock.NewRows([]string{"address", "name"}).
+			FromCSVString("111 Test Addr,Test1\n211 Test Addr,Test2"))
+
+	mock.ExpectExec(`SELECT \* FROM "items" WHERE "id" = ?`).
+		WithArgs(1).
+		WillReturnResult(sqlmock.NewResult(0, 0))
+	mock.ExpectCommit()
+
+	tx, err := newDatabase("db-mock", mDb).Begin()
+	tds.NoError(err)
+	logger := new(dbTestMockLogger)
+	tx.Logger(logger)
+	var items []testActionItem
+	tds.NoError(tx.ScanStructs(&items, `SELECT * FROM "items"`))
+	_, err = tx.Exec(`SELECT * FROM "items" WHERE "id" = ?`, 1)
+	tds.NoError(err)
+	tds.NoError(tx.Commit())
+	tds.Equal([]string{
+		"[goqu - transaction] QUERY [query:=`SELECT * FROM \"items\"`] ",
+		"[goqu - transaction] EXEC [query:=`SELECT * FROM \"items\" WHERE \"id\" = ?` args:=[1]] ",
+		"[goqu - transaction] COMMIT",
+	}, logger.Messages)
+}
+
+func (tds *txdatabaseSuite) TestLogger_FromDb() {
+	mDb, mock, err := sqlmock.New()
+	tds.NoError(err)
+	mock.ExpectBegin()
+	mock.ExpectQuery(`SELECT \* FROM "items"`).
+		WithArgs().
+		WillReturnRows(sqlmock.NewRows([]string{"address", "name"}).
+			FromCSVString("111 Test Addr,Test1\n211 Test Addr,Test2"))
+
+	mock.ExpectExec(`SELECT \* FROM "items" WHERE "id" = ?`).
+		WithArgs(1).
+		WillReturnResult(sqlmock.NewResult(0, 0))
+	mock.ExpectCommit()
+
+	db := New("db-mock", mDb)
+	logger := new(dbTestMockLogger)
+	db.Logger(logger)
+	tx, err := db.Begin()
+	tds.NoError(err)
+
+	var items []testActionItem
+	tds.NoError(tx.ScanStructs(&items, `SELECT * FROM "items"`))
+	_, err = tx.Exec(`SELECT * FROM "items" WHERE "id" = ?`, 1)
+	tds.NoError(err)
+	tds.NoError(tx.Commit())
+	tds.Equal([]string{
+		"[goqu - transaction] QUERY [query:=`SELECT * FROM \"items\"`] ",
+		"[goqu - transaction] EXEC [query:=`SELECT * FROM \"items\" WHERE \"id\" = ?` args:=[1]] ",
+		"[goqu - transaction] COMMIT",
+	}, logger.Messages)
+}
+
+func (tds *txdatabaseSuite) TestCommit() {
+	mDb, mock, err := sqlmock.New()
+	tds.NoError(err)
+	mock.ExpectBegin()
+	mock.ExpectCommit()
+	db := newDatabase("mock", mDb)
+	tx, err := db.Begin()
+	tds.NoError(err)
+	tds.NoError(tx.Commit())
+}
+
+func (tds *txdatabaseSuite) TestRollback() {
+	mDb, mock, err := sqlmock.New()
+	tds.NoError(err)
+	mock.ExpectBegin()
+	mock.ExpectRollback()
+	db := newDatabase("mock", mDb)
+	tx, err := db.Begin()
+	tds.NoError(err)
+	tds.NoError(tx.Rollback())
+}
+
+func (tds *txdatabaseSuite) TestFrom() {
+	mDb, mock, err := sqlmock.New()
+	tds.NoError(err)
+	mock.ExpectBegin()
+	mock.ExpectCommit()
+	db := newDatabase("mock", mDb)
+	tx, err := db.Begin()
+	tds.NoError(err)
+	tds.NotNil(From("test"))
+	tds.NoError(tx.Commit())
+}
+
+func (tds *txdatabaseSuite) TestScanStructs() {
+	mDb, mock, err := sqlmock.New()
+	tds.NoError(err)
+	mock.ExpectBegin()
+	mock.ExpectQuery(`SELECT \* FROM "items"`).
+		WithArgs().
+		WillReturnRows(sqlmock.NewRows([]string{"address", "name"}).
+			FromCSVString("111 Test Addr,Test1\n211 Test Addr,Test2"))
+
+	mock.ExpectQuery(`SELECT "test" FROM "items"`).
+		WithArgs().
+		WillReturnRows(sqlmock.NewRows([]string{"test"}).FromCSVString("test1\ntest2"))
+	mock.ExpectCommit()
+	db := newDatabase("mock", mDb)
+	tx, err := db.Begin()
+	tds.NoError(err)
+	var items []testActionItem
+	tds.NoError(tx.ScanStructs(&items, `SELECT * FROM "items"`))
+	tds.Len(items, 2)
+	tds.Equal("111 Test Addr", items[0].Address)
+	tds.Equal("Test1", items[0].Name)
+
+	tds.Equal("211 Test Addr", items[1].Address)
+	tds.Equal("Test2", items[1].Name)
+
+	items = items[0:0]
+	tds.EqualError(tx.ScanStructs(items, `SELECT * FROM "items"`),
+		"goqu: type must be a pointer to a slice when scanning into structs")
+	tds.EqualError(tx.ScanStructs(&testActionItem{}, `SELECT * FROM "items"`),
+		"goqu: type must be a pointer to a slice when scanning into structs")
+	tds.EqualError(tx.ScanStructs(&items, `SELECT "test" FROM "items"`),
+		`goqu: unable to find corresponding field to column "test" returned by query`)
+	tds.NoError(tx.Commit())
+}
+
+func (tds *txdatabaseSuite) TestScanStruct() {
+	mDb, mock, err := sqlmock.New()
+	tds.NoError(err)
+	mock.ExpectBegin()
+	mock.ExpectQuery(`SELECT \* FROM "items" LIMIT 1`).
+		WithArgs().
+		WillReturnRows(sqlmock.NewRows([]string{"address", "name"}).FromCSVString("111 Test Addr,Test1"))
+
+	mock.ExpectQuery(`SELECT "test" FROM "items" LIMIT 1`).
+		WithArgs().
+		WillReturnRows(sqlmock.NewRows([]string{"test"}).FromCSVString("test1\ntest2"))
+	mock.ExpectCommit()
+	db := newDatabase("mock", mDb)
+	tx, err := db.Begin()
+	tds.NoError(err)
+	var item testActionItem
+	found, err := tx.ScanStruct(&item, `SELECT * FROM "items" LIMIT 1`)
+	tds.NoError(err)
+	tds.True(found)
+	tds.Equal("111 Test Addr", item.Address)
+	tds.Equal("Test1", item.Name)
+
+	_, err = tx.ScanStruct(item, `SELECT * FROM "items" LIMIT 1`)
+	tds.EqualError(err, "goqu: type must be a pointer to a struct when scanning into a struct")
+	_, err = tx.ScanStruct([]testActionItem{}, `SELECT * FROM "items" LIMIT 1`)
+	tds.EqualError(err, "goqu: type must be a pointer to a struct when scanning into a struct")
+	_, err = tx.ScanStruct(&item, `SELECT "test" FROM "items" LIMIT 1`)
+	tds.EqualError(err, `goqu: unable to find corresponding field to column "test" returned by query`)
+	tds.NoError(tx.Commit())
+}
+
+func (tds *txdatabaseSuite) TestScanVals() {
+	mDb, mock, err := sqlmock.New()
+	tds.NoError(err)
+	mock.ExpectBegin()
+	mock.ExpectQuery(`SELECT "id" FROM "items"`).
+		WithArgs().
+		WillReturnRows(sqlmock.NewRows([]string{"id"}).FromCSVString("1\n2\n3\n4\n5"))
+	mock.ExpectCommit()
+	db := newDatabase("mock", mDb)
+	tx, err := db.Begin()
+	tds.NoError(err)
+	var ids []uint32
+	tds.NoError(tx.ScanVals(&ids, `SELECT "id" FROM "items"`))
+	tds.Len(ids, 5)
+
+	tds.EqualError(tx.ScanVals([]uint32{}, `SELECT "id" FROM "items"`),
+		"goqu: type must be a pointer to a slice when scanning into vals")
+	tds.EqualError(tx.ScanVals(testActionItem{}, `SELECT "id" FROM "items"`),
+		"goqu: type must be a pointer to a slice when scanning into vals")
+	tds.NoError(tx.Commit())
+}
+
+func (tds *txdatabaseSuite) TestScanVal() {
+	mDb, mock, err := sqlmock.New()
+	tds.NoError(err)
+	mock.ExpectBegin()
+	mock.ExpectQuery(`SELECT "id" FROM "items"`).
+		WithArgs().
+		WillReturnRows(sqlmock.NewRows([]string{"id"}).FromCSVString("10"))
+	mock.ExpectCommit()
+	db := newDatabase("mock", mDb)
+	tx, err := db.Begin()
+	tds.NoError(err)
+	var id int64
+	found, err := tx.ScanVal(&id, `SELECT "id" FROM "items"`)
+	tds.NoError(err)
+	tds.Equal(int64(10), id)
+	tds.True(found)
+
+	found, err = tx.ScanVal([]int64{}, `SELECT "id" FROM "items"`)
+	tds.False(found)
+	tds.EqualError(err, "goqu: type must be a pointer when scanning into val")
+	found, err = tx.ScanVal(10, `SELECT "id" FROM "items"`)
+	tds.False(found)
+	tds.EqualError(err, "goqu: type must be a pointer when scanning into val")
+	tds.NoError(tx.Commit())
+}
+
+func (tds *txdatabaseSuite) TestExec() {
+	mDb, mock, err := sqlmock.New()
+	tds.NoError(err)
+	mock.ExpectBegin()
+	mock.ExpectExec(`UPDATE "items" SET "address"='111 Test Addr',"name"='Test1' WHERE \("name" IS NULL\)`).
+		WithArgs().
+		WillReturnResult(sqlmock.NewResult(0, 0))
+
+	mock.ExpectExec(`UPDATE "items" SET "address"='111 Test Addr',"name"='Test1' WHERE \("name" IS NULL\)`).
+		WithArgs().
+		WillReturnError(errors.New("mock error"))
+	mock.ExpectCommit()
+	db := newDatabase("mock", mDb)
+	tx, err := db.Begin()
+	tds.NoError(err)
+	_, err = tx.Exec(`UPDATE "items" SET "address"='111 Test Addr',"name"='Test1' WHERE ("name" IS NULL)`)
+	tds.NoError(err)
+	_, err = tx.Exec(`UPDATE "items" SET "address"='111 Test Addr',"name"='Test1' WHERE ("name" IS NULL)`)
+	tds.EqualError(err, "goqu: mock error")
+	tds.NoError(tx.Commit())
+}
+
+func (tds *txdatabaseSuite) TestQuery() {
+	mDb, mock, err := sqlmock.New()
+	tds.NoError(err)
+	mock.ExpectBegin()
+	mock.ExpectQuery(`SELECT \* FROM "items"`).
+		WithArgs().
+		WillReturnRows(sqlmock.NewRows([]string{"address", "name"}).
+			FromCSVString("111 Test Addr,Test1\n211 Test Addr,Test2"))
+
+	mock.ExpectQuery(`SELECT \* FROM "items"`).
+		WithArgs().
+		WillReturnError(errors.New("mock error"))
+	mock.ExpectCommit()
+	db := newDatabase("mock", mDb)
+	tx, err := db.Begin()
+	tds.NoError(err)
+	_, err = tx.Query(`SELECT * FROM "items"`)
+	tds.NoError(err, "goqu - mock error")
+
+	_, err = tx.Query(`SELECT * FROM "items"`)
+	tds.EqualError(err, "goqu: mock error")
+	tds.NoError(tx.Commit())
+}
+
+func (tds *txdatabaseSuite) TestQueryRow() {
+	mDb, mock, err := sqlmock.New()
+	tds.NoError(err)
+	mock.ExpectBegin()
+	mock.ExpectQuery(`SELECT \* FROM "items"`).
+		WithArgs().
+		WillReturnRows(sqlmock.NewRows([]string{"address", "name"}).
+			FromCSVString("111 Test Addr,Test1\n211 Test Addr,Test2"))
+
+	mock.ExpectQuery(`SELECT \* FROM "items"`).
+		WithArgs().
+		WillReturnError(errors.New("mock error"))
+	mock.ExpectCommit()
+	db := newDatabase("mock", mDb)
+	tx, err := db.Begin()
+	tds.NoError(err)
+	rows := tx.QueryRow(`SELECT * FROM "items"`)
+	var address string
+	var name string
+	tds.NoError(rows.Scan(&address, &name))
+
+	rows = tx.QueryRow(`SELECT * FROM "items"`)
+	tds.EqualError(rows.Scan(&address, &name), "goqu: mock error")
+	tds.NoError(tx.Commit())
+}
+
+func (tds *txdatabaseSuite) TestWrap() {
+	mDb, mock, err := sqlmock.New()
+	tds.NoError(err)
+	mock.ExpectBegin()
+	mock.ExpectCommit()
+	mock.ExpectBegin()
+	mock.ExpectRollback()
+	db := newDatabase("mock", mDb)
+	tx, err := db.Begin()
+	tds.NoError(err)
+	tds.NoError(tx.Wrap(func() error {
+		return nil
+	}))
+	tx, err = db.Begin()
+	tds.NoError(err)
+	tds.EqualError(tx.Wrap(func() error {
+		return errors.New("tx error")
+	}), "goqu: tx error")
+}
+
+func (tds *txdatabaseSuite) TestDataRace() {
+	mDb, mock, err := sqlmock.New()
+	tds.NoError(err)
+	mock.ExpectBegin()
+	db := newDatabase("mock", mDb)
+	tx, err := db.Begin()
+	tds.NoError(err)
+
+	const concurrency = 10
+
+	for i := 0; i < concurrency; i++ {
+		mock.ExpectQuery(`SELECT "address", "name" FROM "items"`).
+			WithArgs().
+			WillReturnRows(sqlmock.NewRows([]string{"address", "name"}).
+				FromCSVString("111 Test Addr,Test1\n211 Test Addr,Test2"))
+	}
+
+	wg := sync.WaitGroup{}
+	for i := 0; i < concurrency; i++ {
+		wg.Add(1)
+		go func() {
+			defer wg.Done()
+
+			sql := tx.From("items").Limit(1)
+			var item testActionItem
+			found, err := sql.ScanStruct(&item)
+			tds.NoError(err)
+			tds.True(found)
+			tds.Equal(item.Address, "111 Test Addr")
+			tds.Equal(item.Name, "Test1")
+		}()
+	}
+
+	wg.Wait()
+	mock.ExpectCommit()
+	tds.NoError(tx.Commit())
+}
+
+func TestTxDatabaseSuite(t *testing.T) {
+	suite.Run(t, new(txdatabaseSuite))
+}

+ 242 - 0
src/github.com/doug-martin/goqu/delete_dataset.go

@@ -0,0 +1,242 @@
+package goqu
+
+import (
+	"github.com/doug-martin/goqu/v9/exec"
+	"github.com/doug-martin/goqu/v9/exp"
+	"github.com/doug-martin/goqu/v9/internal/errors"
+	"github.com/doug-martin/goqu/v9/internal/sb"
+)
+
+var errBadFromArgument = errors.New("unsupported DeleteDataset#From argument, a string or identifier expression is required")
+
+type DeleteDataset struct {
+	dialect      SQLDialect
+	clauses      exp.DeleteClauses
+	isPrepared   bool
+	queryFactory exec.QueryFactory
+	err          error
+}
+
+// used internally by database to create a database with a specific adapter
+func newDeleteDataset(d string, queryFactory exec.QueryFactory) *DeleteDataset {
+	return &DeleteDataset{
+		clauses:      exp.NewDeleteClauses(),
+		dialect:      GetDialect(d),
+		queryFactory: queryFactory,
+	}
+}
+
+func Delete(table interface{}) *DeleteDataset {
+	return newDeleteDataset("default", nil).From(table)
+}
+
+func (dd *DeleteDataset) Expression() exp.Expression {
+	return dd
+}
+
+// Clones the dataset
+func (dd *DeleteDataset) Clone() exp.Expression {
+	return dd.copy(dd.clauses)
+}
+
+// Set the parameter interpolation behavior. See examples
+//
+// prepared: If true the dataset WILL NOT interpolate the parameters.
+func (dd *DeleteDataset) Prepared(prepared bool) *DeleteDataset {
+	ret := dd.copy(dd.clauses)
+	ret.isPrepared = prepared
+	return ret
+}
+
+// Returns true if Prepared(true) has been called on this dataset
+func (dd *DeleteDataset) IsPrepared() bool {
+	return dd.isPrepared
+}
+
+// Sets the adapter used to serialize values and create the SQL statement
+func (dd *DeleteDataset) WithDialect(dl string) *DeleteDataset {
+	ds := dd.copy(dd.GetClauses())
+	ds.dialect = GetDialect(dl)
+	return ds
+}
+
+// Returns the current SQLDialect on the dataset
+func (dd *DeleteDataset) Dialect() SQLDialect {
+	return dd.dialect
+}
+
+// Set the dialect for this dataset.
+func (dd *DeleteDataset) SetDialect(dialect SQLDialect) *DeleteDataset {
+	cd := dd.copy(dd.GetClauses())
+	cd.dialect = dialect
+	return cd
+}
+
+// Returns the current clauses on the dataset.
+func (dd *DeleteDataset) GetClauses() exp.DeleteClauses {
+	return dd.clauses
+}
+
+// used interally to copy the dataset
+func (dd *DeleteDataset) copy(clauses exp.DeleteClauses) *DeleteDataset {
+	return &DeleteDataset{
+		dialect:      dd.dialect,
+		clauses:      clauses,
+		isPrepared:   dd.isPrepared,
+		queryFactory: dd.queryFactory,
+		err:          dd.err,
+	}
+}
+
+// Creates a WITH clause for a common table expression (CTE).
+//
+// The name will be available to SELECT from in the associated query; and can optionally
+// contain a list of column names "name(col1, col2, col3)".
+//
+// The name will refer to the results of the specified subquery.
+func (dd *DeleteDataset) With(name string, subquery exp.Expression) *DeleteDataset {
+	return dd.copy(dd.clauses.CommonTablesAppend(exp.NewCommonTableExpression(false, name, subquery)))
+}
+
+// Creates a WITH RECURSIVE clause for a common table expression (CTE)
+//
+// The name will be available to SELECT from in the associated query; and must
+// contain a list of column names "name(col1, col2, col3)" for a recursive clause.
+//
+// The name will refer to the results of the specified subquery. The subquery for
+// a recursive query will always end with a UNION or UNION ALL with a clause that
+// refers to the CTE by name.
+func (dd *DeleteDataset) WithRecursive(name string, subquery exp.Expression) *DeleteDataset {
+	return dd.copy(dd.clauses.CommonTablesAppend(exp.NewCommonTableExpression(true, name, subquery)))
+}
+
+// Adds a FROM clause. This return a new dataset with the original sources replaced. See examples.
+// You can pass in the following.
+//   string: Will automatically be turned into an identifier
+//   Dataset: Will be added as a sub select. If the Dataset is not aliased it will automatically be aliased
+//   LiteralExpression: (See Literal) Will use the literal SQL
+func (dd *DeleteDataset) From(table interface{}) *DeleteDataset {
+	switch t := table.(type) {
+	case exp.IdentifierExpression:
+		return dd.copy(dd.clauses.SetFrom(t))
+	case string:
+		return dd.copy(dd.clauses.SetFrom(exp.ParseIdentifier(t)))
+	default:
+		panic(errBadFromArgument)
+	}
+}
+
+// Adds a WHERE clause. See examples.
+func (dd *DeleteDataset) Where(expressions ...exp.Expression) *DeleteDataset {
+	return dd.copy(dd.clauses.WhereAppend(expressions...))
+}
+
+// Removes the WHERE clause. See examples.
+func (dd *DeleteDataset) ClearWhere() *DeleteDataset {
+	return dd.copy(dd.clauses.ClearWhere())
+}
+
+// Adds a ORDER clause. If the ORDER is currently set it replaces it. See examples.
+func (dd *DeleteDataset) Order(order ...exp.OrderedExpression) *DeleteDataset {
+	return dd.copy(dd.clauses.SetOrder(order...))
+}
+
+// Adds a more columns to the current ORDER BY clause. If no order has be previously specified it is the same as
+// calling Order. See examples.
+func (dd *DeleteDataset) OrderAppend(order ...exp.OrderedExpression) *DeleteDataset {
+	return dd.copy(dd.clauses.OrderAppend(order...))
+}
+
+// Adds a more columns to the beginning of the current ORDER BY clause. If no order has be previously specified it is the same as
+// calling Order. See examples.
+func (dd *DeleteDataset) OrderPrepend(order ...exp.OrderedExpression) *DeleteDataset {
+	return dd.copy(dd.clauses.OrderPrepend(order...))
+}
+
+// Removes the ORDER BY clause. See examples.
+func (dd *DeleteDataset) ClearOrder() *DeleteDataset {
+	return dd.copy(dd.clauses.ClearOrder())
+}
+
+// Adds a LIMIT clause. If the LIMIT is currently set it replaces it. See examples.
+func (dd *DeleteDataset) Limit(limit uint) *DeleteDataset {
+	if limit > 0 {
+		return dd.copy(dd.clauses.SetLimit(limit))
+	}
+	return dd.copy(dd.clauses.ClearLimit())
+}
+
+// Adds a LIMIT ALL clause. If the LIMIT is currently set it replaces it. See examples.
+func (dd *DeleteDataset) LimitAll() *DeleteDataset {
+	return dd.copy(dd.clauses.SetLimit(L("ALL")))
+}
+
+// Removes the LIMIT clause.
+func (dd *DeleteDataset) ClearLimit() *DeleteDataset {
+	return dd.copy(dd.clauses.ClearLimit())
+}
+
+// Adds a RETURNING clause to the dataset if the adapter supports it.
+func (dd *DeleteDataset) Returning(returning ...interface{}) *DeleteDataset {
+	return dd.copy(dd.clauses.SetReturning(exp.NewColumnListExpression(returning...)))
+}
+
+// Get any error that has been set or nil if no error has been set.
+func (dd *DeleteDataset) Error() error {
+	return dd.err
+}
+
+// Set an error on the dataset if one has not already been set. This error will be returned by a future call to Error
+// or as part of ToSQL. This can be used by end users to record errors while building up queries without having to
+// track those separately.
+func (dd *DeleteDataset) SetError(err error) *DeleteDataset {
+	if dd.err == nil {
+		dd.err = err
+	}
+
+	return dd
+}
+
+// Generates a DELETE sql statement, if Prepared has been called with true then the parameters will not be interpolated.
+// See examples.
+//
+// Errors:
+//  * There is an error generating the SQL
+func (dd *DeleteDataset) ToSQL() (sql string, params []interface{}, err error) {
+	return dd.deleteSQLBuilder().ToSQL()
+}
+
+// Appends this Dataset's DELETE statement to the SQLBuilder
+// This is used internally when using deletes in CTEs
+func (dd *DeleteDataset) AppendSQL(b sb.SQLBuilder) {
+	if dd.err != nil {
+		b.SetError(dd.err)
+		return
+	}
+	dd.dialect.ToDeleteSQL(b, dd.GetClauses())
+}
+
+func (dd *DeleteDataset) GetAs() exp.IdentifierExpression {
+	return nil
+}
+
+func (dd *DeleteDataset) ReturnsColumns() bool {
+	return dd.clauses.HasReturning()
+}
+
+// Creates an QueryExecutor to execute the query.
+//    db.Delete("test").Exec()
+//
+// See Dataset#ToUpdateSQL for arguments
+func (dd *DeleteDataset) Executor() exec.QueryExecutor {
+	return dd.queryFactory.FromSQLBuilder(dd.deleteSQLBuilder())
+}
+
+func (dd *DeleteDataset) deleteSQLBuilder() sb.SQLBuilder {
+	buf := sb.NewSQLBuilder(dd.isPrepared)
+	if dd.err != nil {
+		return buf.SetError(dd.err)
+	}
+	dd.dialect.ToDeleteSQL(buf, dd.clauses)
+	return buf
+}

+ 312 - 0
src/github.com/doug-martin/goqu/delete_dataset_example_test.go

@@ -0,0 +1,312 @@
+package goqu_test
+
+import (
+	"fmt"
+
+	"github.com/doug-martin/goqu/v9"
+	_ "github.com/doug-martin/goqu/v9/dialect/mysql"
+)
+
+func ExampleDelete() {
+	ds := goqu.Delete("items")
+
+	sql, args, _ := ds.ToSQL()
+	fmt.Println(sql, args)
+
+	// Output:
+	// DELETE FROM "items" []
+}
+
+func ExampleDeleteDataset_Executor() {
+	db := getDb()
+
+	de := db.Delete("goqu_user").
+		Where(goqu.Ex{"first_name": "Bob"}).
+		Executor()
+	if r, err := de.Exec(); err != nil {
+		fmt.Println(err.Error())
+	} else {
+		c, _ := r.RowsAffected()
+		fmt.Printf("Deleted %d users", c)
+	}
+
+	// Output:
+	// Deleted 1 users
+}
+
+func ExampleDeleteDataset_Executor_returning() {
+	db := getDb()
+
+	de := db.Delete("goqu_user").
+		Where(goqu.C("last_name").Eq("Yukon")).
+		Returning(goqu.C("id")).
+		Executor()
+
+	var ids []int64
+	if err := de.ScanVals(&ids); err != nil {
+		fmt.Println(err.Error())
+	} else {
+		fmt.Printf("Deleted users [ids:=%+v]", ids)
+	}
+
+	// Output:
+	// Deleted users [ids:=[1 2 3]]
+}
+
+func ExampleDeleteDataset_With() {
+	sql, _, _ := goqu.Delete("test").
+		With("check_vals(val)", goqu.From().Select(goqu.L("123"))).
+		Where(goqu.C("val").Eq(goqu.From("check_vals").Select("val"))).
+		ToSQL()
+	fmt.Println(sql)
+
+	// Output:
+	// WITH check_vals(val) AS (SELECT 123) DELETE FROM "test" WHERE ("val" IN (SELECT "val" FROM "check_vals"))
+}
+
+func ExampleDeleteDataset_WithRecursive() {
+	sql, _, _ := goqu.Delete("nums").
+		WithRecursive("nums(x)",
+			goqu.From().Select(goqu.L("1")).
+				UnionAll(goqu.From("nums").
+					Select(goqu.L("x+1")).Where(goqu.C("x").Lt(5)))).
+		ToSQL()
+	fmt.Println(sql)
+	// Output:
+	// WITH RECURSIVE nums(x) AS (SELECT 1 UNION ALL (SELECT x+1 FROM "nums" WHERE ("x" < 5))) DELETE FROM "nums"
+}
+
+func ExampleDeleteDataset_Where() {
+	// By default everything is anded together
+	sql, _, _ := goqu.Delete("test").Where(goqu.Ex{
+		"a": goqu.Op{"gt": 10},
+		"b": goqu.Op{"lt": 10},
+		"c": nil,
+		"d": []string{"a", "b", "c"},
+	}).ToSQL()
+	fmt.Println(sql)
+	// You can use ExOr to get ORed expressions together
+	sql, _, _ = goqu.Delete("test").Where(goqu.ExOr{
+		"a": goqu.Op{"gt": 10},
+		"b": goqu.Op{"lt": 10},
+		"c": nil,
+		"d": []string{"a", "b", "c"},
+	}).ToSQL()
+	fmt.Println(sql)
+	// You can use Or with Ex to Or multiple Ex maps together
+	sql, _, _ = goqu.Delete("test").Where(
+		goqu.Or(
+			goqu.Ex{
+				"a": goqu.Op{"gt": 10},
+				"b": goqu.Op{"lt": 10},
+			},
+			goqu.Ex{
+				"c": nil,
+				"d": []string{"a", "b", "c"},
+			},
+		),
+	).ToSQL()
+	fmt.Println(sql)
+	// By default everything is anded together
+	sql, _, _ = goqu.Delete("test").Where(
+		goqu.C("a").Gt(10),
+		goqu.C("b").Lt(10),
+		goqu.C("c").IsNull(),
+		goqu.C("d").In("a", "b", "c"),
+	).ToSQL()
+	fmt.Println(sql)
+	// You can use a combination of Ors and Ands
+	sql, _, _ = goqu.Delete("test").Where(
+		goqu.Or(
+			goqu.C("a").Gt(10),
+			goqu.And(
+				goqu.C("b").Lt(10),
+				goqu.C("c").IsNull(),
+			),
+		),
+	).ToSQL()
+	fmt.Println(sql)
+	// Output:
+	// DELETE FROM "test" WHERE (("a" > 10) AND ("b" < 10) AND ("c" IS NULL) AND ("d" IN ('a', 'b', 'c')))
+	// DELETE FROM "test" WHERE (("a" > 10) OR ("b" < 10) OR ("c" IS NULL) OR ("d" IN ('a', 'b', 'c')))
+	// DELETE FROM "test" WHERE ((("a" > 10) AND ("b" < 10)) OR (("c" IS NULL) AND ("d" IN ('a', 'b', 'c'))))
+	// DELETE FROM "test" WHERE (("a" > 10) AND ("b" < 10) AND ("c" IS NULL) AND ("d" IN ('a', 'b', 'c')))
+	// DELETE FROM "test" WHERE (("a" > 10) OR (("b" < 10) AND ("c" IS NULL)))
+}
+
+func ExampleDeleteDataset_Where_prepared() {
+	// By default everything is anded together
+	sql, args, _ := goqu.Delete("test").Prepared(true).Where(goqu.Ex{
+		"a": goqu.Op{"gt": 10},
+		"b": goqu.Op{"lt": 10},
+		"c": nil,
+		"d": []string{"a", "b", "c"},
+	}).ToSQL()
+	fmt.Println(sql, args)
+	// You can use ExOr to get ORed expressions together
+	sql, args, _ = goqu.Delete("test").Prepared(true).Where(goqu.ExOr{
+		"a": goqu.Op{"gt": 10},
+		"b": goqu.Op{"lt": 10},
+		"c": nil,
+		"d": []string{"a", "b", "c"},
+	}).ToSQL()
+	fmt.Println(sql, args)
+	// You can use Or with Ex to Or multiple Ex maps together
+	sql, args, _ = goqu.Delete("test").Prepared(true).Where(
+		goqu.Or(
+			goqu.Ex{
+				"a": goqu.Op{"gt": 10},
+				"b": goqu.Op{"lt": 10},
+			},
+			goqu.Ex{
+				"c": nil,
+				"d": []string{"a", "b", "c"},
+			},
+		),
+	).ToSQL()
+	fmt.Println(sql, args)
+	// By default everything is anded together
+	sql, args, _ = goqu.Delete("test").Prepared(true).Where(
+		goqu.C("a").Gt(10),
+		goqu.C("b").Lt(10),
+		goqu.C("c").IsNull(),
+		goqu.C("d").In("a", "b", "c"),
+	).ToSQL()
+	fmt.Println(sql, args)
+	// You can use a combination of Ors and Ands
+	sql, args, _ = goqu.Delete("test").Prepared(true).Where(
+		goqu.Or(
+			goqu.C("a").Gt(10),
+			goqu.And(
+				goqu.C("b").Lt(10),
+				goqu.C("c").IsNull(),
+			),
+		),
+	).ToSQL()
+	fmt.Println(sql, args)
+	// Output:
+	// DELETE FROM "test" WHERE (("a" > ?) AND ("b" < ?) AND ("c" IS NULL) AND ("d" IN (?, ?, ?))) [10 10 a b c]
+	// DELETE FROM "test" WHERE (("a" > ?) OR ("b" < ?) OR ("c" IS NULL) OR ("d" IN (?, ?, ?))) [10 10 a b c]
+	// DELETE FROM "test" WHERE ((("a" > ?) AND ("b" < ?)) OR (("c" IS NULL) AND ("d" IN (?, ?, ?)))) [10 10 a b c]
+	// DELETE FROM "test" WHERE (("a" > ?) AND ("b" < ?) AND ("c" IS NULL) AND ("d" IN (?, ?, ?))) [10 10 a b c]
+	// DELETE FROM "test" WHERE (("a" > ?) OR (("b" < ?) AND ("c" IS NULL))) [10 10]
+}
+
+func ExampleDeleteDataset_ClearWhere() {
+	ds := goqu.Delete("test").Where(
+		goqu.Or(
+			goqu.C("a").Gt(10),
+			goqu.And(
+				goqu.C("b").Lt(10),
+				goqu.C("c").IsNull(),
+			),
+		),
+	)
+	sql, _, _ := ds.ClearWhere().ToSQL()
+	fmt.Println(sql)
+	// Output:
+	// DELETE FROM "test"
+}
+
+func ExampleDeleteDataset_Limit() {
+	ds := goqu.Dialect("mysql").Delete("test").Limit(10)
+	sql, _, _ := ds.ToSQL()
+	fmt.Println(sql)
+	// Output:
+	// DELETE FROM `test` LIMIT 10
+}
+
+func ExampleDeleteDataset_LimitAll() {
+	// Using mysql dialect because it supports limit on delete
+	ds := goqu.Dialect("mysql").Delete("test").LimitAll()
+	sql, _, _ := ds.ToSQL()
+	fmt.Println(sql)
+	// Output:
+	// DELETE FROM `test` LIMIT ALL
+}
+
+func ExampleDeleteDataset_ClearLimit() {
+	// Using mysql dialect because it supports limit on delete
+	ds := goqu.Dialect("mysql").Delete("test").Limit(10)
+	sql, _, _ := ds.ClearLimit().ToSQL()
+	fmt.Println(sql)
+	// Output:
+	// DELETE FROM `test`
+}
+
+func ExampleDeleteDataset_Order() {
+	// use mysql dialect because it supports order by on deletes
+	ds := goqu.Dialect("mysql").Delete("test").Order(goqu.C("a").Asc())
+	sql, _, _ := ds.ToSQL()
+	fmt.Println(sql)
+	// Output:
+	// DELETE FROM `test` ORDER BY `a` ASC
+}
+
+func ExampleDeleteDataset_OrderAppend() {
+	// use mysql dialect because it supports order by on deletes
+	ds := goqu.Dialect("mysql").Delete("test").Order(goqu.C("a").Asc())
+	sql, _, _ := ds.OrderAppend(goqu.C("b").Desc().NullsLast()).ToSQL()
+	fmt.Println(sql)
+	// Output:
+	// DELETE FROM `test` ORDER BY `a` ASC, `b` DESC NULLS LAST
+}
+
+func ExampleDeleteDataset_OrderPrepend() {
+	// use mysql dialect because it supports order by on deletes
+	ds := goqu.Dialect("mysql").Delete("test").Order(goqu.C("a").Asc())
+	sql, _, _ := ds.OrderPrepend(goqu.C("b").Desc().NullsLast()).ToSQL()
+	fmt.Println(sql)
+	// Output:
+	// DELETE FROM `test` ORDER BY `b` DESC NULLS LAST, `a` ASC
+}
+
+func ExampleDeleteDataset_ClearOrder() {
+	ds := goqu.Delete("test").Order(goqu.C("a").Asc())
+	sql, _, _ := ds.ClearOrder().ToSQL()
+	fmt.Println(sql)
+	// Output:
+	// DELETE FROM "test"
+}
+
+func ExampleDeleteDataset_ToSQL() {
+	sql, args, _ := goqu.Delete("items").ToSQL()
+	fmt.Println(sql, args)
+
+	sql, args, _ = goqu.Delete("items").
+		Where(goqu.Ex{"id": goqu.Op{"gt": 10}}).
+		ToSQL()
+	fmt.Println(sql, args)
+
+	// Output:
+	// DELETE FROM "items" []
+	// DELETE FROM "items" WHERE ("id" > 10) []
+}
+
+func ExampleDeleteDataset_Prepared() {
+	sql, args, _ := goqu.Delete("items").Prepared(true).ToSQL()
+	fmt.Println(sql, args)
+
+	sql, args, _ = goqu.Delete("items").
+		Prepared(true).
+		Where(goqu.Ex{"id": goqu.Op{"gt": 10}}).
+		ToSQL()
+	fmt.Println(sql, args)
+
+	// Output:
+	// DELETE FROM "items" []
+	// DELETE FROM "items" WHERE ("id" > ?) [10]
+}
+
+func ExampleDeleteDataset_Returning() {
+	ds := goqu.Delete("items")
+	sql, args, _ := ds.Returning("id").ToSQL()
+	fmt.Println(sql, args)
+
+	sql, args, _ = ds.Returning("id").Where(goqu.C("id").IsNotNull()).ToSQL()
+	fmt.Println(sql, args)
+
+	// Output:
+	// DELETE FROM "items" RETURNING "id" []
+	// DELETE FROM "items" WHERE ("id" IS NOT NULL) RETURNING "id" []
+}

+ 497 - 0
src/github.com/doug-martin/goqu/delete_dataset_test.go

@@ -0,0 +1,497 @@
+package goqu
+
+import (
+	"testing"
+
+	"github.com/DATA-DOG/go-sqlmock"
+	"github.com/doug-martin/goqu/v9/exec"
+	"github.com/doug-martin/goqu/v9/exp"
+	"github.com/doug-martin/goqu/v9/internal/errors"
+	"github.com/doug-martin/goqu/v9/internal/sb"
+	"github.com/doug-martin/goqu/v9/mocks"
+	"github.com/stretchr/testify/mock"
+	"github.com/stretchr/testify/suite"
+)
+
+type (
+	deleteTestCase struct {
+		ds      *DeleteDataset
+		clauses exp.DeleteClauses
+	}
+	deleteDatasetSuite struct {
+		suite.Suite
+	}
+)
+
+func (dds *deleteDatasetSuite) assertCases(cases ...deleteTestCase) {
+	for _, s := range cases {
+		dds.Equal(s.clauses, s.ds.GetClauses())
+	}
+}
+
+func (dds *deleteDatasetSuite) SetupSuite() {
+	noReturn := DefaultDialectOptions()
+	noReturn.SupportsReturn = false
+	RegisterDialect("no-return", noReturn)
+
+	limitOnDelete := DefaultDialectOptions()
+	limitOnDelete.SupportsLimitOnDelete = true
+	RegisterDialect("limit-on-delete", limitOnDelete)
+
+	orderOnDelete := DefaultDialectOptions()
+	orderOnDelete.SupportsOrderByOnDelete = true
+	RegisterDialect("order-on-delete", orderOnDelete)
+}
+
+func (dds *deleteDatasetSuite) TearDownSuite() {
+	DeregisterDialect("no-return")
+	DeregisterDialect("limit-on-delete")
+	DeregisterDialect("order-on-delete")
+}
+
+func (dds *deleteDatasetSuite) TestDelete() {
+	ds := Delete("test")
+	dds.IsType(&DeleteDataset{}, ds)
+	dds.Implements((*exp.Expression)(nil), ds)
+	dds.Implements((*exp.AppendableExpression)(nil), ds)
+}
+
+func (dds *deleteDatasetSuite) TestClone() {
+	ds := Delete("test")
+	dds.Equal(ds.Clone(), ds)
+}
+
+func (dds *deleteDatasetSuite) TestExpression() {
+	ds := Delete("test")
+	dds.Equal(ds.Expression(), ds)
+}
+
+func (dds *deleteDatasetSuite) TestDialect() {
+	ds := Delete("test")
+	dds.NotNil(ds.Dialect())
+}
+
+func (dds *deleteDatasetSuite) TestWithDialect() {
+	ds := Delete("test")
+	md := new(mocks.SQLDialect)
+	ds = ds.SetDialect(md)
+
+	dialect := GetDialect("default")
+	dialectDs := ds.WithDialect("default")
+	dds.Equal(md, ds.Dialect())
+	dds.Equal(dialect, dialectDs.Dialect())
+}
+
+func (dds *deleteDatasetSuite) TestPrepared() {
+	ds := Delete("test")
+	preparedDs := ds.Prepared(true)
+	dds.True(preparedDs.IsPrepared())
+	dds.False(ds.IsPrepared())
+	// should apply the prepared to any datasets created from the root
+	dds.True(preparedDs.Where(Ex{"a": 1}).IsPrepared())
+}
+
+func (dds *deleteDatasetSuite) TestGetClauses() {
+	ds := Delete("test")
+	ce := exp.NewDeleteClauses().SetFrom(I("test"))
+	dds.Equal(ce, ds.GetClauses())
+}
+
+func (dds *deleteDatasetSuite) TestWith() {
+	from := From("cte")
+	bd := Delete("items")
+	dds.assertCases(
+		deleteTestCase{
+			ds: bd.With("test-cte", from),
+			clauses: exp.NewDeleteClauses().SetFrom(C("items")).
+				CommonTablesAppend(exp.NewCommonTableExpression(false, "test-cte", from)),
+		},
+		deleteTestCase{
+			ds:      bd,
+			clauses: exp.NewDeleteClauses().SetFrom(C("items")),
+		},
+	)
+}
+
+func (dds *deleteDatasetSuite) TestWithRecursive() {
+	from := From("cte")
+	bd := Delete("items")
+	dds.assertCases(
+		deleteTestCase{
+			ds: bd.WithRecursive("test-cte", from),
+			clauses: exp.NewDeleteClauses().SetFrom(C("items")).
+				CommonTablesAppend(exp.NewCommonTableExpression(true, "test-cte", from)),
+		},
+		deleteTestCase{
+			ds:      bd,
+			clauses: exp.NewDeleteClauses().SetFrom(C("items")),
+		},
+	)
+}
+
+func (dds *deleteDatasetSuite) TestFrom_withIdentifier() {
+	bd := Delete("items")
+	dds.assertCases(
+		deleteTestCase{
+			ds:      bd.From("items2"),
+			clauses: exp.NewDeleteClauses().SetFrom(C("items2")),
+		},
+		deleteTestCase{
+			ds:      bd.From(C("items2")),
+			clauses: exp.NewDeleteClauses().SetFrom(C("items2")),
+		},
+		deleteTestCase{
+			ds:      bd.From(T("items2")),
+			clauses: exp.NewDeleteClauses().SetFrom(T("items2")),
+		},
+		deleteTestCase{
+			ds:      bd.From("schema.table"),
+			clauses: exp.NewDeleteClauses().SetFrom(I("schema.table")),
+		},
+		deleteTestCase{
+			ds:      bd,
+			clauses: exp.NewDeleteClauses().SetFrom(C("items")),
+		},
+	)
+
+	dds.PanicsWithValue(errBadFromArgument, func() {
+		Delete("test").From(true)
+	})
+}
+
+func (dds *deleteDatasetSuite) TestWhere() {
+	bd := Delete("items")
+	dds.assertCases(
+		deleteTestCase{
+			ds: bd.Where(Ex{"a": 1}),
+			clauses: exp.NewDeleteClauses().
+				SetFrom(C("items")).
+				WhereAppend(Ex{"a": 1}),
+		},
+		deleteTestCase{
+			ds: bd.Where(Ex{"a": 1}).Where(C("b").Eq("c")),
+			clauses: exp.NewDeleteClauses().
+				SetFrom(C("items")).
+				WhereAppend(Ex{"a": 1}).
+				WhereAppend(C("b").Eq("c")),
+		},
+		deleteTestCase{
+			ds:      bd,
+			clauses: exp.NewDeleteClauses().SetFrom(C("items")),
+		},
+	)
+}
+
+func (dds *deleteDatasetSuite) TestClearWhere() {
+	bd := Delete("items").Where(Ex{"a": 1})
+	dds.assertCases(
+		deleteTestCase{
+			ds: bd.ClearWhere(),
+			clauses: exp.NewDeleteClauses().
+				SetFrom(C("items")),
+		},
+		deleteTestCase{
+			ds: bd,
+			clauses: exp.NewDeleteClauses().
+				SetFrom(C("items")).
+				WhereAppend(Ex{"a": 1}),
+		},
+	)
+}
+
+func (dds *deleteDatasetSuite) TestOrder() {
+	bd := Delete("items")
+	dds.assertCases(
+		deleteTestCase{
+			ds: bd.Order(C("a").Asc()),
+			clauses: exp.NewDeleteClauses().
+				SetFrom(C("items")).
+				SetOrder(C("a").Asc()),
+		},
+		deleteTestCase{
+			ds: bd.Order(C("a").Asc()).Order(C("b").Desc()),
+			clauses: exp.NewDeleteClauses().
+				SetFrom(C("items")).
+				SetOrder(C("b").Desc()),
+		},
+		deleteTestCase{
+			ds: bd.Order(C("a").Asc(), C("b").Desc()),
+			clauses: exp.NewDeleteClauses().
+				SetFrom(C("items")).
+				SetOrder(C("a").Asc(), C("b").Desc()),
+		},
+		deleteTestCase{
+			ds:      bd,
+			clauses: exp.NewDeleteClauses().SetFrom(C("items")),
+		},
+	)
+}
+
+func (dds *deleteDatasetSuite) TestOrderAppend() {
+	bd := Delete("items").Order(C("a").Asc())
+	dds.assertCases(
+		deleteTestCase{
+			ds: bd.OrderAppend(C("b").Desc()),
+			clauses: exp.NewDeleteClauses().
+				SetFrom(C("items")).
+				SetOrder(C("a").Asc(), C("b").Desc()),
+		},
+		deleteTestCase{
+			ds: bd,
+			clauses: exp.NewDeleteClauses().
+				SetFrom(C("items")).
+				SetOrder(C("a").Asc()),
+		},
+	)
+}
+
+func (dds *deleteDatasetSuite) TestOrderPrepend() {
+	bd := Delete("items").Order(C("a").Asc())
+	dds.assertCases(
+		deleteTestCase{
+			ds: bd.OrderPrepend(C("b").Desc()),
+			clauses: exp.NewDeleteClauses().
+				SetFrom(C("items")).
+				SetOrder(C("b").Desc(), C("a").Asc()),
+		},
+		deleteTestCase{
+			ds: bd,
+			clauses: exp.NewDeleteClauses().
+				SetFrom(C("items")).
+				SetOrder(C("a").Asc()),
+		},
+	)
+}
+
+func (dds *deleteDatasetSuite) TestClearOrder() {
+	bd := Delete("items").Order(C("a").Asc())
+	dds.assertCases(
+		deleteTestCase{
+			ds:      bd.ClearOrder(),
+			clauses: exp.NewDeleteClauses().SetFrom(C("items")),
+		},
+		deleteTestCase{
+			ds: bd,
+			clauses: exp.NewDeleteClauses().
+				SetFrom(C("items")).
+				SetOrder(C("a").Asc()),
+		},
+	)
+}
+
+func (dds *deleteDatasetSuite) TestLimit() {
+	bd := Delete("test")
+	dds.assertCases(
+		deleteTestCase{
+			ds: bd.Limit(10),
+			clauses: exp.NewDeleteClauses().
+				SetFrom(C("test")).
+				SetLimit(uint(10)),
+		},
+		deleteTestCase{
+			ds:      bd.Limit(0),
+			clauses: exp.NewDeleteClauses().SetFrom(C("test")),
+		},
+		deleteTestCase{
+			ds: bd.Limit(10).Limit(2),
+			clauses: exp.NewDeleteClauses().
+				SetFrom(C("test")).
+				SetLimit(uint(2)),
+		},
+		deleteTestCase{
+			ds:      bd.Limit(10).Limit(0),
+			clauses: exp.NewDeleteClauses().SetFrom(C("test")),
+		},
+		deleteTestCase{
+			ds:      bd,
+			clauses: exp.NewDeleteClauses().SetFrom(C("test")),
+		},
+	)
+}
+
+func (dds *deleteDatasetSuite) TestLimitAll() {
+	bd := Delete("test")
+	dds.assertCases(
+		deleteTestCase{
+			ds: bd.LimitAll(),
+			clauses: exp.NewDeleteClauses().
+				SetFrom(C("test")).
+				SetLimit(L("ALL")),
+		},
+		deleteTestCase{
+			ds: bd.Limit(10).LimitAll(),
+			clauses: exp.NewDeleteClauses().
+				SetFrom(C("test")).
+				SetLimit(L("ALL")),
+		},
+		deleteTestCase{
+			ds:      bd,
+			clauses: exp.NewDeleteClauses().SetFrom(C("test")),
+		},
+	)
+}
+
+func (dds *deleteDatasetSuite) TestClearLimit() {
+	bd := Delete("test").Limit(10)
+	dds.assertCases(
+		deleteTestCase{
+			ds:      bd.ClearLimit(),
+			clauses: exp.NewDeleteClauses().SetFrom(C("test")),
+		},
+		deleteTestCase{
+			ds:      bd,
+			clauses: exp.NewDeleteClauses().SetFrom(C("test")).SetLimit(uint(10)),
+		},
+	)
+}
+
+func (dds *deleteDatasetSuite) TestReturning() {
+	bd := Delete("items")
+	dds.assertCases(
+		deleteTestCase{
+			ds: bd.Returning("a"),
+			clauses: exp.NewDeleteClauses().
+				SetFrom(C("items")).
+				SetReturning(exp.NewColumnListExpression("a")),
+		},
+		deleteTestCase{
+			ds: bd.Returning(),
+			clauses: exp.NewDeleteClauses().
+				SetFrom(C("items")).
+				SetReturning(exp.NewColumnListExpression()),
+		},
+		deleteTestCase{
+			ds: bd.Returning(nil),
+			clauses: exp.NewDeleteClauses().
+				SetFrom(C("items")).
+				SetReturning(exp.NewColumnListExpression()),
+		},
+		deleteTestCase{
+			ds: bd.Returning("a").Returning("b"),
+			clauses: exp.NewDeleteClauses().
+				SetFrom(C("items")).
+				SetReturning(exp.NewColumnListExpression("b")),
+		},
+		deleteTestCase{
+			ds:      bd,
+			clauses: exp.NewDeleteClauses().SetFrom(C("items")),
+		},
+	)
+}
+
+func (dds *deleteDatasetSuite) TestReturnsColumns() {
+	ds := Delete("test")
+	dds.False(ds.ReturnsColumns())
+	dds.True(ds.Returning("foo", "bar").ReturnsColumns())
+}
+
+func (dds *deleteDatasetSuite) TestToSQL() {
+	md := new(mocks.SQLDialect)
+	ds := Delete("test").SetDialect(md)
+	c := ds.GetClauses()
+	sqlB := sb.NewSQLBuilder(false)
+	md.On("ToDeleteSQL", sqlB, c).Return(nil).Once()
+
+	sql, args, err := ds.ToSQL()
+	dds.Empty(sql)
+	dds.Empty(args)
+	dds.Nil(err)
+	md.AssertExpectations(dds.T())
+}
+
+func (dds *deleteDatasetSuite) TestToSQL_Prepared() {
+	md := new(mocks.SQLDialect)
+	ds := Delete("test").Prepared(true).SetDialect(md)
+	c := ds.GetClauses()
+	sqlB := sb.NewSQLBuilder(true)
+	md.On("ToDeleteSQL", sqlB, c).Return(nil).Once()
+
+	sql, args, err := ds.ToSQL()
+	dds.Empty(sql)
+	dds.Empty(args)
+	dds.Nil(err)
+	md.AssertExpectations(dds.T())
+}
+
+func (dds *deleteDatasetSuite) TestToSQL_WithError() {
+	md := new(mocks.SQLDialect)
+	ds := Delete("test").SetDialect(md)
+	c := ds.GetClauses()
+	ee := errors.New("expected error")
+	sqlB := sb.NewSQLBuilder(false)
+	md.On("ToDeleteSQL", sqlB, c).Run(func(args mock.Arguments) {
+		args.Get(0).(sb.SQLBuilder).SetError(ee)
+	}).Once()
+
+	sql, args, err := ds.ToSQL()
+	dds.Empty(sql)
+	dds.Empty(args)
+	dds.Equal(ee, err)
+	md.AssertExpectations(dds.T())
+}
+
+func (dds *deleteDatasetSuite) TestExecutor() {
+	mDb, _, err := sqlmock.New()
+	dds.NoError(err)
+
+	qf := exec.NewQueryFactory(mDb)
+	ds := newDeleteDataset("mock", qf).From("items").Where(Ex{"id": Op{"gt": 10}})
+
+	dsql, args, err := ds.Executor().ToSQL()
+	dds.NoError(err)
+	dds.Empty(args)
+	dds.Equal(`DELETE FROM "items" WHERE ("id" > 10)`, dsql)
+
+	dsql, args, err = ds.Prepared(true).Executor().ToSQL()
+	dds.NoError(err)
+	dds.Equal([]interface{}{int64(10)}, args)
+	dds.Equal(`DELETE FROM "items" WHERE ("id" > ?)`, dsql)
+}
+
+func (dds *deleteDatasetSuite) TestSetError() {
+	err1 := errors.New("error #1")
+	err2 := errors.New("error #2")
+	err3 := errors.New("error #3")
+
+	// Verify initial error set/get works properly
+	md := new(mocks.SQLDialect)
+	ds := Delete("test").SetDialect(md)
+	ds = ds.SetError(err1)
+	dds.Equal(err1, ds.Error())
+	sql, args, err := ds.ToSQL()
+	dds.Empty(sql)
+	dds.Empty(args)
+	dds.Equal(err1, err)
+
+	// Repeated SetError calls on Dataset should not overwrite the original error
+	ds = ds.SetError(err2)
+	dds.Equal(err1, ds.Error())
+	sql, args, err = ds.ToSQL()
+	dds.Empty(sql)
+	dds.Empty(args)
+	dds.Equal(err1, err)
+
+	// Builder functions should not lose the error
+	ds = ds.ClearLimit()
+	dds.Equal(err1, ds.Error())
+	sql, args, err = ds.ToSQL()
+	dds.Empty(sql)
+	dds.Empty(args)
+	dds.Equal(err1, err)
+
+	// Deeper errors inside SQL generation should still return original error
+	c := ds.GetClauses()
+	sqlB := sb.NewSQLBuilder(false)
+	md.On("ToDeleteSQL", sqlB, c).Run(func(args mock.Arguments) {
+		args.Get(0).(sb.SQLBuilder).SetError(err3)
+	}).Once()
+
+	sql, args, err = ds.ToSQL()
+	dds.Empty(sql)
+	dds.Empty(args)
+	dds.Equal(err1, err)
+}
+
+func TestDeleteDataset(t *testing.T) {
+	suite.Run(t, new(deleteDatasetSuite))
+}

+ 78 - 0
src/github.com/doug-martin/goqu/dialect/mysql/mysql.go

@@ -0,0 +1,78 @@
+package mysql
+
+import (
+	"github.com/doug-martin/goqu/v9"
+	"github.com/doug-martin/goqu/v9/exp"
+)
+
+func DialectOptions() *goqu.SQLDialectOptions {
+	opts := goqu.DefaultDialectOptions()
+
+	opts.SupportsReturn = false
+	opts.SupportsOrderByOnUpdate = true
+	opts.SupportsLimitOnUpdate = true
+	opts.SupportsLimitOnDelete = true
+	opts.SupportsOrderByOnDelete = true
+	opts.SupportsConflictUpdateWhere = false
+	opts.SupportsInsertIgnoreSyntax = true
+	opts.SupportsConflictTarget = false
+	opts.SupportsWithCTE = false
+	opts.SupportsWithCTERecursive = false
+	opts.SupportsDistinctOn = false
+	opts.SupportsWindowFunction = false
+
+	opts.UseFromClauseForMultipleUpdateTables = false
+
+	opts.PlaceHolderFragment = []byte("?")
+	opts.IncludePlaceholderNum = false
+	opts.QuoteRune = '`'
+	opts.DefaultValuesFragment = []byte("")
+	opts.True = []byte("1")
+	opts.False = []byte("0")
+	opts.TimeFormat = "2006-01-02 15:04:05"
+	opts.BooleanOperatorLookup = map[exp.BooleanOperation][]byte{
+		exp.EqOp:             []byte("="),
+		exp.NeqOp:            []byte("!="),
+		exp.GtOp:             []byte(">"),
+		exp.GteOp:            []byte(">="),
+		exp.LtOp:             []byte("<"),
+		exp.LteOp:            []byte("<="),
+		exp.InOp:             []byte("IN"),
+		exp.NotInOp:          []byte("NOT IN"),
+		exp.IsOp:             []byte("IS"),
+		exp.IsNotOp:          []byte("IS NOT"),
+		exp.LikeOp:           []byte("LIKE BINARY"),
+		exp.NotLikeOp:        []byte("NOT LIKE BINARY"),
+		exp.ILikeOp:          []byte("LIKE"),
+		exp.NotILikeOp:       []byte("NOT LIKE"),
+		exp.RegexpLikeOp:     []byte("REGEXP BINARY"),
+		exp.RegexpNotLikeOp:  []byte("NOT REGEXP BINARY"),
+		exp.RegexpILikeOp:    []byte("REGEXP"),
+		exp.RegexpNotILikeOp: []byte("NOT REGEXP"),
+	}
+	opts.EscapedRunes = map[rune][]byte{
+		'\'': []byte("\\'"),
+		'"':  []byte("\\\""),
+		'\\': []byte("\\\\"),
+		'\n': []byte("\\n"),
+		'\r': []byte("\\r"),
+		0:    []byte("\\x00"),
+		0x1a: []byte("\\x1a"),
+	}
+	opts.InsertIgnoreClause = []byte("INSERT IGNORE INTO")
+	opts.ConflictFragment = []byte("")
+	opts.ConflictDoUpdateFragment = []byte(" ON DUPLICATE KEY UPDATE ")
+	opts.ConflictDoNothingFragment = []byte("")
+	return opts
+}
+
+func DialectOptionsV8() *goqu.SQLDialectOptions {
+	opts := DialectOptions()
+	opts.SupportsWindowFunction = true
+	return opts
+}
+
+func init() {
+	goqu.RegisterDialect("mysql", DialectOptions())
+	goqu.RegisterDialect("mysql8", DialectOptionsV8())
+}

+ 185 - 0
src/github.com/doug-martin/goqu/dialect/mysql/mysql_dialect_test.go

@@ -0,0 +1,185 @@
+package mysql
+
+import (
+	"regexp"
+	"testing"
+
+	"github.com/doug-martin/goqu/v9"
+	"github.com/stretchr/testify/suite"
+)
+
+type mysqlDialectSuite struct {
+	suite.Suite
+}
+
+func (mds *mysqlDialectSuite) GetDs(table string) *goqu.SelectDataset {
+	return goqu.Dialect("mysql").From(table)
+}
+
+func (mds *mysqlDialectSuite) TestIdentifiers() {
+	ds := mds.GetDs("test")
+	sql, _, err := ds.Select("a",
+		goqu.I("a.b.c"),
+		goqu.I("c.d"),
+		goqu.C("test").As("test"),
+	).ToSQL()
+	mds.NoError(err)
+	mds.Equal("SELECT `a`, `a`.`b`.`c`, `c`.`d`, `test` AS `test` FROM `test`", sql)
+}
+
+func (mds *mysqlDialectSuite) TestLiteralString() {
+	ds := mds.GetDs("test")
+	col := goqu.C("a")
+	sql, _, err := ds.Where(col.Eq("test")).ToSQL()
+	mds.NoError(err)
+	mds.Equal("SELECT * FROM `test` WHERE (`a` = 'test')", sql)
+
+	sql, _, err = ds.Where(col.Eq("test'test")).ToSQL()
+	mds.NoError(err)
+	mds.Equal("SELECT * FROM `test` WHERE (`a` = 'test\\'test')", sql)
+
+	sql, _, err = ds.Where(col.Eq(`test"test`)).ToSQL()
+	mds.NoError(err)
+	mds.Equal("SELECT * FROM `test` WHERE (`a` = 'test\\\"test')", sql)
+
+	sql, _, err = ds.Where(col.Eq(`test\test`)).ToSQL()
+	mds.NoError(err)
+	mds.Equal("SELECT * FROM `test` WHERE (`a` = 'test\\\\test')", sql)
+
+	sql, _, err = ds.Where(col.Eq("test\ntest")).ToSQL()
+	mds.NoError(err)
+	mds.Equal("SELECT * FROM `test` WHERE (`a` = 'test\\ntest')", sql)
+
+	sql, _, err = ds.Where(col.Eq("test\rtest")).ToSQL()
+	mds.NoError(err)
+	mds.Equal("SELECT * FROM `test` WHERE (`a` = 'test\\rtest')", sql)
+
+	sql, _, err = ds.Where(col.Eq("test\x00test")).ToSQL()
+	mds.NoError(err)
+	mds.Equal("SELECT * FROM `test` WHERE (`a` = 'test\\x00test')", sql)
+
+	sql, _, err = ds.Where(col.Eq("test\x1atest")).ToSQL()
+	mds.NoError(err)
+	mds.Equal("SELECT * FROM `test` WHERE (`a` = 'test\\x1atest')", sql)
+}
+
+func (mds *mysqlDialectSuite) TestLiteralBytes() {
+	col := goqu.C("a")
+	ds := mds.GetDs("test")
+	sql, _, err := ds.Where(col.Eq([]byte("test"))).ToSQL()
+	mds.NoError(err)
+	mds.Equal("SELECT * FROM `test` WHERE (`a` = 'test')", sql)
+
+	sql, _, err = ds.Where(col.Eq([]byte("test'test"))).ToSQL()
+	mds.NoError(err)
+	mds.Equal("SELECT * FROM `test` WHERE (`a` = 'test\\'test')", sql)
+
+	sql, _, err = ds.Where(col.Eq([]byte(`test"test`))).ToSQL()
+	mds.NoError(err)
+	mds.Equal("SELECT * FROM `test` WHERE (`a` = 'test\\\"test')", sql)
+
+	sql, _, err = ds.Where(col.Eq([]byte(`test\test`))).ToSQL()
+	mds.NoError(err)
+	mds.Equal("SELECT * FROM `test` WHERE (`a` = 'test\\\\test')", sql)
+
+	sql, _, err = ds.Where(col.Eq([]byte("test\ntest"))).ToSQL()
+	mds.NoError(err)
+	mds.Equal("SELECT * FROM `test` WHERE (`a` = 'test\\ntest')", sql)
+
+	sql, _, err = ds.Where(col.Eq([]byte("test\rtest"))).ToSQL()
+	mds.NoError(err)
+	mds.Equal("SELECT * FROM `test` WHERE (`a` = 'test\\rtest')", sql)
+
+	sql, _, err = ds.Where(col.Eq([]byte("test\x00test"))).ToSQL()
+	mds.NoError(err)
+	mds.Equal("SELECT * FROM `test` WHERE (`a` = 'test\\x00test')", sql)
+
+	sql, _, err = ds.Where(col.Eq([]byte("test\x1atest"))).ToSQL()
+	mds.NoError(err)
+	mds.Equal("SELECT * FROM `test` WHERE (`a` = 'test\\x1atest')", sql)
+}
+
+func (mds *mysqlDialectSuite) TestBooleanOperations() {
+	col := goqu.C("a")
+	ds := mds.GetDs("test")
+	sql, _, err := ds.Where(col.Eq(true)).ToSQL()
+	mds.NoError(err)
+	mds.Equal("SELECT * FROM `test` WHERE (`a` IS TRUE)", sql)
+	sql, _, err = ds.Where(col.Eq(false)).ToSQL()
+	mds.NoError(err)
+	mds.Equal("SELECT * FROM `test` WHERE (`a` IS FALSE)", sql)
+	sql, _, err = ds.Where(col.Is(true)).ToSQL()
+	mds.NoError(err)
+	mds.Equal("SELECT * FROM `test` WHERE (`a` IS TRUE)", sql)
+	sql, _, err = ds.Where(col.Is(false)).ToSQL()
+	mds.NoError(err)
+	mds.Equal("SELECT * FROM `test` WHERE (`a` IS FALSE)", sql)
+	sql, _, err = ds.Where(col.IsTrue()).ToSQL()
+	mds.NoError(err)
+	mds.Equal("SELECT * FROM `test` WHERE (`a` IS TRUE)", sql)
+	sql, _, err = ds.Where(col.IsFalse()).ToSQL()
+	mds.NoError(err)
+	mds.Equal("SELECT * FROM `test` WHERE (`a` IS FALSE)", sql)
+
+	sql, _, err = ds.Where(col.Neq(true)).ToSQL()
+	mds.NoError(err)
+	mds.Equal("SELECT * FROM `test` WHERE (`a` IS NOT TRUE)", sql)
+	sql, _, err = ds.Where(col.Neq(false)).ToSQL()
+	mds.NoError(err)
+	mds.Equal("SELECT * FROM `test` WHERE (`a` IS NOT FALSE)", sql)
+	sql, _, err = ds.Where(col.IsNot(true)).ToSQL()
+	mds.NoError(err)
+	mds.Equal("SELECT * FROM `test` WHERE (`a` IS NOT TRUE)", sql)
+	sql, _, err = ds.Where(col.IsNot(false)).ToSQL()
+	mds.NoError(err)
+	mds.Equal("SELECT * FROM `test` WHERE (`a` IS NOT FALSE)", sql)
+	sql, _, err = ds.Where(col.IsNotTrue()).ToSQL()
+	mds.NoError(err)
+	mds.Equal("SELECT * FROM `test` WHERE (`a` IS NOT TRUE)", sql)
+	sql, _, err = ds.Where(col.IsNotFalse()).ToSQL()
+	mds.NoError(err)
+	mds.Equal("SELECT * FROM `test` WHERE (`a` IS NOT FALSE)", sql)
+
+	sql, _, err = ds.Where(col.Like("a%")).ToSQL()
+	mds.NoError(err)
+	mds.Equal("SELECT * FROM `test` WHERE (`a` LIKE BINARY 'a%')", sql)
+
+	sql, _, err = ds.Where(col.NotLike("a%")).ToSQL()
+	mds.NoError(err)
+	mds.Equal("SELECT * FROM `test` WHERE (`a` NOT LIKE BINARY 'a%')", sql)
+
+	sql, _, err = ds.Where(col.ILike("a%")).ToSQL()
+	mds.NoError(err)
+	mds.Equal("SELECT * FROM `test` WHERE (`a` LIKE 'a%')", sql)
+	sql, _, err = ds.Where(col.NotILike("a%")).ToSQL()
+	mds.NoError(err)
+	mds.Equal("SELECT * FROM `test` WHERE (`a` NOT LIKE 'a%')", sql)
+
+	sql, _, err = ds.Where(col.Like(regexp.MustCompile("(a|b)"))).ToSQL()
+	mds.NoError(err)
+	mds.Equal("SELECT * FROM `test` WHERE (`a` REGEXP BINARY '(a|b)')", sql)
+	sql, _, err = ds.Where(col.NotLike(regexp.MustCompile("(a|b)"))).ToSQL()
+	mds.NoError(err)
+	mds.Equal("SELECT * FROM `test` WHERE (`a` NOT REGEXP BINARY '(a|b)')", sql)
+	sql, _, err = ds.Where(col.ILike(regexp.MustCompile("(a|b)"))).ToSQL()
+	mds.NoError(err)
+	mds.Equal("SELECT * FROM `test` WHERE (`a` REGEXP '(a|b)')", sql)
+	sql, _, err = ds.Where(col.NotILike(regexp.MustCompile("(a|b)"))).ToSQL()
+	mds.NoError(err)
+	mds.Equal("SELECT * FROM `test` WHERE (`a` NOT REGEXP '(a|b)')", sql)
+}
+
+func (mds *mysqlDialectSuite) TestUpdateSQL() {
+	ds := mds.GetDs("test").Update()
+	sql, _, err := ds.
+		Set(goqu.Record{"foo": "bar"}).
+		From("test_2").
+		Where(goqu.I("test.id").Eq(goqu.I("test_2.test_id"))).
+		ToSQL()
+	mds.NoError(err)
+	mds.Equal("UPDATE `test`,`test_2` SET `foo`='bar' WHERE (`test`.`id` = `test_2`.`test_id`)", sql)
+}
+
+func TestDatasetAdapterSuite(t *testing.T) {
+	suite.Run(t, new(mysqlDialectSuite))
+}

+ 544 - 0
src/github.com/doug-martin/goqu/dialect/mysql/mysql_test.go

@@ -0,0 +1,544 @@
+package mysql_test
+
+import (
+	"database/sql"
+	"fmt"
+	"os"
+	"strconv"
+	"strings"
+	"testing"
+	"time"
+
+	"github.com/doug-martin/goqu/v9"
+	"github.com/doug-martin/goqu/v9/dialect/mysql"
+	_ "github.com/go-sql-driver/mysql"
+	"github.com/stretchr/testify/suite"
+)
+
+const (
+	dropTable   = "DROP TABLE IF EXISTS `entry`;"
+	createTable = "CREATE  TABLE `entry` (" +
+		"`id` INT NOT NULL AUTO_INCREMENT ," +
+		"`int` INT NOT NULL UNIQUE," +
+		"`float` FLOAT NOT NULL ," +
+		"`string` VARCHAR(255) NOT NULL ," +
+		"`time` DATETIME NOT NULL ," +
+		"`bool` TINYINT NOT NULL ," +
+		"`bytes` BLOB NOT NULL ," +
+		"PRIMARY KEY (`id`) );"
+	insertDefaultReords = "INSERT INTO `entry` (`int`, `float`, `string`, `time`, `bool`, `bytes`) VALUES" +
+		"(0, 0.000000, '0.000000', '2015-02-22 18:19:55', TRUE,  '0.000000')," +
+		"(1, 0.100000, '0.100000', '2015-02-22 19:19:55', FALSE, '0.100000')," +
+		"(2, 0.200000, '0.200000', '2015-02-22 20:19:55', TRUE,  '0.200000')," +
+		"(3, 0.300000, '0.300000', '2015-02-22 21:19:55', FALSE, '0.300000')," +
+		"(4, 0.400000, '0.400000', '2015-02-22 22:19:55', TRUE,  '0.400000')," +
+		"(5, 0.500000, '0.500000', '2015-02-22 23:19:55', FALSE, '0.500000')," +
+		"(6, 0.600000, '0.600000', '2015-02-23 00:19:55', TRUE,  '0.600000')," +
+		"(7, 0.700000, '0.700000', '2015-02-23 01:19:55', FALSE, '0.700000')," +
+		"(8, 0.800000, '0.800000', '2015-02-23 02:19:55', TRUE,  '0.800000')," +
+		"(9, 0.900000, '0.900000', '2015-02-23 03:19:55', FALSE, '0.900000');"
+)
+
+const defaultDbURI = "root@/goqumysql?parseTime=true"
+
+type (
+	mysqlTest struct {
+		suite.Suite
+		db *goqu.Database
+	}
+	entry struct {
+		ID     uint32    `db:"id" goqu:"skipinsert,skipupdate"`
+		Int    int       `db:"int"`
+		Float  float64   `db:"float"`
+		String string    `db:"string"`
+		Time   time.Time `db:"time"`
+		Bool   bool      `db:"bool"`
+		Bytes  []byte    `db:"bytes"`
+	}
+)
+
+func (mt *mysqlTest) SetupSuite() {
+	dbURI := os.Getenv("MYSQL_URI")
+	if dbURI == "" {
+		dbURI = defaultDbURI
+	}
+	db, err := sql.Open("mysql", dbURI)
+	if err != nil {
+		panic(err.Error())
+	}
+	mt.db = goqu.New("mysql", db)
+}
+
+func (mt *mysqlTest) SetupTest() {
+	if _, err := mt.db.Exec(dropTable); err != nil {
+		panic(err)
+	}
+	if _, err := mt.db.Exec(createTable); err != nil {
+		panic(err)
+	}
+	if _, err := mt.db.Exec(insertDefaultReords); err != nil {
+		panic(err)
+	}
+}
+
+func (mt *mysqlTest) TestToSQL() {
+	ds := mt.db.From("entry")
+	s, _, err := ds.Select("id", "float", "string", "time", "bool").ToSQL()
+	mt.NoError(err)
+	mt.Equal("SELECT `id`, `float`, `string`, `time`, `bool` FROM `entry`", s)
+
+	s, _, err = ds.Where(goqu.C("int").Eq(10)).ToSQL()
+	mt.NoError(err)
+	mt.Equal("SELECT * FROM `entry` WHERE (`int` = 10)", s)
+
+	s, args, err := ds.Prepared(true).Where(goqu.L("? = ?", goqu.C("int"), 10)).ToSQL()
+	mt.NoError(err)
+	mt.Equal([]interface{}{int64(10)}, args)
+	mt.Equal("SELECT * FROM `entry` WHERE `int` = ?", s)
+}
+
+func (mt *mysqlTest) TestQuery() {
+	var entries []entry
+	ds := mt.db.From("entry")
+	mt.NoError(ds.Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	mt.Len(entries, 10)
+	floatVal := float64(0)
+	baseDate, err := time.Parse(
+		"2006-01-02 15:04:05",
+		"2015-02-22 18:19:55",
+	)
+	mt.NoError(err)
+	for i, entry := range entries {
+		f := fmt.Sprintf("%f", floatVal)
+		mt.Equal(uint32(i+1), entry.ID)
+		mt.Equal(i, entry.Int)
+		mt.Equal(f, fmt.Sprintf("%f", entry.Float))
+		mt.Equal(f, entry.String)
+		mt.Equal([]byte(f), entry.Bytes)
+		mt.Equal(i%2 == 0, entry.Bool)
+		mt.Equal(baseDate.Add(time.Duration(i)*time.Hour), entry.Time)
+		floatVal += float64(0.1)
+	}
+	entries = entries[0:0]
+	mt.NoError(ds.Where(goqu.C("bool").IsTrue()).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	mt.Len(entries, 5)
+	mt.NoError(err)
+	for _, entry := range entries {
+		mt.True(entry.Bool)
+	}
+
+	entries = entries[0:0]
+	mt.NoError(ds.Where(goqu.C("int").Gt(4)).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	mt.Len(entries, 5)
+	mt.NoError(err)
+	for _, entry := range entries {
+		mt.True(entry.Int > 4)
+	}
+
+	entries = entries[0:0]
+	mt.NoError(ds.Where(goqu.C("int").Gte(5)).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	mt.Len(entries, 5)
+	mt.NoError(err)
+	for _, entry := range entries {
+		mt.True(entry.Int >= 5)
+	}
+
+	entries = entries[0:0]
+	mt.NoError(ds.Where(goqu.C("int").Lt(5)).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	mt.Len(entries, 5)
+	mt.NoError(err)
+	for _, entry := range entries {
+		mt.True(entry.Int < 5)
+	}
+
+	entries = entries[0:0]
+	mt.NoError(ds.Where(goqu.C("int").Lte(4)).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	mt.Len(entries, 5)
+	mt.NoError(err)
+	for _, entry := range entries {
+		mt.True(entry.Int <= 4)
+	}
+
+	entries = entries[0:0]
+	mt.NoError(ds.Where(goqu.C("int").Between(goqu.Range(3, 6))).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	mt.Len(entries, 4)
+	mt.NoError(err)
+	for _, entry := range entries {
+		mt.True(entry.Int >= 3)
+		mt.True(entry.Int <= 6)
+	}
+
+	entries = entries[0:0]
+	mt.NoError(ds.Where(goqu.C("string").Eq("0.100000")).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	mt.Len(entries, 1)
+	mt.NoError(err)
+	for _, entry := range entries {
+		mt.Equal("0.100000", entry.String)
+	}
+
+	entries = entries[0:0]
+	mt.NoError(ds.Where(goqu.C("string").Like("0.1%")).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	mt.Len(entries, 1)
+	mt.NoError(err)
+	for _, entry := range entries {
+		mt.Equal("0.100000", entry.String)
+	}
+
+	entries = entries[0:0]
+	mt.NoError(ds.Where(goqu.C("string").NotLike("0.1%")).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	mt.Len(entries, 9)
+	mt.NoError(err)
+	for _, entry := range entries {
+		mt.NotEqual("0.100000", entry.String)
+	}
+
+	entries = entries[0:0]
+	mt.NoError(ds.Where(goqu.C("string").IsNull()).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	mt.Len(entries, 0)
+}
+
+func (mt *mysqlTest) TestQuery_Prepared() {
+	var entries []entry
+	ds := mt.db.From("entry").Prepared(true)
+	mt.NoError(ds.Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	mt.Len(entries, 10)
+	floatVal := float64(0)
+	baseDate, err := time.Parse(
+		"2006-01-02 15:04:05",
+		"2015-02-22 18:19:55",
+	)
+	mt.NoError(err)
+	for i, entry := range entries {
+		f := fmt.Sprintf("%f", floatVal)
+		mt.Equal(uint32(i+1), entry.ID)
+		mt.Equal(i, entry.Int)
+		mt.Equal(f, fmt.Sprintf("%f", entry.Float))
+		mt.Equal(f, entry.String)
+		mt.Equal([]byte(f), entry.Bytes)
+		mt.Equal(i%2 == 0, entry.Bool)
+		mt.Equal(baseDate.Add(time.Duration(i)*time.Hour), entry.Time)
+		floatVal += float64(0.1)
+	}
+	entries = entries[0:0]
+	mt.NoError(ds.Where(goqu.C("bool").IsTrue()).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	mt.Len(entries, 5)
+	mt.NoError(err)
+	for _, entry := range entries {
+		mt.True(entry.Bool)
+	}
+
+	entries = entries[0:0]
+	mt.NoError(ds.Where(goqu.C("int").Gt(4)).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	mt.Len(entries, 5)
+	mt.NoError(err)
+	for _, entry := range entries {
+		mt.True(entry.Int > 4)
+	}
+
+	entries = entries[0:0]
+	mt.NoError(ds.Where(goqu.C("int").Gte(5)).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	mt.Len(entries, 5)
+	mt.NoError(err)
+	for _, entry := range entries {
+		mt.True(entry.Int >= 5)
+	}
+
+	entries = entries[0:0]
+	mt.NoError(ds.Where(goqu.C("int").Lt(5)).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	mt.Len(entries, 5)
+	mt.NoError(err)
+	for _, entry := range entries {
+		mt.True(entry.Int < 5)
+	}
+
+	entries = entries[0:0]
+	mt.NoError(ds.Where(goqu.C("int").Lte(4)).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	mt.Len(entries, 5)
+	mt.NoError(err)
+	for _, entry := range entries {
+		mt.True(entry.Int <= 4)
+	}
+
+	entries = entries[0:0]
+	mt.NoError(ds.Where(goqu.C("int").Between(goqu.Range(3, 6))).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	mt.Len(entries, 4)
+	mt.NoError(err)
+	for _, entry := range entries {
+		mt.True(entry.Int >= 3)
+		mt.True(entry.Int <= 6)
+	}
+
+	entries = entries[0:0]
+	mt.NoError(ds.Where(goqu.C("string").Eq("0.100000")).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	mt.Len(entries, 1)
+	mt.NoError(err)
+	for _, entry := range entries {
+		mt.Equal("0.100000", entry.String)
+	}
+
+	entries = entries[0:0]
+	mt.NoError(ds.Where(goqu.C("string").Like("0.1%")).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	mt.Len(entries, 1)
+	mt.NoError(err)
+	for _, entry := range entries {
+		mt.Equal("0.100000", entry.String)
+	}
+
+	entries = entries[0:0]
+	mt.NoError(ds.Where(goqu.C("string").NotLike("0.1%")).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	mt.Len(entries, 9)
+	mt.NoError(err)
+	for _, entry := range entries {
+		mt.NotEqual("0.100000", entry.String)
+	}
+
+	entries = entries[0:0]
+	mt.NoError(ds.Where(goqu.C("string").IsNull()).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	mt.Len(entries, 0)
+}
+
+func (mt *mysqlTest) TestQuery_ValueExpressions() {
+	type wrappedEntry struct {
+		entry
+		BoolValue bool `db:"bool_value"`
+	}
+	expectedDate, err := time.Parse("2006-01-02 15:04:05", "2015-02-22 19:19:55")
+	mt.NoError(err)
+	ds := mt.db.From("entry").Select(goqu.Star(), goqu.V(true).As("bool_value")).Where(goqu.Ex{"int": 1})
+	var we wrappedEntry
+	found, err := ds.ScanStruct(&we)
+	mt.NoError(err)
+	mt.True(found)
+	mt.Equal(wrappedEntry{
+		entry{2, 1, 0.100000, "0.100000", expectedDate, false, []byte("0.100000")},
+		true,
+	}, we)
+}
+
+func (mt *mysqlTest) TestCount() {
+	ds := mt.db.From("entry")
+	count, err := ds.Count()
+	mt.NoError(err)
+	mt.Equal(int64(10), count)
+	count, err = ds.Where(goqu.C("int").Gt(4)).Count()
+	mt.NoError(err)
+	mt.Equal(int64(5), count)
+	count, err = ds.Where(goqu.C("int").Gte(4)).Count()
+	mt.NoError(err)
+	mt.Equal(int64(6), count)
+	count, err = ds.Where(goqu.C("string").Like("0.1%")).Count()
+	mt.NoError(err)
+	mt.Equal(int64(1), count)
+	count, err = ds.Where(goqu.C("string").IsNull()).Count()
+	mt.NoError(err)
+	mt.Equal(int64(0), count)
+}
+
+func (mt *mysqlTest) TestInsert() {
+	ds := mt.db.From("entry")
+	now := time.Now()
+	e := entry{Int: 10, Float: 1.000000, String: "1.000000", Time: now, Bool: true, Bytes: []byte("1.000000")}
+	_, err := ds.Insert().Rows(e).Executor().Exec()
+	mt.NoError(err)
+
+	var insertedEntry entry
+	found, err := ds.Where(goqu.C("int").Eq(10)).ScanStruct(&insertedEntry)
+	mt.NoError(err)
+	mt.True(found)
+	mt.True(insertedEntry.ID > 0)
+
+	entries := []entry{
+		{Int: 11, Float: 1.100000, String: "1.100000", Time: now, Bool: false, Bytes: []byte("1.100000")},
+		{Int: 12, Float: 1.200000, String: "1.200000", Time: now, Bool: true, Bytes: []byte("1.200000")},
+		{Int: 13, Float: 1.300000, String: "1.300000", Time: now, Bool: false, Bytes: []byte("1.300000")},
+		{Int: 14, Float: 1.400000, String: "1.400000", Time: now, Bool: true, Bytes: []byte("1.400000")},
+	}
+	_, err = ds.Insert().Rows(entries).Executor().Exec()
+	mt.NoError(err)
+
+	var newEntries []entry
+	mt.NoError(ds.Where(goqu.C("int").In([]uint32{11, 12, 13, 14})).ScanStructs(&newEntries))
+	mt.Len(newEntries, 4)
+	for i, e := range newEntries {
+		mt.Equal(entries[i].Int, e.Int)
+		mt.Equal(entries[i].Float, e.Float)
+		mt.Equal(entries[i].String, e.String)
+		mt.Equal(entries[i].Time.UTC().Format(mysql.DialectOptions().TimeFormat), e.Time.Format(mysql.DialectOptions().TimeFormat))
+		mt.Equal(entries[i].Bool, e.Bool)
+		mt.Equal(entries[i].Bytes, e.Bytes)
+	}
+
+	_, err = ds.Insert().Rows(
+		entry{Int: 15, Float: 1.500000, String: "1.500000", Time: now, Bool: false, Bytes: []byte("1.500000")},
+		entry{Int: 16, Float: 1.600000, String: "1.600000", Time: now, Bool: true, Bytes: []byte("1.600000")},
+		entry{Int: 17, Float: 1.700000, String: "1.700000", Time: now, Bool: false, Bytes: []byte("1.700000")},
+		entry{Int: 18, Float: 1.800000, String: "1.800000", Time: now, Bool: true, Bytes: []byte("1.800000")},
+	).Executor().Exec()
+	mt.NoError(err)
+
+	newEntries = newEntries[0:0]
+	mt.NoError(ds.Where(goqu.C("int").In([]uint32{15, 16, 17, 18})).ScanStructs(&newEntries))
+	mt.Len(newEntries, 4)
+}
+
+func (mt *mysqlTest) TestInsertReturning() {
+	ds := mt.db.From("entry")
+	now := time.Now()
+	e := entry{Int: 10, Float: 1.000000, String: "1.000000", Time: now, Bool: true, Bytes: []byte("1.000000")}
+	_, err := ds.Insert().Rows(e).Returning(goqu.Star()).Executor().ScanStruct(&e)
+	mt.Error(err)
+}
+
+func (mt *mysqlTest) TestUpdate() {
+	ds := mt.db.From("entry")
+	var e entry
+	found, err := ds.Where(goqu.C("int").Eq(9)).Select("id").ScanStruct(&e)
+	mt.NoError(err)
+	mt.True(found)
+	e.Int = 11
+	_, err = ds.Where(goqu.C("id").Eq(e.ID)).Update().Set(e).Executor().Exec()
+	mt.NoError(err)
+
+	count, err := ds.Where(goqu.C("int").Eq(11)).Count()
+	mt.NoError(err)
+	mt.Equal(int64(1), count)
+}
+
+func (mt *mysqlTest) TestUpdateReturning() {
+	ds := mt.db.From("entry")
+	var id uint32
+	_, err := ds.Where(goqu.C("int").Eq(11)).
+		Update().
+		Set(goqu.Record{"int": 9}).
+		Returning("id").
+		Executor().ScanVal(&id)
+	mt.Error(err)
+	mt.EqualError(err, "goqu: dialect does not support RETURNING clause [dialect=mysql]")
+}
+
+func (mt *mysqlTest) TestDelete() {
+	ds := mt.db.From("entry")
+	var e entry
+	found, err := ds.Where(goqu.C("int").Eq(9)).Select("id").ScanStruct(&e)
+	mt.NoError(err)
+	mt.True(found)
+	_, err = ds.Where(goqu.C("id").Eq(e.ID)).Delete().Executor().Exec()
+	mt.NoError(err)
+
+	count, err := ds.Count()
+	mt.NoError(err)
+	mt.Equal(int64(9), count)
+
+	var id uint32
+	found, err = ds.Where(goqu.C("id").Eq(e.ID)).ScanVal(&id)
+	mt.NoError(err)
+	mt.False(found)
+
+	e = entry{}
+	found, err = ds.Where(goqu.C("int").Eq(8)).Select("id").ScanStruct(&e)
+	mt.NoError(err)
+	mt.True(found)
+	mt.NotEqual(0, e.ID)
+
+	id = 0
+	_, err = ds.Where(goqu.C("id").Eq(e.ID)).Delete().Returning("id").Executor().ScanVal(&id)
+	mt.EqualError(err, "goqu: dialect does not support RETURNING clause [dialect=mysql]")
+}
+
+func (mt *mysqlTest) TestInsertIgnore() {
+	ds := mt.db.From("entry")
+	now := time.Now()
+
+	// insert one
+	entries := []entry{
+		{Int: 8, Float: 6.100000, String: "6.100000", Time: now, Bytes: []byte("6.100000")},
+		{Int: 9, Float: 7.200000, String: "7.200000", Time: now, Bytes: []byte("7.200000")},
+		{Int: 10, Float: 7.200000, String: "7.200000", Time: now, Bytes: []byte("7.200000")},
+	}
+	_, err := ds.Insert().Rows(entries).OnConflict(goqu.DoNothing()).Executor().Exec()
+	mt.NoError(err)
+
+	count, err := ds.Count()
+	mt.NoError(err)
+	mt.Equal(count, int64(11))
+}
+
+func (mt *mysqlTest) TestInsert_OnConflict() {
+	ds := mt.db.From("entry")
+	now := time.Now()
+
+	// insert
+	e := entry{Int: 10, Float: 1.100000, String: "1.100000", Time: now, Bool: false, Bytes: []byte("1.100000")}
+	_, err := ds.Insert().Rows(e).OnConflict(goqu.DoNothing()).Executor().Exec()
+	mt.NoError(err)
+
+	// duplicate
+	e = entry{Int: 10, Float: 2.100000, String: "2.100000", Time: now.Add(time.Hour * 100), Bool: false, Bytes: []byte("2.100000")}
+	_, err = ds.Insert().Rows(e).OnConflict(goqu.DoNothing()).Executor().Exec()
+	mt.NoError(err)
+
+	// update
+	var entryActual entry
+	e2 := entry{Int: 10, String: "2.000000"}
+	_, err = ds.Insert().
+		Rows(e2).
+		OnConflict(goqu.DoUpdate("int", goqu.Record{"string": "upsert"})).
+		Executor().Exec()
+	mt.NoError(err)
+	_, err = ds.Where(goqu.C("int").Eq(10)).ScanStruct(&entryActual)
+	mt.NoError(err)
+	mt.Equal("upsert", entryActual.String)
+
+	// update where should error
+	entries := []entry{
+		{Int: 8, Float: 6.100000, String: "6.100000", Time: now, Bytes: []byte("6.100000")},
+		{Int: 9, Float: 7.200000, String: "7.200000", Time: now, Bytes: []byte("7.200000")},
+	}
+	_, err = ds.Insert().
+		Rows(entries).
+		OnConflict(goqu.DoUpdate("int", goqu.Record{"string": "upsert"}).Where(goqu.C("int").Eq(9))).
+		Executor().Exec()
+	mt.EqualError(err, "goqu: dialect does not support upsert with where clause [dialect=mysql]")
+}
+
+func (mt *mysqlTest) TestWindowFunction() {
+	var version string
+	ok, err := mt.db.Select(goqu.Func("version")).ScanVal(&version)
+	mt.NoError(err)
+	mt.True(ok)
+
+	fields := strings.Split(version, ".")
+	mt.True(len(fields) > 0)
+	major, err := strconv.Atoi(fields[0])
+	mt.NoError(err)
+	if major < 8 {
+		fmt.Printf("SKIPPING MYSQL WINDOW FUNCTION TEST BECAUSE VERSION IS < 8 [mysql_version:=%d]\n", major)
+		return
+	}
+
+	ds := mt.db.From("entry").
+		Select("int", goqu.ROW_NUMBER().OverName(goqu.I("w")).As("id")).
+		Window(goqu.W("w").OrderBy(goqu.I("int").Desc()))
+
+	var entries []entry
+	mt.NoError(ds.WithDialect("mysql8").ScanStructs(&entries))
+
+	mt.Equal([]entry{
+		{Int: 9, ID: 1},
+		{Int: 8, ID: 2},
+		{Int: 7, ID: 3},
+		{Int: 6, ID: 4},
+		{Int: 5, ID: 5},
+		{Int: 4, ID: 6},
+		{Int: 3, ID: 7},
+		{Int: 2, ID: 8},
+		{Int: 1, ID: 9},
+		{Int: 0, ID: 10},
+	}, entries)
+
+	mt.Error(ds.WithDialect("mysql").ScanStructs(&entries), "goqu: adapter does not support window function clause")
+}
+
+func TestMysqlSuite(t *testing.T) {
+	suite.Run(t, new(mysqlTest))
+}

+ 16 - 0
src/github.com/doug-martin/goqu/dialect/postgres/postgres.go

@@ -0,0 +1,16 @@
+package postgres
+
+import (
+	"github.com/doug-martin/goqu/v9"
+)
+
+func DialectOptions() *goqu.SQLDialectOptions {
+	do := goqu.DefaultDialectOptions()
+	do.PlaceHolderFragment = []byte("$")
+	do.IncludePlaceholderNum = true
+	return do
+}
+
+func init() {
+	goqu.RegisterDialect("postgres", DialectOptions())
+}

+ 556 - 0
src/github.com/doug-martin/goqu/dialect/postgres/postgres_test.go

@@ -0,0 +1,556 @@
+package postgres
+
+import (
+	"database/sql"
+	"fmt"
+	"os"
+	"testing"
+	"time"
+
+	"github.com/doug-martin/goqu/v9"
+
+	"github.com/lib/pq"
+	"github.com/stretchr/testify/suite"
+)
+
+const schema = `
+        DROP TABLE IF EXISTS "entry";
+        CREATE  TABLE "entry" (
+            "id" SERIAL PRIMARY KEY NOT NULL,
+            "int" INT NOT NULL UNIQUE,
+            "float" NUMERIC NOT NULL ,
+            "string" VARCHAR(45) NOT NULL ,
+            "time" TIMESTAMP NOT NULL ,
+            "bool" BOOL NOT NULL ,
+            "bytes" VARCHAR(45) NOT NULL);
+        INSERT INTO "entry" ("int", "float", "string", "time", "bool", "bytes") VALUES
+            (0, 0.000000, '0.000000', '2015-02-22T18:19:55.000000000-00:00', TRUE,  '0.000000'),
+            (1, 0.100000, '0.100000', '2015-02-22T19:19:55.000000000-00:00', FALSE, '0.100000'),
+            (2, 0.200000, '0.200000', '2015-02-22T20:19:55.000000000-00:00', TRUE,  '0.200000'),
+            (3, 0.300000, '0.300000', '2015-02-22T21:19:55.000000000-00:00', FALSE, '0.300000'),
+            (4, 0.400000, '0.400000', '2015-02-22T22:19:55.000000000-00:00', TRUE,  '0.400000'),
+            (5, 0.500000, '0.500000', '2015-02-22T23:19:55.000000000-00:00', FALSE, '0.500000'),
+            (6, 0.600000, '0.600000', '2015-02-23T00:19:55.000000000-00:00', TRUE,  '0.600000'),
+            (7, 0.700000, '0.700000', '2015-02-23T01:19:55.000000000-00:00', FALSE, '0.700000'),
+            (8, 0.800000, '0.800000', '2015-02-23T02:19:55.000000000-00:00', TRUE,  '0.800000'),
+            (9, 0.900000, '0.900000', '2015-02-23T03:19:55.000000000-00:00', FALSE, '0.900000');
+    `
+
+const defaultDbURI = "postgres://postgres:@localhost:5435/goqupostgres?sslmode=disable"
+
+type (
+	postgresTest struct {
+		suite.Suite
+		db *goqu.Database
+	}
+	entry struct {
+		ID     uint32    `db:"id" goqu:"skipinsert,skipupdate"`
+		Int    int       `db:"int"`
+		Float  float64   `db:"float"`
+		String string    `db:"string"`
+		Time   time.Time `db:"time"`
+		Bool   bool      `db:"bool"`
+		Bytes  []byte    `db:"bytes"`
+	}
+)
+
+func (pt *postgresTest) SetupSuite() {
+	dbURI := os.Getenv("PG_URI")
+	if dbURI == "" {
+		dbURI = defaultDbURI
+	}
+	uri, err := pq.ParseURL(dbURI)
+	if err != nil {
+		panic(err)
+	}
+	db, err := sql.Open("postgres", uri)
+	if err != nil {
+		panic(err)
+	}
+	pt.db = goqu.New("postgres", db)
+}
+
+func (pt *postgresTest) SetupTest() {
+	if _, err := pt.db.Exec(schema); err != nil {
+		panic(err)
+	}
+}
+
+func (pt *postgresTest) TestToSQL() {
+	ds := pt.db.From("entry")
+	s, _, err := ds.Select("id", "float", "string", "time", "bool").ToSQL()
+	pt.NoError(err)
+	pt.Equal(`SELECT "id", "float", "string", "time", "bool" FROM "entry"`, s)
+
+	s, _, err = ds.Where(goqu.C("int").Eq(10)).ToSQL()
+	pt.NoError(err)
+	pt.Equal(`SELECT * FROM "entry" WHERE ("int" = 10)`, s)
+
+	s, args, err := ds.Prepared(true).Where(goqu.L("? = ?", goqu.C("int"), 10)).ToSQL()
+	pt.NoError(err)
+	pt.Equal([]interface{}{int64(10)}, args)
+	pt.Equal(`SELECT * FROM "entry" WHERE "int" = $1`, s)
+}
+
+func (pt *postgresTest) TestQuery() {
+	var entries []entry
+	ds := pt.db.From("entry")
+	pt.NoError(ds.Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	pt.Len(entries, 10)
+	floatVal := float64(0)
+	baseDate, err := time.Parse(time.RFC3339Nano, "2015-02-22T18:19:55.000000000-00:00")
+	pt.NoError(err)
+	baseDate = baseDate.UTC()
+	for i, entry := range entries {
+		f := fmt.Sprintf("%f", floatVal)
+		pt.Equal(uint32(i+1), entry.ID)
+		pt.Equal(i, entry.Int)
+		pt.Equal(f, fmt.Sprintf("%f", entry.Float))
+		pt.Equal(f, entry.String)
+		pt.Equal([]byte(f), entry.Bytes)
+		pt.Equal(i%2 == 0, entry.Bool)
+		pt.Equal(baseDate.Add(time.Duration(i)*time.Hour).Unix(), entry.Time.Unix())
+		floatVal += float64(0.1)
+	}
+	entries = entries[0:0]
+	pt.NoError(ds.Where(goqu.C("bool").IsTrue()).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	pt.Len(entries, 5)
+	pt.NoError(err)
+	for _, entry := range entries {
+		pt.True(entry.Bool)
+	}
+
+	entries = entries[0:0]
+	pt.NoError(ds.Where(goqu.C("int").Gt(4)).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	pt.Len(entries, 5)
+	pt.NoError(err)
+	for _, entry := range entries {
+		pt.True(entry.Int > 4)
+	}
+
+	entries = entries[0:0]
+	pt.NoError(ds.Where(goqu.C("int").Gte(5)).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	pt.Len(entries, 5)
+	pt.NoError(err)
+	for _, entry := range entries {
+		pt.True(entry.Int >= 5)
+	}
+
+	entries = entries[0:0]
+	pt.NoError(ds.Where(goqu.C("int").Lt(5)).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	pt.Len(entries, 5)
+	pt.NoError(err)
+	for _, entry := range entries {
+		pt.True(entry.Int < 5)
+	}
+
+	entries = entries[0:0]
+	pt.NoError(ds.Where(goqu.C("int").Lte(4)).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	pt.Len(entries, 5)
+	pt.NoError(err)
+	for _, entry := range entries {
+		pt.True(entry.Int <= 4)
+	}
+
+	entries = entries[0:0]
+	pt.NoError(ds.Where(goqu.C("int").Between(goqu.Range(3, 6))).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	pt.Len(entries, 4)
+	pt.NoError(err)
+	for _, entry := range entries {
+		pt.True(entry.Int >= 3)
+		pt.True(entry.Int <= 6)
+	}
+
+	entries = entries[0:0]
+	pt.NoError(ds.Where(goqu.C("string").Eq("0.100000")).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	pt.Len(entries, 1)
+	pt.NoError(err)
+	for _, entry := range entries {
+		pt.Equal(entry.String, "0.100000")
+	}
+
+	entries = entries[0:0]
+	pt.NoError(ds.Where(goqu.C("string").Like("0.1%")).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	pt.Len(entries, 1)
+	pt.NoError(err)
+	for _, entry := range entries {
+		pt.Equal("0.100000", entry.String)
+	}
+
+	entries = entries[0:0]
+	pt.NoError(ds.Where(goqu.C("string").NotLike("0.1%")).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	pt.Len(entries, 9)
+	pt.NoError(err)
+	for _, entry := range entries {
+		pt.NotEqual("0.100000", entry.String)
+	}
+
+	entries = entries[0:0]
+	pt.NoError(ds.Where(goqu.C("string").IsNull()).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	pt.Len(entries, 0)
+}
+
+func (pt *postgresTest) TestQuery_Prepared() {
+	var entries []entry
+	ds := pt.db.From("entry").Prepared(true)
+	pt.NoError(ds.Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	pt.Len(entries, 10)
+	floatVal := float64(0)
+	baseDate, err := time.Parse(time.RFC3339Nano, "2015-02-22T18:19:55.000000000-00:00")
+	pt.NoError(err)
+	baseDate = baseDate.UTC()
+	for i, entry := range entries {
+		f := fmt.Sprintf("%f", floatVal)
+		pt.Equal(uint32(i+1), entry.ID)
+		pt.Equal(i, entry.Int)
+		pt.Equal(f, fmt.Sprintf("%f", entry.Float))
+		pt.Equal(f, entry.String)
+		pt.Equal([]byte(f), entry.Bytes)
+		pt.Equal(i%2 == 0, entry.Bool)
+		pt.Equal(baseDate.Add(time.Duration(i)*time.Hour).Unix(), entry.Time.Unix())
+		floatVal += float64(0.1)
+	}
+	entries = entries[0:0]
+	pt.NoError(ds.Where(goqu.C("bool").IsTrue()).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	pt.Len(entries, 5)
+	pt.NoError(err)
+	for _, entry := range entries {
+		pt.True(entry.Bool)
+	}
+
+	entries = entries[0:0]
+	pt.NoError(ds.Where(goqu.C("bool").IsFalse()).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	pt.Len(entries, 5)
+	pt.NoError(err)
+	for _, entry := range entries {
+		pt.False(entry.Bool)
+	}
+
+	entries = entries[0:0]
+	pt.NoError(ds.Where(goqu.C("int").Gt(4)).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	pt.Len(entries, 5)
+	pt.NoError(err)
+	for _, entry := range entries {
+		pt.True(entry.Int > 4)
+	}
+
+	entries = entries[0:0]
+	pt.NoError(ds.Where(goqu.C("int").Gte(5)).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	pt.Len(entries, 5)
+	pt.NoError(err)
+	for _, entry := range entries {
+		pt.True(entry.Int >= 5)
+	}
+
+	entries = entries[0:0]
+	pt.NoError(ds.Where(goqu.C("int").Lt(5)).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	pt.Len(entries, 5)
+	pt.NoError(err)
+	for _, entry := range entries {
+		pt.True(entry.Int < 5)
+	}
+
+	entries = entries[0:0]
+	pt.NoError(ds.Where(goqu.C("int").Lte(4)).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	pt.Len(entries, 5)
+	pt.NoError(err)
+	for _, entry := range entries {
+		pt.True(entry.Int <= 4)
+	}
+
+	entries = entries[0:0]
+	pt.NoError(ds.Where(goqu.C("int").Between(goqu.Range(3, 6))).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	pt.Len(entries, 4)
+	pt.NoError(err)
+	for _, entry := range entries {
+		pt.True(entry.Int >= 3)
+		pt.True(entry.Int <= 6)
+	}
+
+	entries = entries[0:0]
+	pt.NoError(ds.Where(goqu.C("string").Eq("0.100000")).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	pt.Len(entries, 1)
+	pt.NoError(err)
+	for _, entry := range entries {
+		pt.Equal(entry.String, "0.100000")
+	}
+
+	entries = entries[0:0]
+	pt.NoError(ds.Where(goqu.C("string").Like("0.1%")).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	pt.Len(entries, 1)
+	pt.NoError(err)
+	for _, entry := range entries {
+		pt.Equal("0.100000", entry.String)
+	}
+
+	entries = entries[0:0]
+	pt.NoError(ds.Where(goqu.C("string").NotLike("0.1%")).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	pt.Len(entries, 9)
+	pt.NoError(err)
+	for _, entry := range entries {
+		pt.NotEqual("0.100000", entry.String)
+	}
+
+	entries = entries[0:0]
+	pt.NoError(ds.Where(goqu.C("string").IsNull()).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	pt.Len(entries, 0)
+}
+
+func (pt *postgresTest) TestQuery_ValueExpressions() {
+	type wrappedEntry struct {
+		entry
+		BoolValue bool `db:"bool_value"`
+	}
+	expectedDate, err := time.Parse(time.RFC3339Nano, "2015-02-22T19:19:55.000000000-00:00")
+	pt.NoError(err)
+	ds := pt.db.From("entry").Select(goqu.Star(), goqu.V(true).As("bool_value")).Where(goqu.Ex{"int": 1})
+	var we wrappedEntry
+	found, err := ds.ScanStruct(&we)
+	pt.NoError(err)
+	pt.True(found)
+	pt.Equal(1, we.Int)
+	pt.Equal(0.100000, we.Float)
+	pt.Equal("0.100000", we.String)
+	pt.Equal(expectedDate.Unix(), we.Time.Unix())
+	pt.Equal(false, we.Bool)
+	pt.Equal([]byte("0.100000"), we.Bytes)
+	pt.True(we.BoolValue)
+}
+
+func (pt *postgresTest) TestCount() {
+	ds := pt.db.From("entry")
+	count, err := ds.Count()
+	pt.NoError(err)
+	pt.Equal(int64(10), count)
+	count, err = ds.Where(goqu.C("int").Gt(4)).Count()
+	pt.NoError(err)
+	pt.Equal(int64(5), count)
+	count, err = ds.Where(goqu.C("int").Gte(4)).Count()
+	pt.NoError(err)
+	pt.Equal(int64(6), count)
+	count, err = ds.Where(goqu.C("string").Like("0.1%")).Count()
+	pt.NoError(err)
+	pt.Equal(int64(1), count)
+	count, err = ds.Where(goqu.C("string").IsNull()).Count()
+	pt.NoError(err)
+	pt.Equal(int64(0), count)
+}
+
+func (pt *postgresTest) TestInsert() {
+	ds := pt.db.From("entry")
+	now := time.Now()
+	e := entry{Int: 10, Float: 1.000000, String: "1.000000", Time: now, Bool: true, Bytes: []byte("1.000000")}
+	_, err := ds.Insert().Rows(e).Executor().Exec()
+	pt.NoError(err)
+
+	var insertedEntry entry
+	found, err := ds.Where(goqu.C("int").Eq(10)).ScanStruct(&insertedEntry)
+	pt.NoError(err)
+	pt.True(found)
+	pt.True(insertedEntry.ID > 0)
+
+	entries := []entry{
+		{Int: 11, Float: 1.100000, String: "1.100000", Time: now, Bool: false, Bytes: []byte("1.100000")},
+		{Int: 12, Float: 1.200000, String: "1.200000", Time: now, Bool: true, Bytes: []byte("1.200000")},
+		{Int: 13, Float: 1.300000, String: "1.300000", Time: now, Bool: false, Bytes: []byte("1.300000")},
+		{Int: 14, Float: 1.400000, String: "1.400000", Time: now, Bool: true, Bytes: []byte("1.400000")},
+	}
+	_, err = ds.Insert().Rows(entries).Executor().Exec()
+	pt.NoError(err)
+
+	var newEntries []entry
+
+	pt.NoError(ds.Where(goqu.C("int").In([]uint32{11, 12, 13, 14})).ScanStructs(&newEntries))
+	pt.Len(newEntries, 4)
+	for i, e := range newEntries {
+		pt.Equal(entries[i].Int, e.Int)
+		pt.Equal(entries[i].Float, e.Float)
+		pt.Equal(entries[i].String, e.String)
+		pt.Equal(entries[i].Time.Unix(), e.Time.Unix())
+		pt.Equal(entries[i].Bool, e.Bool)
+		pt.Equal(entries[i].Bytes, e.Bytes)
+	}
+
+	_, err = ds.Insert().Rows(
+		entry{Int: 15, Float: 1.500000, String: "1.500000", Time: now, Bool: false, Bytes: []byte("1.500000")},
+		entry{Int: 16, Float: 1.600000, String: "1.600000", Time: now, Bool: true, Bytes: []byte("1.600000")},
+		entry{Int: 17, Float: 1.700000, String: "1.700000", Time: now, Bool: false, Bytes: []byte("1.700000")},
+		entry{Int: 18, Float: 1.800000, String: "1.800000", Time: now, Bool: true, Bytes: []byte("1.800000")},
+	).Executor().Exec()
+	pt.NoError(err)
+
+	newEntries = newEntries[0:0]
+	pt.NoError(ds.Where(goqu.C("int").In([]uint32{15, 16, 17, 18})).ScanStructs(&newEntries))
+	pt.Len(newEntries, 4)
+}
+
+func (pt *postgresTest) TestInsertReturning() {
+	ds := pt.db.From("entry")
+	now := time.Now()
+	e := entry{Int: 10, Float: 1.000000, String: "1.000000", Time: now, Bool: true, Bytes: []byte("1.000000")}
+	found, err := ds.Insert().Rows(e).Returning(goqu.Star()).Executor().ScanStruct(&e)
+	pt.NoError(err)
+	pt.True(found)
+	pt.True(e.ID > 0)
+
+	var ids []uint32
+	pt.NoError(ds.Insert().Rows([]entry{
+		{Int: 11, Float: 1.100000, String: "1.100000", Time: now, Bool: false, Bytes: []byte("1.100000")},
+		{Int: 12, Float: 1.200000, String: "1.200000", Time: now, Bool: true, Bytes: []byte("1.200000")},
+		{Int: 13, Float: 1.300000, String: "1.300000", Time: now, Bool: false, Bytes: []byte("1.300000")},
+		{Int: 14, Float: 1.400000, String: "1.400000", Time: now, Bool: true, Bytes: []byte("1.400000")},
+	}).Returning("id").Executor().ScanVals(&ids))
+	pt.Len(ids, 4)
+	for _, id := range ids {
+		pt.True(id > 0)
+	}
+
+	var ints []int64
+	pt.NoError(ds.Insert().Rows(
+		entry{Int: 15, Float: 1.500000, String: "1.500000", Time: now, Bool: false, Bytes: []byte("1.500000")},
+		entry{Int: 16, Float: 1.600000, String: "1.600000", Time: now, Bool: true, Bytes: []byte("1.600000")},
+		entry{Int: 17, Float: 1.700000, String: "1.700000", Time: now, Bool: false, Bytes: []byte("1.700000")},
+		entry{Int: 18, Float: 1.800000, String: "1.800000", Time: now, Bool: true, Bytes: []byte("1.800000")},
+	).Returning("int").Executor().ScanVals(&ints))
+	pt.True(found)
+	pt.Equal(ints, []int64{15, 16, 17, 18})
+}
+
+func (pt *postgresTest) TestUpdate() {
+	ds := pt.db.From("entry")
+	var e entry
+	found, err := ds.Where(goqu.C("int").Eq(9)).Select("id").ScanStruct(&e)
+	pt.NoError(err)
+	pt.True(found)
+	e.Int = 11
+	_, err = ds.Where(goqu.C("id").Eq(e.ID)).Update().Set(e).Executor().Exec()
+	pt.NoError(err)
+
+	count, err := ds.Where(goqu.C("int").Eq(11)).Count()
+	pt.NoError(err)
+	pt.Equal(int64(1), count)
+
+	var id uint32
+	found, err = ds.Where(goqu.C("int").Eq(11)).
+		Update().
+		Set(goqu.Record{"int": 9}).
+		Returning("id").Executor().ScanVal(&id)
+	pt.NoError(err)
+	pt.True(found)
+	pt.Equal(id, e.ID)
+}
+
+func (pt *postgresTest) TestUpdateSQL_multipleTables() {
+	ds := pt.db.Update("test")
+	updateSQL, _, err := ds.
+		Set(goqu.Record{"foo": "bar"}).
+		From("test_2").
+		Where(goqu.I("test.id").Eq(goqu.I("test_2.test_id"))).
+		ToSQL()
+	pt.NoError(err)
+	pt.Equal(`UPDATE "test" SET "foo"='bar' FROM "test_2" WHERE ("test"."id" = "test_2"."test_id")`, updateSQL)
+}
+
+func (pt *postgresTest) TestDelete() {
+	ds := pt.db.From("entry")
+	var e entry
+	found, err := ds.Where(goqu.C("int").Eq(9)).Select("id").ScanStruct(&e)
+	pt.NoError(err)
+	pt.True(found)
+	_, err = ds.Where(goqu.C("id").Eq(e.ID)).Delete().Executor().Exec()
+	pt.NoError(err)
+
+	count, err := ds.Count()
+	pt.NoError(err)
+	pt.Equal(int64(9), count)
+
+	var id uint32
+	found, err = ds.Where(goqu.C("id").Eq(e.ID)).ScanVal(&id)
+	pt.NoError(err)
+	pt.False(found)
+
+	e = entry{}
+	found, err = ds.Where(goqu.C("int").Eq(8)).Select("id").ScanStruct(&e)
+	pt.NoError(err)
+	pt.True(found)
+	pt.NotEqual(e.ID, int64(0))
+
+	id = 0
+	_, err = ds.Where(goqu.C("id").Eq(e.ID)).Delete().Returning("id").Executor().ScanVal(&id)
+	pt.NoError(err)
+	pt.Equal(id, e.ID)
+}
+
+func (pt *postgresTest) TestInsert_OnConflict() {
+	ds := pt.db.From("entry")
+	now := time.Now()
+
+	// DO NOTHING insert
+	e := entry{Int: 10, Float: 1.100000, String: "1.100000", Time: now, Bool: false, Bytes: []byte("1.100000")}
+	_, err := ds.Insert().Rows(e).OnConflict(goqu.DoNothing()).Executor().Exec()
+	pt.NoError(err)
+
+	// DO NOTHING duplicate
+	e = entry{Int: 10, Float: 2.100000, String: "2.100000", Time: now.Add(time.Hour * 100), Bool: false, Bytes: []byte("2.100000")}
+	_, err = ds.Insert().Rows(e).OnConflict(goqu.DoNothing()).Executor().Exec()
+	pt.NoError(err)
+
+	// DO NOTHING update
+	var entryActual entry
+	e2 := entry{Int: 0, String: "2.000000"}
+	_, err = ds.Insert().
+		Rows(e2).
+		OnConflict(goqu.DoUpdate("int", goqu.Record{"string": "upsert"})).
+		Executor().Exec()
+	pt.NoError(err)
+	_, err = ds.Where(goqu.C("int").Eq(0)).ScanStruct(&entryActual)
+	pt.NoError(err)
+	pt.Equal("upsert", entryActual.String)
+
+	// DO NOTHING update where
+	entries := []entry{
+		{Int: 1, Float: 6.100000, String: "6.100000", Time: now, Bytes: []byte("6.100000")},
+		{Int: 2, Float: 7.200000, String: "7.200000", Time: now, Bytes: []byte("7.200000")},
+	}
+	_, err = ds.Insert().
+		Rows(entries).
+		OnConflict(goqu.DoUpdate("int", goqu.Record{"string": "upsert"}).Where(goqu.I("excluded.int").Eq(2))).
+		Executor().
+		Exec()
+	pt.NoError(err)
+
+	var entry8, entry9 entry
+	_, err = ds.Where(goqu.Ex{"int": 1}).ScanStruct(&entry8)
+	pt.NoError(err)
+	pt.Equal("0.100000", entry8.String)
+
+	_, err = ds.Where(goqu.Ex{"int": 2}).ScanStruct(&entry9)
+	pt.NoError(err)
+	pt.Equal("upsert", entry9.String)
+}
+
+func (pt *postgresTest) TestWindowFunction() {
+	ds := pt.db.From("entry").
+		Select("int", goqu.ROW_NUMBER().OverName(goqu.I("w")).As("id")).
+		Window(goqu.W("w").OrderBy(goqu.I("int").Desc()))
+
+	var entries []entry
+	pt.NoError(ds.ScanStructs(&entries))
+
+	pt.Equal([]entry{
+		{Int: 9, ID: 1},
+		{Int: 8, ID: 2},
+		{Int: 7, ID: 3},
+		{Int: 6, ID: 4},
+		{Int: 5, ID: 5},
+		{Int: 4, ID: 6},
+		{Int: 3, ID: 7},
+		{Int: 2, ID: 8},
+		{Int: 1, ID: 9},
+		{Int: 0, ID: 10},
+	}, entries)
+}
+
+func TestPostgresSuite(t *testing.T) {
+	suite.Run(t, new(postgresTest))
+}

+ 65 - 0
src/github.com/doug-martin/goqu/dialect/sqlite3/sqlite3.go

@@ -0,0 +1,65 @@
+package sqlite3
+
+import (
+	"github.com/doug-martin/goqu/v9"
+	"github.com/doug-martin/goqu/v9/exp"
+)
+
+func DialectOptions() *goqu.SQLDialectOptions {
+	opts := goqu.DefaultDialectOptions()
+
+	opts.SupportsReturn = false
+	opts.SupportsOrderByOnUpdate = true
+	opts.SupportsLimitOnUpdate = true
+	opts.SupportsOrderByOnDelete = true
+	opts.SupportsLimitOnDelete = true
+	opts.SupportsConflictUpdateWhere = false
+	opts.SupportsInsertIgnoreSyntax = true
+	opts.SupportsConflictTarget = false
+	opts.SupportsMultipleUpdateTables = false
+	opts.WrapCompoundsInParens = false
+	opts.SupportsDistinctOn = false
+	opts.SupportsWindowFunction = false
+	opts.SupportsLateral = false
+
+	opts.PlaceHolderFragment = []byte("?")
+	opts.IncludePlaceholderNum = false
+	opts.QuoteRune = '`'
+	opts.DefaultValuesFragment = []byte("")
+	opts.True = []byte("1")
+	opts.False = []byte("0")
+	opts.TimeFormat = "2006-01-02 15:04:05"
+	opts.BooleanOperatorLookup = map[exp.BooleanOperation][]byte{
+		exp.EqOp:             []byte("="),
+		exp.NeqOp:            []byte("!="),
+		exp.GtOp:             []byte(">"),
+		exp.GteOp:            []byte(">="),
+		exp.LtOp:             []byte("<"),
+		exp.LteOp:            []byte("<="),
+		exp.InOp:             []byte("IN"),
+		exp.NotInOp:          []byte("NOT IN"),
+		exp.IsOp:             []byte("IS"),
+		exp.IsNotOp:          []byte("IS NOT"),
+		exp.LikeOp:           []byte("LIKE"),
+		exp.NotLikeOp:        []byte("NOT LIKE"),
+		exp.ILikeOp:          []byte("LIKE"),
+		exp.NotILikeOp:       []byte("NOT LIKE"),
+		exp.RegexpLikeOp:     []byte("REGEXP"),
+		exp.RegexpNotLikeOp:  []byte("NOT REGEXP"),
+		exp.RegexpILikeOp:    []byte("REGEXP"),
+		exp.RegexpNotILikeOp: []byte("NOT REGEXP"),
+	}
+	opts.UseLiteralIsBools = false
+	opts.EscapedRunes = map[rune][]byte{
+		'\'': []byte("''"),
+	}
+	opts.InsertIgnoreClause = []byte("INSERT OR IGNORE")
+	opts.ConflictFragment = []byte("")
+	opts.ConflictDoUpdateFragment = []byte("")
+	opts.ConflictDoNothingFragment = []byte("")
+	return opts
+}
+
+func init() {
+	goqu.RegisterDialect("sqlite3", DialectOptions())
+}

+ 199 - 0
src/github.com/doug-martin/goqu/dialect/sqlite3/sqlite3_dialect_test.go

@@ -0,0 +1,199 @@
+package sqlite3
+
+import (
+	"regexp"
+	"testing"
+
+	"github.com/doug-martin/goqu/v9"
+
+	"github.com/stretchr/testify/suite"
+)
+
+type sqlite3DialectSuite struct {
+	suite.Suite
+}
+
+func (sds *sqlite3DialectSuite) GetDs(table string) *goqu.SelectDataset {
+	return goqu.Dialect("sqlite3").From(table)
+}
+
+func (sds *sqlite3DialectSuite) TestIdentifiers() {
+	ds := sds.GetDs("test")
+	sql, _, err := ds.Select(
+		"a",
+		goqu.I("a.b.c"),
+		goqu.I("c.d"),
+		goqu.C("test").As("test"),
+	).ToSQL()
+	sds.NoError(err)
+	sds.Equal("SELECT `a`, `a`.`b`.`c`, `c`.`d`, `test` AS `test` FROM `test`", sql)
+}
+
+func (sds *sqlite3DialectSuite) TestUpdateSQL_multipleTables() {
+	ds := sds.GetDs("test").Update()
+	_, _, err := ds.
+		Set(goqu.Record{"foo": "bar"}).
+		From("test_2").
+		Where(goqu.I("test.id").Eq(goqu.I("test_2.test_id"))).
+		ToSQL()
+	sds.EqualError(err, "goqu: sqlite3 dialect does not support multiple tables in UPDATE")
+}
+
+func (sds *sqlite3DialectSuite) TestCompoundExpressions() {
+	ds1 := sds.GetDs("test").Select("a")
+	ds2 := sds.GetDs("test2").Select("b")
+	sql, _, err := ds1.Union(ds2).ToSQL()
+	sds.NoError(err)
+	sds.Equal("SELECT `a` FROM `test` UNION SELECT `b` FROM `test2`", sql)
+
+	sql, _, err = ds1.UnionAll(ds2).ToSQL()
+	sds.NoError(err)
+	sds.Equal("SELECT `a` FROM `test` UNION ALL SELECT `b` FROM `test2`", sql)
+
+	sql, _, err = ds1.Intersect(ds2).ToSQL()
+	sds.NoError(err)
+	sds.Equal("SELECT `a` FROM `test` INTERSECT SELECT `b` FROM `test2`", sql)
+}
+
+func (sds *sqlite3DialectSuite) TestLiteralString() {
+	ds := sds.GetDs("test")
+	sql, _, err := ds.Where(goqu.C("a").Eq("test")).ToSQL()
+	sds.NoError(err)
+	sds.Equal("SELECT * FROM `test` WHERE (`a` = 'test')", sql)
+
+	sql, _, err = ds.Where(goqu.C("a").Eq("test'test")).ToSQL()
+	sds.NoError(err)
+	sds.Equal("SELECT * FROM `test` WHERE (`a` = 'test''test')", sql)
+
+	sql, _, err = ds.Where(goqu.C("a").Eq(`test"test`)).ToSQL()
+	sds.NoError(err)
+	sds.Equal("SELECT * FROM `test` WHERE (`a` = 'test\"test')", sql)
+
+	sql, _, err = ds.Where(goqu.C("a").Eq(`test\test`)).ToSQL()
+	sds.NoError(err)
+	sds.Equal("SELECT * FROM `test` WHERE (`a` = 'test\\test')", sql)
+
+	sql, _, err = ds.Where(goqu.C("a").Eq("test\ntest")).ToSQL()
+	sds.NoError(err)
+	sds.Equal("SELECT * FROM `test` WHERE (`a` = 'test\ntest')", sql)
+
+	sql, _, err = ds.Where(goqu.C("a").Eq("test\rtest")).ToSQL()
+	sds.NoError(err)
+	sds.Equal("SELECT * FROM `test` WHERE (`a` = 'test\rtest')", sql)
+
+	sql, _, err = ds.Where(goqu.C("a").Eq("test\x00test")).ToSQL()
+	sds.NoError(err)
+	sds.Equal("SELECT * FROM `test` WHERE (`a` = 'test\x00test')", sql)
+
+	sql, _, err = ds.Where(goqu.C("a").Eq("test\x1atest")).ToSQL()
+	sds.NoError(err)
+	sds.Equal("SELECT * FROM `test` WHERE (`a` = 'test\x1atest')", sql)
+}
+
+func (sds *sqlite3DialectSuite) TestLiteralBytes() {
+	ds := sds.GetDs("test")
+	sql, _, err := ds.Where(goqu.C("a").Eq([]byte("test"))).ToSQL()
+	sds.NoError(err)
+	sds.Equal("SELECT * FROM `test` WHERE (`a` = 'test')", sql)
+
+	sql, _, err = ds.Where(goqu.C("a").Eq([]byte("test'test"))).ToSQL()
+	sds.NoError(err)
+	sds.Equal("SELECT * FROM `test` WHERE (`a` = 'test''test')", sql)
+
+	sql, _, err = ds.Where(goqu.C("a").Eq([]byte(`test"test`))).ToSQL()
+	sds.NoError(err)
+	sds.Equal("SELECT * FROM `test` WHERE (`a` = 'test\"test')", sql)
+
+	sql, _, err = ds.Where(goqu.C("a").Eq([]byte(`test\test`))).ToSQL()
+	sds.NoError(err)
+	sds.Equal("SELECT * FROM `test` WHERE (`a` = 'test\\test')", sql)
+
+	sql, _, err = ds.Where(goqu.C("a").Eq([]byte("test\ntest"))).ToSQL()
+	sds.NoError(err)
+	sds.Equal("SELECT * FROM `test` WHERE (`a` = 'test\ntest')", sql)
+
+	sql, _, err = ds.Where(goqu.C("a").Eq([]byte("test\rtest"))).ToSQL()
+	sds.NoError(err)
+	sds.Equal("SELECT * FROM `test` WHERE (`a` = 'test\rtest')", sql)
+
+	sql, _, err = ds.Where(goqu.C("a").Eq([]byte("test\x00test"))).ToSQL()
+	sds.NoError(err)
+	sds.Equal("SELECT * FROM `test` WHERE (`a` = 'test\x00test')", sql)
+
+	sql, _, err = ds.Where(goqu.C("a").Eq([]byte("test\x1atest"))).ToSQL()
+	sds.NoError(err)
+	sds.Equal("SELECT * FROM `test` WHERE (`a` = 'test\x1atest')", sql)
+}
+
+func (sds *sqlite3DialectSuite) TestBooleanOperations() {
+	ds := sds.GetDs("test")
+	sql, _, err := ds.Where(goqu.C("a").Eq(true)).ToSQL()
+	sds.NoError(err)
+	sds.Equal("SELECT * FROM `test` WHERE (`a` IS 1)", sql)
+	sql, _, err = ds.Where(goqu.C("a").Eq(false)).ToSQL()
+	sds.NoError(err)
+	sds.Equal("SELECT * FROM `test` WHERE (`a` IS 0)", sql)
+	sql, _, err = ds.Where(goqu.C("a").Is(true)).ToSQL()
+	sds.NoError(err)
+	sds.Equal("SELECT * FROM `test` WHERE (`a` IS 1)", sql)
+	sql, _, err = ds.Where(goqu.C("a").Is(false)).ToSQL()
+	sds.NoError(err)
+	sds.Equal("SELECT * FROM `test` WHERE (`a` IS 0)", sql)
+	sql, _, err = ds.Where(goqu.C("a").IsTrue()).ToSQL()
+	sds.NoError(err)
+	sds.Equal("SELECT * FROM `test` WHERE (`a` IS 1)", sql)
+	sql, _, err = ds.Where(goqu.C("a").IsFalse()).ToSQL()
+	sds.NoError(err)
+	sds.Equal("SELECT * FROM `test` WHERE (`a` IS 0)", sql)
+
+	sql, _, err = ds.Where(goqu.C("a").Neq(true)).ToSQL()
+	sds.NoError(err)
+	sds.Equal("SELECT * FROM `test` WHERE (`a` IS NOT 1)", sql)
+	sql, _, err = ds.Where(goqu.C("a").Neq(false)).ToSQL()
+	sds.NoError(err)
+	sds.Equal("SELECT * FROM `test` WHERE (`a` IS NOT 0)", sql)
+	sql, _, err = ds.Where(goqu.C("a").IsNot(true)).ToSQL()
+	sds.NoError(err)
+	sds.Equal("SELECT * FROM `test` WHERE (`a` IS NOT 1)", sql)
+	sql, _, err = ds.Where(goqu.C("a").IsNot(false)).ToSQL()
+	sds.NoError(err)
+	sds.Equal("SELECT * FROM `test` WHERE (`a` IS NOT 0)", sql)
+	sql, _, err = ds.Where(goqu.C("a").IsNotTrue()).ToSQL()
+	sds.NoError(err)
+	sds.Equal("SELECT * FROM `test` WHERE (`a` IS NOT 1)", sql)
+	sql, _, err = ds.Where(goqu.C("a").IsNotFalse()).ToSQL()
+	sds.NoError(err)
+	sds.Equal("SELECT * FROM `test` WHERE (`a` IS NOT 0)", sql)
+
+	sql, _, err = ds.Where(goqu.C("a").Like("a%")).ToSQL()
+	sds.NoError(err)
+	sds.Equal("SELECT * FROM `test` WHERE (`a` LIKE 'a%')", sql)
+
+	sql, _, err = ds.Where(goqu.C("a").NotLike("a%")).ToSQL()
+	sds.NoError(err)
+	sds.Equal("SELECT * FROM `test` WHERE (`a` NOT LIKE 'a%')", sql)
+
+	sql, _, err = ds.Where(goqu.C("a").ILike("a%")).ToSQL()
+	sds.NoError(err)
+	sds.Equal("SELECT * FROM `test` WHERE (`a` LIKE 'a%')", sql)
+	sql, _, err = ds.Where(goqu.C("a").NotILike("a%")).ToSQL()
+	sds.NoError(err)
+	sds.Equal("SELECT * FROM `test` WHERE (`a` NOT LIKE 'a%')", sql)
+
+	sql, _, err = ds.Where(goqu.C("a").Like(regexp.MustCompile("(a|b)"))).ToSQL()
+	sds.NoError(err)
+	sds.Equal("SELECT * FROM `test` WHERE (`a` REGEXP '(a|b)')", sql)
+	sql, _, err = ds.Where(goqu.C("a").NotLike(regexp.MustCompile("(a|b)"))).ToSQL()
+	sds.NoError(err)
+	sds.Equal("SELECT * FROM `test` WHERE (`a` NOT REGEXP '(a|b)')", sql)
+	sql, _, err = ds.Where(goqu.C("a").ILike(regexp.MustCompile("(a|b)"))).ToSQL()
+	sds.NoError(err)
+	sds.Equal("SELECT * FROM `test` WHERE (`a` REGEXP '(a|b)')", sql)
+	sql, _, err = ds.Where(goqu.C("a").NotILike(regexp.MustCompile("(a|b)"))).ToSQL()
+	sds.NoError(err)
+	sds.Equal("SELECT * FROM `test` WHERE (`a` NOT REGEXP '(a|b)')", sql)
+}
+
+func TestDatasetAdapterSuite(t *testing.T) {
+	suite.Run(t, new(sqlite3DialectSuite))
+}

+ 461 - 0
src/github.com/doug-martin/goqu/dialect/sqlite3/sqlite3_test.go

@@ -0,0 +1,461 @@
+package sqlite3
+
+import (
+	"database/sql"
+	"fmt"
+	"testing"
+	"time"
+
+	"github.com/doug-martin/goqu/v9"
+	"github.com/doug-martin/goqu/v9/dialect/mysql"
+	_ "github.com/mattn/go-sqlite3"
+
+	"github.com/stretchr/testify/suite"
+)
+
+const (
+	dropTable   = "DROP TABLE IF EXISTS `entry`;"
+	createTable = "CREATE  TABLE `entry` (" +
+		"`id` INTEGER PRIMARY KEY," +
+		"`int` INT NOT NULL ," +
+		"`float` FLOAT NOT NULL ," +
+		"`string` VARCHAR(255) NOT NULL ," +
+		"`time` DATETIME NOT NULL ," +
+		"`bool` TINYINT NOT NULL ," +
+		"`bytes` BLOB NOT NULL" +
+		");"
+	insertDefaultReords = "INSERT INTO `entry` (`int`, `float`, `string`, `time`, `bool`, `bytes`) VALUES" +
+		"(0, 0.000000, '0.000000', '2015-02-22 18:19:55', 1,  '0.000000')," +
+		"(1, 0.100000, '0.100000', '2015-02-22 19:19:55', 0, '0.100000')," +
+		"(2, 0.200000, '0.200000', '2015-02-22 20:19:55', 1,  '0.200000')," +
+		"(3, 0.300000, '0.300000', '2015-02-22 21:19:55', 0, '0.300000')," +
+		"(4, 0.400000, '0.400000', '2015-02-22 22:19:55', 1,  '0.400000')," +
+		"(5, 0.500000, '0.500000', '2015-02-22 23:19:55', 0, '0.500000')," +
+		"(6, 0.600000, '0.600000', '2015-02-23 00:19:55', 1,  '0.600000')," +
+		"(7, 0.700000, '0.700000', '2015-02-23 01:19:55', 0, '0.700000')," +
+		"(8, 0.800000, '0.800000', '2015-02-23 02:19:55', 1,  '0.800000')," +
+		"(9, 0.900000, '0.900000', '2015-02-23 03:19:55', 0, '0.900000');"
+)
+
+var dbURI = ":memory:"
+
+type (
+	sqlite3Suite struct {
+		suite.Suite
+		db *goqu.Database
+	}
+	entry struct {
+		ID     uint32    `db:"id" goqu:"skipinsert,skipupdate"`
+		Int    int       `db:"int"`
+		Float  float64   `db:"float"`
+		String string    `db:"string"`
+		Time   time.Time `db:"time"`
+		Bool   bool      `db:"bool"`
+		Bytes  []byte    `db:"bytes"`
+	}
+)
+
+func (st *sqlite3Suite) SetupSuite() {
+	fmt.Println(dbURI)
+	db, err := sql.Open("sqlite3", dbURI)
+	if err != nil {
+		panic(err.Error())
+	}
+	st.db = goqu.New("sqlite3", db)
+}
+
+func (st *sqlite3Suite) SetupTest() {
+	if _, err := st.db.Exec(dropTable); err != nil {
+		panic(err)
+	}
+	if _, err := st.db.Exec(createTable); err != nil {
+		panic(err)
+	}
+	if _, err := st.db.Exec(insertDefaultReords); err != nil {
+		panic(err)
+	}
+}
+
+func (st *sqlite3Suite) TestSelectSQL() {
+	ds := st.db.From("entry")
+	s, _, err := ds.Select("id", "float", "string", "time", "bool").ToSQL()
+	st.NoError(err)
+	st.Equal("SELECT `id`, `float`, `string`, `time`, `bool` FROM `entry`", s)
+
+	s, _, err = ds.Where(goqu.C("int").Eq(10)).ToSQL()
+	st.NoError(err)
+	st.Equal("SELECT * FROM `entry` WHERE (`int` = 10)", s)
+
+	s, args, err := ds.Prepared(true).Where(goqu.L("? = ?", goqu.C("int"), 10)).ToSQL()
+	st.NoError(err)
+	st.Equal([]interface{}{int64(10)}, args)
+	st.Equal("SELECT * FROM `entry` WHERE `int` = ?", s)
+}
+
+func (st *sqlite3Suite) TestCompoundQueries() {
+	ds1 := st.db.From("entry").Select("int").Where(goqu.C("int").Gt(0))
+	ds2 := st.db.From("entry").Select("int").Where(goqu.C("int").Gt(5))
+
+	var ids []int64
+	err := ds1.Union(ds2).ScanVals(&ids)
+	st.NoError(err)
+	st.Equal([]int64{1, 2, 3, 4, 5, 6, 7, 8, 9}, ids)
+
+	ids = ids[0:0]
+	err = ds1.UnionAll(ds2).ScanVals(&ids)
+	st.NoError(err)
+	st.Equal([]int64{1, 2, 3, 4, 5, 6, 7, 8, 9, 6, 7, 8, 9}, ids)
+
+	ids = ids[0:0]
+	err = ds1.Intersect(ds2).ScanVals(&ids)
+	st.NoError(err)
+	st.Equal([]int64{6, 7, 8, 9}, ids)
+}
+
+func (st *sqlite3Suite) TestQuery() {
+	var entries []entry
+	ds := st.db.From("entry")
+	st.NoError(ds.Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	st.Len(entries, 10)
+	floatVal := float64(0)
+	baseDate, err := time.Parse(DialectOptions().TimeFormat, "2015-02-22 18:19:55")
+	st.NoError(err)
+	for i, entry := range entries {
+		f := fmt.Sprintf("%f", floatVal)
+		st.Equal(uint32(i+1), entry.ID)
+		st.Equal(i, entry.Int)
+		st.Equal(f, fmt.Sprintf("%f", entry.Float))
+		st.Equal(f, entry.String)
+		st.Equal([]byte(f), entry.Bytes)
+		st.Equal(i%2 == 0, entry.Bool)
+		st.Equal(baseDate.Add(time.Duration(i)*time.Hour), entry.Time)
+		floatVal += float64(0.1)
+	}
+	entries = entries[0:0]
+	st.NoError(ds.Where(goqu.C("bool").IsTrue()).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	st.Len(entries, 5)
+	st.NoError(err)
+	for _, entry := range entries {
+		st.True(entry.Bool)
+	}
+
+	entries = entries[0:0]
+	st.NoError(ds.Where(goqu.C("int").Gt(4)).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	st.Len(entries, 5)
+	st.NoError(err)
+	for _, entry := range entries {
+		st.True(entry.Int > 4)
+	}
+
+	entries = entries[0:0]
+	st.NoError(ds.Where(goqu.C("int").Gte(5)).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	st.Len(entries, 5)
+	st.NoError(err)
+	for _, entry := range entries {
+		st.True(entry.Int >= 5)
+	}
+
+	entries = entries[0:0]
+	st.NoError(ds.Where(goqu.C("int").Lt(5)).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	st.Len(entries, 5)
+	st.NoError(err)
+	for _, entry := range entries {
+		st.True(entry.Int < 5)
+	}
+
+	entries = entries[0:0]
+	st.NoError(ds.Where(goqu.C("int").Lte(4)).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	st.Len(entries, 5)
+	st.NoError(err)
+	for _, entry := range entries {
+		st.True(entry.Int <= 4)
+	}
+
+	entries = entries[0:0]
+	st.NoError(ds.Where(goqu.C("int").Between(goqu.Range(3, 6))).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	st.Len(entries, 4)
+	st.NoError(err)
+	for _, entry := range entries {
+		st.True(entry.Int >= 3)
+		st.True(entry.Int <= 6)
+	}
+
+	entries = entries[0:0]
+	st.NoError(ds.Where(goqu.C("string").Eq("0.100000")).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	st.Len(entries, 1)
+	st.NoError(err)
+	for _, entry := range entries {
+		st.Equal(entry.String, "0.100000")
+	}
+
+	entries = entries[0:0]
+	st.NoError(ds.Where(goqu.C("string").Like("0.1%")).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	st.Len(entries, 1)
+	st.NoError(err)
+	for _, entry := range entries {
+		st.Equal("0.100000", entry.String)
+	}
+
+	entries = entries[0:0]
+	st.NoError(ds.Where(goqu.C("string").NotLike("0.1%")).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	st.Len(entries, 9)
+	st.NoError(err)
+	for _, entry := range entries {
+		st.NotEqual("0.100000", entry.String)
+	}
+
+	entries = entries[0:0]
+	st.NoError(ds.Where(goqu.C("string").IsNull()).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	st.Empty(entries)
+}
+
+func (st *sqlite3Suite) TestQuery_Prepared() {
+	var entries []entry
+	ds := st.db.From("entry").Prepared(true)
+	st.NoError(ds.Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	st.Len(entries, 10)
+	floatVal := float64(0)
+	baseDate, err := time.Parse(DialectOptions().TimeFormat, "2015-02-22 18:19:55")
+	st.NoError(err)
+	for i, entry := range entries {
+		f := fmt.Sprintf("%f", floatVal)
+		st.Equal(uint32(i+1), entry.ID)
+		st.Equal(i, entry.Int)
+		st.Equal(f, fmt.Sprintf("%f", entry.Float))
+		st.Equal(f, entry.String)
+		st.Equal([]byte(f), entry.Bytes)
+		st.Equal(i%2 == 0, entry.Bool)
+		st.Equal(baseDate.Add(time.Duration(i)*time.Hour), entry.Time)
+		floatVal += float64(0.1)
+	}
+	entries = entries[0:0]
+	st.NoError(ds.Where(goqu.C("bool").IsTrue()).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	st.Len(entries, 5)
+	st.NoError(err)
+	for _, entry := range entries {
+		st.True(entry.Bool)
+	}
+
+	entries = entries[0:0]
+	st.NoError(ds.Where(goqu.C("int").Gt(4)).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	st.Len(entries, 5)
+	st.NoError(err)
+	for _, entry := range entries {
+		st.True(entry.Int > 4)
+	}
+
+	entries = entries[0:0]
+	st.NoError(ds.Where(goqu.C("int").Gte(5)).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	st.Len(entries, 5)
+	st.NoError(err)
+	for _, entry := range entries {
+		st.True(entry.Int >= 5)
+	}
+
+	entries = entries[0:0]
+	st.NoError(ds.Where(goqu.C("int").Lt(5)).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	st.Len(entries, 5)
+	st.NoError(err)
+	for _, entry := range entries {
+		st.True(entry.Int < 5)
+	}
+
+	entries = entries[0:0]
+	st.NoError(ds.Where(goqu.C("int").Lte(4)).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	st.Len(entries, 5)
+	st.NoError(err)
+	for _, entry := range entries {
+		st.True(entry.Int <= 4)
+	}
+
+	entries = entries[0:0]
+	st.NoError(ds.Where(goqu.C("int").Between(goqu.Range(3, 6))).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	st.Len(entries, 4)
+	st.NoError(err)
+	for _, entry := range entries {
+		st.True(entry.Int >= 3)
+		st.True(entry.Int <= 6)
+	}
+
+	entries = entries[0:0]
+	st.NoError(ds.Where(goqu.C("string").Eq("0.100000")).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	st.Len(entries, 1)
+	st.NoError(err)
+	for _, entry := range entries {
+		st.Equal(entry.String, "0.100000")
+	}
+
+	entries = entries[0:0]
+	st.NoError(ds.Where(goqu.C("string").Like("0.1%")).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	st.Len(entries, 1)
+	st.NoError(err)
+	for _, entry := range entries {
+		st.Equal("0.100000", entry.String)
+	}
+
+	entries = entries[0:0]
+	st.NoError(ds.Where(goqu.C("string").NotLike("0.1%")).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	st.Len(entries, 9)
+	st.NoError(err)
+	for _, entry := range entries {
+		st.NotEqual("0.100000", entry.String)
+	}
+
+	entries = entries[0:0]
+	st.NoError(ds.Where(goqu.C("string").IsNull()).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	st.Empty(entries)
+}
+
+func (st *sqlite3Suite) TestQuery_ValueExpressions() {
+	type wrappedEntry struct {
+		entry
+		BoolValue bool `db:"bool_value"`
+	}
+	expectedDate, err := time.Parse("2006-01-02 15:04:05", "2015-02-22 19:19:55")
+	st.NoError(err)
+	ds := st.db.From("entry").Select(goqu.Star(), goqu.V(true).As("bool_value")).Where(goqu.Ex{"int": 1})
+	var we wrappedEntry
+	found, err := ds.ScanStruct(&we)
+	st.NoError(err)
+	st.True(found)
+	st.Equal(we, wrappedEntry{
+		entry{2, 1, 0.100000, "0.100000", expectedDate, false, []byte("0.100000")},
+		true,
+	})
+}
+
+func (st *sqlite3Suite) TestCount() {
+	ds := st.db.From("entry")
+	count, err := ds.Count()
+	st.NoError(err)
+	st.Equal(int64(10), count)
+	count, err = ds.Where(goqu.C("int").Gt(4)).Count()
+	st.NoError(err)
+	st.Equal(int64(5), count)
+	count, err = ds.Where(goqu.C("int").Gte(4)).Count()
+	st.NoError(err)
+	st.Equal(int64(6), count)
+	count, err = ds.Where(goqu.C("string").Like("0.1%")).Count()
+	st.NoError(err)
+	st.Equal(int64(1), count)
+	count, err = ds.Where(goqu.C("string").IsNull()).Count()
+	st.NoError(err)
+	st.Equal(int64(0), count)
+}
+
+func (st *sqlite3Suite) TestInsert() {
+	ds := st.db.From("entry")
+	now := time.Now()
+	e := entry{Int: 10, Float: 1.000000, String: "1.000000", Time: now, Bool: true, Bytes: []byte("1.000000")}
+	_, err := ds.Insert().Rows(e).Executor().Exec()
+	st.NoError(err)
+
+	var insertedEntry entry
+	found, err := ds.Where(goqu.C("int").Eq(10)).ScanStruct(&insertedEntry)
+	st.NoError(err)
+	st.True(found)
+	st.True(insertedEntry.ID > 0)
+
+	entries := []entry{
+		{Int: 11, Float: 1.100000, String: "1.100000", Time: now, Bool: false, Bytes: []byte("1.100000")},
+		{Int: 12, Float: 1.200000, String: "1.200000", Time: now, Bool: true, Bytes: []byte("1.200000")},
+		{Int: 13, Float: 1.300000, String: "1.300000", Time: now, Bool: false, Bytes: []byte("1.300000")},
+		{Int: 14, Float: 1.400000, String: "1.400000", Time: now, Bool: true, Bytes: []byte("1.400000")},
+		{Int: 14, Float: 1.400000, String: `abc'd"e"f\\gh\n\ri\x00`, Time: now, Bool: true, Bytes: []byte("1.400000")},
+	}
+	_, err = ds.Insert().Rows(entries).Executor().Exec()
+	st.NoError(err)
+
+	var newEntries []entry
+	st.NoError(ds.Where(goqu.C("int").In([]uint32{11, 12, 13, 14})).ScanStructs(&newEntries))
+	for i, e := range newEntries {
+		st.Equal(entries[i].Int, e.Int)
+		st.Equal(entries[i].Float, e.Float)
+		st.Equal(entries[i].String, e.String)
+		st.Equal(entries[i].Time.UTC().Format(mysql.DialectOptions().TimeFormat), e.Time.Format(mysql.DialectOptions().TimeFormat))
+		st.Equal(entries[i].Bool, e.Bool)
+		st.Equal(entries[i].Bytes, e.Bytes)
+	}
+
+	_, err = ds.Insert().Rows(
+		entry{Int: 15, Float: 1.500000, String: "1.500000", Time: now, Bool: false, Bytes: []byte("1.500000")},
+		entry{Int: 16, Float: 1.600000, String: "1.600000", Time: now, Bool: true, Bytes: []byte("1.600000")},
+		entry{Int: 17, Float: 1.700000, String: "1.700000", Time: now, Bool: false, Bytes: []byte("1.700000")},
+		entry{Int: 18, Float: 1.800000, String: "1.800000", Time: now, Bool: true, Bytes: []byte("1.800000")},
+		entry{Int: 18, Float: 1.800000, String: `abc'd"e"f\\gh\n\ri\x00`, Time: now, Bool: true, Bytes: []byte("1.800000")},
+	).Executor().Exec()
+	st.NoError(err)
+
+	newEntries = newEntries[0:0]
+	st.NoError(ds.Where(goqu.C("int").In([]uint32{15, 16, 17, 18})).ScanStructs(&newEntries))
+	st.Len(newEntries, 5)
+}
+
+func (st *sqlite3Suite) TestInsert_returning() {
+	ds := st.db.From("entry")
+	now := time.Now()
+	e := entry{Int: 10, Float: 1.000000, String: "1.000000", Time: now, Bool: true, Bytes: []byte("1.000000")}
+	_, err := ds.Insert().Rows(e).Returning(goqu.Star()).Executor().ScanStruct(&e)
+	st.Error(err)
+}
+
+func (st *sqlite3Suite) TestUpdate() {
+	ds := st.db.From("entry")
+	var e entry
+	found, err := ds.Where(goqu.C("int").Eq(9)).Select("id").ScanStruct(&e)
+	st.NoError(err)
+	st.True(found)
+	e.Int = 11
+	_, err = ds.Where(goqu.C("id").Eq(e.ID)).Update().Set(e).Executor().Exec()
+	st.NoError(err)
+
+	count, err := ds.Where(goqu.C("int").Eq(11)).Count()
+	st.NoError(err)
+	st.Equal(int64(1), count)
+}
+
+func (st *sqlite3Suite) TestUpdateReturning() {
+	ds := st.db.From("entry")
+	var id uint32
+	_, err := ds.
+		Where(goqu.C("int").Eq(11)).
+		Update().
+		Set(map[string]interface{}{"int": 9}).
+		Returning("id").
+		Executor().ScanVal(&id)
+	st.Error(err)
+	st.EqualError(err, "goqu: dialect does not support RETURNING clause [dialect=sqlite3]")
+}
+
+func (st *sqlite3Suite) TestDelete() {
+	ds := st.db.From("entry")
+	var e entry
+	found, err := ds.Where(goqu.C("int").Eq(9)).Select("id").ScanStruct(&e)
+	st.NoError(err)
+	st.True(found)
+	_, err = ds.Where(goqu.C("id").Eq(e.ID)).Delete().Executor().Exec()
+	st.NoError(err)
+
+	count, err := ds.Count()
+	st.NoError(err)
+	st.Equal(int64(9), count)
+
+	var id uint32
+	found, err = ds.Where(goqu.C("id").Eq(e.ID)).ScanVal(&id)
+	st.NoError(err)
+	st.False(found)
+
+	e = entry{}
+	found, err = ds.Where(goqu.C("int").Eq(8)).Select("id").ScanStruct(&e)
+	st.NoError(err)
+	st.True(found)
+	st.NotEqual(int64(0), e.ID)
+
+	id = 0
+	_, err = ds.Where(goqu.C("id").Eq(e.ID)).Delete().Returning("id").Executor().ScanVal(&id)
+	st.EqualError(err, "goqu: dialect does not support RETURNING clause [dialect=sqlite3]")
+}
+
+func TestSqlite3Suite(t *testing.T) {
+	suite.Run(t, new(sqlite3Suite))
+}

+ 90 - 0
src/github.com/doug-martin/goqu/dialect/sqlserver/sqlserver.go

@@ -0,0 +1,90 @@
+package sqlserver
+
+import (
+	"github.com/doug-martin/goqu/v9"
+	"github.com/doug-martin/goqu/v9/exp"
+	"github.com/doug-martin/goqu/v9/sqlgen"
+)
+
+func DialectOptions() *goqu.SQLDialectOptions {
+	opts := goqu.DefaultDialectOptions()
+
+	opts.UseLiteralIsBools = false
+
+	opts.SupportsReturn = false
+	opts.SupportsOrderByOnUpdate = false
+	opts.SupportsLimitOnUpdate = false
+	opts.SupportsLimitOnDelete = false
+	opts.SupportsOrderByOnDelete = true
+	opts.SupportsConflictUpdateWhere = false
+	opts.SupportsInsertIgnoreSyntax = false
+	opts.SupportsConflictTarget = false
+	opts.SupportsWithCTE = false
+	opts.SupportsWithCTERecursive = false
+	opts.SupportsDistinctOn = false
+	opts.SupportsWindowFunction = false
+
+	opts.PlaceHolderFragment = []byte("@p")
+	opts.LimitFragment = []byte(" TOP ")
+	opts.IncludePlaceholderNum = true
+	opts.DefaultValuesFragment = []byte("")
+	opts.True = []byte("1")
+	opts.False = []byte("0")
+	opts.TimeFormat = "2006-01-02 15:04:05"
+	opts.BooleanOperatorLookup = map[exp.BooleanOperation][]byte{
+		exp.EqOp:             []byte("="),
+		exp.NeqOp:            []byte("!="),
+		exp.GtOp:             []byte(">"),
+		exp.GteOp:            []byte(">="),
+		exp.LtOp:             []byte("<"),
+		exp.LteOp:            []byte("<="),
+		exp.InOp:             []byte("IN"),
+		exp.NotInOp:          []byte("NOT IN"),
+		exp.IsOp:             []byte("="),
+		exp.IsNotOp:          []byte("IS NOT"),
+		exp.LikeOp:           []byte("LIKE"),
+		exp.NotLikeOp:        []byte("NOT LIKE"),
+		exp.ILikeOp:          []byte("LIKE"),
+		exp.NotILikeOp:       []byte("NOT LIKE"),
+		exp.RegexpLikeOp:     []byte("REGEXP BINARY"),
+		exp.RegexpNotLikeOp:  []byte("NOT REGEXP BINARY"),
+		exp.RegexpILikeOp:    []byte("REGEXP"),
+		exp.RegexpNotILikeOp: []byte("NOT REGEXP"),
+	}
+
+	opts.FetchFragment = []byte(" FETCH FIRST ")
+
+	opts.SelectSQLOrder = []sqlgen.SQLFragmentType{
+		sqlgen.CommonTableSQLFragment,
+		sqlgen.SelectWithLimitSQLFragment,
+		sqlgen.FromSQLFragment,
+		sqlgen.JoinSQLFragment,
+		sqlgen.WhereSQLFragment,
+		sqlgen.GroupBySQLFragment,
+		sqlgen.HavingSQLFragment,
+		sqlgen.WindowSQLFragment,
+		sqlgen.CompoundsSQLFragment,
+		sqlgen.OrderWithOffsetFetchSQLFragment,
+		sqlgen.ForSQLFragment,
+	}
+
+	opts.EscapedRunes = map[rune][]byte{
+		'\'': []byte("\\'"),
+		'"':  []byte("\\\""),
+		'\\': []byte("\\\\"),
+		'\n': []byte("\\n"),
+		'\r': []byte("\\r"),
+		0:    []byte("\\x00"),
+		0x1a: []byte("\\x1a"),
+	}
+
+	opts.ConflictFragment = []byte("")
+	opts.ConflictDoUpdateFragment = []byte("")
+	opts.ConflictDoNothingFragment = []byte("")
+
+	return opts
+}
+
+func init() {
+	goqu.RegisterDialect("sqlserver", DialectOptions())
+}

+ 535 - 0
src/github.com/doug-martin/goqu/dialect/sqlserver/sqlserver_test.go

@@ -0,0 +1,535 @@
+package sqlserver_test
+
+import (
+	"database/sql"
+	"fmt"
+	"os"
+	"testing"
+	"time"
+
+	"github.com/doug-martin/goqu/v9/dialect/mysql"
+
+	_ "github.com/denisenkom/go-mssqldb"
+	"github.com/doug-martin/goqu/v9"
+	_ "github.com/doug-martin/goqu/v9/dialect/sqlserver"
+	"github.com/stretchr/testify/suite"
+)
+
+const (
+	dropTable   = "DROP TABLE IF EXISTS \"entry\";"
+	createTable = "CREATE  TABLE \"entry\" (" +
+		"\"id\" INT NOT NULL IDENTITY(1,1)," +
+		"\"int\" INT NOT NULL UNIQUE," +
+		"\"float\" FLOAT NOT NULL ," +
+		"\"string\" VARCHAR(255) NOT NULL ," +
+		"\"time\" DATETIME NOT NULL ," +
+		"\"bool\" BIT NOT NULL ," +
+		"\"bytes\" VARBINARY(100) NOT NULL ," +
+		"PRIMARY KEY (\"id\") );"
+	insertDefaultRecords = "INSERT INTO [entry] ([int], [float], [string], [time], [bool], [bytes]) VALUES" +
+		"(0, 0.000000, '0.000000', '2015-02-22 18:19:55', 1, CONVERT(BINARY(8), '0.000000'))," +
+		"(1, 0.100000, '0.100000', '2015-02-22 19:19:55', 0, CONVERT(BINARY(8), '0.100000'))," +
+		"(2, 0.200000, '0.200000', '2015-02-22 20:19:55', 1, CONVERT(BINARY(8), '0.200000'))," +
+		"(3, 0.300000, '0.300000', '2015-02-22 21:19:55', 0, CONVERT(BINARY(8), '0.300000'))," +
+		"(4, 0.400000, '0.400000', '2015-02-22 22:19:55', 1, CONVERT(BINARY(8), '0.400000'))," +
+		"(5, 0.500000, '0.500000', '2015-02-22 23:19:55', 0, CONVERT(BINARY(8), '0.500000'))," +
+		"(6, 0.600000, '0.600000', '2015-02-23 00:19:55', 1, CONVERT(BINARY(8), '0.600000'))," +
+		"(7, 0.700000, '0.700000', '2015-02-23 01:19:55', 0, CONVERT(BINARY(8), '0.700000'))," +
+		"(8, 0.800000, '0.800000', '2015-02-23 02:19:55', 1, CONVERT(BINARY(8), '0.800000'))," +
+		"(9, 0.900000, '0.900000', '2015-02-23 03:19:55', 0, CONVERT(BINARY(8), '0.900000'));"
+)
+
+const defaultDbURI = "sqlserver://sa:qwe123QWE@127.0.0.1:1433?database=master&connection+timeout=30"
+
+type (
+	sqlserverTest struct {
+		suite.Suite
+		db *goqu.Database
+	}
+	entry struct {
+		ID     uint32    `db:"id" goqu:"skipinsert,skipupdate"`
+		Int    int       `db:"int"`
+		Float  float64   `db:"float"`
+		String string    `db:"string"`
+		Time   time.Time `db:"time"`
+		Bool   bool      `db:"bool"`
+		Bytes  []byte    `db:"bytes"`
+	}
+)
+
+func (mt *sqlserverTest) SetupSuite() {
+	dbURI := os.Getenv("SQLSERVER_URI")
+	if dbURI == "" {
+		dbURI = defaultDbURI
+	}
+	db, err := sql.Open("sqlserver", dbURI)
+	if err != nil {
+		panic(err.Error())
+	}
+	mt.db = goqu.New("sqlserver", db)
+}
+
+func (mt *sqlserverTest) SetupTest() {
+	if _, err := mt.db.Exec(dropTable); err != nil {
+		panic(err)
+	}
+	if _, err := mt.db.Exec(createTable); err != nil {
+		panic(err)
+	}
+	if _, err := mt.db.Exec(insertDefaultRecords); err != nil {
+		panic(err)
+	}
+}
+
+func (mt *sqlserverTest) TestToSQL() {
+	ds := mt.db.From("entry")
+	s, _, err := ds.Select("id", "float", "string", "time", "bool").ToSQL()
+	mt.NoError(err)
+	mt.Equal("SELECT \"id\", \"float\", \"string\", \"time\", \"bool\" FROM \"entry\"", s)
+
+	s, _, err = ds.Where(goqu.C("int").Eq(10)).ToSQL()
+	mt.NoError(err)
+	mt.Equal("SELECT * FROM \"entry\" WHERE (\"int\" = 10)", s)
+
+	s, args, err := ds.Prepared(true).Where(goqu.L("? = ?", goqu.C("int"), 10)).ToSQL()
+	mt.NoError(err)
+	mt.Equal([]interface{}{int64(10)}, args)
+	mt.Equal("SELECT * FROM \"entry\" WHERE \"int\" = @p1", s)
+}
+
+func (mt *sqlserverTest) TestQuery() {
+	var entries []entry
+	ds := mt.db.From("entry")
+	mt.NoError(ds.Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	mt.Len(entries, 10)
+	floatVal := float64(0)
+	baseDate, err := time.Parse(
+		"2006-01-02 15:04:05",
+		"2015-02-22 18:19:55",
+	)
+	mt.NoError(err)
+	for i, entry := range entries {
+		f := fmt.Sprintf("%f", floatVal)
+		mt.Equal(uint32(i+1), entry.ID)
+		mt.Equal(i, entry.Int)
+		mt.Equal(f, fmt.Sprintf("%f", entry.Float))
+		mt.Equal(f, entry.String)
+		mt.Equal([]byte(f), entry.Bytes)
+		mt.Equal(i%2 == 0, entry.Bool)
+		mt.Equal(baseDate.Add(time.Duration(i)*time.Hour), entry.Time)
+		floatVal += float64(0.1)
+	}
+	entries = entries[0:0]
+
+	mt.NoError(ds.Where(goqu.C("bool").IsTrue()).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	mt.Len(entries, 5)
+	mt.NoError(err)
+	for _, entry := range entries {
+		mt.True(entry.Bool)
+	}
+
+	entries = entries[0:0]
+	mt.NoError(ds.Where(goqu.C("int").Gt(4)).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	mt.Len(entries, 5)
+	mt.NoError(err)
+	for _, entry := range entries {
+		mt.True(entry.Int > 4)
+	}
+
+	entries = entries[0:0]
+	mt.NoError(ds.Where(goqu.C("int").Gte(5)).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	mt.Len(entries, 5)
+	mt.NoError(err)
+	for _, entry := range entries {
+		mt.True(entry.Int >= 5)
+	}
+
+	entries = entries[0:0]
+	mt.NoError(ds.Where(goqu.C("int").Lt(5)).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	mt.Len(entries, 5)
+	mt.NoError(err)
+	for _, entry := range entries {
+		mt.True(entry.Int < 5)
+	}
+
+	entries = entries[0:0]
+	mt.NoError(ds.Where(goqu.C("int").Lte(4)).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	mt.Len(entries, 5)
+	mt.NoError(err)
+	for _, entry := range entries {
+		mt.True(entry.Int <= 4)
+	}
+
+	entries = entries[0:0]
+	mt.NoError(ds.Where(goqu.C("int").Between(goqu.Range(3, 6))).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	mt.Len(entries, 4)
+	mt.NoError(err)
+	for _, entry := range entries {
+		mt.True(entry.Int >= 3)
+		mt.True(entry.Int <= 6)
+	}
+
+	entries = entries[0:0]
+	mt.NoError(ds.Where(goqu.C("string").Eq("0.100000")).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	mt.Len(entries, 1)
+	mt.NoError(err)
+	for _, entry := range entries {
+		mt.Equal("0.100000", entry.String)
+	}
+
+	entries = entries[0:0]
+	mt.NoError(ds.Where(goqu.C("string").Like("0.1%")).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	mt.Len(entries, 1)
+	mt.NoError(err)
+	for _, entry := range entries {
+		mt.Equal("0.100000", entry.String)
+	}
+
+	entries = entries[0:0]
+	mt.NoError(ds.Where(goqu.C("string").NotLike("0.1%")).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	mt.Len(entries, 9)
+	mt.NoError(err)
+	for _, entry := range entries {
+		mt.NotEqual("0.100000", entry.String)
+	}
+
+	entries = entries[0:0]
+	mt.NoError(ds.Where(goqu.C("string").IsNull()).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	mt.Len(entries, 0)
+}
+
+func (mt *sqlserverTest) TestQuery_Prepared() {
+	var entries []entry
+	ds := mt.db.From("entry").Prepared(true)
+
+	mt.NoError(ds.Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	mt.Len(entries, 10)
+	floatVal := float64(0)
+	baseDate, err := time.Parse(
+		"2006-01-02 15:04:05",
+		"2015-02-22 18:19:55",
+	)
+	mt.NoError(err)
+	for i, entry := range entries {
+		f := fmt.Sprintf("%f", floatVal)
+		mt.Equal(uint32(i+1), entry.ID)
+		mt.Equal(i, entry.Int)
+		mt.Equal(f, fmt.Sprintf("%f", entry.Float))
+		mt.Equal(f, entry.String)
+		mt.Equal([]byte(f), entry.Bytes)
+		mt.Equal(i%2 == 0, entry.Bool)
+		mt.Equal(baseDate.Add(time.Duration(i)*time.Hour), entry.Time)
+		floatVal += float64(0.1)
+	}
+
+	entries = entries[0:0]
+	mt.NoError(ds.Where(goqu.C("bool").IsTrue()).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	mt.Len(entries, 5)
+	mt.NoError(err)
+	for _, entry := range entries {
+		mt.True(entry.Bool)
+	}
+
+	entries = entries[0:0]
+	mt.NoError(ds.Where(goqu.C("int").Gt(4)).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	mt.Len(entries, 5)
+	mt.NoError(err)
+	for _, entry := range entries {
+		mt.True(entry.Int > 4)
+	}
+
+	entries = entries[0:0]
+	mt.NoError(ds.Where(goqu.C("int").Gte(5)).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	mt.Len(entries, 5)
+	mt.NoError(err)
+	for _, entry := range entries {
+		mt.True(entry.Int >= 5)
+	}
+
+	entries = entries[0:0]
+	mt.NoError(ds.Where(goqu.C("int").Lt(5)).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	mt.Len(entries, 5)
+	mt.NoError(err)
+	for _, entry := range entries {
+		mt.True(entry.Int < 5)
+	}
+
+	entries = entries[0:0]
+	mt.NoError(ds.Where(goqu.C("int").Lte(4)).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	mt.Len(entries, 5)
+	mt.NoError(err)
+	for _, entry := range entries {
+		mt.True(entry.Int <= 4)
+	}
+
+	entries = entries[0:0]
+	mt.NoError(ds.Where(goqu.C("int").Between(goqu.Range(3, 6))).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	mt.Len(entries, 4)
+	mt.NoError(err)
+	for _, entry := range entries {
+		mt.True(entry.Int >= 3)
+		mt.True(entry.Int <= 6)
+	}
+
+	entries = entries[0:0]
+	mt.NoError(ds.Where(goqu.C("string").Eq("0.100000")).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	mt.Len(entries, 1)
+	mt.NoError(err)
+	for _, entry := range entries {
+		mt.Equal("0.100000", entry.String)
+	}
+
+	entries = entries[0:0]
+	mt.NoError(ds.Where(goqu.C("string").Like("0.1%")).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	mt.Len(entries, 1)
+	mt.NoError(err)
+	for _, entry := range entries {
+		mt.Equal("0.100000", entry.String)
+	}
+
+	entries = entries[0:0]
+	mt.NoError(ds.Where(goqu.C("string").NotLike("0.1%")).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	mt.Len(entries, 9)
+	mt.NoError(err)
+	for _, entry := range entries {
+		mt.NotEqual("0.100000", entry.String)
+	}
+
+	entries = entries[0:0]
+	mt.NoError(ds.Where(goqu.C("string").IsNull()).Order(goqu.C("id").Asc()).ScanStructs(&entries))
+	mt.Len(entries, 0)
+}
+
+func (mt *sqlserverTest) TestQuery_ValueExpressions() {
+	type wrappedEntry struct {
+		entry
+		BoolValue bool `db:"bool_value"`
+	}
+	expectedDate, err := time.Parse("2006-01-02 15:04:05", "2015-02-22 19:19:55")
+	mt.NoError(err)
+	ds := mt.db.From("entry").Select(goqu.Star(), goqu.V(true).As("bool_value")).Where(goqu.Ex{"int": 1})
+	var we wrappedEntry
+	found, err := ds.ScanStruct(&we)
+	mt.NoError(err)
+	mt.True(found)
+	mt.Equal(wrappedEntry{
+		entry{2, 1, 0.100000, "0.100000", expectedDate, false, []byte("0.100000")},
+		true,
+	}, we)
+}
+
+func (mt *sqlserverTest) TestCount() {
+	ds := mt.db.From("entry")
+	count, err := ds.Count()
+	mt.NoError(err)
+	mt.Equal(int64(10), count)
+	count, err = ds.Where(goqu.C("int").Gt(4)).Count()
+	mt.NoError(err)
+	mt.Equal(int64(5), count)
+	count, err = ds.Where(goqu.C("int").Gte(4)).Count()
+	mt.NoError(err)
+	mt.Equal(int64(6), count)
+	count, err = ds.Where(goqu.C("string").Like("0.1%")).Count()
+	mt.NoError(err)
+	mt.Equal(int64(1), count)
+	count, err = ds.Where(goqu.C("string").IsNull()).Count()
+	mt.NoError(err)
+	mt.Equal(int64(0), count)
+}
+
+func (mt *sqlserverTest) TestLimitOffset() {
+	ds := mt.db.From("entry").Where(goqu.C("id").Gte(1)).Limit(1)
+	var e entry
+	found, err := ds.ScanStruct(&e)
+	mt.NoError(err)
+	mt.True(found)
+	mt.Equal(uint32(1), e.ID)
+
+	ds = mt.db.From("entry").Where(goqu.C("id").Gte(1)).Order(goqu.C("id").Desc()).Limit(1)
+	found, err = ds.ScanStruct(&e)
+	mt.NoError(err)
+	mt.True(found)
+	mt.Equal(uint32(10), e.ID)
+
+	ds = mt.db.From("entry").Where(goqu.C("id").Gte(1)).Order(goqu.C("id").Asc()).Offset(1).Limit(1)
+	found, err = ds.ScanStruct(&e)
+	mt.NoError(err)
+	mt.True(found)
+	mt.Equal(uint32(2), e.ID)
+}
+
+func (mt *sqlserverTest) TestInsert() {
+	ds := mt.db.From("entry")
+	now := time.Now()
+	_, err := ds.Insert().Rows(goqu.Record{
+		"Int":    10,
+		"Float":  1.00000,
+		"String": "1.000000",
+		"Time":   now,
+		"Bool":   true,
+		"Bytes":  goqu.Cast(goqu.V([]byte("1.000000")), "BINARY(8)"),
+	}).Executor().Exec()
+	mt.NoError(err)
+
+	var insertedEntry entry
+	found, err := ds.Where(goqu.C("int").Eq(10)).ScanStruct(&insertedEntry)
+	mt.NoError(err)
+	mt.True(found)
+	mt.True(insertedEntry.ID > 0)
+
+	entries := []goqu.Record{
+		{
+			"Int": 11, "Float": 1.100000, "String": "1.100000", "Time": now,
+			"Bool": false, "Bytes": goqu.Cast(goqu.V([]byte("1.100000")), "BINARY(8)"),
+		},
+		{
+			"Int": 12, "Float": 1.200000, "String": "1.200000", "Time": now,
+			"Bool": true, "Bytes": goqu.Cast(goqu.V([]byte("1.200000")), "BINARY(8)"),
+		},
+		{
+			"Int": 13, "Float": 1.300000, "String": "1.300000", "Time": now,
+			"Bool": false, "Bytes": goqu.Cast(goqu.V([]byte("1.300000")), "BINARY(8)"),
+		},
+		{
+			"Int": 14, "Float": 1.400000, "String": "1.400000", "Time": now,
+			"Bool": true, "Bytes": goqu.Cast(goqu.V([]byte("1.400000")), "BINARY(8)"),
+		},
+	}
+	_, err = ds.Insert().Rows(entries).Executor().Exec()
+	mt.NoError(err)
+
+	var newEntries []entry
+	mt.NoError(ds.Where(goqu.C("int").In([]uint32{11, 12, 13, 14})).ScanStructs(&newEntries))
+	mt.Len(newEntries, 4)
+	for i, e := range newEntries {
+		mt.Equal(entries[i]["Int"], e.Int)
+		mt.Equal(entries[i]["Float"], e.Float)
+		mt.Equal(entries[i]["String"], e.String)
+		mt.Equal(
+			entries[i]["Time"].(time.Time).UTC().Format(mysql.DialectOptions().TimeFormat),
+			e.Time.Format(mysql.DialectOptions().TimeFormat),
+		)
+		mt.Equal(entries[i]["Bool"], e.Bool)
+		mt.Equal([]byte(entries[i]["String"].(string)), e.Bytes)
+	}
+
+	_, err = ds.Insert().Rows(
+		goqu.Record{
+			"Int": 15, "Float": 1.500000, "String": "1.500000", "Time": now,
+			"Bool": false, "Bytes": goqu.Cast(goqu.V([]byte("1.500000")), "BINARY(8)"),
+		},
+		goqu.Record{
+			"Int": 16, "Float": 1.600000, "String": "1.600000", "Time": now,
+			"Bool": true, "Bytes": goqu.Cast(goqu.V([]byte("1.600000")), "BINARY(8)"),
+		},
+		goqu.Record{
+			"Int": 17, "Float": 1.700000, "String": "1.700000", "Time": now,
+			"Bool": false, "Bytes": goqu.Cast(goqu.V([]byte("1.700000")), "BINARY(8)"),
+		},
+		goqu.Record{
+			"Int": 18, "Float": 1.800000, "String": "1.800000", "Time": now,
+			"Bool": true, "Bytes": goqu.Cast(goqu.V([]byte("1.800000")), "BINARY(8)"),
+		},
+	).Executor().Exec()
+	mt.NoError(err)
+
+	newEntries = newEntries[0:0]
+	mt.NoError(ds.Where(goqu.C("int").In([]uint32{15, 16, 17, 18})).ScanStructs(&newEntries))
+	mt.Len(newEntries, 4)
+}
+
+func (mt *sqlserverTest) TestInsertReturningProducesError() {
+	ds := mt.db.From("entry")
+	now := time.Now()
+	e := entry{Int: 10, Float: 1.000000, String: "1.000000", Time: now, Bool: true, Bytes: []byte("1.000000")}
+	_, err := ds.Insert().Rows(e).Returning(goqu.Star()).Executor().ScanStruct(&e)
+	mt.Error(err)
+}
+
+func (mt *sqlserverTest) TestUpdate() {
+	ds := mt.db.From("entry")
+	var e entry
+	found, err := ds.Where(goqu.C("int").Eq(9)).Select("id").ScanStruct(&e)
+	mt.NoError(err)
+	mt.True(found)
+	e.Int = 11
+	_, err = ds.Where(goqu.C("id").Eq(e.ID)).Update().Set(goqu.Record{"Int": e.Int}).Executor().Exec()
+	mt.NoError(err)
+
+	count, err := ds.Where(goqu.C("int").Eq(11)).Count()
+	mt.NoError(err)
+	mt.Equal(int64(1), count)
+}
+
+func (mt *sqlserverTest) TestUpdateReturning() {
+	ds := mt.db.From("entry")
+	var id uint32
+	_, err := ds.Where(goqu.C("int").Eq(11)).
+		Update().
+		Set(goqu.Record{"int": 9}).
+		Returning("id").
+		Executor().ScanVal(&id)
+	mt.Error(err)
+	mt.EqualError(err, "goqu: dialect does not support RETURNING clause [dialect=sqlserver]")
+}
+
+func (mt *sqlserverTest) TestDelete() {
+	ds := mt.db.From("entry")
+	var e entry
+	found, err := ds.Where(goqu.C("int").Eq(9)).Select("id").ScanStruct(&e)
+	mt.NoError(err)
+	mt.True(found)
+	_, err = ds.Where(goqu.C("id").Eq(e.ID)).Delete().Executor().Exec()
+	mt.NoError(err)
+
+	count, err := ds.Count()
+	mt.NoError(err)
+	mt.Equal(int64(9), count)
+
+	var id uint32
+	found, err = ds.Where(goqu.C("id").Eq(e.ID)).ScanVal(&id)
+	mt.NoError(err)
+	mt.False(found)
+
+	e = entry{}
+	found, err = ds.Where(goqu.C("int").Eq(8)).Select("id").ScanStruct(&e)
+	mt.NoError(err)
+	mt.True(found)
+	mt.NotEqual(0, e.ID)
+
+	id = 0
+	_, err = ds.Where(goqu.C("id").Eq(e.ID)).Delete().Returning("id").Executor().ScanVal(&id)
+	mt.EqualError(err, "goqu: dialect does not support RETURNING clause [dialect=sqlserver]")
+}
+
+func (mt *sqlserverTest) TestInsertIgnoreNotSupported() {
+	ds := mt.db.From("entry")
+	now := time.Now()
+
+	// insert one
+	entries := []goqu.Record{
+		{
+			"Int": 8, "Float": 6.100000, "String": "6.100000", "Bool": false, "Time": now,
+			"Bytes": goqu.Cast(goqu.V([]byte("6.100000")), "BINARY(8)"),
+		},
+		{
+			"Int": 9, "Float": 7.200000, "String": "7.200000", "Bool": false, "Time": now,
+			"Bytes": goqu.Cast(goqu.V([]byte("7.200000")), "BINARY(8)"),
+		},
+		{
+			"Int": 10, "Float": 7.200000, "String": "7.200000", "Bool": false, "Time": now,
+			"Bytes": goqu.Cast(goqu.V([]byte("7.200000")), "BINARY(8)"),
+		},
+	}
+	_, err := ds.Insert().Rows(entries).OnConflict(goqu.DoNothing()).Executor().Exec()
+	mt.Error(err)
+	mt.Contains(err.Error(), "Cannot insert duplicate key in object 'dbo.entry'. The duplicate key value is (8)")
+
+	count, err := ds.Count()
+	mt.NoError(err)
+	mt.Equal(count, int64(10))
+}
+
+func TestSqlServerSuite(t *testing.T) {
+	suite.Run(t, new(sqlserverTest))
+}

+ 62 - 0
src/github.com/doug-martin/goqu/docker-compose.yml

@@ -0,0 +1,62 @@
+version: "2"
+
+services:
+  postgres:
+    image: "postgres:${POSTGRES_VERSION}"
+    environment:
+      - "POSTGRES_USER=postgres"
+      - "POSTGRES_DB=goqupostgres"
+      - "POSTGRES_HOST_AUTH_METHOD=trust"
+    expose:
+      - "5432"
+    ports:
+      - "5432:5432"
+
+  mysql:
+    image: "mysql:${MYSQL_VERSION}"
+    environment:
+      - "MYSQL_DATABASE=goqumysql"
+      - "MYSQL_ALLOW_EMPTY_PASSWORD=yes"
+    expose:
+      - "3306"
+    ports:
+      - "3306:3306"
+
+  sqlserver:
+    image: "mcr.microsoft.com/mssql/server:${SQLSERVER_VERSION}"
+    environment:
+      - "ACCEPT_EULA=Y"
+      - "SA_PASSWORD=qwe123QWE"
+    expose:
+      - "1433"
+    ports:
+      - "1433:1433"
+
+  goqu:
+    image: "golang:${GO_VERSION}"
+    command: ["./wait-for-it.sh", "postgres:5432", "--", "./wait-for-it.sh", "mysql:3306", "--", "go test -v -race ./..."]
+    working_dir: /go/src/github.com/doug-martin/goqu
+    volumes:
+      - "./:/go/src/github.com/doug-martin/goqu"
+    environment:
+      MYSQL_URI: 'root@tcp(mysql:3306)/goqumysql?parseTime=true'
+      PG_URI: 'postgres://postgres:@postgres:5432/goqupostgres?sslmode=disable'
+      SQLSERVER_URI: 'sqlserver://sa:qwe123QWE@sqlserver:1433?database=master&connection+timeout=30'
+    depends_on:
+      - postgres
+      - mysql
+      - sqlserver
+  goqu-coverage:
+    image: "golang:${GO_VERSION}"
+    command: ["./wait-for-it.sh", "postgres:5432", "--", "./wait-for-it.sh", "mysql:3306", "--", "./go.test.sh"]
+    working_dir: /go/src/github.com/doug-martin/goqu
+    volumes:
+      - "./:/go/src/github.com/doug-martin/goqu"
+    environment:
+      MYSQL_URI: 'root@tcp(mysql:3306)/goqumysql?parseTime=true'
+      PG_URI: 'postgres://postgres:@postgres:5432/goqupostgres?sslmode=disable'
+      SQLSERVER_URI: 'sqlserver://sa:qwe123QWE@sqlserver:1433?database=master&connection+timeout=30'
+    depends_on:
+      - postgres
+      - mysql
+      - sqlserver

+ 77 - 0
src/github.com/doug-martin/goqu/docs/database.md

@@ -0,0 +1,77 @@
+<a name="database"></a>
+### Database
+
+The Database also allows you to execute queries but expects raw SQL to execute. The supported methods are
+
+* [`Exec`](http://godoc.org/github.com/doug-martin/goqu#Database.Exec)
+* [`Prepare`](http://godoc.org/github.com/doug-martin/goqu#Database.Prepare)
+* [`Query`](http://godoc.org/github.com/doug-martin/goqu#Database.Query)
+* [`QueryRow`](http://godoc.org/github.com/doug-martin/goqu#Database.QueryRow)
+* [`ScanStructs`](http://godoc.org/github.com/doug-martin/goqu#Database.ScanStructs)
+* [`ScanStruct`](http://godoc.org/github.com/doug-martin/goqu#Database.ScanStruct)
+* [`ScanVals`](http://godoc.org/github.com/doug-martin/goqu#Database.ScanVals)
+* [`ScanVal`](http://godoc.org/github.com/doug-martin/goqu#Database.ScanVal)
+* [`Begin`](http://godoc.org/github.com/doug-martin/goqu#Database.Begin)
+
+<a name="transactions"></a>
+### Transactions
+
+`goqu` has builtin support for transactions to make the use of the Datasets and querying seamless
+
+```go
+tx, err := db.Begin()
+if err != nil{
+   return err
+}
+//use tx.From to get a dataset that will execute within this transaction
+update := tx.From("user").
+    Where(goqu.Ex("password": nil}).
+    Update(goqu.Record{"status": "inactive"})
+if _, err = update.Exec(); err != nil{
+    if rErr := tx.Rollback(); rErr != nil{
+        return rErr
+    }
+    return err
+}
+if err = tx.Commit(); err != nil{
+    return err
+}
+return
+```
+
+The [`TxDatabase`](http://godoc.org/github.com/doug-martin/goqu/#TxDatabase)  also has all methods that the [`Database`](http://godoc.org/github.com/doug-martin/goqu/#Database) has along with
+
+* [`Commit`](http://godoc.org/github.com/doug-martin/goqu#TxDatabase.Commit)
+* [`Rollback`](http://godoc.org/github.com/doug-martin/goqu#TxDatabase.Rollback)
+* [`Wrap`](http://godoc.org/github.com/doug-martin/goqu#TxDatabase.Wrap)
+
+#### Wrap
+
+The [`TxDatabase.Wrap`](http://godoc.org/github.com/doug-martin/goqu/#TxDatabase.Wrap) is a convience method for automatically handling `COMMIT` and `ROLLBACK`
+
+```go
+tx, err := db.Begin()
+if err != nil{
+   return err
+}
+err = tx.Wrap(func() error{
+  update := tx.From("user").
+      Where(goqu.Ex("password": nil}).
+      Update(goqu.Record{"status": "inactive"})
+  return update.Exec()
+})
+//err will be the original error from the update statement, unless there was an error executing ROLLBACK
+if err != nil{
+    return err
+}
+```
+
+<a name="logging"></a>
+## Logging
+
+To enable trace logging of SQL statements use the [`Database.Logger`](http://godoc.org/github.com/doug-martin/goqu/#Database.Logger) method to set your logger.
+
+**NOTE** The logger must implement the [`Logger`](http://godoc.org/github.com/doug-martin/goqu/#Logger) interface
+
+**NOTE** If you start a transaction using a database your set a logger on the transaction will inherit that logger automatically
+

+ 314 - 0
src/github.com/doug-martin/goqu/docs/deleting.md

@@ -0,0 +1,314 @@
+# Deleting
+
+* [Creating A DeleteDataset](#create)
+* Examples
+  * [Delete All](#delete-all)
+  * [Prepared](#prepared)
+  * [Where](#where)
+  * [Order](#order)
+  * [Limit](#limit)
+  * [Returning](#returning)
+  * [SetError](#seterror)
+  * [Executing](#exec)
+
+<a name="create"></a>
+To create a [`DeleteDataset`](https://godoc.org/github.com/doug-martin/goqu/#DeleteDataset)  you can use
+
+**[`goqu.Delete`](https://godoc.org/github.com/doug-martin/goqu/#Delete)**
+
+When you just want to create some quick SQL, this mostly follows the `Postgres` with the exception of placeholders for prepared statements.
+
+```go
+sql, _, _ := goqu.Delete("table").ToSQL()
+fmt.Println(sql)
+```
+Output:
+```
+DELETE FROM "table"
+```
+
+**[`SelectDataset.Delete`](https://godoc.org/github.com/doug-martin/goqu/#SelectDataset.Delete)**
+
+If you already have a `SelectDataset` you can invoke `Delete()` to get a `DeleteDataset`
+
+**NOTE** This method will also copy over the `WITH`, `WHERE`, `ORDER`, and `LIMIT` from the `SelectDataset`
+
+```go
+
+ds := goqu.From("table")
+
+sql, _, _ := ds.Delete().ToSQL()
+fmt.Println(sql)
+
+sql, _, _ = ds.Where(goqu.C("foo").Eq("bar")).Delete().ToSQL()
+fmt.Println(sql)
+```
+Output:
+```
+DELETE FROM "table"
+DELETE FROM "table" WHERE "foo"='bar'
+```
+
+**[`DialectWrapper.Delete`](https://godoc.org/github.com/doug-martin/goqu/#DialectWrapper.Delete)**
+
+Use this when you want to create SQL for a specific `dialect`
+
+```go
+// import _ "github.com/doug-martin/goqu/v9/dialect/mysql"
+
+dialect := goqu.Dialect("mysql")
+
+sql, _, _ := dialect.Delete("table").ToSQL()
+fmt.Println(sql)
+```
+Output:
+```
+DELETE FROM `table`
+```
+
+**[`Database.Delete`](https://godoc.org/github.com/doug-martin/goqu/#DialectWrapper.Delete)**
+
+Use this when you want to execute the SQL or create SQL for the drivers dialect.
+
+```go
+// import _ "github.com/doug-martin/goqu/v9/dialect/mysql"
+
+mysqlDB := //initialize your db
+db := goqu.New("mysql", mysqlDB)
+
+sql, _, _ := db.Delete("table").ToSQL()
+fmt.Println(sql)
+```
+Output:
+```
+DELETE FROM `table`
+```
+
+### Examples
+
+For more examples visit the **[Docs](https://godoc.org/github.com/doug-martin/goqu/#DeleteDataset)**
+
+<a name="delete-all"></a>
+**Delete All Records**
+
+```go
+ds := goqu.Delete("items")
+
+sql, args, _ := ds.ToSQL()
+fmt.Println(sql, args)
+```
+
+Output:
+```
+DELETE FROM "items" []
+```
+
+<a name="prepared"></a>
+**[`Prepared`](https://godoc.org/github.com/doug-martin/goqu/#DeleteDataset.Prepared)**
+
+```go
+sql, _, _ := goqu.Delete("test").Where(goqu.Ex{
+	"a": goqu.Op{"gt": 10},
+	"b": goqu.Op{"lt": 10},
+	"c": nil,
+	"d": []string{"a", "b", "c"},
+}).ToSQL()
+fmt.Println(sql)
+```
+
+Output:
+```
+DELETE FROM "test" WHERE (("a" > ?) AND ("b" < ?) AND ("c" IS NULL) AND ("d" IN (?, ?, ?))) [10 10 a b c]
+```
+
+<a name="where"></a>
+**[`Where`](https://godoc.org/github.com/doug-martin/goqu/#DeleteDataset.Where)**
+
+```go
+sql, _, _ := goqu.Delete("test").Where(goqu.Ex{
+	"a": goqu.Op{"gt": 10},
+	"b": goqu.Op{"lt": 10},
+	"c": nil,
+	"d": []string{"a", "b", "c"},
+}).ToSQL()
+fmt.Println(sql)
+```
+
+Output:
+```
+DELETE FROM "test" WHERE (("a" > 10) AND ("b" < 10) AND ("c" IS NULL) AND ("d" IN ('a', 'b', 'c')))
+```
+
+<a name="order"></a>
+**[`Order`](https://godoc.org/github.com/doug-martin/goqu/#DeleteDataset.Order)**
+
+**NOTE** This will only work if your dialect supports it
+
+```go
+// import _ "github.com/doug-martin/goqu/v9/dialect/mysql"
+
+ds := goqu.Dialect("mysql").Delete("test").Order(goqu.C("a").Asc())
+sql, _, _ := ds.ToSQL()
+fmt.Println(sql)
+```
+
+Output:
+```
+DELETE FROM `test` ORDER BY `a` ASC
+```
+
+<a name="limit"></a>
+**[`Limit`](https://godoc.org/github.com/doug-martin/goqu/#DeleteDataset.Limit)**
+
+**NOTE** This will only work if your dialect supports it
+
+```go
+// import _ "github.com/doug-martin/goqu/v9/dialect/mysql"
+
+ds := goqu.Dialect("mysql").Delete("test").Limit(10)
+sql, _, _ := ds.ToSQL()
+fmt.Println(sql)
+```
+
+Output:
+```
+DELETE FROM `test` LIMIT 10
+```
+
+<a name="returning"></a>
+**[`Returning`](https://godoc.org/github.com/doug-martin/goqu/#DeleteDataset.Returning)**
+
+Returning a single column example.
+
+```go
+ds := goqu.Delete("items")
+sql, args, _ := ds.Returning("id").ToSQL()
+fmt.Println(sql, args)
+```
+
+Output:
+```
+DELETE FROM "items" RETURNING "id" []
+```
+
+Returning multiple columns
+
+```go
+sql, _, _ := goqu.Delete("test").Returning("a", "b").ToSQL()
+fmt.Println(sql)
+```
+
+Output:
+```
+DELETE FROM "items" RETURNING "a", "b"
+```
+
+Returning all columns
+
+```go
+sql, _, _ := goqu.Delete("test").Returning(goqu.T("test").All()).ToSQL()
+fmt.Println(sql)
+```
+
+Output:
+```
+DELETE FROM "test" RETURNING "test".*
+```
+
+<a name="seterror"></a>
+**[`SetError`](https://godoc.org/github.com/doug-martin/goqu/#DeleteDataset.SetError)**
+
+Sometimes while building up a query with goqu you will encounter situations where certain
+preconditions are not met or some end-user contraint has been violated. While you could
+track this error case separately, goqu provides a convenient built-in mechanism to set an
+error on a dataset if one has not already been set to simplify query building.
+
+Set an Error on a dataset:
+
+```go
+func GetDelete(name string, value string) *goqu.DeleteDataset {
+
+    var ds = goqu.Delete("test")
+
+    if len(name) == 0 {
+        return ds.SetError(fmt.Errorf("name is empty"))
+    }
+
+    if len(value) == 0 {
+        return ds.SetError(fmt.Errorf("value is empty"))
+    }
+
+    return ds.Where(goqu.C(name).Eq(value))
+}
+
+```
+
+This error is returned on any subsequent call to `Error` or `ToSQL`:
+
+```go
+var field, value string
+ds = GetDelete(field, value)
+fmt.Println(ds.Error())
+
+sql, args, err = ds.ToSQL()
+fmt.Println(err)
+```
+
+Output:
+```
+name is empty
+name is empty
+```
+
+## Executing Deletes
+
+To execute DELETES use [`Database.Delete`](https://godoc.org/github.com/doug-martin/goqu/#Database.Delete) to create your dataset
+
+### Examples
+
+<a name="exec"></a>
+**Executing a Delete**
+```go
+db := getDb()
+
+de := db.Delete("goqu_user").
+	Where(goqu.Ex{"first_name": "Bob"}).
+	Executor()
+
+if r, err := de.Exec(); err != nil {
+	fmt.Println(err.Error())
+} else {
+	c, _ := r.RowsAffected()
+	fmt.Printf("Deleted %d users", c)
+}
+```
+
+Output:
+
+```
+Deleted 1 users
+```
+
+If you use the RETURNING clause you can scan into structs or values.
+
+```go
+db := getDb()
+
+de := db.Delete("goqu_user").
+	Where(goqu.C("last_name").Eq("Yukon")).
+	Returning(goqu.C("id")).
+	Executor()
+
+var ids []int64
+if err := de.ScanVals(&ids); err != nil {
+	fmt.Println(err.Error())
+} else {
+	fmt.Printf("Deleted users [ids:=%+v]", ids)
+}
+```
+
+Output:
+
+```
+Deleted users [ids:=[1 2 3]]
+```

+ 195 - 0
src/github.com/doug-martin/goqu/docs/dialect.md

@@ -0,0 +1,195 @@
+# Dialect
+
+Dialects allow goqu the build the correct SQL for each database. There are four dialects that come packaged with `goqu`
+
+* [mysql](./dialect/mysql/mysql.go) - `import _ "github.com/doug-martin/goqu/v9/dialect/mysql"`
+* [postgres](./dialect/postgres/postgres.go) - `import _ "github.com/doug-martin/goqu/v9/dialect/postgres"`
+* [sqlite3](./dialect/sqlite3/sqlite3.go) - `import _ "github.com/doug-martin/goqu/v9/dialect/sqlite3"`
+* [sqlserver](./dialect/sqlserver/sqlserver.go) - `import _ "github.com/doug-martin/goqu/v9/dialect/sqlserver"`
+
+**NOTE** Dialects work like drivers in go where they are not registered until you import the package.
+
+Below are examples for each dialect. Notice how the dialect is imported and then looked up using `goqu.Dialect`
+
+<a name="postgres"></a>
+### Postgres
+```go
+import (
+  "fmt"
+  "github.com/doug-martin/goqu/v9"
+  // import the dialect
+  _ "github.com/doug-martin/goqu/v9/dialect/postgres"
+)
+
+// look up the dialect
+dialect := goqu.Dialect("postgres")
+
+// use dialect.From to get a dataset to build your SQL
+ds := dialect.From("test").Where(goqu.Ex{"id": 10})
+sql, args, err := ds.ToSQL()
+if err != nil{
+  fmt.Println("An error occurred while generating the SQL", err.Error())
+}else{
+  fmt.Println(sql, args)
+}
+```
+
+Output:
+```
+SELECT * FROM "test" WHERE "id" = 10 []
+```
+
+<a name="mysql"></a>
+### MySQL
+```go
+import (
+  "fmt"
+  "github.com/doug-martin/goqu/v9"
+  // import the dialect
+  _ "github.com/doug-martin/goqu/v9/dialect/mysql"
+)
+
+// look up the dialect
+dialect := goqu.Dialect("mysql")
+
+// use dialect.From to get a dataset to build your SQL
+ds := dialect.From("test").Where(goqu.Ex{"id": 10})
+sql, args, err := ds.ToSQL()
+if err != nil{
+  fmt.Println("An error occurred while generating the SQL", err.Error())
+}else{
+  fmt.Println(sql, args)
+}
+```
+
+Output:
+```
+SELECT * FROM `test` WHERE `id` = 10 []
+```
+
+<a name="sqlite3"></a>
+### SQLite3
+```go
+import (
+  "fmt"
+  "github.com/doug-martin/goqu/v9"
+  // import the dialect
+  _ "github.com/doug-martin/goqu/v9/dialect/sqlite3"
+)
+
+// look up the dialect
+dialect := goqu.Dialect("sqlite3")
+
+// use dialect.From to get a dataset to build your SQL
+ds := dialect.From("test").Where(goqu.Ex{"id": 10})
+sql, args, err := ds.ToSQL()
+if err != nil{
+  fmt.Println("An error occurred while generating the SQL", err.Error())
+}else{
+  fmt.Println(sql, args)
+}
+```
+
+Output:
+```
+SELECT * FROM `test` WHERE `id` = 10 []
+```
+
+<a name="sqlserver"></a>
+### SQLServer
+```go
+import (
+  "fmt"
+  "github.com/doug-martin/goqu/v9"
+  // import the dialect
+  _ "github.com/doug-martin/goqu/v9/dialect/sqlserver"
+)
+
+// look up the dialect
+dialect := goqu.Dialect("sqlserver")
+
+// use dialect.From to get a dataset to build your SQL
+ds := dialect.From("test").Where(goqu.Ex{"id": 10})
+sql, args, err := ds.ToSQL()
+if err != nil{
+  fmt.Println("An error occurred while generating the SQL", err.Error())
+}else{
+  fmt.Println(sql, args)
+}
+```
+
+Output:
+```
+SELECT * FROM "test" WHERE "id" = 10 []
+
+### Executing Queries 
+
+You can also create a `goqu.Database` instance to query records.
+
+In the example below notice that we imported the dialect and driver for side effect only.
+
+```go
+import (
+  "database/sql"
+  "github.com/doug-martin/goqu/v9"
+  _ "github.com/doug-martin/goqu/v9/dialect/postgres"
+  _ "github.com/lib/pq"
+)
+
+dialect := goqu.Dialect("postgres")
+
+pgDb, err := sql.Open("postgres", "user=postgres dbname=goqupostgres sslmode=disable ")
+if err != nil {
+  panic(err.Error())
+}
+db := dialect.DB(pgDb)
+
+// "SELECT COUNT(*) FROM "user";
+if count, err := db.From("user").Count(); err != nil {
+  fmt.Println(err.Error())
+}else{
+  fmt.Printf("User count = %d", count)
+}
+```
+
+<a name="custom-dialects"></a>
+## Custom Dialects
+
+Dialects in goqu are the foundation of building the correct SQL for each DB dialect.
+
+### Dialect Options
+
+Most SQL dialects share a majority of their syntax, for this reason `goqu` has a [default set of dialect options]((http://godoc.org/github.com/doug-martin/goqu/#DefaultDialectOptions)) that can be used as a base for any new Dialect.
+
+When creating a new `SQLDialect` you just need to override the default values that are documented in [`SQLDialectOptions`](http://godoc.org/github.com/doug-martin/goqu/#SQLDialectOptions).
+
+Take a look at [`postgres`](./dialect/postgres/postgres.go), [`mysql`](./dialect/mysql/mysql.go) and [`sqlite3`](./dialect/sqlite3/sqlite3.go) for examples.
+
+### Creating a custom dialect
+
+When creating a new dialect you must register it using [`RegisterDialect`](http://godoc.org/github.com/doug-martin/goqu/#RegisterDialect). This method requires 2 arguments.
+
+1. `dialect string` - The name of your dialect
+2. `opts SQLDialectOptions` - The custom options for your dialect
+
+For example you could create a custom dialect that replaced the default quote `'"'` with a backtick <code>`</code>
+```go
+opts := goqu.DefaultDialectOptions()
+opts.QuoteRune = '`'
+goqu.RegisterDialect("custom-dialect", opts)
+
+dialect := goqu.Dialect("custom-dialect")
+
+ds := dialect.From("test")
+
+sql, args, _ := ds.ToSQL()
+fmt.Println(sql, args)
+```
+
+Output:
+```
+SELECT * FROM `test` []
+```
+
+For more examples look at [`postgres`](./dialect/postgres/postgres.go), [`mysql`](./dialect/mysql/mysql.go) and [`sqlite3`](./dialect/sqlite3/sqlite3.go) for examples.
+

+ 447 - 0
src/github.com/doug-martin/goqu/docs/expressions.md

@@ -0,0 +1,447 @@
+# Expressions
+
+`goqu` provides an idiomatic DSL for generating SQL. Datasets only act as a clause builder (i.e. Where, From, Select), most of these clause methods accept Expressions which are the building blocks for your SQL statement, you can think of them as fragments of SQL.
+
+* [`Ex{}`](#ex) - A map where the key will become an Identifier and the Key is the value, this is most commonly used in the Where clause.
+* [`ExOr{}`](#ex-or)- OR version of `Ex`. A map where the key will become an Identifier and the Key is the value, this is most commonly used in the Where clause
+* [`S`](#S) - An Identifier that represents a schema. With a schema identifier you can fully qualify tables and columns.
+* [`T`](#T) - An Identifier that represents a Table. With a Table identifier you can fully qualify columns.
+* [`C`](#C) - An Identifier that represents a Column. See the docs for more examples
+* [`I`](#I) - An Identifier represents a schema, table, or column or any combination. I parses identifiers seperated by a . character.
+* [`L`](#L) - An SQL literal.
+* [`V`](#V) - An Value to be used in SQL. 
+* [`And`](#and) - AND multiple expressions together.
+* [`Or`](#or) - OR multiple expressions together.
+* [Complex Example](#complex) - Complex Example using most of the Expression DSL.
+
+The entry points for expressions are:
+
+<a name="ex"></a>
+**[`Ex{}`](https://godoc.org/github.com/doug-martin/goqu#Ex)** 
+
+A map where the key will become an Identifier and the Key is the value, this is most commonly used in the Where clause. By default `Ex` will use the equality operator except in cases where the equality operator will not work, see the example below.
+
+```go
+sql, _, _ := db.From("items").Where(goqu.Ex{
+  "col1": "a",
+  "col2": 1,
+  "col3": true,
+  "col4": false,
+  "col5": nil,
+  "col6": []string{"a", "b", "c"},
+}).ToSQL()
+fmt.Println(sql)
+```
+
+Output:
+```sql
+SELECT * FROM "items" WHERE (("col1" = 'a') AND ("col2" = 1) AND ("col3" IS TRUE) AND ("col4" IS FALSE) AND ("col5" IS NULL) AND ("col6" IN ('a', 'b', 'c')))
+```
+
+You can also use the [`Op`](https://godoc.org/github.com/doug-martin/goqu#Op) map which allows you to create more complex expressions using the map syntax. When using the `Op` map the key is the name of the comparison you want to make (e.g. `"neq"`, `"like"`, `"is"`, `"in"`), the key is case insensitive.
+
+```go
+sql, _, _ := db.From("items").Where(goqu.Ex{
+  "col1": goqu.Op{"neq": "a"},
+  "col3": goqu.Op{"isNot": true},
+  "col6": goqu.Op{"notIn": []string{"a", "b", "c"}},
+}).ToSQL()
+fmt.Println(sql)
+```
+
+Output:
+```sql
+SELECT * FROM "items" WHERE (("col1" != 'a') AND ("col3" IS NOT TRUE) AND ("col6" NOT IN ('a', 'b', 'c')))
+```
+For a more complete examples see the [`Op`](https://godoc.org/github.com/doug-martin/goqu#Op) and [`Ex`](https://godoc.org/github.com/doug-martin/goqu#Ex) docs
+
+<a name="ex-or"></a>
+**[`ExOr{}`](https://godoc.org/github.com/doug-martin/goqu#ExOr)** 
+
+A map where the key will become an Identifier and the Key is the value, this is most commonly used in the Where clause. By default `ExOr` will use the equality operator except in cases where the equality operator will not work, see the example below.
+
+```go
+sql, _, _ := db.From("items").Where(goqu.ExOr{
+  "col1": "a",
+  "col2": 1,
+  "col3": true,
+  "col4": false,
+  "col5": nil,
+  "col6": []string{"a", "b", "c"},
+}).ToSQL()
+fmt.Println(sql)
+```
+  
+Output:
+```sql
+SELECT * FROM "items" WHERE (("col1" = 'a') OR ("col2" = 1) OR ("col3" IS TRUE) OR ("col4" IS FALSE) OR ("col5" IS NULL) OR ("col6" IN ('a', 'b', 'c')))
+```
+  
+You can also use the [`Op`](https://godoc.org/github.com/doug-martin/goqu#Op) map which allows you to create more complex expressions using the map syntax. When using the `Op` map the key is the name of the comparison you want to make (e.g. `"neq"`, `"like"`, `"is"`, `"in"`), the key is case insensitive.
+  
+```go
+sql, _, _ := db.From("items").Where(goqu.ExOr{
+  "col1": goqu.Op{"neq": "a"},
+  "col3": goqu.Op{"isNot": true},
+  "col6": goqu.Op{"notIn": []string{"a", "b", "c"}},
+}).ToSQL()
+fmt.Println(sql)
+```
+  
+Output:
+```sql
+SELECT * FROM "items" WHERE (("col1" != 'a') OR ("col3" IS NOT TRUE) OR ("col6" NOT IN ('a', 'b', 'c')))
+```
+For a more complete examples see the [`Op`](https://godoc.org/github.com/doug-martin/goqu#Op) and [`ExOr`](https://godoc.org/github.com/doug-martin/goqu#Ex) docs
+
+<a name="S"></a>
+**[`S()`](https://godoc.org/github.com/doug-martin/goqu#S)**
+
+An Identifier that represents a schema. With a schema identifier you can fully qualify tables and columns.
+
+```go
+s := goqu.S("my_schema")
+
+// "my_schema"."my_table"
+t := s.Table("my_table")
+
+// "my_schema"."my_table"."my_column"
+
+sql, _, _ := goqu.From(t).Select(t.Col("my_column").ToSQL()
+// SELECT "my_schema"."my_table"."my_column" FROM "my_schema"."my_table"
+fmt.Println(sql)
+```
+
+<a name="T"></a>
+**[`T()`](https://godoc.org/github.com/doug-martin/goqu#T)** 
+
+An Identifier that represents a Table. With a Table identifier you can fully qualify columns.
+```go
+t := s.Table("my_table")
+
+sql, _, _ := goqu.From(t).Select(t.Col("my_column").ToSQL()
+// SELECT "my_table"."my_column" FROM "my_table"
+fmt.Println(sql)
+
+// qualify the table with a schema
+sql, _, _ := goqu.From(t.Schema("my_schema")).Select(t.Col("my_column").ToSQL()
+// SELECT "my_table"."my_column" FROM "my_schema"."my_table"
+fmt.Println(sql)
+```
+
+<a name="C"></a>
+**[`C()`](https://godoc.org/github.com/doug-martin/goqu#C)** 
+
+An Identifier that represents a Column. See the [docs]((https://godoc.org/github.com/doug-martin/goqu#C)) for more examples
+
+```go
+sql, _, _ := goqu.From("table").Where(goqu.C("col").Eq(10)).ToSQL()
+// SELECT * FROM "table" WHERE "col" = 10
+fmt.Println(sql)
+```
+
+<a name="I"></a>
+**[`I()`](https://godoc.org/github.com/doug-martin/goqu#I)** 
+
+An Identifier represents a schema, table, or column or any combination. `I` parses identifiers seperated by a `.` character.
+
+```go
+// with three parts it is assumed you have provided a schema, table and column
+goqu.I("my_schema.table.col") == goqu.S("my_schema").Table("table").Col("col")
+
+// with two parts it is assumed you have provided a table and column
+goqu.I("table.col") == goqu.T("table").Col("col")
+
+// with a single value it is the same as calling goqu.C
+goqu.I("col") == goqu.C("col")
+```
+
+<a name="L"></a>
+**[`L()`](https://godoc.org/github.com/doug-martin/goqu#L)** 
+
+An SQL literal. You may find yourself in a situation where an IdentifierExpression cannot expression an SQL fragment that your database supports. In that case you can use a LiteralExpression
+
+```go
+// manual casting
+goqu.L(`"json"::TEXT = "other_json"::text`)
+
+// custom function invocation
+goqu.L(`custom_func("a")`)
+
+// postgres JSON access
+goqu.L(`"json_col"->>'someField'`).As("some_field")
+```
+  
+You can also use placeholders in your literal with a `?` character. `goqu` will handle changing it to what the dialect needs (e.g. `?` mysql, `$1` postgres, `?` sqlite3). 
+
+**NOTE** If your query is not prepared the placeholders will be properly interpolated.
+
+```go
+goqu.L("col IN (?, ?, ?)", "a", "b", "c") 
+```
+
+Putting it together
+  
+```go
+ds := db.From("test").Where(
+  goqu.L(`("json"::TEXT = "other_json"::TEXT)`),
+  goqu.L("col IN (?, ?, ?)", "a", "b", "c"),
+)
+
+sql, args, _ := ds.ToSQL()
+fmt.Println(sql, args)
+
+sql, args, _ := ds.Prepared(true).ToSQL()
+fmt.Println(sql, args)
+```
+
+Output:
+```sql
+SELECT * FROM "test" WHERE ("json"::TEXT = "other_json"::TEXT) AND col IN ('a', 'b', 'c') []
+-- assuming postgres dialect
+SELECT * FROM "test" WHERE ("json"::TEXT = "other_json"::TEXT) AND col IN ($1, $2, $3) [a, b, c]
+```
+
+<a name="V"></a>
+**[`V()`](https://godoc.org/github.com/doug-martin/goqu#V)**
+
+Sometimes you may have a value that you want to use directly in SQL. 
+
+**NOTE** This is a shorter version of `goqu.L("?", val)`
+
+For example you may want to select a value as a column.
+
+```go
+ds := goqu.From("user").Select(
+	goqu.V(true).As("is_verified"),
+	goqu.V(1.2).As("version"),
+	"first_name",
+	"last_name",
+)
+
+sql, args, _ := ds.ToSQL()
+fmt.Println(sql, args)
+```
+
+Output:
+```
+SELECT TRUE AS "is_verified", 1.2 AS "version", "first_name", "last_name" FROM "user" []
+```
+
+You can also use `goqu.V` in where clauses.
+
+```
+ds := goqu.From("user").Where(goqu.V(1).Neq(1))
+sql, args, _ := ds.ToSQL()
+fmt.Println(sql, args)
+```
+
+Output:
+
+```
+SELECT * FROM "user" WHERE (1 != 1) []
+```
+
+You can also use them in prepared statements.
+
+```
+ds := goqu.From("user").Where(goqu.V(1).Neq(1))
+sql, args, _ := ds.Prepared(true).ToSQL()
+fmt.Println(sql, args)
+```
+
+Output:
+
+```
+SELECT * FROM "user" WHERE (? != ?) [1, 1]
+```
+
+
+<a name="and"></a>
+**[`And()`](https://godoc.org/github.com/doug-martin/goqu#And)** 
+
+You can use the `And` function to AND multiple expressions together.
+
+**NOTE** By default goqu will AND expressions together
+
+```go
+ds := goqu.From("test").Where(
+  goqu.And(
+	  goqu.C("col").Gt(10),
+	  goqu.C("col").Lt(20),
+  ),
+)
+sql, args, _ := ds.ToSQL()
+fmt.Println(sql, args)
+
+sql, args, _ = ds.Prepared(true).ToSQL()
+fmt.Println(sql, args)
+```
+
+Output:
+```sql
+SELECT * FROM "test" WHERE (("col" > 10) AND ("col" < 20)) []
+SELECT * FROM "test" WHERE (("col" > ?) AND ("col" < ?)) [10 20]
+```
+
+<a name="or"></a>
+**[`Or()`](https://godoc.org/github.com/doug-martin/goqu#Or)** 
+
+You can use the `Or` function to OR multiple expressions together.
+
+```go
+ds := goqu.From("test").Where(
+  goqu.Or(
+	  goqu.C("col").Eq(10),
+	  goqu.C("col").Eq(20),
+  ),
+)
+sql, args, _ := ds.ToSQL()
+fmt.Println(sql, args)
+
+sql, args, _ = ds.Prepared(true).ToSQL()
+fmt.Println(sql, args)
+```
+
+Output:
+```sql
+SELECT * FROM "test" WHERE (("col" = 10) OR ("col" = 20)) []
+SELECT * FROM "test" WHERE (("col" = ?) OR ("col" = ?)) [10 20]
+```
+
+You can also use `Or` and `And` functions in tandem which will give you control not only over how the Expressions are joined together, but also how they are grouped
+ 
+```go
+ds := goqu.From("items").Where(
+  goqu.Or(
+	  goqu.C("a").Gt(10),
+	  goqu.And(
+		  goqu.C("b").Eq(100),
+		  goqu.C("c").Neq("test"),
+	  ),
+  ),
+)
+sql, args, _ := ds.ToSQL()
+fmt.Println(sql, args)
+
+sql, args, _ = ds.Prepared(true).ToSQL()
+fmt.Println(sql, args)
+```
+
+Output:
+```sql
+SELECT * FROM "items" WHERE (("a" > 10) OR (("b" = 100) AND ("c" != 'test'))) []
+SELECT * FROM "items" WHERE (("a" > ?) OR (("b" = ?) AND ("c" != ?))) [10 100 test]
+```
+
+You can also use Or with the map syntax
+```go
+ds := goqu.From("test").Where(
+  goqu.Or(
+    // Ex will be anded together
+    goqu.Ex{
+      "col1": 1,
+      "col2": true,
+    },
+    goqu.Ex{
+      "col3": nil,
+      "col4": "foo",
+    },
+  ),
+)
+sql, args, _ := ds.ToSQL()
+fmt.Println(sql, args)
+
+sql, args, _ = ds.Prepared(true).ToSQL()
+fmt.Println(sql, args)
+```
+
+Output:
+```sql
+SELECT * FROM "test" WHERE ((("col1" = 1) AND ("col2" IS TRUE)) OR (("col3" IS NULL) AND ("col4" = 'foo'))) []
+SELECT * FROM "test" WHERE ((("col1" = ?) AND ("col2" IS TRUE)) OR (("col3" IS NULL) AND ("col4" = ?))) [1 foo]
+```
+
+<a name="complex"></a>
+## Complex Example
+
+This example uses most of the features of the `goqu` Expression DSL
+
+```go
+ds := db.From("test").
+  Select(goqu.COUNT("*")).
+  InnerJoin(goqu.I("test2"), goqu.On(goqu.Ex{"test.fkey": goqu.I("test2.id")})).
+  LeftJoin(goqu.I("test3"), goqu.On(goqu.Ex{"test2.fkey": goqu.I("test3.id")})).
+  Where(
+    goqu.Ex{
+      "test.name":    goqu.Op{"like": regexp.MustCompile("^(a|b)")},
+      "test2.amount": goqu.Op{"isNot": nil},
+    },
+    goqu.ExOr{
+      "test3.id":     nil,
+      "test3.status": []string{"passed", "active", "registered"},
+    },
+  ).
+  Order(goqu.I("test.created").Desc().NullsLast()).
+  GroupBy(goqu.I("test.user_id")).
+  Having(goqu.AVG("test3.age").Gt(10))
+
+sql, args, _ := ds.ToSQL()
+fmt.Println(sql)
+
+sql, args, _ := ds.Prepared(true).ToSQL()
+fmt.Println(sql)
+```
+
+Using the Expression syntax
+```go
+ds := db.From("test").
+  Select(goqu.COUNT("*")).
+  InnerJoin(goqu.I("test2"), goqu.On(goqu.I("test.fkey").Eq(goqu.I("test2.id")))).
+  LeftJoin(goqu.I("test3"), goqu.On(goqu.I("test2.fkey").Eq(goqu.I("test3.id")))).
+  Where(
+    goqu.I("test.name").Like(regexp.MustCompile("^(a|b)")),
+    goqu.I("test2.amount").IsNotNull(),
+    goqu.Or(
+      goqu.I("test3.id").IsNull(),
+      goqu.I("test3.status").In("passed", "active", "registered"),
+    ),
+  ).
+  Order(goqu.I("test.created").Desc().NullsLast()).
+  GroupBy(goqu.I("test.user_id")).
+  Having(goqu.AVG("test3.age").Gt(10))
+
+sql, args, _ := ds.ToSQL()
+fmt.Println(sql)
+
+sql, args, _ := ds.Prepared(true).ToSQL()
+fmt.Println(sql)
+```
+
+Both examples generate the following SQL
+
+```sql
+-- interpolated
+SELECT COUNT(*)
+FROM "test"
+         INNER JOIN "test2" ON ("test"."fkey" = "test2"."id")
+         LEFT JOIN "test3" ON ("test2"."fkey" = "test3"."id")
+WHERE ((("test"."name" ~ '^(a|b)') AND ("test2"."amount" IS NOT NULL)) AND
+       (("test3"."id" IS NULL) OR ("test3"."status" IN ('passed', 'active', 'registered'))))
+GROUP BY "test"."user_id"
+HAVING (AVG("test3"."age") > 10)
+ORDER BY "test"."created" DESC NULLS LAST []
+
+-- prepared
+SELECT COUNT(*)
+FROM "test"
+         INNER JOIN "test2" ON ("test"."fkey" = "test2"."id")
+         LEFT JOIN "test3" ON ("test2"."fkey" = "test3"."id")
+WHERE ((("test"."name" ~ ?) AND ("test2"."amount" IS NOT NULL)) AND
+       (("test3"."id" IS NULL) OR ("test3"."status" IN (?, ?, ?))))
+GROUP BY "test"."user_id"
+HAVING (AVG("test3"."age") > ?)
+ORDER BY "test"."created" DESC NULLS LAST [^(a|b) passed active registered 10]
+```
+
+

+ 496 - 0
src/github.com/doug-martin/goqu/docs/inserting.md

@@ -0,0 +1,496 @@
+# Inserting
+
+* [Creating An InsertDataset](#create)
+* Examples
+  * [Insert Cols and Vals](#insert-cols-vals)
+  * [Insert `goqu.Record`](#insert-record)
+  * [Insert Structs](#insert-structs)
+  * [Insert Map](#insert-map)
+  * [Insert From Query](#insert-from-query)
+  * [Returning](#returning)
+  * [SetError](#seterror)
+  * [Executing](#executing)
+
+<a name="create"></a>
+To create a [`InsertDataset`](https://godoc.org/github.com/doug-martin/goqu/#InsertDataset)  you can use
+
+**[`goqu.Insert`](https://godoc.org/github.com/doug-martin/goqu/#Insert)**
+
+When you just want to create some quick SQL, this mostly follows the `Postgres` with the exception of placeholders for prepared statements.
+
+```go
+ds := goqu.Insert("user").Rows(
+    goqu.Record{"first_name": "Greg", "last_name": "Farley"},
+)
+insertSQL, _, _ := ds.ToSQL()
+fmt.Println(insertSQL, args)
+```
+Output:
+```
+INSERT INTO "user" ("first_name", "last_name") VALUES ('Greg', 'Farley')
+```
+
+**[`SelectDataset.Insert`](https://godoc.org/github.com/doug-martin/goqu/#SelectDataset.Insert)**
+
+If you already have a `SelectDataset` you can invoke `Insert()` to get a `InsertDataset`
+
+**NOTE** This method will also copy over the `WITH` clause as well as the `FROM`
+
+```go
+ds := goqu.From("user")
+
+ds := ds.Insert().Rows(
+    goqu.Record{"first_name": "Greg", "last_name": "Farley"},
+)
+insertSQL, _, _ := ds.ToSQL()
+fmt.Println(insertSQL, args)
+```
+Output:
+```
+INSERT INTO "user" ("first_name", "last_name") VALUES ('Greg', 'Farley')
+```
+
+**[`DialectWrapper.Insert`](https://godoc.org/github.com/doug-martin/goqu/#DialectWrapper.Insert)**
+
+Use this when you want to create SQL for a specific `dialect`
+
+```go
+// import _ "github.com/doug-martin/goqu/v9/dialect/mysql"
+
+dialect := goqu.Dialect("mysql")
+
+ds := dialect.Insert().Rows(
+    goqu.Record{"first_name": "Greg", "last_name": "Farley"},
+)
+insertSQL, _, _ := ds.ToSQL()
+fmt.Println(insertSQL, args)
+```
+Output:
+```
+INSERT INTO `user` (`first_name`, `last_name`) VALUES ('Greg', 'Farley')
+```
+
+**[`Database.Insert`](https://godoc.org/github.com/doug-martin/goqu/#DialectWrapper.Insert)**
+
+Use this when you want to execute the SQL or create SQL for the drivers dialect.
+
+```go
+// import _ "github.com/doug-martin/goqu/v9/dialect/mysql"
+
+mysqlDB := //initialize your db
+db := goqu.New("mysql", mysqlDB)
+
+ds := db.Insert().Rows(
+    goqu.Record{"first_name": "Greg", "last_name": "Farley"},
+)
+insertSQL, _, _ := ds.ToSQL()
+fmt.Println(insertSQL, args)
+```
+Output:
+```
+INSERT INTO `user` (`first_name`, `last_name`) VALUES ('Greg', 'Farley')
+```
+
+### Examples
+
+For more examples visit the **[Docs](https://godoc.org/github.com/doug-martin/goqu/#InsertDataset)**
+
+<a name="insert-cols-vals"></a>
+**Insert with Cols and Vals**
+
+```go
+ds := goqu.Insert("user").
+	Cols("first_name", "last_name").
+	Vals(
+		goqu.Vals{"Greg", "Farley"},
+		goqu.Vals{"Jimmy", "Stewart"},
+		goqu.Vals{"Jeff", "Jeffers"},
+	)
+insertSQL, args, _ := ds.ToSQL()
+fmt.Println(insertSQL, args)
+```
+
+Output:
+```sql
+INSERT INTO "user" ("first_name", "last_name") VALUES ('Greg', 'Farley'), ('Jimmy', 'Stewart'), ('Jeff', 'Jeffers') []
+```
+
+<a name="insert-record"></a>
+**Insert `goqu.Record`**
+
+```go
+ds := goqu.Insert("user").Rows(
+	goqu.Record{"first_name": "Greg", "last_name": "Farley"},
+	goqu.Record{"first_name": "Jimmy", "last_name": "Stewart"},
+	goqu.Record{"first_name": "Jeff", "last_name": "Jeffers"},
+)
+insertSQL, args, _ := ds.ToSQL()
+fmt.Println(insertSQL, args)
+```
+
+Output:
+```
+INSERT INTO "user" ("first_name", "last_name") VALUES ('Greg', 'Farley'), ('Jimmy', 'Stewart'), ('Jeff', 'Jeffers') []
+```
+
+<a name="insert-structs"></a>
+**Insert Structs**
+
+```go
+type User struct {
+	FirstName string `db:"first_name"`
+	LastName  string `db:"last_name"`
+}
+ds := goqu.Insert("user").Rows(
+	User{FirstName: "Greg", LastName: "Farley"},
+	User{FirstName: "Jimmy", LastName: "Stewart"},
+	User{FirstName: "Jeff", LastName: "Jeffers"},
+)
+insertSQL, args, _ := ds.ToSQL()
+fmt.Println(insertSQL, args)
+```
+
+Output:
+```
+INSERT INTO "user" ("first_name", "last_name") VALUES ('Greg', 'Farley'), ('Jimmy', 'Stewart'), ('Jeff', 'Jeffers') []
+```
+
+You can skip fields in a struct by using the `skipinsert` tag
+
+```go
+type User struct {
+	FirstName string `db:"first_name" goqu:"skipinsert"`
+	LastName  string `db:"last_name"`
+}
+ds := goqu.Insert("user").Rows(
+	User{FirstName: "Greg", LastName: "Farley"},
+	User{FirstName: "Jimmy", LastName: "Stewart"},
+	User{FirstName: "Jeff", LastName: "Jeffers"},
+)
+insertSQL, args, _ := ds.ToSQL()
+fmt.Println(insertSQL, args)
+```
+
+Output:
+```
+INSERT INTO "user" ("last_name") VALUES ('Farley'), ('Stewart'), ('Jeffers') []
+```
+
+If you want to use the database `DEFAULT` when the struct field is a zero value you can use the `defaultifempty` tag.
+
+```go
+type User struct {
+	FirstName string `db:"first_name" goqu:"defaultifempty"`
+	LastName  string `db:"last_name"`
+}
+ds := goqu.Insert("user").Rows(
+	User{LastName: "Farley"},
+	User{FirstName: "Jimmy", LastName: "Stewart"},
+	User{LastName: "Jeffers"},
+)
+insertSQL, args, _ := ds.ToSQL()
+fmt.Println(insertSQL, args)
+```
+
+Output:
+```
+INSERT INTO "user" ("first_name", "last_name") VALUES (DEFAULT, 'Farley'), ('Jimmy', 'Stewart'), (DEFAULT, 'Jeffers') []
+```
+
+`goqu` will also use fields in embedded structs when creating an insert.
+
+**NOTE** unexported fields will be ignored!
+
+```go
+type Address struct {
+	Street string `db:"address_street"`
+	State  string `db:"address_state"`
+}
+type User struct {
+	Address
+	FirstName string
+	LastName  string
+}
+ds := goqu.Insert("user").Rows(
+	User{Address: Address{Street: "111 Street", State: "NY"}, FirstName: "Greg", LastName: "Farley"},
+	User{Address: Address{Street: "211 Street", State: "NY"}, FirstName: "Jimmy", LastName: "Stewart"},
+	User{Address: Address{Street: "311 Street", State: "NY"}, FirstName: "Jeff", LastName: "Jeffers"},
+)
+insertSQL, args, _ := ds.ToSQL()
+fmt.Println(insertSQL, args)
+```
+
+Output:
+```
+INSERT INTO "user" ("address_state", "address_street", "firstname", "lastname") VALUES ('NY', '111 Street', 'Greg', 'Farley'), ('NY', '211 Street', 'Jimmy', 'Stewart'), ('NY', '311 Street', 'Jeff', 'Jeffers') []
+```
+
+**NOTE** When working with embedded pointers if the embedded struct is nil then the fields will be ignored.
+
+```go
+type Address struct {
+	Street string
+	State  string
+}
+type User struct {
+	*Address
+	FirstName string
+	LastName  string
+}
+ds := goqu.Insert("user").Rows(
+	User{FirstName: "Greg", LastName: "Farley"},
+	User{FirstName: "Jimmy", LastName: "Stewart"},
+	User{FirstName: "Jeff", LastName: "Jeffers"},
+)
+insertSQL, args, _ := ds.ToSQL()
+fmt.Println(insertSQL, args)
+```
+
+Output:
+```
+INSERT INTO "user" ("firstname", "lastname") VALUES ('Greg', 'Farley'), ('Jimmy', 'Stewart'), ('Jeff', 'Jeffers') []
+```
+
+You can ignore an embedded struct or struct pointer by using `db:"-"`
+
+```go
+type Address struct {
+	Street string
+	State  string
+}
+type User struct {
+	Address   `db:"-"`
+	FirstName string
+	LastName  string
+}
+
+ds := goqu.Insert("user").Rows(
+	User{Address: Address{Street: "111 Street", State: "NY"}, FirstName: "Greg", LastName: "Farley"},
+	User{Address: Address{Street: "211 Street", State: "NY"}, FirstName: "Jimmy", LastName: "Stewart"},
+	User{Address: Address{Street: "311 Street", State: "NY"}, FirstName: "Jeff", LastName: "Jeffers"},
+)
+insertSQL, args, _ := ds.ToSQL()
+fmt.Println(insertSQL, args)
+```
+
+Output:
+```
+INSERT INTO "user" ("firstname", "lastname") VALUES ('Greg', 'Farley'), ('Jimmy', 'Stewart'), ('Jeff', 'Jeffers') []
+```
+
+<a name="insert-map"></a>
+**Insert `map[string]interface{}`**
+
+```go
+ds := goqu.Insert("user").Rows(
+	map[string]interface{}{"first_name": "Greg", "last_name": "Farley"},
+	map[string]interface{}{"first_name": "Jimmy", "last_name": "Stewart"},
+	map[string]interface{}{"first_name": "Jeff", "last_name": "Jeffers"},
+)
+insertSQL, args, _ := ds.ToSQL()
+fmt.Println(insertSQL, args)
+```
+
+Output:
+```
+INSERT INTO "user" ("first_name", "last_name") VALUES ('Greg', 'Farley'), ('Jimmy', 'Stewart'), ('Jeff', 'Jeffers') []
+```
+
+<a name="insert-from-query"></a>
+**Insert from query**
+
+```go
+ds := goqu.Insert("user").Prepared(true).
+	FromQuery(goqu.From("other_table"))
+insertSQL, args, _ := ds.ToSQL()
+fmt.Println(insertSQL, args)
+```
+
+Output:
+```
+INSERT INTO "user" SELECT * FROM "other_table" []
+```
+
+You can also specify the columns
+
+```go
+ds := goqu.Insert("user").Prepared(true).
+	Cols("first_name", "last_name").
+	FromQuery(goqu.From("other_table").Select("fn", "ln"))
+insertSQL, args, _ := ds.ToSQL()
+fmt.Println(insertSQL, args)
+```
+
+Output:
+```
+INSERT INTO "user" ("first_name", "last_name") SELECT "fn", "ln" FROM "other_table" []
+```
+
+<a name="returning"></a>
+**Returning Clause**
+
+Returning a single column example.
+
+```go
+sql, _, _ := goqu.Insert("test").
+	Rows(goqu.Record{"a": "a", "b": "b"}).
+	Returning("id").
+	ToSQL()
+fmt.Println(sql)
+```
+
+Output:
+```
+INSERT INTO "test" ("a", "b") VALUES ('a', 'b') RETURNING "id"
+```
+
+Returning multiple columns
+
+```go
+sql, _, _ = goqu.Insert("test").
+	Rows(goqu.Record{"a": "a", "b": "b"}).
+	Returning("a", "b").
+	ToSQL()
+fmt.Println(sql)
+```
+
+Output:
+```
+INSERT INTO "test" ("a", "b") VALUES ('a', 'b') RETURNING "a", "b"
+```
+
+Returning all columns
+
+```go
+sql, _, _ = goqu.Insert("test").
+	Rows(goqu.Record{"a": "a", "b": "b"}).
+	Returning(goqu.T("test").All()).
+	ToSQL()
+fmt.Println(sql)
+```
+
+Output:
+```
+INSERT INTO "test" ("a", "b") VALUES ('a', 'b') RETURNING "test".*
+```
+
+<a name="seterror"></a>
+**[`SetError`](https://godoc.org/github.com/doug-martin/goqu/#InsertDataset.SetError)**
+
+Sometimes while building up a query with goqu you will encounter situations where certain
+preconditions are not met or some end-user contraint has been violated. While you could
+track this error case separately, goqu provides a convenient built-in mechanism to set an
+error on a dataset if one has not already been set to simplify query building.
+
+Set an Error on a dataset:
+
+```go
+func GetInsert(name string, value string) *goqu.InsertDataset {
+
+    var ds = goqu.Insert("test")
+
+    if len(field) == 0 {
+        return ds.SetError(fmt.Errorf("name is empty"))
+    }
+
+    if len(value) == 0 {
+        return ds.SetError(fmt.Errorf("value is empty"))
+    }
+
+    return ds.Rows(goqu.Record{name: value})
+}
+
+```
+
+This error is returned on any subsequent call to `Error` or `ToSQL`:
+
+```go
+var field, value string
+ds = GetInsert(field, value)
+fmt.Println(ds.Error())
+
+sql, args, err = ds.ToSQL()
+fmt.Println(err)
+```
+
+Output:
+```
+name is empty
+name is empty
+```
+
+<a name="executing"></a>
+## Executing Inserts
+
+To execute INSERTS use [`Database.Insert`](https://godoc.org/github.com/doug-martin/goqu/#Database.Insert) to create your dataset
+
+### Examples
+
+**Executing an single Insert**
+```go
+db := getDb()
+
+insert := db.Insert("goqu_user").Rows(
+	goqu.Record{"first_name": "Jed", "last_name": "Riley", "created": time.Now()},
+).Executor()
+
+if _, err := insert.Exec(); err != nil {
+	fmt.Println(err.Error())
+} else {
+	fmt.Println("Inserted 1 user")
+}
+```
+
+Output:
+
+```
+Inserted 1 user
+```
+
+**Executing multiple inserts**
+
+```go
+db := getDb()
+
+users := []goqu.Record{
+	{"first_name": "Greg", "last_name": "Farley", "created": time.Now()},
+	{"first_name": "Jimmy", "last_name": "Stewart", "created": time.Now()},
+	{"first_name": "Jeff", "last_name": "Jeffers", "created": time.Now()},
+}
+
+insert := db.Insert("goqu_user").Rows(users).Executor()
+if _, err := insert.Exec(); err != nil {
+	fmt.Println(err.Error())
+} else {
+	fmt.Printf("Inserted %d users", len(users))
+}
+
+```
+
+Output:
+```
+Inserted 3 users
+```
+
+If you use the RETURNING clause you can scan into structs or values.
+
+```go
+db := getDb()
+
+insert := db.Insert("goqu_user").Returning(goqu.C("id")).Rows(
+		goqu.Record{"first_name": "Jed", "last_name": "Riley", "created": time.Now()},
+).Executor()
+
+var id int64
+if _, err := insert.ScanVal(&id); err != nil {
+	fmt.Println(err.Error())
+} else {
+	fmt.Printf("Inserted 1 user id:=%d\n", id)
+}
+```
+
+Output:
+
+```
+Inserted 1 user id:=5
+```

+ 56 - 0
src/github.com/doug-martin/goqu/docs/interpolation.md

@@ -0,0 +1,56 @@
+# Prepared Statements
+
+By default the `goqu` will interpolate all parameters, if you do not want to have values interpolated you can use the [`Prepared`](http://godoc.org/github.com/doug-martin/goqu#SelectDataset.Prepared) method to prevent this.
+
+**Note** For the examples all placeholders are `?` this will be dialect specific when using other examples (e.g. Postgres `$1, $2...`)
+
+```go
+
+preparedDs := db.From("items").Prepared(true)
+
+sql, args, _ := preparedDs.Where(goqu.Ex{
+	"col1": "a",
+	"col2": 1,
+	"col3": true,
+	"col4": false,
+	"col5": []string{"a", "b", "c"},
+}).ToSQL()
+fmt.Println(sql, args)
+
+sql, args, _ = preparedDs.Insert().Rows(
+	goqu.Record{"name": "Test1", "address": "111 Test Addr"},
+	goqu.Record{"name": "Test2", "address": "112 Test Addr"},
+).ToSQL()
+fmt.Println(sql, args)
+
+sql, args, _ = preparedDs.Update().Set(
+	goqu.Record{"name": "Test", "address": "111 Test Addr"},
+).ToSQL()
+fmt.Println(sql, args)
+
+sql, args, _ = preparedDs.
+	Delete().
+	Where(goqu.Ex{"id": goqu.Op{"gt": 10}}).
+	ToSQL()
+fmt.Println(sql, args)
+
+// Output:
+// SELECT * FROM "items" WHERE (("col1" = ?) AND ("col2" = ?) AND ("col3" IS TRUE) AND ("col4" IS FALSE) AND ("col5" IN (?, ?, ?))) [a 1 a b c]
+// INSERT INTO "items" ("address", "name") VALUES (?, ?), (?, ?) [111 Test Addr Test1 112 Test Addr Test2]
+// UPDATE "items" SET "address"=?,"name"=? [111 Test Addr Test]
+// DELETE FROM "items" WHERE ("id" > ?) [10]
+```
+
+When setting prepared to true executing the SQL using the different querying methods will also use the non-interpolated SQL also.
+
+```go
+var items []Item
+sql, args, _ := db.From("items").Prepared(true).Where(goqu.Ex{
+	"col1": "a",
+	"col2": 1,
+}).ScanStructs(&items)
+
+//Is the same as
+db.ScanStructs(&items, `SELECT * FROM "items" WHERE (("col1" = ?) AND ("col2" = ?))`,  "a", 1)
+```
+

+ 1255 - 0
src/github.com/doug-martin/goqu/docs/selecting.md

@@ -0,0 +1,1255 @@
+# Selecting
+
+* [Creating a SelectDataset](#create)
+* Building SQL
+  * [`Select`](#select)
+  * [`Distinct`](#distinct)
+  * [`From`](#from)
+  * [`Join`](#joins)
+  * [`Where`](#where)
+  * [`Limit`](#limit)
+  * [`Offset`](#offset)
+  * [`GroupBy`](#group_by)
+  * [`Having`](#having)
+  * [`Window`](#window)
+  * [`With`](#with)
+  * [`SetError`](#seterror)
+* Executing Queries
+  * [`ScanStructs`](#scan-structs) - Scans rows into a slice of structs
+  * [`ScanStruct`](#scan-struct) - Scans a row into a slice a struct, returns false if a row wasnt found
+  * [`ScanVals`](#scan-vals)- Scans a rows of 1 column into a slice of primitive values
+  * [`ScanVal`](#scan-val) - Scans a row of 1 column into a primitive value, returns false if a row wasnt found.
+  * [`Scanner`](#scanner) - Allows you to interatively scan rows into structs or values.
+  * [`Count`](#count) - Returns the count for the current query
+  * [`Pluck`](#pluck) - Selects a single column and stores the results into a slice of primitive values
+
+<a name="create"></a>
+To create a [`SelectDataset`](https://godoc.org/github.com/doug-martin/goqu/#SelectDataset)  you can use
+
+**[`goqu.From`](https://godoc.org/github.com/doug-martin/goqu/#From) and [`goqu.Select`](https://godoc.org/github.com/doug-martin/goqu/#Select)**
+
+When you just want to create some quick SQL, this mostly follows the `Postgres` with the exception of placeholders for prepared statements.
+
+```go
+sql, _, _ := goqu.From("table").ToSQL()
+fmt.Println(sql)
+
+sql, _, _ := goqu.Select(goqu.L("NOW()")).ToSQL()
+fmt.Println(sql)
+```
+Output:
+```
+SELECT * FROM "table"
+SELECT NOW()
+```
+
+**[`DialectWrapper.From`](https://godoc.org/github.com/doug-martin/goqu/#DialectWrapper.From) and [`DialectWrapper.Select`](https://godoc.org/github.com/doug-martin/goqu/#DialectWrapper.Select)**
+
+Use this when you want to create SQL for a specific `dialect`
+
+```go
+// import _ "github.com/doug-martin/goqu/v9/dialect/mysql"
+
+dialect := goqu.Dialect("mysql")
+
+sql, _, _ := dialect.From("table").ToSQL()
+fmt.Println(sql)
+
+sql, _, _ := dialect.Select(goqu.L("NOW()")).ToSQL()
+fmt.Println(sql)
+```
+Output:
+```
+SELECT * FROM `table`
+SELECT NOW()
+```
+
+**[`Database.From`](https://godoc.org/github.com/doug-martin/goqu/#DialectWrapper.From) and [`Database.Select`](https://godoc.org/github.com/doug-martin/goqu/#DialectWrapper.From)**
+
+Use this when you want to execute the SQL or create SQL for the drivers dialect.
+
+```go
+// import _ "github.com/doug-martin/goqu/v9/dialect/mysql"
+
+mysqlDB := //initialize your db
+db := goqu.New("mysql", mysqlDB)
+
+sql, _, _ := db.From("table").ToSQL()
+fmt.Println(sql)
+
+sql, _, _ := db.Select(goqu.L("NOW()")).ToSQL()
+fmt.Println(sql)
+```
+Output:
+```
+SELECT * FROM `table`
+SELECT NOW()
+```
+
+### Examples
+
+For more examples visit the **[Docs](https://godoc.org/github.com/doug-martin/goqu/#SelectDataset)**
+
+<a name="select"></a>
+**[`Select`](https://godoc.org/github.com/doug-martin/goqu/#SelectDataset.Select)**
+
+```go
+sql, _, _ := goqu.From("test").Select("a", "b", "c").ToSQL()
+fmt.Println(sql)
+```
+
+Output:
+```sql
+SELECT "a", "b", "c" FROM "test"
+```
+
+You can also ues another dataset in your select
+
+```go
+ds := goqu.From("test")
+fromDs := ds.Select("age").Where(goqu.C("age").Gt(10))
+sql, _, _ := ds.From().Select(fromDs).ToSQL()
+fmt.Println(sql)
+```
+
+Output:
+```
+SELECT (SELECT "age" FROM "test" WHERE ("age" > 10))
+```
+
+Selecting a literal
+
+```go
+sql, _, _ := goqu.From("test").Select(goqu.L("a + b").As("sum")).ToSQL()
+fmt.Println(sql)
+```
+
+Output:
+```
+SELECT a + b AS "sum" FROM "test"
+```
+
+Select aggregate functions
+
+```go
+sql, _, _ := goqu.From("test").Select(
+	goqu.COUNT("*").As("age_count"),
+	goqu.MAX("age").As("max_age"),
+	goqu.AVG("age").As("avg_age"),
+).ToSQL()
+fmt.Println(sql)
+```
+
+Output:
+```
+SELECT COUNT(*) AS "age_count", MAX("age") AS "max_age", AVG("age") AS "avg_age" FROM "test"
+```
+
+Selecting columns from a struct
+
+```go
+ds := goqu.From("test")
+
+type myStruct struct {
+	Name         string
+	Address      string `db:"address"`
+	EmailAddress string `db:"email_address"`
+}
+
+// Pass with pointer
+sql, _, _ := ds.Select(&myStruct{}).ToSQL()
+fmt.Println(sql)
+```
+
+Output:
+```
+SELECT "address", "email_address", "name" FROM "test"
+```
+
+<a name="distinct"></a>
+**[`Distinct`](https://godoc.org/github.com/doug-martin/goqu/#SelectDataset.Distinct)**
+
+```go
+sql, _, _ := goqu.From("test").Select("a", "b").Distinct().ToSQL()
+fmt.Println(sql)
+```
+
+Output:
+```
+SELECT DISTINCT "a", "b" FROM "test"
+```
+
+If you dialect supports `DISTINCT ON` you provide arguments to the `Distinct` method.
+
+**NOTE** currently only the `postgres` and the default dialects support `DISTINCT ON` clauses
+
+```go
+sql, _, _ := goqu.From("test").Distinct("a").ToSQL()
+fmt.Println(sql)
+```
+Output:
+
+```
+SELECT DISTINCT ON ("a") * FROM "test"
+```
+
+You can also provide other expression arguments
+
+With `goqu.L`
+
+```go
+sql, _, _ := goqu.From("test").Distinct(goqu.L("COALESCE(?, ?)", goqu.C("a"), "empty")).ToSQL()
+fmt.Println(sql)
+```
+Output:
+```
+SELECT DISTINCT ON (COALESCE("a", 'empty')) * FROM "test"
+```
+With `goqu.Coalesce`
+```go
+sql, _, _ := goqu.From("test").Distinct(goqu.COALESCE(goqu.C("a"), "empty")).ToSQL()
+fmt.Println(sql)
+```
+Output:
+```
+SELECT DISTINCT ON (COALESCE("a", 'empty')) * FROM "test"
+```
+
+<a name="from"></a>
+**[`From`](https://godoc.org/github.com/doug-martin/goqu/#SelectDataset.From)**
+
+Overriding the original from
+```go
+ds := goqu.From("test")
+sql, _, _ := ds.From("test2").ToSQL()
+fmt.Println(sql)
+```
+
+Output:
+```
+SELECT * FROM "test2"
+```
+
+From another dataset
+
+```go
+ds := goqu.From("test")
+fromDs := ds.Where(goqu.C("age").Gt(10))
+sql, _, _ := ds.From(fromDs).ToSQL()
+fmt.Println(sql)
+```
+
+Output:
+```
+SELECT * FROM (SELECT * FROM "test" WHERE ("age" > 10)) AS "t1"
+```
+
+From an aliased dataset
+
+```go
+ds := goqu.From("test")
+fromDs := ds.Where(goqu.C("age").Gt(10))
+sql, _, _ := ds.From(fromDs.As("test2")).ToSQL()
+fmt.Println(sql)
+```
+
+Output:
+```
+SELECT * FROM (SELECT * FROM "test" WHERE ("age" > 10)) AS "test2"
+```
+
+Lateral Query
+
+```go
+maxEntry := goqu.From("entry").
+	Select(goqu.MAX("int").As("max_int")).
+	Where(goqu.Ex{"time": goqu.Op{"lt": goqu.I("e.time")}}).
+	As("max_entry")
+
+maxId := goqu.From("entry").
+	Select("id").
+	Where(goqu.Ex{"int": goqu.I("max_entry.max_int")}).
+	As("max_id")
+
+ds := goqu.
+	Select("e.id", "max_entry.max_int", "max_id.id").
+	From(
+		goqu.T("entry").As("e"),
+		goqu.Lateral(maxEntry),
+		goqu.Lateral(maxId),
+	)
+query, args, _ := ds.ToSQL()
+fmt.Println(query, args)
+
+query, args, _ = ds.Prepared(true).ToSQL()
+fmt.Println(query, args)
+```
+
+Output
+```
+SELECT "e"."id", "max_entry"."max_int", "max_id"."id" FROM "entry" AS "e", LATERAL (SELECT MAX("int") AS "max_int" FROM "entry" WHERE ("time" < "e"."time")) AS "max_entry", LATERAL (SELECT "id" FROM "entry" WHERE ("int" = "max_entry"."max_int")) AS "max_id" []
+SELECT "e"."id", "max_entry"."max_int", "max_id"."id" FROM "entry" AS "e", LATERAL (SELECT MAX("int") AS "max_int" FROM "entry" WHERE ("time" < "e"."time")) AS "max_entry", LATERAL (SELECT "id" FROM "entry" WHERE ("int" = "max_entry"."max_int")) AS "max_id" []
+```
+
+<a name="joins"></a>
+**[`Join`](https://godoc.org/github.com/doug-martin/goqu/#SelectDataset.Join)**
+
+```go
+sql, _, _ := goqu.From("test").Join(
+	goqu.T("test2"),
+	goqu.On(goqu.Ex{"test.fkey": goqu.I("test2.Id")}),
+).ToSQL()
+fmt.Println(sql)
+```
+
+Output:
+```
+SELECT * FROM "test" INNER JOIN "test2" ON ("test"."fkey" = "test2"."Id")
+```
+
+[`InnerJoin`](https://godoc.org/github.com/doug-martin/goqu/#SelectDataset.InnerJoin)
+
+```go
+sql, _, _ := goqu.From("test").InnerJoin(
+	goqu.T("test2"),
+	goqu.On(goqu.Ex{"test.fkey": goqu.I("test2.Id")}),
+).ToSQL()
+fmt.Println(sql)
+```
+
+Output:
+```
+SELECT * FROM "test" INNER JOIN "test2" ON ("test"."fkey" = "test2"."Id")
+```
+
+[`FullOuterJoin`](https://godoc.org/github.com/doug-martin/goqu/#SelectDataset.FullOuterJoin)
+
+```go
+sql, _, _ := goqu.From("test").FullOuterJoin(
+	goqu.T("test2"),
+	goqu.On(goqu.Ex{
+		"test.fkey": goqu.I("test2.Id"),
+	}),
+).ToSQL()
+fmt.Println(sql)
+```
+
+Output:
+```
+SELECT * FROM "test" FULL OUTER JOIN "test2" ON ("test"."fkey" = "test2"."Id")
+```
+
+[`RightOuterJoin`](https://godoc.org/github.com/doug-martin/goqu/#SelectDataset.RightOuterJoin)
+
+```go
+sql, _, _ := goqu.From("test").RightOuterJoin(
+	goqu.T("test2"),
+	goqu.On(goqu.Ex{
+		"test.fkey": goqu.I("test2.Id"),
+	}),
+).ToSQL()
+fmt.Println(sql)
+```
+
+Output:
+```
+SELECT * FROM "test" RIGHT OUTER JOIN "test2" ON ("test"."fkey" = "test2"."Id")
+```
+
+[`LeftOuterJoin`](https://godoc.org/github.com/doug-martin/goqu/#SelectDataset.LeftOuterJoin)
+
+```go
+sql, _, _ := goqu.From("test").LeftOuterJoin(
+	goqu.T("test2"),
+	goqu.On(goqu.Ex{
+		"test.fkey": goqu.I("test2.Id"),
+	}),
+).ToSQL()
+fmt.Println(sql)
+```
+
+Output:
+```
+SELECT * FROM "test" LEFT OUTER JOIN "test2" ON ("test"."fkey" = "test2"."Id")
+```
+
+[`FullJoin`](https://godoc.org/github.com/doug-martin/goqu/#SelectDataset.FullJoin)
+
+```go
+sql, _, _ := goqu.From("test").FullJoin(
+	goqu.T("test2"),
+	goqu.On(goqu.Ex{
+		"test.fkey": goqu.I("test2.Id"),
+	}),
+).ToSQL()
+fmt.Println(sql)
+```
+
+Output:
+```
+SELECT * FROM "test" FULL JOIN "test2" ON ("test"."fkey" = "test2"."Id")
+```
+
+
+[`RightJoin`](https://godoc.org/github.com/doug-martin/goqu/#SelectDataset.RightJoin)
+
+```go
+sql, _, _ := goqu.From("test").RightJoin(
+	goqu.T("test2"),
+	goqu.On(goqu.Ex{
+		"test.fkey": goqu.I("test2.Id"),
+	}),
+).ToSQL()
+fmt.Println(sql)
+```
+
+Output:
+```
+SELECT * FROM "test" RIGHT JOIN "test2" ON ("test"."fkey" = "test2"."Id")
+```
+
+[`LeftJoin`](https://godoc.org/github.com/doug-martin/goqu/#SelectDataset.LeftJoin)
+
+```go
+sql, _, _ := goqu.From("test").LeftJoin(
+	goqu.T("test2"),
+	goqu.On(goqu.Ex{
+		"test.fkey": goqu.I("test2.Id"),
+	}),
+).ToSQL()
+fmt.Println(sql)
+```
+
+Output:
+```
+SELECT * FROM "test" LEFT JOIN "test2" ON ("test"."fkey" = "test2"."Id")
+```
+
+[`NaturalJoin`](https://godoc.org/github.com/doug-martin/goqu/#SelectDataset.NaturalJoin)
+
+```go
+sql, _, _ := goqu.From("test").NaturalJoin(goqu.T("test2")).ToSQL()
+fmt.Println(sql)
+```
+
+Output:
+```
+SELECT * FROM "test" NATURAL JOIN "test2"
+```
+
+[`NaturalLeftJoin`](https://godoc.org/github.com/doug-martin/goqu/#SelectDataset.NaturalLeftJoin)
+
+```go
+sql, _, _ := goqu.From("test").NaturalLeftJoin(goqu.T("test2")).ToSQL()
+fmt.Println(sql)
+```
+
+Output:
+```
+SELECT * FROM "test" NATURAL LEFT JOIN "test2"
+```
+
+[`NaturalRightJoin`](https://godoc.org/github.com/doug-martin/goqu/#SelectDataset.NaturalRightJoin)
+
+```go
+sql, _, _ := goqu.From("test").NaturalRightJoin(goqu.T("test2")).ToSQL()
+fmt.Println(sql)
+```
+
+Output:
+```
+SELECT * FROM "test" NATURAL RIGHT LEFT JOIN "test2"
+```
+
+[`NaturalFullJoin`](https://godoc.org/github.com/doug-martin/goqu/#SelectDataset.NaturalFullJoin)
+
+```go
+sql, _, _ := goqu.From("test").NaturalFullJoin(goqu.T("test2")).ToSQL()
+fmt.Println(sql)
+```
+
+Output:
+```
+SELECT * FROM "test" NATURAL FULL LEFT JOIN "test2"
+```
+
+[`CrossJoin`](https://godoc.org/github.com/doug-martin/goqu/#SelectDataset.CrossJoin)
+
+```go
+sql, _, _ := goqu.From("test").CrossJoin(goqu.T("test2")).ToSQL()
+fmt.Println(sql)
+```
+
+Output:
+```
+SELECT * FROM "test" CROSS JOIN "test2"
+```
+
+Join with a Lateral
+
+```go
+maxEntry := goqu.From("entry").
+	Select(goqu.MAX("int").As("max_int")).
+	Where(goqu.Ex{"time": goqu.Op{"lt": goqu.I("e.time")}}).
+	As("max_entry")
+
+maxId := goqu.From("entry").
+	Select("id").
+	Where(goqu.Ex{"int": goqu.I("max_entry.max_int")}).
+	As("max_id")
+
+ds := goqu.
+	Select("e.id", "max_entry.max_int", "max_id.id").
+	From(goqu.T("entry").As("e")).
+	Join(goqu.Lateral(maxEntry), goqu.On(goqu.V(true))).
+	Join(goqu.Lateral(maxId), goqu.On(goqu.V(true)))
+query, args, _ := ds.ToSQL()
+fmt.Println(query, args)
+
+query, args, _ = ds.Prepared(true).ToSQL()
+fmt.Println(query, args)
+```
+
+Output:
+```
+SELECT "e"."id", "max_entry"."max_int", "max_id"."id" FROM "entry" AS "e" INNER JOIN LATERAL (SELECT MAX("int") AS "max_int" FROM "entry" WHERE ("time" < "e"."time")) AS "max_entry" ON TRUE INNER JOIN LATERAL (SELECT "id" FROM "entry" WHERE ("int" = "max_entry"."max_int")) AS "max_id" ON TRUE []
+
+SELECT "e"."id", "max_entry"."max_int", "max_id"."id" FROM "entry" AS "e" INNER JOIN LATERAL (SELECT MAX("int") AS "max_int" FROM "entry" WHERE ("time" < "e"."time")) AS "max_entry" ON ? INNER JOIN LATERAL (SELECT "id" FROM "entry" WHERE ("int" = "max_entry"."max_int")) AS "max_id" ON ? [true true]
+```
+
+<a name="where"></a>
+**[`Where`](https://godoc.org/github.com/doug-martin/goqu/#SelectDataset.Where)**
+
+You can use `goqu.Ex` to create an ANDed condition
+```go
+sql, _, _ := goqu.From("test").Where(goqu.Ex{
+	"a": goqu.Op{"gt": 10},
+	"b": goqu.Op{"lt": 10},
+	"c": nil,
+	"d": []string{"a", "b", "c"},
+}).ToSQL()
+fmt.Println(sql)
+```
+
+Output:
+
+```
+SELECT * FROM "test" WHERE (("a" > 10) AND ("b" < 10) AND ("c" IS NULL) AND ("d" IN ('a', 'b', 'c')))
+```
+
+You can use `goqu.ExOr` to create an ORed condition
+
+```go
+sql, _, _ := goqu.From("test").Where(goqu.ExOr{
+	"a": goqu.Op{"gt": 10},
+	"b": goqu.Op{"lt": 10},
+	"c": nil,
+	"d": []string{"a", "b", "c"},
+}).ToSQL()
+fmt.Println(sql)
+```
+
+Output:
+```
+SELECT * FROM "test" WHERE (("a" > 10) OR ("b" < 10) OR ("c" IS NULL) OR ("d" IN ('a', 'b', 'c')))
+```
+
+You can use `goqu.Ex` with `goqu.ExOr` for complex expressions
+
+```go
+// You can use Or with Ex to Or multiple Ex maps together
+sql, _, _ := goqu.From("test").Where(
+	goqu.Or(
+		goqu.Ex{
+			"a": goqu.Op{"gt": 10},
+			"b": goqu.Op{"lt": 10},
+		},
+		goqu.Ex{
+			"c": nil,
+			"d": []string{"a", "b", "c"},
+		},
+	),
+).ToSQL()
+fmt.Println(sql)
+```
+
+Output:
+
+```
+SELECT * FROM "test" WHERE ((("a" > 10) AND ("b" < 10)) OR (("c" IS NULL) AND ("d" IN ('a', 'b', 'c'))))
+```
+
+You can also use identifiers to create your where condition
+
+```go
+sql, _, _ := goqu.From("test").Where(
+	goqu.C("a").Gt(10),
+	goqu.C("b").Lt(10),
+	goqu.C("c").IsNull(),
+	goqu.C("d").In("a", "b", "c"),
+).ToSQL()
+fmt.Println(sql)
+```
+
+Output:
+```
+SELECT * FROM "test" WHERE (("a" > 10) AND ("b" < 10) AND ("c" IS NULL) AND ("d" IN ('a', 'b', 'c')))
+```
+
+Using `goqu.Or` to create ORed expression
+
+```go
+// You can use a combination of Ors and Ands
+sql, _, _ := goqu.From("test").Where(
+	goqu.Or(
+		goqu.C("a").Gt(10),
+		goqu.And(
+			goqu.C("b").Lt(10),
+			goqu.C("c").IsNull(),
+		),
+	),
+).ToSQL()
+fmt.Println(sql)
+```
+
+Output:
+
+```
+SELECT * FROM "test" WHERE (("a" > 10) OR (("b" < 10) AND ("c" IS NULL)))
+```
+
+<a name="limit"></a>
+**[`Limit`](https://godoc.org/github.com/doug-martin/goqu/#SelectDataset.Limit)**
+
+```go
+ds := goqu.From("test").Limit(10)
+sql, _, _ := ds.ToSQL()
+fmt.Println(sql)
+```
+
+Output:
+
+```
+SELECT * FROM "test" LIMIT 10
+```
+
+<a name="offset"></a>
+**[`Offset`](https://godoc.org/github.com/doug-martin/goqu/#SelectDataset.Offset)**
+
+```go
+ds := goqu.From("test").Offset(2)
+sql, _, _ := ds.ToSQL()
+fmt.Println(sql)
+```
+
+Output:
+
+```
+SELECT * FROM "test" OFFSET 2
+```
+
+<a name="group_by"></a>
+**[`GroupBy`](https://godoc.org/github.com/doug-martin/goqu/#SelectDataset.GroupBy)**
+
+```go
+sql, _, _ := goqu.From("test").
+	Select(goqu.SUM("income").As("income_sum")).
+	GroupBy("age").
+	ToSQL()
+fmt.Println(sql)
+```
+
+Output:
+
+```
+SELECT SUM("income") AS "income_sum" FROM "test" GROUP BY "age"
+```
+
+<a name="having"></a>
+**[`Having`](https://godoc.org/github.com/doug-martin/goqu/#SelectDataset.Having)**
+
+```go
+sql, _, _ = goqu.From("test").GroupBy("age").Having(goqu.SUM("income").Gt(1000)).ToSQL()
+fmt.Println(sql)
+```
+
+Output:
+
+```
+SELECT * FROM "test" GROUP BY "age" HAVING (SUM("income") > 1000)
+```
+
+<a name="with"></a>
+**[`With`](https://godoc.org/github.com/doug-martin/goqu/#SelectDataset.With)**
+
+To use CTEs in `SELECT` statements you can use the `With` method.
+
+Simple Example
+
+```go
+sql, _, _ := goqu.From("one").
+	With("one", goqu.From().Select(goqu.L("1"))).
+	Select(goqu.Star()).
+	ToSQL()
+fmt.Println(sql)
+```
+
+Output:
+
+```
+WITH one AS (SELECT 1) SELECT * FROM "one"
+```
+
+Dependent `WITH` clauses:
+
+```go
+sql, _, _ = goqu.From("derived").
+	With("intermed", goqu.From("test").Select(goqu.Star()).Where(goqu.C("x").Gte(5))).
+	With("derived", goqu.From("intermed").Select(goqu.Star()).Where(goqu.C("x").Lt(10))).
+	Select(goqu.Star()).
+	ToSQL()
+fmt.Println(sql)
+```
+
+Output:
+```
+WITH intermed AS (SELECT * FROM "test" WHERE ("x" >= 5)), derived AS (SELECT * FROM "intermed" WHERE ("x" < 10)) SELECT * FROM "derived"
+```
+
+`WITH` clause with arguments
+
+```go
+sql, _, _ = goqu.From("multi").
+		With("multi(x,y)", goqu.From().Select(goqu.L("1"), goqu.L("2"))).
+		Select(goqu.C("x"), goqu.C("y")).
+		ToSQL()
+fmt.Println(sql)
+```
+
+Output:
+```
+WITH multi(x,y) AS (SELECT 1, 2) SELECT "x", "y" FROM "multi"
+```
+
+Using a `InsertDataset`.
+
+```go
+insertDs := goqu.Insert("foo").Rows(goqu.Record{"user_id": 10}).Returning("id")
+
+ds := goqu.From("bar").
+	With("ins", insertDs).
+	Select("bar_name").
+	Where(goqu.Ex{"bar.user_id": goqu.I("ins.user_id")})
+
+sql, _, _ := ds.ToSQL()
+fmt.Println(sql)
+
+sql, args, _ := ds.Prepared(true).ToSQL()
+fmt.Println(sql, args)
+```
+Output:
+```
+WITH ins AS (INSERT INTO "foo" ("user_id") VALUES (10) RETURNING "id") SELECT "bar_name" FROM "bar" WHERE ("bar"."user_id" = "ins"."user_id")
+WITH ins AS (INSERT INTO "foo" ("user_id") VALUES (?) RETURNING "id") SELECT "bar_name" FROM "bar" WHERE ("bar"."user_id" = "ins"."user_id") [10]
+```
+
+Using an `UpdateDataset`
+
+```go
+updateDs := goqu.Update("foo").Set(goqu.Record{"bar": "baz"}).Returning("id")
+
+ds := goqu.From("bar").
+	With("upd", updateDs).
+	Select("bar_name").
+	Where(goqu.Ex{"bar.user_id": goqu.I("ins.user_id")})
+
+sql, _, _ := ds.ToSQL()
+fmt.Println(sql)
+
+sql, args, _ := ds.Prepared(true).ToSQL()
+fmt.Println(sql, args)
+```
+
+Output:
+```
+WITH upd AS (UPDATE "foo" SET "bar"='baz' RETURNING "id") SELECT "bar_name" FROM "bar" WHERE ("bar"."user_id" = "upd"."user_id")
+WITH upd AS (UPDATE "foo" SET "bar"=? RETURNING "id") SELECT "bar_name" FROM "bar" WHERE ("bar"."user_id" = "upd"."user_id") [baz]
+```
+
+Using a `DeleteDataset`
+
+```go
+deleteDs := goqu.Delete("foo").Where(goqu.Ex{"bar": "baz"}).Returning("id")
+
+ds := goqu.From("bar").
+	With("del", deleteDs).
+	Select("bar_name").
+	Where(goqu.Ex{"bar.user_id": goqu.I("del.user_id")})
+
+sql, _, _ := ds.ToSQL()
+fmt.Println(sql)
+
+sql, args, _ := ds.Prepared(true).ToSQL()
+fmt.Println(sql, args)
+```
+
+Output:
+```
+WITH del AS (DELETE FROM "foo" WHERE ("bar" = 'baz') RETURNING "id") SELECT "bar_name" FROM "bar" WHERE ("bar"."user_id" = "del"."user_id")
+WITH del AS (DELETE FROM "foo" WHERE ("bar" = ?) RETURNING "id") SELECT "bar_name" FROM "bar" WHERE ("bar"."user_id" = "del"."user_id") [baz]
+```
+
+<a name="window"></a>
+**[`Window Function`](https://godoc.org/github.com/doug-martin/goqu/#SelectDataset.Window)**
+
+**NOTE** currently only the `postgres`, `mysql8` (NOT `mysql`) and the default dialect support `Window Function`
+
+To use windowing in `SELECT` statements you can use the `Over` method on an `SQLFunction`
+
+```go
+sql, _, _ := goqu.From("test").Select(
+	goqu.ROW_NUMBER().Over(goqu.W().PartitionBy("a").OrderBy(goqu.I("b").Asc())),
+)
+fmt.Println(sql)
+```
+
+Output:
+
+```
+SELECT ROW_NUMBER() OVER (PARTITION BY "a" ORDER BY "b") FROM "test"
+```
+
+`goqu` also supports the `WINDOW` clause.
+
+```go
+sql, _, _ := goqu.From("test").
+	Select(goqu.ROW_NUMBER().OverName(goqu.I("w"))).
+	Window(goqu.W("w").PartitionBy("a").OrderBy(goqu.I("b").Asc()))
+fmt.Println(sql)
+```
+
+Output:
+
+```
+SELECT ROW_NUMBER() OVER "w" FROM "test" WINDOW "w" AS (PARTITION BY "a" ORDER BY "b")
+```
+
+<a name="seterror"></a>
+**[`SetError`](https://godoc.org/github.com/doug-martin/goqu/#SelectDataset.SetError)**
+
+Sometimes while building up a query with goqu you will encounter situations where certain
+preconditions are not met or some end-user contraint has been violated. While you could
+track this error case separately, goqu provides a convenient built-in mechanism to set an
+error on a dataset if one has not already been set to simplify query building.
+
+Set an Error on a dataset:
+
+```go
+func GetSelect(name string) *goqu.SelectDataset {
+
+    var ds = goqu.From("test")
+
+    if len(name) == 0 {
+        return ds.SetError(fmt.Errorf("name is empty"))
+    }
+
+    return ds.Select(name)
+}
+
+```
+
+This error is returned on any subsequent call to `Error` or `ToSQL`:
+
+```go
+var name string = ""
+ds = GetSelect(name)
+fmt.Println(ds.Error())
+
+sql, args, err = ds.ToSQL()
+fmt.Println(err)
+```
+
+Output:
+```
+name is empty
+name is empty
+```
+
+## Executing Queries
+
+To execute your query use [`goqu.Database#From`](https://godoc.org/github.com/doug-martin/goqu/#Database.From) to create your dataset
+
+<a name="scan-structs"></a>
+**[`ScanStructs`](http://godoc.org/github.com/doug-martin/goqu#SelectDataset.ScanStructs)**
+
+Scans rows into a slice of structs
+
+**NOTE** [`ScanStructs`](http://godoc.org/github.com/doug-martin/goqu#SelectDataset.ScanStructs) will only select the columns that can be scanned in to the structs unless you have explicitly selected certain columns.
+
+ ```go
+type User struct{
+  FirstName string `db:"first_name"`
+  LastName  string `db:"last_name"`
+}
+
+var users []User
+//SELECT "first_name", "last_name" FROM "user";
+if err := db.From("user").ScanStructs(&users); err != nil{
+  panic(err.Error())
+}
+fmt.Printf("\n%+v", users)
+
+var users []User
+//SELECT "first_name" FROM "user";
+if err := db.From("user").Select("first_name").ScanStructs(&users); err != nil{
+  panic(err.Error())
+}
+fmt.Printf("\n%+v", users)
+```
+
+`goqu` also supports scanning into multiple structs. In the example below we define a `Role` and `User` struct that could both be used individually to scan into. However, you can also create a new struct that adds both structs as fields that can be populated in a single query.
+
+**NOTE** When calling `ScanStructs` without a select already defined it will automatically only `SELECT` the columns found in the struct
+
+ ```go
+type Role struct {
+  Id     uint64 `db:"id"`
+	UserID uint64 `db:"user_id"`
+	Name   string `db:"name"`
+}
+type User struct {
+	Id        uint64 `db:"id"`
+	FirstName string `db:"first_name"`
+	LastName  string `db:"last_name"`
+}
+type UserAndRole struct {
+	User User `db:"goqu_user"` // tag as the "goqu_user" table
+	Role Role `db:"user_role"` // tag as "user_role" table
+}
+db := getDb()
+
+ds := db.
+	From("goqu_user").
+	Join(goqu.T("user_role"), goqu.On(goqu.I("goqu_user.id").Eq(goqu.I("user_role.user_id"))))
+var users []UserAndRole
+	// Scan structs will auto build the
+if err := ds.ScanStructs(&users); err != nil {
+	fmt.Println(err.Error())
+	return
+}
+for _, u := range users {
+	fmt.Printf("\n%+v", u)
+}
+```
+
+You can alternatively manually select the columns with the appropriate aliases using the `goqu.C` method to create the alias.
+
+```go
+type Role struct {
+	UserID uint64 `db:"user_id"`
+	Name   string `db:"name"`
+}
+type User struct {
+	Id        uint64 `db:"id"`
+	FirstName string `db:"first_name"`
+	LastName  string `db:"last_name"`
+	Role      Role   `db:"user_role"` // tag as "user_role" table
+}
+db := getDb()
+
+ds := db.
+	Select(
+		"goqu_user.id",
+		"goqu_user.first_name",
+		"goqu_user.last_name",
+		// alias the fully qualified identifier `C` is important here so it doesnt parse it
+		goqu.I("user_role.user_id").As(goqu.C("user_role.user_id")),
+		goqu.I("user_role.name").As(goqu.C("user_role.name")),
+	).
+	From("goqu_user").
+	Join(goqu.T("user_role"), goqu.On(goqu.I("goqu_user.id").Eq(goqu.I("user_role.user_id"))))
+
+var users []User
+if err := ds.ScanStructs(&users); err != nil {
+	fmt.Println(err.Error())
+	return
+}
+for _, u := range users {
+	fmt.Printf("\n%+v", u)
+}
+```
+
+<a name="scan-struct"></a>
+**[`ScanStruct`](http://godoc.org/github.com/doug-martin/goqu#SelectDataset.ScanStruct)**
+
+Scans a row into a slice a struct, returns false if a row wasnt found
+
+**NOTE** [`ScanStruct`](http://godoc.org/github.com/doug-martin/goqu#SelectDataset.ScanStruct) will only select the columns that can be scanned in to the struct unless you have explicitly selected certain columns.
+
+```go
+type User struct{
+  FirstName string `db:"first_name"`
+  LastName  string `db:"last_name"`
+}
+
+var user User
+// SELECT "first_name", "last_name" FROM "user" LIMIT 1;
+found, err := db.From("user").ScanStruct(&user)
+if err != nil{
+  fmt.Println(err.Error())
+  return
+}
+if !found {
+  fmt.Println("No user found")
+} else {
+  fmt.Printf("\nFound user: %+v", user)
+}
+```
+
+`goqu` also supports scanning into multiple structs. In the example below we define a `Role` and `User` struct that could both be used individually to scan into. However, you can also create a new struct that adds both structs as fields that can be populated in a single query.
+
+**NOTE** When calling `ScanStruct` without a select already defined it will automatically only `SELECT` the columns found in the struct
+
+ ```go
+type Role struct {
+	UserID uint64 `db:"user_id"`
+	Name   string `db:"name"`
+}
+type User struct {
+	ID        uint64 `db:"id"`
+	FirstName string `db:"first_name"`
+	LastName  string `db:"last_name"`
+}
+type UserAndRole struct {
+	User User `db:"goqu_user"` // tag as the "goqu_user" table
+	Role Role `db:"user_role"` // tag as "user_role" table
+}
+db := getDb()
+var userAndRole UserAndRole
+ds := db.
+	From("goqu_user").
+	Join(goqu.T("user_role"),goqu.On(goqu.I("goqu_user.id").Eq(goqu.I("user_role.user_id")))).
+	Where(goqu.C("first_name").Eq("Bob"))
+
+found, err := ds.ScanStruct(&userAndRole)
+if err != nil{
+  fmt.Println(err.Error())
+  return
+}
+if !found {
+  fmt.Println("No user found")
+} else {
+  fmt.Printf("\nFound user: %+v", user)
+}
+```
+
+You can alternatively manually select the columns with the appropriate aliases using the `goqu.C` method to create the alias.
+
+```go
+type Role struct {
+	UserID uint64 `db:"user_id"`
+	Name   string `db:"name"`
+}
+type User struct {
+	ID        uint64 `db:"id"`
+	FirstName string `db:"first_name"`
+	LastName  string `db:"last_name"`
+	Role      Role   `db:"user_role"` // tag as "user_role" table
+}
+db := getDb()
+var userAndRole UserAndRole
+ds := db.
+	Select(
+		"goqu_user.id",
+		"goqu_user.first_name",
+		"goqu_user.last_name",
+		// alias the fully qualified identifier `C` is important here so it doesnt parse it
+		goqu.I("user_role.user_id").As(goqu.C("user_role.user_id")),
+		goqu.I("user_role.name").As(goqu.C("user_role.name")),
+	).
+	From("goqu_user").
+	Join(goqu.T("user_role"),goqu.On(goqu.I("goqu_user.id").Eq(goqu.I("user_role.user_id")))).
+	Where(goqu.C("first_name").Eq("Bob"))
+
+found, err := ds.ScanStruct(&userAndRole)
+if err != nil{
+  fmt.Println(err.Error())
+  return
+}
+if !found {
+  fmt.Println("No user found")
+} else {
+  fmt.Printf("\nFound user: %+v", user)
+}
+```
+
+
+**NOTE** Using the `goqu.SetColumnRenameFunction` function, you can change the function that's used to rename struct fields when struct tags aren't defined
+
+```go
+import "strings"
+
+goqu.SetColumnRenameFunction(strings.ToUpper)
+
+type User struct{
+  FirstName string
+  LastName string
+}
+
+var user User
+//SELECT "FIRSTNAME", "LASTNAME" FROM "user" LIMIT 1;
+found, err := db.From("user").ScanStruct(&user)
+// ...
+```
+
+<a name="scan-vals"></a>
+**[`ScanVals`](http://godoc.org/github.com/doug-martin/goqu#SelectDataset.ScanVals)**
+
+Scans a rows of 1 column into a slice of primitive values
+
+```go
+var ids []int64
+if err := db.From("user").Select("id").ScanVals(&ids); err != nil{
+  fmt.Println(err.Error())
+  return
+}
+fmt.Printf("\n%+v", ids)
+```
+
+<a name="scan-val"></a>
+[`ScanVal`](http://godoc.org/github.com/doug-martin/goqu#SelectDataset.ScanVal)
+
+Scans a row of 1 column into a primitive value, returns false if a row wasnt found.
+
+**Note** when using the dataset a `LIMIT` of 1 is automatically applied.
+```go
+var id int64
+found, err := db.From("user").Select("id").ScanVal(&id)
+if err != nil{
+  fmt.Println(err.Error())
+  return
+}
+if !found{
+  fmt.Println("No id found")
+}else{
+  fmt.Printf("\nFound id: %d", id)
+}
+```
+
+<a name="scanner"></a>
+**[`Scanner`](http://godoc.org/github.com/doug-martin/goqu/exec#Scanner)**
+
+Scanner knows how to scan rows into structs. This is useful when dealing with large result sets where you can have only one item scanned in memory at one time.
+
+In the following example we scan each row into struct.
+
+```go
+
+type User struct {
+	FirstName string `db:"first_name"`
+	LastName  string `db:"last_name"`
+}
+db := getDb()
+
+scanner, err := db.
+  From("goqu_user").
+	Select("first_name", "last_name").
+	Where(goqu.Ex{
+		"last_name": "Yukon",
+	}).
+	Executor().
+	Scanner()
+
+if err != nil {
+	fmt.Println(err.Error())
+	return
+}
+
+defer scanner.Close()
+
+for scanner.Next() {
+	u := User{}
+
+	err = scanner.ScanStruct(&u)
+	if err != nil {
+		fmt.Println(err.Error())
+		return
+	}
+
+	fmt.Printf("\n%+v", u)
+}
+
+if scanner.Err() != nil {
+	fmt.Println(scanner.Err().Error())
+}
+```
+
+In this example we scan each row into a val.
+```go
+db := getDb()
+
+scanner, err := db.
+	From("goqu_user").
+	Select("first_name").
+	Where(goqu.Ex{
+		"last_name": "Yukon",
+	}).
+	Executor().
+	Scanner()
+
+if err != nil {
+	fmt.Println(err.Error())
+	return
+}
+
+defer scanner.Close()
+
+for scanner.Next() {
+	name := ""
+
+	err = scanner.ScanVal(&name)
+	if err != nil {
+		fmt.Println(err.Error())
+		return
+	}
+
+	fmt.Println(name)
+}
+
+if scanner.Err() != nil {
+	fmt.Println(scanner.Err().Error())
+}
+```
+
+<a name="count"></a>
+**[`Count`](http://godoc.org/github.com/doug-martin/goqu#SelectDataset.Count)**
+
+Returns the count for the current query
+
+```go
+count, err := db.From("user").Count()
+if err != nil{
+  fmt.Println(err.Error())
+  return
+}
+fmt.Printf("\nCount:= %d", count)
+```
+
+<a name="pluck"></a>
+**[`Pluck`](http://godoc.org/github.com/doug-martin/goqu#SelectDataset.Pluck)**
+
+Selects a single column and stores the results into a slice of primitive values
+
+```go
+var ids []int64
+if err := db.From("user").Pluck(&ids, "id"); err != nil{
+  fmt.Println(err.Error())
+  return
+}
+fmt.Printf("\nIds := %+v", ids)
+```
+
+
+
+
+

+ 62 - 0
src/github.com/doug-martin/goqu/docs/time.md

@@ -0,0 +1,62 @@
+# Working with time.Time
+
+By default when interpolating `time.Time` (and `*time.Time`) `goqu` will convert it `UTC` before interpolating.
+
+## Why?
+
+For most use cases `UTC` should be preferred, if a timezone is specified it is usually ignored silently by `postgres` and `mysql` unless you configure your DB to run in a different timezone, leading to unexpected behavior.
+
+## How to use a different default timezone?
+`goqu` provides a **_global_** configuration settings to set the [location](https://golang.org/pkg/time/#Location) to convert all timestamps to. 
+
+To change the default timezone to covert time instances to you can use [`goqu.SetTimeLocation`](https://godoc.org/github.com/doug-martin/goqu#SetTimeLocation) to change the default timezone.
+
+In the following example the default value `UTC` is used.
+
+```go
+created, err := time.Parse(time.RFC3339, "2019-10-01T15:01:00Z")
+if err != nil {
+	panic(err)
+}
+
+ds := goqu.Insert("test").Rows(goqu.Record{
+	"address": "111 Address",
+	"name":    "Bob Yukon",
+	"created": created,
+})
+```
+
+Output:
+```
+INSERT INTO "test" ("address", "created", "name") VALUES ('111 Address', '2019-10-01T15:01:00Z', 'Bob Yukon')
+```
+
+In the following example `UTC` is overridden to `Asia/Shanghai`
+
+```go
+loc, err := time.LoadLocation("Asia/Shanghai")
+if err != nil {
+	panic(err)
+}
+
+goqu.SetTimeLocation(loc)
+
+created, err := time.Parse(time.RFC3339, "2019-10-01T15:01:00Z")
+if err != nil {
+	panic(err)
+}
+
+ds := goqu.Insert("test").Rows(goqu.Record{
+	"address": "111 Address",
+	"name":    "Bob Yukon",
+	"created": created,
+})
+```
+
+Output:
+```
+INSERT INTO "test" ("address", "created", "name") VALUES ('111 Address', '2019-10-01T23:01:00+08:00', 'Bob Yukon')
+```
+
+
+

+ 517 - 0
src/github.com/doug-martin/goqu/docs/updating.md

@@ -0,0 +1,517 @@
+# Updating
+
+* [Create a UpdateDataset](#create)
+* Examples
+  * [Set with `goqu.Record`](#set-record)
+  * [Set with struct](#set-struct)
+  * [Set with map](#set-map)
+  * [Multi Table](#from)
+  * [Where](#where)
+  * [Order](#order)
+  * [Limit](#limit)
+  * [Returning](#returning)
+  * [SetError](#seterror)
+  * [Executing](#executing)
+
+<a name="create"></a>
+To create a [`UpdateDataset`](https://godoc.org/github.com/doug-martin/goqu/#UpdateDataset)  you can use
+
+**[`goqu.Update`](https://godoc.org/github.com/doug-martin/goqu/#Update)**
+
+When you just want to create some quick SQL, this mostly follows the `Postgres` with the exception of placeholders for prepared statements.
+
+```go
+ds := goqu.Update("user").Set(
+    goqu.Record{"first_name": "Greg", "last_name": "Farley"},
+)
+updateSQL, _, _ := ds.ToSQL()
+fmt.Println(insertSQL, args)
+```
+Output:
+```
+UPDATE "user" SET "first_name"='Greg', "last_name"='Farley'
+```
+
+**[`SelectDataset.Update`](https://godoc.org/github.com/doug-martin/goqu/#SelectDataset.Update)**
+
+If you already have a `SelectDataset` you can invoke `Update()` to get a `UpdateDataset`
+
+**NOTE** This method will also copy over the `WITH`, `WHERE`, `ORDER`, and `LIMIT` clauses from the update
+
+```go
+ds := goqu.From("user")
+
+updateSQL, _, _ := ds.Update().Set(
+    goqu.Record{"first_name": "Greg", "last_name": "Farley"},
+).ToSQL()
+fmt.Println(insertSQL, args)
+
+updateSQL, _, _ = ds.Where(goqu.C("first_name").Eq("Gregory")).Update().Set(
+    goqu.Record{"first_name": "Greg", "last_name": "Farley"},
+).ToSQL()
+fmt.Println(insertSQL, args)
+```
+Output:
+```
+UPDATE "user" SET "first_name"='Greg', "last_name"='Farley'
+UPDATE "user" SET "first_name"='Greg', "last_name"='Farley' WHERE "first_name"='Gregory'
+```
+
+**[`DialectWrapper.Update`](https://godoc.org/github.com/doug-martin/goqu/#DialectWrapper.Update)**
+
+Use this when you want to create SQL for a specific `dialect`
+
+```go
+// import _ "github.com/doug-martin/goqu/v9/dialect/mysql"
+
+dialect := goqu.Dialect("mysql")
+
+ds := dialect.Update("user").Set(
+    goqu.Record{"first_name": "Greg", "last_name": "Farley"},
+)
+updateSQL, _, _ := ds.ToSQL()
+fmt.Println(insertSQL, args)
+```
+Output:
+```
+UPDATE `user` SET `first_name`='Greg', `last_name`='Farley'
+```
+
+**[`Database.Update`](https://godoc.org/github.com/doug-martin/goqu/#DialectWrapper.Update)**
+
+Use this when you want to execute the SQL or create SQL for the drivers dialect.
+
+```go
+// import _ "github.com/doug-martin/goqu/v9/dialect/mysql"
+
+mysqlDB := //initialize your db
+db := goqu.New("mysql", mysqlDB)
+
+ds := db.Update("user").Set(
+    goqu.Record{"first_name": "Greg", "last_name": "Farley"},
+)
+updateSQL, _, _ := ds.ToSQL()
+fmt.Println(insertSQL, args)
+```
+Output:
+```
+UPDATE `user` SET `first_name`='Greg', `last_name`='Farley'
+```
+
+### Examples
+
+For more examples visit the **[Docs](https://godoc.org/github.com/doug-martin/goqu/#UpdateDataset)**
+
+<a name="set-record"></a>
+**[Set with `goqu.Record`](https://godoc.org/github.com/doug-martin/goqu/#UpdateDataset.Set)**
+
+```go
+sql, args, _ := goqu.Update("items").Set(
+	goqu.Record{"name": "Test", "address": "111 Test Addr"},
+).ToSQL()
+fmt.Println(sql, args)
+```
+
+Output:
+```
+UPDATE "items" SET "address"='111 Test Addr',"name"='Test' []
+```
+
+<a name="set-struct"></a>
+**[Set with Struct](https://godoc.org/github.com/doug-martin/goqu/#UpdateDataset.Set)**
+
+```go
+type item struct {
+	Address string `db:"address"`
+	Name    string `db:"name"`
+}
+sql, args, _ := goqu.Update("items").Set(
+	item{Name: "Test", Address: "111 Test Addr"},
+).ToSQL()
+fmt.Println(sql, args)
+```
+
+Output:
+```
+UPDATE "items" SET "address"='111 Test Addr',"name"='Test' []
+```
+
+With structs you can also skip fields by using the `skipupdate` tag
+
+```go
+type item struct {
+	Address string `db:"address"`
+	Name    string `db:"name" goqu:"skipupdate"`
+}
+sql, args, _ := goqu.Update("items").Set(
+	item{Name: "Test", Address: "111 Test Addr"},
+).ToSQL()
+fmt.Println(sql, args)
+```
+
+Output:
+```
+UPDATE "items" SET "address"='111 Test Addr' []
+```
+
+If you want to use the database `DEFAULT` when the struct field is a zero value you can use the `defaultifempty` tag.
+
+```go
+type item struct {
+	Address string `db:"address"`
+	Name    string `db:"name" goqu:"defaultifempty"`
+}
+sql, args, _ := goqu.Update("items").Set(
+	item{Address: "111 Test Addr"},
+).ToSQL()
+fmt.Println(sql, args)
+```
+
+Output:
+```
+UPDATE "items" SET "address"='111 Test Addr',"name"=DEFAULT []
+```
+
+`goqu` will also use fields in embedded structs when creating an update.
+
+**NOTE** unexported fields will be ignored!
+
+```go
+type Address struct {
+	Street string `db:"address_street"`
+	State  string `db:"address_state"`
+}
+type User struct {
+	Address
+	FirstName string
+	LastName  string
+}
+ds := goqu.Update("user").Set(
+	User{Address: Address{Street: "111 Street", State: "NY"}, FirstName: "Greg", LastName: "Farley"},
+)
+updateSQL, args, _ := ds.ToSQL()
+fmt.Println(updateSQL, args)
+```
+
+Output:
+```
+UPDATE "user" SET "address_state"='NY',"address_street"='111 Street',"firstname"='Greg',"lastname"='Farley' []
+```
+
+**NOTE** When working with embedded pointers if the embedded struct is nil then the fields will be ignored.
+
+```go
+type Address struct {
+	Street string
+	State  string
+}
+type User struct {
+	*Address
+	FirstName string
+	LastName  string
+}
+ds := goqu.Update("user").Set(
+	User{FirstName: "Greg", LastName: "Farley"},
+)
+updateSQL, args, _ := ds.ToSQL()
+fmt.Println(updateSQL, args)
+```
+
+Output:
+```
+UPDATE "user" SET "firstname"='Greg',"lastname"='Farley' []
+```
+
+You can ignore an embedded struct or struct pointer by using `db:"-"`
+
+```go
+type Address struct {
+	Street string
+	State  string
+}
+type User struct {
+	Address   `db:"-"`
+	FirstName string
+	LastName  string
+}
+ds := goqu.Update("user").Set(
+	User{Address: Address{Street: "111 Street", State: "NY"}, FirstName: "Greg", LastName: "Farley"},
+)
+updateSQL, args, _ := ds.ToSQL()
+fmt.Println(updateSQL, args)
+```
+
+Output:
+```
+UPDATE "user" SET "firstname"='Greg',"lastname"='Farley' []
+```
+
+
+<a name="set-map"></a>
+**[Set with Map](https://godoc.org/github.com/doug-martin/goqu/#UpdateDataset.Set)**
+
+```go
+sql, args, _ := goqu.Update("items").Set(
+	map[string]interface{}{"name": "Test", "address": "111 Test Addr"},
+).ToSQL()
+fmt.Println(sql, args)
+```
+
+Output:
+```
+UPDATE "items" SET "address"='111 Test Addr',"name"='Test' []
+```
+
+<a name="from"></a>
+**[From / Multi Table](https://godoc.org/github.com/doug-martin/goqu/#UpdateDataset.From)**
+
+`goqu` allows joining multiple tables in a update clause through `From`.
+
+**NOTE** The `sqlite3` adapter does not support a multi table syntax.
+
+`Postgres` Example
+
+```go
+dialect := goqu.Dialect("postgres")
+
+ds := dialect.Update("table_one").
+    Set(goqu.Record{"foo": goqu.I("table_two.bar")}).
+    From("table_two").
+    Where(goqu.Ex{"table_one.id": goqu.I("table_two.id")})
+
+sql, _, _ := ds.ToSQL()
+fmt.Println(sql)
+```
+
+Output:
+```sql
+UPDATE "table_one" SET "foo"="table_two"."bar" FROM "table_two" WHERE ("table_one"."id" = "table_two"."id")
+```
+
+`MySQL` Example
+
+```go
+dialect := goqu.Dialect("mysql")
+
+ds := dialect.Update("table_one").
+    Set(goqu.Record{"foo": goqu.I("table_two.bar")}).
+    From("table_two").
+    Where(goqu.Ex{"table_one.id": goqu.I("table_two.id")})
+
+sql, _, _ := ds.ToSQL()
+fmt.Println(sql)
+```
+Output:
+```sql
+UPDATE `table_one`,`table_two` SET `foo`=`table_two`.`bar` WHERE (`table_one`.`id` = `table_two`.`id`)
+```
+
+<a name="where"></a>
+**[Where](https://godoc.org/github.com/doug-martin/goqu/#UpdateDataset.Where)**
+
+```go
+sql, _, _ := goqu.Update("test").
+	Set(goqu.Record{"foo": "bar"}).
+	Where(goqu.Ex{
+		"a": goqu.Op{"gt": 10},
+		"b": goqu.Op{"lt": 10},
+		"c": nil,
+		"d": []string{"a", "b", "c"},
+	}).ToSQL()
+fmt.Println(sql)
+```
+
+Output:
+```
+UPDATE "test" SET "foo"='bar' WHERE (("a" > 10) AND ("b" < 10) AND ("c" IS NULL) AND ("d" IN ('a', 'b', 'c')))
+```
+
+<a name="order"></a>
+**[Order](https://godoc.org/github.com/doug-martin/goqu/#UpdateDataset.Order)**
+
+**NOTE** This will only work if your dialect supports it
+
+```go
+// import _ "github.com/doug-martin/goqu/v9/dialect/mysql"
+
+ds := goqu.Dialect("mysql").
+	Update("test").
+	Set(goqu.Record{"foo": "bar"}).
+	Order(goqu.C("a").Asc())
+sql, _, _ := ds.ToSQL()
+fmt.Println(sql)
+```
+
+Output:
+```
+UPDATE `test` SET `foo`='bar' ORDER BY `a` ASC
+```
+
+<a name="limit"></a>
+**[Order](https://godoc.org/github.com/doug-martin/goqu/#UpdateDataset.Limit)**
+
+**NOTE** This will only work if your dialect supports it
+
+```go
+// import _ "github.com/doug-martin/goqu/v9/dialect/mysql"
+
+ds := goqu.Dialect("mysql").
+	Update("test").
+	Set(goqu.Record{"foo": "bar"}).
+	Limit(10)
+sql, _, _ := ds.ToSQL()
+fmt.Println(sql)
+```
+
+Output:
+```
+UPDATE `test` SET `foo`='bar' LIMIT 10
+```
+
+<a name="returning"></a>
+**[Returning](https://godoc.org/github.com/doug-martin/goqu/#UpdateDataset.Returning)**
+
+Returning a single column example.
+
+```go
+sql, _, _ := goqu.Update("test").
+	Set(goqu.Record{"foo": "bar"}).
+	Returning("id").
+	ToSQL()
+fmt.Println(sql)
+```
+
+Output:
+```
+UPDATE "test" SET "foo"='bar' RETURNING "id"
+```
+
+Returning multiple columns
+
+```go
+sql, _, _ := goqu.Update("test").
+	Set(goqu.Record{"foo": "bar"}).
+	Returning("a", "b").
+	ToSQL()
+fmt.Println(sql)
+```
+
+Output:
+```
+UPDATE "test" SET "foo"='bar' RETURNING "a", "b"
+```
+
+Returning all columns
+
+```go
+sql, _, _ := goqu.Update("test").
+	Set(goqu.Record{"foo": "bar"}).
+	Returning(goqu.T("test").All()).
+	ToSQL()
+fmt.Println(sql)
+```
+
+Output:
+```
+UPDATE "test" SET "foo"='bar' RETURNING "test".*
+```
+
+<a name="seterror"></a>
+**[`SetError`](https://godoc.org/github.com/doug-martin/goqu/#UpdateDataset.SetError)**
+
+Sometimes while building up a query with goqu you will encounter situations where certain
+preconditions are not met or some end-user contraint has been violated. While you could
+track this error case separately, goqu provides a convenient built-in mechanism to set an
+error on a dataset if one has not already been set to simplify query building.
+
+Set an Error on a dataset:
+
+```go
+func GetUpdate(name string, value string) *goqu.UpdateDataset {
+
+    var ds = goqu.Update("test")
+
+    if len(name) == 0 {
+        return ds.SetError(fmt.Errorf("name is empty"))
+    }
+
+    if len(value) == 0 {
+        return ds.SetError(fmt.Errorf("value is empty"))
+    }
+
+    return ds.Set(goqu.Record{name: value})
+}
+
+```
+
+This error is returned on any subsequent call to `Error` or `ToSQL`:
+
+```go
+var field, value string
+ds = GetUpdate(field, value)
+fmt.Println(ds.Error())
+
+sql, args, err = ds.ToSQL()
+fmt.Println(err)
+```
+
+Output:
+```
+name is empty
+name is empty
+```
+
+<a name="executing"></a>
+## Executing Updates
+
+To execute Updates use [`goqu.Database#Update`](https://godoc.org/github.com/doug-martin/goqu/#Database.Update) to create your dataset
+
+### Examples
+
+**Executing an update**
+```go
+db := getDb()
+
+update := db.Update("goqu_user").
+	Where(goqu.C("first_name").Eq("Bob")).
+	Set(goqu.Record{"first_name": "Bobby"}).
+	Executor()
+
+if r, err := update.Exec(); err != nil {
+	fmt.Println(err.Error())
+} else {
+	c, _ := r.RowsAffected()
+	fmt.Printf("Updated %d users", c)
+}
+```
+
+Output:
+
+```
+Updated 1 users
+```
+
+**Executing with Returning**
+
+```go
+db := getDb()
+
+update := db.Update("goqu_user").
+	Set(goqu.Record{"last_name": "ucon"}).
+	Where(goqu.Ex{"last_name": "Yukon"}).
+	Returning("id").
+	Executor()
+
+var ids []int64
+if err := update.ScanVals(&ids); err != nil {
+	fmt.Println(err.Error())
+} else {
+	fmt.Printf("Updated users with ids %+v", ids)
+}
+
+```
+
+Output:
+```
+Updated users with ids [1 2 3]
+```

+ 212 - 0
src/github.com/doug-martin/goqu/docs/version_migration.md

@@ -0,0 +1,212 @@
+## Migrating Between Versions
+
+* [To v8](#v8)
+* [To v7](#v7)
+
+<a name="v8"></a>
+### `v7 to v8`
+
+A major change the the API was made in `v8` to seperate concerns between the different SQL statement types. 
+
+**Why the change?**
+
+1. There were feature requests that could not be cleanly implemented with everything in a single dataset. 
+2. Too much functionality was encapsulated in a single datastructure.
+    * It was unclear what methods could be used for each SQL statement type.
+    * Changing a feature for one statement type had the possiblity of breaking another statement type.
+    * Test coverage was decent but was almost solely concerned about SELECT statements, breaking them up allowed for focused testing on each statement type.
+    * Most the SQL generation methods (`ToInsertSQL`, `ToUpdateSQL` etc.) took arguments which lead to an ugly API that was not uniform for each statement type, and proved to be inflexible.
+
+**What Changed**
+
+There are now five dataset types, `SelectDataset`, `InsertDataset`, `UpdateDataset`, `DeleteDataset` and `TruncateDataset`
+
+Each dataset type has its own entry point.
+
+* `goqu.From`, `Database#From`, `DialectWrapper#From` - Create SELECT
+* `goqu.Insert`, `Database#Insert`, `DialectWrapper#Insert` - Create INSERT
+* `goqu.Update`, `Database#db.Update`, `DialectWrapper#Update` - Create UPDATE
+* `goqu.Delete`, `Database#Delete`, `DialectWrapper#Delete` - Create DELETE
+* `goqu.Truncate`, `Database#Truncate`, `DialectWrapper#Truncate` - Create TRUNCATE
+  
+`ToInsertSQL`, `ToUpdateSQL`, `ToDeleteSQL`, and `ToTruncateSQL` (and variations of them) methods have been removed from the `SelectDataset`. Instead use the `ToSQL` methods on each dataset type.
+
+Each dataset type will have an `Executor` and `ToSQL` method so a common interface can be created for each type.
+
+**How to insert.**
+
+In older versions of `goqu` there was `ToInsertSQL` method on the `Dataset` in the latest version there is a new entry point to create INSERTS.
+
+```go
+// old way
+goqu.From("test").ToInsertSQL(...rows)
+
+// new way
+goqu.Insert("test").Rows(...rows).ToSQL()
+goqu.From("test").Insert().Rows(...rows).ToSQL()
+```
+
+In older versions of `goqu` there was `Insert` method on the `Dataset` to execute an insert in the latest version there is a new `Exectutor` method.
+
+```go
+// old way
+db.From("test").Insert(...rows).Exec()
+
+// new way
+db.Insert("test").Rows(...rows).Executor().Exec()
+// or
+db.From("test").Insert().Rows(...rows).Executor().Exec()
+```
+
+The new `InsertDataset` also has an `OnConflict` method that replaces the `ToInsertConflictSQL` and `ToInsertIgnoreSQL`
+
+```go
+// old way
+goqu.From("test").ToInsertIgnoreSQL(...rows)
+
+// new way
+goqu.Insert("test").Rows(...rows).OnConflict(goqu.DoNothing()).ToSQL()
+// OR
+goqu.From("test").Insert().Rows(...rows).OnConflict(goqu.DoNothing()).ToSQL()
+```
+
+```go
+// old way
+goqu.From("items").ToInsertConflictSQL(
+    goqu.DoUpdate("key", goqu.Record{"updated": goqu.L("NOW()")}),
+    goqu.Record{"name": "Test1", "address": "111 Test Addr"},
+    goqu.Record{"name": "Test2", "address": "112 Test Addr"},
+)
+fmt.Println(sql, args)
+
+// new way
+goqu.Insert("test").
+  Rows(
+    goqu.Record{"name": "Test1", "address": "111 Test Addr"},
+    goqu.Record{"name": "Test2", "address": "112 Test Addr"},
+  ).
+  OnConflict(goqu.DoUpdate("key", goqu.Record{"updated": goqu.L("NOW()")})).
+  ToSQL()
+// OR
+goqu.From("test").
+  Insert().
+  Rows(
+    goqu.Record{"name": "Test1", "address": "111 Test Addr"},
+    goqu.Record{"name": "Test2", "address": "112 Test Addr"},
+  ).
+  OnConflict(goqu.DoUpdate("key", goqu.Record{"updated": goqu.L("NOW()")})).
+  ToSQL()
+```
+
+**How to update.**
+
+In older versions of `goqu` there was `ToUpdateSQL` method on the `Dataset` in the latest version there is a new entry point to create UPDATEs.
+
+```go
+// old way
+goqu.From("items").ToUpdateSQL(
+    goqu.Record{"name": "Test", "address": "111 Test Addr"},
+)
+
+// new way
+goqu.Update("items").
+  Set(goqu.Record{"name": "Test", "address": "111 Test Addr"}).
+  ToSQL()
+// OR
+goqu.From("items").
+  Update()
+  Set(goqu.Record{"name": "Test", "address": "111 Test Addr"}).
+  ToSQL()
+```
+
+In older versions of `goqu` there was `Insert` method on the `Dataset` to execute an insert in the latest version there is a new `Exectutor` method.
+
+```go
+// old way
+db.From("items").Update(
+    goqu.Record{"name": "Test", "address": "111 Test Addr"},
+).Exec()
+
+// new way
+db.Update("items").
+  Set(goqu.Record{"name": "Test", "address": "111 Test Addr"}).
+  Executor().Exec()
+// OR
+db.From("items").
+  Update().
+  Set(goqu.Record{"name": "Test", "address": "111 Test Addr"}).
+  Executor().Exec()
+
+```
+
+**How to delete.**
+
+In older versions of `goqu` there was `ToDeleteSQL` method on the `Dataset` in the latest version there is a new entry point to create DELETEs.
+
+```go
+// old way
+goqu.From("items").
+  Where(goqu.Ex{"id": goqu.Op{"gt": 10}}).
+  ToDeleteSQL()
+
+// new way
+goqu.Delete("items").
+  Where(goqu.Ex{"id": goqu.Op{"gt": 10}}).
+  ToSQL()
+// OR
+goqu.From("items").
+  Delete()
+  Where(goqu.Ex{"id": goqu.Op{"gt": 10}}).
+  ToSQL()
+```
+
+In older versions of `goqu` there was `Delete` method on the `Dataset` to execute an insert in the latest version there is a new `Exectutor` method.
+
+```go
+// old way
+db.From("items").
+  Where(goqu.Ex{"id": goqu.Op{"gt": 10}}).
+  Delete().Exec()
+
+// new way
+db.Delete("items").
+  Where(goqu.Ex{"id": goqu.Op{"gt": 10}}).
+  Executor().Exec()
+// OR
+db.From("items").
+  Delete()
+  Where(goqu.Ex{"id": goqu.Op{"gt": 10}}).
+  Executor().Exec()
+```
+
+**How to truncate.**
+
+In older versions of `goqu` there was `ToTruncateSQL` method on the `Dataset` in the latest version there is a new entry point to create TRUNCATEs.
+
+```go
+// old way
+goqu.From("items").ToTruncateSQL()
+
+// new way
+goqu.Truncate("items").ToSQL()
+```
+
+<a name="v7"></a>
+### `<v7 to v7`
+
+* Updated all sql generations methods to from `Sql` to `SQL`
+    * `ToSql` -> `ToSQL`
+    * `ToInsertSql` -> `ToInsertSQL`
+    * `ToUpdateSql` -> `ToUpdateSQL`
+    * `ToDeleteSql` -> `ToDeleteSQL`
+    * `ToTruncateSql` -> `ToTruncateSQL`
+* Abstracted out `dialect_options` from the adapter to make the dialect self contained.
+    * This also removed the `dataset<->adapter` co dependency making the dialect self contained.
+    * Added new dialect options to specify the order than SQL statements are built.
+* Refactored the `goqu.I` method.
+    * Added new `goqu.S`, `goqu.T` and `goqu.C` methods to clarify why type of identifier you are using.
+    * `goqu.I` should only be used when you have a qualified identifier (e.g. `goqu.I("my_schema.my_table.my_col")
+* Added new `goqu.Dialect` method to make using `goqu` as an SQL builder easier.
+
+
+

+ 282 - 0
src/github.com/doug-martin/goqu/exec/query_executor.go

@@ -0,0 +1,282 @@
+package exec
+
+import (
+	"context"
+	gsql "database/sql"
+	"reflect"
+
+	"github.com/doug-martin/goqu/v9/internal/errors"
+	"github.com/doug-martin/goqu/v9/internal/util"
+)
+
+type (
+	QueryExecutor struct {
+		de    DbExecutor
+		err   error
+		query string
+		args  []interface{}
+	}
+)
+
+var (
+	errUnsupportedScanStructType  = errors.New("type must be a pointer to a struct when scanning into a struct")
+	errUnsupportedScanStructsType = errors.New("type must be a pointer to a slice when scanning into structs")
+	errUnsupportedScanValsType    = errors.New("type must be a pointer to a slice when scanning into vals")
+	errScanValPointer             = errors.New("type must be a pointer when scanning into val")
+	errScanValNonSlice            = errors.New("type cannot be a pointer to a slice when scanning into val")
+)
+
+func newQueryExecutor(de DbExecutor, err error, query string, args ...interface{}) QueryExecutor {
+	return QueryExecutor{de: de, err: err, query: query, args: args}
+}
+
+func (q QueryExecutor) ToSQL() (sql string, args []interface{}, err error) {
+	return q.query, q.args, q.err
+}
+
+func (q QueryExecutor) Exec() (gsql.Result, error) {
+	return q.ExecContext(context.Background())
+}
+
+func (q QueryExecutor) ExecContext(ctx context.Context) (gsql.Result, error) {
+	if q.err != nil {
+		return nil, q.err
+	}
+	return q.de.ExecContext(ctx, q.query, q.args...)
+}
+
+func (q QueryExecutor) Query() (*gsql.Rows, error) {
+	return q.QueryContext(context.Background())
+}
+
+func (q QueryExecutor) QueryContext(ctx context.Context) (*gsql.Rows, error) {
+	if q.err != nil {
+		return nil, q.err
+	}
+	return q.de.QueryContext(ctx, q.query, q.args...)
+}
+
+// This will execute the SQL and append results to the slice
+//    var myStructs []MyStruct
+//    if err := db.From("test").ScanStructs(&myStructs); err != nil{
+//        panic(err.Error()
+//    }
+//    //use your structs
+//
+//
+// i: A pointer to a slice of structs.
+func (q QueryExecutor) ScanStructs(i interface{}) error {
+	return q.ScanStructsContext(context.Background(), i)
+}
+
+// This will execute the SQL and append results to the slice
+//    var myStructs []MyStruct
+//    if err := db.From("test").ScanStructsContext(ctx, &myStructs); err != nil{
+//        panic(err.Error()
+//    }
+//    //use your structs
+//
+//
+// i: A pointer to a slice of structs.
+func (q QueryExecutor) ScanStructsContext(ctx context.Context, i interface{}) error {
+	val := reflect.ValueOf(i)
+	if !util.IsPointer(val.Kind()) {
+		return errUnsupportedScanStructsType
+	}
+	val = reflect.Indirect(val)
+	if !util.IsSlice(val.Kind()) {
+		return errUnsupportedScanStructsType
+	}
+
+	return q.scanIntoSlice(ctx, val, func(sc Scanner, r interface{}) error {
+		return sc.ScanStruct(r)
+	})
+}
+
+// This will execute the SQL and fill out the struct with the fields returned.
+// This method returns a boolean value that is false if no record was found
+//    var myStruct MyStruct
+//    found, err := db.From("test").Limit(1).ScanStruct(&myStruct)
+//    if err != nil{
+//        panic(err.Error()
+//    }
+//    if !found{
+//          fmt.Println("NOT FOUND")
+//    }
+//
+// i: A pointer to a struct
+func (q QueryExecutor) ScanStruct(i interface{}) (bool, error) {
+	return q.ScanStructContext(context.Background(), i)
+}
+
+// This will execute the SQL and fill out the struct with the fields returned.
+// This method returns a boolean value that is false if no record was found
+//    var myStruct MyStruct
+//    found, err := db.From("test").Limit(1).ScanStructContext(ctx, &myStruct)
+//    if err != nil{
+//        panic(err.Error()
+//    }
+//    if !found{
+//          fmt.Println("NOT FOUND")
+//    }
+//
+// i: A pointer to a struct
+func (q QueryExecutor) ScanStructContext(ctx context.Context, i interface{}) (bool, error) {
+	val := reflect.ValueOf(i)
+	if !util.IsPointer(val.Kind()) {
+		return false, errUnsupportedScanStructType
+	}
+	val = reflect.Indirect(val)
+	if !util.IsStruct(val.Kind()) {
+		return false, errUnsupportedScanStructType
+	}
+
+	scanner, err := q.ScannerContext(ctx)
+	if err != nil {
+		return false, err
+	}
+
+	defer scanner.Close()
+
+	if scanner.Next() {
+		err = scanner.ScanStruct(i)
+		if err != nil {
+			return false, err
+		}
+
+		return true, scanner.Err()
+	}
+
+	return false, scanner.Err()
+}
+
+// This will execute the SQL and append results to the slice.
+//    var ids []uint32
+//    if err := db.From("test").Select("id").ScanVals(&ids); err != nil{
+//        panic(err.Error()
+//    }
+//
+// i: Takes a pointer to a slice of primitive values.
+func (q QueryExecutor) ScanVals(i interface{}) error {
+	return q.ScanValsContext(context.Background(), i)
+}
+
+// This will execute the SQL and append results to the slice.
+//    var ids []uint32
+//    if err := db.From("test").Select("id").ScanValsContext(ctx, &ids); err != nil{
+//        panic(err.Error()
+//    }
+//
+// i: Takes a pointer to a slice of primitive values.
+func (q QueryExecutor) ScanValsContext(ctx context.Context, i interface{}) error {
+	val := reflect.ValueOf(i)
+	if !util.IsPointer(val.Kind()) {
+		return errUnsupportedScanValsType
+	}
+	val = reflect.Indirect(val)
+	if !util.IsSlice(val.Kind()) {
+		return errUnsupportedScanValsType
+	}
+
+	return q.scanIntoSlice(ctx, val, func(sc Scanner, r interface{}) error {
+		return sc.ScanVal(r)
+	})
+}
+
+// This will execute the SQL and set the value of the primitive. This method will return false if no record is found.
+//    var id uint32
+//    found, err := db.From("test").Select("id").Limit(1).ScanVal(&id)
+//    if err != nil{
+//        panic(err.Error()
+//    }
+//    if !found{
+//        fmt.Println("NOT FOUND")
+//    }
+//
+//   i: Takes a pointer to a primitive value.
+func (q QueryExecutor) ScanVal(i interface{}) (bool, error) {
+	return q.ScanValContext(context.Background(), i)
+}
+
+// This will execute the SQL and set the value of the primitive. This method will return false if no record is found.
+//    var id uint32
+//    found, err := db.From("test").Select("id").Limit(1).ScanValContext(ctx, &id)
+//    if err != nil{
+//        panic(err.Error()
+//    }
+//    if !found{
+//        fmt.Println("NOT FOUND")
+//    }
+//
+//   i: Takes a pointer to a primitive value.
+func (q QueryExecutor) ScanValContext(ctx context.Context, i interface{}) (bool, error) {
+	val := reflect.ValueOf(i)
+	if !util.IsPointer(val.Kind()) {
+		return false, errScanValPointer
+	}
+	val = reflect.Indirect(val)
+	if util.IsSlice(val.Kind()) {
+		switch i.(type) {
+		case *gsql.RawBytes: // do nothing
+		case *[]byte: // do nothing
+		case gsql.Scanner: // do nothing
+		default:
+			return false, errScanValNonSlice
+		}
+	}
+
+	scanner, err := q.ScannerContext(ctx)
+	if err != nil {
+		return false, err
+	}
+
+	defer scanner.Close()
+
+	if scanner.Next() {
+		err = scanner.ScanVal(i)
+		if err != nil {
+			return false, err
+		}
+
+		return true, scanner.Err()
+	}
+
+	return false, scanner.Err()
+}
+
+// Scanner will return a Scanner that can be used for manually scanning rows.
+func (q QueryExecutor) Scanner() (Scanner, error) {
+	return q.ScannerContext(context.Background())
+}
+
+// ScannerContext will return a Scanner that can be used for manually scanning rows.
+func (q QueryExecutor) ScannerContext(ctx context.Context) (Scanner, error) {
+	rows, err := q.QueryContext(ctx)
+	if err != nil {
+		return nil, err
+	}
+	return NewScanner(rows), nil
+}
+
+func (q QueryExecutor) scanIntoSlice(ctx context.Context, val reflect.Value, it func(sc Scanner, i interface{}) error) error {
+	elemType := util.GetSliceElementType(val)
+
+	scanner, err := q.ScannerContext(ctx)
+	if err != nil {
+		return err
+	}
+
+	defer scanner.Close()
+
+	for scanner.Next() {
+		row := reflect.New(elemType)
+		err = it(scanner, row.Interface())
+		if err != nil {
+			return err
+		}
+
+		util.AppendSliceElement(val, row)
+	}
+
+	return scanner.Err()
+}

+ 1184 - 0
src/github.com/doug-martin/goqu/exec/query_executor_test.go

@@ -0,0 +1,1184 @@
+package exec
+
+import (
+	"context"
+	"database/sql"
+	"encoding/json"
+	"fmt"
+	"strings"
+	"testing"
+	"time"
+
+	"github.com/DATA-DOG/go-sqlmock"
+	"github.com/stretchr/testify/suite"
+)
+
+var (
+	testAddr1                  = "111 Test Addr"
+	testAddr2                  = "211 Test Addr"
+	testName1                  = "Test1"
+	testName2                  = "Test2"
+	testPhone1                 = "111-111-1111"
+	testPhone2                 = "222-222-2222"
+	testAge1             int64 = 10
+	testAge2             int64 = 20
+	testByteSliceContent       = "byte slice result"
+)
+
+type queryExecutorSuite struct {
+	suite.Suite
+}
+
+func (qes *queryExecutorSuite) TestWithError() {
+	type StructWithTags struct {
+		Address string `db:"address"`
+		Name    string `db:"name"`
+	}
+
+	ctx := context.Background()
+	db, _, err := sqlmock.New()
+	qes.NoError(err)
+	expectedErr := fmt.Errorf("crud exec error")
+	e := newQueryExecutor(db, expectedErr, `SELECT * FROM "items"`)
+	var items []StructWithTags
+	qes.EqualError(e.ScanStructs(&items), expectedErr.Error())
+	qes.EqualError(e.ScanStructsContext(ctx, &items), expectedErr.Error())
+	found, err := e.ScanStruct(&StructWithTags{})
+	qes.EqualError(err, expectedErr.Error())
+	qes.False(found)
+	found, err = e.ScanStructContext(ctx, &StructWithTags{})
+	qes.EqualError(err, expectedErr.Error())
+	qes.False(found)
+	var vals []string
+	qes.EqualError(e.ScanVals(&vals), expectedErr.Error())
+	qes.EqualError(e.ScanValsContext(ctx, &vals), expectedErr.Error())
+	var val string
+	found, err = e.ScanVal(&val)
+	qes.EqualError(err, expectedErr.Error())
+	qes.False(found)
+	found, err = e.ScanValContext(ctx, &val)
+	qes.EqualError(err, expectedErr.Error())
+	qes.False(found)
+}
+
+func (qes *queryExecutorSuite) TestToSQL() {
+	db, _, err := sqlmock.New()
+	qes.NoError(err)
+
+	e := newQueryExecutor(db, nil, `SELECT * FROM "items"`)
+	query, args, err := e.ToSQL()
+	qes.NoError(err)
+	qes.Equal(`SELECT * FROM "items"`, query)
+	qes.Empty(args)
+}
+
+func (qes *queryExecutorSuite) TestScanStructs_withTaggedFields() {
+	type StructWithTags struct {
+		Address string `db:"address"`
+		Name    string `db:"name"`
+	}
+
+	db, mock, err := sqlmock.New()
+	qes.NoError(err)
+
+	mock.ExpectQuery(`SELECT \* FROM "items"`).
+		WithArgs().
+		WillReturnRows(sqlmock.NewRows([]string{"address", "name"}).
+			AddRow(testAddr1, testName1).
+			AddRow(testAddr2, testName2),
+		)
+
+	e := newQueryExecutor(db, nil, `SELECT * FROM "items"`)
+
+	var items []StructWithTags
+	qes.NoError(e.ScanStructs(&items))
+	qes.Equal([]StructWithTags{
+		{Address: testAddr1, Name: testName1},
+		{Address: testAddr2, Name: testName2},
+	}, items)
+}
+
+func (qes *queryExecutorSuite) TestScanStructs_withUntaggedFields() {
+	type StructWithNoTags struct {
+		Address string
+		Name    string
+	}
+	db, mock, err := sqlmock.New()
+	qes.NoError(err)
+
+	mock.ExpectQuery(`SELECT \* FROM "items"`).
+		WithArgs().
+		WillReturnRows(sqlmock.NewRows([]string{"address", "name"}).
+			AddRow(testAddr1, testName1).
+			AddRow(testAddr2, testName2))
+
+	e := newQueryExecutor(db, nil, `SELECT * FROM "items"`)
+
+	var items []StructWithNoTags
+	qes.NoError(e.ScanStructs(&items))
+	qes.Equal([]StructWithNoTags{
+		{Address: testAddr1, Name: testName1},
+		{Address: testAddr2, Name: testName2},
+	}, items)
+}
+
+func (qes *queryExecutorSuite) TestScanStructs_withPointerFields() {
+	type StructWithPointerFields struct {
+		Str   *string
+		Time  *time.Time
+		Bool  *bool
+		Int   *int64
+		Float *float64
+	}
+	db, mock, err := sqlmock.New()
+	qes.NoError(err)
+	now := time.Now()
+	str1, str2 := "str1", "str2"
+	t := true
+	var i1, i2 int64 = 1, 2
+	var f1, f2 = 1.1, 2.1
+	mock.ExpectQuery(`SELECT \* FROM "items"`).
+		WithArgs().
+		WillReturnRows(sqlmock.NewRows([]string{"str", "time", "bool", "int", "float"}).
+			AddRow(str1, now, true, i1, f1).
+			AddRow(str2, now, true, i2, f2).
+			AddRow(nil, nil, nil, nil, nil),
+		)
+
+	e := newQueryExecutor(db, nil, `SELECT * FROM "items"`)
+
+	var items []StructWithPointerFields
+	qes.NoError(e.ScanStructs(&items))
+	qes.Equal([]StructWithPointerFields{
+		{Str: &str1, Time: &now, Bool: &t, Int: &i1, Float: &f1},
+		{Str: &str2, Time: &now, Bool: &t, Int: &i2, Float: &f2},
+		{},
+	}, items)
+}
+
+func (qes *queryExecutorSuite) TestScanStructs_withPrivateFields() {
+	type StructWithPrivateTags struct {
+		private string // nolint:structcheck,unused // need for test
+		Address string `db:"address"`
+		Name    string `db:"name"`
+	}
+
+	db, mock, err := sqlmock.New()
+	qes.NoError(err)
+
+	mock.ExpectQuery(`SELECT \* FROM "items"`).
+		WithArgs().
+		WillReturnRows(sqlmock.NewRows([]string{"address", "name"}).
+			AddRow(testAddr1, testName1).
+			AddRow(testAddr2, testName2),
+		)
+
+	e := newQueryExecutor(db, nil, `SELECT * FROM "items"`)
+
+	var items []StructWithPrivateTags
+	qes.NoError(e.ScanStructs(&items))
+	qes.Equal([]StructWithPrivateTags{
+		{Address: testAddr1, Name: testName1},
+		{Address: testAddr2, Name: testName2},
+	}, items)
+}
+
+func (qes *queryExecutorSuite) TestScanStructs_pointers() {
+	type StructWithTags struct {
+		Address string `db:"address"`
+		Name    string `db:"name"`
+	}
+
+	db, mock, err := sqlmock.New()
+	qes.NoError(err)
+
+	mock.ExpectQuery(`SELECT \* FROM "items"`).
+		WithArgs().
+		WillReturnRows(sqlmock.NewRows([]string{"address", "name"}).
+			AddRow(testAddr1, testName1).
+			AddRow(testAddr2, testName2),
+		)
+
+	e := newQueryExecutor(db, nil, `SELECT * FROM "items"`)
+
+	var items []*StructWithTags
+	qes.NoError(e.ScanStructs(&items))
+	qes.Equal([]*StructWithTags{
+		{Address: testAddr1, Name: testName1},
+		{Address: testAddr2, Name: testName2},
+	}, items)
+}
+
+func (qes *queryExecutorSuite) TestScanStructs_withIgnoredEmbeddedStruct() {
+	type StructWithTags struct {
+		Address string `db:"address"`
+		Name    string `db:"name"`
+	}
+
+	type ComposedIgnoredStruct struct {
+		StructWithTags `db:"-"`
+		PhoneNumber    string `db:"phone_number"`
+		Age            int64  `db:"age"`
+	}
+
+	db, mock, err := sqlmock.New()
+	qes.NoError(err)
+
+	mock.ExpectQuery(`SELECT \* FROM "items"`).
+		WithArgs().
+		WillReturnRows(sqlmock.NewRows([]string{"phone_number", "age"}).
+			AddRow(testPhone1, testAge1).AddRow(testPhone2, testAge2),
+		)
+
+	e := newQueryExecutor(db, nil, `SELECT * FROM "items"`)
+
+	var composed []ComposedIgnoredStruct
+	qes.NoError(e.ScanStructs(&composed))
+	qes.Equal([]ComposedIgnoredStruct{
+		{StructWithTags: StructWithTags{}, PhoneNumber: testPhone1, Age: testAge1},
+		{StructWithTags: StructWithTags{}, PhoneNumber: testPhone2, Age: testAge2},
+	}, composed)
+}
+
+func (qes *queryExecutorSuite) TestScanStructs_withEmbeddedStruct() {
+	type StructWithTags struct {
+		Address string `db:"address"`
+		Name    string `db:"name"`
+	}
+	type ComposedStruct struct {
+		StructWithTags
+		PhoneNumber string `db:"phone_number"`
+		Age         int64  `db:"age"`
+	}
+	db, mock, err := sqlmock.New()
+	qes.NoError(err)
+
+	mock.ExpectQuery(`SELECT \* FROM "items"`).
+		WithArgs().
+		WillReturnRows(sqlmock.NewRows([]string{"address", "name", "phone_number", "age"}).
+			AddRow(testAddr1, testName1, testPhone1, testAge1).
+			AddRow(testAddr2, testName2, testPhone2, testAge2),
+		)
+
+	e := newQueryExecutor(db, nil, `SELECT * FROM "items"`)
+
+	var composed []ComposedStruct
+	qes.NoError(e.ScanStructs(&composed))
+	qes.Equal([]ComposedStruct{
+		{StructWithTags: StructWithTags{Address: testAddr1, Name: testName1}, PhoneNumber: testPhone1, Age: testAge1},
+		{StructWithTags: StructWithTags{Address: testAddr2, Name: testName2}, PhoneNumber: testPhone2, Age: testAge2},
+	}, composed)
+}
+
+func (qes *queryExecutorSuite) TestScanStructs_pointersWithEmbeddedStruct() {
+	type StructWithTags struct {
+		Address string `db:"address"`
+		Name    string `db:"name"`
+	}
+	type ComposedStruct struct {
+		StructWithTags
+		PhoneNumber string `db:"phone_number"`
+		Age         int64  `db:"age"`
+	}
+
+	db, mock, err := sqlmock.New()
+	qes.NoError(err)
+
+	mock.ExpectQuery(`SELECT \* FROM "items"`).
+		WithArgs().
+		WillReturnRows(sqlmock.NewRows([]string{"address", "name", "phone_number", "age"}).
+			AddRow(testAddr1, testName1, testPhone1, testAge1).
+			AddRow(testAddr2, testName2, testPhone2, testAge2),
+		)
+
+	e := newQueryExecutor(db, nil, `SELECT * FROM "items"`)
+
+	var composed []*ComposedStruct
+	qes.NoError(e.ScanStructs(&composed))
+	qes.Equal([]*ComposedStruct{
+		{StructWithTags: StructWithTags{Address: testAddr1, Name: testName1}, PhoneNumber: testPhone1, Age: testAge1},
+		{StructWithTags: StructWithTags{Address: testAddr2, Name: testName2}, PhoneNumber: testPhone2, Age: testAge2},
+	}, composed)
+}
+
+func (qes *queryExecutorSuite) TestScanStructs_pointersWithEmbeddedStructDuplicateFields() {
+	type StructWithTags struct {
+		Address string `db:"address"`
+		Name    string `db:"name"`
+	}
+
+	type ComposedStructWithDuplicateFields struct {
+		StructWithTags
+		Address string `db:"other_address"`
+		Name    string `db:"other_name"`
+	}
+
+	var otherAddr1, otherAddr2 = "111 Test Addr Other", "211 Test Addr Other"
+	var otherName1, otherName2 = "Test1 Other", "Test2 Other"
+
+	db, mock, err := sqlmock.New()
+	qes.NoError(err)
+
+	mock.ExpectQuery(`SELECT \* FROM "items"`).
+		WithArgs().
+		WillReturnRows(sqlmock.NewRows([]string{"address", "name", "other_address", "other_name"}).
+			AddRow(testAddr1, testName1, otherAddr1, otherName1).
+			AddRow(testAddr2, testName2, otherAddr2, otherName2),
+		)
+
+	e := newQueryExecutor(db, nil, `SELECT * FROM "items"`)
+
+	var composed []*ComposedStructWithDuplicateFields
+	qes.NoError(e.ScanStructs(&composed))
+	qes.Equal([]*ComposedStructWithDuplicateFields{
+		{
+			StructWithTags: StructWithTags{Address: testAddr1, Name: testName1},
+			Address:        otherAddr1,
+			Name:           otherName1,
+		},
+		{
+			StructWithTags: StructWithTags{Address: testAddr2, Name: testName2},
+			Address:        otherAddr2,
+			Name:           otherName2,
+		},
+	}, composed)
+}
+
+func (qes *queryExecutorSuite) TestScanStructs_pointersWithEmbeddedPointerDuplicateFields() {
+	type StructWithTags struct {
+		Address string `db:"address"`
+		Name    string `db:"name"`
+	}
+
+	type ComposedWithWithPointerWithDuplicateFields struct {
+		*StructWithTags
+		Address string `db:"other_address"`
+		Name    string `db:"other_name"`
+	}
+
+	var otherAddr1, otherAddr2 = "111 Test Addr Other", "211 Test Addr Other"
+	var otherName1, otherName2 = "Test1 Other", "Test2 Other"
+
+	db, mock, err := sqlmock.New()
+	qes.NoError(err)
+
+	mock.ExpectQuery(`SELECT \* FROM "items"`).
+		WithArgs().
+		WillReturnRows(sqlmock.NewRows([]string{"address", "name", "other_address", "other_name"}).
+			AddRow(testAddr1, testName1, otherAddr1, otherName1).
+			AddRow(testAddr2, testName2, otherAddr2, otherName2),
+		)
+
+	e := newQueryExecutor(db, nil, `SELECT * FROM "items"`)
+
+	var composed []*ComposedWithWithPointerWithDuplicateFields
+	qes.NoError(e.ScanStructs(&composed))
+	qes.Equal([]*ComposedWithWithPointerWithDuplicateFields{
+		{
+			StructWithTags: &StructWithTags{Address: testAddr1, Name: testName1},
+			Address:        otherAddr1,
+			Name:           otherName1,
+		},
+		{
+			StructWithTags: &StructWithTags{Address: testAddr2, Name: testName2},
+			Address:        otherAddr2,
+			Name:           otherName2,
+		},
+	}, composed)
+}
+
+func (qes *queryExecutorSuite) TestScanStructs_withIgnoredEmbeddedPointerStruct() {
+	type StructWithTags struct {
+		Address string `db:"address"`
+		Name    string `db:"name"`
+	}
+
+	type ComposedIgnoredPointerStruct struct {
+		*StructWithTags `db:"-"`
+		PhoneNumber     string `db:"phone_number"`
+		Age             int64  `db:"age"`
+	}
+
+	db, mock, err := sqlmock.New()
+	qes.NoError(err)
+
+	mock.ExpectQuery(`SELECT \* FROM "items"`).
+		WithArgs().
+		WillReturnRows(sqlmock.NewRows([]string{"phone_number", "age"}).
+			AddRow(testPhone1, testAge1).
+			AddRow(testPhone2, testAge2),
+		)
+
+	e := newQueryExecutor(db, nil, `SELECT * FROM "items"`)
+
+	var composed []ComposedIgnoredPointerStruct
+	qes.NoError(e.ScanStructs(&composed))
+	qes.Equal([]ComposedIgnoredPointerStruct{
+		{PhoneNumber: testPhone1, Age: testAge1},
+		{PhoneNumber: testPhone2, Age: testAge2},
+	}, composed)
+}
+
+func (qes *queryExecutorSuite) TestScanStructs_withEmbeddedStructPointer() {
+	type StructWithTags struct {
+		Address string `db:"address"`
+		Name    string `db:"name"`
+	}
+
+	type ComposedWithPointerStruct struct {
+		*StructWithTags
+		PhoneNumber string `db:"phone_number"`
+		Age         int64  `db:"age"`
+	}
+
+	db, mock, err := sqlmock.New()
+	qes.NoError(err)
+
+	mock.ExpectQuery(`SELECT \* FROM "items"`).
+		WithArgs().
+		WillReturnRows(sqlmock.NewRows([]string{"address", "name", "phone_number", "age"}).
+			AddRow(testAddr1, testName1, testPhone1, testAge1).
+			AddRow(testAddr2, testName2, testPhone2, testAge2),
+		)
+
+	e := newQueryExecutor(db, nil, `SELECT * FROM "items"`)
+
+	var composed []ComposedWithPointerStruct
+	qes.NoError(e.ScanStructs(&composed))
+	qes.Equal([]ComposedWithPointerStruct{
+		{StructWithTags: &StructWithTags{Address: testAddr1, Name: testName1}, PhoneNumber: testPhone1, Age: testAge1},
+		{StructWithTags: &StructWithTags{Address: testAddr2, Name: testName2}, PhoneNumber: testPhone2, Age: testAge2},
+	}, composed)
+}
+
+func (qes *queryExecutorSuite) TestScanStructs_pointersWithEmbeddedStructPointer() {
+	type StructWithTags struct {
+		Address string `db:"address"`
+		Name    string `db:"name"`
+	}
+
+	type ComposedWithPointerStruct struct {
+		*StructWithTags
+		PhoneNumber string `db:"phone_number"`
+		Age         int64  `db:"age"`
+	}
+	db, mock, err := sqlmock.New()
+	qes.NoError(err)
+
+	mock.ExpectQuery(`SELECT \* FROM "items"`).
+		WithArgs().
+		WillReturnRows(sqlmock.NewRows([]string{"address", "name", "phone_number", "age"}).
+			AddRow(testAddr1, testName1, testPhone1, testAge1).
+			AddRow(testAddr2, testName2, testPhone2, testAge2),
+		)
+
+	e := newQueryExecutor(db, nil, `SELECT * FROM "items"`)
+
+	var composed []*ComposedWithPointerStruct
+	qes.NoError(e.ScanStructs(&composed))
+	qes.Equal([]*ComposedWithPointerStruct{
+		{StructWithTags: &StructWithTags{Address: testAddr1, Name: testName1}, PhoneNumber: testPhone1, Age: testAge1},
+		{StructWithTags: &StructWithTags{Address: testAddr2, Name: testName2}, PhoneNumber: testPhone2, Age: testAge2},
+	}, composed)
+}
+
+func (qes *queryExecutorSuite) TestScanStructs_badValue() {
+	type StructWithTags struct {
+		Address string `db:"address"`
+		Name    string `db:"name"`
+	}
+
+	db, _, err := sqlmock.New()
+	qes.NoError(err)
+
+	e := newQueryExecutor(db, nil, `SELECT * FROM "items"`)
+
+	var items []StructWithTags
+	qes.Equal(errUnsupportedScanStructsType, e.ScanStructs(items))
+	qes.Equal(errUnsupportedScanStructsType, e.ScanStructs(&StructWithTags{}))
+}
+
+func (qes *queryExecutorSuite) TestScanStructs_queryError() {
+	type StructWithTags struct {
+		Address string `db:"address"`
+		Name    string `db:"name"`
+	}
+
+	db, mock, err := sqlmock.New()
+	qes.NoError(err)
+
+	mock.ExpectQuery(`SELECT \* FROM "items"`).
+		WillReturnError(fmt.Errorf("queryExecutor error"))
+
+	e := newQueryExecutor(db, nil, `SELECT * FROM "items"`)
+
+	var items []StructWithTags
+	qes.EqualError(e.ScanStructs(&items), "queryExecutor error")
+}
+
+func (qes *queryExecutorSuite) TestScanStructsContext_withTaggedFields() {
+	type StructWithTags struct {
+		Address string `db:"address"`
+		Name    string `db:"name"`
+	}
+
+	ctx := context.Background()
+	db, mock, err := sqlmock.New()
+	qes.NoError(err)
+
+	mock.ExpectQuery(`SELECT \* FROM "items"`).
+		WithArgs().
+		WillReturnRows(sqlmock.NewRows([]string{"address", "name"}).
+			AddRow(testAddr1, testName1).
+			AddRow(testAddr2, testName2),
+		)
+
+	e := newQueryExecutor(db, nil, `SELECT * FROM "items"`)
+
+	var items []StructWithTags
+	qes.NoError(e.ScanStructsContext(ctx, &items))
+	qes.Equal([]StructWithTags{
+		{Address: testAddr1, Name: testName1},
+		{Address: testAddr2, Name: testName2},
+	}, items)
+}
+
+func (qes *queryExecutorSuite) TestScanStructsContext_withUntaggedFields() {
+	type StructWithNoTags struct {
+		Address string
+		Name    string
+	}
+
+	ctx := context.Background()
+	db, mock, err := sqlmock.New()
+	qes.NoError(err)
+
+	mock.ExpectQuery(`SELECT \* FROM "items"`).
+		WithArgs().
+		WillReturnRows(sqlmock.NewRows([]string{"address", "name"}).
+			AddRow(testAddr1, testName1).
+			AddRow(testAddr2, testName2),
+		)
+
+	e := newQueryExecutor(db, nil, `SELECT * FROM "items"`)
+
+	var items []StructWithNoTags
+	qes.NoError(e.ScanStructsContext(ctx, &items))
+	qes.Equal([]StructWithNoTags{
+		{Address: testAddr1, Name: testName1},
+		{Address: testAddr2, Name: testName2},
+	}, items)
+}
+
+func (qes *queryExecutorSuite) TestScanStructsContext_withPointerFields() {
+	type StructWithPointerFields struct {
+		Address *string
+		Name    *string
+	}
+	ctx := context.Background()
+	db, mock, err := sqlmock.New()
+	qes.NoError(err)
+
+	mock.ExpectQuery(`SELECT \* FROM "items"`).
+		WithArgs().
+		WillReturnRows(sqlmock.NewRows([]string{"address", "name"}).
+			AddRow(testAddr1, testName1).
+			AddRow(testAddr2, testName2),
+		)
+
+	e := newQueryExecutor(db, nil, `SELECT * FROM "items"`)
+
+	var items []StructWithPointerFields
+	qes.NoError(e.ScanStructsContext(ctx, &items))
+	qes.Equal([]StructWithPointerFields{
+		{Address: &testAddr1, Name: &testName1},
+		{Address: &testAddr2, Name: &testName2},
+	}, items)
+}
+
+func (qes *queryExecutorSuite) TestScanStructsContext_pointers() {
+	type StructWithTags struct {
+		Address string `db:"address"`
+		Name    string `db:"name"`
+	}
+
+	ctx := context.Background()
+	db, mock, err := sqlmock.New()
+	qes.NoError(err)
+
+	mock.ExpectQuery(`SELECT \* FROM "items"`).
+		WithArgs().
+		WillReturnRows(sqlmock.NewRows([]string{"address", "name"}).
+			AddRow(testAddr1, testName1).
+			AddRow(testAddr2, testName2),
+		)
+
+	e := newQueryExecutor(db, nil, `SELECT * FROM "items"`)
+
+	var items []*StructWithTags
+	qes.NoError(e.ScanStructsContext(ctx, &items))
+	qes.Equal([]*StructWithTags{
+		{Address: testAddr1, Name: testName1},
+		{Address: testAddr2, Name: testName2},
+	}, items)
+}
+
+func (qes *queryExecutorSuite) TestScanStructsContext_withEmbeddedStruct() {
+	type StructWithTags struct {
+		Address string `db:"address"`
+		Name    string `db:"name"`
+	}
+	type ComposedStruct struct {
+		StructWithTags
+		PhoneNumber string `db:"phone_number"`
+		Age         int64  `db:"age"`
+	}
+	ctx := context.Background()
+	db, mock, err := sqlmock.New()
+	qes.NoError(err)
+
+	mock.ExpectQuery(`SELECT \* FROM "items"`).
+		WithArgs().
+		WillReturnRows(sqlmock.NewRows([]string{"address", "name", "phone_number", "age"}).
+			AddRow(testAddr1, testName1, testPhone1, testAge1).
+			AddRow(testAddr2, testName2, testPhone2, testAge2),
+		)
+
+	e := newQueryExecutor(db, nil, `SELECT * FROM "items"`)
+
+	var composed []ComposedStruct
+	qes.NoError(e.ScanStructsContext(ctx, &composed))
+	qes.Equal([]ComposedStruct{
+		{StructWithTags: StructWithTags{Address: testAddr1, Name: testName1}, PhoneNumber: testPhone1, Age: testAge1},
+		{StructWithTags: StructWithTags{Address: testAddr2, Name: testName2}, PhoneNumber: testPhone2, Age: testAge2},
+	}, composed)
+}
+
+func (qes *queryExecutorSuite) TestScanStructsContext_withIgnoredEmbeddedStruct() {
+	type StructWithTags struct {
+		Address string `db:"address"`
+		Name    string `db:"name"`
+	}
+
+	type ComposedIgnoredStruct struct {
+		StructWithTags `db:"-"`
+		PhoneNumber    string `db:"phone_number"`
+		Age            int64  `db:"age"`
+	}
+
+	ctx := context.Background()
+	db, mock, err := sqlmock.New()
+	qes.NoError(err)
+
+	mock.ExpectQuery(`SELECT \* FROM "items"`).
+		WithArgs().
+		WillReturnRows(sqlmock.NewRows([]string{"phone_number", "age"}).
+			AddRow(testPhone1, testAge1).
+			AddRow(testPhone2, testAge2),
+		)
+
+	e := newQueryExecutor(db, nil, `SELECT * FROM "items"`)
+
+	var composed []ComposedIgnoredStruct
+	qes.NoError(e.ScanStructsContext(ctx, &composed))
+	qes.Equal([]ComposedIgnoredStruct{
+		{StructWithTags: StructWithTags{}, PhoneNumber: testPhone1, Age: testAge1},
+		{StructWithTags: StructWithTags{}, PhoneNumber: testPhone2, Age: testAge2},
+	}, composed)
+}
+
+func (qes *queryExecutorSuite) TestScanStructsContext_pointersWithEmbeddedStruct() {
+	type StructWithTags struct {
+		Address string `db:"address"`
+		Name    string `db:"name"`
+	}
+	type ComposedStruct struct {
+		StructWithTags
+		PhoneNumber string `db:"phone_number"`
+		Age         int64  `db:"age"`
+	}
+	ctx := context.Background()
+	db, mock, err := sqlmock.New()
+	qes.NoError(err)
+
+	mock.ExpectQuery(`SELECT \* FROM "items"`).
+		WithArgs().
+		WillReturnRows(sqlmock.NewRows([]string{"address", "name", "phone_number", "age"}).
+			AddRow(testAddr1, testName1, testPhone1, testAge1).
+			AddRow(testAddr2, testName2, testPhone2, testAge2),
+		)
+
+	e := newQueryExecutor(db, nil, `SELECT * FROM "items"`)
+
+	var composed []*ComposedStruct
+	qes.NoError(e.ScanStructsContext(ctx, &composed))
+	qes.Equal([]*ComposedStruct{
+		{StructWithTags: StructWithTags{Address: testAddr1, Name: testName1}, PhoneNumber: testPhone1, Age: testAge1},
+		{StructWithTags: StructWithTags{Address: testAddr2, Name: testName2}, PhoneNumber: testPhone2, Age: testAge2},
+	}, composed)
+}
+
+func (qes *queryExecutorSuite) TestScanStructsContext_withEmbeddedStructPointer() {
+	type StructWithTags struct {
+		Address string `db:"address"`
+		Name    string `db:"name"`
+	}
+
+	type ComposedWithPointerStruct struct {
+		*StructWithTags
+		PhoneNumber string `db:"phone_number"`
+		Age         int64  `db:"age"`
+	}
+
+	ctx := context.Background()
+	db, mock, err := sqlmock.New()
+	qes.NoError(err)
+
+	mock.ExpectQuery(`SELECT \* FROM "items"`).
+		WithArgs().
+		WillReturnRows(sqlmock.NewRows([]string{"address", "name", "phone_number", "age"}).
+			AddRow(testAddr1, testName1, testPhone1, testAge1).
+			AddRow(testAddr2, testName2, testPhone2, testAge2),
+		)
+
+	e := newQueryExecutor(db, nil, `SELECT * FROM "items"`)
+
+	var composed []ComposedWithPointerStruct
+	qes.NoError(e.ScanStructsContext(ctx, &composed))
+	qes.Equal([]ComposedWithPointerStruct{
+		{StructWithTags: &StructWithTags{Address: testAddr1, Name: testName1}, PhoneNumber: testPhone1, Age: testAge1},
+		{StructWithTags: &StructWithTags{Address: testAddr2, Name: testName2}, PhoneNumber: testPhone2, Age: testAge2},
+	}, composed)
+}
+
+func (qes *queryExecutorSuite) TestScanStructsContext_pointersWithEmbeddedStructPointer() {
+	type StructWithTags struct {
+		Address string `db:"address"`
+		Name    string `db:"name"`
+	}
+
+	type ComposedWithPointerStruct struct {
+		*StructWithTags
+		PhoneNumber string `db:"phone_number"`
+		Age         int64  `db:"age"`
+	}
+
+	ctx := context.Background()
+	db, mock, err := sqlmock.New()
+	qes.NoError(err)
+
+	mock.ExpectQuery(`SELECT \* FROM "items"`).
+		WithArgs().
+		WillReturnRows(sqlmock.NewRows([]string{"address", "name", "phone_number", "age"}).
+			AddRow(testAddr1, testName1, testPhone1, testAge1).
+			AddRow(testAddr2, testName2, testPhone2, testAge2),
+		)
+
+	e := newQueryExecutor(db, nil, `SELECT * FROM "items"`)
+
+	var composed []*ComposedWithPointerStruct
+	qes.NoError(e.ScanStructsContext(ctx, &composed))
+	qes.Equal([]*ComposedWithPointerStruct{
+		{StructWithTags: &StructWithTags{Address: testAddr1, Name: testName1}, PhoneNumber: testPhone1, Age: testAge1},
+		{StructWithTags: &StructWithTags{Address: testAddr2, Name: testName2}, PhoneNumber: testPhone2, Age: testAge2},
+	}, composed)
+}
+
+func (qes *queryExecutorSuite) TestScanStructsContext_badValue() {
+	type StructWithTags struct {
+		Address string `db:"address"`
+		Name    string `db:"name"`
+	}
+
+	ctx := context.Background()
+	db, _, err := sqlmock.New()
+	qes.NoError(err)
+
+	e := newQueryExecutor(db, nil, `SELECT * FROM "items"`)
+
+	var items []StructWithTags
+	qes.Equal(errUnsupportedScanStructsType, e.ScanStructsContext(ctx, items))
+	qes.Equal(errUnsupportedScanStructsType, e.ScanStructsContext(ctx, &StructWithTags{}))
+}
+
+func (qes *queryExecutorSuite) TestScanStructsContext_queryError() {
+	type StructWithTags struct {
+		Address string `db:"address"`
+		Name    string `db:"name"`
+	}
+
+	ctx := context.Background()
+	db, mock, err := sqlmock.New()
+	qes.NoError(err)
+
+	mock.ExpectQuery(`SELECT \* FROM "items"`).
+		WillReturnError(fmt.Errorf("queryExecutor error"))
+
+	e := newQueryExecutor(db, nil, `SELECT * FROM "items"`)
+
+	var items []StructWithTags
+	qes.EqualError(e.ScanStructsContext(ctx, &items), "queryExecutor error")
+}
+
+func (qes *queryExecutorSuite) TestScanStruct() {
+	type StructWithNoTags struct {
+		Address string
+		Name    string
+	}
+
+	type StructWithTags struct {
+		Address string `db:"address"`
+		Name    string `db:"name"`
+	}
+
+	type ComposedStruct struct {
+		StructWithTags
+		PhoneNumber string `db:"phone_number"`
+		Age         int64  `db:"age"`
+	}
+	type ComposedWithPointerStruct struct {
+		*StructWithTags
+		PhoneNumber string `db:"phone_number"`
+		Age         int64  `db:"age"`
+	}
+
+	db, mock, err := sqlmock.New()
+	qes.NoError(err)
+
+	mock.ExpectQuery(`SELECT \* FROM "items"`).
+		WillReturnError(fmt.Errorf("queryExecutor error"))
+
+	mock.ExpectQuery(`SELECT \* FROM "items"`).
+		WithArgs().
+		WillReturnRows(sqlmock.NewRows([]string{"address", "name"}).
+			AddRow(nil, nil),
+		)
+
+	mock.ExpectQuery(`SELECT \* FROM "items"`).
+		WithArgs().
+		WillReturnRows(sqlmock.NewRows([]string{"address", "name"}))
+
+	mock.ExpectQuery(`SELECT \* FROM "items"`).
+		WithArgs().
+		WillReturnRows(sqlmock.NewRows([]string{"address", "name"}).
+			AddRow(testAddr1, testName1),
+		)
+
+	mock.ExpectQuery(`SELECT \* FROM "items"`).
+		WithArgs().
+		WillReturnRows(sqlmock.NewRows([]string{"address", "name", "phone_number", "age"}).
+			AddRow(testAddr1, testName1, testPhone1, testAge1),
+		)
+
+	mock.ExpectQuery(`SELECT \* FROM "items"`).
+		WithArgs().
+		WillReturnRows(sqlmock.NewRows([]string{"address", "name", "phone_number", "age"}).
+			AddRow(testAddr1, testName1, testPhone1, testAge1),
+		)
+
+	mock.ExpectQuery(`SELECT \* FROM "items"`).
+		WithArgs().
+		WillReturnRows(sqlmock.NewRows([]string{"address", "name"}).AddRow(testAddr1, testName1))
+
+	e := newQueryExecutor(db, nil, `SELECT * FROM "items"`)
+
+	var slicePtr []StructWithTags
+	var item StructWithTags
+	found, err := e.ScanStruct(item)
+	qes.Equal(errUnsupportedScanStructType, err)
+	qes.False(found)
+	found, err = e.ScanStruct(&slicePtr)
+	qes.Equal(errUnsupportedScanStructType, err)
+	qes.False(found)
+	found, err = e.ScanStruct(&item)
+	qes.EqualError(err, "queryExecutor error")
+	qes.False(found)
+
+	found, err = e.ScanStruct(&item)
+	qes.Error(err)
+	qes.False(found)
+
+	found, err = e.ScanStruct(&item)
+	qes.NoError(err)
+	qes.False(found)
+
+	found, err = e.ScanStruct(&item)
+	qes.NoError(err)
+	qes.True(found)
+	qes.Equal(StructWithTags{
+		Address: testAddr1,
+		Name:    testName1,
+	}, item)
+
+	var composed ComposedStruct
+	found, err = e.ScanStruct(&composed)
+	qes.NoError(err)
+	qes.True(found)
+	qes.Equal(ComposedStruct{
+		StructWithTags: StructWithTags{Address: testAddr1, Name: testName1},
+		PhoneNumber:    testPhone1,
+		Age:            testAge1,
+	}, composed)
+
+	var embeddedPtr ComposedWithPointerStruct
+	found, err = e.ScanStruct(&embeddedPtr)
+	qes.NoError(err)
+	qes.True(found)
+	qes.Equal(ComposedWithPointerStruct{
+		StructWithTags: &StructWithTags{
+			Address: testAddr1,
+			Name:    testName1,
+		},
+		PhoneNumber: testPhone1,
+		Age:         testAge1,
+	}, embeddedPtr)
+
+	var noTag StructWithNoTags
+	found, err = e.ScanStruct(&noTag)
+	qes.NoError(err)
+	qes.True(found)
+	qes.Equal(StructWithNoTags{
+		Address: testAddr1,
+		Name:    testName1,
+	}, noTag)
+}
+
+func (qes *queryExecutorSuite) TestScanStruct_taggedStructs() {
+	type StructWithNoTags struct {
+		Address string
+		Name    string
+	}
+
+	type StructWithTags struct {
+		Address string `db:"address"`
+		Name    string `db:"name"`
+	}
+
+	type ComposedStruct struct {
+		StructWithTags
+		PhoneNumber string `db:"phone_number"`
+		Age         int64  `db:"age"`
+	}
+	type ComposedWithPointerStruct struct {
+		*StructWithTags
+		PhoneNumber string `db:"phone_number"`
+		Age         int64  `db:"age"`
+	}
+
+	type StructWithTaggedStructs struct {
+		NoTags          StructWithNoTags          `db:"notags"`
+		Tags            StructWithTags            `db:"tags"`
+		Composed        ComposedStruct            `db:"composedstruct"`
+		ComposedPointer ComposedWithPointerStruct `db:"composedptrstruct"`
+	}
+
+	db, mock, err := sqlmock.New()
+	qes.NoError(err)
+
+	cols := []string{
+		"notags.address", "notags.name",
+		"tags.address", "tags.name",
+		"composedstruct.address", "composedstruct.name", "composedstruct.phone_number", "composedstruct.age",
+		"composedptrstruct.address", "composedptrstruct.name", "composedptrstruct.phone_number", "composedptrstruct.age",
+	}
+
+	q := `SELECT` + strings.Join(cols, ", ") + ` FROM "items"`
+
+	mock.ExpectQuery(q).
+		WithArgs().
+		WillReturnRows(sqlmock.NewRows(cols).AddRow(
+			testAddr1, testName1,
+			testAddr2, testName2,
+			testAddr1, testName1, testPhone1, testAge1,
+			testAddr2, testName2, testPhone2, testAge2,
+		))
+
+	e := newQueryExecutor(db, nil, q)
+
+	var item StructWithTaggedStructs
+	found, err := e.ScanStruct(&item)
+	qes.NoError(err)
+	qes.True(found)
+	qes.Equal(StructWithTaggedStructs{
+		NoTags: StructWithNoTags{Address: testAddr1, Name: testName1},
+		Tags:   StructWithTags{Address: testAddr2, Name: testName2},
+		Composed: ComposedStruct{
+			StructWithTags: StructWithTags{Address: testAddr1, Name: testName1},
+			PhoneNumber:    testPhone1,
+			Age:            testAge1,
+		},
+		ComposedPointer: ComposedWithPointerStruct{
+			StructWithTags: &StructWithTags{Address: testAddr2, Name: testName2},
+			PhoneNumber:    testPhone2,
+			Age:            testAge2,
+		},
+	}, item)
+}
+
+func (qes *queryExecutorSuite) TestScanVals() {
+	db, mock, err := sqlmock.New()
+	qes.NoError(err)
+
+	var id1, id2 int64 = 1, 2
+
+	mock.ExpectQuery(`SELECT "id" FROM "items"`).
+		WillReturnError(fmt.Errorf("queryExecutor error"))
+
+	mock.ExpectQuery(`SELECT "id" FROM "items"`).
+		WithArgs().
+		WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(id1).RowError(0, fmt.Errorf("row error")))
+
+	mock.ExpectQuery(`SELECT "id" FROM "items"`).
+		WithArgs().
+		WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(id1).AddRow("a"))
+
+	mock.ExpectQuery(`SELECT "id" FROM "items"`).
+		WithArgs().
+		WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(id1).AddRow(id2))
+
+	mock.ExpectQuery(`SELECT "id" FROM "items"`).
+		WithArgs().
+		WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(id1).AddRow(id2))
+
+	e := newQueryExecutor(db, nil, `SELECT "id" FROM "items"`)
+
+	var id int64
+	var ids []int64
+	qes.Equal(errUnsupportedScanValsType, e.ScanVals(ids))
+	qes.Equal(errUnsupportedScanValsType, e.ScanVals(&id))
+	qes.EqualError(e.ScanVals(&ids), "queryExecutor error")
+	qes.EqualError(e.ScanVals(&ids), "row error")
+	qes.Error(e.ScanVals(&ids))
+
+	ids = ids[0:0]
+	qes.NoError(e.ScanVals(&ids))
+	qes.Equal(ids, []int64{id1, id2})
+
+	var pointers []*int64
+	qes.NoError(e.ScanVals(&pointers))
+	qes.Len(pointers, 2)
+	qes.Equal(&id1, pointers[0])
+	qes.Equal(&id2, pointers[1])
+}
+
+func (qes *queryExecutorSuite) TestScanVal() {
+	db, mock, err := sqlmock.New()
+	qes.NoError(err)
+
+	id1 := int64(1)
+	mock.ExpectQuery(`SELECT "id" FROM "items"`).
+		WillReturnError(fmt.Errorf("queryExecutor error"))
+
+	mock.ExpectQuery(`SELECT "id" FROM "items"`).
+		WithArgs().
+		WillReturnRows(sqlmock.NewRows([]string{"id"}).RowError(0, fmt.Errorf("row error")).AddRow(id1))
+
+	mock.ExpectQuery(`SELECT "id" FROM "items"`).
+		WithArgs().
+		WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow("c"))
+
+	mock.ExpectQuery(`SELECT "id" FROM "items"`).
+		WithArgs().
+		WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(id1))
+
+	e := newQueryExecutor(db, nil, `SELECT "id" FROM "items"`)
+
+	var id int64
+	var ids []int64
+	found, err := e.ScanVal(id)
+	qes.Equal(errScanValPointer, err)
+	qes.False(found)
+	found, err = e.ScanVal(&ids)
+	qes.Equal(errScanValNonSlice, err)
+	qes.False(found)
+	found, err = e.ScanVal(&id)
+	qes.EqualError(err, "queryExecutor error")
+	qes.False(found)
+
+	found, err = e.ScanVal(&id)
+	qes.EqualError(err, "row error")
+	qes.False(found)
+
+	found, err = e.ScanVal(&id)
+	qes.Error(err)
+	qes.False(found)
+
+	var ptrID *int64
+	found, err = e.ScanVal(&ptrID)
+	qes.NoError(err)
+	qes.True(found)
+	qes.Equal(&id1, ptrID)
+}
+
+func (qes *queryExecutorSuite) TestScanVal_withByteSlice() {
+	db, mock, err := sqlmock.New()
+	qes.NoError(err)
+
+	mock.ExpectQuery(`SELECT "name" FROM "items"`).
+		WithArgs().
+		WillReturnRows(sqlmock.NewRows([]string{"name"}).AddRow(testByteSliceContent))
+
+	e := newQueryExecutor(db, nil, `SELECT "name" FROM "items"`)
+
+	var bytes []byte
+	found, err := e.ScanVal(bytes)
+	qes.Equal(errScanValPointer, err)
+	qes.False(found)
+
+	found, err = e.ScanVal(&bytes)
+	qes.NoError(err)
+	qes.True(found)
+	qes.Equal([]byte(testByteSliceContent), bytes)
+}
+
+func (qes *queryExecutorSuite) TestScanVal_withRawBytes() {
+	db, mock, err := sqlmock.New()
+	qes.NoError(err)
+
+	mock.ExpectQuery(`SELECT "name" FROM "items"`).
+		WithArgs().
+		WillReturnRows(sqlmock.NewRows([]string{"name"}).AddRow(testByteSliceContent))
+
+	e := newQueryExecutor(db, nil, `SELECT "name" FROM "items"`)
+
+	var bytes sql.RawBytes
+	found, err := e.ScanVal(bytes)
+	qes.Equal(errScanValPointer, err)
+	qes.False(found)
+
+	found, err = e.ScanVal(&bytes)
+	qes.NoError(err)
+	qes.True(found)
+	qes.Equal(sql.RawBytes(testByteSliceContent), bytes)
+}
+
+type JSONBoolArray []bool
+
+func (b *JSONBoolArray) Scan(src interface{}) error {
+	return json.Unmarshal(src.([]byte), b)
+}
+
+func (qes *queryExecutorSuite) TestScanVal_withValuerSlice() {
+	db, mock, err := sqlmock.New()
+	qes.NoError(err)
+
+	mock.ExpectQuery(`SELECT "bools" FROM "items"`).
+		WithArgs().
+		WillReturnRows(sqlmock.NewRows([]string{"bools"}).FromCSVString(`"[true, false, true]"`))
+
+	e := newQueryExecutor(db, nil, `SELECT "bools" FROM "items"`)
+
+	var bools JSONBoolArray
+	found, err := e.ScanVal(bools)
+	qes.Equal(errScanValPointer, err)
+	qes.False(found)
+
+	found, err = e.ScanVal(&bools)
+	qes.NoError(err)
+	qes.True(found)
+	qes.Equal(JSONBoolArray{true, false, true}, bools)
+}
+
+func TestQueryExecutorSuite(t *testing.T) {
+	suite.Run(t, new(queryExecutorSuite))
+}

+ 35 - 0
src/github.com/doug-martin/goqu/exec/query_factory.go

@@ -0,0 +1,35 @@
+package exec
+
+import (
+	"context"
+	"database/sql"
+
+	"github.com/doug-martin/goqu/v9/internal/sb"
+)
+
+type (
+	DbExecutor interface {
+		ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error)
+		QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error)
+	}
+	QueryFactory interface {
+		FromSQL(sql string, args ...interface{}) QueryExecutor
+		FromSQLBuilder(b sb.SQLBuilder) QueryExecutor
+	}
+	querySupport struct {
+		de DbExecutor
+	}
+)
+
+func NewQueryFactory(de DbExecutor) QueryFactory {
+	return &querySupport{de}
+}
+
+func (qs *querySupport) FromSQL(query string, args ...interface{}) QueryExecutor {
+	return newQueryExecutor(qs.de, nil, query, args...)
+}
+
+func (qs *querySupport) FromSQLBuilder(b sb.SQLBuilder) QueryExecutor {
+	query, args, err := b.ToSQL()
+	return newQueryExecutor(qs.de, err, query, args...)
+}

+ 108 - 0
src/github.com/doug-martin/goqu/exec/scanner.go

@@ -0,0 +1,108 @@
+package exec
+
+import (
+	"database/sql"
+	"reflect"
+
+	"github.com/doug-martin/goqu/v9/exp"
+	"github.com/doug-martin/goqu/v9/internal/errors"
+	"github.com/doug-martin/goqu/v9/internal/util"
+)
+
+type (
+	// Scanner knows how to scan sql.Rows into structs.
+	Scanner interface {
+		Next() bool
+		ScanStruct(i interface{}) error
+		ScanVal(i interface{}) error
+		Close() error
+		Err() error
+	}
+
+	scanner struct {
+		rows      *sql.Rows
+		columnMap util.ColumnMap
+		columns   []string
+	}
+)
+
+func unableToFindFieldError(col string) error {
+	return errors.New(`unable to find corresponding field to column "%s" returned by query`, col)
+}
+
+// NewScanner returns a scanner that can be used for scanning rows into structs.
+func NewScanner(rows *sql.Rows) Scanner {
+	return &scanner{rows: rows}
+}
+
+// Next prepares the next row for Scanning. See sql.Rows#Next for more
+// information.
+func (s *scanner) Next() bool {
+	return s.rows.Next()
+}
+
+// Err returns the error, if any that was encountered during iteration. See
+// sql.Rows#Err for more information.
+func (s *scanner) Err() error {
+	return s.rows.Err()
+}
+
+// ScanStruct will scan the current row into i.
+func (s *scanner) ScanStruct(i interface{}) error {
+	// Setup columnMap and columns, but only once.
+	if s.columnMap == nil || s.columns == nil {
+		cm, err := util.GetColumnMap(i)
+		if err != nil {
+			return err
+		}
+
+		cols, err := s.rows.Columns()
+		if err != nil {
+			return err
+		}
+
+		s.columnMap = cm
+		s.columns = cols
+	}
+
+	scans := make([]interface{}, len(s.columns))
+	for idx, col := range s.columns {
+		data, ok := s.columnMap[col]
+		switch {
+		case !ok:
+			return unableToFindFieldError(col)
+		default:
+			scans[idx] = reflect.New(data.GoType).Interface()
+		}
+	}
+
+	err := s.rows.Scan(scans...)
+	if err != nil {
+		return err
+	}
+
+	record := exp.Record{}
+	for index, col := range s.columns {
+		record[col] = scans[index]
+	}
+
+	util.AssignStructVals(i, record, s.columnMap)
+
+	return s.Err()
+}
+
+// ScanVal will scan the current row and column into i.
+func (s *scanner) ScanVal(i interface{}) error {
+	err := s.rows.Scan(i)
+	if err != nil {
+		return err
+	}
+
+	return s.Err()
+}
+
+// Close closes the Rows, preventing further enumeration. See sql.Rows#Close
+// for more info.
+func (s *scanner) Close() error {
+	return s.rows.Close()
+}

+ 59 - 0
src/github.com/doug-martin/goqu/exp/alias.go

@@ -0,0 +1,59 @@
+package exp
+
+import "fmt"
+
+type (
+	aliasExpression struct {
+		aliased Expression
+		alias   IdentifierExpression
+	}
+)
+
+// used internally by other expressions to create a new aliased expression
+func aliased(exp Expression, alias interface{}) AliasedExpression {
+	switch v := alias.(type) {
+	case string:
+		return aliasExpression{aliased: exp, alias: ParseIdentifier(v)}
+	case IdentifierExpression:
+		return aliasExpression{aliased: exp, alias: v}
+	default:
+		panic(fmt.Sprintf("Cannot create alias from %+v", v))
+	}
+}
+
+func (ae aliasExpression) Clone() Expression {
+	return aliasExpression{aliased: ae.aliased, alias: ae.alias.Clone().(IdentifierExpression)}
+}
+
+func (ae aliasExpression) Expression() Expression {
+	return ae
+}
+
+func (ae aliasExpression) Aliased() Expression {
+	return ae.aliased
+}
+
+func (ae aliasExpression) GetAs() IdentifierExpression {
+	return ae.alias
+}
+
+// Returns a new IdentifierExpression with the specified schema
+func (ae aliasExpression) Schema(schema string) IdentifierExpression {
+	return ae.alias.Schema(schema)
+}
+
+// Returns a new IdentifierExpression with the specified table
+func (ae aliasExpression) Table(table string) IdentifierExpression {
+	return ae.alias.Table(table)
+}
+
+// Returns a new IdentifierExpression with the specified column
+func (ae aliasExpression) Col(col interface{}) IdentifierExpression {
+	return ae.alias.Col(col)
+}
+
+// Returns a new IdentifierExpression with the column set to *
+//   I("my_table").As("t").All() //"t".*
+func (ae aliasExpression) All() IdentifierExpression {
+	return ae.alias.All()
+}

+ 68 - 0
src/github.com/doug-martin/goqu/exp/alias_test.go

@@ -0,0 +1,68 @@
+package exp
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/suite"
+)
+
+type aliasExpressionSuite struct {
+	suite.Suite
+}
+
+func TestAliasExpressionSuite(t *testing.T) {
+	suite.Run(t, &aliasExpressionSuite{})
+}
+
+func (aes *aliasExpressionSuite) TestClone() {
+	ae := aliased(NewIdentifierExpression("", "", "col"), "c")
+	aes.Equal(ae, ae.Clone())
+}
+
+func (aes *aliasExpressionSuite) TestExpression() {
+	ae := aliased(NewIdentifierExpression("", "", "col"), "c")
+	aes.Equal(ae, ae.Expression())
+}
+
+func (aes *aliasExpressionSuite) TestAliased() {
+	ident := NewIdentifierExpression("", "", "col")
+	ae := aliased(ident, "c")
+	aes.Equal(ident, ae.Aliased())
+}
+
+func (aes *aliasExpressionSuite) TestGetAs() {
+	ae := aliased(NewIdentifierExpression("", "", "col"), "c")
+	aes.Equal(NewIdentifierExpression("", "", "c"), ae.GetAs())
+}
+
+func (aes *aliasExpressionSuite) TestSchema() {
+	si := aliased(
+		NewIdentifierExpression("", "t", nil),
+		NewIdentifierExpression("", "t", nil),
+	).Schema("s")
+	aes.Equal(NewIdentifierExpression("s", "t", nil), si)
+}
+
+func (aes *aliasExpressionSuite) TestTable() {
+	si := aliased(
+		NewIdentifierExpression("schema", "", nil),
+		NewIdentifierExpression("s", "", nil),
+	).Table("t")
+	aes.Equal(NewIdentifierExpression("s", "t", nil), si)
+}
+
+func (aes *aliasExpressionSuite) TestCol() {
+	si := aliased(
+		NewIdentifierExpression("", "table", nil),
+		NewIdentifierExpression("", "t", nil),
+	).Col("c")
+	aes.Equal(NewIdentifierExpression("", "t", "c"), si)
+}
+
+func (aes *aliasExpressionSuite) TestAll() {
+	si := aliased(
+		NewIdentifierExpression("", "table", nil),
+		NewIdentifierExpression("", "t", nil),
+	).All()
+	aes.Equal(NewIdentifierExpression("", "t", Star()), si)
+}

+ 180 - 0
src/github.com/doug-martin/goqu/exp/bool.go

@@ -0,0 +1,180 @@
+package exp
+
+import (
+	"reflect"
+	"regexp"
+)
+
+type boolean struct {
+	lhs Expression
+	rhs interface{}
+	op  BooleanOperation
+}
+
+func NewBooleanExpression(op BooleanOperation, lhs Expression, rhs interface{}) BooleanExpression {
+	return boolean{op: op, lhs: lhs, rhs: rhs}
+}
+
+func (b boolean) Clone() Expression {
+	return NewBooleanExpression(b.op, b.lhs.Clone(), b.rhs)
+}
+
+func (b boolean) Expression() Expression {
+	return b
+}
+
+func (b boolean) RHS() interface{} {
+	return b.rhs
+}
+
+func (b boolean) LHS() Expression {
+	return b.lhs
+}
+
+func (b boolean) Op() BooleanOperation {
+	return b.op
+}
+
+// used internally to create an equality BooleanExpression
+func eq(lhs Expression, rhs interface{}) BooleanExpression {
+	return checkBoolExpType(EqOp, lhs, rhs, false)
+}
+
+// used internally to create an in-equality BooleanExpression
+func neq(lhs Expression, rhs interface{}) BooleanExpression {
+	return checkBoolExpType(EqOp, lhs, rhs, true)
+}
+
+// used internally to create an gt comparison BooleanExpression
+func gt(lhs Expression, rhs interface{}) BooleanExpression {
+	return NewBooleanExpression(GtOp, lhs, rhs)
+}
+
+// used internally to create an gte comparison BooleanExpression
+func gte(lhs Expression, rhs interface{}) BooleanExpression {
+	return NewBooleanExpression(GteOp, lhs, rhs)
+}
+
+// used internally to create an lt comparison BooleanExpression
+func lt(lhs Expression, rhs interface{}) BooleanExpression {
+	return NewBooleanExpression(LtOp, lhs, rhs)
+}
+
+// used internally to create an lte comparison BooleanExpression
+func lte(lhs Expression, rhs interface{}) BooleanExpression {
+	return NewBooleanExpression(LteOp, lhs, rhs)
+}
+
+// used internally to create an IN BooleanExpression
+func in(lhs Expression, vals ...interface{}) BooleanExpression {
+	if len(vals) == 1 && reflect.Indirect(reflect.ValueOf(vals[0])).Kind() == reflect.Slice {
+		return NewBooleanExpression(InOp, lhs, vals[0])
+	}
+	return NewBooleanExpression(InOp, lhs, vals)
+}
+
+// used internally to create a NOT IN BooleanExpression
+func notIn(lhs Expression, vals ...interface{}) BooleanExpression {
+	if len(vals) == 1 && reflect.Indirect(reflect.ValueOf(vals[0])).Kind() == reflect.Slice {
+		return NewBooleanExpression(NotInOp, lhs, vals[0])
+	}
+	return NewBooleanExpression(NotInOp, lhs, vals)
+}
+
+// used internally to create an IS BooleanExpression
+func is(lhs Expression, val interface{}) BooleanExpression {
+	return checkBoolExpType(IsOp, lhs, val, false)
+}
+
+// used internally to create an IS NOT BooleanExpression
+func isNot(lhs Expression, val interface{}) BooleanExpression {
+	return checkBoolExpType(IsOp, lhs, val, true)
+}
+
+// used internally to create a LIKE BooleanExpression
+func like(lhs Expression, val interface{}) BooleanExpression {
+	return checkLikeExp(LikeOp, lhs, val, false)
+}
+
+// used internally to create an ILIKE BooleanExpression
+func iLike(lhs Expression, val interface{}) BooleanExpression {
+	return checkLikeExp(ILikeOp, lhs, val, false)
+}
+
+// used internally to create a NOT LIKE BooleanExpression
+func notLike(lhs Expression, val interface{}) BooleanExpression {
+	return checkLikeExp(LikeOp, lhs, val, true)
+}
+
+// used internally to create a NOT ILIKE BooleanExpression
+func notILike(lhs Expression, val interface{}) BooleanExpression {
+	return checkLikeExp(ILikeOp, lhs, val, true)
+}
+
+// used internally to create a LIKE BooleanExpression
+func regexpLike(lhs Expression, val interface{}) BooleanExpression {
+	return checkLikeExp(RegexpLikeOp, lhs, val, false)
+}
+
+// used internally to create an ILIKE BooleanExpression
+func regexpILike(lhs Expression, val interface{}) BooleanExpression {
+	return checkLikeExp(RegexpILikeOp, lhs, val, false)
+}
+
+// used internally to create a NOT LIKE BooleanExpression
+func regexpNotLike(lhs Expression, val interface{}) BooleanExpression {
+	return checkLikeExp(RegexpLikeOp, lhs, val, true)
+}
+
+// used internally to create a NOT ILIKE BooleanExpression
+func regexpNotILike(lhs Expression, val interface{}) BooleanExpression {
+	return checkLikeExp(RegexpILikeOp, lhs, val, true)
+}
+
+// checks an like rhs to create the proper like expression for strings or regexps
+func checkLikeExp(op BooleanOperation, lhs Expression, val interface{}, invert bool) BooleanExpression {
+	rhs := val
+
+	if t, ok := val.(*regexp.Regexp); ok {
+		if op == LikeOp {
+			op = RegexpLikeOp
+		} else if op == ILikeOp {
+			op = RegexpILikeOp
+		}
+		rhs = t.String()
+	}
+	if invert {
+		op = operatorInversions[op]
+	}
+	return NewBooleanExpression(op, lhs, rhs)
+}
+
+// checks a boolean operation normalizing the operation based on the RHS (e.g. "a" = true vs "a" IS TRUE
+func checkBoolExpType(op BooleanOperation, lhs Expression, rhs interface{}, invert bool) BooleanExpression {
+	if rhs == nil {
+		op = IsOp
+	} else {
+		switch reflect.Indirect(reflect.ValueOf(rhs)).Kind() {
+		case reflect.Bool:
+			op = IsOp
+		case reflect.Slice:
+			// if its a slice of bytes dont treat as an IN
+			if _, ok := rhs.([]byte); !ok {
+				op = InOp
+			}
+		case reflect.Struct:
+			switch rhs.(type) {
+			case SQLExpression:
+				op = InOp
+			case AppendableExpression:
+				op = InOp
+			case *regexp.Regexp:
+				return checkLikeExp(LikeOp, lhs, rhs, invert)
+			}
+		}
+	}
+	if invert {
+		op = operatorInversions[op]
+	}
+	return NewBooleanExpression(op, lhs, rhs)
+}

+ 75 - 0
src/github.com/doug-martin/goqu/exp/case.go

@@ -0,0 +1,75 @@
+package exp
+
+type (
+	caseElse struct {
+		result interface{}
+	}
+	caseWhen struct {
+		caseElse
+		condition interface{}
+	}
+	caseExpression struct {
+		value         interface{}
+		whens         []CaseWhen
+		elseCondition CaseElse
+	}
+)
+
+func NewCaseElse(result interface{}) CaseElse {
+	return caseElse{result: result}
+}
+
+func (ce caseElse) Result() interface{} {
+	return ce.result
+}
+
+func NewCaseWhen(condition, result interface{}) CaseWhen {
+	return caseWhen{caseElse: caseElse{result: result}, condition: condition}
+}
+
+func (cw caseWhen) Condition() interface{} {
+	return cw.condition
+}
+
+func NewCaseExpression() CaseExpression {
+	return caseExpression{value: nil, whens: []CaseWhen{}, elseCondition: nil}
+}
+
+func (c caseExpression) Expression() Expression {
+	return c
+}
+
+func (c caseExpression) Clone() Expression {
+	return caseExpression{value: c.value, whens: c.whens, elseCondition: c.elseCondition}
+}
+
+func (c caseExpression) As(alias interface{}) AliasedExpression {
+	return aliased(c, alias)
+}
+
+func (c caseExpression) GetValue() interface{} {
+	return c.value
+}
+
+func (c caseExpression) GetWhens() []CaseWhen {
+	return c.whens
+}
+
+func (c caseExpression) GetElse() CaseElse {
+	return c.elseCondition
+}
+
+func (c caseExpression) Value(value interface{}) CaseExpression {
+	c.value = value
+	return c
+}
+
+func (c caseExpression) When(condition, result interface{}) CaseExpression {
+	c.whens = append(c.whens, NewCaseWhen(condition, result))
+	return c
+}
+
+func (c caseExpression) Else(result interface{}) CaseExpression {
+	c.elseCondition = NewCaseElse(result)
+	return c
+}

+ 88 - 0
src/github.com/doug-martin/goqu/exp/case_test.go

@@ -0,0 +1,88 @@
+package exp
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/suite"
+)
+
+type caseExpressionSuite struct {
+	suite.Suite
+}
+
+func TestCaseExpressionSuite(t *testing.T) {
+	suite.Run(t, &caseExpressionSuite{})
+}
+
+func (ces *caseExpressionSuite) TestClone() {
+	ce := NewCaseExpression()
+	ces.Equal(ce, ce.Clone())
+}
+
+func (ces *caseExpressionSuite) TestExpression() {
+	ce := NewCaseExpression()
+	ces.Equal(ce, ce.Expression())
+}
+
+func (ces *caseExpressionSuite) TestAs() {
+	ce := NewCaseExpression()
+	ces.Equal(aliased(ce, "a"), ce.As("a"))
+}
+
+func (ces *caseExpressionSuite) TestValue() {
+	ce := NewCaseExpression()
+	ces.Nil(ce.GetValue())
+
+	ce = NewCaseExpression().Value(NewIdentifierExpression("", "", "a"))
+	ces.Equal(NewIdentifierExpression("", "", "a"), ce.GetValue())
+}
+
+func (ces *caseExpressionSuite) TestWhen() {
+	condition1 := NewIdentifierExpression("", "", "a").Eq(10)
+	condition2 := NewIdentifierExpression("", "", "b").Eq(20)
+	ce := NewCaseExpression()
+	ces.Equal([]CaseWhen{
+		NewCaseWhen(condition1, "a"),
+		NewCaseWhen(condition2, "b"),
+	}, ce.When(condition1, "a").When(condition2, "b").GetWhens())
+
+	ces.Empty(ce.GetWhens())
+}
+
+func (ces *caseExpressionSuite) TestElse() {
+	ce := NewCaseExpression()
+	ces.Equal(NewCaseElse("a"), ce.Else("a").GetElse())
+
+	ces.Nil(ce.GetElse())
+}
+
+type caseWhenSuite struct {
+	suite.Suite
+}
+
+func TestCaseWhenSuite(t *testing.T) {
+	suite.Run(t, &caseWhenSuite{})
+}
+
+func (cws *caseWhenSuite) TestCondition() {
+	ce := NewCaseWhen(true, false)
+	cws.Equal(true, ce.Condition())
+}
+
+func (cws *caseWhenSuite) TestResult() {
+	ce := NewCaseWhen(true, false)
+	cws.Equal(false, ce.Result())
+}
+
+type caseElseSuite struct {
+	suite.Suite
+}
+
+func TestCaseElseSuite(t *testing.T) {
+	suite.Run(t, &caseElseSuite{})
+}
+
+func (ces *caseElseSuite) TestResult() {
+	ce := NewCaseElse(false)
+	ces.Equal(false, ce.Result())
+}

+ 56 - 0
src/github.com/doug-martin/goqu/exp/cast.go

@@ -0,0 +1,56 @@
+package exp
+
+type cast struct {
+	casted Expression
+	t      LiteralExpression
+}
+
+// Creates a new Casted expression
+//  Cast(I("a"), "NUMERIC") -> CAST("a" AS NUMERIC)
+func NewCastExpression(e Expression, t string) CastExpression {
+	return cast{casted: e, t: NewLiteralExpression(t)}
+}
+
+func (c cast) Casted() Expression {
+	return c.casted
+}
+
+func (c cast) Type() LiteralExpression {
+	return c.t
+}
+
+func (c cast) Clone() Expression {
+	return cast{casted: c.casted.Clone(), t: c.t}
+}
+
+func (c cast) Expression() Expression                           { return c }
+func (c cast) As(val interface{}) AliasedExpression             { return aliased(c, val) }
+func (c cast) Eq(val interface{}) BooleanExpression             { return eq(c, val) }
+func (c cast) Neq(val interface{}) BooleanExpression            { return neq(c, val) }
+func (c cast) Gt(val interface{}) BooleanExpression             { return gt(c, val) }
+func (c cast) Gte(val interface{}) BooleanExpression            { return gte(c, val) }
+func (c cast) Lt(val interface{}) BooleanExpression             { return lt(c, val) }
+func (c cast) Lte(val interface{}) BooleanExpression            { return lte(c, val) }
+func (c cast) Asc() OrderedExpression                           { return asc(c) }
+func (c cast) Desc() OrderedExpression                          { return desc(c) }
+func (c cast) Like(i interface{}) BooleanExpression             { return like(c, i) }
+func (c cast) NotLike(i interface{}) BooleanExpression          { return notLike(c, i) }
+func (c cast) ILike(i interface{}) BooleanExpression            { return iLike(c, i) }
+func (c cast) NotILike(i interface{}) BooleanExpression         { return notILike(c, i) }
+func (c cast) RegexpLike(val interface{}) BooleanExpression     { return regexpLike(c, val) }
+func (c cast) RegexpNotLike(val interface{}) BooleanExpression  { return regexpNotLike(c, val) }
+func (c cast) RegexpILike(val interface{}) BooleanExpression    { return regexpILike(c, val) }
+func (c cast) RegexpNotILike(val interface{}) BooleanExpression { return regexpNotILike(c, val) }
+func (c cast) In(i ...interface{}) BooleanExpression            { return in(c, i...) }
+func (c cast) NotIn(i ...interface{}) BooleanExpression         { return notIn(c, i...) }
+func (c cast) Is(i interface{}) BooleanExpression               { return is(c, i) }
+func (c cast) IsNot(i interface{}) BooleanExpression            { return isNot(c, i) }
+func (c cast) IsNull() BooleanExpression                        { return is(c, nil) }
+func (c cast) IsNotNull() BooleanExpression                     { return isNot(c, nil) }
+func (c cast) IsTrue() BooleanExpression                        { return is(c, true) }
+func (c cast) IsNotTrue() BooleanExpression                     { return isNot(c, true) }
+func (c cast) IsFalse() BooleanExpression                       { return is(c, false) }
+func (c cast) IsNotFalse() BooleanExpression                    { return isNot(c, false) }
+func (c cast) Distinct() SQLFunctionExpression                  { return NewSQLFunctionExpression("DISTINCT", c) }
+func (c cast) Between(val RangeVal) RangeExpression             { return between(c, val) }
+func (c cast) NotBetween(val RangeVal) RangeExpression          { return notBetween(c, val) }

+ 79 - 0
src/github.com/doug-martin/goqu/exp/cast_test.go

@@ -0,0 +1,79 @@
+package exp
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/suite"
+)
+
+type castExpressionSuite struct {
+	suite.Suite
+	ce CastExpression
+}
+
+func TestCastExpressionSuite(t *testing.T) {
+	suite.Run(t, &castExpressionSuite{
+		ce: NewCastExpression(NewIdentifierExpression("", "", "a"), "TEXT"),
+	})
+}
+
+func (ces *castExpressionSuite) TestClone() {
+	ces.Equal(ces.ce, ces.ce.Clone())
+}
+
+func (ces *castExpressionSuite) TestExpression() {
+	ces.Equal(ces.ce, ces.ce.Expression())
+}
+
+func (ces *castExpressionSuite) TestCasted() {
+	ces.Equal(NewIdentifierExpression("", "", "a"), ces.ce.Casted())
+}
+func (ces *castExpressionSuite) TestType() {
+	ces.Equal(NewLiteralExpression("TEXT"), ces.ce.Type())
+}
+
+func (ces *castExpressionSuite) TestAllOthers() {
+	ce := ces.ce
+	rv := NewRangeVal(1, 2)
+	pattern := "cast like%"
+	inVals := []interface{}{1, 2}
+	testCases := []struct {
+		Ex       Expression
+		Expected Expression
+	}{
+		{Ex: ce.As("a"), Expected: aliased(ce, "a")},
+		{Ex: ce.Eq(1), Expected: NewBooleanExpression(EqOp, ce, 1)},
+		{Ex: ce.Neq(1), Expected: NewBooleanExpression(NeqOp, ce, 1)},
+		{Ex: ce.Gt(1), Expected: NewBooleanExpression(GtOp, ce, 1)},
+		{Ex: ce.Gte(1), Expected: NewBooleanExpression(GteOp, ce, 1)},
+		{Ex: ce.Lt(1), Expected: NewBooleanExpression(LtOp, ce, 1)},
+		{Ex: ce.Lte(1), Expected: NewBooleanExpression(LteOp, ce, 1)},
+		{Ex: ce.Asc(), Expected: asc(ce)},
+		{Ex: ce.Desc(), Expected: desc(ce)},
+		{Ex: ce.Between(rv), Expected: between(ce, rv)},
+		{Ex: ce.NotBetween(rv), Expected: notBetween(ce, rv)},
+		{Ex: ce.Like(pattern), Expected: NewBooleanExpression(LikeOp, ce, pattern)},
+		{Ex: ce.NotLike(pattern), Expected: NewBooleanExpression(NotLikeOp, ce, pattern)},
+		{Ex: ce.ILike(pattern), Expected: NewBooleanExpression(ILikeOp, ce, pattern)},
+		{Ex: ce.NotILike(pattern), Expected: NewBooleanExpression(NotILikeOp, ce, pattern)},
+		{Ex: ce.RegexpLike(pattern), Expected: NewBooleanExpression(RegexpLikeOp, ce, pattern)},
+		{Ex: ce.RegexpNotLike(pattern), Expected: NewBooleanExpression(RegexpNotLikeOp, ce, pattern)},
+		{Ex: ce.RegexpILike(pattern), Expected: NewBooleanExpression(RegexpILikeOp, ce, pattern)},
+		{Ex: ce.RegexpNotILike(pattern), Expected: NewBooleanExpression(RegexpNotILikeOp, ce, pattern)},
+		{Ex: ce.In(inVals), Expected: NewBooleanExpression(InOp, ce, inVals)},
+		{Ex: ce.NotIn(inVals), Expected: NewBooleanExpression(NotInOp, ce, inVals)},
+		{Ex: ce.Is(true), Expected: NewBooleanExpression(IsOp, ce, true)},
+		{Ex: ce.IsNot(true), Expected: NewBooleanExpression(IsNotOp, ce, true)},
+		{Ex: ce.IsNull(), Expected: NewBooleanExpression(IsOp, ce, nil)},
+		{Ex: ce.IsNotNull(), Expected: NewBooleanExpression(IsNotOp, ce, nil)},
+		{Ex: ce.IsTrue(), Expected: NewBooleanExpression(IsOp, ce, true)},
+		{Ex: ce.IsNotTrue(), Expected: NewBooleanExpression(IsNotOp, ce, true)},
+		{Ex: ce.IsFalse(), Expected: NewBooleanExpression(IsOp, ce, false)},
+		{Ex: ce.IsNotFalse(), Expected: NewBooleanExpression(IsNotOp, ce, false)},
+		{Ex: ce.Distinct(), Expected: NewSQLFunctionExpression("DISTINCT", ce)},
+	}
+
+	for _, tc := range testCases {
+		ces.Equal(tc.Expected, tc.Ex)
+	}
+}

+ 84 - 0
src/github.com/doug-martin/goqu/exp/col.go

@@ -0,0 +1,84 @@
+package exp
+
+import (
+	"fmt"
+	"reflect"
+
+	"github.com/doug-martin/goqu/v9/internal/util"
+)
+
+type columnList struct {
+	columns []Expression
+}
+
+func NewColumnListExpression(vals ...interface{}) ColumnListExpression {
+	cols := []Expression{}
+	for _, val := range vals {
+		switch t := val.(type) {
+		case nil: // do nothing
+		case string:
+			cols = append(cols, ParseIdentifier(t))
+		case ColumnListExpression:
+			cols = append(cols, t.Columns()...)
+		case Expression:
+			cols = append(cols, t)
+		default:
+			_, valKind := util.GetTypeInfo(val, reflect.Indirect(reflect.ValueOf(val)))
+
+			if valKind == reflect.Struct {
+				cm, err := util.GetColumnMap(val)
+				if err != nil {
+					panic(err.Error())
+				}
+				structCols := cm.Cols()
+				for _, col := range structCols {
+					i := ParseIdentifier(col)
+					var sc Expression = i
+					if i.IsQualified() {
+						sc = i.As(NewIdentifierExpression("", "", col))
+					}
+					cols = append(cols, sc)
+				}
+			} else {
+				panic(fmt.Sprintf("Cannot created expression from  %+v", val))
+			}
+		}
+	}
+	return columnList{columns: cols}
+}
+
+func NewOrderedColumnList(vals ...OrderedExpression) ColumnListExpression {
+	exps := make([]interface{}, len(vals))
+	for i, col := range vals {
+		exps[i] = col.Expression()
+	}
+	return NewColumnListExpression(exps...)
+}
+
+func (cl columnList) Clone() Expression {
+	newExps := make([]Expression, len(cl.columns))
+	for i, exp := range cl.columns {
+		newExps[i] = exp.Clone()
+	}
+	return columnList{columns: newExps}
+}
+
+func (cl columnList) Expression() Expression {
+	return cl
+}
+
+func (cl columnList) IsEmpty() bool {
+	return len(cl.columns) == 0
+}
+
+func (cl columnList) Columns() []Expression {
+	return cl.columns
+}
+
+func (cl columnList) Append(cols ...Expression) ColumnListExpression {
+	ret := columnList{}
+	exps := append(ret.columns, cl.columns...)
+	exps = append(exps, cols...)
+	ret.columns = exps
+	return ret
+}

+ 19 - 0
src/github.com/doug-martin/goqu/exp/compound.go

@@ -0,0 +1,19 @@
+package exp
+
+type compound struct {
+	t   CompoundType
+	rhs AppendableExpression
+}
+
+func NewCompoundExpression(ct CompoundType, rhs AppendableExpression) CompoundExpression {
+	return compound{t: ct, rhs: rhs}
+}
+
+func (c compound) Expression() Expression { return c }
+
+func (c compound) Clone() Expression {
+	return compound{t: c.t, rhs: c.rhs.Clone().(AppendableExpression)}
+}
+
+func (c compound) Type() CompoundType        { return c.t }
+func (c compound) RHS() AppendableExpression { return c.rhs }

+ 86 - 0
src/github.com/doug-martin/goqu/exp/conflict.go

@@ -0,0 +1,86 @@
+package exp
+
+type (
+	doNothingConflict struct{}
+	// ConflictUpdate is the struct that represents the UPDATE fragment of an
+	// INSERT ... ON CONFLICT/ON DUPLICATE KEY DO UPDATE statement
+	conflictUpdate struct {
+		target      string
+		update      interface{}
+		whereClause ExpressionList
+	}
+)
+
+// Creates a conflict struct to be passed to InsertConflict to ignore constraint errors
+//  InsertConflict(DoNothing(),...) -> INSERT INTO ... ON CONFLICT DO NOTHING
+func NewDoNothingConflictExpression() ConflictExpression {
+	return &doNothingConflict{}
+}
+
+func (c doNothingConflict) Expression() Expression {
+	return c
+}
+
+func (c doNothingConflict) Clone() Expression {
+	return c
+}
+
+func (c doNothingConflict) Action() ConflictAction {
+	return DoNothingConflictAction
+}
+
+// Creates a ConflictUpdate struct to be passed to InsertConflict
+// Represents a ON CONFLICT DO UPDATE portion of an INSERT statement (ON DUPLICATE KEY UPDATE for mysql)
+//
+//  InsertConflict(DoUpdate("target_column", update),...) ->
+//  	INSERT INTO ... ON CONFLICT DO UPDATE SET a=b
+//  InsertConflict(DoUpdate("target_column", update).Where(Ex{"a": 1},...) ->
+//  	INSERT INTO ... ON CONFLICT DO UPDATE SET a=b WHERE a=1
+func NewDoUpdateConflictExpression(target string, update interface{}) ConflictUpdateExpression {
+	return &conflictUpdate{target: target, update: update}
+}
+
+func (c conflictUpdate) Expression() Expression {
+	return c
+}
+
+func (c conflictUpdate) Clone() Expression {
+	return &conflictUpdate{
+		target:      c.target,
+		update:      c.update,
+		whereClause: c.whereClause.Clone().(ExpressionList),
+	}
+}
+
+func (c conflictUpdate) Action() ConflictAction {
+	return DoUpdateConflictAction
+}
+
+// Returns the target conflict column. Only necessary for Postgres.
+// Will return an error for mysql/sqlite. Will also return an error if missing from a postgres ConflictUpdate.
+func (c conflictUpdate) TargetColumn() string {
+	return c.target
+}
+
+// Returns the Updates which represent the ON CONFLICT DO UPDATE portion of an insert statement. If nil,
+// there are no updates.
+func (c conflictUpdate) Update() interface{} {
+	return c.update
+}
+
+// Append to the existing Where clause for an ON CONFLICT DO UPDATE ... WHERE ...
+//  InsertConflict(DoNothing(),...) -> INSERT INTO ... ON CONFLICT DO NOTHING
+func (c *conflictUpdate) Where(expressions ...Expression) ConflictUpdateExpression {
+	if c.whereClause == nil {
+		c.whereClause = NewExpressionList(AndType, expressions...)
+	} else {
+		c.whereClause = c.whereClause.Append(expressions...)
+	}
+	return c
+}
+
+// Append to the existing Where clause for an ON CONFLICT DO UPDATE ... WHERE ...
+//  InsertConflict(DoNothing(),...) -> INSERT INTO ... ON CONFLICT DO NOTHING
+func (c *conflictUpdate) WhereClause() ExpressionList {
+	return c.whereClause
+}

+ 23 - 0
src/github.com/doug-martin/goqu/exp/cte.go

@@ -0,0 +1,23 @@
+package exp
+
+type commonExpr struct {
+	recursive bool
+	name      LiteralExpression
+	subQuery  Expression
+}
+
+// Creates a new WITH common table expression for a SQLExpression, typically Datasets'. This function is used
+// internally by Dataset when a CTE is added to another Dataset
+func NewCommonTableExpression(recursive bool, name string, subQuery Expression) CommonTableExpression {
+	return commonExpr{recursive: recursive, name: NewLiteralExpression(name), subQuery: subQuery}
+}
+
+func (ce commonExpr) Expression() Expression { return ce }
+
+func (ce commonExpr) Clone() Expression {
+	return commonExpr{recursive: ce.recursive, name: ce.name, subQuery: ce.subQuery.Clone().(SQLExpression)}
+}
+
+func (ce commonExpr) IsRecursive() bool       { return ce.recursive }
+func (ce commonExpr) Name() LiteralExpression { return ce.name }
+func (ce commonExpr) SubQuery() Expression    { return ce.subQuery }

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно