You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@arrow.apache.org by li...@apache.org on 2023/06/07 20:48:43 UTC
[arrow-adbc] branch main updated: feat(csharp): adding C# functionality (#697)
This is an automated email from the ASF dual-hosted git repository.
lidavidm pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-adbc.git
The following commit(s) were added to refs/heads/main by this push:
new ad07ba2a feat(csharp): adding C# functionality (#697)
ad07ba2a is described below
commit ad07ba2a125ee8e527dd7de4655ebbde292cedf1
Author: davidhcoe <da...@microsoft.com>
AuthorDate: Wed Jun 7 16:48:36 2023 -0400
feat(csharp): adding C# functionality (#697)
Introduces C# functionality for ADBC, including base classes for
- AdbcDriver
- AdbcDatabase
- AdbcConnection
- AdbcStatement
Also adds a FlightSql implementation
Currently tied to Arrow 13.0.0. Uses a submodule to pull in the Arrow
library until the latest NuGet is released.
---------
Co-authored-by: Eric Erhardt <er...@microsoft.com>
---
csharp/.editorconfig | 169 +++
csharp/.gitignore | 31 +
csharp/.gitmodules | 21 +
csharp/Apache.Arrow.Adbc.sln | 58 +
csharp/ApacheArrow.snk | Bin 0 -> 596 bytes
csharp/Directory.Build.props | 60 +
csharp/README.md | 28 +
.../Apache.Arrow.Adbc.FlightSql.csproj | 16 +
.../FlightSqlConnection.cs | 92 ++
.../FlightSqlDatabase.cs | 53 +
.../Apache.Arrow.Adbc.FlightSql/FlightSqlDriver.cs | 33 +
.../FlightSqlParameters.cs | 30 +
.../Apache.Arrow.Adbc.FlightSql/FlightSqlResult.cs | 79 ++
.../FlightSqlStatement.cs | 233 ++++
csharp/src/Apache.Arrow.Adbc/AdbcConnection.cs | 239 ++++
csharp/src/Apache.Arrow.Adbc/AdbcDatabase.cs | 42 +
csharp/src/Apache.Arrow.Adbc/AdbcDriver.cs | 41 +
csharp/src/Apache.Arrow.Adbc/AdbcException.cs | 88 ++
csharp/src/Apache.Arrow.Adbc/AdbcInfoCode.cs | 55 +
csharp/src/Apache.Arrow.Adbc/AdbcStatement.cs | 142 +++
csharp/src/Apache.Arrow.Adbc/AdbcStatusCode.cs | 121 ++
.../src/Apache.Arrow.Adbc/Apache.Arrow.Adbc.csproj | 12 +
csharp/src/Apache.Arrow.Adbc/BulkIngestMode.cs | 39 +
.../Extensions/MarshalExtensions.netstandard.cs | 80 ++
csharp/src/Apache.Arrow.Adbc/Interop.cs | 1203 ++++++++++++++++++++
csharp/src/Apache.Arrow.Adbc/Interop/LoadDriver.cs | 492 ++++++++
.../Apache.Arrow.Adbc/Interop/NativeDelegate.cs | 41 +
csharp/src/Apache.Arrow.Adbc/IsolationLevel.cs | 77 ++
.../src/Apache.Arrow.Adbc/PartitionDescriptor.cs | 62 +
csharp/src/Apache.Arrow.Adbc/Results.cs | 103 ++
csharp/src/Apache.Arrow.Adbc/StandardSchemas.cs | 182 +++
.../Apache.Arrow.Adbc.FlightSql.Tests.csproj | 30 +
.../ConnectionTests.cs | 138 +++
.../FlightSqlTestConfiguration.cs | 42 +
.../Apache.Arrow.Adbc.FlightSql.Tests/TypeTests.cs | 76 ++
.../Apache.Arrow.Adbc.FlightSql.Tests/Utils.cs | 53 +
.../flightsql.arrow | Bin 0 -> 3662 bytes
.../flightsqlconfig.json | 8 +
.../Apache.Arrow.Adbc.Tests.csproj | 22 +
.../Apache.Arrow.Adbc.Tests/ConnectionTests.cs | 71 ++
.../Apache.Arrow.Adbc.Tests/MockAdbcException.cs | 27 +
.../Apache.Arrow.Adbc.Tests/MockArrayStream.cs | 71 ++
csharp/test/Apache.Arrow.Adbc.Tests/TypeTests.cs | 67 ++
dev/release/rat_exclude_files.txt | 5 +
44 files changed, 4532 insertions(+)
diff --git a/csharp/.editorconfig b/csharp/.editorconfig
new file mode 100644
index 00000000..912ba5ac
--- /dev/null
+++ b/csharp/.editorconfig
@@ -0,0 +1,169 @@
+# 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.
+
+root = true
+
+# Default settings:
+# A newline ending every file
+# Use 4 spaces as indentation
+[*]
+insert_final_newline = true
+indent_style = space
+indent_size = 4
+trim_trailing_whitespace = true
+
+# C# files
+[*.cs]
+# New line preferences
+csharp_new_line_before_open_brace = all
+csharp_new_line_before_else = true
+csharp_new_line_before_catch = true
+csharp_new_line_before_finally = true
+csharp_new_line_before_members_in_object_initializers = true
+csharp_new_line_before_members_in_anonymous_types = true
+csharp_new_line_between_query_expression_clauses = true
+
+# Indentation preferences
+csharp_indent_block_contents = true
+csharp_indent_braces = false
+csharp_indent_case_contents = true
+csharp_indent_case_contents_when_block = true
+csharp_indent_switch_labels = true
+csharp_indent_labels = one_less_than_current
+
+# Modifier preferences
+csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion
+
+# avoid this. unless absolutely necessary
+dotnet_style_qualification_for_field = false:suggestion
+dotnet_style_qualification_for_property = false:suggestion
+dotnet_style_qualification_for_method = false:suggestion
+dotnet_style_qualification_for_event = false:suggestion
+
+# Types: use keywords instead of BCL types, and permit var only when the type is clear
+csharp_style_var_for_built_in_types = false:suggestion
+csharp_style_var_when_type_is_apparent = false:none
+csharp_style_var_elsewhere = false:suggestion
+dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
+dotnet_style_predefined_type_for_member_access = true:suggestion
+
+# name all constant fields using PascalCase
+dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields
+dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style
+dotnet_naming_symbols.constant_fields.applicable_kinds = field
+dotnet_naming_symbols.constant_fields.required_modifiers = const
+dotnet_naming_style.pascal_case_style.capitalization = pascal_case
+
+# static fields should have s_ prefix
+dotnet_naming_rule.static_fields_should_have_prefix.severity = suggestion
+dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields
+dotnet_naming_rule.static_fields_should_have_prefix.style = static_prefix_style
+dotnet_naming_symbols.static_fields.applicable_kinds = field
+dotnet_naming_symbols.static_fields.required_modifiers = static
+dotnet_naming_symbols.static_fields.applicable_accessibilities = private, internal, private_protected
+dotnet_naming_style.static_prefix_style.required_prefix = s_
+dotnet_naming_style.static_prefix_style.capitalization = camel_case
+
+# internal and private fields should be _camelCase
+dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion
+dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields
+dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style
+dotnet_naming_symbols.private_internal_fields.applicable_kinds = field
+dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal
+dotnet_naming_style.camel_case_underscore_style.required_prefix = _
+dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case
+
+# Code style defaults
+csharp_using_directive_placement = outside_namespace:suggestion
+dotnet_sort_system_directives_first = true
+csharp_prefer_braces = true:refactoring
+csharp_preserve_single_line_blocks = true:none
+csharp_preserve_single_line_statements = false:none
+csharp_prefer_static_local_function = true:suggestion
+csharp_prefer_simple_using_statement = false:none
+csharp_style_prefer_switch_expression = true:suggestion
+
+# Code quality
+dotnet_style_readonly_field = true:suggestion
+dotnet_code_quality_unused_parameters = non_public:suggestion
+
+# Expression-level preferences
+dotnet_style_object_initializer = true:suggestion
+dotnet_style_collection_initializer = true:suggestion
+dotnet_style_explicit_tuple_names = true:suggestion
+dotnet_style_coalesce_expression = true:suggestion
+dotnet_style_null_propagation = true:suggestion
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
+dotnet_style_prefer_inferred_tuple_names = true:suggestion
+dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
+dotnet_style_prefer_auto_properties = true:suggestion
+dotnet_style_prefer_conditional_expression_over_assignment = true:refactoring
+dotnet_style_prefer_conditional_expression_over_return = true:refactoring
+csharp_prefer_simple_default_expression = true:suggestion
+
+# Expression-bodied members
+csharp_style_expression_bodied_methods = true:refactoring
+csharp_style_expression_bodied_constructors = true:refactoring
+csharp_style_expression_bodied_operators = true:refactoring
+csharp_style_expression_bodied_properties = true:refactoring
+csharp_style_expression_bodied_indexers = true:refactoring
+csharp_style_expression_bodied_accessors = true:refactoring
+csharp_style_expression_bodied_lambdas = true:refactoring
+csharp_style_expression_bodied_local_functions = true:refactoring
+
+# Pattern matching
+csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
+csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
+csharp_style_inlined_variable_declaration = true:suggestion
+
+# Null checking preferences
+csharp_style_throw_expression = true:suggestion
+csharp_style_conditional_delegate_call = true:suggestion
+
+# Other features
+csharp_style_prefer_index_operator = false:none
+csharp_style_prefer_range_operator = false:none
+csharp_style_pattern_local_over_anonymous_function = false:none
+
+# Space preferences
+csharp_space_after_cast = false
+csharp_space_after_colon_in_inheritance_clause = true
+csharp_space_after_comma = true
+csharp_space_after_dot = false
+csharp_space_after_keywords_in_control_flow_statements = true
+csharp_space_after_semicolon_in_for_statement = true
+csharp_space_around_binary_operators = before_and_after
+csharp_space_around_declaration_statements = do_not_ignore
+csharp_space_before_colon_in_inheritance_clause = true
+csharp_space_before_comma = false
+csharp_space_before_dot = false
+csharp_space_before_open_square_brackets = false
+csharp_space_before_semicolon_in_for_statement = false
+csharp_space_between_empty_square_brackets = false
+csharp_space_between_method_call_empty_parameter_list_parentheses = false
+csharp_space_between_method_call_name_and_opening_parenthesis = false
+csharp_space_between_method_call_parameter_list_parentheses = false
+csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
+csharp_space_between_method_declaration_name_and_open_parenthesis = false
+csharp_space_between_method_declaration_parameter_list_parentheses = false
+csharp_space_between_parentheses = false
+csharp_space_between_square_brackets = false
+
+# Xml project files
+[*.{csproj,props,targets}]
+indent_size = 2
+charset = utf-8
diff --git a/csharp/.gitignore b/csharp/.gitignore
new file mode 100644
index 00000000..6cbea915
--- /dev/null
+++ b/csharp/.gitignore
@@ -0,0 +1,31 @@
+# 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.
+
+.vs
+.vscode
+bin
+obj
+x64
+*.dll
+*.lib
+*.obj
+*.exe
+*.csproj.user
+*.pass
+
+src/arrow
+artifacts/
diff --git a/csharp/.gitmodules b/csharp/.gitmodules
new file mode 100644
index 00000000..3d9ea4ba
--- /dev/null
+++ b/csharp/.gitmodules
@@ -0,0 +1,21 @@
+# 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.
+
+[submodule "arrow"]
+ path = arrow
+ url = https://github.com/apache/arrow
+ branch = main
diff --git a/csharp/Apache.Arrow.Adbc.sln b/csharp/Apache.Arrow.Adbc.sln
new file mode 100644
index 00000000..78bc7df8
--- /dev/null
+++ b/csharp/Apache.Arrow.Adbc.sln
@@ -0,0 +1,58 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.4.33403.182
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Apache.Arrow.Adbc", "src\Apache.Arrow.Adbc\Apache.Arrow.Adbc.csproj", "{232F2EC7-5FD3-4E0B-89FF-317A15CF698F}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Arrow", "Arrow", "{9ADA26E0-F328-4466-908A-2FA506DBDF7D}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Apache.Arrow", "src\arrow\csharp\src\Apache.Arrow\Apache.Arrow.csproj", "{851FBF0D-21A8-4C04-A040-24B6AAE31D2C}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Apache.Arrow.Adbc.FlightSql", "src\Apache.Arrow.Adbc.FlightSql\Apache.Arrow.Adbc.FlightSql.csproj", "{93D7AD3B-D6DC-4123-9796-63DD35A4708F}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Apache.Arrow.Adbc.Tests", "test\Apache.Arrow.Adbc.Tests\Apache.Arrow.Adbc.Tests.csproj", "{00C143BA-F1CF-4117-9DE6-E73DC4D208F8}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Apache.Arrow.Adbc.FlightSql.Tests", "test\Apache.Arrow.Adbc.FlightSql.Tests\Apache.Arrow.Adbc.FlightSql.Tests.csproj", "{23C3952F-2AB8-40B9-AD95-E06ECEB12D7A}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{5BD04C26-CE52-4893-8C1A-479705195CEF}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {232F2EC7-5FD3-4E0B-89FF-317A15CF698F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {232F2EC7-5FD3-4E0B-89FF-317A15CF698F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {232F2EC7-5FD3-4E0B-89FF-317A15CF698F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {232F2EC7-5FD3-4E0B-89FF-317A15CF698F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {851FBF0D-21A8-4C04-A040-24B6AAE31D2C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {851FBF0D-21A8-4C04-A040-24B6AAE31D2C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {851FBF0D-21A8-4C04-A040-24B6AAE31D2C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {851FBF0D-21A8-4C04-A040-24B6AAE31D2C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {93D7AD3B-D6DC-4123-9796-63DD35A4708F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {93D7AD3B-D6DC-4123-9796-63DD35A4708F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {93D7AD3B-D6DC-4123-9796-63DD35A4708F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {93D7AD3B-D6DC-4123-9796-63DD35A4708F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {00C143BA-F1CF-4117-9DE6-E73DC4D208F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {00C143BA-F1CF-4117-9DE6-E73DC4D208F8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {00C143BA-F1CF-4117-9DE6-E73DC4D208F8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {00C143BA-F1CF-4117-9DE6-E73DC4D208F8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {23C3952F-2AB8-40B9-AD95-E06ECEB12D7A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {23C3952F-2AB8-40B9-AD95-E06ECEB12D7A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {23C3952F-2AB8-40B9-AD95-E06ECEB12D7A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {23C3952F-2AB8-40B9-AD95-E06ECEB12D7A}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {851FBF0D-21A8-4C04-A040-24B6AAE31D2C} = {9ADA26E0-F328-4466-908A-2FA506DBDF7D}
+ {00C143BA-F1CF-4117-9DE6-E73DC4D208F8} = {5BD04C26-CE52-4893-8C1A-479705195CEF}
+ {23C3952F-2AB8-40B9-AD95-E06ECEB12D7A} = {5BD04C26-CE52-4893-8C1A-479705195CEF}
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {4795CF16-0FDB-4BE0-9768-5CF31564DC03}
+ EndGlobalSection
+EndGlobal
diff --git a/csharp/ApacheArrow.snk b/csharp/ApacheArrow.snk
new file mode 100644
index 00000000..68df4397
Binary files /dev/null and b/csharp/ApacheArrow.snk differ
diff --git a/csharp/Directory.Build.props b/csharp/Directory.Build.props
new file mode 100644
index 00000000..92ba5325
--- /dev/null
+++ b/csharp/Directory.Build.props
@@ -0,0 +1,60 @@
+<!--
+ 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.
+-->
+
+<Project>
+
+ <!-- Common repo directories -->
+ <PropertyGroup>
+ <RepoRoot>$(MSBuildThisFileDirectory)../</RepoRoot>
+ <CSharpDir>$(MSBuildThisFileDirectory)</CSharpDir>
+ <BaseOutputPath>$(CSharpDir)/artifacts/$(MSBuildProjectName)</BaseOutputPath>
+ </PropertyGroup>
+
+ <!-- AssemblyInfo properties -->
+ <PropertyGroup>
+ <Product>Apache Arrow ADBC library</Product>
+ <Copyright>Copyright 2022-2023 The Apache Software Foundation</Copyright>
+ <Company>The Apache Software Foundation</Company>
+ <Version>0.5.0</Version>
+ </PropertyGroup>
+
+ <PropertyGroup>
+ <EmbedUntrackedSources>true</EmbedUntrackedSources>
+ <LangVersion>latest</LangVersion>
+ <SignAssembly>true</SignAssembly>
+ <AssemblyOriginatorKeyFile>$(CSharpDir)ApacheArrow.snk</AssemblyOriginatorKeyFile>
+ </PropertyGroup>
+
+ <!-- NuGet properties -->
+ <PropertyGroup>
+ <Authors>The Apache Software Foundation</Authors>
+ <PackageIconUrl>https://www.apache.org/images/feather.png</PackageIconUrl>
+ <!-- We can't use PackageLicenseExpression; the license file also contains 3rd-party notices. -->
+ <PackageLicenseFile>LICENSE.txt</PackageLicenseFile>
+ <PackageProjectUrl>https://arrow.apache.org/</PackageProjectUrl>
+ <PackageTags>apache arrow adbc</PackageTags>
+ <RepositoryType>git</RepositoryType>
+ <RepositoryUrl>https://github.com/apache/arrow-adbc</RepositoryUrl>
+ <IncludeSymbols>true</IncludeSymbols>
+ <SymbolPackageFormat>snupkg</SymbolPackageFormat>
+ </PropertyGroup>
+
+ <ItemGroup Condition="'$(IsPackable)' == 'true'">
+ <Content Include="$(RepoRoot)LICENSE.txt" Pack="true" PackagePath="" />
+ </ItemGroup>
+
+</Project>
diff --git a/csharp/README.md b/csharp/README.md
new file mode 100644
index 00000000..2068e981
--- /dev/null
+++ b/csharp/README.md
@@ -0,0 +1,28 @@
+<!---
+ 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.
+-->
+
+# Apache Arrow ADBC
+
+An implementation of Arrow ADBC targeting .NET Standard 2.0 and .NET 6 or later.
+
+# Arrow Submodule
+
+This library uses the Arrow C Data API that is published in the [Arrow repo](https://github.com/apache/arrow/).
+
+This change has not been published to NuGet, so a submodule is currently used to obtain the Arrow project. Be sure to download the submodule so the solution loads and correctly.
diff --git a/csharp/src/Apache.Arrow.Adbc.FlightSql/Apache.Arrow.Adbc.FlightSql.csproj b/csharp/src/Apache.Arrow.Adbc.FlightSql/Apache.Arrow.Adbc.FlightSql.csproj
new file mode 100644
index 00000000..c60d4e0c
--- /dev/null
+++ b/csharp/src/Apache.Arrow.Adbc.FlightSql/Apache.Arrow.Adbc.FlightSql.csproj
@@ -0,0 +1,16 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFrameworks>netstandard2.0;net6.0</TargetFrameworks>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="Apache.Arrow.Flight" Version="12.0.0" />
+ <PackageReference Include="System.Net.Http.WinHttpHandler" Version="7.0.0" Condition="'$(TargetFrameworkIdentifier)' == '.NETStandard'" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\Apache.Arrow.Adbc\Apache.Arrow.Adbc.csproj" />
+ </ItemGroup>
+
+</Project>
diff --git a/csharp/src/Apache.Arrow.Adbc.FlightSql/FlightSqlConnection.cs b/csharp/src/Apache.Arrow.Adbc.FlightSql/FlightSqlConnection.cs
new file mode 100644
index 00000000..ce6cf8f3
--- /dev/null
+++ b/csharp/src/Apache.Arrow.Adbc.FlightSql/FlightSqlConnection.cs
@@ -0,0 +1,92 @@
+/*
+ * 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 Apache.Arrow.Flight.Client;
+using Grpc.Core;
+using Grpc.Net.Client;
+
+namespace Apache.Arrow.Adbc.FlightSql
+{
+ /// <summary>
+ /// A Flight SQL implementation of <see cref="AdbcConnection"/>.
+ /// </summary>
+ public class FlightSqlConnection : AdbcConnection
+ {
+ private FlightClient _flightClientInternal = null;
+ private readonly IReadOnlyDictionary<string, string> _metadata;
+
+ private Metadata headers = null;
+
+ public FlightSqlConnection(IReadOnlyDictionary<string, string> metadata)
+ {
+ _metadata = metadata;
+ }
+
+ internal FlightClient FlightClient
+ {
+ get => _flightClientInternal;
+ }
+
+ internal Metadata Metadata
+ {
+ get => GetMetaData();
+ }
+
+ private Metadata GetMetaData()
+ {
+ if (headers is null)
+ {
+ headers = new Metadata();
+
+ foreach (string key in _metadata.Keys)
+ {
+ headers.Add(key, _metadata[key]);
+ }
+ }
+
+ return headers;
+ }
+
+ public void Open(string uri)
+ {
+#if NETSTANDARD // and Win11 or later --> https://learn.microsoft.com/en-us/aspnet/core/grpc/netstandard?view=aspnetcore-7.0#net-framework
+
+ var channel = GrpcChannel.ForAddress(uri, new GrpcChannelOptions
+ {
+ HttpHandler = new System.Net.Http.WinHttpHandler()
+ {
+ ReceiveDataTimeout = TimeSpan.FromMinutes(5),
+ SendTimeout = TimeSpan.FromMinutes(5),
+ ReceiveHeadersTimeout = TimeSpan.FromMinutes(5)
+ },
+
+ });
+
+ _flightClientInternal = new FlightClient(channel);
+#else
+ _flightClientInternal = new FlightClient(GrpcChannel.ForAddress(uri));
+#endif
+ }
+
+ public override AdbcStatement CreateStatement()
+ {
+ return new FlightSqlStatement(this);
+ }
+ }
+}
diff --git a/csharp/src/Apache.Arrow.Adbc.FlightSql/FlightSqlDatabase.cs b/csharp/src/Apache.Arrow.Adbc.FlightSql/FlightSqlDatabase.cs
new file mode 100644
index 00000000..08d8d2bf
--- /dev/null
+++ b/csharp/src/Apache.Arrow.Adbc.FlightSql/FlightSqlDatabase.cs
@@ -0,0 +1,53 @@
+/*
+ * 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;
+
+namespace Apache.Arrow.Adbc.FlightSql
+{
+ /// <summary>
+ /// A Flight SQL implementation of <see cref="AdbcDatabase"/>.
+ /// </summary>
+ public class FlightSqlDatabase : AdbcDatabase
+ {
+ private readonly IReadOnlyDictionary<string, string> _metadata;
+
+ public FlightSqlDatabase(IReadOnlyDictionary<string, string> metadata)
+ {
+ _metadata = metadata;
+ }
+
+ public override AdbcConnection Connect(IReadOnlyDictionary<string, string> options)
+ {
+ if (options == null) throw new ArgumentNullException("options");
+
+ string flightSqlServerAddress = string.Empty;
+
+ if (options.TryGetValue(FlightSqlParameters.ServerAddress, out flightSqlServerAddress))
+ {
+ FlightSqlConnection connection = new FlightSqlConnection(_metadata);
+ connection.Open(flightSqlServerAddress);
+ return connection;
+ }
+ else
+ {
+ throw new ArgumentException($"Options must include the {FlightSqlParameters.ServerAddress} parameter");
+ }
+ }
+ }
+}
diff --git a/csharp/src/Apache.Arrow.Adbc.FlightSql/FlightSqlDriver.cs b/csharp/src/Apache.Arrow.Adbc.FlightSql/FlightSqlDriver.cs
new file mode 100644
index 00000000..afcaf3f3
--- /dev/null
+++ b/csharp/src/Apache.Arrow.Adbc.FlightSql/FlightSqlDriver.cs
@@ -0,0 +1,33 @@
+/*
+ * 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.Collections.Generic;
+
+namespace Apache.Arrow.Adbc.FlightSql
+{
+ /// <summary>
+ /// Represents an Arrpw Flight SQL driver for connecting to
+ /// data sources that support Arrow Flight SQL.
+ /// </summary>
+ public class FlightSqlDriver : AdbcDriver
+ {
+ public override AdbcDatabase Open(IReadOnlyDictionary<string, string> parameters)
+ {
+ return new FlightSqlDatabase(parameters);
+ }
+ }
+}
diff --git a/csharp/src/Apache.Arrow.Adbc.FlightSql/FlightSqlParameters.cs b/csharp/src/Apache.Arrow.Adbc.FlightSql/FlightSqlParameters.cs
new file mode 100644
index 00000000..b301c23d
--- /dev/null
+++ b/csharp/src/Apache.Arrow.Adbc.FlightSql/FlightSqlParameters.cs
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+
+namespace Apache.Arrow.Adbc.FlightSql
+{
+ /// <summary>
+ /// Parameters used for connecting to Flight SQL data sources.
+ /// </summary>
+ public class FlightSqlParameters
+ {
+ public const string ServerAddress = "FLIGHT_SQL_SERVER_ADDRESS";
+ public const string RoutingTag = "routing_tag";
+ public const string RoutingQueue = "routing_queue";
+ public const string Authorization = "authorization";
+ }
+}
diff --git a/csharp/src/Apache.Arrow.Adbc.FlightSql/FlightSqlResult.cs b/csharp/src/Apache.Arrow.Adbc.FlightSql/FlightSqlResult.cs
new file mode 100644
index 00000000..3b68579b
--- /dev/null
+++ b/csharp/src/Apache.Arrow.Adbc.FlightSql/FlightSqlResult.cs
@@ -0,0 +1,79 @@
+/*
+ * 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.Threading;
+using System.Threading.Tasks;
+using Apache.Arrow.Flight;
+using Apache.Arrow.Flight.Client;
+using Apache.Arrow.Ipc;
+
+namespace Apache.Arrow.Adbc.FlightSql
+{
+ /// <summary>
+ /// A Flight SQL implementation of <see cref="IArrowArrayStream"/>.
+ /// </summary>
+ internal class FlightSqlResult : IArrowArrayStream
+ {
+ private FlightClientRecordBatchStreamReader _recordBatchStreamReader;
+
+ private readonly FlightInfo _flightInfo;
+ private readonly FlightSqlConnection _flightSqlConnection;
+ private readonly int _maxEndPoints = 0;
+ private int _currentEndPointIndex = -1;
+
+ public FlightSqlResult(FlightSqlConnection flightSqlConnection, FlightInfo flightInfo)
+ {
+ _flightSqlConnection = flightSqlConnection;
+ _flightInfo = flightInfo;
+ _maxEndPoints = flightInfo.Endpoints.Count;
+ }
+
+ public Schema Schema { get { return _flightInfo.Schema; } }
+
+ public async ValueTask<RecordBatch> ReadNextRecordBatchAsync(CancellationToken cancellationToken = default)
+ {
+ if (_recordBatchStreamReader is null)
+ {
+ RefreshRecordBatchStreamReader();
+ }
+
+ if (await _recordBatchStreamReader.MoveNext(cancellationToken))
+ {
+ return _recordBatchStreamReader.Current;
+ }
+ else if (_currentEndPointIndex < _maxEndPoints - 1)
+ {
+ _recordBatchStreamReader = default;
+
+ return await ReadNextRecordBatchAsync(cancellationToken);
+ }
+ return default;
+ }
+
+ public void Dispose()
+ {
+ _recordBatchStreamReader?.Dispose();
+ _flightSqlConnection.Dispose();
+ }
+
+ private void RefreshRecordBatchStreamReader()
+ {
+ _currentEndPointIndex += 1;
+ _recordBatchStreamReader = _flightSqlConnection.FlightClient.GetStream(_flightInfo.Endpoints[_currentEndPointIndex].Ticket, _flightSqlConnection.Metadata).ResponseStream;
+ }
+ }
+}
diff --git a/csharp/src/Apache.Arrow.Adbc.FlightSql/FlightSqlStatement.cs b/csharp/src/Apache.Arrow.Adbc.FlightSql/FlightSqlStatement.cs
new file mode 100644
index 00000000..99c8cdf0
--- /dev/null
+++ b/csharp/src/Apache.Arrow.Adbc.FlightSql/FlightSqlStatement.cs
@@ -0,0 +1,233 @@
+/*
+ * 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.Text.RegularExpressions;
+using System.Threading.Tasks;
+using Apache.Arrow.Flight;
+using Grpc.Core;
+
+namespace Apache.Arrow.Adbc.FlightSql
+{
+ /// <summary>
+ /// A Flight SQL implementation of <see cref="AdbcStatement"/>.
+ /// </summary>
+ public class FlightSqlStatement : AdbcStatement
+ {
+ private FlightSqlConnection _flightSqlConnection;
+
+ public FlightSqlStatement(FlightSqlConnection flightSqlConnection)
+ {
+ _flightSqlConnection = flightSqlConnection;
+ }
+
+ public override async ValueTask<QueryResult> ExecuteQueryAsync()
+ {
+ FlightInfo info = await GetInfo(SqlQuery, _flightSqlConnection.Metadata);
+
+ return new QueryResult(info.TotalRecords, new FlightSqlResult(_flightSqlConnection, info));
+ }
+
+ public override QueryResult ExecuteQuery()
+ {
+ return ExecuteQueryAsync().Result;
+ }
+
+ public override UpdateResult ExecuteUpdate()
+ {
+ throw new NotImplementedException();
+ }
+
+ public async ValueTask<FlightInfo> GetInfo(string query, Metadata headers)
+ {
+ FlightDescriptor commandDescripter = FlightDescriptor.CreateCommandDescriptor(query);
+
+ return await _flightSqlConnection.FlightClient.GetInfo(commandDescripter, headers).ResponseAsync;
+ }
+
+ /// <summary>
+ /// Gets a value from the Arrow array at the specified index
+ /// using the Arrow field for metadata.
+ /// </summary>
+ /// <param name="arrowArray">
+ /// The array containing the value.
+ /// </param>
+ /// <param name="field">
+ /// The Arrow field.
+ /// </param>
+ /// <param name="index">
+ /// The index of the item.
+ /// </param>
+ /// <returns>
+ /// The item at the index position.
+ /// </returns>
+ public override object GetValue(IArrowArray arrowArray, Field field, int index)
+ {
+ if (arrowArray is BooleanArray)
+ {
+ return Convert.ToBoolean(((BooleanArray)arrowArray).Values[index]);
+ }
+ else if (arrowArray is Date32Array)
+ {
+ Date32Array date32Array = (Date32Array)arrowArray;
+
+ return date32Array.GetDateTime(index);
+ }
+ else if (arrowArray is Date64Array)
+ {
+ Date64Array date64Array = (Date64Array)arrowArray;
+
+ return date64Array.GetDateTime(index);
+ }
+ else if (arrowArray is Decimal128Array)
+ {
+ try
+ {
+ // the value may be <decimal.min or >decimal.max
+ // then Arrow throws an exception
+ // no good way to check prior to
+ return ((Decimal128Array)arrowArray).GetValue(index);
+ }
+ catch (OverflowException oex)
+ {
+ return ParseDecimalValueFromOverflowException(oex);
+ }
+ }
+ else if (arrowArray is Decimal256Array)
+ {
+ try
+ {
+ return ((Decimal256Array)arrowArray).GetValue(index);
+ }
+ catch (OverflowException oex)
+ {
+ return ParseDecimalValueFromOverflowException(oex);
+ }
+ }
+ else if (arrowArray is DoubleArray)
+ {
+ return ((DoubleArray)arrowArray).GetValue(index);
+ }
+ else if (arrowArray is FloatArray)
+ {
+ return ((FloatArray)arrowArray).GetValue(index);
+ }
+#if NET5_0_OR_GREATER
+ else if (arrowArray is PrimitiveArray<Half>)
+ {
+ // TODO: HalfFloatArray not present in current library
+
+ return ((PrimitiveArray<Half>)arrowArray).GetValue(index);
+ }
+#endif
+ else if (arrowArray is Int8Array)
+ {
+ return ((Int8Array)arrowArray).GetValue(index);
+ }
+ else if (arrowArray is Int16Array)
+ {
+ return ((Int16Array)arrowArray).GetValue(index);
+ }
+ else if (arrowArray is Int32Array)
+ {
+ return ((Int32Array)arrowArray).GetValue(index);
+ }
+ else if (arrowArray is Int64Array)
+ {
+ Int64Array array = (Int64Array)arrowArray;
+ return array.GetValue(index);
+ }
+ else if (arrowArray is StringArray)
+ {
+ return ((StringArray)arrowArray).GetString(index);
+ }
+ else if (arrowArray is Time32Array)
+ {
+ return ((Time32Array)arrowArray).GetValue(index);
+ }
+ else if (arrowArray is Time64Array)
+ {
+ return ((Time64Array)arrowArray).GetValue(index);
+ }
+ else if (arrowArray is TimestampArray)
+ {
+ TimestampArray timestampArray = (TimestampArray)arrowArray;
+ DateTimeOffset dateTimeOffset = timestampArray.GetTimestamp(index).Value;
+ return dateTimeOffset;
+ }
+ else if (arrowArray is UInt8Array)
+ {
+ return ((UInt8Array)arrowArray).GetValue(index);
+ }
+ else if (arrowArray is UInt16Array)
+ {
+ return ((UInt16Array)arrowArray).GetValue(index);
+ }
+ else if (arrowArray is UInt32Array)
+ {
+ return ((UInt32Array)arrowArray).GetValue(index);
+ }
+ else if (arrowArray is UInt64Array)
+ {
+ return ((UInt64Array)arrowArray).GetValue(index);
+ }
+
+ // not covered:
+ // -- struct array
+ // -- binary array
+ // -- dictionary array
+ // -- fixed size binary
+ // -- list array
+ // -- union array
+
+ return null;
+ }
+
+ /// <summary>
+ /// For decimals, Arrow throws an OverflowException if a value
+ /// is < decimal.min or > decimal.max
+ /// So parse the numeric value and return it as a string,
+ /// if possible
+ /// </summary>
+ /// <param name="oex">The OverflowException</param>
+ /// <returns>
+ /// A string value of the decimal that threw the exception
+ /// or rethrows the OverflowException.
+ /// </returns>
+ /// <exception cref="ArgumentNullException"></exception>
+ private string ParseDecimalValueFromOverflowException(OverflowException oex)
+ {
+ if (oex == null)
+ throw new ArgumentNullException(nameof(oex));
+
+ // any decimal value, positive or negative, with or without a decimal in place
+ Regex regex = new Regex(" -?\\d*\\.?\\d* ");
+
+ var matches = regex.Matches(oex.Message);
+
+ foreach (Match match in matches)
+ {
+ string value = match.Value;
+
+ if (!string.IsNullOrEmpty(value))
+ return value;
+ }
+
+ throw oex;
+ }
+ }
+}
diff --git a/csharp/src/Apache.Arrow.Adbc/AdbcConnection.cs b/csharp/src/Apache.Arrow.Adbc/AdbcConnection.cs
new file mode 100644
index 00000000..a96b39d4
--- /dev/null
+++ b/csharp/src/Apache.Arrow.Adbc/AdbcConnection.cs
@@ -0,0 +1,239 @@
+/*
+ * 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.Linq;
+using Apache.Arrow.Ipc;
+
+namespace Apache.Arrow.Adbc
+{
+ /// <summary>
+ /// Provides methods for query execution, managing prepared statements,
+ /// using transactions, and so on.
+ /// </summary>
+ public abstract class AdbcConnection : IDisposable
+ {
+ private bool _autoCommit = true;
+ private bool _readOnly = false;
+ private IsolationLevel _isolationLevel = IsolationLevel.Default;
+
+ /// <summary>
+ /// Commit the pending transaction.
+ /// </summary>
+ public virtual void Commit()
+ {
+ throw AdbcException.NotImplemented("Connection does not support transactions");
+ }
+
+ /// <summary>
+ /// Create a new statement that can be executed.
+ /// </summary>
+ public abstract AdbcStatement CreateStatement();
+
+ /// <summary>
+ /// Create a new statement to bulk insert into a table.
+ /// </summary>
+ /// <param name="targetTableName">
+ /// The table name
+ /// </param>
+ /// <param name="mode">
+ /// The ingest mode
+ /// </param>
+ public virtual AdbcStatement BulkIngest(string targetTableName, BulkIngestMode mode)
+ {
+ throw AdbcException.NotImplemented("Connection does not support BulkIngest");
+ }
+
+ public virtual void Dispose()
+ {
+ }
+
+ /// <summary>
+ /// Get metadata about the driver/database.
+ /// </summary>
+ /// <param name="codes">
+ /// The metadata items to fetch.
+ /// </param>
+ /// <returns>
+ /// A statement that can be immediately executed.
+ /// </returns>
+ public virtual IArrowArrayStream GetInfo(List<int> codes)
+ {
+ throw AdbcException.NotImplemented("Connection does not support GetInfo");
+ }
+
+ /// <summary>
+ /// Get metadata about the driver/database.
+ /// </summary>
+ /// <param name="codes">
+ /// The metadata items to fetch.
+ /// </param>
+ /// <returns>
+ /// A statement that can be immediately executed.
+ /// </returns>
+ /// <exception cref="ArgumentNullException"></exception>
+ public virtual IArrowArrayStream GetInfo(List<AdbcInfoCode> codes)
+ {
+ if (codes == null)
+ throw new ArgumentNullException(nameof(codes));
+
+ List<int> codeValues = codes.Select(x => (int)x).ToList();
+
+ return GetInfo(codeValues);
+ }
+
+ /// <summary>
+ /// Get a hierarchical view of all catalogs, database schemas, tables,
+ /// and columns.
+ /// </summary>
+ /// <param name="depth">
+ /// The level of nesting to display.
+ /// If ALL, display all levels (up through columns).
+ /// If CATALOGS, display only catalogs (i.e., catalog_schemas will be
+ /// null), and so on. May be a* search pattern.
+ /// </param>
+ /// <param name="catalogPattern">
+ /// Only show tables in the given catalog.
+ /// If null, do not filter by catalog.If an empty string, only show tables
+ /// without a catalog. May be a search pattern.
+ /// </param>
+ /// <param name="dbSchemaPattern">
+ /// Only show tables in the given database schema. If null, do not
+ /// filter by database schema.If an empty string, only show tables
+ /// without a database schema. May be a search pattern.
+ /// </param>
+ /// <param name="tableNamePattern">
+ /// Only show tables with the given name. If an empty string, only
+ /// show tables without a catalog. May be a search pattern.
+ /// </param>
+ /// <param name="tableTypes">
+ /// Only show tables matching one of the given table types.
+ /// If null, show tables of any type. Valid table types can be
+ /// fetched from <see cref="GetTableTypes"/>}.
+ /// </param>
+ /// <param name="columnNamePattern">
+ /// Only show columns with the given name.
+ /// If null, do not filter by name.May be a search pattern.
+ /// </param>
+ public virtual IArrowArrayStream GetObjects(
+ GetObjectsDepth depth,
+ string catalogPattern,
+ string dbSchemaPattern,
+ string tableNamePattern,
+ List<string> tableTypes,
+ string columnNamePattern)
+ {
+ throw AdbcException.NotImplemented("Connection does not support GetObjects");
+ }
+
+ public enum GetObjectsDepth
+ {
+ /// <summary>
+ /// Display ALL objects (catalog, database schemas, tables,
+ /// and columns).
+ /// </summary>
+ All,
+
+ /// <summary>
+ /// Display only catalogs.
+ /// </summary>
+ Catalogs,
+
+ /// <summary>
+ /// Display catalogs and database schemas.
+ /// </summary>
+ DbSchemas,
+
+ /// <summary>
+ /// Display catalogs, database schemas, and tables.
+ /// </summary>
+ Tables
+ }
+
+ /// <summary>
+ /// Get the Arrow schema of a database table.
+ /// </summary>
+ /// <param name="catalog">
+ /// The catalog of the table (or null).
+ /// </param>
+ /// <param name="dbSchema">
+ /// The database schema of the table (or null).
+ /// </param>
+ /// <param name="tableName">
+ /// The table name.
+ /// </param>
+ public virtual Schema GetTableSchema(string catalog, string dbSchema, string tableName)
+ {
+ throw AdbcException.NotImplemented("Connection does not support GetTableSchema");
+ }
+
+ /// <summary>
+ /// Get a list of table types supported by the database.
+ /// </summary>
+ public virtual IArrowArrayStream GetTableTypes()
+ {
+ throw AdbcException.NotImplemented("Connection does not support GetTableTypes");
+ }
+
+ /// <summary>
+ /// Create a result set from a serialized PartitionDescriptor.
+ /// </summary>
+ /// <param name="partition">
+ /// The partition descriptor.
+ /// </param>
+ public virtual IArrowArrayStream ReadPartition(PartitionDescriptor partition)
+ {
+ throw AdbcException.NotImplemented("Connection does not support partitions");
+ }
+
+ /// <summary>
+ /// Rollback the pending transaction.
+ /// </summary>
+ public virtual void Rollback()
+ {
+ throw AdbcException.NotImplemented("Connection does not support transactions");
+ }
+
+ /// <summary>
+ /// Gets or sets the autocommit state.
+ /// </summary>
+ public virtual bool AutoCommit
+ {
+ get => _autoCommit;
+ set => throw AdbcException.NotImplemented("Connection does not support transactions");
+ }
+
+ /// <summary>
+ /// Gets or sets whether the connection is read-only.
+ /// </summary>
+ public virtual bool ReadOnly
+ {
+ get => _readOnly;
+ set => throw AdbcException.NotImplemented("Connection does not support read-only mode");
+ }
+
+ /// <summary>
+ /// Gets or sets the isolation level used by transactions.
+ /// </summary>
+ public virtual IsolationLevel IsolationLevel
+ {
+ get => _isolationLevel;
+ set => throw AdbcException.NotImplemented("Connection does not support setting isolation level");
+ }
+ }
+}
diff --git a/csharp/src/Apache.Arrow.Adbc/AdbcDatabase.cs b/csharp/src/Apache.Arrow.Adbc/AdbcDatabase.cs
new file mode 100644
index 00000000..d2338386
--- /dev/null
+++ b/csharp/src/Apache.Arrow.Adbc/AdbcDatabase.cs
@@ -0,0 +1,42 @@
+/*
+ * 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;
+
+namespace Apache.Arrow.Adbc
+{
+ /// <summary>
+ /// Clients first initialize a database, then create a connection.
+ /// This gives the implementation a place to initialize and own any
+ /// common connection state.
+ /// For example, in-memory databases can place ownership of the actual
+ /// database in this object.
+ /// </summary>
+ public abstract class AdbcDatabase : IDisposable
+ {
+ /// <summary>
+ /// Create a new connection to the database.
+ /// </summary>
+ /// <param name="options">
+ /// Additional options to use when connecting.
+ /// </param>
+ public abstract AdbcConnection Connect(IReadOnlyDictionary<string, string> options);
+
+ public virtual void Dispose() { }
+ }
+}
diff --git a/csharp/src/Apache.Arrow.Adbc/AdbcDriver.cs b/csharp/src/Apache.Arrow.Adbc/AdbcDriver.cs
new file mode 100644
index 00000000..1674263a
--- /dev/null
+++ b/csharp/src/Apache.Arrow.Adbc/AdbcDriver.cs
@@ -0,0 +1,41 @@
+/*
+ * 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;
+
+namespace Apache.Arrow.Adbc
+{
+ /// <summary>
+ /// This provides a common interface for vendor-specific driver
+ /// initialization routines.
+ /// </summary>
+ public abstract class AdbcDriver : IDisposable
+ {
+ /// <summary>
+ /// Open a database via this driver.
+ /// </summary>
+ /// <param name="parameters">
+ /// Driver-specific parameters.
+ /// </param>
+ public abstract AdbcDatabase Open(IReadOnlyDictionary<string, string> parameters);
+
+ public virtual void Dispose()
+ {
+ }
+ }
+}
diff --git a/csharp/src/Apache.Arrow.Adbc/AdbcException.cs b/csharp/src/Apache.Arrow.Adbc/AdbcException.cs
new file mode 100644
index 00000000..8c1e6ed6
--- /dev/null
+++ b/csharp/src/Apache.Arrow.Adbc/AdbcException.cs
@@ -0,0 +1,88 @@
+/*
+ * 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 Apache.Arrow.Adbc;
+
+/// <summary>
+/// The root exception when working with Adbc drivers.
+/// </summary>
+public class AdbcException : Exception
+{
+ private AdbcStatusCode _statusCode = AdbcStatusCode.UnknownError;
+
+ public AdbcException()
+ {
+ }
+
+ public AdbcException(string message)
+ : base(message)
+ {
+ }
+
+ public AdbcException(string message, AdbcStatusCode statusCode)
+ : base(message)
+ {
+ _statusCode = statusCode;
+ }
+
+ public AdbcException(string message, AdbcStatusCode statusCode, Exception innerException)
+ : base(message, innerException)
+ {
+ }
+
+ public AdbcException(string message, Exception innerException)
+ : base(message, innerException)
+ {
+ }
+
+ public static AdbcException NotImplemented(string message)
+ {
+ return new AdbcException(message, AdbcStatusCode.NotImplemented);
+ }
+
+ /// <summary>
+ /// For database providers which support it, contains a standard
+ /// SQL 5-character return code indicating the success or failure
+ /// of the database operation. The first 2 characters represent the
+ /// class of the return code (e.g. error, success), while the
+ /// last 3 characters represent the subclass, allowing detection
+ /// of error scenarios in a database-portable way.
+ /// For database providers which don't support it, or for
+ /// inapplicable error scenarios, contains null.
+ /// </summary>
+ public virtual string SqlState
+ {
+ get => null;
+ }
+
+ /// <summary>
+ /// Gets or sets the <see cref="AdbcStatusCode"/> for the error.
+ /// </summary>
+ public AdbcStatusCode Status
+ {
+ get => _statusCode;
+ }
+
+ /// <summary>
+ /// Gets a native error number.
+ /// </summary>
+ public virtual int NativeError
+ {
+ get => 0;
+ }
+}
diff --git a/csharp/src/Apache.Arrow.Adbc/AdbcInfoCode.cs b/csharp/src/Apache.Arrow.Adbc/AdbcInfoCode.cs
new file mode 100644
index 00000000..c69f62e1
--- /dev/null
+++ b/csharp/src/Apache.Arrow.Adbc/AdbcInfoCode.cs
@@ -0,0 +1,55 @@
+/*
+ * 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.
+ */
+
+namespace Apache.Arrow.Adbc
+{
+ /// <summary>
+ /// Info codes used for GetInfo
+ /// </summary>
+ public enum AdbcInfoCode
+ {
+ /// <summary>
+ /// The database vendor/product name (e.g. the server name).
+ /// </summary>
+ VendorName = 0,
+
+ /// <summary>
+ /// The database vendor/product version
+ /// </summary>
+ VendorVersion = 1,
+
+ /// <summary>
+ /// The database vendor/product Arrow library version
+ /// </summary>
+ VendorArrowVersion = 2,
+
+ /// <summary>
+ /// The driver name
+ /// </summary>
+ DriverName = 100,
+
+ /// <summary>
+ /// The driver version
+ /// </summary>
+ DriverVersion = 101,
+
+ /// <summary>
+ /// The driver Arrow library version
+ /// </summary>
+ DriverArrowVersion = 102
+ }
+}
diff --git a/csharp/src/Apache.Arrow.Adbc/AdbcStatement.cs b/csharp/src/Apache.Arrow.Adbc/AdbcStatement.cs
new file mode 100644
index 00000000..4d3f4a98
--- /dev/null
+++ b/csharp/src/Apache.Arrow.Adbc/AdbcStatement.cs
@@ -0,0 +1,142 @@
+/*
+ * 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.Text.RegularExpressions;
+using System.Threading.Tasks;
+using Apache.Arrow.Ipc;
+
+namespace Apache.Arrow.Adbc
+{
+ /// <summary>
+ /// Statements may represent queries or prepared statements. Statements
+ /// may be used multiple times and can be reconfigured (e.g. they can
+ /// be reused to execute multiple different queries).
+ /// </summary>
+ public abstract class AdbcStatement : IDisposable
+ {
+ public AdbcStatement()
+ {
+
+ }
+
+ /// <summary>
+ /// Gets or sets a SQL query to be executed on this statement.
+ /// </summary>
+ public virtual string SqlQuery { get; set; }
+
+ /// <summary>
+ /// Gets or sets the Substrait plan.
+ /// </summary>
+ public virtual byte[] SubstraitPlan
+ {
+ get { throw new NotImplementedException(); }
+ set { throw new NotImplementedException(); }
+ }
+
+ public virtual void Bind(RecordBatch batch, Schema schema)
+ {
+ throw AdbcException.NotImplemented("Statement does not support Bind");
+ }
+
+ /// <summary>
+ /// Executes the statement and returns a tuple containing the number
+ /// of records and the <see cref="IArrowArrayStream"/>.
+ /// </summary>
+ /// <returns>
+ /// A <see cref="ValueTuple"/> where the first item is the number
+ /// of records and the second is the <see cref="IArrowArrayStream"/>.
+ /// </returns>
+ public abstract QueryResult ExecuteQuery();
+
+ /// <summary>
+ /// Executes the statement and returns a tuple containing the number
+ /// of records and the <see cref="IArrowArrayStream"/>.
+ /// </summary>
+ /// <returns>
+ /// A <see cref="ValueTuple"/> where the first item is the number
+ /// of records and the second is the <see cref="IArrowArrayStream"/>.
+ /// </returns>
+ public virtual async ValueTask<QueryResult> ExecuteQueryAsync()
+ {
+ return await Task.Run(() => ExecuteQuery());
+ }
+
+ /// <summary>
+ /// Executes an update command and returns the number of
+ /// records effected.
+ /// </summary>
+ /// <exception cref="NotImplementedException"></exception>
+ public abstract UpdateResult ExecuteUpdate();
+
+ /// <summary>
+ /// Executes an update command and returns the number of
+ /// records effected.
+ /// </summary>
+ /// <exception cref="NotImplementedException"></exception>
+ public virtual async Task<UpdateResult> ExecuteUpdateAsync()
+ {
+ return await Task.Run(() => ExecuteUpdate());
+ }
+
+ /// <summary>
+ /// Execute a result set-generating query and get a list of
+ /// partitions of the result set.
+ /// </summary>
+ public virtual PartitionedResult ExecutePartitioned()
+ {
+ throw AdbcException.NotImplemented("Statement does not support ExecutePartitioned");
+ }
+
+ /// <summary>
+ /// Get the schema for bound parameters.
+ /// </summary>
+ public virtual Schema GetParameterSchema()
+ {
+ throw AdbcException.NotImplemented("Statement does not support GetParameterSchema");
+ }
+
+ /// <summary>
+ /// Turn this statement into a prepared statement to be
+ /// executed multiple times.
+ /// </summary>
+ public virtual void Prepare()
+ {
+ throw AdbcException.NotImplemented("Statement does not support Prepare");
+ }
+
+ public virtual void Dispose()
+ {
+ }
+
+ /// <summary>
+ /// Gets a value from the Arrow array at the specified index,
+ /// using the Field metadata for information.
+ /// </summary>
+ /// <param name="arrowArray">
+ /// The Arrow array.
+ /// </param>
+ /// <param name="field">
+ /// The <see cref="Field"/> from the <see cref="Schema"/> that can
+ /// be used for metadata inspection.
+ /// </param>
+ /// <param name="index">
+ /// The index in the array to get the value from.
+ /// </param>
+ public abstract object GetValue(IArrowArray arrowArray, Field field, int index);
+ }
+}
diff --git a/csharp/src/Apache.Arrow.Adbc/AdbcStatusCode.cs b/csharp/src/Apache.Arrow.Adbc/AdbcStatusCode.cs
new file mode 100644
index 00000000..496d17e3
--- /dev/null
+++ b/csharp/src/Apache.Arrow.Adbc/AdbcStatusCode.cs
@@ -0,0 +1,121 @@
+/*
+ * 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.
+ */
+
+namespace Apache.Arrow.Adbc
+{
+ /// <summary>
+ /// ADBC uses integer error codes to signal errors. To provide more
+ /// detail about errors, functions may also return an AdbcError via an
+ /// optional out parameter, which can be inspected.
+ ///
+ /// Generally, error codes may indicate a driver-side or database-side
+ /// error.
+ /// </summary>
+ public enum AdbcStatusCode : byte
+ {
+ /// <summary>
+ /// No error
+ /// </summary>
+ Success = 0,
+
+ /// <summary>
+ /// An unknown error occurred.
+ /// </summary>
+ UnknownError = 1,
+
+ /// <summary>
+ /// The operation is not implemented or supported.
+ /// </summary>
+ NotImplemented = 2,
+
+ /// <summary>
+ /// A requested resource was not found.
+ /// </summary>
+ NotFound = 3,
+
+ /// <summary>
+ /// A requested resource already exists.
+ /// </summary>
+ AlreadyExists = 4,
+
+ /// <summary>
+ /// The arguments are invalid, likely a programming error.
+ /// For instance, they may be of the wrong format, or out of range.
+ /// </summary>
+ InvalidArgument = 5,
+
+ /// <summary>
+ /// The preconditions for the operation are not met, likely a
+ /// programming error. For instance, the object may be uninitialized,
+ /// or may have not been fully configured.
+ /// </summary>
+ InvalidState = 6,
+
+ /// <summary>
+ /// Invalid data was processed (not a programming error). For
+ /// instance, a division by zero may have occurred during
+ /// query execution.
+ ///
+ /// Indicates a database-side error only.
+ /// </summary>
+ InvalidData = 7,
+
+ /// <summary>
+ /// The database's integrity was affected. For instance, a foreign
+ /// key check may have failed, or a uniqueness constraint may have
+ /// been violated.
+ ///
+ /// Indicates a database-side error only.
+ /// </summary>
+ IntegrityError = 8,
+
+ /// <summary>
+ /// An error internal to the driver or database occurred.
+ /// </summary>
+ InternalError = 9,
+
+ /// <summary>
+ /// An I/O error occurred. For instance, a remote service may be
+ /// unavailable.
+ /// </summary>
+ IOError = 10,
+
+ /// <summary>
+ /// The operation was cancelled, not due to a timeout.
+ /// </summary>
+ Cancelled = 11,
+
+ /// <summary>
+ /// The operation was cancelled due to a timeout.
+ /// </summary>
+ Timeout = 12,
+
+ /// <summary>
+ /// Authentication failed.
+ ///
+ /// Indicates a database-side error only.
+ /// </summary>
+ Unauthenticated = 13,
+
+ /// <summary>
+ /// The client is not authorized to perform the given operation.
+ ///
+ /// Indicates a database-side error only.
+ /// </summary>
+ Unauthorized = 14,
+ }
+}
diff --git a/csharp/src/Apache.Arrow.Adbc/Apache.Arrow.Adbc.csproj b/csharp/src/Apache.Arrow.Adbc/Apache.Arrow.Adbc.csproj
new file mode 100644
index 00000000..9276cc1b
--- /dev/null
+++ b/csharp/src/Apache.Arrow.Adbc/Apache.Arrow.Adbc.csproj
@@ -0,0 +1,12 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFrameworks>netstandard2.0;net6.0</TargetFrameworks>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\arrow\csharp\src\Apache.Arrow\Apache.Arrow.csproj" />
+ </ItemGroup>
+
+</Project>
diff --git a/csharp/src/Apache.Arrow.Adbc/BulkIngestMode.cs b/csharp/src/Apache.Arrow.Adbc/BulkIngestMode.cs
new file mode 100644
index 00000000..e324d3c8
--- /dev/null
+++ b/csharp/src/Apache.Arrow.Adbc/BulkIngestMode.cs
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+
+namespace Apache.Arrow.Adbc
+{
+ /// <summary>
+ /// How to handle already-existing/nonexistent tables for bulk ingest
+ /// operations.
+ /// </summary>
+ public enum BulkIngestMode
+ {
+ /// <summary>
+ /// Create the table and insert data; error if the table exists.
+ /// </summary>
+ Create,
+
+ /// <summary>
+ /// Do not create the table and append data; error if the table
+ /// does not exist (<see cref="AdbcStatusCode.NotFound"/>) or
+ /// does not match the schema of the data to append
+ /// (<see cref="AdbcStatusCode.AlreadyExists"/>).
+ /// </summary>
+ Append
+ }
+}
diff --git a/csharp/src/Apache.Arrow.Adbc/Extensions/MarshalExtensions.netstandard.cs b/csharp/src/Apache.Arrow.Adbc/Extensions/MarshalExtensions.netstandard.cs
new file mode 100644
index 00000000..211fcdbe
--- /dev/null
+++ b/csharp/src/Apache.Arrow.Adbc/Extensions/MarshalExtensions.netstandard.cs
@@ -0,0 +1,80 @@
+/*
+ * 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.
+ */
+
+#if NETSTANDARD
+
+using System;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace Apache.Arrow.Adbc.Extensions
+{
+ public static class MarshalExtensions
+ {
+ public static unsafe string PtrToStringUTF8(IntPtr intPtr)
+ {
+ if (intPtr == IntPtr.Zero)
+ {
+ return null;
+ }
+
+ byte* source = (byte*)intPtr;
+ int length = 0;
+
+ while (source[length] != 0)
+ {
+ length++;
+ }
+
+ byte[] bytes = new byte[length];
+ Marshal.Copy(intPtr, bytes, 0, length);
+
+ return Encoding.UTF8.GetString(bytes);
+ }
+
+ public static TDelegate GetDelegateForFunctionPointer<TDelegate>(IntPtr ptr)
+ {
+ return (TDelegate)(object)Marshal.GetDelegateForFunctionPointer(ptr, typeof(TDelegate));
+ }
+
+ public static unsafe IntPtr StringToCoTaskMemUTF8(string s)
+ {
+ if (s is null)
+ {
+ return IntPtr.Zero;
+ }
+
+ int nb = Encoding.UTF8.GetMaxByteCount(s.Length);
+
+ IntPtr pMem = Marshal.AllocCoTaskMem(nb + 1);
+
+ int nbWritten;
+ byte* pbMem = (byte*)pMem;
+
+ fixed (char* firstChar = s)
+ {
+ nbWritten = Encoding.UTF8.GetBytes(firstChar, s.Length, pbMem, nb);
+ }
+
+ pbMem[nbWritten] = 0;
+
+ return pMem;
+ }
+ }
+}
+
+#endif
diff --git a/csharp/src/Apache.Arrow.Adbc/Interop.cs b/csharp/src/Apache.Arrow.Adbc/Interop.cs
new file mode 100644
index 00000000..feda55f7
--- /dev/null
+++ b/csharp/src/Apache.Arrow.Adbc/Interop.cs
@@ -0,0 +1,1203 @@
+/*
+ * 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.Buffers;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using Apache.Arrow.Adbc.Interop;
+using Apache.Arrow.C;
+using Apache.Arrow.Ipc;
+
+#if NETSTANDARD
+using Apache.Arrow.Adbc.Extensions;
+#endif
+
+namespace Apache.Arrow.Adbc
+{
+ internal static class AdbcInterop
+ {
+ private unsafe static readonly NativeDelegate<ErrorRelease> releaseError = new NativeDelegate<ErrorRelease>(ReleaseError);
+ private unsafe static readonly NativeDelegate<DriverRelease> releaseDriver = new NativeDelegate<DriverRelease>(ReleaseDriver);
+
+ private unsafe static readonly NativeDelegate<DatabaseFn> databaseInit = new NativeDelegate<DatabaseFn>(InitDatabase);
+ private unsafe static readonly NativeDelegate<DatabaseFn> databaseRelease = new NativeDelegate<DatabaseFn>(ReleaseDatabase);
+ private unsafe static readonly NativeDelegate<DatabaseSetOption> databaseSetOption = new NativeDelegate<DatabaseSetOption>(SetDatabaseOption);
+
+ private unsafe static readonly NativeDelegate<ConnectionGetObjects> connectionGetObjects = new NativeDelegate<ConnectionGetObjects>(GetConnectionObjects);
+ private unsafe static readonly NativeDelegate<ConnectionGetTableSchema> connectionGetTableSchema = new NativeDelegate<ConnectionGetTableSchema>(GetConnectionTableSchema);
+ private unsafe static readonly NativeDelegate<ConnectionGetTableTypes> connectionGetTableTypes = new NativeDelegate<ConnectionGetTableTypes>(GetConnectionTableTypes);
+ private unsafe static readonly NativeDelegate<ConnectionInit> connectionInit = new NativeDelegate<ConnectionInit>(InitConnection);
+ private unsafe static readonly NativeDelegate<ConnectionFn> connectionRelease = new NativeDelegate<ConnectionFn>(ReleaseConnection);
+ private unsafe static readonly NativeDelegate<ConnectionGetInfo> connectionGetInfo = new NativeDelegate<ConnectionGetInfo>(GetConnectionInfo);
+ private unsafe static readonly NativeDelegate<ConnectionReadPartition> connectionReadPartition = new NativeDelegate<ConnectionReadPartition>(ReadConnectionPartition);
+ private unsafe static readonly NativeDelegate<ConnectionSetOption> connectionSetOption = new NativeDelegate<ConnectionSetOption>(SetConnectionOption);
+
+ private unsafe static readonly NativeDelegate<StatementBind> statementBind = new NativeDelegate<StatementBind>(BindStatement);
+ private unsafe static readonly NativeDelegate<StatementExecuteQuery> statementExecuteQuery = new NativeDelegate<StatementExecuteQuery>(ExecuteStatementQuery);
+ private unsafe static readonly NativeDelegate<StatementNew> statementNew = new NativeDelegate<StatementNew>(NewStatement);
+ private unsafe static readonly NativeDelegate<StatementFn> statementRelease = new NativeDelegate<StatementFn>(ReleaseStatement);
+ private unsafe static readonly NativeDelegate<StatementSetSqlQuery> statementSetSqlQuery = new NativeDelegate<StatementSetSqlQuery>(SetStatementSqlQuery);
+
+ public unsafe static AdbcStatusCode AdbcDriverInit(int version, NativeAdbcDriver* nativeDriver, NativeAdbcError* error, AdbcDriver driver)
+ {
+ DriverStub stub = new DriverStub(driver);
+ GCHandle handle = GCHandle.Alloc(stub);
+ nativeDriver->private_data = (void*)GCHandle.ToIntPtr(handle);
+ nativeDriver->release = (delegate* unmanaged[Stdcall]<NativeAdbcDriver*, NativeAdbcError*, AdbcStatusCode>)releaseDriver.Pointer;
+
+ nativeDriver->DatabaseInit = (delegate* unmanaged[Stdcall]<NativeAdbcDatabase*, NativeAdbcError*, AdbcStatusCode>)databaseInit.Pointer;
+ nativeDriver->DatabaseNew = (delegate* unmanaged[Stdcall]<NativeAdbcDatabase*, NativeAdbcError*, AdbcStatusCode>)stub.newDatabase.Pointer;
+ nativeDriver->DatabaseSetOption = (delegate* unmanaged[Stdcall]<NativeAdbcDatabase*, byte*, byte*, NativeAdbcError*, AdbcStatusCode>)databaseSetOption.Pointer;
+ nativeDriver->DatabaseRelease = (delegate* unmanaged[Stdcall]<NativeAdbcDatabase*, NativeAdbcError*, AdbcStatusCode>)databaseRelease.Pointer;
+
+ nativeDriver->ConnectionCommit = (delegate* unmanaged[Stdcall]<NativeAdbcConnection*, NativeAdbcError*, AdbcStatusCode>)connectionRelease.Pointer;
+ nativeDriver->ConnectionGetInfo = (delegate* unmanaged[Stdcall]<NativeAdbcConnection*, byte*, int, CArrowArrayStream*, NativeAdbcError*, AdbcStatusCode>)connectionGetInfo.Pointer;
+ nativeDriver->ConnectionGetObjects = (delegate* unmanaged[Stdcall]<NativeAdbcConnection*, int, byte*, byte*, byte*, byte**, byte*, CArrowArrayStream*, NativeAdbcError*, AdbcStatusCode>)connectionGetObjects.Pointer;
+ nativeDriver->ConnectionGetTableSchema = (delegate* unmanaged[Stdcall]<NativeAdbcConnection*, byte*, byte*, byte*, CArrowSchema*, NativeAdbcError*, AdbcStatusCode>)connectionGetTableSchema.Pointer;
+ nativeDriver->ConnectionGetTableTypes = (delegate* unmanaged[Stdcall]<NativeAdbcConnection*, CArrowArrayStream*, NativeAdbcError*, AdbcStatusCode>)connectionGetTableTypes.Pointer;
+ nativeDriver->ConnectionInit = (delegate* unmanaged[Stdcall]<NativeAdbcConnection*, NativeAdbcDatabase*, NativeAdbcError*, AdbcStatusCode>)connectionInit.Pointer;
+ nativeDriver->ConnectionNew = (delegate* unmanaged[Stdcall]<NativeAdbcConnection*, NativeAdbcError*, AdbcStatusCode>)stub.newConnection.Pointer;
+ nativeDriver->ConnectionSetOption = (delegate* unmanaged[Stdcall]<NativeAdbcConnection*, byte*, byte*, NativeAdbcError*, AdbcStatusCode>)connectionSetOption.Pointer;
+ nativeDriver->ConnectionReadPartition = (delegate* unmanaged[Stdcall]<NativeAdbcConnection*, byte*, int, CArrowArrayStream*, NativeAdbcError*, AdbcStatusCode>)connectionReadPartition.Pointer;
+ nativeDriver->ConnectionRelease = (delegate* unmanaged[Stdcall]<NativeAdbcConnection*, NativeAdbcError*, AdbcStatusCode>)connectionRelease.Pointer;
+ nativeDriver->ConnectionRollback = (delegate* unmanaged[Stdcall]<NativeAdbcConnection*, NativeAdbcError*, AdbcStatusCode>)connectionRelease.Pointer;
+
+ nativeDriver->StatementBind = (delegate* unmanaged[Stdcall]<NativeAdbcStatement*, CArrowArray*, CArrowSchema*, NativeAdbcError*, AdbcStatusCode>)statementBind.Pointer;
+ nativeDriver->StatementNew = (delegate* unmanaged[Stdcall]<NativeAdbcConnection*, NativeAdbcStatement*, NativeAdbcError*, AdbcStatusCode>)statementNew.Pointer;
+ nativeDriver->StatementSetSqlQuery = (delegate* unmanaged[Stdcall]<NativeAdbcStatement*, byte*, NativeAdbcError*, AdbcStatusCode>)statementSetSqlQuery.Pointer;
+ nativeDriver->StatementExecuteQuery = (delegate* unmanaged[Stdcall]<NativeAdbcStatement*, CArrowArrayStream*, long*, NativeAdbcError*, AdbcStatusCode>)statementExecuteQuery.Pointer;
+ nativeDriver->StatementPrepare = (delegate* unmanaged[Stdcall]<NativeAdbcStatement*, NativeAdbcError*, AdbcStatusCode>)statementRelease.Pointer;
+ nativeDriver->StatementRelease = (delegate* unmanaged[Stdcall]<NativeAdbcStatement*, NativeAdbcError*, AdbcStatusCode>)statementRelease.Pointer;
+
+ return 0;
+ }
+
+ private unsafe static void ReleaseError(NativeAdbcError* error)
+ {
+ if (error != null && ((IntPtr)error->message) != IntPtr.Zero)
+ {
+ Marshal.FreeHGlobal((IntPtr)error->message);
+ }
+ }
+
+ private unsafe static AdbcStatusCode SetError(NativeAdbcError* error, Exception exception)
+ {
+ ReleaseError(error);
+
+#if NETSTANDARD
+ error->message = (byte*)MarshalExtensions.StringToCoTaskMemUTF8(exception.Message);
+#else
+ error->message = (byte*)Marshal.StringToCoTaskMemUTF8(exception.Message);
+#endif
+
+ error->sqlstate0 = (byte)0;
+ error->sqlstate1 = (byte)0;
+ error->sqlstate2 = (byte)0;
+ error->sqlstate3 = (byte)0;
+ error->sqlstate4 = (byte)0;
+ error->vendor_code = 0;
+ error->vendor_code = 0;
+ error->release = (delegate* unmanaged[Stdcall]<NativeAdbcError*, void>)releaseError.Pointer;
+
+ return AdbcStatusCode.UnknownError;
+ }
+
+ private sealed class PinnedArray : IDisposable
+ {
+ IArrowArray _array;
+ MemoryHandle[] pinnedHandles;
+
+ public PinnedArray(IArrowArray array)
+ {
+ _array = array;
+ pinnedHandles = new MemoryHandle[GetHandleCount(array.Data)];
+ int index = 0;
+ PinBuffers(array.Data, pinnedHandles, ref index);
+ Debug.Assert(index == pinnedHandles.Length);
+ }
+
+ public void Dispose()
+ {
+ if (_array != null)
+ {
+ _array.Dispose();
+ foreach (MemoryHandle handle in pinnedHandles)
+ {
+ handle.Dispose();
+ }
+ _array = null;
+ }
+ }
+
+ static int GetHandleCount(ArrayData data)
+ {
+ int handleCount = data.Buffers.Length;
+ foreach (ArrayData child in data.Children)
+ {
+ handleCount += GetHandleCount(child);
+ }
+ if (data.Dictionary != null)
+ {
+ handleCount += GetHandleCount(data.Dictionary);
+ }
+ return handleCount;
+ }
+
+ static void PinBuffers(ArrayData data, MemoryHandle[] handles, ref int index)
+ {
+ foreach (ArrowBuffer buffer in data.Buffers)
+ {
+ handles[index++] = buffer.Memory.Pin();
+ }
+ foreach (ArrayData child in data.Children)
+ {
+ PinBuffers(child, handles, ref index);
+ }
+ if (data.Dictionary != null)
+ {
+ PinBuffers(data.Dictionary, handles, ref index);
+ }
+ }
+ }
+
+ private static IntPtr FromDisposable(IDisposable d)
+ {
+ GCHandle gch = GCHandle.Alloc(d);
+ return GCHandle.ToIntPtr(gch);
+ }
+
+ private static void Dispose(ref IntPtr p)
+ {
+ GCHandle gch = GCHandle.FromIntPtr(p);
+ ((IDisposable)gch.Target).Dispose();
+ gch.Free();
+ p = IntPtr.Zero;
+ }
+
+ private unsafe static AdbcStatusCode ReleaseDriver(NativeAdbcDriver* nativeDriver, NativeAdbcError* error)
+ {
+ GCHandle gch = GCHandle.FromIntPtr((IntPtr)nativeDriver->private_data);
+ DriverStub stub = (DriverStub)gch.Target;
+ stub.Dispose();
+ gch.Free();
+ nativeDriver->private_data = null;
+ return 0;
+ }
+
+ private unsafe static AdbcStatusCode InitDatabase(NativeAdbcDatabase* nativeDatabase, NativeAdbcError* error)
+ {
+ GCHandle gch = GCHandle.FromIntPtr((IntPtr)nativeDatabase->private_data);
+ DatabaseStub stub = (DatabaseStub)gch.Target;
+ return stub.Init(ref *error);
+ }
+
+ private unsafe static AdbcStatusCode ReleaseDatabase(NativeAdbcDatabase* nativeDatabase, NativeAdbcError* error)
+ {
+ if (nativeDatabase->private_data == null)
+ {
+ return AdbcStatusCode.UnknownError;
+ }
+
+ GCHandle gch = GCHandle.FromIntPtr((IntPtr)nativeDatabase->private_data);
+ DatabaseStub stub = (DatabaseStub)gch.Target;
+ stub.Dispose();
+ gch.Free();
+ nativeDatabase->private_data = null;
+ return AdbcStatusCode.Success;
+ }
+
+ private unsafe static AdbcStatusCode SetConnectionOption(NativeAdbcConnection* nativeConnection, byte* name, byte* value, NativeAdbcError* error)
+ {
+ GCHandle gch = GCHandle.FromIntPtr((IntPtr)nativeConnection->private_data);
+ ConnectionStub stub = (ConnectionStub)gch.Target;
+ return stub.SetOption(name, value, ref *error);
+ }
+
+ private unsafe static AdbcStatusCode SetDatabaseOption(NativeAdbcDatabase* nativeDatabase, byte* name, byte* value, NativeAdbcError* error)
+ {
+ GCHandle gch = GCHandle.FromIntPtr((IntPtr)nativeDatabase->private_data);
+ DatabaseStub stub = (DatabaseStub)gch.Target;
+
+ return stub.SetOption(name, value, ref *error);
+ }
+
+ private unsafe static AdbcStatusCode InitConnection(NativeAdbcConnection* nativeConnection, NativeAdbcDatabase* database, NativeAdbcError* error)
+ {
+ GCHandle gch = GCHandle.FromIntPtr((IntPtr)nativeConnection->private_data);
+ ConnectionStub stub = (ConnectionStub)gch.Target;
+ return stub.InitConnection(ref *database, ref *error);
+ }
+
+
+ private unsafe static AdbcStatusCode GetConnectionObjects(NativeAdbcConnection* nativeConnection, int depth, byte* catalog, byte* db_schema, byte* table_name, byte** table_type, byte* column_name, CArrowArrayStream* stream, NativeAdbcError* error)
+ {
+ if (nativeConnection->private_data == null)
+ {
+ return AdbcStatusCode.UnknownError;
+ }
+
+ GCHandle gch = GCHandle.FromIntPtr((IntPtr)nativeConnection->private_data);
+ ConnectionStub stub = (ConnectionStub)gch.Target;
+ return stub.GetObjects(ref *nativeConnection, depth, catalog, db_schema, table_name, table_type, column_name, stream, ref *error);
+ }
+
+ private unsafe static AdbcStatusCode GetConnectionTableTypes(NativeAdbcConnection* nativeConnection, CArrowArrayStream* stream, NativeAdbcError* error)
+ {
+ if (nativeConnection->private_data == null)
+ {
+ return AdbcStatusCode.UnknownError;
+ }
+
+ GCHandle gch = GCHandle.FromIntPtr((IntPtr)nativeConnection->private_data);
+ ConnectionStub stub = (ConnectionStub)gch.Target;
+ return stub.GetTableTypes(ref *nativeConnection, stream, ref *error);
+ }
+
+ private unsafe static AdbcStatusCode GetConnectionTableSchema(NativeAdbcConnection* nativeConnection, byte* catalog, byte* db_schema, byte* table_name, CArrowSchema* schema, NativeAdbcError* error)
+ {
+ if (nativeConnection->private_data == null)
+ {
+ return AdbcStatusCode.UnknownError;
+ }
+
+ GCHandle gch = GCHandle.FromIntPtr((IntPtr)nativeConnection->private_data);
+ ConnectionStub stub = (ConnectionStub)gch.Target;
+ return stub.GetTableSchema(ref *nativeConnection, catalog, db_schema, table_name, schema, ref *error);
+ }
+
+ private unsafe static AdbcStatusCode ReleaseConnection(NativeAdbcConnection* nativeConnection, NativeAdbcError* error)
+ {
+ if (nativeConnection->private_data == null)
+ {
+ return AdbcStatusCode.UnknownError;
+ }
+
+ GCHandle gch = GCHandle.FromIntPtr((IntPtr)nativeConnection->private_data);
+ ConnectionStub stub = (ConnectionStub)gch.Target;
+ stub.Dispose();
+ gch.Free();
+ nativeConnection->private_data = null;
+ return AdbcStatusCode.Success;
+ }
+
+ private unsafe static AdbcStatusCode ReadConnectionPartition(NativeAdbcConnection* nativeConnection, byte* serialized_partition, int serialized_length, CArrowArrayStream* stream, NativeAdbcError* error)
+ {
+ GCHandle gch = GCHandle.FromIntPtr((IntPtr)nativeConnection->private_data);
+ ConnectionStub stub = (ConnectionStub)gch.Target;
+ return stub.ReadPartition(ref *nativeConnection, serialized_partition, serialized_length, stream, ref *error);
+ }
+
+ private unsafe static AdbcStatusCode GetConnectionInfo(NativeAdbcConnection* nativeConnection, byte* info_codes, int info_codes_length, CArrowArrayStream* stream, NativeAdbcError* error)
+ {
+ GCHandle gch = GCHandle.FromIntPtr((IntPtr)nativeConnection->private_data);
+ ConnectionStub stub = (ConnectionStub)gch.Target;
+ return stub.GetInfo(ref *nativeConnection, info_codes, info_codes_length, stream, ref *error);
+ }
+
+ private unsafe static AdbcStatusCode SetStatementSqlQuery(NativeAdbcStatement* nativeStatement, byte* text, NativeAdbcError* error)
+ {
+ GCHandle gch = GCHandle.FromIntPtr((IntPtr)nativeStatement->private_data);
+ AdbcStatement stub = (AdbcStatement)gch.Target;
+
+#if NETSTANDARD
+ stub.SqlQuery = MarshalExtensions.PtrToStringUTF8((IntPtr)text);
+#else
+ stub.SqlQuery = Marshal.PtrToStringUTF8((IntPtr)text);
+#endif
+
+ return AdbcStatusCode.Success;
+ }
+
+ private unsafe static AdbcStatusCode BindStatement(NativeAdbcStatement* nativeStatement, CArrowArray* array, CArrowSchema* cschema, NativeAdbcError* error)
+ {
+ GCHandle gch = GCHandle.FromIntPtr((IntPtr)nativeStatement->private_data);
+ AdbcStatement stub = (AdbcStatement)gch.Target;
+
+ Schema schema = CArrowSchemaImporter.ImportSchema(cschema);
+
+ RecordBatch batch = CArrowArrayImporter.ImportRecordBatch(array, schema);
+
+ stub.Bind(batch, schema);
+
+ return 0;
+ }
+
+ private unsafe static AdbcStatusCode ExecuteStatementQuery(NativeAdbcStatement* nativeStatement, CArrowArrayStream* stream, long* rows, NativeAdbcError* error)
+ {
+ GCHandle gch = GCHandle.FromIntPtr((IntPtr)nativeStatement->private_data);
+ AdbcStatement stub = (AdbcStatement)gch.Target;
+ var result = stub.ExecuteQuery();
+ if (rows != null)
+ {
+ *rows = result.RowCount;
+ }
+
+ GCHandle streamHandle = GCHandle.Alloc(result.Stream);
+ stream->private_data = (void*)GCHandle.ToIntPtr(streamHandle);
+
+ return 0;
+ }
+
+ private unsafe static AdbcStatusCode NewStatement(NativeAdbcConnection* nativeConnection, NativeAdbcStatement* nativeStatement, NativeAdbcError* error)
+ {
+ GCHandle gch = GCHandle.FromIntPtr((IntPtr)nativeConnection->private_data);
+ ConnectionStub stub = (ConnectionStub)gch.Target;
+ return stub.NewStatement(ref *nativeStatement, ref *error);
+ }
+
+ private unsafe static AdbcStatusCode ReleaseStatement(NativeAdbcStatement* nativeStatement, NativeAdbcError* error)
+ {
+ if (nativeStatement->private_data == null)
+ {
+ return AdbcStatusCode.UnknownError;
+ }
+
+ GCHandle gch = GCHandle.FromIntPtr((IntPtr)nativeStatement->private_data);
+ AdbcStatement stub = (AdbcStatement)gch.Target;
+ stub.Dispose();
+ gch.Free();
+ nativeStatement->private_data = null;
+ return AdbcStatusCode.Success;
+ }
+ }
+
+ /// <summary>
+ /// Clients first initialize a database, then create a connection.
+ /// This gives the implementation a place to initialize and
+ /// own any common connection state. For example, in-memory databases
+ /// can place ownership of the actual database in this object.
+ /// </summary>
+ /// <remarks>
+ /// An instance of a database.
+ ///
+ /// Must be kept alive as long as any connections exist.
+ /// </remarks>
+ [StructLayout(LayoutKind.Sequential)]
+ public unsafe struct NativeAdbcDatabase
+ {
+ /// <summary>
+ /// Opaque implementation-defined state.
+ /// This field is NULLPTR iff the connection is unintialized/freed.
+ /// </summary>
+ public void* private_data;
+
+ /// <summary>
+ /// The associated driver (used by the driver manager to help track
+ /// state).
+ /// </summary>
+ public NativeAdbcDriver* private_driver;
+
+ public static NativeAdbcDatabase* Create()
+ {
+ var ptr = (NativeAdbcDatabase*)Marshal.AllocHGlobal(sizeof(NativeAdbcDatabase));
+
+ ptr->private_data = null;
+ ptr->private_driver = null;
+
+ return ptr;
+ }
+
+ /// <summary>
+ /// Free a pointer that was allocated in <see cref="Create"/>.
+ /// </summary>
+ /// <remarks>
+ /// Do not call this on a pointer that was allocated elsewhere.
+ /// </remarks>
+ public static void Free(NativeAdbcDatabase* database)
+ {
+ Marshal.FreeHGlobal((IntPtr)database);
+ }
+ }
+
+ /// <summary>
+ /// An active database connection.
+ ///
+ /// Provides methods for query execution, managing prepared
+ /// statements, using transactions, and so on.
+ ///
+ /// Connections are not required to be thread-safe, but they can be
+ /// used from multiple threads so long as clients take care to
+ /// serialize accesses to a connection.
+ /// </summary>
+ [StructLayout(LayoutKind.Sequential)]
+ public unsafe struct NativeAdbcConnection
+ {
+ /// <summary>
+ /// Opaque implementation-defined state.
+ /// This field is NULLPTR iff the connection is unintialized/freed.
+ /// </summary>
+ public void* private_data;
+
+ /// <summary>
+ /// The associated driver (used by the driver manager to help
+ /// track state).
+ /// </summary>
+ public NativeAdbcDriver* private_driver;
+ }
+
+ /// <summary>
+ /// A container for all state needed to execute a database
+ /// query, such as the query itself, parameters for prepared
+ /// statements, driver parameters, etc.
+ ///
+ /// Statements may represent queries or prepared statements.
+ ///
+ /// Statements may be used multiple times and can be reconfigured
+ /// (e.g. they can be reused to execute multiple different queries).
+ /// However, executing a statement (and changing certain other state)
+ /// will invalidate result sets obtained prior to that execution.
+ ///
+ /// Multiple statements may be created from a single connection.
+ /// However, the driver may block or error if they are used
+ /// concurrently (whether from a single thread or multiple threads).
+ ///
+ /// Statements are not required to be thread-safe, but they can be
+ /// used from multiple threads so long as clients take care to
+ /// serialize accesses to a statement.
+ /// </summary>
+ [StructLayout(LayoutKind.Sequential)]
+ public unsafe struct NativeAdbcStatement
+ {
+ /// <summary>
+ /// Opaque implementation-defined state.
+ /// This field is NULLPTR iff the connection is unintialized/freed.
+ /// </summary>
+ public void* private_data;
+
+ /// <summary>
+ /// The associated driver (used by the driver manager to help
+ /// track state).
+ /// </summary>
+ public NativeAdbcDriver* private_driver;
+ }
+
+ /// <summary>
+ /// The partitions of a distributed/partitioned result set.
+ /// </summary>
+ /// <remarks>
+ /// Some backends may internally partition the results. These
+ /// partitions are exposed to clients who may wish to integrate them
+ /// with a threaded or distributed execution model, where partitions
+ /// can be divided among threads or machines and fetched in parallel.
+ ///
+ /// To use partitioning, execute the statement with
+ /// AdbcStatementExecutePartitions to get the partition descriptors.
+ /// Call AdbcConnectionReadPartition to turn the individual
+ /// descriptors into ArrowArrayStream instances. This may be done on
+ /// a different connection than the one the partition was created
+ /// with, or even in a different process on another machine.
+ ///
+ /// Drivers are not required to support partitioning.
+ /// </remarks>
+ [StructLayout(LayoutKind.Sequential)]
+ public unsafe struct NativeAdbcPartitions
+ {
+ /// <summary>
+ /// The number of partitions.
+ /// </summary>
+ public int num_partitions;
+
+ /// <summary>
+ /// The partitions of the result set, where each entry (up to
+ /// num_partitions entries) is an opaque identifier that can be
+ /// passed to AdbcConnectionReadPartition.
+ /// </summary>
+ public byte** partitions;
+
+ /// <summary>
+ /// The length of each corresponding entry in partitions.
+ /// </summary>
+ public nuint* partition_lengths;
+
+ /// <summary>
+ /// Opaque implementation-defined state.
+ /// This field is NULLPTR iff the connection is unintialized/freed.
+ /// </summary>
+ public void* private_data;
+
+ /// <summary>
+ /// Release the contained partitions.
+ ///
+ /// Unlike other structures, this is an embedded callback to make it
+ /// easier for the driver manager and driver to cooperate.
+ /// </summary>
+ public delegate* unmanaged[Stdcall]<NativeAdbcPartitions*, void> release;
+ }
+
+ /// <summary>
+ /// A detailed error message for an operation.
+ /// </summary>
+ [StructLayout(LayoutKind.Sequential)]
+ public unsafe struct NativeAdbcError
+ {
+ /// <summary>
+ /// The error message.
+ /// </summary>
+ public byte* message;
+
+ /// <summary>
+ /// A vendor-specific error code, if applicable.
+ /// </summary>
+ public int vendor_code;
+
+ /// <summary>
+ /// A SQLSTATE error code, if provided, as defined by the
+ /// SQL:2003 standard. If not set, it should be set to
+ /// "\0\0\0\0\0".
+ ///
+ /// This is the first value.
+ ///</summary>
+ public byte sqlstate0;
+
+ /// <summary>
+ /// A SQLSTATE error code, if provided, as defined by the
+ /// SQL:2003 standard. If not set, it should be set to
+ /// "\0\0\0\0\0".
+ ///
+ /// This is the second value.
+ ///</summary>
+ public byte sqlstate1;
+
+ /// <summary>
+ /// A SQLSTATE error code, if provided, as defined by the
+ /// SQL:2003 standard. If not set, it should be set to
+ /// "\0\0\0\0\0".
+ ///
+ /// This is the third value.
+ ///</summary>
+ public byte sqlstate2;
+
+ /// <summary>
+ /// A SQLSTATE error code, if provided, as defined by the
+ /// SQL:2003 standard. If not set, it should be set to
+ /// "\0\0\0\0\0".
+ ///
+ /// This is the fourth value.
+ ///</summary>
+ public byte sqlstate3;
+
+ /// <summary>
+ /// A SQLSTATE error code, if provided, as defined by the
+ /// SQL:2003 standard. If not set, it should be set to
+ /// "\0\0\0\0\0".
+ ///
+ /// This is the last value.
+ ///</summary>
+ public byte sqlstate4;
+
+ /// <summary>
+ /// Release the contained error.
+ ///
+ /// Unlike other structures, this is an embedded callback to make it
+ /// easier for the driver manager and driver to cooperate.
+ /// </summary>
+ public delegate* unmanaged[Stdcall]<NativeAdbcError*, void> release;
+ };
+
+ /// <summary>
+ /// An instance of an initialized database driver.
+ /// </summary>
+ /// <remarks>
+ /// This provides a common interface for vendor-specific driver
+ /// initialization routines. Drivers should populate this struct, and
+ /// applications can call ADBC functions through this struct, without
+ /// worrying about multiple definitions of the same symbol.
+ /// </remarks>
+ [StructLayout(LayoutKind.Sequential)]
+ public unsafe struct NativeAdbcDriver
+ {
+ /// <summary>
+ /// Opaque driver-defined state.
+ /// This field is NULL if the driver is unintialized/freed (but
+ /// it need not have a value even if the driver is initialized).
+ /// </summary>
+ public void* private_data;
+
+ /// <summary>
+ /// Opaque driver manager-defined state.
+ /// This field is NULL if the driver is unintialized/freed (but
+ /// it need not have a value even if the driver is initialized).
+ /// </summary>
+ public void* private_manager;
+
+ /// <summary>
+ /// Release the driver and perform any cleanup.
+ ///
+ /// This is an embedded callback to make it easier for the driver
+ /// manager and driver to cooperate.
+ /// </summary>
+ public delegate* unmanaged[Stdcall]<NativeAdbcDriver*, NativeAdbcError*, AdbcStatusCode> release;
+
+ /// <summary>
+ /// Finish setting options and initialize the database.
+ ///
+ /// Some drivers may support setting options after initialization
+ /// as well.
+ /// </summary>
+ public delegate* unmanaged[Stdcall]<NativeAdbcDatabase*, NativeAdbcError*, AdbcStatusCode> DatabaseInit;
+
+ /// <summary>
+ /// Allocate a new (but uninitialized) database.
+ ///
+ /// Callers pass in a zero-initialized AdbcDatabase.
+ ///
+ /// Drivers should allocate their internal data structure and set
+ /// the private_data field to point to the newly allocated struct.
+ /// This struct should be released when AdbcDatabaseRelease is called.
+ /// </summary>
+ public delegate* unmanaged[Stdcall]<NativeAdbcDatabase*, NativeAdbcError*, AdbcStatusCode> DatabaseNew;
+
+ /// <summary>
+ /// Set a byte* option.
+ ///
+ /// Options may be set before AdbcDatabaseInit. Some drivers may
+ /// support setting options after initialization as well.
+ ///
+ /// </summary>
+ public delegate* unmanaged[Stdcall]<NativeAdbcDatabase*, byte*, byte*, NativeAdbcError*, AdbcStatusCode> DatabaseSetOption;
+
+ /// <summary>
+ /// Destroy this database. No connections may exist.
+ /// </summary>
+ public delegate* unmanaged[Stdcall]<NativeAdbcDatabase*, NativeAdbcError*, AdbcStatusCode> DatabaseRelease;
+
+ /// <summary>
+ /// Commit any pending transactions. Only used if autocommit is
+ /// disabled.
+ ///
+ /// Behavior is undefined if this is mixed with SQL transaction
+ /// statements.
+ /// </summary>
+ public delegate* unmanaged[Stdcall]<NativeAdbcConnection*, NativeAdbcError*, AdbcStatusCode> ConnectionCommit; // ConnectionFn
+
+ /// <summary>
+ /// Get metadata about the database/driver.
+ ///
+ /// The result is an Arrow dataset with the following schema:
+ ///
+ /// Field Name | Field Type
+ /// ----------------------------|------------------------
+ /// info_name | uint32 not null
+ /// info_value | INFO_SCHEMA
+ ///
+ /// INFO_SCHEMA is a dense union with members:
+ ///
+ /// Field Name (Type Code) | Field Type
+ /// ----------------------------|------------------------
+ /// string_value (0) | utf8
+ /// bool_value (1) | bool
+ /// int64_value (2) | int64
+ /// int32_bitmask (3) | int32
+ /// string_list (4) | list<utf8>
+ /// int32_to_int32_list_map (5) | map<int32, list<int32>>
+ ///
+ /// Each metadatum is identified by an integer code. The recognized
+ /// codes are defined as constants. Codes [0, 10_000) are reserved
+ /// for ADBC usage. Drivers/vendors will ignore requests for
+ /// unrecognized codes (the row will be omitted from the result).
+ /// </summary>
+ public delegate* unmanaged[Stdcall]<NativeAdbcConnection*, byte*, int, CArrowArrayStream*, NativeAdbcError*, AdbcStatusCode> ConnectionGetInfo;
+
+ /// <summary>
+ /// Get a hierarchical view of all catalogs, database schemas,
+ /// tables, and columns.
+ ///
+ /// The result is an Arrow dataset with the following schema:
+ ///
+ /// | Field Name | Field Type |
+ /// |--------------------------|-------------------------|
+ /// | catalog_name | utf8 |
+ /// | catalog_db_schemas | list<DB_SCHEMA_SCHEMA> |
+ ///
+ /// DB_SCHEMA_SCHEMA is a Struct with fields:
+ ///
+ /// | Field Name | Field Type |
+ /// |--------------------------|-------------------------|
+ /// | db_schema_name | utf8 |
+ /// | db_schema_tables | list<TABLE_SCHEMA> |
+ ///
+ /// TABLE_SCHEMA is a Struct with fields:
+ ///
+ /// | Field Name | Field Type |
+ /// |--------------------------|-------------------------|
+ /// | table_name | utf8 not null |
+ /// | table_type | utf8 not null |
+ /// | table_columns | list<COLUMN_SCHEMA> |
+ /// | table_constraints | list<CONSTRAINT_SCHEMA> |
+ /// </summary>
+ public delegate* unmanaged[Stdcall]<NativeAdbcConnection*, int, byte*, byte*, byte*, byte**, byte*, CArrowArrayStream*, NativeAdbcError*, AdbcStatusCode> ConnectionGetObjects;
+
+ /// <summary>
+ /// Get the Arrow schema of a table.
+ /// </summary>
+ public delegate* unmanaged[Stdcall]<NativeAdbcConnection*, byte*, byte*, byte*, CArrowSchema*, NativeAdbcError*, AdbcStatusCode> ConnectionGetTableSchema;
+
+ /// <summary>
+ /// Get a list of table types in the database.
+ ///
+ /// The result is an Arrow dataset with the following schema:
+ ///
+ /// Field Name | Field Type
+ /// ---------------|--------------
+ /// table_type | utf8 not null
+ /// </summary>
+ public delegate* unmanaged[Stdcall]<NativeAdbcConnection*, CArrowArrayStream*, NativeAdbcError*, AdbcStatusCode> ConnectionGetTableTypes;
+
+ /// <summary>
+ /// Finish setting options and initialize the connection.
+ ///
+ /// Some drivers may support setting options after initialization
+ /// as well.
+ /// </summary>
+ public delegate* unmanaged[Stdcall]<NativeAdbcConnection*, NativeAdbcDatabase*, NativeAdbcError*, AdbcStatusCode> ConnectionInit;
+
+ /// <summary>
+ /// Allocate a new (but uninitialized) connection.
+ ///
+ /// Callers pass in a zero-initialized AdbcConnection.
+ ///
+ /// Drivers should allocate their internal data structure and set
+ /// the private_data field to point to the newly allocated struct.
+ /// This struct should be released when AdbcConnectionRelease is
+ /// called.
+ /// </summary>
+ public delegate* unmanaged[Stdcall]<NativeAdbcConnection*, NativeAdbcError*, AdbcStatusCode> ConnectionNew; // ConnectionFn
+
+ /// <summary>
+ /// Set a byte* option.
+ ///
+ /// Options may be set before AdbcConnectionInit. Some drivers may
+ /// support setting options after initialization as well.
+ /// </summary>
+ public delegate* unmanaged[Stdcall]<NativeAdbcConnection*, byte*, byte*, NativeAdbcError*, AdbcStatusCode> ConnectionSetOption;
+
+ /// <summary>
+ /// Construct a statement for a partition of a query. The
+ /// results can then be read independently.
+ ///
+ /// A partition can be retrieved from AdbcPartitions.
+ /// </summary>
+ public delegate* unmanaged[Stdcall]<NativeAdbcConnection*, byte*, int, CArrowArrayStream*, NativeAdbcError*, AdbcStatusCode> ConnectionReadPartition;
+
+ /// <summary>
+ /// Destroy this connection.
+ /// </summary>
+ public delegate* unmanaged[Stdcall]<NativeAdbcConnection*, NativeAdbcError*, AdbcStatusCode> ConnectionRelease; // ConnectionFn
+
+ /// <summary>
+ /// Roll back any pending transactions. Only used if autocommit is disabled.
+ ///
+ /// Behavior is undefined if this is mixed with SQL transaction
+ /// statements.
+ /// </summary>
+ public delegate* unmanaged[Stdcall]<NativeAdbcConnection*, NativeAdbcError*, AdbcStatusCode> ConnectionRollback; // ConnectionFn
+
+ /// <summary>
+ /// Bind Arrow data. This can be used for bulk inserts or prepared
+ /// statements.
+ /// </summary>
+ public delegate* unmanaged[Stdcall]<NativeAdbcStatement*, CArrowArray*, CArrowSchema*, NativeAdbcError*, AdbcStatusCode> StatementBind;
+
+ /// <summary>
+ /// Bind Arrow data. This can be used for bulk inserts or prepared
+ /// statements.
+ /// </summary>
+ public delegate* unmanaged[Stdcall]<NativeAdbcStatement*, CArrowArrayStream*, NativeAdbcError*, AdbcStatusCode> StatementBindStream;
+
+ /// <summary>
+ /// Execute a statement and get the results.
+ ///
+ /// This invalidates any prior result sets.
+ /// </summary>
+ public delegate* unmanaged[Stdcall]<NativeAdbcStatement*, CArrowArrayStream*, long*, NativeAdbcError*, AdbcStatusCode> StatementExecuteQuery;
+
+ /// <summary>
+ /// Execute a statement and get the results as a partitioned result
+ /// set.
+ /// </summary>
+ public delegate* unmanaged[Stdcall]<NativeAdbcStatement*, CArrowSchema*, NativeAdbcPartitions*, long*, NativeAdbcError*, AdbcStatusCode> StatementExecutePartitions;
+
+ /// <summary>
+ /// Get the schema for bound parameters.
+ ///
+ /// This retrieves an Arrow schema describing the number, names, and
+ /// types of the parameters in a parameterized statement. The fields
+ /// of the schema should be in order of the ordinal position of the
+ /// parameters; named parameters should appear only once.
+ ///
+ /// If the parameter does not have a name, or the name cannot be
+ /// determined, the name of the corresponding field in the schema
+ /// will be an empty string. If the type cannot be determined,
+ /// the type of the corresponding field will be NA (NullType).
+ ///
+ /// This should be called after AdbcStatementPrepare.
+ /// </summary>
+ public delegate* unmanaged[Stdcall]<NativeAdbcStatement*, CArrowSchema*, NativeAdbcError*, AdbcStatusCode> StatementGetParameterSchema;
+
+ /// <summary>
+ /// Create a new statement for a given connection.
+ ///
+ /// Callers pass in a zero-initialized AdbcStatement.
+ ///
+ /// Drivers should allocate their internal data structure and set
+ /// the private_data field to point to the newly allocated struct.
+ /// This struct should be released when AdbcStatementRelease is called.
+ /// </summary>
+ public delegate* unmanaged[Stdcall]<NativeAdbcConnection*, NativeAdbcStatement*, NativeAdbcError*, AdbcStatusCode> StatementNew;
+
+ /// <summary>
+ /// Turn this statement into a prepared statement to be
+ /// executed multiple times.
+ ///
+ /// This invalidates any prior result sets.
+ /// </summary>
+ public delegate* unmanaged[Stdcall]<NativeAdbcStatement*, NativeAdbcError*, AdbcStatusCode> StatementPrepare; // StatementFn
+
+ /// <summary>
+ /// Destroy a statement.
+ /// </summary>
+ public delegate* unmanaged[Stdcall]<NativeAdbcStatement*, NativeAdbcError*, AdbcStatusCode> StatementRelease; // StatementFn
+
+ /// <summary>
+ /// Set a string option on a statement.
+ /// </summary>
+ public delegate* unmanaged[Stdcall]<NativeAdbcStatement*, byte*, byte*, NativeAdbcError*, AdbcStatusCode> StatementSetOption;
+
+ /// <summary>
+ /// Set the SQL query to execute.
+ ///
+ /// The query can then be executed with StatementExecute. For
+ /// queries expected to be executed repeatedly, StatementPrepare
+ /// the statement first.
+ /// </summary>
+ public delegate* unmanaged[Stdcall]<NativeAdbcStatement*, byte*, NativeAdbcError*, AdbcStatusCode> StatementSetSqlQuery;
+
+ /// <summary>
+ /// Set the Substrait plan to execute.
+ ///
+ /// The query can then be executed with AdbcStatementExecute. For
+ /// queries expected to be executed repeatedly, AdbcStatementPrepare
+ /// the statement first.
+ /// </summary>
+ public delegate* unmanaged[Stdcall]<NativeAdbcStatement*, byte*, int, NativeAdbcError*, AdbcStatusCode> StatementSetSubstraitPlan;
+ }
+
+ unsafe delegate AdbcStatusCode DriverRelease(NativeAdbcDriver* driver, NativeAdbcError* error);
+
+ unsafe delegate AdbcStatusCode DatabaseFn(NativeAdbcDatabase* database, NativeAdbcError* error);
+ unsafe delegate AdbcStatusCode DatabaseSetOption(NativeAdbcDatabase* database, byte* name, byte* value, NativeAdbcError* error);
+
+ unsafe delegate AdbcStatusCode ConnectionFn(NativeAdbcConnection* connection, NativeAdbcError* error);
+ unsafe delegate AdbcStatusCode ConnectionGetInfo(NativeAdbcConnection* connection, byte* info_codes, int info_codes_length, CArrowArrayStream* stream, NativeAdbcError* error);
+ unsafe delegate AdbcStatusCode ConnectionGetObjects(NativeAdbcConnection* connection, int depth, byte* catalog, byte* db_schema, byte* table_name, byte** table_type, byte* column_name, CArrowArrayStream* stream, NativeAdbcError* error);
+ unsafe delegate AdbcStatusCode ConnectionGetTableSchema(NativeAdbcConnection* connection, byte* catalog, byte* db_schema, byte* table_name, CArrowSchema* schema, NativeAdbcError* error);
+ unsafe delegate AdbcStatusCode ConnectionGetTableTypes(NativeAdbcConnection* connection, CArrowArrayStream* stream, NativeAdbcError* error);
+ unsafe delegate AdbcStatusCode ConnectionInit(NativeAdbcConnection* connection, NativeAdbcDatabase* database, NativeAdbcError* error);
+ unsafe delegate AdbcStatusCode ConnectionSetOption(NativeAdbcConnection* connection, byte* name, byte* value, NativeAdbcError* error);
+ unsafe delegate AdbcStatusCode ConnectionReadPartition(NativeAdbcConnection* connection, byte* serialized_partition, int serialized_length, CArrowArrayStream* stream, NativeAdbcError* error);
+
+ unsafe delegate AdbcStatusCode StatementBind(NativeAdbcStatement* statement, CArrowArray* array, CArrowSchema* schema, NativeAdbcError* error);
+ unsafe delegate AdbcStatusCode StatementBindStream(NativeAdbcStatement* statement, CArrowArrayStream* stream, NativeAdbcError* error);
+ unsafe delegate AdbcStatusCode StatementExecuteQuery(NativeAdbcStatement* statement, CArrowArrayStream* stream, long* rows, NativeAdbcError* error);
+ unsafe delegate AdbcStatusCode StatementExecutePartitions(NativeAdbcStatement* statement, CArrowSchema* schema, NativeAdbcPartitions* partitions, long* rows_affected, NativeAdbcError* error);
+ unsafe delegate AdbcStatusCode StatementGetParameterSchema(NativeAdbcStatement* statement, CArrowSchema* schema, NativeAdbcError* error);
+ unsafe delegate AdbcStatusCode StatementNew(NativeAdbcConnection* connection, NativeAdbcStatement* statement, NativeAdbcError* error);
+ unsafe delegate AdbcStatusCode StatementFn(NativeAdbcStatement* statement, NativeAdbcError* error);
+ unsafe delegate AdbcStatusCode StatementSetOption(NativeAdbcStatement* statement, byte* name, byte* value, NativeAdbcError* error);
+ unsafe delegate AdbcStatusCode StatementSetSqlQuery(NativeAdbcStatement* statement, byte* text, NativeAdbcError* error);
+ unsafe delegate AdbcStatusCode StatementSetSubstraitPlan(NativeAdbcStatement statement, byte* plan, int length, NativeAdbcError error);
+
+ unsafe delegate void ErrorRelease(NativeAdbcError* error);
+
+ sealed class DriverStub : IDisposable
+ {
+ readonly AdbcDriver _driver;
+ public unsafe readonly NativeDelegate<DatabaseFn> newDatabase;
+ public unsafe readonly NativeDelegate<ConnectionFn> newConnection;
+
+ public DriverStub(AdbcDriver driver)
+ {
+ _driver = driver;
+
+ unsafe
+ {
+ newDatabase = new NativeDelegate<DatabaseFn>(NewDatabase);
+ newConnection = new NativeDelegate<ConnectionFn>(NewConnection);
+ }
+ }
+
+ public unsafe AdbcStatusCode NewDatabase(NativeAdbcDatabase* nativeDatabase, NativeAdbcError* error)
+ {
+ if (nativeDatabase->private_data == null)
+ {
+ return AdbcStatusCode.UnknownError;
+ }
+
+ DatabaseStub stub = new DatabaseStub(_driver);
+ GCHandle handle = GCHandle.Alloc(stub);
+ nativeDatabase->private_data = (void*)GCHandle.ToIntPtr(handle);
+
+ return AdbcStatusCode.Success;
+ }
+
+ public unsafe AdbcStatusCode NewConnection(NativeAdbcConnection* nativeConnection, NativeAdbcError* error)
+ {
+ if (nativeConnection->private_data == null)
+ {
+ return AdbcStatusCode.UnknownError;
+ }
+
+ ConnectionStub stub = new ConnectionStub(_driver);
+ GCHandle handle = GCHandle.Alloc(stub);
+ nativeConnection->private_data = (void*)GCHandle.ToIntPtr(handle);
+
+ return AdbcStatusCode.Success;
+ }
+
+ public void Dispose()
+ {
+ _driver.Dispose();
+ }
+ }
+
+ sealed class DatabaseStub : IDisposable
+ {
+ readonly AdbcDriver _driver;
+ readonly Dictionary<string, string> options;
+ AdbcDatabase database;
+
+ public DatabaseStub(AdbcDriver driver)
+ {
+ _driver = driver;
+ options = new Dictionary<string, string>();
+ }
+
+ public AdbcStatusCode Init(ref NativeAdbcError error)
+ {
+ if (database != null)
+ {
+ return AdbcStatusCode.UnknownError;
+ }
+
+ database = _driver.Open(options);
+ return AdbcStatusCode.Success;
+ }
+
+ public unsafe AdbcStatusCode SetOption(byte* name, byte* value, ref NativeAdbcError error)
+ {
+ IntPtr namePtr = (IntPtr)name;
+ IntPtr valuePtr = (IntPtr)value;
+
+#if NETSTANDARD
+ options[MarshalExtensions.PtrToStringUTF8(namePtr)] = MarshalExtensions.PtrToStringUTF8(valuePtr);
+#else
+ options[Marshal.PtrToStringUTF8(namePtr)] = Marshal.PtrToStringUTF8(valuePtr);
+#endif
+
+ return AdbcStatusCode.Success;
+ }
+
+ public AdbcStatusCode OpenConnection(IReadOnlyDictionary<string, string> options, ref NativeAdbcError error, out AdbcConnection connection)
+ {
+ if (database == null)
+ {
+ connection = null;
+ return AdbcStatusCode.UnknownError;
+ }
+
+ connection = database.Connect(options);
+ return AdbcStatusCode.Success;
+ }
+
+ public void Dispose()
+ {
+ database?.Dispose();
+ database = null;
+ }
+ }
+
+ sealed class ConnectionStub : IDisposable
+ {
+ readonly AdbcDriver _driver;
+ readonly Dictionary<string, string> options;
+ AdbcConnection connection;
+
+ public ConnectionStub(AdbcDriver driver)
+ {
+ _driver = driver;
+ options = new Dictionary<string, string>();
+ }
+
+ public unsafe AdbcStatusCode SetOption(byte* name, byte* value, ref NativeAdbcError error)
+ {
+ IntPtr namePtr = (IntPtr)name;
+ IntPtr valuePtr = (IntPtr)value;
+
+#if NETSTANDARD
+ options[MarshalExtensions.PtrToStringUTF8(namePtr)] = MarshalExtensions.PtrToStringUTF8(valuePtr);
+#else
+ options[Marshal.PtrToStringUTF8(namePtr)] = Marshal.PtrToStringUTF8(valuePtr);
+#endif
+
+ return AdbcStatusCode.Success;
+ }
+
+ public void Dispose()
+ {
+ connection?.Dispose();
+ connection = null;
+ }
+
+ public unsafe AdbcStatusCode GetObjects(ref NativeAdbcConnection nativeConnection, int depth, byte* catalog, byte* db_schema, byte* table_name, byte** table_type, byte* column_name, CArrowArrayStream* cstream, ref NativeAdbcError error)
+ {
+ if (nativeConnection.private_data == null)
+ {
+ return AdbcStatusCode.UnknownError;
+ }
+
+ string catalogPattern = string.Empty;
+ string dbSchemaPattern = string.Empty;
+ string tableNamePattern = string.Empty;
+ string columnNamePattern = string.Empty;
+
+#if NETSTANDARD
+ catalogPattern = MarshalExtensions.PtrToStringUTF8((IntPtr)catalog);
+ dbSchemaPattern = MarshalExtensions.PtrToStringUTF8((IntPtr)db_schema);
+ tableNamePattern = MarshalExtensions.PtrToStringUTF8((IntPtr)table_name);
+ columnNamePattern = MarshalExtensions.PtrToStringUTF8((IntPtr)column_name);
+#else
+ catalogPattern = Marshal.PtrToStringUTF8((IntPtr)catalog);
+ dbSchemaPattern = Marshal.PtrToStringUTF8((IntPtr)db_schema);
+ tableNamePattern = Marshal.PtrToStringUTF8((IntPtr)table_name);
+ columnNamePattern = Marshal.PtrToStringUTF8((IntPtr)column_name);
+#endif
+
+ GCHandle gch = GCHandle.FromIntPtr((IntPtr)table_type);
+ List<string> tableTypes = (List<string>)gch.Target;
+
+ AdbcConnection.GetObjectsDepth goDepth = (AdbcConnection.GetObjectsDepth)depth;
+
+ IArrowArrayStream stream = connection.GetObjects(goDepth, catalogPattern, dbSchemaPattern, tableNamePattern, tableTypes, columnNamePattern);
+
+ CArrowArrayStreamExporter.ExportArrayStream(stream, cstream);
+
+ return AdbcStatusCode.Success;
+ }
+
+ public unsafe AdbcStatusCode GetTableSchema(ref NativeAdbcConnection nativeConnection, byte* catalog, byte* db_schema, byte* table_name, CArrowSchema* cschema, ref NativeAdbcError error)
+ {
+ if (nativeConnection.private_data == null)
+ {
+ return AdbcStatusCode.UnknownError;
+ }
+
+ string sCatalog = string.Empty;
+ string sDbSchema = string.Empty;
+ string sTableName = string.Empty;
+
+#if NETSTANDARD
+ sCatalog = MarshalExtensions.PtrToStringUTF8((IntPtr)catalog);
+ sDbSchema = MarshalExtensions.PtrToStringUTF8((IntPtr)db_schema);
+ sTableName = MarshalExtensions.PtrToStringUTF8((IntPtr)table_name);
+#else
+ sCatalog = Marshal.PtrToStringUTF8((IntPtr)catalog);
+ sDbSchema = Marshal.PtrToStringUTF8((IntPtr)db_schema);
+ sTableName = Marshal.PtrToStringUTF8((IntPtr)table_name);
+#endif
+
+ Schema schema = connection.GetTableSchema(sCatalog, sDbSchema, sTableName);
+
+ CArrowSchemaExporter.ExportSchema(schema, cschema);
+
+ return AdbcStatusCode.Success;
+ }
+
+ public unsafe AdbcStatusCode GetTableTypes(ref NativeAdbcConnection nativeConnection, CArrowArrayStream* cArrayStream, ref NativeAdbcError error)
+ {
+ if (nativeConnection.private_data == null)
+ {
+ return AdbcStatusCode.UnknownError;
+ }
+
+ CArrowArrayStreamExporter.ExportArrayStream(connection.GetTableTypes(), cArrayStream);
+
+ return AdbcStatusCode.Success;
+ }
+
+ public unsafe AdbcStatusCode ReadPartition(ref NativeAdbcConnection nativeConnection, byte* serializedPartition, int serialized_length, CArrowArrayStream* stream, ref NativeAdbcError error)
+ {
+ if (nativeConnection.private_data == null)
+ {
+ return AdbcStatusCode.UnknownError;
+ }
+
+ GCHandle gch = GCHandle.FromIntPtr((IntPtr)serializedPartition);
+ PartitionDescriptor descriptor = (PartitionDescriptor)gch.Target;
+
+ CArrowArrayStreamExporter.ExportArrayStream(connection.ReadPartition(descriptor), stream);
+
+ return AdbcStatusCode.Success;
+ }
+
+ public unsafe AdbcStatusCode GetInfo(ref NativeAdbcConnection nativeConnection, byte* info_codes, int info_codes_length, CArrowArrayStream* stream, ref NativeAdbcError error)
+ {
+ if (nativeConnection.private_data == null)
+ {
+ return AdbcStatusCode.UnknownError;
+ }
+
+ GCHandle gch = GCHandle.FromIntPtr((IntPtr)info_codes);
+ List<int> codes = (List<int>)gch.Target;
+
+ CArrowArrayStreamExporter.ExportArrayStream(connection.GetInfo(codes), stream);
+
+ return AdbcStatusCode.Success;
+ }
+
+ public unsafe AdbcStatusCode InitConnection(ref NativeAdbcDatabase nativeDatabase, ref NativeAdbcError error)
+ {
+ if (nativeDatabase.private_data == null)
+ {
+ return AdbcStatusCode.UnknownError;
+ }
+
+ GCHandle gch = GCHandle.FromIntPtr((IntPtr)nativeDatabase.private_data);
+ DatabaseStub stub = (DatabaseStub)gch.Target;
+ return stub.OpenConnection(options, ref error, out connection);
+ }
+
+ public unsafe AdbcStatusCode NewStatement(ref NativeAdbcStatement nativeStatement, ref NativeAdbcError error)
+ {
+ if (connection == null)
+ {
+ return AdbcStatusCode.UnknownError;
+ }
+ if (nativeStatement.private_data == null)
+ {
+ return AdbcStatusCode.UnknownError;
+ }
+
+ AdbcStatement statement = connection.CreateStatement();
+ GCHandle handle = GCHandle.Alloc(statement);
+ nativeStatement.private_data = (void*)GCHandle.ToIntPtr(handle);
+
+ return AdbcStatusCode.Success;
+ }
+ }
+}
diff --git a/csharp/src/Apache.Arrow.Adbc/Interop/LoadDriver.cs b/csharp/src/Apache.Arrow.Adbc/Interop/LoadDriver.cs
new file mode 100644
index 00000000..ad51cfa5
--- /dev/null
+++ b/csharp/src/Apache.Arrow.Adbc/Interop/LoadDriver.cs
@@ -0,0 +1,492 @@
+/*
+ * 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.Buffers;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Xml.Linq;
+using Apache.Arrow.Adbc;
+using Apache.Arrow.C;
+using Apache.Arrow.Ipc;
+using Apache.Arrow.Types;
+using Microsoft.Win32.SafeHandles;
+
+#if NETSTANDARD
+using Apache.Arrow.Adbc.Extensions;
+#endif
+
+namespace Apache.Arrow.Adbc.Interop
+{
+ public delegate byte AdbcDriverInit(int version, ref NativeAdbcDriver driver, ref NativeAdbcError error);
+
+ /// <summary>
+ /// Class for working with loading drivers from files
+ /// </summary>
+ public static class LoadDriver
+ {
+ private const string driverInit = "AdbcDriverInit";
+
+ class NativeDriver
+ {
+ public SafeHandle driverHandle;
+ public NativeAdbcDriver driver;
+ }
+
+ /// <summary>
+ /// Class used for Mac interoperability
+ /// </summary>
+ static class MacInterop
+ {
+ const string libdl = "libdl.dylib";
+
+ [DllImport(libdl)]
+ static extern SafeLibraryHandle dlopen(string fileName, int flags);
+
+ [DllImport(libdl)]
+ static extern IntPtr dlsym(SafeHandle libraryHandle, string symbol);
+
+ [DllImport(libdl)]
+ static extern int dlclose(IntPtr handle);
+
+ sealed class SafeLibraryHandle : SafeHandleZeroOrMinusOneIsInvalid
+ {
+ SafeLibraryHandle() : base(true) { }
+
+ protected override bool ReleaseHandle()
+ {
+ return dlclose(handle) == 0;
+ }
+ }
+
+ public static NativeDriver GetDriver(string file)
+ {
+ SafeHandle library = dlopen(file, 2); // TODO: find a symbol for 2
+ IntPtr symbol = dlsym(library, "AdbcDriverInit");
+ AdbcDriverInit init = Marshal.GetDelegateForFunctionPointer<AdbcDriverInit>(symbol);
+ NativeAdbcDriver driver = new NativeAdbcDriver();
+ NativeAdbcError error = new NativeAdbcError();
+ byte result = init(1000000, ref driver, ref error);
+ return new NativeDriver { driverHandle = library, driver = driver };
+ }
+ }
+
+ /// <summary>
+ /// Class used for Windows interoperability
+ /// </summary>
+ static class WindowsInterop
+ {
+ const string kernel32 = "kernel32.dll";
+
+ [DllImport(kernel32)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ static extern bool FreeLibrary(IntPtr libraryHandle);
+
+ [DllImport(kernel32, CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true)]
+ static extern IntPtr GetProcAddress(SafeHandle libraryHandle, string functionName);
+
+ [DllImport(kernel32, CharSet = CharSet.Unicode, SetLastError = true)]
+ static extern SafeLibraryHandle LoadLibraryEx(string fileName, IntPtr hFile, uint flags);
+
+ sealed class SafeLibraryHandle : SafeHandleZeroOrMinusOneIsInvalid
+ {
+ SafeLibraryHandle() : base(true) { }
+
+ protected override bool ReleaseHandle()
+ {
+ return FreeLibrary(handle);
+ }
+ }
+
+ public static NativeDriver GetDriver(string file)
+ {
+ SafeHandle library = LoadLibraryEx(file, IntPtr.Zero, 0x1100);
+ IntPtr symbol = GetProcAddress(library, "AdbcDriverInit");
+ AdbcDriverInit init = Marshal.GetDelegateForFunctionPointer<AdbcDriverInit>(symbol);
+ NativeAdbcDriver driver = new NativeAdbcDriver();
+ NativeAdbcError error = new NativeAdbcError();
+ byte result = init(1000000, ref driver, ref error);
+ return new NativeDriver { /* driverHandle = library, */ driver = driver };
+ }
+ }
+
+ /// <summary>
+ /// Loads an <see cref="AdbcDriver"/> from the file system.
+ /// </summary>
+ /// <param name="file">
+ /// The path to the file.
+ /// </param>
+ public static AdbcDriver Load(string file)
+ {
+ if (file[0] == '/')
+ {
+ return new AdbcDriverNative(MacInterop.GetDriver(file).driver);
+ }
+ else
+ {
+ return new AdbcDriverNative(WindowsInterop.GetDriver(file).driver);
+ }
+ }
+
+ /// <summary>
+ /// Native implementation of <see cref="AdbcDriver"/>
+ /// </summary>
+ sealed class AdbcDriverNative : AdbcDriver
+ {
+ private NativeAdbcDriver _nativeDriver;
+
+ public AdbcDriverNative(NativeAdbcDriver nativeDriver)
+ {
+ _nativeDriver = nativeDriver;
+ }
+
+ /// <summary>
+ /// Opens a database
+ /// </summary>
+ /// <param name="parameters">
+ /// Parameters to use when calling DatabaseNew.
+ /// </param>
+ public unsafe override AdbcDatabase Open(IReadOnlyDictionary<string, string> parameters)
+ {
+ NativeAdbcDatabase nativeDatabase = new NativeAdbcDatabase();
+
+ using (CallHelper caller = new CallHelper())
+ {
+ caller.Call(_nativeDriver.DatabaseNew, ref nativeDatabase);
+
+ if (parameters != null)
+ {
+ foreach (KeyValuePair<string, string> pair in parameters)
+ {
+ caller.Call(_nativeDriver.DatabaseSetOption, ref nativeDatabase, pair.Key, pair.Value);
+ }
+ }
+
+ caller.Call(_nativeDriver.DatabaseInit, ref nativeDatabase);
+ }
+
+ return new AdbcDatabaseNative(_nativeDriver, nativeDatabase);
+ }
+
+ public unsafe override void Dispose()
+ {
+ if (_nativeDriver.release != null)
+ {
+ using (CallHelper caller = new CallHelper())
+ {
+ try
+ {
+ caller.Call(_nativeDriver.release, ref _nativeDriver);
+ }
+ finally
+ {
+ _nativeDriver.release = null;
+ }
+ }
+ base.Dispose();
+ }
+ }
+ }
+
+ /// <summary>
+ /// A native implementation of <see cref="AdbcDatabase"/>
+ /// </summary>
+ internal sealed class AdbcDatabaseNative : AdbcDatabase
+ {
+ private NativeAdbcDriver _nativeDriver;
+ private NativeAdbcDatabase _nativeDatabase;
+
+ public AdbcDatabaseNative(NativeAdbcDriver nativeDriver, NativeAdbcDatabase nativeDatabase)
+ {
+ _nativeDriver = nativeDriver;
+ _nativeDatabase = nativeDatabase;
+ }
+
+ public unsafe override AdbcConnection Connect(IReadOnlyDictionary<string, string> options)
+ {
+ NativeAdbcConnection nativeConnection = new NativeAdbcConnection();
+
+ using (CallHelper caller = new CallHelper())
+ {
+ if (options != null)
+ {
+ foreach (KeyValuePair<string, string> pair in options)
+ {
+ caller.Call(_nativeDriver.ConnectionSetOption, ref nativeConnection, pair.Key, pair.Value);
+ }
+ }
+
+ caller.Call(_nativeDriver.ConnectionInit, ref nativeConnection, ref _nativeDatabase);
+ }
+
+ return new AdbcConnectionNative(_nativeDriver, nativeConnection);
+ }
+
+ public override void Dispose()
+ {
+ base.Dispose();
+ }
+ }
+
+ /// <summary>
+ /// A native implementation of <see cref="AdbcConnection"/>
+ /// </summary>
+ internal sealed class AdbcConnectionNative : AdbcConnection
+ {
+ private NativeAdbcDriver _nativeDriver;
+ private NativeAdbcConnection _nativeConnection;
+
+ public AdbcConnectionNative(NativeAdbcDriver nativeDriver, NativeAdbcConnection nativeConnection)
+ {
+ _nativeDriver = nativeDriver;
+ _nativeConnection = nativeConnection;
+ }
+
+ public unsafe override AdbcStatement CreateStatement()
+ {
+ NativeAdbcStatement nativeStatement = new NativeAdbcStatement();
+
+ using (CallHelper caller = new CallHelper())
+ {
+ caller.Call(_nativeDriver.StatementNew, ref _nativeConnection, ref nativeStatement);
+ }
+
+ return new AdbcStatementNative(_nativeDriver, nativeStatement);
+ }
+
+ }
+
+ /// <summary>
+ /// A native implementation of <see cref="AdbcStatement"/>
+ /// </summary>
+ sealed class AdbcStatementNative : AdbcStatement
+ {
+ private NativeAdbcDriver _nativeDriver;
+ private NativeAdbcStatement _nativeStatement;
+
+ public AdbcStatementNative(NativeAdbcDriver nativeDriver, NativeAdbcStatement nativeStatement)
+ {
+ _nativeDriver = nativeDriver;
+ _nativeStatement = nativeStatement;
+ }
+
+ public unsafe override QueryResult ExecuteQuery()
+ {
+ CArrowArrayStream* nativeArrayStream = CArrowArrayStream.Create();
+
+ using (CallHelper caller = new CallHelper())
+ {
+ caller.Call(_nativeDriver.StatementSetSqlQuery, ref _nativeStatement, SqlQuery);
+
+ long rows = 0;
+
+ caller.Call(_nativeDriver.StatementExecuteQuery, ref _nativeStatement, nativeArrayStream, ref rows);
+
+ return new QueryResult(rows, CArrowArrayStreamImporter.ImportArrayStream(nativeArrayStream));
+ }
+ }
+
+ public override unsafe UpdateResult ExecuteUpdate()
+ {
+ throw AdbcException.NotImplemented("Driver does not support ExecuteUpdate");
+ }
+
+ public override object GetValue(IArrowArray arrowArray, Field field, int index)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ /// <summary>
+ /// Assists with UTF8/string marshalling
+ /// </summary>
+ struct Utf8Helper : IDisposable
+ {
+ private IntPtr _s;
+
+ public Utf8Helper(string s)
+ {
+#if NETSTANDARD
+ _s = MarshalExtensions.StringToCoTaskMemUTF8(s);
+#else
+ _s = Marshal.StringToCoTaskMemUTF8(s);
+#endif
+ }
+
+ public static implicit operator IntPtr(Utf8Helper s) { return s._s; }
+ public void Dispose() { Marshal.FreeCoTaskMem(_s); }
+ }
+
+ /// <summary>
+ /// Assists with delegate calls and handling error codes
+ /// </summary>
+ struct CallHelper : IDisposable
+ {
+ private NativeAdbcError _error;
+
+ public unsafe void Call(delegate* unmanaged[Stdcall]<NativeAdbcDriver*, NativeAdbcError*, AdbcStatusCode> fn, ref NativeAdbcDriver nativeDriver)
+ {
+ fixed (NativeAdbcDriver* driver = &nativeDriver)
+ fixed (NativeAdbcError* e = &_error)
+ {
+ TranslateCode(fn(driver, e));
+ }
+ }
+
+ public unsafe void Call(delegate* unmanaged[Stdcall]<NativeAdbcDatabase*, NativeAdbcError*, AdbcStatusCode> fn, ref NativeAdbcDatabase nativeDatabase)
+ {
+ fixed (NativeAdbcDatabase* db = &nativeDatabase)
+ fixed (NativeAdbcError* e = &_error)
+ {
+ TranslateCode(fn(db, e));
+ }
+ }
+
+ public unsafe void Call(delegate* unmanaged[Stdcall]<NativeAdbcDatabase*, byte*, byte*, NativeAdbcError*, AdbcStatusCode> fn, ref NativeAdbcDatabase nativeDatabase, string key, string value)
+ {
+ fixed (NativeAdbcDatabase* db = &nativeDatabase)
+ fixed (NativeAdbcError* e = &_error)
+ {
+ using (Utf8Helper utf8Key = new Utf8Helper(key))
+ using (Utf8Helper utf8Value = new Utf8Helper(value))
+ {
+ unsafe
+ {
+ IntPtr keyPtr = utf8Key;
+ IntPtr valuePtr = utf8Value;
+
+ TranslateCode(fn(db, (byte*)keyPtr, (byte*)valuePtr, e));
+ }
+ }
+ }
+ }
+
+ public unsafe void Call(delegate* unmanaged[Stdcall]<NativeAdbcConnection*, NativeAdbcError*, AdbcStatusCode> fn, ref NativeAdbcConnection nativeConnection)
+ {
+ fixed (NativeAdbcConnection* cn = &nativeConnection)
+ fixed (NativeAdbcError* e = &_error)
+ {
+ TranslateCode(fn(cn, e));
+ }
+ }
+
+ public unsafe void Call(delegate* unmanaged[Stdcall]<NativeAdbcConnection*, byte*, byte*, NativeAdbcError*, AdbcStatusCode> fn, ref NativeAdbcConnection nativeConnection, string key, string value)
+ {
+ fixed (NativeAdbcConnection* cn = &nativeConnection)
+ fixed (NativeAdbcError* e = &_error)
+ {
+ using (Utf8Helper utf8Key = new Utf8Helper(key))
+ using (Utf8Helper utf8Value = new Utf8Helper(value))
+ {
+ unsafe
+ {
+ IntPtr keyPtr = utf8Key;
+ IntPtr valuePtr = utf8Value;
+
+ TranslateCode(fn(cn, (byte*)keyPtr, (byte*)valuePtr, e));
+ }
+ }
+ }
+ }
+
+ public unsafe void Call(delegate* unmanaged[Stdcall]<NativeAdbcConnection*, NativeAdbcDatabase*, NativeAdbcError*, AdbcStatusCode> fn, ref NativeAdbcConnection nativeConnection, ref NativeAdbcDatabase database)
+ {
+ fixed (NativeAdbcConnection* cn = &nativeConnection)
+ fixed (NativeAdbcDatabase* db = &database)
+ fixed (NativeAdbcError* e = &_error)
+ {
+ TranslateCode(fn(cn, db, e));
+ }
+ }
+
+ public unsafe void Call(delegate* unmanaged[Stdcall]<NativeAdbcConnection*, NativeAdbcStatement*, NativeAdbcError*, AdbcStatusCode> fn, ref NativeAdbcConnection nativeConnection, ref NativeAdbcStatement nativeStatement)
+ {
+ fixed (NativeAdbcConnection* cn = &nativeConnection)
+ fixed (NativeAdbcStatement* stmt = &nativeStatement)
+ fixed (NativeAdbcError* e = &_error)
+ {
+ TranslateCode(fn(cn, stmt, e));
+ }
+ }
+
+ public unsafe void Call(delegate* unmanaged[Stdcall]<NativeAdbcStatement*, NativeAdbcError*, AdbcStatusCode> fn, ref NativeAdbcStatement nativeStatement)
+ {
+ fixed (NativeAdbcStatement* stmt = &nativeStatement)
+ fixed (NativeAdbcError* e = &_error)
+ {
+ TranslateCode(fn(stmt, e));
+ }
+ }
+
+ public unsafe void Call(delegate* unmanaged[Stdcall]<NativeAdbcStatement*, byte*, NativeAdbcError*, AdbcStatusCode> fn, ref NativeAdbcStatement nativeStatement, string sqlQuery)
+ {
+ fixed (NativeAdbcStatement* stmt = &nativeStatement)
+ fixed (NativeAdbcError* e = &_error)
+ {
+ using (Utf8Helper query = new Utf8Helper(sqlQuery))
+ {
+ IntPtr bQuery = (IntPtr)(query);
+
+ TranslateCode(fn(stmt, (byte*)bQuery, e));
+ }
+ }
+ }
+
+ public unsafe void Call(delegate* unmanaged[Stdcall]<NativeAdbcStatement*, CArrowArrayStream*, long*, NativeAdbcError*, AdbcStatusCode> fn, ref NativeAdbcStatement nativeStatement, CArrowArrayStream* arrowStream, ref long nRows)
+ {
+ fixed (NativeAdbcStatement* stmt = &nativeStatement)
+ fixed (long* rows = &nRows)
+ fixed (NativeAdbcError* e = &_error)
+ {
+ TranslateCode(fn(stmt, arrowStream, rows, e));
+ }
+ }
+
+ public unsafe void Dispose()
+ {
+ if (_error.release != null)
+ {
+ fixed (NativeAdbcError* err = &_error)
+ {
+ _error.release(err);
+ _error.release = null;
+ }
+ }
+ }
+
+ internal unsafe void TranslateCode(AdbcStatusCode statusCode)
+ {
+ if (statusCode != AdbcStatusCode.Success)
+ {
+ string message = "Undefined error";
+ if ((IntPtr)_error.message != IntPtr.Zero)
+ {
+#if NETSTANDARD
+ message = MarshalExtensions.PtrToStringUTF8((IntPtr)_error.message);
+#else
+ message = Marshal.PtrToStringUTF8((IntPtr)_error.message);
+#endif
+ }
+ Dispose();
+ throw new AdbcException(message);
+ }
+ }
+ }
+ }
+}
diff --git a/csharp/src/Apache.Arrow.Adbc/Interop/NativeDelegate.cs b/csharp/src/Apache.Arrow.Adbc/Interop/NativeDelegate.cs
new file mode 100644
index 00000000..c7a52055
--- /dev/null
+++ b/csharp/src/Apache.Arrow.Adbc/Interop/NativeDelegate.cs
@@ -0,0 +1,41 @@
+/*
+ * 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;
+
+namespace Apache.Arrow.Adbc.Interop
+{
+ internal readonly struct NativeDelegate<T>
+ {
+ private readonly T _managedDelegate; // For lifetime management
+ private readonly IntPtr _nativePointer;
+
+ public NativeDelegate(T managedDelegate)
+ {
+ _managedDelegate = managedDelegate;
+ _nativePointer = Marshal.GetFunctionPointerForDelegate<T>(managedDelegate);
+ }
+
+ public static implicit operator IntPtr(NativeDelegate<T> thunk)
+ {
+ return thunk._nativePointer;
+ }
+
+ public IntPtr Pointer { get { return _nativePointer; } }
+ }
+}
diff --git a/csharp/src/Apache.Arrow.Adbc/IsolationLevel.cs b/csharp/src/Apache.Arrow.Adbc/IsolationLevel.cs
new file mode 100644
index 00000000..9a28e83f
--- /dev/null
+++ b/csharp/src/Apache.Arrow.Adbc/IsolationLevel.cs
@@ -0,0 +1,77 @@
+/*
+ * 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.
+ */
+
+namespace Apache.Arrow.Adbc
+{
+ /// <summary>
+ /// The isolation level to use for transactions when autocommit is
+ /// disabled.
+ /// </summary>
+ public enum IsolationLevel
+ {
+ Default,
+
+ /// <summary>
+ /// The lowest isolation level. Dirty reads are allowed, so one
+ /// transaction may see not-yet-committed changes made by others.
+ /// </summary>
+ ReadUncommitted,
+
+ /// <summary>
+ /// Lock-based concurrency control keeps write locks until the
+ /// end of the transaction, but read locks are released as soon
+ /// as a SELECT is performed. Non-repeatable reads can occur
+ /// in this isolation level.
+ /// </summary>
+ ReadCommitted,
+
+ /// <summary>
+ /// Lock-based concurrency control keeps read AND write locks
+ /// (acquired on selection data) until the end of the transaction.
+ /// </summary>
+ RepeatableRead,
+
+ /// <summary>
+ /// This isolation guarantees that all reads in the transaction
+ /// will see a consistent snapshot of the database and the
+ /// transaction should only successfully commit if no updates
+ /// conflict with concurrent updates made since that snapshot.
+ /// </summary>
+ Snapshot,
+
+ /// <summary>
+ /// Serializability requires read and write locks to be released only
+ /// at the end of the transaction. This includes acquiring range-locks
+ /// when a select query uses a ranged WHERE clause to avoid phantom reads.
+ /// </summary>
+ Serializable,
+
+ /// <summary>
+ /// The central distinction between serializability and
+ /// linearizability is that serializability is a global property;
+ /// a property of an entire history of operations and transactions.
+ /// Linearizability is a local property; a property of a single
+ /// operation/transaction.
+ ///
+ /// Linearizability can be viewed as a special case of strict
+ /// serializability where transactions are restricted to consist
+ /// of a single operation applied to a single object.
+ /// </summary>
+ Linearizable,
+ }
+
+}
diff --git a/csharp/src/Apache.Arrow.Adbc/PartitionDescriptor.cs b/csharp/src/Apache.Arrow.Adbc/PartitionDescriptor.cs
new file mode 100644
index 00000000..ea4d917b
--- /dev/null
+++ b/csharp/src/Apache.Arrow.Adbc/PartitionDescriptor.cs
@@ -0,0 +1,62 @@
+/*
+ * 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;
+
+namespace Apache.Arrow.Adbc
+{
+ /// <summary>
+ /// A descriptor for a part of a potentially distributed or
+ /// partitioned result set.
+ /// </summary>
+ public struct PartitionDescriptor : IEquatable<PartitionDescriptor>
+ {
+ readonly byte[] _descriptor;
+
+ public PartitionDescriptor(byte[] descriptor)
+ {
+ _descriptor = descriptor;
+ }
+
+ public override bool Equals(object obj)
+ {
+ PartitionDescriptor? other = obj as PartitionDescriptor?;
+ return other != null && Equals(other.Value);
+ }
+
+ public bool Equals(PartitionDescriptor other)
+ {
+ if (_descriptor.Length != other._descriptor.Length)
+ {
+ return false;
+ }
+ for (int i = 0; i < _descriptor.Length; i++)
+ {
+ if (_descriptor[i] != other._descriptor[i])
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public override int GetHashCode()
+ {
+ return _descriptor.GetHashCode();
+ }
+ }
+}
diff --git a/csharp/src/Apache.Arrow.Adbc/Results.cs b/csharp/src/Apache.Arrow.Adbc/Results.cs
new file mode 100644
index 00000000..82c26b01
--- /dev/null
+++ b/csharp/src/Apache.Arrow.Adbc/Results.cs
@@ -0,0 +1,103 @@
+/*
+ * 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.Collections.Generic;
+using Apache.Arrow.Ipc;
+
+namespace Apache.Arrow.Adbc
+{
+ /// <summary>
+ /// Represents a query result.
+ /// </summary>
+ public sealed class QueryResult
+ {
+ /// <summary>
+ /// Initializes an AdbcQueryResult
+ /// </summary>
+ /// <param name="rowCount">
+ /// The number of records in the result
+ /// </param>
+ /// <param name="stream">
+ /// The <see cref="IArrowArrayStream"/> for reading
+ /// </param>
+ public QueryResult(long rowCount, IArrowArrayStream stream)
+ {
+ RowCount = rowCount;
+ Stream = stream;
+ }
+
+ /// <summary>
+ /// The number of records in the result.
+ /// </summary>
+ public long RowCount { get; set; }
+
+ /// <summary>
+ /// The <see cref="IArrowArrayStream"/> for reading.
+ /// </summary>
+ public IArrowArrayStream Stream { get; set; }
+ }
+
+ /// <summary>
+ /// The result of executing a query without a result set.
+ /// </summary>
+ public sealed class UpdateResult
+ {
+ private readonly long _affectedRows = -1;
+
+ public UpdateResult(long affectedRows)
+ {
+ _affectedRows = affectedRows;
+ }
+
+ /// <summary>
+ /// The number of records in the result or -1 if not known.
+ /// </summary>
+ public long AffectedRows { get => _affectedRows; }
+ }
+
+ /// <summary>
+ /// The partitions of a result set.
+ /// </summary>
+ public sealed class PartitionedResult
+ {
+ private readonly Schema _schema;
+ private readonly long _affectedRows = -1;
+ private readonly List<PartitionDescriptor> _partitionDescriptors;
+
+ public PartitionedResult(Schema schema, long affectedRows, List<PartitionDescriptor> partitionDescriptors)
+ {
+ _schema = schema;
+ _affectedRows = affectedRows;
+ _partitionDescriptors = partitionDescriptors;
+ }
+
+ /// <summary>
+ /// Get the schema of the eventual result set.
+ /// </summary>
+ public Schema Schema { get => _schema; }
+
+ /// <summary>
+ /// Get the number of affected rows, or -1 if not known.
+ /// </summary>
+ public long AffectedRows { get => _affectedRows; }
+
+ /// <summary>
+ /// Get partitions.
+ /// </summary>
+ public List<PartitionDescriptor> PartitionDescriptors { get => _partitionDescriptors; }
+ }
+}
diff --git a/csharp/src/Apache.Arrow.Adbc/StandardSchemas.cs b/csharp/src/Apache.Arrow.Adbc/StandardSchemas.cs
new file mode 100644
index 00000000..10fdd04b
--- /dev/null
+++ b/csharp/src/Apache.Arrow.Adbc/StandardSchemas.cs
@@ -0,0 +1,182 @@
+/*
+ * 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.Linq;
+using Apache.Arrow.Types;
+
+namespace Apache.Arrow.Adbc
+{
+ /// <summary>
+ /// The standard schemas
+ /// </summary>
+ public static class StandardSchemas
+ {
+ /// <summary>
+ /// The schema of the result set of
+ /// <see cref="AdbcConnection.GetInfo(int[])"/>}.
+ /// </summary>
+ public static readonly Schema GetInfoSchema =
+ new Schema(
+ new List<Field>()
+ {
+ new Field("info_name", UInt32Type.Default, false),
+ new Field(
+ "info_value",
+ new UnionType(
+ new List<Field>()
+ {
+ new Field("string_value", StringType.Default, true),
+ new Field("bool_value", BooleanType.Default, true),
+ new Field("int64_value", Int64Type.Default, true),
+ new Field("int32_bitmask", Int32Type.Default, true),
+ new Field(
+ "string_list",
+ new ListType(
+ new Field("item", StringType.Default, true)
+ ),
+ false
+ ),
+ new Field(
+ "int32_to_int32_list_map",
+ new ListType(
+ new Field("entries", new StructType(
+ new List<Field>()
+ {
+ new Field("key", Int32Type.Default, false),
+ new Field("value", Int32Type.Default, true),
+ }
+ ), false)
+ ),
+ true
+ )
+ },
+ // TBD if this line is the best approach but its a good one-liner
+ new int[] {0, 1, 2, 3, 4, 5}.SelectMany(BitConverter.GetBytes).ToArray(),
+ UnionMode.Dense),
+ true)
+ },
+ metadata: null
+ );
+
+ public static readonly Schema TableTypesSchema = new Schema(
+ new List<Field>()
+ {
+ new Field("table_type", StringType.Default, false)
+ },
+ metadata: null
+ );
+
+ public static readonly List<Field> UsageSchema = new List<Field>()
+ {
+ new Field("fk_catalog", StringType.Default, true),
+ new Field("fk_db_schema", StringType.Default, true),
+ new Field("fk_table", StringType.Default, false),
+ new Field("fk_column_name", StringType.Default, false)
+ };
+
+ public static readonly List<Field> ConstraintSchema = new List<Field>()
+ {
+ new Field("constraint_name", StringType.Default, false),
+ new Field("constraint_type", StringType.Default, false),
+ new Field("constraint_column_usage",
+ new ListType(
+ new Field("item", StringType.Default, true)
+ ),
+ false
+ ),
+ new Field("constraint_column_usage",
+ new ListType(
+ new Field("item", new StructType(UsageSchema), true)
+ ),
+ false
+ ),
+ };
+
+ public static readonly List<Field> ColumnSchema =
+ new List<Field>()
+ {
+ new Field("column_name", StringType.Default, false),
+ new Field("ordinal_position", Int32Type.Default, true),
+ new Field("remarks", StringType.Default, true),
+ new Field("xdbc_data_type", Int16Type.Default, true),
+ new Field("xdbc_type_name", StringType.Default, true),
+ new Field("xdbc_column_size", Int32Type.Default, true),
+ new Field("xdbc_decimal_digits", Int16Type.Default, true),
+ new Field("xdbc_num_prec_radix", Int16Type.Default, true),
+ new Field("xdbc_nullable", Int16Type.Default, true),
+ new Field("xdbc_column_def", StringType.Default, true),
+ new Field("xdbc_sql_data_type", Int16Type.Default, true),
+ new Field("xdbc_datetime_sub", Int16Type.Default, true),
+ new Field("xdbc_char_octet_length", Int32Type.Default, true),
+ new Field("xdbc_is_nullable", StringType.Default, true),
+ new Field("xdbc_scope_catalog", StringType.Default, true),
+ new Field("xdbc_scope_schema", StringType.Default, true),
+ new Field("xdbc_scope_table", StringType.Default, true),
+ new Field("xdbc_is_autoincrement", StringType.Default, true),
+ new Field("xdbc_is_generatedcolumn", StringType.Default, true)
+ };
+
+ public static readonly List<Field> TableSchema = new List<Field>() {
+ new Field("table_name", StringType.Default, false, null),
+ new Field("table_type", StringType.Default, false, null),
+ new Field(
+ "table_columns",
+ new ListType(
+ new Field("item", new StructType(ColumnSchema), true)
+ ),
+ false
+ ),
+ new Field(
+ "table_constraints",
+ new ListType(
+ new Field("item", new StructType(ConstraintSchema), true)
+ ),
+ false
+ )
+ };
+
+ public static readonly List<Field> DbSchemaSchema = new List<Field>()
+ {
+ new Field("db_schema_name", StringType.Default, false, null),
+ new Field(
+ "db_schema_tables",
+ new ListType(
+ new Field("item", new StructType(TableSchema), true)
+ ),
+ false
+ )
+ };
+
+ public static readonly Schema GetObjectsSchema = new Schema(
+ new List<Field>()
+ {
+ new Field("catalog_name", StringType.Default, false),
+ new Field(
+ "catalog_db_schemas",
+ new ListType(
+ new Field("item", new StructType(DbSchemaSchema), true)
+ ),
+ false
+ )
+ },
+ metadata: null
+ );
+ }
+
+}
diff --git a/csharp/test/Apache.Arrow.Adbc.FlightSql.Tests/Apache.Arrow.Adbc.FlightSql.Tests.csproj b/csharp/test/Apache.Arrow.Adbc.FlightSql.Tests/Apache.Arrow.Adbc.FlightSql.Tests.csproj
new file mode 100644
index 00000000..1cafe9bf
--- /dev/null
+++ b/csharp/test/Apache.Arrow.Adbc.FlightSql.Tests/Apache.Arrow.Adbc.FlightSql.Tests.csproj
@@ -0,0 +1,30 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFrameworks>net472;net6.0</TargetFrameworks>
+ <ImplicitUsings>disable</ImplicitUsings>
+ <SignAssembly>False</SignAssembly>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="coverlet.collector" Version="3.1.2" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0" />
+ <PackageReference Include="Moq" Version="4.18.4" />
+ <PackageReference Include="MSTest.TestAdapter" Version="3.0.3" />
+ <PackageReference Include="MSTest.TestFramework" Version="3.0.3" />
+ <PackageReference Include="System.Text.Json" Version="7.0.2" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\..\src\Apache.Arrow.Adbc.FlightSql\Apache.Arrow.Adbc.FlightSql.csproj" />
+ <ProjectReference Include="..\..\src\Apache.Arrow.Adbc\Apache.Arrow.Adbc.csproj" />
+ <ProjectReference Include="..\Apache.Arrow.Adbc.Tests\Apache.Arrow.Adbc.Tests.csproj" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <None Update="flightsql.arrow">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </None>
+ </ItemGroup>
+
+</Project>
diff --git a/csharp/test/Apache.Arrow.Adbc.FlightSql.Tests/ConnectionTests.cs b/csharp/test/Apache.Arrow.Adbc.FlightSql.Tests/ConnectionTests.cs
new file mode 100644
index 00000000..62cc6424
--- /dev/null
+++ b/csharp/test/Apache.Arrow.Adbc.FlightSql.Tests/ConnectionTests.cs
@@ -0,0 +1,138 @@
+/*
+* 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.IO;
+using System.Linq;
+using System.Text.Json;
+using Apache.Arrow.Adbc.Tests;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+
+namespace Apache.Arrow.Adbc.FlightSql.Tests
+{
+ /// <summary>
+ /// Abstract class for the ADBC connection tests.
+ /// </summary>
+ [TestClass]
+ public class ConnectionTests
+ {
+ /// <summary>
+ /// Validates if the driver behaves as it should with missing
+ /// values and parsing mock results.
+ /// </summary>
+ [TestMethod]
+ public void CanMockDriverConnect()
+ {
+ Mock<FlightSqlStatement> mockFlightSqlStatement = GetMockSqlStatement();
+
+ FlightSqlDatabase db = new FlightSqlDatabase(new Dictionary<string, string>());
+
+ Assert.ThrowsException<ArgumentNullException>(() => db.Connect(null));
+
+ Assert.ThrowsException<ArgumentException>(() => db.Connect(new Dictionary<string, string>()));
+
+ QueryResult queryResult = mockFlightSqlStatement.Object.ExecuteQuery();
+
+ Adbc.Tests.ConnectionTests.CanDriverConnect(queryResult, 50);
+ }
+
+ /// <summary>
+ /// Validates if the driver can connect to a live server and
+ /// parse the results.
+ /// </summary>
+ [TestMethod]
+ public void CanDriverConnect()
+ {
+ FlightSqlTestConfiguration flightSqlTestConfiguration = GetFlightSqlTestConfiguration();
+
+ Dictionary<string, string> parameters = new Dictionary<string, string>
+ {
+ { FlightSqlParameters.ServerAddress, flightSqlTestConfiguration.ServerAddress },
+ { FlightSqlParameters.RoutingTag, flightSqlTestConfiguration.RoutingTag },
+ { FlightSqlParameters.RoutingQueue, flightSqlTestConfiguration.RoutingQueue },
+ { FlightSqlParameters.Authorization, flightSqlTestConfiguration.Authorization}
+ };
+
+ Dictionary<string, string> options = new Dictionary<string, string>()
+ {
+ { FlightSqlParameters.ServerAddress, flightSqlTestConfiguration.ServerAddress },
+ };
+
+ FlightSqlDriver flightSqlDriver = new FlightSqlDriver();
+ FlightSqlDatabase flightSqlDatabase = flightSqlDriver.Open(parameters) as FlightSqlDatabase;
+ FlightSqlConnection connection = flightSqlDatabase.Connect(options) as FlightSqlConnection;
+ FlightSqlStatement statement = connection.CreateStatement() as FlightSqlStatement;
+
+ statement.SqlQuery = flightSqlTestConfiguration.Query;
+ QueryResult queryResult = statement.ExecuteQuery();
+
+ Adbc.Tests.ConnectionTests.CanDriverConnect(queryResult, flightSqlTestConfiguration.ExpectedResultsCount);
+ }
+
+ /// <summary>
+ /// Validates exceptions thrown are ADBC exceptions
+ /// </summary>
+ [TestMethod]
+ public void VerifyBadQueryGeneratesError()
+ {
+ Mock<FlightSqlStatement> mockFlightSqlStatement = GetMockSqlStatement();
+
+ mockFlightSqlStatement.Setup(s => s.ExecuteQuery()).Throws(new MockAdbcException());
+
+ try
+ {
+ mockFlightSqlStatement.Object.ExecuteQuery();
+ }
+ catch (AdbcException e)
+ {
+ Adbc.Tests.ConnectionTests.VerifyBadQueryGeneratesError(e);
+ }
+ }
+
+ /// <summary>
+ /// Loads a FlightSqlStatement with mocked results.
+ /// </summary>
+ private Mock<FlightSqlStatement> GetMockSqlStatement()
+ {
+ List<RecordBatch> recordBatches = Utils.LoadTestRecordBatches();
+
+ Schema s = recordBatches.First().Schema;
+ QueryResult mockQueryResult = new QueryResult(50, new MockArrayStream(s, recordBatches));
+ FlightSqlConnection cn = new FlightSqlConnection(null);
+
+ Mock<FlightSqlStatement> mockFlightSqlStatement = new Mock<FlightSqlStatement>(cn);
+ mockFlightSqlStatement.Setup(s => s.ExecuteQuery()).Returns(mockQueryResult);
+
+ return mockFlightSqlStatement;
+ }
+
+ /// <summary>
+ /// Gets the configuration for connecting to a live Flight SQL server.
+ /// </summary>
+ private FlightSqlTestConfiguration GetFlightSqlTestConfiguration()
+ {
+ // use a JSON file vs. setting up environment variables
+ string json = File.ReadAllText("flightsqlconfig.json");
+
+ FlightSqlTestConfiguration flightSqlTestConfiguration = JsonSerializer.Deserialize<FlightSqlTestConfiguration>(json);
+
+ return flightSqlTestConfiguration;
+ }
+ }
+}
diff --git a/csharp/test/Apache.Arrow.Adbc.FlightSql.Tests/FlightSqlTestConfiguration.cs b/csharp/test/Apache.Arrow.Adbc.FlightSql.Tests/FlightSqlTestConfiguration.cs
new file mode 100644
index 00000000..ff450626
--- /dev/null
+++ b/csharp/test/Apache.Arrow.Adbc.FlightSql.Tests/FlightSqlTestConfiguration.cs
@@ -0,0 +1,42 @@
+/*
+* 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.Text.Json.Serialization;
+
+namespace Apache.Arrow.Adbc.FlightSql.Tests
+{
+ internal class FlightSqlTestConfiguration
+ {
+ [JsonPropertyName("serverAddress")]
+ public string ServerAddress { get; set; }
+
+ [JsonPropertyName("routing_tag"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
+ public string RoutingTag { get; set; }
+
+ [JsonPropertyName("routing_queue"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
+ public string RoutingQueue { get; set; }
+
+ [JsonPropertyName("authorization")]
+ public string Authorization { get; set; }
+
+ [JsonPropertyName("query")]
+ public string Query { get; set; }
+
+ [JsonPropertyName("expectedResults")]
+ public long ExpectedResultsCount { get; set; }
+ }
+}
diff --git a/csharp/test/Apache.Arrow.Adbc.FlightSql.Tests/TypeTests.cs b/csharp/test/Apache.Arrow.Adbc.FlightSql.Tests/TypeTests.cs
new file mode 100644
index 00000000..78712a9d
--- /dev/null
+++ b/csharp/test/Apache.Arrow.Adbc.FlightSql.Tests/TypeTests.cs
@@ -0,0 +1,76 @@
+/*
+* 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.Linq;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Apache.Arrow.Adbc.FlightSql.Tests
+{
+ [TestClass]
+ public class TypeTests
+ {
+ /// <summary>
+ /// Verify the types and values for the arrays
+ /// </summary>
+ [TestMethod]
+ public void VerifyTypesAndValues()
+ {
+ List<RecordBatch> recordBatches = Utils.LoadTestRecordBatches();
+
+ RecordBatch recordBatch = recordBatches[0];
+
+ Assert.AreEqual(1, recordBatches.Count);
+ Assert.AreEqual(50, recordBatch.Length);
+
+ var actualArrays = recordBatch.Arrays.ToList();
+
+ List<Type> expectedArrayTypes = new List<Type>()
+ {
+ typeof(TimestampArray),
+ typeof(Int64Array),
+ typeof(DoubleArray),
+ typeof(DoubleArray),
+ typeof(DoubleArray),
+ typeof(DoubleArray)
+ };
+
+ List<object> actualValues = new List<object>()
+ {
+ ((TimestampArray)actualArrays[0]).GetValue(0),
+ ((Int64Array)actualArrays[1]).GetValue(0),
+ ((DoubleArray)actualArrays[2]).GetValue(0),
+ ((DoubleArray)actualArrays[3]).GetValue(0),
+ ((DoubleArray)actualArrays[4]).GetValue(0),
+ ((DoubleArray)actualArrays[5]).GetValue(0),
+ };
+
+ List<object> expectedValues = new List<object>()
+ {
+ 1369682100000L,
+ 1L,
+ 1.26d,
+ 7.5d,
+ 0d,
+ 8d
+ };
+
+ Adbc.Tests.TypeTests.VerifyTypesAndValues(actualArrays, expectedArrayTypes, actualValues, expectedValues);
+ }
+ }
+}
diff --git a/csharp/test/Apache.Arrow.Adbc.FlightSql.Tests/Utils.cs b/csharp/test/Apache.Arrow.Adbc.FlightSql.Tests/Utils.cs
new file mode 100644
index 00000000..ac7c46f3
--- /dev/null
+++ b/csharp/test/Apache.Arrow.Adbc.FlightSql.Tests/Utils.cs
@@ -0,0 +1,53 @@
+/*
+* 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.Collections.Generic;
+using System.IO;
+using Apache.Arrow.Ipc;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Apache.Arrow.Adbc.FlightSql.Tests
+{
+ internal class Utils
+ {
+ /// <summary>
+ /// Loads record batches from an arrow file.
+ /// </summary>
+ public static List<RecordBatch> LoadTestRecordBatches()
+ {
+ // this file was generated from the Flight SQL data source
+ string file = "flightsql.arrow";
+
+ Assert.IsTrue(File.Exists(file), $"Cannot find {file}");
+
+ List<RecordBatch> recordBatches = new List<RecordBatch>();
+
+ using (FileStream fs = new FileStream(file, FileMode.Open))
+ using (ArrowFileReader reader = new ArrowFileReader(fs))
+ {
+ int batches = reader.RecordBatchCountAsync().Result;
+
+ for (int i = 0; i < batches; i++)
+ {
+ recordBatches.Add(reader.ReadNextRecordBatch());
+ }
+ }
+
+ return recordBatches;
+ }
+ }
+}
diff --git a/csharp/test/Apache.Arrow.Adbc.FlightSql.Tests/flightsql.arrow b/csharp/test/Apache.Arrow.Adbc.FlightSql.Tests/flightsql.arrow
new file mode 100644
index 00000000..e0f3ddb2
Binary files /dev/null and b/csharp/test/Apache.Arrow.Adbc.FlightSql.Tests/flightsql.arrow differ
diff --git a/csharp/test/Apache.Arrow.Adbc.FlightSql.Tests/flightsqlconfig.json b/csharp/test/Apache.Arrow.Adbc.FlightSql.Tests/flightsqlconfig.json
new file mode 100644
index 00000000..04db1c4c
--- /dev/null
+++ b/csharp/test/Apache.Arrow.Adbc.FlightSql.Tests/flightsqlconfig.json
@@ -0,0 +1,8 @@
+{
+ "serverAddress": "",
+ "routing_tag": "",
+ "routing_queue": "",
+ "authorization": "",
+ "query": "",
+ "expectedResults": 0
+}
diff --git a/csharp/test/Apache.Arrow.Adbc.Tests/Apache.Arrow.Adbc.Tests.csproj b/csharp/test/Apache.Arrow.Adbc.Tests/Apache.Arrow.Adbc.Tests.csproj
new file mode 100644
index 00000000..fc49de66
--- /dev/null
+++ b/csharp/test/Apache.Arrow.Adbc.Tests/Apache.Arrow.Adbc.Tests.csproj
@@ -0,0 +1,22 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFrameworks>net472;net6.0</TargetFrameworks>
+ <ImplicitUsings>disable</ImplicitUsings>
+ <IsPackable>false</IsPackable>
+ <IsTestProject>true</IsTestProject>
+ <SignAssembly>False</SignAssembly>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0" />
+ <PackageReference Include="MSTest.TestAdapter" Version="3.0.3" />
+ <PackageReference Include="MSTest.TestFramework" Version="3.0.3" />
+ <PackageReference Include="coverlet.collector" Version="3.1.2" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\..\src\Apache.Arrow.Adbc\Apache.Arrow.Adbc.csproj" />
+ </ItemGroup>
+
+</Project>
diff --git a/csharp/test/Apache.Arrow.Adbc.Tests/ConnectionTests.cs b/csharp/test/Apache.Arrow.Adbc.Tests/ConnectionTests.cs
new file mode 100644
index 00000000..c39cd56b
--- /dev/null
+++ b/csharp/test/Apache.Arrow.Adbc.Tests/ConnectionTests.cs
@@ -0,0 +1,71 @@
+/*
+* 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 Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Apache.Arrow.Adbc.Tests
+{
+ /// <summary>
+ /// Performs tests related to connecting with ADBC drivers.
+ /// </summary>
+ public class ConnectionTests
+ {
+ /// <summary>
+ /// Validates that a <see cref="QueryResult"/> contains a number
+ /// of records.
+ /// </summary>
+ /// <param name="queryResult">
+ /// The query result.
+ /// </param>
+ /// <param name="expectedNumberOfResults">
+ /// The number of records.
+ /// </param>
+ public static void CanDriverConnect(QueryResult queryResult, long expectedNumberOfResults)
+ {
+ long count = 0;
+
+ while (true)
+ {
+ var nextBatch = queryResult.Stream.ReadNextRecordBatchAsync().Result;
+ if (nextBatch == null) { break; }
+ count += nextBatch.Length;
+ }
+
+ Assert.AreEqual(expectedNumberOfResults, count, "The parsed records differ from the specified amount");
+
+ // if the values were set, make sure they are correct
+ if (queryResult.RowCount != -1)
+ {
+ Assert.AreEqual(queryResult.RowCount, expectedNumberOfResults, "The RowCount value does not match the expected results");
+
+ Assert.AreEqual(queryResult.RowCount, count, "The RowCount value does not match the counted records");
+ }
+ }
+
+ /// <summary>
+ /// Validates if an exception is an AdbcException
+ /// </summary>
+ /// <param name="ex">
+ /// The exception
+ /// </param>
+ public static void VerifyBadQueryGeneratesError(Exception ex)
+ {
+ Assert.IsTrue(ex is AdbcException, "Can only validate AdbcException types");
+ }
+ }
+}
diff --git a/csharp/test/Apache.Arrow.Adbc.Tests/MockAdbcException.cs b/csharp/test/Apache.Arrow.Adbc.Tests/MockAdbcException.cs
new file mode 100644
index 00000000..302de14b
--- /dev/null
+++ b/csharp/test/Apache.Arrow.Adbc.Tests/MockAdbcException.cs
@@ -0,0 +1,27 @@
+/*
+* 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.
+*/
+
+namespace Apache.Arrow.Adbc.Tests
+{
+ /// <summary>
+ /// Represents a downstream exception that can be thrown by drivers
+ /// </summary>
+ public class MockAdbcException : AdbcException
+ {
+
+ }
+}
diff --git a/csharp/test/Apache.Arrow.Adbc.Tests/MockArrayStream.cs b/csharp/test/Apache.Arrow.Adbc.Tests/MockArrayStream.cs
new file mode 100644
index 00000000..76c80760
--- /dev/null
+++ b/csharp/test/Apache.Arrow.Adbc.Tests/MockArrayStream.cs
@@ -0,0 +1,71 @@
+/*
+* 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.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Apache.Arrow.Ipc;
+
+namespace Apache.Arrow.Adbc.Tests
+{
+ /// <summary>
+ /// Provides a mechanism to easily run tests against an array stream.
+ /// </summary>
+ public class MockArrayStream : IArrowArrayStream
+ {
+ private readonly List<RecordBatch> recordBatches;
+ private readonly Schema schema;
+
+ // start at -1 to use the count the number of calls as the index
+ private int calls = -1;
+
+ /// <summary>
+ /// Initializes the TestArrayStream.
+ /// </summary>
+ /// <param name="schema">
+ /// The Arrow schema.
+ /// </param>
+ /// <param name="recordBatches">
+ /// A list of record batches.
+ /// </param>
+ public MockArrayStream(Schema schema, List<RecordBatch> recordBatches)
+ {
+ this.schema = schema;
+ this.recordBatches = recordBatches;
+ }
+
+ public Schema Schema => this.schema;
+
+ public void Dispose() { }
+
+ /// <summary>
+ /// Moves through the list of record batches.
+ /// </summary>
+ /// <param name="cancellationToken">
+ /// Optional cancellation token.
+ /// </param>
+ public ValueTask<RecordBatch> ReadNextRecordBatchAsync(CancellationToken cancellationToken = default)
+ {
+ calls++;
+
+ if (calls >= this.recordBatches.Count)
+ return new ValueTask<RecordBatch>();
+ else
+ return new ValueTask<RecordBatch>(this.recordBatches[calls]);
+ }
+ }
+}
diff --git a/csharp/test/Apache.Arrow.Adbc.Tests/TypeTests.cs b/csharp/test/Apache.Arrow.Adbc.Tests/TypeTests.cs
new file mode 100644
index 00000000..3ceabd53
--- /dev/null
+++ b/csharp/test/Apache.Arrow.Adbc.Tests/TypeTests.cs
@@ -0,0 +1,67 @@
+/*
+* 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 Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Apache.Arrow.Adbc.Tests
+{
+ /// <summary>
+ /// Performs verification for data types and values.
+ /// </summary>
+ public class TypeTests
+ {
+ /// <summary>
+ /// Verifies that the arrays passed as actual match the expected and
+ /// that the values match actual and expected.
+ /// </summary>
+ /// <param name="actualArrays">
+ /// The actual arrays
+ /// </param>
+ /// <param name="expectedArrays">
+ /// The expected array types
+ /// </param>
+ /// <param name="actualFirstValues">
+ /// The actual values
+ /// </param>
+ /// <param name="expectedFirstValues">
+ /// The expected values
+ /// </param>
+ public static void VerifyTypesAndValues(List<IArrowArray> actualArrays, List<Type> expectedArrays, List<object> actualFirstValues, List<object> expectedFirstValues)
+ {
+ Assert.IsTrue(actualArrays.Count == expectedArrays.Count, "The actual and expected array lengths must be the same length");
+
+ Assert.IsTrue(actualArrays.Count == actualFirstValues.Count, "actualArrays and actualFirstValues must be the same length");
+
+ Assert.IsTrue(expectedArrays.Count == expectedFirstValues.Count, "expectedArrays and expectedFirstValues must be the same length");
+
+ for (int i = 0; i < actualArrays.Count; i++)
+ {
+ IArrowArray actualArray = actualArrays[i];
+ Type expectedArrayType = expectedArrays[i];
+
+ Assert.IsTrue(actualArray.GetType() == expectedArrayType, $"{actualArray.GetType()} != {expectedArrayType} at position {i}");
+
+ object actualValue = actualFirstValues[i];
+ object expectedValue = expectedFirstValues[i];
+
+ Assert.IsTrue(actualValue.Equals(expectedValue), $"{actualValue} != {expectedValue} at position {i}");
+ }
+ }
+ }
+}
diff --git a/dev/release/rat_exclude_files.txt b/dev/release/rat_exclude_files.txt
index f8e26fb9..f5e56d86 100644
--- a/dev/release/rat_exclude_files.txt
+++ b/dev/release/rat_exclude_files.txt
@@ -30,3 +30,8 @@ c/vendor/sqlite3/sqlite3.c
c/vendor/sqlite3/sqlite3.h
*.Rproj
*.Rd
+csharp/Apache.Arrow.Adbc.sln
+csharp/src/Apache.Arrow.Adbc.FlightSql/Apache.Arrow.Adbc.FlightSql.csproj
+csharp/src/Apache.Arrow.Adbc/Apache.Arrow.Adbc.csproj
+csharp/test/Apache.Arrow.Adbc.FlightSql.Tests/Apache.Arrow.Adbc.FlightSql.Tests.csproj
+csharp/test/Apache.Arrow.Adbc.Tests/Apache.Arrow.Adbc.Tests.csproj