github.com/mitranim/sqlb@v0.7.2/sqlb_dict.go (about)

     1  package sqlb
     2  
     3  import r "reflect"
     4  
     5  /*
     6  Variant of `[]any` conforming to the `ArgDict` interface. Supports only
     7  ordinal parameters, not named parameters. Used for `StrQ`. See the `ListQ`
     8  shortcut.
     9  */
    10  type List []any
    11  
    12  // Implement part of the `ArgDict` interface.
    13  func (self List) IsEmpty() bool { return self.Len() == 0 }
    14  
    15  // Implement part of the `ArgDict` interface.
    16  func (self List) Len() int { return len(self) }
    17  
    18  // Implement part of the `ArgDict` interface.
    19  func (self List) GotOrdinal(key int) (any, bool) {
    20  	if key >= 0 && key < len(self) {
    21  		return self[key], true
    22  	}
    23  	return nil, false
    24  }
    25  
    26  // Implement part of the `ArgDict` interface. Always returns `nil, false`.
    27  func (self List) GotNamed(string) (any, bool) { return nil, false }
    28  
    29  // Implement `OrdinalRanger` to automatically validate used/unused arguments.
    30  func (self List) RangeOrdinal(fun func(int)) {
    31  	if fun != nil {
    32  		for ind := range counter(len(self)) {
    33  			fun(ind)
    34  		}
    35  	}
    36  }
    37  
    38  /*
    39  Variant of `map[string]any` conforming to the `ArgDict` interface.
    40  Supports only named parameters, not ordinal parameters. Used for `StrQ`. See
    41  the `DictQ` shortcut.
    42  */
    43  type Dict map[string]any
    44  
    45  // Implement part of the `ArgDict` interface.
    46  func (self Dict) IsEmpty() bool { return self.Len() == 0 }
    47  
    48  // Implement part of the `ArgDict` interface.
    49  func (self Dict) Len() int { return len(self) }
    50  
    51  // Implement part of the `ArgDict` interface. Always returns `nil, false`.
    52  func (self Dict) GotOrdinal(int) (any, bool) { return nil, false }
    53  
    54  // Implement part of the `ArgDict` interface.
    55  func (self Dict) GotNamed(key string) (any, bool) {
    56  	val, ok := self[key]
    57  	return val, ok
    58  }
    59  
    60  // Implement `NamedRanger` to automatically validate used/unused arguments.
    61  func (self Dict) RangeNamed(fun func(string)) {
    62  	if fun != nil {
    63  		for key := range self {
    64  			fun(key)
    65  		}
    66  	}
    67  }
    68  
    69  /*
    70  Variant of `Dict` without support for validating unused arguments. Note that
    71  missing arguments are still detected and cause errors. Useful when generating
    72  the dictionary dynamically, rather than hardcoding the set of keys. Must be
    73  used with `StrQ` or `Prep`, rather than with `DictQ`, because the latter always
    74  converts the given dictionary to `Dict`.
    75  */
    76  type LaxDict Dict
    77  
    78  // Implement part of the `ArgDict` interface.
    79  func (self LaxDict) IsEmpty() bool { return Dict(self).IsEmpty() }
    80  
    81  // Implement part of the `ArgDict` interface.
    82  func (self LaxDict) Len() int { return Dict(self).Len() }
    83  
    84  // Implement part of the `ArgDict` interface. Always returns `nil, false`.
    85  func (self LaxDict) GotOrdinal(int) (any, bool) { return nil, false }
    86  
    87  // Implement part of the `ArgDict` interface.
    88  func (self LaxDict) GotNamed(key string) (any, bool) { return Dict(self).GotNamed(key) }
    89  
    90  /*
    91  Implements `ArgDict` by reading struct fields and methods by name. Supports only
    92  named parameters, not ordinal parameters. The inner value must be either
    93  invalid or a struct. Compared to `Dict`, a struct is way faster to construct,
    94  but reading fields by name is way slower. Used for `StrQ`. See the `StructQ`
    95  shortcut.
    96  */
    97  type StructDict [1]r.Value
    98  
    99  // Implement part of the `ArgDict` interface.
   100  func (self StructDict) IsEmpty() bool {
   101  	return !self[0].IsValid() || isStructTypeEmpty(self[0].Type())
   102  }
   103  
   104  // Implement part of the `ArgDict` interface. Always returns 0.
   105  func (self StructDict) Len() int { return 0 }
   106  
   107  // Implement part of the `ArgDict` interface. Always returns `nil, false`.
   108  func (self StructDict) GotOrdinal(int) (any, bool) { return nil, false }
   109  
   110  // Implement part of the `ArgDict` interface.
   111  func (self StructDict) GotNamed(key string) (any, bool) {
   112  	/**
   113  	(Tested in Go 1.17.)
   114  
   115  	In our benchmarks, making a struct dict is about 15 times faster than a normal
   116  	dict (100ns vs 1500ns for 12 fields and 12 methods), but accessing various
   117  	fields and methods is about 25 times slower on average (5000ns vs 200ns for
   118  	12 fields and 12 methods). When using only fields without methods, the
   119  	access time numbers are much closer (700ns vs 100ns for 12 fields). The
   120  	total numbers are close enough, and small enough, to justify both, depending
   121  	on the use case.
   122  
   123  	Compared to using `reflect.Value.FieldByName` and `reflect.Value.MethodByName`
   124  	every time, using a cached dict with field and method indexes improves
   125  	average access performance by about 3 times in our benchmarks.
   126  	*/
   127  
   128  	val := valueDeref(self[0])
   129  	if !val.IsValid() {
   130  		return nil, false
   131  	}
   132  
   133  	path, ok := loadStructPathMap(val.Type())[key]
   134  	if !ok {
   135  		return nil, false
   136  	}
   137  
   138  	if path.FieldIndex != nil {
   139  		return val.FieldByIndex(path.FieldIndex).Interface(), true
   140  	}
   141  
   142  	meth := val.Method(path.MethodIndex)
   143  	if meth.IsValid() {
   144  		reqGetter(val.Type(), meth.Type(), key)
   145  		return meth.Call(nil)[0].Interface(), true
   146  	}
   147  
   148  	return nil, false
   149  }