Custom Go types for seamless PostgreSQL array handling with GORM and database/sql.
- UUIDArray: Proper handling of PostgreSQL UUID arrays (
uuid[]) - Full
sql.Scanneranddriver.Valuerimplementation - Works with GORM's
db.Raw().Scan()methods - Zero external dependencies (except
google/uuidand test libs) - Comprehensive test coverage
- Helper methods for common operations
go get github.com/nex-prospect/go-postgres-typesWhen using GORM with raw SQL queries that return PostgreSQL arrays, you'll encounter this error:
[error] failed to parse field: TagIDs, error: unsupported data type: &[]
This happens because PostgreSQL returns arrays in the format {uuid1,uuid2,uuid3}, but Go's standard []uuid.UUID type cannot scan this format directly.
Use postgres.UUIDArray instead:
import "github.com/nex-prospect/go-postgres-types/postgres"
type Conversation struct {
LeadID uuid.UUID `json:"lead_id"`
LeadTagIDs postgres.UUIDArray `json:"lead_tag_ids,omitempty"`
}package main
import (
"github.com/nex-prospect/go-postgres-types/postgres"
"github.com/google/uuid"
"gorm.io/gorm"
)
type User struct {
ID uuid.UUID `json:"id"`
Name string `json:"name"`
TagIDs postgres.UUIDArray `json:"tag_ids"`
}
func GetUsersWithTags(db *gorm.DB) ([]User, error) {
var users []User
query := `
SELECT
u.id,
u.name,
COALESCE(array_agg(ut.tag_id), ARRAY[]::uuid[]) as tag_ids
FROM users u
LEFT JOIN user_tags ut ON u.id = ut.user_id
GROUP BY u.id, u.name
`
err := db.Raw(query).Scan(&users).Error
return users, err
}// PostgreSQL returns empty arrays as {}
query := `
SELECT
id,
COALESCE(tag_ids, ARRAY[]::uuid[]) as tag_ids
FROM users
`
var user User
db.Raw(query).Scan(&user)
// user.TagIDs will be an empty postgres.UUIDArray, not nilfunc UpdateUserTags(db *gorm.DB, userID uuid.UUID, tagIDs postgres.UUIDArray) error {
return db.Exec(
"UPDATE users SET tag_ids = ? WHERE id = ?",
tagIDs,
userID,
).Error
}tags := postgres.UUIDArray{
uuid.MustParse("550e8400-e29b-41d4-a716-446655440000"),
uuid.MustParse("6ba7b810-9dad-11d1-80b4-00c04fd430c8"),
}
// Check if contains a UUID
if tags.Contains(someUUID) {
fmt.Println("Tag found!")
}
// Get length
fmt.Printf("Number of tags: %d\n", tags.Len())
// Check if empty
if tags.IsEmpty() {
fmt.Println("No tags")
}
// Convert to regular []uuid.UUID slice
regularSlice := tags.ToUUIDSlice()
// Convert from regular []uuid.UUID slice
tags = postgres.FromUUIDSlice(regularSlice)-- Basic aggregation
SELECT
lead_id,
array_agg(tag_id) as tag_ids
FROM lead_tags
GROUP BY lead_id;
-- With COALESCE for NULL handling
SELECT
l.id as lead_id,
COALESCE(array_agg(lt.tag_id), ARRAY[]::uuid[]) as tag_ids
FROM leads l
LEFT JOIN lead_tags lt ON l.id = lt.lead_id
GROUP BY l.id;
-- Filter out NULLs in aggregation
SELECT
lead_id,
array_agg(tag_id) FILTER (WHERE tag_id IS NOT NULL) as tag_ids
FROM lead_tags
GROUP BY lead_id;WITH lead_tags AS (
SELECT
lead_id,
array_agg(tag_id) as tag_ids
FROM lead_tags
GROUP BY lead_id
)
SELECT
l.id,
l.name,
COALESCE(lt.tag_ids, ARRAY[]::uuid[]) as tag_ids
FROM leads l
LEFT JOIN lead_tags lt ON l.id = lt.lead_id;Run tests with coverage:
go test -v -race -coverprofile=coverage.out ./...
go tool cover -html=coverage.outRun benchmarks:
go test -bench=. -benchmemBenchmarkUUIDArray_Scan_Empty-8 5000000 250 ns/op 64 B/op 2 allocs/op
BenchmarkUUIDArray_Scan_Single-8 2000000 850 ns/op 128 B/op 4 allocs/op
BenchmarkUUIDArray_Scan_Multiple-8 1000000 1850 ns/op 256 B/op 6 allocs/op
BenchmarkUUIDArray_Value_Multiple-8 3000000 450 ns/op 192 B/op 5 allocs/op
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
None at the moment. If you find a bug, please open an issue!
StringArrayfortext[]PostgreSQL arraysIntArrayforinteger[]PostgreSQL arraysJSONBArrayforjsonb[]PostgreSQL arraysTimestampArrayfortimestamp[]PostgreSQL arrays
This library was created to solve a common pain point when working with PostgreSQL arrays in Go:
- GORM + Raw Queries: When using
db.Raw().Scan(), GORM cannot automatically convert PostgreSQL array formats - Type Safety: Standard
[]uuid.UUIDdoesn't implement the necessary scanner interfaces - Nil Handling: Proper handling of NULL and empty arrays from PostgreSQL
- Developer Experience: Simple drop-in replacement with helpful utility methods
Created while solving real-world problems in microservice architectures at Nex Prospect.
- Issues: GitHub Issues
- Pull Requests: GitHub PRs
Made with โค๏ธ for the Go and PostgreSQL community