summaryrefslogtreecommitdiff
path: root/plan_test.go
diff options
context:
space:
mode:
authorStefan Majewsky <majewsky@gmx.net>2026-09-17 16:51:17 +0200
committerStefan Majewsky <majewsky@gmx.net>2026-09-17 16:51:17 +0200
commit1825c3040f5ee71ad26185a7a08f2657ede94df5 (patch)
treebf65a15764fae5a947ef1a53d9d6c54a8ccdae11 /plan_test.go
parent1b8935ec8cc8ebe9ad74ecd12141e8ad115cebfb (diff)
downloadgo-oblast-1825c3040f5ee71ad26185a7a08f2657ede94df5.tar.gz
rebase onto gg@v1.16.0/oblast
Diffstat (limited to 'plan_test.go')
-rw-r--r--plan_test.go657
1 files changed, 0 insertions, 657 deletions
diff --git a/plan_test.go b/plan_test.go
deleted file mode 100644
index 45666d7..0000000
--- a/plan_test.go
+++ /dev/null
@@ -1,657 +0,0 @@
-// SPDX-FileCopyrightText: 2026 Stefan Majewsky <majewsky@gmx.net>
-// SPDX-License-Identifier: Apache-2.0
-
-package oblast
-
-// ^ NOTE: This is testing internal types and thus must reside in the same package.
-
-import (
- "reflect"
- "testing"
- "time"
-
- "go.xyrillian.de/gg/assert"
-)
-
-// Clears all fields of `p` that are not used when actually running queries,
-// i.e. intermediate fields holding analysis results that went into query planning.
-// This is used to shorten the assertions in TestQueryConstruction...() below.
-func onlyQueryPlans(p plan) plan {
- p.AllColumnNames = nil
- p.AutoColumnNames = nil
- p.IndexByColumnName = nil
- p.PrimaryKeyColumnNames = nil
- p.TableName = ""
- p.TypeName = ""
- return p
-}
-
-// Basically the opposite of onlyQueryPlans.
-// TestPlanFieldTraversal() only cares about the analysis part, not the query construction phase.
-func onlyAnalysisResult(p plan) plan {
- p.Select = plannedQuery{}
- p.Insert = plannedQuery{}
- p.Upsert = plannedQuery{}
- p.Update = plannedQuery{}
- p.Delete = plannedQuery{}
- return p
-}
-
-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 {
- ID int64 `db:"id,auto"`
- Message string
- private1 bool `db:"private1"` //nolint:unused
- Ignored any `db:"-"`
- Timestamps
- *yetMoreTimestamps
- MoreText struct {
- Description string
- }
- YetMoreText struct {
- Payload string
- } `db:"-"`
- OpaqueText struct {
- ShortMessage string
- LongMessage string
- } `db:"OpaqueText"`
- }
-
- // check that the plan for Log:
- // 1. uses the field name as a column name for "Message"
- // 2. ignores "private1" because it cannot be written through reflection
- // 3. ignores "Ignored" because its column name is "-"
- // 4. traverses into "Timestamps" and includes its fields as well
- // 5. traverses into "yetMoreTimestamps" as well (despite the extra pointer and the type being private)
- // 6. traverses into "MoreText" and includes its fields as well
- // 7. does not traverse into "YetMoreText" and does not include its fields because of `db:"-"`
- // 8. does not traverse into "OpaqueText" because the struct is mapped as a whole
- // 9. recognizes "id" as an autofilled column
- p, err := buildPlan(reflect.TypeFor[Log](), PostgresDialect(), planOpts{
- StructTagKey: "db",
- TableName: "log_entries",
- PrimaryKeyColumnNames: []string{"id"},
- })
- if err != nil {
- t.Error(err)
- }
- assert.Equal(t, onlyAnalysisResult(p), plan{
- TypeName: "Log",
- TableName: "log_entries",
- AllColumnNames: []string{"id", "Message", "created_at", "updated_at", "deleted_at", "Description", "OpaqueText"},
- PrimaryKeyColumnNames: []string{"id"},
- AutoColumnNames: []string{"id"},
- IndexByColumnName: map[string][]int{
- "id": {0},
- "Message": {1},
- "created_at": {4, 0},
- "updated_at": {4, 1},
- "deleted_at": {5, 0},
- "Description": {6, 0},
- "OpaqueText": {8},
- },
- InsertUsesQueryRow: true,
- TransparentPointerStructFields: []fieldInfo{{
- Name: "yetMoreTimestamps",
- Index: []int{5},
- }},
- })
-}
-
-func TestQueryConstructionBasic(t *testing.T) {
- type record struct {
- ID int64 `db:",auto"`
- Description string
- CreatedAt time.Time `db:"CreatedAt"`
- }
- opts := planOpts{
- StructTagKey: "db",
- TableName: "basic_records",
- PrimaryKeyColumnNames: []string{"ID"},
- }
-
- t.Run("MariaDBDialect", func(t *testing.T) {
- opts.ReadOnly = false
- p, err := buildPlan(reflect.TypeFor[record](), MariaDBDialect(), opts)
- if err != nil {
- t.Error(err)
- }
- assert.Equal(t, onlyQueryPlans(p), plan{
- Select: plannedQuery{
- Query: "SELECT `ID`, `Description`, `CreatedAt` FROM `basic_records` WHERE ",
- ScanIndexes: [][]int{{0}, {1}, {2}},
- },
- Insert: plannedQuery{
- Query: "INSERT INTO `basic_records` (`Description`, `CreatedAt`) VALUES (?, ?)",
- ArgumentIndexes: [][]int{{1}, {2}},
- ScanIndexes: [][]int{{0}},
- },
- Update: plannedQuery{
- Query: "UPDATE `basic_records` SET `Description` = ?, `CreatedAt` = ? WHERE `ID` = ?",
- ArgumentIndexes: [][]int{{1}, {2}, {0}},
- },
- Delete: plannedQuery{
- Query: "DELETE FROM `basic_records` WHERE `ID` = ?",
- ArgumentIndexes: [][]int{{0}},
- },
- })
-
- opts.ReadOnly = true
- p2, err := buildPlan(reflect.TypeFor[record](), MariaDBDialect(), opts)
- if err != nil {
- t.Error(err)
- }
- assert.Equal(t, onlyQueryPlans(p2), plan{
- ReadOnly: true,
- Select: p.Select,
- })
- })
-
- t.Run("PostgresDialect", func(t *testing.T) {
- opts.ReadOnly = false
- p, err := buildPlan(reflect.TypeFor[record](), PostgresDialect(), opts)
- if err != nil {
- t.Error(err)
- }
- assert.Equal(t, onlyQueryPlans(p), plan{
- InsertUsesQueryRow: true,
- Select: plannedQuery{
- Query: `SELECT "ID", "Description", "CreatedAt" FROM "basic_records" WHERE `,
- ScanIndexes: [][]int{{0}, {1}, {2}},
- },
- Insert: plannedQuery{
- Query: `INSERT INTO "basic_records" ("Description", "CreatedAt") VALUES ($1, $2) RETURNING "ID"`,
- ArgumentIndexes: [][]int{{1}, {2}},
- ScanIndexes: [][]int{{0}},
- },
- Update: plannedQuery{
- Query: `UPDATE "basic_records" SET "Description" = $1, "CreatedAt" = $2 WHERE "ID" = $3`,
- ArgumentIndexes: [][]int{{1}, {2}, {0}},
- },
- Delete: plannedQuery{
- Query: `DELETE FROM "basic_records" WHERE "ID" = $1`,
- ArgumentIndexes: [][]int{{0}},
- },
- })
-
- opts.ReadOnly = true
- p2, err := buildPlan(reflect.TypeFor[record](), PostgresDialect(), opts)
- if err != nil {
- t.Error(err)
- }
- assert.Equal(t, onlyQueryPlans(p2), plan{
- InsertUsesQueryRow: true,
- ReadOnly: true,
- Select: p.Select,
- })
- })
-
- t.Run("SqliteDialect", func(t *testing.T) {
- opts.ReadOnly = false
- p, err := buildPlan(reflect.TypeFor[record](), SqliteDialect(), opts)
- if err != nil {
- t.Error(err)
- }
- assert.Equal(t, onlyQueryPlans(p), plan{
- Select: plannedQuery{
- Query: `SELECT "ID", "Description", "CreatedAt" FROM "basic_records" WHERE `,
- ScanIndexes: [][]int{{0}, {1}, {2}},
- },
- Insert: plannedQuery{
- Query: `INSERT INTO "basic_records" ("Description", "CreatedAt") VALUES (?, ?)`,
- ArgumentIndexes: [][]int{{1}, {2}},
- ScanIndexes: [][]int{{0}},
- },
- Update: plannedQuery{
- Query: `UPDATE "basic_records" SET "Description" = ?, "CreatedAt" = ? WHERE "ID" = ?`,
- ArgumentIndexes: [][]int{{1}, {2}, {0}},
- },
- Delete: plannedQuery{
- Query: `DELETE FROM "basic_records" WHERE "ID" = ?`,
- ArgumentIndexes: [][]int{{0}},
- },
- })
-
- opts.ReadOnly = true
- p2, err := buildPlan(reflect.TypeFor[record](), SqliteDialect(), opts)
- if err != nil {
- t.Error(err)
- }
- assert.Equal(t, onlyQueryPlans(p2), plan{
- ReadOnly: true,
- Select: p.Select,
- })
- })
-}
-
-func TestQueryConstructionWithOnlyPrimaryKey(t *testing.T) {
- type relation struct {
- FooID int64 `db:"foo_id"`
- BarID int64 `db:"bar_id"`
- }
- opts := planOpts{
- StructTagKey: "db",
- TableName: "foo_bar_relations",
- PrimaryKeyColumnNames: []string{"foo_id", "bar_id"},
- }
-
- t.Run("MariaDBDialect", func(t *testing.T) {
- p, err := buildPlan(reflect.TypeFor[relation](), MariaDBDialect(), opts)
- if err != nil {
- t.Error(err)
- }
- assert.Equal(t, onlyQueryPlans(p), plan{
- Select: plannedQuery{
- Query: "SELECT `foo_id`, `bar_id` FROM `foo_bar_relations` WHERE ",
- ScanIndexes: [][]int{{0}, {1}},
- },
- Insert: plannedQuery{
- Query: "INSERT INTO `foo_bar_relations` (`foo_id`, `bar_id`) VALUES (?, ?)",
- ArgumentIndexes: [][]int{{0}, {1}},
- },
- Upsert: plannedQuery{
- Query: "INSERT INTO `foo_bar_relations` (`foo_id`, `bar_id`) VALUES (?, ?) ON DUPLICATE KEY UPDATE `foo_id` = VALUES(`foo_id`)",
- ArgumentIndexes: [][]int{{0}, {1}},
- },
- Delete: plannedQuery{
- Query: "DELETE FROM `foo_bar_relations` WHERE `foo_id` = ? AND `bar_id` = ?",
- ArgumentIndexes: [][]int{{0}, {1}},
- },
- })
- })
-
- t.Run("PostgresDialect", func(t *testing.T) {
- p, err := buildPlan(reflect.TypeFor[relation](), PostgresDialect(), opts)
- if err != nil {
- t.Error(err)
- }
- assert.Equal(t, onlyQueryPlans(p), plan{
- Select: plannedQuery{
- Query: `SELECT "foo_id", "bar_id" FROM "foo_bar_relations" WHERE `,
- ScanIndexes: [][]int{{0}, {1}},
- },
- Insert: plannedQuery{
- Query: `INSERT INTO "foo_bar_relations" ("foo_id", "bar_id") VALUES ($1, $2)`,
- ArgumentIndexes: [][]int{{0}, {1}},
- },
- Upsert: plannedQuery{
- Query: `INSERT INTO "foo_bar_relations" ("foo_id", "bar_id") VALUES ($1, $2) ON CONFLICT ("foo_id", "bar_id") DO NOTHING`,
- ArgumentIndexes: [][]int{{0}, {1}},
- },
- Delete: plannedQuery{
- Query: `DELETE FROM "foo_bar_relations" WHERE "foo_id" = $1 AND "bar_id" = $2`,
- ArgumentIndexes: [][]int{{0}, {1}},
- },
- })
- })
-
- t.Run("SqliteDialect", func(t *testing.T) {
- p, err := buildPlan(reflect.TypeFor[relation](), SqliteDialect(), opts)
- if err != nil {
- t.Error(err)
- }
- assert.Equal(t, onlyQueryPlans(p), plan{
- Select: plannedQuery{
- Query: `SELECT "foo_id", "bar_id" FROM "foo_bar_relations" WHERE `,
- ScanIndexes: [][]int{{0}, {1}},
- },
- Insert: plannedQuery{
- Query: `INSERT INTO "foo_bar_relations" ("foo_id", "bar_id") VALUES (?, ?)`,
- ArgumentIndexes: [][]int{{0}, {1}},
- },
- Upsert: plannedQuery{
- Query: `INSERT INTO "foo_bar_relations" ("foo_id", "bar_id") VALUES (?, ?) ON CONFLICT ("foo_id", "bar_id") DO NOTHING`,
- ArgumentIndexes: [][]int{{0}, {1}},
- },
- Delete: plannedQuery{
- Query: `DELETE FROM "foo_bar_relations" WHERE "foo_id" = ? AND "bar_id" = ?`,
- ArgumentIndexes: [][]int{{0}, {1}},
- },
- })
- })
-}
-
-func TestQueryConstructionWithoutPrimaryKey(t *testing.T) {
- type relation struct {
- FooID int64 `db:"foo_id"`
- BarID int64 `db:"bar_id"`
- }
- opts := planOpts{
- StructTagKey: "db",
- TableName: "foo_bar_relations",
- }
-
- t.Run("MariaDBDialect", func(t *testing.T) {
- p, err := buildPlan(reflect.TypeFor[relation](), MariaDBDialect(), opts)
- if err != nil {
- t.Error(err)
- }
- assert.Equal(t, onlyQueryPlans(p), plan{
- Select: plannedQuery{
- Query: "SELECT `foo_id`, `bar_id` FROM `foo_bar_relations` WHERE ",
- ScanIndexes: [][]int{{0}, {1}},
- },
- Insert: plannedQuery{
- Query: "INSERT INTO `foo_bar_relations` (`foo_id`, `bar_id`) VALUES (?, ?)",
- ArgumentIndexes: [][]int{{0}, {1}},
- },
- })
- })
-
- t.Run("PostgresDialect", func(t *testing.T) {
- p, err := buildPlan(reflect.TypeFor[relation](), PostgresDialect(), opts)
- if err != nil {
- t.Error(err)
- }
- assert.Equal(t, onlyQueryPlans(p), plan{
- Select: plannedQuery{
- Query: `SELECT "foo_id", "bar_id" FROM "foo_bar_relations" WHERE `,
- ScanIndexes: [][]int{{0}, {1}},
- },
- Insert: plannedQuery{
- Query: `INSERT INTO "foo_bar_relations" ("foo_id", "bar_id") VALUES ($1, $2)`,
- ArgumentIndexes: [][]int{{0}, {1}},
- },
- })
- })
-
- t.Run("SqliteDialect", func(t *testing.T) {
- p, err := buildPlan(reflect.TypeFor[relation](), SqliteDialect(), opts)
- if err != nil {
- t.Error(err)
- }
- assert.Equal(t, onlyQueryPlans(p), plan{
- Select: plannedQuery{
- Query: `SELECT "foo_id", "bar_id" FROM "foo_bar_relations" WHERE `,
- ScanIndexes: [][]int{{0}, {1}},
- },
- Insert: plannedQuery{
- Query: `INSERT INTO "foo_bar_relations" ("foo_id", "bar_id") VALUES (?, ?)`,
- ArgumentIndexes: [][]int{{0}, {1}},
- },
- })
- })
-}
-
-func TestQueryConstructionImpossible(t *testing.T) {
- type unstructuredData struct {
- Foo int
- Bar *string
- }
- opts := planOpts{
- StructTagKey: "db",
- }
-
- testWith := func(dialect Dialect) func(*testing.T) {
- return func(t *testing.T) {
- p, err := buildPlan(reflect.TypeFor[unstructuredData](), dialect, opts)
- if err != nil {
- t.Error(err)
- }
-
- assert.Equal(t, onlyQueryPlans(p), plan{})
- }
- }
-
- t.Run("MariaDBDialect", testWith(MariaDBDialect()))
- t.Run("PostgresDialect", testWith(PostgresDialect()))
- t.Run("SqliteDialect", testWith(SqliteDialect()))
-}
-
-func TestQueryConstructionWithMultiplePrimaryKeyColumns(t *testing.T) {
- type record struct {
- GroupID int64 `db:"group_id"`
- Name string `db:"name"`
- CreatedAt time.Time `db:"created_at"`
- }
- opts := planOpts{
- StructTagKey: "db",
- TableName: "complex_records",
- PrimaryKeyColumnNames: []string{"group_id", "name"},
- }
-
- t.Run("MariaDBDialect", func(t *testing.T) {
- p, err := buildPlan(reflect.TypeFor[record](), MariaDBDialect(), opts)
- if err != nil {
- t.Error(err)
- }
- assert.Equal(t, onlyQueryPlans(p), plan{
- Select: plannedQuery{
- Query: "SELECT `group_id`, `name`, `created_at` FROM `complex_records` WHERE ",
- ScanIndexes: [][]int{{0}, {1}, {2}},
- },
- Insert: plannedQuery{
- Query: "INSERT INTO `complex_records` (`group_id`, `name`, `created_at`) VALUES (?, ?, ?)",
- ArgumentIndexes: [][]int{{0}, {1}, {2}},
- },
- Upsert: plannedQuery{
- Query: "INSERT INTO `complex_records` (`group_id`, `name`, `created_at`) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE `created_at` = VALUES(`created_at`)",
- ArgumentIndexes: [][]int{{0}, {1}, {2}},
- },
- Update: plannedQuery{
- Query: "UPDATE `complex_records` SET `created_at` = ? WHERE `group_id` = ? AND `name` = ?",
- ArgumentIndexes: [][]int{{2}, {0}, {1}},
- },
- Delete: plannedQuery{
- Query: "DELETE FROM `complex_records` WHERE `group_id` = ? AND `name` = ?",
- ArgumentIndexes: [][]int{{0}, {1}},
- },
- })
- })
-
- t.Run("PostgresDialect", func(t *testing.T) {
- p, err := buildPlan(reflect.TypeFor[record](), PostgresDialect(), opts)
- if err != nil {
- t.Error(err)
- }
- assert.Equal(t, onlyQueryPlans(p), plan{
- Select: plannedQuery{
- Query: `SELECT "group_id", "name", "created_at" FROM "complex_records" WHERE `,
- ScanIndexes: [][]int{{0}, {1}, {2}},
- },
- Insert: plannedQuery{
- Query: `INSERT INTO "complex_records" ("group_id", "name", "created_at") VALUES ($1, $2, $3)`,
- ArgumentIndexes: [][]int{{0}, {1}, {2}},
- },
- Upsert: plannedQuery{
- Query: `INSERT INTO "complex_records" ("group_id", "name", "created_at") VALUES ($1, $2, $3) ON CONFLICT ("group_id", "name") DO UPDATE SET "created_at" = EXCLUDED."created_at"`,
- ArgumentIndexes: [][]int{{0}, {1}, {2}},
- },
- Update: plannedQuery{
- Query: `UPDATE "complex_records" SET "created_at" = $1 WHERE "group_id" = $2 AND "name" = $3`,
- ArgumentIndexes: [][]int{{2}, {0}, {1}},
- },
- Delete: plannedQuery{
- Query: `DELETE FROM "complex_records" WHERE "group_id" = $1 AND "name" = $2`,
- ArgumentIndexes: [][]int{{0}, {1}},
- },
- })
- })
-
- t.Run("SqliteDialect", func(t *testing.T) {
- p, err := buildPlan(reflect.TypeFor[record](), SqliteDialect(), opts)
- if err != nil {
- t.Error(err)
- }
- assert.Equal(t, onlyQueryPlans(p), plan{
- Select: plannedQuery{
- Query: `SELECT "group_id", "name", "created_at" FROM "complex_records" WHERE `,
- ScanIndexes: [][]int{{0}, {1}, {2}},
- },
- Insert: plannedQuery{
- Query: `INSERT INTO "complex_records" ("group_id", "name", "created_at") VALUES (?, ?, ?)`,
- ArgumentIndexes: [][]int{{0}, {1}, {2}},
- },
- Upsert: plannedQuery{
- Query: `INSERT INTO "complex_records" ("group_id", "name", "created_at") VALUES (?, ?, ?) ON CONFLICT ("group_id", "name") DO UPDATE SET "created_at" = EXCLUDED."created_at"`,
- ArgumentIndexes: [][]int{{0}, {1}, {2}},
- },
- Update: plannedQuery{
- Query: `UPDATE "complex_records" SET "created_at" = ? WHERE "group_id" = ? AND "name" = ?`,
- ArgumentIndexes: [][]int{{2}, {0}, {1}},
- },
- Delete: plannedQuery{
- Query: `DELETE FROM "complex_records" WHERE "group_id" = ? AND "name" = ?`,
- ArgumentIndexes: [][]int{{0}, {1}},
- },
- })
- })
-}
-
-func TestQueryConstructionWithMultipleAutoColumns(t *testing.T) {
- type record struct {
- ID int64 `db:"id,auto"`
- Name string `db:"name"`
- CreatedAt time.Time `db:"created_at,auto"`
- }
- opts := planOpts{
- StructTagKey: "db",
- TableName: "autogenerated_records",
- PrimaryKeyColumnNames: []string{"id"},
- }
-
- t.Run("MariaDBDialect", func(t *testing.T) {
- p, err := buildPlan(reflect.TypeFor[record](), MariaDBDialect(), opts)
- if err != nil {
- t.Error(err)
- }
- assert.Equal(t, onlyQueryPlans(p), plan{
- InsertUsesQueryRow: true,
- Select: plannedQuery{
- Query: "SELECT `id`, `name`, `created_at` FROM `autogenerated_records` WHERE ",
- ScanIndexes: [][]int{{0}, {1}, {2}},
- },
- Insert: plannedQuery{
- Query: "INSERT INTO `autogenerated_records` (`name`) VALUES (?) RETURNING `id`, `created_at`",
- ArgumentIndexes: [][]int{{1}},
- ScanIndexes: [][]int{{0}, {2}},
- },
- Update: plannedQuery{
- Query: "UPDATE `autogenerated_records` SET `name` = ?, `created_at` = ? WHERE `id` = ?",
- ArgumentIndexes: [][]int{{1}, {2}, {0}},
- },
- Delete: plannedQuery{
- Query: "DELETE FROM `autogenerated_records` WHERE `id` = ?",
- ArgumentIndexes: [][]int{{0}},
- },
- })
- })
-
- t.Run("PostgresDialect", func(t *testing.T) {
- p, err := buildPlan(reflect.TypeFor[record](), PostgresDialect(), opts)
- if err != nil {
- t.Error(err)
- }
- assert.Equal(t, onlyQueryPlans(p), plan{
- InsertUsesQueryRow: true,
- Select: plannedQuery{
- Query: `SELECT "id", "name", "created_at" FROM "autogenerated_records" WHERE `,
- ScanIndexes: [][]int{{0}, {1}, {2}},
- },
- Insert: plannedQuery{
- Query: `INSERT INTO "autogenerated_records" ("name") VALUES ($1) RETURNING "id", "created_at"`,
- ArgumentIndexes: [][]int{{1}},
- ScanIndexes: [][]int{{0}, {2}},
- },
- Update: plannedQuery{
- Query: `UPDATE "autogenerated_records" SET "name" = $1, "created_at" = $2 WHERE "id" = $3`,
- ArgumentIndexes: [][]int{{1}, {2}, {0}},
- },
- Delete: plannedQuery{
- Query: `DELETE FROM "autogenerated_records" WHERE "id" = $1`,
- ArgumentIndexes: [][]int{{0}},
- },
- })
- })
-
- t.Run("SqliteDialect", func(t *testing.T) {
- p, err := buildPlan(reflect.TypeFor[record](), SqliteDialect(), opts)
- if err != nil {
- t.Error(err)
- }
- assert.Equal(t, onlyQueryPlans(p), plan{
- InsertUsesQueryRow: true,
- Select: plannedQuery{
- Query: `SELECT "id", "name", "created_at" FROM "autogenerated_records" WHERE `,
- ScanIndexes: [][]int{{0}, {1}, {2}},
- },
- Insert: plannedQuery{
- Query: `INSERT INTO "autogenerated_records" ("name") VALUES (?) RETURNING "id", "created_at"`,
- ArgumentIndexes: [][]int{{1}},
- ScanIndexes: [][]int{{0}, {2}},
- },
- Update: plannedQuery{
- Query: `UPDATE "autogenerated_records" SET "name" = ?, "created_at" = ? WHERE "id" = ?`,
- ArgumentIndexes: [][]int{{1}, {2}, {0}},
- },
- Delete: plannedQuery{
- Query: `DELETE FROM "autogenerated_records" WHERE "id" = ?`,
- ArgumentIndexes: [][]int{{0}},
- },
- })
- })
-}
-
-func TestPlanErrorCases(t *testing.T) {
- type recordUsedViaPointer struct {
- ID int64 `db:"id"`
- }
-
- _, err := NewStore[*recordUsedViaPointer](SqliteDialect())
- assert.Equal(t, err.Error(), `cannot use type *oblast.recordUsedViaPointer for queries: `+
- `expected struct type, but got kind "ptr"`)
-
- type recordWithDuplicateTags struct {
- Foo int64 `db:"Bar"`
- Qux float64
- Bar string
- }
- _, err = NewStore[recordWithDuplicateTags](SqliteDialect())
- assert.Equal(t, err.Error(), `cannot use type oblast.recordWithDuplicateTags for queries: `+
- "duplicate tag `db:\"Bar\"` on field index [0], but also on field index [2]")
-
- type recordWithUnusedTransparentStruct struct {
- ID int64
- CreatedAt time.Time // has no exported fields!
- }
- _, err = NewStore[recordWithUnusedTransparentStruct](SqliteDialect())
- assert.Equal(t, err.Error(), `cannot use type oblast.recordWithUnusedTransparentStruct for queries: `+
- "field \"CreatedAt\" of type time.Time does not contain any mapped fields (to map this whole field to a DB column, add an explicit `db:\"...\"` tag)")
-
- type recordWithPKButNoTableName struct {
- ID int64 `db:"id"`
- Name string `db:"name"`
- }
- _, err = NewStore[recordWithPKButNoTableName](SqliteDialect(),
- PrimaryKeyIs("id"),
- )
- assert.Equal(t, err.Error(), `cannot use type oblast.recordWithPKButNoTableName for queries: `+
- `cannot declare a primary key without also providing the TableNameIs option`)
-
- type recordWithUnknownPK struct {
- ID int64 `db:"id"`
- Name string `db:"name"`
- }
- _, err = NewStore[recordWithUnknownPK](SqliteDialect(),
- TableNameIs("records"),
- PrimaryKeyIs("record_id"),
- )
- assert.Equal(t, err.Error(), `cannot use type oblast.recordWithUnknownPK for queries: `+
- "no field has tag `db:\"record_id\"`, but a field of this name was declared in the primary key")
-
- type recordWithWeirdTagOption struct {
- ID int64 `db:",auto"`
- Name string `db:",unique"`
- Description string
- }
- _, err = NewStore[recordWithWeirdTagOption](SqliteDialect())
- assert.Equal(t, err.Error(), `cannot use type oblast.recordWithWeirdTagOption for queries: `+
- "unknown option `db:\",unique\"` on field \"Name\"")
-}