diff --git a/Makefile b/Makefile index bc17d050..a9485498 100644 --- a/Makefile +++ b/Makefile @@ -11,9 +11,39 @@ include extension-ci-tools/makefiles/duckdb_extension.Makefile # both MEOS (meos_initialize_timezone) and DuckDB (DBConfig::SetOptionByName # "TimeZone") to Europe/Brussels. Tests pass on any OS timezone — the # extension is the single source of truth, no TZ env var needed. +# +# LoadInternal also calls ExtensionHelper::AutoLoadExtension(db, "icu") so +# the timezone option is honoured. Autoload looks for the extension on disk +# at $HOME/.duckdb/extensions///icu.duckdb_extension +# and falls back to a hub download. That fails both inside the linux_amd64 +# test docker container (empty path, no network egress) and on the macOS +# osx_arm64 test runner (hub icu not reliably resolvable). We copy the +# icu.duckdb_extension that was built locally as part of this extension's +# build (declared in extension_config.cmake) into the expected path, +# matched to the DuckDB platform string, before running the unittester. +DUCKDB_VERSION_TAG := v1.4.4 + +define stage_icu + @if [ -f ./build/$(1)/extension/icu/icu.duckdb_extension ]; then \ + case "$$(uname -s)-$$(uname -m)" in \ + Linux-x86_64) platform=linux_amd64 ;; \ + Linux-aarch64) platform=linux_arm64 ;; \ + Darwin-arm64) platform=osx_arm64 ;; \ + Darwin-x86_64) platform=osx_amd64 ;; \ + *) platform=$$(uname -m) ;; \ + esac; \ + target=$$HOME/.duckdb/extensions/$(DUCKDB_VERSION_TAG)/$$platform; \ + mkdir -p "$$target" && cp -f ./build/$(1)/extension/icu/icu.duckdb_extension "$$target/" && \ + echo "Staged icu.duckdb_extension at $$target/"; \ + fi +endef + test_release_internal: + $(call stage_icu,release) ./build/release/$(TEST_PATH) "$(PROJ_DIR)test/*" test_debug_internal: + $(call stage_icu,debug) ./build/debug/$(TEST_PATH) "$(PROJ_DIR)test/*" test_reldebug_internal: - ./build/reldebug/$(TEST_PATH) "$(PROJ_DIR)test/*" \ No newline at end of file + $(call stage_icu,reldebug) + ./build/reldebug/$(TEST_PATH) "$(PROJ_DIR)test/*" diff --git a/src/include/index/rtree_module.hpp b/src/include/index/rtree_module.hpp index 71d34964..b16c05de 100644 --- a/src/include/index/rtree_module.hpp +++ b/src/include/index/rtree_module.hpp @@ -86,6 +86,7 @@ class TRTreeIndex : public BoundIndex { MeosType bbox_meostype; size_t bbox_size_; + LogicalType column_type_; size_t current_size_ = 0; size_t current_capacity_ = 0; diff --git a/src/include/tydef.hpp b/src/include/tydef.hpp index b7b28109..b9860ca0 100644 --- a/src/include/tydef.hpp +++ b/src/include/tydef.hpp @@ -11,11 +11,27 @@ extern "C" { #include } -// Forward-compat alias for the meosType → MeosType rename (MobilityDB -// pr785-sync-script). Vcpkg's MEOS exposes `MeosType`; existing -// MobilityDuck code still uses `meosType`. This alias bridges the two -// without touching every reference site. -using meosType = MeosType; +// MEOS naming history: `meosType` is the **pre-consolidation** spelling +// and `MeosType` is the **post-consolidation** target (the rename is +// part of the upstream consolidation sweep, not yet reached by the +// vcpkg pin). The current pin +// (`vcpkg_ports/meos/portfile.cmake` REF f11b7443ee98…) is still +// pre-consolidation and exposes `meosType` — see +// meos/include/temporal/meos_catalog.h, where line 121 declares +// `} meosType;`. MobilityDuck's source consistently uses +// `meosType` (verified via `grep -rn '\bmeosType\b' src/`), which +// matches the pin, so no alias is needed today. +// +// An earlier version of this file added `using meosType = MeosType;` +// as a forward-looking bridge for the eventual consolidation bump. +// That alias references `MeosType`, which the current pin does NOT +// yet expose, so it broke the build: +// "'MeosType' does not name a type; did you mean 'meosType'?". +// +// When the MEOS pin is bumped past the consolidation point, restore +// a bridge here (`using meosType = MeosType;` becomes valid then) or +// sweep the source `meosType → MeosType` in one PR — whichever the +// project prefers at that time. namespace duckdb { diff --git a/src/index/rtree_index_create_physical.cpp b/src/index/rtree_index_create_physical.cpp index 8ea0cb61..fc86cb05 100644 --- a/src/index/rtree_index_create_physical.cpp +++ b/src/index/rtree_index_create_physical.cpp @@ -151,11 +151,9 @@ class TRTreeIndexConstructTask final : public ExecutorTask { const auto count = scan_chunk.size(); - auto &vec_vec = scan_chunk.data[0]; - auto &rowid_vec = scan_chunk.data[1]; + auto &vec_vec = scan_chunk.data[0]; + auto &rowid_vec = scan_chunk.data[1]; - auto vector_type = vec_vec.GetType(); - if (vec_vec.GetVectorType() != VectorType::FLAT_VECTOR) { vec_vec.Flatten(count); } @@ -163,87 +161,22 @@ class TRTreeIndexConstructTask final : public ExecutorTask { rowid_vec.Flatten(count); } - UnifiedVectorFormat vec_format; - UnifiedVectorFormat rowid_format; - - vec_vec.ToUnifiedFormat(count, vec_format); - rowid_vec.ToUnifiedFormat(count, rowid_format); - - const auto row_ptr = UnifiedVectorFormat::GetData(rowid_format); - STBox* boxes = (STBox*)malloc(sizeof(STBox) * count); - if (!boxes) { - executor.PushError(ErrorData("Failed to allocate memory for STBox array")); - return TaskExecutionResult::TASK_ERROR; - } - - idx_t valid_count = 0; - vector valid_row_ids; - - for (idx_t i = 0; i < count; i++) { - const auto vec_idx = vec_format.sel->get_index(i); - const auto row_idx = rowid_format.sel->get_index(i); - - const auto vec_valid = vec_format.validity.RowIsValid(vec_idx); - const auto rowid_valid = rowid_format.validity.RowIsValid(row_idx); - - if (!vec_valid || !rowid_valid) { - continue; - } - - fprintf(stderr, "Processing row %zu (vec_idx=%zu, row_idx=%zu)\n", i, vec_idx, row_idx); - - STBox *box = nullptr; - - if (vector_type.id() == LogicalTypeId::BLOB) { - - const auto stbox_data_ptr = UnifiedVectorFormat::GetData(vec_format); - auto blob_data = stbox_data_ptr[vec_idx]; - const uint8_t *stbox_bytes = reinterpret_cast(blob_data.GetData()); - size_t stbox_size = blob_data.GetSize(); - box = (STBox*)malloc(stbox_size); - memcpy(box, stbox_bytes, stbox_size); - - int32_t box_srid = stbox_srid(box); - - if (box_srid != 0) { - STBox *normalized_box = stbox_set_srid(box, 0); - if (normalized_box) { - free(box); - box = normalized_box; - } - } - - // Copy to our batch array - memcpy(&boxes[valid_count], box, sizeof(STBox)); - valid_row_ids.push_back(row_ptr[row_idx]); - valid_count++; - - free(box); - - - } - else { - free(boxes); - executor.PushError(ErrorData("Unsupported data type for RTree index: " + vector_type.ToString())); - return TaskExecutionResult::TASK_ERROR; - } + // Build a one-column DataChunk with the indexed expression so we + // can hand it to TRTreeIndex::Construct, which dispatches on the + // indexed column type to choose between rtree_insert (for box / + // span blobs whose bytes ARE the bbox) and rtree_insert_temporal + // (for Temporal blobs whose bbox is extracted at insert time). + DataChunk col_chunk; + vector col_types = {vec_vec.GetType()}; + col_chunk.Initialize(Allocator::DefaultAllocator(), col_types); + col_chunk.SetCardinality(count); + col_chunk.data[0].Reference(vec_vec); + + { + lock_guard l(gstate.glock); + gstate.global_index->Construct(col_chunk, rowid_vec); } - // Now batch insert the valid STBoxes into the index - if (valid_count > 0) { - auto &rtree_index = gstate.global_index; - - auto result = rtree_index->BulkConstruct(boxes, valid_row_ids.data(), valid_count); - if (result.HasError()) { - free(boxes); - executor.PushError(result); - return TaskExecutionResult::TASK_ERROR; - } - - } - - free(boxes); - gstate.built_count += count; if (mode == TaskExecutionMode::PROCESS_PARTIAL) { diff --git a/src/index/rtree_module.cpp b/src/index/rtree_module.cpp index bf6c1d7c..9b9fdaac 100644 --- a/src/index/rtree_module.cpp +++ b/src/index/rtree_module.cpp @@ -27,6 +27,13 @@ #include "duckdb/optimizer/matcher/expression_matcher.hpp" #include "index/rtree_module.hpp" #include "geo/stbox.hpp" +#include "geo/tgeompoint.hpp" +#include "geo/tgeometry.hpp" +#include "geo/tgeography.hpp" +#include "geo/tgeogpoint.hpp" +#include "temporal/span.hpp" +#include "temporal/tbox.hpp" +#include "temporal/temporal.hpp" #include "index/rtree_index_create_physical.hpp" #include "time_util.hpp" @@ -48,17 +55,67 @@ TRTreeIndex::TRTreeIndex(const string &name, IndexConstraintType constraint_type auto &type = unbound_expressions[0]->return_type; - + column_type_ = type; + + // The R-tree's bbox flavour is determined by the indexed column's type. + // Span / box types are stored directly (`rtree_insert` with the raw + // bytes); temporal types are bbox-extracted at insert time (spatial + // temporals via tspatial_to_stbox, the rest via rtree_insert_temporal). if (type == StboxType::STBOX()) { bbox_meostype = T_STBOX; bbox_size_ = sizeof(STBox); rtree_ = rtree_create_stbox(); + } else if (type == TboxType::TBOX()) { + bbox_meostype = T_TBOX; + bbox_size_ = sizeof(TBox); + rtree_ = rtree_create_tbox(); } else if (type == SpanTypes::TSTZSPAN()) { bbox_meostype = T_TSTZSPAN; - bbox_size_ = sizeof(Span); + bbox_size_ = sizeof(Span); + rtree_ = rtree_create_tstzspan(); + } else if (type == SpanTypes::INTSPAN()) { + bbox_meostype = T_INTSPAN; + bbox_size_ = sizeof(Span); + rtree_ = rtree_create_intspan(); + } else if (type == SpanTypes::BIGINTSPAN()) { + bbox_meostype = T_BIGINTSPAN; + bbox_size_ = sizeof(Span); + rtree_ = rtree_create_bigintspan(); + } else if (type == SpanTypes::FLOATSPAN()) { + bbox_meostype = T_FLOATSPAN; + bbox_size_ = sizeof(Span); + rtree_ = rtree_create_floatspan(); + } else if (type == SpanTypes::DATESPAN()) { + bbox_meostype = T_DATESPAN; + bbox_size_ = sizeof(Span); + rtree_ = rtree_create_datespan(); + } else if (type == TemporalTypes::TINT() || type == TemporalTypes::TFLOAT()) { + // Temporal numbers: bbox is a tbox. + bbox_meostype = T_TBOX; + bbox_size_ = sizeof(TBox); + rtree_ = rtree_create_tbox(); + } else if (type == TemporalTypes::TBOOL() || type == TemporalTypes::TTEXT()) { + // Non-numeric, non-spatial temporals: bbox is the time span only. + bbox_meostype = T_TSTZSPAN; + bbox_size_ = sizeof(Span); rtree_ = rtree_create_tstzspan(); + } else if (type == TgeompointType::TGEOMPOINT() || + type == TGeometryTypes::TGEOMETRY() || + type == TGeographyTypes::TGEOGRAPHY() || + type == TGeogpointType::TGEOGPOINT()) { + // Temporal-spatial types: bbox is an stbox. + bbox_meostype = T_STBOX; + bbox_size_ = sizeof(STBox); + rtree_ = rtree_create_stbox(); } else { - throw InternalException("RTree index only supports STBOX and TSTZSPAN types, got: " + type.ToString()); + // Unsupported indexed column type. This is user input, not an + // internal invariant, so raise a clean BinderException rather than + // an InternalException (which DuckDB renders as the generic + // "assertion failure within DuckDB" crash message). + throw BinderException( + "TRTREE index supports stbox, tbox, the 5 span types, and the " + "temporal types (tint, tfloat, tbool, ttext, tgeompoint, " + "tgeogpoint, tgeometry, tgeography). Got: " + type.ToString()); } if (!rtree_) { @@ -103,23 +160,22 @@ PhysicalOperator &TRTreeIndex::CreatePlan(PlanIndexInput &input) { select_list.push_back(std::move(expression)); } - // new_column_types.emplace_back(LogicalType::ROW_TYPE); - // select_list.push_back( - // make_uniq(LogicalType::ROW_TYPE, create_index.info->scan_types.size() - 1)); + LogicalType row_type = LogicalType::ROW_TYPE; + new_column_types.push_back(row_type); + select_list.push_back( + make_uniq(row_type, create_index.info->scan_types.size() - 1)); - auto &projection = planner.Make(new_column_types, std::move(select_list), + auto &projection = planner.Make(new_column_types, std::move(select_list), create_index.estimated_cardinality); projection.children.push_back(input.table_scan); - auto &physical_create_index = planner.Make( - create_index.types, create_index.table, create_index.info->column_ids, - std::move(create_index.info), std::move(create_index.unbound_expressions), + create_index.types, create_index.table, create_index.info->column_ids, + std::move(create_index.info), std::move(create_index.unbound_expressions), create_index.estimated_cardinality); - + physical_create_index.children.push_back(projection); return physical_create_index; - return input.table_scan; } //------------------------------------------------------------------------------ @@ -224,58 +280,88 @@ void TRTreeIndex::Construct(DataChunk &expression_result, Vector &row_identifier if (vector.GetVectorType() != VectorType::FLAT_VECTOR) { vector.Flatten(expression_result.size()); } - - auto vector_type = vector.GetType(); - - void* boxes = malloc(bbox_size_ * expression_result.size()); - + // True if the indexed column holds a Temporal value (the bbox is + // derived per-row at insert time). False if the column already holds a + // span / tbox / stbox blob whose bytes are the bbox itself. + const bool indexes_temporal = + column_type_ == TemporalTypes::TINT() || + column_type_ == TemporalTypes::TFLOAT() || + column_type_ == TemporalTypes::TBOOL() || + column_type_ == TemporalTypes::TTEXT() || + column_type_ == TgeompointType::TGEOMPOINT() || + column_type_ == TGeometryTypes::TGEOMETRY() || + column_type_ == TGeographyTypes::TGEOGRAPHY() || + column_type_ == TGeogpointType::TGEOGPOINT(); + + void *boxes = indexes_temporal ? nullptr + : malloc(bbox_size_ * expression_result.size()); + for (idx_t i = 0; i < expression_result.size(); i++) { if (FlatVector::IsNull(vector, i)) { - continue; + continue; } - void *box = nullptr; - - if (vector_type.id() == LogicalTypeId::BLOB) { - auto blob_data = FlatVector::GetData(vector)[i]; - const uint8_t *data = reinterpret_cast(blob_data.GetData()); - size_t data_size = blob_data.GetSize(); - - - if (data_size != bbox_size_) { - continue; - } - - box = malloc(data_size); - memcpy(box, data, data_size); + if (vector.GetType().id() != LogicalTypeId::BLOB) { + continue; + } + + auto blob_data = FlatVector::GetData(vector)[i]; + const uint8_t *data = reinterpret_cast(blob_data.GetData()); + size_t data_size = blob_data.GetSize(); + if (indexes_temporal) { + const Temporal *temp = reinterpret_cast(data); + // For temporal-spatial types extract the bbox and strip the + // SRID so index keys agree with the SRID-stripped query box + // used at search time (InitializeScan strips it too). if (bbox_meostype == T_STBOX) { - STBox *stbox = (STBox*)box; - int32_t box_srid = stbox_srid(stbox); - if (box_srid != 0) { - STBox *normalized_box = stbox_set_srid(stbox, 0); - if (normalized_box) { + STBox *box = tspatial_to_stbox(temp); + if (!box) { + continue; + } + if (stbox_srid(box) != 0) { + STBox *normalized = stbox_set_srid(box, 0); + if (normalized) { free(box); - box = normalized_box; + box = normalized; } } + rtree_insert(rtree_, box, static_cast(row_data[i])); + free(box); + } else { + rtree_insert_temporal(rtree_, temp, static_cast(row_data[i])); } - } else { continue; } - if (box == nullptr) { + // Box / span blob: the bytes ARE the bbox. + if (data_size != bbox_size_) { continue; } - - void* target = (char*)boxes + (i * bbox_size_); + + void *box = malloc(data_size); + memcpy(box, data, data_size); + + if (bbox_meostype == T_STBOX) { + STBox *stbox = (STBox *) box; + int32_t box_srid = stbox_srid(stbox); + if (box_srid != 0) { + STBox *normalized_box = stbox_set_srid(stbox, 0); + if (normalized_box) { + free(box); + box = normalized_box; + } + } + } + + void *target = (char *) boxes + (i * bbox_size_); memcpy(target, box, bbox_size_); - rtree_insert(rtree_, target, static_cast(row_data[i])); + rtree_insert(rtree_, target, static_cast(row_data[i])); free(box); } - - free(boxes); + + if (boxes) free(boxes); } @@ -474,21 +560,15 @@ unique_ptr TRTreeIndex::MakeFunctionMatcher() const { matcher->expr_type = make_uniq(ExpressionType::BOUND_FUNCTION); matcher->policy = SetMatcher::Policy::UNORDERED; - LogicalType index_type; - if (bbox_meostype == T_STBOX) { - index_type = StboxType::STBOX(); - } else if (bbox_meostype == T_TSTZSPAN) { - index_type = SpanTypes::TSTZSPAN(); - } else { - index_type = LogicalType::BLOB; - } - - // Left operand + // The left operand is the indexed column, so the matcher must accept + // the column's actual type, not the bbox type. A tgeompoint column + // with an R-tree over its bbox still appears as tgeompoint in the + // predicate AST. auto lhs_matcher = make_uniq(); - lhs_matcher->type = make_uniq(index_type); + lhs_matcher->type = make_uniq(column_type_); matcher->matchers.push_back(std::move(lhs_matcher)); - // Right operand + // Right operand: any type (constant bbox, span, or temporal expr). auto rhs_matcher = make_uniq(); matcher->matchers.push_back(std::move(rhs_matcher)); diff --git a/test/sql/parity/050_index_types.test b/test/sql/parity/050_index_types.test new file mode 100644 index 00000000..40150568 --- /dev/null +++ b/test/sql/parity/050_index_types.test @@ -0,0 +1,285 @@ +# name: test/sql/parity/050_index_types.test +# description: TRTREE index parity — coverage of every column type the index +# supports: STBOX, TBOX, the 5 span types (intspan, bigintspan, +# floatspan, datespan, tstzspan) and the 8 temporal types +# (tint, tfloat, tbool, ttext, tgeompoint, tgeogpoint, +# tgeometry, tgeography). +# +# Each block builds a TRTREE index over a small fixture and +# fires a query whose `&&` predicate must hit exactly one row. +# The index is therefore exercised end-to-end: CREATE INDEX +# builds it via the parallel construct path, and the optimizer +# rewrites the predicate into an index scan. +# group: [sql] + +require mobilityduck + +# ============================================================================= +# Box types +# ============================================================================= + +statement ok +CREATE TABLE idx_stbox(b stbox); + +statement ok +INSERT INTO idx_stbox VALUES ('STBOX X((0,0),(3,3))'::stbox), ('STBOX X((10,10),(13,13))'::stbox); + +statement ok +CREATE INDEX i_stbox ON idx_stbox USING TRTREE (b); + +query I +SELECT count(*) FROM idx_stbox WHERE b && 'STBOX X((1,1),(2,2))'::stbox; +---- +1 + +statement ok +CREATE TABLE idx_tbox(b tbox); + +statement ok +INSERT INTO idx_tbox VALUES + ('TBOXFLOAT XT([0,3], [2000-01-01, 2000-01-03])'::tbox), + ('TBOXFLOAT XT([10,13], [2001-01-01, 2001-01-03])'::tbox); + +statement ok +CREATE INDEX i_tbox ON idx_tbox USING TRTREE (b); + +query I +SELECT count(*) FROM idx_tbox WHERE b && 'TBOXFLOAT XT([1,2], [2000-01-02, 2000-01-02])'::tbox; +---- +1 + +# ============================================================================= +# Span types +# ============================================================================= + +statement ok +CREATE TABLE idx_intspan(s intspan); + +statement ok +INSERT INTO idx_intspan VALUES ('[1,5]'::intspan), ('[10,15]'::intspan); + +statement ok +CREATE INDEX i_intspan ON idx_intspan USING TRTREE (s); + +query I +SELECT count(*) FROM idx_intspan WHERE s && '[2,3]'::intspan; +---- +1 + +statement ok +CREATE TABLE idx_bigintspan(s bigintspan); + +statement ok +INSERT INTO idx_bigintspan VALUES ('[1,5]'::bigintspan), ('[10,15]'::bigintspan); + +statement ok +CREATE INDEX i_bigintspan ON idx_bigintspan USING TRTREE (s); + +query I +SELECT count(*) FROM idx_bigintspan WHERE s && '[2,3]'::bigintspan; +---- +1 + +statement ok +CREATE TABLE idx_floatspan(s floatspan); + +statement ok +INSERT INTO idx_floatspan VALUES ('[1.0,5.0]'::floatspan), ('[10.0,15.0]'::floatspan); + +statement ok +CREATE INDEX i_floatspan ON idx_floatspan USING TRTREE (s); + +query I +SELECT count(*) FROM idx_floatspan WHERE s && '[2.0,3.0]'::floatspan; +---- +1 + +statement ok +CREATE TABLE idx_datespan(s datespan); + +statement ok +INSERT INTO idx_datespan VALUES + ('[2000-01-01, 2000-01-05]'::datespan), + ('[2001-01-01, 2001-01-05]'::datespan); + +statement ok +CREATE INDEX i_datespan ON idx_datespan USING TRTREE (s); + +query I +SELECT count(*) FROM idx_datespan WHERE s && '[2000-01-02, 2000-01-03]'::datespan; +---- +1 + +statement ok +CREATE TABLE idx_tstzspan(s tstzspan); + +statement ok +INSERT INTO idx_tstzspan VALUES + ('[2000-01-01, 2000-01-05]'::tstzspan), + ('[2001-01-01, 2001-01-05]'::tstzspan); + +statement ok +CREATE INDEX i_tstzspan ON idx_tstzspan USING TRTREE (s); + +query I +SELECT count(*) FROM idx_tstzspan WHERE s && '[2000-01-02, 2000-01-03]'::tstzspan; +---- +1 + +# ============================================================================= +# Temporal numeric / non-spatial types +# ============================================================================= + +statement ok +CREATE TABLE idx_tint(t tint); + +statement ok +INSERT INTO idx_tint VALUES ('1@2000-01-01'::tint), ('5@2001-01-01'::tint); + +statement ok +CREATE INDEX i_tint ON idx_tint USING TRTREE (t); + +query I +SELECT count(*) FROM idx_tint + WHERE t && 'TBOXINT XT([0,2], [2000-01-01, 2000-01-02])'::tbox; +---- +1 + +statement ok +CREATE TABLE idx_tfloat(t tfloat); + +statement ok +INSERT INTO idx_tfloat VALUES ('1.0@2000-01-01'::tfloat), ('5.0@2001-01-01'::tfloat); + +statement ok +CREATE INDEX i_tfloat ON idx_tfloat USING TRTREE (t); + +query I +SELECT count(*) FROM idx_tfloat + WHERE t && 'TBOXFLOAT XT([0,2], [2000-01-01, 2000-01-02])'::tbox; +---- +1 + +statement ok +CREATE TABLE idx_tbool(t tbool); + +statement ok +INSERT INTO idx_tbool VALUES ('true@2000-01-01'::tbool), ('false@2001-01-01'::tbool); + +statement ok +CREATE INDEX i_tbool ON idx_tbool USING TRTREE (t); + +query I +SELECT count(*) FROM idx_tbool WHERE t && '[2000-01-01, 2000-01-02]'::tstzspan; +---- +1 + +statement ok +CREATE TABLE idx_ttext(t ttext); + +statement ok +INSERT INTO idx_ttext VALUES ('"a"@2000-01-01'::ttext), ('"b"@2001-01-01'::ttext); + +statement ok +CREATE INDEX i_ttext ON idx_ttext USING TRTREE (t); + +query I +SELECT count(*) FROM idx_ttext WHERE t && '[2000-01-01, 2000-01-02]'::tstzspan; +---- +1 + +# ============================================================================= +# Temporal-spatial types — Cartesian +# ============================================================================= + +statement ok +CREATE TABLE idx_tgeompoint(t tgeompoint); + +statement ok +INSERT INTO idx_tgeompoint VALUES + ('Point(0 0)@2000-01-01'::tgeompoint), + ('Point(10 10)@2001-01-01'::tgeompoint); + +statement ok +CREATE INDEX i_tgeompoint ON idx_tgeompoint USING TRTREE (t); + +query I +SELECT count(*) FROM idx_tgeompoint + WHERE t && 'STBOX XT(((-1,-1),(1,1)),[2000-01-01, 2000-01-02])'::stbox; +---- +1 + +statement ok +CREATE TABLE idx_tgeometry(t tgeometry); + +statement ok +INSERT INTO idx_tgeometry VALUES + ('Point(0 0)@2000-01-01'::tgeometry), + ('Point(10 10)@2001-01-01'::tgeometry); + +statement ok +CREATE INDEX i_tgeometry ON idx_tgeometry USING TRTREE (t); + +query I +SELECT count(*) FROM idx_tgeometry + WHERE t && 'STBOX XT(((-1,-1),(1,1)),[2000-01-01, 2000-01-02])'::stbox; +---- +1 + +# ============================================================================= +# Temporal-spatial types — geographic (SRID 4326 by default) +# ============================================================================= + +statement ok +CREATE TABLE idx_tgeogpoint(t tgeogpoint); + +statement ok +INSERT INTO idx_tgeogpoint VALUES + ('Point(0 0)@2000-01-01'::tgeogpoint), + ('Point(10 10)@2001-01-01'::tgeogpoint); + +statement ok +CREATE INDEX i_tgeogpoint ON idx_tgeogpoint USING TRTREE (t); + +query I +SELECT count(*) FROM idx_tgeogpoint + WHERE t && 'GEODSTBOX XT(((-1,-1),(1,1)),[2000-01-01, 2000-01-02])'::stbox; +---- +1 + +statement ok +CREATE TABLE idx_tgeography(t tgeography); + +statement ok +INSERT INTO idx_tgeography VALUES + ('Point(0 0)@2000-01-01'::tgeography), + ('Point(10 10)@2001-01-01'::tgeography); + +statement ok +CREATE INDEX i_tgeography ON idx_tgeography USING TRTREE (t); + +query I +SELECT count(*) FROM idx_tgeography + WHERE t && 'GEODSTBOX XT(((-1,-1),(1,1)),[2000-01-01, 2000-01-02])'::stbox; +---- +1 + +# ============================================================================= +# Unsupported column type — regression for the original report. +# +# TRTREE over a non-bbox, non-temporal column used to throw a DuckDB +# InternalException, which the engine renders as the generic +# "assertion failure within DuckDB" crash message. It must now surface +# as a clean Binder Error instead. +# ============================================================================= + +statement ok +CREATE TABLE idx_unsupported(x integer); + +statement ok +INSERT INTO idx_unsupported VALUES (1), (2); + +statement error +CREATE INDEX i_unsupported ON idx_unsupported USING TRTREE (x); +---- +TRTREE index supports