From a4f020d5a674f7305332e924bb23005a19e6260d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20Zim=C3=A1nyi?= Date: Thu, 3 Sep 2026 03:43:26 +0200 Subject: [PATCH] Read from the catalog which arguments are arrays MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An array argument is one parameter to the caller and two to MEOS, the pointer and its length, and the layer told them apart by the length's NAME. The surface spells that length eight ways — `count`, `count1`, `count2`, `size`, `ngeoms`, `keys_len`, `path_len`, `pixels_size` — so the rule reached the two it knew and left the rest, and it had to exclude the receiver by hand because `set_value_n` looks the same as an array beside its length. `shape.inputArrays` names each array, the parameter its length comes from and its element type, so the layer reads it and both problems go: a length is found whatever it is called, and a receiver is no array because the catalog does not say it is one. 14 methods more — `Jsonb.ExistsArray`, `Jsonb.DeleteArray`, `Geo.ClusterKmeans`, `Geo.ClusterWithin` and their kin, whose lengths are `keys_len` and `ngeoms` — 1246 methods against 1232, 49 deferred against 63. Two tests take an array each: a geometry set built from an array of geometries, and a jsonb asked which of an array of keys it holds, whose length MEOS calls `keys_len`. --- MEOS.NET.Tests/ArrayArgumentTests.cs | 44 ++++++++ MEOS.NET/Types/Geo.g.cs | 70 ++++++++++++ MEOS.NET/Types/Jsonb.g.cs | 152 +++++++++++++++++++++++++++ MEOS.NET/Types/SpanSet.g.cs | 3 + MEOS.NET/Types/TJsonb.g.cs | 19 ++++ tools/objectgen.py | 47 +++------ 6 files changed, 302 insertions(+), 33 deletions(-) create mode 100644 MEOS.NET.Tests/ArrayArgumentTests.cs diff --git a/MEOS.NET.Tests/ArrayArgumentTests.cs b/MEOS.NET.Tests/ArrayArgumentTests.cs new file mode 100644 index 0000000..c7fa52f --- /dev/null +++ b/MEOS.NET.Tests/ArrayArgumentTests.cs @@ -0,0 +1,44 @@ +using MEOS.NET.Types; + +namespace MEOS.NET.Tests +{ + /// + /// An array MEOS reads is one parameter to the caller and two to MEOS, the + /// pointer and its length. The catalog names both, so a method takes the + /// array whatever MEOS calls the length. + /// + [TestClass] + public class ArrayArgumentTests : MeosTest + { + [TestMethod] + public void ASetIsBuiltFromAnArrayOfGeometries() + { + Geo[] points = + { + Geo.FromText("POINT(1 1)", 0)!, + Geo.FromText("POINT(2 2)", 0)!, + }; + + Set? set = GeomSet.Make(points); + + Assert.IsNotNull(set); + Assert.AreEqual(2, set!.NumValues()); + } + + [TestMethod] + public void AJsonbIsQueriedWithAnArrayOfKeysWhoseLengthIsNotNamedCount() + { + // `jsonb_exists_array(jb, text **keys_elems, int keys_len, bool any)` + // — the length is `keys_len`, and the catalog is what says so. + Jsonb jb = Jsonb.In("{\"a\": 1, \"b\": 2}")!; + Text[] keys = { Text.In("a")!, Text.In("z")! }; + + Assert.IsTrue(jb.ExistsArray(keys, true)); + Assert.IsFalse(jb.ExistsArray(keys, false)); + + Text[] missing = { Text.In("y")!, Text.In("z")! }; + + Assert.IsFalse(jb.ExistsArray(missing, true)); + } + } +} diff --git a/MEOS.NET/Types/Geo.g.cs b/MEOS.NET/Types/Geo.g.cs index bd7f6c4..0a1b42e 100644 --- a/MEOS.NET/Types/Geo.g.cs +++ b/MEOS.NET/Types/Geo.g.cs @@ -111,6 +111,63 @@ public ulong ToQuadbinCell(int resolution) public STBox? TstzspanToStbox(Span s) => MEOSFactory.WrapSTBox(Meos.GeoTstzspanToStbox(this.Ptr, s.Ptr)); + public static Geo?[] ClusterIntersecting(Geo[] geoms) + { + IntPtr[] _geomsValues = new IntPtr[geoms.Length]; + for (int i = 0; i < geoms.Length; i++) + { + _geomsValues[i] = geoms[i].Ptr; + } + + GCHandle _geoms = GCHandle.Alloc(_geomsValues, GCHandleType.Pinned); + try + { + return MEOSFactory.WrapGeoArray(Meos.GeoClusterIntersecting(_geoms.AddrOfPinnedObject(), (uint) geoms.Length)); + } + finally + { + _geoms.Free(); + } + } + + public static int[] ClusterKmeans(Geo[] geoms, uint k) + { + IntPtr[] _geomsValues = new IntPtr[geoms.Length]; + for (int i = 0; i < geoms.Length; i++) + { + _geomsValues[i] = geoms[i].Ptr; + } + + GCHandle _geoms = GCHandle.Alloc(_geomsValues, GCHandleType.Pinned); + try + { + return Meos.GeoClusterKmeans(_geoms.AddrOfPinnedObject(), (uint) geoms.Length, k); + } + finally + { + _geoms.Free(); + } + } + + public static Geo?[] ClusterWithin(Geo[] geoms, double tolerance) + { + IntPtr[] _geomsValues = new IntPtr[geoms.Length]; + for (int i = 0; i < geoms.Length; i++) + { + _geomsValues[i] = geoms[i].Ptr; + } + + GCHandle _geoms = GCHandle.Alloc(_geomsValues, GCHandleType.Pinned); + try + { + return MEOSFactory.WrapGeoArray(Meos.GeoClusterWithin(_geoms.AddrOfPinnedObject(), (uint) geoms.Length, tolerance)); + } + finally + { + _geoms.Free(); + } + } + public static Geo? CollectGarray(Geo[] gsarr) { IntPtr[] _gsarrValues = new IntPtr[gsarr.Length]; @@ -130,6 +187,19 @@ public ulong ToQuadbinCell(int resolution) } } + public static Geo? FromEWKB(byte[] wkb, int srid) + { + GCHandle _wkb = GCHandle.Alloc(wkb, GCHandleType.Pinned); + try + { + return MEOSFactory.WrapGeo(Meos.GeoFromEwkb(_wkb.AddrOfPinnedObject(), (ulong) wkb.Length, srid)); + } + finally + { + _wkb.Free(); + } + } + public static Geo? FromGEOJSON(string geojson) => MEOSFactory.WrapGeo(Meos.GeoFromGeojson(geojson)); diff --git a/MEOS.NET/Types/Jsonb.g.cs b/MEOS.NET/Types/Jsonb.g.cs index ca29bf1..3f390e7 100644 --- a/MEOS.NET/Types/Jsonb.g.cs +++ b/MEOS.NET/Types/Jsonb.g.cs @@ -45,18 +45,132 @@ public bool Contained(Jsonb jb2) public Jsonb? Delete(Text key) => MEOSFactory.WrapJsonb(Meos.JsonbDelete(this.Ptr, key.Ptr)); + public Jsonb? DeleteArray(Text[] keys_elems) + { + IntPtr[] _keys_elemsValues = new IntPtr[keys_elems.Length]; + for (int i = 0; i < keys_elems.Length; i++) + { + _keys_elemsValues[i] = keys_elems[i].Ptr; + } + + GCHandle _keys_elems = GCHandle.Alloc(_keys_elemsValues, GCHandleType.Pinned); + try + { + return MEOSFactory.WrapJsonb(Meos.JsonbDeleteArray(this.Ptr, _keys_elems.AddrOfPinnedObject(), keys_elems.Length)); + } + finally + { + _keys_elems.Free(); + } + } + public Jsonb? DeleteIndex(int idx) => MEOSFactory.WrapJsonb(Meos.JsonbDeleteIndex(this.Ptr, idx)); + public Jsonb? DeletePath(Text[] path_elems) + { + IntPtr[] _path_elemsValues = new IntPtr[path_elems.Length]; + for (int i = 0; i < path_elems.Length; i++) + { + _path_elemsValues[i] = path_elems[i].Ptr; + } + + GCHandle _path_elems = GCHandle.Alloc(_path_elemsValues, GCHandleType.Pinned); + try + { + return MEOSFactory.WrapJsonb(Meos.JsonbDeletePath(this.Ptr, _path_elems.AddrOfPinnedObject(), path_elems.Length)); + } + finally + { + _path_elems.Free(); + } + } + public bool Exists(Text key) => Meos.JsonbExists(this.Ptr, key.Ptr); + public bool ExistsArray(Text[] keys_elems, bool any) + { + IntPtr[] _keys_elemsValues = new IntPtr[keys_elems.Length]; + for (int i = 0; i < keys_elems.Length; i++) + { + _keys_elemsValues[i] = keys_elems[i].Ptr; + } + + GCHandle _keys_elems = GCHandle.Alloc(_keys_elemsValues, GCHandleType.Pinned); + try + { + return Meos.JsonbExistsArray(this.Ptr, _keys_elems.AddrOfPinnedObject(), keys_elems.Length, any); + } + finally + { + _keys_elems.Free(); + } + } + + public Jsonb? ExtractPath(Text[] path_elems) + { + IntPtr[] _path_elemsValues = new IntPtr[path_elems.Length]; + for (int i = 0; i < path_elems.Length; i++) + { + _path_elemsValues[i] = path_elems[i].Ptr; + } + + GCHandle _path_elems = GCHandle.Alloc(_path_elemsValues, GCHandleType.Pinned); + try + { + return MEOSFactory.WrapJsonb(Meos.JsonbExtractPath(this.Ptr, _path_elems.AddrOfPinnedObject(), path_elems.Length)); + } + finally + { + _path_elems.Free(); + } + } + + public Text? ExtractPathText(Text[] path_elems) + { + IntPtr[] _path_elemsValues = new IntPtr[path_elems.Length]; + for (int i = 0; i < path_elems.Length; i++) + { + _path_elemsValues[i] = path_elems[i].Ptr; + } + + GCHandle _path_elems = GCHandle.Alloc(_path_elemsValues, GCHandleType.Pinned); + try + { + return MEOSFactory.WrapText(Meos.JsonbExtractPathText(this.Ptr, _path_elems.AddrOfPinnedObject(), path_elems.Length)); + } + finally + { + _path_elems.Free(); + } + } + public uint Hash() => Meos.JsonbHash(this.Ptr); public ulong HashExtended(ulong seed) => Meos.JsonbHashExtended(this.Ptr, seed); + public Jsonb? Insert(Text[] path_elems, Jsonb newjb, bool after) + { + IntPtr[] _path_elemsValues = new IntPtr[path_elems.Length]; + for (int i = 0; i < path_elems.Length; i++) + { + _path_elemsValues[i] = path_elems[i].Ptr; + } + + GCHandle _path_elems = GCHandle.Alloc(_path_elemsValues, GCHandleType.Pinned); + try + { + return MEOSFactory.WrapJsonb(Meos.JsonbInsert(this.Ptr, _path_elems.AddrOfPinnedObject(), path_elems.Length, newjb.Ptr, after)); + } + finally + { + _path_elems.Free(); + } + } + public Jsonb? ObjectField(Text key) => MEOSFactory.WrapJsonb(Meos.JsonbObjectField(this.Ptr, key.Ptr)); @@ -72,6 +186,44 @@ public string Out() public Text? Pretty() => MEOSFactory.WrapText(Meos.JsonbPretty(this.Ptr)); + public Jsonb? Set(Text[] path_elems, Jsonb newjb, bool create) + { + IntPtr[] _path_elemsValues = new IntPtr[path_elems.Length]; + for (int i = 0; i < path_elems.Length; i++) + { + _path_elemsValues[i] = path_elems[i].Ptr; + } + + GCHandle _path_elems = GCHandle.Alloc(_path_elemsValues, GCHandleType.Pinned); + try + { + return MEOSFactory.WrapJsonb(Meos.JsonbSet(this.Ptr, _path_elems.AddrOfPinnedObject(), path_elems.Length, newjb.Ptr, create)); + } + finally + { + _path_elems.Free(); + } + } + + public Jsonb? SetLax(Text[] path_elems, Jsonb newjb, bool create, Text handle_null) + { + IntPtr[] _path_elemsValues = new IntPtr[path_elems.Length]; + for (int i = 0; i < path_elems.Length; i++) + { + _path_elemsValues[i] = path_elems[i].Ptr; + } + + GCHandle _path_elems = GCHandle.Alloc(_path_elemsValues, GCHandleType.Pinned); + try + { + return MEOSFactory.WrapJsonb(Meos.JsonbSetLax(this.Ptr, _path_elems.AddrOfPinnedObject(), path_elems.Length, newjb.Ptr, create, handle_null.Ptr)); + } + finally + { + _path_elems.Free(); + } + } + public Jsonb? StripNulls(bool strip_in_arrays) => MEOSFactory.WrapJsonb(Meos.JsonbStripNulls(this.Ptr, strip_in_arrays)); diff --git a/MEOS.NET/Types/SpanSet.g.cs b/MEOS.NET/Types/SpanSet.g.cs index ccb9dd5..d8589ee 100644 --- a/MEOS.NET/Types/SpanSet.g.cs +++ b/MEOS.NET/Types/SpanSet.g.cs @@ -130,5 +130,8 @@ public bool UpperInc() } } + public static SpanSet? Make(Span spans, int count) + => MEOSFactory.WrapSpanSet(Meos.SpansetMake(spans.Ptr, count)); + } } diff --git a/MEOS.NET/Types/TJsonb.g.cs b/MEOS.NET/Types/TJsonb.g.cs index ede2b4b..9d58489 100644 --- a/MEOS.NET/Types/TJsonb.g.cs +++ b/MEOS.NET/Types/TJsonb.g.cs @@ -49,6 +49,25 @@ public override string ToString() public Temporal? DeleteIndex(int idx) => MEOSFactory.WrapTemporal(Meos.TjsonbDeleteIndex(this.Ptr, idx)); + public Temporal? DeletePath(Text[] path_elems) + { + IntPtr[] _path_elemsValues = new IntPtr[path_elems.Length]; + for (int i = 0; i < path_elems.Length; i++) + { + _path_elemsValues[i] = path_elems[i].Ptr; + } + + GCHandle _path_elems = GCHandle.Alloc(_path_elemsValues, GCHandleType.Pinned); + try + { + return MEOSFactory.WrapTemporal(Meos.TjsonbDeletePath(this.Ptr, _path_elems.AddrOfPinnedObject(), path_elems.Length)); + } + finally + { + _path_elems.Free(); + } + } + public Jsonb? EndValue() => MEOSFactory.WrapJsonb(Meos.TjsonbEndValue(this.Ptr)); diff --git a/tools/objectgen.py b/tools/objectgen.py index 128a756..f5c48f3 100644 --- a/tools/objectgen.py +++ b/tools/objectgen.py @@ -394,29 +394,15 @@ def method_for(self, cls: str, entry: dict) -> Method | None: for p in f.get("params", []) } - # `T *values, int count` is MEOS's counted-array convention: the pointer is - # the first element of an array, not one value, so wrapping it as one - # object would hand the callee a single element and a length that lies. + # An array ARGUMENT is one parameter to the caller and two to MEOS, the + # pointer and its length. Which parameters those are is the catalog's to + # say — `shape.inputArrays` names each array, the parameter its length + # comes from and its element type — so the layer reads it rather than + # matching a length by its name, which the surface spells eight ways. + input_arrays = (f.get("shape") or {}).get("inputArrays") or [] + counted = {codegen.csharp_param_name(a["param"]): a["element"]["c"] + for a in input_arrays} recv_ctype = self.m.ctype.get(cls) - declared = f.get("params", []) - receiver_first = (bool(declared) - and clean(declared[0]["cType"]) == f"{recv_ctype} *") - # `T *values, int count` is MEOS's counted-array convention. The length is - # named `count`; a parameter named `n` is the INDEX of the `*_n` - # accessors, whose pointer is the receiver and no array at all. - counted = set() - for i, p in enumerate(declared): - nxt = declared[i + 1] if i + 1 < len(declared) else None - if (i == 0 and receiver_first) or not clean(p["cType"]).endswith("*"): - continue - # The length is named `count` beside an array of values and `size` - # beside a byte buffer, and both say the same thing: the pointer is - # the first element of an array the caller already holds whole. - if (nxt and ((nxt["name"] == "count" - and clean(nxt["cType"]) in ("int", "int32", "int32_t")) - or (nxt["name"] == "size" - and clean(nxt["cType"]) == "size_t"))): - counted.add(codegen.csharp_param_name(p["name"])) params = list(wrapper_params) static = True @@ -467,15 +453,10 @@ def method_for(self, cls: str, entry: dict) -> Method | None: self.deferred[cls].append(f"{oo}: neither a receiver nor a value to return") return None - # A counted array is one parameter to the caller and two to MEOS: the - # array and its length, which the array itself answers. - declared = f.get("params", []) - count_of = {} - for i, p in enumerate(declared): - if (codegen.csharp_param_name(p["name"]) in counted - and i + 1 < len(declared)): - count_of[codegen.csharp_param_name(declared[i + 1]["name"])] = \ - codegen.csharp_param_name(p["name"]) + # The length is the array's own, so it leaves the C# signature. + count_of = {codegen.csharp_param_name(a["lengthFrom"]["name"]): + codegen.csharp_param_name(a["param"]) + for a in input_arrays} if result_out is not None: ret_type = f"{result_out[1][0]}?" @@ -497,8 +478,8 @@ def method_for(self, cls: str, entry: dict) -> Method | None: args.append(f"{cast}{count_of[pname]}.Length") continue if pname in counted: - pointee = clean(c_by_name[pname])[:-1].strip() - element = self.m.class_for_ctype(clean(c_by_name[pname])[:-1]) + pointee = clean(counted[pname]) + element = self.m.class_for_ctype(pointee) if element is not None: sig.append((f"{element}[]", pname)) arrays.append((pname, element))