Copilot commented on code in PR #4983: URL: https://github.com/apache/arrow-adbc/pull/4983#discussion_r4425886529
########## csharp/src/Apache.Arrow.Adbc/C/CAdbcDriverExporter.Streams.cs: ########## @@ -0,0 +1,353 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using Apache.Arrow.C; +using Apache.Arrow.Ipc; + +namespace Apache.Arrow.Adbc.C +{ + public partial class CAdbcDriverExporter + { +#if NET5_0_OR_GREATER + private static unsafe delegate* unmanaged<CArrowArrayStream*, CArrowSchema*, int> StreamGetSchemaPtr => &GetStreamSchema; + private static unsafe delegate* unmanaged<CArrowArrayStream*, CArrowArray*, int> StreamGetNextPtr => &GetStreamNext; + private static unsafe delegate* unmanaged<CArrowArrayStream*, byte*> StreamGetLastErrorPtr => &GetStreamLastError; + private static unsafe delegate* unmanaged<CArrowArrayStream*, void> StreamReleasePtr => &ReleaseStream; + private static unsafe delegate* unmanaged<CArrowArrayStream*, AdbcStatusCode*, CAdbcError*> ErrorFromArrayStreamPtr => &GetErrorFromArrayStream; +#else + private static unsafe IntPtr StreamGetSchemaPtr = NativeDelegate<ArrayStreamGetSchema>.AsNativePointer(GetStreamSchema); + private static unsafe IntPtr StreamGetNextPtr = NativeDelegate<ArrayStreamGetNext>.AsNativePointer(GetStreamNext); + private static unsafe IntPtr StreamGetLastErrorPtr = NativeDelegate<ArrayStreamGetLastError>.AsNativePointer(GetStreamLastError); + private static unsafe IntPtr StreamReleasePtr = NativeDelegate<ArrayStreamRelease>.AsNativePointer(ReleaseStream); Review Comment: On .NET Framework these pointers are backed by ADBC-specific delegate types, so an in-process Apache.Arrow consumer cannot invoke or release this stream using Arrow's own callback delegates: `Marshal.GetDelegateForFunctionPointer` returns the original delegate and the cast fails. The previous `CArrowArrayStreamExporter` produced callbacks that Arrow's importer and `CArrowArrayStream.Free` could consume. Please preserve interoperability with those consumers, not just with the new `ImportedArrayStream` wrapper. ########## csharp/src/Apache.Arrow.Adbc/C/CAdbcDriverImporter.Streams.cs: ########## @@ -0,0 +1,224 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using Apache.Arrow.Adbc.Extensions; +using Apache.Arrow.C; +using Apache.Arrow.Ipc; + +namespace Apache.Arrow.Adbc.C +{ + public static partial class CAdbcDriverImporter + { + /// <summary> + /// A result stream imported from a driver. When the stream fails, it asks the driver + /// for the ADBC error behind the failure (see <c>AdbcErrorFromArrayStream</c>) and + /// throws it as an <see cref="AdbcException"/>. + /// </summary> + private sealed unsafe class ImportedArrayStream : IArrowArrayStream + { + private readonly ImportedAdbcDriver? _driver; + private readonly Schema _schema; + private CArrowArrayStream* _cStream; + + /// <summary> + /// Imports <paramref name="cStream"/>, which must have been allocated with + /// <see cref="CArrowArrayStream.Create"/>. On success, the imported stream owns + /// it; on failure, the caller still does. + /// </summary> + public ImportedArrayStream(ImportedAdbcDriver? driver, CArrowArrayStream* cStream) + { + if (NativeArrayStream.From(cStream)->release == default) + { + throw new ArgumentException("Tried to import an array stream that has already been released.", nameof(cStream)); + } + + _driver = driver; + + CArrowSchema* cSchema = CArrowSchema.Create(); + try + { + int errno = GetSchema(cStream, cSchema); + if (errno != 0) + { + throw StreamError(cStream, errno); + } + _schema = CArrowSchemaImporter.ImportSchema(cSchema); + } + finally + { + CArrowSchema.Free(cSchema); + } + + _cStream = cStream; + } + + ~ImportedArrayStream() + { + Dispose(); + } + + public Schema Schema => _schema; + + public ValueTask<RecordBatch?> ReadNextRecordBatchAsync(CancellationToken cancellationToken = default) + { + if (_cStream == null) + { + throw new ObjectDisposedException(nameof(ImportedArrayStream)); + } + + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask<RecordBatch?>(Task.FromCanceled<RecordBatch?>(cancellationToken)); + } + + try + { + return new ValueTask<RecordBatch?>(ReadNext()); + } + catch (Exception e) + { + return new ValueTask<RecordBatch?>(Task.FromException<RecordBatch?>(e)); + } + } + + private RecordBatch? ReadNext() + { + CArrowArray* cArray = CArrowArray.Create(); + try + { + int errno = GetNext(_cStream, cArray); + if (errno != 0) + { + throw StreamError(_cStream, errno); + } + + // A released array marks the end of the stream + if (NativeArray.From(cArray)->release == IntPtr.Zero) + { + return null; + } + + return CArrowArrayImporter.ImportRecordBatch(cArray, _schema); + } + finally + { + CArrowArray.Free(cArray); + } + } + + public void Dispose() + { + CArrowArrayStream* cStream = _cStream; + if (cStream != null) + { + _cStream = null; + FreeStream(cStream); + } + GC.SuppressFinalize(this); + } + + /// <summary> + /// Releases and frees a stream allocated with <see cref="CArrowArrayStream.Create"/>. + /// </summary> + /// <remarks> + /// Unlike <see cref="CArrowArrayStream.Free"/>, this can release a stream that + /// <see cref="CAdbcDriverExporter"/> exported in the same process on .NET Framework, + /// where a callback created from a managed delegate can only be called through a + /// delegate of the same type. + /// </remarks> + public static void FreeStream(CArrowArrayStream* cStream) + { + NativeArrayStream* nativeStream = NativeArrayStream.From(cStream); + if (nativeStream->release != default) + { +#if NET5_0_OR_GREATER + nativeStream->release(cStream); +#else + Marshal.GetDelegateForFunctionPointer<ArrayStreamRelease>(nativeStream->release)(cStream); +#endif + nativeStream->release = default; + } + CArrowArrayStream.Free(cStream); + } + + /// <summary> + /// Builds the exception for a stream call that returned <paramref name="errno"/>: + /// the driver's ADBC error if it reports one, and otherwise the stream's last error. + /// </summary> + private Exception StreamError(CArrowArrayStream* cStream, int errno) + { + if (_driver != null && _driver.DriverVersion >= AdbcVersion.Version_1_1_0) + { + ref CAdbcDriver nativeDriver = ref _driver.DriverUnsafe; + if (nativeDriver.ErrorFromArrayStream != default) + { + AdbcStatusCode status = AdbcStatusCode.UnknownError; +#if NET5_0_OR_GREATER + CAdbcError* error = nativeDriver.ErrorFromArrayStream(cStream, &status); +#else + CAdbcError* error = Marshal.GetDelegateForFunctionPointer<ErrorFromArrayStream>(nativeDriver.ErrorFromArrayStream)(cStream, &status); +#endif + // The stream owns the error, so it must not be released here + if (error != null) + { + IReadOnlyList<KeyValuePair<string, byte[]>>? details = ReadErrorDetails(_driver, error); + return new ImportedAdbcException( + ref *error, + status == AdbcStatusCode.Success ? AdbcStatusCode.UnknownError : status, + details); + } + } + } + + NativeArrayStream* nativeStream = NativeArrayStream.From(cStream); +#if NET5_0_OR_GREATER + byte* message = nativeStream->get_last_error(cStream); +#else + byte* message = Marshal.GetDelegateForFunctionPointer<ArrayStreamGetLastError>(nativeStream->get_last_error)(cStream); +#endif + return new Exception(message == null + ? $"Array stream operation failed with no message. Error code: {errno}" + : MarshalExtensions.PtrToStringUTF8(message)); + } + + private static int GetSchema(CArrowArrayStream* cStream, CArrowSchema* cSchema) + { + NativeArrayStream* nativeStream = NativeArrayStream.From(cStream); +#if NET5_0_OR_GREATER + return nativeStream->get_schema(cStream, cSchema); +#else + return Marshal.GetDelegateForFunctionPointer<ArrayStreamGetSchema>(nativeStream->get_schema)(cStream, cSchema); Review Comment: On .NET Framework, importing an in-process `CArrowArrayStreamExporter` result throws `InvalidCastException`: its managed callback has Arrow's delegate type, not `ArrayStreamGetSchema`. The same mismatch affects `get_next`, `get_last_error`, and `FreeStream` at line 156, where cleanup of a rejected `BindStream` masks the driver error and leaves the stream unreleased. Use a callback bridge compatible with both Arrow-exported and ADBC-exported streams, including their release callbacks. ########## csharp/src/Apache.Arrow.Adbc/C/CAdbcDriverExporter.Streams.cs: ########## @@ -0,0 +1,353 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using Apache.Arrow.C; +using Apache.Arrow.Ipc; + +namespace Apache.Arrow.Adbc.C +{ + public partial class CAdbcDriverExporter + { +#if NET5_0_OR_GREATER + private static unsafe delegate* unmanaged<CArrowArrayStream*, CArrowSchema*, int> StreamGetSchemaPtr => &GetStreamSchema; + private static unsafe delegate* unmanaged<CArrowArrayStream*, CArrowArray*, int> StreamGetNextPtr => &GetStreamNext; + private static unsafe delegate* unmanaged<CArrowArrayStream*, byte*> StreamGetLastErrorPtr => &GetStreamLastError; + private static unsafe delegate* unmanaged<CArrowArrayStream*, void> StreamReleasePtr => &ReleaseStream; + private static unsafe delegate* unmanaged<CArrowArrayStream*, AdbcStatusCode*, CAdbcError*> ErrorFromArrayStreamPtr => &GetErrorFromArrayStream; +#else + private static unsafe IntPtr StreamGetSchemaPtr = NativeDelegate<ArrayStreamGetSchema>.AsNativePointer(GetStreamSchema); + private static unsafe IntPtr StreamGetNextPtr = NativeDelegate<ArrayStreamGetNext>.AsNativePointer(GetStreamNext); + private static unsafe IntPtr StreamGetLastErrorPtr = NativeDelegate<ArrayStreamGetLastError>.AsNativePointer(GetStreamLastError); + private static unsafe IntPtr StreamReleasePtr = NativeDelegate<ArrayStreamRelease>.AsNativePointer(ReleaseStream); + private static unsafe IntPtr ErrorFromArrayStreamPtr = NativeDelegate<ErrorFromArrayStream>.AsNativePointer(GetErrorFromArrayStream); +#endif + + /// <summary> + /// Exports a stream produced by the managed driver. The exported stream takes + /// ownership on success; if exporting fails, nobody else will dispose it. + /// </summary> + /// <remarks> + /// When reading the stream fails, the exported stream keeps the exception so that + /// <c>AdbcErrorFromArrayStream</c> can report its status, SQLSTATE, vendor code and + /// details, and returns an errno that corresponds to the status. + /// </remarks> + private unsafe static void ExportStream(IArrowArrayStream? stream, CArrowArrayStream* cStream) + { + try + { + if (stream == null) + { + throw new ArgumentNullException(nameof(stream)); + } + if (cStream == null) + { + throw new ArgumentNullException(nameof(cStream)); + } + + NativeArrayStream* nativeStream = NativeArrayStream.From(cStream); + nativeStream->private_data = (void*)GCHandle.ToIntPtr(GCHandle.Alloc(new ExportedStream(stream))); + nativeStream->get_schema = StreamGetSchemaPtr; + nativeStream->get_next = StreamGetNextPtr; + nativeStream->get_last_error = StreamGetLastErrorPtr; + nativeStream->release = StreamReleasePtr; + } + catch + { + stream?.Dispose(); + throw; + } + } + + private unsafe static ExportedStream? GetExportedStream(CArrowArrayStream* cStream) + { + NativeArrayStream* nativeStream = NativeArrayStream.From(cStream); + if (cStream == null || + (IntPtr)nativeStream->release != (IntPtr)StreamReleasePtr || + nativeStream->private_data == null) + { + return null; + } + return GCHandle.FromIntPtr((IntPtr)nativeStream->private_data).Target as ExportedStream; + } + +#if NET5_0_OR_GREATER + [UnmanagedCallersOnly] +#endif + private unsafe static int GetStreamSchema(CArrowArrayStream* cStream, CArrowSchema* cSchema) + { + ExportedStream? stream = null; + try + { + stream = GetExportedStream(cStream) ?? throw new ArgumentException("not a stream exported by this driver", nameof(cStream)); + stream.ClearError(); + CArrowSchemaExporter.ExportSchema(stream.Stream.Schema, cSchema); + return 0; + } + catch (Exception e) + { + return stream?.SetError(e) ?? Errno.EINVAL; + } + } + +#if NET5_0_OR_GREATER + [UnmanagedCallersOnly] +#endif + private unsafe static int GetStreamNext(CArrowArrayStream* cStream, CArrowArray* cArray) + { + ExportedStream? stream = null; + try + { + NativeArray.From(cArray)->release = default; + stream = GetExportedStream(cStream) ?? throw new ArgumentException("not a stream exported by this driver", nameof(cStream)); + stream.ClearError(); + + ValueTask<RecordBatch?> pending = stream.Stream.ReadNextRecordBatchAsync(); + RecordBatch? batch = pending.IsCompletedSuccessfully ? pending.Result : pending.AsTask().GetAwaiter().GetResult(); Review Comment: Starting the asynchronous read on the caller's synchronization context and then blocking that thread can deadlock an in-process read on a UI or other single-threaded context. `DatabricksCompositeReader` awaits without suppressing context capture, so its continuation cannot run while `get_next` waits here. Start the read on a context-free worker before synchronously waiting, following the pattern in `AdbcStatement11.ExecuteQuery`. ########## csharp/src/Apache.Arrow.Adbc/C/CAdbcDriverExporter.Streams.cs: ########## @@ -0,0 +1,353 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using Apache.Arrow.C; +using Apache.Arrow.Ipc; + +namespace Apache.Arrow.Adbc.C +{ + public partial class CAdbcDriverExporter + { +#if NET5_0_OR_GREATER + private static unsafe delegate* unmanaged<CArrowArrayStream*, CArrowSchema*, int> StreamGetSchemaPtr => &GetStreamSchema; + private static unsafe delegate* unmanaged<CArrowArrayStream*, CArrowArray*, int> StreamGetNextPtr => &GetStreamNext; + private static unsafe delegate* unmanaged<CArrowArrayStream*, byte*> StreamGetLastErrorPtr => &GetStreamLastError; + private static unsafe delegate* unmanaged<CArrowArrayStream*, void> StreamReleasePtr => &ReleaseStream; + private static unsafe delegate* unmanaged<CArrowArrayStream*, AdbcStatusCode*, CAdbcError*> ErrorFromArrayStreamPtr => &GetErrorFromArrayStream; +#else + private static unsafe IntPtr StreamGetSchemaPtr = NativeDelegate<ArrayStreamGetSchema>.AsNativePointer(GetStreamSchema); + private static unsafe IntPtr StreamGetNextPtr = NativeDelegate<ArrayStreamGetNext>.AsNativePointer(GetStreamNext); + private static unsafe IntPtr StreamGetLastErrorPtr = NativeDelegate<ArrayStreamGetLastError>.AsNativePointer(GetStreamLastError); + private static unsafe IntPtr StreamReleasePtr = NativeDelegate<ArrayStreamRelease>.AsNativePointer(ReleaseStream); + private static unsafe IntPtr ErrorFromArrayStreamPtr = NativeDelegate<ErrorFromArrayStream>.AsNativePointer(GetErrorFromArrayStream); +#endif + + /// <summary> + /// Exports a stream produced by the managed driver. The exported stream takes + /// ownership on success; if exporting fails, nobody else will dispose it. + /// </summary> + /// <remarks> + /// When reading the stream fails, the exported stream keeps the exception so that + /// <c>AdbcErrorFromArrayStream</c> can report its status, SQLSTATE, vendor code and + /// details, and returns an errno that corresponds to the status. + /// </remarks> + private unsafe static void ExportStream(IArrowArrayStream? stream, CArrowArrayStream* cStream) + { + try + { + if (stream == null) + { + throw new ArgumentNullException(nameof(stream)); + } + if (cStream == null) + { + throw new ArgumentNullException(nameof(cStream)); + } + + NativeArrayStream* nativeStream = NativeArrayStream.From(cStream); + nativeStream->private_data = (void*)GCHandle.ToIntPtr(GCHandle.Alloc(new ExportedStream(stream))); + nativeStream->get_schema = StreamGetSchemaPtr; + nativeStream->get_next = StreamGetNextPtr; + nativeStream->get_last_error = StreamGetLastErrorPtr; + nativeStream->release = StreamReleasePtr; + } + catch + { + stream?.Dispose(); + throw; + } + } + + private unsafe static ExportedStream? GetExportedStream(CArrowArrayStream* cStream) + { + NativeArrayStream* nativeStream = NativeArrayStream.From(cStream); + if (cStream == null || + (IntPtr)nativeStream->release != (IntPtr)StreamReleasePtr || + nativeStream->private_data == null) + { + return null; + } + return GCHandle.FromIntPtr((IntPtr)nativeStream->private_data).Target as ExportedStream; + } + +#if NET5_0_OR_GREATER + [UnmanagedCallersOnly] +#endif + private unsafe static int GetStreamSchema(CArrowArrayStream* cStream, CArrowSchema* cSchema) + { + ExportedStream? stream = null; + try + { + stream = GetExportedStream(cStream) ?? throw new ArgumentException("not a stream exported by this driver", nameof(cStream)); + stream.ClearError(); + CArrowSchemaExporter.ExportSchema(stream.Stream.Schema, cSchema); + return 0; + } + catch (Exception e) + { + return stream?.SetError(e) ?? Errno.EINVAL; + } + } + +#if NET5_0_OR_GREATER + [UnmanagedCallersOnly] +#endif + private unsafe static int GetStreamNext(CArrowArrayStream* cStream, CArrowArray* cArray) + { + ExportedStream? stream = null; + try + { + NativeArray.From(cArray)->release = default; + stream = GetExportedStream(cStream) ?? throw new ArgumentException("not a stream exported by this driver", nameof(cStream)); + stream.ClearError(); + + ValueTask<RecordBatch?> pending = stream.Stream.ReadNextRecordBatchAsync(); + RecordBatch? batch = pending.IsCompletedSuccessfully ? pending.Result : pending.AsTask().GetAwaiter().GetResult(); + if (batch != null) + { + CArrowArrayExporter.ExportRecordBatch(batch, cArray); + } + return 0; + } + catch (Exception e) + { + return stream?.SetError(e) ?? Errno.EINVAL; + } + } + +#if NET5_0_OR_GREATER + [UnmanagedCallersOnly] +#endif + private unsafe static byte* GetStreamLastError(CArrowArrayStream* cStream) + { + try + { + return GetExportedStream(cStream)?.LastErrorMessage; + } + catch (Exception) + { + return null; + } + } + +#if NET5_0_OR_GREATER + [UnmanagedCallersOnly] +#endif + private unsafe static void ReleaseStream(CArrowArrayStream* cStream) + { + NativeArrayStream* nativeStream = NativeArrayStream.From(cStream); + try + { + if (nativeStream->private_data != null) + { + GCHandle gch = GCHandle.FromIntPtr((IntPtr)nativeStream->private_data); + try + { + ((ExportedStream)gch.Target!).Dispose(); + } + finally + { + gch.Free(); + nativeStream->private_data = null; + } + } + } + catch (Exception) + { + // An exception would end the process, and the consumer has no way to learn of it. + } + nativeStream->release = default; + } + +#if NET5_0_OR_GREATER + [UnmanagedCallersOnly] +#endif + private unsafe static CAdbcError* GetErrorFromArrayStream(CArrowArrayStream* cStream, AdbcStatusCode* status) + { + try + { + return GetExportedStream(cStream)?.GetError(status); + } + catch (Exception) + { + return null; + } + } + + /// <summary> + /// The state behind an exported <see cref="CArrowArrayStream"/>: the managed stream + /// and the error from the last call that failed, if any. + /// </summary> + private sealed unsafe class ExportedStream : IDisposable + { + private IArrowArrayStream? _stream; + private CAdbcError* _error; + private AdbcStatusCode _status; + + public ExportedStream(IArrowArrayStream stream) + { + _stream = stream; + } + + public IArrowArrayStream Stream => _stream ?? throw new ObjectDisposedException(nameof(ExportedStream)); + + public byte* LastErrorMessage => _error != null && _error->release != default ? _error->message : null; + + public CAdbcError* GetError(AdbcStatusCode* status) + { + if (_error == null || _error->release == default) + { + return null; + } + if (status != null) + { + *status = _status; + } + return _error; + } + + /// <summary> + /// Records <paramref name="exception"/> as the stream's error and returns the + /// errno that corresponds to its status. + /// </summary> + public int SetError(Exception exception) + { + ClearError(); + _status = CAdbcDriverExporter.SetError(null, exception); + if (_status == AdbcStatusCode.Success) + { + // An errno of 0 would read as the end of the stream + _status = AdbcStatusCode.UnknownError; + } + + try + { + if (_error == null) + { + _error = (CAdbcError*)Marshal.AllocHGlobal(sizeof(CAdbcError)); + *_error = default; + } + + // AdbcErrorFromArrayStream is part of ADBC 1.1.0, so the consumer + // understands the 1.1.0 error layout. + _error->vendor_code = ErrorVendorCodePrivateData; + CAdbcDriverExporter.SetError(_error, exception); Review Comment: A stream exception with both a nonzero `NativeError` and `Details` does not preserve the vendor code as promised. `FillError` uses the vendor-code field for the private-data marker when details are present, and `ImportedAdbcException` consequently reports `NativeError` as 0. Please define a way to carry and recover both values, or document the ABI limitation, and add a round-trip case that supplies both. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
