This is an automated email from the ASF dual-hosted git repository.
zeroshade 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 7f35429e5 feat(c/driver_manager): add connection profile interface
(#3876)
7f35429e5 is described below
commit 7f35429e502f3cbe8fd827e288c12dbd8f4cea9f
Author: Matt Topol <[email protected]>
AuthorDate: Fri Feb 13 12:41:35 2026 -0500
feat(c/driver_manager): add connection profile interface (#3876)
An attempt to implement the ideas as suggested in #3765 to provide the
ability to specify and load connection profiles to define options for
use by the driver manager.
This creates an `AdbcConnectionProfile` struct and defines an
`AdbcConnectionProfileProvider` function pointer typedef to allow for
customized management of profiles. This also implements a default
file-based profile provider as described in
https://github.com/apache/arrow-adbc/issues/3765#issuecomment-3635178411
which will be used if no custom provider has been set.
This allows easy expansion in the future for non-file-based connection
profile providers while still implementing the easier case of using
file-based profiles, including hierarchical specification for now. See
the documentation comments added to `adbc_driver_manager.h` for the full
description of the semantics and explanation.
---------
Co-authored-by: Ian Cook <[email protected]>
Co-authored-by: David Li <[email protected]>
---
c/driver_manager/adbc_driver_manager.cc | 582 +++++++++++++++++++-
c/driver_manager/adbc_driver_manager_test.cc | 513 +++++++++++++++++-
c/include/arrow-adbc/adbc.h | 5 +
c/include/arrow-adbc/adbc_driver_manager.h | 180 +++++++
docs/source/format/connection_profiles.rst | 589 +++++++++++++++++++++
docs/source/format/driver_manifests.rst | 7 +
docs/source/index.rst | 1 +
go/adbc/drivermgr/adbc_driver_manager.cc | 582 +++++++++++++++++++-
go/adbc/drivermgr/arrow-adbc/adbc.h | 5 +
go/adbc/drivermgr/arrow-adbc/adbc_driver_manager.h | 180 +++++++
10 files changed, 2608 insertions(+), 36 deletions(-)
diff --git a/c/driver_manager/adbc_driver_manager.cc
b/c/driver_manager/adbc_driver_manager.cc
index 9397b142f..15d98c00b 100644
--- a/c/driver_manager/adbc_driver_manager.cc
+++ b/c/driver_manager/adbc_driver_manager.cc
@@ -46,6 +46,8 @@
#include <cstdio>
#include <cstring>
#include <filesystem>
+#include <functional>
+#include <regex>
#include <string>
#include <unordered_map>
#include <utility>
@@ -65,6 +67,7 @@ std::filesystem::path InternalAdbcSystemConfigDir();
struct ParseDriverUriResult {
std::string_view driver;
std::optional<std::string_view> uri;
+ std::optional<std::string_view> profile;
};
ADBC_EXPORT
@@ -87,16 +90,32 @@ enum class SearchPathSource {
kOtherError,
};
+enum class SearchPathType {
+ kManifest,
+ kProfile,
+};
+
using SearchPaths = std::vector<std::pair<SearchPathSource,
std::filesystem::path>>;
-void AddSearchPathsToError(const SearchPaths& search_paths, std::string&
error_message) {
+void AddSearchPathsToError(const SearchPaths& search_paths, const
SearchPathType& type,
+ std::string& error_message) {
if (!search_paths.empty()) {
- error_message += "\nAlso searched these paths for manifests:";
+ error_message += "\nAlso searched these paths for";
+ if (type == SearchPathType::kManifest) {
+ error_message += " manifests:";
+ } else if (type == SearchPathType::kProfile) {
+ error_message += " profiles:";
+ }
+
for (const auto& [source, path] : search_paths) {
error_message += "\n\t";
switch (source) {
case SearchPathSource::kEnv:
- error_message += "ADBC_DRIVER_PATH: ";
+ if (type == SearchPathType::kManifest) {
+ error_message += "ADBC_DRIVER_PATH: ";
+ } else if (type == SearchPathType::kProfile) {
+ error_message += "ADBC_PROFILE_PATH: ";
+ }
break;
case SearchPathSource::kUser:
error_message += "user config dir: ";
@@ -234,7 +253,7 @@ void SetError(struct AdbcError* error, struct AdbcError*
src_error) {
if (src_error->message) {
size_t message_size = strlen(src_error->message);
- error->message = new char[message_size];
+ error->message = new char[message_size + 1]; // +1 to include null
std::memcpy(error->message, src_error->message, message_size);
error->message[message_size] = '\0';
} else {
@@ -398,6 +417,86 @@ AdbcStatusCode LoadDriverFromRegistry(HKEY root, const
std::wstring& driver_name
}
#endif // _WIN32
+#define CHECK_STATUS(EXPR) \
+ if (auto _status = (EXPR); _status != ADBC_STATUS_OK) { \
+ return _status; \
+ }
+
+AdbcStatusCode ProcessProfileValue(std::string_view value, std::string& out,
+ struct AdbcError* error) {
+ if (value.empty()) {
+ SetError(error, "Profile value is null");
+ return ADBC_STATUS_INVALID_ARGUMENT;
+ }
+
+ static const std::regex pattern(R"(\{\{\s*([^{}]*?)\s*\}\})");
+ auto end_of_last_match = value.begin();
+ auto begin = std::regex_iterator(value.begin(), value.end(), pattern);
+ auto end = decltype(begin){};
+ std::match_results<std::string_view::iterator>::difference_type
pos_last_match = 0;
+
+ out.resize(0);
+ for (auto itr = begin; itr != end; ++itr) {
+ auto match = *itr;
+ auto pos_match = match.position();
+ auto diff = pos_match - pos_last_match;
+ auto start_match = end_of_last_match;
+ std::advance(start_match, diff);
+ out.append(end_of_last_match, start_match);
+
+ const auto content = match[1].str();
+ if (content.rfind("env_var(", 0) != 0) {
+ SetError(error, "Unsupported interpolation type in profile value: " +
content);
+ return ADBC_STATUS_INVALID_ARGUMENT;
+ }
+
+ if (content[content.size() - 1] != ')') {
+ SetError(error, "Malformed env_var() profile value: missing closing
parenthesis");
+ return ADBC_STATUS_INVALID_ARGUMENT;
+ }
+
+ const auto env_var_name = content.substr(8, content.size() - 9);
+ if (env_var_name.empty()) {
+ SetError(error,
+ "Malformed env_var() profile value: missing environment
variable name");
+ return ADBC_STATUS_INVALID_ARGUMENT;
+ }
+
+#ifdef _WIN32
+ auto local_env_var = Utf8Decode(std::string(env_var_name));
+ DWORD required_size = GetEnvironmentVariableW(local_env_var.c_str(), NULL,
0);
+ if (required_size == 0) {
+ out = "";
+ return ADBC_STATUS_OK;
+ }
+
+ std::wstring wvalue;
+ wvalue.resize(required_size);
+ DWORD actual_size =
+ GetEnvironmentVariableW(local_env_var.c_str(), wvalue.data(),
required_size);
+ // remove null terminator
+ wvalue.resize(actual_size);
+ const auto env_var_value = Utf8Encode(wvalue);
+#else
+ const char* env_value = std::getenv(env_var_name.c_str());
+ if (!env_value) {
+ out = "";
+ return ADBC_STATUS_OK;
+ }
+ const auto env_var_value = std::string(env_value);
+#endif
+ out.append(env_var_value);
+
+ auto length_match = match.length();
+ pos_last_match = pos_match + length_match;
+ end_of_last_match = start_match;
+ std::advance(end_of_last_match, length_match);
+ }
+
+ out.append(end_of_last_match, value.end());
+ return ADBC_STATUS_OK;
+}
+
/// \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)
@@ -520,8 +619,10 @@ SearchPaths GetEnvPaths(const char_type* env_var) {
#ifdef _WIN32
static const wchar_t* kAdbcDriverPath = L"ADBC_DRIVER_PATH";
+static const wchar_t* kAdbcProfilePath = L"ADBC_PROFILE_PATH";
#else
static const char* kAdbcDriverPath = "ADBC_DRIVER_PATH";
+static const char* kAdbcProfilePath = "ADBC_PROFILE_PATH";
#endif // _WIN32
SearchPaths GetSearchPaths(const AdbcLoadFlags levels) {
@@ -728,7 +829,7 @@ struct ManagedLibrary {
extra_debug_info.end());
if (intermediate_error.error.message) {
std::string error_message = intermediate_error.error.message;
- AddSearchPathsToError(search_paths, error_message);
+ AddSearchPathsToError(search_paths, SearchPathType::kManifest,
error_message);
SetError(error, std::move(error_message));
}
return status;
@@ -947,7 +1048,7 @@ struct ManagedLibrary {
error_message += "\n";
error_message += message;
}
- AddSearchPathsToError(attempted_paths, error_message);
+ AddSearchPathsToError(attempted_paths, SearchPathType::kManifest,
error_message);
SetError(error, error_message);
return ADBC_STATUS_NOT_FOUND;
} else {
@@ -1000,7 +1101,7 @@ struct ManagedLibrary {
error_message += "\n";
error_message += message;
}
- AddSearchPathsToError(attempted_paths, error_message);
+ AddSearchPathsToError(attempted_paths, SearchPathType::kManifest,
error_message);
SetError(error, error_message);
return ADBC_STATUS_NOT_FOUND;
}
@@ -1042,6 +1143,263 @@ struct ManagedLibrary {
#endif // defined(_WIN32)
};
+struct FilesystemProfile {
+ std::filesystem::path path;
+ std::string driver;
+ std::unordered_map<std::string, std::string> options;
+ std::unordered_map<std::string, int64_t> int_options;
+ std::unordered_map<std::string, double> double_options;
+
+ std::vector<const char*> options_keys;
+ std::vector<const char*> options_values;
+
+ std::vector<const char*> int_option_keys;
+ std::vector<int64_t> int_option_values;
+
+ std::vector<const char*> double_option_keys;
+ std::vector<double> double_option_values;
+
+ void PopulateConnectionProfile(struct AdbcConnectionProfile* out) {
+ options_keys.reserve(options.size());
+ options_values.reserve(options.size());
+ for (const auto& [key, value] : options) {
+ options_keys.push_back(key.c_str());
+ options_values.push_back(value.c_str());
+ }
+
+ int_option_keys.reserve(int_options.size());
+ int_option_values.reserve(int_options.size());
+ for (const auto& [key, value] : int_options) {
+ int_option_keys.push_back(key.c_str());
+ int_option_values.push_back(value);
+ }
+
+ double_option_keys.reserve(double_options.size());
+ double_option_values.reserve(double_options.size());
+ for (const auto& [key, value] : double_options) {
+ double_option_keys.push_back(key.c_str());
+ double_option_values.push_back(value);
+ }
+
+ out->private_data = new FilesystemProfile(std::move(*this));
+ out->release = [](AdbcConnectionProfile* profile) {
+ if (!profile || !profile->private_data) {
+ return;
+ }
+
+ delete static_cast<FilesystemProfile*>(profile->private_data);
+ profile->private_data = nullptr;
+ profile->release = nullptr;
+ };
+
+ out->GetDriverName = [](AdbcConnectionProfile* profile, const char** out,
+ AdbcDriverInitFunc* init_func,
+ struct AdbcError* error) -> AdbcStatusCode {
+ if (!profile || !profile->private_data) {
+ SetError(error, "Invalid connection profile");
+ return ADBC_STATUS_INVALID_ARGUMENT;
+ }
+
+ auto* fs_profile =
static_cast<FilesystemProfile*>(profile->private_data);
+ *out = fs_profile->driver.c_str();
+ *init_func = nullptr;
+ return ADBC_STATUS_OK;
+ };
+
+ out->GetOptions = [](AdbcConnectionProfile* profile, const char*** keys,
+ const char*** values, size_t* num_options,
+ struct AdbcError* error) -> AdbcStatusCode {
+ if (!profile || !profile->private_data) {
+ SetError(error, "Invalid connection profile");
+ return ADBC_STATUS_INVALID_ARGUMENT;
+ }
+
+ if (!keys || !values || !num_options) {
+ SetError(error, "Output parameters cannot be null");
+ return ADBC_STATUS_INVALID_ARGUMENT;
+ }
+
+ auto* fs_profile =
static_cast<FilesystemProfile*>(profile->private_data);
+ *num_options = fs_profile->options.size();
+ *keys = fs_profile->options_keys.data();
+ *values = fs_profile->options_values.data();
+ return ADBC_STATUS_OK;
+ };
+
+ out->GetIntOptions = [](AdbcConnectionProfile* profile, const char*** keys,
+ const int64_t** values, size_t* num_options,
+ struct AdbcError* error) -> AdbcStatusCode {
+ if (!profile || !profile->private_data) {
+ SetError(error, "Invalid connection profile");
+ return ADBC_STATUS_INVALID_ARGUMENT;
+ }
+
+ if (!keys || !values || !num_options) {
+ SetError(error, "Output parameters cannot be null");
+ return ADBC_STATUS_INVALID_ARGUMENT;
+ }
+
+ auto* fs_profile =
static_cast<FilesystemProfile*>(profile->private_data);
+ *num_options = fs_profile->int_options.size();
+ *keys = fs_profile->int_option_keys.data();
+ *values = fs_profile->int_option_values.data();
+ return ADBC_STATUS_OK;
+ };
+
+ out->GetDoubleOptions = [](AdbcConnectionProfile* profile, const char***
keys,
+ const double** values, size_t* num_options,
+ struct AdbcError* error) -> AdbcStatusCode {
+ if (!profile || !profile->private_data) {
+ SetError(error, "Invalid connection profile");
+ return ADBC_STATUS_INVALID_ARGUMENT;
+ }
+
+ if (!keys || !values || !num_options) {
+ SetError(error, "Output parameters cannot be null");
+ return ADBC_STATUS_INVALID_ARGUMENT;
+ }
+
+ auto* fs_profile =
static_cast<FilesystemProfile*>(profile->private_data);
+ *num_options = fs_profile->double_options.size();
+ *keys = fs_profile->double_option_keys.data();
+ *values = fs_profile->double_option_values.data();
+ return ADBC_STATUS_OK;
+ };
+ }
+};
+
+struct ProfileVisitor {
+ FilesystemProfile& profile;
+ const std::filesystem::path& profile_path;
+ struct AdbcError* error;
+
+ bool VisitTable(const std::string& prefix, toml::table& table) {
+ for (const auto& [key, value] : table) {
+ if (auto* str = value.as_string()) {
+ profile.options[prefix + key.data()] = str->get();
+ } else if (auto* int_val = value.as_integer()) {
+ profile.int_options[prefix + key.data()] = int_val->get();
+ } else if (auto* double_val = value.as_floating_point()) {
+ profile.double_options[prefix + key.data()] = double_val->get();
+ } else if (auto* bool_val = value.as_boolean()) {
+ profile.options[prefix + key.data()] = bool_val->get() ? "true" :
"false";
+ } else if (value.is_table()) {
+ if (!VisitTable(prefix + key.data() + ".", *value.as_table())) {
+ return false;
+ }
+ } else {
+ std::string message = "Unsupported value type for key '" +
+ std::string(key.str()) + "' in profile '" +
+ profile_path.string() + "'";
+ SetError(error, std::move(message));
+ return false;
+ }
+ }
+ return !error->message;
+ }
+};
+
+AdbcStatusCode LoadProfileFile(const std::filesystem::path& profile_path,
+ FilesystemProfile& profile, struct AdbcError*
error) {
+ toml::table config;
+ try {
+ config = toml::parse_file(profile_path.native());
+ } catch (const toml::parse_error& err) {
+ std::string message = "Could not open profile. ";
+ message += err.what();
+ message += ". Profile: ";
+ message += profile_path.string();
+ SetError(error, std::move(message));
+ return ADBC_STATUS_INVALID_ARGUMENT;
+ }
+
+ profile.path = profile_path;
+ if (!config["version"].is_integer()) {
+ std::string message =
+ "Profile version is not an integer in profile '" +
profile_path.string() + "'";
+ SetError(error, std::move(message));
+ return ADBC_STATUS_INVALID_ARGUMENT;
+ }
+
+ const auto version = config["version"].value_or(int64_t(1));
+ switch (version) {
+ case 1:
+ break;
+ default: {
+ std::string message =
+ "Profile version '" + std::to_string(version) +
+ "' is not supported by this driver manager. Profile: " +
profile_path.string();
+ SetError(error, std::move(message));
+ return ADBC_STATUS_INVALID_ARGUMENT;
+ }
+ }
+
+ profile.driver = config["driver"].value_or(""s);
+
+ auto options = config.at_path("options");
+ if (!options.is_table()) {
+ std::string message =
+ "Profile options is not a table in profile '" + profile_path.string()
+ "'";
+ SetError(error, std::move(message));
+ return ADBC_STATUS_INVALID_ARGUMENT;
+ }
+
+ auto* options_table = options.as_table();
+ ProfileVisitor v{profile, profile_path, error};
+ if (!v.VisitTable("", *options_table)) {
+ return ADBC_STATUS_INVALID_ARGUMENT;
+ }
+
+ return ADBC_STATUS_OK;
+}
+
+SearchPaths GetProfileSearchPaths(const char* additional_search_path_list) {
+ SearchPaths search_paths;
+ {
+ std::vector<std::filesystem::path> additional_paths;
+ if (additional_search_path_list) {
+ additional_paths = InternalAdbcParsePath(additional_search_path_list);
+ }
+
+ for (const auto& path : additional_paths) {
+ search_paths.emplace_back(SearchPathSource::kAdditional, path);
+ }
+ }
+
+ {
+ auto env_paths = GetEnvPaths(kAdbcProfilePath);
+ search_paths.insert(search_paths.end(), env_paths.begin(),
env_paths.end());
+ }
+
+#if ADBC_CONDA_BUILD
+#ifdef _WIN32
+ const wchar_t* conda_name = L"CONDA_PREFIX";
+#else
+ const char* conda_name = "CONDA_PREFIX";
+#endif // _WIN32
+
+ auto venv = GetEnvPaths(conda_name);
+ for (const auto& [_, venv_path] : venv) {
+ search_paths.emplace_back(SearchPathSource::kConda,
+ venv_path / "etc" / "adbc" / "profiles");
+ }
+#else
+ search_paths.emplace_back(SearchPathSource::kDisabledAtCompileTime, "Conda
prefix");
+#endif // ADBC_CONDA_BUILD
+
+#ifdef _WIN32
+ const wchar_t* profiles_dir = L"Profiles";
+#elif defined(__APPLE__)
+ const char* profiles_dir = "Profiles";
+#else
+ const char* profiles_dir = "profiles";
+#endif // defined(_WIN32)
+
+ auto user_dir = InternalAdbcUserConfigDir().parent_path() / profiles_dir;
+ search_paths.emplace_back(SearchPathSource::kUser, user_dir);
+ return search_paths;
+}
+
/// Hold the driver DLL and the driver release callback in the driver struct.
struct ManagerDriverState {
// The original release callback
@@ -1417,6 +1775,7 @@ struct TempDatabase {
AdbcDriverInitFunc init_func = nullptr;
AdbcLoadFlags load_flags = ADBC_LOAD_FLAG_ALLOW_RELATIVE_PATHS;
std::string additional_search_path_list;
+ AdbcConnectionProfileProvider profile_provider = nullptr;
};
/// Temporary state while the database is being configured.
@@ -1425,14 +1784,14 @@ struct TempConnection {
std::unordered_map<std::string, std::string> bytes_options;
std::unordered_map<std::string, int64_t> int_options;
std::unordered_map<std::string, double> double_options;
+ AdbcConnectionProfile* connection_profile = nullptr;
};
static const char kDefaultEntrypoint[] = "AdbcDriverInit";
} // namespace
// Other helpers (intentionally not in an anonymous namespace so they can be
tested)
-ADBC_EXPORT
-std::filesystem::path InternalAdbcUserConfigDir() {
+ADBC_EXPORT std::filesystem::path InternalAdbcUserConfigDir() {
std::filesystem::path config_dir;
#if defined(_WIN32)
// SHGetFolderPath is just an alias to SHGetKnownFolderPath since Vista
@@ -1600,22 +1959,26 @@ std::optional<ParseDriverUriResult>
InternalAdbcParseDriverUri(std::string_view
std::string_view d = str.substr(0, pos);
if (str.size() <= pos + 1) {
- return ParseDriverUriResult{d, std::nullopt};
+ return ParseDriverUriResult{d, std::nullopt, std::nullopt};
}
#ifdef _WIN32
if (std::filesystem::exists(std::filesystem::path(str))) {
// No scheme, just a path
- return ParseDriverUriResult{str, std::nullopt};
+ return ParseDriverUriResult{str, std::nullopt, std::nullopt};
}
#endif
if (str[pos + 1] == '/') { // scheme is also driver
- return ParseDriverUriResult{d, str};
+ if (d == "profile" && str.size() > pos + 2) {
+ // found a profile URI "profile://"
+ return ParseDriverUriResult{"", std::nullopt, str.substr(pos + 3)};
+ }
+ return ParseDriverUriResult{d, str, std::nullopt};
}
// driver:scheme:.....
- return ParseDriverUriResult{d, str.substr(pos + 1)};
+ return ParseDriverUriResult{d, str.substr(pos + 1), std::nullopt};
}
// Direct implementations of API methods
@@ -1672,6 +2035,19 @@ AdbcStatusCode AdbcDatabaseNew(struct AdbcDatabase*
database, struct AdbcError*
return ADBC_STATUS_OK;
}
+AdbcStatusCode AdbcDriverManagerDatabaseSetProfileProvider(
+ struct AdbcDatabase* database, AdbcConnectionProfileProvider provider,
+ struct AdbcError* error) {
+ if (database->private_driver) {
+ SetError(error, "Cannot SetProfileProvider after AdbcDatabaseInit");
+ return ADBC_STATUS_INVALID_STATE;
+ }
+
+ TempDatabase* args = reinterpret_cast<TempDatabase*>(database->private_data);
+ args->profile_provider = provider;
+ return ADBC_STATUS_OK;
+}
+
AdbcStatusCode AdbcDatabaseGetOption(struct AdbcDatabase* database, const
char* key,
char* value, size_t* length,
struct AdbcError* error) {
@@ -1857,20 +2233,191 @@ AdbcStatusCode
AdbcDriverManagerDatabaseSetInitFunc(struct AdbcDatabase* databas
return ADBC_STATUS_OK;
}
+AdbcStatusCode AdbcProfileProviderFilesystem(const char* profile_name,
+ const char*
additional_search_path_list,
+ struct AdbcConnectionProfile* out,
+ struct AdbcError* error) {
+ if (profile_name == nullptr || strlen(profile_name) == 0) {
+ SetError(error, "Profile name is empty");
+ return ADBC_STATUS_INVALID_ARGUMENT;
+ }
+
+ if (!out) {
+ SetError(error, "Output profile is null");
+ return ADBC_STATUS_INVALID_ARGUMENT;
+ }
+
+ std::memset(out, 0, sizeof(*out));
+ std::filesystem::path profile_path(profile_name);
+ if (profile_path.has_extension()) {
+ if (HasExtension(profile_path, ".toml")) {
+ if (!std::filesystem::exists(profile_path)) {
+ SetError(error, "Profile file does not exist: " +
profile_path.string());
+ return ADBC_STATUS_NOT_FOUND;
+ }
+
+ FilesystemProfile profile;
+ CHECK_STATUS(LoadProfileFile(profile_path, profile, error));
+ profile.PopulateConnectionProfile(out);
+ return ADBC_STATUS_OK;
+ }
+ }
+
+ if (profile_path.is_absolute()) {
+ profile_path.replace_extension(".toml");
+
+ FilesystemProfile profile;
+ CHECK_STATUS(LoadProfileFile(profile_path, profile, error));
+ profile.PopulateConnectionProfile(out);
+ return ADBC_STATUS_OK;
+ }
+
+ SearchPaths search_paths =
GetProfileSearchPaths(additional_search_path_list);
+ SearchPaths extra_debug_info;
+ for (const auto& [source, search_path] : search_paths) {
+ if (source == SearchPathSource::kRegistry || source ==
SearchPathSource::kUnset ||
+ source == SearchPathSource::kDoesNotExist ||
+ source == SearchPathSource::kDisabledAtCompileTime ||
+ source == SearchPathSource::kDisabledAtRunTime ||
+ source == SearchPathSource::kOtherError) {
+ continue;
+ }
+
+ std::filesystem::path full_path = search_path / profile_path;
+ full_path.replace_extension(".toml");
+ if (std::filesystem::exists(full_path)) {
+ OwnedError intermediate_error;
+
+ FilesystemProfile profile;
+ auto status = LoadProfileFile(full_path, profile,
&intermediate_error.error);
+ if (status == ADBC_STATUS_OK) {
+ profile.PopulateConnectionProfile(out);
+ return ADBC_STATUS_OK;
+ } else if (status == ADBC_STATUS_INVALID_ARGUMENT) {
+ 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, SearchPathType::kProfile,
error_message);
+ SetError(error, std::move(error_message));
+ }
+ return status;
+ }
+
+ std::string message = "found ";
+ message += full_path.string();
+ message += " but: ";
+ if (intermediate_error.error.message) {
+ message += intermediate_error.error.message;
+ } else {
+ message += "could not load the profile";
+ }
+ extra_debug_info.emplace_back(SearchPathSource::kOtherError,
std::move(message));
+ }
+ }
+
+ search_paths.insert(search_paths.end(), extra_debug_info.begin(),
+ extra_debug_info.end());
+ std::string error_message = "Profile not found: " +
std::string(profile_name);
+ AddSearchPathsToError(search_paths, SearchPathType::kProfile, error_message);
+ SetError(error, std::move(error_message));
+ return ADBC_STATUS_NOT_FOUND;
+}
+
+struct ProfileGuard {
+ AdbcConnectionProfile profile;
+ explicit ProfileGuard() : profile{} {}
+ ~ProfileGuard() {
+ if (profile.release) {
+ profile.release(&profile);
+ }
+ }
+};
+
+AdbcStatusCode InternalInitializeProfile(TempDatabase* args,
+ const std::string_view profile,
+ struct AdbcError* error) {
+ if (!args->profile_provider) {
+ args->profile_provider = AdbcProfileProviderFilesystem;
+ }
+
+ ProfileGuard guard{};
+ CHECK_STATUS(args->profile_provider(
+ profile.data(), args->additional_search_path_list.c_str(),
&guard.profile, error));
+
+ const char* driver_name = nullptr;
+ AdbcDriverInitFunc init_func = nullptr;
+ CHECK_STATUS(
+ guard.profile.GetDriverName(&guard.profile, &driver_name, &init_func,
error));
+ if (driver_name != nullptr && strlen(driver_name) > 0) {
+ args->driver = driver_name;
+ }
+
+ if (init_func != nullptr) {
+ args->init_func = init_func;
+ }
+
+ const char** keys = nullptr;
+ const char** values = nullptr;
+ size_t num_options = 0;
+ const int64_t* int_values = nullptr;
+ const double* double_values = nullptr;
+
+ CHECK_STATUS(
+ guard.profile.GetOptions(&guard.profile, &keys, &values, &num_options,
error));
+ for (size_t i = 0; i < num_options; ++i) {
+ // use try_emplace so we only add the option if there isn't
+ // already an option with the same name
+ std::string processed;
+ CHECK_STATUS(ProcessProfileValue(values[i], processed, error));
+ args->options.try_emplace(keys[i], processed);
+ }
+
+ CHECK_STATUS(guard.profile.GetIntOptions(&guard.profile, &keys, &int_values,
+ &num_options, error));
+ for (size_t i = 0; i < num_options; ++i) {
+ // use try_emplace so we only add the option if there isn't
+ // already an option with the same name
+ args->int_options.try_emplace(keys[i], int_values[i]);
+ }
+
+ CHECK_STATUS(guard.profile.GetDoubleOptions(&guard.profile, &keys,
&double_values,
+ &num_options, error));
+ for (size_t i = 0; i < num_options; ++i) {
+ // use try_emplace so we only add the option if there isn't already an
option with the
+ // same name
+ args->double_options.try_emplace(keys[i], double_values[i]);
+ }
+
+ return ADBC_STATUS_OK;
+}
+
AdbcStatusCode AdbcDatabaseInit(struct AdbcDatabase* database, struct
AdbcError* error) {
if (!database->private_data) {
SetError(error, "Must call AdbcDatabaseNew before AdbcDatabaseInit");
return ADBC_STATUS_INVALID_STATE;
}
TempDatabase* args = reinterpret_cast<TempDatabase*>(database->private_data);
+ const auto profile_in_use = args->options.find("profile");
+ if (profile_in_use != args->options.end()) {
+ std::string_view profile = profile_in_use->second;
+ CHECK_STATUS(InternalInitializeProfile(args, profile, error));
+ args->options.erase("profile");
+ }
+
if (!args->init_func) {
const auto uri = args->options.find("uri");
if (args->driver.empty() && uri != args->options.end()) {
std::string owned_uri = uri->second;
auto result = InternalAdbcParseDriverUri(owned_uri);
- if (result && result->uri) {
- args->driver = std::string{result->driver};
- args->options["uri"] = std::string{*result->uri};
+ if (result) {
+ if (result->uri) {
+ args->driver = std::string{result->driver};
+ args->options["uri"] = std::string{*result->uri};
+ } else if (result->profile) {
+ args->options.erase("uri");
+ CHECK_STATUS(InternalInitializeProfile(args, *result->profile,
error));
+ }
}
} else if (!args->driver.empty() && uri == args->options.end()) {
std::string owned_driver = args->driver;
@@ -1879,6 +2426,8 @@ AdbcStatusCode AdbcDatabaseInit(struct AdbcDatabase*
database, struct AdbcError*
args->driver = std::string{result->driver};
if (result->uri) {
args->options["uri"] = std::string{*result->uri};
+ } else if (result->profile) {
+ CHECK_STATUS(InternalInitializeProfile(args, *result->profile,
error));
}
}
}
@@ -2217,6 +2766,7 @@ AdbcStatusCode AdbcConnectionInit(struct AdbcConnection*
connection,
SetError(error, "Database is not initialized");
return ADBC_STATUS_INVALID_ARGUMENT;
}
+
TempConnection* args =
reinterpret_cast<TempConnection*>(connection->private_data);
connection->private_data = nullptr;
std::unordered_map<std::string, std::string> options =
std::move(args->options);
diff --git a/c/driver_manager/adbc_driver_manager_test.cc
b/c/driver_manager/adbc_driver_manager_test.cc
index 64400a01b..fcd3a8d28 100644
--- a/c/driver_manager/adbc_driver_manager_test.cc
+++ b/c/driver_manager/adbc_driver_manager_test.cc
@@ -43,6 +43,7 @@ std::filesystem::path InternalAdbcUserConfigDir();
struct ParseDriverUriResult {
std::string_view driver;
std::optional<std::string_view> uri;
+ std::optional<std::string_view> profile;
};
std::optional<ParseDriverUriResult>
InternalAdbcParseDriverUri(std::string_view str);
@@ -624,11 +625,15 @@ TEST(AdbcDriverManagerInternal, InternalAdbcParsePath) {
TEST(AdbcDriverManagerInternal, InternalAdbcParseDriverUri) {
std::vector<std::pair<std::string, std::optional<ParseDriverUriResult>>>
uris = {
{"sqlite", std::nullopt},
- {"sqlite:", {{"sqlite", std::nullopt}}},
- {"sqlite:file::memory:", {{"sqlite", "file::memory:"}}},
- {"sqlite:file::memory:?cache=shared", {{"sqlite",
"file::memory:?cache=shared"}}},
+ {"sqlite:", {{"sqlite", std::nullopt, std::nullopt}}},
+ {"sqlite:file::memory:", {{"sqlite", "file::memory:", std::nullopt}}},
+ {"sqlite:file::memory:?cache=shared",
+ {{"sqlite", "file::memory:?cache=shared", std::nullopt}}},
{"postgresql://a:b@localhost:9999/nonexistent",
- {{"postgresql", "postgresql://a:b@localhost:9999/nonexistent"}}}};
+ {{"postgresql", "postgresql://a:b@localhost:9999/nonexistent",
std::nullopt}}},
+ {"profile://foo_prof", {{"", std::nullopt, "foo_prof"}}},
+ {"profile:///foo/bar/profile.toml", {{"", std::nullopt,
"/foo/bar/profile.toml"}}},
+ };
#ifdef _WIN32
auto temp_dir = std::filesystem::temp_directory_path() /
"adbc_driver_manager_tests";
@@ -1460,4 +1465,504 @@ TEST_F(DriverManifest, ControlCodes) {
}
}
+class ConnectionProfiles : public ::testing::Test {
+ public:
+ void SetUp() override {
+ std::memset(&driver, 0, sizeof(driver));
+ std::memset(&error, 0, sizeof(error));
+
+ temp_dir =
+ std::filesystem::temp_directory_path() /
"adbc_driver_manager_profile_test";
+ std::filesystem::create_directories(temp_dir);
+
+ simple_profile = toml::table{
+ {"version", 1},
+ {"driver", "adbc_driver_sqlite"},
+ {"options",
+ toml::table{
+ {"uri", "file::memory:"},
+ }},
+ };
+ }
+
+ void TearDown() override {
+ if (error.release) {
+ error.release(&error);
+ }
+
+ if (driver.release) {
+ ASSERT_THAT(driver.release(&driver, &error), IsOkStatus(&error));
+ ASSERT_EQ(driver.private_data, nullptr);
+ ASSERT_EQ(driver.private_manager, nullptr);
+ }
+
+ driver_path.clear();
+ if (std::filesystem::exists(temp_dir)) {
+ std::filesystem::remove_all(temp_dir);
+ }
+ }
+
+ protected:
+ void SetConfigPath(const char* path) {
+#ifdef _WIN32
+ int size_needed = MultiByteToWideChar(CP_UTF8, 0, path, -1, nullptr, 0);
+ std::wstring wpath(size_needed, 0);
+ MultiByteToWideChar(CP_UTF8, 0, path, -1, &wpath[0], size_needed);
+ ASSERT_TRUE(SetEnvironmentVariableW(L"ADBC_PROFILE_PATH", wpath.c_str()));
+#else
+ setenv("ADBC_PROFILE_PATH", path, 1);
+#endif
+ }
+
+ void UnsetConfigPath() { SetConfigPath(""); }
+
+ struct AdbcDriver driver = {};
+ struct AdbcError error = {};
+
+ std::filesystem::path temp_dir;
+ std::filesystem::path driver_path;
+ toml::table simple_profile;
+};
+
+TEST_F(ConnectionProfiles, SetProfileOption) {
+ auto filepath = temp_dir / "profile.toml";
+ toml::table profile = simple_profile;
+ std::ofstream test_manifest_file(filepath);
+ ASSERT_TRUE(test_manifest_file.is_open());
+ test_manifest_file << profile;
+ test_manifest_file.close();
+
+ adbc_validation::Handle<struct AdbcDatabase> database;
+
+ // absolute path to the profile
+ ASSERT_THAT(AdbcDatabaseNew(&database.value, &error), IsOkStatus(&error));
+ ASSERT_THAT(AdbcDatabaseSetOption(&database.value, "profile",
filepath.string().c_str(),
+ &error),
+ IsOkStatus(&error));
+ ASSERT_THAT(AdbcDatabaseInit(&database.value, &error), IsOkStatus(&error));
+ ASSERT_THAT(AdbcDatabaseRelease(&database.value, &error),
IsOkStatus(&error));
+
+ // inherit additional_search_path_list
+ ASSERT_THAT(AdbcDatabaseNew(&database.value, &error), IsOkStatus(&error));
+ ASSERT_THAT(AdbcDatabaseSetOption(&database.value, "profile", "profile",
&error),
+ IsOkStatus(&error));
+ ASSERT_THAT(AdbcDriverManagerDatabaseSetAdditionalSearchPathList(
+ &database.value, temp_dir.string().c_str(), &error),
+ IsOkStatus(&error));
+ ASSERT_THAT(AdbcDatabaseInit(&database.value, &error), IsOkStatus(&error));
+ ASSERT_THAT(AdbcDatabaseRelease(&database.value, &error),
IsOkStatus(&error));
+
+ // find profile by name using ADBC_PROFILE_PATH
+ SetConfigPath(temp_dir.string().c_str());
+ ASSERT_THAT(AdbcDatabaseNew(&database.value, &error), IsOkStatus(&error));
+ ASSERT_THAT(AdbcDatabaseSetOption(&database.value, "profile", "profile",
&error),
+ IsOkStatus(&error));
+ ASSERT_THAT(AdbcDatabaseInit(&database.value, &error), IsOkStatus(&error));
+ ASSERT_THAT(AdbcDatabaseRelease(&database.value, &error),
IsOkStatus(&error));
+ UnsetConfigPath();
+}
+
+TEST_F(ConnectionProfiles, HierarchicalProfile) {
+ auto filepath = temp_dir / "dev" / "profile.toml";
+ std::filesystem::create_directories(filepath.parent_path());
+ toml::table profile = simple_profile;
+ std::ofstream test_manifest_file(filepath);
+ ASSERT_TRUE(test_manifest_file.is_open());
+ test_manifest_file << profile;
+ test_manifest_file.close();
+
+ adbc_validation::Handle<struct AdbcDatabase> database;
+
+ SetConfigPath(temp_dir.string().c_str());
+ ASSERT_THAT(AdbcDatabaseNew(&database.value, &error), IsOkStatus(&error));
+ ASSERT_THAT(AdbcDatabaseSetOption(&database.value, "profile", "dev/profile",
&error),
+ IsOkStatus(&error));
+ ASSERT_THAT(AdbcDatabaseInit(&database.value, &error), IsOkStatus(&error));
+ ASSERT_THAT(AdbcDatabaseRelease(&database.value, &error),
IsOkStatus(&error));
+ UnsetConfigPath();
+}
+
+TEST_F(ConnectionProfiles, UriProfileOption) {
+ auto filepath = temp_dir / "profile.toml";
+ toml::table profile = simple_profile;
+ std::ofstream test_manifest_file(filepath);
+ ASSERT_TRUE(test_manifest_file.is_open());
+ test_manifest_file << profile;
+ test_manifest_file.close();
+
+ adbc_validation::Handle<struct AdbcDatabase> database;
+
+ // absolute path to the profile
+ ASSERT_THAT(AdbcDatabaseNew(&database.value, &error), IsOkStatus(&error));
+ ASSERT_THAT(AdbcDatabaseSetOption(&database.value, "uri",
+ ("profile://" +
filepath.string()).c_str(), &error),
+ IsOkStatus(&error));
+ ASSERT_THAT(AdbcDatabaseInit(&database.value, &error), IsOkStatus(&error));
+ ASSERT_THAT(AdbcDatabaseRelease(&database.value, &error),
IsOkStatus(&error));
+
+ // find profile by name using ADBC_PROFILE_PATH
+ SetConfigPath(temp_dir.string().c_str());
+ ASSERT_THAT(AdbcDatabaseNew(&database.value, &error), IsOkStatus(&error));
+ ASSERT_THAT(AdbcDatabaseSetOption(&database.value, "uri",
"profile://profile", &error),
+ IsOkStatus(&error));
+ ASSERT_THAT(AdbcDatabaseInit(&database.value, &error), IsOkStatus(&error));
+ ASSERT_THAT(AdbcDatabaseRelease(&database.value, &error),
IsOkStatus(&error));
+ UnsetConfigPath();
+}
+
+TEST_F(ConnectionProfiles, DriverProfileOption) {
+ auto filepath = temp_dir / "profile.toml";
+ toml::table profile = simple_profile;
+ std::ofstream test_manifest_file(filepath);
+ ASSERT_TRUE(test_manifest_file.is_open());
+ test_manifest_file << profile;
+ test_manifest_file.close();
+
+ adbc_validation::Handle<struct AdbcDatabase> database;
+
+ // absolute path to the profile
+ ASSERT_THAT(AdbcDatabaseNew(&database.value, &error), IsOkStatus(&error));
+ ASSERT_THAT(AdbcDatabaseSetOption(&database.value, "driver",
+ ("profile://" +
filepath.string()).c_str(), &error),
+ IsOkStatus(&error));
+ ASSERT_THAT(AdbcDatabaseInit(&database.value, &error), IsOkStatus(&error));
+ ASSERT_THAT(AdbcDatabaseRelease(&database.value, &error),
IsOkStatus(&error));
+
+ // find profile by name using ADBC_PROFILE_PATH
+ SetConfigPath(temp_dir.string().c_str());
+ ASSERT_THAT(AdbcDatabaseNew(&database.value, &error), IsOkStatus(&error));
+ ASSERT_THAT(
+ AdbcDatabaseSetOption(&database.value, "driver", "profile://profile",
&error),
+ IsOkStatus(&error));
+ ASSERT_THAT(AdbcDatabaseInit(&database.value, &error), IsOkStatus(&error));
+ ASSERT_THAT(AdbcDatabaseRelease(&database.value, &error),
IsOkStatus(&error));
+ UnsetConfigPath();
+}
+
+TEST_F(ConnectionProfiles, ExtraStringOption) {
+ auto filepath = temp_dir / "profile.toml";
+ toml::table profile = simple_profile;
+ profile["options"].as_table()->insert("foo", "bar");
+ std::ofstream test_manifest_file(filepath);
+ ASSERT_TRUE(test_manifest_file.is_open());
+ test_manifest_file << profile;
+ test_manifest_file.close();
+
+ adbc_validation::Handle<struct AdbcDatabase> database;
+
+ // find profile by name using ADBC_PROFILE_PATH
+ SetConfigPath(temp_dir.string().c_str());
+ ASSERT_THAT(AdbcDatabaseNew(&database.value, &error), IsOkStatus(&error));
+ ASSERT_THAT(AdbcDatabaseSetOption(&database.value, "profile", "profile",
&error),
+ IsOkStatus(&error));
+ ASSERT_THAT(AdbcDatabaseInit(&database.value, &error),
+ IsStatus(ADBC_STATUS_NOT_IMPLEMENTED, &error));
+ ASSERT_THAT(error.message, ::testing::HasSubstr("Unknown database option
foo='bar'"));
+ UnsetConfigPath();
+}
+
+TEST_F(ConnectionProfiles, ExtraIntOption) {
+ auto filepath = temp_dir / "profile.toml";
+ toml::table profile = simple_profile;
+ profile["options"].as_table()->insert("foo", int64_t(42));
+ std::ofstream test_manifest_file(filepath);
+ ASSERT_TRUE(test_manifest_file.is_open());
+ test_manifest_file << profile;
+ test_manifest_file.close();
+
+ adbc_validation::Handle<struct AdbcDatabase> database;
+
+ // find profile by name using ADBC_PROFILE_PATH
+ SetConfigPath(temp_dir.string().c_str());
+ ASSERT_THAT(AdbcDatabaseNew(&database.value, &error), IsOkStatus(&error));
+ ASSERT_THAT(AdbcDatabaseSetOption(&database.value, "profile", "profile",
&error),
+ IsOkStatus(&error));
+ ASSERT_THAT(AdbcDatabaseInit(&database.value, &error),
+ IsStatus(ADBC_STATUS_NOT_IMPLEMENTED, &error));
+ ASSERT_THAT(error.message, ::testing::HasSubstr("Unknown database option
foo=42"));
+ UnsetConfigPath();
+}
+
+TEST_F(ConnectionProfiles, ExtraDoubleOption) {
+ auto filepath = temp_dir / "profile.toml";
+ toml::table profile = simple_profile;
+ profile["options"].as_table()->insert("foo", 42.0);
+ std::ofstream test_manifest_file(filepath);
+ ASSERT_TRUE(test_manifest_file.is_open());
+ test_manifest_file << profile;
+ test_manifest_file.close();
+
+ adbc_validation::Handle<struct AdbcDatabase> database;
+
+ // find profile by name using ADBC_PROFILE_PATH
+ SetConfigPath(temp_dir.string().c_str());
+ ASSERT_THAT(AdbcDatabaseNew(&database.value, &error), IsOkStatus(&error));
+ ASSERT_THAT(AdbcDatabaseSetOption(&database.value, "profile", "profile",
&error),
+ IsOkStatus(&error));
+ ASSERT_THAT(AdbcDatabaseInit(&database.value, &error),
+ IsStatus(ADBC_STATUS_NOT_IMPLEMENTED, &error));
+ ASSERT_THAT(error.message, ::testing::HasSubstr("Unknown database option
foo=42"));
+ UnsetConfigPath();
+}
+
+TEST_F(ConnectionProfiles, DotSeparatedKey) {
+ auto filepath = temp_dir / "profile.toml";
+ toml::table profile = toml::parse(R"(
+ version = 1
+ driver = "adbc_driver_sqlite"
+ [options]
+ foo.bar.baz = "bar"
+ )");
+
+ std::ofstream test_manifest_file(filepath);
+ ASSERT_TRUE(test_manifest_file.is_open());
+ test_manifest_file << profile;
+ test_manifest_file.close();
+
+ adbc_validation::Handle<struct AdbcDatabase> database;
+
+ // find profile by name using ADBC_PROFILE_PATH
+ SetConfigPath(temp_dir.string().c_str());
+ ASSERT_THAT(AdbcDatabaseNew(&database.value, &error), IsOkStatus(&error));
+ ASSERT_THAT(AdbcDatabaseSetOption(&database.value, "profile", "profile",
&error),
+ IsOkStatus(&error));
+ ASSERT_THAT(AdbcDatabaseInit(&database.value, &error),
+ IsStatus(ADBC_STATUS_NOT_IMPLEMENTED, &error));
+ ASSERT_THAT(error.message,
+ ::testing::HasSubstr("Unknown database option
foo.bar.baz='bar'"));
+ UnsetConfigPath();
+}
+
+TEST_F(ConnectionProfiles, UseEnvVar) {
+ auto filepath = temp_dir / "profile.toml";
+ toml::table profile = toml::parse(R"|(
+ version = 1
+ driver = "adbc_driver_sqlite"
+ [options]
+ foo = "{{ env_var(ADBC_PROFILE_PATH) }}"
+ )|");
+
+ std::ofstream test_manifest_file(filepath);
+ ASSERT_TRUE(test_manifest_file.is_open());
+ test_manifest_file << profile;
+ test_manifest_file.close();
+
+ adbc_validation::Handle<struct AdbcDatabase> database;
+
+ // find profile by name using ADBC_PROFILE_PATH
+ SetConfigPath(temp_dir.string().c_str());
+ ASSERT_THAT(AdbcDatabaseNew(&database.value, &error), IsOkStatus(&error));
+ ASSERT_THAT(AdbcDatabaseSetOption(&database.value, "profile", "profile",
&error),
+ IsOkStatus(&error));
+ ASSERT_THAT(AdbcDatabaseInit(&database.value, &error),
+ IsStatus(ADBC_STATUS_NOT_IMPLEMENTED, &error));
+ ASSERT_THAT(error.message, ::testing::HasSubstr("Unknown database option
foo='" +
+ temp_dir.string() + "'"));
+ UnsetConfigPath();
+}
+
+TEST_F(ConnectionProfiles, UseEnvVarNotExist) {
+ auto filepath = temp_dir / "profile.toml";
+ toml::table profile = toml::parse(R"|(
+ version = 1
+ driver = "adbc_driver_sqlite"
+ [options]
+ foo = "{{ env_var(FOOBAR_ENV_VAR_THAT_DOES_NOT_EXIST) }}"
+ )|");
+
+ std::ofstream test_manifest_file(filepath);
+ ASSERT_TRUE(test_manifest_file.is_open());
+ test_manifest_file << profile;
+ test_manifest_file.close();
+
+ adbc_validation::Handle<struct AdbcDatabase> database;
+
+ // find profile by name using ADBC_PROFILE_PATH
+ SetConfigPath(temp_dir.string().c_str());
+ ASSERT_THAT(AdbcDatabaseNew(&database.value, &error), IsOkStatus(&error));
+ ASSERT_THAT(AdbcDatabaseSetOption(&database.value, "profile", "profile",
&error),
+ IsOkStatus(&error));
+ ASSERT_THAT(AdbcDatabaseInit(&database.value, &error),
+ IsStatus(ADBC_STATUS_NOT_IMPLEMENTED, &error));
+ ASSERT_THAT(error.message, ::testing::HasSubstr("Unknown database option
foo=''"));
+ UnsetConfigPath();
+}
+
+TEST_F(ConnectionProfiles, UseEnvVarMalformed) {
+ auto filepath = temp_dir / "profile.toml";
+ toml::table profile = toml::parse(R"|(
+ version = 1
+ driver = "adbc_driver_sqlite"
+ [options]
+ foo = "{{ env_var(ENV_VAR_WITHOUT_CLOSING_PAREN }}"
+ )|");
+
+ std::ofstream test_manifest_file(filepath);
+ ASSERT_TRUE(test_manifest_file.is_open());
+ test_manifest_file << profile;
+ test_manifest_file.close();
+
+ adbc_validation::Handle<struct AdbcDatabase> database;
+
+ // find profile by name using ADBC_PROFILE_PATH
+ SetConfigPath(temp_dir.string().c_str());
+ ASSERT_THAT(AdbcDatabaseNew(&database.value, &error), IsOkStatus(&error));
+ ASSERT_THAT(AdbcDatabaseSetOption(&database.value, "profile", "profile",
&error),
+ IsOkStatus(&error));
+ ASSERT_THAT(AdbcDatabaseInit(&database.value, &error),
+ IsStatus(ADBC_STATUS_INVALID_ARGUMENT, &error));
+ ASSERT_THAT(error.message,
+ ::testing::HasSubstr(
+ "Malformed env_var() profile value: missing closing
parenthesis"));
+ UnsetConfigPath();
+}
+
+TEST_F(ConnectionProfiles, UseEnvVarMissingArg) {
+ auto filepath = temp_dir / "profile.toml";
+ toml::table profile = toml::parse(R"|(
+ version = 1
+ driver = "adbc_driver_sqlite"
+ [options]
+ foo = "{{ env_var() }}"
+ )|");
+
+ std::ofstream test_manifest_file(filepath);
+ ASSERT_TRUE(test_manifest_file.is_open());
+ test_manifest_file << profile;
+ test_manifest_file.close();
+
+ adbc_validation::Handle<struct AdbcDatabase> database;
+
+ // find profile by name using ADBC_PROFILE_PATH
+ SetConfigPath(temp_dir.string().c_str());
+ ASSERT_THAT(AdbcDatabaseNew(&database.value, &error), IsOkStatus(&error));
+ ASSERT_THAT(AdbcDatabaseSetOption(&database.value, "profile", "profile",
&error),
+ IsOkStatus(&error));
+ ASSERT_THAT(AdbcDatabaseInit(&database.value, &error),
+ IsStatus(ADBC_STATUS_INVALID_ARGUMENT, &error));
+ ASSERT_THAT(
+ error.message,
+ ::testing::HasSubstr(
+ "Malformed env_var() profile value: missing environment variable
name"));
+ UnsetConfigPath();
+}
+
+TEST_F(ConnectionProfiles, UseEnvVarInterpolation) {
+ auto filepath = temp_dir / "profile.toml";
+ toml::table profile = toml::parse(R"|(
+ version = 1
+ driver = "adbc_driver_sqlite"
+ [options]
+ foo = "super {{ env_var(ADBC_PROFILE_PATH) }} duper"
+ )|");
+
+ std::ofstream test_manifest_file(filepath);
+ ASSERT_TRUE(test_manifest_file.is_open());
+ test_manifest_file << profile;
+ test_manifest_file.close();
+
+ adbc_validation::Handle<struct AdbcDatabase> database;
+
+ // find profile by name using ADBC_PROFILE_PATH
+ SetConfigPath(temp_dir.string().c_str());
+ ASSERT_THAT(AdbcDatabaseNew(&database.value, &error), IsOkStatus(&error));
+ ASSERT_THAT(AdbcDatabaseSetOption(&database.value, "profile", "profile",
&error),
+ IsOkStatus(&error));
+ ASSERT_THAT(AdbcDatabaseInit(&database.value, &error),
+ IsStatus(ADBC_STATUS_NOT_IMPLEMENTED, &error));
+ ASSERT_THAT(error.message, ::testing::HasSubstr("Unknown database option
foo='super " +
+ temp_dir.string() + "
duper'"));
+ UnsetConfigPath();
+}
+
+TEST_F(ConnectionProfiles, UseEnvVarInterpolationMultiple) {
+ auto filepath = temp_dir / "profile.toml";
+ toml::table profile = toml::parse(R"|(
+ version = 1
+ driver = "adbc_driver_sqlite"
+ [options]
+ foo = "super {{ env_var(ADBC_PROFILE_PATH) }} duper {{
env_var(ADBC_PROFILE_PATH) }} end"
+ )|");
+
+ std::ofstream test_manifest_file(filepath);
+ ASSERT_TRUE(test_manifest_file.is_open());
+ test_manifest_file << profile;
+ test_manifest_file.close();
+
+ adbc_validation::Handle<struct AdbcDatabase> database;
+
+ // find profile by name using ADBC_PROFILE_PATH
+ SetConfigPath(temp_dir.string().c_str());
+ ASSERT_THAT(AdbcDatabaseNew(&database.value, &error), IsOkStatus(&error));
+ ASSERT_THAT(AdbcDatabaseSetOption(&database.value, "profile", "profile",
&error),
+ IsOkStatus(&error));
+ ASSERT_THAT(AdbcDatabaseInit(&database.value, &error),
+ IsStatus(ADBC_STATUS_NOT_IMPLEMENTED, &error));
+ ASSERT_THAT(error.message, ::testing::HasSubstr("Unknown database option
foo='super " +
+ temp_dir.string() + " duper
" +
+ temp_dir.string() + "
end'"));
+ UnsetConfigPath();
+}
+
+TEST_F(ConnectionProfiles, ProfileNotFound) {
+ adbc_validation::Handle<struct AdbcDatabase> database;
+
+ // absolute path to the profile
+ ASSERT_THAT(AdbcDatabaseNew(&database.value, &error), IsOkStatus(&error));
+ ASSERT_THAT(AdbcDatabaseSetOption(&database.value, "profile",
+ (temp_dir /
"profile.toml").string().c_str(), &error),
+ IsOkStatus(&error));
+ ASSERT_THAT(AdbcDatabaseInit(&database.value, &error),
+ IsStatus(ADBC_STATUS_NOT_FOUND, &error));
+ ASSERT_THAT(error.message, ::testing::HasSubstr("Profile file does not
exist: " +
+ (temp_dir /
"profile.toml").string()));
+ ASSERT_THAT(AdbcDatabaseRelease(&database.value, &error),
IsOkStatus(&error));
+
+ // find profile by name using ADBC_PROFILE_PATH
+ SetConfigPath(temp_dir.string().c_str());
+ ASSERT_THAT(AdbcDatabaseNew(&database.value, &error), IsOkStatus(&error));
+ ASSERT_THAT(AdbcDatabaseSetOption(&database.value, "profile", "profile",
&error),
+ IsOkStatus(&error));
+ ASSERT_THAT(AdbcDatabaseInit(&database.value, &error),
+ IsStatus(ADBC_STATUS_NOT_FOUND, &error));
+ ASSERT_THAT(error.message,
+ ::testing::HasSubstr(std::string("Profile not found: profile\n")
+
+ "Also searched these paths for
profiles:\n\t" +
+ "ADBC_PROFILE_PATH: " + temp_dir.string() +
"\n\t"));
+ ASSERT_THAT(AdbcDatabaseRelease(&database.value, &error),
IsOkStatus(&error));
+ UnsetConfigPath();
+}
+
+TEST_F(ConnectionProfiles, CustomProfileProvider) {
+ adbc_validation::Handle<struct AdbcDatabase> database;
+
+ AdbcConnectionProfileProvider provider =
+ [](const char* profile_name, const char* additional_path_list,
+ struct AdbcConnectionProfile* out, struct AdbcError* error) ->
AdbcStatusCode {
+ EXPECT_EQ(std::string(profile_name), "profile");
+
+ static const std::string expected = "custom profile provider error";
+ error->message = new char[expected.size() + 1];
+ std::copy(expected.begin(), expected.end(), error->message);
+ error->message[expected.size()] = '\0';
+ error->release = [](struct AdbcError* error) {
+ delete[] error->message;
+ error->message = nullptr;
+ error->release = nullptr;
+ };
+ return ADBC_STATUS_INVALID_ARGUMENT;
+ };
+
+ ASSERT_THAT(AdbcDatabaseNew(&database.value, &error), IsOkStatus(&error));
+ ASSERT_THAT(AdbcDatabaseSetOption(&database.value, "profile", "profile",
&error),
+ IsOkStatus(&error));
+ ASSERT_THAT(
+ AdbcDriverManagerDatabaseSetProfileProvider(&database.value, provider,
&error),
+ IsOkStatus(&error));
+ ASSERT_THAT(AdbcDatabaseInit(&database.value, &error),
+ IsStatus(ADBC_STATUS_INVALID_ARGUMENT, &error));
+ ASSERT_THAT(error.message, ::testing::HasSubstr("custom profile provider
error"));
+ ASSERT_THAT(AdbcDatabaseRelease(&database.value, &error),
IsOkStatus(&error));
+}
+
} // namespace adbc
diff --git a/c/include/arrow-adbc/adbc.h b/c/include/arrow-adbc/adbc.h
index a55f645ed..57e665f84 100644
--- a/c/include/arrow-adbc/adbc.h
+++ b/c/include/arrow-adbc/adbc.h
@@ -1300,6 +1300,11 @@ AdbcStatusCode AdbcDatabaseGetOptionInt(struct
AdbcDatabase* database, const cha
/// Options may be set before AdbcDatabaseInit. Some drivers may
/// support setting options after initialization as well.
///
+/// Driver managers may treat some option keys as manager-reserved and
+/// handle them without forwarding them to the underlying driver. In
+/// particular, the option key "profile" is reserved for connection
+/// profiles and must not be implemented or interpreted by drivers.
+///
/// \param[in] database The database.
/// \param[in] key The option to set.
/// \param[in] value The option value.
diff --git a/c/include/arrow-adbc/adbc_driver_manager.h
b/c/include/arrow-adbc/adbc_driver_manager.h
index cf968ffdb..9418e0072 100644
--- a/c/include/arrow-adbc/adbc_driver_manager.h
+++ b/c/include/arrow-adbc/adbc_driver_manager.h
@@ -175,6 +175,186 @@ AdbcStatusCode
AdbcDriverManagerDatabaseSetAdditionalSearchPathList(
ADBC_EXPORT
const char* AdbcStatusCodeMessage(AdbcStatusCode code);
+/// \defgroup adbc-driver-manager-connection-profile Connection Profiles
+/// The ADBC driver manager can support "connection profiles" that specify
+/// a driver and options to use when connecting. This allows users to
+/// specify connection information in a file or environment variable, and have
the
+/// driver manager load the appropriate driver and set options accordingly.
+///
+/// This allows creating reusable connection configurations for sharing and
distribution
+/// without needing to hardcode driver names and options in application code.
Profiles
+/// will be loaded during DatabaseInit before attempting to initialize the
driver. Any
+/// options specified by the profile will be applied but will not override
options
+/// that have already been set using DatabaseSetOption.
+///
+/// To facilitate customization, we define an interface for implementing a
Connection
+/// Profile object along with a provider function definition which can be set
into
+/// the driver manager to allow for customized profile loading.
+///
+/// A profile can be specified to the Driver Manager in one of two ways,
+/// which will invoke the profile provider during the call to DatabaseInit:
+///
+/// 1. The "profile" option can be set using DatabaseSetOption with the name
of the
+/// profile to load.
+/// 2. The "uri" being used can have the form "profile://<profile>"
+///
+/// @{
+
+/// \brief Abstract interface for connection profile providers
+struct ADBC_EXPORT AdbcConnectionProfile {
+ /// \brief Opaque implementation-defined state.
+ /// This field is NULL if the profile is uninitialized/freed (but
+ /// it need not have a value even if the profile is initialized).
+ void* private_data;
+
+ /// \brief Release the profile and perform any cleanup.
+ void (*release)(struct AdbcConnectionProfile* profile);
+
+ /// \brief Get the driver to use as specified by this profile.
+ ///
+ /// It is not required that a profile specify a driver. If the options
+ // can be reusable across drivers, then the profile does not need to specify
+ /// a driver (if this provides an empty string or nullptr then the driver
+ /// must be defined by other means, e.g. by the driver / uri options).
+ ///
+ /// \param[in] profile The profile to query.
+ /// \param[out] driver_name The name of the driver to use, or NULL if not
specified.
+ /// \param[out] init_func The init function to use for the driver, or NULL
if not
+ /// specified.
+ /// \param[out] error An optional location to return an error message
+ AdbcStatusCode (*GetDriverName)(struct AdbcConnectionProfile* profile,
+ const char** driver_name,
AdbcDriverInitFunc* init_func,
+ struct AdbcError* error);
+
+ /// \brief Get the string options specified by the profile
+ ///
+ /// The keys and values returned by this function are owned by the profile
+ /// object itself and do not need to be freed or managed by the caller.
+ /// They must not be accessed after calling release on the profile.
+ ///
+ /// The profile can also indicate that a value should be pulled from the
environment
+ /// by having a value in the form `env_var(ENV_VAR_NAME)`. If the driver
+ /// manager encounters a value of this form, it will replace it with the
actual value
+ /// of the environment variable `ENV_VAR_NAME` before setting the option.
This
+ /// is only valid for option *values* not *keys*.
+ ///
+ /// \param[in] profile The profile to query.
+ /// \param[out] keys The keys of the options specified by the profile.
+ /// \param[out] values The values of the options specified by the profile.
+ /// \param[out] num_options The number of options specified by the profile,
+ /// consumers must not access keys or values beyond this count.
+ /// \param[out] error An optional location to return an error message
+ AdbcStatusCode (*GetOptions)(struct AdbcConnectionProfile* profile, const
char*** keys,
+ const char*** values, size_t* num_options,
+ struct AdbcError* error);
+
+ /// \brief Get the integer options specified by the profile
+ ///
+ /// The keys and values returned by this function are owned by the profile
+ /// object itself and do not need to be freed or managed by the caller. They
must not be
+ /// accessed after calling release on the profile.
+ ///
+ /// Values returned by this function will be set using the
DatabaseSetOptionInt function
+ /// on the database object being initialized. If the driver does not support
the
+ /// DatabaseSetOptionInt function, then options should only be returned as
strings.
+ ///
+ /// \param[in] profile The profile to query.
+ /// \param[out] keys The keys of the options specified by the profile.
+ /// \param[out] values The values of the options specified by the profile.
+ /// \param[out] num_options The number of options specified by the profile,
+ /// consumers must not access keys or values beyond this count.
+ /// \param[out] error An optional location to return an error message
+ AdbcStatusCode (*GetIntOptions)(struct AdbcConnectionProfile* profile,
+ const char*** keys, const int64_t** values,
+ size_t* num_options, struct AdbcError*
error);
+
+ /// \brief Get the double options specified by the profile
+ ///
+ /// The keys and values returned by this function are owned by the profile
+ /// object itself and do not need to be freed or managed by the caller. They
must not be
+ /// accessed after calling release on the profile.
+ ///
+ /// Values returned by this function will be set using the
DatabaseSetOptionDouble
+ /// function on the database object being initialized. If the driver does
not support
+ /// the DatabaseSetOptionDouble function, then options should only be
returned as
+ /// strings.
+ ///
+ /// \param[in] profile The profile to query.
+ /// \param[out] keys The keys of the options specified by the profile.
+ /// \param[out] values The values of the options specified by the profile.
+ /// \param[out] num_options The number of options specified by the profile,
+ /// consumers must not access keys or values beyond this count.
+ /// \param[out] error An optional location to return an error message
+ AdbcStatusCode (*GetDoubleOptions)(struct AdbcConnectionProfile* profile,
+ const char*** keys, const double** values,
+ size_t* num_options, struct AdbcError*
error);
+};
+
+/// \brief Common definition for a connection profile provider
+///
+/// \param[in] profile_name The name of the profile to load. This is the value
of the
+/// "profile" option or the profile specified in the URI.
+/// \param[in] additional_search_path_list A list of additional paths to
search for
+/// profiles, delimited by the OS specific path list separator.
+/// \param[out] out The profile to return. The caller will take ownership of
the profile
+/// and is responsible for calling release on it when finished.
+/// \param[out] error An optional location to return an error message if
necessary.
+typedef AdbcStatusCode (*AdbcConnectionProfileProvider)(
+ const char* profile_name, const char* additional_search_path_list,
+ struct AdbcConnectionProfile* out, struct AdbcError* error);
+
+/// \brief Set a custom connection profile provider for the driver manager.
+///
+/// If no provider is set, the driver manager will use a default,
filesystem-based
+/// provider which will look for profiles in the following locations if not
given an
+/// absolute path to a file:
+///
+/// 1. The environment variable ADBC_PROFILE_PATH, which is a list of paths to
search for
+/// profiles.
+/// 2. The user-level configuration directory (e.g. ~/.config/adbc/profiles on
Linux).
+///
+/// The filesystem-based profile looks for a file named <profile_name>.toml if
there is
+/// no extension provided, attempting to parse the toml file for the profile
information.
+/// If the file is found and parsed successfully, the options specified in the
profile
+/// which have not already been set will be set as if by DatabaseSetOption
just before
+/// initialization as part of DatabaseInit.
+///
+/// For file-based profiles the expected format is as follows:
+/// ```toml
+/// version = 1
+/// driver = "driver_name"
+///
+/// [options]
+/// option1 = "value1"
+/// option2 = 42
+/// option3 = 3.14
+/// ```
+///
+/// Boolean options will be converted to string equivalents of "true" or
"false".
+///
+/// \param[in] database The database to set the profile provider for.
+/// \param[in] provider The profile provider to use. If NULL, the default
filesystem-based
+/// provider will be used if a profile is needed.
+/// \param[out] error An optional location to return an error message if
necessary
+ADBC_EXPORT
+AdbcStatusCode AdbcDriverManagerDatabaseSetProfileProvider(
+ struct AdbcDatabase* database, AdbcConnectionProfileProvider provider,
+ struct AdbcError* error);
+
+/// \brief Default Filesystem-based profile provider for the driver manager.
+///
+/// We expose this so that consumers would be able to write a provider that
falls back on
+/// the default filesystem-based provider if their custom provider fails to
find a
+/// profile. This allows for more flexible provider implementations that can
still
+/// leverage the default behavior when needed.
+ADBC_EXPORT
+AdbcStatusCode AdbcProfileProviderFilesystem(const char* profile_name,
+ const char*
additional_search_path_list,
+ struct AdbcConnectionProfile* out,
+ struct AdbcError* error);
+
+/// @}
+
#endif // ADBC_DRIVER_MANAGER_H
#ifdef __cplusplus
diff --git a/docs/source/format/connection_profiles.rst
b/docs/source/format/connection_profiles.rst
new file mode 100644
index 000000000..cf3ae87f2
--- /dev/null
+++ b/docs/source/format/connection_profiles.rst
@@ -0,0 +1,589 @@
+.. 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.
+
+==================================
+Driver Manager Connection Profiles
+==================================
+
+Overview
+========
+
+There are two ways to pass connection options to driver managers:
+
+1. Directly specifying all connection options as arguments to driver manager
functions in your
+ application code. (see the `SetOption` family of functions in
:doc:`specification` for details)
+2. Referring to a **connection profile** which contains connection options,
and optionally overriding
+ some options in your application code.
+
+The ADBC driver manager supports **connection profiles** that specify a driver
and connection options
+in a reusable configuration. This allows users to:
+
+- Define connection information in files or environment variables
+- Share connection configurations across applications
+- Distribute standardized connection settings
+- Avoid hardcoding driver names and credentials in application code
+
+Profiles are loaded during ``AdbcDatabaseInit()`` before initializing the
driver. Options
+from the profile are applied automatically but do not override options already
set via ``AdbcDatabaseSetOption()``.
+
+Quick Start
+===========
+
+Using a Profile via URI
+-----------------------
+
+The simplest way to use a profile is through a URI:
+
+.. code-block:: c
+
+ AdbcDatabase database;
+ AdbcDatabaseNew(&database, &error);
+ AdbcDatabaseSetOption(&database, "uri", "profile://my_snowflake_prod",
&error);
+ AdbcDatabaseInit(&database, &error);
+
+Using a Profile via Option
+---------------------------
+
+Alternatively, specify the profile name directly:
+
+.. code-block:: c
+
+ AdbcDatabase database;
+ AdbcDatabaseNew(&database, &error);
+ AdbcDatabaseSetOption(&database, "profile", "my_snowflake_prod", &error);
+ AdbcDatabaseInit(&database, &error);
+
+Profile File Format
+===================
+
+Filesystem-based profiles use TOML format with the following structure:
+
+.. code-block:: toml
+
+ version = 1
+ driver = "snowflake"
+
+ [options]
+ # String options
+ adbc.snowflake.sql.account = "mycompany"
+ adbc.snowflake.sql.warehouse = "COMPUTE_WH"
+ adbc.snowflake.sql.database = "PRODUCTION"
+ adbc.snowflake.sql.schema = "PUBLIC"
+
+ # Integer options
+ adbc.snowflake.sql.client_session_keep_alive_heartbeat_frequency = 3600
+
+ # Double options
+ adbc.snowflake.sql.client_timeout = 30.5
+
+ # Boolean options (converted to "true" or "false" strings)
+ adbc.snowflake.sql.client_session_keep_alive = true
+
+version
+-------
+
+- **Required**: Yes
+- **Type**: Integer
+- **Supported values**: ``1``
+
+The ``version`` field specifies the profile format version. Currently, only
version 1 is supported.
+This will enable future changes while maintaining backward compatibility.
+
+driver
+------
+
+- **Required**: No
+- **Type**: String
+
+The ``driver`` field specifies which ADBC driver to load. This can be:
+
+- A driver name (e.g., ``"snowflake"``)
+- A path to a shared library (e.g.,
``"/usr/local/lib/libadbc_driver_snowflake.so"``)
+- A path to a driver manifest (e.g., ``"/etc/adbc/drivers/snowflake.toml"``)
+
+If omitted, the driver must be specified through other means (e.g., the
``driver`` option or ``uri`` parameter).
+The driver will be loaded identically to if it was specified via
``AdbcDatabaseSetOption("driver", "<driver>")``.
+For more detils, see :doc:`driver_manifests`.
+
+Options Section
+---------------
+
+The ``[options]`` section contains driver-specific configuration options.
Options can be of the following types:
+
+**String values**
+ Applied using ``AdbcDatabaseSetOption()``
+
+ .. code-block:: toml
+
+ adbc.snowflake.sql.account = "mycompany"
+ adbc.snowflake.sql.warehouse = "COMPUTE_WH"
+
+**Integer values**
+ Applied using ``AdbcDatabaseSetOptionInt()``
+
+ .. code-block:: toml
+
+ adbc.snowflake.sql.client_session_keep_alive_heartbeat_frequency = 3600
+
+**Double values**
+ Applied using ``AdbcDatabaseSetOptionDouble()``
+
+ .. code-block:: toml
+
+ adbc.snowflake.sql.client_timeout = 30.5
+
+**Boolean values**
+ Converted to strings ``"true"`` or ``"false"`` and applied using
``AdbcDatabaseSetOption()``
+
+ .. code-block:: toml
+
+ adbc.snowflake.sql.client_session_keep_alive = true
+
+Value Substitution
+------------------
+
+Profile values support substitution of environment variables and other dynamic
content.
+This allows profiles to reference sensitive information (like passwords or
tokens) without
+hardcoding them in the profile file. Dynamic values can be injected by the
presence of the ``{{ }}`` syntax,
+similar to many templating engines. Within the double curly braces, the driver
manager can
+recognize certain functions to perform substitutions.
+
+Currently, the only recognized function is ``env_var()`` for environment
variable substitution,
+but this may be extended in the future to support other types of dynamic
content.
+
+.. important::
+ Dynamic content substitution only applies to option **values**, not
**keys**.
+
+Environment Variable Substitution
+'''''''''''''''''''''''''''''''''
+
+Profile values can reference environment variables using the ``{{ env_var()
}}`` syntax:
+
+.. code-block:: toml
+
+ version = 1
+ driver = "adbc_driver_snowflake"
+
+ [options]
+ adbc.snowflake.sql.account = "{{ env_var(SNOWFLAKE_ACCOUNT) }}"
+ adbc.snowflake.sql.auth_token = "{{ env_var(SNOWFLAKE_TOKEN) }}"
+ adbc.snowflake.sql.warehouse = "COMPUTE_WH"
+
+When the driver manager encounters ``{{ env_var(VAR_NAME) }}``, it replaces
the value with the contents of environment variable ``VAR_NAME``. If the
environment variable is not set, the value becomes an empty string.
+
+Profile Search Locations
+=========================
+
+When using a profile name (not an absolute path), the driver manager searches
for ``<profile_name>.toml`` in the following locations:
+
+1. **Additional Search Paths** (if configured via
``AdbcDriverManagerDatabaseSetAdditionalSearchPathList()``)
+2. **ADBC_PROFILE_PATH** environment variable (colon-separated on Unix,
semicolon-separated on Windows)
+3. **Conda Environment** (if built with Conda support and ``CONDA_PREFIX`` is
set):
+ - ``$CONDA_PREFIX/etc/adbc/profiles/``
+4. **User Configuration Directory**:
+ - Linux: ``~/.config/adbc/profiles/``
+ - macOS: ``~/Library/Application Support/ADBC/Profiles/``
+ - Windows: ``%LOCALAPPDATA%\ADBC\Profiles\``
+
+The driver manager searches locations in order and uses the first matching
profile file found.
+
+Using Absolute Paths
+--------------------
+
+To specify an absolute path to a profile file:
+
+.. code-block:: c
+
+ // Via profile option
+ AdbcDatabaseSetOption(&database, "profile",
"/etc/adbc/profiles/production.toml", &error);
+
+ // Via URI (must have .toml extension)
+ AdbcDatabaseSetOption(&database, "uri",
"profile:///etc/adbc/profiles/production.toml", &error);
+
+Examples
+========
+
+Example 1: Snowflake Production Profile
+----------------------------------------
+
+File: ``~/.config/adbc/profiles/snowflake_prod.toml``
+
+.. code-block:: toml
+
+ version = 1
+ driver = "snowflake"
+
+ [options]
+ adbc.snowflake.sql.account = "{{ env_var(SNOWFLAKE_ACCOUNT) }}"
+ adbc.snowflake.sql.auth_token = "{{ env_var(SNOWFLAKE_TOKEN) }}"
+ adbc.snowflake.sql.warehouse = "PRODUCTION_WH"
+ adbc.snowflake.sql.database = "PROD_DB"
+ adbc.snowflake.sql.schema = "PUBLIC"
+ adbc.snowflake.sql.client_session_keep_alive = true
+ adbc.snowflake.sql.client_session_keep_alive_heartbeat_frequency = 3600
+
+Usage:
+
+.. code-block:: c
+
+ // Set environment variables
+ setenv("SNOWFLAKE_ACCOUNT", "mycompany", 1);
+ setenv("SNOWFLAKE_TOKEN", "secret_token", 1);
+
+ // Use profile
+ AdbcDatabase database;
+ AdbcDatabaseNew(&database, &error);
+ AdbcDatabaseSetOption(&database, "uri", "profile://snowflake_prod", &error);
+ AdbcDatabaseInit(&database, &error);
+
+Example 2: PostgreSQL Development Profile
+------------------------------------------
+
+File: ``~/.config/adbc/profiles/postgres_dev.toml``
+
+.. code-block:: toml
+
+ version = 1
+ driver = "postgresql"
+
+ [options]
+ uri = "postgresql://localhost:5432/dev_db?sslmode=disable"
+ username = "dev_user"
+ password = "{{ env_var(POSTGRES_DEV_PASSWORD) }}"
+
+Example 3: Driver-Agnostic Profile
+-----------------------------------
+
+Profiles can omit the driver field for reusable configurations:
+
+File: ``~/.config/adbc/profiles/default_timeouts.toml``
+
+.. code-block:: toml
+
+ version = 1
+ # No driver specified - can be used with any driver
+
+ [options]
+ adbc.connection.timeout = 30.0
+ adbc.statement.timeout = 60.0
+
+Usage (driver specified separately):
+
+.. code-block:: c
+
+ AdbcDatabase database;
+ AdbcDatabaseNew(&database, &error);
+ AdbcDatabaseSetOption(&database, "driver", "adbc_driver_snowflake", &error);
+ AdbcDatabaseSetOption(&database, "profile", "default_timeouts", &error);
+ AdbcDatabaseInit(&database, &error);
+
+Advanced Usage
+==============
+
+Option Precedence
+-----------------
+
+Options are applied in the following order (later overrides earlier):
+
+1. Driver defaults
+2. Profile options (from ``[options]`` section)
+3. Options set via ``AdbcDatabaseSetOption()`` before ``AdbcDatabaseInit()``
+
+Example:
+
+.. code-block:: c
+
+ AdbcDatabase database;
+ AdbcDatabaseNew(&database, &error);
+
+ // Profile sets warehouse = "COMPUTE_WH"
+ AdbcDatabaseSetOption(&database, "profile", "snowflake_prod", &error);
+
+ // This overrides the profile setting
+ AdbcDatabaseSetOption(&database, "adbc.snowflake.sql.warehouse",
"ANALYTICS_WH", &error);
+
+ AdbcDatabaseInit(&database, &error);
+ // Result: warehouse = "ANALYTICS_WH"
+
+Custom Profile Providers
+=========================
+
+Applications can implement custom profile providers to load profiles from
alternative sources (databases, key vaults, configuration services, etc.).
+
+Interface Definition
+--------------------
+
+A profile provider must implement the ``AdbcConnectionProfile`` interface:
+
+.. code-block:: c
+
+ struct AdbcConnectionProfile {
+ void* private_data;
+ // this will be called by the driver manager after retrieving the
necessary information from the profile.
+ void (*release)(struct AdbcConnectionProfile* profile);
+ AdbcStatusCode (*GetDriverName)(struct AdbcConnectionProfile* profile,
+ const char** driver_name,
+ AdbcDriverInit* init_func,
+ struct AdbcError* error);
+ AdbcStatusCode (*GetOptions)(struct AdbcConnectionProfile* profile,
+ const char*** keys, const char*** values,
+ size_t* num_options, struct AdbcError*
error);
+ AdbcStatusCode (*GetIntOptions)(struct AdbcConnectionProfile* profile,
+ const char*** keys, const int64_t**
values,
+ size_t* num_options, struct AdbcError*
error);
+ AdbcStatusCode (*GetDoubleOptions)(struct AdbcConnectionProfile*
profile,
+ const char*** keys, const double**
values,
+ size_t* num_options, struct
AdbcError* error);
+ };
+
+Provider Function
+-----------------
+
+The provider function signature:
+
+.. code-block:: c
+
+ typedef AdbcStatusCode (*AdbcConnectionProfileProvider)(
+ const char* profile_name,
+ const char* additional_search_path_list,
+ struct AdbcConnectionProfile* out,
+ struct AdbcError* error);
+
+Example Implementation
+----------------------
+
+.. code-block:: c
+
+ // Example: Load profiles from a key-value store
+ AdbcStatusCode MyCustomProfileProvider(const char* profile_name,
+ const char*
additional_search_path_list,
+ struct AdbcConnectionProfile* out,
+ struct AdbcError* error) {
+ // Fetch profile from custom source
+ MyProfileData* data = LoadProfileFromKeyVault(profile_name);
+ if (!data) {
+ SetError(error, "Profile not found in key vault");
+ return ADBC_STATUS_NOT_FOUND;
+ }
+
+ std::memset(out, 0, sizeof(struct AdbcConnectionProfile));
+ // Populate profile structure
+ out->private_data = data;
+ out->release = MyProfileRelease;
+ out->GetDriverName = MyGetDriverName;
+ out->GetOptions = MyGetOptions;
+ out->GetIntOptions = MyGetIntOptions;
+ out->GetDoubleOptions = MyGetDoubleOptions;
+
+ return ADBC_STATUS_OK;
+ }
+
+ // Register custom provider
+ AdbcDatabase database;
+ AdbcDatabaseNew(&database, &error);
+ AdbcDriverManagerDatabaseSetProfileProvider(&database,
MyCustomProfileProvider, &error);
+ AdbcDatabaseSetOption(&database, "profile", "prod_config", &error);
+ AdbcDatabaseInit(&database, &error);
+
+Use Cases
+=========
+
+Development vs. Production
+---------------------------
+
+Maintain separate profiles for different environments:
+
+.. code-block:: bash
+
+ # Development
+ export ADBC_PROFILE=snowflake_dev
+
+ # Production
+ export ADBC_PROFILE=snowflake_prod
+
+Application code:
+
+.. code-block:: c
+
+ const char* profile = getenv("ADBC_PROFILE");
+ if (!profile) profile = "default";
+
+ AdbcDatabaseSetOption(&database, "profile", profile, &error);
+
+Credential Management
+---------------------
+
+Store credentials separately from code:
+
+.. code-block:: toml
+
+ [options]
+ adbc.snowflake.sql.account = "mycompany"
+ adbc.snowflake.sql.auth_token = "env_var(SNOWFLAKE_TOKEN)"
+
+Then set ``SNOWFLAKE_TOKEN`` via environment variable, secrets manager, or
configuration service.
+
+Multi-Tenant Applications
+--------------------------
+
+Use profiles to support different customer configurations:
+
+.. code-block:: c
+
+ char profile_name[256];
+ snprintf(profile_name, sizeof(profile_name), "customer_%s", customer_id);
+
+ AdbcDatabaseSetOption(&database, "profile", profile_name, &error);
+
+Testing
+-------
+
+Use profiles to switch between mock and real databases:
+
+.. code-block:: c
+
+ #ifdef TESTING
+ const char* profile = "mock_database";
+ #else
+ const char* profile = "production";
+ #endif
+
+ AdbcDatabaseSetOption(&database, "profile", profile, &error);
+
+Error Handling
+==============
+
+Profile Not Found
+-----------------
+
+If a profile cannot be found, ``AdbcDatabaseInit()`` returns
``ADBC_STATUS_NOT_FOUND`` with a detailed error message listing all searched
locations:
+
+.. code-block:: text
+
+ [Driver Manager] Profile not found: my_profile
+ Also searched these paths for profiles:
+ ADBC_PROFILE_PATH: /custom/path
+ user config dir: /home/user/.config/adbc/profiles
+ system config dir: /etc/adbc/profiles
+
+Invalid Profile Format
+----------------------
+
+If a profile file exists but is malformed, ``AdbcDatabaseInit()`` returns
``ADBC_STATUS_INVALID_ARGUMENT``:
+
+.. code-block:: text
+
+ [Driver Manager] Could not open profile. Error at line 5: expected '='
after key.
+ Profile: /home/user/.config/adbc/profiles/my_profile.toml
+
+Missing Driver
+--------------
+
+If a profile doesn't specify a driver and none is provided via other means:
+
+.. code-block:: text
+
+ [Driver Manager] Must provide 'driver' parameter
+ (or encode driver in 'uri' parameter)
+
+Best Practices
+==============
+
+1. **Use environment variables for secrets**: Never store credentials directly
in profile files.
+
+ .. code-block:: toml
+
+ # Good
+ password = "{{ env_var(DB_PASSWORD) }}"
+
+ # Bad
+ password = "my_secret_password"
+
+2. **Organize profiles hierarchically**: Group related profiles in
subdirectories using additional search paths.
+
+3. **Document profile schemas**: Maintain documentation of required
environment variables for each profile.
+
+4. **Version control without secrets**: Profile files can be version
controlled when using ``{{ env_var(VAR_NAME) }}`` for sensitive values.
+
+5. **Test profile loading**: Verify profiles load correctly in CI/CD pipelines.
+
+6. **Use meaningful names**: Name profiles descriptively (e.g.,
``snowflake_prod_analytics`` vs. ``profile1``).
+
+7. **Validate environment variables**: Check that required environment
variables are set before calling ``AdbcDatabaseInit()``.
+
+API Reference
+=============
+
+Setting a Profile Provider
+---------------------------
+
+.. code-block:: c
+
+ AdbcStatusCode AdbcDriverManagerDatabaseSetProfileProvider(
+ struct AdbcDatabase* database,
+ AdbcConnectionProfileProvider provider,
+ struct AdbcError* error);
+
+Sets a custom connection profile provider. Must be called before
``AdbcDatabaseInit()``.
+
+**Parameters:**
+
+- ``database``: Database object to configure
+- ``provider``: Profile provider function, or ``NULL`` for default filesystem
provider
+- ``error``: Optional error output
+
+**Returns:** ``ADBC_STATUS_OK`` on success, error code otherwise.
+
+Setting Additional Search Paths
+--------------------------------
+
+.. code-block:: c
+
+ AdbcStatusCode AdbcDriverManagerDatabaseSetAdditionalSearchPathList(
+ struct AdbcDatabase* database,
+ const char* path_list,
+ struct AdbcError* error);
+
+Adds additional directories to search for profiles. Must be called before
``AdbcDatabaseInit()``.
+
+**Parameters:**
+
+- ``database``: Database object to configure
+- ``path_list``: OS-specific path separator delimited list (``:``) on Unix,
``;`` on Windows), or ``NULL`` to clear
+- ``error``: Optional error output
+
+**Returns:** ``ADBC_STATUS_OK`` on success, error code otherwise.
+
+**Example:**
+
+.. code-block:: c
+
+ // Unix/Linux/macOS
+ AdbcDriverManagerDatabaseSetAdditionalSearchPathList(
+ &database, "/opt/app/profiles:/etc/app/profiles", &error);
+
+ // Windows
+ AdbcDriverManagerDatabaseSetAdditionalSearchPathList(
+ &database, "C:\\App\\Profiles;C:\\ProgramData\\App\\Profiles", &error);
+
+
+See Also
+========
+
+- :doc:`how_manager` - Driver Manager overview
+- :doc:`specification` - Driver specification and options
+- :doc:`../cpp/driver_manager` - CPP Driver Manager Reference
diff --git a/docs/source/format/driver_manifests.rst
b/docs/source/format/driver_manifests.rst
index f2d46137a..1a48060a8 100644
--- a/docs/source/format/driver_manifests.rst
+++ b/docs/source/format/driver_manifests.rst
@@ -438,6 +438,13 @@ to control which directories will be searched for
manifests, with the behavior b
* ``LOAD_FLAG_ALLOW_RELATIVE_PATHS`` - allow a relative path to be used
* ``LOAD_FLAG_DEFAULT`` - default value with all flags set
+.. warning:: A driver manifest file must not be named ``profile.toml``.
+ The name ``profile`` is reserved for
+ :doc:`connection profiles <connection_profiles>`.
+ Using it as the basename of a driver manifest conflicts with how
+ driver managers interpret URIs beginning with the
+ ``profile://`` scheme.
+
Unix-like Platforms
^^^^^^^^^^^^^^^^^^^
diff --git a/docs/source/index.rst b/docs/source/index.rst
index 80ecb7519..5d4b8966f 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -257,6 +257,7 @@ Why ADBC?
format/comparison
format/how_manager
format/driver_manifests
+ format/connection_profiles
format/related_work
.. toctree::
diff --git a/go/adbc/drivermgr/adbc_driver_manager.cc
b/go/adbc/drivermgr/adbc_driver_manager.cc
index 9397b142f..15d98c00b 100644
--- a/go/adbc/drivermgr/adbc_driver_manager.cc
+++ b/go/adbc/drivermgr/adbc_driver_manager.cc
@@ -46,6 +46,8 @@
#include <cstdio>
#include <cstring>
#include <filesystem>
+#include <functional>
+#include <regex>
#include <string>
#include <unordered_map>
#include <utility>
@@ -65,6 +67,7 @@ std::filesystem::path InternalAdbcSystemConfigDir();
struct ParseDriverUriResult {
std::string_view driver;
std::optional<std::string_view> uri;
+ std::optional<std::string_view> profile;
};
ADBC_EXPORT
@@ -87,16 +90,32 @@ enum class SearchPathSource {
kOtherError,
};
+enum class SearchPathType {
+ kManifest,
+ kProfile,
+};
+
using SearchPaths = std::vector<std::pair<SearchPathSource,
std::filesystem::path>>;
-void AddSearchPathsToError(const SearchPaths& search_paths, std::string&
error_message) {
+void AddSearchPathsToError(const SearchPaths& search_paths, const
SearchPathType& type,
+ std::string& error_message) {
if (!search_paths.empty()) {
- error_message += "\nAlso searched these paths for manifests:";
+ error_message += "\nAlso searched these paths for";
+ if (type == SearchPathType::kManifest) {
+ error_message += " manifests:";
+ } else if (type == SearchPathType::kProfile) {
+ error_message += " profiles:";
+ }
+
for (const auto& [source, path] : search_paths) {
error_message += "\n\t";
switch (source) {
case SearchPathSource::kEnv:
- error_message += "ADBC_DRIVER_PATH: ";
+ if (type == SearchPathType::kManifest) {
+ error_message += "ADBC_DRIVER_PATH: ";
+ } else if (type == SearchPathType::kProfile) {
+ error_message += "ADBC_PROFILE_PATH: ";
+ }
break;
case SearchPathSource::kUser:
error_message += "user config dir: ";
@@ -234,7 +253,7 @@ void SetError(struct AdbcError* error, struct AdbcError*
src_error) {
if (src_error->message) {
size_t message_size = strlen(src_error->message);
- error->message = new char[message_size];
+ error->message = new char[message_size + 1]; // +1 to include null
std::memcpy(error->message, src_error->message, message_size);
error->message[message_size] = '\0';
} else {
@@ -398,6 +417,86 @@ AdbcStatusCode LoadDriverFromRegistry(HKEY root, const
std::wstring& driver_name
}
#endif // _WIN32
+#define CHECK_STATUS(EXPR) \
+ if (auto _status = (EXPR); _status != ADBC_STATUS_OK) { \
+ return _status; \
+ }
+
+AdbcStatusCode ProcessProfileValue(std::string_view value, std::string& out,
+ struct AdbcError* error) {
+ if (value.empty()) {
+ SetError(error, "Profile value is null");
+ return ADBC_STATUS_INVALID_ARGUMENT;
+ }
+
+ static const std::regex pattern(R"(\{\{\s*([^{}]*?)\s*\}\})");
+ auto end_of_last_match = value.begin();
+ auto begin = std::regex_iterator(value.begin(), value.end(), pattern);
+ auto end = decltype(begin){};
+ std::match_results<std::string_view::iterator>::difference_type
pos_last_match = 0;
+
+ out.resize(0);
+ for (auto itr = begin; itr != end; ++itr) {
+ auto match = *itr;
+ auto pos_match = match.position();
+ auto diff = pos_match - pos_last_match;
+ auto start_match = end_of_last_match;
+ std::advance(start_match, diff);
+ out.append(end_of_last_match, start_match);
+
+ const auto content = match[1].str();
+ if (content.rfind("env_var(", 0) != 0) {
+ SetError(error, "Unsupported interpolation type in profile value: " +
content);
+ return ADBC_STATUS_INVALID_ARGUMENT;
+ }
+
+ if (content[content.size() - 1] != ')') {
+ SetError(error, "Malformed env_var() profile value: missing closing
parenthesis");
+ return ADBC_STATUS_INVALID_ARGUMENT;
+ }
+
+ const auto env_var_name = content.substr(8, content.size() - 9);
+ if (env_var_name.empty()) {
+ SetError(error,
+ "Malformed env_var() profile value: missing environment
variable name");
+ return ADBC_STATUS_INVALID_ARGUMENT;
+ }
+
+#ifdef _WIN32
+ auto local_env_var = Utf8Decode(std::string(env_var_name));
+ DWORD required_size = GetEnvironmentVariableW(local_env_var.c_str(), NULL,
0);
+ if (required_size == 0) {
+ out = "";
+ return ADBC_STATUS_OK;
+ }
+
+ std::wstring wvalue;
+ wvalue.resize(required_size);
+ DWORD actual_size =
+ GetEnvironmentVariableW(local_env_var.c_str(), wvalue.data(),
required_size);
+ // remove null terminator
+ wvalue.resize(actual_size);
+ const auto env_var_value = Utf8Encode(wvalue);
+#else
+ const char* env_value = std::getenv(env_var_name.c_str());
+ if (!env_value) {
+ out = "";
+ return ADBC_STATUS_OK;
+ }
+ const auto env_var_value = std::string(env_value);
+#endif
+ out.append(env_var_value);
+
+ auto length_match = match.length();
+ pos_last_match = pos_match + length_match;
+ end_of_last_match = start_match;
+ std::advance(end_of_last_match, length_match);
+ }
+
+ out.append(end_of_last_match, value.end());
+ return ADBC_STATUS_OK;
+}
+
/// \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)
@@ -520,8 +619,10 @@ SearchPaths GetEnvPaths(const char_type* env_var) {
#ifdef _WIN32
static const wchar_t* kAdbcDriverPath = L"ADBC_DRIVER_PATH";
+static const wchar_t* kAdbcProfilePath = L"ADBC_PROFILE_PATH";
#else
static const char* kAdbcDriverPath = "ADBC_DRIVER_PATH";
+static const char* kAdbcProfilePath = "ADBC_PROFILE_PATH";
#endif // _WIN32
SearchPaths GetSearchPaths(const AdbcLoadFlags levels) {
@@ -728,7 +829,7 @@ struct ManagedLibrary {
extra_debug_info.end());
if (intermediate_error.error.message) {
std::string error_message = intermediate_error.error.message;
- AddSearchPathsToError(search_paths, error_message);
+ AddSearchPathsToError(search_paths, SearchPathType::kManifest,
error_message);
SetError(error, std::move(error_message));
}
return status;
@@ -947,7 +1048,7 @@ struct ManagedLibrary {
error_message += "\n";
error_message += message;
}
- AddSearchPathsToError(attempted_paths, error_message);
+ AddSearchPathsToError(attempted_paths, SearchPathType::kManifest,
error_message);
SetError(error, error_message);
return ADBC_STATUS_NOT_FOUND;
} else {
@@ -1000,7 +1101,7 @@ struct ManagedLibrary {
error_message += "\n";
error_message += message;
}
- AddSearchPathsToError(attempted_paths, error_message);
+ AddSearchPathsToError(attempted_paths, SearchPathType::kManifest,
error_message);
SetError(error, error_message);
return ADBC_STATUS_NOT_FOUND;
}
@@ -1042,6 +1143,263 @@ struct ManagedLibrary {
#endif // defined(_WIN32)
};
+struct FilesystemProfile {
+ std::filesystem::path path;
+ std::string driver;
+ std::unordered_map<std::string, std::string> options;
+ std::unordered_map<std::string, int64_t> int_options;
+ std::unordered_map<std::string, double> double_options;
+
+ std::vector<const char*> options_keys;
+ std::vector<const char*> options_values;
+
+ std::vector<const char*> int_option_keys;
+ std::vector<int64_t> int_option_values;
+
+ std::vector<const char*> double_option_keys;
+ std::vector<double> double_option_values;
+
+ void PopulateConnectionProfile(struct AdbcConnectionProfile* out) {
+ options_keys.reserve(options.size());
+ options_values.reserve(options.size());
+ for (const auto& [key, value] : options) {
+ options_keys.push_back(key.c_str());
+ options_values.push_back(value.c_str());
+ }
+
+ int_option_keys.reserve(int_options.size());
+ int_option_values.reserve(int_options.size());
+ for (const auto& [key, value] : int_options) {
+ int_option_keys.push_back(key.c_str());
+ int_option_values.push_back(value);
+ }
+
+ double_option_keys.reserve(double_options.size());
+ double_option_values.reserve(double_options.size());
+ for (const auto& [key, value] : double_options) {
+ double_option_keys.push_back(key.c_str());
+ double_option_values.push_back(value);
+ }
+
+ out->private_data = new FilesystemProfile(std::move(*this));
+ out->release = [](AdbcConnectionProfile* profile) {
+ if (!profile || !profile->private_data) {
+ return;
+ }
+
+ delete static_cast<FilesystemProfile*>(profile->private_data);
+ profile->private_data = nullptr;
+ profile->release = nullptr;
+ };
+
+ out->GetDriverName = [](AdbcConnectionProfile* profile, const char** out,
+ AdbcDriverInitFunc* init_func,
+ struct AdbcError* error) -> AdbcStatusCode {
+ if (!profile || !profile->private_data) {
+ SetError(error, "Invalid connection profile");
+ return ADBC_STATUS_INVALID_ARGUMENT;
+ }
+
+ auto* fs_profile =
static_cast<FilesystemProfile*>(profile->private_data);
+ *out = fs_profile->driver.c_str();
+ *init_func = nullptr;
+ return ADBC_STATUS_OK;
+ };
+
+ out->GetOptions = [](AdbcConnectionProfile* profile, const char*** keys,
+ const char*** values, size_t* num_options,
+ struct AdbcError* error) -> AdbcStatusCode {
+ if (!profile || !profile->private_data) {
+ SetError(error, "Invalid connection profile");
+ return ADBC_STATUS_INVALID_ARGUMENT;
+ }
+
+ if (!keys || !values || !num_options) {
+ SetError(error, "Output parameters cannot be null");
+ return ADBC_STATUS_INVALID_ARGUMENT;
+ }
+
+ auto* fs_profile =
static_cast<FilesystemProfile*>(profile->private_data);
+ *num_options = fs_profile->options.size();
+ *keys = fs_profile->options_keys.data();
+ *values = fs_profile->options_values.data();
+ return ADBC_STATUS_OK;
+ };
+
+ out->GetIntOptions = [](AdbcConnectionProfile* profile, const char*** keys,
+ const int64_t** values, size_t* num_options,
+ struct AdbcError* error) -> AdbcStatusCode {
+ if (!profile || !profile->private_data) {
+ SetError(error, "Invalid connection profile");
+ return ADBC_STATUS_INVALID_ARGUMENT;
+ }
+
+ if (!keys || !values || !num_options) {
+ SetError(error, "Output parameters cannot be null");
+ return ADBC_STATUS_INVALID_ARGUMENT;
+ }
+
+ auto* fs_profile =
static_cast<FilesystemProfile*>(profile->private_data);
+ *num_options = fs_profile->int_options.size();
+ *keys = fs_profile->int_option_keys.data();
+ *values = fs_profile->int_option_values.data();
+ return ADBC_STATUS_OK;
+ };
+
+ out->GetDoubleOptions = [](AdbcConnectionProfile* profile, const char***
keys,
+ const double** values, size_t* num_options,
+ struct AdbcError* error) -> AdbcStatusCode {
+ if (!profile || !profile->private_data) {
+ SetError(error, "Invalid connection profile");
+ return ADBC_STATUS_INVALID_ARGUMENT;
+ }
+
+ if (!keys || !values || !num_options) {
+ SetError(error, "Output parameters cannot be null");
+ return ADBC_STATUS_INVALID_ARGUMENT;
+ }
+
+ auto* fs_profile =
static_cast<FilesystemProfile*>(profile->private_data);
+ *num_options = fs_profile->double_options.size();
+ *keys = fs_profile->double_option_keys.data();
+ *values = fs_profile->double_option_values.data();
+ return ADBC_STATUS_OK;
+ };
+ }
+};
+
+struct ProfileVisitor {
+ FilesystemProfile& profile;
+ const std::filesystem::path& profile_path;
+ struct AdbcError* error;
+
+ bool VisitTable(const std::string& prefix, toml::table& table) {
+ for (const auto& [key, value] : table) {
+ if (auto* str = value.as_string()) {
+ profile.options[prefix + key.data()] = str->get();
+ } else if (auto* int_val = value.as_integer()) {
+ profile.int_options[prefix + key.data()] = int_val->get();
+ } else if (auto* double_val = value.as_floating_point()) {
+ profile.double_options[prefix + key.data()] = double_val->get();
+ } else if (auto* bool_val = value.as_boolean()) {
+ profile.options[prefix + key.data()] = bool_val->get() ? "true" :
"false";
+ } else if (value.is_table()) {
+ if (!VisitTable(prefix + key.data() + ".", *value.as_table())) {
+ return false;
+ }
+ } else {
+ std::string message = "Unsupported value type for key '" +
+ std::string(key.str()) + "' in profile '" +
+ profile_path.string() + "'";
+ SetError(error, std::move(message));
+ return false;
+ }
+ }
+ return !error->message;
+ }
+};
+
+AdbcStatusCode LoadProfileFile(const std::filesystem::path& profile_path,
+ FilesystemProfile& profile, struct AdbcError*
error) {
+ toml::table config;
+ try {
+ config = toml::parse_file(profile_path.native());
+ } catch (const toml::parse_error& err) {
+ std::string message = "Could not open profile. ";
+ message += err.what();
+ message += ". Profile: ";
+ message += profile_path.string();
+ SetError(error, std::move(message));
+ return ADBC_STATUS_INVALID_ARGUMENT;
+ }
+
+ profile.path = profile_path;
+ if (!config["version"].is_integer()) {
+ std::string message =
+ "Profile version is not an integer in profile '" +
profile_path.string() + "'";
+ SetError(error, std::move(message));
+ return ADBC_STATUS_INVALID_ARGUMENT;
+ }
+
+ const auto version = config["version"].value_or(int64_t(1));
+ switch (version) {
+ case 1:
+ break;
+ default: {
+ std::string message =
+ "Profile version '" + std::to_string(version) +
+ "' is not supported by this driver manager. Profile: " +
profile_path.string();
+ SetError(error, std::move(message));
+ return ADBC_STATUS_INVALID_ARGUMENT;
+ }
+ }
+
+ profile.driver = config["driver"].value_or(""s);
+
+ auto options = config.at_path("options");
+ if (!options.is_table()) {
+ std::string message =
+ "Profile options is not a table in profile '" + profile_path.string()
+ "'";
+ SetError(error, std::move(message));
+ return ADBC_STATUS_INVALID_ARGUMENT;
+ }
+
+ auto* options_table = options.as_table();
+ ProfileVisitor v{profile, profile_path, error};
+ if (!v.VisitTable("", *options_table)) {
+ return ADBC_STATUS_INVALID_ARGUMENT;
+ }
+
+ return ADBC_STATUS_OK;
+}
+
+SearchPaths GetProfileSearchPaths(const char* additional_search_path_list) {
+ SearchPaths search_paths;
+ {
+ std::vector<std::filesystem::path> additional_paths;
+ if (additional_search_path_list) {
+ additional_paths = InternalAdbcParsePath(additional_search_path_list);
+ }
+
+ for (const auto& path : additional_paths) {
+ search_paths.emplace_back(SearchPathSource::kAdditional, path);
+ }
+ }
+
+ {
+ auto env_paths = GetEnvPaths(kAdbcProfilePath);
+ search_paths.insert(search_paths.end(), env_paths.begin(),
env_paths.end());
+ }
+
+#if ADBC_CONDA_BUILD
+#ifdef _WIN32
+ const wchar_t* conda_name = L"CONDA_PREFIX";
+#else
+ const char* conda_name = "CONDA_PREFIX";
+#endif // _WIN32
+
+ auto venv = GetEnvPaths(conda_name);
+ for (const auto& [_, venv_path] : venv) {
+ search_paths.emplace_back(SearchPathSource::kConda,
+ venv_path / "etc" / "adbc" / "profiles");
+ }
+#else
+ search_paths.emplace_back(SearchPathSource::kDisabledAtCompileTime, "Conda
prefix");
+#endif // ADBC_CONDA_BUILD
+
+#ifdef _WIN32
+ const wchar_t* profiles_dir = L"Profiles";
+#elif defined(__APPLE__)
+ const char* profiles_dir = "Profiles";
+#else
+ const char* profiles_dir = "profiles";
+#endif // defined(_WIN32)
+
+ auto user_dir = InternalAdbcUserConfigDir().parent_path() / profiles_dir;
+ search_paths.emplace_back(SearchPathSource::kUser, user_dir);
+ return search_paths;
+}
+
/// Hold the driver DLL and the driver release callback in the driver struct.
struct ManagerDriverState {
// The original release callback
@@ -1417,6 +1775,7 @@ struct TempDatabase {
AdbcDriverInitFunc init_func = nullptr;
AdbcLoadFlags load_flags = ADBC_LOAD_FLAG_ALLOW_RELATIVE_PATHS;
std::string additional_search_path_list;
+ AdbcConnectionProfileProvider profile_provider = nullptr;
};
/// Temporary state while the database is being configured.
@@ -1425,14 +1784,14 @@ struct TempConnection {
std::unordered_map<std::string, std::string> bytes_options;
std::unordered_map<std::string, int64_t> int_options;
std::unordered_map<std::string, double> double_options;
+ AdbcConnectionProfile* connection_profile = nullptr;
};
static const char kDefaultEntrypoint[] = "AdbcDriverInit";
} // namespace
// Other helpers (intentionally not in an anonymous namespace so they can be
tested)
-ADBC_EXPORT
-std::filesystem::path InternalAdbcUserConfigDir() {
+ADBC_EXPORT std::filesystem::path InternalAdbcUserConfigDir() {
std::filesystem::path config_dir;
#if defined(_WIN32)
// SHGetFolderPath is just an alias to SHGetKnownFolderPath since Vista
@@ -1600,22 +1959,26 @@ std::optional<ParseDriverUriResult>
InternalAdbcParseDriverUri(std::string_view
std::string_view d = str.substr(0, pos);
if (str.size() <= pos + 1) {
- return ParseDriverUriResult{d, std::nullopt};
+ return ParseDriverUriResult{d, std::nullopt, std::nullopt};
}
#ifdef _WIN32
if (std::filesystem::exists(std::filesystem::path(str))) {
// No scheme, just a path
- return ParseDriverUriResult{str, std::nullopt};
+ return ParseDriverUriResult{str, std::nullopt, std::nullopt};
}
#endif
if (str[pos + 1] == '/') { // scheme is also driver
- return ParseDriverUriResult{d, str};
+ if (d == "profile" && str.size() > pos + 2) {
+ // found a profile URI "profile://"
+ return ParseDriverUriResult{"", std::nullopt, str.substr(pos + 3)};
+ }
+ return ParseDriverUriResult{d, str, std::nullopt};
}
// driver:scheme:.....
- return ParseDriverUriResult{d, str.substr(pos + 1)};
+ return ParseDriverUriResult{d, str.substr(pos + 1), std::nullopt};
}
// Direct implementations of API methods
@@ -1672,6 +2035,19 @@ AdbcStatusCode AdbcDatabaseNew(struct AdbcDatabase*
database, struct AdbcError*
return ADBC_STATUS_OK;
}
+AdbcStatusCode AdbcDriverManagerDatabaseSetProfileProvider(
+ struct AdbcDatabase* database, AdbcConnectionProfileProvider provider,
+ struct AdbcError* error) {
+ if (database->private_driver) {
+ SetError(error, "Cannot SetProfileProvider after AdbcDatabaseInit");
+ return ADBC_STATUS_INVALID_STATE;
+ }
+
+ TempDatabase* args = reinterpret_cast<TempDatabase*>(database->private_data);
+ args->profile_provider = provider;
+ return ADBC_STATUS_OK;
+}
+
AdbcStatusCode AdbcDatabaseGetOption(struct AdbcDatabase* database, const
char* key,
char* value, size_t* length,
struct AdbcError* error) {
@@ -1857,20 +2233,191 @@ AdbcStatusCode
AdbcDriverManagerDatabaseSetInitFunc(struct AdbcDatabase* databas
return ADBC_STATUS_OK;
}
+AdbcStatusCode AdbcProfileProviderFilesystem(const char* profile_name,
+ const char*
additional_search_path_list,
+ struct AdbcConnectionProfile* out,
+ struct AdbcError* error) {
+ if (profile_name == nullptr || strlen(profile_name) == 0) {
+ SetError(error, "Profile name is empty");
+ return ADBC_STATUS_INVALID_ARGUMENT;
+ }
+
+ if (!out) {
+ SetError(error, "Output profile is null");
+ return ADBC_STATUS_INVALID_ARGUMENT;
+ }
+
+ std::memset(out, 0, sizeof(*out));
+ std::filesystem::path profile_path(profile_name);
+ if (profile_path.has_extension()) {
+ if (HasExtension(profile_path, ".toml")) {
+ if (!std::filesystem::exists(profile_path)) {
+ SetError(error, "Profile file does not exist: " +
profile_path.string());
+ return ADBC_STATUS_NOT_FOUND;
+ }
+
+ FilesystemProfile profile;
+ CHECK_STATUS(LoadProfileFile(profile_path, profile, error));
+ profile.PopulateConnectionProfile(out);
+ return ADBC_STATUS_OK;
+ }
+ }
+
+ if (profile_path.is_absolute()) {
+ profile_path.replace_extension(".toml");
+
+ FilesystemProfile profile;
+ CHECK_STATUS(LoadProfileFile(profile_path, profile, error));
+ profile.PopulateConnectionProfile(out);
+ return ADBC_STATUS_OK;
+ }
+
+ SearchPaths search_paths =
GetProfileSearchPaths(additional_search_path_list);
+ SearchPaths extra_debug_info;
+ for (const auto& [source, search_path] : search_paths) {
+ if (source == SearchPathSource::kRegistry || source ==
SearchPathSource::kUnset ||
+ source == SearchPathSource::kDoesNotExist ||
+ source == SearchPathSource::kDisabledAtCompileTime ||
+ source == SearchPathSource::kDisabledAtRunTime ||
+ source == SearchPathSource::kOtherError) {
+ continue;
+ }
+
+ std::filesystem::path full_path = search_path / profile_path;
+ full_path.replace_extension(".toml");
+ if (std::filesystem::exists(full_path)) {
+ OwnedError intermediate_error;
+
+ FilesystemProfile profile;
+ auto status = LoadProfileFile(full_path, profile,
&intermediate_error.error);
+ if (status == ADBC_STATUS_OK) {
+ profile.PopulateConnectionProfile(out);
+ return ADBC_STATUS_OK;
+ } else if (status == ADBC_STATUS_INVALID_ARGUMENT) {
+ 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, SearchPathType::kProfile,
error_message);
+ SetError(error, std::move(error_message));
+ }
+ return status;
+ }
+
+ std::string message = "found ";
+ message += full_path.string();
+ message += " but: ";
+ if (intermediate_error.error.message) {
+ message += intermediate_error.error.message;
+ } else {
+ message += "could not load the profile";
+ }
+ extra_debug_info.emplace_back(SearchPathSource::kOtherError,
std::move(message));
+ }
+ }
+
+ search_paths.insert(search_paths.end(), extra_debug_info.begin(),
+ extra_debug_info.end());
+ std::string error_message = "Profile not found: " +
std::string(profile_name);
+ AddSearchPathsToError(search_paths, SearchPathType::kProfile, error_message);
+ SetError(error, std::move(error_message));
+ return ADBC_STATUS_NOT_FOUND;
+}
+
+struct ProfileGuard {
+ AdbcConnectionProfile profile;
+ explicit ProfileGuard() : profile{} {}
+ ~ProfileGuard() {
+ if (profile.release) {
+ profile.release(&profile);
+ }
+ }
+};
+
+AdbcStatusCode InternalInitializeProfile(TempDatabase* args,
+ const std::string_view profile,
+ struct AdbcError* error) {
+ if (!args->profile_provider) {
+ args->profile_provider = AdbcProfileProviderFilesystem;
+ }
+
+ ProfileGuard guard{};
+ CHECK_STATUS(args->profile_provider(
+ profile.data(), args->additional_search_path_list.c_str(),
&guard.profile, error));
+
+ const char* driver_name = nullptr;
+ AdbcDriverInitFunc init_func = nullptr;
+ CHECK_STATUS(
+ guard.profile.GetDriverName(&guard.profile, &driver_name, &init_func,
error));
+ if (driver_name != nullptr && strlen(driver_name) > 0) {
+ args->driver = driver_name;
+ }
+
+ if (init_func != nullptr) {
+ args->init_func = init_func;
+ }
+
+ const char** keys = nullptr;
+ const char** values = nullptr;
+ size_t num_options = 0;
+ const int64_t* int_values = nullptr;
+ const double* double_values = nullptr;
+
+ CHECK_STATUS(
+ guard.profile.GetOptions(&guard.profile, &keys, &values, &num_options,
error));
+ for (size_t i = 0; i < num_options; ++i) {
+ // use try_emplace so we only add the option if there isn't
+ // already an option with the same name
+ std::string processed;
+ CHECK_STATUS(ProcessProfileValue(values[i], processed, error));
+ args->options.try_emplace(keys[i], processed);
+ }
+
+ CHECK_STATUS(guard.profile.GetIntOptions(&guard.profile, &keys, &int_values,
+ &num_options, error));
+ for (size_t i = 0; i < num_options; ++i) {
+ // use try_emplace so we only add the option if there isn't
+ // already an option with the same name
+ args->int_options.try_emplace(keys[i], int_values[i]);
+ }
+
+ CHECK_STATUS(guard.profile.GetDoubleOptions(&guard.profile, &keys,
&double_values,
+ &num_options, error));
+ for (size_t i = 0; i < num_options; ++i) {
+ // use try_emplace so we only add the option if there isn't already an
option with the
+ // same name
+ args->double_options.try_emplace(keys[i], double_values[i]);
+ }
+
+ return ADBC_STATUS_OK;
+}
+
AdbcStatusCode AdbcDatabaseInit(struct AdbcDatabase* database, struct
AdbcError* error) {
if (!database->private_data) {
SetError(error, "Must call AdbcDatabaseNew before AdbcDatabaseInit");
return ADBC_STATUS_INVALID_STATE;
}
TempDatabase* args = reinterpret_cast<TempDatabase*>(database->private_data);
+ const auto profile_in_use = args->options.find("profile");
+ if (profile_in_use != args->options.end()) {
+ std::string_view profile = profile_in_use->second;
+ CHECK_STATUS(InternalInitializeProfile(args, profile, error));
+ args->options.erase("profile");
+ }
+
if (!args->init_func) {
const auto uri = args->options.find("uri");
if (args->driver.empty() && uri != args->options.end()) {
std::string owned_uri = uri->second;
auto result = InternalAdbcParseDriverUri(owned_uri);
- if (result && result->uri) {
- args->driver = std::string{result->driver};
- args->options["uri"] = std::string{*result->uri};
+ if (result) {
+ if (result->uri) {
+ args->driver = std::string{result->driver};
+ args->options["uri"] = std::string{*result->uri};
+ } else if (result->profile) {
+ args->options.erase("uri");
+ CHECK_STATUS(InternalInitializeProfile(args, *result->profile,
error));
+ }
}
} else if (!args->driver.empty() && uri == args->options.end()) {
std::string owned_driver = args->driver;
@@ -1879,6 +2426,8 @@ AdbcStatusCode AdbcDatabaseInit(struct AdbcDatabase*
database, struct AdbcError*
args->driver = std::string{result->driver};
if (result->uri) {
args->options["uri"] = std::string{*result->uri};
+ } else if (result->profile) {
+ CHECK_STATUS(InternalInitializeProfile(args, *result->profile,
error));
}
}
}
@@ -2217,6 +2766,7 @@ AdbcStatusCode AdbcConnectionInit(struct AdbcConnection*
connection,
SetError(error, "Database is not initialized");
return ADBC_STATUS_INVALID_ARGUMENT;
}
+
TempConnection* args =
reinterpret_cast<TempConnection*>(connection->private_data);
connection->private_data = nullptr;
std::unordered_map<std::string, std::string> options =
std::move(args->options);
diff --git a/go/adbc/drivermgr/arrow-adbc/adbc.h
b/go/adbc/drivermgr/arrow-adbc/adbc.h
index a55f645ed..57e665f84 100644
--- a/go/adbc/drivermgr/arrow-adbc/adbc.h
+++ b/go/adbc/drivermgr/arrow-adbc/adbc.h
@@ -1300,6 +1300,11 @@ AdbcStatusCode AdbcDatabaseGetOptionInt(struct
AdbcDatabase* database, const cha
/// Options may be set before AdbcDatabaseInit. Some drivers may
/// support setting options after initialization as well.
///
+/// Driver managers may treat some option keys as manager-reserved and
+/// handle them without forwarding them to the underlying driver. In
+/// particular, the option key "profile" is reserved for connection
+/// profiles and must not be implemented or interpreted by drivers.
+///
/// \param[in] database The database.
/// \param[in] key The option to set.
/// \param[in] value The option value.
diff --git a/go/adbc/drivermgr/arrow-adbc/adbc_driver_manager.h
b/go/adbc/drivermgr/arrow-adbc/adbc_driver_manager.h
index cf968ffdb..9418e0072 100644
--- a/go/adbc/drivermgr/arrow-adbc/adbc_driver_manager.h
+++ b/go/adbc/drivermgr/arrow-adbc/adbc_driver_manager.h
@@ -175,6 +175,186 @@ AdbcStatusCode
AdbcDriverManagerDatabaseSetAdditionalSearchPathList(
ADBC_EXPORT
const char* AdbcStatusCodeMessage(AdbcStatusCode code);
+/// \defgroup adbc-driver-manager-connection-profile Connection Profiles
+/// The ADBC driver manager can support "connection profiles" that specify
+/// a driver and options to use when connecting. This allows users to
+/// specify connection information in a file or environment variable, and have
the
+/// driver manager load the appropriate driver and set options accordingly.
+///
+/// This allows creating reusable connection configurations for sharing and
distribution
+/// without needing to hardcode driver names and options in application code.
Profiles
+/// will be loaded during DatabaseInit before attempting to initialize the
driver. Any
+/// options specified by the profile will be applied but will not override
options
+/// that have already been set using DatabaseSetOption.
+///
+/// To facilitate customization, we define an interface for implementing a
Connection
+/// Profile object along with a provider function definition which can be set
into
+/// the driver manager to allow for customized profile loading.
+///
+/// A profile can be specified to the Driver Manager in one of two ways,
+/// which will invoke the profile provider during the call to DatabaseInit:
+///
+/// 1. The "profile" option can be set using DatabaseSetOption with the name
of the
+/// profile to load.
+/// 2. The "uri" being used can have the form "profile://<profile>"
+///
+/// @{
+
+/// \brief Abstract interface for connection profile providers
+struct ADBC_EXPORT AdbcConnectionProfile {
+ /// \brief Opaque implementation-defined state.
+ /// This field is NULL if the profile is uninitialized/freed (but
+ /// it need not have a value even if the profile is initialized).
+ void* private_data;
+
+ /// \brief Release the profile and perform any cleanup.
+ void (*release)(struct AdbcConnectionProfile* profile);
+
+ /// \brief Get the driver to use as specified by this profile.
+ ///
+ /// It is not required that a profile specify a driver. If the options
+ // can be reusable across drivers, then the profile does not need to specify
+ /// a driver (if this provides an empty string or nullptr then the driver
+ /// must be defined by other means, e.g. by the driver / uri options).
+ ///
+ /// \param[in] profile The profile to query.
+ /// \param[out] driver_name The name of the driver to use, or NULL if not
specified.
+ /// \param[out] init_func The init function to use for the driver, or NULL
if not
+ /// specified.
+ /// \param[out] error An optional location to return an error message
+ AdbcStatusCode (*GetDriverName)(struct AdbcConnectionProfile* profile,
+ const char** driver_name,
AdbcDriverInitFunc* init_func,
+ struct AdbcError* error);
+
+ /// \brief Get the string options specified by the profile
+ ///
+ /// The keys and values returned by this function are owned by the profile
+ /// object itself and do not need to be freed or managed by the caller.
+ /// They must not be accessed after calling release on the profile.
+ ///
+ /// The profile can also indicate that a value should be pulled from the
environment
+ /// by having a value in the form `env_var(ENV_VAR_NAME)`. If the driver
+ /// manager encounters a value of this form, it will replace it with the
actual value
+ /// of the environment variable `ENV_VAR_NAME` before setting the option.
This
+ /// is only valid for option *values* not *keys*.
+ ///
+ /// \param[in] profile The profile to query.
+ /// \param[out] keys The keys of the options specified by the profile.
+ /// \param[out] values The values of the options specified by the profile.
+ /// \param[out] num_options The number of options specified by the profile,
+ /// consumers must not access keys or values beyond this count.
+ /// \param[out] error An optional location to return an error message
+ AdbcStatusCode (*GetOptions)(struct AdbcConnectionProfile* profile, const
char*** keys,
+ const char*** values, size_t* num_options,
+ struct AdbcError* error);
+
+ /// \brief Get the integer options specified by the profile
+ ///
+ /// The keys and values returned by this function are owned by the profile
+ /// object itself and do not need to be freed or managed by the caller. They
must not be
+ /// accessed after calling release on the profile.
+ ///
+ /// Values returned by this function will be set using the
DatabaseSetOptionInt function
+ /// on the database object being initialized. If the driver does not support
the
+ /// DatabaseSetOptionInt function, then options should only be returned as
strings.
+ ///
+ /// \param[in] profile The profile to query.
+ /// \param[out] keys The keys of the options specified by the profile.
+ /// \param[out] values The values of the options specified by the profile.
+ /// \param[out] num_options The number of options specified by the profile,
+ /// consumers must not access keys or values beyond this count.
+ /// \param[out] error An optional location to return an error message
+ AdbcStatusCode (*GetIntOptions)(struct AdbcConnectionProfile* profile,
+ const char*** keys, const int64_t** values,
+ size_t* num_options, struct AdbcError*
error);
+
+ /// \brief Get the double options specified by the profile
+ ///
+ /// The keys and values returned by this function are owned by the profile
+ /// object itself and do not need to be freed or managed by the caller. They
must not be
+ /// accessed after calling release on the profile.
+ ///
+ /// Values returned by this function will be set using the
DatabaseSetOptionDouble
+ /// function on the database object being initialized. If the driver does
not support
+ /// the DatabaseSetOptionDouble function, then options should only be
returned as
+ /// strings.
+ ///
+ /// \param[in] profile The profile to query.
+ /// \param[out] keys The keys of the options specified by the profile.
+ /// \param[out] values The values of the options specified by the profile.
+ /// \param[out] num_options The number of options specified by the profile,
+ /// consumers must not access keys or values beyond this count.
+ /// \param[out] error An optional location to return an error message
+ AdbcStatusCode (*GetDoubleOptions)(struct AdbcConnectionProfile* profile,
+ const char*** keys, const double** values,
+ size_t* num_options, struct AdbcError*
error);
+};
+
+/// \brief Common definition for a connection profile provider
+///
+/// \param[in] profile_name The name of the profile to load. This is the value
of the
+/// "profile" option or the profile specified in the URI.
+/// \param[in] additional_search_path_list A list of additional paths to
search for
+/// profiles, delimited by the OS specific path list separator.
+/// \param[out] out The profile to return. The caller will take ownership of
the profile
+/// and is responsible for calling release on it when finished.
+/// \param[out] error An optional location to return an error message if
necessary.
+typedef AdbcStatusCode (*AdbcConnectionProfileProvider)(
+ const char* profile_name, const char* additional_search_path_list,
+ struct AdbcConnectionProfile* out, struct AdbcError* error);
+
+/// \brief Set a custom connection profile provider for the driver manager.
+///
+/// If no provider is set, the driver manager will use a default,
filesystem-based
+/// provider which will look for profiles in the following locations if not
given an
+/// absolute path to a file:
+///
+/// 1. The environment variable ADBC_PROFILE_PATH, which is a list of paths to
search for
+/// profiles.
+/// 2. The user-level configuration directory (e.g. ~/.config/adbc/profiles on
Linux).
+///
+/// The filesystem-based profile looks for a file named <profile_name>.toml if
there is
+/// no extension provided, attempting to parse the toml file for the profile
information.
+/// If the file is found and parsed successfully, the options specified in the
profile
+/// which have not already been set will be set as if by DatabaseSetOption
just before
+/// initialization as part of DatabaseInit.
+///
+/// For file-based profiles the expected format is as follows:
+/// ```toml
+/// version = 1
+/// driver = "driver_name"
+///
+/// [options]
+/// option1 = "value1"
+/// option2 = 42
+/// option3 = 3.14
+/// ```
+///
+/// Boolean options will be converted to string equivalents of "true" or
"false".
+///
+/// \param[in] database The database to set the profile provider for.
+/// \param[in] provider The profile provider to use. If NULL, the default
filesystem-based
+/// provider will be used if a profile is needed.
+/// \param[out] error An optional location to return an error message if
necessary
+ADBC_EXPORT
+AdbcStatusCode AdbcDriverManagerDatabaseSetProfileProvider(
+ struct AdbcDatabase* database, AdbcConnectionProfileProvider provider,
+ struct AdbcError* error);
+
+/// \brief Default Filesystem-based profile provider for the driver manager.
+///
+/// We expose this so that consumers would be able to write a provider that
falls back on
+/// the default filesystem-based provider if their custom provider fails to
find a
+/// profile. This allows for more flexible provider implementations that can
still
+/// leverage the default behavior when needed.
+ADBC_EXPORT
+AdbcStatusCode AdbcProfileProviderFilesystem(const char* profile_name,
+ const char*
additional_search_path_list,
+ struct AdbcConnectionProfile* out,
+ struct AdbcError* error);
+
+/// @}
+
#endif // ADBC_DRIVER_MANAGER_H
#ifdef __cplusplus