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

Reply via email to