From f03f6e90c725a1de9e2b62e8e4aeab5ff6ec4b80 Mon Sep 17 00:00:00 2001 From: Stefan Majewsky Date: Sat, 18 Apr 2026 17:35:07 +0200 Subject: add StructTagKeyIs Forgot this in 0.1.0. Without this, I cannot migrate existing Gorp users because Gorp does not understand `db:",auto"`. --- CHANGELOG.md | 6 ++++++ oblast.go | 8 ++++++++ plan.go | 22 ++++++++++++++-------- query_test.go | 5 +++-- 4 files changed, 31 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e63da04..cdc9516 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,12 @@ SPDX-FileCopyrightText: 2026 Stefan Majewsky SPDX-License-Identifier: Apache-2.0 --> +# v0.2.0 (TBD) + +Changes: + +- Add func StructTagKeyIs. + # v0.1.0 (2026-04-18) Initial release. This release has code quality worthy of a 1.x version number, diff --git a/oblast.go b/oblast.go index 52c0cfd..338f245 100644 --- a/oblast.go +++ b/oblast.go @@ -123,6 +123,14 @@ func PrimaryKeyIs(columnNames ...string) PlanOption { return func(opts *planOpts) { opts.PrimaryKeyColumnNames = columnNames } } +// StructTagKeyIs is a PlanOption for record types that allows renaming the struct tag key that Oblast inspects from its default value of "db". +// For example, providing StructTagKeyIs("oblast") means that a struct tag like `db:",auto"` must be written as `oblast:",auto"` instead. +// +// This is useful when migrating from or to another ORM library that uses the same `db:"..."` tag as Oblast, but with conflicting semantics. +func StructTagKeyIs(key string) PlanOption { + return func(opts *planOpts) { opts.StructTagKey = key } +} + // Handle is an interface for functions providing direct DB access. // It covers methods provided by both *sql.DB and *sql.Tx, thus allowing functions using it to be used both within and outside of transactions. type Handle interface { diff --git a/plan.go b/plan.go index 8b7ec1d..9c4da54 100644 --- a/plan.go +++ b/plan.go @@ -49,6 +49,7 @@ type plannedQuery struct { // planOpts holds additional arguments to buildPlan(). type planOpts struct { + StructTagKey string // defaults to "db" TableName string PrimaryKeyColumnNames []string } @@ -59,6 +60,11 @@ func buildPlan(t reflect.Type, dialect Dialect, opts planOpts) (plan, error) { return plan{}, fmt.Errorf("expected struct type, but got kind %q", t.Kind().String()) } + // apply defaults to planOpts fields + if opts.StructTagKey == "" { + opts.StructTagKey = "db" + } + var p = plan{ TypeName: t.Name(), TableName: opts.TableName, @@ -85,7 +91,7 @@ func buildPlan(t reflect.Type, dialect Dialect, opts planOpts) (plan, error) { // recurse into struct fields (i.e. ignore the struct itself and consider its members instead) // unless the field itself has a `db:"..."` tag if field.Type.Kind() == reflect.Struct || (field.Type.Kind() == reflect.Pointer && field.Type.Elem().Kind() == reflect.Struct) { - if field.Tag.Get("db") == "" { + if field.Tag.Get(opts.StructTagKey) == "" { indexesOfUnusedTransparentStructs = append(indexesOfUnusedTransparentStructs, field.Index) if field.Type.Kind() == reflect.Pointer { // remember that, when scanning into a record of type `t`, we need to write a non-nil zeroed struct into this field @@ -105,7 +111,7 @@ func buildPlan(t reflect.Type, dialect Dialect, opts planOpts) (plan, error) { } // check `db:"..."` tag, ignore fields that are declared with column name "-" - tags := strings.Split(strings.TrimSpace(field.Tag.Get("db")), ",") + tags := strings.Split(strings.TrimSpace(field.Tag.Get(opts.StructTagKey)), ",") columnName, extraTags := cmp.Or(tags[0], field.Name), tags[1:] if columnName == "-" { continue @@ -113,8 +119,8 @@ func buildPlan(t reflect.Type, dialect Dialect, opts planOpts) (plan, error) { if otherIndex := p.IndexByColumnName[columnName]; otherIndex != nil { return plan{}, fmt.Errorf( - "duplicate tag `db:%q` on field index %v, but also on field index %v", - columnName, otherIndex, field.Index, + "duplicate tag `%s:%q` on field index %v, but also on field index %v", + opts.StructTagKey, columnName, otherIndex, field.Index, ) } p.IndexByColumnName[columnName] = field.Index @@ -134,7 +140,7 @@ func buildPlan(t reflect.Type, dialect Dialect, opts planOpts) (plan, error) { case "auto": p.AutoColumnNames = append(p.AutoColumnNames, columnName) default: - return plan{}, fmt.Errorf("unknown option `db:%q` on field %q", ","+tag, field.Name) + return plan{}, fmt.Errorf("unknown option `%s:%q` on field %q", opts.StructTagKey, ","+tag, field.Name) } } } @@ -146,8 +152,8 @@ func buildPlan(t reflect.Type, dialect Dialect, opts planOpts) (plan, error) { for _, index := range indexesOfUnusedTransparentStructs { field := t.FieldByIndex(index) return plan{}, fmt.Errorf( - "field %q of type %s does not contain any mapped fields (to map this whole field to a DB column, add an explicit `db:\"...\"` tag)", - field.Name, field.Type.String(), + "field %q of type %s does not contain any mapped fields (to map this whole field to a DB column, add an explicit `%s:\"...\"` tag)", + field.Name, field.Type.String(), opts.StructTagKey, ) } @@ -160,7 +166,7 @@ func buildPlan(t reflect.Type, dialect Dialect, opts planOpts) (plan, error) { for _, columnName := range p.PrimaryKeyColumnNames { _, ok := p.IndexByColumnName[columnName] if !ok { - return plan{}, fmt.Errorf("no field has tag `db:%q`, but a field of this name was declared in the primary key", columnName) + return plan{}, fmt.Errorf("no field has tag `%s:%q`, but a field of this name was declared in the primary key", opts.StructTagKey, columnName) } } diff --git a/query_test.go b/query_test.go index 7dde757..2d14a88 100644 --- a/query_test.go +++ b/query_test.go @@ -19,11 +19,12 @@ func TestInsertBasicUsingLastInsertId(t *testing.T) { db := sql.OpenDB(md) type basicRecord struct { - ID int64 `db:"id,auto"` - Name string `db:"name"` + ID int64 `oblast:"id,auto"` + Name string `oblast:"name"` } store := oblast.MustNewStore[basicRecord]( oblast.SqliteDialect(), + oblast.StructTagKeyIs("oblast"), // this test also randomly provides coverage for this option oblast.TableNameIs("basic_records"), oblast.PrimaryKeyIs("id"), ) -- cgit v1.2.3