Skip to content

Commit

Permalink
feat: Native X-Pack SQL ElasticSearch Driver (#551)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
chadfaurie authored Apr 4, 2020
1 parent d52ede5 commit efde731
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,17 @@ const BaseDriver = require('@cubejs-backend/query-orchestrator/driver/BaseDriver
class ElasticSearchDriver extends BaseDriver {
constructor(config) {
super();

// TODO: This config applies to AWS ES, Native ES and OpenDistro ES
// All 3 have different dialects according to their respective documentation
this.config = {
url: process.env.CUBEJS_DB_URL,
openDistro:
(process.env.CUBEJS_DB_ELASTIC_OPENDISTRO || 'false').toLowerCase() === 'true' ||
process.env.CUBEJS_DB_TYPE === 'odelasticsearch',
...config
};
this.client = new Client({ node: this.config.url });
this.client = new Client({ node: this.config.url, cloud: this.config.cloud });
this.sqlClient = this.config.openDistro ? new Client({ node: `${this.config.url}/_opendistro` }) : this.client;
}

Expand All @@ -30,6 +33,15 @@ class ElasticSearchDriver extends BaseDriver {
}
})).body;

// TODO: Clean this up, will need a better identifier than the cloud setting
if (this.config.cloud) {
const compiled = result.rows.map(
r => result.columns.reduce((prev, cur, idx) => ({ ...prev, [cur.name]: r[idx] }), {})
);

return compiled;
}

return result && result.aggregations && this.traverseAggregations(result.aggregations);
} catch (e) {
if (e.body) {
Expand Down
108 changes: 108 additions & 0 deletions packages/cubejs-schema-compiler/adapter/ElasticSearchQuery.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/* eslint-disable max-classes-per-file */
// const moment = require('moment-timezone');
const R = require("ramda");

const BaseQuery = require("./BaseQuery");
const BaseFilter = require("./BaseFilter");

const GRANULARITY_TO_INTERVAL = {
day: date => `DATE_TRUNC('day', ${date}::datetime)`,
week: date => `DATE_TRUNC('week', ${date}::datetime)`,
hour: date => `DATE_TRUNC('hour', ${date}::datetime)`,
minute: date => `DATE_TRUNC('minute', ${date}::datetime)`,
second: date => `DATE_TRUNC('second', ${date}::datetime)`,
month: date => `DATE_TRUNC('month', ${date}::datetime)`,
year: date => `DATE_TRUNC('year', ${date}::datetime)`
};

class ElasticSearchQueryFilter extends BaseFilter {
likeIgnoreCase(column, not) {
return `${not ? " NOT" : ""} MATCH(${column}, ?, 'fuzziness=AUTO:1,5')`;
}
}

class ElasticSearchQuery extends BaseQuery {
newFilter(filter) {
return new ElasticSearchQueryFilter(this, filter);
}

convertTz(field) {
return `${field}`; // TODO
}

timeStampCast(value) {
return `${value}`;
}

dateTimeCast(value) {
return `${value}`; // TODO
}

subtractInterval(date, interval) {
// TODO: Test this, note sure how value gets populated here
return `${date} - INTERVAL ${interval}`;
}

addInterval(date, interval) {
// TODO: Test this, note sure how value gets populated here
return `${date} + INTERVAL ${interval}`;
}

timeGroupedColumn(granularity, dimension) {
return GRANULARITY_TO_INTERVAL[granularity](dimension);
}

groupByClause() {
const dimensionsForSelect = this.dimensionsForSelect();
const dimensionColumns = R.flatten(
dimensionsForSelect.map(s => s.selectColumns() && s.dimensionSql())
).filter(s => !!s);

return dimensionColumns.length ? ` GROUP BY ${dimensionColumns.join(", ")}` : "";
}

orderHashToString(hash) {
if (!hash || !hash.id) {
return null;
}

const fieldAlias = this.getFieldAlias(hash.id);

if (fieldAlias === null) {
return null;
}

const direction = hash.desc ? "DESC" : "ASC";
return `${fieldAlias} ${direction}`;
}

getFieldAlias(id) {
const equalIgnoreCase = (a, b) => typeof a === "string" &&
typeof b === "string" &&
a.toUpperCase() === b.toUpperCase();

let field;

field = this.dimensionsForSelect().find(d => equalIgnoreCase(d.dimension, id));

if (field) {
return field.dimensionSql();
}

field = this.measures.find(
d => equalIgnoreCase(d.measure, id) || equalIgnoreCase(d.expressionName, id)
);

if (field) {
return field.aliasName(); // TODO isn't supported
}

return null;
}

escapeColumnName(name) {
return `${name}`; // TODO
}
}

module.exports = ElasticSearchQuery;
2 changes: 2 additions & 0 deletions packages/cubejs-schema-compiler/adapter/QueryBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const hive = require('./HiveQuery');
const oracle = require('./OracleQuery');
const sqlite = require('./SqliteQuery');
const odelasticsearch = require('./OpenDistroElasticSearchQuery');
const elasticsearch = require('./ElasticSearchQuery');

const ADAPTERS = {
postgres,
Expand All @@ -30,6 +31,7 @@ const ADAPTERS = {
oracle,
sqlite,
odelasticsearch,
elasticsearch
};
exports.query = (compilers, dbType, queryOptions) => {
if (!ADAPTERS[dbType]) {
Expand Down
1 change: 1 addition & 0 deletions packages/cubejs-server-core/core/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const DriverDependencies = {
oracle: '@cubejs-backend/oracle-driver',
sqlite: '@cubejs-backend/sqlite-driver',
odelasticsearch: '@cubejs-backend/elasticsearch-driver',
elasticsearch: '@cubejs-backend/elasticsearch-driver',
};

const checkEnvForPlaceholders = () => {
Expand Down

0 comments on commit efde731

Please sign in to comment.