From d536c870529da221abd786f128391e360b115c1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20Zim=C3=A1nyi?= Date: Thu, 3 Sep 2026 14:38:29 +0200 Subject: [PATCH 1/2] Take the array the each family fills rather than making one MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `jsonb_each` and its seven siblings fill an array the CALLER allocates: MEOS writes one `Jsonb *` per member of the object into the storage the parameter points at, and states how many it wrote through the count. The catalog now says so, and the wrappers take that array — `JsonbEach(IntPtr jb, IntPtr values)` answering the keys — instead of allocating one pointer's worth of storage, handing MEOS the address of it, and reading the first pointer MEOS wrote as the address of an array. For an object of more than one member the old shape wrote past its allocation and then walked whatever the first written pointer aimed at. The object layer defers the four methods over that shape. Nothing in a class's signature says how many members the value it is called on has, so a method answering `each` cannot size the array the call needs; the flat wrapper, whose caller holds the value, is where the size is known. The layer emits 1349 methods over 115 classes with 11 deferred. Its own test allocates room for eight, reads the two keys and the two values MEOS wrote, and so states the contract the wrapper now keeps. --- MEOS.NET.Tests/CallerAllocatedArrayTests.cs | 39 ++++++++ MEOS.NET/Functions/Meos.meos_json.g.cs | 100 +++++++------------- MEOS.NET/Functions/Meos.pgtypes.g.cs | 100 +++++++------------- MEOS.NET/Types/Jsonb.g.cs | 14 --- 4 files changed, 103 insertions(+), 150 deletions(-) create mode 100644 MEOS.NET.Tests/CallerAllocatedArrayTests.cs diff --git a/MEOS.NET.Tests/CallerAllocatedArrayTests.cs b/MEOS.NET.Tests/CallerAllocatedArrayTests.cs new file mode 100644 index 0000000..f1ea7a5 --- /dev/null +++ b/MEOS.NET.Tests/CallerAllocatedArrayTests.cs @@ -0,0 +1,39 @@ +using System.Runtime.InteropServices; + +using MEOS.NET.Functions; +using MEOS.NET.Types; + +namespace MEOS.NET.Tests +{ + /// + /// The `each` family fills an array the CALLER allocates, so the wrapper + /// takes that array rather than making one: MEOS writes one pointer per + /// member of the object, and storage for fewer of them is written past. + /// + [TestClass] + public class CallerAllocatedArrayTests : MeosTest + { + [TestMethod] + public void EachFillsTheArrayTheCallerGivesIt() + { + Jsonb jb = Jsonb.In("{\"a\": 1, \"b\": \"two\"}")!; + IntPtr values = Marshal.AllocHGlobal(IntPtr.Size * 8); + try + { + IntPtr[] keys = Meos.JsonbEach(jb.Ptr, values); + + Assert.AreEqual(2, keys.Length); + Assert.AreEqual("a", Meos.TextOut(keys[0])); + Assert.AreEqual("b", Meos.TextOut(keys[1])); + Assert.AreEqual("1", + Meos.JsonbOut(Marshal.ReadIntPtr(values, 0))); + Assert.AreEqual("\"two\"", + Meos.JsonbOut(Marshal.ReadIntPtr(values, IntPtr.Size))); + } + finally + { + Marshal.FreeHGlobal(values); + } + } + } +} diff --git a/MEOS.NET/Functions/Meos.meos_json.g.cs b/MEOS.NET/Functions/Meos.meos_json.g.cs index 27542e7..81fd440 100644 --- a/MEOS.NET/Functions/Meos.meos_json.g.cs +++ b/MEOS.NET/Functions/Meos.meos_json.g.cs @@ -105,52 +105,34 @@ public static IntPtr[] JsonArrayElementsText(IntPtr js) public static int JsonArrayLength(IntPtr js) => SafeExecution(() => Native.JsonArrayLength(js)); - public static (IntPtr[], IntPtr[]) JsonEach(IntPtr js) + public static IntPtr[] JsonEach(IntPtr js, IntPtr values) { - IntPtr _out_values = Marshal.AllocHGlobal(IntPtr.Size); - IntPtr _count_count = Marshal.AllocHGlobal(sizeof(int)); + IntPtr _cnt = Marshal.AllocHGlobal(sizeof(int)); try { - IntPtr _resultPtr = SafeExecution(() => Native.JsonEach(js, _out_values, _count_count)); - int _n = Marshal.ReadInt32(_count_count); - IntPtr[] _resultArr = new IntPtr[_n]; - for (int _i = 0; _i < _n; _i++) - { _resultArr[_i] = Marshal.ReadIntPtr(_resultPtr, _i * IntPtr.Size); } - IntPtr __out_values_arr = Marshal.ReadIntPtr(_out_values); - IntPtr[] __out_values_out = new IntPtr[_n]; + IntPtr _p = SafeExecution(() => Native.JsonEach(js, values, _cnt)); + int _n = Marshal.ReadInt32(_cnt); + IntPtr[] _out = new IntPtr[_n]; for (int _i = 0; _i < _n; _i++) - { __out_values_out[_i] = Marshal.ReadIntPtr(__out_values_arr, _i * IntPtr.Size); } - return (_resultArr, __out_values_out); - } - finally - { - Marshal.FreeHGlobal(_out_values); - Marshal.FreeHGlobal(_count_count); + { _out[_i] = Marshal.ReadIntPtr(_p, _i * IntPtr.Size); } + return _out; } + finally { Marshal.FreeHGlobal(_cnt); } } - public static (IntPtr[], IntPtr[]) JsonEachText(IntPtr js) + public static IntPtr[] JsonEachText(IntPtr js, IntPtr values) { - IntPtr _out_values = Marshal.AllocHGlobal(IntPtr.Size); - IntPtr _count_count = Marshal.AllocHGlobal(sizeof(int)); + IntPtr _cnt = Marshal.AllocHGlobal(sizeof(int)); try { - IntPtr _resultPtr = SafeExecution(() => Native.JsonEachText(js, _out_values, _count_count)); - int _n = Marshal.ReadInt32(_count_count); - IntPtr[] _resultArr = new IntPtr[_n]; - for (int _i = 0; _i < _n; _i++) - { _resultArr[_i] = Marshal.ReadIntPtr(_resultPtr, _i * IntPtr.Size); } - IntPtr __out_values_arr = Marshal.ReadIntPtr(_out_values); - IntPtr[] __out_values_out = new IntPtr[_n]; + IntPtr _p = SafeExecution(() => Native.JsonEachText(js, values, _cnt)); + int _n = Marshal.ReadInt32(_cnt); + IntPtr[] _out = new IntPtr[_n]; for (int _i = 0; _i < _n; _i++) - { __out_values_out[_i] = Marshal.ReadIntPtr(__out_values_arr, _i * IntPtr.Size); } - return (_resultArr, __out_values_out); - } - finally - { - Marshal.FreeHGlobal(_out_values); - Marshal.FreeHGlobal(_count_count); + { _out[_i] = Marshal.ReadIntPtr(_p, _i * IntPtr.Size); } + return _out; } + finally { Marshal.FreeHGlobal(_cnt); } } public static IntPtr JsonExtractPath(IntPtr js, IntPtr path_elems, int path_len) @@ -228,52 +210,34 @@ public static bool JsonbContained(IntPtr jb1, IntPtr jb2) public static bool JsonbContains(IntPtr jb1, IntPtr jb2) => SafeExecution(() => Native.JsonbContains(jb1, jb2)); - public static (IntPtr[], IntPtr[]) JsonbEach(IntPtr jb) + public static IntPtr[] JsonbEach(IntPtr jb, IntPtr values) { - IntPtr _out_values = Marshal.AllocHGlobal(IntPtr.Size); - IntPtr _count_count = Marshal.AllocHGlobal(sizeof(int)); + IntPtr _cnt = Marshal.AllocHGlobal(sizeof(int)); try { - IntPtr _resultPtr = SafeExecution(() => Native.JsonbEach(jb, _out_values, _count_count)); - int _n = Marshal.ReadInt32(_count_count); - IntPtr[] _resultArr = new IntPtr[_n]; - for (int _i = 0; _i < _n; _i++) - { _resultArr[_i] = Marshal.ReadIntPtr(_resultPtr, _i * IntPtr.Size); } - IntPtr __out_values_arr = Marshal.ReadIntPtr(_out_values); - IntPtr[] __out_values_out = new IntPtr[_n]; + IntPtr _p = SafeExecution(() => Native.JsonbEach(jb, values, _cnt)); + int _n = Marshal.ReadInt32(_cnt); + IntPtr[] _out = new IntPtr[_n]; for (int _i = 0; _i < _n; _i++) - { __out_values_out[_i] = Marshal.ReadIntPtr(__out_values_arr, _i * IntPtr.Size); } - return (_resultArr, __out_values_out); - } - finally - { - Marshal.FreeHGlobal(_out_values); - Marshal.FreeHGlobal(_count_count); + { _out[_i] = Marshal.ReadIntPtr(_p, _i * IntPtr.Size); } + return _out; } + finally { Marshal.FreeHGlobal(_cnt); } } - public static (IntPtr[], IntPtr[]) JsonbEachText(IntPtr jb) + public static IntPtr[] JsonbEachText(IntPtr jb, IntPtr values) { - IntPtr _out_values = Marshal.AllocHGlobal(IntPtr.Size); - IntPtr _count_count = Marshal.AllocHGlobal(sizeof(int)); + IntPtr _cnt = Marshal.AllocHGlobal(sizeof(int)); try { - IntPtr _resultPtr = SafeExecution(() => Native.JsonbEachText(jb, _out_values, _count_count)); - int _n = Marshal.ReadInt32(_count_count); - IntPtr[] _resultArr = new IntPtr[_n]; - for (int _i = 0; _i < _n; _i++) - { _resultArr[_i] = Marshal.ReadIntPtr(_resultPtr, _i * IntPtr.Size); } - IntPtr __out_values_arr = Marshal.ReadIntPtr(_out_values); - IntPtr[] __out_values_out = new IntPtr[_n]; + IntPtr _p = SafeExecution(() => Native.JsonbEachText(jb, values, _cnt)); + int _n = Marshal.ReadInt32(_cnt); + IntPtr[] _out = new IntPtr[_n]; for (int _i = 0; _i < _n; _i++) - { __out_values_out[_i] = Marshal.ReadIntPtr(__out_values_arr, _i * IntPtr.Size); } - return (_resultArr, __out_values_out); - } - finally - { - Marshal.FreeHGlobal(_out_values); - Marshal.FreeHGlobal(_count_count); + { _out[_i] = Marshal.ReadIntPtr(_p, _i * IntPtr.Size); } + return _out; } + finally { Marshal.FreeHGlobal(_cnt); } } public static bool JsonbExists(IntPtr jb, IntPtr key) diff --git a/MEOS.NET/Functions/Meos.pgtypes.g.cs b/MEOS.NET/Functions/Meos.pgtypes.g.cs index 5dd3553..5992523 100644 --- a/MEOS.NET/Functions/Meos.pgtypes.g.cs +++ b/MEOS.NET/Functions/Meos.pgtypes.g.cs @@ -534,52 +534,34 @@ public static IntPtr[] PgJsonArrayElementsText(IntPtr js) public static int PgJsonArrayLength(IntPtr js) => SafeExecution(() => Native.PgJsonArrayLength(js)); - public static (IntPtr[], IntPtr[]) PgJsonEach(IntPtr js) + public static IntPtr[] PgJsonEach(IntPtr js, IntPtr values) { - IntPtr _out_values = Marshal.AllocHGlobal(IntPtr.Size); - IntPtr _count_count = Marshal.AllocHGlobal(sizeof(int)); + IntPtr _cnt = Marshal.AllocHGlobal(sizeof(int)); try { - IntPtr _resultPtr = SafeExecution(() => Native.PgJsonEach(js, _out_values, _count_count)); - int _n = Marshal.ReadInt32(_count_count); - IntPtr[] _resultArr = new IntPtr[_n]; - for (int _i = 0; _i < _n; _i++) - { _resultArr[_i] = Marshal.ReadIntPtr(_resultPtr, _i * IntPtr.Size); } - IntPtr __out_values_arr = Marshal.ReadIntPtr(_out_values); - IntPtr[] __out_values_out = new IntPtr[_n]; + IntPtr _p = SafeExecution(() => Native.PgJsonEach(js, values, _cnt)); + int _n = Marshal.ReadInt32(_cnt); + IntPtr[] _out = new IntPtr[_n]; for (int _i = 0; _i < _n; _i++) - { __out_values_out[_i] = Marshal.ReadIntPtr(__out_values_arr, _i * IntPtr.Size); } - return (_resultArr, __out_values_out); - } - finally - { - Marshal.FreeHGlobal(_out_values); - Marshal.FreeHGlobal(_count_count); + { _out[_i] = Marshal.ReadIntPtr(_p, _i * IntPtr.Size); } + return _out; } + finally { Marshal.FreeHGlobal(_cnt); } } - public static (IntPtr[], IntPtr[]) PgJsonEachText(IntPtr js) + public static IntPtr[] PgJsonEachText(IntPtr js, IntPtr values) { - IntPtr _out_values = Marshal.AllocHGlobal(IntPtr.Size); - IntPtr _count_count = Marshal.AllocHGlobal(sizeof(int)); + IntPtr _cnt = Marshal.AllocHGlobal(sizeof(int)); try { - IntPtr _resultPtr = SafeExecution(() => Native.PgJsonEachText(js, _out_values, _count_count)); - int _n = Marshal.ReadInt32(_count_count); - IntPtr[] _resultArr = new IntPtr[_n]; - for (int _i = 0; _i < _n; _i++) - { _resultArr[_i] = Marshal.ReadIntPtr(_resultPtr, _i * IntPtr.Size); } - IntPtr __out_values_arr = Marshal.ReadIntPtr(_out_values); - IntPtr[] __out_values_out = new IntPtr[_n]; + IntPtr _p = SafeExecution(() => Native.PgJsonEachText(js, values, _cnt)); + int _n = Marshal.ReadInt32(_cnt); + IntPtr[] _out = new IntPtr[_n]; for (int _i = 0; _i < _n; _i++) - { __out_values_out[_i] = Marshal.ReadIntPtr(__out_values_arr, _i * IntPtr.Size); } - return (_resultArr, __out_values_out); - } - finally - { - Marshal.FreeHGlobal(_out_values); - Marshal.FreeHGlobal(_count_count); + { _out[_i] = Marshal.ReadIntPtr(_p, _i * IntPtr.Size); } + return _out; } + finally { Marshal.FreeHGlobal(_cnt); } } public static IntPtr[] PgJsonObjectKeys(IntPtr js) @@ -642,52 +624,34 @@ public static bool PgJsonbContained(IntPtr jb1, IntPtr jb2) public static bool PgJsonbContains(IntPtr jb1, IntPtr jb2) => SafeExecution(() => Native.PgJsonbContains(jb1, jb2)); - public static (IntPtr[], IntPtr[]) PgJsonbEach(IntPtr jb) + public static IntPtr[] PgJsonbEach(IntPtr jb, IntPtr values) { - IntPtr _out_values = Marshal.AllocHGlobal(IntPtr.Size); - IntPtr _count_count = Marshal.AllocHGlobal(sizeof(int)); + IntPtr _cnt = Marshal.AllocHGlobal(sizeof(int)); try { - IntPtr _resultPtr = SafeExecution(() => Native.PgJsonbEach(jb, _out_values, _count_count)); - int _n = Marshal.ReadInt32(_count_count); - IntPtr[] _resultArr = new IntPtr[_n]; - for (int _i = 0; _i < _n; _i++) - { _resultArr[_i] = Marshal.ReadIntPtr(_resultPtr, _i * IntPtr.Size); } - IntPtr __out_values_arr = Marshal.ReadIntPtr(_out_values); - IntPtr[] __out_values_out = new IntPtr[_n]; + IntPtr _p = SafeExecution(() => Native.PgJsonbEach(jb, values, _cnt)); + int _n = Marshal.ReadInt32(_cnt); + IntPtr[] _out = new IntPtr[_n]; for (int _i = 0; _i < _n; _i++) - { __out_values_out[_i] = Marshal.ReadIntPtr(__out_values_arr, _i * IntPtr.Size); } - return (_resultArr, __out_values_out); - } - finally - { - Marshal.FreeHGlobal(_out_values); - Marshal.FreeHGlobal(_count_count); + { _out[_i] = Marshal.ReadIntPtr(_p, _i * IntPtr.Size); } + return _out; } + finally { Marshal.FreeHGlobal(_cnt); } } - public static (IntPtr[], IntPtr[]) PgJsonbEachText(IntPtr jb) + public static IntPtr[] PgJsonbEachText(IntPtr jb, IntPtr values) { - IntPtr _out_values = Marshal.AllocHGlobal(IntPtr.Size); - IntPtr _count_count = Marshal.AllocHGlobal(sizeof(int)); + IntPtr _cnt = Marshal.AllocHGlobal(sizeof(int)); try { - IntPtr _resultPtr = SafeExecution(() => Native.PgJsonbEachText(jb, _out_values, _count_count)); - int _n = Marshal.ReadInt32(_count_count); - IntPtr[] _resultArr = new IntPtr[_n]; - for (int _i = 0; _i < _n; _i++) - { _resultArr[_i] = Marshal.ReadIntPtr(_resultPtr, _i * IntPtr.Size); } - IntPtr __out_values_arr = Marshal.ReadIntPtr(_out_values); - IntPtr[] __out_values_out = new IntPtr[_n]; + IntPtr _p = SafeExecution(() => Native.PgJsonbEachText(jb, values, _cnt)); + int _n = Marshal.ReadInt32(_cnt); + IntPtr[] _out = new IntPtr[_n]; for (int _i = 0; _i < _n; _i++) - { __out_values_out[_i] = Marshal.ReadIntPtr(__out_values_arr, _i * IntPtr.Size); } - return (_resultArr, __out_values_out); - } - finally - { - Marshal.FreeHGlobal(_out_values); - Marshal.FreeHGlobal(_count_count); + { _out[_i] = Marshal.ReadIntPtr(_p, _i * IntPtr.Size); } + return _out; } + finally { Marshal.FreeHGlobal(_cnt); } } public static bool PgJsonbEq(IntPtr jb1, IntPtr jb2) diff --git a/MEOS.NET/Types/Jsonb.g.cs b/MEOS.NET/Types/Jsonb.g.cs index d08e44c..210a745 100644 --- a/MEOS.NET/Types/Jsonb.g.cs +++ b/MEOS.NET/Types/Jsonb.g.cs @@ -86,20 +86,6 @@ public bool Contained(Jsonb jb2) } } - public (Text?[], Jsonb?[]) Each() - { - var _answered = Meos.JsonbEach(this.Ptr); - - return (MEOSFactory.WrapTextArray(_answered.Item1), MEOSFactory.WrapJsonbArray(_answered.Item2)); - } - - public (Text?[], Text?[]) EachText() - { - var _answered = Meos.JsonbEachText(this.Ptr); - - return (MEOSFactory.WrapTextArray(_answered.Item1), MEOSFactory.WrapTextArray(_answered.Item2)); - } - public bool Exists(Text key) => Meos.JsonbExists(this.Ptr, key.Ptr); From 70eec317111a42d0fabb3195d58513f088ad8e81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20Zim=C3=A1nyi?= Date: Thu, 3 Sep 2026 14:46:04 +0200 Subject: [PATCH 2/2] Answer every value a call states through an out-parameter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MEOS states some answers through a pointer the caller supplies, and the object layer read one of them: the value of a call whose `bool` return says whether it exists. A call stating two — the nearest-neighbour walk, which answers the entry it reached and how far away it is — and a call stating one beside a return of its own — `minBoundingRadius`, which answers the circle's centre and its radius — had no reading and deferred. Every out-parameter is now read back and none appears in the C# signature: the walk answers `(long, double)?`, null where it ends, and the radius answers `(Geo?, double)`. Two pointer parameters are not answers, and the signature says which. A pointer to a MEOS value is that value handed over for the call to fill, as `rtree_search` takes the `MeosArray` it appends to; and a by-pointer count says the call answers a NUMBER of things, so an out-parameter beside one holds as many as the count states rather than one value, which is what `jsonb_each` fills. Both stay ordinary arguments, so the six index searches keep the array they are given and the two `each` methods keep deferring. The layer emits 1352 methods over 115 classes with 8 deferred, against 1349 and 11. Its own test walks an RTree of two boxes from a query at the origin and reads the nearer entry, then the farther one, then the end of the walk. --- MEOS.NET.Tests/OutParameterTests.cs | 29 +++++++++ MEOS.NET/Types/Geometry.g.cs | 15 +++++ MEOS.NET/Types/RTreeNNCursor.g.cs | 20 ++++++ MEOS.NET/Types/SPNNCursor.g.cs | 20 ++++++ tools/objectgen.py | 95 +++++++++++++++++++---------- 5 files changed, 148 insertions(+), 31 deletions(-) diff --git a/MEOS.NET.Tests/OutParameterTests.cs b/MEOS.NET.Tests/OutParameterTests.cs index c80285d..a96d16d 100644 --- a/MEOS.NET.Tests/OutParameterTests.cs +++ b/MEOS.NET.Tests/OutParameterTests.cs @@ -49,5 +49,34 @@ public void ATemporalFloatAnswersItsValueAtAMoment() Assert.IsNull( temp.ValueAtTimestamptz(new DateTime(2024, 12, 9, 0, 0, 0, DateTimeKind.Utc), true)); } + + [TestMethod] + public void ANearestNeighbourStepAnswersTheEntryAndItsDistance() + { + RTree tree = RTree.CreateStbox()!; + STBox near = STBox.In("STBOX X((1,1),(2,2))")!; + STBox far = STBox.In("STBOX X((9,9),(10,10))")!; + Assert.IsTrue(tree.Insert(near.Ptr, 1)); + Assert.IsTrue(tree.Insert(far.Ptr, 2)); + + STBox query = STBox.In("STBOX X((0,0),(0,0))")!; + RTreeNNCursor walk = RTreeNNCursor.Open(tree, query.Ptr)!; + try + { + (long Id, double Distance)? nearest = walk.Next(); + (long Id, double Distance)? next = walk.Next(); + + Assert.IsNotNull(nearest); + Assert.IsNotNull(next); + Assert.AreEqual(1L, nearest!.Value.Id); + Assert.AreEqual(2L, next!.Value.Id); + Assert.IsTrue(next.Value.Distance > nearest.Value.Distance); + Assert.IsNull(walk.Next()); + } + finally + { + walk.Close(); + } + } } } diff --git a/MEOS.NET/Types/Geometry.g.cs b/MEOS.NET/Types/Geometry.g.cs index 41b26c9..66ec8f2 100644 --- a/MEOS.NET/Types/Geometry.g.cs +++ b/MEOS.NET/Types/Geometry.g.cs @@ -89,6 +89,21 @@ public double Length() public double MaxDistance2d(Geo gs2) => Meos.GeomMaxDistance2d(this.Ptr, gs2.Ptr); + public (Geo?, double) MinBoundingRadius() + { + IntPtr _radius = Marshal.AllocHGlobal(8); + try + { + var _answered = Meos.GeomMinBoundingRadius(this.Ptr, _radius); + + return (MEOSFactory.WrapGeo(_answered), Marshal.PtrToStructure(_radius)); + } + finally + { + Marshal.FreeHGlobal(_radius); + } + } + public Geo? OrientedEnvelope() => MEOSFactory.WrapGeo(Meos.GeomOrientedEnvelope(this.Ptr)); diff --git a/MEOS.NET/Types/RTreeNNCursor.g.cs b/MEOS.NET/Types/RTreeNNCursor.g.cs index 0c638b4..06e88f1 100644 --- a/MEOS.NET/Types/RTreeNNCursor.g.cs +++ b/MEOS.NET/Types/RTreeNNCursor.g.cs @@ -17,6 +17,26 @@ internal RTreeNNCursor(IntPtr ptr) : base(ptr) { } public void Close() => Meos.RtreeNnCursorClose(this.Ptr); + public (long, double)? Next() + { + IntPtr _id_out = Marshal.AllocHGlobal(8); + IntPtr _dist_out = Marshal.AllocHGlobal(8); + try + { + if (!Meos.RtreeNnCursorNext(this.Ptr, _id_out, _dist_out)) + { + return null; + } + + return (Marshal.ReadInt64(_id_out), Marshal.PtrToStructure(_dist_out)); + } + finally + { + Marshal.FreeHGlobal(_id_out); + Marshal.FreeHGlobal(_dist_out); + } + } + public static RTreeNNCursor? Open(RTree rtree, IntPtr query) => MEOSFactory.WrapRTreeNNCursor(Meos.RtreeNnCursorOpen(rtree.Ptr, query)); diff --git a/MEOS.NET/Types/SPNNCursor.g.cs b/MEOS.NET/Types/SPNNCursor.g.cs index 6ad4c3a..48859a7 100644 --- a/MEOS.NET/Types/SPNNCursor.g.cs +++ b/MEOS.NET/Types/SPNNCursor.g.cs @@ -17,6 +17,26 @@ internal SPNNCursor(IntPtr ptr) : base(ptr) { } public void Close() => Meos.SptreeNnCursorClose(this.Ptr); + public (long, double)? Next() + { + IntPtr _id_out = Marshal.AllocHGlobal(8); + IntPtr _dist_out = Marshal.AllocHGlobal(8); + try + { + if (!Meos.SptreeNnCursorNext(this.Ptr, _id_out, _dist_out)) + { + return null; + } + + return (Marshal.ReadInt64(_id_out), Marshal.PtrToStructure(_dist_out)); + } + finally + { + Marshal.FreeHGlobal(_id_out); + Marshal.FreeHGlobal(_dist_out); + } + } + public static SPNNCursor? Open(SPTree sptree, IntPtr query) => MEOSFactory.WrapSPNNCursor(Meos.SptreeNnCursorOpen(sptree.Ptr, query)); diff --git a/tools/objectgen.py b/tools/objectgen.py index 5763f01..7bcd57d 100644 --- a/tools/objectgen.py +++ b/tools/objectgen.py @@ -275,7 +275,7 @@ class Method: def __init__(self, name: str, ret: str, params: list[tuple[str, str]], body: str, static: bool, arrays: list[tuple[str, str]] | None = None, - out_param: tuple[str, int, str] | None = None, + out_params: list[tuple[str, int, str]] | None = None, structs: list[tuple[str, str]] | None = None, scalar_arrays: list[str] | None = None, length_out: str | None = None, byte_buffer: bool = False): @@ -286,8 +286,9 @@ def __init__(self, name: str, ret: str, params: list[tuple[str, str]], self.static = static # (parameter name, element class) for each counted array the method takes. self.arrays = arrays or [] - # (parameter name, bytes, reader expression) for a value out-parameter. - self.out_param = out_param + # (parameter name, bytes, reader expression) for each value MEOS states + # through an out-parameter. + self.out_params = out_params or [] # (parameter name, struct type) for each struct argument passed by value. self.structs = structs or [] # The caller's own arrays of scalars, pinned across the call. @@ -300,7 +301,7 @@ def __init__(self, name: str, ret: str, params: list[tuple[str, str]], def needs_a_body(self) -> bool: """Whether the call needs anything allocated, pinned or read around it.""" return bool(self.structs or self.arrays or self.scalar_arrays - or self.out_param or self.length_out + or self.out_params or self.length_out or (self.ret.startswith("(") and self.ret.endswith(")"))) @@ -508,21 +509,40 @@ def method_for(self, cls: str, entry: dict) -> Method | None: out_params = [o for o in out_params if codegen.csharp_param_name(o) != length_out] - # A `bool` return with one value out-parameter is MEOS saying whether the - # value exists: the method answers the value, or nothing. - result_out = None - if clean(f["returnType"]["c"]) == "bool" and len(out_params) == 1: + # An out-parameter is a value MEOS answers, so every one of them is read + # back and none appears in the C# signature. A `bool` return alongside + # them is MEOS saying whether the values exist rather than an answer of + # its own, so the method answers them or nothing; any other return is an + # answer and stands beside them. + # An out-parameter the flat wrapper already answers — the array it fills + # and that array's count — is gone from the wrapper's own signature, and + # what the wrapper answers is not the object layer's to read again. + taken = {pname for _t, pname in params} + out_params = [o for o in out_params + if codegen.csharp_param_name(o) in taken] + # A by-pointer count says the call answers a NUMBER of things, so an + # out-parameter beside one holds as many as it states — `jsonb_each` + # fills its `Jsonb **values` with one pointer per member of the object. + # Reading such a parameter as a single value allocates room for one and + # takes what MEOS wrote first for the whole answer. + if any(p["name"] == "count" and clean(p["cType"]) == "int *" + for p in f.get("params", [])): + out_params = [] + + # And a pointer to a MEOS value is the value itself, handed over for the + # call to fill — `rtree_search(..., MeosArray *result)` takes the array + # it appends to. What MEOS WRITES THROUGH the pointer is a scalar, or a + # value whose address it states (`T **`); those are its answers, and the + # rest stay ordinary arguments. + result_outs = [] + for name in out_params: pointee = clean(next( (p["cType"] for p in f.get("params", []) - if p["name"] == out_params[0]), "")) + if p["name"] == name), "")) reader = OUT_PARAM_READERS.get(pointee) or self.wrapped_out_reader(pointee) if reader: - result_out = (codegen.csharp_param_name(out_params[0]), reader) - else: - self.deferred[cls].append( - f"{oo}: the value out-parameter {out_params[0]} is a " - f"{pointee}, which has no reader") - return None + result_outs.append((codegen.csharp_param_name(name), reader)) + answers_existence = clean(f["returnType"]["c"]) == "bool" and result_outs if length_out and clean(f["returnType"]["c"]) == "uint8_t *": ret = ("byte[]?", "$") @@ -539,16 +559,24 @@ def method_for(self, cls: str, entry: dict) -> Method | None: codegen.csharp_param_name(a["param"]) for a in input_arrays} - if result_out is not None: - ret_type = f"{result_out[1][0]}?" + if answers_existence: + # The `bool` is not an answer: what MEOS wrote is. + answered = ", ".join(r[0] for _n, r in result_outs) + ret_type = (f"{answered}?" if len(result_outs) == 1 + else f"({answered})?") ret_expr = "$" + elif result_outs: + # The return is an answer of its own and the out-parameters stand + # beside it, in the order MEOS declares them. + ret_type = ", ".join([ret_type] + [r[0] for _n, r in result_outs]) + ret_type = f"({ret_type})" sig: list[tuple[str, str]] = [] arrays: list[tuple[str, str]] = [] scalar_arrays: list[str] = [] structs: list[tuple[str, str]] = [] for cs_type, pname in params: - if result_out is not None and pname == result_out[0]: + if any(pname == n for n, _r in result_outs): args.append(scratch(pname)) continue if pname == length_out: @@ -590,12 +618,17 @@ def method_for(self, cls: str, entry: dict) -> Method | None: structs.append((pname, mapped[0])) call = f"Meos.{codegen.public_name(fname)}({', '.join(args)})" - out = None - if result_out is not None: - name, (_, size, reader) = result_out - out = (name, size, reader.format(scratch(name))) - return Method(pascal(oo), ret_type, sig, ret_expr.replace("$", call), static, - arrays, out, structs, scalar_arrays, length_out, + outs = [(name, size, reader.format(scratch(name))) + for name, (_t, size, reader) in result_outs] + body = ret_expr.replace("$", call) + if result_outs and not answers_existence: + # The call is made once and its own answer read beside the values it + # wrote, so the template carries the two halves apart. + read = ", ".join([ret_expr.replace("$", "_answered")] + + [reader for _n, _s, reader in outs]) + body = f"{call}|>({read})" + return Method(pascal(oo), ret_type, sig, body, static, + arrays, outs, structs, scalar_arrays, length_out, ret_type == "byte[]?") def inherited_names(self, cls: str) -> set[tuple]: @@ -693,8 +726,7 @@ def call_body(self, method: Method) -> list[str]: name = scratch(method.length_out) setup.append(f" IntPtr {name} = Marshal.AllocHGlobal(sizeof(long));") teardown.append(f" Marshal.FreeHGlobal({name});") - if method.out_param: - name, size, _reader = method.out_param + for name, size, _reader in method.out_params: setup.append( f" IntPtr {scratch(name)} = Marshal.AllocHGlobal({size});") teardown.append(f" Marshal.FreeHGlobal({scratch(name)});") @@ -712,17 +744,18 @@ def call_body(self, method: Method) -> list[str]: def answer(self, method: Method) -> list[str]: """What the body does with the call, once everything is in place.""" - if method.out_param: - # MEOS answers `false` where the value does not exist and leaves the - # out-parameter untouched, so the method answers null there. - _name, _size, reader = method.out_param + if method.out_params and "|>" not in method.body: + # MEOS answers `false` where the values do not exist and leaves the + # out-parameters untouched, so the method answers null there. + read = ", ".join(reader for _n, _s, reader in method.out_params) + answered = read if len(method.out_params) == 1 else f"({read})" return [ f" if (!{method.body})", " {", " return null;", " }", "", - f" return {reader};", + f" return {answered};", ] if method.byte_buffer: # A byte array does not carry its own length, so what MEOS wrote