@effectuate/cubejs-mongosql-driver 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +23 -0
- package/README.md +579 -0
- package/dist/MongoSqlDriver.d.ts +238 -0
- package/dist/MongoSqlDriver.d.ts.map +1 -0
- package/dist/MongoSqlDriver.js +745 -0
- package/dist/MongoSqlDriver.js.map +1 -0
- package/dist/MongoSqlQuery.d.ts +353 -0
- package/dist/MongoSqlQuery.d.ts.map +1 -0
- package/dist/MongoSqlQuery.js +551 -0
- package/dist/MongoSqlQuery.js.map +1 -0
- package/dist/config.d.ts +29 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +298 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/native.d.ts +104 -0
- package/dist/native.d.ts.map +1 -0
- package/dist/native.js +192 -0
- package/dist/native.js.map +1 -0
- package/dist/types.d.ts +49 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +24 -0
- package/dist/types.js.map +1 -0
- package/index.d.ts +160 -0
- package/index.js +316 -0
- package/package.json +108 -0
|
@@ -0,0 +1,745 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* MongoSqlDriver — Cube data source driver for MongoDB via MongoSQL.
|
|
4
|
+
*
|
|
5
|
+
* See SPEC.md FR-1 (driver protocol), FR-7 (env-var configuration), and
|
|
6
|
+
* ARCHITECTURE.md §2.1.
|
|
7
|
+
*
|
|
8
|
+
* BaseDriver method-list audit (against
|
|
9
|
+
* `@cubejs-backend/base-driver/dist/src/BaseDriver.d.ts`):
|
|
10
|
+
*
|
|
11
|
+
* Required by DriverInterface (we override these):
|
|
12
|
+
* - query(sql, values?, options?) — REQUIRED, override (delegates + flattens)
|
|
13
|
+
* - testConnection() — REQUIRED, override (lazy native init)
|
|
14
|
+
* - release() — override (closes native client)
|
|
15
|
+
* - tablesSchema() — override (delegate to native)
|
|
16
|
+
* - downloadQueryResults(sql, ...) — override → memory shape; streamImport ignored
|
|
17
|
+
* (capabilities().streamImport=false; see below)
|
|
18
|
+
* - capabilities() — override → no export bucket / streaming source
|
|
19
|
+
* - nowTimestamp() — INHERIT (Date.now())
|
|
20
|
+
* - stream(table, values, opts) — NOT implemented; `streamImport: false`
|
|
21
|
+
* advertises this. Cube routes the
|
|
22
|
+
* pre-agg upload path through
|
|
23
|
+
* `downloadQueryResults` instead.
|
|
24
|
+
*
|
|
25
|
+
* Schema-introspection helpers (BaseDriver default reads information_schema —
|
|
26
|
+
* MongoSQL doesn't expose one, so we route every shape through the native
|
|
27
|
+
* cached `tablesSchema()` rendering):
|
|
28
|
+
* - tablesSchema() — override (full snapshot, bulk path)
|
|
29
|
+
* - getSchemas() — override (DB list from tablesSchema)
|
|
30
|
+
* - getTablesForSpecificSchemas(...) — override (filter tablesSchema by db)
|
|
31
|
+
* - getColumnsForSpecificTables(...) — override (filter tablesSchema by table)
|
|
32
|
+
* - capabilities().incrementalSchemaLoading=true — opts Cube into the
|
|
33
|
+
* granular three-method path; SQL
|
|
34
|
+
* information_schema queries are
|
|
35
|
+
* never issued against mongosql.
|
|
36
|
+
* - getTablesQuery(schema) — N/A — runs SQL against information_schema
|
|
37
|
+
* - informationSchemaQuery() — N/A (protected default)
|
|
38
|
+
* - tableColumnTypes(table) — INHERIT (uses query()); only needed for
|
|
39
|
+
* uploadTableWithIndexes path which we reject
|
|
40
|
+
*
|
|
41
|
+
* Mutation methods (read-only driver — explicit refusal beats silent no-op):
|
|
42
|
+
* - createSchemaIfNotExists(schemaName) — throw
|
|
43
|
+
* - dropTable(tableName, options?) — throw
|
|
44
|
+
* - uploadTable(...) — throw
|
|
45
|
+
* - uploadTableWithIndexes(...) — throw
|
|
46
|
+
* - createTable(...) — throw
|
|
47
|
+
* - createTableRaw(...) — throw
|
|
48
|
+
* - loadPreAggregationIntoTable(...) — INHERIT default (delegates to query())
|
|
49
|
+
* so partitioned/incremental pre-aggs work
|
|
50
|
+
*
|
|
51
|
+
* Bucket-export (no MongoDB equivalent — SPEC FR-6 says EXPORT_BUCKET unsupported):
|
|
52
|
+
* - downloadTable(...) — INHERIT (uses query(); fine for memory path)
|
|
53
|
+
* - parseBucketUrl / extractFilesFromS3 / extractFilesFromGCS / extractFilesFromAzure
|
|
54
|
+
* — N/A (only invoked via unload paths we don't expose)
|
|
55
|
+
* - readOnly() — override → true (signals to Cube/Cube Store)
|
|
56
|
+
*
|
|
57
|
+
* SQL-shape helpers used by Cube but irrelevant to MongoSQL (the dialect class
|
|
58
|
+
* handles quoting; the driver does not echo SQL strings):
|
|
59
|
+
* - quoteIdentifier(id) — INHERIT (default uses '"', dialect class
|
|
60
|
+
* in MongoSqlQuery overrides to backticks)
|
|
61
|
+
* - param(i) — INHERIT
|
|
62
|
+
* - wrapQueryWithLimit({query, limit}) — INHERIT (mutates the object in place)
|
|
63
|
+
*
|
|
64
|
+
* Static:
|
|
65
|
+
* - dialectClass() — override → MongoSqlQuery
|
|
66
|
+
*/
|
|
67
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
68
|
+
exports._normalizeRowShapeForTests = exports.MongoSqlDriver = void 0;
|
|
69
|
+
const base_driver_1 = require("@cubejs-backend/base-driver");
|
|
70
|
+
const native_js_1 = require("./native.js");
|
|
71
|
+
const MongoSqlQuery_js_1 = require("./MongoSqlQuery.js");
|
|
72
|
+
const config_js_1 = require("./config.js");
|
|
73
|
+
/**
|
|
74
|
+
* Cube driver class. Cube instantiates this when CUBEJS_DB_TYPE=mongosql.
|
|
75
|
+
*
|
|
76
|
+
* Construction is cheap and pure: env vars are read and validated, but no
|
|
77
|
+
* native client is created. The native client is created lazily on the first
|
|
78
|
+
* `testConnection()` / `query()` / `tablesSchema()` call so that throwing in
|
|
79
|
+
* the constructor reports config errors crisply (Cube wraps construction
|
|
80
|
+
* exceptions less helpfully than runtime ones).
|
|
81
|
+
*
|
|
82
|
+
* **Configuration env vars (see `./config.ts` and README "Configuration"):**
|
|
83
|
+
*
|
|
84
|
+
* Cube-standard (`CUBEJS_DB_*`):
|
|
85
|
+
* - `CUBEJS_DB_URL` / `CUBEJS_DB_URI` — full MongoDB connection string
|
|
86
|
+
* - `CUBEJS_DB_HOST` / `_PORT` / `_USER` / `_PASS` / `_NAME` — composed URI parts
|
|
87
|
+
* - `CUBEJS_DB_NAME` — database (also required separately by the driver)
|
|
88
|
+
* - `CUBEJS_DB_SSL` — `tls=true|false`
|
|
89
|
+
* - `CUBEJS_DB_MAX_POOL` / `_MIN_POOL` — `maxPoolSize` / `minPoolSize`
|
|
90
|
+
* - `CUBEJS_DB_QUERY_TIMEOUT` — per-query `maxTimeMS` (duration string or ms)
|
|
91
|
+
* - `CUBEJS_DB_IDLE_TIMEOUT` — `maxIdleTimeMS` (duration string or ms)
|
|
92
|
+
*
|
|
93
|
+
* MongoDB-specific (`CUBEJS_MONGOSQL_*`):
|
|
94
|
+
* - `_SCHEMA_SOURCE` — `collection` (default), `file`, or `atlas-sql`
|
|
95
|
+
* - `_SCHEMA_FILE` — path (required if `_SCHEMA_SOURCE=file`)
|
|
96
|
+
* - `_SCHEMA_REFRESH_SEC` — background refresh cadence in seconds
|
|
97
|
+
* - `_SCHEMA_FAIL_OPEN` — `true` to soft-fail initial schema load
|
|
98
|
+
* - `_QUERY_TIMEOUT_MS` — (legacy) bare-ms timeout; overridden by
|
|
99
|
+
* `CUBEJS_DB_QUERY_TIMEOUT` when both set
|
|
100
|
+
* - `_MAX_ROWS` — row cap per query
|
|
101
|
+
* - `_APP_NAME` — `appName` (shows up in `serverStatus().connections`)
|
|
102
|
+
* - `_MAX_CONNECTING` — `maxConnecting`
|
|
103
|
+
* - `_WAIT_QUEUE_TIMEOUT_MS` — `waitQueueTimeoutMS`
|
|
104
|
+
* - `_CONNECT_TIMEOUT_MS` — `connectTimeoutMS`
|
|
105
|
+
* - `_SOCKET_TIMEOUT_MS` — `socketTimeoutMS`
|
|
106
|
+
* - `_SERVER_SELECTION_TIMEOUT_MS` — `serverSelectionTimeoutMS`
|
|
107
|
+
* - `_HEARTBEAT_FREQUENCY_MS` — `heartbeatFrequencyMS`
|
|
108
|
+
* - `_RETRY_WRITES` / `_RETRY_READS` — `retryWrites` / `retryReads`
|
|
109
|
+
* - `_COMPRESSORS` — `compressors`
|
|
110
|
+
*
|
|
111
|
+
* User-set URI params (those already encoded in `CUBEJS_DB_URL/URI`) ALWAYS
|
|
112
|
+
* win — we only append env-driven params for keys the URI doesn't already
|
|
113
|
+
* specify.
|
|
114
|
+
*/
|
|
115
|
+
class MongoSqlDriver extends base_driver_1.BaseDriver {
|
|
116
|
+
resolvedConfig;
|
|
117
|
+
client;
|
|
118
|
+
constructor(config) {
|
|
119
|
+
super();
|
|
120
|
+
this.resolvedConfig = buildConfig(config, process.env);
|
|
121
|
+
}
|
|
122
|
+
/** Test hook: surface the config the driver resolved from constructor + env. */
|
|
123
|
+
_config() {
|
|
124
|
+
return this.resolvedConfig;
|
|
125
|
+
}
|
|
126
|
+
// ---------- BaseDriver overrides ----------
|
|
127
|
+
async testConnection() {
|
|
128
|
+
const client = this.ensureClient();
|
|
129
|
+
await client.testConnection();
|
|
130
|
+
}
|
|
131
|
+
async query(sql, values, options) {
|
|
132
|
+
// Mongosql v1.8.5 does not accept SQL parameters via the wire (no
|
|
133
|
+
// `?` / `$N` placeholder substitution at the translator layer). Cube
|
|
134
|
+
// passes a values array on pre-aggregation build paths
|
|
135
|
+
// (partitioned/incremental rollups with `WHERE created_at >= CAST(? AS TIMESTAMP)`).
|
|
136
|
+
// We substitute the values into the SQL as quoted literals BEFORE
|
|
137
|
+
// sending to mongosql — equivalent to what CubeJS's `BaseQuery.paramAllocator`
|
|
138
|
+
// would emit when the dialect declares no param support.
|
|
139
|
+
const finalSql = values !== undefined && values.length > 0 ? substituteParameters(sql, values) : sql;
|
|
140
|
+
if (projectionHasNameCollision(finalSql)) {
|
|
141
|
+
throw translateFailed('JOIN projection contains two or more qualified columns with the same name ' +
|
|
142
|
+
'(e.g. `SELECT a.col, b.col` where the column names match). Mongosql ' +
|
|
143
|
+
'collapses these into an empty-string envelope `{"": {col, col}}` and BSON ' +
|
|
144
|
+
'document keys silently overwrite — losing data. Use `SELECT *` (returns ' +
|
|
145
|
+
'`<table>__<column>` prefixes) or alias each column explicitly ' +
|
|
146
|
+
'(`SELECT a.col AS a_col, b.col AS b_col`).');
|
|
147
|
+
}
|
|
148
|
+
// Cube's QueryOptions is `{ [key: string]: any }`; consumers that want
|
|
149
|
+
// cancellation can pass a plain AbortSignal via `options.signal`.
|
|
150
|
+
// Cube core does not pass one today, but `release()` cancellation flows
|
|
151
|
+
// through the parent close-token on the native side regardless — so
|
|
152
|
+
// SIGTERM cleanup works whether or not Cube ever wires this up.
|
|
153
|
+
const signal = extractAbortSignal(options);
|
|
154
|
+
const client = this.ensureClient();
|
|
155
|
+
const raw = await client.query(finalSql, signal);
|
|
156
|
+
const flat = flattenRows(raw);
|
|
157
|
+
return normalizeRowShape(flat);
|
|
158
|
+
}
|
|
159
|
+
async tablesSchema() {
|
|
160
|
+
const client = this.ensureClient();
|
|
161
|
+
return client.tablesSchema();
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Cube's incremental-schema-loading entry point #1.
|
|
165
|
+
*
|
|
166
|
+
* Returns the list of schemas (databases) we expose. BaseDriver's
|
|
167
|
+
* default issues `SELECT table_schema ... FROM information_schema.tables`,
|
|
168
|
+
* which MongoSQL has no equivalent for. We re-render from the cached
|
|
169
|
+
* `tablesSchema()` snapshot — which the native side already keeps
|
|
170
|
+
* fresh via the background refresh task (SPEC FR-3 / ARCHITECTURE §3.2).
|
|
171
|
+
* Each call into `client.tablesSchema()` returns a fresh clone of the
|
|
172
|
+
* cached snapshot, so the cost is O(N) in catalog column count — for
|
|
173
|
+
* the typical few-thousand-cell catalog this is sub-millisecond on the
|
|
174
|
+
* hot path (no native I/O, no `__sql_schemas` round-trip).
|
|
175
|
+
*
|
|
176
|
+
* Driver only ever exposes the database configured at construction
|
|
177
|
+
* (FR-7 — `CUBEJS_DB_NAME`), so the returned list has at most one
|
|
178
|
+
* entry. An empty list is possible if no schemas have been loaded
|
|
179
|
+
* yet (e.g. `testConnection()` was never called or the schema source
|
|
180
|
+
* is empty); Cube handles that case the same way it would for the
|
|
181
|
+
* SQL path's empty resultset.
|
|
182
|
+
*/
|
|
183
|
+
async getSchemas() {
|
|
184
|
+
const snapshot = await this.tablesSchema();
|
|
185
|
+
return Object.keys(snapshot).map((schema_name) => ({ schema_name }));
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Cube's incremental-schema-loading entry point #2.
|
|
189
|
+
*
|
|
190
|
+
* For each requested schema, returns its table list from the cached
|
|
191
|
+
* `tablesSchema()`. Schemas the snapshot doesn't know about are
|
|
192
|
+
* silently dropped — mirrors the SQL path's "WHERE schema IN (...)"
|
|
193
|
+
* which would naturally exclude unknown names. Passing an empty
|
|
194
|
+
* `schemas` array returns an empty result with no native I/O cost
|
|
195
|
+
* beyond a single cache read.
|
|
196
|
+
*/
|
|
197
|
+
async getTablesForSpecificSchemas(schemas) {
|
|
198
|
+
if (schemas.length === 0)
|
|
199
|
+
return [];
|
|
200
|
+
const snapshot = await this.tablesSchema();
|
|
201
|
+
const out = [];
|
|
202
|
+
for (const { schema_name } of schemas) {
|
|
203
|
+
const tables = snapshot[schema_name];
|
|
204
|
+
if (!tables)
|
|
205
|
+
continue;
|
|
206
|
+
for (const table_name of Object.keys(tables)) {
|
|
207
|
+
out.push({ schema_name, table_name });
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return out;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Cube's incremental-schema-loading entry point #3.
|
|
214
|
+
*
|
|
215
|
+
* For each requested `(schema_name, table_name)`, returns one row
|
|
216
|
+
* per column with `column_name` and `data_type`. `data_type` is the
|
|
217
|
+
* Cube generic-type string emitted by the native side (`string`,
|
|
218
|
+
* `int`, `bigint`, `decimal`, `boolean`, `timestamp`, `text`) — the
|
|
219
|
+
* same values that surface in `tablesSchema()`. Unknown tables are
|
|
220
|
+
* silently dropped, same contract as `getTablesForSpecificSchemas`.
|
|
221
|
+
*
|
|
222
|
+
* `attributes` is forwarded verbatim from the column descriptor IF
|
|
223
|
+
* a future native version supplies one — but the Rust `do_tables_schema`
|
|
224
|
+
* (crates/native/src/client.rs:314) always emits `attributes: []` today,
|
|
225
|
+
* so in production this is effectively always an empty array. We do
|
|
226
|
+
* not emit `primaryKey` automatically — MongoDB's implicit `_id` IS a
|
|
227
|
+
* primary key, but tagging it without explicit schema annotation would
|
|
228
|
+
* surface as a foreign-key target in Cube's relationship inference and
|
|
229
|
+
* break heuristic joins. The TS-side forwarding path exists for a
|
|
230
|
+
* future Rust change where `__sql_schemas` documents' attribute fields
|
|
231
|
+
* are propagated; callers that need the tag today still must encode
|
|
232
|
+
* it explicitly in their __sql_schemas document AND in a Rust patch
|
|
233
|
+
* that surfaces it through `ColumnSchema::attributes`.
|
|
234
|
+
*
|
|
235
|
+
* `foreign_keys` is not derivable from MongoDB schema documents — the
|
|
236
|
+
* field is omitted (Cube's `QueryColumnsResult` declares it as
|
|
237
|
+
* optional via the spread of `TableColumnQueryResult`).
|
|
238
|
+
*/
|
|
239
|
+
async getColumnsForSpecificTables(tables) {
|
|
240
|
+
if (tables.length === 0)
|
|
241
|
+
return [];
|
|
242
|
+
const snapshot = await this.tablesSchema();
|
|
243
|
+
const out = [];
|
|
244
|
+
for (const { schema_name, table_name } of tables) {
|
|
245
|
+
const cols = snapshot[schema_name]?.[table_name];
|
|
246
|
+
if (!cols)
|
|
247
|
+
continue;
|
|
248
|
+
for (const col of cols) {
|
|
249
|
+
out.push({
|
|
250
|
+
schema_name,
|
|
251
|
+
table_name,
|
|
252
|
+
column_name: col.name,
|
|
253
|
+
data_type: col.type,
|
|
254
|
+
attributes: col.attributes,
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return out;
|
|
259
|
+
}
|
|
260
|
+
async release() {
|
|
261
|
+
if (this.client) {
|
|
262
|
+
const c = this.client;
|
|
263
|
+
this.client = undefined;
|
|
264
|
+
await c.close();
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
async downloadQueryResults(sql, values, options) {
|
|
268
|
+
// Pass through any caller-supplied AbortSignal so downloadQueryResults
|
|
269
|
+
// is cancellable on the same terms as query().
|
|
270
|
+
const signal = extractAbortSignal(options);
|
|
271
|
+
// The DownloadQueryResultsOptions interface includes `streamImport`
|
|
272
|
+
// (driver advertises whether it can stream rows during pre-aggregation
|
|
273
|
+
// builds). We advertise `streamImport: false` in `capabilities()` —
|
|
274
|
+
// mongosql v1.8.5 has no streaming cursor wired through napi-rs
|
|
275
|
+
// (napi-rs's ThreadsafeFunction round-trip is post-MVP per SPEC
|
|
276
|
+
// NFR-1) and the result is buffered up to `CUBEJS_MONGOSQL_MAX_ROWS`.
|
|
277
|
+
//
|
|
278
|
+
// Cube's contract: when a driver advertises `streamImport: false`,
|
|
279
|
+
// Cube does NOT call `downloadQueryResults` with `streamImport:
|
|
280
|
+
// true` (the option is reserved for `streamImport: true` drivers).
|
|
281
|
+
// We honor the contract by ignoring the flag entirely: callers that
|
|
282
|
+
// pass `streamImport: true` get the same memory `{rows, types}`
|
|
283
|
+
// shape — the option has no effect. This mirrors the BaseDriver
|
|
284
|
+
// default (`base-driver/dist/src/BaseDriver.js`), which also ignores
|
|
285
|
+
// `streamImport`.
|
|
286
|
+
//
|
|
287
|
+
// If a future caller wants streaming, the right shape is for the
|
|
288
|
+
// driver to ALSO implement the optional `stream(table, values,
|
|
289
|
+
// options) -> StreamTableData` method on `DriverInterface` (see
|
|
290
|
+
// `driver.interface.d.ts`). We do not implement it; until we do,
|
|
291
|
+
// `streamImport: false` is the right capability flag and the only
|
|
292
|
+
// honest answer is to keep returning the memory shape.
|
|
293
|
+
// Cube's pre-aggregation upload path passes `types` (a `[{name, type},
|
|
294
|
+
// ...]` list) to Cube Store to drive the LOAD ROWS column list. The
|
|
295
|
+
// types come from `mongosql::Translation::{select_order, result_set_schema}`
|
|
296
|
+
// — the authoritative projection order and BSON type mapping. We no
|
|
297
|
+
// longer sniff from row values: that path was non-deterministic in
|
|
298
|
+
// multi-partition pre-aggregations because mongosql constructs its
|
|
299
|
+
// `$project` stage by iterating a HashMap-backed `Schema::Document`,
|
|
300
|
+
// so two translations of the same SQL produced different field
|
|
301
|
+
// orders. Divergent column orders across partition rebuilds caused
|
|
302
|
+
// Cube Store UNIONs to fail with `type_coercion ... Timestamp ... Int64`.
|
|
303
|
+
const finalSql = values !== undefined && values.length > 0 ? substituteParameters(sql, values) : sql;
|
|
304
|
+
if (projectionHasNameCollision(finalSql)) {
|
|
305
|
+
throw translateFailed('JOIN projection contains two or more qualified columns with the same name ' +
|
|
306
|
+
'(e.g. `SELECT a.col, b.col` where the column names match). Mongosql ' +
|
|
307
|
+
'collapses these into an empty-string envelope `{"": {col, col}}` and BSON ' +
|
|
308
|
+
'document keys silently overwrite — losing data. Use `SELECT *` (returns ' +
|
|
309
|
+
'`<table>__<column>` prefixes) or alias each column explicitly ' +
|
|
310
|
+
'(`SELECT a.col AS a_col, b.col AS b_col`).');
|
|
311
|
+
}
|
|
312
|
+
const client = this.ensureClient();
|
|
313
|
+
const { rows, types } = await client.queryWithTypes(finalSql, signal);
|
|
314
|
+
// Use the authoritative type list (from `mongosql::Translation::select_order`)
|
|
315
|
+
// to null-fill any key that's expected by the projection but missing
|
|
316
|
+
// from some/all rows. Same root cause as `normalizeRowShape` — see its
|
|
317
|
+
// doc-comment. Here we prefer the type list over union-of-keys because
|
|
318
|
+
// (a) it's deterministic, and (b) it covers the edge case where a
|
|
319
|
+
// column is missing from EVERY row (a union would miss it; the type
|
|
320
|
+
// list still names it).
|
|
321
|
+
const flat = flattenRows(rows);
|
|
322
|
+
const expected = types.map((t) => t.name);
|
|
323
|
+
// NOTE: Mutates `flat` in place (see `normalizeRowShape` doc-comment for the
|
|
324
|
+
// mutation contract). First pass: null-fill every projected column named in
|
|
325
|
+
// the authoritative type list — covers the rare case where a column is
|
|
326
|
+
// missing from EVERY row (a pure union-of-keys would miss it). If `expected`
|
|
327
|
+
// is empty (e.g. an empty native response with no types), the for-loop is a
|
|
328
|
+
// no-op and the union pass below alone honors the FR-1 contract.
|
|
329
|
+
for (const k of expected) {
|
|
330
|
+
for (const r of flat) {
|
|
331
|
+
if (!Object.hasOwn(r, k))
|
|
332
|
+
r[k] = null;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
// Belt-and-braces union-of-keys pass — handles any row keys outside
|
|
336
|
+
// `expected` (e.g. a future mongosql version emitting an extra envelope
|
|
337
|
+
// field) so this path honors the FR-1 "every row has the same key set"
|
|
338
|
+
// contract symmetrically with `query()`.
|
|
339
|
+
normalizeRowShape(flat);
|
|
340
|
+
return { rows: flat, types: types.map(normalizeColumnType) };
|
|
341
|
+
}
|
|
342
|
+
capabilities() {
|
|
343
|
+
return {
|
|
344
|
+
// No EXPORT_BUCKET, no streaming source, no CSV/stream import path.
|
|
345
|
+
unloadWithoutTempTable: false,
|
|
346
|
+
streamingSource: false,
|
|
347
|
+
// We implement getSchemas / getTablesForSpecificSchemas /
|
|
348
|
+
// getColumnsForSpecificTables on top of the cached native
|
|
349
|
+
// `tablesSchema()` snapshot. Cube uses the granular three-method
|
|
350
|
+
// path when this flag is true, which means it never falls back to
|
|
351
|
+
// the BaseDriver default that issues SQL against
|
|
352
|
+
// `information_schema.*` (MongoSQL has no such schema).
|
|
353
|
+
incrementalSchemaLoading: true,
|
|
354
|
+
csvImport: false,
|
|
355
|
+
// Driver has no streaming cursor wired through napi-rs
|
|
356
|
+
// (ThreadsafeFunction round-trip is post-MVP per SPEC NFR-1) so
|
|
357
|
+
// pre-aggregation builds use `downloadQueryResults`'s memory shape
|
|
358
|
+
// capped at `CUBEJS_MONGOSQL_MAX_ROWS`. The option is honored by
|
|
359
|
+
// `downloadQueryResults` as a no-op — see that method's comment.
|
|
360
|
+
streamImport: false,
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
readOnly() {
|
|
364
|
+
return true;
|
|
365
|
+
}
|
|
366
|
+
// ---------- Methods CubeJS expects but MongoSQL cannot fulfil ----------
|
|
367
|
+
async createSchemaIfNotExists(_schemaName) {
|
|
368
|
+
throw notSupported('createSchemaIfNotExists');
|
|
369
|
+
}
|
|
370
|
+
async dropTable(_tableName, _options) {
|
|
371
|
+
throw notSupported('dropTable');
|
|
372
|
+
}
|
|
373
|
+
async uploadTable(_table, _columns, _tableData) {
|
|
374
|
+
throw notSupported('uploadTable');
|
|
375
|
+
}
|
|
376
|
+
async uploadTableWithIndexes(_table, _columns, _tableData, _indexesSql, _uniqueKeyColumns, _queryTracingObj, _externalOptions) {
|
|
377
|
+
throw notSupported('uploadTableWithIndexes');
|
|
378
|
+
}
|
|
379
|
+
async createTable(_quotedTableName, _columns) {
|
|
380
|
+
throw notSupported('createTable');
|
|
381
|
+
}
|
|
382
|
+
async createTableRaw(_query) {
|
|
383
|
+
throw notSupported('createTableRaw');
|
|
384
|
+
}
|
|
385
|
+
// ---------- Static ----------
|
|
386
|
+
static dialectClass() {
|
|
387
|
+
return MongoSqlQuery_js_1.MongoSqlQuery;
|
|
388
|
+
}
|
|
389
|
+
// ---------- Internals ----------
|
|
390
|
+
ensureClient() {
|
|
391
|
+
if (!this.client) {
|
|
392
|
+
this.client = new native_js_1.MongoSqlClient(this.resolvedConfig);
|
|
393
|
+
}
|
|
394
|
+
return this.client;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
exports.MongoSqlDriver = MongoSqlDriver;
|
|
398
|
+
/**
|
|
399
|
+
* Mongosql wraps each result row in a per-collection envelope:
|
|
400
|
+
* `[{ users: { _id, email, ... } }, ...]`
|
|
401
|
+
*
|
|
402
|
+
* Cube expects flat rows (`Record<string, unknown>`). Strategy:
|
|
403
|
+
* - Single top-level key whose value is a plain object → unwrap.
|
|
404
|
+
* - Multiple top-level keys (JOIN result) → merge with `<table>__<column>`.
|
|
405
|
+
* - Anything else (scalar at top level, no envelope) → pass through.
|
|
406
|
+
*
|
|
407
|
+
* Discovery: see IMPLEMENTATION_PLAN.md → 2026-05-09 — T08 (mongosql per-
|
|
408
|
+
* collection envelope) for the pipeline-shape source.
|
|
409
|
+
*
|
|
410
|
+
* **Critic v2 — Issue 2: empty-string envelope collision**.
|
|
411
|
+
*
|
|
412
|
+
* Mongosql emits `{"": {col: ..., col: ...}}` for explicit projections in
|
|
413
|
+
* JOINs (e.g. `SELECT users.col, orders.col FROM users JOIN orders ...`).
|
|
414
|
+
* If both sides project a column with the same name (`account_id`,
|
|
415
|
+
* `created_at`, `_id`), the BSON document keys collide and silently
|
|
416
|
+
* collapse — by the time JS has parsed the row we cannot detect the loss.
|
|
417
|
+
*
|
|
418
|
+
* Mitigation: input-side heuristic check in `query()` — if the SQL has a
|
|
419
|
+
* JOIN AND projects multiple qualified columns with the same trailing
|
|
420
|
+
* name, throw `MONGOSQL_TRANSLATE_FAILED` before executing. The flatten
|
|
421
|
+
* path itself stays permissive so single-collection queries with the
|
|
422
|
+
* naturally-occurring empty-string envelope (`SELECT col1, col2 FROM
|
|
423
|
+
* users` → `{"": {col1, col2}}`) keep working.
|
|
424
|
+
*
|
|
425
|
+
* Callers hitting the heuristic must either: (a) use `SELECT *` (multi-
|
|
426
|
+
* key envelope preserves `<table>__<col>` prefixes), or (b) alias the
|
|
427
|
+
* conflicting columns explicitly (`SELECT a.col AS a_col, b.col AS
|
|
428
|
+
* b_col`).
|
|
429
|
+
*/
|
|
430
|
+
function flattenRows(rows) {
|
|
431
|
+
return rows.map((row) => flattenRow(row));
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Make every row's key set identical by null-filling missing keys.
|
|
435
|
+
*
|
|
436
|
+
* **Mutates `rows` in place** — callers must not retain references to
|
|
437
|
+
* the pre-normalize array if they need the original sparse shape. The
|
|
438
|
+
* returned reference is the same array.
|
|
439
|
+
*
|
|
440
|
+
* **Why this exists.** Mongosql's `$project` stage that references a
|
|
441
|
+
* nested-path expression (e.g. `agent.displayName` on a docs collection)
|
|
442
|
+
* OMITS the field entirely when the source path is missing on the
|
|
443
|
+
* underlying document — it does not emit `null`. This bites downstream
|
|
444
|
+
* consumers whenever the row at index 0 happens to be sparser than later
|
|
445
|
+
* rows; `ORDER BY <nested-field> ASC` is the most common way this
|
|
446
|
+
* surfaces, but any query whose row 0 lacks a key that later rows have
|
|
447
|
+
* triggers the same.
|
|
448
|
+
*
|
|
449
|
+
* Downstream consumers (notably Cube's native `getFinalQueryResult`
|
|
450
|
+
* transform in `@cubejs-backend/native`) compile their row→member
|
|
451
|
+
* extraction plan from the keys present in **row 0**. A sparse row 0
|
|
452
|
+
* causes Cube to drop the column from every row in the response — even
|
|
453
|
+
* rows that DO have the value. Reproduced empirically against the
|
|
454
|
+
* `configs` collection (`SELECT id, agent_display_name FROM configs ...
|
|
455
|
+
* ORDER BY agent_display_name ASC LIMIT 500` → 500 rows, all missing
|
|
456
|
+
* `configs.agent_display_name`, even though 497/500 source docs have it).
|
|
457
|
+
*
|
|
458
|
+
* **Fix shape.** Take the union of keys across all rows and null-fill
|
|
459
|
+
* each row so every row has every key. We can't simply look at `row[0]`
|
|
460
|
+
* because that's the symptom. Iteration is O(rows × cols). At the
|
|
461
|
+
* `MAX_ROWS=100000` pre-agg cap with a ~20-column projection this is
|
|
462
|
+
* roughly 2M property-existence checks and stays under 100ms in practice.
|
|
463
|
+
*
|
|
464
|
+
* `downloadQueryResults` uses the authoritative type list instead of
|
|
465
|
+
* a key union (the union would miss columns absent from every row); that
|
|
466
|
+
* variant is implemented inline at the call site, not via this helper.
|
|
467
|
+
*/
|
|
468
|
+
function normalizeRowShape(rows) {
|
|
469
|
+
if (rows.length === 0)
|
|
470
|
+
return rows;
|
|
471
|
+
const union = new Set();
|
|
472
|
+
for (const r of rows) {
|
|
473
|
+
for (const k of Object.keys(r))
|
|
474
|
+
union.add(k);
|
|
475
|
+
}
|
|
476
|
+
for (const r of rows) {
|
|
477
|
+
for (const k of union) {
|
|
478
|
+
// `Object.hasOwn` over `k in r` — the `in` operator traverses
|
|
479
|
+
// the prototype chain, which would diverge from `Object.keys(r)`
|
|
480
|
+
// above (own enumerable keys only) on rows that inherit through a
|
|
481
|
+
// non-default prototype.
|
|
482
|
+
if (!Object.hasOwn(r, k))
|
|
483
|
+
r[k] = null;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
return rows;
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Test-only export. Lets `tests/unit/driver.test.ts` exercise the
|
|
490
|
+
* key-union null-fill in isolation without spinning up the driver.
|
|
491
|
+
* Not part of the public API.
|
|
492
|
+
*/
|
|
493
|
+
exports._normalizeRowShapeForTests = normalizeRowShape;
|
|
494
|
+
function flattenRow(row) {
|
|
495
|
+
const keys = Object.keys(row);
|
|
496
|
+
if (keys.length === 1) {
|
|
497
|
+
const only = row[keys[0]];
|
|
498
|
+
if (isPlainObject(only))
|
|
499
|
+
return only;
|
|
500
|
+
return row;
|
|
501
|
+
}
|
|
502
|
+
const out = {};
|
|
503
|
+
for (const [tbl, val] of Object.entries(row)) {
|
|
504
|
+
if (isPlainObject(val)) {
|
|
505
|
+
for (const [col, v] of Object.entries(val)) {
|
|
506
|
+
out[`${tbl}__${col}`] = v;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
else {
|
|
510
|
+
out[tbl] = val;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
return out;
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* Heuristic detector for the empty-string-envelope column-collision risk
|
|
517
|
+
* (Critic v2 — Issue 2). Returns true iff `sql` contains a JOIN AND
|
|
518
|
+
* projects two or more *qualified* columns (`<ident>.<column>`) where
|
|
519
|
+
* the trailing column name appears more than once. False negatives are
|
|
520
|
+
* acceptable (we can only see source SQL, not the full algebra); false
|
|
521
|
+
* positives are bounded by the JOIN gate so plain single-table queries
|
|
522
|
+
* are never blocked.
|
|
523
|
+
*
|
|
524
|
+
* The check is deliberately conservative — it does not parse SQL,
|
|
525
|
+
* doesn't strip comments, and only flags the obvious shape. Cube's own
|
|
526
|
+
* generated SQL always uses aliases or `SELECT *`-via-cube-views; users
|
|
527
|
+
* writing raw SQL who hit this should add `AS <alias>` and re-run.
|
|
528
|
+
*/
|
|
529
|
+
function projectionHasNameCollision(sql) {
|
|
530
|
+
if (!/\bjoin\b/i.test(sql))
|
|
531
|
+
return false;
|
|
532
|
+
const head = sql.match(/^\s*select\s+([\s\S]+?)\s+from\b/i);
|
|
533
|
+
if (!head)
|
|
534
|
+
return false;
|
|
535
|
+
const projection = head[1];
|
|
536
|
+
if (projection.trim() === '*')
|
|
537
|
+
return false;
|
|
538
|
+
// Strip parenthesised expressions (function args, subqueries) so we
|
|
539
|
+
// don't pick up nested column refs as projection items.
|
|
540
|
+
const flat = projection.replace(/\([^()]*\)/g, '');
|
|
541
|
+
const items = flat.split(',');
|
|
542
|
+
const trailingNames = [];
|
|
543
|
+
for (const raw of items) {
|
|
544
|
+
const item = raw.trim();
|
|
545
|
+
if (!item)
|
|
546
|
+
continue;
|
|
547
|
+
// If the projection item has an alias (`AS xxx` or bare alias),
|
|
548
|
+
// collisions are no longer possible — skip.
|
|
549
|
+
if (/\bas\b/i.test(item))
|
|
550
|
+
continue;
|
|
551
|
+
// Look for `<ident>.<column>` at the end of the item.
|
|
552
|
+
const m = item.match(/[A-Za-z_][\w]*\.([A-Za-z_][\w]*)\s*$/);
|
|
553
|
+
if (m)
|
|
554
|
+
trailingNames.push(m[1].toLowerCase());
|
|
555
|
+
}
|
|
556
|
+
const seen = new Set();
|
|
557
|
+
for (const n of trailingNames) {
|
|
558
|
+
if (seen.has(n))
|
|
559
|
+
return true;
|
|
560
|
+
seen.add(n);
|
|
561
|
+
}
|
|
562
|
+
return false;
|
|
563
|
+
}
|
|
564
|
+
/**
|
|
565
|
+
* Substitute `?` placeholders in `sql` with literal values from `values`.
|
|
566
|
+
*
|
|
567
|
+
* Mongosql v1.8.5 has no wire-level parameter protocol, so Cube's
|
|
568
|
+
* pre-aggregation paths (which emit `WHERE col >= CAST(? AS TIMESTAMP)` +
|
|
569
|
+
* `[Date, Date]`) need their placeholders inlined before translation.
|
|
570
|
+
*
|
|
571
|
+
* Skips `?` that appear inside single-quoted string literals. Doubled
|
|
572
|
+
* single-quote (`''`) is treated as a literal `'` inside the current
|
|
573
|
+
* string — matches the standard SQL escape convention.
|
|
574
|
+
*
|
|
575
|
+
* Throws `MONGOSQL_CONFIG_INVALID` on placeholder/value count mismatch.
|
|
576
|
+
*/
|
|
577
|
+
function substituteParameters(sql, values) {
|
|
578
|
+
let out = '';
|
|
579
|
+
let inString = false;
|
|
580
|
+
let idx = 0;
|
|
581
|
+
for (let i = 0; i < sql.length; i++) {
|
|
582
|
+
const c = sql[i];
|
|
583
|
+
if (c === "'") {
|
|
584
|
+
// Doubled single-quote inside a string is an escaped quote — pass
|
|
585
|
+
// through both characters and stay in-string.
|
|
586
|
+
if (inString && sql[i + 1] === "'") {
|
|
587
|
+
out += "''";
|
|
588
|
+
i++;
|
|
589
|
+
continue;
|
|
590
|
+
}
|
|
591
|
+
inString = !inString;
|
|
592
|
+
out += c;
|
|
593
|
+
}
|
|
594
|
+
else if (c === '?' && !inString) {
|
|
595
|
+
if (idx >= values.length) {
|
|
596
|
+
throw configInvalid(`SQL has more '?' placeholders than provided values (consumed ${idx} of ${values.length})`);
|
|
597
|
+
}
|
|
598
|
+
out += formatSqlLiteral(values[idx]);
|
|
599
|
+
idx++;
|
|
600
|
+
}
|
|
601
|
+
else {
|
|
602
|
+
out += c;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
if (idx < values.length) {
|
|
606
|
+
throw configInvalid(`parameter list has more values (${values.length}) than '?' placeholders in SQL (${idx})`);
|
|
607
|
+
}
|
|
608
|
+
return out;
|
|
609
|
+
}
|
|
610
|
+
function formatSqlLiteral(v) {
|
|
611
|
+
if (v === null || v === undefined)
|
|
612
|
+
return 'NULL';
|
|
613
|
+
if (typeof v === 'boolean')
|
|
614
|
+
return v ? 'TRUE' : 'FALSE';
|
|
615
|
+
if (typeof v === 'number')
|
|
616
|
+
return Number.isFinite(v) ? String(v) : 'NULL';
|
|
617
|
+
if (typeof v === 'bigint')
|
|
618
|
+
return v.toString();
|
|
619
|
+
if (v instanceof Date) {
|
|
620
|
+
if (Number.isNaN(v.getTime()))
|
|
621
|
+
return 'NULL';
|
|
622
|
+
// ISO-8601, no quoting wrapper — callers usually wrap with `CAST(... AS TIMESTAMP)`.
|
|
623
|
+
return `'${v.toISOString()}'`;
|
|
624
|
+
}
|
|
625
|
+
return `'${String(v).replace(/'/g, "''")}'`;
|
|
626
|
+
}
|
|
627
|
+
/**
|
|
628
|
+
* Defensive passthrough for the native-side `(name, type)` entry.
|
|
629
|
+
*
|
|
630
|
+
* The Rust layer already returns one of Cube's documented generic-type
|
|
631
|
+
* strings (`timestamp`, `int`, `bigint`, `decimal`, `boolean`, `string`,
|
|
632
|
+
* `text`). We re-shape into a plain object so consumers depending on
|
|
633
|
+
* structural typing don't end up with the napi-rs deserialised view.
|
|
634
|
+
*/
|
|
635
|
+
function normalizeColumnType(c) {
|
|
636
|
+
return { name: c.name, type: c.type };
|
|
637
|
+
}
|
|
638
|
+
function isPlainObject(v) {
|
|
639
|
+
return typeof v === 'object' && v !== null && !Array.isArray(v) && !(v instanceof Date);
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* Extract a caller-supplied AbortSignal from Cube's open `QueryOptions`
|
|
643
|
+
* shape. Cube core does not currently pass one, but the contract is
|
|
644
|
+
* `[key: string]: any`, so any caller that wires `options.signal` will
|
|
645
|
+
* have it propagate through to the native cancel token. Returns
|
|
646
|
+
* `undefined` if no signal is present or the value is not an AbortSignal.
|
|
647
|
+
*/
|
|
648
|
+
function extractAbortSignal(options) {
|
|
649
|
+
if (!options)
|
|
650
|
+
return undefined;
|
|
651
|
+
const candidate = options.signal;
|
|
652
|
+
// Defensive check: AbortSignal is the standard browser/Node API.
|
|
653
|
+
// Reject plain objects so we don't crash inside the native bridge.
|
|
654
|
+
if (typeof AbortSignal !== 'undefined' && candidate instanceof AbortSignal) {
|
|
655
|
+
return candidate;
|
|
656
|
+
}
|
|
657
|
+
return undefined;
|
|
658
|
+
}
|
|
659
|
+
/**
|
|
660
|
+
* Resolve the runtime `MongoSqlConfig` from constructor overrides + env.
|
|
661
|
+
*
|
|
662
|
+
* URI building lives in `./config.ts` — see that module's header for
|
|
663
|
+
* the env vars honoured, the precedence rules (constructor uri >
|
|
664
|
+
* `CUBEJS_DB_URL` > `CUBEJS_DB_URI` > composed from `CUBEJS_DB_HOST`
|
|
665
|
+
* + parts), and the duration-string format accepted by
|
|
666
|
+
* `CUBEJS_DB_QUERY_TIMEOUT` / `CUBEJS_DB_IDLE_TIMEOUT`.
|
|
667
|
+
*
|
|
668
|
+
* Everything else stays here:
|
|
669
|
+
* - `database` (required): explicit > `CUBEJS_DB_NAME`.
|
|
670
|
+
* - `schemaSource`, `schemaRefreshSec`, `schemaFailOpen`, `maxRows`:
|
|
671
|
+
* existing `CUBEJS_MONGOSQL_*` semantics, unchanged.
|
|
672
|
+
* - `queryTimeoutMs`: explicit > `CUBEJS_DB_QUERY_TIMEOUT` >
|
|
673
|
+
* `CUBEJS_MONGOSQL_QUERY_TIMEOUT_MS` (resolved in config.ts).
|
|
674
|
+
*/
|
|
675
|
+
function buildConfig(override, env) {
|
|
676
|
+
const { uri, queryTimeoutMs: envQueryTimeoutMs } = (0, config_js_1.resolveUriConfig)(override?.uri, env);
|
|
677
|
+
const database = override?.database ?? env.CUBEJS_DB_NAME;
|
|
678
|
+
if (!database) {
|
|
679
|
+
throw configInvalidMissing('database (set CUBEJS_DB_NAME or pass `database` to the constructor)');
|
|
680
|
+
}
|
|
681
|
+
const schemaSource = override?.schemaSource ?? schemaSourceFromEnv(env);
|
|
682
|
+
return {
|
|
683
|
+
uri,
|
|
684
|
+
database,
|
|
685
|
+
schemaSource,
|
|
686
|
+
schemaRefreshSec: override?.schemaRefreshSec ?? numEnv(env.CUBEJS_MONGOSQL_SCHEMA_REFRESH_SEC),
|
|
687
|
+
schemaFailOpen: override?.schemaFailOpen ?? boolEnv(env.CUBEJS_MONGOSQL_SCHEMA_FAIL_OPEN),
|
|
688
|
+
queryTimeoutMs: override?.queryTimeoutMs ?? envQueryTimeoutMs,
|
|
689
|
+
maxRows: override?.maxRows ?? numEnv(env.CUBEJS_MONGOSQL_MAX_ROWS),
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
function schemaSourceFromEnv(env) {
|
|
693
|
+
const kind = env.CUBEJS_MONGOSQL_SCHEMA_SOURCE;
|
|
694
|
+
if (!kind)
|
|
695
|
+
return undefined;
|
|
696
|
+
if (kind === 'collection')
|
|
697
|
+
return { kind: 'collection' };
|
|
698
|
+
if (kind === 'file') {
|
|
699
|
+
const path = env.CUBEJS_MONGOSQL_SCHEMA_FILE;
|
|
700
|
+
if (!path) {
|
|
701
|
+
throw configInvalid('CUBEJS_MONGOSQL_SCHEMA_FILE must be set when CUBEJS_MONGOSQL_SCHEMA_SOURCE=file');
|
|
702
|
+
}
|
|
703
|
+
return { kind: 'file', path };
|
|
704
|
+
}
|
|
705
|
+
// Atlas SQL endpoints (`*.a.query.mongodb.net`) do not expose
|
|
706
|
+
// `__sql_schemas` as a queryable collection — schemas live in an
|
|
707
|
+
// internal store reachable only via the `sqlGetSchema` admin command.
|
|
708
|
+
// See https://www.mongodb.com/docs/sql-interface/schema/view/ and
|
|
709
|
+
// `crates/native/src/schema.rs` module docs.
|
|
710
|
+
if (kind === 'atlas-sql')
|
|
711
|
+
return { kind: 'atlas-sql' };
|
|
712
|
+
throw configInvalid(`CUBEJS_MONGOSQL_SCHEMA_SOURCE must be 'collection', 'file', or 'atlas-sql' (got '${kind}')`);
|
|
713
|
+
}
|
|
714
|
+
function numEnv(v) {
|
|
715
|
+
if (v === undefined || v === '')
|
|
716
|
+
return undefined;
|
|
717
|
+
const n = Number(v);
|
|
718
|
+
if (!Number.isFinite(n))
|
|
719
|
+
return undefined;
|
|
720
|
+
return n;
|
|
721
|
+
}
|
|
722
|
+
function boolEnv(v) {
|
|
723
|
+
if (v === undefined || v === '')
|
|
724
|
+
return undefined;
|
|
725
|
+
return v.toLowerCase() === 'true' || v === '1';
|
|
726
|
+
}
|
|
727
|
+
function configInvalidMissing(detail) {
|
|
728
|
+
return configInvalid(`missing required config: ${detail}`);
|
|
729
|
+
}
|
|
730
|
+
function configInvalid(detail) {
|
|
731
|
+
const err = new Error(`MONGOSQL_CONFIG_INVALID: ${detail}`);
|
|
732
|
+
err.code = 'MONGOSQL_CONFIG_INVALID';
|
|
733
|
+
err.name = 'MongoSqlError';
|
|
734
|
+
return err;
|
|
735
|
+
}
|
|
736
|
+
function translateFailed(detail) {
|
|
737
|
+
const err = new Error(`MONGOSQL_TRANSLATE_FAILED: ${detail}`);
|
|
738
|
+
err.code = 'MONGOSQL_TRANSLATE_FAILED';
|
|
739
|
+
err.name = 'MongoSqlError';
|
|
740
|
+
return err;
|
|
741
|
+
}
|
|
742
|
+
function notSupported(method) {
|
|
743
|
+
return new Error(`MongoSqlDriver: '${method}' is not supported by the MongoSQL driver (read-only / no EXPORT_BUCKET path)`);
|
|
744
|
+
}
|
|
745
|
+
//# sourceMappingURL=MongoSqlDriver.js.map
|