From 0151a6b4b69e629070e25b084929c26460793bcd Mon Sep 17 00:00:00 2001 From: Esteban Zimanyi Date: Mon, 14 Sep 2026 00:57:30 +0200 Subject: [PATCH] Resolve the S2 cell id to uint64_t like the other cell ids S2CellId is MobilityDB's `typedef uint64 S2CellId`, a 64-bit cell id exactly like H3Index and Quadbin. The type recovery and the header-type pass list H3Index and Quadbin as uint64 cell ids and leave S2CellId out, so the catalog spells its slots in two ways neither sibling uses: a scalar carries the platform canonical "unsigned long" (s2cell_in, ts2cell_start_value) and an array keeps the opaque "S2CellId *" (s2cellset_values, ts2cell_values). The shape pass reads the element of a written-back array against the by-value scalars, so the `S2CellId **values` out-parameter of ts2cell_unnest is not a parallel output array, and a binding hands the callee one element's storage for the array it allocates. S2CellId joins H3Index and Quadbin in both tables, and the 67 s2cell functions read as the h3 and quadbin ones do: cType and canonical uint64_t. Witness: the catalog derived from MobilityDB with the typed unnest functions carries ts2cell_unnest with outputArrays [values], as th3index_unnest and tquadbin_unnest do; without the entry its shape carries no outputArrays. test_cell_id_canonical_normalized_uniform covers ts2cell_start_value, ts2cell_end_value, s2cell_in and the values arrays of the three cell types. Why: the network surface reads the s2cell functions as it reads the h3 and quadbin ones. A uint64_t slot has no JSON decoder, so the exposable count moves from 2506 to 2467 (s2cell 52 -> 13 exposable, the quadbin count). The JSON integer that exposed them cannot carry an S2 cell id, whose face bits sit above 2^61, beyond the 2^53 a JSON number holds exactly. --- parser/header_types.py | 8 ++++---- parser/typerecover.py | 1 + tests/test_typerecover.py | 18 +++++++++++++----- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/parser/header_types.py b/parser/header_types.py index 543f7e1..a0c5911 100644 --- a/parser/header_types.py +++ b/parser/header_types.py @@ -36,10 +36,10 @@ "uint64", "int8_t", "int16_t", "int32_t", "int64_t", "uint8_t", "uint16_t", "uint32_t", "uint64_t", "TimestampTz", "TimeADT", "DateADT", "Timestamp", "Datum", "meosType", "interpType", - # H3Index / Quadbin are uint64 cell ids: typerecover resolves them to - # uint64_t, so treat them as scalars here and leave that recovery intact - # rather than restoring the opaque spelling from the header source. - "H3Index", "Quadbin", + # H3Index / Quadbin / S2CellId are uint64 cell ids: typerecover resolves + # them to uint64_t, so treat them as scalars here and leave that recovery + # intact rather than restoring the opaque spelling from the header source. + "H3Index", "Quadbin", "S2CellId", } diff --git a/parser/typerecover.py b/parser/typerecover.py index c22dab8..b374693 100644 --- a/parser/typerecover.py +++ b/parser/typerecover.py @@ -44,6 +44,7 @@ "TimestampTz": "TimestampTz", "H3Index": "uint64_t", "Quadbin": "uint64_t", + "S2CellId": "uint64_t", "text": "text", "GSERIALIZED": "GSERIALIZED", "Interval": "Interval", diff --git a/tests/test_typerecover.py b/tests/test_typerecover.py index 6c2c3e9..77c75c9 100644 --- a/tests/test_typerecover.py +++ b/tests/test_typerecover.py @@ -168,15 +168,23 @@ def test_uint32_canonical_normalized(self): def test_cell_id_canonical_normalized_uniform(self): # H3Index (libh3's typedef, whose fully-resolved canonical is the platform - # "unsigned long") and Quadbin (MobilityDB's typedef, recovered to "uint64_t") - # are BOTH uint64 cell ids; as Tcell subtypes they must be spelled identically. - # The ``canonical`` field must normalize to "uint64_t" for both, not leave H3Index - # at "unsigned long" — a guard on the _CANON_ALIAS canonical-normalization pass. + # "unsigned long"), Quadbin and S2CellId (MobilityDB's typedefs, recovered to + # "uint64_t") are ALL uint64 cell ids; as Tcell subtypes they must be spelled + # identically. The ``canonical`` field must normalize to "uint64_t" for each, not + # leave one at "unsigned long" — a guard on the _CANON_ALIAS canonical-normalization + # pass. for name in ("th3index_start_value", "th3index_end_value", - "tquadbin_start_value", "tquadbin_end_value", "h3index_in"): + "tquadbin_start_value", "tquadbin_end_value", "h3index_in", + "ts2cell_start_value", "ts2cell_end_value", "s2cell_in"): rt = self.by_name[name]["returnType"] self.assertEqual(rt["c"], "uint64_t", f"{name} c") self.assertEqual(rt["canonical"], "uint64_t", f"{name} canonical") + # An array of cell ids is an array of by-value uint64_t, whichever cell + # typedef the header spells it with. + for name in ("th3index_values", "tquadbin_values", "ts2cell_values"): + rt = self.by_name[name]["returnType"] + self.assertEqual(rt["c"], "uint64_t *", f"{name} c") + self.assertEqual(rt["canonical"], "uint64_t *", f"{name} canonical") def test_typedef_canonical_not_platform_resolved(self): # ``canonical`` is the MEOS typedef its ``cType`` names, never libclang's