Skip to content

Commit efde731

Browse files
authored
feat: Native X-Pack SQL ElasticSearch Driver (#551)
* Implemented DATE_TRUNC * Typing Fix for QueryFilter interface * Initial Modifications for Native ElasticSearch SQL * Auto stash before cherry pick of "Typing Fix for QueryFilter interface" * Using Match instead of like in Query * Updated Driver Dependencies * Revert Code Formatting, while keeping additions * Reverted dimension typing * Renamed elasticsearchcloud to elasticsearch
1 parent d52ede5 commit efde731

File tree

4 files changed

+124
-1
lines changed

4 files changed

+124
-1
lines changed

packages/cubejs-elasticsearch-driver/driver/ElasticSearchDriver.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,17 @@ const BaseDriver = require('@cubejs-backend/query-orchestrator/driver/BaseDriver
55
class ElasticSearchDriver extends BaseDriver {
66
constructor(config) {
77
super();
8+
9+
// TODO: This config applies to AWS ES, Native ES and OpenDistro ES
10+
// All 3 have different dialects according to their respective documentation
811
this.config = {
912
url: process.env.CUBEJS_DB_URL,
1013
openDistro:
1114
(process.env.CUBEJS_DB_ELASTIC_OPENDISTRO || 'false').toLowerCase() === 'true' ||
1215
process.env.CUBEJS_DB_TYPE === 'odelasticsearch',
1316
...config
1417
};
15-
this.client = new Client({ node: this.config.url });
18+
this.client = new Client({ node: this.config.url, cloud: this.config.cloud });
1619
this.sqlClient = this.config.openDistro ? new Client({ node: `${this.config.url}/_opendistro` }) : this.client;
1720
}
1821

@@ -30,6 +33,15 @@ class ElasticSearchDriver extends BaseDriver {
3033
}
3134
})).body;
3235

36+
// TODO: Clean this up, will need a better identifier than the cloud setting
37+
if (this.config.cloud) {
38+
const compiled = result.rows.map(
39+
r => result.columns.reduce((prev, cur, idx) => ({ ...prev, [cur.name]: r[idx] }), {})
40+
);
41+
42+
return compiled;
43+
}
44+
3345
return result && result.aggregations && this.traverseAggregations(result.aggregations);
3446
} catch (e) {
3547
if (e.body) {
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/* eslint-disable max-classes-per-file */
2+
// const moment = require('moment-timezone');
3+
const R = require("ramda");
4+
5+
const BaseQuery = require("./BaseQuery");
6+
const BaseFilter = require("./BaseFilter");
7+
8+
const GRANULARITY_TO_INTERVAL = {
9+
day: date => `DATE_TRUNC('day', ${date}::datetime)`,
10+
week: date => `DATE_TRUNC('week', ${date}::datetime)`,
11+
hour: date => `DATE_TRUNC('hour', ${date}::datetime)`,
12+
minute: date => `DATE_TRUNC('minute', ${date}::datetime)`,
13+
second: date => `DATE_TRUNC('second', ${date}::datetime)`,
14+
month: date => `DATE_TRUNC('month', ${date}::datetime)`,
15+
year: date => `DATE_TRUNC('year', ${date}::datetime)`
16+
};
17+
18+
class ElasticSearchQueryFilter extends BaseFilter {
19+
likeIgnoreCase(column, not) {
20+
return `${not ? " NOT" : ""} MATCH(${column}, ?, 'fuzziness=AUTO:1,5')`;
21+
}
22+
}
23+
24+
class ElasticSearchQuery extends BaseQuery {
25+
newFilter(filter) {
26+
return new ElasticSearchQueryFilter(this, filter);
27+
}
28+
29+
convertTz(field) {
30+
return `${field}`; // TODO
31+
}
32+
33+
timeStampCast(value) {
34+
return `${value}`;
35+
}
36+
37+
dateTimeCast(value) {
38+
return `${value}`; // TODO
39+
}
40+
41+
subtractInterval(date, interval) {
42+
// TODO: Test this, note sure how value gets populated here
43+
return `${date} - INTERVAL ${interval}`;
44+
}
45+
46+
addInterval(date, interval) {
47+
// TODO: Test this, note sure how value gets populated here
48+
return `${date} + INTERVAL ${interval}`;
49+
}
50+
51+
timeGroupedColumn(granularity, dimension) {
52+
return GRANULARITY_TO_INTERVAL[granularity](dimension);
53+
}
54+
55+
groupByClause() {
56+
const dimensionsForSelect = this.dimensionsForSelect();
57+
const dimensionColumns = R.flatten(
58+
dimensionsForSelect.map(s => s.selectColumns() && s.dimensionSql())
59+
).filter(s => !!s);
60+
61+
return dimensionColumns.length ? ` GROUP BY ${dimensionColumns.join(", ")}` : "";
62+
}
63+
64+
orderHashToString(hash) {
65+
if (!hash || !hash.id) {
66+
return null;
67+
}
68+
69+
const fieldAlias = this.getFieldAlias(hash.id);
70+
71+
if (fieldAlias === null) {
72+
return null;
73+
}
74+
75+
const direction = hash.desc ? "DESC" : "ASC";
76+
return `${fieldAlias} ${direction}`;
77+
}
78+
79+
getFieldAlias(id) {
80+
const equalIgnoreCase = (a, b) => typeof a === "string" &&
81+
typeof b === "string" &&
82+
a.toUpperCase() === b.toUpperCase();
83+
84+
let field;
85+
86+
field = this.dimensionsForSelect().find(d => equalIgnoreCase(d.dimension, id));
87+
88+
if (field) {
89+
return field.dimensionSql();
90+
}
91+
92+
field = this.measures.find(
93+
d => equalIgnoreCase(d.measure, id) || equalIgnoreCase(d.expressionName, id)
94+
);
95+
96+
if (field) {
97+
return field.aliasName(); // TODO isn't supported
98+
}
99+
100+
return null;
101+
}
102+
103+
escapeColumnName(name) {
104+
return `${name}`; // TODO
105+
}
106+
}
107+
108+
module.exports = ElasticSearchQuery;

packages/cubejs-schema-compiler/adapter/QueryBuilder.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const hive = require('./HiveQuery');
1212
const oracle = require('./OracleQuery');
1313
const sqlite = require('./SqliteQuery');
1414
const odelasticsearch = require('./OpenDistroElasticSearchQuery');
15+
const elasticsearch = require('./ElasticSearchQuery');
1516

1617
const ADAPTERS = {
1718
postgres,
@@ -30,6 +31,7 @@ const ADAPTERS = {
3031
oracle,
3132
sqlite,
3233
odelasticsearch,
34+
elasticsearch
3335
};
3436
exports.query = (compilers, dbType, queryOptions) => {
3537
if (!ADAPTERS[dbType]) {

packages/cubejs-server-core/core/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ const DriverDependencies = {
3030
oracle: '@cubejs-backend/oracle-driver',
3131
sqlite: '@cubejs-backend/sqlite-driver',
3232
odelasticsearch: '@cubejs-backend/elasticsearch-driver',
33+
elasticsearch: '@cubejs-backend/elasticsearch-driver',
3334
};
3435

3536
const checkEnvForPlaceholders = () => {

0 commit comments

Comments
 (0)