aboutsummaryrefslogtreecommitdiff
path: root/internal/plan_test.go
diff options
context:
space:
mode:
authorStefan Majewsky <majewsky@gmx.net>2026-04-11 20:19:12 +0200
committerStefan Majewsky <majewsky@gmx.net>2026-04-11 20:20:03 +0200
commite9d31443f01eda2ecee66dbc25f154a6949a9c97 (patch)
tree1824c7dc3290e4d38ab111522938e8a33e2f9618 /internal/plan_test.go
parent3d28ce0650fc85ca054a608bce32f88f2d90295f (diff)
downloadgo-oblast-e9d31443f01eda2ecee66dbc25f154a6949a9c97.tar.gz
reorganize the API from type DB to type Store
Diffstat (limited to 'internal/plan_test.go')
-rw-r--r--internal/plan_test.go176
1 files changed, 79 insertions, 97 deletions
diff --git a/internal/plan_test.go b/internal/plan_test.go
index c504ace..88afedc 100644
--- a/internal/plan_test.go
+++ b/internal/plan_test.go
@@ -8,79 +8,52 @@ import (
"testing"
"time"
- "go.xyrillian.de/oblast/info"
"go.xyrillian.de/oblast/internal"
"go.xyrillian.de/oblast/internal/assert"
)
func TestPlanFieldTraversal(t *testing.T) {
+ type Timestamps struct {
+ CreatedAt time.Time `db:"created_at"`
+ UpdatedAt *time.Time `db:"updated_at"`
+ }
+ type yetMoreTimestamps struct {
+ DeletedAt *time.Time `db:"deleted_at"`
+ }
type Log struct {
- info.TableNameIs `db:"log_entries"`
- info.PrimaryKeyIs `db:"id"`
- ID int64 `db:"id,auto"`
- CreatedAt time.Time `db:"created_at"`
- Message string
- private1 bool `db:"private1"` //nolint:unused
- Ignored any `db:"-"`
+ ID int64 `db:"id,auto"`
+ Message string
+ private1 bool `db:"private1"` //nolint:unused
+ Ignored any `db:"-"`
+ Timestamps
+ yetMoreTimestamps
}
- // assert on interface implementations
- var (
- _ info.IsTable = Log{}
- _ info.IsTableWithPrimaryKey = Log{}
- )
-
// check that the plan for Log:
// 1. has no IndexByColumnName entries for marker types
// 2. uses the field name as a column name for "Message"
// 3. ignores "private1" because it cannot be written through reflection
// 4. ignores "Ignored" because its column name is "-"
- // 5. recognizes "id" as an autofilled column
- plan, err := internal.BuildPlan(reflect.TypeFor[Log](), internal.PostgresDialect{})
+ // 5. traverses into "Timestamps" and includes its fields as well
+ // 6. traverses into "yetMoreTimestamps" as well (despite the extra pointer and the type being private)
+ // 7. recognizes "id" as an autofilled column
+ plan, err := internal.BuildPlan(reflect.TypeFor[Log](), internal.PostgresDialect{}, internal.PlanOpts{
+ TableName: "log_entries",
+ PrimaryKeyColumnNames: []string{"id"},
+ })
if err != nil {
t.Error(err)
}
assert.Equal(t, plan.TableName, "log_entries")
- assert.DeepEqual(t, plan.AllColumnNames, []string{"id", "created_at", "Message"})
+ assert.DeepEqual(t, plan.AllColumnNames, []string{"id", "Message", "created_at", "updated_at", "deleted_at"})
assert.DeepEqual(t, plan.PrimaryKeyColumnNames, []string{"id"})
assert.DeepEqual(t, plan.AutoColumnNames, []string{"id"})
assert.DeepEqual(t, plan.IndexByColumnName, map[string][]int{
- "id": {2},
- "created_at": {3},
- "Message": {4},
- })
-
- type extraTimestampFields struct {
- UpdatedAt *time.Time `db:"updated_at"`
- DeletedAt *time.Time `db:"deleted_at"`
- }
-
- type record struct {
- Log
- *extraTimestampFields
- Foo bool `db:"foo"`
- }
-
- // check that the plan for record:
- // 1. works at all, even though it as a whole is an unexported type
- // 2. traverses into Log and includes all of its fields as well
- // 3. traverses into *extraTimestampFields (despite the extra pointer and the type being private), too
- // 3. completely ignores the marker types in type Log
- plan, err = internal.BuildPlan(reflect.TypeFor[record](), internal.PostgresDialect{})
- if err != nil {
- t.Error(err)
- }
- assert.Equal(t, plan.TableName, "")
- assert.DeepEqual(t, plan.AllColumnNames, []string{"id", "created_at", "Message", "updated_at", "deleted_at", "foo"})
- assert.DeepEqual(t, plan.PrimaryKeyColumnNames, nil)
- assert.DeepEqual(t, plan.AutoColumnNames, []string{"id"}) // this is okay, it does not bear significance in practice since no queries are generated
- assert.DeepEqual(t, plan.IndexByColumnName, map[string][]int{
- "id": {0, 2},
- "created_at": {0, 3},
- "Message": {0, 4},
- "updated_at": {1, 0},
- "deleted_at": {1, 1},
- "foo": {2},
+ "id": {0},
+ "Message": {1},
+ "created_at": {4, 0},
+ "updated_at": {4, 1},
+ "deleted_at": {5, 0},
})
}
@@ -88,54 +61,58 @@ func TestPlanFieldTraversal(t *testing.T) {
func TestQueryConstructionBasic(t *testing.T) {
type record struct {
- info.TableNameIs `db:"basic_records"`
- info.PrimaryKeyIs `db:"ID"`
- ID int64 `db:",auto"`
- Description string
- CreatedAt time.Time
+ ID int64 `db:",auto"`
+ Description string
+ CreatedAt time.Time
+ }
+ opts := internal.PlanOpts{
+ TableName: "basic_records",
+ PrimaryKeyColumnNames: []string{"ID"},
}
t.Run("PostgresDialect", func(t *testing.T) {
- plan, err := internal.BuildPlan(reflect.TypeFor[record](), internal.PostgresDialect{})
+ plan, err := internal.BuildPlan(reflect.TypeFor[record](), internal.PostgresDialect{}, opts)
if err != nil {
t.Error(err)
}
assert.Equal(t, plan.Insert.Query, `INSERT INTO "basic_records" ("Description", "CreatedAt") VALUES ($1, $2) RETURNING "ID"`)
- assert.DeepEqual(t, plan.Insert.ArgumentIndexes, [][]int{{3}, {4}})
+ assert.DeepEqual(t, plan.Insert.ArgumentIndexes, [][]int{{1}, {2}})
assert.Equal(t, plan.Update.Query, `UPDATE "basic_records" SET "Description" = $1, "CreatedAt" = $2 WHERE "ID" = $3`)
- assert.DeepEqual(t, plan.Update.ArgumentIndexes, [][]int{{3}, {4}, {2}})
+ assert.DeepEqual(t, plan.Update.ArgumentIndexes, [][]int{{1}, {2}, {0}})
assert.Equal(t, plan.Delete.Query, `DELETE FROM "basic_records" WHERE "ID" = $1`)
- assert.DeepEqual(t, plan.Delete.ArgumentIndexes, [][]int{{2}})
+ assert.DeepEqual(t, plan.Delete.ArgumentIndexes, [][]int{{0}})
})
t.Run("SqliteDialect", func(t *testing.T) {
- plan, err := internal.BuildPlan(reflect.TypeFor[record](), internal.SqliteDialect{})
+ plan, err := internal.BuildPlan(reflect.TypeFor[record](), internal.SqliteDialect{}, opts)
if err != nil {
t.Error(err)
}
assert.Equal(t, plan.Insert.Query, `INSERT INTO "basic_records" ("Description", "CreatedAt") VALUES (?, ?)`)
- assert.DeepEqual(t, plan.Insert.ArgumentIndexes, [][]int{{3}, {4}})
+ assert.DeepEqual(t, plan.Insert.ArgumentIndexes, [][]int{{1}, {2}})
assert.Equal(t, plan.Update.Query, `UPDATE "basic_records" SET "Description" = ?, "CreatedAt" = ? WHERE "ID" = ?`)
- assert.DeepEqual(t, plan.Update.ArgumentIndexes, [][]int{{3}, {4}, {2}})
+ assert.DeepEqual(t, plan.Update.ArgumentIndexes, [][]int{{1}, {2}, {0}})
assert.Equal(t, plan.Delete.Query, `DELETE FROM "basic_records" WHERE "ID" = ?`)
- assert.DeepEqual(t, plan.Delete.ArgumentIndexes, [][]int{{2}})
+ assert.DeepEqual(t, plan.Delete.ArgumentIndexes, [][]int{{0}})
})
}
func TestQueryConstructionWithoutPrimaryKey(t *testing.T) {
type relation struct {
- info.TableNameIs `db:"foo_bar_relations"`
- FooID int64 `db:"foo_id"`
- BarID int64 `db:"bar_id"`
+ FooID int64 `db:"foo_id"`
+ BarID int64 `db:"bar_id"`
+ }
+ opts := internal.PlanOpts{
+ TableName: "foo_bar_relations",
}
t.Run("PostgresDialect", func(t *testing.T) {
- plan, err := internal.BuildPlan(reflect.TypeFor[relation](), internal.PostgresDialect{})
+ plan, err := internal.BuildPlan(reflect.TypeFor[relation](), internal.PostgresDialect{}, opts)
if err != nil {
t.Error(err)
}
assert.Equal(t, plan.Insert.Query, `INSERT INTO "foo_bar_relations" ("foo_id", "bar_id") VALUES ($1, $2)`)
- assert.DeepEqual(t, plan.Insert.ArgumentIndexes, [][]int{{1}, {2}})
+ assert.DeepEqual(t, plan.Insert.ArgumentIndexes, [][]int{{0}, {1}})
assert.Equal(t, plan.Update.Query, "")
assert.DeepEqual(t, plan.Update.ArgumentIndexes, nil)
assert.Equal(t, plan.Delete.Query, "")
@@ -143,12 +120,12 @@ func TestQueryConstructionWithoutPrimaryKey(t *testing.T) {
})
t.Run("SqliteDialect", func(t *testing.T) {
- plan, err := internal.BuildPlan(reflect.TypeFor[relation](), internal.SqliteDialect{})
+ plan, err := internal.BuildPlan(reflect.TypeFor[relation](), internal.SqliteDialect{}, opts)
if err != nil {
t.Error(err)
}
assert.Equal(t, plan.Insert.Query, `INSERT INTO "foo_bar_relations" ("foo_id", "bar_id") VALUES (?, ?)`)
- assert.DeepEqual(t, plan.Insert.ArgumentIndexes, [][]int{{1}, {2}})
+ assert.DeepEqual(t, plan.Insert.ArgumentIndexes, [][]int{{0}, {1}})
assert.Equal(t, plan.Update.Query, "")
assert.DeepEqual(t, plan.Update.ArgumentIndexes, nil)
assert.Equal(t, plan.Delete.Query, "")
@@ -161,10 +138,11 @@ func TestQueryConstructionImpossble(t *testing.T) {
Foo int
Bar string
}
+ opts := internal.PlanOpts{}
testWith := func(dialect internal.Dialect) func(*testing.T) {
return func(t *testing.T) {
- plan, err := internal.BuildPlan(reflect.TypeFor[unstructuredData](), dialect)
+ plan, err := internal.BuildPlan(reflect.TypeFor[unstructuredData](), dialect, opts)
if err != nil {
t.Error(err)
}
@@ -184,64 +162,68 @@ func TestQueryConstructionImpossble(t *testing.T) {
func TestQueryConstructionWithMultiplePrimaryKeyColumns(t *testing.T) {
type record struct {
- info.TableNameIs `db:"complex_records"`
- info.PrimaryKeyIs `db:"group_id,name"`
- GroupID int64 `db:"group_id"`
- Name string `db:"name"`
- CreatedAt time.Time `db:"created_at"`
+ GroupID int64 `db:"group_id"`
+ Name string `db:"name"`
+ CreatedAt time.Time `db:"created_at"`
+ }
+ opts := internal.PlanOpts{
+ TableName: "complex_records",
+ PrimaryKeyColumnNames: []string{"group_id", "name"},
}
t.Run("PostgresDialect", func(t *testing.T) {
- plan, err := internal.BuildPlan(reflect.TypeFor[record](), internal.PostgresDialect{})
+ plan, err := internal.BuildPlan(reflect.TypeFor[record](), internal.PostgresDialect{}, opts)
if err != nil {
t.Error(err)
}
assert.Equal(t, plan.Insert.Query, `INSERT INTO "complex_records" ("group_id", "name", "created_at") VALUES ($1, $2, $3)`)
- assert.DeepEqual(t, plan.Insert.ArgumentIndexes, [][]int{{2}, {3}, {4}})
+ assert.DeepEqual(t, plan.Insert.ArgumentIndexes, [][]int{{0}, {1}, {2}})
assert.Equal(t, plan.Update.Query, `UPDATE "complex_records" SET "created_at" = $1 WHERE "group_id" = $2 AND "name" = $3`)
- assert.DeepEqual(t, plan.Update.ArgumentIndexes, [][]int{{4}, {2}, {3}})
+ assert.DeepEqual(t, plan.Update.ArgumentIndexes, [][]int{{2}, {0}, {1}})
assert.Equal(t, plan.Delete.Query, `DELETE FROM "complex_records" WHERE "group_id" = $1 AND "name" = $2`)
- assert.DeepEqual(t, plan.Delete.ArgumentIndexes, [][]int{{2}, {3}})
+ assert.DeepEqual(t, plan.Delete.ArgumentIndexes, [][]int{{0}, {1}})
})
t.Run("SqliteDialect", func(t *testing.T) {
- plan, err := internal.BuildPlan(reflect.TypeFor[record](), internal.SqliteDialect{})
+ plan, err := internal.BuildPlan(reflect.TypeFor[record](), internal.SqliteDialect{}, opts)
if err != nil {
t.Error(err)
}
assert.Equal(t, plan.Insert.Query, `INSERT INTO "complex_records" ("group_id", "name", "created_at") VALUES (?, ?, ?)`)
- assert.DeepEqual(t, plan.Insert.ArgumentIndexes, [][]int{{2}, {3}, {4}})
+ assert.DeepEqual(t, plan.Insert.ArgumentIndexes, [][]int{{0}, {1}, {2}})
assert.Equal(t, plan.Update.Query, `UPDATE "complex_records" SET "created_at" = ? WHERE "group_id" = ? AND "name" = ?`)
- assert.DeepEqual(t, plan.Update.ArgumentIndexes, [][]int{{4}, {2}, {3}})
+ assert.DeepEqual(t, plan.Update.ArgumentIndexes, [][]int{{2}, {0}, {1}})
assert.Equal(t, plan.Delete.Query, `DELETE FROM "complex_records" WHERE "group_id" = ? AND "name" = ?`)
- assert.DeepEqual(t, plan.Delete.ArgumentIndexes, [][]int{{2}, {3}})
+ assert.DeepEqual(t, plan.Delete.ArgumentIndexes, [][]int{{0}, {1}})
})
}
func TestQueryConstructionWithMultipleAutoColumns(t *testing.T) {
type record struct {
- info.TableNameIs `db:"autogenerated_records"`
- info.PrimaryKeyIs `db:"id"`
- ID int64 `db:"id,auto"`
- Name string `db:"name"`
- CreatedAt time.Time `db:"created_at,auto"`
+ ID int64 `db:"id,auto"`
+ Name string `db:"name"`
+ CreatedAt time.Time `db:"created_at,auto"`
+ }
+ opts := internal.PlanOpts{
+ TableName: "autogenerated_records",
+ PrimaryKeyColumnNames: []string{"id"},
}
t.Run("PostgresDialect", func(t *testing.T) {
- plan, err := internal.BuildPlan(reflect.TypeFor[record](), internal.PostgresDialect{})
+ plan, err := internal.BuildPlan(reflect.TypeFor[record](), internal.PostgresDialect{}, opts)
if err != nil {
t.Error(err)
}
assert.Equal(t, plan.Insert.Query, `INSERT INTO "autogenerated_records" ("name") VALUES ($1) RETURNING "id", "created_at"`)
- assert.DeepEqual(t, plan.Insert.ArgumentIndexes, [][]int{{3}})
+ assert.DeepEqual(t, plan.Insert.ArgumentIndexes, [][]int{{1}})
assert.Equal(t, plan.Update.Query, `UPDATE "autogenerated_records" SET "name" = $1, "created_at" = $2 WHERE "id" = $3`)
- assert.DeepEqual(t, plan.Update.ArgumentIndexes, [][]int{{3}, {4}, {2}})
+ assert.DeepEqual(t, plan.Update.ArgumentIndexes, [][]int{{1}, {2}, {0}})
assert.Equal(t, plan.Delete.Query, `DELETE FROM "autogenerated_records" WHERE "id" = $1`)
- assert.DeepEqual(t, plan.Delete.ArgumentIndexes, [][]int{{2}})
+ assert.DeepEqual(t, plan.Delete.ArgumentIndexes, [][]int{{0}})
})
t.Run("SqliteDialect", func(t *testing.T) {
- _, err := internal.BuildPlan(reflect.TypeFor[record](), internal.SqliteDialect{})
+ _, err := internal.BuildPlan(reflect.TypeFor[record](), internal.SqliteDialect{}, opts)
assert.Equal(t, err.Error(), `cannot use type go.xyrillian.de/oblast/internal_test.record for queries: multiple columns are marked as auto-filled (id, created_at), but this SQL dialect only supports at most one per table`)
})
}