Skip to content

Commit e665311

Browse files
authored
Add ADBC connection support to config (#56)
- Add ADBCConnection type with extra="allow" for pure passthrough - All driver-specific params passed through as-is (no translation) - Users provide driver-native keys (e.g., adbc.snowflake.sql.account) - Add adbc_snowflake example
1 parent 50a54dd commit e665311

6 files changed

Lines changed: 161 additions & 1 deletion

File tree

examples/adbc_snowflake/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
sidemantic.yaml
2+
snowflake_key.p8
3+
snowflake_key.pub

examples/adbc_snowflake/README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# ADBC + Snowflake Example
2+
3+
Uses ADBC with key-pair auth to connect to Snowflake.
4+
5+
## Prerequisites
6+
7+
```bash
8+
pip install adbc-driver-manager
9+
dbc install snowflake
10+
```
11+
12+
## Config
13+
14+
Copy `sidemantic.yaml.example` to `sidemantic.yaml` and fill in your Snowflake details.
15+
16+
## Usage
17+
18+
```bash
19+
sidemantic query "SELECT orders.total_revenue, orders.region FROM orders"
20+
```
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
cubes:
2+
- name: orders
3+
sql_table: demo.orders
4+
description: E-commerce orders
5+
6+
dimensions:
7+
- name: order_id
8+
sql: order_id
9+
type: number
10+
primary_key: true
11+
12+
- name: customer_id
13+
sql: customer_id
14+
type: number
15+
16+
- name: status
17+
sql: status
18+
type: string
19+
20+
- name: region
21+
sql: region
22+
type: string
23+
24+
- name: order_date
25+
sql: order_date
26+
type: time
27+
28+
measures:
29+
- name: total_orders
30+
type: count
31+
description: Total number of orders
32+
33+
- name: total_revenue
34+
type: sum
35+
sql: amount
36+
description: Total revenue from orders
37+
38+
- name: avg_order_value
39+
type: avg
40+
sql: amount
41+
description: Average order value

examples/adbc_snowflake/setup.sql

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
-- Setup script for ADBC + Snowflake demo
2+
-- Run this in Snowflake to create test data
3+
4+
-- Create schema if needed
5+
CREATE SCHEMA IF NOT EXISTS demo;
6+
7+
-- Create orders table
8+
CREATE OR REPLACE TABLE demo.orders (
9+
order_id NUMBER PRIMARY KEY,
10+
customer_id NUMBER,
11+
status VARCHAR(50),
12+
region VARCHAR(50),
13+
amount NUMBER(10,2),
14+
order_date TIMESTAMP
15+
);
16+
17+
-- Insert sample data
18+
INSERT INTO demo.orders (order_id, customer_id, status, region, amount, order_date)
19+
VALUES
20+
(1, 101, 'completed', 'North', 150.00, '2024-01-15 10:30:00'),
21+
(2, 102, 'completed', 'South', 275.50, '2024-01-15 14:22:00'),
22+
(3, 103, 'pending', 'East', 89.99, '2024-01-16 09:15:00'),
23+
(4, 101, 'completed', 'North', 432.00, '2024-01-16 16:45:00'),
24+
(5, 104, 'cancelled', 'West', 67.25, '2024-01-17 11:00:00'),
25+
(6, 105, 'completed', 'South', 299.99, '2024-01-17 13:30:00'),
26+
(7, 102, 'completed', 'East', 185.00, '2024-01-18 10:00:00'),
27+
(8, 106, 'pending', 'North', 520.00, '2024-01-18 15:20:00'),
28+
(9, 103, 'completed', 'West', 145.75, '2024-01-19 09:45:00'),
29+
(10, 107, 'completed', 'South', 88.50, '2024-01-19 14:10:00'),
30+
(11, 108, 'completed', 'North', 675.00, '2024-01-20 11:30:00'),
31+
(12, 101, 'pending', 'East', 234.99, '2024-01-20 16:00:00'),
32+
(13, 109, 'completed', 'West', 412.50, '2024-01-21 10:15:00'),
33+
(14, 110, 'cancelled', 'South', 55.00, '2024-01-21 12:45:00'),
34+
(15, 102, 'completed', 'North', 189.99, '2024-01-22 09:30:00'),
35+
(16, 111, 'completed', 'East', 333.33, '2024-01-22 14:00:00'),
36+
(17, 103, 'completed', 'West', 267.80, '2024-01-23 11:20:00'),
37+
(18, 112, 'pending', 'South', 445.00, '2024-01-23 15:45:00'),
38+
(19, 104, 'completed', 'North', 128.50, '2024-01-24 10:00:00'),
39+
(20, 113, 'completed', 'East', 599.99, '2024-01-24 13:30:00');
40+
41+
-- Verify data
42+
SELECT region, status, COUNT(*) as orders, SUM(amount) as revenue
43+
FROM demo.orders
44+
GROUP BY region, status
45+
ORDER BY region, status;
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
models_dir: .
2+
3+
connection:
4+
type: adbc
5+
driver: snowflake
6+
adbc.snowflake.sql.account: ORG-ACCOUNT
7+
adbc.snowflake.sql.db: YOUR_DATABASE
8+
adbc.snowflake.sql.warehouse: COMPUTE_WH
9+
adbc.snowflake.sql.auth_type: auth_jwt
10+
adbc.snowflake.sql.client_option.jwt_private_key: snowflake_key.p8
11+
username: your_service_user

sidemantic/config.py

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,34 @@ class SparkConnection(BaseModel):
7070
password: str | None = Field(default=None, description="Password (optional)")
7171

7272

73+
class ADBCConnection(BaseModel):
74+
"""ADBC (Arrow Database Connectivity) connection configuration.
75+
76+
Uses dbc-installed drivers for efficient Arrow-native database access.
77+
Pass driver-specific parameters directly - they're passed through as-is.
78+
79+
Prerequisites:
80+
pip install adbc-driver-manager
81+
dbc install <driver> # e.g., dbc install snowflake
82+
83+
Example (Snowflake with key-pair auth):
84+
connection:
85+
type: adbc
86+
driver: snowflake
87+
adbc.snowflake.sql.account: ORG-ACCOUNT
88+
adbc.snowflake.sql.db: MY_DATABASE
89+
adbc.snowflake.sql.warehouse: COMPUTE_WH
90+
adbc.snowflake.sql.auth_type: auth_jwt
91+
adbc.snowflake.sql.client_option.jwt_private_key: key.p8
92+
username: service_user
93+
"""
94+
95+
model_config = ConfigDict(extra="allow")
96+
97+
type: Literal["adbc"] = "adbc"
98+
driver: str = Field(..., description="ADBC driver name (e.g., snowflake, postgresql, bigquery)")
99+
100+
73101
class PostgresServerConfig(BaseModel):
74102
"""PostgreSQL wire protocol server configuration (ALPHA).
75103
@@ -88,6 +116,7 @@ class PostgresServerConfig(BaseModel):
88116
| ClickHouseConnection
89117
| SnowflakeConnection
90118
| SparkConnection
119+
| ADBCConnection
91120
)
92121

93122

@@ -150,7 +179,7 @@ def resolve_paths(self, base_dir: Path | None = None) -> "SidemanticConfig":
150179
if not models_path.is_absolute():
151180
models_path = (base / models_path).resolve()
152181

153-
# Resolve connection paths if DuckDB
182+
# Resolve connection paths
154183
connection = self.connection
155184
if connection and isinstance(connection, DuckDBConnection) and connection.path != ":memory:":
156185
db_p = Path(connection.path)
@@ -288,5 +317,16 @@ def build_connection_string(config: SidemanticConfig) -> str:
288317
f"{config.connection.host}:{config.connection.port}/{config.connection.database}"
289318
)
290319
return f"spark://{config.connection.host}:{config.connection.port}/{config.connection.database}"
320+
elif isinstance(config.connection, ADBCConnection):
321+
from urllib.parse import quote
322+
323+
conn = config.connection
324+
# Pass through all fields except type and driver
325+
params = []
326+
for key, value in conn.model_dump(exclude={"type", "driver"}, exclude_none=True).items():
327+
params.append(f"{quote(key, safe='')}={quote(str(value), safe='')}")
328+
329+
query = "&".join(params)
330+
return f"adbc://{conn.driver}?{query}" if query else f"adbc://{conn.driver}"
291331
else:
292332
raise ValueError(f"Unknown connection type: {type(config.connection)}")

0 commit comments

Comments
 (0)