2015-12-04 00:28:18 +08:00
|
|
|
'use strict';
|
|
|
|
|
2019-07-27 02:20:26 +08:00
|
|
|
const bodyParser = require('../middlewares/body-parser');
|
|
|
|
const { initializeProfilerMiddleware: initializeProfiler } = require('../middlewares/profiler');
|
|
|
|
const user = require('../middlewares/user');
|
|
|
|
const rateLimits = require('../middlewares/rate-limit');
|
|
|
|
const authorization = require('../middlewares/authorization');
|
|
|
|
const connectionParams = require('../middlewares/connection-params');
|
|
|
|
const timeoutLimits = require('../middlewares/timeout-limits');
|
2019-07-29 22:24:48 +08:00
|
|
|
const params = require('../middlewares/params');
|
2019-07-27 02:20:26 +08:00
|
|
|
const log = require('../middlewares/log');
|
2019-07-05 21:41:26 +08:00
|
|
|
const cancelOnClientAbort = require('../middlewares/cancel-on-client-abort');
|
2019-07-26 23:44:28 +08:00
|
|
|
const affectedTables = require('../middlewares/affected-tables');
|
2019-07-27 00:05:47 +08:00
|
|
|
const accessValidator = require('../middlewares/access-validator');
|
2019-07-27 00:23:14 +08:00
|
|
|
const queryMayWrite = require('../middlewares/query-may-write');
|
2019-07-27 00:42:09 +08:00
|
|
|
const cacheControl = require('../middlewares/cache-control');
|
2019-07-27 00:56:54 +08:00
|
|
|
const cacheChannel = require('../middlewares/cache-channel');
|
|
|
|
const surrogateKey = require('../middlewares/surrogate-key');
|
2019-07-27 01:08:39 +08:00
|
|
|
const lastModified = require('../middlewares/last-modified');
|
2019-07-27 01:22:26 +08:00
|
|
|
const formatter = require('../middlewares/formatter');
|
2019-07-27 01:31:28 +08:00
|
|
|
const content = require('../middlewares/content');
|
2019-07-27 02:20:26 +08:00
|
|
|
const error = require('../middlewares/error');
|
|
|
|
|
|
|
|
const { RATE_LIMIT_ENDPOINTS_GROUPS } = rateLimits;
|
|
|
|
const PSQL = require('cartodb-psql');
|
2016-01-28 21:08:18 +08:00
|
|
|
|
2019-07-27 01:51:06 +08:00
|
|
|
module.exports = class QueryController {
|
|
|
|
constructor (metadataBackend, userDatabaseService, statsdClient, userLimitsService) {
|
|
|
|
this.metadataBackend = metadataBackend;
|
|
|
|
this.stats = statsdClient;
|
|
|
|
this.userDatabaseService = userDatabaseService;
|
|
|
|
this.userLimitsService = userLimitsService;
|
|
|
|
}
|
|
|
|
|
|
|
|
route (app) {
|
|
|
|
const { base_url } = global.settings;
|
|
|
|
const forceToBeMaster = false;
|
|
|
|
|
|
|
|
const queryMiddlewares = () => {
|
|
|
|
return [
|
2019-07-27 02:20:26 +08:00
|
|
|
bodyParser(),
|
|
|
|
initializeProfiler('query'),
|
|
|
|
user(this.metadataBackend),
|
|
|
|
rateLimits(this.userLimitsService, RATE_LIMIT_ENDPOINTS_GROUPS.QUERY),
|
|
|
|
authorization(this.metadataBackend, forceToBeMaster),
|
|
|
|
connectionParams(this.userDatabaseService),
|
|
|
|
timeoutLimits(this.metadataBackend),
|
2019-07-29 22:24:48 +08:00
|
|
|
params({ strategy: 'query' }),
|
2019-07-27 02:20:26 +08:00
|
|
|
log(log.TYPES.QUERY),
|
2019-07-27 01:51:06 +08:00
|
|
|
cancelOnClientAbort(),
|
|
|
|
affectedTables(),
|
|
|
|
accessValidator(),
|
|
|
|
queryMayWrite(),
|
|
|
|
cacheControl(),
|
|
|
|
cacheChannel(),
|
|
|
|
surrogateKey(),
|
|
|
|
lastModified(),
|
|
|
|
formatter(),
|
|
|
|
content(),
|
|
|
|
handleQuery({ stats: this.stats }),
|
2019-07-27 02:20:26 +08:00
|
|
|
error()
|
2019-07-27 01:51:06 +08:00
|
|
|
];
|
|
|
|
};
|
|
|
|
|
|
|
|
app.all(`${base_url}/sql`, queryMiddlewares());
|
|
|
|
app.all(`${base_url}/sql.:f`, queryMiddlewares());
|
|
|
|
}
|
2015-12-04 00:28:18 +08:00
|
|
|
};
|
|
|
|
|
2019-07-27 01:45:44 +08:00
|
|
|
function handleQuery ({ stats } = {}) {
|
|
|
|
return function handleQueryMiddleware (req, res, next) {
|
|
|
|
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;
|
2018-02-19 20:24:44 +08:00
|
|
|
|
2019-07-27 01:45:44 +08:00
|
|
|
let { formatter } = req;
|
2019-07-27 01:38:17 +08:00
|
|
|
|
2019-07-27 01:45:44 +08:00
|
|
|
try {
|
|
|
|
if (req.profiler) {
|
|
|
|
req.profiler.done('init');
|
|
|
|
}
|
2015-12-04 00:28:18 +08:00
|
|
|
|
2019-07-27 01:45:44 +08:00
|
|
|
const 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
|
2019-07-27 01:38:17 +08:00
|
|
|
};
|
2019-02-27 16:02:31 +08:00
|
|
|
|
2019-07-27 01:45:44 +08:00
|
|
|
if (req.profiler) {
|
|
|
|
opts.profiler = req.profiler;
|
|
|
|
opts.beforeSink = function () {
|
|
|
|
req.profiler.done('beforeSink');
|
|
|
|
res.header('X-SQLAPI-Profiler', req.profiler.toJSONString());
|
|
|
|
};
|
2019-07-27 01:38:17 +08:00
|
|
|
}
|
|
|
|
|
2019-07-27 01:45:44 +08:00
|
|
|
if (dbopts.host) {
|
|
|
|
res.header('X-Served-By-DB-Host', dbopts.host);
|
2019-07-27 01:38:17 +08:00
|
|
|
}
|
2019-07-27 01:45:44 +08:00
|
|
|
|
|
|
|
formatter.sendResponse(opts, (err) => {
|
|
|
|
formatter = null;
|
|
|
|
|
|
|
|
if (err) {
|
|
|
|
next(err);
|
2015-12-04 00:28:18 +08:00
|
|
|
}
|
|
|
|
|
2019-07-27 01:51:06 +08:00
|
|
|
if (req.profiler) {
|
2019-07-27 01:45:44 +08:00
|
|
|
req.profiler.sendStats();
|
|
|
|
}
|
2019-07-27 01:51:06 +08:00
|
|
|
|
|
|
|
if (stats) {
|
|
|
|
if (err) {
|
2019-07-27 01:45:44 +08:00
|
|
|
stats.increment('sqlapi.query.error');
|
|
|
|
} else {
|
|
|
|
stats.increment('sqlapi.query.success');
|
|
|
|
}
|
|
|
|
}
|
2019-07-27 01:51:06 +08:00
|
|
|
|
2019-07-27 01:45:44 +08:00
|
|
|
});
|
|
|
|
} catch (err) {
|
|
|
|
next(err);
|
2015-12-04 00:28:18 +08:00
|
|
|
|
2019-07-27 01:45:44 +08:00
|
|
|
if (stats) {
|
|
|
|
stats.increment('sqlapi.query.error');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
2019-07-27 01:51:06 +08:00
|
|
|
}
|