@malloydata/db-duckdb 0.0.1 → 0.0.4
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/duckdb_common.d.ts +60 -0
- package/dist/duckdb_common.js +290 -0
- package/dist/duckdb_connection.d.ts +5 -46
- package/dist/duckdb_connection.js +23 -273
- package/dist/duckdb_wasm_connection.d.ts +22 -0
- package/dist/duckdb_wasm_connection.js +174 -0
- package/package.json +9 -2
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Connection, FetchSchemaAndRunSimultaneously, FetchSchemaAndRunStreamSimultaneously, MalloyQueryData, PersistSQLResults, PooledConnection, RunSQLOptions, SQLBlock, StreamingConnection, StructDef, QueryDataRow } from "@malloydata/malloy";
|
|
2
|
+
export interface DuckDBQueryOptions {
|
|
3
|
+
rowLimit: number;
|
|
4
|
+
}
|
|
5
|
+
export declare type QueryOptionsReader = Partial<DuckDBQueryOptions> | (() => Partial<DuckDBQueryOptions>);
|
|
6
|
+
export declare abstract class DuckDBCommon implements Connection, PersistSQLResults, StreamingConnection {
|
|
7
|
+
private queryOptions?;
|
|
8
|
+
static DEFAULT_QUERY_OPTIONS: DuckDBQueryOptions;
|
|
9
|
+
readonly name: string;
|
|
10
|
+
get dialectName(): string;
|
|
11
|
+
private readQueryOptions;
|
|
12
|
+
constructor(queryOptions?: QueryOptionsReader | undefined);
|
|
13
|
+
isPool(): this is PooledConnection;
|
|
14
|
+
canPersist(): this is PersistSQLResults;
|
|
15
|
+
protected abstract setup(): Promise<void>;
|
|
16
|
+
protected abstract runDuckDBQuery(sql: string): Promise<{
|
|
17
|
+
rows: QueryDataRow[];
|
|
18
|
+
totalRows: number;
|
|
19
|
+
}>;
|
|
20
|
+
runRawSQL(sql: string): Promise<{
|
|
21
|
+
rows: QueryDataRow[];
|
|
22
|
+
totalRows: number;
|
|
23
|
+
}>;
|
|
24
|
+
runSQL(sql: string, options?: RunSQLOptions): Promise<MalloyQueryData>;
|
|
25
|
+
abstract runSQLStream(sql: string, _options: RunSQLOptions): AsyncIterableIterator<QueryDataRow>;
|
|
26
|
+
runSQLBlockAndFetchResultSchema(sqlBlock: SQLBlock): Promise<{
|
|
27
|
+
data: MalloyQueryData;
|
|
28
|
+
schema: StructDef;
|
|
29
|
+
}>;
|
|
30
|
+
private getSQLBlockSchema;
|
|
31
|
+
/**
|
|
32
|
+
* Split's a structs columns declaration into individual columns
|
|
33
|
+
* to be fed back into fillStructDefFromTypeMap(). Handles commas
|
|
34
|
+
* within nested STRUCT() declarations.
|
|
35
|
+
*
|
|
36
|
+
* (https://github.com/looker-open-source/malloy/issues/635)
|
|
37
|
+
*
|
|
38
|
+
* @param s struct's column declaration
|
|
39
|
+
* @returns Array of column type declarations
|
|
40
|
+
*/
|
|
41
|
+
private splitColumns;
|
|
42
|
+
private stringToTypeMap;
|
|
43
|
+
private fillStructDefFromTypeMap;
|
|
44
|
+
private schemaFromQuery;
|
|
45
|
+
fetchSchemaForSQLBlocks(sqlRefs: SQLBlock[]): Promise<{
|
|
46
|
+
schemas: Record<string, StructDef>;
|
|
47
|
+
errors: Record<string, string>;
|
|
48
|
+
}>;
|
|
49
|
+
fetchSchemaForTables(tables: string[]): Promise<{
|
|
50
|
+
schemas: Record<string, StructDef>;
|
|
51
|
+
errors: Record<string, string>;
|
|
52
|
+
}>;
|
|
53
|
+
private getTableSchema;
|
|
54
|
+
canFetchSchemaAndRunSimultaneously(): this is FetchSchemaAndRunSimultaneously;
|
|
55
|
+
canStream(): this is StreamingConnection;
|
|
56
|
+
canFetchSchemaAndRunStreamSimultaneously(): this is FetchSchemaAndRunStreamSimultaneously;
|
|
57
|
+
test(): Promise<void>;
|
|
58
|
+
protected abstract createHash(sqlCommand: string): Promise<string>;
|
|
59
|
+
manifestTemporaryTable(sqlCommand: string): Promise<string>;
|
|
60
|
+
}
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DuckDBCommon = void 0;
|
|
4
|
+
/*
|
|
5
|
+
* Copyright 2021 Google LLC
|
|
6
|
+
*
|
|
7
|
+
* This program is free software; you can redistribute it and/or
|
|
8
|
+
* modify it under the terms of the GNU General Public License
|
|
9
|
+
* version 2 as published by the Free Software Foundation.
|
|
10
|
+
*
|
|
11
|
+
* This program is distributed in the hope that it will be useful,
|
|
12
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
13
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
14
|
+
* GNU General Public License for more details.
|
|
15
|
+
*/
|
|
16
|
+
const malloy_1 = require("@malloydata/malloy");
|
|
17
|
+
const duckDBToMalloyTypes = {
|
|
18
|
+
BIGINT: "number",
|
|
19
|
+
DOUBLE: "number",
|
|
20
|
+
VARCHAR: "string",
|
|
21
|
+
DATE: "date",
|
|
22
|
+
TIMESTAMP: "timestamp",
|
|
23
|
+
TIME: "string",
|
|
24
|
+
DECIMAL: "number",
|
|
25
|
+
BOOLEAN: "boolean",
|
|
26
|
+
INTEGER: "number",
|
|
27
|
+
};
|
|
28
|
+
class DuckDBCommon {
|
|
29
|
+
constructor(queryOptions) {
|
|
30
|
+
this.queryOptions = queryOptions;
|
|
31
|
+
this.name = "duckdb_common";
|
|
32
|
+
}
|
|
33
|
+
get dialectName() {
|
|
34
|
+
return "duckdb";
|
|
35
|
+
}
|
|
36
|
+
readQueryOptions() {
|
|
37
|
+
const options = DuckDBCommon.DEFAULT_QUERY_OPTIONS;
|
|
38
|
+
if (this.queryOptions) {
|
|
39
|
+
if (this.queryOptions instanceof Function) {
|
|
40
|
+
return { ...options, ...this.queryOptions() };
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
return { ...options, ...this.queryOptions };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
return options;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
isPool() {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
canPersist() {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
async runRawSQL(sql) {
|
|
57
|
+
await this.setup();
|
|
58
|
+
return this.runDuckDBQuery(sql);
|
|
59
|
+
}
|
|
60
|
+
async runSQL(sql, options = {}) {
|
|
61
|
+
var _a;
|
|
62
|
+
const defaultOptions = this.readQueryOptions();
|
|
63
|
+
const rowLimit = (_a = options.rowLimit) !== null && _a !== void 0 ? _a : defaultOptions.rowLimit;
|
|
64
|
+
const statements = sql.split("-- hack: split on this");
|
|
65
|
+
while (statements.length > 1) {
|
|
66
|
+
await this.runRawSQL(statements[0]);
|
|
67
|
+
statements.shift();
|
|
68
|
+
}
|
|
69
|
+
const retVal = await this.runRawSQL(statements[0]);
|
|
70
|
+
let result = retVal.rows;
|
|
71
|
+
if (result.length > rowLimit) {
|
|
72
|
+
result = result.slice(0, rowLimit);
|
|
73
|
+
}
|
|
74
|
+
return { rows: result, totalRows: result.length };
|
|
75
|
+
}
|
|
76
|
+
async runSQLBlockAndFetchResultSchema(sqlBlock) {
|
|
77
|
+
const data = await this.runSQL(sqlBlock.select);
|
|
78
|
+
const schema = (await this.fetchSchemaForSQLBlocks([sqlBlock])).schemas[sqlBlock.name];
|
|
79
|
+
return { data, schema };
|
|
80
|
+
}
|
|
81
|
+
async getSQLBlockSchema(sqlRef) {
|
|
82
|
+
const structDef = {
|
|
83
|
+
type: "struct",
|
|
84
|
+
dialect: "duckdb",
|
|
85
|
+
name: sqlRef.name,
|
|
86
|
+
structSource: {
|
|
87
|
+
type: "sql",
|
|
88
|
+
method: "subquery",
|
|
89
|
+
sqlBlock: sqlRef,
|
|
90
|
+
},
|
|
91
|
+
structRelationship: {
|
|
92
|
+
type: "basetable",
|
|
93
|
+
connectionName: this.name,
|
|
94
|
+
},
|
|
95
|
+
fields: [],
|
|
96
|
+
};
|
|
97
|
+
await this.schemaFromQuery(`DESCRIBE SELECT * FROM (${sqlRef.select})`, structDef);
|
|
98
|
+
return structDef;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Split's a structs columns declaration into individual columns
|
|
102
|
+
* to be fed back into fillStructDefFromTypeMap(). Handles commas
|
|
103
|
+
* within nested STRUCT() declarations.
|
|
104
|
+
*
|
|
105
|
+
* (https://github.com/looker-open-source/malloy/issues/635)
|
|
106
|
+
*
|
|
107
|
+
* @param s struct's column declaration
|
|
108
|
+
* @returns Array of column type declarations
|
|
109
|
+
*/
|
|
110
|
+
splitColumns(s) {
|
|
111
|
+
const columns = [];
|
|
112
|
+
let parens = 0;
|
|
113
|
+
let column = "";
|
|
114
|
+
let eatSpaces = true;
|
|
115
|
+
for (let idx = 0; idx < s.length; idx++) {
|
|
116
|
+
const c = s.charAt(idx);
|
|
117
|
+
if (eatSpaces && c === " ") {
|
|
118
|
+
// Eat space
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
eatSpaces = false;
|
|
122
|
+
if (!parens && c === ",") {
|
|
123
|
+
columns.push(column);
|
|
124
|
+
column = "";
|
|
125
|
+
eatSpaces = true;
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
column += c;
|
|
129
|
+
}
|
|
130
|
+
if (c === "(") {
|
|
131
|
+
parens += 1;
|
|
132
|
+
}
|
|
133
|
+
else if (c === ")") {
|
|
134
|
+
parens -= 1;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
columns.push(column);
|
|
139
|
+
return columns;
|
|
140
|
+
}
|
|
141
|
+
stringToTypeMap(s) {
|
|
142
|
+
const ret = {};
|
|
143
|
+
const columns = this.splitColumns(s);
|
|
144
|
+
for (const c of columns) {
|
|
145
|
+
//const [name, type] = c.split(" ", 1);
|
|
146
|
+
const columnMatch = c.match(/^(?<name>[^\s]+) (?<type>.*)$/);
|
|
147
|
+
if (columnMatch && columnMatch.groups) {
|
|
148
|
+
ret[columnMatch.groups["name"]] = columnMatch.groups["type"];
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
throw new Error(`Badly form Structure definition ${s}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return ret;
|
|
155
|
+
}
|
|
156
|
+
fillStructDefFromTypeMap(structDef, typeMap) {
|
|
157
|
+
for (const name in typeMap) {
|
|
158
|
+
let duckDBType = typeMap[name];
|
|
159
|
+
// Remove DECIMAL(x,y) precision to simplify lookup
|
|
160
|
+
duckDBType = duckDBType.replace(/^DECIMAL\(\d+,\d+\)/g, "DECIMAL");
|
|
161
|
+
let malloyType = duckDBToMalloyTypes[duckDBType];
|
|
162
|
+
const arrayMatch = duckDBType.match(/(?<duckDBType>.*)\[\]$/);
|
|
163
|
+
if (arrayMatch && arrayMatch.groups) {
|
|
164
|
+
duckDBType = arrayMatch.groups["duckDBType"];
|
|
165
|
+
}
|
|
166
|
+
const structMatch = duckDBType.match(/^STRUCT\((?<fields>.*)\)$/);
|
|
167
|
+
if (structMatch && structMatch.groups) {
|
|
168
|
+
const newTypeMap = this.stringToTypeMap(structMatch.groups["fields"]);
|
|
169
|
+
const innerStructDef = {
|
|
170
|
+
type: "struct",
|
|
171
|
+
name,
|
|
172
|
+
dialect: this.dialectName,
|
|
173
|
+
structSource: { type: arrayMatch ? "nested" : "inline" },
|
|
174
|
+
structRelationship: {
|
|
175
|
+
type: arrayMatch ? "nested" : "inline",
|
|
176
|
+
field: name,
|
|
177
|
+
isArray: false,
|
|
178
|
+
},
|
|
179
|
+
fields: [],
|
|
180
|
+
};
|
|
181
|
+
this.fillStructDefFromTypeMap(innerStructDef, newTypeMap);
|
|
182
|
+
structDef.fields.push(innerStructDef);
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
if (arrayMatch) {
|
|
186
|
+
malloyType = duckDBToMalloyTypes[duckDBType];
|
|
187
|
+
const innerStructDef = {
|
|
188
|
+
type: "struct",
|
|
189
|
+
name,
|
|
190
|
+
dialect: this.dialectName,
|
|
191
|
+
structSource: { type: "nested" },
|
|
192
|
+
structRelationship: { type: "nested", field: name, isArray: true },
|
|
193
|
+
fields: [{ type: malloyType, name: "value" }],
|
|
194
|
+
};
|
|
195
|
+
structDef.fields.push(innerStructDef);
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
if (malloyType !== undefined) {
|
|
199
|
+
structDef.fields.push({
|
|
200
|
+
type: malloyType,
|
|
201
|
+
name,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
throw new Error(`unknown duckdb type ${duckDBType}`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
async schemaFromQuery(infoQuery, structDef) {
|
|
212
|
+
const typeMap = {};
|
|
213
|
+
const result = await this.runRawSQL(infoQuery);
|
|
214
|
+
for (const row of result.rows) {
|
|
215
|
+
typeMap[row["column_name"]] = row["column_type"];
|
|
216
|
+
}
|
|
217
|
+
this.fillStructDefFromTypeMap(structDef, typeMap);
|
|
218
|
+
}
|
|
219
|
+
async fetchSchemaForSQLBlocks(sqlRefs) {
|
|
220
|
+
const schemas = {};
|
|
221
|
+
const errors = {};
|
|
222
|
+
for (const sqlRef of sqlRefs) {
|
|
223
|
+
try {
|
|
224
|
+
schemas[sqlRef.name] = await this.getSQLBlockSchema(sqlRef);
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
227
|
+
errors[sqlRef.name] = error;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return { schemas, errors };
|
|
231
|
+
}
|
|
232
|
+
async fetchSchemaForTables(tables) {
|
|
233
|
+
const schemas = {};
|
|
234
|
+
const errors = {};
|
|
235
|
+
for (const tableURL of tables) {
|
|
236
|
+
try {
|
|
237
|
+
schemas[tableURL] = await this.getTableSchema(tableURL);
|
|
238
|
+
}
|
|
239
|
+
catch (error) {
|
|
240
|
+
errors[tableURL] = error.toString();
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return { schemas, errors };
|
|
244
|
+
}
|
|
245
|
+
async getTableSchema(tableURL) {
|
|
246
|
+
const { tablePath } = (0, malloy_1.parseTableURI)(tableURL);
|
|
247
|
+
const structDef = {
|
|
248
|
+
type: "struct",
|
|
249
|
+
name: tablePath,
|
|
250
|
+
dialect: "duckdb",
|
|
251
|
+
structSource: { type: "table", tablePath },
|
|
252
|
+
structRelationship: {
|
|
253
|
+
type: "basetable",
|
|
254
|
+
connectionName: this.name,
|
|
255
|
+
},
|
|
256
|
+
fields: [],
|
|
257
|
+
};
|
|
258
|
+
const quotedTablePath = tablePath.match(/\//)
|
|
259
|
+
? `'${tablePath}'`
|
|
260
|
+
: tablePath;
|
|
261
|
+
const infoQuery = `DESCRIBE SELECT * FROM ${quotedTablePath}`;
|
|
262
|
+
await this.schemaFromQuery(infoQuery, structDef);
|
|
263
|
+
return structDef;
|
|
264
|
+
}
|
|
265
|
+
canFetchSchemaAndRunSimultaneously() {
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
canStream() {
|
|
269
|
+
return true;
|
|
270
|
+
}
|
|
271
|
+
canFetchSchemaAndRunStreamSimultaneously() {
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
async test() {
|
|
275
|
+
await this.runRawSQL("SELECT 1");
|
|
276
|
+
}
|
|
277
|
+
async manifestTemporaryTable(sqlCommand) {
|
|
278
|
+
const hash = await this.createHash(sqlCommand);
|
|
279
|
+
const tableName = `tt${hash}`;
|
|
280
|
+
const cmd = `CREATE TEMPORARY TABLE IF NOT EXISTS ${tableName} AS (${sqlCommand});`;
|
|
281
|
+
// console.log(cmd);
|
|
282
|
+
await this.runRawSQL(cmd);
|
|
283
|
+
return tableName;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
exports.DuckDBCommon = DuckDBCommon;
|
|
287
|
+
DuckDBCommon.DEFAULT_QUERY_OPTIONS = {
|
|
288
|
+
rowLimit: 10,
|
|
289
|
+
};
|
|
290
|
+
//# sourceMappingURL=duckdb_common.js.map
|
|
@@ -1,60 +1,19 @@
|
|
|
1
1
|
/// <reference path="../src/duckdb.d.ts" />
|
|
2
|
-
import {
|
|
3
|
-
import { FetchSchemaAndRunSimultaneously, FetchSchemaAndRunStreamSimultaneously, StreamingConnection } from "@malloydata/malloy/src/runtime_types";
|
|
2
|
+
import { DuckDBCommon, QueryOptionsReader } from "./duckdb_common";
|
|
4
3
|
import { Database, Row } from "duckdb";
|
|
5
|
-
import { RunSQLOptions } from "@malloydata/malloy
|
|
6
|
-
export declare class DuckDBConnection
|
|
4
|
+
import { QueryDataRow, RunSQLOptions } from "@malloydata/malloy";
|
|
5
|
+
export declare class DuckDBConnection extends DuckDBCommon {
|
|
7
6
|
readonly name: string;
|
|
8
7
|
private workingDirectory;
|
|
9
8
|
protected connection: import("duckdb").Connection;
|
|
10
9
|
protected database: Database;
|
|
11
10
|
protected isSetup: boolean;
|
|
12
|
-
constructor(name: string, databasePath?: string, workingDirectory?: string);
|
|
13
|
-
get dialectName(): string;
|
|
14
|
-
isPool(): this is PooledConnection;
|
|
15
|
-
canPersist(): this is PersistSQLResults;
|
|
11
|
+
constructor(name: string, databasePath?: string, workingDirectory?: string, queryOptions?: QueryOptionsReader);
|
|
16
12
|
protected setup(): Promise<void>;
|
|
17
13
|
protected runDuckDBQuery(sql: string): Promise<{
|
|
18
14
|
rows: Row[];
|
|
19
15
|
totalRows: number;
|
|
20
16
|
}>;
|
|
21
|
-
runRawSQL(sql: string): Promise<{
|
|
22
|
-
rows: Row[];
|
|
23
|
-
totalRows: number;
|
|
24
|
-
}>;
|
|
25
|
-
runSQL(sql: string, options?: RunSQLOptions): Promise<MalloyQueryData>;
|
|
26
17
|
runSQLStream(sql: string, _options?: RunSQLOptions): AsyncIterableIterator<QueryDataRow>;
|
|
27
|
-
|
|
28
|
-
data: MalloyQueryData;
|
|
29
|
-
schema: StructDef;
|
|
30
|
-
}>;
|
|
31
|
-
private getSQLBlockSchema;
|
|
32
|
-
/**
|
|
33
|
-
* Split's a structs columns declaration into individual columns
|
|
34
|
-
* to be fed back into fillStructDefFromTypeMap(). Handles commas
|
|
35
|
-
* within nested STRUCT() declarations.
|
|
36
|
-
*
|
|
37
|
-
* (https://github.com/looker-open-source/malloy/issues/635)
|
|
38
|
-
*
|
|
39
|
-
* @param s struct's column declaration
|
|
40
|
-
* @returns Array of column type declarations
|
|
41
|
-
*/
|
|
42
|
-
private splitColumns;
|
|
43
|
-
private stringToTypeMap;
|
|
44
|
-
private fillStructDefFromTypeMap;
|
|
45
|
-
private schemaFromQuery;
|
|
46
|
-
fetchSchemaForSQLBlocks(sqlRefs: SQLBlock[]): Promise<{
|
|
47
|
-
schemas: Record<string, StructDef>;
|
|
48
|
-
errors: Record<string, string>;
|
|
49
|
-
}>;
|
|
50
|
-
fetchSchemaForTables(tables: string[]): Promise<{
|
|
51
|
-
schemas: Record<string, StructDef>;
|
|
52
|
-
errors: Record<string, string>;
|
|
53
|
-
}>;
|
|
54
|
-
private getTableSchema;
|
|
55
|
-
canFetchSchemaAndRunSimultaneously(): this is FetchSchemaAndRunSimultaneously;
|
|
56
|
-
canStream(): this is StreamingConnection;
|
|
57
|
-
canFetchSchemaAndRunStreamSimultaneously(): this is FetchSchemaAndRunStreamSimultaneously;
|
|
58
|
-
test(): Promise<void>;
|
|
59
|
-
manifestTemporaryTable(sqlCommand: string): Promise<string>;
|
|
18
|
+
createHash(sqlCommand: string): Promise<string>;
|
|
60
19
|
}
|
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright 2022 Google LLC
|
|
4
|
+
*
|
|
5
|
+
* This program is free software; you can redistribute it and/or
|
|
6
|
+
* modify it under the terms of the GNU General Public License
|
|
7
|
+
* version 2 as published by the Free Software Foundation.
|
|
8
|
+
*
|
|
9
|
+
* This program is distributed in the hope that it will be useful,
|
|
10
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
* GNU General Public License for more details.
|
|
13
|
+
*/
|
|
2
14
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
15
|
if (k2 === undefined) k2 = k;
|
|
4
16
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
@@ -24,34 +36,12 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
24
36
|
};
|
|
25
37
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
38
|
exports.DuckDBConnection = void 0;
|
|
27
|
-
/*
|
|
28
|
-
* Copyright 2021 Google LLC
|
|
29
|
-
*
|
|
30
|
-
* This program is free software; you can redistribute it and/or
|
|
31
|
-
* modify it under the terms of the GNU General Public License
|
|
32
|
-
* version 2 as published by the Free Software Foundation.
|
|
33
|
-
*
|
|
34
|
-
* This program is distributed in the hope that it will be useful,
|
|
35
|
-
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
36
|
-
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
37
|
-
* GNU General Public License for more details.
|
|
38
|
-
*/
|
|
39
39
|
const crypto = __importStar(require("crypto"));
|
|
40
|
-
const
|
|
40
|
+
const duckdb_common_1 = require("./duckdb_common");
|
|
41
41
|
const duckdb_1 = require("duckdb");
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
VARCHAR: "string",
|
|
46
|
-
DATE: "date",
|
|
47
|
-
TIMESTAMP: "timestamp",
|
|
48
|
-
TIME: "string",
|
|
49
|
-
DECIMAL: "number",
|
|
50
|
-
BOOLEAN: "boolean",
|
|
51
|
-
INTEGER: "number",
|
|
52
|
-
};
|
|
53
|
-
class DuckDBConnection {
|
|
54
|
-
constructor(name, databasePath = "test/data/duckdb/duckdb_test.db", workingDirectory = "/") {
|
|
42
|
+
class DuckDBConnection extends duckdb_common_1.DuckDBCommon {
|
|
43
|
+
constructor(name, databasePath = "test/data/duckdb/duckdb_test.db", workingDirectory = "/", queryOptions) {
|
|
44
|
+
super(queryOptions);
|
|
55
45
|
this.name = name;
|
|
56
46
|
this.workingDirectory = workingDirectory;
|
|
57
47
|
this.isSetup = false;
|
|
@@ -63,15 +53,6 @@ class DuckDBConnection {
|
|
|
63
53
|
});
|
|
64
54
|
this.connection = this.database.connect();
|
|
65
55
|
}
|
|
66
|
-
get dialectName() {
|
|
67
|
-
return "duckdb";
|
|
68
|
-
}
|
|
69
|
-
isPool() {
|
|
70
|
-
return false;
|
|
71
|
-
}
|
|
72
|
-
canPersist() {
|
|
73
|
-
return true;
|
|
74
|
-
}
|
|
75
56
|
async setup() {
|
|
76
57
|
if (!this.isSetup) {
|
|
77
58
|
if (this.workingDirectory) {
|
|
@@ -98,37 +79,19 @@ class DuckDBConnection {
|
|
|
98
79
|
}
|
|
99
80
|
async runDuckDBQuery(sql) {
|
|
100
81
|
return new Promise((resolve, reject) => {
|
|
101
|
-
this.connection.all(sql, (err,
|
|
82
|
+
this.connection.all(sql, (err, rows) => {
|
|
102
83
|
if (err) {
|
|
103
84
|
reject(err);
|
|
104
85
|
}
|
|
105
|
-
else
|
|
86
|
+
else {
|
|
106
87
|
resolve({
|
|
107
|
-
rows
|
|
108
|
-
totalRows:
|
|
88
|
+
rows,
|
|
89
|
+
totalRows: rows.length,
|
|
109
90
|
});
|
|
91
|
+
}
|
|
110
92
|
});
|
|
111
93
|
});
|
|
112
94
|
}
|
|
113
|
-
async runRawSQL(sql) {
|
|
114
|
-
await this.setup();
|
|
115
|
-
return this.runDuckDBQuery(sql);
|
|
116
|
-
}
|
|
117
|
-
async runSQL(sql, options = {}) {
|
|
118
|
-
var _a;
|
|
119
|
-
const rowLimit = (_a = options.rowLimit) !== null && _a !== void 0 ? _a : 10;
|
|
120
|
-
const statements = sql.split("-- hack: split on this");
|
|
121
|
-
while (statements.length > 1) {
|
|
122
|
-
await this.runRawSQL(statements[0]);
|
|
123
|
-
statements.shift();
|
|
124
|
-
}
|
|
125
|
-
const retVal = await this.runRawSQL(statements[0]);
|
|
126
|
-
let result = retVal.rows;
|
|
127
|
-
if (result.length > rowLimit) {
|
|
128
|
-
result = result.slice(0, rowLimit);
|
|
129
|
-
}
|
|
130
|
-
return { rows: result, totalRows: result.length };
|
|
131
|
-
}
|
|
132
95
|
async *runSQLStream(sql, _options = {}) {
|
|
133
96
|
await this.setup();
|
|
134
97
|
const statements = sql.split("-- hack: split on this");
|
|
@@ -140,221 +103,8 @@ class DuckDBConnection {
|
|
|
140
103
|
yield row;
|
|
141
104
|
}
|
|
142
105
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
const schema = (await this.fetchSchemaForSQLBlocks([sqlBlock])).schemas[sqlBlock.name];
|
|
146
|
-
return { data, schema };
|
|
147
|
-
}
|
|
148
|
-
async getSQLBlockSchema(sqlRef) {
|
|
149
|
-
const structDef = {
|
|
150
|
-
type: "struct",
|
|
151
|
-
dialect: "duckdb",
|
|
152
|
-
name: sqlRef.name,
|
|
153
|
-
structSource: {
|
|
154
|
-
type: "sql",
|
|
155
|
-
method: "subquery",
|
|
156
|
-
sqlBlock: sqlRef,
|
|
157
|
-
},
|
|
158
|
-
structRelationship: {
|
|
159
|
-
type: "basetable",
|
|
160
|
-
connectionName: this.name,
|
|
161
|
-
},
|
|
162
|
-
fields: [],
|
|
163
|
-
};
|
|
164
|
-
await this.schemaFromQuery(`DESCRIBE SELECT * FROM (${sqlRef.select})`, structDef);
|
|
165
|
-
return structDef;
|
|
166
|
-
}
|
|
167
|
-
/**
|
|
168
|
-
* Split's a structs columns declaration into individual columns
|
|
169
|
-
* to be fed back into fillStructDefFromTypeMap(). Handles commas
|
|
170
|
-
* within nested STRUCT() declarations.
|
|
171
|
-
*
|
|
172
|
-
* (https://github.com/looker-open-source/malloy/issues/635)
|
|
173
|
-
*
|
|
174
|
-
* @param s struct's column declaration
|
|
175
|
-
* @returns Array of column type declarations
|
|
176
|
-
*/
|
|
177
|
-
splitColumns(s) {
|
|
178
|
-
const columns = [];
|
|
179
|
-
let parens = 0;
|
|
180
|
-
let column = "";
|
|
181
|
-
let eatSpaces = true;
|
|
182
|
-
for (let idx = 0; idx < s.length; idx++) {
|
|
183
|
-
const c = s.charAt(idx);
|
|
184
|
-
if (eatSpaces && c === " ") {
|
|
185
|
-
// Eat space
|
|
186
|
-
}
|
|
187
|
-
else {
|
|
188
|
-
eatSpaces = false;
|
|
189
|
-
if (!parens && c === ",") {
|
|
190
|
-
columns.push(column);
|
|
191
|
-
column = "";
|
|
192
|
-
eatSpaces = true;
|
|
193
|
-
}
|
|
194
|
-
else {
|
|
195
|
-
column += c;
|
|
196
|
-
}
|
|
197
|
-
if (c === "(") {
|
|
198
|
-
parens += 1;
|
|
199
|
-
}
|
|
200
|
-
else if (c === ")") {
|
|
201
|
-
parens -= 1;
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
columns.push(column);
|
|
206
|
-
return columns;
|
|
207
|
-
}
|
|
208
|
-
stringToTypeMap(s) {
|
|
209
|
-
const ret = {};
|
|
210
|
-
const columns = this.splitColumns(s);
|
|
211
|
-
for (const c of columns) {
|
|
212
|
-
//const [name, type] = c.split(" ", 1);
|
|
213
|
-
const columnMatch = c.match(/^(?<name>[^\s]+) (?<type>.*)$/);
|
|
214
|
-
if (columnMatch && columnMatch.groups) {
|
|
215
|
-
ret[columnMatch.groups["name"]] = columnMatch.groups["type"];
|
|
216
|
-
}
|
|
217
|
-
else {
|
|
218
|
-
throw new Error(`Badly form Structure definition ${s}`);
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
return ret;
|
|
222
|
-
}
|
|
223
|
-
fillStructDefFromTypeMap(structDef, typeMap) {
|
|
224
|
-
for (const name in typeMap) {
|
|
225
|
-
let duckDBType = typeMap[name];
|
|
226
|
-
// Remove DECIMAL(x,y) precision to simplify lookup
|
|
227
|
-
duckDBType = duckDBType.replace(/^DECIMAL\(\d+,\d+\)/g, "DECIMAL");
|
|
228
|
-
let malloyType = duckDBToMalloyTypes[duckDBType];
|
|
229
|
-
const arrayMatch = duckDBType.match(/(?<duckDBType>.*)\[\]$/);
|
|
230
|
-
if (arrayMatch && arrayMatch.groups) {
|
|
231
|
-
duckDBType = arrayMatch.groups["duckDBType"];
|
|
232
|
-
}
|
|
233
|
-
const structMatch = duckDBType.match(/^STRUCT\((?<fields>.*)\)$/);
|
|
234
|
-
if (structMatch && structMatch.groups) {
|
|
235
|
-
const newTypeMap = this.stringToTypeMap(structMatch.groups["fields"]);
|
|
236
|
-
const innerStructDef = {
|
|
237
|
-
type: "struct",
|
|
238
|
-
name,
|
|
239
|
-
dialect: this.dialectName,
|
|
240
|
-
structSource: { type: arrayMatch ? "nested" : "inline" },
|
|
241
|
-
structRelationship: {
|
|
242
|
-
type: arrayMatch ? "nested" : "inline",
|
|
243
|
-
field: name,
|
|
244
|
-
isArray: false,
|
|
245
|
-
},
|
|
246
|
-
fields: [],
|
|
247
|
-
};
|
|
248
|
-
this.fillStructDefFromTypeMap(innerStructDef, newTypeMap);
|
|
249
|
-
structDef.fields.push(innerStructDef);
|
|
250
|
-
}
|
|
251
|
-
else {
|
|
252
|
-
if (arrayMatch) {
|
|
253
|
-
malloyType = duckDBToMalloyTypes[duckDBType];
|
|
254
|
-
const innerStructDef = {
|
|
255
|
-
type: "struct",
|
|
256
|
-
name,
|
|
257
|
-
dialect: this.dialectName,
|
|
258
|
-
structSource: { type: "nested" },
|
|
259
|
-
structRelationship: { type: "nested", field: name, isArray: true },
|
|
260
|
-
fields: [{ type: malloyType, name: "value" }],
|
|
261
|
-
};
|
|
262
|
-
structDef.fields.push(innerStructDef);
|
|
263
|
-
}
|
|
264
|
-
else {
|
|
265
|
-
if (malloyType !== undefined) {
|
|
266
|
-
structDef.fields.push({
|
|
267
|
-
type: malloyType,
|
|
268
|
-
name,
|
|
269
|
-
});
|
|
270
|
-
}
|
|
271
|
-
else {
|
|
272
|
-
throw new Error(`unknown duckdb type ${duckDBType}`);
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
async schemaFromQuery(infoQuery, structDef) {
|
|
279
|
-
const typeMap = {};
|
|
280
|
-
const result = await this.runRawSQL(infoQuery);
|
|
281
|
-
for (const row of result.rows) {
|
|
282
|
-
typeMap[row["column_name"]] = row["column_type"];
|
|
283
|
-
}
|
|
284
|
-
this.fillStructDefFromTypeMap(structDef, typeMap);
|
|
285
|
-
}
|
|
286
|
-
async fetchSchemaForSQLBlocks(sqlRefs) {
|
|
287
|
-
const schemas = {};
|
|
288
|
-
const errors = {};
|
|
289
|
-
for (const sqlRef of sqlRefs) {
|
|
290
|
-
try {
|
|
291
|
-
schemas[sqlRef.name] = await this.getSQLBlockSchema(sqlRef);
|
|
292
|
-
}
|
|
293
|
-
catch (error) {
|
|
294
|
-
errors[sqlRef.name] = error;
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
return { schemas, errors };
|
|
298
|
-
}
|
|
299
|
-
async fetchSchemaForTables(tables) {
|
|
300
|
-
const schemas = {};
|
|
301
|
-
const errors = {};
|
|
302
|
-
for (const tableURL of tables) {
|
|
303
|
-
try {
|
|
304
|
-
schemas[tableURL] = await this.getTableSchema(tableURL);
|
|
305
|
-
}
|
|
306
|
-
catch (error) {
|
|
307
|
-
errors[tableURL] = error.toString();
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
return { schemas, errors };
|
|
311
|
-
}
|
|
312
|
-
async getTableSchema(tableURL) {
|
|
313
|
-
const { tablePath: tableName } = (0, malloy_1.parseTableURL)(tableURL);
|
|
314
|
-
const structDef = {
|
|
315
|
-
type: "struct",
|
|
316
|
-
name: tableName,
|
|
317
|
-
dialect: "duckdb",
|
|
318
|
-
structSource: { type: "table" },
|
|
319
|
-
structRelationship: {
|
|
320
|
-
type: "basetable",
|
|
321
|
-
connectionName: this.name,
|
|
322
|
-
},
|
|
323
|
-
fields: [],
|
|
324
|
-
};
|
|
325
|
-
// const { tablePath: tableName } = parseTableURL(tableURL);
|
|
326
|
-
// const [schema, table] = tableName.split(".");
|
|
327
|
-
// if (table === undefined) {
|
|
328
|
-
// throw new Error("Default schema not yet supported in DuckDB");
|
|
329
|
-
// }
|
|
330
|
-
// const infoQuery = `
|
|
331
|
-
// SELECT column_name, data_type FROM information_schema.columns
|
|
332
|
-
// WHERE table_name = '${table}'
|
|
333
|
-
// AND table_schema = '${schema}'
|
|
334
|
-
// `;
|
|
335
|
-
const infoQuery = `DESCRIBE SELECT * FROM ${tableName.match(/\//) ? `'${tableName}'` : tableName};`;
|
|
336
|
-
await this.schemaFromQuery(infoQuery, structDef);
|
|
337
|
-
return structDef;
|
|
338
|
-
}
|
|
339
|
-
canFetchSchemaAndRunSimultaneously() {
|
|
340
|
-
return false;
|
|
341
|
-
}
|
|
342
|
-
canStream() {
|
|
343
|
-
return true;
|
|
344
|
-
}
|
|
345
|
-
canFetchSchemaAndRunStreamSimultaneously() {
|
|
346
|
-
return false;
|
|
347
|
-
}
|
|
348
|
-
async test() {
|
|
349
|
-
await this.runRawSQL("SELECT 1");
|
|
350
|
-
}
|
|
351
|
-
async manifestTemporaryTable(sqlCommand) {
|
|
352
|
-
const hash = crypto.createHash("md5").update(sqlCommand).digest("hex");
|
|
353
|
-
const tableName = `tt${hash}`;
|
|
354
|
-
const cmd = `CREATE TEMPORARY TABLE IF NOT EXISTS ${tableName} AS (${sqlCommand});`;
|
|
355
|
-
// console.log(cmd);
|
|
356
|
-
await this.runRawSQL(cmd);
|
|
357
|
-
return tableName;
|
|
106
|
+
createHash(sqlCommand) {
|
|
107
|
+
return Promise.resolve(crypto.createHash("md5").update(sqlCommand).digest("hex"));
|
|
358
108
|
}
|
|
359
109
|
}
|
|
360
110
|
exports.DuckDBConnection = DuckDBConnection;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { QueryDataRow, RunSQLOptions } from "@malloydata/malloy";
|
|
2
|
+
import * as duckdb from "@duckdb/duckdb-wasm";
|
|
3
|
+
import { DuckDBCommon, QueryOptionsReader } from "./duckdb_common";
|
|
4
|
+
export declare class DuckDBWASMConnection extends DuckDBCommon {
|
|
5
|
+
readonly name: string;
|
|
6
|
+
private workingDirectory;
|
|
7
|
+
connecting: Promise<void>;
|
|
8
|
+
protected _connection: duckdb.AsyncDuckDBConnection | null;
|
|
9
|
+
protected _database: duckdb.AsyncDuckDB | null;
|
|
10
|
+
protected isSetup: boolean;
|
|
11
|
+
constructor(name: string, databasePath?: string, workingDirectory?: string, queryOptions?: QueryOptionsReader);
|
|
12
|
+
private init;
|
|
13
|
+
get connection(): duckdb.AsyncDuckDBConnection | null;
|
|
14
|
+
get database(): duckdb.AsyncDuckDB | null;
|
|
15
|
+
protected setup(): Promise<void>;
|
|
16
|
+
protected runDuckDBQuery(sql: string): Promise<{
|
|
17
|
+
rows: QueryDataRow[];
|
|
18
|
+
totalRows: number;
|
|
19
|
+
}>;
|
|
20
|
+
runSQLStream(sql: string, _options?: RunSQLOptions): AsyncIterableIterator<QueryDataRow>;
|
|
21
|
+
protected createHash(sqlCommand: string): Promise<string>;
|
|
22
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.DuckDBWASMConnection = void 0;
|
|
27
|
+
const duckdb = __importStar(require("@duckdb/duckdb-wasm"));
|
|
28
|
+
const apache_arrow_1 = require("apache-arrow");
|
|
29
|
+
const duckdb_common_1 = require("./duckdb_common");
|
|
30
|
+
/**
|
|
31
|
+
* Arrow's toJSON() doesn't really do what I'd expect, since
|
|
32
|
+
* it still includes Arrow objects like DecimalBigNums and Vectors,
|
|
33
|
+
* so we need this fairly gross function to unwrap those.
|
|
34
|
+
*
|
|
35
|
+
* @param value Element from an Arrow StructRow.
|
|
36
|
+
* @returns Vanilla Javascript value
|
|
37
|
+
*/
|
|
38
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
39
|
+
const unwrapArrow = (value) => {
|
|
40
|
+
if (value === null) {
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
else if (value instanceof apache_arrow_1.Vector) {
|
|
44
|
+
return [...value].map(unwrapArrow);
|
|
45
|
+
}
|
|
46
|
+
else if (value instanceof Date) {
|
|
47
|
+
return value;
|
|
48
|
+
}
|
|
49
|
+
else if (typeof value === "bigint") {
|
|
50
|
+
return Number(value);
|
|
51
|
+
}
|
|
52
|
+
else if (typeof value === "object") {
|
|
53
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
54
|
+
const obj = value;
|
|
55
|
+
// DecimalBigNums appear as Uint32Arrays, but can be identified
|
|
56
|
+
// because they have a Symbol.toPrimitive method
|
|
57
|
+
if (obj[Symbol.toPrimitive]) {
|
|
58
|
+
// There seems to be a bug in [Symbol.toPrimitive]("number") so
|
|
59
|
+
// convert to string first and then to number.
|
|
60
|
+
return Number(obj[Symbol.toPrimitive]());
|
|
61
|
+
}
|
|
62
|
+
else if (Array.isArray(value)) {
|
|
63
|
+
return value.map(unwrapArrow);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
67
|
+
const result = {};
|
|
68
|
+
for (const key in obj) {
|
|
69
|
+
result[key] = unwrapArrow(obj[key]);
|
|
70
|
+
}
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return value;
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Process a single Arrow result row into a Malloy QueryDataRow
|
|
78
|
+
* Unfortunately simply calling JSONParse(JSON.stringify(row)) even
|
|
79
|
+
* winds up converting DecimalBigNums to strings instead of numbers.
|
|
80
|
+
* For some reason a custom replacer only sees DecimalBigNums as
|
|
81
|
+
* strings, as well.
|
|
82
|
+
*/
|
|
83
|
+
const unwrapRow = (row) => {
|
|
84
|
+
return unwrapArrow(row.toJSON());
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* Process a duckedb Table into an array of Malloy QueryDataRows
|
|
88
|
+
*/
|
|
89
|
+
const unwrapTable = (table) => {
|
|
90
|
+
return table.toArray().map(unwrapRow);
|
|
91
|
+
};
|
|
92
|
+
class DuckDBWASMConnection extends duckdb_common_1.DuckDBCommon {
|
|
93
|
+
constructor(name, databasePath = "test/data/duckdb/duckdb_test.db", workingDirectory = "/", queryOptions) {
|
|
94
|
+
super(queryOptions);
|
|
95
|
+
this.name = name;
|
|
96
|
+
this.workingDirectory = workingDirectory;
|
|
97
|
+
this._connection = null;
|
|
98
|
+
this._database = null;
|
|
99
|
+
this.isSetup = false;
|
|
100
|
+
this.connecting = this.init();
|
|
101
|
+
}
|
|
102
|
+
async init() {
|
|
103
|
+
const JSDELIVR_BUNDLES = duckdb.getJsDelivrBundles();
|
|
104
|
+
// Select a bundle based on browser checks
|
|
105
|
+
const bundle = await duckdb.selectBundle(JSDELIVR_BUNDLES);
|
|
106
|
+
if (bundle.mainWorker) {
|
|
107
|
+
const workerUrl = URL.createObjectURL(new Blob([`importScripts("${bundle.mainWorker}");`], {
|
|
108
|
+
type: "text/javascript",
|
|
109
|
+
}));
|
|
110
|
+
// Instantiate the asynchronous version of DuckDB-wasm
|
|
111
|
+
const worker = new Worker(workerUrl);
|
|
112
|
+
const logger = new duckdb.ConsoleLogger();
|
|
113
|
+
this._database = new duckdb.AsyncDuckDB(logger, worker);
|
|
114
|
+
await this._database.instantiate(bundle.mainModule, bundle.pthreadWorker);
|
|
115
|
+
URL.revokeObjectURL(workerUrl);
|
|
116
|
+
this._connection = await this._database.connect();
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
throw new Error("Unable to instantiate duckdb-wasm");
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
get connection() {
|
|
123
|
+
return this._connection;
|
|
124
|
+
}
|
|
125
|
+
get database() {
|
|
126
|
+
return this._database;
|
|
127
|
+
}
|
|
128
|
+
async setup() {
|
|
129
|
+
await this.connecting;
|
|
130
|
+
}
|
|
131
|
+
async runDuckDBQuery(sql) {
|
|
132
|
+
var _a;
|
|
133
|
+
const table = await ((_a = this.connection) === null || _a === void 0 ? void 0 : _a.query(sql));
|
|
134
|
+
if ((table === null || table === void 0 ? void 0 : table.numRows) != null) {
|
|
135
|
+
const rows = unwrapTable(table);
|
|
136
|
+
// console.log(rows);
|
|
137
|
+
return {
|
|
138
|
+
// Normalize the data from its default proxied form
|
|
139
|
+
rows,
|
|
140
|
+
totalRows: table.numRows,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
throw new Error("Boom");
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
async *runSQLStream(sql, _options = {}) {
|
|
148
|
+
if (!this.connection) {
|
|
149
|
+
throw new Error("duckdb-wasm not connected");
|
|
150
|
+
}
|
|
151
|
+
await this.setup();
|
|
152
|
+
const statements = sql.split("-- hack: split on this");
|
|
153
|
+
while (statements.length > 1) {
|
|
154
|
+
await this.runDuckDBQuery(statements[0]);
|
|
155
|
+
statements.shift();
|
|
156
|
+
}
|
|
157
|
+
for await (const chunk of await this.connection.send(statements[0])) {
|
|
158
|
+
for (const row of chunk.toArray()) {
|
|
159
|
+
yield unwrapRow(row);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
async createHash(sqlCommand) {
|
|
164
|
+
const msgUint8 = new TextEncoder().encode(sqlCommand);
|
|
165
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", msgUint8);
|
|
166
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
167
|
+
const hashHex = hashArray
|
|
168
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
169
|
+
.join("");
|
|
170
|
+
return hashHex;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
exports.DuckDBWASMConnection = DuckDBWASMConnection;
|
|
174
|
+
//# sourceMappingURL=duckdb_wasm_connection.js.map
|
package/package.json
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@malloydata/db-duckdb",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"license": "GPL-2.0",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
|
+
"homepage": "https://github.com/looker-open-source/malloy#readme",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/looker-open-source/malloy"
|
|
11
|
+
},
|
|
7
12
|
"scripts": {
|
|
8
13
|
"lint": "eslint '**/*.ts{,x}'",
|
|
9
14
|
"lint-fix": "eslint '**/*.ts{,x}' --fix",
|
|
@@ -12,6 +17,8 @@
|
|
|
12
17
|
"malloyc": "ts-node ../../scripts/malloy-to-json"
|
|
13
18
|
},
|
|
14
19
|
"dependencies": {
|
|
15
|
-
"duckdb": "
|
|
20
|
+
"@duckdb/duckdb-wasm": "^1.17.0",
|
|
21
|
+
"@malloydata/malloy": "^0.0.4",
|
|
22
|
+
"duckdb": "0.5.1"
|
|
16
23
|
}
|
|
17
24
|
}
|