Skip to content

Commit

Permalink
add LaxDict
Browse files Browse the repository at this point in the history
Additional cosmetic change: avoid single-letter variable names.
  • Loading branch information
mitranim committed Feb 15, 2023
1 parent d66f3ef commit adfeca5
Show file tree
Hide file tree
Showing 8 changed files with 98 additions and 51 deletions.
4 changes: 4 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ func Example_composition() {

## Changelog

### v0.6.8

Added `LaxDict`: dictionary of named arguments similar to `Dict`, but without support for validating unused arguments.

### v0.6.7

Fixed an edge case bug in `Upsert`.
Expand Down
4 changes: 2 additions & 2 deletions sqlb_array.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,8 @@ func (self SliceCommaAppender) Append(buf []byte) []byte {
}

var found bool
for i := range counter(src.Len()) {
elem := src.Index(i)
for ind := range counter(src.Len()) {
elem := src.Index(ind)
if !elem.IsValid() {
continue
}
Expand Down
25 changes: 23 additions & 2 deletions sqlb_dict.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ func (self List) GotNamed(string) (any, bool) { return nil, false }
// Implement `OrdinalRanger` to automatically validate used/unused arguments.
func (self List) RangeOrdinal(fun func(int)) {
if fun != nil {
for i := range counter(len(self)) {
fun(i)
for ind := range counter(len(self)) {
fun(ind)
}
}
}
Expand Down Expand Up @@ -66,6 +66,27 @@ func (self Dict) RangeNamed(fun func(string)) {
}
}

/*
Variant of `Dict` without support for validating unused arguments. Note that
missing arguments are still detected and cause errors. Useful when generating
the dictionary dynamically, rather than hardcoding the set of keys. Must be
used with `StrQ` or `Prep`, rather than with `DictQ`, because the latter always
converts the given dictionary to `Dict`.
*/
type LaxDict Dict

// Implement part of the `ArgDict` interface.
func (self LaxDict) IsEmpty() bool { return Dict(self).IsEmpty() }

// Implement part of the `ArgDict` interface.
func (self LaxDict) Len() int { return Dict(self).Len() }

// Implement part of the `ArgDict` interface. Always returns `nil, false`.
func (self LaxDict) GotOrdinal(int) (any, bool) { return nil, false }

// Implement part of the `ArgDict` interface.
func (self LaxDict) GotNamed(key string) (any, bool) { return Dict(self).GotNamed(key) }

/*
Implements `ArgDict` by reading struct fields and methods by name. Supports only
named parameters, not ordinal parameters. The inner value must be either
Expand Down
14 changes: 7 additions & 7 deletions sqlb_expr.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@ func (self Identifier) Append(text []byte) []byte {
if len(self) == 0 {
return text
}
for i, val := range self {
if i > 0 {
for ind, val := range self {
if ind > 0 {
text = append(text, `.`...)
}
text = Ident(val).Append(text)
Expand Down Expand Up @@ -172,9 +172,9 @@ func (self PseudoPath) Append(text []byte) []byte {
text = maybeAppendSpace(text)
text = append(text, quoteDouble)

for i, val := range self {
for ind, val := range self {
validateIdent(val)
if i > 0 {
if ind > 0 {
text = append(text, `.`...)
}
text = append(text, val...)
Expand Down Expand Up @@ -546,11 +546,11 @@ func (self Seq) appendSlice(bui *Bui, src any) {
return
}

for i := range counter(val.Len()) {
if i > 0 {
for ind := range counter(val.Len()) {
if ind > 0 {
bui.Str(self.Delim)
}
bui.SubAny(val.Index(i).Interface())
bui.SubAny(val.Index(ind).Interface())
}
}

Expand Down
8 changes: 4 additions & 4 deletions sqlb_jel.go
Original file line number Diff line number Diff line change
Expand Up @@ -311,8 +311,8 @@ func (self *Jel) decodeOpInfix(bui *Bui, name string, args []json.RawMessage) {
}

bui.Str(`(`)
for i, arg := range args {
if i > 0 {
for ind, arg := range args {
if ind > 0 {
bui.Str(name)
}
self.decode(bui, arg)
Expand All @@ -323,8 +323,8 @@ func (self *Jel) decodeOpInfix(bui *Bui, name string, args []json.RawMessage) {
func (self *Jel) decodeOpFunc(bui *Bui, name string, args []json.RawMessage) {
bui.Str(name)
bui.Str(`(`)
for i, arg := range args {
if i > 0 {
for ind, arg := range args {
if ind > 0 {
bui.Str(`,`)
}
self.decode(bui, arg)
Expand Down
54 changes: 27 additions & 27 deletions sqlb_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,9 @@ func (self *charset) addStr(vals string) *charset {
}

func (self *charset) addSet(vals *charset) *charset {
for i, val := range vals {
for ind, val := range vals {
if val {
self[i] = true
self[ind] = true
}
}
return self
Expand Down Expand Up @@ -483,8 +483,8 @@ var structDbFieldsCache = cacheOf(func(typ r.Type) []r.StructField {
reqStructType(`scanning DB-related struct fields`, typ)

path := make([]int, 0, expectedStructNestingDepth)
for i := range counter(typ.NumField()) {
appendStructDbFields(&out, &path, typ, i)
for ind := range counter(typ.NumField()) {
appendStructDbFields(&out, &path, typ, ind)
}

return out
Expand All @@ -505,14 +505,14 @@ var structPathsCache = cacheOf(func(typ r.Type) []structPath {
reqStructType(`scanning struct field and method paths`, typ)

path := make([]int, 0, expectedStructNestingDepth)
for i := range counter(typ.NumField()) {
appendStructFieldPaths(&out, &path, typ, i)
for ind := range counter(typ.NumField()) {
appendStructFieldPaths(&out, &path, typ, ind)
}

for i := range counter(typ.NumMethod()) {
meth := typ.Method(i)
for ind := range counter(typ.NumMethod()) {
meth := typ.Method(ind)
if isPublic(meth.PkgPath) {
out = append(out, structPath{Name: meth.Name, MethodIndex: i})
out = append(out, structPath{Name: meth.Name, MethodIndex: ind})
}
}

Expand Down Expand Up @@ -548,8 +548,8 @@ var structJsonPathToNestedDbFieldMapCache = cacheOf(func(typ r.Type) map[string]
jsonPath := make([]string, 0, expectedStructNestingDepth)
dbPath := make([]string, 0, expectedStructNestingDepth)

for i := range counter(typ.NumField()) {
addJsonPathsToDbPaths(buf, &jsonPath, &dbPath, typ.Field(i))
for ind := range counter(typ.NumField()) {
addJsonPathsToDbPaths(buf, &jsonPath, &dbPath, typ.Field(ind))
}
return buf
})
Expand Down Expand Up @@ -587,8 +587,8 @@ func appendStructDbFields(buf *[]r.StructField, path *[]int, typ r.Type, index i

typ = typeDeref(field.Type)
if field.Anonymous && typ.Kind() == r.Struct {
for i := range counter(typ.NumField()) {
appendStructDbFields(buf, path, typ, i)
for ind := range counter(typ.NumField()) {
appendStructDbFields(buf, path, typ, ind)
}
}
}
Expand All @@ -605,8 +605,8 @@ func appendStructFieldPaths(buf *[]structPath, path *[]int, typ r.Type, index in

typ = typeDeref(field.Type)
if field.Anonymous && typ.Kind() == r.Struct {
for i := range counter(typ.NumField()) {
appendStructFieldPaths(buf, path, typ, i)
for ind := range counter(typ.NumField()) {
appendStructFieldPaths(buf, path, typ, ind)
}
}
}
Expand Down Expand Up @@ -845,8 +845,8 @@ func structCols(typ r.Type) string {
reqStructType(`generating struct columns string from struct type`, typ)

var buf []byte
for i, field := range loadStructDbFields(typ) {
if i > 0 {
for ind, field := range loadStructDbFields(typ) {
if ind > 0 {
buf = append(buf, `, `...)
}
buf = Ident(FieldDbName(field)).Append(buf)
Expand All @@ -860,8 +860,8 @@ func structColsDeep(typ r.Type) string {
var buf []byte
var path []string

for i := range counter(typ.NumField()) {
appendFieldCols(&buf, &path, typ.Field(i))
for ind := range counter(typ.NumField()) {
appendFieldCols(&buf, &path, typ.Field(ind))
}
return bytesToMutableString(buf)
}
Expand All @@ -878,8 +878,8 @@ func appendFieldCols(buf *[]byte, path *[]string, field r.StructField) {
if dbName == `` {
if !ok {
if field.Anonymous && typ.Kind() == r.Struct {
for i := range counter(typ.NumField()) {
appendFieldCols(buf, path, typ.Field(i))
for ind := range counter(typ.NumField()) {
appendFieldCols(buf, path, typ.Field(ind))
}
}
}
Expand All @@ -890,8 +890,8 @@ func appendFieldCols(buf *[]byte, path *[]string, field r.StructField) {
*path = append(*path, dbName)

if isStructType(typ) {
for i := range counter(typ.NumField()) {
appendFieldCols(buf, path, typ.Field(i))
for ind := range counter(typ.NumField()) {
appendFieldCols(buf, path, typ.Field(ind))
}
return
}
Expand Down Expand Up @@ -921,8 +921,8 @@ func addJsonPathsToDbPaths(
if dbName == `` {
if !ok {
if field.Anonymous && typ.Kind() == r.Struct {
for i := range counter(typ.NumField()) {
addJsonPathsToDbPaths(buf, jsonPath, dbPath, typ.Field(i))
for ind := range counter(typ.NumField()) {
addJsonPathsToDbPaths(buf, jsonPath, dbPath, typ.Field(ind))
}
}
}
Expand All @@ -941,8 +941,8 @@ func addJsonPathsToDbPaths(
}

if isStructType(typ) {
for i := range counter(typ.NumField()) {
addJsonPathsToDbPaths(buf, jsonPath, dbPath, typ.Field(i))
for ind := range counter(typ.NumField()) {
addJsonPathsToDbPaths(buf, jsonPath, dbPath, typ.Field(ind))
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions t_bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,8 +175,8 @@ func Benchmark_make_list(b *testing.B) {
//go:noinline
func benchMakeList() ArgDict {
list := make(List, 24)
for i := range list {
list[i] = (i + 1) * 10
for ind := range list {
list[ind] = (ind + 1) * 10
}
return list
}
Expand Down
36 changes: 29 additions & 7 deletions t_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1889,15 +1889,16 @@ func TestList(t *testing.T) {
}

func TestDict(t *testing.T) {
zero := Dict(nil)
empty := Dict{}
full := benchDict
testArgDictMap[Dict](t)

eq(t, 0, zero.Len())
eq(t, 0, empty.Len())
eq(t, 24, full.Len())
panics(t, `unused named argument ":two" (key "two")`, func() {
tryUnusedNamedArg[Dict]()
})
}

testArgDictNamed(t, zero, empty, full)
func TestLaxDict(t *testing.T) {
testArgDictMap[LaxDict](t)
tryUnusedNamedArg[LaxDict]()
}

func TestStructDict(t *testing.T) {
Expand All @@ -1912,6 +1913,27 @@ func TestStructDict(t *testing.T) {
testArgDictNamed(t, zero, empty, full)
}

type ArgDictMap interface {
ArgDict
~map[string]any
}

func testArgDictMap[Type ArgDictMap](t *testing.T) {
zero := Type(nil)
empty := Type{}
full := benchDict

eq(t, 0, zero.Len())
eq(t, 0, empty.Len())
eq(t, 24, full.Len())

testArgDictNamed(t, zero, empty, full)
}

func tryUnusedNamedArg[Type ArgDictMap]() {
StrQ{`:one`, Type{`one`: 10, `two`: 20}}.AppendExpr(nil, nil)
}

func testArgDictNamed(t testing.TB, zero, empty, full ArgDict) {
eq(t, true, zero.IsEmpty())
eq(t, true, empty.IsEmpty())
Expand Down

0 comments on commit adfeca5

Please sign in to comment.