feat(bun): generate native Go array slices for PostgreSQL array columns
Bun's pgdialect scans/appends native slices directly, so array columns
(text[], integer[], uuid[], ...) always generate as plain []string,
[]int32, etc. with an explicit "array" bun tag, regardless of --types
(sqltypes/stdlib/baselib). The SqlXxxArray wrapper types are no longer
used for Bun array columns (gorm is unaffected and keeps using them).
Adds --array-nullable pointer_slice to represent nullable array columns
as *[]T instead of []T, so callers can distinguish SQL NULL (nil) from
'{}' (pointer to an empty slice). Verified end-to-end against a live
PostgreSQL instance for NULL/{}/populated arrays in every --types mode.
Closes #13
This commit is contained in:
+87
@@ -0,0 +1,87 @@
|
||||
package pgdialect
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/uptrace/bun/dialect"
|
||||
"github.com/uptrace/bun/schema"
|
||||
)
|
||||
|
||||
var (
|
||||
driverValuerType = reflect.TypeFor[driver.Valuer]()
|
||||
|
||||
stringType = reflect.TypeFor[string]()
|
||||
sliceStringType = reflect.TypeFor[[]string]()
|
||||
|
||||
intType = reflect.TypeFor[int]()
|
||||
sliceIntType = reflect.TypeFor[[]int]()
|
||||
|
||||
int64Type = reflect.TypeFor[int64]()
|
||||
sliceInt64Type = reflect.TypeFor[[]int64]()
|
||||
|
||||
float64Type = reflect.TypeFor[float64]()
|
||||
sliceFloat64Type = reflect.TypeFor[[]float64]()
|
||||
|
||||
timeType = reflect.TypeFor[time.Time]()
|
||||
sliceTimeType = reflect.TypeFor[[]time.Time]()
|
||||
)
|
||||
|
||||
func appendTime(buf []byte, tm time.Time) []byte {
|
||||
return tm.UTC().AppendFormat(buf, "2006-01-02 15:04:05.999999-07:00")
|
||||
}
|
||||
|
||||
var mapStringStringType = reflect.TypeOf(map[string]string(nil))
|
||||
|
||||
func (d *Dialect) hstoreAppender(typ reflect.Type) schema.AppenderFunc {
|
||||
kind := typ.Kind()
|
||||
|
||||
switch kind {
|
||||
case reflect.Ptr:
|
||||
if fn := d.hstoreAppender(typ.Elem()); fn != nil {
|
||||
return schema.PtrAppender(fn)
|
||||
}
|
||||
case reflect.Map:
|
||||
// ok:
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
if typ.Key() == stringType && typ.Elem() == stringType {
|
||||
return appendMapStringStringValue
|
||||
}
|
||||
|
||||
return func(gen schema.QueryGen, b []byte, v reflect.Value) []byte {
|
||||
err := fmt.Errorf("bun: Hstore(unsupported %s)", v.Type())
|
||||
return dialect.AppendError(b, err)
|
||||
}
|
||||
}
|
||||
|
||||
func appendMapStringString(b []byte, m map[string]string) []byte {
|
||||
if m == nil {
|
||||
return dialect.AppendNull(b)
|
||||
}
|
||||
|
||||
b = append(b, '\'')
|
||||
|
||||
for key, value := range m {
|
||||
b = appendStringElem(b, key)
|
||||
b = append(b, '=', '>')
|
||||
b = appendStringElem(b, value)
|
||||
b = append(b, ',')
|
||||
}
|
||||
if len(m) > 0 {
|
||||
b = b[:len(b)-1] // Strip trailing comma.
|
||||
}
|
||||
|
||||
b = append(b, '\'')
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func appendMapStringStringValue(gen schema.QueryGen, b []byte, v reflect.Value) []byte {
|
||||
m := v.Convert(mapStringStringType).Interface().(map[string]string)
|
||||
return appendMapStringString(b, m)
|
||||
}
|
||||
Reference in New Issue
Block a user