@lightdash/warehouses 0.2532.1 → 0.2533.0
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/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/warehouseClients/DuckdbWarehouseClient.d.ts +58 -0
- package/dist/warehouseClients/DuckdbWarehouseClient.d.ts.map +1 -0
- package/dist/warehouseClients/DuckdbWarehouseClient.js +175 -0
- package/dist/warehouseClients/DuckdbWarehouseClient.test.d.ts +2 -0
- package/dist/warehouseClients/DuckdbWarehouseClient.test.d.ts.map +1 -0
- package/dist/warehouseClients/DuckdbWarehouseClient.test.js +218 -0
- package/dist/warehouseSqlBuilderFromType.d.ts +2 -1
- package/dist/warehouseSqlBuilderFromType.d.ts.map +1 -1
- package/dist/warehouseSqlBuilderFromType.js +3 -0
- package/package.json +3 -2
package/dist/index.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export * from './warehouseClientFromCredentials';
|
|
|
4
4
|
export * from './warehouseClients/AthenaWarehouseClient';
|
|
5
5
|
export * from './warehouseClients/BigqueryWarehouseClient';
|
|
6
6
|
export * from './warehouseClients/DatabricksWarehouseClient';
|
|
7
|
+
export * from './warehouseClients/DuckdbWarehouseClient';
|
|
7
8
|
export * from './warehouseClients/PostgresWarehouseClient';
|
|
8
9
|
export * from './warehouseClients/RedshiftWarehouseClient';
|
|
9
10
|
export * from './warehouseClients/SnowflakeWarehouseClient';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,SAAS,CAAC;AACxB,cAAc,kCAAkC,CAAC;AACjD,cAAc,0CAA0C,CAAC;AACzD,cAAc,4CAA4C,CAAC;AAC3D,cAAc,8CAA8C,CAAC;AAC7D,cAAc,4CAA4C,CAAC;AAC3D,cAAc,4CAA4C,CAAC;AAC3D,cAAc,6CAA6C,CAAC;AAC5D,cAAc,yCAAyC,CAAC;AACxD,cAAc,+BAA+B,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,SAAS,CAAC;AACxB,cAAc,kCAAkC,CAAC;AACjD,cAAc,0CAA0C,CAAC;AACzD,cAAc,4CAA4C,CAAC;AAC3D,cAAc,8CAA8C,CAAC;AAC7D,cAAc,0CAA0C,CAAC;AACzD,cAAc,4CAA4C,CAAC;AAC3D,cAAc,4CAA4C,CAAC;AAC3D,cAAc,6CAA6C,CAAC;AAC5D,cAAc,yCAAyC,CAAC;AACxD,cAAc,+BAA+B,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -20,6 +20,7 @@ __exportStar(require("./warehouseClientFromCredentials"), exports);
|
|
|
20
20
|
__exportStar(require("./warehouseClients/AthenaWarehouseClient"), exports);
|
|
21
21
|
__exportStar(require("./warehouseClients/BigqueryWarehouseClient"), exports);
|
|
22
22
|
__exportStar(require("./warehouseClients/DatabricksWarehouseClient"), exports);
|
|
23
|
+
__exportStar(require("./warehouseClients/DuckdbWarehouseClient"), exports);
|
|
23
24
|
__exportStar(require("./warehouseClients/PostgresWarehouseClient"), exports);
|
|
24
25
|
__exportStar(require("./warehouseClients/RedshiftWarehouseClient"), exports);
|
|
25
26
|
__exportStar(require("./warehouseClients/SnowflakeWarehouseClient"), exports);
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { AnyType, CreatePostgresCredentials, DimensionType, Metric, SupportedDbtAdapter, WarehouseCatalog, WarehouseResults } from '@lightdash/common';
|
|
2
|
+
import WarehouseBaseClient from './WarehouseBaseClient';
|
|
3
|
+
import WarehouseBaseSqlBuilder from './WarehouseBaseSqlBuilder';
|
|
4
|
+
export type DuckdbS3SessionConfig = {
|
|
5
|
+
endpoint: string;
|
|
6
|
+
region?: string;
|
|
7
|
+
accessKey?: string;
|
|
8
|
+
secretKey?: string;
|
|
9
|
+
forcePathStyle: boolean;
|
|
10
|
+
useSsl: boolean;
|
|
11
|
+
};
|
|
12
|
+
export type DuckdbWarehouseClientArgs = {
|
|
13
|
+
databasePath?: string;
|
|
14
|
+
s3Config?: DuckdbS3SessionConfig;
|
|
15
|
+
};
|
|
16
|
+
export declare const mapFieldTypeFromTypeId: (typeId: number) => DimensionType;
|
|
17
|
+
export declare class DuckdbSqlBuilder extends WarehouseBaseSqlBuilder {
|
|
18
|
+
getAdapterType(): SupportedDbtAdapter;
|
|
19
|
+
getFloatingType(): string;
|
|
20
|
+
getMetricSql(sql: string, metric: Metric): string;
|
|
21
|
+
concatString(...args: string[]): string;
|
|
22
|
+
}
|
|
23
|
+
export declare class DuckdbWarehouseClient extends WarehouseBaseClient<CreatePostgresCredentials> {
|
|
24
|
+
private readonly databasePath;
|
|
25
|
+
private readonly s3Config?;
|
|
26
|
+
constructor(args?: DuckdbWarehouseClientArgs);
|
|
27
|
+
private getSQLWithMetadata;
|
|
28
|
+
private withSession;
|
|
29
|
+
private bootstrapSession;
|
|
30
|
+
private getBindValues;
|
|
31
|
+
private static getFieldsFromStreamResult;
|
|
32
|
+
streamQuery(sql: string, streamCallback: (data: WarehouseResults) => void | Promise<void>, options?: {
|
|
33
|
+
values?: AnyType[];
|
|
34
|
+
queryParams?: Record<string, AnyType>;
|
|
35
|
+
tags?: Record<string, string>;
|
|
36
|
+
timezone?: string;
|
|
37
|
+
}): Promise<void>;
|
|
38
|
+
executeAsyncQuery(...args: Parameters<WarehouseBaseClient<CreatePostgresCredentials>['executeAsyncQuery']>): Promise<import("@lightdash/common").WarehouseExecuteAsyncQuery>;
|
|
39
|
+
runQuery(...args: Parameters<WarehouseBaseClient<CreatePostgresCredentials>['runQuery']>): Promise<{
|
|
40
|
+
fields: Record<string, {
|
|
41
|
+
type: DimensionType;
|
|
42
|
+
}>;
|
|
43
|
+
rows: Record<string, any>[];
|
|
44
|
+
}>;
|
|
45
|
+
test(): Promise<void>;
|
|
46
|
+
getCatalog(_config: {
|
|
47
|
+
database: string;
|
|
48
|
+
schema: string;
|
|
49
|
+
table: string;
|
|
50
|
+
}[]): Promise<WarehouseCatalog>;
|
|
51
|
+
getAllTables(_schema?: string, _tags?: Record<string, string>): Promise<{
|
|
52
|
+
database: string;
|
|
53
|
+
schema: string;
|
|
54
|
+
table: string;
|
|
55
|
+
}[]>;
|
|
56
|
+
getFields(_tableName: string, _schema?: string, _database?: string, _tags?: Record<string, string>): Promise<WarehouseCatalog>;
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=DuckdbWarehouseClient.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DuckdbWarehouseClient.d.ts","sourceRoot":"","sources":["../../src/warehouseClients/DuckdbWarehouseClient.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.DuckdbWarehouseClient = exports.DuckdbSqlBuilder = exports.mapFieldTypeFromTypeId = void 0;
|
|
7
|
+
const node_api_1 = require("@duckdb/node-api");
|
|
8
|
+
const common_1 = require("@lightdash/common");
|
|
9
|
+
const WarehouseBaseClient_1 = __importDefault(require("./WarehouseBaseClient"));
|
|
10
|
+
const WarehouseBaseSqlBuilder_1 = __importDefault(require("./WarehouseBaseSqlBuilder"));
|
|
11
|
+
const DUCKDB_INTERNAL_CREDENTIALS = {
|
|
12
|
+
type: common_1.WarehouseTypes.POSTGRES,
|
|
13
|
+
host: 'localhost',
|
|
14
|
+
port: 5432,
|
|
15
|
+
dbname: 'duckdb',
|
|
16
|
+
schema: 'main',
|
|
17
|
+
user: 'duckdb',
|
|
18
|
+
password: 'duckdb',
|
|
19
|
+
};
|
|
20
|
+
const mapFieldTypeFromTypeId = (typeId) => {
|
|
21
|
+
switch (typeId) {
|
|
22
|
+
case node_api_1.DuckDBTypeId.DATE:
|
|
23
|
+
return common_1.DimensionType.DATE;
|
|
24
|
+
case node_api_1.DuckDBTypeId.TIME:
|
|
25
|
+
case node_api_1.DuckDBTypeId.TIME_TZ:
|
|
26
|
+
case node_api_1.DuckDBTypeId.TIMESTAMP:
|
|
27
|
+
case node_api_1.DuckDBTypeId.TIMESTAMP_S:
|
|
28
|
+
case node_api_1.DuckDBTypeId.TIMESTAMP_MS:
|
|
29
|
+
case node_api_1.DuckDBTypeId.TIMESTAMP_NS:
|
|
30
|
+
case node_api_1.DuckDBTypeId.TIMESTAMP_TZ:
|
|
31
|
+
return common_1.DimensionType.TIMESTAMP;
|
|
32
|
+
case node_api_1.DuckDBTypeId.BOOLEAN:
|
|
33
|
+
return common_1.DimensionType.BOOLEAN;
|
|
34
|
+
case node_api_1.DuckDBTypeId.TINYINT:
|
|
35
|
+
case node_api_1.DuckDBTypeId.SMALLINT:
|
|
36
|
+
case node_api_1.DuckDBTypeId.INTEGER:
|
|
37
|
+
case node_api_1.DuckDBTypeId.BIGINT:
|
|
38
|
+
case node_api_1.DuckDBTypeId.HUGEINT:
|
|
39
|
+
case node_api_1.DuckDBTypeId.UTINYINT:
|
|
40
|
+
case node_api_1.DuckDBTypeId.USMALLINT:
|
|
41
|
+
case node_api_1.DuckDBTypeId.UINTEGER:
|
|
42
|
+
case node_api_1.DuckDBTypeId.UBIGINT:
|
|
43
|
+
case node_api_1.DuckDBTypeId.UHUGEINT:
|
|
44
|
+
case node_api_1.DuckDBTypeId.FLOAT:
|
|
45
|
+
case node_api_1.DuckDBTypeId.DOUBLE:
|
|
46
|
+
case node_api_1.DuckDBTypeId.DECIMAL:
|
|
47
|
+
return common_1.DimensionType.NUMBER;
|
|
48
|
+
default:
|
|
49
|
+
return common_1.DimensionType.STRING;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
exports.mapFieldTypeFromTypeId = mapFieldTypeFromTypeId;
|
|
53
|
+
class DuckdbSqlBuilder extends WarehouseBaseSqlBuilder_1.default {
|
|
54
|
+
getAdapterType() {
|
|
55
|
+
return common_1.SupportedDbtAdapter.DUCKDB;
|
|
56
|
+
}
|
|
57
|
+
getFloatingType() {
|
|
58
|
+
return 'DOUBLE';
|
|
59
|
+
}
|
|
60
|
+
getMetricSql(sql, metric) {
|
|
61
|
+
switch (metric.type) {
|
|
62
|
+
case common_1.MetricType.PERCENTILE:
|
|
63
|
+
return `QUANTILE_CONT(${sql}, ${(metric.percentile ?? 50) / 100})`;
|
|
64
|
+
case common_1.MetricType.MEDIAN:
|
|
65
|
+
return `MEDIAN(${sql})`;
|
|
66
|
+
default:
|
|
67
|
+
return super.getMetricSql(sql, metric);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
concatString(...args) {
|
|
71
|
+
return `(${args.join(' || ')})`;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
exports.DuckdbSqlBuilder = DuckdbSqlBuilder;
|
|
75
|
+
class DuckdbWarehouseClient extends WarehouseBaseClient_1.default {
|
|
76
|
+
constructor(args = {}) {
|
|
77
|
+
super(DUCKDB_INTERNAL_CREDENTIALS, new DuckdbSqlBuilder());
|
|
78
|
+
this.databasePath = args.databasePath ?? ':memory:';
|
|
79
|
+
this.s3Config = args.s3Config;
|
|
80
|
+
}
|
|
81
|
+
getSQLWithMetadata(sql, tags) {
|
|
82
|
+
if (!tags) {
|
|
83
|
+
return sql;
|
|
84
|
+
}
|
|
85
|
+
return `${sql}\n-- ${JSON.stringify(tags)}`;
|
|
86
|
+
}
|
|
87
|
+
async withSession(callback) {
|
|
88
|
+
const instance = (await node_api_1.DuckDBInstance.create(this.databasePath));
|
|
89
|
+
const connection = await instance.connect();
|
|
90
|
+
try {
|
|
91
|
+
await this.bootstrapSession(connection);
|
|
92
|
+
return await callback(connection);
|
|
93
|
+
}
|
|
94
|
+
finally {
|
|
95
|
+
connection.closeSync?.();
|
|
96
|
+
connection.disconnectSync?.();
|
|
97
|
+
instance.closeSync?.();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async bootstrapSession(db) {
|
|
101
|
+
await db.run('LOAD httpfs;');
|
|
102
|
+
if (!this.s3Config) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
await db.run(`SET s3_endpoint = '${this.escapeString(this.s3Config.endpoint)}';`);
|
|
106
|
+
if (this.s3Config.region) {
|
|
107
|
+
await db.run(`SET s3_region = '${this.escapeString(this.s3Config.region)}';`);
|
|
108
|
+
}
|
|
109
|
+
if (this.s3Config.accessKey) {
|
|
110
|
+
await db.run(`SET s3_access_key_id = '${this.escapeString(this.s3Config.accessKey)}';`);
|
|
111
|
+
}
|
|
112
|
+
if (this.s3Config.secretKey) {
|
|
113
|
+
await db.run(`SET s3_secret_access_key = '${this.escapeString(this.s3Config.secretKey)}';`);
|
|
114
|
+
}
|
|
115
|
+
await db.run(`SET s3_use_ssl = ${this.s3Config.useSsl};`);
|
|
116
|
+
await db.run(`SET s3_url_style = '${this.s3Config.forcePathStyle ? 'path' : 'vhost'}';`);
|
|
117
|
+
}
|
|
118
|
+
getBindValues(options) {
|
|
119
|
+
const hasValues = !!options?.values && options.values.length > 0;
|
|
120
|
+
const hasQueryParams = !!options?.queryParams &&
|
|
121
|
+
Object.keys(options.queryParams).length > 0;
|
|
122
|
+
if (hasValues && hasQueryParams) {
|
|
123
|
+
throw new common_1.NotImplementedError('DuckDB streamQuery does not support using values and queryParams together');
|
|
124
|
+
}
|
|
125
|
+
if (hasValues) {
|
|
126
|
+
return options?.values;
|
|
127
|
+
}
|
|
128
|
+
if (hasQueryParams) {
|
|
129
|
+
return options?.queryParams;
|
|
130
|
+
}
|
|
131
|
+
return undefined;
|
|
132
|
+
}
|
|
133
|
+
static getFieldsFromStreamResult(result) {
|
|
134
|
+
const columnNames = result.columnNames();
|
|
135
|
+
const fields = {};
|
|
136
|
+
for (let i = 0; i < result.columnCount; i += 1) {
|
|
137
|
+
fields[columnNames[i]] = {
|
|
138
|
+
type: (0, exports.mapFieldTypeFromTypeId)(result.columnTypeId(i)),
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
return fields;
|
|
142
|
+
}
|
|
143
|
+
async streamQuery(sql, streamCallback, options) {
|
|
144
|
+
await this.withSession(async (db) => {
|
|
145
|
+
if (options?.timezone) {
|
|
146
|
+
await db.run(`SET TimeZone = '${this.escapeString(options.timezone)}';`);
|
|
147
|
+
}
|
|
148
|
+
const result = await db.stream(this.getSQLWithMetadata(sql, options?.tags), this.getBindValues(options));
|
|
149
|
+
const fields = DuckdbWarehouseClient.getFieldsFromStreamResult(result);
|
|
150
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
151
|
+
for await (const rows of result.yieldRowObjectJson()) {
|
|
152
|
+
await streamCallback({ fields, rows });
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
async executeAsyncQuery(...args) {
|
|
157
|
+
return super.executeAsyncQuery(...args);
|
|
158
|
+
}
|
|
159
|
+
async runQuery(...args) {
|
|
160
|
+
return super.runQuery(...args);
|
|
161
|
+
}
|
|
162
|
+
async test() {
|
|
163
|
+
await super.test();
|
|
164
|
+
}
|
|
165
|
+
async getCatalog(_config) {
|
|
166
|
+
throw new common_1.NotImplementedError('DuckDB catalog discovery is not implemented yet');
|
|
167
|
+
}
|
|
168
|
+
async getAllTables(_schema, _tags) {
|
|
169
|
+
throw new common_1.NotImplementedError('DuckDB table discovery is not implemented yet');
|
|
170
|
+
}
|
|
171
|
+
async getFields(_tableName, _schema, _database, _tags) {
|
|
172
|
+
throw new common_1.NotImplementedError('DuckDB field discovery is not implemented yet');
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
exports.DuckdbWarehouseClient = DuckdbWarehouseClient;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DuckdbWarehouseClient.test.d.ts","sourceRoot":"","sources":["../../src/warehouseClients/DuckdbWarehouseClient.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const common_1 = require("@lightdash/common");
|
|
4
|
+
const DuckdbWarehouseClient_1 = require("./DuckdbWarehouseClient");
|
|
5
|
+
const createInstanceMock = jest.fn();
|
|
6
|
+
// Must provide DuckDBTypeId since mapFieldTypeFromTypeId references it at runtime
|
|
7
|
+
const DUCKDB_TYPE_IDS = {
|
|
8
|
+
BOOLEAN: 1,
|
|
9
|
+
TINYINT: 2,
|
|
10
|
+
SMALLINT: 3,
|
|
11
|
+
INTEGER: 4,
|
|
12
|
+
BIGINT: 5,
|
|
13
|
+
UTINYINT: 6,
|
|
14
|
+
USMALLINT: 7,
|
|
15
|
+
UINTEGER: 8,
|
|
16
|
+
UBIGINT: 9,
|
|
17
|
+
FLOAT: 10,
|
|
18
|
+
DOUBLE: 11,
|
|
19
|
+
TIMESTAMP: 12,
|
|
20
|
+
DATE: 13,
|
|
21
|
+
TIME: 14,
|
|
22
|
+
DECIMAL: 19,
|
|
23
|
+
HUGEINT: 25,
|
|
24
|
+
TIMESTAMP_S: 27,
|
|
25
|
+
TIMESTAMP_MS: 28,
|
|
26
|
+
TIMESTAMP_NS: 29,
|
|
27
|
+
TIMESTAMP_TZ: 31,
|
|
28
|
+
TIME_TZ: 32,
|
|
29
|
+
UHUGEINT: 49,
|
|
30
|
+
// Not referenced in switch — used to test default→STRING
|
|
31
|
+
VARCHAR: 17,
|
|
32
|
+
BLOB: 18,
|
|
33
|
+
};
|
|
34
|
+
jest.mock('@duckdb/node-api', () => ({
|
|
35
|
+
DuckDBTypeId: {
|
|
36
|
+
BOOLEAN: 1,
|
|
37
|
+
TINYINT: 2,
|
|
38
|
+
SMALLINT: 3,
|
|
39
|
+
INTEGER: 4,
|
|
40
|
+
BIGINT: 5,
|
|
41
|
+
UTINYINT: 6,
|
|
42
|
+
USMALLINT: 7,
|
|
43
|
+
UINTEGER: 8,
|
|
44
|
+
UBIGINT: 9,
|
|
45
|
+
FLOAT: 10,
|
|
46
|
+
DOUBLE: 11,
|
|
47
|
+
TIMESTAMP: 12,
|
|
48
|
+
DATE: 13,
|
|
49
|
+
TIME: 14,
|
|
50
|
+
DECIMAL: 19,
|
|
51
|
+
HUGEINT: 25,
|
|
52
|
+
TIMESTAMP_S: 27,
|
|
53
|
+
TIMESTAMP_MS: 28,
|
|
54
|
+
TIMESTAMP_NS: 29,
|
|
55
|
+
TIMESTAMP_TZ: 31,
|
|
56
|
+
TIME_TZ: 32,
|
|
57
|
+
UHUGEINT: 49,
|
|
58
|
+
},
|
|
59
|
+
DuckDBInstance: {
|
|
60
|
+
create: (...args) => createInstanceMock(...args),
|
|
61
|
+
},
|
|
62
|
+
}), { virtual: true });
|
|
63
|
+
const getMockStreamResult = (chunks, columnTypeIds) => {
|
|
64
|
+
const columnNames = Object.keys(chunks[0]?.[0] || {});
|
|
65
|
+
return {
|
|
66
|
+
columnCount: columnNames.length,
|
|
67
|
+
columnNames: () => columnNames,
|
|
68
|
+
columnTypeId: (i) => columnTypeIds[i] ?? 0,
|
|
69
|
+
// eslint-disable-next-line object-shorthand, func-names, no-restricted-syntax
|
|
70
|
+
yieldRowObjectJson: async function* () {
|
|
71
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
72
|
+
for (const chunk of chunks) {
|
|
73
|
+
yield chunk;
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
const createMockConnection = (streamMock, runMock = jest.fn()) => ({
|
|
79
|
+
connect: async () => ({
|
|
80
|
+
run: runMock,
|
|
81
|
+
stream: streamMock,
|
|
82
|
+
closeSync: jest.fn(),
|
|
83
|
+
disconnectSync: jest.fn(),
|
|
84
|
+
}),
|
|
85
|
+
closeSync: jest.fn(),
|
|
86
|
+
});
|
|
87
|
+
describe('mapFieldTypeFromTypeId', () => {
|
|
88
|
+
it('should map date types', () => {
|
|
89
|
+
expect((0, DuckdbWarehouseClient_1.mapFieldTypeFromTypeId)(DUCKDB_TYPE_IDS.DATE)).toBe(common_1.DimensionType.DATE);
|
|
90
|
+
});
|
|
91
|
+
it('should map timestamp types', () => {
|
|
92
|
+
const timestampIds = [
|
|
93
|
+
DUCKDB_TYPE_IDS.TIMESTAMP,
|
|
94
|
+
DUCKDB_TYPE_IDS.TIMESTAMP_S,
|
|
95
|
+
DUCKDB_TYPE_IDS.TIMESTAMP_MS,
|
|
96
|
+
DUCKDB_TYPE_IDS.TIMESTAMP_NS,
|
|
97
|
+
DUCKDB_TYPE_IDS.TIMESTAMP_TZ,
|
|
98
|
+
DUCKDB_TYPE_IDS.TIME,
|
|
99
|
+
DUCKDB_TYPE_IDS.TIME_TZ,
|
|
100
|
+
];
|
|
101
|
+
timestampIds.forEach((id) => {
|
|
102
|
+
expect((0, DuckdbWarehouseClient_1.mapFieldTypeFromTypeId)(id)).toBe(common_1.DimensionType.TIMESTAMP);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
it('should map boolean type', () => {
|
|
106
|
+
expect((0, DuckdbWarehouseClient_1.mapFieldTypeFromTypeId)(DUCKDB_TYPE_IDS.BOOLEAN)).toBe(common_1.DimensionType.BOOLEAN);
|
|
107
|
+
});
|
|
108
|
+
it('should map numeric types', () => {
|
|
109
|
+
const numericIds = [
|
|
110
|
+
DUCKDB_TYPE_IDS.TINYINT,
|
|
111
|
+
DUCKDB_TYPE_IDS.SMALLINT,
|
|
112
|
+
DUCKDB_TYPE_IDS.INTEGER,
|
|
113
|
+
DUCKDB_TYPE_IDS.BIGINT,
|
|
114
|
+
DUCKDB_TYPE_IDS.UTINYINT,
|
|
115
|
+
DUCKDB_TYPE_IDS.USMALLINT,
|
|
116
|
+
DUCKDB_TYPE_IDS.UINTEGER,
|
|
117
|
+
DUCKDB_TYPE_IDS.UBIGINT,
|
|
118
|
+
DUCKDB_TYPE_IDS.HUGEINT,
|
|
119
|
+
DUCKDB_TYPE_IDS.UHUGEINT,
|
|
120
|
+
DUCKDB_TYPE_IDS.FLOAT,
|
|
121
|
+
DUCKDB_TYPE_IDS.DOUBLE,
|
|
122
|
+
DUCKDB_TYPE_IDS.DECIMAL,
|
|
123
|
+
];
|
|
124
|
+
numericIds.forEach((id) => {
|
|
125
|
+
expect((0, DuckdbWarehouseClient_1.mapFieldTypeFromTypeId)(id)).toBe(common_1.DimensionType.NUMBER);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
it('should default to string for unknown types', () => {
|
|
129
|
+
expect((0, DuckdbWarehouseClient_1.mapFieldTypeFromTypeId)(DUCKDB_TYPE_IDS.VARCHAR)).toBe(common_1.DimensionType.STRING);
|
|
130
|
+
expect((0, DuckdbWarehouseClient_1.mapFieldTypeFromTypeId)(DUCKDB_TYPE_IDS.BLOB)).toBe(common_1.DimensionType.STRING);
|
|
131
|
+
expect((0, DuckdbWarehouseClient_1.mapFieldTypeFromTypeId)(9999)).toBe(common_1.DimensionType.STRING);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
describe('DuckdbWarehouseClient', () => {
|
|
135
|
+
beforeEach(() => {
|
|
136
|
+
jest.clearAllMocks();
|
|
137
|
+
});
|
|
138
|
+
it('should return query rows and mapped fields', async () => {
|
|
139
|
+
const rows = [
|
|
140
|
+
{
|
|
141
|
+
customer_name: 'Alice',
|
|
142
|
+
order_count: 2,
|
|
143
|
+
last_order_at: '2026-02-22 12:00:00',
|
|
144
|
+
},
|
|
145
|
+
];
|
|
146
|
+
const streamMock = jest.fn(async () => getMockStreamResult([rows], [
|
|
147
|
+
DUCKDB_TYPE_IDS.VARCHAR,
|
|
148
|
+
DUCKDB_TYPE_IDS.INTEGER,
|
|
149
|
+
DUCKDB_TYPE_IDS.TIMESTAMP,
|
|
150
|
+
]));
|
|
151
|
+
createInstanceMock.mockResolvedValue(createMockConnection(streamMock));
|
|
152
|
+
const client = new DuckdbWarehouseClient_1.DuckdbWarehouseClient({
|
|
153
|
+
s3Config: {
|
|
154
|
+
endpoint: 'localhost:9000',
|
|
155
|
+
region: 'us-east-1',
|
|
156
|
+
forcePathStyle: true,
|
|
157
|
+
useSsl: false,
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
const result = await client.runQuery('SELECT * FROM customers');
|
|
161
|
+
expect(result.rows).toEqual(rows);
|
|
162
|
+
expect(result.fields).toEqual({
|
|
163
|
+
customer_name: { type: common_1.DimensionType.STRING },
|
|
164
|
+
order_count: { type: common_1.DimensionType.NUMBER },
|
|
165
|
+
last_order_at: { type: common_1.DimensionType.TIMESTAMP },
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
it('should stream results in multiple chunks', async () => {
|
|
169
|
+
const chunk1 = [{ id: 1 }, { id: 2 }];
|
|
170
|
+
const chunk2 = [{ id: 3 }];
|
|
171
|
+
const streamMock = jest.fn(async () => getMockStreamResult([chunk1, chunk2], [DUCKDB_TYPE_IDS.INTEGER]));
|
|
172
|
+
createInstanceMock.mockResolvedValue(createMockConnection(streamMock));
|
|
173
|
+
const client = new DuckdbWarehouseClient_1.DuckdbWarehouseClient();
|
|
174
|
+
const streamCallback = jest.fn();
|
|
175
|
+
const result = await client.executeAsyncQuery({
|
|
176
|
+
sql: 'SELECT id FROM t',
|
|
177
|
+
tags: { project_uuid: 'proj-1' },
|
|
178
|
+
}, streamCallback);
|
|
179
|
+
expect(streamCallback).toHaveBeenCalledTimes(2);
|
|
180
|
+
expect(streamCallback).toHaveBeenNthCalledWith(1, chunk1, {
|
|
181
|
+
id: { type: common_1.DimensionType.NUMBER },
|
|
182
|
+
});
|
|
183
|
+
expect(streamCallback).toHaveBeenNthCalledWith(2, chunk2, {
|
|
184
|
+
id: { type: common_1.DimensionType.NUMBER },
|
|
185
|
+
});
|
|
186
|
+
expect(result.totalRows).toBe(3);
|
|
187
|
+
});
|
|
188
|
+
it('should handle empty result set', async () => {
|
|
189
|
+
const streamMock = jest.fn(async () => getMockStreamResult([], [DUCKDB_TYPE_IDS.INTEGER]));
|
|
190
|
+
createInstanceMock.mockResolvedValue(createMockConnection(streamMock));
|
|
191
|
+
const client = new DuckdbWarehouseClient_1.DuckdbWarehouseClient();
|
|
192
|
+
const result = await client.runQuery('SELECT id FROM empty_table');
|
|
193
|
+
expect(result.rows).toEqual([]);
|
|
194
|
+
expect(result.fields).toEqual({});
|
|
195
|
+
});
|
|
196
|
+
it('should set timezone and S3 config before streaming', async () => {
|
|
197
|
+
const runMock = jest.fn();
|
|
198
|
+
const streamMock = jest.fn(async () => getMockStreamResult([[{ val: 1 }]], [DUCKDB_TYPE_IDS.INTEGER]));
|
|
199
|
+
createInstanceMock.mockResolvedValue(createMockConnection(streamMock, runMock));
|
|
200
|
+
const client = new DuckdbWarehouseClient_1.DuckdbWarehouseClient({
|
|
201
|
+
s3Config: {
|
|
202
|
+
endpoint: 'localhost:9000',
|
|
203
|
+
region: 'us-east-1',
|
|
204
|
+
accessKey: 'key',
|
|
205
|
+
secretKey: 'secret',
|
|
206
|
+
forcePathStyle: true,
|
|
207
|
+
useSsl: false,
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
await client.runQuery('SELECT 1 AS val', undefined, 'UTC');
|
|
211
|
+
const runCalls = runMock.mock.calls.map((call) => call[0]);
|
|
212
|
+
expect(runCalls).toContain('LOAD httpfs;');
|
|
213
|
+
expect(runCalls).toContain("SET s3_endpoint = 'localhost:9000';");
|
|
214
|
+
expect(runCalls).toContain("SET s3_region = 'us-east-1';");
|
|
215
|
+
expect(runCalls).toContain("SET TimeZone = 'UTC';");
|
|
216
|
+
expect(streamMock).toHaveBeenCalledTimes(1);
|
|
217
|
+
});
|
|
218
|
+
});
|
|
@@ -3,8 +3,9 @@ import { AthenaSqlBuilder } from './warehouseClients/AthenaWarehouseClient';
|
|
|
3
3
|
import { BigquerySqlBuilder } from './warehouseClients/BigqueryWarehouseClient';
|
|
4
4
|
import { ClickhouseSqlBuilder } from './warehouseClients/ClickhouseWarehouseClient';
|
|
5
5
|
import { DatabricksSqlBuilder } from './warehouseClients/DatabricksWarehouseClient';
|
|
6
|
+
import { DuckdbSqlBuilder } from './warehouseClients/DuckdbWarehouseClient';
|
|
6
7
|
import { PostgresSqlBuilder } from './warehouseClients/PostgresWarehouseClient';
|
|
7
8
|
import { SnowflakeSqlBuilder } from './warehouseClients/SnowflakeWarehouseClient';
|
|
8
9
|
import { TrinoSqlBuilder } from './warehouseClients/TrinoWarehouseClient';
|
|
9
|
-
export declare const warehouseSqlBuilderFromType: (adapterType: string | SupportedDbtAdapter, startOfWeek?: import("@lightdash/common").WeekDay | null | undefined) => AthenaSqlBuilder | BigquerySqlBuilder | ClickhouseSqlBuilder | DatabricksSqlBuilder | PostgresSqlBuilder | SnowflakeSqlBuilder | TrinoSqlBuilder;
|
|
10
|
+
export declare const warehouseSqlBuilderFromType: (adapterType: string | SupportedDbtAdapter, startOfWeek?: import("@lightdash/common").WeekDay | null | undefined) => AthenaSqlBuilder | BigquerySqlBuilder | ClickhouseSqlBuilder | DatabricksSqlBuilder | PostgresSqlBuilder | SnowflakeSqlBuilder | TrinoSqlBuilder | DuckdbSqlBuilder;
|
|
10
11
|
//# sourceMappingURL=warehouseSqlBuilderFromType.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"warehouseSqlBuilderFromType.d.ts","sourceRoot":"","sources":["../src/warehouseSqlBuilderFromType.ts"],"names":[],"mappings":"AAAA,OAAO,EAEH,mBAAmB,EACtB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AAC5E,OAAO,EAAE,kBAAkB,EAAE,MAAM,4CAA4C,CAAC;AAChF,OAAO,EAAE,oBAAoB,EAAE,MAAM,8CAA8C,CAAC;AACpF,OAAO,EAAE,oBAAoB,EAAE,MAAM,8CAA8C,CAAC;AACpF,OAAO,EAAE,kBAAkB,EAAE,MAAM,4CAA4C,CAAC;AAEhF,OAAO,EAAE,mBAAmB,EAAE,MAAM,6CAA6C,CAAC;AAClF,OAAO,EAAE,eAAe,EAAE,MAAM,yCAAyC,CAAC;AAG1E,eAAO,MAAM,2BAA2B,gBACvB,MAAM,GAAG,mBAAmB,
|
|
1
|
+
{"version":3,"file":"warehouseSqlBuilderFromType.d.ts","sourceRoot":"","sources":["../src/warehouseSqlBuilderFromType.ts"],"names":[],"mappings":"AAAA,OAAO,EAEH,mBAAmB,EACtB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AAC5E,OAAO,EAAE,kBAAkB,EAAE,MAAM,4CAA4C,CAAC;AAChF,OAAO,EAAE,oBAAoB,EAAE,MAAM,8CAA8C,CAAC;AACpF,OAAO,EAAE,oBAAoB,EAAE,MAAM,8CAA8C,CAAC;AACpF,OAAO,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AAC5E,OAAO,EAAE,kBAAkB,EAAE,MAAM,4CAA4C,CAAC;AAEhF,OAAO,EAAE,mBAAmB,EAAE,MAAM,6CAA6C,CAAC;AAClF,OAAO,EAAE,eAAe,EAAE,MAAM,yCAAyC,CAAC;AAG1E,eAAO,MAAM,2BAA2B,gBACvB,MAAM,GAAG,mBAAmB,8OAkC5C,CAAC"}
|
|
@@ -6,6 +6,7 @@ const AthenaWarehouseClient_1 = require("./warehouseClients/AthenaWarehouseClien
|
|
|
6
6
|
const BigqueryWarehouseClient_1 = require("./warehouseClients/BigqueryWarehouseClient");
|
|
7
7
|
const ClickhouseWarehouseClient_1 = require("./warehouseClients/ClickhouseWarehouseClient");
|
|
8
8
|
const DatabricksWarehouseClient_1 = require("./warehouseClients/DatabricksWarehouseClient");
|
|
9
|
+
const DuckdbWarehouseClient_1 = require("./warehouseClients/DuckdbWarehouseClient");
|
|
9
10
|
const PostgresWarehouseClient_1 = require("./warehouseClients/PostgresWarehouseClient");
|
|
10
11
|
const RedshiftWarehouseClient_1 = require("./warehouseClients/RedshiftWarehouseClient");
|
|
11
12
|
const SnowflakeWarehouseClient_1 = require("./warehouseClients/SnowflakeWarehouseClient");
|
|
@@ -21,6 +22,8 @@ const warehouseSqlBuilderFromType = (adapterType, ...args) => {
|
|
|
21
22
|
return new ClickhouseWarehouseClient_1.ClickhouseSqlBuilder(...args);
|
|
22
23
|
case common_1.SupportedDbtAdapter.DATABRICKS:
|
|
23
24
|
return new DatabricksWarehouseClient_1.DatabricksSqlBuilder(...args);
|
|
25
|
+
case common_1.SupportedDbtAdapter.DUCKDB:
|
|
26
|
+
return new DuckdbWarehouseClient_1.DuckdbSqlBuilder(...args);
|
|
24
27
|
case common_1.SupportedDbtAdapter.POSTGRES:
|
|
25
28
|
return new PostgresWarehouseClient_1.PostgresSqlBuilder(...args);
|
|
26
29
|
case common_1.SupportedDbtAdapter.REDSHIFT:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lightdash/warehouses",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2533.0",
|
|
4
4
|
"description": "Warehouse connectors for Lightdash",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"@aws-sdk/client-athena": "^3.972.0",
|
|
17
17
|
"@clickhouse/client": "^1.12.1",
|
|
18
18
|
"@databricks/sql": "1.8.4",
|
|
19
|
+
"@duckdb/node-api": "^1.4.4-r.1",
|
|
19
20
|
"@google-cloud/bigquery": "^7.9.2",
|
|
20
21
|
"lodash": "^4.17.23",
|
|
21
22
|
"node-fetch": "^2.7.0",
|
|
@@ -24,7 +25,7 @@
|
|
|
24
25
|
"snowflake-sdk": "~2.3.4",
|
|
25
26
|
"ssh2": "^1.14.0",
|
|
26
27
|
"trino-client": "0.2.9",
|
|
27
|
-
"@lightdash/common": "0.
|
|
28
|
+
"@lightdash/common": "0.2533.0"
|
|
28
29
|
},
|
|
29
30
|
"devDependencies": {
|
|
30
31
|
"@types/node-fetch": "^2.6.13",
|