2016-03-19 00:22:02 +08:00
|
|
|
var assert = require('assert');
|
|
|
|
|
|
|
|
var _ = require('underscore');
|
|
|
|
var PSQL = require('cartodb-psql');
|
|
|
|
var camshaft = require('camshaft');
|
|
|
|
var step = require('step');
|
|
|
|
|
|
|
|
var Timer = require('../stats/timer');
|
|
|
|
|
|
|
|
var BBoxFilter = require('../models/filter/bbox');
|
2016-05-14 00:46:58 +08:00
|
|
|
|
2016-03-22 20:10:42 +08:00
|
|
|
var DataviewFactory = require('../models/dataview/factory');
|
2016-05-14 00:46:58 +08:00
|
|
|
var DataviewFactoryWithOverviews = require('../models/dataview/overviews/factory');
|
2016-05-12 00:16:18 +08:00
|
|
|
var OverviewsQueryRewriter = require('../utils/overviews_query_rewriter');
|
|
|
|
var overviewsQueryRewriter = new OverviewsQueryRewriter({
|
|
|
|
zoom_level: 'CDB_ZoomFromScale(!scale_denominator!)'
|
|
|
|
});
|
2016-03-19 00:22:02 +08:00
|
|
|
|
2016-04-14 23:09:07 +08:00
|
|
|
function DataviewBackend(analysisBackend) {
|
|
|
|
this.analysisBackend = analysisBackend;
|
2016-03-19 00:22:02 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = DataviewBackend;
|
|
|
|
|
|
|
|
|
|
|
|
DataviewBackend.prototype.getDataview = function (mapConfigProvider, user, params, callback) {
|
2016-04-14 23:09:07 +08:00
|
|
|
var self = this;
|
|
|
|
|
2016-03-19 00:22:02 +08:00
|
|
|
var timer = new Timer();
|
|
|
|
|
2016-03-22 17:52:02 +08:00
|
|
|
var dataviewName = params.dataviewName;
|
|
|
|
|
2016-03-19 00:22:02 +08:00
|
|
|
var mapConfig;
|
|
|
|
var dataviewDefinition;
|
|
|
|
step(
|
|
|
|
function getMapConfig() {
|
|
|
|
mapConfigProvider.getMapConfig(this);
|
|
|
|
},
|
2016-03-23 19:14:17 +08:00
|
|
|
function _getDataviewDefinition(err, _mapConfig) {
|
2016-03-19 00:22:02 +08:00
|
|
|
assert.ifError(err);
|
|
|
|
|
|
|
|
mapConfig = _mapConfig;
|
|
|
|
|
2016-03-22 17:52:02 +08:00
|
|
|
var _dataviewDefinition = getDataviewDefinition(mapConfig.obj(), dataviewName);
|
2016-03-19 00:22:02 +08:00
|
|
|
if (!_dataviewDefinition) {
|
2016-03-22 17:52:02 +08:00
|
|
|
throw new Error("Dataview '" + dataviewName + "' does not exists");
|
2016-03-19 00:22:02 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
dataviewDefinition = _dataviewDefinition;
|
|
|
|
|
|
|
|
return dataviewDefinition;
|
|
|
|
},
|
|
|
|
function loadAnalysis(err) {
|
|
|
|
assert.ifError(err);
|
|
|
|
|
|
|
|
var analysisConfiguration = {
|
|
|
|
db: {
|
|
|
|
host: params.dbhost,
|
|
|
|
port: params.dbport,
|
|
|
|
dbname: params.dbname,
|
|
|
|
user: params.dbuser,
|
|
|
|
pass: params.dbpassword
|
|
|
|
},
|
|
|
|
batch: {
|
|
|
|
username: user,
|
|
|
|
apiKey: params.api_key
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
var sourceId = dataviewDefinition.source.id;
|
|
|
|
var analysisDefinition = getAnalysisDefinition(mapConfig.obj().analyses, sourceId);
|
|
|
|
|
|
|
|
var next = this;
|
|
|
|
|
2016-04-14 23:09:07 +08:00
|
|
|
self.analysisBackend.create(analysisConfiguration, analysisDefinition, function(err, analysis) {
|
2016-03-19 00:22:02 +08:00
|
|
|
if (err) {
|
|
|
|
return next(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
var sourceId2Node = {};
|
|
|
|
var rootNode = analysis.getRoot();
|
|
|
|
if (rootNode.params && rootNode.params.id) {
|
|
|
|
sourceId2Node[rootNode.params.id] = rootNode;
|
|
|
|
}
|
|
|
|
|
|
|
|
analysis.getSortedNodes().forEach(function(node) {
|
|
|
|
if (node.params && node.params.id) {
|
|
|
|
sourceId2Node[node.params.id] = node;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
var node = sourceId2Node[sourceId];
|
|
|
|
|
|
|
|
if (!node) {
|
|
|
|
return next(new Error('Analysis node not found for dataview'));
|
|
|
|
}
|
|
|
|
|
|
|
|
return next(null, node);
|
|
|
|
});
|
|
|
|
},
|
|
|
|
function runDataviewQuery(err, node) {
|
|
|
|
assert.ifError(err);
|
|
|
|
|
|
|
|
var pg = new PSQL(dbParamsFromReqParams(params));
|
|
|
|
|
|
|
|
var ownFilter = +params.own_filter;
|
|
|
|
ownFilter = !!ownFilter;
|
|
|
|
|
|
|
|
var query;
|
2016-05-12 00:16:18 +08:00
|
|
|
|
2016-05-12 16:25:09 +08:00
|
|
|
if (ownFilter) {
|
|
|
|
query = node.getQuery();
|
|
|
|
} else {
|
|
|
|
var applyFilters = {};
|
|
|
|
applyFilters[dataviewName] = false;
|
|
|
|
query = node.getQuery(applyFilters);
|
|
|
|
}
|
|
|
|
|
2016-05-12 00:16:18 +08:00
|
|
|
var sourceId = dataviewDefinition.source.id; // node.id
|
2016-05-12 22:20:34 +08:00
|
|
|
var layer = _.find(
|
|
|
|
mapConfig.obj().layers,
|
|
|
|
function(l){ return l.options.source && (l.options.source.id === sourceId); }
|
|
|
|
);
|
2016-05-13 00:29:30 +08:00
|
|
|
var queryRewriteData = layer && layer.options.query_rewrite_data;
|
2016-05-12 00:16:18 +08:00
|
|
|
if ( queryRewriteData ) {
|
2016-05-12 22:20:34 +08:00
|
|
|
if ( node.type === 'source' ) {
|
2016-05-12 00:16:18 +08:00
|
|
|
var filters = node.filters; // TODO: node.getFilters() when available in camshaft
|
2016-05-12 22:20:34 +08:00
|
|
|
var filters_disabler = _.keys(filters).reduce(
|
|
|
|
function(disabler, filter_id){ disabler[filter_id] = false; return disabler; },
|
|
|
|
{}
|
|
|
|
);
|
2016-05-12 00:16:18 +08:00
|
|
|
var unfiltered_query = node.getQuery(filters_disabler);
|
2016-05-12 22:20:34 +08:00
|
|
|
queryRewriteData = _.extend(
|
|
|
|
{},
|
|
|
|
queryRewriteData, { filters: filters, unfiltered_query: unfiltered_query }
|
|
|
|
);
|
2016-05-12 00:16:18 +08:00
|
|
|
}
|
2016-03-19 00:22:02 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (params.bbox) {
|
|
|
|
var bboxFilter = new BBoxFilter({column: 'the_geom', srid: 4326}, {bbox: params.bbox});
|
|
|
|
query = bboxFilter.sql(query);
|
2016-05-17 19:55:00 +08:00
|
|
|
var bbox_filter_definition = {
|
|
|
|
type: 'bbox',
|
|
|
|
options: {
|
|
|
|
column: 'the_geom',
|
|
|
|
srid: 4326,
|
|
|
|
},
|
|
|
|
params: {
|
|
|
|
bbox: params.bbox
|
|
|
|
}
|
|
|
|
};
|
|
|
|
queryRewriteData = _.extend(queryRewriteData, { bbox_filter: bbox_filter_definition });
|
2016-03-19 00:22:02 +08:00
|
|
|
}
|
|
|
|
|
2016-05-14 02:47:36 +08:00
|
|
|
var dataviewFactory = DataviewFactoryWithOverviews.getFactory(
|
|
|
|
overviewsQueryRewriter, queryRewriteData, { bbox: params.bbox }
|
|
|
|
);
|
|
|
|
|
2016-03-19 00:22:02 +08:00
|
|
|
var overrideParams = _.reduce(_.pick(params, 'start', 'end', 'bins'),
|
|
|
|
function castNumbers(overrides, val, k) {
|
|
|
|
overrides[k] = Number.isFinite(+val) ? +val : val;
|
|
|
|
return overrides;
|
|
|
|
},
|
|
|
|
{ownFilter: ownFilter}
|
|
|
|
);
|
|
|
|
|
2016-05-14 00:46:58 +08:00
|
|
|
var dataview = dataviewFactory.getDataview(query, dataviewDefinition);
|
2016-03-22 20:10:42 +08:00
|
|
|
dataview.getResult(pg, overrideParams, this);
|
2016-03-19 00:22:02 +08:00
|
|
|
},
|
|
|
|
function returnCallback(err, result) {
|
|
|
|
return callback(err, result, timer.getTimes());
|
|
|
|
}
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2016-03-23 19:14:17 +08:00
|
|
|
DataviewBackend.prototype.search = function (mapConfigProvider, user, params, callback) {
|
2016-04-14 23:09:07 +08:00
|
|
|
var self = this;
|
|
|
|
|
2016-03-23 19:14:17 +08:00
|
|
|
var timer = new Timer();
|
|
|
|
|
|
|
|
var dataviewName = params.dataviewName;
|
|
|
|
|
|
|
|
var mapConfig;
|
|
|
|
var dataviewDefinition;
|
|
|
|
step(
|
|
|
|
function getMapConfig() {
|
|
|
|
mapConfigProvider.getMapConfig(this);
|
|
|
|
},
|
|
|
|
function _getDataviewDefinition(err, _mapConfig) {
|
|
|
|
assert.ifError(err);
|
|
|
|
|
|
|
|
mapConfig = _mapConfig;
|
|
|
|
|
|
|
|
var _dataviewDefinition = getDataviewDefinition(mapConfig.obj(), dataviewName);
|
|
|
|
if (!_dataviewDefinition) {
|
|
|
|
throw new Error("Dataview '" + dataviewName + "' does not exists");
|
|
|
|
}
|
|
|
|
|
|
|
|
dataviewDefinition = _dataviewDefinition;
|
|
|
|
|
|
|
|
return dataviewDefinition;
|
|
|
|
},
|
|
|
|
function loadAnalysis(err) {
|
|
|
|
assert.ifError(err);
|
|
|
|
|
|
|
|
var analysisConfiguration = {
|
|
|
|
db: {
|
|
|
|
host: params.dbhost,
|
|
|
|
port: params.dbport,
|
|
|
|
dbname: params.dbname,
|
|
|
|
user: params.dbuser,
|
|
|
|
pass: params.dbpassword
|
|
|
|
},
|
|
|
|
batch: {
|
|
|
|
// TODO load this from configuration
|
|
|
|
endpoint: 'http://127.0.0.1:8080/api/v1/sql/job',
|
|
|
|
username: user,
|
|
|
|
apiKey: params.api_key
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
var sourceId = dataviewDefinition.source.id;
|
|
|
|
var analysisDefinition = getAnalysisDefinition(mapConfig.obj().analyses, sourceId);
|
|
|
|
|
|
|
|
var next = this;
|
|
|
|
|
2016-04-14 23:09:07 +08:00
|
|
|
self.analysisBackend.create(analysisConfiguration, analysisDefinition, function(err, analysis) {
|
2016-03-23 19:14:17 +08:00
|
|
|
if (err) {
|
|
|
|
return next(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
var sourceId2Node = {};
|
|
|
|
var rootNode = analysis.getRoot();
|
|
|
|
if (rootNode.params && rootNode.params.id) {
|
|
|
|
sourceId2Node[rootNode.params.id] = rootNode;
|
|
|
|
}
|
|
|
|
|
|
|
|
analysis.getSortedNodes().forEach(function(node) {
|
|
|
|
if (node.params && node.params.id) {
|
|
|
|
sourceId2Node[node.params.id] = node;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
var node = sourceId2Node[sourceId];
|
|
|
|
|
|
|
|
if (!node) {
|
|
|
|
return next(new Error('Analysis node not found for dataview'));
|
|
|
|
}
|
|
|
|
|
|
|
|
return next(null, node);
|
|
|
|
});
|
|
|
|
},
|
|
|
|
function runDataviewQuery(err, node) {
|
|
|
|
assert.ifError(err);
|
|
|
|
|
|
|
|
var pg = new PSQL(dbParamsFromReqParams(params));
|
|
|
|
|
|
|
|
var ownFilter = +params.own_filter;
|
|
|
|
ownFilter = !!ownFilter;
|
|
|
|
|
|
|
|
var query;
|
|
|
|
if (ownFilter) {
|
|
|
|
query = node.getQuery();
|
|
|
|
} else {
|
|
|
|
var applyFilters = {};
|
|
|
|
applyFilters[dataviewName] = false;
|
|
|
|
query = node.getQuery(applyFilters);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (params.bbox) {
|
|
|
|
var bboxFilter = new BBoxFilter({column: 'the_geom', srid: 4326}, {bbox: params.bbox});
|
|
|
|
query = bboxFilter.sql(query);
|
|
|
|
}
|
|
|
|
|
|
|
|
var userQuery = params.q;
|
|
|
|
|
|
|
|
var dataview = DataviewFactory.getDataview(query, dataviewDefinition);
|
|
|
|
dataview.search(pg, userQuery, this);
|
|
|
|
},
|
|
|
|
function returnCallback(err, result) {
|
|
|
|
return callback(err, result, timer.getTimes());
|
|
|
|
}
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2016-03-19 00:22:02 +08:00
|
|
|
function getAnalysisDefinition(mapConfigAnalyses, sourceId) {
|
|
|
|
mapConfigAnalyses = mapConfigAnalyses || [];
|
|
|
|
for (var i = 0; i < mapConfigAnalyses.length; i++) {
|
|
|
|
var analysisGraph = new camshaft.reference.AnalysisGraph(mapConfigAnalyses[i]);
|
|
|
|
var nodes = analysisGraph.getNodesWithId();
|
|
|
|
if (nodes.hasOwnProperty(sourceId)) {
|
|
|
|
return mapConfigAnalyses[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
throw new Error('There is no associated analysis for the dataview source id');
|
|
|
|
}
|
|
|
|
|
|
|
|
function getDataviewDefinition(mapConfig, dataviewName) {
|
|
|
|
var dataviews = mapConfig.dataviews || {};
|
|
|
|
return dataviews[dataviewName];
|
|
|
|
}
|
|
|
|
|
|
|
|
function dbParamsFromReqParams(params) {
|
|
|
|
var dbParams = {};
|
|
|
|
if ( params.dbuser ) {
|
|
|
|
dbParams.user = params.dbuser;
|
|
|
|
}
|
|
|
|
if ( params.dbpassword ) {
|
|
|
|
dbParams.pass = params.dbpassword;
|
|
|
|
}
|
|
|
|
if ( params.dbhost ) {
|
|
|
|
dbParams.host = params.dbhost;
|
|
|
|
}
|
|
|
|
if ( params.dbport ) {
|
|
|
|
dbParams.port = params.dbport;
|
|
|
|
}
|
|
|
|
if ( params.dbname ) {
|
|
|
|
dbParams.dbname = params.dbname;
|
|
|
|
}
|
|
|
|
return dbParams;
|
2016-05-12 00:16:18 +08:00
|
|
|
}
|