144 lines
5.2 KiB
JavaScript
144 lines
5.2 KiB
JavaScript
'use strict';
|
|
|
|
var step = require('step');
|
|
var PSQL = require('cartodb-psql');
|
|
const bodyParserMiddleware = require('../middlewares/body-parser');
|
|
const userMiddleware = require('../middlewares/user');
|
|
const errorMiddleware = require('../middlewares/error');
|
|
const authorizationMiddleware = require('../middlewares/authorization');
|
|
const connectionParamsMiddleware = require('../middlewares/connection-params');
|
|
const timeoutLimitsMiddleware = require('../middlewares/timeout-limits');
|
|
const { initializeProfilerMiddleware } = require('../middlewares/profiler');
|
|
const rateLimitsMiddleware = require('../middlewares/rate-limit');
|
|
const { RATE_LIMIT_ENDPOINTS_GROUPS } = rateLimitsMiddleware;
|
|
const parameters = require('../middlewares/parameters');
|
|
const logMiddleware = require('../middlewares/log');
|
|
const cancelOnClientAbort = require('../middlewares/cancel-on-client-abort');
|
|
const affectedTables = require('../middlewares/affected-tables');
|
|
const accessValidator = require('../middlewares/access-validator');
|
|
const queryMayWrite = require('../middlewares/query-may-write');
|
|
const cacheControl = require('../middlewares/cache-control');
|
|
const cacheChannel = require('../middlewares/cache-channel');
|
|
const surrogateKey = require('../middlewares/surrogate-key');
|
|
const lastModified = require('../middlewares/last-modified');
|
|
const formatter = require('../middlewares/formatter');
|
|
const content = require('../middlewares/content');
|
|
|
|
function QueryController(metadataBackend, userDatabaseService, statsd_client, userLimitsService) {
|
|
this.metadataBackend = metadataBackend;
|
|
this.statsd_client = statsd_client;
|
|
this.userDatabaseService = userDatabaseService;
|
|
this.userLimitsService = userLimitsService;
|
|
}
|
|
|
|
QueryController.prototype.route = function (app) {
|
|
const { base_url } = global.settings;
|
|
const forceToBeMaster = false;
|
|
|
|
const queryMiddlewares = () => {
|
|
return [
|
|
bodyParserMiddleware(),
|
|
initializeProfilerMiddleware('query'),
|
|
userMiddleware(this.metadataBackend),
|
|
rateLimitsMiddleware(this.userLimitsService, RATE_LIMIT_ENDPOINTS_GROUPS.QUERY),
|
|
authorizationMiddleware(this.metadataBackend, forceToBeMaster),
|
|
connectionParamsMiddleware(this.userDatabaseService),
|
|
timeoutLimitsMiddleware(this.metadataBackend),
|
|
parameters({ strategy: 'query' }),
|
|
logMiddleware(logMiddleware.TYPES.QUERY),
|
|
cancelOnClientAbort(),
|
|
affectedTables(),
|
|
accessValidator(),
|
|
queryMayWrite(),
|
|
cacheControl(),
|
|
cacheChannel(),
|
|
surrogateKey(),
|
|
lastModified(),
|
|
formatter(),
|
|
content(),
|
|
this.handleQuery.bind(this),
|
|
errorMiddleware()
|
|
];
|
|
};
|
|
|
|
app.all(`${base_url}/sql`, queryMiddlewares());
|
|
app.all(`${base_url}/sql.:f`, queryMiddlewares());
|
|
};
|
|
|
|
// jshint maxcomplexity:21
|
|
QueryController.prototype.handleQuery = function (req, res, next) {
|
|
var self = this;
|
|
|
|
const { user: username, userDbParams: dbopts, userLimits } = res.locals;
|
|
const { orderBy, sortOrder, limit, offset } = res.locals.params;
|
|
const { sql, skipfields, decimalPrecision, filename, callback } = res.locals.params;
|
|
let { formatter } = req;
|
|
|
|
try {
|
|
if (req.profiler) {
|
|
req.profiler.done('init');
|
|
}
|
|
|
|
// 1. Send formatted results back
|
|
// 2. Handle error
|
|
step(
|
|
function generateFormat(){
|
|
var opts = {
|
|
username: username,
|
|
dbopts: dbopts,
|
|
sink: res,
|
|
gn: 'the_geom', // TODO: read from configuration FILE,
|
|
dp: decimalPrecision,
|
|
skipfields: skipfields,
|
|
sql: new PSQL.QueryWrapper(sql).orderBy(orderBy, sortOrder).window(limit, offset).query(),
|
|
filename: filename,
|
|
bufferedRows: global.settings.bufferedRows,
|
|
callback: callback,
|
|
timeout: userLimits.timeout
|
|
};
|
|
|
|
if (req.profiler) {
|
|
opts.profiler = req.profiler;
|
|
opts.beforeSink = function() {
|
|
req.profiler.done('beforeSink');
|
|
res.header('X-SQLAPI-Profiler', req.profiler.toJSONString());
|
|
};
|
|
}
|
|
|
|
if (dbopts.host) {
|
|
res.header('X-Served-By-DB-Host', dbopts.host);
|
|
}
|
|
|
|
formatter.sendResponse(opts, this);
|
|
},
|
|
function errorHandle(err){
|
|
formatter = null;
|
|
|
|
if (err) {
|
|
next(err);
|
|
}
|
|
|
|
if ( req.profiler ) {
|
|
req.profiler.sendStats();
|
|
}
|
|
if (self.statsd_client) {
|
|
if ( err ) {
|
|
self.statsd_client.increment('sqlapi.query.error');
|
|
} else {
|
|
self.statsd_client.increment('sqlapi.query.success');
|
|
}
|
|
}
|
|
}
|
|
);
|
|
} catch (err) {
|
|
next(err);
|
|
|
|
if (self.statsd_client) {
|
|
self.statsd_client.increment('sqlapi.query.error');
|
|
}
|
|
}
|
|
|
|
};
|
|
|
|
module.exports = QueryController;
|