The neutral type system#
Scythe uses language-agnostic types internally — bool, int32, float64, string, uuid, datetime, json, and more — that map to backend-specific implementations. See the full list in the Neutral Type Reference.
JOIN nullability#
Columns from the right side of a LEFT JOIN become nullable regardless of their schema definition — a NOT NULL column on the joined table can still come back NULL if there's no matching row. FULL OUTER JOINs widen both sides to nullable; SEMI/ANTI joins follow comparable rules based on which side can fail to match.
COALESCE#
COALESCE strips nullability when any argument is non-nullable — a single guaranteed non-null value anywhere in the argument list guarantees a non-null result, regardless of how many nullable arguments surround it.
Aggregate functions#
COUNT always returns a non-nullable value. SUM, AVG, MIN, and MAX are nullable, because each returns NULL for an empty group. Windowed aggregates behave differently from their grouped counterparts: a function like SUM(...) OVER (...) cannot evaluate over zero rows the way a GROUP BY aggregate can, which changes its nullability profile.
Nested JSON aggregates#
On PostgreSQL and CockroachDB, json_agg(alias.*) and its relatives generate a synthesized struct type rather than an opaque JSON scalar, with field names derived from the aggregated relation's columns. This only decodes on backends that support it — see the PostgreSQL notes for which backends parse the result versus passing through raw JSON.
CASE expressions#
A CASE result is nullable if any branch can produce NULL. The analyzer recognizes the "IS NOT NULL guard" exception: a pattern like CASE WHEN col IS NOT NULL THEN col ELSE 'default' END is provably non-nullable, and scythe infers it as such rather than defaulting to nullable out of caution.
Binary operators#
Arithmetic and string concatenation propagate nullability from their operands. Comparison and boolean operators always yield a non-nullable bool — a comparison against NULL evaluates to NULL at the SQL level, but scythe's own SC-A01 lint rule flags exactly that pattern (= NULL instead of IS NULL) as a likely bug rather than modeling it as a valid nullable-bool result. JSON operators (->, ->>, #>) are always nullable.
Manual control#
When automatic inference gets it wrong, or when you know something the analyzer can't — like a CHECK constraint the parser doesn't model — override it directly in the query file:
-- @name SearchUsers
-- @returns :many
-- @nullable bio
-- @nonnull email
SELECT id, email, bio FROM users WHERE name ILIKE $1;Related#
- Neutral Type Reference — the full scalar and container type list.
- Custom Types — overriding types scythe doesn't recognize at all, as opposed to overriding nullability on types it does.