From 8d20c0c9ad7004bd1d2fa48a8f66c4a5bf471234 Mon Sep 17 00:00:00 2001 From: Esteban Zimanyi Date: Mon, 5 Oct 2026 17:59:38 +0200 Subject: [PATCH 1/2] Keep the plain hex-WKB reader and writer as the codec of a class having both MobilityDB 2510e750d0 gives cbuffer, npoint, nsegment, pose, posechain, stbox and tpcbox an E writer beside the plain one, cbuffer_as_hexewkb beside cbuffer_as_hexwkb. One pattern of the encoding table, _as_hex_?e?wkb, matched both, so #keyed of parser/codecs.py ranked them alike, each serving one SQL type, and stopped the catalog: "type encodings that contradict themselves: Cbuffer: cbuffer written by cbuffer_as_hexewkb and cbuffer_as_hexwkb", and so for the six others. Every binding provisioning from master fails at that step, MobilitySpark main among them. The encoding tables of parser/enrich.py now state the plain hex-WKB pattern before the E one, for the writers and the readers alike, so a class having both keeps the plain pair as its codec, the one it stated before: the variant its send binds already decides whether the bytes carry the SRID. A class having only the E pair, GSERIALIZED, keeps it. Each function is classified under the same encoding as before; only the rank of the two patterns differs. Witness: over the installed headers of a libmeos built from MobilityDB master 881945dca9, run.py exits 1 with the seven contradictions above. Measured over the same headers: run.py derives the catalog, 5149 functions; the seven classes state their plain *_as_hexwkb writer and *_from_hexwkb reader, with the variant their send binds, and GSERIALIZED its HexEWKB readers and writer. tests/test_codecs.py states the rank over a class having both pairs, failing without the change. --- parser/codecs.py | 3 ++- parser/enrich.py | 8 ++++++-- tests/test_codecs.py | 26 ++++++++++++++++++++++++++ 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/parser/codecs.py b/parser/codecs.py index ef9cc4d..b25a15a 100644 --- a/parser/codecs.py +++ b/parser/codecs.py @@ -128,7 +128,8 @@ def keyed(cls, cands, sql_type, side, table): for the class only when it is the encoding's one candidate (``interval_in``). Two functions of one encoding for one SQL type rank by ``table``, whose first match names the encoding (``cbuffer_out`` before ``cbuffer_as_text`` and - ``cbuffer_as_ewkt``), then by how many SQL types each serves, the narrower first, + ``cbuffer_as_ewkt``, ``cbuffer_as_hexwkb`` before ``cbuffer_as_hexewkb``), then by + how many SQL types each serves, the narrower first, as PostgreSQL resolves an overload to its most specific candidate (``cbufferset_out`` before ``spatialset_out`` for a ``cbufferset``). Two that tie on both contradict each other.""" diff --git a/parser/enrich.py b/parser/enrich.py index a518e1c..ff031c7 100644 --- a/parser/enrich.py +++ b/parser/enrich.py @@ -65,7 +65,10 @@ (re.compile(r"_as_e?wkt$"), "text"), (re.compile(r"_as_mfjson$"), "mfjson"), (re.compile(r"_as_geojson$"), "mfjson"), - (re.compile(r"_as_hex_?e?wkb$"), "wkb"), + # The plain hex-WKB writer ranks before the E one, so a class having both keeps the + # plain one as its codec (#keyed of parser/codecs.py ranks by this order). + (re.compile(r"_as_hex_?wkb$"), "wkb"), + (re.compile(r"_as_hex_?ewkb$"), "wkb"), (re.compile(r"_as_e?wkb$"), "wkb"), ] _DECODERS = [ @@ -74,7 +77,8 @@ (re.compile(r"_from_text$"), "text"), (re.compile(r"_from_mfjson$"), "mfjson"), (re.compile(r"_from_geojson$"), "mfjson"), - (re.compile(r"_from_hex_?e?wkb$"), "wkb"), + (re.compile(r"_from_hex_?wkb$"), "wkb"), + (re.compile(r"_from_hex_?ewkb$"), "wkb"), (re.compile(r"_from_e?wkb$"), "wkb"), ] _IO_RE = [rx for rx, _ in _DECODERS + _ENCODERS] diff --git a/tests/test_codecs.py b/tests/test_codecs.py index 6344877..5b8774b 100644 --- a/tests/test_codecs.py +++ b/tests/test_codecs.py @@ -139,6 +139,32 @@ def test_a_hexewkb_reader_per_type_and_one_writer_for_both(self): self.assertEqual(g["encoderAux"]["wkb"], [{"name": "endian", "kind": "string", "default": None}]) + def test_the_plain_hex_writer_and_reader_rank_before_the_e_ones(self): + """A class having both the plain and the E hex-WKB functions keeps the plain ones, + as #test_a_hexewkb_reader_per_type_and_one_writer_for_both keeps the E ones of a + class having no other.""" + cb = "const Cbuffer *" + both = FUNCTIONS + [ + fn("cbuffer_from_hexwkb", "Cbuffer *", [("const char *", "hexwkb")], + [sig("cbufferFromHexWKB", ["text"], "cbuffer")]), + fn("cbuffer_from_hexewkb", "Cbuffer *", [("const char *", "hexwkb")], + [sig("cbufferFromHexEWKB", ["text"], "cbuffer")]), + fn("cbuffer_as_hexwkb", "char *", + [(cb, "cb"), ("uint8_t", "variant"), ("size_t *", "size_out")], + [sig("asHexWKB", ["cbuffer", "text"], "text")], + shape={"outParams": ["size_out"]}), + fn("cbuffer_as_hexewkb", "char *", + [(cb, "cb"), ("uint8_t", "variant"), ("size_t *", "size_out")], + [sig("asHexEWKB", ["cbuffer", "text"], "text")], + shape={"outParams": ["size_out"]})] + idl = _idl(both) + idl["typeEncodings"]["Cbuffer"] = {} + idl, errors = state_type_encodings(idl) + c = idl["typeEncodings"]["Cbuffer"] + self.assertEqual((c["decoders"]["wkb"], c["encoders"]["wkb"]), + ("cbuffer_from_hexwkb", "cbuffer_as_hexwkb")) + self.assertEqual(errors, []) + def test_a_cell_is_a_class_of_its_own(self): h = self.te["H3Index"] self.assertEqual((h["in"], h["out"]), ("h3index_in", "h3index_out")) From 553d61afda761aea4b49a546033f5f246054ca2b Mon Sep 17 00:00:00 2001 From: Esteban Zimanyi Date: Mon, 5 Oct 2026 17:59:38 +0200 Subject: [PATCH 2/2] Read the class-prefixed name of the static circular-buffer containment MobilityDB 881945dca9 names the six relationships between two circular buffers by their class, as every other static function of a base type is named: contains_cbuffer_cbuffer is cbufferContains, declared as CREATE FUNCTION cbufferContains(cbuffer, cbuffer) in mobilitydb/sql/cbuffer/200_cbuffer.in.sql. test_sqlfn_deployed.py reads that name over the catalog generated from master, where it read cbuffer_contains. Measured over the catalog derived from master 881945dca9: the suite passes, 484 tests, 25 skipped for want of a MobilityDB source tree or installed headers, which CI provides. --- tests/test_sqlfn_deployed.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_sqlfn_deployed.py b/tests/test_sqlfn_deployed.py index 160fcb8..1f3bc9b 100644 --- a/tests/test_sqlfn_deployed.py +++ b/tests/test_sqlfn_deployed.py @@ -87,7 +87,7 @@ def setUp(self): def test_per_type_functions_carry_their_own_name(self): for name, sqlfn in (("floatset_in", "floatset_in"), ("tfloat_in", "tfloat_in"), ("tfloatinst_make", "tfloat"), ("tfloat_values", "valueSet"), - ("contains_cbuffer_cbuffer", "cbuffer_contains")): + ("contains_cbuffer_cbuffer", "cbufferContains")): self.assertEqual(self.fns[name]["sqlfn"], sqlfn, name) def test_an_operator_function_carries_its_class_prefixed_name(self):