SET timezone TO 'Etc/UTC'; /* * Pushdown regression tests. * All queries use EXPLAIN (VERBOSE, COSTS OFF) - no network calls are made. * This validates SQL-to-SPARQL translation for every pushable construct * without depending on any external triple store. */ CREATE SERVER test_server FOREIGN DATA WRAPPER rdf_fdw OPTIONS (endpoint 'http://localhost/sparql'); /* ---------------------------------------------------------------- * rdfnode_ft — rdfnode column pushdown tests * ---------------------------------------------------------------- */ CREATE FOREIGN TABLE rdfnode_ft ( p rdfnode OPTIONS (variable '?p'), o rdfnode OPTIONS (variable '?o') ) SERVER test_server OPTIONS ( sparql 'SELECT * WHERE { ?p ?o}'); /* rdfnode_opt_ft — BOUND / COALESCE tests (needs OPTIONAL binding) */ CREATE FOREIGN TABLE rdfnode_opt_ft ( p rdfnode OPTIONS (variable '?p'), o rdfnode OPTIONS (variable '?o'), x rdfnode OPTIONS (variable '?x') ) SERVER test_server OPTIONS ( sparql 'SELECT * WHERE { ?p ?o OPTIONAL {?o ?x}}'); /* ---------------------------------------------------------------- * pgtypes_ft — pg-typed column pushdown tests * ---------------------------------------------------------------- */ CREATE FOREIGN TABLE pgtypes_ft ( label text OPTIONS (variable '?label', language '*'), version bigint OPTIONS (variable '?version', literaltype 'xsd:integer'), num_smallint smallint OPTIONS (variable '?sint', literaltype 'xsd:short'), num_int int OPTIONS (variable '?int', literaltype 'xsd:int'), num_real real OPTIONS (variable '?real', literaltype 'xsd:float'), num_double double precision OPTIONS (variable '?double', literaltype 'xsd:double'), num_numeric numeric OPTIONS (variable '?numeric', literaltype 'xsd:decimal'), modified timestamp OPTIONS (variable '?modified',literaltype 'xsd:dateTime'), tstz timestamptz OPTIONS (variable '?tstz', literaltype 'xsd:dateTime'), dt date OPTIONS (variable '?dt', literaltype 'xsd:date'), ttz timetz OPTIONS (variable '?ttz', literaltype 'xsd:time'), bl boolean OPTIONS (variable '?bl', literaltype 'xsd:boolean'), type text OPTIONS (variable '?type', nodetype 'iri') ) SERVER test_server OPTIONS ( sparql 'SELECT * WHERE { ?p ?o}'); /* ================================================================ * SPARQL 15.5 - LIMIT * ================================================================ */ EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o FROM rdfnode_ft LIMIT 5; EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o FROM rdfnode_ft FETCH FIRST 5 ROWS ONLY; /* ================================================================ * SPARQL 15.4 - OFFSET * ================================================================ */ EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o FROM rdfnode_ft OFFSET 5 LIMIT 10; EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o FROM rdfnode_ft OFFSET 5 ROWS FETCH FIRST 10 ROWS ONLY; /* ================================================================ * SPARQL 15.1 - ORDER BY * ================================================================ */ EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o FROM rdfnode_ft ORDER BY p DESC LIMIT 3; EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o FROM rdfnode_ft ORDER BY o ASC LIMIT 3; EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o FROM rdfnode_ft ORDER BY p DESC, o ASC LIMIT 3; EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o FROM rdfnode_ft ORDER BY p DESC, o ASC OFFSET 5 LIMIT 2; EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o FROM rdfnode_ft ORDER BY 1 DESC, 2 ASC OFFSET 5 LIMIT 10; /* ================================================================ * SPARQL 18.2.5.3 - DISTINCT * ================================================================ */ EXPLAIN (VERBOSE, COSTS OFF) SELECT DISTINCT p FROM rdfnode_ft WHERE p = ''; -- DISTINCT ON is not supported and won't be pushed down EXPLAIN (VERBOSE, COSTS OFF) SELECT DISTINCT ON (p) p, o FROM rdfnode_ft WHERE p = ''; /* ================================================================ * SPARQL 17.4.1.7 - RDFterm-equal * ================================================================ */ EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o FROM rdfnode_ft WHERE p = sparql.iri('http://www.w3.org/2000/01/rdf-schema#label') AND o = '"hello"@en'; /* ================================================================ * SPARQL 17.4.1.9 - IN * ================================================================ */ EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o FROM rdfnode_ft WHERE p = sparql.iri('http://www.w3.org/2000/01/rdf-schema#label') AND o IN ('"hello"@en'::rdfnode, '"hello"@fr', sparql.strlang('hello', 'de')); /* ================================================================ * SPARQL 17.4.1.10 - NOT IN * ================================================================ */ EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o FROM rdfnode_ft WHERE p = sparql.iri('http://www.w3.org/2000/01/rdf-schema#label') AND o NOT IN ('"hello"@en'::rdfnode, '"hello"@fr', sparql.strlang('hello', 'de')) LIMIT 5; /* ================================================================ * SPARQL 17.3 - Operator Mapping (text op rdfnode) * ================================================================ */ EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o FROM rdfnode_ft WHERE p = '' AND o > '"a"' AND o < '"z"' AND o >= '"a"' AND o <= '"z"' AND o <> '"foo"' AND sparql.str(o) BETWEEN '"a"' AND '"z"' LIMIT 3; /* SPARQL 17.3 - Operator Mapping (rdfnode op rdfnode) */ EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o FROM rdfnode_ft WHERE p = ''::rdfnode AND o > '"a"'::rdfnode AND o < '"z"'::rdfnode AND o >= '"a"'::rdfnode AND o <= '"z"'::rdfnode AND o <> '"foo"'::rdfnode AND sparql.str(o) BETWEEN '"a"'::rdfnode AND '"z"'::rdfnode LIMIT 3; /* SPARQL 17.3 - Operator Mapping (smallint op rdfnode) */ EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o FROM rdfnode_ft WHERE p = ''::rdfnode AND o = 100::smallint AND o <> 999::smallint AND o > 10::smallint AND o < 999::smallint AND o >= 100::smallint AND o <= 100::smallint AND o BETWEEN 10::smallint AND 200::smallint AND 100::smallint = o; /* SPARQL 17.3 - Operator Mapping (int op rdfnode) */ EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o FROM rdfnode_ft WHERE p = ''::rdfnode AND o = 100::int AND o <> 999::int AND o > 10::int AND o < 999::int AND o >= 100::int AND o <= 100::int AND o BETWEEN 10::int AND 200::int AND 100::int = o; /* SPARQL 17.3 - Operator Mapping (bigint op rdfnode) */ EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o FROM rdfnode_ft WHERE p = ''::rdfnode AND o = 100::bigint AND o <> 999::bigint AND o > 10::bigint AND o < 999::bigint AND o >= 100::bigint AND o <= 100::bigint AND o BETWEEN 10::bigint AND 200::bigint AND 100::bigint = o; /* SPARQL 17.3 - Operator Mapping (real op rdfnode) */ EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o FROM rdfnode_ft WHERE p = ''::rdfnode AND o = 1.5::real AND o <> 9.9::real AND o > 1.0::real AND o < 9.9::real AND o >= 1.5::real AND o <= 1.5::real AND o BETWEEN 1.0::real AND 2.0::real AND 1.5::real = o; /* SPARQL 17.3 - Operator Mapping (double precision op rdfnode) */ EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o FROM rdfnode_ft WHERE p = ''::rdfnode AND o = 1.5::double precision AND o <> 9.9::double precision AND o > 1.0::double precision AND o < 9.9::double precision AND o >= 1.5::double precision AND o <= 1.5::double precision AND o BETWEEN 1.0::double precision AND 2.0::double precision AND 1.5::double precision = o; /* SPARQL 17.3 - Operator Mapping (numeric op rdfnode) */ EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o FROM rdfnode_ft WHERE p = ''::rdfnode AND o = 1.5::numeric AND o <> 9.9::numeric AND o > 1.0::numeric AND o < 9.9::numeric AND o >= 1.5::numeric AND o <= 1.5::numeric AND o BETWEEN 1.0::numeric AND 2.0::numeric AND 1.5::numeric = o; /* SPARQL 17.3 - Operator Mapping (arithmetic between rdfnode and a PostgreSQL number). An integer constant reaches the endpoint as an xsd:integer and a numeric one as an xsd:decimal, which is what the operator here computes with, so the FILTER and the operator agree. */ EXPLAIN (VERBOSE, COSTS OFF) SELECT o FROM rdfnode_ft WHERE o + 1 > '"10"^^xsd:integer'::rdfnode AND o * 2 < '"99"^^xsd:integer'::rdfnode AND 2 * o < '"99"^^xsd:integer'::rdfnode AND o + 1.5 > '"10"^^xsd:decimal'::rdfnode; /* an arithmetic operand is parenthesised, whichever side the number is on */ EXPLAIN (VERBOSE, COSTS OFF) SELECT o FROM rdfnode_ft WHERE (o + 1) * 2 > '"10"^^xsd:integer'::rdfnode; /* a float constant is sent as the typed literal the operator converts it to: SPARQL reads a bare 2.5 as an xsd:decimal, and the endpoint would then compute in a different datatype than the operator. NaN and infinity have no bare spelling at all. */ EXPLAIN (VERBOSE, COSTS OFF) SELECT o FROM rdfnode_ft WHERE o * 2.5::double precision > '"10"^^xsd:double'::rdfnode; EXPLAIN (VERBOSE, COSTS OFF) SELECT o FROM rdfnode_ft WHERE o + 2.5::real > '"10"^^xsd:float'::rdfnode; EXPLAIN (VERBOSE, COSTS OFF) SELECT o FROM rdfnode_ft WHERE 'NaN'::double precision * o = o AND o + '-Infinity'::real < o; /* SPARQL has no FILTER form for - and /, so both stay local */ EXPLAIN (VERBOSE, COSTS OFF) SELECT o FROM rdfnode_ft WHERE o - 1 > '"10"^^xsd:integer'::rdfnode; EXPLAIN (VERBOSE, COSTS OFF) SELECT o FROM rdfnode_ft WHERE o / 2 > '"10"^^xsd:integer'::rdfnode; /* SPARQL 17.3 - Operator Mapping (timestamp op rdfnode) */ EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o FROM rdfnode_ft WHERE p = ''::rdfnode AND o = '2015-01-01 00:00:00'::timestamp AND o <> '2020-01-01 00:00:00'::timestamp AND o > '2010-01-01 00:00:00'::timestamp AND o < '2020-01-01 00:00:00'::timestamp AND o >= '2015-01-01 00:00:00'::timestamp AND o <= '2015-01-01 00:00:00'::timestamp AND o BETWEEN '2010-01-01 00:00:00'::timestamp AND '2020-01-01 00:00:00'::timestamp AND '2015-01-01 00:00:00'::timestamp = o; /* SPARQL 17.3 - Operator Mapping (timestamptz op rdfnode) */ EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o FROM rdfnode_ft WHERE p = ''::rdfnode AND o = '2015-01-01 00:00:00'::timestamptz AND o <> '2020-01-01 00:00:00'::timestamptz AND o > '2010-01-01 00:00:00'::timestamptz AND o < '2020-01-01 00:00:00'::timestamptz AND o >= '2015-01-01 00:00:00'::timestamptz AND o <= '2015-01-01 00:00:00'::timestamptz AND o BETWEEN '2010-01-01 00:00:00'::timestamptz AND '2020-01-01 00:00:00'::timestamptz AND '2015-01-01 00:00:00'::timestamptz = o; /* SPARQL 17.3 - Operator Mapping (date op rdfnode, via strdt/substr/str) */ EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o FROM rdfnode_ft WHERE p = ''::rdfnode AND '2015-01-01'::date = sparql.strdt(sparql.substr(sparql.str(o), 1, 10), 'xsd:date') AND '2015-01-01'::date >= sparql.strdt(sparql.substr(sparql.str(o), 1, 10), 'xsd:date') AND '2020-01-01'::date > sparql.strdt(sparql.substr(sparql.str(o), 1, 10), 'xsd:date'); /* SPARQL 17.3 - Operator Mapping (timetz op rdfnode, via strdt/substr/str) */ EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o FROM rdfnode_ft WHERE p = ''::rdfnode AND '12:00:00 UTC'::timetz = sparql.strdt(sparql.substr(sparql.str(o), 12, 8), 'xsd:time') AND '12:00:00 UTC'::timetz >= sparql.strdt(sparql.substr(sparql.str(o), 12, 8), 'xsd:time') AND '23:00:00 UTC'::timetz > sparql.strdt(sparql.substr(sparql.str(o), 12, 8), 'xsd:time'); /* SPARQL 17.3 - Operator Mapping (boolean op rdfnode) */ EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o FROM rdfnode_ft WHERE p = ''::rdfnode AND true <> o AND false <> o; /* * A literal whose datatype is outside the operator table of SPARQL 17.3 falls * to RDFterm-equal (17.4.1.7), which raises a type error for two literals that * are not the same term. A FILTER drops the row an error comes from, so * '?o != C' keeps nothing at the endpoint while the operator here keeps every * term that is not C: '!=' against such a literal stays local. '=' does not * have to, since the endpoint's TRUE and type error select the same rows as * the operator's true and false. */ EXPLAIN (VERBOSE, COSTS OFF) SELECT o FROM rdfnode_ft WHERE o <> '"http://a"^^xsd:anyURI'::rdfnode; EXPLAIN (VERBOSE, COSTS OFF) SELECT o FROM rdfnode_ft WHERE o != '"a"^^'::rdfnode; EXPLAIN (VERBOSE, COSTS OFF) SELECT o FROM rdfnode_ft WHERE '"http://a"^^xsd:anyURI'::rdfnode <> o; EXPLAIN (VERBOSE, COSTS OFF) SELECT o FROM rdfnode_ft WHERE o = '"http://a"^^xsd:anyURI'::rdfnode; /* the datatypes the table does cover are unaffected, and so is a language-tagged literal, which RDFterm-equal answers FALSE for rather than raising, and an IRI */ EXPLAIN (VERBOSE, COSTS OFF) SELECT o FROM rdfnode_ft WHERE o <> '"a"'::rdfnode; EXPLAIN (VERBOSE, COSTS OFF) SELECT o FROM rdfnode_ft WHERE o <> '"a"^^xsd:string'::rdfnode; EXPLAIN (VERBOSE, COSTS OFF) SELECT o FROM rdfnode_ft WHERE o <> '"1"^^xsd:integer'::rdfnode; EXPLAIN (VERBOSE, COSTS OFF) SELECT o FROM rdfnode_ft WHERE o <> '"a"@en'::rdfnode; EXPLAIN (VERBOSE, COSTS OFF) SELECT o FROM rdfnode_ft WHERE o <> ''::rdfnode; /* ================================================================ * SPARQL 17.3 - Operator Mapping (pg-typed columns) * ================================================================ */ /* text: =, <>, IN, NOT IN */ EXPLAIN (VERBOSE, COSTS OFF) SELECT label FROM pgtypes_ft WHERE label = 'hello' AND label <> 'foo' AND label IN ('hello', 'world') AND label NOT IN ('foo', 'bar'); /* ---------------------------------------------------------------- * LIKE / NOT LIKE -> REGEX / !REGEX * * A LIKE pattern and a regular expression do not mean the same thing, * so CreateRegexString() has to translate rather than copy. These pin * the translation; the rows they would match are not the point. * ---------------------------------------------------------------- */ /* the whole pattern is anchored, whichever wildcard sits at either end: * LIKE matches the entire value, REGEX matches anywhere by default */ EXPLAIN (VERBOSE, COSTS OFF) SELECT label FROM pgtypes_ft WHERE label LIKE 'foo'; EXPLAIN (VERBOSE, COSTS OFF) SELECT label FROM pgtypes_ft WHERE label LIKE '%foo%'; EXPLAIN (VERBOSE, COSTS OFF) SELECT label FROM pgtypes_ft WHERE label LIKE '_foo'; EXPLAIN (VERBOSE, COSTS OFF) SELECT label FROM pgtypes_ft WHERE label LIKE 'foo_'; /* a pattern that opens or closes with a regex anchor is still ordinary * text to LIKE, so the anchor is escaped and the pattern anchored around it */ EXPLAIN (VERBOSE, COSTS OFF) SELECT label FROM pgtypes_ft WHERE label LIKE '^foo'; EXPLAIN (VERBOSE, COSTS OFF) SELECT label FROM pgtypes_ft WHERE label LIKE 'foo$'; /* regex metacharacters are escaped */ EXPLAIN (VERBOSE, COSTS OFF) SELECT label FROM pgtypes_ft WHERE label LIKE 'a.b'; EXPLAIN (VERBOSE, COSTS OFF) SELECT label FROM pgtypes_ft WHERE label LIKE 'a(b)c'; EXPLAIN (VERBOSE, COSTS OFF) SELECT label FROM pgtypes_ft WHERE label LIKE 'a|b'; EXPLAIN (VERBOSE, COSTS OFF) SELECT label FROM pgtypes_ft WHERE label LIKE 'C++'; /* characters that are not metacharacters are left alone: XML Schema * regular expressions reject an escape that has no meaning */ EXPLAIN (VERBOSE, COSTS OFF) SELECT label FROM pgtypes_ft WHERE label LIKE 'a-b'; EXPLAIN (VERBOSE, COSTS OFF) SELECT label FROM pgtypes_ft WHERE label LIKE 'a/b'; EXPLAIN (VERBOSE, COSTS OFF) SELECT label FROM pgtypes_ft WHERE label LIKE 'a:b'; /* a backslash escapes the wildcard that follows it, which then stands for * itself rather than becoming ".*" or "." */ EXPLAIN (VERBOSE, COSTS OFF) SELECT label FROM pgtypes_ft WHERE label LIKE 'a\%b'; EXPLAIN (VERBOSE, COSTS OFF) SELECT label FROM pgtypes_ft WHERE label LIKE 'a\_b'; EXPLAIN (VERBOSE, COSTS OFF) SELECT label FROM pgtypes_ft WHERE label LIKE 'a\\b'; /* and a pattern may not end with one */ SELECT label FROM pgtypes_ft WHERE label LIKE 'ab\'; /* a double quote would close the SPARQL string literal, and a control * character cannot appear in one at all */ EXPLAIN (VERBOSE, COSTS OFF) SELECT label FROM pgtypes_ft WHERE label LIKE 'x"y'; EXPLAIN (VERBOSE, COSTS OFF) SELECT label FROM pgtypes_ft WHERE label LIKE E'a\nb\tc\rd'; /* NOT LIKE negates the whole match */ EXPLAIN (VERBOSE, COSTS OFF) SELECT label FROM pgtypes_ft WHERE label NOT LIKE '%foo%'; /* ILIKE is not pushed down. SPARQL's "i" flag folds case by Unicode rule, * while ILIKE follows the database collation, and the two disagree: under a * Turkish collation 'Istanbul' ILIKE 'i%' is false, where REGEX with "i" * matches. */ EXPLAIN (VERBOSE, COSTS OFF) SELECT label FROM pgtypes_ft WHERE label ILIKE '%foo%'; EXPLAIN (VERBOSE, COSTS OFF) SELECT label FROM pgtypes_ft WHERE label NOT ILIKE '%foo%'; /* the pattern has to be a constant and the value a plain column, or the * REGEX would be built from something other than what is being compared */ EXPLAIN (VERBOSE, COSTS OFF) SELECT label FROM pgtypes_ft WHERE label LIKE label; EXPLAIN (VERBOSE, COSTS OFF) SELECT label FROM pgtypes_ft WHERE upper(label) LIKE '%foo%'; /* bigint: all operators */ EXPLAIN (VERBOSE, COSTS OFF) SELECT version FROM pgtypes_ft WHERE version = 42 AND version <> 99 AND version > 10 AND version < 99 AND version >= 42 AND version <= 42 AND version BETWEEN 10 AND 100 AND version IN (42, 43) AND version NOT IN (0, 99); /* smallint: all operators */ EXPLAIN (VERBOSE, COSTS OFF) SELECT num_smallint FROM pgtypes_ft WHERE num_smallint = 42::smallint AND num_smallint <> 99::smallint AND num_smallint > 10::smallint AND num_smallint < 99::smallint AND num_smallint >= 42::smallint AND num_smallint <= 42::smallint AND num_smallint BETWEEN 10::smallint AND 100::smallint; /* int: all operators */ EXPLAIN (VERBOSE, COSTS OFF) SELECT num_int FROM pgtypes_ft WHERE num_int = 42 AND num_int <> 99 AND num_int > 10 AND num_int < 99 AND num_int >= 42 AND num_int <= 42 AND num_int BETWEEN 10 AND 100; /* real: all operators */ EXPLAIN (VERBOSE, COSTS OFF) SELECT num_real FROM pgtypes_ft WHERE num_real = 1.5::real AND num_real <> 9.9::real AND num_real > 1.0::real AND num_real < 9.9::real AND num_real >= 1.5::real AND num_real <= 1.5::real AND num_real BETWEEN 1.0::real AND 2.0::real; /* double precision: all operators */ EXPLAIN (VERBOSE, COSTS OFF) SELECT num_double FROM pgtypes_ft WHERE num_double = 1.5::double precision AND num_double <> 9.9::double precision AND num_double > 1.0::double precision AND num_double < 9.9::double precision AND num_double >= 1.5::double precision AND num_double <= 1.5::double precision AND num_double BETWEEN 1.0::double precision AND 2.0::double precision; /* numeric: all operators */ EXPLAIN (VERBOSE, COSTS OFF) SELECT num_numeric FROM pgtypes_ft WHERE num_numeric = 1.5::numeric AND num_numeric <> 9.9::numeric AND num_numeric > 1.0::numeric AND num_numeric < 9.9::numeric AND num_numeric >= 1.5::numeric AND num_numeric <= 1.5::numeric AND num_numeric BETWEEN 1.0::numeric AND 2.0::numeric; /* timestamp: all operators */ EXPLAIN (VERBOSE, COSTS OFF) SELECT modified FROM pgtypes_ft WHERE modified = '2015-07-12 20:41:25'::timestamp AND modified <> '2020-07-12 20:41:25'::timestamp AND modified > '2014-07-12 20:41:25'::timestamp AND modified < '2016-07-12 20:41:25'::timestamp AND modified >= '2015-07-12 20:41:25'::timestamp AND modified <= '2015-07-12 20:41:25'::timestamp AND modified BETWEEN '2014-07-12 20:41:25'::timestamp AND '2016-07-12 20:41:25'::timestamp; /* timestamptz: all operators */ EXPLAIN (VERBOSE, COSTS OFF) SELECT tstz FROM pgtypes_ft WHERE tstz = '2011-01-10 14:45:13.815-05:00'::timestamptz AND tstz <> '2020-01-10 14:45:13.815-05:00'::timestamptz AND tstz > '2010-01-10 14:45:13.815-05:00'::timestamptz AND tstz < '2012-01-10 14:45:13.815-05:00'::timestamptz AND tstz >= '2011-01-10 14:45:13.815-05:00'::timestamptz AND tstz <= '2011-01-10 14:45:13.815-05:00'::timestamptz AND tstz BETWEEN '2010-01-10 14:45:13.815-05:00'::timestamptz AND '2012-01-10 14:45:13.815-05:00'::timestamptz; /* date: all operators */ EXPLAIN (VERBOSE, COSTS OFF) SELECT dt FROM pgtypes_ft WHERE dt = '2018-05-01'::date AND dt <> '2020-05-01'::date AND dt > '2017-05-01'::date AND dt < '2019-05-01'::date AND dt >= '2018-05-01'::date AND dt <= '2018-05-01'::date AND dt BETWEEN '2017-05-01'::date AND '2019-05-01'::date AND dt IN ('2018-05-01', '2019-05-01') AND dt NOT IN ('2000-01-01', '2020-01-01'); /* timetz: all operators */ EXPLAIN (VERBOSE, COSTS OFF) SELECT ttz FROM pgtypes_ft WHERE ttz = '12:00:00 UTC'::timetz AND ttz <> '23:00:00 UTC'::timetz AND ttz > '10:00:00 UTC'::timetz AND ttz < '23:00:00 UTC'::timetz AND ttz >= '12:00:00 UTC'::timetz AND ttz <= '12:00:00 UTC'::timetz AND ttz BETWEEN '10:00:00 UTC'::timetz AND '14:00:00 UTC'::timetz; /* boolean: IS / IS NOT (pushable) */ EXPLAIN (VERBOSE, COSTS OFF) SELECT bl FROM pgtypes_ft WHERE bl IS true AND bl IS NOT false; EXPLAIN (VERBOSE, COSTS OFF) SELECT bl FROM pgtypes_ft WHERE bl IS false AND bl IS NOT true; /* boolean: = / <> (NOT pushable) */ EXPLAIN (VERBOSE, COSTS OFF) SELECT bl FROM pgtypes_ft WHERE bl = true AND bl <> false; /* iri column */ EXPLAIN (VERBOSE, COSTS OFF) SELECT type FROM pgtypes_ft WHERE type = 'http://example.org/SomeType'; /* iri column: the value is written into a SPARQL string, so a '"' in it must be escaped rather than ending the string and letting the rest be read as query text. The constant on the right and the constant on the left are two separate branches of the deparser, so both are covered. */ EXPLAIN (VERBOSE, COSTS OFF) SELECT type FROM pgtypes_ft WHERE type = 'http://example.org/a") || isLiteral("x'; EXPLAIN (VERBOSE, COSTS OFF) SELECT type FROM pgtypes_ft WHERE 'http://example.org/a") || isLiteral("x' = type; /* a backslash already in the value must not be joined to the escape added for the quote that follows it */ EXPLAIN (VERBOSE, COSTS OFF) SELECT type FROM pgtypes_ft WHERE type = 'http://example.org/a\") || isLiteral("x'; /* a newline cannot stand inside a SPARQL string literal either */ EXPLAIN (VERBOSE, COSTS OFF) SELECT type FROM pgtypes_ft WHERE type = E'http://example.org/a\nb'; /* The escaping happens once, where the constant is read, so every other column option gets it too: a line break in the value used to reach the endpoint as a line break, and the request was refused. */ EXPLAIN (VERBOSE, COSTS OFF) SELECT label FROM pgtypes_ft WHERE label = E'a\nb'; -- language '*' EXPLAIN (VERBOSE, COSTS OFF) SELECT label FROM pgtypes_ft WHERE label = E'a\tb'; /* the literaltype branch needs a text column carrying the option */ CREATE FOREIGN TABLE escaping_ft ( typed text OPTIONS (variable '?typed', literaltype 'xsd:string'), tagged text OPTIONS (variable '?tagged', language 'en'), plain text OPTIONS (variable '?plain') ) SERVER test_server OPTIONS ( sparql 'SELECT * WHERE { ?p ?o}'); EXPLAIN (VERBOSE, COSTS OFF) SELECT typed FROM escaping_ft WHERE typed = E'a\nb'; EXPLAIN (VERBOSE, COSTS OFF) SELECT tagged FROM escaping_ft WHERE tagged = E'a\nb'; EXPLAIN (VERBOSE, COSTS OFF) SELECT plain FROM escaping_ft WHERE plain = E'a\nb'; EXPLAIN (VERBOSE, COSTS OFF) SELECT plain FROM escaping_ft WHERE plain = 'a\nb'; /* A value may contain '@' and '^^'. A native constant carries no annotation, and an rdfnode's annotation is what follows its closing quote: these used to be pushed down as "x"@en, "user"@en and "a"@en. */ EXPLAIN (VERBOSE, COSTS OFF) SELECT tagged FROM escaping_ft WHERE tagged = 'x^^y'; EXPLAIN (VERBOSE, COSTS OFF) SELECT tagged FROM escaping_ft WHERE tagged = 'user@host'; EXPLAIN (VERBOSE, COSTS OFF) SELECT o FROM rdfnode_ft WHERE o = '"a@b"@en'::rdfnode; /* A backslash in a native datum is a backslash, not the start of an escape. It used to be handed over as written, so "a\nb" arrived at the endpoint meaning a line break. */ EXPLAIN (VERBOSE, COSTS OFF) SELECT label FROM pgtypes_ft WHERE label = 'a\nb'; /* An rdfnode's lexical form already carries its escapes, so they must not be escaped a second time: both of these mean one line break, not a backslash followed by an n. */ EXPLAIN (VERBOSE, COSTS OFF) SELECT o FROM rdfnode_ft WHERE o = '"a\nb"@en'::rdfnode; EXPLAIN (VERBOSE, COSTS OFF) SELECT o FROM rdfnode_ft WHERE o = (E'"a\nb"@en')::rdfnode; /* and a plain rdfnode constant carrying a raw tab used to be wrapped twice, so the term became the string '"ab"', quotes included */ EXPLAIN (VERBOSE, COSTS OFF) SELECT o FROM rdfnode_ft WHERE o = (E'a\tb')::rdfnode; /* ================================================================ * pg function pushdown (length, abs, round, ceil, floor, * substring, md5) * ================================================================ */ /* length */ EXPLAIN (VERBOSE, COSTS OFF) SELECT label FROM pgtypes_ft WHERE length(label) = 5 AND length(label) <> 1 AND length(label) > 1 AND length(label) < 99 AND length(label) >= 5 AND length(label) <= 5 AND length(label) BETWEEN 1 AND 99; /* abs */ EXPLAIN (VERBOSE, COSTS OFF) SELECT version FROM pgtypes_ft WHERE abs(version) = 42 AND abs(version) > 10 AND abs(version) >= 42 AND abs(version) < 99 AND abs(version) <= 42 AND abs(version) BETWEEN 10 AND 100; /* round */ EXPLAIN (VERBOSE, COSTS OFF) SELECT num_numeric FROM pgtypes_ft WHERE round(num_numeric) = 2 AND round(num_numeric) > 1 AND round(num_numeric) >= 2 AND round(num_numeric) < 99 AND round(num_numeric) <= 2 AND round(num_numeric) BETWEEN 1 AND 99; /* ceil */ EXPLAIN (VERBOSE, COSTS OFF) SELECT num_numeric FROM pgtypes_ft WHERE ceil(num_numeric) = 2 AND ceil(num_numeric) > 1 AND ceil(num_numeric) >= 2 AND ceil(num_numeric) < 99 AND ceil(num_numeric) <= 2 AND ceil(num_numeric) BETWEEN 1 AND 99; /* floor */ EXPLAIN (VERBOSE, COSTS OFF) SELECT num_numeric FROM pgtypes_ft WHERE floor(num_numeric) = 1 AND floor(num_numeric) > 0 AND floor(num_numeric) >= 1 AND floor(num_numeric) < 99 AND floor(num_numeric) <= 1 AND floor(num_numeric) BETWEEN 1 AND 99; /* substring */ EXPLAIN (VERBOSE, COSTS OFF) SELECT label FROM pgtypes_ft WHERE substring(label, 1, 5) = 'hello'; /* md5 */ EXPLAIN (VERBOSE, COSTS OFF) SELECT label FROM pgtypes_ft WHERE md5(label) = '5d41402abc4b2a76b9719d911017c592'; /* ================================================================ * SPARQL 17.4.1.1 - BOUND * ================================================================ */ EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o, sparql.bound(p), sparql.bound(x) FROM rdfnode_opt_ft WHERE p = '' AND sparql.bound(o) AND NOT sparql.bound(x); /* ================================================================ * SPARQL 17.4.1.3 - COALESCE * ================================================================ */ EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o, x, sparql.coalesce(x, o, p) FROM rdfnode_opt_ft WHERE p = '' AND sparql.coalesce(x, x, p) = ''; /* ================================================================ * SPARQL 17.4.1.8 - sameTerm * ================================================================ */ EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o, sparql.sameterm(o, '"hello"@fr') FROM rdfnode_ft WHERE p = sparql.iri('http://www.w3.org/2000/01/rdf-schema#label') AND sparql.sameterm(p, ''); /* ================================================================ * SPARQL 17.4.2.1 - isIRI * ================================================================ */ EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o, sparql.isIRI(p), sparql.isIRI(o) FROM rdfnode_ft WHERE p = '' AND sparql.isIRI(p) AND NOT sparql.isIRI(o); /* ================================================================ * SPARQL 17.4.2.2 - isBlank * ================================================================ */ EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o, sparql.isblank(o) FROM rdfnode_ft WHERE sparql.isblank(o); /* ================================================================ * SPARQL 17.4.2.3 - isLiteral * ================================================================ */ EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o, sparql.isliteral(o), sparql.isliteral(p) FROM rdfnode_ft WHERE p = '' AND sparql.isliteral(o) AND NOT sparql.isliteral(p); /* ================================================================ * SPARQL 17.4.2.4 - isNumeric * ================================================================ */ EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o, sparql.isnumeric(o), sparql.isnumeric(p) FROM rdfnode_ft WHERE p = '' AND sparql.isnumeric(o) AND NOT sparql.isnumeric(p); /* ================================================================ * SPARQL 17.4.2.5 - str * ================================================================ */ EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o, sparql.str(o) FROM rdfnode_ft WHERE p = '' AND sparql.str(o) = sparql.str('"hello"@en'); /* ================================================================ * SPARQL 17.4.2.6 - lang * ================================================================ */ EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o, sparql.lang(o) FROM rdfnode_ft WHERE p = '' AND sparql.lang(o) = sparql.lang('"hello"@en'); /* ================================================================ * SPARQL 17.4.2.7 - datatype * ================================================================ */ EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o, sparql.datatype(o) FROM rdfnode_ft WHERE p = '' AND sparql.datatype(o) = sparql.datatype('"42"^^'); /* ================================================================ * SPARQL 17.4.2.8 - IRI * ================================================================ */ EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o, sparql.iri(p) FROM rdfnode_ft WHERE sparql.iri(p) = sparql.iri('http://example.org/property') AND sparql.iri('http://example.org/property') = sparql.iri(p) AND p = sparql.iri('http://example.org/property'); /* ================================================================ * SPARQL 17.4.2.9 - BNODE * ================================================================ */ EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o, sparql.bnode(o) FROM rdfnode_ft WHERE p = '' AND sparql.isblank(sparql.bnode(o)); /* ================================================================ * SPARQL 17.4.2.10 - STRDT * ================================================================ */ EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o, sparql.strdt(o, 'xsd:string') FROM rdfnode_ft WHERE p = sparql.iri('') AND '"42"^^xsd:string'::rdfnode = sparql.strdt(sparql.str(o), 'xsd:string') AND sparql.strdt(sparql.str(o), 'xsd:string') = '"42"^^xsd:string'::rdfnode AND sparql.strdt(sparql.str('"42"^^xsd:integer'), 'xsd:string') = sparql.strdt(sparql.str(o), 'xsd:string') AND sparql.strdt(sparql.str(o), 'xsd:string') = sparql.strdt(sparql.str('"42"^^xsd:integer'), 'xsd:string'); /* ================================================================ * SPARQL 17.4.2.11 - STRLANG * ================================================================ */ EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o, sparql.strlang(o, 'en') FROM rdfnode_ft WHERE p = sparql.iri('http://www.w3.org/2000/01/rdf-schema#label') AND sparql.langmatches(sparql.lang(o), 'en') AND sparql.strlang(sparql.str(o), 'en') = sparql.strlang('"hello"', 'en') AND sparql.strlang('"hello"', 'en') = sparql.strlang(sparql.str(o), 'en') AND sparql.strlang('"hello"', 'en') = '"hello"@en' AND '"hello"@en' = sparql.strlang('"hello"', 'en'); /* ================================================================ * SPARQL 17.4.3.2 - STRLEN * ================================================================ */ EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o, sparql.strlen(o) FROM rdfnode_ft WHERE p = sparql.iri('http://www.w3.org/2000/01/rdf-schema#label') AND sparql.langmatches(sparql.lang(o), 'en') AND sparql.strlen(o) = sparql.strlen('"hello"@en') AND sparql.strlen(o) = 5 AND 5 = sparql.strlen(o); /* ================================================================ * SPARQL 17.4.3.3 - SUBSTR * ================================================================ */ EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o, sparql.substr(o, 1, 3) FROM rdfnode_ft WHERE p = sparql.iri('http://www.w3.org/2000/01/rdf-schema#label') AND sparql.langmatches(sparql.lang(o), 'en') AND sparql.substr(o, 1, 3) = sparql.substr('"hello"@en', 1, 3) AND sparql.substr('"hello"@en', 1, 3) = sparql.substr(o, 1, 3); /* ================================================================ * SPARQL 17.4.3.4 - UCASE * ================================================================ */ EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o, sparql.ucase(o) FROM rdfnode_ft WHERE p = sparql.iri('http://www.w3.org/2000/01/rdf-schema#label') AND sparql.ucase(o) = sparql.ucase('"hello"@en') AND sparql.ucase(o) = '"HELLO"@en' AND '"HELLO"@en' = sparql.ucase(o); /* ================================================================ * SPARQL 17.4.3.5 - LCASE * ================================================================ */ EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o, sparql.lcase(o) FROM rdfnode_ft WHERE p = sparql.iri('http://www.w3.org/2000/01/rdf-schema#label') AND sparql.lcase(o) = sparql.lcase('"HELLO"@en') AND sparql.lcase(o) = '"hello"@en' AND '"hello"@en' = sparql.lcase(o); /* ================================================================ * SPARQL 17.4.3.6 - STRSTARTS * ================================================================ */ EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o, sparql.strstarts(o, sparql.str('"hel"@en')) FROM rdfnode_ft WHERE p = sparql.iri('http://www.w3.org/2000/01/rdf-schema#label') AND sparql.langmatches(sparql.lang(o), 'en') AND sparql.strstarts(o, '"hel"@en') AND sparql.strstarts(o, '"日本"') AND sparql.strstarts(o, '"🐘"'); /* ================================================================ * SPARQL 17.4.3.7 - STRENDS * ================================================================ */ EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o, sparql.strends(o, sparql.str('"llo"@en')) FROM rdfnode_ft WHERE p = sparql.iri('http://www.w3.org/2000/01/rdf-schema#label') AND sparql.langmatches(sparql.lang(o), 'en') AND sparql.strends(o, '"llo"') AND sparql.strends(o, '"日本"') AND sparql.strends(o, '"🐘"'); /* ================================================================ * SPARQL 17.4.3.8 - CONTAINS * ================================================================ */ EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o, sparql.contains(o, '"ell"@en') FROM rdfnode_ft WHERE p = sparql.iri('http://www.w3.org/2000/01/rdf-schema#label') AND sparql.langmatches(sparql.lang(o), 'en') AND sparql.contains(o, '"ell"') AND sparql.contains(o, '"hel"'); /* ================================================================ * SPARQL 17.4.3.9 - STRBEFORE * ================================================================ */ EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o, sparql.strbefore(sparql.str(o), '"llo"') FROM rdfnode_ft WHERE p = sparql.iri('http://www.w3.org/2000/01/rdf-schema#label') AND sparql.langmatches(sparql.lang(o), 'en') AND sparql.strbefore(sparql.str(o), '"llo"') = sparql.strbefore(sparql.str('"hello"@en'), '"llo"') AND sparql.strbefore(sparql.str(o), '"llo"') = '"he"' AND '"he"' = sparql.strbefore(sparql.str(o), '"llo"'); /* ================================================================ * SPARQL 17.4.3.10 - STRAFTER * ================================================================ */ EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o, sparql.strafter(sparql.str(o), '"hel"') FROM rdfnode_ft WHERE p = sparql.iri('http://www.w3.org/2000/01/rdf-schema#label') AND sparql.langmatches(sparql.lang(o), 'en') AND sparql.strafter(sparql.str(o), '"hel"') = sparql.strafter(sparql.str('"hello"@en'), '"hel"') AND sparql.strafter(sparql.str(o), '"hel"') = '"lo"'::rdfnode AND '"lo"' = sparql.strafter(sparql.str(o), '"hel"'); /* ================================================================ * SPARQL 17.4.3.11 - ENCODE_FOR_URI * ================================================================ */ EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o, sparql.encode_for_uri(o) FROM rdfnode_ft WHERE p = sparql.iri('') AND sparql.langmatches(sparql.lang(o), 'en') AND sparql.encode_for_uri(o) = '"hello%20world"' AND '"hello%20world"' = sparql.encode_for_uri(o); /* ================================================================ * SPARQL 17.4.3.12 - CONCAT * ================================================================ */ EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o, sparql.concat(o, sparql.strlang(' world', 'en')) FROM rdfnode_ft WHERE p = sparql.iri('http://www.w3.org/2000/01/rdf-schema#label') AND sparql.langmatches(sparql.lang(o), 'en') AND sparql.concat(o, '" world"') = sparql.concat('"hello"@en', '" world"') AND sparql.concat('"hello"@en', '" world"') = sparql.concat(o, '" world"'); /* ================================================================ * SPARQL 17.4.3.13 - langMatches * ================================================================ */ EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o, sparql.langmatches(sparql.lang(o), '*'), sparql.langmatches(sparql.lang(o), 'en') FROM rdfnode_ft WHERE sparql.langmatches(sparql.lang(o), 'en') ORDER BY p, o; /* ================================================================ * SPARQL 17.4.3.15 - REPLACE * ================================================================ */ EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o, sparql.replace(o, 'hel', 'HEL') FROM rdfnode_ft WHERE p = sparql.iri('http://www.w3.org/2000/01/rdf-schema#label') AND sparql.langmatches(sparql.lang(o), 'en') AND sparql.replace(sparql.str(o), 'hel', 'HEL') = '"HELlo"'::rdfnode AND '"HELlo"' = sparql.replace(sparql.str(o), 'hel', 'HEL') AND sparql.replace(sparql.str(o), 'HEL', 'hel', 'i') = sparql.replace('"hello"', 'HEL', 'hel', 'i'); /* ================================================================ * SPARQL 17.4.4.1 - abs * ================================================================ */ EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o, sparql.abs(o) FROM rdfnode_ft WHERE p = ''::rdfnode AND sparql.abs(o) = 42::bigint AND sparql.abs(o) <> 99::bigint AND sparql.abs(o) >= 42::bigint AND sparql.abs(o) <= 42::bigint AND sparql.abs(o) BETWEEN 10::bigint AND 99::bigint AND sparql.abs(o) = '"42"^^xsd:long'::rdfnode AND sparql.abs(o) > '"10"^^xsd:long'::rdfnode AND sparql.abs(o) >= '"42"^^xsd:long'::rdfnode AND sparql.abs(o) < '"99"^^xsd:long'::rdfnode AND sparql.abs(o) <= '"42"^^xsd:long'::rdfnode AND 42::bigint = sparql.abs(o) AND '"42"^^xsd:long'::rdfnode = sparql.abs(o); /* ================================================================ * SPARQL 17.4.4.2 - round * ================================================================ */ EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o, sparql.round(o) FROM rdfnode_ft WHERE p = ''::rdfnode AND sparql.round(o) = sparql.round(1.5) AND sparql.round(o) > 1.0 AND sparql.round(o) >= sparql.round(1.5) AND sparql.round(o) < 9.9 AND sparql.round(o) <= sparql.round(1.5) AND sparql.round(o) = '"2"^^xsd:decimal'::rdfnode AND sparql.round(o) > '"1"^^xsd:decimal'::rdfnode AND sparql.round(o) >= '"2"^^xsd:decimal'::rdfnode AND sparql.round(o) < '"9"^^xsd:decimal'::rdfnode AND sparql.round(o) <= '"2"^^xsd:decimal'::rdfnode AND sparql.round(1.5) = sparql.round(o) AND sparql.round('"1.5"^^xsd:decimal'::rdfnode) = sparql.round(o); /* ================================================================ * SPARQL 17.4.4.3 - ceil * ================================================================ */ EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o, sparql.ceil(o) FROM rdfnode_ft WHERE p = ''::rdfnode AND sparql.ceil(o) = sparql.ceil(1.5) AND sparql.ceil(o) > 1.0 AND sparql.ceil(o) >= sparql.ceil(1.5) AND sparql.ceil(o) < 9.9 AND sparql.ceil(o) <= sparql.ceil(1.5) AND sparql.ceil(o) = '"2"^^xsd:decimal'::rdfnode AND sparql.ceil(o) > '"1"^^xsd:decimal'::rdfnode AND sparql.ceil(o) >= '"2"^^xsd:decimal'::rdfnode AND sparql.ceil(o) < '"9"^^xsd:decimal'::rdfnode AND sparql.ceil(o) <= sparql.ceil('"1.5"^^xsd:decimal'::rdfnode) AND sparql.ceil(1.5) = sparql.ceil(o) AND sparql.ceil('"1.5"^^xsd:decimal'::rdfnode) = sparql.ceil(o); /* ================================================================ * SPARQL 17.4.4.4 - floor * ================================================================ */ EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o, sparql.floor(o) FROM rdfnode_ft WHERE p = ''::rdfnode AND sparql.floor(o) = sparql.floor(1.5) AND sparql.floor(o) > 1.0 AND sparql.floor(o) >= sparql.floor(1.5) AND sparql.floor(o) < 9.9 AND sparql.floor(o) <= sparql.floor(1.5) AND sparql.floor(o) = '"1"^^xsd:decimal'::rdfnode AND sparql.floor(o) > '"0"^^xsd:decimal'::rdfnode AND sparql.floor(o) >= '"1"^^xsd:decimal'::rdfnode AND sparql.floor(o) < '"9"^^xsd:decimal'::rdfnode AND sparql.floor(o) <= sparql.floor('"1.5"^^xsd:decimal'::rdfnode) AND sparql.floor(1.5) = sparql.floor(o) AND sparql.floor('"1.5"^^xsd:decimal'::rdfnode) = sparql.floor(o); /* ================================================================ * SPARQL 17.4.5.2 - year * ================================================================ */ EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o, sparql.year(o) FROM rdfnode_ft WHERE p = sparql.iri('http://example.org/date') AND sparql.year(o) = 2015 AND sparql.year(o) > 2000 AND sparql.year(o) < 2020 AND sparql.year(o) >= 2015 AND sparql.year(o) <= 2015 AND sparql.year(o) = sparql.year('"2015-07-08T00:00:00Z"^^xsd:dateTime') AND sparql.year(o) > sparql.year('"2000-01-01T00:00:00Z"^^xsd:dateTime') AND sparql.year(o) < sparql.year('"2020-01-01T00:00:00Z"^^xsd:dateTime') AND sparql.year(o) >= sparql.year('"2015-07-08T00:00:00Z"^^xsd:dateTime') AND sparql.year(o) <= sparql.year('"2015-07-08T00:00:00Z"^^xsd:dateTime'); /* ================================================================ * SPARQL 17.4.5.3 - month * ================================================================ */ EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o, sparql.month(o) FROM rdfnode_ft WHERE p = sparql.iri('http://example.org/date') AND sparql.month(o) = 7 AND sparql.month(o) > 1 AND sparql.month(o) < 12 AND sparql.month(o) >= 7 AND sparql.month(o) <= 7 AND sparql.month(o) = sparql.month('"2015-07-08T00:00:00Z"^^xsd:dateTime') AND sparql.month(o) > sparql.month('"2015-01-08T00:00:00Z"^^xsd:dateTime') AND sparql.month(o) < sparql.month('"2015-12-08T00:00:00Z"^^xsd:dateTime') AND sparql.month(o) >= sparql.month('"2015-07-08T00:00:00Z"^^xsd:dateTime') AND sparql.month(o) <= sparql.month('"2015-07-08T00:00:00Z"^^xsd:dateTime'); /* ================================================================ * SPARQL 17.4.5.4 - day * ================================================================ */ EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o, sparql.day(o) FROM rdfnode_ft WHERE p = sparql.iri('http://example.org/date') AND sparql.day(o) = 8 AND sparql.day(o) > 1 AND sparql.day(o) < 30 AND sparql.day(o) >= 8 AND sparql.day(o) <= 8 AND sparql.day(o) = sparql.day('"2015-07-08T00:00:00Z"^^xsd:dateTime') AND sparql.day(o) > sparql.day('"2015-07-01T00:00:00Z"^^xsd:dateTime') AND sparql.day(o) < sparql.day('"2015-07-30T00:00:00Z"^^xsd:dateTime') AND sparql.day(o) >= sparql.day('"2015-07-08T00:00:00Z"^^xsd:dateTime') AND sparql.day(o) <= sparql.day('"2015-07-08T00:00:00Z"^^xsd:dateTime'); /* ================================================================ * SPARQL 17.4.5.5 - hours * ================================================================ */ EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o, sparql.hours(o) FROM rdfnode_ft WHERE p = sparql.iri('http://example.org/date') AND sparql.hours(o) = 20 AND sparql.hours(o) > 0 AND sparql.hours(o) < 23 AND sparql.hours(o) >= 20 AND sparql.hours(o) <= 20 AND sparql.hours(o) = sparql.hours('"2015-07-08T20:41:25Z"^^xsd:dateTime') AND sparql.hours(o) < sparql.hours('"2015-07-08T23:00:00Z"^^xsd:dateTime') AND sparql.hours(o) >= sparql.hours('"2015-07-08T20:41:25Z"^^xsd:dateTime') AND sparql.hours(o) <= sparql.hours('"2015-07-08T20:41:25Z"^^xsd:dateTime'); /* ================================================================ * SPARQL 17.4.5.6 - minutes * ================================================================ */ EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o, sparql.minutes(o) FROM rdfnode_ft WHERE p = sparql.iri('http://example.org/date') AND sparql.minutes(o) = 41 AND sparql.minutes(o) > 0 AND sparql.minutes(o) < 59 AND sparql.minutes(o) >= 41 AND sparql.minutes(o) <= 41 AND sparql.minutes(o) = sparql.minutes('"2015-07-08T20:41:25Z"^^xsd:dateTime') AND sparql.minutes(o) < sparql.minutes('"2015-07-08T20:59:00Z"^^xsd:dateTime') AND sparql.minutes(o) >= sparql.minutes('"2015-07-08T20:41:25Z"^^xsd:dateTime') AND sparql.minutes(o) <= sparql.minutes('"2015-07-08T20:41:25Z"^^xsd:dateTime'); /* ================================================================ * SPARQL 17.4.5.7 - seconds * ================================================================ */ EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o, sparql.seconds(o) FROM rdfnode_ft WHERE p = sparql.iri('http://example.org/date') AND sparql.seconds(o) = 25 AND sparql.seconds(o) > 0 AND sparql.seconds(o) < 59 AND sparql.seconds(o) >= 25 AND sparql.seconds(o) <= 25 AND sparql.seconds(o) = sparql.seconds('"2015-07-08T20:41:25Z"^^xsd:dateTime') AND sparql.seconds(o) < sparql.seconds('"2015-07-08T20:41:59Z"^^xsd:dateTime') AND sparql.seconds(o) >= sparql.seconds('"2015-07-08T20:41:25Z"^^xsd:dateTime') AND sparql.seconds(o) <= sparql.seconds('"2015-07-08T20:41:25Z"^^xsd:dateTime'); /* ================================================================ * SPARQL 17.4.6.1 - MD5 * ================================================================ */ EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o, sparql.md5(o) FROM rdfnode_ft WHERE p = sparql.iri('http://www.w3.org/2000/01/rdf-schema#label') AND sparql.langmatches(sparql.lang(o), 'en') AND sparql.md5(o) = sparql.md5('"hello"@en'); /* ================================================================ * Non-pushable tables * (SPARQL query contains MINUS, UNION, LIMIT, ORDER BY, GROUP BY) * ================================================================ */ CREATE FOREIGN TABLE np_minus ( p rdfnode OPTIONS (variable '?p'), o rdfnode OPTIONS (variable '?o') ) SERVER test_server OPTIONS ( sparql 'SELECT * WHERE { ?p ?o MINUS { ?o}}'); EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o FROM np_minus WHERE p = ''; CREATE FOREIGN TABLE np_union ( p rdfnode OPTIONS (variable '?p'), o rdfnode OPTIONS (variable '?o') ) SERVER test_server OPTIONS ( sparql 'SELECT * WHERE {{ ?p ?o} UNION { ?p ?o}}'); EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o FROM np_union WHERE p = ''; CREATE FOREIGN TABLE np_limit ( p rdfnode OPTIONS (variable '?p'), o rdfnode OPTIONS (variable '?o') ) SERVER test_server OPTIONS ( sparql 'SELECT * WHERE { ?p ?o} LIMIT 10'); EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o FROM np_limit WHERE p = ''; CREATE FOREIGN TABLE np_orderby ( p rdfnode OPTIONS (variable '?p'), o rdfnode OPTIONS (variable '?o') ) SERVER test_server OPTIONS ( sparql 'SELECT * WHERE { ?p ?o} ORDER BY ?o'); EXPLAIN (VERBOSE, COSTS OFF) SELECT p, o FROM np_orderby WHERE p = ''; CREATE FOREIGN TABLE np_groupby ( p rdfnode OPTIONS (variable '?p'), c int OPTIONS (variable '?c') ) SERVER test_server OPTIONS ( sparql 'SELECT ?p (COUNT(?o) AS ?c) WHERE { ?p ?o} GROUP BY ?p'); EXPLAIN (VERBOSE, COSTS OFF) SELECT p, c FROM np_groupby WHERE c > 1; /* ================================================================ * Non-pushable tables * (the supplied query says something a rewrite would not preserve) * ================================================================ */ /* * A rewrite replaces the supplied SELECT clause with one of its own and * appends its own solution modifiers. That is only faithful where the clause * says nothing but which variables to project: a DISTINCT, a REDUCED or an * expression alias is part of what the query asks for and would be dropped, * and a BASE decides what the relative IRIs beneath it mean. A projection that * does not carry a mapped column's variable cannot be rewritten to one that * does, either - the variable is not bound. Such a query is sent as it was * written and everything is applied locally. */ CREATE FOREIGN TABLE np_distinct ( p rdfnode OPTIONS (variable '?p') ) SERVER test_server OPTIONS ( sparql 'SELECT DISTINCT ?p WHERE { ?p ?o}'); EXPLAIN (VERBOSE, COSTS OFF) SELECT p FROM np_distinct WHERE p = ''; CREATE FOREIGN TABLE np_reduced ( p rdfnode OPTIONS (variable '?p') ) SERVER test_server OPTIONS ( sparql 'SELECT REDUCED ?p WHERE { ?p ?o}'); EXPLAIN (VERBOSE, COSTS OFF) SELECT p FROM np_reduced WHERE p = ''; CREATE FOREIGN TABLE np_alias ( p rdfnode OPTIONS (variable '?p') ) SERVER test_server OPTIONS ( sparql 'SELECT (?o AS ?p) WHERE { ?x ?o}'); EXPLAIN (VERBOSE, COSTS OFF) SELECT p FROM np_alias WHERE p = ''; CREATE FOREIGN TABLE np_base ( p rdfnode OPTIONS (variable '?p') ) SERVER test_server OPTIONS ( sparql 'BASE SELECT ?p WHERE { ?p ?o}'); EXPLAIN (VERBOSE, COSTS OFF) SELECT p FROM np_base WHERE p = ''; /* the projection does not bind the variable the column is mapped to */ CREATE FOREIGN TABLE np_unprojected ( p rdfnode OPTIONS (variable '?p') ) SERVER test_server OPTIONS ( sparql 'SELECT ?s WHERE {?s ?p ?o}'); EXPLAIN (VERBOSE, COSTS OFF) SELECT p FROM np_unprojected WHERE p = ''; /* * A keyword written inside a string is not a keyword, whichever of SPARQL's * four quotings the string uses, and neither is one inside an IRI or a * comment. A query carrying one is still rewritten: the text is read in order * and what is not query text is stepped over, rather than the quoting being * inferred from how many double quotes came before. */ CREATE FOREIGN TABLE kw_dquote ( p rdfnode OPTIONS (variable '?p'), o rdfnode OPTIONS (variable '?o') ) SERVER test_server OPTIONS ( sparql 'SELECT ?p ?o WHERE {?s ?p ?o FILTER(?o != " SELECT ")}'); EXPLAIN (VERBOSE, COSTS OFF) SELECT p FROM kw_dquote WHERE p = ''; CREATE FOREIGN TABLE kw_squote ( p rdfnode OPTIONS (variable '?p'), o rdfnode OPTIONS (variable '?o') ) SERVER test_server OPTIONS ( sparql $$SELECT ?p ?o WHERE {?s ?p ?o FILTER(?o != ' SELECT ')}$$); EXPLAIN (VERBOSE, COSTS OFF) SELECT p FROM kw_squote WHERE p = ''; CREATE FOREIGN TABLE kw_tquote ( p rdfnode OPTIONS (variable '?p'), o rdfnode OPTIONS (variable '?o') ) SERVER test_server OPTIONS ( sparql 'SELECT ?p ?o WHERE {?s ?p ?o FILTER(?o != """ SELECT """)}'); EXPLAIN (VERBOSE, COSTS OFF) SELECT p FROM kw_tquote WHERE p = ''; /* a real second SELECT is still a subquery, and still stops the rewrite */ CREATE FOREIGN TABLE kw_subselect ( p rdfnode OPTIONS (variable '?p'), o rdfnode OPTIONS (variable '?o') ) SERVER test_server OPTIONS ( sparql 'SELECT ?p ?o WHERE { SELECT ?p ?o WHERE {?s ?p ?o} }'); EXPLAIN (VERBOSE, COSTS OFF) SELECT p FROM kw_subselect WHERE p = ''; /* a plain projection naming the mapped variable is still rewritten */ CREATE FOREIGN TABLE np_plain ( p rdfnode OPTIONS (variable '?p') ) SERVER test_server OPTIONS ( sparql 'SELECT ?p WHERE { ?p ?o}'); EXPLAIN (VERBOSE, COSTS OFF) SELECT p FROM np_plain WHERE p = ''; /* ================================================================ * Arithmetic grouping * ================================================================ */ /* The shape of the expression tree, not the textual order of the operators, * decides what the filter means. SPARQL applies its own precedence to whatever * it is handed, so an expression whose grouping departs from that precedence * has to be parenthesised on the way out: (n + 1) * 2 and n + 1 * 2 select * different rows, and both are written with the same three operands. */ CREATE FOREIGN TABLE arith_ft ( n int OPTIONS (variable '?n'), o rdfnode OPTIONS (variable '?o') ) SERVER test_server OPTIONS ( sparql 'SELECT ?n ?o WHERE { ?p ?o}'); EXPLAIN (VERBOSE, COSTS OFF) SELECT n FROM arith_ft WHERE (n + 1) * 2 = 10; EXPLAIN (VERBOSE, COSTS OFF) SELECT n FROM arith_ft WHERE n + 1 * 2 = 10; EXPLAIN (VERBOSE, COSTS OFF) SELECT n FROM arith_ft WHERE (n + 2) * (n + 3) = 20; /* ================================================================ * Array comparisons that cannot be translated * ================================================================ */ /* SQL's IN and NOT IN give NULL its own truth table, which SPARQL has no term * for, and an array built at run time rather than folded to a constant carries * elements this deparser renders as bare identifiers. Neither can be sent, so * both stay with the executor -- a remote filter here would be a syntax error * at the endpoint rather than a wrong answer. */ EXPLAIN (VERBOSE, COSTS OFF) SELECT o FROM rdfnode_ft WHERE o IN ('"hello"', NULL); EXPLAIN (VERBOSE, COSTS OFF) SELECT o FROM rdfnode_ft WHERE o = ANY(ARRAY[o, '"hello"'::rdfnode]); /* a constant list of ordinary values is still pushed down */ EXPLAIN (VERBOSE, COSTS OFF) SELECT o FROM rdfnode_ft WHERE o IN ('"hello"', '"world"'); /* ================================================================ * LIMIT pushdown boundaries * ================================================================ */ /* A remote LIMIT decides which rows the endpoint sends, so it may only be used * where that is also the set the query wants. A sort is the case where it is * not: SPARQL orders RDF terms by value, while PostgreSQL orders rdfnode by its * stored representation, so the two disagree on ordinary data -- "9" sorts * after "10" here and before it there. Whichever rows the endpoint kept, the * local sort cannot recover the ones it dropped. */ EXPLAIN (VERBOSE, COSTS OFF) SELECT o FROM rdfnode_ft ORDER BY o LIMIT 3; /* the same holds when the sort cannot be pushed at all, where a remote limit * would hand back an arbitrary three rows for the executor to sort */ EXPLAIN (VERBOSE, COSTS OFF) SELECT o FROM rdfnode_ft ORDER BY o::text || 'x' LIMIT 3; /* a join decides which rows survive after the scan, so the limit cannot be * applied to either side on its own */ EXPLAIN (VERBOSE, COSTS OFF) SELECT a.o FROM rdfnode_ft a, rdfnode_ft b WHERE a.o = b.o LIMIT 3; /* a plain limit on a single scan is still pushed down, and OFFSET is added to * it in 64-bit arithmetic -- the sum of two values that each fit in an int32 * need not */ EXPLAIN (VERBOSE, COSTS OFF) SELECT o FROM rdfnode_ft LIMIT 3; EXPLAIN (VERBOSE, COSTS OFF) SELECT o FROM rdfnode_ft OFFSET 3000000000 LIMIT 10; /* ================================================================ * Function identity and casts * ================================================================ */ /* Recognising a function by name alone means a user's own function is shipped * as the SPARQL builtin that happens to share its name, and never runs. The * one below returns false for every row; sent as SPARQL CONTAINS it would * return whatever the endpoint decides. PL/pgSQL rather than SQL so that the * planner cannot inline it away before the deparser sees it. */ CREATE FUNCTION contains(rdfnode, rdfnode) RETURNS boolean AS $$ BEGIN RETURN false; END; $$ LANGUAGE plpgsql IMMUTABLE; EXPLAIN (VERBOSE, COSTS OFF) SELECT o FROM rdfnode_ft WHERE contains(o, '"x"'::rdfnode); DROP FUNCTION contains(rdfnode, rdfnode); /* Comparing a term with a PostgreSQL temporal type is not the comparison the * endpoint would make, so it stays local whichever temporal type and whichever * operand order is used. The local operator reads the term's lexical form with * the temporal type's input function, which discards the timezone offset of an * xsd:dateTime and accepts an xsd:date where an xsd:dateTime was asked for; * SPARQL compares instants and only between matching datatypes. */ EXPLAIN (VERBOSE, COSTS OFF) SELECT o FROM rdfnode_ft WHERE o = '2015-01-01 00:00:00'::timestamp; EXPLAIN (VERBOSE, COSTS OFF) SELECT o FROM rdfnode_ft WHERE '2015-01-01 00:00:00'::timestamptz >= o; EXPLAIN (VERBOSE, COSTS OFF) SELECT o FROM rdfnode_ft WHERE o <> '2015-01-01'::date; EXPLAIN (VERBOSE, COSTS OFF) SELECT o FROM rdfnode_ft WHERE o < '12:00:00'::time; EXPLAIN (VERBOSE, COSTS OFF) SELECT o FROM rdfnode_ft WHERE o > '12:00:00+00'::timetz; /* a comparison against a value of the column's own type is still pushed */ EXPLAIN (VERBOSE, COSTS OFF) SELECT o FROM rdfnode_ft WHERE o = '"x"'::rdfnode; /* A string constant is never a column, even when its value spells the name of * one. A column used to be recognised by comparing the deparsed argument with * the column names, so 'p' was sent as ?p, 'len' as the expression of the len * column, and the name of a dropped column as that column's variable, which it * does not have: a NULL pointer, printed as "(null)" or terminating the * backend, depending on the snprintf() doing the printing. */ CREATE FOREIGN TABLE colname_ft ( p rdfnode OPTIONS (variable '?p'), o rdfnode OPTIONS (variable '?o'), gone rdfnode OPTIONS (variable '?g'), label text OPTIONS (variable '?label'), len int OPTIONS (variable '?len', expression 'STRLEN(?o)') ) SERVER test_server OPTIONS ( sparql 'SELECT * WHERE { ?p ?o}'); ALTER FOREIGN TABLE colname_ft DROP COLUMN gone; EXPLAIN (VERBOSE, COSTS OFF) SELECT o FROM colname_ft WHERE sparql.contains(o, 'p'); EXPLAIN (VERBOSE, COSTS OFF) SELECT o FROM colname_ft WHERE sparql.coalesce(o, 'p') = '"x"'; EXPLAIN (VERBOSE, COSTS OFF) SELECT o FROM colname_ft WHERE label = 'len'; EXPLAIN (VERBOSE, COSTS OFF) SELECT o FROM colname_ft WHERE sparql.contains(o, '........pg.dropped.3........'); /* a column is still recognised as one */ EXPLAIN (VERBOSE, COSTS OFF) SELECT o FROM colname_ft WHERE sparql.contains(o, p); EXPLAIN (VERBOSE, COSTS OFF) SELECT o FROM colname_ft WHERE sparql.coalesce(o, p) = '"x"'; EXPLAIN (VERBOSE, COSTS OFF) SELECT o FROM colname_ft WHERE length(label) = len; DROP FOREIGN TABLE colname_ft; DROP SERVER test_server CASCADE;