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:
Hein
2026-07-21 12:41:38 +02:00
parent 2cecb4c11c
commit 5d9ff5df03
65 changed files with 9584 additions and 167 deletions
+87
View File
@@ -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)
}