Types
The type system is the product. Columns are strictly, statically typed — a value is never silently reinterpreted at runtime. Where the SQL raises a question of semantics, jed follows PostgreSQL closely.
Every example below is a live database in your browser. Edit the SQL and run it.
Exact decimals
numeric / decimal is exact base-10, not binary floating point. So 0.1 + 0.2 is exactly 0.3, 1.50 equals 1.5 by value, and a value keeps its display scale.
No results yet. Run a query to see output.
Integers with defined overflow
Integers are fixed-width (i16, i32, i64) and trap on overflow — there is no silent
wraparound. Adding 1 to the largest i16 raises error 22003:
No results yet. Run a query to see output.
Boolean ⇄ integer casts
Following PostgreSQL, boolean casts to and from int (i32) only, and always with an explicit CAST / :: — never implicitly. true becomes 1 and false becomes 0; an integer becomes false when it is 0 and true for any nonzero value (including negatives):
No results yet. Run a query to see output.
Only i32 is involved: a boolean ⇄ i16 or boolean ⇄ i64 cast is rejected (42804), matching
PostgreSQL’s choice of int4 as the sole boolean-integer cast.
Parsing text into numbers and booleans
An explicit CAST / :: parses a text value into a number or boolean at runtime — i16 / i32 / i64, decimal (with a numeric(p,s) re-scale), f32 / f64, and boolean. The string
is trimmed, a leading sign is accepted, and a boolean follows PostgreSQL’s spellings
(t/true/yes/on/1 …). A malformed string raises 22P02 and an out-of-range integer raises 22003:
No results yet. Run a query to see output.
The type must be named — a bare string never silently becomes a number, so int_col = '42' stays
a type error (42804). jed uses its own literal grammar, so hex, digit underscores, and NaN are
rejected (22P02) where PostgreSQL accepts them. (Parsing a string into a date / timestamp / interval / bytea is a separate feature — use that type’s literal form, e.g. date '2024-01-15'.)
varchar(n) length limits
varchar(n) (and the jed alias string(n)) is text with a maximum length, counted in Unicode code points — so 'café' (4 code points) fits varchar(4). An explicit CAST / :: silently truncates to n, exactly like PostgreSQL, so 'hello world'::varchar(5) is 'hello':
No results yet. Run a query to see output.
Storing into a varchar(n) column is stricter: an over-length value raises 22001 (string_data_right_truncation), unless the excess characters are all spaces — then it is
truncated to n (the SQL-standard trailing-space rule). UPDATE re-checks the length the same way.
The limit lives on the value, not the type: a varchar(4) and an unbounded text are the same type
underneath, with the same collation, comparison, and key encoding.
UUID ⇄ text and bytea casts
A uuid casts to and from text and bytea, always with an explicit CAST / ::. text → uuid parses PostgreSQL-flexibly (braces, hyphens after any byte pair, a bare 32-hex run, any case), and uuid → text always renders the canonical lowercase 8-4-4-4-12 form. Because a UUID is exactly
16 bytes, it also casts to and from bytea (those 16 raw bytes); bytea → uuid requires exactly 16
bytes and raises 22P02 otherwise:
No results yet. Run a query to see output.
text → uuid matches PostgreSQL. The other three are deliberately stricter or additional: jed makes uuid → text explicit-only (PostgreSQL assignment-casts any type to text), and the bytea ↔ uuid casts are a jed convenience PostgreSQL does not offer at all.
Array casts
An array casts three ways, all with an explicit CAST / ::. array → text renders the {…} form
(array_out); text → T[] parses the {…} form per row (array_in); and an array → array of a
different element type casts each element through the scalar cast, preserving the array’s shape
(its dimensions, lengths, and lower bounds):
No results yet. Run a query to see output.
The element cast follows the scalar matrix exactly — so widening (i32[] → i64[]), numeric[] → i32[] (rounding half away from zero), and text[] → i32[] all work, while an element pair with no scalar
cast is a type error (42804). Like uuid/json → text, array → text is explicit-only: an array
never silently lands in a text column.
Three-valued NULL logic
Comparisons with NULL yield NULL (unknown), not false — three-valued logic, as in PostgreSQL.
Note that NULL = NULL is NULL, while NULL IS NULL is true:
No results yet. Run a query to see output.
Range types
A range is a structural type over a scalar element — PostgreSQL’s six built-in ranges
(int4range/int8range/numrange/tsrange/tstzrange/daterange; jed also spells the integer
ones i32range/i64range). A range carries inclusive/exclusive ([1,5)) and unbounded ((,5))
endpoints and a distinguished empty; discrete ranges are stored in the canonical [) form.
Construct one with a literal cast ('[1,5)'::int4range) or a constructor (int4range(1, 10)), test
it with the boolean operators (@> contains, && overlaps, <</>> strictly left/right, -|- adjacent), and combine ranges with the set operators — + (union), * (intersection), - (difference), and range_merge. A union of non-adjacent ranges raises 22000; range_merge spans
the gap instead:
No results yet. Run a query to see output.
Time zones
timestamptz stores a UTC instant; a time zone is an I/O-time interpretation, never part of the
stored value or its order (PostgreSQL’s model). The AT TIME ZONE operator converts both ways — timestamptz AT TIME ZONE zone renders the instant as the local wall clock in zone, and timestamp AT TIME ZONE zone interprets a wall clock as being in zone and gives back the UTC
instant. UTC and fixed offsets like +05:30 are built in (note the POSIX sign: '+05:30' means UTC−5:30, matching PostgreSQL):
No results yet. Run a query to see output.
Named IANA zones (America/New_York, Europe/Paris, …) come from a time-zone database the host loads as bytes — the engine ships none by itself, so a query can only ever use an
already-loaded zone (an unknown zone raises 22023). This is the same host-loaded-data model jed
uses for Unicode collation; see the design notes for the JTZ bundle and the db.loadTimeZoneData host call.
Truncating, extracting, and converting
date_trunc(unit, value) rounds a timestamp / timestamptz / interval down to a unit
(hour, day, week, month, quarter, year, …); EXTRACT(field FROM value) pulls a single
field out as exact numeric (year, dow, epoch, doy, ISO week, …); and the timestamp / timestamptz / date types cast across each other. For a timestamptz, both date_trunc and EXTRACT (and the casts) decompose the instant in the session time zone — the
panel below runs in the default UTC session, and date_trunc(unit, timestamptz, zone) takes an
explicit zone:
No results yet. Run a query to see output.
(date_part is deferred — it returns double precision, and jed has no binary float type — as are
the text↔datetime casts; cast a string with the timestamp '…' / date '…' literal form instead.)
Date arithmetic
A date does calendar arithmetic, matching PostgreSQL. Adding or subtracting an integer shifts
the day count and stays a date; subtracting one date from another gives the number of days
between as an i32; and adding or subtracting an interval widens the date to midnight and
returns a timestamp (month steps clamp the day-of-month, so Jan 31 + 1 month is Feb 29 in a leap
year). The ±infinity dates absorb any shift, and an out-of-range result raises 22008:
No results yet. Run a query to see output.
See the full type reference for every scalar type and its range.