# sqltypes Nullable SQL types for hand-written or generated Go models. Each type wraps a value with a `Valid` flag and implements `database/sql.Scanner`, `driver.Valuer`, `encoding/json`, `gopkg.in/yaml.v3`, and `encoding/xml` marshalling — so a single struct field can be scanned from a database row, round-tripped through JSON/YAML/XML, and written back to the database without any per-format glue code. This package is what the `bun` and `gorm` writers emit when generating models with `--types sqltypes` (see [`pkg/writers/bun`](../writers/bun/README.md) and [`pkg/writers/gorm`](../writers/gorm/README.md)). It can also be imported directly in hand-written models. ## Import ```go import sql_types "git.warky.dev/wdevs/relspecgo/pkg/sqltypes" ``` ## Scalar types All scalar types are instantiations of the generic `SqlNull[T]`: | Type | Underlying | Typical SQL type | |---|---|---| | `SqlInt16` | `int16` | `smallint` | | `SqlInt32` | `int32` | `integer` | | `SqlInt64` | `int64` | `bigint` | | `SqlFloat32` | `float32` | `real`, `float4` | | `SqlFloat64` | `float64` | `double precision`, `numeric`, `decimal`, `money` | | `SqlBool` | `bool` | `boolean` | | `SqlString` | `string` | `text`, `varchar`, `char`, `citext`, `inet`, `cidr`, `macaddr` | | `SqlByteArray` | `[]byte` | `bytea` (base64-encoded in JSON/YAML/XML) | | `SqlUUID` | `uuid.UUID` (`github.com/google/uuid`) | `uuid` | You can also instantiate `SqlNull[T]` directly for any type not covered above, e.g. `SqlNull[MyEnum]`. ### Date/time types Plain `time.Time` doesn't distinguish date-only, time-only, and timestamp semantics, and its zero value marshals to a confusing `0001-01-01T00:00:00Z`. These wrapper types fix both problems: | Type | Format | Notes | |---|---|---| | `SqlTimeStamp` | `2006-01-02T15:04:05` | Full timestamp | | `SqlDate` | `2006-01-02` | Date only | | `SqlTime` | `15:04:05` | Time only | Zero/pre-epoch values (`time.Time{}` or anything before `0002-01-01`) marshal to `null` and `Value()` returns `nil`, instead of leaking Go's zero-time sentinel into the database or API responses. ### JSON types | Type | Underlying | Notes | |---|---|---| | `SqlJSONB` | `[]byte` | Raw JSON bytes; `MarshalYAML` decodes to native YAML mappings/sequences instead of an embedded JSON string | | `SqlJSON` | `= SqlJSONB` | Alias — PostgreSQL's `json` and `jsonb` share the same Go representation | `SqlJSONB` has `AsMap()` / `AsSlice()` helpers for pulling out `map[string]any` / `[]any` without a separate `json.Unmarshal` call. ### Vector type (pgvector) `SqlVector` wraps `[]float32` for the `vector` column type ([pgvector](https://github.com/pgvector/pgvector)), scanning/writing the `[1,2,3]` literal format pgvector uses over the wire. ## Array types PostgreSQL array columns (`text[]`, `integer[]`, …) map to `SqlXxxArray` types, each wrapping `Val []T` + `Valid bool` and handling PostgreSQL's `{a,b,c}` array literal format on `Scan`/`Value`: `SqlStringArray`, `SqlInt16Array`, `SqlInt32Array`, `SqlInt64Array`, `SqlFloat32Array`, `SqlFloat64Array`, `SqlBoolArray`, `SqlUUIDArray`. ## Constructing values Every type has a `NewSqlXxx(v)` constructor that sets `Valid: true`: ```go name := sql_types.NewSqlString("Ada Lovelace") age := sql_types.NewSqlInt32(36) tags := sql_types.NewSqlStringArray([]string{"engineer", "mathematician"}) ``` The zero value of any type (`sql_types.SqlString{}`) is null/invalid — use it directly for a `NULL` field instead of a separate constructor. Generic helpers: ```go sql_types.Null(v, valid) // SqlNull[T]{Val: v, Valid: valid} sql_types.NewSql[T](anyValue) // best-effort conversion from any Go value ``` ## Reading values back Each scalar type has typed accessors that return the zero value instead of panicking when `Valid` is false: ```go n.Int64() // SqlInt16/32/64, SqlFloat32/64, SqlBool, SqlString → int64 n.Float64() // → float64 n.Bool() // → bool n.Time() // SqlNull[time.Time]-based types → time.Time n.UUID() // SqlUUID → uuid.UUID n.String() // fmt.Stringer — empty string when invalid ``` ## Example ```go type User struct { ID sql_types.SqlUUID `json:"id"` Name sql_types.SqlString `json:"name"` Tags sql_types.SqlStringArray `json:"tags"` Metadata sql_types.SqlJSONB `json:"metadata"` CreatedAt sql_types.SqlTimeStamp `json:"created_at"` } u := User{ ID: sql_types.NewSqlUUID(uuid.New()), Name: sql_types.NewSqlString("Ada Lovelace"), Tags: sql_types.NewSqlStringArray([]string{"engineer"}), CreatedAt: sql_types.SqlTimeStampNow(), } // Metadata left as the zero value → serializes as null, scans as NULL. ``` Every type implements `sql.Scanner` and `driver.Valuer`, so these fields can be used directly as struct fields with `database/sql`, `bun`, or `gorm` without additional tags or hooks.