SET datestyle = 'ISO, YMD'; SET timezone = 'UTC'; SELECT '"foo"'::rdfnode; SELECT '"foo"^^xsd:string'::rdfnode; SELECT '"foo"@es'::rdfnode; SELECT '"foo"@es'::rdfnode::text; SELECT '"foo"@es'::rdfnode::text::rdfnode; SELECT '"foo"^^xsd:string'::rdfnode::text::rdfnode; SELECT '"nan"^^xsd:double'::rdfnode; SELECT '"NAN"^^xsd:double'::rdfnode; SELECT '"nAn"^^xsd:double'::rdfnode; SELECT '"forty-two"^^xsd:int'::rdfnode; -- works despite invalid lexical form for int SELECT '"invalid"^^xsd:dateTime'::rdfnode; -- works despite invalid lexical form for dateTime SELECT '"25:00:00"^^xsd:time'::rdfnode; SELECT '"2025-13-01"^^xsd:date'::rdfnode; SELECT '"abc"^^invalid:datatype'::rdfnode; SELECT '"abc"@invalid_lang'::rdfnode; SELECT '"foo'::rdfnode; SELECT 'f"o"o'::rdfnode; SELECT '𝄞'::rdfnode; SELECT ''::rdfnode; SELECT '"'::rdfnode; SELECT '"\""'::rdfnode; SELECT '😀'::rdfnode; SELECT '"x^^y"'::rdfnode; -- "x^^y" SELECT '"a\\"b"@en'::rdfnode; -- "a\"b"@en SELECT '"𝄞"^^'::rdfnode; /* language tags (BCP 47) */ SELECT '"foo"@'::rdfnode; -- invalid: empty tag SELECT '"foo"@en-US'::rdfnode; -- valid SELECT '"foo"@en-Latn-US-valencia'::rdfnode; -- valid extended BCP 47 SELECT '"foo"@123'::rdfnode; -- invalid: must start with letter SELECT '"foo"@EN'::rdfnode; -- valid; the tag is lowercased /* * 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; 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; SELECT '"foo"@en-us'::rdfnode; -- subtags after the primary are stored as-is (no region canonicalization) SELECT '"foo"@en-'::rdfnode; -- invalid trailing hyphen /* IRIs and blank nodes */ SELECT ''::rdfnode; SELECT ''::rdfnode; SELECT ''::rdfnode; -- invalid: spaces in IRI SELECT '<>'::rdfnode; -- relative empty IRI SELECT '_:b1'::rdfnode; -- blank node SELECT '_:'::rdfnode; -- invalid blank node label /* numeric edge cases */ SELECT '"+42"^^xsd:int'::rdfnode; -- explicit plus (valid) SELECT '"042"^^xsd:int'::rdfnode; -- leading zeros (valid lex, non-canonical) SELECT '"2147483647"^^xsd:int'::rdfnode::int; -- INT_MAX SELECT '"2147483648"^^xsd:int'::rdfnode; -- works despite overflow SELECT '"-2147483648"^^xsd:int'::rdfnode::int; -- INT_MIN SELECT '"3.14e0"^^xsd:double'::rdfnode; -- exponent SELECT '"3."^^xsd:decimal'::rdfnode; -- XSD 1.1: invalid; XSD 1.0: valid SELECT '"-0"^^xsd:integer'::rdfnode; -- valid lex, canonical is "0" SELECT '"1.0"^^xsd:integer'::rdfnode; -- works despite invalid lexical form for integer /* xsd:boolean alternative lexical forms */ SELECT '"1"^^xsd:boolean'::rdfnode::boolean; -- true SELECT '"0"^^xsd:boolean'::rdfnode::boolean; -- false SELECT '"TRUE"^^xsd:boolean'::rdfnode; -- invalid (case-sensitive) SELECT '"yes"^^xsd:boolean'::rdfnode; -- invalid /* date/time variations */ SELECT '"2025-04-25T18:44:38+02:00"^^xsd:dateTime'::rdfnode; SELECT '"2025-04-25T18:44:38.123Z"^^xsd:dateTime'::rdfnode; SELECT '"-0044-03-15T12:00:00Z"^^xsd:dateTime'::rdfnode; -- BCE SELECT '"2025-04-25Z"^^xsd:date'::rdfnode; SELECT '"2025-04-25-08:00"^^xsd:date'::rdfnode; SELECT '"24:00:00"^^xsd:time'::rdfnode; -- XSD 1.1: valid; XSD 1.0: invalid SELECT '"2025-02-29"^^xsd:date'::rdfnode; -- works despite not being a leap year SELECT '"2024-02-29"^^xsd:date'::rdfnode; -- leap year /* gYear / gMonth / gDay */ SELECT '"2025"^^'::rdfnode; SELECT '"--04"^^'::rdfnode; SELECT '"---25"^^'::rdfnode; SELECT '"2025-04"^^'::rdfnode; /* escape sequences inside literals */ SELECT E'"line1\\nline2"'::rdfnode; -- \n inside literal SELECT E'"tab\\there"'::rdfnode; -- \t SELECT E'"\\u00E9"'::rdfnode; -- é SELECT E'"\\U0001F600"'::rdfnode; -- 😀 SELECT E'"backslash\\\\test"'::rdfnode; -- \\ SELECT E'"quote\\""'::rdfnode; -- \" /* whitespace handling */ SELECT ' "foo"@en '::rdfnode; -- leading/trailing with language tag SELECT ' "foo"^^xsd:int '::rdfnode; -- leading/trailing with data type SELECT '"foo" @en'::rdfnode; -- space before @ -> invalid SELECT '"foo"^^ xsd:string'::rdfnode; -- space after ^^ -> invalid SELECT E'"foo"\n@en'::rdfnode; -- newline before @ -> invalid SELECT 'foo"bar"@en'::rdfnode; -- embedded quote + lang-looking suffix: keep verbatim SELECT 'x\"y'::rdfnode; -- already-escaped text, must NOT double-escape /* datatype IRI variants */ SELECT '"abc"^^<>'::rdfnode; -- empty datatype IRI SELECT '"abc"^^'::rdfnode; -- invalid IRI SELECT '"abc"^^xsd:'::rdfnode; -- empty local part SELECT '"abc"^^:bar'::rdfnode; -- empty prefix /* interval with mixed signs */ SELECT '1 year -3 days'::interval::rdfnode; SELECT '-1 day 12 hours'::interval::rdfnode; SELECT '3 days -4 hours'::interval::rdfnode; SELECT '-2 months 5 days'::interval::rdfnode; SELECT '1 month -30 minutes'::interval::rdfnode; SELECT '-4 hours'::interval::rdfnode; SELECT '-1 day -0.5 seconds'::interval::rdfnode; SELECT '1 day -0.5 seconds'::interval::rdfnode; SELECT '-1 month 30 minutes'::interval::rdfnode; SELECT '1 day -1.5 seconds'::interval::rdfnode; SELECT '-1 day -1.5 seconds'::interval::rdfnode; SELECT '-1 day 0.5 seconds'::interval::rdfnode; /* NULL handling */ SELECT NULL::rdfnode; SELECT NULL::text::rdfnode; SELECT ''::rdfnode IS NOT NULL; -- empty string != NULL /* equality / ordering (RDF term equality vs value equality) */ SELECT '"1"^^xsd:int'::rdfnode = '"1"^^xsd:integer'::rdfnode; -- term !=, value = SELECT '"foo"@en'::rdfnode = '"foo"@EN'::rdfnode; -- lang tag case SELECT '"foo"'::rdfnode = '"foo"^^xsd:string'::rdfnode; -- RDF 1.1: equal /* long literals */ SELECT length(repeat('a', 1000000)::rdfnode::text); -- large literal SELECT length(sparql.strdt(repeat('a', 1000000)::rdfnode,'')::text); -- large literal with data type SELECT length(sparql.strlang(repeat('a', 1000000)::rdfnode,'de')::text); -- large literal with data type /* literals with malicious trailing content */ SELECT '"x"@en } ; INSERT DATA { "foo" }'::rdfnode; SELECT '"x" } ; INSERT DATA { "foo"'::rdfnode; SELECT '"42"^^xsd:long } ; INSERT DATA { "foo"'::rdfnode; SELECT '"42"^^ } ; INSERT DATA { "foo"'::rdfnode; SELECT '"9223372036854775.807"^^'::rdfnode::numeric; SELECT '"-9223372036854775.807"^^'::rdfnode::numeric; SELECT '"9223372036854775.807"^^'::rdfnode::numeric::rdfnode; SELECT '"-9223372036854775.807"^^'::rdfnode::numeric::rdfnode; SELECT '"42.73"^^xsd:double'::rdfnode::double precision; SELECT '"-42.73"^^xsd:double'::rdfnode::double precision; SELECT '"42.73"^^xsd:double'::rdfnode::double precision::rdfnode; SELECT '"-42.73"^^xsd:double'::rdfnode::double precision::rdfnode; SELECT '"4.2E1"^^xsd:double'::rdfnode::double precision; SELECT '"-4.2E1"^^xsd:double'::rdfnode::double precision; SELECT 42.73::real::rdfnode; SELECT 42.73::real::rdfnode::real; SELECT (-42.73)::real::rdfnode; SELECT (-42.73)::real::rdfnode::real; SELECT 'INF'::real::rdfnode; SELECT 'INF'::real::rdfnode::real; SELECT '-INF'::real::rdfnode; SELECT '-INF'::real::rdfnode::real; SELECT 'NaN'::real::rdfnode; SELECT 'NaN'::real::rdfnode::real; /* 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); RESET extra_float_digits; SELECT 42::bigint::rdfnode; SELECT 42::bigint::rdfnode::bigint; SELECT (-42)::bigint::rdfnode; SELECT (-42)::bigint::rdfnode::bigint; SELECT 42746357267238767::bigint::rdfnode; SELECT 42746357267238767::bigint::rdfnode::bigint; SELECT (-42746357267238767)::bigint::rdfnode; SELECT (-42746357267238767)::bigint::rdfnode::bigint; SELECT 42::int::rdfnode; SELECT 42::int::rdfnode::int; SELECT (-42)::int::rdfnode; SELECT (-42)::int::rdfnode::int; SELECT 427463::int::rdfnode; SELECT 427463::int::rdfnode::int; SELECT (-427463)::int::rdfnode; SELECT (-427463)::int::rdfnode::int; SELECT 42::smallint::rdfnode; SELECT 42::smallint::rdfnode::smallint; SELECT (-42)::smallint::rdfnode; SELECT (-42)::smallint::rdfnode::smallint; SELECT 4273::smallint::rdfnode; SELECT 4273::smallint::rdfnode::smallint; SELECT (-4273)::smallint::rdfnode; SELECT (-4273)::smallint::rdfnode::smallint; /* timestamp <-> rdfnode round-trip */ SELECT '2025-04-25 18:44:38.149101+00'::timestamptz::rdfnode; SELECT '2025-04-25 18:44:38.149101+00'::timestamptz::rdfnode::timestamptz; SELECT '2025-04-25 18:44:38'::timestamptz::rdfnode; SELECT '2025-04-25 18:44:38'::timestamptz::rdfnode::timestamptz; SELECT '2025-04-25 18:44:38'::timestamp::rdfnode; SELECT '2025-04-25 18:44:38'::timestamp::rdfnode::timestamp; SELECT '2025-04-25 18:44:38'::timestamp::rdfnode; SELECT '2025-04-25 18:44:38'::timestamp::rdfnode::timestamp; SELECT '"0000-01-01T00:00:00"^^'::rdfnode::timestamp; SELECT '"-0043-03-15T00:00:00"^^'::rdfnode::timestamp; SELECT '"0000-12-31T23:59:59.999999"^^'::rdfnode::timestamp::rdfnode; SELECT '0044-03-15 BC'::timestamp::rdfnode; SELECT '0044-03-15 BC'::timestamp::rdfnode::timestamp; SELECT '0044-03-15 18:33:45 BC'::timestamp::rdfnode; SELECT '0001-01-01 AD'::timestamp::rdfnode; SELECT '0001-01-01 BC'::timestamp::rdfnode; SELECT '0002-01-01 BC'::timestamp::rdfnode; SELECT '0003-01-01 BC'::timestamp::rdfnode; SELECT '0044-03-15 BC'::timestamp::rdfnode; SELECT '0044-03-15 BC'::timestamp::rdfnode::timestamp; SELECT '0044-03-15 18:33:45 BC'::timestamp::rdfnode::timestamp; SELECT '0001-01-01 AD'::timestamp::rdfnode::timestamp; SELECT '0001-01-01 BC'::timestamp::rdfnode::timestamp; SELECT '0002-01-01 BC'::timestamp::rdfnode::timestamp; SELECT '0003-01-01 BC'::timestamp::rdfnode::timestamp; SELECT '0044-03-15 BC'::timestamp::rdfnode::timestamp; SELECT '0044-03-15 18:33:45.123456 BC'::timestamp::rdfnode::timestamp; /* timestamptz <-> rdfnode round-trip */ SET timezone = 'America/New_York'; SELECT '2025-04-25 18:44:38'::timestamptz::rdfnode; SELECT '2025-04-25 18:44:38'::timestamptz::rdfnode::timestamptz; SET timezone = 'UTC'; SELECT '"0000-01-01T00:00:00Z"^^'::rdfnode::timestamptz; SELECT '"-0001-01-01T00:00:00Z"^^'::rdfnode::timestamptz; SELECT '"-0043-03-15T12:34:56+02:00"^^'::rdfnode::timestamptz; SELECT '"-0043-03-15T12:34:56-05:00"^^'::rdfnode::timestamptz; SELECT '"0000-01-01T00:00:00Z"^^'::rdfnode::timestamptz::rdfnode; SELECT '0044-03-15 BC'::timestamptz::rdfnode; SELECT '0044-03-15 18:33:45 BC'::timestamptz::rdfnode; SELECT '0001-01-01 AD'::timestamptz::rdfnode; SELECT '0001-01-01 BC'::timestamptz::rdfnode; SELECT '0002-01-01 BC'::timestamptz::rdfnode; SELECT '0003-01-01 BC'::timestamptz::rdfnode; SELECT '0044-03-15 BC'::timestamptz::rdfnode; SELECT '0044-03-15 BC'::timestamptz::rdfnode::timestamptz; SELECT '0044-03-15 18:33:45 BC'::timestamptz::rdfnode::timestamptz; SELECT '0001-01-01 AD'::timestamptz::rdfnode::timestamptz; SELECT '0001-01-01 BC'::timestamptz::rdfnode::timestamptz; SELECT '0002-01-01 BC'::timestamptz::rdfnode::timestamptz; SELECT '0003-01-01 BC'::timestamptz::rdfnode::timestamptz; SELECT '0044-03-15 BC'::timestamptz::rdfnode::timestamptz; SELECT '0044-03-15 18:33:45.123456 BC'::timestamptz::rdfnode::timestamptz; /* date <-> rdfnode round-trip */ SELECT '2020-05-12'::date::rdfnode; SELECT '0001-01-01 AD'::date::rdfnode; SELECT '0001-01-01 BC'::date::rdfnode; SELECT '0002-01-01 BC'::date::rdfnode; SELECT '0003-01-01 BC'::date::rdfnode; SELECT '0044-03-15 BC'::date::rdfnode; SELECT '2020-05-12'::date::rdfnode::date; SELECT '0001-01-01 AD'::date::rdfnode::date; SELECT '0001-01-01 BC'::date::rdfnode::date; SELECT '0002-01-01 BC'::date::rdfnode::date; SELECT '0003-01-01 BC'::date::rdfnode::date; SELECT '0044-03-15 BC'::date::rdfnode::date; SELECT '"0000-01-01"^^'::rdfnode::date; SELECT '"-0043-03-15"^^'::rdfnode::date; SELECT '"0000-01-01"^^'::rdfnode::date::rdfnode; SELECT '18:44:38'::time::rdfnode; SELECT '18:44:38'::time::rdfnode::time; SELECT '00:00:00'::time::rdfnode; SELECT '00:00:00'::time::rdfnode::time; SELECT '04:05:06-08:00'::timetz::rdfnode; SELECT '04:05:06-08:00'::timetz::rdfnode::timetz; SELECT '04:05:06 PST'::timetz::rdfnode; SELECT '04:05:06 PST'::timetz::rdfnode::timetz; SELECT '18:30:25.123456+02:00'::timetz::rdfnode; SELECT true::rdfnode; SELECT false::rdfnode; SELECT true::rdfnode::boolean; SELECT false::rdfnode::boolean; SELECT (1=1)::rdfnode::boolean; SELECT (1<>1)::rdfnode::boolean; SELECT '1 day'::interval::rdfnode; SELECT '1 hour 30 minutes'::interval::rdfnode; SELECT '2 years 3 months'::interval::rdfnode; SELECT '5 days 12 hours'::interval::rdfnode; SELECT '1 year 2 months 3 days 4 hours 5 minutes 6 seconds'::interval::rdfnode; SELECT '5.123456 seconds'::interval::rdfnode; SELECT '0.000001 seconds'::interval::rdfnode; SELECT '1 minute 0.5 seconds'::interval::rdfnode; SELECT '-1 year -2 months'::interval::rdfnode; SELECT '-3 days -4 hours'::interval::rdfnode; SELECT '0 seconds'::interval::rdfnode; SELECT '1 year 2 months 3 days 4 hours 5 minutes 6 seconds'::interval::rdfnode::interval; SELECT '-1 year -2 months'::interval::rdfnode::interval; SELECT '5.123456 seconds'::interval::rdfnode::interval; SELECT '0 seconds'::interval::rdfnode::interval; SELECT '0.000001 seconds'::interval::rdfnode::interval; /* 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; /* 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; /* the exact payload from the original report: a lexical value that is * two literal backslash characters */ SELECT (('"' || repeat('\', 4) || '"'))::rdfnode; /* 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; /* trailing content after a closing quote + language tag is rejected, not silently accepted */ SELECT '"x"@en } ; INSERT DATA { } #'::rdfnode; /* already-escaped text must still not be double-escaped */ SELECT 'x\"y'::rdfnode; /* ------------------------------------------------------------------ * * Malformed IRIs and blank node labels are not classified as such, so * a term whose body could break out of its SPARQL token is stored as a * quoted literal instead (grammar rules [139] and [142]). * ------------------------------------------------------------------ */ SELECT t AS input, t::rdfnode::text AS stored, sparql.isiri(t::rdfnode) AS is_iri, sparql.isblank(t::rdfnode) AS is_blank FROM (VALUES ('.'), (''), (''), ('_:b1 x'), ('_:.b1'), ('_:b1.')) v(t); /* 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; /* ------------------------------------------------------------------ * * 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"; /* 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; /* ------------------------------------------------------------------ * * 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"; /* ------------------------------------------------------------------ * * 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; /* ------------------------------------------------------------------ * * 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; 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; 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";