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

     1  package sqlb
     2  
     3  import (
     4  	r "reflect"
     5  )
     6  
     7  const (
     8  	TagNameDb   = `db`
     9  	TagNameJson = `json`
    10  )
    11  
    12  /*
    13  Encodes the provided expressions and returns the resulting text and args.
    14  Shortcut for using `(*Bui).Exprs` and `Bui.Reify`. Provided mostly for
    15  examples. Actual code may want to use `Bui`:
    16  
    17  	bui := MakeBui(4096, 64)
    18  	panic(bui.CatchExprs(someExprs...))
    19  	text, args := bui.Reify()
    20  */
    21  func Reify(vals ...Expr) (string, []any) {
    22  	var bui Bui
    23  	bui.Exprs(vals...)
    24  	return bui.Reify()
    25  }
    26  
    27  /*
    28  Returns the output of `Cols` for the given type, but takes `reflect.Type` as
    29  input, rather than a type-carrying `any`. Used internally by `Cols`.
    30  The result is cached and reused. Subsequent calls for the same type are nearly
    31  free.
    32  */
    33  func TypeCols(typ r.Type) string {
    34  	return colsCache.Get(typeElem(typ))
    35  }
    36  
    37  /*
    38  Returns the output of `ColsDeep` for the given type, but takes `reflect.Type` as
    39  input, rather than a type-carrying `any`. Used internally by
    40  `ColsDeep`. The result is cached and reused. Subsequent calls for the same type
    41  are nearly free.
    42  */
    43  func TypeColsDeep(typ r.Type) string {
    44  	return colsDeepCache.Get(typeElem(typ))
    45  }
    46  
    47  /*
    48  Returns a parsed `Prep` for the given source string. Panics if parsing fails.
    49  Caches the result for each source string, reusing it for future calls. Used
    50  internally by `StrQ`. User code shouldn't have to call this, but it's exported
    51  just in case.
    52  */
    53  func Preparse(val string) Prep { return prepCache.Get(val) }
    54  
    55  // Shortcut for `StrQ{text, List(args)}`.
    56  func ListQ(text string, args ...any) StrQ {
    57  	if len(args) == 0 {
    58  		return StrQ{text, nil}
    59  	}
    60  	return StrQ{text, List(args)}
    61  }
    62  
    63  // Shortcut for `StrQ{text, Dict(args)}`.
    64  func DictQ(text string, args map[string]any) StrQ {
    65  	if len(args) == 0 {
    66  		return StrQ{text, nil}
    67  	}
    68  	return StrQ{text, Dict(args)}
    69  }
    70  
    71  // Shortcut for `StrQ{text, StructDict{reflect.ValueOf(args)}}`.
    72  func StructQ(text string, args any) StrQ {
    73  	val := valueOf(args)
    74  	if !val.IsValid() {
    75  		return StrQ{text, nil}
    76  	}
    77  	return StrQ{text, StructDict{val}}
    78  }
    79  
    80  // Returns the field's DB column name from the "db" tag, following the JSON
    81  // convention of eliding anything after a comma and treating "-" as a
    82  // non-name.
    83  func FieldDbName(field r.StructField) string {
    84  	return tagIdent(field.Tag.Get(TagNameDb))
    85  }
    86  
    87  // Returns the field's JSON column name from the "json" tag, following the same
    88  // conventions as the `encoding/json` package.
    89  func FieldJsonName(field r.StructField) string {
    90  	return tagIdent(field.Tag.Get(TagNameJson))
    91  }
    92  
    93  const (
    94  	DirNone Dir = 0
    95  	DirAsc  Dir = 1
    96  	DirDesc Dir = 2
    97  )
    98  
    99  // Short for "direction". Enum for ordering direction: none, "asc", "desc".
   100  type Dir byte
   101  
   102  // Implement the `AppenderTo` interface, sometimes allowing more efficient text
   103  // encoding.
   104  func (self Dir) AppendTo(text []byte) []byte {
   105  	return appendMaybeSpaced(text, self.String())
   106  }
   107  
   108  // Implement `fmt.Stringer` for debug purposes.
   109  func (self Dir) String() string {
   110  	switch self {
   111  	default:
   112  		return ``
   113  	case DirAsc:
   114  		return `asc`
   115  	case DirDesc:
   116  		return `desc`
   117  	}
   118  }
   119  
   120  // Parses from a string, which must be either empty, "asc" or "desc".
   121  func (self *Dir) Parse(src string) error {
   122  	switch src {
   123  	case ``:
   124  		*self = DirNone
   125  		return nil
   126  	case `asc`:
   127  		*self = DirAsc
   128  		return nil
   129  	case `desc`:
   130  		*self = DirDesc
   131  		return nil
   132  	default:
   133  		return ErrInvalidInput{Err{
   134  			`parsing order direction`,
   135  			errf(`unrecognized direction %q`, src),
   136  		}}
   137  	}
   138  }
   139  
   140  // Implement `encoding.TextUnmarshaler`.
   141  func (self Dir) MarshalText() ([]byte, error) {
   142  	return stringToBytesUnsafe(self.String()), nil
   143  }
   144  
   145  // Implement `encoding.TextMarshaler`.
   146  func (self *Dir) UnmarshalText(src []byte) error {
   147  	return self.Parse(bytesToMutableString(src))
   148  }
   149  
   150  // Implement `json.Marshaler`.
   151  func (self Dir) MarshalJSON() ([]byte, error) {
   152  	switch self {
   153  	default:
   154  		return stringToBytesUnsafe(`null`), nil
   155  	case DirAsc:
   156  		return stringToBytesUnsafe(`"asc"`), nil
   157  	case DirDesc:
   158  		return stringToBytesUnsafe(`"desc"`), nil
   159  	}
   160  }
   161  
   162  // Implement `fmt.GoStringer` for debug purposes. Returns valid Go code
   163  // representing this value.
   164  func (self Dir) GoString() string {
   165  	switch self {
   166  	default:
   167  		return `sqlb.DirNone`
   168  	case DirAsc:
   169  		return `sqlb.DirAsc`
   170  	case DirDesc:
   171  		return `sqlb.DirDesc`
   172  	}
   173  }
   174  
   175  const (
   176  	NullsNone  Nulls = 0
   177  	NullsFirst Nulls = 1
   178  	NullsLast  Nulls = 2
   179  )
   180  
   181  // Enum for nulls handling in ordering: none, "nulls first", "nulls last".
   182  type Nulls byte
   183  
   184  // Implement the `AppenderTo` interface, sometimes allowing more efficient text
   185  // encoding.
   186  func (self Nulls) AppendTo(text []byte) []byte {
   187  	return appendMaybeSpaced(text, self.String())
   188  }
   189  
   190  // Implement `fmt.Stringer` for debug purposes.
   191  func (self Nulls) String() string {
   192  	switch self {
   193  	case NullsFirst:
   194  		return `nulls first`
   195  	case NullsLast:
   196  		return `nulls last`
   197  	default:
   198  		return ``
   199  	}
   200  }
   201  
   202  // Implement `fmt.GoStringer` for debug purposes. Returns valid Go code
   203  // representing this value.
   204  func (self Nulls) GoString() string {
   205  	switch self {
   206  	case NullsFirst:
   207  		return `sqlb.NullsFirst`
   208  	case NullsLast:
   209  		return `sqlb.NullsLast`
   210  	default:
   211  		return `sqlb.NullsNone`
   212  	}
   213  }
   214  
   215  /*
   216  Implements `Sparse` by filtering fields on their JSON names, using only
   217  explicit "json" tags. Fields without explicit "json" names are automatically
   218  considered missing. Fields with "json" tags must be present in the provided
   219  string set represented by `.Fil`.
   220  
   221  Designed for compatibility with HTTP request decoders provided
   222  by "github.com/mitranim/rd", which either implement `Haser` or can easily
   223  generate one. Example PATCH endpoint using "rd":
   224  
   225  	import "github.com/mitranim/rd"
   226  	import "github.com/mitranim/try"
   227  	import s "github.com/mitranim/sqlb"
   228  
   229  	dec := rd.TryDownload(req)
   230  
   231  	var input SomeStructType
   232  	try.To(dec.Decode(&input))
   233  
   234  	expr := s.Exprs{
   235  		s.Update{s.Ident(`some_table`)},
   236  		s.Set{s.StructAssign{s.Partial{input, dec.Haser()}}},
   237  	}
   238  */
   239  type Partial struct {
   240  	Val any
   241  	Fil Haser
   242  }
   243  
   244  var _ = Sparse(Partial{})
   245  
   246  // Implement `Sparse`, returning the underlying value.
   247  func (self Partial) Get() any { return self.Val }
   248  
   249  // Implement `Sparse`, using the underlying filter.
   250  func (self Partial) AllowField(field r.StructField) bool {
   251  	name := FieldJsonName(field)
   252  	return name != `` && self.Fil != nil && self.Fil.Has(name)
   253  }
   254  
   255  /*
   256  Implements `Filter` by requiring that the struct field has this specific tag.
   257  The tag's value for any given field is ignored, only its existence is checked.
   258  */
   259  type TagFilter string
   260  
   261  func (self TagFilter) AllowField(field r.StructField) bool {
   262  	_, ok := field.Tag.Lookup(string(self))
   263  	return ok
   264  }