@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.
@@ -0,0 +1,238 @@
1
+ /**
2
+ * MongoSqlDriver — Cube data source driver for MongoDB via MongoSQL.
3
+ *
4
+ * See SPEC.md FR-1 (driver protocol), FR-7 (env-var configuration), and
5
+ * ARCHITECTURE.md §2.1.
6
+ *
7
+ * BaseDriver method-list audit (against
8
+ * `@cubejs-backend/base-driver/dist/src/BaseDriver.d.ts`):
9
+ *
10
+ * Required by DriverInterface (we override these):
11
+ * - query(sql, values?, options?) — REQUIRED, override (delegates + flattens)
12
+ * - testConnection() — REQUIRED, override (lazy native init)
13
+ * - release() — override (closes native client)
14
+ * - tablesSchema() — override (delegate to native)
15
+ * - downloadQueryResults(sql, ...) — override → memory shape; streamImport ignored
16
+ * (capabilities().streamImport=false; see below)
17
+ * - capabilities() — override → no export bucket / streaming source
18
+ * - nowTimestamp() — INHERIT (Date.now())
19
+ * - stream(table, values, opts) — NOT implemented; `streamImport: false`
20
+ * advertises this. Cube routes the
21
+ * pre-agg upload path through
22
+ * `downloadQueryResults` instead.
23
+ *
24
+ * Schema-introspection helpers (BaseDriver default reads information_schema —
25
+ * MongoSQL doesn't expose one, so we route every shape through the native
26
+ * cached `tablesSchema()` rendering):
27
+ * - tablesSchema() — override (full snapshot, bulk path)
28
+ * - getSchemas() — override (DB list from tablesSchema)
29
+ * - getTablesForSpecificSchemas(...) — override (filter tablesSchema by db)
30
+ * - getColumnsForSpecificTables(...) — override (filter tablesSchema by table)
31
+ * - capabilities().incrementalSchemaLoading=true — opts Cube into the
32
+ * granular three-method path; SQL
33
+ * information_schema queries are
34
+ * never issued against mongosql.
35
+ * - getTablesQuery(schema) — N/A — runs SQL against information_schema
36
+ * - informationSchemaQuery() — N/A (protected default)
37
+ * - tableColumnTypes(table) — INHERIT (uses query()); only needed for
38
+ * uploadTableWithIndexes path which we reject
39
+ *
40
+ * Mutation methods (read-only driver — explicit refusal beats silent no-op):
41
+ * - createSchemaIfNotExists(schemaName) — throw
42
+ * - dropTable(tableName, options?) — throw
43
+ * - uploadTable(...) — throw
44
+ * - uploadTableWithIndexes(...) — throw
45
+ * - createTable(...) — throw
46
+ * - createTableRaw(...) — throw
47
+ * - loadPreAggregationIntoTable(...) — INHERIT default (delegates to query())
48
+ * so partitioned/incremental pre-aggs work
49
+ *
50
+ * Bucket-export (no MongoDB equivalent — SPEC FR-6 says EXPORT_BUCKET unsupported):
51
+ * - downloadTable(...) — INHERIT (uses query(); fine for memory path)
52
+ * - parseBucketUrl / extractFilesFromS3 / extractFilesFromGCS / extractFilesFromAzure
53
+ * — N/A (only invoked via unload paths we don't expose)
54
+ * - readOnly() — override → true (signals to Cube/Cube Store)
55
+ *
56
+ * SQL-shape helpers used by Cube but irrelevant to MongoSQL (the dialect class
57
+ * handles quoting; the driver does not echo SQL strings):
58
+ * - quoteIdentifier(id) — INHERIT (default uses '"', dialect class
59
+ * in MongoSqlQuery overrides to backticks)
60
+ * - param(i) — INHERIT
61
+ * - wrapQueryWithLimit({query, limit}) — INHERIT (mutates the object in place)
62
+ *
63
+ * Static:
64
+ * - dialectClass() — override → MongoSqlQuery
65
+ */
66
+ import { BaseDriver } from '@cubejs-backend/base-driver';
67
+ import type { DownloadQueryResultsOptions, DownloadQueryResultsResult, DownloadTableData, DriverCapabilities, ExternalCreateTableOptions, IndexesSQL, QueryColumnsResult, QueryOptions, QuerySchemasResult, QueryTablesResult, TableColumn, TableStructure } from '@cubejs-backend/base-driver';
68
+ import { type TablesSchema } from './native.js';
69
+ import { MongoSqlQuery } from './MongoSqlQuery.js';
70
+ import type { MongoSqlConfig } from './types.js';
71
+ /**
72
+ * Cube driver class. Cube instantiates this when CUBEJS_DB_TYPE=mongosql.
73
+ *
74
+ * Construction is cheap and pure: env vars are read and validated, but no
75
+ * native client is created. The native client is created lazily on the first
76
+ * `testConnection()` / `query()` / `tablesSchema()` call so that throwing in
77
+ * the constructor reports config errors crisply (Cube wraps construction
78
+ * exceptions less helpfully than runtime ones).
79
+ *
80
+ * **Configuration env vars (see `./config.ts` and README "Configuration"):**
81
+ *
82
+ * Cube-standard (`CUBEJS_DB_*`):
83
+ * - `CUBEJS_DB_URL` / `CUBEJS_DB_URI` — full MongoDB connection string
84
+ * - `CUBEJS_DB_HOST` / `_PORT` / `_USER` / `_PASS` / `_NAME` — composed URI parts
85
+ * - `CUBEJS_DB_NAME` — database (also required separately by the driver)
86
+ * - `CUBEJS_DB_SSL` — `tls=true|false`
87
+ * - `CUBEJS_DB_MAX_POOL` / `_MIN_POOL` — `maxPoolSize` / `minPoolSize`
88
+ * - `CUBEJS_DB_QUERY_TIMEOUT` — per-query `maxTimeMS` (duration string or ms)
89
+ * - `CUBEJS_DB_IDLE_TIMEOUT` — `maxIdleTimeMS` (duration string or ms)
90
+ *
91
+ * MongoDB-specific (`CUBEJS_MONGOSQL_*`):
92
+ * - `_SCHEMA_SOURCE` — `collection` (default), `file`, or `atlas-sql`
93
+ * - `_SCHEMA_FILE` — path (required if `_SCHEMA_SOURCE=file`)
94
+ * - `_SCHEMA_REFRESH_SEC` — background refresh cadence in seconds
95
+ * - `_SCHEMA_FAIL_OPEN` — `true` to soft-fail initial schema load
96
+ * - `_QUERY_TIMEOUT_MS` — (legacy) bare-ms timeout; overridden by
97
+ * `CUBEJS_DB_QUERY_TIMEOUT` when both set
98
+ * - `_MAX_ROWS` — row cap per query
99
+ * - `_APP_NAME` — `appName` (shows up in `serverStatus().connections`)
100
+ * - `_MAX_CONNECTING` — `maxConnecting`
101
+ * - `_WAIT_QUEUE_TIMEOUT_MS` — `waitQueueTimeoutMS`
102
+ * - `_CONNECT_TIMEOUT_MS` — `connectTimeoutMS`
103
+ * - `_SOCKET_TIMEOUT_MS` — `socketTimeoutMS`
104
+ * - `_SERVER_SELECTION_TIMEOUT_MS` — `serverSelectionTimeoutMS`
105
+ * - `_HEARTBEAT_FREQUENCY_MS` — `heartbeatFrequencyMS`
106
+ * - `_RETRY_WRITES` / `_RETRY_READS` — `retryWrites` / `retryReads`
107
+ * - `_COMPRESSORS` — `compressors`
108
+ *
109
+ * User-set URI params (those already encoded in `CUBEJS_DB_URL/URI`) ALWAYS
110
+ * win — we only append env-driven params for keys the URI doesn't already
111
+ * specify.
112
+ */
113
+ export declare class MongoSqlDriver extends BaseDriver {
114
+ private readonly resolvedConfig;
115
+ private client;
116
+ constructor(config?: Partial<MongoSqlConfig>);
117
+ /** Test hook: surface the config the driver resolved from constructor + env. */
118
+ _config(): Readonly<MongoSqlConfig>;
119
+ testConnection(): Promise<void>;
120
+ query<R = unknown>(sql: string, values?: unknown[], options?: QueryOptions): Promise<R[]>;
121
+ tablesSchema(): Promise<TablesSchema>;
122
+ /**
123
+ * Cube's incremental-schema-loading entry point #1.
124
+ *
125
+ * Returns the list of schemas (databases) we expose. BaseDriver's
126
+ * default issues `SELECT table_schema ... FROM information_schema.tables`,
127
+ * which MongoSQL has no equivalent for. We re-render from the cached
128
+ * `tablesSchema()` snapshot — which the native side already keeps
129
+ * fresh via the background refresh task (SPEC FR-3 / ARCHITECTURE §3.2).
130
+ * Each call into `client.tablesSchema()` returns a fresh clone of the
131
+ * cached snapshot, so the cost is O(N) in catalog column count — for
132
+ * the typical few-thousand-cell catalog this is sub-millisecond on the
133
+ * hot path (no native I/O, no `__sql_schemas` round-trip).
134
+ *
135
+ * Driver only ever exposes the database configured at construction
136
+ * (FR-7 — `CUBEJS_DB_NAME`), so the returned list has at most one
137
+ * entry. An empty list is possible if no schemas have been loaded
138
+ * yet (e.g. `testConnection()` was never called or the schema source
139
+ * is empty); Cube handles that case the same way it would for the
140
+ * SQL path's empty resultset.
141
+ */
142
+ getSchemas(): Promise<QuerySchemasResult[]>;
143
+ /**
144
+ * Cube's incremental-schema-loading entry point #2.
145
+ *
146
+ * For each requested schema, returns its table list from the cached
147
+ * `tablesSchema()`. Schemas the snapshot doesn't know about are
148
+ * silently dropped — mirrors the SQL path's "WHERE schema IN (...)"
149
+ * which would naturally exclude unknown names. Passing an empty
150
+ * `schemas` array returns an empty result with no native I/O cost
151
+ * beyond a single cache read.
152
+ */
153
+ getTablesForSpecificSchemas(schemas: QuerySchemasResult[]): Promise<QueryTablesResult[]>;
154
+ /**
155
+ * Cube's incremental-schema-loading entry point #3.
156
+ *
157
+ * For each requested `(schema_name, table_name)`, returns one row
158
+ * per column with `column_name` and `data_type`. `data_type` is the
159
+ * Cube generic-type string emitted by the native side (`string`,
160
+ * `int`, `bigint`, `decimal`, `boolean`, `timestamp`, `text`) — the
161
+ * same values that surface in `tablesSchema()`. Unknown tables are
162
+ * silently dropped, same contract as `getTablesForSpecificSchemas`.
163
+ *
164
+ * `attributes` is forwarded verbatim from the column descriptor IF
165
+ * a future native version supplies one — but the Rust `do_tables_schema`
166
+ * (crates/native/src/client.rs:314) always emits `attributes: []` today,
167
+ * so in production this is effectively always an empty array. We do
168
+ * not emit `primaryKey` automatically — MongoDB's implicit `_id` IS a
169
+ * primary key, but tagging it without explicit schema annotation would
170
+ * surface as a foreign-key target in Cube's relationship inference and
171
+ * break heuristic joins. The TS-side forwarding path exists for a
172
+ * future Rust change where `__sql_schemas` documents' attribute fields
173
+ * are propagated; callers that need the tag today still must encode
174
+ * it explicitly in their __sql_schemas document AND in a Rust patch
175
+ * that surfaces it through `ColumnSchema::attributes`.
176
+ *
177
+ * `foreign_keys` is not derivable from MongoDB schema documents — the
178
+ * field is omitted (Cube's `QueryColumnsResult` declares it as
179
+ * optional via the spread of `TableColumnQueryResult`).
180
+ */
181
+ getColumnsForSpecificTables(tables: QueryTablesResult[]): Promise<QueryColumnsResult[]>;
182
+ release(): Promise<void>;
183
+ downloadQueryResults(sql: string, values?: unknown[], options?: DownloadQueryResultsOptions): Promise<DownloadQueryResultsResult>;
184
+ capabilities(): DriverCapabilities;
185
+ readOnly(): boolean;
186
+ createSchemaIfNotExists(_schemaName: string): Promise<void>;
187
+ dropTable(_tableName: string, _options?: QueryOptions): Promise<unknown>;
188
+ uploadTable(_table: string, _columns: TableStructure, _tableData: DownloadTableData): Promise<void>;
189
+ uploadTableWithIndexes(_table: string, _columns: TableStructure, _tableData: DownloadTableData, _indexesSql: IndexesSQL, _uniqueKeyColumns: string[] | null, _queryTracingObj: unknown, _externalOptions: ExternalCreateTableOptions): Promise<void>;
190
+ createTable(_quotedTableName: string, _columns: TableColumn[]): Promise<void>;
191
+ createTableRaw(_query: string): Promise<void>;
192
+ static dialectClass(): typeof MongoSqlQuery;
193
+ private ensureClient;
194
+ }
195
+ /**
196
+ * Make every row's key set identical by null-filling missing keys.
197
+ *
198
+ * **Mutates `rows` in place** — callers must not retain references to
199
+ * the pre-normalize array if they need the original sparse shape. The
200
+ * returned reference is the same array.
201
+ *
202
+ * **Why this exists.** Mongosql's `$project` stage that references a
203
+ * nested-path expression (e.g. `agent.displayName` on a docs collection)
204
+ * OMITS the field entirely when the source path is missing on the
205
+ * underlying document — it does not emit `null`. This bites downstream
206
+ * consumers whenever the row at index 0 happens to be sparser than later
207
+ * rows; `ORDER BY <nested-field> ASC` is the most common way this
208
+ * surfaces, but any query whose row 0 lacks a key that later rows have
209
+ * triggers the same.
210
+ *
211
+ * Downstream consumers (notably Cube's native `getFinalQueryResult`
212
+ * transform in `@cubejs-backend/native`) compile their row→member
213
+ * extraction plan from the keys present in **row 0**. A sparse row 0
214
+ * causes Cube to drop the column from every row in the response — even
215
+ * rows that DO have the value. Reproduced empirically against the
216
+ * `configs` collection (`SELECT id, agent_display_name FROM configs ...
217
+ * ORDER BY agent_display_name ASC LIMIT 500` → 500 rows, all missing
218
+ * `configs.agent_display_name`, even though 497/500 source docs have it).
219
+ *
220
+ * **Fix shape.** Take the union of keys across all rows and null-fill
221
+ * each row so every row has every key. We can't simply look at `row[0]`
222
+ * because that's the symptom. Iteration is O(rows × cols). At the
223
+ * `MAX_ROWS=100000` pre-agg cap with a ~20-column projection this is
224
+ * roughly 2M property-existence checks and stays under 100ms in practice.
225
+ *
226
+ * `downloadQueryResults` uses the authoritative type list instead of
227
+ * a key union (the union would miss columns absent from every row); that
228
+ * variant is implemented inline at the call site, not via this helper.
229
+ */
230
+ declare function normalizeRowShape<R>(rows: Array<Record<string, unknown>>): R[];
231
+ /**
232
+ * Test-only export. Lets `tests/unit/driver.test.ts` exercise the
233
+ * key-union null-fill in isolation without spinning up the driver.
234
+ * Not part of the public API.
235
+ */
236
+ export declare const _normalizeRowShapeForTests: typeof normalizeRowShape;
237
+ export {};
238
+ //# sourceMappingURL=MongoSqlDriver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MongoSqlDriver.d.ts","sourceRoot":"","sources":["../src/MongoSqlDriver.ts"],"names":[],"mappings":"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"}