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 }