ADBC Driver Manager and Connection Profiles¶
Note
This document describes using the driver manager to load drivers. The driver manager is not required to use ADBC in general but it allows loading drivers written in a different language from the application and improves the experience when using multiple drivers in a single application. For more information on how the driver manager works see How Drivers and the Driver Manager Work Together.
There are two ways to pass database options through the driver manager:
Directly specifying all options as arguments to the driver manager in your application code (see the SetOption family of functions in ADBC API Standard for details).
Referring to a connection profile which contains options, and optionally overriding some options by setting them through the above method.
Connection profiles combine a driver and database 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:
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:
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:
# The version is required.
profile_version = 1
# The driver is optional, but if not provided it must be set by the application.
driver = "snowflake"
# The Options table is required, even if empty
[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
profile_version¶
Required: Yes
Type: Integer
Supported values:
1
The profile_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 or driver manifest 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).
If the application specifies a driver, and specifies a profile that itself references a driver, the two must match exactly, or it is an error.
The driver will be loaded identically to if it was specified via AdbcDatabaseSetOption("driver", "<driver>").
For more detils, see ADBC Driver Manager and Manifests.
Options Section¶
The [Options] section contains driver-specific configuration options to apply to the AdbcDatabase upon creation. This section must be present, even if empty. Options can be of the following types:
- String values
Applied using
AdbcDatabaseSetOption()adbc.snowflake.sql.account = "mycompany" adbc.snowflake.sql.warehouse = "COMPUTE_WH"
- Integer values
Applied using
AdbcDatabaseSetOptionInt()adbc.snowflake.sql.client_session_keep_alive_heartbeat_frequency = 3600
- Double values
Applied using
AdbcDatabaseSetOptionDouble()adbc.snowflake.sql.client_timeout = 30.5
- Boolean values
Converted to strings
"true"or"false"and applied usingAdbcDatabaseSetOption()adbc.snowflake.sql.client_session_keep_alive = true
Warning
If the application overrides option values but uses a different type for the value than the profile does, it is undefined which will take effect.
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:
profile_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 placeholder with the contents of environment variable VAR_NAME. If the environment variable is not set, the placeholder is replaced with an empty string and processing of the rest of the value continues (e.g. "foo{{ env_var(MISSING) }}bar" becomes "foobar").
Profile Search Locations¶
When using a profile name (not an absolute path), the driver manager searches for <profile_name>.toml in the following locations:
Additional Search Paths (if configured via
additional_profile_search_path_listoption)ADBC_PROFILE_PATH environment variable (colon-separated on Unix, semicolon-separated on Windows)
Conda Environment (if built with Conda support and
CONDA_PREFIXis set):$CONDA_PREFIX/etc/adbc/profiles/
User Configuration Directory:
Linux:
$XDG_CONFIG_HOME/adbc/profilesif set, else~/.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:
// 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
profile_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:
// 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
profile_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
profile_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):
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):
Driver defaults
Profile options (from
[Options]section)Options set via
AdbcDatabaseSetOption()beforeAdbcDatabaseInit()
Example:
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"
Note
Options of different types are set separately. For example, if the profile defines an option with an integer value, and the application sets the same option but with a string value, it is implementation-defined as to which value will take precedence. If the application were to use an integer value instead, then the application value would take precedence as expected.
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:
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:
typedef AdbcStatusCode (*AdbcConnectionProfileProvider)(
const char* profile_name,
const char* additional_search_path_list,
struct AdbcConnectionProfile* out,
struct AdbcError* error);
Example Implementation¶
// 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:
# Development
export ADBC_PROFILE=snowflake_dev
# Production
export ADBC_PROFILE=snowflake_prod
Application code:
const char* profile = getenv("ADBC_PROFILE");
if (!profile) profile = "default";
AdbcDatabaseSetOption(&database, "profile", profile, &error);
Credential Management¶
Store credentials separately from code:
[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:
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:
#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:
[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:
[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:
[Driver Manager] Must provide 'driver' parameter
(or encode driver in 'uri' parameter)
Best Practices¶
Use environment variables for secrets: Never store credentials directly in profile files.
# Good password = "{{ env_var(DB_PASSWORD) }}" # Bad password = "my_secret_password"
Organize profiles hierarchically: Group related profiles in subdirectories using additional search paths.
Document profile schemas: Maintain documentation of required environment variables for each profile.
Version control without secrets: Profile files can be version controlled when using
{{ env_var(VAR_NAME) }}for sensitive values.Test profile loading: Verify profiles load correctly in CI/CD pipelines.
Use meaningful names: Name profiles descriptively (e.g.,
snowflake_prod_analyticsvs.profile1).Validate environment variables: Check that required environment variables are set before calling
AdbcDatabaseInit().
API Reference¶
Setting a Profile Provider¶
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 configureprovider: Profile provider function, orNULLfor default filesystem providererror: Optional error output
Returns: ADBC_STATUS_OK on success, error code otherwise.
Setting Additional Search Paths¶
This can be done via the additional_profile_search_path_list option. It
must be set before AdbcDatabaseInit(). The value of this option is an
OS-specific delimited list (: on Unix, ; on Windows), or NULL to
clear.
Example:
// Unix/Linux/macOS
AdbcDatabaseSetOption(
&database,
"additional_profile_search_path_list",
"/opt/app/profiles:/etc/app/profiles",
&error);
// Windows
AdbcDatabaseSetOption(
&database,
"additional_profile_search_path_list",
"C:\\App\\Profiles;C:\\ProgramData\\App\\Profiles",
&error);
See Also¶
How Drivers and the Driver Manager Work Together - Driver Manager overview
ADBC API Standard - Driver specification and options
Driver Manager - CPP Driver Manager Reference