Add last_modified field to POST layergroup response (#72)
Includes testcases
This commit is contained in:
parent
dfc4a02398
commit
4605bd1e1d
1
NEWS.md
1
NEWS.md
@ -2,6 +2,7 @@
|
|||||||
-----
|
-----
|
||||||
* Handle SQL API errors by requesting no Varnish cache
|
* Handle SQL API errors by requesting no Varnish cache
|
||||||
* Fix X-Cache-Channel for multilayer (by token) responses
|
* Fix X-Cache-Channel for multilayer (by token) responses
|
||||||
|
* Add last_modified field to POST layergroup response (#72)
|
||||||
|
|
||||||
1.1.8
|
1.1.8
|
||||||
-----
|
-----
|
||||||
|
@ -54,42 +54,70 @@ module.exports = function(){
|
|||||||
// we have no SQL after layer creation.
|
// we have no SQL after layer creation.
|
||||||
me.channelCache = {};
|
me.channelCache = {};
|
||||||
|
|
||||||
me.affectedTables = function (username, api_key, sql, callback) {
|
// Run a query through the SQL api
|
||||||
|
me.sqlQuery = function (username, api_key, sql, callback) {
|
||||||
var api = global.environment.sqlapi;
|
var api = global.environment.sqlapi;
|
||||||
|
|
||||||
// build up api string
|
// build up api string
|
||||||
var sqlapi = api.protocol + '://' + username + '.' + api.host + ':' + api.port + '/api/' + api.version + '/sql'
|
var sqlapi = api.protocol + '://' + username + '.' + api.host + ':' + api.port + '/api/' + api.version + '/sql'
|
||||||
|
|
||||||
var qs = {};
|
var qs = { q: sql }
|
||||||
|
|
||||||
// add query to querystring
|
|
||||||
qs.q = 'SELECT CDB_QueryTables($windshaft$' + sql + '$windshaft$)';
|
|
||||||
|
|
||||||
// add api_key if given
|
// add api_key if given
|
||||||
if (_.isString(api_key) && api_key != ''){ qs.api_key = api_key; }
|
if (_.isString(api_key) && api_key != '') { qs.api_key = api_key; }
|
||||||
|
|
||||||
// call sql api
|
// call sql api
|
||||||
request.get({url:sqlapi, qs:qs, json:true}, function(err, res, body){
|
request.get({url:sqlapi, qs:qs, json:true}, function(err, res, body){
|
||||||
var epref = 'could not detect source tables using SQL api at ' + sqlapi;
|
|
||||||
if (err){
|
if (err){
|
||||||
var msg = err.message ? err.message : err;
|
callback(err);
|
||||||
callback(new Error(epref + ': ' + msg));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (res.statusCode != 200) {
|
if (res.statusCode != 200) {
|
||||||
var msg = res.body.error ? res.body.error : res.body;
|
var msg = res.body.error ? res.body.error : res.body;
|
||||||
callback(new Error(epref + ': ' + msg));
|
callback(new Error('unexpected response status (' + res.statusCode + ') for sql query: ' + sql));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var qtables = body.rows[0].cdb_querytables;
|
callback(null, body.rows);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
me.findLastUpdated = function (username, api_key, tableNames, callback) {
|
||||||
|
|
||||||
|
var sql = 'SELECT EXTRACT(EPOCH FROM max(updated_at)) FROM CDB_TableMetadata WHERE m.tabname::name = any ({'
|
||||||
|
+ tableNames.join(',') + '})';
|
||||||
|
|
||||||
|
// call sql api
|
||||||
|
me.sqlQuery(username, api_key, sql, function(err, rows){
|
||||||
|
if (err){
|
||||||
|
var msg = err.message ? err.message : err;
|
||||||
|
callback(new Error('could not find last updated timestamp: ' + msg));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var last_updated = rows[0].max;
|
||||||
|
callback(null, last_updated);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
me.affectedTables = function (username, api_key, sql, callback) {
|
||||||
|
|
||||||
|
var sql = 'SELECT CDB_QueryTables($windshaft$' + sql + '$windshaft$)';
|
||||||
|
|
||||||
|
// call sql api
|
||||||
|
me.sqlQuery(username, api_key, sql, function(err, rows){
|
||||||
|
if (err){
|
||||||
|
var msg = err.message ? err.message : err;
|
||||||
|
callback(new Error('could not fetch source tables: ' + msg));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var qtables = rows[0].cdb_querytables;
|
||||||
var tableNames = qtables.split(/^\{(.*)\}$/)[1];
|
var tableNames = qtables.split(/^\{(.*)\}$/)[1];
|
||||||
|
tableNames = tableNames.split(',');
|
||||||
callback(null, tableNames);
|
callback(null, tableNames);
|
||||||
});
|
});
|
||||||
},
|
};
|
||||||
|
|
||||||
me.buildCacheChannel = function (dbName, tableNames){
|
me.buildCacheChannel = function (dbName, tableNames){
|
||||||
return dbName + ':' + tableNames;
|
return dbName + ':' + tableNames.join(',');
|
||||||
};
|
};
|
||||||
|
|
||||||
me.generateMD5 = function(data){
|
me.generateMD5 = function(data){
|
||||||
@ -124,14 +152,13 @@ module.exports = function(){
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ( ! req.params.sql && ! req.params.token ) {
|
if ( ! req.params.sql && ! req.params.token ) {
|
||||||
var cacheChannel = me.buildCacheChannel(dbName, req.params.table);
|
var cacheChannel = me.buildCacheChannel(dbName, [req.params.table]);
|
||||||
// not worth caching this
|
// not worth caching this
|
||||||
callback(null, cacheChannel);
|
callback(null, cacheChannel);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( ! req.params.sql ) {
|
if ( ! req.params.sql ) {
|
||||||
console.log('req:'); console.dir(req);
|
|
||||||
callback(new Error("this request doesn't need an X-Cache-Channel generated"));
|
callback(new Error("this request doesn't need an X-Cache-Channel generated"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -194,7 +221,6 @@ console.log('req:'); console.dir(req);
|
|||||||
sql.push(lyr.options.sql);
|
sql.push(lyr.options.sql);
|
||||||
});
|
});
|
||||||
sql = sql.join(';');
|
sql = sql.join(';');
|
||||||
console.log('afterLayergroupCreate: sql:'+sql);
|
|
||||||
|
|
||||||
var dbName = req.params.dbname;
|
var dbName = req.params.dbname;
|
||||||
var usr = req.headers.host.split('.')[0];
|
var usr = req.headers.host.split('.')[0];
|
||||||
@ -203,10 +229,16 @@ console.log('req:'); console.dir(req);
|
|||||||
var cacheKey = dbName + ':' + token;
|
var cacheKey = dbName + ':' + token;
|
||||||
|
|
||||||
me.affectedTables(usr, key, sql, function(err, tableNames) {
|
me.affectedTables(usr, key, sql, function(err, tableNames) {
|
||||||
|
|
||||||
if ( err ) { callback(err); return; }
|
if ( err ) { callback(err); return; }
|
||||||
var cacheChannel = me.buildCacheChannel(dbName,tableNames);
|
var cacheChannel = me.buildCacheChannel(dbName,tableNames);
|
||||||
me.channelCache[cacheKey] = cacheChannel; // store for caching
|
me.channelCache[cacheKey] = cacheChannel; // store for caching
|
||||||
callback(null);
|
// find last updated
|
||||||
|
me.findLastUpdated(usr, key, tableNames, function(err, lastUpdated) {
|
||||||
|
if ( err ) { callback(err); return; }
|
||||||
|
response.last_updated = lastUpdated;
|
||||||
|
callback(null);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -6,8 +6,7 @@ var querystring = require('querystring');
|
|||||||
var semver = require('semver');
|
var semver = require('semver');
|
||||||
var mapnik = require('mapnik');
|
var mapnik = require('mapnik');
|
||||||
var Step = require('step');
|
var Step = require('step');
|
||||||
var http = require('http');
|
var SQLAPIEmu = require(__dirname + '/../support/SQLAPIEmu.js');
|
||||||
var url = require('url');
|
|
||||||
|
|
||||||
require(__dirname + '/../support/test_helper');
|
require(__dirname + '/../support/test_helper');
|
||||||
|
|
||||||
@ -24,18 +23,7 @@ suite('multilayer', function() {
|
|||||||
var sqlapi_server;
|
var sqlapi_server;
|
||||||
|
|
||||||
suiteSetup(function(done){
|
suiteSetup(function(done){
|
||||||
sqlapi_server = http.createServer(function(req,res) {
|
sqlapi_server = new SQLAPIEmu(global.environment.sqlapi.port, done);
|
||||||
var query = url.parse(req.url, true).query;
|
|
||||||
if ( query.q.match('SQLAPIERROR') ) {
|
|
||||||
res.statusCode = 400;
|
|
||||||
res.write(JSON.stringify({'error':'Some error occurred'}));
|
|
||||||
} else {
|
|
||||||
res.write(JSON.stringify({rows: [ { 'cdb_querytables': '{' +
|
|
||||||
JSON.stringify(query) + '}' } ]}));
|
|
||||||
}
|
|
||||||
res.end();
|
|
||||||
});
|
|
||||||
sqlapi_server.listen(global.environment.sqlapi.port, done);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("layergroup with 2 layers, each with its style", function(done) {
|
test("layergroup with 2 layers, each with its style", function(done) {
|
||||||
@ -70,9 +58,23 @@ suite('multilayer', function() {
|
|||||||
assert.equal(res.statusCode, 200, res.body);
|
assert.equal(res.statusCode, 200, res.body);
|
||||||
var parsedBody = JSON.parse(res.body);
|
var parsedBody = JSON.parse(res.body);
|
||||||
var expectedBody = { layergroupid: expected_token };
|
var expectedBody = { layergroupid: expected_token };
|
||||||
// TODO: check last modified
|
// check last modified
|
||||||
//expectedBody.layercount = 2;
|
var qTables = JSON.stringify({
|
||||||
if ( expected_token ) assert.deepEqual(parsedBody, expectedBody);
|
'q': 'SELECT CDB_QueryTables($windshaft$'
|
||||||
|
+ layergroup.layers[0].options.sql + ';'
|
||||||
|
+ layergroup.layers[1].options.sql
|
||||||
|
+ '$windshaft$)'
|
||||||
|
});
|
||||||
|
expectedBody.last_updated = JSON.stringify({
|
||||||
|
'q': 'SELECT EXTRACT(EPOCH FROM max(updated_at)) '
|
||||||
|
+ 'FROM CDB_TableMetadata WHERE m.tabname::name = any ({'
|
||||||
|
+ qTables + '})'
|
||||||
|
});
|
||||||
|
if ( expected_token ) {
|
||||||
|
//assert.equal(parsedBody.layergroupid, expectedBody.layergroupid);
|
||||||
|
//assert.equal(parsedBody.last_updated, expectedBody.last_updated);
|
||||||
|
assert.deepEqual(parsedBody, expectedBody);
|
||||||
|
}
|
||||||
else expected_token = parsedBody.layergroupid;
|
else expected_token = parsedBody.layergroupid;
|
||||||
next(null, res);
|
next(null, res);
|
||||||
});
|
});
|
||||||
|
@ -7,7 +7,7 @@ var semver = require('semver');
|
|||||||
var mapnik = require('mapnik');
|
var mapnik = require('mapnik');
|
||||||
var Step = require('step');
|
var Step = require('step');
|
||||||
var http = require('http');
|
var http = require('http');
|
||||||
var url = require('url');
|
var SQLAPIEmu = require(__dirname + '/../support/SQLAPIEmu.js');
|
||||||
|
|
||||||
require(__dirname + '/../support/test_helper');
|
require(__dirname + '/../support/test_helper');
|
||||||
|
|
||||||
@ -34,18 +34,7 @@ suite('server', function() {
|
|||||||
var test_style_black_210 = "#test_table{marker-fill:black;marker-line-color:red;marker-width:20}";
|
var test_style_black_210 = "#test_table{marker-fill:black;marker-line-color:red;marker-width:20}";
|
||||||
|
|
||||||
suiteSetup(function(done){
|
suiteSetup(function(done){
|
||||||
sqlapi_server = http.createServer(function(req,res) {
|
sqlapi_server = new SQLAPIEmu(global.environment.sqlapi.port, done);
|
||||||
var query = url.parse(req.url, true).query;
|
|
||||||
if ( query.q.match('SQLAPIERROR') ) {
|
|
||||||
res.statusCode = 400;
|
|
||||||
res.write(JSON.stringify({'error':'Some error occurred'}));
|
|
||||||
} else {
|
|
||||||
res.write(JSON.stringify({rows: [ { 'cdb_querytables': '{' +
|
|
||||||
JSON.stringify(query) + '}' } ]}));
|
|
||||||
}
|
|
||||||
res.end();
|
|
||||||
});
|
|
||||||
sqlapi_server.listen(global.environment.sqlapi.port, done);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////
|
||||||
|
29
test/support/SQLAPIEmu.js
Normal file
29
test/support/SQLAPIEmu.js
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
var http = require('http');
|
||||||
|
var url = require('url');
|
||||||
|
|
||||||
|
var o = function(port, cb) {
|
||||||
|
|
||||||
|
this.sqlapi_server = http.createServer(function(req,res) {
|
||||||
|
var query = url.parse(req.url, true).query;
|
||||||
|
if ( query.q.match('SQLAPIERROR') ) {
|
||||||
|
res.statusCode = 400;
|
||||||
|
res.write(JSON.stringify({'error':'Some error occurred'}));
|
||||||
|
} else {
|
||||||
|
var qs = JSON.stringify(query);
|
||||||
|
var row = {
|
||||||
|
// This is the structure of the known query sent by tiler
|
||||||
|
'cdb_querytables': '{' + qs + '}',
|
||||||
|
'max': qs
|
||||||
|
};
|
||||||
|
res.write(JSON.stringify({rows: [ row ]}));
|
||||||
|
}
|
||||||
|
res.end();
|
||||||
|
}).listen(port, cb);
|
||||||
|
};
|
||||||
|
|
||||||
|
o.prototype.close = function(cb) {
|
||||||
|
this.sqlapi_server.close(cb);
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = o;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user