Skip to content

Add experimental dimension label APIs #258

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
201 changes: 201 additions & 0 deletions examples/TileDB.CSharp.Example/ExampleDimensionLabels.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
using System;
using System.IO;
using System.Linq;

namespace TileDB.CSharp.Examples
{
public static class ExampleDimensionLabels
{
private static readonly string ArrayPath = ExampleUtil.MakeExamplePath("dimension-labels");
private static readonly Context Ctx = Context.GetDefault();

private static void CreateArray()
{
using var xIndex = Dimension.Create(Ctx, "x_index", 0, 5, 6);
using var sample = Dimension.Create(Ctx, "sample", 0, 3, 4);

using var domain = new Domain(Ctx);
domain.AddDimensions(xIndex, sample);

using var a = Attribute.Create<short>(Ctx, "a");

using var schema = new ArraySchema(Ctx, ArrayType.Dense);
schema.SetCellOrder(LayoutType.RowMajor);
schema.SetTileOrder(LayoutType.RowMajor);
schema.SetDomain(domain);
schema.AddAttribute(a);
schema.AddDimensionLabel(0, "x", DataOrder.Increasing, DataType.Float64);
schema.AddDimensionLabel(0, "y", DataOrder.Increasing, DataType.Float64);
schema.AddDimensionLabel(1, "timestamp", DataOrder.Increasing, DataType.DateTimeSecond);

Array.Create(Ctx, ArrayPath, schema);
}

private static void WriteArrayAndLabels()
{
short[] a = Enumerable.Range(1, 24).Select(x => (short)x).ToArray();
double[] x = { -1.0, -0.6, -0.2, 0.2, 0.6, 1.0 };
double[] y = { 0.0, 2.0, 4.0, 6.0, 8.0, 10.0 };
long[] timestamp = { 31943, 32380, 33131, 33228 };

using Array array = new Array(Ctx, ArrayPath);
array.Open(QueryType.Write);

using Query query = new Query(Ctx, array);
query.SetLayout(LayoutType.RowMajor);
query.SetDataBuffer("a", a);
query.SetDataBuffer("x", x);
query.SetDataBuffer("y", y);
query.SetDataBuffer("timestamp", timestamp);

query.Submit();

if (query.Status() != QueryStatus.Completed)
{
throw new Exception("Write query did not complete.");
}

array.Close();
}

private static void ReadArrayAndLabels()
{
Console.WriteLine("Read from main array");

using var array = new Array(Ctx, ArrayPath);
array.Open(QueryType.Read);

using var subarray = new Subarray(array);
subarray.AddRange(0, 1, 2);
subarray.AddRange(1, 0, 2);

short[] a = new short[6];
double[] x = new double[2];
double[] y = new double[2];
long[] timestamp = new long[3];

using var query = new Query(Ctx, array);
query.SetLayout(LayoutType.RowMajor);
query.SetSubarray(subarray);
query.SetDataBuffer("a", a);
query.SetDataBuffer("x", x);
query.SetDataBuffer("y", y);
query.SetDataBuffer("timestamp", timestamp);

query.Submit();

if (query.Status() != QueryStatus.Completed)
{
throw new Exception("Read query did not complete.");
}

for (int i = 0; i < 2; i++)
{
for (int j = 0; j < 3; j++)
{
int x_val = i + 1;
int sample_val = j;
Console.WriteLine($"Cell ({x_val}, {sample_val})");
Console.WriteLine($" * a({x_val}, {sample_val}) = {a[3 * i + j]}");
Console.WriteLine($" * x({x_val}) = {x[i]}");
Console.WriteLine($" * y({x_val}) = {y[i]}");
Console.WriteLine($" * timestamp({sample_val}) = {TimeSpan.FromSeconds(timestamp[j])}");
}
}

array.Close();
}

private static void ReadTimestampData()
{
Console.WriteLine("Read from dimension label");

using var array = new Array(Ctx, ArrayPath);
array.Open(QueryType.Read);

using var subarray = new Subarray(array);
subarray.AddRange(1, 1, 3);

long[] timestamp = new long[3];

using var query = new Query(Ctx, array);
query.SetLayout(LayoutType.RowMajor);
query.SetSubarray(subarray);
query.SetDataBuffer("timestamp", timestamp);

query.Submit();

if (query.Status() != QueryStatus.Completed)
{
throw new Exception("Read query did not complete.");
}

for (int i = 0; i < 3; i++)
{
int sample_val = i + 1;
Console.WriteLine($"Cell ({sample_val})");
Console.WriteLine($" * timestamp({sample_val}) = {TimeSpan.FromSeconds(timestamp[i])}");
}

array.Close();
}

private static void ReadArrayByLabel()
{
Console.WriteLine("Read array from label ranges");

using var array = new Array(Ctx, ArrayPath);
array.Open(QueryType.Read);

using var subarray = new Subarray(array);
subarray.AddLabelRange("y", 3.0, 8.0);
subarray.AddLabelRange<long>("timestamp", 31943, 32380);

short[] a = new short[6];
double[] y = new double[3];
long[] timestamp = new long[2];

using var query = new Query(Ctx, array);
query.SetLayout(LayoutType.RowMajor);
query.SetSubarray(subarray);
query.SetDataBuffer("y", y);
query.SetDataBuffer("timestamp", timestamp);
query.SetDataBuffer("a", a);

query.Submit();

if (query.Status() != QueryStatus.Completed)
{
throw new Exception("Read query did not complete.");
}

for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 2; j++)
{
Console.WriteLine($"Cell ({y[i]}, {TimeSpan.FromSeconds(timestamp[j])})");
Console.WriteLine($" * a = {a[2 * i + j]}");
}
}

array.Close();
}

public static void Run()
{
if (Directory.Exists(ArrayPath))
{
Directory.Delete(ArrayPath, true);
}

CreateArray();
WriteArrayAndLabels();
ReadArrayAndLabels();
Console.WriteLine();
ReadTimestampData();
Console.WriteLine();
ReadArrayByLabel();
Console.WriteLine();
}
}
}
3 changes: 2 additions & 1 deletion examples/TileDB.CSharp.Example/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.IO;
using TileDB.CSharp;
namespace TileDB.CSharp.Examples;
@@ -17,6 +17,7 @@ static void Main(string[] args)
ExampleWritingSparseGlobal.Run();
ExampleDataframe.Run();
ExampleAggregateQuery.Run();
ExampleDimensionLabels.Run();

ExampleFile.RunLocal();
// ExampleFile.RunCloud("tiledb_api_token", "tiledb_namespace", "new_cloud_array_name", "s3://bucket/prefix/");
106 changes: 106 additions & 0 deletions sources/TileDB.CSharp/ArraySchema.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using TileDB.CSharp.Marshalling.SafeHandles;
using TileDB.Interop;
using ArraySchemaHandle = TileDB.CSharp.Marshalling.SafeHandles.ArraySchemaHandle;
@@ -81,6 +82,46 @@ public void AddEnumeration(Enumeration enumeration)
_ctx.handle_error(Methods.tiledb_array_schema_add_enumeration(ctxHandle, handle, enumHandle));
}

/// <summary>
/// Adds a dimension label to the <see cref="ArraySchema"/>.
/// </summary>
/// <param name="dimensionIndex">The dimension's index.</param>
/// <param name="name">The dimension label's name.</param>
/// <param name="labelOrder">The data order of the dimension label.</param>
/// <param name="labelType">The dimension lable's data type.</param>
/// <remarks>This API is experimental and subject to breaking changes without advance notice.</remarks>
public void AddDimensionLabel(uint dimensionIndex, string name, DataOrder labelOrder, DataType labelType)
{
using var ctxHandle = _ctx.Handle.Acquire();
using var handle = _handle.Acquire();
using var ms_name = new MarshaledString(name);
_ctx.handle_error(Methods.tiledb_array_schema_add_dimension_label(ctxHandle, handle, dimensionIndex, ms_name, (tiledb_data_order_t)labelOrder, (tiledb_datatype_t)labelType));
}

/// <summary>
/// Adds a dimension label to the <see cref="ArraySchema"/>, that has a specified tile extent.
/// </summary>
/// <param name="dimensionIndex">The dimension's index.</param>
/// <param name="name">The dimension label's name.</param>
/// <param name="labelOrder">The data order of the dimension label.</param>
/// <param name="labelType">The dimension lable's data type.</param>
/// <param name="extent">The dimension label's tile extent.</param>
/// <exception cref="InvalidOperationException"><typeparamref name="T"/> and <paramref name="labelType"/> do not match.</exception>
/// <remarks>This API is experimental and subject to breaking changes without advance notice.</remarks>
public void AddDimensionLabel<T>(uint dimensionIndex, string name, DataOrder labelOrder, DataType labelType, T extent) where T : struct
{
if (RuntimeHelpers.IsReferenceOrContainsReferences<T>() || typeof(T) != EnumUtil.DataTypeToType(labelType))
{
ThrowHelpers.ThrowTypeMismatch(labelType);
}

using var ctxHandle = _ctx.Handle.Acquire();
using var handle = _handle.Acquire();
using var ms_name = new MarshaledString(name);
_ctx.handle_error(Methods.tiledb_array_schema_add_dimension_label(ctxHandle, handle, dimensionIndex, ms_name, (tiledb_data_order_t)labelOrder, (tiledb_datatype_t)labelType));
_ctx.handle_error(Methods.tiledb_array_schema_set_dimension_label_tile_extent(ctxHandle, handle, ms_name, (tiledb_datatype_t)labelType, &extent));
}

/// <summary>
/// Sets whether cells with duplicate coordinates are allowed in the <see cref="ArraySchema"/>.
/// </summary>
@@ -145,6 +186,21 @@ public void SetCellOrder(LayoutType layoutType)
_ctx.handle_error(Methods.tiledb_array_schema_set_cell_order(ctxHandle, handle, tiledb_layout));
}

/// <summary>
/// Sets the <see cref="FilterList"/> of filters that will be applied in one of the <see cref="ArraySchema"/>'s dimension labels.
/// </summary>
/// <param name="name">The dimension label's name.</param>
/// <param name="filterList">The dimension label's filter list.</param>
/// <remarks>This API is experimental and subject to breaking changes without advance notice.</remarks>
public void SetDimensionLabelFilterList(string name, FilterList filterList)
{
using var ctxHandle = _ctx.Handle.Acquire();
using var handle = _handle.Acquire();
using var ms_name = new MarshaledString(name);
using var filterListHandle = filterList.Handle.Acquire();
_ctx.handle_error(Methods.tiledb_array_schema_set_dimension_label_filter_list(ctxHandle, handle, ms_name, filterListHandle));
}

/// <summary>
/// Sets the <see cref="ArraySchema"/>'s tile order.
/// </summary>
@@ -491,6 +547,56 @@ public bool HasAttribute(string name)
return has_attr > 0;
}

/// <summary>
/// Gets a <see cref="CSharp.DimensionLabel"/> from the <see cref="ArraySchema"/> by name.
/// </summary>
/// <param name="name">The dimension label's name.</param>
/// <remarks>This API is experimental and subject to breaking changes without advance notice.</remarks>
public DimensionLabel DimensionLabel(string name)
{
var handle = new DimensionLabelHandle();
var successful = false;
tiledb_dimension_label_t* dimension_label_p = null;
try
{
using (var ctxHandle = _ctx.Handle.Acquire())
using (var schemaHandle = _handle.Acquire())
using (var ms_name = new MarshaledString(name))
{
_ctx.handle_error(Methods.tiledb_array_schema_get_dimension_label_from_name(ctxHandle, schemaHandle, ms_name, &dimension_label_p));
}
successful = true;
}
finally
{
if (successful)
{
handle.InitHandle(dimension_label_p);
}
else
{
handle.SetHandleAsInvalid();
}
}

return new DimensionLabel(_ctx, handle);
}

/// <summary>
/// Checks if a dimension label with the given name exists in the <see cref="ArraySchema"/> or not.
/// </summary>
/// <param name="name">The name to check.</param>
/// <remarks>This API is experimental and subject to breaking changes without advance notice.</remarks>
public bool HasDimensionLabel(string name)
{
int has_attr;
using var ctxHandle = _ctx.Handle.Acquire();
using var handle = _handle.Acquire();
using var ms_name = new MarshaledString(name);
_ctx.handle_error(Methods.tiledb_array_schema_has_dimension_label(ctxHandle, handle, ms_name, &has_attr));
return has_attr > 0;
}

/// <summary>
/// Load an <see cref="ArraySchema"/> from a URI.
/// </summary>
24 changes: 24 additions & 0 deletions sources/TileDB.CSharp/DataOrder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using TileDB.Interop;

namespace TileDB.CSharp
{
/// <summary>
/// Specifies the order of data in dimension labels.
/// </summary>
/// <remarks>This API is experimental and subject to breaking changes without advance notice.</remarks>
public enum DataOrder
{
/// <summary>
/// Data are not ordered.
/// </summary>
Unordered = tiledb_data_order_t.TILEDB_UNORDERED_DATA,
/// <summary>
/// Data are stored in increasing order.
/// </summary>
Increasing = tiledb_data_order_t.TILEDB_INCREASING_DATA,
/// <summary>
/// Data are stored in decreasing order.
/// </summary>
Decreasing = tiledb_data_order_t.TILEDB_DECREASING_DATA
}
}
135 changes: 135 additions & 0 deletions sources/TileDB.CSharp/DimensionLabel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
using System;
using TileDB.CSharp.Marshalling.SafeHandles;
using TileDB.Interop;

namespace TileDB.CSharp
{
/// <summary>
/// Represents a TileDB dimension label object.
/// </summary>
/// <remarks>This API is experimental and susceptible to breaking changes without advance notice.</remarks>
/// <seealso cref="ArraySchema.DimensionLabel"/>
public sealed unsafe class DimensionLabel : IDisposable
{
private readonly Context _ctx;

private readonly DimensionLabelHandle _handle;

internal DimensionLabel(Context ctx, DimensionLabelHandle handle)
{
_ctx = ctx;
_handle = handle;
}

/// <summary>
/// This value indicates a variable-sized dimension label.
/// It may be returned from <see cref="ValuesPerCell"/>.
/// </summary>
public const uint VariableSized = Constants.VariableSizedImpl;

/// <summary>
/// The <see cref="DimensionLabel"/>'s data type.
/// </summary>
public DataType DataType
{
get
{
using var ctxHandle = _ctx.Handle.Acquire();
using var handle = _handle.Acquire();
tiledb_datatype_t result;
_ctx.handle_error(Methods.tiledb_dimension_label_get_label_type(ctxHandle, handle, &result));
return (DataType)result;
}
}

/// <summary>
/// The order of the <see cref="DimensionLabel"/>'s data.
/// </summary>
public DataOrder DataOrder
{
get
{
using var ctxHandle = _ctx.Handle.Acquire();
using var handle = _handle.Acquire();
tiledb_data_order_t result;
_ctx.handle_error(Methods.tiledb_dimension_label_get_label_order(ctxHandle, handle, &result));
return (DataOrder)result;
}
}

/// <summary>
/// The index of the <see cref="DimensionLabel"/>'s underlying dimension.
/// </summary>
public uint DimensionIndex
{
get
{
using var ctxHandle = _ctx.Handle.Acquire();
using var handle = _handle.Acquire();
uint result;
_ctx.handle_error(Methods.tiledb_dimension_label_get_dimension_index(ctxHandle, handle, &result));
return result;
}
}

/// <summary>
/// Gets the number of values per cell for this <see cref="DimensionLabel"/>.
/// </summary>
/// <remarks>
/// For variable-sized dimension labels the result is <see cref="VariableSized"/>.
/// </remarks>
public uint ValuesPerCell
{
get
{
using var ctxHandle = _ctx.Handle.Acquire();
using var handle = _handle.Acquire();
uint result;
_ctx.handle_error(Methods.tiledb_dimension_label_get_label_cell_val_num(ctxHandle, handle, &result));
return result;
}
}

/// <summary>
/// Gets the name of the attrbiute the <see cref="DimensionLabel"/>'s data are stored under.
/// </summary>
public string GetAttributeName()
{
using var ctxHandle = _ctx.Handle.Acquire();
using var handle = _handle.Acquire();
sbyte* result;
_ctx.handle_error(Methods.tiledb_dimension_label_get_label_attr_name(ctxHandle, handle, &result));
return MarshaledStringOut.GetStringFromNullTerminated(result);
}

/// <summary>
/// Gets the name of the <see cref="DimensionLabel"/>.
/// </summary>
public string GetName()
{
using var ctxHandle = _ctx.Handle.Acquire();
using var handle = _handle.Acquire();
sbyte* result;
_ctx.handle_error(Methods.tiledb_dimension_label_get_name(ctxHandle, handle, &result));
return MarshaledStringOut.GetStringFromNullTerminated(result);
}

/// <summary>
/// Gets the URI of the <see cref="DimensionLabel"/>'s array.
/// </summary>
public string GetUri()
{
using var ctxHandle = _ctx.Handle.Acquire();
using var handle = _handle.Acquire();
sbyte* result;
_ctx.handle_error(Methods.tiledb_dimension_label_get_uri(ctxHandle, handle, &result));
return MarshaledStringOut.GetStringFromNullTerminated(result);
}

/// <inheritdoc/>
public void Dispose()
{
_handle.Dispose();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System;
using System.Runtime.InteropServices;
using TileDB.Interop;

namespace TileDB.CSharp.Marshalling.SafeHandles
{
internal unsafe sealed class DimensionLabelHandle : SafeHandle
{
public DimensionLabelHandle() : base(IntPtr.Zero, true) { }

public DimensionLabelHandle(IntPtr handle, bool ownsHandle) : base(IntPtr.Zero, ownsHandle) { SetHandle(handle); }

protected override bool ReleaseHandle()
{
fixed (IntPtr* p = &handle)
{
Methods.tiledb_dimension_label_free((tiledb_dimension_label_t**)p);
}
return true;
}

internal void InitHandle(tiledb_dimension_label_t* h) { SetHandle((IntPtr)h); }
public override bool IsInvalid => handle == IntPtr.Zero;

public SafeHandleHolder<tiledb_dimension_label_t> Acquire() => new(this);
}
}
5 changes: 5 additions & 0 deletions sources/TileDB.CSharp/Query.cs
Original file line number Diff line number Diff line change
@@ -1048,6 +1048,11 @@ private static DataType GetDataType(string name, ArraySchema schema, Domain doma
using var dimension = domain.Dimension(name);
return dimension.Type();
}
else if (schema.HasDimensionLabel(name))
{
using var label = schema.DimensionLabel(name);
return label.DataType;
}

if (name == "__coords")
{
141 changes: 140 additions & 1 deletion sources/TileDB.CSharp/Subarray.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using TileDB.CSharp.Marshalling;
@@ -134,6 +134,14 @@ public void SetSubarray<T>(ReadOnlySpan<T> data) where T : struct
}
}

private void ValidateLabelType<T>(string name) where T : struct
{
ErrorHandling.ThrowIfManagedType<T>();
using var schema = _array.Schema();
using var label = schema.DimensionLabel(name);
ErrorHandling.CheckDataType<T>(label.DataType);
}

private void ValidateType<T>(string name) where T : struct
{
ErrorHandling.ThrowIfManagedType<T>();
@@ -152,6 +160,46 @@ private void ValidateType<T>(uint index) where T : struct
ErrorHandling.CheckDataType<T>(dimension.Type());
}

/// <summary>
/// Adds a 1D range along a subarray dimension index, in the form (start, end).
/// </summary>
/// <typeparam name="T">The dimension's type.</typeparam>
/// <param name="labelName">The dimension's index.</param>
/// <param name="start">The start of the dimension's range.</param>
/// <param name="end">The end of the dimension's range.</param>
/// <exception cref="NotSupportedException"><typeparamref name="T"/> is not supported.</exception>
/// <remarks>This API is experimental and subject to breaking changes without advance notice.</remarks>
public void AddLabelRange<T>(string labelName, T start, T end) where T : struct
{
ValidateLabelType<T>(labelName);
AddLabelRange(labelName, &start, &end, null);
}

private void AddLabelRange(string labelName, void* start, void* end, void* stride)
{
using var ctxHandle = _ctx.Handle.Acquire();
using var handle = _handle.Acquire();
using var ms_name = new MarshaledString(labelName);
_ctx.handle_error(Methods.tiledb_subarray_add_label_range(ctxHandle, handle, ms_name, start, end, stride));
}

/// <summary>
/// Adds a 1D string range along a variable-sized subarray dimension label, in the form (start, end).
/// </summary>
/// <param name="labelName">The dimension label's name.</param>
/// <param name="start">The start of the dimension label's range.</param>
/// <param name="end">The end of the dimension label's range.</param>
/// <remarks>This API is experimental and subject to breaking changes without advance notice.</remarks>
public void AddLabelRange(string labelName, string start, string end)
{
using var ctxHandle = _ctx.Handle.Acquire();
using var handle = _handle.Acquire();
using var ms_name = new MarshaledString(labelName);
using var ms_start = new MarshaledString(start);
using var ms_end = new MarshaledString(end);
_ctx.handle_error(Methods.tiledb_subarray_add_label_range_var(ctxHandle, handle, ms_name, ms_start, (ulong)ms_start.Length, ms_end, (ulong)ms_end.Length));
}

/// <summary>
/// Adds a 1D range along a subarray dimension index, in the form (start, end).
/// </summary>
@@ -258,6 +306,83 @@ public void AddRange(string dimensionName, string start, string end)
_ctx.handle_error(Methods.tiledb_subarray_add_range_var_by_name(ctxHandle, handle, ms_name, ms_start, (ulong)ms_start.Length, ms_end, (ulong)ms_end.Length));
}

/// <summary>
/// Gets the name of the dimension label for label ranges set on this dimension of the <see cref="Subarray"/>.
/// </summary>
/// <param name="dimensionIndex">The dimension's index.</param>
/// <remarks>This API is experimental and subject to breaking changes without advance notice.</remarks>
public string GetLabelName(uint dimensionIndex)
{
using var ctxHandle = _ctx.Handle.Acquire();
using var handle = _handle.Acquire();
sbyte* result;
_ctx.handle_error(Methods.tiledb_subarray_get_label_name(ctxHandle, handle, dimensionIndex, &result));
return MarshaledStringOut.GetStringFromNullTerminated(result);
}

/// <summary>
/// Gets the range for a given dimension label and range index.
/// </summary>
/// <typeparam name="T">The dimension label's type.</typeparam>
/// <param name="labelName">The dimension label's name.</param>
/// <param name="rangeIndex">The range's index.</param>
/// <returns>The dimension's start and end values.</returns>
/// <exception cref="NotSupportedException"><typeparamref name="T"/> is not supported.</exception>
/// <remarks>This API is experimental and subject to breaking changes without advance notice.</remarks>
public (T Start, T End) GetLabelRange<T>(string labelName, uint rangeIndex) where T : struct
{
ValidateLabelType<T>(labelName);
using var ctxHandle = _ctx.Handle.Acquire();
using var handle = _handle.Acquire();
using var ms_dimensionName = new MarshaledString(labelName);
void* startPtr, endPtr, stridePtr_unused;
_ctx.handle_error(Methods.tiledb_subarray_get_label_range(ctxHandle, handle, ms_dimensionName, rangeIndex, &startPtr, &endPtr, &stridePtr_unused));
return (Unsafe.ReadUnaligned<T>(startPtr), Unsafe.ReadUnaligned<T>(endPtr));
}

/// <summary>
/// Gets the string range for a given dimension label and range index.
/// </summary>
/// <param name="labelName">The dimension label's name.</param>
/// <param name="rangeIndex">The range's index.</param>
/// <returns>The dimension's start and end values.</returns>
/// <remarks>This API is experimental and subject to breaking changes without advance notice.</remarks>
public (string Start, string End) GetStringLabelRange(string labelName, uint rangeIndex)
{
using var ctxHandle = _ctx.Handle.Acquire();
using var handle = _handle.Acquire();
using var ms_dimensionName = new MarshaledString(labelName);
ulong start_size;
ulong end_size;
_ctx.handle_error(Methods.tiledb_subarray_get_label_range_var_size(ctxHandle, handle, ms_dimensionName, rangeIndex, &start_size, &end_size));

using var startBuffer = new ScratchBuffer<byte>(checked((int)start_size), stackalloc byte[128]);
using var endBuffer = new ScratchBuffer<byte>(checked((int)end_size), stackalloc byte[128]);
fixed (byte* startPtr = startBuffer, endPtr = endBuffer)
{
_ctx.handle_error(Methods.tiledb_subarray_get_label_range_var(ctxHandle, handle, ms_dimensionName, rangeIndex, startPtr, endPtr));
}

var startStr = MarshaledStringOut.GetString(startBuffer.Span);
var endStr = MarshaledStringOut.GetString(endBuffer.Span);
return (startStr, endStr);
}

/// <summary>
/// Gets the number of ranges for a given dimension label name.
/// </summary>
/// <param name="labelName">The dimension label's name.</param>
/// <remarks>This API is experimental and subject to breaking changes without advance notice.</remarks>
public ulong GetLabelRangeCount(string labelName)
{
using var ctxHandle = _ctx.Handle.Acquire();
using var handle = _handle.Acquire();
using var ms_dimensionName = new MarshaledString(labelName);
ulong result;
_ctx.handle_error(Methods.tiledb_subarray_get_label_range_num(ctxHandle, handle, ms_dimensionName, &result));
return result;
}

/// <summary>
/// Gets the number of ranges for a given dimension index.
/// </summary>
@@ -374,4 +499,18 @@ public ulong GetRangeCount(string dimensionName)
var endStr = MarshaledStringOut.GetString(endBuffer.Span);
return (startStr, endStr);
}

/// <summary>
/// Checks whether the <see cref="Subarray"/> has label ranges set on the requested dimension.
/// </summary>
/// <param name="dimensionIndex">The dimension's index.</param>
/// <remarks>This API is experimental and subject to breaking changes without advance notice.</remarks>
public bool HasLabelRanges(uint dimensionIndex)
{
using var ctxHandle = _ctx.Handle.Acquire();
using var handle = _handle.Acquire();
int result;
_ctx.handle_error(Methods.tiledb_subarray_has_label_ranges(ctxHandle, handle, dimensionIndex, &result));
return result != 0;
}
}