diff --git a/src/include/generated/index_search_ops.hpp b/src/include/generated/index_search_ops.hpp new file mode 100644 index 00000000..9970a5e6 --- /dev/null +++ b/src/include/generated/index_search_ops.hpp @@ -0,0 +1,117 @@ +// GENERATED by tools/codegen_duck_udfs.py from the MEOS-API catalog — DO NOT EDIT. +// The index search of every operator name, `column query` and `query column`, +// from the per-signature indexSearch of the catalog. +#pragma once + +#include "meos_wrapper_simple.hpp" + +namespace duckdb { + +//! A name an in-memory index answers and the MEOS search it asks for. A predicate is read as +//! `column query`; with the query on the left it asks for `commuted`, and an operator +//! whose MobilityDB declaration has no COMMUTATOR (the overlapping orderings) has none, so a +//! predicate written that way is answered by a scan. +struct IndexOperatorEntry { + const char *name; + //! The search of `column query` + IndexSearchOp direct; + //! True when `query column` has a search + bool has_commuted; + //! The search of `query column`, meaningful only when has_commuted + IndexSearchOp commuted; +}; + +static constexpr IndexOperatorEntry INDEX_OPERATORS[] = { + {"#&>", INDEX_OVERAFTER, false, INDEX_OVERAFTER}, + {"#>>", INDEX_AFTER, true, INDEX_BEFORE}, + {"&&", INDEX_OVERLAPS, true, INDEX_OVERLAPS}, + {"&<", INDEX_OVERLEFT, false, INDEX_OVERLEFT}, + {"&<#", INDEX_OVERBEFORE, false, INDEX_OVERBEFORE}, + {"&", INDEX_OVERRIGHT, false, INDEX_OVERRIGHT}, + {"-|-", INDEX_ADJACENT, true, INDEX_ADJACENT}, + {"/&>", INDEX_OVERBACK, false, INDEX_OVERBACK}, + {"/>>", INDEX_BACK, true, INDEX_FRONT}, + {"<<", INDEX_LEFT, true, INDEX_RIGHT}, + {"<<#", INDEX_BEFORE, true, INDEX_AFTER}, + {"<>", INDEX_RIGHT, true, INDEX_LEFT}, + {"@>", INDEX_CONTAINS, true, INDEX_CONTAINED_BY}, + {"adjacent", INDEX_ADJACENT, true, INDEX_ADJACENT}, + {"contained", INDEX_CONTAINED_BY, true, INDEX_CONTAINS}, + {"contains", INDEX_CONTAINS, true, INDEX_CONTAINED_BY}, + {"overlaps", INDEX_OVERLAPS, true, INDEX_OVERLAPS}, + {"same", INDEX_SAME, true, INDEX_SAME}, + {"setAfter", INDEX_AFTER, true, INDEX_BEFORE}, + {"setBefore", INDEX_BEFORE, true, INDEX_AFTER}, + {"setLeft", INDEX_LEFT, true, INDEX_RIGHT}, + {"setOverafter", INDEX_OVERAFTER, false, INDEX_OVERAFTER}, + {"setOverbefore", INDEX_OVERBEFORE, false, INDEX_OVERBEFORE}, + {"setOverleft", INDEX_OVERLEFT, false, INDEX_OVERLEFT}, + {"setOverright", INDEX_OVERRIGHT, false, INDEX_OVERRIGHT}, + {"setRight", INDEX_RIGHT, true, INDEX_LEFT}, + {"spanAfter", INDEX_AFTER, true, INDEX_BEFORE}, + {"spanBefore", INDEX_BEFORE, true, INDEX_AFTER}, + {"spanLeft", INDEX_LEFT, true, INDEX_RIGHT}, + {"spanOverafter", INDEX_OVERAFTER, false, INDEX_OVERAFTER}, + {"spanOverbefore", INDEX_OVERBEFORE, false, INDEX_OVERBEFORE}, + {"spanOverleft", INDEX_OVERLEFT, false, INDEX_OVERLEFT}, + {"spanOverright", INDEX_OVERRIGHT, false, INDEX_OVERRIGHT}, + {"spanRight", INDEX_RIGHT, true, INDEX_LEFT}, + {"spansetAfter", INDEX_AFTER, true, INDEX_BEFORE}, + {"spansetBefore", INDEX_BEFORE, true, INDEX_AFTER}, + {"spansetLeft", INDEX_LEFT, true, INDEX_RIGHT}, + {"spansetOverafter", INDEX_OVERAFTER, false, INDEX_OVERAFTER}, + {"spansetOverbefore", INDEX_OVERBEFORE, false, INDEX_OVERBEFORE}, + {"spansetOverleft", INDEX_OVERLEFT, false, INDEX_OVERLEFT}, + {"spansetOverright", INDEX_OVERRIGHT, false, INDEX_OVERRIGHT}, + {"spansetRight", INDEX_RIGHT, true, INDEX_LEFT}, + {"stboxAbove", INDEX_ABOVE, true, INDEX_BELOW}, + {"stboxAfter", INDEX_AFTER, true, INDEX_BEFORE}, + {"stboxBack", INDEX_BACK, true, INDEX_FRONT}, + {"stboxBefore", INDEX_BEFORE, true, INDEX_AFTER}, + {"stboxBelow", INDEX_BELOW, true, INDEX_ABOVE}, + {"stboxFront", INDEX_FRONT, true, INDEX_BACK}, + {"stboxLeft", INDEX_LEFT, true, INDEX_RIGHT}, + {"stboxOverabove", INDEX_OVERABOVE, false, INDEX_OVERABOVE}, + {"stboxOverafter", INDEX_OVERAFTER, false, INDEX_OVERAFTER}, + {"stboxOverback", INDEX_OVERBACK, false, INDEX_OVERBACK}, + {"stboxOverbefore", INDEX_OVERBEFORE, false, INDEX_OVERBEFORE}, + {"stboxOverbelow", INDEX_OVERBELOW, false, INDEX_OVERBELOW}, + {"stboxOverfront", INDEX_OVERFRONT, false, INDEX_OVERFRONT}, + {"stboxOverleft", INDEX_OVERLEFT, false, INDEX_OVERLEFT}, + {"stboxOverright", INDEX_OVERRIGHT, false, INDEX_OVERRIGHT}, + {"stboxRight", INDEX_RIGHT, true, INDEX_LEFT}, + {"tboxAfter", INDEX_AFTER, true, INDEX_BEFORE}, + {"tboxBefore", INDEX_BEFORE, true, INDEX_AFTER}, + {"tboxLeft", INDEX_LEFT, true, INDEX_RIGHT}, + {"tboxOverafter", INDEX_OVERAFTER, false, INDEX_OVERAFTER}, + {"tboxOverbefore", INDEX_OVERBEFORE, false, INDEX_OVERBEFORE}, + {"tboxOverleft", INDEX_OVERLEFT, false, INDEX_OVERLEFT}, + {"tboxOverright", INDEX_OVERRIGHT, false, INDEX_OVERRIGHT}, + {"tboxRight", INDEX_RIGHT, true, INDEX_LEFT}, + {"tpcboxAbove", INDEX_ABOVE, true, INDEX_BELOW}, + {"tpcboxAfter", INDEX_AFTER, true, INDEX_BEFORE}, + {"tpcboxBack", INDEX_BACK, true, INDEX_FRONT}, + {"tpcboxBefore", INDEX_BEFORE, true, INDEX_AFTER}, + {"tpcboxBelow", INDEX_BELOW, true, INDEX_ABOVE}, + {"tpcboxFront", INDEX_FRONT, true, INDEX_BACK}, + {"tpcboxLeft", INDEX_LEFT, true, INDEX_RIGHT}, + {"tpcboxOverabove", INDEX_OVERABOVE, false, INDEX_OVERABOVE}, + {"tpcboxOverafter", INDEX_OVERAFTER, false, INDEX_OVERAFTER}, + {"tpcboxOverback", INDEX_OVERBACK, false, INDEX_OVERBACK}, + {"tpcboxOverbefore", INDEX_OVERBEFORE, false, INDEX_OVERBEFORE}, + {"tpcboxOverbelow", INDEX_OVERBELOW, false, INDEX_OVERBELOW}, + {"tpcboxOverfront", INDEX_OVERFRONT, false, INDEX_OVERFRONT}, + {"tpcboxOverleft", INDEX_OVERLEFT, false, INDEX_OVERLEFT}, + {"tpcboxOverright", INDEX_OVERRIGHT, false, INDEX_OVERRIGHT}, + {"tpcboxRight", INDEX_RIGHT, true, INDEX_LEFT}, + {"|&>", INDEX_OVERABOVE, false, INDEX_OVERABOVE}, + {"|>>", INDEX_ABOVE, true, INDEX_BELOW}, + {"~=", INDEX_SAME, true, INDEX_SAME}, +}; + +} // namespace duckdb diff --git a/src/include/index/index_search_ops.hpp b/src/include/index/index_search_ops.hpp index 1a22c9b7..afb459ed 100644 --- a/src/include/index/index_search_ops.hpp +++ b/src/include/index/index_search_ops.hpp @@ -1,9 +1,8 @@ #pragma once -#include - #include "duckdb.hpp" #include "meos_wrapper_simple.hpp" +#include "generated/index_search_ops.hpp" namespace duckdb { @@ -19,86 +18,41 @@ enum IndexOperatorAxis { INDEX_AXIS_TIME, }; -//! A bounding-box operator an in-memory index answers, and the MEOS operation it asks the index -//! for. A predicate is read as `column query`, so an operator whose two sides play -//! different roles asks for a different operation once the query sits on the left: `a @> b` with -//! the indexed column on the left looks for stored boxes that CONTAIN the query, and the same -//! operator with the column on the right looks for stored boxes CONTAINED BY it. Both directions -//! are named here so that a caller reads the operation off the operand order rather than -//! assuming one. -struct IndexOperatorEntry { - const char *name; - //! The operation of `column query` - IndexSearchOp direct; - //! The operation of `query column` - IndexSearchOp commuted; - //! The axis the operator compares along, which decides the box types answering it - IndexOperatorAxis axis; -}; - -//! Every operator an index answers. An operator absent from this table is answered by a scan. -//! -//! The ordering operators are the negated duals of one another under an exchange of operands -- -//! `a << b` holds exactly when `b >> a` -- so the commuted column of each is its opposite. The set -//! mirrors the operators the PostgreSQL operator classes of the same box types index, so a -//! predicate that reaches an index here reaches one there. -//! -//! Equality and adjacency are their own opposites -- a box equals, or meets, another exactly when -//! that other equals, or meets, it -- so each names one operation in both columns, and both compare -//! whole extents, so every box type answers them. -//! -//! A bounding-box predicate is registered under two spellings, a symbol and a word, and both are -//! named here so that a query reaches the index however it is written. The temporal orderings are -//! the exception: they carry no symbol, `<<#` and its siblings not being operators DuckDB lexes, -//! so the word is the only spelling to route. -//! -//! A word is also reached through its class-prefixed spelling: the functions behind the position -//! operators carry the name of the class they compare, `stboxLeft`, `tboxBefore`, `spanOverleft`, -//! and such a spelling reaches the entry of the word it prefixes (see IndexOperatorWord). -static constexpr IndexOperatorEntry INDEX_OPERATORS[] = { - {"&&", INDEX_OVERLAPS, INDEX_OVERLAPS, INDEX_AXIS_EXTENT}, - {"overlaps", INDEX_OVERLAPS, INDEX_OVERLAPS, INDEX_AXIS_EXTENT}, - {"@>", INDEX_CONTAINS, INDEX_CONTAINED_BY, INDEX_AXIS_EXTENT}, - {"contains", INDEX_CONTAINS, INDEX_CONTAINED_BY, INDEX_AXIS_EXTENT}, - {"<@", INDEX_CONTAINED_BY, INDEX_CONTAINS, INDEX_AXIS_EXTENT}, - {"contained", INDEX_CONTAINED_BY, INDEX_CONTAINS, INDEX_AXIS_EXTENT}, - {"~=", INDEX_SAME, INDEX_SAME, INDEX_AXIS_EXTENT}, - {"same", INDEX_SAME, INDEX_SAME, INDEX_AXIS_EXTENT}, - {"-|-", INDEX_ADJACENT, INDEX_ADJACENT, INDEX_AXIS_EXTENT}, - {"adjacent", INDEX_ADJACENT, INDEX_ADJACENT, INDEX_AXIS_EXTENT}, - - {"<<", INDEX_LEFT, INDEX_RIGHT, INDEX_AXIS_HORIZONTAL}, - {"left", INDEX_LEFT, INDEX_RIGHT, INDEX_AXIS_HORIZONTAL}, - {"&<", INDEX_OVERLEFT, INDEX_OVERRIGHT, INDEX_AXIS_HORIZONTAL}, - {"overleft", INDEX_OVERLEFT, INDEX_OVERRIGHT, INDEX_AXIS_HORIZONTAL}, - {">>", INDEX_RIGHT, INDEX_LEFT, INDEX_AXIS_HORIZONTAL}, - {"right", INDEX_RIGHT, INDEX_LEFT, INDEX_AXIS_HORIZONTAL}, - {"&>", INDEX_OVERRIGHT, INDEX_OVERLEFT, INDEX_AXIS_HORIZONTAL}, - {"overright", INDEX_OVERRIGHT, INDEX_OVERLEFT, INDEX_AXIS_HORIZONTAL}, - - {"<<|", INDEX_BELOW, INDEX_ABOVE, INDEX_AXIS_VERTICAL}, - {"below", INDEX_BELOW, INDEX_ABOVE, INDEX_AXIS_VERTICAL}, - {"&<|", INDEX_OVERBELOW, INDEX_OVERABOVE, INDEX_AXIS_VERTICAL}, - {"overbelow", INDEX_OVERBELOW, INDEX_OVERABOVE, INDEX_AXIS_VERTICAL}, - {"|>>", INDEX_ABOVE, INDEX_BELOW, INDEX_AXIS_VERTICAL}, - {"above", INDEX_ABOVE, INDEX_BELOW, INDEX_AXIS_VERTICAL}, - {"|&>", INDEX_OVERABOVE, INDEX_OVERBELOW, INDEX_AXIS_VERTICAL}, - {"overabove", INDEX_OVERABOVE, INDEX_OVERBELOW, INDEX_AXIS_VERTICAL}, - - {"<>", INDEX_BACK, INDEX_FRONT, INDEX_AXIS_DEPTH}, - {"back", INDEX_BACK, INDEX_FRONT, INDEX_AXIS_DEPTH}, - {"/&>", INDEX_OVERBACK, INDEX_OVERFRONT, INDEX_AXIS_DEPTH}, - {"overback", INDEX_OVERBACK, INDEX_OVERFRONT, INDEX_AXIS_DEPTH}, - - {"before", INDEX_BEFORE, INDEX_AFTER, INDEX_AXIS_TIME}, - {"overbefore", INDEX_OVERBEFORE, INDEX_OVERAFTER, INDEX_AXIS_TIME}, - {"after", INDEX_AFTER, INDEX_BEFORE, INDEX_AXIS_TIME}, - {"overafter", INDEX_OVERAFTER, INDEX_OVERBEFORE, INDEX_AXIS_TIME}, -}; +//! The axis the MEOS search `op` compares along. The overlap, containment, equality and adjacency +//! searches compare whole extents; each ordering search, strict or overlapping, compares along +//! the one axis it names. +inline IndexOperatorAxis IndexSearchAxis(IndexSearchOp op) { + switch (op) { + case INDEX_OVERLAPS: + case INDEX_CONTAINS: + case INDEX_CONTAINED_BY: + case INDEX_SAME: + case INDEX_ADJACENT: + return INDEX_AXIS_EXTENT; + case INDEX_LEFT: + case INDEX_OVERLEFT: + case INDEX_RIGHT: + case INDEX_OVERRIGHT: + return INDEX_AXIS_HORIZONTAL; + case INDEX_BELOW: + case INDEX_OVERBELOW: + case INDEX_ABOVE: + case INDEX_OVERABOVE: + return INDEX_AXIS_VERTICAL; + case INDEX_FRONT: + case INDEX_OVERFRONT: + case INDEX_BACK: + case INDEX_OVERBACK: + return INDEX_AXIS_DEPTH; + case INDEX_BEFORE: + case INDEX_OVERBEFORE: + case INDEX_AFTER: + case INDEX_OVERAFTER: + return INDEX_AXIS_TIME; + default: + throw InternalException("IndexSearchAxis: unknown IndexSearchOp %d", (int) op); + } +} //! Return true if a box of `bbox_type` carries `axis`, and so answers the operators ordering //! along it. A spatiotemporal box carries every axis; a temporal box carries a value extent and a @@ -125,63 +79,34 @@ inline bool IndexBboxHasAxis(MeosType bbox_type, IndexOperatorAxis axis) { } } -//! The class prefixes a word of INDEX_OPERATORS takes in its class-prefixed spelling, longest first -//! so that `spansetLeft` is read as `spanset` + `Left` and never as `span` + `setLeft`. -static constexpr const char *INDEX_OPERATOR_CLASS_PREFIXES[] = { - "spanset", "tpcbox", "stbox", "tbox", "span", "set", -}; - -//! Return the word of INDEX_OPERATORS that `name` spells. A class-prefixed spelling, one of -//! INDEX_OPERATOR_CLASS_PREFIXES followed by an uppercase letter, spells the remainder with its -//! first letter lowercased: `stboxOverleft` spells `overleft`. Every other name, a symbol or an -//! unprefixed word, spells itself. -inline string IndexOperatorWord(const string &name) { - for (auto prefix : INDEX_OPERATOR_CLASS_PREFIXES) { - const size_t length = strlen(prefix); - if (name.size() > length && name.compare(0, length, prefix) == 0 && name[length] >= 'A' && - name[length] <= 'Z') { - string word = name.substr(length); - word[0] = static_cast(word[0] - 'A' + 'a'); - return word; - } - } - return name; -} - -//! Return the operation `name` asks of an index over `bbox_type`, false when such an index answers -//! no such operator. `name` is a symbol, a word or the word's class-prefixed spelling. -//! `query_on_left` names the operand order: true when the query is the left argument. +//! Return the search `name` asks of an index over `bbox_type`, false when such an index answers no +//! such operator. `name` is an operator symbol or the SQL name of a function backing it, as the +//! generated INDEX_OPERATORS states them. `query_on_left` names the operand order: true when the +//! query is the left argument, which asks for the commuted search, and an operator with none is +//! answered by a scan. inline bool IndexSearchOpFromName(const string &name, MeosType bbox_type, bool query_on_left, IndexSearchOp &result) { - const string word = IndexOperatorWord(name); for (auto &entry : INDEX_OPERATORS) { - if (word == entry.name && IndexBboxHasAxis(bbox_type, entry.axis)) { - result = query_on_left ? entry.commuted : entry.direct; - return true; + if (name != entry.name || !IndexBboxHasAxis(bbox_type, IndexSearchAxis(entry.direct))) { + continue; } + if (query_on_left && !entry.has_commuted) { + return false; + } + result = query_on_left ? entry.commuted : entry.direct; + return true; } return false; } -//! The operator names an index over `bbox_type` answers, each word under its bare and its -//! class-prefixed spellings. Overlap and containment compare whole extents, so every box type -//! answers those; an ordering operator is answered only where the box carries the axis it orders -//! along. +//! The operator names an index over `bbox_type` answers in at least one operand order: overlap and +//! containment compare whole extents, so every box type answers those, and an ordering operator is +//! answered only where the box carries the axis it orders along. inline unordered_set IndexOperatorNames(MeosType bbox_type) { unordered_set names; for (auto &entry : INDEX_OPERATORS) { - if (!IndexBboxHasAxis(bbox_type, entry.axis)) { - continue; - } - const string name = entry.name; - names.insert(name); - if (name[0] < 'a' || name[0] > 'z') { - continue; - } - string capitalized = name; - capitalized[0] = static_cast(capitalized[0] - 'a' + 'A'); - for (auto prefix : INDEX_OPERATOR_CLASS_PREFIXES) { - names.insert(prefix + capitalized); + if (IndexBboxHasAxis(bbox_type, IndexSearchAxis(entry.direct))) { + names.insert(entry.name); } } return names; diff --git a/src/index/rtree_optimize_scan.cpp b/src/index/rtree_optimize_scan.cpp index c9ceee02..16947e72 100644 --- a/src/index/rtree_optimize_scan.cpp +++ b/src/index/rtree_optimize_scan.cpp @@ -18,6 +18,7 @@ #include "index/rtree_module.hpp" #include "index/rtree_index_scan.hpp" +#include "index/index_search_ops.hpp" #include "time_util.hpp" @@ -95,6 +96,14 @@ class TRTreeIndexScanOptimizer : public OptimizerExtension { } else { return false; } + // The index answers an operator only in an operand order it has a search for: an + // overlapping ordering with the query on the left (`query &< column`) has none, so + // the predicate stays a filter and a scan answers it. + IndexSearchOp search_op; + if (!IndexSearchOpFromName(function_name, rtree_index.GetBboxType(), query_on_left, + search_op)) { + return false; + } Expression *const_expr = nullptr; diff --git a/src/index/sptree_optimize_scan.cpp b/src/index/sptree_optimize_scan.cpp index c5c70ffa..7fc91321 100644 --- a/src/index/sptree_optimize_scan.cpp +++ b/src/index/sptree_optimize_scan.cpp @@ -18,6 +18,7 @@ #include "index/sptree_module.hpp" #include "index/sptree_index_scan.hpp" +#include "index/index_search_ops.hpp" #include "time_util.hpp" @@ -95,6 +96,14 @@ class TSPTreeIndexScanOptimizer : public OptimizerExtension { } else { return false; } + // The index answers an operator only in an operand order it has a search for: an + // overlapping ordering with the query on the left (`query &< column`) has none, so + // the predicate stays a filter and a scan answers it. + IndexSearchOp search_op; + if (!IndexSearchOpFromName(function_name, sptree_index.GetBboxType(), query_on_left, + search_op)) { + return false; + } Expression *const_expr = nullptr; diff --git a/test/sql/parity/095_index_ordering_query_on_left.test b/test/sql/parity/095_index_ordering_query_on_left.test new file mode 100644 index 00000000..7d0eee7d --- /dev/null +++ b/test/sql/parity/095_index_ordering_query_on_left.test @@ -0,0 +1,72 @@ +# name: test/sql/parity/095_index_ordering_query_on_left.test +# description: the position operators with the query on the left agree with a scan of the same rows +# group: [sql] + +require mobilityduck + +statement ok +CREATE TABLE ord_idx(t tgeompoint); + +statement ok +CREATE TABLE ord_scan(t tgeompoint); + +statement ok +INSERT INTO ord_idx SELECT ('Point(' || i || ' ' || i || ')@' || strftime(DATE '2000-01-01' + INTERVAL (i) DAY, '%Y-%m-%d'))::tgeompoint FROM range(1,3000) tbl(i); + +statement ok +INSERT INTO ord_scan SELECT * FROM ord_idx; + +# only one of the two tables is indexed, so the answers below are the index's against a scan's +statement ok +CREATE INDEX i_ord ON ord_idx USING TRTREE (t); + +# A strict ordering with the query on the left asks the index for its opposite: `query << column` +# holds exactly when `column >> query` does. + +query II +EXPLAIN SELECT count(*) FROM ord_idx WHERE 'STBOX XT(((500,500),(501,501)),[2001-01-01,2001-01-02])'::stbox << t; +---- +physical_plan :.*MOBILITY RTREE INDEX.* + +query II +SELECT (SELECT count(*) FROM ord_idx WHERE 'STBOX XT(((500,500),(501,501)),[2001-01-01,2001-01-02])'::stbox << t), (SELECT count(*) FROM ord_scan WHERE 'STBOX XT(((500,500),(501,501)),[2001-01-01,2001-01-02])'::stbox << t); +---- +2498 2498 + +query II +SELECT (SELECT count(*) FROM ord_idx WHERE 'STBOX XT(((500,500),(501,501)),[2001-01-01,2001-01-02])'::stbox >> t), (SELECT count(*) FROM ord_scan WHERE 'STBOX XT(((500,500),(501,501)),[2001-01-01,2001-01-02])'::stbox >> t); +---- +499 499 + +# An overlapping ordering has no opposite: `query &< column` asks for a stored box whose end is not +# before the end of the query, which no index search states, so the predicate is answered by a scan. + +query II +EXPLAIN SELECT count(*) FROM ord_idx WHERE 'STBOX XT(((500,500),(501,501)),[2001-01-01,2001-01-02])'::stbox &< t; +---- +physical_plan :.*MOBILITY RTREE INDEX.* + +query II +SELECT (SELECT count(*) FROM ord_idx WHERE 'STBOX XT(((500,500),(501,501)),[2001-01-01,2001-01-02])'::stbox &< t), (SELECT count(*) FROM ord_scan WHERE 'STBOX XT(((500,500),(501,501)),[2001-01-01,2001-01-02])'::stbox &< t); +---- +2499 2499 + +query II +SELECT (SELECT count(*) FROM ord_idx WHERE stboxOverleft('STBOX XT(((500,500),(501,501)),[2001-01-01,2001-01-02])'::stbox, t)), (SELECT count(*) FROM ord_scan WHERE stboxOverleft('STBOX XT(((500,500),(501,501)),[2001-01-01,2001-01-02])'::stbox, t)); +---- +2499 2499 + +query II +SELECT (SELECT count(*) FROM ord_idx WHERE 'STBOX XT(((500,500),(501,501)),[2001-01-01,2001-01-02])'::stbox &> t), (SELECT count(*) FROM ord_scan WHERE 'STBOX XT(((500,500),(501,501)),[2001-01-01,2001-01-02])'::stbox &> t); +---- +500 500 + +query II +SELECT (SELECT count(*) FROM ord_idx WHERE stboxOverbefore('STBOX XT(((500,500),(501,501)),[2001-01-01,2001-01-02])'::stbox, t)), (SELECT count(*) FROM ord_scan WHERE stboxOverbefore('STBOX XT(((500,500),(501,501)),[2001-01-01,2001-01-02])'::stbox, t)); +---- +2633 2633 + +query II +SELECT (SELECT count(*) FROM ord_idx WHERE stboxOverafter('STBOX XT(((500,500),(501,501)),[2001-01-01,2001-01-02])'::stbox, t)), (SELECT count(*) FROM ord_scan WHERE stboxOverafter('STBOX XT(((500,500),(501,501)),[2001-01-01,2001-01-02])'::stbox, t)); +---- +366 366 diff --git a/tools/codegen_duck_udfs.py b/tools/codegen_duck_udfs.py index d03f1959..0c0a0972 100644 --- a/tools/codegen_duck_udfs.py +++ b/tools/codegen_duck_udfs.py @@ -5353,6 +5353,70 @@ def gen_temporal_covering(catalog, out_path): open(out_path, "w").write("\n".join(lines)) return len(entries) + +def gen_index_search_ops(catalog, out_path): + """The index search each operator name asks of an in-memory index, from the catalog's + per-signature `indexSearch` {columnLeft, columnRight} (MEOS-API docs/index-search.md), as + #gen_temporal_covering emits the covering from the catalog's temporalCovering. A signature is + reached under its SQL name and under its operator's symbol, the symbol read from the doc + comment of its columnLeft value in the catalog's IndexSearchOp enum ("..., `&<` operator"). + A name answering two different pairs fails generation: one name routes one way.""" + enum = next((e for e in catalog.get("enums", []) if e.get("name") == "IndexSearchOp"), None) + if enum is None: + raise SystemExit("FATAL: the catalog carries no IndexSearchOp enum") + symbol = {} + for v in enum["values"]: + m = re.search(r"`([^`]+)`\s+operator", v.get("doc") or "") + if not m: + raise SystemExit("FATAL: IndexSearchOp %s names no operator in its doc" % v["name"]) + symbol[v["name"]] = m.group(1) + routes = {} + for f in catalog["functions"]: + for sig in f.get("sqlSignatures") or []: + ix = sig.get("indexSearch") + if not ix: + continue + pair = (ix["columnLeft"], ix.get("columnRight")) + for name in (symbol[ix["columnLeft"]], sig.get("sqlName") or f["sqlfn"]): + routes.setdefault(name, set()).add(pair) + clash = sorted(n for n, pairs in routes.items() if len(pairs) > 1) + if clash: + raise SystemExit("FATAL: names routed to two index searches: %s" % ", ".join(clash)) + lines = [ + "// GENERATED by tools/codegen_duck_udfs.py from the MEOS-API catalog — DO NOT EDIT.", + "// The index search of every operator name, `column query` and `query column`,", + "// from the per-signature indexSearch of the catalog.", + "#pragma once", + "", + "#include \"meos_wrapper_simple.hpp\"", + "", + "namespace duckdb {", + "", + "//! A name an in-memory index answers and the MEOS search it asks for. A predicate is read as", + "//! `column query`; with the query on the left it asks for `commuted`, and an operator", + "//! whose MobilityDB declaration has no COMMUTATOR (the overlapping orderings) has none, so a", + "//! predicate written that way is answered by a scan.", + "struct IndexOperatorEntry {", + "\tconst char *name;", + "\t//! The search of `column query`", + "\tIndexSearchOp direct;", + "\t//! True when `query column` has a search", + "\tbool has_commuted;", + "\t//! The search of `query column`, meaningful only when has_commuted", + "\tIndexSearchOp commuted;", + "};", + "", + "static constexpr IndexOperatorEntry INDEX_OPERATORS[] = {", + ] + for name in sorted(routes): + left, right = next(iter(routes[name])) + lines.append(' {"%s", %s, %s, %s},' % (name, left, "true" if right else "false", + right or left)) + lines += ["};", "", "} // namespace duckdb", ""] + os.makedirs(os.path.dirname(out_path), exist_ok=True) + open(out_path, "w").write("\n".join(lines)) + return len(routes) + def main(): global RETIRED_GROUPS pos = [a for a in sys.argv[1:] if not a.startswith("--")] @@ -5440,6 +5504,12 @@ def main(): tcov = os.path.join(os.path.dirname(out), "generated_temporal_covering.cpp") ncov = gen_temporal_covering(d, tcov) print(f"wrote the TemporalParquet covering of {ncov} temporal types -> {tcov}") + # The index search of every operator name, from the catalog's per-signature indexSearch, + # emitted beside the hand-written routing that reads it (src/include/index). + isearch = os.path.join(os.path.dirname(os.path.dirname(out)), "include", "generated", + "index_search_ops.hpp") + nis = gen_index_search_ops(d, isearch) + print(f"wrote the index search of {nis} operator names -> {isearch}") # Regularity invariant (build-failing): MEOS keeps locale/collation, session # timezone, PROJ context and RNGs in thread-local storage, so every UDF body # must run EnsureMeosThreadInitialized() before any MEOS call. Verify it for