SET datestyle = 'ISO, YMD'; SET timezone = 'UTC'; SELECT '"foo"'::rdfnode; rdfnode --------- "foo" (1 row) SELECT '"foo"^^xsd:string'::rdfnode; rdfnode -------------------------------------------------- "foo"^^ (1 row) SELECT '"foo"@es'::rdfnode; rdfnode ---------- "foo"@es (1 row) SELECT '"foo"@es'::rdfnode::text; text ---------- "foo"@es (1 row) SELECT '"foo"@es'::rdfnode::text::rdfnode; rdfnode ---------- "foo"@es (1 row) SELECT '"foo"^^xsd:string'::rdfnode::text::rdfnode; rdfnode -------------------------------------------------- "foo"^^ (1 row) SELECT '"nan"^^xsd:double'::rdfnode; rdfnode -------------------------------------------------- "nan"^^ (1 row) SELECT '"NAN"^^xsd:double'::rdfnode; rdfnode -------------------------------------------------- "NAN"^^ (1 row) SELECT '"nAn"^^xsd:double'::rdfnode; rdfnode -------------------------------------------------- "nAn"^^ (1 row) SELECT '"forty-two"^^xsd:int'::rdfnode; -- works despite invalid lexical form for int rdfnode ----------------------------------------------------- "forty-two"^^ (1 row) SELECT '"invalid"^^xsd:dateTime'::rdfnode; -- works despite invalid lexical form for dateTime rdfnode -------------------------------------------------------- "invalid"^^ (1 row) SELECT '"25:00:00"^^xsd:time'::rdfnode; rdfnode ----------------------------------------------------- "25:00:00"^^ (1 row) SELECT '"2025-13-01"^^xsd:date'::rdfnode; rdfnode ------------------------------------------------------- "2025-13-01"^^ (1 row) SELECT '"abc"^^invalid:datatype'::rdfnode; rdfnode --------------------------- "abc"^^ (1 row) SELECT '"abc"@invalid_lang'::rdfnode; ERROR: invalid input syntax for type rdfnode: ""abc"@invalid_lang" LINE 1: SELECT '"abc"@invalid_lang'::rdfnode; ^ DETAIL: Trailing content after RDF literal. SELECT '"foo'::rdfnode; rdfnode --------- "\"foo" (1 row) SELECT 'f"o"o'::rdfnode; rdfnode ----------- "f\"o\"o" (1 row) SELECT '𝄞'::rdfnode; rdfnode --------- "𝄞" (1 row) SELECT ''::rdfnode; rdfnode --------- "" (1 row) SELECT '"'::rdfnode; rdfnode --------- "\"" (1 row) SELECT '"\""'::rdfnode; rdfnode --------- "\"" (1 row) SELECT '😀'::rdfnode; rdfnode --------- "😀" (1 row) SELECT '"x^^y"'::rdfnode; -- "x^^y" rdfnode --------- "x^^y" (1 row) SELECT '"a\\"b"@en'::rdfnode; -- "a\"b"@en ERROR: invalid input syntax for type rdfnode: ""a\\"b"@en" LINE 1: SELECT '"a\\"b"@en'::rdfnode; ^ DETAIL: Trailing content after RDF literal. SELECT '"𝄞"^^'::rdfnode; rdfnode ------------------------------ "𝄞"^^ (1 row) /* language tags (BCP 47) */ SELECT '"foo"@'::rdfnode; -- invalid: empty tag ERROR: invalid input syntax for type rdfnode: ""foo"@" LINE 2: SELECT '"foo"@'::rdfnode; ^ SELECT '"foo"@en-US'::rdfnode; -- valid rdfnode ------------- "foo"@en-us (1 row) SELECT '"foo"@en-Latn-US-valencia'::rdfnode; -- valid extended BCP 47 rdfnode --------------------------- "foo"@en-latn-us-valencia (1 row) SELECT '"foo"@123'::rdfnode; -- invalid: must start with letter ERROR: invalid language tag: "123" LINE 1: SELECT '"foo"@123'::rdfnode; ^ SELECT '"foo"@EN'::rdfnode; -- valid; the tag is lowercased rdfnode ---------- "foo"@en (1 row) /* * The whole tag is lowercased, not just the primary subtag. RDF 1.1 Concepts * 3.3 puts the value space of language tags in lower case and allows the * lexical form to be converted to it. Lowering only the part before the first * hyphen left one tag reaching storage as several terms, which value equality * called equal and the operator class -- which compares the stored term -- did * not, and which gave LANG two strings for one tag. */ SELECT '"foo"@EN-GB'::rdfnode, '"foo"@en-gb'::rdfnode, '"foo"@ZH-Hant-TW'::rdfnode; rdfnode | rdfnode | rdfnode -------------+-------------+------------------ "foo"@en-gb | "foo"@en-gb | "foo"@zh-hant-tw (1 row) SELECT ('"x"@en-GB'::rdfnode = '"x"@en-gb'::rdfnode) AS same_value, ('"x"@en-GB'::rdfnode ~= '"x"@en-gb'::rdfnode) AS same_term, rdfnode_cmp('"x"@en-GB', '"x"@en-gb') AS term_order, (sparql.lang('"x"@en-GB') = sparql.lang('"x"@en-gb')) AS one_tag_one_string; same_value | same_term | term_order | one_tag_one_string ------------+-----------+------------+-------------------- t | t | 0 | t (1 row) SELECT '"foo"@en-us'::rdfnode; -- subtags after the primary are stored as-is (no region canonicalization) rdfnode ------------- "foo"@en-us (1 row) SELECT '"foo"@en-'::rdfnode; -- invalid trailing hyphen ERROR: invalid language tag: "en-" LINE 1: SELECT '"foo"@en-'::rdfnode; ^ /* IRIs and blank nodes */ SELECT ''::rdfnode; rdfnode -------------------------- (1 row) SELECT ''::rdfnode; rdfnode ----------------------- (1 row) SELECT ''::rdfnode; -- invalid: spaces in IRI rdfnode -------------------------------- "" (1 row) SELECT '<>'::rdfnode; -- relative empty IRI rdfnode --------- "<>" (1 row) SELECT '_:b1'::rdfnode; -- blank node rdfnode --------- _:b1 (1 row) SELECT '_:'::rdfnode; -- invalid blank node label rdfnode --------- "_:" (1 row) /* numeric edge cases */ SELECT '"+42"^^xsd:int'::rdfnode; -- explicit plus (valid) rdfnode ----------------------------------------------- "+42"^^ (1 row) SELECT '"042"^^xsd:int'::rdfnode; -- leading zeros (valid lex, non-canonical) rdfnode ----------------------------------------------- "042"^^ (1 row) SELECT '"2147483647"^^xsd:int'::rdfnode::int; -- INT_MAX int4 ------------ 2147483647 (1 row) SELECT '"2147483648"^^xsd:int'::rdfnode; -- works despite overflow rdfnode ------------------------------------------------------ "2147483648"^^ (1 row) SELECT '"-2147483648"^^xsd:int'::rdfnode::int; -- INT_MIN int4 ------------- -2147483648 (1 row) SELECT '"3.14e0"^^xsd:double'::rdfnode; -- exponent rdfnode ----------------------------------------------------- "3.14e0"^^ (1 row) SELECT '"3."^^xsd:decimal'::rdfnode; -- XSD 1.1: invalid; XSD 1.0: valid rdfnode -------------------------------------------------- "3."^^ (1 row) SELECT '"-0"^^xsd:integer'::rdfnode; -- valid lex, canonical is "0" rdfnode -------------------------------------------------- "-0"^^ (1 row) SELECT '"1.0"^^xsd:integer'::rdfnode; -- works despite invalid lexical form for integer rdfnode --------------------------------------------------- "1.0"^^ (1 row) /* xsd:boolean alternative lexical forms */ SELECT '"1"^^xsd:boolean'::rdfnode::boolean; -- true bool ------ t (1 row) SELECT '"0"^^xsd:boolean'::rdfnode::boolean; -- false bool ------ f (1 row) SELECT '"TRUE"^^xsd:boolean'::rdfnode; -- invalid (case-sensitive) rdfnode ---------------------------------------------------- "TRUE"^^ (1 row) SELECT '"yes"^^xsd:boolean'::rdfnode; -- invalid rdfnode --------------------------------------------------- "yes"^^ (1 row) /* date/time variations */ SELECT '"2025-04-25T18:44:38+02:00"^^xsd:dateTime'::rdfnode; rdfnode -------------------------------------------------------------------------- "2025-04-25T18:44:38+02:00"^^ (1 row) SELECT '"2025-04-25T18:44:38.123Z"^^xsd:dateTime'::rdfnode; rdfnode ------------------------------------------------------------------------- "2025-04-25T18:44:38.123Z"^^ (1 row) SELECT '"-0044-03-15T12:00:00Z"^^xsd:dateTime'::rdfnode; -- BCE rdfnode ---------------------------------------------------------------------- "-0044-03-15T12:00:00Z"^^ (1 row) SELECT '"2025-04-25Z"^^xsd:date'::rdfnode; rdfnode -------------------------------------------------------- "2025-04-25Z"^^ (1 row) SELECT '"2025-04-25-08:00"^^xsd:date'::rdfnode; rdfnode ------------------------------------------------------------- "2025-04-25-08:00"^^ (1 row) SELECT '"24:00:00"^^xsd:time'::rdfnode; -- XSD 1.1: valid; XSD 1.0: invalid rdfnode ----------------------------------------------------- "24:00:00"^^ (1 row) SELECT '"2025-02-29"^^xsd:date'::rdfnode; -- works despite not being a leap year rdfnode ------------------------------------------------------- "2025-02-29"^^ (1 row) SELECT '"2024-02-29"^^xsd:date'::rdfnode; -- leap year rdfnode ------------------------------------------------------- "2024-02-29"^^ (1 row) /* gYear / gMonth / gDay */ SELECT '"2025"^^'::rdfnode; rdfnode -------------------------------------------------- "2025"^^ (1 row) SELECT '"--04"^^'::rdfnode; rdfnode --------------------------------------------------- "--04"^^ (1 row) SELECT '"---25"^^'::rdfnode; rdfnode -------------------------------------------------- "---25"^^ (1 row) SELECT '"2025-04"^^'::rdfnode; rdfnode ---------------------------------------------------------- "2025-04"^^ (1 row) /* escape sequences inside literals */ SELECT E'"line1\\nline2"'::rdfnode; -- \n inside literal rdfnode ---------------- "line1\nline2" (1 row) SELECT E'"tab\\there"'::rdfnode; -- \t rdfnode ------------- "tab\there" (1 row) SELECT E'"\\u00E9"'::rdfnode; -- é rdfnode --------- "é" (1 row) SELECT E'"\\U0001F600"'::rdfnode; -- 😀 rdfnode --------- "😀" (1 row) SELECT E'"backslash\\\\test"'::rdfnode; -- \\ rdfnode ------------------- "backslash\\test" (1 row) SELECT E'"quote\\""'::rdfnode; -- \" rdfnode ----------- "quote\"" (1 row) /* whitespace handling */ SELECT ' "foo"@en '::rdfnode; -- leading/trailing with language tag rdfnode ------------------ " \"foo\"@en " (1 row) SELECT ' "foo"^^xsd:int '::rdfnode; -- leading/trailing with data type rdfnode ------------------------ " \"foo\"^^xsd:int " (1 row) SELECT '"foo" @en'::rdfnode; -- space before @ -> invalid ERROR: invalid input syntax for type rdfnode: ""foo" @en" LINE 1: SELECT '"foo" @en'::rdfnode; ^ DETAIL: Trailing content after RDF literal. SELECT '"foo"^^ xsd:string'::rdfnode; -- space after ^^ -> invalid ERROR: invalid input syntax for type rdfnode: ""foo"^^ xsd:string" LINE 1: SELECT '"foo"^^ xsd:string'::rdfnode; ^ DETAIL: Trailing content after RDF literal. SELECT E'"foo"\n@en'::rdfnode; -- newline before @ -> invalid ERROR: invalid input syntax for type rdfnode: ""foo" @en" LINE 1: SELECT E'"foo"\n@en'::rdfnode; ^ DETAIL: Trailing content after RDF literal. SELECT 'foo"bar"@en'::rdfnode; -- embedded quote + lang-looking suffix: keep verbatim rdfnode ----------------- "foo\"bar\"@en" (1 row) SELECT 'x\"y'::rdfnode; -- already-escaped text, must NOT double-escape rdfnode --------- "x\"y" (1 row) /* datatype IRI variants */ SELECT '"abc"^^<>'::rdfnode; -- empty datatype IRI ERROR: invalid input syntax for type rdfnode: ""abc"^^<>" LINE 2: SELECT '"abc"^^<>'::rdfnode; ^ SELECT '"abc"^^'::rdfnode; -- invalid IRI ERROR: invalid IRI: LINE 1: SELECT '"abc"^^'::rdfnode; ^ DETAIL: An IRI cannot contain <, >, ", {, }, |, ^, `, \ or a character in #x00-#x20. SELECT '"abc"^^xsd:'::rdfnode; -- empty local part rdfnode --------------- "abc"^^ (1 row) SELECT '"abc"^^:bar'::rdfnode; -- empty prefix rdfnode --------------- "abc"^^<:bar> (1 row) /* interval with mixed signs */ SELECT '1 year -3 days'::interval::rdfnode; rdfnode ------------------------------------------------------- "P1Y-3D"^^ (1 row) SELECT '-1 day 12 hours'::interval::rdfnode; rdfnode --------------------------------------------------------- "P-1DT12H"^^ (1 row) SELECT '3 days -4 hours'::interval::rdfnode; rdfnode -------------------------------------------------------- "P3DT-4H"^^ (1 row) SELECT '-2 months 5 days'::interval::rdfnode; rdfnode ------------------------------------------------------- "P-2M5D"^^ (1 row) SELECT '1 month -30 minutes'::interval::rdfnode; rdfnode --------------------------------------------------------- "P1MT-30M"^^ (1 row) SELECT '-4 hours'::interval::rdfnode; rdfnode ------------------------------------------------------ "-PT4H"^^ (1 row) SELECT '-1 day -0.5 seconds'::interval::rdfnode; rdfnode ---------------------------------------------------------- "-P1DT0.5S"^^ (1 row) SELECT '1 day -0.5 seconds'::interval::rdfnode; rdfnode ---------------------------------------------------------- "P1DT-0.5S"^^ (1 row) SELECT '-1 month 30 minutes'::interval::rdfnode; rdfnode --------------------------------------------------------- "P-1MT30M"^^ (1 row) SELECT '1 day -1.5 seconds'::interval::rdfnode; rdfnode ---------------------------------------------------------- "P1DT-1.5S"^^ (1 row) SELECT '-1 day -1.5 seconds'::interval::rdfnode; rdfnode ---------------------------------------------------------- "-P1DT1.5S"^^ (1 row) SELECT '-1 day 0.5 seconds'::interval::rdfnode; rdfnode ---------------------------------------------------------- "P-1DT0.5S"^^ (1 row) /* NULL handling */ SELECT NULL::rdfnode; rdfnode --------- (1 row) SELECT NULL::text::rdfnode; rdfnode --------- (1 row) SELECT ''::rdfnode IS NOT NULL; -- empty string != NULL ?column? ---------- t (1 row) /* equality / ordering (RDF term equality vs value equality) */ SELECT '"1"^^xsd:int'::rdfnode = '"1"^^xsd:integer'::rdfnode; -- term !=, value = ?column? ---------- t (1 row) SELECT '"foo"@en'::rdfnode = '"foo"@EN'::rdfnode; -- lang tag case ?column? ---------- t (1 row) SELECT '"foo"'::rdfnode = '"foo"^^xsd:string'::rdfnode; -- RDF 1.1: equal ?column? ---------- t (1 row) /* long literals */ SELECT length(repeat('a', 1000000)::rdfnode::text); -- large literal length --------- 1000002 (1 row) SELECT length(sparql.strdt(repeat('a', 1000000)::rdfnode,'')::text); -- large literal with data type length --------- 1000045 (1 row) SELECT length(sparql.strlang(repeat('a', 1000000)::rdfnode,'de')::text); -- large literal with data type length --------- 1000005 (1 row) /* literals with malicious trailing content */ SELECT '"x"@en } ; INSERT DATA { "foo" }'::rdfnode; ERROR: invalid input syntax for type rdfnode: ""x"@en } ; INSERT DATA { "foo" }" LINE 2: SELECT '"x"@en } ; INSERT DATA { "foo"'::rdfnode; ERROR: invalid input syntax for type rdfnode: ""x" } ; INSERT DATA { "foo"" LINE 1: SELECT '"x" } ; INSERT DATA { "foo"'::rdfnode; ERROR: invalid input syntax for type rdfnode: ""42"^^xsd:long } ; INSERT DATA { "foo"" LINE 1: SELECT '"42"^^xsd:long } ; INSERT DATA { } ; INSERT DATA { "foo"'::rdfnode; ERROR: invalid input syntax for type rdfnode: ""42"^^ } ; INSERT DATA { "foo"" LINE 1: SELECT '"42"^^ } ; I... ^ DETAIL: Trailing content after RDF literal. SELECT '"9223372036854775.807"^^'::rdfnode::numeric; numeric ---------------------- 9223372036854775.807 (1 row) SELECT '"-9223372036854775.807"^^'::rdfnode::numeric; numeric ----------------------- -9223372036854775.807 (1 row) SELECT '"9223372036854775.807"^^'::rdfnode::numeric::rdfnode; rdfnode -------------------------------------------------------------------- "9223372036854775.807"^^ (1 row) SELECT '"-9223372036854775.807"^^'::rdfnode::numeric::rdfnode; rdfnode --------------------------------------------------------------------- "-9223372036854775.807"^^ (1 row) SELECT '"42.73"^^xsd:double'::rdfnode::double precision; float8 -------- 42.73 (1 row) SELECT '"-42.73"^^xsd:double'::rdfnode::double precision; float8 -------- -42.73 (1 row) SELECT '"42.73"^^xsd:double'::rdfnode::double precision::rdfnode; rdfnode ---------------------------------------------------- "42.73"^^ (1 row) SELECT '"-42.73"^^xsd:double'::rdfnode::double precision::rdfnode; rdfnode ----------------------------------------------------- "-42.73"^^ (1 row) SELECT '"4.2E1"^^xsd:double'::rdfnode::double precision; float8 -------- 42 (1 row) SELECT '"-4.2E1"^^xsd:double'::rdfnode::double precision; float8 -------- -42 (1 row) SELECT 42.73::real::rdfnode; rdfnode --------------------------------------------------- "42.73"^^ (1 row) SELECT 42.73::real::rdfnode::real; float4 -------- 42.73 (1 row) SELECT (-42.73)::real::rdfnode; rdfnode ---------------------------------------------------- "-42.73"^^ (1 row) SELECT (-42.73)::real::rdfnode::real; float4 -------- -42.73 (1 row) SELECT 'INF'::real::rdfnode; rdfnode ------------------------------------------------- "INF"^^ (1 row) SELECT 'INF'::real::rdfnode::real; float4 ---------- Infinity (1 row) SELECT '-INF'::real::rdfnode; rdfnode -------------------------------------------------- "-INF"^^ (1 row) SELECT '-INF'::real::rdfnode::real; float4 ----------- -Infinity (1 row) SELECT 'NaN'::real::rdfnode; rdfnode ------------------------------------------------- "NaN"^^ (1 row) SELECT 'NaN'::real::rdfnode::real; float4 -------- NaN (1 row) /* A float4 needs up to nine significant digits to survive a round trip, and * the serialisation has to be able to produce them. Only the round trip is * asserted here, not the text: the lexical form differs between servers that * print the shortest exact representation and older ones that print a fixed * number of digits, while the round trip holds on both. */ SET extra_float_digits = 3; SELECT v AS input, v::real::rdfnode::real = v::real AS round_trips FROM (VALUES ('1.1234567'),('3.4028235e38'),('1.2345679e-5'),('16777217'),('0.1')) t(v); input | round_trips --------------+------------- 1.1234567 | t 3.4028235e38 | t 1.2345679e-5 | t 16777217 | t 0.1 | t (5 rows) RESET extra_float_digits; SELECT 42::bigint::rdfnode; rdfnode ----------------------------------------------- "42"^^ (1 row) SELECT 42::bigint::rdfnode::bigint; int8 ------ 42 (1 row) SELECT (-42)::bigint::rdfnode; rdfnode ------------------------------------------------ "-42"^^ (1 row) SELECT (-42)::bigint::rdfnode::bigint; int8 ------ -42 (1 row) SELECT 42746357267238767::bigint::rdfnode; rdfnode -------------------------------------------------------------- "42746357267238767"^^ (1 row) SELECT 42746357267238767::bigint::rdfnode::bigint; int8 ------------------- 42746357267238767 (1 row) SELECT (-42746357267238767)::bigint::rdfnode; rdfnode --------------------------------------------------------------- "-42746357267238767"^^ (1 row) SELECT (-42746357267238767)::bigint::rdfnode::bigint; int8 -------------------- -42746357267238767 (1 row) SELECT 42::int::rdfnode; rdfnode ---------------------------------------------- "42"^^ (1 row) SELECT 42::int::rdfnode::int; int4 ------ 42 (1 row) SELECT (-42)::int::rdfnode; rdfnode ----------------------------------------------- "-42"^^ (1 row) SELECT (-42)::int::rdfnode::int; int4 ------ -42 (1 row) SELECT 427463::int::rdfnode; rdfnode -------------------------------------------------- "427463"^^ (1 row) SELECT 427463::int::rdfnode::int; int4 -------- 427463 (1 row) SELECT (-427463)::int::rdfnode; rdfnode --------------------------------------------------- "-427463"^^ (1 row) SELECT (-427463)::int::rdfnode::int; int4 --------- -427463 (1 row) SELECT 42::smallint::rdfnode; rdfnode ------------------------------------------------ "42"^^ (1 row) SELECT 42::smallint::rdfnode::smallint; int2 ------ 42 (1 row) SELECT (-42)::smallint::rdfnode; rdfnode ------------------------------------------------- "-42"^^ (1 row) SELECT (-42)::smallint::rdfnode::smallint; int2 ------ -42 (1 row) SELECT 4273::smallint::rdfnode; rdfnode -------------------------------------------------- "4273"^^ (1 row) SELECT 4273::smallint::rdfnode::smallint; int2 ------ 4273 (1 row) SELECT (-4273)::smallint::rdfnode; rdfnode --------------------------------------------------- "-4273"^^ (1 row) SELECT (-4273)::smallint::rdfnode::smallint; int2 ------- -4273 (1 row) /* timestamp <-> rdfnode round-trip */ SELECT '2025-04-25 18:44:38.149101+00'::timestamptz::rdfnode; rdfnode ---------------------------------------------------------------------------- "2025-04-25T18:44:38.149101Z"^^ (1 row) SELECT '2025-04-25 18:44:38.149101+00'::timestamptz::rdfnode::timestamptz; timestamptz ------------------------------- 2025-04-25 18:44:38.149101+00 (1 row) SELECT '2025-04-25 18:44:38'::timestamptz::rdfnode; rdfnode --------------------------------------------------------------------- "2025-04-25T18:44:38Z"^^ (1 row) SELECT '2025-04-25 18:44:38'::timestamptz::rdfnode::timestamptz; timestamptz ------------------------ 2025-04-25 18:44:38+00 (1 row) SELECT '2025-04-25 18:44:38'::timestamp::rdfnode; rdfnode -------------------------------------------------------------------- "2025-04-25T18:44:38"^^ (1 row) SELECT '2025-04-25 18:44:38'::timestamp::rdfnode::timestamp; timestamp --------------------- 2025-04-25 18:44:38 (1 row) SELECT '2025-04-25 18:44:38'::timestamp::rdfnode; rdfnode -------------------------------------------------------------------- "2025-04-25T18:44:38"^^ (1 row) SELECT '2025-04-25 18:44:38'::timestamp::rdfnode::timestamp; timestamp --------------------- 2025-04-25 18:44:38 (1 row) SELECT '"0000-01-01T00:00:00"^^'::rdfnode::timestamp; timestamp ------------------------ 0001-01-01 00:00:00 BC (1 row) SELECT '"-0043-03-15T00:00:00"^^'::rdfnode::timestamp; timestamp ------------------------ 0044-03-15 00:00:00 BC (1 row) SELECT '"0000-12-31T23:59:59.999999"^^'::rdfnode::timestamp::rdfnode; rdfnode --------------------------------------------------------------------------- "0000-12-31T23:59:59.999999"^^ (1 row) SELECT '0044-03-15 BC'::timestamp::rdfnode; rdfnode --------------------------------------------------------------------- "-0043-03-15T00:00:00"^^ (1 row) SELECT '0044-03-15 BC'::timestamp::rdfnode::timestamp; timestamp ------------------------ 0044-03-15 00:00:00 BC (1 row) SELECT '0044-03-15 18:33:45 BC'::timestamp::rdfnode; rdfnode --------------------------------------------------------------------- "-0043-03-15T18:33:45"^^ (1 row) SELECT '0001-01-01 AD'::timestamp::rdfnode; rdfnode -------------------------------------------------------------------- "0001-01-01T00:00:00"^^ (1 row) SELECT '0001-01-01 BC'::timestamp::rdfnode; rdfnode -------------------------------------------------------------------- "0000-01-01T00:00:00"^^ (1 row) SELECT '0002-01-01 BC'::timestamp::rdfnode; rdfnode --------------------------------------------------------------------- "-0001-01-01T00:00:00"^^ (1 row) SELECT '0003-01-01 BC'::timestamp::rdfnode; rdfnode --------------------------------------------------------------------- "-0002-01-01T00:00:00"^^ (1 row) SELECT '0044-03-15 BC'::timestamp::rdfnode; rdfnode --------------------------------------------------------------------- "-0043-03-15T00:00:00"^^ (1 row) SELECT '0044-03-15 BC'::timestamp::rdfnode::timestamp; timestamp ------------------------ 0044-03-15 00:00:00 BC (1 row) SELECT '0044-03-15 18:33:45 BC'::timestamp::rdfnode::timestamp; timestamp ------------------------ 0044-03-15 18:33:45 BC (1 row) SELECT '0001-01-01 AD'::timestamp::rdfnode::timestamp; timestamp --------------------- 0001-01-01 00:00:00 (1 row) SELECT '0001-01-01 BC'::timestamp::rdfnode::timestamp; timestamp ------------------------ 0001-01-01 00:00:00 BC (1 row) SELECT '0002-01-01 BC'::timestamp::rdfnode::timestamp; timestamp ------------------------ 0002-01-01 00:00:00 BC (1 row) SELECT '0003-01-01 BC'::timestamp::rdfnode::timestamp; timestamp ------------------------ 0003-01-01 00:00:00 BC (1 row) SELECT '0044-03-15 BC'::timestamp::rdfnode::timestamp; timestamp ------------------------ 0044-03-15 00:00:00 BC (1 row) SELECT '0044-03-15 18:33:45.123456 BC'::timestamp::rdfnode::timestamp; timestamp ------------------------------- 0044-03-15 18:33:45.123456 BC (1 row) /* timestamptz <-> rdfnode round-trip */ SET timezone = 'America/New_York'; SELECT '2025-04-25 18:44:38'::timestamptz::rdfnode; rdfnode --------------------------------------------------------------------- "2025-04-25T22:44:38Z"^^ (1 row) SELECT '2025-04-25 18:44:38'::timestamptz::rdfnode::timestamptz; timestamptz ------------------------ 2025-04-25 18:44:38-04 (1 row) SET timezone = 'UTC'; SELECT '"0000-01-01T00:00:00Z"^^'::rdfnode::timestamptz; timestamptz --------------------------- 0001-01-01 00:00:00+00 BC (1 row) SELECT '"-0001-01-01T00:00:00Z"^^'::rdfnode::timestamptz; timestamptz --------------------------- 0002-01-01 00:00:00+00 BC (1 row) SELECT '"-0043-03-15T12:34:56+02:00"^^'::rdfnode::timestamptz; timestamptz --------------------------- 0044-03-15 10:34:56+00 BC (1 row) SELECT '"-0043-03-15T12:34:56-05:00"^^'::rdfnode::timestamptz; timestamptz --------------------------- 0044-03-15 17:34:56+00 BC (1 row) SELECT '"0000-01-01T00:00:00Z"^^'::rdfnode::timestamptz::rdfnode; rdfnode --------------------------------------------------------------------- "0000-01-01T00:00:00Z"^^ (1 row) SELECT '0044-03-15 BC'::timestamptz::rdfnode; rdfnode ---------------------------------------------------------------------- "-0043-03-15T00:00:00Z"^^ (1 row) SELECT '0044-03-15 18:33:45 BC'::timestamptz::rdfnode; rdfnode ---------------------------------------------------------------------- "-0043-03-15T18:33:45Z"^^ (1 row) SELECT '0001-01-01 AD'::timestamptz::rdfnode; rdfnode --------------------------------------------------------------------- "0001-01-01T00:00:00Z"^^ (1 row) SELECT '0001-01-01 BC'::timestamptz::rdfnode; rdfnode --------------------------------------------------------------------- "0000-01-01T00:00:00Z"^^ (1 row) SELECT '0002-01-01 BC'::timestamptz::rdfnode; rdfnode ---------------------------------------------------------------------- "-0001-01-01T00:00:00Z"^^ (1 row) SELECT '0003-01-01 BC'::timestamptz::rdfnode; rdfnode ---------------------------------------------------------------------- "-0002-01-01T00:00:00Z"^^ (1 row) SELECT '0044-03-15 BC'::timestamptz::rdfnode; rdfnode ---------------------------------------------------------------------- "-0043-03-15T00:00:00Z"^^ (1 row) SELECT '0044-03-15 BC'::timestamptz::rdfnode::timestamptz; timestamptz --------------------------- 0044-03-15 00:00:00+00 BC (1 row) SELECT '0044-03-15 18:33:45 BC'::timestamptz::rdfnode::timestamptz; timestamptz --------------------------- 0044-03-15 18:33:45+00 BC (1 row) SELECT '0001-01-01 AD'::timestamptz::rdfnode::timestamptz; timestamptz ------------------------ 0001-01-01 00:00:00+00 (1 row) SELECT '0001-01-01 BC'::timestamptz::rdfnode::timestamptz; timestamptz --------------------------- 0001-01-01 00:00:00+00 BC (1 row) SELECT '0002-01-01 BC'::timestamptz::rdfnode::timestamptz; timestamptz --------------------------- 0002-01-01 00:00:00+00 BC (1 row) SELECT '0003-01-01 BC'::timestamptz::rdfnode::timestamptz; timestamptz --------------------------- 0003-01-01 00:00:00+00 BC (1 row) SELECT '0044-03-15 BC'::timestamptz::rdfnode::timestamptz; timestamptz --------------------------- 0044-03-15 00:00:00+00 BC (1 row) SELECT '0044-03-15 18:33:45.123456 BC'::timestamptz::rdfnode::timestamptz; timestamptz ---------------------------------- 0044-03-15 18:33:45.123456+00 BC (1 row) /* date <-> rdfnode round-trip */ SELECT '2020-05-12'::date::rdfnode; rdfnode ------------------------------------------------------- "2020-05-12"^^ (1 row) SELECT '0001-01-01 AD'::date::rdfnode; rdfnode ------------------------------------------------------- "0001-01-01"^^ (1 row) SELECT '0001-01-01 BC'::date::rdfnode; rdfnode ------------------------------------------------------- "0000-01-01"^^ (1 row) SELECT '0002-01-01 BC'::date::rdfnode; rdfnode -------------------------------------------------------- "-0001-01-01"^^ (1 row) SELECT '0003-01-01 BC'::date::rdfnode; rdfnode -------------------------------------------------------- "-0002-01-01"^^ (1 row) SELECT '0044-03-15 BC'::date::rdfnode; rdfnode -------------------------------------------------------- "-0043-03-15"^^ (1 row) SELECT '2020-05-12'::date::rdfnode::date; date ------------ 2020-05-12 (1 row) SELECT '0001-01-01 AD'::date::rdfnode::date; date ------------ 0001-01-01 (1 row) SELECT '0001-01-01 BC'::date::rdfnode::date; date --------------- 0001-01-01 BC (1 row) SELECT '0002-01-01 BC'::date::rdfnode::date; date --------------- 0002-01-01 BC (1 row) SELECT '0003-01-01 BC'::date::rdfnode::date; date --------------- 0003-01-01 BC (1 row) SELECT '0044-03-15 BC'::date::rdfnode::date; date --------------- 0044-03-15 BC (1 row) SELECT '"0000-01-01"^^'::rdfnode::date; date --------------- 0001-01-01 BC (1 row) SELECT '"-0043-03-15"^^'::rdfnode::date; date --------------- 0044-03-15 BC (1 row) SELECT '"0000-01-01"^^'::rdfnode::date::rdfnode; rdfnode ------------------------------------------------------- "0000-01-01"^^ (1 row) SELECT '18:44:38'::time::rdfnode; rdfnode ----------------------------------------------------- "18:44:38"^^ (1 row) SELECT '18:44:38'::time::rdfnode::time; time ---------- 18:44:38 (1 row) SELECT '00:00:00'::time::rdfnode; rdfnode ----------------------------------------------------- "00:00:00"^^ (1 row) SELECT '00:00:00'::time::rdfnode::time; time ---------- 00:00:00 (1 row) SELECT '04:05:06-08:00'::timetz::rdfnode; rdfnode ----------------------------------------------------------- "04:05:06-08:00"^^ (1 row) SELECT '04:05:06-08:00'::timetz::rdfnode::timetz; timetz ------------- 04:05:06-08 (1 row) SELECT '04:05:06 PST'::timetz::rdfnode; rdfnode ----------------------------------------------------------- "04:05:06-08:00"^^ (1 row) SELECT '04:05:06 PST'::timetz::rdfnode::timetz; timetz ------------- 04:05:06-08 (1 row) SELECT '18:30:25.123456+02:00'::timetz::rdfnode; rdfnode ------------------------------------------------------------------ "18:30:25.123456+02:00"^^ (1 row) SELECT true::rdfnode; rdfnode ---------------------------------------------------- "true"^^ (1 row) SELECT false::rdfnode; rdfnode ----------------------------------------------------- "false"^^ (1 row) SELECT true::rdfnode::boolean; bool ------ t (1 row) SELECT false::rdfnode::boolean; bool ------ f (1 row) SELECT (1=1)::rdfnode::boolean; bool ------ t (1 row) SELECT (1<>1)::rdfnode::boolean; bool ------ f (1 row) SELECT '1 day'::interval::rdfnode; rdfnode ---------------------------------------------------- "P1D"^^ (1 row) SELECT '1 hour 30 minutes'::interval::rdfnode; rdfnode -------------------------------------------------------- "PT1H30M"^^ (1 row) SELECT '2 years 3 months'::interval::rdfnode; rdfnode ------------------------------------------------------ "P2Y3M"^^ (1 row) SELECT '5 days 12 hours'::interval::rdfnode; rdfnode -------------------------------------------------------- "P5DT12H"^^ (1 row) SELECT '1 year 2 months 3 days 4 hours 5 minutes 6 seconds'::interval::rdfnode; rdfnode --------------------------------------------------------------- "P1Y2M3DT4H5M6S"^^ (1 row) SELECT '5.123456 seconds'::interval::rdfnode; rdfnode ------------------------------------------------------------ "PT5.123456S"^^ (1 row) SELECT '0.000001 seconds'::interval::rdfnode; rdfnode ------------------------------------------------------------ "PT0.000001S"^^ (1 row) SELECT '1 minute 0.5 seconds'::interval::rdfnode; rdfnode --------------------------------------------------------- "PT1M0.5S"^^ (1 row) SELECT '-1 year -2 months'::interval::rdfnode; rdfnode ------------------------------------------------------- "-P1Y2M"^^ (1 row) SELECT '-3 days -4 hours'::interval::rdfnode; rdfnode -------------------------------------------------------- "-P3DT4H"^^ (1 row) SELECT '0 seconds'::interval::rdfnode; rdfnode ----------------------------------------------------- "PT0S"^^ (1 row) SELECT '1 year 2 months 3 days 4 hours 5 minutes 6 seconds'::interval::rdfnode::interval; interval ---------------------------------------------- @ 1 year 2 mons 3 days 4 hours 5 mins 6 secs (1 row) SELECT '-1 year -2 months'::interval::rdfnode::interval; interval --------------------- @ 1 year 2 mons ago (1 row) SELECT '5.123456 seconds'::interval::rdfnode::interval; interval ----------------- @ 5.123456 secs (1 row) SELECT '0 seconds'::interval::rdfnode::interval; interval ---------- @ 0 (1 row) SELECT '0.000001 seconds'::interval::rdfnode::interval; interval ----------------- @ 0.000001 secs (1 row) /* backslash runs of every parity, 0 through 8, immediately preceding * the closing quote of an otherwise plain literal */ SELECT n, (('"' || repeat('\', n) || '"'))::rdfnode = ((('"' || repeat('\', n) || '"'))::rdfnode)::text::rdfnode AS stable_roundtrip FROM generate_series(0, 8) AS n ORDER BY n; n | stable_roundtrip ---+------------------ 0 | t 1 | t 2 | t 3 | t 4 | t 5 | t 6 | t 7 | t 8 | t (9 rows) /* the check above only says the value stops changing after the first * conversion, which an inflated value also satisfies. This one pins the * result to the input. An even-length backslash run leaves the closing quote * unescaped, so the literal is well formed and must come back exactly as it * was written; an odd-length run escapes it, so the literal is unterminated * and is deliberately re-escaped as raw content. Either way the result is * determined by the input alone. */ SELECT n, length('"' || repeat('\', n) || '"') AS in_len, length((('"' || repeat('\', n) || '"'))::rdfnode::text) AS out_len, (('"' || repeat('\', n) || '"'))::rdfnode::text = ('"' || repeat('\', n) || '"') AS unchanged FROM generate_series(0, 8) AS n ORDER BY n; n | in_len | out_len | unchanged ---+--------+---------+----------- 0 | 2 | 2 | t 1 | 3 | 6 | f 2 | 4 | 4 | t 3 | 5 | 8 | f 4 | 6 | 6 | t 5 | 7 | 10 | f 6 | 8 | 8 | t 7 | 9 | 12 | f 8 | 10 | 10 | t (9 rows) /* the exact payload from the original report: a lexical value that is * two literal backslash characters */ SELECT (('"' || repeat('\', 4) || '"'))::rdfnode; rdfnode --------- "\\\\" (1 row) /* same backslash run, but immediately followed by a language tag -- * must not be confused with part of the escape run */ SELECT (('"' || repeat('\', 4) || '"@en'))::rdfnode; rdfnode ----------- "\\\\"@en (1 row) /* trailing content after a closing quote + language tag is rejected, not silently accepted */ SELECT '"x"@en } ; INSERT DATA { } #'::rdfnode; ERROR: invalid input syntax for type rdfnode: ""x"@en } ; INSERT DATA { } #" LINE 2: SELECT '"x"@en } ; INSERT DATA { .'), (''), (''), ('_:b1 x'), ('_:.b1'), ('_:b1.')) v(t); input | stored | is_iri | is_blank -----------------------------------+-------------------------------------+--------+---------- . | "." | f | f | "" | f | f | "" | f | f _:b1 x | "_:b1 x" | f | f _:.b1 | "_:.b1" | f | f _:b1. | "_:b1." | f | f (6 rows) /* well-formed IRIs and blank nodes are unaffected */ SELECT ''::rdfnode::text AS good_iri, '_:b1'::rdfnode::text AS good_blank, sparql.isiri(''::rdfnode) AS iri_ok, sparql.isblank('_:abc.def'::rdfnode) AS blank_ok; good_iri | good_blank | iri_ok | blank_ok -------------------+------------+--------+---------- | _:b1 | t | t (1 row) /* ------------------------------------------------------------------ * * isNumeric() validates the lexical form against the datatype, so a * value that is not in the datatype's XSD lexical space is not numeric. * Surrounding whitespace is not such a value: every XSD numeric datatype * fixes whiteSpace="collapse", so " 12" is a valid xsd:integer. * ------------------------------------------------------------------ */ SELECT t AS term, sparql.isnumeric(t::rdfnode) AS is_numeric FROM (VALUES ('"0x10"^^xsd:integer'), ('"1.5"^^xsd:integer'), ('"nan"^^xsd:integer'), ('" 12"^^xsd:integer'), ('" 1.5 "^^xsd:decimal'), ('"42"^^xsd:integer'), ('"-3.14"^^xsd:decimal'), ('"1.5e10"^^xsd:double'), ('"INF"^^xsd:double'), ('"+INF"^^xsd:double'), ('"INF"^^xsd:decimal'), ('"NaN"^^xsd:float')) v(t) ORDER BY term COLLATE "C"; ERROR: column "term" does not exist LINE 20: ORDER BY term COLLATE "C"; ^ /* a collapsed lexical form is a number, and behaves as one */ SELECT '" 12"^^xsd:integer'::rdfnode + '"1"^^xsd:integer'::rdfnode AS ws_arith, '" 12"^^xsd:integer'::rdfnode = '"12"^^xsd:integer'::rdfnode AS ws_equals; ws_arith | ws_equals --------------------------------------------------+----------- "13"^^ | t (1 row) /* ------------------------------------------------------------------ * * The integer subtypes share one lexical space but not one value space, * so a value outside the subtype's range is ill-typed, not numeric. * ------------------------------------------------------------------ */ SELECT t AS term, sparql.isnumeric(t::rdfnode) AS is_numeric FROM (VALUES ('"1200"^^xsd:byte'), ('"100"^^xsd:byte'), ('"99999"^^xsd:short'), ('"-5"^^xsd:nonNegativeInteger'), ('"0"^^xsd:nonNegativeInteger'), ('"0"^^xsd:positiveInteger'), ('"1"^^xsd:positiveInteger'), ('"5"^^xsd:negativeInteger'), ('"-1"^^xsd:unsignedByte'), ('"-0"^^xsd:unsignedByte'), ('"300"^^xsd:unsignedByte'), ('"255"^^xsd:unsignedByte'), ('"99999999999999999999999"^^xsd:integer')) v(t) ORDER BY term COLLATE "C"; ERROR: column "term" does not exist LINE 19: ORDER BY term COLLATE "C"; ^ /* ------------------------------------------------------------------ * * A lexical form may contain '@' and '^^'. The annotation is what * follows the closing quote, so neither may be taken for its start: the * first two used to lose their language tag or datatype, the others * were cut at the '^^'. * ------------------------------------------------------------------ */ SELECT '"a@b"@en'::rdfnode AS at_in_tagged, '"a@b.org"^^'::rdfnode AS at_in_typed, '"foo^^bar"@en'::rdfnode AS caret_in_tagged, '"^^b"@en'::rdfnode AS caret_first, '"@en"@de'::rdfnode AS tag_as_content; at_in_tagged | at_in_typed | caret_in_tagged | caret_first | tag_as_content --------------+------------------------------------+-----------------+-------------+---------------- "a@b"@en | "a@b.org"^^ | "foo^^bar"@en | "^^b"@en | "@en"@de (1 row) /* ------------------------------------------------------------------ * * A dateTime or time without an offset is read in the session's * TimeZone when it is cast to, or compared with, timestamptz or * timetz, so the result changes with that setting ... * ------------------------------------------------------------------ */ SET timezone = 'UTC'; SELECT '"2020-01-01T00:00:00"^^xsd:dateTime'::rdfnode = '2020-01-01 00:00:00+00'::timestamptz AS eq_in_utc, '"12:00:00"^^xsd:time'::rdfnode = '12:00:00+00'::timetz AS time_eq_in_utc; eq_in_utc | time_eq_in_utc -----------+---------------- t | t (1 row) SET timezone = 'Asia/Tokyo'; SELECT '"2020-01-01T00:00:00"^^xsd:dateTime'::rdfnode = '2020-01-01 00:00:00+00'::timestamptz AS eq_in_tokyo, '"12:00:00"^^xsd:time'::rdfnode = '12:00:00+00'::timetz AS time_eq_in_tokyo; eq_in_tokyo | time_eq_in_tokyo -------------+------------------ f | f (1 row) SET timezone = 'UTC'; /* ... and those functions are STABLE, as PostgreSQL's own timestamp -> * timestamptz cast is: they used to be IMMUTABLE, which let them into * generated columns and index expressions. Only the conversions to rdfnode, * which write the offset out, and sparql.datatype() do not depend on it. * sparql.describe() returns what the endpoint holds, so it is VOLATILE. */ SELECT p.oid::regprocedure AS function, p.provolatile AS volatility FROM pg_proc AS p JOIN pg_depend AS d ON d.classid = 'pg_proc'::regclass AND d.objid = p.oid AND d.deptype = 'e' JOIN pg_extension AS e ON e.oid = d.refobjid AND e.extname = 'rdf_fdw' WHERE (p.prorettype IN ('timestamptz'::regtype, 'timetz'::regtype) OR 'timestamptz'::regtype = ANY (p.proargtypes::oid[]) OR 'timetz'::regtype = ANY (p.proargtypes::oid[]) OR p.proname = 'describe') AND p.provolatile <> 's' ORDER BY p.oid::regprocedure::text COLLATE "C"; function | volatility --------------------------------------------------+------------ sparql.datatype(time with time zone) | i sparql.datatype(timestamp with time zone) | i sparql.describe(text,text,text) | v timestamptz_to_rdfnode(timestamp with time zone) | i timetz_to_rdfnode(time with time zone) | i (5 rows)