Skip to content

Commit c97f99a

Browse files
feat(duckdb-driver): Add support for installing and loading DuckDB Community Extensions (#9169)
* feat(duckdb-driver): Add support for installing and loading DuckDB Community Extensions * remarks on PR --------- Co-authored-by: Konstantin Burkalev <[email protected]>
1 parent e7901a7 commit c97f99a

File tree

4 files changed

+61
-32
lines changed

4 files changed

+61
-32
lines changed

docs/pages/product/configuration/data-sources/duckdb.mdx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ deployment][ref-demo-deployment] in Cube Cloud.
7373
| `CUBEJS_DB_DUCKDB_S3_URL_STYLE` | To choose the S3 URL style(vhost or path) | 'vhost' or 'path' |||
7474
| `CUBEJS_DB_DUCKDB_S3_SESSION_TOKEN` | The token for the S3 session | A valid Session Token |||
7575
| `CUBEJS_DB_DUCKDB_EXTENSIONS` | A comma-separated list of DuckDB extensions to install and load | A comma-separated list of DuckDB extensions |||
76+
| `CUBEJS_DB_DUCKDB_COMMUNITY_EXTENSIONS` | A comma-separated list of DuckDB community extensions to install and load | A comma-separated list of DuckDB community extensions |||
7677
| `CUBEJS_CONCURRENCY` | The number of [concurrent queries][ref-data-source-concurrency] to the data source | A valid number ||
7778

7879
[ref-data-source-concurrency]: /product/configuration/concurrency#data-source-concurrency
@@ -133,4 +134,4 @@ connections are made over HTTPS.
133134
/product/caching/using-pre-aggregations#pre-aggregation-build-strategies
134135
[ref-schema-ref-types-formats-countdistinctapprox]: /reference/data-model/types-and-formats#count_distinct_approx
135136
[self-preaggs-batching]: #batching
136-
[ref-demo-deployment]: /product/deployment/cloud/deployments#demo-deployments
137+
[ref-demo-deployment]: /product/deployment/cloud/deployments#demo-deployments

docs/pages/reference/configuration/environment-variables.mdx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,14 @@ A comma-separated list of DuckDB extensions to install and load.
372372
| ------------------------------------------- | ---------------------- | --------------------- |
373373
| A comma-separated list of DuckDB extensions | N/A | N/A |
374374

375+
## `CUBEJS_DB_DUCKDB_COMMUNITY_EXTENSIONS`
376+
377+
A comma-separated list of DuckDB community extensions to install and load.
378+
379+
| Possible Values | Default in Development | Default in Production |
380+
| ----------------------------------------------------- | ---------------------- | --------------------- |
381+
| A comma-separated list of DuckDB community extensions | N/A | N/A |
382+
375383
## `CUBEJS_DB_ELASTIC_APIKEY_ID`
376384

377385
The [ID of the API key from elastic.co][elastic-docs-api-keys]. Required when

packages/cubejs-backend-shared/src/env.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1773,6 +1773,19 @@ const variables: Record<string, (...args: any) => any> = {
17731773
}
17741774
return [];
17751775
},
1776+
duckdbCommunityExtensions: ({
1777+
dataSource
1778+
}: {
1779+
dataSource: string,
1780+
}) => {
1781+
const extensions = process.env[
1782+
keyByDataSource('CUBEJS_DB_DUCKDB_COMMUNITY_EXTENSIONS', dataSource)
1783+
];
1784+
if (extensions) {
1785+
return extensions.split(',').map(e => e.trim());
1786+
}
1787+
return [];
1788+
},
17761789
/** ***************************************************************
17771790
* Presto Driver *
17781791
**************************************************************** */

packages/cubejs-duckdb-driver/src/DuckDBDriver.ts

Lines changed: 38 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,38 @@ export class DuckDBDriver extends BaseDriver implements DriverInterface {
5757
return super.toGenericType(columnType.toLowerCase());
5858
}
5959

60+
private async installExtensions(extensions: string[], execAsync: (sql: string, ...params: any[]) => Promise<void>, repository: string = ''): Promise<void> {
61+
repository = repository ? ` FROM ${repository}` : '';
62+
for (const extension of extensions) {
63+
try {
64+
await execAsync(`INSTALL ${extension}${repository}`);
65+
} catch (e) {
66+
if (this.logger) {
67+
console.error(`DuckDB - error on installing ${extension}`, { e });
68+
}
69+
// DuckDB will lose connection_ref on connection on error, this will lead to broken connection object
70+
throw e;
71+
}
72+
}
73+
}
74+
75+
private async loadExtensions(extensions: string[], execAsync: (sql: string, ...params: any[]) => Promise<void>): Promise<void> {
76+
for (const extension of extensions) {
77+
try {
78+
await execAsync(`LOAD ${extension}`);
79+
} catch (e) {
80+
if (this.logger) {
81+
console.error(`DuckDB - error on loading ${extension}`, { e });
82+
}
83+
// DuckDB will lose connection_ref on connection on error, this will lead to broken connection object
84+
throw e;
85+
}
86+
}
87+
}
88+
6089
protected async init(): Promise<InitPromise> {
6190
const token = this.config.motherDuckToken || getEnv('duckdbMotherDuckToken', this.config);
6291
const dbPath = this.config.databasePath || getEnv('duckdbDatabasePath', this.config);
63-
6492
// Determine the database URL based on the provided db_path or token
6593
let dbUrl: string;
6694
if (dbPath) {
@@ -121,7 +149,7 @@ export class DuckDBDriver extends BaseDriver implements DriverInterface {
121149
value: getEnv('duckdbS3SessionToken', this.config),
122150
}
123151
];
124-
152+
125153
for (const { key, value } of configuration) {
126154
if (value) {
127155
try {
@@ -137,34 +165,13 @@ export class DuckDBDriver extends BaseDriver implements DriverInterface {
137165
}
138166

139167
// Install & load extensions if configured in env variable.
140-
const extensions = getEnv('duckdbExtensions', this.config);
141-
for (const extension of extensions) {
142-
try {
143-
await execAsync(`INSTALL ${extension}`);
144-
} catch (e) {
145-
if (this.logger) {
146-
console.error(`DuckDB - error on installing ${extension}`, {
147-
e
148-
});
149-
}
150-
151-
// DuckDB will lose connection_ref on connection on error, this will lead to broken connection object
152-
throw e;
153-
}
154-
155-
try {
156-
await execAsync(`LOAD ${extension}`);
157-
} catch (e) {
158-
if (this.logger) {
159-
console.error(`DuckDB - error on loading ${extension}`, {
160-
e
161-
});
162-
}
163-
164-
// DuckDB will lose connection_ref on connection on error, this will lead to broken connection object
165-
throw e;
166-
}
167-
}
168+
const officialExtensions = getEnv('duckdbExtensions', this.config);
169+
await this.installExtensions(officialExtensions, execAsync);
170+
await this.loadExtensions(officialExtensions, execAsync);
171+
const communityExtensions = getEnv('duckdbCommunityExtensions', this.config);
172+
// @see https://duckdb.org/community_extensions/
173+
await this.installExtensions(communityExtensions, execAsync, 'community');
174+
await this.loadExtensions(communityExtensions, execAsync);
168175

169176
if (this.config.initSql) {
170177
try {
@@ -177,7 +184,7 @@ export class DuckDBDriver extends BaseDriver implements DriverInterface {
177184
}
178185
}
179186
}
180-
187+
181188
return {
182189
defaultConnection,
183190
db

0 commit comments

Comments
 (0)