diff --git a/c/driver_manager/CMakeLists.txt b/c/driver_manager/CMakeLists.txt index 21054eed6c..64ba340a3b 100644 --- a/c/driver_manager/CMakeLists.txt +++ b/c/driver_manager/CMakeLists.txt @@ -69,6 +69,11 @@ foreach(LIB_TARGET ${ADBC_LIBRARIES}) ${REPOSITORY_ROOT}/c/vendor ${REPOSITORY_ROOT}/c/driver) target_compile_definitions(${LIB_TARGET} PRIVATE ADBC_EXPORTING) + if("$ENV{CONDA_BUILD}" STREQUAL "1") + target_compile_definitions(${LIB_TARGET} PRIVATE ADBC_CONDA_BUILD=1) + else() + target_compile_definitions(${LIB_TARGET} PRIVATE ADBC_CONDA_BUILD=0) + endif() endforeach() if(ADBC_BUILD_TESTS) diff --git a/c/driver_manager/adbc_driver_manager.cc b/c/driver_manager/adbc_driver_manager.cc index 3044199f2e..29bb5bf4ae 100644 --- a/c/driver_manager/adbc_driver_manager.cc +++ b/c/driver_manager/adbc_driver_manager.cc @@ -159,7 +159,7 @@ std::wstring Utf8Decode(const std::string& str) { #else using char_type = char; -#endif +#endif // _WIN32 // Driver state struct DriverInfo { @@ -239,7 +239,7 @@ AdbcStatusCode LoadDriverFromRegistry(HKEY root, const std::wstring& driver_name } return ADBC_STATUS_OK; } -#endif +#endif // _WIN32 AdbcStatusCode LoadDriverManifest(const std::filesystem::path& driver_manifest, DriverInfo& info, struct AdbcError* error) { @@ -273,14 +273,39 @@ AdbcStatusCode LoadDriverManifest(const std::filesystem::path& driver_manifest, return ADBC_STATUS_OK; } +std::vector GetEnvPaths(const char_type* env_var) { +#ifdef _WIN32 + size_t required_size; + + _wgetenv_s(&required_size, NULL, 0, env_var); + if (required_size == 0) { + return {}; + } + + std::wstring path_var; + path_var.resize(required_size); + _wgetenv_s(&required_size, path_var.data(), required_size, env_var); + auto path = Utf8Encode(path_var); +#else + const char* path_var = std::getenv(env_var); + if (!path_var) { + return {}; + } + std::string path(path_var); +#endif // _WIN32 + return InternalAdbcParsePath(path); +} + std::vector GetSearchPaths(const AdbcLoadFlags levels) { std::vector paths; if (levels & ADBC_LOAD_FLAG_SEARCH_ENV) { +#ifdef _WIN32 + static const wchar_t* env_var = L"ADBC_CONFIG_PATH"; +#else + static const char* env_var = "ADBC_CONFIG_PATH"; +#endif // _WIN32 // Check the ADBC_CONFIG_PATH environment variable - const char* env_path = std::getenv("ADBC_CONFIG_PATH"); - if (env_path) { - paths = InternalAdbcParsePath(env_path); - } + paths = GetEnvPaths(env_var); } if (levels & ADBC_LOAD_FLAG_SEARCH_USER) { @@ -305,7 +330,7 @@ std::vector GetSearchPaths(const AdbcLoadFlags levels) { if (std::filesystem::exists(system_config_dir)) { paths.push_back(system_config_dir); } -#endif +#endif // defined(__APPLE__) } return paths; @@ -319,7 +344,7 @@ bool HasExtension(const std::filesystem::path& path, const std::string& ext) { _wcsnicmp(path_ext.data(), wext.data(), wext.size()) == 0; #else return path.extension() == ext; -#endif +#endif // _WIN32 } /// A driver DLL. @@ -344,9 +369,10 @@ struct ManagedLibrary { // release() from the DLL - how to handle this? } - AdbcStatusCode GetDriverInfo(const std::string_view driver_name, - const AdbcLoadFlags load_options, DriverInfo& info, - struct AdbcError* error) { + AdbcStatusCode GetDriverInfo( + const std::string_view driver_name, const AdbcLoadFlags load_options, + const std::vector& additional_search_paths, DriverInfo& info, + struct AdbcError* error) { if (driver_name.empty()) { SetError(error, "Driver name is empty"); return ADBC_STATUS_INVALID_ARGUMENT; @@ -405,7 +431,7 @@ struct ManagedLibrary { static const std::string kPlatformLibrarySuffix = ".dylib"; #else static const std::string kPlatformLibrarySuffix = ".so"; -#endif +#endif // defined(_WIN32) if (HasExtension(driver_path, kPlatformLibrarySuffix)) { info.lib_path = driver_path; return Load(driver_path.c_str(), error); @@ -418,7 +444,7 @@ struct ManagedLibrary { // not an absolute path, no extension. Let's search the configured paths // based on the options - return FindDriver(driver_path, load_options, info, error); + return FindDriver(driver_path, load_options, additional_search_paths, info, error); } AdbcStatusCode SearchPaths(const std::filesystem::path& driver_path, @@ -446,27 +472,52 @@ struct ManagedLibrary { return ADBC_STATUS_NOT_FOUND; } - AdbcStatusCode FindDriver(const std::filesystem::path& driver_path, - const AdbcLoadFlags load_options, DriverInfo& info, - struct AdbcError* error) { + AdbcStatusCode FindDriver( + const std::filesystem::path& driver_path, const AdbcLoadFlags load_options, + const std::vector& additional_search_paths, DriverInfo& info, + struct AdbcError* error) { if (driver_path.empty()) { SetError(error, "Driver path is empty"); return ADBC_STATUS_INVALID_ARGUMENT; } + { + // First search the paths in the env var `ADBC_CONFIG_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()); + +#if ADBC_CONDA_BUILD + // Then, if this is a conda build, search in the conda environment if + // it is activated. + if (load_options & ADBC_LOAD_FLAG_SEARCH_ENV) { #ifdef _WIN32 - // windows is slightly more complex since we also need to check registry keys - // so we can't just grab the search paths only. - if (load_options & ADBC_LOAD_FLAG_SEARCH_ENV) { - auto search_paths = GetSearchPaths(ADBC_LOAD_FLAG_SEARCH_ENV); + const wchar_t* conda_name = L"CONDA_PREFIX"; +#else + const char* conda_name = "CONDA_PREFIX"; +#endif // _WIN32 + auto venv = GetEnvPaths(conda_name); + if (!venv.empty()) { + for (const auto& venv_path : venv) { + search_paths.push_back(venv_path / "etc" / "adbc"); + } + } + } +#endif // ADBC_CONDA_BUILD + auto status = SearchPaths(driver_path, search_paths, info, error); if (status == ADBC_STATUS_OK) { return status; } } + // We searched environment paths and additional search paths (if they + // exist), so now search the rest. +#ifdef _WIN32 + // On Windows, check registry keys, not just search paths. if (load_options & ADBC_LOAD_FLAG_SEARCH_USER) { - // Check the user registry for the driver + // Check the user registry for the driver. auto status = LoadDriverFromRegistry(HKEY_CURRENT_USER, driver_path.native(), info, error); if (status == ADBC_STATUS_OK) { @@ -481,7 +532,7 @@ struct ManagedLibrary { } if (load_options & ADBC_LOAD_FLAG_SEARCH_SYSTEM) { - // Check the system registry for the driver + // Check the system registry for the driver. auto status = LoadDriverFromRegistry(HKEY_LOCAL_MACHINE, driver_path.native(), info, error); if (status == ADBC_STATUS_OK) { @@ -498,17 +549,17 @@ struct ManagedLibrary { info.lib_path = driver_path; return Load(driver_path.c_str(), error); #else - // Otherwise, search the configured paths - auto search_paths = GetSearchPaths(load_options); + // 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); 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 + // so let's just attempt to load it as default behavior. info.lib_path = driver_path; return Load(driver_path.c_str(), error); } return status; -#endif +#endif // _WIN32 } AdbcStatusCode Load(const char_type* library, struct AdbcError* error) { @@ -991,6 +1042,7 @@ struct TempDatabase { std::string entrypoint; AdbcDriverInitFunc init_func = nullptr; AdbcLoadFlags load_flags = ADBC_LOAD_FLAG_ALLOW_RELATIVE_PATHS; + std::string additional_search_path_list; }; /// Temporary state while the database is being configured. @@ -1043,7 +1095,7 @@ std::filesystem::path InternalAdbcUserConfigDir() { if (!config_dir.empty()) { config_dir /= "adbc"; } -#endif +#endif // defined(_WIN32) return config_dir; } @@ -1362,6 +1414,22 @@ AdbcStatusCode AdbcDriverManagerDatabaseSetLoadFlags(struct AdbcDatabase* databa return ADBC_STATUS_OK; } +AdbcStatusCode AdbcDriverManagerDatabaseSetAdditionalSearchPathList( + struct AdbcDatabase* database, const char* path_list, struct AdbcError* error) { + if (database->private_driver) { + SetError(error, "Cannot SetAdditionalSearchPathList after AdbcDatabaseInit"); + return ADBC_STATUS_INVALID_STATE; + } + + TempDatabase* args = reinterpret_cast(database->private_data); + if (path_list) { + args->additional_search_path_list.assign(path_list); + } else { + args->additional_search_path_list.clear(); + } + return ADBC_STATUS_OK; +} + AdbcStatusCode AdbcDriverManagerDatabaseSetInitFunc(struct AdbcDatabase* database, AdbcDriverInitFunc init_func, struct AdbcError* error) { @@ -1399,10 +1467,12 @@ AdbcStatusCode AdbcDatabaseInit(struct AdbcDatabase* database, struct AdbcError* } else if (!args->entrypoint.empty()) { status = AdbcFindLoadDriver(args->driver.c_str(), args->entrypoint.c_str(), ADBC_VERSION_1_1_0, args->load_flags, + args->additional_search_path_list.data(), database->private_driver, error); } else { - status = AdbcFindLoadDriver(args->driver.c_str(), nullptr, ADBC_VERSION_1_1_0, - args->load_flags, database->private_driver, error); + status = AdbcFindLoadDriver( + args->driver.c_str(), nullptr, ADBC_VERSION_1_1_0, args->load_flags, + args->additional_search_path_list.data(), database->private_driver, error); } if (status != ADBC_STATUS_OK) { @@ -2134,6 +2204,7 @@ const char* AdbcStatusCodeMessage(AdbcStatusCode code) { AdbcStatusCode AdbcFindLoadDriver(const char* driver_name, const char* entrypoint, const int version, const AdbcLoadFlags load_options, + const char* additional_search_path_list, void* raw_driver, struct AdbcError* error) { AdbcDriverInitFunc init_func = nullptr; std::string error_message; @@ -2163,7 +2234,13 @@ AdbcStatusCode AdbcFindLoadDriver(const char* driver_name, const char* entrypoin info.entrypoint = entrypoint; } - AdbcStatusCode status = library.GetDriverInfo(driver_name, load_options, info, error); + std::vector additional_paths; + if (additional_search_path_list) { + additional_paths = InternalAdbcParsePath(additional_search_path_list); + } + + AdbcStatusCode status = + library.GetDriverInfo(driver_name, load_options, additional_paths, info, error); if (status != ADBC_STATUS_OK) { driver->release = nullptr; return status; @@ -2205,7 +2282,8 @@ AdbcStatusCode AdbcLoadDriver(const char* driver_name, const char* entrypoint, // but don't enable searching for manifests by default. It will need to be explicitly // enabled by calling AdbcFindLoadDriver directly. return AdbcFindLoadDriver(driver_name, entrypoint, version, - ADBC_LOAD_FLAG_ALLOW_RELATIVE_PATHS, raw_driver, error); + ADBC_LOAD_FLAG_ALLOW_RELATIVE_PATHS, nullptr, raw_driver, + error); } AdbcStatusCode AdbcLoadDriverFromInitFunc(AdbcDriverInitFunc init_func, int version, diff --git a/c/driver_manager/adbc_driver_manager_test.cc b/c/driver_manager/adbc_driver_manager_test.cc index 4d3ec15a80..57fbfb20db 100644 --- a/c/driver_manager/adbc_driver_manager_test.cc +++ b/c/driver_manager/adbc_driver_manager_test.cc @@ -457,7 +457,7 @@ class DriverManifest : public ::testing::Test { TEST_F(DriverManifest, LoadDriverEnv) { ASSERT_THAT(AdbcFindLoadDriver("sqlite", nullptr, ADBC_VERSION_1_1_0, - ADBC_LOAD_FLAG_DEFAULT, &driver, &error), + ADBC_LOAD_FLAG_DEFAULT, nullptr, &driver, &error), Not(IsOkStatus(&error))); std::ofstream test_manifest_file(temp_dir / "sqlite.toml"); @@ -468,7 +468,7 @@ TEST_F(DriverManifest, LoadDriverEnv) { SetConfigPath(temp_dir.string().c_str()); ASSERT_THAT(AdbcFindLoadDriver("sqlite", nullptr, ADBC_VERSION_1_1_0, - ADBC_LOAD_FLAG_DEFAULT, &driver, &error), + ADBC_LOAD_FLAG_DEFAULT, nullptr, &driver, &error), IsOkStatus(&error)); ASSERT_TRUE(std::filesystem::remove(temp_dir / "sqlite.toml")); @@ -478,7 +478,7 @@ TEST_F(DriverManifest, LoadDriverEnv) { TEST_F(DriverManifest, LoadNonAsciiPath) { ASSERT_THAT(AdbcFindLoadDriver("sqlite", nullptr, ADBC_VERSION_1_1_0, - ADBC_LOAD_FLAG_DEFAULT, &driver, &error), + ADBC_LOAD_FLAG_DEFAULT, nullptr, &driver, &error), Not(IsOkStatus(&error))); #ifdef _WIN32 @@ -497,7 +497,7 @@ TEST_F(DriverManifest, LoadNonAsciiPath) { SetConfigPath(non_ascii_dir.string().c_str()); ASSERT_THAT(AdbcFindLoadDriver("sqlite", nullptr, ADBC_VERSION_1_1_0, - ADBC_LOAD_FLAG_DEFAULT, &driver, &error), + ADBC_LOAD_FLAG_DEFAULT, nullptr, &driver, &error), IsOkStatus(&error)); ASSERT_TRUE(std::filesystem::remove(non_ascii_dir / "sqlite.toml")); @@ -515,7 +515,7 @@ TEST_F(DriverManifest, DisallowEnvConfig) { auto load_options = ADBC_LOAD_FLAG_DEFAULT & ~ADBC_LOAD_FLAG_SEARCH_ENV; ASSERT_THAT(AdbcFindLoadDriver("sqlite", nullptr, ADBC_VERSION_1_1_0, load_options, - &driver, &error), + nullptr, &driver, &error), Not(IsOkStatus(&error))); ASSERT_TRUE(std::filesystem::remove(temp_dir / "sqlite.toml")); @@ -543,7 +543,7 @@ TEST_F(DriverManifest, ConfigEntrypoint) { test_manifest_file.close(); ASSERT_THAT(AdbcFindLoadDriver(filepath.string().data(), nullptr, ADBC_VERSION_1_1_0, - ADBC_LOAD_FLAG_DEFAULT, &driver, &error), + ADBC_LOAD_FLAG_DEFAULT, nullptr, &driver, &error), Not(IsOkStatus(&error))); ASSERT_TRUE(std::filesystem::remove(filepath)); @@ -557,7 +557,7 @@ TEST_F(DriverManifest, LoadAbsolutePath) { test_manifest_file.close(); ASSERT_THAT(AdbcFindLoadDriver(filepath.string().data(), nullptr, ADBC_VERSION_1_1_0, - ADBC_LOAD_FLAG_DEFAULT, &driver, &error), + ADBC_LOAD_FLAG_DEFAULT, nullptr, &driver, &error), IsOkStatus(&error)); ASSERT_TRUE(std::filesystem::remove(filepath)); @@ -573,7 +573,7 @@ TEST_F(DriverManifest, LoadAbsolutePathNoExtension) { auto noext = filepath; noext.replace_extension(); // Remove the .toml extension ASSERT_THAT(AdbcFindLoadDriver(noext.string().data(), nullptr, ADBC_VERSION_1_1_0, - ADBC_LOAD_FLAG_DEFAULT, &driver, &error), + ADBC_LOAD_FLAG_DEFAULT, nullptr, &driver, &error), IsOkStatus(&error)); ASSERT_TRUE(std::filesystem::remove(filepath)); @@ -585,13 +585,14 @@ TEST_F(DriverManifest, LoadRelativePath) { test_manifest_file << simple_manifest; test_manifest_file.close(); - ASSERT_THAT( - AdbcFindLoadDriver("sqlite.toml", nullptr, ADBC_VERSION_1_1_0, 0, &driver, &error), - IsStatus(ADBC_STATUS_INVALID_ARGUMENT, &error)); + ASSERT_THAT(AdbcFindLoadDriver("sqlite.toml", nullptr, ADBC_VERSION_1_1_0, 0, nullptr, + &driver, &error), + IsStatus(ADBC_STATUS_INVALID_ARGUMENT, &error)); - ASSERT_THAT(AdbcFindLoadDriver("sqlite.toml", nullptr, ADBC_VERSION_1_1_0, - ADBC_LOAD_FLAG_ALLOW_RELATIVE_PATHS, &driver, &error), - IsOkStatus(&error)); + ASSERT_THAT( + AdbcFindLoadDriver("sqlite.toml", nullptr, ADBC_VERSION_1_1_0, + ADBC_LOAD_FLAG_ALLOW_RELATIVE_PATHS, nullptr, &driver, &error), + IsOkStatus(&error)); ASSERT_TRUE(std::filesystem::remove("sqlite.toml")); } @@ -609,7 +610,7 @@ TEST_F(DriverManifest, ManifestMissingDriver) { // Attempt to load the driver ASSERT_THAT(AdbcFindLoadDriver(filepath.string().data(), nullptr, ADBC_VERSION_1_1_0, - ADBC_LOAD_FLAG_DEFAULT, &driver, &error), + ADBC_LOAD_FLAG_DEFAULT, nullptr, &driver, &error), IsStatus(ADBC_STATUS_NOT_FOUND, &error)); ASSERT_TRUE(std::filesystem::remove(filepath)); @@ -634,7 +635,7 @@ TEST_F(DriverManifest, ManifestWrongArch) { // Attempt to load the driver ASSERT_THAT(AdbcFindLoadDriver(filepath.string().data(), nullptr, ADBC_VERSION_1_1_0, - ADBC_LOAD_FLAG_DEFAULT, &driver, &error), + ADBC_LOAD_FLAG_DEFAULT, nullptr, &driver, &error), IsStatus(ADBC_STATUS_NOT_FOUND, &error)); ASSERT_TRUE(std::filesystem::remove(filepath)); @@ -645,7 +646,7 @@ TEST_F(DriverManifest, ManifestWrongArch) { #ifdef ADBC_DRIVER_MANAGER_TEST_MANIFEST_USER_LEVEL TEST_F(DriverManifest, LoadUserLevelManifest) { ASSERT_THAT(AdbcFindLoadDriver("adbc-test-sqlite", nullptr, ADBC_VERSION_1_1_0, - ADBC_LOAD_FLAG_DEFAULT, &driver, &error), + ADBC_LOAD_FLAG_DEFAULT, nullptr, &driver, &error), Not(IsOkStatus(&error))); auto user_config_dir = InternalAdbcUserConfigDir(); @@ -662,12 +663,12 @@ TEST_F(DriverManifest, LoadUserLevelManifest) { // fail to load if flag doesn't have ADBC_LOAD_FLAG_SEARCH_USER ASSERT_THAT(AdbcFindLoadDriver("adbc-test-sqlite", nullptr, ADBC_VERSION_1_1_0, 0, - &driver, &error), + nullptr, &driver, &error), Not(IsOkStatus(&error))); // succeed with default load options ASSERT_THAT(AdbcFindLoadDriver("adbc-test-sqlite", nullptr, ADBC_VERSION_1_1_0, - ADBC_LOAD_FLAG_DEFAULT, &driver, &error), + ADBC_LOAD_FLAG_DEFAULT, nullptr, &driver, &error), IsOkStatus(&error)); ASSERT_TRUE(std::filesystem::remove(user_config_dir / "adbc-test-sqlite.toml")); @@ -682,7 +683,7 @@ TEST_F(DriverManifest, LoadUserLevelManifest) { #ifdef ADBC_DRIVER_MANAGER_TEST_MANIFEST_SYSTEM_LEVEL TEST_F(DriverManifest, LoadSystemLevelManifest) { ASSERT_THAT(AdbcFindLoadDriver("adbc-test-sqlite", nullptr, ADBC_VERSION_1_1_0, - ADBC_LOAD_FLAG_DEFAULT, &driver, &error), + ADBC_LOAD_FLAG_DEFAULT, nullptr, &driver, &error), Not(IsOkStatus(&error))); auto system_config_dir = std::filesystem::path("/etc/adbc"); @@ -699,12 +700,12 @@ TEST_F(DriverManifest, LoadSystemLevelManifest) { // fail to load if flag doesn't have ADBC_LOAD_FLAG_SEARCH_SYSTEM ASSERT_THAT(AdbcFindLoadDriver("adbc-test-sqlite", nullptr, ADBC_VERSION_1_1_0, 0, - &driver, &error), + nullptr, &driver, &error), Not(IsOkStatus(&error))); // succeed with default load options ASSERT_THAT(AdbcFindLoadDriver("adbc-test-sqlite", nullptr, ADBC_VERSION_1_1_0, - ADBC_LOAD_FLAG_DEFAULT, &driver, &error), + ADBC_LOAD_FLAG_DEFAULT, nullptr, &driver, &error), IsOkStatus(&error)); ASSERT_TRUE(std::filesystem::remove(system_config_dir / "adbc-test-sqlite.toml")); diff --git a/c/include/arrow-adbc/adbc_driver_manager.h b/c/include/arrow-adbc/adbc_driver_manager.h index 9cd0fa1d0c..62591da4d6 100644 --- a/c/include/arrow-adbc/adbc_driver_manager.h +++ b/c/include/arrow-adbc/adbc_driver_manager.h @@ -80,10 +80,10 @@ AdbcStatusCode AdbcLoadDriver(const char* driver_name, const char* entrypoint, /// a shared library. Erroring if this fails. /// /// If the passed in driver_name does not have an extension and is not an absolute path: -/// - The load_options parameter will control whether the driver manager will search -/// the ADBC_CONFIG_PATH environment variable, the user configuration directory, and/or -/// the system level directory of /etc/adbc for either a manifest file or a shared -/// library. +/// - The load_options parameter will control whether the driver manager will search the +/// environment variable ADBC_CONFIG_PATH and (if built or installed with conda) the +/// conda environment, the user-level configuration, and/or the system-level +/// configuration for either a manifest file or a shared library. /// - For each path to be searched, it will first look for /.toml. If /// that file exists, it will attempt to parse the manifest and load the driver /// specified within it, erroring if this fails. @@ -101,12 +101,15 @@ AdbcStatusCode AdbcLoadDriver(const char* driver_name, const char* entrypoint, /// entrypoint based on the driver name. /// \param[in] version The ADBC revision to attempt to initialize. /// \param[in] load_options bit mask of AdbcLoadFlags to control the directories searched +/// \param[in] additional_search_path_list A list of additional paths to search for +/// delimited by the OS specific path list separator. /// \param[out] driver The table of function pointers to initialize /// \param[out] error An optional location to return an error message ADBC_EXPORT AdbcStatusCode AdbcFindLoadDriver(const char* driver_name, const char* entrypoint, const int version, const AdbcLoadFlags load_options, - void* driver, struct AdbcError* error); + const char* additional_search_path_list, void* driver, + struct AdbcError* error); /// \brief Common entry point for drivers via the driver manager. /// @@ -151,6 +154,23 @@ AdbcStatusCode AdbcDriverManagerDatabaseSetLoadFlags(struct AdbcDatabase* databa AdbcLoadFlags flags, struct AdbcError* error); +/// \brief Set an additional manifest search path list for the driver manager. +/// +/// This is an extension to the ADBC API. The driver manager shims +/// the AdbcDatabase* functions to allow you to specify the +/// driver/entrypoint dynamically. This function lets you explicitly +/// set a path list at runtime for additional paths to search when +/// looking for a driver manifest. While users can add additional +/// paths via the ADBC_CONFIG_PATH environment variable, this allows +/// an application to specify search paths at runtime which are not tied +/// to the load flags. +/// +/// Calling this function with NULL as the `path_list` will clear any +/// previously set additional search paths. +ADBC_EXPORT +AdbcStatusCode AdbcDriverManagerDatabaseSetAdditionalSearchPathList( + struct AdbcDatabase* database, const char* path_list, struct AdbcError* error); + /// \brief Get a human-friendly description of a status code. ADBC_EXPORT const char* AdbcStatusCodeMessage(AdbcStatusCode code); diff --git a/docs/source/format/driver_manifests.rst b/docs/source/format/driver_manifests.rst index 9dcdfcbf2e..9efd279ddb 100644 --- a/docs/source/format/driver_manifests.rst +++ b/docs/source/format/driver_manifests.rst @@ -351,7 +351,8 @@ to control which directories will be searched for manifests, with the behavior b The type :c:type:`AdbcLoadFlags` is a set of bitflags to control the directories to be searched. The flags are - * :c:macro:`ADBC_LOAD_FLAG_SEARCH_ENV` - search the environment variable ``ADBC_CONFIG_PATH`` + * :c:macro:`ADBC_LOAD_FLAG_SEARCH_ENV` - search the directory paths in the environment variable + ``ADBC_CONFIG_PATH`` and (when built or installed with conda) search in the conda environment * :c:macro:`ADBC_LOAD_FLAG_SEARCH_USER` - search the user configuration directory * :c:macro:`ADBC_LOAD_FLAG_SEARCH_SYSTEM` - search the system configuration directory * :c:macro:`ADBC_LOAD_FLAG_ALLOW_RELATIVE_PATHS` - allow a relative path to be provided @@ -364,7 +365,8 @@ to control which directories will be searched for manifests, with the behavior b The type ``GADBCLoadFlags`` is a set of bitflags to control the directories to be searched. The flags are - * ``GADBC_LOAD_SEARCH_ENV`` - search the environment variable ``ADBC_CONFIG_PATH`` + * ``GADBC_LOAD_SEARCH_ENV`` - search the directory paths in the environment variable + ``ADBC_CONFIG_PATH`` and (when built or installed with conda) search in the conda environment * ``GADBC_LOAD_FLAG_SEARCH_USER`` - search the user configuration directory * ``GADBC_LOAD_FLAG_SEARCH_SYSTEM`` - search the system configuration directory * ``GADBC_LOAD_FLAG_ALLOW_RELATIVE_PATHS`` - allow a relative path to be provided @@ -380,7 +382,8 @@ to control which directories will be searched for manifests, with the behavior b ``drivermgr.LoadFlagsOptionKey`` with the value being the ``strconv.Itoa`` of the flags you want to use when you call ``NewDatabase`` or ``NewDatabaseWithContext``. The flags are defined in the ``drivermgr`` package as constants: - * ``drivermgr.LoadFlagsSearchEnv`` - search the environment variable ``ADBC_CONFIG_PATH`` + * ``drivermgr.LoadFlagsSearchEnv`` - search the directory paths in the environment variable + ``ADBC_CONFIG_PATH`` * ``drivermgr.LoadFlagsSearchUser`` - search the user configuration directory * ``drivermgr.LoadFlagsSearchSystem`` - search the system configuration directory * ``drivermgr.LoadFlagsAllowRelativePaths`` - allow a relative path to be used @@ -403,7 +406,8 @@ to control which directories will be searched for manifests, with the behavior b The class ``ADBC::LoadFlags`` is a set of bitflags to control the directories to be searched. The flags are - * ``ADBC::LoadFlags::SEARCH_ENV`` - search the environment variable ``ADBC_CONFIG_PATH`` + * ``ADBC::LoadFlags::SEARCH_ENV`` - search the directory paths in the environment variable + ``ADBC_CONFIG_PATH`` and (when built or installed with conda) search in the conda environment * ``ADBC::LoadFlags::SEARCH_USER`` - search the user configuration directory * ``ADBC::LoadFlags::SEARCH_SYSTEM`` - search the system configuration directory * ``ADBC::LoadFlags::ALLOW_RELATIVE_PATHS`` - allow a relative path to be provided @@ -419,7 +423,8 @@ to control which directories will be searched for manifests, with the behavior b The ``ManagedDriver`` type has a method ``load_dynamic_from_name`` which takes an optional ``load_flags`` parameter. The flags as a ``u32`` with the type ``adbc_core::driver_manager::LoadFlags``, which has the following constants: - * ``LOAD_FLAG_SEARCH_ENV`` - search the environment variable ``ADBC_CONFIG_PATH`` + * ``LOAD_FLAG_SEARCH_ENV`` - search the directory paths in the environment variable + ``ADBC_CONFIG_PATH`` and (when built or installed with conda) search in the conda environment * ``LOAD_FLAG_SEARCH_USER`` - search the user configuration directory * ``LOAD_FLAG_SEARCH_SYSTEM`` - search the system configuration directory * ``LOAD_FLAG_ALLOW_RELATIVE_PATHS`` - allow a relative path to be used @@ -431,9 +436,15 @@ Unix-like Platforms For Unix-like platforms, (e.g. Linux, macOS), the driver manager will search the following directories based on the options provided, in the given order: -#. If the ``LOAD_FLAG_SEARCH_ENV`` load option is set, then the environment variable ``ADBC_CONFIG_PATH`` will be searched +#. If the ``LOAD_FLAG_SEARCH_ENV`` load option is set, then the paths in the environment variable ``ADBC_CONFIG_PATH`` will be searched - * ``ADBC_CONFIG_PATH`` is a colon-separated list of directories to search for ``${name}.toml`` + * ``ADBC_CONFIG_PATH`` is a colon-separated list of directories + +#. If additional search paths have been specified, those will be searched + + * The Python driver manager automatically adds ``$VIRTUAL_ENV/etc/adbc`` to the search paths when running in a ``venv`` virtual environment + +#. If the driver manager was built or installed with conda and the ``LOAD_FLAG_SEARCH_ENV`` load option is set, ``$CONDA_PREFIX/etc/adbc`` will be searched #. If the ``LOAD_FLAG_SEARCH_USER`` load option is set, then a user-level configuration directory will be searched @@ -452,9 +463,15 @@ Windows Things are slightly different on Windows, where the driver manager will also search for driver information in the registry just as would happen for ODBC drivers. The search for a manifest on Windows would be the following: -#. If the ``LOAD_FLAG_SEARCH_ENV`` load option is set, then the environment variable ``ADBC_CONFIG_PATH`` will be searched +#. If the ``LOAD_FLAG_SEARCH_ENV`` load option is set, then the paths in the environment variable ``ADBC_CONFIG_PATH`` will be searched + + * ``ADBC_CONFIG_PATH`` is a semicolon-separated list of directories + +#. If additional search paths have been specified, those will be searched + + * The Python driver manager automatically adds ``$VIRTUAL_ENV\etc\adbc`` to the search paths when running in a ``venv`` virtual environment - * ``ADBC_CONFIG_PATH`` is a semicolon-separated list of directories to search for ``${name}.toml`` +#. If the driver manager was built or installed with conda and the ``LOAD_FLAG_SEARCH_ENV`` load option is set, ``$CONDA_PREFIX\etc\adbc`` will be searched #. If the ``LOAD_FLAG_SEARCH_USER`` load option is set, then a user-level configuration is searched for @@ -467,7 +484,7 @@ would happen for ODBC drivers. The search for a manifest on Windows would be the * ``entrypoint`` - the entrypoint to use for the driver if a non-default entrypoint is needed * ``driver`` - the path to the driver shared library - * If no registry key is found, then the directory ``%LOCAL_APPDATA%\ADBC\drivers`` is searched for ``${name}.toml`` + * If no registry key is found, then the directory ``%LOCAL_APPDATA%\ADBC\drivers`` is searched #. If the ``LOAD_FLAG_SEARCH_SYSTEM`` load option is set, the driver manager will search for a system-level configuration diff --git a/glib/adbc-glib/database.h b/glib/adbc-glib/database.h index 85ae59e0de..8efe4b6cb2 100644 --- a/glib/adbc-glib/database.h +++ b/glib/adbc-glib/database.h @@ -27,9 +27,11 @@ G_BEGIN_DECLS /** * GADBCLoadFlags: - * @GADBC_LOAD_SEARCH_ENV: Check the ADBC_CONFIG_PATH environment variable. - * @GADBC_LOAD_SEARCH_USER: Check the user configuration directory. - * @GADBC_LOAD_SEARCH_SYSTEM: Check the system configuration directory. + * @GADBC_LOAD_SEARCH_ENV: Search the directory paths in the environment + * variable ADBC_CONFIG_PATH and (when built or installed with conda) + * search in the conda environment + * @GADBC_LOAD_SEARCH_USER: Search the user configuration directory. + * @GADBC_LOAD_SEARCH_SYSTEM: Search the system configuration directory. * @GADBC_LOAD_ALLOW_RELATIVE_PATHS: Allow relative driver paths. * * The flags are used by gadbc_database_set_load_flags(). diff --git a/go/adbc/drivermgr/adbc_driver_manager.cc b/go/adbc/drivermgr/adbc_driver_manager.cc index 3044199f2e..9f5cbdbc94 100644 --- a/go/adbc/drivermgr/adbc_driver_manager.cc +++ b/go/adbc/drivermgr/adbc_driver_manager.cc @@ -159,7 +159,7 @@ std::wstring Utf8Decode(const std::string& str) { #else using char_type = char; -#endif +#endif // _WIN32 // Driver state struct DriverInfo { @@ -239,7 +239,7 @@ AdbcStatusCode LoadDriverFromRegistry(HKEY root, const std::wstring& driver_name } return ADBC_STATUS_OK; } -#endif +#endif // _WIN32 AdbcStatusCode LoadDriverManifest(const std::filesystem::path& driver_manifest, DriverInfo& info, struct AdbcError* error) { @@ -273,14 +273,39 @@ AdbcStatusCode LoadDriverManifest(const std::filesystem::path& driver_manifest, return ADBC_STATUS_OK; } +std::vector GetEnvPaths(const char_type* env_var) { +#ifdef _WIN32 + size_t required_size; + + _wgetenv_s(&required_size, NULL, 0, env_var); + if (required_size == 0) { + return {}; + } + + std::wstring path_var; + path_var.resize(required_size); + _wgetenv_s(&required_size, path_var.data(), required_size, env_var); + auto path = Utf8Encode(path_var); +#else + const char* path_var = std::getenv(env_var); + if (!path_var) { + return {}; + } + std::string path(path_var); +#endif + return InternalAdbcParsePath(path); +} + std::vector GetSearchPaths(const AdbcLoadFlags levels) { std::vector paths; if (levels & ADBC_LOAD_FLAG_SEARCH_ENV) { +#ifdef _WIN32 + static const wchar_t* env_var = L"ADBC_CONFIG_PATH"; +#else + static const char* env_var = "ADBC_CONFIG_PATH"; +#endif // Check the ADBC_CONFIG_PATH environment variable - const char* env_path = std::getenv("ADBC_CONFIG_PATH"); - if (env_path) { - paths = InternalAdbcParsePath(env_path); - } + paths = GetEnvPaths(env_var); } if (levels & ADBC_LOAD_FLAG_SEARCH_USER) { @@ -305,7 +330,7 @@ std::vector GetSearchPaths(const AdbcLoadFlags levels) { if (std::filesystem::exists(system_config_dir)) { paths.push_back(system_config_dir); } -#endif +#endif // defined(__APPLE__) } return paths; @@ -319,7 +344,7 @@ bool HasExtension(const std::filesystem::path& path, const std::string& ext) { _wcsnicmp(path_ext.data(), wext.data(), wext.size()) == 0; #else return path.extension() == ext; -#endif +#endif // _WIN32 } /// A driver DLL. @@ -344,9 +369,10 @@ struct ManagedLibrary { // release() from the DLL - how to handle this? } - AdbcStatusCode GetDriverInfo(const std::string_view driver_name, - const AdbcLoadFlags load_options, DriverInfo& info, - struct AdbcError* error) { + AdbcStatusCode GetDriverInfo( + const std::string_view driver_name, const AdbcLoadFlags load_options, + const std::vector& additional_search_paths, DriverInfo& info, + struct AdbcError* error) { if (driver_name.empty()) { SetError(error, "Driver name is empty"); return ADBC_STATUS_INVALID_ARGUMENT; @@ -405,7 +431,7 @@ struct ManagedLibrary { static const std::string kPlatformLibrarySuffix = ".dylib"; #else static const std::string kPlatformLibrarySuffix = ".so"; -#endif +#endif // defined(_WIN32) if (HasExtension(driver_path, kPlatformLibrarySuffix)) { info.lib_path = driver_path; return Load(driver_path.c_str(), error); @@ -418,7 +444,7 @@ struct ManagedLibrary { // not an absolute path, no extension. Let's search the configured paths // based on the options - return FindDriver(driver_path, load_options, info, error); + return FindDriver(driver_path, load_options, additional_search_paths, info, error); } AdbcStatusCode SearchPaths(const std::filesystem::path& driver_path, @@ -446,27 +472,52 @@ struct ManagedLibrary { return ADBC_STATUS_NOT_FOUND; } - AdbcStatusCode FindDriver(const std::filesystem::path& driver_path, - const AdbcLoadFlags load_options, DriverInfo& info, - struct AdbcError* error) { + AdbcStatusCode FindDriver( + const std::filesystem::path& driver_path, const AdbcLoadFlags load_options, + const std::vector& additional_search_paths, DriverInfo& info, + struct AdbcError* error) { if (driver_path.empty()) { SetError(error, "Driver path is empty"); return ADBC_STATUS_INVALID_ARGUMENT; } + { + // First search the paths in the env var `ADBC_CONFIG_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()); + +#if ADBC_CONDA_BUILD + // Then, if this is a conda build, search in the conda environment if + // it is activated. + if (load_options & ADBC_LOAD_FLAG_SEARCH_ENV) { #ifdef _WIN32 - // windows is slightly more complex since we also need to check registry keys - // so we can't just grab the search paths only. - if (load_options & ADBC_LOAD_FLAG_SEARCH_ENV) { - auto search_paths = GetSearchPaths(ADBC_LOAD_FLAG_SEARCH_ENV); + const wchar_t* conda_name = L"CONDA_PREFIX"; +#else + const char* conda_name = "CONDA_PREFIX"; +#endif // _WIN32 + auto venv = GetEnvPaths(conda_name); + if (!venv.empty()) { + for (const auto& venv_path : venv) { + search_paths.push_back(venv_path / "etc" / "adbc"); + } + } + } +#endif // ADBC_CONDA_BUILD + auto status = SearchPaths(driver_path, search_paths, info, error); if (status == ADBC_STATUS_OK) { return status; } } + // We searched environment paths and additional search paths (if they + // exist), so now search the rest. +#ifdef _WIN32 + // On Windows, check registry keys, not just search paths. if (load_options & ADBC_LOAD_FLAG_SEARCH_USER) { - // Check the user registry for the driver + // Check the user registry for the driver. auto status = LoadDriverFromRegistry(HKEY_CURRENT_USER, driver_path.native(), info, error); if (status == ADBC_STATUS_OK) { @@ -481,7 +532,7 @@ struct ManagedLibrary { } if (load_options & ADBC_LOAD_FLAG_SEARCH_SYSTEM) { - // Check the system registry for the driver + // Check the system registry for the driver. auto status = LoadDriverFromRegistry(HKEY_LOCAL_MACHINE, driver_path.native(), info, error); if (status == ADBC_STATUS_OK) { @@ -498,17 +549,17 @@ struct ManagedLibrary { info.lib_path = driver_path; return Load(driver_path.c_str(), error); #else - // Otherwise, search the configured paths - auto search_paths = GetSearchPaths(load_options); + // 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); 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 + // so let's just attempt to load it as default behavior. info.lib_path = driver_path; return Load(driver_path.c_str(), error); } return status; -#endif +#endif // _WIN32 } AdbcStatusCode Load(const char_type* library, struct AdbcError* error) { @@ -991,6 +1042,7 @@ struct TempDatabase { std::string entrypoint; AdbcDriverInitFunc init_func = nullptr; AdbcLoadFlags load_flags = ADBC_LOAD_FLAG_ALLOW_RELATIVE_PATHS; + std::string additional_search_path_list; }; /// Temporary state while the database is being configured. @@ -1043,7 +1095,7 @@ std::filesystem::path InternalAdbcUserConfigDir() { if (!config_dir.empty()) { config_dir /= "adbc"; } -#endif +#endif // defined(_WIN32) return config_dir; } @@ -1362,6 +1414,22 @@ AdbcStatusCode AdbcDriverManagerDatabaseSetLoadFlags(struct AdbcDatabase* databa return ADBC_STATUS_OK; } +AdbcStatusCode AdbcDriverManagerDatabaseSetAdditionalSearchPathList( + struct AdbcDatabase* database, const char* path_list, struct AdbcError* error) { + if (database->private_driver) { + SetError(error, "Cannot SetAdditionalSearchPathList after AdbcDatabaseInit"); + return ADBC_STATUS_INVALID_STATE; + } + + TempDatabase* args = reinterpret_cast(database->private_data); + if (path_list) { + args->additional_search_path_list.assign(path_list); + } else { + args->additional_search_path_list.clear(); + } + return ADBC_STATUS_OK; +} + AdbcStatusCode AdbcDriverManagerDatabaseSetInitFunc(struct AdbcDatabase* database, AdbcDriverInitFunc init_func, struct AdbcError* error) { @@ -1399,10 +1467,12 @@ AdbcStatusCode AdbcDatabaseInit(struct AdbcDatabase* database, struct AdbcError* } else if (!args->entrypoint.empty()) { status = AdbcFindLoadDriver(args->driver.c_str(), args->entrypoint.c_str(), ADBC_VERSION_1_1_0, args->load_flags, + args->additional_search_path_list.data(), database->private_driver, error); } else { - status = AdbcFindLoadDriver(args->driver.c_str(), nullptr, ADBC_VERSION_1_1_0, - args->load_flags, database->private_driver, error); + status = AdbcFindLoadDriver( + args->driver.c_str(), nullptr, ADBC_VERSION_1_1_0, args->load_flags, + args->additional_search_path_list.data(), database->private_driver, error); } if (status != ADBC_STATUS_OK) { @@ -2134,6 +2204,7 @@ const char* AdbcStatusCodeMessage(AdbcStatusCode code) { AdbcStatusCode AdbcFindLoadDriver(const char* driver_name, const char* entrypoint, const int version, const AdbcLoadFlags load_options, + const char* additional_search_path_list, void* raw_driver, struct AdbcError* error) { AdbcDriverInitFunc init_func = nullptr; std::string error_message; @@ -2163,7 +2234,13 @@ AdbcStatusCode AdbcFindLoadDriver(const char* driver_name, const char* entrypoin info.entrypoint = entrypoint; } - AdbcStatusCode status = library.GetDriverInfo(driver_name, load_options, info, error); + std::vector additional_paths; + if (additional_search_path_list) { + additional_paths = InternalAdbcParsePath(additional_search_path_list); + } + + AdbcStatusCode status = + library.GetDriverInfo(driver_name, load_options, additional_paths, info, error); if (status != ADBC_STATUS_OK) { driver->release = nullptr; return status; @@ -2205,7 +2282,8 @@ AdbcStatusCode AdbcLoadDriver(const char* driver_name, const char* entrypoint, // but don't enable searching for manifests by default. It will need to be explicitly // enabled by calling AdbcFindLoadDriver directly. return AdbcFindLoadDriver(driver_name, entrypoint, version, - ADBC_LOAD_FLAG_ALLOW_RELATIVE_PATHS, raw_driver, error); + ADBC_LOAD_FLAG_ALLOW_RELATIVE_PATHS, nullptr, raw_driver, + error); } AdbcStatusCode AdbcLoadDriverFromInitFunc(AdbcDriverInitFunc init_func, int version, diff --git a/go/adbc/drivermgr/arrow-adbc/adbc_driver_manager.h b/go/adbc/drivermgr/arrow-adbc/adbc_driver_manager.h index 9cd0fa1d0c..62591da4d6 100644 --- a/go/adbc/drivermgr/arrow-adbc/adbc_driver_manager.h +++ b/go/adbc/drivermgr/arrow-adbc/adbc_driver_manager.h @@ -80,10 +80,10 @@ AdbcStatusCode AdbcLoadDriver(const char* driver_name, const char* entrypoint, /// a shared library. Erroring if this fails. /// /// If the passed in driver_name does not have an extension and is not an absolute path: -/// - The load_options parameter will control whether the driver manager will search -/// the ADBC_CONFIG_PATH environment variable, the user configuration directory, and/or -/// the system level directory of /etc/adbc for either a manifest file or a shared -/// library. +/// - The load_options parameter will control whether the driver manager will search the +/// environment variable ADBC_CONFIG_PATH and (if built or installed with conda) the +/// conda environment, the user-level configuration, and/or the system-level +/// configuration for either a manifest file or a shared library. /// - For each path to be searched, it will first look for /.toml. If /// that file exists, it will attempt to parse the manifest and load the driver /// specified within it, erroring if this fails. @@ -101,12 +101,15 @@ AdbcStatusCode AdbcLoadDriver(const char* driver_name, const char* entrypoint, /// entrypoint based on the driver name. /// \param[in] version The ADBC revision to attempt to initialize. /// \param[in] load_options bit mask of AdbcLoadFlags to control the directories searched +/// \param[in] additional_search_path_list A list of additional paths to search for +/// delimited by the OS specific path list separator. /// \param[out] driver The table of function pointers to initialize /// \param[out] error An optional location to return an error message ADBC_EXPORT AdbcStatusCode AdbcFindLoadDriver(const char* driver_name, const char* entrypoint, const int version, const AdbcLoadFlags load_options, - void* driver, struct AdbcError* error); + const char* additional_search_path_list, void* driver, + struct AdbcError* error); /// \brief Common entry point for drivers via the driver manager. /// @@ -151,6 +154,23 @@ AdbcStatusCode AdbcDriverManagerDatabaseSetLoadFlags(struct AdbcDatabase* databa AdbcLoadFlags flags, struct AdbcError* error); +/// \brief Set an additional manifest search path list for the driver manager. +/// +/// This is an extension to the ADBC API. The driver manager shims +/// the AdbcDatabase* functions to allow you to specify the +/// driver/entrypoint dynamically. This function lets you explicitly +/// set a path list at runtime for additional paths to search when +/// looking for a driver manifest. While users can add additional +/// paths via the ADBC_CONFIG_PATH environment variable, this allows +/// an application to specify search paths at runtime which are not tied +/// to the load flags. +/// +/// Calling this function with NULL as the `path_list` will clear any +/// previously set additional search paths. +ADBC_EXPORT +AdbcStatusCode AdbcDriverManagerDatabaseSetAdditionalSearchPathList( + struct AdbcDatabase* database, const char* path_list, struct AdbcError* error); + /// \brief Get a human-friendly description of a status code. ADBC_EXPORT const char* AdbcStatusCodeMessage(AdbcStatusCode code); diff --git a/python/adbc_driver_manager/adbc_driver_manager/_lib.pxd b/python/adbc_driver_manager/adbc_driver_manager/_lib.pxd index 013acf8e0f..a7b41e1ea5 100644 --- a/python/adbc_driver_manager/adbc_driver_manager/_lib.pxd +++ b/python/adbc_driver_manager/adbc_driver_manager/_lib.pxd @@ -298,3 +298,7 @@ cdef extern from "arrow-adbc/adbc_driver_manager.h": CAdbcDatabase* database, CAdbcLoadFlags flags, CAdbcError* error) + CAdbcStatusCode AdbcDriverManagerDatabaseSetAdditionalSearchPathList( + CAdbcDatabase* database, + const char* path_list, + CAdbcError* error) diff --git a/python/adbc_driver_manager/adbc_driver_manager/_lib.pyx b/python/adbc_driver_manager/adbc_driver_manager/_lib.pyx index 3da101ab41..c18cd62c1e 100644 --- a/python/adbc_driver_manager/adbc_driver_manager/_lib.pyx +++ b/python/adbc_driver_manager/adbc_driver_manager/_lib.pyx @@ -564,6 +564,15 @@ cdef class AdbcDatabase(_AdbcHandle): &self.database, c_key, c_value, &c_error) check_error(status, &c_error) + # check if we're running in a venv + if sys.prefix != sys.base_prefix: + # if we're in a venv, add the venv prefix to the search path list + status = AdbcDriverManagerDatabaseSetAdditionalSearchPathList( + &self.database, _to_bytes(os.path.join(sys.prefix, 'etc/adbc'), + "sys.prefix"), + &c_error) + check_error(status, &c_error) + with nogil: status = AdbcDatabaseInit(&self.database, &c_error) check_error(status, &c_error) diff --git a/r/adbcdrivermanager/R/driver_void.R b/r/adbcdrivermanager/R/driver_void.R index a2510841b2..23ea7c8658 100644 --- a/r/adbcdrivermanager/R/driver_void.R +++ b/r/adbcdrivermanager/R/driver_void.R @@ -96,11 +96,11 @@ adbc_driver <- function(x, entrypoint = NULL, ..., #' @export #' adbc_driver_load <- function(x, entrypoint, version, driver, error, - load_flags = adbc_load_flags()) { + load_flags = adbc_load_flags(), additional_search_path_list = NULL) { if (inherits(x, "adbc_driver_init_func")) { .Call(RAdbcLoadDriverFromInitFunc, x, version, driver, error) } else { - .Call(RAdbcLoadDriver, x, entrypoint, version, load_flags, driver, error) + .Call(RAdbcLoadDriver, x, entrypoint, version, load_flags, additional_search_path_list, driver, error) } } @@ -108,12 +108,13 @@ adbc_driver_load <- function(x, entrypoint, version, driver, error, #' #' Options that indicate where to look for driver manifests. Manifests #' (.toml files) can be installed at the system level, the user level, -#' and/or location(s) specified by the ADBC_CONFIG_PATH environment variable. -#' See the ADBC documentation for details regarding the locations of the -#' user and system paths on various platforms. +#' in location(s) specified by the ADBC_CONFIG_PATH environment variable, +#' and/or in a conda environment. See the ADBC documentation for details +#' regarding the locations of the user and system paths on various platforms. #' -#' @param search_env Search for manifest files in the directories specified by -#' the ADBC_CONFIG_PATH environment variable. +#' @param search_env Search for manifest files in the directories specified in +#' the ADBC_CONFIG_PATH environment variable and (when installed with conda) +#' in the conda environment. #' @param search_user Search for manifest files in the designated directory #' for user ADBC driver installs. #' @param search_system Search for manifest files in the designtaed directory diff --git a/r/adbcdrivermanager/man/adbc_driver_load.Rd b/r/adbcdrivermanager/man/adbc_driver_load.Rd index c721027c71..a154746ff2 100644 --- a/r/adbcdrivermanager/man/adbc_driver_load.Rd +++ b/r/adbcdrivermanager/man/adbc_driver_load.Rd @@ -10,8 +10,8 @@ adbc_driver_load( version, driver, error, - load_flags = adbc_load_flags() -) + load_flags = adbc_load_flags(), + additional_search_path_list = NULL) } \arguments{ \item{x, entrypoint}{An ADBC driver may be defined either as an init function @@ -26,6 +26,8 @@ must be an external pointer to a DL_FUNC with the type \item{error}{An external pointer to an \code{AdbcError} or NULL} \item{load_flags}{Integer flags generated by \code{\link[=adbc_load_flags]{adbc_load_flags()}}} + +\item{additional_search_path_list}{A path list of additional locations to search for driver manifests} } \value{ An integer ADBC status code diff --git a/r/adbcdrivermanager/man/adbc_load_flags.Rd b/r/adbcdrivermanager/man/adbc_load_flags.Rd index 6b49a5af84..4dec948f81 100644 --- a/r/adbcdrivermanager/man/adbc_load_flags.Rd +++ b/r/adbcdrivermanager/man/adbc_load_flags.Rd @@ -12,8 +12,9 @@ adbc_load_flags( ) } \arguments{ -\item{search_env}{Search for manifest files in the directories specified by -the ADBC_CONFIG_PATH environment variable.} +\item{search_env}{Search for manifest files in the directories specified in +the ADBC_CONFIG_PATH environment variable and (when installed with conda) +in the conda environment.} \item{search_user}{Search for manifest files in the designated directory for user ADBC driver installs.} @@ -30,7 +31,7 @@ An integer flag value for use in \code{adbc_driver()} \description{ Options that indicate where to look for driver manifests. Manifests (.toml files) can be installed at the system level, the user level, -and/or location(s) specified by the ADBC_CONFIG_PATH environment variable. -See the ADBC documentation for details regarding the locations of the -user and system paths on various platforms. +in location(s) specified by the ADBC_CONFIG_PATH environment variable, +and/or in a conda environment. See the ADBC documentation for details +regarding the locations of the user and system paths on various platforms. } diff --git a/r/adbcdrivermanager/src/Makevars b/r/adbcdrivermanager/src/Makevars index 6011ee79ee..c04d29064b 100644 --- a/r/adbcdrivermanager/src/Makevars +++ b/r/adbcdrivermanager/src/Makevars @@ -16,7 +16,8 @@ # under the License. CXX_STD = CXX17 -PKG_CPPFLAGS=-I../src/c/include -I../src/c -I../src/c/vendor -DADBC_EXPORT="" -D_LIBCPP_DISABLE_AVAILABILITY +CONDA_BUILD ?= "0" +PKG_CPPFLAGS=-I../src/c/include -I../src/c -I../src/c/vendor -DADBC_EXPORT="" -D_LIBCPP_DISABLE_AVAILABILITY -DADBC_CONDA_BUILD=$(CONDA_BUILD) OBJECTS = driver_test.o \ error.o \ diff --git a/r/adbcdrivermanager/src/Makevars.win b/r/adbcdrivermanager/src/Makevars.win index fe0f01afb2..688de0c2c5 100644 --- a/r/adbcdrivermanager/src/Makevars.win +++ b/r/adbcdrivermanager/src/Makevars.win @@ -16,7 +16,8 @@ # under the License. CXX_STD = CXX17 -PKG_CPPFLAGS=-I../src/c/include -I../src/c -I../src/c/vendor -DADBC_EXPORT="" +CONDA_BUILD ?= "0" +PKG_CPPFLAGS=-I../src/c/include -I../src/c -I../src/c/vendor -DADBC_EXPORT="" -DADBC_CONDA_BUILD=$(CONDA_BUILD) PKG_LIBS=-lshell32 -ladvapi32 -luuid OBJECTS = driver_test.o \ diff --git a/r/adbcdrivermanager/src/init.c b/r/adbcdrivermanager/src/init.c index fb2d1a6ae3..a062d71503 100644 --- a/r/adbcdrivermanager/src/init.c +++ b/r/adbcdrivermanager/src/init.c @@ -48,7 +48,8 @@ SEXP RAdbcStatementGetOptionDouble(SEXP statement_xptr, SEXP key_sexp, SEXP erro SEXP RAdbcCurrentArch(void); SEXP RAdbcAllocateDriver(void); SEXP RAdbcLoadDriver(SEXP driver_name_sexp, SEXP entrypoint_sexp, SEXP version_sexp, - SEXP load_flags_sexp, SEXP driver_sexp, SEXP error_sexp); + SEXP load_flags_sexp, SEXP additional_search_path_list_sexp, + SEXP driver_sexp, SEXP error_sexp); SEXP RAdbcLoadDriverFromInitFunc(SEXP driver_init_func_xptr, SEXP version_sexp, SEXP driver_sexp, SEXP error_sexp); SEXP RAdbcDatabaseNew(SEXP driver_init_func_xptr, SEXP load_flags_sexp); @@ -130,7 +131,7 @@ static const R_CallMethodDef CallEntries[] = { {"RAdbcStatementGetOptionDouble", (DL_FUNC)&RAdbcStatementGetOptionDouble, 3}, {"RAdbcCurrentArch", (DL_FUNC)&RAdbcCurrentArch, 0}, {"RAdbcAllocateDriver", (DL_FUNC)&RAdbcAllocateDriver, 0}, - {"RAdbcLoadDriver", (DL_FUNC)&RAdbcLoadDriver, 6}, + {"RAdbcLoadDriver", (DL_FUNC)&RAdbcLoadDriver, 7}, {"RAdbcLoadDriverFromInitFunc", (DL_FUNC)&RAdbcLoadDriverFromInitFunc, 4}, {"RAdbcDatabaseNew", (DL_FUNC)&RAdbcDatabaseNew, 2}, {"RAdbcMoveDatabase", (DL_FUNC)&RAdbcMoveDatabase, 1}, diff --git a/r/adbcdrivermanager/src/radbc.cc b/r/adbcdrivermanager/src/radbc.cc index c07ab1d075..3010c59643 100644 --- a/r/adbcdrivermanager/src/radbc.cc +++ b/r/adbcdrivermanager/src/radbc.cc @@ -117,12 +117,15 @@ extern "C" SEXP RAdbcAllocateDriver(void) { } extern "C" SEXP RAdbcLoadDriver(SEXP driver_name_sexp, SEXP entrypoint_sexp, - SEXP version_sexp, SEXP load_flags_sexp, SEXP driver_sexp, + SEXP version_sexp, SEXP load_flags_sexp, + SEXP additional_search_path_list_sexp, SEXP driver_sexp, SEXP error_sexp) { const char* driver_name = adbc_as_const_char(driver_name_sexp); const char* entrypoint = adbc_as_const_char(entrypoint_sexp, /*nullable*/ true); int version = adbc_as_int(version_sexp); int load_flags = adbc_as_int(load_flags_sexp); + const char* additional_search_path_list = + adbc_as_const_char(additional_search_path_list_sexp, /*nullable*/ true); if (TYPEOF(driver_sexp) != EXTPTRSXP) { Rf_error("driver must be an externalptr"); @@ -138,8 +141,8 @@ extern "C" SEXP RAdbcLoadDriver(SEXP driver_name_sexp, SEXP entrypoint_sexp, Rf_error("error must be an externalptr"); } - int status = - AdbcFindLoadDriver(driver_name, entrypoint, version, load_flags, driver, error); + int status = AdbcFindLoadDriver(driver_name, entrypoint, version, load_flags, + additional_search_path_list, driver, error); return Rf_ScalarInteger(status); } diff --git a/rust/driver_manager/build.rs b/rust/driver_manager/build.rs new file mode 100644 index 0000000000..ba20b61039 --- /dev/null +++ b/rust/driver_manager/build.rs @@ -0,0 +1,26 @@ +// 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. + +use std::env; + +fn main() { + println!("cargo::rustc-check-cfg=cfg(conda_build)"); + let is_conda_build = env::var("CONDA_BUILD").unwrap_or_default() == "1"; + if is_conda_build { + println!("cargo::rustc-cfg=conda_build"); + } +} diff --git a/rust/driver_manager/src/lib.rs b/rust/driver_manager/src/lib.rs index 21f08251a9..834aaf2aeb 100644 --- a/rust/driver_manager/src/lib.rs +++ b/rust/driver_manager/src/lib.rs @@ -310,6 +310,7 @@ impl ManagedDriver { entrypoint: Option<&[u8]>, version: AdbcVersion, load_flags: LoadFlags, + additional_search_paths: Option>, ) -> Result { let driver_path = Path::new(name.as_ref()); let allow_relative = load_flags & LOAD_FLAG_ALLOW_RELATIVE_PATHS != 0; @@ -334,7 +335,13 @@ impl ManagedDriver { Self::load_dynamic_from_filename(driver_path, entrypoint, version) } else { - Self::find_driver(driver_path, entrypoint, version, load_flags) + Self::find_driver( + driver_path, + entrypoint, + version, + load_flags, + additional_search_paths, + ) } } @@ -481,6 +488,7 @@ impl ManagedDriver { entrypoint: Option<&[u8]>, version: AdbcVersion, load_flags: LoadFlags, + additional_search_paths: Option>, ) -> Result { if load_flags & LOAD_FLAG_SEARCH_ENV != 0 { if let Ok(result) = Self::search_path_list( @@ -493,6 +501,29 @@ impl ManagedDriver { } } + // the logic we want is that we first search ADBC_CONFIG_PATH if set, + // then we search the additional search paths if they exist. Finally, + // we will search CONDA_PREFIX if built with conda_build before moving on. + if let Some(additional_search_paths) = additional_search_paths { + if let Ok(result) = + Self::search_path_list(driver_path, additional_search_paths, entrypoint, version) + { + return Ok(result); + } + } + + #[cfg(conda_build)] + if load_flags & LOAD_FLAG_SEARCH_ENV != 0 { + if let Some(conda_prefix) = env::var_os("CONDA_PREFIX") { + let conda_path = PathBuf::from(conda_prefix).join("etc").join("adbc"); + if let Ok(result) = + Self::search_path_list(driver_path, vec![conda_path], entrypoint, version) + { + return Ok(result); + } + } + } + if load_flags & LOAD_FLAG_SEARCH_USER != 0 { // first check registry for the driver, then check the user config path if let Ok(result) = load_driver_from_registry( @@ -542,13 +573,24 @@ impl ManagedDriver { entrypoint: Option<&[u8]>, version: AdbcVersion, load_flags: LoadFlags, + additional_search_paths: Option>, ) -> Result { - if let Ok(result) = Self::search_path_list( - driver_path, - get_search_paths(load_flags), - entrypoint, - version, - ) { + let mut path_list = get_search_paths(load_flags & LOAD_FLAG_SEARCH_ENV); + + if let Some(additional_search_paths) = additional_search_paths { + path_list.extend(additional_search_paths); + } + + #[cfg(conda_build)] + if load_flags & LOAD_FLAG_SEARCH_ENV != 0 { + if let Some(conda_prefix) = env::var_os("CONDA_PREFIX") { + let conda_path = PathBuf::from(conda_prefix).join("etc").join("adbc"); + path_list.push(conda_path); + } + } + + path_list.extend(get_search_paths(load_flags & !LOAD_FLAG_SEARCH_ENV)); + if let Ok(result) = Self::search_path_list(driver_path, path_list, entrypoint, version) { return Ok(result); } @@ -1949,6 +1991,7 @@ mod tests { None, AdbcVersion::V100, LOAD_FLAG_SEARCH_ENV, + None, ) .unwrap_err(); assert_eq!(err.status, Status::NotFound); @@ -1966,6 +2009,7 @@ mod tests { None, AdbcVersion::V100, LOAD_FLAG_SEARCH_ENV, + None, ) .unwrap(); }, @@ -1990,8 +2034,14 @@ mod tests { .unwrap(); with_var("ADBC_CONFIG_PATH", Some(&path_os_string), || { - ManagedDriver::load_from_name("sqlite", None, AdbcVersion::V100, LOAD_FLAG_SEARCH_ENV) - .unwrap(); + ManagedDriver::load_from_name( + "sqlite", + None, + AdbcVersion::V100, + LOAD_FLAG_SEARCH_ENV, + None, + ) + .unwrap(); }); tmp_dir @@ -2014,6 +2064,7 @@ mod tests { None, AdbcVersion::V100, LOAD_FLAG_SEARCH_ENV, + None, ) .unwrap(); }, @@ -2036,9 +2087,14 @@ mod tests { Some(manifest_path.parent().unwrap().as_os_str()), || { let load_flags = LOAD_FLAG_DEFAULT & !LOAD_FLAG_SEARCH_ENV; - let err = - ManagedDriver::load_from_name("sqlite", None, AdbcVersion::V100, load_flags) - .unwrap_err(); + let err = ManagedDriver::load_from_name( + "sqlite", + None, + AdbcVersion::V100, + load_flags, + None, + ) + .unwrap_err(); assert_eq!(err.status, Status::NotFound); }, ); @@ -2048,14 +2104,40 @@ mod tests { .expect("Failed to close/remove temporary directory"); } + #[test] + #[cfg_attr(not(feature = "driver_manager_test_lib"), ignore)] + fn test_load_additional_path() { + let p = PathBuf::from("majestik møøse/sqlite.toml"); + let (tmp_dir, manifest_path) = write_manifest_to_tempfile(p, simple_manifest()); + + ManagedDriver::load_from_name( + "sqlite", + None, + AdbcVersion::V100, + LOAD_FLAG_SEARCH_ENV, + Some(vec![manifest_path.parent().unwrap().to_path_buf()]), + ) + .unwrap(); + + tmp_dir + .close() + .expect("Failed to close/remove temporary directory"); + } + #[test] #[cfg_attr(not(feature = "driver_manager_test_lib"), ignore)] fn test_load_absolute_path() { let (tmp_dir, manifest_path) = write_manifest_to_tempfile(PathBuf::from("sqlite.toml"), simple_manifest()); - ManagedDriver::load_from_name(manifest_path, None, AdbcVersion::V100, LOAD_FLAG_DEFAULT) - .unwrap(); + ManagedDriver::load_from_name( + manifest_path, + None, + AdbcVersion::V100, + LOAD_FLAG_DEFAULT, + None, + ) + .unwrap(); tmp_dir .close() @@ -2069,8 +2151,14 @@ mod tests { write_manifest_to_tempfile(PathBuf::from("sqlite.toml"), simple_manifest()); manifest_path.set_extension(""); - ManagedDriver::load_from_name(manifest_path, None, AdbcVersion::V100, LOAD_FLAG_DEFAULT) - .unwrap(); + ManagedDriver::load_from_name( + manifest_path, + None, + AdbcVersion::V100, + LOAD_FLAG_DEFAULT, + None, + ) + .unwrap(); tmp_dir .close() @@ -2083,8 +2171,8 @@ mod tests { std::fs::write(PathBuf::from("sqlite.toml"), simple_manifest()) .expect("Failed to write driver manager manifest to file"); - let err = - ManagedDriver::load_from_name("sqlite.toml", None, AdbcVersion::V100, 0).unwrap_err(); + let err = ManagedDriver::load_from_name("sqlite.toml", None, AdbcVersion::V100, 0, None) + .unwrap_err(); assert_eq!(err.status, Status::InvalidArguments); ManagedDriver::load_from_name( @@ -2092,6 +2180,7 @@ mod tests { None, AdbcVersion::V100, LOAD_FLAG_ALLOW_RELATIVE_PATHS, + None, ) .unwrap(); @@ -2112,6 +2201,7 @@ mod tests { None, AdbcVersion::V100, LOAD_FLAG_DEFAULT, + None, ) .unwrap_err(); assert_eq!(err.status, Status::InvalidArguments); @@ -2143,6 +2233,7 @@ mod tests { None, AdbcVersion::V100, LOAD_FLAG_DEFAULT, + None, ) .unwrap_err(); assert_eq!(err.status, Status::InvalidArguments); @@ -2167,6 +2258,7 @@ mod tests { None, AdbcVersion::V110, LOAD_FLAG_DEFAULT, + None, ) .unwrap_err(); assert_eq!(err.status, Status::NotFound); @@ -2189,6 +2281,7 @@ mod tests { None, AdbcVersion::V110, LOAD_FLAG_DEFAULT & !LOAD_FLAG_SEARCH_USER, + None, ) .unwrap_err(); assert_eq!(err.status, Status::NotFound); @@ -2199,6 +2292,7 @@ mod tests { None, AdbcVersion::V110, LOAD_FLAG_SEARCH_USER, + None, ) .unwrap();