@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.
@@ -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 { Connection, MalloyQueryData, PersistSQLResults, PooledConnection, SQLBlock, StructDef, QueryDataRow } from "@malloydata/malloy";
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/src/malloy";
6
- export declare class DuckDBConnection implements Connection, PersistSQLResults, StreamingConnection {
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
- runSQLBlockAndFetchResultSchema(sqlBlock: SQLBlock): Promise<{
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 malloy_1 = require("@malloydata/malloy");
40
+ const duckdb_common_1 = require("./duckdb_common");
41
41
  const duckdb_1 = require("duckdb");
42
- const duckDBToMalloyTypes = {
43
- BIGINT: "number",
44
- DOUBLE: "number",
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, result) => {
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: result,
108
- totalRows: result.length,
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
- async runSQLBlockAndFetchResultSchema(sqlBlock) {
144
- const data = await this.runSQL(sqlBlock.select);
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.1",
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": "0.4.1-dev1094.0"
20
+ "@duckdb/duckdb-wasm": "^1.17.0",
21
+ "@malloydata/malloy": "^0.0.4",
22
+ "duckdb": "0.5.1"
16
23
  }
17
24
  }