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 e4404e761 feat(c/driver_manager): improve error reporting for manifests (#3386) e4404e761 is described below commit e4404e761be2ca730f708fc712f77f752bf42e1d Author: David Li <li.david...@gmail.com> AuthorDate: Thu Sep 4 13:02:56 2025 +0900 feat(c/driver_manager): improve error reporting for manifests (#3386) Fixes #3223. --- c/driver_manager/adbc_driver_manager.cc | 279 +++++++++++++++++----- c/driver_manager/adbc_driver_manager_test.cc | 152 +++++++++++- python/adbc_driver_manager/tests/test_dbapi.py | 2 +- python/adbc_driver_manager/tests/test_lowlevel.py | 2 +- 4 files changed, 375 insertions(+), 60 deletions(-) diff --git a/c/driver_manager/adbc_driver_manager.cc b/c/driver_manager/adbc_driver_manager.cc index e8ef6f01a..7b49df98e 100644 --- a/c/driver_manager/adbc_driver_manager.cc +++ b/c/driver_manager/adbc_driver_manager.cc @@ -58,6 +58,60 @@ std::filesystem::path InternalAdbcUserConfigDir(); namespace { +/// \brief Where a search path came from (for error reporting) +enum class SearchPathSource { + kEnv, + kUser, + kRegistry, + kSystem, + kAdditional, + kConda, + kUnset, + kDoesNotExist, + kDisabled, +}; + +using SearchPaths = std::vector<std::pair<SearchPathSource, std::filesystem::path>>; + +void AddSearchPathsToError(const SearchPaths& search_paths, std::string& error_message) { + if (!search_paths.empty()) { + error_message += "\nAlso searched these paths for manifests:"; + for (const auto& [source, path] : search_paths) { + error_message += "\n\t"; + switch (source) { + case SearchPathSource::kEnv: + error_message += "ADBC_DRIVER_PATH: "; + break; + case SearchPathSource::kUser: + error_message += "user config dir: "; + break; + case SearchPathSource::kRegistry: + error_message += "Registry: "; + break; + case SearchPathSource::kSystem: + error_message += "system config dir: "; + break; + case SearchPathSource::kAdditional: + error_message += "additional search path: "; + break; + case SearchPathSource::kConda: + error_message += "Conda prefix: "; + break; + case SearchPathSource::kUnset: + error_message += "not set: "; + break; + case SearchPathSource::kDoesNotExist: + error_message += "does not exist: "; + break; + case SearchPathSource::kDisabled: + error_message += "not enabled at build time: "; + break; + } + error_message += path.string(); + } + } +} + // Platform-specific helpers #if defined(_WIN32) @@ -161,7 +215,7 @@ std::wstring Utf8Decode(const std::string& str) { using char_type = char; #endif // _WIN32 -// Driver state +/// \brief The location and entrypoint of a resolved driver. struct DriverInfo { std::string manifest_file; std::string driver_name; @@ -216,14 +270,23 @@ class RegistryKey { AdbcStatusCode LoadDriverFromRegistry(HKEY root, const std::wstring& driver_name, DriverInfo& info, struct AdbcError* error) { + // N.B. start all error messages with the subkey so that the calling code + // can prepend the name of 'root' to the error message (easier than trying + // to invoke win32 API to get the name of the HKEY) static const LPCWSTR kAdbcDriverRegistry = L"SOFTWARE\\ADBC\\Drivers"; RegistryKey drivers_key(root, kAdbcDriverRegistry); if (!drivers_key.is_open()) { + std::string error_message = "SOFTWARE\\ADBC\\DRIVERS not found"s; + SetError(error, std::move(error_message)); return ADBC_STATUS_NOT_FOUND; } RegistryKey dkey(drivers_key.key(), driver_name); if (!dkey.is_open()) { + std::string error_message = "SOFTWARE\\ADBC\\DRIVERS has no entry for driver \""s; + error_message += Utf8Encode(driver_name); + error_message += "\""s; + SetError(error, std::move(error_message)); return ADBC_STATUS_NOT_FOUND; } @@ -233,8 +296,10 @@ AdbcStatusCode LoadDriverFromRegistry(HKEY root, const std::wstring& driver_name info.source = Utf8Encode(dkey.GetString(L"source", L"")); info.lib_path = std::filesystem::path(dkey.GetString(L"driver", L"")); if (info.lib_path.empty()) { - SetError(error, "Driver path not found in registry for \""s + - Utf8Encode(driver_name) + "\""); + std::string error_message = "SOFTWARE\\ADBC\\DRIVERS\\"s; + error_message += Utf8Encode(driver_name); + error_message += " has no driver path"s; + SetError(error, std::move(error_message)); return ADBC_STATUS_NOT_FOUND; } return ADBC_STATUS_OK; @@ -253,27 +318,73 @@ AdbcStatusCode LoadDriverManifest(const std::filesystem::path& driver_manifest, info.manifest_file = driver_manifest.string(); info.driver_name = config["name"].value_or(""s); - info.entrypoint = config.at_path("Driver.entrypoint").value_or(""s); info.version = config["version"].value_or(""s); info.source = config["source"].value_or(""s); + auto entrypoint = config.at_path("Driver.entrypoint"); + if (entrypoint) { + if (auto* ep = entrypoint.as_string()) { + info.entrypoint = ep->get(); + } else { + SetError(error, "Driver entrypoint not a string in manifest '"s + + driver_manifest.string() + "'"s); + return ADBC_STATUS_INVALID_ARGUMENT; + } + } + auto driver = config.at_path("Driver.shared"); if (toml::table* platforms = driver.as_table()) { - info.lib_path = platforms->at_path(adbc::CurrentArch()).value_or(""s); + auto view = platforms->at_path(adbc::CurrentArch()); + if (!view) { + std::string message = "Driver path not found in manifest '"; + message += driver_manifest.string(); + message += "' for current architecture '"; + message += adbc::CurrentArch(); + message += "'. Architectures found:"; + for (const auto& [key, val] : *platforms) { + message += " "; + message += key; + } + SetError(error, std::move(message)); + return ADBC_STATUS_NOT_FOUND; + } else if (auto* path = view.as_string()) { + if (path->get().empty()) { + std::string message = "Driver path is an empty string in manifest '"; + message += driver_manifest.string(); + message += "' for current architecture '"; + message += adbc::CurrentArch(); + message += "'"; + SetError(error, std::move(message)); + return ADBC_STATUS_INVALID_ARGUMENT; + } + + info.lib_path = path->get(); + return ADBC_STATUS_OK; + } else { + std::string message = "Driver path not found in manifest '"; + message += driver_manifest.string(); + message += "' for current architecture '"; + message += adbc::CurrentArch(); + message += "'. Value was not a string"; + SetError(error, std::move(message)); + return ADBC_STATUS_INVALID_ARGUMENT; + } + return ADBC_STATUS_OK; } else if (auto* path = driver.as_string()) { info.lib_path = path->get(); + if (info.lib_path.empty()) { + SetError(error, "Driver path is an empty string in manifest '"s + + driver_manifest.string() + "'"s); + return ADBC_STATUS_INVALID_ARGUMENT; + } + return ADBC_STATUS_OK; } - - if (info.lib_path.empty()) { - SetError(error, "Driver path not found in manifest '"s + driver_manifest.string() + - "' for current architecture '" + adbc::CurrentArch() + "'"); - return ADBC_STATUS_NOT_FOUND; - } - - return ADBC_STATUS_OK; + 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; } -std::vector<std::filesystem::path> GetEnvPaths(const char_type* env_var) { +SearchPaths GetEnvPaths(const char_type* env_var) { #ifdef _WIN32 size_t required_size; @@ -293,26 +404,33 @@ std::vector<std::filesystem::path> GetEnvPaths(const char_type* env_var) { } std::string path(path_var); #endif // _WIN32 - return InternalAdbcParsePath(path); + SearchPaths paths; + for (auto path : InternalAdbcParsePath(path)) { + paths.emplace_back(SearchPathSource::kEnv, path); + } + return paths; } -std::vector<std::filesystem::path> GetSearchPaths(const AdbcLoadFlags levels) { - std::vector<std::filesystem::path> paths; - if (levels & ADBC_LOAD_FLAG_SEARCH_ENV) { #ifdef _WIN32 - static const wchar_t* env_var = L"ADBC_DRIVER_PATH"; +static const wchar_t* kAdbcDriverPath = L"ADBC_DRIVER_PATH"; #else - static const char* env_var = "ADBC_DRIVER_PATH"; +static const char* kAdbcDriverPath = "ADBC_DRIVER_PATH"; #endif // _WIN32 + +SearchPaths GetSearchPaths(const AdbcLoadFlags levels) { + SearchPaths paths; + if (levels & ADBC_LOAD_FLAG_SEARCH_ENV) { // Check the ADBC_DRIVER_PATH environment variable - paths = GetEnvPaths(env_var); + paths = GetEnvPaths(kAdbcDriverPath); } if (levels & ADBC_LOAD_FLAG_SEARCH_USER) { // Check the user configuration directory std::filesystem::path user_config_dir = InternalAdbcUserConfigDir(); if (!user_config_dir.empty() && std::filesystem::exists(user_config_dir)) { - paths.push_back(user_config_dir); + paths.emplace_back(SearchPathSource::kUser, std::move(user_config_dir)); + } else { + paths.emplace_back(SearchPathSource::kDoesNotExist, std::move(user_config_dir)); } } @@ -324,12 +442,16 @@ std::vector<std::filesystem::path> GetSearchPaths(const AdbcLoadFlags levels) { const std::filesystem::path system_config_dir( "/Library/Application Support/ADBC/Drivers"); if (std::filesystem::exists(system_config_dir)) { - paths.push_back(system_config_dir); + paths.emplace_back(SearchPathSource::kSystem, std::move(system_config_dir)); + } else { + paths.emplace_back(SearchPathSource::kDoesNotExist, std::move(system_config_dir)); } #elif !defined(_WIN32) const std::filesystem::path system_config_dir("/etc/adbc/drivers"); if (std::filesystem::exists(system_config_dir)) { - paths.push_back(system_config_dir); + paths.emplace_back(SearchPathSource::kSystem, std::move(system_config_dir)); + } else { + paths.emplace_back(SearchPathSource::kDoesNotExist, std::move(system_config_dir)); } #endif // defined(__APPLE__) } @@ -365,11 +487,13 @@ struct ManagedLibrary { ~ManagedLibrary() { Release(); } void Release() { - // TODO(apache/arrow-adbc#204): causes tests to segfault - // Need to refcount the driver DLL; also, errors may retain a reference to - // release() from the DLL - how to handle this? + // TODO(apache/arrow-adbc#204): causes tests to segfault. Need to + // refcount the driver DLL; also, errors may retain a reference to + // release() from the DLL - how to handle this? It's unlikely we can + // actually do this - in general shared libraries are not safe to unload. } + /// \brief Resolve the driver name to a concrete location. AdbcStatusCode GetDriverInfo( const std::string_view driver_name, const AdbcLoadFlags load_options, const std::vector<std::filesystem::path>& additional_search_paths, DriverInfo& info, @@ -379,6 +503,7 @@ struct ManagedLibrary { return ADBC_STATUS_INVALID_ARGUMENT; } + // First try to treat the given driver name as a path to a manifest or shared library std::filesystem::path driver_path(driver_name); const bool allow_relative_paths = load_options & ADBC_LOAD_FLAG_ALLOW_RELATIVE_PATHS; if (driver_path.has_extension()) { @@ -393,14 +518,14 @@ struct ManagedLibrary { auto status = LoadDriverManifest(driver_path, info, error); if (status == ADBC_STATUS_OK) { - return Load(info.lib_path.c_str(), error); + return Load(info.lib_path.c_str(), {}, error); } return status; } // if the extension is not .toml, then just try to load the provided // path as if it was an absolute path to a driver library - return Load(driver_path.c_str(), error); + return Load(driver_path.c_str(), {}, error); } if (driver_path.is_absolute()) { @@ -410,14 +535,14 @@ struct ManagedLibrary { if (std::filesystem::exists(driver_path)) { auto status = LoadDriverManifest(driver_path, info, error); if (status == ADBC_STATUS_OK) { - return Load(info.lib_path.c_str(), error); + return Load(info.lib_path.c_str(), {}, error); } } driver_path.replace_extension(""); info.lib_path = driver_path; // otherwise just try to load the provided path as if it was an absolute path - return Load(driver_path.c_str(), error); + return Load(driver_path.c_str(), {}, error); } if (driver_path.has_extension()) { @@ -435,7 +560,7 @@ struct ManagedLibrary { #endif // defined(_WIN32) if (HasExtension(driver_path, kPlatformLibrarySuffix)) { info.lib_path = driver_path; - return Load(driver_path.c_str(), error); + return Load(driver_path.c_str(), {}, error); } SetError(error, "Driver name has unrecognized extension: " + @@ -448,10 +573,15 @@ struct ManagedLibrary { return FindDriver(driver_path, load_options, additional_search_paths, info, error); } - AdbcStatusCode SearchPaths(const std::filesystem::path& driver_path, - const std::vector<std::filesystem::path>& search_paths, - DriverInfo& info, struct AdbcError* error) { - for (const auto& search_path : search_paths) { + AdbcStatusCode SearchPathsForDriver(const std::filesystem::path& driver_path, + const SearchPaths& search_paths, DriverInfo& info, + struct AdbcError* error) { + for (const auto& [source, search_path] : search_paths) { + if (source == SearchPathSource::kRegistry || source == SearchPathSource::kUnset || + source == SearchPathSource::kDoesNotExist || + source == SearchPathSource::kDisabled) { + continue; + } std::filesystem::path full_path = search_path / driver_path; // check for toml first, then dll @@ -459,12 +589,12 @@ struct ManagedLibrary { if (std::filesystem::exists(full_path)) { auto status = LoadDriverManifest(full_path, info, nullptr); if (status == ADBC_STATUS_OK) { - return Load(info.lib_path.c_str(), error); + return Load(info.lib_path.c_str(), search_paths, error); } } full_path.replace_extension(""); // remove the .toml extension - auto status = Load(full_path.c_str(), nullptr); + auto status = Load(full_path.c_str(), search_paths, nullptr); if (status == ADBC_STATUS_OK) { return status; } @@ -482,12 +612,17 @@ struct ManagedLibrary { return ADBC_STATUS_INVALID_ARGUMENT; } + SearchPaths search_paths; { // First search the paths in the env var `ADBC_DRIVER_PATH`. // Then search the runtime application-defined additional search paths. - auto search_paths = GetSearchPaths(load_options & ADBC_LOAD_FLAG_SEARCH_ENV); - search_paths.insert(search_paths.end(), additional_search_paths.begin(), - additional_search_paths.end()); + search_paths = GetSearchPaths(load_options & ADBC_LOAD_FLAG_SEARCH_ENV); + if (search_paths.empty()) { + search_paths.emplace_back(SearchPathSource::kUnset, "ADBC_DRIVER_PATH"); + } + for (const auto& path : additional_search_paths) { + search_paths.emplace_back(SearchPathSource::kAdditional, path); + } #if ADBC_CONDA_BUILD // Then, if this is a conda build, search in the conda environment if @@ -501,13 +636,18 @@ struct ManagedLibrary { auto venv = GetEnvPaths(conda_name); if (!venv.empty()) { for (const auto& venv_path : venv) { - search_paths.push_back(venv_path / "etc" / "adbc" / "drivers"); + search_paths.emplace_back(SearchPathSource::kConda, + venv_path / "etc" / "adbc" / "drivers"); } } } +#else + if (load_options & ADBC_LOAD_FLAG_SEARCH_ENV) { + search_paths.emplace_back(SearchPathSource::kDisabled, "Conda prefix"); + } #endif // ADBC_CONDA_BUILD - auto status = SearchPaths(driver_path, search_paths, info, error); + auto status = SearchPathsForDriver(driver_path, search_paths, info, error); if (status == ADBC_STATUS_OK) { return status; } @@ -522,14 +662,23 @@ struct ManagedLibrary { auto status = LoadDriverFromRegistry(HKEY_CURRENT_USER, driver_path.native(), info, error); if (status == ADBC_STATUS_OK) { - return Load(info.lib_path.c_str(), error); + return Load(info.lib_path.c_str(), {}, error); + } + if (error && error->message) { + std::string message = "HKEY_CURRENT_USER\\"s; + message += error->message; + search_paths.emplace_back(SearchPathSource::kRegistry, std::move(message)); + } else { + search_paths.emplace_back(SearchPathSource::kRegistry, + "not found in HKEY_CURRENT_USER"); } - status = SearchPaths(driver_path, GetSearchPaths(ADBC_LOAD_FLAG_SEARCH_USER), info, - error); + auto user_paths = GetSearchPaths(ADBC_LOAD_FLAG_SEARCH_USER); + status = SearchPathsForDriver(driver_path, user_paths, info, error); if (status == ADBC_STATUS_OK) { return status; } + search_paths.insert(search_paths.end(), user_paths.begin(), user_paths.end()); } if (load_options & ADBC_LOAD_FLAG_SEARCH_SYSTEM) { @@ -537,33 +686,46 @@ struct ManagedLibrary { auto status = LoadDriverFromRegistry(HKEY_LOCAL_MACHINE, driver_path.native(), info, error); if (status == ADBC_STATUS_OK) { - return Load(info.lib_path.c_str(), error); + return Load(info.lib_path.c_str(), {}, error); + } + if (error && error->message) { + std::string message = "HKEY_LOCAL_MACHINE\\"s; + message += error->message; + search_paths.emplace_back(SearchPathSource::kRegistry, std::move(message)); + } else { + search_paths.emplace_back(SearchPathSource::kRegistry, + "not found in HKEY_LOCAL_MACHINE"); } - status = SearchPaths(driver_path, GetSearchPaths(ADBC_LOAD_FLAG_SEARCH_SYSTEM), - info, error); + auto system_paths = GetSearchPaths(ADBC_LOAD_FLAG_SEARCH_SYSTEM); + status = SearchPathsForDriver(driver_path, system_paths, info, error); if (status == ADBC_STATUS_OK) { return status; } + search_paths.insert(search_paths.end(), system_paths.begin(), system_paths.end()); } info.lib_path = driver_path; - return Load(driver_path.c_str(), error); + return Load(driver_path.c_str(), search_paths, error); #else // Otherwise, search the configured paths. - auto search_paths = GetSearchPaths(load_options & ~ADBC_LOAD_FLAG_SEARCH_ENV); - auto status = SearchPaths(driver_path, search_paths, info, error); + SearchPaths more_search_paths = + GetSearchPaths(load_options & ~ADBC_LOAD_FLAG_SEARCH_ENV); + auto status = SearchPathsForDriver(driver_path, more_search_paths, info, error); if (status == ADBC_STATUS_NOT_FOUND) { // If we reach here, we didn't find the driver in any of the paths // so let's just attempt to load it as default behavior. + search_paths.insert(search_paths.end(), more_search_paths.begin(), + more_search_paths.end()); info.lib_path = driver_path; - return Load(driver_path.c_str(), error); + return Load(driver_path.c_str(), search_paths, error); } return status; #endif // _WIN32 } - AdbcStatusCode Load(const char_type* library, struct AdbcError* error) { + AdbcStatusCode Load(const char_type* library, const SearchPaths& attempted_paths, + struct AdbcError* error) { std::string error_message; #if defined(_WIN32) HMODULE handle = LoadLibraryExW(library, NULL, 0); @@ -583,8 +745,9 @@ struct ManagedLibrary { } } if (!handle) { + AddSearchPathsToError(attempted_paths, error_message); SetError(error, error_message); - return ADBC_STATUS_INTERNAL; + return ADBC_STATUS_NOT_FOUND; } else { this->handle = handle; } @@ -628,8 +791,9 @@ struct ManagedLibrary { if (handle) { this->handle = handle; } else { + AddSearchPathsToError(attempted_paths, error_message); SetError(error, error_message); - return ADBC_STATUS_INTERNAL; + return ADBC_STATUS_NOT_FOUND; } #endif // defined(_WIN32) return ADBC_STATUS_OK; @@ -2223,7 +2387,6 @@ AdbcStatusCode AdbcFindLoadDriver(const char* driver_name, const char* entrypoin SetError(error, "Driver pointer is null"); return ADBC_STATUS_INVALID_ARGUMENT; } - auto* driver = reinterpret_cast<struct AdbcDriver*>(raw_driver); if (!driver_name) { SetError(error, "Driver name is null"); return ADBC_STATUS_INVALID_ARGUMENT; @@ -2240,6 +2403,8 @@ AdbcStatusCode AdbcFindLoadDriver(const char* driver_name, const char* entrypoin additional_paths = InternalAdbcParsePath(additional_search_path_list); } + auto* driver = reinterpret_cast<struct AdbcDriver*>(raw_driver); + AdbcStatusCode status = library.GetDriverInfo(driver_name, load_options, additional_paths, info, error); if (status != ADBC_STATUS_OK) { diff --git a/c/driver_manager/adbc_driver_manager_test.cc b/c/driver_manager/adbc_driver_manager_test.cc index aab045dd1..2b1c3c29c 100644 --- a/c/driver_manager/adbc_driver_manager_test.cc +++ b/c/driver_manager/adbc_driver_manager_test.cc @@ -597,7 +597,16 @@ TEST_F(DriverManifest, LoadRelativePath) { ASSERT_TRUE(std::filesystem::remove("sqlite.toml")); } -TEST_F(DriverManifest, ManifestMissingDriver) { +TEST_F(DriverManifest, NotFound) { + ASSERT_THAT(AdbcFindLoadDriver("nosuchdriver", nullptr, ADBC_VERSION_1_1_0, + ADBC_LOAD_FLAG_DEFAULT, nullptr, &driver, &error), + IsStatus(ADBC_STATUS_NOT_FOUND, &error)); + ASSERT_THAT(error.message, + ::testing::HasSubstr("Also searched these paths for manifests:\n\tnot " + "set: ADBC_DRIVER_PATH")); +} + +TEST_F(DriverManifest, ManifestDriverMissing) { // Create a manifest without the "Driver" section auto filepath = temp_dir / "sqlite.toml"; toml::table manifest_without_driver = simple_manifest; @@ -613,6 +622,54 @@ TEST_F(DriverManifest, ManifestMissingDriver) { ADBC_LOAD_FLAG_DEFAULT, nullptr, &driver, &error), IsStatus(ADBC_STATUS_NOT_FOUND, &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, ManifestDriverInvalid) { + // "Driver" section is not a table + 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", true}}); + + std::ofstream test_manifest_file(filepath); + ASSERT_TRUE(test_manifest_file.is_open()); + test_manifest_file << manifest_without_driver; + test_manifest_file.close(); + + // 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)); + + 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, ManifestDriverEmpty) { + // "Driver" section is not a table + 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", ""}}); + + std::ofstream test_manifest_file(filepath); + ASSERT_TRUE(test_manifest_file.is_open()); + test_manifest_file << manifest_without_driver; + test_manifest_file.close(); + + // 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_INVALID_ARGUMENT, &error)); + + ASSERT_THAT(error.message, + ::testing::HasSubstr("Driver path is an empty string in manifest")); ASSERT_TRUE(std::filesystem::remove(filepath)); } @@ -625,6 +682,7 @@ TEST_F(DriverManifest, ManifestWrongArch) { {"shared", toml::table{ {"non-existent", "path/to/bad/driver.so"}, + {"windows-alpha64", "path/to/bad/driver.so"}, }}, }); @@ -638,6 +696,95 @@ TEST_F(DriverManifest, ManifestWrongArch) { ADBC_LOAD_FLAG_DEFAULT, nullptr, &driver, &error), IsStatus(ADBC_STATUS_NOT_FOUND, &error)); + ASSERT_THAT(error.message, ::testing::HasSubstr("Driver path not found in manifest")); + ASSERT_THAT(error.message, + ::testing::HasSubstr("Architectures found: non-existent windows-alpha64")); + ASSERT_TRUE(std::filesystem::remove(filepath)); +} + +TEST_F(DriverManifest, ManifestArchPathEmpty) { + 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{ + {"linux_arm64", ""}, + {"linux_amd64", ""}, + {"macos_arm64", ""}, + {"macos_amd64", ""}, + {"windows_arm64", ""}, + {"windows_amd64", ""}, + }}, + }); + + std::ofstream test_manifest_file(filepath); + ASSERT_TRUE(test_manifest_file.is_open()); + test_manifest_file << manifest_without_driver; + test_manifest_file.close(); + + // 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_INVALID_ARGUMENT, &error)); + + ASSERT_THAT(error.message, + ::testing::HasSubstr("Driver path is an empty string in manifest")); + ASSERT_TRUE(std::filesystem::remove(filepath)); +} + +TEST_F(DriverManifest, ManifestArchPathInvalid) { + 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{ + {"linux_arm64", 42}, + {"linux_amd64", 42}, + {"macos_arm64", 42}, + {"macos_amd64", 42}, + {"windows_arm64", 42}, + {"windows_amd64", 42}, + }}, + }); + + std::ofstream test_manifest_file(filepath); + ASSERT_TRUE(test_manifest_file.is_open()); + test_manifest_file << manifest_without_driver; + test_manifest_file.close(); + + // 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_INVALID_ARGUMENT, &error)); + + ASSERT_THAT(error.message, ::testing::HasSubstr("Driver path not found in manifest")); + ASSERT_THAT(error.message, ::testing::HasSubstr("Value was not a string")); + ASSERT_TRUE(std::filesystem::remove(filepath)); +} + +TEST_F(DriverManifest, ManifestEntrypointInvalid) { + 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", "foobar"}, + {"entrypoint", 42}, + }); + + std::ofstream test_manifest_file(filepath); + ASSERT_TRUE(test_manifest_file.is_open()); + test_manifest_file << manifest_without_driver; + test_manifest_file.close(); + + // 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_INVALID_ARGUMENT, &error)); + + ASSERT_THAT(error.message, + ::testing::HasSubstr("Driver entrypoint not a string in manifest")); ASSERT_TRUE(std::filesystem::remove(filepath)); } @@ -715,4 +862,7 @@ TEST_F(DriverManifest, LoadSystemLevelManifest) { } #endif +// TEST CASES +// manifest not found on given paths + } // namespace adbc diff --git a/python/adbc_driver_manager/tests/test_dbapi.py b/python/adbc_driver_manager/tests/test_dbapi.py index 3c02da1e6..30cf8c2fb 100644 --- a/python/adbc_driver_manager/tests/test_dbapi.py +++ b/python/adbc_driver_manager/tests/test_dbapi.py @@ -482,7 +482,7 @@ def test_release(sqlite, op) -> None: def test_driver_path(): with pytest.raises( - dbapi.InternalError, + dbapi.ProgrammingError, match="(dlopen|LoadLibraryExW).*failed:", ): with dbapi.connect(driver=pathlib.Path("/tmp/thisdriverdoesnotexist")): diff --git a/python/adbc_driver_manager/tests/test_lowlevel.py b/python/adbc_driver_manager/tests/test_lowlevel.py index ac85e9489..e1cb503dc 100644 --- a/python/adbc_driver_manager/tests/test_lowlevel.py +++ b/python/adbc_driver_manager/tests/test_lowlevel.py @@ -468,7 +468,7 @@ def test_pycapsule(sqlite): def test_driver_path(): with pytest.raises( - adbc_driver_manager.InternalError, + adbc_driver_manager.ProgrammingError, match="(dlopen|LoadLibraryExW).*failed:", ): with adbc_driver_manager.AdbcDatabase(