diff --git a/NEWS.md b/NEWS.md index fde7f461..9eeb06e3 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,8 +1,18 @@ # Changelog +## 2.44.0 + +Released 2016-mm-dd + +Adds support for sql wrap in all layers + + ## 2.43.1 -Released 2016-mm-dd +Released 2016-05-19 + +Bug fixes: + - Dataview error when bbox present without query rewrite data #458 ## 2.43.0 diff --git a/lib/cartodb/backends/dataview.js b/lib/cartodb/backends/dataview.js index 44db254e..b7bb6d0b 100644 --- a/lib/cartodb/backends/dataview.js +++ b/lib/cartodb/backends/dataview.js @@ -139,17 +139,19 @@ DataviewBackend.prototype.getDataview = function (mapConfigProvider, user, param if (params.bbox) { var bboxFilter = new BBoxFilter({column: 'the_geom', srid: 4326}, {bbox: params.bbox}); query = bboxFilter.sql(query); - var bbox_filter_definition = { - type: 'bbox', - options: { - column: 'the_geom', - srid: 4326, - }, - params: { - bbox: params.bbox - } - }; - queryRewriteData = _.extend(queryRewriteData, { bbox_filter: bbox_filter_definition }); + if ( queryRewriteData ) { + var bbox_filter_definition = { + type: 'bbox', + options: { + column: 'the_geom', + srid: 4326, + }, + params: { + bbox: params.bbox + } + }; + queryRewriteData = _.extend(queryRewriteData, { bbox_filter: bbox_filter_definition }); + } } var dataviewFactory = DataviewFactoryWithOverviews.getFactory( diff --git a/lib/cartodb/controllers/map.js b/lib/cartodb/controllers/map.js index feaf6fa5..1f8f25bb 100644 --- a/lib/cartodb/controllers/map.js +++ b/lib/cartodb/controllers/map.js @@ -19,6 +19,7 @@ var MapConfigNamedLayersAdapter = require('../models/mapconfig_named_layers_adap var AnalysisMapConfigAdapter = require('../models/analysis-mapconfig-adapter'); var NamedMapMapConfigProvider = require('../models/mapconfig/named_map_provider'); var CreateLayergroupMapConfigProvider = require('../models/mapconfig/create_layergroup_provider'); +var SqlWrapMapConfigAdapter = require('../models/mapconfig/adapter/sql-wrap-mapconfig-adapter'); /** * @param {AuthApi} authApi @@ -52,6 +53,7 @@ function MapController(authApi, pgConnection, templateMaps, mapBackend, metadata this.analysisMapConfigAdapter = new AnalysisMapConfigAdapter(analysisBackend); this.namedLayersAdapter = new MapConfigNamedLayersAdapter(templateMaps); this.overviewsAdapter = overviewsAdapter; + this.sqlWrapMapConfigAdapter = new SqlWrapMapConfigAdapter(); } util.inherits(MapController, BaseController); @@ -139,6 +141,10 @@ MapController.prototype.create = function(req, res, prepareConfigFn) { self.req2params(req, this); }, prepareConfigFn, + function prepareSqlWrap(err, requestMapConfig) { + assert.ifError(err); + self.sqlWrapMapConfigAdapter.getMapConfig(requestMapConfig, this); + }, function prepareAnalysisLayers(err, requestMapConfig) { assert.ifError(err); var analysisConfiguration = { diff --git a/lib/cartodb/models/mapconfig/adapter/sql-wrap-mapconfig-adapter.js b/lib/cartodb/models/mapconfig/adapter/sql-wrap-mapconfig-adapter.js new file mode 100644 index 00000000..6b9a5130 --- /dev/null +++ b/lib/cartodb/models/mapconfig/adapter/sql-wrap-mapconfig-adapter.js @@ -0,0 +1,25 @@ +function SqlWrapMapConfigAdapter() { +} + +module.exports = SqlWrapMapConfigAdapter; + + +SqlWrapMapConfigAdapter.prototype.getMapConfig = function(requestMapConfig, callback) { + if (requestMapConfig && Array.isArray(requestMapConfig.layers)) { + requestMapConfig.layers = requestMapConfig.layers.map(function(layer) { + if (layer.options) { + var sqlQueryWrap = layer.options.sql_wrap; + if (sqlQueryWrap) { + var layerSql = layer.options.sql; + if (layerSql) { + layer.options.sql_raw = layerSql; + layer.options.sql = sqlQueryWrap.replace(/<%=\s*sql\s*%>/g, layerSql); + } + } + } + return layer; + }); + } + + return callback(null, requestMapConfig); +}; diff --git a/lib/cartodb/models/mapconfig/named_map_provider.js b/lib/cartodb/models/mapconfig/named_map_provider.js index e285b2b7..e1ad6fea 100644 --- a/lib/cartodb/models/mapconfig/named_map_provider.js +++ b/lib/cartodb/models/mapconfig/named_map_provider.js @@ -6,6 +6,7 @@ var step = require('step'); var MapConfig = require('windshaft').model.MapConfig; var templateName = require('../../backends/template_maps').templateName; var QueryTables = require('cartodb-query-tables'); +var SqlWrapMapConfigAdapter = require('./adapter/sql-wrap-mapconfig-adapter'); /** * @constructor @@ -22,6 +23,7 @@ function NamedMapMapConfigProvider(templateMaps, pgConnection, metadataBackend, this.turboCartoAdapter = turboCartoAdapter; this.analysisMapConfigAdapter = analysisMapConfigAdapter; this.overviewsAdapter = overviewsAdapter; + this.sqlWrapMapConfigAdapter = new SqlWrapMapConfigAdapter(); this.owner = owner; this.templateName = templateName(templateId); @@ -95,6 +97,10 @@ NamedMapMapConfigProvider.prototype.getMapConfig = function(callback) { assert.ifError(err); return self.templateMaps.instance(self.template, templateParams); }, + function prepareSqlWrap(err, requestMapConfig) { + assert.ifError(err); + self.sqlWrapMapConfigAdapter.getMapConfig(requestMapConfig, this); + }, function prepareAnalysisLayers(err, requestMapConfig) { assert.ifError(err); var analysisConfiguration = { diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index c3b9529d..04f931c5 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,6 +1,6 @@ { "name": "windshaft-cartodb", - "version": "2.42.3", + "version": "2.44.0", "dependencies": { "body-parser": { "version": "1.14.2", diff --git a/package.json b/package.json index ecf28729..c426011b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "windshaft-cartodb", - "version": "2.43.1", + "version": "2.44.0", "description": "A map tile server for CartoDB", "keywords": [ "cartodb" diff --git a/test/acceptance/dataviews/overviews.js b/test/acceptance/dataviews/overviews.js index 9acda126..601124a7 100644 --- a/test/acceptance/dataviews/overviews.js +++ b/test/acceptance/dataviews/overviews.js @@ -58,6 +58,21 @@ describe('dataviews using tables without overviews', function() { }); }); + it("should admit a bbox", function(done) { + var params = { + bbox: "-170,-80,170,80" + }; + var testClient = new TestClient(nonOverviewsMapConfig); + testClient.getDataview('country_places_count', params, function(err, formula_result) { + if (err) { + return done(err); + } + assert.deepEqual(formula_result, { operation: 'count', result: 7253, nulls: 0, type: 'formula' }); + + testClient.drain(done); + }); + }); + describe('filters', function() { describe('category', function () { @@ -77,6 +92,23 @@ describe('dataviews using tables without overviews', function() { testClient.drain(done); }); }); + + it("should expose a filtered formula and admit a bbox", function (done) { + var params = { + filters: { + dataviews: {country_categories: {accept: ['CAN']}} + }, + bbox: "-170,-80,170,80" + }; + var testClient = new TestClient(nonOverviewsMapConfig); + testClient.getDataview('country_places_count', params, function (err, formula_result) { + if (err) { + return done(err); + } + assert.deepEqual(formula_result, { operation: 'count', result: 254, nulls: 0, type: 'formula' }); + testClient.drain(done); + }); + }); }); }); @@ -218,16 +250,32 @@ describe('dataviews using tables with overviews', function() { }); }); + it("should admit a bbox", function(done) { + var params = { + bbox: "-170,-80,170,80" + }; + var testClient = new TestClient(overviewsMapConfig); + testClient.getDataview('test_sum', params, function(err, formula_result) { + if (err) { + return done(err); + } + assert.deepEqual(formula_result, {"operation":"sum","result":15,"nulls":0,"type":"formula"}); + + testClient.drain(done); + }); + }); + describe('filters', function() { describe('category', function () { - it("should expose a filtered formula", function (done) { - var params = { - filters: { - dataviews: {test_categories: {accept: ['Hawai']}} - } - }; + var params = { + filters: { + dataviews: {test_categories: {accept: ['Hawai']}} + } + }; + + it("should expose a filtered sum formula", function (done) { var testClient = new TestClient(overviewsMapConfig); testClient.getDataview('test_sum', params, function (err, formula_result) { if (err) { @@ -236,56 +284,74 @@ describe('dataviews using tables with overviews', function() { assert.deepEqual(formula_result, {"operation":"sum","result":1,"nulls":0,"type":"formula"}); testClient.drain(done); }); - - it("should expose an avg formula", function(done) { - var testClient = new TestClient(overviewsMapConfig); - testClient.getDataview('test_avg', { own_filter: 0 }, function(err, formula_result) { - if (err) { - return done(err); - } - assert.deepEqual(formula_result, {"operation":"avg","result":1,"nulls":0,"type":"formula"}); - - testClient.drain(done); - }); - }); - - it("should expose a count formula", function(done) { - var testClient = new TestClient(overviewsMapConfig); - testClient.getDataview('test_count', { own_filter: 0 }, function(err, formula_result) { - if (err) { - return done(err); - } - assert.deepEqual(formula_result, {"operation":"count","result":1,"nulls":0,"type":"formula"}); - - testClient.drain(done); - }); - }); - - it("should expose a max formula", function(done) { - var testClient = new TestClient(overviewsMapConfig); - testClient.getDataview('test_max', { own_filter: 0 }, function(err, formula_result) { - if (err) { - return done(err); - } - assert.deepEqual(formula_result, {"operation":"max","result":1,"nulls":0,"type":"formula"}); - - testClient.drain(done); - }); - }); - - it("should expose a min formula", function(done) { - var testClient = new TestClient(overviewsMapConfig); - testClient.getDataview('test_min', { own_filter: 0 }, function(err, formula_result) { - if (err) { - return done(err); - } - assert.deepEqual(formula_result, {"operation":"min","result":1,"nulls":0,"type":"formula"}); - - testClient.drain(done); - }); - }); - }); + + it("should expose a filtered avg formula", function(done) { + var testClient = new TestClient(overviewsMapConfig); + testClient.getDataview('test_avg', params, function(err, formula_result) { + if (err) { + return done(err); + } + assert.deepEqual(formula_result, {"operation":"avg","result":1,"nulls":0,"type":"formula"}); + + testClient.drain(done); + }); + }); + + it("should expose a filtered count formula", function(done) { + var testClient = new TestClient(overviewsMapConfig); + testClient.getDataview('test_count', params, function(err, formula_result) { + if (err) { + return done(err); + } + assert.deepEqual(formula_result, {"operation":"count","result":1,"nulls":0,"type":"formula"}); + + testClient.drain(done); + }); + }); + + it("should expose a filterd max formula", function(done) { + var testClient = new TestClient(overviewsMapConfig); + testClient.getDataview('test_max', params, function(err, formula_result) { + if (err) { + return done(err); + } + assert.deepEqual(formula_result, {"operation":"max","result":1,"nulls":0,"type":"formula"}); + + testClient.drain(done); + }); + }); + + it("should expose a filterd min formula", function(done) { + var testClient = new TestClient(overviewsMapConfig); + testClient.getDataview('test_min', params, function(err, formula_result) { + if (err) { + return done(err); + } + assert.deepEqual(formula_result, {"operation":"min","result":1,"nulls":0,"type":"formula"}); + + testClient.drain(done); + }); + }); + + it("should expose a filtered sum formula with bbox", function (done) { + var bboxparams = { + filters: { + dataviews: {test_categories: {accept: ['Hawai']}} + }, + bbox: "-170,-80,170,80" + }; + var testClient = new TestClient(overviewsMapConfig); + testClient.getDataview('test_sum', bboxparams, function (err, formula_result) { + if (err) { + return done(err); + } + assert.deepEqual(formula_result, {"operation":"sum","result":1,"nulls":0,"type":"formula"}); + testClient.drain(done); + }); + }); + + }); }); diff --git a/test/acceptance/sql-wrap.js b/test/acceptance/sql-wrap.js new file mode 100644 index 00000000..7b3c25d8 --- /dev/null +++ b/test/acceptance/sql-wrap.js @@ -0,0 +1,52 @@ +require('../support/test_helper'); + +var assert = require('../support/assert'); +var TestClient = require('../support/test-client'); + +describe('sql-wrap', function() { + + afterEach(function(done) { + if (this.testClient) { + this.testClient.drain(done); + } else { + return done(); + } + }); + + it('should use sql_wrap from layer options', function(done) { + var mapConfig = { + version: '1.5.0', + layers: [ + { + "type": "cartodb", + "options": { + "sql": "SELECT * FROM populated_places_simple_reduced", + "sql_wrap": "SELECT * FROM (<%= sql %>) _w WHERE adm0_a3 = 'USA'", + "cartocss": [ + "#points {", + " marker-fill-opacity: 1;", + " marker-line-color: #FFF;", + " marker-line-width: 0.5;", + " marker-line-opacity: 1;", + " marker-placement: point;", + " marker-type: ellipse;", + " marker-width: 8;", + " marker-fill: red;", + " marker-allow-overlap: true;", + "}" + ].join('\n'), + "cartocss_version": "2.3.0" + } + } + ] + }; + + this.testClient = new TestClient(mapConfig, 1234); + this.testClient.getTile(0, 0, 0, function(err, tile, img) { + assert.ok(!err, err); + var fixtureImg = './test/fixtures/sql-wrap-usa-filter.png'; + assert.imageIsSimilarToFile(img, fixtureImg, 20, done); + }); + }); + +}); diff --git a/test/fixtures/sql-wrap-usa-filter.png b/test/fixtures/sql-wrap-usa-filter.png new file mode 100644 index 00000000..30bd4eb3 Binary files /dev/null and b/test/fixtures/sql-wrap-usa-filter.png differ