diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6ddf8e3f..d488f9e2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,7 +13,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - go-version: [1.19.x, 1.20.x, 1.21.x] + go-version: [1.20.x, 1.21.x, 1.22.x] runs-on: ${{ matrix.os }} diff --git a/Makefile b/Makefile index 5216760f..eea84293 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,9 @@ -SHELL ?= /bin/bash +SHELL ?= bash +CPU_CORES ?= $(shell nproc) -PARALLEL_FLAGS ?= --halt-on-error 2 --jobs=2 -v -u +PARALLEL_FLAGS ?= --halt-on-error 2 --jobs=$(CPU_CORES) -v -u -TEST_FLAGS ?= +TEST_FLAGS ?= -v -failfast -race -timeout 20m UPPER_DB_LOG ?= WARN diff --git a/adapter/cockroachdb/Makefile b/adapter/cockroachdb/Makefile index 2c15af39..a6ba1323 100644 --- a/adapter/cockroachdb/Makefile +++ b/adapter/cockroachdb/Makefile @@ -1,8 +1,8 @@ SHELL ?= bash -COCKROACHDB_VERSION ?= v23.1.8 -COCKROACHDB_SUPPORTED ?= $(COCKROACHDB_VERSION) v22.2.9 v21.2.9 -PROJECT ?= upper_cockroachdb_$(COCKROACHDB_VERSION) +COCKROACHDB_VERSION ?= v23.2.6 +COCKROACHDB_SUPPORTED ?= $(COCKROACHDB_VERSION) v22.2.19 v21.2.17 +PROJECT ?= $(subst .,_,"upper_cockroachdb_$(COCKROACHDB_VERSION)") DB_HOST ?= 127.0.0.1 DB_PORT ?= 26257 @@ -30,7 +30,7 @@ test-no-race: go test -v -failfast $(TEST_FLAGS) server-up: server-down - docker-compose -p $(PROJECT) up -d && \ + docker compose -p $(PROJECT) up -d && \ sleep 5 && \ psql -Uroot -h$(DB_HOST) -p$(DB_PORT) postgres -c "\ DROP USER IF EXISTS $(DB_USERNAME); \ @@ -41,7 +41,7 @@ server-up: server-down " server-down: - docker-compose -p $(PROJECT) down + docker compose -p $(PROJECT) down test-extended: parallel $(PARALLEL_FLAGS) \ diff --git a/adapter/cockroachdb/connection.go b/adapter/cockroachdb/connection.go index 04ba8af6..bfab6af6 100644 --- a/adapter/cockroachdb/connection.go +++ b/adapter/cockroachdb/connection.go @@ -75,14 +75,14 @@ func (vs values) Isset(k string) bool { // // You can use a ConnectionURL struct as an argument for Open: // -// var settings = cockroachdb.ConnectionURL{ -// Host: "localhost", // Database server IP or host name. -// Database: "peanuts", // Database name. -// User: "cbrown", // Optional user name. -// Password: "snoopy", // Optional user password. -// } +// var settings = cockroachdb.ConnectionURL{ +// Host: "localhost", // Database server IP or host name. +// Database: "peanuts", // Database name. +// User: "cbrown", // Optional user name. +// Password: "snoopy", // Optional user password. +// } // -// sess, err = cockroachdb.Open(settings) +// sess, err = cockroachdb.Open(settings) // // If you already have a valid DSN, you can use ParseURL to convert it into // a ConnectionURL before passing it to Open. diff --git a/adapter/cockroachdb/custom_types.go b/adapter/cockroachdb/custom_types.go index af5d528b..6f246e10 100644 --- a/adapter/cockroachdb/custom_types.go +++ b/adapter/cockroachdb/custom_types.go @@ -27,6 +27,7 @@ import ( "database/sql/driver" "time" + "github.com/upper/db/v4" "github.com/upper/db/v4/internal/sqlbuilder" ) @@ -90,12 +91,12 @@ func DecodeJSONB(dst interface{}, src interface{}) error { // // Example: // -// type MyCustomStruct struct { -// ID int64 `db:"id" json:"id"` -// Name string `db:"name" json:"name"` -// ... -// cockroachdb.JSONBConverter -// } +// type MyCustomStruct struct { +// ID int64 `db:"id" json:"id"` +// Name string `db:"name" json:"name"` +// ... +// cockroachdb.JSONBConverter +// } type JSONBConverter struct { } @@ -144,7 +145,7 @@ func (t *timeWrapper) Scan(src interface{}) error { } func (d *database) ConvertValueContext(ctx context.Context, in interface{}) interface{} { - tz, _ := ctx.Value("timezone").(*time.Location) + tz, _ := ctx.Value(db.ContextKey("timezone")).(*time.Location) switch v := in.(type) { case *time.Time: diff --git a/adapter/cockroachdb/custom_types_pgx.go b/adapter/cockroachdb/custom_types_pgx.go index 42be2a54..2d969580 100644 --- a/adapter/cockroachdb/custom_types_pgx.go +++ b/adapter/cockroachdb/custom_types_pgx.go @@ -1,3 +1,4 @@ +//go:build !pq // +build !pq // Copyright (c) 2012-present The upper.io/db authors. All rights reserved. diff --git a/adapter/cockroachdb/custom_types_pq.go b/adapter/cockroachdb/custom_types_pq.go index f843e95f..92cdd7b3 100644 --- a/adapter/cockroachdb/custom_types_pq.go +++ b/adapter/cockroachdb/custom_types_pq.go @@ -1,3 +1,4 @@ +//go:build pq // +build pq // Copyright (c) 2012-present The upper.io/db authors. All rights reserved. diff --git a/adapter/cockroachdb/database_pgx.go b/adapter/cockroachdb/database_pgx.go index a4aa0d75..102a1f5f 100644 --- a/adapter/cockroachdb/database_pgx.go +++ b/adapter/cockroachdb/database_pgx.go @@ -1,3 +1,4 @@ +//go:build !pq // +build !pq // Copyright (c) 2012-present The upper.io/db authors. All rights reserved. @@ -26,9 +27,12 @@ package cockroachdb import ( "context" "database/sql" + + "time" + _ "github.com/jackc/pgx/v4/stdlib" + "github.com/upper/db/v4" "github.com/upper/db/v4/internal/sqladapter" - "time" ) func (*database) OpenDSN(sess sqladapter.Session, dsn string) (*sql.DB, error) { @@ -38,7 +42,7 @@ func (*database) OpenDSN(sess sqladapter.Session, dsn string) (*sql.DB, error) { } if tz := connURL.Options["timezone"]; tz != "" { loc, _ := time.LoadLocation(tz) - ctx := context.WithValue(sess.Context(), "timezone", loc) + ctx := context.WithValue(sess.Context(), db.ContextKey("timezone"), loc) sess.SetContext(ctx) } return sql.Open("pgx", dsn) diff --git a/adapter/cockroachdb/database_pq.go b/adapter/cockroachdb/database_pq.go index 0540e77c..ea42c061 100644 --- a/adapter/cockroachdb/database_pq.go +++ b/adapter/cockroachdb/database_pq.go @@ -1,3 +1,4 @@ +//go:build pq // +build pq // Copyright (c) 2012-present The upper.io/db authors. All rights reserved. @@ -26,9 +27,11 @@ package cockroachdb import ( "context" "database/sql" + "time" + _ "github.com/lib/pq" + db "github.com/upper/db/v4" "github.com/upper/db/v4/internal/sqladapter" - "time" ) func (*database) OpenDSN(sess sqladapter.Session, dsn string) (*sql.DB, error) { @@ -38,7 +41,7 @@ func (*database) OpenDSN(sess sqladapter.Session, dsn string) (*sql.DB, error) { } if tz := connURL.Options["timezone"]; tz != "" { loc, _ := time.LoadLocation(tz) - ctx := context.WithValue(sess.Context(), "timezone", loc) + ctx := context.WithValue(sess.Context(), db.ContextKey("timezone"), loc) sess.SetContext(ctx) } return sql.Open("postgres", dsn) diff --git a/adapter/cockroachdb/docker-compose.yml b/adapter/cockroachdb/docker-compose.yml index f9f022ff..d59e86ac 100644 --- a/adapter/cockroachdb/docker-compose.yml +++ b/adapter/cockroachdb/docker-compose.yml @@ -1,7 +1,4 @@ -version: '3' - services: - server: image: cockroachdb/cockroach:${COCKROACHDB_VERSION:-v2} environment: diff --git a/adapter/mongo/Makefile b/adapter/mongo/Makefile index 7660d30f..fe58bac0 100644 --- a/adapter/mongo/Makefile +++ b/adapter/mongo/Makefile @@ -2,7 +2,7 @@ SHELL ?= bash MONGO_VERSION ?= 4 MONGO_SUPPORTED ?= $(MONGO_VERSION) 3 -PROJECT ?= upper_mongo_$(MONGO_VERSION) +PROJECT ?= $(subst .,_,"upper_mongo_$(MONGO_VERSION)") DB_HOST ?= 127.0.0.1 DB_PORT ?= 27017 @@ -31,11 +31,11 @@ test-no-race: go test -v -failfast $(TEST_FLAGS) server-up: server-down - docker-compose -p $(PROJECT) up -d && \ + docker compose -p $(PROJECT) up -d && \ sleep 10 server-down: - docker-compose -p $(PROJECT) down + docker compose -p $(PROJECT) down test-extended: parallel $(PARALLEL_FLAGS) \ diff --git a/adapter/mongo/connection_test.go b/adapter/mongo/connection_test.go index 4b21726f..8dba6512 100644 --- a/adapter/mongo/connection_test.go +++ b/adapter/mongo/connection_test.go @@ -23,23 +23,20 @@ package mongo import ( "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestConnectionURL(t *testing.T) { - c := ConnectionURL{} // Default connection string is only the protocol. - if c.String() != "" { - t.Fatal(`Expecting default connectiong string to be empty, got:`, c.String()) - } + assert.Equal(t, "", c.String(), "Expecting default connectiong string to be empty") // Adding a database name. c.Database = "myfilename" - - if c.String() != "mongodb://myfilename" { - t.Fatal(`Test failed, got:`, c.String()) - } + assert.Equal(t, "mongodb://myfilename", c.String()) // Adding an option. c.Options = map[string]string{ @@ -54,31 +51,22 @@ func TestConnectionURL(t *testing.T) { // Setting host. c.Host = "localhost" - if c.String() != "mongodb://user:pass@localhost/myfilename?cache=foobar&mode=ro" { - t.Fatal(`Test failed, got:`, c.String()) - } + assert.Equal(t, "mongodb://user:pass@localhost/myfilename?cache=foobar&mode=ro", c.String()) // Setting host and port. c.Host = "localhost:27017" - if c.String() != "mongodb://user:pass@localhost:27017/myfilename?cache=foobar&mode=ro" { - t.Fatal(`Test failed, got:`, c.String()) - } + assert.Equal(t, "mongodb://user:pass@localhost:27017/myfilename?cache=foobar&mode=ro", c.String()) // Setting cluster. c.Host = "localhost,1.2.3.4,example.org:1234" - if c.String() != "mongodb://user:pass@localhost,1.2.3.4,example.org:1234/myfilename?cache=foobar&mode=ro" { - t.Fatal(`Test failed, got:`, c.String()) - } + assert.Equal(t, "mongodb://user:pass@localhost,1.2.3.4,example.org:1234/myfilename?cache=foobar&mode=ro", c.String()) // Setting another database. c.Database = "another_database" - if c.String() != "mongodb://user:pass@localhost,1.2.3.4,example.org:1234/another_database?cache=foobar&mode=ro" { - t.Fatal(`Test failed, got:`, c.String()) - } - + assert.Equal(t, "mongodb://user:pass@localhost,1.2.3.4,example.org:1234/another_database?cache=foobar&mode=ro", c.String()) } func TestParseConnectionURL(t *testing.T) { @@ -88,48 +76,24 @@ func TestParseConnectionURL(t *testing.T) { s = "mongodb:///mydatabase" - if u, err = ParseURL(s); err != nil { - t.Fatal(err) - } + u, err = ParseURL(s) + require.NoError(t, err) - if u.Database != "mydatabase" { - t.Fatal("Failed to parse database.") - } + assert.Equal(t, "mydatabase", u.Database) s = "mongodb://user:pass@localhost,1.2.3.4,example.org:1234/another_database?cache=foobar&mode=ro" - if u, err = ParseURL(s); err != nil { - t.Fatal(err) - } + u, err = ParseURL(s) + require.NoError(t, err) - if u.Database != "another_database" { - t.Fatal("Failed to get database.") - } - - if u.Options["cache"] != "foobar" { - t.Fatal("Expecting option.") - } - - if u.Options["mode"] != "ro" { - t.Fatal("Expecting option.") - } - - if u.User != "user" { - t.Fatal("Expecting user.") - } - - if u.Password != "pass" { - t.Fatal("Expecting password.") - } - - if u.Host != "localhost,1.2.3.4,example.org:1234" { - t.Fatal("Expecting host.") - } + assert.Equal(t, "another_database", u.Database) + assert.Equal(t, "foobar", u.Options["cache"]) + assert.Equal(t, "ro", u.Options["mode"]) + assert.Equal(t, "user", u.User) + assert.Equal(t, "pass", u.Password) + assert.Equal(t, "localhost,1.2.3.4,example.org:1234", u.Host) s = "http://example.org" - - if _, err = ParseURL(s); err == nil { - t.Fatal("Expecting error.") - } - + _, err = ParseURL(s) + require.Error(t, err) } diff --git a/adapter/mssql/Makefile b/adapter/mssql/Makefile index acb1a83d..c70e54c2 100644 --- a/adapter/mssql/Makefile +++ b/adapter/mssql/Makefile @@ -2,7 +2,7 @@ SHELL ?= bash MSSQL_VERSION ?= 2022-latest MSSQL_SUPPORTED ?= $(MSSQL_VERSION) 2019-latest -PROJECT ?= upper_mssql_$(MSSQL_VERSION) +PROJECT ?= $(subst .,_,"upper_mssql_$(MSSQL_VERSION)") DB_HOST ?= 127.0.0.1 DB_PORT ?= 1433 @@ -31,11 +31,11 @@ test-no-race: go test -v -failfast $(TEST_FLAGS) server-up: server-down - docker-compose -p $(PROJECT) up -d && \ + docker compose -p $(PROJECT) up -d && \ sleep 20 server-down: - docker-compose -p $(PROJECT) down + docker compose -p $(PROJECT) down test-extended: parallel $(PARALLEL_FLAGS) \ diff --git a/adapter/mssql/connection_test.go b/adapter/mssql/connection_test.go index 9b1533a7..ef9b451d 100644 --- a/adapter/mssql/connection_test.go +++ b/adapter/mssql/connection_test.go @@ -23,23 +23,20 @@ package mssql import ( "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestConnectionURL(t *testing.T) { - c := ConnectionURL{} // Zero value equals to an empty string. - if c.String() != "" { - t.Fatal(`Expecting default connectiong string to be empty, got:`, c.String()) - } + assert.Equal(t, "", c.String(), "Expecting default connectiong string to be empty") // Adding a database name. c.Database = "mydbname" - - if c.String() != "sqlserver://127.0.0.1?database=mydbname" { - t.Fatal(`Test failed, got:`, c.String()) - } + assert.Equal(t, "sqlserver://127.0.0.1?database=mydbname", c.String()) // Adding an option. c.Options = map[string]string{ @@ -48,9 +45,7 @@ func TestConnectionURL(t *testing.T) { "instance": "instance1", } - if c.String() != "sqlserver://127.0.0.1/instance1?connection+timeout=30&database=mydbname¶m1=value1" { - t.Fatal(`Test failed, got:`, c.String()) - } + assert.Equal(t, "sqlserver://127.0.0.1/instance1?connection+timeout=30&database=mydbname¶m1=value1", c.String()) // Setting default options c.Options = nil @@ -59,16 +54,11 @@ func TestConnectionURL(t *testing.T) { c.User = "user" c.Password = "pass" - if c.String() != `sqlserver://user:pass@127.0.0.1?database=mydbname` { - t.Fatal(`Test failed, got:`, c.String()) - } + assert.Equal(t, `sqlserver://user:pass@127.0.0.1?database=mydbname`, c.String()) // Setting host. c.Host = "1.2.3.4:1433" - - if c.String() != `sqlserver://user:pass@1.2.3.4:1433?database=mydbname` { - t.Fatal(`Test failed, got:`, c.String()) - } + assert.Equal(t, `sqlserver://user:pass@1.2.3.4:1433?database=mydbname`, c.String()) } func TestParseConnectionURL(t *testing.T) { @@ -78,23 +68,11 @@ func TestParseConnectionURL(t *testing.T) { s = "sqlserver://user:pass@127.0.0.1:1433?connection+timeout=30&database=mydbname¶m1=value1" - if u, err = ParseURL(s); err != nil { - t.Fatal(err) - } - - if u.User != "user" { - t.Fatal("Expecting username.") - } - - if u.Password != "pass" { - t.Fatal("Expecting password.") - } - - if u.Host != "127.0.0.1:1433" { - t.Fatal("Expecting host.") - } + u, err = ParseURL(s) + require.NoError(t, err) - if u.Database != "mydbname" { - t.Fatal("Expecting database.") - } + assert.Equal(t, "user", u.User) + assert.Equal(t, "pass", u.Password) + assert.Equal(t, "127.0.0.1:1433", u.Host) + assert.Equal(t, "mydbname", u.Database) } diff --git a/adapter/mysql/Makefile b/adapter/mysql/Makefile index f3a8759d..dcad0e6a 100644 --- a/adapter/mysql/Makefile +++ b/adapter/mysql/Makefile @@ -1,8 +1,8 @@ SHELL ?= bash -MYSQL_VERSION ?= 8.1 +MYSQL_VERSION ?= 8.4 MYSQL_SUPPORTED ?= $(MYSQL_VERSION) 5.7 -PROJECT ?= upper_mysql_$(MYSQL_VERSION) +PROJECT ?= $(subst .,_,"upper_mysql_$(MYSQL_VERSION)") DB_HOST ?= 127.0.0.1 DB_PORT ?= 3306 @@ -31,11 +31,11 @@ test-no-race: go test -v -failfast $(TEST_FLAGS) server-up: server-down - docker-compose -p $(PROJECT) up -d && \ + docker compose -p $(PROJECT) up -d && \ sleep 15 server-down: - docker-compose -p $(PROJECT) down + docker compose -p $(PROJECT) down test-extended: parallel $(PARALLEL_FLAGS) \ diff --git a/adapter/mysql/connection_test.go b/adapter/mysql/connection_test.go index 894e4217..ca1a3a15 100644 --- a/adapter/mysql/connection_test.go +++ b/adapter/mysql/connection_test.go @@ -23,6 +23,9 @@ package mysql import ( "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestConnectionURL(t *testing.T) { @@ -30,26 +33,19 @@ func TestConnectionURL(t *testing.T) { c := ConnectionURL{} // Zero value equals to an empty string. - if c.String() != "" { - t.Fatal(`Expecting default connectiong string to be empty, got:`, c.String()) - } + assert.Equal(t, "", c.String(), "Expecting default connectiong string to be empty") // Adding a database name. c.Database = "mydbname" + assert.Equal(t, "/mydbname?charset=utf8&parseTime=true", c.String()) - if c.String() != "/mydbname?charset=utf8&parseTime=true" { - t.Fatal(`Test failed, got:`, c.String()) - } - - // Adding an option. + // Adding options c.Options = map[string]string{ "charset": "utf8mb4,utf8", "sys_var": "esc@ped", } - if c.String() != "/mydbname?charset=utf8mb4%2Cutf8&parseTime=true&sys_var=esc%40ped" { - t.Fatal(`Test failed, got:`, c.String()) - } + assert.Equal(t, "/mydbname?charset=utf8mb4%2Cutf8&parseTime=true&sys_var=esc%40ped", c.String()) // Setting default options c.Options = nil @@ -58,24 +54,17 @@ func TestConnectionURL(t *testing.T) { c.User = "user" c.Password = "pass" - if c.String() != `user:pass@/mydbname?charset=utf8&parseTime=true` { - t.Fatal(`Test failed, got:`, c.String()) - } + assert.Equal(t, "user:pass@/mydbname?charset=utf8&parseTime=true", c.String()) // Setting host. c.Host = "1.2.3.4:3306" - if c.String() != `user:pass@tcp(1.2.3.4:3306)/mydbname?charset=utf8&parseTime=true` { - t.Fatal(`Test failed, got:`, c.String()) - } + assert.Equal(t, `user:pass@tcp(1.2.3.4:3306)/mydbname?charset=utf8&parseTime=true`, c.String()) // Setting socket. c.Socket = "/path/to/socket" - if c.String() != `user:pass@unix(/path/to/socket)/mydbname?charset=utf8&parseTime=true` { - t.Fatal(`Test failed, got:`, c.String()) - } - + assert.Equal(t, `user:pass@unix(/path/to/socket)/mydbname?charset=utf8&parseTime=true`, c.String()) } func TestParseConnectionURL(t *testing.T) { @@ -85,54 +74,22 @@ func TestParseConnectionURL(t *testing.T) { s = "user:pass@unix(/path/to/socket)/mydbname?charset=utf8" - if u, err = ParseURL(s); err != nil { - t.Fatal(err) - } - - if u.User != "user" { - t.Fatal("Expecting username.") - } - - if u.Password != "pass" { - t.Fatal("Expecting password.") - } - - if u.Socket != "/path/to/socket" { - t.Fatal("Expecting socket.") - } - - if u.Database != "mydbname" { - t.Fatal("Expecting database.") - } + u, err = ParseURL(s) + require.NoError(t, err) - if u.Options["charset"] != "utf8" { - t.Fatal("Expecting charset.") - } + assert.Equal(t, "user", u.User) + assert.Equal(t, "pass", u.Password) + assert.Equal(t, "/path/to/socket", u.Socket) + assert.Equal(t, "mydbname", u.Database) + assert.Equal(t, "utf8", u.Options["charset"]) s = "user:pass@tcp(1.2.3.4:5678)/mydbname?charset=utf8" - - if u, err = ParseURL(s); err != nil { - t.Fatal(err) - } - - if u.User != "user" { - t.Fatal("Expecting username.") - } - - if u.Password != "pass" { - t.Fatal("Expecting password.") - } - - if u.Host != "1.2.3.4:5678" { - t.Fatal("Expecting host.") - } - - if u.Database != "mydbname" { - t.Fatal("Expecting database.") - } - - if u.Options["charset"] != "utf8" { - t.Fatal("Expecting charset.") - } - + u, err = ParseURL(s) + require.NoError(t, err) + + assert.Equal(t, "user", u.User) + assert.Equal(t, "pass", u.Password) + assert.Equal(t, "1.2.3.4:5678", u.Host) + assert.Equal(t, "mydbname", u.Database) + assert.Equal(t, "utf8", u.Options["charset"]) } diff --git a/adapter/mysql/custom_types.go b/adapter/mysql/custom_types.go index 4b78aff4..13f22801 100644 --- a/adapter/mysql/custom_types.go +++ b/adapter/mysql/custom_types.go @@ -149,12 +149,12 @@ func DecodeJSON(dst interface{}, src interface{}) error { // // Example: // -// type MyCustomStruct struct { -// ID int64 `db:"id" json:"id"` -// Name string `db:"name" json:"name"` -// ... -// mysql.JSONConverter -// } +// type MyCustomStruct struct { +// ID int64 `db:"id" json:"id"` +// Name string `db:"name" json:"name"` +// ... +// mysql.JSONConverter +// } type JSONConverter struct{} func (*JSONConverter) ConvertValue(in interface{}) interface { diff --git a/adapter/mysql/docker-compose.yml b/adapter/mysql/docker-compose.yml index 18ab3499..8e2859bd 100644 --- a/adapter/mysql/docker-compose.yml +++ b/adapter/mysql/docker-compose.yml @@ -1,7 +1,4 @@ -version: '3' - services: - server: image: mysql:${MYSQL_VERSION:-5} environment: diff --git a/adapter/postgresql/Makefile b/adapter/postgresql/Makefile index c9a0ff2b..162ffdd4 100644 --- a/adapter/postgresql/Makefile +++ b/adapter/postgresql/Makefile @@ -1,9 +1,8 @@ SHELL ?= bash -POSTGRES_VERSION ?= 15-alpine -POSTGRES_SUPPORTED ?= $(POSTGRES_VERSION) 14-alpine 13-alpine 12-alpine - -PROJECT ?= upper_postgres_$(POSTGRES_VERSION) +POSTGRES_VERSION ?= 16-alpine +POSTGRES_SUPPORTED ?= $(POSTGRES_VERSION) 15-alpine 14-alpine +PROJECT ?= $(subst .,_,"upper_postgres_$(POSTGRES_VERSION)") DB_HOST ?= 127.0.0.1 DB_PORT ?= 5432 @@ -26,17 +25,14 @@ export DB_USERNAME export TEST_FLAGS test: - go test -v -failfast -race -timeout 20m $(TEST_FLAGS) - -test-no-race: - go test -v -failfast $(TEST_FLAGS) + go test $(TEST_FLAGS) server-up: server-down - docker-compose -p $(PROJECT) up -d && \ + docker compose -p $(PROJECT) up -d && \ sleep 10 server-down: - docker-compose -p $(PROJECT) down + docker compose -p $(PROJECT) down test-extended: parallel $(PARALLEL_FLAGS) \ diff --git a/adapter/postgresql/connection.go b/adapter/postgresql/connection.go index 47699442..ee7a7cd7 100644 --- a/adapter/postgresql/connection.go +++ b/adapter/postgresql/connection.go @@ -77,14 +77,14 @@ func (vs values) Isset(k string) bool { // // You can use a ConnectionURL struct as an argument for Open: // -// var settings = postgresql.ConnectionURL{ -// Host: "localhost", // PostgreSQL server IP or name. -// Database: "peanuts", // Database name. -// User: "cbrown", // Optional user name. -// Password: "snoopy", // Optional user password. -// } +// var settings = postgresql.ConnectionURL{ +// Host: "localhost", // PostgreSQL server IP or name. +// Database: "peanuts", // Database name. +// User: "cbrown", // Optional user name. +// Password: "snoopy", // Optional user password. +// } // -// sess, err = postgresql.Open(settings) +// sess, err = postgresql.Open(settings) // // If you already have a valid DSN, you can use ParseURL to convert it into // a ConnectionURL before passing it to Open. @@ -104,7 +104,7 @@ var escaper = strings.NewReplacer(` `, `\ `, `'`, `\'`, `\`, `\\`) // ParseURL parses the given DSN into a ConnectionURL struct. // A typical PostgreSQL connection URL looks like: // -// postgres://bob:secret@1.2.3.4:5432/mydb?sslmode=verify-full +// postgres://bob:secret@1.2.3.4:5432/mydb?sslmode=verify-full func ParseURL(s string) (u *ConnectionURL, err error) { o := make(values) @@ -260,7 +260,7 @@ func newScanner(s string) *scanner { // // "postgres://" // -// This will be blank, causing driver.Open to use all of the defaults +// # This will be blank, causing driver.Open to use all of the defaults // // NOTE: vendored/copied from github.com/lib/pq func parseURL(uri string) (string, error) { diff --git a/adapter/postgresql/custom_types.go b/adapter/postgresql/custom_types.go index d06ee209..ff178552 100644 --- a/adapter/postgresql/custom_types.go +++ b/adapter/postgresql/custom_types.go @@ -27,6 +27,7 @@ import ( "database/sql/driver" "time" + "github.com/upper/db/v4" "github.com/upper/db/v4/internal/sqlbuilder" ) @@ -122,7 +123,7 @@ func (t *timeWrapper) Scan(src interface{}) error { } func (d *database) ConvertValueContext(ctx context.Context, in interface{}) interface{} { - tz, _ := ctx.Value("timezone").(*time.Location) + tz, _ := ctx.Value(db.ContextKey("timezone")).(*time.Location) switch v := in.(type) { case *time.Time: diff --git a/adapter/postgresql/custom_types_pgx.go b/adapter/postgresql/custom_types_pgx.go index 3559e6bf..2a0b6583 100644 --- a/adapter/postgresql/custom_types_pgx.go +++ b/adapter/postgresql/custom_types_pgx.go @@ -1,3 +1,4 @@ +//go:build !pq // +build !pq // Copyright (c) 2012-present The upper.io/db authors. All rights reserved. diff --git a/adapter/postgresql/custom_types_pq.go b/adapter/postgresql/custom_types_pq.go index 20ef131a..0ffdeb3d 100644 --- a/adapter/postgresql/custom_types_pq.go +++ b/adapter/postgresql/custom_types_pq.go @@ -1,3 +1,4 @@ +//go:build pq // +build pq package postgresql diff --git a/adapter/postgresql/database_pgx.go b/adapter/postgresql/database_pgx.go index 954a9382..6d5a2f3b 100644 --- a/adapter/postgresql/database_pgx.go +++ b/adapter/postgresql/database_pgx.go @@ -27,9 +27,11 @@ package postgresql import ( "context" "database/sql" + "time" + _ "github.com/jackc/pgx/v4/stdlib" + "github.com/upper/db/v4" "github.com/upper/db/v4/internal/sqladapter" - "time" ) func (*database) OpenDSN(sess sqladapter.Session, dsn string) (*sql.DB, error) { @@ -39,7 +41,7 @@ func (*database) OpenDSN(sess sqladapter.Session, dsn string) (*sql.DB, error) { } if tz := connURL.Options["timezone"]; tz != "" { loc, _ := time.LoadLocation(tz) - ctx := context.WithValue(sess.Context(), "timezone", loc) + ctx := context.WithValue(sess.Context(), db.ContextKey("timezone"), loc) sess.SetContext(ctx) } return sql.Open("pgx", dsn) diff --git a/adapter/postgresql/database_pq.go b/adapter/postgresql/database_pq.go index 7b0c9b76..2415f624 100644 --- a/adapter/postgresql/database_pq.go +++ b/adapter/postgresql/database_pq.go @@ -1,3 +1,4 @@ +//go:build pq // +build pq package postgresql @@ -26,9 +27,11 @@ package postgresql import ( "context" "database/sql" + "time" + _ "github.com/lib/pq" + db "github.com/upper/db/v4" "github.com/upper/db/v4/internal/sqladapter" - "time" ) func (*database) OpenDSN(sess sqladapter.Session, dsn string) (*sql.DB, error) { @@ -38,7 +41,7 @@ func (*database) OpenDSN(sess sqladapter.Session, dsn string) (*sql.DB, error) { } if tz := connURL.Options["timezone"]; tz != "" { loc, _ := time.LoadLocation(tz) - ctx := context.WithValue(sess.Context(), "timezone", loc) + ctx := context.WithValue(sess.Context(), db.ContextKey("timezone"), loc) sess.SetContext(ctx) } return sql.Open("postgres", dsn) diff --git a/adapter/postgresql/docker-compose.yml b/adapter/postgresql/docker-compose.yml index 4f4884a3..7f751f0a 100644 --- a/adapter/postgresql/docker-compose.yml +++ b/adapter/postgresql/docker-compose.yml @@ -1,7 +1,4 @@ -version: '3' - services: - server: image: postgres:${POSTGRES_VERSION:-11} environment: diff --git a/adapter/postgresql/postgresql_test.go b/adapter/postgresql/postgresql_test.go index 900277af..182352b5 100644 --- a/adapter/postgresql/postgresql_test.go +++ b/adapter/postgresql/postgresql_test.go @@ -126,8 +126,6 @@ func (i64a *int64CompatArray) Scan(src interface{}) error { return nil } -type uintCompatArray []uintCompat - type AdapterTests struct { testsuite.Suite } diff --git a/adapter/ql/connection_test.go b/adapter/ql/connection_test.go index cd453e00..dfb5e2e4 100644 --- a/adapter/ql/connection_test.go +++ b/adapter/ql/connection_test.go @@ -22,9 +22,10 @@ package ql import ( - "github.com/stretchr/testify/assert" "path/filepath" "testing" + + "github.com/stretchr/testify/assert" ) func TestConnectionURL(t *testing.T) { diff --git a/adapter/sqlite/connection_test.go b/adapter/sqlite/connection_test.go index 7baa810d..f55cca70 100644 --- a/adapter/sqlite/connection_test.go +++ b/adapter/sqlite/connection_test.go @@ -24,25 +24,23 @@ package sqlite import ( "path/filepath" "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestConnectionURL(t *testing.T) { c := ConnectionURL{} - // Default connection string is only the protocol. - if c.String() != "" { - t.Fatal(`Expecting default connectiong string to be empty, got:`, c.String()) - } + assert.Equal(t, "", c.String(), "Expecting default connectiong string to be empty") // Adding a database name. c.Database = "myfilename" absoluteName, _ := filepath.Abs(c.Database) - if c.String() != "file://"+absoluteName+"?_busy_timeout=10000" { - t.Fatal(`Test failed, got:`, c.String()) - } + assert.Equal(t, "file://"+absoluteName+"?_busy_timeout=10000", c.String()) // Adding an option. c.Options = map[string]string{ @@ -50,17 +48,12 @@ func TestConnectionURL(t *testing.T) { "mode": "ro", } - if c.String() != "file://"+absoluteName+"?_busy_timeout=10000&cache=foobar&mode=ro" { - t.Fatal(`Test failed, got:`, c.String()) - } + assert.Equal(t, "file://"+absoluteName+"?_busy_timeout=10000&cache=foobar&mode=ro", c.String()) // Setting another database. c.Database = "/another/database" - if c.String() != `file:///another/database?_busy_timeout=10000&cache=foobar&mode=ro` { - t.Fatal(`Test failed, got:`, c.String()) - } - + assert.Equal(t, "file:///another/database?_busy_timeout=10000&cache=foobar&mode=ro", c.String()) } func TestParseConnectionURL(t *testing.T) { @@ -70,40 +63,24 @@ func TestParseConnectionURL(t *testing.T) { s = "file://mydatabase.db" - if u, err = ParseURL(s); err != nil { - t.Fatal(err) - } + u, err = ParseURL(s) + require.NoError(t, err) - if u.Database != "mydatabase.db" { - t.Fatal("Failed to parse database.") - } + assert.Equal(t, "mydatabase.db", u.Database) - if u.Options["cache"] != "shared" { - t.Fatal("If not defined, cache should be shared by default.") - } + assert.Equal(t, "shared", u.Options["cache"]) s = "file:///path/to/my/database.db?_busy_timeout=10000&mode=ro&cache=foobar" - if u, err = ParseURL(s); err != nil { - t.Fatal(err) - } - - if u.Database != "/path/to/my/database.db" { - t.Fatal("Failed to parse username.") - } + u, err = ParseURL(s) + require.NoError(t, err) - if u.Options["cache"] != "foobar" { - t.Fatal("Expecting option.") - } + assert.Equal(t, "/path/to/my/database.db", u.Database) - if u.Options["mode"] != "ro" { - t.Fatal("Expecting option.") - } + assert.Equal(t, "foobar", u.Options["cache"]) + assert.Equal(t, "ro", u.Options["mode"]) s = "http://example.org" - - if _, err = ParseURL(s); err == nil { - t.Fatal("Expecting error.") - } - + _, err = ParseURL(s) + require.Error(t, err) } diff --git a/comparison_test.go b/comparison_test.go index 3d171fb1..6177a8ea 100644 --- a/comparison_test.go +++ b/comparison_test.go @@ -22,6 +22,7 @@ package db import ( + "fmt" "testing" "time" @@ -29,7 +30,7 @@ import ( "github.com/upper/db/v4/internal/adapter" ) -func TestComparison(t *testing.T) { +func TestComparisonOperators(t *testing.T) { testTimeVal := time.Now() testCases := []struct { @@ -127,6 +128,8 @@ func TestComparison(t *testing.T) { } for i := range testCases { - assert.Equal(t, testCases[i].expects, testCases[i].result.Comparison) + t.Run(fmt.Sprintf("Case %02d", i), func(t *testing.T) { + assert.Equal(t, testCases[i].expects, testCases[i].result.Comparison) + }) } } diff --git a/cond.go b/cond.go index cd8c070f..5895ad52 100644 --- a/cond.go +++ b/cond.go @@ -43,20 +43,20 @@ type LogicalOperator = adapter.LogicalOperator // // Examples: // -// // Age equals 18. -// db.Cond{"age": 18} +// // Age equals 18. +// db.Cond{"age": 18} // -// // Age is greater than or equal to 18. -// db.Cond{"age >=": 18} +// // Age is greater than or equal to 18. +// db.Cond{"age >=": 18} // -// // id is any of the values 1, 2 or 3. -// db.Cond{"id IN": []{1, 2, 3}} +// // id is any of the values 1, 2 or 3. +// db.Cond{"id IN": []{1, 2, 3}} // -// // Age is lower than 18 (MongoDB syntax) -// db.Cond{"age $lt": 18} +// // Age is lower than 18 (MongoDB syntax) +// db.Cond{"age $lt": 18} // -// // age > 32 and age < 35 -// db.Cond{"age >": 32, "age <": 35} +// // age > 32 and age < 35 +// db.Cond{"age >": 32, "age <": 35} type Cond map[interface{}]interface{} // Empty returns false if there are no conditions. diff --git a/cond_test.go b/cond_test.go index aed02437..8e0ed400 100644 --- a/cond_test.go +++ b/cond_test.go @@ -2,68 +2,50 @@ package db import ( "testing" + + "github.com/stretchr/testify/assert" ) func TestCond(t *testing.T) { - c := Cond{} - - if !c.Empty() { - t.Fatal("Cond is empty.") - } - - c = Cond{"id": 1} - if c.Empty() { - t.Fatal("Cond is not empty.") - } -} + t.Run("Base", func(t *testing.T) { + var c Cond -func TestCondAnd(t *testing.T) { - a := And() + c = Cond{} + assert.True(t, c.Empty()) - if !a.Empty() { - t.Fatal("Cond is empty") - } + c = Cond{"id": 1} + assert.False(t, c.Empty()) + }) - _ = a.And(Cond{"id": 1}) + t.Run("And", func(t *testing.T) { + var a *AndExpr - if !a.Empty() { - t.Fatal("Cond is still empty") - } + a = And() + assert.True(t, a.Empty()) - a = a.And(Cond{"name": "Ana"}) - - if a.Empty() { - t.Fatal("Cond is not empty anymore") - } - - a = a.And().And() - - if a.Empty() { - t.Fatal("Cond is not empty anymore") - } -} + _ = a.And(Cond{"id": 1}) + assert.True(t, a.Empty(), "conditions are immutable") -func TestCondOr(t *testing.T) { - a := Or() + a = a.And(Cond{"name": "Ana"}) + assert.False(t, a.Empty()) - if !a.Empty() { - t.Fatal("Cond is empty") - } + a = a.And().And() + assert.False(t, a.Empty()) + }) - _ = a.Or(Cond{"id": 1}) + t.Run("Or", func(t *testing.T) { + var a *OrExpr - if !a.Empty() { - t.Fatal("Cond is empty") - } + a = Or() + assert.True(t, a.Empty()) - a = a.Or(Cond{"name": "Ana"}) + _ = a.Or(Cond{"id": 1}) + assert.True(t, a.Empty(), "conditions are immutable") - if a.Empty() { - t.Fatal("Cond is not empty") - } + a = a.Or(Cond{"name": "Ana"}) + assert.False(t, a.Empty()) - a = a.Or().Or() - if a.Empty() { - t.Fatal("Cond is not empty") - } + a = a.Or().Or() + assert.False(t, a.Empty()) + }) } diff --git a/context.go b/context.go new file mode 100644 index 00000000..8c6192c4 --- /dev/null +++ b/context.go @@ -0,0 +1,3 @@ +package db + +type ContextKey string diff --git a/db.go b/db.go index dc882b74..f644d074 100644 --- a/db.go +++ b/db.go @@ -24,48 +24,48 @@ // // Install upper/db: // -// go get github.com/upper/db +// go get github.com/upper/db // // Usage // -// package main +// package main // -// import ( -// "log" +// import ( +// "log" // -// "github.com/upper/db/v4/adapter/postgresql" // Imports the postgresql adapter. -// ) +// "github.com/upper/db/v4/adapter/postgresql" // Imports the postgresql adapter. +// ) // -// var settings = postgresql.ConnectionURL{ -// Database: `booktown`, -// Host: `demo.upper.io`, -// User: `demouser`, -// Password: `demop4ss`, -// } +// var settings = postgresql.ConnectionURL{ +// Database: `booktown`, +// Host: `demo.upper.io`, +// User: `demouser`, +// Password: `demop4ss`, +// } // -// // Book represents a book. -// type Book struct { -// ID uint `db:"id"` -// Title string `db:"title"` -// AuthorID uint `db:"author_id"` -// SubjectID uint `db:"subject_id"` -// } +// // Book represents a book. +// type Book struct { +// ID uint `db:"id"` +// Title string `db:"title"` +// AuthorID uint `db:"author_id"` +// SubjectID uint `db:"subject_id"` +// } // -// func main() { -// sess, err := postgresql.Open(settings) -// if err != nil { -// log.Fatal(err) -// } -// defer sess.Close() +// func main() { +// sess, err := postgresql.Open(settings) +// if err != nil { +// log.Fatal(err) +// } +// defer sess.Close() // -// var books []Book -// if err := sess.Collection("books").Find().OrderBy("title").All(&books); err != nil { -// log.Fatal(err) -// } +// var books []Book +// if err := sess.Collection("books").Find().OrderBy("title").All(&books); err != nil { +// log.Fatal(err) +// } // -// log.Println("Books:") -// for _, book := range books { -// log.Printf("%q (ID: %d)\n", book.Title, book.ID) -// } -// } +// log.Println("Books:") +// for _, book := range books { +// log.Printf("%q (ID: %d)\n", book.Title, book.ID) +// } +// } package db diff --git a/errors_test.go b/errors_test.go deleted file mode 100644 index 927b71f6..00000000 --- a/errors_test.go +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) 2012-present The upper.io/db authors. All rights reserved. -// -// 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 db - -import ( - "errors" - "fmt" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestErrorWrap(t *testing.T) { - adapterFakeErr := fmt.Errorf("could not find item in %q: %w", "users", ErrCollectionDoesNotExist) - assert.True(t, errors.Is(adapterFakeErr, ErrCollectionDoesNotExist)) -} diff --git a/function_test.go b/function_test.go index 1ffd4a35..bf4f90f3 100644 --- a/function_test.go +++ b/function_test.go @@ -6,46 +6,46 @@ import ( "github.com/stretchr/testify/assert" ) -func TestFunction(t *testing.T) { - { - fn := Func("MOD", 29, 9) - assert.Equal(t, "MOD", fn.Name()) - assert.Equal(t, []interface{}{29, 9}, fn.Arguments()) - } - - { +func TestCustomFunctions(t *testing.T) { + t.Run("Nil arguments", func(t *testing.T) { fn := Func("HELLO") assert.Equal(t, "HELLO", fn.Name()) assert.Equal(t, []interface{}(nil), fn.Arguments()) - } + }) - { + t.Run("Single argument", func(t *testing.T) { fn := Func("CONCAT", "a") assert.Equal(t, "CONCAT", fn.Name()) assert.Equal(t, []interface{}{"a"}, fn.Arguments()) - } + }) + + t.Run("Two arguments", func(t *testing.T) { + fn := Func("MOD", 29, 9) + assert.Equal(t, "MOD", fn.Name()) + assert.Equal(t, []interface{}{29, 9}, fn.Arguments()) + }) - { + t.Run("Multiple arguments", func(t *testing.T) { fn := Func("CONCAT", "a", "b", "c") assert.Equal(t, "CONCAT", fn.Name()) assert.Equal(t, []interface{}{"a", "b", "c"}, fn.Arguments()) - } + }) - { + t.Run("Slice argument", func(t *testing.T) { fn := Func("IN", []interface{}{"a", "b", "c"}) assert.Equal(t, "IN", fn.Name()) assert.Equal(t, []interface{}{[]interface{}{"a", "b", "c"}}, fn.Arguments()) - } + }) - { + t.Run("Slice argument with one element", func(t *testing.T) { fn := Func("IN", []interface{}{"a"}) assert.Equal(t, "IN", fn.Name()) assert.Equal(t, []interface{}{[]interface{}{"a"}}, fn.Arguments()) - } + }) - { + t.Run("Nil slice argument", func(t *testing.T) { fn := Func("IN", []interface{}(nil)) assert.Equal(t, "IN", fn.Name()) assert.Equal(t, []interface{}{[]interface{}(nil)}, fn.Arguments()) - } + }) } diff --git a/go.mod b/go.mod index 9ad8930d..7997a9cc 100644 --- a/go.mod +++ b/go.mod @@ -4,25 +4,24 @@ go 1.15 require ( github.com/denisenkom/go-mssqldb v0.12.3 - github.com/go-sql-driver/mysql v1.7.1 + github.com/go-sql-driver/mysql v1.8.1 github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect github.com/google/uuid v1.1.1 github.com/ipfs/go-detect-race v0.0.1 - github.com/jackc/pgconn v1.14.1 // indirect - github.com/jackc/pgtype v1.14.0 - github.com/jackc/pgx/v4 v4.18.1 + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/pgtype v1.14.3 + github.com/jackc/pgx/v4 v4.18.3 github.com/lib/pq v1.10.9 - github.com/mattn/go-sqlite3 v1.14.17 + github.com/mattn/go-sqlite3 v1.14.22 github.com/segmentio/fasthash v1.0.3 github.com/sirupsen/logrus v1.9.3 - github.com/stretchr/testify v1.8.4 - golang.org/x/crypto v0.12.0 // indirect - golang.org/x/sys v0.12.0 // indirect - golang.org/x/text v0.13.0 // indirect + github.com/stretchr/testify v1.9.0 + golang.org/x/crypto v0.24.0 // indirect gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 modernc.org/b v1.1.0 // indirect modernc.org/db v1.0.10 // indirect modernc.org/file v1.0.8 // indirect + modernc.org/fileutil v1.3.0 // indirect modernc.org/golex v1.1.0 // indirect modernc.org/lldb v1.0.8 // indirect modernc.org/ql v1.4.7 diff --git a/go.sum b/go.sum index e3ae3635..a5371d9f 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0/go.mod h1:h6H6c8enJmmocHUbLiiGY6sx7f9i+X3m1CHdd5c6Rdw= github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0/go.mod h1:HcM1YX14R7CJcghJGOYCgdezslRSVzqwLf/q+4Y2r/0= github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8= @@ -21,6 +23,8 @@ github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vb github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= @@ -31,6 +35,7 @@ github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -50,6 +55,8 @@ github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRb github.com/jackc/pgconn v1.14.0/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E= github.com/jackc/pgconn v1.14.1 h1:smbxIaZA08n6YuxEX1sDyjV/qkbtUtkH20qLkR9MUR4= github.com/jackc/pgconn v1.14.1/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E= +github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w= +github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM= github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= @@ -68,21 +75,30 @@ github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwX github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgproto3/v2 v2.3.2 h1:7eY55bdBeCz1F2fTzSz69QC+pG46jYq9/jtSPiJ5nn0= github.com/jackc/pgproto3/v2 v2.3.2/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag= +github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw= github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgtype v1.14.3 h1:h6W9cPuHsRWQFTWUZMAKMgG5jSwQI0Zurzdvlx3Plus= +github.com/jackc/pgtype v1.14.3/go.mod h1:aKeozOde08iifGosdJpz9MBZonJOUJxqNpPBcMJTlVA= github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= github.com/jackc/pgx/v4 v4.18.1 h1:YP7G1KABtKpB5IHrO9vYwSrCOhs7p3uqhvhhQBptya0= github.com/jackc/pgx/v4 v4.18.1/go.mod h1:FydWkUyadDmdNH/mHnGob881GawxeEm7TcMCzkb+qQE= +github.com/jackc/pgx/v4 v4.18.2/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw= +github.com/jackc/pgx/v4 v4.18.3 h1:dE2/TrEsGX3RBprb3qryqSV9Y60iZN1C6i8IrmW9/BA= +github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw= github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= @@ -107,8 +123,10 @@ github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM= -github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/mattn/go-sqlite3 v1.14.18 h1:JL0eqdCOq6DJVNPSvArO/bIV9/P7fbGrV00LZHc+5aI= +github.com/mattn/go-sqlite3 v1.14.18/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= @@ -138,6 +156,7 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -148,6 +167,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= @@ -174,14 +195,23 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= -golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= +golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= golang.org/x/exp v0.0.0-20181106170214-d68db9428509/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 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= @@ -192,9 +222,15 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -214,15 +250,25 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -231,9 +277,12 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= @@ -245,6 +294,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -281,6 +332,8 @@ modernc.org/fileutil v1.1.1/go.mod h1:HdjlliqRHrMAI4nVOvvpYVzVgvRSK7WnoCiG0GUWJN modernc.org/fileutil v1.1.2/go.mod h1:HdjlliqRHrMAI4nVOvvpYVzVgvRSK7WnoCiG0GUWJNo= modernc.org/fileutil v1.2.0 h1:c7fsfzHf9WfUFXvv/RY9sStAr+VAKXYGKiAhBQQNoT4= modernc.org/fileutil v1.2.0/go.mod h1:0rLMFc17WSz6Bm/GtHeme7TOX8pNRhFN2NkfBlOZhrQ= +modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= +modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= modernc.org/golex v1.0.5/go.mod h1:pTY7KKjdvZbv2ROjfp6FFX5BXMM9QWZEnmCsl60aCfI= modernc.org/golex v1.1.0 h1:dmSaksHMd+y6NkBsRsCShNPRaSNCNH+abrVm5/gZic8= modernc.org/golex v1.1.0/go.mod h1:2pVlfqApurXhR1m0N+WDYu6Twnc4QuvO4+U8HnwoiRA= diff --git a/internal/cache/cache.go b/internal/cache/cache.go index 80dadac9..b429b25b 100644 --- a/internal/cache/cache.go +++ b/internal/cache/cache.go @@ -31,9 +31,10 @@ const defaultCapacity = 128 // Cache holds a map of volatile key -> values. type Cache struct { + mu sync.RWMutex + keys *list.List items map[uint64]*list.Element - mu sync.RWMutex capacity int } @@ -48,9 +49,11 @@ func NewCacheWithCapacity(capacity int) (*Cache, error) { if capacity < 1 { return nil, errors.New("Capacity must be greater than zero.") } + c := &Cache{ capacity: capacity, } + c.init() return c, nil } @@ -58,9 +61,11 @@ func NewCacheWithCapacity(capacity int) (*Cache, error) { // NewCache initializes a new caching space with default settings. func NewCache() *Cache { c, err := NewCacheWithCapacity(defaultCapacity) + if err != nil { panic(err.Error()) // Should never happen as we're not providing a negative defaultCapacity. } + return c } @@ -112,8 +117,9 @@ func (c *Cache) Write(h Hashable, value interface{}) { for c.keys.Len() > c.capacity { item := c.keys.Remove(c.keys.Back()).(*cacheItem) delete(c.items, item.key) - if p, ok := item.value.(HasOnEvict); ok { - p.OnEvict() + + if evictor, hasOnEvict := item.value.(HasOnEvict); hasOnEvict { + evictor.OnEvict() } } } diff --git a/internal/cache/cache_test.go b/internal/cache/cache_test.go index 76634cce..a9615074 100644 --- a/internal/cache/cache_test.go +++ b/internal/cache/cache_test.go @@ -25,9 +25,9 @@ import ( "fmt" "hash/fnv" "testing" -) -var c *Cache + "github.com/stretchr/testify/assert" +) type cacheableT struct { Name string @@ -39,39 +39,34 @@ func (ct *cacheableT) Hash() uint64 { return s.Sum64() } -var ( - key = cacheableT{"foo"} - value = "bar" -) - -func TestNewCache(t *testing.T) { - c = NewCache() - if c == nil { - t.Fatal("Expecting a new cache object.") - } -} - -func TestCacheReadNonExistentValue(t *testing.T) { - if _, ok := c.Read(&key); ok { - t.Fatal("Expecting false.") - } -} - -func TestCacheWritingValue(t *testing.T) { - c.Write(&key, value) - c.Write(&key, value) -} - -func TestCacheReadExistentValue(t *testing.T) { - s, ok := c.Read(&key) - - if !ok { - t.Fatal("Expecting true.") - } - - if s != value { - t.Fatal("Expecting value.") - } +func TestCache(t *testing.T) { + var c *Cache + + var ( + key = cacheableT{"foo"} + value = "bar" + ) + + t.Run("New", func(t *testing.T) { + c = NewCache() + assert.NotNil(t, c) + }) + + t.Run("ReadNonExistentValue", func(t *testing.T) { + _, ok := c.Read(&key) + assert.False(t, ok) + }) + + t.Run("Write", func(t *testing.T) { + c.Write(&key, value) + c.Write(&key, value) + }) + + t.Run("ReadExistentValue", func(t *testing.T) { + v, ok := c.Read(&key) + assert.True(t, ok) + assert.Equal(t, value, v) + }) } func BenchmarkNewCache(b *testing.B) { @@ -88,6 +83,8 @@ func BenchmarkNewCacheAndClear(b *testing.B) { } func BenchmarkReadNonExistentValue(b *testing.B) { + key := cacheableT{"foo"} + z := NewCache() for i := 0; i < b.N; i++ { z.Read(&key) @@ -95,6 +92,9 @@ func BenchmarkReadNonExistentValue(b *testing.B) { } func BenchmarkWriteSameValue(b *testing.B) { + key := cacheableT{"foo"} + value := "bar" + z := NewCache() for i := 0; i < b.N; i++ { z.Write(&key, value) @@ -102,6 +102,8 @@ func BenchmarkWriteSameValue(b *testing.B) { } func BenchmarkWriteNewValue(b *testing.B) { + value := "bar" + z := NewCache() for i := 0; i < b.N; i++ { key := cacheableT{fmt.Sprintf("item-%d", i)} @@ -110,6 +112,9 @@ func BenchmarkWriteNewValue(b *testing.B) { } func BenchmarkReadExistentValue(b *testing.B) { + key := cacheableT{"foo"} + value := "bar" + z := NewCache() z.Write(&key, value) for i := 0; i < b.N; i++ { diff --git a/internal/reflectx/reflect.go b/internal/reflectx/reflect.go index 888edeb8..5ca8cbc6 100644 --- a/internal/reflectx/reflect.go +++ b/internal/reflectx/reflect.go @@ -3,7 +3,6 @@ // allows for Go-compatible named attribute access, including accessing embedded // struct attributes and the ability to use functions and struct tags to // customize field names. -// package reflectx import ( @@ -155,7 +154,7 @@ func (m *Mapper) FieldByName(v reflect.Value, name string) reflect.Value { tm := m.TypeMap(v.Type()) fi, ok := tm.Names[name] if !ok { - return v + return reflect.Value{} } return FieldByIndexes(v, fi.Index) } diff --git a/internal/reflectx/reflect_test.go b/internal/reflectx/reflect_test.go index 80722443..e769e475 100644 --- a/internal/reflectx/reflect_test.go +++ b/internal/reflectx/reflect_test.go @@ -4,524 +4,400 @@ import ( "reflect" "strings" "testing" + + "github.com/stretchr/testify/assert" ) -func ival(v reflect.Value) int { - return v.Interface().(int) +type E1 struct { + A int +} +type E2 struct { + E1 + B int +} +type E3 struct { + E2 + C int +} +type E4 struct { + E3 + D int } -func TestBasic(t *testing.T) { - type Foo struct { - A int - B int - C int - } +func TestReflectMapper(t *testing.T) { + t.Run("TopLevelField", func(t *testing.T) { + type A struct { + F0 int + F1 int + F2 int + } - f := Foo{1, 2, 3} - fv := reflect.ValueOf(f) - m := NewMapperFunc("", func(s string) string { return s }) + f := A{1, 2, 3} + fv := reflect.ValueOf(f) - v := m.FieldByName(fv, "A") - if ival(v) != f.A { - t.Errorf("Expecting %d, got %d", ival(v), f.A) - } - v = m.FieldByName(fv, "B") - if ival(v) != f.B { - t.Errorf("Expecting %d, got %d", f.B, ival(v)) - } - v = m.FieldByName(fv, "C") - if ival(v) != f.C { - t.Errorf("Expecting %d, got %d", f.C, ival(v)) - } -} + m := NewMapperFunc("", func(s string) string { return s }) -func TestBasicEmbedded(t *testing.T) { - type Foo struct { - A int - } + { + v := m.FieldByName(fv, "F0") + assert.Equal(t, f.F0, v.Interface().(int)) + } - type Bar struct { - Foo // `db:""` is implied for an embedded struct - B int - C int `db:"-"` - } + { + v := m.FieldByName(fv, "F2") + assert.Equal(t, f.F2, v.Interface().(int)) + } + }) - type Baz struct { - A int - Bar `db:"Bar"` - } + t.Run("NestedFields", func(t *testing.T) { + type A struct { + F0 int + F1 int + F2 int + } - m := NewMapperFunc("db", func(s string) string { return s }) + type B struct { + A // nested - z := Baz{} - z.A = 1 - z.B = 2 - z.C = 4 - z.Bar.Foo.A = 3 + F3 int + F4 int + } - zv := reflect.ValueOf(z) - fields := m.TypeMap(reflect.TypeOf(z)) + type C struct { + F5 int - if len(fields.Index) != 5 { - t.Errorf("Expecting 5 fields") - } + B `db:"B"` + } - // for _, fi := range fields.Index { - // log.Println(fi) - // } + c := C{1, B{A{2, 3, 4}, 5, 6}} + cv := reflect.ValueOf(c) - v := m.FieldByName(zv, "A") - if ival(v) != z.A { - t.Errorf("Expecting %d, got %d", z.A, ival(v)) - } - v = m.FieldByName(zv, "Bar.B") - if ival(v) != z.Bar.B { - t.Errorf("Expecting %d, got %d", z.Bar.B, ival(v)) - } - v = m.FieldByName(zv, "Bar.A") - if ival(v) != z.Bar.Foo.A { - t.Errorf("Expecting %d, got %d", z.Bar.Foo.A, ival(v)) - } - v = m.FieldByName(zv, "Bar.C") - if _, ok := v.Interface().(int); ok { - t.Errorf("Expecting Bar.C to not exist") - } + m := NewMapperFunc("db", func(s string) string { return s }) - fi := fields.GetByPath("Bar.C") - if fi != nil { - t.Errorf("Bar.C should not exist") - } -} + assert.Equal(t, 1, m.FieldByName(cv, "F5").Interface().(int)) + assert.Equal(t, 2, m.FieldByName(cv, "B.F0").Interface().(int)) + assert.Equal(t, 3, m.FieldByName(cv, "B.F1").Interface().(int)) + assert.Equal(t, 4, m.FieldByName(cv, "B.F2").Interface().(int)) + assert.Equal(t, 5, m.FieldByName(cv, "B.F3").Interface().(int)) + assert.Equal(t, 6, m.FieldByName(cv, "B.F4").Interface().(int)) -func TestEmbeddedSimple(t *testing.T) { - type UUID [16]byte - type MyID struct { - UUID - } - type Item struct { - ID MyID - } - z := Item{} + assert.False(t, m.FieldByName(cv, "D").IsValid()) - m := NewMapper("db") - m.TypeMap(reflect.TypeOf(z)) -} + t.Run("TypeMap", func(t *testing.T) { + fields := m.TypeMap(reflect.TypeOf(c)) -func TestBasicEmbeddedWithTags(t *testing.T) { - type Foo struct { - A int `db:"a"` - } + assert.Equal(t, 8, len(fields.Index)) + assert.Equal(t, 1, len(fields.GetByPath("F5").Index)) + assert.Equal(t, "F5", fields.GetByPath("F5").Name) + assert.Zero(t, fields.GetByPath("F6")) + }) + }) - type Bar struct { - Foo // `db:""` is implied for an embedded struct - B int `db:"b"` - } + t.Run("NestedFieldsWithTags", func(t *testing.T) { + m := NewMapper("db") - type Baz struct { - A int `db:"a"` - Bar // `db:""` is implied for an embedded struct - } + type Details struct { + Active bool `db:"active"` + } - m := NewMapper("db") + type Asset struct { + Title string `db:"title"` + Details Details `db:"details"` + } - z := Baz{} - z.A = 1 - z.B = 2 - z.Bar.Foo.A = 3 + type Post struct { + Author string `db:"author,required"` + Asset `db:"asset"` + } - zv := reflect.ValueOf(z) - fields := m.TypeMap(reflect.TypeOf(z)) + post := Post{ + Author: "Joe", + Asset: Asset{ + Title: "Hello", + Details: Details{ + Active: true, + }, + }, + } - if len(fields.Index) != 5 { - t.Errorf("Expecting 5 fields") - } + pv := reflect.ValueOf(post) - // for _, fi := range fields.index { - // log.Println(fi) - // } + assert.Equal(t, "Joe", m.FieldByName(pv, "author").Interface().(string)) + assert.Equal(t, "Hello", m.FieldByName(pv, "asset.title").Interface().(string)) + assert.Zero(t, m.FieldByName(pv, "title")) + assert.Equal(t, true, m.FieldByName(pv, "asset.details.active").Interface().(bool)) + }) - v := m.FieldByName(zv, "a") - if ival(v) != z.Bar.Foo.A { // the dominant field - t.Errorf("Expecting %d, got %d", z.Bar.Foo.A, ival(v)) - } - v = m.FieldByName(zv, "b") - if ival(v) != z.B { - t.Errorf("Expecting %d, got %d", z.B, ival(v)) - } -} + t.Run("NestedFieldsWithAmbiguousTags", func(t *testing.T) { + type Foo struct { + A int `db:"a"` + } -func TestFlatTags(t *testing.T) { - m := NewMapper("db") + type Bar struct { + Foo // `db:""` is implied for an embedded struct + B int `db:"b"` + } - type Asset struct { - Title string `db:"title"` - } - type Post struct { - Author string `db:"author,required"` - Asset Asset `db:""` - } - // Post columns: (author title) + type Baz struct { + A int `db:"a"` + Bar // `db:""` is implied for an embedded struct + } - post := Post{Author: "Joe", Asset: Asset{Title: "Hello"}} - pv := reflect.ValueOf(post) + m := NewMapper("db") - v := m.FieldByName(pv, "author") - if v.Interface().(string) != post.Author { - t.Errorf("Expecting %s, got %s", post.Author, v.Interface().(string)) - } - v = m.FieldByName(pv, "title") - if v.Interface().(string) != post.Asset.Title { - t.Errorf("Expecting %s, got %s", post.Asset.Title, v.Interface().(string)) - } -} + z := Baz{A: 1, Bar: Bar{Foo: Foo{A: 3}, B: 2}} -func TestNestedStruct(t *testing.T) { - m := NewMapper("db") + zv := reflect.ValueOf(z) + fields := m.TypeMap(reflect.TypeOf(z)) - type Details struct { - Active bool `db:"active"` - } - type Asset struct { - Title string `db:"title"` - Details Details `db:"details"` - } - type Post struct { - Author string `db:"author,required"` - Asset `db:"asset"` - } - // Post columns: (author asset.title asset.details.active) + assert.Equal(t, 5, len(fields.Index)) - post := Post{ - Author: "Joe", - Asset: Asset{Title: "Hello", Details: Details{Active: true}}, - } - pv := reflect.ValueOf(post) + assert.Equal(t, 3, m.FieldByName(zv, "a").Interface().(int)) + assert.Equal(t, 2, m.FieldByName(zv, "b").Interface().(int)) + }) - v := m.FieldByName(pv, "author") - if v.Interface().(string) != post.Author { - t.Errorf("Expecting %s, got %s", post.Author, v.Interface().(string)) - } - v = m.FieldByName(pv, "title") - if _, ok := v.Interface().(string); ok { - t.Errorf("Expecting field to not exist") - } - v = m.FieldByName(pv, "asset.title") - if v.Interface().(string) != post.Asset.Title { - t.Errorf("Expecting %s, got %s", post.Asset.Title, v.Interface().(string)) - } - v = m.FieldByName(pv, "asset.details.active") - if v.Interface().(bool) != post.Asset.Details.Active { - t.Errorf("Expecting %v, got %v", post.Asset.Details.Active, v.Interface().(bool)) - } -} + t.Run("InlineStructs", func(t *testing.T) { + m := NewMapperTagFunc("db", strings.ToLower, nil) -func TestInlineStruct(t *testing.T) { - m := NewMapperTagFunc("db", strings.ToLower, nil) + type Employee struct { + Name string + ID int + } - type Employee struct { - Name string - ID int - } - type Boss Employee - type person struct { - Employee `db:"employee"` - Boss `db:"boss"` - } - // employees columns: (employee.name employee.id boss.name boss.id) + type Boss Employee - em := person{Employee: Employee{Name: "Joe", ID: 2}, Boss: Boss{Name: "Dick", ID: 1}} - ev := reflect.ValueOf(em) + type person struct { + Employee `db:"employee"` + Boss `db:"boss"` + } - fields := m.TypeMap(reflect.TypeOf(em)) - if len(fields.Index) != 6 { - t.Errorf("Expecting 6 fields") - } + em := person{ + Employee: Employee{ + Name: "Joe", + ID: 2, + }, + Boss: Boss{ + Name: "Rick", + ID: 1, + }, + } + ev := reflect.ValueOf(em) - v := m.FieldByName(ev, "employee.name") - if v.Interface().(string) != em.Employee.Name { - t.Errorf("Expecting %s, got %s", em.Employee.Name, v.Interface().(string)) - } - v = m.FieldByName(ev, "boss.id") - if ival(v) != em.Boss.ID { - t.Errorf("Expecting %v, got %v", em.Boss.ID, ival(v)) - } -} + fields := m.TypeMap(reflect.TypeOf(em)) -func TestFieldsEmbedded(t *testing.T) { - m := NewMapper("db") + assert.Equal(t, 6, len(fields.Index)) - type Person struct { - Name string `db:"name"` - } - type Place struct { - Name string `db:"name"` - } - type Article struct { - Title string `db:"title"` - } - type PP struct { - Person `db:"person,required"` - Place `db:",someflag"` - Article `db:",required"` - } - // PP columns: (person.name name title) + assert.Equal(t, "Joe", m.FieldByName(ev, "employee.name").Interface().(string)) + assert.Equal(t, 2, m.FieldByName(ev, "employee.id").Interface().(int)) + assert.Equal(t, "Rick", m.FieldByName(ev, "boss.name").Interface().(string)) + assert.Equal(t, 1, m.FieldByName(ev, "boss.id").Interface().(int)) + }) - pp := PP{} - pp.Person.Name = "Peter" - pp.Place.Name = "Toronto" - pp.Article.Title = "Best city ever" + t.Run("FieldsWithTags", func(t *testing.T) { + m := NewMapper("db") - fields := m.TypeMap(reflect.TypeOf(pp)) - // for i, f := range fields { - // log.Println(i, f) - // } + type Person struct { + Name string `db:"name"` + } - ppv := reflect.ValueOf(pp) + type Place struct { + Name string `db:"name"` + } - v := m.FieldByName(ppv, "person.name") - if v.Interface().(string) != pp.Person.Name { - t.Errorf("Expecting %s, got %s", pp.Person.Name, v.Interface().(string)) - } + type Article struct { + Title string `db:"title"` + } - v = m.FieldByName(ppv, "name") - if v.Interface().(string) != pp.Place.Name { - t.Errorf("Expecting %s, got %s", pp.Place.Name, v.Interface().(string)) - } + type PP struct { + Person `db:"person,required"` + Place `db:",someflag"` + Article `db:",required"` + } - v = m.FieldByName(ppv, "title") - if v.Interface().(string) != pp.Article.Title { - t.Errorf("Expecting %s, got %s", pp.Article.Title, v.Interface().(string)) - } + pp := PP{ + Person: Person{ + Name: "Peter", + }, + Place: Place{ + Name: "Toronto", + }, + Article: Article{ + Title: "Best city ever", + }, + } - fi := fields.GetByPath("person") - if _, ok := fi.Options["required"]; !ok { - t.Errorf("Expecting required option to be set") - } - if !fi.Embedded { - t.Errorf("Expecting field to be embedded") - } - if len(fi.Index) != 1 || fi.Index[0] != 0 { - t.Errorf("Expecting index to be [0]") - } + ppv := reflect.ValueOf(pp) + fields := m.TypeMap(reflect.TypeOf(pp)) - fi = fields.GetByPath("person.name") - if fi == nil { - t.Errorf("Expecting person.name to exist") - } - if fi.Path != "person.name" { - t.Errorf("Expecting %s, got %s", "person.name", fi.Path) - } + v := m.FieldByName(ppv, "person.name") - fi = fields.GetByTraversal([]int{1, 0}) - if fi == nil { - t.Errorf("Expecting traveral to exist") - } - if fi.Path != "name" { - t.Errorf("Expecting %s, got %s", "name", fi.Path) - } + assert.Equal(t, "Peter", v.Interface().(string)) + assert.Equal(t, "Toronto", m.FieldByName(ppv, "name").Interface().(string)) + assert.Equal(t, "Best city ever", m.FieldByName(ppv, "title").Interface().(string)) - fi = fields.GetByTraversal([]int{2}) - if fi == nil { - t.Errorf("Expecting traversal to exist") - } - if _, ok := fi.Options["required"]; !ok { - t.Errorf("Expecting required option to be set") - } + fi := fields.GetByPath("person") - trs := m.TraversalsByName(reflect.TypeOf(pp), []string{"person.name", "name", "title"}) - if !reflect.DeepEqual(trs, [][]int{{0, 0}, {1, 0}, {2, 0}}) { - t.Errorf("Expecting traversal: %v", trs) - } -} + { + _, ok := fi.Options["required"] + assert.True(t, ok) -func TestPtrFields(t *testing.T) { - m := NewMapperTagFunc("db", strings.ToLower, nil) - type Asset struct { - Title string - } - type Post struct { - *Asset `db:"asset"` - Author string - } + assert.Zero(t, fi.Options["required"]) + } + assert.True(t, fi.Embedded) - post := &Post{Author: "Joe", Asset: &Asset{Title: "Hiyo"}} - pv := reflect.ValueOf(post) + assert.Len(t, fi.Index, 1) + assert.Equal(t, 0, fi.Index[0]) - fields := m.TypeMap(reflect.TypeOf(post)) - if len(fields.Index) != 3 { - t.Errorf("Expecting 3 fields") - } + assert.Equal(t, "person.name", fields.GetByPath("person.name").Path) - v := m.FieldByName(pv, "asset.title") - if v.Interface().(string) != post.Asset.Title { - t.Errorf("Expecting %s, got %s", post.Asset.Title, v.Interface().(string)) - } - v = m.FieldByName(pv, "author") - if v.Interface().(string) != post.Author { - t.Errorf("Expecting %s, got %s", post.Author, v.Interface().(string)) - } -} + assert.Equal(t, "name", fields.GetByTraversal([]int{1, 0}).Path) -func TestNamedPtrFields(t *testing.T) { - m := NewMapperTagFunc("db", strings.ToLower, nil) + fi = fields.GetByTraversal([]int{2}) + assert.NotNil(t, fi) - type User struct { - Name string - } + _, ok := fi.Options["required"] + assert.True(t, ok) - type Asset struct { - Title string + trs := m.TraversalsByName(reflect.TypeOf(pp), []string{"person.name", "name", "title"}) + assert.Equal(t, [][]int{{0, 0}, {1, 0}, {2, 0}}, trs) + }) - Owner *User `db:"owner"` - } - type Post struct { - Author string + t.Run("PointerFields", func(t *testing.T) { + m := NewMapperTagFunc("db", strings.ToLower, nil) - Asset1 *Asset `db:"asset1"` - Asset2 *Asset `db:"asset2"` - } + type Asset struct { + Title string + } - post := &Post{Author: "Joe", Asset1: &Asset{Title: "Hiyo", Owner: &User{"Username"}}} // Let Asset2 be nil - pv := reflect.ValueOf(post) + type Post struct { + *Asset `db:"asset"` + Author string + } - fields := m.TypeMap(reflect.TypeOf(post)) - if len(fields.Index) != 9 { - t.Errorf("Expecting 9 fields") - } + post := &Post{ + Author: "Joe", + Asset: &Asset{ + Title: "Hiyo", + }, + } - v := m.FieldByName(pv, "asset1.title") - if v.Interface().(string) != post.Asset1.Title { - t.Errorf("Expecting %s, got %s", post.Asset1.Title, v.Interface().(string)) - } - v = m.FieldByName(pv, "asset1.owner.name") - if v.Interface().(string) != post.Asset1.Owner.Name { - t.Errorf("Expecting %s, got %s", post.Asset1.Owner.Name, v.Interface().(string)) - } - v = m.FieldByName(pv, "asset2.title") - if v.Interface().(string) != post.Asset2.Title { - t.Errorf("Expecting %s, got %s", post.Asset2.Title, v.Interface().(string)) - } - v = m.FieldByName(pv, "asset2.owner.name") - if v.Interface().(string) != post.Asset2.Owner.Name { - t.Errorf("Expecting %s, got %s", post.Asset2.Owner.Name, v.Interface().(string)) - } - v = m.FieldByName(pv, "author") - if v.Interface().(string) != post.Author { - t.Errorf("Expecting %s, got %s", post.Author, v.Interface().(string)) - } -} + pv := reflect.ValueOf(post) -func TestFieldMap(t *testing.T) { - type Foo struct { - A int - B int - C int - } + fields := m.TypeMap(reflect.TypeOf(post)) + assert.Equal(t, 3, len(fields.Index)) - f := Foo{1, 2, 3} - m := NewMapperFunc("db", strings.ToLower) + assert.Equal(t, "Hiyo", m.FieldByName(pv, "asset.title").Interface().(string)) + assert.Equal(t, "Joe", m.FieldByName(pv, "author").Interface().(string)) + }) - fm := m.FieldMap(reflect.ValueOf(f)) + t.Run("PointerFieldsWithNames", func(t *testing.T) { + m := NewMapperTagFunc("db", strings.ToLower, nil) - if len(fm) != 3 { - t.Errorf("Expecting %d keys, got %d", 3, len(fm)) - } - if fm["a"].Interface().(int) != 1 { - t.Errorf("Expecting %d, got %d", 1, ival(fm["a"])) - } - if fm["b"].Interface().(int) != 2 { - t.Errorf("Expecting %d, got %d", 2, ival(fm["b"])) - } - if fm["c"].Interface().(int) != 3 { - t.Errorf("Expecting %d, got %d", 3, ival(fm["c"])) - } -} + type User struct { + Name string + } -func TestTagNameMapping(t *testing.T) { - type Strategy struct { - StrategyID string `protobuf:"bytes,1,opt,name=strategy_id" json:"strategy_id,omitempty"` - StrategyName string - } + type Asset struct { + Title string - m := NewMapperTagFunc("json", strings.ToUpper, func(value string) string { - if strings.Contains(value, ",") { - return strings.Split(value, ",")[0] + Owner *User `db:"owner"` } - return value - }) - strategy := Strategy{"1", "Alpah"} - mapping := m.TypeMap(reflect.TypeOf(strategy)) - for _, key := range []string{"strategy_id", "STRATEGYNAME"} { - if fi := mapping.GetByPath(key); fi == nil { - t.Errorf("Expecting to find key %s in mapping but did not.", key) + type Post struct { + Author string + + Asset1 *Asset `db:"asset1"` + Asset2 *Asset `db:"asset2"` } - } -} -func TestMapping(t *testing.T) { - type Person struct { - ID int - Name string - WearsGlasses bool `db:"wears_glasses"` - } + post := &Post{ + Author: "Joe", + Asset1: &Asset{ + Title: "Hiyo", + Owner: &User{"Username"}, + }, + } // Asset2 is nil + + pv := reflect.ValueOf(post) - m := NewMapperFunc("db", strings.ToLower) - p := Person{1, "Jason", true} - mapping := m.TypeMap(reflect.TypeOf(p)) + fields := m.TypeMap(reflect.TypeOf(post)) + assert.Equal(t, 9, len(fields.Index)) - for _, key := range []string{"id", "name", "wears_glasses"} { - if fi := mapping.GetByPath(key); fi == nil { - t.Errorf("Expecting to find key %s in mapping but did not.", key) + assert.Equal(t, "Hiyo", m.FieldByName(pv, "asset1.title").Interface().(string)) + assert.Equal(t, "Username", m.FieldByName(pv, "asset1.owner.name").Interface().(string)) + assert.Equal(t, post.Asset2.Title, m.FieldByName(pv, "asset2.title").Interface().(string)) + assert.Equal(t, post.Asset2.Owner.Name, m.FieldByName(pv, "asset2.owner.name").Interface().(string)) + assert.Equal(t, post.Author, m.FieldByName(pv, "author").Interface().(string)) + }) + + t.Run("NameMapping", func(t *testing.T) { + type Strategy struct { + StrategyID string `protobuf:"bytes,1,opt,name=strategy_id" json:"strategy_id,omitempty"` + StrategyName string } - } - type SportsPerson struct { - Weight int - Age int - Person - } - s := SportsPerson{Weight: 100, Age: 30, Person: p} - mapping = m.TypeMap(reflect.TypeOf(s)) - for _, key := range []string{"id", "name", "wears_glasses", "weight", "age"} { - if fi := mapping.GetByPath(key); fi == nil { - t.Errorf("Expecting to find key %s in mapping but did not.", key) + mapperTagFunc := NewMapperTagFunc("json", strings.ToUpper, func(value string) string { + if strings.Contains(value, ",") { + return strings.Split(value, ",")[0] + } + return value + }) + + strategy := Strategy{"1", "Alpha"} + m := mapperTagFunc.TypeMap(reflect.TypeOf(strategy)) + + assert.NotNil(t, m.GetByPath("strategy_id")) // explicitly tagged + assert.NotNil(t, m.GetByPath("STRATEGYNAME")) // mapped by name + assert.Nil(t, m.GetByPath("strategyname")) // not mapped by tag + assert.Nil(t, m.GetByPath("STRATEGYID")) // not mapped by tag + }) + + t.Run("MapperFuncWithTags", func(t *testing.T) { + type Person struct { + ID int + Name string + WearsGlasses bool `db:"wears_glasses"` } - } - type RugbyPlayer struct { - Position int - IsIntense bool `db:"is_intense"` - IsAllBlack bool `db:"-"` - SportsPerson - } - r := RugbyPlayer{12, true, false, s} - mapping = m.TypeMap(reflect.TypeOf(r)) - for _, key := range []string{"id", "name", "wears_glasses", "weight", "age", "position", "is_intense"} { - if fi := mapping.GetByPath(key); fi == nil { - t.Errorf("Expecting to find key %s in mapping but did not.", key) + m := NewMapperFunc("db", strings.ToLower) + p := Person{1, "Jason", true} + mapping := m.TypeMap(reflect.TypeOf(p)) + + assert.NotNil(t, mapping.GetByPath("id")) + assert.NotNil(t, mapping.GetByPath("name")) + assert.NotNil(t, mapping.GetByPath("wears_glasses")) + + type SportsPerson struct { + Weight int + Age int + Person } - } + s := SportsPerson{Weight: 100, Age: 30, Person: p} + mapping = m.TypeMap(reflect.TypeOf(s)) + + assert.NotNil(t, mapping.GetByPath("id")) + assert.NotNil(t, mapping.GetByPath("name")) + assert.NotNil(t, mapping.GetByPath("wears_glasses")) + assert.NotNil(t, mapping.GetByPath("weight")) + assert.NotNil(t, mapping.GetByPath("age")) + + type RugbyPlayer struct { + Position int + IsIntense bool `db:"is_intense"` + IsAllBlack bool `db:"-"` + SportsPerson + } + r := RugbyPlayer{12, true, false, s} + mapping = m.TypeMap(reflect.TypeOf(r)) - if fi := mapping.GetByPath("isallblack"); fi != nil { - t.Errorf("Expecting to ignore `IsAllBlack` field") - } -} + assert.NotNil(t, mapping.GetByPath("id")) + assert.NotNil(t, mapping.GetByPath("name")) + assert.NotNil(t, mapping.GetByPath("wears_glasses")) + assert.NotNil(t, mapping.GetByPath("weight")) + assert.NotNil(t, mapping.GetByPath("age")) + assert.NotNil(t, mapping.GetByPath("position")) -type E1 struct { - A int -} -type E2 struct { - E1 - B int -} -type E3 struct { - E2 - C int -} -type E4 struct { - E3 - D int + assert.Nil(t, mapping.GetByPath("isallblack")) + }) } func BenchmarkFieldNameL1(b *testing.B) { diff --git a/internal/sqladapter/collection.go b/internal/sqladapter/collection.go index f70d0c93..40078162 100644 --- a/internal/sqladapter/collection.go +++ b/internal/sqladapter/collection.go @@ -307,7 +307,7 @@ func (c *collectionWithSession) UpdateReturning(item interface{}) error { conds[pk] = db.Eq(sqlbuilder.Mapper.FieldByName(itemValue, pk).Interface()) } - col := tx.(Session).Collection(c.Name()) + col := tx.Collection(c.Name()) err = col.Find(conds).Update(item) if err != nil { diff --git a/internal/sqladapter/compat/query.go b/internal/sqladapter/compat/query.go index 93cb8fcf..91297fba 100644 --- a/internal/sqladapter/compat/query.go +++ b/internal/sqladapter/compat/query.go @@ -1,3 +1,4 @@ +//go:build !go1.8 // +build !go1.8 package compat diff --git a/internal/sqladapter/compat/query_go18.go b/internal/sqladapter/compat/query_go18.go index a3abbaf8..fbe38905 100644 --- a/internal/sqladapter/compat/query_go18.go +++ b/internal/sqladapter/compat/query_go18.go @@ -1,3 +1,4 @@ +//go:build go1.8 // +build go1.8 package compat diff --git a/internal/sqladapter/exql/column_value.go b/internal/sqladapter/exql/column_value.go index 49296114..7ea7d2ba 100644 --- a/internal/sqladapter/exql/column_value.go +++ b/internal/sqladapter/exql/column_value.go @@ -1,8 +1,9 @@ package exql import ( - "github.com/upper/db/v4/internal/cache" "strings" + + "github.com/upper/db/v4/internal/cache" ) // ColumnValue represents a bundle between a column and a corresponding value. diff --git a/internal/sqladapter/exql/statement.go b/internal/sqladapter/exql/statement.go index 9b9fd480..5fa009bf 100644 --- a/internal/sqladapter/exql/statement.go +++ b/internal/sqladapter/exql/statement.go @@ -10,7 +10,7 @@ import ( var errUnknownTemplateType = errors.New("Unknown template type") -// represents different kinds of SQL statements. +// represents different kinds of SQL statements. type Statement struct { Type Table Fragment diff --git a/internal/sqladapter/hash.go b/internal/sqladapter/hash.go index 4d754914..98fe71d0 100644 --- a/internal/sqladapter/hash.go +++ b/internal/sqladapter/hash.go @@ -1,7 +1,7 @@ package sqladapter const ( - hashTypeNone = iota + 345065139389 + _ = iota + 345065139389 hashTypeCollection hashTypePrimaryKeys diff --git a/internal/sqladapter/session.go b/internal/sqladapter/session.go index 0978205a..a7c9492e 100644 --- a/internal/sqladapter/session.go +++ b/internal/sqladapter/session.go @@ -223,8 +223,6 @@ type session struct { connURL db.ConnectionURL - builder db.SQL - lookupNameOnce sync.Once name string @@ -1031,7 +1029,7 @@ func ReplaceWithDollarSign(buf []byte) []byte { i = i + 1 } - out = append(out, buf[:len(buf)]...) + out = append(out, buf...) buf = nil return out diff --git a/internal/sqladapter/sqladapter_test.go b/internal/sqladapter/sqladapter_test.go index a95275af..a0c797e2 100644 --- a/internal/sqladapter/sqladapter_test.go +++ b/internal/sqladapter/sqladapter_test.go @@ -1,6 +1,7 @@ package sqladapter import ( + "fmt" "testing" "github.com/stretchr/testify/assert" @@ -39,7 +40,9 @@ func TestReplaceWithDollarSign(t *testing.T) { }, } - for _, test := range tests { - assert.Equal(t, []byte(test.out), ReplaceWithDollarSign([]byte(test.in))) + for i, test := range tests { + t.Run(fmt.Sprintf("Case_%03d", i), func(t *testing.T) { + assert.Equal(t, []byte(test.out), ReplaceWithDollarSign([]byte(test.in))) + }) } } diff --git a/internal/sqlbuilder/builder.go b/internal/sqlbuilder/builder.go index c3f494c7..f54d6094 100644 --- a/internal/sqlbuilder/builder.go +++ b/internal/sqlbuilder/builder.go @@ -437,7 +437,7 @@ func prepareQueryForDisplay(in string) string { } } if !whitespace { - out = append(out, in[offset:len(in)]...) + out = append(out, in[offset:]...) } return string(out) } diff --git a/internal/sqlbuilder/builder_test.go b/internal/sqlbuilder/builder_test.go index 4842b389..c72449d6 100644 --- a/internal/sqlbuilder/builder_test.go +++ b/internal/sqlbuilder/builder_test.go @@ -7,15 +7,202 @@ import ( "testing" "github.com/stretchr/testify/assert" - db "github.com/upper/db/v4" + "github.com/upper/db/v4" + "github.com/upper/db/v4/internal/cache" + "github.com/upper/db/v4/internal/sqladapter/exql" ) var ( reInvisibleChars = regexp.MustCompile(`[\s\r\n\t]+`) ) -func TestSelect(t *testing.T) { +const ( + defaultColumnSeparator = `.` + defaultIdentifierSeparator = `, ` + defaultIdentifierQuote = `"{{.Value}}"` + defaultValueSeparator = `, ` + defaultValueQuote = `'{{.}}'` + defaultAndKeyword = `AND` + defaultOrKeyword = `OR` + defaultDescKeyword = `DESC` + defaultAscKeyword = `ASC` + defaultAssignmentOperator = `=` + defaultClauseGroup = `({{.}})` + defaultClauseOperator = ` {{.}} ` + defaultColumnValue = `{{.Column}} {{.Operator}} {{.Value}}` + defaultTableAliasLayout = `{{.Name}}{{if .Alias}} AS {{.Alias}}{{end}}` + defaultColumnAliasLayout = `{{.Name}}{{if .Alias}} AS {{.Alias}}{{end}}` + defaultSortByColumnLayout = `{{.Column}} {{.Order}}` + + defaultOrderByLayout = ` + {{if .SortColumns}} + ORDER BY {{.SortColumns}} + {{end}} + ` + + defaultWhereLayout = ` + {{if .Conds}} + WHERE {{.Conds}} + {{end}} + ` + + defaultUsingLayout = ` + {{if .Columns}} + USING ({{.Columns}}) + {{end}} + ` + + defaultJoinLayout = ` + {{if .Table}} + {{ if .On }} + {{.Type}} JOIN {{.Table}} + {{.On}} + {{ else if .Using }} + {{.Type}} JOIN {{.Table}} + {{.Using}} + {{ else if .Type | eq "CROSS" }} + {{.Type}} JOIN {{.Table}} + {{else}} + NATURAL {{.Type}} JOIN {{.Table}} + {{end}} + {{end}} + ` + + defaultOnLayout = ` + {{if .Conds}} + ON {{.Conds}} + {{end}} + ` + + defaultSelectLayout = ` + SELECT + {{if .Distinct}} + DISTINCT + {{end}} + + {{if defined .Columns}} + {{.Columns | compile}} + {{else}} + * + {{end}} + + {{if defined .Table}} + FROM {{.Table | compile}} + {{end}} + + {{.Joins | compile}} + + {{.Where | compile}} + + {{if defined .GroupBy}} + {{.GroupBy | compile}} + {{end}} + + {{.OrderBy | compile}} + + {{if .Limit}} + LIMIT {{.Limit}} + {{end}} + + {{if .Offset}} + OFFSET {{.Offset}} + {{end}} + ` + defaultDeleteLayout = ` + DELETE + FROM {{.Table | compile}} + {{.Where | compile}} + ` + defaultUpdateLayout = ` + UPDATE + {{.Table | compile}} + SET {{.ColumnValues | compile}} + {{.Where | compile}} + ` + + defaultCountLayout = ` + SELECT + COUNT(1) AS _t + FROM {{.Table | compile}} + {{.Where | compile}} + + {{if .Limit}} + LIMIT {{.Limit}} + {{end}} + + {{if .Offset}} + OFFSET {{.Offset}} + {{end}} + ` + + defaultInsertLayout = ` + INSERT INTO {{.Table | compile}} + {{if defined .Columns }}({{.Columns | compile}}){{end}} + VALUES + {{if defined .Values}} + {{.Values | compile}} + {{else}} + (default) + {{end}} + {{if defined .Returning}} + RETURNING {{.Returning | compile}} + {{end}} + ` + + defaultTruncateLayout = ` + TRUNCATE TABLE {{.Table | compile}} + ` + + defaultDropDatabaseLayout = ` + DROP DATABASE {{.Database | compile}} + ` + + defaultDropTableLayout = ` + DROP TABLE {{.Table | compile}} + ` + + defaultGroupByLayout = ` + {{if .GroupColumns}} + GROUP BY {{.GroupColumns}} + {{end}} + ` +) +var testTemplate = exql.Template{ + ColumnSeparator: defaultColumnSeparator, + IdentifierSeparator: defaultIdentifierSeparator, + IdentifierQuote: defaultIdentifierQuote, + ValueSeparator: defaultValueSeparator, + ValueQuote: defaultValueQuote, + AndKeyword: defaultAndKeyword, + OrKeyword: defaultOrKeyword, + DescKeyword: defaultDescKeyword, + AscKeyword: defaultAscKeyword, + AssignmentOperator: defaultAssignmentOperator, + ClauseGroup: defaultClauseGroup, + ClauseOperator: defaultClauseOperator, + ColumnValue: defaultColumnValue, + TableAliasLayout: defaultTableAliasLayout, + ColumnAliasLayout: defaultColumnAliasLayout, + SortByColumnLayout: defaultSortByColumnLayout, + WhereLayout: defaultWhereLayout, + OnLayout: defaultOnLayout, + UsingLayout: defaultUsingLayout, + JoinLayout: defaultJoinLayout, + OrderByLayout: defaultOrderByLayout, + InsertLayout: defaultInsertLayout, + SelectLayout: defaultSelectLayout, + UpdateLayout: defaultUpdateLayout, + DeleteLayout: defaultDeleteLayout, + TruncateLayout: defaultTruncateLayout, + DropDatabaseLayout: defaultDropDatabaseLayout, + DropTableLayout: defaultDropTableLayout, + CountLayout: defaultCountLayout, + GroupByLayout: defaultGroupByLayout, + Cache: cache.NewCache(), +} + +func TestSelect(t *testing.T) { b := &sqlBuilder{t: newTemplateWithUtils(&testTemplate)} assert := assert.New(t) diff --git a/internal/sqlbuilder/convert.go b/internal/sqlbuilder/convert.go index 21e161fb..c1cf218c 100644 --- a/internal/sqlbuilder/convert.go +++ b/internal/sqlbuilder/convert.go @@ -44,10 +44,10 @@ func expandQuery(in []byte, inArgs []interface{}) ([]byte, []interface{}) { return in, inArgs } - out = append(out, in[:len(in)]...) + out = append(out, in[:]...) in = nil - outArgs = append(outArgs, inArgs[:len(inArgs)]...) + outArgs = append(outArgs, inArgs[:]...) inArgs = nil return out, outArgs diff --git a/internal/sqlbuilder/fetch.go b/internal/sqlbuilder/fetch.go index fe35dd89..b7fedae5 100644 --- a/internal/sqlbuilder/fetch.go +++ b/internal/sqlbuilder/fetch.go @@ -26,6 +26,7 @@ import ( "database/sql" "database/sql/driver" + db "github.com/upper/db/v4" "github.com/upper/db/v4/internal/reflectx" ) diff --git a/internal/sqlbuilder/placeholder_test.go b/internal/sqlbuilder/placeholder_test.go index 93317f5b..00aec9c0 100644 --- a/internal/sqlbuilder/placeholder_test.go +++ b/internal/sqlbuilder/placeholder_test.go @@ -1,6 +1,7 @@ package sqlbuilder import ( + "fmt" "testing" "github.com/stretchr/testify/assert" @@ -65,8 +66,10 @@ func TestPrepareForDisplay(t *testing.T) { Out: "$1$2$3", }, } - for _, sample := range samples { - assert.Equal(t, sample.Out, prepareQueryForDisplay(sample.In)) + for i, sample := range samples { + t.Run(fmt.Sprintf("sample %d", i), func(t *testing.T) { + assert.Equal(t, sample.Out, prepareQueryForDisplay(sample.In)) + }) } } diff --git a/internal/sqlbuilder/template_test.go b/internal/sqlbuilder/template_test.go deleted file mode 100644 index 8d617e09..00000000 --- a/internal/sqlbuilder/template_test.go +++ /dev/null @@ -1,192 +0,0 @@ -package sqlbuilder - -import ( - "github.com/upper/db/v4/internal/cache" - "github.com/upper/db/v4/internal/sqladapter/exql" -) - -const ( - defaultColumnSeparator = `.` - defaultIdentifierSeparator = `, ` - defaultIdentifierQuote = `"{{.Value}}"` - defaultValueSeparator = `, ` - defaultValueQuote = `'{{.}}'` - defaultAndKeyword = `AND` - defaultOrKeyword = `OR` - defaultDescKeyword = `DESC` - defaultAscKeyword = `ASC` - defaultAssignmentOperator = `=` - defaultClauseGroup = `({{.}})` - defaultClauseOperator = ` {{.}} ` - defaultColumnValue = `{{.Column}} {{.Operator}} {{.Value}}` - defaultTableAliasLayout = `{{.Name}}{{if .Alias}} AS {{.Alias}}{{end}}` - defaultColumnAliasLayout = `{{.Name}}{{if .Alias}} AS {{.Alias}}{{end}}` - defaultSortByColumnLayout = `{{.Column}} {{.Order}}` - - defaultOrderByLayout = ` - {{if .SortColumns}} - ORDER BY {{.SortColumns}} - {{end}} - ` - - defaultWhereLayout = ` - {{if .Conds}} - WHERE {{.Conds}} - {{end}} - ` - - defaultUsingLayout = ` - {{if .Columns}} - USING ({{.Columns}}) - {{end}} - ` - - defaultJoinLayout = ` - {{if .Table}} - {{ if .On }} - {{.Type}} JOIN {{.Table}} - {{.On}} - {{ else if .Using }} - {{.Type}} JOIN {{.Table}} - {{.Using}} - {{ else if .Type | eq "CROSS" }} - {{.Type}} JOIN {{.Table}} - {{else}} - NATURAL {{.Type}} JOIN {{.Table}} - {{end}} - {{end}} - ` - - defaultOnLayout = ` - {{if .Conds}} - ON {{.Conds}} - {{end}} - ` - - defaultSelectLayout = ` - SELECT - {{if .Distinct}} - DISTINCT - {{end}} - - {{if defined .Columns}} - {{.Columns | compile}} - {{else}} - * - {{end}} - - {{if defined .Table}} - FROM {{.Table | compile}} - {{end}} - - {{.Joins | compile}} - - {{.Where | compile}} - - {{if defined .GroupBy}} - {{.GroupBy | compile}} - {{end}} - - {{.OrderBy | compile}} - - {{if .Limit}} - LIMIT {{.Limit}} - {{end}} - - {{if .Offset}} - OFFSET {{.Offset}} - {{end}} - ` - defaultDeleteLayout = ` - DELETE - FROM {{.Table | compile}} - {{.Where | compile}} - ` - defaultUpdateLayout = ` - UPDATE - {{.Table | compile}} - SET {{.ColumnValues | compile}} - {{.Where | compile}} - ` - - defaultCountLayout = ` - SELECT - COUNT(1) AS _t - FROM {{.Table | compile}} - {{.Where | compile}} - - {{if .Limit}} - LIMIT {{.Limit}} - {{end}} - - {{if .Offset}} - OFFSET {{.Offset}} - {{end}} - ` - - defaultInsertLayout = ` - INSERT INTO {{.Table | compile}} - {{if defined .Columns }}({{.Columns | compile}}){{end}} - VALUES - {{if defined .Values}} - {{.Values | compile}} - {{else}} - (default) - {{end}} - {{if defined .Returning}} - RETURNING {{.Returning | compile}} - {{end}} - ` - - defaultTruncateLayout = ` - TRUNCATE TABLE {{.Table | compile}} - ` - - defaultDropDatabaseLayout = ` - DROP DATABASE {{.Database | compile}} - ` - - defaultDropTableLayout = ` - DROP TABLE {{.Table | compile}} - ` - - defaultGroupByLayout = ` - {{if .GroupColumns}} - GROUP BY {{.GroupColumns}} - {{end}} - ` -) - -var testTemplate = exql.Template{ - ColumnSeparator: defaultColumnSeparator, - IdentifierSeparator: defaultIdentifierSeparator, - IdentifierQuote: defaultIdentifierQuote, - ValueSeparator: defaultValueSeparator, - ValueQuote: defaultValueQuote, - AndKeyword: defaultAndKeyword, - OrKeyword: defaultOrKeyword, - DescKeyword: defaultDescKeyword, - AscKeyword: defaultAscKeyword, - AssignmentOperator: defaultAssignmentOperator, - ClauseGroup: defaultClauseGroup, - ClauseOperator: defaultClauseOperator, - ColumnValue: defaultColumnValue, - TableAliasLayout: defaultTableAliasLayout, - ColumnAliasLayout: defaultColumnAliasLayout, - SortByColumnLayout: defaultSortByColumnLayout, - WhereLayout: defaultWhereLayout, - OnLayout: defaultOnLayout, - UsingLayout: defaultUsingLayout, - JoinLayout: defaultJoinLayout, - OrderByLayout: defaultOrderByLayout, - InsertLayout: defaultInsertLayout, - SelectLayout: defaultSelectLayout, - UpdateLayout: defaultUpdateLayout, - DeleteLayout: defaultDeleteLayout, - TruncateLayout: defaultTruncateLayout, - DropDatabaseLayout: defaultDropDatabaseLayout, - DropTableLayout: defaultDropTableLayout, - CountLayout: defaultCountLayout, - GroupByLayout: defaultGroupByLayout, - Cache: cache.NewCache(), -} diff --git a/internal/sqlbuilder/wrapper.go b/internal/sqlbuilder/wrapper.go index a16c3984..acb6359c 100644 --- a/internal/sqlbuilder/wrapper.go +++ b/internal/sqlbuilder/wrapper.go @@ -77,7 +77,7 @@ func (d *dbAdapter) Open(conn db.ConnectionURL) (db.Session, error) { if err != nil { return nil, err } - return sess.(db.Session), nil + return sess, nil } func NewCompatAdapter(adapter Adapter) db.Adapter { diff --git a/internal/testsuite/sql_suite.go b/internal/testsuite/sql_suite.go index 6e68c87f..844e0dfa 100644 --- a/internal/testsuite/sql_suite.go +++ b/internal/testsuite/sql_suite.go @@ -1886,13 +1886,16 @@ func (s *SQLTestSuite) TestCustomType() { } func (s *SQLTestSuite) Test_Issue565() { - s.Session().Collection("birthdays").Insert(&birthday{ + _, err := s.Session().Collection("birthdays").Insert(&birthday{ Name: "Lucy", Born: time.Now(), }) + s.NoError(err) + + ctxKeyCarry := db.ContextKey("carry") - parentCtx := context.WithValue(s.Session().Context(), "carry", 1) - s.NotZero(parentCtx.Value("carry")) + parentCtx := context.WithValue(s.Session().Context(), ctxKeyCarry, 1) + s.NotZero(parentCtx.Value(ctxKeyCarry)) { ctx, cancel := context.WithTimeout(parentCtx, time.Nanosecond) @@ -1908,7 +1911,7 @@ func (s *SQLTestSuite) Test_Issue565() { s.Error(err) s.Zero(result.Name) - s.NotZero(ctx.Value("carry")) + s.NotZero(ctx.Value(ctxKeyCarry)) } { @@ -1923,7 +1926,7 @@ func (s *SQLTestSuite) Test_Issue565() { s.Error(err) s.Zero(result.Name) - s.NotZero(ctx.Value("carry")) + s.NotZero(ctx.Value(ctxKeyCarry)) } { @@ -1938,7 +1941,7 @@ func (s *SQLTestSuite) Test_Issue565() { s.NoError(err) s.NotZero(result.Name) - s.NotZero(ctx.Value("carry")) + s.NotZero(ctx.Value(ctxKeyCarry)) } } diff --git a/logger_test.go b/logger_test.go deleted file mode 100644 index e9b71fda..00000000 --- a/logger_test.go +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) 2012-present The upper.io/db authors. All rights reserved. -// -// 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 db - -import ( - "errors" - "testing" -) - -func TestLogger(t *testing.T) { - err := errors.New("fake error") - LC().Error(err) -} diff --git a/sql.go b/sql.go index a4bc18b9..5aaa2bd4 100644 --- a/sql.go +++ b/sql.go @@ -33,12 +33,11 @@ import ( // pointer, if you want to build a query using variables you need to reassign // them, like this: // -// a = builder.Select("name").From("foo") // "a" is created +// a = builder.Select("name").From("foo") // "a" is created // -// a.Where(...) // No effect, the value returned from Where is ignored. -// -// a = a.Where(...) // "a" is reassigned and points to a different address. +// a.Where(...) // No effect, the value returned from Where is ignored. // +// a = a.Where(...) // "a" is reassigned and points to a different address. type SQL interface { // Select initializes and returns a Selector, it accepts column names as