From 1bb9dd807906824b25431ba0f8a28aad1f09fcf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Nieto?= Date: Sun, 23 Jun 2024 13:14:53 -0600 Subject: [PATCH] use testify in all tests --- Makefile | 4 +- adapter/cockroachdb/Makefile | 10 +- adapter/cockroachdb/custom_types.go | 15 +- adapter/cockroachdb/database_pgx.go | 5 +- adapter/cockroachdb/docker-compose.yml | 3 - adapter/mongo/Makefile | 6 +- adapter/mongo/connection_test.go | 80 +-- adapter/mssql/Makefile | 6 +- adapter/mssql/connection_test.go | 50 +- adapter/mysql/Makefile | 8 +- adapter/mysql/connection_test.go | 93 +-- adapter/mysql/docker-compose.yml | 3 - adapter/postgresql/Makefile | 12 +- adapter/postgresql/custom_types.go | 3 +- adapter/postgresql/database_pgx.go | 3 +- adapter/postgresql/database_pq.go | 3 +- adapter/postgresql/docker-compose.yml | 3 - adapter/postgresql/postgresql_test.go | 2 - adapter/sqlite/connection_test.go | 59 +- comparison_test.go | 7 +- cond_test.go | 80 +-- context.go | 3 + db.go | 68 +-- errors_test.go | 35 -- function_test.go | 38 +- internal/cache/cache.go | 12 +- internal/cache/cache_test.go | 75 +-- internal/reflectx/reflect.go | 3 +- internal/reflectx/reflect_test.go | 744 ++++++++++-------------- internal/sqladapter/collection.go | 2 +- internal/sqladapter/hash.go | 2 +- internal/sqladapter/session.go | 4 +- internal/sqladapter/sqladapter_test.go | 7 +- internal/sqlbuilder/builder.go | 2 +- internal/sqlbuilder/builder_test.go | 191 +++++- internal/sqlbuilder/convert.go | 4 +- internal/sqlbuilder/placeholder_test.go | 7 +- internal/sqlbuilder/template_test.go | 192 ------ internal/sqlbuilder/wrapper.go | 2 +- internal/testsuite/sql_suite.go | 15 +- logger_test.go | 32 - 41 files changed, 785 insertions(+), 1108 deletions(-) create mode 100644 context.go delete mode 100644 errors_test.go delete mode 100644 internal/sqlbuilder/template_test.go delete mode 100644 logger_test.go diff --git a/Makefile b/Makefile index 4b6d7473..eea84293 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,9 @@ -SHELL ?= /bin/bash +SHELL ?= bash CPU_CORES ?= $(shell nproc) 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 bf05b363..a6ba1323 100644 --- a/adapter/cockroachdb/Makefile +++ b/adapter/cockroachdb/Makefile @@ -1,8 +1,8 @@ SHELL ?= bash -COCKROACHDB_VERSION ?= v23.1.11 -COCKROACHDB_SUPPORTED ?= $(COCKROACHDB_VERSION) v22.2.15 v21.2.16 -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/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/database_pgx.go b/adapter/cockroachdb/database_pgx.go index a4aa0d75..ccf5faad 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,7 +27,9 @@ package cockroachdb import ( "context" "database/sql" + _ "github.com/jackc/pgx/v4/stdlib" + "github.com/upper/db/v4" "github.com/upper/db/v4/internal/sqladapter" "time" ) @@ -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("pgx", 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/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 b7ed6bbd..162ffdd4 100644 --- a/adapter/postgresql/Makefile +++ b/adapter/postgresql/Makefile @@ -2,8 +2,7 @@ SHELL ?= bash POSTGRES_VERSION ?= 16-alpine POSTGRES_SUPPORTED ?= $(POSTGRES_VERSION) 15-alpine 14-alpine - -PROJECT ?= upper_postgres_$(POSTGRES_VERSION) +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/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/database_pgx.go b/adapter/postgresql/database_pgx.go index 954a9382..9cc40c30 100644 --- a/adapter/postgresql/database_pgx.go +++ b/adapter/postgresql/database_pgx.go @@ -28,6 +28,7 @@ import ( "context" "database/sql" _ "github.com/jackc/pgx/v4/stdlib" + "github.com/upper/db/v4" "github.com/upper/db/v4/internal/sqladapter" "time" ) @@ -39,7 +40,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..559315c5 100644 --- a/adapter/postgresql/database_pq.go +++ b/adapter/postgresql/database_pq.go @@ -1,3 +1,4 @@ +//go:build pq // +build pq package postgresql @@ -38,7 +39,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/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_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/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/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/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) -}