@malloydata/db-duckdb 0.0.120-dev240125201336 → 0.0.120-dev240131220338

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.
@@ -32,51 +32,72 @@ const duckdb_connection_1 = require("./duckdb_connection");
32
32
  */
33
33
  describe('DuckDBConnection', () => {
34
34
  let connection;
35
- let runRawSQL;
36
35
  beforeAll(async () => {
37
36
  connection = new duckdb_connection_1.DuckDBConnection('duckdb');
38
37
  await connection.runSQL('SELECT 1');
38
+ expect(Object.keys(duckdb_connection_1.DuckDBConnection.activeDBs).length).toEqual(1);
39
39
  });
40
40
  afterAll(async () => {
41
41
  await connection.close();
42
+ expect(Object.keys(duckdb_connection_1.DuckDBConnection.activeDBs).length).toEqual(0);
42
43
  });
43
- beforeEach(async () => {
44
- runRawSQL = jest
45
- .spyOn(duckdb_common_1.DuckDBCommon.prototype, 'runRawSQL')
46
- .mockResolvedValue({ rows: [], totalRows: 0 });
47
- });
48
- afterEach(() => {
49
- jest.resetAllMocks();
50
- });
51
- it('caches table schema', async () => {
52
- await connection.fetchSchemaForTables({ 'test1': 'table1' }, {});
53
- expect(runRawSQL).toBeCalledTimes(1);
54
- await new Promise(resolve => setTimeout(resolve));
55
- await connection.fetchSchemaForTables({ 'test1': 'table1' }, {});
56
- expect(runRawSQL).toBeCalledTimes(1);
57
- });
58
- it('refreshes table schema', async () => {
59
- await connection.fetchSchemaForTables({ 'test2': 'table2' }, {});
60
- expect(runRawSQL).toBeCalledTimes(1);
61
- await new Promise(resolve => setTimeout(resolve));
62
- await connection.fetchSchemaForTables({ 'test2': 'table2' }, { refreshTimestamp: Date.now() + 10 });
63
- expect(runRawSQL).toBeCalledTimes(2);
64
- });
65
- it('caches sql schema', async () => {
66
- await connection.fetchSchemaForSQLBlock(SQL_BLOCK_1, {});
67
- expect(runRawSQL).toBeCalledTimes(1);
68
- await new Promise(resolve => setTimeout(resolve));
69
- await connection.fetchSchemaForSQLBlock(SQL_BLOCK_1, {});
70
- expect(runRawSQL).toBeCalledTimes(1);
44
+ describe('schema', () => {
45
+ let runRawSQL;
46
+ beforeEach(async () => {
47
+ runRawSQL = jest
48
+ .spyOn(duckdb_common_1.DuckDBCommon.prototype, 'runRawSQL')
49
+ .mockResolvedValue({ rows: [], totalRows: 0 });
50
+ });
51
+ afterEach(() => {
52
+ jest.resetAllMocks();
53
+ runRawSQL.mockRestore();
54
+ });
55
+ it('caches table schema', async () => {
56
+ await connection.fetchSchemaForTables({ 'test1': 'table1' }, {});
57
+ expect(runRawSQL).toBeCalledTimes(1);
58
+ await new Promise(resolve => setTimeout(resolve));
59
+ await connection.fetchSchemaForTables({ 'test1': 'table1' }, {});
60
+ expect(runRawSQL).toBeCalledTimes(1);
61
+ });
62
+ it('refreshes table schema', async () => {
63
+ await connection.fetchSchemaForTables({ 'test2': 'table2' }, {});
64
+ expect(runRawSQL).toBeCalledTimes(1);
65
+ await new Promise(resolve => setTimeout(resolve));
66
+ await connection.fetchSchemaForTables({ 'test2': 'table2' }, { refreshTimestamp: Date.now() + 10 });
67
+ expect(runRawSQL).toBeCalledTimes(2);
68
+ });
69
+ it('caches sql schema', async () => {
70
+ await connection.fetchSchemaForSQLBlock(SQL_BLOCK_1, {});
71
+ expect(runRawSQL).toBeCalledTimes(1);
72
+ await new Promise(resolve => setTimeout(resolve));
73
+ await connection.fetchSchemaForSQLBlock(SQL_BLOCK_1, {});
74
+ expect(runRawSQL).toBeCalledTimes(1);
75
+ });
76
+ it('refreshes sql schema', async () => {
77
+ await connection.fetchSchemaForSQLBlock(SQL_BLOCK_2, {});
78
+ expect(runRawSQL).toBeCalledTimes(1);
79
+ await new Promise(resolve => setTimeout(resolve));
80
+ await connection.fetchSchemaForSQLBlock(SQL_BLOCK_2, {
81
+ refreshTimestamp: Date.now() + 10,
82
+ });
83
+ expect(runRawSQL).toBeCalledTimes(2);
84
+ });
71
85
  });
72
- it('refreshes sql schema', async () => {
73
- await connection.fetchSchemaForSQLBlock(SQL_BLOCK_2, {});
74
- expect(runRawSQL).toBeCalledTimes(1);
75
- await new Promise(resolve => setTimeout(resolve));
76
- await connection.fetchSchemaForSQLBlock(SQL_BLOCK_2, {
77
- refreshTimestamp: Date.now() + 10,
86
+ describe('multiple connections', () => {
87
+ it('can open multiple connections with different settings', async () => {
88
+ const connection1 = new duckdb_connection_1.DuckDBConnection('duckdb1');
89
+ const connection2 = new duckdb_connection_1.DuckDBConnection('duckdb2');
90
+ await connection1.runRawSQL("SET FILE_SEARCH_PATH='/home/user1'");
91
+ await connection2.runRawSQL("SET FILE_SEARCH_PATH='/home/user2'");
92
+ const val1 = await connection1.runSQL("SELECT current_setting('FILE_SEARCH_PATH') AS val");
93
+ const val2 = await connection2.runSQL("SELECT current_setting('FILE_SEARCH_PATH') AS val");
94
+ expect(Object.keys(duckdb_connection_1.DuckDBConnection.activeDBs).length).toEqual(1);
95
+ expect(duckdb_connection_1.DuckDBConnection.activeDBs[':memory:'].connections.length).toEqual(3);
96
+ expect(val1).toEqual({ rows: [{ val: '/home/user1' }], totalRows: 1 });
97
+ expect(val2).toEqual({ rows: [{ val: '/home/user2' }], totalRows: 1 });
98
+ await connection1.close();
99
+ await connection2.close();
78
100
  });
79
- expect(runRawSQL).toBeCalledTimes(2);
80
101
  });
81
102
  });
82
103
  const SQL_BLOCK_1 = {
@@ -1,14 +1,18 @@
1
1
  import { DuckDBCommon, QueryOptionsReader } from './duckdb_common';
2
2
  import { Connection, Database, TableData } from 'duckdb';
3
3
  import { QueryDataRow, RunSQLOptions } from '@malloydata/malloy';
4
+ interface ActiveDB {
5
+ database: Database;
6
+ connections: Connection[];
7
+ }
4
8
  export declare class DuckDBConnection extends DuckDBCommon {
5
9
  readonly name: string;
6
10
  private databasePath;
7
11
  private workingDirectory;
8
12
  connecting: Promise<void>;
9
13
  protected connection: Connection | null;
10
- protected database: Database | null;
11
14
  protected isSetup: Promise<void> | undefined;
15
+ static activeDBs: Record<string, ActiveDB>;
12
16
  constructor(name: string, databasePath?: string, workingDirectory?: string, queryOptions?: QueryOptionsReader);
13
17
  private init;
14
18
  loadExtension(ext: string): Promise<void>;
@@ -21,3 +25,4 @@ export declare class DuckDBConnection extends DuckDBCommon {
21
25
  createHash(sqlCommand: string): Promise<string>;
22
26
  close(): Promise<void>;
23
27
  }
28
+ export {};
@@ -36,19 +36,30 @@ class DuckDBConnection extends duckdb_common_1.DuckDBCommon {
36
36
  this.databasePath = databasePath;
37
37
  this.workingDirectory = workingDirectory;
38
38
  this.connection = null;
39
- this.database = null;
40
39
  this.connecting = this.init();
41
40
  }
42
41
  async init() {
43
42
  return new Promise((resolve, reject) => {
44
- this.database = new duckdb_1.Database(this.databasePath, duckdb_1.OPEN_READWRITE, // databasePath === ":memory:" ? OPEN_READWRITE : OPEN_READONLY,
45
- // databasePath === ":memory:" ? OPEN_READWRITE : OPEN_READONLY,
46
- err => {
47
- if (err) {
48
- reject(err);
49
- }
50
- });
51
- this.connection = this.database.connect();
43
+ let activeDB;
44
+ if (this.databasePath in DuckDBConnection.activeDBs) {
45
+ activeDB = DuckDBConnection.activeDBs[this.databasePath];
46
+ }
47
+ else {
48
+ const database = new duckdb_1.Database(this.databasePath, duckdb_1.OPEN_READWRITE, // databasePath === ":memory:" ? OPEN_READWRITE : OPEN_READONLY,
49
+ // databasePath === ":memory:" ? OPEN_READWRITE : OPEN_READONLY,
50
+ err => {
51
+ if (err) {
52
+ reject(err);
53
+ }
54
+ });
55
+ activeDB = {
56
+ database,
57
+ connections: [],
58
+ };
59
+ DuckDBConnection.activeDBs[this.databasePath] = activeDB;
60
+ }
61
+ this.connection = activeDB.database.connect();
62
+ activeDB.connections.push(this.connection);
52
63
  resolve();
53
64
  });
54
65
  }
@@ -137,14 +148,16 @@ class DuckDBConnection extends duckdb_common_1.DuckDBCommon {
137
148
  return crypto_1.default.createHash('md5').update(sqlCommand).digest('hex');
138
149
  }
139
150
  async close() {
140
- if (this.connection) {
141
- this.connection = null;
142
- }
143
- if (this.database) {
144
- this.database.close();
145
- this.database = null;
151
+ const activeDB = DuckDBConnection.activeDBs[this.databasePath];
152
+ if (activeDB) {
153
+ activeDB.connections = activeDB.connections.filter(connection => connection !== this.connection);
154
+ if (activeDB.connections.length === 0) {
155
+ activeDB.database.close();
156
+ delete DuckDBConnection.activeDBs[this.databasePath];
157
+ }
146
158
  }
147
159
  }
148
160
  }
149
161
  exports.DuckDBConnection = DuckDBConnection;
162
+ DuckDBConnection.activeDBs = {};
150
163
  //# sourceMappingURL=duckdb_connection.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@malloydata/db-duckdb",
3
- "version": "0.0.120-dev240125201336",
3
+ "version": "0.0.120-dev240131220338",
4
4
  "license": "MIT",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -41,7 +41,7 @@
41
41
  },
42
42
  "dependencies": {
43
43
  "@malloydata/duckdb-wasm": "0.0.6",
44
- "@malloydata/malloy": "^0.0.120-dev240125201336",
44
+ "@malloydata/malloy": "^0.0.120-dev240131220338",
45
45
  "apache-arrow": "^13.0.0",
46
46
  "duckdb": "0.9.2",
47
47
  "web-worker": "^1.2.0"