Create comprehensive documentation for future Claude Code instances working in this repository, including: - Development commands for testing, building, and code quality - Core architecture overview of the SQL query builder system - Directory structure and component explanations - Testing patterns and conventions - Key dependencies and their purposes 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
497 lines
14 KiB
Go
497 lines
14 KiB
Go
package db_test
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
dbv2 "git.fsdpf.net/go/db"
|
|
"git.fsdpf.net/go/db/exp"
|
|
"github.com/DATA-DOG/go-sqlmock"
|
|
"github.com/stretchr/testify/suite"
|
|
)
|
|
|
|
type githubIssuesSuite struct {
|
|
suite.Suite
|
|
}
|
|
|
|
func (gis *githubIssuesSuite) AfterTest(_, _ string) {
|
|
dbv2.SetColumnRenameFunction(strings.ToLower)
|
|
}
|
|
|
|
// Test for https://github.com/doug-martin/dbv2/issues/49
|
|
func (gis *githubIssuesSuite) TestIssue49() {
|
|
dialect := dbv2.Dialect("default")
|
|
|
|
filters := dbv2.Or()
|
|
sql, args, err := dialect.From("table").Where(filters).ToSQL()
|
|
gis.NoError(err)
|
|
gis.Empty(args)
|
|
gis.Equal(`SELECT * FROM "table"`, sql)
|
|
|
|
sql, args, err = dialect.From("table").Where(dbv2.Ex{}).ToSQL()
|
|
gis.NoError(err)
|
|
gis.Empty(args)
|
|
gis.Equal(`SELECT * FROM "table"`, sql)
|
|
|
|
sql, args, err = dialect.From("table").Where(dbv2.ExOr{}).ToSQL()
|
|
gis.NoError(err)
|
|
gis.Empty(args)
|
|
gis.Equal(`SELECT * FROM "table"`, sql)
|
|
}
|
|
|
|
// Test for https://github.com/doug-martin/dbv2/issues/115
|
|
func (gis *githubIssuesSuite) TestIssue115() {
|
|
type TestStruct struct {
|
|
Field string
|
|
}
|
|
dbv2.SetColumnRenameFunction(func(col string) string {
|
|
return ""
|
|
})
|
|
|
|
_, _, err := dbv2.Insert("test").Rows(TestStruct{Field: "hello"}).ToSQL()
|
|
gis.EqualError(err, `db: a empty identifier was encountered, please specify a "schema", "table" or "column"`)
|
|
}
|
|
|
|
// Test for https://github.com/doug-martin/dbv2/issues/118
|
|
func (gis *githubIssuesSuite) TestIssue118_withEmbeddedStructWithoutExportedFields() {
|
|
// struct is in a custom package
|
|
type SimpleRole struct {
|
|
permissions []string //nolint:structcheck,unused //needed for test
|
|
}
|
|
|
|
// .....
|
|
|
|
type Role struct {
|
|
*SimpleRole
|
|
|
|
ID string `json:"id" db:"id" ff:"skipinsert"`
|
|
Key string `json:"key" db:"key"`
|
|
Name string `json:"name" db:"name"`
|
|
CreatedAt time.Time `json:"-" db:"created_at" ff:"skipinsert"`
|
|
}
|
|
|
|
rUser := &Role{
|
|
Key: `user`,
|
|
Name: `User role`,
|
|
}
|
|
|
|
sql, arg, err := dbv2.Insert(`rbac_roles`).
|
|
Returning(dbv2.C(`id`)).
|
|
Rows(rUser).
|
|
ToSQL()
|
|
gis.NoError(err)
|
|
gis.Empty(arg)
|
|
gis.Equal(`INSERT INTO "rbac_roles" ("key", "name") VALUES ('user', 'User role') RETURNING "id"`, sql)
|
|
|
|
sql, arg, err = dbv2.Update(`rbac_roles`).
|
|
Returning(dbv2.C(`id`)).
|
|
Set(rUser).
|
|
ToSQL()
|
|
gis.NoError(err)
|
|
gis.Empty(arg)
|
|
gis.Equal(
|
|
`UPDATE "rbac_roles" SET "created_at"='0001-01-01T00:00:00Z',"id"='',"key"='user',"name"='User role' RETURNING "id"`,
|
|
sql,
|
|
)
|
|
|
|
rUser = &Role{
|
|
SimpleRole: &SimpleRole{},
|
|
Key: `user`,
|
|
Name: `User role`,
|
|
}
|
|
|
|
sql, arg, err = dbv2.Insert(`rbac_roles`).
|
|
Returning(dbv2.C(`id`)).
|
|
Rows(rUser).
|
|
ToSQL()
|
|
gis.NoError(err)
|
|
gis.Empty(arg)
|
|
gis.Equal(`INSERT INTO "rbac_roles" ("key", "name") VALUES ('user', 'User role') RETURNING "id"`, sql)
|
|
|
|
sql, arg, err = dbv2.Update(`rbac_roles`).
|
|
Returning(dbv2.C(`id`)).
|
|
Set(rUser).
|
|
ToSQL()
|
|
gis.NoError(err)
|
|
gis.Empty(arg)
|
|
gis.Equal(
|
|
`UPDATE "rbac_roles" SET `+
|
|
`"created_at"='0001-01-01T00:00:00Z',"id"='',"key"='user',"name"='User role' RETURNING "id"`,
|
|
sql,
|
|
)
|
|
}
|
|
|
|
// Test for https://github.com/doug-martin/dbv2/issues/118
|
|
func (gis *githubIssuesSuite) TestIssue118_withNilEmbeddedStructWithExportedFields() {
|
|
// struct is in a custom package
|
|
type SimpleRole struct {
|
|
permissions []string //nolint:structcheck,unused // needed for test
|
|
IDStr string
|
|
}
|
|
|
|
// .....
|
|
|
|
type Role struct {
|
|
*SimpleRole
|
|
|
|
ID string `json:"id" db:"id" ff:"skipinsert"`
|
|
Key string `json:"key" db:"key"`
|
|
Name string `json:"name" db:"name"`
|
|
CreatedAt time.Time `json:"-" db:"created_at" ff:"skipinsert"`
|
|
}
|
|
|
|
rUser := &Role{
|
|
Key: `user`,
|
|
Name: `User role`,
|
|
}
|
|
sql, arg, err := dbv2.Insert(`rbac_roles`).
|
|
Returning(dbv2.C(`id`)).
|
|
Rows(rUser).
|
|
ToSQL()
|
|
gis.NoError(err)
|
|
gis.Empty(arg)
|
|
// it should not insert fields on nil embedded pointers
|
|
gis.Equal(`INSERT INTO "rbac_roles" ("key", "name") VALUES ('user', 'User role') RETURNING "id"`, sql)
|
|
|
|
sql, arg, err = dbv2.Update(`rbac_roles`).
|
|
Returning(dbv2.C(`id`)).
|
|
Set(rUser).
|
|
ToSQL()
|
|
gis.NoError(err)
|
|
gis.Empty(arg)
|
|
// it should not insert fields on nil embedded pointers
|
|
gis.Equal(
|
|
`UPDATE "rbac_roles" SET "created_at"='0001-01-01T00:00:00Z',"id"='',"key"='user',"name"='User role' RETURNING "id"`,
|
|
sql,
|
|
)
|
|
|
|
rUser = &Role{
|
|
SimpleRole: &SimpleRole{},
|
|
Key: `user`,
|
|
Name: `User role`,
|
|
}
|
|
sql, arg, err = dbv2.Insert(`rbac_roles`).
|
|
Returning(dbv2.C(`id`)).
|
|
Rows(rUser).
|
|
ToSQL()
|
|
gis.NoError(err)
|
|
gis.Empty(arg)
|
|
// it should not insert fields on nil embedded pointers
|
|
gis.Equal(
|
|
`INSERT INTO "rbac_roles" ("idstr", "key", "name") VALUES ('', 'user', 'User role') RETURNING "id"`,
|
|
sql,
|
|
)
|
|
|
|
sql, arg, err = dbv2.Update(`rbac_roles`).
|
|
Returning(dbv2.C(`id`)).
|
|
Set(rUser).
|
|
ToSQL()
|
|
gis.NoError(err)
|
|
gis.Empty(arg)
|
|
// it should not insert fields on nil embedded pointers
|
|
gis.Equal(
|
|
`UPDATE "rbac_roles" SET `+
|
|
`"created_at"='0001-01-01T00:00:00Z',"id"='',"idstr"='',"key"='user',"name"='User role' RETURNING "id"`,
|
|
sql,
|
|
)
|
|
}
|
|
|
|
// Test for https://github.com/doug-martin/dbv2/issues/118
|
|
func (gis *githubIssuesSuite) TestIssue140() {
|
|
sql, arg, err := dbv2.Insert(`test`).Returning().ToSQL()
|
|
gis.NoError(err)
|
|
gis.Empty(arg)
|
|
gis.Equal(`INSERT INTO "test" DEFAULT VALUES`, sql)
|
|
|
|
sql, arg, err = dbv2.Update(`test`).Set(dbv2.Record{"a": "b"}).Returning().ToSQL()
|
|
gis.NoError(err)
|
|
gis.Empty(arg)
|
|
gis.Equal(
|
|
`UPDATE "test" SET "a"='b'`,
|
|
sql,
|
|
)
|
|
|
|
sql, arg, err = dbv2.Delete(`test`).Returning().ToSQL()
|
|
gis.NoError(err)
|
|
gis.Empty(arg)
|
|
gis.Equal(
|
|
`DELETE FROM "test"`,
|
|
sql,
|
|
)
|
|
|
|
sql, arg, err = dbv2.Insert(`test`).Returning(nil).ToSQL()
|
|
gis.NoError(err)
|
|
gis.Empty(arg)
|
|
gis.Equal(`INSERT INTO "test" DEFAULT VALUES`, sql)
|
|
|
|
sql, arg, err = dbv2.Update(`test`).Set(dbv2.Record{"a": "b"}).Returning(nil).ToSQL()
|
|
gis.NoError(err)
|
|
gis.Empty(arg)
|
|
gis.Equal(
|
|
`UPDATE "test" SET "a"='b'`,
|
|
sql,
|
|
)
|
|
|
|
sql, arg, err = dbv2.Delete(`test`).Returning(nil).ToSQL()
|
|
gis.NoError(err)
|
|
gis.Empty(arg)
|
|
gis.Equal(
|
|
`DELETE FROM "test"`,
|
|
sql,
|
|
)
|
|
}
|
|
|
|
// Test for https://github.com/doug-martin/dbv2/issues/164
|
|
func (gis *githubIssuesSuite) TestIssue164() {
|
|
insertDs := dbv2.Insert("foo").Rows(dbv2.Record{"user_id": 10}).Returning("id")
|
|
|
|
ds := dbv2.From("bar").
|
|
With("ins", insertDs).
|
|
Select("bar_name").
|
|
Where(dbv2.Ex{"bar.user_id": dbv2.I("ins.user_id")})
|
|
|
|
sql, args, err := ds.ToSQL()
|
|
gis.NoError(err)
|
|
gis.Empty(args)
|
|
gis.Equal(
|
|
`WITH ins AS (INSERT INTO "foo" ("user_id") VALUES (10) RETURNING "id") `+
|
|
`SELECT "bar_name" FROM "bar" WHERE ("bar"."user_id" = "ins"."user_id")`,
|
|
sql,
|
|
)
|
|
|
|
sql, args, err = ds.Prepared(true).ToSQL()
|
|
gis.NoError(err)
|
|
gis.Equal([]interface{}{int64(10)}, args)
|
|
gis.Equal(
|
|
`WITH ins AS (INSERT INTO "foo" ("user_id") VALUES (?) RETURNING "id")`+
|
|
` SELECT "bar_name" FROM "bar" WHERE ("bar"."user_id" = "ins"."user_id")`,
|
|
sql,
|
|
)
|
|
|
|
updateDs := dbv2.Update("foo").Set(dbv2.Record{"bar": "baz"}).Returning("id")
|
|
|
|
ds = dbv2.From("bar").
|
|
With("upd", updateDs).
|
|
Select("bar_name").
|
|
Where(dbv2.Ex{"bar.user_id": dbv2.I("upd.user_id")})
|
|
|
|
sql, args, err = ds.ToSQL()
|
|
gis.NoError(err)
|
|
gis.Empty(args)
|
|
gis.Equal(
|
|
`WITH upd AS (UPDATE "foo" SET "bar"='baz' RETURNING "id") SELECT "bar_name" FROM "bar" WHERE ("bar"."user_id" = "upd"."user_id")`,
|
|
sql,
|
|
)
|
|
|
|
sql, args, err = ds.Prepared(true).ToSQL()
|
|
gis.NoError(err)
|
|
gis.Equal([]interface{}{"baz"}, args)
|
|
gis.Equal(
|
|
`WITH upd AS (UPDATE "foo" SET "bar"=? RETURNING "id") SELECT "bar_name" FROM "bar" WHERE ("bar"."user_id" = "upd"."user_id")`,
|
|
sql,
|
|
)
|
|
|
|
deleteDs := dbv2.Delete("foo").Where(dbv2.Ex{"bar": "baz"}).Returning("id")
|
|
|
|
ds = dbv2.From("bar").
|
|
With("del", deleteDs).
|
|
Select("bar_name").
|
|
Where(dbv2.Ex{"bar.user_id": dbv2.I("del.user_id")})
|
|
|
|
sql, args, err = ds.ToSQL()
|
|
gis.NoError(err)
|
|
gis.Empty(args)
|
|
gis.Equal(
|
|
`WITH del AS (DELETE FROM "foo" WHERE ("bar" = 'baz') RETURNING "id")`+
|
|
` SELECT "bar_name" FROM "bar" WHERE ("bar"."user_id" = "del"."user_id")`,
|
|
sql,
|
|
)
|
|
|
|
sql, args, err = ds.Prepared(true).ToSQL()
|
|
gis.NoError(err)
|
|
gis.Equal([]interface{}{"baz"}, args)
|
|
gis.Equal(
|
|
`WITH del AS (DELETE FROM "foo" WHERE ("bar" = ?) RETURNING "id")`+
|
|
` SELECT "bar_name" FROM "bar" WHERE ("bar"."user_id" = "del"."user_id")`,
|
|
sql,
|
|
)
|
|
}
|
|
|
|
// Test for https://github.com/doug-martin/dbv2/issues/177
|
|
func (gis *githubIssuesSuite) TestIssue177() {
|
|
ds := dbv2.Dialect("postgres").
|
|
From("ins1").
|
|
With("ins1",
|
|
dbv2.Dialect("postgres").
|
|
Insert("account").
|
|
Rows(dbv2.Record{"email": "email@email.com", "status": "active", "uuid": "XXX-XXX-XXXX"}).
|
|
Returning("*"),
|
|
).
|
|
With("ins2",
|
|
dbv2.Dialect("postgres").
|
|
Insert("account_user").
|
|
Cols("account_id", "user_id").
|
|
FromQuery(dbv2.Dialect("postgres").
|
|
From("ins1").
|
|
Select(
|
|
"id",
|
|
dbv2.V(1001),
|
|
),
|
|
),
|
|
).
|
|
Select("*")
|
|
sql, args, err := ds.ToSQL()
|
|
gis.NoError(err)
|
|
gis.Equal(`WITH ins1 AS (`+
|
|
`INSERT INTO "account" ("email", "status", "uuid") VALUES ('email@email.com', 'active', 'XXX-XXX-XXXX') RETURNING *),`+
|
|
` ins2 AS (INSERT INTO "account_user" ("account_id", "user_id") SELECT "id", 1001 FROM "ins1")`+
|
|
` SELECT * FROM "ins1"`, sql)
|
|
gis.Len(args, 0)
|
|
|
|
sql, args, err = ds.Prepared(true).ToSQL()
|
|
gis.NoError(err)
|
|
gis.Equal(`WITH ins1 AS (INSERT INTO "account" ("email", "status", "uuid") VALUES ($1, $2, $3) RETURNING *), ins2`+
|
|
` AS (INSERT INTO "account_user" ("account_id", "user_id") SELECT "id", $4 FROM "ins1") SELECT * FROM "ins1"`, sql)
|
|
gis.Equal(args, []interface{}{"email@email.com", "active", "XXX-XXX-XXXX", int64(1001)})
|
|
}
|
|
|
|
// Test for https://github.com/doug-martin/dbv2/issues/183
|
|
func (gis *githubIssuesSuite) TestIssue184() {
|
|
expectedErr := fmt.Errorf("an error")
|
|
testCases := []struct {
|
|
ds exp.AppendableExpression
|
|
}{
|
|
{ds: dbv2.From("test").As("t").SetError(expectedErr)},
|
|
{ds: dbv2.Insert("test").Rows(dbv2.Record{"foo": "bar"}).Returning("foo").SetError(expectedErr)},
|
|
{ds: dbv2.Update("test").Set(dbv2.Record{"foo": "bar"}).Returning("foo").SetError(expectedErr)},
|
|
{ds: dbv2.Update("test").Set(dbv2.Record{"foo": "bar"}).Returning("foo").SetError(expectedErr)},
|
|
{ds: dbv2.Delete("test").Returning("foo").SetError(expectedErr)},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
ds := dbv2.From(tc.ds)
|
|
sql, args, err := ds.ToSQL()
|
|
gis.Equal(expectedErr, err)
|
|
gis.Empty(sql)
|
|
gis.Empty(args)
|
|
|
|
sql, args, err = ds.Prepared(true).ToSQL()
|
|
gis.Equal(expectedErr, err)
|
|
gis.Empty(sql)
|
|
gis.Empty(args)
|
|
|
|
ds = dbv2.From("test2").Where(dbv2.Ex{"foo": tc.ds})
|
|
|
|
sql, args, err = ds.ToSQL()
|
|
gis.Equal(expectedErr, err)
|
|
gis.Empty(sql)
|
|
gis.Empty(args)
|
|
|
|
sql, args, err = ds.Prepared(true).ToSQL()
|
|
gis.Equal(expectedErr, err)
|
|
gis.Empty(sql)
|
|
gis.Empty(args)
|
|
}
|
|
}
|
|
|
|
// Test for https://github.com/doug-martin/dbv2/issues/185
|
|
func (gis *githubIssuesSuite) TestIssue185() {
|
|
mDB, sqlMock, err := sqlmock.New()
|
|
gis.NoError(err)
|
|
sqlMock.ExpectQuery(
|
|
`SELECT \* FROM \(SELECT "id" FROM "table" ORDER BY "id" ASC\) AS "t1" UNION
|
|
\(SELECT \* FROM \(SELECT "id" FROM "table" ORDER BY "id" ASC\) AS "t1"\)`,
|
|
).
|
|
WillReturnRows(sqlmock.NewRows([]string{"id"}).FromCSVString("1\n2\n3\n4\n"))
|
|
db := dbv2.New("mock", mDB)
|
|
|
|
ds := db.Select("id").From("table").Order(dbv2.C("id").Asc()).
|
|
Union(
|
|
db.Select("id").From("table").Order(dbv2.C("id").Asc()),
|
|
)
|
|
|
|
ctx := context.Background()
|
|
var i []int
|
|
gis.NoError(ds.ScanValsContext(ctx, &i))
|
|
gis.Equal([]int{1, 2, 3, 4}, i)
|
|
}
|
|
|
|
// Test for https://github.com/doug-martin/dbv2/issues/203
|
|
func (gis *githubIssuesSuite) TestIssue203() {
|
|
// Schema definitions.
|
|
authSchema := dbv2.S("company_auth")
|
|
|
|
// Table definitions
|
|
usersTable := authSchema.Table("users")
|
|
|
|
u := usersTable.As("u")
|
|
|
|
ds := dbv2.From(u).Select(
|
|
u.Col("id"),
|
|
u.Col("name"),
|
|
u.Col("created_at"),
|
|
u.Col("updated_at"),
|
|
)
|
|
|
|
sql, args, err := ds.ToSQL()
|
|
gis.NoError(err)
|
|
gis.Equal(`SELECT "u"."id", "u"."name", "u"."created_at", "u"."updated_at" FROM "company_auth"."users" AS "u"`, sql)
|
|
gis.Empty(args, []interface{}{})
|
|
|
|
sql, args, err = ds.Prepared(true).ToSQL()
|
|
gis.NoError(err)
|
|
gis.Equal(`SELECT "u"."id", "u"."name", "u"."created_at", "u"."updated_at" FROM "company_auth"."users" AS "u"`, sql)
|
|
gis.Empty(args, []interface{}{})
|
|
}
|
|
|
|
func (gis *githubIssuesSuite) TestIssue290() {
|
|
type OcomModel struct {
|
|
ID uint `json:"id" db:"id" ff:"skipinsert"`
|
|
CreatedDate time.Time `json:"created_date" db:"created_date" ff:"skipupdate"`
|
|
ModifiedDate time.Time `json:"modified_date" db:"modified_date"`
|
|
}
|
|
|
|
type ActiveModel struct {
|
|
OcomModel
|
|
ActiveStartDate time.Time `json:"active_start_date" db:"active_start_date"`
|
|
ActiveEndDate *time.Time `json:"active_end_date" db:"active_end_date"`
|
|
}
|
|
|
|
type CodeModel struct {
|
|
ActiveModel
|
|
|
|
Code string `json:"code" db:"code"`
|
|
Description string `json:"description" binding:"required" db:"description"`
|
|
}
|
|
|
|
type CodeExample struct {
|
|
CodeModel
|
|
}
|
|
|
|
var item CodeExample
|
|
item.Code = "Code"
|
|
item.Description = "Description"
|
|
item.ID = 1 // Value set HERE!
|
|
item.CreatedDate = time.Date(
|
|
2021, 1, 1, 1, 1, 1, 1, time.UTC)
|
|
item.ModifiedDate = time.Date(
|
|
2021, 2, 2, 2, 2, 2, 2, time.UTC) // The Value we Get!
|
|
item.ActiveStartDate = time.Date(
|
|
2021, 3, 3, 3, 3, 3, 3, time.UTC)
|
|
|
|
updateQuery := dbv2.From("example").Update().Set(item).Where(dbv2.C("id").Eq(1))
|
|
|
|
sql, params, err := updateQuery.ToSQL()
|
|
|
|
gis.NoError(err)
|
|
gis.Empty(params)
|
|
gis.Equal(`UPDATE "example" SET "active_end_date"=NULL,"active_start_date"='2021-03-03T03:03:03.000000003Z',"code"='Code',"description"='Description',"id"=1,"modified_date"='2021-02-02T02:02:02.000000002Z' WHERE ("id" = 1)`, sql) //nolint:lll
|
|
}
|
|
|
|
func TestGithubIssuesSuite(t *testing.T) {
|
|
suite.Run(t, new(githubIssuesSuite))
|
|
}
|