diff --git a/csharp/.gitignore b/csharp/.gitignore new file mode 100644 index 00000000000..4b82ccd9149 --- /dev/null +++ b/csharp/.gitignore @@ -0,0 +1,3 @@ +.vs/ +bin/ +obj/ diff --git a/csharp/client/DeephavenClient/AggregateCombo.cs b/csharp/client/DeephavenClient/AggregateCombo.cs new file mode 100644 index 00000000000..2006e14f8c9 --- /dev/null +++ b/csharp/client/DeephavenClient/AggregateCombo.cs @@ -0,0 +1,64 @@ +using System; +using System.Runtime.InteropServices; +using Deephaven.DeephavenClient.Interop; + +namespace Deephaven.DeephavenClient; + +public class AggregateCombo { + private readonly Aggregate[] _aggregates; + + public AggregateCombo(IEnumerable aggregates) => _aggregates = aggregates.ToArray(); + + internal InternalAggregateCombo Invoke() { + return new InternalAggregateCombo(_aggregates); + } +} + +internal class InternalAggregateCombo : IDisposable { + internal NativePtr Self; + + internal InternalAggregateCombo(Aggregate[] aggregates) { + var internalAggregates = new List(); + try { + // Invoke the lazy method on the aggregate to get its C++ wrapper + foreach (var agg in aggregates) { + internalAggregates.Add(agg.Materialize()); + } + + var internalAggPtrs = internalAggregates.Select(ag => ag.Self).ToArray(); + NativeAggregateCombo.deephaven_client_AggregateCombo_Create( + internalAggPtrs, internalAggPtrs.Length, out var result, out var status); + status.OkOrThrow(); + Self = result; + } finally { + foreach (var agg in internalAggregates) { + agg.Dispose(); + } + } + } + + ~InternalAggregateCombo() { + ReleaseUnmanagedResources(); + } + + public void Dispose() { + ReleaseUnmanagedResources(); + GC.SuppressFinalize(this); + } + + private void ReleaseUnmanagedResources() { + if (!Self.TryRelease(out var old)) { + return; + } + NativeAggregateCombo.deephaven_client_AggregateCombo_dtor(old); + } +} + +internal partial class NativeAggregateCombo { + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_AggregateCombo_Create( + NativePtr[] aggregates, Int32 numAggregates, + out NativePtr self, out ErrorStatus status); + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_AggregateCombo_dtor(NativePtr self); +} diff --git a/csharp/client/DeephavenClient/Aggregates.cs b/csharp/client/DeephavenClient/Aggregates.cs new file mode 100644 index 00000000000..0816aa9040b --- /dev/null +++ b/csharp/client/DeephavenClient/Aggregates.cs @@ -0,0 +1,169 @@ +using System; +using System.Runtime.InteropServices; +using Deephaven.DeephavenClient.Interop; + +namespace Deephaven.DeephavenClient; + +public class Aggregate { + [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Unicode)] + private delegate void AggregateMethod( + string[] columns, Int32 numColumns, out NativePtr result, out ErrorStatus status); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Unicode)] + private delegate void LazyMaterializer(out NativePtr result, out ErrorStatus status); + + public static Aggregate AbsSum(IEnumerable columnSpecs) { + return CreateHelper(columnSpecs, NativeAggregate.deephaven_client_Aggregate_AbsSum); + } + + public static Aggregate Group(IEnumerable columnSpecs) { + return CreateHelper(columnSpecs, NativeAggregate.deephaven_client_Aggregate_Group); + } + + public static Aggregate Avg(IEnumerable columnSpecs) { + return CreateHelper(columnSpecs, NativeAggregate.deephaven_client_Aggregate_Avg); + } + + public static Aggregate First(IEnumerable columnSpecs) { + return CreateHelper(columnSpecs, NativeAggregate.deephaven_client_Aggregate_First); + } + + public static Aggregate Last(IEnumerable columnSpecs) { + return CreateHelper(columnSpecs, NativeAggregate.deephaven_client_Aggregate_Last); + } + + public static Aggregate Max(IEnumerable columnSpecs) { + return CreateHelper(columnSpecs, NativeAggregate.deephaven_client_Aggregate_Max); + } + + public static Aggregate Med(IEnumerable columnSpecs) { + return CreateHelper(columnSpecs, NativeAggregate.deephaven_client_Aggregate_Med); + } + + public static Aggregate Min(IEnumerable columnSpecs) { + return CreateHelper(columnSpecs, NativeAggregate.deephaven_client_Aggregate_Min); + } + + public static Aggregate Std(IEnumerable columnSpecs) { + return CreateHelper(columnSpecs, NativeAggregate.deephaven_client_Aggregate_Std); + } + + public static Aggregate Sum(IEnumerable columnSpecs) { + return CreateHelper(columnSpecs, NativeAggregate.deephaven_client_Aggregate_Sum); + } + + public static Aggregate Var(IEnumerable columnSpecs) { + return CreateHelper(columnSpecs, NativeAggregate.deephaven_client_Aggregate_Var); + } + + public static Aggregate WAvg(IEnumerable columnSpecs) { + return CreateHelper(columnSpecs, NativeAggregate.deephaven_client_Aggregate_WAvg); + } + + public static Aggregate Count(string columnSpec) { + LazyMaterializer lazyMaterializer = (out NativePtr result, out ErrorStatus status) => + NativeAggregate.deephaven_client_Aggregate_Count(columnSpec, out result, out status); + return new Aggregate(lazyMaterializer); + } + + public static Aggregate Pct(double percentile, bool avgMedian, IEnumerable columnSpecs) { + var cols = columnSpecs.ToArray(); + LazyMaterializer lazyMaterializer = (out NativePtr result, out ErrorStatus status) => + NativeAggregate.deephaven_client_Aggregate_Pct(percentile, (InteropBool)avgMedian, + cols, cols.Length, out result, out status); + return new Aggregate(lazyMaterializer); + } + + /// + /// Helper method for all the Aggregate functions except Count, which is special because + /// it takes a string rather than an IEnumerable<string> + /// + private static Aggregate CreateHelper(IEnumerable columnSpecs, AggregateMethod aggregateMethod) { + var cs = columnSpecs.ToArray(); + + LazyMaterializer lazyMaterializer = (out NativePtr result, out ErrorStatus status) => + aggregateMethod(cs, cs.Length, out result, out status); + + return new Aggregate(lazyMaterializer); + } + + private readonly LazyMaterializer _lazyMaterializer; + + private Aggregate(LazyMaterializer lazyMaterializer) => _lazyMaterializer = lazyMaterializer; + + internal InternalAggregate Materialize() { + _lazyMaterializer(out var result, out var status); + status.OkOrThrow(); + return new InternalAggregate(result); + } +} + +internal class InternalAggregate : IDisposable { + internal NativePtr Self; + + internal InternalAggregate(NativePtr self) => Self = self; + + ~InternalAggregate() { + ReleaseUnmanagedResources(); + } + + public void Dispose() { + ReleaseUnmanagedResources(); + GC.SuppressFinalize(this); + } + + private void ReleaseUnmanagedResources() { + if (!Self.TryRelease(out var old)) { + return; + } + NativeAggregate.deephaven_client_Aggregate_dtor(old); + } +} + +internal partial class NativeAggregate { + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_Aggregate_dtor(NativePtr self); + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_Aggregate_AbsSum( + string[] columns, Int32 numColumns, out NativePtr result, out ErrorStatus status); + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_Aggregate_Group( + string[] columns, Int32 numColumns, out NativePtr result, out ErrorStatus status); + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_Aggregate_Avg( + string[] columns, Int32 numColumns, out NativePtr result, out ErrorStatus status); + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_Aggregate_Count( + string column, out NativePtr result, out ErrorStatus status); + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_Aggregate_First( + string[] columns, Int32 numColumns, out NativePtr result, out ErrorStatus status); + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_Aggregate_Last( + string[] columns, Int32 numColumns, out NativePtr result, out ErrorStatus status); + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_Aggregate_Max( + string[] columns, Int32 numColumns, out NativePtr result, out ErrorStatus status); + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_Aggregate_Med( + string[] columns, Int32 numColumns, out NativePtr result, out ErrorStatus status); + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_Aggregate_Min( + string[] columns, Int32 numColumns, out NativePtr result, out ErrorStatus status); + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_Aggregate_Pct( + double percentile, InteropBool avgMedian, + string[] columns, Int32 numColumns, out NativePtr result, out ErrorStatus status); + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_Aggregate_Std( + string[] columns, Int32 numColumns, out NativePtr result, out ErrorStatus status); + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_Aggregate_Sum( + string[] columns, Int32 numColumns, out NativePtr result, out ErrorStatus status); + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_Aggregate_Var( + string[] columns, Int32 numColumns, out NativePtr result, out ErrorStatus status); + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_Aggregate_WAvg( + string[] columns, Int32 numColumns, out NativePtr result, out ErrorStatus status); +} diff --git a/csharp/client/DeephavenClient/ArrowTable.cs b/csharp/client/DeephavenClient/ArrowTable.cs new file mode 100644 index 00000000000..469af9dcb76 --- /dev/null +++ b/csharp/client/DeephavenClient/ArrowTable.cs @@ -0,0 +1,198 @@ +using Deephaven.DeephavenClient.Interop; +using System.Runtime.InteropServices; +using Deephaven.DeephavenClient.Utility; + +namespace Deephaven.DeephavenClient; + +public class ArrowTable : IDisposable { + internal NativePtr Self; + public readonly Int32 NumColumns; + public readonly Int64 NumRows; + private readonly string[] _columnNames; + private readonly ElementTypeId[] _columnElementTypes; + + internal ArrowTable(NativePtr self) { + Self = self; + NativeArrowTable.deephaven_client_ArrowTable_GetDimensions(self, out NumColumns, out NumRows, out var status1); + status1.OkOrThrow(); + + var columnHandles = new StringHandle[NumColumns]; + var elementTypesAsInt = new Int32[NumColumns]; + NativeArrowTable.deephaven_client_ArrowTable_GetSchema(self, NumColumns, columnHandles, elementTypesAsInt, out var stringPoolHandle, out var status2); + status2.OkOrThrow(); + var pool = stringPoolHandle.ExportAndDestroy(); + + _columnNames = new string[NumColumns]; + _columnElementTypes = new ElementTypeId[NumColumns]; + for (var i = 0; i != NumColumns; ++i) { + _columnNames[i] = pool.Get(columnHandles[i]); + _columnElementTypes[i] = (ElementTypeId)elementTypesAsInt[i]; + } + } + + ~ArrowTable() { + ReleaseUnmanagedResources(); + } + + public void Dispose() { + ReleaseUnmanagedResources(); + GC.SuppressFinalize(this); + } + + public (Array, bool[]) GetColumn(Int32 index) { + var elementType = _columnElementTypes[index]; + var factory = ArrowTableColumnFactory.Of(elementType); + var (data, nulls) = factory.GetColumn(Self, index, NumRows); + return (data, nulls); + } + + public Array GetNullableColumn(Int32 index) { + var elementType = _columnElementTypes[index]; + var factory = ArrowTableColumnFactory.Of(elementType); + return factory.GetNullableColumn(Self, index, NumRows); + } + + public void ReleaseUnmanagedResources() { + if (!Self.TryRelease(out var old)) { + return; + } + NativeArrowTable.deephaven_client_ArrowTable_dtor(old); + } +} + +internal static class ArrowTableColumnFactory { + private static readonly ColumnFactory[] Factories = { + new ColumnFactory.ForChar(NativeArrowTable.deephaven_client_ArrowTable_GetCharAsInt16Column), + new ColumnFactory.ForOtherValueType(NativeArrowTable.deephaven_client_ArrowTable_GetInt8Column), + new ColumnFactory.ForOtherValueType(NativeArrowTable.deephaven_client_ArrowTable_GetInt16Column), + new ColumnFactory.ForOtherValueType(NativeArrowTable.deephaven_client_ArrowTable_GetInt32Column), + new ColumnFactory.ForOtherValueType(NativeArrowTable.deephaven_client_ArrowTable_GetInt64Column), + new ColumnFactory.ForOtherValueType(NativeArrowTable.deephaven_client_ArrowTable_GetFloatColumn), + new ColumnFactory.ForOtherValueType(NativeArrowTable.deephaven_client_ArrowTable_GetDoubleColumn), + new ColumnFactory.ForBool(NativeArrowTable.deephaven_client_ArrowTable_GetBooleanAsInteropBoolColumn), + new ColumnFactory.ForString(NativeArrowTable.deephaven_client_ArrowTable_GetStringColumn), + new ColumnFactory.ForDateTime(NativeArrowTable.deephaven_client_ArrowTable_GetDateTimeAsInt64Column), + // List - TODO(kosak) + }; + + public static ColumnFactory Of(ElementTypeId typeId) { + return Factories[(int)typeId]; + } +} + +internal partial class NativeArrowTable { + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_ArrowTable_dtor(NativePtr self); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_ArrowTable_GetDimensions( + NativePtr self, out Int32 numColumns, out Int64 numRows, out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_ArrowTable_GetSchema( + NativePtr self, Int32 numColumns, + StringHandle[] columnHandles, + Int32[] columnTypes, + out StringPoolHandle stringPoolHandle, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_ArrowTable_GetInt8Column( + NativePtr self, + Int32 numColumns, + SByte[] data, + InteropBool[]? nullFlags, + Int64 numRows, + out StringPoolHandle stringPoolHandle, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_ArrowTable_GetInt16Column( + NativePtr self, + Int32 numColumns, + Int16[] data, + InteropBool[]? nullFlags, + Int64 numRows, + out StringPoolHandle stringPoolHandle, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_ArrowTable_GetInt32Column( + NativePtr self, + Int32 numColumns, + Int32[] data, + InteropBool[]? nullFlags, + Int64 numRows, + out StringPoolHandle stringPoolHandle, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_ArrowTable_GetInt64Column( + NativePtr self, + Int32 numColumns, + Int64[] data, + InteropBool[]? nullFlags, + Int64 numRows, + out StringPoolHandle stringPoolHandle, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_ArrowTable_GetFloatColumn( + NativePtr self, + Int32 numColumns, + float[] data, + InteropBool[]? nullFlags, + Int64 numRows, + out StringPoolHandle stringPoolHandle, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_ArrowTable_GetDoubleColumn( + NativePtr self, + Int32 numColumns, + double[] data, + InteropBool[]? nullFlags, + Int64 numRows, + out StringPoolHandle stringPoolHandle, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_ArrowTable_GetBooleanAsInteropBoolColumn( + NativePtr self, + Int32 numColumns, + InteropBool[] data, + InteropBool[]? nullFlags, + Int64 numRows, + out StringPoolHandle stringPoolHandle, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_ArrowTable_GetCharAsInt16Column( + NativePtr self, + Int32 numColumns, + Int16[] data, + InteropBool[]? nullFlags, + Int64 numRows, + out StringPoolHandle stringPoolHandle, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_ArrowTable_GetStringColumn( + NativePtr self, + Int32 numColumns, + StringHandle[] data, + InteropBool[]? nullFlags, + Int64 numRows, + out StringPoolHandle stringPoolHandle, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_ArrowTable_GetDateTimeAsInt64Column( + NativePtr self, + Int32 numColumns, + Int64[] data, + InteropBool[]? nullFlags, + Int64 numRows, + out StringPoolHandle stringPoolHandle, + out ErrorStatus status); +} diff --git a/csharp/client/DeephavenClient/Client.cs b/csharp/client/DeephavenClient/Client.cs new file mode 100644 index 00000000000..f4439a1caae --- /dev/null +++ b/csharp/client/DeephavenClient/Client.cs @@ -0,0 +1,64 @@ +using Deephaven.DeephavenClient.Interop; +using System; +using System.Runtime.InteropServices; + +namespace Deephaven.DeephavenClient; + +public class Client : IDisposable { + internal NativePtr Self; + public TableHandleManager Manager; + + public static Client Connect(string target, ClientOptions options) { + NativeClient.deephaven_client_Client_Connect(target, options.Self, out var clientResult, out var status1); + status1.OkOrThrow(); + NativeClient.deephaven_client_Client_GetManager(clientResult, out var managerResult, out var status2); + status2.OkOrThrow(); + var manager = new TableHandleManager(managerResult); + return new Client(clientResult, manager); + } + + private Client(NativePtr self, TableHandleManager manager) { + Self = self; + Manager = manager; + } + + ~Client() { + ReleaseUnmanagedResources(); + } + + public void Close() { + Dispose(); + } + + public void Dispose() { + ReleaseUnmanagedResources(); + GC.SuppressFinalize(this); + } + + private void ReleaseUnmanagedResources() { + if (!Self.TryRelease(out var old)) { + return; + } + NativeClient.deephaven_client_Client_dtor(old); + } +} + +internal partial class NativeClient { + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_Client_Connect(string target, + NativePtr options, + out NativePtr result, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_Client_dtor(NativePtr self); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_Client_Close(NativePtr self, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_Client_GetManager(NativePtr self, + out NativePtr result, + out ErrorStatus status); +} diff --git a/csharp/client/DeephavenClient/ClientOptions.cs b/csharp/client/DeephavenClient/ClientOptions.cs new file mode 100644 index 00000000000..0c3187d96cd --- /dev/null +++ b/csharp/client/DeephavenClient/ClientOptions.cs @@ -0,0 +1,161 @@ +using Deephaven.DeephavenClient.Interop; +using System.Runtime.InteropServices; + +namespace Deephaven.DeephavenClient; + +public class ClientOptions : IDisposable { + internal NativePtr Self; + + public ClientOptions() { + NativeClientOptions.deephaven_client_ClientOptions_ctor(out var result, out var status); + status.OkOrThrow(); + Self = result; + } + + ~ClientOptions() { + ReleaseUnmanagedResources(); + } + + public void Dispose() { + ReleaseUnmanagedResources(); + GC.SuppressFinalize(this); + } + + private void ReleaseUnmanagedResources() { + if (!Self.TryRelease(out var old)) { + return; + } + NativeClientOptions.deephaven_client_ClientOptions_dtor(old); + } + + public ClientOptions SetDefaultAuthentication() { + NativeClientOptions.deephaven_client_ClientOptions_SetDefaultAuthentication(Self, + out var status); + status.OkOrThrow(); + return this; + } + + public ClientOptions SetBasicAuthentication(string username, string password) { + NativeClientOptions.deephaven_client_ClientOptions_SetBasicAuthentication(Self, + username, password, out var status); + status.OkOrThrow(); + return this; + } + + public ClientOptions SetCustomAuthentication(string authenticationKey, string authenticationValue) { + NativeClientOptions.deephaven_client_ClientOptions_SetCustomAuthentication(Self, + authenticationKey, authenticationValue, out var status); + status.OkOrThrow(); + return this; + } + + public ClientOptions SetSessionType(string sessionType) { + NativeClientOptions.deephaven_client_ClientOptions_SetSessionType(Self, sessionType, + out var status); + status.OkOrThrow(); + return this; + } + + public ClientOptions SetUseTls(bool useTls) { + NativeClientOptions.deephaven_client_ClientOptions_SetUseTls(Self, (InteropBool)useTls, + out var status); + status.OkOrThrow(); + return this; + } + + public ClientOptions SetTlsRootCerts(string tlsRootCerts) { + NativeClientOptions.deephaven_client_ClientOptions_SetTlsRootCerts(Self, tlsRootCerts, + out var status); + status.OkOrThrow(); + return this; + } + + public ClientOptions SetClientCertChain(string clientCertChain) { + NativeClientOptions.deephaven_client_ClientOptions_SetClientCertChain(Self, clientCertChain, + out var status); + status.OkOrThrow(); + return this; + } + + public ClientOptions SetClientPrivateKey(string clientPrivateKey) { + NativeClientOptions.deephaven_client_ClientOptions_SetClientPrivateKey(Self, clientPrivateKey, + out var status); + status.OkOrThrow(); + return this; + } + + public ClientOptions AddIntOption(string opt, Int32 val) { + NativeClientOptions.deephaven_client_ClientOptions_AddIntOption(Self, opt, val, + out var status); + status.OkOrThrow(); + return this; + } + + public ClientOptions AddStringOption(string opt, string val) { + NativeClientOptions.deephaven_client_ClientOptions_AddStringOption(Self, opt, val, + out var status); + status.OkOrThrow(); + return this; + } + + public ClientOptions AddExtraHeader(string headerName, string headerValue) { + NativeClientOptions.deephaven_client_ClientOptions_AddExtraHeader(Self, headerName, headerValue, + out var status); + status.OkOrThrow(); + return this; + } +} + + +internal partial class NativeClientOptions { + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_ClientOptions_ctor( + out NativePtr result, out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_ClientOptions_dtor(NativePtr self); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_ClientOptions_SetDefaultAuthentication(NativePtr self, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_ClientOptions_SetBasicAuthentication(NativePtr self, + string username, string password, out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_ClientOptions_SetCustomAuthentication(NativePtr self, + string authentication_key, string authentication_value, out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_ClientOptions_SetSessionType(NativePtr self, + string session_type, out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_ClientOptions_SetUseTls(NativePtr self, + InteropBool use_tls, out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_ClientOptions_SetTlsRootCerts(NativePtr self, + string tls_root_certs, out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_ClientOptions_SetClientCertChain(NativePtr self, + string client_cert_chain, out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_ClientOptions_SetClientPrivateKey(NativePtr self, + string client_private_key, out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_ClientOptions_AddIntOption(NativePtr self, + string opt, Int32 val, out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_ClientOptions_AddStringOption(NativePtr self, + string opt, string val, out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_ClientOptions_AddExtraHeader(NativePtr self, + string header_name, string header_value, out ErrorStatus status); +} diff --git a/csharp/client/DeephavenClient/ClientTable.cs b/csharp/client/DeephavenClient/ClientTable.cs new file mode 100644 index 00000000000..97aa74b4abe --- /dev/null +++ b/csharp/client/DeephavenClient/ClientTable.cs @@ -0,0 +1,226 @@ +using Deephaven.DeephavenClient.Interop; +using System.Runtime.InteropServices; +using Deephaven.DeephavenClient.Utility; + +namespace Deephaven.DeephavenClient; + +public class ClientTable : IDisposable { + internal NativePtr Self; + public readonly Schema Schema; + + public Int32 NumCols => Schema.NumCols; + public Int64 NumRows => Schema.NumRows; + + internal ClientTable(NativePtr self) { + Self = self; + NativeClientTable.deephaven_client_ClientTable_GetDimensions(Self, + out var numColumns, out var numRows, out var status1); + status1.OkOrThrow(); + + var columnNameHandles = new StringHandle[numColumns]; + var elementTypesAsInt = new Int32[numColumns]; + NativeClientTable.deephaven_client_ClientTable_Schema(self, numColumns, columnNameHandles, elementTypesAsInt, + out var stringPoolHandle, out var status2); + status2.OkOrThrow(); + var pool = stringPoolHandle.ExportAndDestroy(); + + var columnNames = columnNameHandles.Select(pool.Get).ToArray(); + Schema = new Schema(columnNames, elementTypesAsInt, numRows); + } + + ~ClientTable() { + ReleaseUnmanagedResources(); + } + + public void Dispose() { + ReleaseUnmanagedResources(); + GC.SuppressFinalize(this); + } + + private void ReleaseUnmanagedResources() { + if (!Self.TryRelease(out var old)) { + return; + } + NativeClientTable.deephaven_client_ClientTable_dtor(old); + } + + public (Array, bool[]) GetColumn(Int32 index) { + var elementType = Schema.Types[index]; + var factory = ClientTableColumnFactory.Of(elementType); + var (data, nulls) = factory.GetColumn(Self, index, Schema.NumRows); + return (data, nulls); + } + + public (Array, bool[]) GetColumn(string name) { + var colIndex = Schema.GetColumnIndex(name); + return GetColumn(colIndex); + } + + public Array GetNullableColumn(Int32 index) { + var elementType = Schema.Types[index]; + var factory = ClientTableColumnFactory.Of(elementType); + return factory.GetNullableColumn(Self, index, Schema.NumRows); + } + + public Array GetNullableColumn(string name) { + var colIndex = Schema.GetColumnIndex(name); + return GetNullableColumn(colIndex); + } + + public string ToString(bool wantHeaders, bool wantLineNumbers) { + NativeClientTable.deephaven_client_ClientTable_ToString(Self, + (InteropBool)wantHeaders, (InteropBool)wantLineNumbers, + out var textHandle, out var poolHandle, out var status); + status.OkOrThrow(); + var pool = poolHandle.ExportAndDestroy(); + return pool.Get(textHandle); + } +} + +internal abstract class ClientTableColumnFactory { + private static readonly ColumnFactory[] Factories = { + new ColumnFactory.ForChar(NativeClientTableHelper.deephaven_client_ClientTableHelper_GetCharAsInt16Column), + new ColumnFactory.ForOtherValueType(NativeClientTableHelper.deephaven_client_ClientTableHelper_GetInt8Column), + new ColumnFactory.ForOtherValueType(NativeClientTableHelper.deephaven_client_ClientTableHelper_GetInt16Column), + new ColumnFactory.ForOtherValueType(NativeClientTableHelper.deephaven_client_ClientTableHelper_GetInt32Column), + new ColumnFactory.ForOtherValueType(NativeClientTableHelper.deephaven_client_ClientTableHelper_GetInt64Column), + new ColumnFactory.ForOtherValueType(NativeClientTableHelper.deephaven_client_ClientTableHelper_GetFloatColumn), + new ColumnFactory.ForOtherValueType(NativeClientTableHelper.deephaven_client_ClientTableHelper_GetDoubleColumn), + new ColumnFactory.ForBool(NativeClientTableHelper.deephaven_client_ClientTableHelper_GetBooleanAsInteropBoolColumn), + new ColumnFactory.ForString(NativeClientTableHelper.deephaven_client_ClientTableHelper_GetStringColumn), + new ColumnFactory.ForDateTime(NativeClientTableHelper.deephaven_client_ClientTableHelper_GetDateTimeAsInt64Column), + // List - TODO(kosak) + }; + + public static ColumnFactory Of(ElementTypeId typeId) { + return Factories[(int)typeId]; + } +} + +internal partial class NativeClientTable { + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_ClientTable_dtor(NativePtr self); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_ClientTable_GetDimensions( + NativePtr self, out Int32 numColumns, out Int64 numWRows, out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_ClientTable_Schema( + NativePtr self, + Int32 numColumns, + StringHandle[] columnHandles, + Int32[] columnTypes, + out StringPoolHandle stringPool, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_ClientTable_ToString( + NativePtr self, + InteropBool wantHeaders, InteropBool wantRowNumbers, + out StringHandle text, + out StringPoolHandle stringPool, + out ErrorStatus status); +} + +internal partial class NativeClientTableHelper { + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_ClientTableHelper_GetInt8Column( + NativePtr self, + Int32 columnIndex, + sbyte[] data, + InteropBool[]? optionalDestNullFlags, + Int64 numRows, + out StringPoolHandle stringPoolHandle, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_ClientTableHelper_GetInt16Column( + NativePtr self, + Int32 columnIndex, + Int16[] data, + InteropBool[]? optionalDestNullFlags, + Int64 numRows, + out StringPoolHandle stringPoolHandle, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_ClientTableHelper_GetInt32Column( + NativePtr self, + Int32 columnIndex, + Int32[] data, + InteropBool[]? optionalDestNullFlags, + Int64 numRows, + out StringPoolHandle stringPoolHandle, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_ClientTableHelper_GetInt64Column( + NativePtr self, + Int32 columnIndex, + Int64[] data, + InteropBool[]? optionalDestNullFlags, + Int64 numRows, + out StringPoolHandle stringPoolHandle, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_ClientTableHelper_GetFloatColumn( + NativePtr self, + Int32 columnIndex, + float[] data, + InteropBool[]? optionalDestNullFlags, + Int64 numRows, + out StringPoolHandle stringPoolHandle, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_ClientTableHelper_GetDoubleColumn( + NativePtr self, + Int32 columnIndex, + double[] data, + InteropBool[]? optionalDestNullFlags, + Int64 numRows, + out StringPoolHandle stringPoolHandle, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_ClientTableHelper_GetBooleanAsInteropBoolColumn( + NativePtr self, + Int32 columnIndex, + InteropBool[] data, + InteropBool[]? optionalDestNullFlags, + Int64 numRows, + out StringPoolHandle stringPoolHandle, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_ClientTableHelper_GetCharAsInt16Column( + NativePtr self, + Int32 columnIndex, + Int16[] data, + InteropBool[]? optionalDestNullFlags, + Int64 numRows, + out StringPoolHandle stringPoolHandle, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_ClientTableHelper_GetStringColumn( + NativePtr self, + Int32 columnIndex, + StringHandle[] data, + InteropBool[]? optionalDestNullFlags, + Int64 numRows, + out StringPoolHandle stringPoolHandle, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_ClientTableHelper_GetDateTimeAsInt64Column( + NativePtr self, + Int32 columnIndex, + Int64[] data, + InteropBool[]? optionalDestNullFlags, + Int64 numRows, + out StringPoolHandle stringPoolHandle, + out ErrorStatus status); +} diff --git a/csharp/client/DeephavenClient/DeephavenClient.csproj b/csharp/client/DeephavenClient/DeephavenClient.csproj new file mode 100644 index 00000000000..707e4dad819 --- /dev/null +++ b/csharp/client/DeephavenClient/DeephavenClient.csproj @@ -0,0 +1,10 @@ + + + + net7.0 + enable + enable + True + + + diff --git a/csharp/client/DeephavenClient/DeephavenClient.sln b/csharp/client/DeephavenClient/DeephavenClient.sln new file mode 100644 index 00000000000..cdbf96e90c3 --- /dev/null +++ b/csharp/client/DeephavenClient/DeephavenClient.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.7.34221.43 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeephavenClient", "DeephavenClient.csproj", "{60349B25-7FF1-477A-807A-EA1CEA019DA1}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {60349B25-7FF1-477A-807A-EA1CEA019DA1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {60349B25-7FF1-477A-807A-EA1CEA019DA1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {60349B25-7FF1-477A-807A-EA1CEA019DA1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {60349B25-7FF1-477A-807A-EA1CEA019DA1}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {DAE1D808-7E3F-4536-9AA5-6D88112C6B3A} + EndGlobalSection +EndGlobal diff --git a/csharp/client/DeephavenClient/DhDateTime.cs b/csharp/client/DeephavenClient/DhDateTime.cs new file mode 100644 index 00000000000..be943b7fd15 --- /dev/null +++ b/csharp/client/DeephavenClient/DhDateTime.cs @@ -0,0 +1,37 @@ +using System; +using Deephaven.DeephavenClient.Utility; + +namespace Deephaven.DeephavenClient; + +/// +/// Deephaven's custom representation of DateTime, with full nanosecond resolution, +/// unlike .NET's own System.DateTime which has 100ns resolution. +/// +public readonly struct DhDateTime { + public static DhDateTime Parse(string date) { + // TODO(kosak): do something about the extra nanosecond resolution + var dt = DateTime.Parse(date).ToUniversalTime(); + var ts = dt - DateTime.UnixEpoch; + + return new DhDateTime((Int64)ts.TotalNanoseconds); + } + + public readonly Int64 Nanos; + + public static DhDateTime FromNanos(Int64 nanos) => new (nanos); + + public DhDateTime(int year, int month, int day, int hour, int minute = 0, int second = 0, int nanos = 0) { + var ts = new DateTime(year, month, day, hour, minute, second) - DateTime.UnixEpoch; + Nanos = (Int64)ts.TotalNanoseconds + nanos; + } + + public DhDateTime(Int64 nanos) { + Nanos = nanos == DeephavenConstants.NullLong ? 0 : nanos; + } + + public DateTime DateTime => DateTime.UnixEpoch.AddTicks(Nanos / 100); + + public override string ToString() { + return $"[Nanos={Nanos}]"; + } +} diff --git a/csharp/client/DeephavenClient/DurationSpecifier.cs b/csharp/client/DeephavenClient/DurationSpecifier.cs new file mode 100644 index 00000000000..5bf1782b67a --- /dev/null +++ b/csharp/client/DeephavenClient/DurationSpecifier.cs @@ -0,0 +1,66 @@ +using System; +using System.Runtime.InteropServices; +using Deephaven.DeephavenClient.Interop; + +namespace Deephaven.DeephavenClient; + +public class DurationSpecifier { + private readonly object _duration; + + public DurationSpecifier(Int64 nanos) => _duration = nanos; + public DurationSpecifier(string duration) => _duration = duration; + + public static implicit operator DurationSpecifier(Int64 nanos) => new (nanos); + public static implicit operator DurationSpecifier(string duration) => new (duration); + public static implicit operator DurationSpecifier(TimeSpan ts) => new((long)(ts.TotalMicroseconds * 1000)); + + internal InternalDurationSpecifier Materialize() => new (_duration); +} + +internal class InternalDurationSpecifier : IDisposable { + internal NativePtr Self; + + public InternalDurationSpecifier(object duration) { + NativePtr result; + ErrorStatus status; + if (duration is Int64 nanos) { + NativeDurationSpecifier.deephaven_client_utility_DurationSpecifier_ctor_nanos(nanos, + out result, out status); + } else if (duration is string dur) { + NativeDurationSpecifier.deephaven_client_utility_DurationSpecifier_ctor_durationstr(dur, + out result, out status); + } else { + throw new ArgumentException($"Unexpected type {duration.GetType().Name} for duration"); + } + status.OkOrThrow(); + Self = result; + } + + ~InternalDurationSpecifier() { + ReleaseUnmanagedResources(); + } + + public void Dispose() { + ReleaseUnmanagedResources(); + GC.SuppressFinalize(this); + } + + private void ReleaseUnmanagedResources() { + if (!Self.TryRelease(out var old)) { + return; + } + NativeDurationSpecifier.deephaven_client_utility_DurationSpecifier_dtor(old); + } +} + + +internal partial class NativeDurationSpecifier { + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_utility_DurationSpecifier_ctor_nanos(Int64 nanos, + out NativePtr result, out ErrorStatus status); + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_utility_DurationSpecifier_ctor_durationstr(string duration, + out NativePtr result, out ErrorStatus status); + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_utility_DurationSpecifier_dtor(NativePtr self); +} diff --git a/csharp/client/DeephavenClient/Interop/InteropSupport.cs b/csharp/client/DeephavenClient/Interop/InteropSupport.cs new file mode 100644 index 00000000000..c703469cfe7 --- /dev/null +++ b/csharp/client/DeephavenClient/Interop/InteropSupport.cs @@ -0,0 +1,152 @@ +using System; +using System.Runtime.InteropServices; +using System.Text; +using System.Text.Unicode; + +namespace Deephaven.DeephavenClient.Interop; + +internal class LibraryPaths { + internal const string Dhcore = "dhcore.dll"; + internal const string Dhclient = "dhclient.dll"; +} + +/// +/// This is simply a wrapper for an IntPtr. Its purpose is to give us more careful type checking. +/// It basically turns IntPtr into a "strong" IntPtr that can only be assigned to IntPtrs of the +/// same type. The T isn't really used otherwise. Note that for correctness, the C++ side needs +/// to receive a struct with the same layout (i.e. a C++ struct containing a single pointer). +/// +[StructLayout(LayoutKind.Sequential)] +public struct NativePtr { + public IntPtr ptr; + + public NativePtr(IntPtr ptr) => this.ptr = ptr; + + public bool TryRelease(out NativePtr oldPtr) { + oldPtr = new NativePtr(ptr); + if (IsNull) { + return false; + } + + ptr = IntPtr.Zero; + return true; + } + + public readonly bool IsNull => ptr == IntPtr.Zero; +} + +/// +/// This is a wrapper for a bool type. It is necessary because the managed and native sides +/// don't agree on a representation for 'bool'. +/// +[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] +public readonly struct InteropBool : IEquatable { + private readonly sbyte _value; + + public InteropBool(bool value) { _value = value ? (sbyte)1 : (sbyte)0; } + + public bool BoolValue => _value != 0; + + public override bool Equals(object? obj) { + return obj is InteropBool o && Equals(o); + } + + public override int GetHashCode() { + return _value.GetHashCode(); + } + + public bool Equals(InteropBool other) { + return _value == other._value; + } + + public static explicit operator bool(InteropBool ib) => ib.BoolValue; + public static explicit operator InteropBool(bool b) => new(b); +} + +[StructLayout(LayoutKind.Sequential)] +public struct StringHandle { + public Int32 Index; +} + +[StructLayout(LayoutKind.Sequential)] +public struct StringPoolHandle { + private NativePtr _nativeStringPool; + public Int32 NumBytes; + public Int32 NumStrings; + + public StringPool ExportAndDestroy() { + if (NumStrings == 0) { + // Optimization: if there are no strings, then there is no pool and there is nothing to destroy. + if (!_nativeStringPool.IsNull) { + throw new Exception("Programming error: 0 strings but non-null _nativeStringPool ptr"); + } + return new StringPool(Array.Empty()); + } + if (!_nativeStringPool.TryRelease(out var old)) { + throw new InvalidOperationException("Can't run ExportAndDestroy twice"); + } + + var bytes = new byte[NumBytes]; + var ends = new Int32[NumStrings]; + var errorCode = NativeStringPool.deephaven_dhcore_interop_StringPool_ExportAndDestroy(old, + bytes, bytes.Length, + ends, ends.Length); + if (errorCode != 0) { + throw new InvalidOperationException( + $"Internal error {errorCode} in deephaven_dhcore_interop_StringPool_ExportAndDestroy"); + } + + var strings = new string[NumStrings]; + for (var i = 0; i != NumStrings; ++i) { + var begin = i == 0 ? 0 : ends[i - 1]; + var end = ends[i]; + strings[i] = Encoding.UTF8.GetString(bytes, begin, end - begin); + } + + return new StringPool(strings); + } +} + +public sealed class StringPool { + public readonly string[] Strings; + + public StringPool(string[] strings) => Strings = strings; + + public string Get(StringHandle handle) { + return Strings[handle.Index]; + } +} + +[StructLayout(LayoutKind.Sequential)] +public struct ErrorStatus { + internal StringHandle StringHandle; + internal StringPoolHandle StringPoolHandle; + + /// + /// OkOrThrow and GetError are destructive and you can only call one of them, once. + /// + public void OkOrThrow() { + var error = GetError(); + if (error != null) { + throw new Exception(error); + } + } + + /// + /// OkOrThrow and GetError are destructive and you can only call one of them, once. + /// + public string? GetError() { + if (StringPoolHandle.NumStrings == 0) { + return null; + } + + return StringPoolHandle.ExportAndDestroy().Get(StringHandle); + } +} + +internal partial class NativeStringPool { + [LibraryImport(LibraryPaths.Dhcore, StringMarshalling = StringMarshalling.Utf8)] + public static partial Int32 deephaven_dhcore_interop_StringPool_ExportAndDestroy(NativePtr self, + byte[] bytes, Int32 bytesLength, + Int32[] ends, Int32 endsLength); +} diff --git a/csharp/client/DeephavenClient/Interop/TestApi/BasicInteropInteractions.cs b/csharp/client/DeephavenClient/Interop/TestApi/BasicInteropInteractions.cs new file mode 100644 index 00000000000..385e36ba006 --- /dev/null +++ b/csharp/client/DeephavenClient/Interop/TestApi/BasicInteropInteractions.cs @@ -0,0 +1,82 @@ +using System; +using System.Runtime.InteropServices; + +namespace Deephaven.DeephavenClient.Interop.TestApi; + +[StructLayout(LayoutKind.Sequential)] +public readonly struct BasicStruct { + public readonly Int32 I; + public readonly double D; + + public BasicStruct() { + } + + public BasicStruct(Int32 i, double d) { + I = i; + D = d; + } + + public BasicStruct Add(BasicStruct other) { + return new BasicStruct(I + other.I, D + other.D); + } +} + +[StructLayout(LayoutKind.Sequential)] +public readonly struct NestedStruct { + public readonly BasicStruct A; + public readonly BasicStruct B; + + public NestedStruct(BasicStruct a, BasicStruct b) { + A = a; + B = b; + } + + public NestedStruct Add(NestedStruct other) { + return new NestedStruct(A.Add(other.A), B.Add(other.B)); + } +} + +public partial class BasicInteropInteractions { + [LibraryImport(LibraryPaths.Dhcore, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_dhcore_interop_testapi_BasicInteropInteractions_Add(Int32 a, Int32 b, out Int32 result); + + [LibraryImport(LibraryPaths.Dhcore, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_dhcore_interop_testapi_BasicInteropInteractions_AddArrays(Int32[] a, Int32[] b, Int32 length, Int32[] result); + + [LibraryImport(LibraryPaths.Dhcore, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_dhcore_interop_testapi_BasicInteropInteractions_Xor(InteropBool a, InteropBool b, out InteropBool result); + + [LibraryImport(LibraryPaths.Dhcore, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_dhcore_interop_testapi_BasicInteropInteractions_XorArrays(InteropBool[] a, InteropBool[] b, Int32 length, InteropBool[] result); + + [LibraryImport(LibraryPaths.Dhcore, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_dhcore_interop_testapi_BasicInteropInteractions_Concat(string a, string b, + out StringHandle resultHandle, out StringPoolHandle resultPoolHandle); + + [LibraryImport(LibraryPaths.Dhcore, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_dhcore_interop_testapi_BasicInteropInteractions_ConcatArrays( + string[] a, + string[] b, + Int32 numItems, + StringHandle[] resultHandles, + out StringPoolHandle resultPoolHandle); + + [LibraryImport(LibraryPaths.Dhcore, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_dhcore_interop_testapi_BasicInteropInteractions_AddBasicStruct( + ref BasicStruct a, ref BasicStruct b, out BasicStruct result); + [LibraryImport(LibraryPaths.Dhcore, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_dhcore_interop_testapi_BasicInteropInteractions_AddBasicStructArrays( + BasicStruct[] a, BasicStruct[] b, Int32 length, BasicStruct[] result); + + [LibraryImport(LibraryPaths.Dhcore, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_dhcore_interop_testapi_BasicInteropInteractions_AddNestedStruct( + ref NestedStruct a, ref NestedStruct b, out NestedStruct result); + + [LibraryImport(LibraryPaths.Dhcore, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_dhcore_interop_testapi_BasicInteropInteractions_AddNestedStructArrays( + NestedStruct[] a, NestedStruct[] b, Int32 length, NestedStruct[] result); + + [LibraryImport(LibraryPaths.Dhcore, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_dhcore_interop_testapi_BasicInteropInteractions_SetErrorIfLessThan( + Int32 a, Int32 b, out ErrorStatus status); +} diff --git a/csharp/client/DeephavenClient/SortPair.cs b/csharp/client/DeephavenClient/SortPair.cs new file mode 100644 index 00000000000..dcd4a318a47 --- /dev/null +++ b/csharp/client/DeephavenClient/SortPair.cs @@ -0,0 +1,25 @@ +namespace Deephaven.DeephavenClient; + +public enum SortDirection { + Ascending = 0, Descending = 1 +}; + +public class SortPair { + public readonly string Column; + public readonly SortDirection Direction; + public readonly bool Abs; + + public static SortPair Ascending(string column, bool abs = false) { + return new SortPair(column, SortDirection.Ascending, abs); + } + + public static SortPair Descending(string column, bool abs = false) { + return new SortPair(column, SortDirection.Descending, abs); + } + + SortPair(string column, SortDirection sortDirection, bool abs) { + Column = column; + Direction = sortDirection; + Abs = abs; + } +} diff --git a/csharp/client/DeephavenClient/TableHandle.cs b/csharp/client/DeephavenClient/TableHandle.cs new file mode 100644 index 00000000000..5df9b414f0b --- /dev/null +++ b/csharp/client/DeephavenClient/TableHandle.cs @@ -0,0 +1,801 @@ +using System.Diagnostics; +using Deephaven.DeephavenClient.Interop; +using System.Runtime.InteropServices; +using Deephaven.DeephavenClient.UpdateBy; +using Deephaven.DeephavenClient.Utility; + +namespace Deephaven.DeephavenClient; + +public sealed class TableHandle : IDisposable { + internal NativePtr Self; + public TableHandleManager Manager; + public Schema Schema; + public Int32 NumCols => Schema.NumCols; + public Int64 NumRows => Schema.NumRows; + public readonly bool IsStatic; + + internal TableHandle(NativePtr self, TableHandleManager manager) { + Self = self; + Manager = manager; + + NativeTableHandle.deephaven_client_TableHandle_GetAttributes(Self, + out var numCols, out var numRows, out InteropBool isStatic, out var status1); + status1.OkOrThrow(); + IsStatic = (bool)isStatic; + + var columnHandles = new StringHandle[numCols]; + var elementTypesAsInt = new Int32[numCols]; + NativeTableHandle.deephaven_client_TableHandle_GetSchema(self, numCols, columnHandles, + elementTypesAsInt, out var stringPoolHandle, out var status2); + status2.OkOrThrow(); + + var pool = stringPoolHandle.ExportAndDestroy(); + var columnNames = columnHandles.Select(pool.Get).ToArray(); + Schema = new Schema(columnNames, elementTypesAsInt, numRows); + } + + ~TableHandle() { + ReleaseUnmanagedResources(); + } + + public void Dispose() { + ReleaseUnmanagedResources(); + GC.SuppressFinalize(this); + } + + public TableHandle Where(string condition) { + NativeTableHandle.deephaven_client_TableHandle_Where(Self, condition, out var result, + out var status); + status.OkOrThrow(); + return new TableHandle(result, Manager); + } + + public TableHandle Sort(params SortPair[] sortPairs) { + var columns = new string[sortPairs.Length]; + var ascendings = new InteropBool[sortPairs.Length]; + var abss = new InteropBool[sortPairs.Length]; + for (var i = 0; i != sortPairs.Length; ++i) { + var sp = sortPairs[i]; + columns[i] = sp.Column; + ascendings[i] = (InteropBool)(sp.Direction == SortDirection.Ascending); + abss[i] = (InteropBool)sp.Abs; + } + NativeTableHandle.deephaven_client_TableHandle_Sort(Self, + columns, ascendings, abss, sortPairs.Length, + out var result, out var status); + status.OkOrThrow(); + return new TableHandle(result, Manager); + } + + public TableHandle Select(params string[] columnSpecs) { + NativeTableHandle.deephaven_client_TableHandle_Select(Self, + columnSpecs, columnSpecs.Length, out var result, out var status); + status.OkOrThrow(); + return new TableHandle(result, Manager); + } + + public TableHandle View(params string[] columnSpecs) { + NativeTableHandle.deephaven_client_TableHandle_View(Self, + columnSpecs, columnSpecs.Length, out var result, out var status); + status.OkOrThrow(); + return new TableHandle(result, Manager); + } + + public TableHandle DropColumns(params string[] columns) { + NativeTableHandle.deephaven_client_TableHandle_DropColumns(Self, + columns, columns.Length, out var result, out var status); + status.OkOrThrow(); + return new TableHandle(result, Manager); + } + + public TableHandle Update(params string[] columnSpecs) { + NativeTableHandle.deephaven_client_TableHandle_Update(Self, columnSpecs, columnSpecs.Length, + out var result, out var status); + status.OkOrThrow(); + return new TableHandle(result, Manager); + } + + public TableHandle LazyUpdate(params string[] columnSpecs) { + NativeTableHandle.deephaven_client_TableHandle_LazyUpdate(Self, columnSpecs, columnSpecs.Length, + out var result, out var status); + status.OkOrThrow(); + return new TableHandle(result, Manager); + } + + public TableHandle SelectDistinct(params string[] columnSpecs) { + NativeTableHandle.deephaven_client_TableHandle_SelectDistinct(Self, + columnSpecs, columnSpecs.Length, out var result, out var status); + status.OkOrThrow(); + return new TableHandle(result, Manager); + } + + public TableHandle Head(Int64 numRows) { + NativeTableHandle.deephaven_client_TableHandle_Head(Self, numRows, + out var result, out var status); + status.OkOrThrow(); + return new TableHandle(result, Manager); + } + + public TableHandle Tail(Int64 numRows) { + NativeTableHandle.deephaven_client_TableHandle_Tail(Self, numRows, + out var result, out var status); + status.OkOrThrow(); + return new TableHandle(result, Manager); + } + + public TableHandle Ungroup(params string[] groupByColumns) { + return Ungroup(false, groupByColumns); + } + + public TableHandle Ungroup(bool nullFill, params string[] groupByColumns) { + NativeTableHandle.deephaven_client_TableHandle_Ungroup(Self, (InteropBool)nullFill, + groupByColumns, groupByColumns.Length, + out var result, out var status); + status.OkOrThrow(); + return new TableHandle(result, Manager); + } + + public TableHandle Merge(string keyColumns, TableHandle[] sources) { + var tableHandlePtrs = sources.Select(s => s.Self).ToArray(); + NativeTableHandle.deephaven_client_TableHandle_Merge(Self, keyColumns, + tableHandlePtrs, tableHandlePtrs.Length, + out var result, out var status); + status.OkOrThrow(); + return new TableHandle(result, Manager); + } + + public TableHandle Merge(TableHandle[] sources) { + return Merge("", sources); + } + + public TableHandle CrossJoin(TableHandle rightSide, string[] columnsToMatch, + string[]? columnsToAdd = null) => + JoinHelper(rightSide, columnsToMatch, columnsToAdd, + NativeTableHandle.deephaven_client_TableHandle_CrossJoin); + + public TableHandle NaturalJoin(TableHandle rightSide, string[] columnsToMatch, + string[]? columnsToAdd = null) => + JoinHelper(rightSide, columnsToMatch, columnsToAdd, + NativeTableHandle.deephaven_client_TableHandle_NaturalJoin); + + public TableHandle LeftOuterJoin(TableHandle rightSide, string[] columnsToMatch, + string[]? columnsToAdd = null) => + JoinHelper(rightSide, columnsToMatch, columnsToAdd, + NativeTableHandle.deephaven_client_TableHandle_LeftOuterJoin); + + public TableHandle ExactJoin(TableHandle rightSide, string[] columnsToMatch, + string[]? columnsToAdd = null) => + JoinHelper(rightSide, columnsToMatch, columnsToAdd, + NativeTableHandle.deephaven_client_TableHandle_ExactJoin); + + public TableHandle UpdateBy(UpdateByOperation[] operations, string[] by) { + var internalOperations = new List(); + foreach (var operation in operations) { + internalOperations.Add(operation.MakeInternal()); + } + + try { + var internalOperationPtrs = internalOperations.Select(s => s.Self).ToArray(); + NativeTableHandle.deephaven_client_TableHandle_UpdateBy(Self, + internalOperationPtrs, internalOperationPtrs.Length, + by, by.Length, + out var result, out var status); + status.OkOrThrow(); + return new TableHandle(result, Manager); + } finally { + foreach (var intOp in internalOperations) { + intOp.Dispose(); + } + } + } + + public TableHandle Aj(TableHandle rightSide, string[] columnsToMatch, + string[]? columnsToAdd = null) => + JoinHelper(rightSide, columnsToMatch, columnsToAdd, + NativeTableHandle.deephaven_client_TableHandle_Aj); + + public TableHandle Raj(TableHandle rightSide, string[] columnsToMatch, + string[]? columnsToAdd = null) => + JoinHelper(rightSide, columnsToMatch, columnsToAdd, + NativeTableHandle.deephaven_client_TableHandle_Raj); + + private delegate void NativeJoinInvoker( + NativePtr self, + NativePtr rightSide, + string[] columnsToMatch, Int32 columnsToMatchLength, + string[] columnsToAdd, Int32 columnsToAddLength, + out NativePtr result, + out ErrorStatus status); + + private TableHandle JoinHelper(TableHandle rightSide, string[] columnsToMatch, + string[]? columnsToAdd, NativeJoinInvoker invoker) { + columnsToAdd ??= Array.Empty(); + invoker(Self, rightSide.Self, columnsToMatch, columnsToMatch.Length, + columnsToAdd, columnsToAdd.Length, + out var result, out var status); + status.OkOrThrow(); + return new TableHandle(result, Manager); + } + + public TableHandle MinBy(params string[] columnSpecs) { + NativeTableHandle.deephaven_client_TableHandle_MinBy(Self, + columnSpecs, columnSpecs.Length, out var result, out var status); + status.OkOrThrow(); + return new TableHandle(result, Manager); + } + + public TableHandle MaxBy(params string[] columnSpecs) { + NativeTableHandle.deephaven_client_TableHandle_MaxBy(Self, + columnSpecs, columnSpecs.Length, out var result, out var status); + status.OkOrThrow(); + return new TableHandle(result, Manager); + } + + public TableHandle SumBy(params string[] columnSpecs) { + NativeTableHandle.deephaven_client_TableHandle_SumBy(Self, + columnSpecs, columnSpecs.Length, out var result, out var status); + status.OkOrThrow(); + return new TableHandle(result, Manager); + } + + public TableHandle AbsSumBy(params string[] columnSpecs) { + NativeTableHandle.deephaven_client_TableHandle_AbsSumBy(Self, + columnSpecs, columnSpecs.Length, out var result, out var status); + status.OkOrThrow(); + return new TableHandle(result, Manager); + } + + public TableHandle VarBy(params string[] columnSpecs) { + NativeTableHandle.deephaven_client_TableHandle_VarBy(Self, + columnSpecs, columnSpecs.Length, out var result, out var status); + status.OkOrThrow(); + return new TableHandle(result, Manager); + } + + public TableHandle StdBy(params string[] columnSpecs) { + NativeTableHandle.deephaven_client_TableHandle_StdBy(Self, + columnSpecs, columnSpecs.Length, out var result, out var status); + status.OkOrThrow(); + return new TableHandle(result, Manager); + } + + public TableHandle AvgBy(params string[] columnSpecs) { + NativeTableHandle.deephaven_client_TableHandle_AvgBy(Self, + columnSpecs, columnSpecs.Length, out var result, out var status); + status.OkOrThrow(); + return new TableHandle(result, Manager); + } + + public TableHandle FirstBy(params string[] columnSpecs) { + NativeTableHandle.deephaven_client_TableHandle_FirstBy(Self, + columnSpecs, columnSpecs.Length, out var result, out var status); + status.OkOrThrow(); + return new TableHandle(result, Manager); + } + + public TableHandle LastBy(params string[] columnSpecs) { + NativeTableHandle.deephaven_client_TableHandle_LastBy(Self, + columnSpecs, columnSpecs.Length, out var result, out var status); + status.OkOrThrow(); + return new TableHandle(result, Manager); + } + + public TableHandle MedianBy(params string[] columnSpecs) { + NativeTableHandle.deephaven_client_TableHandle_MedianBy(Self, + columnSpecs, columnSpecs.Length, out var result, out var status); + status.OkOrThrow(); + return new TableHandle(result, Manager); + } + + public TableHandle PercentileBy(double percentile, bool avgMedian, params string[] columnSpecs) { + NativeTableHandle.deephaven_client_TableHandle_PercentileBy(Self, + percentile, (InteropBool)avgMedian, columnSpecs, columnSpecs.Length, out var result, out var status); + status.OkOrThrow(); + return new TableHandle(result, Manager); + } + + public TableHandle PercentileBy(double percentile, params string[] columnSpecs) { + NativeTableHandle.deephaven_client_TableHandle_PercentileBy(Self, + percentile, columnSpecs, columnSpecs.Length, out var result, out var status); + status.OkOrThrow(); + return new TableHandle(result, Manager); + } + + public TableHandle CountBy(params string[] columnSpecs) { + NativeTableHandle.deephaven_client_TableHandle_CountBy(Self, + columnSpecs, columnSpecs.Length, out var result, out var status); + status.OkOrThrow(); + return new TableHandle(result, Manager); + } + + public TableHandle WAvgBy(params string[] columnSpecs) { + NativeTableHandle.deephaven_client_TableHandle_WAvgBy(Self, + columnSpecs, columnSpecs.Length, out var result, out var status); + status.OkOrThrow(); + return new TableHandle(result, Manager); + } + + public TableHandle TailBy(Int64 n, params string[] columnSpecs) { + NativeTableHandle.deephaven_client_TableHandle_TailBy(Self, + n, columnSpecs, columnSpecs.Length, out var result, out var status); + status.OkOrThrow(); + return new TableHandle(result, Manager); + } + + public TableHandle HeadBy(Int64 n, params string[] columnSpecs) { + NativeTableHandle.deephaven_client_TableHandle_HeadBy(Self, + n, columnSpecs, columnSpecs.Length, out var result, out var status); + status.OkOrThrow(); + return new TableHandle(result, Manager); + } + + public TableHandle WhereIn(TableHandle filterTable, params string[] columnSpecs) { + NativeTableHandle.deephaven_client_TableHandle_WhereIn(Self, + filterTable.Self, columnSpecs, columnSpecs.Length, out var result, out var status); + status.OkOrThrow(); + return new TableHandle(result, Manager); + } + + public void AddTable(TableHandle tableToAdd) { + NativeTableHandle.deephaven_client_TableHandle_AddTable(Self, tableToAdd.Self, out var status); + status.OkOrThrow(); + } + + public void RemoveTable(TableHandle tableToRemove) { + NativeTableHandle.deephaven_client_TableHandle_RemoveTable(Self, tableToRemove.Self, out var status); + status.OkOrThrow(); + } + + public TableHandle By(AggregateCombo combo, params string[] groupByColumns) { + using var comboInternal = combo.Invoke(); + NativeTableHandle.deephaven_client_TableHandle_By(Self, + comboInternal.Self, groupByColumns, groupByColumns.Length, out var result, out var status); + status.OkOrThrow(); + return new TableHandle(result, Manager); + } + + public void BindToVariable(string variable) { + NativeTableHandle.deephaven_client_TableHandle_BindToVariable(Self, variable, out var status); + status.OkOrThrow(); + } + + private class TickingWrapper { + private readonly ITickingCallback _callback; + /// + /// The point of these fields is to keep the delegates cached so they stay reachable + /// (so long as TickingWrapper is itself reachable) and are not garbage collected while + /// native code still holds a pointer to them. + /// + public NativeTableHandle.NativeOnUpdate NativeOnUpdate; + /// + /// The point of these fields is to keep the delegates cached so they stay reachable + /// (so long as TickingWrapper is itself reachable) and are not garbage collected while + /// native code still holds a pointer to them. + /// + public NativeTableHandle.NativeOnFailure NativeOnFailure; + + public TickingWrapper(ITickingCallback callback) { + _callback = callback; + NativeOnUpdate = NativeOnUpdateImpl; + NativeOnFailure = NativeOnFailureImpl; + } + + private void NativeOnUpdateImpl(NativePtr nativeTickingUpdate) { + using var tickingUpdate = new TickingUpdate(nativeTickingUpdate); + try { + _callback.OnTick(tickingUpdate); + } catch (Exception ex) { + _callback.OnFailure(ex.Message); + } + } + + private void NativeOnFailureImpl(StringHandle errorHandle, StringPoolHandle stringPoolHandle) { + var pool = stringPoolHandle.ExportAndDestroy(); + var errorText = pool.Get(errorHandle); + _callback.OnFailure(errorText); + } + } + + public SubscriptionHandle Subscribe(ITickingCallback callback) { + var tw = new TickingWrapper(callback); + NativeTableHandle.deephaven_client_TableHandle_Subscribe(Self, tw.NativeOnUpdate, + tw.NativeOnFailure, out var nativeSusbcriptionHandle, out var status); + status.OkOrThrow(); + var result = new SubscriptionHandle(nativeSusbcriptionHandle); + Manager.AddSubscription(result, tw); + return result; + } + + public void Unsubscribe(SubscriptionHandle handle) { + Manager.RemoveSubscription(handle); + NativeTableHandle.deephaven_client_TableHandle_Unsubscribe(Self, handle.Self, out var status); + status.OkOrThrow(); + handle.Dispose(); + } + + public ArrowTable ToArrowTable() { + NativeTableHandle.deephaven_client_TableHandle_ToArrowTable(Self, out var arrowTable, out var status); + status.OkOrThrow(); + return new ArrowTable(arrowTable); + } + + public ClientTable ToClientTable() { + NativeTableHandle.deephaven_client_TableHandle_ToClientTable(Self, out var clientTable, out var status); + status.OkOrThrow(); + return new ClientTable(clientTable); + } + + public string ToString(bool wantHeaders) { + NativeTableHandle.deephaven_client_TableHandle_ToString(Self, (InteropBool)wantHeaders, out var resultHandle, + out var stringPoolHandle, out var status); + status.OkOrThrow(); + return stringPoolHandle.ExportAndDestroy().Get(resultHandle); + } + + public void Stream(TextWriter textWriter, bool wantHeaders) { + var s = ToString(wantHeaders); + textWriter.Write(s); + } + + private void ReleaseUnmanagedResources() { + if (!Self.TryRelease(out var old)) { + return; + } + NativeTableHandle.deephaven_client_TableHandle_dtor(old); + } +} + +internal partial class NativeTableHandle { + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + internal static partial void deephaven_client_TableHandle_dtor(NativePtr self); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_TableHandle_GetAttributes( + NativePtr self, + out Int32 numColumns, out Int64 numRows, out InteropBool isStatic, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_TableHandle_GetSchema( + NativePtr self, + Int32 numColumns, + StringHandle[] columnHandles, + Int32[] columnTypes, + out StringPoolHandle stringPoolHandle, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + internal static partial void deephaven_client_TableHandle_Where( + NativePtr self, + string condition, + out NativePtr result, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + internal static partial void deephaven_client_TableHandle_Sort( + NativePtr self, + string[] columns, InteropBool[] ascendings, InteropBool[] abss, Int32 numSortPairs, + out NativePtr result, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + internal static partial void deephaven_client_TableHandle_Select( + NativePtr self, + string[] columns, Int32 numColumns, + out NativePtr result, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + internal static partial void deephaven_client_TableHandle_SelectDistinct( + NativePtr self, + string[] columns, Int32 numColumns, + out NativePtr result, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + internal static partial void deephaven_client_TableHandle_View( + NativePtr self, + string[] columns, Int32 numColumns, + out NativePtr clientTable, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + internal static partial void deephaven_client_TableHandle_MinBy( + NativePtr self, + string[] columns, Int32 numColumns, + out NativePtr result, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + internal static partial void deephaven_client_TableHandle_MaxBy( + NativePtr self, + string[] columns, Int32 numColumns, + out NativePtr result, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + internal static partial void deephaven_client_TableHandle_SumBy( + NativePtr self, + string[] columns, Int32 numColumns, + out NativePtr result, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + internal static partial void deephaven_client_TableHandle_AbsSumBy( + NativePtr self, + string[] columns, Int32 numColumns, + out NativePtr result, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + internal static partial void deephaven_client_TableHandle_VarBy( + NativePtr self, + string[] columns, Int32 numColumns, + out NativePtr result, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + internal static partial void deephaven_client_TableHandle_StdBy( + NativePtr self, + string[] columns, Int32 numColumns, + out NativePtr result, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + internal static partial void deephaven_client_TableHandle_AvgBy( + NativePtr self, + string[] columns, Int32 numColumns, + out NativePtr result, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + internal static partial void deephaven_client_TableHandle_FirstBy( + NativePtr self, + string[] columns, Int32 numColumns, + out NativePtr result, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + internal static partial void deephaven_client_TableHandle_LastBy( + NativePtr self, + string[] columns, Int32 numColumns, + out NativePtr result, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + internal static partial void deephaven_client_TableHandle_MedianBy( + NativePtr self, + string[] columns, Int32 numColumns, + out NativePtr result, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + internal static partial void deephaven_client_TableHandle_PercentileBy( + NativePtr self, + double percentile, InteropBool avgMedian, + string[] columns, Int32 numColumns, + out NativePtr result, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + internal static partial void deephaven_client_TableHandle_PercentileBy( + NativePtr self, + double percentile, + string[] columns, Int32 numColumns, + out NativePtr result, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + internal static partial void deephaven_client_TableHandle_CountBy( + NativePtr self, + string[] columns, Int32 numColumns, + out NativePtr result, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + internal static partial void deephaven_client_TableHandle_WAvgBy( + NativePtr self, + string[] columns, Int32 numColumns, + out NativePtr result, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + internal static partial void deephaven_client_TableHandle_TailBy( + NativePtr self, + Int64 n, string[] columns, Int32 numColumns, + out NativePtr result, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + internal static partial void deephaven_client_TableHandle_HeadBy( + NativePtr self, + Int64 n, string[] columns, Int32 numColumns, + out NativePtr result, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + internal static partial void deephaven_client_TableHandle_WhereIn( + NativePtr self, + NativePtr filterTable, + string[] columns, Int32 numColumns, + out NativePtr result, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + internal static partial void deephaven_client_TableHandle_AddTable( + NativePtr self, + NativePtr tableToAdd, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + internal static partial void deephaven_client_TableHandle_RemoveTable( + NativePtr self, + NativePtr tableToRemove, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + internal static partial void deephaven_client_TableHandle_By( + NativePtr self, + NativePtr aggregateCombo, + string[] columns, Int32 numColumns, + out NativePtr result, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + internal static partial void deephaven_client_TableHandle_DropColumns( + NativePtr self, + string[] columns, Int32 numColumns, + out NativePtr clientTable, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + internal static partial void deephaven_client_TableHandle_Update( + NativePtr self, + string[] columns, Int32 numColumns, + out NativePtr result, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + internal static partial void deephaven_client_TableHandle_LazyUpdate( + NativePtr self, + string[] columns, Int32 numColumns, + out NativePtr result, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + internal static partial void deephaven_client_TableHandle_BindToVariable( + NativePtr self, + string variable, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + internal static partial void deephaven_client_TableHandle_ToString( + NativePtr self, + InteropBool wantHeaders, + out StringHandle resulHandle, + out StringPoolHandle stringPoolHandle, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + internal static partial void deephaven_client_TableHandle_ToArrowTable( + NativePtr self, + out NativePtr arrowTable, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + internal static partial void deephaven_client_TableHandle_ToClientTable( + NativePtr self, + out NativePtr clientTable, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + internal static partial void deephaven_client_TableHandle_Head( + NativePtr self, + Int64 numRows, + out NativePtr result, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + internal static partial void deephaven_client_TableHandle_Tail( + NativePtr self, + Int64 numRows, + out NativePtr result, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + internal static partial void deephaven_client_TableHandle_Ungroup( + NativePtr self, + InteropBool nullFill, + string[] groupByColumns, Int32 numGroupByColumns, + out NativePtr result, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + internal static partial void deephaven_client_TableHandle_Merge( + NativePtr self, + string keyColumn, + NativePtr[] tableHandles, Int32 numTableHandles, + out NativePtr result, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + internal static partial void deephaven_client_TableHandle_CrossJoin( + NativePtr self, + NativePtr rightSide, + string[] columnsToMatch, Int32 numColumnsToMatch, + string[] columnsToAdd, Int32 numColumnsToAdd, + out NativePtr result, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + internal static partial void deephaven_client_TableHandle_NaturalJoin( + NativePtr self, + NativePtr rightSide, + string[] columnsToMatch, Int32 numColumnsToMatch, + string[] columnsToAdd, Int32 numColumnsToAdd, + out NativePtr result, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + internal static partial void deephaven_client_TableHandle_LeftOuterJoin( + NativePtr self, + NativePtr rightSide, + string[] columnsToMatch, Int32 numColumnsToMatch, + string[] columnsToAdd, Int32 numColumnsToAdd, + out NativePtr result, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + internal static partial void deephaven_client_TableHandle_ExactJoin( + NativePtr self, + NativePtr rightSide, + string[] columnsToMatch, Int32 numColumnsToMatch, + string[] columnsToAdd, Int32 numColumnsToAdd, + out NativePtr result, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + internal static partial void deephaven_client_TableHandle_UpdateBy( + NativePtr self, + NativePtr[] ops, Int32 numOps, + string[] by, Int32 numBy, + out NativePtr result, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + internal static partial void deephaven_client_TableHandle_Aj( + NativePtr self, + NativePtr rightSide, + string[] columnsToMatch, Int32 numColumnsToMatch, + string[] columnsToAdd, Int32 numColumnsToAdd, + out NativePtr result, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + internal static partial void deephaven_client_TableHandle_Raj( + NativePtr self, + NativePtr rightSide, + string[] columnsToMatch, Int32 numColumnsToMatch, + string[] columnsToAdd, Int32 numColumnsToAdd, + out NativePtr result, + out ErrorStatus status); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Unicode)] + public delegate void NativeOnUpdate(NativePtr tickingUpdate); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Unicode)] + public delegate void NativeOnFailure(StringHandle errorHandle, StringPoolHandle stringPoolHandle); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + internal static partial void deephaven_client_TableHandle_Subscribe( + NativePtr self, + NativeOnUpdate nativeOnUpdate, NativeOnFailure nativeOnFailure, + out NativePtr nativeSubscriptionHandle, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + internal static partial void deephaven_client_TableHandle_Unsubscribe( + NativePtr self, + NativePtr nativeSubscriptionHandle, + out ErrorStatus status); +} diff --git a/csharp/client/DeephavenClient/TableHandleManager.cs b/csharp/client/DeephavenClient/TableHandleManager.cs new file mode 100644 index 00000000000..09af0ba87fc --- /dev/null +++ b/csharp/client/DeephavenClient/TableHandleManager.cs @@ -0,0 +1,104 @@ +using Deephaven.DeephavenClient.Interop; +using System.Runtime.InteropServices; + +namespace Deephaven.DeephavenClient; + +public sealed class TableHandleManager : IDisposable { + internal NativePtr Self; + private readonly Dictionary _subscriptions; + + internal TableHandleManager(NativePtr self) { + Self = self; + _subscriptions = new Dictionary(); + } + + ~TableHandleManager() { + ReleaseUnmanagedResources(); + } + + public void Dispose() { + ReleaseUnmanagedResources(); + GC.SuppressFinalize(this); + } + + private void ReleaseUnmanagedResources() { + if (!Self.TryRelease(out var old)) { + return; + } + NativeTableHandleManager.deephaven_client_TableHandleManager_dtor(old); + } + + public TableHandle EmptyTable(Int64 size) { + NativeTableHandleManager.deephaven_client_TableHandleManager_EmptyTable(Self, size, + out var result, out var status); + status.OkOrThrow(); + return new TableHandle(result, this); + } + + public TableHandle FetchTable(string tableName) { + NativeTableHandleManager.deephaven_client_TableHandleManager_FetchTable(Self, tableName, + out var result, out var status); + status.OkOrThrow(); + return new TableHandle(result, this); + } + + public TableHandle TimeTable(DurationSpecifier period, TimePointSpecifier? startTime = null, + bool blinkTable = false) { + startTime ??= 0; + using var per = period.Materialize(); + using var st = startTime.Materialize(); + NativeTableHandleManager.deephaven_client_TableHandleManager_TimeTable(Self, per.Self, st.Self, + (InteropBool)blinkTable, out var result, out var status); + status.OkOrThrow(); + return new TableHandle(result, this); + } + + public TableHandle InputTable(TableHandle initialTable, params string[] keyColumns) { + NativeTableHandleManager.deephaven_client_TableHandleManager_InputTable(Self, initialTable.Self, + keyColumns, keyColumns.Length, out var result, out var status); + status.OkOrThrow(); + return new TableHandle(result, initialTable.Manager); + } + + public void RunScript(string script) { + NativeTableHandleManager.deephaven_client_TableHandleManager_RunScript(Self, script, + out var status); + status.OkOrThrow(); + } + + internal void AddSubscription(SubscriptionHandle handle, object keepalive) { + _subscriptions.Add(handle, keepalive); + } + + internal void RemoveSubscription(SubscriptionHandle handle) { + _subscriptions.Remove(handle); + } +} + +internal partial class NativeTableHandleManager { + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_TableHandleManager_dtor(NativePtr self); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_TableHandleManager_EmptyTable(NativePtr self, + Int64 size, out NativePtr result, out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_TableHandleManager_FetchTable(NativePtr self, + string tableName, out NativePtr result, out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_TableHandleManager_TimeTable(NativePtr self, + NativePtr period, NativePtr startTime, + InteropBool blinkTable, out NativePtr result, out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_TableHandleManager_InputTable(NativePtr self, + NativePtr initialTable, + string[]keyColumns, Int32 numKeyColumns, + out NativePtr result, out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_TableHandleManager_RunScript(NativePtr self, + string code, out ErrorStatus errorStatus); +} diff --git a/csharp/client/DeephavenClient/Ticking.cs b/csharp/client/DeephavenClient/Ticking.cs new file mode 100644 index 00000000000..64680f2374b --- /dev/null +++ b/csharp/client/DeephavenClient/Ticking.cs @@ -0,0 +1,89 @@ +using Deephaven.DeephavenClient.Interop; +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; + +namespace Deephaven.DeephavenClient; + +public class SubscriptionHandle : IDisposable { + internal NativePtr Self; + + internal SubscriptionHandle(NativePtr self) { + Self = self; + } + + ~SubscriptionHandle() { + ReleaseUnmanagedResources(); + } + + public void Dispose() { + ReleaseUnmanagedResources(); + GC.SuppressFinalize(this); + } + + private void ReleaseUnmanagedResources() { + if (!Self.TryRelease(out var old)) { + return; + } + NativeSubscriptionHandle.deephaven_client_SubscriptionHandle_dtor(old); + } +} + +public class TickingUpdate : IDisposable { + internal NativePtr Self; + + internal TickingUpdate(NativePtr self) => this.Self = self; + + public ClientTable Current { + get { + NativeTickingUpdate.deephaven_client_TickingUpdate_Current(Self, + out var ct, out var status); + status.OkOrThrow(); + return new ClientTable(ct); + } + } + // public ClientTable BeforeRemoves { get; } + // public RowSequence RemovedRows { get; } + + ~TickingUpdate() { + ReleaseUnmanagedResources(); + } + + public void Dispose() { + ReleaseUnmanagedResources(); + GC.SuppressFinalize(this); + } + + public void ReleaseUnmanagedResources() { + if (!Self.TryRelease(out var old)) { + return; + } + NativeTickingUpdate.deephaven_client_TickingUpdate_dtor(old); + } +} + +public interface ITickingCallback { + /** + * Invoked on each update to the subscription. + */ + void OnTick(TickingUpdate update); + + /** + * Invoked if there is an error involving the subscription. + */ + void OnFailure(string errorText); +} + +internal partial class NativeSubscriptionHandle { + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_SubscriptionHandle_dtor(NativePtr self); +} + +internal partial class NativeTickingUpdate { + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_TickingUpdate_dtor(NativePtr self); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_TickingUpdate_Current(NativePtr self, + out NativePtr result, out ErrorStatus status); +} diff --git a/csharp/client/DeephavenClient/TimePointSpecifier.cs b/csharp/client/DeephavenClient/TimePointSpecifier.cs new file mode 100644 index 00000000000..db703cb811c --- /dev/null +++ b/csharp/client/DeephavenClient/TimePointSpecifier.cs @@ -0,0 +1,63 @@ +using Deephaven.DeephavenClient.Interop; +using System.Runtime.InteropServices; + +namespace Deephaven.DeephavenClient; + +public class TimePointSpecifier { + private readonly object _timePoint; + + public TimePointSpecifier(Int64 nanos) => _timePoint = nanos; + public TimePointSpecifier(string timePoint) => _timePoint = timePoint; + + public static implicit operator TimePointSpecifier(Int64 nanos) => new(nanos); + public static implicit operator TimePointSpecifier(string timePoint) => new(timePoint); + + internal InternalTimePointSpecifier Materialize() => new (_timePoint); +} + +internal class InternalTimePointSpecifier : IDisposable { + internal NativePtr Self; + + public InternalTimePointSpecifier(object duration) { + NativePtr result; + ErrorStatus status; + if (duration is Int64 nanos) { + NativeTimePointSpecifier.deephaven_client_utility_TimePointSpecifier_ctor_nanos(nanos, + out result, out status); + } else if (duration is string dur) { + NativeTimePointSpecifier.deephaven_client_utility_TimePointSpecifier_ctor_timepointstr(dur, + out result, out status); + } else { + throw new ArgumentException($"Unexpected type {duration.GetType().Name} for duration"); + } + status.OkOrThrow(); + Self = result; + } + + ~InternalTimePointSpecifier() { + ReleaseUnmanagedResources(); + } + + public void Dispose() { + ReleaseUnmanagedResources(); + GC.SuppressFinalize(this); + } + + private void ReleaseUnmanagedResources() { + if (!Self.TryRelease(out var old)) { + return; + } + NativeTimePointSpecifier.deephaven_client_utility_TimePointSpecifier_dtor(old); + } +} + +internal partial class NativeTimePointSpecifier { + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_utility_TimePointSpecifier_ctor_nanos(Int64 nanos, + out NativePtr result, out ErrorStatus status); + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_utility_TimePointSpecifier_ctor_timepointstr( + string timePointStr, out NativePtr result, out ErrorStatus status); + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_utility_TimePointSpecifier_dtor(NativePtr self); +} diff --git a/csharp/client/DeephavenClient/UpdateBy.cs b/csharp/client/DeephavenClient/UpdateBy.cs new file mode 100644 index 00000000000..ac60590764c --- /dev/null +++ b/csharp/client/DeephavenClient/UpdateBy.cs @@ -0,0 +1,519 @@ +using System; +using System.Runtime.InteropServices; +using Deephaven.DeephavenClient.Interop; + +namespace Deephaven.DeephavenClient.UpdateBy; + +public enum MathContext : Int32 { + Unlimited, Decimal32, Decimal64, Decimal128 +}; + +public enum BadDataBehavior : Int32 { + Reset, Skip, Throw, Poison +}; + +public enum DeltaControl : Int32 { + NullDominates, ValueDominates, ZeroDominates +}; + +public readonly struct OperationControl { + public readonly BadDataBehavior OnNull; + public readonly BadDataBehavior OnNaN; + public readonly MathContext BigValueContext; + + public OperationControl(BadDataBehavior onNull = BadDataBehavior.Skip, + BadDataBehavior onNaN = BadDataBehavior.Skip, + MathContext bigValueContext = MathContext.Decimal128) { + OnNull = onNull; + OnNaN = onNaN; + BigValueContext = bigValueContext; + } +} + +public abstract class UpdateByOperation { + internal abstract InternalUpdateByOperation MakeInternal(); + + private sealed class WithCols : UpdateByOperation { + public delegate void NativeInvoker(string[] cols, Int32 numCols, + out NativePtr result, out ErrorStatus status); + + private readonly string[] _cols; + private readonly NativeInvoker _invoker; + + public WithCols(string[] cols, NativeInvoker invoker) { + _cols = cols; + _invoker = invoker; + } + + internal override InternalUpdateByOperation MakeInternal() { + _invoker(_cols, _cols.Length, out var result, out var status); + status.OkOrThrow(); + return new InternalUpdateByOperation(result); + } + } + + private sealed class WithDelta : UpdateByOperation { + public delegate void NativeInvoker(string[] cols, Int32 numCols, DeltaControl deltaControl, + out NativePtr result, out ErrorStatus status); + + private readonly string[] _cols; + private readonly DeltaControl _deltaControl; + private readonly NativeInvoker _invoker; + + public WithDelta(string[] cols, DeltaControl deltaControl, NativeInvoker invoker) { + _cols = cols; + _deltaControl = deltaControl; + _invoker = invoker; + } + + internal override InternalUpdateByOperation MakeInternal() { + _invoker(_cols, _cols.Length, _deltaControl, out var result, out var status); + status.OkOrThrow(); + return new InternalUpdateByOperation(result); + } + } + + private sealed class WithTicks : UpdateByOperation { + public delegate void NativeInvoker(double decayTicks, string[] cols, Int32 numCols, + ref OperationControl operationControl, + out NativePtr result, out ErrorStatus status); + + private readonly double _decayTicks; + private readonly string[] _cols; + private OperationControl _operationControl; + private readonly NativeInvoker _invoker; + + public WithTicks(double decayTicks, string[] cols, OperationControl? operationControl, + NativeInvoker invoker) { + _decayTicks = decayTicks; + _cols = cols; + _operationControl = operationControl ?? new OperationControl(); + _invoker = invoker; + } + + internal override InternalUpdateByOperation MakeInternal() { + _invoker(_decayTicks, _cols, _cols.Length, ref _operationControl, out var result, out var status); + status.OkOrThrow(); + return new InternalUpdateByOperation(result); + } + } + + private sealed class WithTime : UpdateByOperation { + public delegate void NativeInvoker(string timestampCol, + NativePtr decayTime, string[] cols, Int32 numCols, + ref OperationControl operationControl, + out NativePtr result, out ErrorStatus status); + + private readonly string _timestampCol; + private readonly DurationSpecifier _decayTime; + private readonly string[] _cols; + private OperationControl _operationControl; + private readonly NativeInvoker _invoker; + + public WithTime(string timestampCol, DurationSpecifier decayTime, string[] cols, OperationControl? operationControl, + NativeInvoker invoker) { + _timestampCol = timestampCol; + _decayTime = decayTime; + _cols = cols; + _operationControl = operationControl ?? new OperationControl(); + _invoker = invoker; + } + + internal override InternalUpdateByOperation MakeInternal() { + using var dc = _decayTime.Materialize(); + _invoker(_timestampCol, dc.Self, _cols, _cols.Length, ref _operationControl, out var result, out var status); + status.OkOrThrow(); + return new InternalUpdateByOperation(result); + } + } + + private sealed class WithRollingTicks : UpdateByOperation { + public delegate void NativeInvoker(string[] cols, Int32 numCols, Int32 revTicks, Int32 fwdTicks, + out NativePtr result, out ErrorStatus status); + + private readonly string[] _cols; + private readonly Int32 _revTicks; + private readonly Int32 _fwdTicks; + private readonly NativeInvoker _invoker; + + public WithRollingTicks(string[] cols, Int32 revTicks, Int32 fwdTicks, NativeInvoker invoker) { + _cols = cols; + _revTicks = revTicks; + _fwdTicks = fwdTicks; + _invoker = invoker; + } + + internal override InternalUpdateByOperation MakeInternal() { + _invoker(_cols, _cols.Length, _revTicks, _fwdTicks, out var result, out var status); + status.OkOrThrow(); + return new InternalUpdateByOperation(result); + } + } + + private sealed class WithRollingTime : UpdateByOperation { + public delegate void NativeInvoker(string timestampCol, string[] cols, Int32 numCols, + NativePtr revTime, + NativePtr fwdTime, + out NativePtr result, out ErrorStatus status); + + private readonly string _timestampCol; + private readonly string[] _cols; + private readonly DurationSpecifier _revTime; + private readonly DurationSpecifier _fwdTime; + private readonly NativeInvoker _invoker; + + public WithRollingTime(string timestampCol, string[] cols, + DurationSpecifier revTime, DurationSpecifier? fwdTime, NativeInvoker invoker) { + _timestampCol = timestampCol; + _cols = cols; + _revTime = revTime; + _fwdTime = fwdTime ?? new DurationSpecifier(0); + _invoker = invoker; + } + + internal override InternalUpdateByOperation MakeInternal() { + using var revNative = _revTime.Materialize(); + using var fwdNative = _fwdTime.Materialize(); + _invoker(_timestampCol, _cols, _cols.Length, revNative.Self, fwdNative.Self, + out var result, out var status); + status.OkOrThrow(); + return new InternalUpdateByOperation(result); + } + } + + private sealed class WithRollingWavgTicks : UpdateByOperation { + public delegate void NativeInvoker(string weightCol, string[] cols, Int32 numCols, Int32 revTicks, Int32 fwdTicks, + out NativePtr result, out ErrorStatus status); + + private readonly string _weightCol; + private readonly string[] _cols; + private readonly Int32 _revTicks; + private readonly Int32 _fwdTicks; + private readonly NativeInvoker _invoker; + + public WithRollingWavgTicks(string weightCol, string[] cols, Int32 revTicks, Int32 fwdTicks, NativeInvoker invoker) { + _weightCol = weightCol; + _cols = cols; + _revTicks = revTicks; + _fwdTicks = fwdTicks; + _invoker = invoker; + } + + internal override InternalUpdateByOperation MakeInternal() { + _invoker(_weightCol, _cols, _cols.Length, _revTicks, _fwdTicks, out var result, out var status); + status.OkOrThrow(); + return new InternalUpdateByOperation(result); + } + } + + private sealed class WithRollingWavgTime : UpdateByOperation { + public delegate void NativeInvoker(string timestampCol, string weightCol, string[] cols, Int32 numCols, + NativePtr revTime, + NativePtr fwdTime, + out NativePtr result, out ErrorStatus status); + + private readonly string _timestampCol; + private readonly string _weightCol; + private readonly string[] _cols; + private readonly DurationSpecifier _revTime; + private readonly DurationSpecifier _fwdTime; + private readonly NativeInvoker _invoker; + + public WithRollingWavgTime(string timestampCol, string weightCol, string[] cols, + DurationSpecifier revTime, DurationSpecifier? fwdTime, NativeInvoker invoker) { + _timestampCol = timestampCol; + _weightCol = weightCol; + _cols = cols; + _revTime = revTime; + _fwdTime = fwdTime ?? new DurationSpecifier(0); + _invoker = invoker; + } + + internal override InternalUpdateByOperation MakeInternal() { + using var revNative = _revTime.Materialize(); + using var fwdNative = _fwdTime.Materialize(); + _invoker(_timestampCol, _weightCol, _cols, _cols.Length, revNative.Self, + fwdNative.Self, out var result, out var status); + status.OkOrThrow(); + return new InternalUpdateByOperation(result); + } + } + + public static UpdateByOperation CumSum(string[] cols) => + new WithCols(cols, NativeUpdateByOperation.deephaven_client_update_by_cumSum); + public static UpdateByOperation CumProd(string[] cols) => + new WithCols(cols, NativeUpdateByOperation.deephaven_client_update_by_cumProd); + public static UpdateByOperation CumMin(string[] cols) => + new WithCols(cols, NativeUpdateByOperation.deephaven_client_update_by_cumMin); + public static UpdateByOperation CumMax(string[] cols) => + new WithCols(cols, NativeUpdateByOperation.deephaven_client_update_by_cumMin); + public static UpdateByOperation ForwardFill(string[] cols) => + new WithCols(cols, NativeUpdateByOperation.deephaven_client_update_by_forwardFill); + + public static UpdateByOperation Delta(string[] cols, DeltaControl deltaControl = DeltaControl.NullDominates) => + new WithDelta(cols, deltaControl, + NativeUpdateByOperation.deephaven_client_update_by_delta); + + public static UpdateByOperation EmaTick(double decayTicks, string[] cols, OperationControl? opControl = null) => + new WithTicks(decayTicks, cols, opControl, + NativeUpdateByOperation.deephaven_client_update_by_emaTick); + public static UpdateByOperation EmaTime(string timestampCol, DurationSpecifier decayTime, string[] cols, OperationControl? opControl = null) => + new WithTime(timestampCol, decayTime, cols, opControl, + NativeUpdateByOperation.deephaven_client_update_by_emaTime); + public static UpdateByOperation EmsTick(double decayTicks, string[] cols, OperationControl? opControl = null) => + new WithTicks(decayTicks, cols, opControl, + NativeUpdateByOperation.deephaven_client_update_by_emsTick); + public static UpdateByOperation EmsTime(string timestampCol, DurationSpecifier decayTime, string[] cols, OperationControl? opControl = null) => + new WithTime(timestampCol, decayTime, cols, opControl, + NativeUpdateByOperation.deephaven_client_update_by_emsTime); + public static UpdateByOperation EmminTick(double decayTicks, string[] cols, OperationControl? opControl = null) => + new WithTicks(decayTicks, cols, opControl, + NativeUpdateByOperation.deephaven_client_update_by_emminTick); + public static UpdateByOperation EmminTime(string timestampCol, DurationSpecifier decayTime, string[] cols, OperationControl? opControl = null) => + new WithTime(timestampCol, decayTime, cols, opControl, + NativeUpdateByOperation.deephaven_client_update_by_emminTime); + public static UpdateByOperation EmmaxTick(double decayTicks, string[] cols, OperationControl? opControl = null) => + new WithTicks(decayTicks, cols, opControl, + NativeUpdateByOperation.deephaven_client_update_by_emmaxTick); + public static UpdateByOperation EmmaxTime(string timestampCol, DurationSpecifier decayTime, string[] cols, OperationControl? opControl = null) => + new WithTime(timestampCol, decayTime, cols, opControl, + NativeUpdateByOperation.deephaven_client_update_by_emmaxTime); + public static UpdateByOperation EmstdTick(double decayTicks, string[] cols, OperationControl? opControl = null) => + new WithTicks(decayTicks, cols, opControl, + NativeUpdateByOperation.deephaven_client_update_by_emstdTick); + public static UpdateByOperation EmstdTime(string timestampCol, DurationSpecifier decayTime, string[] cols, OperationControl? opControl = null) => + new WithTime(timestampCol, decayTime, cols, opControl, + NativeUpdateByOperation.deephaven_client_update_by_emstdTime); + + public static UpdateByOperation RollingSumTick(string[] cols, Int32 revTicks, Int32 fwdTicks = 0) => + new WithRollingTicks(cols, revTicks, fwdTicks, + NativeUpdateByOperation.deephaven_client_update_by_rollingSumTick); + public static UpdateByOperation RollingSumTime(string timestampCol, string[] cols, + DurationSpecifier revTime, DurationSpecifier? fwdTime = null) => + new WithRollingTime(timestampCol, cols, revTime, fwdTime, + NativeUpdateByOperation.deephaven_client_update_by_rollingSumTime); + public static UpdateByOperation RollingGroupTick(string[] cols, Int32 revTicks, Int32 fwdTicks = 0) => + new WithRollingTicks(cols, revTicks, fwdTicks, + NativeUpdateByOperation.deephaven_client_update_by_rollingGroupTick); + public static UpdateByOperation RollingGroupTime(string timestampCol, string[] cols, + DurationSpecifier revTime, DurationSpecifier? fwdTime = null) => + new WithRollingTime(timestampCol, cols, revTime, fwdTime, + NativeUpdateByOperation.deephaven_client_update_by_rollingGroupTime); + public static UpdateByOperation RollingAvgTick(string[] cols, Int32 revTicks, Int32 fwdTicks = 0) => + new WithRollingTicks(cols, revTicks, fwdTicks, + NativeUpdateByOperation.deephaven_client_update_by_rollingAvgTick); + public static UpdateByOperation RollingAvgTime(string timestampCol, string[] cols, + DurationSpecifier revTime, DurationSpecifier? fwdTime = null) => + new WithRollingTime(timestampCol, cols, revTime, fwdTime, + NativeUpdateByOperation.deephaven_client_update_by_rollingAvgTime); + public static UpdateByOperation RollingMinTick(string[] cols, Int32 revTicks, Int32 fwdTicks = 0) => + new WithRollingTicks(cols, revTicks, fwdTicks, + NativeUpdateByOperation.deephaven_client_update_by_rollingMinTick); + public static UpdateByOperation RollingMinTime(string timestampCol, string[] cols, + DurationSpecifier revTime, DurationSpecifier? fwdTime = null) => + new WithRollingTime(timestampCol, cols, revTime, fwdTime, + NativeUpdateByOperation.deephaven_client_update_by_rollingMinTime); + public static UpdateByOperation RollingMaxTick(string[] cols, Int32 revTicks, Int32 fwdTicks = 0) => + new WithRollingTicks(cols, revTicks, fwdTicks, + NativeUpdateByOperation.deephaven_client_update_by_rollingMaxTick); + public static UpdateByOperation RollingMaxTime(string timestampCol, string[] cols, + DurationSpecifier revTime, DurationSpecifier? fwdTime = null) => + new WithRollingTime(timestampCol, cols, revTime, fwdTime, + NativeUpdateByOperation.deephaven_client_update_by_rollingMaxTime); + public static UpdateByOperation RollingProdTick(string[] cols, Int32 revTicks, Int32 fwdTicks = 0) => + new WithRollingTicks(cols, revTicks, fwdTicks, + NativeUpdateByOperation.deephaven_client_update_by_rollingProdTick); + public static UpdateByOperation RollingProdTime(string timestampCol, string[] cols, + DurationSpecifier revTime, DurationSpecifier? fwdTime = null) => + new WithRollingTime(timestampCol, cols, revTime, fwdTime, + NativeUpdateByOperation.deephaven_client_update_by_rollingProdTime); + public static UpdateByOperation RollingCountTick(string[] cols, Int32 revTicks, Int32 fwdTicks = 0) => + new WithRollingTicks(cols, revTicks, fwdTicks, + NativeUpdateByOperation.deephaven_client_update_by_rollingCountTick); + public static UpdateByOperation RollingCountTime(string timestampCol, string[] cols, + DurationSpecifier revTime, DurationSpecifier? fwdTime = null) => + new WithRollingTime(timestampCol, cols, revTime, fwdTime, + NativeUpdateByOperation.deephaven_client_update_by_rollingCountTime); + public static UpdateByOperation RollingStdTick(string[] cols, Int32 revTicks, Int32 fwdTicks = 0) => + new WithRollingTicks(cols, revTicks, fwdTicks, + NativeUpdateByOperation.deephaven_client_update_by_rollingStdTick); + public static UpdateByOperation RollingStdTime(string timestampCol, string[] cols, + DurationSpecifier revTime, DurationSpecifier? fwdTime = null) => + new WithRollingTime(timestampCol, cols, revTime, fwdTime, + NativeUpdateByOperation.deephaven_client_update_by_rollingStdTime); + + public static UpdateByOperation RollingWavgTick(string weightCol, string[] cols, + Int32 revTicks, Int32 fwdTicks = 0) => + new WithRollingWavgTicks(weightCol, cols, revTicks, fwdTicks, + NativeUpdateByOperation.deephaven_client_update_by_rollingWavgTick); + public static UpdateByOperation RollingWavgTime(string timestampCol, string weightCol, string[] cols, + DurationSpecifier revTime, DurationSpecifier? fwdTime = null) => + new WithRollingWavgTime(timestampCol, weightCol, cols, revTime, fwdTime, + NativeUpdateByOperation.deephaven_client_update_by_rollingWavgTime); +} + +internal class InternalUpdateByOperation : IDisposable { + internal NativePtr Self; + + internal InternalUpdateByOperation(NativePtr self) => Self = self; + + ~InternalUpdateByOperation() { + ReleaseUnmanagedResources(); + } + + public void Dispose() { + ReleaseUnmanagedResources(); + GC.SuppressFinalize(this); + } + + private void ReleaseUnmanagedResources() { + if (!Self.TryRelease(out var old)) { + return; + } + NativeUpdateByOperation.deephaven_client_UpdateByOperation_dtor(old); + } +} + +internal partial class NativeUpdateByOperation { + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_UpdateByOperation_dtor(NativePtr self); + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_update_by_cumSum(string[] cols, Int32 numCols, + out NativePtr result, out ErrorStatus status); + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_update_by_cumProd(string[] cols, Int32 numCols, + out NativePtr result, out ErrorStatus status); + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_update_by_cumMin(string[] cols, Int32 numCols, + out NativePtr result, out ErrorStatus status); + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_update_by_cumMax(string[] cols, Int32 numCols, + out NativePtr result, out ErrorStatus status); + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_update_by_forwardFill(string[] cols, Int32 numCols, + out NativePtr result, out ErrorStatus status); + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_update_by_delta(string[] cols, Int32 numCols, + DeltaControl deltaControl, out NativePtr result, out ErrorStatus status); + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_update_by_emaTick(double decayTicks, + string[] cols, Int32 numCols, ref OperationControl opControl, + out NativePtr result, out ErrorStatus status); + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_update_by_emaTime(string timestampCol, + NativePtr decayTime, string[] cols, Int32 numCols, + ref OperationControl opControl, out NativePtr result, out ErrorStatus status); + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_update_by_emsTick(double decayTicks, + string[] cols, Int32 numCols, ref OperationControl opControl, + out NativePtr result, out ErrorStatus status); + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_update_by_emsTime(string timestampCol, + NativePtr decayTime, string[] cols, Int32 numCols, + ref OperationControl opControl, out NativePtr result, out ErrorStatus status); + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_update_by_emminTick(double decayTicks, + string[] cols, Int32 numCols, ref OperationControl opControl, + out NativePtr result, out ErrorStatus status); + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_update_by_emminTime(string timestampCol, + NativePtr decayTime, string[] cols, Int32 numCols, + ref OperationControl opControl, out NativePtr result, out ErrorStatus status); + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_update_by_emmaxTick(double decayTicks, + string[] cols, Int32 numCols, ref OperationControl opControl, + out NativePtr result, out ErrorStatus status); + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_update_by_emmaxTime(string timestampCol, + NativePtr decayTime, string[] cols, Int32 numCols, + ref OperationControl opControl, out NativePtr result, out ErrorStatus status); + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_update_by_emstdTick(double decayTicks, + string[] cols, Int32 numCols, ref OperationControl opControl, + out NativePtr result, out ErrorStatus status); + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_update_by_emstdTime(string timestampCol, + NativePtr decayTime, string[] cols, Int32 numCols, + ref OperationControl opControl, out NativePtr result, out ErrorStatus status); + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_update_by_rollingSumTick( + string[] cols, Int32 numCols, Int32 revTicks, Int32 fwdTicks, + out NativePtr result, out ErrorStatus status); + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_update_by_rollingSumTime(string timestampCol, + string[] cols, Int32 numCols, NativePtr revTime, + NativePtr fwdTime, + out NativePtr result, out ErrorStatus status); + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_update_by_rollingGroupTick( + string[] cols, Int32 numCols, Int32 revTicks, Int32 fwdTicks, + out NativePtr result, out ErrorStatus status); + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_update_by_rollingGroupTime(string timestampCol, + string[] cols, Int32 numCols, NativePtr revTime, + NativePtr fwdTime, + out NativePtr result, out ErrorStatus status); + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_update_by_rollingAvgTick( + string[] cols, Int32 numCols, Int32 revTicks, Int32 fwdTicks, + out NativePtr result, out ErrorStatus status); + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_update_by_rollingAvgTime(string timestampCol, + string[] cols, Int32 numCols, NativePtr revTime, + NativePtr fwdTime, + out NativePtr result, out ErrorStatus status); + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_update_by_rollingMinTick( + string[] cols, Int32 numCols, Int32 revTicks, Int32 fwdTicks, + out NativePtr result, out ErrorStatus status); + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_update_by_rollingMinTime(string timestampCol, + string[] cols, Int32 numCols, NativePtr revTime, + NativePtr fwdTime, + out NativePtr result, out ErrorStatus status); + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_update_by_rollingMaxTick( + string[] cols, Int32 numCols, Int32 revTicks, Int32 fwdTicks, + out NativePtr result, out ErrorStatus status); + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_update_by_rollingMaxTime(string timestampCol, + string[] cols, Int32 numCols, NativePtr revTime, + NativePtr fwdTime, + out NativePtr result, out ErrorStatus status); + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_update_by_rollingProdTick( + string[] cols, Int32 numCols, Int32 revTicks, Int32 fwdTicks, + out NativePtr result, out ErrorStatus status); + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_update_by_rollingProdTime(string timestampCol, + string[] cols, Int32 numCols, NativePtr revTime, + NativePtr fwdTime, + out NativePtr result, out ErrorStatus status); + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_update_by_rollingCountTick( + string[] cols, Int32 numCols, Int32 revTicks, Int32 fwdTicks, + out NativePtr result, out ErrorStatus status); + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_update_by_rollingCountTime(string timestampCol, + string[] cols, Int32 numCols, NativePtr revTime, + NativePtr fwdTime, + out NativePtr result, out ErrorStatus status); + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_update_by_rollingStdTick( + string[] cols, Int32 numCols, Int32 revTicks, Int32 fwdTicks, + out NativePtr result, out ErrorStatus status); + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_update_by_rollingStdTime(string timestampCol, + string[] cols, Int32 numCols, NativePtr revTime, + NativePtr fwdTime, + out NativePtr result, out ErrorStatus status); + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_update_by_rollingWavgTick( + string weightCol, string[] cols, Int32 numCols, Int32 revTicks, Int32 fwdTicks, + out NativePtr result, out ErrorStatus status); + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + public static partial void deephaven_client_update_by_rollingWavgTime(string timestampCol, + string weightCol, string[] cols, Int32 numCols, NativePtr revTime, + NativePtr fwdTime, + out NativePtr result, out ErrorStatus status); +} diff --git a/csharp/client/DeephavenClient/Utility/ColumnFactory.cs b/csharp/client/DeephavenClient/Utility/ColumnFactory.cs new file mode 100644 index 00000000000..18c25a04530 --- /dev/null +++ b/csharp/client/DeephavenClient/Utility/ColumnFactory.cs @@ -0,0 +1,129 @@ +using Deephaven.DeephavenClient.Interop; + +namespace Deephaven.DeephavenClient.Utility; + +internal abstract class ColumnFactory { + public abstract (Array, bool[]) GetColumn(NativePtr table, Int32 columnIndex, + Int64 numRows); + + public abstract Array GetNullableColumn(NativePtr table, Int32 columnIndex, + Int64 numRows); + + public delegate void NativeImpl(NativePtr table, Int32 columnIndex, + T[] data, InteropBool[]? nullFlags, Int64 numRows, out StringPoolHandle stringPoolHandle, out ErrorStatus status); + + public abstract class ForType : ColumnFactory { + private readonly NativeImpl _nativeImpl; + + protected ForType(NativeImpl nativeImpl) => _nativeImpl = nativeImpl; + + public sealed override (Array, bool[]) GetColumn(NativePtr table, Int32 columnIndex, + Int64 numRows) { + return GetColumnInternal(table, columnIndex, numRows); + } + + protected (TTarget[], bool[]) GetColumnInternal(NativePtr table, Int32 columnIndex, + Int64 numRows) { + var intermediate = new TNative[numRows]; + var interopNulls = new InteropBool[numRows]; + _nativeImpl(table, columnIndex, intermediate, interopNulls, numRows, out var stringPoolHandle, out var errorStatus); + errorStatus.OkOrThrow(); + var pool = stringPoolHandle.ExportAndDestroy(); + var nulls = new bool[numRows]; + for (Int64 i = 0; i < numRows; ++i) { + nulls[i] = (bool)interopNulls[i]; + } + + var data = ConvertNativeToTarget(intermediate, nulls, pool); + return (data, nulls); + } + + protected abstract TTarget[] ConvertNativeToTarget(TNative[] native, bool[] nulls, StringPool pool); + } + + public sealed class ForString : ForType { + public ForString(NativeImpl nativeImpl) : base(nativeImpl) { + } + + public override Array GetNullableColumn(NativePtr table, int columnIndex, long numRows) { + // string is a reference type so there's no such thing as a Nullable. + // For the case of string, the return value is the same as GetColumn(). + return GetColumn(table, columnIndex, numRows).Item1; + } + + protected override string?[] ConvertNativeToTarget(StringHandle[] native, bool[] nulls, StringPool pool) { + var result = new string?[native.Length]; + for (Int64 i = 0; i != native.Length; ++i) { + result[i] = nulls[i] ? null : pool.Get(native[i]); + } + + return result; + } + } + + public abstract class ForValueTypes : ForType + where TTarget : struct where TNative : struct { + protected ForValueTypes(NativeImpl nativeImpl) : base(nativeImpl) { + } + + public sealed override Array GetNullableColumn(NativePtr table, int columnIndex, long numRows) { + var (data, nulls) = GetColumnInternal(table, columnIndex, numRows); + var result = new TTarget?[numRows]; + for (var i = 0; i != numRows; ++i) { + if (!nulls[i]) { + result[i] = data[i]; + } + } + return result; + } + } + + public sealed class ForChar : ForValueTypes { + public ForChar(NativeImpl nativeImpl) : base(nativeImpl) { + } + + protected override char[] ConvertNativeToTarget(Int16[] native, bool[] nulls, StringPool pool) { + var result = new char[native.Length]; + for (var i = 0; i != native.Length; ++i) { + result[i] = (char)native[i]; + } + return result; + } + } + + public sealed class ForBool : ForValueTypes { + public ForBool(NativeImpl nativeImpl) : base(nativeImpl) { + } + + protected override bool[] ConvertNativeToTarget(InteropBool[] native, bool[] nulls, StringPool pool) { + var result = new bool[native.Length]; + for (var i = 0; i != native.Length; ++i) { + result[i] = (bool)native[i]; + } + return result; + } + + } + + public sealed class ForDateTime : ForValueTypes { + public ForDateTime(NativeImpl nativeImpl) : base(nativeImpl) { + } + + protected override DhDateTime[] ConvertNativeToTarget(Int64[] native, bool[] nulls, StringPool pool) { + var result = new DhDateTime[native.Length]; + for (var i = 0; i != native.Length; ++i) { + result[i] = nulls[i] ? new DhDateTime(0) : new DhDateTime(native[i]); + } + return result; + } + } + + public sealed class ForOtherValueType : ForValueTypes where T : struct { + public ForOtherValueType(NativeImpl nativeImpl) : base(nativeImpl) { + } + + protected override T[] ConvertNativeToTarget(T[] native, bool[] nulls, StringPool pool) { + return native; + } + } +} diff --git a/csharp/client/DeephavenClient/Utility/DeephavenConstants.cs b/csharp/client/DeephavenClient/Utility/DeephavenConstants.cs new file mode 100644 index 00000000000..7b64d53edc4 --- /dev/null +++ b/csharp/client/DeephavenClient/Utility/DeephavenConstants.cs @@ -0,0 +1,5 @@ +namespace Deephaven.DeephavenClient.Utility { + public class DeephavenConstants { + public const Int64 NullLong = Int64.MinValue; + } +} diff --git a/csharp/client/DeephavenClient/Utility/Schema.cs b/csharp/client/DeephavenClient/Utility/Schema.cs new file mode 100644 index 00000000000..f9a46c4d0b2 --- /dev/null +++ b/csharp/client/DeephavenClient/Utility/Schema.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Deephaven.DeephavenClient.Utility; + +/** + * These values need to be kept in sync with the corresponding values on the C++ side. + */ +internal enum ElementTypeId { + Char, + Int8, + Int16, + Int32, + Int64, + Float, + Double, + Bool, + String, + Timestamp, + List +}; + +public class Schema { + public Int32 NumCols => Names.Length; + public Int64 NumRows; + public string[] Names { get; } + internal ElementTypeId[] Types { get; } + private readonly Dictionary _nameToIndex; + + internal Schema(string[] names, int[] elementTypesAsInt, Int64 numRows) { + if (names.Length != elementTypesAsInt.Length) { + throw new ArgumentException($"names.Length ({names.Length}) != types.Length({elementTypesAsInt.Length})"); + } + Names = names; + Types = elementTypesAsInt.Select(elt => (ElementTypeId)elt).ToArray(); + _nameToIndex = Names.Select((name, idx) => new { name, idx }) + .ToDictionary(elt => elt.name, elt => elt.idx); + NumRows = numRows; + } + + public Int32 GetColumnIndex(string name) { + if (TryGetColumnIndex(name, out var result)) { + return result; + } + + throw new ArgumentException($"""Column name "{name}" not found"""); + } + + public bool TryGetColumnIndex(string name, out Int32 result) { + return _nameToIndex.TryGetValue(name, out result); + } +} diff --git a/csharp/client/DeephavenClient/Utility/TableMaker.cs b/csharp/client/DeephavenClient/Utility/TableMaker.cs new file mode 100644 index 00000000000..1c094fbb07a --- /dev/null +++ b/csharp/client/DeephavenClient/Utility/TableMaker.cs @@ -0,0 +1,338 @@ +using System.Buffers; +using Deephaven.DeephavenClient.Interop; +using System.Runtime.InteropServices; + +namespace Deephaven.DeephavenClient.Utility; + +public class TableMaker : IDisposable { + internal NativePtr Self; + + public TableMaker() { + NativeTableMaker.deephaven_dhclient_utility_TableMaker_ctor(out Self, out var status); + status.OkOrThrow(); + } + + ~TableMaker() { + ReleaseUnmanagedResources(); + } + + public void Dispose() { + ReleaseUnmanagedResources(); + GC.SuppressFinalize(this); + } + + private void ReleaseUnmanagedResources() { + if (!Self.TryRelease(out var old)) { + return; + } + NativeTableMaker.deephaven_dhclient_utility_TableMaker_dtor(old); + } + + public void AddColumn(string name, IList column) { + var array = column.ToArray(); + var myVisitor = new MyVisitor(this, name); + ArrayDispatcher.AcceptVisitor(myVisitor, array); + } + + public TableHandle MakeTable(TableHandleManager manager) { + NativeTableMaker.deephaven_dhclient_utility_TableMaker_MakeTable(Self, manager.Self, out var result, out var status); + status.OkOrThrow(); + return new TableHandle(result, manager); + } + + private static class ArrayDispatcher { + public static void AcceptVisitor(IArrayVisitor visitor, T[] array) { + // TODO: make this faster + if (array is char[] chars) { + visitor.Visit(chars, null); + return; + } + + if (array is char?[] optionalChars) { + ConvertOptional(optionalChars, out var data, out var nulls); + visitor.Visit(data, nulls); + return; + } + + if (array is sbyte[] sbytes) { + visitor.Visit(sbytes, null); + return; + } + + if (array is sbyte?[] optionalSbytes) { + ConvertOptional(optionalSbytes, out var data, out var nulls); + visitor.Visit(data, nulls); + return; + } + + if (array is Int16[] int16s) { + visitor.Visit(int16s, null); + return; + } + + if (array is Int16?[] optionalInt16s) { + ConvertOptional(optionalInt16s, out var data, out var nulls); + visitor.Visit(data, nulls); + return; + } + + if (array is Int32[] int32s) { + visitor.Visit(int32s, null); + return; + } + + if (array is Int32?[] optionalInt32s) { + ConvertOptional(optionalInt32s, out var data, out var nulls); + visitor.Visit(data, nulls); + return; + } + + if (array is Int64[] int64s) { + visitor.Visit(int64s, null); + return; + } + + if (array is Int64?[] optionalInt64s) { + ConvertOptional(optionalInt64s, out var data, out var nulls); + visitor.Visit(data, nulls); + return; + } + + if (array is float[] floats) { + visitor.Visit(floats, null); + return; + } + + if (array is float?[] optionalFloats) { + ConvertOptional(optionalFloats, out var data, out var nulls); + visitor.Visit(data, nulls); + return; + } + + if (array is double[] doubles) { + visitor.Visit(doubles, null); + return; + } + + if (array is float?[] optionalDoubles) { + ConvertOptional(optionalDoubles, out var data, out var nulls); + visitor.Visit(data, nulls); + return; + } + + if (array is bool[] bools) { + visitor.Visit(bools, null); + return; + } + + if (array is bool?[] optionalBools) { + ConvertOptional(optionalBools, out var data, out var nulls); + visitor.Visit(data, nulls); + return; + } + + if (array is DhDateTime[] datetimes) { + visitor.Visit(datetimes, null); + return; + } + + if (array is DhDateTime?[] optionalDateTimes) { + ConvertOptional(optionalDateTimes, out var data, out var nulls); + visitor.Visit(data, nulls); + return; + } + + if (array is string[] strings) { + visitor.Visit(strings); + return; + } + + throw new ArgumentException($"Don't know how to handle type {array.GetType().Name}"); + } + + private static void ConvertOptional(T?[] input, out T[] data, out InteropBool[] nulls) where T : struct { + data = new T[input.Length]; + nulls = new InteropBool[input.Length]; + for (var i = 0; i != input.Length; ++i) { + if (input[i].HasValue) { + data[i] = input[i]!.Value; + } else { + nulls[i] = (InteropBool)true; + } + } + } + + } + + // put this somewhere + private interface IArrayVisitor { + public void Visit(char[] array, InteropBool[]? nulls); + public void Visit(sbyte[] array, InteropBool[]? nulls); + public void Visit(Int16[] array, InteropBool[]? nulls); + public void Visit(Int32[] array, InteropBool[]? nulls); + public void Visit(Int64[] array, InteropBool[]? nulls); + public void Visit(float[] array, InteropBool[]? nulls); + public void Visit(double[] array, InteropBool[]? nulls); + public void Visit(bool[] array, InteropBool[]? nulls); + public void Visit(DhDateTime[] array, InteropBool[]? nulls); + // No nulls array because string is a reference type + public void Visit(string[] array); + } + + private class MyVisitor : IArrayVisitor { + private readonly TableMaker _owner; + private readonly string _name; + + public MyVisitor(TableMaker owner, string name) { + _owner = owner; + _name = name; + } + + public void Visit(char[] array, InteropBool[]? nulls) { + var nativeData = new Int16[array.Length]; + for (var i = 0; i != array.Length; ++i) { + nativeData[i] = (Int16)array[i]; + } + + NativeTableMaker.deephaven_dhclient_utility_TableMaker_AddColumn__CharAsInt16( + _owner.Self, _name, nativeData, nativeData.Length, nulls, out var status); + status.OkOrThrow(); + } + + public void Visit(sbyte[] array, InteropBool[]? nulls) { + NativeTableMaker.deephaven_dhclient_utility_TableMaker_AddColumn__Int8( + _owner.Self, _name, array, array.Length, nulls, out var status); + status.OkOrThrow(); + } + + public void Visit(Int16[] array, InteropBool[]? nulls) { + NativeTableMaker.deephaven_dhclient_utility_TableMaker_AddColumn__Int16( + _owner.Self, _name, array, array.Length, nulls, out var status); + status.OkOrThrow(); + } + + public void Visit(Int32[] array, InteropBool[]? nulls) { + NativeTableMaker.deephaven_dhclient_utility_TableMaker_AddColumn__Int32( + _owner.Self, _name, array, array.Length, nulls, out var status); + status.OkOrThrow(); + } + + public void Visit(Int64[] array, InteropBool[]? nulls) { + NativeTableMaker.deephaven_dhclient_utility_TableMaker_AddColumn__Int64( + _owner.Self, _name, array, array.Length, nulls, out var status); + status.OkOrThrow(); + } + + public void Visit(float[] array, InteropBool[]? nulls) { + NativeTableMaker.deephaven_dhclient_utility_TableMaker_AddColumn__Float( + _owner.Self, _name, array, array.Length, nulls, out var status); + status.OkOrThrow(); + } + + public void Visit(double[] array, InteropBool[]? nulls) { + NativeTableMaker.deephaven_dhclient_utility_TableMaker_AddColumn__Double( + _owner.Self, _name, array, array.Length, nulls, out var status); + status.OkOrThrow(); + } + + public void Visit(bool[] array, InteropBool[]? nulls) { + var nativeData = new InteropBool[array.Length]; + for (var i = 0; i != array.Length; ++i) { + nativeData[i] = (InteropBool)array[i]; + } + + NativeTableMaker.deephaven_dhclient_utility_TableMaker_AddColumn__BoolAsInteropBool( + _owner.Self, _name, nativeData, nativeData.Length, nulls, out var status); + status.OkOrThrow(); + } + + public void Visit(string?[] array) { + var nulls = new InteropBool[array.Length]; + for (var i = 0; i != array.Length; ++i) { + nulls[i] = (InteropBool)(array[i] == null); + } + + NativeTableMaker.deephaven_dhclient_utility_TableMaker_AddColumn__String( + _owner.Self, _name, array, array.Length, nulls, out var status); + status.OkOrThrow(); + } + + public void Visit(DhDateTime[] array, InteropBool[]? nulls) { + var nativeData = new Int64[array.Length]; + for (var i = 0; i != array.Length; ++i) { + nativeData[i] = array[i].Nanos; + } + + NativeTableMaker.deephaven_dhclient_utility_TableMaker_AddColumn__DateTimeAsInt64( + _owner.Self, _name, nativeData, nativeData.Length, nulls, out var status); + status.OkOrThrow(); + } + } +} + +internal partial class NativeTableMaker { + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + internal static partial void deephaven_dhclient_utility_TableMaker_ctor(out NativePtr result, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + internal static partial void deephaven_dhclient_utility_TableMaker_dtor(NativePtr self); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + internal static partial void deephaven_dhclient_utility_TableMaker_MakeTable(NativePtr self, + NativePtr manager, + out NativePtr result, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + internal static partial void deephaven_dhclient_utility_TableMaker_AddColumn__CharAsInt16( + NativePtr self, + string name, Int16[] data, Int32 length, InteropBool[]? nulls, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + internal static partial void deephaven_dhclient_utility_TableMaker_AddColumn__Int8( + NativePtr self, + string name, sbyte[] data, Int32 length, InteropBool[]? nulls, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + internal static partial void deephaven_dhclient_utility_TableMaker_AddColumn__Int16(NativePtr self, + string name, Int16[] data, Int32 length, InteropBool[]? nulls, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + internal static partial void deephaven_dhclient_utility_TableMaker_AddColumn__Int32(NativePtr self, + string name, Int32[] data, Int32 length, InteropBool[]? nulls, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + internal static partial void deephaven_dhclient_utility_TableMaker_AddColumn__Int64(NativePtr self, + string name, Int64[] data, Int32 length, InteropBool[]? nulls, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + internal static partial void deephaven_dhclient_utility_TableMaker_AddColumn__Float(NativePtr self, + string name, float[] data, Int32 length, InteropBool[]? nulls, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + internal static partial void deephaven_dhclient_utility_TableMaker_AddColumn__Double(NativePtr self, + string name, double[] data, Int32 length, InteropBool[]? nulls, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + internal static partial void deephaven_dhclient_utility_TableMaker_AddColumn__BoolAsInteropBool(NativePtr self, + string name, InteropBool[] data, Int32 length, InteropBool[]? nulls, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + internal static partial void deephaven_dhclient_utility_TableMaker_AddColumn__DateTimeAsInt64(NativePtr self, + string name, Int64[] data, Int32 length, InteropBool[]? nulls, + out ErrorStatus status); + + [LibraryImport(LibraryPaths.Dhclient, StringMarshalling = StringMarshalling.Utf8)] + internal static partial void deephaven_dhclient_utility_TableMaker_AddColumn__String(NativePtr self, + string name, string?[] data, Int32 length, InteropBool[]? nulls, + out ErrorStatus status); +} diff --git a/csharp/client/DhClientTests/AddDropTest.cs b/csharp/client/DhClientTests/AddDropTest.cs new file mode 100644 index 00000000000..9f53335bfa8 --- /dev/null +++ b/csharp/client/DhClientTests/AddDropTest.cs @@ -0,0 +1,32 @@ +using Deephaven.DeephavenClient.Interop; +using Deephaven.DeephavenClient; +using Xunit.Abstractions; + +namespace Deephaven.DhClientTests; + +public class AddDropTest { + private readonly ITestOutputHelper _output; + + public AddDropTest(ITestOutputHelper output) { + _output = output; + } + + [Fact] + public void TestDropSomeColumns() { + using var ctx = CommonContextForTests.Create(new ClientOptions()); + var table = ctx.TestTable; + + var t = table.Update("II = ii").Where("Ticker == `AAPL`"); + var cn = ctx.ColumnNames; + var t2 = t.DropColumns(cn.ImportDate, cn.Ticker, cn.Open, cn.Close); + _output.WriteLine(t2.ToString(true)); + + var volData = new Int64[] { 100000, 250000, 19000 }; + var iiData = new Int64[] { 5, 6, 7 }; + + var tc = new TableComparer(); + tc.AddColumn("Volume", volData); + tc.AddColumn("II", iiData); + tc.AssertEqualTo(t2); + } +} diff --git a/csharp/client/DhClientTests/AggregatesTest.cs b/csharp/client/DhClientTests/AggregatesTest.cs new file mode 100644 index 00000000000..e0e69db98b5 --- /dev/null +++ b/csharp/client/DhClientTests/AggregatesTest.cs @@ -0,0 +1,46 @@ +using Deephaven.DeephavenClient.Interop; +using Deephaven.DeephavenClient; +using Xunit.Abstractions; + +namespace Deephaven.DhClientTests; + +public class AggregatesTest { + private readonly ITestOutputHelper _output; + + public AggregatesTest(ITestOutputHelper output) { + _output = output; + } + + [Fact] + public void TestVariousAggregates() { + using var ctx = CommonContextForTests.Create(new ClientOptions()); + var table = ctx.TestTable; + + table = table.Where("ImportDate == `2017-11-01`"); + var zngaTable = table.Where("Ticker == `ZNGA`"); + + var aggTable = zngaTable.View("Close") + .By(new AggregateCombo(new[] { + Aggregate.Avg(new []{"AvgClose=Close"}), + Aggregate.Sum(new []{"SumClose=Close"}), + Aggregate.Min(new [] {"MinClose=Close"}), + Aggregate.Max(new []{"MaxClose=Close"}), + Aggregate.Count("Count") + })); + + var tickerData = new[]{ "AAPL", "AAPL", "AAPL"}; + var avgCloseData = new[] { 541.55 }; + var sumCloseData = new[] { 1083.1 }; + var minCloseData = new[] { 538.2 }; + var maxCloseData = new[] { 544.9 }; + var countData = new Int64[] { 2 }; + + var tc = new TableComparer(); + tc.AddColumn("AvgClose", avgCloseData); + tc.AddColumn("SumClose", sumCloseData); + tc.AddColumn("MinClose", minCloseData); + tc.AddColumn("MaxClose", maxCloseData); + tc.AddColumn("Count", countData); + tc.AssertEqualTo(aggTable); + } +} diff --git a/csharp/client/DhClientTests/BasicInteropInteractionsTest.cs b/csharp/client/DhClientTests/BasicInteropInteractionsTest.cs new file mode 100644 index 00000000000..27cd2d1e1e0 --- /dev/null +++ b/csharp/client/DhClientTests/BasicInteropInteractionsTest.cs @@ -0,0 +1,172 @@ +using Deephaven.DeephavenClient.Interop; +using Deephaven.DeephavenClient.Interop.TestApi; + +namespace Deephaven.DhClientTests; + +public class BasicInteropInteractionsTest { + [Fact] + public void TestInAndOutPrimitives() { + BasicInteropInteractions.deephaven_dhcore_interop_testapi_BasicInteropInteractions_Add(3, 4, out var result); + Assert.Equal(7, result); + } + + [Fact] + public void TestInAndOutPrimitiveArrays() { + const int length = 3; + var a = new Int32[length] { 10, 20, 30 }; + var b = new Int32[length] { -5, 10, -15 }; + var actualResult = new Int32[length]; + BasicInteropInteractions.deephaven_dhcore_interop_testapi_BasicInteropInteractions_AddArrays(a, b, + length, actualResult); + var expectedResult = new Int32[length] { 5, 30, 15 }; + Assert.Equal(expectedResult, actualResult); + } + + [Fact] + public void TestInAndOutBooleans() { + foreach (var a in new[] { false, true }) { + foreach (var b in new[] { false, true }) { + BasicInteropInteractions.deephaven_dhcore_interop_testapi_BasicInteropInteractions_Xor((InteropBool)a, + (InteropBool)b, out var actualResult); + var expectedResult = a ^ b; + Assert.Equal(expectedResult, (bool)actualResult); + } + } + } + + [Fact] + public void TestInAndOutBooleanArrays() { + var aList = new List(); + var bList = new List(); + var expectedResult = new List(); + foreach (var a in new[] { false, true }) { + foreach (var b in new[] { false, true }) { + aList.Add(a); + bList.Add(b); + expectedResult.Add(a ^ b); + } + } + + var aArray = aList.Select(e => (InteropBool)e).ToArray(); + var bArray = bList.Select(e => (InteropBool)e).ToArray(); + var size = aArray.Length; + var actualResultArray = new InteropBool[size]; + BasicInteropInteractions.deephaven_dhcore_interop_testapi_BasicInteropInteractions_XorArrays( + aArray, bArray, size, actualResultArray); + + var expectedResultArray = expectedResult.Select(e => (InteropBool)e).ToArray(); + + Assert.Equal(expectedResultArray, actualResultArray); + } + + [Fact] + public void TestInAndOutStrings() { + const string a = "Deep🎔"; + const string b = "haven"; + var expectedResult = a + b; + BasicInteropInteractions.deephaven_dhcore_interop_testapi_BasicInteropInteractions_Concat(a, b, + out var resultHandle, out var poolHandle); + var pool = poolHandle.ExportAndDestroy(); + var actualResult = pool.Get(resultHandle); + Assert.Equal(expectedResult, actualResult); + } + + [Fact] + public void TestInAndOutStringArrays() { + const int numItems = 30; + var prefixes = new string[numItems]; + var suffixes = new string[numItems]; + var expectedResult = new string[numItems]; + + for (int i = 0; i != numItems; ++i) { + prefixes[i] = $"Deep[{i}"; + suffixes[i] = $"-🎔-{i}haven]"; + expectedResult[i] = prefixes[i] + suffixes[i]; + } + + var resultHandles = new StringHandle[numItems]; + BasicInteropInteractions.deephaven_dhcore_interop_testapi_BasicInteropInteractions_ConcatArrays( + prefixes, suffixes, numItems, resultHandles, out var poolHandle); + var pool = poolHandle.ExportAndDestroy(); + var actualResult = resultHandles.Select(pool.Get).ToArray(); + Assert.Equal(expectedResult, actualResult); + } + + [Fact] + public void TestInAndOutStruct() { + var a = new BasicStruct(100, 33.25); + var b = new BasicStruct(12, 8.5); + BasicInteropInteractions.deephaven_dhcore_interop_testapi_BasicInteropInteractions_AddBasicStruct(ref a, ref b, out var actualResult); + var expectedResult = a.Add(b); + Assert.Equal(expectedResult, actualResult); + } + + [Fact] + public void TestInAndOutStructArrays() { + const Int32 size = 37; + var a = new BasicStruct[size]; + var b = new BasicStruct[size]; + var expectedResult = new BasicStruct[size]; + + for (Int32 i = 0; i != size; ++i) { + a[i] = new BasicStruct(i, 1234.5 + i); + b[i] = new BasicStruct(100 + i, 824.3 + i); + expectedResult[i] = a[i].Add(b[i]); + } + var actualResult = new BasicStruct[size]; + BasicInteropInteractions.deephaven_dhcore_interop_testapi_BasicInteropInteractions_AddBasicStructArrays(a, b, size, actualResult); + + for (Int32 i = 0; i != size; ++i) { + Assert.Equal(expectedResult[i], actualResult[i]); + } + Assert.Equal(expectedResult, actualResult); + } + + [Fact] + public void TestInAndOutNestedStruct() { + var a1 = new BasicStruct(11, 22.22); + var a2 = new BasicStruct(33, 44.44); + var a = new NestedStruct(a1, a2); + + var b1 = new BasicStruct(55, 66.66); + var b2 = new BasicStruct(77, 88.88); + var b = new NestedStruct(b1, b2); + + BasicInteropInteractions.deephaven_dhcore_interop_testapi_BasicInteropInteractions_AddNestedStruct(ref a, ref b, out var actualResult); + var expectedResult = a.Add(b); + Assert.Equal(expectedResult, actualResult); + } + + [Fact] + public void TestInAndOutNestedStructArray() { + const Int32 size = 52; + var a = new NestedStruct[size]; + var b = new NestedStruct[size]; + var expectedResult = new NestedStruct[size]; + + for (Int32 i = 0; i != size; ++i) { + var aa = new BasicStruct(i * 100, i * 11.11); + var ab = new BasicStruct(i + 200, i * 22.22); + var ba = new BasicStruct(i + 300, i * 33.33); + var bb = new BasicStruct(i + 400, i * 44.44); + a[i] = new NestedStruct(aa, ab); + b[i] = new NestedStruct(ba, bb); + expectedResult[i] = a[i].Add(b[i]); + } + + var actualResult = new NestedStruct[size]; + BasicInteropInteractions.deephaven_dhcore_interop_testapi_BasicInteropInteractions_AddNestedStructArrays(a, b, size, actualResult); + Assert.Equal(expectedResult, actualResult); + } + + [Fact] + public void TestErrorStatus() { + BasicInteropInteractions.deephaven_dhcore_interop_testapi_BasicInteropInteractions_SetErrorIfLessThan(10, 1, out var status1); + var error1 = status1.GetError(); + Assert.Null(error1); + + BasicInteropInteractions.deephaven_dhcore_interop_testapi_BasicInteropInteractions_SetErrorIfLessThan(1, 10, out var status2); + var error2 = status2.GetError(); + Assert.NotNull(error2); + } +} diff --git a/csharp/client/DhClientTests/BasicTests.cs b/csharp/client/DhClientTests/BasicTests.cs new file mode 100644 index 00000000000..7c89409120b --- /dev/null +++ b/csharp/client/DhClientTests/BasicTests.cs @@ -0,0 +1,22 @@ +using Deephaven.DeephavenClient.Interop; +using Deephaven.DeephavenClient; +using Xunit.Abstractions; +using System; + +namespace Deephaven.DhClientTests; + +public class BasicTests { + private readonly ITestOutputHelper _output; + + public BasicTests(ITestOutputHelper output) { + _output = output; + } + + [Fact] + public void TestClosePlaysNiceWithDispose() { + using var ctx = CommonContextForTests.Create(new ClientOptions()); + var table = ctx.TestTable; + using var updated = table.Update("QQQ = i"); + ctx.Client.Close(); + } +} diff --git a/csharp/client/DhClientTests/CommonContextForTests.cs b/csharp/client/DhClientTests/CommonContextForTests.cs new file mode 100644 index 00000000000..e173e9de1bb --- /dev/null +++ b/csharp/client/DhClientTests/CommonContextForTests.cs @@ -0,0 +1,138 @@ +using System; +using Deephaven.DeephavenClient; +using Deephaven.DeephavenClient.Utility; + +namespace Deephaven.DhClientTests; + +public sealed class CommonContextForTests : IDisposable { + public readonly Client Client; + public readonly TableHandle TestTable; + public readonly ColumnNamesForTests ColumnNames; + public readonly ColumnDataForTests ColumnData; + + public static CommonContextForTests Create(ClientOptions options) { + var client = CreateClient(options); + var manager = client.Manager; + + var cn = new ColumnNamesForTests(); + var cd = new ColumnDataForTests(); + + var maker = new TableMaker(); + maker.AddColumn(cn.ImportDate, cd.ImportDate); + maker.AddColumn(cn.Ticker, cd.Ticker); + maker.AddColumn(cn.Open, cd.Open); + maker.AddColumn(cn.Close, cd.Close); + maker.AddColumn(cn.Volume, cd.Volume); + + var testTable = maker.MakeTable(manager); + return new CommonContextForTests(client, testTable, cn, cd); + } + + private CommonContextForTests(Client client, TableHandle testTable, + ColumnNamesForTests cn, ColumnDataForTests cd) { + Client = client; + TestTable = testTable; + ColumnNames = cn; + ColumnData = cd; + } + + public void Dispose() { + TestTable.Dispose(); + Client.Dispose(); + } + + private static Client CreateClient(ClientOptions clientOptions) { + var host = GlobalEnvironmentForTests.GetEnv("DH_HOST", "10.0.4.106"); + var port = GlobalEnvironmentForTests.GetEnv("DH_PORT", "10000"); + var connectionString = $"{host}:{port}"; + var client = Client.Connect(connectionString, clientOptions); + return client; + } +} + +// TODO(kosak): put this somewhere, and implement for real +class GlobalEnvironmentForTests { + public static string GetEnv(string environmentVariable, string defaultValue) { + return defaultValue; + } +} + +public class ColumnNamesForTests { + public string ImportDate = "ImportDate"; + public string Ticker = "Ticker"; + public string Open = "Open"; + public string Close = "Close"; + public string Volume = "Volume"; +} + +public class ColumnDataForTests { + public string[] ImportDate = { + "2017-11-01", + "2017-11-01", + "2017-11-01", + "2017-11-01", + "2017-11-01", + "2017-11-01", + "2017-11-01", + "2017-11-01", + "2017-11-01", + "2017-11-01", + "2017-11-02" + }; + + public string[] Ticker = { + "XRX", + "XRX", + "XYZZY", + "IBM", + "GME", + "AAPL", + "AAPL", + "AAPL", + "ZNGA", + "ZNGA", + "T" + }; + + public double[] Open = { + 83.1, + 50.5, + 92.3, + 40.1, + 681.43, + 22.1, + 26.8, + 31.5, + 541.2, + 685.3, + 18.8 + }; + + public double[] Close = { + 88.2, + 53.8, + 88.5, + 38.7, + 453, + 23.5, + 24.2, + 26.7, + 538.2, + 544.9, + 13.4 + }; + + public long[] Volume = { + 345000, + 87000, + 6060842, + 138000, + 138000000, + 100000, + 250000, + 19000, + 46123, + 48300, + 1500 + }; +} diff --git a/csharp/client/DhClientTests/DateTimeTest.cs b/csharp/client/DhClientTests/DateTimeTest.cs new file mode 100644 index 00000000000..ee2e3287fce --- /dev/null +++ b/csharp/client/DhClientTests/DateTimeTest.cs @@ -0,0 +1,28 @@ +using Deephaven.DeephavenClient.Interop; +using Deephaven.DeephavenClient; +using Xunit.Abstractions; + +namespace Deephaven.DhClientTests; + +public class DateTimeTest { + private readonly ITestOutputHelper _output; + + public DateTimeTest(ITestOutputHelper output) { + _output = output; + } + + [Fact] + public void TestDhDateTimeConstructor() { + var dt0 = new DhDateTime(2001, 3, 1, 12, 34, 56); + Assert.Equal(983450096000000000, dt0.Nanos); + var dt1 = new DhDateTime(2001, 3, 1, 12, 34, 56, 987000000); + Assert.Equal(983450096987000000, dt1.Nanos); + var dt2 = new DhDateTime(2001, 3, 1, 12, 34, 56, 987654000); + Assert.Equal(983450096987654000, dt2.Nanos); + var dt3 = new DhDateTime(2001, 3, 1, 12, 34, 56, 987654321); + Assert.Equal(983450096987654321, dt3.Nanos); + } + + // TODO(kosak): DhDateTime.Parse (including parsing full nanosecond resolution) + // and DhDateTime.Format +} diff --git a/csharp/client/DhClientTests/DhClientTests.csproj b/csharp/client/DhClientTests/DhClientTests.csproj new file mode 100644 index 00000000000..f3891fe822b --- /dev/null +++ b/csharp/client/DhClientTests/DhClientTests.csproj @@ -0,0 +1,29 @@ + + + + net7.0 + enable + enable + + false + true + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/csharp/client/DhClientTests/DhClientTests.sln b/csharp/client/DhClientTests/DhClientTests.sln new file mode 100644 index 00000000000..b70b6047321 --- /dev/null +++ b/csharp/client/DhClientTests/DhClientTests.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.7.34221.43 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DhClientTests", "DhClientTests.csproj", "{3C5EE5FF-3DB8-42FF-B8CA-675F63929F51}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DeephavenClient", "..\DeephavenClient\DeephavenClient.csproj", "{1F55F88C-23E7-403A-8136-EAFA58E3CA19}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {3C5EE5FF-3DB8-42FF-B8CA-675F63929F51}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3C5EE5FF-3DB8-42FF-B8CA-675F63929F51}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3C5EE5FF-3DB8-42FF-B8CA-675F63929F51}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3C5EE5FF-3DB8-42FF-B8CA-675F63929F51}.Release|Any CPU.Build.0 = Release|Any CPU + {1F55F88C-23E7-403A-8136-EAFA58E3CA19}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1F55F88C-23E7-403A-8136-EAFA58E3CA19}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1F55F88C-23E7-403A-8136-EAFA58E3CA19}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1F55F88C-23E7-403A-8136-EAFA58E3CA19}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {78B771BA-9731-4321-A0A1-E711829D766F} + EndGlobalSection +EndGlobal diff --git a/csharp/client/DhClientTests/DhClientTests.sln.DotSettings.user b/csharp/client/DhClientTests/DhClientTests.sln.DotSettings.user new file mode 100644 index 00000000000..09f18a46c5a --- /dev/null +++ b/csharp/client/DhClientTests/DhClientTests.sln.DotSettings.user @@ -0,0 +1,17 @@ + + <SessionState ContinuousTestingMode="0" IsActive="True" Name="TestAdd" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> + <TestAncestor> + <TestId>xUnit::3C5EE5FF-3DB8-42FF-B8CA-675F63929F51::net7.0::Deephaven.DhClientTests.BasicInteropTest.TestAdd</TestId> + <TestId>xUnit::3C5EE5FF-3DB8-42FF-B8CA-675F63929F51::net7.0::Deephaven.DhClientTests.BasicInteropTest.TestConcat</TestId> + <TestId>xUnit::3C5EE5FF-3DB8-42FF-B8CA-675F63929F51::net7.0::Deephaven.DhClientTests.BasicInteropTest.TestArrayRunningSum</TestId> + <TestId>xUnit::3C5EE5FF-3DB8-42FF-B8CA-675F63929F51::net7.0::Deephaven.DhClientTests.BasicInteropTest.TestArrayElementConcat</TestId> + <TestId>xUnit::3C5EE5FF-3DB8-42FF-B8CA-675F63929F51::net7.0::Deephaven.DhClientTests.BasicInteropTest.TestBasicStruct</TestId> + <TestId>xUnit::3C5EE5FF-3DB8-42FF-B8CA-675F63929F51::net7.0::Deephaven.DhClientTests.SelectTest.TestSupportAllTypes</TestId> + <TestId>xUnit::3C5EE5FF-3DB8-42FF-B8CA-675F63929F51::net7.0::Deephaven.DhClientTests.SelectTest.TestSimpleWhere</TestId> + <TestId>xUnit::3C5EE5FF-3DB8-42FF-B8CA-675F63929F51::net7.0::Deephaven.DhClientTests.SelectTest.TestSelectAFewColumns</TestId> + <TestId>xUnit::3C5EE5FF-3DB8-42FF-B8CA-675F63929F51::net7.0::Deephaven.DhClientTests.UngroupTest.UngroupColumns</TestId> + <TestId>xUnit::3C5EE5FF-3DB8-42FF-B8CA-675F63929F51::net7.0::Deephaven.DhClientTests.TickingTest.AllEventuallyGreaterThan10</TestId> + <TestId>xUnit::3C5EE5FF-3DB8-42FF-B8CA-675F63929F51::net7.0::Deephaven.DhClientTests.TickingTest.AllDataEventuallyPresent</TestId> + <TestId>xUnit::3C5EE5FF-3DB8-42FF-B8CA-675F63929F51::net7.0::Deephaven.DhClientTests.TickingTest.wtf</TestId> + </TestAncestor> +</SessionState> \ No newline at end of file diff --git a/csharp/client/DhClientTests/FilterTest.cs b/csharp/client/DhClientTests/FilterTest.cs new file mode 100644 index 00000000000..64c032020d2 --- /dev/null +++ b/csharp/client/DhClientTests/FilterTest.cs @@ -0,0 +1,39 @@ +using Deephaven.DeephavenClient.Interop; +using System; +using Deephaven.DeephavenClient; +using Deephaven.DeephavenClient.Utility; +using Xunit.Abstractions; + +namespace Deephaven.DhClientTests; + +public class FilterTest { + private readonly ITestOutputHelper _output; + + public FilterTest(ITestOutputHelper output) { + _output = output; + } + + [Fact] + public void TestFilterATable() { + using var ctx = CommonContextForTests.Create(new ClientOptions()); + var table = ctx.TestTable; + + var t1 = table.Where( + "ImportDate == `2017-11-01` && Ticker == `AAPL` && (Close <= 120.0 || isNull(Close))"); + _output.WriteLine(t1.ToString(true)); + + var importDateData = new[] { "2017-11-01", "2017-11-01", "2017-11-01"}; + var tickerData = new []{ "AAPL", "AAPL", "AAPL"}; + var openData = new[] { 22.1, 26.8, 31.5 }; + var closeData = new[] { 23.5, 24.2, 26.7 }; + var volData = new Int64[] { 100000, 250000, 19000 }; + + var tc = new TableComparer(); + tc.AddColumn("ImportDate", importDateData); + tc.AddColumn("Ticker", tickerData); + tc.AddColumn("Open", openData); + tc.AddColumn("Close", closeData); + tc.AddColumn("Volume", volData); + tc.AssertEqualTo(t1); + } +} diff --git a/csharp/client/DhClientTests/GlobalUsings.cs b/csharp/client/DhClientTests/GlobalUsings.cs new file mode 100644 index 00000000000..8c927eb747a --- /dev/null +++ b/csharp/client/DhClientTests/GlobalUsings.cs @@ -0,0 +1 @@ +global using Xunit; \ No newline at end of file diff --git a/csharp/client/DhClientTests/HeadAndTailTest.cs b/csharp/client/DhClientTests/HeadAndTailTest.cs new file mode 100644 index 00000000000..6b7b06af16c --- /dev/null +++ b/csharp/client/DhClientTests/HeadAndTailTest.cs @@ -0,0 +1,44 @@ +using Deephaven.DeephavenClient.Interop; +using Deephaven.DeephavenClient; +using Xunit.Abstractions; + +namespace Deephaven.DhClientTests; + +public class HeadAndTailTest { + private readonly ITestOutputHelper _output; + + public HeadAndTailTest(ITestOutputHelper output) { + _output = output; + } + + [Fact] + public void TestHeadAndTail() { + using var ctx = CommonContextForTests.Create(new ClientOptions()); + var table = ctx.TestTable; + + table = table.Where("ImportDate == `2017-11-01`"); + + var th = table.Head(2).Select("Ticker", "Volume"); + var tt = table.Tail(2).Select("Ticker", "Volume"); + + { + var tickerData = new[] { "XRX", "XRX" }; + var volumeData = new Int64[] { 345000, 87000 }; + var tc = new TableComparer(); + + tc.AddColumn("Ticker", tickerData); + tc.AddColumn("Volume", volumeData); + tc.AssertEqualTo(th); + } + + { + var tickerData = new[] { "ZNGA", "ZNGA" }; + var volumeData = new Int64[] { 46123, 48300 }; + var tc = new TableComparer(); + + tc.AddColumn("Ticker", tickerData); + tc.AddColumn("Volume", volumeData); + tc.AssertEqualTo(tt); + } + } +} diff --git a/csharp/client/DhClientTests/InputTableTest.cs b/csharp/client/DhClientTests/InputTableTest.cs new file mode 100644 index 00000000000..2f8f8cd6fa7 --- /dev/null +++ b/csharp/client/DhClientTests/InputTableTest.cs @@ -0,0 +1,81 @@ +using Deephaven.DeephavenClient; +using Xunit.Abstractions; + +namespace Deephaven.DhClientTests; + +public class InputTableTest { + private readonly ITestOutputHelper _output; + + public InputTableTest(ITestOutputHelper output) { + _output = output; + } + + [Fact] + public void TestInputTableAppend() { + using var ctx = CommonContextForTests.Create(new ClientOptions()); + var tm = ctx.Client.Manager; + + var source = tm.EmptyTable(3).Update("A = ii", "B = ii + 100"); + // No keys, so InputTable will be in append-only mode. + var inputTable = tm.InputTable(source); + + // expect inputTable to be {0, 100}, {1, 101}, {2, 102} + { + var aData = new Int64[] { 0, 1, 2 }; + var bData = new Int64[] { 100, 101, 102 }; + var tc = new TableComparer(); + tc.AddColumn("A", aData); + tc.AddColumn("B", bData); + tc.AssertEqualTo(inputTable); + } + + var tableToAdd = tm.EmptyTable(2).Update("A = ii", "B = ii + 200"); + inputTable.AddTable(tableToAdd); + + // Because of append, expect input_table to be {0, 100}, {1, 101}, {2, 102}, {0, 200}, {1, 201} + { + var aData = new Int64[] { 0, 1, 2, 0, 1 }; + var bData = new Int64[] { 100, 101, 102, 200, 201 }; + var tc = new TableComparer(); + tc.AddColumn("A", aData); + tc.AddColumn("B", bData); + tc.AssertEqualTo(inputTable); + } + } + + + [Fact] + public void TestInputTableKeyed() { + using var ctx = CommonContextForTests.Create(new ClientOptions()); + var tm = ctx.Client.Manager; + + var source = tm.EmptyTable(3).Update("A = ii", "B = ii + 100"); + // Keys = {"A"}, so InputTable will be in keyed mode + var inputTable = tm.InputTable(source, "A"); + + + // expect input_table to be {0, 100}, {1, 101}, {2, 102} + { + var aData = new Int64[] { 0, 1, 2 }; + var bData = new Int64[] { 100, 101, 102 }; + var tc = new TableComparer(); + tc.AddColumn("A", aData); + tc.AddColumn("B", bData); + tc.AssertEqualTo(inputTable); + } + + + var tableToAdd = tm.EmptyTable(2).Update("A = ii", "B = ii + 200"); + inputTable.AddTable(tableToAdd); + + // Because key is "A", expect input_table to be {0, 200}, {1, 201}, {2, 102} + { + var aData = new Int64[] { 0, 1, 2 }; + var bData = new Int64[] { 200, 201, 102 }; + var tc = new TableComparer(); + tc.AddColumn("A", aData); + tc.AddColumn("B", bData); + tc.AssertEqualTo(inputTable); + } + } +} diff --git a/csharp/client/DhClientTests/JoinTest.cs b/csharp/client/DhClientTests/JoinTest.cs new file mode 100644 index 00000000000..f889a3b53db --- /dev/null +++ b/csharp/client/DhClientTests/JoinTest.cs @@ -0,0 +1,200 @@ +using Deephaven.DeephavenClient; +using Deephaven.DeephavenClient.Utility; +using System; +using Xunit.Abstractions; + +namespace Deephaven.DhClientTests; + +public class JoinTest { + private readonly ITestOutputHelper _output; + + public JoinTest(ITestOutputHelper output) { + _output = output; + } + + [Fact] + public void TestJoin() { + using var ctx = CommonContextForTests.Create(new ClientOptions()); + var tm = ctx.Client.Manager; + var testTable = ctx.TestTable; + + using var table = testTable.Where("ImportDate == `2017-11-01`"); + using var lastClose = table.LastBy("Ticker"); + using var avgView = table.View("Ticker", "Volume").AvgBy("Ticker"); + + using var joined = lastClose.NaturalJoin(avgView, new[] {"Ticker"}, new[]{ "ADV = Volume"}); + using var filtered = joined.Select("Ticker", "Close", "ADV"); + + var tickerData = new[] { "XRX", "XYZZY", "IBM", "GME", "AAPL", "ZNGA" }; + var closeData = new[] { 53.8, 88.5, 38.7, 453, 26.7, 544.9 }; + var advData = new[] { 216000, 6060842, 138000, 138000000, 123000, 47211.50 }; + + var tc = new TableComparer(); + tc.AddColumn("Ticker", tickerData); + tc.AddColumn("Close", closeData); + tc.AddColumn("ADV", advData); + tc.AssertEqualTo(filtered); + } + + [Fact] + public void TestAj() { + using var ctx = CommonContextForTests.Create(new ClientOptions()); + var tm = ctx.Client.Manager; + + TableHandle trades; + { + var tickerData = new[] { "AAPL", "AAPL", "AAPL", "IBM", "IBM" }; + var instantDdata = new[] { + DhDateTime.Parse("2021-04-05T09:10:00-0500"), + DhDateTime.Parse("2021-04-05T09:31:00-0500"), + DhDateTime.Parse("2021-04-05T16:00:00-0500"), + DhDateTime.Parse("2021-04-05T16:00:00-0500"), + DhDateTime.Parse("2021-04-05T16:30:00-0500") + }; + var priceData = new[] { 2.5, 3.7, 3.0, 100.50, 110 }; + var sizeData = new[] { 52, 14, 73, 11, 6 }; + using var tableMaker = new TableMaker(); + tableMaker.AddColumn("Ticker", tickerData); + tableMaker.AddColumn("Timestamp", instantDdata); + tableMaker.AddColumn("Price", priceData); + tableMaker.AddColumn("Size", sizeData); + trades = tableMaker.MakeTable(tm); + } + + TableHandle quotes; + { + var tickerData = new[] { "AAPL", "AAPL", "IBM", "IBM", "IBM" }; + var timeStampData = new[] { + DhDateTime.Parse("2021-04-05T09:11:00-0500"), + DhDateTime.Parse("2021-04-05T09:30:00-0500"), + DhDateTime.Parse("2021-04-05T16:00:00-0500"), + DhDateTime.Parse("2021-04-05T16:30:00-0500"), + DhDateTime.Parse("2021-04-05T17:00:00-0500") + }; + var bidData = new[] { 2.5, 3.4, 97, 102, 108 }; + var bidSizeData = new[] { 10, 20, 5, 13, 23 }; + var askData = new[] { 2.5, 3.4, 105, 110, 111 }; + var askSizeData = new[] { 83, 33, 47, 15, 5 }; + var tableMaker = new TableMaker(); + tableMaker.AddColumn("Ticker", tickerData); + tableMaker.AddColumn("Timestamp", timeStampData); + tableMaker.AddColumn("Bid", bidData); + tableMaker.AddColumn("BidSize", bidSizeData); + tableMaker.AddColumn("Ask", askData); + tableMaker.AddColumn("AskSize", askSizeData); + quotes = tableMaker.MakeTable(tm); + } + + using var result = trades.Aj(quotes, new[] { "Ticker", "Timestamp" }); + + // Expected data + { + var tickerData = new[] { "AAPL", "AAPL", "AAPL", "IBM", "IBM" }; + var timestampData = new[] { + DhDateTime.Parse("2021-04-05T09:10:00-0500"), + DhDateTime.Parse("2021-04-05T09:31:00-0500"), + DhDateTime.Parse("2021-04-05T16:00:00-0500"), + DhDateTime.Parse("2021-04-05T16:00:00-0500"), + DhDateTime.Parse("2021-04-05T16:30:00-0500") + }; + var priceData = new[] { 2.5, 3.7, 3.0, 100.50, 110 }; + var sizeData = new[] { 52, 14, 73, 11, 6 }; + var bidData = new double?[] { null, 3.4, 3.4, 97, 102 }; + var bidSizeData = new int?[] { null, 20, 20, 5, 13 }; + var askData = new double?[] { null, 3.4, 3.4, 105, 110 }; + var askSizeData = new int?[] { null, 33, 33, 47, 15 }; + + var tc = new TableComparer(); + tc.AddColumn("Ticker", tickerData); + tc.AddColumn("Timestamp", timestampData); + tc.AddColumn("Price", priceData); + tc.AddColumn("Size", sizeData); + tc.AddColumn("Bid", bidData); + tc.AddColumn("BidSize", bidSizeData); + tc.AddColumn("Ask", askData); + tc.AddColumn("AskSize", askSizeData); + tc.AssertEqualTo(result); + } + } + + [Fact] + public void TestRaj() { + using var ctx = CommonContextForTests.Create(new ClientOptions()); + var tm = ctx.Client.Manager; + + TableHandle trades; + { + var tickerData = new[] { "AAPL", "AAPL", "AAPL", "IBM", "IBM" }; + var instantData = new[] { + DhDateTime.Parse("2021-04-05T09:10:00-0500"), + DhDateTime.Parse("2021-04-05T09:31:00-0500"), + DhDateTime.Parse("2021-04-05T16:00:00-0500"), + DhDateTime.Parse("2021-04-05T16:00:00-0500"), + DhDateTime.Parse("2021-04-05T16:30:00-0500") + }; + var priceData = new[] { 2.5, 3.7, 3.0, 100.50, 110 }; + var sizeData = new[] { 52, 14, 73, 11, 6 }; + var tableMaker = new TableMaker(); + tableMaker.AddColumn("Ticker", tickerData); + tableMaker.AddColumn("Timestamp", instantData); + tableMaker.AddColumn("Price", priceData); + tableMaker.AddColumn("Size", sizeData); + trades = tableMaker.MakeTable(tm); + } + + TableHandle quotes; + { + var tickerData = new[] { "AAPL", "AAPL", "IBM", "IBM", "IBM"}; + var timestampData = new[] { + DhDateTime.Parse("2021-04-05T09:11:00-0500"), + DhDateTime.Parse("2021-04-05T09:30:00-0500"), + DhDateTime.Parse("2021-04-05T16:00:00-0500"), + DhDateTime.Parse("2021-04-05T16:30:00-0500"), + DhDateTime.Parse("2021-04-05T17:00:00-0500") + }; + var bidData = new[] { 2.5, 3.4, 97, 102, 108 }; + var bidSizeData = new[] { 10, 20, 5, 13, 23 }; + var askData = new[] { 2.5, 3.4, 105, 110, 111 }; + var askSizeData = new[] { 83, 33, 47, 15, 5 }; + var tableMaker = new TableMaker(); + tableMaker.AddColumn("Ticker", tickerData); + tableMaker.AddColumn("Timestamp", timestampData); + tableMaker.AddColumn("Bid", bidData); + tableMaker.AddColumn("BidSize", bidSizeData); + tableMaker.AddColumn("Ask", askData); + tableMaker.AddColumn("AskSize", askSizeData); + quotes = tableMaker.MakeTable(tm); + } + + var result = trades.Raj(quotes, new[] { "Ticker", "Timestamp"}); + + // Expected data + { + var tickerData = new[] { "AAPL", "AAPL", "AAPL", "IBM", "IBM"}; + var timestampData = new[] { + DhDateTime.Parse("2021-04-05T09:10:00-0500"), + DhDateTime.Parse("2021-04-05T09:31:00-0500"), + DhDateTime.Parse("2021-04-05T16:00:00-0500"), + DhDateTime.Parse("2021-04-05T16:00:00-0500"), + DhDateTime.Parse("2021-04-05T16:30:00-0500") + }; + + var priceData = new[] { 2.5, 3.7, 3.0, 100.50, 110 }; + var sizeData = new[] { 52, 14, 73, 11, 6 }; + var bidData = new double?[] { 2.5, null, null, 97, 102 }; + var bidSizeData = new int?[] { 10, null, null, 5, 13 }; + var askData = new double?[] { 2.5, null, null, 105, 110 }; + var askSizeData = new int?[] { 83, null, null, 47, 15 }; + var tableComparer = new TableComparer(); + tableComparer.AddColumn("Ticker", tickerData); + tableComparer.AddColumn("Timestamp", timestampData); + tableComparer.AddColumn("Price", priceData); + tableComparer.AddColumn("Size", sizeData); + tableComparer.AddColumn("Bid", bidData); + tableComparer.AddColumn("BidSize", bidSizeData); + tableComparer.AddColumn("Ask", askData); + tableComparer.AddColumn("AskSize", askSizeData); + tableComparer.AssertEqualTo(result); + } + } +} diff --git a/csharp/client/DhClientTests/LastByTest.cs b/csharp/client/DhClientTests/LastByTest.cs new file mode 100644 index 00000000000..179d52b535f --- /dev/null +++ b/csharp/client/DhClientTests/LastByTest.cs @@ -0,0 +1,35 @@ +using Deephaven.DeephavenClient; +using Xunit.Abstractions; + +namespace Deephaven.DhClientTests; + +public class LastByTest { + private readonly ITestOutputHelper _output; + + public LastByTest(ITestOutputHelper output) { + _output = output; + } + + [Fact] + public void TestLastBy() { + using var ctx = CommonContextForTests.Create(new ClientOptions()); + var tm = ctx.Client.Manager; + var testTable = ctx.TestTable; + + using var lb = testTable.Where("ImportDate == `2017-11-01`") + .Select("Ticker", "Open", "Close") + .LastBy("Ticker"); + + var tickerData = new[] { + "XRX", "XYZZY", "IBM", "GME", "AAPL", "ZNGA" + }; + + var openData = new[] { 50.5, 92.3, 40.1, 681.43, 31.5, 685.3 }; + var closeData = new[] { 53.8, 88.5, 38.7, 453, 26.7, 544.9 }; + var tc = new TableComparer(); + tc.AddColumn("Ticker", tickerData); + tc.AddColumn("Open", openData); + tc.AddColumn("Close", closeData); + tc.AssertEqualTo(lb); + } +} diff --git a/csharp/client/DhClientTests/MergeTest.cs b/csharp/client/DhClientTests/MergeTest.cs new file mode 100644 index 00000000000..2e45f6f48b0 --- /dev/null +++ b/csharp/client/DhClientTests/MergeTest.cs @@ -0,0 +1,35 @@ +using Deephaven.DeephavenClient; + +namespace Deephaven.DhClientTests; + +public class MergeTest { + [Fact] + public void TestMerge() { + using var ctx = CommonContextForTests.Create(new ClientOptions()); + var testTable = ctx.TestTable; + + using var table = testTable.Where("ImportDate == `2017-11-01`"); + + // Run a merge by fetching two tables and them merging them + var aaplTable = table.Where("Ticker == `AAPL`").Tail(10); + var zngaTable = table.Where("Ticker == `ZNGA`").Tail(10); + + var merged = aaplTable.Merge(new[] { zngaTable} ); + + var importDateData = new[] { + "2017-11-01", "2017-11-01", "2017-11-01", + "2017-11-01", "2017-11-01"}; + var tickerData = new[] { "AAPL", "AAPL", "AAPL", "ZNGA", "ZNGA"}; + var openData = new[] { 22.1, 26.8, 31.5, 541.2, 685.3 }; + var closeData = new[] { 23.5, 24.2, 26.7, 538.2, 544.9 }; + var volData = new Int64[] { 100000, 250000, 19000, 46123, 48300 }; + + var tc = new TableComparer(); + tc.AddColumn("ImportDate", importDateData); + tc.AddColumn("Ticker", tickerData); + tc.AddColumn("Open", openData); + tc.AddColumn("Close", closeData); + tc.AddColumn("Volume", volData); + tc.AssertEqualTo(merged); + } +} \ No newline at end of file diff --git a/csharp/client/DhClientTests/ScriptTest.cs b/csharp/client/DhClientTests/ScriptTest.cs new file mode 100644 index 00000000000..3734d04d105 --- /dev/null +++ b/csharp/client/DhClientTests/ScriptTest.cs @@ -0,0 +1,44 @@ +using Deephaven.DeephavenClient; + +namespace Deephaven.DhClientTests; + +public class ScriptTest { + [Fact] + public void TestScriptSessionError() { + using var ctx = CommonContextForTests.Create(new ClientOptions().SetSessionType("")); + var m = ctx.Client.Manager; + const string script = "from deephaven import empty_table"; + var ex = Record.Exception(() => m.RunScript(script)); + Assert.NotNull(ex); + Assert.Contains("Client was created without specifying a script language", ex.Message); + } + + [Fact] + public void TestScriptExecution() { + var intData = new List(); + var longData = new List(); + + const int startValue = -8; + const int endValue = 8; + for (var i = startValue; i != endValue; ++i) { + intData.Add(i); + longData.Add(i * 100); + } + + using var ctx = CommonContextForTests.Create(new ClientOptions()); + var m = ctx.Client.Manager; + + const string script = """ + from deephaven import empty_table + mytable = empty_table(16).update(["intData = (int)(ii - 8)", "longData = (long)((ii - 8) * 100)"]) + """; + + m.RunScript(script); + var t = m.FetchTable("mytable"); + + var tc = new TableComparer(); + tc.AddColumn("intData", intData); + tc.AddColumn("longData", longData); + tc.AssertEqualTo(t); + } +} diff --git a/csharp/client/DhClientTests/SelectTest.cs b/csharp/client/DhClientTests/SelectTest.cs new file mode 100644 index 00000000000..2bfe28245a9 --- /dev/null +++ b/csharp/client/DhClientTests/SelectTest.cs @@ -0,0 +1,313 @@ +using Deephaven.DeephavenClient.Interop; +using System; +using Deephaven.DeephavenClient; +using Deephaven.DeephavenClient.Utility; +using Xunit.Abstractions; + +namespace Deephaven.DhClientTests; + +public class SelectTest { + private readonly ITestOutputHelper _output; + + public SelectTest(ITestOutputHelper output) { + _output = output; + } + + [Fact] + public void TestSupportAllTypes() { + using var ctx = CommonContextForTests.Create(new ClientOptions()); + + var boolData = new List(); + var charData = new List(); + var byteData = new List(); + var shortData = new List(); + var intData = new List(); + var longData = new List(); + var floatData = new List(); + var doubleData = new List(); + var stringData = new List(); + var dateTimeData = new List(); + + const int startValue = -8; + const int endValue = 8; + for (var i = startValue; i != endValue; ++i) { + boolData.Add((i % 2) == 0); + charData.Add((char)(i * 10)); + byteData.Add((sbyte)(i * 11)); + shortData.Add((short)(i * 1000)); + intData.Add(i * 1_000_000); + longData.Add(i * (long)1_000_000_000); + floatData.Add(i * 123.456F); + doubleData.Add(i * 987654.321); + stringData.Add($"test {i}"); + dateTimeData.Add(DhDateTime.FromNanos(i)); + } + + using var maker = new TableMaker(); + maker.AddColumn("boolData", boolData); + maker.AddColumn("charData", charData); + maker.AddColumn("byteData", byteData); + maker.AddColumn("shortData", shortData); + maker.AddColumn("intData", intData); + maker.AddColumn("longData", longData); + maker.AddColumn("floatData", floatData); + maker.AddColumn("doubleData", doubleData); + maker.AddColumn("stringData", stringData); + maker.AddColumn("dateTimeData", dateTimeData); + + var t = maker.MakeTable(ctx.Client.Manager); + + _output.WriteLine(t.ToString(true)); + + var tc = new TableComparer(); + tc.AddColumn("boolData", boolData); + tc.AddColumn("charData", charData); + tc.AddColumn("byteData", byteData); + tc.AddColumn("shortData", shortData); + tc.AddColumn("intData", intData); + tc.AddColumn("longData", longData); + tc.AddColumn("floatData", floatData); + tc.AddColumn("doubleData", doubleData); + tc.AddColumn("stringData", stringData); + tc.AddColumn("dateTimeData", dateTimeData); + tc.AssertEqualTo(t); + } + + [Fact] + public void TestCreateUpdateFetchATable() { + using var ctx = CommonContextForTests.Create(new ClientOptions()); + + var intData = new[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + var doubleData = new[] { 0.0, 1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9 }; + var stringData = new[] { + "zero", "one", "two", "three", "four", "five", "six", "seven", + "eight", "nine" + }; + + using var maker = new TableMaker(); + maker.AddColumn("IntValue", intData); + maker.AddColumn("DoubleValue", doubleData); + maker.AddColumn("StringValue", stringData); + var t = maker.MakeTable(ctx.Client.Manager); + var t2 = t.Update("Q2 = IntValue * 100"); + var t3 = t2.Update("Q3 = Q2 + 10"); + var t4 = t3.Update("Q4 = Q2 + 100"); + + var q2Data = new[] { 0, 100, 200, 300, 400, 500, 600, 700, 800, 900 }; + var q3Data = new[] { 10, 110, 210, 310, 410, 510, 610, 710, 810, 910 }; + var q4Data = new[] { 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000 }; + + var tc = new TableComparer(); + tc.AddColumn("IntValue", intData); + tc.AddColumn("DoubleValue", doubleData); + tc.AddColumn("StringValue", stringData); + tc.AddColumn("Q2", q2Data); + tc.AddColumn("Q3", q3Data); + tc.AddColumn("Q4", q4Data); + tc.AssertEqualTo(t4); + } + + [Fact] + public void TestSelectAFewColumns() { + using var ctx = CommonContextForTests.Create(new ClientOptions()); + var table = ctx.TestTable; + + var t1 = table.Where("ImportDate == `2017-11-01` && Ticker == `AAPL`") + .Select("Ticker", "Close", "Volume") + .Head(2); + + var tickerData = new[] { "AAPL", "AAPL" }; + var closeData = new[] { 23.5, 24.2 }; + var volData = new Int64[] { 100000, 250000 }; + + var tc = new TableComparer(); + tc.AddColumn("Ticker", tickerData); + tc.AddColumn("Close", closeData); + tc.AddColumn("Volume", volData); + tc.AssertEqualTo(t1); + } + + [Fact] + public void TestLastByAndSelect() { + using var ctx = CommonContextForTests.Create(new ClientOptions()); + var table = ctx.TestTable; + + var t1 = table.Where("ImportDate == `2017-11-01` && Ticker == `AAPL`") + .LastBy("Ticker") + .Select("Ticker", "Close", "Volume"); + _output.WriteLine(t1.ToString(true)); + + var tickerData = new[] { "AAPL" }; + var closeData = new[] { 26.7 }; + var volData = new Int64[] { 19000 }; + + var tc = new TableComparer(); + tc.AddColumn("Ticker", tickerData); + tc.AddColumn("Close", closeData); + tc.AddColumn("Volume", volData); + tc.AssertEqualTo(t1); + } + + [Fact] + public void TestNewColumns() { + using var ctx = CommonContextForTests.Create(new ClientOptions()); + var table = ctx.TestTable; + + // A formula expression + var t1 = table.Where("ImportDate == `2017-11-01` && Ticker == `AAPL`") + .Select("MV1 = Volume * Close", "V_plus_12 = Volume + 12"); + + var mv1Data = new double[] { 2350000, 6050000, 507300 }; + var vp2Data = new long[] { 100012, 250012, 19012 }; + + var tc = new TableComparer(); + tc.AddColumn("MV1", mv1Data); + tc.AddColumn("V_plus_12", vp2Data); + tc.AssertEqualTo(t1); + } + + [Fact] + public void TestDropColumns() { + using var ctx = CommonContextForTests.Create(new ClientOptions()); + var table = ctx.TestTable; + + var t1 = table.DropColumns("ImportDate", "Open", "Close"); + Assert.Equal(5, table.Schema.NumCols); + Assert.Equal(2, t1.Schema.NumCols); + } + + [Fact] + public void TestSimpleWhere() { + using var ctx = CommonContextForTests.Create(new ClientOptions()); + var table = ctx.TestTable; + var updated = table.Update("QQQ = i"); + + var t1 = updated.Where("ImportDate == `2017-11-01` && Ticker == `IBM`") + .Select("Ticker", "Volume"); + + var tickerData = new[] { "IBM" }; + var volData = new Int64[] { 138000 }; + + var tc = new TableComparer(); + tc.AddColumn("Ticker", tickerData); + tc.AddColumn("Volume", volData); + tc.AssertEqualTo(t1); + } + + [Fact] + public void TestFormulaInTheWhereClause() { + using var ctx = CommonContextForTests.Create(new ClientOptions()); + var table = ctx.TestTable; + + var t1 = table.Where( + "ImportDate == `2017-11-01` && Ticker == `AAPL` && Volume % 10 == Volume % 100") + .Select("Ticker", "Volume"); + _output.WriteLine(t1.ToString(true)); + + var tickerData = new[] { "AAPL", "AAPL", "AAPL" }; + var volData = new Int64[] { 100000, 250000, 19000 }; + + var tc = new TableComparer(); + tc.AddColumn("Ticker", tickerData); + tc.AddColumn("Volume", volData); + tc.AssertEqualTo(t1); + } + + [Fact] + public void TestSimpleWhereWithSyntaxError() { + using var ctx = CommonContextForTests.Create(new ClientOptions()); + var table = ctx.TestTable; + + Assert.Throws(() => { + var t1 = table.Where(")))))"); + _output.WriteLine(t1.ToString(true)); + }); + } + + [Fact] + public void TestWhereIn() { + using var ctx = CommonContextForTests.Create(new ClientOptions()); + + var letterData = new[] { "A", "C", "F", "B", "E", "D", "A" }; + var numberData = new Int32?[] { null, 2, 1, null, 4, 5, 3 }; + var colorData = new[] { "red", "blue", "orange", "purple", "yellow", "pink", "blue" }; + var codeData = new Int32?[] { 12, 13, 11, null, 16, 14, null }; + + using var sourceMaker = new TableMaker(); + sourceMaker.AddColumn("Letter", letterData); + sourceMaker.AddColumn("Number", numberData); + sourceMaker.AddColumn("Color", colorData); + sourceMaker.AddColumn("Code", codeData); + + var source = sourceMaker.MakeTable(ctx.Client.Manager); + + var filterColorData = new[] { "blue", "red", "purple", "white" }; + + using var filterMaker = new TableMaker(); + filterMaker.AddColumn("Colors", filterColorData); + var filter = filterMaker.MakeTable(ctx.Client.Manager); + + var result = source.WhereIn(filter, "Color = Colors"); + + var letterExpected = new[] { "A", "C", "B", "A" }; + var numberExpected = new Int32?[] { null, 2, null, 3 }; + var colorExpected = new[] { "red", "blue", "purple", "blue" }; + var codeExpected = new Int32?[] { 12, 13, null, null }; + + var expected = new TableComparer(); + expected.AddColumn("Letter", letterExpected); + expected.AddColumn("Number", numberExpected); + expected.AddColumn("Color", colorExpected); + expected.AddColumn("Code", codeExpected); + + expected.AssertEqualTo(result); + } + + [Fact] + public void TestLazyUpdate() { + using var ctx = CommonContextForTests.Create(new ClientOptions()); + + var aData = new[] { "The", "At", "Is", "On" }; + var bData = new[] { 1, 2, 3, 4 }; + var cData = new[] { 5, 2, 5, 5 }; + + using var sourceMaker = new TableMaker(); + sourceMaker.AddColumn("A", aData); + sourceMaker.AddColumn("B", bData); + sourceMaker.AddColumn("C", cData); + var source = sourceMaker.MakeTable(ctx.Client.Manager); + + var result = source.LazyUpdate("Y = sqrt(C)"); + + var sqrtData = new[] { Math.Sqrt(5), Math.Sqrt(2), Math.Sqrt(5), Math.Sqrt(5) }; + + var tc = new TableComparer(); + tc.AddColumn("A", aData); + tc.AddColumn("B", bData); + tc.AddColumn("C", cData); + tc.AddColumn("Y", sqrtData); + tc.AssertEqualTo(result); + } + + [Fact] + public void TestSelectDistinct() { + using var ctx = CommonContextForTests.Create(new ClientOptions()); + + var aData = new[] { "apple", "apple", "orange", "orange", "plum", "grape" }; + var bData = new[] { 1, 1, 2, 2, 3, 3 }; + + using var sourceMaker = new TableMaker(); + sourceMaker.AddColumn("A", aData); + sourceMaker.AddColumn("B", bData); + var source = sourceMaker.MakeTable(ctx.Client.Manager); + + var result = source.SelectDistinct("A"); + _output.WriteLine(result.ToString(true)); + + var expectedAData = new[] { "apple", "orange", "plum", "grape" }; + + var tc = new TableComparer(); + tc.AddColumn("A", expectedAData); + tc.AssertEqualTo(result); + } +} diff --git a/csharp/client/DhClientTests/SortTest.cs b/csharp/client/DhClientTests/SortTest.cs new file mode 100644 index 00000000000..d5fd185a7cf --- /dev/null +++ b/csharp/client/DhClientTests/SortTest.cs @@ -0,0 +1,62 @@ +using Deephaven.DeephavenClient; +using System; +using Deephaven.DeephavenClient.Utility; + +namespace Deephaven.DhClientTests; + +public class SortTest { + [Fact] + public void SortDemoTable() { + using var ctx = CommonContextForTests.Create(new ClientOptions()); + var testTable = ctx.TestTable; + + // Limit by date and ticker + var filtered = testTable.Where("ImportDate == `2017-11-01`") + .Where("Ticker >= `X`") + .Select("Ticker", "Open", "Volume"); + + var table1 = filtered.Sort(SortPair.Descending("Ticker"), SortPair.Ascending("Volume")); + + var tickerData = new[] { "ZNGA", "ZNGA", "XYZZY", "XRX", "XRX"}; + var openData = new[] { 541.2, 685.3, 92.3, 50.5, 83.1 }; + var volData = new Int64[] { 46123, 48300, 6060842, 87000, 345000 }; + + var tc = new TableComparer(); + tc.AddColumn("Ticker", tickerData); + tc.AddColumn("Open", openData); + tc.AddColumn("Volume", volData); + tc.AssertEqualTo(table1); + } + + [Fact] + public void SortTempTable() { + using var ctx = CommonContextForTests.Create(new ClientOptions()); + + var intData0 = new []{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; + var intData1 = new []{ 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7}; + var intData2 = new []{ 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3}; + var intData3 = new []{ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1}; + + var maker = new TableMaker(); + maker.AddColumn("IntValue0", intData0); + maker.AddColumn("IntValue1", intData1); + maker.AddColumn("IntValue2", intData2); + maker.AddColumn("IntValue3", intData3); + + var temp_table = maker.MakeTable(ctx.Client.Manager); + + var sorted = temp_table.Sort(SortPair.Descending("IntValue3"), SortPair.Ascending("IntValue2")); + + var sid0 = new[] { 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7}; + var sid1 = new[] { 4, 4, 5, 5, 6, 6, 7, 7, 0, 0, 1, 1, 2, 2, 3, 3}; + var sid2 = new[] { 2, 2, 2, 2, 3, 3, 3, 3, 0, 0, 0, 0, 1, 1, 1, 1}; + var sid3 = new[] { 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0}; + + var tc = new TableComparer(); + tc.AddColumn("IntValue0", sid0); + tc.AddColumn("IntValue1", sid1); + tc.AddColumn("IntValue2", sid2); + tc.AddColumn("IntValue3", sid3); + tc.AssertEqualTo(sorted); + } +} diff --git a/csharp/client/DhClientTests/StringFilterTest.cs b/csharp/client/DhClientTests/StringFilterTest.cs new file mode 100644 index 00000000000..ec8049e1079 --- /dev/null +++ b/csharp/client/DhClientTests/StringFilterTest.cs @@ -0,0 +1,49 @@ +using Deephaven.DeephavenClient; + +namespace Deephaven.DhClientTests; + +public class StringFilterTest { + [Fact] + public void StringFilter() { + using var ctx = CommonContextForTests.Create(new ClientOptions()); + var testTable = ctx.TestTable; + + var t2 = testTable.Where("ImportDate == `2017-11-01`").Select("Ticker", "Close"); + + { + var tickerData = new[] { "AAPL", "AAPL", "AAPL", "ZNGA", "ZNGA" }; + var closeData = new[] { 23.5, 24.2, 26.7, 538.2, 544.9 }; + TestFilter("Contains A", t2.Where("Ticker.contains(`A`)"), + tickerData, closeData); + } + + { + var tickerData = new string[] { }; + var closeData = new double[] { }; + TestFilter("Starts with BL", t2.Where("Ticker.startsWith(`BL`)"), + tickerData, closeData); + } + + { + var tickerData = new[] { "XRX", "XRX"}; + var closeData = new[] { 88.2, 53.8 }; + TestFilter("Ends with X", t2.Where("Ticker.endsWith(`X`)"), + tickerData, closeData); + } + + { + var tickerData = new[] { "IBM" }; + var closeData = new[] { 38.7 }; + TestFilter("Matches ^I.*M$", t2.Where("Ticker.matches(`^I.*M$`)"), + tickerData, closeData); + } + } + + private static void TestFilter(string description, TableHandle filteredTable, + string[] tickerData, double[] closeData) { + var tc = new TableComparer(); + tc.AddColumn("Ticker", tickerData); + tc.AddColumn("Close", closeData); + tc.AssertEqualTo(filteredTable); + } +} diff --git a/csharp/client/DhClientTests/TableHandleAttributesTest.cs b/csharp/client/DhClientTests/TableHandleAttributesTest.cs new file mode 100644 index 00000000000..47eb8d4f868 --- /dev/null +++ b/csharp/client/DhClientTests/TableHandleAttributesTest.cs @@ -0,0 +1,40 @@ +using Deephaven.DeephavenClient; +using Xunit.Abstractions; + +namespace Deephaven.DhClientTests; + +public class TableHandleAttributesTest { + private readonly ITestOutputHelper _output; + + public TableHandleAttributesTest(ITestOutputHelper output) { + _output = output; + } + + [Fact] + public void TableHandleAttributes() { + using var ctx = CommonContextForTests.Create(new ClientOptions()); + var thm = ctx.Client.Manager; + const Int64 numRows = 37; + var t = thm.EmptyTable(numRows).Update("II = ii"); + Assert.Equal(numRows, t.NumRows); + Assert.True(t.IsStatic); + } + + [Fact] + public void TableHandleDynamicAttributes() { + using var ctx = CommonContextForTests.Create(new ClientOptions()); + var thm = ctx.Client.Manager; + var t = thm.TimeTable(1_000_000_000).Update("II = ii"); + Assert.False(t.IsStatic); + } + + [Fact] + public void TableHandleCreatedByDoPut() { + using var ctx = CommonContextForTests.Create(new ClientOptions()); + var table = ctx.TestTable; + Assert.True(table.IsStatic); + // The columns all have the same size, so look at the source data for any one of them and get its size + var expectedSize = ctx.ColumnData.ImportDate.Length; + Assert.Equal(expectedSize, table.NumRows); + } +} diff --git a/csharp/client/DhClientTests/TableTest.cs b/csharp/client/DhClientTests/TableTest.cs new file mode 100644 index 00000000000..ccb3344f847 --- /dev/null +++ b/csharp/client/DhClientTests/TableTest.cs @@ -0,0 +1,80 @@ +using Deephaven.DeephavenClient; +using Deephaven.DeephavenClient.Utility; +using System; +using Xunit.Abstractions; + +namespace Deephaven.DhClientTests; + +public class TableTest { + [Fact] + public void FetchTheWholeTable() { + const int target = 10; + using var ctx = CommonContextForTests.Create(new ClientOptions()); + var thm = ctx.Client.Manager; + var th = thm.EmptyTable(target) + .Update( + "Chars = ii == 5 ? null : (char)('a' + ii)", + "Bytes = ii == 5 ? null : (byte)(ii)", + "Shorts = ii == 5 ? null : (short)(ii)", + "Ints = ii == 5 ? null : (int)(ii)", + "Longs = ii == 5 ? null : (long)(ii)", + "Floats = ii == 5 ? null : (float)(ii)", + "Doubles = ii == 5 ? null : (double)(ii)", + "Bools = ii == 5 ? null : ((ii % 2) == 0)", + "Strings = ii == 5 ? null : `hello ` + i", + "DateTimes = ii == 5 ? null : '2001-03-01T12:34:56Z' + ii" + ); + + var chars = new List(); + var int8s = new List(); + var int16s = new List(); + var int32s = new List(); + var int64s = new List(); + var floats = new List(); + var doubles = new List(); + var bools = new List(); + var strings = new List(); + var dateTimes = new List(); + + var dateTimeStart = DhDateTime.Parse("2001-03-01T12:34:56Z"); + + for (var i = 0; i != target; ++i) { + chars.Add((char)('a' + i)); + int8s.Add((sbyte)i); + int16s.Add((Int16)i); + int32s.Add((Int32)i); + int64s.Add((Int64)i); + floats.Add((float)i); + doubles.Add((double)i); + bools.Add((i % 2) == 0); + strings.Add($"hello {i}"); + dateTimes.Add(DhDateTime.FromNanos(dateTimeStart.Nanos + i)); + } + + var t2 = target / 2; + // Set the middle element to the null + chars[t2] = null; + int8s[t2] = null; + int16s[t2] = null; + int32s[t2] = null; + int64s[t2] = null; + floats[t2] = null; + doubles[t2] = null; + bools[t2] = null; + strings[t2] = null; + dateTimes[t2] = null; + + var tc = new TableComparer(); + tc.AddColumn("Chars", chars); + tc.AddColumn("Bytes", int8s); + tc.AddColumn("Shorts", int16s); + tc.AddColumn("Ints", int32s); + tc.AddColumn("Longs", int64s); + tc.AddColumn("Floats", floats); + tc.AddColumn("Doubles", doubles); + tc.AddColumn("Bools", bools); + tc.AddColumn("Strings", strings); + tc.AddColumn("DateTimes", dateTimes); + tc.AssertEqualTo(th); + } +} diff --git a/csharp/client/DhClientTests/TestUtils.cs b/csharp/client/DhClientTests/TestUtils.cs new file mode 100644 index 00000000000..2e4c6a98835 --- /dev/null +++ b/csharp/client/DhClientTests/TestUtils.cs @@ -0,0 +1,106 @@ +using Deephaven.DeephavenClient; +using System.Collections; + +namespace Deephaven.DhClientTests; + +public static class TestUtils { + public static bool TryCompareEnumerables(IEnumerable expected, IEnumerable actual, out string failureReason) { + var nextIndex = 0; + var actualEnum = actual.GetEnumerator(); + try { + foreach (var expectedItem in expected) { + if (!actualEnum.MoveNext()) { + failureReason = $"expected has more elements than actual, which has only {nextIndex} elements"; + return false; + } + + var actualItem = actualEnum.Current; + if (!object.Equals(expectedItem, actualItem)) { + failureReason = + $"Row {nextIndex}: expected is {ExplicitToString(expectedItem)}, actual is {ExplicitToString(actualItem)}"; + return false; + } + + ++nextIndex; + } + + if (actualEnum.MoveNext()) { + failureReason = $"Actual has more elements than expected, which has only {nextIndex} elements"; + return false; + } + + failureReason = ""; + return true; + } finally { + if (actualEnum is IDisposable id) { + id.Dispose(); + } + } + } + + private static string ExplicitToString(object? item) { + if (item == null) { + return "[null]"; + } + + return $"{item}[{item.GetType().Name}]"; + } +} + +class TableComparer { + private readonly Dictionary _columns = new(); + private Int64 _numRows = 0; + + /// + /// T can be a primitive type, Nullable<T> or string + /// + public void AddColumn(string name, IList data) { + if (_columns.Count == 0) { + _numRows = data.Count; + } else { + if (_numRows != data.Count) { + throw new ArgumentException($"Columns have inconsistent size: {_numRows} vs {data.Count}"); + } + } + + if (!_columns.TryAdd(name, data)) { + throw new ArgumentException($"Can't add duplicate column name \"{name}\""); + } + } + + public void AddColumnWithNulls(string name, IList data, bool[]? nulls) where T : struct { + var nullableData = new T?[data.Count]; + for (var i = 0; i < data.Count; ++i) { + if (nulls == null || !nulls[i]) { + nullableData[i] = data[i]; + } + } + AddColumn(name, nullableData); + } + + public void AssertEqualTo(TableHandle table) { + using var ct = table.ToClientTable(); + AssertEqualTo(ct); + } + + public void AssertEqualTo(ClientTable clientTable) { + if (_columns.Count == 0) { + throw new Exception("TableComparer doesn't have any columns"); + } + + if (_columns.Count != clientTable.Schema.NumCols) { + throw new Exception($"Expected number of columns is {_columns.Count}. Actual is {clientTable.Schema.NumCols}"); + } + + foreach (var (name, expectedColumn) in _columns) { + if (!clientTable.Schema.TryGetColumnIndex(name, out var actualColIndex)) { + throw new Exception($"Expected table has column named \"{name}\". Actual does not."); + } + + var actualColumn = clientTable.GetNullableColumn(actualColIndex); + if (!TestUtils.TryCompareEnumerables(expectedColumn, actualColumn, out var failureReason)) { + throw new Exception($"While comparing column \"{name}\": {failureReason}"); + } + } + } +} diff --git a/csharp/client/DhClientTests/TickingTest.cs b/csharp/client/DhClientTests/TickingTest.cs new file mode 100644 index 00000000000..10cd6f9e3bf --- /dev/null +++ b/csharp/client/DhClientTests/TickingTest.cs @@ -0,0 +1,277 @@ +using Deephaven.DeephavenClient; +using Microsoft.VisualBasic; +using System.Runtime.InteropServices; +using System; +using Xunit.Abstractions; +using static System.Reflection.Metadata.BlobBuilder; + +namespace Deephaven.DhClientTests; + +public class TickingTest { + private readonly ITestOutputHelper _output; + + public TickingTest(ITestOutputHelper output) { + _output = output; + } + + [Fact] + public void EventuallyReaches10Rows() { + const Int64 maxRows = 10; + using var ctx = CommonContextForTests.Create(new ClientOptions()); + var thm = ctx.Client.Manager; + + using var table = thm.TimeTable(TimeSpan.FromMilliseconds(500)).Update("II = ii"); + var callback = new ReachesNRowsCallback(_output, maxRows); + using var cookie = table.Subscribe(callback); + + while (true) { + var (done, errorText) = callback.WaitForUpdate(); + if (done) { + break; + } + if (errorText != null) { + throw new Exception(errorText); + } + } + + table.Unsubscribe(cookie); + } + + [Fact] + public void AFourthTest() { + Assert.Equal(7, 7); + } + + [Fact] + public void AFifthTest() { + Assert.Equal(7, 7); + } + + [Fact] + public void AllEventuallyGreaterThan10() { + const Int64 maxRows = 10; + using var ctx = CommonContextForTests.Create(new ClientOptions()); + var thm = ctx.Client.Manager; + + using var table = thm.TimeTable(TimeSpan.FromMilliseconds(500)) + .View("Key = (long)(ii % 10)", "Value = ii") + .LastBy("Key"); + + var callback = new AllValuesGreaterThanNCallback(_output, maxRows); + using var cookie = table.Subscribe(callback); + + while (true) { + var (done, errorText) = callback.WaitForUpdate(); + if (done) { + break; + } + if (errorText != null) { + throw new Exception(errorText); + } + } + + table.Unsubscribe(cookie); + } + + [Fact] + public void AllDataEventuallyPresent() { + const Int64 maxRows = 10; + using var ctx = CommonContextForTests.Create(new ClientOptions()); + var thm = ctx.Client.Manager; + + using var table = thm.TimeTable("PT0:00:0.5") + .Update( + "II = (int)((ii * 7) % 10)", + "Chars = II == 5 ? null : (char)(II + 'a')", + "Bytes = II == 5 ? null : (byte)II", + "Shorts = II == 5 ? null : (short)II", + "Ints = II == 5 ? null : (int)II", + "Longs = II == 5 ? null : (long)II", + "Floats = II == 5 ? null : (float)II", + "Doubles = II == 5 ? null : (double)II", + "Bools = II == 5 ? null : ((II % 2) == 0)", + "Strings = II == 5 ? null : (`hello ` + II)", + "DateTimes = II == 5 ? null : ('2001-03-01T12:34:56Z' + II)") + .LastBy("II") + .Sort(SortPair.Ascending("II")) + .DropColumns("Timestamp", "II"); + + var callback = new WaitForPopulatedTableCallback(_output, maxRows); + using var cookie = table.Subscribe(callback); + + while (true) { + var (done, errorText) = callback.WaitForUpdate(); + if (done) { + break; + } + if (errorText != null) { + throw new Exception(errorText); + } + } + + table.Unsubscribe(cookie); + } +} + +public abstract class CommonBase : ITickingCallback { + private readonly object _sync = new(); + private bool _done = false; + private string? _errorText = null; + + public void OnFailure(string errorText) { + lock (_sync) { + _errorText = errorText; + Monitor.PulseAll(_sync); + } + } + + public (bool, string?) WaitForUpdate() { + lock (_sync) { + while (true) { + if (_done || _errorText != null) { + return (_done, _errorText); + } + + Monitor.Wait(_sync); + } + } + } + + public abstract void OnTick(TickingUpdate update); + + protected void NotifyDone() { + lock (_sync) { + _done = true; + Monitor.PulseAll(_sync); + } + } +} + +public sealed class ReachesNRowsCallback : CommonBase { + private readonly ITestOutputHelper _output; + private readonly Int64 _targetRows; + + public ReachesNRowsCallback(ITestOutputHelper output, Int64 targetRows) { + _output = output; + _targetRows = targetRows; + } + + public override void OnTick(TickingUpdate update) { + _output.WriteLine($"=== The Full Table ===\n{update.Current.ToString(true, true)}"); + if (update.Current.NumRows >= _targetRows) { + NotifyDone(); + } + } +} + +public sealed class WaitForPopulatedTableCallback : CommonBase { + private readonly ITestOutputHelper _output; + private readonly Int64 _target; + + public WaitForPopulatedTableCallback(ITestOutputHelper output, Int64 target) { + _output = output; + _target = target; + } + + public override void OnTick(TickingUpdate update) { + _output.WriteLine($"=== The Full Table ===\n{update.Current.ToString(true, true)}"); + + var current = update.Current; + + if (current.NumRows < _target) { + return; + } + + if (current.NumRows > _target) { + // table has more rows than expected. + var message = $"Expected table to have {_target} rows, got {current.NumRows}"; + throw new Exception(message); + } + + var charData = new List(); + var int8Data = new List(); + var int16Data = new List(); + var int32Data = new List(); + var int64Data = new List(); + var floatData = new List(); + var doubleData = new List(); + var boolData = new List(); + var stringData = new List(); + var dateTimeData = new List(); + + var dateTimeStart = DhDateTime.Parse("2001-03-01T12:34:56Z"); + + for (Int64 i = 0; i != _target; ++i) { + charData.Add((char)('a' + i)); + int8Data.Add((sbyte)(i)); + int16Data.Add((Int16)(i)); + int32Data.Add((Int32)(i)); + int64Data.Add((Int64)(i)); + floatData.Add((float)(i)); + doubleData.Add((double)(i)); + boolData.Add((i % 2) == 0); + stringData.Add($"hello {i}"); + dateTimeData.Add(DhDateTime.FromNanos(dateTimeStart.Nanos + i)); + } + + if (_target == 0) { + throw new Exception("Target should not be 0"); + } + + var t2 = (int)(_target / 2); + // Set the middle element to the unset optional, which for the purposes of this test is + // our representation of null. + charData[t2] = null; + int8Data[t2] = null; + int16Data[t2] = null; + int32Data[t2] = null; + int64Data[t2] = null; + floatData[t2] = null; + doubleData[t2] = null; + boolData[t2] = null; + stringData[t2] = null; + dateTimeData[t2] = null; + + var tc = new TableComparer(); + tc.AddColumn("Chars", charData); + tc.AddColumn("Bytes", int8Data); + tc.AddColumn("Shorts", int16Data); + tc.AddColumn("Ints", int32Data); + tc.AddColumn("Longs", int64Data); + tc.AddColumn("Floats", floatData); + tc.AddColumn("Doubles", doubleData); + tc.AddColumn("Bools", boolData); + tc.AddColumn("Strings", stringData); + tc.AddColumn("DateTimes", dateTimeData); + + tc.AssertEqualTo(current); + NotifyDone(); + } +} + +public sealed class AllValuesGreaterThanNCallback : CommonBase { + private readonly ITestOutputHelper _output; + private readonly Int64 _target; + + public AllValuesGreaterThanNCallback(ITestOutputHelper output, Int64 target) { + _output = output; + _target = target; + } + + public override void OnTick(TickingUpdate update) { + _output.WriteLine($"=== The Full Table ===\n{update.Current.ToString(true, true)}"); + + var current = update.Current; + + if (current.NumRows == 0) { + return; + } + + var (values, nulls) = current.GetColumn("Value"); + var data = (Int64[])values; + var allGreater = data.All(elt => elt > _target); + if (allGreater) { + NotifyDone(); + } + } +} diff --git a/csharp/client/DhClientTests/UngroupTest.cs b/csharp/client/DhClientTests/UngroupTest.cs new file mode 100644 index 00000000000..adeea94715b --- /dev/null +++ b/csharp/client/DhClientTests/UngroupTest.cs @@ -0,0 +1,11 @@ +using Deephaven.DeephavenClient; +using System; + +namespace Deephaven.DhClientTests; + +public class UngroupTest { + [Fact] + public void UngroupColumns() { + // This test is disabled. Get it working when the C++ side works + } +} diff --git a/csharp/client/DhClientTests/UpdateByTest.cs b/csharp/client/DhClientTests/UpdateByTest.cs new file mode 100644 index 00000000000..42fee76c3cb --- /dev/null +++ b/csharp/client/DhClientTests/UpdateByTest.cs @@ -0,0 +1,275 @@ +using Deephaven.DeephavenClient; +using Deephaven.DeephavenClient.Utility; +using System; +using Deephaven.DeephavenClient.UpdateBy; +using Xunit.Abstractions; +using static Deephaven.DeephavenClient.UpdateBy.UpdateByOperation; + +namespace Deephaven.DhClientTests; + +public class UpdateByTest { + const int NumCols = 5; + const int NumRows = 1000; + + private readonly ITestOutputHelper _output; + + public UpdateByTest(ITestOutputHelper output) { + _output = output; + } + + [Fact] + public void SimpleCumSum() { + using var ctx = CommonContextForTests.Create(new ClientOptions()); + var tm = ctx.Client.Manager; + + var source = tm.EmptyTable(10).Update("Letter = (i % 2 == 0) ? `A` : `B`", "X = i"); + var result = source.UpdateBy(new[] { CumSum(new[] { "SumX = X" }) }, new[] { "Letter" }); + var filtered = result.Select("SumX"); + var tc = new TableComparer(); + tc.AddColumn("SumX", new Int64[] { 0, 1, 2, 4, 6, 9, 12, 16, 20, 25 }); + tc.AssertEqualTo(filtered); + } + + [Fact] + public void SimpleOps() { + using var ctx = CommonContextForTests.Create(new ClientOptions()); + var tm = ctx.Client.Manager; + + var tables = MakeTables(tm); + var simpleOps = MakeSimpleOps(); + + for (var opIndex = 0; opIndex != simpleOps.Length; ++opIndex) { + var op = simpleOps[opIndex]; + for (var tableIndex = 0; tableIndex != tables.Length; ++tableIndex) { + var table = tables[tableIndex]; + _output.WriteLine($"Processing op {opIndex} on Table {tableIndex}"); + using var result = table.UpdateBy(new[] { op }, new[] { "e" }); + Assert.Equal(table.IsStatic, result.IsStatic); + Assert.Equal(2 + table.NumCols, result.NumCols); + Assert.True(result.NumRows >= table.NumRows); + } + } + } + + [Fact] + public void EmOps() { + using var ctx = CommonContextForTests.Create(new ClientOptions()); + var tm = ctx.Client.Manager; + + var tables = MakeTables(tm); + var emOps = MakeEmOps(); + + for (var opIndex = 0; opIndex != emOps.Length; ++opIndex) { + var op = emOps[opIndex]; + for (var tableIndex = 0; tableIndex != tables.Length; ++tableIndex) { + var table = tables[tableIndex]; + _output.WriteLine($"Processing op {opIndex} on Table {tableIndex}"); + using var result = table.UpdateBy(new[] { op }, new[] { "b" }); + Assert.Equal(table.IsStatic, result.IsStatic); + Assert.Equal(1 + table.NumCols, result.NumCols); + if (result.IsStatic) { + Assert.Equal(result.NumRows, table.NumRows); + } + } + } + } + + [Fact] + public void RollingOps() { + using var ctx = CommonContextForTests.Create(new ClientOptions()); + var tm = ctx.Client.Manager; + + var tables = MakeTables(tm); + var rollingOps = MakeRollingOps(); + + for (var opIndex = 0; opIndex != rollingOps.Length; ++opIndex) { + var op = rollingOps[opIndex]; + for (var tableIndex = 0; tableIndex != tables.Length; ++tableIndex) { + var table = tables[tableIndex]; + _output.WriteLine($"Processing op {opIndex} on Table {tableIndex}"); + using var result = table.UpdateBy(new[] { op }, new[] { "c" }); + Assert.Equal(table.IsStatic, result.IsStatic); + Assert.Equal(2 + table.NumCols, result.NumCols); + Assert.True(result.NumRows >= table.NumRows); + } + } + } + + [Fact] + public void MultipleOps() { + using var ctx = CommonContextForTests.Create(new ClientOptions()); + var tm = ctx.Client.Manager; + + var tables = MakeTables(tm); + var multipleOps = new[] { + CumSum(new[] { "sum_a=a", "sum_b=b" }), + CumSum(new[] { "max_a=a", "max_d=d" }), + EmaTick(10, new[] { "ema_d=d", "ema_e=e" }), + EmaTime("Timestamp", "PT00:00:00.1", new[] { "ema_time_d=d", "ema_time_e=e" }), + RollingWavgTick("b", new[] { "rwavg_a = a", "rwavg_d = d" }, 10) + }; + + for (var tableIndex = 0; tableIndex != tables.Length; ++tableIndex) { + var table = tables[tableIndex]; + _output.WriteLine($"Processing table {tableIndex}"); + using var result = table.UpdateBy(multipleOps, new[] { "c" }); + Assert.Equal(table.IsStatic, result.IsStatic); + Assert.Equal(10 + table.NumCols, result.NumCols); + if (result.IsStatic) { + Assert.Equal(result.NumRows, table.NumRows); + } + } + } + + private TableHandle[] MakeTables(TableHandleManager tm) { + var staticTable = MakeRandomTable(tm).Update("Timestamp=now()"); + var tickingTable = tm.TimeTable(TimeSpan.FromSeconds(1)) + .Update("a = i", "b = i*i % 13", "c = i * 13 % 23", "d = a + b", "e = a - b"); + return new[] { staticTable, tickingTable }; + } + + private static TableHandle MakeRandomTable(TableHandleManager tm) { + var rng = new Random(12345); + + var maker = new TableMaker(); + if (NumCols > 26) { + throw new ArgumentException("NumCols constant is too big for this test"); + } + + for (var col = 0; col != NumCols; ++col) { + var name = ((char)('a' + col)).ToString(); + var values = new Int32[NumRows]; + for (var i = 0; i != NumRows; ++i) { + values[i] = rng.Next(1000); + } + + maker.AddColumn(name, values); + } + + return maker.MakeTable(tm); + } + + private static UpdateByOperation[] MakeSimpleOps() { + var simpleOpPairs = new[] { "UA=a", "UB=b" }; + var result = new[] { + CumSum(simpleOpPairs), + CumProd(simpleOpPairs), + CumMin(simpleOpPairs), + CumMax(simpleOpPairs), + ForwardFill(simpleOpPairs), + Delta(simpleOpPairs), + Delta(simpleOpPairs, DeltaControl.NullDominates), + Delta(simpleOpPairs, DeltaControl.ValueDominates), + Delta(simpleOpPairs, DeltaControl.ZeroDominates) + }; + return result; + } + + private static UpdateByOperation[] MakeEmOps() { + var emOpControl = new OperationControl(BadDataBehavior.Throw, BadDataBehavior.Reset, + MathContext.Unlimited); + + var result = new [] { + // exponential moving average + EmaTick(100, new[] { "ema_a = a" }), + EmaTick(100, new[] { "ema_a = a" }, emOpControl), + EmaTime("Timestamp", 10, new[] { "ema_a = a" }), + EmaTime("Timestamp", "PT00:00:00.001", new[] { "ema_c = c" }, emOpControl), + EmaTime("Timestamp", "PT1M", new[] { "ema_c = c" }), + EmaTime("Timestamp", "PT1M", new[] { "ema_c = c" }, emOpControl), + // exponential moving sum + EmsTick(100, new[] { "ems_a = a" }), + EmsTick(100, new[] { "ems_a = a" }, emOpControl), + EmsTime("Timestamp", 10, new[] { "ems_a = a"}), + EmsTime("Timestamp", "PT00:00:00.001", new[] { "ems_c = c" }, emOpControl), + EmsTime("Timestamp", "PT1M", new[] { "ema_c = c" }), + EmsTime("Timestamp", "PT1M", new[] { "ema_c = c" }, emOpControl), + // exponential moving minimum + EmminTick(100, new[] { "emmin_a = a" }), + EmminTick(100, new[] { "emmin_a = a" }, emOpControl), + EmminTime("Timestamp", 10, new[] { "emmin_a = a" }), + EmminTime("Timestamp", "PT00:00:00.001", new[] { "emmin_c = c" }, emOpControl), + EmminTime("Timestamp", "PT1M", new[] { "ema_c = c" }), + EmminTime("Timestamp", "PT1M", new[] { "ema_c = c" }, emOpControl), + // exponential moving maximum + EmmaxTick(100, new[] { "emmax_a = a" }), + EmmaxTick(100, new[] { "emmax_a = a" }, emOpControl), + EmmaxTime("Timestamp", 10, new[] { "emmax_a = a" }), + EmmaxTime("Timestamp", "PT00:00:00.001", new[] { "emmax_c = c" }, emOpControl), + EmmaxTime("Timestamp", "PT1M", new[] { "ema_c = c" }), + EmmaxTime("Timestamp", "PT1M", new[] { "ema_c = c" }, emOpControl), + // exponential moving standard deviation + EmstdTick(100, new[] { "emstd_a = a" }), + EmstdTick(100, new[] { "emstd_a = a" }, emOpControl), + EmstdTime("Timestamp", 10, new[] { "emstd_a = a" }), + EmstdTime("Timestamp", "PT00:00:00.001", new[] { "emtd_c = c" }, emOpControl), + EmstdTime("Timestamp", "PT1M", new[] { "ema_c = c" }), + EmstdTime("Timestamp", "PT1M", new[] { "ema_c = c" }, emOpControl) + }; + + return result; + } + + private static UpdateByOperation[] MakeRollingOps() { + static TimeSpan Secs(int s) => TimeSpan.FromSeconds(s); + + // exponential moving average + var result = new UpdateByOperation[] { + // rolling sum + RollingSumTick(new[] {"rsum_a = a", "rsum_d = d"}, 10), + RollingSumTick(new[] { "rsum_a = a", "rsum_d = d"}, 10, 10), + RollingSumTime("Timestamp", new[] { "rsum_b = b", "rsum_e = e"}, "PT00:00:10"), + RollingSumTime("Timestamp", new[] { "rsum_b = b", "rsum_e = e"}, Secs(10), Secs(-10)), + RollingSumTime("Timestamp", new[] { "rsum_b = b", "rsum_e = e"}, "PT30S", "-PT00:00:20"), + // rolling group + RollingGroupTick(new[] { "rgroup_a = a", "rgroup_d = d"}, 10), + RollingGroupTick(new[] { "rgroup_a = a", "rgroup_d = d"}, 10, 10), + RollingGroupTime("Timestamp", new[] { "rgroup_b = b", "rgroup_e = e"}, "PT00:00:10"), + RollingGroupTime("Timestamp", new[] { "rgroup_b = b", "rgroup_e = e"}, Secs(10), Secs(-10)), + RollingGroupTime("Timestamp", new[] { "rgroup_b = b", "rgroup_e = e"}, "PT30S", "-PT00:00:20"), + // rolling average + RollingAvgTick(new[] { "ravg_a = a", "ravg_d = d"}, 10), + RollingAvgTick(new[] { "ravg_a = a", "ravg_d = d"}, 10, 10), + RollingAvgTime("Timestamp", new[] { "ravg_b = b", "ravg_e = e"}, "PT00:00:10"), + RollingAvgTime("Timestamp", new[] { "ravg_b = b", "ravg_e = e"}, Secs(10), Secs(-10)), + RollingAvgTime("Timestamp", new[] { "ravg_b = b", "ravg_e = e"}, "PT30S", "-PT00:00:20"), + // rolling minimum + RollingMinTick(new[] { "rmin_a = a", "rmin_d = d"}, 10), + RollingMinTick(new[] { "rmin_a = a", "rmin_d = d"}, 10, 10), + RollingMinTime("Timestamp", new[] { "rmin_b = b", "rmin_e = e"}, "PT00:00:10"), + RollingMinTime("Timestamp", new[] { "rmin_b = b", "rmin_e = e"}, Secs(10), Secs(-10)), + RollingMinTime("Timestamp", new[] { "rmin_b = b", "rmin_e = e"}, "PT30S", "-PT00:00:20"), + // rolling maximum + RollingMaxTick(new[] { "rmax_a = a", "rmax_d = d"}, 10), + RollingMaxTick(new[] { "rmax_a = a", "rmax_d = d"}, 10, 10), + RollingMaxTime("Timestamp", new[] { "rmax_b = b", "rmax_e = e"}, "PT00:00:10"), + RollingMaxTime("Timestamp", new[] { "rmax_b = b", "rmax_e = e"}, Secs(10), Secs(-10)), + RollingMaxTime("Timestamp", new[] { "rmax_b = b", "rmax_e = e"}, "PT30S", "-PT00:00:20"), + // rolling product + RollingProdTick(new[] { "rprod_a = a", "rprod_d = d"}, 10), + RollingProdTick(new[] { "rprod_a = a", "rprod_d = d"}, 10, 10), + RollingProdTime("Timestamp", new[] { "rprod_b = b", "rprod_e = e"}, "PT00:00:10"), + RollingProdTime("Timestamp", new[] { "rprod_b = b", "rprod_e = e"}, Secs(10), Secs(-10)), + RollingProdTime("Timestamp", new[] { "rprod_b = b", "rprod_e = e"}, "PT30S", "-PT00:00:20"), + // rolling count + RollingCountTick(new[] { "rcount_a = a", "rcount_d = d"}, 10), + RollingCountTick(new[] { "rcount_a = a", "rcount_d = d"}, 10, 10), + RollingCountTime("Timestamp", new[] { "rcount_b = b", "rcount_e = e"}, "PT00:00:10"), + RollingCountTime("Timestamp", new[] { "rcount_b = b", "rcount_e = e"}, Secs(10), Secs(-10)), + RollingCountTime("Timestamp", new[] { "rcount_b = b", "rcount_e = e"}, "PT30S", "-PT00:00:20"), + // rolling standard deviation + RollingStdTick(new[] { "rstd_a = a", "rstd_d = d"}, 10), + RollingStdTick(new[] { "rstd_a = a", "rstd_d = d"}, 10, 10), + RollingStdTime("Timestamp", new[] { "rstd_b = b", "rstd_e = e"}, "PT00:00:10"), + RollingStdTime("Timestamp", new[] { "rstd_b = b", "rstd_e = e"}, Secs(10), Secs(-10)), + RollingStdTime("Timestamp", new[] { "rstd_b = b", "rstd_e = e"}, "PT30S", "-PT00:00:20"), + // rolling weighted average (using "b" as the weight column) + RollingWavgTick("b", new[] { "rwavg_a = a", "rwavg_d = d"}, 10), + RollingWavgTick("b", new[] { "rwavg_a = a", "rwavg_d = d"}, 10, 10), + RollingWavgTime("Timestamp", "b", new[] { "rwavg_b = b", "rwavg_e = e"}, "PT00:00:10"), + RollingWavgTime("Timestamp", "b", new[] { "rwavg_b = b", "rwavg_e = e"}, Secs(10), Secs(-10)), + RollingWavgTime("Timestamp", "b", new[] { "rwavg_b = b", "rwavg_e = e"}, "PT30S", "-PT00:00:20") + }; + return result; + } +} diff --git a/csharp/client/DhClientTests/ValidationTest.cs b/csharp/client/DhClientTests/ValidationTest.cs new file mode 100644 index 00000000000..edda3e13528 --- /dev/null +++ b/csharp/client/DhClientTests/ValidationTest.cs @@ -0,0 +1,101 @@ +using Deephaven.DeephavenClient; +using Deephaven.DeephavenClient.Utility; +using System; +using Xunit.Abstractions; + +namespace Deephaven.DhClientTests; + +public class ValidationTest { + private readonly ITestOutputHelper _output; + + public ValidationTest(ITestOutputHelper output) { + _output = output; + } + + [Fact] + public void Select() { + var badSelects = new[] { + new[] { "X = 3)" }, + new[] { "S = `hello`", "T = java.util.regex.Pattern.quote(S)" }, // Pattern.quote not on whitelist + new[] { "X = Math.min(3, 4)" } // Math.min not on whitelist + }; + var goodSelects = new[] { + new[] { "X = 3" }, + new[] { "S = `hello`", "T = S.length()" }, // instance methods of String ok + new[] { "X = min(3, 4)" }, // "builtin" from GroovyStaticImports + new[] { "X = isFinite(3)" }, // another builtin from GroovyStaticImports + }; + + using var ctx = CommonContextForTests.Create(new ClientOptions()); + var thm = ctx.Client.Manager; + using var staticTable = thm.EmptyTable(10) + .Update("X = 12", "S = `hello`"); + TestSelectsHelper("static Table", staticTable, badSelects, goodSelects); + } + + [Fact] + public void Where() { + var badWheres = new[] { + "X > 3)", // syntax error + "S = new String(`hello`)", // new not allowed + "S = java.util.regex.Pattern.quote(S)", // Pattern.quote not on whitelist + "X = Math.min(3, 4)" // Math.min not on whitelist + }; + + var goodWheres = new[] { + "X = 3", + "S = `hello`", + "S.length() = 17", // instance methods of String ok + "X = min(3, 4)", // "builtin" from GroovyStaticImports + "X = isFinite(3)", // another builtin from GroovyStaticImports + "X in 3, 4, 5", + }; + + using var ctx = CommonContextForTests.Create(new ClientOptions()); + var thm = ctx.Client.Manager; + using var staticTable = thm.EmptyTable(10) + .Update("X = 12", "S = `hello`"); + TestWheresHelper("static Table", staticTable, badWheres, goodWheres); + } + + private void TestWheresHelper(string what, TableHandle table, + IEnumerable badWheres, IEnumerable goodWheres) { + foreach (var bw in badWheres) { + try { + _output.WriteLine($"Trying {what} {bw}"); + using var dummy = table.Where(bw); + } catch (Exception e) { + _output.WriteLine($"{what}: {bw}: Failed *as expected* with: {e.Message}"); + continue; + } + + throw new Exception($"{what}: {bw}: Expected to fail, but succeeded"); + } + + foreach (var gw in goodWheres) { + using var dummy = table.Where(gw); + _output.WriteLine($"{what}: {gw}: Succeeded as expected"); + } + } + + private void TestSelectsHelper(string what, TableHandle table, + IEnumerable badSelects, IEnumerable goodSelects) { + foreach (var bs in badSelects) { + var printable = string.Join(", ", bs); + try { + using var dummy = table.Select(bs); + } catch (Exception e) { + _output.WriteLine($"{what}: {printable}: Failed as expected with: {e.Message}"); + continue; + } + + throw new Exception($"{what}: {printable}: Expected to fail, but succeeded"); + } + + foreach (var gs in goodSelects) { + var printable = string.Join(", ", gs); + using var dummy = table.Select(gs); + _output.WriteLine($"{what}: {printable}: Succeeded as expected"); + } + } +} diff --git a/csharp/client/DhClientTests/ViewTest.cs b/csharp/client/DhClientTests/ViewTest.cs new file mode 100644 index 00000000000..3e12801b0c7 --- /dev/null +++ b/csharp/client/DhClientTests/ViewTest.cs @@ -0,0 +1,25 @@ +using Deephaven.DeephavenClient; +using Xunit.Abstractions; + +namespace Deephaven.DhClientTests; + +public class ViewTest { + [Fact] + public void View() { + using var ctx = CommonContextForTests.Create(new ClientOptions()); + var table = ctx.TestTable; + + // literal strings + using var t1 = table.LastBy("Ticker").View("Ticker", "Close", "Volume"); + + var tickerData = new[] { "XRX", "XYZZY", "IBM", "GME", "AAPL", "ZNGA", "T" }; + var closeData = new[] { 53.8, 88.5, 38.7, 453, 26.7, 544.9, 13.4 }; + var volData = new Int64[] { 87000, 6060842, 138000, 138000000, 19000, 48300, 1500 }; + + var tc = new TableComparer(); + tc.AddColumn("Ticker", tickerData); + tc.AddColumn("Close", closeData); + tc.AddColumn("Volume", volData); + tc.AssertEqualTo(t1); + } +}