Schema

Postgres types

Ranges, network, geometry, search, named types, and other Postgres types.

These types are specific to Postgres.

Ranges

A range is empty, or a pair of bounds. A null bound is unbounded. lowerInclusive and upperInclusive choose [ or (.

BuilderSQLBoundBytesPostgres
t.int4range()int4rangenumber, the integer rangevariableyes
t.int8range()int8rangestring, a bigintvariableyes
t.numrange()numrangestring, a numericvariableyes
t.daterange()daterangeTemporal.PlainDatevariableyes
t.tstzrange()tstzrangeTemporal.Instantvariableyes

There is no tsrange builder. An instant range is t.tstzrange().

plans.ts
import { table, t } from "okmodel/pg";

export const plans = table("plans", {
  id: t.identity(),
  scores: t.int4range(), 
  active: t.tstzrange(), 
});

A bound is { empty: false, lower, upper, lowerInclusive, upperInclusive }. { empty: true } is the empty range.

Network

Values are canonical text.

BuilderSQLValueBytesPostgres
t.inet()inetAn IPv4 or IPv6 host. A prefix is optional.7 or 19yes
t.cidr()cidrAn IPv4 or IPv6 network. A prefix is required.7 or 19yes
t.macaddr()macaddrSix lowercase groups: aa:bb:cc:dd:ee:ff.6yes
t.macaddr8()macaddr8Eight lowercase groups.8yes

7 bytes is IPv4. 19 bytes is IPv6.

An IPv4 prefix above 32, or any prefix above 128, is refused. t.cidr() with no prefix is refused.

hosts.ts
import { table, t } from "okmodel/pg";

export const hosts = table("hosts", {
  id: t.identity(),
  address: t.inet(), 
  block: t.cidr(), 
  mac: t.macaddr(), 
});

Geometry

Built-in plane geometry. A point is { x, y }. A line is { a, b, c } for ax + by + c = 0.

BuilderSQLTypeScriptBytesPostgres
t.point()point{ x: number, y: number }16yes
t.line()line{ a: number, b: number, c: number }24yes

box, circle, lseg, path, and polygon have no builder. PostGIS is not these types.

places.ts
import { table, t } from "okmodel/pg";

export const places = table("places", {
  id: t.identity(),
  location: t.point(), 
});
BuilderSQLTypeScriptBytesPostgres
t.tsvector()tsvectorstringvariableyes
t.ltree()ltreestringvariableextension

t.tsvector() stores the vector text. A generated vector is .generated(). t.ltree() is labels separated by dots, such as top.science. Each label is letters, digits, or underscores. The schema lists extension("ltree") from okmodel/ext. See extensions. There is no tsquery builder.

notes.ts
import { extension } from "okmodel/ext";
import { schema, table, t } from "okmodel/pg";

const notes = table("notes", {
  id: t.identity(),
  search: t.tsvector(), 
  path: t.ltree(), 
});

export default schema({
  tables: [notes],
  extensions: [extension("ltree")], 
});

OKM1020

t.ltree() with no ltree extension on the schema is refused when the catalog is built.

Named types

BuilderSQLTypeScriptBytesPostgres
t.enum(name, values)the enum namethe union of the labels4yes
t.domain(name, base, check)the domain namethe base column's typethe base typeyes
t.custom({ sqlType, encode, decode })sqlTypethe type encode acceptsthe SQL typeyes

An enum is its own type. A picklist stays a string column and adds a CHECK.

t.domain("email", t.text(), "VALUE LIKE '%@%'") stores the check as written. The name and the check must be non-empty. An enum cannot be the base. Changing the base type of an existing domain is OKM1020. An enum or domain name with a capital letter, a space, or a quote is written as one quoted identifier wherever the type is used, so it never runs as SQL.

t.custom() stores a SQL type that has no builder. sqlType must be non-empty. accepts names the object kinds the codec takes, such as "Object". Omit accepts for a scalar. extension records an extension dependency. sqlForm defaults to "quote".

notes.ts
import { table, t } from "okmodel/pg";

export const notes = table("notes", {
  id: t.identity(),
  email: t.domain("email", t.text(), "VALUE LIKE '%@%'"), 
  label: t.custom({
    sqlType: "text",
    encode: (value: string) => value,
    decode: (wire: string) => wire,
  }), 
});

Other Postgres types

Postgres has these types. This dialect has no builder under that name. The Postgres cell stays yes, because the database has the type. The call is what you write instead.

Postgres typeBytesCallPostgres
serial4t.integerIdentity()yes
bigserial8t.identity()yes
smallserial2t.smallint()yes
money8t.numeric(precision, scale)yes
decimalvariablet.numeric()yes
tsrangevariablet.tstzrange()yes
tsqueryvariablet.custom() with sqlType: "tsquery"yes
bit, bit varying1 byte per 8 bits, plus 5 or 8t.custom() with that SQL typeyes
xml, hstorevariablet.custom() with that SQL typeyes
box, lseg32t.custom() with that SQL typeyes
circle24t.custom() with that SQL typeyes
path16 + 16 per pointt.custom() with that SQL typeyes
polygon40 + 16 per pointt.custom() with that SQL typeyes
oid4t.custom() with that SQL typeyes
pg_lsn8t.custom() with that SQL typeyes
multirangevariablet.custom() with that SQL typeyes

Next: Columns.