package bun_test import ( "context" "database/sql" "os" "testing" _ "github.com/jackc/pgx/v5/stdlib" "github.com/uptrace/bun" "github.com/uptrace/bun/dialect/pgdialect" ) // TestBunArrayColumns_NativeSlice verifies against a live PostgreSQL // database (issue #13) that RelSpec's generated Bun array tags round-trip // correctly through Bun's own pgdialect for both NOT NULL and nullable // columns: NULL, '{}', and populated arrays must all scan and insert // without a "bun: Scan(unsupported ...)" error. // // Requires the RELSPEC_TEST_PG_CONN environment variable, e.g.: // // RELSPEC_TEST_PG_CONN="postgres://postgres:postgres@localhost:5432/relspec_test?sslmode=disable" func TestBunArrayColumns_NativeSlice(t *testing.T) { connStr := os.Getenv("RELSPEC_TEST_PG_CONN") if connStr == "" { t.Skip("Skipping Bun array integration test: RELSPEC_TEST_PG_CONN environment variable not set") } sqldb, err := sql.Open("pgx", connStr) if err != nil { t.Fatalf("open connection: %v", err) } defer sqldb.Close() db := bun.NewDB(sqldb, pgdialect.New()) ctx := context.Background() type notNullArrayRow struct { bun.BaseModel `bun:"table:relspec_test_arr_notnull"` ID int64 `bun:"id,pk,autoincrement"` Tags []string `bun:"tags,type:text[],notnull"` } type nullableArrayRow struct { bun.BaseModel `bun:"table:relspec_test_arr_nullable"` ID int64 `bun:"id,pk,autoincrement"` Tags *[]string `bun:"tags,type:text[]"` } if _, err := db.NewDropTable().Model((*notNullArrayRow)(nil)).IfExists().Exec(ctx); err != nil { t.Fatalf("drop table: %v", err) } if _, err := db.NewDropTable().Model((*nullableArrayRow)(nil)).IfExists().Exec(ctx); err != nil { t.Fatalf("drop table: %v", err) } if _, err := db.NewCreateTable().Model((*notNullArrayRow)(nil)).Exec(ctx); err != nil { t.Fatalf("create not-null table: %v", err) } if _, err := db.NewCreateTable().Model((*nullableArrayRow)(nil)).Exec(ctx); err != nil { t.Fatalf("create nullable table: %v", err) } t.Cleanup(func() { db.NewDropTable().Model((*notNullArrayRow)(nil)).IfExists().Exec(ctx) db.NewDropTable().Model((*nullableArrayRow)(nil)).IfExists().Exec(ctx) }) t.Run("not null populated array", func(t *testing.T) { row := ¬NullArrayRow{Tags: []string{"a", "b", "c"}} if _, err := db.NewInsert().Model(row).Exec(ctx); err != nil { t.Fatalf("insert: %v", err) } var out notNullArrayRow if err := db.NewSelect().Model(&out).Where("id = ?", row.ID).Scan(ctx); err != nil { t.Fatalf("select: %v", err) } if len(out.Tags) != 3 || out.Tags[0] != "a" || out.Tags[2] != "c" { t.Errorf("Tags = %v, want [a b c]", out.Tags) } }) t.Run("not null empty array", func(t *testing.T) { row := ¬NullArrayRow{Tags: []string{}} if _, err := db.NewInsert().Model(row).Exec(ctx); err != nil { t.Fatalf("insert: %v", err) } var out notNullArrayRow if err := db.NewSelect().Model(&out).Where("id = ?", row.ID).Scan(ctx); err != nil { t.Fatalf("select: %v", err) } if len(out.Tags) != 0 { t.Errorf("Tags = %v, want empty", out.Tags) } }) t.Run("nullable NULL", func(t *testing.T) { row := &nullableArrayRow{Tags: nil} if _, err := db.NewInsert().Model(row).Exec(ctx); err != nil { t.Fatalf("insert: %v", err) } var out nullableArrayRow if err := db.NewSelect().Model(&out).Where("id = ?", row.ID).Scan(ctx); err != nil { t.Fatalf("select: %v", err) } if out.Tags != nil { t.Errorf("Tags = %v, want nil (SQL NULL)", *out.Tags) } }) t.Run("nullable empty array", func(t *testing.T) { empty := []string{} row := &nullableArrayRow{Tags: &empty} if _, err := db.NewInsert().Model(row).Exec(ctx); err != nil { t.Fatalf("insert: %v", err) } var out nullableArrayRow if err := db.NewSelect().Model(&out).Where("id = ?", row.ID).Scan(ctx); err != nil { t.Fatalf("select: %v", err) } if out.Tags == nil { t.Fatalf("Tags = nil, want non-nil pointer to empty slice") } if len(*out.Tags) != 0 { t.Errorf("Tags = %v, want empty slice", *out.Tags) } }) t.Run("nullable populated array", func(t *testing.T) { tags := []string{"x", "y"} row := &nullableArrayRow{Tags: &tags} if _, err := db.NewInsert().Model(row).Exec(ctx); err != nil { t.Fatalf("insert: %v", err) } var out nullableArrayRow if err := db.NewSelect().Model(&out).Where("id = ?", row.ID).Scan(ctx); err != nil { t.Fatalf("select: %v", err) } if out.Tags == nil || len(*out.Tags) != 2 || (*out.Tags)[0] != "x" { t.Errorf("Tags = %v, want [x y]", out.Tags) } }) }