diff --git a/.gitignore b/.gitignore index a96dfe6..1a0170a 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,7 @@ debug_test # Mac .DS_Store + +# ignore local work file +go.work +go.work.sum \ No newline at end of file diff --git a/README.md b/README.md index 356ff6d..34dcab8 100644 --- a/README.md +++ b/README.md @@ -374,6 +374,30 @@ fmt.Println(err) // ``` +## Performance + +There are some benchmark cases in [./internal/benchmark](./internal/benchmark/). We can enter this directory and run `go test -bench . -benchmem` to see results. + +Here is the result running on my dev machine with v1.19.0 as a sample. + +```text +Benchmark_SelectBasic_SQLBuilder-12 1406461 848.1 ns/op 1064 B/op 18 allocs/op +Benchmark_SelectComplex_SQLBuilder-12 294868 4087 ns/op 3537 B/op 71 allocs/op +Benchmark_Update_SQLBuilder-12 351915 3172 ns/op 2937 B/op 49 allocs/op +Benchmark_Delete_SqlBuilder-12 473796 2569 ns/op 2008 B/op 38 allocs/op +``` + +As a comparasion, there are cases implemented by [squirrel v1.5.3](https://github.com/Masterminds/squirrel). Here is the result on my dev machine. + +```text +Benchmark_SelectBasic_Squirrel-12 210292 5740 ns/op 3281 B/op 70 allocs/op +Benchmark_SelectComplex_Squirrel-12 118238 10283 ns/op 6027 B/op 121 allocs/op +Benchmark_Update_Squirrel-12 94173 12899 ns/op 7691 B/op 165 allocs/op +Benchmark_Delete_Squirrel-12 153379 7869 ns/op 4858 B/op 102 allocs/op +``` + +It's highly appreciated to contribute more and representative cases. + ## FAQ ### What's the difference between this package and `squirrel` diff --git a/go.sum b/go.sum index a38953e..8682cdb 100644 --- a/go.sum +++ b/go.sum @@ -5,28 +5,8 @@ github.com/huandu/go-assert v1.1.5 h1:fjemmA7sSfYHJD7CUqs9qTwwfdNAx7/j2/ZlHXzNB3 github.com/huandu/go-assert v1.1.5/go.mod h1:yOLvuqZwmcHIC5rIzrBhT7D3Q9c3GFnd0JrPVhn/06U= github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/mattn/goveralls v0.0.7 h1:vzy0i4a2iDzEFMdXIxcanRadkr0FBvSBKUmj0P8SPlQ= -github.com/mattn/goveralls v0.0.7/go.mod h1:h8b4ow6FxSPMQHF6o2ve3qsclnffZjYTNEKmLesRwqw= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200522201501-cb1345f3a375 h1:SjQ2+AKWgZLc1xej6WSzL+Dfs5Uyd5xcZH1mGC411IA= -golang.org/x/tools v0.0.0-20200522201501-cb1345f3a375/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/internal/benchmark/doc.go b/internal/benchmark/doc.go new file mode 100644 index 0000000..7e8a95a --- /dev/null +++ b/internal/benchmark/doc.go @@ -0,0 +1,5 @@ +// Copyright 2018 Huan Du. All rights reserved. +// Licensed under the MIT license that can be found in the LICENSE file. + +// Package benchmark is to run benchmark test cases against some similar builder packages. +package benchmark diff --git a/internal/benchmark/go.mod b/internal/benchmark/go.mod new file mode 100644 index 0000000..bbe7501 --- /dev/null +++ b/internal/benchmark/go.mod @@ -0,0 +1,16 @@ +module github.com/huandu/go-sqlbuilder/benchmark + +go 1.19 + +require ( + github.com/Masterminds/squirrel v1.5.3 + github.com/huandu/go-sqlbuilder v1.19.0 +) + +require ( + github.com/huandu/xstrings v1.3.2 // indirect + github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect + github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect +) + +replace github.com/huandu/go-sqlbuilder => ../.. diff --git a/internal/benchmark/go.sum b/internal/benchmark/go.sum new file mode 100644 index 0000000..6cc9be6 --- /dev/null +++ b/internal/benchmark/go.sum @@ -0,0 +1,22 @@ +github.com/Masterminds/squirrel v1.5.3 h1:YPpoceAcxuzIljlr5iWpNKaql7hLeG1KLSrhvdHpkZc= +github.com/Masterminds/squirrel v1.5.3/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/huandu/go-assert v1.1.5 h1:fjemmA7sSfYHJD7CUqs9qTwwfdNAx7/j2/ZlHXzNB3c= +github.com/huandu/go-assert v1.1.5/go.mod h1:yOLvuqZwmcHIC5rIzrBhT7D3Q9c3GFnd0JrPVhn/06U= +github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= +github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/internal/benchmark/sqlbuilder_test.go b/internal/benchmark/sqlbuilder_test.go new file mode 100644 index 0000000..42cf3e2 --- /dev/null +++ b/internal/benchmark/sqlbuilder_test.go @@ -0,0 +1,78 @@ +package benchmark + +import ( + "testing" + + "github.com/huandu/go-sqlbuilder" +) + +func Benchmark_SelectBasic_SQLBuilder(b *testing.B) { + for i := 0; i < b.N; i++ { + sb := sqlbuilder.Select("*"). + From("users"). + Join("emails USING (email_id)") + sb.Where(sb.IsNull("deleted_at")) + sb.Build() + } +} + +func Benchmark_SelectComplex_SQLBuilder(b *testing.B) { + for i := 0; i < b.N; i++ { + sb := sqlbuilder.Select("id", "name") + innerSb := sqlbuilder.Select("*").From("banned") + innerSb.Where( + innerSb.NotIn("name", sqlbuilder.Flatten([]string{"Huan Du", "Charmy Liu"})...), + ) + + sb.From( + sb.BuilderAs(innerSb, "user"), + ) + sb.Where( + sb.In("status", sqlbuilder.Flatten([]int{1, 2, 3})...), + sb.Between("created_at", 1234567890, 1234599999), + ) + sb.OrderBy("modified_at").Desc() + + sb.Build() + } +} + +func Benchmark_Update_SQLBuilder(b *testing.B) { + for i := 0; i < b.N; i++ { + ub := sqlbuilder.Update("demo.user") + ub.Set( + ub.Assign("type", "sys"), + ub.Incr("credit"), + "modified_at = UNIX_TIMESTAMP(NOW())", + ) + ub.Where( + ub.GreaterThan("id", 1234), + ub.Like("name", "%Du"), + ub.Or( + ub.IsNull("id_card"), + ub.In("status", 1, 2, 5), + ), + "modified_at > created_at + "+ub.Var(86400), + ) + ub.OrderBy("id").Asc() + + ub.Build() + } +} + +func Benchmark_Delete_SqlBuilder(b *testing.B) { + for i := 0; i < b.N; i++ { + del := sqlbuilder.DeleteFrom("demo.user") + del.Where( + del.GreaterThan("id", 1234), + del.Like("name", "%Du"), + del.Or( + del.IsNull("id_card"), + del.In("status", 1, 2, 5), + ), + "modified_at > created_at + "+del.Var(86400), + ) + + del.Build() + } +} diff --git a/internal/benchmark/squirrel_test.go b/internal/benchmark/squirrel_test.go new file mode 100644 index 0000000..73cb09d --- /dev/null +++ b/internal/benchmark/squirrel_test.go @@ -0,0 +1,65 @@ +package benchmark + +import ( + "testing" + + sq "github.com/Masterminds/squirrel" +) + +func Benchmark_SelectBasic_Squirrel(b *testing.B) { + for i := 0; i < b.N; i++ { + users := sq.Select("*"). + From("users"). + Join("emails USING (email_id)"). + Where(sq.Eq{"deleted_at": nil}) + users.ToSql() + } +} + +func Benchmark_SelectComplex_Squirrel(b *testing.B) { + for i := 0; i < b.N; i++ { + innerSb := sq.Select("*"). + From("banned"). + Where("name NOT IN (?, ?)", "Huan Du", "Charmy Liu") + sb := sq.Select("id", "name"). + FromSelect(innerSb, "user"). + Where("status IN (?, ?, ?)", 1, 2, 3). + Where("created_at BETWEEN ? AND ?", 1234567890, 1234599999) + + sb.ToSql() + } +} + +func Benchmark_Update_Squirrel(b *testing.B) { + for i := 0; i < b.N; i++ { + ub := sq.Update("demo.user"). + Set("type", "sys"). + Set("credit", sq.Expr("credit + 1")). + Set("modified_at", sq.Expr("UNIX_TIMESTAMP(NOW())")). + Where(sq.Gt{"id": 1234}). + Where(sq.Like{"name": "%Du"}). + Where(sq.Or{ + sq.Eq{"id_card": nil}, + sq.Expr("status IN (?, ?, ?)", 1, 2, 5), + }). + Where(sq.Expr("modified_at > created_at + ?", 86400)). + OrderBy("id ASC") + + ub.ToSql() + } +} + +func Benchmark_Delete_Squirrel(b *testing.B) { + for i := 0; i < b.N; i++ { + del := sq.Delete("demo.user"). + Where(sq.Gt{"id": 1234}). + Where(sq.Like{"name": "%Du"}). + Where(sq.Or{ + sq.Eq{"id_card": nil}, + sq.Expr("status IN (?, ?, ?)", 1, 2, 5), + }). + Where(sq.Expr("modified_at > created_at + ?", 86400)) + + del.ToSql() + } +}