Merge pull request #261 from CartoDB/private-cdb_tablemetadata
Authenticated query to retrieve last update
This commit is contained in:
commit
5be8afbbeb
4
NEWS.md
4
NEWS.md
@ -1,6 +1,10 @@
|
||||
1.27.1 -- 2015-mm-dd
|
||||
--------------------
|
||||
|
||||
New features:
|
||||
- QueryTablesApi will always use an authenticated query to retrieve last update, this allows to query affected private
|
||||
tables last update (#253)
|
||||
|
||||
|
||||
1.27.0 -- 2015-02-16
|
||||
--------------------
|
||||
|
@ -1,7 +1,10 @@
|
||||
var sqlApi = require('../sql/sql_api'),
|
||||
PSQL = require('cartodb-psql');
|
||||
var sqlApi = require('../sql/sql_api');
|
||||
var PSQL = require('cartodb-psql');
|
||||
var Step = require('step');
|
||||
|
||||
function QueryTablesApi() {
|
||||
function QueryTablesApi(pgConnection, metadataBackend) {
|
||||
this.pgConnection = pgConnection;
|
||||
this.metadataBackend = metadataBackend;
|
||||
}
|
||||
|
||||
var affectedTableRegexCache = {
|
||||
@ -14,11 +17,11 @@ var affectedTableRegexCache = {
|
||||
module.exports = QueryTablesApi;
|
||||
|
||||
|
||||
QueryTablesApi.prototype.getAffectedTablesInQuery = function (username, options, sql, callback) {
|
||||
QueryTablesApi.prototype.getAffectedTablesInQuery = function (username, sql, callback) {
|
||||
|
||||
var query = 'SELECT CDB_QueryTables($windshaft$' + prepareSql(sql) + '$windshaft$)';
|
||||
|
||||
runQuery(username, options, query, handleAffectedTablesInQueryRows, callback);
|
||||
this.runQuery(username, query, handleAffectedTablesInQueryRows, callback);
|
||||
};
|
||||
|
||||
function handleAffectedTablesInQueryRows(err, rows, callback) {
|
||||
@ -33,7 +36,7 @@ function handleAffectedTablesInQueryRows(err, rows, callback) {
|
||||
callback(null, tableNames);
|
||||
}
|
||||
|
||||
QueryTablesApi.prototype.getAffectedTablesAndLastUpdatedTime = function (username, options, sql, callback) {
|
||||
QueryTablesApi.prototype.getAffectedTablesAndLastUpdatedTime = function (username, sql, callback) {
|
||||
|
||||
var query = [
|
||||
'WITH querytables AS (',
|
||||
@ -44,7 +47,7 @@ QueryTablesApi.prototype.getAffectedTablesAndLastUpdatedTime = function (usernam
|
||||
'WHERE m.tabname = any ((SELECT tablenames from querytables)::regclass[])'
|
||||
].join(' ');
|
||||
|
||||
runQuery(username, options, query, handleAffectedTablesAndLastUpdatedTimeRows, callback);
|
||||
this.runQuery(username, query, handleAffectedTablesAndLastUpdatedTimeRows, callback);
|
||||
};
|
||||
|
||||
function handleAffectedTablesAndLastUpdatedTimeRows(err, rows, callback) {
|
||||
@ -68,20 +71,60 @@ function handleAffectedTablesAndLastUpdatedTimeRows(err, rows, callback) {
|
||||
}
|
||||
|
||||
|
||||
function runQuery(username, options, query, queryHandler, callback) {
|
||||
QueryTablesApi.prototype.runQuery = function(username, query, queryHandler, callback) {
|
||||
var self = this;
|
||||
|
||||
if (shouldQueryPostgresDirectly()) {
|
||||
var psql = new PSQL(options);
|
||||
psql.query(query, function(err, resultSet) {
|
||||
resultSet = resultSet || {};
|
||||
var rows = resultSet.rows || [];
|
||||
queryHandler(err, rows, callback);
|
||||
});
|
||||
|
||||
var params = {};
|
||||
|
||||
Step(
|
||||
function setAuth() {
|
||||
self.pgConnection.setDBAuth(username, params, this);
|
||||
},
|
||||
function setConn(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
self.pgConnection.setDBConn(username, params, this);
|
||||
},
|
||||
function executeQuery(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
var psql = new PSQL({
|
||||
user: params.dbuser,
|
||||
pass: params.dbpass,
|
||||
host: params.dbhost,
|
||||
port: params.dbport,
|
||||
dbname: params.dbname
|
||||
});
|
||||
psql.query(query, function(err, resultSet) {
|
||||
resultSet = resultSet || {};
|
||||
var rows = resultSet.rows || [];
|
||||
queryHandler(err, rows, callback);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
} else {
|
||||
sqlApi.query(username, options.api_key, query, function(err, rows) {
|
||||
queryHandler(err, rows, callback);
|
||||
});
|
||||
|
||||
Step(
|
||||
function getApiKey() {
|
||||
self.metadataBackend.getUserMapKey(username, this);
|
||||
},
|
||||
function executeQuery(err, apiKey) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
sqlApi.query(username, apiKey, query, function(err, rows) {
|
||||
queryHandler(err, rows, callback);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function prepareSql(sql) {
|
||||
|
96
lib/cartodb/backends/pg_connection.js
Normal file
96
lib/cartodb/backends/pg_connection.js
Normal file
@ -0,0 +1,96 @@
|
||||
var Step = require('step');
|
||||
var _ = require('underscore');
|
||||
|
||||
function PgConnection(metadataBackend) {
|
||||
this.metadataBackend = metadataBackend;
|
||||
}
|
||||
|
||||
module.exports = PgConnection;
|
||||
|
||||
|
||||
// Set db authentication parameters to those of the given username
|
||||
//
|
||||
// @param username the cartodb username, mapped to a database username
|
||||
// via CartodbRedis metadata records
|
||||
//
|
||||
// @param params the parameters to set auth options into
|
||||
// added params are: "dbuser" and "dbpassword"
|
||||
//
|
||||
// @param callback function(err)
|
||||
//
|
||||
PgConnection.prototype.setDBAuth = function(username, params, callback) {
|
||||
var self = this;
|
||||
|
||||
var user_params = {};
|
||||
var auth_user = global.environment.postgres_auth_user;
|
||||
var auth_pass = global.environment.postgres_auth_pass;
|
||||
Step(
|
||||
function getId() {
|
||||
self.metadataBackend.getUserId(username, this);
|
||||
},
|
||||
function(err, user_id) {
|
||||
if (err) throw err;
|
||||
user_params['user_id'] = user_id;
|
||||
var dbuser = _.template(auth_user, user_params);
|
||||
_.extend(params, {dbuser:dbuser});
|
||||
|
||||
// skip looking up user_password if postgres_auth_pass
|
||||
// doesn't contain the "user_password" label
|
||||
if (!auth_pass || ! auth_pass.match(/\buser_password\b/) ) return null;
|
||||
|
||||
self.metadataBackend.getUserDBPass(username, this);
|
||||
},
|
||||
function(err, user_password) {
|
||||
if (err) throw err;
|
||||
user_params['user_password'] = user_password;
|
||||
if ( auth_pass ) {
|
||||
var dbpass = _.template(auth_pass, user_params);
|
||||
_.extend(params, {dbpassword:dbpass});
|
||||
}
|
||||
return true;
|
||||
},
|
||||
function finish(err) {
|
||||
callback(err);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// Set db connection parameters to those for the given username
|
||||
//
|
||||
// @param dbowner cartodb username of database owner,
|
||||
// mapped to a database username
|
||||
// via CartodbRedis metadata records
|
||||
//
|
||||
// @param params the parameters to set connection options into
|
||||
// added params are: "dbname", "dbhost"
|
||||
//
|
||||
// @param callback function(err)
|
||||
//
|
||||
PgConnection.prototype.setDBConn = function(dbowner, params, callback) {
|
||||
var self = this;
|
||||
// Add default database connection parameters
|
||||
// if none given
|
||||
_.defaults(params, {
|
||||
dbuser: global.environment.postgres.user,
|
||||
dbpassword: global.environment.postgres.password,
|
||||
dbhost: global.environment.postgres.host,
|
||||
dbport: global.environment.postgres.port
|
||||
});
|
||||
Step(
|
||||
function getConnectionParams() {
|
||||
self.metadataBackend.getUserDBConnectionParams(dbowner, this);
|
||||
},
|
||||
function extendParams(err, dbParams){
|
||||
if (err) throw err;
|
||||
// we don't want null values or overwrite a non public user
|
||||
if (params.dbuser != 'publicuser' || !dbParams.dbuser) {
|
||||
delete dbParams.dbuser;
|
||||
}
|
||||
if ( dbParams ) _.extend(params, dbParams);
|
||||
return null;
|
||||
},
|
||||
function finish(err) {
|
||||
callback(err);
|
||||
}
|
||||
);
|
||||
};
|
@ -152,7 +152,14 @@ var CartodbWindshaft = function(serverOptions) {
|
||||
|
||||
var TemplateMapsController = require('./controllers/template_maps'),
|
||||
templateMapsController = new TemplateMapsController(
|
||||
ws, serverOptions, templateMaps, cartoData, template_baseurl, surrogateKeysCache, NamedMapsCacheEntry
|
||||
ws,
|
||||
serverOptions,
|
||||
templateMaps,
|
||||
cartoData,
|
||||
template_baseurl,
|
||||
surrogateKeysCache,
|
||||
NamedMapsCacheEntry,
|
||||
serverOptions.pgConnection
|
||||
);
|
||||
templateMapsController.register(ws);
|
||||
|
||||
|
@ -2,7 +2,7 @@ var Step = require('step');
|
||||
var _ = require('underscore');
|
||||
|
||||
function TemplateMapsController(app, serverOptions, templateMaps, metadataBackend, templateBaseUrl, surrogateKeysCache,
|
||||
NamedMapsCacheEntry) {
|
||||
NamedMapsCacheEntry, pgConnection) {
|
||||
this.app = app;
|
||||
this.serverOptions = serverOptions;
|
||||
this.templateMaps = templateMaps;
|
||||
@ -10,6 +10,7 @@ function TemplateMapsController(app, serverOptions, templateMaps, metadataBacken
|
||||
this.templateBaseUrl = templateBaseUrl;
|
||||
this.surrogateKeysCache = surrogateKeysCache;
|
||||
this.NamedMapsCacheEntry = NamedMapsCacheEntry;
|
||||
this.pgConnection = pgConnection;
|
||||
}
|
||||
|
||||
module.exports = TemplateMapsController;
|
||||
@ -465,11 +466,11 @@ TemplateMapsController.prototype.setDBParams = function(cdbuser, params, callbac
|
||||
var self = this;
|
||||
Step(
|
||||
function setAuth() {
|
||||
self.serverOptions.setDBAuth(cdbuser, params, this);
|
||||
self.pgConnection.setDBAuth(cdbuser, params, this);
|
||||
},
|
||||
function setConn(err) {
|
||||
if ( err ) throw err;
|
||||
self.serverOptions.setDBConn(cdbuser, params, this);
|
||||
self.pgConnection.setDBConn(cdbuser, params, this);
|
||||
},
|
||||
function finish(err) {
|
||||
callback(err);
|
||||
|
@ -1,6 +1,7 @@
|
||||
var _ = require('underscore');
|
||||
var Step = require('step');
|
||||
var QueryTablesApi = require('./api/query_tables_api');
|
||||
var PgConnection = require('./backends/pg_connection');
|
||||
var crypto = require('crypto');
|
||||
var LZMA = require('lzma').LZMA;
|
||||
var TemplateMaps = require('./template_maps.js');
|
||||
@ -39,7 +40,8 @@ module.exports = function(redisPool) {
|
||||
|
||||
var cartoData = require('cartodb-redis')({ pool: redisPool }),
|
||||
lzmaWorker = new LZMA(),
|
||||
queryTablesApi = new QueryTablesApi();
|
||||
pgConnection = new PgConnection(cartoData),
|
||||
queryTablesApi = new QueryTablesApi(pgConnection, cartoData);
|
||||
|
||||
var rendererConfig = _.defaults(global.environment.renderer || {}, {
|
||||
cache_ttl: 60000, // milliseconds
|
||||
@ -106,6 +108,9 @@ module.exports = function(redisPool) {
|
||||
// Re-use redisPool
|
||||
me.redis.pool = redisPool;
|
||||
|
||||
// Re-use pgConnection
|
||||
me.pgConnection = pgConnection;
|
||||
|
||||
var templateMaps = new TemplateMaps(redisPool, {
|
||||
max_user_templates: global.environment.maxUserTemplates
|
||||
});
|
||||
@ -203,38 +208,8 @@ module.exports = function(redisPool) {
|
||||
}
|
||||
return [req.params.table];
|
||||
}
|
||||
var user, key;
|
||||
var next = this;
|
||||
Step (
|
||||
function findUserKey() {
|
||||
if ( req.params.hasOwnProperty('_authorizedBySigner') ) {
|
||||
user = req.params._authorizedBySigner;
|
||||
cartoData.getUserMapKey(user, this);
|
||||
} else {
|
||||
user = that.userByReq(req);
|
||||
key = req.params.map_key || req.params.api_key;
|
||||
return null;
|
||||
}
|
||||
},
|
||||
function getAffected(err, data) {
|
||||
if ( err ) throw err;
|
||||
if ( data ) {
|
||||
if ( req.profiler ) req.profiler.done('getSignerMapKey');
|
||||
key = data;
|
||||
}
|
||||
queryTablesApi.getAffectedTablesInQuery(user, {
|
||||
user: req.params.dbuser,
|
||||
pass: req.params.dbpass,
|
||||
host: req.params.dbhost,
|
||||
port: req.params.dbport,
|
||||
dbname: req.params.dbname,
|
||||
api_key: key
|
||||
}, sql, this); // in addCacheChannel
|
||||
},
|
||||
function finish(err, data) {
|
||||
next(err,data);
|
||||
}
|
||||
);
|
||||
|
||||
queryTablesApi.getAffectedTablesInQuery(that.userByReq(req), sql, this); // in addCacheChannel
|
||||
},
|
||||
function buildCacheChannel(err, tableNames) {
|
||||
if ( err ) throw err;
|
||||
@ -306,17 +281,11 @@ module.exports = function(redisPool) {
|
||||
};
|
||||
|
||||
me.beforeLayergroupCreate = function(req, requestMapConfig, callback) {
|
||||
mapConfigNamedLayersAdapter.getLayers(this.userByReq(req), requestMapConfig.layers, this, function(err, layers, datasource) {
|
||||
mapConfigNamedLayersAdapter.getLayers(this.userByReq(req), requestMapConfig.layers, pgConnection, function(err, layers, datasource) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if (!datasource.isEmpty()) {
|
||||
setContext(req, 'queryTablesApiDatasource', _.find(datasource.layersDbParams, function(layerDbParams) {
|
||||
return !!layerDbParams;
|
||||
}));
|
||||
}
|
||||
|
||||
requestMapConfig.layers = layers;
|
||||
return callback(null, requestMapConfig, datasource)
|
||||
});
|
||||
@ -357,30 +326,16 @@ module.exports = function(redisPool) {
|
||||
done();
|
||||
});
|
||||
|
||||
var sql = [];
|
||||
_.each(mapconfig.layers, function(lyr) {
|
||||
sql.push(lyr.options.sql);
|
||||
});
|
||||
sql = sql.join(';');
|
||||
var sql = mapconfig.layers.map(function(layer) {
|
||||
return layer.options.sql;
|
||||
}).join(';');
|
||||
|
||||
var dbName = req.params.dbname;
|
||||
var usr = this.userByReq(req);
|
||||
var key = req.params.map_key || req.params.api_key;
|
||||
|
||||
var cacheKey = dbName + ':' + token;
|
||||
|
||||
Step(
|
||||
function getAffectedTablesAndLastUpdatedTime() {
|
||||
var queryTablesOpts = {
|
||||
user: req.params.dbuser,
|
||||
pass: req.params.dbpass,
|
||||
host: req.params.dbhost,
|
||||
port: req.params.dbport,
|
||||
dbname: req.params.dbname,
|
||||
api_key: key
|
||||
};
|
||||
_.extend(queryTablesOpts, getContext(req, 'queryTablesApiDatasource'));
|
||||
queryTablesApi.getAffectedTablesAndLastUpdatedTime(usr, queryTablesOpts, sql, this);
|
||||
queryTablesApi.getAffectedTablesAndLastUpdatedTime(username, sql, this);
|
||||
},
|
||||
function handleAffectedTablesAndLastUpdatedTime(err, result) {
|
||||
if (req.profiler) req.profiler.done('queryTablesAndLastUpdated');
|
||||
@ -435,92 +390,6 @@ module.exports = function(redisPool) {
|
||||
return mat[1];
|
||||
};
|
||||
|
||||
// Set db authentication parameters to those of the given username
|
||||
//
|
||||
// @param username the cartodb username, mapped to a database username
|
||||
// via CartodbRedis metadata records
|
||||
//
|
||||
// @param params the parameters to set auth options into
|
||||
// added params are: "dbuser" and "dbpassword"
|
||||
//
|
||||
// @param callback function(err)
|
||||
//
|
||||
me.setDBAuth = function(username, params, callback) {
|
||||
|
||||
var user_params = {};
|
||||
var auth_user = global.environment.postgres_auth_user;
|
||||
var auth_pass = global.environment.postgres_auth_pass;
|
||||
Step(
|
||||
function getId() {
|
||||
cartoData.getUserId(username, this);
|
||||
},
|
||||
function(err, user_id) {
|
||||
if (err) throw err;
|
||||
user_params['user_id'] = user_id;
|
||||
var dbuser = _.template(auth_user, user_params);
|
||||
_.extend(params, {dbuser:dbuser});
|
||||
|
||||
// skip looking up user_password if postgres_auth_pass
|
||||
// doesn't contain the "user_password" label
|
||||
if (!auth_pass || ! auth_pass.match(/\buser_password\b/) ) return null;
|
||||
|
||||
cartoData.getUserDBPass(username, this);
|
||||
},
|
||||
function(err, user_password) {
|
||||
if (err) throw err;
|
||||
user_params['user_password'] = user_password;
|
||||
if ( auth_pass ) {
|
||||
var dbpass = _.template(auth_pass, user_params);
|
||||
_.extend(params, {dbpassword:dbpass});
|
||||
}
|
||||
return true;
|
||||
},
|
||||
function finish(err) {
|
||||
callback(err);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// Set db connection parameters to those for the given username
|
||||
//
|
||||
// @param dbowner cartodb username of database owner,
|
||||
// mapped to a database username
|
||||
// via CartodbRedis metadata records
|
||||
//
|
||||
// @param params the parameters to set connection options into
|
||||
// added params are: "dbname", "dbhost"
|
||||
//
|
||||
// @param callback function(err)
|
||||
//
|
||||
me.setDBConn = function(dbowner, params, callback) {
|
||||
// Add default database connection parameters
|
||||
// if none given
|
||||
_.defaults(params, {
|
||||
dbuser: global.environment.postgres.user,
|
||||
dbpassword: global.environment.postgres.password,
|
||||
dbhost: global.environment.postgres.host,
|
||||
dbport: global.environment.postgres.port
|
||||
});
|
||||
Step(
|
||||
function getConnectionParams() {
|
||||
cartoData.getUserDBConnectionParams(dbowner, this);
|
||||
},
|
||||
function extendParams(err, dbParams){
|
||||
if (err) throw err;
|
||||
// we don't want null values or overwrite a non public user
|
||||
if (params.dbuser != 'publicuser' || !dbParams.dbuser) {
|
||||
delete dbParams.dbuser;
|
||||
}
|
||||
if ( dbParams ) _.extend(params, dbParams);
|
||||
return null;
|
||||
},
|
||||
function finish(err) {
|
||||
callback(err);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
// Check if a request is authorized by a signer
|
||||
//
|
||||
// @param req express request object
|
||||
@ -619,7 +488,7 @@ module.exports = function(redisPool) {
|
||||
_.extend(req.params, { _authorizedByApiKey: true });
|
||||
|
||||
// authorized by api key, login as the given username and stop
|
||||
that.setDBAuth(user, req.params, function(err) {
|
||||
pgConnection.setDBAuth(user, req.params, function(err) {
|
||||
callback(err, true); // authorized (or error)
|
||||
});
|
||||
},
|
||||
@ -654,7 +523,7 @@ module.exports = function(redisPool) {
|
||||
|
||||
// Authorized by "signed_by" !
|
||||
_.extend(req.params, { _authorizedBySigner: signed_by });
|
||||
that.setDBAuth(signed_by, req.params, function(err) {
|
||||
pgConnection.setDBAuth(signed_by, req.params, function(err) {
|
||||
if (req.profiler) req.profiler.done('setDBAuth');
|
||||
callback(err, true); // authorized (or error)
|
||||
});
|
||||
@ -768,7 +637,7 @@ module.exports = function(redisPool) {
|
||||
},
|
||||
function getDatabase(err){
|
||||
if(err) throw err;
|
||||
that.setDBConn(user, req.params, this);
|
||||
pgConnection.setDBConn(user, req.params, this);
|
||||
},
|
||||
function getGeometryType(err){
|
||||
if (req.profiler) req.profiler.done('setDBConn');
|
||||
@ -852,36 +721,5 @@ module.exports = function(redisPool) {
|
||||
);
|
||||
};
|
||||
|
||||
/*******************************************************************************************************************
|
||||
* Private methods
|
||||
******************************************************************************************************************/
|
||||
|
||||
/**
|
||||
* Handles context for a given Request object
|
||||
* @param {Object|IncomingMessage} req
|
||||
* @param {String} key
|
||||
* @returns {*}
|
||||
*/
|
||||
function getContext(req, key) {
|
||||
return req.context && req.context[key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles context for a given Request object
|
||||
* @param {Object|IncomingMessage} req
|
||||
* @param {String} key
|
||||
* @param {*} value
|
||||
* @returns {*} The previous value
|
||||
*/
|
||||
function setContext(req, key, value) {
|
||||
var previousValue;
|
||||
if (value) {
|
||||
req.context = req.context || {};
|
||||
previousValue = req.context[key];
|
||||
req.context[key] = value;
|
||||
}
|
||||
return previousValue;
|
||||
}
|
||||
|
||||
return me;
|
||||
};
|
||||
|
@ -24,8 +24,6 @@ server.setMaxListeners(0);
|
||||
|
||||
[true, false].forEach(function(cdbQueryTablesFromPostgresEnabledValue) {
|
||||
|
||||
global.environment.enabledFeatures = {cdbQueryTablesFromPostgres: cdbQueryTablesFromPostgresEnabledValue};
|
||||
|
||||
suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function() {
|
||||
|
||||
var redis_client = redis.createClient(global.environment.redis.port);
|
||||
@ -38,6 +36,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
var test_database = test_user + '_db';
|
||||
|
||||
suiteSetup(function(done){
|
||||
global.environment.enabledFeatures = { cdbQueryTablesFromPostgres: cdbQueryTablesFromPostgresEnabledValue };
|
||||
sqlapi_server = new SQLAPIEmu(global.environment.sqlapi.port, done);
|
||||
});
|
||||
|
||||
@ -112,15 +111,17 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
assert.ok(cc);
|
||||
var dbname = test_database;
|
||||
assert.equal(cc.substring(0, dbname.length), dbname);
|
||||
var jsonquery = cc.substring(dbname.length+1);
|
||||
var sentquery = JSON.parse(jsonquery);
|
||||
var expectedQuery = [layergroup.layers[0].options.sql, ';', layergroup.layers[1].options.sql].join('');
|
||||
assert.equal(sentquery.q, 'WITH querytables AS ( SELECT * FROM CDB_QueryTables($windshaft$'
|
||||
+ expectedQuery
|
||||
+ '$windshaft$) as tablenames )'
|
||||
+ ' SELECT (SELECT tablenames FROM querytables), EXTRACT(EPOCH FROM max(updated_at)) as max'
|
||||
+ ' FROM CDB_TableMetadata m'
|
||||
+ ' WHERE m.tabname = any ((SELECT tablenames from querytables)::regclass[])');
|
||||
if (!cdbQueryTablesFromPostgresEnabledValue) { // only test if it was using the SQL API
|
||||
var jsonquery = cc.substring(dbname.length + 1);
|
||||
var sentquery = JSON.parse(jsonquery);
|
||||
var expectedQuery = [layergroup.layers[0].options.sql, ';', layergroup.layers[1].options.sql].join('');
|
||||
assert.equal(sentquery.q, 'WITH querytables AS ( SELECT * FROM CDB_QueryTables($windshaft$'
|
||||
+ expectedQuery
|
||||
+ '$windshaft$) as tablenames )'
|
||||
+ ' SELECT (SELECT tablenames FROM querytables), EXTRACT(EPOCH FROM max(updated_at)) as max'
|
||||
+ ' FROM CDB_TableMetadata m'
|
||||
+ ' WHERE m.tabname = any ((SELECT tablenames from querytables)::regclass[])');
|
||||
}
|
||||
|
||||
assert.imageEqualsFile(res.body, 'test/fixtures/test_table_0_0_0_multilayer1.png', IMAGE_EQUALS_HIGHER_TOLERANCE_PER_MIL,
|
||||
function(err, similarity) {
|
||||
@ -337,7 +338,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
layers: [
|
||||
{ options: {
|
||||
sql: 'select 1 as cartodb_id, '
|
||||
+ 'ST_Buffer(!bbox!, -32*greatest(!pixel_width!,!pixel_height!)) as the_geom_webmercator',
|
||||
+ 'ST_Buffer(!bbox!, -32*greatest(!pixel_width!,!pixel_height!)) as the_geom_webmercator from test_table limit 1',
|
||||
cartocss: '#layer { polygon-fill:red; }',
|
||||
cartocss_version: '2.0.1',
|
||||
interactivity: 'cartodb_id'
|
||||
@ -388,21 +389,23 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
|
||||
// Check X-Cache-Channel
|
||||
var cc = res.headers['x-cache-channel'];
|
||||
assert.ok(cc);
|
||||
assert.ok(cc);
|
||||
var dbname = test_database;
|
||||
assert.equal(cc.substring(0, dbname.length), dbname);
|
||||
var jsonquery = cc.substring(dbname.length+1);
|
||||
var sentquery = JSON.parse(jsonquery);
|
||||
var expectedQuery = layergroup.layers[0].options.sql
|
||||
.replace(/!bbox!/g, 'ST_MakeEnvelope(0,0,0,0)')
|
||||
.replace(/!pixel_width!/g, '1')
|
||||
.replace(/!pixel_height!/g, '1');
|
||||
assert.equal(sentquery.q, 'WITH querytables AS ( SELECT * FROM CDB_QueryTables($windshaft$'
|
||||
+ expectedQuery
|
||||
+ '$windshaft$) as tablenames )'
|
||||
+ ' SELECT (SELECT tablenames FROM querytables), EXTRACT(EPOCH FROM max(updated_at)) as max'
|
||||
+ ' FROM CDB_TableMetadata m'
|
||||
+ ' WHERE m.tabname = any ((SELECT tablenames from querytables)::regclass[])');
|
||||
if (!cdbQueryTablesFromPostgresEnabledValue) { // only test if it was using the SQL API
|
||||
var jsonquery = cc.substring(dbname.length + 1);
|
||||
var sentquery = JSON.parse(jsonquery);
|
||||
var expectedQuery = layergroup.layers[0].options.sql
|
||||
.replace(/!bbox!/g, 'ST_MakeEnvelope(0,0,0,0)')
|
||||
.replace(/!pixel_width!/g, '1')
|
||||
.replace(/!pixel_height!/g, '1');
|
||||
assert.equal(sentquery.q, 'WITH querytables AS ( SELECT * FROM CDB_QueryTables($windshaft$'
|
||||
+ expectedQuery
|
||||
+ '$windshaft$) as tablenames )'
|
||||
+ ' SELECT (SELECT tablenames FROM querytables), EXTRACT(EPOCH FROM max(updated_at)) as max'
|
||||
+ ' FROM CDB_TableMetadata m'
|
||||
+ ' WHERE m.tabname = any ((SELECT tablenames from querytables)::regclass[])');
|
||||
}
|
||||
|
||||
assert.imageEqualsFile(res.body, 'test/fixtures/test_multilayer_bbox.png', IMAGE_EQUALS_TOLERANCE_PER_MIL,
|
||||
function(err, similarity) {
|
||||
@ -428,18 +431,20 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
assert.ok(cc);
|
||||
var dbname = test_database;
|
||||
assert.equal(cc.substring(0, dbname.length), dbname);
|
||||
var jsonquery = cc.substring(dbname.length+1);
|
||||
var sentquery = JSON.parse(jsonquery);
|
||||
var expectedQuery = layergroup.layers[0].options.sql
|
||||
.replace('!bbox!', 'ST_MakeEnvelope(0,0,0,0)')
|
||||
.replace('!pixel_width!', '1')
|
||||
.replace('!pixel_height!', '1');
|
||||
assert.equal(sentquery.q, 'WITH querytables AS ( SELECT * FROM CDB_QueryTables($windshaft$'
|
||||
+ expectedQuery
|
||||
+ '$windshaft$) as tablenames )'
|
||||
+ ' SELECT (SELECT tablenames FROM querytables), EXTRACT(EPOCH FROM max(updated_at)) as max'
|
||||
+ ' FROM CDB_TableMetadata m'
|
||||
+ ' WHERE m.tabname = any ((SELECT tablenames from querytables)::regclass[])');
|
||||
if (!cdbQueryTablesFromPostgresEnabledValue) { // only test if it was using the SQL API
|
||||
var jsonquery = cc.substring(dbname.length + 1);
|
||||
var sentquery = JSON.parse(jsonquery);
|
||||
var expectedQuery = layergroup.layers[0].options.sql
|
||||
.replace('!bbox!', 'ST_MakeEnvelope(0,0,0,0)')
|
||||
.replace('!pixel_width!', '1')
|
||||
.replace('!pixel_height!', '1');
|
||||
assert.equal(sentquery.q, 'WITH querytables AS ( SELECT * FROM CDB_QueryTables($windshaft$'
|
||||
+ expectedQuery
|
||||
+ '$windshaft$) as tablenames )'
|
||||
+ ' SELECT (SELECT tablenames FROM querytables), EXTRACT(EPOCH FROM max(updated_at)) as max'
|
||||
+ ' FROM CDB_TableMetadata m'
|
||||
+ ' WHERE m.tabname = any ((SELECT tablenames from querytables)::regclass[])');
|
||||
}
|
||||
|
||||
assert.imageEqualsFile(res.body, 'test/fixtures/test_multilayer_bbox.png', IMAGE_EQUALS_TOLERANCE_PER_MIL,
|
||||
function(err, similarity) {
|
||||
@ -1218,8 +1223,10 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
var parsedBody = JSON.parse(res.body);
|
||||
var token_components = parsedBody.layergroupid.split(':');
|
||||
expected_token = token_components[0];
|
||||
var last_request = sqlapi_server.getLastRequest();
|
||||
assert.equal(last_request.method, 'POST');
|
||||
if (!cdbQueryTablesFromPostgresEnabledValue) { // only test if it was using the SQL API
|
||||
var last_request = sqlapi_server.getLastRequest();
|
||||
assert.equal(last_request.method, 'POST');
|
||||
}
|
||||
return null;
|
||||
},
|
||||
function cleanup(err) {
|
||||
@ -1283,6 +1290,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
);
|
||||
});
|
||||
|
||||
if (!cdbQueryTablesFromPostgresEnabledValue) { // only test if it was using the SQL API
|
||||
// See https://github.com/CartoDB/Windshaft-cartodb/issues/167
|
||||
test("lack of response from sql-api will result in a timeout", function(done) {
|
||||
|
||||
@ -1323,6 +1331,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
var layergroupTtlRequest = {
|
||||
url: '/tiles/layergroup?config=' + encodeURIComponent(JSON.stringify({
|
||||
|
@ -430,7 +430,7 @@ suite('named_layers', function() {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
//assert.ok(res.headers['X-Cache-Channel']); -> https://github.com/CartoDB/Windshaft-cartodb/issues/253
|
||||
test_helper.checkCache(res);
|
||||
return true;
|
||||
},
|
||||
function deleteTemplate(err) {
|
||||
|
@ -20,10 +20,7 @@ server.setMaxListeners(0);
|
||||
|
||||
[true, false].forEach(function(cdbQueryTablesFromPostgresEnabledValue) {
|
||||
|
||||
global.environment.enabledFeatures = {cdbQueryTablesFromPostgres: cdbQueryTablesFromPostgresEnabledValue};
|
||||
|
||||
suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function() {
|
||||
|
||||
suite('server:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function() {
|
||||
|
||||
var redis_client = redis.createClient(global.environment.redis.port);
|
||||
var sqlapi_server;
|
||||
@ -47,8 +44,9 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
// A couple of styles to use during testing
|
||||
var test_style_black_200 = "#test_table{marker-fill:black;marker-line-color:red;marker-width:10}";
|
||||
var test_style_black_210 = "#test_table{marker-fill:black;marker-line-color:red;marker-width:20}";
|
||||
|
||||
|
||||
suiteSetup(function(done){
|
||||
global.environment.enabledFeatures = { cdbQueryTablesFromPostgres: cdbQueryTablesFromPostgresEnabledValue };
|
||||
sqlapi_server = new SQLAPIEmu(global.environment.sqlapi.port, done);
|
||||
});
|
||||
|
||||
@ -1152,10 +1150,12 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
assert(cc, 'Missing X-Cache-Channel');
|
||||
var dbname = test_database;
|
||||
assert.equal(cc.substring(0, dbname.length), dbname);
|
||||
var jsonquery = cc.substring(dbname.length+1);
|
||||
var sentquery = JSON.parse(jsonquery);
|
||||
assert.equal(sentquery.api_key, qo.map_key);
|
||||
assert.equal(sentquery.q, 'SELECT CDB_QueryTables($windshaft$' + qo.sql + '$windshaft$)');
|
||||
if (!cdbQueryTablesFromPostgresEnabledValue) { // only test if it was using the SQL API
|
||||
var jsonquery = cc.substring(dbname.length + 1);
|
||||
var sentquery = JSON.parse(jsonquery);
|
||||
assert.equal(sentquery.api_key, qo.map_key);
|
||||
assert.equal(sentquery.q, 'SELECT CDB_QueryTables($windshaft$' + qo.sql + '$windshaft$)');
|
||||
}
|
||||
return null;
|
||||
},
|
||||
function finish(err) {
|
||||
@ -1164,6 +1164,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
);
|
||||
});
|
||||
|
||||
if (!cdbQueryTablesFromPostgresEnabledValue) { // only test if it was a post if it's using the SQL API
|
||||
test("passes hostname header to sqlapi", function(done){
|
||||
var qo = {
|
||||
sql: "SELECT * from gadm4",
|
||||
@ -1195,7 +1196,9 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
if (!cdbQueryTablesFromPostgresEnabledValue) { // only test if it was using the SQL API
|
||||
test("requests to skip cache on sqlapi error", function(done){
|
||||
var qo = {
|
||||
sql: "SELECT g.cartodb_id, g.codineprov, t.the_geom_webmercator "
|
||||
@ -1228,6 +1231,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Zoom is a special variable
|
||||
test("Specifying zoom level in CartoCSS does not need a 'zoom' variable in SQL output", function(done){
|
||||
|
@ -27,9 +27,8 @@ server.setMaxListeners(0);
|
||||
|
||||
[true, false].forEach(function(cdbQueryTablesFromPostgresEnabledValue) {
|
||||
|
||||
global.environment.enabledFeatures = {cdbQueryTablesFromPostgres: cdbQueryTablesFromPostgresEnabledValue};
|
||||
|
||||
suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function() {
|
||||
serverOptions.channelCache = {};
|
||||
|
||||
var redis_client = redis.createClient(global.environment.redis.port);
|
||||
var sqlapi_server;
|
||||
@ -37,6 +36,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
var expected_last_updated = new Date(expected_last_updated_epoch).toISOString();
|
||||
|
||||
suiteSetup(function(done){
|
||||
global.environment.enabledFeatures = { cdbQueryTablesFromPostgres: cdbQueryTablesFromPostgresEnabledValue };
|
||||
sqlapi_server = new SQLAPIEmu(global.environment.sqlapi.port, done);
|
||||
// TODO: check redis is clean ?
|
||||
});
|
||||
|
@ -3,7 +3,7 @@ var test_helper = require('../support/test_helper');
|
||||
var assert = require('assert');
|
||||
var RedisPool = require('redis-mpool');
|
||||
var TemplateMaps = require('../../lib/cartodb/template_maps.js');
|
||||
var serverOptions = require(__dirname + '/../../lib/cartodb/server_options')();
|
||||
var PgConnection = require(__dirname + '/../../lib/cartodb/backends/pg_connection');
|
||||
var MapConfigNamedLayersAdapter = require('../../lib/cartodb/models/mapconfig_named_layers_adapter');
|
||||
|
||||
var Step = require('step');
|
||||
@ -11,6 +11,7 @@ var _ = require('underscore');
|
||||
|
||||
// configure redis pool instance to use in tests
|
||||
var redisPool = RedisPool(global.environment.redis);
|
||||
var pgConnection = new PgConnection(require('cartodb-redis')({ pool: redisPool }));
|
||||
|
||||
var templateMaps = new TemplateMaps(redisPool, {
|
||||
max_user_templates: global.environment.maxUserTemplates
|
||||
@ -280,7 +281,7 @@ suite('named_layers datasources', function() {
|
||||
|
||||
testScenarios.forEach(function(testScenario) {
|
||||
test('should return a list of layers ' + testScenario.desc, function(done) {
|
||||
mapConfigNamedLayersAdapter.getLayers(username, testScenario.config.layers, serverOptions, function(err, layers, datasource) {
|
||||
mapConfigNamedLayersAdapter.getLayers(username, testScenario.config.layers, pgConnection, function(err, layers, datasource) {
|
||||
testScenario.test(err, layers, datasource, done);
|
||||
});
|
||||
});
|
||||
|
@ -3,7 +3,7 @@ var testHelper = require('../support/test_helper');
|
||||
var assert = require('assert');
|
||||
var RedisPool = require('redis-mpool');
|
||||
var TemplateMaps = require('../../lib/cartodb/template_maps.js');
|
||||
var serverOptions = require(__dirname + '/../../lib/cartodb/server_options')();
|
||||
var PgConnection = require(__dirname + '/../../lib/cartodb/backends/pg_connection');
|
||||
var MapConfigNamedLayersAdapter = require('../../lib/cartodb/models/mapconfig_named_layers_adapter');
|
||||
|
||||
var Step = require('step');
|
||||
@ -13,6 +13,7 @@ suite('mapconfig_named_layers_adapter', function() {
|
||||
|
||||
// configure redis pool instance to use in tests
|
||||
var redisPool = RedisPool(global.environment.redis);
|
||||
var pgConnection = new PgConnection(require('cartodb-redis')({ pool: redisPool }));
|
||||
|
||||
var templateMaps = new TemplateMaps(redisPool, {
|
||||
max_user_templates: global.environment.maxUserTemplates
|
||||
@ -145,7 +146,7 @@ suite('mapconfig_named_layers_adapter', function() {
|
||||
var missingNamedMapLayerConfig = makeNamedMapLayerConfig({
|
||||
config: {}
|
||||
});
|
||||
mapConfigNamedLayersAdapter.getLayers(username, missingNamedMapLayerConfig.layers, serverOptions, function(err, layers, datasource) {
|
||||
mapConfigNamedLayersAdapter.getLayers(username, missingNamedMapLayerConfig.layers, pgConnection, function(err, layers, datasource) {
|
||||
assert.ok(err);
|
||||
assert.ok(!layers);
|
||||
assert.ok(!datasource);
|
||||
@ -160,7 +161,7 @@ suite('mapconfig_named_layers_adapter', function() {
|
||||
var nonExistentNamedMapLayerConfig = makeNamedMapLayerConfig({
|
||||
name: missingTemplateName
|
||||
});
|
||||
mapConfigNamedLayersAdapter.getLayers(username, nonExistentNamedMapLayerConfig.layers, serverOptions, function(err, layers, datasource) {
|
||||
mapConfigNamedLayersAdapter.getLayers(username, nonExistentNamedMapLayerConfig.layers, pgConnection, function(err, layers, datasource) {
|
||||
assert.ok(err);
|
||||
assert.ok(!layers);
|
||||
assert.ok(!datasource);
|
||||
@ -179,7 +180,7 @@ suite('mapconfig_named_layers_adapter', function() {
|
||||
var nonAuthTokensNamedMapLayerConfig = makeNamedMapLayerConfig({
|
||||
name: tokenAuthTemplateName
|
||||
});
|
||||
mapConfigNamedLayersAdapter.getLayers(username, nonAuthTokensNamedMapLayerConfig.layers, serverOptions, function(err, layers, datasource) {
|
||||
mapConfigNamedLayersAdapter.getLayers(username, nonAuthTokensNamedMapLayerConfig.layers, pgConnection, function(err, layers, datasource) {
|
||||
assert.ok(err);
|
||||
assert.ok(!layers);
|
||||
assert.ok(!datasource);
|
||||
@ -199,7 +200,7 @@ suite('mapconfig_named_layers_adapter', function() {
|
||||
var nestedNamedMapLayerConfig = makeNamedMapLayerConfig({
|
||||
name: nestedNamedMapTemplateName
|
||||
});
|
||||
mapConfigNamedLayersAdapter.getLayers(username, nestedNamedMapLayerConfig.layers, serverOptions, function(err, layers, datasource) {
|
||||
mapConfigNamedLayersAdapter.getLayers(username, nestedNamedMapLayerConfig.layers, pgConnection, function(err, layers, datasource) {
|
||||
assert.ok(err);
|
||||
assert.ok(!layers);
|
||||
assert.ok(!datasource);
|
||||
@ -214,7 +215,7 @@ suite('mapconfig_named_layers_adapter', function() {
|
||||
var validNamedMapMapLayerConfig = makeNamedMapLayerConfig({
|
||||
name: templateName
|
||||
});
|
||||
mapConfigNamedLayersAdapter.getLayers(username, validNamedMapMapLayerConfig.layers, serverOptions, function(err, layers, datasource) {
|
||||
mapConfigNamedLayersAdapter.getLayers(username, validNamedMapMapLayerConfig.layers, pgConnection, function(err, layers, datasource) {
|
||||
assert.ok(!err);
|
||||
assert.ok(layers.length, 1);
|
||||
assert.ok(layers[0].type, 'cartodb');
|
||||
@ -234,7 +235,7 @@ suite('mapconfig_named_layers_adapter', function() {
|
||||
name: tokenAuthTemplateName,
|
||||
auth_tokens: ['valid1']
|
||||
});
|
||||
mapConfigNamedLayersAdapter.getLayers(username, validAuthTokensNamedMapLayerConfig.layers, serverOptions, function(err, layers, datasource) {
|
||||
mapConfigNamedLayersAdapter.getLayers(username, validAuthTokensNamedMapLayerConfig.layers, pgConnection, function(err, layers, datasource) {
|
||||
assert.ok(!err);
|
||||
assert.equal(layers.length, 1);
|
||||
assert.notEqual(datasource.getLayerDatasource(0), undefined);
|
||||
@ -254,7 +255,7 @@ suite('mapconfig_named_layers_adapter', function() {
|
||||
name: multipleLayersTemplateName,
|
||||
auth_tokens: ['valid2']
|
||||
});
|
||||
mapConfigNamedLayersAdapter.getLayers(username, multipleLayersNamedMapLayerConfig.layers, serverOptions, function(err, layers, datasource) {
|
||||
mapConfigNamedLayersAdapter.getLayers(username, multipleLayersNamedMapLayerConfig.layers, pgConnection, function(err, layers, datasource) {
|
||||
assert.ok(!err);
|
||||
assert.equal(layers.length, 2);
|
||||
|
||||
@ -288,7 +289,7 @@ suite('mapconfig_named_layers_adapter', function() {
|
||||
},
|
||||
auth_tokens: ['valid2']
|
||||
});
|
||||
mapConfigNamedLayersAdapter.getLayers(username, multipleLayersNamedMapLayerConfig.layers, serverOptions, function(err, layers, datasource) {
|
||||
mapConfigNamedLayersAdapter.getLayers(username, multipleLayersNamedMapLayerConfig.layers, pgConnection, function(err, layers, datasource) {
|
||||
assert.ok(!err);
|
||||
assert.equal(layers.length, 2);
|
||||
|
||||
|
@ -184,5 +184,8 @@ CREATE TABLE IF NOT EXISTS
|
||||
updated_at timestamp with time zone not null default now()
|
||||
);
|
||||
|
||||
GRANT SELECT ON CDB_TableMetadata TO :PUBLICUSER;
|
||||
INSERT INTO CDB_TableMetadata (tabname, updated_at) VALUES ('test_table'::regclass, '2009-02-13T23:31:30.123Z');
|
||||
INSERT INTO CDB_TableMetadata (tabname, updated_at) VALUES ('test_table_private_1'::regclass, '2009-02-13T23:31:30.123Z');
|
||||
|
||||
-- GRANT SELECT ON CDB_TableMetadata TO :PUBLICUSER;
|
||||
GRANT SELECT ON CDB_TableMetadata TO :TESTUSER;
|
||||
|
Loading…
Reference in New Issue
Block a user