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))