|
| 1 | +# MobilityNebula MEOS-operator codegen — design + generator |
| 2 | + |
| 3 | +This directory contains the design proposal and Python generator for |
| 4 | +scaling MobilityNebula's MEOS-operator surface from the current |
| 5 | +~17 hand-written operators (PRs #14, #15, #16, #17) to a larger |
| 6 | +fraction of MEOS' ~1,949 streamable public functions, mirroring the |
| 7 | +infrastructure parity that the Flink and Kafka platforms reached via |
| 8 | +their codegen + wirings stacks. |
| 9 | + |
| 10 | +## Why codegen on Nebula |
| 11 | + |
| 12 | +The streaming-platform parity audit |
| 13 | +([assessment](../../docs/berlinmod-streaming-forms.md)) shows: |
| 14 | + |
| 15 | +| Platform | Wirable MEOS surface | |
| 16 | +|---|---:| |
| 17 | +| Flink | 2,097 / 2,097 (100%) via codegen + 5 generic wiring classes | |
| 18 | +| Kafka | 2,097 / 2,097 (100%) via codegen + 5 generic wiring classes | |
| 19 | +| **Nebula** | **~17 / 2,097 (~1%)** via hand-written 4-layer pipeline per function | |
| 20 | + |
| 21 | +The Nebula gap is structural: each MEOS function on NebulaStream |
| 22 | +requires a full **4-layer pipeline tuple** — logical class, physical |
| 23 | +class, parser dispatch, lowering rule — totalling ~350–400 LOC of |
| 24 | +mostly-mechanical boilerplate per function. Hand-writing all of MEOS' |
| 25 | +streamable surface this way is multi-month engineering; codegen makes |
| 26 | +it tractable. |
| 27 | + |
| 28 | +## What this codegen produces |
| 29 | + |
| 30 | +For each MEOS scalar function `f` in the input list, the generator |
| 31 | +emits the four NebulaStream pipeline-layer files following the |
| 32 | +established style of the existing hand-written operators |
| 33 | +(`TemporalEDWithinGeometryLogicalFunction` etc.): |
| 34 | + |
| 35 | +``` |
| 36 | +nes-logical-operators/include/Functions/Meos/<NebulaName>LogicalFunction.hpp |
| 37 | +nes-logical-operators/src/Functions/Meos/<NebulaName>LogicalFunction.cpp |
| 38 | +nes-physical-operators/include/Functions/Meos/<NebulaName>PhysicalFunction.hpp |
| 39 | +nes-physical-operators/src/Functions/Meos/<NebulaName>PhysicalFunction.cpp |
| 40 | +``` |
| 41 | + |
| 42 | +Plus updates to: |
| 43 | +- `nes-logical-operators/src/Functions/Meos/CMakeLists.txt` |
| 44 | +- `nes-physical-operators/src/Functions/Meos/CMakeLists.txt` |
| 45 | +- Parser dispatch: a single block per generated function inserted into |
| 46 | + `nes-sql-parser/src/AntlrSQLQueryPlanCreator.cpp` (manual edit |
| 47 | + recommended; the generator emits the dispatch snippet for |
| 48 | + copy-paste) |
| 49 | +- Parser grammar: a single token per function added to |
| 50 | + `nes-sql-parser/AntlrSQL.g4` (same) |
| 51 | + |
| 52 | +## Scope of this PR |
| 53 | + |
| 54 | +**Generator infrastructure only.** No generated C++ committed. Reasons: |
| 55 | + |
| 56 | +1. **Compile-environment constraint.** The generator's author cannot |
| 57 | + build NebulaStream (full C++23 + vcpkg toolchain). Committing |
| 58 | + unverified generated code would ship potentially broken operators. |
| 59 | +2. **Per-function review value.** Mariana (maintainer) can run the |
| 60 | + generator against a small input list (e.g. one MEOS family at a |
| 61 | + time), review the output, iterate on the templates if needed, and |
| 62 | + ship operators in follow-up PRs at a controlled pace. |
| 63 | +3. **Template iteration cost.** First-pass templates may need |
| 64 | + adjustment after the first build — better to land the generator |
| 65 | + and iterate on templates than to ship a large batch of generated |
| 66 | + operators that all have the same wrong shape. |
| 67 | + |
| 68 | +## How to use the generator |
| 69 | + |
| 70 | +```bash |
| 71 | +# Edit the input list to choose which MEOS functions to generate |
| 72 | +$EDITOR tools/codegen/codegen_input.example.json |
| 73 | + |
| 74 | +# Run the generator |
| 75 | +python3 tools/codegen/codegen_nebula.py \ |
| 76 | + --input tools/codegen/codegen_input.example.json \ |
| 77 | + --output-root . |
| 78 | + |
| 79 | +# Output: |
| 80 | +# nes-logical-operators/include/Functions/Meos/<NebulaName>LogicalFunction.hpp |
| 81 | +# nes-logical-operators/src/Functions/Meos/<NebulaName>LogicalFunction.cpp |
| 82 | +# nes-physical-operators/include/Functions/Meos/<NebulaName>PhysicalFunction.hpp |
| 83 | +# nes-physical-operators/src/Functions/Meos/<NebulaName>PhysicalFunction.cpp |
| 84 | +# |
| 85 | +# Plus a stderr-printed "parser snippet" per function that you paste into |
| 86 | +# nes-sql-parser/src/AntlrSQLQueryPlanCreator.cpp (the parser dispatch), |
| 87 | +# and a "grammar snippet" that you paste into AntlrSQL.g4 |
| 88 | +``` |
| 89 | + |
| 90 | +## Input format |
| 91 | + |
| 92 | +`codegen_input.example.json` is a list of MEOS-function descriptors. |
| 93 | +One descriptor per output operator: |
| 94 | + |
| 95 | +```json |
| 96 | +{ |
| 97 | + "operators": [ |
| 98 | + { |
| 99 | + "nebula_name": "TemporalEDisjointGeometry", |
| 100 | + "sql_token": "TEMPORAL_EDISJOINT_GEOMETRY", |
| 101 | + "meos_call": "edisjoint_tgeo_geo", |
| 102 | + "args": [ |
| 103 | + {"name": "lon", "nautilus_type": "double", "cpp_type": "double"}, |
| 104 | + {"name": "lat", "nautilus_type": "double", "cpp_type": "double"}, |
| 105 | + {"name": "timestamp","nautilus_type": "uint64_t", "cpp_type": "uint64_t"}, |
| 106 | + {"name": "geometry", "nautilus_type": "VariableSizedData", "cpp_type": "const char*"} |
| 107 | + ], |
| 108 | + "return_type": "int", |
| 109 | + "nautilus_return": "INT32", |
| 110 | + "build_temporal_point": true, |
| 111 | + "comment_one_liner": "Per-event ever-disjoint between a tgeompoint built from event fields and a static geometry." |
| 112 | + } |
| 113 | + ] |
| 114 | +} |
| 115 | +``` |
| 116 | + |
| 117 | +Field meanings: |
| 118 | +- `nebula_name`: PascalCase NebulaStream class name (without `LogicalFunction` / `PhysicalFunction` suffix; the generator adds those) |
| 119 | +- `sql_token`: the uppercase SQL function name (Antlr lexer token) |
| 120 | +- `meos_call`: the underlying MEOS C function symbol the physical operator wraps |
| 121 | +- `args`: ordered list of per-record argument fields; the generator builds the constructor + `parameters` vector from these |
| 122 | +- `return_type` / `nautilus_return`: the MEOS function's C return type and the NebulaStream `DataType::Type` enum value |
| 123 | +- `build_temporal_point`: if true, the physical operator builds a single-instant tgeompoint from `(lon, lat, timestamp)` before calling MEOS (the common pattern for spatial predicates); if false, the operator passes args directly to MEOS |
| 124 | +- `comment_one_liner`: drops into the Javadoc-equivalent C++ doc comment |
| 125 | + |
| 126 | +## Templates |
| 127 | + |
| 128 | +The generator's templates are embedded in the Python source as |
| 129 | +multi-line f-strings. They mirror the exact layout of the existing |
| 130 | +hand-written operators (`TemporalEDWithinGeometryLogicalFunction` and |
| 131 | +its physical sibling are the reference; the templates were derived by |
| 132 | +1:1 inspection of those files). |
| 133 | + |
| 134 | +To adjust a template (e.g. when NebulaStream's `LogicalFunctionConcept` |
| 135 | +adds a new override), edit the corresponding string in |
| 136 | +`codegen_nebula.py`; the change applies to all subsequent |
| 137 | +regenerations. |
| 138 | + |
| 139 | +## Scaling path (recommended sequence) |
| 140 | + |
| 141 | +| Wave | Scope | Expected output | Effort estimate | |
| 142 | +|---|---|---|---| |
| 143 | +| W1 | First batch: 5 MEOS spatial-relation E/A predicates (e.g. `TemporalEDisjoint`, `TemporalATouches`, `TemporalECovers`, `TemporalACrosses`, `TemporalAOverlaps`) | 20 generated files + 5 parser entries | Single follow-up PR after this generator lands | |
| 144 | +| W2 | All ever / always spatial-relation predicates over `tgeo_geo` (~18 functions) | 72 generated files | ~1 follow-up PR | |
| 145 | +| W3 | Distance functions over `tgeo_geo` and `tgeo_tgeo` (NAD, NAI, distance, etc.) | ~30 generated files | ~1 follow-up PR | |
| 146 | +| W4 | Scalar accessors that decompose to per-event reads | template extension required (read MEOS handle) | design decision point | |
| 147 | +| W5 | Aggregations (windowed / cross-stream) | separate generator (aggregation 4-layer pattern is different from scalar 4-layer pattern; the existing TEMPORAL_LENGTH / PAIR_MEETING / CROSS_DISTANCE shape) | full aggregation-codegen design | |
| 148 | + |
| 149 | +Per-PR scope keeps the review surface small and lets each batch land |
| 150 | +with its own build verification. |
| 151 | + |
| 152 | +## What the generator does NOT do (deliberately) |
| 153 | + |
| 154 | +- **No build-system integration.** The CMakeLists updates are emitted |
| 155 | + as text snippets for the maintainer to apply manually. This avoids |
| 156 | + the generator silently corrupting CMakeLists on regeneration. |
| 157 | +- **No parser/grammar integration.** Same reason — the dispatch and |
| 158 | + grammar snippets are emitted to stderr for manual paste. |
| 159 | +- **No aggregation-pattern support yet.** Aggregations require a |
| 160 | + different 4-layer shape (lift/combine/lower/cleanup) that depends |
| 161 | + on per-aggregation state design. A separate generator with the |
| 162 | + aggregation-specific template is W5 in the table above. |
| 163 | + |
| 164 | +## Compile-verification note |
| 165 | + |
| 166 | +The generator's first output should be reviewed against an existing |
| 167 | +hand-written operator for shape parity, then `mvn compile` (or the |
| 168 | +NebulaStream `cmake --build` equivalent) should be run against a |
| 169 | +single small batch (1–2 generated functions) before scaling up. The |
| 170 | +generator's templates are derived 1:1 from the existing operator |
| 171 | +shape but have not been compile-tested in this PR (out of the |
| 172 | +generator author's environment). |
0 commit comments