@cubejs-backend/oracle-driver 1.6.64 → 1.6.65
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 +6 -0
- package/driver/OracleDriver.js +153 -36
- package/package.json +7 -3
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,12 @@
|
|
|
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.65](https://github.com/cube-js/cube/compare/v1.6.64...v1.6.65) (2026-07-01)
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
- **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))
|
|
11
|
+
|
|
6
12
|
## [1.6.64](https://github.com/cube-js/cube/compare/v1.6.63...v1.6.64) (2026-06-25)
|
|
7
13
|
|
|
8
14
|
**Note:** Version bump only for package @cubejs-backend/oracle-driver
|
package/driver/OracleDriver.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
|
|
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
|
-
|
|
134
|
-
|
|
194
|
+
static normalizeParams(query, values) {
|
|
195
|
+
if (!values || values.length === 0) {
|
|
196
|
+
return { sql: query, binds: {} };
|
|
197
|
+
}
|
|
135
198
|
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
5
|
+
"version": "1.6.65",
|
|
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.
|
|
16
|
+
"@cubejs-backend/base-driver": "1.6.65",
|
|
17
|
+
"@cubejs-backend/shared": "1.6.65",
|
|
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": "
|
|
33
|
+
"gitHead": "64502a0ff14f50aa71f4577eac8fc2908419be87"
|
|
30
34
|
}
|