Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 8 additions & 44 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,8 @@ on:
push:
pull_request:

# Both the libmeos build and the MEOS-API header parse are pinned to the
# same MobilityDB ref so the regenerated bindings and the runtime library
# always match. Current MEOS lives on master (there is no stable-1.4 yet);
# bump MOBILITYDB_REF once a stable release branch exists.
env:
MOBILITYDB_REF: master
# The bindings need two not-yet-merged MEOS-API changes: the shape
# metadata (PR #2, branch feat/shape-metadata) and the postgres
# integer-typedef stub (PR #1, branch fix/stdbool-stub) without which
# int64/TimestampTz collapse to 32-bit int. Stack them: check out
# feat/shape-metadata and cherry-pick PR #1's fix. Once both land on
# MEOS-API master, drop MEOS_API_PR1_BRANCH and set MEOS_API_REF=master.
MEOS_API_REF: feat/shape-metadata
MEOS_API_PR1_BRANCH: fix/stdbool-stub
MEOS_API_PR1_COMMIT: fd83b2825af641f8c54f422dd7f66b148b287b68

jobs:
build:
Expand All @@ -28,42 +15,19 @@ jobs:
steps:
- uses: actions/checkout@v4

- name: Install native dependencies
run: |
sudo apt-get update
sudo apt-get install -y \
build-essential cmake git python3-pip \
libgeos-dev libproj-dev libjson-c-dev libgsl-dev

- uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'

- name: Build and install libmeos (${{ env.MOBILITYDB_REF }})
run: |
git clone --depth 1 --branch "$MOBILITYDB_REF" \
https://github.com/MobilityDB/MobilityDB "$HOME/MobilityDB"
cmake -S "$HOME/MobilityDB" -B "$HOME/MobilityDB/build" \
-DCMAKE_BUILD_TYPE=Release -DMEOS=on
cmake --build "$HOME/MobilityDB/build" -j "$(nproc)"
sudo cmake --install "$HOME/MobilityDB/build"
sudo ldconfig

- name: Generate meos-idl.json (${{ env.MOBILITYDB_REF }})
run: |
git clone --branch "$MEOS_API_REF" \
https://github.com/MobilityDB/MEOS-API "$HOME/MEOS-API"
cd "$HOME/MEOS-API"
git config user.email ci@local
git config user.name CI
git fetch origin "$MEOS_API_PR1_BRANCH"
git cherry-pick "$MEOS_API_PR1_COMMIT"
pip install -r requirements.txt
python setup.py --branch "$MOBILITYDB_REF"
python run.py
- name: Provision MEOS (build libmeos + generate catalog)
id: meos
uses: MobilityDB/MEOS-API/.github/actions/provision-meos@master
with:
mobilitydb-ref: ${{ env.MOBILITYDB_REF }}
build-libmeos: "true"

- name: Regenerate bindings
run: python3 tools/codegen.py "$HOME/MEOS-API/output/meos-idl.json"
run: python3 tools/codegen.py "${{ steps.meos.outputs.catalog-path }}"

- name: Report binding drift
run: git --no-pager diff --stat -- MEOS.NET/Internal || true
Expand All @@ -77,4 +41,4 @@ jobs:
- name: Smoke-test the FFI
run: dotnet run --project ExampleApp/ExampleApp.csproj -c Release --no-build
env:
LD_LIBRARY_PATH: /usr/local/lib
LD_LIBRARY_PATH: ${{ steps.meos.outputs.libmeos-prefix }}/lib
4 changes: 2 additions & 2 deletions MEOS.NET/Helpers/DateHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ internal static class DateHelper
{
internal static DateTime ToDateTime(this TimestampTz pgTimestamp)
{
var str = MEOSExposedFunctions.pg_timestamptz_out(pgTimestamp.Time);
var str = MEOSExposedFunctions.timestamptz_out(pgTimestamp.Time);
return DateTime.Parse(str);
}

internal static long ToPgTimestamp(this DateTime dateTime)
{
var res = MEOSExposedFunctions.pg_timestamptz_in(dateTime.ToString("s"), -1); // ToString("s") -> ISO 8601 formatted date string
var res = MEOSExposedFunctions.timestamptz_in(dateTime.ToString("s"), -1); // ToString("s") -> ISO 8601 formatted date string
return res;
}
}
Expand Down
13,918 changes: 11,126 additions & 2,792 deletions MEOS.NET/Internal/MEOSExposedFunctions.cs

Large diffs are not rendered by default.

9,544 changes: 8,204 additions & 1,340 deletions MEOS.NET/Internal/MEOSExternalFunctions.cs

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions MEOS.NET/Types/Collections/Float/FloatSet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ public sealed class FloatSet : Set
internal FloatSet(IntPtr ptr) : base(ptr)
{ }

internal static FloatSet FromValuesPointer(IntPtr valuesArray, int arrayLength)
internal static FloatSet FromValues(double[] values)
{
var res = MEOSExposedFunctions.floatset_make(valuesArray, arrayLength);
var res = AllocHelper.AllocateArrayPointer<double, IntPtr>(values,
(valuesPtr) => MEOSExposedFunctions.floatset_make(valuesPtr, values.Length));
return new FloatSet(res);
}

Expand Down
7 changes: 2 additions & 5 deletions MEOS.NET/Types/Collections/SpanSet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace MEOS.NET.Types.Collections
{
public class SpanSet : MEOSObject

Check warning on line 7 in MEOS.NET/Types/Collections/SpanSet.cs

View workflow job for this annotation

GitHub Actions / Regenerate, build and smoke-test against MEOS

'SpanSet' defines operator == or operator != but does not override Object.Equals(object o)

Check warning on line 7 in MEOS.NET/Types/Collections/SpanSet.cs

View workflow job for this annotation

GitHub Actions / Regenerate, build and smoke-test against MEOS

'SpanSet' defines operator == or operator != but does not override Object.Equals(object o)
{
internal SpanSet(IntPtr ptr) : base(ptr)
{ }
Expand Down Expand Up @@ -89,11 +89,8 @@

public virtual IEnumerable<Span> GetSpans()
{
var nbSpans = this.SpanCount();
var arr = MEOSExposedFunctions.spanset_spans(this._ptr);

var spans = arr.ToArrayOfType<IntPtr>(nbSpans);
List<Span> spanList = new List<Span>(nbSpans);
var spans = MEOSExposedFunctions.spanset_spans(this._ptr);
List<Span> spanList = new List<Span>(spans.Length);

foreach(var span in spans)
{
Expand Down
14 changes: 3 additions & 11 deletions MEOS.NET/Types/Temporal/Number/Float/TemporalFloat.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,16 +73,8 @@ public double MaxValue()

public FloatSet ToFloatSet()
{
int count = 0;
var arrayPtr = AllocHelper.AllocatePointer<IntPtr>(sizeof(int), (countPtr) =>
{
var arr = MEOSExposedFunctions.tfloat_values(this._ptr, countPtr);
count = countPtr.ToStructure<int>();

return arr;
});

return FloatSet.FromValuesPointer(arrayPtr, count);
var values = MEOSExposedFunctions.tfloat_values(this._ptr);
return FloatSet.FromValues(values);
}

public bool IsAlwaysLessThan(double value)
Expand Down Expand Up @@ -247,7 +239,7 @@ public TemporalFloat Multiply(int value)

public TemporalFloat Multiply(double value)
{
var res = MEOSExposedFunctions.mult_tfloat_float(this._ptr, value);
var res = MEOSExposedFunctions.mul_tfloat_float(this._ptr, value);
return new TemporalFloat(res);
}

Expand Down
2 changes: 1 addition & 1 deletion MEOS.NET/Types/Temporal/Number/TemporalNumber.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@
return new TemporalNumber(res);
}

public TemporalBox BoundingBox()

Check warning on line 99 in MEOS.NET/Types/Temporal/Number/TemporalNumber.cs

View workflow job for this annotation

GitHub Actions / Regenerate, build and smoke-test against MEOS

'TemporalNumber.BoundingBox()' hides inherited member 'Temporal.BoundingBox()'. Use the new keyword if hiding was intended.

Check warning on line 99 in MEOS.NET/Types/Temporal/Number/TemporalNumber.cs

View workflow job for this annotation

GitHub Actions / Regenerate, build and smoke-test against MEOS

'TemporalNumber.BoundingBox()' hides inherited member 'Temporal.BoundingBox()'. Use the new keyword if hiding was intended.
{
var res = MEOSExposedFunctions.tnumber_to_tbox(this._ptr);
return new TemporalBox(res);
Expand Down Expand Up @@ -146,7 +146,7 @@

public TemporalNumber Multiply(TemporalNumber other)
{
var res = MEOSExposedFunctions.mult_tnumber_tnumber(this._ptr, other._ptr);
var res = MEOSExposedFunctions.mul_tnumber_tnumber(this._ptr, other._ptr);
return new TemporalNumber(res);
}

Expand Down
2 changes: 1 addition & 1 deletion MEOS.NET/Types/Temporal/Temporal.cs
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ public TimestampTzSpanSet Time()
public string Duration(bool ignoreGaps = false)
{
var res = MEOSExposedFunctions.temporal_duration(this._ptr, ignoreGaps);
return MEOSExposedFunctions.pg_interval_out(res);
return MEOSExposedFunctions.interval_out(res);
}

public TimestampTzSpan TimeSpan()
Expand Down
14 changes: 3 additions & 11 deletions MEOS.NET/Types/Temporal/TemporalInstant.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,14 @@ public TemporalInstant(IntPtr ptr) : base(ptr)

public DateTime Timestamp()
{
int count = 0;
var timestampArrPtr = AllocHelper.AllocatePointer(sizeof(int), (countPtr) =>
{
var res = MEOSExposedFunctions.temporal_timestamps(this._ptr, countPtr);
count = countPtr.ToStructure<int>();
var timestamps = MEOSExposedFunctions.temporal_timestamps(this._ptr);

return res;
});

if (count != 1)
if (timestamps.Length != 1)
{
throw new InvalidOperationException("The number of elements in the array of instants should be 1 for a single instant.");
}

var array = timestampArrPtr.ToArrayOfType<TimestampTz>(count);
return array[0].ToDateTime();
return new TimestampTz { Time = timestamps[0] }.ToDateTime();
}

public IEnumerable<DateTime> TimestampAsEnumerable()
Expand Down
79 changes: 53 additions & 26 deletions tools/codegen.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,9 @@ def csharp_param_name(name: str) -> str:
"for", "foreach", "while", "do", "if", "else", "switch", "case",
"break", "continue", "return", "throw", "try", "catch", "finally",
"goto", "yield", "using", "void",
"bool", "byte", "sbyte", "char", "short", "ushort", "int", "uint",
"long", "ulong", "float", "double", "decimal", "nint", "nuint",
"const",
}
return f"@{name}" if name in reserved else name

Expand Down Expand Up @@ -320,40 +323,64 @@ def _emit_outputs_wrapper(f: dict) -> list[str]:
def _emit_array_return_wrapper(f: dict, ext_params: str, ext_args: str) -> list[str]:
"""Emit a wrapper that materialises an array return via Marshal.Copy.

Used when shape.arrayReturn is present. Slice length comes from either
a sibling accessor call (kind=accessor) or an output parameter
(kind=param) of the same function."""
The slice length comes from either a sibling accessor call (kind=accessor)
or a by-pointer ``int *count`` output of the same function (kind=param). In
the param case the count is allocated internally and hidden from the public
signature, mirroring the MEOS ``TYPE *f(..., int *count)`` convention."""
name = f["name"]
shape = f["shape"]
length_meta = shape["arrayReturn"]["lengthFrom"]
elem, strategy = _csharp_array_element(f["returnType"]["c"], f["returnType"]["canonical"])
ret_type = f"{elem}[]"

lines: list[str] = []
lines.append(f" public static {ret_type} {name}({ext_params})")
lines.append(" {")
def _copy(indent: str) -> list[str]:
out = [f"{indent}{elem}[] _out = new {elem}[_n];"]
if strategy == "Marshal.Copy":
out.append(f"{indent}Marshal.Copy(_p, _out, 0, _n);")
else:
out.append(f"{indent}for (int _i = 0; _i < _n; _i++)")
out.append(f"{indent}{{ _out[_i] = Marshal.ReadIntPtr(_p, _i * IntPtr.Size); }}")
out.append(f"{indent}return _out;")
return out

if length_meta["kind"] == "accessor":
accessor = length_meta["func"]
arg = csharp_param_name(length_meta["arg"])
lines.append(f" int _n = (int)MEOSExposedFunctions.{accessor}({arg});")
else:
# kind == "param": the length is one of the function's own outputs.
# Emit a stub for now; full param-output unpacking lives in the
# outputArrays wrapper. Falls back to IntPtr return.
return _emit_simple_passthrough(f, ext_params, ext_args, default_rt="IntPtr")

lines.append(f" IntPtr _p = SafeExecution<IntPtr>(() => MEOSExternalFunctions.{name}({ext_args}));")
if strategy == "Marshal.Copy":
lines.append(f" {elem}[] _out = new {elem}[_n];")
lines.append(f" Marshal.Copy(_p, _out, 0, _n);")
lines.append(" return _out;")
else:
# Read N IntPtrs out of the array.
lines.append(f" {elem}[] _out = new {elem}[_n];")
lines.append(f" for (int _i = 0; _i < _n; _i++)")
lines.append(f" {{ _out[_i] = Marshal.ReadIntPtr(_p, _i * IntPtr.Size); }}")
lines.append(" return _out;")
lines.append(" }")
lines = [
f" public static {ret_type} {name}({ext_params})",
" {",
f" int _n = (int)MEOSExposedFunctions.{accessor}({arg});",
f" IntPtr _p = SafeExecution<IntPtr>(() => MEOSExternalFunctions.{name}({ext_args}));",
]
lines += _copy(" ")
lines.append(" }")
return lines

# kind == "param": the length is written back through the function's own
# ``int *count`` output. Allocate it, hide it from the public signature,
# and read the count back after the call.
count_name = csharp_param_name(length_meta["name"])
pub = ", ".join(
f"{csharp_param_type(p['cType'], p['canonical'])} {csharp_param_name(p['name'])}"
for p in f["params"] if csharp_param_name(p["name"]) != count_name)
call_args = ", ".join(
"_cnt" if csharp_param_name(p["name"]) == count_name else csharp_param_name(p["name"])
for p in f["params"])
lines = [
f" public static {ret_type} {name}({pub})",
" {",
" IntPtr _cnt = Marshal.AllocHGlobal(sizeof(int));",
" try",
" {",
f" IntPtr _p = SafeExecution<IntPtr>(() => MEOSExternalFunctions.{name}({call_args}));",
" int _n = Marshal.ReadInt32(_cnt);",
]
lines += _copy(" ")
lines += [
" }",
" finally { Marshal.FreeHGlobal(_cnt); }",
" }",
]
return lines


Expand Down Expand Up @@ -398,7 +425,7 @@ def gen_exposed_functions(funcs: list[dict]) -> str:
emitted: list[str] = []
if "outputArrays" in shape:
emitted = _emit_outputs_wrapper(f)
if not emitted and "arrayReturn" in shape and shape["arrayReturn"]["lengthFrom"]["kind"] == "accessor":
if not emitted and "arrayReturn" in shape and shape["arrayReturn"]["lengthFrom"]["kind"] in ("accessor", "param"):
emitted = _emit_array_return_wrapper(f, ext_params, ext_args)
if not emitted:
emitted = _emit_simple_passthrough(f, ext_params, ext_args)
Expand Down
Loading