Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
550 changes: 534 additions & 16 deletions c/driver_manager/adbc_driver_manager.cc

Large diffs are not rendered by default.

370 changes: 366 additions & 4 deletions c/driver_manager/adbc_driver_manager_test.cc

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions c/include/arrow-adbc/adbc.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
177 changes: 177 additions & 0 deletions c/include/arrow-adbc/adbc_driver_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,183 @@ AdbcStatusCode AdbcDriverManagerDatabaseSetAdditionalSearchPathList(
ADBC_EXPORT
const char* AdbcStatusCodeMessage(AdbcStatusCode code);

/// \defgroup adbc-driver-manager-connection-profile Connection Profiles
/// Similar to odbc.ini, 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);
Comment on lines +210 to +211
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Does the manager call release or does the application?)

Copy link
Member Author

@zeroshade zeroshade Jan 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The manager calls release currently, in general the application will never interact with profile objects unless they explicitly want to.


/// \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] error An optional location to return an error message
AdbcStatusCode (*GetDriverName)(struct AdbcConnectionProfile* profile,
const char** driver_name, struct AdbcError* error);
Comment on lines +223 to +224
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
AdbcStatusCode (*GetDriverName)(struct AdbcConnectionProfile* profile,
const char** driver_name, struct AdbcError* error);
AdbcStatusCode (*GetDriverName)(struct AdbcConnectionProfile* profile,
const char** driver_name, AdbcDriverInitFunc* init_func, struct AdbcError* error);

Is there any value to allowing a profile to optionally specify the init function directly? (R could maybe use this to find R package versions of drivers)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The way the code is set up doesn't allow for this. If there is an init func set then the profile handling is currently entirely skipped and it's up to the init_func to handle everything. Given the way we envision profiles working, I don't think that it makes sense for the profile to set or specify the init function.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you'd just set args->init_func instead of args->driver (understood if you'd prefer not to)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I personally prefer not, but I'm open to it if others think it's worthwhile.


/// \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
Comment on lines +321 to +327
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you want to wrap this in a top level [profile] item (like pyproject.toml's [project] or Cargo.toml's [workspace])?

Suggested change
/// version = 1
/// driver = "driver_name"
///
/// [options]
/// option1 = "value1"
/// option2 = 42
/// option3 = 3.14
/// [adbc.profile]
/// version = 1
/// driver = "driver_name"
///
/// [options]
/// option1 = "value1"
/// option2 = 42
/// option3 = 3.14

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally I was trying to avoid having to do that. Is there any benefit to doing so?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just seemed like a common idiom in .toml configs, perhaps to allow for future expansion (e.g., if you ever want >1 profile to live in one .toml)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think after a lot of discussion between myself, @lidavidm and @ianmcook we came to the decision that we don't want more than 1 profile to live in one .toml file haha 😄

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having multiple connection profiles defined in a single TOML file could potentially be nice, but it complicates the profile search procedure, and I worry that it could confuse users.

/// ```
///
/// 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 AdbcFilesystemProfileProvider(const char* profile_name,
const char* additional_search_path_list,
struct AdbcConnectionProfile* out,
struct AdbcError* error);

/// @}

#endif // ADBC_DRIVER_MANAGER_H

#ifdef __cplusplus
Expand Down
Loading
Loading