@lightdash/warehouses 0.2603.2 → 0.2604.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.tsbuildinfo +1 -1
- package/dist/warehouseClients/DuckdbWarehouseClient.d.ts +6 -0
- package/dist/warehouseClients/DuckdbWarehouseClient.d.ts.map +1 -1
- package/dist/warehouseClients/DuckdbWarehouseClient.js +78 -6
- package/dist/warehouseClients/DuckdbWarehouseClient.test.js +5 -0
- package/package.json +2 -2
|
@@ -20,6 +20,7 @@ export type DuckdbWarehouseClientArgs = {
|
|
|
20
20
|
databasePath?: string;
|
|
21
21
|
s3Config?: DuckdbS3SessionConfig;
|
|
22
22
|
resourceLimits?: DuckdbResourceLimits;
|
|
23
|
+
bufferPoolSize?: string;
|
|
23
24
|
logger?: DuckdbLogger;
|
|
24
25
|
};
|
|
25
26
|
export declare const mapFieldTypeFromTypeId: (typeId: number) => DimensionType;
|
|
@@ -29,13 +30,18 @@ export declare class DuckdbSqlBuilder extends WarehouseBaseSqlBuilder {
|
|
|
29
30
|
getMetricSql(sql: string, metric: Metric): string;
|
|
30
31
|
concatString(...args: string[]): string;
|
|
31
32
|
}
|
|
33
|
+
/** Reset shared state without closing — for use in tests with mocked instances. */
|
|
34
|
+
export declare function resetSharedDuckdbStateForTesting(): void;
|
|
32
35
|
export declare class DuckdbWarehouseClient extends WarehouseBaseClient<CreatePostgresCredentials> {
|
|
33
36
|
private readonly databasePath;
|
|
34
37
|
private readonly s3Config?;
|
|
35
38
|
private readonly resourceLimits?;
|
|
39
|
+
private readonly bufferPoolSize?;
|
|
36
40
|
private readonly logger?;
|
|
37
41
|
constructor(args?: DuckdbWarehouseClientArgs);
|
|
42
|
+
close(): Promise<void>;
|
|
38
43
|
private getSQLWithMetadata;
|
|
44
|
+
private connectWithRetry;
|
|
39
45
|
private withSession;
|
|
40
46
|
private bootstrapSession;
|
|
41
47
|
private getBindValues;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DuckdbWarehouseClient.d.ts","sourceRoot":"","sources":["../../src/warehouseClients/DuckdbWarehouseClient.ts"],"names":[],"mappings":"AACA,OAAO,EACH,OAAO,EACP,yBAAyB,EACzB,aAAa,EACb,MAAM,EAGN,mBAAmB,EACnB,gBAAgB,EAChB,gBAAgB,EAEnB,MAAM,mBAAmB,CAAC;AAI3B,OAAO,mBAAmB,MAAM,uBAAuB,CAAC;AACxD,OAAO,uBAAuB,MAAM,2BAA2B,CAAC;AA2BhE,MAAM,MAAM,qBAAqB,GAAG;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,OAAO,CAAC;IACxB,MAAM,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACvB,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;CACvE,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACpC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,qBAAqB,CAAC;IACjC,cAAc,CAAC,EAAE,oBAAoB,CAAC;IACtC,MAAM,CAAC,EAAE,YAAY,CAAC;CACzB,CAAC;AAYF,eAAO,MAAM,sBAAsB,GAAI,QAAQ,MAAM,KAAG,aA+BvD,CAAC;AAEF,qBAAa,gBAAiB,SAAQ,uBAAuB;IACzD,cAAc,IAAI,mBAAmB;IAIrC,eAAe,IAAI,MAAM;IAIzB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM;IAWjD,YAAY,CAAC,GAAG,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM;CAG1C;AAED,qBAAa,qBAAsB,SAAQ,mBAAmB,CAAC,yBAAyB,CAAC;IACrF,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IAEtC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAwB;IAElD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAuB;IAEvD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAe;gBAE3B,IAAI,GAAE,yBAA8B;
|
|
1
|
+
{"version":3,"file":"DuckdbWarehouseClient.d.ts","sourceRoot":"","sources":["../../src/warehouseClients/DuckdbWarehouseClient.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.DuckdbWarehouseClient = exports.DuckdbSqlBuilder = exports.mapFieldTypeFromTypeId = void 0;
|
|
7
|
+
exports.resetSharedDuckdbStateForTesting = resetSharedDuckdbStateForTesting;
|
|
7
8
|
const node_api_1 = require("@duckdb/node-api");
|
|
8
9
|
const common_1 = require("@lightdash/common");
|
|
9
10
|
const promises_1 = __importDefault(require("fs/promises"));
|
|
@@ -75,24 +76,78 @@ class DuckdbSqlBuilder extends WarehouseBaseSqlBuilder_1.default {
|
|
|
75
76
|
}
|
|
76
77
|
}
|
|
77
78
|
exports.DuckdbSqlBuilder = DuckdbSqlBuilder;
|
|
79
|
+
/**
|
|
80
|
+
* Shared DuckDB instance — one per worker process.
|
|
81
|
+
* All DuckdbWarehouseClient instances share the same underlying DuckDB instance
|
|
82
|
+
* to maximize cache hits (parquet metadata, HTTP metadata, buffer pool).
|
|
83
|
+
*/
|
|
84
|
+
let sharedInstance = null;
|
|
85
|
+
let httpfsInstalled = false;
|
|
86
|
+
let cachesConfigured = false;
|
|
87
|
+
async function getOrCreateSharedInstance(databasePath, logger) {
|
|
88
|
+
if (!sharedInstance) {
|
|
89
|
+
const t0 = performance.now();
|
|
90
|
+
sharedInstance = (await node_api_1.DuckDBInstance.create(databasePath));
|
|
91
|
+
const createMs = performance.now() - t0;
|
|
92
|
+
httpfsInstalled = false;
|
|
93
|
+
cachesConfigured = false;
|
|
94
|
+
logger?.info(`DuckDB shared instance created: path=${databasePath} createMs=${Math.round(createMs)}ms`);
|
|
95
|
+
}
|
|
96
|
+
return sharedInstance;
|
|
97
|
+
}
|
|
98
|
+
function clearSharedInstance(logger) {
|
|
99
|
+
if (sharedInstance) {
|
|
100
|
+
try {
|
|
101
|
+
sharedInstance.closeSync?.();
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
// best-effort cleanup
|
|
105
|
+
}
|
|
106
|
+
sharedInstance = null;
|
|
107
|
+
httpfsInstalled = false;
|
|
108
|
+
cachesConfigured = false;
|
|
109
|
+
logger?.info('DuckDB shared instance cleared');
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/** Reset shared state without closing — for use in tests with mocked instances. */
|
|
113
|
+
function resetSharedDuckdbStateForTesting() {
|
|
114
|
+
sharedInstance = null;
|
|
115
|
+
httpfsInstalled = false;
|
|
116
|
+
cachesConfigured = false;
|
|
117
|
+
}
|
|
78
118
|
class DuckdbWarehouseClient extends WarehouseBaseClient_1.default {
|
|
79
119
|
constructor(args = {}) {
|
|
80
120
|
super(DUCKDB_INTERNAL_CREDENTIALS, new DuckdbSqlBuilder());
|
|
81
121
|
this.databasePath = args.databasePath ?? ':memory:';
|
|
82
122
|
this.s3Config = args.s3Config;
|
|
83
123
|
this.resourceLimits = args.resourceLimits;
|
|
124
|
+
this.bufferPoolSize = args.bufferPoolSize;
|
|
84
125
|
this.logger = args.logger;
|
|
85
126
|
}
|
|
127
|
+
async close() {
|
|
128
|
+
clearSharedInstance(this.logger);
|
|
129
|
+
}
|
|
86
130
|
getSQLWithMetadata(sql, tags) {
|
|
87
131
|
if (!tags) {
|
|
88
132
|
return sql;
|
|
89
133
|
}
|
|
90
134
|
return `${sql}\n-- ${JSON.stringify(tags)}`;
|
|
91
135
|
}
|
|
136
|
+
async connectWithRetry() {
|
|
137
|
+
const instance = await getOrCreateSharedInstance(this.databasePath, this.logger);
|
|
138
|
+
try {
|
|
139
|
+
return await instance.connect();
|
|
140
|
+
}
|
|
141
|
+
catch (firstError) {
|
|
142
|
+
this.logger?.info(`DuckDB connect failed, retrying with fresh instance: ${firstError}`);
|
|
143
|
+
clearSharedInstance(this.logger);
|
|
144
|
+
const freshInstance = await getOrCreateSharedInstance(this.databasePath, this.logger);
|
|
145
|
+
return freshInstance.connect();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
92
148
|
async withSession(callback) {
|
|
93
149
|
const sessionStart = performance.now();
|
|
94
|
-
const
|
|
95
|
-
const connection = await instance.connect();
|
|
150
|
+
const connection = await this.connectWithRetry();
|
|
96
151
|
const connectMs = performance.now() - sessionStart;
|
|
97
152
|
// Only create a temp dir when resource limits are set (spill to disk).
|
|
98
153
|
const tempDir = this.resourceLimits
|
|
@@ -112,19 +167,36 @@ class DuckdbWarehouseClient extends WarehouseBaseClient_1.default {
|
|
|
112
167
|
finally {
|
|
113
168
|
connection.closeSync?.();
|
|
114
169
|
connection.disconnectSync?.();
|
|
115
|
-
instance
|
|
170
|
+
// Note: we do NOT close the instance — it's shared across queries
|
|
116
171
|
if (tempDir) {
|
|
117
172
|
await promises_1.default.rm(tempDir, { recursive: true, force: true }).catch(() => { });
|
|
118
173
|
}
|
|
119
174
|
}
|
|
120
175
|
}
|
|
121
176
|
async bootstrapSession(db, tempDir) {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
177
|
+
let installMs = 0;
|
|
178
|
+
if (!httpfsInstalled) {
|
|
179
|
+
const t0 = performance.now();
|
|
180
|
+
await db.run('INSTALL httpfs;');
|
|
181
|
+
installMs = performance.now() - t0;
|
|
182
|
+
httpfsInstalled = true;
|
|
183
|
+
this.logger?.info(`DuckDB httpfs installed (first use): ${Math.round(installMs)}ms`);
|
|
184
|
+
}
|
|
125
185
|
const t1 = performance.now();
|
|
126
186
|
await db.run('LOAD httpfs;');
|
|
127
187
|
const loadMs = performance.now() - t1;
|
|
188
|
+
// Enable built-in caches — these are GLOBAL settings (instance-level, not
|
|
189
|
+
// connection-level), so they only need to be set once per shared instance.
|
|
190
|
+
if (!cachesConfigured) {
|
|
191
|
+
await db.run('SET enable_http_metadata_cache = true;');
|
|
192
|
+
await db.run('SET enable_external_file_cache = true;');
|
|
193
|
+
await db.run('SET parquet_metadata_cache = true;');
|
|
194
|
+
cachesConfigured = true;
|
|
195
|
+
if (this.bufferPoolSize) {
|
|
196
|
+
await db.run(`SET buffer_pool_size = '${this.bufferPoolSize}';`);
|
|
197
|
+
}
|
|
198
|
+
this.logger?.info(`DuckDB caches enabled: http_metadata=true external_file=true parquet_metadata=true buffer_pool_size=${this.bufferPoolSize ?? 'default'}`);
|
|
199
|
+
}
|
|
128
200
|
if (this.resourceLimits && tempDir) {
|
|
129
201
|
await db.run(`SET memory_limit = '${this.resourceLimits.memoryLimit}';`);
|
|
130
202
|
await db.run(`SET temp_directory = '${tempDir}';`);
|
|
@@ -138,6 +138,7 @@ describe('mapFieldTypeFromTypeId', () => {
|
|
|
138
138
|
describe('DuckdbWarehouseClient', () => {
|
|
139
139
|
beforeEach(() => {
|
|
140
140
|
jest.clearAllMocks();
|
|
141
|
+
(0, DuckdbWarehouseClient_1.resetSharedDuckdbStateForTesting)();
|
|
141
142
|
});
|
|
142
143
|
it('should return query rows and mapped fields', async () => {
|
|
143
144
|
const rows = [
|
|
@@ -213,7 +214,11 @@ describe('DuckdbWarehouseClient', () => {
|
|
|
213
214
|
});
|
|
214
215
|
await client.runQuery('SELECT 1 AS val', undefined, 'UTC');
|
|
215
216
|
const runCalls = runMock.mock.calls.map((call) => call[0]);
|
|
217
|
+
expect(runCalls).toContain('INSTALL httpfs;');
|
|
216
218
|
expect(runCalls).toContain('LOAD httpfs;');
|
|
219
|
+
expect(runCalls).toContain('SET enable_http_metadata_cache = true;');
|
|
220
|
+
expect(runCalls).toContain('SET enable_external_file_cache = true;');
|
|
221
|
+
expect(runCalls).toContain('SET parquet_metadata_cache = true;');
|
|
217
222
|
expect(runCalls).toContain("SET s3_endpoint = 'localhost:9000';");
|
|
218
223
|
expect(runCalls).toContain("SET s3_region = 'us-east-1';");
|
|
219
224
|
expect(runCalls).toContain("SET TimeZone = 'UTC';");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lightdash/warehouses",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2604.1",
|
|
4
4
|
"description": "Warehouse connectors for Lightdash",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"snowflake-sdk": "~2.3.4",
|
|
27
27
|
"ssh2": "^1.14.0",
|
|
28
28
|
"trino-client": "0.2.9",
|
|
29
|
-
"@lightdash/common": "0.
|
|
29
|
+
"@lightdash/common": "0.2604.1"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@types/node-fetch": "^2.6.13",
|