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 69ba923fa feat(c/driver_manager): don't ignore invalid manifests 
(#3399)
69ba923fa is described below

commit 69ba923fad12ed8a70e5bf0ebb5ef284387c40ad
Author: David Li <[email protected]>
AuthorDate: Tue Sep 9 08:27:37 2025 +0900

    feat(c/driver_manager): don't ignore invalid manifests (#3399)
    
    Fixes #3398.
---
 .pre-commit-config.yaml                           |   6 ++
 c/driver_manager/adbc_driver_manager.cc           |  92 ++++++++++++++--
 c/driver_manager/adbc_driver_manager_test.cc      | 124 +++++++++++++++++++++-
 ci/scripts/run_cgo_drivermgr_check.sh             |  16 ++-
 go/adbc/drivermgr/adbc_driver_manager.cc          |  92 ++++++++++++++--
 python/adbc_driver_manager/tests/test_manifest.py | 106 ++++++++++++++++++
 6 files changed, 408 insertions(+), 28 deletions(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 05ce5478c..8441d6a62 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -117,6 +117,12 @@ repos:
       pass_filenames: true
       files: '^c/include/arrow-adbc/[^/]*\.h$'
       entry: "./ci/scripts/run_cgo_drivermgr_check.sh"
+    - id: check-cgo-adbc-impl
+      name: Ensure CGO adbc_driver_manager.cc is syncd
+      language: script
+      pass_filenames: true
+      files: '^c/driver_manager/adbc_driver_manager\.cc$'
+      entry: "./ci/scripts/run_cgo_drivermgr_check.sh"
     # https://infra.apache.org/github-actions-policy.html
     - id: check-pin
       name: Ensure GitHub Actions and pre-commit hooks are pinned to a 
specific SHA
diff --git a/c/driver_manager/adbc_driver_manager.cc 
b/c/driver_manager/adbc_driver_manager.cc
index ed8946e52..3169fd480 100644
--- a/c/driver_manager/adbc_driver_manager.cc
+++ b/c/driver_manager/adbc_driver_manager.cc
@@ -69,6 +69,7 @@ enum class SearchPathSource {
   kUnset,
   kDoesNotExist,
   kDisabled,
+  kOtherError,
 };
 
 using SearchPaths = std::vector<std::pair<SearchPathSource, 
std::filesystem::path>>;
@@ -106,6 +107,9 @@ void AddSearchPathsToError(const SearchPaths& search_paths, 
std::string& error_m
         case SearchPathSource::kDisabled:
           error_message += "not enabled at build time: ";
           break;
+        case SearchPathSource::kOtherError:
+          // Don't add any prefix
+          break;
       }
       error_message += path.string();
     }
@@ -327,13 +331,22 @@ AdbcStatusCode LoadDriverFromRegistry(HKEY root, const 
std::wstring& driver_name
 }
 #endif  // _WIN32
 
+/// \return ADBC_STATUS_NOT_FOUND if the manifest does not contain a driver
+///   path for this platform, ADBC_STATUS_INVALID_ARGUMENT if the manifest
+///   could not be parsed, ADBC_STATUS_OK otherwise (`info` will be populated)
 AdbcStatusCode LoadDriverManifest(const std::filesystem::path& driver_manifest,
                                   DriverInfo& info, struct AdbcError* error) {
   toml::table config;
   try {
     config = toml::parse_file(driver_manifest.native());
   } catch (const toml::parse_error& err) {
-    SetError(error, err.what());
+    // Despite the name, this exception covers IO errors too.  Hence, we can't
+    // differentiate between bad syntax and other I/O error.
+    std::string message = "Could not open manifest. ";
+    message += err.what();
+    message += ". Manifest: ";
+    message += driver_manifest.string();
+    SetError(error, std::move(message));
     return ADBC_STATUS_INVALID_ARGUMENT;
   }
 
@@ -410,7 +423,7 @@ AdbcStatusCode LoadDriverManifest(const 
std::filesystem::path& driver_manifest,
   }
   SetError(error, "Driver path not defined in manifest '"s + 
driver_manifest.string() +
                       "'. `Driver.shared` must be a string or table"s);
-  return ADBC_STATUS_NOT_FOUND;
+  return ADBC_STATUS_INVALID_ARGUMENT;
 }
 
 SearchPaths GetEnvPaths(const char_type* env_var) {
@@ -425,6 +438,8 @@ SearchPaths GetEnvPaths(const char_type* env_var) {
   std::wstring path_var;
   path_var.resize(required_size);
   _wgetenv_s(&required_size, path_var.data(), required_size, env_var);
+  // Remove null terminator
+  path_var.resize(required_size - 1);
   auto path = Utf8Encode(path_var);
 #else
   const char* path_var = std::getenv(env_var);
@@ -602,13 +617,21 @@ struct ManagedLibrary {
     return FindDriver(driver_path, load_options, additional_search_paths, 
info, error);
   }
 
+  /// \return ADBC_STATUS_NOT_FOUND if the driver shared library could not be
+  ///   found (via dlopen) or if a manifest was found but did not contain a
+  ///   path for the current platform, ADBC_STATUS_INVALID_ARGUMENT if a
+  ///   manifest was found but could not be parsed, ADBC_STATUS_OK otherwise
+  ///
+  /// May modify search_paths to add error info
   AdbcStatusCode SearchPathsForDriver(const std::filesystem::path& driver_path,
-                                      const SearchPaths& search_paths, 
DriverInfo& info,
+                                      SearchPaths& search_paths, DriverInfo& 
info,
                                       struct AdbcError* error) {
+    SearchPaths extra_debug_info;
     for (const auto& [source, search_path] : search_paths) {
       if (source == SearchPathSource::kRegistry || source == 
SearchPathSource::kUnset ||
           source == SearchPathSource::kDoesNotExist ||
-          source == SearchPathSource::kDisabled) {
+          source == SearchPathSource::kDisabled ||
+          source == SearchPathSource::kOtherError) {
         continue;
       }
       std::filesystem::path full_path = search_path / driver_path;
@@ -616,19 +639,63 @@ struct ManagedLibrary {
       // check for toml first, then dll
       full_path.replace_extension(".toml");
       if (std::filesystem::exists(full_path)) {
-        auto status = LoadDriverManifest(full_path, info, nullptr);
+        OwnedError intermediate_error;
+
+        auto status = LoadDriverManifest(full_path, info, 
&intermediate_error.error);
         if (status == ADBC_STATUS_OK) {
-          return Load(info.lib_path.c_str(), search_paths, error);
+          // Don't pass attempted_paths here; we'll generate the error at a 
higher level
+          status = Load(info.lib_path.c_str(), {}, &intermediate_error.error);
+          if (status == ADBC_STATUS_OK) {
+            return status;
+          }
+          std::string message = "found ";
+          message += full_path.string();
+          if (intermediate_error.error.message) {
+            message += " but: ";
+            message += intermediate_error.error.message;
+          } else {
+            message += " could not load the driver it specified";
+          }
+          extra_debug_info.emplace_back(SearchPathSource::kOtherError,
+                                        std::move(message));
+          search_paths.insert(search_paths.end(), extra_debug_info.begin(),
+                              extra_debug_info.end());
+          return status;
+        } else if (status == ADBC_STATUS_INVALID_ARGUMENT) {
+          // The manifest was invalid. Don't ignore that!
+          search_paths.insert(search_paths.end(), extra_debug_info.begin(),
+                              extra_debug_info.end());
+          if (intermediate_error.error.message) {
+            std::string error_message = intermediate_error.error.message;
+            AddSearchPathsToError(search_paths, error_message);
+            SetError(error, std::move(error_message));
+          }
+          return status;
         }
+        // Should be NOT_FOUND otherwise
+        std::string message = "found ";
+        message += full_path.string();
+        if (intermediate_error.error.message) {
+          message += " but: ";
+          message += intermediate_error.error.message;
+        } else {
+          message += " which did not define a driver for this platform";
+        }
+
+        extra_debug_info.emplace_back(SearchPathSource::kOtherError, 
std::move(message));
       }
 
-      full_path.replace_extension("");  // remove the .toml extension
-      auto status = Load(full_path.c_str(), search_paths, nullptr);
+      // remove the .toml extension; Load will add the DLL/SO/DYLIB suffix
+      full_path.replace_extension("");
+      // Don't pass error here - it'll be suppressed anyways
+      auto status = Load(full_path.c_str(), {}, nullptr);
       if (status == ADBC_STATUS_OK) {
         return status;
       }
     }
 
+    search_paths.insert(search_paths.end(), extra_debug_info.begin(),
+                        extra_debug_info.end());
     return ADBC_STATUS_NOT_FOUND;
   }
 
@@ -677,7 +744,8 @@ struct ManagedLibrary {
 #endif  // ADBC_CONDA_BUILD
 
       auto status = SearchPathsForDriver(driver_path, search_paths, info, 
error);
-      if (status == ADBC_STATUS_OK) {
+      if (status != ADBC_STATUS_NOT_FOUND) {
+        // If NOT_FOUND, then keep searching; if OK or INVALID_ARGUMENT, stop
         return status;
       }
     }
@@ -704,7 +772,7 @@ struct ManagedLibrary {
 
       auto user_paths = GetSearchPaths(ADBC_LOAD_FLAG_SEARCH_USER);
       status = SearchPathsForDriver(driver_path, user_paths, info, error);
-      if (status == ADBC_STATUS_OK) {
+      if (status != ADBC_STATUS_NOT_FOUND) {
         return status;
       }
       search_paths.insert(search_paths.end(), user_paths.begin(), 
user_paths.end());
@@ -728,7 +796,7 @@ struct ManagedLibrary {
 
       auto system_paths = GetSearchPaths(ADBC_LOAD_FLAG_SEARCH_SYSTEM);
       status = SearchPathsForDriver(driver_path, system_paths, info, error);
-      if (status == ADBC_STATUS_OK) {
+      if (status != ADBC_STATUS_NOT_FOUND) {
         return status;
       }
       search_paths.insert(search_paths.end(), system_paths.begin(), 
system_paths.end());
@@ -753,6 +821,8 @@ struct ManagedLibrary {
 #endif  // _WIN32
   }
 
+  /// \return ADBC_STATUS_NOT_FOUND if the driver shared library could not be
+  ///   found, ADBC_STATUS_OK otherwise
   AdbcStatusCode Load(const char_type* library, const SearchPaths& 
attempted_paths,
                       struct AdbcError* error) {
     std::string error_message;
diff --git a/c/driver_manager/adbc_driver_manager_test.cc 
b/c/driver_manager/adbc_driver_manager_test.cc
index b17ec236d..0fe83ca65 100644
--- a/c/driver_manager/adbc_driver_manager_test.cc
+++ b/c/driver_manager/adbc_driver_manager_test.cc
@@ -620,11 +620,44 @@ TEST_F(DriverManifest, ManifestDriverMissing) {
   // Attempt to load the driver
   ASSERT_THAT(AdbcFindLoadDriver(filepath.string().data(), nullptr, 
ADBC_VERSION_1_1_0,
                                  ADBC_LOAD_FLAG_DEFAULT, nullptr, &driver, 
&error),
-              IsStatus(ADBC_STATUS_NOT_FOUND, &error));
+              IsStatus(ADBC_STATUS_INVALID_ARGUMENT, &error));
+
+  ASSERT_THAT(error.message, ::testing::HasSubstr("Driver path not defined in 
manifest"));
+  ASSERT_THAT(error.message,
+              ::testing::HasSubstr("`Driver.shared` must be a string or 
table"));
+  ASSERT_TRUE(std::filesystem::remove(filepath));
+}
 
+TEST_F(DriverManifest, ManifestDriverMissingAdbcDatabase) {
+  // Similar test as above but with AdbcDatabaseInit path and using the
+  // additional search path.
+  // Create a manifest without the "Driver" section
+  auto filepath = temp_dir / "sqlite.toml";
+  toml::table manifest_without_driver = simple_manifest;
+  manifest_without_driver.erase("Driver");
+
+  std::ofstream test_manifest_file(filepath);
+  ASSERT_TRUE(test_manifest_file.is_open());
+  test_manifest_file << manifest_without_driver;
+  test_manifest_file.close();
+
+  adbc_validation::Handle<struct AdbcDatabase> database;
+  ASSERT_THAT(AdbcDatabaseNew(&database.value, &error), IsOkStatus(&error));
+  ASSERT_THAT(AdbcDatabaseSetOption(&database.value, "driver", "sqlite", 
&error),
+              IsOkStatus(&error));
+  ASSERT_THAT(AdbcDriverManagerDatabaseSetLoadFlags(&database.value,
+                                                    ADBC_LOAD_FLAG_DEFAULT, 
&error),
+              IsOkStatus(&error));
+  std::string search_path = temp_dir.string();
+  ASSERT_THAT(AdbcDriverManagerDatabaseSetAdditionalSearchPathList(
+                  &database.value, search_path.data(), &error),
+              IsOkStatus(&error));
+  ASSERT_THAT(AdbcDatabaseInit(&database.value, &error),
+              IsStatus(ADBC_STATUS_INVALID_ARGUMENT, &error));
   ASSERT_THAT(error.message, ::testing::HasSubstr("Driver path not defined in 
manifest"));
   ASSERT_THAT(error.message,
               ::testing::HasSubstr("`Driver.shared` must be a string or 
table"));
+
   ASSERT_TRUE(std::filesystem::remove(filepath));
 }
 
@@ -643,7 +676,7 @@ TEST_F(DriverManifest, ManifestDriverInvalid) {
   // Attempt to load the driver
   ASSERT_THAT(AdbcFindLoadDriver(filepath.string().data(), nullptr, 
ADBC_VERSION_1_1_0,
                                  ADBC_LOAD_FLAG_DEFAULT, nullptr, &driver, 
&error),
-              IsStatus(ADBC_STATUS_NOT_FOUND, &error));
+              IsStatus(ADBC_STATUS_INVALID_ARGUMENT, &error));
 
   ASSERT_THAT(error.message, ::testing::HasSubstr("Driver path not defined in 
manifest"));
   ASSERT_THAT(error.message,
@@ -702,6 +735,93 @@ TEST_F(DriverManifest, ManifestWrongArch) {
   ASSERT_TRUE(std::filesystem::remove(filepath));
 }
 
+TEST_F(DriverManifest, ManifestDriverMissingArchAdbcDatabase) {
+  // Similar test as above but with AdbcDatabaseInit path and using the
+  // additional search path.
+  // Create a manifest without the "Driver" section
+  auto filepath = temp_dir / "sqlite.toml";
+  toml::table manifest_without_driver = simple_manifest;
+  manifest_without_driver.erase("Driver");
+  manifest_without_driver.insert("Driver",
+                                 toml::table{
+                                     {"shared",
+                                      toml::table{
+                                          {"non-existent", 
"path/to/bad/driver.so"},
+                                          {"windows-alpha64", 
"path/to/bad/driver.so"},
+                                      }},
+                                 });
+
+  std::ofstream test_manifest_file(filepath);
+  ASSERT_TRUE(test_manifest_file.is_open());
+  test_manifest_file << manifest_without_driver;
+  test_manifest_file.close();
+
+  adbc_validation::Handle<struct AdbcDatabase> database;
+  ASSERT_THAT(AdbcDatabaseNew(&database.value, &error), IsOkStatus(&error));
+  ASSERT_THAT(AdbcDatabaseSetOption(&database.value, "driver", "sqlite", 
&error),
+              IsOkStatus(&error));
+  ASSERT_THAT(AdbcDriverManagerDatabaseSetLoadFlags(&database.value,
+                                                    ADBC_LOAD_FLAG_DEFAULT, 
&error),
+              IsOkStatus(&error));
+  std::string search_path = temp_dir.string();
+  ASSERT_THAT(AdbcDriverManagerDatabaseSetAdditionalSearchPathList(
+                  &database.value, search_path.data(), &error),
+              IsOkStatus(&error));
+  ASSERT_THAT(AdbcDatabaseInit(&database.value, &error),
+              IsStatus(ADBC_STATUS_NOT_FOUND, &error));
+  ASSERT_THAT(error.message, ::testing::HasSubstr("sqlite.toml but:"));
+  ASSERT_THAT(error.message,
+              ::testing::HasSubstr("Architectures found: non-existent 
windows-alpha64"));
+
+  ASSERT_TRUE(std::filesystem::remove(filepath));
+}
+
+TEST_F(DriverManifest, ManifestDriverPointsNowhere) {
+  // Similar test as above but with AdbcDatabaseInit path and using the
+  // additional search path.
+  // Create a manifest without the "Driver" section
+  auto filepath = temp_dir / "sqlite.toml";
+  toml::table manifest_without_driver = simple_manifest;
+  manifest_without_driver.erase("Driver");
+  // The idea is that we can find the manifest, but not the driver it points 
to.
+  manifest_without_driver.insert("Driver", toml::table{
+                                               {"shared",
+                                                toml::table{
+                                                    {"linux_arm64", 
"adbc-goosedb"},
+                                                    {"linux_amd64", 
"adbc-goosedb"},
+                                                    {"macos_arm64", 
"adbc-goosedb"},
+                                                    {"macos_amd64", 
"adbc-goosedb"},
+                                                    {"windows_arm64", 
"adbc-goosedb"},
+                                                    {"windows_amd64", 
"adbc-goosedb"},
+                                                }},
+                                           });
+
+  std::ofstream test_manifest_file(filepath);
+  ASSERT_TRUE(test_manifest_file.is_open());
+  test_manifest_file << manifest_without_driver;
+  test_manifest_file.close();
+
+  adbc_validation::Handle<struct AdbcDatabase> database;
+  ASSERT_THAT(AdbcDatabaseNew(&database.value, &error), IsOkStatus(&error));
+  ASSERT_THAT(AdbcDatabaseSetOption(&database.value, "driver", "sqlite", 
&error),
+              IsOkStatus(&error));
+  ASSERT_THAT(AdbcDriverManagerDatabaseSetLoadFlags(&database.value,
+                                                    ADBC_LOAD_FLAG_DEFAULT, 
&error),
+              IsOkStatus(&error));
+  std::string search_path = temp_dir.string();
+  ASSERT_THAT(AdbcDriverManagerDatabaseSetAdditionalSearchPathList(
+                  &database.value, search_path.data(), &error),
+              IsOkStatus(&error));
+  ASSERT_THAT(AdbcDatabaseInit(&database.value, &error),
+              IsStatus(ADBC_STATUS_NOT_FOUND, &error));
+  ASSERT_THAT(error.message, ::testing::HasSubstr("sqlite.toml but:"));
+  // Message is platform-specific but something like "dlopen() failed:
+  // adbc-goosedb: cannot open shared object file..."
+  ASSERT_THAT(error.message, ::testing::HasSubstr("adbc-goosedb"));
+
+  ASSERT_TRUE(std::filesystem::remove(filepath));
+}
+
 TEST_F(DriverManifest, ManifestArchPathEmpty) {
   auto filepath = temp_dir / "sqlite.toml";
   toml::table manifest_without_driver = simple_manifest;
diff --git a/ci/scripts/run_cgo_drivermgr_check.sh 
b/ci/scripts/run_cgo_drivermgr_check.sh
index 946ab0906..5e8fb87a5 100755
--- a/ci/scripts/run_cgo_drivermgr_check.sh
+++ b/ci/scripts/run_cgo_drivermgr_check.sh
@@ -32,10 +32,18 @@ main() {
 
   for f in "$@"; do
     fn=$(basename $f)
-    if ! diff -q "$f" "go/adbc/drivermgr/arrow-adbc/$fn" &>/dev/null; then
-      >&2 echo "OUT OF SYNC: $f differs from go/adbc/drivermgr/arrow-adbc/$fn"
-      popd
-      return 1
+    if [[ "$fn" == "adbc_driver_manager.cc" ]]; then
+        if ! diff -q "$f" "go/adbc/drivermgr/$fn" &>/dev/null; then
+            >&2 echo "OUT OF SYNC: $f differs from go/adbc/drivermgr/$fn"
+            popd
+            return 1
+        fi
+    else
+        if ! diff -q "$f" "go/adbc/drivermgr/arrow-adbc/$fn" &>/dev/null; then
+            >&2 echo "OUT OF SYNC: $f differs from 
go/adbc/drivermgr/arrow-adbc/$fn"
+            popd
+            return 1
+        fi
     fi
   done
 
diff --git a/go/adbc/drivermgr/adbc_driver_manager.cc 
b/go/adbc/drivermgr/adbc_driver_manager.cc
index ed8946e52..3169fd480 100644
--- a/go/adbc/drivermgr/adbc_driver_manager.cc
+++ b/go/adbc/drivermgr/adbc_driver_manager.cc
@@ -69,6 +69,7 @@ enum class SearchPathSource {
   kUnset,
   kDoesNotExist,
   kDisabled,
+  kOtherError,
 };
 
 using SearchPaths = std::vector<std::pair<SearchPathSource, 
std::filesystem::path>>;
@@ -106,6 +107,9 @@ void AddSearchPathsToError(const SearchPaths& search_paths, 
std::string& error_m
         case SearchPathSource::kDisabled:
           error_message += "not enabled at build time: ";
           break;
+        case SearchPathSource::kOtherError:
+          // Don't add any prefix
+          break;
       }
       error_message += path.string();
     }
@@ -327,13 +331,22 @@ AdbcStatusCode LoadDriverFromRegistry(HKEY root, const 
std::wstring& driver_name
 }
 #endif  // _WIN32
 
+/// \return ADBC_STATUS_NOT_FOUND if the manifest does not contain a driver
+///   path for this platform, ADBC_STATUS_INVALID_ARGUMENT if the manifest
+///   could not be parsed, ADBC_STATUS_OK otherwise (`info` will be populated)
 AdbcStatusCode LoadDriverManifest(const std::filesystem::path& driver_manifest,
                                   DriverInfo& info, struct AdbcError* error) {
   toml::table config;
   try {
     config = toml::parse_file(driver_manifest.native());
   } catch (const toml::parse_error& err) {
-    SetError(error, err.what());
+    // Despite the name, this exception covers IO errors too.  Hence, we can't
+    // differentiate between bad syntax and other I/O error.
+    std::string message = "Could not open manifest. ";
+    message += err.what();
+    message += ". Manifest: ";
+    message += driver_manifest.string();
+    SetError(error, std::move(message));
     return ADBC_STATUS_INVALID_ARGUMENT;
   }
 
@@ -410,7 +423,7 @@ AdbcStatusCode LoadDriverManifest(const 
std::filesystem::path& driver_manifest,
   }
   SetError(error, "Driver path not defined in manifest '"s + 
driver_manifest.string() +
                       "'. `Driver.shared` must be a string or table"s);
-  return ADBC_STATUS_NOT_FOUND;
+  return ADBC_STATUS_INVALID_ARGUMENT;
 }
 
 SearchPaths GetEnvPaths(const char_type* env_var) {
@@ -425,6 +438,8 @@ SearchPaths GetEnvPaths(const char_type* env_var) {
   std::wstring path_var;
   path_var.resize(required_size);
   _wgetenv_s(&required_size, path_var.data(), required_size, env_var);
+  // Remove null terminator
+  path_var.resize(required_size - 1);
   auto path = Utf8Encode(path_var);
 #else
   const char* path_var = std::getenv(env_var);
@@ -602,13 +617,21 @@ struct ManagedLibrary {
     return FindDriver(driver_path, load_options, additional_search_paths, 
info, error);
   }
 
+  /// \return ADBC_STATUS_NOT_FOUND if the driver shared library could not be
+  ///   found (via dlopen) or if a manifest was found but did not contain a
+  ///   path for the current platform, ADBC_STATUS_INVALID_ARGUMENT if a
+  ///   manifest was found but could not be parsed, ADBC_STATUS_OK otherwise
+  ///
+  /// May modify search_paths to add error info
   AdbcStatusCode SearchPathsForDriver(const std::filesystem::path& driver_path,
-                                      const SearchPaths& search_paths, 
DriverInfo& info,
+                                      SearchPaths& search_paths, DriverInfo& 
info,
                                       struct AdbcError* error) {
+    SearchPaths extra_debug_info;
     for (const auto& [source, search_path] : search_paths) {
       if (source == SearchPathSource::kRegistry || source == 
SearchPathSource::kUnset ||
           source == SearchPathSource::kDoesNotExist ||
-          source == SearchPathSource::kDisabled) {
+          source == SearchPathSource::kDisabled ||
+          source == SearchPathSource::kOtherError) {
         continue;
       }
       std::filesystem::path full_path = search_path / driver_path;
@@ -616,19 +639,63 @@ struct ManagedLibrary {
       // check for toml first, then dll
       full_path.replace_extension(".toml");
       if (std::filesystem::exists(full_path)) {
-        auto status = LoadDriverManifest(full_path, info, nullptr);
+        OwnedError intermediate_error;
+
+        auto status = LoadDriverManifest(full_path, info, 
&intermediate_error.error);
         if (status == ADBC_STATUS_OK) {
-          return Load(info.lib_path.c_str(), search_paths, error);
+          // Don't pass attempted_paths here; we'll generate the error at a 
higher level
+          status = Load(info.lib_path.c_str(), {}, &intermediate_error.error);
+          if (status == ADBC_STATUS_OK) {
+            return status;
+          }
+          std::string message = "found ";
+          message += full_path.string();
+          if (intermediate_error.error.message) {
+            message += " but: ";
+            message += intermediate_error.error.message;
+          } else {
+            message += " could not load the driver it specified";
+          }
+          extra_debug_info.emplace_back(SearchPathSource::kOtherError,
+                                        std::move(message));
+          search_paths.insert(search_paths.end(), extra_debug_info.begin(),
+                              extra_debug_info.end());
+          return status;
+        } else if (status == ADBC_STATUS_INVALID_ARGUMENT) {
+          // The manifest was invalid. Don't ignore that!
+          search_paths.insert(search_paths.end(), extra_debug_info.begin(),
+                              extra_debug_info.end());
+          if (intermediate_error.error.message) {
+            std::string error_message = intermediate_error.error.message;
+            AddSearchPathsToError(search_paths, error_message);
+            SetError(error, std::move(error_message));
+          }
+          return status;
         }
+        // Should be NOT_FOUND otherwise
+        std::string message = "found ";
+        message += full_path.string();
+        if (intermediate_error.error.message) {
+          message += " but: ";
+          message += intermediate_error.error.message;
+        } else {
+          message += " which did not define a driver for this platform";
+        }
+
+        extra_debug_info.emplace_back(SearchPathSource::kOtherError, 
std::move(message));
       }
 
-      full_path.replace_extension("");  // remove the .toml extension
-      auto status = Load(full_path.c_str(), search_paths, nullptr);
+      // remove the .toml extension; Load will add the DLL/SO/DYLIB suffix
+      full_path.replace_extension("");
+      // Don't pass error here - it'll be suppressed anyways
+      auto status = Load(full_path.c_str(), {}, nullptr);
       if (status == ADBC_STATUS_OK) {
         return status;
       }
     }
 
+    search_paths.insert(search_paths.end(), extra_debug_info.begin(),
+                        extra_debug_info.end());
     return ADBC_STATUS_NOT_FOUND;
   }
 
@@ -677,7 +744,8 @@ struct ManagedLibrary {
 #endif  // ADBC_CONDA_BUILD
 
       auto status = SearchPathsForDriver(driver_path, search_paths, info, 
error);
-      if (status == ADBC_STATUS_OK) {
+      if (status != ADBC_STATUS_NOT_FOUND) {
+        // If NOT_FOUND, then keep searching; if OK or INVALID_ARGUMENT, stop
         return status;
       }
     }
@@ -704,7 +772,7 @@ struct ManagedLibrary {
 
       auto user_paths = GetSearchPaths(ADBC_LOAD_FLAG_SEARCH_USER);
       status = SearchPathsForDriver(driver_path, user_paths, info, error);
-      if (status == ADBC_STATUS_OK) {
+      if (status != ADBC_STATUS_NOT_FOUND) {
         return status;
       }
       search_paths.insert(search_paths.end(), user_paths.begin(), 
user_paths.end());
@@ -728,7 +796,7 @@ struct ManagedLibrary {
 
       auto system_paths = GetSearchPaths(ADBC_LOAD_FLAG_SEARCH_SYSTEM);
       status = SearchPathsForDriver(driver_path, system_paths, info, error);
-      if (status == ADBC_STATUS_OK) {
+      if (status != ADBC_STATUS_NOT_FOUND) {
         return status;
       }
       search_paths.insert(search_paths.end(), system_paths.begin(), 
system_paths.end());
@@ -753,6 +821,8 @@ struct ManagedLibrary {
 #endif  // _WIN32
   }
 
+  /// \return ADBC_STATUS_NOT_FOUND if the driver shared library could not be
+  ///   found, ADBC_STATUS_OK otherwise
   AdbcStatusCode Load(const char_type* library, const SearchPaths& 
attempted_paths,
                       struct AdbcError* error) {
     std::string error_message;
diff --git a/python/adbc_driver_manager/tests/test_manifest.py 
b/python/adbc_driver_manager/tests/test_manifest.py
new file mode 100644
index 000000000..0b4e62bfa
--- /dev/null
+++ b/python/adbc_driver_manager/tests/test_manifest.py
@@ -0,0 +1,106 @@
+# 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.
+
+import pytest
+
+import adbc_driver_manager.dbapi
+
+
[email protected]
+def test_manifest_indirect(tmp_path, monkeypatch) -> None:
+    with (tmp_path / "testdriver.toml").open("w") as sink:
+        sink.write(
+            """
+name = "my driver"
+version = "0.1.0"
+
+[Driver]
+shared = "adbc_driver_sqlite"
+            """
+        )
+
+    monkeypatch.setenv("ADBC_DRIVER_PATH", str(tmp_path))
+
+    with adbc_driver_manager.dbapi.connect(driver="testdriver") as conn:
+        with conn.cursor() as cursor:
+            cursor.execute("SELECT sqlite_version()")
+            assert cursor.fetchone() is not None
+
+
+def test_manifest_indirect_unknown_driver(tmp_path, monkeypatch) -> None:
+    with (tmp_path / "testdriver2.toml").open("w") as sink:
+        sink.write(
+            """
+name = "my driver"
+version = "0.1.0"
+
+[Driver]
+shared = "adbc_driver_goosedb"
+            """
+        )
+
+    monkeypatch.setenv("ADBC_DRIVER_PATH", str(tmp_path))
+
+    with pytest.raises(
+        adbc_driver_manager.dbapi.Error, match="adbc_driver_goosedb"
+    ) as excinfo:
+        with adbc_driver_manager.dbapi.connect(driver="testdriver2"):
+            pass
+
+    assert "ADBC_DRIVER_PATH: " + str(tmp_path) in str(excinfo.value)
+    assert "found {}".format(str(tmp_path / "testdriver2.toml")) in 
str(excinfo.value)
+
+
+def test_manifest_indirect_missing_platform(tmp_path, monkeypatch) -> None:
+    with (tmp_path / "testdriver3.toml").open("w") as sink:
+        sink.write(
+            """
+name = "my driver"
+version = "0.1.0"
+
+[Driver.shared]
+msdos_itanium64 = "adbc_driver_sqlite"
+            """
+        )
+
+    monkeypatch.setenv("ADBC_DRIVER_PATH", str(tmp_path))
+
+    with pytest.raises(adbc_driver_manager.dbapi.Error) as excinfo:
+        with adbc_driver_manager.dbapi.connect(driver="testdriver3"):
+            pass
+
+    assert "ADBC_DRIVER_PATH: " + str(tmp_path) in str(excinfo.value)
+    assert "found {}".format(str(tmp_path / "testdriver3.toml")) in 
str(excinfo.value)
+    assert "Architectures found: msdos_itanium64" in str(excinfo.value)
+
+
+def test_manifest_bad(tmp_path, monkeypatch) -> None:
+    with (tmp_path / "testdriver4.toml").open("w") as sink:
+        sink.write(
+            """
+name = "my driver"
+version = "0.1.0"
+            """
+        )
+
+    monkeypatch.setenv("ADBC_DRIVER_PATH", str(tmp_path))
+
+    with pytest.raises(
+        adbc_driver_manager.dbapi.Error, match="Driver path not defined in 
manifest"
+    ):
+        with adbc_driver_manager.dbapi.connect(driver="testdriver4"):
+            pass

Reply via email to