fix(bun): use PostgreSQL internal array type name for sqltypes array columns

bun's pgdialect overrides Field.Scan/Append with its own slice-only array
handling whenever the tag's type: value ends in "[]", clobbering the
sql.Scanner/driver.Valuer implemented on SqlXxxArray wrapper types and
causing "bun: Scan(unsupported sqltypes.SqlStringArray)" at query time.
Emit the underscore-prefixed internal type name (e.g. _text) instead,
which is DDL-valid but doesn't end in "[]" so bun leaves our scanner alone.
This commit is contained in:
Hein
2026-07-20 13:58:24 +02:00
parent 1c217b546c
commit f0410221d8
2 changed files with 75 additions and 2 deletions
+38 -2
View File
@@ -827,9 +827,45 @@ func TestTypeMapper_BuildBunTag(t *testing.T) {
t.Errorf("BuildBunTag() = %q, missing %q", result, part)
}
}
// sqltypes mode must NOT add "array" — SqlXxxArray uses sql.Scanner
// baselib mode must NOT add "array" — the Go type is already a
// real slice ([]string, []int32, ...), which bun's pgdialect
// scans natively without the explicit "array" tag option.
if strings.Contains(result, ",array,") || strings.HasSuffix(result, ",array,") {
t.Errorf("BuildBunTag() = %q, must not contain 'array' in sqltypes mode", result)
t.Errorf("BuildBunTag() = %q, must not contain 'array' in baselib mode", result)
}
})
}
}
// TestTypeMapper_BuildBunTag_SqlTypesArrayUsesInternalTypeName verifies that
// array columns in sqltypes mode never produce a "[]"-suffixed "type:" tag.
// bun's pgdialect unconditionally overrides Field.Scan/Append with its own
// (slice-only) array handling whenever the tag's "type:" value ends in "[]",
// which clobbers the sql.Scanner/driver.Valuer implemented on the
// SqlXxxArray wrapper types and produces
// "bun: Scan(unsupported sqltypes.SqlXxxArray)" at query time.
func TestTypeMapper_BuildBunTag_SqlTypesArrayUsesInternalTypeName(t *testing.T) {
mapper := NewTypeMapper(writers.NullableTypeSqlTypes)
cases := []struct {
name string
column *models.Column
wantSubstr string
}{
{name: "text array", column: &models.Column{Name: "tags", Type: "text[]"}, wantSubstr: "type:_text,"},
{name: "varchar array", column: &models.Column{Name: "labels", Type: "varchar[]"}, wantSubstr: "type:_varchar,"},
{name: "integer array", column: &models.Column{Name: "scores", Type: "integer[]", NotNull: true}, wantSubstr: "type:_int4,"},
{name: "boolean array", column: &models.Column{Name: "flags", Type: "boolean[]"}, wantSubstr: "type:_bool,"},
{name: "uuid array", column: &models.Column{Name: "ids", Type: "uuid[]"}, wantSubstr: "type:_uuid,"},
}
for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) {
result := mapper.BuildBunTag(tt.column, nil)
if !strings.Contains(result, tt.wantSubstr) {
t.Errorf("BuildBunTag() = %q, missing %q", result, tt.wantSubstr)
}
if strings.Contains(result, "[]") {
t.Errorf("BuildBunTag() = %q, must not use a \"[]\"-suffixed type in sqltypes mode", result)
}
})
}