Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding druid SQL support #56

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
node_modules
npm-debug.log
.*.sw*
100 changes: 90 additions & 10 deletions dist/datasource.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,23 @@ define([
'lodash',
'app/core/utils/datemath',
'moment',
'./druid_sql'
],
function (angular, _, dateMath, moment) {
function (angular, _, dateMath, moment, druidSQL) {
'use strict';

function formatSQLResponse(from, response) {
if (!response.length) {
return [];
}

var datapoints = response.map(function(row) {
return [row.measure, formatTimestamp(row.timestamp)];
});

return [{target: 't', datapoints: datapoints}];
}

/** @ngInject */
function DruidDatasource(instanceSettings, $q, backendSrv, templateSrv) {
this.type = 'druid-datasource';
Expand Down Expand Up @@ -110,52 +123,96 @@ function (angular, _, dateMath, moment) {
});
};

this.metricFindQuery = function(query) {
return this._druidSQLQuery(query).then(r => r.data);
};

function shouldQuery(target) {
if (target.hide===true) {
return false;
}

if (target.rawQuery) {
return true;
}

if (_.isEmpty(target.druidDS)) {
return false;
}

if (_.isEmpty(target.aggregators) && target.queryType !== "select") {
return false;
}

return true;
}

// Called once per panel (graph)
this.query = function(options) {
var dataSource = this;
var from = dateToMoment(options.range.from, false);
var to = dateToMoment(options.range.to, true);

console.log("Do query");
console.log(options);

var promises = options.targets.map(function (target) {
if (target.hide===true || _.isEmpty(target.druidDS) || (_.isEmpty(target.aggregators) && target.queryType !== "select")) {
if (!shouldQuery(target)) {
console.log("target.hide: " + target.hide + ", target.druidDS: " + target.druidDS + ", target.aggregators: " + target.aggregators);
var d = $q.defer();
d.resolve([]);
return d.promise;
}

var maxDataPointsByResolution = options.maxDataPoints;
var maxDataPointsByConfig = target.maxDataPoints? target.maxDataPoints : Number.MAX_VALUE;
var maxDataPoints = Math.min(maxDataPointsByResolution, maxDataPointsByConfig);
var granularity = target.shouldOverrideGranularity? target.customGranularity : computeGranularity(from, to, maxDataPoints);

//Round up to start of an interval
//Width of bar chars in Grafana is determined by size of the smallest interval
var roundedFrom = granularity === "all" ? from : roundUpStartTime(from, granularity);

if (target.rawQuery) {
return dataSource._doSqlQuery(roundedFrom, to, granularity, target, options.scopedVars);
}

if(dataSource.periodGranularity!=""){
if(granularity==='day'){
granularity = {"type": "period", "period": "P1D", "timeZone": dataSource.periodGranularity}
}
if(granularity==='day'){
granularity = {"type": "period", "period": "P1D", "timeZone": dataSource.periodGranularity};
}
}
return dataSource._doQuery(roundedFrom, to, granularity, target);
return dataSource._doQuery(roundedFrom, to, granularity, target, options.scopedVars);
});

return $q.all(promises).then(function(results) {
return { data: _.flatten(results) };
});
};

this._doQuery = function (from, to, granularity, target) {
this._doSqlQuery = function (from, to, granularity, target, scopedVars) {
var myScopedVars = druidSQL.makeScopedVars(from, to, granularity, scopedVars);
Object.keys(scopedVars).forEach((k) => {
myScopedVars[k] = scopedVars[k];
});
var queryString = templateSrv.replace(target.query, myScopedVars);
//var queryString = druidSQL.prepareQuery(from, to, granularity, target.query);
return this._druidSQLQuery(queryString).then(function (resp) {
var transformed = druidSQL.transformResultSet(resp.data);
return transformed;
});
};

this._doQuery = function (from, to, granularity, target, scopedVars) {
var promise = null;

var datasource = target.druidDS;
var filters = target.filters;
var aggregators = target.aggregators;
var postAggregators = target.postAggregators;
var groupBy = _.map(target.groupBy, (e) => { return templateSrv.replace(e) });
var groupBy = _.map(target.groupBy, (e) => { return templateSrv.replace(e); });
var limitSpec = null;
var metricNames = getMetricNames(aggregators, postAggregators);
var intervals = getQueryIntervals(from, to);
var promise = null;

var selectMetrics = target.selectMetrics;
var selectDimensions = target.selectDimensions;
Expand Down Expand Up @@ -307,6 +364,17 @@ function (angular, _, dateMath, moment) {
return backendSrv.datasourceRequest(options);
};

this._druidSQLQuery = function (query) {
var options = {
method: 'POST',
url: this.url + '/druid/v2/sql',
data: {query: query}
};
console.log("Sending druid sql query");
console.log(options);
return backendSrv.datasourceRequest(options);
};

function getLimitSpec(limitNum, orderBy) {
return {
"type": "default",
Expand Down Expand Up @@ -356,6 +424,18 @@ function (angular, _, dateMath, moment) {
return moment(ts).format('X')*1000;
}

function formatSQLResponse(from, response) {
if (!response.length) {
return [];
}

var datapoints = response.map(function(row) {
return [row.measure, formatTimestamp(row.timestamp)];
});

return [{target: 't', datapoints: datapoints}];
}

function convertTimeSeriesData(md, metrics) {
return metrics.map(function (metric) {
return {
Expand Down
112 changes: 112 additions & 0 deletions dist/druid_sql.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
define(['moment'], function(moment) {
var GRANULARITIES = {
minute: {
duration: "PT1M",
seconds: 60
},
fifteen_minute: {
duration: "PT15M",
seconds: 900
},
thirty_minute: {
duration: "PT30M",
seconds: 1800
},
hour: {
duration: "PT1H",
seconds: 3600
},
day: {
duration: "PT24H",
seconds: 86400
}
};

function formatTimestamp(ts) {
return moment(ts).format('X') * 1000;
}

function grabDimensions(keys) {
return keys.filter(function(k) {
return k != 'measure' && k != 'timestamp';
});
}

function targetFromDimensions(keys) {
return function(row) {
return keys.map(function(k) {
return row[k] || '';
}).join('-');
};
}

function transformResultSet(r) {
if (!r.length) {
return [];
}

var dimensions = grabDimensions(Object.keys(r[0]));
var namer = targetFromDimensions(dimensions);
var targets = {};
r.forEach(function(row) {
var targetName = namer(row);
if (!targets[targetName]) {
targets[targetName] = [];
}


targets[targetName].push([row.measure, formatTimestamp(row.timestamp)]);
});

return Object.keys(targets).map(function(k) {
return {
target: k,
datapoints: targets[k]
};
});
}

function momentToTimestampClause(m) {
return 'MILLIS_TO_TIMESTAMP(' + m.format('x') + ')';
}

function timeClause(from, to) {
return '__time >= ' + momentToTimestampClause(from) + ' AND __time <= ' + momentToTimestampClause(to);
}

function prepareQuery(from, to, granularity, rawQuery) {
var intervalSpec = "TIME_FLOOR(__time, '{}')".replace('{}', granularity);
var timestampCol = intervalSpec + ' as "timestamp"';
var timeRange = timeClause(from, to);
var q = rawQuery.replace('$interval', intervalSpec).replace('$timeRange', timeRange).replace('$timestamp', timestampCol);
console.log("Druid query:", q);
return q;
}

function makeScopedVars(from, to, granularity) {
var g = GRANULARITIES[granularity];
var duration = g.duration;
var intervalSeconds = g.seconds;
var interval = "TIME_FLOOR(__time, '{}')".replace('{}', duration);
return {
interval: {
value: interval
},
timeRange: {
value: timeClause(from, to)
},
timestamp: {
value: interval + ' as "timestamp"'
},
intervalSeconds: {
value: intervalSeconds
}
};
}

return {
prepareQuery: prepareQuery,
transformResultSet: transformResultSet,
makeScopedVars: makeScopedVars,
};
});
25 changes: 23 additions & 2 deletions dist/partials/query.editor.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
<query-editor-row query-ctrl="ctrl" can-collapse="false">
<query-editor-row query-ctrl="ctrl" can-collapse="false" has-text-edit-mode="true">
<div ng-if="ctrl.target.rawQuery">
<div class="gf-form">
<textarea rows="3" class="gf-form-input" ng-model="ctrl.target.query" spellcheck="false" placeholder="Druid SQL Query" ng-model-onblur ng-change="ctrl.refresh()"></textarea>
</div>
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label query-keyword">FORMAT AS</label>
<div class="gf-form-select-wrapper">
<select class="gf-form-input gf-size-auto" ng-model="ctrl.target.resultFormat" ng-options="f.value as f.text for f in ctrl.resultFormats" ng-change="ctrl.refresh()"></select>
</div>
</div>
<div class="gf-form max-width-25" ng-hide="ctrl.target.resultFormat === 'table'">
<label class="gf-form-label query-keyword">ALIAS BY</label>
<input type="text" class="gf-form-input" ng-model="ctrl.target.alias" spellcheck='false' placeholder="Naming pattern" ng-blur="ctrl.refresh()">
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
</div>
<div ng-if="!ctrl.target.rawQuery">
<div class="gf-form-inline" >
<div class="gf-form max-width-20">
<label class="gf-form-label query-keyword width-9">
Datasource
Expand Down Expand Up @@ -265,7 +286,7 @@
</label>
</div>

<div class="gf-form" ng-repeat="aggregator in ctrl.target.aggregators track by $index" class="gf-form">
<div class="gf-form" ng-repeat="aggregator in ctrl.target.aggregators track by $index">
<label class="gf-form-label" ng-if="aggregator.hidden">Hidden&nbsp;(&nbsp;</label>
<div ng-switch on="aggregator.type">
<div ng-switch-when="count">
Expand Down
1 change: 1 addition & 0 deletions dist/query_ctrl.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,4 +109,5 @@ export declare class DruidQueryCtrl extends QueryCtrl {
validateQuantilePostAggregator(target: any): string;
validateArithmeticPostAggregator(target: any): string;
validateTarget(): any;
toggleEditorMode(): void;
}
3 changes: 3 additions & 0 deletions dist/query_ctrl.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/query_ctrl.js.map

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions dist/query_ctrl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -604,4 +604,8 @@ export class DruidQueryCtrl extends QueryCtrl {

return errs;
}

toggleEditorMode() {
this.target.rawQuery = !this.target.rawQuery;
}
}
1 change: 1 addition & 0 deletions dist/test/query_ctrl.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,4 +109,5 @@ export declare class DruidQueryCtrl extends QueryCtrl {
validateQuantilePostAggregator(target: any): string;
validateArithmeticPostAggregator(target: any): string;
validateTarget(): any;
toggleEditorMode(): void;
}
3 changes: 3 additions & 0 deletions dist/test/query_ctrl.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/test/query_ctrl.js.map

Large diffs are not rendered by default.

Loading