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