Golang & PostgreSQL | sql | sqlx (Transactions) | © | ©

See Labs

Prepared Statements

Use or Avoid?

Nullable (db) Field Values | SQL Null Types | © | ©

TL;DR

Use pointer field types (*T) for any nullable field. That way the end-to-end interface is NULL to/from JSON null, while at Golang (struct) the value is whatever the type's zero value (and nil at the pointer itself). Optionally, additionally, use the json tag, json:"foo,omitempty", to omit any of zero-value from the JSON result upon json.Marshal().

Guidelines:

Roundtrip Data-type Flow (Type vs *Type)

JSON <==> Golang <==> DB/SQL

From json.Decode to INSERT, to SELECT, to json.Marshal, per case (no-key vs zero-value).

Database field Foo *string type scans to Golang nil, and so JSON-marshals to missing key if json:"foo,omitempty", else to "foo":null. This is the cleanest scheme; db NULL to/from JSON null (or absent key).

Handling Schemes

From best to worst:

  1. Foo *string type scans (sql pkg) and decodes (json pkg) to Golang nil, and so JSON-marshals to missing key if json:"foo,omitempty", else to "foo":null. This is the cleanest scheme; db NULL to/from JSON null (or absent key); no need for Nil UUID values in its database table (which fail versioned UUID validator constraints, e.g., uuid4, though validate as uuid).

  2. Foo string type fails on SELECT scan of NULL to string

    • Use Nil UUID (and NOT NULL constraint) as a proxy for NULL.
    • Handle at (un)marshal. E.g.,

      // @ Unmarshal
      if got.ID3 == "" {
          got.ID3 = dbUNIL.String
      }
      // @ Marshall
      if s.ID3 == dbUNIL.String {
          s.ID3 = ""
      }
      
      var (
          // Insert dbUNIL as proxy for NULL (validates as `uuid`, but not `uuid4`)
          // @ Golang: `uuid.Nil`; @ Postgres "uuid-oosp" extension: `uuid_nil()`
          dbUNIL sql.NullString = sql.NullString{String: fmt.Sprintf("%s", uuid.Nil), Valid: true}
      )
      
    • This is a workaround, not a genuine solution, since it prohibits (db) NULL. If no NULL fields, then simplify:

      dbUNIL := fmt.Sprintf("%s", uuid.Nil)
      
  3. Foo sql.NullString type is itself a struct, and so JSON-marshals as such:

    • NULL marshals to "id3": {"String": "","Valid": false}
    • Else marshals to "id3": {"String": "1aad24fd-cf8a-4051-8863-0200f8a26616","Valid": true}

SQL Null Types :: sql.NullString and sql.NullFloat64

type Book struct {
    isbn  string
    title  string
    author string
    price  float32
}

… we set NOT NULL constraints on the columns … If the table contained nullable fields we would need to use the sql.NullString and sql.NullFloat64 types instead.

type Book struct {
    Isbn  string
    Title  sql.NullString
    Author sql.NullString
    Price  sql.NullFloat64
}

Roundtrip Data-type Flow (Type vs *Type)

From json.Decode to INSERT, to SELECT, to json.Marshal, per case (no-key vs zero-value).

string (NOT NULLable)

*string (NULLable)

choose the desired API dynamic (per key).

Pairing types across app boundaries

PostgreSQL Golang Javascript
BIGINT int integer
UUID string string
NUMERIC(5,2) ??? ???
TIMESTAMPTZ time.Time string