Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -228,16 +228,18 @@ Manual annotations (ownership rules, additional documentation, deprecation flags
bare-name mapping that MobilityDB now registers natively (PR #1075). The
pipeline folds it into the catalog as `portableAliases` (with `byOperator`
/ `byBareName` lookups), so **every binding/engine generates the identical
bare names** and a user learns one reference and assumes the rest.
bare names** and a user learns one reference and assumes the rest. A
position operator has one name per class instead (`setLeft` … `stboxLeft`),
which the catalog derives from the `@sqlfn` tags as `positionNames`.

It is curated canonical data, kept verbatim (only bijective lookups are
derived — no C-symbol guessing; upstream aliases reuse each operator's own
backing function, equivalence by construction). The mapping is
type-agnostic and applies to **every** temporal type family —
`temporal`, `geo`, `cbuffer`, `npoint`, `pose`, `rgeo` are all in scope and
must not be excluded from any parity headline. `python tools/portable_parity.py`
audits it against the catalog — currently **29/29 = 100%** backed (verified,
no guessing). See [`docs/portable-aliases.md`](docs/portable-aliases.md).
audits it against the catalog, flagging every operator no catalog family backs
(no guessing). See [`docs/portable-aliases.md`](docs/portable-aliases.md).

## OpenAPI generation

Expand Down
6 changes: 4 additions & 2 deletions docs/cross-repo-handoff.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ python tools/portable_parity.py # -> output/meos-portable-parity.json (bare-nam

| Artifact | Contents |
|---|---|
| `meos-idl.json#/portableAliases` | canonical operator→bare-name dialect: `byOperator`, `byBareName`, `families`, `explicitBacking`, `scope` |
| `meos-idl.json#/portableAliases` | canonical operator→bare-name dialect: `byOperator`, `byBareName`, `families`, `explicitBacking`, `scope`; the position operators' names by class: `positionFamilies`, `byPositionOperator`, `positionNames` (operator → class → SQL name) |
| `meos-idl.json#/functions[].{network,wire,api}` | per-function projectability + decode/encode/array/out-param wire model |
| `meos-idl.json#/temporalTypes` | per `Temporal<T>`: its `base`, its `bbox`, the `mfjson` type token `asMFJSON` writes, and the `number` / `spatial` / `linear` classes |
| `meos-idl.json#/typeRelations/byBase` | each base type's `set`, `span`, `spanset` and its `temporal` types, the last a list since a base carries several |
Expand All @@ -31,7 +31,9 @@ python tools/portable_parity.py # -> output/meos-portable-parity.json (bare-nam
`portableAliases.byOperator` (drop type-qualified forms like
`spanOverlaps`). Done = every operator in `byOperator` is callable by its
bare name, parity-checked with the same prefix logic as
`portable_parity.py`, **0 unbacked**.
`portable_parity.py`, **0 unbacked**. A position operator registers its
names by class from `portableAliases.positionNames` (`stboxLeft`,
`tboxBefore`), which are the `sqlfn` of its functions.

**PyMEOS, JMEOS, MEOS.NET** — code-generate from `meos-idl.json`
(`functions` + `portableAliases`) so every binding emits **identical** bare
Expand Down
62 changes: 44 additions & 18 deletions docs/portable-aliases.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,29 +17,56 @@ family, and is **type-agnostic** (it applies to every temporal type):
| Family | Operator → bare name |
|---|---|
| Topology | `&&`→`overlaps` `@>`→`contains` `<@`→`contained` `-\|-`→`adjacent` |
| Temporal comparison | `#=`→`tEqual` `#<>`→`tNotEqual` `#<`→`tLessThan` `#<=`→`tLessEqual` `#>`→`tGreaterThan` `#>=`→`tGreaterEqual` |
| Ever comparison | `?=`→`eEqual` `?<>`→`eNotEqual` `?<`→`eLessThan` `?<=`→`eLessEqual` `?>`→`eGreaterThan` `?>=`→`eGreaterEqual` |
| Always comparison | `%=`→`aEqual` `%<>`→`aNotEqual` `%<`→`aLessThan` `%<=`→`aLessEqual` `%>`→`aGreaterThan` `%>=`→`aGreaterEqual` |
| Distance | `<->`→`tDistance` `\|=\|`→`nearestApproachDistance` |
| Same | `~=`→`same` |

25 operator→bare-name pairs. Already-canonical (no aliasing needed):
`eIntersects`, `atTime`, restriction and spatial-relationship functions.

## Position operators

A position operator has one name per class of its operands instead: the
class followed by the position, a temporal operand taking the class of its
bounding box (MobilityDB#2717). `<<` is `setLeft`, `spanLeft`,
`spansetLeft`, `tboxLeft`, `stboxLeft` and `tpcboxLeft`; the Y and Z
positions exist for `stbox` and `tpcbox`. The mapping holds each operator
with its position, the stem those names and the MEOS C functions share
(`left_set_set`, `left_tspatial_tspatial`), under `positionFamilies`:

| Family | Operator → position |
|---|---|
| Time position | `<<#`→`before` `#>>`→`after` `&<#`→`overbefore` `#&>`→`overafter` |
| Space X | `<<`→`left` `>>`→`right` `&<`→`overleft` `&>`→`overright` |
| Space Y | `<<\|`→`below` `\|>>`→`above` `&<\|`→`overbelow` `\|&>`→`overabove` |
| Space Z | `<</`→`front` `/>>`→`back` `&</`→`overfront` `/&>`→`overback` |
| Temporal comparison | `#=`→`teq` `#<>`→`tne` `#<`→`tlt` `#<=`→`tle` `#>`→`tgt` `#>=`→`tge` |
| Distance | `<->`→`tdistance` `\|=\|`→`nearestApproachDistance` |
| Same | `~=`→`same` |

29 operator→bare-name pairs. Already-canonical (no aliasing needed):
`ever_*`/`always_*` (`?=`/`%=`), `eIntersects`, `atTime`, restriction and
spatial-relationship functions.
The catalog derives the names by class from the `@sqlfn` tags of the
functions whose `@sqlop` is the operator (`positionNames`, 64 names for the
16 operators). A name that is not the class followed by the position, or an
operator that no function carries, stops the pipeline: either means the
tags and the mapping disagree.

## In the catalog

`portableAliases` carries the verbatim `families`, plus derived bijective
lookups for codegen:
`portableAliases` carries the verbatim `families` and `positionFamilies`,
plus derived lookups for codegen:

```json
"portableAliases": {
"byOperator": { "&&": "overlaps", "#=": "tEqual", "~=": "same", ... },
"byBareName": { "overlaps": "&&", "tEqual": "#=", "same": "~=", ... },
"bareNames": ["aEqual", "aGreaterEqual", ..., "tLessEqual", "tLessThan", "tNotEqual"],
"count": 41, "provenance": {...}, "scope": {...}, "notes": [...]
"count": 25,
"byPositionOperator": { "<<": "left", "<<#": "before", ... },
"positionNames": {
"<<": { "set": "setLeft", "span": "spanLeft", "spanset": "spansetLeft",
"stbox": "stboxLeft", "tbox": "tboxLeft", "tpcbox": "tpcboxLeft" },
"<<|": { "stbox": "stboxBelow", "tpcbox": "tpcboxBelow" }, ...
},
"provenance": {...}, "scope": {...}, "notes": [...]
}
```

Expand Down Expand Up @@ -67,15 +94,14 @@ end state.
`portable_parity.py` is the meos-api.json analogue of MobilityDB's
`tools/portable_aliases/generate.py --check`: it cross-references every
bare name against the catalog's function families (by the MEOS bare-name
prefix convention) and writes `output/meos-portable-parity.json`.

Live result: **29 / 29 = 100%** — every operator's bare name is backed in
the catalog (28 directly by prefix; `nearestApproachDistance` via the
*verified* `explicitBacking` entry `nad` — the `nad_*` family, 35
functions, confirmed present, not guessed). A bare name whose C family
prefix differs is resolved through `explicitBacking`, never false-flagged
as a gap and never silently dropped; `tests/test_portable_parity.py`
gates this (no bare name may be unclassified or regressed).
prefix convention), and every position operator against the family its
position prefixes (`left_*`, `before_*`) with its SQL names by class, and
writes `output/meos-portable-parity.json`. A bare name whose C family
prefix differs is resolved through `explicitBacking` (`nearestApproachDistance`
through `nad`, the `nad_*` family), and one that no entry resolves is
flagged `needs-explicit-backing` — never silently dropped;
`tests/test_portable_parity.py` gates this (no operator may be
unclassified).

## Provenance

Expand Down
103 changes: 29 additions & 74 deletions meta/portable-aliases.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"_comment": "Canonical portable bare-name dialect \u2014 the single codegen source of truth (RFC #920). Every binding/engine generates the SAME bare names from this mapping so users learn one reference and assume the rest. Operators are SQL operator symbols; bareName is the portable function name. The mapping is type-agnostic: it applies to EVERY temporal type family.",
"_comment": "Canonical portable bare-name dialect \u2014 the single codegen source of truth (RFC #920). Every binding/engine generates the SAME bare names from this mapping so users learn one reference and assume the rest. Operators are SQL operator symbols; bareName is the portable function name. The mapping is type-agnostic: it applies to EVERY temporal type family. A position operator (`positionFamilies`) has instead one name per class of its operands.",
"provenance": {
"discussion": "MobilityDB#861",
"rfc": "MobilityDB RFC #920 (doc/rfc/sql-portability/README.md, branch rfc/sql-portability)",
Expand All @@ -25,78 +25,6 @@
"bareName": "adjacent"
}
],
"timePosition": [
{
"operator": "<<#",
"bareName": "before"
},
{
"operator": "#>>",
"bareName": "after"
},
{
"operator": "&<#",
"bareName": "overbefore"
},
{
"operator": "#&>",
"bareName": "overafter"
}
],
"spaceX": [
{
"operator": "<<",
"bareName": "left"
},
{
"operator": ">>",
"bareName": "right"
},
{
"operator": "&<",
"bareName": "overleft"
},
{
"operator": "&>",
"bareName": "overright"
}
],
"spaceY": [
{
"operator": "<<|",
"bareName": "below"
},
{
"operator": "|>>",
"bareName": "above"
},
{
"operator": "&<|",
"bareName": "overbelow"
},
{
"operator": "|&>",
"bareName": "overabove"
}
],
"spaceZ": [
{
"operator": "<</",
"bareName": "front"
},
{
"operator": "/>>",
"bareName": "back"
},
{
"operator": "&</",
"bareName": "overfront"
},
{
"operator": "/&>",
"bareName": "overback"
}
],
"temporalComparison": [
{ "operator": "#=", "bareName": "tEqual" },
{ "operator": "#<>", "bareName": "tNotEqual" },
Expand Down Expand Up @@ -138,6 +66,33 @@
}
]
},
"_positionFamiliesComment": "A position operator has one SQL name per class of its operands, the class followed by the position: `<<` is setLeft, spanLeft, spansetLeft, tboxLeft, stboxLeft and tpcboxLeft (MobilityDB#2717). A temporal operand takes the class of its bounding box. `position` is the stem those names and the MEOS C functions share (left_set_set, left_tspatial_tspatial). The catalog derives the names by class from the @sqlfn tags of the functions whose @sqlop is the operator (`portableAliases.positionNames`).",
"positionFamilies": {
"timePosition": [
{ "operator": "<<#", "position": "before" },
{ "operator": "#>>", "position": "after" },
{ "operator": "&<#", "position": "overbefore" },
{ "operator": "#&>", "position": "overafter" }
],
"spaceX": [
{ "operator": "<<", "position": "left" },
{ "operator": ">>", "position": "right" },
{ "operator": "&<", "position": "overleft" },
{ "operator": "&>", "position": "overright" }
],
"spaceY": [
{ "operator": "<<|", "position": "below" },
{ "operator": "|>>", "position": "above" },
{ "operator": "&<|", "position": "overbelow" },
{ "operator": "|&>", "position": "overabove" }
],
"spaceZ": [
{ "operator": "<</", "position": "front" },
{ "operator": "/>>", "position": "back" },
{ "operator": "&</", "position": "overfront" },
{ "operator": "/&>", "position": "overback" }
]
},
"alreadyCanonical": [
{
"kind": "functions",
Expand Down Expand Up @@ -170,6 +125,6 @@
"notes": [
"Generate aliases by reusing each operator's own backing C function (equivalence by construction), never by reimplementing; mirror MobilityDB tools/portable_aliases/generate.py + its 100%-coverage audit.",
"User-facing API uses the full name `trgeometry`; internal functions keep the `trgeo_` prefix \u2014 do NOT normalize the internal prefix.",
"Goal: 100% parity ecosystem-wide \u2014 every operator has its bare name on every engine, no gaps, no headline exclusions."
"Goal: 100% parity ecosystem-wide \u2014 every operator has its portable name (its bare name, or its name by class for a position operator) on every engine, no gaps, no headline exclusions."
]
}
22 changes: 21 additions & 1 deletion meta/portable-aliases.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
"description": "Schema for `meta/portable-aliases.json` (RFC #920). Catches shape regressions earlier than the unit tests; validated as a test step in `tests/test_portable.py`.",
"type": "object",
"additionalProperties": true,
"required": ["provenance", "families", "alreadyCanonical", "explicitBacking", "scope", "notes"],
"required": ["provenance", "families", "positionFamilies", "alreadyCanonical", "explicitBacking", "scope", "notes"],
"properties": {
"_comment": {"type": "string"},
"_positionFamiliesComment": {"type": "string"},
"_explicitBackingComment": {"type": "string"},

"provenance": {
Expand Down Expand Up @@ -40,6 +41,25 @@
}
},

"positionFamilies": {
"type": "object",
"description": "Position operators, whose SQL names are the class of their operands followed by the position (setLeft … stboxLeft). `position` is the stem the names and the MEOS C functions share; the catalog derives the names by class.",
"minProperties": 1,
"additionalProperties": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"additionalProperties": false,
"required": ["operator", "position"],
"properties": {
"operator": {"type": "string", "minLength": 1, "maxLength": 8},
"position": {"type": "string", "pattern": "^[a-z]+$"}
}
}
}
},

"alreadyCanonical": {
"type": "array",
"description": "Entries already aligned with the canonical naming — no new alias needed. Discriminated by `kind` so consumers don't have to guess the shape.",
Expand Down
59 changes: 58 additions & 1 deletion parser/portable.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
`meta/portable-aliases.json` is the curated, authoritative operator →
bare-name mapping (RFC #920; native in MobilityDB via PR #1075). Folding it
into the catalog means every binding/engine generates the *identical* bare
names, so a user learns one reference and assumes the rest.
names, so a user learns one reference and assumes the rest. A position
operator has one name per class instead (setLeft … stboxLeft), derived from
the catalog's @sqlfn/@sqlop tags by ``attach_position_names``.

This is curated canonical data, not a heuristic — it is preserved verbatim
and only *derived* lookups are added (no guessing of C symbols: upstream
Expand All @@ -30,9 +32,20 @@ def attach_portable_aliases(idl: dict, path: Path) -> dict:
if len(by_operator) != len(pairs) or len(by_bare_name) != len(pairs):
raise ValueError("portable-aliases: duplicate operator or bareName")

# A position operator has one SQL name per class of its operands (setLeft …
# stboxLeft), so it has no bare name: it is absent from the families above, and
# its operator and position are each unique.
positions = [p for fam in data["positionFamilies"].values() for p in fam]
by_position_operator = {p["operator"]: p["position"] for p in positions}
if (len(by_position_operator) != len(positions)
or len(set(by_position_operator.values())) != len(positions)
or set(by_position_operator) & set(by_operator)):
raise ValueError("portable-aliases: duplicate position operator or position")

idl["portableAliases"] = {
"provenance": data["provenance"],
"families": data["families"],
"positionFamilies": data["positionFamilies"],
"alreadyCanonical": data["alreadyCanonical"],
"explicitBacking": data.get("explicitBacking", {}),
"scope": data["scope"], # cbuffer/npoint/pose/rgeo in scope
Expand All @@ -41,10 +54,54 @@ def attach_portable_aliases(idl: dict, path: Path) -> dict:
"byBareName": by_bare_name, # "overlaps" -> "&&"
"bareNames": sorted(by_bare_name),
"count": len(pairs),
"byPositionOperator": by_position_operator, # "<<" -> "left"
}
return idl


def attach_position_names(idl: dict) -> dict:
"""Derive the SQL names of each position operator, by class.

A position operator has one SQL name per class of its operands, the class
followed by the position: ``<<`` is setLeft, spanLeft, spansetLeft, tboxLeft,
stboxLeft and tpcboxLeft (MobilityDB#2717), a temporal operand taking the class of
its bounding box. The names are the ``@sqlfn`` tags of the functions whose
``@sqlop`` is the operator, and the class is the name less its position. Adds
``portableAliases.positionNames``: operator -> class -> SQL name.

A name that does not end with its operator's position, or an operator that no
function carries, raises: either means the @sqlfn/@sqlop tags and the mapping
disagree, which a binding would otherwise inherit silently.

MUST run AFTER ``attach_sqlfn_map`` (sqlfn/sqlop) AND ``attach_portable_aliases``
(byPositionOperator).
"""
pa = idl.get("portableAliases")
if not pa:
return idl
by_position_operator = pa["byPositionOperator"]
names = {op: {} for op in by_position_operator}
for f in idl.get("functions", []):
op = (f.get("sqlop") or "").replace("\\", "")
sqlfn = f.get("sqlfn") or ""
if op not in by_position_operator or not sqlfn:
continue
pos = by_position_operator[op]
suffix = pos[0].upper() + pos[1:]
cls = sqlfn[:-len(suffix)] if sqlfn.endswith(suffix) else ""
if not cls:
raise ValueError(f"portable-aliases: {f['name']} has @sqlop {op} and "
f"@sqlfn {sqlfn}, which is not <class>{suffix}")
names[op][cls] = sqlfn
missing = sorted(op for op, by_class in names.items() if not by_class)
if missing:
raise ValueError("portable-aliases: no function has @sqlop "
+ " ".join(missing))
pa["positionNames"] = {op: dict(sorted(by_class.items()))
for op, by_class in names.items()}
return idl


def classify_backing_sqlfn(idl: dict) -> dict:
"""Mark the bounding-box topological BACKING ``@sqlfn`` tags.

Expand Down
Loading
Loading