From 8b034d0c30e06fe9cf5c15558e4c5d796ccb6537 Mon Sep 17 00:00:00 2001 From: Esteban Zimanyi Date: Thu, 11 Jun 2026 20:32:32 +0200 Subject: [PATCH 1/5] fix(ci): make the Nix build work with NES_ENABLE_MEOS=OFF and stock paho-mqtt-cpp The Nix environment provides paho-mqtt-cpp as a shared-only library and does not include libmeos. Four changes make the build pass: - flake.nix: add stock paho-mqtt-c + paho-mqtt-cpp to baseThirdPartyDeps; pass -DNES_ENABLE_MEOS=OFF in both defaultPackage and devShell cmakeFlags. - nes-plugins/CMakeLists.txt: introduce NES_ENABLE_MQTT and NES_ENABLE_MEOS options that gate the respective plugin subdirectories via activate_optional_plugin(). - nes-plugins/Sources/MQTTSource, nes-plugins/Sinks/MQTTSink: select PahoMqttCpp::paho-mqttpp3-static when available, fall back to the shared PahoMqttCpp::paho-mqttpp3 target (Nix only ships the shared variant). - nes-physical-operators: gate all MEOS-specific add_plugin calls and the nes-meos link behind if(NES_ENABLE_MEOS) in three CMakeLists files (top-level, Functions/Meos, Aggregation/Function/Meos). - CMakeLists.txt (root): declare option(NES_ENABLE_MEOS) and add_compile_definitions(NES_ENABLE_MEOS) before the nes-* subdirectory loop so the preprocessor symbol is visible to all sibling components. - nes-query-optimizer/LowerToPhysicalWindowedAggregation.cpp: guard the TemporalSequenceAggregationPhysicalFunction include and instantiation with #ifdef NES_ENABLE_MEOS so the translation unit compiles and links when MEOS is disabled. --- CMakeLists.txt | 7 +++ flake.nix | 4 ++ nes-physical-operators/CMakeLists.txt | 6 +- .../Aggregation/Function/Meos/CMakeLists.txt | 2 + .../src/Functions/Meos/CMakeLists.txt | 2 + nes-plugins/CMakeLists.txt | 55 ++++++++++--------- nes-plugins/Sinks/MQTTSink/CMakeLists.txt | 9 ++- nes-plugins/Sources/MQTTSource/CMakeLists.txt | 9 ++- .../LowerToPhysicalWindowedAggregation.cpp | 4 ++ 9 files changed, 67 insertions(+), 31 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9dcaa97099..c67d32c34d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -239,6 +239,13 @@ endif () add_custom_target(build_all_plugins) +# Declared here so add_compile_definitions reaches all sibling nes-* targets. +# nes-plugins/CMakeLists.txt re-declares the same option (no-op when cached). +option(NES_ENABLE_MEOS "Enable MEOS plugin (requires libmeos installed on the system)" ON) +if(NES_ENABLE_MEOS) + add_compile_definitions(NES_ENABLE_MEOS) +endif() + # Add target for common lib, which contains a minimal set # of shared functionality used by all components of nes file(GLOB NES_DIRECTORIES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "nes-*") diff --git a/flake.nix b/flake.nix index 2d9788a4de..026e310022 100644 --- a/flake.nix +++ b/flake.nix @@ -54,6 +54,8 @@ tbb python3 openjdk21 + paho-mqtt-c + paho-mqtt-cpp ]) ++ [ follyPkg antlr4Pkg ]; antlr4Jar = pkgs.fetchurl { @@ -244,6 +246,7 @@ "-DNES_ENABLES_TESTS=ON" "-DCMAKE_MODULE_PATH=${libdwarfModule}/share/cmake/Modules" "-DANTLR4_JAR_LOCATION=${antlr4Jar}" + "-DNES_ENABLE_MEOS=OFF" ]; enableParallelBuilding = true; @@ -347,6 +350,7 @@ "-DLLVM_DIR=${commonCmakeEnv.LLVM_DIR}" "-DANTLR4_JAR_LOCATION=${antlr4Jar}" "-DCMAKE_MODULE_PATH=${libdwarfModule}/share/cmake/Modules" + "-DNES_ENABLE_MEOS=OFF" ]; shellHook = '' unset NES_PREBUILT_VCPKG_ROOT diff --git a/nes-physical-operators/CMakeLists.txt b/nes-physical-operators/CMakeLists.txt index c38afaf4db..b62a5922a9 100644 --- a/nes-physical-operators/CMakeLists.txt +++ b/nes-physical-operators/CMakeLists.txt @@ -19,7 +19,11 @@ get_source(nes-physical-operators NES_PHYSICAL_OPERATORS_SOURCE_FILES) # Add Library add_library(nes-physical-operators ${NES_PHYSICAL_OPERATORS_SOURCE_FILES}) -target_link_libraries(nes-physical-operators PUBLIC nes-sources nes-sinks nes-nautilus nes-meos) +if(NES_ENABLE_MEOS) + target_link_libraries(nes-physical-operators PUBLIC nes-sources nes-sinks nes-nautilus nes-meos) +else() + target_link_libraries(nes-physical-operators PUBLIC nes-sources nes-sinks nes-nautilus) +endif() target_include_directories(nes-physical-operators PUBLIC $ $) diff --git a/nes-physical-operators/src/Aggregation/Function/Meos/CMakeLists.txt b/nes-physical-operators/src/Aggregation/Function/Meos/CMakeLists.txt index c34e12f47e..0b107d5c2a 100644 --- a/nes-physical-operators/src/Aggregation/Function/Meos/CMakeLists.txt +++ b/nes-physical-operators/src/Aggregation/Function/Meos/CMakeLists.txt @@ -10,5 +10,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +if(NES_ENABLE_MEOS) add_plugin(TemporalSequence AggregationPhysicalFunction nes-physical-operators TemporalSequenceAggregationPhysicalFunction.cpp) add_plugin(Var AggregationPhysicalFunction nes-physical-operators VarAggregationFunction.cpp) +endif() diff --git a/nes-physical-operators/src/Functions/Meos/CMakeLists.txt b/nes-physical-operators/src/Functions/Meos/CMakeLists.txt index 516cdce4c3..cf83ac5aad 100644 --- a/nes-physical-operators/src/Functions/Meos/CMakeLists.txt +++ b/nes-physical-operators/src/Functions/Meos/CMakeLists.txt @@ -10,9 +10,11 @@ # See the License for the specific language governing permissions and # limitations under the License. +if(NES_ENABLE_MEOS) add_plugin(TemporalIntersects PhysicalFunction nes-physical-operators TemporalIntersectsPhysicalFunction.cpp) add_plugin(TemporalIntersectsGeometry PhysicalFunction nes-physical-operators TemporalIntersectsGeometryPhysicalFunction.cpp) add_plugin(TemporalAIntersectsGeometry PhysicalFunction nes-physical-operators TemporalAIntersectsGeometryPhysicalFunction.cpp) add_plugin(TemporalEContainsGeometry PhysicalFunction nes-physical-operators TemporalEContainsGeometryPhysicalFunction.cpp) add_plugin(TemporalEDWithinGeometry PhysicalFunction nes-physical-operators TemporalEDWithinGeometryPhysicalFunction.cpp) add_plugin(TemporalAtStBox PhysicalFunction nes-physical-operators TemporalAtStBoxPhysicalFunction.cpp) +endif() diff --git a/nes-plugins/CMakeLists.txt b/nes-plugins/CMakeLists.txt index e666dd616e..3d33385853 100644 --- a/nes-plugins/CMakeLists.txt +++ b/nes-plugins/CMakeLists.txt @@ -20,33 +20,36 @@ activate_optional_plugin("Sources/TCPSource" ON) # Enable the Generator source plugin; required by systests and repl tests activate_optional_plugin("Sources/GeneratorSource" ON) activate_optional_plugin("Sinks/VoidSink" ON) -activate_optional_plugin("Sources/MQTTSource" ON) -activate_optional_plugin("Sinks/MQTTSink" ON) - -# MEOS is a dependency -activate_optional_plugin("MEOS" ON) -message(STATUS "Enable MEOS Plugin") - -# Detect the platform -if (APPLE) - message(STATUS "Building on macOS") - # Set the include and library directories for Homebrew (macOS) - set(MEOS_INCLUDE_DIR "/opt/homebrew/include" CACHE PATH "Path to MEOS include directory") - set(MEOS_PLUGIN_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/include" CACHE PATH "Path to MEOS plugin include directory") - include_directories(SYSTEM ${MEOS_INCLUDE_DIR} ${MEOS_PLUGIN_INCLUDE_DIR}) - include_directories(SYSTEM ${MEOS_INCLUDE_DIR} ${MEOS_PLUGIN_INCLUDE_DIR}) - link_directories(${MEOS_LIBRARY_DIR} ) - -else() - message(STATUS "Building on Linux") - - # Default behavior for Linux - find_package(meos REQUIRED) - if(meos_FOUND) - message(STATUS "MEOS found: include=${meos_INCLUDE_DIR}, library=${meos}") - include_directories(SYSTEM ${meos_INCLUDE_DIR}) +option(NES_ENABLE_MQTT "Enable MQTT source and sink plugins (requires PahoMqttCpp)" ON) +activate_optional_plugin("Sources/MQTTSource" ${NES_ENABLE_MQTT}) +activate_optional_plugin("Sinks/MQTTSink" ${NES_ENABLE_MQTT}) + +option(NES_ENABLE_MEOS "Enable MEOS plugin (requires libmeos installed on the system)" ON) +activate_optional_plugin("MEOS" ${NES_ENABLE_MEOS}) +if (NES_ENABLE_MEOS) + message(STATUS "Enable MEOS Plugin") + + # Detect the platform + if (APPLE) + message(STATUS "Building on macOS") + # Set the include and library directories for Homebrew (macOS) + set(MEOS_INCLUDE_DIR "/opt/homebrew/include" CACHE PATH "Path to MEOS include directory") + set(MEOS_PLUGIN_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/include" CACHE PATH "Path to MEOS plugin include directory") + include_directories(SYSTEM ${MEOS_INCLUDE_DIR} ${MEOS_PLUGIN_INCLUDE_DIR}) + include_directories(SYSTEM ${MEOS_INCLUDE_DIR} ${MEOS_PLUGIN_INCLUDE_DIR}) + link_directories(${MEOS_LIBRARY_DIR} ) + else() - message(FATAL_ERROR "MEOS library not found") + message(STATUS "Building on Linux") + + # Default behavior for Linux + find_package(meos REQUIRED) + if(meos_FOUND) + message(STATUS "MEOS found: include=${meos_INCLUDE_DIR}, library=${meos}") + include_directories(SYSTEM ${meos_INCLUDE_DIR}) + else() + message(FATAL_ERROR "MEOS library not found") + endif() endif() endif() diff --git a/nes-plugins/Sinks/MQTTSink/CMakeLists.txt b/nes-plugins/Sinks/MQTTSink/CMakeLists.txt index 4bf42deeab..80b3122e89 100644 --- a/nes-plugins/Sinks/MQTTSink/CMakeLists.txt +++ b/nes-plugins/Sinks/MQTTSink/CMakeLists.txt @@ -23,5 +23,10 @@ target_include_directories(mqtt_sink_validation_plugin_library ) find_package(PahoMqttCpp CONFIG REQUIRED) -target_link_libraries(mqtt_sink_plugin_library PRIVATE PahoMqttCpp::paho-mqttpp3-static) -target_link_libraries(mqtt_sink_validation_plugin_library PRIVATE PahoMqttCpp::paho-mqttpp3-static) +if(TARGET PahoMqttCpp::paho-mqttpp3-static) + target_link_libraries(mqtt_sink_plugin_library PRIVATE PahoMqttCpp::paho-mqttpp3-static) + target_link_libraries(mqtt_sink_validation_plugin_library PRIVATE PahoMqttCpp::paho-mqttpp3-static) +else() + target_link_libraries(mqtt_sink_plugin_library PRIVATE PahoMqttCpp::paho-mqttpp3) + target_link_libraries(mqtt_sink_validation_plugin_library PRIVATE PahoMqttCpp::paho-mqttpp3) +endif() diff --git a/nes-plugins/Sources/MQTTSource/CMakeLists.txt b/nes-plugins/Sources/MQTTSource/CMakeLists.txt index 9f9ed0dd17..27db6702a0 100644 --- a/nes-plugins/Sources/MQTTSource/CMakeLists.txt +++ b/nes-plugins/Sources/MQTTSource/CMakeLists.txt @@ -23,5 +23,10 @@ target_include_directories(mqtt_source_validation_plugin_library ) find_package(PahoMqttCpp CONFIG REQUIRED) -target_link_libraries(mqtt_source_plugin_library PRIVATE PahoMqttCpp::paho-mqttpp3-static) -target_link_libraries(mqtt_source_validation_plugin_library PRIVATE PahoMqttCpp::paho-mqttpp3-static) +if(TARGET PahoMqttCpp::paho-mqttpp3-static) + target_link_libraries(mqtt_source_plugin_library PRIVATE PahoMqttCpp::paho-mqttpp3-static) + target_link_libraries(mqtt_source_validation_plugin_library PRIVATE PahoMqttCpp::paho-mqttpp3-static) +else() + target_link_libraries(mqtt_source_plugin_library PRIVATE PahoMqttCpp::paho-mqttpp3) + target_link_libraries(mqtt_source_validation_plugin_library PRIVATE PahoMqttCpp::paho-mqttpp3) +endif() diff --git a/nes-query-optimizer/src/RewriteRules/LowerToPhysical/LowerToPhysicalWindowedAggregation.cpp b/nes-query-optimizer/src/RewriteRules/LowerToPhysical/LowerToPhysicalWindowedAggregation.cpp index c994b91a9d..eb667c31ed 100644 --- a/nes-query-optimizer/src/RewriteRules/LowerToPhysical/LowerToPhysicalWindowedAggregation.cpp +++ b/nes-query-optimizer/src/RewriteRules/LowerToPhysical/LowerToPhysicalWindowedAggregation.cpp @@ -54,8 +54,10 @@ #include #include // Special-case lowering for TEMPORAL_SEQUENCE (multi-input) aggregation +#ifdef NES_ENABLE_MEOS #include #include +#endif namespace NES { @@ -128,6 +130,7 @@ getAggregationPhysicalFunctions(const WindowedAggregationLogicalOperator& logica const auto name = descriptor->getName(); // Custom lowering path for TEMPORAL_SEQUENCE: needs three field functions (lon, lat, ts) +#ifdef NES_ENABLE_MEOS if (name == std::string_view("TemporalSequence")) { auto tsDescriptor = std::dynamic_pointer_cast(descriptor); @@ -159,6 +162,7 @@ getAggregationPhysicalFunctions(const WindowedAggregationLogicalOperator& logica aggregationPhysicalFunctions.push_back(std::move(phys)); continue; } +#endif // Default path: use registry for single-input aggregations auto aggregationInputFunction = QueryCompilation::FunctionProvider::lowerFunction(descriptor->onField); From ae1067f4b19838ddab7237298de19d85b53590db Mon Sep 17 00:00:00 2001 From: Esteban Zimanyi Date: Wed, 24 Jun 2026 22:12:25 +0200 Subject: [PATCH 2/5] feat(codegen): IDL-driven NES MEOS-operator generator with idempotent build/grammar/QPC glue MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit tools/codegen/ contains codegen_nebula.py, which reads a JSON operator descriptor and emits the four-layer NebulaStream pipeline tuple (logical .hpp/.cpp + physical .hpp/.cpp) per MEOS scalar function, then idempotently injects add_plugin entries into the Meos CMakeLists files, lexer tokens and functionName alternations into AntlrSQL.g4, and dispatch cases into AntlrSQLQueryPlanCreator.cpp — each injection gated by a per-op marker so repeated runs are safe. build_descriptor.py classifies MEOS gap functions by signature into named SHAPEs that select the corresponding physical C++ template; trgeo-descriptor.json is the ready-to-use descriptor for the 34-operator trgeometry family (W148–W149). codegen_aggregations.py handles the separate aggregation four-layer shape. codegen_input.example.json documents the descriptor format. build_local.sh drives the NebulaStream dev-image cmake build without a host C++23 toolchain. The generator reproduces the committed W148–W149 trgeometry surface byte-for-byte from the descriptor. The generated IDL (meos-idl.json) is excluded from the repository and regenerated via MEOS-API run.py against the pinned MEOS headers. --- tools/codegen/.gitignore | 5 + tools/codegen/README.md | 167 + tools/codegen/build_descriptor.py | 687 +++ tools/codegen/build_local.sh | 31 + tools/codegen/codegen_aggregations.py | 2745 ++++++++++++ tools/codegen/codegen_input.example.json | 160 + tools/codegen/codegen_nebula.py | 5091 ++++++++++++++++++++++ tools/codegen/trgeo-descriptor.json | 311 ++ 8 files changed, 9197 insertions(+) create mode 100644 tools/codegen/.gitignore create mode 100644 tools/codegen/README.md create mode 100644 tools/codegen/build_descriptor.py create mode 100755 tools/codegen/build_local.sh create mode 100644 tools/codegen/codegen_aggregations.py create mode 100644 tools/codegen/codegen_input.example.json create mode 100644 tools/codegen/codegen_nebula.py create mode 100644 tools/codegen/trgeo-descriptor.json diff --git a/tools/codegen/.gitignore b/tools/codegen/.gitignore new file mode 100644 index 0000000000..50ab203187 --- /dev/null +++ b/tools/codegen/.gitignore @@ -0,0 +1,5 @@ +__pycache__/ +*.pyc +meos-idl*.json +.venv/ +venv/ diff --git a/tools/codegen/README.md b/tools/codegen/README.md new file mode 100644 index 0000000000..63928751f6 --- /dev/null +++ b/tools/codegen/README.md @@ -0,0 +1,167 @@ +# tools/codegen — IDL-driven MEOS operator generator for NebulaStream + +This directory contains the Python generator that produces the four-layer +NebulaStream pipeline tuple (logical .hpp/.cpp + physical .hpp/.cpp) for every +MEOS scalar function in a descriptor, and idempotently patches the CMakeLists, +Antlr grammar, and QPC dispatch so the generated operators build and parse +without manual edits. + +## Pipeline overview + +``` +MEOS headers (pinned) + │ + ▼ + MEOS-API run.py # libclang-based extractor (lives in MEOS-API repo) + │ produces meos-idl.json (name + signature authority; NOT committed here) + ▼ + build_descriptor.py # classifies gap functions into known operator SHAPEs + │ --sigs --gap --shapes --out + │ produces e.g. trgeo-descriptor.json + ▼ + codegen_nebula.py # emits C++ + patches build/grammar/QPC + │ --input --output-root + │ + ├── nes-logical-operators/include/Functions/Meos/LogicalFunction.hpp + ├── nes-logical-operators/src/Functions/Meos/LogicalFunction.cpp + ├── nes-physical-operators/include/Functions/Meos/PhysicalFunction.hpp + ├── nes-physical-operators/src/Functions/Meos/PhysicalFunction.cpp + │ + ├── (idempotent) nes-{logical,physical}-operators/src/Functions/Meos/CMakeLists.txt + ├── (idempotent) nes-sql-parser/AntlrSQL.g4 — lexer token + functionName rule + └── (idempotent) nes-sql-parser/src/AntlrSQLQueryPlanCreator.cpp — dispatch case +``` + +The IDL (`meos-idl.json`) is a 2 MB generated artifact; regenerate it via +`python3 run.py ` in the MEOS-API repo. It is the +name/signature authority for all downstream ecosystem bindings and is never +committed here. + +## Regenerating the IDL + +```bash +# In the MEOS-API repo, against the pinned MEOS headers: +python3 run.py /usr/local/include/meos.h /usr/local/include/meos_geo.h \ + > /tmp/meos-idl.json +``` + +## Running the generator + +```bash +# 1. Build a descriptor from the IDL gap (example: trgeometry family) +python3 tools/codegen/build_descriptor.py \ + --sigs /tmp/cmp_sigs.txt \ + --gap /tmp/nebula_gap.txt \ + --shapes trgeometry_geo_predicate,geo_trgeometry_predicate,trgeometry_geo_dwithin,\ +trgeometry_trgeometry_predicate,trgeometry_trgeometry_dwithin,trgeometry_nad \ + --out tools/codegen/trgeo-descriptor.json + +# 2. Emit C++ files and patch build/grammar/QPC +python3 tools/codegen/codegen_nebula.py \ + --input tools/codegen/trgeo-descriptor.json \ + --output-root . + +# Optional flags: +# --no-parser-glue skip AntlrSQL.g4 + AntlrSQLQueryPlanCreator.cpp injection +# --no-cmake-entries skip CMakeLists.txt injection +``` + +Both injectors are idempotent: per-op markers +(`/* BEGIN CODEGEN PARSER GLUE: */ … /* END CODEGEN PARSER GLUE */`) +gate each block, and the script skips insertion when the marker is already +present. The physical CMakeLists places `add_plugin` entries inside the +`if(NES_ENABLE_MEOS) … endif()` guard. + +## Operator SHAPEs (build_descriptor.py classifiers) + +Each SHAPE classifier matches a class of MEOS function signatures and +synthesises the per-event SQL argument list and the `build_*` flag that +selects the corresponding physical C++ template in `codegen_nebula.py`. + +| build_* flag | Per-event SQL args | Typical MEOS signature | +|---|---|---| +| `build_temporal_point` | lon, lat, ts, geometry | `int fn(Temporal*, GSERIALIZED*)` | +| `build_temporal_point_with_dist` | lon, lat, ts, geometry, dist | `int fn(Temporal*, GSERIALIZED*, double)` | +| `build_tgeo_tgeo` | lonA, latA, tsA, lonB, latB, tsB | `int fn(Temporal*, Temporal*)` | +| `build_tgeo_tgeo_with_dist` | lonA, latA, tsA, lonB, latB, tsB, dist | `int fn(Temporal*, Temporal*, double)` | +| `build_trgeometry_geo` | ref_wkt, x1, y1, theta1, ts1, tgt_wkt | `int fn(Trgeometry*, GSERIALIZED*)` | +| `build_geo_trgeometry` | tgt_wkt, ref_wkt, x1, y1, theta1, ts1 | `int fn(GSERIALIZED*, Trgeometry*)` | +| `build_trgeometry_geo_with_dist` | ref_wkt, x1, y1, theta1, ts1, tgt_wkt, dist | `int fn(Trgeometry*, GSERIALIZED*, double)` | +| `build_trgeometry_trgeometry` | ref1_wkt, x1, y1, theta1, ts1, ref2_wkt, x2, y2, theta2, ts2 | `int fn(Trgeometry*, Trgeometry*)` | +| `build_trgeometry_trgeometry_with_dist` | + dist (11-arg) | `int fn(Trgeometry*, Trgeometry*, double)` | +| `build_tnumber_point_with_scalar` | value, ts, scalar | `int fn(Temporal*, double\|int)` | +| `build_generic` | arbitrary typed fields | any MEOS scalar, via GENERIC_RETURNS map | + +## C-to-NES type map (GENERIC_RETURNS) + +| MEOS C return | nautilus_return | C++ return | zero literal | +|---|---|---|---| +| `int` | `INT32` | `int` | `0` | +| `double` | `FLOAT64` | `double` | `0.0` | +| `bool` | `BOOLEAN` | `bool` | `false` | +| `int` (from `Temporal*` via accessor) | `INT32` | `int` | `0` | +| `double` (from `Temporal*` via accessor) | `FLOAT64` | `double` | `0.0` | +| `VariableSizedData` (hex-WKB) | `VARSIZED` | `VariableSizedData` | `nullptr` | + +Per-event field types follow the same mapping: `double` -> `nautilus::val`, +`uint64_t` -> `nautilus::val`, `VariableSizedData` -> pointer+size pair. + +## Descriptor format + +`codegen_input.example.json` shows a minimal descriptor. +`trgeo-descriptor.json` shows the full trgeometry family (80 operators). + +Key fields: + +| Field | Meaning | +|---|---| +| `nebula_name` | PascalCase class name prefix (generator adds `LogicalFunction` / `PhysicalFunction`) | +| `sql_token` | UPPER_SNAKE_CASE Antlr lexer token and SQL function name | +| `meos_call` | Underlying MEOS C symbol | +| `return_type` | C return type (`int`, `double`, `bool`) | +| `nautilus_return` | NES `DataType::Type` enum value | +| `build_*` | SHAPE flag selecting the physical C++ template | +| `comment_one_liner` | Drops into the C++ doc comment | + +## Self-test (reproducibility gate) + +Run the generator against `trgeo-descriptor.json` at the W147 baseline +(commit `ac7e7d1469`, the clean state before any trgeometry files existed) +and diff the output against the committed trgeometry files at W149 +(`fork/feat/nebula-codegen-w149-trgeometry-trgeometry-predicates`, +commit `6b2180c089`). All 80 per-op files must be byte-identical. + +```bash +git worktree add /tmp/nebgen-selftest ac7e7d1469 +cp -r tools/codegen /tmp/nebgen-selftest/tools/ +cd /tmp/nebgen-selftest +python3 tools/codegen/codegen_nebula.py \ + --input tools/codegen/trgeo-descriptor.json \ + --output-root . \ + --no-cmake-entries --no-parser-glue +# diff against W149 committed files +git diff 6b2180c089 -- 'nes-*/include/Functions/Meos/*Trgeometry*' \ + 'nes-*/src/Functions/Meos/*Trgeometry*' +git worktree remove /tmp/nebgen-selftest +``` + +## Local compile check + +`build_local.sh` drives the NebulaStream dev image build for the three +operator/parser libraries without requiring a host C++23 toolchain. +It temporarily disables the optional MQTT plugins (absent in the dev image) +and restores `nes-plugins/CMakeLists.txt` on exit via a `trap`. + +```bash +tools/codegen/build_local.sh # default: nes-physical-operators nes-logical-operators nes-sql-parser +NES_DEV_IMAGE=localhost/nes-development:my-tag \ + BUILD_DIR=build-custom \ + tools/codegen/build_local.sh nes-physical-operators +``` + +## Aggregation operators + +`codegen_aggregations.py` handles the separate aggregation four-layer shape +(lift / combine / lower / cleanup), whose per-op structure differs from the +scalar template above. Run it the same way, with a descriptor whose operators +carry aggregation-specific fields. diff --git a/tools/codegen/build_descriptor.py b/tools/codegen/build_descriptor.py new file mode 100644 index 0000000000..007e480abb --- /dev/null +++ b/tools/codegen/build_descriptor.py @@ -0,0 +1,687 @@ +#!/usr/bin/env python3 +"""Build a codegen_nebula.py descriptor from classified MEOS gap functions. + +Reads a header-signature dump (one `extern ();` per line, as +produced from the in-container /usr/local/include/meos*.h) plus a gap list +(streamable functions not yet wired by a Nebula operator), classifies each +function into a known operator SHAPE, and emits the JSON descriptor that +codegen_nebula.py consumes. + +A SHAPE knows: the per-event SQL arg list, the `build_*` flag selecting the +physical-cpp template, the MEOS return marshaling, and the per-base-type input +ctor (tfloat_in / tint_in / ...). Adding a family = adding a classifier here; +the C++ templates live in codegen_nebula.py. + +This keeps the bulk-emission measured-not-guessed: only functions whose exact +in-header signature matches a SHAPE are emitted, and only if they are in the +gap. Functions that don't match any SHAPE are reported, never silently dropped. + +Usage: + build_descriptor.py --sigs /tmp/cmp_sigs.txt --gap /tmp/nebula_gap.txt \\ + --shapes cmp_scalar_tempfirst --out wave22.json +""" +import argparse +import json +import re +import sys + +# --- per-base-type input construction ------------------------------------- +# token in the meos-call name -> (tnumber_in_fn, cpp value type, wkt format) +BASE = { + "tfloat": ("tfloat_in", "double", "{}@{}"), + "tint": ("tint_in", "int", "{}@{}"), +} +SCALAR_CPP = {"double": "double", "int": "int"} + + +def pascal(meos_call): + return "".join(p.capitalize() for p in meos_call.split("_")) + + +def parse_sigs(path): + """name -> (ret, [normalized-arg-type, ...]).""" + out = {} + for ln in open(path): + m = re.match(r"extern\s+([\w ]+?)\s*(\*?)\s*(\w+)\(([^)]*)\);", ln.strip()) + if not m: + continue + ret = (m.group(1) + m.group(2)).strip() + name = m.group(3) + args = [] + for a in m.group(4).split(","): + a = a.strip() + if not a or a == "void": + continue + base = re.sub(r"^const\s+", "", a) + ty = base.split()[0] + ("*" if "*" in a else "") + args.append(ty) + out[name] = (ret, tuple(args)) + return out + + +# --- SHAPE classifiers ------------------------------------------------------ +# Each returns a descriptor dict for `fn` if it matches, else None. + +def cmp_scalar_tempfirst(fn, ret, args): + """ever/always comparison: int fn(const Temporal*, double|int), temp first. + Reuses build_tnumber_point_with_scalar (already in codegen_nebula.py).""" + if ret != "int" or args not in (("Temporal*", "double"), ("Temporal*", "int")): + return None + base = "tfloat" if args[1] == "double" else "tint" + in_fn, vcpp, wkt = BASE[base] + return { + "nebula_name": pascal(fn), + "sql_token": fn.upper(), + "meos_call": fn, + "args": [ + {"name": "value", "nautilus_type": vcpp, "cpp_type": vcpp}, + {"name": "timestamp", "nautilus_type": "uint64_t", "cpp_type": "uint64_t"}, + {"name": "scalar", "nautilus_type": vcpp, "cpp_type": vcpp}, + ], + "return_type": "int", + "nautilus_return": "INT32", + "build_tnumber_point_with_scalar": True, + "tnumber_value_cpp_type": vcpp, + "scalar_cpp_type": SCALAR_CPP[vcpp], + "tnumber_wkt_format": wkt, + "tnumber_in_fn": in_fn, + "comment_one_liner": ( + f"Per-event {fn.split('_')[0]} comparison of a single-instant {base} " + f"(built from value+timestamp) against a scalar constant."), + } + + +def cmp_scalar_scalarfirst(fn, ret, args): + """ever/always comparison: int fn(double|int, const Temporal*), scalar first. + Reuses build_tnumber_scalar_first (MEOS call passes scalar as 1st arg).""" + if ret != "int" or args not in (("double", "Temporal*"), ("int", "Temporal*")): + return None + base = "tfloat" if args[0] == "double" else "tint" + in_fn, vcpp, wkt = BASE[base] + return { + "nebula_name": pascal(fn), + "sql_token": fn.upper(), + "meos_call": fn, + "args": [ + {"name": "value", "nautilus_type": vcpp, "cpp_type": vcpp}, + {"name": "timestamp", "nautilus_type": "uint64_t", "cpp_type": "uint64_t"}, + {"name": "scalar", "nautilus_type": vcpp, "cpp_type": vcpp}, + ], + "return_type": "int", + "nautilus_return": "INT32", + "build_tnumber_scalar_first": True, + "tnumber_value_cpp_type": vcpp, + "scalar_cpp_type": SCALAR_CPP[vcpp], + "tnumber_wkt_format": wkt, + "tnumber_in_fn": in_fn, + "comment_one_liner": ( + f"Per-event {fn.split('_')[0]} comparison of a scalar constant against a " + f"single-instant {base} (built from value+timestamp); scalar-first MEOS arg order."), + } + + +def cmp_two_temporal(fn, ret, args): + """Generic two-temporal comparison: int fn(const Temporal*, const Temporal*) + on the *_temporal_temporal functions. Builds two single-instant tfloats from + (valueA,tsA)/(valueB,tsB) and reuses build_two_tnumber_points.""" + if ret != "int" or args != ("Temporal*", "Temporal*") or not fn.endswith("_temporal_temporal"): + return None + in_fn, vcpp, wkt = BASE["tfloat"] + return { + "nebula_name": pascal(fn), + "sql_token": fn.upper(), + "meos_call": fn, + "args": [ + {"name": "valueA", "nautilus_type": vcpp, "cpp_type": vcpp}, + {"name": "tsA", "nautilus_type": "uint64_t", "cpp_type": "uint64_t"}, + {"name": "valueB", "nautilus_type": vcpp, "cpp_type": vcpp}, + {"name": "tsB", "nautilus_type": "uint64_t", "cpp_type": "uint64_t"}, + ], + "return_type": "int", + "nautilus_return": "INT32", + "build_two_tnumber_points": True, + "tnumber_value_cpp_type": vcpp, + "tnumber_wkt_format": wkt, + "tnumber_in_fn": in_fn, + "comment_one_liner": ( + f"Per-event {fn.split('_')[0]} comparison between two single-instant " + f"temporals (built from valueA/tsA and valueB/tsB)."), + } + + +def _args(spec): + return [{"name": n, "nautilus_type": t, + "cpp_type": "const char*" if t == "VariableSizedData" else t} for n, t in spec] + + +# per-template SQL arg layouts (must match the physical-cpp template's parameterValues order) +_A_TGEO_GEO = [("lon", "double"), ("lat", "double"), ("timestamp", "uint64_t"), ("geometry", "VariableSizedData")] +_A_TWO_TGEO = [("lonA", "double"), ("latA", "double"), ("tsA", "uint64_t"), + ("lonB", "double"), ("latB", "double"), ("tsB", "uint64_t")] +_A_TCB_CB = [("lon", "double"), ("lat", "double"), ("radius", "double"), ("timestamp", "uint64_t"), ("cbuffer", "VariableSizedData")] +_A_TWO_TCB = [("lonA", "double"), ("latA", "double"), ("radiusA", "double"), ("tsA", "uint64_t"), + ("lonB", "double"), ("latB", "double"), ("radiusB", "double"), ("tsB", "uint64_t")] + + +_SCALAR_RET = {"int": ("int", "INT32"), "double": ("double", "FLOAT64"), "bool": ("bool", "BOOLEAN")} + + +def _mk_scalar(fn, flag, argspec, note, ret="int"): + rt, nr = _SCALAR_RET[ret] + return { + "nebula_name": pascal(fn), + "sql_token": fn.upper(), + "meos_call": fn, + "args": _args(argspec), + "return_type": rt, + "nautilus_return": nr, + flag: True, + "comment_one_liner": note, + } + + +def sprel_scalar_existing(fn, ret, args): + """Scalar-returning (int/double/bool) spatial-relation / comparison / topological / + distance whose per-event input build is already covered by an existing + physical-cpp template. Routes by arg-shape + type token; returns None for + shapes needing a new template (tnpoint/tpose native two-temporal, reversed-arg, + text/bool object args, non-scalar return).""" + if ret not in _SCALAR_RET: + return None + if args == ("Temporal*", "GSERIALIZED*") and ("_tgeo_" in fn or "_tpoint_" in fn or "_tspatial_" in fn): + return _mk_scalar(fn, "build_temporal_point", _A_TGEO_GEO, + f"Per-event {fn}: single-instant tgeompoint vs a static geometry -> {ret}.", ret) + if args == ("Temporal*", "Temporal*") and "tcbuffer" in fn: + return _mk_scalar(fn, "build_two_tcbuffer_points", _A_TWO_TCB, + f"Per-event {fn} between two single-instant tcbuffers -> {ret}.", ret) + if args == ("Temporal*", "Temporal*") and not ("tnpoint" in fn or "tpose" in fn or "tnumber" in fn): + return _mk_scalar(fn, "build_two_temporal_points", _A_TWO_TGEO, + f"Per-event {fn} between two single-instant tgeompoints -> {ret}.", ret) + if args == ("Temporal*", "Cbuffer*"): + return _mk_scalar(fn, "build_tcbuffer_point_cbuffer", _A_TCB_CB, + f"Per-event {fn}: single-instant tcbuffer vs a static cbuffer -> {ret}.", ret) + return None + + +# leading/embedded type token -> generic input-builder key (codegen_nebula GENERIC_INPUTS) +_GENERIC_INPUT_FOR = [ + ("tgeompoint", "tgeompoint"), ("tgeogpoint", "tgeompoint"), ("tgeometry", "tgeometry"), + ("tcbuffer", "tcbuffer"), ("tnpoint", "tnpoint"), ("tpose", "tpose"), + ("tfloat", "tfloat"), ("tint", "tint"), ("tbool", "tbool"), + ("tnumber", "tfloat"), ("tgeo", "tgeompoint"), ("tpoint", "tgeompoint"), + ("tspatial", "tgeompoint"), +] + + +def _infer_input(fn): + for tok, inp in _GENERIC_INPUT_FOR: + if fn.startswith(tok + "_") or ("_" + tok + "_") in fn or fn.endswith("_" + tok): + return inp + return None + + +def temporal_unary_scalar(fn, ret, args): + """Unary scalar accessor: int|double|bool fn(const Temporal*). Generic shape; + input type inferred from the name token.""" + if args != ("Temporal*",): + return None + rk = {"int": "int", "double": "double", "bool": "bool"}.get(ret) + if not rk: + return None + inp = _infer_input(fn) + if not inp: + return None + return { + "nebula_name": pascal(fn), "sql_token": fn.upper(), "meos_call": fn, + "build_generic": True, "input_type": inp, "extra_args": [], "return_kind": rk, + "comment_one_liner": f"Per-event {fn}: {rk} accessor over a single-instant {inp}.", + } + + +_SCALAR_CPP = {"double": "double", "int": "int32_t", "bool": "bool"} + + +def temporal_x_scalar(fn, ret, args): + """int|double|bool fn(const Temporal*, scalar). Generic shape, one scalar extra.""" + if len(args) != 2 or args[0] != "Temporal*" or args[1] not in _SCALAR_CPP: + return None + rk = {"int": "int", "double": "double", "bool": "bool"}.get(ret) + inp = _infer_input(fn) + if not rk or not inp: + return None + return { + "nebula_name": pascal(fn), "sql_token": fn.upper(), "meos_call": fn, + "build_generic": True, "input_type": inp, + "extra_args": [{"kind": "scalar", "cpp": _SCALAR_CPP[args[1]]}], "return_kind": rk, + "comment_one_liner": f"Per-event {fn}: single-instant {inp} against a scalar -> {rk}.", + } + + +def temporal_x_geom(fn, ret, args): + """int|double|bool fn(const Temporal*, const GSERIALIZED*). Generic shape, one geom extra.""" + if args != ("Temporal*", "GSERIALIZED*"): + return None + rk = {"int": "int", "double": "double", "bool": "bool"}.get(ret) + inp = _infer_input(fn) + if not rk or not inp: + return None + return { + "nebula_name": pascal(fn), "sql_token": fn.upper(), "meos_call": fn, + "build_generic": True, "input_type": inp, + "extra_args": [{"kind": "geom"}], "return_kind": rk, + "comment_one_liner": f"Per-event {fn}: single-instant {inp} against a static geometry -> {rk}.", + } + + +def _result_extract_kind(fn): + """Scalar return_kind for a Temporal*-returning transform whose single-instant + result carries a tint/tfloat/tbool value — inferred from the function name.""" + if "_to_tint" in fn: + return "extract_int" + if "_to_tfloat" in fn: + return "extract_double" + if "_to_tbool" in fn: + return "extract_bool" + if fn.startswith("tfloat_"): + return "extract_double" + if fn.startswith("tint_"): + return "extract_int" + if fn.startswith("tbool_"): + return "extract_bool" + return None + + +def temporal_extract_scalar(fn, ret, args): + """Unary Temporal->Temporal* transform whose single-instant result carries a + scalar value (tfloat_ceil, tbool_to_tint, ...). Generic shape with an extract + marshaler. Result/text/geo-returning transforms are deferred (varsize).""" + if ret != "Temporal*" or args != ("Temporal*",): + return None + inp = _infer_input(fn) + rk = _result_extract_kind(fn) + if not inp or not rk: + return None + return { + "nebula_name": pascal(fn), "sql_token": fn.upper(), "meos_call": fn, + "build_generic": True, "input_type": inp, "extra_args": [], "return_kind": rk, + "comment_one_liner": f"Per-event {fn}: single-instant {inp} transform, value extracted -> {rk[8:]}.", + } + + +_BOX_PARSER = { # name-suffix token -> (cpp box type, MEOS parser, header) + "stbox": ("STBox", "stbox_in", "meos_geo.h"), + "tbox": ("TBox", "tbox_in", "meos.h"), + "tstzspan": ("Span", "tstzspan_in", "meos.h"), +} + + +# C arg-type -> (cpp box type, MEOS parser, header). Used for the box-first form +# where the box token is not the function-name suffix. STBox/TBox only: a bare +# `Span*` is ambiguous (tstzspan vs floatspan/numspan) so the box-first path +# skips it — the temporal-first path keeps resolving Span via the name suffix. +_BOXTYPE_PARSER = { + "STBox*": ("STBox", "stbox_in", "meos_geo.h"), + "TBox*": ("TBox", "tbox_in", "meos.h"), +} + + +def temporal_x_box(fn, ret, args): + """int|double|bool fn over a Temporal and an STBox/TBox/Span query LITERAL, + in EITHER argument order (e.g. `left_tspatial_stbox(temp, box)` and + `above_stbox_tspatial(box, temp)`). The literal is parsed at runtime + (stbox_in/tbox_in/tstzspan_in). For the temporal-first form the box type is + the name suffix (distinguishing tstzspan from numspan); for the box-first + form it is the C arg type (STBox/TBox).""" + if len(args) != 2: + return None + rk = {"int": "int", "double": "double", "bool": "bool"}.get(ret) + inp = _infer_input(fn) + if not rk or not inp: + return None + box_first = False + if args[0] == "Temporal*" and args[1] in ("STBox*", "TBox*", "Span*"): + tok = next((t for t in _BOX_PARSER if fn.endswith("_" + t)), None) + if not tok: + return None + bt, parser, hdr = _BOX_PARSER[tok] + elif args[1] == "Temporal*" and args[0] in _BOXTYPE_PARSER: + bt, parser, hdr = _BOXTYPE_PARSER[args[0]] + box_first = True + else: + return None + d = { + "nebula_name": pascal(fn), "sql_token": fn.upper(), "meos_call": fn, + "build_generic": True, "input_type": inp, "return_kind": rk, + "extra_args": [{"kind": "box", "box_type": bt, "parser": parser, "header": hdr}], + "comment_one_liner": f"Per-event {fn}: single-instant {inp} against a {bt} literal -> {rk}.", + } + if box_first: + d["box_first"] = True + return d + + +def stbox_x_stbox(fn, ret, args): + """bool|double fn(const STBox*, const STBox*) — a cross-vehicle comparison over + two per-vehicle extent boxes (each carried as a VARSIZED stbox-text field, e.g. + two TSPATIAL_EXTENT outputs in a self-join). The first box is the primary + stbox_text input; the second is a `box` extra arg; both are stbox_in-parsed and + freed. This is the production-faithful cross-stream shape: per-vehicle + aggregates (GROUP BY vehicle_id) compared pairwise downstream.""" + if len(args) != 2 or args[0] != "STBox*" or args[1] != "STBox*": + return None + rk = {"bool": "bool", "double": "double", "int": "int"}.get(ret) + if not rk: + return None + return { + "nebula_name": pascal(fn), "sql_token": fn.upper(), "meos_call": fn, + "build_generic": True, "input_type": "stbox_text", "return_kind": rk, + "extra_args": [{"kind": "box", "box_type": "STBox", "parser": "stbox_in", "header": "meos_geo.h"}], + "comment_one_liner": f"Per-event {fn}: two per-vehicle extent STBoxes -> {rk}.", + } + + +def two_temporal_scalar(fn, ret, args): + """bool|int|double fn(const Temporal*, const Temporal*) over TWO temporal + operands carried as hex-WKB VARSIZED fields — the cross-vehicle f(trajA, trajB) + scalar shape (e.g. two per-vehicle aggregate outputs compared in a self-join). + The first temporal is the primary wkb_temporal input; the second is a + wkb_temporal extra arg; both temporal_from_hexwkb-parsed and freed.""" + if len(args) != 2 or args[0] != "Temporal*" or args[1] != "Temporal*": + return None + rk = {"bool": "bool", "int": "int", "double": "double"}.get(ret) + if not rk: + return None + # the meos_call's type may live outside meos.h/meos_geo.h + extra_headers = [] + if "tnpoint" in fn: + extra_headers = ["meos_npoint.h"] + elif "tpose" in fn or "trgeometry" in fn: + extra_headers = ["meos_pose.h"] + d = { + "nebula_name": pascal(fn), "sql_token": fn.upper(), "meos_call": fn, + "build_generic": True, "input_type": "wkb_temporal", "return_kind": rk, + "extra_args": [{"kind": "wkb_temporal"}], + "comment_one_liner": f"Per-event {fn}: two hex-WKB temporal operands -> {rk}.", + } + if extra_headers: + d["extra_headers"] = extra_headers + return d + + +def two_temporal_temporal(fn, ret, args): + """Temporal* fn(const Temporal*, const Temporal*) over TWO temporal operands + carried as hex-WKB VARSIZED fields, returning a Temporal* serialized back to + hex-WKB VARSIZED — the cross-vehicle f(trajA, trajB) -> trajectory shape + (temporal distance / arithmetic / topology between two per-vehicle aggregate + outputs in a self-join). Both operands temporal_from_hexwkb-parsed and freed; + the result temporal_as_hexwkb-serialized into the arena and the MEOS result + freed. The output VARSIZED can itself feed another per-event MEOS operator.""" + if len(args) != 2 or args[0] != "Temporal*" or args[1] != "Temporal*": + return None + if ret != "Temporal*": + return None + # the meos_call symbol may live outside meos.h/meos_geo.h + extra_headers = [] + if "tcbuffer" in fn: + extra_headers = ["meos_cbuffer.h"] + elif "tnpoint" in fn: + extra_headers = ["meos_npoint.h"] + elif "tpose" in fn or "trgeometry" in fn: + extra_headers = ["meos_pose.h"] + d = { + "nebula_name": pascal(fn), "sql_token": fn.upper(), "meos_call": fn, + "build_generic": True, "input_type": "wkb_temporal", "return_kind": "wkb", + "extra_args": [{"kind": "wkb_temporal"}], + "comment_one_liner": f"Per-event {fn}: two hex-WKB temporal operands -> hex-WKB temporal.", + } + if extra_headers: + d["extra_headers"] = extra_headers + return d + + +# --- Trgeometry spatial predicate shapes (W148/W149 wave) ------------------- +# MEOS normalizes GSERIALIZED* -> "int*" in the parse_sigs output (the IDL +# carries it as "const int *"; after strip-const + split it becomes "int*"). +# +# Five classifiers, each selecting the correct emit_trgeometry_operator build key: +# +# trgeometry_geo_predicate (Temporal*, int*) int — e/a intersects/disjoint/covers/touches +# geo_trgeometry_predicate (int*, Temporal*) int — geo-first: econtains/acontains/ecovers/acovers +# trgeometry_geo_dwithin (Temporal*, int*, double) int — edwithin/adwithin with distance +# trgeometry_trgeometry_predicate (Temporal*, Temporal*) int — both-trgeometry predicates +# trgeometry_trgeometry_dwithin (Temporal*, Temporal*, double) int — both-trgeometry dwithin +# +# Each covers the "compact" (trgeometryinst_make) subtree; the "nad" subtypes +# (ever_eq/always_eq/nad_) are caught by the same arg-shape but different name +# prefixes. The classifier checks name tokens to pick the right build key. + +_TRGEO_NAME_IS_EQ_NE = frozenset(["ever_eq", "ever_ne", "always_eq", "always_ne"]) + + +def _is_eq_ne(fn): + return any(fn.startswith(p + "_") for p in _TRGEO_NAME_IS_EQ_NE) + + +def _trgeo_brief(fn): + """Derive the short brief string matching the probe's comment_one_liner.""" + verb = fn.split("_")[0] # eintersects / adisjoint / ever / always / nad + if verb == "nad": + if fn.endswith("_geo"): + return ("Returns the nearest approach distance between a 2D trgeometry " + "instant and a static geometry.") + return "Returns the nearest approach distance between two 2D trgeometry instants." + if verb in ("ever", "always"): + # ever_eq / ever_ne / always_eq / always_ne + op2 = fn.split("_")[1] # eq or ne + adj = "equal" if op2 == "eq" else "not equal" + quant = "always" if verb == "always" else "ever" + if "_trgeometry_geo" in fn: + return (f"Returns 1.0 if the 2D trgeometry instant is {quant} {adj} " + f"to the static geometry.") + if "_geo_trgeometry" in fn: + return (f"Returns 1.0 if the static geometry is {quant} {adj} " + f"to the 2D trgeometry instant.") + return f"Returns 1.0 if the two 2D trgeometry instants are {quant} {adj}." + # dwithin variants have a dist arg — use a specific phrasing + if verb in ("edwithin", "adwithin"): + if "_trgeometry_geo" in fn: + return (f"Returns 1 if the trgeometry instant is within dist of the geometry " + f"({verb}).") + return f"Returns 1 if the two trgeometry instants are within dist ({verb})." + # remaining spatial predicates: eintersects / adisjoint / ecovers / … + if "_geo_trgeometry" in fn: + return f"Returns 1 if the geometry {verb} the trgeometry instant." + if "_trgeometry_geo" in fn: + return f"Returns 1 if the trgeometry instant {verb} the geometry." + return f"Returns 1 if the two trgeometry instants {verb}." + + +def trgeometry_geo_predicate(fn, ret, args): + """int fn(Temporal*, int*) — trgeometry_geo spatial predicates and eq/ne. + + Routes to build_trgeometry_geo (compact, trgeometryinst_make) for the + spatial-predicate families (eintersects/adisjoint/ecovers/etouches/…) and + to build_trgeometry_geo_nad (nad, trgeoinst_make) for ever_eq/always_eq/…""" + if ret != "int" or args != ("Temporal*", "int*"): + return None + # Must be a trgeometry function (not a plain tgeo/tpoint that hits "int*" too) + if "trgeometry" not in fn: + return None + build_key = "build_trgeometry_geo_nad" if _is_eq_ne(fn) else "build_trgeometry_geo" + d = { + "nebula_name": "".join(p.capitalize() for p in fn.split("_")), + "sql_token": fn.upper(), + "meos_call": fn, + "return_type": "int", + "nautilus_return": "INT32", + build_key: True, + "comment_one_liner": _trgeo_brief(fn), + } + return d + + +def geo_trgeometry_predicate(fn, ret, args): + """int fn(int*, Temporal*) — geo-first trgeometry predicates and eq/ne. + + Routes to build_geo_trgeometry (compact, trgeometryinst_make) for spatial + predicates (econtains/acontains/ecovers/acovers_geo_trgeometry) and to + build_geo_trgeometry_eq (nad, trgeoinst_make) for ever_eq/always_eq geo-first.""" + if ret != "int" or args != ("int*", "Temporal*"): + return None + if "trgeometry" not in fn: + return None + build_key = "build_geo_trgeometry_eq" if _is_eq_ne(fn) else "build_geo_trgeometry" + d = { + "nebula_name": "".join(p.capitalize() for p in fn.split("_")), + "sql_token": fn.upper(), + "meos_call": fn, + "return_type": "int", + "nautilus_return": "INT32", + build_key: True, + "comment_one_liner": _trgeo_brief(fn), + } + return d + + +def trgeometry_geo_dwithin(fn, ret, args): + """int fn(Temporal*, int*, double) — trgeometry_geo dwithin with distance arg.""" + if ret != "int" or args != ("Temporal*", "int*", "double"): + return None + if "trgeometry" not in fn: + return None + d = { + "nebula_name": "".join(p.capitalize() for p in fn.split("_")), + "sql_token": fn.upper(), + "meos_call": fn, + "return_type": "int", + "nautilus_return": "INT32", + "build_trgeometry_geo_with_dist": True, + "comment_one_liner": _trgeo_brief(fn), + } + return d + + +def trgeometry_trgeometry_predicate(fn, ret, args): + """int fn(Temporal*, Temporal*) — trgeometry-vs-trgeometry predicates. + + Routes to build_trgeometry_trgeometry (compact) for spatial predicates and + to build_trgeometry_trgeometry_nad (nad, trgeoinst_make) for ever_eq/always_eq. + Also covers nad_trgeometry_trgeometry (double) via the double branch below — + this classifier handles only int-returning variants.""" + if ret != "int" or args != ("Temporal*", "Temporal*"): + return None + if "trgeometry" not in fn: + return None + build_key = ("build_trgeometry_trgeometry_nad" if _is_eq_ne(fn) + else "build_trgeometry_trgeometry") + d = { + "nebula_name": "".join(p.capitalize() for p in fn.split("_")), + "sql_token": fn.upper(), + "meos_call": fn, + "return_type": "int", + "nautilus_return": "INT32", + build_key: True, + "comment_one_liner": _trgeo_brief(fn), + } + return d + + +def trgeometry_trgeometry_dwithin(fn, ret, args): + """int fn(Temporal*, Temporal*, double) — trgeometry dwithin with distance.""" + if ret != "int" or args != ("Temporal*", "Temporal*", "double"): + return None + if "trgeometry" not in fn: + return None + d = { + "nebula_name": "".join(p.capitalize() for p in fn.split("_")), + "sql_token": fn.upper(), + "meos_call": fn, + "return_type": "int", + "nautilus_return": "INT32", + "build_trgeometry_trgeometry_with_dist": True, + "comment_one_liner": _trgeo_brief(fn), + } + return d + + +def trgeometry_nad(fn, ret, args): + """double fn(Temporal*, int* | Temporal*) — nad nearest-approach-distance. + + Two sub-shapes: trgeometry_geo (Temporal*, int*) and trgeometry_trgeometry + (Temporal*, Temporal*). Both emit double-returning nad layouts.""" + if ret != "double" or "trgeometry" not in fn or not fn.startswith("nad_"): + return None + if args == ("Temporal*", "int*"): + build_key = "build_nad_trgeometry_geo" + elif args == ("Temporal*", "Temporal*"): + build_key = "build_nad_trgeometry_trgeometry" + else: + return None + return { + "nebula_name": "".join(p.capitalize() for p in fn.split("_")), + "sql_token": fn.upper(), + "meos_call": fn, + "return_type": "double", + "nautilus_return": "FLOAT64", + build_key: True, + "comment_one_liner": _trgeo_brief(fn), + } + + +SHAPES = { + "cmp_scalar_tempfirst": cmp_scalar_tempfirst, + "cmp_scalar_scalarfirst": cmp_scalar_scalarfirst, + "cmp_two_temporal": cmp_two_temporal, + "two_temporal_scalar": two_temporal_scalar, + "two_temporal_temporal": two_temporal_temporal, + "sprel_scalar_existing": sprel_scalar_existing, + "temporal_unary_scalar": temporal_unary_scalar, + "temporal_x_scalar": temporal_x_scalar, + "temporal_x_geom": temporal_x_geom, + "temporal_extract_scalar": temporal_extract_scalar, + "temporal_x_box": temporal_x_box, + "stbox_x_stbox": stbox_x_stbox, + "trgeometry_geo_predicate": trgeometry_geo_predicate, + "geo_trgeometry_predicate": geo_trgeometry_predicate, + "trgeometry_geo_dwithin": trgeometry_geo_dwithin, + "trgeometry_trgeometry_predicate": trgeometry_trgeometry_predicate, + "trgeometry_trgeometry_dwithin": trgeometry_trgeometry_dwithin, + "trgeometry_nad": trgeometry_nad, +} + + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument("--sigs", required=True, help="header signature dump") + ap.add_argument("--gap", required=True, help="streamable-not-wired function list") + ap.add_argument("--shapes", required=True, + help="comma-separated SHAPE names to apply (order = match priority)") + ap.add_argument("--out", required=True, help="descriptor JSON output path") + a = ap.parse_args() + + sigs = parse_sigs(a.sigs) + gap = {ln.strip() for ln in open(a.gap) if ln.strip()} + shapes = [SHAPES[s] for s in a.shapes.split(",")] + + ops, unmatched = [], [] + for fn in sorted(gap): + if fn not in sigs: + continue + ret, args = sigs[fn] + for cls in shapes: + d = cls(fn, ret, args) + if d: + ops.append(d) + break + else: + unmatched.append(fn) + + json.dump({"_comment": f"codegen descriptor; shapes={a.shapes}", "operators": ops}, + open(a.out, "w"), indent=2) + sys.stderr.write(f"emitted {len(ops)} operator descriptor(s) -> {a.out}\n") + sys.stderr.write(f"(gap functions present in sig-dump but unmatched by these shapes: " + f"{len([f for f in unmatched if f in sigs])})\n") + + +if __name__ == "__main__": + main() diff --git a/tools/codegen/build_local.sh b/tools/codegen/build_local.sh new file mode 100755 index 0000000000..a3d78c139a --- /dev/null +++ b/tools/codegen/build_local.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +# +# Local compile-check of the generated MEOS operator + parser libraries in the +# NebulaStream dev image. +# +# The dev image ships without PahoMqttCpp (CI's image has it), so a configure +# that activates the optional MQTT Source/Sink plugins fails. Those plugins are +# irrelevant to the MEOS operator libraries, so this wrapper disables them for +# the local configure and ALWAYS restores nes-plugins/CMakeLists.txt afterwards +# (trap on EXIT) — no manual sed, and the working tree is never left modified +# even if the build fails or is interrupted. +# +# Usage: +# tools/codegen/build_local.sh [target ...] # default: the 3 op/parser libs +# NES_DEV_IMAGE=... BUILD_DIR=... tools/codegen/build_local.sh +# +set -euo pipefail +ROOT="$(git rev-parse --show-toplevel)" +PLUGINS="$ROOT/nes-plugins/CMakeLists.txt" +IMAGE="${NES_DEV_IMAGE:-localhost/nes-development:mobilitynebula-v3}" +BUILD_DIR="${BUILD_DIR:-build-w15}" +TARGETS="${*:-nes-physical-operators nes-logical-operators nes-sql-parser}" + +restore() { git -C "$ROOT" checkout -- "$PLUGINS" 2>/dev/null || true; } +trap restore EXIT + +# Disable the optional MQTT plugins for the local configure (absent in this image). +sed -i 's#"Sources/MQTTSource" ON)#"Sources/MQTTSource" OFF)#; s#"Sinks/MQTTSink" ON)#"Sinks/MQTTSink" OFF)#' "$PLUGINS" + +podman run --rm -v "$ROOT":/workspace -w /workspace "$IMAGE" \ + bash -lc "cmake --build '$BUILD_DIR' --target $TARGETS -j 8 -- -k 0; echo \"### EXIT=\$?\"" diff --git a/tools/codegen/codegen_aggregations.py b/tools/codegen/codegen_aggregations.py new file mode 100644 index 0000000000..e2b1e86bb8 --- /dev/null +++ b/tools/codegen/codegen_aggregations.py @@ -0,0 +1,2745 @@ +#!/usr/bin/env python3 +"""MobilityNebula MEOS-aggregation generator. + +Companion to ``codegen_nebula.py`` (per-event ops). This generator targets +the WINDOWED-aggregation surface: MEOS scalar functions of the shape +`` fn(const Temporal*)`` where the Temporal* is a per-(window, +group) sequence assembled across multiple events. + +For each operator in the JSON descriptor list, emits four C++ files +mirroring mariana's hand-written TemporalLengthAggregation 1:1: + + * nes-logical-operators/include/Operators/Windows/Aggregations/Meos/ + XXXAggregationLogicalFunction.hpp + * nes-logical-operators/src/Operators/Windows/Aggregations/Meos/ + XXXAggregationLogicalFunction.cpp + * nes-physical-operators/include/Aggregation/Function/Meos/ + XXXAggregationPhysicalFunction.hpp + * nes-physical-operators/src/Aggregation/Function/Meos/ + XXXAggregationPhysicalFunction.cpp + +And idempotently injects into 5 in-tree shared files: + + * nes-sql-parser/AntlrSQL.g4 + - lexer-token entries + - functionName: alternation list + * nes-sql-parser/src/AntlrSQLQueryPlanCreator.cpp + - case AntlrSQLLexer::TOKEN: dispatch in the dedicated-token switch + - else if (funcName == "TOKEN") dispatch in the IDENTIFIER fallback chain + * nes-query-optimizer/src/RewriteRules/LowerToPhysical/ + LowerToPhysicalWindowedAggregation.cpp + - if (name == "Xxx") { ... } block lowering logical → physical + * nes-{logical,physical}-operators/.../{Aggregation*}/CMakeLists.txt + - add_plugin(...) per layer + +All injections are bracketed with +``/* BEGIN CODEGEN AGGREGATION GLUE: TOKEN */ ... /* END ... */`` markers +so re-runs are no-ops and pre-existing hand-written cases (mariana's) are +detected by raw token match and skipped. + +Two lift-shape branches, picked by descriptor ``input_shape``: + * ``tgeo`` — 3 fields per event (lon, lat, ts); lower builds + ``{Point(lon lat)@ts, ...}`` trajectory string parsed via + ``MEOS::Meos::parseTemporalPoint``. + * ``tnumber``— 2 fields per event (value, ts); lower builds + ``{value@ts, ...}`` string parsed via ``tfloat_in`` or + ``tint_in`` per descriptor. + +Usage: + python3 codegen_aggregations.py --input \\ + --output-root /path/to/MobilityNebula \\ + [--no-parser-glue] [--no-cmake-entries] [--no-optimizer-glue] +""" +import argparse +import json +import re +import sys +from pathlib import Path + +# =========================================================================== +# Logical-layer .hpp template (mirrors TemporalLengthAggregationLogicalFunction.hpp). +# =========================================================================== +LOGICAL_HPP_TGEO = """\ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#pragma once + +#include + +namespace NES +{{ + +/** + * @brief {comment_one_liner} + * + * Three-input (lon, lat, ts) tgeo aggregation. Lift accumulates the events + * into a paged vector; lower assembles the per-(window, group) trajectory + * and calls MEOS `{meos_scalar_fn}` to fold it to a single scalar. + */ +class {nebula_name}AggregationLogicalFunction : public WindowAggregationLogicalFunction +{{ +public: + static std::shared_ptr + create(const FieldAccessLogicalFunction& lonField, const FieldAccessLogicalFunction& latField, const FieldAccessLogicalFunction& timestampField); + + {nebula_name}AggregationLogicalFunction( + const FieldAccessLogicalFunction& lonField, + const FieldAccessLogicalFunction& latField, + const FieldAccessLogicalFunction& timestampField, + const FieldAccessLogicalFunction& asField); + + void inferStamp(const Schema& schema) override; + ~{nebula_name}AggregationLogicalFunction() override = default; + [[nodiscard]] NES::SerializableAggregationFunction serialize() const override; + [[nodiscard]] std::string_view getName() const noexcept override; + [[nodiscard]] bool requiresSequentialAggregation() const {{ return true; }} + + [[nodiscard]] const FieldAccessLogicalFunction& getLonField() const noexcept {{ return lonField; }} + [[nodiscard]] const FieldAccessLogicalFunction& getLatField() const noexcept {{ return latField; }} + [[nodiscard]] const FieldAccessLogicalFunction& getTimestampField() const noexcept {{ return timestampField; }} + +private: + static constexpr std::string_view NAME = "{class_name_token}"; + static constexpr DataType::Type partialAggregateStampType = DataType::Type::UNDEFINED; + static constexpr DataType::Type finalAggregateStampType = DataType::Type::{final_stamp_type}; + + FieldAccessLogicalFunction lonField; + FieldAccessLogicalFunction latField; + FieldAccessLogicalFunction timestampField; +}}; +}} +""" + +LOGICAL_HPP_TNUMBER = """\ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#pragma once + +#include + +namespace NES +{{ + +/** + * @brief {comment_one_liner} + * + * Two-input (value, ts) tnumber aggregation. Lift accumulates the events + * into a paged vector; lower assembles the per-(window, group) tnumber + * sequence and calls MEOS `{meos_scalar_fn}` to fold it to a single scalar. + */ +class {nebula_name}AggregationLogicalFunction : public WindowAggregationLogicalFunction +{{ +public: + static std::shared_ptr + create(const FieldAccessLogicalFunction& valueField, const FieldAccessLogicalFunction& timestampField); + + {nebula_name}AggregationLogicalFunction( + const FieldAccessLogicalFunction& valueField, + const FieldAccessLogicalFunction& timestampField, + const FieldAccessLogicalFunction& asField); + + void inferStamp(const Schema& schema) override; + ~{nebula_name}AggregationLogicalFunction() override = default; + [[nodiscard]] NES::SerializableAggregationFunction serialize() const override; + [[nodiscard]] std::string_view getName() const noexcept override; + [[nodiscard]] bool requiresSequentialAggregation() const {{ return true; }} + + [[nodiscard]] const FieldAccessLogicalFunction& getValueField() const noexcept {{ return valueField; }} + [[nodiscard]] const FieldAccessLogicalFunction& getTimestampField() const noexcept {{ return timestampField; }} + +private: + static constexpr std::string_view NAME = "{class_name_token}"; + static constexpr DataType::Type partialAggregateStampType = DataType::Type::UNDEFINED; + static constexpr DataType::Type finalAggregateStampType = DataType::Type::{final_stamp_type}; + + FieldAccessLogicalFunction valueField; + FieldAccessLogicalFunction timestampField; +}}; +}} +""" + +# Logical .cpp templates — share scaffold (ctor, inferStamp, serialize, registry) +# but differ in field count (3 for tgeo, 2 for tnumber). +LOGICAL_CPP_TGEO = """\ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace NES +{{ + +{nebula_name}AggregationLogicalFunction::{nebula_name}AggregationLogicalFunction( + const FieldAccessLogicalFunction& lonField, + const FieldAccessLogicalFunction& latField, + const FieldAccessLogicalFunction& timestampField, + const FieldAccessLogicalFunction& asField) + : WindowAggregationLogicalFunction( + lonField.getDataType(), + DataTypeProvider::provideDataType(partialAggregateStampType), + DataTypeProvider::provideDataType(finalAggregateStampType), + lonField, + asField) + , lonField(lonField) + , latField(latField) + , timestampField(timestampField) +{{ +}} + +std::shared_ptr +{nebula_name}AggregationLogicalFunction::create( + const FieldAccessLogicalFunction& lonField, + const FieldAccessLogicalFunction& latField, + const FieldAccessLogicalFunction& timestampField) +{{ + return std::make_shared<{nebula_name}AggregationLogicalFunction>(lonField, latField, timestampField, lonField); +}} + +std::string_view {nebula_name}AggregationLogicalFunction::getName() const noexcept +{{ + return NAME; +}} + +void {nebula_name}AggregationLogicalFunction::inferStamp(const Schema& schema) +{{ + lonField = lonField.withInferredDataType(schema).get(); + latField = latField.withInferredDataType(schema).get(); + timestampField = timestampField.withInferredDataType(schema).get(); + + onField = lonField; + + if (!lonField.getDataType().isNumeric() || !latField.getDataType().isNumeric() || !timestampField.getDataType().isNumeric()) + {{ + throw CannotInferSchema("{nebula_name}AggregationLogicalFunction: lon, lat, and timestamp fields must be numeric."); + }} + + const auto onFieldName = onField.getFieldName(); + const auto asFieldName = asField.getFieldName(); + const auto attributeNameResolver = onFieldName.substr(0, onFieldName.find(Schema::ATTRIBUTE_NAME_SEPARATOR) + 1); + if (asFieldName.find(Schema::ATTRIBUTE_NAME_SEPARATOR) == std::string::npos) + {{ + asField = asField.withFieldName(attributeNameResolver + asFieldName).get(); + }} + else + {{ + const auto fieldName = asFieldName.substr(asFieldName.find_last_of(Schema::ATTRIBUTE_NAME_SEPARATOR) + 1); + asField = asField.withFieldName(attributeNameResolver + fieldName).get(); + }} + asField = asField.withDataType(getFinalAggregateStamp()).get(); + inputStamp = onField.getDataType(); +}} + +NES::SerializableAggregationFunction {nebula_name}AggregationLogicalFunction::serialize() const +{{ + auto saf = TemporalAggregationSerde::serializeTemporalSequence(lonField, latField, timestampField, asField); + saf.set_type(std::string(NAME)); + return saf; +}} + +AggregationLogicalFunctionRegistryReturnType AggregationLogicalFunctionGeneratedRegistrar::Register{nebula_name}AggregationLogicalFunction( + AggregationLogicalFunctionRegistryArguments arguments) +{{ + if (arguments.fields.size() == 4) + {{ + auto ptr = std::make_shared<{nebula_name}AggregationLogicalFunction>( + arguments.fields[0], arguments.fields[1], arguments.fields[2], arguments.fields[3]); + return ptr; + }} + throw CannotDeserialize( + "{nebula_name}AggregationLogicalFunction requires lon, lat, timestamp, and alias fields but got {{}}", + arguments.fields.size()); +}} + +}} // namespace NES +""" + +LOGICAL_CPP_TNUMBER = """\ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace NES +{{ + +{nebula_name}AggregationLogicalFunction::{nebula_name}AggregationLogicalFunction( + const FieldAccessLogicalFunction& valueField, + const FieldAccessLogicalFunction& timestampField, + const FieldAccessLogicalFunction& asField) + : WindowAggregationLogicalFunction( + valueField.getDataType(), + DataTypeProvider::provideDataType(partialAggregateStampType), + DataTypeProvider::provideDataType(finalAggregateStampType), + valueField, + asField) + , valueField(valueField) + , timestampField(timestampField) +{{ +}} + +std::shared_ptr +{nebula_name}AggregationLogicalFunction::create( + const FieldAccessLogicalFunction& valueField, + const FieldAccessLogicalFunction& timestampField) +{{ + return std::make_shared<{nebula_name}AggregationLogicalFunction>(valueField, timestampField, valueField); +}} + +std::string_view {nebula_name}AggregationLogicalFunction::getName() const noexcept +{{ + return NAME; +}} + +void {nebula_name}AggregationLogicalFunction::inferStamp(const Schema& schema) +{{ + valueField = valueField.withInferredDataType(schema).get(); + timestampField = timestampField.withInferredDataType(schema).get(); + + onField = valueField; + + if (!valueField.getDataType().isNumeric() || !timestampField.getDataType().isNumeric()) + {{ + throw CannotInferSchema("{nebula_name}AggregationLogicalFunction: value and timestamp fields must be numeric."); + }} + + const auto onFieldName = onField.getFieldName(); + const auto asFieldName = asField.getFieldName(); + const auto attributeNameResolver = onFieldName.substr(0, onFieldName.find(Schema::ATTRIBUTE_NAME_SEPARATOR) + 1); + if (asFieldName.find(Schema::ATTRIBUTE_NAME_SEPARATOR) == std::string::npos) + {{ + asField = asField.withFieldName(attributeNameResolver + asFieldName).get(); + }} + else + {{ + const auto fieldName = asFieldName.substr(asFieldName.find_last_of(Schema::ATTRIBUTE_NAME_SEPARATOR) + 1); + asField = asField.withFieldName(attributeNameResolver + fieldName).get(); + }} + asField = asField.withDataType(getFinalAggregateStamp()).get(); + inputStamp = onField.getDataType(); +}} + +NES::SerializableAggregationFunction {nebula_name}AggregationLogicalFunction::serialize() const +{{ + auto saf = TemporalAggregationSerde::serializeTemporalSequence(valueField, timestampField, valueField, asField); + saf.set_type(std::string(NAME)); + return saf; +}} + +AggregationLogicalFunctionRegistryReturnType AggregationLogicalFunctionGeneratedRegistrar::Register{nebula_name}AggregationLogicalFunction( + AggregationLogicalFunctionRegistryArguments arguments) +{{ + // serializeTemporalSequence only has a 4-field (lon, lat, ts, as) form, so + // the two-field (value, ts) shape packs the value field twice; fields[2] is + // that duplicate and is ignored here — the alias is fields[3]. + if (arguments.fields.size() == 4) + {{ + auto ptr = std::make_shared<{nebula_name}AggregationLogicalFunction>( + arguments.fields[0], arguments.fields[1], arguments.fields[3]); + return ptr; + }} + throw CannotDeserialize( + "{nebula_name}AggregationLogicalFunction requires value, timestamp, and alias fields but got {{}}", + arguments.fields.size()); +}} + +}} // namespace NES +""" + +# Physical-layer .hpp templates. +PHYSICAL_HPP_TGEO = """\ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace NES +{{ + +class {nebula_name}AggregationPhysicalFunction : public AggregationPhysicalFunction +{{ +public: + {nebula_name}AggregationPhysicalFunction( + DataType inputType, + DataType resultType, + PhysicalFunction lonFunctionParam, + PhysicalFunction latFunctionParam, + PhysicalFunction timestampFunctionParam, + Nautilus::Record::RecordFieldIdentifier resultFieldIdentifier, + std::shared_ptr bufferRef); + void lift( + const nautilus::val& aggregationState, + PipelineMemoryProvider& pipelineMemoryProvider, + const Nautilus::Record& record) + override; + void combine( + nautilus::val aggregationState1, + nautilus::val aggregationState2, + PipelineMemoryProvider& pipelineMemoryProvider) override; + Nautilus::Record lower(nautilus::val aggregationState, [[maybe_unused]] PipelineMemoryProvider& pipelineMemoryProvider) override; + void reset(nautilus::val aggregationState, PipelineMemoryProvider& pipelineMemoryProvider) override; + [[nodiscard]] size_t getSizeOfStateInBytes() const override; + ~{nebula_name}AggregationPhysicalFunction() override = default; + void cleanup(nautilus::val aggregationState) override; + +private: + std::shared_ptr bufferRef; + PhysicalFunction lonFunction; + PhysicalFunction latFunction; + PhysicalFunction timestampFunction; +}}; + +}} +""" + +PHYSICAL_HPP_TNUMBER = """\ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace NES +{{ + +class {nebula_name}AggregationPhysicalFunction : public AggregationPhysicalFunction +{{ +public: + {nebula_name}AggregationPhysicalFunction( + DataType inputType, + DataType resultType, + PhysicalFunction valueFunctionParam, + PhysicalFunction timestampFunctionParam, + Nautilus::Record::RecordFieldIdentifier resultFieldIdentifier, + std::shared_ptr bufferRef); + void lift( + const nautilus::val& aggregationState, + PipelineMemoryProvider& pipelineMemoryProvider, + const Nautilus::Record& record) + override; + void combine( + nautilus::val aggregationState1, + nautilus::val aggregationState2, + PipelineMemoryProvider& pipelineMemoryProvider) override; + Nautilus::Record lower(nautilus::val aggregationState, [[maybe_unused]] PipelineMemoryProvider& pipelineMemoryProvider) override; + void reset(nautilus::val aggregationState, PipelineMemoryProvider& pipelineMemoryProvider) override; + [[nodiscard]] size_t getSizeOfStateInBytes() const override; + ~{nebula_name}AggregationPhysicalFunction() override = default; + void cleanup(nautilus::val aggregationState) override; + +private: + std::shared_ptr bufferRef; + PhysicalFunction valueFunction; + PhysicalFunction timestampFunction; +}}; + +}} +""" + +# Physical .cpp templates — the core logic. lift/combine/reset/cleanup are identical +# scaffold; lower() is the per-op differential (builds trajectory string + MEOS call). +PHYSICAL_CPP_TGEO = """\ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +extern "C" {{ +#include +#include +}} + +namespace NES +{{ + +constexpr static std::string_view LonFieldName = "lon"; +constexpr static std::string_view LatFieldName = "lat"; +constexpr static std::string_view TimestampFieldName = "timestamp"; + +static std::mutex {mutex_name}; + + +{nebula_name}AggregationPhysicalFunction::{nebula_name}AggregationPhysicalFunction( + DataType inputType, + DataType resultType, + PhysicalFunction lonFunctionParam, + PhysicalFunction latFunctionParam, + PhysicalFunction timestampFunctionParam, + Nautilus::Record::RecordFieldIdentifier resultFieldIdentifier, + std::shared_ptr bufferRef) + : AggregationPhysicalFunction(std::move(inputType), std::move(resultType), lonFunctionParam, std::move(resultFieldIdentifier)) + , bufferRef(std::move(bufferRef)) + , lonFunction(std::move(lonFunctionParam)) + , latFunction(std::move(latFunctionParam)) + , timestampFunction(std::move(timestampFunctionParam)) +{{ +}} + +void {nebula_name}AggregationPhysicalFunction::lift( + const nautilus::val& aggregationState, PipelineMemoryProvider& pipelineMemoryProvider, const Nautilus::Record& record) +{{ + const auto pagedVectorPtr = static_cast>(aggregationState); + + auto lonValue = lonFunction.execute(record, pipelineMemoryProvider.arena); + auto latValue = latFunction.execute(record, pipelineMemoryProvider.arena); + auto timestampValue = timestampFunction.execute(record, pipelineMemoryProvider.arena); + + Record aggregateStateRecord({{ + {{std::string(LonFieldName), lonValue}}, + {{std::string(LatFieldName), latValue}}, + {{std::string(TimestampFieldName), timestampValue}} + }}); + + const Nautilus::Interface::PagedVectorRef pagedVectorRef(pagedVectorPtr, bufferRef); + pagedVectorRef.writeRecord(aggregateStateRecord, pipelineMemoryProvider.bufferProvider); +}} + +void {nebula_name}AggregationPhysicalFunction::combine( + const nautilus::val aggregationState1, + const nautilus::val aggregationState2, + PipelineMemoryProvider&) +{{ + const auto memArea1 = static_cast>(aggregationState1); + const auto memArea2 = static_cast>(aggregationState2); + + nautilus::invoke( + +[](Nautilus::Interface::PagedVector* vector1, const Nautilus::Interface::PagedVector* vector2) -> void + {{ vector1->copyFrom(*vector2); }}, + memArea1, + memArea2); +}} + +Nautilus::Record {nebula_name}AggregationPhysicalFunction::lower( + const nautilus::val aggregationState, [[maybe_unused]] PipelineMemoryProvider& pipelineMemoryProvider) +{{ + MEOS::Meos::ensureMeosInitialized(); + + const auto pagedVectorPtr = static_cast>(aggregationState); + const Nautilus::Interface::PagedVectorRef pagedVectorRef(pagedVectorPtr, bufferRef); + const auto allFieldNames = bufferRef->getMemoryLayout()->getSchema().getFieldNames(); + const auto numberOfEntries = invoke( + +[](const Nautilus::Interface::PagedVector* pagedVector) + {{ + return pagedVector->getTotalNumberOfEntries(); + }}, + pagedVectorPtr); + + if (numberOfEntries == nautilus::val(0)) {{ + Nautilus::Record resultRecord; + resultRecord.write(resultFieldIdentifier, nautilus::val<{return_cpp_type}>(0)); + return resultRecord; + }} + + auto trajectoryStr = nautilus::invoke( + +[](const Nautilus::Interface::PagedVector* pagedVector) -> char* + {{ + size_t bufferSize = pagedVector->getTotalNumberOfEntries() * 150 + 50; + char* buffer = (char*)malloc(bufferSize); + memset(buffer, 0, bufferSize); + strcpy(buffer, "{{"); + return buffer; + }}, + pagedVectorPtr); + + auto pointCounter = nautilus::val(0); + + const auto endIt = pagedVectorRef.end(allFieldNames); + for (auto candidateIt = pagedVectorRef.begin(allFieldNames); candidateIt != endIt; ++candidateIt) + {{ + const auto itemRecord = *candidateIt; + + const auto lonValue = itemRecord.read(std::string(LonFieldName)); + const auto latValue = itemRecord.read(std::string(LatFieldName)); + const auto timestampValue = itemRecord.read(std::string(TimestampFieldName)); + + auto lon = lonValue.cast>(); + auto lat = latValue.cast>(); + auto timestamp = timestampValue.cast>(); + + trajectoryStr = nautilus::invoke( + +[](char* buffer, double lonVal, double latVal, int64_t tsVal, int64_t counter) -> char* + {{ + if (counter > 0) {{ + strcat(buffer, ", "); + }} + + long long adjustedTime; + if (tsVal > 1000000000000LL) {{ + adjustedTime = tsVal / 1000; + }} else {{ + adjustedTime = tsVal; + }} + + std::string timestampString = MEOS::Meos::convertSecondsToTimestamp(adjustedTime); + const char* timestampStr = timestampString.c_str(); + + char pointStr[120]; + sprintf(pointStr, "Point(%.6f %.6f)@%s", lonVal, latVal, timestampStr); + strcat(buffer, pointStr); + return buffer; + }}, + trajectoryStr, + lon, + lat, + timestamp, + pointCounter); + + pointCounter = pointCounter + nautilus::val(1); + }} + + trajectoryStr = nautilus::invoke( + +[](char* buffer) -> char* + {{ + strcat(buffer, "}}"); + return buffer; + }}, + trajectoryStr); + + auto resultValue = nautilus::invoke( + +[](const char* trajStr) -> {return_cpp_type} + {{ + if (!trajStr || strlen(trajStr) == 0) {{ + free((void*)trajStr); + return ({return_cpp_type})0; + }} + + MEOS::Meos::ensureMeosInitialized(); + std::lock_guard lock({mutex_name}); + + std::string trajString(trajStr); + void* temp = MEOS::Meos::parseTemporalPoint(trajString); + if (!temp) {{ + free((void*)trajStr); + return ({return_cpp_type})0; + }} + + {value_compute} + + MEOS::Meos::freeTemporalObject(temp); + free((void*)trajStr); + return value; + }}, + trajectoryStr); + + Nautilus::Record resultRecord; + resultRecord.write(resultFieldIdentifier, resultValue); + return resultRecord; +}} + +void {nebula_name}AggregationPhysicalFunction::reset(const nautilus::val aggregationState, PipelineMemoryProvider&) +{{ + nautilus::invoke( + +[](AggregationState* pagedVectorMemArea) -> void + {{ + auto* pagedVector = reinterpret_cast(pagedVectorMemArea); + new (pagedVector) Nautilus::Interface::PagedVector(); + }}, + aggregationState); +}} + +size_t {nebula_name}AggregationPhysicalFunction::getSizeOfStateInBytes() const +{{ + return sizeof(Nautilus::Interface::PagedVector); +}} + +void {nebula_name}AggregationPhysicalFunction::cleanup(nautilus::val aggregationState) +{{ + nautilus::invoke( + +[](AggregationState* pagedVectorMemArea) -> void + {{ + auto* pagedVector = reinterpret_cast(pagedVectorMemArea); + pagedVector->~PagedVector(); + }}, + aggregationState); +}} + + +AggregationPhysicalFunctionRegistryReturnType AggregationPhysicalFunctionGeneratedRegistrar::Register{nebula_name}AggregationPhysicalFunction( + AggregationPhysicalFunctionRegistryArguments) +{{ + throw std::runtime_error("{class_name_token} aggregation cannot be created through the registry. " + "It requires three field functions (longitude, latitude, timestamp)"); +}} + +}} // namespace NES +""" + +PHYSICAL_CPP_TNUMBER = """\ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +extern "C" {{ +#include +}} + +namespace NES +{{ + +constexpr static std::string_view ValueFieldName = "value"; +constexpr static std::string_view TimestampFieldName = "timestamp"; + +static std::mutex {mutex_name}; + + +{nebula_name}AggregationPhysicalFunction::{nebula_name}AggregationPhysicalFunction( + DataType inputType, + DataType resultType, + PhysicalFunction valueFunctionParam, + PhysicalFunction timestampFunctionParam, + Nautilus::Record::RecordFieldIdentifier resultFieldIdentifier, + std::shared_ptr bufferRef) + : AggregationPhysicalFunction(std::move(inputType), std::move(resultType), valueFunctionParam, std::move(resultFieldIdentifier)) + , bufferRef(std::move(bufferRef)) + , valueFunction(std::move(valueFunctionParam)) + , timestampFunction(std::move(timestampFunctionParam)) +{{ +}} + +void {nebula_name}AggregationPhysicalFunction::lift( + const nautilus::val& aggregationState, PipelineMemoryProvider& pipelineMemoryProvider, const Nautilus::Record& record) +{{ + const auto pagedVectorPtr = static_cast>(aggregationState); + + auto valueValue = valueFunction.execute(record, pipelineMemoryProvider.arena); + auto timestampValue = timestampFunction.execute(record, pipelineMemoryProvider.arena); + + Record aggregateStateRecord({{ + {{std::string(ValueFieldName), valueValue}}, + {{std::string(TimestampFieldName), timestampValue}} + }}); + + const Nautilus::Interface::PagedVectorRef pagedVectorRef(pagedVectorPtr, bufferRef); + pagedVectorRef.writeRecord(aggregateStateRecord, pipelineMemoryProvider.bufferProvider); +}} + +void {nebula_name}AggregationPhysicalFunction::combine( + const nautilus::val aggregationState1, + const nautilus::val aggregationState2, + PipelineMemoryProvider&) +{{ + const auto memArea1 = static_cast>(aggregationState1); + const auto memArea2 = static_cast>(aggregationState2); + + nautilus::invoke( + +[](Nautilus::Interface::PagedVector* vector1, const Nautilus::Interface::PagedVector* vector2) -> void + {{ vector1->copyFrom(*vector2); }}, + memArea1, + memArea2); +}} + +Nautilus::Record {nebula_name}AggregationPhysicalFunction::lower( + const nautilus::val aggregationState, [[maybe_unused]] PipelineMemoryProvider& pipelineMemoryProvider) +{{ + MEOS::Meos::ensureMeosInitialized(); + + const auto pagedVectorPtr = static_cast>(aggregationState); + const Nautilus::Interface::PagedVectorRef pagedVectorRef(pagedVectorPtr, bufferRef); + const auto allFieldNames = bufferRef->getMemoryLayout()->getSchema().getFieldNames(); + const auto numberOfEntries = invoke( + +[](const Nautilus::Interface::PagedVector* pagedVector) + {{ + return pagedVector->getTotalNumberOfEntries(); + }}, + pagedVectorPtr); + + if (numberOfEntries == nautilus::val(0)) {{ + Nautilus::Record resultRecord; + resultRecord.write(resultFieldIdentifier, nautilus::val<{return_cpp_type}>(0)); + return resultRecord; + }} + + auto sequenceStr = nautilus::invoke( + +[](const Nautilus::Interface::PagedVector* pagedVector) -> char* + {{ + size_t bufferSize = pagedVector->getTotalNumberOfEntries() * 80 + 50; + char* buffer = (char*)malloc(bufferSize); + memset(buffer, 0, bufferSize); + strcpy(buffer, "{{"); + return buffer; + }}, + pagedVectorPtr); + + auto pointCounter = nautilus::val(0); + + const auto endIt = pagedVectorRef.end(allFieldNames); + for (auto candidateIt = pagedVectorRef.begin(allFieldNames); candidateIt != endIt; ++candidateIt) + {{ + const auto itemRecord = *candidateIt; + + const auto valueRaw = itemRecord.read(std::string(ValueFieldName)); + const auto timestampRaw = itemRecord.read(std::string(TimestampFieldName)); + + auto value = valueRaw.cast>(); + auto timestamp = timestampRaw.cast>(); + + sequenceStr = nautilus::invoke( + +[](char* buffer, {lift_value_cpp_type} valueVal, int64_t tsVal, int64_t counter) -> char* + {{ + if (counter > 0) {{ + strcat(buffer, ", "); + }} + + long long adjustedTime; + if (tsVal > 1000000000000LL) {{ + adjustedTime = tsVal / 1000; + }} else {{ + adjustedTime = tsVal; + }} + + std::string timestampString = MEOS::Meos::convertSecondsToTimestamp(adjustedTime); + const char* timestampStr = timestampString.c_str(); + + char itemStr[80]; + sprintf(itemStr, "{value_printf_fmt}@%s", valueVal, timestampStr); + strcat(buffer, itemStr); + return buffer; + }}, + sequenceStr, + value, + timestamp, + pointCounter); + + pointCounter = pointCounter + nautilus::val(1); + }} + + sequenceStr = nautilus::invoke( + +[](char* buffer) -> char* + {{ + strcat(buffer, "}}"); + return buffer; + }}, + sequenceStr); + + auto resultValue = nautilus::invoke( + +[](const char* seqStr) -> {return_cpp_type} + {{ + if (!seqStr || strlen(seqStr) == 0) {{ + free((void*)seqStr); + return ({return_cpp_type})0; + }} + + MEOS::Meos::ensureMeosInitialized(); + std::lock_guard lock({mutex_name}); + + Temporal* temp = {tnumber_in_fn}(seqStr); + if (!temp) {{ + free((void*)seqStr); + return ({return_cpp_type})0; + }} + + {return_cpp_type} value = {meos_scalar_fn}(temp); + + free(temp); + free((void*)seqStr); + return value; + }}, + sequenceStr); + + Nautilus::Record resultRecord; + resultRecord.write(resultFieldIdentifier, resultValue); + return resultRecord; +}} + +void {nebula_name}AggregationPhysicalFunction::reset(const nautilus::val aggregationState, PipelineMemoryProvider&) +{{ + nautilus::invoke( + +[](AggregationState* pagedVectorMemArea) -> void + {{ + auto* pagedVector = reinterpret_cast(pagedVectorMemArea); + new (pagedVector) Nautilus::Interface::PagedVector(); + }}, + aggregationState); +}} + +size_t {nebula_name}AggregationPhysicalFunction::getSizeOfStateInBytes() const +{{ + return sizeof(Nautilus::Interface::PagedVector); +}} + +void {nebula_name}AggregationPhysicalFunction::cleanup(nautilus::val aggregationState) +{{ + nautilus::invoke( + +[](AggregationState* pagedVectorMemArea) -> void + {{ + auto* pagedVector = reinterpret_cast(pagedVectorMemArea); + pagedVector->~PagedVector(); + }}, + aggregationState); +}} + + +AggregationPhysicalFunctionRegistryReturnType AggregationPhysicalFunctionGeneratedRegistrar::Register{nebula_name}AggregationPhysicalFunction( + AggregationPhysicalFunctionRegistryArguments) +{{ + throw std::runtime_error("{class_name_token} aggregation cannot be created through the registry. " + "It requires two field functions (value, timestamp)"); +}} + +}} // namespace NES +""" + +# =========================================================================== +# Box-output (VARSIZED) physical .cpp templates. +# +# The 11 MEOS `*_extent_transfn` aggregates do not fold a window to a scalar — +# they fold it to a *box* (a Span / TBox / STBox). NebulaStream emits such a +# windowed value through the same variable-sized-data path that +# TemporalSequenceAggregationPhysicalFunction already uses: in lower() we +# serialize the box to text (`*_out`) and write it as VARSIZED. +# +# To stay byte-identical to the proven scalar templates above (lift / combine / +# reset / cleanup / the trajectory-assembly loop are unchanged), the box +# templates are DERIVED from the scalar templates by swapping exactly two +# well-delimited regions: the empty-window early-return and the finalize tail. +# The swap is asserted (count == 1) so any drift in the scalar template fails +# loudly at import time rather than emitting silently-wrong C++. +# =========================================================================== + +# Empty-window early-return — identical in the TGEO and TNUMBER scalar templates. +_EMPTY_SCALAR = """\ + if (numberOfEntries == nautilus::val(0)) {{ + Nautilus::Record resultRecord; + resultRecord.write(resultFieldIdentifier, nautilus::val<{return_cpp_type}>(0)); + return resultRecord; + }}""" + +_EMPTY_BOX = """\ + if (numberOfEntries == nautilus::val(0)) {{ + auto emptyVarSized = pipelineMemoryProvider.arena.allocateVariableSizedData(0); + Nautilus::Record resultRecord; + resultRecord.write(resultFieldIdentifier, emptyVarSized); + return resultRecord; + }}""" + +# Finalize tail — TGEO scalar (parseTemporalPoint / trajectoryStr / freeTemporalObject). +_FINALIZE_SCALAR_TGEO = """\ + auto resultValue = nautilus::invoke( + +[](const char* trajStr) -> {return_cpp_type} + {{ + if (!trajStr || strlen(trajStr) == 0) {{ + free((void*)trajStr); + return ({return_cpp_type})0; + }} + + MEOS::Meos::ensureMeosInitialized(); + std::lock_guard lock({mutex_name}); + + std::string trajString(trajStr); + void* temp = MEOS::Meos::parseTemporalPoint(trajString); + if (!temp) {{ + free((void*)trajStr); + return ({return_cpp_type})0; + }} + + {value_compute} + + MEOS::Meos::freeTemporalObject(temp); + free((void*)trajStr); + return value; + }}, + trajectoryStr); + + Nautilus::Record resultRecord; + resultRecord.write(resultFieldIdentifier, resultValue); + return resultRecord;""" + +# Finalize tail — TGEO box: fold the windowed trajectory's extent box and emit +# its serialized text as VARSIZED. With a NULL initial state the MEOS extent +# transition fn returns the bbox of the whole-window temporal (e.g. +# tspatial_extent_transfn(NULL, traj) == tspatial_to_stbox(traj)). +_FINALIZE_BOX_TGEO = """\ + auto boxStr = nautilus::invoke( + +[](const char* trajStr) -> char* + {{ + if (!trajStr || strlen(trajStr) == 0) {{ + free((void*)trajStr); + return (char*)nullptr; + }} + + MEOS::Meos::ensureMeosInitialized(); + std::lock_guard lock({mutex_name}); + + std::string trajString(trajStr); + void* temp = MEOS::Meos::parseTemporalPoint(trajString); + free((void*)trajStr); + if (!temp) {{ + return (char*)nullptr; + }} + + {extent_box_type}* aggBox = {extent_transfn}(nullptr, static_cast(temp)); + MEOS::Meos::freeTemporalObject(temp); + if (!aggBox) {{ + return (char*)nullptr; + }} + + char* boxText = {box_out_fn}(aggBox, 15); + free(aggBox); + return boxText; + }}, + trajectoryStr); + + const auto boxStrLen = nautilus::invoke( + +[](const char* s) -> size_t {{ return s ? strlen(s) : (size_t) 0; }}, + boxStr); + + auto variableSized = pipelineMemoryProvider.arena.allocateVariableSizedData(boxStrLen); + + nautilus::invoke( + +[](int8_t* dest, const char* s, size_t len) -> void + {{ + if (s) {{ + memcpy(dest, s, len); + free((void*)s); + }} + }}, + variableSized.getContent(), + boxStr, + boxStrLen); + + Nautilus::Record resultRecord; + resultRecord.write(resultFieldIdentifier, variableSized); + return resultRecord;""" + +# Finalize tail — TNUMBER scalar ({tnumber_in_fn} / sequenceStr / free(temp)). +_FINALIZE_SCALAR_TNUMBER = """\ + auto resultValue = nautilus::invoke( + +[](const char* seqStr) -> {return_cpp_type} + {{ + if (!seqStr || strlen(seqStr) == 0) {{ + free((void*)seqStr); + return ({return_cpp_type})0; + }} + + MEOS::Meos::ensureMeosInitialized(); + std::lock_guard lock({mutex_name}); + + Temporal* temp = {tnumber_in_fn}(seqStr); + if (!temp) {{ + free((void*)seqStr); + return ({return_cpp_type})0; + }} + + {return_cpp_type} value = {meos_scalar_fn}(temp); + + free(temp); + free((void*)seqStr); + return value; + }}, + sequenceStr); + + Nautilus::Record resultRecord; + resultRecord.write(resultFieldIdentifier, resultValue); + return resultRecord;""" + +# Finalize tail — TNUMBER box. +_FINALIZE_BOX_TNUMBER = """\ + auto boxStr = nautilus::invoke( + +[](const char* seqStr) -> char* + {{ + if (!seqStr || strlen(seqStr) == 0) {{ + free((void*)seqStr); + return (char*)nullptr; + }} + + MEOS::Meos::ensureMeosInitialized(); + std::lock_guard lock({mutex_name}); + + Temporal* temp = {tnumber_in_fn}(seqStr); + free((void*)seqStr); + if (!temp) {{ + return (char*)nullptr; + }} + + {extent_box_type}* aggBox = {extent_transfn}(nullptr, temp); + free(temp); + if (!aggBox) {{ + return (char*)nullptr; + }} + + char* boxText = {box_out_fn}(aggBox, 15); + free(aggBox); + return boxText; + }}, + sequenceStr); + + const auto boxStrLen = nautilus::invoke( + +[](const char* s) -> size_t {{ return s ? strlen(s) : (size_t) 0; }}, + boxStr); + + auto variableSized = pipelineMemoryProvider.arena.allocateVariableSizedData(boxStrLen); + + nautilus::invoke( + +[](int8_t* dest, const char* s, size_t len) -> void + {{ + if (s) {{ + memcpy(dest, s, len); + free((void*)s); + }} + }}, + variableSized.getContent(), + boxStr, + boxStrLen); + + Nautilus::Record resultRecord; + resultRecord.write(resultFieldIdentifier, variableSized); + return resultRecord;""" + + +def _swap_once(template, old, new, what): + """Replace exactly one occurrence of `old` with `new`, asserting the count + so a drifted scalar template fails at import rather than emitting bad C++.""" + n = template.count(old) + if n != 1: + raise AssertionError( + f"box-template derivation: expected exactly 1 occurrence of {what}, found {n}") + return template.replace(old, new) + + +PHYSICAL_CPP_TGEO_BOX = _swap_once( + _swap_once(PHYSICAL_CPP_TGEO, _EMPTY_SCALAR, _EMPTY_BOX, "tgeo empty-window block"), + _FINALIZE_SCALAR_TGEO, _FINALIZE_BOX_TGEO, "tgeo finalize tail") + +PHYSICAL_CPP_TNUMBER_BOX = _swap_once( + _swap_once(PHYSICAL_CPP_TNUMBER, _EMPTY_SCALAR, _EMPTY_BOX, "tnumber empty-window block"), + _FINALIZE_SCALAR_TNUMBER, _FINALIZE_BOX_TNUMBER, "tnumber finalize tail") + +# =========================================================================== +# WKB-trajectory output (return_mode "wkb"): materialize the windowed mini-trip +# as a SEQUENCE ([ ... ], linear interpolation — so trajectory functions like +# length are meaningful) and emit its hex-WKB. This is the value the MEOS +# function library composes over (the efficient materialize-once mechanism). +# Derived from the tgeo scalar template by swapping the empty-window write, the +# instant-set braces for sequence brackets, and the finalize. +# =========================================================================== +_FINALIZE_WKB_TGEO = """\ + auto boxStr = nautilus::invoke( + +[](const char* trajStr) -> char* + {{ + if (!trajStr || strlen(trajStr) == 0) {{ + free((void*)trajStr); + return (char*)nullptr; + }} + + MEOS::Meos::ensureMeosInitialized(); + std::lock_guard lock({mutex_name}); + + std::string trajString(trajStr); + void* temp = MEOS::Meos::parseTemporalPoint(trajString); + free((void*)trajStr); + if (!temp) {{ + return (char*)nullptr; + }} + + size_t hexSize = 0; + char* hexOut = temporal_as_hexwkb(static_cast(temp), 0, &hexSize); + MEOS::Meos::freeTemporalObject(temp); + return hexOut; + }}, + trajectoryStr); + + const auto boxStrLen = nautilus::invoke( + +[](const char* s) -> size_t {{ return s ? strlen(s) : (size_t) 0; }}, + boxStr); + + auto variableSized = pipelineMemoryProvider.arena.allocateVariableSizedData(boxStrLen); + + nautilus::invoke( + +[](int8_t* dest, const char* s, size_t len) -> void + {{ + if (s) {{ + memcpy(dest, s, len); + free((void*)s); + }} + }}, + variableSized.getContent(), + boxStr, + boxStrLen); + + Nautilus::Record resultRecord; + resultRecord.write(resultFieldIdentifier, variableSized); + return resultRecord;""" + +PHYSICAL_CPP_TGEO_WKB = _swap_once( + _swap_once( + _swap_once( + _swap_once(PHYSICAL_CPP_TGEO, _EMPTY_SCALAR, _EMPTY_BOX, "tgeo-wkb empty-window block"), + ' strcpy(buffer, "{{");', ' strcpy(buffer, "[");', "tgeo-wkb open bracket -> sequence"), + ' strcat(buffer, "}}");', ' strcat(buffer, "]");', "tgeo-wkb close bracket -> sequence"), + _FINALIZE_SCALAR_TGEO, _FINALIZE_WKB_TGEO, "tgeo-wkb finalize tail") + +# =========================================================================== +# Expandable-Temporal* aggregate (return_mode "expand"): the MEOS-native +# streaming model — the aggregate STATE is a live expandable `Temporal*` (a +# mini-trip trajectory), grown in place per event via the public streaming +# primitive `temporal_append_tinstant(..., expand=true)` (amortized-O(1), +# doubling). lower() applies the invariant MEOS scalar fn DIRECTLY to the live +# trajectory — no per-event string build, no parse-the-whole-window, no WKB. +# State is a `Temporal*` slot (sizeof(Temporal*)); public funcs only +# (tgeompoint_in / tsequence_make / temporal_append_tinstant / temporal_merge). +# =========================================================================== +PHYSICAL_CPP_TGEO_EXPAND = """\ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +extern "C" {{ +#include +#include +}} + +namespace NES +{{ + +static std::mutex {mutex_name}; + + +{nebula_name}AggregationPhysicalFunction::{nebula_name}AggregationPhysicalFunction( + DataType inputType, + DataType resultType, + PhysicalFunction lonFunctionParam, + PhysicalFunction latFunctionParam, + PhysicalFunction timestampFunctionParam, + Nautilus::Record::RecordFieldIdentifier resultFieldIdentifier, + std::shared_ptr bufferRef) + : AggregationPhysicalFunction(std::move(inputType), std::move(resultType), lonFunctionParam, std::move(resultFieldIdentifier)) + , bufferRef(std::move(bufferRef)) + , lonFunction(std::move(lonFunctionParam)) + , latFunction(std::move(latFunctionParam)) + , timestampFunction(std::move(timestampFunctionParam)) +{{ +}} + +void {nebula_name}AggregationPhysicalFunction::lift( + const nautilus::val& aggregationState, PipelineMemoryProvider& pipelineMemoryProvider, const Nautilus::Record& record) +{{ + auto lonValue = lonFunction.execute(record, pipelineMemoryProvider.arena); + auto latValue = latFunction.execute(record, pipelineMemoryProvider.arena); + auto timestampValue = timestampFunction.execute(record, pipelineMemoryProvider.arena); + + auto lon = lonValue.cast>(); + auto lat = latValue.cast>(); + auto timestamp = timestampValue.cast>(); + + nautilus::invoke( + +[](AggregationState* st, double lonVal, double latVal, int64_t tsVal) -> void + {{ + MEOS::Meos::ensureMeosInitialized(); + std::lock_guard lock({mutex_name}); + Temporal** slot = reinterpret_cast(st); + + long long sec = (tsVal > 1000000000000LL) ? (tsVal / 1000) : tsVal; + std::string ts = MEOS::Meos::convertSecondsToTimestamp(sec); + char wkt[120]; + snprintf(wkt, sizeof(wkt), "SRID=4326;Point(%.6f %.6f)@%s", lonVal, latVal, ts.c_str()); + + // Public instant constructor: a single-instant tgeompoint Temporal. + Temporal* instTemp = tgeompoint_in(wkt); + if (!instTemp) {{ + return; + }} + if (*slot == nullptr) {{ + // First event: a 1-instant sequence; subsequent appendInstant calls + // grow it in place (expand=true doubles maxcount when full). + TInstant* arr[1]; + arr[0] = (TInstant*) instTemp; + *slot = (Temporal*) tsequence_make((TInstant**) arr, 1, true, true, LINEAR, false); + }} else {{ + *slot = temporal_append_tinstant(*slot, (const TInstant*) instTemp, LINEAR, 0.0, nullptr, true); + }} + free(instTemp); // copied by tsequence_make / temporal_append_tinstant + }}, + aggregationState, + lon, + lat, + timestamp); +}} + +void {nebula_name}AggregationPhysicalFunction::combine( + const nautilus::val aggregationState1, + const nautilus::val aggregationState2, + PipelineMemoryProvider&) +{{ + nautilus::invoke( + +[](AggregationState* st1, AggregationState* st2) -> void + {{ + MEOS::Meos::ensureMeosInitialized(); + std::lock_guard lock({mutex_name}); + Temporal** s1 = reinterpret_cast(st1); + Temporal** s2 = reinterpret_cast(st2); + if (*s2 == nullptr) {{ + return; + }} + if (*s1 == nullptr) {{ + *s1 = *s2; + *s2 = nullptr; + return; + }} + // temporal_merge returns a fresh temporal (copies inputs, frees nothing). + Temporal* merged = temporal_merge(*s1, *s2); + free(*s1); + free(*s2); + *s2 = nullptr; + *s1 = merged; + }}, + aggregationState1, + aggregationState2); +}} + +Nautilus::Record {nebula_name}AggregationPhysicalFunction::lower( + const nautilus::val aggregationState, [[maybe_unused]] PipelineMemoryProvider& pipelineMemoryProvider) +{{ + MEOS::Meos::ensureMeosInitialized(); + + auto resultValue = nautilus::invoke( + +[](AggregationState* st) -> {return_cpp_type} + {{ + MEOS::Meos::ensureMeosInitialized(); + std::lock_guard lock({mutex_name}); + Temporal** slot = reinterpret_cast(st); + if (*slot == nullptr) {{ + return ({return_cpp_type})0; + }} + return {meos_scalar_fn}(*slot); + }}, + aggregationState); + + Nautilus::Record resultRecord; + resultRecord.write(resultFieldIdentifier, resultValue); + return resultRecord; +}} + +void {nebula_name}AggregationPhysicalFunction::reset(const nautilus::val aggregationState, PipelineMemoryProvider&) +{{ + nautilus::invoke( + +[](AggregationState* st) -> void + {{ + Temporal** slot = reinterpret_cast(st); + *slot = nullptr; + }}, + aggregationState); +}} + +size_t {nebula_name}AggregationPhysicalFunction::getSizeOfStateInBytes() const +{{ + return sizeof(Temporal*); +}} + +void {nebula_name}AggregationPhysicalFunction::cleanup(nautilus::val aggregationState) +{{ + nautilus::invoke( + +[](AggregationState* st) -> void + {{ + Temporal** slot = reinterpret_cast(st); + if (*slot != nullptr) {{ + free(*slot); + *slot = nullptr; + }} + }}, + aggregationState); +}} + + +AggregationPhysicalFunctionRegistryReturnType AggregationPhysicalFunctionGeneratedRegistrar::Register{nebula_name}AggregationPhysicalFunction( + AggregationPhysicalFunctionRegistryArguments) +{{ + throw std::runtime_error("{class_name_token} aggregation cannot be created through the registry. " + "It requires three field functions (longitude, latitude, timestamp)"); +}} + +}} // namespace NES +""" + +# =========================================================================== +# Scalar-fold box-output template (value/time Span extents). +# +# Reuses the tnumber (value, ts) HPP / ctor / lift / combine / reset / cleanup +# verbatim — only lower() differs. There is NO trajectory/sequence string and +# NO MEOS parse: the chosen scalar field is folded DIRECTLY through the MEOS +# extent transition fn (`float_extent_transfn`, `timestamptz_extent_transfn`, +# …), the Span state threading across events as an opaque pointer (NULL initial +# state -> first call allocates via span_make, later calls span_expand in place; +# one allocation total, freed after serialization via the external typed +# wrapper `floatspan_out` / `intspan_out` / `bigintspan_out` / `tstzspan_out`). +# =========================================================================== +PHYSICAL_CPP_SCALARFOLD = """\ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +extern "C" {{ +#include +}} + +namespace NES +{{ + +constexpr static std::string_view ValueFieldName = "value"; +constexpr static std::string_view TimestampFieldName = "timestamp"; + +static std::mutex {mutex_name}; + + +{nebula_name}AggregationPhysicalFunction::{nebula_name}AggregationPhysicalFunction( + DataType inputType, + DataType resultType, + PhysicalFunction valueFunctionParam, + PhysicalFunction timestampFunctionParam, + Nautilus::Record::RecordFieldIdentifier resultFieldIdentifier, + std::shared_ptr bufferRef) + : AggregationPhysicalFunction(std::move(inputType), std::move(resultType), valueFunctionParam, std::move(resultFieldIdentifier)) + , bufferRef(std::move(bufferRef)) + , valueFunction(std::move(valueFunctionParam)) + , timestampFunction(std::move(timestampFunctionParam)) +{{ +}} + +void {nebula_name}AggregationPhysicalFunction::lift( + const nautilus::val& aggregationState, PipelineMemoryProvider& pipelineMemoryProvider, const Nautilus::Record& record) +{{ + const auto pagedVectorPtr = static_cast>(aggregationState); + + auto valueValue = valueFunction.execute(record, pipelineMemoryProvider.arena); + auto timestampValue = timestampFunction.execute(record, pipelineMemoryProvider.arena); + + Record aggregateStateRecord({{ + {{std::string(ValueFieldName), valueValue}}, + {{std::string(TimestampFieldName), timestampValue}} + }}); + + const Nautilus::Interface::PagedVectorRef pagedVectorRef(pagedVectorPtr, bufferRef); + pagedVectorRef.writeRecord(aggregateStateRecord, pipelineMemoryProvider.bufferProvider); +}} + +void {nebula_name}AggregationPhysicalFunction::combine( + const nautilus::val aggregationState1, + const nautilus::val aggregationState2, + PipelineMemoryProvider&) +{{ + const auto memArea1 = static_cast>(aggregationState1); + const auto memArea2 = static_cast>(aggregationState2); + + nautilus::invoke( + +[](Nautilus::Interface::PagedVector* vector1, const Nautilus::Interface::PagedVector* vector2) -> void + {{ vector1->copyFrom(*vector2); }}, + memArea1, + memArea2); +}} + +Nautilus::Record {nebula_name}AggregationPhysicalFunction::lower( + const nautilus::val aggregationState, [[maybe_unused]] PipelineMemoryProvider& pipelineMemoryProvider) +{{ + MEOS::Meos::ensureMeosInitialized(); + + const auto pagedVectorPtr = static_cast>(aggregationState); + const Nautilus::Interface::PagedVectorRef pagedVectorRef(pagedVectorPtr, bufferRef); + const auto allFieldNames = bufferRef->getMemoryLayout()->getSchema().getFieldNames(); + const auto numberOfEntries = invoke( + +[](const Nautilus::Interface::PagedVector* pagedVector) + {{ + return pagedVector->getTotalNumberOfEntries(); + }}, + pagedVectorPtr); + + if (numberOfEntries == nautilus::val(0)) {{ + auto emptyVarSized = pipelineMemoryProvider.arena.allocateVariableSizedData(0); + Nautilus::Record resultRecord; + resultRecord.write(resultFieldIdentifier, emptyVarSized); + return resultRecord; + }} + + // Fold the windowed scalar field through the MEOS extent transition fn. + // The Span state threads across events as an opaque pointer; a NULL initial + // state makes the first call allocate, later calls expand in place. + auto spanState = nautilus::invoke( + +[](const Nautilus::Interface::PagedVector*) -> void* {{ return nullptr; }}, + pagedVectorPtr); + + const auto endIt = pagedVectorRef.end(allFieldNames); + for (auto candidateIt = pagedVectorRef.begin(allFieldNames); candidateIt != endIt; ++candidateIt) + {{ + const auto itemRecord = *candidateIt; + const auto valueRaw = itemRecord.read(std::string(ValueFieldName)); + auto value = valueRaw.cast>(); + + spanState = nautilus::invoke( + +[](void* state, {fold_field_cpp_type} val) -> void* + {{ + MEOS::Meos::ensureMeosInitialized(); + std::lock_guard lock({mutex_name}); + {fold_invoke_body} + }}, + spanState, + value); + }} + + auto boxStr = nautilus::invoke( + +[](void* state) -> char* + {{ + if (!state) {{ + return (char*)nullptr; + }} + MEOS::Meos::ensureMeosInitialized(); + std::lock_guard lock({mutex_name}); + Span* sp = static_cast(state); + char* out = {box_out_call}; + free(state); + return out; + }}, + spanState); + + const auto boxStrLen = nautilus::invoke( + +[](const char* s) -> size_t {{ return s ? strlen(s) : (size_t) 0; }}, + boxStr); + + auto variableSized = pipelineMemoryProvider.arena.allocateVariableSizedData(boxStrLen); + + nautilus::invoke( + +[](int8_t* dest, const char* s, size_t len) -> void + {{ + if (s) {{ + memcpy(dest, s, len); + free((void*)s); + }} + }}, + variableSized.getContent(), + boxStr, + boxStrLen); + + Nautilus::Record resultRecord; + resultRecord.write(resultFieldIdentifier, variableSized); + return resultRecord; +}} + +void {nebula_name}AggregationPhysicalFunction::reset(const nautilus::val aggregationState, PipelineMemoryProvider&) +{{ + nautilus::invoke( + +[](AggregationState* pagedVectorMemArea) -> void + {{ + auto* pagedVector = reinterpret_cast(pagedVectorMemArea); + new (pagedVector) Nautilus::Interface::PagedVector(); + }}, + aggregationState); +}} + +size_t {nebula_name}AggregationPhysicalFunction::getSizeOfStateInBytes() const +{{ + return sizeof(Nautilus::Interface::PagedVector); +}} + +void {nebula_name}AggregationPhysicalFunction::cleanup(nautilus::val aggregationState) +{{ + nautilus::invoke( + +[](AggregationState* pagedVectorMemArea) -> void + {{ + auto* pagedVector = reinterpret_cast(pagedVectorMemArea); + pagedVector->~PagedVector(); + }}, + aggregationState); +}} + + +AggregationPhysicalFunctionRegistryReturnType AggregationPhysicalFunctionGeneratedRegistrar::Register{nebula_name}AggregationPhysicalFunction( + AggregationPhysicalFunctionRegistryArguments) +{{ + throw std::runtime_error("{class_name_token} aggregation cannot be created through the registry. " + "It requires two field functions (value, timestamp)"); +}} + +}} // namespace NES +""" + +# =========================================================================== +# Set-collect aggregate template (windowed union -> Set). +# +# Same scalar-fold mechanism as PHYSICAL_CPP_SCALARFOLD, but the per-event +# `*_union_transfn` accumulates an unordered Set state (not a Span); the window +# is finalized with `set_union_finalfn` into the canonical Set before +# serialization through an external typed wrapper (floatset_out / intset_out / +# bigintset_out / tstzset_out). Derived from the scalar-fold template by an +# asserted swap of only the serialize lambda — the fold loop / lift / combine / +# reset / cleanup stay byte-identical. +# =========================================================================== +_SCALARFOLD_SERIALIZE_SPAN = """\ + auto boxStr = nautilus::invoke( + +[](void* state) -> char* + {{ + if (!state) {{ + return (char*)nullptr; + }} + MEOS::Meos::ensureMeosInitialized(); + std::lock_guard lock({mutex_name}); + Span* sp = static_cast(state); + char* out = {box_out_call}; + free(state); + return out; + }}, + spanState);""" + +_SCALARFOLD_SERIALIZE_SET = """\ + auto boxStr = nautilus::invoke( + +[](void* state) -> char* + {{ + if (!state) {{ + return (char*)nullptr; + }} + MEOS::Meos::ensureMeosInitialized(); + std::lock_guard lock({mutex_name}); + // set_union_finalfn pfree()s the state internally and returns a new + // Set, so the state must NOT be freed again here (double free). + Set* sp = {finalfn}(static_cast(state)); + if (!sp) {{ + return (char*)nullptr; + }} + char* out = {box_out_call}; + free(sp); + return out; + }}, + spanState);""" + +PHYSICAL_CPP_SETFOLD = _swap_once( + PHYSICAL_CPP_SCALARFOLD, _SCALARFOLD_SERIALIZE_SPAN, _SCALARFOLD_SERIALIZE_SET, + "scalarfold serialize -> setfold (finalfn)") + +# =========================================================================== +# Parser-glue templates: TWO dispatch sites in AntlrSQLQueryPlanCreator.cpp. +# Site 1 is the dedicated-token case-switch (~line 965 in mariana's tree). +# Site 2 is the IDENTIFIER fallback `else if (funcName == "TOKEN")` chain +# (~line 2062 in mariana's tree). +# =========================================================================== + +# Site 1 — case-switch dispatch. Two shapes (tgeo 3-arg, tnumber 2-arg). +CASE_SWITCH_TGEO = """\ + /* BEGIN CODEGEN AGGREGATION GLUE: {sql_token} (case-switch) */ + case AntlrSQLLexer::{sql_token}: + // {comment_one_liner} + if (helpers.top().functionBuilder.size() != 3) {{ + throw InvalidQuerySyntax("{sql_token} requires exactly three arguments (longitude, latitude, timestamp), but got {{}}", helpers.top().functionBuilder.size()); + }} + {{ + const auto timestampFunction = helpers.top().functionBuilder.back(); + helpers.top().functionBuilder.pop_back(); + const auto latitudeFunction = helpers.top().functionBuilder.back(); + helpers.top().functionBuilder.pop_back(); + const auto longitudeFunction = helpers.top().functionBuilder.back(); + helpers.top().functionBuilder.pop_back(); + + if (!longitudeFunction.tryGet() || + !latitudeFunction.tryGet() || + !timestampFunction.tryGet()) {{ + throw InvalidQuerySyntax("{sql_token} arguments must be field references"); + }} + + helpers.top().windowAggs.push_back( + {nebula_name}AggregationLogicalFunction::create(longitudeFunction.get(), + latitudeFunction.get(), + timestampFunction.get())); + helpers.top().functionBuilder.push_back(longitudeFunction); + }} + break; + /* END CODEGEN AGGREGATION GLUE: {sql_token} (case-switch) */ +""" + +CASE_SWITCH_TNUMBER = """\ + /* BEGIN CODEGEN AGGREGATION GLUE: {sql_token} (case-switch) */ + case AntlrSQLLexer::{sql_token}: + // {comment_one_liner} + if (helpers.top().functionBuilder.size() != 2) {{ + throw InvalidQuerySyntax("{sql_token} requires exactly two arguments (value, timestamp), but got {{}}", helpers.top().functionBuilder.size()); + }} + {{ + const auto timestampFunction = helpers.top().functionBuilder.back(); + helpers.top().functionBuilder.pop_back(); + const auto valueFunction = helpers.top().functionBuilder.back(); + helpers.top().functionBuilder.pop_back(); + + if (!valueFunction.tryGet() || + !timestampFunction.tryGet()) {{ + throw InvalidQuerySyntax("{sql_token} arguments must be field references"); + }} + + helpers.top().windowAggs.push_back( + {nebula_name}AggregationLogicalFunction::create(valueFunction.get(), + timestampFunction.get())); + helpers.top().functionBuilder.push_back(valueFunction); + }} + break; + /* END CODEGEN AGGREGATION GLUE: {sql_token} (case-switch) */ +""" + +# Site 2 — funcName == "TOKEN" string chain. +FUNCNAME_CHAIN_TGEO = """\ + /* BEGIN CODEGEN AGGREGATION GLUE: {sql_token} (funcName chain) */ + else if (funcName == "{sql_token}") + {{ + if (helpers.top().functionBuilder.size() < 3) + {{ + throw InvalidQuerySyntax("{sql_token} requires three arguments at {{}}", context->getText()); + }} + const auto ts = helpers.top().functionBuilder.back().get(); + helpers.top().functionBuilder.pop_back(); + const auto lat = helpers.top().functionBuilder.back().get(); + helpers.top().functionBuilder.pop_back(); + const auto lon = helpers.top().functionBuilder.back().get(); + helpers.top().functionBuilder.pop_back(); + helpers.top().windowAggs.push_back({nebula_name}AggregationLogicalFunction::create(lon, lat, ts)); + }} + /* END CODEGEN AGGREGATION GLUE: {sql_token} (funcName chain) */ +""" + +FUNCNAME_CHAIN_TNUMBER = """\ + /* BEGIN CODEGEN AGGREGATION GLUE: {sql_token} (funcName chain) */ + else if (funcName == "{sql_token}") + {{ + if (helpers.top().functionBuilder.size() < 2) + {{ + throw InvalidQuerySyntax("{sql_token} requires two arguments at {{}}", context->getText()); + }} + const auto ts = helpers.top().functionBuilder.back().get(); + helpers.top().functionBuilder.pop_back(); + const auto value = helpers.top().functionBuilder.back().get(); + helpers.top().functionBuilder.pop_back(); + helpers.top().windowAggs.push_back({nebula_name}AggregationLogicalFunction::create(value, ts)); + }} + /* END CODEGEN AGGREGATION GLUE: {sql_token} (funcName chain) */ +""" + +# Site 3 — optimizer logical→physical lowering rule. +OPTIMIZER_LOWERING_TGEO = """\ + /* BEGIN CODEGEN AGGREGATION GLUE: {class_name_token} (optimizer lowering) */ + if (name == std::string_view("{class_name_token}")) + {{ + auto specificDescriptor = std::dynamic_pointer_cast<{nebula_name}AggregationLogicalFunction>(descriptor); + INVARIANT(specificDescriptor != nullptr, "Expected {nebula_name}AggregationLogicalFunction for {class_name_token}"); + + auto lonPF = QueryCompilation::FunctionProvider::lowerFunction(specificDescriptor->getLonField()); + auto latPF = QueryCompilation::FunctionProvider::lowerFunction(specificDescriptor->getLatField()); + auto tsPF = QueryCompilation::FunctionProvider::lowerFunction(specificDescriptor->getTimestampField()); + + Schema stateSchema; + stateSchema.addField("lon", specificDescriptor->getLonField().getDataType()); + stateSchema.addField("lat", specificDescriptor->getLatField().getDataType()); + stateSchema.addField("timestamp", specificDescriptor->getTimestampField().getDataType()); + auto tupleBufferRef = Interface::BufferRef::TupleBufferRef::create(configuration.pageSize.getValue(), stateSchema); + + auto phys = std::make_shared<{nebula_name}AggregationPhysicalFunction>( + std::move(physicalInputType), + std::move(physicalFinalType), + lonPF, + latPF, + tsPF, + resultFieldIdentifier, + tupleBufferRef); + aggregationPhysicalFunctions.push_back(std::move(phys)); + continue; + }} + /* END CODEGEN AGGREGATION GLUE: {class_name_token} (optimizer lowering) */ +""" + +OPTIMIZER_LOWERING_TNUMBER = """\ + /* BEGIN CODEGEN AGGREGATION GLUE: {class_name_token} (optimizer lowering) */ + if (name == std::string_view("{class_name_token}")) + {{ + auto specificDescriptor = std::dynamic_pointer_cast<{nebula_name}AggregationLogicalFunction>(descriptor); + INVARIANT(specificDescriptor != nullptr, "Expected {nebula_name}AggregationLogicalFunction for {class_name_token}"); + + auto valuePF = QueryCompilation::FunctionProvider::lowerFunction(specificDescriptor->getValueField()); + auto tsPF = QueryCompilation::FunctionProvider::lowerFunction(specificDescriptor->getTimestampField()); + + Schema stateSchema; + stateSchema.addField("value", specificDescriptor->getValueField().getDataType()); + stateSchema.addField("timestamp", specificDescriptor->getTimestampField().getDataType()); + auto tupleBufferRef = Interface::BufferRef::TupleBufferRef::create(configuration.pageSize.getValue(), stateSchema); + + auto phys = std::make_shared<{nebula_name}AggregationPhysicalFunction>( + std::move(physicalInputType), + std::move(physicalFinalType), + valuePF, + tsPF, + resultFieldIdentifier, + tupleBufferRef); + aggregationPhysicalFunctions.push_back(std::move(phys)); + continue; + }} + /* END CODEGEN AGGREGATION GLUE: {class_name_token} (optimizer lowering) */ +""" + +# =========================================================================== +# Expandable-Temporal* VALUE-OUTPUT: f(live mini-trip) -> Temporal* result, +# serialized to hex-WKB as VARSIZED (the proven box-output VARSIZED tail). +# Derived from PHYSICAL_CPP_TGEO_EXPAND by swapping only the scalar lower() for +# the value-output one. Wires the Temporal-returning single-temporal transforms +# (tgeo_centroid, tpoint_azimuth, tgeompoint_to_tgeometry, …) as windowed +# aggregates over the expandable trajectory. +# =========================================================================== +_EXPAND_LOWER_SCALAR = """\ + auto resultValue = nautilus::invoke( + +[](AggregationState* st) -> {return_cpp_type} + {{ + MEOS::Meos::ensureMeosInitialized(); + std::lock_guard lock({mutex_name}); + Temporal** slot = reinterpret_cast(st); + if (*slot == nullptr) {{ + return ({return_cpp_type})0; + }} + return {meos_scalar_fn}(*slot); + }}, + aggregationState); + + Nautilus::Record resultRecord; + resultRecord.write(resultFieldIdentifier, resultValue); + return resultRecord;""" + +_EXPAND_LOWER_WKB = """\ + auto hexStr = nautilus::invoke( + +[](AggregationState* st) -> char* + {{ + MEOS::Meos::ensureMeosInitialized(); + std::lock_guard lock({mutex_name}); + Temporal** slot = reinterpret_cast(st); + if (*slot == nullptr) {{ + return (char*)nullptr; + }} + Temporal* res = {meos_scalar_fn}(*slot); + if (!res) {{ + return (char*)nullptr; + }} + size_t hexSize = 0; + char* hexOut = temporal_as_hexwkb(res, 0, &hexSize); + free(res); + return hexOut; + }}, + aggregationState); + + const auto hexLen = nautilus::invoke( + +[](const char* s) -> size_t {{ return s ? strlen(s) : (size_t) 0; }}, + hexStr); + + auto variableSized = pipelineMemoryProvider.arena.allocateVariableSizedData(hexLen); + + nautilus::invoke( + +[](int8_t* dest, const char* s, size_t len) -> void + {{ + if (s) {{ + memcpy(dest, s, len); + free((void*)s); + }} + }}, + variableSized.getContent(), + hexStr, + hexLen); + + Nautilus::Record resultRecord; + resultRecord.write(resultFieldIdentifier, variableSized); + return resultRecord;""" + +PHYSICAL_CPP_TGEO_EXPAND_WKB = _swap_once( + PHYSICAL_CPP_TGEO_EXPAND, _EXPAND_LOWER_SCALAR, _EXPAND_LOWER_WKB, + "expand scalar lower -> value-output (hex-WKB) lower") + +# geometry value-output: f(traj) returns a GSERIALIZED (start/end point, convex +# hull, time-weighted centroid of the windowed trajectory), serialized as +# canonical hex-EWKB via geo_out. Same Temporal* slot/lift/append; only the +# finalize differs from the temporal value-output (GSERIALIZED + geo_out, no +# hexSize out-param). +_EXPAND_LOWER_GEO_WKB = _swap_once( + _swap_once(_EXPAND_LOWER_WKB, + "Temporal* res = {meos_scalar_fn}(*slot);", + "GSERIALIZED* res = {meos_scalar_fn}(*slot);", + "value-output res type Temporal -> GSERIALIZED"), + "size_t hexSize = 0;\n char* hexOut = temporal_as_hexwkb(res, 0, &hexSize);", + "char* hexOut = geo_out(res);", + "temporal hex-WKB -> geometry hex-EWKB (geo_out)") + +PHYSICAL_CPP_TGEO_EXPAND_GEO_WKB = _swap_once( + PHYSICAL_CPP_TGEO_EXPAND, _EXPAND_LOWER_SCALAR, _EXPAND_LOWER_GEO_WKB, + "expand scalar lower -> geometry value-output (hex-EWKB) lower") + +# tnumber expandable value-output: same Temporal*-slot lower/reset/cleanup, but +# the per-event instant is a tfloat ("value@ts" via tfloat_in) and the ctor takes +# (value, ts). Derived from the tgeo expand-wkb template by swapping only the ctor +# and lift (the rest — Temporal* slot, appendInstant, value-output finalize — is +# input-shape-independent). +_EXPAND_CTOR_TGEO = """\ +{nebula_name}AggregationPhysicalFunction::{nebula_name}AggregationPhysicalFunction( + DataType inputType, + DataType resultType, + PhysicalFunction lonFunctionParam, + PhysicalFunction latFunctionParam, + PhysicalFunction timestampFunctionParam, + Nautilus::Record::RecordFieldIdentifier resultFieldIdentifier, + std::shared_ptr bufferRef) + : AggregationPhysicalFunction(std::move(inputType), std::move(resultType), lonFunctionParam, std::move(resultFieldIdentifier)) + , bufferRef(std::move(bufferRef)) + , lonFunction(std::move(lonFunctionParam)) + , latFunction(std::move(latFunctionParam)) + , timestampFunction(std::move(timestampFunctionParam)) +{{ +}}""" +_EXPAND_CTOR_TNUMBER = """\ +{nebula_name}AggregationPhysicalFunction::{nebula_name}AggregationPhysicalFunction( + DataType inputType, + DataType resultType, + PhysicalFunction valueFunctionParam, + PhysicalFunction timestampFunctionParam, + Nautilus::Record::RecordFieldIdentifier resultFieldIdentifier, + std::shared_ptr bufferRef) + : AggregationPhysicalFunction(std::move(inputType), std::move(resultType), valueFunctionParam, std::move(resultFieldIdentifier)) + , bufferRef(std::move(bufferRef)) + , valueFunction(std::move(valueFunctionParam)) + , timestampFunction(std::move(timestampFunctionParam)) +{{ +}}""" +_EXPAND_LIFT_TGEO = """\ + auto lonValue = lonFunction.execute(record, pipelineMemoryProvider.arena); + auto latValue = latFunction.execute(record, pipelineMemoryProvider.arena); + auto timestampValue = timestampFunction.execute(record, pipelineMemoryProvider.arena); + + auto lon = lonValue.cast>(); + auto lat = latValue.cast>(); + auto timestamp = timestampValue.cast>(); + + nautilus::invoke( + +[](AggregationState* st, double lonVal, double latVal, int64_t tsVal) -> void + {{ + MEOS::Meos::ensureMeosInitialized(); + std::lock_guard lock({mutex_name}); + Temporal** slot = reinterpret_cast(st); + + long long sec = (tsVal > 1000000000000LL) ? (tsVal / 1000) : tsVal; + std::string ts = MEOS::Meos::convertSecondsToTimestamp(sec); + char wkt[120]; + snprintf(wkt, sizeof(wkt), "SRID=4326;Point(%.6f %.6f)@%s", lonVal, latVal, ts.c_str()); + + // Public instant constructor: a single-instant tgeompoint Temporal. + Temporal* instTemp = tgeompoint_in(wkt); + if (!instTemp) {{ + return; + }} + if (*slot == nullptr) {{ + // First event: a 1-instant sequence; subsequent appendInstant calls + // grow it in place (expand=true doubles maxcount when full). + TInstant* arr[1]; + arr[0] = (TInstant*) instTemp; + *slot = (Temporal*) tsequence_make((TInstant**) arr, 1, true, true, LINEAR, false); + }} else {{ + *slot = temporal_append_tinstant(*slot, (const TInstant*) instTemp, LINEAR, 0.0, nullptr, true); + }} + free(instTemp); // copied by tsequence_make / temporal_append_tinstant + }}, + aggregationState, + lon, + lat, + timestamp);""" +_EXPAND_LIFT_TNUMBER = """\ + auto valueValue = valueFunction.execute(record, pipelineMemoryProvider.arena); + auto timestampValue = timestampFunction.execute(record, pipelineMemoryProvider.arena); + + auto value = valueValue.cast>(); + auto timestamp = timestampValue.cast>(); + + nautilus::invoke( + +[](AggregationState* st, double valueVal, int64_t tsVal) -> void + {{ + MEOS::Meos::ensureMeosInitialized(); + std::lock_guard lock({mutex_name}); + Temporal** slot = reinterpret_cast(st); + + long long sec = (tsVal > 1000000000000LL) ? (tsVal / 1000) : tsVal; + std::string ts = MEOS::Meos::convertSecondsToTimestamp(sec); + char wkt[80]; + snprintf(wkt, sizeof(wkt), "%.6f@%s", valueVal, ts.c_str()); + + // Public instant constructor: a single-instant tfloat Temporal. + Temporal* instTemp = tfloat_in(wkt); + if (!instTemp) {{ + return; + }} + if (*slot == nullptr) {{ + TInstant* arr[1]; + arr[0] = (TInstant*) instTemp; + *slot = (Temporal*) tsequence_make((TInstant**) arr, 1, true, true, LINEAR, false); + }} else {{ + *slot = temporal_append_tinstant(*slot, (const TInstant*) instTemp, LINEAR, 0.0, nullptr, true); + }} + free(instTemp); + }}, + aggregationState, + value, + timestamp);""" + +PHYSICAL_CPP_TNUMBER_EXPAND_WKB = _swap_once( + _swap_once(PHYSICAL_CPP_TGEO_EXPAND_WKB, _EXPAND_CTOR_TGEO, _EXPAND_CTOR_TNUMBER, "expand ctor tgeo->tnumber"), + _EXPAND_LIFT_TGEO, _EXPAND_LIFT_TNUMBER, "expand lift tgeo->tnumber") + +# tnpoint expandable value-output: reuses the 3-field tgeo HPP/parser/optimizer +# (the 3 args are rid, frac, ts); only the lift (NPoint instant via tnpoint_in) +# and the npoint include change. Wires tnpoint trajectory transforms over the +# windowed tnpoint mini-series. +_EXPAND_LIFT_TNPOINT = """\ + auto ridValue = lonFunction.execute(record, pipelineMemoryProvider.arena); + auto fracValue = latFunction.execute(record, pipelineMemoryProvider.arena); + auto timestampValue = timestampFunction.execute(record, pipelineMemoryProvider.arena); + + auto rid = ridValue.cast>(); + auto frac = fracValue.cast>(); + auto timestamp = timestampValue.cast>(); + + nautilus::invoke( + +[](AggregationState* st, int64_t ridVal, double fracVal, int64_t tsVal) -> void + {{ + MEOS::Meos::ensureMeosInitialized(); + std::lock_guard lock({mutex_name}); + Temporal** slot = reinterpret_cast(st); + + long long sec = (tsVal > 1000000000000LL) ? (tsVal / 1000) : tsVal; + std::string ts = MEOS::Meos::convertSecondsToTimestamp(sec); + char wkt[80]; + snprintf(wkt, sizeof(wkt), "NPoint(%lld,%.6f)@%s", (long long) ridVal, fracVal, ts.c_str()); + + Temporal* instTemp = tnpoint_in(wkt); + if (!instTemp) {{ + return; + }} + if (*slot == nullptr) {{ + TInstant* arr[1]; + arr[0] = (TInstant*) instTemp; + *slot = (Temporal*) tsequence_make((TInstant**) arr, 1, true, true, LINEAR, false); + }} else {{ + *slot = temporal_append_tinstant(*slot, (const TInstant*) instTemp, LINEAR, 0.0, nullptr, true); + }} + free(instTemp); + }}, + aggregationState, + rid, + frac, + timestamp);""" + +PHYSICAL_CPP_TNPOINT_EXPAND_WKB = _swap_once( + _swap_once(PHYSICAL_CPP_TGEO_EXPAND_WKB, _EXPAND_LIFT_TGEO, _EXPAND_LIFT_TNPOINT, "expand lift tgeo->tnpoint"), + "#include ", "#include \n#include ", "tnpoint include") + + +# =========================================================================== +# Shape dispatchers + emit_operator. +# =========================================================================== + +def physical_template_for(op): + box = op.get("return_mode") == "box" + if op["input_shape"] == "tgeo": + if op.get("return_mode") == "wkb": + return PHYSICAL_HPP_TGEO, PHYSICAL_CPP_TGEO_WKB + if op.get("return_mode") == "expand": + return PHYSICAL_HPP_TGEO, PHYSICAL_CPP_TGEO_EXPAND + if op.get("return_mode") == "expand_wkb": + return PHYSICAL_HPP_TGEO, PHYSICAL_CPP_TGEO_EXPAND_WKB + if op.get("return_mode") == "expand_geo_wkb": + return PHYSICAL_HPP_TGEO, PHYSICAL_CPP_TGEO_EXPAND_GEO_WKB + if op.get("return_mode") == "expand_wkb_tnpoint": + return PHYSICAL_HPP_TGEO, PHYSICAL_CPP_TNPOINT_EXPAND_WKB + return PHYSICAL_HPP_TGEO, (PHYSICAL_CPP_TGEO_BOX if box else PHYSICAL_CPP_TGEO) + if op["input_shape"] == "tnumber": + # Scalar-fold reuses the tnumber (value, ts) HPP but folds the field + # directly through the MEOS extent transition fn (no string / no parse); + # set-collect is the same shape with a Set state + a union finalfn. + if op.get("return_mode") == "expand_wkb": + return PHYSICAL_HPP_TNUMBER, PHYSICAL_CPP_TNUMBER_EXPAND_WKB + if op.get("fold") == "scalar": + return PHYSICAL_HPP_TNUMBER, PHYSICAL_CPP_SCALARFOLD + if op.get("fold") == "set": + return PHYSICAL_HPP_TNUMBER, PHYSICAL_CPP_SETFOLD + return PHYSICAL_HPP_TNUMBER, (PHYSICAL_CPP_TNUMBER_BOX if box else PHYSICAL_CPP_TNUMBER) + raise ValueError(f"unknown input_shape: {op['input_shape']}") + + +def logical_template_for(op): + if op["input_shape"] == "tgeo": + return LOGICAL_HPP_TGEO, LOGICAL_CPP_TGEO + if op["input_shape"] == "tnumber": + return LOGICAL_HPP_TNUMBER, LOGICAL_CPP_TNUMBER + raise ValueError(f"unknown input_shape: {op['input_shape']}") + + +def case_switch_template_for(op): + return CASE_SWITCH_TGEO if op["input_shape"] == "tgeo" else CASE_SWITCH_TNUMBER + + +def funcname_chain_template_for(op): + return FUNCNAME_CHAIN_TGEO if op["input_shape"] == "tgeo" else FUNCNAME_CHAIN_TNUMBER + + +def optimizer_lowering_template_for(op): + return OPTIMIZER_LOWERING_TGEO if op["input_shape"] == "tgeo" else OPTIMIZER_LOWERING_TNUMBER + + +def emit_operator(op, output_root: Path): + nebula_name = op["nebula_name"] + logical_hpp_tmpl, logical_cpp_tmpl = logical_template_for(op) + physical_hpp_tmpl, physical_cpp_tmpl = physical_template_for(op) + + # Common substitution dict. + fmt = { + "nebula_name": nebula_name, + "class_name_token": op["class_name_token"], + "sql_token": op["sql_token"], + "comment_one_liner": op["comment_one_liner"], + "meos_scalar_fn": op.get("meos_scalar_fn", ""), + "return_cpp_type": op.get("return_cpp_type", "double"), + "final_stamp_type": op["final_stamp_type"], + "mutex_name": f"meos_{nebula_name.lower()}_mutex", + # tnumber-only extras (harmless for tgeo since unused) + "lift_value_cpp_type": op.get("lift_value_cpp_type", "double"), + "value_printf_fmt": op.get("value_printf_fmt", "%.6f"), + "tnumber_in_fn": op.get("tnumber_in_fn", "tfloat_in"), + # box-output (VARSIZED extent) extras — only referenced by the *_BOX + # physical templates; harmless for scalar ops. + "extent_transfn": op.get("extent_transfn", ""), + "extent_box_type": op.get("extent_box_type", "STBox"), + "box_out_fn": op.get("box_out_fn", ""), + # scalar-fold / set-collect extras — referenced by the *FOLD templates. + "fold_field_cpp_type": op.get("fold_field_cpp_type", "double"), + "fold_invoke_body": op.get("fold_invoke_body", ""), + "box_out_call": op.get("box_out_call", ""), + "finalfn": op.get("finalfn", ""), + } + + # value_compute (point/tgeo finalize): either fold the windowed sequence + # directly with meos_scalar_fn, or — for the EXTENT shape — first reduce the + # sequence to its bounding box (tspatial_to_stbox / ...) and apply a box + # accessor/predicate to that windowed extent. In box-output mode the + # finalize is the serialized extent box itself (no value_compute). + box_build = op.get("extent_box_build_fn") + if op.get("return_mode") in ("box", "wkb"): + fmt["value_compute"] = "" + elif box_build: + box_t = op.get("extent_box_type", "STBox") + fmt["value_compute"] = ( + f'{box_t}* aggBox = {box_build}(static_cast(temp));\n' + f' {op["return_cpp_type"]} value = aggBox ? ' + f'{op["meos_scalar_fn"]}(aggBox) : ({op["return_cpp_type"]})0;\n' + f' if (aggBox) free(aggBox);') + else: + fmt["value_compute"] = ( + f'{op["return_cpp_type"]} value = ' + f'{op["meos_scalar_fn"]}(static_cast(temp));') + + paths = { + "logical_hpp": output_root / "nes-logical-operators/include/Operators/Windows/Aggregations/Meos" / f"{nebula_name}AggregationLogicalFunction.hpp", + "logical_cpp": output_root / "nes-logical-operators/src/Operators/Windows/Aggregations/Meos" / f"{nebula_name}AggregationLogicalFunction.cpp", + "physical_hpp": output_root / "nes-physical-operators/include/Aggregation/Function/Meos" / f"{nebula_name}AggregationPhysicalFunction.hpp", + "physical_cpp": output_root / "nes-physical-operators/src/Aggregation/Function/Meos" / f"{nebula_name}AggregationPhysicalFunction.cpp", + } + for p in paths.values(): + p.parent.mkdir(parents=True, exist_ok=True) + + paths["logical_hpp"].write_text(logical_hpp_tmpl.format(**fmt)) + paths["logical_cpp"].write_text(logical_cpp_tmpl.format(**fmt)) + paths["physical_hpp"].write_text(physical_hpp_tmpl.format(**fmt)) + paths["physical_cpp"].write_text(physical_cpp_tmpl.format(**fmt)) + sys.stderr.write(f" ✓ {nebula_name}: emitted 4 files\n") + + +# =========================================================================== +# Idempotent injectors. +# =========================================================================== + +def inject_cmake_entries(operators, output_root: Path) -> int: + """Append per-op `add_plugin(...)` entries to both layers' aggregation + CMakeLists. Idempotent: skips entries already present.""" + n_added = 0 + # Layer (logical | physical) → (CMakeLists path, plugin suffix) + layers = [ + ("logical", output_root / "nes-logical-operators/src/Operators/Windows/Aggregations/Meos/CMakeLists.txt", "Logical"), + ("physical", output_root / "nes-physical-operators/src/Aggregation/Function/Meos/CMakeLists.txt", "Physical"), + ] + for label, cml, suffix in layers: + if not cml.exists(): + sys.stderr.write(f" ! cmake-entries: {cml} not found, skipping {label}\n") + continue + body = cml.read_text() + new_lines = [] + for op in operators: + # Target name must NOT include "Aggregation" suffix — the registry codegen + # appends "Aggregation" itself, so a "...Aggregation" target + # would yield a double-Aggregation symbol. Mariana's convention is the + # target name = the SQL-side aggregation name (e.g. "TemporalLength"), + # NOT the C++ class basename. We follow that. + target_name = op["nebula_name"] + suffix_kind = "AggregationLogicalFunction" if label == "logical" else "AggregationPhysicalFunction" + registry_kind = "AggregationLogicalFunction" if label == "logical" else "AggregationPhysicalFunction" + cpp_basename = f"{op['nebula_name']}{suffix_kind}.cpp" + entry = ( + f"add_plugin({target_name} {registry_kind} " + f"nes-{label}-operators {cpp_basename})" + ) + # Match by basename to be tolerant of formatting drift + marker = f"add_plugin({target_name} {registry_kind}" + if marker in body: + continue + new_lines.append(entry) + if new_lines: + with cml.open("a") as f: + f.write("\n".join(new_lines) + "\n") + sys.stderr.write(f" ✓ cmake-entries ({label}): appended {len(new_lines)} entry(ies)\n") + n_added += len(new_lines) + return n_added + + +def inject_g4(operators, g4_path: Path) -> int: + """Inject lexer-token + functionName alternation entries into AntlrSQL.g4.""" + if not g4_path.exists(): + sys.stderr.write(f" ! g4: {g4_path} not found, skipping\n") + return 0 + body = g4_path.read_text() + n_added = 0 + + new_tokens = [] + for op in operators: + tok = op["sql_token"] + if re.search(rf"^{re.escape(tok)}\s*:", body, re.MULTILINE): + continue + new_tokens.append(f"{tok}: '{tok}' | '{tok.lower()}';") + if new_tokens: + if "/* BEGIN CODEGEN AGGREGATION LEXER TOKENS */" in body: + body = re.sub( + r"(/\* BEGIN CODEGEN AGGREGATION LEXER TOKENS \*/\n)(.*?)(/\* END CODEGEN AGGREGATION LEXER TOKENS \*/)", + lambda mm: mm.group(1) + mm.group(2) + "\n".join(new_tokens) + "\n" + mm.group(3), + body, count=1, flags=re.DOTALL, + ) + else: + anchor_re = re.compile(r"^WATERMARK:.*$", re.MULTILINE) + m = anchor_re.search(body) + if m is None: + sys.stderr.write(f" ! g4: WATERMARK anchor not found\n") + else: + insertion = ( + "/* BEGIN CODEGEN AGGREGATION LEXER TOKENS */\n" + + "\n".join(new_tokens) + + "\n/* END CODEGEN AGGREGATION LEXER TOKENS */\n" + ) + body = body[: m.start()] + insertion + body[m.start():] + n_added += len(new_tokens) + sys.stderr.write(f" ✓ g4 lexer-tokens: added {len(new_tokens)} token(s)\n") + + # functionName alternation + fn_re = re.compile(r"^functionName:\s*([^;]+);", re.MULTILINE) + m = fn_re.search(body) + if m is None: + sys.stderr.write(f" ! g4: functionName production not found\n") + else: + alternation = m.group(1) + new_alts = [] + for op in operators: + tok = op["sql_token"] + if re.search(rf"\b{re.escape(tok)}\b", alternation): + continue + new_alts.append(tok) + if new_alts: + new_alt_text = alternation.rstrip() + " | " + " | ".join(new_alts) + body = body[: m.start()] + f"functionName: {new_alt_text};" + body[m.end():] + sys.stderr.write(f" ✓ g4 functionName: added {len(new_alts)} alternative(s)\n") + + g4_path.write_text(body) + return n_added + + +def inject_parser_cpp(operators, cpp_path: Path) -> int: + """Inject TWO dispatch sites + per-op #include.""" + if not cpp_path.exists(): + sys.stderr.write(f" ! parser-cpp: {cpp_path} not found, skipping\n") + return 0 + body = cpp_path.read_text() + n_added = 0 + + # 1) #includes — insert after the LAST `#include ` line. + new_includes = [] + for op in operators: + inc = f"#include " + if inc in body: + continue + new_includes.append(inc) + if new_includes: + agg_inc_re = re.compile(r"(^#include ]+>\s*\n)+", re.MULTILINE) + matches = list(agg_inc_re.finditer(body)) + if matches: + last = matches[-1] + body = body[: last.end()] + "\n".join(new_includes) + "\n" + body[last.end():] + sys.stderr.write(f" ✓ parser-cpp aggregation includes: added {len(new_includes)}\n") + else: + # Fall back: insert after any Meos include + meos_inc_re = re.compile(r"(^#include ]+>\s*\n)+", re.MULTILINE) + matches = list(meos_inc_re.finditer(body)) + if matches: + last = matches[-1] + body = body[: last.end()] + "\n".join(new_includes) + "\n" + body[last.end():] + sys.stderr.write(f" ✓ parser-cpp aggregation includes (fallback): added {len(new_includes)}\n") + else: + sys.stderr.write(f" ! parser-cpp: no Meos include anchor found\n") + + # 2) Case-switch dispatch — insert after the last `END CODEGEN AGGREGATION GLUE: ... (case-switch)` + # marker, else before the `default:` of the switch that contains TGEO_AT_STBOX. + new_case_blocks = [] + for op in operators: + tmpl = case_switch_template_for(op) + marker = f"/* BEGIN CODEGEN AGGREGATION GLUE: {op['sql_token']} (case-switch) */" + if marker in body: + continue + # Skip if pre-existing hand-written case + if re.search(rf"case\s+AntlrSQLLexer::{re.escape(op['sql_token'])}\s*:", body): + sys.stderr.write( + f" ! parser-cpp: pre-existing case for {op['sql_token']} (case-switch); skipping\n" + ) + continue + new_case_blocks.append(tmpl.format( + sql_token=op["sql_token"], nebula_name=op["nebula_name"], comment_one_liner=op["comment_one_liner"], + )) + if new_case_blocks: + # Anchor preference order: + # 1. last `END CODEGEN AGGREGATION GLUE: ... (case-switch)` (own marker) + # 2. last `END CODEGEN PARSER GLUE: ...` (codegen_nebula.py W4.5+) + # 3. TGEO_AT_STBOX → default: (pre-W4.5 layout) + last_end_agg = list(re.finditer(r"/\* END CODEGEN AGGREGATION GLUE: [^*]+\(case-switch\)\s*\*/", body)) + last_end_nebula = list(re.finditer(r"/\* END CODEGEN PARSER GLUE: [^*]+\*/", body)) + if last_end_agg: + insert_at = last_end_agg[-1].end() + body = body[:insert_at] + "\n" + "\n".join(new_case_blocks) + body[insert_at:] + sys.stderr.write(f" ✓ parser-cpp case-switch: added {len(new_case_blocks)} (after own marker)\n") + elif last_end_nebula: + insert_at = last_end_nebula[-1].end() + body = body[:insert_at] + "\n" + "\n".join(new_case_blocks) + body[insert_at:] + sys.stderr.write(f" ✓ parser-cpp case-switch: added {len(new_case_blocks)} (after codegen_nebula marker)\n") + else: + anchor_re = re.compile(r"(case AntlrSQLLexer::TGEO_AT_STBOX:[\s\S]+?\n\s*break;\n)(\s*default:)") + m = anchor_re.search(body) + if m is None: + sys.stderr.write(f" ! parser-cpp: no case-switch anchor found\n") + else: + insertion = m.group(1) + "\n" + "\n".join(new_case_blocks) + "\n" + m.group(2) + body = body[: m.start()] + insertion + body[m.end():] + sys.stderr.write(f" ✓ parser-cpp case-switch: added {len(new_case_blocks)} (before default:)\n") + n_added += len(new_case_blocks) + + # 3) funcName-chain dispatch — insert after the last `END CODEGEN AGGREGATION GLUE: ... (funcName chain)`, + # else after mariana's CrossDistance else-if block. + new_chain_blocks = [] + for op in operators: + tmpl = funcname_chain_template_for(op) + marker = f"/* BEGIN CODEGEN AGGREGATION GLUE: {op['sql_token']} (funcName chain) */" + if marker in body: + continue + if re.search(rf'funcName == "{re.escape(op["sql_token"])}"', body): + sys.stderr.write( + f" ! parser-cpp: pre-existing funcName chain for {op['sql_token']}; skipping\n" + ) + continue + new_chain_blocks.append(tmpl.format(sql_token=op["sql_token"], nebula_name=op["nebula_name"])) + if new_chain_blocks: + last_end_re = re.compile(r"/\* END CODEGEN AGGREGATION GLUE: [^*]+\(funcName chain\)\s*\*/") + ends = list(last_end_re.finditer(body)) + if ends: + insert_at = ends[-1].end() + body = body[:insert_at] + "\n" + "\n".join(new_chain_blocks) + body[insert_at:] + sys.stderr.write(f" ✓ parser-cpp funcName chain: added {len(new_chain_blocks)} (after marker)\n") + else: + anchor_re = re.compile( + r'(else if \(funcName == "CROSS_DISTANCE"\)[\s\S]+?\n\s*\}\n)', + ) + m = anchor_re.search(body) + if m is None: + sys.stderr.write(f" ! parser-cpp: no funcName chain anchor (after CROSS_DISTANCE) found\n") + else: + insertion = m.group(1) + "\n".join(new_chain_blocks) + body = body[: m.end()] + "\n".join(new_chain_blocks) + body[m.end():] + sys.stderr.write(f" ✓ parser-cpp funcName chain: added {len(new_chain_blocks)} (after CROSS_DISTANCE)\n") + n_added += len(new_chain_blocks) + + cpp_path.write_text(body) + return n_added + + +def inject_optimizer(operators, opt_path: Path) -> int: + """Inject `if (name == "...")` blocks into LowerToPhysicalWindowedAggregation.cpp.""" + if not opt_path.exists(): + sys.stderr.write(f" ! optimizer: {opt_path} not found, skipping\n") + return 0 + body = opt_path.read_text() + n_added = 0 + + # 1) #include for the physical class header + new_includes = [] + for op in operators: + inc = f"#include " + if inc in body: + continue + new_includes.append(inc) + # Also need the logical class header + new_logical_includes = [] + for op in operators: + inc = f"#include " + if inc in body: + continue + new_logical_includes.append(inc) + if new_includes or new_logical_includes: + agg_inc_re = re.compile(r"(^#include ]+>\s*\n)+", re.MULTILINE) + matches = list(agg_inc_re.finditer(body)) + if matches: + last = matches[-1] + inserts = [] + if new_includes: + inserts.extend(new_includes) + if new_logical_includes: + inserts.extend(new_logical_includes) + body = body[: last.end()] + "\n".join(inserts) + "\n" + body[last.end():] + sys.stderr.write(f" ✓ optimizer includes: added {len(new_includes)} phys + {len(new_logical_includes)} logical\n") + else: + sys.stderr.write(f" ! optimizer: no Aggregation/Function/Meos include anchor found\n") + + # 2) The if-name-match block. Insert after last codegen END marker, else after mariana's CrossDistance block. + new_blocks = [] + for op in operators: + tmpl = optimizer_lowering_template_for(op) + marker = f"/* BEGIN CODEGEN AGGREGATION GLUE: {op['class_name_token']} (optimizer lowering) */" + if marker in body: + continue + # Skip if a pre-existing hand-written block exists for this class_name_token + if re.search(rf'name == std::string_view\("{re.escape(op["class_name_token"])}"\)', body): + sys.stderr.write( + f" ! optimizer: pre-existing lowering block for {op['class_name_token']}; skipping\n" + ) + continue + new_blocks.append(tmpl.format(class_name_token=op["class_name_token"], nebula_name=op["nebula_name"])) + if new_blocks: + last_end_re = re.compile(r"/\* END CODEGEN AGGREGATION GLUE: [^*]+\(optimizer lowering\)\s*\*/") + ends = list(last_end_re.finditer(body)) + if ends: + insert_at = ends[-1].end() + body = body[:insert_at] + "\n" + "\n".join(new_blocks) + body[insert_at:] + sys.stderr.write(f" ✓ optimizer lowering: added {len(new_blocks)} (after marker)\n") + else: + # Anchor: insert just before the "Default path: use registry" comment. + anchor_re = re.compile(r"(\n\s*// Default path: use registry)") + m = anchor_re.search(body) + if m is None: + sys.stderr.write(f" ! optimizer: 'Default path' anchor not found\n") + else: + insertion = "\n" + "\n".join(new_blocks) + m.group(1) + body = body[: m.start()] + insertion + body[m.end():] + sys.stderr.write(f" ✓ optimizer lowering: added {len(new_blocks)} (before Default path)\n") + n_added += len(new_blocks) + + opt_path.write_text(body) + return n_added + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--input", required=True) + parser.add_argument("--output-root", required=True) + parser.add_argument("--no-parser-glue", action="store_true") + parser.add_argument("--no-cmake-entries", action="store_true") + parser.add_argument("--no-optimizer-glue", action="store_true") + args = parser.parse_args() + + with open(args.input) as f: + config = json.load(f) + operators = config["operators"] + + # The serialized aggregation type (NAME), the optimizer-lowering match, and + # the registry key must be the same string for the query plan to round-trip + # (serialize set_type(NAME) -> worker create(type) -> registry key). The + # registry key is the add_plugin target = nebula_name (PascalCase), so + # class_name_token (which drives NAME + the optimizer match) MUST equal + # nebula_name. Earlier specs set it to the SQL token (UPPER_SNAKE), which + # made create(type) miss the registry and throw UnknownLogicalOperator at + # deserialize. The SQL spelling lives in sql_token (lexer/parser); it never + # belongs in NAME. Normalize here so a stray spec value cannot reintroduce + # the mismatch. + for op in operators: + op["class_name_token"] = op["nebula_name"] + + output_root = Path(args.output_root).resolve() + if not (output_root / "nes-logical-operators").exists(): + sys.exit(f"ERROR: {output_root} does not look like MobilityNebula root") + + sys.stderr.write(f"Emitting {len(operators)} aggregation operator(s):\n\n") + for op in operators: + emit_operator(op, output_root) + + if not args.no_cmake_entries: + sys.stderr.write("\nCMakeLists.txt:\n") + inject_cmake_entries(operators, output_root) + + if not args.no_parser_glue: + sys.stderr.write("\nParser glue:\n") + inject_g4(operators, output_root / "nes-sql-parser/AntlrSQL.g4") + inject_parser_cpp(operators, output_root / "nes-sql-parser/src/AntlrSQLQueryPlanCreator.cpp") + + if not args.no_optimizer_glue: + sys.stderr.write("\nOptimizer lowering glue:\n") + inject_optimizer(operators, output_root / "nes-query-optimizer/src/RewriteRules/LowerToPhysical/LowerToPhysicalWindowedAggregation.cpp") + + sys.stderr.write(f"\nDone. {len(operators) * 4} files emitted.\n") + + +if __name__ == "__main__": + main() diff --git a/tools/codegen/codegen_input.example.json b/tools/codegen/codegen_input.example.json new file mode 100644 index 0000000000..cbfd0ed15e --- /dev/null +++ b/tools/codegen/codegen_input.example.json @@ -0,0 +1,160 @@ +{ + "_comment": "Example input for codegen_nebula.py \u2014 first wave of MEOS spatial-relation E/A predicates. Each operator descriptor produces one logical .hpp/.cpp + one physical .hpp/.cpp file. Adjust the list to control which functions get generated.", + "operators": [ + { + "nebula_name": "TemporalEDisjointGeometry", + "sql_token": "TEMPORAL_EDISJOINT_GEOMETRY", + "meos_call": "edisjoint_tgeo_geo", + "args": [ + { + "name": "lon", + "nautilus_type": "double", + "cpp_type": "double" + }, + { + "name": "lat", + "nautilus_type": "double", + "cpp_type": "double" + }, + { + "name": "timestamp", + "nautilus_type": "uint64_t", + "cpp_type": "uint64_t" + }, + { + "name": "geometry", + "nautilus_type": "VariableSizedData", + "cpp_type": "const char*" + } + ], + "return_type": "int", + "nautilus_return": "INT32", + "build_temporal_point": true, + "comment_one_liner": "Per-event ever-disjoint between a single-instant tgeompoint built from event fields and a static geometry." + }, + { + "nebula_name": "TemporalATouchesGeometry", + "sql_token": "TEMPORAL_ATOUCHES_GEOMETRY", + "meos_call": "atouches_tgeo_geo", + "args": [ + { + "name": "lon", + "nautilus_type": "double", + "cpp_type": "double" + }, + { + "name": "lat", + "nautilus_type": "double", + "cpp_type": "double" + }, + { + "name": "timestamp", + "nautilus_type": "uint64_t", + "cpp_type": "uint64_t" + }, + { + "name": "geometry", + "nautilus_type": "VariableSizedData", + "cpp_type": "const char*" + } + ], + "return_type": "int", + "nautilus_return": "INT32", + "build_temporal_point": true, + "comment_one_liner": "Per-event always-touches between a single-instant tgeompoint and a static geometry." + }, + { + "nebula_name": "TemporalECoversGeometry", + "sql_token": "TEMPORAL_ECOVERS_GEOMETRY", + "meos_call": "ecovers_tgeo_geo", + "args": [ + { + "name": "lon", + "nautilus_type": "double", + "cpp_type": "double" + }, + { + "name": "lat", + "nautilus_type": "double", + "cpp_type": "double" + }, + { + "name": "timestamp", + "nautilus_type": "uint64_t", + "cpp_type": "uint64_t" + }, + { + "name": "geometry", + "nautilus_type": "VariableSizedData", + "cpp_type": "const char*" + } + ], + "return_type": "int", + "nautilus_return": "INT32", + "build_temporal_point": true, + "comment_one_liner": "Per-event ever-covers between a single-instant tgeompoint and a static geometry." + }, + { + "nebula_name": "TemporalAContainsGeometry", + "sql_token": "TEMPORAL_ACONTAINS_GEOMETRY", + "meos_call": "acontains_tgeo_geo", + "args": [ + { + "name": "lon", + "nautilus_type": "double", + "cpp_type": "double" + }, + { + "name": "lat", + "nautilus_type": "double", + "cpp_type": "double" + }, + { + "name": "timestamp", + "nautilus_type": "uint64_t", + "cpp_type": "uint64_t" + }, + { + "name": "geometry", + "nautilus_type": "VariableSizedData", + "cpp_type": "const char*" + } + ], + "return_type": "int", + "nautilus_return": "INT32", + "build_temporal_point": true, + "comment_one_liner": "Per-event always-contains between a single-instant tgeompoint and a static geometry." + }, + { + "nebula_name": "TemporalETouchesGeometry", + "sql_token": "TEMPORAL_ETOUCHES_GEOMETRY", + "meos_call": "etouches_tgeo_geo", + "args": [ + { + "name": "lon", + "nautilus_type": "double", + "cpp_type": "double" + }, + { + "name": "lat", + "nautilus_type": "double", + "cpp_type": "double" + }, + { + "name": "timestamp", + "nautilus_type": "uint64_t", + "cpp_type": "uint64_t" + }, + { + "name": "geometry", + "nautilus_type": "VariableSizedData", + "cpp_type": "const char*" + } + ], + "return_type": "int", + "nautilus_return": "INT32", + "build_temporal_point": true, + "comment_one_liner": "Per-event ever-touches between a single-instant tgeompoint and a static geometry." + } + ] +} diff --git a/tools/codegen/codegen_nebula.py b/tools/codegen/codegen_nebula.py new file mode 100644 index 0000000000..8744d8463b --- /dev/null +++ b/tools/codegen/codegen_nebula.py @@ -0,0 +1,5091 @@ +#!/usr/bin/env python3 +"""MobilityNebula MEOS-operator generator. + +Given a JSON descriptor list of MEOS scalar functions to wrap as +NebulaStream operators, emits the 4 pipeline-layer C++ files per +function (logical .hpp/.cpp + physical .hpp/.cpp) following the +established style of the existing hand-written operators (e.g. +TemporalEDWithinGeometryLogicalFunction), AND auto-injects: + +- per-op CMakeLists.txt entries in nes-{logical,physical}-operators/ + src/Functions/Meos/ +- AntlrSQL.g4 lexer-token + functionName-alternation entries +- AntlrSQLQueryPlanCreator.cpp #include + dispatch-case block + +Injection is idempotent — markers like +`/* BEGIN CODEGEN PARSER GLUE: */ … /* END CODEGEN PARSER GLUE */` +gate each per-op block, and the script skips on re-run when the marker +is already present. + +Usage: + python3 codegen_nebula.py --input codegen_input.example.json \\ + --output-root /path/to/MobilityNebula \\ + [--no-parser-glue] # skip .g4 + parser .cpp + [--no-cmake-entries] # skip CMakeLists.txt +""" +import argparse +import json +import re +import sys +from pathlib import Path + +# =========================================================================== +# Templates (mirror the hand-written TemporalEDWithinGeometry style 1:1). +# =========================================================================== + +LOGICAL_HPP_TEMPLATE = """\ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#pragma once + +#include +#include +#include +#include + +namespace NES {{ + +/** + * @brief {comment_one_liner} + * + * Generated by tools/codegen/codegen_nebula.py from the MEOS function + * `{meos_call}`. Per-event scalar operator following the + * TemporalEDWithinGeometry pattern. + */ +class {nebula_name}LogicalFunction : public LogicalFunctionConcept {{ +public: + static constexpr std::string_view NAME = "{nebula_name}"; + + {nebula_name}LogicalFunction({ctor_logical_args}); + + DataType getDataType() const override; + LogicalFunction withDataType(const DataType& dataType) const override; + std::vector getChildren() const override; + LogicalFunction withChildren(const std::vector& children) const override; + std::string_view getType() const override; + bool operator==(const LogicalFunctionConcept& rhs) const override; + std::string explain(ExplainVerbosity verbosity) const override; + LogicalFunction withInferredDataType(const Schema& schema) const override; + SerializableFunction serialize() const override; + +private: + DataType dataType; + std::vector parameters; +}}; + +}} // namespace NES +""" + +LOGICAL_CPP_TEMPLATE = """\ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace NES +{{ + +{nebula_name}LogicalFunction::{nebula_name}LogicalFunction({ctor_logical_args}) + : dataType(DataTypeProvider::provideDataType(DataType::Type::{nautilus_return})) +{{ + parameters.reserve({n_args}); +{ctor_logical_pushes} +}} + +DataType {nebula_name}LogicalFunction::getDataType() const +{{ + return dataType; +}} + +LogicalFunction {nebula_name}LogicalFunction::withDataType(const DataType& newDataType) const +{{ + auto copy = *this; + copy.dataType = newDataType; + return copy; +}} + +std::vector {nebula_name}LogicalFunction::getChildren() const +{{ + return parameters; +}} + +LogicalFunction {nebula_name}LogicalFunction::withChildren(const std::vector& children) const +{{ + PRECONDITION(children.size() == {n_args}, "{nebula_name}LogicalFunction requires {n_args} children, but got {{}}", children.size()); + auto copy = *this; + copy.parameters = children; + return copy; +}} + +std::string_view {nebula_name}LogicalFunction::getType() const +{{ + return NAME; +}} + +bool {nebula_name}LogicalFunction::operator==(const LogicalFunctionConcept& rhs) const +{{ + if (const auto* other = dynamic_cast(&rhs)) + {{ + return parameters == other->parameters; + }} + return false; +}} + +std::string {nebula_name}LogicalFunction::explain(ExplainVerbosity verbosity) const +{{ + std::string args; + for (size_t index = 0; index < parameters.size(); ++index) + {{ + if (index > 0) + {{ + args += ", "; + }} + args += parameters[index].explain(verbosity); + }} + return fmt::format("{{}}({{}})", NAME, args); +}} + +LogicalFunction {nebula_name}LogicalFunction::withInferredDataType(const Schema& schema) const +{{ + std::vector newChildren; + newChildren.reserve(parameters.size()); + for (const auto& child : parameters) + {{ + newChildren.emplace_back(child.withInferredDataType(schema)); + }} + return withChildren(newChildren); +}} + +SerializableFunction {nebula_name}LogicalFunction::serialize() const +{{ + SerializableFunction proto; + proto.set_function_type(std::string(NAME)); + DataTypeSerializationUtil::serializeDataType(dataType, proto.mutable_data_type()); + for (const auto& child : parameters) + {{ + proto.add_children()->CopyFrom(child.serialize()); + }} + return proto; +}} + +LogicalFunctionRegistryReturnType LogicalFunctionGeneratedRegistrar::Register{nebula_name}LogicalFunction( + LogicalFunctionRegistryArguments arguments) +{{ + PRECONDITION(arguments.children.size() == {n_args}, + "{nebula_name}LogicalFunction requires {n_args} children but got {{}}", + arguments.children.size()); +{registrar_pushes} +}} + +}} // namespace NES +""" + +PHYSICAL_HPP_TEMPLATE = """\ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#pragma once + +#include +#include +#include +#include + +namespace NES {{ + +/** + * @brief Physical operator for `{meos_call}`. + * + * {comment_one_liner} + * + * Generated by tools/codegen/codegen_nebula.py. + */ +class {nebula_name}PhysicalFunction : public PhysicalFunctionConcept {{ +public: + {nebula_name}PhysicalFunction({ctor_physical_args}); + + VarVal execute(const Record& record, ArenaRef& arena) const override; + +private: + std::vector parameterFunctions; +}}; + +}} // namespace NES +""" + +# Physical .cpp template; the `body` placeholder is the MEOS-call body +# (the heart of the operator). For `build_temporal_point` operators +# we emit a per-event temporal-point build + MEOS call, mirroring +# TemporalEDWithinGeometry; for non-temporal-point operators (future +# templates) the body shape differs and a separate template branch +# would be added here. +PHYSICAL_CPP_TEMPLATE_TEMPORAL_POINT = """\ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" {{ +#include +#include +}} + +namespace NES {{ + +{nebula_name}PhysicalFunction::{nebula_name}PhysicalFunction({ctor_physical_args}) +{{ + parameterFunctions.reserve({n_args}); +{ctor_physical_pushes} +}} + +VarVal {nebula_name}PhysicalFunction::execute(const Record& record, ArenaRef& arena) const +{{ + std::vector parameterValues; + parameterValues.reserve(parameterFunctions.size()); + for (const auto& function : parameterFunctions) + {{ + parameterValues.emplace_back(function.execute(record, arena)); + }} + + auto lon = parameterValues[0].cast>(); + auto lat = parameterValues[1].cast>(); + auto timestamp = parameterValues[2].cast>(); + auto geometry = parameterValues[3].cast(); + + const auto result = nautilus::invoke( + +[](double lonValue, + double latValue, + uint64_t timestampValue, + const char* geometryPtr, + uint32_t geometrySize) -> {return_type} {{ + try + {{ + MEOS::Meos::ensureMeosInitialized(); + if (!(lonValue >= -180.0 && lonValue <= 180.0 && latValue >= -90.0 && latValue <= 90.0)) {{ + return 0; + }} + + const std::string timestampString = MEOS::Meos::convertEpochToTimestamp(timestampValue); + std::string temporalGeometryWkt = fmt::format("SRID=4326;Point({{}} {{}})@{{}}", lonValue, latValue, timestampString); + std::string staticGeometryWkt(geometryPtr, geometrySize); + + while (!staticGeometryWkt.empty() && (staticGeometryWkt.front() == '\\'' || staticGeometryWkt.front() == '"')) + staticGeometryWkt.erase(staticGeometryWkt.begin()); + while (!staticGeometryWkt.empty() && (staticGeometryWkt.back() == '\\'' || staticGeometryWkt.back() == '"')) + staticGeometryWkt.pop_back(); + + if (temporalGeometryWkt.empty() || staticGeometryWkt.empty()) + return 0; + + MEOS::Meos::TemporalGeometry temporalGeometry(temporalGeometryWkt); + if (!temporalGeometry.getGeometry()) return 0; + MEOS::Meos::StaticGeometry staticGeometry(staticGeometryWkt); + if (!staticGeometry.getGeometry()) return 0; + + // MEOS spatial-relation call — same shape as TemporalEDWithin's + // edwithin_tgeo_geo, but specific MEOS function per generated operator. + // Real MEOS spatial-rel signature: int fn(const Temporal *, const GSERIALIZED *) + // (no `atstart` flag — that's specific to geog_dwithin / edwithin's 3-arg variant). + return {meos_call}(temporalGeometry.getGeometry(), + staticGeometry.getGeometry()); + }} + catch (const std::exception&) + {{ + return 0; + }} + }}, + lon, lat, timestamp, geometry.getContent(), geometry.getContentSize()); + + return VarVal(result); +}} + +PhysicalFunctionRegistryReturnType PhysicalFunctionGeneratedRegistrar::Register{nebula_name}PhysicalFunction( + PhysicalFunctionRegistryArguments arguments) +{{ + PRECONDITION(arguments.childFunctions.size() == {n_args}, + "{nebula_name}PhysicalFunction requires {n_args} children but got {{}}", + arguments.childFunctions.size()); +{registrar_pushes} +}} + +}} // namespace NES +""" + +# Physical .cpp template for two-temporal-points operators (e.g. *_tgeo_tgeo +# spatial-relations). Two single-instant tgeompoints are built from event +# fields (lonA/latA/tsA + lonB/latB/tsB) and passed to a MEOS function whose +# C signature is `int fn(const Temporal*, const Temporal*)`. Mirrors the +# one-temporal-point template above; the bodies differ only in arg shape +# and in the absence of a static-geometry argument. +PHYSICAL_CPP_TEMPLATE_TWO_TEMPORAL_POINTS = """\ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" {{ +#include +#include +}} + +namespace NES {{ + +{nebula_name}PhysicalFunction::{nebula_name}PhysicalFunction({ctor_physical_args}) +{{ + parameterFunctions.reserve({n_args}); +{ctor_physical_pushes} +}} + +VarVal {nebula_name}PhysicalFunction::execute(const Record& record, ArenaRef& arena) const +{{ + std::vector parameterValues; + parameterValues.reserve(parameterFunctions.size()); + for (const auto& function : parameterFunctions) + {{ + parameterValues.emplace_back(function.execute(record, arena)); + }} + + auto lonA = parameterValues[0].cast>(); + auto latA = parameterValues[1].cast>(); + auto tsA = parameterValues[2].cast>(); + auto lonB = parameterValues[3].cast>(); + auto latB = parameterValues[4].cast>(); + auto tsB = parameterValues[5].cast>(); + + const auto result = nautilus::invoke( + +[](double lonAValue, double latAValue, uint64_t tsAValue, + double lonBValue, double latBValue, uint64_t tsBValue) -> {return_type} {{ + try + {{ + MEOS::Meos::ensureMeosInitialized(); + if (!(lonAValue >= -180.0 && lonAValue <= 180.0 && latAValue >= -90.0 && latAValue <= 90.0)) return 0; + if (!(lonBValue >= -180.0 && lonBValue <= 180.0 && latBValue >= -90.0 && latBValue <= 90.0)) return 0; + + const std::string tsAString = MEOS::Meos::convertEpochToTimestamp(tsAValue); + const std::string tsBString = MEOS::Meos::convertEpochToTimestamp(tsBValue); + std::string temporalGeometryAWkt = fmt::format("SRID=4326;Point({{}} {{}})@{{}}", lonAValue, latAValue, tsAString); + std::string temporalGeometryBWkt = fmt::format("SRID=4326;Point({{}} {{}})@{{}}", lonBValue, latBValue, tsBString); + + MEOS::Meos::TemporalGeometry temporalGeometryA(temporalGeometryAWkt); + if (!temporalGeometryA.getGeometry()) return 0; + MEOS::Meos::TemporalGeometry temporalGeometryB(temporalGeometryBWkt); + if (!temporalGeometryB.getGeometry()) return 0; + + // MEOS *_tgeo_tgeo spatial-relation: int fn(const Temporal*, const Temporal*). + return {meos_call}(temporalGeometryA.getGeometry(), + temporalGeometryB.getGeometry()); + }} + catch (const std::exception&) + {{ + return 0; + }} + }}, + lonA, latA, tsA, lonB, latB, tsB); + + return VarVal(result); +}} + +PhysicalFunctionRegistryReturnType PhysicalFunctionGeneratedRegistrar::Register{nebula_name}PhysicalFunction( + PhysicalFunctionRegistryArguments arguments) +{{ + PRECONDITION(arguments.childFunctions.size() == {n_args}, + "{nebula_name}PhysicalFunction requires {n_args} children but got {{}}", + arguments.childFunctions.size()); +{registrar_pushes} +}} + +}} // namespace NES +""" + + +# Physical .cpp template for one-temporal-point operators with a trailing +# `double dist` argument (e.g. edwithin_tgeo_geo / adwithin_tgeo_geo). Same +# layout as PHYSICAL_CPP_TEMPLATE_TEMPORAL_POINT but the MEOS call passes +# `dist` as the 3rd argument. +PHYSICAL_CPP_TEMPLATE_TEMPORAL_POINT_WITH_DIST = """\ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" {{ +#include +#include +}} + +namespace NES {{ + +{nebula_name}PhysicalFunction::{nebula_name}PhysicalFunction({ctor_physical_args}) +{{ + parameterFunctions.reserve({n_args}); +{ctor_physical_pushes} +}} + +VarVal {nebula_name}PhysicalFunction::execute(const Record& record, ArenaRef& arena) const +{{ + std::vector parameterValues; + parameterValues.reserve(parameterFunctions.size()); + for (const auto& function : parameterFunctions) + {{ + parameterValues.emplace_back(function.execute(record, arena)); + }} + + auto lon = parameterValues[0].cast>(); + auto lat = parameterValues[1].cast>(); + auto timestamp = parameterValues[2].cast>(); + auto geometry = parameterValues[3].cast(); + auto dist = parameterValues[4].cast>(); + + const auto result = nautilus::invoke( + +[](double lonValue, + double latValue, + uint64_t timestampValue, + const char* geometryPtr, + uint32_t geometrySize, + double distValue) -> {return_type} {{ + try + {{ + MEOS::Meos::ensureMeosInitialized(); + if (!(lonValue >= -180.0 && lonValue <= 180.0 && latValue >= -90.0 && latValue <= 90.0)) return 0; + + const std::string timestampString = MEOS::Meos::convertEpochToTimestamp(timestampValue); + std::string temporalGeometryWkt = fmt::format("SRID=4326;Point({{}} {{}})@{{}}", lonValue, latValue, timestampString); + std::string staticGeometryWkt(geometryPtr, geometrySize); + + while (!staticGeometryWkt.empty() && (staticGeometryWkt.front() == '\\'' || staticGeometryWkt.front() == '"')) + staticGeometryWkt.erase(staticGeometryWkt.begin()); + while (!staticGeometryWkt.empty() && (staticGeometryWkt.back() == '\\'' || staticGeometryWkt.back() == '"')) + staticGeometryWkt.pop_back(); + + if (temporalGeometryWkt.empty() || staticGeometryWkt.empty()) return 0; + + MEOS::Meos::TemporalGeometry temporalGeometry(temporalGeometryWkt); + if (!temporalGeometry.getGeometry()) return 0; + MEOS::Meos::StaticGeometry staticGeometry(staticGeometryWkt); + if (!staticGeometry.getGeometry()) return 0; + + // MEOS *_tgeo_geo with trailing distance arg + // — int fn(const Temporal*, const GSERIALIZED*, double). + return {meos_call}(temporalGeometry.getGeometry(), + staticGeometry.getGeometry(), + distValue); + }} + catch (const std::exception&) + {{ + return 0; + }} + }}, + lon, lat, timestamp, geometry.getContent(), geometry.getContentSize(), dist); + + return VarVal(result); +}} + +PhysicalFunctionRegistryReturnType PhysicalFunctionGeneratedRegistrar::Register{nebula_name}PhysicalFunction( + PhysicalFunctionRegistryArguments arguments) +{{ + PRECONDITION(arguments.childFunctions.size() == {n_args}, + "{nebula_name}PhysicalFunction requires {n_args} children but got {{}}", + arguments.childFunctions.size()); +{registrar_pushes} +}} + +}} // namespace NES +""" + +# Physical .cpp template for two-temporal-points operators with a trailing +# `double dist` argument (edwithin_tgeo_tgeo / adwithin_tgeo_tgeo). +PHYSICAL_CPP_TEMPLATE_TWO_TEMPORAL_POINTS_WITH_DIST = """\ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" {{ +#include +#include +}} + +namespace NES {{ + +{nebula_name}PhysicalFunction::{nebula_name}PhysicalFunction({ctor_physical_args}) +{{ + parameterFunctions.reserve({n_args}); +{ctor_physical_pushes} +}} + +VarVal {nebula_name}PhysicalFunction::execute(const Record& record, ArenaRef& arena) const +{{ + std::vector parameterValues; + parameterValues.reserve(parameterFunctions.size()); + for (const auto& function : parameterFunctions) + {{ + parameterValues.emplace_back(function.execute(record, arena)); + }} + + auto lonA = parameterValues[0].cast>(); + auto latA = parameterValues[1].cast>(); + auto tsA = parameterValues[2].cast>(); + auto lonB = parameterValues[3].cast>(); + auto latB = parameterValues[4].cast>(); + auto tsB = parameterValues[5].cast>(); + auto dist = parameterValues[6].cast>(); + + const auto result = nautilus::invoke( + +[](double lonAValue, double latAValue, uint64_t tsAValue, + double lonBValue, double latBValue, uint64_t tsBValue, + double distValue) -> {return_type} {{ + try + {{ + MEOS::Meos::ensureMeosInitialized(); + if (!(lonAValue >= -180.0 && lonAValue <= 180.0 && latAValue >= -90.0 && latAValue <= 90.0)) return 0; + if (!(lonBValue >= -180.0 && lonBValue <= 180.0 && latBValue >= -90.0 && latBValue <= 90.0)) return 0; + + const std::string tsAString = MEOS::Meos::convertEpochToTimestamp(tsAValue); + const std::string tsBString = MEOS::Meos::convertEpochToTimestamp(tsBValue); + std::string temporalGeometryAWkt = fmt::format("SRID=4326;Point({{}} {{}})@{{}}", lonAValue, latAValue, tsAString); + std::string temporalGeometryBWkt = fmt::format("SRID=4326;Point({{}} {{}})@{{}}", lonBValue, latBValue, tsBString); + + MEOS::Meos::TemporalGeometry temporalGeometryA(temporalGeometryAWkt); + if (!temporalGeometryA.getGeometry()) return 0; + MEOS::Meos::TemporalGeometry temporalGeometryB(temporalGeometryBWkt); + if (!temporalGeometryB.getGeometry()) return 0; + + // MEOS *_tgeo_tgeo with trailing distance arg + // — int fn(const Temporal*, const Temporal*, double). + return {meos_call}(temporalGeometryA.getGeometry(), + temporalGeometryB.getGeometry(), + distValue); + }} + catch (const std::exception&) + {{ + return 0; + }} + }}, + lonA, latA, tsA, lonB, latB, tsB, dist); + + return VarVal(result); +}} + +PhysicalFunctionRegistryReturnType PhysicalFunctionGeneratedRegistrar::Register{nebula_name}PhysicalFunction( + PhysicalFunctionRegistryArguments arguments) +{{ + PRECONDITION(arguments.childFunctions.size() == {n_args}, + "{nebula_name}PhysicalFunction requires {n_args} children but got {{}}", + arguments.childFunctions.size()); +{registrar_pushes} +}} + +}} // namespace NES +""" + + +# =========================================================================== +# Trgeometry operator templates (W148/W149 wave). +# +# Physical CPP builders for the 5 trgeometry spatial-predicate layouts +# plus 2 NAD variants. Called directly from emit_operator() instead of +# being .format()-expanded, because the C++ bodies contain literal braces. +# +# Logical HPP/CPP: two style variants ("compact" = Eintersects style, +# "nad" = NadTrgeometry / AlwaysEq / EverEq multi-line style). +# =========================================================================== + +_TRGEO_LICENSE = """\ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/""" + +_TRGEO_PHYS_INCLUDES_VS = """\ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include """ + +_TRGEO_PHYS_INCLUDES_NO_VS = """\ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include """ + + +def _trgeo_phys_header(nebula_name, includes): + return ( + _TRGEO_LICENSE + "\n\n" + + f"#include \n" + + includes + "\n\n" + + "extern \"C\" {\n" + + "#include \n" + + "#include \n" + + "#include \n" + + "#include \n" + + "}\n\n" + + "namespace NES {\n\n" + ) + + +def _trgeo_reg_compact(nebula_name, n): + """Registrar block: compact style (== without spaces, 34-space arg indent).""" + pad = " " + args = "\n".join( + pad + f"std::move(arguments.childFunctions[{i}])" + ("," if i < n - 1 else ")") + for i in range(n) + ) + return ( + f"\nPhysicalFunctionRegistryReturnType PhysicalFunctionGeneratedRegistrar::" + f"Register{nebula_name}PhysicalFunction(\n" + f" PhysicalFunctionRegistryArguments arguments)\n" + f"{{\n" + f" PRECONDITION(arguments.childFunctions.size()=={n},\n" + f" \"{nebula_name}PhysicalFunction requires {n} children but got {{}}\",\n" + f" arguments.childFunctions.size());\n" + f" return {nebula_name}PhysicalFunction(\n" + f"{args};\n" + f"}}\n\n" + f"}} // namespace NES\n" + ) + + +def _trgeo_reg_nad(nebula_name, n): + """Registrar block: nad/multiline style (== with spaces, 34-space indent).""" + pad = " " + args = "\n".join( + pad + f"std::move(arguments.childFunctions[{i}])" + ("," if i < n - 1 else ")") + for i in range(n) + ) + return ( + f"\nPhysicalFunctionRegistryReturnType PhysicalFunctionGeneratedRegistrar::" + f"Register{nebula_name}PhysicalFunction(\n" + f" PhysicalFunctionRegistryArguments arguments)\n" + f"{{\n" + f" PRECONDITION(arguments.childFunctions.size() == {n},\n" + f" \"{nebula_name}PhysicalFunction requires {n} children but got {{}}\",\n" + f" arguments.childFunctions.size());\n" + f" return {nebula_name}PhysicalFunction(\n" + f"{args};\n" + f"}}\n\n" + f"}} // namespace NES\n" + ) + + +# ---------- Logical HPP helpers ---------------------------------------- + +def _trgeo_logical_hpp_compact(nebula_name, brief, ctor_args): + ctor = ", ".join(f"LogicalFunction {a}" for a in ctor_args) + return ( + _TRGEO_LICENSE + "\n\n" + "#pragma once\n\n" + "#include \n" + "#include \n" + "#include \n" + "#include \n\n" + "namespace NES {\n\n" + "/**\n" + f" * @brief {brief}\n" + " */\n" + f"class {nebula_name}LogicalFunction : public LogicalFunctionConcept {{\n" + "public:\n" + f" static constexpr std::string_view NAME = \"{nebula_name}\";\n" + f" {nebula_name}LogicalFunction({ctor});\n" + " DataType getDataType() const override;\n" + " LogicalFunction withDataType(const DataType& dataType) const override;\n" + " std::vector getChildren() const override;\n" + " LogicalFunction withChildren(const std::vector& children) const override;\n" + " std::string_view getType() const override;\n" + " bool operator==(const LogicalFunctionConcept& rhs) const override;\n" + " std::string explain(ExplainVerbosity verbosity) const override;\n" + " LogicalFunction withInferredDataType(const Schema& schema) const override;\n" + " SerializableFunction serialize() const override;\n" + "private:\n" + " DataType dataType;\n" + " std::vector parameters;\n" + "};\n\n" + "} // namespace NES\n" + ) + + +def _trgeo_logical_hpp_nad(nebula_name, brief, ctor_args): + ctor = ", ".join(f"LogicalFunction {a}" for a in ctor_args) + return ( + _TRGEO_LICENSE + "\n\n" + "#pragma once\n\n" + "#include \n" + "#include \n" + "#include \n" + "#include \n\n" + "namespace NES {\n\n" + "/**\n" + f" * @brief {brief}\n" + " */\n" + f"class {nebula_name}LogicalFunction : public LogicalFunctionConcept {{\n" + "public:\n" + f" static constexpr std::string_view NAME = \"{nebula_name}\";\n\n" + f" {nebula_name}LogicalFunction({ctor});\n\n" + " DataType getDataType() const override;\n" + " LogicalFunction withDataType(const DataType& dataType) const override;\n" + " std::vector getChildren() const override;\n" + " LogicalFunction withChildren(const std::vector& children) const override;\n" + " std::string_view getType() const override;\n" + " bool operator==(const LogicalFunctionConcept& rhs) const override;\n" + " std::string explain(ExplainVerbosity verbosity) const override;\n" + " LogicalFunction withInferredDataType(const Schema& schema) const override;\n" + " SerializableFunction serialize() const override;\n\n" + "private:\n" + " DataType dataType;\n" + " std::vector parameters;\n" + "};\n\n" + "} // namespace NES\n" + ) + + +# ---------- Logical CPP helpers ---------------------------------------- + +def _trgeo_logical_cpp_compact(nebula_name, ctor_args, invariants, ret="FLOAT64"): + n = len(ctor_args) + ctor = ", ".join(f"LogicalFunction {a}" for a in ctor_args) + pushes = "\n".join(f" parameters.push_back(std::move({a}));" for a in ctor_args) + inv = "\n".join( + f" INVARIANT(c[{i}].getDataType().isType(DataType::Type::{t}), \"{nm} must be {t}\");" + for i, t, nm in invariants + ) + reg_args = "\n".join( + " std::move(arguments.children[" + str(i) + "])" + + ("," if i < n - 1 else ")") + for i in range(n) + ) + return ( + _TRGEO_LICENSE + "\n\n" + f"#include \n" + "#include \n" + "#include \n" + "#include \n" + "#include \n" + "#include \n" + "#include \n" + "#include \n" + "#include \n\n" + "namespace NES\n{\n\n" + f"{nebula_name}LogicalFunction::{nebula_name}LogicalFunction({ctor})\n" + f" : dataType(DataTypeProvider::provideDataType(DataType::Type::{ret}))\n" + "{\n" + f" parameters.reserve({n});\n" + f"{pushes}\n" + "}\n" + f"DataType {nebula_name}LogicalFunction::getDataType() const {{ return dataType; }}\n" + f"LogicalFunction {nebula_name}LogicalFunction::withDataType(const DataType& d) const {{ auto c=*this; c.dataType=d; return c; }}\n" + f"std::vector {nebula_name}LogicalFunction::getChildren() const {{ return parameters; }}\n" + f"LogicalFunction {nebula_name}LogicalFunction::withChildren(const std::vector& children) const {{\n" + f" PRECONDITION(children.size()=={n},\"{nebula_name}LogicalFunction requires {n} children, but got {{}}\",children.size());\n" + f" auto c=*this; c.parameters=children; return c;\n" + "}\n" + f"std::string_view {nebula_name}LogicalFunction::getType() const {{ return NAME; }}\n" + f"bool {nebula_name}LogicalFunction::operator==(const LogicalFunctionConcept& rhs) const {{\n" + f" if (const auto* o=dynamic_cast(&rhs)) return parameters==o->parameters;\n" + " return false;\n" + "}\n" + f"std::string {nebula_name}LogicalFunction::explain(ExplainVerbosity v) const {{\n" + " return fmt::format(\"{}({})\",NAME,parameters[0].explain(v));\n" + "}\n" + f"LogicalFunction {nebula_name}LogicalFunction::withInferredDataType(const Schema& schema) const {{\n" + f" std::vector c; c.reserve({n});\n" + " for (const auto& p : parameters) c.emplace_back(p.withInferredDataType(schema));\n" + f"{inv}\n" + " return withChildren(c);\n" + "}\n" + f"SerializableFunction {nebula_name}LogicalFunction::serialize() const {{\n" + " SerializableFunction proto;\n" + " proto.set_function_type(std::string(NAME));\n" + " DataTypeSerializationUtil::serializeDataType(dataType, proto.mutable_data_type());\n" + " for (const auto& ch : parameters) proto.add_children()->CopyFrom(ch.serialize());\n" + " return proto;\n" + "}\n" + f"LogicalFunctionRegistryReturnType LogicalFunctionGeneratedRegistrar::Register{nebula_name}LogicalFunction(\n" + " LogicalFunctionRegistryArguments arguments)\n" + "{\n" + f" PRECONDITION(arguments.children.size()=={n},\n" + f" \"{nebula_name}LogicalFunction requires {n} children but got {{}}\",\n" + " arguments.children.size());\n" + f" return {nebula_name}LogicalFunction(\n" + f"{reg_args};\n" + "}\n\n" + "} // namespace NES\n" + ) + + +def _trgeo_logical_cpp_nad(nebula_name, ctor_args, invariants, ret="FLOAT64"): + n = len(ctor_args) + ctor = ", ".join(f"LogicalFunction {a}" for a in ctor_args) + pushes = "\n".join(f" parameters.push_back(std::move({a}));" for a in ctor_args) + inv = "\n".join( + f" INVARIANT(c[{i}].getDataType().isType(DataType::Type::{t}), \"{nm} must be {t}\");" + for i, t, nm in invariants + ) + reg_args = "\n".join( + " std::move(arguments.children[" + str(i) + "])" + + ("," if i < n - 1 else ")") + for i in range(n) + ) + return ( + _TRGEO_LICENSE + "\n\n" + f"#include \n\n" + "#include \n" + "#include \n" + "#include \n" + "#include \n" + "#include \n" + "#include \n" + "#include \n" + "#include \n\n" + "namespace NES\n{\n\n" + f"{nebula_name}LogicalFunction::{nebula_name}LogicalFunction({ctor})\n" + f" : dataType(DataTypeProvider::provideDataType(DataType::Type::{ret}))\n" + "{\n" + f" parameters.reserve({n});\n" + f"{pushes}\n" + "}\n\n" + f"DataType {nebula_name}LogicalFunction::getDataType() const {{ return dataType; }}\n\n" + f"LogicalFunction {nebula_name}LogicalFunction::withDataType(const DataType& newDataType) const\n" + "{\n" + " auto copy = *this; copy.dataType = newDataType; return copy;\n" + "}\n\n" + f"std::vector {nebula_name}LogicalFunction::getChildren() const {{ return parameters; }}\n\n" + f"LogicalFunction {nebula_name}LogicalFunction::withChildren(const std::vector& children) const\n" + "{\n" + f" PRECONDITION(children.size() == {n},\n" + f" \"{nebula_name}LogicalFunction requires {n} children, but got {{}}\", children.size());\n" + " auto copy = *this; copy.parameters = children; return copy;\n" + "}\n\n" + f"std::string_view {nebula_name}LogicalFunction::getType() const {{ return NAME; }}\n\n" + f"bool {nebula_name}LogicalFunction::operator==(const LogicalFunctionConcept& rhs) const\n" + "{\n" + f" if (const auto* other = dynamic_cast(&rhs))\n" + " return parameters == other->parameters;\n" + " return false;\n" + "}\n\n" + f"std::string {nebula_name}LogicalFunction::explain(ExplainVerbosity verbosity) const\n" + "{\n" + " return fmt::format(\"{}({})\", NAME, parameters[0].explain(verbosity));\n" + "}\n\n" + f"LogicalFunction {nebula_name}LogicalFunction::withInferredDataType(const Schema& schema) const\n" + "{\n" + f" std::vector c;\n" + f" c.reserve({n});\n" + " for (const auto& p : parameters)\n" + " c.emplace_back(p.withInferredDataType(schema));\n" + f"{inv}\n" + " return withChildren(c);\n" + "}\n\n" + f"SerializableFunction {nebula_name}LogicalFunction::serialize() const\n" + "{\n" + " SerializableFunction proto;\n" + " proto.set_function_type(std::string(NAME));\n" + " DataTypeSerializationUtil::serializeDataType(dataType, proto.mutable_data_type());\n" + " for (const auto& child : parameters)\n" + " proto.add_children()->CopyFrom(child.serialize());\n" + " return proto;\n" + "}\n\n" + f"LogicalFunctionRegistryReturnType LogicalFunctionGeneratedRegistrar::Register{nebula_name}LogicalFunction(\n" + " LogicalFunctionRegistryArguments arguments)\n" + "{\n" + f" PRECONDITION(arguments.children.size() == {n},\n" + f" \"{nebula_name}LogicalFunction requires {n} children but got {{}}\",\n" + " arguments.children.size());\n" + f" return {nebula_name}LogicalFunction(\n" + f"{reg_args};\n" + "}\n\n" + "} // namespace NES\n" + ) + + +# ---------- Physical HPP ----------------------------------------------- + +def _trgeo_physical_hpp(nebula_name, ctor_args): + ctor = ", ".join(f"PhysicalFunction {a}" for a in ctor_args) + return ( + _TRGEO_LICENSE + "\n\n" + "#pragma once\n\n" + "#include \n" + "#include \n" + "#include \n" + "#include \n\n" + "namespace NES {\n\n" + f"class {nebula_name}PhysicalFunction : public PhysicalFunctionConcept {{\n" + "public:\n" + f" {nebula_name}PhysicalFunction({ctor});\n" + " VarVal execute(const Record& record, ArenaRef& arena) const override;\n" + "private:\n" + " std::vector paramFns;\n" + "};\n\n" + "} // namespace NES\n" + ) + + +# ---------- Physical CPP builders -------------------------------------- +# Each returns a complete file string for a specific layout. + +def _trgeo_phys_cpp_trgeometry_geo(nebula_name, meos_call, ctor_args): + """Layout A: trgeometry_geo — VariableSizedData cast, trgeometryinst_make, compact registrar.""" + n = len(ctor_args) + ctor = ", ".join(f"PhysicalFunction {a}" for a in ctor_args) + pushes = "\n".join(f" paramFns.push_back(std::move({a}));" for a in ctor_args) + return ( + _trgeo_phys_header(nebula_name, _TRGEO_PHYS_INCLUDES_VS) + + f"{nebula_name}PhysicalFunction::{nebula_name}PhysicalFunction({ctor}) {{\n" + + f" paramFns.reserve({n});\n" + + pushes + "\n}\n\n" + + f"VarVal {nebula_name}PhysicalFunction::execute(const Record& record, ArenaRef& arena) const {{\n" + + " auto ref_wkt = paramFns[0].execute(record, arena).cast();\n" + + " auto x1 = paramFns[1].execute(record, arena).cast();\n" + + " auto y1 = paramFns[2].execute(record, arena).cast();\n" + + " auto theta1 = paramFns[3].execute(record, arena).cast();\n" + + " auto ts1 = paramFns[4].execute(record, arena).cast();\n" + + " auto tgt_wkt = paramFns[5].execute(record, arena).cast();\n" + + " const auto result = nautilus::invoke(\n" + + " +[](const char* ref_wkt, uint32_t ref_wktsz, double x1, double y1, double theta1, uint64_t ts1, const char* tgt_wkt, uint32_t tgt_wktsz) -> double {\n" + + " try {\n" + + " MEOS::Meos::ensureMeosInitialized();\n" + + " char* ref1_str = (char*)malloc(ref_wktsz + 1);\n" + + " memcpy(ref1_str, ref_wkt, ref_wktsz); ref1_str[ref_wktsz] = '\\0';\n" + + " GSERIALIZED* gref1 = geom_in(ref1_str, -1); free(ref1_str);\n" + + " if (!gref1) return 0.0;\n" + + " Pose* pose1 = pose_make_2d(x1, y1, theta1, false, 0);\n" + + " if (!pose1) { free(gref1); return 0.0; }\n" + + " TInstant* inst1 = trgeometryinst_make(gref1, pose1, (TimestampTz)ts1);\n" + + " free(gref1); free(pose1);\n" + + " if (!inst1) return 0.0;\n" + + " char* tgt_str = (char*)malloc(tgt_wktsz + 1);\n" + + " memcpy(tgt_str, tgt_wkt, tgt_wktsz); tgt_str[tgt_wktsz] = '\\0';\n" + + " GSERIALIZED* gs_tgt = geom_in(tgt_str, -1); free(tgt_str);\n" + + " if (!gs_tgt) { free(inst1); return 0.0; }\n" + + f" int r = {meos_call}((Temporal*)inst1, gs_tgt);\n" + + " free(inst1); free(gs_tgt);\n" + + " return r > 0 ? 1.0 : 0.0;\n" + + " } catch (const std::exception&) { return 0.0; }\n" + + " },\n" + + " ref_wkt, x1, y1, theta1, ts1, tgt_wkt);\n" + + " return VarVal(result);\n" + + "}\n" + + _trgeo_reg_compact(nebula_name, n) + ) + + +def _trgeo_phys_cpp_trgeometry_geo_nad(nebula_name, meos_call, ctor_args): + """Layout B: trgeometry_geo nad-style — no VariableSizedData cast, trgeoinst_make, nad registrar.""" + n = len(ctor_args) + ctor = ", ".join(f"PhysicalFunction {a}" for a in ctor_args) + pushes = "\n".join(f" paramFns.push_back(std::move({a}));" for a in ctor_args) + return ( + _trgeo_phys_header(nebula_name, _TRGEO_PHYS_INCLUDES_NO_VS) + + f"{nebula_name}PhysicalFunction::{nebula_name}PhysicalFunction({ctor})\n" + + "{\n" + + f" paramFns.reserve({n});\n" + + pushes + "\n}\n\n" + + f"VarVal {nebula_name}PhysicalFunction::execute(const Record& record, ArenaRef& arena) const\n" + + "{\n" + + " auto ref_wkt = paramFns[0].execute(record, arena);\n" + + " auto x = paramFns[1].execute(record, arena).cast();\n" + + " auto y = paramFns[2].execute(record, arena).cast();\n" + + " auto theta = paramFns[3].execute(record, arena).cast();\n" + + " auto ts = paramFns[4].execute(record, arena).cast();\n" + + " auto tgt_wkt = paramFns[5].execute(record, arena);\n" + + " const auto result = nautilus::invoke(\n" + + " +[](const char* ref_wkt, uint32_t ref_len, double x, double y, double theta, uint64_t ts, const char* tgt_wkt, uint32_t tgt_len) -> double {\n" + + " try {\n" + + " MEOS::Meos::ensureMeosInitialized();\n" + + " char* ref_str = (char*)malloc(ref_len + 1);\n" + + " memcpy(ref_str, ref_wkt, ref_len); ref_str[ref_len] = '\\0';\n" + + " GSERIALIZED* gs_ref = geom_in(ref_str, -1); free(ref_str);\n" + + " if (!gs_ref) return 0.0;\n" + + " Pose* pose = pose_make_2d(x, y, theta, false, 0);\n" + + " if (!pose) { free(gs_ref); return 0.0; }\n" + + " TInstant* inst = trgeoinst_make(gs_ref, pose, (TimestampTz)ts);\n" + + " free(gs_ref); free(pose);\n" + + " if (!inst) return 0.0;\n" + + " char* tgt_str = (char*)malloc(tgt_len + 1);\n" + + " memcpy(tgt_str, tgt_wkt, tgt_len); tgt_str[tgt_len] = '\\0';\n" + + " GSERIALIZED* gs_tgt = geom_in(tgt_str, -1); free(tgt_str);\n" + + " if (!gs_tgt) { free(inst); return 0.0; }\n" + + f" int r = {meos_call}((Temporal*)inst, gs_tgt);\n" + + " free(inst); free(gs_tgt);\n" + + " return r > 0 ? 1.0 : 0.0;\n" + + " } catch (const std::exception&) { return 0.0; }\n" + + " },\n" + + " ref_wkt, x, y, theta, ts, tgt_wkt);\n" + + " return VarVal(result);\n" + + "}\n" + + _trgeo_reg_nad(nebula_name, n) + ) + + +def _trgeo_phys_cpp_geo_trgeometry(nebula_name, meos_call, ctor_args): + """Layout C: geo_trgeometry — VariableSizedData cast, trgeometryinst_make, geo-first MEOS call.""" + n = len(ctor_args) + ctor = ", ".join(f"PhysicalFunction {a}" for a in ctor_args) + pushes = "\n".join(f" paramFns.push_back(std::move({a}));" for a in ctor_args) + return ( + _trgeo_phys_header(nebula_name, _TRGEO_PHYS_INCLUDES_VS) + + f"{nebula_name}PhysicalFunction::{nebula_name}PhysicalFunction({ctor}) {{\n" + + f" paramFns.reserve({n});\n" + + pushes + "\n}\n\n" + + f"VarVal {nebula_name}PhysicalFunction::execute(const Record& record, ArenaRef& arena) const {{\n" + + " auto tgt_wkt = paramFns[0].execute(record, arena).cast();\n" + + " auto ref_wkt = paramFns[1].execute(record, arena).cast();\n" + + " auto x1 = paramFns[2].execute(record, arena).cast();\n" + + " auto y1 = paramFns[3].execute(record, arena).cast();\n" + + " auto theta1 = paramFns[4].execute(record, arena).cast();\n" + + " auto ts1 = paramFns[5].execute(record, arena).cast();\n" + + " const auto result = nautilus::invoke(\n" + + " +[](const char* tgt_wkt, uint32_t tgt_wktsz, const char* ref_wkt, uint32_t ref_wktsz, double x1, double y1, double theta1, uint64_t ts1) -> double {\n" + + " try {\n" + + " MEOS::Meos::ensureMeosInitialized();\n" + + " char* ref1_str = (char*)malloc(ref_wktsz + 1);\n" + + " memcpy(ref1_str, ref_wkt, ref_wktsz); ref1_str[ref_wktsz] = '\\0';\n" + + " GSERIALIZED* gref1 = geom_in(ref1_str, -1); free(ref1_str);\n" + + " if (!gref1) return 0.0;\n" + + " Pose* pose1 = pose_make_2d(x1, y1, theta1, false, 0);\n" + + " if (!pose1) { free(gref1); return 0.0; }\n" + + " TInstant* inst1 = trgeometryinst_make(gref1, pose1, (TimestampTz)ts1);\n" + + " free(gref1); free(pose1);\n" + + " if (!inst1) return 0.0;\n" + + " char* tgt_str = (char*)malloc(tgt_wktsz + 1);\n" + + " memcpy(tgt_str, tgt_wkt, tgt_wktsz); tgt_str[tgt_wktsz] = '\\0';\n" + + " GSERIALIZED* gs_tgt = geom_in(tgt_str, -1); free(tgt_str);\n" + + " if (!gs_tgt) { free(inst1); return 0.0; }\n" + + f" int r = {meos_call}(gs_tgt, (Temporal*)inst1);\n" + + " free(inst1); free(gs_tgt);\n" + + " return r > 0 ? 1.0 : 0.0;\n" + + " } catch (const std::exception&) { return 0.0; }\n" + + " },\n" + + " tgt_wkt, ref_wkt, x1, y1, theta1, ts1);\n" + + " return VarVal(result);\n" + + "}\n" + + _trgeo_reg_compact(nebula_name, n) + ) + + +def _trgeo_phys_cpp_geo_trgeometry_eq(nebula_name, meos_call, ctor_args): + """Layout D: geo_trgeometry eq-style — trgeoinst_make, gs_tgt-first build order, nad registrar.""" + n = len(ctor_args) + ctor = ", ".join(f"PhysicalFunction {a}" for a in ctor_args) + pushes = "\n".join(f" paramFns.push_back(std::move({a}));" for a in ctor_args) + return ( + _trgeo_phys_header(nebula_name, _TRGEO_PHYS_INCLUDES_NO_VS) + + f"{nebula_name}PhysicalFunction::{nebula_name}PhysicalFunction({ctor})\n" + + "{\n" + + f" paramFns.reserve({n});\n" + + pushes + "\n}\n\n" + + f"VarVal {nebula_name}PhysicalFunction::execute(const Record& record, ArenaRef& arena) const\n" + + "{\n" + + " auto tgt_wkt = paramFns[0].execute(record, arena);\n" + + " auto ref_wkt = paramFns[1].execute(record, arena);\n" + + " auto x = paramFns[2].execute(record, arena).cast();\n" + + " auto y = paramFns[3].execute(record, arena).cast();\n" + + " auto theta = paramFns[4].execute(record, arena).cast();\n" + + " auto ts = paramFns[5].execute(record, arena).cast();\n" + + " const auto result = nautilus::invoke(\n" + + " +[](const char* tgt_wkt, uint32_t tgt_len, const char* ref_wkt, uint32_t ref_len, double x, double y, double theta, uint64_t ts) -> double {\n" + + " try {\n" + + " MEOS::Meos::ensureMeosInitialized();\n" + + " char* tgt_str = (char*)malloc(tgt_len + 1);\n" + + " memcpy(tgt_str, tgt_wkt, tgt_len); tgt_str[tgt_len] = '\\0';\n" + + " GSERIALIZED* gs_tgt = geom_in(tgt_str, -1); free(tgt_str);\n" + + " if (!gs_tgt) return 0.0;\n" + + " char* ref_str = (char*)malloc(ref_len + 1);\n" + + " memcpy(ref_str, ref_wkt, ref_len); ref_str[ref_len] = '\\0';\n" + + " GSERIALIZED* gs_ref = geom_in(ref_str, -1); free(ref_str);\n" + + " if (!gs_ref) { free(gs_tgt); return 0.0; }\n" + + " Pose* pose = pose_make_2d(x, y, theta, false, 0);\n" + + " if (!pose) { free(gs_tgt); free(gs_ref); return 0.0; }\n" + + " TInstant* inst = trgeoinst_make(gs_ref, pose, (TimestampTz)ts);\n" + + " free(gs_ref); free(pose);\n" + + " if (!inst) { free(gs_tgt); return 0.0; }\n" + + f" int r = {meos_call}(gs_tgt, (Temporal*)inst);\n" + + " free(gs_tgt); free(inst);\n" + + " return r > 0 ? 1.0 : 0.0;\n" + + " } catch (const std::exception&) { return 0.0; }\n" + + " },\n" + + " tgt_wkt, ref_wkt, x, y, theta, ts);\n" + + " return VarVal(result);\n" + + "}\n" + + _trgeo_reg_nad(nebula_name, n) + ) + + +def _trgeo_phys_cpp_trgeometry_geo_with_dist(nebula_name, meos_call, ctor_args): + """Layout E: trgeometry_geo_with_dist — 7 args, dist as 3rd MEOS arg.""" + n = len(ctor_args) + ctor = ", ".join(f"PhysicalFunction {a}" for a in ctor_args) + pushes = "\n".join(f" paramFns.push_back(std::move({a}));" for a in ctor_args) + return ( + _trgeo_phys_header(nebula_name, _TRGEO_PHYS_INCLUDES_VS) + + f"{nebula_name}PhysicalFunction::{nebula_name}PhysicalFunction({ctor}) {{\n" + + f" paramFns.reserve({n});\n" + + pushes + "\n}\n\n" + + f"VarVal {nebula_name}PhysicalFunction::execute(const Record& record, ArenaRef& arena) const {{\n" + + " auto ref_wkt = paramFns[0].execute(record, arena).cast();\n" + + " auto x1 = paramFns[1].execute(record, arena).cast();\n" + + " auto y1 = paramFns[2].execute(record, arena).cast();\n" + + " auto theta1 = paramFns[3].execute(record, arena).cast();\n" + + " auto ts1 = paramFns[4].execute(record, arena).cast();\n" + + " auto tgt_wkt = paramFns[5].execute(record, arena).cast();\n" + + " auto dist = paramFns[6].execute(record, arena).cast();\n" + + " const auto result = nautilus::invoke(\n" + + " +[](const char* ref_wkt, uint32_t ref_wktsz, double x1, double y1, double theta1, uint64_t ts1, const char* tgt_wkt, uint32_t tgt_wktsz, double dist) -> double {\n" + + " try {\n" + + " MEOS::Meos::ensureMeosInitialized();\n" + + " char* ref1_str = (char*)malloc(ref_wktsz + 1);\n" + + " memcpy(ref1_str, ref_wkt, ref_wktsz); ref1_str[ref_wktsz] = '\\0';\n" + + " GSERIALIZED* gref1 = geom_in(ref1_str, -1); free(ref1_str);\n" + + " if (!gref1) return 0.0;\n" + + " Pose* pose1 = pose_make_2d(x1, y1, theta1, false, 0);\n" + + " if (!pose1) { free(gref1); return 0.0; }\n" + + " TInstant* inst1 = trgeometryinst_make(gref1, pose1, (TimestampTz)ts1);\n" + + " free(gref1); free(pose1);\n" + + " if (!inst1) return 0.0;\n" + + " char* tgt_str = (char*)malloc(tgt_wktsz + 1);\n" + + " memcpy(tgt_str, tgt_wkt, tgt_wktsz); tgt_str[tgt_wktsz] = '\\0';\n" + + " GSERIALIZED* gs_tgt = geom_in(tgt_str, -1); free(tgt_str);\n" + + " if (!gs_tgt) { free(inst1); return 0.0; }\n" + + f" int r = {meos_call}((Temporal*)inst1, gs_tgt, dist);\n" + + " free(inst1); free(gs_tgt);\n" + + " return r > 0 ? 1.0 : 0.0;\n" + + " } catch (const std::exception&) { return 0.0; }\n" + + " },\n" + + " ref_wkt, x1, y1, theta1, ts1, tgt_wkt, dist);\n" + + " return VarVal(result);\n" + + "}\n" + + _trgeo_reg_compact(nebula_name, n) + ) + + +def _trgeo_phys_cpp_trgeometry_trgeometry(nebula_name, meos_call, ctor_args): + """Layout F: trgeometry_trgeometry — 10 args, VariableSizedData cast, trgeometryinst_make.""" + n = len(ctor_args) + ctor = ", ".join(f"PhysicalFunction {a}" for a in ctor_args) + pushes = "\n".join(f" paramFns.push_back(std::move({a}));" for a in ctor_args) + return ( + _trgeo_phys_header(nebula_name, _TRGEO_PHYS_INCLUDES_VS) + + f"{nebula_name}PhysicalFunction::{nebula_name}PhysicalFunction({ctor}) {{\n" + + f" paramFns.reserve({n});\n" + + pushes + "\n}\n\n" + + f"VarVal {nebula_name}PhysicalFunction::execute(const Record& record, ArenaRef& arena) const {{\n" + + " auto ref1_wkt = paramFns[0].execute(record, arena).cast();\n" + + " auto x1 = paramFns[1].execute(record, arena).cast();\n" + + " auto y1 = paramFns[2].execute(record, arena).cast();\n" + + " auto theta1 = paramFns[3].execute(record, arena).cast();\n" + + " auto ts1 = paramFns[4].execute(record, arena).cast();\n" + + " auto ref2_wkt = paramFns[5].execute(record, arena).cast();\n" + + " auto x2 = paramFns[6].execute(record, arena).cast();\n" + + " auto y2 = paramFns[7].execute(record, arena).cast();\n" + + " auto theta2 = paramFns[8].execute(record, arena).cast();\n" + + " auto ts2 = paramFns[9].execute(record, arena).cast();\n" + + " const auto result = nautilus::invoke(\n" + + " +[](const char* ref1_wkt, uint32_t ref1_wktsz, double x1, double y1, double theta1, uint64_t ts1, const char* ref2_wkt, uint32_t ref2_wktsz, double x2, double y2, double theta2, uint64_t ts2) -> double {\n" + + " try {\n" + + " MEOS::Meos::ensureMeosInitialized();\n" + + " char* ref1_str = (char*)malloc(ref1_wktsz + 1);\n" + + " memcpy(ref1_str, ref1_wkt, ref1_wktsz); ref1_str[ref1_wktsz] = '\\0';\n" + + " GSERIALIZED* gref1 = geom_in(ref1_str, -1); free(ref1_str);\n" + + " if (!gref1) return 0.0;\n" + + " Pose* pose1 = pose_make_2d(x1, y1, theta1, false, 0);\n" + + " if (!pose1) { free(gref1); return 0.0; }\n" + + " TInstant* inst1 = trgeometryinst_make(gref1, pose1, (TimestampTz)ts1);\n" + + " free(gref1); free(pose1);\n" + + " if (!inst1) return 0.0;\n" + + " char* ref2_str = (char*)malloc(ref2_wktsz + 1);\n" + + " memcpy(ref2_str, ref2_wkt, ref2_wktsz); ref2_str[ref2_wktsz] = '\\0';\n" + + " GSERIALIZED* gref2 = geom_in(ref2_str, -1); free(ref2_str);\n" + + " if (!gref2) return 0.0;\n" + + " Pose* pose2 = pose_make_2d(x2, y2, theta2, false, 0);\n" + + " if (!pose2) { free(gref2); return 0.0; }\n" + + " TInstant* inst2 = trgeometryinst_make(gref2, pose2, (TimestampTz)ts2);\n" + + " free(gref2); free(pose2);\n" + + " if (!inst2) { free(inst1); return 0.0; }\n" + + f" int r = {meos_call}((Temporal*)inst1, (Temporal*)inst2);\n" + + " free(inst1); free(inst2);\n" + + " return r > 0 ? 1.0 : 0.0;\n" + + " } catch (const std::exception&) { return 0.0; }\n" + + " },\n" + + " ref1_wkt, x1, y1, theta1, ts1, ref2_wkt, x2, y2, theta2, ts2);\n" + + " return VarVal(result);\n" + + "}\n" + + _trgeo_reg_compact(nebula_name, n) + ) + + +def _trgeo_phys_cpp_trgeometry_trgeometry_nad(nebula_name, meos_call, ctor_args): + """Layout G: trgeometry_trgeometry nad-style — trgeoinst_make, no cast, nad registrar.""" + n = len(ctor_args) + ctor = ", ".join(f"PhysicalFunction {a}" for a in ctor_args) + pushes = "\n".join(f" paramFns.push_back(std::move({a}));" for a in ctor_args) + return ( + _trgeo_phys_header(nebula_name, _TRGEO_PHYS_INCLUDES_NO_VS) + + f"{nebula_name}PhysicalFunction::{nebula_name}PhysicalFunction({ctor})\n" + + "{\n" + + f" paramFns.reserve({n});\n" + + pushes + "\n}\n\n" + + f"VarVal {nebula_name}PhysicalFunction::execute(const Record& record, ArenaRef& arena) const\n" + + "{\n" + + " auto ref1_wkt = paramFns[0].execute(record, arena);\n" + + " auto x1 = paramFns[1].execute(record, arena).cast();\n" + + " auto y1 = paramFns[2].execute(record, arena).cast();\n" + + " auto theta1 = paramFns[3].execute(record, arena).cast();\n" + + " auto ts1 = paramFns[4].execute(record, arena).cast();\n" + + " auto ref2_wkt = paramFns[5].execute(record, arena);\n" + + " auto x2 = paramFns[6].execute(record, arena).cast();\n" + + " auto y2 = paramFns[7].execute(record, arena).cast();\n" + + " auto theta2 = paramFns[8].execute(record, arena).cast();\n" + + " auto ts2 = paramFns[9].execute(record, arena).cast();\n" + + " const auto result = nautilus::invoke(\n" + + " +[](const char* ref1_wkt, uint32_t ref1_len, double x1, double y1, double theta1, uint64_t ts1, const char* ref2_wkt, uint32_t ref2_len, double x2, double y2, double theta2, uint64_t ts2) -> double {\n" + + " try {\n" + + " MEOS::Meos::ensureMeosInitialized();\n" + + " char* s1 = (char*)malloc(ref1_len + 1);\n" + + " memcpy(s1, ref1_wkt, ref1_len); s1[ref1_len] = '\\0';\n" + + " GSERIALIZED* gs1 = geom_in(s1, -1); free(s1);\n" + + " if (!gs1) return 0.0;\n" + + " Pose* p1 = pose_make_2d(x1, y1, theta1, false, 0);\n" + + " if (!p1) { free(gs1); return 0.0; }\n" + + " TInstant* inst1 = trgeoinst_make(gs1, p1, (TimestampTz)ts1);\n" + + " free(gs1); free(p1);\n" + + " if (!inst1) return 0.0;\n" + + " char* s2 = (char*)malloc(ref2_len + 1);\n" + + " memcpy(s2, ref2_wkt, ref2_len); s2[ref2_len] = '\\0';\n" + + " GSERIALIZED* gs2 = geom_in(s2, -1); free(s2);\n" + + " if (!gs2) { free(inst1); return 0.0; }\n" + + " Pose* p2 = pose_make_2d(x2, y2, theta2, false, 0);\n" + + " if (!p2) { free(inst1); free(gs2); return 0.0; }\n" + + " TInstant* inst2 = trgeoinst_make(gs2, p2, (TimestampTz)ts2);\n" + + " free(gs2); free(p2);\n" + + " if (!inst2) { free(inst1); return 0.0; }\n" + + f" int r = {meos_call}((Temporal*)inst1, (Temporal*)inst2);\n" + + " free(inst1); free(inst2);\n" + + " return r > 0 ? 1.0 : 0.0;\n" + + " } catch (const std::exception&) { return 0.0; }\n" + + " },\n" + + " ref1_wkt, x1, y1, theta1, ts1, ref2_wkt, x2, y2, theta2, ts2);\n" + + " return VarVal(result);\n" + + "}\n" + + _trgeo_reg_nad(nebula_name, n) + ) + + +def _trgeo_phys_cpp_trgeometry_trgeometry_with_dist(nebula_name, meos_call, ctor_args): + """Layout H: trgeometry_trgeometry_with_dist — 11 args, trgeometryinst_make.""" + n = len(ctor_args) + ctor = ", ".join(f"PhysicalFunction {a}" for a in ctor_args) + pushes = "\n".join(f" paramFns.push_back(std::move({a}));" for a in ctor_args) + return ( + _trgeo_phys_header(nebula_name, _TRGEO_PHYS_INCLUDES_VS) + + f"{nebula_name}PhysicalFunction::{nebula_name}PhysicalFunction({ctor}) {{\n" + + f" paramFns.reserve({n});\n" + + pushes + "\n}\n\n" + + f"VarVal {nebula_name}PhysicalFunction::execute(const Record& record, ArenaRef& arena) const {{\n" + + " auto ref1_wkt = paramFns[0].execute(record, arena).cast();\n" + + " auto x1 = paramFns[1].execute(record, arena).cast();\n" + + " auto y1 = paramFns[2].execute(record, arena).cast();\n" + + " auto theta1 = paramFns[3].execute(record, arena).cast();\n" + + " auto ts1 = paramFns[4].execute(record, arena).cast();\n" + + " auto ref2_wkt = paramFns[5].execute(record, arena).cast();\n" + + " auto x2 = paramFns[6].execute(record, arena).cast();\n" + + " auto y2 = paramFns[7].execute(record, arena).cast();\n" + + " auto theta2 = paramFns[8].execute(record, arena).cast();\n" + + " auto ts2 = paramFns[9].execute(record, arena).cast();\n" + + " auto dist = paramFns[10].execute(record, arena).cast();\n" + + " const auto result = nautilus::invoke(\n" + + " +[](const char* ref1_wkt, uint32_t ref1_wktsz, double x1, double y1, double theta1, uint64_t ts1, const char* ref2_wkt, uint32_t ref2_wktsz, double x2, double y2, double theta2, uint64_t ts2, double dist) -> double {\n" + + " try {\n" + + " MEOS::Meos::ensureMeosInitialized();\n" + + " char* ref1_str = (char*)malloc(ref1_wktsz + 1);\n" + + " memcpy(ref1_str, ref1_wkt, ref1_wktsz); ref1_str[ref1_wktsz] = '\\0';\n" + + " GSERIALIZED* gref1 = geom_in(ref1_str, -1); free(ref1_str);\n" + + " if (!gref1) return 0.0;\n" + + " Pose* pose1 = pose_make_2d(x1, y1, theta1, false, 0);\n" + + " if (!pose1) { free(gref1); return 0.0; }\n" + + " TInstant* inst1 = trgeometryinst_make(gref1, pose1, (TimestampTz)ts1);\n" + + " free(gref1); free(pose1);\n" + + " if (!inst1) return 0.0;\n" + + " char* ref2_str = (char*)malloc(ref2_wktsz + 1);\n" + + " memcpy(ref2_str, ref2_wkt, ref2_wktsz); ref2_str[ref2_wktsz] = '\\0';\n" + + " GSERIALIZED* gref2 = geom_in(ref2_str, -1); free(ref2_str);\n" + + " if (!gref2) return 0.0;\n" + + " Pose* pose2 = pose_make_2d(x2, y2, theta2, false, 0);\n" + + " if (!pose2) { free(gref2); return 0.0; }\n" + + " TInstant* inst2 = trgeometryinst_make(gref2, pose2, (TimestampTz)ts2);\n" + + " free(gref2); free(pose2);\n" + + " if (!inst2) { free(inst1); return 0.0; }\n" + + f" int r = {meos_call}((Temporal*)inst1, (Temporal*)inst2, dist);\n" + + " free(inst1); free(inst2);\n" + + " return r > 0 ? 1.0 : 0.0;\n" + + " } catch (const std::exception&) { return 0.0; }\n" + + " },\n" + + " ref1_wkt, x1, y1, theta1, ts1, ref2_wkt, x2, y2, theta2, ts2, dist);\n" + + " return VarVal(result);\n" + + "}\n" + + _trgeo_reg_compact(nebula_name, n) + ) + + +def _trgeo_phys_cpp_nad_trgeometry_geo(nebula_name, meos_call, ctor_args): + """Layout I: nad_trgeometry_geo — double return, trgeoinst_make, nad registrar.""" + n = len(ctor_args) + ctor = ", ".join(f"PhysicalFunction {a}" for a in ctor_args) + pushes = "\n".join(f" paramFns.push_back(std::move({a}));" for a in ctor_args) + return ( + _trgeo_phys_header(nebula_name, _TRGEO_PHYS_INCLUDES_NO_VS) + + f"{nebula_name}PhysicalFunction::{nebula_name}PhysicalFunction({ctor})\n" + + "{\n" + + f" paramFns.reserve({n});\n" + + pushes + "\n}\n\n" + + f"VarVal {nebula_name}PhysicalFunction::execute(const Record& record, ArenaRef& arena) const\n" + + "{\n" + + " auto ref_wkt = paramFns[0].execute(record, arena);\n" + + " auto x = paramFns[1].execute(record, arena).cast();\n" + + " auto y = paramFns[2].execute(record, arena).cast();\n" + + " auto theta = paramFns[3].execute(record, arena).cast();\n" + + " auto ts = paramFns[4].execute(record, arena).cast();\n" + + " auto tgt_wkt = paramFns[5].execute(record, arena);\n" + + " const auto result = nautilus::invoke(\n" + + " +[](const char* ref_wkt, uint32_t ref_len, double x, double y, double theta, uint64_t ts, const char* tgt_wkt, uint32_t tgt_len) -> double {\n" + + " try {\n" + + " MEOS::Meos::ensureMeosInitialized();\n" + + " char* ref_str = (char*)malloc(ref_len + 1);\n" + + " memcpy(ref_str, ref_wkt, ref_len); ref_str[ref_len] = '\\0';\n" + + " GSERIALIZED* gs_ref = geom_in(ref_str, -1); free(ref_str);\n" + + " if (!gs_ref) return 0.0;\n" + + " Pose* pose = pose_make_2d(x, y, theta, false, 0);\n" + + " if (!pose) { free(gs_ref); return 0.0; }\n" + + " TInstant* inst = trgeoinst_make(gs_ref, pose, (TimestampTz)ts);\n" + + " free(gs_ref); free(pose);\n" + + " if (!inst) return 0.0;\n" + + " char* tgt_str = (char*)malloc(tgt_len + 1);\n" + + " memcpy(tgt_str, tgt_wkt, tgt_len); tgt_str[tgt_len] = '\\0';\n" + + " GSERIALIZED* gs_tgt = geom_in(tgt_str, -1); free(tgt_str);\n" + + " if (!gs_tgt) { free(inst); return 0.0; }\n" + + f" double r = {meos_call}((Temporal*)inst, gs_tgt);\n" + + " free(inst); free(gs_tgt);\n" + + " return r;\n" + + " } catch (const std::exception&) { return 0.0; }\n" + + " },\n" + + " ref_wkt, x, y, theta, ts, tgt_wkt);\n" + + " return VarVal(result);\n" + + "}\n" + + _trgeo_reg_nad(nebula_name, n) + ) + + +def _trgeo_phys_cpp_nad_trgeometry_trgeometry(nebula_name, meos_call, ctor_args): + """Layout J: nad_trgeometry_trgeometry — 10 args, double return, trgeoinst_make.""" + n = len(ctor_args) + ctor = ", ".join(f"PhysicalFunction {a}" for a in ctor_args) + pushes = "\n".join(f" paramFns.push_back(std::move({a}));" for a in ctor_args) + return ( + _trgeo_phys_header(nebula_name, _TRGEO_PHYS_INCLUDES_NO_VS) + + f"{nebula_name}PhysicalFunction::{nebula_name}PhysicalFunction({ctor})\n" + + "{\n" + + f" paramFns.reserve({n});\n" + + pushes + "\n}\n\n" + + f"VarVal {nebula_name}PhysicalFunction::execute(const Record& record, ArenaRef& arena) const\n" + + "{\n" + + " auto ref1_wkt = paramFns[0].execute(record, arena);\n" + + " auto x1 = paramFns[1].execute(record, arena).cast();\n" + + " auto y1 = paramFns[2].execute(record, arena).cast();\n" + + " auto theta1 = paramFns[3].execute(record, arena).cast();\n" + + " auto ts1 = paramFns[4].execute(record, arena).cast();\n" + + " auto ref2_wkt = paramFns[5].execute(record, arena);\n" + + " auto x2 = paramFns[6].execute(record, arena).cast();\n" + + " auto y2 = paramFns[7].execute(record, arena).cast();\n" + + " auto theta2 = paramFns[8].execute(record, arena).cast();\n" + + " auto ts2 = paramFns[9].execute(record, arena).cast();\n" + + " const auto result = nautilus::invoke(\n" + + " +[](const char* ref1_wkt, uint32_t ref1_len, double x1, double y1, double theta1, uint64_t ts1, const char* ref2_wkt, uint32_t ref2_len, double x2, double y2, double theta2, uint64_t ts2) -> double {\n" + + " try {\n" + + " MEOS::Meos::ensureMeosInitialized();\n" + + " char* s1 = (char*)malloc(ref1_len + 1);\n" + + " memcpy(s1, ref1_wkt, ref1_len); s1[ref1_len] = '\\0';\n" + + " GSERIALIZED* gs1 = geom_in(s1, -1); free(s1);\n" + + " if (!gs1) return 0.0;\n" + + " Pose* p1 = pose_make_2d(x1, y1, theta1, false, 0);\n" + + " if (!p1) { free(gs1); return 0.0; }\n" + + " TInstant* inst1 = trgeoinst_make(gs1, p1, (TimestampTz)ts1);\n" + + " free(gs1); free(p1);\n" + + " if (!inst1) return 0.0;\n" + + " char* s2 = (char*)malloc(ref2_len + 1);\n" + + " memcpy(s2, ref2_wkt, ref2_len); s2[ref2_len] = '\\0';\n" + + " GSERIALIZED* gs2 = geom_in(s2, -1); free(s2);\n" + + " if (!gs2) { free(inst1); return 0.0; }\n" + + " Pose* p2 = pose_make_2d(x2, y2, theta2, false, 0);\n" + + " if (!p2) { free(inst1); free(gs2); return 0.0; }\n" + + " TInstant* inst2 = trgeoinst_make(gs2, p2, (TimestampTz)ts2);\n" + + " free(gs2); free(p2);\n" + + " if (!inst2) { free(inst1); return 0.0; }\n" + + f" double r = {meos_call}((Temporal*)inst1, (Temporal*)inst2);\n" + + " free(inst1); free(inst2);\n" + + " return r;\n" + + " } catch (const std::exception&) { return 0.0; }\n" + + " },\n" + + " ref1_wkt, x1, y1, theta1, ts1, ref2_wkt, x2, y2, theta2, ts2);\n" + + " return VarVal(result);\n" + + "}\n" + + _trgeo_reg_nad(nebula_name, n) + ) + + +# Invariant sets used by build_descriptor.py classifiers (exported here so +# emit_trgeometry_operator can reference them by name). +_INV_TRG_GEO_X1 = [(0,"VARSIZED","ref_wkt"),(1,"FLOAT64","x1"),(2,"FLOAT64","y1"),(3,"FLOAT64","theta1"),(4,"UINT64","ts1"),(5,"VARSIZED","tgt_wkt")] +_INV_TRG_GEO_X = [(0,"VARSIZED","ref_wkt"),(1,"FLOAT64","x"),(2,"FLOAT64","y"),(3,"FLOAT64","theta"),(4,"UINT64","ts"),(5,"VARSIZED","tgt_wkt")] +_INV_GEO_TRG_X1 = [(0,"VARSIZED","tgt_wkt"),(1,"VARSIZED","ref_wkt"),(2,"FLOAT64","x1"),(3,"FLOAT64","y1"),(4,"FLOAT64","theta1"),(5,"UINT64","ts1")] +_INV_GEO_TRG_X = [(0,"VARSIZED","tgt_wkt"),(1,"VARSIZED","ref_wkt"),(2,"FLOAT64","x"),(3,"FLOAT64","y"),(4,"FLOAT64","theta"),(5,"UINT64","ts")] +_INV_TRG_GEO_DIST = _INV_TRG_GEO_X1 + [(6,"FLOAT64","dist")] +_INV_TRG_TRG_X1 = [(0,"VARSIZED","ref1_wkt"),(1,"FLOAT64","x1"),(2,"FLOAT64","y1"),(3,"FLOAT64","theta1"),(4,"UINT64","ts1"),(5,"VARSIZED","ref2_wkt"),(6,"FLOAT64","x2"),(7,"FLOAT64","y2"),(8,"FLOAT64","theta2"),(9,"UINT64","ts2")] +_INV_TRG_TRG_DIST = _INV_TRG_TRG_X1 + [(10,"FLOAT64","dist")] + +_TRGEO_PHYS_DISPATCH = { + "build_trgeometry_geo": (_trgeo_phys_cpp_trgeometry_geo, ["ref_wkt","x1","y1","theta1","ts1","tgt_wkt"], _INV_TRG_GEO_X1, "compact"), + "build_trgeometry_geo_nad": (_trgeo_phys_cpp_trgeometry_geo_nad, ["ref_wkt","x","y","theta","ts","tgt_wkt"], _INV_TRG_GEO_X, "nad"), + "build_geo_trgeometry": (_trgeo_phys_cpp_geo_trgeometry, ["tgt_wkt","ref_wkt","x1","y1","theta1","ts1"], _INV_GEO_TRG_X1, "compact"), + "build_geo_trgeometry_eq": (_trgeo_phys_cpp_geo_trgeometry_eq, ["tgt_wkt","ref_wkt","x","y","theta","ts"], _INV_GEO_TRG_X, "nad"), + "build_trgeometry_geo_with_dist": (_trgeo_phys_cpp_trgeometry_geo_with_dist, ["ref_wkt","x1","y1","theta1","ts1","tgt_wkt","dist"], _INV_TRG_GEO_DIST,"compact"), + "build_trgeometry_trgeometry": (_trgeo_phys_cpp_trgeometry_trgeometry, ["ref1_wkt","x1","y1","theta1","ts1","ref2_wkt","x2","y2","theta2","ts2"], _INV_TRG_TRG_X1, "compact"), + "build_trgeometry_trgeometry_nad": (_trgeo_phys_cpp_trgeometry_trgeometry_nad, ["ref1_wkt","x1","y1","theta1","ts1","ref2_wkt","x2","y2","theta2","ts2"], _INV_TRG_TRG_X1, "nad"), + "build_trgeometry_trgeometry_with_dist": (_trgeo_phys_cpp_trgeometry_trgeometry_with_dist,["ref1_wkt","x1","y1","theta1","ts1","ref2_wkt","x2","y2","theta2","ts2","dist"], _INV_TRG_TRG_DIST,"compact"), + "build_nad_trgeometry_geo": (_trgeo_phys_cpp_nad_trgeometry_geo, ["ref_wkt","x","y","theta","ts","tgt_wkt"], _INV_TRG_GEO_X, "nad"), + "build_nad_trgeometry_trgeometry": (_trgeo_phys_cpp_nad_trgeometry_trgeometry, ["ref1_wkt","x1","y1","theta1","ts1","ref2_wkt","x2","y2","theta2","ts2"], _INV_TRG_TRG_X1, "nad"), +} + + +def emit_trgeometry_operator(op, output_root: Path): + """Emit 4 files for a trgeometry operator using the W148/W149 templates. + + op must have: nebula_name, meos_call, comment_one_liner, and one of the + build_* keys from _TRGEO_PHYS_DISPATCH set to True. + """ + nebula_name = op["nebula_name"] + meos_call = op["meos_call"] + brief = op.get("comment_one_liner", f"MEOS {meos_call} over a trgeometry instant.") + + build_key = next((k for k in _TRGEO_PHYS_DISPATCH if op.get(k)), None) + if build_key is None: + sys.stderr.write(f" ! {nebula_name}: no trgeometry build key found\n") + return + + phys_fn, ctor_args, invariants, log_style = _TRGEO_PHYS_DISPATCH[build_key] + + logical_hpp_path = output_root / "nes-logical-operators/include/Functions/Meos" / f"{nebula_name}LogicalFunction.hpp" + logical_cpp_path = output_root / "nes-logical-operators/src/Functions/Meos" / f"{nebula_name}LogicalFunction.cpp" + physical_hpp_path = output_root / "nes-physical-operators/include/Functions/Meos" / f"{nebula_name}PhysicalFunction.hpp" + physical_cpp_path = output_root / "nes-physical-operators/src/Functions/Meos" / f"{nebula_name}PhysicalFunction.cpp" + + for p in (logical_hpp_path, logical_cpp_path, physical_hpp_path, physical_cpp_path): + p.parent.mkdir(parents=True, exist_ok=True) + + if log_style == "nad": + logical_hpp_path.write_text(_trgeo_logical_hpp_nad(nebula_name, brief, ctor_args)) + logical_cpp_path.write_text(_trgeo_logical_cpp_nad(nebula_name, ctor_args, invariants)) + else: + logical_hpp_path.write_text(_trgeo_logical_hpp_compact(nebula_name, brief, ctor_args)) + logical_cpp_path.write_text(_trgeo_logical_cpp_compact(nebula_name, ctor_args, invariants)) + + physical_hpp_path.write_text(_trgeo_physical_hpp(nebula_name, ctor_args)) + physical_cpp_path.write_text(phys_fn(nebula_name, meos_call, ctor_args)) + + sys.stderr.write(f" ✓ {nebula_name} [trgeometry/{build_key}]: emitted 4 files\n") + + +def cpp_logical_type(arg): + """C++ constructor-arg type for a LogicalFunction parameter.""" + return "LogicalFunction" + + +def cpp_physical_type(arg): + """C++ constructor-arg type for a PhysicalFunction parameter.""" + return "PhysicalFunction" + + +def build_ctor_args(args, type_fn): + return ",\n ".join( + f"{type_fn(a)} {a['name']}" for a in args + ) + + +def build_pushes_logical(args): + return "\n".join(f" parameters.push_back(std::move({a['name']}));" for a in args) + + +def build_pushes_physical(args): + return "\n".join( + f" parameterFunctions.push_back(std::move({a['name']}Function));" for a in args + ) + + +def build_registrar_pushes_logical(args, nebula_name): + pushes = [] + for i, _ in enumerate(args): + pushes.append(f" auto arg{i} = std::move(arguments.children[{i}]);") + pushes.append( + f" return {nebula_name}LogicalFunction(" + ", ".join(f"std::move(arg{i})" for i in range(len(args))) + ");" + ) + return "\n".join(pushes) + + +def build_registrar_pushes_physical(args, nebula_name): + pushes = [] + for i, _ in enumerate(args): + # PhysicalFunctionRegistryArguments uses `childFunctions`, not `children` + # (LogicalFunctionRegistryArguments uses `children` — see registry headers). + pushes.append(f" auto arg{i} = std::move(arguments.childFunctions[{i}]);") + pushes.append( + f" return {nebula_name}PhysicalFunction(" + ", ".join(f"std::move(arg{i})" for i in range(len(args))) + ");" + ) + return "\n".join(pushes) + + +def generic_fields(op): + """The ordered (name, cpp) event fields for a 'generic' operator: + the input type's fields followed by one per extra arg.""" + fields = list(GENERIC_INPUTS[op["input_type"]]["fields"]) + for i, ex in enumerate(op.get("extra_args", [])): + fields.append((f"arg{i}", ex["cpp"] if ex["kind"] == "scalar" else "VariableSizedData")) + return fields + + +def emit_operator(op, output_root: Path): + nebula_name = op["nebula_name"] + # Trgeometry operators use dedicated per-layout builders (W148/W149 wave). + # Detected by any of the 10 trgeometry build keys registered in _TRGEO_PHYS_DISPATCH. + if any(op.get(k) for k in _TRGEO_PHYS_DISPATCH): + emit_trgeometry_operator(op, output_root) + return + if op.get("build_generic"): + # Derive the canonical arg list + return metadata so the shared logical + # and physical-hpp templates match the assembled physical .cpp. + op["args"] = [{"name": n, "nautilus_type": c, "cpp_type": c} for n, c in generic_fields(op)] + rt, nr = GENERIC_RETURNS[op["return_kind"]][:2] + op.setdefault("return_type", rt) + op.setdefault("nautilus_return", nr) + n_args = len(op["args"]) + + # Logical .hpp constructor args (LogicalFunction type each) + ctor_logical_args = build_ctor_args(op["args"], cpp_logical_type) + # Physical .hpp / .cpp constructor args use 'XxxFunction' naming convention + physical_args = [{"name": a["name"] + "Function"} for a in op["args"]] + ctor_physical_args = ",\n ".join( + f"PhysicalFunction {a['name']}" for a in physical_args + ) + + ctor_logical_pushes = build_pushes_logical(op["args"]) + ctor_physical_pushes = build_pushes_physical(op["args"]) + registrar_l = build_registrar_pushes_logical(op["args"], nebula_name) + registrar_p = build_registrar_pushes_physical(op["args"], nebula_name) + + common = { + "nebula_name": nebula_name, + "comment_one_liner": op["comment_one_liner"], + "meos_call": op["meos_call"], + "n_args": n_args, + "nautilus_return": op["nautilus_return"], + "return_type": op["return_type"], + "ctor_logical_args": ctor_logical_args, + "ctor_physical_args": ctor_physical_args, + "ctor_logical_pushes": ctor_logical_pushes, + "ctor_physical_pushes": ctor_physical_pushes, + "registrar_pushes": registrar_l, + # tnumber-shape extras (only consumed by the two tnumber templates). + # tnumber_wkt_format is a fmt::format pattern that ends up in C++ as-is; + # Python single-pass .format() means we want raw `{}@{}` here (no doubling). + "tnumber_value_cpp_type": op.get("tnumber_value_cpp_type", "double"), + "scalar_cpp_type": op.get("scalar_cpp_type", "double"), + "tnumber_wkt_format": op.get("tnumber_wkt_format", "{}@{}"), + "tnumber_in_fn": op.get("tnumber_in_fn", "tfloat_in"), + } + + logical_hpp_path = output_root / "nes-logical-operators/include/Functions/Meos" / f"{nebula_name}LogicalFunction.hpp" + logical_cpp_path = output_root / "nes-logical-operators/src/Functions/Meos" / f"{nebula_name}LogicalFunction.cpp" + physical_hpp_path = output_root / "nes-physical-operators/include/Functions/Meos" / f"{nebula_name}PhysicalFunction.hpp" + physical_cpp_path = output_root / "nes-physical-operators/src/Functions/Meos" / f"{nebula_name}PhysicalFunction.cpp" + + for p in (logical_hpp_path, logical_cpp_path, physical_hpp_path, physical_cpp_path): + p.parent.mkdir(parents=True, exist_ok=True) + + logical_hpp_path.write_text(LOGICAL_HPP_TEMPLATE.format(**common)) + logical_cpp_path.write_text(LOGICAL_CPP_TEMPLATE.format(**common)) + physical_hpp_path.write_text(PHYSICAL_HPP_TEMPLATE.format(**common)) + + physical_common = dict(common) + physical_common["registrar_pushes"] = registrar_p + if op.get("build_generic"): + physical_cpp_path.write_text(assemble_generic_physical(op)) + elif op.get("build_two_temporal_points_with_dist"): + physical_cpp_path.write_text(PHYSICAL_CPP_TEMPLATE_TWO_TEMPORAL_POINTS_WITH_DIST.format(**physical_common)) + elif op.get("build_temporal_point_with_dist"): + physical_cpp_path.write_text(PHYSICAL_CPP_TEMPLATE_TEMPORAL_POINT_WITH_DIST.format(**physical_common)) + elif op.get("build_two_temporal_points"): + physical_cpp_path.write_text(PHYSICAL_CPP_TEMPLATE_TWO_TEMPORAL_POINTS.format(**physical_common)) + elif op.get("build_temporal_point_restriction"): + physical_cpp_path.write_text(PHYSICAL_CPP_TEMPLATE_TEMPORAL_POINT_RESTRICTION.format(**physical_common)) + elif op.get("build_two_tpose_points_via_composition"): + physical_cpp_path.write_text(PHYSICAL_CPP_TEMPLATE_TWO_TPOSE_POINTS_VIA_COMPOSITION.format(**physical_common)) + elif op.get("build_tpose_point_via_composition"): + physical_cpp_path.write_text(PHYSICAL_CPP_TEMPLATE_TPOSE_POINT_VIA_COMPOSITION.format(**physical_common)) + elif op.get("build_two_tnpoint_points_via_composition"): + physical_cpp_path.write_text(PHYSICAL_CPP_TEMPLATE_TWO_TNPOINT_POINTS_VIA_COMPOSITION.format(**physical_common)) + elif op.get("build_tnpoint_point_via_composition"): + physical_cpp_path.write_text(PHYSICAL_CPP_TEMPLATE_TNPOINT_POINT_VIA_COMPOSITION.format(**physical_common)) + elif op.get("build_two_tpose_points_with_dist_via_composition"): + physical_cpp_path.write_text(PHYSICAL_CPP_TEMPLATE_TWO_TPOSE_POINTS_WITH_DIST_VIA_COMPOSITION.format(**physical_common)) + elif op.get("build_tpose_point_with_dist_via_composition"): + physical_cpp_path.write_text(PHYSICAL_CPP_TEMPLATE_TPOSE_POINT_WITH_DIST_VIA_COMPOSITION.format(**physical_common)) + elif op.get("build_two_tnpoint_points_with_dist_via_composition"): + physical_cpp_path.write_text(PHYSICAL_CPP_TEMPLATE_TWO_TNPOINT_POINTS_WITH_DIST_VIA_COMPOSITION.format(**physical_common)) + elif op.get("build_tnpoint_point_with_dist_via_composition"): + physical_cpp_path.write_text(PHYSICAL_CPP_TEMPLATE_TNPOINT_POINT_WITH_DIST_VIA_COMPOSITION.format(**physical_common)) + elif op.get("build_two_tcbuffer_points_with_dist"): + physical_cpp_path.write_text(PHYSICAL_CPP_TEMPLATE_TWO_TCBUFFER_POINTS_WITH_DIST.format(**physical_common)) + elif op.get("build_tcbuffer_point_cbuffer_with_dist"): + physical_cpp_path.write_text(PHYSICAL_CPP_TEMPLATE_TCBUFFER_POINT_CBUFFER_WITH_DIST.format(**physical_common)) + elif op.get("build_tcbuffer_point_with_dist"): + physical_cpp_path.write_text(PHYSICAL_CPP_TEMPLATE_TCBUFFER_POINT_WITH_DIST.format(**physical_common)) + elif op.get("build_two_tcbuffer_points"): + physical_cpp_path.write_text(PHYSICAL_CPP_TEMPLATE_TWO_TCBUFFER_POINTS.format(**physical_common)) + elif op.get("build_tcbuffer_point_cbuffer"): + physical_cpp_path.write_text(PHYSICAL_CPP_TEMPLATE_TCBUFFER_POINT_CBUFFER.format(**physical_common)) + elif op.get("build_tcbuffer_point"): + physical_cpp_path.write_text(PHYSICAL_CPP_TEMPLATE_TCBUFFER_POINT.format(**physical_common)) + elif op.get("build_temporal_point"): + physical_cpp_path.write_text(PHYSICAL_CPP_TEMPLATE_TEMPORAL_POINT.format(**physical_common)) + elif op.get("build_tnumber_point_with_scalar"): + physical_cpp_path.write_text(PHYSICAL_CPP_TEMPLATE_TNUMBER_POINT_WITH_SCALAR.format(**physical_common)) + elif op.get("build_tnumber_scalar_first"): + physical_cpp_path.write_text(PHYSICAL_CPP_TEMPLATE_TNUMBER_SCALAR_FIRST.format(**physical_common)) + elif op.get("build_two_tnumber_points"): + physical_cpp_path.write_text(PHYSICAL_CPP_TEMPLATE_TWO_TNUMBER_POINTS.format(**physical_common)) + else: + sys.stderr.write( + f" ! {nebula_name}: physical-cpp template for non-temporal-point ops is not yet implemented; " + f"skipping .cpp — the .hpp + logical files are still emitted, but the .cpp must be hand-written.\n" + ) + + sys.stderr.write(f" ✓ {nebula_name}: emitted 4 files ({logical_hpp_path.relative_to(output_root)} + siblings)\n") + + +# Physical .cpp template for one-temporal-point restriction operators — +# MEOS signature `Temporal* fn(const Temporal*, const GSERIALIZED*)`. The +# returned Temporal* is checked for non-null (i.e. survived the restriction), +# freed, and reduced to an int (1 = survives, 0 = clipped/null/error). +# Per-event single-instant semantics: equivalent to a filter predicate. +# Mirrors mariana's TemporalAtStBox int-collapse pattern. +PHYSICAL_CPP_TEMPLATE_TEMPORAL_POINT_RESTRICTION = """\ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" {{ +#include +#include +}} + +namespace NES {{ + +{nebula_name}PhysicalFunction::{nebula_name}PhysicalFunction({ctor_physical_args}) +{{ + parameterFunctions.reserve({n_args}); +{ctor_physical_pushes} +}} + +VarVal {nebula_name}PhysicalFunction::execute(const Record& record, ArenaRef& arena) const +{{ + std::vector parameterValues; + parameterValues.reserve(parameterFunctions.size()); + for (const auto& function : parameterFunctions) + {{ + parameterValues.emplace_back(function.execute(record, arena)); + }} + + auto lon = parameterValues[0].cast>(); + auto lat = parameterValues[1].cast>(); + auto timestamp = parameterValues[2].cast>(); + auto geometry = parameterValues[3].cast(); + + const auto result = nautilus::invoke( + +[](double lonValue, + double latValue, + uint64_t timestampValue, + const char* geometryPtr, + uint32_t geometrySize) -> {return_type} {{ + try + {{ + MEOS::Meos::ensureMeosInitialized(); + if (!(lonValue >= -180.0 && lonValue <= 180.0 && latValue >= -90.0 && latValue <= 90.0)) return 0; + + const std::string timestampString = MEOS::Meos::convertEpochToTimestamp(timestampValue); + std::string temporalGeometryWkt = fmt::format("SRID=4326;Point({{}} {{}})@{{}}", lonValue, latValue, timestampString); + std::string staticGeometryWkt(geometryPtr, geometrySize); + + while (!staticGeometryWkt.empty() && (staticGeometryWkt.front() == '\\'' || staticGeometryWkt.front() == '"')) + staticGeometryWkt.erase(staticGeometryWkt.begin()); + while (!staticGeometryWkt.empty() && (staticGeometryWkt.back() == '\\'' || staticGeometryWkt.back() == '"')) + staticGeometryWkt.pop_back(); + + if (temporalGeometryWkt.empty() || staticGeometryWkt.empty()) return 0; + + MEOS::Meos::TemporalGeometry temporalGeometry(temporalGeometryWkt); + if (!temporalGeometry.getGeometry()) return 0; + MEOS::Meos::StaticGeometry staticGeometry(staticGeometryWkt); + if (!staticGeometry.getGeometry()) return 0; + + // MEOS restriction call — returns Temporal* (non-null if the + // input survived the restriction, null if clipped/empty). + // For per-event single-instant inputs this collapses to a + // filter predicate: 1 if the point survives, 0 if clipped. + Temporal* clipped = {meos_call}(temporalGeometry.getGeometry(), + staticGeometry.getGeometry()); + if (clipped == nullptr) return 0; + free(clipped); + return 1; + }} + catch (const std::exception&) + {{ + return 0; + }} + }}, + lon, lat, timestamp, geometry.getContent(), geometry.getContentSize()); + + return VarVal(result); +}} + +PhysicalFunctionRegistryReturnType PhysicalFunctionGeneratedRegistrar::Register{nebula_name}PhysicalFunction( + PhysicalFunctionRegistryArguments arguments) +{{ + PRECONDITION(arguments.childFunctions.size() == {n_args}, + "{nebula_name}PhysicalFunction requires {n_args} children but got {{}}", + arguments.childFunctions.size()); +{registrar_pushes} +}} + +}} // namespace NES +""" + + +# Physical .cpp template for one-tcbuffer-point operators with a static +# geometry — e.g. econtains_tcbuffer_geo. The MEOS call signature is +# ` fn(const Temporal*, const GSERIALIZED*)` where the Temporal is a +# tcbuffer (Cbuffer instant) built per-event from (lon, lat, radius, ts). +# WKT format: "Cbuffer(Point(lon lat),radius)@ts". +# 5 SQL args: lon, lat, radius, ts, geometry. +PHYSICAL_CPP_TEMPLATE_TCBUFFER_POINT = """\ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" {{ +#include +#include +}} + +namespace NES {{ + +{nebula_name}PhysicalFunction::{nebula_name}PhysicalFunction({ctor_physical_args}) +{{ + parameterFunctions.reserve({n_args}); +{ctor_physical_pushes} +}} + +VarVal {nebula_name}PhysicalFunction::execute(const Record& record, ArenaRef& arena) const +{{ + std::vector parameterValues; + parameterValues.reserve(parameterFunctions.size()); + for (const auto& function : parameterFunctions) + {{ + parameterValues.emplace_back(function.execute(record, arena)); + }} + + auto lon = parameterValues[0].cast>(); + auto lat = parameterValues[1].cast>(); + auto radius = parameterValues[2].cast>(); + auto timestamp = parameterValues[3].cast>(); + auto geometry = parameterValues[4].cast(); + + const auto result = nautilus::invoke( + +[](double lonValue, + double latValue, + double radiusValue, + uint64_t timestampValue, + const char* geometryPtr, + uint32_t geometrySize) -> {return_type} {{ + try + {{ + MEOS::Meos::ensureMeosInitialized(); + if (!(lonValue >= -180.0 && lonValue <= 180.0 && latValue >= -90.0 && latValue <= 90.0)) return 0; + if (radiusValue < 0.0) return 0; + + const std::string timestampString = MEOS::Meos::convertEpochToTimestamp(timestampValue); + std::string tcbufferWkt = fmt::format("Cbuffer(Point({{}} {{}}),{{}})@{{}}", + lonValue, latValue, radiusValue, timestampString); + std::string staticGeometryWkt(geometryPtr, geometrySize); + + while (!staticGeometryWkt.empty() && (staticGeometryWkt.front() == '\\'' || staticGeometryWkt.front() == '"')) + staticGeometryWkt.erase(staticGeometryWkt.begin()); + while (!staticGeometryWkt.empty() && (staticGeometryWkt.back() == '\\'' || staticGeometryWkt.back() == '"')) + staticGeometryWkt.pop_back(); + + if (tcbufferWkt.empty() || staticGeometryWkt.empty()) return 0; + + Temporal* tcbuffer = tcbuffer_in(tcbufferWkt.c_str()); + if (!tcbuffer) return 0; + MEOS::Meos::StaticGeometry staticGeometry(staticGeometryWkt); + if (!staticGeometry.getGeometry()) {{ free(tcbuffer); return 0; }} + + {return_type} r = {meos_call}(tcbuffer, staticGeometry.getGeometry()); + free(tcbuffer); + return r; + }} + catch (const std::exception&) + {{ + return 0; + }} + }}, + lon, lat, radius, timestamp, geometry.getContent(), geometry.getContentSize()); + + return VarVal(result); +}} + +PhysicalFunctionRegistryReturnType PhysicalFunctionGeneratedRegistrar::Register{nebula_name}PhysicalFunction( + PhysicalFunctionRegistryArguments arguments) +{{ + PRECONDITION(arguments.childFunctions.size() == {n_args}, + "{nebula_name}PhysicalFunction requires {n_args} children but got {{}}", + arguments.childFunctions.size()); +{registrar_pushes} +}} + +}} // namespace NES +""" + + +# Physical .cpp template for tpose × static geom spatial-rels VIA +# COMPOSITION — the existing _tgeo_geo MEOS call is applied to a tpose +# converted to tgeompoint at run time. Per-event tpose instant from +# (x, y, theta, ts), then tpose_to_tpoint(), then the MEOS spatial-rel +# call. Matches MobilityDB PR #987's SQL-level composition recipe at the +# binding layer (no new MEOS spatial-rel symbols are needed for tpose). +# 5 SQL args: x, y, theta, ts, geometry. +PHYSICAL_CPP_TEMPLATE_TPOSE_POINT_VIA_COMPOSITION = """\ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" {{ +#include +#include +#include +}} + +namespace NES {{ + +{nebula_name}PhysicalFunction::{nebula_name}PhysicalFunction({ctor_physical_args}) +{{ + parameterFunctions.reserve({n_args}); +{ctor_physical_pushes} +}} + +VarVal {nebula_name}PhysicalFunction::execute(const Record& record, ArenaRef& arena) const +{{ + std::vector parameterValues; + parameterValues.reserve(parameterFunctions.size()); + for (const auto& function : parameterFunctions) + {{ + parameterValues.emplace_back(function.execute(record, arena)); + }} + + auto x = parameterValues[0].cast>(); + auto y = parameterValues[1].cast>(); + auto theta = parameterValues[2].cast>(); + auto timestamp = parameterValues[3].cast>(); + auto geometry = parameterValues[4].cast(); + + const auto result = nautilus::invoke( + +[](double xValue, double yValue, double thetaValue, uint64_t timestampValue, + const char* geometryPtr, uint32_t geometrySize) -> {return_type} {{ + try + {{ + MEOS::Meos::ensureMeosInitialized(); + if (!(xValue >= -180.0 && xValue <= 180.0 && yValue >= -90.0 && yValue <= 90.0)) return 0; + + const std::string timestampString = MEOS::Meos::convertEpochToTimestamp(timestampValue); + std::string tposeWkt = fmt::format("Pose(Point({{}} {{}}), {{}})@{{}}", + xValue, yValue, thetaValue, timestampString); + std::string staticGeometryWkt(geometryPtr, geometrySize); + + while (!staticGeometryWkt.empty() && (staticGeometryWkt.front() == '\\'' || staticGeometryWkt.front() == '"')) + staticGeometryWkt.erase(staticGeometryWkt.begin()); + while (!staticGeometryWkt.empty() && (staticGeometryWkt.back() == '\\'' || staticGeometryWkt.back() == '"')) + staticGeometryWkt.pop_back(); + + if (tposeWkt.empty() || staticGeometryWkt.empty()) return 0; + + Temporal* tpose = tpose_in(tposeWkt.c_str()); + if (!tpose) return 0; + Temporal* tgeo = tpose_to_tpoint(tpose); + if (!tgeo) {{ free(tpose); return 0; }} + MEOS::Meos::StaticGeometry staticGeometry(staticGeometryWkt); + if (!staticGeometry.getGeometry()) {{ free(tgeo); free(tpose); return 0; }} + + {return_type} r = {meos_call}(tgeo, staticGeometry.getGeometry()); + free(tgeo); + free(tpose); + return r; + }} + catch (const std::exception&) + {{ + return 0; + }} + }}, + x, y, theta, timestamp, geometry.getContent(), geometry.getContentSize()); + + return VarVal(result); +}} + +PhysicalFunctionRegistryReturnType PhysicalFunctionGeneratedRegistrar::Register{nebula_name}PhysicalFunction( + PhysicalFunctionRegistryArguments arguments) +{{ + PRECONDITION(arguments.childFunctions.size() == {n_args}, + "{nebula_name}PhysicalFunction requires {n_args} children but got {{}}", + arguments.childFunctions.size()); +{registrar_pushes} +}} + +}} // namespace NES +""" + + +# Physical .cpp template for tpose × tpose spatial-rels VIA COMPOSITION — +# the existing _tgeo_tgeo MEOS call is applied to two tposes each converted +# to a single-instant tgeompoint at run time. Per-event tpose instants from +# (xA, yA, thetaA, tsA) and (xB, yB, thetaB, tsB), each tpose_in() then +# tpose_to_tpoint(), then the MEOS two-temporal spatial-rel call. Mirrors the +# W14 one-tpose composition recipe; no new MEOS symbols are needed for tpose +# (the _tgeo_tgeo row was shipped in W3). 8 SQL args. +PHYSICAL_CPP_TEMPLATE_TWO_TPOSE_POINTS_VIA_COMPOSITION = """\ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" {{ +#include +#include +#include +}} + +namespace NES {{ + +{nebula_name}PhysicalFunction::{nebula_name}PhysicalFunction({ctor_physical_args}) +{{ + parameterFunctions.reserve({n_args}); +{ctor_physical_pushes} +}} + +VarVal {nebula_name}PhysicalFunction::execute(const Record& record, ArenaRef& arena) const +{{ + std::vector parameterValues; + parameterValues.reserve(parameterFunctions.size()); + for (const auto& function : parameterFunctions) + {{ + parameterValues.emplace_back(function.execute(record, arena)); + }} + + auto xA = parameterValues[0].cast>(); + auto yA = parameterValues[1].cast>(); + auto thetaA = parameterValues[2].cast>(); + auto tsA = parameterValues[3].cast>(); + auto xB = parameterValues[4].cast>(); + auto yB = parameterValues[5].cast>(); + auto thetaB = parameterValues[6].cast>(); + auto tsB = parameterValues[7].cast>(); + + const auto result = nautilus::invoke( + +[](double xAValue, double yAValue, double thetaAValue, uint64_t tsAValue, + double xBValue, double yBValue, double thetaBValue, uint64_t tsBValue) -> {return_type} {{ + try + {{ + MEOS::Meos::ensureMeosInitialized(); + if (!(xAValue >= -180.0 && xAValue <= 180.0 && yAValue >= -90.0 && yAValue <= 90.0)) return 0; + if (!(xBValue >= -180.0 && xBValue <= 180.0 && yBValue >= -90.0 && yBValue <= 90.0)) return 0; + + const std::string tsAString = MEOS::Meos::convertEpochToTimestamp(tsAValue); + const std::string tsBString = MEOS::Meos::convertEpochToTimestamp(tsBValue); + std::string tposeAWkt = fmt::format("Pose(Point({{}} {{}}), {{}})@{{}}", xAValue, yAValue, thetaAValue, tsAString); + std::string tposeBWkt = fmt::format("Pose(Point({{}} {{}}), {{}})@{{}}", xBValue, yBValue, thetaBValue, tsBString); + + if (tposeAWkt.empty() || tposeBWkt.empty()) return 0; + + Temporal* tposeA = tpose_in(tposeAWkt.c_str()); + if (!tposeA) return 0; + Temporal* tgeoA = tpose_to_tpoint(tposeA); + if (!tgeoA) {{ free(tposeA); return 0; }} + Temporal* tposeB = tpose_in(tposeBWkt.c_str()); + if (!tposeB) {{ free(tgeoA); free(tposeA); return 0; }} + Temporal* tgeoB = tpose_to_tpoint(tposeB); + if (!tgeoB) {{ free(tposeB); free(tgeoA); free(tposeA); return 0; }} + + {return_type} r = {meos_call}(tgeoA, tgeoB); + free(tgeoB); + free(tposeB); + free(tgeoA); + free(tposeA); + return r; + }} + catch (const std::exception&) + {{ + return 0; + }} + }}, + xA, yA, thetaA, tsA, xB, yB, thetaB, tsB); + + return VarVal(result); +}} + +PhysicalFunctionRegistryReturnType PhysicalFunctionGeneratedRegistrar::Register{nebula_name}PhysicalFunction( + PhysicalFunctionRegistryArguments arguments) +{{ + PRECONDITION(arguments.childFunctions.size() == {n_args}, + "{nebula_name}PhysicalFunction requires {n_args} children but got {{}}", + arguments.childFunctions.size()); +{registrar_pushes} +}} + +}} // namespace NES +""" + + +# Physical .cpp template for tnpoint × static geom spatial-rels VIA +# COMPOSITION — a temporal network point is resolved to a temporal +# geometry point at run time (tnpoint_to_tgeompoint, which looks up each +# route's geometry from the MEOS ways network), then the existing +# _tgeo_geo spatial-rel is applied. Same shape as the tpose composition +# (W14); no new MEOS spatial-rel symbols are needed for tnpoint. +# NOTE: tnpoint_to_tgeompoint yields a tgeompoint in the *network* SRID, +# so the static geometry must use that SRID (else MEOS errors on mixed +# SRID). 4 SQL args: rid, fraction, ts, geometry. +PHYSICAL_CPP_TEMPLATE_TNPOINT_POINT_VIA_COMPOSITION = """\ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" {{ +#include +#include +#include +}} + +namespace NES {{ + +{nebula_name}PhysicalFunction::{nebula_name}PhysicalFunction({ctor_physical_args}) +{{ + parameterFunctions.reserve({n_args}); +{ctor_physical_pushes} +}} + +VarVal {nebula_name}PhysicalFunction::execute(const Record& record, ArenaRef& arena) const +{{ + std::vector parameterValues; + parameterValues.reserve(parameterFunctions.size()); + for (const auto& function : parameterFunctions) + {{ + parameterValues.emplace_back(function.execute(record, arena)); + }} + + auto rid = parameterValues[0].cast>(); + auto fraction = parameterValues[1].cast>(); + auto timestamp = parameterValues[2].cast>(); + auto geometry = parameterValues[3].cast(); + + const auto result = nautilus::invoke( + +[](uint64_t ridValue, double fractionValue, uint64_t timestampValue, + const char* geometryPtr, uint32_t geometrySize) -> {return_type} {{ + try + {{ + MEOS::Meos::ensureMeosInitialized(); + + const std::string timestampString = MEOS::Meos::convertEpochToTimestamp(timestampValue); + std::string tnpointWkt = fmt::format("NPoint({{}}, {{}})@{{}}", ridValue, fractionValue, timestampString); + std::string staticGeometryWkt(geometryPtr, geometrySize); + + while (!staticGeometryWkt.empty() && (staticGeometryWkt.front() == '\\'' || staticGeometryWkt.front() == '"')) + staticGeometryWkt.erase(staticGeometryWkt.begin()); + while (!staticGeometryWkt.empty() && (staticGeometryWkt.back() == '\\'' || staticGeometryWkt.back() == '"')) + staticGeometryWkt.pop_back(); + + if (tnpointWkt.empty() || staticGeometryWkt.empty()) return 0; + + Temporal* tnpoint = tnpoint_in(tnpointWkt.c_str()); + if (!tnpoint) return 0; + Temporal* tgeo = tnpoint_to_tgeompoint(tnpoint); + if (!tgeo) {{ free(tnpoint); return 0; }} + MEOS::Meos::StaticGeometry staticGeometry(staticGeometryWkt); + if (!staticGeometry.getGeometry()) {{ free(tgeo); free(tnpoint); return 0; }} + + {return_type} r = {meos_call}(tgeo, staticGeometry.getGeometry()); + free(tgeo); + free(tnpoint); + return r; + }} + catch (const std::exception&) + {{ + return 0; + }} + }}, + rid, fraction, timestamp, geometry.getContent(), geometry.getContentSize()); + + return VarVal(result); +}} + +PhysicalFunctionRegistryReturnType PhysicalFunctionGeneratedRegistrar::Register{nebula_name}PhysicalFunction( + PhysicalFunctionRegistryArguments arguments) +{{ + PRECONDITION(arguments.childFunctions.size() == {n_args}, + "{nebula_name}PhysicalFunction requires {n_args} children but got {{}}", + arguments.childFunctions.size()); +{registrar_pushes} +}} + +}} // namespace NES +""" + + +# Physical .cpp template for tnpoint × tnpoint spatial-rels VIA +# COMPOSITION — two temporal network points each resolved to a temporal +# geometry point (tnpoint_to_tgeompoint), then the existing _tgeo_tgeo +# spatial-rel (W3) is applied. Both operands land in the network SRID, so +# no mixed-SRID concern (unlike tnpoint × static geom). 6 SQL args: +# ridA, fracA, tsA, ridB, fracB, tsB. +PHYSICAL_CPP_TEMPLATE_TWO_TNPOINT_POINTS_VIA_COMPOSITION = """\ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" {{ +#include +#include +#include +}} + +namespace NES {{ + +{nebula_name}PhysicalFunction::{nebula_name}PhysicalFunction({ctor_physical_args}) +{{ + parameterFunctions.reserve({n_args}); +{ctor_physical_pushes} +}} + +VarVal {nebula_name}PhysicalFunction::execute(const Record& record, ArenaRef& arena) const +{{ + std::vector parameterValues; + parameterValues.reserve(parameterFunctions.size()); + for (const auto& function : parameterFunctions) + {{ + parameterValues.emplace_back(function.execute(record, arena)); + }} + + auto ridA = parameterValues[0].cast>(); + auto fractionA = parameterValues[1].cast>(); + auto tsA = parameterValues[2].cast>(); + auto ridB = parameterValues[3].cast>(); + auto fractionB = parameterValues[4].cast>(); + auto tsB = parameterValues[5].cast>(); + + const auto result = nautilus::invoke( + +[](uint64_t ridAValue, double fractionAValue, uint64_t tsAValue, + uint64_t ridBValue, double fractionBValue, uint64_t tsBValue) -> {return_type} {{ + try + {{ + MEOS::Meos::ensureMeosInitialized(); + + const std::string tsAString = MEOS::Meos::convertEpochToTimestamp(tsAValue); + const std::string tsBString = MEOS::Meos::convertEpochToTimestamp(tsBValue); + std::string tnpointAWkt = fmt::format("NPoint({{}}, {{}})@{{}}", ridAValue, fractionAValue, tsAString); + std::string tnpointBWkt = fmt::format("NPoint({{}}, {{}})@{{}}", ridBValue, fractionBValue, tsBString); + + if (tnpointAWkt.empty() || tnpointBWkt.empty()) return 0; + + Temporal* tnpointA = tnpoint_in(tnpointAWkt.c_str()); + if (!tnpointA) return 0; + Temporal* tgeoA = tnpoint_to_tgeompoint(tnpointA); + if (!tgeoA) {{ free(tnpointA); return 0; }} + Temporal* tnpointB = tnpoint_in(tnpointBWkt.c_str()); + if (!tnpointB) {{ free(tgeoA); free(tnpointA); return 0; }} + Temporal* tgeoB = tnpoint_to_tgeompoint(tnpointB); + if (!tgeoB) {{ free(tnpointB); free(tgeoA); free(tnpointA); return 0; }} + + {return_type} r = {meos_call}(tgeoA, tgeoB); + free(tgeoB); + free(tnpointB); + free(tgeoA); + free(tnpointA); + return r; + }} + catch (const std::exception&) + {{ + return 0; + }} + }}, + ridA, fractionA, tsA, ridB, fractionB, tsB); + + return VarVal(result); +}} + +PhysicalFunctionRegistryReturnType PhysicalFunctionGeneratedRegistrar::Register{nebula_name}PhysicalFunction( + PhysicalFunctionRegistryArguments arguments) +{{ + PRECONDITION(arguments.childFunctions.size() == {n_args}, + "{nebula_name}PhysicalFunction requires {n_args} children but got {{}}", + arguments.childFunctions.size()); +{registrar_pushes} +}} + +}} // namespace NES +""" + + +# Physical .cpp template for tpose × static geom dwithin VIA COMPOSITION — +# the tpose composition body (W14) plus a trailing `double dist` forwarded +# to the 3-arg `_tgeo_geo` dwithin call. 6 SQL args: x, y, theta, ts, +# geometry, dist. +PHYSICAL_CPP_TEMPLATE_TPOSE_POINT_WITH_DIST_VIA_COMPOSITION = """\ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" {{ +#include +#include +#include +}} + +namespace NES {{ + +{nebula_name}PhysicalFunction::{nebula_name}PhysicalFunction({ctor_physical_args}) +{{ + parameterFunctions.reserve({n_args}); +{ctor_physical_pushes} +}} + +VarVal {nebula_name}PhysicalFunction::execute(const Record& record, ArenaRef& arena) const +{{ + std::vector parameterValues; + parameterValues.reserve(parameterFunctions.size()); + for (const auto& function : parameterFunctions) + {{ + parameterValues.emplace_back(function.execute(record, arena)); + }} + + auto x = parameterValues[0].cast>(); + auto y = parameterValues[1].cast>(); + auto theta = parameterValues[2].cast>(); + auto timestamp = parameterValues[3].cast>(); + auto geometry = parameterValues[4].cast(); + auto dist = parameterValues[5].cast>(); + + const auto result = nautilus::invoke( + +[](double xValue, double yValue, double thetaValue, uint64_t timestampValue, + const char* geometryPtr, uint32_t geometrySize, double distValue) -> {return_type} {{ + try + {{ + MEOS::Meos::ensureMeosInitialized(); + if (!(xValue >= -180.0 && xValue <= 180.0 && yValue >= -90.0 && yValue <= 90.0)) return 0; + + const std::string timestampString = MEOS::Meos::convertEpochToTimestamp(timestampValue); + std::string tposeWkt = fmt::format("Pose(Point({{}} {{}}), {{}})@{{}}", + xValue, yValue, thetaValue, timestampString); + std::string staticGeometryWkt(geometryPtr, geometrySize); + + while (!staticGeometryWkt.empty() && (staticGeometryWkt.front() == '\\'' || staticGeometryWkt.front() == '"')) + staticGeometryWkt.erase(staticGeometryWkt.begin()); + while (!staticGeometryWkt.empty() && (staticGeometryWkt.back() == '\\'' || staticGeometryWkt.back() == '"')) + staticGeometryWkt.pop_back(); + + if (tposeWkt.empty() || staticGeometryWkt.empty()) return 0; + + Temporal* tpose = tpose_in(tposeWkt.c_str()); + if (!tpose) return 0; + Temporal* tgeo = tpose_to_tpoint(tpose); + if (!tgeo) {{ free(tpose); return 0; }} + MEOS::Meos::StaticGeometry staticGeometry(staticGeometryWkt); + if (!staticGeometry.getGeometry()) {{ free(tgeo); free(tpose); return 0; }} + + {return_type} r = {meos_call}(tgeo, staticGeometry.getGeometry(), distValue); + free(tgeo); + free(tpose); + return r; + }} + catch (const std::exception&) + {{ + return 0; + }} + }}, + x, y, theta, timestamp, geometry.getContent(), geometry.getContentSize(), dist); + + return VarVal(result); +}} + +PhysicalFunctionRegistryReturnType PhysicalFunctionGeneratedRegistrar::Register{nebula_name}PhysicalFunction( + PhysicalFunctionRegistryArguments arguments) +{{ + PRECONDITION(arguments.childFunctions.size() == {n_args}, + "{nebula_name}PhysicalFunction requires {n_args} children but got {{}}", + arguments.childFunctions.size()); +{registrar_pushes} +}} + +}} // namespace NES +""" + + +# Physical .cpp template for tpose × tpose dwithin VIA COMPOSITION — the +# two-tpose composition body (W15) plus a trailing `double dist` forwarded +# to the 3-arg `_tgeo_tgeo` dwithin call. 9 SQL args. +PHYSICAL_CPP_TEMPLATE_TWO_TPOSE_POINTS_WITH_DIST_VIA_COMPOSITION = """\ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" {{ +#include +#include +#include +}} + +namespace NES {{ + +{nebula_name}PhysicalFunction::{nebula_name}PhysicalFunction({ctor_physical_args}) +{{ + parameterFunctions.reserve({n_args}); +{ctor_physical_pushes} +}} + +VarVal {nebula_name}PhysicalFunction::execute(const Record& record, ArenaRef& arena) const +{{ + std::vector parameterValues; + parameterValues.reserve(parameterFunctions.size()); + for (const auto& function : parameterFunctions) + {{ + parameterValues.emplace_back(function.execute(record, arena)); + }} + + auto xA = parameterValues[0].cast>(); + auto yA = parameterValues[1].cast>(); + auto thetaA = parameterValues[2].cast>(); + auto tsA = parameterValues[3].cast>(); + auto xB = parameterValues[4].cast>(); + auto yB = parameterValues[5].cast>(); + auto thetaB = parameterValues[6].cast>(); + auto tsB = parameterValues[7].cast>(); + auto dist = parameterValues[8].cast>(); + + const auto result = nautilus::invoke( + +[](double xAValue, double yAValue, double thetaAValue, uint64_t tsAValue, + double xBValue, double yBValue, double thetaBValue, uint64_t tsBValue, + double distValue) -> {return_type} {{ + try + {{ + MEOS::Meos::ensureMeosInitialized(); + if (!(xAValue >= -180.0 && xAValue <= 180.0 && yAValue >= -90.0 && yAValue <= 90.0)) return 0; + if (!(xBValue >= -180.0 && xBValue <= 180.0 && yBValue >= -90.0 && yBValue <= 90.0)) return 0; + + const std::string tsAString = MEOS::Meos::convertEpochToTimestamp(tsAValue); + const std::string tsBString = MEOS::Meos::convertEpochToTimestamp(tsBValue); + std::string tposeAWkt = fmt::format("Pose(Point({{}} {{}}), {{}})@{{}}", xAValue, yAValue, thetaAValue, tsAString); + std::string tposeBWkt = fmt::format("Pose(Point({{}} {{}}), {{}})@{{}}", xBValue, yBValue, thetaBValue, tsBString); + + if (tposeAWkt.empty() || tposeBWkt.empty()) return 0; + + Temporal* tposeA = tpose_in(tposeAWkt.c_str()); + if (!tposeA) return 0; + Temporal* tgeoA = tpose_to_tpoint(tposeA); + if (!tgeoA) {{ free(tposeA); return 0; }} + Temporal* tposeB = tpose_in(tposeBWkt.c_str()); + if (!tposeB) {{ free(tgeoA); free(tposeA); return 0; }} + Temporal* tgeoB = tpose_to_tpoint(tposeB); + if (!tgeoB) {{ free(tposeB); free(tgeoA); free(tposeA); return 0; }} + + {return_type} r = {meos_call}(tgeoA, tgeoB, distValue); + free(tgeoB); + free(tposeB); + free(tgeoA); + free(tposeA); + return r; + }} + catch (const std::exception&) + {{ + return 0; + }} + }}, + xA, yA, thetaA, tsA, xB, yB, thetaB, tsB, dist); + + return VarVal(result); +}} + +PhysicalFunctionRegistryReturnType PhysicalFunctionGeneratedRegistrar::Register{nebula_name}PhysicalFunction( + PhysicalFunctionRegistryArguments arguments) +{{ + PRECONDITION(arguments.childFunctions.size() == {n_args}, + "{nebula_name}PhysicalFunction requires {n_args} children but got {{}}", + arguments.childFunctions.size()); +{registrar_pushes} +}} + +}} // namespace NES +""" + + +# Physical .cpp template for tnpoint × static geom dwithin VIA COMPOSITION — +# tnpoint composition body (W18) plus a trailing `double dist` forwarded to +# the 3-arg `_tgeo_geo` dwithin call. 5 SQL args: rid, fraction, ts, +# geometry, dist. +PHYSICAL_CPP_TEMPLATE_TNPOINT_POINT_WITH_DIST_VIA_COMPOSITION = """\ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" {{ +#include +#include +#include +}} + +namespace NES {{ + +{nebula_name}PhysicalFunction::{nebula_name}PhysicalFunction({ctor_physical_args}) +{{ + parameterFunctions.reserve({n_args}); +{ctor_physical_pushes} +}} + +VarVal {nebula_name}PhysicalFunction::execute(const Record& record, ArenaRef& arena) const +{{ + std::vector parameterValues; + parameterValues.reserve(parameterFunctions.size()); + for (const auto& function : parameterFunctions) + {{ + parameterValues.emplace_back(function.execute(record, arena)); + }} + + auto rid = parameterValues[0].cast>(); + auto fraction = parameterValues[1].cast>(); + auto timestamp = parameterValues[2].cast>(); + auto geometry = parameterValues[3].cast(); + auto dist = parameterValues[4].cast>(); + + const auto result = nautilus::invoke( + +[](uint64_t ridValue, double fractionValue, uint64_t timestampValue, + const char* geometryPtr, uint32_t geometrySize, double distValue) -> {return_type} {{ + try + {{ + MEOS::Meos::ensureMeosInitialized(); + + const std::string timestampString = MEOS::Meos::convertEpochToTimestamp(timestampValue); + std::string tnpointWkt = fmt::format("NPoint({{}}, {{}})@{{}}", ridValue, fractionValue, timestampString); + std::string staticGeometryWkt(geometryPtr, geometrySize); + + while (!staticGeometryWkt.empty() && (staticGeometryWkt.front() == '\\'' || staticGeometryWkt.front() == '"')) + staticGeometryWkt.erase(staticGeometryWkt.begin()); + while (!staticGeometryWkt.empty() && (staticGeometryWkt.back() == '\\'' || staticGeometryWkt.back() == '"')) + staticGeometryWkt.pop_back(); + + if (tnpointWkt.empty() || staticGeometryWkt.empty()) return 0; + + Temporal* tnpoint = tnpoint_in(tnpointWkt.c_str()); + if (!tnpoint) return 0; + Temporal* tgeo = tnpoint_to_tgeompoint(tnpoint); + if (!tgeo) {{ free(tnpoint); return 0; }} + MEOS::Meos::StaticGeometry staticGeometry(staticGeometryWkt); + if (!staticGeometry.getGeometry()) {{ free(tgeo); free(tnpoint); return 0; }} + + {return_type} r = {meos_call}(tgeo, staticGeometry.getGeometry(), distValue); + free(tgeo); + free(tnpoint); + return r; + }} + catch (const std::exception&) + {{ + return 0; + }} + }}, + rid, fraction, timestamp, geometry.getContent(), geometry.getContentSize(), dist); + + return VarVal(result); +}} + +PhysicalFunctionRegistryReturnType PhysicalFunctionGeneratedRegistrar::Register{nebula_name}PhysicalFunction( + PhysicalFunctionRegistryArguments arguments) +{{ + PRECONDITION(arguments.childFunctions.size() == {n_args}, + "{nebula_name}PhysicalFunction requires {n_args} children but got {{}}", + arguments.childFunctions.size()); +{registrar_pushes} +}} + +}} // namespace NES +""" + + +# Physical .cpp template for tnpoint × tnpoint dwithin VIA COMPOSITION — the +# two-tnpoint composition body (W18) plus a trailing `double dist` forwarded +# to the 3-arg `_tgeo_tgeo` dwithin call. 7 SQL args. +PHYSICAL_CPP_TEMPLATE_TWO_TNPOINT_POINTS_WITH_DIST_VIA_COMPOSITION = """\ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" {{ +#include +#include +#include +}} + +namespace NES {{ + +{nebula_name}PhysicalFunction::{nebula_name}PhysicalFunction({ctor_physical_args}) +{{ + parameterFunctions.reserve({n_args}); +{ctor_physical_pushes} +}} + +VarVal {nebula_name}PhysicalFunction::execute(const Record& record, ArenaRef& arena) const +{{ + std::vector parameterValues; + parameterValues.reserve(parameterFunctions.size()); + for (const auto& function : parameterFunctions) + {{ + parameterValues.emplace_back(function.execute(record, arena)); + }} + + auto ridA = parameterValues[0].cast>(); + auto fractionA = parameterValues[1].cast>(); + auto tsA = parameterValues[2].cast>(); + auto ridB = parameterValues[3].cast>(); + auto fractionB = parameterValues[4].cast>(); + auto tsB = parameterValues[5].cast>(); + auto dist = parameterValues[6].cast>(); + + const auto result = nautilus::invoke( + +[](uint64_t ridAValue, double fractionAValue, uint64_t tsAValue, + uint64_t ridBValue, double fractionBValue, uint64_t tsBValue, + double distValue) -> {return_type} {{ + try + {{ + MEOS::Meos::ensureMeosInitialized(); + + const std::string tsAString = MEOS::Meos::convertEpochToTimestamp(tsAValue); + const std::string tsBString = MEOS::Meos::convertEpochToTimestamp(tsBValue); + std::string tnpointAWkt = fmt::format("NPoint({{}}, {{}})@{{}}", ridAValue, fractionAValue, tsAString); + std::string tnpointBWkt = fmt::format("NPoint({{}}, {{}})@{{}}", ridBValue, fractionBValue, tsBString); + + if (tnpointAWkt.empty() || tnpointBWkt.empty()) return 0; + + Temporal* tnpointA = tnpoint_in(tnpointAWkt.c_str()); + if (!tnpointA) return 0; + Temporal* tgeoA = tnpoint_to_tgeompoint(tnpointA); + if (!tgeoA) {{ free(tnpointA); return 0; }} + Temporal* tnpointB = tnpoint_in(tnpointBWkt.c_str()); + if (!tnpointB) {{ free(tgeoA); free(tnpointA); return 0; }} + Temporal* tgeoB = tnpoint_to_tgeompoint(tnpointB); + if (!tgeoB) {{ free(tnpointB); free(tgeoA); free(tnpointA); return 0; }} + + {return_type} r = {meos_call}(tgeoA, tgeoB, distValue); + free(tgeoB); + free(tnpointB); + free(tgeoA); + free(tnpointA); + return r; + }} + catch (const std::exception&) + {{ + return 0; + }} + }}, + ridA, fractionA, tsA, ridB, fractionB, tsB, dist); + + return VarVal(result); +}} + +PhysicalFunctionRegistryReturnType PhysicalFunctionGeneratedRegistrar::Register{nebula_name}PhysicalFunction( + PhysicalFunctionRegistryArguments arguments) +{{ + PRECONDITION(arguments.childFunctions.size() == {n_args}, + "{nebula_name}PhysicalFunction requires {n_args} children but got {{}}", + arguments.childFunctions.size()); +{registrar_pushes} +}} + +}} // namespace NES +""" + + +# Physical .cpp template for one-tcbuffer-point + static geom + dist — +# e.g. edwithin_tcbuffer_geo. Same per-event tcbuffer construction as +# PHYSICAL_CPP_TEMPLATE_TCBUFFER_POINT but trailing `double dist`. +# 6 SQL args: lon, lat, radius, ts, geometry, dist. +PHYSICAL_CPP_TEMPLATE_TCBUFFER_POINT_WITH_DIST = """\ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" {{ +#include +#include +}} + +namespace NES {{ + +{nebula_name}PhysicalFunction::{nebula_name}PhysicalFunction({ctor_physical_args}) +{{ + parameterFunctions.reserve({n_args}); +{ctor_physical_pushes} +}} + +VarVal {nebula_name}PhysicalFunction::execute(const Record& record, ArenaRef& arena) const +{{ + std::vector parameterValues; + parameterValues.reserve(parameterFunctions.size()); + for (const auto& function : parameterFunctions) + {{ + parameterValues.emplace_back(function.execute(record, arena)); + }} + + auto lon = parameterValues[0].cast>(); + auto lat = parameterValues[1].cast>(); + auto radius = parameterValues[2].cast>(); + auto timestamp = parameterValues[3].cast>(); + auto geometry = parameterValues[4].cast(); + auto dist = parameterValues[5].cast>(); + + const auto result = nautilus::invoke( + +[](double lonValue, double latValue, double radiusValue, uint64_t timestampValue, + const char* geometryPtr, uint32_t geometrySize, double distValue) -> {return_type} {{ + try + {{ + MEOS::Meos::ensureMeosInitialized(); + if (!(lonValue >= -180.0 && lonValue <= 180.0 && latValue >= -90.0 && latValue <= 90.0)) return 0; + if (radiusValue < 0.0) return 0; + + const std::string timestampString = MEOS::Meos::convertEpochToTimestamp(timestampValue); + std::string tcbufferWkt = fmt::format("Cbuffer(Point({{}} {{}}),{{}})@{{}}", + lonValue, latValue, radiusValue, timestampString); + std::string staticGeometryWkt(geometryPtr, geometrySize); + + while (!staticGeometryWkt.empty() && (staticGeometryWkt.front() == '\\'' || staticGeometryWkt.front() == '"')) + staticGeometryWkt.erase(staticGeometryWkt.begin()); + while (!staticGeometryWkt.empty() && (staticGeometryWkt.back() == '\\'' || staticGeometryWkt.back() == '"')) + staticGeometryWkt.pop_back(); + + if (tcbufferWkt.empty() || staticGeometryWkt.empty()) return 0; + + Temporal* tcbuffer = tcbuffer_in(tcbufferWkt.c_str()); + if (!tcbuffer) return 0; + MEOS::Meos::StaticGeometry staticGeometry(staticGeometryWkt); + if (!staticGeometry.getGeometry()) {{ free(tcbuffer); return 0; }} + + {return_type} r = {meos_call}(tcbuffer, staticGeometry.getGeometry(), distValue); + free(tcbuffer); + return r; + }} + catch (const std::exception&) + {{ + return 0; + }} + }}, + lon, lat, radius, timestamp, geometry.getContent(), geometry.getContentSize(), dist); + + return VarVal(result); +}} + +PhysicalFunctionRegistryReturnType PhysicalFunctionGeneratedRegistrar::Register{nebula_name}PhysicalFunction( + PhysicalFunctionRegistryArguments arguments) +{{ + PRECONDITION(arguments.childFunctions.size() == {n_args}, + "{nebula_name}PhysicalFunction requires {n_args} children but got {{}}", + arguments.childFunctions.size()); +{registrar_pushes} +}} + +}} // namespace NES +""" + +# Physical .cpp template for one-tcbuffer-point + static Cbuffer + dist — +# e.g. edwithin_tcbuffer_cbuffer. +# 6 SQL args: lon, lat, radius, ts, cbufferLiteral, dist. +PHYSICAL_CPP_TEMPLATE_TCBUFFER_POINT_CBUFFER_WITH_DIST = """\ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" {{ +#include +#include +}} + +namespace NES {{ + +{nebula_name}PhysicalFunction::{nebula_name}PhysicalFunction({ctor_physical_args}) +{{ + parameterFunctions.reserve({n_args}); +{ctor_physical_pushes} +}} + +VarVal {nebula_name}PhysicalFunction::execute(const Record& record, ArenaRef& arena) const +{{ + std::vector parameterValues; + parameterValues.reserve(parameterFunctions.size()); + for (const auto& function : parameterFunctions) + {{ + parameterValues.emplace_back(function.execute(record, arena)); + }} + + auto lon = parameterValues[0].cast>(); + auto lat = parameterValues[1].cast>(); + auto radius = parameterValues[2].cast>(); + auto timestamp = parameterValues[3].cast>(); + auto cbufLit = parameterValues[4].cast(); + auto dist = parameterValues[5].cast>(); + + const auto result = nautilus::invoke( + +[](double lonValue, double latValue, double radiusValue, uint64_t timestampValue, + const char* cbufLitPtr, uint32_t cbufLitSize, double distValue) -> {return_type} {{ + try + {{ + MEOS::Meos::ensureMeosInitialized(); + if (!(lonValue >= -180.0 && lonValue <= 180.0 && latValue >= -90.0 && latValue <= 90.0)) return 0; + if (radiusValue < 0.0) return 0; + + const std::string timestampString = MEOS::Meos::convertEpochToTimestamp(timestampValue); + std::string tcbufferWkt = fmt::format("Cbuffer(Point({{}} {{}}),{{}})@{{}}", + lonValue, latValue, radiusValue, timestampString); + std::string cbufferLiteral(cbufLitPtr, cbufLitSize); + + while (!cbufferLiteral.empty() && (cbufferLiteral.front() == '\\'' || cbufferLiteral.front() == '"')) + cbufferLiteral.erase(cbufferLiteral.begin()); + while (!cbufferLiteral.empty() && (cbufferLiteral.back() == '\\'' || cbufferLiteral.back() == '"')) + cbufferLiteral.pop_back(); + + if (tcbufferWkt.empty() || cbufferLiteral.empty()) return 0; + + Temporal* tcbuffer = tcbuffer_in(tcbufferWkt.c_str()); + if (!tcbuffer) return 0; + Cbuffer* cb = cbuffer_in(cbufferLiteral.c_str()); + if (!cb) {{ free(tcbuffer); return 0; }} + + {return_type} r = {meos_call}(tcbuffer, cb, distValue); + free(tcbuffer); + free(cb); + return r; + }} + catch (const std::exception&) + {{ + return 0; + }} + }}, + lon, lat, radius, timestamp, cbufLit.getContent(), cbufLit.getContentSize(), dist); + + return VarVal(result); +}} + +PhysicalFunctionRegistryReturnType PhysicalFunctionGeneratedRegistrar::Register{nebula_name}PhysicalFunction( + PhysicalFunctionRegistryArguments arguments) +{{ + PRECONDITION(arguments.childFunctions.size() == {n_args}, + "{nebula_name}PhysicalFunction requires {n_args} children but got {{}}", + arguments.childFunctions.size()); +{registrar_pushes} +}} + +}} // namespace NES +""" + +# Physical .cpp template for two-tcbuffer-points + dist — e.g. +# edwithin_tcbuffer_tcbuffer. 9 SQL args: lonA, latA, radiusA, tsA, +# lonB, latB, radiusB, tsB, dist. +PHYSICAL_CPP_TEMPLATE_TWO_TCBUFFER_POINTS_WITH_DIST = """\ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" {{ +#include +#include +}} + +namespace NES {{ + +{nebula_name}PhysicalFunction::{nebula_name}PhysicalFunction({ctor_physical_args}) +{{ + parameterFunctions.reserve({n_args}); +{ctor_physical_pushes} +}} + +VarVal {nebula_name}PhysicalFunction::execute(const Record& record, ArenaRef& arena) const +{{ + std::vector parameterValues; + parameterValues.reserve(parameterFunctions.size()); + for (const auto& function : parameterFunctions) + {{ + parameterValues.emplace_back(function.execute(record, arena)); + }} + + auto lonA = parameterValues[0].cast>(); + auto latA = parameterValues[1].cast>(); + auto radiusA = parameterValues[2].cast>(); + auto tsA = parameterValues[3].cast>(); + auto lonB = parameterValues[4].cast>(); + auto latB = parameterValues[5].cast>(); + auto radiusB = parameterValues[6].cast>(); + auto tsB = parameterValues[7].cast>(); + auto dist = parameterValues[8].cast>(); + + const auto result = nautilus::invoke( + +[](double lonAValue, double latAValue, double radiusAValue, uint64_t tsAValue, + double lonBValue, double latBValue, double radiusBValue, uint64_t tsBValue, + double distValue) -> {return_type} {{ + try + {{ + MEOS::Meos::ensureMeosInitialized(); + if (!(lonAValue >= -180.0 && lonAValue <= 180.0 && latAValue >= -90.0 && latAValue <= 90.0)) return 0; + if (!(lonBValue >= -180.0 && lonBValue <= 180.0 && latBValue >= -90.0 && latBValue <= 90.0)) return 0; + if (radiusAValue < 0.0 || radiusBValue < 0.0) return 0; + + const std::string tsAString = MEOS::Meos::convertEpochToTimestamp(tsAValue); + const std::string tsBString = MEOS::Meos::convertEpochToTimestamp(tsBValue); + std::string wktA = fmt::format("Cbuffer(Point({{}} {{}}),{{}})@{{}}", lonAValue, latAValue, radiusAValue, tsAString); + std::string wktB = fmt::format("Cbuffer(Point({{}} {{}}),{{}})@{{}}", lonBValue, latBValue, radiusBValue, tsBString); + + Temporal* tA = tcbuffer_in(wktA.c_str()); + if (!tA) return 0; + Temporal* tB = tcbuffer_in(wktB.c_str()); + if (!tB) {{ free(tA); return 0; }} + + {return_type} r = {meos_call}(tA, tB, distValue); + free(tA); + free(tB); + return r; + }} + catch (const std::exception&) + {{ + return 0; + }} + }}, + lonA, latA, radiusA, tsA, lonB, latB, radiusB, tsB, dist); + + return VarVal(result); +}} + +PhysicalFunctionRegistryReturnType PhysicalFunctionGeneratedRegistrar::Register{nebula_name}PhysicalFunction( + PhysicalFunctionRegistryArguments arguments) +{{ + PRECONDITION(arguments.childFunctions.size() == {n_args}, + "{nebula_name}PhysicalFunction requires {n_args} children but got {{}}", + arguments.childFunctions.size()); +{registrar_pushes} +}} + +}} // namespace NES +""" + + +# Physical .cpp template for TWO tcbuffer points (no static arg) — e.g. +# eintersects_tcbuffer_tcbuffer. Two per-event tcbuffer instants built +# from (lonA, latA, radiusA, tsA) and (lonB, latB, radiusB, tsB). +# MEOS signature: `int fn(const Temporal*, const Temporal*)`. +# 8 SQL args. +PHYSICAL_CPP_TEMPLATE_TWO_TCBUFFER_POINTS = """\ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" {{ +#include +#include +}} + +namespace NES {{ + +{nebula_name}PhysicalFunction::{nebula_name}PhysicalFunction({ctor_physical_args}) +{{ + parameterFunctions.reserve({n_args}); +{ctor_physical_pushes} +}} + +VarVal {nebula_name}PhysicalFunction::execute(const Record& record, ArenaRef& arena) const +{{ + std::vector parameterValues; + parameterValues.reserve(parameterFunctions.size()); + for (const auto& function : parameterFunctions) + {{ + parameterValues.emplace_back(function.execute(record, arena)); + }} + + auto lonA = parameterValues[0].cast>(); + auto latA = parameterValues[1].cast>(); + auto radiusA = parameterValues[2].cast>(); + auto tsA = parameterValues[3].cast>(); + auto lonB = parameterValues[4].cast>(); + auto latB = parameterValues[5].cast>(); + auto radiusB = parameterValues[6].cast>(); + auto tsB = parameterValues[7].cast>(); + + const auto result = nautilus::invoke( + +[](double lonAValue, double latAValue, double radiusAValue, uint64_t tsAValue, + double lonBValue, double latBValue, double radiusBValue, uint64_t tsBValue) -> {return_type} {{ + try + {{ + MEOS::Meos::ensureMeosInitialized(); + if (!(lonAValue >= -180.0 && lonAValue <= 180.0 && latAValue >= -90.0 && latAValue <= 90.0)) return 0; + if (!(lonBValue >= -180.0 && lonBValue <= 180.0 && latBValue >= -90.0 && latBValue <= 90.0)) return 0; + if (radiusAValue < 0.0 || radiusBValue < 0.0) return 0; + + const std::string tsAString = MEOS::Meos::convertEpochToTimestamp(tsAValue); + const std::string tsBString = MEOS::Meos::convertEpochToTimestamp(tsBValue); + std::string wktA = fmt::format("Cbuffer(Point({{}} {{}}),{{}})@{{}}", lonAValue, latAValue, radiusAValue, tsAString); + std::string wktB = fmt::format("Cbuffer(Point({{}} {{}}),{{}})@{{}}", lonBValue, latBValue, radiusBValue, tsBString); + + Temporal* tA = tcbuffer_in(wktA.c_str()); + if (!tA) return 0; + Temporal* tB = tcbuffer_in(wktB.c_str()); + if (!tB) {{ free(tA); return 0; }} + + {return_type} r = {meos_call}(tA, tB); + free(tA); + free(tB); + return r; + }} + catch (const std::exception&) + {{ + return 0; + }} + }}, + lonA, latA, radiusA, tsA, lonB, latB, radiusB, tsB); + + return VarVal(result); +}} + +PhysicalFunctionRegistryReturnType PhysicalFunctionGeneratedRegistrar::Register{nebula_name}PhysicalFunction( + PhysicalFunctionRegistryArguments arguments) +{{ + PRECONDITION(arguments.childFunctions.size() == {n_args}, + "{nebula_name}PhysicalFunction requires {n_args} children but got {{}}", + arguments.childFunctions.size()); +{registrar_pushes} +}} + +}} // namespace NES +""" + + +# Physical .cpp template for one-tcbuffer-point operators with a STATIC +# CBUFFER second arg — e.g. econtains_tcbuffer_cbuffer. Same per-event +# tcbuffer construction as PHYSICAL_CPP_TEMPLATE_TCBUFFER_POINT; the +# second arg is parsed via cbuffer_in() from a literal WKT +# "Cbuffer(Point(lon lat),radius)" instead of as a GSERIALIZED geometry. +# MEOS signature: `int fn(const Temporal*, const Cbuffer*)`. +# 5 SQL args: lon, lat, radius, ts, cbufferLiteral. +PHYSICAL_CPP_TEMPLATE_TCBUFFER_POINT_CBUFFER = """\ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" {{ +#include +#include +}} + +namespace NES {{ + +{nebula_name}PhysicalFunction::{nebula_name}PhysicalFunction({ctor_physical_args}) +{{ + parameterFunctions.reserve({n_args}); +{ctor_physical_pushes} +}} + +VarVal {nebula_name}PhysicalFunction::execute(const Record& record, ArenaRef& arena) const +{{ + std::vector parameterValues; + parameterValues.reserve(parameterFunctions.size()); + for (const auto& function : parameterFunctions) + {{ + parameterValues.emplace_back(function.execute(record, arena)); + }} + + auto lon = parameterValues[0].cast>(); + auto lat = parameterValues[1].cast>(); + auto radius = parameterValues[2].cast>(); + auto timestamp = parameterValues[3].cast>(); + auto cbufLit = parameterValues[4].cast(); + + const auto result = nautilus::invoke( + +[](double lonValue, + double latValue, + double radiusValue, + uint64_t timestampValue, + const char* cbufLitPtr, + uint32_t cbufLitSize) -> {return_type} {{ + try + {{ + MEOS::Meos::ensureMeosInitialized(); + if (!(lonValue >= -180.0 && lonValue <= 180.0 && latValue >= -90.0 && latValue <= 90.0)) return 0; + if (radiusValue < 0.0) return 0; + + const std::string timestampString = MEOS::Meos::convertEpochToTimestamp(timestampValue); + std::string tcbufferWkt = fmt::format("Cbuffer(Point({{}} {{}}),{{}})@{{}}", + lonValue, latValue, radiusValue, timestampString); + std::string cbufferLiteral(cbufLitPtr, cbufLitSize); + + while (!cbufferLiteral.empty() && (cbufferLiteral.front() == '\\'' || cbufferLiteral.front() == '"')) + cbufferLiteral.erase(cbufferLiteral.begin()); + while (!cbufferLiteral.empty() && (cbufferLiteral.back() == '\\'' || cbufferLiteral.back() == '"')) + cbufferLiteral.pop_back(); + + if (tcbufferWkt.empty() || cbufferLiteral.empty()) return 0; + + Temporal* tcbuffer = tcbuffer_in(tcbufferWkt.c_str()); + if (!tcbuffer) return 0; + Cbuffer* cb = cbuffer_in(cbufferLiteral.c_str()); + if (!cb) {{ free(tcbuffer); return 0; }} + + {return_type} r = {meos_call}(tcbuffer, cb); + free(tcbuffer); + free(cb); + return r; + }} + catch (const std::exception&) + {{ + return 0; + }} + }}, + lon, lat, radius, timestamp, cbufLit.getContent(), cbufLit.getContentSize()); + + return VarVal(result); +}} + +PhysicalFunctionRegistryReturnType PhysicalFunctionGeneratedRegistrar::Register{nebula_name}PhysicalFunction( + PhysicalFunctionRegistryArguments arguments) +{{ + PRECONDITION(arguments.childFunctions.size() == {n_args}, + "{nebula_name}PhysicalFunction requires {n_args} children but got {{}}", + arguments.childFunctions.size()); +{registrar_pushes} +}} + +}} // namespace NES +""" + + +# Physical .cpp template for one-tnumber-point operators with a trailing +# scalar (double or int) — e.g. nad_tfloat_float, nad_tint_int. The MEOS +# call signature is ` fn(const Temporal*, )`. +# 3 args: value, timestamp, scalar. +PHYSICAL_CPP_TEMPLATE_TNUMBER_POINT_WITH_SCALAR = """\ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" {{ +#include +}} + +namespace NES {{ + +{nebula_name}PhysicalFunction::{nebula_name}PhysicalFunction({ctor_physical_args}) +{{ + parameterFunctions.reserve({n_args}); +{ctor_physical_pushes} +}} + +VarVal {nebula_name}PhysicalFunction::execute(const Record& record, ArenaRef& arena) const +{{ + std::vector parameterValues; + parameterValues.reserve(parameterFunctions.size()); + for (const auto& function : parameterFunctions) + {{ + parameterValues.emplace_back(function.execute(record, arena)); + }} + + auto value = parameterValues[0].cast>(); + auto timestamp = parameterValues[1].cast>(); + auto scalar = parameterValues[2].cast>(); + + const auto result = nautilus::invoke( + +[]({tnumber_value_cpp_type} valueValue, + uint64_t timestampValue, + {scalar_cpp_type} scalarValue) -> {return_type} {{ + try + {{ + MEOS::Meos::ensureMeosInitialized(); + const std::string tsString = MEOS::Meos::convertEpochToTimestamp(timestampValue); + std::string wkt = fmt::format("{tnumber_wkt_format}", valueValue, tsString); + Temporal* temp = {tnumber_in_fn}(wkt.c_str()); + if (!temp) return 0; + {return_type} r = {meos_call}(temp, scalarValue); + free(temp); + return r; + }} + catch (const std::exception&) + {{ + return 0; + }} + }}, + value, timestamp, scalar); + + return VarVal(result); +}} + +PhysicalFunctionRegistryReturnType PhysicalFunctionGeneratedRegistrar::Register{nebula_name}PhysicalFunction( + PhysicalFunctionRegistryArguments arguments) +{{ + PRECONDITION(arguments.childFunctions.size() == {n_args}, + "{nebula_name}PhysicalFunction requires {n_args} children but got {{}}", + arguments.childFunctions.size()); +{registrar_pushes} +}} + +}} // namespace NES +""" + +# Physical .cpp template for two-tnumber-point operators (e.g. nad_tfloat_tfloat, +# nad_tint_tint). MEOS signature ` fn(const Temporal*, const Temporal*)`. +# 4 args: valueA, tsA, valueB, tsB. +PHYSICAL_CPP_TEMPLATE_TWO_TNUMBER_POINTS = """\ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" {{ +#include +}} + +namespace NES {{ + +{nebula_name}PhysicalFunction::{nebula_name}PhysicalFunction({ctor_physical_args}) +{{ + parameterFunctions.reserve({n_args}); +{ctor_physical_pushes} +}} + +VarVal {nebula_name}PhysicalFunction::execute(const Record& record, ArenaRef& arena) const +{{ + std::vector parameterValues; + parameterValues.reserve(parameterFunctions.size()); + for (const auto& function : parameterFunctions) + {{ + parameterValues.emplace_back(function.execute(record, arena)); + }} + + auto valueA = parameterValues[0].cast>(); + auto tsA = parameterValues[1].cast>(); + auto valueB = parameterValues[2].cast>(); + auto tsB = parameterValues[3].cast>(); + + const auto result = nautilus::invoke( + +[]({tnumber_value_cpp_type} valueAValue, uint64_t tsAValue, + {tnumber_value_cpp_type} valueBValue, uint64_t tsBValue) -> {return_type} {{ + try + {{ + MEOS::Meos::ensureMeosInitialized(); + const std::string tsAStr = MEOS::Meos::convertEpochToTimestamp(tsAValue); + const std::string tsBStr = MEOS::Meos::convertEpochToTimestamp(tsBValue); + std::string wktA = fmt::format("{tnumber_wkt_format}", valueAValue, tsAStr); + std::string wktB = fmt::format("{tnumber_wkt_format}", valueBValue, tsBStr); + Temporal* tempA = {tnumber_in_fn}(wktA.c_str()); + if (!tempA) return 0; + Temporal* tempB = {tnumber_in_fn}(wktB.c_str()); + if (!tempB) {{ free(tempA); return 0; }} + {return_type} r = {meos_call}(tempA, tempB); + free(tempA); + free(tempB); + return r; + }} + catch (const std::exception&) + {{ + return 0; + }} + }}, + valueA, tsA, valueB, tsB); + + return VarVal(result); +}} + +PhysicalFunctionRegistryReturnType PhysicalFunctionGeneratedRegistrar::Register{nebula_name}PhysicalFunction( + PhysicalFunctionRegistryArguments arguments) +{{ + PRECONDITION(arguments.childFunctions.size() == {n_args}, + "{nebula_name}PhysicalFunction requires {n_args} children but got {{}}", + arguments.childFunctions.size()); +{registrar_pushes} +}} + +}} // namespace NES +""" + + +# Physical .cpp for scalar-FIRST tnumber operators — the MEOS signature is +# ` fn(scalar, const Temporal*)` (e.g. ever_eq_float_tfloat, +# always_lt_int_tint). Identical to TNUMBER_POINT_WITH_SCALAR except the MEOS +# call passes the scalar as the FIRST argument. Per-event SQL shape is still +# (value, timestamp, scalar) so it reuses DISPATCH_CASE_TNUMBER_POINT_WITH_SCALAR. +PHYSICAL_CPP_TEMPLATE_TNUMBER_SCALAR_FIRST = """\ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" {{ +#include +}} + +namespace NES {{ + +{nebula_name}PhysicalFunction::{nebula_name}PhysicalFunction({ctor_physical_args}) +{{ + parameterFunctions.reserve({n_args}); +{ctor_physical_pushes} +}} + +VarVal {nebula_name}PhysicalFunction::execute(const Record& record, ArenaRef& arena) const +{{ + std::vector parameterValues; + parameterValues.reserve(parameterFunctions.size()); + for (const auto& function : parameterFunctions) + {{ + parameterValues.emplace_back(function.execute(record, arena)); + }} + + auto value = parameterValues[0].cast>(); + auto timestamp = parameterValues[1].cast>(); + auto scalar = parameterValues[2].cast>(); + + const auto result = nautilus::invoke( + +[]({tnumber_value_cpp_type} valueValue, + uint64_t timestampValue, + {scalar_cpp_type} scalarValue) -> {return_type} {{ + try + {{ + MEOS::Meos::ensureMeosInitialized(); + const std::string tsString = MEOS::Meos::convertEpochToTimestamp(timestampValue); + std::string wkt = fmt::format("{tnumber_wkt_format}", valueValue, tsString); + Temporal* temp = {tnumber_in_fn}(wkt.c_str()); + if (!temp) return 0; + {return_type} r = {meos_call}(scalarValue, temp); + free(temp); + return r; + }} + catch (const std::exception&) + {{ + return 0; + }} + }}, + value, timestamp, scalar); + + return VarVal(result); +}} + +PhysicalFunctionRegistryReturnType PhysicalFunctionGeneratedRegistrar::Register{nebula_name}PhysicalFunction( + PhysicalFunctionRegistryArguments arguments) +{{ + PRECONDITION(arguments.childFunctions.size() == {n_args}, + "{nebula_name}PhysicalFunction requires {n_args} children but got {{}}", + arguments.childFunctions.size()); +{registrar_pushes} +}} + +}} // namespace NES +""" + + +# =========================================================================== +# Generalized per-event operator assembler (the "generic" shape). +# +# Composes a physical .cpp from three orthogonal parts instead of a hand-written +# template per shape: +# - INPUT builder : how to construct the primary `Temporal* temp` of a +# given temporal type from per-event fields, +# - EXTRA args : 0+ trailing MEOS args (scalar / static geometry), +# - RETURN marshaler : how the scalar MEOS return becomes the VarVal. +# Scope: Temporal-input operators with a SCALAR return (int/double/bool) — the +# proven lambda-returns-scalar pattern. Variable-sized (text*/GSERIALIZED*) and +# Temporal*-extract returns, and Set/Span/Box inputs, are out of this assembler. +# =========================================================================== + +# input_type -> dict(fields=[(name,cpp)], header, build) where build is C++ that +# defines `Temporal* {var}` (NULL-checked) from the fields. {z} = zero-return literal. +GENERIC_INPUTS = { + "tgeompoint": dict(fields=[("lon", "double"), ("lat", "double"), ("ts", "uint64_t")], header="meos_geo.h", build=( + ' if (!(lon >= -180.0 && lon <= 180.0 && lat >= -90.0 && lat <= 90.0)) return {z};\n' + ' std::string {var}Wkt = fmt::format("SRID=4326;Point({{}} {{}})@{{}}", lon, lat, MEOS::Meos::convertEpochToTimestamp(ts));\n' + ' Temporal* {var} = tgeompoint_in({var}Wkt.c_str());\n' + ' if (!{var}) return {z};\n')), + "tgeometry": dict(fields=[("geomWkt", "VariableSizedData"), ("ts", "uint64_t")], header="meos_geo.h", build=( + ' std::string {var}G(geomWktPtr, geomWktSize);\n' + ' std::string {var}Wkt = {var}G + "@" + MEOS::Meos::convertEpochToTimestamp(ts);\n' + ' Temporal* {var} = tgeometry_in({var}Wkt.c_str());\n' + ' if (!{var}) return {z};\n')), + "tcbuffer": dict(fields=[("lon", "double"), ("lat", "double"), ("radius", "double"), ("ts", "uint64_t")], header="meos_cbuffer.h", build=( + ' if (!(lon >= -180.0 && lon <= 180.0 && lat >= -90.0 && lat <= 90.0) || radius < 0.0) return {z};\n' + ' std::string {var}Wkt = fmt::format("Cbuffer(Point({{}} {{}}),{{}})@{{}}", lon, lat, radius, MEOS::Meos::convertEpochToTimestamp(ts));\n' + ' Temporal* {var} = tcbuffer_in({var}Wkt.c_str());\n' + ' if (!{var}) return {z};\n')), + "tpose": dict(fields=[("x", "double"), ("y", "double"), ("theta", "double"), ("ts", "uint64_t")], header="meos_pose.h", build=( + ' std::string {var}Wkt = fmt::format("Pose(Point({{}} {{}}),{{}})@{{}}", x, y, theta, MEOS::Meos::convertEpochToTimestamp(ts));\n' + ' Temporal* {var} = tpose_in({var}Wkt.c_str());\n' + ' if (!{var}) return {z};\n')), + "tnpoint": dict(fields=[("rid", "int64_t"), ("frac", "double"), ("ts", "uint64_t")], header="meos_npoint.h", build=( + ' if (frac < 0.0 || frac > 1.0) return {z};\n' + ' std::string {var}Wkt = fmt::format("NPoint({{}},{{}})@{{}}", rid, frac, MEOS::Meos::convertEpochToTimestamp(ts));\n' + ' Temporal* {var} = tnpoint_in({var}Wkt.c_str());\n' + ' if (!{var}) return {z};\n')), + "tfloat": dict(fields=[("value", "double"), ("ts", "uint64_t")], header="meos_geo.h", build=( + ' std::string {var}Wkt = fmt::format("{{}}@{{}}", value, MEOS::Meos::convertEpochToTimestamp(ts));\n' + ' Temporal* {var} = tfloat_in({var}Wkt.c_str());\n' + ' if (!{var}) return {z};\n')), + "tint": dict(fields=[("value", "int32_t"), ("ts", "uint64_t")], header="meos_geo.h", build=( + ' std::string {var}Wkt = fmt::format("{{}}@{{}}", value, MEOS::Meos::convertEpochToTimestamp(ts));\n' + ' Temporal* {var} = tint_in({var}Wkt.c_str());\n' + ' if (!{var}) return {z};\n')), + "tbool": dict(fields=[("value", "bool"), ("ts", "uint64_t")], header="meos_geo.h", build=( + ' std::string {var}Wkt = fmt::format("{{}}@{{}}", value ? "t" : "f", MEOS::Meos::convertEpochToTimestamp(ts));\n' + ' Temporal* {var} = tbool_in({var}Wkt.c_str());\n' + ' if (!{var}) return {z};\n')), + # The efficient mechanism: the operand is an UPSTREAM MEOS value (a windowed + # mini-trip trajectory, or any temporal) carried as a VARSIZED hex-WKB field, + # not rebuilt from per-event scalars. The MEOS function library composes over + # such values like scalar functions over a float. hex-WKB is the (testable, + # ASCII) canonical form; raw WKB is a later optimization. + "wkb_temporal": dict(fields=[("traj", "VariableSizedData")], header="meos_geo.h", build=( + ' std::string {var}Hex(trajPtr, trajSize);\n' + ' Temporal* {var} = temporal_from_hexwkb({var}Hex.c_str());\n' + ' if (!{var}) return {z};\n')), + # A bounding STBox carried as a VARSIZED text field — the per-vehicle + # TSPATIAL_EXTENT output (stbox_out). Used as the first operand of a + # cross-vehicle predicate f(boxA, boxB); the second box is a `box` extra arg + # (also stbox_in). Freed via free(temp) by the generic cleanup. + "stbox_text": dict(fields=[("box", "VariableSizedData")], header="meos_geo.h", build=( + ' std::string {var}S(boxPtr, boxSize);\n' + ' while (!{var}S.empty() && ({var}S.front()==\'\\\'\' || {var}S.front()==\'"\')) {var}S.erase({var}S.begin());\n' + ' while (!{var}S.empty() && ({var}S.back()==\'\\\'\' || {var}S.back()==\'"\')) {var}S.pop_back();\n' + ' STBox* {var} = stbox_in({var}S.c_str());\n' + ' if (!{var}) return {z};\n')), +} + +# return_kind -> (cpp_return_type, nautilus_return, zero_literal, extract_fn|None) +# For a direct scalar return extract_fn is None. For a Temporal*-returning +# transform/restriction whose single-instant result carries a scalar value, the +# extract_fn is the result type's *_start_value accessor (the value at the +# single instant); the wrapper temporal is freed. +GENERIC_RETURNS = { + "int": ("int", "INT32", "0", None), + "double": ("double", "FLOAT64", "0.0", None), + "bool": ("bool", "BOOLEAN", "false", None), + "extract_int": ("int", "INT32", "0", "tint_start_value"), + "extract_double": ("double", "FLOAT64", "0.0", "tfloat_start_value"), + "extract_bool": ("bool", "BOOLEAN", "false", "tbool_start_value"), + # A Temporal*-returning transform whose result is serialized back to hex-WKB + # and emitted as a VARSIZED field (the cross-operator exchange form). Handled + # by assemble_wkb_output, not the scalar GENERIC_PHYSICAL_TEMPLATE. + "wkb": ("VariableSizedData", "VARSIZED", "nullptr", None), +} + + +def _generic_field_decl(name, cpp): + """Lambda parameter declaration + the cast expression for one event field.""" + if cpp == "VariableSizedData": + return None # handled specially (pointer + size pair) + return cpp + + +def assemble_wkb_output(op): + """Physical .cpp for a per-event op that calls f(Temporal*, Temporal*) -> + Temporal* over two hex-WKB VARSIZED operands and emits the result as a + hex-WKB VARSIZED field. The MEOS call + serialization run inside one + nautilus::invoke (returning the heap hex string); the bytes are then copied + into an arena-allocated VariableSizedData (the canonical VARSIZED-output + idiom, see TemporalDerivativeExpAggregation::lower). A null/empty result + yields a zero-length VARSIZED.""" + name = op["nebula_name"] + headers = {"meos.h", "meos_geo.h"} + for h in op.get("extra_headers", []): + headers.add(h) + inc = "\n".join(f"#include <{h}>" for h in + ["meos.h"] + sorted(h for h in headers if h != "meos.h")) + registrar = "\n".join( + [f" auto arg{i} = std::move(arguments.childFunctions[{i}]);" for i in range(2)] + + [f" return {name}PhysicalFunction(std::move(arg0), std::move(arg1));"]) + physical_args = ("PhysicalFunction trajFunction,\n" + " PhysicalFunction arg0Function") + pushes = (" parameterFunctions.push_back(std::move(trajFunction));\n" + " parameterFunctions.push_back(std::move(arg0Function));") + return GENERIC_PHYSICAL_WKB_TEMPLATE.format( + nebula_name=name, includes=inc, meos_call=op["meos_call"], + ctor_physical_args=physical_args, ctor_physical_pushes=pushes, + registrar_pushes=registrar) + + +def assemble_generic_physical(op): + """Build the physical .cpp for a 'generic' (build_generic) operator.""" + name = op["nebula_name"] + if op["return_kind"] == "wkb": + return assemble_wkb_output(op) + inp = GENERIC_INPUTS[op["input_type"]] + ret_cpp, _, zero, extract_fn = GENERIC_RETURNS[op["return_kind"]] + extras = op.get("extra_args", []) + + # Ordered (lambda-param) fields: primary input fields, then each extra arg's. + fields = list(inp["fields"]) + headers = {"meos.h", inp["header"]} + # op-level extra headers (e.g. meos_npoint.h / meos_pose.h for a meos_call + # whose type lives outside meos.h/meos_geo.h). + for h in op.get("extra_headers", []): + headers.add(h) + call_terms = ["temp"] + parse_lines = [] + box_frees = [] # raw box/span literals to free after the MEOS call + for i, ex in enumerate(extras): + if ex["kind"] == "scalar": + fields.append((f"arg{i}", ex["cpp"])) + call_terms.append(f"arg{i}") + elif ex["kind"] == "geom": + fields.append((f"arg{i}", "VariableSizedData")) + headers.add("meos_geo.h") + parse_lines.append( + f' std::string arg{i}S(arg{i}Ptr, arg{i}Size);\n' + f' while (!arg{i}S.empty() && (arg{i}S.front()==\'\\\'\' || arg{i}S.front()==\'"\')) arg{i}S.erase(arg{i}S.begin());\n' + f' while (!arg{i}S.empty() && (arg{i}S.back()==\'\\\'\' || arg{i}S.back()==\'"\')) arg{i}S.pop_back();\n' + f' MEOS::Meos::StaticGeometry arg{i}G(arg{i}S);\n' + f' if (!arg{i}G.getGeometry()) {{ free(temp); return {zero}; }}\n') + call_terms.append(f"arg{i}G.getGeometry()") + elif ex["kind"] == "box": + # query-literal STBox/TBox/Span parsed from a text constant; freed + # after the call. parser = stbox_in / tbox_in / tstzspan_in; box_type + # = STBox / TBox / Span. + fields.append((f"arg{i}", "VariableSizedData")) + headers.add(ex.get("header", "meos.h")) + parse_lines.append( + f' std::string arg{i}S(arg{i}Ptr, arg{i}Size);\n' + f' while (!arg{i}S.empty() && (arg{i}S.front()==\'\\\'\' || arg{i}S.front()==\'"\')) arg{i}S.erase(arg{i}S.begin());\n' + f' while (!arg{i}S.empty() && (arg{i}S.back()==\'\\\'\' || arg{i}S.back()==\'"\')) arg{i}S.pop_back();\n' + f' {ex["box_type"]}* arg{i}B = {ex["parser"]}(arg{i}S.c_str());\n' + f' if (!arg{i}B) {{ free(temp); return {zero}; }}\n') + call_terms.append(f"arg{i}B") + box_frees.append(f"free(arg{i}B);") + elif ex["kind"] == "wkb_temporal": + # a SECOND temporal operand carried as a VARSIZED hex-WKB field (e.g. + # another per-vehicle aggregate output) — parsed via temporal_from_hexwkb, + # freed after the call. For cross-vehicle f(trajA, trajB). + fields.append((f"arg{i}", "VariableSizedData")) + headers.add("meos_geo.h") + parse_lines.append( + f' std::string arg{i}Hex(arg{i}Ptr, arg{i}Size);\n' + f' Temporal* arg{i}T = temporal_from_hexwkb(arg{i}Hex.c_str());\n' + f' if (!arg{i}T) {{ free(temp); return {zero}; }}\n') + call_terms.append(f"arg{i}T") + box_frees.append(f"free(arg{i}T);") + + # Build the parameterValues casts, lambda params, and invoke args from fields. + casts, lparams, invoke = [], [], [] + idx = 0 + for fn, cpp in fields: + if cpp == "VariableSizedData": + casts.append(f" auto {fn} = parameterValues[{idx}].cast();") + lparams.append(f"const char* {fn}Ptr, uint32_t {fn}Size") + invoke.append(f"{fn}.getContent(), {fn}.getContentSize()") + else: + casts.append(f" auto {fn} = parameterValues[{idx}].cast>();") + lparams.append(f"{cpp} {fn}") + invoke.append(fn) + idx += 1 + n_args = idx + + build = inp["build"].format(var="temp", z=zero) + "".join(parse_lines) + inc = "\n".join(f"#include <{h}>" for h in + ["meos.h"] + sorted(h for h in headers if h != "meos.h")) + + # box_first ops (e.g. above_stbox_tspatial(box, temp)) call with the box/span + # literal before the temporal; the default order is temporal-first. + if op.get("box_first") and len(call_terms) == 2: + call_terms = [call_terms[1], call_terms[0]] + callargs = ", ".join(call_terms) + bf = "".join(f" {x}\n" for x in box_frees) + if extract_fn is None: + call_marshal = (f" {ret_cpp} r = {op['meos_call']}({callargs});\n" + f" free(temp);\n" + f"{bf}" + f" return r;") + else: + # Temporal*-returning transform: result is a single-instant temporal; take + # its value via the result type's *_start_value accessor, free both. + call_marshal = (f" Temporal* res = {op['meos_call']}({callargs});\n" + f" free(temp);\n" + f"{bf}" + f" if (!res) return {zero};\n" + f" {ret_cpp} r = {extract_fn}(res);\n" + f" free(res);\n" + f" return r;") + + # physical-hpp/logical ctor args are PhysicalFunction/LogicalFunction per child. + physical_args = ",\n ".join( + f"PhysicalFunction {fn}Function" for fn, _ in fields) + pushes = "\n".join(f" parameterFunctions.push_back(std::move({fn}Function));" for fn, _ in fields) + registrar = "\n".join( + [f" auto arg{i} = std::move(arguments.childFunctions[{i}]);" for i in range(n_args)] + + [f" return {name}PhysicalFunction(" + ", ".join(f"std::move(arg{i})" for i in range(n_args)) + ");"]) + + return GENERIC_PHYSICAL_TEMPLATE.format( + nebula_name=name, includes=inc, + ctor_physical_args=physical_args, n_args=n_args, ctor_physical_pushes=pushes, + casts="\n".join(casts), lambda_params=",\n ".join(lparams), + return_type=ret_cpp, build=build, call_marshal=call_marshal, + zero=zero, invoke_args=", ".join(invoke), registrar_pushes=registrar) + + +GENERIC_PHYSICAL_TEMPLATE = """\ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" {{ +{includes} +}} + +namespace NES {{ + +{nebula_name}PhysicalFunction::{nebula_name}PhysicalFunction({ctor_physical_args}) +{{ + parameterFunctions.reserve({n_args}); +{ctor_physical_pushes} +}} + +VarVal {nebula_name}PhysicalFunction::execute(const Record& record, ArenaRef& arena) const +{{ + std::vector parameterValues; + parameterValues.reserve(parameterFunctions.size()); + for (const auto& function : parameterFunctions) + {{ + parameterValues.emplace_back(function.execute(record, arena)); + }} + +{casts} + + const auto result = nautilus::invoke( + +[]({lambda_params}) -> {return_type} {{ + try + {{ + MEOS::Meos::ensureMeosInitialized(); +{build} +{call_marshal} + }} + catch (const std::exception&) + {{ + return {zero}; + }} + }}, + {invoke_args}); + + return VarVal(result); +}} + +PhysicalFunctionRegistryReturnType PhysicalFunctionGeneratedRegistrar::Register{nebula_name}PhysicalFunction( + PhysicalFunctionRegistryArguments arguments) +{{ + PRECONDITION(arguments.childFunctions.size() == {n_args}, + "{nebula_name}PhysicalFunction requires {n_args} children but got {{}}", + arguments.childFunctions.size()); +{registrar_pushes} +}} + +}} // namespace NES +""" + + +GENERIC_PHYSICAL_WKB_TEMPLATE = """\ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" {{ +{includes} +}} + +namespace NES {{ + +{nebula_name}PhysicalFunction::{nebula_name}PhysicalFunction({ctor_physical_args}) +{{ + parameterFunctions.reserve(2); +{ctor_physical_pushes} +}} + +VarVal {nebula_name}PhysicalFunction::execute(const Record& record, ArenaRef& arena) const +{{ + std::vector parameterValues; + parameterValues.reserve(parameterFunctions.size()); + for (const auto& function : parameterFunctions) + {{ + parameterValues.emplace_back(function.execute(record, arena)); + }} + + auto traj = parameterValues[0].cast(); + auto arg0 = parameterValues[1].cast(); + + // Call MEOS f(Temporal*, Temporal*) -> Temporal* over the two hex-WKB + // operands and serialize the result back to hex-WKB inside the invoke; the + // returned heap string is copied into the arena below. Both operands and the + // MEOS result are freed here. + auto hexStr = nautilus::invoke( + +[](const char* aPtr, uint32_t aSize, const char* bPtr, uint32_t bSize) -> char* + {{ + try + {{ + MEOS::Meos::ensureMeosInitialized(); + std::string aHex(aPtr, aSize); + std::string bHex(bPtr, bSize); + Temporal* a = temporal_from_hexwkb(aHex.c_str()); + if (!a) return (char*) nullptr; + Temporal* b = temporal_from_hexwkb(bHex.c_str()); + if (!b) {{ free(a); return (char*) nullptr; }} + Temporal* res = {meos_call}(a, b); + free(a); + free(b); + if (!res) return (char*) nullptr; + size_t hexSize = 0; + char* hexOut = temporal_as_hexwkb(res, 0, &hexSize); + free(res); + return hexOut; + }} + catch (const std::exception&) + {{ + return (char*) nullptr; + }} + }}, + traj.getContent(), traj.getContentSize(), arg0.getContent(), arg0.getContentSize()); + + const auto hexLen = nautilus::invoke( + +[](const char* s) -> uint32_t {{ return s ? (uint32_t) strlen(s) : (uint32_t) 0; }}, + hexStr); + + auto variableSized = arena.allocateVariableSizedData(hexLen); + + nautilus::invoke( + +[](int8_t* dest, const char* s, uint32_t len) -> void + {{ + if (s) + {{ + memcpy(dest, s, len); + free((void*) s); + }} + }}, + variableSized.getContent(), hexStr, hexLen); + + return variableSized; +}} + +PhysicalFunctionRegistryReturnType PhysicalFunctionGeneratedRegistrar::Register{nebula_name}PhysicalFunction( + PhysicalFunctionRegistryArguments arguments) +{{ + PRECONDITION(arguments.childFunctions.size() == 2, + "{nebula_name}PhysicalFunction requires 2 children but got {{}}", + arguments.childFunctions.size()); +{registrar_pushes} +}} + +}} // namespace NES +""" + + +# =========================================================================== +# Parser-glue dispatch-case templates (one per shape). +# The shape is encoded by the build_* flag; the dispatch block produces a +# LogicalFunction ctor invocation matching the C++ operator's arg order. +# +# Mariana's existing TGEO_AT_STBOX and EDWITHIN_TGEO_GEO blocks are the +# in-tree reference for the constantBuilder→functionBuilder lift pattern. +# =========================================================================== + +# 4-arg shape: lon, lat, ts, geometry (geometry is the only constant — WKT). +DISPATCH_CASE_ONE_TEMPORAL_POINT = """\ + /* BEGIN CODEGEN PARSER GLUE: {sql_token} */ + case AntlrSQLLexer::{sql_token}: + {{ + const auto argCount = context->expression().size(); + if (argCount != 4) + throw InvalidQuerySyntax("{sql_token} requires exactly 4 arguments (lon, lat, timestamp, geometry), but got {{}}", argCount); + + /* Lift the WKT constant into the function builder */ + while (!helpers.top().constantBuilder.empty()) + {{ + auto v = std::move(helpers.top().constantBuilder.back()); + helpers.top().constantBuilder.pop_back(); + helpers.top().functionBuilder.emplace_back( + ConstantValueLogicalFunction( + DataTypeProvider::provideDataType(DataType::Type::VARSIZED), std::move(v))); + }} + + auto geometry = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + auto timestamp = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + auto lat = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + auto lon = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + + helpers.top().functionBuilder.emplace_back( + {nebula_name}LogicalFunction(lon, lat, timestamp, geometry)); + }} + break; + /* END CODEGEN PARSER GLUE: {sql_token} */ +""" + +# 6-arg shape: lonA, latA, tsA, lonB, latB, tsB (no constants). +DISPATCH_CASE_TWO_TEMPORAL_POINTS = """\ + /* BEGIN CODEGEN PARSER GLUE: {sql_token} */ + case AntlrSQLLexer::{sql_token}: + {{ + const auto argCount = context->expression().size(); + if (argCount != 6) + throw InvalidQuerySyntax("{sql_token} requires exactly 6 arguments (lonA, latA, tsA, lonB, latB, tsB), but got {{}}", argCount); + + auto tsB = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + auto latB = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + auto lonB = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + auto tsA = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + auto latA = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + auto lonA = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + + helpers.top().functionBuilder.emplace_back( + {nebula_name}LogicalFunction(lonA, latA, tsA, lonB, latB, tsB)); + }} + break; + /* END CODEGEN PARSER GLUE: {sql_token} */ +""" + +# 8-arg shape: xA, yA, thetaA, tsA, xB, yB, thetaB, tsB — two tpose instants, +# each lifted to a tgeompoint via tpose_to_tpoint at run time (W15 composition). +DISPATCH_CASE_TWO_TPOSE_POINTS = """\ + /* BEGIN CODEGEN PARSER GLUE: {sql_token} */ + case AntlrSQLLexer::{sql_token}: + {{ + const auto argCount = context->expression().size(); + if (argCount != 8) + throw InvalidQuerySyntax("{sql_token} requires exactly 8 arguments (xA, yA, thetaA, tsA, xB, yB, thetaB, tsB), but got {{}}", argCount); + + auto tsB = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + auto thetaB = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + auto yB = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + auto xB = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + auto tsA = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + auto thetaA = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + auto yA = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + auto xA = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + + helpers.top().functionBuilder.emplace_back( + {nebula_name}LogicalFunction(xA, yA, thetaA, tsA, xB, yB, thetaB, tsB)); + }} + break; + /* END CODEGEN PARSER GLUE: {sql_token} */ +""" + +# 5-arg shape: lon, lat, ts, geometry, dist (both geometry and dist are constants). +# Constant lift uses mariana's pattern: TRUE/FALSE → BOOLEAN, strtod-clean → FLOAT64, else → VARSIZED. +DISPATCH_CASE_ONE_TEMPORAL_POINT_WITH_DIST = """\ + /* BEGIN CODEGEN PARSER GLUE: {sql_token} */ + case AntlrSQLLexer::{sql_token}: + {{ + const auto argCount = context->expression().size(); + if (argCount != 5) + throw InvalidQuerySyntax("{sql_token} requires exactly 5 arguments (lon, lat, timestamp, geometry, distance), but got {{}}", argCount); + + /* Lift constants (geometry + distance) — same shape as EDWITHIN_TGEO_GEO */ + while (!helpers.top().constantBuilder.empty()) + {{ + auto constantValue = std::move(helpers.top().constantBuilder.back()); + helpers.top().constantBuilder.pop_back(); + + DataType dataType; + const auto upperValue = Util::toUpperCase(constantValue); + if (upperValue == "TRUE" || upperValue == "FALSE") + {{ + dataType = DataTypeProvider::provideDataType(DataType::Type::BOOLEAN); + }} + else + {{ + char* endPtr = nullptr; + std::strtod(constantValue.c_str(), &endPtr); + if (endPtr != nullptr && *endPtr == '\\0') + dataType = DataTypeProvider::provideDataType(DataType::Type::FLOAT64); + else + dataType = DataTypeProvider::provideDataType(DataType::Type::VARSIZED); + }} + helpers.top().functionBuilder.emplace_back(ConstantValueLogicalFunction(dataType, std::move(constantValue))); + }} + + /* After lift: [lon, lat, ts, distance, geometry] (geometry pushed last because lifted last in LIFO) */ + auto geometry = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + auto dist = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + auto timestamp = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + auto lat = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + auto lon = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + + helpers.top().functionBuilder.emplace_back( + {nebula_name}LogicalFunction(lon, lat, timestamp, geometry, dist)); + }} + break; + /* END CODEGEN PARSER GLUE: {sql_token} */ +""" + +# 7-arg shape: lonA, latA, tsA, lonB, latB, tsB, dist (only dist is constant). +DISPATCH_CASE_TWO_TEMPORAL_POINTS_WITH_DIST = """\ + /* BEGIN CODEGEN PARSER GLUE: {sql_token} */ + case AntlrSQLLexer::{sql_token}: + {{ + const auto argCount = context->expression().size(); + if (argCount != 7) + throw InvalidQuerySyntax("{sql_token} requires exactly 7 arguments (lonA, latA, tsA, lonB, latB, tsB, distance), but got {{}}", argCount); + + /* Lift the distance constant */ + while (!helpers.top().constantBuilder.empty()) + {{ + auto v = std::move(helpers.top().constantBuilder.back()); + helpers.top().constantBuilder.pop_back(); + helpers.top().functionBuilder.emplace_back( + ConstantValueLogicalFunction( + DataTypeProvider::provideDataType(DataType::Type::FLOAT64), std::move(v))); + }} + + auto dist = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + auto tsB = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + auto latB = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + auto lonB = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + auto tsA = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + auto latA = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + auto lonA = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + + helpers.top().functionBuilder.emplace_back( + {nebula_name}LogicalFunction(lonA, latA, tsA, lonB, latB, tsB, dist)); + }} + break; + /* END CODEGEN PARSER GLUE: {sql_token} */ +""" + + +# 6-arg shape: lon, lat, radius, ts, geometry, dist — tcbuffer × static geom + dist. +DISPATCH_CASE_TCBUFFER_POINT_WITH_DIST = """\ + /* BEGIN CODEGEN PARSER GLUE: {sql_token} */ + case AntlrSQLLexer::{sql_token}: + {{ + const auto argCount = context->expression().size(); + if (argCount != 6) + throw InvalidQuerySyntax("{sql_token} requires exactly 6 arguments (lon, lat, radius, timestamp, blob, distance), but got {{}}", argCount); + + while (!helpers.top().constantBuilder.empty()) + {{ + auto constantValue = std::move(helpers.top().constantBuilder.back()); + helpers.top().constantBuilder.pop_back(); + + DataType dataType; + char* endPtr = nullptr; + std::strtod(constantValue.c_str(), &endPtr); + if (endPtr != nullptr && *endPtr == '\\0') + dataType = DataTypeProvider::provideDataType(DataType::Type::FLOAT64); + else + dataType = DataTypeProvider::provideDataType(DataType::Type::VARSIZED); + helpers.top().functionBuilder.emplace_back(ConstantValueLogicalFunction(dataType, std::move(constantValue))); + }} + + auto blobLast = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + auto distLast = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + auto timestamp = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + auto radius = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + auto lat = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + auto lon = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + + helpers.top().functionBuilder.emplace_back( + {nebula_name}LogicalFunction(lon, lat, radius, timestamp, blobLast, distLast)); + }} + break; + /* END CODEGEN PARSER GLUE: {sql_token} */ +""" + +# 9-arg shape: lonA, latA, radiusA, tsA, lonB, latB, radiusB, tsB, dist — two tcbuffers + dist. +DISPATCH_CASE_TWO_TCBUFFER_POINTS_WITH_DIST = """\ + /* BEGIN CODEGEN PARSER GLUE: {sql_token} */ + case AntlrSQLLexer::{sql_token}: + {{ + const auto argCount = context->expression().size(); + if (argCount != 9) + throw InvalidQuerySyntax("{sql_token} requires exactly 9 arguments (lonA, latA, radiusA, tsA, lonB, latB, radiusB, tsB, distance), but got {{}}", argCount); + + while (!helpers.top().constantBuilder.empty()) + {{ + auto v = std::move(helpers.top().constantBuilder.back()); + helpers.top().constantBuilder.pop_back(); + helpers.top().functionBuilder.emplace_back( + ConstantValueLogicalFunction( + DataTypeProvider::provideDataType(DataType::Type::FLOAT64), std::move(v))); + }} + + auto dist = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + auto tsB = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + auto radiusB = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + auto latB = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + auto lonB = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + auto tsA = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + auto radiusA = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + auto latA = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + auto lonA = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + + helpers.top().functionBuilder.emplace_back( + {nebula_name}LogicalFunction(lonA, latA, radiusA, tsA, lonB, latB, radiusB, tsB, dist)); + }} + break; + /* END CODEGEN PARSER GLUE: {sql_token} */ +""" + +# 8-arg shape: lonA, latA, radiusA, tsA, lonB, latB, radiusB, tsB (no constants). +DISPATCH_CASE_TWO_TCBUFFER_POINTS = """\ + /* BEGIN CODEGEN PARSER GLUE: {sql_token} */ + case AntlrSQLLexer::{sql_token}: + {{ + const auto argCount = context->expression().size(); + if (argCount != 8) + throw InvalidQuerySyntax("{sql_token} requires exactly 8 arguments (lonA, latA, radiusA, tsA, lonB, latB, radiusB, tsB), but got {{}}", argCount); + + auto tsB = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + auto radiusB = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + auto latB = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + auto lonB = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + auto tsA = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + auto radiusA = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + auto latA = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + auto lonA = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + + helpers.top().functionBuilder.emplace_back( + {nebula_name}LogicalFunction(lonA, latA, radiusA, tsA, lonB, latB, radiusB, tsB)); + }} + break; + /* END CODEGEN PARSER GLUE: {sql_token} */ +""" + +# 5-arg shape: lon, lat, radius, ts, geometry — tcbuffer × static geom. +# Geometry is the only constant (lifted to FLOAT64 / VARSIZED via the same lift +# pattern as the existing with-dist templates). +DISPATCH_CASE_TCBUFFER_POINT = """\ + /* BEGIN CODEGEN PARSER GLUE: {sql_token} */ + case AntlrSQLLexer::{sql_token}: + {{ + const auto argCount = context->expression().size(); + if (argCount != 5) + throw InvalidQuerySyntax("{sql_token} requires exactly 5 arguments (lon, lat, radius, timestamp, geometry), but got {{}}", argCount); + + /* Lift the WKT constant into the function builder */ + while (!helpers.top().constantBuilder.empty()) + {{ + auto v = std::move(helpers.top().constantBuilder.back()); + helpers.top().constantBuilder.pop_back(); + helpers.top().functionBuilder.emplace_back( + ConstantValueLogicalFunction( + DataTypeProvider::provideDataType(DataType::Type::VARSIZED), std::move(v))); + }} + + auto geometry = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + auto timestamp = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + auto radius = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + auto lat = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + auto lon = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + + helpers.top().functionBuilder.emplace_back( + {nebula_name}LogicalFunction(lon, lat, radius, timestamp, geometry)); + }} + break; + /* END CODEGEN PARSER GLUE: {sql_token} */ +""" + +# 3-arg shape: value, ts, scalar (scalar may be FLOAT64 or INT32, only one constant). +DISPATCH_CASE_TNUMBER_POINT_WITH_SCALAR = """\ + /* BEGIN CODEGEN PARSER GLUE: {sql_token} */ + case AntlrSQLLexer::{sql_token}: + {{ + const auto argCount = context->expression().size(); + if (argCount != 3) + throw InvalidQuerySyntax("{sql_token} requires exactly 3 arguments (value, timestamp, scalar), but got {{}}", argCount); + + /* Lift the scalar constant — accept FLOAT64 (strtod-clean) and INT32 */ + while (!helpers.top().constantBuilder.empty()) + {{ + auto constantValue = std::move(helpers.top().constantBuilder.back()); + helpers.top().constantBuilder.pop_back(); + DataType dataType; + char* endPtr = nullptr; + std::strtod(constantValue.c_str(), &endPtr); + if (endPtr != nullptr && *endPtr == '\\0') + dataType = DataTypeProvider::provideDataType(DataType::Type::FLOAT64); + else + dataType = DataTypeProvider::provideDataType(DataType::Type::VARSIZED); + helpers.top().functionBuilder.emplace_back(ConstantValueLogicalFunction(dataType, std::move(constantValue))); + }} + + auto scalar = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + auto timestamp = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + auto value = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + + helpers.top().functionBuilder.emplace_back( + {nebula_name}LogicalFunction(value, timestamp, scalar)); + }} + break; + /* END CODEGEN PARSER GLUE: {sql_token} */ +""" + +# 4-arg shape: valueA, tsA, valueB, tsB (no constants). +DISPATCH_CASE_TWO_TNUMBER_POINTS = """\ + /* BEGIN CODEGEN PARSER GLUE: {sql_token} */ + case AntlrSQLLexer::{sql_token}: + {{ + const auto argCount = context->expression().size(); + if (argCount != 4) + throw InvalidQuerySyntax("{sql_token} requires exactly 4 arguments (valueA, tsA, valueB, tsB), but got {{}}", argCount); + + auto tsB = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + auto valueB = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + auto tsA = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + auto valueA = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + + helpers.top().functionBuilder.emplace_back( + {nebula_name}LogicalFunction(valueA, tsA, valueB, tsB)); + }} + break; + /* END CODEGEN PARSER GLUE: {sql_token} */ +""" + + +def dispatch_case_for(op): + """Pick the dispatch-case template that matches an operator's shape.""" + if op.get("build_two_temporal_points_with_dist"): + return DISPATCH_CASE_TWO_TEMPORAL_POINTS_WITH_DIST + if op.get("build_temporal_point_with_dist"): + return DISPATCH_CASE_ONE_TEMPORAL_POINT_WITH_DIST + if op.get("build_two_temporal_points"): + return DISPATCH_CASE_TWO_TEMPORAL_POINTS + if op.get("build_two_tpose_points_via_composition"): + return DISPATCH_CASE_TWO_TPOSE_POINTS + if op.get("build_tnpoint_point_via_composition"): + # 4-arg SQL shape (rid, fraction, ts, geometry) — same arity as a + # one-temporal-point op; the parser only pops/forwards by arity. + return DISPATCH_CASE_ONE_TEMPORAL_POINT + if op.get("build_two_tnpoint_points_via_composition"): + # 6-arg SQL shape (ridA, fracA, tsA, ridB, fracB, tsB) — same arity + # as a two-temporal-points op. + return DISPATCH_CASE_TWO_TEMPORAL_POINTS + # dwithin (W20): the with-dist composition shapes reuse existing with-dist + # dispatches by arity + constant pattern (geometry+dist or dist-only lifted). + if op.get("build_tpose_point_with_dist_via_composition"): + # 6-arg: x, y, theta, ts (cols) + geometry, dist (constants) — same + # shape as tcbuffer × geom + dist. + return DISPATCH_CASE_TCBUFFER_POINT_WITH_DIST + if op.get("build_two_tpose_points_with_dist_via_composition"): + # 9-arg: 8 cols + dist constant — same shape as two-tcbuffer + dist. + return DISPATCH_CASE_TWO_TCBUFFER_POINTS_WITH_DIST + if op.get("build_tnpoint_point_with_dist_via_composition"): + # 5-arg: rid, fraction, ts (cols) + geometry, dist (constants). + return DISPATCH_CASE_ONE_TEMPORAL_POINT_WITH_DIST + if op.get("build_two_tnpoint_points_with_dist_via_composition"): + # 7-arg: 6 cols + dist constant. + return DISPATCH_CASE_TWO_TEMPORAL_POINTS_WITH_DIST + if op.get("build_temporal_point") or op.get("build_temporal_point_restriction"): + # Both shapes share the same 4-arg dispatch (lon, lat, ts, geom); + # only the physical-cpp body differs (filter-predicate int return vs. + # restriction-survival int return). + return DISPATCH_CASE_ONE_TEMPORAL_POINT + if op.get("build_tcbuffer_point") or op.get("build_tcbuffer_point_cbuffer") \ + or op.get("build_tpose_point_via_composition"): + # All three shapes share the same 5-arg dispatch (lon/x, lat/y, radius/theta, ts, blob); + # only the physical-cpp body differs (blob parsed as GSERIALIZED vs Cbuffer; per-event + # type construction is tcbuffer vs tpose with optional tpose_to_tpoint composition). + return DISPATCH_CASE_TCBUFFER_POINT + if op.get("build_two_tcbuffer_points"): + return DISPATCH_CASE_TWO_TCBUFFER_POINTS + if op.get("build_tcbuffer_point_with_dist") or op.get("build_tcbuffer_point_cbuffer_with_dist"): + # Same 6-arg SQL dispatch for both — only physical-cpp body differs (blob parser). + return DISPATCH_CASE_TCBUFFER_POINT_WITH_DIST + if op.get("build_two_tcbuffer_points_with_dist"): + return DISPATCH_CASE_TWO_TCBUFFER_POINTS_WITH_DIST + if op.get("build_tnumber_point_with_scalar") or op.get("build_tnumber_scalar_first"): + # scalar-first reuses the same 3-arg (value, ts, scalar) SQL dispatch; + # only the physical-cpp body differs (MEOS call arg order). + return DISPATCH_CASE_TNUMBER_POINT_WITH_SCALAR + if op.get("build_two_tnumber_points"): + return DISPATCH_CASE_TWO_TNUMBER_POINTS + return None + + +def _dispatch_case_current(op): + """Parser dispatch case in the CURRENT AntlrSQLParser style (visit()/return + LogicalFunction), matching the live W2..W147 chain. Generic over arity. + Returns the final C++ string (no further .format). END marker only (the + live style carries no BEGIN marker).""" + build_key = next((k for k in _TRGEO_PHYS_DISPATCH if op.get(k)), None) + n = len(_TRGEO_PHYS_DISPATCH[build_key][1]) if build_key else len(op.get("args", [])) + tok, name = op["sql_token"], op["nebula_name"] + visits = "\n".join( + f" auto arg{i} = visit(ctx->functionParam({i})).as();" + for i in range(n)) + ctor_args = ", ".join(f"std::move(arg{i})" for i in range(n)) + return f""" case AntlrSQLParser::{tok}: {{ + PRECONDITION(ctx->functionParam().size() == {n}, + "{name} requires {n} args but got {{}}", + ctx->functionParam().size()); +{visits} + return LogicalFunction({name}LogicalFunction({ctor_args})); + }} + /* END CODEGEN PARSER GLUE: {tok} */""" + + +def _generic_dispatch_case(op): + """Parser dispatch case for a 'generic' operator. Arity is baked in (n = + number of event fields); lifts string/number constants (geometry blobs, + scalars) then pops n children in reverse and builds the LogicalFunction. + Returns the final C++ string (no further .format).""" + n = len(op["args"]) + tok, name = op["sql_token"], op["nebula_name"] + pops = "\n".join( + f" auto a{i} = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back();" + for i in range(n - 1, -1, -1)) + ctor_args = ", ".join(f"a{i}" for i in range(n)) + return f""" /* BEGIN CODEGEN PARSER GLUE: {tok} */ + case AntlrSQLLexer::{tok}: + {{ + const auto argCount = context->expression().size(); + if (argCount != {n}) + throw InvalidQuerySyntax("{tok} requires exactly {n} arguments, but got {{}}", argCount); + + while (!helpers.top().constantBuilder.empty()) + {{ + auto constantValue = std::move(helpers.top().constantBuilder.back()); + helpers.top().constantBuilder.pop_back(); + DataType dataType; + char* endPtr = nullptr; + std::strtod(constantValue.c_str(), &endPtr); + if (endPtr != nullptr && *endPtr == '\\0') + dataType = DataTypeProvider::provideDataType(DataType::Type::FLOAT64); + else + dataType = DataTypeProvider::provideDataType(DataType::Type::VARSIZED); + helpers.top().functionBuilder.emplace_back(ConstantValueLogicalFunction(dataType, std::move(constantValue))); + }} + +{pops} + + helpers.top().functionBuilder.emplace_back({name}LogicalFunction({ctor_args})); + }} + break; + /* END CODEGEN PARSER GLUE: {tok} */ +""" + + +# =========================================================================== +# Idempotent injectors — each scans for a per-op marker and inserts only +# if not present, so re-runs are safe. +# =========================================================================== + +def inject_cmake_entries(operators, output_root: Path) -> int: + """Append per-op `add_plugin(...)` entries to the Meos CMakeLists files + (logical + physical layers). Idempotent: skips ops already listed.""" + n_added = 0 + for layer in ("logical", "physical"): + cml = output_root / f"nes-{layer}-operators/src/Functions/Meos/CMakeLists.txt" + if not cml.exists(): + sys.stderr.write(f" ! cmake-entries: {cml} not found, skipping {layer} layer\n") + continue + body = cml.read_text() + layer_suffix = "Logical" if layer == "logical" else "Physical" + new_lines = [] + for op in operators: + entry = ( + f"add_plugin({op['nebula_name']} {layer_suffix}Function " + f"nes-{layer}-operators {op['nebula_name']}{layer_suffix}Function.cpp)" + ) + if entry in body or f"add_plugin({op['nebula_name']} {layer_suffix}Function" in body: + continue + new_lines.append(entry) + if new_lines: + block = "\n".join(new_lines) + "\n" + # The physical Meos CMakeLists wraps its add_plugin calls in + # `if(NES_ENABLE_MEOS) ... endif()`; new entries MUST land inside the + # guard (before the final endif) so they inherit the MEOS plugin + # include dir that provides MEOSWrapper.hpp. The logical layer has no + # guard, so it appends at EOF. + idx = body.rfind("\nendif()") + if idx != -1: + insert_at = idx + 1 # just before the final `endif()` line + body = body[:insert_at] + block + body[insert_at:] + cml.write_text(body) + else: + with cml.open("a") as f: + f.write(block) + sys.stderr.write(f" ✓ cmake-entries ({layer}): added {len(new_lines)} entry(ies)\n") + n_added += len(new_lines) + return n_added + + +def inject_g4(operators, g4_path: Path) -> int: + """Inject lexer-token + functionName-alternation entries into AntlrSQL.g4. + Idempotent: skips tokens already present.""" + if not g4_path.exists(): + sys.stderr.write(f" ! g4: {g4_path} not found, skipping\n") + return 0 + body = g4_path.read_text() + n_added = 0 + + # 1) Lexer-token entries — insert just before the WATERMARK: lexer token. + new_tokens = [] + for op in operators: + tok = op["sql_token"] + if re.search(rf"^{re.escape(tok)}\s*:", body, re.MULTILINE): + continue + new_tokens.append( + f"{tok}: '{tok}' | '{tok.lower()}';" + ) + if new_tokens: + anchor_re = re.compile(r"^WATERMARK:.*$", re.MULTILINE) + m = anchor_re.search(body) + if m is None: + sys.stderr.write(f" ! g4: WATERMARK lexer anchor not found; cannot inject tokens\n") + else: + insertion = "/* BEGIN CODEGEN LEXER TOKENS */\n" + "\n".join(new_tokens) + "\n/* END CODEGEN LEXER TOKENS */\n" + # If the BEGIN marker already exists, append inside that block; else insert before WATERMARK. + if "/* BEGIN CODEGEN LEXER TOKENS */" in body: + body = re.sub( + r"(/\* BEGIN CODEGEN LEXER TOKENS \*/\n)(.*?)(/\* END CODEGEN LEXER TOKENS \*/)", + lambda mm: mm.group(1) + mm.group(2) + "\n".join(new_tokens) + "\n" + mm.group(3), + body, + count=1, + flags=re.DOTALL, + ) + else: + body = body[: m.start()] + insertion + body[m.start():] + n_added += len(new_tokens) + sys.stderr.write(f" ✓ g4 lexer-tokens: added {len(new_tokens)} token(s)\n") + + # 2) functionName: alternation — append missing tokens before the trailing ';'. + fn_re = re.compile(r"^functionName:\s*([^;]+);", re.MULTILINE) + m = fn_re.search(body) + if m is None: + sys.stderr.write(f" ! g4: functionName production not found\n") + else: + alternation = m.group(1) + new_alts = [] + for op in operators: + tok = op["sql_token"] + if re.search(rf"\b{re.escape(tok)}\b", alternation): + continue + new_alts.append(tok) + if new_alts: + new_alt_text = alternation.rstrip() + " | " + " | ".join(new_alts) + body = body[: m.start()] + f"functionName: {new_alt_text};" + body[m.end():] + sys.stderr.write(f" ✓ g4 functionName: added {len(new_alts)} alternative(s)\n") + + g4_path.write_text(body) + return n_added + + +def inject_parser_cpp(operators, cpp_path: Path) -> int: + """Inject #include + dispatch-case block into AntlrSQLQueryPlanCreator.cpp. + Idempotent: skips when the per-op BEGIN marker is already present.""" + if not cpp_path.exists(): + sys.stderr.write(f" ! parser-cpp: {cpp_path} not found, skipping\n") + return 0 + body = cpp_path.read_text() + n_added = 0 + + # 1) Per-op #include — append after the last existing Meos LogicalFunction include. + new_includes = [] + for op in operators: + inc = f"#include " + if inc in body: + continue + new_includes.append(inc) + if new_includes: + # Insert immediately after the last #include line. + meos_inc_re = re.compile(r"(^#include ]+>\s*\n)+", re.MULTILINE) + matches = list(meos_inc_re.finditer(body)) + if not matches: + sys.stderr.write(f" ! parser-cpp: could not find Meos include anchor\n") + else: + last = matches[-1] + body = body[: last.end()] + "\n".join(new_includes) + "\n" + body[last.end():] + sys.stderr.write(f" ✓ parser-cpp includes: added {len(new_includes)}\n") + + # 2) Per-op dispatch cases — insert just before the `default:` of the + # switch that already contains the TGEO_AT_STBOX case. + cases_block = [] + for op in operators: + marker = f"/* BEGIN CODEGEN PARSER GLUE: {op['sql_token']} */" + end_marker = f"/* END CODEGEN PARSER GLUE: {op['sql_token']} */" + if marker in body or end_marker in body: + continue + # Also skip if a pre-existing hand-written case for this token already + # exists (no marker, but a `case AntlrSQL{Lexer,Parser}::TOKEN:` line is present). + if re.search(rf"case\s+AntlrSQL(?:Lexer|Parser)::{re.escape(op['sql_token'])}\s*:", body): + sys.stderr.write( + f" ! parser-cpp: pre-existing hand-written case for {op['sql_token']} detected; " + f"skipping codegen injection (will not duplicate)\n" + ) + continue + if any(op.get(k) for k in _TRGEO_PHYS_DISPATCH): + case_str = _dispatch_case_current(op) # current AntlrSQLParser style; final string + elif op.get("build_generic"): + case_str = _generic_dispatch_case(op) # arity baked in; final string + else: + tmpl = dispatch_case_for(op) + if tmpl is None: + sys.stderr.write(f" ! parser-cpp: {op['nebula_name']} has no dispatch shape, skipping case\n") + continue + case_str = tmpl.format(sql_token=op["sql_token"], nebula_name=op["nebula_name"]) + cases_block.append(case_str) + n_added += 1 + if cases_block: + # Find the insertion point: prefer just after the LAST existing + # `/* END CODEGEN PARSER GLUE: ... */` marker (so successive codegen + # runs cluster their cases), else fall back to inserting before the + # `default:` that immediately follows the TGEO_AT_STBOX case block. + last_end_re = re.compile(r"/\* END CODEGEN PARSER GLUE: [^*]+\*/") + ends = list(last_end_re.finditer(body)) + if ends: + insert_at = ends[-1].end() + body = body[:insert_at] + "\n" + "\n".join(cases_block) + body[insert_at:] + sys.stderr.write(f" ✓ parser-cpp dispatch: added {len(cases_block)} case(s) after last codegen marker\n") + else: + anchor_re = re.compile( + r"(case AntlrSQLLexer::TGEO_AT_STBOX:[\s\S]+?\n\s*break;\n)(\s*default:)", + ) + m = anchor_re.search(body) + if m is None: + sys.stderr.write(f" ! parser-cpp: no anchor (TGEO_AT_STBOX→default or codegen END marker) found\n") + else: + insertion = m.group(1) + "\n" + "\n".join(cases_block) + "\n" + m.group(2) + body = body[: m.start()] + insertion + body[m.end():] + sys.stderr.write(f" ✓ parser-cpp dispatch: added {len(cases_block)} case(s) before default:\n") + + cpp_path.write_text(body) + return n_added + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--input", required=True, help="Path to JSON descriptor file") + parser.add_argument("--output-root", required=True, help="MobilityNebula repo root") + parser.add_argument("--no-parser-glue", action="store_true", + help="Skip .g4 + parser .cpp injection (default: inject)") + parser.add_argument("--no-cmake-entries", action="store_true", + help="Skip CMakeLists.txt injection (default: inject)") + args = parser.parse_args() + + with open(args.input) as f: + config = json.load(f) + + output_root = Path(args.output_root).resolve() + if not (output_root / "nes-logical-operators").exists(): + sys.exit(f"ERROR: {output_root} does not look like a MobilityNebula root (no nes-logical-operators/)") + + operators = config["operators"] + sys.stderr.write(f"Emitting {len(operators)} operator(s):\n\n") + for op in operators: + emit_operator(op, output_root) + + if not args.no_cmake_entries: + sys.stderr.write("\nCMakeLists.txt:\n") + inject_cmake_entries(operators, output_root) + + if not args.no_parser_glue: + sys.stderr.write("\nParser glue:\n") + inject_g4(operators, output_root / "nes-sql-parser/AntlrSQL.g4") + inject_parser_cpp(operators, output_root / "nes-sql-parser/src/AntlrSQLQueryPlanCreator.cpp") + + sys.stderr.write( + f"\nDone. {len(operators) * 4} files emitted " + f"(or 3 + .cpp-skipped for shapes without a physical-cpp template).\n" + ) + + +if __name__ == "__main__": + main() diff --git a/tools/codegen/trgeo-descriptor.json b/tools/codegen/trgeo-descriptor.json new file mode 100644 index 0000000000..015f98474b --- /dev/null +++ b/tools/codegen/trgeo-descriptor.json @@ -0,0 +1,311 @@ +{ + "_comment": "codegen descriptor; shapes=trgeometry_geo_predicate,geo_trgeometry_predicate,trgeometry_geo_dwithin,trgeometry_trgeometry_predicate,trgeometry_trgeometry_dwithin,trgeometry_nad", + "operators": [ + { + "nebula_name": "AcontainsGeoTrgeometry", + "sql_token": "ACONTAINS_GEO_TRGEOMETRY", + "meos_call": "acontains_geo_trgeometry", + "return_type": "int", + "nautilus_return": "INT32", + "build_geo_trgeometry": true, + "comment_one_liner": "Returns 1 if the geometry acontains the trgeometry instant." + }, + { + "nebula_name": "AcoversGeoTrgeometry", + "sql_token": "ACOVERS_GEO_TRGEOMETRY", + "meos_call": "acovers_geo_trgeometry", + "return_type": "int", + "nautilus_return": "INT32", + "build_geo_trgeometry": true, + "comment_one_liner": "Returns 1 if the geometry acovers the trgeometry instant." + }, + { + "nebula_name": "AcoversTrgeometryGeo", + "sql_token": "ACOVERS_TRGEOMETRY_GEO", + "meos_call": "acovers_trgeometry_geo", + "return_type": "int", + "nautilus_return": "INT32", + "build_trgeometry_geo": true, + "comment_one_liner": "Returns 1 if the trgeometry instant acovers the geometry." + }, + { + "nebula_name": "AdisjointTrgeometryGeo", + "sql_token": "ADISJOINT_TRGEOMETRY_GEO", + "meos_call": "adisjoint_trgeometry_geo", + "return_type": "int", + "nautilus_return": "INT32", + "build_trgeometry_geo": true, + "comment_one_liner": "Returns 1 if the trgeometry instant adisjoint the geometry." + }, + { + "nebula_name": "AdisjointTrgeometryTrgeometry", + "sql_token": "ADISJOINT_TRGEOMETRY_TRGEOMETRY", + "meos_call": "adisjoint_trgeometry_trgeometry", + "return_type": "int", + "nautilus_return": "INT32", + "build_trgeometry_trgeometry": true, + "comment_one_liner": "Returns 1 if the two trgeometry instants adisjoint." + }, + { + "nebula_name": "AdwithinTrgeometryGeo", + "sql_token": "ADWITHIN_TRGEOMETRY_GEO", + "meos_call": "adwithin_trgeometry_geo", + "return_type": "int", + "nautilus_return": "INT32", + "build_trgeometry_geo_with_dist": true, + "comment_one_liner": "Returns 1 if the trgeometry instant is within dist of the geometry (adwithin)." + }, + { + "nebula_name": "AdwithinTrgeometryTrgeometry", + "sql_token": "ADWITHIN_TRGEOMETRY_TRGEOMETRY", + "meos_call": "adwithin_trgeometry_trgeometry", + "return_type": "int", + "nautilus_return": "INT32", + "build_trgeometry_trgeometry_with_dist": true, + "comment_one_liner": "Returns 1 if the two trgeometry instants are within dist (adwithin)." + }, + { + "nebula_name": "AintersectsTrgeometryGeo", + "sql_token": "AINTERSECTS_TRGEOMETRY_GEO", + "meos_call": "aintersects_trgeometry_geo", + "return_type": "int", + "nautilus_return": "INT32", + "build_trgeometry_geo": true, + "comment_one_liner": "Returns 1 if the trgeometry instant aintersects the geometry." + }, + { + "nebula_name": "AintersectsTrgeometryTrgeometry", + "sql_token": "AINTERSECTS_TRGEOMETRY_TRGEOMETRY", + "meos_call": "aintersects_trgeometry_trgeometry", + "return_type": "int", + "nautilus_return": "INT32", + "build_trgeometry_trgeometry": true, + "comment_one_liner": "Returns 1 if the two trgeometry instants aintersects." + }, + { + "nebula_name": "AlwaysEqGeoTrgeometry", + "sql_token": "ALWAYS_EQ_GEO_TRGEOMETRY", + "meos_call": "always_eq_geo_trgeometry", + "return_type": "int", + "nautilus_return": "INT32", + "build_geo_trgeometry_eq": true, + "comment_one_liner": "Returns 1.0 if the static geometry is always equal to the 2D trgeometry instant." + }, + { + "nebula_name": "AlwaysEqTrgeometryGeo", + "sql_token": "ALWAYS_EQ_TRGEOMETRY_GEO", + "meos_call": "always_eq_trgeometry_geo", + "return_type": "int", + "nautilus_return": "INT32", + "build_trgeometry_geo_nad": true, + "comment_one_liner": "Returns 1.0 if the 2D trgeometry instant is always equal to the static geometry." + }, + { + "nebula_name": "AlwaysEqTrgeometryTrgeometry", + "sql_token": "ALWAYS_EQ_TRGEOMETRY_TRGEOMETRY", + "meos_call": "always_eq_trgeometry_trgeometry", + "return_type": "int", + "nautilus_return": "INT32", + "build_trgeometry_trgeometry_nad": true, + "comment_one_liner": "Returns 1.0 if the two 2D trgeometry instants are always equal." + }, + { + "nebula_name": "AlwaysNeGeoTrgeometry", + "sql_token": "ALWAYS_NE_GEO_TRGEOMETRY", + "meos_call": "always_ne_geo_trgeometry", + "return_type": "int", + "nautilus_return": "INT32", + "build_geo_trgeometry_eq": true, + "comment_one_liner": "Returns 1.0 if the static geometry is always not equal to the 2D trgeometry instant." + }, + { + "nebula_name": "AlwaysNeTrgeometryGeo", + "sql_token": "ALWAYS_NE_TRGEOMETRY_GEO", + "meos_call": "always_ne_trgeometry_geo", + "return_type": "int", + "nautilus_return": "INT32", + "build_trgeometry_geo_nad": true, + "comment_one_liner": "Returns 1.0 if the 2D trgeometry instant is always not equal to the static geometry." + }, + { + "nebula_name": "AlwaysNeTrgeometryTrgeometry", + "sql_token": "ALWAYS_NE_TRGEOMETRY_TRGEOMETRY", + "meos_call": "always_ne_trgeometry_trgeometry", + "return_type": "int", + "nautilus_return": "INT32", + "build_trgeometry_trgeometry_nad": true, + "comment_one_liner": "Returns 1.0 if the two 2D trgeometry instants are always not equal." + }, + { + "nebula_name": "AtouchesTrgeometryGeo", + "sql_token": "ATOUCHES_TRGEOMETRY_GEO", + "meos_call": "atouches_trgeometry_geo", + "return_type": "int", + "nautilus_return": "INT32", + "build_trgeometry_geo": true, + "comment_one_liner": "Returns 1 if the trgeometry instant atouches the geometry." + }, + { + "nebula_name": "EcontainsGeoTrgeometry", + "sql_token": "ECONTAINS_GEO_TRGEOMETRY", + "meos_call": "econtains_geo_trgeometry", + "return_type": "int", + "nautilus_return": "INT32", + "build_geo_trgeometry": true, + "comment_one_liner": "Returns 1 if the geometry econtains the trgeometry instant." + }, + { + "nebula_name": "EcoversGeoTrgeometry", + "sql_token": "ECOVERS_GEO_TRGEOMETRY", + "meos_call": "ecovers_geo_trgeometry", + "return_type": "int", + "nautilus_return": "INT32", + "build_geo_trgeometry": true, + "comment_one_liner": "Returns 1 if the geometry ecovers the trgeometry instant." + }, + { + "nebula_name": "EcoversTrgeometryGeo", + "sql_token": "ECOVERS_TRGEOMETRY_GEO", + "meos_call": "ecovers_trgeometry_geo", + "return_type": "int", + "nautilus_return": "INT32", + "build_trgeometry_geo": true, + "comment_one_liner": "Returns 1 if the trgeometry instant ecovers the geometry." + }, + { + "nebula_name": "EdisjointTrgeometryGeo", + "sql_token": "EDISJOINT_TRGEOMETRY_GEO", + "meos_call": "edisjoint_trgeometry_geo", + "return_type": "int", + "nautilus_return": "INT32", + "build_trgeometry_geo": true, + "comment_one_liner": "Returns 1 if the trgeometry instant edisjoint the geometry." + }, + { + "nebula_name": "EdisjointTrgeometryTrgeometry", + "sql_token": "EDISJOINT_TRGEOMETRY_TRGEOMETRY", + "meos_call": "edisjoint_trgeometry_trgeometry", + "return_type": "int", + "nautilus_return": "INT32", + "build_trgeometry_trgeometry": true, + "comment_one_liner": "Returns 1 if the two trgeometry instants edisjoint." + }, + { + "nebula_name": "EdwithinTrgeometryGeo", + "sql_token": "EDWITHIN_TRGEOMETRY_GEO", + "meos_call": "edwithin_trgeometry_geo", + "return_type": "int", + "nautilus_return": "INT32", + "build_trgeometry_geo_with_dist": true, + "comment_one_liner": "Returns 1 if the trgeometry instant is within dist of the geometry (edwithin)." + }, + { + "nebula_name": "EdwithinTrgeometryTrgeometry", + "sql_token": "EDWITHIN_TRGEOMETRY_TRGEOMETRY", + "meos_call": "edwithin_trgeometry_trgeometry", + "return_type": "int", + "nautilus_return": "INT32", + "build_trgeometry_trgeometry_with_dist": true, + "comment_one_liner": "Returns 1 if the two trgeometry instants are within dist (edwithin)." + }, + { + "nebula_name": "EintersectsTrgeometryGeo", + "sql_token": "EINTERSECTS_TRGEOMETRY_GEO", + "meos_call": "eintersects_trgeometry_geo", + "return_type": "int", + "nautilus_return": "INT32", + "build_trgeometry_geo": true, + "comment_one_liner": "Returns 1 if the trgeometry instant eintersects the geometry." + }, + { + "nebula_name": "EintersectsTrgeometryTrgeometry", + "sql_token": "EINTERSECTS_TRGEOMETRY_TRGEOMETRY", + "meos_call": "eintersects_trgeometry_trgeometry", + "return_type": "int", + "nautilus_return": "INT32", + "build_trgeometry_trgeometry": true, + "comment_one_liner": "Returns 1 if the two trgeometry instants eintersects." + }, + { + "nebula_name": "EtouchesTrgeometryGeo", + "sql_token": "ETOUCHES_TRGEOMETRY_GEO", + "meos_call": "etouches_trgeometry_geo", + "return_type": "int", + "nautilus_return": "INT32", + "build_trgeometry_geo": true, + "comment_one_liner": "Returns 1 if the trgeometry instant etouches the geometry." + }, + { + "nebula_name": "EverEqGeoTrgeometry", + "sql_token": "EVER_EQ_GEO_TRGEOMETRY", + "meos_call": "ever_eq_geo_trgeometry", + "return_type": "int", + "nautilus_return": "INT32", + "build_geo_trgeometry_eq": true, + "comment_one_liner": "Returns 1.0 if the static geometry is ever equal to the 2D trgeometry instant." + }, + { + "nebula_name": "EverEqTrgeometryGeo", + "sql_token": "EVER_EQ_TRGEOMETRY_GEO", + "meos_call": "ever_eq_trgeometry_geo", + "return_type": "int", + "nautilus_return": "INT32", + "build_trgeometry_geo_nad": true, + "comment_one_liner": "Returns 1.0 if the 2D trgeometry instant is ever equal to the static geometry." + }, + { + "nebula_name": "EverEqTrgeometryTrgeometry", + "sql_token": "EVER_EQ_TRGEOMETRY_TRGEOMETRY", + "meos_call": "ever_eq_trgeometry_trgeometry", + "return_type": "int", + "nautilus_return": "INT32", + "build_trgeometry_trgeometry_nad": true, + "comment_one_liner": "Returns 1.0 if the two 2D trgeometry instants are ever equal." + }, + { + "nebula_name": "EverNeGeoTrgeometry", + "sql_token": "EVER_NE_GEO_TRGEOMETRY", + "meos_call": "ever_ne_geo_trgeometry", + "return_type": "int", + "nautilus_return": "INT32", + "build_geo_trgeometry_eq": true, + "comment_one_liner": "Returns 1.0 if the static geometry is ever not equal to the 2D trgeometry instant." + }, + { + "nebula_name": "EverNeTrgeometryGeo", + "sql_token": "EVER_NE_TRGEOMETRY_GEO", + "meos_call": "ever_ne_trgeometry_geo", + "return_type": "int", + "nautilus_return": "INT32", + "build_trgeometry_geo_nad": true, + "comment_one_liner": "Returns 1.0 if the 2D trgeometry instant is ever not equal to the static geometry." + }, + { + "nebula_name": "EverNeTrgeometryTrgeometry", + "sql_token": "EVER_NE_TRGEOMETRY_TRGEOMETRY", + "meos_call": "ever_ne_trgeometry_trgeometry", + "return_type": "int", + "nautilus_return": "INT32", + "build_trgeometry_trgeometry_nad": true, + "comment_one_liner": "Returns 1.0 if the two 2D trgeometry instants are ever not equal." + }, + { + "nebula_name": "NadTrgeometryGeo", + "sql_token": "NAD_TRGEOMETRY_GEO", + "meos_call": "nad_trgeometry_geo", + "return_type": "double", + "nautilus_return": "FLOAT64", + "build_nad_trgeometry_geo": true, + "comment_one_liner": "Returns the nearest approach distance between a 2D trgeometry instant and a static geometry." + }, + { + "nebula_name": "NadTrgeometryTrgeometry", + "sql_token": "NAD_TRGEOMETRY_TRGEOMETRY", + "meos_call": "nad_trgeometry_trgeometry", + "return_type": "double", + "nautilus_return": "FLOAT64", + "build_nad_trgeometry_trgeometry": true, + "comment_one_liner": "Returns the nearest approach distance between two 2D trgeometry instants." + } + ] +} \ No newline at end of file From 337aae75239d7432118f2c13d437e4908e38552b Mon Sep 17 00:00:00 2001 From: Esteban Zimanyi Date: Mon, 6 Jul 2026 16:44:27 +0200 Subject: [PATCH 3/5] fix(codegen): update trgeometry shape classifiers from int* to GSERIALIZED* MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MEOS-API run.py correctly resolves GSERIALIZED in the IDL; parse_sigs normalizes `const GSERIALIZED *` to GSERIALIZED* (strip-const + first-token + "*"). The four trgeometry shape classifiers that match geometry arguments use GSERIALIZED* throughout: - trgeometry_geo_predicate: (Temporal*, int*) → (Temporal*, GSERIALIZED*) - geo_trgeometry_predicate: (int*, Temporal*) → (GSERIALIZED*, Temporal*) - trgeometry_geo_dwithin: (Temporal*, int*, double) → (Temporal*, GSERIALIZED*, double) - trgeometry_nad geo-branch: (Temporal*, int*) → (Temporal*, GSERIALIZED*) The trgeometry_nad two-temporal branch adds fn.endswith("_trgeometry") so nad_trgeometry_tpoint (Temporal*, Temporal*) is excluded, matching the committed descriptor's scope. Equivalence probe: build_descriptor.py against the master IDL (4492 fns) reproduces the committed trgeo-descriptor.json 34-operator set byte-for-byte. Genuine int* out-params (trgeometry_instants/segments/sequences/stboxes, all with non-int return types) are unaffected. nad_trgeometry_tpoint is correctly excluded. --- tools/codegen/build_descriptor.py | 36 ++++++++++++++++--------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/tools/codegen/build_descriptor.py b/tools/codegen/build_descriptor.py index 007e480abb..7ccf65ef81 100644 --- a/tools/codegen/build_descriptor.py +++ b/tools/codegen/build_descriptor.py @@ -440,14 +440,15 @@ def two_temporal_temporal(fn, ret, args): # --- Trgeometry spatial predicate shapes (W148/W149 wave) ------------------- -# MEOS normalizes GSERIALIZED* -> "int*" in the parse_sigs output (the IDL -# carries it as "const int *"; after strip-const + split it becomes "int*"). +# The current MEOS-API run.py correctly resolves GSERIALIZED* in the IDL, so +# parse_sigs normalizes `const GSERIALIZED *` -> "GSERIALIZED*" (strip-const + +# first-token + "*"). Classifiers use GSERIALIZED* for every geometry argument. # # Five classifiers, each selecting the correct emit_trgeometry_operator build key: # -# trgeometry_geo_predicate (Temporal*, int*) int — e/a intersects/disjoint/covers/touches -# geo_trgeometry_predicate (int*, Temporal*) int — geo-first: econtains/acontains/ecovers/acovers -# trgeometry_geo_dwithin (Temporal*, int*, double) int — edwithin/adwithin with distance +# trgeometry_geo_predicate (Temporal*, GSERIALIZED*) int — e/a intersects/disjoint/covers/touches +# geo_trgeometry_predicate (GSERIALIZED*, Temporal*) int — geo-first: econtains/acontains/ecovers/acovers +# trgeometry_geo_dwithin (Temporal*, GSERIALIZED*, double) int — edwithin/adwithin with distance # trgeometry_trgeometry_predicate (Temporal*, Temporal*) int — both-trgeometry predicates # trgeometry_trgeometry_dwithin (Temporal*, Temporal*, double) int — both-trgeometry dwithin # @@ -497,14 +498,14 @@ def _trgeo_brief(fn): def trgeometry_geo_predicate(fn, ret, args): - """int fn(Temporal*, int*) — trgeometry_geo spatial predicates and eq/ne. + """int fn(Temporal*, GSERIALIZED*) — trgeometry_geo spatial predicates and eq/ne. Routes to build_trgeometry_geo (compact, trgeometryinst_make) for the spatial-predicate families (eintersects/adisjoint/ecovers/etouches/…) and to build_trgeometry_geo_nad (nad, trgeoinst_make) for ever_eq/always_eq/…""" - if ret != "int" or args != ("Temporal*", "int*"): + if ret != "int" or args != ("Temporal*", "GSERIALIZED*"): return None - # Must be a trgeometry function (not a plain tgeo/tpoint that hits "int*" too) + # Must be a trgeometry function (not a plain tgeo/tpoint that hits "GSERIALIZED*" too) if "trgeometry" not in fn: return None build_key = "build_trgeometry_geo_nad" if _is_eq_ne(fn) else "build_trgeometry_geo" @@ -521,12 +522,12 @@ def trgeometry_geo_predicate(fn, ret, args): def geo_trgeometry_predicate(fn, ret, args): - """int fn(int*, Temporal*) — geo-first trgeometry predicates and eq/ne. + """int fn(GSERIALIZED*, Temporal*) — geo-first trgeometry predicates and eq/ne. Routes to build_geo_trgeometry (compact, trgeometryinst_make) for spatial predicates (econtains/acontains/ecovers/acovers_geo_trgeometry) and to build_geo_trgeometry_eq (nad, trgeoinst_make) for ever_eq/always_eq geo-first.""" - if ret != "int" or args != ("int*", "Temporal*"): + if ret != "int" or args != ("GSERIALIZED*", "Temporal*"): return None if "trgeometry" not in fn: return None @@ -544,8 +545,8 @@ def geo_trgeometry_predicate(fn, ret, args): def trgeometry_geo_dwithin(fn, ret, args): - """int fn(Temporal*, int*, double) — trgeometry_geo dwithin with distance arg.""" - if ret != "int" or args != ("Temporal*", "int*", "double"): + """int fn(Temporal*, GSERIALIZED*, double) — trgeometry_geo dwithin with distance arg.""" + if ret != "int" or args != ("Temporal*", "GSERIALIZED*", "double"): return None if "trgeometry" not in fn: return None @@ -605,15 +606,16 @@ def trgeometry_trgeometry_dwithin(fn, ret, args): def trgeometry_nad(fn, ret, args): - """double fn(Temporal*, int* | Temporal*) — nad nearest-approach-distance. + """double fn(Temporal*, GSERIALIZED* | Temporal*) — nad nearest-approach-distance. - Two sub-shapes: trgeometry_geo (Temporal*, int*) and trgeometry_trgeometry - (Temporal*, Temporal*). Both emit double-returning nad layouts.""" + Two sub-shapes: trgeometry_geo (Temporal*, GSERIALIZED*) and trgeometry_trgeometry + (Temporal*, Temporal*). Both emit double-returning nad layouts. The two-temporal + branch requires fn.endswith("_trgeometry") to exclude nad_trgeometry_tpoint.""" if ret != "double" or "trgeometry" not in fn or not fn.startswith("nad_"): return None - if args == ("Temporal*", "int*"): + if args == ("Temporal*", "GSERIALIZED*"): build_key = "build_nad_trgeometry_geo" - elif args == ("Temporal*", "Temporal*"): + elif args == ("Temporal*", "Temporal*") and fn.endswith("_trgeometry"): build_key = "build_nad_trgeometry_trgeometry" else: return None From 3cfbf5f1188ce78f1d13111de3746f646b94f479 Mon Sep 17 00:00:00 2001 From: Esteban Zimanyi Date: Mon, 6 Jul 2026 17:49:17 +0200 Subject: [PATCH 4/5] fix(codegen): name-filter the scalar comparison classifiers to ever/always verbs cmp_scalar_tempfirst and cmp_scalar_scalarfirst matched on signature alone, so a nearest-approach function like nad_tint_int (int(Temporal*, int)) was misclassified as a comparison operator. Require the ever/always + {eq,ne,lt,le,gt,ge} naming so only comparison functions match. --- tools/codegen/build_descriptor.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tools/codegen/build_descriptor.py b/tools/codegen/build_descriptor.py index 7ccf65ef81..3d77389490 100644 --- a/tools/codegen/build_descriptor.py +++ b/tools/codegen/build_descriptor.py @@ -65,6 +65,9 @@ def parse_sigs(path): def cmp_scalar_tempfirst(fn, ret, args): """ever/always comparison: int fn(const Temporal*, double|int), temp first. Reuses build_tnumber_point_with_scalar (already in codegen_nebula.py).""" + parts = fn.split("_") + if len(parts) < 2 or parts[0] not in ("ever", "always") or parts[1] not in ("eq", "ne", "lt", "le", "gt", "ge"): + return None if ret != "int" or args not in (("Temporal*", "double"), ("Temporal*", "int")): return None base = "tfloat" if args[1] == "double" else "tint" @@ -94,6 +97,9 @@ def cmp_scalar_tempfirst(fn, ret, args): def cmp_scalar_scalarfirst(fn, ret, args): """ever/always comparison: int fn(double|int, const Temporal*), scalar first. Reuses build_tnumber_scalar_first (MEOS call passes scalar as 1st arg).""" + parts = fn.split("_") + if len(parts) < 2 or parts[0] not in ("ever", "always") or parts[1] not in ("eq", "ne", "lt", "le", "gt", "ge"): + return None if ret != "int" or args not in (("double", "Temporal*"), ("int", "Temporal*")): return None base = "tfloat" if args[0] == "double" else "tint" From feeeaceb848927dbbdbc923c8e5dfec50fe68905 Mon Sep 17 00:00:00 2001 From: Esteban Zimanyi Date: Sat, 11 Jul 2026 13:46:01 +0200 Subject: [PATCH 5/5] Generate temporal-vs-geometry transform operators via hex-WKB serialization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Generate the four per-event transforms of a temporal spatial value against a static geometry whose result is itself a temporal — trgeometry_at_geom, trgeometry_minus_geom, trgeometry_body_point_trajectory, and tpose_apply_geo — across the logical and physical layers, with matching grammar tokens, parser dispatch, and CMake entries. Each takes one hex-WKB temporal operand plus a static geometry and returns a temporal, so it follows the serialize-on-return round-trip the two-operand cross-vehicle form uses: parse the temporal with temporal_from_hexwkb, parse the geometry with the StaticGeometry wrapper, call the MEOS function, and serialize the temporal result with temporal_as_hexwkb into an arena-allocated variable-sized field. A geometry-argument physical template and its classifier extend the serialize-on-return surface for a static-geometry second operand, reusing the geometry-parsing idiom of the scalar-return generic path; the trgeometry and tpose symbols resolve through meos_rgeo.h and meos_pose.h. --- .../Meos/TposeApplyGeoLogicalFunction.hpp | 53 +++++++ .../Meos/TrgeometryAtGeomLogicalFunction.hpp | 53 +++++++ ...etryBodyPointTrajectoryLogicalFunction.hpp | 53 +++++++ .../TrgeometryMinusGeomLogicalFunction.hpp | 53 +++++++ .../src/Functions/Meos/CMakeLists.txt | 4 + .../Meos/TposeApplyGeoLogicalFunction.cpp | 125 +++++++++++++++ .../Meos/TrgeometryAtGeomLogicalFunction.cpp | 125 +++++++++++++++ ...etryBodyPointTrajectoryLogicalFunction.cpp | 125 +++++++++++++++ .../TrgeometryMinusGeomLogicalFunction.cpp | 125 +++++++++++++++ .../Meos/TposeApplyGeoPhysicalFunction.hpp | 42 +++++ .../Meos/TrgeometryAtGeomPhysicalFunction.hpp | 42 +++++ ...tryBodyPointTrajectoryPhysicalFunction.hpp | 42 +++++ .../TrgeometryMinusGeomPhysicalFunction.hpp | 42 +++++ .../src/Functions/Meos/CMakeLists.txt | 4 + .../Meos/TposeApplyGeoPhysicalFunction.cpp | 124 +++++++++++++++ .../Meos/TrgeometryAtGeomPhysicalFunction.cpp | 124 +++++++++++++++ ...tryBodyPointTrajectoryPhysicalFunction.cpp | 124 +++++++++++++++ .../TrgeometryMinusGeomPhysicalFunction.cpp | 124 +++++++++++++++ nes-sql-parser/AntlrSQL.g4 | 8 +- .../src/AntlrSQLQueryPlanCreator.cpp | 121 +++++++++++++++ tools/codegen/build_descriptor.py | 33 ++++ tools/codegen/codegen_nebula.py | 146 ++++++++++++++++++ 22 files changed, 1691 insertions(+), 1 deletion(-) create mode 100644 nes-logical-operators/include/Functions/Meos/TposeApplyGeoLogicalFunction.hpp create mode 100644 nes-logical-operators/include/Functions/Meos/TrgeometryAtGeomLogicalFunction.hpp create mode 100644 nes-logical-operators/include/Functions/Meos/TrgeometryBodyPointTrajectoryLogicalFunction.hpp create mode 100644 nes-logical-operators/include/Functions/Meos/TrgeometryMinusGeomLogicalFunction.hpp create mode 100644 nes-logical-operators/src/Functions/Meos/TposeApplyGeoLogicalFunction.cpp create mode 100644 nes-logical-operators/src/Functions/Meos/TrgeometryAtGeomLogicalFunction.cpp create mode 100644 nes-logical-operators/src/Functions/Meos/TrgeometryBodyPointTrajectoryLogicalFunction.cpp create mode 100644 nes-logical-operators/src/Functions/Meos/TrgeometryMinusGeomLogicalFunction.cpp create mode 100644 nes-physical-operators/include/Functions/Meos/TposeApplyGeoPhysicalFunction.hpp create mode 100644 nes-physical-operators/include/Functions/Meos/TrgeometryAtGeomPhysicalFunction.hpp create mode 100644 nes-physical-operators/include/Functions/Meos/TrgeometryBodyPointTrajectoryPhysicalFunction.hpp create mode 100644 nes-physical-operators/include/Functions/Meos/TrgeometryMinusGeomPhysicalFunction.hpp create mode 100644 nes-physical-operators/src/Functions/Meos/TposeApplyGeoPhysicalFunction.cpp create mode 100644 nes-physical-operators/src/Functions/Meos/TrgeometryAtGeomPhysicalFunction.cpp create mode 100644 nes-physical-operators/src/Functions/Meos/TrgeometryBodyPointTrajectoryPhysicalFunction.cpp create mode 100644 nes-physical-operators/src/Functions/Meos/TrgeometryMinusGeomPhysicalFunction.cpp diff --git a/nes-logical-operators/include/Functions/Meos/TposeApplyGeoLogicalFunction.hpp b/nes-logical-operators/include/Functions/Meos/TposeApplyGeoLogicalFunction.hpp new file mode 100644 index 0000000000..897561f83c --- /dev/null +++ b/nes-logical-operators/include/Functions/Meos/TposeApplyGeoLogicalFunction.hpp @@ -0,0 +1,53 @@ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#pragma once + +#include +#include +#include +#include + +namespace NES { + +/** + * @brief Per-event tpose_apply_geo: a hex-WKB temporal and a static geometry -> hex-WKB temporal. + * + * Generated by tools/codegen/codegen_nebula.py from the MEOS function + * `tpose_apply_geo`. Per-event scalar operator following the + * TemporalEDWithinGeometry pattern. + */ +class TposeApplyGeoLogicalFunction : public LogicalFunctionConcept { +public: + static constexpr std::string_view NAME = "TposeApplyGeo"; + + TposeApplyGeoLogicalFunction(LogicalFunction traj, + LogicalFunction arg0); + + DataType getDataType() const override; + LogicalFunction withDataType(const DataType& dataType) const override; + std::vector getChildren() const override; + LogicalFunction withChildren(const std::vector& children) const override; + std::string_view getType() const override; + bool operator==(const LogicalFunctionConcept& rhs) const override; + std::string explain(ExplainVerbosity verbosity) const override; + LogicalFunction withInferredDataType(const Schema& schema) const override; + SerializableFunction serialize() const override; + +private: + DataType dataType; + std::vector parameters; +}; + +} // namespace NES diff --git a/nes-logical-operators/include/Functions/Meos/TrgeometryAtGeomLogicalFunction.hpp b/nes-logical-operators/include/Functions/Meos/TrgeometryAtGeomLogicalFunction.hpp new file mode 100644 index 0000000000..79af64c54c --- /dev/null +++ b/nes-logical-operators/include/Functions/Meos/TrgeometryAtGeomLogicalFunction.hpp @@ -0,0 +1,53 @@ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#pragma once + +#include +#include +#include +#include + +namespace NES { + +/** + * @brief Per-event trgeometry_at_geom: a hex-WKB temporal and a static geometry -> hex-WKB temporal. + * + * Generated by tools/codegen/codegen_nebula.py from the MEOS function + * `trgeometry_at_geom`. Per-event scalar operator following the + * TemporalEDWithinGeometry pattern. + */ +class TrgeometryAtGeomLogicalFunction : public LogicalFunctionConcept { +public: + static constexpr std::string_view NAME = "TrgeometryAtGeom"; + + TrgeometryAtGeomLogicalFunction(LogicalFunction traj, + LogicalFunction arg0); + + DataType getDataType() const override; + LogicalFunction withDataType(const DataType& dataType) const override; + std::vector getChildren() const override; + LogicalFunction withChildren(const std::vector& children) const override; + std::string_view getType() const override; + bool operator==(const LogicalFunctionConcept& rhs) const override; + std::string explain(ExplainVerbosity verbosity) const override; + LogicalFunction withInferredDataType(const Schema& schema) const override; + SerializableFunction serialize() const override; + +private: + DataType dataType; + std::vector parameters; +}; + +} // namespace NES diff --git a/nes-logical-operators/include/Functions/Meos/TrgeometryBodyPointTrajectoryLogicalFunction.hpp b/nes-logical-operators/include/Functions/Meos/TrgeometryBodyPointTrajectoryLogicalFunction.hpp new file mode 100644 index 0000000000..d1055c26bd --- /dev/null +++ b/nes-logical-operators/include/Functions/Meos/TrgeometryBodyPointTrajectoryLogicalFunction.hpp @@ -0,0 +1,53 @@ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#pragma once + +#include +#include +#include +#include + +namespace NES { + +/** + * @brief Per-event trgeometry_body_point_trajectory: a hex-WKB temporal and a static geometry -> hex-WKB temporal. + * + * Generated by tools/codegen/codegen_nebula.py from the MEOS function + * `trgeometry_body_point_trajectory`. Per-event scalar operator following the + * TemporalEDWithinGeometry pattern. + */ +class TrgeometryBodyPointTrajectoryLogicalFunction : public LogicalFunctionConcept { +public: + static constexpr std::string_view NAME = "TrgeometryBodyPointTrajectory"; + + TrgeometryBodyPointTrajectoryLogicalFunction(LogicalFunction traj, + LogicalFunction arg0); + + DataType getDataType() const override; + LogicalFunction withDataType(const DataType& dataType) const override; + std::vector getChildren() const override; + LogicalFunction withChildren(const std::vector& children) const override; + std::string_view getType() const override; + bool operator==(const LogicalFunctionConcept& rhs) const override; + std::string explain(ExplainVerbosity verbosity) const override; + LogicalFunction withInferredDataType(const Schema& schema) const override; + SerializableFunction serialize() const override; + +private: + DataType dataType; + std::vector parameters; +}; + +} // namespace NES diff --git a/nes-logical-operators/include/Functions/Meos/TrgeometryMinusGeomLogicalFunction.hpp b/nes-logical-operators/include/Functions/Meos/TrgeometryMinusGeomLogicalFunction.hpp new file mode 100644 index 0000000000..698a000509 --- /dev/null +++ b/nes-logical-operators/include/Functions/Meos/TrgeometryMinusGeomLogicalFunction.hpp @@ -0,0 +1,53 @@ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#pragma once + +#include +#include +#include +#include + +namespace NES { + +/** + * @brief Per-event trgeometry_minus_geom: a hex-WKB temporal and a static geometry -> hex-WKB temporal. + * + * Generated by tools/codegen/codegen_nebula.py from the MEOS function + * `trgeometry_minus_geom`. Per-event scalar operator following the + * TemporalEDWithinGeometry pattern. + */ +class TrgeometryMinusGeomLogicalFunction : public LogicalFunctionConcept { +public: + static constexpr std::string_view NAME = "TrgeometryMinusGeom"; + + TrgeometryMinusGeomLogicalFunction(LogicalFunction traj, + LogicalFunction arg0); + + DataType getDataType() const override; + LogicalFunction withDataType(const DataType& dataType) const override; + std::vector getChildren() const override; + LogicalFunction withChildren(const std::vector& children) const override; + std::string_view getType() const override; + bool operator==(const LogicalFunctionConcept& rhs) const override; + std::string explain(ExplainVerbosity verbosity) const override; + LogicalFunction withInferredDataType(const Schema& schema) const override; + SerializableFunction serialize() const override; + +private: + DataType dataType; + std::vector parameters; +}; + +} // namespace NES diff --git a/nes-logical-operators/src/Functions/Meos/CMakeLists.txt b/nes-logical-operators/src/Functions/Meos/CMakeLists.txt index 474ba11608..3649c18346 100644 --- a/nes-logical-operators/src/Functions/Meos/CMakeLists.txt +++ b/nes-logical-operators/src/Functions/Meos/CMakeLists.txt @@ -18,3 +18,7 @@ add_plugin(TemporalAIntersectsGeometry LogicalFunction nes-logical-operators Tem add_plugin(TemporalEContainsGeometry LogicalFunction nes-logical-operators TemporalEContainsGeometryLogicalFunction.cpp) add_plugin(TemporalEDWithinGeometry LogicalFunction nes-logical-operators TemporalEDWithinGeometryLogicalFunction.cpp) add_plugin(TemporalAtStBox LogicalFunction nes-logical-operators TemporalAtStBoxLogicalFunction.cpp) +add_plugin(TposeApplyGeo LogicalFunction nes-logical-operators TposeApplyGeoLogicalFunction.cpp) +add_plugin(TrgeometryAtGeom LogicalFunction nes-logical-operators TrgeometryAtGeomLogicalFunction.cpp) +add_plugin(TrgeometryBodyPointTrajectory LogicalFunction nes-logical-operators TrgeometryBodyPointTrajectoryLogicalFunction.cpp) +add_plugin(TrgeometryMinusGeom LogicalFunction nes-logical-operators TrgeometryMinusGeomLogicalFunction.cpp) diff --git a/nes-logical-operators/src/Functions/Meos/TposeApplyGeoLogicalFunction.cpp b/nes-logical-operators/src/Functions/Meos/TposeApplyGeoLogicalFunction.cpp new file mode 100644 index 0000000000..e70749d341 --- /dev/null +++ b/nes-logical-operators/src/Functions/Meos/TposeApplyGeoLogicalFunction.cpp @@ -0,0 +1,125 @@ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace NES +{ + +TposeApplyGeoLogicalFunction::TposeApplyGeoLogicalFunction(LogicalFunction traj, + LogicalFunction arg0) + : dataType(DataTypeProvider::provideDataType(DataType::Type::VARSIZED)) +{ + parameters.reserve(2); + parameters.push_back(std::move(traj)); + parameters.push_back(std::move(arg0)); +} + +DataType TposeApplyGeoLogicalFunction::getDataType() const +{ + return dataType; +} + +LogicalFunction TposeApplyGeoLogicalFunction::withDataType(const DataType& newDataType) const +{ + auto copy = *this; + copy.dataType = newDataType; + return copy; +} + +std::vector TposeApplyGeoLogicalFunction::getChildren() const +{ + return parameters; +} + +LogicalFunction TposeApplyGeoLogicalFunction::withChildren(const std::vector& children) const +{ + PRECONDITION(children.size() == 2, "TposeApplyGeoLogicalFunction requires 2 children, but got {}", children.size()); + auto copy = *this; + copy.parameters = children; + return copy; +} + +std::string_view TposeApplyGeoLogicalFunction::getType() const +{ + return NAME; +} + +bool TposeApplyGeoLogicalFunction::operator==(const LogicalFunctionConcept& rhs) const +{ + if (const auto* other = dynamic_cast(&rhs)) + { + return parameters == other->parameters; + } + return false; +} + +std::string TposeApplyGeoLogicalFunction::explain(ExplainVerbosity verbosity) const +{ + std::string args; + for (size_t index = 0; index < parameters.size(); ++index) + { + if (index > 0) + { + args += ", "; + } + args += parameters[index].explain(verbosity); + } + return fmt::format("{}({})", NAME, args); +} + +LogicalFunction TposeApplyGeoLogicalFunction::withInferredDataType(const Schema& schema) const +{ + std::vector newChildren; + newChildren.reserve(parameters.size()); + for (const auto& child : parameters) + { + newChildren.emplace_back(child.withInferredDataType(schema)); + } + return withChildren(newChildren); +} + +SerializableFunction TposeApplyGeoLogicalFunction::serialize() const +{ + SerializableFunction proto; + proto.set_function_type(std::string(NAME)); + DataTypeSerializationUtil::serializeDataType(dataType, proto.mutable_data_type()); + for (const auto& child : parameters) + { + proto.add_children()->CopyFrom(child.serialize()); + } + return proto; +} + +LogicalFunctionRegistryReturnType LogicalFunctionGeneratedRegistrar::RegisterTposeApplyGeoLogicalFunction( + LogicalFunctionRegistryArguments arguments) +{ + PRECONDITION(arguments.children.size() == 2, + "TposeApplyGeoLogicalFunction requires 2 children but got {}", + arguments.children.size()); + auto arg0 = std::move(arguments.children[0]); + auto arg1 = std::move(arguments.children[1]); + return TposeApplyGeoLogicalFunction(std::move(arg0), std::move(arg1)); +} + +} // namespace NES diff --git a/nes-logical-operators/src/Functions/Meos/TrgeometryAtGeomLogicalFunction.cpp b/nes-logical-operators/src/Functions/Meos/TrgeometryAtGeomLogicalFunction.cpp new file mode 100644 index 0000000000..48b9e3127d --- /dev/null +++ b/nes-logical-operators/src/Functions/Meos/TrgeometryAtGeomLogicalFunction.cpp @@ -0,0 +1,125 @@ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace NES +{ + +TrgeometryAtGeomLogicalFunction::TrgeometryAtGeomLogicalFunction(LogicalFunction traj, + LogicalFunction arg0) + : dataType(DataTypeProvider::provideDataType(DataType::Type::VARSIZED)) +{ + parameters.reserve(2); + parameters.push_back(std::move(traj)); + parameters.push_back(std::move(arg0)); +} + +DataType TrgeometryAtGeomLogicalFunction::getDataType() const +{ + return dataType; +} + +LogicalFunction TrgeometryAtGeomLogicalFunction::withDataType(const DataType& newDataType) const +{ + auto copy = *this; + copy.dataType = newDataType; + return copy; +} + +std::vector TrgeometryAtGeomLogicalFunction::getChildren() const +{ + return parameters; +} + +LogicalFunction TrgeometryAtGeomLogicalFunction::withChildren(const std::vector& children) const +{ + PRECONDITION(children.size() == 2, "TrgeometryAtGeomLogicalFunction requires 2 children, but got {}", children.size()); + auto copy = *this; + copy.parameters = children; + return copy; +} + +std::string_view TrgeometryAtGeomLogicalFunction::getType() const +{ + return NAME; +} + +bool TrgeometryAtGeomLogicalFunction::operator==(const LogicalFunctionConcept& rhs) const +{ + if (const auto* other = dynamic_cast(&rhs)) + { + return parameters == other->parameters; + } + return false; +} + +std::string TrgeometryAtGeomLogicalFunction::explain(ExplainVerbosity verbosity) const +{ + std::string args; + for (size_t index = 0; index < parameters.size(); ++index) + { + if (index > 0) + { + args += ", "; + } + args += parameters[index].explain(verbosity); + } + return fmt::format("{}({})", NAME, args); +} + +LogicalFunction TrgeometryAtGeomLogicalFunction::withInferredDataType(const Schema& schema) const +{ + std::vector newChildren; + newChildren.reserve(parameters.size()); + for (const auto& child : parameters) + { + newChildren.emplace_back(child.withInferredDataType(schema)); + } + return withChildren(newChildren); +} + +SerializableFunction TrgeometryAtGeomLogicalFunction::serialize() const +{ + SerializableFunction proto; + proto.set_function_type(std::string(NAME)); + DataTypeSerializationUtil::serializeDataType(dataType, proto.mutable_data_type()); + for (const auto& child : parameters) + { + proto.add_children()->CopyFrom(child.serialize()); + } + return proto; +} + +LogicalFunctionRegistryReturnType LogicalFunctionGeneratedRegistrar::RegisterTrgeometryAtGeomLogicalFunction( + LogicalFunctionRegistryArguments arguments) +{ + PRECONDITION(arguments.children.size() == 2, + "TrgeometryAtGeomLogicalFunction requires 2 children but got {}", + arguments.children.size()); + auto arg0 = std::move(arguments.children[0]); + auto arg1 = std::move(arguments.children[1]); + return TrgeometryAtGeomLogicalFunction(std::move(arg0), std::move(arg1)); +} + +} // namespace NES diff --git a/nes-logical-operators/src/Functions/Meos/TrgeometryBodyPointTrajectoryLogicalFunction.cpp b/nes-logical-operators/src/Functions/Meos/TrgeometryBodyPointTrajectoryLogicalFunction.cpp new file mode 100644 index 0000000000..857a8ba3eb --- /dev/null +++ b/nes-logical-operators/src/Functions/Meos/TrgeometryBodyPointTrajectoryLogicalFunction.cpp @@ -0,0 +1,125 @@ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace NES +{ + +TrgeometryBodyPointTrajectoryLogicalFunction::TrgeometryBodyPointTrajectoryLogicalFunction(LogicalFunction traj, + LogicalFunction arg0) + : dataType(DataTypeProvider::provideDataType(DataType::Type::VARSIZED)) +{ + parameters.reserve(2); + parameters.push_back(std::move(traj)); + parameters.push_back(std::move(arg0)); +} + +DataType TrgeometryBodyPointTrajectoryLogicalFunction::getDataType() const +{ + return dataType; +} + +LogicalFunction TrgeometryBodyPointTrajectoryLogicalFunction::withDataType(const DataType& newDataType) const +{ + auto copy = *this; + copy.dataType = newDataType; + return copy; +} + +std::vector TrgeometryBodyPointTrajectoryLogicalFunction::getChildren() const +{ + return parameters; +} + +LogicalFunction TrgeometryBodyPointTrajectoryLogicalFunction::withChildren(const std::vector& children) const +{ + PRECONDITION(children.size() == 2, "TrgeometryBodyPointTrajectoryLogicalFunction requires 2 children, but got {}", children.size()); + auto copy = *this; + copy.parameters = children; + return copy; +} + +std::string_view TrgeometryBodyPointTrajectoryLogicalFunction::getType() const +{ + return NAME; +} + +bool TrgeometryBodyPointTrajectoryLogicalFunction::operator==(const LogicalFunctionConcept& rhs) const +{ + if (const auto* other = dynamic_cast(&rhs)) + { + return parameters == other->parameters; + } + return false; +} + +std::string TrgeometryBodyPointTrajectoryLogicalFunction::explain(ExplainVerbosity verbosity) const +{ + std::string args; + for (size_t index = 0; index < parameters.size(); ++index) + { + if (index > 0) + { + args += ", "; + } + args += parameters[index].explain(verbosity); + } + return fmt::format("{}({})", NAME, args); +} + +LogicalFunction TrgeometryBodyPointTrajectoryLogicalFunction::withInferredDataType(const Schema& schema) const +{ + std::vector newChildren; + newChildren.reserve(parameters.size()); + for (const auto& child : parameters) + { + newChildren.emplace_back(child.withInferredDataType(schema)); + } + return withChildren(newChildren); +} + +SerializableFunction TrgeometryBodyPointTrajectoryLogicalFunction::serialize() const +{ + SerializableFunction proto; + proto.set_function_type(std::string(NAME)); + DataTypeSerializationUtil::serializeDataType(dataType, proto.mutable_data_type()); + for (const auto& child : parameters) + { + proto.add_children()->CopyFrom(child.serialize()); + } + return proto; +} + +LogicalFunctionRegistryReturnType LogicalFunctionGeneratedRegistrar::RegisterTrgeometryBodyPointTrajectoryLogicalFunction( + LogicalFunctionRegistryArguments arguments) +{ + PRECONDITION(arguments.children.size() == 2, + "TrgeometryBodyPointTrajectoryLogicalFunction requires 2 children but got {}", + arguments.children.size()); + auto arg0 = std::move(arguments.children[0]); + auto arg1 = std::move(arguments.children[1]); + return TrgeometryBodyPointTrajectoryLogicalFunction(std::move(arg0), std::move(arg1)); +} + +} // namespace NES diff --git a/nes-logical-operators/src/Functions/Meos/TrgeometryMinusGeomLogicalFunction.cpp b/nes-logical-operators/src/Functions/Meos/TrgeometryMinusGeomLogicalFunction.cpp new file mode 100644 index 0000000000..f936473f9b --- /dev/null +++ b/nes-logical-operators/src/Functions/Meos/TrgeometryMinusGeomLogicalFunction.cpp @@ -0,0 +1,125 @@ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace NES +{ + +TrgeometryMinusGeomLogicalFunction::TrgeometryMinusGeomLogicalFunction(LogicalFunction traj, + LogicalFunction arg0) + : dataType(DataTypeProvider::provideDataType(DataType::Type::VARSIZED)) +{ + parameters.reserve(2); + parameters.push_back(std::move(traj)); + parameters.push_back(std::move(arg0)); +} + +DataType TrgeometryMinusGeomLogicalFunction::getDataType() const +{ + return dataType; +} + +LogicalFunction TrgeometryMinusGeomLogicalFunction::withDataType(const DataType& newDataType) const +{ + auto copy = *this; + copy.dataType = newDataType; + return copy; +} + +std::vector TrgeometryMinusGeomLogicalFunction::getChildren() const +{ + return parameters; +} + +LogicalFunction TrgeometryMinusGeomLogicalFunction::withChildren(const std::vector& children) const +{ + PRECONDITION(children.size() == 2, "TrgeometryMinusGeomLogicalFunction requires 2 children, but got {}", children.size()); + auto copy = *this; + copy.parameters = children; + return copy; +} + +std::string_view TrgeometryMinusGeomLogicalFunction::getType() const +{ + return NAME; +} + +bool TrgeometryMinusGeomLogicalFunction::operator==(const LogicalFunctionConcept& rhs) const +{ + if (const auto* other = dynamic_cast(&rhs)) + { + return parameters == other->parameters; + } + return false; +} + +std::string TrgeometryMinusGeomLogicalFunction::explain(ExplainVerbosity verbosity) const +{ + std::string args; + for (size_t index = 0; index < parameters.size(); ++index) + { + if (index > 0) + { + args += ", "; + } + args += parameters[index].explain(verbosity); + } + return fmt::format("{}({})", NAME, args); +} + +LogicalFunction TrgeometryMinusGeomLogicalFunction::withInferredDataType(const Schema& schema) const +{ + std::vector newChildren; + newChildren.reserve(parameters.size()); + for (const auto& child : parameters) + { + newChildren.emplace_back(child.withInferredDataType(schema)); + } + return withChildren(newChildren); +} + +SerializableFunction TrgeometryMinusGeomLogicalFunction::serialize() const +{ + SerializableFunction proto; + proto.set_function_type(std::string(NAME)); + DataTypeSerializationUtil::serializeDataType(dataType, proto.mutable_data_type()); + for (const auto& child : parameters) + { + proto.add_children()->CopyFrom(child.serialize()); + } + return proto; +} + +LogicalFunctionRegistryReturnType LogicalFunctionGeneratedRegistrar::RegisterTrgeometryMinusGeomLogicalFunction( + LogicalFunctionRegistryArguments arguments) +{ + PRECONDITION(arguments.children.size() == 2, + "TrgeometryMinusGeomLogicalFunction requires 2 children but got {}", + arguments.children.size()); + auto arg0 = std::move(arguments.children[0]); + auto arg1 = std::move(arguments.children[1]); + return TrgeometryMinusGeomLogicalFunction(std::move(arg0), std::move(arg1)); +} + +} // namespace NES diff --git a/nes-physical-operators/include/Functions/Meos/TposeApplyGeoPhysicalFunction.hpp b/nes-physical-operators/include/Functions/Meos/TposeApplyGeoPhysicalFunction.hpp new file mode 100644 index 0000000000..3728ce1322 --- /dev/null +++ b/nes-physical-operators/include/Functions/Meos/TposeApplyGeoPhysicalFunction.hpp @@ -0,0 +1,42 @@ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#pragma once + +#include +#include +#include +#include + +namespace NES { + +/** + * @brief Physical operator for `tpose_apply_geo`. + * + * Per-event tpose_apply_geo: a hex-WKB temporal and a static geometry -> hex-WKB temporal. + * + * Generated by tools/codegen/codegen_nebula.py. + */ +class TposeApplyGeoPhysicalFunction : public PhysicalFunctionConcept { +public: + TposeApplyGeoPhysicalFunction(PhysicalFunction trajFunction, + PhysicalFunction arg0Function); + + VarVal execute(const Record& record, ArenaRef& arena) const override; + +private: + std::vector parameterFunctions; +}; + +} // namespace NES diff --git a/nes-physical-operators/include/Functions/Meos/TrgeometryAtGeomPhysicalFunction.hpp b/nes-physical-operators/include/Functions/Meos/TrgeometryAtGeomPhysicalFunction.hpp new file mode 100644 index 0000000000..3236f5e516 --- /dev/null +++ b/nes-physical-operators/include/Functions/Meos/TrgeometryAtGeomPhysicalFunction.hpp @@ -0,0 +1,42 @@ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#pragma once + +#include +#include +#include +#include + +namespace NES { + +/** + * @brief Physical operator for `trgeometry_at_geom`. + * + * Per-event trgeometry_at_geom: a hex-WKB temporal and a static geometry -> hex-WKB temporal. + * + * Generated by tools/codegen/codegen_nebula.py. + */ +class TrgeometryAtGeomPhysicalFunction : public PhysicalFunctionConcept { +public: + TrgeometryAtGeomPhysicalFunction(PhysicalFunction trajFunction, + PhysicalFunction arg0Function); + + VarVal execute(const Record& record, ArenaRef& arena) const override; + +private: + std::vector parameterFunctions; +}; + +} // namespace NES diff --git a/nes-physical-operators/include/Functions/Meos/TrgeometryBodyPointTrajectoryPhysicalFunction.hpp b/nes-physical-operators/include/Functions/Meos/TrgeometryBodyPointTrajectoryPhysicalFunction.hpp new file mode 100644 index 0000000000..9511c3215a --- /dev/null +++ b/nes-physical-operators/include/Functions/Meos/TrgeometryBodyPointTrajectoryPhysicalFunction.hpp @@ -0,0 +1,42 @@ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#pragma once + +#include +#include +#include +#include + +namespace NES { + +/** + * @brief Physical operator for `trgeometry_body_point_trajectory`. + * + * Per-event trgeometry_body_point_trajectory: a hex-WKB temporal and a static geometry -> hex-WKB temporal. + * + * Generated by tools/codegen/codegen_nebula.py. + */ +class TrgeometryBodyPointTrajectoryPhysicalFunction : public PhysicalFunctionConcept { +public: + TrgeometryBodyPointTrajectoryPhysicalFunction(PhysicalFunction trajFunction, + PhysicalFunction arg0Function); + + VarVal execute(const Record& record, ArenaRef& arena) const override; + +private: + std::vector parameterFunctions; +}; + +} // namespace NES diff --git a/nes-physical-operators/include/Functions/Meos/TrgeometryMinusGeomPhysicalFunction.hpp b/nes-physical-operators/include/Functions/Meos/TrgeometryMinusGeomPhysicalFunction.hpp new file mode 100644 index 0000000000..b75c24bec0 --- /dev/null +++ b/nes-physical-operators/include/Functions/Meos/TrgeometryMinusGeomPhysicalFunction.hpp @@ -0,0 +1,42 @@ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#pragma once + +#include +#include +#include +#include + +namespace NES { + +/** + * @brief Physical operator for `trgeometry_minus_geom`. + * + * Per-event trgeometry_minus_geom: a hex-WKB temporal and a static geometry -> hex-WKB temporal. + * + * Generated by tools/codegen/codegen_nebula.py. + */ +class TrgeometryMinusGeomPhysicalFunction : public PhysicalFunctionConcept { +public: + TrgeometryMinusGeomPhysicalFunction(PhysicalFunction trajFunction, + PhysicalFunction arg0Function); + + VarVal execute(const Record& record, ArenaRef& arena) const override; + +private: + std::vector parameterFunctions; +}; + +} // namespace NES diff --git a/nes-physical-operators/src/Functions/Meos/CMakeLists.txt b/nes-physical-operators/src/Functions/Meos/CMakeLists.txt index cf83ac5aad..06abae100b 100644 --- a/nes-physical-operators/src/Functions/Meos/CMakeLists.txt +++ b/nes-physical-operators/src/Functions/Meos/CMakeLists.txt @@ -17,4 +17,8 @@ add_plugin(TemporalAIntersectsGeometry PhysicalFunction nes-physical-operators T add_plugin(TemporalEContainsGeometry PhysicalFunction nes-physical-operators TemporalEContainsGeometryPhysicalFunction.cpp) add_plugin(TemporalEDWithinGeometry PhysicalFunction nes-physical-operators TemporalEDWithinGeometryPhysicalFunction.cpp) add_plugin(TemporalAtStBox PhysicalFunction nes-physical-operators TemporalAtStBoxPhysicalFunction.cpp) +add_plugin(TposeApplyGeo PhysicalFunction nes-physical-operators TposeApplyGeoPhysicalFunction.cpp) +add_plugin(TrgeometryAtGeom PhysicalFunction nes-physical-operators TrgeometryAtGeomPhysicalFunction.cpp) +add_plugin(TrgeometryBodyPointTrajectory PhysicalFunction nes-physical-operators TrgeometryBodyPointTrajectoryPhysicalFunction.cpp) +add_plugin(TrgeometryMinusGeom PhysicalFunction nes-physical-operators TrgeometryMinusGeomPhysicalFunction.cpp) endif() diff --git a/nes-physical-operators/src/Functions/Meos/TposeApplyGeoPhysicalFunction.cpp b/nes-physical-operators/src/Functions/Meos/TposeApplyGeoPhysicalFunction.cpp new file mode 100644 index 0000000000..4b26ab8bcd --- /dev/null +++ b/nes-physical-operators/src/Functions/Meos/TposeApplyGeoPhysicalFunction.cpp @@ -0,0 +1,124 @@ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" { +#include +#include +#include +} + +namespace NES { + +TposeApplyGeoPhysicalFunction::TposeApplyGeoPhysicalFunction(PhysicalFunction trajFunction, + PhysicalFunction arg0Function) +{ + parameterFunctions.reserve(2); + parameterFunctions.push_back(std::move(trajFunction)); + parameterFunctions.push_back(std::move(arg0Function)); +} + +VarVal TposeApplyGeoPhysicalFunction::execute(const Record& record, ArenaRef& arena) const +{ + std::vector parameterValues; + parameterValues.reserve(parameterFunctions.size()); + for (const auto& function : parameterFunctions) + { + parameterValues.emplace_back(function.execute(record, arena)); + } + + auto traj = parameterValues[0].cast(); + auto arg0 = parameterValues[1].cast(); + + // Call MEOS f over one hex-WKB temporal operand and a static geometry (WKT/text + // literal) and serialize the temporal result back to hex-WKB inside the invoke; the + // returned heap string is copied into the arena below. The operand, the parsed + // geometry (StaticGeometry RAII), and the MEOS result are freed here. + auto hexStr = nautilus::invoke( + +[](const char* aPtr, uint32_t aSize, const char* gPtr, uint32_t gSize) -> char* + { + try + { + MEOS::Meos::ensureMeosInitialized(); + std::string aHex(aPtr, aSize); + Temporal* a = temporal_from_hexwkb(aHex.c_str()); + if (!a) return (char*) nullptr; + std::string gS(gPtr, gSize); + while (!gS.empty() && (gS.front()=='\'' || gS.front()=='"')) gS.erase(gS.begin()); + while (!gS.empty() && (gS.back()=='\'' || gS.back()=='"')) gS.pop_back(); + MEOS::Meos::StaticGeometry g(gS); + if (!g.getGeometry()) { free(a); return (char*) nullptr; } + Temporal* res = tpose_apply_geo(a, g.getGeometry()); + free(a); + if (!res) return (char*) nullptr; + size_t hexSize = 0; + char* hexOut = temporal_as_hexwkb(res, 0, &hexSize); + free(res); + return hexOut; + } + catch (const std::exception&) + { + return (char*) nullptr; + } + }, + traj.getContent(), traj.getContentSize(), arg0.getContent(), arg0.getContentSize()); + + const auto hexLen = nautilus::invoke( + +[](const char* s) -> uint32_t { return s ? (uint32_t) strlen(s) : (uint32_t) 0; }, + hexStr); + + auto variableSized = arena.allocateVariableSizedData(hexLen); + + nautilus::invoke( + +[](int8_t* dest, const char* s, uint32_t len) -> void + { + if (s) + { + memcpy(dest, s, len); + free((void*) s); + } + }, + variableSized.getContent(), hexStr, hexLen); + + return variableSized; +} + +PhysicalFunctionRegistryReturnType PhysicalFunctionGeneratedRegistrar::RegisterTposeApplyGeoPhysicalFunction( + PhysicalFunctionRegistryArguments arguments) +{ + PRECONDITION(arguments.childFunctions.size() == 2, + "TposeApplyGeoPhysicalFunction requires 2 children but got {}", + arguments.childFunctions.size()); + auto arg0 = std::move(arguments.childFunctions[0]); + auto arg1 = std::move(arguments.childFunctions[1]); + return TposeApplyGeoPhysicalFunction(std::move(arg0), std::move(arg1)); +} + +} // namespace NES diff --git a/nes-physical-operators/src/Functions/Meos/TrgeometryAtGeomPhysicalFunction.cpp b/nes-physical-operators/src/Functions/Meos/TrgeometryAtGeomPhysicalFunction.cpp new file mode 100644 index 0000000000..3e53b7e7c7 --- /dev/null +++ b/nes-physical-operators/src/Functions/Meos/TrgeometryAtGeomPhysicalFunction.cpp @@ -0,0 +1,124 @@ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" { +#include +#include +#include +} + +namespace NES { + +TrgeometryAtGeomPhysicalFunction::TrgeometryAtGeomPhysicalFunction(PhysicalFunction trajFunction, + PhysicalFunction arg0Function) +{ + parameterFunctions.reserve(2); + parameterFunctions.push_back(std::move(trajFunction)); + parameterFunctions.push_back(std::move(arg0Function)); +} + +VarVal TrgeometryAtGeomPhysicalFunction::execute(const Record& record, ArenaRef& arena) const +{ + std::vector parameterValues; + parameterValues.reserve(parameterFunctions.size()); + for (const auto& function : parameterFunctions) + { + parameterValues.emplace_back(function.execute(record, arena)); + } + + auto traj = parameterValues[0].cast(); + auto arg0 = parameterValues[1].cast(); + + // Call MEOS f over one hex-WKB temporal operand and a static geometry (WKT/text + // literal) and serialize the temporal result back to hex-WKB inside the invoke; the + // returned heap string is copied into the arena below. The operand, the parsed + // geometry (StaticGeometry RAII), and the MEOS result are freed here. + auto hexStr = nautilus::invoke( + +[](const char* aPtr, uint32_t aSize, const char* gPtr, uint32_t gSize) -> char* + { + try + { + MEOS::Meos::ensureMeosInitialized(); + std::string aHex(aPtr, aSize); + Temporal* a = temporal_from_hexwkb(aHex.c_str()); + if (!a) return (char*) nullptr; + std::string gS(gPtr, gSize); + while (!gS.empty() && (gS.front()=='\'' || gS.front()=='"')) gS.erase(gS.begin()); + while (!gS.empty() && (gS.back()=='\'' || gS.back()=='"')) gS.pop_back(); + MEOS::Meos::StaticGeometry g(gS); + if (!g.getGeometry()) { free(a); return (char*) nullptr; } + Temporal* res = trgeometry_at_geom(a, g.getGeometry()); + free(a); + if (!res) return (char*) nullptr; + size_t hexSize = 0; + char* hexOut = temporal_as_hexwkb(res, 0, &hexSize); + free(res); + return hexOut; + } + catch (const std::exception&) + { + return (char*) nullptr; + } + }, + traj.getContent(), traj.getContentSize(), arg0.getContent(), arg0.getContentSize()); + + const auto hexLen = nautilus::invoke( + +[](const char* s) -> uint32_t { return s ? (uint32_t) strlen(s) : (uint32_t) 0; }, + hexStr); + + auto variableSized = arena.allocateVariableSizedData(hexLen); + + nautilus::invoke( + +[](int8_t* dest, const char* s, uint32_t len) -> void + { + if (s) + { + memcpy(dest, s, len); + free((void*) s); + } + }, + variableSized.getContent(), hexStr, hexLen); + + return variableSized; +} + +PhysicalFunctionRegistryReturnType PhysicalFunctionGeneratedRegistrar::RegisterTrgeometryAtGeomPhysicalFunction( + PhysicalFunctionRegistryArguments arguments) +{ + PRECONDITION(arguments.childFunctions.size() == 2, + "TrgeometryAtGeomPhysicalFunction requires 2 children but got {}", + arguments.childFunctions.size()); + auto arg0 = std::move(arguments.childFunctions[0]); + auto arg1 = std::move(arguments.childFunctions[1]); + return TrgeometryAtGeomPhysicalFunction(std::move(arg0), std::move(arg1)); +} + +} // namespace NES diff --git a/nes-physical-operators/src/Functions/Meos/TrgeometryBodyPointTrajectoryPhysicalFunction.cpp b/nes-physical-operators/src/Functions/Meos/TrgeometryBodyPointTrajectoryPhysicalFunction.cpp new file mode 100644 index 0000000000..22c49d2a54 --- /dev/null +++ b/nes-physical-operators/src/Functions/Meos/TrgeometryBodyPointTrajectoryPhysicalFunction.cpp @@ -0,0 +1,124 @@ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" { +#include +#include +#include +} + +namespace NES { + +TrgeometryBodyPointTrajectoryPhysicalFunction::TrgeometryBodyPointTrajectoryPhysicalFunction(PhysicalFunction trajFunction, + PhysicalFunction arg0Function) +{ + parameterFunctions.reserve(2); + parameterFunctions.push_back(std::move(trajFunction)); + parameterFunctions.push_back(std::move(arg0Function)); +} + +VarVal TrgeometryBodyPointTrajectoryPhysicalFunction::execute(const Record& record, ArenaRef& arena) const +{ + std::vector parameterValues; + parameterValues.reserve(parameterFunctions.size()); + for (const auto& function : parameterFunctions) + { + parameterValues.emplace_back(function.execute(record, arena)); + } + + auto traj = parameterValues[0].cast(); + auto arg0 = parameterValues[1].cast(); + + // Call MEOS f over one hex-WKB temporal operand and a static geometry (WKT/text + // literal) and serialize the temporal result back to hex-WKB inside the invoke; the + // returned heap string is copied into the arena below. The operand, the parsed + // geometry (StaticGeometry RAII), and the MEOS result are freed here. + auto hexStr = nautilus::invoke( + +[](const char* aPtr, uint32_t aSize, const char* gPtr, uint32_t gSize) -> char* + { + try + { + MEOS::Meos::ensureMeosInitialized(); + std::string aHex(aPtr, aSize); + Temporal* a = temporal_from_hexwkb(aHex.c_str()); + if (!a) return (char*) nullptr; + std::string gS(gPtr, gSize); + while (!gS.empty() && (gS.front()=='\'' || gS.front()=='"')) gS.erase(gS.begin()); + while (!gS.empty() && (gS.back()=='\'' || gS.back()=='"')) gS.pop_back(); + MEOS::Meos::StaticGeometry g(gS); + if (!g.getGeometry()) { free(a); return (char*) nullptr; } + Temporal* res = trgeometry_body_point_trajectory(a, g.getGeometry()); + free(a); + if (!res) return (char*) nullptr; + size_t hexSize = 0; + char* hexOut = temporal_as_hexwkb(res, 0, &hexSize); + free(res); + return hexOut; + } + catch (const std::exception&) + { + return (char*) nullptr; + } + }, + traj.getContent(), traj.getContentSize(), arg0.getContent(), arg0.getContentSize()); + + const auto hexLen = nautilus::invoke( + +[](const char* s) -> uint32_t { return s ? (uint32_t) strlen(s) : (uint32_t) 0; }, + hexStr); + + auto variableSized = arena.allocateVariableSizedData(hexLen); + + nautilus::invoke( + +[](int8_t* dest, const char* s, uint32_t len) -> void + { + if (s) + { + memcpy(dest, s, len); + free((void*) s); + } + }, + variableSized.getContent(), hexStr, hexLen); + + return variableSized; +} + +PhysicalFunctionRegistryReturnType PhysicalFunctionGeneratedRegistrar::RegisterTrgeometryBodyPointTrajectoryPhysicalFunction( + PhysicalFunctionRegistryArguments arguments) +{ + PRECONDITION(arguments.childFunctions.size() == 2, + "TrgeometryBodyPointTrajectoryPhysicalFunction requires 2 children but got {}", + arguments.childFunctions.size()); + auto arg0 = std::move(arguments.childFunctions[0]); + auto arg1 = std::move(arguments.childFunctions[1]); + return TrgeometryBodyPointTrajectoryPhysicalFunction(std::move(arg0), std::move(arg1)); +} + +} // namespace NES diff --git a/nes-physical-operators/src/Functions/Meos/TrgeometryMinusGeomPhysicalFunction.cpp b/nes-physical-operators/src/Functions/Meos/TrgeometryMinusGeomPhysicalFunction.cpp new file mode 100644 index 0000000000..6bd726ef4b --- /dev/null +++ b/nes-physical-operators/src/Functions/Meos/TrgeometryMinusGeomPhysicalFunction.cpp @@ -0,0 +1,124 @@ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" { +#include +#include +#include +} + +namespace NES { + +TrgeometryMinusGeomPhysicalFunction::TrgeometryMinusGeomPhysicalFunction(PhysicalFunction trajFunction, + PhysicalFunction arg0Function) +{ + parameterFunctions.reserve(2); + parameterFunctions.push_back(std::move(trajFunction)); + parameterFunctions.push_back(std::move(arg0Function)); +} + +VarVal TrgeometryMinusGeomPhysicalFunction::execute(const Record& record, ArenaRef& arena) const +{ + std::vector parameterValues; + parameterValues.reserve(parameterFunctions.size()); + for (const auto& function : parameterFunctions) + { + parameterValues.emplace_back(function.execute(record, arena)); + } + + auto traj = parameterValues[0].cast(); + auto arg0 = parameterValues[1].cast(); + + // Call MEOS f over one hex-WKB temporal operand and a static geometry (WKT/text + // literal) and serialize the temporal result back to hex-WKB inside the invoke; the + // returned heap string is copied into the arena below. The operand, the parsed + // geometry (StaticGeometry RAII), and the MEOS result are freed here. + auto hexStr = nautilus::invoke( + +[](const char* aPtr, uint32_t aSize, const char* gPtr, uint32_t gSize) -> char* + { + try + { + MEOS::Meos::ensureMeosInitialized(); + std::string aHex(aPtr, aSize); + Temporal* a = temporal_from_hexwkb(aHex.c_str()); + if (!a) return (char*) nullptr; + std::string gS(gPtr, gSize); + while (!gS.empty() && (gS.front()=='\'' || gS.front()=='"')) gS.erase(gS.begin()); + while (!gS.empty() && (gS.back()=='\'' || gS.back()=='"')) gS.pop_back(); + MEOS::Meos::StaticGeometry g(gS); + if (!g.getGeometry()) { free(a); return (char*) nullptr; } + Temporal* res = trgeometry_minus_geom(a, g.getGeometry()); + free(a); + if (!res) return (char*) nullptr; + size_t hexSize = 0; + char* hexOut = temporal_as_hexwkb(res, 0, &hexSize); + free(res); + return hexOut; + } + catch (const std::exception&) + { + return (char*) nullptr; + } + }, + traj.getContent(), traj.getContentSize(), arg0.getContent(), arg0.getContentSize()); + + const auto hexLen = nautilus::invoke( + +[](const char* s) -> uint32_t { return s ? (uint32_t) strlen(s) : (uint32_t) 0; }, + hexStr); + + auto variableSized = arena.allocateVariableSizedData(hexLen); + + nautilus::invoke( + +[](int8_t* dest, const char* s, uint32_t len) -> void + { + if (s) + { + memcpy(dest, s, len); + free((void*) s); + } + }, + variableSized.getContent(), hexStr, hexLen); + + return variableSized; +} + +PhysicalFunctionRegistryReturnType PhysicalFunctionGeneratedRegistrar::RegisterTrgeometryMinusGeomPhysicalFunction( + PhysicalFunctionRegistryArguments arguments) +{ + PRECONDITION(arguments.childFunctions.size() == 2, + "TrgeometryMinusGeomPhysicalFunction requires 2 children but got {}", + arguments.childFunctions.size()); + auto arg0 = std::move(arguments.childFunctions[0]); + auto arg1 = std::move(arguments.childFunctions[1]); + return TrgeometryMinusGeomPhysicalFunction(std::move(arg0), std::move(arg1)); +} + +} // namespace NES diff --git a/nes-sql-parser/AntlrSQL.g4 b/nes-sql-parser/AntlrSQL.g4 index 256726e087..abdb35cda9 100644 --- a/nes-sql-parser/AntlrSQL.g4 +++ b/nes-sql-parser/AntlrSQL.g4 @@ -295,7 +295,7 @@ timeUnit: MS timestampParameter: name=identifier; -functionName: IDENTIFIER | AVG | MAX | MIN | SUM | COUNT | MEDIAN | ARRAY_AGG | VAR | TEMPORAL_SEQUENCE | TEMPORAL_EINTERSECTS_GEOMETRY | TEMPORAL_AINTERSECTS_GEOMETRY | TEMPORAL_ECONTAINS_GEOMETRY | EDWITHIN_TGEO_GEO | TGEO_AT_STBOX; +functionName: IDENTIFIER | AVG | MAX | MIN | SUM | COUNT | MEDIAN | ARRAY_AGG | VAR | TEMPORAL_SEQUENCE | TEMPORAL_EINTERSECTS_GEOMETRY | TEMPORAL_AINTERSECTS_GEOMETRY | TEMPORAL_ECONTAINS_GEOMETRY | EDWITHIN_TGEO_GEO | TGEO_AT_STBOX | TPOSE_APPLY_GEO | TRGEOMETRY_AT_GEOM | TRGEOMETRY_BODY_POINT_TRAJECTORY | TRGEOMETRY_MINUS_GEOM; sinkClause: INTO sink (',' sink)*; @@ -488,6 +488,12 @@ TEMPORAL_AINTERSECTS_GEOMETRY: 'TEMPORAL_AINTERSECTS_GEOMETRY' | 'temporal_ainte TEMPORAL_ECONTAINS_GEOMETRY: 'TEMPORAL_ECONTAINS_GEOMETRY' | 'temporal_econtains_geometry'; EDWITHIN_TGEO_GEO: 'EDWITHIN_TGEO_GEO' | 'edwithin_tgeo_geo'; TGEO_AT_STBOX: 'TGEO_AT_STBOX' | 'tgeo_at_stbox'; +/* BEGIN CODEGEN LEXER TOKENS */ +TPOSE_APPLY_GEO: 'TPOSE_APPLY_GEO' | 'tpose_apply_geo'; +TRGEOMETRY_AT_GEOM: 'TRGEOMETRY_AT_GEOM' | 'trgeometry_at_geom'; +TRGEOMETRY_BODY_POINT_TRAJECTORY: 'TRGEOMETRY_BODY_POINT_TRAJECTORY' | 'trgeometry_body_point_trajectory'; +TRGEOMETRY_MINUS_GEOM: 'TRGEOMETRY_MINUS_GEOM' | 'trgeometry_minus_geom'; +/* END CODEGEN LEXER TOKENS */ WATERMARK: 'WATERMARK' | 'watermark'; OFFSET: 'OFFSET' | 'offset'; LOCALHOST: 'LOCALHOST' | 'localhost'; diff --git a/nes-sql-parser/src/AntlrSQLQueryPlanCreator.cpp b/nes-sql-parser/src/AntlrSQLQueryPlanCreator.cpp index 4e9f1d7642..d6b13a376b 100644 --- a/nes-sql-parser/src/AntlrSQLQueryPlanCreator.cpp +++ b/nes-sql-parser/src/AntlrSQLQueryPlanCreator.cpp @@ -69,6 +69,10 @@ #include #include #include +#include +#include +#include +#include #include #include #include @@ -1188,6 +1192,123 @@ void AntlrSQLQueryPlanCreator::exitFunctionCall(AntlrSQLParser::FunctionCallCont } break; + /* BEGIN CODEGEN PARSER GLUE: TPOSE_APPLY_GEO */ + case AntlrSQLLexer::TPOSE_APPLY_GEO: + { + const auto argCount = context->expression().size(); + if (argCount != 2) + throw InvalidQuerySyntax("TPOSE_APPLY_GEO requires exactly 2 arguments, but got {}", argCount); + + while (!helpers.top().constantBuilder.empty()) + { + auto constantValue = std::move(helpers.top().constantBuilder.back()); + helpers.top().constantBuilder.pop_back(); + DataType dataType; + char* endPtr = nullptr; + std::strtod(constantValue.c_str(), &endPtr); + if (endPtr != nullptr && *endPtr == '\0') + dataType = DataTypeProvider::provideDataType(DataType::Type::FLOAT64); + else + dataType = DataTypeProvider::provideDataType(DataType::Type::VARSIZED); + helpers.top().functionBuilder.emplace_back(ConstantValueLogicalFunction(dataType, std::move(constantValue))); + } + + auto a1 = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + auto a0 = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + + helpers.top().functionBuilder.emplace_back(TposeApplyGeoLogicalFunction(a0, a1)); + } + break; + /* END CODEGEN PARSER GLUE: TPOSE_APPLY_GEO */ + + /* BEGIN CODEGEN PARSER GLUE: TRGEOMETRY_AT_GEOM */ + case AntlrSQLLexer::TRGEOMETRY_AT_GEOM: + { + const auto argCount = context->expression().size(); + if (argCount != 2) + throw InvalidQuerySyntax("TRGEOMETRY_AT_GEOM requires exactly 2 arguments, but got {}", argCount); + + while (!helpers.top().constantBuilder.empty()) + { + auto constantValue = std::move(helpers.top().constantBuilder.back()); + helpers.top().constantBuilder.pop_back(); + DataType dataType; + char* endPtr = nullptr; + std::strtod(constantValue.c_str(), &endPtr); + if (endPtr != nullptr && *endPtr == '\0') + dataType = DataTypeProvider::provideDataType(DataType::Type::FLOAT64); + else + dataType = DataTypeProvider::provideDataType(DataType::Type::VARSIZED); + helpers.top().functionBuilder.emplace_back(ConstantValueLogicalFunction(dataType, std::move(constantValue))); + } + + auto a1 = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + auto a0 = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + + helpers.top().functionBuilder.emplace_back(TrgeometryAtGeomLogicalFunction(a0, a1)); + } + break; + /* END CODEGEN PARSER GLUE: TRGEOMETRY_AT_GEOM */ + + /* BEGIN CODEGEN PARSER GLUE: TRGEOMETRY_BODY_POINT_TRAJECTORY */ + case AntlrSQLLexer::TRGEOMETRY_BODY_POINT_TRAJECTORY: + { + const auto argCount = context->expression().size(); + if (argCount != 2) + throw InvalidQuerySyntax("TRGEOMETRY_BODY_POINT_TRAJECTORY requires exactly 2 arguments, but got {}", argCount); + + while (!helpers.top().constantBuilder.empty()) + { + auto constantValue = std::move(helpers.top().constantBuilder.back()); + helpers.top().constantBuilder.pop_back(); + DataType dataType; + char* endPtr = nullptr; + std::strtod(constantValue.c_str(), &endPtr); + if (endPtr != nullptr && *endPtr == '\0') + dataType = DataTypeProvider::provideDataType(DataType::Type::FLOAT64); + else + dataType = DataTypeProvider::provideDataType(DataType::Type::VARSIZED); + helpers.top().functionBuilder.emplace_back(ConstantValueLogicalFunction(dataType, std::move(constantValue))); + } + + auto a1 = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + auto a0 = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + + helpers.top().functionBuilder.emplace_back(TrgeometryBodyPointTrajectoryLogicalFunction(a0, a1)); + } + break; + /* END CODEGEN PARSER GLUE: TRGEOMETRY_BODY_POINT_TRAJECTORY */ + + /* BEGIN CODEGEN PARSER GLUE: TRGEOMETRY_MINUS_GEOM */ + case AntlrSQLLexer::TRGEOMETRY_MINUS_GEOM: + { + const auto argCount = context->expression().size(); + if (argCount != 2) + throw InvalidQuerySyntax("TRGEOMETRY_MINUS_GEOM requires exactly 2 arguments, but got {}", argCount); + + while (!helpers.top().constantBuilder.empty()) + { + auto constantValue = std::move(helpers.top().constantBuilder.back()); + helpers.top().constantBuilder.pop_back(); + DataType dataType; + char* endPtr = nullptr; + std::strtod(constantValue.c_str(), &endPtr); + if (endPtr != nullptr && *endPtr == '\0') + dataType = DataTypeProvider::provideDataType(DataType::Type::FLOAT64); + else + dataType = DataTypeProvider::provideDataType(DataType::Type::VARSIZED); + helpers.top().functionBuilder.emplace_back(ConstantValueLogicalFunction(dataType, std::move(constantValue))); + } + + auto a1 = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + auto a0 = helpers.top().functionBuilder.back(); helpers.top().functionBuilder.pop_back(); + + helpers.top().functionBuilder.emplace_back(TrgeometryMinusGeomLogicalFunction(a0, a1)); + } + break; + /* END CODEGEN PARSER GLUE: TRGEOMETRY_MINUS_GEOM */ + + default: /// Check if the function is a constructor for a datatype if (const auto dataType = DataTypeProvider::tryProvideDataType(funcName); dataType.has_value()) diff --git a/tools/codegen/build_descriptor.py b/tools/codegen/build_descriptor.py index 3d77389490..9d6c2dff19 100644 --- a/tools/codegen/build_descriptor.py +++ b/tools/codegen/build_descriptor.py @@ -636,12 +636,45 @@ def trgeometry_nad(fn, ret, args): } +def temporal_geom_transform_wkb(fn, ret, args): + """Temporal* fn(const Temporal*, const GSERIALIZED*) — a per-event transform of a + temporal against a static geometry whose result is itself a temporal (restrict / + difference / project / body-trajectory of a temporal spatial value against a + geometry). The temporal operand is carried as a hex-WKB VARSIZED field and the + static geometry as a WKT/text VARSIZED literal; the temporal result is serialized + back to hex-WKB VARSIZED, the same serialize-on-return round-trip + two_temporal_temporal uses with a static-geometry second operand.""" + if ret != "Temporal*" or args != ("Temporal*", "GSERIALIZED*"): + return None + extra_headers = [] + if "trgeometry" in fn: + extra_headers = ["meos_rgeo.h"] + elif "tpose" in fn: + extra_headers = ["meos_pose.h"] + elif "tnpoint" in fn: + extra_headers = ["meos_npoint.h"] + elif "tcbuffer" in fn: + extra_headers = ["meos_cbuffer.h"] + d = { + "nebula_name": pascal(fn), "sql_token": fn.upper(), "meos_call": fn, + "build_generic": True, "input_type": "wkb_temporal", "return_kind": "wkb", + "extra_args": [{"kind": "geom"}], + "comment_one_liner": ( + f"Per-event {fn}: a hex-WKB temporal and a static geometry -> " + f"hex-WKB temporal."), + } + if extra_headers: + d["extra_headers"] = extra_headers + return d + + SHAPES = { "cmp_scalar_tempfirst": cmp_scalar_tempfirst, "cmp_scalar_scalarfirst": cmp_scalar_scalarfirst, "cmp_two_temporal": cmp_two_temporal, "two_temporal_scalar": two_temporal_scalar, "two_temporal_temporal": two_temporal_temporal, + "temporal_geom_transform_wkb": temporal_geom_transform_wkb, "sprel_scalar_existing": sprel_scalar_existing, "temporal_unary_scalar": temporal_unary_scalar, "temporal_x_scalar": temporal_x_scalar, diff --git a/tools/codegen/codegen_nebula.py b/tools/codegen/codegen_nebula.py index 8744d8463b..8ae81467d0 100644 --- a/tools/codegen/codegen_nebula.py +++ b/tools/codegen/codegen_nebula.py @@ -4055,10 +4055,30 @@ def assemble_wkb_output(op): registrar_pushes=registrar) +def assemble_wkb_output_geom(op): + """Physical .cpp for a per-event op f(Temporal*, GSERIALIZED*) -> Temporal* over one + hex-WKB VARSIZED temporal operand and one static geometry (WKT/text VARSIZED literal), + emitting the temporal result as a hex-WKB VARSIZED field — the temporal-vs-geometry + transform round-trip (restrict / difference / project a temporal spatial value to a + geometry). The geometry is parsed with the StaticGeometry RAII wrapper (leading/ + trailing quotes stripped), matching the scalar-return generic path.""" + name = op["nebula_name"] + headers = {"meos.h", "meos_geo.h"} + for h in op.get("extra_headers", []): + headers.add(h) + inc = "\n".join(f"#include <{h}>" for h in + ["meos.h"] + sorted(h for h in headers if h != "meos.h")) + return GENERIC_PHYSICAL_WKB_GEOM_TEMPLATE.format( + nebula_name=name, includes=inc, meos_call=op["meos_call"]) + + def assemble_generic_physical(op): """Build the physical .cpp for a 'generic' (build_generic) operator.""" name = op["nebula_name"] if op["return_kind"] == "wkb": + extras = op.get("extra_args", []) + if len(extras) == 1 and extras[0]["kind"] == "geom": + return assemble_wkb_output_geom(op) return assemble_wkb_output(op) inp = GENERIC_INPUTS[op["input_type"]] ret_cpp, _, zero, extract_fn = GENERIC_RETURNS[op["return_kind"]] @@ -4257,6 +4277,132 @@ def assemble_generic_physical(op): """ +GENERIC_PHYSICAL_WKB_GEOM_TEMPLATE = """\ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" {{ +{includes} +}} + +namespace NES {{ + +{nebula_name}PhysicalFunction::{nebula_name}PhysicalFunction(PhysicalFunction trajFunction, + PhysicalFunction arg0Function) +{{ + parameterFunctions.reserve(2); + parameterFunctions.push_back(std::move(trajFunction)); + parameterFunctions.push_back(std::move(arg0Function)); +}} + +VarVal {nebula_name}PhysicalFunction::execute(const Record& record, ArenaRef& arena) const +{{ + std::vector parameterValues; + parameterValues.reserve(parameterFunctions.size()); + for (const auto& function : parameterFunctions) + {{ + parameterValues.emplace_back(function.execute(record, arena)); + }} + + auto traj = parameterValues[0].cast(); + auto arg0 = parameterValues[1].cast(); + + // Call MEOS f over one hex-WKB temporal operand and a static geometry (WKT/text + // literal) and serialize the temporal result back to hex-WKB inside the invoke; the + // returned heap string is copied into the arena below. The operand, the parsed + // geometry (StaticGeometry RAII), and the MEOS result are freed here. + auto hexStr = nautilus::invoke( + +[](const char* aPtr, uint32_t aSize, const char* gPtr, uint32_t gSize) -> char* + {{ + try + {{ + MEOS::Meos::ensureMeosInitialized(); + std::string aHex(aPtr, aSize); + Temporal* a = temporal_from_hexwkb(aHex.c_str()); + if (!a) return (char*) nullptr; + std::string gS(gPtr, gSize); + while (!gS.empty() && (gS.front()=='\\'' || gS.front()=='"')) gS.erase(gS.begin()); + while (!gS.empty() && (gS.back()=='\\'' || gS.back()=='"')) gS.pop_back(); + MEOS::Meos::StaticGeometry g(gS); + if (!g.getGeometry()) {{ free(a); return (char*) nullptr; }} + Temporal* res = {meos_call}(a, g.getGeometry()); + free(a); + if (!res) return (char*) nullptr; + size_t hexSize = 0; + char* hexOut = temporal_as_hexwkb(res, 0, &hexSize); + free(res); + return hexOut; + }} + catch (const std::exception&) + {{ + return (char*) nullptr; + }} + }}, + traj.getContent(), traj.getContentSize(), arg0.getContent(), arg0.getContentSize()); + + const auto hexLen = nautilus::invoke( + +[](const char* s) -> uint32_t {{ return s ? (uint32_t) strlen(s) : (uint32_t) 0; }}, + hexStr); + + auto variableSized = arena.allocateVariableSizedData(hexLen); + + nautilus::invoke( + +[](int8_t* dest, const char* s, uint32_t len) -> void + {{ + if (s) + {{ + memcpy(dest, s, len); + free((void*) s); + }} + }}, + variableSized.getContent(), hexStr, hexLen); + + return variableSized; +}} + +PhysicalFunctionRegistryReturnType PhysicalFunctionGeneratedRegistrar::Register{nebula_name}PhysicalFunction( + PhysicalFunctionRegistryArguments arguments) +{{ + PRECONDITION(arguments.childFunctions.size() == 2, + "{nebula_name}PhysicalFunction requires 2 children but got {{}}", + arguments.childFunctions.size()); + auto arg0 = std::move(arguments.childFunctions[0]); + auto arg1 = std::move(arguments.childFunctions[1]); + return {nebula_name}PhysicalFunction(std::move(arg0), std::move(arg1)); +}} + +}} // namespace NES +""" + + GENERIC_PHYSICAL_WKB_TEMPLATE = """\ /* Licensed under the Apache License, Version 2.0 (the "License");