github.com/mitranim/gg@v0.1.17/gsql/gsql_arr.go (about)

     1  package gsql
     2  
     3  import (
     4  	"database/sql/driver"
     5  
     6  	"github.com/mitranim/gg"
     7  )
     8  
     9  /*
    10  Shortcut for converting to `Arr`. Workaround for the lack of type inference in
    11  type literals and casts. This is a free cast with no reallocation.
    12  */
    13  func ToArr[A any](val []A) Arr[A] { return val }
    14  
    15  // Shortcut for creating `Arr` from the arguments.
    16  func ArrOf[A any](val ...A) Arr[A] { return val }
    17  
    18  /*
    19  Short for "array". A slice type that supports SQL array encoding and decoding,
    20  using the `{}` format. Examples:
    21  
    22  	Arr[int]{10, 20}                  <-> '{10,20}'
    23  	Arr[Arr[int]]{{10, 20}, {30, 40}} <-> '{{10,20},{30,40}}'
    24  */
    25  type Arr[A any] []A
    26  
    27  var (
    28  	_ = gg.Encoder(gg.Zero[Arr[any]]())
    29  	_ = gg.Decoder(gg.Zero[*Arr[any]]())
    30  )
    31  
    32  // Implement `gg.Nullable`. True if the slice is nil.
    33  func (self Arr[A]) IsNull() bool { return self == nil }
    34  
    35  // Implement `fmt.Stringer`. Returns an SQL encoding of the array.
    36  func (self Arr[A]) String() string { return gg.AppenderString(self) }
    37  
    38  /*
    39  Implement `AppenderTo`, appending the array's SQL encoding to the buffer.
    40  If the slice is nil, appends nothing.
    41  */
    42  func (self Arr[A]) AppendTo(buf []byte) []byte {
    43  	if self != nil {
    44  		buf = append(buf, '{')
    45  		buf = self.AppendInner(buf)
    46  		buf = append(buf, '}')
    47  	}
    48  	return buf
    49  }
    50  
    51  // Same as `.AppenderTo` but without the enclosing `{}`.
    52  func (self Arr[A]) AppendInner(buf []byte) []byte {
    53  	var found bool
    54  	for _, val := range self {
    55  		if found {
    56  			buf = append(buf, ',')
    57  		}
    58  		found = true
    59  		buf = gg.AppendTo(buf, val)
    60  	}
    61  	return buf
    62  }
    63  
    64  // Decodes from an SQL array literal string. Supports nested arrays.
    65  func (self *Arr[A]) Parse(src string) (err error) {
    66  	defer gg.Rec(&err)
    67  	defer gg.Detailf(`unable to decode %q into %T`, src, self)
    68  
    69  	self.Clear()
    70  
    71  	if len(src) <= 0 {
    72  		return nil
    73  	}
    74  
    75  	if src == `{}` {
    76  		if *self == nil {
    77  			*self = Arr[A]{}
    78  		}
    79  		return nil
    80  	}
    81  
    82  	if !(gg.TextHeadByte(src) == '{' && gg.TextLastByte(src) == '}') {
    83  		panic(gg.ErrInvalidInput)
    84  	}
    85  	src = src[1 : len(src)-1]
    86  
    87  	for len(src) > 0 {
    88  		gg.Append(self, gg.ParseTo[A](popSqlArrSegment(&src)))
    89  	}
    90  	return nil
    91  }
    92  
    93  // Truncates the length, keeping the capacity.
    94  func (self *Arr[A]) Clear() { gg.Trunc(self) }
    95  
    96  // Implement `driver.Valuer`.
    97  func (self Arr[A]) Value() (driver.Value, error) {
    98  	if self.IsNull() {
    99  		return nil, nil
   100  	}
   101  	return self.String(), nil
   102  }
   103  
   104  // Implement `sql.Scanner`.
   105  func (self *Arr[A]) Scan(src any) error {
   106  	// Known inefficiency: when the source is `[]byte`, this may allocate, which
   107  	// is wasted when the output is transient, but correct when parts of the
   108  	// output are stored in the result.
   109  	str, ok := gg.AnyToText[string](src)
   110  	if ok {
   111  		return self.Parse(str)
   112  	}
   113  
   114  	switch src := src.(type) {
   115  	case Arr[A]:
   116  		*self = src
   117  		return nil
   118  
   119  	default:
   120  		return gg.ErrConv(src, gg.Type[Arr[A]]())
   121  	}
   122  }