@cubejs-backend/dremio-driver 1.4.2 → 1.4.4

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,17 @@
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.4.4](https://github.com/cube-js/cube/compare/v1.4.3...v1.4.4) (2026-07-28)
7
+
8
+ ### Bug Fixes
9
+
10
+ - **pinot/dremio/ksql/databriks/hive/jdbc-driver:** Сorrect SQL parameter escaping ([#11374](https://github.com/cube-js/cube/issues/11374)) ([e31ccf0](https://github.com/cube-js/cube/commit/e31ccf0e040bf6df0a0898797552302352b0aa99))
11
+ - **schema-compiler:** Escape LIKE wildcards on Tesseract for all dialects ([0432799](https://github.com/cube-js/cube/commit/04327997e013d11432b007710db51913875680ab)), closes [#11345](https://github.com/cube-js/cube/issues/11345) [#11345](https://github.com/cube-js/cube/issues/11345) [#10862](https://github.com/cube-js/cube/issues/10862) [#11345](https://github.com/cube-js/cube/issues/11345) [#11345](https://github.com/cube-js/cube/issues/11345) [#11345](https://github.com/cube-js/cube/issues/11345) [#11345](https://github.com/cube-js/cube/issues/11345) [opensearch-project/sql#779](https://github.com/opensearch-project/sql/issues/779) [#11345](https://github.com/cube-js/cube/issues/11345)
12
+
13
+ ## [1.4.3](https://github.com/cube-js/cube/compare/v1.4.2...v1.4.3) (2026-06-08)
14
+
15
+ **Note:** Version bump only for package @cubejs-backend/dremio-driver
16
+
6
17
  ## [1.4.2](https://github.com/cube-js/cube/compare/v1.4.1...v1.4.2) (2025-12-16)
7
18
 
8
19
  **Note:** Version bump only for package @cubejs-backend/dremio-driver
@@ -7,10 +7,10 @@
7
7
  const {
8
8
  getEnv,
9
9
  assertDataSource,
10
+ formatAnsi,
10
11
  pausePromise,
11
12
  } = require('@cubejs-backend/shared');
12
13
  const axios = require('axios');
13
- const SqlString = require('sqlstring');
14
14
  const { BaseDriver } = require('@cubejs-backend/base-driver');
15
15
  const DremioQuery = require('./DremioQuery');
16
16
 
@@ -18,7 +18,7 @@ const DremioQuery = require('./DremioQuery');
18
18
  // @see https://docs.dremio.com/rest-api/jobs/get-job.html
19
19
  const DREMIO_JOB_LIMIT = 500;
20
20
 
21
- const applyParams = (query, params) => SqlString.format(query, params);
21
+ const applyParams = (query, params) => formatAnsi(query, params);
22
22
 
23
23
  /**
24
24
  * Dremio driver class.
@@ -207,12 +207,7 @@ class DremioDriver extends BaseDriver {
207
207
  }
208
208
 
209
209
  async query(query, values) {
210
- const queryString = applyParams(
211
- query,
212
- (values || []).map(s => (typeof s === 'string' ? {
213
- toSqlString: () => SqlString.escape(s).replace(/\\\\([_%])/g, '\\$1').replace(/\\'/g, '\'\'')
214
- } : s))
215
- );
210
+ const queryString = applyParams(query, values || []);
216
211
 
217
212
  await this.getToken();
218
213
  const jobId = await this.executeQuery(queryString);
@@ -280,3 +275,4 @@ class DremioDriver extends BaseDriver {
280
275
  }
281
276
 
282
277
  module.exports = DremioDriver;
278
+ module.exports.applyParams = applyParams;
@@ -13,10 +13,18 @@ const GRANULARITY_TO_INTERVAL = {
13
13
  };
14
14
 
15
15
  class DremioFilter extends BaseFilter {
16
+ /**
17
+ * ILIKE(<expression>, <pattern> [, <escape_character>]) → boolean
18
+ * Dremio's ILIKE is a function, not an operator, so `NOT` negates the call from the outside
19
+ * and the escape character is its optional third argument — without it
20
+ * BaseFilter.escapeWildcardChars has no effect, as ILIKE has no default escape character.
21
+ * The 3-arg form is documented for Dremio Cloud and for Software down to 24.3.x.
22
+ * @see https://docs.dremio.com/cloud/reference/sql/sql-functions/functions/ILIKE/
23
+ */
16
24
  likeIgnoreCase(column, not, param, type) {
17
25
  const p = (!type || type === 'contains' || type === 'ends') ? '%' : '';
18
26
  const s = (!type || type === 'contains' || type === 'starts') ? '%' : '';
19
- return ` ILIKE (${column}${not ? ' NOT' : ''}, CONCAT('${p}', ${this.allocateParam(param)}, '${s}'))`;
27
+ return `${not ? 'NOT ' : ''}ILIKE(${column}, CONCAT('${p}', ${this.allocateParam(param)}, '${s}'), '\\')`;
20
28
  }
21
29
 
22
30
  castParameter() {
@@ -165,6 +173,10 @@ class DremioQuery extends BaseQuery {
165
173
  templates.functions.DATEDIFF = 'DATE_DIFF(DATE, DATE_TRUNC(\'{{ date_part }}\', {{ args[1] }}), DATE_TRUNC(\'{{ date_part }}\', {{ args[2] }}))';
166
174
  templates.expressions.interval_single_date_part = 'CAST({{ num }} as INTERVAL {{ date_part }})';
167
175
  templates.quotes.identifiers = '"';
176
+ // Dremio's ILIKE is a function whose optional third argument is the escape character, so
177
+ // `filters.like_escape_char` cannot be emitted as an ESCAPE clause. Mirror
178
+ // DremioFilter.likeIgnoreCase instead.
179
+ templates.tesseract.ilike = '{% if negated %}NOT {% endif %}ILIKE({{ expr }}, {{ pattern }}, \'\\\')';
168
180
  return templates;
169
181
  }
170
182
  }
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@cubejs-backend/dremio-driver",
3
3
  "description": "Cube.js Dremio driver",
4
4
  "author": "Cube Dev, Inc.",
5
- "version": "1.4.2",
5
+ "version": "1.4.4",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "https://github.com/cube-js/cube.git",
@@ -16,21 +16,21 @@
16
16
  "tsc": "tsc",
17
17
  "watch": "tsc -w",
18
18
  "test": "yarn integration",
19
+ "unit": "jest --verbose dist/test/unit",
19
20
  "integration": "npm run integration:dremio",
20
21
  "integration:dremio": "jest --verbose dist/test",
21
22
  "lint": "eslint driver/*.js",
22
23
  "lint:fix": "eslint driver/*.js"
23
24
  },
24
25
  "dependencies": {
25
- "@cubejs-backend/base-driver": "1.4.2",
26
- "@cubejs-backend/schema-compiler": "1.4.2",
27
- "@cubejs-backend/shared": "1.4.2",
28
- "axios": "^1.8.3",
29
- "sqlstring": "^2.3.1"
26
+ "@cubejs-backend/base-driver": "1.4.4",
27
+ "@cubejs-backend/schema-compiler": "1.4.4",
28
+ "@cubejs-backend/shared": "1.4.4",
29
+ "axios": "^1.8.3"
30
30
  },
31
31
  "devDependencies": {
32
- "@cubejs-backend/linter": "1.4.2",
33
- "@cubejs-backend/testing-shared": "1.4.2",
32
+ "@cubejs-backend/linter": "1.4.4",
33
+ "@cubejs-backend/testing-shared": "1.4.4",
34
34
  "jest": "^29"
35
35
  },
36
36
  "license": "Apache-2.0",
@@ -40,5 +40,5 @@
40
40
  "eslintConfig": {
41
41
  "extends": "../cubejs-linter"
42
42
  },
43
- "gitHead": "4e47fc6ca2fe0bb4b921e64448c647b3c0a8707a"
43
+ "gitHead": "1675e538f739774b1335e2e0baed8b29040b9c4d"
44
44
  }
@@ -118,4 +118,49 @@ cube(\`sales\`, {
118
118
  '("sales".sales_datetime >= TO_TIMESTAMP(?, \'YYYY-MM-DD"T"HH24:MI:SS.FFF\') AND "sales".sales_datetime <= TO_TIMESTAMP(?, \'YYYY-MM-DD"T"HH24:MI:SS.FFF\'))'
119
119
  );
120
120
  }));
121
+
122
+ const likeQuery = (operator: string, value: string) => new DremioQuery(
123
+ { joinGraph, cubeEvaluator, compiler },
124
+ {
125
+ measures: ['sales.count'],
126
+ filters: [
127
+ {
128
+ member: 'sales.category',
129
+ operator,
130
+ values: [value],
131
+ },
132
+ ],
133
+ }
134
+ );
135
+
136
+ // Dremio's ILIKE is a function: `NOT` cannot sit inside the argument list and the escape
137
+ // character is the third argument, without which the `\` BaseFilter.escapeWildcardChars binds
138
+ // would stay a literal backslash
139
+ it('should call ILIKE as a function with an escape character', () => compiler.compile().then(() => {
140
+ const [sql, params] = likeQuery('contains', 'demo').buildSqlAndParams();
141
+
142
+ expect(sql).toContain('ILIKE("sales".category, CONCAT(\'%\', ?, \'%\'), \'\\\')');
143
+ expect(params).toEqual(['demo']);
144
+ }));
145
+
146
+ it('should negate the whole ILIKE call', () => compiler.compile().then(() => {
147
+ const [sql] = likeQuery('notContains', 'demo').buildSqlAndParams();
148
+
149
+ expect(sql).toContain('NOT ILIKE("sales".category, CONCAT(\'%\', ?, \'%\'), \'\\\')');
150
+ }));
151
+
152
+ it('should escape LIKE wildcards in filter parameters', () => compiler.compile().then(() => {
153
+ expect(likeQuery('contains', 'a_b%').buildSqlAndParams()[1]).toEqual(['a\\_b\\%']);
154
+ expect(likeQuery('startsWith', '100%').buildSqlAndParams()[1]).toEqual(['100\\%']);
155
+ expect(likeQuery('endsWith', 'c:\\users').buildSqlAndParams()[1]).toEqual(['c:\\\\users']);
156
+ }));
157
+
158
+ it('should pass the escape character to ILIKE for the native planner too', () => compiler.compile().then(() => {
159
+ const templates = likeQuery('contains', 'demo').sqlTemplates();
160
+
161
+ expect(templates.filters.like_escape_char).toEqual('\\');
162
+ expect(templates.tesseract.ilike).toEqual(
163
+ '{% if negated %}NOT {% endif %}ILIKE({{ expr }}, {{ pattern }}, \'\\\')'
164
+ );
165
+ }));
121
166
  });
@@ -0,0 +1,88 @@
1
+ /* eslint-disable quotes */
2
+ const { applyParams } = require('../../../driver/DremioDriver');
3
+
4
+ // Dremio is a Calcite-based, standard-SQL engine: a quote inside a string
5
+ // literal is escaped by doubling it and a backslash is plain data.
6
+ describe('DremioDriver SQL parameter escaping', () => {
7
+ it('preserves LIKE escape sequences emitted by the schema compiler', () => {
8
+ const sql = applyParams(
9
+ `SELECT * FROM orders WHERE LOWER(name) LIKE '%' || LOWER(?) || '%' ESCAPE '\\'`,
10
+ ['new\\_order\\%'],
11
+ );
12
+
13
+ expect(sql).toBe(
14
+ `SELECT * FROM orders WHERE LOWER(name) LIKE '%' || LOWER('new\\_order\\%') || '%' ESCAPE '\\'`
15
+ );
16
+ });
17
+
18
+ it('does not double literal backslashes in LIKE parameters', () => {
19
+ const sql = applyParams(
20
+ `SELECT * FROM orders WHERE LOWER(name) LIKE '%' || LOWER(?) || '%' ESCAPE '\\'`,
21
+ ['folder\\\\name'],
22
+ );
23
+
24
+ expect(sql).toBe(
25
+ `SELECT * FROM orders WHERE LOWER(name) LIKE '%' || LOWER('folder\\\\name') || '%' ESCAPE '\\'`
26
+ );
27
+ });
28
+
29
+ it('doubles quotes so a value cannot break out of the literal', () => {
30
+ const sql = applyParams(
31
+ 'SELECT * FROM orders WHERE name = ?',
32
+ [`o'reilly'); DROP TABLE orders; --`],
33
+ );
34
+
35
+ expect(sql).toBe(
36
+ `SELECT * FROM orders WHERE name = 'o''reilly''); DROP TABLE orders; --'`
37
+ );
38
+ });
39
+
40
+ it('keeps the literal closed for a backslash-then-quote payload', () => {
41
+ // In a backslash-escaping dialect `\'` would smuggle a quote through; on
42
+ // Dremio the backslash is data and only the quote needs doubling.
43
+ const sql = applyParams(
44
+ 'SELECT * FROM orders WHERE name = ?',
45
+ [`foo\\' OR 1=1 --`],
46
+ );
47
+
48
+ expect(sql).toBe(`SELECT * FROM orders WHERE name = 'foo\\'' OR 1=1 --'`);
49
+ });
50
+
51
+ it('keeps the literal closed for a value ending in a backslash', () => {
52
+ const sql = applyParams(
53
+ 'SELECT * FROM orders WHERE name = ? AND status = ?',
54
+ ['payload\\', 'new'],
55
+ );
56
+
57
+ expect(sql).toBe(`SELECT * FROM orders WHERE name = 'payload\\' AND status = 'new'`);
58
+ });
59
+
60
+ it('keeps a literal percent sign in an equality parameter verbatim', () => {
61
+ const sql = applyParams(
62
+ 'SELECT * FROM orders WHERE discount_label = ?',
63
+ ['100% cotton'],
64
+ );
65
+
66
+ expect(sql).toBe(`SELECT * FROM orders WHERE discount_label = '100% cotton'`);
67
+ });
68
+
69
+ it('escapes every element of an array parameter', () => {
70
+ const sql = applyParams(
71
+ 'SELECT * FROM orders WHERE status IN (?)',
72
+ [[`it's`, 'b']],
73
+ );
74
+
75
+ expect(sql).toBe(`SELECT * FROM orders WHERE status IN ('it''s', 'b')`);
76
+ });
77
+
78
+ it('substitutes multiple placeholders in order', () => {
79
+ const sql = applyParams(
80
+ `SELECT * FROM orders WHERE LOWER(name) LIKE '%' || LOWER(?) || '%' ESCAPE '\\' AND status = ? AND amount > ?`,
81
+ ['pending\\_review', 'new', 100],
82
+ );
83
+
84
+ expect(sql).toBe(
85
+ `SELECT * FROM orders WHERE LOWER(name) LIKE '%' || LOWER('pending\\_review') || '%' ESCAPE '\\' AND status = 'new' AND amount > 100`
86
+ );
87
+ });
88
+ });