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
77 changes: 59 additions & 18 deletions MAINTAINING.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ the development-era working notes.

## Repository Shape

This repository contains the hand-written C# binding for the native Ladybug C API. It is also used as the
`tools/csharp_api` submodule inside the main `LadybugDB/ladybug` monorepo, where the engine source and
`src/include/c_api/lbug.h` are available at `../../`.
This is the standalone, hand-written C# binding for the native Ladybug C API. It can be mounted locally
at `tools/csharp_api` inside a `LadybugDB/ladybug` checkout, where the engine source and
`src/include/c_api/lbug.h` are available at `../../`. The repositories advance independently: a binding
release pins a published engine tag rather than a parent-repository submodule commit.

Main areas:

Expand Down Expand Up @@ -102,25 +103,65 @@ dotnet test tools/csharp_api/test/LadybugDB.Tests/LadybugDB.Tests.csproj -c Rele
PowerShell can mangle unquoted `-D` arguments; pass CMake flags as quoted strings or via an explicit
PowerShell array when scripting.

## Release Flow
## Adopting an Upstream Engine Release

1. Decide the package version and update `version.txt`.
2. Ensure the native engine release exists in `LadybugDB/ladybug` for the first three numeric package
version segments, or pass `--engine-version` / workflow `engine_version`.
3. Run local validation where practical:
Use a fresh binding worktree so `lib/` and `download/` cannot contain a native or release archive from
the previous engine. Set `LADYBUG_ENGINE_REPO` to a local `LadybugDB/ladybug` checkout, then fetch and
inspect the old and new release tags:

```powershell
./build.ps1 --target Test
./build.ps1 --target Pack
```
```powershell
$engine = $env:LADYBUG_ENGINE_REPO
if (-not $engine -or -not (Test-Path (Join-Path $engine 'src/include/c_api/lbug.h'))) {
throw 'Set LADYBUG_ENGINE_REPO to a LadybugDB/ladybug checkout.'
}
$old = 'v0.17.0'
$new = 'v0.18.1'

git -C $engine fetch origin --tags --prune
gh release view $new --repo LadybugDB/ladybug
git -C $engine diff --exit-code $old $new -- src/include/c_api/
git -C $engine log --oneline "$old..$new" -- src/include/c_api/ src/c_api/
```

4. Merge through CI.
5. Tag the package version:
The header diff is the ABI gate. An empty diff means no release-driven interop change. A non-empty diff
requires the ABI checklist below, including updates to both declaration files and native-gated tests.
Implementation-only changes can still affect behavior, so review the `src/c_api/` log even when the
header is unchanged.

Confirm that the new release contains every asset named by `cake/BuildContext.cs`:

```powershell
$required = @(
'liblbug-windows-x86_64.zip',
'liblbug-linux-x86_64.tar.gz',
'liblbug-linux-aarch64.tar.gz',
'liblbug-osx-x86_64.tar.gz',
'liblbug-osx-arm64.tar.gz'
)
$assets = @(gh release view $new --repo LadybugDB/ladybug --json assets --jq '.assets[].name')
$missing = @($required | Where-Object { $_ -notin $assets })
if ($missing.Count -ne 0) { throw "Missing release assets: $($missing -join ', ')" }
```

```bash
git tag v0.17.0.1
git push origin v0.17.0.1
```
Set `version.txt` to the exact stable package version, update active version references in the README
and examples, then validate the real native and the full package family:

```powershell
.\build.ps1 --target Test --engine-version $new
.\build.ps1 --target Pack --package-version ($new.TrimStart('v')) --engine-version $new
```

`Test` must run with native skips disabled. `Pack` must verify the managed package, all five per-RID
native packages, and the native meta-package. Merge through CI before creating and pushing the matching
`vX.Y.Z` binding tag; only a tag push publishes to NuGet.

## Release Flow

1. Follow **Adopting an Upstream Engine Release** when the first three version segments change.
2. Update `version.txt`; for a binding-only release, increment only the optional fourth segment.
3. Run Cake `Test` and `Pack` against the intended engine tag.
4. Merge through CI.
5. Tag the merged package version and push that tag; the tag-triggered workflow publishes.

The release workflow gates on linux-x64 against the real engine, packs the full package family, verifies
contents, and publishes all packages to NuGet through trusted publishing.
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Official C# binding for the [Ladybug](https://github.com/ladybugdb/ladybug) embe
It wraps the native Ladybug C API via P/Invoke and ships prebuilt native libraries for supported
platforms, so you can run Cypher queries against an embedded graph database directly from .NET.

> Current package family: `0.17.0.1`, built against the Ladybug `v0.17.0` engine.
> Current package family: `0.18.1`, built against the Ladybug `v0.18.1` engine.

## Target frameworks

Expand Down Expand Up @@ -78,8 +78,8 @@ project under [`cake/`](cake) (run it with the `build.ps1` / `build.sh` bootstra

All packages in the family share one version. The first three numeric segments track the upstream engine
release, and the optional fourth segment is the .NET package revision for binding-only releases. For
example, package `0.17.0.1` wraps the Ladybug `v0.17.0` engine; a future binding-only fix over the same
engine would be `0.17.0.2`. Prerelease suffixes are reserved for preview builds.
example, package `0.18.1` wraps the Ladybug `v0.18.1` engine; a future binding-only fix over the same
engine would be `0.18.1.1`. Prerelease suffixes are reserved for preview builds.

The package version is defined once in `version.txt` at the repo root. Override it with
`--package-version <v>` (the release workflow uses the git tag). The engine release defaults to the first
Expand Down
2 changes: 1 addition & 1 deletion examples/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<PropertyGroup>
<!-- Published package version used by the examples. Override with:
dotnet run -p:LadybugVersion=<version> -->
<LadybugVersion Condition="'$(LadybugVersion)' == ''">0.17.0.1</LadybugVersion>
<LadybugVersion Condition="'$(LadybugVersion)' == ''">0.18.1</LadybugVersion>
</PropertyGroup>

</Project>
2 changes: 1 addition & 1 deletion examples/native-loading/Dockerfile.bundled
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# (liblbug.so) is published next to the app and loaded from the app's own deployment directory.
FROM mcr.microsoft.com/dotnet/sdk:10.0

ARG LADYBUG_VERSION=0.17.0-alpha.1
ARG LADYBUG_VERSION=0.18.1

WORKDIR /src
COPY nuget.config ./nuget.config
Expand Down
4 changes: 2 additions & 2 deletions examples/native-loading/Dockerfile.system
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# installed system-wide via a Debian package; the app must locate it through the OS dynamic linker.
FROM mcr.microsoft.com/dotnet/sdk:10.0

ARG LADYBUG_VERSION=0.17.0-alpha.1
ARG LADYBUG_VERSION=0.18.1

# unzip: extract liblbug.so from the native nupkg for the .deb. binutils: readelf (SONAME inspection).
RUN apt-get update \
Expand All @@ -21,6 +21,6 @@ RUN dotnet publish app/ConsumerApp.csproj -c Release -r linux-x64 --self-contain
-p:LadybugVersion=${LADYBUG_VERSION} -o /app/publish

# Build the .deb at image-build time; installation happens at run time (after the negative control).
RUN bash scripts/build-deb.sh /src/feed /tmp 0.17.0
RUN bash scripts/build-deb.sh /src/feed /tmp 0.18.1

ENTRYPOINT ["bash", "/src/scripts/run-system.sh"]
2 changes: 1 addition & 1 deletion examples/native-loading/scripts/build-deb.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ set -euo pipefail

FEED="${1:?feed dir required}"
OUT="${2:?output dir required}"
VER="${3:-0.17.0}"
VER="${3:-0.18.1}"

work="$(mktemp -d)"
trap 'rm -rf "$work"' EXIT
Expand Down
2 changes: 1 addition & 1 deletion examples/quickstart/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ dotnet run
Expected output:

```
LadybugDB 0.17.0 (storage v41)
LadybugDB 0.18.1 (storage v42)

name | age
Alice is 25 years old
Expand Down
75 changes: 75 additions & 0 deletions src/LadybugDB/Interop/Native.DllImport.cs
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,12 @@ internal static LbugState PreparedStatementBindTimestampTz(ref LbugPreparedState
internal static LbugState PreparedStatementBindInterval(ref LbugPreparedStatement preparedStatement, string paramName, LbugInterval value)
=> PreparedStatementBindIntervalRaw(ref preparedStatement, ToUtf8(paramName), value);

[DllImport(LibraryName, EntryPoint = "lbug_prepared_statement_bind_value", CallingConvention = Conv)]
private static extern LbugState PreparedStatementBindValueRaw(ref LbugPreparedStatement preparedStatement, byte[] paramName, IntPtr value);

internal static LbugState PreparedStatementBindValue(ref LbugPreparedStatement preparedStatement, string paramName, IntPtr value)
=> PreparedStatementBindValueRaw(ref preparedStatement, ToUtf8(paramName), value);

// ---- QueryResult -----------------------------------------------------------------------------
[DllImport(LibraryName, EntryPoint = "lbug_query_result_destroy", CallingConvention = Conv)]
internal static extern void QueryResultDestroy(ref LbugQueryResult queryResult);
Expand Down Expand Up @@ -223,13 +229,82 @@ internal static LbugState PreparedStatementBindInterval(ref LbugPreparedStatemen
[DllImport(LibraryName, EntryPoint = "lbug_data_type_get_id", CallingConvention = Conv)]
internal static extern LbugDataTypeId DataTypeGetId(ref LbugLogicalType dataType);

[DllImport(LibraryName, EntryPoint = "lbug_data_type_create", CallingConvention = Conv)]
internal static extern void DataTypeCreate(LbugDataTypeId id, IntPtr childType, ulong numElementsInArray, out LbugLogicalType outType);

[DllImport(LibraryName, EntryPoint = "lbug_data_type_create", CallingConvention = Conv)]
internal static extern void DataTypeCreateWithChild(LbugDataTypeId id, ref LbugLogicalType childType, ulong numElementsInArray, out LbugLogicalType outType);

[DllImport(LibraryName, EntryPoint = "lbug_data_type_destroy", CallingConvention = Conv)]
internal static extern void DataTypeDestroy(ref LbugLogicalType dataType);

// ---- Value -----------------------------------------------------------------------------------
[DllImport(LibraryName, EntryPoint = "lbug_value_destroy", CallingConvention = Conv)]
internal static extern void ValueDestroy(ref LbugValue value);

[DllImport(LibraryName, EntryPoint = "lbug_value_destroy", CallingConvention = Conv)]
internal static extern void ValueDestroy(IntPtr value);

[DllImport(LibraryName, EntryPoint = "lbug_value_create_null", CallingConvention = Conv)]
internal static extern IntPtr ValueCreateNull();

[DllImport(LibraryName, EntryPoint = "lbug_value_create_default", CallingConvention = Conv)]
internal static extern IntPtr ValueCreateDefault(ref LbugLogicalType dataType);

[DllImport(LibraryName, EntryPoint = "lbug_value_create_bool", CallingConvention = Conv)]
internal static extern IntPtr ValueCreateBool([MarshalAs(UnmanagedType.U1)] bool value);

[DllImport(LibraryName, EntryPoint = "lbug_value_create_int8", CallingConvention = Conv)]
internal static extern IntPtr ValueCreateInt8(sbyte value);

[DllImport(LibraryName, EntryPoint = "lbug_value_create_int16", CallingConvention = Conv)]
internal static extern IntPtr ValueCreateInt16(short value);

[DllImport(LibraryName, EntryPoint = "lbug_value_create_int32", CallingConvention = Conv)]
internal static extern IntPtr ValueCreateInt32(int value);

[DllImport(LibraryName, EntryPoint = "lbug_value_create_int64", CallingConvention = Conv)]
internal static extern IntPtr ValueCreateInt64(long value);

[DllImport(LibraryName, EntryPoint = "lbug_value_create_uint8", CallingConvention = Conv)]
internal static extern IntPtr ValueCreateUInt8(byte value);

[DllImport(LibraryName, EntryPoint = "lbug_value_create_uint16", CallingConvention = Conv)]
internal static extern IntPtr ValueCreateUInt16(ushort value);

[DllImport(LibraryName, EntryPoint = "lbug_value_create_uint32", CallingConvention = Conv)]
internal static extern IntPtr ValueCreateUInt32(uint value);

[DllImport(LibraryName, EntryPoint = "lbug_value_create_uint64", CallingConvention = Conv)]
internal static extern IntPtr ValueCreateUInt64(ulong value);

[DllImport(LibraryName, EntryPoint = "lbug_value_create_float", CallingConvention = Conv)]
internal static extern IntPtr ValueCreateFloat(float value);

[DllImport(LibraryName, EntryPoint = "lbug_value_create_double", CallingConvention = Conv)]
internal static extern IntPtr ValueCreateDouble(double value);

[DllImport(LibraryName, EntryPoint = "lbug_value_create_string", CallingConvention = Conv)]
private static extern IntPtr ValueCreateStringRaw(byte[] value);

internal static IntPtr ValueCreateString(string value)
=> ValueCreateStringRaw(ToUtf8(value));

[DllImport(LibraryName, EntryPoint = "lbug_value_create_date", CallingConvention = Conv)]
internal static extern IntPtr ValueCreateDate(LbugDate value);

[DllImport(LibraryName, EntryPoint = "lbug_value_create_timestamp", CallingConvention = Conv)]
internal static extern IntPtr ValueCreateTimestamp(LbugTimestamp value);

[DllImport(LibraryName, EntryPoint = "lbug_value_create_timestamp_tz", CallingConvention = Conv)]
internal static extern IntPtr ValueCreateTimestampTz(LbugTimestamp value);

[DllImport(LibraryName, EntryPoint = "lbug_value_create_interval", CallingConvention = Conv)]
internal static extern IntPtr ValueCreateInterval(LbugInterval value);

[DllImport(LibraryName, EntryPoint = "lbug_value_create_list", CallingConvention = Conv)]
internal static extern LbugState ValueCreateList(ulong numElements, [In] IntPtr[] elements, out IntPtr outValue);

[DllImport(LibraryName, EntryPoint = "lbug_value_is_null", CallingConvention = Conv)]
[return: MarshalAs(UnmanagedType.U1)]
internal static extern bool ValueIsNull(ref LbugValue value);
Expand Down
69 changes: 69 additions & 0 deletions src/LadybugDB/Interop/Native.LibraryImport.cs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,9 @@ internal static partial class Native
[LibraryImport(LibraryName, EntryPoint = "lbug_prepared_statement_bind_interval", StringMarshalling = StringMarshalling.Utf8)]
internal static partial LbugState PreparedStatementBindInterval(ref LbugPreparedStatement preparedStatement, string paramName, LbugInterval value);

[LibraryImport(LibraryName, EntryPoint = "lbug_prepared_statement_bind_value", StringMarshalling = StringMarshalling.Utf8)]
internal static partial LbugState PreparedStatementBindValue(ref LbugPreparedStatement preparedStatement, string paramName, IntPtr value);

// ---- QueryResult -----------------------------------------------------------------------------
[LibraryImport(LibraryName, EntryPoint = "lbug_query_result_destroy")]
internal static partial void QueryResultDestroy(ref LbugQueryResult queryResult);
Expand Down Expand Up @@ -155,13 +158,79 @@ internal static partial class Native
[LibraryImport(LibraryName, EntryPoint = "lbug_data_type_get_id")]
internal static partial LbugDataTypeId DataTypeGetId(ref LbugLogicalType dataType);

[LibraryImport(LibraryName, EntryPoint = "lbug_data_type_create")]
internal static partial void DataTypeCreate(LbugDataTypeId id, IntPtr childType, ulong numElementsInArray, out LbugLogicalType outType);

[LibraryImport(LibraryName, EntryPoint = "lbug_data_type_create")]
internal static partial void DataTypeCreateWithChild(LbugDataTypeId id, ref LbugLogicalType childType, ulong numElementsInArray, out LbugLogicalType outType);

[LibraryImport(LibraryName, EntryPoint = "lbug_data_type_destroy")]
internal static partial void DataTypeDestroy(ref LbugLogicalType dataType);

// ---- Value -----------------------------------------------------------------------------------
[LibraryImport(LibraryName, EntryPoint = "lbug_value_destroy")]
internal static partial void ValueDestroy(ref LbugValue value);

[LibraryImport(LibraryName, EntryPoint = "lbug_value_destroy")]
internal static partial void ValueDestroy(IntPtr value);

[LibraryImport(LibraryName, EntryPoint = "lbug_value_create_null")]
internal static partial IntPtr ValueCreateNull();

[LibraryImport(LibraryName, EntryPoint = "lbug_value_create_default")]
internal static partial IntPtr ValueCreateDefault(ref LbugLogicalType dataType);

[LibraryImport(LibraryName, EntryPoint = "lbug_value_create_bool")]
internal static partial IntPtr ValueCreateBool([MarshalAs(UnmanagedType.U1)] bool value);

[LibraryImport(LibraryName, EntryPoint = "lbug_value_create_int8")]
internal static partial IntPtr ValueCreateInt8(sbyte value);

[LibraryImport(LibraryName, EntryPoint = "lbug_value_create_int16")]
internal static partial IntPtr ValueCreateInt16(short value);

[LibraryImport(LibraryName, EntryPoint = "lbug_value_create_int32")]
internal static partial IntPtr ValueCreateInt32(int value);

[LibraryImport(LibraryName, EntryPoint = "lbug_value_create_int64")]
internal static partial IntPtr ValueCreateInt64(long value);

[LibraryImport(LibraryName, EntryPoint = "lbug_value_create_uint8")]
internal static partial IntPtr ValueCreateUInt8(byte value);

[LibraryImport(LibraryName, EntryPoint = "lbug_value_create_uint16")]
internal static partial IntPtr ValueCreateUInt16(ushort value);

[LibraryImport(LibraryName, EntryPoint = "lbug_value_create_uint32")]
internal static partial IntPtr ValueCreateUInt32(uint value);

[LibraryImport(LibraryName, EntryPoint = "lbug_value_create_uint64")]
internal static partial IntPtr ValueCreateUInt64(ulong value);

[LibraryImport(LibraryName, EntryPoint = "lbug_value_create_float")]
internal static partial IntPtr ValueCreateFloat(float value);

[LibraryImport(LibraryName, EntryPoint = "lbug_value_create_double")]
internal static partial IntPtr ValueCreateDouble(double value);

[LibraryImport(LibraryName, EntryPoint = "lbug_value_create_string", StringMarshalling = StringMarshalling.Utf8)]
internal static partial IntPtr ValueCreateString(string value);

[LibraryImport(LibraryName, EntryPoint = "lbug_value_create_date")]
internal static partial IntPtr ValueCreateDate(LbugDate value);

[LibraryImport(LibraryName, EntryPoint = "lbug_value_create_timestamp")]
internal static partial IntPtr ValueCreateTimestamp(LbugTimestamp value);

[LibraryImport(LibraryName, EntryPoint = "lbug_value_create_timestamp_tz")]
internal static partial IntPtr ValueCreateTimestampTz(LbugTimestamp value);

[LibraryImport(LibraryName, EntryPoint = "lbug_value_create_interval")]
internal static partial IntPtr ValueCreateInterval(LbugInterval value);

[LibraryImport(LibraryName, EntryPoint = "lbug_value_create_list")]
internal static partial LbugState ValueCreateList(ulong numElements, [In] IntPtr[] elements, out IntPtr outValue);

[LibraryImport(LibraryName, EntryPoint = "lbug_value_is_null")]
[return: MarshalAs(UnmanagedType.U1)]
internal static partial bool ValueIsNull(ref LbugValue value);
Expand Down
Loading