diff --git a/go.mod b/go.mod index cf3eead..5abccc4 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/google/subcommands v1.2.0 github.com/hashicorp/vault/api v1.15.0 github.com/huandu/xstrings v1.5.0 - github.com/jacobbrewer1/patcher v0.1.17 + github.com/jacobbrewer1/patcher v0.1.19 github.com/jacobbrewer1/vaulty v0.1.7 github.com/jmoiron/sqlx v1.4.0 github.com/magefile/mage v1.15.0 diff --git a/go.sum b/go.sum index 5d1ca4b..8fe39cc 100644 --- a/go.sum +++ b/go.sum @@ -94,8 +94,8 @@ github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/jacobbrewer1/patcher v0.1.17 h1:qxnRb5hIQWixcu6wE8Du9s9XHsJrX2GhwP3tG+mC1XE= -github.com/jacobbrewer1/patcher v0.1.17/go.mod h1:rurnK9D/WNAbHGO86m9naLmY5EXxrerZl6NeReFouZo= +github.com/jacobbrewer1/patcher v0.1.19 h1:OBIwnkHXzIEc5QArOJneKx1uEdsQ9qEXVZ1ou0fQc5M= +github.com/jacobbrewer1/patcher v0.1.19/go.mod h1:msM0fn0VFnEtSSFkBD1B2Id/fuyt+EuOA9OSRglkOrw= github.com/jacobbrewer1/vaulty v0.1.7 h1:Y1UVwFu/zwQK3octiLh95DefSfxZ0ano25UowpYfbfc= github.com/jacobbrewer1/vaulty v0.1.7/go.mod h1:8yPrEqRwBSjVK+CUo7vQ81cDJwIzrouoaqHYy9kX0cg= github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= diff --git a/vendor/github.com/jacobbrewer1/patcher/.golangci.yaml b/vendor/github.com/jacobbrewer1/patcher/.golangci.yaml new file mode 100644 index 0000000..5b87cbe --- /dev/null +++ b/vendor/github.com/jacobbrewer1/patcher/.golangci.yaml @@ -0,0 +1,143 @@ +linters: + enable: + - revive + - sloglint + - godox + - gosec + - musttag + - nilnil + - goconst + - gocritic + - gofmt + - iface + - thelper + - tparallel +issues: + exclude-rules: + - path: (.+)_test.go + linters: + - revive + text: "^(enforce-slice-style|enforce-map-style)" +linters-settings: + revive: + # Default: false + ignore-generated-header: true + # Default: 0.8 + confidence: 0.1 + rules: + # https://github.com/mgechev/revive/blob/HEAD/RULES_DESCRIPTIONS.md#context-as-argument + - name: context-as-argument + severity: error + disabled: false + exclude: [ "" ] + arguments: + - allowTypesBefore: "*testing.T" + # https://github.com/mgechev/revive/blob/HEAD/RULES_DESCRIPTIONS.md#early-return + - name: early-return + severity: error + disabled: false + exclude: [ "" ] + arguments: + - "preserveScope" + - "allowJump" + # https://github.com/mgechev/revive/blob/HEAD/RULES_DESCRIPTIONS.md#enforce-map-style + - name: enforce-map-style + severity: error + disabled: false + exclude: [ "" ] + arguments: + - "make" + # https://github.com/mgechev/revive/blob/HEAD/RULES_DESCRIPTIONS.md#enforce-slice-style + - name: enforce-slice-style + severity: error + disabled: false + exclude: [ "" ] + arguments: + - "make" + # https://github.com/mgechev/revive/blob/HEAD/RULES_DESCRIPTIONS.md#filename-format + - name: filename-format + severity: error + disabled: false + exclude: [ "" ] + arguments: + - "^[_a-z][_a-z0-9]*.go$" + # https://github.com/mgechev/revive/blob/HEAD/RULES_DESCRIPTIONS.md#optimize-operands-order + - name: optimize-operands-order + severity: error + disabled: false + exclude: [ "" ] + sloglint: + # Enforce using attributes only (overrides no-mixed-args, incompatible with kv-only). + # https://github.com/go-simpler/sloglint?tab=readme-ov-file#attributes-only + # Default: false + attr-only: true + # Enforce using static values for log messages. + # https://github.com/go-simpler/sloglint?tab=readme-ov-file#static-messages + # Default: false + static-msg: true + # Enforce using constants instead of raw keys. + # https://github.com/go-simpler/sloglint?tab=readme-ov-file#no-raw-keys + # Default: false + no-raw-keys: true + # Enforce a single key naming convention. + # Values: snake, kebab, camel, pascal + # https://github.com/go-simpler/sloglint?tab=readme-ov-file#key-naming-convention + # Default: "" + key-naming-case: snake + # Enforce not using specific keys. + # https://github.com/go-simpler/sloglint?tab=readme-ov-file#forbidden-keys + # Default: [] + forbidden-keys: + - time + - level + - msg + - source + - foo + # Enforce putting arguments on separate lines. + # https://github.com/go-simpler/sloglint?tab=readme-ov-file#arguments-on-separate-lines + # Default: false + args-on-sep-lines: true + goconst: + # Ignore test files. + # Default: false + ignore-tests: true + # Ignore when constant is not used as function argument. + # Default: true + ignore-calls: true + # Exclude strings matching the given regular expression. + # Default: "" + ignore-strings: '' + nilnil: + # In addition, detect opposite situation (simultaneous return of non-nil error and valid value). + # Default: false + detect-opposite: true + # List of return types to check. + # Default: ["chan", "func", "iface", "map", "ptr", "uintptr", "unsafeptr"] + checked-types: + - chan + - func + - iface + - map + - ptr + - uintptr + - unsafeptr + gocritic: + enable-all: true + gofmt: + # Simplify code: gofmt with `-s` option. + # Default: true + simplify: false + # Apply the rewrite rules to the source before reformatting. + # https://pkg.go.dev/cmd/gofmt + # Default: [] + rewrite-rules: + - pattern: 'interface{}' + replacement: 'any' + - pattern: 'a[b:len(a)]' + replacement: 'a[b:]' + iface: + # List of analyzers. + # Default: ["identical"] + enable: + - identical # Identifies interfaces in the same package that have identical method sets. + - unused # Identifies interfaces that are not used anywhere in the same package where the interface is defined. diff --git a/vendor/github.com/jacobbrewer1/patcher/README.md b/vendor/github.com/jacobbrewer1/patcher/README.md index 21326eb..40c7434 100644 --- a/vendor/github.com/jacobbrewer1/patcher/README.md +++ b/vendor/github.com/jacobbrewer1/patcher/README.md @@ -37,12 +37,8 @@ you. * **Easy Integration**: It is easy to integrate into existing projects and can be used with any Go project that needs to generate SQL queries from structs. * **Open Source**: It is open-source and available under the Apache 2.0 license. -* **Actively Maintained**: It is actively maintained and updated to support the latest Go versions and best practices, - ensuring compatibility and reliability. * **Comprehensive Documentation**: It has comprehensive documentation and examples to help you get started quickly and understand how to use the library effectively. -* **Tested and Reliable**: It is thoroughly tested and reliable, ensuring that it works as expected and meets the - requirements of your project. ## Usage @@ -60,8 +56,8 @@ you. * You can pass a struct that implements the `WhereTyper` interface to use `OR` in the where clause. Patcher will default to `AND` if the `WhereTyper` interface is not implemented. * `WithJoin(joinClause Joiner)`: Add join clauses to the SQL query. -* `includeZeroValues`: Set to true to include zero values in the diff. (Only for NewDiffSQLPatch) -* `includeNilValues`: Set to true to include nil values in the diff. (Only for NewDiffSQLPatch) +* `includeZeroValues`: Set to true to include zero values in the Patch. +* `includeNilValues`: Set to true to include nil values in the Patch. ### Basic Examples diff --git a/vendor/github.com/jacobbrewer1/patcher/inserter/batch_opts.go b/vendor/github.com/jacobbrewer1/patcher/inserter/batch_opts.go index 53984bd..49fc19e 100644 --- a/vendor/github.com/jacobbrewer1/patcher/inserter/batch_opts.go +++ b/vendor/github.com/jacobbrewer1/patcher/inserter/batch_opts.go @@ -44,8 +44,8 @@ func WithIgnoreFieldsFunc(f patcher.IgnoreFieldsFunc) BatchOpt { } // WithIncludePrimaryKey determines whether the primary key should be included in the insert -func WithIncludePrimaryKey() BatchOpt { +func WithIncludePrimaryKey(includePrimaryKey bool) BatchOpt { return func(b *SQLBatch) { - b.includePrimaryKey = true + b.includePrimaryKey = includePrimaryKey } } diff --git a/vendor/github.com/jacobbrewer1/patcher/inserter/sql.go b/vendor/github.com/jacobbrewer1/patcher/inserter/sql.go index 44b6960..1ea0f9d 100644 --- a/vendor/github.com/jacobbrewer1/patcher/inserter/sql.go +++ b/vendor/github.com/jacobbrewer1/patcher/inserter/sql.go @@ -62,7 +62,7 @@ func (b *SQLBatch) genBatch(resources []any) { } // Skip fields that are to be ignored - if b.checkSkipField(f) { + if b.checkSkipField(&f) { continue } @@ -79,7 +79,7 @@ func (b *SQLBatch) genBatch(resources []any) { tag = f.Name } - b.args = append(b.args, b.getFieldValue(v.Field(i), f)) + b.args = append(b.args, b.getFieldValue(v.Field(i), &f)) // if the field is not unique, skip it if _, ok := uniqueFields[tag]; ok { @@ -93,7 +93,7 @@ func (b *SQLBatch) genBatch(resources []any) { } } -func (b *SQLBatch) getFieldValue(v reflect.Value, f reflect.StructField) any { +func (b *SQLBatch) getFieldValue(v reflect.Value, f *reflect.StructField) any { if f.Type.Kind() == reflect.Ptr { if v.IsNil() { return nil @@ -104,7 +104,7 @@ func (b *SQLBatch) getFieldValue(v reflect.Value, f reflect.StructField) any { return v.Interface() } -func (b *SQLBatch) GenerateSQL() (string, []any, error) { +func (b *SQLBatch) GenerateSQL() (sqlStr string, args []any, err error) { if err := b.validateSQLGen(); err != nil { return "", nil, err } @@ -145,7 +145,7 @@ func (b *SQLBatch) Perform() (sql.Result, error) { return b.db.Exec(sqlStr, args...) } -func (b *SQLBatch) checkSkipField(field reflect.StructField) bool { +func (b *SQLBatch) checkSkipField(field *reflect.StructField) bool { // The ignore fields tag takes precedence over the ignore fields list if b.checkSkipTag(field) { return true @@ -159,7 +159,7 @@ func (b *SQLBatch) checkSkipField(field reflect.StructField) bool { return b.ignoredFieldsCheck(field) } -func (b *SQLBatch) checkSkipTag(field reflect.StructField) bool { +func (b *SQLBatch) checkSkipTag(field *reflect.StructField) bool { val, ok := field.Tag.Lookup(patcher.TagOptsName) if !ok { return false @@ -169,7 +169,7 @@ func (b *SQLBatch) checkSkipTag(field reflect.StructField) bool { return slices.Contains(tags, patcher.TagOptSkip) } -func (b *SQLBatch) checkPrimaryKey(field reflect.StructField) bool { +func (b *SQLBatch) checkPrimaryKey(field *reflect.StructField) bool { // If we are including the primary key, we can immediately return false if b.includePrimaryKey { return false @@ -184,14 +184,14 @@ func (b *SQLBatch) checkPrimaryKey(field reflect.StructField) bool { return slices.Contains(tags, patcher.DBTagPrimaryKey) } -func (b *SQLBatch) ignoredFieldsCheck(field reflect.StructField) bool { - return b.checkIgnoredFields(strings.ToLower(field.Name)) || b.checkIgnoreFunc(field) +func (b *SQLBatch) ignoredFieldsCheck(field *reflect.StructField) bool { + return b.checkIgnoredFields(field.Name) || b.checkIgnoreFunc(field) } -func (b *SQLBatch) checkIgnoreFunc(field reflect.StructField) bool { +func (b *SQLBatch) checkIgnoreFunc(field *reflect.StructField) bool { return b.ignoreFieldsFunc != nil && b.ignoreFieldsFunc(field) } func (b *SQLBatch) checkIgnoredFields(field string) bool { - return len(b.ignoreFields) > 0 && slices.Contains(b.ignoreFields, strings.ToLower(field)) + return len(b.ignoreFields) > 0 && slices.Contains(b.ignoreFields, field) } diff --git a/vendor/github.com/jacobbrewer1/patcher/joiner.go b/vendor/github.com/jacobbrewer1/patcher/joiner.go index 5e3949c..149c5e7 100644 --- a/vendor/github.com/jacobbrewer1/patcher/joiner.go +++ b/vendor/github.com/jacobbrewer1/patcher/joiner.go @@ -25,6 +25,6 @@ type joinStringOption struct { args []any } -func (j *joinStringOption) Join() (string, []any) { +func (j *joinStringOption) Join() (sqlStr string, args []any) { return j.join, j.args } diff --git a/vendor/github.com/jacobbrewer1/patcher/loader.go b/vendor/github.com/jacobbrewer1/patcher/loader.go index cfc7e45..10fa4f3 100644 --- a/vendor/github.com/jacobbrewer1/patcher/loader.go +++ b/vendor/github.com/jacobbrewer1/patcher/loader.go @@ -3,8 +3,6 @@ package patcher import ( "errors" "reflect" - "slices" - "strings" ) var ( @@ -20,7 +18,7 @@ var ( // // This function is useful if you are inserting a patch into an existing object but require a new object to be returned with // all fields updated. -func LoadDiff[T any](old *T, newT *T, opts ...PatchOpt) error { +func LoadDiff[T any](old, newT *T, opts ...PatchOpt) error { return newPatchDefaults(opts...).loadDiff(old, newT) } @@ -36,7 +34,7 @@ func (s *SQLPatch) loadDiff(old, newT any) error { oElem := reflect.ValueOf(old).Elem() nElem := reflect.ValueOf(newT).Elem() - for i := 0; i < oElem.NumField(); i++ { + for i := range oElem.NumField() { oField := oElem.Field(i) nField := nElem.Field(i) @@ -45,22 +43,11 @@ func (s *SQLPatch) loadDiff(old, newT any) error { continue } - // Handle embedded structs (Anonymous fields) - if oElem.Type().Field(i).Anonymous { - // If the embedded field is a pointer, dereference it - if oField.Kind() == reflect.Ptr { - if !oField.IsNil() && !nField.IsNil() { // If both are not nil, we need to recursively call LoadDiff - if err := s.loadDiff(oField.Interface(), nField.Interface()); err != nil { - return err - } - } else if nElem.Field(i).IsValid() && !nField.IsNil() { - oField.Set(nField) - } - - continue - } + oldField := oElem.Type().Field(i) - if err := s.loadDiff(oField.Addr().Interface(), nField.Addr().Interface()); err != nil { + // Handle embedded structs (Anonymous fields) + if oldField.Anonymous { + if err := s.handleEmbeddedStruct(oField, nField); err != nil { return err } continue @@ -75,52 +62,35 @@ func (s *SQLPatch) loadDiff(old, newT any) error { } // See if the field should be ignored. - if s.checkSkipField(oElem.Type().Field(i)) { + if s.checkSkipField(&oldField) { continue } - patcherOptsTag := oElem.Type().Field(i).Tag.Get(TagOptsName) + patcherOptsTag := oldField.Tag.Get(TagOptsName) // Compare the old and new fields. // // New fields take priority over old fields if they are provided based on the configuration. - if nElem.Field(i).Kind() != reflect.Ptr && (!nField.IsZero() || s.shouldIncludeZero(patcherOptsTag)) { - oElem.Field(i).Set(nElem.Field(i)) - } else if nElem.Field(i).Kind() == reflect.Ptr && (!nField.IsNil() || s.shouldIncludeNil(patcherOptsTag)) { - oField.Set(nElem.Field(i)) + if nField.Kind() != reflect.Ptr && (!nField.IsZero() || s.shouldIncludeZero(patcherOptsTag)) { + oField.Set(nField) + } else if nField.Kind() == reflect.Ptr && (!nField.IsNil() || s.shouldIncludeNil(patcherOptsTag)) { + oField.Set(nField) } } return nil } -func (s *SQLPatch) checkSkipField(field reflect.StructField) bool { - // The ignore fields tag takes precedence over the ignore fields list - if s.checkSkipTag(field) { - return true +func (s *SQLPatch) handleEmbeddedStruct(oField, nField reflect.Value) error { + if oField.Kind() != reflect.Ptr { + return s.loadDiff(oField.Addr().Interface(), nField.Addr().Interface()) } - return s.ignoredFieldsCheck(field) -} - -func (s *SQLPatch) checkSkipTag(field reflect.StructField) bool { - val, ok := field.Tag.Lookup(TagOptsName) - if !ok { - return false + if !oField.IsNil() && !nField.IsNil() { + return s.loadDiff(oField.Interface(), nField.Interface()) + } else if nField.IsValid() && !nField.IsNil() { + oField.Set(nField) } - tags := strings.Split(val, TagOptSeparator) - return slices.Contains(tags, TagOptSkip) -} - -func (s *SQLPatch) ignoredFieldsCheck(field reflect.StructField) bool { - return s.checkIgnoredFields(field.Name) || s.checkIgnoreFunc(field) -} - -func (s *SQLPatch) checkIgnoreFunc(field reflect.StructField) bool { - return s.ignoreFieldsFunc != nil && s.ignoreFieldsFunc(field) -} - -func (s *SQLPatch) checkIgnoredFields(field string) bool { - return len(s.ignoreFields) > 0 && slices.Contains(s.ignoreFields, field) + return nil } diff --git a/vendor/github.com/jacobbrewer1/patcher/mock_IgnoreFieldsFunc.go b/vendor/github.com/jacobbrewer1/patcher/mock_IgnoreFieldsFunc.go index 7781a40..4bbfb25 100644 --- a/vendor/github.com/jacobbrewer1/patcher/mock_IgnoreFieldsFunc.go +++ b/vendor/github.com/jacobbrewer1/patcher/mock_IgnoreFieldsFunc.go @@ -14,7 +14,7 @@ type MockIgnoreFieldsFunc struct { } // Execute provides a mock function with given fields: field -func (_m *MockIgnoreFieldsFunc) Execute(field reflect.StructField) bool { +func (_m *MockIgnoreFieldsFunc) Execute(field *reflect.StructField) bool { ret := _m.Called(field) if len(ret) == 0 { @@ -22,7 +22,7 @@ func (_m *MockIgnoreFieldsFunc) Execute(field reflect.StructField) bool { } var r0 bool - if rf, ok := ret.Get(0).(func(reflect.StructField) bool); ok { + if rf, ok := ret.Get(0).(func(*reflect.StructField) bool); ok { r0 = rf(field) } else { r0 = ret.Get(0).(bool) diff --git a/vendor/github.com/jacobbrewer1/patcher/mock_MultiFilter.go b/vendor/github.com/jacobbrewer1/patcher/mock_MultiFilter.go index 9575980..79993ba 100644 --- a/vendor/github.com/jacobbrewer1/patcher/mock_MultiFilter.go +++ b/vendor/github.com/jacobbrewer1/patcher/mock_MultiFilter.go @@ -10,10 +10,40 @@ type MockMultiFilter struct { } // Add provides a mock function with given fields: where -func (_m *MockMultiFilter) Add(where Wherer) { +func (_m *MockMultiFilter) Add(where interface{}) { _m.Called(where) } +// Join provides a mock function with no fields +func (_m *MockMultiFilter) Join() (string, []interface{}) { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Join") + } + + var r0 string + var r1 []interface{} + if rf, ok := ret.Get(0).(func() (string, []interface{})); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + if rf, ok := ret.Get(1).(func() []interface{}); ok { + r1 = rf() + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).([]interface{}) + } + } + + return r0, r1 +} + // Where provides a mock function with no fields func (_m *MockMultiFilter) Where() (string, []interface{}) { ret := _m.Called() diff --git a/vendor/github.com/jacobbrewer1/patcher/multifilter.go b/vendor/github.com/jacobbrewer1/patcher/multifilter.go index 1d8b595..c97824c 100644 --- a/vendor/github.com/jacobbrewer1/patcher/multifilter.go +++ b/vendor/github.com/jacobbrewer1/patcher/multifilter.go @@ -3,26 +3,41 @@ package patcher import "strings" type MultiFilter interface { + Joiner Wherer - Add(where Wherer) + Add(where any) } type multiFilter struct { + joinSql *strings.Builder + joinArgs []any whereSql *strings.Builder whereArgs []any } +func (m *multiFilter) Join() (sqlStr string, args []any) { + return m.joinSql.String(), m.joinArgs +} + +func (m *multiFilter) Where() (sqlStr string, args []any) { + return m.whereSql.String(), m.whereArgs +} + +func (m *multiFilter) Add(filter any) { + if joiner, ok := filter.(Joiner); ok { + appendJoin(joiner, m.joinSql, &m.joinArgs) + } + + if wherer, ok := filter.(Wherer); ok { + appendWhere(wherer, m.whereSql, &m.whereArgs) + } +} + func NewMultiFilter() MultiFilter { return &multiFilter{ + joinSql: new(strings.Builder), + joinArgs: nil, whereSql: new(strings.Builder), whereArgs: nil, } } - -func (m *multiFilter) Add(where Wherer) { - appendWhere(where, m.whereSql, &m.whereArgs) -} - -func (m *multiFilter) Where() (string, []any) { - return m.whereSql.String(), m.whereArgs -} diff --git a/vendor/github.com/jacobbrewer1/patcher/patch.go b/vendor/github.com/jacobbrewer1/patcher/patch.go index 82a6599..da59f55 100644 --- a/vendor/github.com/jacobbrewer1/patcher/patch.go +++ b/vendor/github.com/jacobbrewer1/patcher/patch.go @@ -25,7 +25,7 @@ var ( ErrNoWhere = errors.New("no where clause set") ) -type IgnoreFieldsFunc func(field reflect.StructField) bool +type IgnoreFieldsFunc func(field *reflect.StructField) bool type SQLPatch struct { // fields is the fields to update in the SQL statement @@ -116,34 +116,36 @@ func (s *SQLPatch) Args() []any { // validatePerformPatch validates the SQLPatch for the PerformPatch method func (s *SQLPatch) validatePerformPatch() error { - if s.db == nil { + switch { + case s.db == nil: return ErrNoDatabaseConnection - } else if s.table == "" { + case s.table == "": return ErrNoTable - } else if len(s.fields) == 0 { + case len(s.fields) == 0: return ErrNoFields - } else if len(s.args) == 0 { + case len(s.args) == 0: return ErrNoArgs - } else if s.whereSql.String() == "" { + case s.whereSql.String() == "": return ErrNoWhere + default: + return nil } - - return nil } // validateSQLGen validates the SQLPatch for the SQLGen method func (s *SQLPatch) validateSQLGen() error { - if s.table == "" { + switch { + case s.table == "": return ErrNoTable - } else if len(s.fields) == 0 { + case len(s.fields) == 0: return ErrNoFields - } else if len(s.args) == 0 { + case len(s.args) == 0: return ErrNoArgs - } else if s.whereSql.String() == "" { + case s.whereSql.String() == "": return ErrNoWhere + default: + return nil } - - return nil } // shouldIncludeNil determines whether the field should be included in the patch @@ -175,3 +177,55 @@ func (s *SQLPatch) shouldOmitEmpty(tag string) bool { return false } + +func (s *SQLPatch) shouldSkipField(fType *reflect.StructField, fVal reflect.Value) bool { + if !fType.IsExported() || !isValidType(fVal) || s.checkSkipField(fType) { + return true + } + + patcherOptsTag := fType.Tag.Get(TagOptsName) + if fVal.Kind() == reflect.Ptr && (fVal.IsNil() && !s.shouldIncludeNil(patcherOptsTag)) { + return true + } + if fVal.Kind() != reflect.Ptr && (fVal.IsZero() && !s.shouldIncludeZero(patcherOptsTag)) { + return true + } + if patcherOptsTag != "" { + patcherOpts := strings.Split(patcherOptsTag, TagOptSeparator) + if slices.Contains(patcherOpts, TagOptSkip) { + return true + } + } + return false +} + +func (s *SQLPatch) checkSkipField(field *reflect.StructField) bool { + // The ignore fields tag takes precedence over the ignore fields list + if s.checkSkipTag(field) { + return true + } + + return s.ignoredFieldsCheck(field) +} + +func (s *SQLPatch) checkSkipTag(field *reflect.StructField) bool { + val, ok := field.Tag.Lookup(TagOptsName) + if !ok { + return false + } + + tags := strings.Split(val, TagOptSeparator) + return slices.Contains(tags, TagOptSkip) +} + +func (s *SQLPatch) ignoredFieldsCheck(field *reflect.StructField) bool { + return s.checkIgnoredFields(field.Name) || s.checkIgnoreFunc(field) +} + +func (s *SQLPatch) checkIgnoreFunc(field *reflect.StructField) bool { + return s.ignoreFieldsFunc != nil && s.ignoreFieldsFunc(field) +} + +func (s *SQLPatch) checkIgnoredFields(field string) bool { + return len(s.ignoreFields) > 0 && slices.Contains(s.ignoreFields, field) +} diff --git a/vendor/github.com/jacobbrewer1/patcher/patch_opts.go b/vendor/github.com/jacobbrewer1/patcher/patch_opts.go index 8b2b8e6..a40825a 100644 --- a/vendor/github.com/jacobbrewer1/patcher/patch_opts.go +++ b/vendor/github.com/jacobbrewer1/patcher/patch_opts.go @@ -92,18 +92,18 @@ func WithDB(db *sql.DB) PatchOpt { // WithIncludeZeroValues sets whether zero values should be included in the patch. // // This is useful when you want to set a field to zero. -func WithIncludeZeroValues() PatchOpt { +func WithIncludeZeroValues(includeZeroValues bool) PatchOpt { return func(s *SQLPatch) { - s.includeZeroValues = true + s.includeZeroValues = includeZeroValues } } // WithIncludeNilValues sets whether nil values should be included in the patch. // // This is useful when you want to set a field to nil. -func WithIncludeNilValues() PatchOpt { +func WithIncludeNilValues(includeNilValues bool) PatchOpt { return func(s *SQLPatch) { - s.includeNilValues = true + s.includeNilValues = includeNilValues } } diff --git a/vendor/github.com/jacobbrewer1/patcher/sql.go b/vendor/github.com/jacobbrewer1/patcher/sql.go index 95caaee..be62057 100644 --- a/vendor/github.com/jacobbrewer1/patcher/sql.go +++ b/vendor/github.com/jacobbrewer1/patcher/sql.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "reflect" - "slices" "strings" ) @@ -32,16 +31,8 @@ func NewSQLPatch(resource any, opts ...PatchOpt) *SQLPatch { // It processes the fields of the struct, applying the necessary tags and options, // and prepares the SQL update statement components (fields and arguments). func (s *SQLPatch) patchGen(resource any) { - // If the resource is a pointer, we need to dereference it to get the value - if reflect.TypeOf(resource).Kind() == reflect.Ptr { - resource = reflect.ValueOf(resource).Elem().Interface() - } - - // Ensure that the resource is a struct - if reflect.TypeOf(resource).Kind() != reflect.Struct { - // This is intentionally a panic as this is a programming error and should be fixed by the developer - panic("resource is not a struct") - } + resource = dereferenceIfPointer(resource) + ensureStruct(resource) rType := reflect.TypeOf(resource) rVal := reflect.ValueOf(resource) @@ -50,91 +41,34 @@ func (s *SQLPatch) patchGen(resource any) { s.fields = make([]string, 0, n) s.args = make([]any, 0, n) - for i := 0; i < n; i++ { + for i := range n { fType := rType.Field(i) fVal := rVal.Field(i) - tag := fType.Tag.Get(s.tagName) - - // Skip unexported fields - if !fType.IsExported() { - continue - } + tag := getTag(&fType, s.tagName) + optsTag := fType.Tag.Get(TagOptsName) - tags := strings.Split(tag, TagOptSeparator) - if len(tags) > 1 { - tag = tags[0] - } - - patcherOptsTag := fType.Tag.Get(TagOptsName) - - // Skip fields that are to be ignored - if s.checkSkipField(fType) { - continue - } else if fVal.Kind() == reflect.Ptr && (fVal.IsNil() && !s.shouldIncludeNil(patcherOptsTag)) { - continue - } else if fVal.Kind() != reflect.Ptr && (fVal.IsZero() && !s.shouldIncludeZero(patcherOptsTag)) { + if s.shouldSkipField(&fType, fVal) { continue } - if patcherOptsTag != "" { - patcherOpts := strings.Split(patcherOptsTag, TagOptSeparator) - if slices.Contains(patcherOpts, TagOptSkip) { + var arg any = nil + if fVal.Kind() == reflect.Ptr && fVal.IsNil() { + if !s.shouldIncludeNil(optsTag) { continue } + } else { + arg = getValue(fVal) } - // If no tag is set, use the field name - if tag == "" { - tag = fType.Name - } - - addField := func() { - s.fields = append(s.fields, tag+" = ?") - } - - if fVal.Kind() == reflect.Ptr && fVal.IsNil() && s.shouldIncludeNil(patcherOptsTag) { - s.args = append(s.args, nil) - addField() - continue - } else if fVal.Kind() == reflect.Ptr && fVal.IsNil() { - continue - } - - addField() - - val := fVal - if fVal.Kind() == reflect.Ptr { - val = fVal.Elem() - } - - switch val.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - s.args = append(s.args, val.Int()) - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - s.args = append(s.args, val.Uint()) - case reflect.Float32, reflect.Float64: - s.args = append(s.args, val.Float()) - case reflect.Complex64, reflect.Complex128: - s.args = append(s.args, val.Complex()) - case reflect.String: - s.args = append(s.args, val.String()) - case reflect.Bool: - boolArg := 0 - if val.Bool() { - boolArg = 1 - } - s.args = append(s.args, boolArg) - default: - // This is intentionally a panic as this is a programming error and should be fixed by the developer - panic(fmt.Sprintf("unsupported type: %s", val.Kind())) - } + s.fields = append(s.fields, tag+" = ?") + s.args = append(s.args, arg) } } // GenerateSQL generates the SQL update statement and its arguments for the given resource. // It creates a new SQLPatch instance with the provided options, processes the resource's fields, // and constructs the SQL update statement along with the necessary arguments. -func GenerateSQL(resource any, opts ...PatchOpt) (string, []any, error) { +func GenerateSQL(resource any, opts ...PatchOpt) (sqlStr string, args []any, err error) { return NewSQLPatch(resource, opts...).GenerateSQL() } @@ -142,7 +76,7 @@ func GenerateSQL(resource any, opts ...PatchOpt) (string, []any, error) { // It validates the SQL generation process, builds the SQL update statement // with the table name, join clauses, set clauses, and where clauses, // and returns the final SQL string along with the arguments. -func (s *SQLPatch) GenerateSQL() (string, []any, error) { +func (s *SQLPatch) GenerateSQL() (sqlStr string, args []any, err error) { if err := s.validateSQLGen(); err != nil { return "", nil, fmt.Errorf("validate SQL generation: %w", err) } @@ -174,10 +108,11 @@ func (s *SQLPatch) GenerateSQL() (string, []any, error) { sqlBuilder.WriteString(strings.TrimSpace(where) + "\n") sqlBuilder.WriteString(")") - args := append(s.joinArgs, s.args...) - args = append(args, s.whereArgs...) + sqlArgs := s.joinArgs + sqlArgs = append(sqlArgs, s.args...) + sqlArgs = append(sqlArgs, s.whereArgs...) - return sqlBuilder.String(), args, nil + return sqlBuilder.String(), sqlArgs, nil } // PerformPatch executes the SQL update statement for the given resource. diff --git a/vendor/github.com/jacobbrewer1/patcher/utils.go b/vendor/github.com/jacobbrewer1/patcher/utils.go index df9f6be..0c2524b 100644 --- a/vendor/github.com/jacobbrewer1/patcher/utils.go +++ b/vendor/github.com/jacobbrewer1/patcher/utils.go @@ -1,8 +1,8 @@ package patcher import ( - "errors" "reflect" + "strings" ) // ptr returns a pointer to the value passed in. @@ -19,21 +19,48 @@ func isPointerToStruct[T any](t T) bool { return rv.Elem().Kind() == reflect.Struct } -// IgnoreNoChangesErr ignores the ErrNoChanges error. This is useful when you want to ignore the error when no changes -// were made. Please ensure that you are still handling the errors as needed. We will return a "nil" patch when there -// are no changes as the ErrNoChanges error is returned. -// -// Example: -// -// err := report.Patch(db, newReport) -// if patcher.IgnoreNoChangesErr(err) != nil { -// return err -// } -func IgnoreNoChangesErr(err error) error { - switch { - case errors.Is(err, ErrNoChanges): - return nil +func dereferenceIfPointer(resource any) any { + if reflect.TypeOf(resource).Kind() == reflect.Ptr { + return reflect.ValueOf(resource).Elem().Interface() + } + return resource +} + +func ensureStruct(resource any) { + if reflect.TypeOf(resource).Kind() != reflect.Struct { + // Intentionally panic here as this should never happen. This is a programming error. + panic("resource is not a struct") + } +} + +func getTag(fType *reflect.StructField, tagName string) string { + tag := fType.Tag.Get(tagName) + if tag == "" { + tag = strings.ToLower(fType.Name) + } + + tags := strings.Split(tag, TagOptSeparator) + if len(tags) > 1 { + return tags[0] + } + return tag +} + +func getValue(fVal reflect.Value) any { + if fVal.Kind() == reflect.Ptr { + return fVal.Elem().Interface() + } + return fVal.Interface() +} + +// isValidType checks if the given value is of a type that can be stored as a database field. +func isValidType(val reflect.Value) bool { + switch val.Kind() { + case reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, + reflect.Float32, reflect.Float64, reflect.String, reflect.Struct, reflect.Ptr: + return true default: - return err + return false } } diff --git a/vendor/github.com/jacobbrewer1/patcher/wherer.go b/vendor/github.com/jacobbrewer1/patcher/wherer.go index 207f14f..4679ed8 100644 --- a/vendor/github.com/jacobbrewer1/patcher/wherer.go +++ b/vendor/github.com/jacobbrewer1/patcher/wherer.go @@ -55,6 +55,6 @@ type whereStringOption struct { args []any } -func (w *whereStringOption) Where() (string, []any) { +func (w *whereStringOption) Where() (sqlStr string, args []any) { return w.where, w.args } diff --git a/vendor/modules.txt b/vendor/modules.txt index f407b46..2acb7f2 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -108,7 +108,7 @@ github.com/huandu/xstrings # github.com/imdario/mergo v0.3.11 ## explicit; go 1.13 github.com/imdario/mergo -# github.com/jacobbrewer1/patcher v0.1.17 +# github.com/jacobbrewer1/patcher v0.1.19 ## explicit; go 1.23 github.com/jacobbrewer1/patcher github.com/jacobbrewer1/patcher/inserter