@cubejs-backend/oracle-driver 1.6.64 → 1.6.66

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/CHANGELOG.md CHANGED
@@ -3,6 +3,16 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [1.6.66](https://github.com/cube-js/cube/compare/v1.6.65...v1.6.66) (2026-07-03)
7
+
8
+ **Note:** Version bump only for package @cubejs-backend/oracle-driver
9
+
10
+ ## [1.6.65](https://github.com/cube-js/cube/compare/v1.6.64...v1.6.65) (2026-07-01)
11
+
12
+ ### Features
13
+
14
+ - **oracle-driver:** Improvements & fixes, fix [#10834](https://github.com/cube-js/cube/issues/10834) ([#11174](https://github.com/cube-js/cube/issues/11174)) ([9da8e07](https://github.com/cube-js/cube/commit/9da8e076897071f204711456b92ff69150547d9c))
15
+
6
16
  ## [1.6.64](https://github.com/cube-js/cube/compare/v1.6.63...v1.6.64) (2026-06-25)
7
17
 
8
18
  **Note:** Version bump only for package @cubejs-backend/oracle-driver
@@ -7,11 +7,28 @@
7
7
  const {
8
8
  getEnv,
9
9
  assertDataSource,
10
+ Pool,
10
11
  } = require('@cubejs-backend/shared');
11
- const { BaseDriver, TableColumn } = require('@cubejs-backend/base-driver');
12
+ const { BaseDriver, TableColumn, createPoolName } = require('@cubejs-backend/base-driver');
12
13
  const oracledb = require('oracledb');
13
14
  const { reduce } = require('ramda');
14
15
 
16
+ // Maps Oracle `metaData.dbTypeName` strings to Cube generic types. NUMBER and the
17
+ // TIMESTAMP* family are handled separately (scale-based / prefix match) below.
18
+ const OracleTypeToGenericType = {
19
+ varchar2: 'text',
20
+ nvarchar2: 'text',
21
+ char: 'text',
22
+ nchar: 'text',
23
+ clob: 'text',
24
+ nclob: 'text',
25
+ long: 'text',
26
+ binary_float: 'float',
27
+ binary_double: 'double',
28
+ date: 'timestamp',
29
+ 'number': 'decimal',
30
+ };
31
+
15
32
  const sortByKeys = (unordered) => {
16
33
  const ordered = {};
17
34
 
@@ -43,20 +60,11 @@ const reduceCb = (result, i) => {
43
60
  return sortByKeys(result);
44
61
  };
45
62
 
46
- /**
47
- * Oracle driver class.
48
- */
49
63
  class OracleDriver extends BaseDriver {
50
- /**
51
- * Returns default concurrency value.
52
- */
53
64
  static getDefaultConcurrency() {
54
65
  return 2;
55
66
  }
56
67
 
57
- /**
58
- * Class constructor.
59
- */
60
68
  constructor(config = {}) {
61
69
  super({
62
70
  testConnectionTimeout: config.testConnectionTimeout,
@@ -72,20 +80,51 @@ class OracleDriver extends BaseDriver {
72
80
  this.db.partRows = 100000;
73
81
  this.db.maxRows = 100000;
74
82
  this.db.prefetchRows = 500;
83
+
84
+ const { maxPoolSize, pool, ...connectionConfig } = config;
85
+
75
86
  this.config = {
76
87
  user: getEnv('dbUser', { dataSource, preAggregations }),
77
88
  password: getEnv('dbPass', { dataSource, preAggregations }),
78
89
  db: getEnv('dbName', { dataSource, preAggregations }),
79
90
  host: getEnv('dbHost', { dataSource, preAggregations }),
80
91
  port: getEnv('dbPort', { dataSource, preAggregations }) || 1521,
81
- poolMin: 0,
82
- poolMax:
83
- config.maxPoolSize ||
84
- getEnv('dbMaxPoolSize', { dataSource, preAggregations }) ||
85
- 50,
86
- ...config
92
+ ...connectionConfig,
87
93
  };
88
94
  this.config.connectionString = this.config.connectionString || `${this.config.host}:${this.config.port}/${this.config.db}`;
95
+
96
+ const poolName = createPoolName('oracle', dataSource, preAggregations);
97
+ this.pool = new Pool(poolName, {
98
+ create: async () => {
99
+ const connection = await this.db.getConnection(this.config);
100
+ await OracleDriver.initConnection(connection);
101
+
102
+ return connection;
103
+ },
104
+ validate: async (connection) => {
105
+ try {
106
+ await connection.ping();
107
+ } catch (e) {
108
+ this.databasePoolError(e);
109
+ return false;
110
+ }
111
+
112
+ return true;
113
+ },
114
+ destroy: (connection) => connection.close(),
115
+ }, {
116
+ min: 0,
117
+ max:
118
+ maxPoolSize ||
119
+ getEnv('dbMaxPoolSize', { dataSource, preAggregations }) ||
120
+ 50,
121
+ evictionRunIntervalMillis: 10000,
122
+ softIdleTimeoutMillis: 30000,
123
+ idleTimeoutMillis: 30000,
124
+ testOnBorrow: true,
125
+ acquireTimeoutMillis: 20000,
126
+ ...pool,
127
+ });
89
128
  }
90
129
 
91
130
  async tablesSchema() {
@@ -110,12 +149,33 @@ class OracleDriver extends BaseDriver {
110
149
  return reduce(reduceCb, {}, data);
111
150
  }
112
151
 
113
- async getConnectionFromPool() {
114
- if (!this.pool) {
115
- this.pool = await this.db.createPool(this.config);
116
- }
152
+ /**
153
+ * Runs once per pooled session. Aligns the session NLS formats with the ISO-ish
154
+ * date strings Cube binds, so implicit string→DATE/TIMESTAMP conversions (e.g.
155
+ * the native planner's `CAST(? AS TIMESTAMP)` over a 'YYYY-MM-DD' filter bound)
156
+ * parse instead of failing with ORA-01843 under Oracle's default NLS. Explicit
157
+ * TO_DATE/TO_TIMESTAMP calls carry their own masks and are unaffected.
158
+ * @protected
159
+ */
160
+ static async initConnection(connection) {
161
+ await connection.execute(
162
+ "ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD' NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD' NLS_TIMESTAMP_TZ_FORMAT = 'YYYY-MM-DD HH24:MI:SS.FF TZH:TZM'"
163
+ );
164
+ }
165
+
166
+ /**
167
+ * Acquires a connection from the pool, runs `fn`, and always releases the
168
+ * connection back to the pool (on both success and failure).
169
+ * @protected
170
+ */
171
+ async withConnection(fn) {
172
+ const connection = await this.pool.acquire();
117
173
 
118
- return this.pool.getConnection()
174
+ try {
175
+ return await fn(connection);
176
+ } finally {
177
+ await this.pool.release(connection);
178
+ }
119
179
  }
120
180
 
121
181
  async testConnection() {
@@ -127,28 +187,84 @@ class OracleDriver extends BaseDriver {
127
187
  throw new Error('Oracle can not work with table names longer than 128 symbols. ' +
128
188
  `Consider using the 'sqlAlias' attribute in your cube definition for ${quotedTableName}.`);
129
189
  }
190
+
130
191
  return super.createTable(quotedTableName, columns);
131
192
  }
132
193
 
133
- async query(query, values) {
134
- const conn = await this.getConnectionFromPool();
194
+ static normalizeParams(query, values) {
195
+ if (!values || values.length === 0) {
196
+ return { sql: query, binds: {} };
197
+ }
135
198
 
136
- try {
137
- const res = await conn.execute(query, values || {});
199
+ const binds = {};
200
+ const valueToName = new Map();
201
+ let idx = 0;
202
+ let nextName = 0;
203
+
204
+ // `:"?"` must be matched as a whole before a lone `?`, so it appears first
205
+ // in the alternation; since it starts with `:`, its inner `?` is consumed
206
+ // as part of the match and never matched again on its own.
207
+ //
208
+ // Placeholders carrying the same value share a single named bind. This is
209
+ // semantically identical (the same value is bound) and keeps repeated
210
+ // expressions textually identical across clauses — required by Oracle, which
211
+ // otherwise rejects e.g. a CASE expression in both SELECT and GROUP BY when
212
+ // its param renders as two different bind names (ORA-00979).
213
+ const sql = query.replace(/:"\?"|\?/g, () => {
214
+ const value = values[idx];
215
+ idx += 1;
216
+ // A Map distinguishes values by SameValueZero, so 1 and '1' stay separate;
217
+ // the raw value works as the key without stringifying.
218
+ let name = valueToName.get(value);
219
+ if (name === undefined) {
220
+ name = `cb_param_${nextName}`;
221
+ nextName += 1;
222
+ valueToName.set(value, name);
223
+ binds[name] = value;
224
+ }
225
+ return `:${name}`;
226
+ });
227
+
228
+ return { sql, binds };
229
+ }
230
+
231
+ async query(query, values) {
232
+ return this.withConnection(async (conn) => {
233
+ const { sql, binds } = OracleDriver.normalizeParams(query, values);
234
+ const res = await conn.execute(sql, binds);
138
235
  return res && res.rows;
139
- } catch (e) {
140
- throw (e);
141
- } finally {
142
- try {
143
- await conn.close();
144
- } catch (e) {
145
- throw e;
236
+ });
237
+ }
238
+
239
+ static metaDataToColumnTypes(metaData) {
240
+ return (metaData || []).map((column) => {
241
+ const dbTypeName = (column.dbTypeName || '').toLowerCase();
242
+ let type = 'text';
243
+
244
+ if (dbTypeName.startsWith('timestamp')) {
245
+ type = 'timestamp';
246
+ } else {
247
+ type = OracleTypeToGenericType[dbTypeName] || 'text';
146
248
  }
147
- }
249
+
250
+ return { name: column.name, type };
251
+ });
252
+ }
253
+
254
+ async downloadQueryResults(query, values, _options) {
255
+ return this.withConnection(async (conn) => {
256
+ const { sql, binds } = OracleDriver.normalizeParams(query, values);
257
+ const res = await conn.execute(sql, binds);
258
+ return {
259
+ rows: (res && res.rows) || [],
260
+ types: OracleDriver.metaDataToColumnTypes(res && res.metaData),
261
+ };
262
+ });
148
263
  }
149
264
 
150
- release() {
151
- return this.pool && this.pool.close();
265
+ async release() {
266
+ await this.pool.drain();
267
+ await this.pool.clear();
152
268
  }
153
269
 
154
270
  readOnly() {
@@ -156,7 +272,8 @@ class OracleDriver extends BaseDriver {
156
272
  }
157
273
 
158
274
  wrapQueryWithLimit(query) {
159
- query.query = `SELECT * FROM (${query.query}) AS t WHERE ROWNUM <= ${query.limit}`;
275
+ // Oracle forbids the `AS` keyword for table/subquery aliases.
276
+ query.query = `SELECT * FROM (${query.query}) t WHERE ROWNUM <= ${query.limit}`;
160
277
  }
161
278
  }
162
279
 
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@cubejs-backend/oracle-driver",
3
3
  "description": "Cube.js oracle database driver",
4
4
  "author": "Cube Dev, Inc.",
5
- "version": "1.6.64",
5
+ "version": "1.6.66",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "https://github.com/cube-js/cube.git",
@@ -13,9 +13,13 @@
13
13
  },
14
14
  "main": "driver/OracleDriver.js",
15
15
  "dependencies": {
16
- "@cubejs-backend/base-driver": "1.6.64",
16
+ "@cubejs-backend/base-driver": "1.6.66",
17
+ "@cubejs-backend/shared": "1.6.66",
17
18
  "ramda": "^0.27.0"
18
19
  },
20
+ "devDependencies": {
21
+ "@types/oracledb": "^6.2.3"
22
+ },
19
23
  "optionalDependencies": {
20
24
  "oracledb": "^6.2.0"
21
25
  },
@@ -26,5 +30,5 @@
26
30
  "publishConfig": {
27
31
  "access": "public"
28
32
  },
29
- "gitHead": "8d76ec23f35cd64663e70952bedb89a1423daa21"
33
+ "gitHead": "ea64dffa4d2a5c9422928ae8e96a76196bd9d5ce"
30
34
  }