···22-- This column stores the older of publishedAt (from JSON data) or indexed_at
33-- Used for sorting feeds chronologically by when content was actually published
4455--- Note: We use ::timestamp AT TIME ZONE 'UTC' to make the expression immutable
66--- (direct ::timestamptz cast is not immutable as it depends on session timezone)
55+-- Create an immutable function to parse ISO 8601 timestamps from text
66+-- This is needed because direct ::timestamp cast is not immutable (accepts 'now', 'today', etc.)
77+-- The regex validates the format before casting to ensure immutability
88+CREATE OR REPLACE FUNCTION parse_iso_timestamp(text) RETURNS timestamptz
99+LANGUAGE sql IMMUTABLE STRICT AS $$
1010+ SELECT CASE
1111+ -- Match ISO 8601 format: YYYY-MM-DDTHH:MM:SS with optional fractional seconds and Z/timezone
1212+ WHEN $1 ~ '^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:?\d{2})?$' THEN
1313+ $1::timestamptz
1414+ ELSE
1515+ NULL
1616+ END
1717+$$;
1818+719ALTER TABLE documents
820ADD COLUMN sort_date timestamptz GENERATED ALWAYS AS (
921 LEAST(
1010- COALESCE((data->>'publishedAt')::timestamp AT TIME ZONE 'UTC', indexed_at),
2222+ COALESCE(parse_iso_timestamp(data->>'publishedAt'), indexed_at),
1123 indexed_at
1224 )
1325) STORED;