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

     1  package sqlb
     2  
     3  import (
     4  	"encoding/json"
     5  	r "reflect"
     6  	"unsafe"
     7  )
     8  
     9  /*
    10  Options related to parsing text into `Ords`. Used by `ParserOrds` and
    11  `OrdsParser`.
    12  */
    13  type ParseOpt struct {
    14  	/**
    15  	Must be a struct type. Ords parsing uses this to detect which fields are
    16  	allowed, and to convert JSON field names to DB column names.
    17  	*/
    18  	Type r.Type
    19  
    20  	/**
    21  	Optional filter. When non-nil, this is invoked for each struct field during
    22  	ords parsing. If this returns false, the field is "unknown" and may generate
    23  	a parse error depending on `.Lax`.
    24  	*/
    25  	Filter Filter
    26  
    27  	/**
    28  	When true, unknown JSON fields are skipped/ignored durung parsing. When false,
    29  	unknown JSON fields cause ords parsing to fail with a descriptive error.
    30  	*/
    31  	Lax bool
    32  }
    33  
    34  /*
    35  If `.Type` is empty, sets the type of the provided value. Otherwise this is a
    36  nop. The input is used only as a type carrier; its actual value is ignored. The
    37  type is consulted when decoding orderings from an input such as JSON.
    38  */
    39  func (self *ParseOpt) OrType(typ any) {
    40  	if self.Type == nil {
    41  		self.Type = typeElemOf(typ)
    42  	}
    43  }
    44  
    45  /*
    46  Contains `Ords` and parsing options, and implements decoder interfaces such as
    47  `json.Unmarshaler`. Intended to be included into other structs. Unmarshals text
    48  into inner `Ords` in accordance with the parsing options.
    49  */
    50  type ParserOrds struct {
    51  	Ords
    52  	ParseOpt
    53  }
    54  
    55  /*
    56  Implement `json.Unmarshaler`. Consults `.Type` to determine known field paths,
    57  and converts them to DB column paths, rejecting unknown identifiers. The JSON
    58  input must represent an array of strings. See the method `.ParseSlice` for more
    59  docs.
    60  */
    61  func (self *ParserOrds) UnmarshalJSON(src []byte) error {
    62  	return OrdsParser{&self.Ords, self.ParseOpt}.UnmarshalJSON(src)
    63  }
    64  
    65  /*
    66  Parses a string slice which must consist of individual ordering strings such
    67  as "one.two.three desc". Ignores empty strings. Used internally for parsing
    68  JSON. String slices may also come from URL queries, form-encoded data, and so
    69  on. Supported input format:
    70  
    71  	<path> <asc|desc>? <nulls first | nulls last>?
    72  
    73  Each path can be a single identifier or dot-separated:
    74  
    75  	one
    76  	one.two
    77  	one.two.three
    78  
    79  The path MUST correspond to JSON-tagged fields in the reference struct type,
    80  which MUST have corresponding DB column names. The parsed ordering uses DB
    81  column names, rather than the original JSON names.
    82  */
    83  func (self *ParserOrds) ParseSlice(src []string) error {
    84  	return OrdsParser{&self.Ords, self.ParseOpt}.ParseSlice(src)
    85  }
    86  
    87  /*
    88  Similar to `ParserOrds`, but intended to be transient and stackframe-local,
    89  rather than included into other types. Usually obtained by calling
    90  `(*Ords).OrdsParser`.
    91  */
    92  type OrdsParser struct {
    93  	*Ords
    94  	ParseOpt
    95  }
    96  
    97  // Implement `json.Unmarshaler`. See `(*ParserOrds).UnmarshalJSON` for docs.
    98  func (self OrdsParser) UnmarshalJSON(src []byte) (err error) {
    99  	defer rec(&err)
   100  	var vals []string
   101  	try(json.Unmarshal(src, &vals))
   102  	self.noescape().parseSlice(vals)
   103  	return
   104  }
   105  
   106  // See `(*ParserOrds).ParseSlice` for docs.
   107  func (self OrdsParser) ParseSlice(src []string) (err error) {
   108  	defer rec(&err)
   109  	self.noescape().parseSlice(src)
   110  	return
   111  }
   112  
   113  func (self *OrdsParser) parseSlice(src []string) {
   114  	self.Zero()
   115  	self.Grow(countNonEmptyStrings(src))
   116  	for _, val := range src {
   117  		self.parseAppend(val)
   118  	}
   119  }
   120  
   121  func (self *OrdsParser) parseAppend(src string) {
   122  	if src == `` {
   123  		return
   124  	}
   125  
   126  	/**
   127  	This regexp-based parsing is simple to implement, but extremely inefficient,
   128  	easily trouncing all our optimizations. TODO rewrite in an efficient manner.
   129  	*/
   130  	match := ordReg.FindStringSubmatch(src)
   131  	if match == nil {
   132  		panic(errInvalidOrd(src))
   133  	}
   134  
   135  	typ := self.Type
   136  	pathStr := match[1]
   137  	entry, ok := loadStructJsonPathToDbPathFieldValueMap(typ)[pathStr]
   138  
   139  	if !ok || !self.filter(entry.Field) {
   140  		if self.Lax {
   141  			return
   142  		}
   143  		panic(errUnknownField(`converting JSON identifier path to DB path`, pathStr, typeName(typ)))
   144  	}
   145  
   146  	dir := strDir(match[2])
   147  	if dir == DirNone {
   148  		def := entry.Field.Tag.Get(`ord.dir`)
   149  		if def != `` {
   150  			dir = strDir(def)
   151  		}
   152  	}
   153  
   154  	nulls := strNulls(match[3])
   155  	if nulls == NullsNone {
   156  		def := entry.Field.Tag.Get(`ord.nulls`)
   157  		if def != `` {
   158  			nulls = strNulls(def)
   159  		}
   160  	}
   161  
   162  	path := entry.Value
   163  
   164  	/**
   165  	This weird trickery saves some allocations. If we had unwrapped the concrete
   166  	type `[]string` or `Path`, converted it to another concrete type such as
   167  	`OrdAsc`, and then converted back to an indirect `Expr`, the final
   168  	conversion would allocate an exact copy of the original slice header, even
   169  	though due to being stored behind an interface it's immutable and still
   170  	points to the same original backing array. As far as I'm concerned, that's a
   171  	language defect. This is a workaround.
   172  	*/
   173  	switch {
   174  	case dir == DirAsc && nulls == NullsFirst:
   175  		path = path.Convert(typOrdAscNullsFirst)
   176  	case dir == DirAsc && nulls == NullsLast:
   177  		path = path.Convert(typOrdAscNullsLast)
   178  	case dir == DirDesc && nulls == NullsFirst:
   179  		path = path.Convert(typOrdDescNullsFirst)
   180  	case dir == DirDesc && nulls == NullsLast:
   181  		path = path.Convert(typOrdDescNullsLast)
   182  	case dir == DirAsc:
   183  		path = path.Convert(typOrdAsc)
   184  	case dir == DirDesc:
   185  		path = path.Convert(typOrdDesc)
   186  	case nulls == NullsFirst:
   187  		path = path.Convert(typOrdNullsFirst)
   188  	case nulls == NullsLast:
   189  		path = path.Convert(typOrdNullsLast)
   190  	default:
   191  		path = path.Convert(typPath)
   192  	}
   193  
   194  	self.Add(path.Interface().(Expr))
   195  }
   196  
   197  func (self *OrdsParser) filter(field r.StructField) bool {
   198  	return self.Filter == nil || self.Filter.AllowField(field)
   199  }
   200  
   201  // Prevents a weird spurious escape that shows up in benchmarks.
   202  func (self *OrdsParser) noescape() *OrdsParser {
   203  	return (*OrdsParser)(noescape(unsafe.Pointer(self)))
   204  }
   205  
   206  var (
   207  	typOrdAsc            = r.TypeOf((*OrdAsc)(nil)).Elem()
   208  	typOrdDesc           = r.TypeOf((*OrdDesc)(nil)).Elem()
   209  	typOrdNullsFirst     = r.TypeOf((*OrdNullsFirst)(nil)).Elem()
   210  	typOrdNullsLast      = r.TypeOf((*OrdNullsLast)(nil)).Elem()
   211  	typOrdAscNullsFirst  = r.TypeOf((*OrdAscNullsFirst)(nil)).Elem()
   212  	typOrdAscNullsLast   = r.TypeOf((*OrdAscNullsLast)(nil)).Elem()
   213  	typOrdDescNullsFirst = r.TypeOf((*OrdDescNullsFirst)(nil)).Elem()
   214  	typOrdDescNullsLast  = r.TypeOf((*OrdDescNullsLast)(nil)).Elem()
   215  	typPath              = r.TypeOf((*Path)(nil)).Elem()
   216  )