Skip to content

Commit 5e1ff3a

Browse files
authored
Squashed commit of the following: (#4)
commit 4b3f8fb Author: Erik Unger <[email protected]> Date: Tue Jun 7 13:27:00 2022 +0200 NewTaggedStructFieldMapping commit b012649 Author: Erik Unger <[email protected]> Date: Tue Jun 7 13:01:02 2022 +0200 StructFieldMapper commit 05175ca Author: Erik Unger <[email protected]> Date: Fri Jun 3 18:29:56 2022 +0200 fix ReflectStructValues commit 3e82003 Author: Erik Unger <[email protected]> Date: Fri Jun 3 18:19:34 2022 +0200 add ColumnFilter
1 parent 31b8fcd commit 5e1ff3a

36 files changed

+750
-743
lines changed

columnfilter.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package sqldb
2+
3+
import (
4+
"reflect"
5+
)
6+
7+
type ColumnFilter interface {
8+
IgnoreColumn(name string, flags FieldFlag, fieldType reflect.StructField, fieldValue reflect.Value) bool
9+
}
10+
11+
type ColumnFilterFunc func(name string, flags FieldFlag, fieldType reflect.StructField, fieldValue reflect.Value) bool
12+
13+
func (f ColumnFilterFunc) IgnoreColumn(name string, flags FieldFlag, fieldType reflect.StructField, fieldValue reflect.Value) bool {
14+
return f(name, flags, fieldType, fieldValue)
15+
}
16+
17+
func IgnoreColumns(names ...string) ColumnFilter {
18+
return ColumnFilterFunc(func(name string, flags FieldFlag, fieldType reflect.StructField, fieldValue reflect.Value) bool {
19+
for _, ignore := range names {
20+
if name == ignore {
21+
return true
22+
}
23+
}
24+
return false
25+
})
26+
}
27+
28+
func OnlyColumns(names ...string) ColumnFilter {
29+
return ColumnFilterFunc(func(name string, flags FieldFlag, fieldType reflect.StructField, fieldValue reflect.Value) bool {
30+
for _, include := range names {
31+
if name == include {
32+
return false
33+
}
34+
}
35+
return true
36+
})
37+
}
38+
39+
func IgnoreStructFields(names ...string) ColumnFilter {
40+
return ColumnFilterFunc(func(name string, flags FieldFlag, fieldType reflect.StructField, fieldValue reflect.Value) bool {
41+
for _, ignore := range names {
42+
if fieldType.Name == ignore {
43+
return true
44+
}
45+
}
46+
return false
47+
})
48+
}
49+
50+
func OnlyStructFields(names ...string) ColumnFilter {
51+
return ColumnFilterFunc(func(name string, flags FieldFlag, fieldType reflect.StructField, fieldValue reflect.Value) bool {
52+
for _, include := range names {
53+
if fieldType.Name == include {
54+
return false
55+
}
56+
}
57+
return true
58+
})
59+
}
60+
61+
func IgnoreFlags(ignore FieldFlag) ColumnFilter {
62+
return ColumnFilterFunc(func(name string, flags FieldFlag, fieldType reflect.StructField, fieldValue reflect.Value) bool {
63+
return flags&ignore != 0
64+
})
65+
}
66+
67+
var IgnoreDefault ColumnFilter = ColumnFilterFunc(func(name string, flags FieldFlag, fieldType reflect.StructField, fieldValue reflect.Value) bool {
68+
return flags.Default()
69+
})
70+
71+
var IgnorePrimaryKey ColumnFilter = ColumnFilterFunc(func(name string, flags FieldFlag, fieldType reflect.StructField, fieldValue reflect.Value) bool {
72+
return flags.PrimaryKey()
73+
})
74+
75+
var IgnoreReadOnly ColumnFilter = ColumnFilterFunc(func(name string, flags FieldFlag, fieldType reflect.StructField, fieldValue reflect.Value) bool {
76+
return flags.ReadOnly()
77+
})
78+
79+
var IgnoreNull ColumnFilter = ColumnFilterFunc(func(name string, flags FieldFlag, fieldType reflect.StructField, fieldValue reflect.Value) bool {
80+
return IsNull(fieldValue)
81+
})
82+
83+
var IgnoreNullOrZero ColumnFilter = ColumnFilterFunc(func(name string, flags FieldFlag, fieldType reflect.StructField, fieldValue reflect.Value) bool {
84+
return IsNullOrZero(fieldValue)
85+
})
86+
87+
var IgnoreNullOrZeroDefault ColumnFilter = ColumnFilterFunc(func(name string, flags FieldFlag, fieldType reflect.StructField, fieldValue reflect.Value) bool {
88+
return flags.Default() && IsNullOrZero(fieldValue)
89+
})

connection.go

Lines changed: 14 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,10 @@ type Connection interface {
2626

2727
// WithStructFieldNamer returns a copy of the connection
2828
// that will use the passed StructFieldNamer.
29-
WithStructFieldNamer(namer StructFieldNamer) Connection
29+
WithStructFieldNamer(namer StructFieldMapper) Connection
3030

3131
// StructFieldNamer used by methods of this Connection.
32-
StructFieldNamer() StructFieldNamer
32+
StructFieldNamer() StructFieldMapper
3333

3434
// Ping returns an error if the database
3535
// does not answer on this connection
@@ -66,37 +66,17 @@ type Connection interface {
6666
// and returns values from the inserted row listed in returning.
6767
InsertReturning(table string, values Values, returning string) RowScanner
6868

69-
// InsertStruct inserts a new row into table using the exported fields
70-
// of rowStruct which have a `db` tag that is not "-".
71-
// If restrictToColumns are provided, then only struct fields with a `db` tag
72-
// matching any of the passed column names will be used.
73-
InsertStruct(table string, rowStruct any, restrictToColumns ...string) error
74-
75-
// InsertStructNonDefault inserts a new row into table using the exported fields
76-
// of rowStruct which have a `db` tag that is not "-".
77-
// Struct fields with the `db` tags ",default" or ",readonly" will not be inserted.
78-
InsertStructNonDefault(table string, rowStruct any) error
79-
80-
// TODO insert multiple structs with single query if possible
81-
// InsertStructs(table string, rowStructs any, restrictToColumns ...string) error
82-
83-
// InsertStructIgnoreColumns inserts a new row into table using the exported fields
84-
// of rowStruct which have a `db` tag that is not "-".
85-
// Struct fields with a `db` tag matching any of the passed ignoreColumns will not be used.
86-
InsertStructIgnoreColumns(table string, rowStruct any, ignoreColumns ...string) error
87-
88-
// InsertUniqueStruct inserts a new row into table using the exported fields
89-
// of rowStruct which have a `db` tag that is not "-".
90-
// If restrictToColumns are provided, then only struct fields with a `db` tag
91-
// matching any of the passed column names will be used.
92-
// Does nothing if the onConflict statement applies and returns if a row was inserted.
93-
InsertUniqueStruct(table string, rowStruct any, onConflict string, restrictToColumns ...string) (inserted bool, err error)
69+
// InsertStruct inserts a new row into table using the connection's
70+
// StructFieldNamer to map struct fields to column names.
71+
// Optional ColumnFilter can be passed to ignore mapped columns.
72+
InsertStruct(table string, rowStruct any, ignoreColumns ...ColumnFilter) error
9473

95-
// InsertUniqueStructIgnoreColumns inserts a new row into table using the exported fields
96-
// of rowStruct which have a `db` tag that is not "-".
97-
// Struct fields with a `db` tag matching any of the passed ignoreColumns will not be used.
98-
// Does nothing if the onConflict statement applies and returns if a row was inserted.
99-
InsertUniqueStructIgnoreColumns(table string, rowStruct any, onConflict string, ignoreColumns ...string) (inserted bool, err error)
74+
// InsertUniqueStruct inserts a new row into table using the connection's
75+
// StructFieldNamer to map struct fields to column names.
76+
// Optional ColumnFilter can be passed to ignore mapped columns.
77+
// Does nothing if the onConflict statement applies
78+
// and returns if a row was inserted.
79+
InsertUniqueStruct(table string, rowStruct any, onConflict string, ignoreColumns ...ColumnFilter) (inserted bool, err error)
10080

10181
// Update table rows(s) with values using the where statement with passed in args starting at $1.
10282
Update(table string, values Values, where string, args ...any) error
@@ -115,14 +95,7 @@ type Connection interface {
11595
// matching any of the passed column names will be used.
11696
// The struct must have at least one field with a `db` tag value having a ",pk" suffix
11797
// to mark primary key column(s).
118-
UpdateStruct(table string, rowStruct any, restrictToColumns ...string) error
119-
120-
// UpdateStructIgnoreColumns updates a row in a table using the exported fields
121-
// of rowStruct which have a `db` tag that is not "-".
122-
// Struct fields with a `db` tag matching any of the passed ignoreColumns will not be used.
123-
// The struct must have at least one field with a `db` tag value having a ",pk" suffix
124-
// to mark primary key column(s).
125-
UpdateStructIgnoreColumns(table string, rowStruct any, ignoreColumns ...string) error
98+
UpdateStruct(table string, rowStruct any, ignoreColumns ...ColumnFilter) error
12699

127100
// UpsertStruct upserts a row to table using the exported fields
128101
// of rowStruct which have a `db` tag that is not "-".
@@ -131,15 +104,7 @@ type Connection interface {
131104
// The struct must have at least one field with a `db` tag value having a ",pk" suffix
132105
// to mark primary key column(s).
133106
// If inserting conflicts on the primary key column(s), then an update is performed.
134-
UpsertStruct(table string, rowStruct any, restrictToColumns ...string) error
135-
136-
// UpsertStructIgnoreColumns upserts a row to table using the exported fields
137-
// of rowStruct which have a `db` tag that is not "-".
138-
// Struct fields with a `db` tag matching any of the passed ignoreColumns will not be used.
139-
// The struct must have at least one field with a `db` tag value having a ",pk" suffix
140-
// to mark primary key column(s).
141-
// If inserting conflicts on the primary key column(s), then an update is performed.
142-
UpsertStructIgnoreColumns(table string, rowStruct any, ignoreColumns ...string) error
107+
UpsertStruct(table string, rowStruct any, ignoreColumns ...ColumnFilter) error
143108

144109
// QueryRow queries a single row and returns a RowScanner for the results.
145110
QueryRow(query string, args ...any) RowScanner

db/columnfilter.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package db
2+
3+
import (
4+
"github.com/domonda/go-sqldb"
5+
)
6+
7+
var (
8+
IgnoreDefault = sqldb.IgnoreDefault
9+
IgnorePrimaryKey = sqldb.IgnorePrimaryKey
10+
IgnoreReadOnly = sqldb.IgnoreReadOnly
11+
IgnoreNull = sqldb.IgnoreNull
12+
IgnoreNullOrZero = sqldb.IgnoreNullOrZero
13+
IgnoreNullOrZeroDefault = sqldb.IgnoreNullOrZeroDefault
14+
)
15+
16+
func IgnoreColumns(names ...string) sqldb.ColumnFilter {
17+
return sqldb.IgnoreColumns(names...)
18+
}
19+
20+
func OnlyColumns(names ...string) sqldb.ColumnFilter {
21+
return sqldb.OnlyColumns(names...)
22+
}
23+
24+
func IgnoreStructFields(names ...string) sqldb.ColumnFilter {
25+
return sqldb.IgnoreStructFields(names...)
26+
}
27+
28+
func OnlyStructFields(names ...string) sqldb.ColumnFilter {
29+
return sqldb.OnlyStructFields(names...)
30+
}

db/queryrow.go

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ import (
1212
// QueryRowStruct uses the passed pkValues to query a table row
1313
// and scan it into a struct of type S that must have tagged fields
1414
// with primary key flags to identify the primary key column names
15-
// for the passed pkValues.
16-
func QueryRowStruct[S any](ctx context.Context, table string, pkValues ...any) (row *S, err error) {
15+
// for the passed pkValues and a table name.
16+
func QueryRowStruct[S any](ctx context.Context, pkValues ...any) (row *S, err error) {
1717
if len(pkValues) == 0 {
1818
return nil, errors.New("no primaryKeyValues passed")
1919
}
@@ -22,7 +22,10 @@ func QueryRowStruct[S any](ctx context.Context, table string, pkValues ...any) (
2222
return nil, fmt.Errorf("expected struct template type instead of %s", t)
2323
}
2424
conn := Conn(ctx)
25-
pkColumns := pkColumnsOfStruct(t, conn.StructFieldNamer())
25+
table, pkColumns, err := pkColumnsOfStruct(t, conn.StructFieldNamer())
26+
if err != nil {
27+
return nil, err
28+
}
2629
if len(pkColumns) != len(pkValues) {
2730
return nil, fmt.Errorf("got %d primary key values, but struct %s has %d primary key fields", len(pkValues), t, len(pkColumns))
2831
}
@@ -37,18 +40,35 @@ func QueryRowStruct[S any](ctx context.Context, table string, pkValues ...any) (
3740
return row, nil
3841
}
3942

40-
func pkColumnsOfStruct(t reflect.Type, namer sqldb.StructFieldNamer) (columns []string) {
43+
func pkColumnsOfStruct(t reflect.Type, mapper sqldb.StructFieldMapper) (table string, columns []string, err error) {
4144
for i := 0; i < t.NumField(); i++ {
4245
field := t.Field(i)
43-
name, flags, ok := namer.StructFieldName(field)
46+
fieldTable, column, flags, ok := mapper.MapStructField(field)
4447
if !ok {
4548
continue
4649
}
47-
if name == "" {
48-
columns = append(columns, pkColumnsOfStruct(field.Type, namer)...)
50+
if fieldTable != "" && fieldTable != table {
51+
if table != "" {
52+
return "", nil, fmt.Errorf("table name not unique (%s vs %s) in struct %s", table, fieldTable, t)
53+
}
54+
table = fieldTable
55+
}
56+
57+
if column == "" {
58+
fieldTable, columnsEmbed, err := pkColumnsOfStruct(field.Type, mapper)
59+
if err != nil {
60+
return "", nil, err
61+
}
62+
if fieldTable != "" && fieldTable != table {
63+
if table != "" {
64+
return "", nil, fmt.Errorf("table name not unique (%s vs %s) in struct %s", table, fieldTable, t)
65+
}
66+
table = fieldTable
67+
}
68+
columns = append(columns, columnsEmbed...)
4969
} else if flags.PrimaryKey() {
50-
columns = append(columns, name)
70+
columns = append(columns, column)
5171
}
5272
}
53-
return columns
73+
return table, columns, nil
5474
}

errors.go

Lines changed: 7 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -80,12 +80,12 @@ func (e connectionWithError) WithContext(ctx context.Context) Connection {
8080
return connectionWithError{ctx: ctx, err: e.err}
8181
}
8282

83-
func (e connectionWithError) WithStructFieldNamer(namer StructFieldNamer) Connection {
83+
func (e connectionWithError) WithStructFieldNamer(namer StructFieldMapper) Connection {
8484
return e
8585
}
8686

87-
func (e connectionWithError) StructFieldNamer() StructFieldNamer {
88-
return &DefaultStructFieldTagNaming
87+
func (e connectionWithError) StructFieldNamer() StructFieldMapper {
88+
return DefaultStructFieldMapping
8989
}
9090

9191
func (e connectionWithError) Ping(time.Duration) error {
@@ -120,23 +120,11 @@ func (e connectionWithError) InsertReturning(table string, values Values, return
120120
return RowScannerWithError(e.err)
121121
}
122122

123-
func (e connectionWithError) InsertStruct(table string, rowStruct any, restrictToColumns ...string) error {
123+
func (e connectionWithError) InsertStruct(table string, rowStruct any, ignoreColumns ...ColumnFilter) error {
124124
return e.err
125125
}
126126

127-
func (e connectionWithError) InsertStructNonDefault(table string, rowStruct any) error {
128-
return e.err
129-
}
130-
131-
func (e connectionWithError) InsertStructIgnoreColumns(table string, rowStruct any, ignoreColumns ...string) error {
132-
return e.err
133-
}
134-
135-
func (e connectionWithError) InsertUniqueStruct(table string, rowStruct any, onConflict string, restrictToColumns ...string) (inserted bool, err error) {
136-
return false, e.err
137-
}
138-
139-
func (e connectionWithError) InsertUniqueStructIgnoreColumns(table string, rowStruct any, onConflict string, ignoreColumns ...string) (inserted bool, err error) {
127+
func (e connectionWithError) InsertUniqueStruct(table string, rowStruct any, onConflict string, ignoreColumns ...ColumnFilter) (inserted bool, err error) {
140128
return false, e.err
141129
}
142130

@@ -152,19 +140,11 @@ func (e connectionWithError) UpdateReturningRows(table string, values Values, re
152140
return RowsScannerWithError(e.err)
153141
}
154142

155-
func (e connectionWithError) UpdateStruct(table string, rowStruct any, restrictToColumns ...string) error {
156-
return e.err
157-
}
158-
159-
func (e connectionWithError) UpdateStructIgnoreColumns(table string, rowStruct any, ignoreColumns ...string) error {
160-
return e.err
161-
}
162-
163-
func (e connectionWithError) UpsertStruct(table string, rowStruct any, restrictToColumns ...string) error {
143+
func (e connectionWithError) UpdateStruct(table string, rowStruct any, ignoreColumns ...ColumnFilter) error {
164144
return e.err
165145
}
166146

167-
func (e connectionWithError) UpsertStructIgnoreColumns(table string, rowStruct any, ignoreColumns ...string) error {
147+
func (e connectionWithError) UpsertStruct(table string, rowStruct any, ignoreColumns ...ColumnFilter) error {
168148
return e.err
169149
}
170150

examples/user_demo/user_demo.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,16 @@ import (
1616
)
1717

1818
type User struct {
19-
ID uu.ID `db:"id,pk"`
19+
ID uu.ID `db:"id,pk,default"`
2020

2121
Email email.NullableAddress `db:"email"`
2222
Title nullable.NonEmptyString `db:"title"`
2323
Name string `db:"name"`
2424

2525
SessionToken nullable.NonEmptyString `db:"session_token"`
2626

27-
CreatedAt time.Time `db:"created_at"`
28-
UpdatedAt time.Time `db:"updated_at"`
27+
CreatedAt time.Time `db:"created_at,default"`
28+
UpdatedAt time.Time `db:"updated_at,default"`
2929
DisabledAt nullable.Time `db:"disabled_at"`
3030
}
3131

@@ -45,9 +45,9 @@ func main() {
4545
panic(err)
4646
}
4747

48-
conn = conn.WithStructFieldNamer(sqldb.StructFieldTagNaming{
48+
conn = conn.WithStructFieldNamer(&sqldb.TaggedStructFieldMapping{
4949
NameTag: "col",
50-
IgnoreName: "ignore",
50+
Ignore: "ignore",
5151
UntaggedNameFunc: sqldb.ToSnakeCase,
5252
})
5353

@@ -93,7 +93,7 @@ func main() {
9393
panic(err)
9494
}
9595

96-
err = conn.InsertStructIgnoreColumns("public.user", newUser, "created_at", "updated_at")
96+
err = conn.InsertStruct("public.user", newUser, sqldb.IgnoreNullOrZeroDefault)
9797
if err != nil {
9898
panic(err)
9999
}
@@ -106,7 +106,7 @@ func main() {
106106
panic(err)
107107
}
108108

109-
err = conn.UpsertStructIgnoreColumns("public.user", newUser, "created_at")
109+
err = conn.UpsertStruct("public.user", newUser, sqldb.IgnoreColumns("created_at"))
110110
if err != nil {
111111
panic(err)
112112
}

0 commit comments

Comments
 (0)