@lightdash/warehouses 2.228.1 → 2.229.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.tsbuildinfo.build +1 -0
- package/package.json +4 -4
- package/dist/.tsbuildinfo +0 -1
- package/dist/ssh/sshTunnelFailure.test.d.ts +0 -2
- package/dist/ssh/sshTunnelFailure.test.d.ts.map +0 -1
- package/dist/ssh/sshTunnelFailure.test.js +0 -90
- package/dist/validateWarehouseColumnReferences.test.d.ts +0 -2
- package/dist/validateWarehouseColumnReferences.test.d.ts.map +0 -1
- package/dist/validateWarehouseColumnReferences.test.js +0 -168
- package/dist/warehouseClients/AthenaWarehouseClient.test.d.ts +0 -2
- package/dist/warehouseClients/AthenaWarehouseClient.test.d.ts.map +0 -1
- package/dist/warehouseClients/AthenaWarehouseClient.test.js +0 -261
- package/dist/warehouseClients/BigqueryWarehouseClient.mock.d.ts +0 -83
- package/dist/warehouseClients/BigqueryWarehouseClient.mock.d.ts.map +0 -1
- package/dist/warehouseClients/BigqueryWarehouseClient.mock.js +0 -130
- package/dist/warehouseClients/BigqueryWarehouseClient.test.d.ts +0 -2
- package/dist/warehouseClients/BigqueryWarehouseClient.test.d.ts.map +0 -1
- package/dist/warehouseClients/BigqueryWarehouseClient.test.js +0 -493
- package/dist/warehouseClients/ClickhouseWarehouseClient.test.d.ts +0 -2
- package/dist/warehouseClients/ClickhouseWarehouseClient.test.d.ts.map +0 -1
- package/dist/warehouseClients/ClickhouseWarehouseClient.test.js +0 -115
- package/dist/warehouseClients/DatabricksWarehouseClient.mock.d.ts +0 -17
- package/dist/warehouseClients/DatabricksWarehouseClient.mock.d.ts.map +0 -1
- package/dist/warehouseClients/DatabricksWarehouseClient.mock.js +0 -111
- package/dist/warehouseClients/DatabricksWarehouseClient.test.d.ts +0 -2
- package/dist/warehouseClients/DatabricksWarehouseClient.test.d.ts.map +0 -1
- package/dist/warehouseClients/DatabricksWarehouseClient.test.js +0 -592
- package/dist/warehouseClients/DatabricksWarehouseStartupRetry.test.d.ts +0 -2
- package/dist/warehouseClients/DatabricksWarehouseStartupRetry.test.d.ts.map +0 -1
- package/dist/warehouseClients/DatabricksWarehouseStartupRetry.test.js +0 -129
- package/dist/warehouseClients/DuckdbWarehouseClient.embedded.test.d.ts +0 -2
- package/dist/warehouseClients/DuckdbWarehouseClient.embedded.test.d.ts.map +0 -1
- package/dist/warehouseClients/DuckdbWarehouseClient.embedded.test.js +0 -92
- package/dist/warehouseClients/DuckdbWarehouseClient.test.d.ts +0 -2
- package/dist/warehouseClients/DuckdbWarehouseClient.test.d.ts.map +0 -1
- package/dist/warehouseClients/DuckdbWarehouseClient.test.js +0 -1749
- package/dist/warehouseClients/MotherduckErrorClassifier.test.d.ts +0 -2
- package/dist/warehouseClients/MotherduckErrorClassifier.test.d.ts.map +0 -1
- package/dist/warehouseClients/MotherduckErrorClassifier.test.js +0 -31
- package/dist/warehouseClients/MotherduckInstanceCache.catalog.test.d.ts +0 -2
- package/dist/warehouseClients/MotherduckInstanceCache.catalog.test.d.ts.map +0 -1
- package/dist/warehouseClients/MotherduckInstanceCache.catalog.test.js +0 -52
- package/dist/warehouseClients/MotherduckInstanceCache.integration.test.d.ts +0 -2
- package/dist/warehouseClients/MotherduckInstanceCache.integration.test.d.ts.map +0 -1
- package/dist/warehouseClients/MotherduckInstanceCache.integration.test.js +0 -172
- package/dist/warehouseClients/MotherduckInstanceCache.security.test.d.ts +0 -2
- package/dist/warehouseClients/MotherduckInstanceCache.security.test.d.ts.map +0 -1
- package/dist/warehouseClients/MotherduckInstanceCache.security.test.js +0 -192
- package/dist/warehouseClients/MotherduckInstanceCache.test.d.ts +0 -2
- package/dist/warehouseClients/MotherduckInstanceCache.test.d.ts.map +0 -1
- package/dist/warehouseClients/MotherduckInstanceCache.test.js +0 -302
- package/dist/warehouseClients/PostgresWarehouseClient.mock.d.ts +0 -8
- package/dist/warehouseClients/PostgresWarehouseClient.mock.d.ts.map +0 -1
- package/dist/warehouseClients/PostgresWarehouseClient.mock.js +0 -66
- package/dist/warehouseClients/PostgresWarehouseClient.test.d.ts +0 -2
- package/dist/warehouseClients/PostgresWarehouseClient.test.d.ts.map +0 -1
- package/dist/warehouseClients/PostgresWarehouseClient.test.js +0 -371
- package/dist/warehouseClients/RedshiftWarehouseClient.test.d.ts +0 -2
- package/dist/warehouseClients/RedshiftWarehouseClient.test.d.ts.map +0 -1
- package/dist/warehouseClients/RedshiftWarehouseClient.test.js +0 -126
- package/dist/warehouseClients/SnowflakeWarehouseClient.mock.d.ts +0 -41
- package/dist/warehouseClients/SnowflakeWarehouseClient.mock.d.ts.map +0 -1
- package/dist/warehouseClients/SnowflakeWarehouseClient.mock.js +0 -184
- package/dist/warehouseClients/SnowflakeWarehouseClient.test.d.ts +0 -2
- package/dist/warehouseClients/SnowflakeWarehouseClient.test.d.ts.map +0 -1
- package/dist/warehouseClients/SnowflakeWarehouseClient.test.js +0 -777
- package/dist/warehouseClients/TrinoWarehouseClient.mock.d.ts +0 -23
- package/dist/warehouseClients/TrinoWarehouseClient.mock.d.ts.map +0 -1
- package/dist/warehouseClients/TrinoWarehouseClient.mock.js +0 -102
- package/dist/warehouseClients/TrinoWarehouseClient.test.d.ts +0 -2
- package/dist/warehouseClients/TrinoWarehouseClient.test.d.ts.map +0 -1
- package/dist/warehouseClients/TrinoWarehouseClient.test.js +0 -230
- package/dist/warehouseClients/WarehouseClient.mock.d.ts +0 -13
- package/dist/warehouseClients/WarehouseClient.mock.d.ts.map +0 -1
- package/dist/warehouseClients/WarehouseClient.mock.js +0 -51
- package/dist/warehouseClients/numericKinds.test.d.ts +0 -2
- package/dist/warehouseClients/numericKinds.test.d.ts.map +0 -1
- package/dist/warehouseClients/numericKinds.test.js +0 -168
- package/dist/warehouseClients/pgProtocolGuard.integration.test.d.ts +0 -2
- package/dist/warehouseClients/pgProtocolGuard.integration.test.d.ts.map +0 -1
- package/dist/warehouseClients/pgProtocolGuard.integration.test.js +0 -93
- package/dist/warehouseClients/pgProtocolGuard.test.d.ts +0 -2
- package/dist/warehouseClients/pgProtocolGuard.test.d.ts.map +0 -1
- package/dist/warehouseClients/pgProtocolGuard.test.js +0 -48
- package/dist/warehouseClients/redshiftIamCredentials.test.d.ts +0 -2
- package/dist/warehouseClients/redshiftIamCredentials.test.d.ts.map +0 -1
- package/dist/warehouseClients/redshiftIamCredentials.test.js +0 -210
- package/dist/warehouseClients/timestampDomain.test.d.ts +0 -2
- package/dist/warehouseClients/timestampDomain.test.d.ts.map +0 -1
- package/dist/warehouseClients/timestampDomain.test.js +0 -101
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"MotherduckErrorClassifier.test.d.ts","sourceRoot":"","sources":["../../src/warehouseClients/MotherduckErrorClassifier.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const MotherduckErrorClassifier_1 = require("./MotherduckErrorClassifier");
|
|
4
|
-
describe('classifyMotherduckError', () => {
|
|
5
|
-
it.each([
|
|
6
|
-
'Connection Error: Connection has already been closed',
|
|
7
|
-
'Invalid Input Error: Cannot execute statement of closed connection',
|
|
8
|
-
'Catalog Error: Database analytics was detached',
|
|
9
|
-
'Invalid Input Error: database was invalidated because the instance was closed',
|
|
10
|
-
])('classifies stale DuckDB handles: %s', (message) => {
|
|
11
|
-
expect((0, MotherduckErrorClassifier_1.classifyMotherduckError)(new Error(message))).toBe('stale');
|
|
12
|
-
});
|
|
13
|
-
it.each([
|
|
14
|
-
'MotherDuck Authentication failed: token rejected',
|
|
15
|
-
'Invalid Input Error: MD Authentication Error: Invalid token',
|
|
16
|
-
'Your request is not authenticated. Please check your MotherDuck token...',
|
|
17
|
-
'HTTP Error: Unauthorized',
|
|
18
|
-
"Could not connect to MotherDuck... (PERMISSION_DENIED, RPC 'CREATE_SLT')",
|
|
19
|
-
'RPC failed with PeRmIsSiOn_DeNiEd',
|
|
20
|
-
'COULD NOT CONNECT TO MOTHERDUCK',
|
|
21
|
-
])('classifies MotherDuck authentication failures: %s', (message) => {
|
|
22
|
-
expect((0, MotherduckErrorClassifier_1.classifyMotherduckError)(new Error(message))).toBe('auth');
|
|
23
|
-
});
|
|
24
|
-
it.each([
|
|
25
|
-
'Binder Error: Referenced column missing_column not found',
|
|
26
|
-
'Catalog Error: Table with name missing_table does not exist',
|
|
27
|
-
'Invalid Input Error: configuration has been locked',
|
|
28
|
-
])('leaves SQL and configuration errors untouched: %s', (message) => {
|
|
29
|
-
expect((0, MotherduckErrorClassifier_1.classifyMotherduckError)(new Error(message))).toBe('other');
|
|
30
|
-
});
|
|
31
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"MotherduckInstanceCache.catalog.test.d.ts","sourceRoot":"","sources":["../../src/warehouseClients/MotherduckInstanceCache.catalog.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const MotherduckInstanceCache_1 = require("./MotherduckInstanceCache");
|
|
4
|
-
describe('MotherduckInstanceCache catalog freshness regression', () => {
|
|
5
|
-
beforeEach(() => {
|
|
6
|
-
(0, MotherduckInstanceCache_1.resetForTesting)();
|
|
7
|
-
(0, MotherduckInstanceCache_1.configure)({
|
|
8
|
-
idleTtlMs: 60000,
|
|
9
|
-
maxAgeMs: 60000,
|
|
10
|
-
maxEntries: 1,
|
|
11
|
-
});
|
|
12
|
-
});
|
|
13
|
-
afterEach(async () => {
|
|
14
|
-
await (0, MotherduckInstanceCache_1.closeAll)('shutdown');
|
|
15
|
-
});
|
|
16
|
-
it('serves a new table and column through both an already-warm connection and a fresh connection on the held instance', async () => {
|
|
17
|
-
let entryId = '';
|
|
18
|
-
await (0, MotherduckInstanceCache_1.withInstance)(':memory:', {}, async (instance, acquiredEntryId) => {
|
|
19
|
-
entryId = acquiredEntryId;
|
|
20
|
-
const warmConnection = await instance.connect();
|
|
21
|
-
const ddlConnection = await instance.connect();
|
|
22
|
-
try {
|
|
23
|
-
await warmConnection.run('SELECT * FROM information_schema.columns');
|
|
24
|
-
await ddlConnection.run('CREATE TABLE catalog_freshness (initial_column INTEGER)');
|
|
25
|
-
await ddlConnection.run('ALTER TABLE catalog_freshness ADD COLUMN added_column VARCHAR');
|
|
26
|
-
const warmResult = await warmConnection.run("SELECT column_name FROM information_schema.columns WHERE table_name = 'catalog_freshness' ORDER BY ordinal_position");
|
|
27
|
-
await expect(warmResult.getRowObjects()).resolves.toEqual([
|
|
28
|
-
{ column_name: 'initial_column' },
|
|
29
|
-
{ column_name: 'added_column' },
|
|
30
|
-
]);
|
|
31
|
-
}
|
|
32
|
-
finally {
|
|
33
|
-
ddlConnection.closeSync();
|
|
34
|
-
warmConnection.closeSync();
|
|
35
|
-
}
|
|
36
|
-
});
|
|
37
|
-
await (0, MotherduckInstanceCache_1.withInstance)(':memory:', {}, async (instance, acquiredEntryId) => {
|
|
38
|
-
expect(acquiredEntryId).toBe(entryId);
|
|
39
|
-
const freshConnection = await instance.connect();
|
|
40
|
-
try {
|
|
41
|
-
const freshResult = await freshConnection.run("SELECT column_name FROM information_schema.columns WHERE table_name = 'catalog_freshness' ORDER BY ordinal_position");
|
|
42
|
-
await expect(freshResult.getRowObjects()).resolves.toEqual([
|
|
43
|
-
{ column_name: 'initial_column' },
|
|
44
|
-
{ column_name: 'added_column' },
|
|
45
|
-
]);
|
|
46
|
-
}
|
|
47
|
-
finally {
|
|
48
|
-
freshConnection.closeSync();
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
});
|
|
52
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"MotherduckInstanceCache.integration.test.d.ts","sourceRoot":"","sources":["../../src/warehouseClients/MotherduckInstanceCache.integration.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
// Run: set -a; source .motherduck.env; set +a; pnpm -F warehouses exec vitest run src/warehouseClients/MotherduckInstanceCache.integration.test.ts
|
|
3
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
-
if (k2 === undefined) k2 = k;
|
|
5
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
-
}
|
|
9
|
-
Object.defineProperty(o, k2, desc);
|
|
10
|
-
}) : (function(o, m, k, k2) {
|
|
11
|
-
if (k2 === undefined) k2 = k;
|
|
12
|
-
o[k2] = m[k];
|
|
13
|
-
}));
|
|
14
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
-
}) : function(o, v) {
|
|
17
|
-
o["default"] = v;
|
|
18
|
-
});
|
|
19
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
-
var ownKeys = function(o) {
|
|
21
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
-
var ar = [];
|
|
23
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
-
return ar;
|
|
25
|
-
};
|
|
26
|
-
return ownKeys(o);
|
|
27
|
-
};
|
|
28
|
-
return function (mod) {
|
|
29
|
-
if (mod && mod.__esModule) return mod;
|
|
30
|
-
var result = {};
|
|
31
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
-
__setModuleDefault(result, mod);
|
|
33
|
-
return result;
|
|
34
|
-
};
|
|
35
|
-
})();
|
|
36
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
-
const node_api_1 = require("@duckdb/node-api");
|
|
38
|
-
const crypto_1 = require("crypto");
|
|
39
|
-
const MotherduckInstanceCache = __importStar(require("./MotherduckInstanceCache"));
|
|
40
|
-
const motherduckToken = process.env.MOTHERDUCK_TOKEN?.trim();
|
|
41
|
-
const database = 'jaffle_shop';
|
|
42
|
-
const query = 'SELECT * FROM jaffle_shop.jaffle.orders LIMIT 10';
|
|
43
|
-
const tablePrefix = 'ld_spk822_catalog_freshness_';
|
|
44
|
-
const ownedTablePattern = /^ld_spk822_catalog_freshness_[0-9a-f]{8}_[0-9a-f]{4}_[0-9a-f]{4}_[0-9a-f]{4}_[0-9a-f]{12}$/;
|
|
45
|
-
const getConnectionString = () => {
|
|
46
|
-
if (!motherduckToken) {
|
|
47
|
-
throw new Error('MOTHERDUCK_TOKEN is required');
|
|
48
|
-
}
|
|
49
|
-
return `md:${database}?${new URLSearchParams({
|
|
50
|
-
motherduck_token: motherduckToken,
|
|
51
|
-
saas_mode: 'true',
|
|
52
|
-
}).toString()}`;
|
|
53
|
-
};
|
|
54
|
-
const closeConnection = (connection) => {
|
|
55
|
-
connection.closeSync?.();
|
|
56
|
-
connection.disconnectSync?.();
|
|
57
|
-
};
|
|
58
|
-
const consumeQuery = async (connection, sql) => {
|
|
59
|
-
const result = await connection.stream(sql);
|
|
60
|
-
const rows = [];
|
|
61
|
-
for await (const chunk of result.yieldRowObjectJson()) {
|
|
62
|
-
rows.push(...chunk);
|
|
63
|
-
}
|
|
64
|
-
return rows;
|
|
65
|
-
};
|
|
66
|
-
const withFreshInstance = async (callback) => {
|
|
67
|
-
const instance = await node_api_1.DuckDBInstance.create(getConnectionString());
|
|
68
|
-
const connection = await instance.connect();
|
|
69
|
-
try {
|
|
70
|
-
return await callback(connection);
|
|
71
|
-
}
|
|
72
|
-
finally {
|
|
73
|
-
closeConnection(connection);
|
|
74
|
-
instance.closeSync?.();
|
|
75
|
-
}
|
|
76
|
-
};
|
|
77
|
-
const measureFreshInstance = async () => {
|
|
78
|
-
const startedAt = performance.now();
|
|
79
|
-
await withFreshInstance(async (connection) => consumeQuery(connection, query));
|
|
80
|
-
return performance.now() - startedAt;
|
|
81
|
-
};
|
|
82
|
-
const measureHeldInstance = async (instance) => {
|
|
83
|
-
const startedAt = performance.now();
|
|
84
|
-
const connection = await instance.connect();
|
|
85
|
-
try {
|
|
86
|
-
await consumeQuery(connection, query);
|
|
87
|
-
return performance.now() - startedAt;
|
|
88
|
-
}
|
|
89
|
-
finally {
|
|
90
|
-
closeConnection(connection);
|
|
91
|
-
}
|
|
92
|
-
};
|
|
93
|
-
const median = (values) => {
|
|
94
|
-
const sorted = [...values].sort((left, right) => left - right);
|
|
95
|
-
const middle = sorted[Math.floor(sorted.length / 2)];
|
|
96
|
-
if (middle === undefined) {
|
|
97
|
-
throw new Error('Cannot calculate the median of an empty set');
|
|
98
|
-
}
|
|
99
|
-
return middle;
|
|
100
|
-
};
|
|
101
|
-
describe.skipIf(!motherduckToken)('MotherDuck instance cache live integration', () => {
|
|
102
|
-
let createdTableName;
|
|
103
|
-
beforeEach(() => {
|
|
104
|
-
MotherduckInstanceCache.resetForTesting();
|
|
105
|
-
MotherduckInstanceCache.configure({
|
|
106
|
-
idleTtlMs: 60000,
|
|
107
|
-
maxAgeMs: 60000,
|
|
108
|
-
maxEntries: 1,
|
|
109
|
-
});
|
|
110
|
-
});
|
|
111
|
-
afterEach(async () => {
|
|
112
|
-
await MotherduckInstanceCache.closeAll('shutdown');
|
|
113
|
-
if (createdTableName) {
|
|
114
|
-
if (!ownedTablePattern.test(createdTableName)) {
|
|
115
|
-
throw new Error('Refusing to clean up an unowned table');
|
|
116
|
-
}
|
|
117
|
-
await withFreshInstance(async (connection) => {
|
|
118
|
-
await connection.run(`DROP TABLE main."${createdTableName}"`);
|
|
119
|
-
});
|
|
120
|
-
createdTableName = undefined;
|
|
121
|
-
}
|
|
122
|
-
});
|
|
123
|
-
it('keeps a held instance fresh for a table and column created after warmup', async () => {
|
|
124
|
-
const tableName = `${tablePrefix}${(0, crypto_1.randomUUID)().replaceAll('-', '_')}`;
|
|
125
|
-
await MotherduckInstanceCache.withInstance(getConnectionString(), {}, async (instance) => {
|
|
126
|
-
const warmConnection = await instance.connect();
|
|
127
|
-
try {
|
|
128
|
-
await consumeQuery(warmConnection, 'SELECT * FROM information_schema.columns LIMIT 1');
|
|
129
|
-
await withFreshInstance(async (connection) => {
|
|
130
|
-
await connection.run(`CREATE TABLE main."${tableName}" (initial_column INTEGER)`);
|
|
131
|
-
createdTableName = tableName;
|
|
132
|
-
await connection.run(`ALTER TABLE main."${tableName}" ADD COLUMN added_column VARCHAR`);
|
|
133
|
-
});
|
|
134
|
-
const columnsSql = `SELECT column_name FROM information_schema.columns WHERE table_catalog = '${database}' AND table_schema = 'main' AND table_name = '${tableName}' ORDER BY ordinal_position`;
|
|
135
|
-
const warmRows = await consumeQuery(warmConnection, columnsSql);
|
|
136
|
-
const freshConnection = await instance.connect();
|
|
137
|
-
try {
|
|
138
|
-
const freshRows = await consumeQuery(freshConnection, columnsSql);
|
|
139
|
-
expect(warmRows).toEqual([
|
|
140
|
-
{ column_name: 'initial_column' },
|
|
141
|
-
{ column_name: 'added_column' },
|
|
142
|
-
]);
|
|
143
|
-
expect(freshRows).toEqual(warmRows);
|
|
144
|
-
}
|
|
145
|
-
finally {
|
|
146
|
-
closeConnection(freshConnection);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
finally {
|
|
150
|
-
closeConnection(warmConnection);
|
|
151
|
-
}
|
|
152
|
-
});
|
|
153
|
-
}, 60000);
|
|
154
|
-
it('keeps held-instance query cost several times below fresh-instance cost', async () => {
|
|
155
|
-
const freshInstanceMs = [];
|
|
156
|
-
const heldInstanceMs = [];
|
|
157
|
-
await MotherduckInstanceCache.withInstance(getConnectionString(), {}, async (instance) => {
|
|
158
|
-
await measureHeldInstance(instance);
|
|
159
|
-
const collectMeasurements = async (remaining) => {
|
|
160
|
-
if (remaining === 0) {
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
freshInstanceMs.push(await measureFreshInstance());
|
|
164
|
-
heldInstanceMs.push(await measureHeldInstance(instance));
|
|
165
|
-
await collectMeasurements(remaining - 1);
|
|
166
|
-
};
|
|
167
|
-
await collectMeasurements(3);
|
|
168
|
-
});
|
|
169
|
-
// A 3x threshold catches loss of instance reuse while leaving headroom for observed cross-run variance.
|
|
170
|
-
expect(median(freshInstanceMs) / median(heldInstanceMs)).toBeGreaterThanOrEqual(3);
|
|
171
|
-
}, 60000);
|
|
172
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"MotherduckInstanceCache.security.test.d.ts","sourceRoot":"","sources":["../../src/warehouseClients/MotherduckInstanceCache.security.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,192 +0,0 @@
|
|
|
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 () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
const common_1 = require("@lightdash/common");
|
|
37
|
-
const DuckdbWarehouseClient_1 = require("./DuckdbWarehouseClient");
|
|
38
|
-
const MotherduckInstanceCache = __importStar(require("./MotherduckInstanceCache"));
|
|
39
|
-
const createInstanceMock = vi.fn();
|
|
40
|
-
vi.mock('@duckdb/node-api', () => ({
|
|
41
|
-
DuckDBTypeId: { INTEGER: 4, VARCHAR: 17 },
|
|
42
|
-
DuckDBInstance: {
|
|
43
|
-
create: (...args) => createInstanceMock(...args),
|
|
44
|
-
},
|
|
45
|
-
version: () => 'v1.5.2',
|
|
46
|
-
}));
|
|
47
|
-
const getMockStreamResult = (marker) => ({
|
|
48
|
-
columnCount: 1,
|
|
49
|
-
columnNames: () => ['marker'],
|
|
50
|
-
columnType: () => ({ typeId: 17 }),
|
|
51
|
-
yieldRowObjectJson: async function* yieldRows() {
|
|
52
|
-
yield [{ marker }];
|
|
53
|
-
},
|
|
54
|
-
});
|
|
55
|
-
const createMockInstance = (streamMock) => ({
|
|
56
|
-
connect: async () => ({
|
|
57
|
-
run: vi.fn(),
|
|
58
|
-
stream: streamMock,
|
|
59
|
-
extractStatements: vi.fn(async () => ({
|
|
60
|
-
count: 1,
|
|
61
|
-
prepare: async () => ({
|
|
62
|
-
statementType: 1,
|
|
63
|
-
destroySync: vi.fn(),
|
|
64
|
-
}),
|
|
65
|
-
})),
|
|
66
|
-
interrupt: vi.fn(),
|
|
67
|
-
closeSync: vi.fn(),
|
|
68
|
-
disconnectSync: vi.fn(),
|
|
69
|
-
}),
|
|
70
|
-
closeSync: vi.fn(),
|
|
71
|
-
});
|
|
72
|
-
const motherduckCredentials = (database, token) => ({
|
|
73
|
-
type: common_1.WarehouseTypes.DUCKDB,
|
|
74
|
-
connectionType: common_1.DuckdbConnectionType.MOTHERDUCK,
|
|
75
|
-
database,
|
|
76
|
-
schema: 'main',
|
|
77
|
-
token,
|
|
78
|
-
});
|
|
79
|
-
const cachedOptions = (projectUuid) => ({
|
|
80
|
-
enableInstanceCache: true,
|
|
81
|
-
projectUuid,
|
|
82
|
-
});
|
|
83
|
-
const successStream = (marker) => vi.fn(async () => getMockStreamResult(marker));
|
|
84
|
-
describe('MotherDuck instance cache security boundaries', () => {
|
|
85
|
-
beforeEach(() => {
|
|
86
|
-
vi.useFakeTimers();
|
|
87
|
-
vi.clearAllMocks();
|
|
88
|
-
MotherduckInstanceCache.resetForTesting();
|
|
89
|
-
MotherduckInstanceCache.configure({
|
|
90
|
-
idleTtlMs: 60000,
|
|
91
|
-
maxAgeMs: 3600000,
|
|
92
|
-
maxEntries: 64,
|
|
93
|
-
});
|
|
94
|
-
DuckdbWarehouseClient_1.DuckdbWarehouseClient.resetSharedDuckdbStateForTesting();
|
|
95
|
-
});
|
|
96
|
-
afterEach(async () => {
|
|
97
|
-
await MotherduckInstanceCache.closeAll('shutdown');
|
|
98
|
-
vi.useRealTimers();
|
|
99
|
-
});
|
|
100
|
-
it('isolates concurrent clients with different tokens for one database', async () => {
|
|
101
|
-
const streamA = successStream('token-a-rows');
|
|
102
|
-
const streamB = successStream('token-b-rows');
|
|
103
|
-
const instanceA = createMockInstance(streamA);
|
|
104
|
-
const instanceB = createMockInstance(streamB);
|
|
105
|
-
createInstanceMock.mockImplementation(async (connectionString) => String(connectionString).includes('token-a')
|
|
106
|
-
? instanceA
|
|
107
|
-
: instanceB);
|
|
108
|
-
const clientA = new DuckdbWarehouseClient_1.DuckdbWarehouseClient(motherduckCredentials('analytics', 'token-a'), cachedOptions('project-a'));
|
|
109
|
-
const clientB = new DuckdbWarehouseClient_1.DuckdbWarehouseClient(motherduckCredentials('analytics', 'token-b'), cachedOptions('project-b'));
|
|
110
|
-
const [resultA, resultB] = await Promise.all([
|
|
111
|
-
clientA.runQuery('SELECT 1'),
|
|
112
|
-
clientB.runQuery('SELECT 1'),
|
|
113
|
-
]);
|
|
114
|
-
expect(createInstanceMock).toHaveBeenCalledTimes(2);
|
|
115
|
-
expect(resultA.rows).toEqual([{ marker: 'token-a-rows' }]);
|
|
116
|
-
expect(resultB.rows).toEqual([{ marker: 'token-b-rows' }]);
|
|
117
|
-
});
|
|
118
|
-
it('isolates clients with one token across different databases', async () => {
|
|
119
|
-
createInstanceMock.mockImplementation(async (connectionString) => createMockInstance(successStream(String(connectionString).startsWith('md:first')
|
|
120
|
-
? 'first-rows'
|
|
121
|
-
: 'second-rows')));
|
|
122
|
-
const firstClient = new DuckdbWarehouseClient_1.DuckdbWarehouseClient(motherduckCredentials('first', 'shared-token'), cachedOptions('project-a'));
|
|
123
|
-
const secondClient = new DuckdbWarehouseClient_1.DuckdbWarehouseClient(motherduckCredentials('second', 'shared-token'), cachedOptions('project-b'));
|
|
124
|
-
const [firstResult, secondResult] = await Promise.all([
|
|
125
|
-
firstClient.runQuery('SELECT 1'),
|
|
126
|
-
secondClient.runQuery('SELECT 1'),
|
|
127
|
-
]);
|
|
128
|
-
expect(createInstanceMock).toHaveBeenCalledTimes(2);
|
|
129
|
-
expect(firstResult.rows).toEqual([{ marker: 'first-rows' }]);
|
|
130
|
-
expect(secondResult.rows).toEqual([{ marker: 'second-rows' }]);
|
|
131
|
-
});
|
|
132
|
-
it('never maps adversarial database names onto another credential entry', async () => {
|
|
133
|
-
const victimConnectionString = `md:analytics?${new URLSearchParams({
|
|
134
|
-
motherduck_token: 'victim-token',
|
|
135
|
-
saas_mode: 'true',
|
|
136
|
-
}).toString()}`;
|
|
137
|
-
const adversarialNames = [
|
|
138
|
-
'analytics?motherduck_token=victim-token',
|
|
139
|
-
'analytics?motherduck_token=victim-token&saas_mode=true',
|
|
140
|
-
'analytics&saas_mode=false',
|
|
141
|
-
'analytics%3Fmotherduck_token%3Dvictim-token',
|
|
142
|
-
'analytics/../analytics',
|
|
143
|
-
'analytics#',
|
|
144
|
-
'analytics/',
|
|
145
|
-
'analytics ',
|
|
146
|
-
'ANALYTICS',
|
|
147
|
-
'café',
|
|
148
|
-
'café',
|
|
149
|
-
'analytics%00',
|
|
150
|
-
'',
|
|
151
|
-
'�',
|
|
152
|
-
];
|
|
153
|
-
createInstanceMock.mockImplementation(async () => createMockInstance(successStream('rows')));
|
|
154
|
-
await Promise.all(adversarialNames.map((database, index) => {
|
|
155
|
-
const client = new DuckdbWarehouseClient_1.DuckdbWarehouseClient(motherduckCredentials(database, 'attacker-token'), cachedOptions(`attacker-project-${index}`));
|
|
156
|
-
return client.runQuery('SELECT 1');
|
|
157
|
-
}));
|
|
158
|
-
const adversarialConnectionStrings = createInstanceMock.mock.calls.map(([connectionString]) => String(connectionString));
|
|
159
|
-
expect(adversarialConnectionStrings).not.toContain(victimConnectionString);
|
|
160
|
-
expect(new Set(adversarialConnectionStrings).size).toBe(adversarialNames.length);
|
|
161
|
-
const victimClient = new DuckdbWarehouseClient_1.DuckdbWarehouseClient(motherduckCredentials('analytics', 'victim-token'), cachedOptions('victim-project'));
|
|
162
|
-
await victimClient.runQuery('SELECT 1');
|
|
163
|
-
expect(createInstanceMock).toHaveBeenLastCalledWith(victimConnectionString);
|
|
164
|
-
});
|
|
165
|
-
it('does not close an in-flight instance when max age evicts it', async () => {
|
|
166
|
-
MotherduckInstanceCache.configure({
|
|
167
|
-
idleTtlMs: 60000,
|
|
168
|
-
maxAgeMs: 1000,
|
|
169
|
-
maxEntries: 8,
|
|
170
|
-
});
|
|
171
|
-
const instance = createMockInstance(successStream('rows'));
|
|
172
|
-
createInstanceMock
|
|
173
|
-
.mockResolvedValueOnce(instance)
|
|
174
|
-
.mockResolvedValue(createMockInstance(successStream('other')));
|
|
175
|
-
let releaseQuery = () => undefined;
|
|
176
|
-
const queryGate = new Promise((resolve) => {
|
|
177
|
-
releaseQuery = resolve;
|
|
178
|
-
});
|
|
179
|
-
let entryId = '';
|
|
180
|
-
const inFlight = MotherduckInstanceCache.withInstance('md:analytics?motherduck_token=token-a', {}, async (_instance, acquiredEntryId) => {
|
|
181
|
-
entryId = acquiredEntryId;
|
|
182
|
-
await queryGate;
|
|
183
|
-
});
|
|
184
|
-
await vi.waitFor(() => expect(entryId).not.toBe(''));
|
|
185
|
-
await vi.advanceTimersByTimeAsync(1500);
|
|
186
|
-
await MotherduckInstanceCache.withInstance('md:other?motherduck_token=token-b', {}, async () => undefined);
|
|
187
|
-
expect(instance.closeSync).not.toHaveBeenCalled();
|
|
188
|
-
releaseQuery();
|
|
189
|
-
await inFlight;
|
|
190
|
-
expect(instance.closeSync).toHaveBeenCalledOnce();
|
|
191
|
-
});
|
|
192
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"MotherduckInstanceCache.test.d.ts","sourceRoot":"","sources":["../../src/warehouseClients/MotherduckInstanceCache.test.ts"],"names":[],"mappings":""}
|