From 213a3e297cbf4494ac3ee591b481e213fe2f050a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Fri, 24 Mar 2017 17:28:23 +0100 Subject: [PATCH 001/159] Check node version --- app.js | 10 +++++++++- package.json | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/app.js b/app.js index f6692d36..fe5d7468 100755 --- a/app.js +++ b/app.js @@ -2,8 +2,16 @@ var http = require('http'); var https = require('https'); var path = require('path'); var fs = require('fs'); - var _ = require('underscore'); +var semver = require('semver'); + +var ver = process.versions.node; +ver = ver.split('-')[0]; // explode and truncate tag from version #511 + +if (!semver.satisfies(ver, '>6.9.0')) { + console.log('Node version ' + ver + ' is not supported, please use Node.js 6.9 or higher.'); + process.exit(1); +} // jshint undef:false var log = console.log.bind(console); diff --git a/package.json b/package.json index 14d6d2f8..813c943d 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "queue-async": "~1.0.7", "redis-mpool": "0.4.1", "request": "~2.79.0", + "semver": "~5.3.0", "step": "~0.0.6", "step-profiler": "~0.3.0", "turbo-carto": "0.19.0", @@ -48,7 +49,6 @@ "mocha": "~1.21.4", "nock": "~2.11.0", "redis": "~0.12.1", - "semver": "~1.1.4", "strftime": "~0.8.2" }, "scripts": { From 2374711d6387fb10dd504361a3616cfbe0242227 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Fri, 24 Mar 2017 17:34:05 +0100 Subject: [PATCH 002/159] Remove reference to issue --- app.js | 2 +- yarn.lock | 22 +++++++++------------- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/app.js b/app.js index fe5d7468..dde17dde 100755 --- a/app.js +++ b/app.js @@ -6,7 +6,7 @@ var _ = require('underscore'); var semver = require('semver'); var ver = process.versions.node; -ver = ver.split('-')[0]; // explode and truncate tag from version #511 +ver = ver.split('-')[0]; // explode and truncate tag from version if (!semver.satisfies(ver, '>6.9.0')) { console.log('Node version ' + ver + ' is not supported, please use Node.js 6.9 or higher.'); diff --git a/yarn.lock b/yarn.lock index df4c739d..89e0eb13 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,7 +2,7 @@ # yarn lockfile v1 -"abaculus@github:cartodb/abaculus#2.0.3-cdb1": +abaculus@cartodb/abaculus#2.0.3-cdb1: version "2.0.3-cdb1" resolved "https://codeload.github.com/cartodb/abaculus/tar.gz/f5f34e1c80cdd8d49edd1d6fe3b2220ab2e23aaf" dependencies: @@ -205,7 +205,7 @@ camshaft@0.50.3: dot "^1.0.3" request "^2.69.0" -"canvas@github:cartodb/node-canvas#1.6.2-cdb2": +canvas@cartodb/node-canvas#1.6.2-cdb2: version "1.6.2-cdb2" resolved "https://codeload.github.com/cartodb/node-canvas/tar.gz/8acf04557005c633f9e68524488a2657c04f3766" dependencies: @@ -223,15 +223,15 @@ carto@0.16.3: semver "^5.1.0" yargs "^4.2.0" -"carto@github:cartodb/carto#0.15.1-cdb1": +carto@CartoDB/carto#0.15.1-cdb1: version "0.15.1-cdb1" - resolved "https://codeload.github.com/cartodb/carto/tar.gz/8050ec843f1f32a6469e5d1cf49602773015d398" + resolved "https://codeload.github.com/CartoDB/carto/tar.gz/8050ec843f1f32a6469e5d1cf49602773015d398" dependencies: mapnik-reference "~6.0.2" optimist "~0.6.0" underscore "~1.6.0" -"carto@github:cartodb/carto#0.15.1-cdb3": +carto@cartodb/carto#0.15.1-cdb3: version "0.15.1-cdb3" resolved "https://codeload.github.com/cartodb/carto/tar.gz/945f5efb74fd1af1f5e1f69f409f9567f94fb5a7" dependencies: @@ -1509,7 +1509,7 @@ pg-types@1.*: postgres-date "~1.0.0" postgres-interval "~1.0.0" -"pg@github:cartodb/node-postgres#6.1.2-cdb1": +pg@cartodb/node-postgres#6.1.2-cdb1: version "6.1.2" resolved "https://codeload.github.com/cartodb/node-postgres/tar.gz/3c81aea432ce58d20a795786c58bbb14f68f9689" dependencies: @@ -1820,7 +1820,7 @@ safe-json-stringify@~1: version "1.0.4" resolved "https://registry.yarnpkg.com/safe-json-stringify/-/safe-json-stringify-1.0.4.tgz#81a098f447e4bbc3ff3312a243521bc060ef5911" -"semver@2 || 3 || 4 || 5", semver@^5.1.0, semver@^5.3.0: +"semver@2 || 3 || 4 || 5", semver@^5.1.0, semver@^5.3.0, semver@~5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" @@ -1828,10 +1828,6 @@ semver@4.3.2: version "4.3.2" resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.2.tgz#c7a07158a80bedd052355b770d82d6640f803be7" -semver@~1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/semver/-/semver-1.1.4.tgz#2e5a4e72bab03472cc97f72753b4508912ef5540" - semver@~4.3.3: version "4.3.6" resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.6.tgz#300bc6e0e86374f7ba61068b5b1ecd57fc6532da" @@ -2084,7 +2080,7 @@ through@2: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" -"tilelive-bridge@github:cartodb/tilelive-bridge#2.3.1-cdb1": +tilelive-bridge@cartodb/tilelive-bridge#2.3.1-cdb1: version "2.3.1-cdb1" resolved "https://codeload.github.com/cartodb/tilelive-bridge/tar.gz/3f76c278c782e93d79045870387a0a06bace720b" dependencies: @@ -2092,7 +2088,7 @@ through@2: mapnik-pool "~0.1.3" sphericalmercator "1.0.x" -"tilelive-mapnik@github:cartodb/tilelive-mapnik#0.6.18-cdb1": +tilelive-mapnik@cartodb/tilelive-mapnik#0.6.18-cdb1: version "0.6.18-cdb1" resolved "https://codeload.github.com/cartodb/tilelive-mapnik/tar.gz/cf7e5b4633db653a889a6c6e6a5ddcbcf4ddc3b5" dependencies: From 6a4b412cd31e19c1aa07e531f9eddc06c57d2140 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Mon, 27 Mar 2017 18:27:55 +0200 Subject: [PATCH 003/159] Avoid lint --- app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app.js b/app.js index dde17dde..759b87cc 100755 --- a/app.js +++ b/app.js @@ -8,12 +8,12 @@ var semver = require('semver'); var ver = process.versions.node; ver = ver.split('-')[0]; // explode and truncate tag from version +// jshint undef:false if (!semver.satisfies(ver, '>6.9.0')) { console.log('Node version ' + ver + ' is not supported, please use Node.js 6.9 or higher.'); process.exit(1); } -// jshint undef:false var log = console.log.bind(console); var logError = console.error.bind(console); // jshint undef:true From 399561d076a69ce5ba7aa9b21a147ccc9931d12e Mon Sep 17 00:00:00 2001 From: Raul Ochoa Date: Fri, 4 Aug 2017 17:30:46 +0200 Subject: [PATCH 004/159] Implement LZMA query param inflating as middleware The req2params method is doing too many things, this is an initial step to do fewer things in that method. --- lib/cartodb/controllers/base.js | 34 +--------------- lib/cartodb/middleware/lzma.js | 30 ++++++++++++++ lib/cartodb/server.js | 4 ++ test/unit/cartodb/lzmaMiddleware.test.js | 36 +++++++++++++++++ test/unit/cartodb/req2params.test.js | 51 +++++++++++------------- 5 files changed, 95 insertions(+), 60 deletions(-) create mode 100644 lib/cartodb/middleware/lzma.js create mode 100644 test/unit/cartodb/lzmaMiddleware.test.js diff --git a/lib/cartodb/controllers/base.js b/lib/cartodb/controllers/base.js index 5aab7cb3..43235c82 100644 --- a/lib/cartodb/controllers/base.js +++ b/lib/cartodb/controllers/base.js @@ -4,9 +4,6 @@ var _ = require('underscore'); var step = require('step'); var debug = require('debug')('windshaft:cartodb'); -var LZMA = require('lzma').LZMA; -var lzmaWorker = new LZMA(); - // Whitelist query parameters and attach format var REQUEST_QUERY_PARAMS_WHITELIST = [ 'config', @@ -28,7 +25,7 @@ function BaseController(authApi, pgConnection) { module.exports = BaseController; -// jshint maxcomplexity:10 +// jshint maxcomplexity:8 /** * Whitelist input and get database name & default geometry type from * subdomain/user metadata held in CartoDB Redis @@ -38,35 +35,6 @@ module.exports = BaseController; BaseController.prototype.req2params = function(req, callback){ var self = this; - if ( req.query.lzma ) { - - // Decode (from base64) - var lzma = new Buffer(req.query.lzma, 'base64') - .toString('binary') - .split('') - .map(function(c) { - return c.charCodeAt(0) - 128; - }); - - - // Decompress - lzmaWorker.decompress( - lzma, - function(result) { - req.profiler.done('lzma'); - try { - delete req.query.lzma; - _.extend(req.query, JSON.parse(result)); - self.req2params(req, callback); - } catch (err) { - req.profiler.done('req2params'); - callback(new Error('Error parsing lzma as JSON: ' + err)); - } - } - ); - return; - } - var allowedQueryParams = REQUEST_QUERY_PARAMS_WHITELIST; if (Array.isArray(req.context.allowedQueryParams)) { allowedQueryParams = allowedQueryParams.concat(req.context.allowedQueryParams); diff --git a/lib/cartodb/middleware/lzma.js b/lib/cartodb/middleware/lzma.js new file mode 100644 index 00000000..d58f16cc --- /dev/null +++ b/lib/cartodb/middleware/lzma.js @@ -0,0 +1,30 @@ +'use strict'; + +var LZMA = require('lzma').LZMA; + +var lzmaWorker = new LZMA(); + +module.exports = function lzmaMiddleware(req, res, next) { + if (!req.query.hasOwnProperty('lzma')) { + return next(); + } + + // Decode (from base64) + var lzma = new Buffer(req.query.lzma, 'base64') + .toString('binary') + .split('') + .map(function(c) { + return c.charCodeAt(0) - 128; + }); + + // Decompress + lzmaWorker.decompress(lzma, function(result) { + try { + delete req.query.lzma; + Object.assign(req.query, JSON.parse(result)); + next(); + } catch (err) { + next(new Error('Error parsing lzma as JSON: ' + err)); + } + }); +}; diff --git a/lib/cartodb/server.js b/lib/cartodb/server.js index deb88e16..7177b12f 100644 --- a/lib/cartodb/server.js +++ b/lib/cartodb/server.js @@ -4,6 +4,8 @@ var RedisPool = require('redis-mpool'); var cartodbRedis = require('cartodb-redis'); var _ = require('underscore'); +var lzmaMiddleware = require('./middleware/lzma'); + var controller = require('./controllers'); var SurrogateKeysCache = require('./cache/surrogate_keys_cache'); @@ -345,6 +347,8 @@ function bootstrap(opts) { next(); }); + app.use(lzmaMiddleware); + // temporary measure until we upgrade to newer version expressjs so we can check err.status app.use(function(err, req, res, next) { if (err) { diff --git a/test/unit/cartodb/lzmaMiddleware.test.js b/test/unit/cartodb/lzmaMiddleware.test.js new file mode 100644 index 00000000..9a41030a --- /dev/null +++ b/test/unit/cartodb/lzmaMiddleware.test.js @@ -0,0 +1,36 @@ +var assert = require('assert'); +var testHelper = require('../../support/test_helper'); + +var lzmaMiddleware = require('../../../lib/cartodb/middleware/lzma'); + +describe('lzma-middleware', function() { + + it('it should extend params with decoded lzma', function(done) { + var qo = { + config: { + version: '1.3.0' + } + }; + testHelper.lzma_compress_to_base64(JSON.stringify(qo), 1, function(err, data) { + var req = { + headers: { + host:'localhost' + }, + query: { + api_key: 'test', + lzma: data + } + }; + lzmaMiddleware(req, {}, function(err) { + if ( err ) { + return done(err); + } + var query = req.query; + assert.deepEqual(qo.config, query.config); + assert.equal('test', query.api_key); + done(); + }); + }); + }); + +}); diff --git a/test/unit/cartodb/req2params.test.js b/test/unit/cartodb/req2params.test.js index 6293199e..7b055e99 100644 --- a/test/unit/cartodb/req2params.test.js +++ b/test/unit/cartodb/req2params.test.js @@ -1,6 +1,6 @@ var assert = require('assert'); var _ = require('underscore'); -var test_helper = require('../../support/test_helper'); +require('../../support/test_helper'); var RedisPool = require('redis-mpool'); var cartodbRedis = require('cartodb-redis'); @@ -98,34 +98,31 @@ describe('req2params', function() { }); }); - it('it should extend params with decoded lzma', function(done) { - var qo = { - config: { - version: '1.3.0' + it('it should remove invalid params', function(done) { + var config = { + version: '1.3.0' + }; + var req = { + headers: { + host:'localhost' + }, + query: { + non_included: 'toberemoved', + api_key: 'test', + style: 'override', + config: config } }; - test_helper.lzma_compress_to_base64(JSON.stringify(qo), 1, function(err, data) { - var req = { - headers: { - host:'localhost' - }, - query: { - non_included: 'toberemoved', - api_key: 'test', - style: 'override', - lzma: data - } - }; - baseController.req2params(prepareRequest(req), function(err, req) { - if ( err ) { - return done(err); - } - var query = req.params; - assert.deepEqual(qo.config, query.config); - assert.equal('test', query.api_key); - assert.equal(undefined, query.non_included); - done(); - }); + baseController.req2params(prepareRequest(req), function(err, req) { + if (err) { + return done(err); + } + var query = req.params; + assert.deepEqual(config, query.config); + assert.equal('test', query.api_key); + assert.equal(undefined, query.non_included); + assert.equal(undefined, query.style); + done(); }); }); From fc0dbaaab12b0bbe377eafd6c9ce06440a55467d Mon Sep 17 00:00:00 2001 From: Raul Ochoa Date: Tue, 29 Aug 2017 13:04:20 +0000 Subject: [PATCH 005/159] Going red: Bounding box parameter ignored in static named maps --- test/acceptance/named_maps_static_view.js | 26 +++++++++++++++++- ...ed_places_simple_reduced-override-bbox.png | Bin 0 -> 95961 bytes 2 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 test/fixtures/previews/populated_places_simple_reduced-override-bbox.png diff --git a/test/acceptance/named_maps_static_view.js b/test/acceptance/named_maps_static_view.js index c7ddff2f..9a216451 100644 --- a/test/acceptance/named_maps_static_view.js +++ b/test/acceptance/named_maps_static_view.js @@ -192,7 +192,31 @@ describe('named maps static view', function() { } getStaticMap({ zoom: 3 }, function(err, img) { assert.ok(!err); - img.save('/tmp/static.png'); + assert.imageIsSimilarToFile(img, previewFixture('override-zoom'), IMAGE_TOLERANCE, done); + }); + }); + }); + + it('should return override bbox', function (done) { + var view = { + bounds: { + west: 0, + south: 0, + east: 45, + north: 45 + }, + zoom: 4, + center: { + lng: 40, + lat: 20 + } + }; + templateMaps.addTemplate(username, createTemplate(view), function (err) { + if (err) { + return done(err); + } + getStaticMap({ bbox: '0,45,90,45' }, function(err, img) { + assert.ok(!err); assert.imageIsSimilarToFile(img, previewFixture('override-zoom'), IMAGE_TOLERANCE, done); }); }); diff --git a/test/fixtures/previews/populated_places_simple_reduced-override-bbox.png b/test/fixtures/previews/populated_places_simple_reduced-override-bbox.png new file mode 100644 index 0000000000000000000000000000000000000000..f7fee54c020fb306e1a9b82b3a509c4e670a6d86 GIT binary patch literal 95961 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 M07*qoM6N<$f+~=qUjP6A literal 0 HcmV?d00001 From 91ce3a5489683454eac2a04046a5f719be8abb26 Mon Sep 17 00:00:00 2001 From: Raul Ochoa Date: Tue, 29 Aug 2017 13:05:03 +0000 Subject: [PATCH 006/159] Going green: allow to use bbox param, along lon, lat, and zoom The `bbox` param was removed from the base controller, the rest kept working as they are declared in the base, but it's better to declare them here as well. Fixes #735. --- lib/cartodb/controllers/named_maps.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/cartodb/controllers/named_maps.js b/lib/cartodb/controllers/named_maps.js index bde3827d..b6aa8bdf 100644 --- a/lib/cartodb/controllers/named_maps.js +++ b/lib/cartodb/controllers/named_maps.js @@ -32,7 +32,8 @@ NamedMapsController.prototype.register = function(app) { this.tile.bind(this)); app.get(app.base_url_mapconfig + - '/static/named/:template_id/:width/:height.:format', cors(), userMiddleware, allowQueryParams(['layer']), + '/static/named/:template_id/:width/:height.:format', cors(), userMiddleware, + allowQueryParams(['layer', 'zoom', 'lon', 'lat', 'bbox']), this.staticMap.bind(this)); }; From ca6eb609b2e5f08e55d24af3f6ef19420d13df77 Mon Sep 17 00:00:00 2001 From: Raul Ochoa Date: Tue, 29 Aug 2017 13:08:14 +0000 Subject: [PATCH 007/159] Update news --- NEWS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NEWS.md b/NEWS.md index 79a2bdd4..01b40345 100644 --- a/NEWS.md +++ b/NEWS.md @@ -3,6 +3,8 @@ ## 3.12.6 Released 2017-mm-dd +Bugfixes: + - Bounding box parameter ignored in static named maps #735. ## 3.12.5 From e1990fc2f95cd8afa505b53b3f352efa34f54d26 Mon Sep 17 00:00:00 2001 From: Raul Ochoa Date: Tue, 29 Aug 2017 13:29:39 +0000 Subject: [PATCH 008/159] Use the correct fixture image --- test/acceptance/named_maps_static_view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/acceptance/named_maps_static_view.js b/test/acceptance/named_maps_static_view.js index 9a216451..b7af4197 100644 --- a/test/acceptance/named_maps_static_view.js +++ b/test/acceptance/named_maps_static_view.js @@ -217,7 +217,7 @@ describe('named maps static view', function() { } getStaticMap({ bbox: '0,45,90,45' }, function(err, img) { assert.ok(!err); - assert.imageIsSimilarToFile(img, previewFixture('override-zoom'), IMAGE_TOLERANCE, done); + assert.imageIsSimilarToFile(img, previewFixture('override-bbox'), IMAGE_TOLERANCE, done); }); }); }); From 5ce419d86392ec9f4cdb736d18cea6c8ac90437c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Mon, 4 Sep 2017 18:42:30 +0200 Subject: [PATCH 009/159] Use template string for dateIntervalQueryTpl --- lib/cartodb/models/dataview/histogram.js | 78 ++++++++++++------------ 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/lib/cartodb/models/dataview/histogram.js b/lib/cartodb/models/dataview/histogram.js index c27c696f..1a526637 100644 --- a/lib/cartodb/models/dataview/histogram.js +++ b/lib/cartodb/models/dataview/histogram.js @@ -7,45 +7,45 @@ dot.templateSettings.strip = false; var columnCastTpl = dot.template("date_part('epoch', {{=it.column}})"); -var dateIntervalQueryTpl = dot.template([ - 'WITH', - '__cdb_dates AS (', - ' SELECT', - ' MAX({{=it.column}}::timestamp) AS __cdb_end,', - ' MIN({{=it.column}}::timestamp) AS __cdb_start', - ' FROM ({{=it.query}}) __cdb_source', - '),', - '__cdb_interval_in_days AS (', - ' SELECT' , - ' DATE_PART(\'day\', __cdb_end - __cdb_start) AS __cdb_days', - ' FROM __cdb_dates', - '),', - '__cdb_interval_in_hours AS (', - ' SELECT', - ' __cdb_days * 24 + DATE_PART(\'hour\', __cdb_end - __cdb_start) AS __cdb_hours', - ' FROM __cdb_interval_in_days, __cdb_dates', - '),', - '__cdb_interval_in_minutes AS (', - ' SELECT', - ' __cdb_hours * 60 + DATE_PART(\'minute\', __cdb_end - __cdb_start) AS __cdb_minutes', - ' FROM __cdb_interval_in_hours, __cdb_dates', - '),', - '__cdb_interval_in_seconds AS (', - ' SELECT', - ' __cdb_minutes * 60 + DATE_PART(\'second\', __cdb_end - __cdb_start) AS __cdb_seconds', - ' FROM __cdb_interval_in_minutes, __cdb_dates', - ')', - 'SELECT', - ' ROUND(__cdb_days / 365) AS year,', - ' ROUND(__cdb_days / 90) AS quarter,', - ' ROUND(__cdb_days / 30) AS month,', - ' ROUND(__cdb_days / 7) AS week,', - ' __cdb_days AS day,', - ' __cdb_hours AS hour,', - ' __cdb_minutes AS minute,', - ' __cdb_seconds AS second', - 'FROM __cdb_interval_in_days, __cdb_interval_in_hours, __cdb_interval_in_minutes, __cdb_interval_in_seconds' -].join('\n')); +const dateIntervalQueryTpl = ctx => ` + WITH + __cdb_dates AS ( + SELECT + MAX(${ctx.column}::timestamp) AS __cdb_end + MIN(${ctx.column}::timestamp) AS __cdb_start + FROM (${ctx.query}) __cdb_source + ), + __cdb_interval_in_days AS ( + SELECT + DATE_PART(\day\ __cdb_end - __cdb_start) AS __cdb_days + FROM __cdb_dates + ), + __cdb_interval_in_hours AS ( + SELECT + __cdb_days * 24 + DATE_PART(\hour\ __cdb_end - __cdb_start) AS __cdb_hours + FROM __cdb_interval_in_days __cdb_dates + ), + __cdb_interval_in_minutes AS ( + SELECT + __cdb_hours * 60 + DATE_PART(\minute\ __cdb_end - __cdb_start) AS __cdb_minutes + FROM __cdb_interval_in_hours __cdb_dates + ), + __cdb_interval_in_seconds AS ( + SELECT + __cdb_minutes * 60 + DATE_PART(\second\ __cdb_end - __cdb_start) AS __cdb_seconds + FROM __cdb_interval_in_minutes __cdb_dates + ), + SELECT + ROUND(__cdb_days / 365) AS year + ROUND(__cdb_days / 90) AS quarter + ROUND(__cdb_days / 30) AS month + ROUND(__cdb_days / 7) AS week + __cdb_days AS day + __cdb_hours AS hour + __cdb_minutes AS minute + __cdb_seconds AS second + FROM __cdb_interval_in_days __cdb_interval_in_hours __cdb_interval_in_minutes __cdb_interval_in_seconds +`; var MAX_INTERVAL_VALUE = 366; var BIN_MIN_NUMBER = 6; From 6373fe86521405a30ea870a257ac22dbeacbc91b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Mon, 4 Sep 2017 19:01:58 +0200 Subject: [PATCH 010/159] Use template string for filteredQueryTpl --- lib/cartodb/models/dataview/histogram.js | 31 ++++++++++++------------ 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/lib/cartodb/models/dataview/histogram.js b/lib/cartodb/models/dataview/histogram.js index 1a526637..60e10418 100644 --- a/lib/cartodb/models/dataview/histogram.js +++ b/lib/cartodb/models/dataview/histogram.js @@ -51,21 +51,22 @@ var MAX_INTERVAL_VALUE = 366; var BIN_MIN_NUMBER = 6; var BIN_MAX_NUMBER = 48; -var filteredQueryTpl = dot.template([ - '__cdb_filtered_source AS (', - ' SELECT *', - ' FROM ({{=it._query}}) __cdb_filtered_source_query', - ' WHERE', - ' {{=it._column}} IS NOT NULL', - ' {{?it._isFloatColumn}}AND', - ' {{=it._column}} != \'infinity\'::float', - ' AND', - ' {{=it._column}} != \'-infinity\'::float', - ' AND', - ' {{=it._column}} != \'NaN\'::float{{?}}', - ')' -].join(' \n')); - +const filteredQueryTpl = ctx => ` + __cdb_filtered_source AS ( + SELECT * + FROM (${ctx._query}) __cdb_filtered_source_query + WHERE + ${ctx._column} IS NOT NULL + ${ctx._isFloatColumn ? ` + AND + ${ctx._column} != 'infinity'::float + AND + ${ctx._column} != '-infinity'::float + AND + ${ctx._column} != 'NaN'::float + ` : ''} + ) +` var basicsQueryTpl = dot.template([ '__cdb_basics AS (', ' SELECT', From c5806005908b3bbe471d9d863ca145f434adbb6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Tue, 5 Sep 2017 10:36:18 +0200 Subject: [PATCH 011/159] Extract template to filter out special numeric values --- lib/cartodb/models/dataview/histogram.js | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/lib/cartodb/models/dataview/histogram.js b/lib/cartodb/models/dataview/histogram.js index 60e10418..3f31bea8 100644 --- a/lib/cartodb/models/dataview/histogram.js +++ b/lib/cartodb/models/dataview/histogram.js @@ -51,22 +51,23 @@ var MAX_INTERVAL_VALUE = 366; var BIN_MIN_NUMBER = 6; var BIN_MAX_NUMBER = 48; +const filterOutSpecialNumericValues = ctx => ` + ${ctx._column} != 'infinity'::float + AND + ${ctx._column} != '-infinity'::float + AND + ${ctx._column} != 'NaN'::float +`; + const filteredQueryTpl = ctx => ` __cdb_filtered_source AS ( SELECT * FROM (${ctx._query}) __cdb_filtered_source_query - WHERE - ${ctx._column} IS NOT NULL - ${ctx._isFloatColumn ? ` - AND - ${ctx._column} != 'infinity'::float - AND - ${ctx._column} != '-infinity'::float - AND - ${ctx._column} != 'NaN'::float - ` : ''} + WHERE ${ctx._column} IS NOT NULL + ${ctx._isFloatColumn ? `AND ${filterOutSpecialNumericValues(ctx)}` : ''} ) -` +`; + var basicsQueryTpl = dot.template([ '__cdb_basics AS (', ' SELECT', From 00741bc0a4a9b773d781063b168f9f2e8e9e86a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Tue, 5 Sep 2017 10:41:48 +0200 Subject: [PATCH 012/159] Use template string for basicsQueryTpl --- lib/cartodb/models/dataview/histogram.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/cartodb/models/dataview/histogram.js b/lib/cartodb/models/dataview/histogram.js index 3f31bea8..11fe7bda 100644 --- a/lib/cartodb/models/dataview/histogram.js +++ b/lib/cartodb/models/dataview/histogram.js @@ -68,14 +68,14 @@ const filteredQueryTpl = ctx => ` ) `; -var basicsQueryTpl = dot.template([ - '__cdb_basics AS (', - ' SELECT', - ' max({{=it._column}}) AS __cdb_max_val, min({{=it._column}}) AS __cdb_min_val,', - ' avg({{=it._column}}) AS __cdb_avg_val, count(1) AS __cdb_total_rows', - ' FROM __cdb_filtered_source', - ')' -].join(' \n')); +const basicsQueryTpl = ctx => ` + __cdb_basics AS ( + SELECT + max(${ctx._column}) AS __cdb_max_val, min(${ctx._column}) AS __cdb_min_val, + avg(${ctx._column}) AS __cdb_avg_val, count(1) AS __cdb_total_rows + FROM __cdb_filtered_source + ) +`; var overrideBasicsQueryTpl = dot.template([ '__cdb_basics AS (', From 37b137676703e70aafe829415228cde18cd3bffc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Tue, 5 Sep 2017 11:26:27 +0200 Subject: [PATCH 013/159] Fix bad find & replace --- lib/cartodb/models/dataview/histogram.js | 34 ++++++++++++------------ 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/lib/cartodb/models/dataview/histogram.js b/lib/cartodb/models/dataview/histogram.js index 11fe7bda..ca1b9151 100644 --- a/lib/cartodb/models/dataview/histogram.js +++ b/lib/cartodb/models/dataview/histogram.js @@ -11,40 +11,40 @@ const dateIntervalQueryTpl = ctx => ` WITH __cdb_dates AS ( SELECT - MAX(${ctx.column}::timestamp) AS __cdb_end + MAX(${ctx.column}::timestamp) AS __cdb_end, MIN(${ctx.column}::timestamp) AS __cdb_start FROM (${ctx.query}) __cdb_source ), __cdb_interval_in_days AS ( SELECT - DATE_PART(\day\ __cdb_end - __cdb_start) AS __cdb_days + DATE_PART('day', __cdb_end - __cdb_start) AS __cdb_days FROM __cdb_dates ), __cdb_interval_in_hours AS ( SELECT - __cdb_days * 24 + DATE_PART(\hour\ __cdb_end - __cdb_start) AS __cdb_hours - FROM __cdb_interval_in_days __cdb_dates + __cdb_days * 24 + DATE_PART('hour', __cdb_end - __cdb_start) AS __cdb_hours + FROM __cdb_interval_in_days, __cdb_dates ), __cdb_interval_in_minutes AS ( SELECT - __cdb_hours * 60 + DATE_PART(\minute\ __cdb_end - __cdb_start) AS __cdb_minutes - FROM __cdb_interval_in_hours __cdb_dates + __cdb_hours * 60 + DATE_PART('minute', __cdb_end - __cdb_start) AS __cdb_minutes + FROM __cdb_interval_in_hours, __cdb_dates ), __cdb_interval_in_seconds AS ( SELECT - __cdb_minutes * 60 + DATE_PART(\second\ __cdb_end - __cdb_start) AS __cdb_seconds - FROM __cdb_interval_in_minutes __cdb_dates - ), + __cdb_minutes * 60 + DATE_PART('second', __cdb_end - __cdb_start) AS __cdb_seconds + FROM __cdb_interval_in_minutes, __cdb_dates + ) SELECT - ROUND(__cdb_days / 365) AS year - ROUND(__cdb_days / 90) AS quarter - ROUND(__cdb_days / 30) AS month - ROUND(__cdb_days / 7) AS week - __cdb_days AS day - __cdb_hours AS hour - __cdb_minutes AS minute + ROUND(__cdb_days / 365) AS year, + ROUND(__cdb_days / 90) AS quarter, + ROUND(__cdb_days / 30) AS month, + ROUND(__cdb_days / 7) AS week, + __cdb_days AS day, + __cdb_hours AS hour, + __cdb_minutes AS minute, __cdb_seconds AS second - FROM __cdb_interval_in_days __cdb_interval_in_hours __cdb_interval_in_minutes __cdb_interval_in_seconds + FROM __cdb_interval_in_days, __cdb_interval_in_hours, __cdb_interval_in_minutes, __cdb_interval_in_seconds `; var MAX_INTERVAL_VALUE = 366; From 5d413ac1f98d806f754d3f0a7d4d264551496b5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Tue, 5 Sep 2017 11:26:38 +0200 Subject: [PATCH 014/159] Use template string for overrideBasicsQueryTpl --- lib/cartodb/models/dataview/histogram.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/cartodb/models/dataview/histogram.js b/lib/cartodb/models/dataview/histogram.js index ca1b9151..b00b1e1a 100644 --- a/lib/cartodb/models/dataview/histogram.js +++ b/lib/cartodb/models/dataview/histogram.js @@ -77,14 +77,14 @@ const basicsQueryTpl = ctx => ` ) `; -var overrideBasicsQueryTpl = dot.template([ - '__cdb_basics AS (', - ' SELECT', - ' max({{=it._end}}) AS __cdb_max_val, min({{=it._start}}) AS __cdb_min_val,', - ' avg({{=it._column}}) AS __cdb_avg_val, count(1) AS __cdb_total_rows', - ' FROM __cdb_filtered_source', - ')' -].join('\n')); +const overrideBasicsQueryTpl = ctx => ` + __cdb_basics AS ( + SELECT + max(${ctx._end}) AS __cdb_max_val, min(${ctx._start}) AS __cdb_min_val, + avg(${ctx._column}) AS __cdb_avg_val, count(1) AS __cdb_total_rows + FROM __cdb_filtered_source + ) +`; var iqrQueryTpl = dot.template([ '__cdb_iqrange AS (', From 151bdec1fde503d8b2d8e781cec47c69ec0d93bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Tue, 5 Sep 2017 12:16:38 +0200 Subject: [PATCH 015/159] Use template string for overrideBinsQueryTpl --- lib/cartodb/models/dataview/histogram.js | 69 ++++++++++++------------ 1 file changed, 35 insertions(+), 34 deletions(-) diff --git a/lib/cartodb/models/dataview/histogram.js b/lib/cartodb/models/dataview/histogram.js index b00b1e1a..5eec9713 100644 --- a/lib/cartodb/models/dataview/histogram.js +++ b/lib/cartodb/models/dataview/histogram.js @@ -86,42 +86,43 @@ const overrideBasicsQueryTpl = ctx => ` ) `; -var iqrQueryTpl = dot.template([ - '__cdb_iqrange AS (', - ' SELECT max(quartile_max) - min(quartile_max) AS __cdb_iqr', - ' FROM (', - ' SELECT quartile, max(_cdb_iqr_column) AS quartile_max from (', - ' SELECT {{=it._column}} AS _cdb_iqr_column, ntile(4) over (order by {{=it._column}}', - ' ) AS quartile', - ' FROM __cdb_filtered_source) _cdb_quartiles', - ' WHERE quartile = 1 or quartile = 3', - ' GROUP BY quartile', - ' ) __cdb_iqr', - ')' -].join('\n')); +const iqrQueryTpl = ctx => ` + __cdb_iqrange AS ( + SELECT max(quartile_max) - min(quartile_max) AS __cdb_iqr + FROM ( + SELECT quartile, max(_cdb_iqr_column) AS quartile_max from ( + SELECT ${ctx._column} AS _cdb_iqr_column, ntile(4) over (order by ${ctx._column} + ) AS quartile + FROM __cdb_filtered_source) _cdb_quartiles + WHERE quartile = 1 or quartile = 3 + GROUP BY quartile + ) __cdb_iqr + ) +`; -var binsQueryTpl = dot.template([ - '__cdb_bins AS (', - ' SELECT CASE WHEN __cdb_total_rows = 0 OR __cdb_iqr = 0', - ' THEN 1', - ' ELSE GREATEST(', - ' LEAST({{=it._minBins}}, CAST(__cdb_total_rows AS INT)),', - ' LEAST(', - ' CAST(((__cdb_max_val - __cdb_min_val) / (2 * __cdb_iqr * power(__cdb_total_rows, 1/3))) AS INT),', - ' {{=it._maxBins}}', - ' )', - ' )', - ' END AS __cdb_bins_number', - ' FROM __cdb_basics, __cdb_iqrange, __cdb_filtered_source', - ' LIMIT 1', - ')' -].join('\n')); +const binsQueryTpl = ctx => ` + __cdb_bins AS ( + SELECT + CASE WHEN __cdb_total_rows = 0 OR __cdb_iqr = 0 + THEN 1 + ELSE GREATEST( + LEAST(${ctx._minBins}, CAST(__cdb_total_rows AS INT)), + LEAST( + CAST(((__cdb_max_val - __cdb_min_val) / (2 * __cdb_iqr * power(__cdb_total_rows, 1/3))) AS INT), + ${ctx._maxBins} + ) + ) + END AS __cdb_bins_number + FROM __cdb_basics, __cdb_iqrange, __cdb_filtered_source + LIMIT 1 + ) +`; -var overrideBinsQueryTpl = dot.template([ - '__cdb_bins AS (', - ' SELECT {{=it._bins}} AS __cdb_bins_number', - ')' -].join('\n')); +const overrideBinsQueryTpl = ctx => ` + __cdb_bins AS ( + SELECT ${ctx._bins} AS __cdb_bins_number + ) +`; var nullsQueryTpl = dot.template([ '__cdb_nulls AS (', From f1018f3272cbd226723adb5b2671a95c5e393151 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Tue, 5 Sep 2017 12:18:42 +0200 Subject: [PATCH 016/159] Use template string for nullsQueryTpl --- lib/cartodb/models/dataview/histogram.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/cartodb/models/dataview/histogram.js b/lib/cartodb/models/dataview/histogram.js index 5eec9713..35c9b43c 100644 --- a/lib/cartodb/models/dataview/histogram.js +++ b/lib/cartodb/models/dataview/histogram.js @@ -124,14 +124,14 @@ const overrideBinsQueryTpl = ctx => ` ) `; -var nullsQueryTpl = dot.template([ - '__cdb_nulls AS (', - ' SELECT', - ' count(*) AS __cdb_nulls_count', - ' FROM ({{=it._query}}) __cdb_histogram_nulls', - ' WHERE {{=it._column}} IS NULL', - ')' -].join('\n')); +const nullsQueryTpl = ctx => ` + __cdb_nulls AS ( + SELECT + count(*) AS __cdb_nulls_count + FROM (${it._query}) __cdb_histogram_nulls + WHERE ${it._column} IS NULL + ) +`; var infinitiesQueryTpl = dot.template([ '__cdb_infinities AS (', From 7ea6b3e37181ecbbed581b28d1446bba95859068 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Tue, 5 Sep 2017 12:21:30 +0200 Subject: [PATCH 017/159] Use template string for infinitiesQueryTpl --- lib/cartodb/models/dataview/histogram.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/cartodb/models/dataview/histogram.js b/lib/cartodb/models/dataview/histogram.js index 35c9b43c..af18ea12 100644 --- a/lib/cartodb/models/dataview/histogram.js +++ b/lib/cartodb/models/dataview/histogram.js @@ -133,17 +133,17 @@ const nullsQueryTpl = ctx => ` ) `; -var infinitiesQueryTpl = dot.template([ - '__cdb_infinities AS (', - ' SELECT', - ' count(*) AS __cdb_infinities_count', - ' FROM ({{=it._query}}) __cdb_infinities_query', - ' WHERE', - ' {{=it._column}} = \'infinity\'::float', - ' OR', - ' {{=it._column}} = \'-infinity\'::float', - ')' -].join('\n')); +const infinitiesQueryTpl = ctx => ` + __cdb_infinities AS ( + SELECT + count(*) AS __cdb_infinities_count + FROM (${ctx._query}) __cdb_infinities_query + WHERE + ${ctx._column} = 'infinity'::float + OR + ${ctx._column} = '-infinity'::float + ) +`; var nansQueryTpl = dot.template([ '__cdb_nans AS (', From 734cfa6d8392ffa2a3e1697119136a434734eecb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Tue, 5 Sep 2017 15:51:31 +0200 Subject: [PATCH 018/159] Fix undefined argument --- lib/cartodb/models/dataview/histogram.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/cartodb/models/dataview/histogram.js b/lib/cartodb/models/dataview/histogram.js index af18ea12..fdacbbe0 100644 --- a/lib/cartodb/models/dataview/histogram.js +++ b/lib/cartodb/models/dataview/histogram.js @@ -128,8 +128,8 @@ const nullsQueryTpl = ctx => ` __cdb_nulls AS ( SELECT count(*) AS __cdb_nulls_count - FROM (${it._query}) __cdb_histogram_nulls - WHERE ${it._column} IS NULL + FROM (${ctx._query}) __cdb_histogram_nulls + WHERE ${ctx._column} IS NULL ) `; From adcff54589dd3c3af8b7e7a5de94ee75e841c985 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Tue, 5 Sep 2017 15:53:06 +0200 Subject: [PATCH 019/159] Use template string for nansQueryTpl --- lib/cartodb/models/dataview/histogram.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/cartodb/models/dataview/histogram.js b/lib/cartodb/models/dataview/histogram.js index fdacbbe0..c9d89fd5 100644 --- a/lib/cartodb/models/dataview/histogram.js +++ b/lib/cartodb/models/dataview/histogram.js @@ -145,14 +145,14 @@ const infinitiesQueryTpl = ctx => ` ) `; -var nansQueryTpl = dot.template([ - '__cdb_nans AS (', - ' SELECT', - ' count(*) AS __cdb_nans_count', - ' FROM ({{=it._query}}) __cdb_nans_query', - ' WHERE {{=it._column}} = \'NaN\'::float', - ')' -].join('\n')); +const nansQueryTpl = ctx => ` + __cdb_nans AS ( + SELECT + count(*) AS __cdb_nans_count + FROM (${ctx._query}) __cdb_nans_query + WHERE ${ctx._column} = 'NaN'::float + ) +`; var histogramQueryTpl = dot.template([ 'SELECT', From 27393641935ccced2e968820b7fed77225dbc450 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Tue, 5 Sep 2017 15:53:21 +0200 Subject: [PATCH 020/159] Use template string for histogramQueryTpl --- lib/cartodb/models/dataview/histogram.js | 66 ++++++++++++++---------- 1 file changed, 38 insertions(+), 28 deletions(-) diff --git a/lib/cartodb/models/dataview/histogram.js b/lib/cartodb/models/dataview/histogram.js index c9d89fd5..7a391ecb 100644 --- a/lib/cartodb/models/dataview/histogram.js +++ b/lib/cartodb/models/dataview/histogram.js @@ -154,34 +154,44 @@ const nansQueryTpl = ctx => ` ) `; -var histogramQueryTpl = dot.template([ - 'SELECT', - ' (__cdb_max_val - __cdb_min_val) / cast(__cdb_bins_number as float) AS bin_width,', - ' __cdb_bins_number AS bins_number,', - ' __cdb_nulls_count AS nulls_count,', - ' {{?it._isFloatColumn}}__cdb_infinities_count AS infinities_count,', - ' __cdb_nans_count AS nans_count,{{?}}', - ' __cdb_avg_val AS avg_val,', - ' CASE WHEN __cdb_min_val = __cdb_max_val', - ' THEN 0', - ' ELSE GREATEST(', - ' 1,', - ' LEAST(', - ' WIDTH_BUCKET({{=it._column}}, __cdb_min_val, __cdb_max_val, __cdb_bins_number),', - ' __cdb_bins_number', - ' )', - ' ) - 1', - ' END AS bin,', - ' min({{=it._column}})::numeric AS min,', - ' max({{=it._column}})::numeric AS max,', - ' avg({{=it._column}})::numeric AS avg,', - ' count(*) AS freq', - 'FROM __cdb_filtered_source, __cdb_basics, __cdb_nulls,', - ' __cdb_bins{{?it._isFloatColumn}}, __cdb_infinities, __cdb_nans{{?}}', - 'GROUP BY bin, bins_number, bin_width, nulls_count,', - ' avg_val{{?it._isFloatColumn}}, infinities_count, nans_count{{?}}', - 'ORDER BY bin' -].join('\n')); +const specialNumericValuesColumnDefinitionTpl = () => ` + __cdb_infinities_count AS infinities_count, + __cdb_nans_count AS nans_count +`; + +const specialNumericValuesCTETpl = () => ` + __cdb_infinities, __cdb_nans +`; + +const specialNumericValuesColumnTpl = () => ` + infinities_count, nans_count +`; + +const histogramQueryTpl = ctx => ` + SELECT + (__cdb_max_val - __cdb_min_val) / cast(__cdb_bins_number as float) AS bin_width, + __cdb_bins_number AS bins_number, + __cdb_nulls_count AS nulls_count, + ${ctx._isFloatColumn ? `${specialNumericValuesColumnDefinitionTpl()},` : ''} + __cdb_avg_val AS avg_val, + CASE WHEN __cdb_min_val = __cdb_max_val + THEN 0 + ELSE GREATEST( + 1, + LEAST( + WIDTH_BUCKET(${ctx._column}, __cdb_min_val, __cdb_max_val, __cdb_bins_number), + __cdb_bins_number + ) + ) - 1 + END AS bin, + min(${ctx._column})::numeric AS min, + max(${ctx._column})::numeric AS max, + avg(${ctx._column})::numeric AS avg, + count(*) AS freq + FROM __cdb_filtered_source, __cdb_basics, __cdb_nulls, __cdb_bins ${ctx._isFloatColumn ? `, ${specialNumericValuesCTETpl()}` : ''} + GROUP BY bin, bins_number, bin_width, nulls_count, avg_val${ctx._isFloatColumn ? `, ${specialNumericValuesColumnTpl()}` : ''} + ORDER BY bin +`; var dateBasicsQueryTpl = dot.template([ '__cdb_basics AS (', From 0b79ac76dbd3566604c945c376a8e397c547b56e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Wed, 6 Sep 2017 10:39:17 +0200 Subject: [PATCH 021/159] Use template string for dateBasicsQueryTpl --- lib/cartodb/models/dataview/histogram.js | 30 +++++++++++++----------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/lib/cartodb/models/dataview/histogram.js b/lib/cartodb/models/dataview/histogram.js index 7a391ecb..2adcc944 100644 --- a/lib/cartodb/models/dataview/histogram.js +++ b/lib/cartodb/models/dataview/histogram.js @@ -193,20 +193,22 @@ const histogramQueryTpl = ctx => ` ORDER BY bin `; -var dateBasicsQueryTpl = dot.template([ - '__cdb_basics AS (', - ' SELECT', - ' max(date_part(\'epoch\', {{=it._column}})) AS __cdb_max_val,', - ' min(date_part(\'epoch\', {{=it._column}})) AS __cdb_min_val,', - ' avg(date_part(\'epoch\', {{=it._column}})) AS __cdb_avg_val,', - ' min(date_trunc(', - ' \'{{=it._aggregation}}\', {{=it._column}}::timestamp AT TIME ZONE \'{{=it._offset}}\'', - ' )) AS __cdb_start_date,', - ' max({{=it._column}}::timestamp AT TIME ZONE \'{{=it._offset}}\') AS __cdb_end_date,', - ' count(1) AS __cdb_total_rows', - ' FROM ({{=it._query}}) __cdb_basics_query', - ')' -].join(' \n')); +const dateBasicsQueryTpl = ctx => ` + __cdb_basics AS ( + SELECT + max(date_part('epoch', ${ctx._column})) AS __cdb_max_val, + min(date_part('epoch', ${ctx._column})) AS __cdb_min_val, + avg(date_part('epoch', ${ctx._column})) AS __cdb_avg_val, + min( + date_trunc( + '${ctx._aggregation}', ${ctx._column}::timestamp AT TIME ZONE '${ctx._offset}' + ) + ) AS __cdb_start_date, + max(${ctx._column}::timestamp AT TIME ZONE '${ctx._offset}') AS __cdb_end_date, + count(1) AS __cdb_total_rows + FROM (${ctx._query}) __cdb_basics_query + ) +`; var dateOverrideBasicsQueryTpl = dot.template([ '__cdb_basics AS (', From 0ede3013dbf1ef60b9ba2dcf8b87fe8a983b928a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Wed, 6 Sep 2017 10:57:38 +0200 Subject: [PATCH 022/159] Use template string for dateOverrideBasicsQueryTpl --- lib/cartodb/models/dataview/histogram.js | 38 ++++++++++++------------ 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/lib/cartodb/models/dataview/histogram.js b/lib/cartodb/models/dataview/histogram.js index 2adcc944..9ec42906 100644 --- a/lib/cartodb/models/dataview/histogram.js +++ b/lib/cartodb/models/dataview/histogram.js @@ -210,25 +210,25 @@ const dateBasicsQueryTpl = ctx => ` ) `; -var dateOverrideBasicsQueryTpl = dot.template([ - '__cdb_basics AS (', - ' SELECT', - ' max({{=it._end}}) AS __cdb_max_val,', - ' min({{=it._start}}) AS __cdb_min_val,', - ' avg(date_part(\'epoch\', {{=it._column}})) AS __cdb_avg_val,', - ' min(', - ' date_trunc(', - ' \'{{=it._aggregation}}\',', - ' TO_TIMESTAMP({{=it._start}})::timestamp AT TIME ZONE \'{{=it._offset}}\'', - ' )', - ' ) AS __cdb_start_date,', - ' max(', - ' TO_TIMESTAMP({{=it._end}})::timestamp AT TIME ZONE \'{{=it._offset}}\'', - ' ) AS __cdb_end_date,', - ' count(1) AS __cdb_total_rows', - ' FROM ({{=it._query}}) __cdb_basics_query', - ')' -].join(' \n')); +const dateOverrideBasicsQueryTpl = ctx => ` + __cdb_basics AS ( + SELECT + max(${ctx._end}) AS __cdb_max_val, + min(${ctx._start}) AS __cdb_min_val, + avg(date_part('epoch', ${ctx._column})) AS __cdb_avg_val, + min( + date_trunc( + '${ctx._aggregation}', + TO_TIMESTAMP(${ctx._start})::timestamp AT TIME ZONE '${ctx._offset}' + ) + ) AS __cdb_start_date, + max( + TO_TIMESTAMP(${ctx._end})::timestamp AT TIME ZONE '${ctx._offset}' + ) AS __cdb_end_date, + count(1) AS __cdb_total_rows + FROM (${ctx._query}) __cdb_basics_query + ) +`; var dateBinsQueryTpl = dot.template([ '__cdb_bins AS (', From 4f75f6c07bf34ae7df8af5370ca34c30f5058527 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Wed, 6 Sep 2017 11:41:24 +0200 Subject: [PATCH 023/159] Use template string for dateBinsQueryTpl and dateHistogramQueryTpl --- lib/cartodb/models/dataview/histogram.js | 105 ++++++++++++----------- 1 file changed, 54 insertions(+), 51 deletions(-) diff --git a/lib/cartodb/models/dataview/histogram.js b/lib/cartodb/models/dataview/histogram.js index 9ec42906..42dd15d3 100644 --- a/lib/cartodb/models/dataview/histogram.js +++ b/lib/cartodb/models/dataview/histogram.js @@ -230,58 +230,61 @@ const dateOverrideBasicsQueryTpl = ctx => ` ) `; -var dateBinsQueryTpl = dot.template([ - '__cdb_bins AS (', - ' SELECT', - ' __cdb_bins_array,', - ' ARRAY_LENGTH(__cdb_bins_array, 1) AS __cdb_bins_number', - ' FROM (', - ' SELECT', - ' ARRAY(', - ' SELECT GENERATE_SERIES(', - ' __cdb_start_date::timestamptz,', - ' __cdb_end_date::timestamptz,', - ' {{?it._aggregation==="quarter"}}\'3 month\'{{??}}\'1 {{=it._aggregation}}\'{{?}}::interval', - ' )', - ' ) AS __cdb_bins_array', - ' FROM __cdb_basics', - ' ) __cdb_bins_array_query', - ')' -].join('\n')); +const dateBinsQueryTpl = ctx => ` + __cdb_bins AS ( + SELECT + __cdb_bins_array, + ARRAY_LENGTH(__cdb_bins_array, 1) AS __cdb_bins_number + FROM ( + SELECT + ARRAY( + SELECT GENERATE_SERIES( + __cdb_start_date::timestamptz, + __cdb_end_date::timestamptz, + ${ctx._aggregation === 'quarter' ? `'3 month'::interval` : `'1 ${ctx._aggregation}'::interval`} + ) + ) AS __cdb_bins_array + FROM __cdb_basics + ) __cdb_bins_array_query + ) +`; -var dateHistogramQueryTpl = dot.template([ - 'SELECT', - ' (__cdb_max_val - __cdb_min_val) / cast(__cdb_bins_number as float) AS bin_width,', - ' __cdb_bins_number AS bins_number,', - ' __cdb_nulls_count AS nulls_count,', - ' CASE WHEN __cdb_min_val = __cdb_max_val', - ' THEN 0', - ' ELSE GREATEST(1, LEAST(', - ' WIDTH_BUCKET(', - ' {{=it._column}}::timestamp AT TIME ZONE \'{{=it._offset}}\',', - ' __cdb_bins_array', - ' ),', - ' __cdb_bins_number', - ' )) - 1', - ' END AS bin,', - ' min(', - ' date_part(', - ' \'epoch\', ', - ' date_trunc(', - ' \'{{=it._aggregation}}\', {{=it._column}}::timestamp AT TIME ZONE \'{{=it._offset}}\'', - ' ) AT TIME ZONE \'{{=it._offset}}\'', - ' )', - ' )::numeric AS timestamp,', - ' date_part(\'epoch\', __cdb_start_date)::numeric AS timestamp_start,', - ' min(date_part(\'epoch\', {{=it._column}}))::numeric AS min,', - ' max(date_part(\'epoch\', {{=it._column}}))::numeric AS max,', - ' avg(date_part(\'epoch\', {{=it._column}}))::numeric AS avg,', - ' count(*) AS freq', - 'FROM ({{=it._query}}) __cdb_histogram, __cdb_basics, __cdb_bins, __cdb_nulls', - 'WHERE date_part(\'epoch\', {{=it._column}}) IS NOT NULL', - 'GROUP BY bin, bins_number, bin_width, nulls_count, timestamp_start', - 'ORDER BY bin' -].join('\n')); +const dateHistogramQueryTpl = ctx => ` + SELECT + (__cdb_max_val - __cdb_min_val) / cast(__cdb_bins_number as float) AS bin_width, + __cdb_bins_number AS bins_number, + __cdb_nulls_count AS nulls_count, + CASE WHEN __cdb_min_val = __cdb_max_val + THEN 0 + ELSE GREATEST( + 1, + LEAST( + WIDTH_BUCKET( + ${ctx._column}::timestamp AT TIME ZONE '${ctx._offset}', + __cdb_bins_array + ), + __cdb_bins_number + ) + ) - 1 + END AS bin, + min( + date_part( + 'epoch', + date_trunc( + '${ctx._aggregation}', ${ctx._column}::timestamp AT TIME ZONE '${ctx._offset}' + ) AT TIME ZONE '${ctx._offset}' + ) + )::numeric AS timestamp, + date_part('epoch', __cdb_start_date)::numeric AS timestamp_start, + min(date_part('epoch', ${ctx._column}))::numeric AS min, + max(date_part('epoch', ${ctx._column}))::numeric AS max, + avg(date_part('epoch', ${ctx._column}))::numeric AS avg, + count(*) AS freq + FROM (${ctx._query}) __cdb_histogram, __cdb_basics, __cdb_bins, __cdb_nulls + WHERE date_part('epoch', ${ctx._column}) IS NOT NULL + GROUP BY bin, bins_number, bin_width, nulls_count, timestamp_start + ORDER BY bin +`; var TYPE = 'histogram'; From 8e2535745eaa034dce3914755f12379afd709479 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Wed, 6 Sep 2017 11:44:25 +0200 Subject: [PATCH 024/159] Use template string for columnCastTpl --- lib/cartodb/models/dataview/histogram.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cartodb/models/dataview/histogram.js b/lib/cartodb/models/dataview/histogram.js index 42dd15d3..4cbfd0f1 100644 --- a/lib/cartodb/models/dataview/histogram.js +++ b/lib/cartodb/models/dataview/histogram.js @@ -5,7 +5,7 @@ var debug = require('debug')('windshaft:dataview:histogram'); var dot = require('dot'); dot.templateSettings.strip = false; -var columnCastTpl = dot.template("date_part('epoch', {{=it.column}})"); +var columnCastTpl = ctx => `date_part('epoch', ${ctx.column})` const dateIntervalQueryTpl = ctx => ` WITH From bbd42b73f2bc9e56495e26fd9150f1826d342c72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Wed, 6 Sep 2017 11:44:52 +0200 Subject: [PATCH 025/159] Remove dot requirement --- lib/cartodb/models/dataview/histogram.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/cartodb/models/dataview/histogram.js b/lib/cartodb/models/dataview/histogram.js index 4cbfd0f1..b2782e31 100644 --- a/lib/cartodb/models/dataview/histogram.js +++ b/lib/cartodb/models/dataview/histogram.js @@ -2,10 +2,7 @@ var _ = require('underscore'); var BaseWidget = require('./base'); var debug = require('debug')('windshaft:dataview:histogram'); -var dot = require('dot'); -dot.templateSettings.strip = false; - -var columnCastTpl = ctx => `date_part('epoch', ${ctx.column})` +const columnCastTpl = ctx => `date_part('epoch', ${ctx.column})` const dateIntervalQueryTpl = ctx => ` WITH From af4b3d81cd1a881d0cb10eb423785ac43ffe0da7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Wed, 6 Sep 2017 11:55:40 +0200 Subject: [PATCH 026/159] make happy to jshint --- lib/cartodb/models/dataview/histogram.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/cartodb/models/dataview/histogram.js b/lib/cartodb/models/dataview/histogram.js index b2782e31..0c57a563 100644 --- a/lib/cartodb/models/dataview/histogram.js +++ b/lib/cartodb/models/dataview/histogram.js @@ -2,7 +2,7 @@ var _ = require('underscore'); var BaseWidget = require('./base'); var debug = require('debug')('windshaft:dataview:histogram'); -const columnCastTpl = ctx => `date_part('epoch', ${ctx.column})` +const columnCastTpl = ctx => `date_part('epoch', ${ctx.column})`; const dateIntervalQueryTpl = ctx => ` WITH @@ -185,8 +185,10 @@ const histogramQueryTpl = ctx => ` max(${ctx._column})::numeric AS max, avg(${ctx._column})::numeric AS avg, count(*) AS freq - FROM __cdb_filtered_source, __cdb_basics, __cdb_nulls, __cdb_bins ${ctx._isFloatColumn ? `, ${specialNumericValuesCTETpl()}` : ''} - GROUP BY bin, bins_number, bin_width, nulls_count, avg_val${ctx._isFloatColumn ? `, ${specialNumericValuesColumnTpl()}` : ''} + FROM __cdb_filtered_source, __cdb_basics, __cdb_nulls, __cdb_bins + ${ctx._isFloatColumn ? `, ${specialNumericValuesCTETpl()}` : ''} + GROUP BY bin, bins_number, bin_width, nulls_count, avg_val + ${ctx._isFloatColumn ? `, ${specialNumericValuesColumnTpl()}` : ''} ORDER BY bin `; From 5ba2dfbbd6cb99bdd972c54f1fdd236c8235ce6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Wed, 6 Sep 2017 13:43:54 +0200 Subject: [PATCH 027/159] Use ES6 class syntax --- lib/cartodb/models/dataview/histogram.js | 616 +++++++++++------------ 1 file changed, 308 insertions(+), 308 deletions(-) diff --git a/lib/cartodb/models/dataview/histogram.js b/lib/cartodb/models/dataview/histogram.js index 0c57a563..25a08da0 100644 --- a/lib/cartodb/models/dataview/histogram.js +++ b/lib/cartodb/models/dataview/histogram.js @@ -307,159 +307,349 @@ Time series: } } */ -function Histogram(query, options, queries) { - if (!_.isString(options.column)) { - throw new Error('Histogram expects `column` in widget options'); + +module.exports = class Histogram extends BaseWidget { + constructor (query, options, queries) { + super(); + + if (!_.isString(options.column)) { + throw new Error('Histogram expects `column` in widget options'); + } + + this.query = query; + this.queries = queries; + this.column = options.column; + this.bins = options.bins; + this.aggregation = options.aggregation; + this.offset = options.offset; + + this._columnType = null; } - this.query = query; - this.queries = queries; - this.column = options.column; - this.bins = options.bins; - this.aggregation = options.aggregation; - this.offset = options.offset; + sql (psql, override, callback) { + var self = this; - this._columnType = null; -} + if (!callback) { + callback = override; + override = {}; + } -Histogram.prototype = new BaseWidget(); -Histogram.prototype.constructor = Histogram; + if (this._columnType === null) { + this.getColumnType(psql, this.column, this.queries.no_filters, function (err, type) { + // assume numeric, will fail later + self._columnType = 'numeric'; + if (!err && !!type) { + self._columnType = Object.keys(type).find(function (key) { + return type[key]; + }); + } + self.sql(psql, override, callback); + }, true); // use read-only transaction + return null; + } -module.exports = Histogram; - -Histogram.prototype.sql = function(psql, override, callback) { - var self = this; - - if (!callback) { - callback = override; - override = {}; + this._buildQuery(psql, override, callback); } - if (this._columnType === null) { - this.getColumnType(psql, this.column, this.queries.no_filters, function (err, type) { - // assume numeric, will fail later - self._columnType = 'numeric'; - if (!err && !!type) { - self._columnType = Object.keys(type).find(function (key) { - return type[key]; - }); - } - self.sql(psql, override, callback); - }, true); // use read-only transaction - return null; + isDateHistogram (override) { + return this._columnType === 'date' && (this.aggregation !== undefined || override.aggregation !== undefined); } - this._buildQuery(psql, override, callback); -}; + _buildQuery (psql, override, callback) { + var filteredQuery, basicsQuery, binsQuery; + var _column = this.column; + var _query = this.query; -Histogram.prototype.isDateHistogram = function (override) { - return this._columnType === 'date' && (this.aggregation !== undefined || override.aggregation !== undefined); -}; + if (this.isDateHistogram(override)) { + return this._buildDateHistogramQuery(psql, override, callback); + } -Histogram.prototype._buildQuery = function (psql, override, callback) { - var filteredQuery, basicsQuery, binsQuery; - var _column = this.column; - var _query = this.query; + if (this._columnType === 'date') { + _column = columnCastTpl({column: _column}); + } - if (this.isDateHistogram(override)) { - return this._buildDateHistogramQuery(psql, override, callback); - } - - if (this._columnType === 'date') { - _column = columnCastTpl({column: _column}); - } - - filteredQuery = filteredQueryTpl({ - _isFloatColumn: this._columnType === 'float', - _query: _query, - _column: _column - }); - - if (this._shouldOverride(override)) { - debug('overriding with %j', override); - basicsQuery = overrideBasicsQueryTpl({ - _query: _query, - _column: _column, - _start: getBinStart(override), - _end: getBinEnd(override) - }); - - binsQuery = [ - overrideBinsQueryTpl({ - _bins: override.bins - }) - ].join(',\n'); - } else { - basicsQuery = basicsQueryTpl({ + filteredQuery = filteredQueryTpl({ + _isFloatColumn: this._columnType === 'float', _query: _query, _column: _column }); - if (this._shouldOverrideBins(override)) { + if (this._shouldOverride(override)) { + debug('overriding with %j', override); + basicsQuery = overrideBasicsQueryTpl({ + _query: _query, + _column: _column, + _start: getBinStart(override), + _end: getBinEnd(override) + }); + binsQuery = [ overrideBinsQueryTpl({ _bins: override.bins }) ].join(',\n'); } else { - binsQuery = [ - iqrQueryTpl({ - _query: _query, - _column: _column - }), - binsQueryTpl({ - _query: _query, - _minBins: BIN_MIN_NUMBER, - _maxBins: BIN_MAX_NUMBER - }) - ].join(',\n'); - } - } - - var cteSql = [ - filteredQuery, - basicsQuery, - binsQuery, - nullsQueryTpl({ - _query: _query, - _column: _column - }) - ]; - - if (this._columnType === 'float') { - cteSql.push( - infinitiesQueryTpl({ + basicsQuery = basicsQueryTpl({ _query: _query, _column: _column - }), - nansQueryTpl({ + }); + + if (this._shouldOverrideBins(override)) { + binsQuery = [ + overrideBinsQueryTpl({ + _bins: override.bins + }) + ].join(',\n'); + } else { + binsQuery = [ + iqrQueryTpl({ + _query: _query, + _column: _column + }), + binsQueryTpl({ + _query: _query, + _minBins: BIN_MIN_NUMBER, + _maxBins: BIN_MAX_NUMBER + }) + ].join(',\n'); + } + } + + var cteSql = [ + filteredQuery, + basicsQuery, + binsQuery, + nullsQueryTpl({ _query: _query, _column: _column }) - ); + ]; + + if (this._columnType === 'float') { + cteSql.push( + infinitiesQueryTpl({ + _query: _query, + _column: _column + }), + nansQueryTpl({ + _query: _query, + _column: _column + }) + ); + } + + var histogramSql = [ + "WITH", + cteSql.join(',\n'), + histogramQueryTpl({ + _isFloatColumn: this._columnType === 'float', + _query: _query, + _column: _column + }) + ].join('\n'); + + debug(histogramSql); + + return callback(null, histogramSql); } - var histogramSql = [ - "WITH", - cteSql.join(',\n'), - histogramQueryTpl({ - _isFloatColumn: this._columnType === 'float', + _shouldOverride (override) { + return override && _.has(override, 'start') && _.has(override, 'end') && _.has(override, 'bins'); + } + + _shouldOverrideBins (override) { + return override && _.has(override, 'bins'); + } + + _buildDateHistogramQuery (psql, override, callback) { + var _column = this.column; + var _query = this.query; + var _aggregation = override && override.aggregation ? override.aggregation : this.aggregation; + var _offset = override && Number.isFinite(override.offset) ? override.offset : this.offset; + + if (!DATE_AGGREGATIONS.hasOwnProperty(_aggregation)) { + return callback(new Error('Invalid aggregation value. Valid ones: ' + + Object.keys(DATE_AGGREGATIONS).join(', ') + )); + } + + if (_aggregation === 'auto') { + this.getAutomaticAggregation(psql, function (err, aggregation) { + if (err || aggregation === 'none') { + this.aggregation = 'day'; + } else { + this.aggregation = aggregation; + } + override.aggregation = this.aggregation; + this._buildDateHistogramQuery(psql, override, callback); + }.bind(this)); + return null; + } + + var dateBasicsQuery; + + if (override && _.has(override, 'start') && _.has(override, 'end')) { + dateBasicsQuery = dateOverrideBasicsQueryTpl({ + _query: _query, + _column: _column, + _aggregation: _aggregation, + _start: getBinStart(override), + _end: getBinEnd(override), + _offset: parseOffset(_offset, _aggregation) + }); + } else { + dateBasicsQuery = dateBasicsQueryTpl({ + _query: _query, + _column: _column, + _aggregation: _aggregation, + _offset: parseOffset(_offset, _aggregation) + }); + } + + var dateBinsQuery = [ + dateBinsQueryTpl({ + _aggregation: _aggregation + }) + ].join(',\n'); + + var nullsQuery = nullsQueryTpl({ _query: _query, _column: _column - }) - ].join('\n'); + }); - debug(histogramSql); + var dateHistogramQuery = dateHistogramQueryTpl({ + _query: _query, + _column: _column, + _aggregation: _aggregation, + _offset: parseOffset(_offset, _aggregation) + }); - return callback(null, histogramSql); -}; + var histogramSql = [ + "WITH", + [ + dateBasicsQuery, + dateBinsQuery, + nullsQuery + ].join(',\n'), + dateHistogramQuery + ].join('\n'); -Histogram.prototype._shouldOverride = function (override) { - return override && _.has(override, 'start') && _.has(override, 'end') && _.has(override, 'bins'); -}; + debug(histogramSql); -Histogram.prototype._shouldOverrideBins = function (override) { - return override && _.has(override, 'bins'); + return callback(null, histogramSql); + } + + getAutomaticAggregation (psql, callback) { + var dateIntervalQuery = dateIntervalQueryTpl({ + query: this.query, + column: this.column + }); + + debug(dateIntervalQuery); + + psql.query(dateIntervalQuery, function (err, result) { + if (err) { + return callback(err); + } + + var aggegations = result.rows[0]; + var aggregation = Object.keys(aggegations) + .map(function (key) { + return { + name: key, + value: aggegations[key] + }; + }) + .reduce(function (closer, current) { + if (current.value > MAX_INTERVAL_VALUE) { + return closer; + } + + var closerDiff = MAX_INTERVAL_VALUE - closer.value; + var currentDiff = MAX_INTERVAL_VALUE - current.value; + + if (Number.isFinite(current.value) && closerDiff > currentDiff) { + return current; + } + + return closer; + }, { name: 'none', value: -1 }); + + callback(null, aggregation.name); + }); + } + + format (result, override) { + override = override || {}; + var buckets = []; + + var binsCount = getBinsCount(override); + var width = getWidth(override); + var binsStart = getBinStart(override); + var nulls = 0; + var infinities = 0; + var nans = 0; + var avg; + var timestampStart; + var aggregation; + var offset; + + if (result.rows.length) { + var firstRow = result.rows[0]; + binsCount = firstRow.bins_number; + width = firstRow.bin_width || width; + avg = firstRow.avg_val; + nulls = firstRow.nulls_count; + timestampStart = firstRow.timestamp_start; + infinities = firstRow.infinities_count; + nans = firstRow.nans_count; + binsStart = populateBinStart(override, firstRow); + + if (Number.isFinite(timestampStart)) { + aggregation = getAggregation(override, this.aggregation); + offset = getOffset(override, this.offset); + } + + buckets = result.rows.map(function(row) { + return _.omit( + row, + 'bins_number', + 'bin_width', + 'nulls_count', + 'infinities_count', + 'nans_count', + 'avg_val', + 'timestamp_start' + ); + }); + } + + return { + aggregation: aggregation, + offset: offset, + timestamp_start: timestampStart, + bin_width: width, + bins_count: binsCount, + bins_start: binsStart, + nulls: nulls, + infinities: infinities, + nans: nans, + avg: avg, + bins: buckets + }; + } + + getType () { + return TYPE; + } + + toString () { + return JSON.stringify({ + _type: TYPE, + _column: this.column, + _query: this.query + }); + } }; var DATE_AGGREGATIONS = { @@ -473,184 +663,6 @@ var DATE_AGGREGATIONS = { 'year': true }; -Histogram.prototype._buildDateHistogramQuery = function (psql, override, callback) { - var _column = this.column; - var _query = this.query; - var _aggregation = override && override.aggregation ? override.aggregation : this.aggregation; - var _offset = override && Number.isFinite(override.offset) ? override.offset : this.offset; - - if (!DATE_AGGREGATIONS.hasOwnProperty(_aggregation)) { - return callback(new Error('Invalid aggregation value. Valid ones: ' + - Object.keys(DATE_AGGREGATIONS).join(', ') - )); - } - - if (_aggregation === 'auto') { - this.getAutomaticAggregation(psql, function (err, aggregation) { - if (err || aggregation === 'none') { - this.aggregation = 'day'; - } else { - this.aggregation = aggregation; - } - override.aggregation = this.aggregation; - this._buildDateHistogramQuery(psql, override, callback); - }.bind(this)); - return null; - } - - var dateBasicsQuery; - - if (override && _.has(override, 'start') && _.has(override, 'end')) { - dateBasicsQuery = dateOverrideBasicsQueryTpl({ - _query: _query, - _column: _column, - _aggregation: _aggregation, - _start: getBinStart(override), - _end: getBinEnd(override), - _offset: parseOffset(_offset, _aggregation) - }); - } else { - dateBasicsQuery = dateBasicsQueryTpl({ - _query: _query, - _column: _column, - _aggregation: _aggregation, - _offset: parseOffset(_offset, _aggregation) - }); - } - - var dateBinsQuery = [ - dateBinsQueryTpl({ - _aggregation: _aggregation - }) - ].join(',\n'); - - var nullsQuery = nullsQueryTpl({ - _query: _query, - _column: _column - }); - - var dateHistogramQuery = dateHistogramQueryTpl({ - _query: _query, - _column: _column, - _aggregation: _aggregation, - _offset: parseOffset(_offset, _aggregation) - }); - - var histogramSql = [ - "WITH", - [ - dateBasicsQuery, - dateBinsQuery, - nullsQuery - ].join(',\n'), - dateHistogramQuery - ].join('\n'); - - debug(histogramSql); - - return callback(null, histogramSql); -}; - -Histogram.prototype.getAutomaticAggregation = function (psql, callback) { - var dateIntervalQuery = dateIntervalQueryTpl({ - query: this.query, - column: this.column - }); - - debug(dateIntervalQuery); - - psql.query(dateIntervalQuery, function (err, result) { - if (err) { - return callback(err); - } - - var aggegations = result.rows[0]; - var aggregation = Object.keys(aggegations) - .map(function (key) { - return { - name: key, - value: aggegations[key] - }; - }) - .reduce(function (closer, current) { - if (current.value > MAX_INTERVAL_VALUE) { - return closer; - } - - var closerDiff = MAX_INTERVAL_VALUE - closer.value; - var currentDiff = MAX_INTERVAL_VALUE - current.value; - - if (Number.isFinite(current.value) && closerDiff > currentDiff) { - return current; - } - - return closer; - }, { name: 'none', value: -1 }); - - callback(null, aggregation.name); - }); -}; - -Histogram.prototype.format = function(result, override) { - override = override || {}; - var buckets = []; - - var binsCount = getBinsCount(override); - var width = getWidth(override); - var binsStart = getBinStart(override); - var nulls = 0; - var infinities = 0; - var nans = 0; - var avg; - var timestampStart; - var aggregation; - var offset; - - if (result.rows.length) { - var firstRow = result.rows[0]; - binsCount = firstRow.bins_number; - width = firstRow.bin_width || width; - avg = firstRow.avg_val; - nulls = firstRow.nulls_count; - timestampStart = firstRow.timestamp_start; - infinities = firstRow.infinities_count; - nans = firstRow.nans_count; - binsStart = populateBinStart(override, firstRow); - - if (Number.isFinite(timestampStart)) { - aggregation = getAggregation(override, this.aggregation); - offset = getOffset(override, this.offset); - } - - buckets = result.rows.map(function(row) { - return _.omit( - row, - 'bins_number', - 'bin_width', - 'nulls_count', - 'infinities_count', - 'nans_count', - 'avg_val', - 'timestamp_start' - ); - }); - } - - return { - aggregation: aggregation, - offset: offset, - timestamp_start: timestampStart, - bin_width: width, - bins_count: binsCount, - bins_start: binsStart, - nulls: nulls, - infinities: infinities, - nans: nans, - avg: avg, - bins: buckets - }; -}; - function getAggregation(override, aggregation) { return override && override.aggregation ? override.aggregation : aggregation; } @@ -720,15 +732,3 @@ function populateBinStart(override, firstRow) { return binStart; } - -Histogram.prototype.getType = function() { - return TYPE; -}; - -Histogram.prototype.toString = function() { - return JSON.stringify({ - _type: TYPE, - _column: this.column, - _query: this.query - }); -}; From 1be66e155253fb83a2b5ef5e7b9af88917a71564 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Wed, 6 Sep 2017 13:47:19 +0200 Subject: [PATCH 028/159] Use const for requirements --- lib/cartodb/models/dataview/histogram.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/cartodb/models/dataview/histogram.js b/lib/cartodb/models/dataview/histogram.js index 25a08da0..5a4f713b 100644 --- a/lib/cartodb/models/dataview/histogram.js +++ b/lib/cartodb/models/dataview/histogram.js @@ -1,6 +1,6 @@ -var _ = require('underscore'); -var BaseWidget = require('./base'); -var debug = require('debug')('windshaft:dataview:histogram'); +const _ = require('underscore'); +const BaseWidget = require('./base'); +const debug = require('debug')('windshaft:dataview:histogram'); const columnCastTpl = ctx => `date_part('epoch', ${ctx.column})`; From 96740b82ed3a84d8066cc6bc3f222474601d5eac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Wed, 6 Sep 2017 13:52:02 +0200 Subject: [PATCH 029/159] Move getAggregation function to a class method --- lib/cartodb/models/dataview/histogram.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/cartodb/models/dataview/histogram.js b/lib/cartodb/models/dataview/histogram.js index 5a4f713b..dbe8f014 100644 --- a/lib/cartodb/models/dataview/histogram.js +++ b/lib/cartodb/models/dataview/histogram.js @@ -606,7 +606,7 @@ module.exports = class Histogram extends BaseWidget { binsStart = populateBinStart(override, firstRow); if (Number.isFinite(timestampStart)) { - aggregation = getAggregation(override, this.aggregation); + aggregation = this.getAggregation(override); offset = getOffset(override, this.offset); } @@ -650,6 +650,10 @@ module.exports = class Histogram extends BaseWidget { _query: this.query }); } + + getAggregation (override) { + return override && override.aggregation ? override.aggregation : this.aggregation; + } }; var DATE_AGGREGATIONS = { @@ -663,9 +667,6 @@ var DATE_AGGREGATIONS = { 'year': true }; -function getAggregation(override, aggregation) { - return override && override.aggregation ? override.aggregation : aggregation; -} function getOffset(override, offset) { if (override && override.offset) { From 288656301bd4b853e53521a6cda493ab985b01fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Wed, 6 Sep 2017 13:53:48 +0200 Subject: [PATCH 030/159] Move getOffset function to a class method --- lib/cartodb/models/dataview/histogram.js | 25 ++++++++++++------------ 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/lib/cartodb/models/dataview/histogram.js b/lib/cartodb/models/dataview/histogram.js index dbe8f014..0e6279b8 100644 --- a/lib/cartodb/models/dataview/histogram.js +++ b/lib/cartodb/models/dataview/histogram.js @@ -607,7 +607,7 @@ module.exports = class Histogram extends BaseWidget { if (Number.isFinite(timestampStart)) { aggregation = this.getAggregation(override); - offset = getOffset(override, this.offset); + offset = this.getOffset(override); } buckets = result.rows.map(function(row) { @@ -654,6 +654,17 @@ module.exports = class Histogram extends BaseWidget { getAggregation (override) { return override && override.aggregation ? override.aggregation : this.aggregation; } + + getOffset (override) { + if (override && override.offset) { + return override.offset; + } + if (this.offset) { + return this.offset; + } + + return 0; + } }; var DATE_AGGREGATIONS = { @@ -667,18 +678,6 @@ var DATE_AGGREGATIONS = { 'year': true }; - -function getOffset(override, offset) { - if (override && override.offset) { - return override.offset; - } - if (offset) { - return offset; - } - - return 0; -} - function getBinStart(override) { if (override.hasOwnProperty('start') && override.hasOwnProperty('end')) { return Math.min(override.start, override.end); From 392e004879019318c5878ac0ba0f6b08cd573ca3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Wed, 6 Sep 2017 15:38:23 +0200 Subject: [PATCH 031/159] Move getBinStart and populateBinStart function to a class method --- lib/cartodb/models/dataview/histogram.js | 49 ++++++++++++------------ 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/lib/cartodb/models/dataview/histogram.js b/lib/cartodb/models/dataview/histogram.js index 0e6279b8..6a5081c1 100644 --- a/lib/cartodb/models/dataview/histogram.js +++ b/lib/cartodb/models/dataview/histogram.js @@ -379,7 +379,7 @@ module.exports = class Histogram extends BaseWidget { basicsQuery = overrideBasicsQueryTpl({ _query: _query, _column: _column, - _start: getBinStart(override), + _start: this.getBinStart(override), _end: getBinEnd(override) }); @@ -493,7 +493,7 @@ module.exports = class Histogram extends BaseWidget { _query: _query, _column: _column, _aggregation: _aggregation, - _start: getBinStart(override), + _start: this.getBinStart(override), _end: getBinEnd(override), _offset: parseOffset(_offset, _aggregation) }); @@ -585,7 +585,7 @@ module.exports = class Histogram extends BaseWidget { var binsCount = getBinsCount(override); var width = getWidth(override); - var binsStart = getBinStart(override); + var binsStart = this.getBinStart(override); var nulls = 0; var infinities = 0; var nans = 0; @@ -603,7 +603,7 @@ module.exports = class Histogram extends BaseWidget { timestampStart = firstRow.timestamp_start; infinities = firstRow.infinities_count; nans = firstRow.nans_count; - binsStart = populateBinStart(override, firstRow); + binsStart = this.populateBinStart(override, firstRow); if (Number.isFinite(timestampStart)) { aggregation = this.getAggregation(override); @@ -665,6 +665,27 @@ module.exports = class Histogram extends BaseWidget { return 0; } + + getBinStart (override) { + if (override.hasOwnProperty('start') && override.hasOwnProperty('end')) { + return Math.min(override.start, override.end); + } + return override.start || 0; + } + + populateBinStart (override, firstRow) { + var binStart; + + if (firstRow.hasOwnProperty('timestamp')) { + binStart = firstRow.timestamp; + } else if (override.hasOwnProperty('start')) { + binStart = this.getBinStart(override); + } else { + binStart = firstRow.min; + } + + return binStart; + } }; var DATE_AGGREGATIONS = { @@ -678,13 +699,6 @@ var DATE_AGGREGATIONS = { 'year': true }; -function getBinStart(override) { - if (override.hasOwnProperty('start') && override.hasOwnProperty('end')) { - return Math.min(override.start, override.end); - } - return override.start || 0; -} - function getBinEnd(override) { if (override.hasOwnProperty('start') && override.hasOwnProperty('end')) { return Math.max(override.start, override.end); @@ -719,16 +733,3 @@ function parseOffset(offset, aggregation) { return '' + offsetInHours; } -function populateBinStart(override, firstRow) { - var binStart; - - if (firstRow.hasOwnProperty('timestamp')) { - binStart = firstRow.timestamp; - } else if (override.hasOwnProperty('start')) { - binStart = getBinStart(override); - } else { - binStart = firstRow.min; - } - - return binStart; -} From 49a60caffcf6d94b615211cd32e5263bf937b890 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Wed, 6 Sep 2017 15:42:29 +0200 Subject: [PATCH 032/159] Move getBinEnd function to a class method --- lib/cartodb/models/dataview/histogram.js | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/lib/cartodb/models/dataview/histogram.js b/lib/cartodb/models/dataview/histogram.js index 6a5081c1..01b62148 100644 --- a/lib/cartodb/models/dataview/histogram.js +++ b/lib/cartodb/models/dataview/histogram.js @@ -380,7 +380,7 @@ module.exports = class Histogram extends BaseWidget { _query: _query, _column: _column, _start: this.getBinStart(override), - _end: getBinEnd(override) + _end: this.getBinEnd(override) }); binsQuery = [ @@ -494,7 +494,7 @@ module.exports = class Histogram extends BaseWidget { _column: _column, _aggregation: _aggregation, _start: this.getBinStart(override), - _end: getBinEnd(override), + _end: this.getBinEnd(override), _offset: parseOffset(_offset, _aggregation) }); } else { @@ -686,6 +686,14 @@ module.exports = class Histogram extends BaseWidget { return binStart; } + + getBinEnd (override) { + if (override.hasOwnProperty('start') && override.hasOwnProperty('end')) { + return Math.max(override.start, override.end); + } + return override.end || 0; + } + }; var DATE_AGGREGATIONS = { @@ -699,13 +707,6 @@ var DATE_AGGREGATIONS = { 'year': true }; -function getBinEnd(override) { - if (override.hasOwnProperty('start') && override.hasOwnProperty('end')) { - return Math.max(override.start, override.end); - } - return override.end || 0; -} - function getBinsCount(override) { return override.bins || 0; } From e84f30488fb14f97d2b1697578938b09a770e523 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Wed, 6 Sep 2017 15:45:51 +0200 Subject: [PATCH 033/159] Move getBinsCount function to a class method --- lib/cartodb/models/dataview/histogram.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/cartodb/models/dataview/histogram.js b/lib/cartodb/models/dataview/histogram.js index 01b62148..c2df2c5f 100644 --- a/lib/cartodb/models/dataview/histogram.js +++ b/lib/cartodb/models/dataview/histogram.js @@ -583,7 +583,7 @@ module.exports = class Histogram extends BaseWidget { override = override || {}; var buckets = []; - var binsCount = getBinsCount(override); + var binsCount = this.getBinsCount(override); var width = getWidth(override); var binsStart = this.getBinStart(override); var nulls = 0; @@ -694,6 +694,9 @@ module.exports = class Histogram extends BaseWidget { return override.end || 0; } + getBinsCount (override) { + return override.bins || 0; + } }; var DATE_AGGREGATIONS = { @@ -707,10 +710,6 @@ var DATE_AGGREGATIONS = { 'year': true }; -function getBinsCount(override) { - return override.bins || 0; -} - function getWidth(override) { var width = 0; var binsCount = override.bins; From 9e3e1cad9a64ee346cee0c7cf9c239c0d5ca1088 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Wed, 6 Sep 2017 15:47:21 +0200 Subject: [PATCH 034/159] Move getWidth function to a class method --- lib/cartodb/models/dataview/histogram.js | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/lib/cartodb/models/dataview/histogram.js b/lib/cartodb/models/dataview/histogram.js index c2df2c5f..3e9868d9 100644 --- a/lib/cartodb/models/dataview/histogram.js +++ b/lib/cartodb/models/dataview/histogram.js @@ -584,7 +584,7 @@ module.exports = class Histogram extends BaseWidget { var buckets = []; var binsCount = this.getBinsCount(override); - var width = getWidth(override); + var width = this.getWidth(override); var binsStart = this.getBinStart(override); var nulls = 0; var infinities = 0; @@ -697,6 +697,17 @@ module.exports = class Histogram extends BaseWidget { getBinsCount (override) { return override.bins || 0; } + + getWidth (override) { + var width = 0; + var binsCount = override.bins; + + if (binsCount && Number.isFinite(override.start) && Number.isFinite(override.end)) { + width = (override.end - override.start) / binsCount; + } + + return width; + } }; var DATE_AGGREGATIONS = { @@ -710,16 +721,6 @@ var DATE_AGGREGATIONS = { 'year': true }; -function getWidth(override) { - var width = 0; - var binsCount = override.bins; - - if (binsCount && Number.isFinite(override.start) && Number.isFinite(override.end)) { - width = (override.end - override.start) / binsCount; - } - - return width; -} function parseOffset(offset, aggregation) { if (!offset) { From c37ef36a611b53192067f03b94a2c116ad9be292 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Wed, 6 Sep 2017 15:49:05 +0200 Subject: [PATCH 035/159] Move parseOffset function to a class method --- lib/cartodb/models/dataview/histogram.js | 32 +++++++++++------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/lib/cartodb/models/dataview/histogram.js b/lib/cartodb/models/dataview/histogram.js index 3e9868d9..9ab349df 100644 --- a/lib/cartodb/models/dataview/histogram.js +++ b/lib/cartodb/models/dataview/histogram.js @@ -495,14 +495,14 @@ module.exports = class Histogram extends BaseWidget { _aggregation: _aggregation, _start: this.getBinStart(override), _end: this.getBinEnd(override), - _offset: parseOffset(_offset, _aggregation) + _offset: this.parseOffset(_offset, _aggregation) }); } else { dateBasicsQuery = dateBasicsQueryTpl({ _query: _query, _column: _column, _aggregation: _aggregation, - _offset: parseOffset(_offset, _aggregation) + _offset: this.parseOffset(_offset, _aggregation) }); } @@ -521,7 +521,7 @@ module.exports = class Histogram extends BaseWidget { _query: _query, _column: _column, _aggregation: _aggregation, - _offset: parseOffset(_offset, _aggregation) + _offset: this.parseOffset(_offset, _aggregation) }); var histogramSql = [ @@ -708,6 +708,18 @@ module.exports = class Histogram extends BaseWidget { return width; } + + parseOffset (offset, aggregation) { + if (!offset) { + return '0'; + } + if (aggregation === 'hour' || aggregation === 'minute') { + return '0'; + } + + var offsetInHours = Math.ceil(offset / 3600); + return '' + offsetInHours; + } }; var DATE_AGGREGATIONS = { @@ -720,17 +732,3 @@ var DATE_AGGREGATIONS = { 'quarter': true, 'year': true }; - - -function parseOffset(offset, aggregation) { - if (!offset) { - return '0'; - } - if (aggregation === 'hour' || aggregation === 'minute') { - return '0'; - } - - var offsetInHours = Math.ceil(offset / 3600); - return '' + offsetInHours; -} - From da1d0550f6b48edaaff2751aa82157a7a8e53ae6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Wed, 6 Sep 2017 15:52:13 +0200 Subject: [PATCH 036/159] Use const keyword for constants --- lib/cartodb/models/dataview/histogram.js | 32 ++++++++++++------------ 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/lib/cartodb/models/dataview/histogram.js b/lib/cartodb/models/dataview/histogram.js index 9ab349df..8726268d 100644 --- a/lib/cartodb/models/dataview/histogram.js +++ b/lib/cartodb/models/dataview/histogram.js @@ -44,10 +44,6 @@ const dateIntervalQueryTpl = ctx => ` FROM __cdb_interval_in_days, __cdb_interval_in_hours, __cdb_interval_in_minutes, __cdb_interval_in_seconds `; -var MAX_INTERVAL_VALUE = 366; -var BIN_MIN_NUMBER = 6; -var BIN_MAX_NUMBER = 48; - const filterOutSpecialNumericValues = ctx => ` ${ctx._column} != 'infinity'::float AND @@ -285,7 +281,22 @@ const dateHistogramQueryTpl = ctx => ` ORDER BY bin `; -var TYPE = 'histogram'; +const MAX_INTERVAL_VALUE = 366; +const BIN_MIN_NUMBER = 6; +const BIN_MAX_NUMBER = 48; + +const TYPE = 'histogram'; + +const DATE_AGGREGATIONS = { + 'auto': true, + 'minute': true, + 'hour': true, + 'day': true, + 'week': true, + 'month': true, + 'quarter': true, + 'year': true +}; /** Numeric histogram: @@ -721,14 +732,3 @@ module.exports = class Histogram extends BaseWidget { return '' + offsetInHours; } }; - -var DATE_AGGREGATIONS = { - 'auto': true, - 'minute': true, - 'hour': true, - 'day': true, - 'week': true, - 'month': true, - 'quarter': true, - 'year': true -}; From 3e55bd2abb80f4bb53f2e562ba66c4f1eb9f3c1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Wed, 6 Sep 2017 15:56:52 +0200 Subject: [PATCH 037/159] Make happy to jshint --- lib/cartodb/models/dataview/histogram.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cartodb/models/dataview/histogram.js b/lib/cartodb/models/dataview/histogram.js index 8726268d..98d59214 100644 --- a/lib/cartodb/models/dataview/histogram.js +++ b/lib/cartodb/models/dataview/histogram.js @@ -731,4 +731,4 @@ module.exports = class Histogram extends BaseWidget { var offsetInHours = Math.ceil(offset / 3600); return '' + offsetInHours; } -}; +} From 6e0dc8666d103c3e7d7f3bcc7d6a3bf753e9b973 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Wed, 6 Sep 2017 16:14:29 +0200 Subject: [PATCH 038/159] Use .hasOwnProperty() instead of underscore's equivalent --- lib/cartodb/models/dataview/histogram.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/cartodb/models/dataview/histogram.js b/lib/cartodb/models/dataview/histogram.js index 98d59214..a1e08494 100644 --- a/lib/cartodb/models/dataview/histogram.js +++ b/lib/cartodb/models/dataview/histogram.js @@ -465,11 +465,14 @@ module.exports = class Histogram extends BaseWidget { } _shouldOverride (override) { - return override && _.has(override, 'start') && _.has(override, 'end') && _.has(override, 'bins'); + return override && + override.hasOwnProperty('start') && + override.hasOwnProperty('end') && + override.hasOwnProperty('bins'); } _shouldOverrideBins (override) { - return override && _.has(override, 'bins'); + return override && override.hasOwnProperty('bins'); } _buildDateHistogramQuery (psql, override, callback) { @@ -499,7 +502,7 @@ module.exports = class Histogram extends BaseWidget { var dateBasicsQuery; - if (override && _.has(override, 'start') && _.has(override, 'end')) { + if (override && override.hasOwnProperty('start') && override.hasOwnProperty('end')) { dateBasicsQuery = dateOverrideBasicsQueryTpl({ _query: _query, _column: _column, @@ -731,4 +734,4 @@ module.exports = class Histogram extends BaseWidget { var offsetInHours = Math.ceil(offset / 3600); return '' + offsetInHours; } -} +}; From 8ac041805c51ec806759d1bc05ee62cb5438ab2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Wed, 6 Sep 2017 16:20:01 +0200 Subject: [PATCH 039/159] Use typeof !== string instead of underscore's equivalent --- lib/cartodb/models/dataview/histogram.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cartodb/models/dataview/histogram.js b/lib/cartodb/models/dataview/histogram.js index a1e08494..8624efc5 100644 --- a/lib/cartodb/models/dataview/histogram.js +++ b/lib/cartodb/models/dataview/histogram.js @@ -323,7 +323,7 @@ module.exports = class Histogram extends BaseWidget { constructor (query, options, queries) { super(); - if (!_.isString(options.column)) { + if (typeof options.column !== 'string') { throw new Error('Histogram expects `column` in widget options'); } From b5d2de8edc35811f4cc207ccfbb4bd7ee1c0bfc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Wed, 6 Sep 2017 16:49:00 +0200 Subject: [PATCH 040/159] Do not use _.omit() --- lib/cartodb/models/dataview/histogram.js | 29 ++++++++++++++---------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/lib/cartodb/models/dataview/histogram.js b/lib/cartodb/models/dataview/histogram.js index 8624efc5..3d758b17 100644 --- a/lib/cartodb/models/dataview/histogram.js +++ b/lib/cartodb/models/dataview/histogram.js @@ -624,18 +624,23 @@ module.exports = class Histogram extends BaseWidget { offset = this.getOffset(override); } - buckets = result.rows.map(function(row) { - return _.omit( - row, - 'bins_number', - 'bin_width', - 'nulls_count', - 'infinities_count', - 'nans_count', - 'avg_val', - 'timestamp_start' - ); - }); + // TODO: after applying strategy we could use specialized .format() + // in order to know what props we want to expose using destructuring assignment + + const blacklisted = [ + 'bins_number', + 'bin_width', + 'nulls_count', + 'infinities_count', + 'nans_count', + 'avg_val', + 'timestamp_start' + ]; + + buckets = result.rows.map(row => Object.keys(row) + .filter((key) => blacklisted.indexOf(key) < 0) + .reduce((newObj, key) => Object.assign(newObj, { [key]: row[key] }), {}) + ); } return { From 4edf18f77a733d125e4688b3cbbcb01bd4531c8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Wed, 6 Sep 2017 16:52:17 +0200 Subject: [PATCH 041/159] Remove underscore requirement --- lib/cartodb/models/dataview/histogram.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/cartodb/models/dataview/histogram.js b/lib/cartodb/models/dataview/histogram.js index 3d758b17..271c3e95 100644 --- a/lib/cartodb/models/dataview/histogram.js +++ b/lib/cartodb/models/dataview/histogram.js @@ -1,4 +1,3 @@ -const _ = require('underscore'); const BaseWidget = require('./base'); const debug = require('debug')('windshaft:dataview:histogram'); From 9f52e58be896bcc670da3951d81b57e4479c0249 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Wed, 6 Sep 2017 16:54:08 +0200 Subject: [PATCH 042/159] Rename BaseWidget by BaseDataview --- lib/cartodb/models/dataview/histogram.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/cartodb/models/dataview/histogram.js b/lib/cartodb/models/dataview/histogram.js index 271c3e95..b732f712 100644 --- a/lib/cartodb/models/dataview/histogram.js +++ b/lib/cartodb/models/dataview/histogram.js @@ -1,4 +1,4 @@ -const BaseWidget = require('./base'); +const BaseDataview = require('./base'); const debug = require('debug')('windshaft:dataview:histogram'); const columnCastTpl = ctx => `date_part('epoch', ${ctx.column})`; @@ -318,7 +318,7 @@ Time series: } */ -module.exports = class Histogram extends BaseWidget { +module.exports = class Histogram extends BaseDataview { constructor (query, options, queries) { super(); From 1ac8455dc28cc8f66c1f207f5f812f371dc3aeee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Wed, 6 Sep 2017 18:13:34 +0200 Subject: [PATCH 043/159] Use template strings to build histogram query --- lib/cartodb/models/dataview/histogram.js | 110 ++++++----------------- 1 file changed, 26 insertions(+), 84 deletions(-) diff --git a/lib/cartodb/models/dataview/histogram.js b/lib/cartodb/models/dataview/histogram.js index b732f712..60d6cd36 100644 --- a/lib/cartodb/models/dataview/histogram.js +++ b/lib/cartodb/models/dataview/histogram.js @@ -366,103 +366,45 @@ module.exports = class Histogram extends BaseDataview { } _buildQuery (psql, override, callback) { - var filteredQuery, basicsQuery, binsQuery; - var _column = this.column; - var _query = this.query; if (this.isDateHistogram(override)) { return this._buildDateHistogramQuery(psql, override, callback); } - if (this._columnType === 'date') { - _column = columnCastTpl({column: _column}); - } - - filteredQuery = filteredQueryTpl({ + const histogramSql = this.buildNumericHistogramQueryTpl({ + _override: override, + _column: this._columnType === 'date' ? columnCastTpl({ column: this.column }) : this.column, _isFloatColumn: this._columnType === 'float', - _query: _query, - _column: _column + _query: this.query, + _start: this.getBinStart(override), + _end: this.getBinEnd(override), + _minBins: BIN_MIN_NUMBER, + _maxBins: BIN_MAX_NUMBER, + _bins: override.bins }); - if (this._shouldOverride(override)) { - debug('overriding with %j', override); - basicsQuery = overrideBasicsQueryTpl({ - _query: _query, - _column: _column, - _start: this.getBinStart(override), - _end: this.getBinEnd(override) - }); - - binsQuery = [ - overrideBinsQueryTpl({ - _bins: override.bins - }) - ].join(',\n'); - } else { - basicsQuery = basicsQueryTpl({ - _query: _query, - _column: _column - }); - - if (this._shouldOverrideBins(override)) { - binsQuery = [ - overrideBinsQueryTpl({ - _bins: override.bins - }) - ].join(',\n'); - } else { - binsQuery = [ - iqrQueryTpl({ - _query: _query, - _column: _column - }), - binsQueryTpl({ - _query: _query, - _minBins: BIN_MIN_NUMBER, - _maxBins: BIN_MAX_NUMBER - }) - ].join(',\n'); - } - } - - var cteSql = [ - filteredQuery, - basicsQuery, - binsQuery, - nullsQueryTpl({ - _query: _query, - _column: _column - }) - ]; - - if (this._columnType === 'float') { - cteSql.push( - infinitiesQueryTpl({ - _query: _query, - _column: _column - }), - nansQueryTpl({ - _query: _query, - _column: _column - }) - ); - } - - var histogramSql = [ - "WITH", - cteSql.join(',\n'), - histogramQueryTpl({ - _isFloatColumn: this._columnType === 'float', - _query: _query, - _column: _column - }) - ].join('\n'); - debug(histogramSql); return callback(null, histogramSql); } + buildNumericHistogramQueryTpl (ctx) { + return ` + WITH + ${filteredQueryTpl(ctx)}, + ${this._shouldOverride(ctx._override) ? overrideBasicsQueryTpl(ctx) : basicsQueryTpl(ctx)}, + ${this._shouldOverride(ctx._override) ? + overrideBinsQueryTpl(ctx) : + this._shouldOverrideBins(ctx._override) ? + overrideBinsQueryTpl(ctx) : + `${iqrQueryTpl(ctx)}, ${binsQueryTpl(ctx)}` + }, + ${nullsQueryTpl(ctx)} + ${ctx._isFloatColumn ? `,${infinitiesQueryTpl(ctx)}, ${nansQueryTpl(ctx)}` : ''} + ${histogramQueryTpl(ctx)} + `; + } + _shouldOverride (override) { return override && override.hasOwnProperty('start') && From b63a67a5b89b0d4843636be2e5b1526b558fc49d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Wed, 6 Sep 2017 18:33:51 +0200 Subject: [PATCH 044/159] Avoid nested ternaries --- lib/cartodb/models/dataview/histogram.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/lib/cartodb/models/dataview/histogram.js b/lib/cartodb/models/dataview/histogram.js index 60d6cd36..3ca87bb7 100644 --- a/lib/cartodb/models/dataview/histogram.js +++ b/lib/cartodb/models/dataview/histogram.js @@ -112,7 +112,7 @@ const binsQueryTpl = ctx => ` const overrideBinsQueryTpl = ctx => ` __cdb_bins AS ( - SELECT ${ctx._bins} AS __cdb_bins_number + SELECT ${ctx._override.bins} AS __cdb_bins_number ) `; @@ -380,7 +380,6 @@ module.exports = class Histogram extends BaseDataview { _end: this.getBinEnd(override), _minBins: BIN_MIN_NUMBER, _maxBins: BIN_MAX_NUMBER, - _bins: override.bins }); debug(histogramSql); @@ -393,11 +392,9 @@ module.exports = class Histogram extends BaseDataview { WITH ${filteredQueryTpl(ctx)}, ${this._shouldOverride(ctx._override) ? overrideBasicsQueryTpl(ctx) : basicsQueryTpl(ctx)}, - ${this._shouldOverride(ctx._override) ? + ${this._shouldOverride(ctx._override) || this._shouldOverrideBins(ctx._override) ? overrideBinsQueryTpl(ctx) : - this._shouldOverrideBins(ctx._override) ? - overrideBinsQueryTpl(ctx) : - `${iqrQueryTpl(ctx)}, ${binsQueryTpl(ctx)}` + `${iqrQueryTpl(ctx)}, ${binsQueryTpl(ctx)}` }, ${nullsQueryTpl(ctx)} ${ctx._isFloatColumn ? `,${infinitiesQueryTpl(ctx)}, ${nansQueryTpl(ctx)}` : ''} From 63a7ee08d0cf686da39740710e25d754953f4a4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Wed, 6 Sep 2017 19:15:39 +0200 Subject: [PATCH 045/159] Avoid nested ternaries for date histograms --- lib/cartodb/models/dataview/histogram.js | 64 +++++++----------------- 1 file changed, 18 insertions(+), 46 deletions(-) diff --git a/lib/cartodb/models/dataview/histogram.js b/lib/cartodb/models/dataview/histogram.js index 3ca87bb7..c4b53423 100644 --- a/lib/cartodb/models/dataview/histogram.js +++ b/lib/cartodb/models/dataview/histogram.js @@ -413,9 +413,19 @@ module.exports = class Histogram extends BaseDataview { return override && override.hasOwnProperty('bins'); } + buildDateHistogramQueryTpl (ctx) { + return ` + WITH + ${ctx._override && ctx._override.hasOwnProperty('start') && ctx._override.hasOwnProperty('end') ? + dateOverrideBasicsQueryTpl(ctx) : + dateBasicsQueryTpl(ctx)}, + ${dateBinsQueryTpl(ctx)}, + ${nullsQueryTpl(ctx)} + ${dateHistogramQueryTpl(ctx)} + `; + } + _buildDateHistogramQuery (psql, override, callback) { - var _column = this.column; - var _query = this.query; var _aggregation = override && override.aggregation ? override.aggregation : this.aggregation; var _offset = override && Number.isFinite(override.offset) ? override.offset : this.offset; @@ -438,54 +448,16 @@ module.exports = class Histogram extends BaseDataview { return null; } - var dateBasicsQuery; - - if (override && override.hasOwnProperty('start') && override.hasOwnProperty('end')) { - dateBasicsQuery = dateOverrideBasicsQueryTpl({ - _query: _query, - _column: _column, - _aggregation: _aggregation, - _start: this.getBinStart(override), - _end: this.getBinEnd(override), - _offset: this.parseOffset(_offset, _aggregation) - }); - } else { - dateBasicsQuery = dateBasicsQueryTpl({ - _query: _query, - _column: _column, - _aggregation: _aggregation, - _offset: this.parseOffset(_offset, _aggregation) - }); - } - - var dateBinsQuery = [ - dateBinsQueryTpl({ - _aggregation: _aggregation - }) - ].join(',\n'); - - var nullsQuery = nullsQueryTpl({ - _query: _query, - _column: _column - }); - - var dateHistogramQuery = dateHistogramQueryTpl({ - _query: _query, - _column: _column, + const histogramSql = this.buildDateHistogramQueryTpl({ + _override: override, + _query: this.query, + _column: this.column, _aggregation: _aggregation, + _start: this.getBinStart(override), + _end: this.getBinEnd(override), _offset: this.parseOffset(_offset, _aggregation) }); - var histogramSql = [ - "WITH", - [ - dateBasicsQuery, - dateBinsQuery, - nullsQuery - ].join(',\n'), - dateHistogramQuery - ].join('\n'); - debug(histogramSql); return callback(null, histogramSql); From 7e099be13474cd1050fba7367c07209829abd1a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Fri, 8 Sep 2017 10:29:54 +0200 Subject: [PATCH 046/159] Add specific implementations of histograms based on column type; still not used --- lib/cartodb/models/dataview/date-histogram.js | 441 ++++++++++++++++++ .../models/dataview/numeric-histogram.js | 369 +++++++++++++++ 2 files changed, 810 insertions(+) create mode 100644 lib/cartodb/models/dataview/date-histogram.js create mode 100644 lib/cartodb/models/dataview/numeric-histogram.js diff --git a/lib/cartodb/models/dataview/date-histogram.js b/lib/cartodb/models/dataview/date-histogram.js new file mode 100644 index 00000000..2e55ea2d --- /dev/null +++ b/lib/cartodb/models/dataview/date-histogram.js @@ -0,0 +1,441 @@ +const BaseDataview = require('./base'); +const debug = require('debug')('windshaft:dataview:histogram'); + +const dateIntervalQueryTpl = ctx => ` + WITH + __cdb_dates AS ( + SELECT + MAX(${ctx.column}::timestamp) AS __cdb_end, + MIN(${ctx.column}::timestamp) AS __cdb_start + FROM (${ctx.query}) __cdb_source + ), + __cdb_interval_in_days AS ( + SELECT + DATE_PART('day', __cdb_end - __cdb_start) AS __cdb_days + FROM __cdb_dates + ), + __cdb_interval_in_hours AS ( + SELECT + __cdb_days * 24 + DATE_PART('hour', __cdb_end - __cdb_start) AS __cdb_hours + FROM __cdb_interval_in_days, __cdb_dates + ), + __cdb_interval_in_minutes AS ( + SELECT + __cdb_hours * 60 + DATE_PART('minute', __cdb_end - __cdb_start) AS __cdb_minutes + FROM __cdb_interval_in_hours, __cdb_dates + ), + __cdb_interval_in_seconds AS ( + SELECT + __cdb_minutes * 60 + DATE_PART('second', __cdb_end - __cdb_start) AS __cdb_seconds + FROM __cdb_interval_in_minutes, __cdb_dates + ) + SELECT + ROUND(__cdb_days / 365) AS year, + ROUND(__cdb_days / 90) AS quarter, + ROUND(__cdb_days / 30) AS month, + ROUND(__cdb_days / 7) AS week, + __cdb_days AS day, + __cdb_hours AS hour, + __cdb_minutes AS minute, + __cdb_seconds AS second + FROM __cdb_interval_in_days, __cdb_interval_in_hours, __cdb_interval_in_minutes, __cdb_interval_in_seconds +`; + +const nullsQueryTpl = ctx => ` + __cdb_nulls AS ( + SELECT + count(*) AS __cdb_nulls_count + FROM (${ctx._query}) __cdb_histogram_nulls + WHERE ${ctx._column} IS NULL + ) +`; + +const dateBasicsQueryTpl = ctx => ` + __cdb_basics AS ( + SELECT + max(date_part('epoch', ${ctx._column})) AS __cdb_max_val, + min(date_part('epoch', ${ctx._column})) AS __cdb_min_val, + avg(date_part('epoch', ${ctx._column})) AS __cdb_avg_val, + min( + date_trunc( + '${ctx._aggregation}', ${ctx._column}::timestamp AT TIME ZONE '${ctx._offset}' + ) + ) AS __cdb_start_date, + max(${ctx._column}::timestamp AT TIME ZONE '${ctx._offset}') AS __cdb_end_date, + count(1) AS __cdb_total_rows + FROM (${ctx._query}) __cdb_basics_query + ) +`; + +const dateOverrideBasicsQueryTpl = ctx => ` + __cdb_basics AS ( + SELECT + max(${ctx._end}) AS __cdb_max_val, + min(${ctx._start}) AS __cdb_min_val, + avg(date_part('epoch', ${ctx._column})) AS __cdb_avg_val, + min( + date_trunc( + '${ctx._aggregation}', + TO_TIMESTAMP(${ctx._start})::timestamp AT TIME ZONE '${ctx._offset}' + ) + ) AS __cdb_start_date, + max( + TO_TIMESTAMP(${ctx._end})::timestamp AT TIME ZONE '${ctx._offset}' + ) AS __cdb_end_date, + count(1) AS __cdb_total_rows + FROM (${ctx._query}) __cdb_basics_query + ) +`; + +const dateBinsQueryTpl = ctx => ` + __cdb_bins AS ( + SELECT + __cdb_bins_array, + ARRAY_LENGTH(__cdb_bins_array, 1) AS __cdb_bins_number + FROM ( + SELECT + ARRAY( + SELECT GENERATE_SERIES( + __cdb_start_date::timestamptz, + __cdb_end_date::timestamptz, + ${ctx._aggregation === 'quarter' ? `'3 month'::interval` : `'1 ${ctx._aggregation}'::interval`} + ) + ) AS __cdb_bins_array + FROM __cdb_basics + ) __cdb_bins_array_query + ) +`; + +const dateHistogramQueryTpl = ctx => ` + SELECT + (__cdb_max_val - __cdb_min_val) / cast(__cdb_bins_number as float) AS bin_width, + __cdb_bins_number AS bins_number, + __cdb_nulls_count AS nulls_count, + CASE WHEN __cdb_min_val = __cdb_max_val + THEN 0 + ELSE GREATEST( + 1, + LEAST( + WIDTH_BUCKET( + ${ctx._column}::timestamp AT TIME ZONE '${ctx._offset}', + __cdb_bins_array + ), + __cdb_bins_number + ) + ) - 1 + END AS bin, + min( + date_part( + 'epoch', + date_trunc( + '${ctx._aggregation}', ${ctx._column}::timestamp AT TIME ZONE '${ctx._offset}' + ) AT TIME ZONE '${ctx._offset}' + ) + )::numeric AS timestamp, + date_part('epoch', __cdb_start_date)::numeric AS timestamp_start, + min(date_part('epoch', ${ctx._column}))::numeric AS min, + max(date_part('epoch', ${ctx._column}))::numeric AS max, + avg(date_part('epoch', ${ctx._column}))::numeric AS avg, + count(*) AS freq + FROM (${ctx._query}) __cdb_histogram, __cdb_basics, __cdb_bins, __cdb_nulls + WHERE date_part('epoch', ${ctx._column}) IS NOT NULL + GROUP BY bin, bins_number, bin_width, nulls_count, timestamp_start + ORDER BY bin +`; + +const MAX_INTERVAL_VALUE = 366; + +const TYPE = 'histogram'; + +const DATE_AGGREGATIONS = { + 'auto': true, + 'minute': true, + 'hour': true, + 'day': true, + 'week': true, + 'month': true, + 'quarter': true, + 'year': true +}; + +/** + date_histogram: { + type: 'histogram', + options: { + column: 'date', // column data type: date + aggregation: 'day' // auto by default + offset: -7200 // OPTIONAL (UTC offset in seconds) + } + } +*/ +module.exports = class Histogram extends BaseDataview { + constructor (query, options, queries) { + super(); + + if (typeof options.column !== 'string') { + throw new Error('Histogram expects `column` in widget options'); + } + + this.query = query; + this.queries = queries; + this.column = options.column; + this.bins = options.bins; + this.aggregation = options.aggregation; + this.offset = options.offset; + + this._columnType = null; + } + + sql (psql, override, callback) { + var self = this; + + if (!callback) { + callback = override; + override = {}; + } + + if (this._columnType === null) { + this.getColumnType(psql, this.column, this.queries.no_filters, function (err, type) { + // assume numeric, will fail later + self._columnType = 'numeric'; + if (!err && !!type) { + self._columnType = Object.keys(type).find(function (key) { + return type[key]; + }); + } + self.sql(psql, override, callback); + }, true); // use read-only transaction + return null; + } + + if (this.isDateHistogram(override)) { + return this._buildQuery(psql, override, callback); + } + + } + + isDateHistogram (override) { + return this._columnType === 'date' && (this.aggregation !== undefined || override.aggregation !== undefined); + } + + buildDateHistogramQueryTpl (ctx) { + return ` + WITH + ${ctx._override && ctx._override.hasOwnProperty('start') && ctx._override.hasOwnProperty('end') ? + dateOverrideBasicsQueryTpl(ctx) : + dateBasicsQueryTpl(ctx)}, + ${dateBinsQueryTpl(ctx)}, + ${nullsQueryTpl(ctx)} + ${dateHistogramQueryTpl(ctx)} + `; + } + + _buildQuery (psql, override, callback) { + var _aggregation = override && override.aggregation ? override.aggregation : this.aggregation; + var _offset = override && Number.isFinite(override.offset) ? override.offset : this.offset; + + if (!DATE_AGGREGATIONS.hasOwnProperty(_aggregation)) { + return callback(new Error('Invalid aggregation value. Valid ones: ' + + Object.keys(DATE_AGGREGATIONS).join(', ') + )); + } + + if (_aggregation === 'auto') { + this.getAutomaticAggregation(psql, function (err, aggregation) { + if (err || aggregation === 'none') { + this.aggregation = 'day'; + } else { + this.aggregation = aggregation; + } + override.aggregation = this.aggregation; + this._buildDateHistogramQuery(psql, override, callback); + }.bind(this)); + return null; + } + + const histogramSql = this.buildDateHistogramQueryTpl({ + _override: override, + _query: this.query, + _column: this.column, + _aggregation: _aggregation, + _start: this.getBinStart(override), + _end: this.getBinEnd(override), + _offset: this.parseOffset(_offset, _aggregation) + }); + + debug(histogramSql); + + return callback(null, histogramSql); + } + + getAutomaticAggregation (psql, callback) { + var dateIntervalQuery = dateIntervalQueryTpl({ + query: this.query, + column: this.column + }); + + debug(dateIntervalQuery); + + psql.query(dateIntervalQuery, function (err, result) { + if (err) { + return callback(err); + } + + var aggegations = result.rows[0]; + var aggregation = Object.keys(aggegations) + .map(function (key) { + return { + name: key, + value: aggegations[key] + }; + }) + .reduce(function (closer, current) { + if (current.value > MAX_INTERVAL_VALUE) { + return closer; + } + + var closerDiff = MAX_INTERVAL_VALUE - closer.value; + var currentDiff = MAX_INTERVAL_VALUE - current.value; + + if (Number.isFinite(current.value) && closerDiff > currentDiff) { + return current; + } + + return closer; + }, { name: 'none', value: -1 }); + + callback(null, aggregation.name); + }); + } + + format (result, override) { + override = override || {}; + var buckets = []; + + var binsCount = this.getBinsCount(override); + var width = this.getWidth(override); + var binsStart = this.getBinStart(override); + var nulls = 0; + var infinities = 0; + var nans = 0; + var avg; + var timestampStart; + var aggregation; + var offset; + + if (result.rows.length) { + var firstRow = result.rows[0]; + binsCount = firstRow.bins_number; + width = firstRow.bin_width || width; + avg = firstRow.avg_val; + nulls = firstRow.nulls_count; + timestampStart = firstRow.timestamp_start; + infinities = firstRow.infinities_count; + nans = firstRow.nans_count; + binsStart = this.populateBinStart(override, firstRow); + + if (Number.isFinite(timestampStart)) { + aggregation = this.getAggregation(override); + offset = this.getOffset(override); + } + + buckets = result.rows.map(row => ({ bin, min, max, avg, freq, timestamp } = row)); + } + + return { + aggregation: aggregation, + offset: offset, + timestamp_start: timestampStart, + bin_width: width, + bins_count: binsCount, + bins_start: binsStart, + nulls: nulls, + infinities: infinities, + nans: nans, + avg: avg, + bins: buckets + }; + } + + getType () { + return TYPE; + } + + toString () { + return JSON.stringify({ + _type: TYPE, + _column: this.column, + _query: this.query + }); + } + + getAggregation (override) { + return override && override.aggregation ? override.aggregation : this.aggregation; + } + + getOffset (override) { + if (override && override.offset) { + return override.offset; + } + if (this.offset) { + return this.offset; + } + + return 0; + } + + getBinStart (override) { + if (override.hasOwnProperty('start') && override.hasOwnProperty('end')) { + return Math.min(override.start, override.end); + } + return override.start || 0; + } + + populateBinStart (override, firstRow) { + var binStart; + + if (firstRow.hasOwnProperty('timestamp')) { + binStart = firstRow.timestamp; + } else if (override.hasOwnProperty('start')) { + binStart = this.getBinStart(override); + } else { + binStart = firstRow.min; + } + + return binStart; + } + + getBinEnd (override) { + if (override.hasOwnProperty('start') && override.hasOwnProperty('end')) { + return Math.max(override.start, override.end); + } + return override.end || 0; + } + + getBinsCount (override) { + return override.bins || 0; + } + + getWidth (override) { + var width = 0; + var binsCount = override.bins; + + if (binsCount && Number.isFinite(override.start) && Number.isFinite(override.end)) { + width = (override.end - override.start) / binsCount; + } + + return width; + } + + parseOffset (offset, aggregation) { + if (!offset) { + return '0'; + } + if (aggregation === 'hour' || aggregation === 'minute') { + return '0'; + } + + var offsetInHours = Math.ceil(offset / 3600); + return '' + offsetInHours; + } +}; diff --git a/lib/cartodb/models/dataview/numeric-histogram.js b/lib/cartodb/models/dataview/numeric-histogram.js new file mode 100644 index 00000000..e5149793 --- /dev/null +++ b/lib/cartodb/models/dataview/numeric-histogram.js @@ -0,0 +1,369 @@ +const BaseDataview = require('./base'); +const debug = require('debug')('windshaft:dataview:histogram'); + +const columnCastTpl = ctx => `date_part('epoch', ${ctx.column})`; + +const filterOutSpecialNumericValues = ctx => ` + ${ctx._column} != 'infinity'::float + AND + ${ctx._column} != '-infinity'::float + AND + ${ctx._column} != 'NaN'::float +`; + +const filteredQueryTpl = ctx => ` + __cdb_filtered_source AS ( + SELECT * + FROM (${ctx._query}) __cdb_filtered_source_query + WHERE ${ctx._column} IS NOT NULL + ${ctx._isFloatColumn ? `AND ${filterOutSpecialNumericValues(ctx)}` : ''} + ) +`; + +const basicsQueryTpl = ctx => ` + __cdb_basics AS ( + SELECT + max(${ctx._column}) AS __cdb_max_val, min(${ctx._column}) AS __cdb_min_val, + avg(${ctx._column}) AS __cdb_avg_val, count(1) AS __cdb_total_rows + FROM __cdb_filtered_source + ) +`; + +const overrideBasicsQueryTpl = ctx => ` + __cdb_basics AS ( + SELECT + max(${ctx._end}) AS __cdb_max_val, min(${ctx._start}) AS __cdb_min_val, + avg(${ctx._column}) AS __cdb_avg_val, count(1) AS __cdb_total_rows + FROM __cdb_filtered_source + ) +`; + +const iqrQueryTpl = ctx => ` + __cdb_iqrange AS ( + SELECT max(quartile_max) - min(quartile_max) AS __cdb_iqr + FROM ( + SELECT quartile, max(_cdb_iqr_column) AS quartile_max from ( + SELECT ${ctx._column} AS _cdb_iqr_column, ntile(4) over (order by ${ctx._column} + ) AS quartile + FROM __cdb_filtered_source) _cdb_quartiles + WHERE quartile = 1 or quartile = 3 + GROUP BY quartile + ) __cdb_iqr + ) +`; + +const binsQueryTpl = ctx => ` + __cdb_bins AS ( + SELECT + CASE WHEN __cdb_total_rows = 0 OR __cdb_iqr = 0 + THEN 1 + ELSE GREATEST( + LEAST(${ctx._minBins}, CAST(__cdb_total_rows AS INT)), + LEAST( + CAST(((__cdb_max_val - __cdb_min_val) / (2 * __cdb_iqr * power(__cdb_total_rows, 1/3))) AS INT), + ${ctx._maxBins} + ) + ) + END AS __cdb_bins_number + FROM __cdb_basics, __cdb_iqrange, __cdb_filtered_source + LIMIT 1 + ) +`; + +const overrideBinsQueryTpl = ctx => ` + __cdb_bins AS ( + SELECT ${ctx._override.bins} AS __cdb_bins_number + ) +`; + +const nullsQueryTpl = ctx => ` + __cdb_nulls AS ( + SELECT + count(*) AS __cdb_nulls_count + FROM (${ctx._query}) __cdb_histogram_nulls + WHERE ${ctx._column} IS NULL + ) +`; + +const infinitiesQueryTpl = ctx => ` + __cdb_infinities AS ( + SELECT + count(*) AS __cdb_infinities_count + FROM (${ctx._query}) __cdb_infinities_query + WHERE + ${ctx._column} = 'infinity'::float + OR + ${ctx._column} = '-infinity'::float + ) +`; + +const nansQueryTpl = ctx => ` + __cdb_nans AS ( + SELECT + count(*) AS __cdb_nans_count + FROM (${ctx._query}) __cdb_nans_query + WHERE ${ctx._column} = 'NaN'::float + ) +`; + +const specialNumericValuesColumnDefinitionTpl = () => ` + __cdb_infinities_count AS infinities_count, + __cdb_nans_count AS nans_count +`; + +const specialNumericValuesCTETpl = () => ` + __cdb_infinities, __cdb_nans +`; + +const specialNumericValuesColumnTpl = () => ` + infinities_count, nans_count +`; + +const histogramQueryTpl = ctx => ` + SELECT + (__cdb_max_val - __cdb_min_val) / cast(__cdb_bins_number as float) AS bin_width, + __cdb_bins_number AS bins_number, + __cdb_nulls_count AS nulls_count, + ${ctx._isFloatColumn ? `${specialNumericValuesColumnDefinitionTpl()},` : ''} + __cdb_avg_val AS avg_val, + CASE WHEN __cdb_min_val = __cdb_max_val + THEN 0 + ELSE GREATEST( + 1, + LEAST( + WIDTH_BUCKET(${ctx._column}, __cdb_min_val, __cdb_max_val, __cdb_bins_number), + __cdb_bins_number + ) + ) - 1 + END AS bin, + min(${ctx._column})::numeric AS min, + max(${ctx._column})::numeric AS max, + avg(${ctx._column})::numeric AS avg, + count(*) AS freq + FROM __cdb_filtered_source, __cdb_basics, __cdb_nulls, __cdb_bins + ${ctx._isFloatColumn ? `, ${specialNumericValuesCTETpl()}` : ''} + GROUP BY bin, bins_number, bin_width, nulls_count, avg_val + ${ctx._isFloatColumn ? `, ${specialNumericValuesColumnTpl()}` : ''} + ORDER BY bin +`; + +const BIN_MIN_NUMBER = 6; +const BIN_MAX_NUMBER = 48; + +const TYPE = 'histogram'; + +/** +Numeric histogram: +{ + type: 'histogram', + options: { + column: 'name', // column data type: numeric + bins: 10 // OPTIONAL + } +} +*/ +module.exports = class NumericHistogram extends BaseDataview { + constructor (query, options, queries) { + super(); + + if (typeof options.column !== 'string') { + throw new Error('Histogram expects `column` in widget options'); + } + + this.query = query; + this.queries = queries; + this.column = options.column; + this.bins = options.bins; + this.aggregation = options.aggregation; + this.offset = options.offset; + + this._columnType = null; + } + + sql (psql, override, callback) { + var self = this; + + if (!callback) { + callback = override; + override = {}; + } + + if (this._columnType === null) { + this.getColumnType(psql, this.column, this.queries.no_filters, function (err, type) { + // assume numeric, will fail later + self._columnType = 'numeric'; + if (!err && !!type) { + self._columnType = Object.keys(type).find(function (key) { + return type[key]; + }); + } + self.sql(psql, override, callback); + }, true); // use read-only transaction + return null; + } + + this._buildQuery(psql, override, callback); + } + + + _buildQuery (psql, override, callback) { + const histogramSql = this.buildNumericHistogramQueryTpl({ + _override: override, + _column: this._columnType === 'date' ? columnCastTpl({ column: this.column }) : this.column, + _isFloatColumn: this._columnType === 'float', + _query: this.query, + _start: this.getBinStart(override), + _end: this.getBinEnd(override), + _minBins: BIN_MIN_NUMBER, + _maxBins: BIN_MAX_NUMBER, + }); + + debug(histogramSql); + + return callback(null, histogramSql); + } + + buildNumericHistogramQueryTpl (ctx) { + return ` + WITH + ${filteredQueryTpl(ctx)}, + ${this._shouldOverride(ctx._override) ? overrideBasicsQueryTpl(ctx) : basicsQueryTpl(ctx)}, + ${this._shouldOverrideRange(ctx._override) || this._shouldOverrideBins(ctx._override) ? + overrideBinsQueryTpl(ctx) : + `${iqrQueryTpl(ctx)}, ${binsQueryTpl(ctx)}` + }, + ${nullsQueryTpl(ctx)} + ${ctx._isFloatColumn ? `,${infinitiesQueryTpl(ctx)}, ${nansQueryTpl(ctx)}` : ''} + ${histogramQueryTpl(ctx)} + `; + } + + _shouldOverride (override) { + return this._shouldOverrideRange(override) && this._shouldOverrideBins(override); + } + + _shouldOverrideRange (override) { + return override && override.hasOwnProperty('start') && override.hasOwnProperty('end'); + } + + _shouldOverrideBins (override) { + return override && override.hasOwnProperty('bins'); + } + + format (result, override) { + override = override || {}; + var buckets = []; + + var binsCount = this.getBinsCount(override); + var width = this.getWidth(override); + var binsStart = this.getBinStart(override); + var nulls = 0; + var infinities = 0; + var nans = 0; + var avg; + var timestampStart; + var aggregation; + var offset; + + if (result.rows.length) { + var firstRow = result.rows[0]; + binsCount = firstRow.bins_number; + width = firstRow.bin_width || width; + avg = firstRow.avg_val; + nulls = firstRow.nulls_count; + timestampStart = firstRow.timestamp_start; + infinities = firstRow.infinities_count; + nans = firstRow.nans_count; + binsStart = this.populateBinStart(override, firstRow); + + if (Number.isFinite(timestampStart)) { + aggregation = this.getAggregation(override); + offset = this.getOffset(override); + } + + buckets = result.rows.map(row => ({ bin, min, max, avg, freq } = row)); + } + + return { + aggregation: aggregation, + offset: offset, + timestamp_start: timestampStart, + bin_width: width, + bins_count: binsCount, + bins_start: binsStart, + nulls: nulls, + infinities: infinities, + nans: nans, + avg: avg, + bins: buckets + }; + } + + getType () { + return TYPE; + } + + toString () { + return JSON.stringify({ + _type: TYPE, + _column: this.column, + _query: this.query + }); + } + + getAggregation (override) { + return override && override.aggregation ? override.aggregation : this.aggregation; + } + + getOffset (override) { + if (override && override.offset) { + return override.offset; + } + if (this.offset) { + return this.offset; + } + + return 0; + } + + getBinStart (override) { + if (override.hasOwnProperty('start') && override.hasOwnProperty('end')) { + return Math.min(override.start, override.end); + } + return override.start || 0; + } + + populateBinStart (override, firstRow) { + var binStart; + + if (override.hasOwnProperty('start')) { + binStart = this.getBinStart(override); + } else { + binStart = firstRow.min; + } + + return binStart; + } + + getBinEnd (override) { + if (override.hasOwnProperty('start') && override.hasOwnProperty('end')) { + return Math.max(override.start, override.end); + } + return override.end || 0; + } + + getBinsCount (override) { + return override.bins || 0; + } + + getWidth (override) { + var width = 0; + var binsCount = override.bins; + + if (binsCount && Number.isFinite(override.start) && Number.isFinite(override.end)) { + width = (override.end - override.start) / binsCount; + } + + return width; + } +}; From 8ce25d958c1e9b1a9ddac8110d13841ea2dad036 Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 8 Sep 2017 11:53:47 +0200 Subject: [PATCH 047/159] fix namespace for debugging --- lib/cartodb/models/dataview/date-histogram.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cartodb/models/dataview/date-histogram.js b/lib/cartodb/models/dataview/date-histogram.js index 2e55ea2d..eafd5f34 100644 --- a/lib/cartodb/models/dataview/date-histogram.js +++ b/lib/cartodb/models/dataview/date-histogram.js @@ -1,5 +1,5 @@ const BaseDataview = require('./base'); -const debug = require('debug')('windshaft:dataview:histogram'); +const debug = require('debug')('windshaft:dataview:date-histogram'); const dateIntervalQueryTpl = ctx => ` WITH From 157946cc42ecb3c28b51646f7860daf3b194d3b7 Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 8 Sep 2017 11:54:30 +0200 Subject: [PATCH 048/159] Rename DateHistogram class --- lib/cartodb/models/dataview/date-histogram.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cartodb/models/dataview/date-histogram.js b/lib/cartodb/models/dataview/date-histogram.js index eafd5f34..c8f59b1b 100644 --- a/lib/cartodb/models/dataview/date-histogram.js +++ b/lib/cartodb/models/dataview/date-histogram.js @@ -168,7 +168,7 @@ const DATE_AGGREGATIONS = { } } */ -module.exports = class Histogram extends BaseDataview { +module.exports = class DateHistogram extends BaseDataview { constructor (query, options, queries) { super(); From a3112aa929d79fcf1980495005b08b7aac8e2a0e Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 8 Sep 2017 11:55:28 +0200 Subject: [PATCH 049/159] fix function name in recursion --- lib/cartodb/models/dataview/date-histogram.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cartodb/models/dataview/date-histogram.js b/lib/cartodb/models/dataview/date-histogram.js index c8f59b1b..f7d11c67 100644 --- a/lib/cartodb/models/dataview/date-histogram.js +++ b/lib/cartodb/models/dataview/date-histogram.js @@ -248,7 +248,7 @@ module.exports = class DateHistogram extends BaseDataview { this.aggregation = aggregation; } override.aggregation = this.aggregation; - this._buildDateHistogramQuery(psql, override, callback); + this._buildQuery(psql, override, callback); }.bind(this)); return null; } From 347dea8f6656f2429a9c4f456f0c44b30fedc57f Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 8 Sep 2017 12:01:15 +0200 Subject: [PATCH 050/159] naming private functions --- lib/cartodb/models/dataview/date-histogram.js | 52 +++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/lib/cartodb/models/dataview/date-histogram.js b/lib/cartodb/models/dataview/date-histogram.js index f7d11c67..efd3f4be 100644 --- a/lib/cartodb/models/dataview/date-histogram.js +++ b/lib/cartodb/models/dataview/date-histogram.js @@ -208,17 +208,17 @@ module.exports = class DateHistogram extends BaseDataview { return null; } - if (this.isDateHistogram(override)) { + if (this._isDateHistogram(override)) { return this._buildQuery(psql, override, callback); } } - isDateHistogram (override) { + _isDateHistogram (override) { return this._columnType === 'date' && (this.aggregation !== undefined || override.aggregation !== undefined); } - buildDateHistogramQueryTpl (ctx) { + _buildDateHistogramQueryTpl (ctx) { return ` WITH ${ctx._override && ctx._override.hasOwnProperty('start') && ctx._override.hasOwnProperty('end') ? @@ -241,7 +241,7 @@ module.exports = class DateHistogram extends BaseDataview { } if (_aggregation === 'auto') { - this.getAutomaticAggregation(psql, function (err, aggregation) { + this._getAutomaticAggregation(psql, function (err, aggregation) { if (err || aggregation === 'none') { this.aggregation = 'day'; } else { @@ -253,14 +253,14 @@ module.exports = class DateHistogram extends BaseDataview { return null; } - const histogramSql = this.buildDateHistogramQueryTpl({ + const histogramSql = this._buildDateHistogramQueryTpl({ _override: override, _query: this.query, _column: this.column, _aggregation: _aggregation, - _start: this.getBinStart(override), - _end: this.getBinEnd(override), - _offset: this.parseOffset(_offset, _aggregation) + _start: this._getBinStart(override), + _end: this._getBinEnd(override), + _offset: this._parseOffset(_offset, _aggregation) }); debug(histogramSql); @@ -268,7 +268,7 @@ module.exports = class DateHistogram extends BaseDataview { return callback(null, histogramSql); } - getAutomaticAggregation (psql, callback) { + _getAutomaticAggregation (psql, callback) { var dateIntervalQuery = dateIntervalQueryTpl({ query: this.query, column: this.column @@ -312,9 +312,9 @@ module.exports = class DateHistogram extends BaseDataview { override = override || {}; var buckets = []; - var binsCount = this.getBinsCount(override); - var width = this.getWidth(override); - var binsStart = this.getBinStart(override); + var binsCount = this._getBinsCount(override); + var width = this._getWidth(override); + var binsStart = this._getBinStart(override); var nulls = 0; var infinities = 0; var nans = 0; @@ -332,11 +332,11 @@ module.exports = class DateHistogram extends BaseDataview { timestampStart = firstRow.timestamp_start; infinities = firstRow.infinities_count; nans = firstRow.nans_count; - binsStart = this.populateBinStart(override, firstRow); + binsStart = this._populateBinStart(override, firstRow); if (Number.isFinite(timestampStart)) { - aggregation = this.getAggregation(override); - offset = this.getOffset(override); + aggregation = this._getAggregation(override); + offset = this._getOffset(override); } buckets = result.rows.map(row => ({ bin, min, max, avg, freq, timestamp } = row)); @@ -357,11 +357,11 @@ module.exports = class DateHistogram extends BaseDataview { }; } - getType () { + _getType () { return TYPE; } - toString () { + _toString () { return JSON.stringify({ _type: TYPE, _column: this.column, @@ -369,11 +369,11 @@ module.exports = class DateHistogram extends BaseDataview { }); } - getAggregation (override) { + _getAggregation (override) { return override && override.aggregation ? override.aggregation : this.aggregation; } - getOffset (override) { + _getOffset (override) { if (override && override.offset) { return override.offset; } @@ -384,20 +384,20 @@ module.exports = class DateHistogram extends BaseDataview { return 0; } - getBinStart (override) { + _getBinStart (override) { if (override.hasOwnProperty('start') && override.hasOwnProperty('end')) { return Math.min(override.start, override.end); } return override.start || 0; } - populateBinStart (override, firstRow) { + _populateBinStart (override, firstRow) { var binStart; if (firstRow.hasOwnProperty('timestamp')) { binStart = firstRow.timestamp; } else if (override.hasOwnProperty('start')) { - binStart = this.getBinStart(override); + binStart = this._getBinStart(override); } else { binStart = firstRow.min; } @@ -405,18 +405,18 @@ module.exports = class DateHistogram extends BaseDataview { return binStart; } - getBinEnd (override) { + _getBinEnd (override) { if (override.hasOwnProperty('start') && override.hasOwnProperty('end')) { return Math.max(override.start, override.end); } return override.end || 0; } - getBinsCount (override) { + _getBinsCount (override) { return override.bins || 0; } - getWidth (override) { + _getWidth (override) { var width = 0; var binsCount = override.bins; @@ -427,7 +427,7 @@ module.exports = class DateHistogram extends BaseDataview { return width; } - parseOffset (offset, aggregation) { + _parseOffset (offset, aggregation) { if (!offset) { return '0'; } From 3441ad6aa96568b6c14ecfb8d1407275f98c2bd5 Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 8 Sep 2017 12:04:25 +0200 Subject: [PATCH 051/159] rename _buildDateHistogramQueryTpl by _buildQueryTpl --- lib/cartodb/models/dataview/date-histogram.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/cartodb/models/dataview/date-histogram.js b/lib/cartodb/models/dataview/date-histogram.js index efd3f4be..7dd25683 100644 --- a/lib/cartodb/models/dataview/date-histogram.js +++ b/lib/cartodb/models/dataview/date-histogram.js @@ -218,7 +218,7 @@ module.exports = class DateHistogram extends BaseDataview { return this._columnType === 'date' && (this.aggregation !== undefined || override.aggregation !== undefined); } - _buildDateHistogramQueryTpl (ctx) { + _buildQueryTpl (ctx) { return ` WITH ${ctx._override && ctx._override.hasOwnProperty('start') && ctx._override.hasOwnProperty('end') ? @@ -253,7 +253,7 @@ module.exports = class DateHistogram extends BaseDataview { return null; } - const histogramSql = this._buildDateHistogramQueryTpl({ + const histogramSql = this._buildQueryTpl({ _override: override, _query: this.query, _column: this.column, From db969a51ad7c46061beacc9d4194a9ce28c7bd67 Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 8 Sep 2017 12:07:07 +0200 Subject: [PATCH 052/159] make public some private functions --- lib/cartodb/models/dataview/date-histogram.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/cartodb/models/dataview/date-histogram.js b/lib/cartodb/models/dataview/date-histogram.js index 7dd25683..63b4115e 100644 --- a/lib/cartodb/models/dataview/date-histogram.js +++ b/lib/cartodb/models/dataview/date-histogram.js @@ -357,11 +357,11 @@ module.exports = class DateHistogram extends BaseDataview { }; } - _getType () { + getType () { return TYPE; } - _toString () { + toString () { return JSON.stringify({ _type: TYPE, _column: this.column, From 7f14785091cf53506176744b615d495646b640c0 Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 8 Sep 2017 12:07:49 +0200 Subject: [PATCH 053/159] fix namespace for debugging --- lib/cartodb/models/dataview/numeric-histogram.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cartodb/models/dataview/numeric-histogram.js b/lib/cartodb/models/dataview/numeric-histogram.js index e5149793..cb4d8a58 100644 --- a/lib/cartodb/models/dataview/numeric-histogram.js +++ b/lib/cartodb/models/dataview/numeric-histogram.js @@ -1,5 +1,5 @@ const BaseDataview = require('./base'); -const debug = require('debug')('windshaft:dataview:histogram'); +const debug = require('debug')('windshaft:dataview:numeric-histogram'); const columnCastTpl = ctx => `date_part('epoch', ${ctx.column})`; From 38f0e23efe05d1834e2c35eb2d28e981f9b388d2 Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 8 Sep 2017 12:10:31 +0200 Subject: [PATCH 054/159] rename buildNumericHistogramQueryTpl by buildQueryTpl --- lib/cartodb/models/dataview/numeric-histogram.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/cartodb/models/dataview/numeric-histogram.js b/lib/cartodb/models/dataview/numeric-histogram.js index cb4d8a58..55368f6d 100644 --- a/lib/cartodb/models/dataview/numeric-histogram.js +++ b/lib/cartodb/models/dataview/numeric-histogram.js @@ -207,7 +207,7 @@ module.exports = class NumericHistogram extends BaseDataview { _buildQuery (psql, override, callback) { - const histogramSql = this.buildNumericHistogramQueryTpl({ + const histogramSql = this.buildQueryTpl({ _override: override, _column: this._columnType === 'date' ? columnCastTpl({ column: this.column }) : this.column, _isFloatColumn: this._columnType === 'float', @@ -223,7 +223,7 @@ module.exports = class NumericHistogram extends BaseDataview { return callback(null, histogramSql); } - buildNumericHistogramQueryTpl (ctx) { + buildQueryTpl (ctx) { return ` WITH ${filteredQueryTpl(ctx)}, From 4a00a2d6739270aafba54ae44448324a6b94406c Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 8 Sep 2017 12:15:21 +0200 Subject: [PATCH 055/159] rename buildQueryTpl by _buildQueryTpl --- lib/cartodb/models/dataview/numeric-histogram.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/cartodb/models/dataview/numeric-histogram.js b/lib/cartodb/models/dataview/numeric-histogram.js index 55368f6d..59400a00 100644 --- a/lib/cartodb/models/dataview/numeric-histogram.js +++ b/lib/cartodb/models/dataview/numeric-histogram.js @@ -207,7 +207,7 @@ module.exports = class NumericHistogram extends BaseDataview { _buildQuery (psql, override, callback) { - const histogramSql = this.buildQueryTpl({ + const histogramSql = this._buildQueryTpl({ _override: override, _column: this._columnType === 'date' ? columnCastTpl({ column: this.column }) : this.column, _isFloatColumn: this._columnType === 'float', @@ -223,7 +223,7 @@ module.exports = class NumericHistogram extends BaseDataview { return callback(null, histogramSql); } - buildQueryTpl (ctx) { + _buildQueryTpl (ctx) { return ` WITH ${filteredQueryTpl(ctx)}, From a4ebce52db238b850cea0d5b582d75c60aa9a05e Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 8 Sep 2017 12:16:56 +0200 Subject: [PATCH 056/159] remove unnecessary properties in format function --- lib/cartodb/models/dataview/numeric-histogram.js | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/lib/cartodb/models/dataview/numeric-histogram.js b/lib/cartodb/models/dataview/numeric-histogram.js index 59400a00..7beb8976 100644 --- a/lib/cartodb/models/dataview/numeric-histogram.js +++ b/lib/cartodb/models/dataview/numeric-histogram.js @@ -261,9 +261,6 @@ module.exports = class NumericHistogram extends BaseDataview { var infinities = 0; var nans = 0; var avg; - var timestampStart; - var aggregation; - var offset; if (result.rows.length) { var firstRow = result.rows[0]; @@ -271,23 +268,14 @@ module.exports = class NumericHistogram extends BaseDataview { width = firstRow.bin_width || width; avg = firstRow.avg_val; nulls = firstRow.nulls_count; - timestampStart = firstRow.timestamp_start; infinities = firstRow.infinities_count; nans = firstRow.nans_count; binsStart = this.populateBinStart(override, firstRow); - if (Number.isFinite(timestampStart)) { - aggregation = this.getAggregation(override); - offset = this.getOffset(override); - } - buckets = result.rows.map(row => ({ bin, min, max, avg, freq } = row)); } return { - aggregation: aggregation, - offset: offset, - timestamp_start: timestampStart, bin_width: width, bins_count: binsCount, bins_start: binsStart, From c863cdd9f69f3194dc1bd3cd0450b518e29a7727 Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 8 Sep 2017 12:17:29 +0200 Subject: [PATCH 057/159] remove getAggregation function --- lib/cartodb/models/dataview/numeric-histogram.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/cartodb/models/dataview/numeric-histogram.js b/lib/cartodb/models/dataview/numeric-histogram.js index 7beb8976..9fd5bdfd 100644 --- a/lib/cartodb/models/dataview/numeric-histogram.js +++ b/lib/cartodb/models/dataview/numeric-histogram.js @@ -299,10 +299,6 @@ module.exports = class NumericHistogram extends BaseDataview { }); } - getAggregation (override) { - return override && override.aggregation ? override.aggregation : this.aggregation; - } - getOffset (override) { if (override && override.offset) { return override.offset; From fb753e50a25955adc5a97ef30c3ed8270caef1f7 Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 8 Sep 2017 12:19:05 +0200 Subject: [PATCH 058/159] remove getOffset function --- lib/cartodb/models/dataview/numeric-histogram.js | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/lib/cartodb/models/dataview/numeric-histogram.js b/lib/cartodb/models/dataview/numeric-histogram.js index 9fd5bdfd..3f1f9840 100644 --- a/lib/cartodb/models/dataview/numeric-histogram.js +++ b/lib/cartodb/models/dataview/numeric-histogram.js @@ -299,17 +299,6 @@ module.exports = class NumericHistogram extends BaseDataview { }); } - getOffset (override) { - if (override && override.offset) { - return override.offset; - } - if (this.offset) { - return this.offset; - } - - return 0; - } - getBinStart (override) { if (override.hasOwnProperty('start') && override.hasOwnProperty('end')) { return Math.min(override.start, override.end); From 78b95d05d000446ed33d35f32c0282a278fac963 Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 8 Sep 2017 12:21:22 +0200 Subject: [PATCH 059/159] make private functions --- .../models/dataview/numeric-histogram.js | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/cartodb/models/dataview/numeric-histogram.js b/lib/cartodb/models/dataview/numeric-histogram.js index 3f1f9840..56167492 100644 --- a/lib/cartodb/models/dataview/numeric-histogram.js +++ b/lib/cartodb/models/dataview/numeric-histogram.js @@ -212,8 +212,8 @@ module.exports = class NumericHistogram extends BaseDataview { _column: this._columnType === 'date' ? columnCastTpl({ column: this.column }) : this.column, _isFloatColumn: this._columnType === 'float', _query: this.query, - _start: this.getBinStart(override), - _end: this.getBinEnd(override), + _start: this._getBinStart(override), + _end: this._getBinEnd(override), _minBins: BIN_MIN_NUMBER, _maxBins: BIN_MAX_NUMBER, }); @@ -254,9 +254,9 @@ module.exports = class NumericHistogram extends BaseDataview { override = override || {}; var buckets = []; - var binsCount = this.getBinsCount(override); - var width = this.getWidth(override); - var binsStart = this.getBinStart(override); + var binsCount = this._getBinsCount(override); + var width = this._getWidth(override); + var binsStart = this._getBinStart(override); var nulls = 0; var infinities = 0; var nans = 0; @@ -270,7 +270,7 @@ module.exports = class NumericHistogram extends BaseDataview { nulls = firstRow.nulls_count; infinities = firstRow.infinities_count; nans = firstRow.nans_count; - binsStart = this.populateBinStart(override, firstRow); + binsStart = this._populateBinStart(override, firstRow); buckets = result.rows.map(row => ({ bin, min, max, avg, freq } = row)); } @@ -299,18 +299,18 @@ module.exports = class NumericHistogram extends BaseDataview { }); } - getBinStart (override) { + _getBinStart (override) { if (override.hasOwnProperty('start') && override.hasOwnProperty('end')) { return Math.min(override.start, override.end); } return override.start || 0; } - populateBinStart (override, firstRow) { + _populateBinStart (override, firstRow) { var binStart; if (override.hasOwnProperty('start')) { - binStart = this.getBinStart(override); + binStart = this._getBinStart(override); } else { binStart = firstRow.min; } @@ -318,18 +318,18 @@ module.exports = class NumericHistogram extends BaseDataview { return binStart; } - getBinEnd (override) { + _getBinEnd (override) { if (override.hasOwnProperty('start') && override.hasOwnProperty('end')) { return Math.max(override.start, override.end); } return override.end || 0; } - getBinsCount (override) { + _getBinsCount (override) { return override.bins || 0; } - getWidth (override) { + _getWidth (override) { var width = 0; var binsCount = override.bins; From 7d42afcdb4c72b34f0bf59d5b4d6f1287fae5d5a Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 8 Sep 2017 12:22:24 +0200 Subject: [PATCH 060/159] remove unnecessary properties of NumericHistogram --- lib/cartodb/models/dataview/numeric-histogram.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/cartodb/models/dataview/numeric-histogram.js b/lib/cartodb/models/dataview/numeric-histogram.js index 56167492..ea0beb9a 100644 --- a/lib/cartodb/models/dataview/numeric-histogram.js +++ b/lib/cartodb/models/dataview/numeric-histogram.js @@ -174,9 +174,7 @@ module.exports = class NumericHistogram extends BaseDataview { this.queries = queries; this.column = options.column; this.bins = options.bins; - this.aggregation = options.aggregation; - this.offset = options.offset; - + this._columnType = null; } From 23dd143fa50ebc2ceec88b55d29961f435c5f25e Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 8 Sep 2017 12:48:08 +0200 Subject: [PATCH 061/159] Make Histogram class as context of state pattern --- lib/cartodb/models/dataview/histogram.js | 663 +---------------------- 1 file changed, 24 insertions(+), 639 deletions(-) diff --git a/lib/cartodb/models/dataview/histogram.js b/lib/cartodb/models/dataview/histogram.js index c4b53423..a8cd12ed 100644 --- a/lib/cartodb/models/dataview/histogram.js +++ b/lib/cartodb/models/dataview/histogram.js @@ -1,652 +1,37 @@ -const BaseDataview = require('./base'); const debug = require('debug')('windshaft:dataview:histogram'); - -const columnCastTpl = ctx => `date_part('epoch', ${ctx.column})`; - -const dateIntervalQueryTpl = ctx => ` - WITH - __cdb_dates AS ( - SELECT - MAX(${ctx.column}::timestamp) AS __cdb_end, - MIN(${ctx.column}::timestamp) AS __cdb_start - FROM (${ctx.query}) __cdb_source - ), - __cdb_interval_in_days AS ( - SELECT - DATE_PART('day', __cdb_end - __cdb_start) AS __cdb_days - FROM __cdb_dates - ), - __cdb_interval_in_hours AS ( - SELECT - __cdb_days * 24 + DATE_PART('hour', __cdb_end - __cdb_start) AS __cdb_hours - FROM __cdb_interval_in_days, __cdb_dates - ), - __cdb_interval_in_minutes AS ( - SELECT - __cdb_hours * 60 + DATE_PART('minute', __cdb_end - __cdb_start) AS __cdb_minutes - FROM __cdb_interval_in_hours, __cdb_dates - ), - __cdb_interval_in_seconds AS ( - SELECT - __cdb_minutes * 60 + DATE_PART('second', __cdb_end - __cdb_start) AS __cdb_seconds - FROM __cdb_interval_in_minutes, __cdb_dates - ) - SELECT - ROUND(__cdb_days / 365) AS year, - ROUND(__cdb_days / 90) AS quarter, - ROUND(__cdb_days / 30) AS month, - ROUND(__cdb_days / 7) AS week, - __cdb_days AS day, - __cdb_hours AS hour, - __cdb_minutes AS minute, - __cdb_seconds AS second - FROM __cdb_interval_in_days, __cdb_interval_in_hours, __cdb_interval_in_minutes, __cdb_interval_in_seconds -`; - -const filterOutSpecialNumericValues = ctx => ` - ${ctx._column} != 'infinity'::float - AND - ${ctx._column} != '-infinity'::float - AND - ${ctx._column} != 'NaN'::float -`; - -const filteredQueryTpl = ctx => ` - __cdb_filtered_source AS ( - SELECT * - FROM (${ctx._query}) __cdb_filtered_source_query - WHERE ${ctx._column} IS NOT NULL - ${ctx._isFloatColumn ? `AND ${filterOutSpecialNumericValues(ctx)}` : ''} - ) -`; - -const basicsQueryTpl = ctx => ` - __cdb_basics AS ( - SELECT - max(${ctx._column}) AS __cdb_max_val, min(${ctx._column}) AS __cdb_min_val, - avg(${ctx._column}) AS __cdb_avg_val, count(1) AS __cdb_total_rows - FROM __cdb_filtered_source - ) -`; - -const overrideBasicsQueryTpl = ctx => ` - __cdb_basics AS ( - SELECT - max(${ctx._end}) AS __cdb_max_val, min(${ctx._start}) AS __cdb_min_val, - avg(${ctx._column}) AS __cdb_avg_val, count(1) AS __cdb_total_rows - FROM __cdb_filtered_source - ) -`; - -const iqrQueryTpl = ctx => ` - __cdb_iqrange AS ( - SELECT max(quartile_max) - min(quartile_max) AS __cdb_iqr - FROM ( - SELECT quartile, max(_cdb_iqr_column) AS quartile_max from ( - SELECT ${ctx._column} AS _cdb_iqr_column, ntile(4) over (order by ${ctx._column} - ) AS quartile - FROM __cdb_filtered_source) _cdb_quartiles - WHERE quartile = 1 or quartile = 3 - GROUP BY quartile - ) __cdb_iqr - ) -`; - -const binsQueryTpl = ctx => ` - __cdb_bins AS ( - SELECT - CASE WHEN __cdb_total_rows = 0 OR __cdb_iqr = 0 - THEN 1 - ELSE GREATEST( - LEAST(${ctx._minBins}, CAST(__cdb_total_rows AS INT)), - LEAST( - CAST(((__cdb_max_val - __cdb_min_val) / (2 * __cdb_iqr * power(__cdb_total_rows, 1/3))) AS INT), - ${ctx._maxBins} - ) - ) - END AS __cdb_bins_number - FROM __cdb_basics, __cdb_iqrange, __cdb_filtered_source - LIMIT 1 - ) -`; - -const overrideBinsQueryTpl = ctx => ` - __cdb_bins AS ( - SELECT ${ctx._override.bins} AS __cdb_bins_number - ) -`; - -const nullsQueryTpl = ctx => ` - __cdb_nulls AS ( - SELECT - count(*) AS __cdb_nulls_count - FROM (${ctx._query}) __cdb_histogram_nulls - WHERE ${ctx._column} IS NULL - ) -`; - -const infinitiesQueryTpl = ctx => ` - __cdb_infinities AS ( - SELECT - count(*) AS __cdb_infinities_count - FROM (${ctx._query}) __cdb_infinities_query - WHERE - ${ctx._column} = 'infinity'::float - OR - ${ctx._column} = '-infinity'::float - ) -`; - -const nansQueryTpl = ctx => ` - __cdb_nans AS ( - SELECT - count(*) AS __cdb_nans_count - FROM (${ctx._query}) __cdb_nans_query - WHERE ${ctx._column} = 'NaN'::float - ) -`; - -const specialNumericValuesColumnDefinitionTpl = () => ` - __cdb_infinities_count AS infinities_count, - __cdb_nans_count AS nans_count -`; - -const specialNumericValuesCTETpl = () => ` - __cdb_infinities, __cdb_nans -`; - -const specialNumericValuesColumnTpl = () => ` - infinities_count, nans_count -`; - -const histogramQueryTpl = ctx => ` - SELECT - (__cdb_max_val - __cdb_min_val) / cast(__cdb_bins_number as float) AS bin_width, - __cdb_bins_number AS bins_number, - __cdb_nulls_count AS nulls_count, - ${ctx._isFloatColumn ? `${specialNumericValuesColumnDefinitionTpl()},` : ''} - __cdb_avg_val AS avg_val, - CASE WHEN __cdb_min_val = __cdb_max_val - THEN 0 - ELSE GREATEST( - 1, - LEAST( - WIDTH_BUCKET(${ctx._column}, __cdb_min_val, __cdb_max_val, __cdb_bins_number), - __cdb_bins_number - ) - ) - 1 - END AS bin, - min(${ctx._column})::numeric AS min, - max(${ctx._column})::numeric AS max, - avg(${ctx._column})::numeric AS avg, - count(*) AS freq - FROM __cdb_filtered_source, __cdb_basics, __cdb_nulls, __cdb_bins - ${ctx._isFloatColumn ? `, ${specialNumericValuesCTETpl()}` : ''} - GROUP BY bin, bins_number, bin_width, nulls_count, avg_val - ${ctx._isFloatColumn ? `, ${specialNumericValuesColumnTpl()}` : ''} - ORDER BY bin -`; - -const dateBasicsQueryTpl = ctx => ` - __cdb_basics AS ( - SELECT - max(date_part('epoch', ${ctx._column})) AS __cdb_max_val, - min(date_part('epoch', ${ctx._column})) AS __cdb_min_val, - avg(date_part('epoch', ${ctx._column})) AS __cdb_avg_val, - min( - date_trunc( - '${ctx._aggregation}', ${ctx._column}::timestamp AT TIME ZONE '${ctx._offset}' - ) - ) AS __cdb_start_date, - max(${ctx._column}::timestamp AT TIME ZONE '${ctx._offset}') AS __cdb_end_date, - count(1) AS __cdb_total_rows - FROM (${ctx._query}) __cdb_basics_query - ) -`; - -const dateOverrideBasicsQueryTpl = ctx => ` - __cdb_basics AS ( - SELECT - max(${ctx._end}) AS __cdb_max_val, - min(${ctx._start}) AS __cdb_min_val, - avg(date_part('epoch', ${ctx._column})) AS __cdb_avg_val, - min( - date_trunc( - '${ctx._aggregation}', - TO_TIMESTAMP(${ctx._start})::timestamp AT TIME ZONE '${ctx._offset}' - ) - ) AS __cdb_start_date, - max( - TO_TIMESTAMP(${ctx._end})::timestamp AT TIME ZONE '${ctx._offset}' - ) AS __cdb_end_date, - count(1) AS __cdb_total_rows - FROM (${ctx._query}) __cdb_basics_query - ) -`; - -const dateBinsQueryTpl = ctx => ` - __cdb_bins AS ( - SELECT - __cdb_bins_array, - ARRAY_LENGTH(__cdb_bins_array, 1) AS __cdb_bins_number - FROM ( - SELECT - ARRAY( - SELECT GENERATE_SERIES( - __cdb_start_date::timestamptz, - __cdb_end_date::timestamptz, - ${ctx._aggregation === 'quarter' ? `'3 month'::interval` : `'1 ${ctx._aggregation}'::interval`} - ) - ) AS __cdb_bins_array - FROM __cdb_basics - ) __cdb_bins_array_query - ) -`; - -const dateHistogramQueryTpl = ctx => ` - SELECT - (__cdb_max_val - __cdb_min_val) / cast(__cdb_bins_number as float) AS bin_width, - __cdb_bins_number AS bins_number, - __cdb_nulls_count AS nulls_count, - CASE WHEN __cdb_min_val = __cdb_max_val - THEN 0 - ELSE GREATEST( - 1, - LEAST( - WIDTH_BUCKET( - ${ctx._column}::timestamp AT TIME ZONE '${ctx._offset}', - __cdb_bins_array - ), - __cdb_bins_number - ) - ) - 1 - END AS bin, - min( - date_part( - 'epoch', - date_trunc( - '${ctx._aggregation}', ${ctx._column}::timestamp AT TIME ZONE '${ctx._offset}' - ) AT TIME ZONE '${ctx._offset}' - ) - )::numeric AS timestamp, - date_part('epoch', __cdb_start_date)::numeric AS timestamp_start, - min(date_part('epoch', ${ctx._column}))::numeric AS min, - max(date_part('epoch', ${ctx._column}))::numeric AS max, - avg(date_part('epoch', ${ctx._column}))::numeric AS avg, - count(*) AS freq - FROM (${ctx._query}) __cdb_histogram, __cdb_basics, __cdb_bins, __cdb_nulls - WHERE date_part('epoch', ${ctx._column}) IS NOT NULL - GROUP BY bin, bins_number, bin_width, nulls_count, timestamp_start - ORDER BY bin -`; - -const MAX_INTERVAL_VALUE = 366; -const BIN_MIN_NUMBER = 6; -const BIN_MAX_NUMBER = 48; +const NumericHistogram = require('./numeric-histogram'); +const DateHistogram = require('./date-histogram'); const TYPE = 'histogram'; +const DATE_HISTOGRAM = 'DateHistogram'; +const NUMERIC_HISTOGRAM = 'NumericHistogram'; -const DATE_AGGREGATIONS = { - 'auto': true, - 'minute': true, - 'hour': true, - 'day': true, - 'week': true, - 'month': true, - 'quarter': true, - 'year': true -}; - -/** -Numeric histogram: -{ - type: 'histogram', - options: { - column: 'name', // column data type: numeric - bins: 10 // OPTIONAL - } -} - -Time series: -{ - type: 'histogram', - options: { - column: 'date', // column data type: date - aggregation: 'day' // OPTIONAL (if undefined then it'll be built as numeric) - offset: -7200 // OPTIONAL (UTC offset in seconds) - } - } - */ - -module.exports = class Histogram extends BaseDataview { +module.exports = class Histogram { constructor (query, options, queries) { - super(); - - if (typeof options.column !== 'string') { - throw new Error('Histogram expects `column` in widget options'); + switch (this._getHistogramSubtype(options)) { + case DATE_HISTOGRAM: + debug('Delegating to DateHistogram with options: %j', options) + this.dataview = new DateHistogram(query, options, queries); + break; + case NUMERIC_HISTOGRAM: + debug('Delegating to NumericHistogram with options: %j', options) + this.dataview = new NumericHistogram(query, options, queries); + break; + + default: + throw new Error('Unsupported Histogram type'); } - - this.query = query; - this.queries = queries; - this.column = options.column; - this.bins = options.bins; - this.aggregation = options.aggregation; - this.offset = options.offset; - - this._columnType = null; } - sql (psql, override, callback) { - var self = this; - - if (!callback) { - callback = override; - override = {}; + _getHistogramSubtype (options) { + if(options.bins && !options.aggregation) { + return NUMERIC_HISTOGRAM + } else if(options.aggregation && !options.bins) { + return DATE_HISTOGRAM } - - if (this._columnType === null) { - this.getColumnType(psql, this.column, this.queries.no_filters, function (err, type) { - // assume numeric, will fail later - self._columnType = 'numeric'; - if (!err && !!type) { - self._columnType = Object.keys(type).find(function (key) { - return type[key]; - }); - } - self.sql(psql, override, callback); - }, true); // use read-only transaction - return null; - } - - this._buildQuery(psql, override, callback); } - isDateHistogram (override) { - return this._columnType === 'date' && (this.aggregation !== undefined || override.aggregation !== undefined); - } - - _buildQuery (psql, override, callback) { - - if (this.isDateHistogram(override)) { - return this._buildDateHistogramQuery(psql, override, callback); - } - - const histogramSql = this.buildNumericHistogramQueryTpl({ - _override: override, - _column: this._columnType === 'date' ? columnCastTpl({ column: this.column }) : this.column, - _isFloatColumn: this._columnType === 'float', - _query: this.query, - _start: this.getBinStart(override), - _end: this.getBinEnd(override), - _minBins: BIN_MIN_NUMBER, - _maxBins: BIN_MAX_NUMBER, - }); - - debug(histogramSql); - - return callback(null, histogramSql); - } - - buildNumericHistogramQueryTpl (ctx) { - return ` - WITH - ${filteredQueryTpl(ctx)}, - ${this._shouldOverride(ctx._override) ? overrideBasicsQueryTpl(ctx) : basicsQueryTpl(ctx)}, - ${this._shouldOverride(ctx._override) || this._shouldOverrideBins(ctx._override) ? - overrideBinsQueryTpl(ctx) : - `${iqrQueryTpl(ctx)}, ${binsQueryTpl(ctx)}` - }, - ${nullsQueryTpl(ctx)} - ${ctx._isFloatColumn ? `,${infinitiesQueryTpl(ctx)}, ${nansQueryTpl(ctx)}` : ''} - ${histogramQueryTpl(ctx)} - `; - } - - _shouldOverride (override) { - return override && - override.hasOwnProperty('start') && - override.hasOwnProperty('end') && - override.hasOwnProperty('bins'); - } - - _shouldOverrideBins (override) { - return override && override.hasOwnProperty('bins'); - } - - buildDateHistogramQueryTpl (ctx) { - return ` - WITH - ${ctx._override && ctx._override.hasOwnProperty('start') && ctx._override.hasOwnProperty('end') ? - dateOverrideBasicsQueryTpl(ctx) : - dateBasicsQueryTpl(ctx)}, - ${dateBinsQueryTpl(ctx)}, - ${nullsQueryTpl(ctx)} - ${dateHistogramQueryTpl(ctx)} - `; - } - - _buildDateHistogramQuery (psql, override, callback) { - var _aggregation = override && override.aggregation ? override.aggregation : this.aggregation; - var _offset = override && Number.isFinite(override.offset) ? override.offset : this.offset; - - if (!DATE_AGGREGATIONS.hasOwnProperty(_aggregation)) { - return callback(new Error('Invalid aggregation value. Valid ones: ' + - Object.keys(DATE_AGGREGATIONS).join(', ') - )); - } - - if (_aggregation === 'auto') { - this.getAutomaticAggregation(psql, function (err, aggregation) { - if (err || aggregation === 'none') { - this.aggregation = 'day'; - } else { - this.aggregation = aggregation; - } - override.aggregation = this.aggregation; - this._buildDateHistogramQuery(psql, override, callback); - }.bind(this)); - return null; - } - - const histogramSql = this.buildDateHistogramQueryTpl({ - _override: override, - _query: this.query, - _column: this.column, - _aggregation: _aggregation, - _start: this.getBinStart(override), - _end: this.getBinEnd(override), - _offset: this.parseOffset(_offset, _aggregation) - }); - - debug(histogramSql); - - return callback(null, histogramSql); - } - - getAutomaticAggregation (psql, callback) { - var dateIntervalQuery = dateIntervalQueryTpl({ - query: this.query, - column: this.column - }); - - debug(dateIntervalQuery); - - psql.query(dateIntervalQuery, function (err, result) { - if (err) { - return callback(err); - } - - var aggegations = result.rows[0]; - var aggregation = Object.keys(aggegations) - .map(function (key) { - return { - name: key, - value: aggegations[key] - }; - }) - .reduce(function (closer, current) { - if (current.value > MAX_INTERVAL_VALUE) { - return closer; - } - - var closerDiff = MAX_INTERVAL_VALUE - closer.value; - var currentDiff = MAX_INTERVAL_VALUE - current.value; - - if (Number.isFinite(current.value) && closerDiff > currentDiff) { - return current; - } - - return closer; - }, { name: 'none', value: -1 }); - - callback(null, aggregation.name); - }); - } - - format (result, override) { - override = override || {}; - var buckets = []; - - var binsCount = this.getBinsCount(override); - var width = this.getWidth(override); - var binsStart = this.getBinStart(override); - var nulls = 0; - var infinities = 0; - var nans = 0; - var avg; - var timestampStart; - var aggregation; - var offset; - - if (result.rows.length) { - var firstRow = result.rows[0]; - binsCount = firstRow.bins_number; - width = firstRow.bin_width || width; - avg = firstRow.avg_val; - nulls = firstRow.nulls_count; - timestampStart = firstRow.timestamp_start; - infinities = firstRow.infinities_count; - nans = firstRow.nans_count; - binsStart = this.populateBinStart(override, firstRow); - - if (Number.isFinite(timestampStart)) { - aggregation = this.getAggregation(override); - offset = this.getOffset(override); - } - - // TODO: after applying strategy we could use specialized .format() - // in order to know what props we want to expose using destructuring assignment - - const blacklisted = [ - 'bins_number', - 'bin_width', - 'nulls_count', - 'infinities_count', - 'nans_count', - 'avg_val', - 'timestamp_start' - ]; - - buckets = result.rows.map(row => Object.keys(row) - .filter((key) => blacklisted.indexOf(key) < 0) - .reduce((newObj, key) => Object.assign(newObj, { [key]: row[key] }), {}) - ); - } - - return { - aggregation: aggregation, - offset: offset, - timestamp_start: timestampStart, - bin_width: width, - bins_count: binsCount, - bins_start: binsStart, - nulls: nulls, - infinities: infinities, - nans: nans, - avg: avg, - bins: buckets - }; - } - - getType () { - return TYPE; - } - - toString () { - return JSON.stringify({ - _type: TYPE, - _column: this.column, - _query: this.query - }); - } - - getAggregation (override) { - return override && override.aggregation ? override.aggregation : this.aggregation; - } - - getOffset (override) { - if (override && override.offset) { - return override.offset; - } - if (this.offset) { - return this.offset; - } - - return 0; - } - - getBinStart (override) { - if (override.hasOwnProperty('start') && override.hasOwnProperty('end')) { - return Math.min(override.start, override.end); - } - return override.start || 0; - } - - populateBinStart (override, firstRow) { - var binStart; - - if (firstRow.hasOwnProperty('timestamp')) { - binStart = firstRow.timestamp; - } else if (override.hasOwnProperty('start')) { - binStart = this.getBinStart(override); - } else { - binStart = firstRow.min; - } - - return binStart; - } - - getBinEnd (override) { - if (override.hasOwnProperty('start') && override.hasOwnProperty('end')) { - return Math.max(override.start, override.end); - } - return override.end || 0; - } - - getBinsCount (override) { - return override.bins || 0; - } - - getWidth (override) { - var width = 0; - var binsCount = override.bins; - - if (binsCount && Number.isFinite(override.start) && Number.isFinite(override.end)) { - width = (override.end - override.start) / binsCount; - } - - return width; - } - - parseOffset (offset, aggregation) { - if (!offset) { - return '0'; - } - if (aggregation === 'hour' || aggregation === 'minute') { - return '0'; - } - - var offsetInHours = Math.ceil(offset / 3600); - return '' + offsetInHours; + getResult (psql, override, callback) { + this.dataview.getResult(psql, override, callback); } }; From f75b4312a16ca0c566b4d1585f2a54cd32f07f93 Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 8 Sep 2017 15:49:25 +0200 Subject: [PATCH 062/159] Fix undefined while destructuring assignment --- lib/cartodb/models/dataview/numeric-histogram.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/cartodb/models/dataview/numeric-histogram.js b/lib/cartodb/models/dataview/numeric-histogram.js index ea0beb9a..5cf6f96e 100644 --- a/lib/cartodb/models/dataview/numeric-histogram.js +++ b/lib/cartodb/models/dataview/numeric-histogram.js @@ -270,7 +270,10 @@ module.exports = class NumericHistogram extends BaseDataview { nans = firstRow.nans_count; binsStart = this._populateBinStart(override, firstRow); - buckets = result.rows.map(row => ({ bin, min, max, avg, freq } = row)); + buckets = result.rows.map(row => { + const { bin, min, max, avg, freq } = row; + return { bin, min, max, avg, freq }; + }); } return { From 1d4935cc9a4992ae2a1a6e79b535915e28ccaa32 Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 8 Sep 2017 15:50:01 +0200 Subject: [PATCH 063/159] Fix undefined while destrutcuring assignment --- lib/cartodb/models/dataview/date-histogram.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/cartodb/models/dataview/date-histogram.js b/lib/cartodb/models/dataview/date-histogram.js index 63b4115e..aee5562e 100644 --- a/lib/cartodb/models/dataview/date-histogram.js +++ b/lib/cartodb/models/dataview/date-histogram.js @@ -339,7 +339,10 @@ module.exports = class DateHistogram extends BaseDataview { offset = this._getOffset(override); } - buckets = result.rows.map(row => ({ bin, min, max, avg, freq, timestamp } = row)); + buckets = result.rows.map(row => { + const { bin, min, max, avg, freq, timestamp } = row; + return { bin, min, max, avg, freq, timestamp }; + }); } return { From 29f32cb9cc7d09e945e7bb15b8026896aab0a38c Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 8 Sep 2017 15:53:00 +0200 Subject: [PATCH 064/159] Expose dataview's methods to bypass concrete overview's implementations --- lib/cartodb/models/dataview/histogram.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/lib/cartodb/models/dataview/histogram.js b/lib/cartodb/models/dataview/histogram.js index a8cd12ed..2261b73a 100644 --- a/lib/cartodb/models/dataview/histogram.js +++ b/lib/cartodb/models/dataview/histogram.js @@ -34,4 +34,24 @@ module.exports = class Histogram { getResult (psql, override, callback) { this.dataview.getResult(psql, override, callback); } + + // In order to keep previous behaviour with overviews, + // we have to expose the following methods to bypass + // the concrete overview implementation + + sql (psql, override, callback) { + this.dataview.sql(psql, override, callback); + } + + format (result, override) { + return this.dataview.format(result, override); + } + + getType () { + return this.dataview.getType(); + } + + toString () { + return this.dataview.toString(); + } }; From 5593d92c4bcedf5fedc11e300cef6ea9c05855d0 Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 8 Sep 2017 15:55:23 +0200 Subject: [PATCH 065/159] Do not choose histogram implementation until getResult() --- lib/cartodb/models/dataview/histogram.js | 37 ++++++++++++++---------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/lib/cartodb/models/dataview/histogram.js b/lib/cartodb/models/dataview/histogram.js index 2261b73a..461f0528 100644 --- a/lib/cartodb/models/dataview/histogram.js +++ b/lib/cartodb/models/dataview/histogram.js @@ -7,31 +7,38 @@ const DATE_HISTOGRAM = 'DateHistogram'; const NUMERIC_HISTOGRAM = 'NumericHistogram'; module.exports = class Histogram { - constructor (query, options, queries) { - switch (this._getHistogramSubtype(options)) { + constructor (query, options = {}, queries) { + this.query = query; + this.options = options; + this.queries = queries; + + this.dataview = this._getHistogramImplemetation(); + } + + _getHistogramImplemetation (override) { + switch (this._getHistogramSubtype(override)) { case DATE_HISTOGRAM: - debug('Delegating to DateHistogram with options: %j', options) - this.dataview = new DateHistogram(query, options, queries); + debug('Delegating to DateHistogram with options: %j and overriding: %j', this.options, override) + return new DateHistogram(this.query, this.options, this.queries); break; case NUMERIC_HISTOGRAM: - debug('Delegating to NumericHistogram with options: %j', options) - this.dataview = new NumericHistogram(query, options, queries); + debug('Delegating to NumericHistogram with options: %j and overriding: %j', this.options, override) + return new NumericHistogram(this.query, this.options, this.queries); break; - default: throw new Error('Unsupported Histogram type'); } - } - - _getHistogramSubtype (options) { - if(options.bins && !options.aggregation) { - return NUMERIC_HISTOGRAM - } else if(options.aggregation && !options.bins) { - return DATE_HISTOGRAM + } + + _getHistogramSubtype (override = {}) { + if(this.options.aggregation !== undefined || override.aggregation !== undefined) { + return DATE_HISTOGRAM; } + return NUMERIC_HISTOGRAM; } - + getResult (psql, override, callback) { + this.dataview = this._getHistogramImplemetation(override); this.dataview.getResult(psql, override, callback); } From 5e07cc2ad1ae9fed6a4943a296e3d5685d79c8ff Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 8 Sep 2017 16:06:54 +0200 Subject: [PATCH 066/159] Remove unneeded condittion --- lib/cartodb/models/dataview/date-histogram.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/cartodb/models/dataview/date-histogram.js b/lib/cartodb/models/dataview/date-histogram.js index aee5562e..a38bd792 100644 --- a/lib/cartodb/models/dataview/date-histogram.js +++ b/lib/cartodb/models/dataview/date-histogram.js @@ -334,10 +334,8 @@ module.exports = class DateHistogram extends BaseDataview { nans = firstRow.nans_count; binsStart = this._populateBinStart(override, firstRow); - if (Number.isFinite(timestampStart)) { - aggregation = this._getAggregation(override); - offset = this._getOffset(override); - } + aggregation = this._getAggregation(override); + offset = this._getOffset(override); buckets = result.rows.map(row => { const { bin, min, max, avg, freq, timestamp } = row; From 92f6f59e079a16d59a08e9e2e87ba5474e396bb0 Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 8 Sep 2017 16:09:57 +0200 Subject: [PATCH 067/159] Fix jshint style errors --- lib/cartodb/models/dataview/histogram.js | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/lib/cartodb/models/dataview/histogram.js b/lib/cartodb/models/dataview/histogram.js index 461f0528..e4179df6 100644 --- a/lib/cartodb/models/dataview/histogram.js +++ b/lib/cartodb/models/dataview/histogram.js @@ -2,32 +2,35 @@ const debug = require('debug')('windshaft:dataview:histogram'); const NumericHistogram = require('./numeric-histogram'); const DateHistogram = require('./date-histogram'); -const TYPE = 'histogram'; const DATE_HISTOGRAM = 'DateHistogram'; const NUMERIC_HISTOGRAM = 'NumericHistogram'; module.exports = class Histogram { - constructor (query, options = {}, queries) { + constructor (query, options, queries) { this.query = query; - this.options = options; + this.options = options ||{}; this.queries = queries; this.dataview = this._getHistogramImplemetation(); } _getHistogramImplemetation (override) { + let implementation = null; + switch (this._getHistogramSubtype(override)) { case DATE_HISTOGRAM: - debug('Delegating to DateHistogram with options: %j and overriding: %j', this.options, override) - return new DateHistogram(this.query, this.options, this.queries); + debug('Delegating to DateHistogram with options: %j and overriding: %j', this.options, override); + implementation = new DateHistogram(this.query, this.options, this.queries); break; case NUMERIC_HISTOGRAM: - debug('Delegating to NumericHistogram with options: %j and overriding: %j', this.options, override) - return new NumericHistogram(this.query, this.options, this.queries); + debug('Delegating to NumericHistogram with options: %j and overriding: %j', this.options, override); + implementation = new NumericHistogram(this.query, this.options, this.queries); break; default: throw new Error('Unsupported Histogram type'); } + + return implementation; } _getHistogramSubtype (override = {}) { From 25ef2610aa2e0266562621a4b5df122fc01d5cb3 Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 8 Sep 2017 17:43:10 +0200 Subject: [PATCH 068/159] Varible declarations to let/const --- lib/cartodb/models/dataview/date-histogram.js | 47 +++++++++---------- .../models/dataview/numeric-histogram.js | 29 +++++------- 2 files changed, 34 insertions(+), 42 deletions(-) diff --git a/lib/cartodb/models/dataview/date-histogram.js b/lib/cartodb/models/dataview/date-histogram.js index a38bd792..0e11befe 100644 --- a/lib/cartodb/models/dataview/date-histogram.js +++ b/lib/cartodb/models/dataview/date-histogram.js @@ -1,5 +1,4 @@ const BaseDataview = require('./base'); -const debug = require('debug')('windshaft:dataview:date-histogram'); const dateIntervalQueryTpl = ctx => ` WITH @@ -187,7 +186,7 @@ module.exports = class DateHistogram extends BaseDataview { } sql (psql, override, callback) { - var self = this; + let self = this; if (!callback) { callback = override; @@ -231,8 +230,8 @@ module.exports = class DateHistogram extends BaseDataview { } _buildQuery (psql, override, callback) { - var _aggregation = override && override.aggregation ? override.aggregation : this.aggregation; - var _offset = override && Number.isFinite(override.offset) ? override.offset : this.offset; + const _aggregation = override && override.aggregation ? override.aggregation : this.aggregation; + const _offset = override && Number.isFinite(override.offset) ? override.offset : this.offset; if (!DATE_AGGREGATIONS.hasOwnProperty(_aggregation)) { return callback(new Error('Invalid aggregation value. Valid ones: ' + @@ -263,26 +262,22 @@ module.exports = class DateHistogram extends BaseDataview { _offset: this._parseOffset(_offset, _aggregation) }); - debug(histogramSql); - return callback(null, histogramSql); } _getAutomaticAggregation (psql, callback) { - var dateIntervalQuery = dateIntervalQueryTpl({ + const dateIntervalQuery = dateIntervalQueryTpl({ query: this.query, column: this.column }); - debug(dateIntervalQuery); - psql.query(dateIntervalQuery, function (err, result) { if (err) { return callback(err); } - var aggegations = result.rows[0]; - var aggregation = Object.keys(aggegations) + const aggegations = result.rows[0]; + const aggregation = Object.keys(aggegations) .map(function (key) { return { name: key, @@ -310,21 +305,21 @@ module.exports = class DateHistogram extends BaseDataview { format (result, override) { override = override || {}; - var buckets = []; + let buckets = []; - var binsCount = this._getBinsCount(override); - var width = this._getWidth(override); - var binsStart = this._getBinStart(override); - var nulls = 0; - var infinities = 0; - var nans = 0; - var avg; - var timestampStart; - var aggregation; - var offset; + let binsCount = this._getBinsCount(override); + let width = this._getWidth(override); + let binsStart = this._getBinStart(override); + let nulls = 0; + let infinities = 0; + let nans = 0; + let avg; + let timestampStart; + let aggregation; + let offset; if (result.rows.length) { - var firstRow = result.rows[0]; + const firstRow = result.rows[0]; binsCount = firstRow.bins_number; width = firstRow.bin_width || width; avg = firstRow.avg_val; @@ -418,8 +413,8 @@ module.exports = class DateHistogram extends BaseDataview { } _getWidth (override) { - var width = 0; - var binsCount = override.bins; + let width = 0; + const binsCount = override.bins; if (binsCount && Number.isFinite(override.start) && Number.isFinite(override.end)) { width = (override.end - override.start) / binsCount; @@ -436,7 +431,7 @@ module.exports = class DateHistogram extends BaseDataview { return '0'; } - var offsetInHours = Math.ceil(offset / 3600); + const offsetInHours = Math.ceil(offset / 3600); return '' + offsetInHours; } }; diff --git a/lib/cartodb/models/dataview/numeric-histogram.js b/lib/cartodb/models/dataview/numeric-histogram.js index 5cf6f96e..891de107 100644 --- a/lib/cartodb/models/dataview/numeric-histogram.js +++ b/lib/cartodb/models/dataview/numeric-histogram.js @@ -1,5 +1,4 @@ const BaseDataview = require('./base'); -const debug = require('debug')('windshaft:dataview:numeric-histogram'); const columnCastTpl = ctx => `date_part('epoch', ${ctx.column})`; @@ -179,7 +178,7 @@ module.exports = class NumericHistogram extends BaseDataview { } sql (psql, override, callback) { - var self = this; + let self = this; if (!callback) { callback = override; @@ -216,8 +215,6 @@ module.exports = class NumericHistogram extends BaseDataview { _maxBins: BIN_MAX_NUMBER, }); - debug(histogramSql); - return callback(null, histogramSql); } @@ -250,18 +247,18 @@ module.exports = class NumericHistogram extends BaseDataview { format (result, override) { override = override || {}; - var buckets = []; + let buckets = []; - var binsCount = this._getBinsCount(override); - var width = this._getWidth(override); - var binsStart = this._getBinStart(override); - var nulls = 0; - var infinities = 0; - var nans = 0; - var avg; + let binsCount = this._getBinsCount(override); + let width = this._getWidth(override); + let binsStart = this._getBinStart(override); + let nulls = 0; + let infinities = 0; + let nans = 0; + let avg; if (result.rows.length) { - var firstRow = result.rows[0]; + const firstRow = result.rows[0]; binsCount = firstRow.bins_number; width = firstRow.bin_width || width; avg = firstRow.avg_val; @@ -308,7 +305,7 @@ module.exports = class NumericHistogram extends BaseDataview { } _populateBinStart (override, firstRow) { - var binStart; + let binStart; if (override.hasOwnProperty('start')) { binStart = this._getBinStart(override); @@ -331,8 +328,8 @@ module.exports = class NumericHistogram extends BaseDataview { } _getWidth (override) { - var width = 0; - var binsCount = override.bins; + let width = 0; + const binsCount = override.bins; if (binsCount && Number.isFinite(override.start) && Number.isFinite(override.end)) { width = (override.end - override.start) / binsCount; From 2650c3b3e6a67aeee7a38b16b161780997136a1e Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 8 Sep 2017 17:51:02 +0200 Subject: [PATCH 069/159] removing self=this assignment --- lib/cartodb/models/dataview/date-histogram.js | 10 ++++------ lib/cartodb/models/dataview/numeric-histogram.js | 10 ++++------ 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/lib/cartodb/models/dataview/date-histogram.js b/lib/cartodb/models/dataview/date-histogram.js index 0e11befe..8315cd81 100644 --- a/lib/cartodb/models/dataview/date-histogram.js +++ b/lib/cartodb/models/dataview/date-histogram.js @@ -186,23 +186,21 @@ module.exports = class DateHistogram extends BaseDataview { } sql (psql, override, callback) { - let self = this; - if (!callback) { callback = override; override = {}; } if (this._columnType === null) { - this.getColumnType(psql, this.column, this.queries.no_filters, function (err, type) { + this.getColumnType(psql, this.column, this.queries.no_filters, (err, type) => { // assume numeric, will fail later - self._columnType = 'numeric'; + this._columnType = 'numeric'; if (!err && !!type) { - self._columnType = Object.keys(type).find(function (key) { + this._columnType = Object.keys(type).find(function (key) { return type[key]; }); } - self.sql(psql, override, callback); + this.sql(psql, override, callback); }, true); // use read-only transaction return null; } diff --git a/lib/cartodb/models/dataview/numeric-histogram.js b/lib/cartodb/models/dataview/numeric-histogram.js index 891de107..4968bd58 100644 --- a/lib/cartodb/models/dataview/numeric-histogram.js +++ b/lib/cartodb/models/dataview/numeric-histogram.js @@ -178,23 +178,21 @@ module.exports = class NumericHistogram extends BaseDataview { } sql (psql, override, callback) { - let self = this; - if (!callback) { callback = override; override = {}; } if (this._columnType === null) { - this.getColumnType(psql, this.column, this.queries.no_filters, function (err, type) { + this.getColumnType(psql, this.column, this.queries.no_filters, (err, type) => { // assume numeric, will fail later - self._columnType = 'numeric'; + this._columnType = 'numeric'; if (!err && !!type) { - self._columnType = Object.keys(type).find(function (key) { + this._columnType = Object.keys(type).find(function (key) { return type[key]; }); } - self.sql(psql, override, callback); + this.sql(psql, override, callback); }, true); // use read-only transaction return null; } From a20900210d431688a84955e77cf3226de969eb9c Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 8 Sep 2017 17:54:25 +0200 Subject: [PATCH 070/159] removing unneeded _isDateHistogram function --- lib/cartodb/models/dataview/date-histogram.js | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/lib/cartodb/models/dataview/date-histogram.js b/lib/cartodb/models/dataview/date-histogram.js index 8315cd81..a0ba927c 100644 --- a/lib/cartodb/models/dataview/date-histogram.js +++ b/lib/cartodb/models/dataview/date-histogram.js @@ -205,14 +205,7 @@ module.exports = class DateHistogram extends BaseDataview { return null; } - if (this._isDateHistogram(override)) { - return this._buildQuery(psql, override, callback); - } - - } - - _isDateHistogram (override) { - return this._columnType === 'date' && (this.aggregation !== undefined || override.aggregation !== undefined); + return this._buildQuery(psql, override, callback); } _buildQueryTpl (ctx) { From e4a20fa9545634da597adfd26edd908ec0c163b4 Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 8 Sep 2017 17:57:35 +0200 Subject: [PATCH 071/159] adding forgotten return --- lib/cartodb/models/dataview/numeric-histogram.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cartodb/models/dataview/numeric-histogram.js b/lib/cartodb/models/dataview/numeric-histogram.js index 4968bd58..da6792dc 100644 --- a/lib/cartodb/models/dataview/numeric-histogram.js +++ b/lib/cartodb/models/dataview/numeric-histogram.js @@ -197,7 +197,7 @@ module.exports = class NumericHistogram extends BaseDataview { return null; } - this._buildQuery(psql, override, callback); + return this._buildQuery(psql, override, callback); } From 5e9b2e45c7a1698e0fa617f2c9d9fb6bf825dbe7 Mon Sep 17 00:00:00 2001 From: Simon Date: Mon, 11 Sep 2017 11:30:09 +0200 Subject: [PATCH 072/159] creating HistogramBase with the common functions of NumericHistogram and DateHistogram --- lib/cartodb/models/dataview/histogram.js | 4 +- .../{ => histograms}/date-histogram.js | 77 +------------------ .../dataview/histograms/histogramBase.js | 77 +++++++++++++++++++ .../{ => histograms}/numeric-histogram.js | 76 +----------------- 4 files changed, 86 insertions(+), 148 deletions(-) rename lib/cartodb/models/dataview/{ => histograms}/date-histogram.js (85%) create mode 100644 lib/cartodb/models/dataview/histograms/histogramBase.js rename lib/cartodb/models/dataview/{ => histograms}/numeric-histogram.js (79%) diff --git a/lib/cartodb/models/dataview/histogram.js b/lib/cartodb/models/dataview/histogram.js index e4179df6..7e0f71bf 100644 --- a/lib/cartodb/models/dataview/histogram.js +++ b/lib/cartodb/models/dataview/histogram.js @@ -1,6 +1,6 @@ const debug = require('debug')('windshaft:dataview:histogram'); -const NumericHistogram = require('./numeric-histogram'); -const DateHistogram = require('./date-histogram'); +const NumericHistogram = require('./histograms/numeric-histogram'); +const DateHistogram = require('./histograms/date-histogram'); const DATE_HISTOGRAM = 'DateHistogram'; const NUMERIC_HISTOGRAM = 'NumericHistogram'; diff --git a/lib/cartodb/models/dataview/date-histogram.js b/lib/cartodb/models/dataview/histograms/date-histogram.js similarity index 85% rename from lib/cartodb/models/dataview/date-histogram.js rename to lib/cartodb/models/dataview/histograms/date-histogram.js index a0ba927c..064de0ae 100644 --- a/lib/cartodb/models/dataview/date-histogram.js +++ b/lib/cartodb/models/dataview/histograms/date-histogram.js @@ -1,4 +1,4 @@ -const BaseDataview = require('./base'); +const HistogramBase = require('./histogramBase'); const dateIntervalQueryTpl = ctx => ` WITH @@ -144,8 +144,6 @@ const dateHistogramQueryTpl = ctx => ` const MAX_INTERVAL_VALUE = 366; -const TYPE = 'histogram'; - const DATE_AGGREGATIONS = { 'auto': true, 'minute': true, @@ -167,45 +165,12 @@ const DATE_AGGREGATIONS = { } } */ -module.exports = class DateHistogram extends BaseDataview { +module.exports = class DateHistogram extends HistogramBase { constructor (query, options, queries) { - super(); - - if (typeof options.column !== 'string') { - throw new Error('Histogram expects `column` in widget options'); - } - - this.query = query; - this.queries = queries; - this.column = options.column; - this.bins = options.bins; + super(query, options, queries); + this.aggregation = options.aggregation; this.offset = options.offset; - - this._columnType = null; - } - - sql (psql, override, callback) { - if (!callback) { - callback = override; - override = {}; - } - - if (this._columnType === null) { - this.getColumnType(psql, this.column, this.queries.no_filters, (err, type) => { - // assume numeric, will fail later - this._columnType = 'numeric'; - if (!err && !!type) { - this._columnType = Object.keys(type).find(function (key) { - return type[key]; - }); - } - this.sql(psql, override, callback); - }, true); // use read-only transaction - return null; - } - - return this._buildQuery(psql, override, callback); } _buildQueryTpl (ctx) { @@ -344,18 +309,6 @@ module.exports = class DateHistogram extends BaseDataview { }; } - getType () { - return TYPE; - } - - toString () { - return JSON.stringify({ - _type: TYPE, - _column: this.column, - _query: this.query - }); - } - _getAggregation (override) { return override && override.aggregation ? override.aggregation : this.aggregation; } @@ -392,28 +345,6 @@ module.exports = class DateHistogram extends BaseDataview { return binStart; } - _getBinEnd (override) { - if (override.hasOwnProperty('start') && override.hasOwnProperty('end')) { - return Math.max(override.start, override.end); - } - return override.end || 0; - } - - _getBinsCount (override) { - return override.bins || 0; - } - - _getWidth (override) { - let width = 0; - const binsCount = override.bins; - - if (binsCount && Number.isFinite(override.start) && Number.isFinite(override.end)) { - width = (override.end - override.start) / binsCount; - } - - return width; - } - _parseOffset (offset, aggregation) { if (!offset) { return '0'; diff --git a/lib/cartodb/models/dataview/histograms/histogramBase.js b/lib/cartodb/models/dataview/histograms/histogramBase.js new file mode 100644 index 00000000..dc6c40d6 --- /dev/null +++ b/lib/cartodb/models/dataview/histograms/histogramBase.js @@ -0,0 +1,77 @@ +const BaseDataview = require('../base'); + +const TYPE = 'histogram'; + +module.exports = class HistogramBase extends BaseDataview { + constructor (query, options, queries) { + super(); + + if (typeof options.column !== 'string') { + throw new Error('Histogram expects `column` in widget options'); + } + + this.query = query; + this.queries = queries; + this.column = options.column; + this.bins = options.bins; + + this._columnType = null; + } + + sql (psql, override, callback) { + if (!callback) { + callback = override; + override = {}; + } + + if (this._columnType === null) { + this.getColumnType(psql, this.column, this.queries.no_filters, (err, type) => { + // assume numeric, will fail later + this._columnType = 'numeric'; + if (!err && !!type) { + this._columnType = Object.keys(type).find(function (key) { + return type[key]; + }); + } + this.sql(psql, override, callback); + }, true); // use read-only transaction + return null; + } + + return this._buildQuery(psql, override, callback); + } + + getType () { + return TYPE; + } + + toString () { + return JSON.stringify({ + _type: TYPE, + _column: this.column, + _query: this.query + }); + } + + _getBinEnd (override) { + if (override.hasOwnProperty('start') && override.hasOwnProperty('end')) { + return Math.max(override.start, override.end); + } + return override.end || 0; + } + + _getBinsCount (override) { + return override.bins || 0; + } + + _getWidth (override) { + let width = 0; + const binsCount = override.bins; + + if (binsCount && Number.isFinite(override.start) && Number.isFinite(override.end)) { + width = (override.end - override.start) / binsCount; + } + + return width; + } +}; \ No newline at end of file diff --git a/lib/cartodb/models/dataview/numeric-histogram.js b/lib/cartodb/models/dataview/histograms/numeric-histogram.js similarity index 79% rename from lib/cartodb/models/dataview/numeric-histogram.js rename to lib/cartodb/models/dataview/histograms/numeric-histogram.js index da6792dc..2c6ac8e6 100644 --- a/lib/cartodb/models/dataview/numeric-histogram.js +++ b/lib/cartodb/models/dataview/histograms/numeric-histogram.js @@ -1,4 +1,4 @@ -const BaseDataview = require('./base'); +const HistogramBase = require('./histogramBase'); const columnCastTpl = ctx => `date_part('epoch', ${ctx.column})`; @@ -149,8 +149,6 @@ const histogramQueryTpl = ctx => ` const BIN_MIN_NUMBER = 6; const BIN_MAX_NUMBER = 48; -const TYPE = 'histogram'; - /** Numeric histogram: { @@ -161,46 +159,11 @@ Numeric histogram: } } */ -module.exports = class NumericHistogram extends BaseDataview { +module.exports = class NumericHistogram extends HistogramBase { constructor (query, options, queries) { - super(); - - if (typeof options.column !== 'string') { - throw new Error('Histogram expects `column` in widget options'); - } - - this.query = query; - this.queries = queries; - this.column = options.column; - this.bins = options.bins; - - this._columnType = null; + super(query, options, queries); } - sql (psql, override, callback) { - if (!callback) { - callback = override; - override = {}; - } - - if (this._columnType === null) { - this.getColumnType(psql, this.column, this.queries.no_filters, (err, type) => { - // assume numeric, will fail later - this._columnType = 'numeric'; - if (!err && !!type) { - this._columnType = Object.keys(type).find(function (key) { - return type[key]; - }); - } - this.sql(psql, override, callback); - }, true); // use read-only transaction - return null; - } - - return this._buildQuery(psql, override, callback); - } - - _buildQuery (psql, override, callback) { const histogramSql = this._buildQueryTpl({ _override: override, @@ -283,18 +246,6 @@ module.exports = class NumericHistogram extends BaseDataview { }; } - getType () { - return TYPE; - } - - toString () { - return JSON.stringify({ - _type: TYPE, - _column: this.column, - _query: this.query - }); - } - _getBinStart (override) { if (override.hasOwnProperty('start') && override.hasOwnProperty('end')) { return Math.min(override.start, override.end); @@ -314,25 +265,4 @@ module.exports = class NumericHistogram extends BaseDataview { return binStart; } - _getBinEnd (override) { - if (override.hasOwnProperty('start') && override.hasOwnProperty('end')) { - return Math.max(override.start, override.end); - } - return override.end || 0; - } - - _getBinsCount (override) { - return override.bins || 0; - } - - _getWidth (override) { - let width = 0; - const binsCount = override.bins; - - if (binsCount && Number.isFinite(override.start) && Number.isFinite(override.end)) { - width = (override.end - override.start) / binsCount; - } - - return width; - } }; From d75ee965aeaba2fa1f1ff04d2b7e1c81a9979a9d Mon Sep 17 00:00:00 2001 From: Simon Date: Mon, 11 Sep 2017 11:48:33 +0200 Subject: [PATCH 073/159] changing some 'var' to 'let/const' --- lib/cartodb/models/dataview/histograms/date-histogram.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/cartodb/models/dataview/histograms/date-histogram.js b/lib/cartodb/models/dataview/histograms/date-histogram.js index 064de0ae..6060dbac 100644 --- a/lib/cartodb/models/dataview/histograms/date-histogram.js +++ b/lib/cartodb/models/dataview/histograms/date-histogram.js @@ -245,8 +245,8 @@ module.exports = class DateHistogram extends HistogramBase { return closer; } - var closerDiff = MAX_INTERVAL_VALUE - closer.value; - var currentDiff = MAX_INTERVAL_VALUE - current.value; + const closerDiff = MAX_INTERVAL_VALUE - closer.value; + const currentDiff = MAX_INTERVAL_VALUE - current.value; if (Number.isFinite(current.value) && closerDiff > currentDiff) { return current; @@ -332,7 +332,7 @@ module.exports = class DateHistogram extends HistogramBase { } _populateBinStart (override, firstRow) { - var binStart; + let binStart; if (firstRow.hasOwnProperty('timestamp')) { binStart = firstRow.timestamp; From 6c3fa045cd39a90f8a9d99ac3d37341bd5c62421 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Mon, 11 Sep 2017 13:53:05 +0200 Subject: [PATCH 074/159] Rename HistogramBase by BaseHistogram --- .../dataview/histograms/base-histogram.js | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 lib/cartodb/models/dataview/histograms/base-histogram.js diff --git a/lib/cartodb/models/dataview/histograms/base-histogram.js b/lib/cartodb/models/dataview/histograms/base-histogram.js new file mode 100644 index 00000000..33a184d0 --- /dev/null +++ b/lib/cartodb/models/dataview/histograms/base-histogram.js @@ -0,0 +1,77 @@ +const BaseDataview = require('../base'); + +const TYPE = 'histogram'; + +module.exports = class BaseHistogram extends BaseDataview { + constructor (query, options, queries) { + super(); + + if (typeof options.column !== 'string') { + throw new Error('Histogram expects `column` in widget options'); + } + + this.query = query; + this.queries = queries; + this.column = options.column; + this.bins = options.bins; + + this._columnType = null; + } + + sql (psql, override, callback) { + if (!callback) { + callback = override; + override = {}; + } + + if (this._columnType === null) { + this.getColumnType(psql, this.column, this.queries.no_filters, (err, type) => { + // assume numeric, will fail later + this._columnType = 'numeric'; + if (!err && !!type) { + this._columnType = Object.keys(type).find(function (key) { + return type[key]; + }); + } + this.sql(psql, override, callback); + }, true); // use read-only transaction + return null; + } + + return this._buildQuery(psql, override, callback); + } + + getType () { + return TYPE; + } + + toString () { + return JSON.stringify({ + _type: TYPE, + _column: this.column, + _query: this.query + }); + } + + _getBinEnd (override) { + if (override.hasOwnProperty('start') && override.hasOwnProperty('end')) { + return Math.max(override.start, override.end); + } + return override.end || 0; + } + + _getBinsCount (override) { + return override.bins || 0; + } + + _getWidth (override) { + let width = 0; + const binsCount = override.bins; + + if (binsCount && Number.isFinite(override.start) && Number.isFinite(override.end)) { + width = (override.end - override.start) / binsCount; + } + + return width; + } +}; \ No newline at end of file From ec23bfc79b29e7b1244d4be0b57eace3b12d7e7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Mon, 11 Sep 2017 13:54:46 +0200 Subject: [PATCH 075/159] Rename HistogramBase by BaseHistogram --- lib/cartodb/models/dataview/histogram.js | 14 ++-- .../dataview/histograms/date-histogram.js | 6 +- .../dataview/histograms/histogramBase.js | 77 ------------------- .../dataview/histograms/numeric-histogram.js | 4 +- 4 files changed, 12 insertions(+), 89 deletions(-) delete mode 100644 lib/cartodb/models/dataview/histograms/histogramBase.js diff --git a/lib/cartodb/models/dataview/histogram.js b/lib/cartodb/models/dataview/histogram.js index 7e0f71bf..6a59f021 100644 --- a/lib/cartodb/models/dataview/histogram.js +++ b/lib/cartodb/models/dataview/histogram.js @@ -8,13 +8,13 @@ const NUMERIC_HISTOGRAM = 'NumericHistogram'; module.exports = class Histogram { constructor (query, options, queries) { this.query = query; - this.options = options ||{}; + this.options = options || {}; this.queries = queries; - this.dataview = this._getHistogramImplemetation(); + this.dataview = this._getHistogramImplementation(); } - _getHistogramImplemetation (override) { + _getHistogramImplementation (override) { let implementation = null; switch (this._getHistogramSubtype(override)) { @@ -31,17 +31,17 @@ module.exports = class Histogram { } return implementation; - } - + } + _getHistogramSubtype (override = {}) { if(this.options.aggregation !== undefined || override.aggregation !== undefined) { return DATE_HISTOGRAM; } return NUMERIC_HISTOGRAM; } - + getResult (psql, override, callback) { - this.dataview = this._getHistogramImplemetation(override); + this.dataview = this._getHistogramImplementation(override); this.dataview.getResult(psql, override, callback); } diff --git a/lib/cartodb/models/dataview/histograms/date-histogram.js b/lib/cartodb/models/dataview/histograms/date-histogram.js index 6060dbac..0ee832bc 100644 --- a/lib/cartodb/models/dataview/histograms/date-histogram.js +++ b/lib/cartodb/models/dataview/histograms/date-histogram.js @@ -1,4 +1,4 @@ -const HistogramBase = require('./histogramBase'); +const BaseHistogram = require('./base-histogram'); const dateIntervalQueryTpl = ctx => ` WITH @@ -165,10 +165,10 @@ const DATE_AGGREGATIONS = { } } */ -module.exports = class DateHistogram extends HistogramBase { +module.exports = class DateHistogram extends BaseHistogram { constructor (query, options, queries) { super(query, options, queries); - + this.aggregation = options.aggregation; this.offset = options.offset; } diff --git a/lib/cartodb/models/dataview/histograms/histogramBase.js b/lib/cartodb/models/dataview/histograms/histogramBase.js deleted file mode 100644 index dc6c40d6..00000000 --- a/lib/cartodb/models/dataview/histograms/histogramBase.js +++ /dev/null @@ -1,77 +0,0 @@ -const BaseDataview = require('../base'); - -const TYPE = 'histogram'; - -module.exports = class HistogramBase extends BaseDataview { - constructor (query, options, queries) { - super(); - - if (typeof options.column !== 'string') { - throw new Error('Histogram expects `column` in widget options'); - } - - this.query = query; - this.queries = queries; - this.column = options.column; - this.bins = options.bins; - - this._columnType = null; - } - - sql (psql, override, callback) { - if (!callback) { - callback = override; - override = {}; - } - - if (this._columnType === null) { - this.getColumnType(psql, this.column, this.queries.no_filters, (err, type) => { - // assume numeric, will fail later - this._columnType = 'numeric'; - if (!err && !!type) { - this._columnType = Object.keys(type).find(function (key) { - return type[key]; - }); - } - this.sql(psql, override, callback); - }, true); // use read-only transaction - return null; - } - - return this._buildQuery(psql, override, callback); - } - - getType () { - return TYPE; - } - - toString () { - return JSON.stringify({ - _type: TYPE, - _column: this.column, - _query: this.query - }); - } - - _getBinEnd (override) { - if (override.hasOwnProperty('start') && override.hasOwnProperty('end')) { - return Math.max(override.start, override.end); - } - return override.end || 0; - } - - _getBinsCount (override) { - return override.bins || 0; - } - - _getWidth (override) { - let width = 0; - const binsCount = override.bins; - - if (binsCount && Number.isFinite(override.start) && Number.isFinite(override.end)) { - width = (override.end - override.start) / binsCount; - } - - return width; - } -}; \ No newline at end of file diff --git a/lib/cartodb/models/dataview/histograms/numeric-histogram.js b/lib/cartodb/models/dataview/histograms/numeric-histogram.js index 2c6ac8e6..ece7babc 100644 --- a/lib/cartodb/models/dataview/histograms/numeric-histogram.js +++ b/lib/cartodb/models/dataview/histograms/numeric-histogram.js @@ -1,4 +1,4 @@ -const HistogramBase = require('./histogramBase'); +const BaseHistogram = require('./base-histogram'); const columnCastTpl = ctx => `date_part('epoch', ${ctx.column})`; @@ -159,7 +159,7 @@ Numeric histogram: } } */ -module.exports = class NumericHistogram extends HistogramBase { +module.exports = class NumericHistogram extends BaseHistogram { constructor (query, options, queries) { super(query, options, queries); } From 4f33e0d794e5332eba9aa39e522b7d54baa19292 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Mon, 11 Sep 2017 15:34:42 +0200 Subject: [PATCH 076/159] Rename Histogram.dataview by Histogram.histogramImplementation --- lib/cartodb/models/dataview/histogram.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/cartodb/models/dataview/histogram.js b/lib/cartodb/models/dataview/histogram.js index 6a59f021..5099ee53 100644 --- a/lib/cartodb/models/dataview/histogram.js +++ b/lib/cartodb/models/dataview/histogram.js @@ -11,7 +11,7 @@ module.exports = class Histogram { this.options = options || {}; this.queries = queries; - this.dataview = this._getHistogramImplementation(); + this.histogramImplementation = this._getHistogramImplementation(); } _getHistogramImplementation (override) { @@ -41,8 +41,8 @@ module.exports = class Histogram { } getResult (psql, override, callback) { - this.dataview = this._getHistogramImplementation(override); - this.dataview.getResult(psql, override, callback); + this.histogramImplementation = this._getHistogramImplementation(override); + this.histogramImplementation.getResult(psql, override, callback); } // In order to keep previous behaviour with overviews, @@ -50,18 +50,18 @@ module.exports = class Histogram { // the concrete overview implementation sql (psql, override, callback) { - this.dataview.sql(psql, override, callback); + this.histogramImplementation.sql(psql, override, callback); } format (result, override) { - return this.dataview.format(result, override); + return this.histogramImplementation.format(result, override); } getType () { - return this.dataview.getType(); + return this.histogramImplementation.getType(); } toString () { - return this.dataview.toString(); + return this.histogramImplementation.toString(); } }; From 271932a80da90bdf74a83337306225d0cf6b0e61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Mon, 11 Sep 2017 17:17:42 +0200 Subject: [PATCH 077/159] Extract condition to a method --- lib/cartodb/models/dataview/histogram.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/cartodb/models/dataview/histogram.js b/lib/cartodb/models/dataview/histogram.js index 5099ee53..ba69c303 100644 --- a/lib/cartodb/models/dataview/histogram.js +++ b/lib/cartodb/models/dataview/histogram.js @@ -33,13 +33,18 @@ module.exports = class Histogram { return implementation; } - _getHistogramSubtype (override = {}) { - if(this.options.aggregation !== undefined || override.aggregation !== undefined) { + _getHistogramSubtype (override) { + if(this._isDateHistogram(override)) { return DATE_HISTOGRAM; } + return NUMERIC_HISTOGRAM; } + _isDateHistogram (override = {}) { + return (this.options.aggregation !== undefined || override.aggregation !== undefined); + } + getResult (psql, override, callback) { this.histogramImplementation = this._getHistogramImplementation(override); this.histogramImplementation.getResult(psql, override, callback); From 38e7e7132877555022e2f966589633a9be9e575b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Mon, 11 Sep 2017 17:19:02 +0200 Subject: [PATCH 078/159] Implement template method pattern to format histogram query output --- .../dataview/histograms/base-histogram.js | 28 +++--- .../dataview/histograms/date-histogram.js | 99 +++++-------------- .../dataview/histograms/numeric-histogram.js | 72 +++++--------- 3 files changed, 61 insertions(+), 138 deletions(-) diff --git a/lib/cartodb/models/dataview/histograms/base-histogram.js b/lib/cartodb/models/dataview/histograms/base-histogram.js index 33a184d0..c9a53a88 100644 --- a/lib/cartodb/models/dataview/histograms/base-histogram.js +++ b/lib/cartodb/models/dataview/histograms/base-histogram.js @@ -41,6 +41,12 @@ module.exports = class BaseHistogram extends BaseDataview { return this._buildQuery(psql, override, callback); } + format (result, override) { + const histogram = this._getSummary(result, override); + histogram.bins = this._getBuckets(result); + return histogram; + } + getType () { return TYPE; } @@ -53,25 +59,21 @@ module.exports = class BaseHistogram extends BaseDataview { }); } - _getBinEnd (override) { + _getBinStart (override) { + if (override.hasOwnProperty('start') && override.hasOwnProperty('end')) { + return Math.min(override.start, override.end); + } + return override.start || 0; + } + + _getBinEnd (override = {}) { if (override.hasOwnProperty('start') && override.hasOwnProperty('end')) { return Math.max(override.start, override.end); } return override.end || 0; } - _getBinsCount (override) { + _getBinsCount (override = {}) { return override.bins || 0; } - - _getWidth (override) { - let width = 0; - const binsCount = override.bins; - - if (binsCount && Number.isFinite(override.start) && Number.isFinite(override.end)) { - width = (override.end - override.start) / binsCount; - } - - return width; - } }; \ No newline at end of file diff --git a/lib/cartodb/models/dataview/histograms/date-histogram.js b/lib/cartodb/models/dataview/histograms/date-histogram.js index 0ee832bc..20523a46 100644 --- a/lib/cartodb/models/dataview/histograms/date-histogram.js +++ b/lib/cartodb/models/dataview/histograms/date-histogram.js @@ -259,90 +259,37 @@ module.exports = class DateHistogram extends BaseHistogram { }); } - format (result, override) { - override = override || {}; - let buckets = []; - - let binsCount = this._getBinsCount(override); - let width = this._getWidth(override); - let binsStart = this._getBinStart(override); - let nulls = 0; - let infinities = 0; - let nans = 0; - let avg; - let timestampStart; - let aggregation; - let offset; - - if (result.rows.length) { - const firstRow = result.rows[0]; - binsCount = firstRow.bins_number; - width = firstRow.bin_width || width; - avg = firstRow.avg_val; - nulls = firstRow.nulls_count; - timestampStart = firstRow.timestamp_start; - infinities = firstRow.infinities_count; - nans = firstRow.nans_count; - binsStart = this._populateBinStart(override, firstRow); - - aggregation = this._getAggregation(override); - offset = this._getOffset(override); - - buckets = result.rows.map(row => { - const { bin, min, max, avg, freq, timestamp } = row; - return { bin, min, max, avg, freq, timestamp }; - }); - } + _getSummary (result, override) { + const firstRow = result.rows[0] || {}; return { - aggregation: aggregation, - offset: offset, - timestamp_start: timestampStart, - bin_width: width, - bins_count: binsCount, - bins_start: binsStart, - nulls: nulls, - infinities: infinities, - nans: nans, - avg: avg, - bins: buckets + aggregation: this._getAggregation(override), + offset: this._getOffset(override), + timestamp_start: firstRow.timestamp_start, + + bin_width: firstRow.bin_width, + bins_count: firstRow.bins_number, + bins_start: firstRow.timestamp, + nulls: firstRow.nulls_count, + infinities: firstRow.infinities_count, + nans: firstRow.nans_count, + avg: firstRow.avg_val }; } - _getAggregation (override) { + _getBuckets (result) { + return result.rows.map(row => { + const { bin, min, max, avg, freq, timestamp } = row; + return { bin, min, max, avg, freq, timestamp }; + }); + } + + _getAggregation (override = {}) { return override && override.aggregation ? override.aggregation : this.aggregation; } - _getOffset (override) { - if (override && override.offset) { - return override.offset; - } - if (this.offset) { - return this.offset; - } - - return 0; - } - - _getBinStart (override) { - if (override.hasOwnProperty('start') && override.hasOwnProperty('end')) { - return Math.min(override.start, override.end); - } - return override.start || 0; - } - - _populateBinStart (override, firstRow) { - let binStart; - - if (firstRow.hasOwnProperty('timestamp')) { - binStart = firstRow.timestamp; - } else if (override.hasOwnProperty('start')) { - binStart = this._getBinStart(override); - } else { - binStart = firstRow.min; - } - - return binStart; + _getOffset (override = {}) { + return override.offset || this.offset || 0; } _parseOffset (offset, aggregation) { diff --git a/lib/cartodb/models/dataview/histograms/numeric-histogram.js b/lib/cartodb/models/dataview/histograms/numeric-histogram.js index ece7babc..ed14d78f 100644 --- a/lib/cartodb/models/dataview/histograms/numeric-histogram.js +++ b/lib/cartodb/models/dataview/histograms/numeric-histogram.js @@ -182,14 +182,14 @@ module.exports = class NumericHistogram extends BaseHistogram { _buildQueryTpl (ctx) { return ` WITH - ${filteredQueryTpl(ctx)}, - ${this._shouldOverride(ctx._override) ? overrideBasicsQueryTpl(ctx) : basicsQueryTpl(ctx)}, - ${this._shouldOverrideRange(ctx._override) || this._shouldOverrideBins(ctx._override) ? - overrideBinsQueryTpl(ctx) : - `${iqrQueryTpl(ctx)}, ${binsQueryTpl(ctx)}` - }, - ${nullsQueryTpl(ctx)} - ${ctx._isFloatColumn ? `,${infinitiesQueryTpl(ctx)}, ${nansQueryTpl(ctx)}` : ''} + ${filteredQueryTpl(ctx)}, + ${this._shouldOverride(ctx._override) ? overrideBasicsQueryTpl(ctx) : basicsQueryTpl(ctx)}, + ${this._shouldOverrideRange(ctx._override) || this._shouldOverrideBins(ctx._override) ? + overrideBinsQueryTpl(ctx) : + `${iqrQueryTpl(ctx)}, ${binsQueryTpl(ctx)}` + }, + ${nullsQueryTpl(ctx)} + ${ctx._isFloatColumn ? `,${infinitiesQueryTpl(ctx)}, ${nansQueryTpl(ctx)}` : ''} ${histogramQueryTpl(ctx)} `; } @@ -206,54 +206,28 @@ module.exports = class NumericHistogram extends BaseHistogram { return override && override.hasOwnProperty('bins'); } - format (result, override) { - override = override || {}; - let buckets = []; - - let binsCount = this._getBinsCount(override); - let width = this._getWidth(override); - let binsStart = this._getBinStart(override); - let nulls = 0; - let infinities = 0; - let nans = 0; - let avg; - - if (result.rows.length) { - const firstRow = result.rows[0]; - binsCount = firstRow.bins_number; - width = firstRow.bin_width || width; - avg = firstRow.avg_val; - nulls = firstRow.nulls_count; - infinities = firstRow.infinities_count; - nans = firstRow.nans_count; - binsStart = this._populateBinStart(override, firstRow); - - buckets = result.rows.map(row => { - const { bin, min, max, avg, freq } = row; - return { bin, min, max, avg, freq }; - }); - } + _getSummary (result, override) { + const firstRow = result.rows[0] || {}; return { - bin_width: width, - bins_count: binsCount, - bins_start: binsStart, - nulls: nulls, - infinities: infinities, - nans: nans, - avg: avg, - bins: buckets + bin_width: firstRow.bin_width, + bins_count: firstRow.bins_number, + bins_start: this._populateBinStart(firstRow, override), + nulls: firstRow.nulls_count, + infinities: firstRow.infinities_count, + nans: firstRow.nans_count, + avg: firstRow.avg_val, }; } - _getBinStart (override) { - if (override.hasOwnProperty('start') && override.hasOwnProperty('end')) { - return Math.min(override.start, override.end); - } - return override.start || 0; + _getBuckets (result) { + return result.rows.map(row => { + const { bin, min, max, avg, freq } = row; + return { bin, min, max, avg, freq }; + }); } - _populateBinStart (override, firstRow) { + _populateBinStart (firstRow, override = {}) { let binStart; if (override.hasOwnProperty('start')) { From fd9534797c519bf6be33cc9df07a62a3aa1abc4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Mon, 11 Sep 2017 18:44:14 +0200 Subject: [PATCH 079/159] Minor refactors --- .../dataview/histograms/date-histogram.js | 32 ++++++++++--------- .../dataview/histograms/numeric-histogram.js | 3 ++ 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/lib/cartodb/models/dataview/histograms/date-histogram.js b/lib/cartodb/models/dataview/histograms/date-histogram.js index 20523a46..a14e90e9 100644 --- a/lib/cartodb/models/dataview/histograms/date-histogram.js +++ b/lib/cartodb/models/dataview/histograms/date-histogram.js @@ -1,4 +1,5 @@ const BaseHistogram = require('./base-histogram'); +const debug = require('debug')('windshaft:dataview:date-histogram'); const dateIntervalQueryTpl = ctx => ` WITH @@ -186,16 +187,13 @@ module.exports = class DateHistogram extends BaseHistogram { } _buildQuery (psql, override, callback) { - const _aggregation = override && override.aggregation ? override.aggregation : this.aggregation; - const _offset = override && Number.isFinite(override.offset) ? override.offset : this.offset; - - if (!DATE_AGGREGATIONS.hasOwnProperty(_aggregation)) { + if (!DATE_AGGREGATIONS.hasOwnProperty(this._getAggregation(override))) { return callback(new Error('Invalid aggregation value. Valid ones: ' + Object.keys(DATE_AGGREGATIONS).join(', ') )); } - if (_aggregation === 'auto') { + if (this._getAggregation(override) === 'auto') { this._getAutomaticAggregation(psql, function (err, aggregation) { if (err || aggregation === 'none') { this.aggregation = 'day'; @@ -212,12 +210,14 @@ module.exports = class DateHistogram extends BaseHistogram { _override: override, _query: this.query, _column: this.column, - _aggregation: _aggregation, + _aggregation: this._getAggregation(override), _start: this._getBinStart(override), _end: this._getBinEnd(override), - _offset: this._parseOffset(_offset, _aggregation) + _offset: this._parseOffset(override) }); + debug(histogramSql); + return callback(null, histogramSql); } @@ -285,22 +285,24 @@ module.exports = class DateHistogram extends BaseHistogram { } _getAggregation (override = {}) { - return override && override.aggregation ? override.aggregation : this.aggregation; + return override.aggregation ? override.aggregation : this.aggregation; } _getOffset (override = {}) { - return override.offset || this.offset || 0; + return Number.isFinite(override.offset) ? override.offset : (this.offset || 0); } - _parseOffset (offset, aggregation) { - if (!offset) { - return '0'; - } - if (aggregation === 'hour' || aggregation === 'minute') { + _parseOffset (override) { + if (this._shouldIgnoreOffset(override)) { return '0'; } - const offsetInHours = Math.ceil(offset / 3600); + const offsetInHours = Math.ceil(this._getOffset(override) / 3600); + return '' + offsetInHours; } + + _shouldIgnoreOffset (override) { + return (this._getAggregation(override) === 'hour' || this._getAggregation(override) === 'minute'); + } }; diff --git a/lib/cartodb/models/dataview/histograms/numeric-histogram.js b/lib/cartodb/models/dataview/histograms/numeric-histogram.js index ed14d78f..60716eb6 100644 --- a/lib/cartodb/models/dataview/histograms/numeric-histogram.js +++ b/lib/cartodb/models/dataview/histograms/numeric-histogram.js @@ -1,4 +1,5 @@ const BaseHistogram = require('./base-histogram'); +const debug = require('debug')('windshaft:dataview:numeric-histogram'); const columnCastTpl = ctx => `date_part('epoch', ${ctx.column})`; @@ -176,6 +177,8 @@ module.exports = class NumericHistogram extends BaseHistogram { _maxBins: BIN_MAX_NUMBER, }); + debug(histogramSql); + return callback(null, histogramSql); } From 09981c256053176ef5fd020724fc58bc7e124878 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Mon, 11 Sep 2017 18:57:16 +0200 Subject: [PATCH 080/159] Extract method to check valid aggregation --- lib/cartodb/models/dataview/histograms/date-histogram.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/cartodb/models/dataview/histograms/date-histogram.js b/lib/cartodb/models/dataview/histograms/date-histogram.js index a14e90e9..6695877a 100644 --- a/lib/cartodb/models/dataview/histograms/date-histogram.js +++ b/lib/cartodb/models/dataview/histograms/date-histogram.js @@ -187,7 +187,7 @@ module.exports = class DateHistogram extends BaseHistogram { } _buildQuery (psql, override, callback) { - if (!DATE_AGGREGATIONS.hasOwnProperty(this._getAggregation(override))) { + if (!this.isValidAggregation(override)) { return callback(new Error('Invalid aggregation value. Valid ones: ' + Object.keys(DATE_AGGREGATIONS).join(', ') )); @@ -221,6 +221,10 @@ module.exports = class DateHistogram extends BaseHistogram { return callback(null, histogramSql); } + _isValidAggregation (override) { + return DATE_AGGREGATIONS.hasOwnProperty(this._getAggregation(override)); + } + _getAutomaticAggregation (psql, callback) { const dateIntervalQuery = dateIntervalQueryTpl({ query: this.query, From ee43378c68122c283e83020011ff4c7ce4565671 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Mon, 11 Sep 2017 18:57:50 +0200 Subject: [PATCH 081/159] Use arrow function --- .../models/dataview/histograms/date-histogram.js | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/lib/cartodb/models/dataview/histograms/date-histogram.js b/lib/cartodb/models/dataview/histograms/date-histogram.js index 6695877a..2684eec9 100644 --- a/lib/cartodb/models/dataview/histograms/date-histogram.js +++ b/lib/cartodb/models/dataview/histograms/date-histogram.js @@ -187,7 +187,7 @@ module.exports = class DateHistogram extends BaseHistogram { } _buildQuery (psql, override, callback) { - if (!this.isValidAggregation(override)) { + if (!this._isValidAggregation(override)) { return callback(new Error('Invalid aggregation value. Valid ones: ' + Object.keys(DATE_AGGREGATIONS).join(', ') )); @@ -238,13 +238,8 @@ module.exports = class DateHistogram extends BaseHistogram { const aggegations = result.rows[0]; const aggregation = Object.keys(aggegations) - .map(function (key) { - return { - name: key, - value: aggegations[key] - }; - }) - .reduce(function (closer, current) { + .map(key => ({ name: key, value: aggegations[key] })) + .reduce((closer, current) => { if (current.value > MAX_INTERVAL_VALUE) { return closer; } From 7c6924074846228dda9cada218b1e05423136a4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Mon, 11 Sep 2017 19:10:06 +0200 Subject: [PATCH 082/159] Use parameter default value --- lib/cartodb/models/dataview/histograms/base-histogram.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cartodb/models/dataview/histograms/base-histogram.js b/lib/cartodb/models/dataview/histograms/base-histogram.js index c9a53a88..2089e0c5 100644 --- a/lib/cartodb/models/dataview/histograms/base-histogram.js +++ b/lib/cartodb/models/dataview/histograms/base-histogram.js @@ -59,7 +59,7 @@ module.exports = class BaseHistogram extends BaseDataview { }); } - _getBinStart (override) { + _getBinStart (override = {}) { if (override.hasOwnProperty('start') && override.hasOwnProperty('end')) { return Math.min(override.start, override.end); } From 5fc2b46d5684cde1611cabe7f5852e1c8fd1ef8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Mon, 11 Sep 2017 19:14:11 +0200 Subject: [PATCH 083/159] Fix bad condition --- lib/cartodb/models/dataview/histograms/numeric-histogram.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cartodb/models/dataview/histograms/numeric-histogram.js b/lib/cartodb/models/dataview/histograms/numeric-histogram.js index 60716eb6..c9a58736 100644 --- a/lib/cartodb/models/dataview/histograms/numeric-histogram.js +++ b/lib/cartodb/models/dataview/histograms/numeric-histogram.js @@ -187,7 +187,7 @@ module.exports = class NumericHistogram extends BaseHistogram { WITH ${filteredQueryTpl(ctx)}, ${this._shouldOverride(ctx._override) ? overrideBasicsQueryTpl(ctx) : basicsQueryTpl(ctx)}, - ${this._shouldOverrideRange(ctx._override) || this._shouldOverrideBins(ctx._override) ? + ${this._shouldOverrideBins(ctx._override) ? overrideBinsQueryTpl(ctx) : `${iqrQueryTpl(ctx)}, ${binsQueryTpl(ctx)}` }, From e3bffcd39d8c2d5eb86209ade7581c58581ba067 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Mon, 11 Sep 2017 19:24:01 +0200 Subject: [PATCH 084/159] Use inline functions to filter desired fields of the row --- lib/cartodb/models/dataview/histograms/date-histogram.js | 5 +---- lib/cartodb/models/dataview/histograms/numeric-histogram.js | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/lib/cartodb/models/dataview/histograms/date-histogram.js b/lib/cartodb/models/dataview/histograms/date-histogram.js index 2684eec9..631cb6c7 100644 --- a/lib/cartodb/models/dataview/histograms/date-histogram.js +++ b/lib/cartodb/models/dataview/histograms/date-histogram.js @@ -277,10 +277,7 @@ module.exports = class DateHistogram extends BaseHistogram { } _getBuckets (result) { - return result.rows.map(row => { - const { bin, min, max, avg, freq, timestamp } = row; - return { bin, min, max, avg, freq, timestamp }; - }); + return result.rows.map(({ bin, min, max, avg, freq, timestamp }) => ({ bin, min, max, avg, freq, timestamp })); } _getAggregation (override = {}) { diff --git a/lib/cartodb/models/dataview/histograms/numeric-histogram.js b/lib/cartodb/models/dataview/histograms/numeric-histogram.js index c9a58736..fb158dc0 100644 --- a/lib/cartodb/models/dataview/histograms/numeric-histogram.js +++ b/lib/cartodb/models/dataview/histograms/numeric-histogram.js @@ -224,10 +224,7 @@ module.exports = class NumericHistogram extends BaseHistogram { } _getBuckets (result) { - return result.rows.map(row => { - const { bin, min, max, avg, freq } = row; - return { bin, min, max, avg, freq }; - }); + return result.rows.map(({ bin, min, max, avg, freq }) => ({ bin, min, max, avg, freq })); } _populateBinStart (firstRow, override = {}) { From 9aa5a9e8503b8cf60605683f12648a95aff6c488 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Mon, 11 Sep 2017 19:26:28 +0200 Subject: [PATCH 085/159] Improve comment --- lib/cartodb/models/dataview/histograms/date-histogram.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cartodb/models/dataview/histograms/date-histogram.js b/lib/cartodb/models/dataview/histograms/date-histogram.js index 631cb6c7..fd7e8ba4 100644 --- a/lib/cartodb/models/dataview/histograms/date-histogram.js +++ b/lib/cartodb/models/dataview/histograms/date-histogram.js @@ -161,7 +161,7 @@ const DATE_AGGREGATIONS = { type: 'histogram', options: { column: 'date', // column data type: date - aggregation: 'day' // auto by default + aggregation: 'day' // MANDATORY offset: -7200 // OPTIONAL (UTC offset in seconds) } } From 03babcb43b397d2ffedf811af506074a4263d916 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Mon, 11 Sep 2017 19:31:38 +0200 Subject: [PATCH 086/159] Simplify condition and remove unused method --- lib/cartodb/models/dataview/histograms/numeric-histogram.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/cartodb/models/dataview/histograms/numeric-histogram.js b/lib/cartodb/models/dataview/histograms/numeric-histogram.js index fb158dc0..7a562ec7 100644 --- a/lib/cartodb/models/dataview/histograms/numeric-histogram.js +++ b/lib/cartodb/models/dataview/histograms/numeric-histogram.js @@ -186,7 +186,7 @@ module.exports = class NumericHistogram extends BaseHistogram { return ` WITH ${filteredQueryTpl(ctx)}, - ${this._shouldOverride(ctx._override) ? overrideBasicsQueryTpl(ctx) : basicsQueryTpl(ctx)}, + ${this._shouldOverrideRange(ctx._override) ? overrideBasicsQueryTpl(ctx) : basicsQueryTpl(ctx)}, ${this._shouldOverrideBins(ctx._override) ? overrideBinsQueryTpl(ctx) : `${iqrQueryTpl(ctx)}, ${binsQueryTpl(ctx)}` @@ -197,10 +197,6 @@ module.exports = class NumericHistogram extends BaseHistogram { `; } - _shouldOverride (override) { - return this._shouldOverrideRange(override) && this._shouldOverrideBins(override); - } - _shouldOverrideRange (override) { return override && override.hasOwnProperty('start') && override.hasOwnProperty('end'); } From 3a936474cf9a1aa8ef07cbc701fdd5e9cd7954af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Mon, 11 Sep 2017 19:48:46 +0200 Subject: [PATCH 087/159] Fix bad merge with master --- lib/cartodb/models/dataview/histograms/date-histogram.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/cartodb/models/dataview/histograms/date-histogram.js b/lib/cartodb/models/dataview/histograms/date-histogram.js index fd7e8ba4..e4176146 100644 --- a/lib/cartodb/models/dataview/histograms/date-histogram.js +++ b/lib/cartodb/models/dataview/histograms/date-histogram.js @@ -70,8 +70,8 @@ const dateBasicsQueryTpl = ctx => ` const dateOverrideBasicsQueryTpl = ctx => ` __cdb_basics AS ( SELECT - max(${ctx._end}) AS __cdb_max_val, - min(${ctx._start}) AS __cdb_min_val, + max(${ctx._end})::float AS __cdb_max_val, + min(${ctx._start})::float AS __cdb_min_val, avg(date_part('epoch', ${ctx._column})) AS __cdb_avg_val, min( date_trunc( From 2576c3e7d50846829a27503e029430bc8ea707dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Tue, 12 Sep 2017 10:14:55 +0200 Subject: [PATCH 088/159] Rename _shouldOverrideRange & _shouldOverrideBins methods --- .../models/dataview/histograms/base-histogram.js | 10 ++++++++-- .../models/dataview/histograms/date-histogram.js | 4 +--- .../models/dataview/histograms/numeric-histogram.js | 10 +++------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/cartodb/models/dataview/histograms/base-histogram.js b/lib/cartodb/models/dataview/histograms/base-histogram.js index 2089e0c5..9b3f3759 100644 --- a/lib/cartodb/models/dataview/histograms/base-histogram.js +++ b/lib/cartodb/models/dataview/histograms/base-histogram.js @@ -59,17 +59,23 @@ module.exports = class BaseHistogram extends BaseDataview { }); } + _hasOverridenRange (override) { + return override && override.hasOwnProperty('start') && override.hasOwnProperty('end'); + } + _getBinStart (override = {}) { - if (override.hasOwnProperty('start') && override.hasOwnProperty('end')) { + if (this._hasOverridenRange(override)) { return Math.min(override.start, override.end); } + return override.start || 0; } _getBinEnd (override = {}) { - if (override.hasOwnProperty('start') && override.hasOwnProperty('end')) { + if (this._hasOverridenRange(override)) { return Math.max(override.start, override.end); } + return override.end || 0; } diff --git a/lib/cartodb/models/dataview/histograms/date-histogram.js b/lib/cartodb/models/dataview/histograms/date-histogram.js index e4176146..8124f2db 100644 --- a/lib/cartodb/models/dataview/histograms/date-histogram.js +++ b/lib/cartodb/models/dataview/histograms/date-histogram.js @@ -177,9 +177,7 @@ module.exports = class DateHistogram extends BaseHistogram { _buildQueryTpl (ctx) { return ` WITH - ${ctx._override && ctx._override.hasOwnProperty('start') && ctx._override.hasOwnProperty('end') ? - dateOverrideBasicsQueryTpl(ctx) : - dateBasicsQueryTpl(ctx)}, + ${this._hasOverridenRange(ctx._override) ? dateOverrideBasicsQueryTpl(ctx) : dateBasicsQueryTpl(ctx)}, ${dateBinsQueryTpl(ctx)}, ${nullsQueryTpl(ctx)} ${dateHistogramQueryTpl(ctx)} diff --git a/lib/cartodb/models/dataview/histograms/numeric-histogram.js b/lib/cartodb/models/dataview/histograms/numeric-histogram.js index 7a562ec7..638d3f0a 100644 --- a/lib/cartodb/models/dataview/histograms/numeric-histogram.js +++ b/lib/cartodb/models/dataview/histograms/numeric-histogram.js @@ -186,8 +186,8 @@ module.exports = class NumericHistogram extends BaseHistogram { return ` WITH ${filteredQueryTpl(ctx)}, - ${this._shouldOverrideRange(ctx._override) ? overrideBasicsQueryTpl(ctx) : basicsQueryTpl(ctx)}, - ${this._shouldOverrideBins(ctx._override) ? + ${this._hasOverridenRange(ctx._override) ? overrideBasicsQueryTpl(ctx) : basicsQueryTpl(ctx)}, + ${this._hasOverridenBins(ctx._override) ? overrideBinsQueryTpl(ctx) : `${iqrQueryTpl(ctx)}, ${binsQueryTpl(ctx)}` }, @@ -197,11 +197,7 @@ module.exports = class NumericHistogram extends BaseHistogram { `; } - _shouldOverrideRange (override) { - return override && override.hasOwnProperty('start') && override.hasOwnProperty('end'); - } - - _shouldOverrideBins (override) { + _hasOverridenBins (override) { return override && override.hasOwnProperty('bins'); } From 8f797c3c41f9fdf375e7170f889f9a655e42ba4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Tue, 12 Sep 2017 10:16:16 +0200 Subject: [PATCH 089/159] Fix EOF --- lib/cartodb/models/dataview/histograms/base-histogram.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cartodb/models/dataview/histograms/base-histogram.js b/lib/cartodb/models/dataview/histograms/base-histogram.js index 9b3f3759..ec251d09 100644 --- a/lib/cartodb/models/dataview/histograms/base-histogram.js +++ b/lib/cartodb/models/dataview/histograms/base-histogram.js @@ -82,4 +82,4 @@ module.exports = class BaseHistogram extends BaseDataview { _getBinsCount (override = {}) { return override.bins || 0; } -}; \ No newline at end of file +}; From c0cfdad7d18788f1bcf2a099c8d8991dafa01c00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Tue, 12 Sep 2017 10:38:53 +0200 Subject: [PATCH 090/159] Use hasOwnProperty method to check if histogram is a time-series --- lib/cartodb/models/dataview/histogram.js | 2 +- lib/cartodb/models/dataview/histograms/numeric-histogram.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/cartodb/models/dataview/histogram.js b/lib/cartodb/models/dataview/histogram.js index ba69c303..affbbd25 100644 --- a/lib/cartodb/models/dataview/histogram.js +++ b/lib/cartodb/models/dataview/histogram.js @@ -42,7 +42,7 @@ module.exports = class Histogram { } _isDateHistogram (override = {}) { - return (this.options.aggregation !== undefined || override.aggregation !== undefined); + return (this.options.hasOwnProperty('aggregation') || override.hasOwnProperty('aggregation')); } getResult (psql, override, callback) { diff --git a/lib/cartodb/models/dataview/histograms/numeric-histogram.js b/lib/cartodb/models/dataview/histograms/numeric-histogram.js index 638d3f0a..1df88adb 100644 --- a/lib/cartodb/models/dataview/histograms/numeric-histogram.js +++ b/lib/cartodb/models/dataview/histograms/numeric-histogram.js @@ -64,7 +64,7 @@ const binsQueryTpl = ctx => ` ${ctx._maxBins} ) ) - END AS __cdb_bins_number + END AS __cdb_bins_number FROM __cdb_basics, __cdb_iqrange, __cdb_filtered_source LIMIT 1 ) From 4a89ad57d76ed6f123e2e3d08fa7ae36e54e1c08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Tue, 12 Sep 2017 13:05:46 +0200 Subject: [PATCH 091/159] Remove '_' as prefix for template's context properties --- .../dataview/histograms/date-histogram.js | 64 +++++++-------- .../dataview/histograms/numeric-histogram.js | 78 +++++++++---------- 2 files changed, 71 insertions(+), 71 deletions(-) diff --git a/lib/cartodb/models/dataview/histograms/date-histogram.js b/lib/cartodb/models/dataview/histograms/date-histogram.js index 8124f2db..34a42f9a 100644 --- a/lib/cartodb/models/dataview/histograms/date-histogram.js +++ b/lib/cartodb/models/dataview/histograms/date-histogram.js @@ -45,45 +45,45 @@ const nullsQueryTpl = ctx => ` __cdb_nulls AS ( SELECT count(*) AS __cdb_nulls_count - FROM (${ctx._query}) __cdb_histogram_nulls - WHERE ${ctx._column} IS NULL + FROM (${ctx.query}) __cdb_histogram_nulls + WHERE ${ctx.column} IS NULL ) `; const dateBasicsQueryTpl = ctx => ` __cdb_basics AS ( SELECT - max(date_part('epoch', ${ctx._column})) AS __cdb_max_val, - min(date_part('epoch', ${ctx._column})) AS __cdb_min_val, - avg(date_part('epoch', ${ctx._column})) AS __cdb_avg_val, + max(date_part('epoch', ${ctx.column})) AS __cdb_max_val, + min(date_part('epoch', ${ctx.column})) AS __cdb_min_val, + avg(date_part('epoch', ${ctx.column})) AS __cdb_avg_val, min( date_trunc( - '${ctx._aggregation}', ${ctx._column}::timestamp AT TIME ZONE '${ctx._offset}' + '${ctx.aggregation}', ${ctx.column}::timestamp AT TIME ZONE '${ctx.offset}' ) ) AS __cdb_start_date, - max(${ctx._column}::timestamp AT TIME ZONE '${ctx._offset}') AS __cdb_end_date, + max(${ctx.column}::timestamp AT TIME ZONE '${ctx.offset}') AS __cdb_end_date, count(1) AS __cdb_total_rows - FROM (${ctx._query}) __cdb_basics_query + FROM (${ctx.query}) __cdb_basics_query ) `; const dateOverrideBasicsQueryTpl = ctx => ` __cdb_basics AS ( SELECT - max(${ctx._end})::float AS __cdb_max_val, - min(${ctx._start})::float AS __cdb_min_val, - avg(date_part('epoch', ${ctx._column})) AS __cdb_avg_val, + max(${ctx.end})::float AS __cdb_max_val, + min(${ctx.start})::float AS __cdb_min_val, + avg(date_part('epoch', ${ctx.column})) AS __cdb_avg_val, min( date_trunc( - '${ctx._aggregation}', - TO_TIMESTAMP(${ctx._start})::timestamp AT TIME ZONE '${ctx._offset}' + '${ctx.aggregation}', + TO_TIMESTAMP(${ctx.start})::timestamp AT TIME ZONE '${ctx.offset}' ) ) AS __cdb_start_date, max( - TO_TIMESTAMP(${ctx._end})::timestamp AT TIME ZONE '${ctx._offset}' + TO_TIMESTAMP(${ctx.end})::timestamp AT TIME ZONE '${ctx.offset}' ) AS __cdb_end_date, count(1) AS __cdb_total_rows - FROM (${ctx._query}) __cdb_basics_query + FROM (${ctx.query}) __cdb_basics_query ) `; @@ -98,7 +98,7 @@ const dateBinsQueryTpl = ctx => ` SELECT GENERATE_SERIES( __cdb_start_date::timestamptz, __cdb_end_date::timestamptz, - ${ctx._aggregation === 'quarter' ? `'3 month'::interval` : `'1 ${ctx._aggregation}'::interval`} + ${ctx.aggregation === 'quarter' ? `'3 month'::interval` : `'1 ${ctx.aggregation}'::interval`} ) ) AS __cdb_bins_array FROM __cdb_basics @@ -117,7 +117,7 @@ const dateHistogramQueryTpl = ctx => ` 1, LEAST( WIDTH_BUCKET( - ${ctx._column}::timestamp AT TIME ZONE '${ctx._offset}', + ${ctx.column}::timestamp AT TIME ZONE '${ctx.offset}', __cdb_bins_array ), __cdb_bins_number @@ -128,17 +128,17 @@ const dateHistogramQueryTpl = ctx => ` date_part( 'epoch', date_trunc( - '${ctx._aggregation}', ${ctx._column}::timestamp AT TIME ZONE '${ctx._offset}' - ) AT TIME ZONE '${ctx._offset}' + '${ctx.aggregation}', ${ctx.column}::timestamp AT TIME ZONE '${ctx.offset}' + ) AT TIME ZONE '${ctx.offset}' ) )::numeric AS timestamp, date_part('epoch', __cdb_start_date)::numeric AS timestamp_start, - min(date_part('epoch', ${ctx._column}))::numeric AS min, - max(date_part('epoch', ${ctx._column}))::numeric AS max, - avg(date_part('epoch', ${ctx._column}))::numeric AS avg, + min(date_part('epoch', ${ctx.column}))::numeric AS min, + max(date_part('epoch', ${ctx.column}))::numeric AS max, + avg(date_part('epoch', ${ctx.column}))::numeric AS avg, count(*) AS freq - FROM (${ctx._query}) __cdb_histogram, __cdb_basics, __cdb_bins, __cdb_nulls - WHERE date_part('epoch', ${ctx._column}) IS NOT NULL + FROM (${ctx.query}) __cdb_histogram, __cdb_basics, __cdb_bins, __cdb_nulls + WHERE date_part('epoch', ${ctx.column}) IS NOT NULL GROUP BY bin, bins_number, bin_width, nulls_count, timestamp_start ORDER BY bin `; @@ -177,7 +177,7 @@ module.exports = class DateHistogram extends BaseHistogram { _buildQueryTpl (ctx) { return ` WITH - ${this._hasOverridenRange(ctx._override) ? dateOverrideBasicsQueryTpl(ctx) : dateBasicsQueryTpl(ctx)}, + ${this._hasOverridenRange(ctx.override) ? dateOverrideBasicsQueryTpl(ctx) : dateBasicsQueryTpl(ctx)}, ${dateBinsQueryTpl(ctx)}, ${nullsQueryTpl(ctx)} ${dateHistogramQueryTpl(ctx)} @@ -205,13 +205,13 @@ module.exports = class DateHistogram extends BaseHistogram { } const histogramSql = this._buildQueryTpl({ - _override: override, - _query: this.query, - _column: this.column, - _aggregation: this._getAggregation(override), - _start: this._getBinStart(override), - _end: this._getBinEnd(override), - _offset: this._parseOffset(override) + override: override, + query: this.query, + column: this.column, + aggregation: this._getAggregation(override), + start: this._getBinStart(override), + end: this._getBinEnd(override), + offset: this._parseOffset(override) }); debug(histogramSql); diff --git a/lib/cartodb/models/dataview/histograms/numeric-histogram.js b/lib/cartodb/models/dataview/histograms/numeric-histogram.js index 1df88adb..ea191bea 100644 --- a/lib/cartodb/models/dataview/histograms/numeric-histogram.js +++ b/lib/cartodb/models/dataview/histograms/numeric-histogram.js @@ -4,27 +4,27 @@ const debug = require('debug')('windshaft:dataview:numeric-histogram'); const columnCastTpl = ctx => `date_part('epoch', ${ctx.column})`; const filterOutSpecialNumericValues = ctx => ` - ${ctx._column} != 'infinity'::float + ${ctx.column} != 'infinity'::float AND - ${ctx._column} != '-infinity'::float + ${ctx.column} != '-infinity'::float AND - ${ctx._column} != 'NaN'::float + ${ctx.column} != 'NaN'::float `; const filteredQueryTpl = ctx => ` __cdb_filtered_source AS ( SELECT * - FROM (${ctx._query}) __cdb_filtered_source_query - WHERE ${ctx._column} IS NOT NULL - ${ctx._isFloatColumn ? `AND ${filterOutSpecialNumericValues(ctx)}` : ''} + FROM (${ctx.query}) __cdb_filtered_source_query + WHERE ${ctx.column} IS NOT NULL + ${ctx.isFloatColumn ? `AND ${filterOutSpecialNumericValues(ctx)}` : ''} ) `; const basicsQueryTpl = ctx => ` __cdb_basics AS ( SELECT - max(${ctx._column}) AS __cdb_max_val, min(${ctx._column}) AS __cdb_min_val, - avg(${ctx._column}) AS __cdb_avg_val, count(1) AS __cdb_total_rows + max(${ctx.column}) AS __cdb_max_val, min(${ctx.column}) AS __cdb_min_val, + avg(${ctx.column}) AS __cdb_avg_val, count(1) AS __cdb_total_rows FROM __cdb_filtered_source ) `; @@ -32,8 +32,8 @@ const basicsQueryTpl = ctx => ` const overrideBasicsQueryTpl = ctx => ` __cdb_basics AS ( SELECT - max(${ctx._end}) AS __cdb_max_val, min(${ctx._start}) AS __cdb_min_val, - avg(${ctx._column}) AS __cdb_avg_val, count(1) AS __cdb_total_rows + max(${ctx.end}) AS __cdb_max_val, min(${ctx.start}) AS __cdb_min_val, + avg(${ctx.column}) AS __cdb_avg_val, count(1) AS __cdb_total_rows FROM __cdb_filtered_source ) `; @@ -43,7 +43,7 @@ const iqrQueryTpl = ctx => ` SELECT max(quartile_max) - min(quartile_max) AS __cdb_iqr FROM ( SELECT quartile, max(_cdb_iqr_column) AS quartile_max from ( - SELECT ${ctx._column} AS _cdb_iqr_column, ntile(4) over (order by ${ctx._column} + SELECT ${ctx.column} AS _cdb_iqr_column, ntile(4) over (order by ${ctx.column} ) AS quartile FROM __cdb_filtered_source) _cdb_quartiles WHERE quartile = 1 or quartile = 3 @@ -58,10 +58,10 @@ const binsQueryTpl = ctx => ` CASE WHEN __cdb_total_rows = 0 OR __cdb_iqr = 0 THEN 1 ELSE GREATEST( - LEAST(${ctx._minBins}, CAST(__cdb_total_rows AS INT)), + LEAST(${ctx.minBins}, CAST(__cdb_total_rows AS INT)), LEAST( CAST(((__cdb_max_val - __cdb_min_val) / (2 * __cdb_iqr * power(__cdb_total_rows, 1/3))) AS INT), - ${ctx._maxBins} + ${ctx.maxBins} ) ) END AS __cdb_bins_number @@ -72,7 +72,7 @@ const binsQueryTpl = ctx => ` const overrideBinsQueryTpl = ctx => ` __cdb_bins AS ( - SELECT ${ctx._override.bins} AS __cdb_bins_number + SELECT ${ctx.override.bins} AS __cdb_bins_number ) `; @@ -80,8 +80,8 @@ const nullsQueryTpl = ctx => ` __cdb_nulls AS ( SELECT count(*) AS __cdb_nulls_count - FROM (${ctx._query}) __cdb_histogram_nulls - WHERE ${ctx._column} IS NULL + FROM (${ctx.query}) __cdb_histogram_nulls + WHERE ${ctx.column} IS NULL ) `; @@ -89,11 +89,11 @@ const infinitiesQueryTpl = ctx => ` __cdb_infinities AS ( SELECT count(*) AS __cdb_infinities_count - FROM (${ctx._query}) __cdb_infinities_query + FROM (${ctx.query}) __cdb_infinities_query WHERE - ${ctx._column} = 'infinity'::float + ${ctx.column} = 'infinity'::float OR - ${ctx._column} = '-infinity'::float + ${ctx.column} = '-infinity'::float ) `; @@ -101,8 +101,8 @@ const nansQueryTpl = ctx => ` __cdb_nans AS ( SELECT count(*) AS __cdb_nans_count - FROM (${ctx._query}) __cdb_nans_query - WHERE ${ctx._column} = 'NaN'::float + FROM (${ctx.query}) __cdb_nans_query + WHERE ${ctx.column} = 'NaN'::float ) `; @@ -124,26 +124,26 @@ const histogramQueryTpl = ctx => ` (__cdb_max_val - __cdb_min_val) / cast(__cdb_bins_number as float) AS bin_width, __cdb_bins_number AS bins_number, __cdb_nulls_count AS nulls_count, - ${ctx._isFloatColumn ? `${specialNumericValuesColumnDefinitionTpl()},` : ''} + ${ctx.isFloatColumn ? `${specialNumericValuesColumnDefinitionTpl()},` : ''} __cdb_avg_val AS avg_val, CASE WHEN __cdb_min_val = __cdb_max_val THEN 0 ELSE GREATEST( 1, LEAST( - WIDTH_BUCKET(${ctx._column}, __cdb_min_val, __cdb_max_val, __cdb_bins_number), + WIDTH_BUCKET(${ctx.column}, __cdb_min_val, __cdb_max_val, __cdb_bins_number), __cdb_bins_number ) ) - 1 END AS bin, - min(${ctx._column})::numeric AS min, - max(${ctx._column})::numeric AS max, - avg(${ctx._column})::numeric AS avg, + min(${ctx.column})::numeric AS min, + max(${ctx.column})::numeric AS max, + avg(${ctx.column})::numeric AS avg, count(*) AS freq FROM __cdb_filtered_source, __cdb_basics, __cdb_nulls, __cdb_bins - ${ctx._isFloatColumn ? `, ${specialNumericValuesCTETpl()}` : ''} + ${ctx.isFloatColumn ? `, ${specialNumericValuesCTETpl()}` : ''} GROUP BY bin, bins_number, bin_width, nulls_count, avg_val - ${ctx._isFloatColumn ? `, ${specialNumericValuesColumnTpl()}` : ''} + ${ctx.isFloatColumn ? `, ${specialNumericValuesColumnTpl()}` : ''} ORDER BY bin `; @@ -167,14 +167,14 @@ module.exports = class NumericHistogram extends BaseHistogram { _buildQuery (psql, override, callback) { const histogramSql = this._buildQueryTpl({ - _override: override, - _column: this._columnType === 'date' ? columnCastTpl({ column: this.column }) : this.column, - _isFloatColumn: this._columnType === 'float', - _query: this.query, - _start: this._getBinStart(override), - _end: this._getBinEnd(override), - _minBins: BIN_MIN_NUMBER, - _maxBins: BIN_MAX_NUMBER, + override: override, + column: this._columnType === 'date' ? columnCastTpl({ column: this.column }) : this.column, + isFloatColumn: this._columnType === 'float', + query: this.query, + start: this._getBinStart(override), + end: this._getBinEnd(override), + minBins: BIN_MIN_NUMBER, + maxBins: BIN_MAX_NUMBER, }); debug(histogramSql); @@ -186,13 +186,13 @@ module.exports = class NumericHistogram extends BaseHistogram { return ` WITH ${filteredQueryTpl(ctx)}, - ${this._hasOverridenRange(ctx._override) ? overrideBasicsQueryTpl(ctx) : basicsQueryTpl(ctx)}, - ${this._hasOverridenBins(ctx._override) ? + ${this._hasOverridenRange(ctx.override) ? overrideBasicsQueryTpl(ctx) : basicsQueryTpl(ctx)}, + ${this._hasOverridenBins(ctx.override) ? overrideBinsQueryTpl(ctx) : `${iqrQueryTpl(ctx)}, ${binsQueryTpl(ctx)}` }, ${nullsQueryTpl(ctx)} - ${ctx._isFloatColumn ? `,${infinitiesQueryTpl(ctx)}, ${nansQueryTpl(ctx)}` : ''} + ${ctx.isFloatColumn ? `,${infinitiesQueryTpl(ctx)}, ${nansQueryTpl(ctx)}` : ''} ${histogramQueryTpl(ctx)} `; } From 6fd3388fa2de1ac9fd855ea102146fe9636f8969 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Wed, 13 Sep 2017 18:38:54 +0200 Subject: [PATCH 092/159] Replace dot templates by ES6 string templates --- lib/cartodb/models/dataview/aggregation.js | 252 ++++++++++++--------- 1 file changed, 149 insertions(+), 103 deletions(-) diff --git a/lib/cartodb/models/dataview/aggregation.js b/lib/cartodb/models/dataview/aggregation.js index 77f37723..1a4fc3ed 100644 --- a/lib/cartodb/models/dataview/aggregation.js +++ b/lib/cartodb/models/dataview/aggregation.js @@ -2,90 +2,134 @@ var _ = require('underscore'); var BaseWidget = require('./base'); var debug = require('debug')('windshaft:widget:aggregation'); -var dot = require('dot'); -dot.templateSettings.strip = false; +const filteredQueryTpl = ctx => ` + filtered_source AS ( + SELECT * + FROM (${ctx._query}) _cdb_filtered_source + ${ctx._aggregationColumn && ctx._isFloatColumn ? ` + WHERE + ${ctx._aggregationColumn} != 'infinity'::float + AND + ${ctx._aggregationColumn} != '-infinity'::float + AND + ${ctx._aggregationColumn} != 'NaN'::float` : + '' + } + ) +`; -var filteredQueryTpl = dot.template([ - 'filtered_source AS (', - ' SELECT *', - ' FROM ({{=it._query}}) _cdb_filtered_source', - ' {{?it._aggregationColumn && it._isFloatColumn}}WHERE', - ' {{=it._aggregationColumn}} != \'infinity\'::float', - ' AND', - ' {{=it._aggregationColumn}} != \'-infinity\'::float', - ' AND', - ' {{=it._aggregationColumn}} != \'NaN\'::float{{?}}', - ')' -].join(' \n')); +const summaryQueryTpl = ctx => ` + summary AS ( + SELECT + count(1) AS count, + sum(CASE WHEN ${ctx._column} IS NULL THEN 1 ELSE 0 END) AS nulls_count + ${ctx._isFloatColumn ? `, + sum( + CASE + WHEN ${ctx._aggregationColumn} = 'infinity'::float OR ${ctx._aggregationColumn} = '-infinity'::float + THEN 1 + ELSE 0 + END + ) AS infinities_count, + sum(CASE WHEN ${ctx._aggregationColumn} = 'NaN'::float THEN 1 ELSE 0 END) AS nans_count` : + '' + } + FROM (${ctx._query}) _cdb_aggregation_nulls + ) +`; -var summaryQueryTpl = dot.template([ - 'summary AS (', - ' SELECT', - ' count(1) AS count,', - ' sum(CASE WHEN {{=it._column}} IS NULL THEN 1 ELSE 0 END) AS nulls_count', - ' {{?it._isFloatColumn}},sum(', - ' CASE', - ' WHEN {{=it._aggregationColumn}} = \'infinity\'::float OR {{=it._aggregationColumn}} = \'-infinity\'::float', - ' THEN 1', - ' ELSE 0', - ' END', - ' ) AS infinities_count,', - ' sum(CASE WHEN {{=it._aggregationColumn}} = \'NaN\'::float THEN 1 ELSE 0 END) AS nans_count{{?}}', - ' FROM ({{=it._query}}) _cdb_aggregation_nulls', - ')' -].join('\n')); +const rankedCategoriesQueryTpl = ctx => ` + categories AS( + SELECT + ${ctx._column} AS category, + ${ctx._aggregation} AS value, + row_number() OVER (ORDER BY ${ctx._aggregation} desc) as rank + FROM filtered_source + ${ctx._aggregationColumn !== null ? `WHERE ${ctx._aggregationColumn} IS NOT NULL` : ''} + GROUP BY ${ctx._column} + ORDER BY 2 DESC + ) +`; -var rankedCategoriesQueryTpl = dot.template([ - 'categories AS(', - ' SELECT {{=it._column}} AS category, {{=it._aggregation}} AS value,', - ' row_number() OVER (ORDER BY {{=it._aggregation}} desc) as rank', - ' FROM filtered_source', - ' {{?it._aggregationColumn!==null}}WHERE {{=it._aggregationColumn}} IS NOT NULL{{?}}', - ' GROUP BY {{=it._column}}', - ' ORDER BY 2 DESC', - ')' -].join('\n')); +const categoriesSummaryMinMaxQueryTpl = () => ` + categories_summary_min_max AS( + SELECT + max(value) max_val, + min(value) min_val + FROM categories + ) +`; -var categoriesSummaryMinMaxQueryTpl = dot.template([ - 'categories_summary_min_max AS(', - ' SELECT max(value) max_val, min(value) min_val', - ' FROM categories', - ')' -].join('\n')); +const categoriesSummaryCountQueryTpl = ctx => ` + categories_summary_count AS( + SELECT count(1) AS categories_count + FROM ( + SELECT ${ctx._column} AS category + FROM (${ctx._query}) _cdb_categories + GROUP BY ${ctx._column} + ) _cdb_categories_count + ) +`; -var categoriesSummaryCountQueryTpl = dot.template([ - 'categories_summary_count AS(', - ' SELECT count(1) AS categories_count', - ' FROM (', - ' SELECT {{=it._column}} AS category', - ' FROM ({{=it._query}}) _cdb_categories', - ' GROUP BY {{=it._column}}', - ' ) _cdb_categories_count', - ')' -].join('\n')); +const specialNumericValuesColumns = () => `, nans_count, infinities_count`; -var rankedAggregationQueryTpl = dot.template([ - 'SELECT CAST(category AS text), value, false as agg, nulls_count, min_val, max_val,', - ' count, categories_count{{?it._isFloatColumn}}, nans_count, infinities_count{{?}}', - ' FROM categories, summary, categories_summary_min_max, categories_summary_count', - ' WHERE rank < {{=it._limit}}', - 'UNION ALL', - 'SELECT \'Other\' category, {{=it._aggregationFn}}(value) as value, true as agg, nulls_count,', - ' min_val, max_val, count, categories_count{{?it._isFloatColumn}}, nans_count, infinities_count{{?}}', - ' FROM categories, summary, categories_summary_min_max, categories_summary_count', - ' WHERE rank >= {{=it._limit}}', - 'GROUP BY nulls_count, min_val, max_val, count,', - ' categories_count{{?it._isFloatColumn}}, nans_count, infinities_count{{?}}' -].join('\n')); +const rankedAggregationQueryTpl = ctx => ` + SELECT + CAST(category AS text), + value, + false as agg, + nulls_count, + min_val, + max_val, + count, + categories_count + ${ctx._isFloatColumn ? `${specialNumericValuesColumns(ctx)}` : '' } + FROM categories, summary, categories_summary_min_max, categories_summary_count + WHERE rank < ${ctx._limit} + UNION ALL + SELECT + 'Other' category, + ${ctx._aggregationFn}(value) as value, + true as agg, + nulls_count, + min_val, + max_val, + count, + categories_count + ${ctx._isFloatColumn ? `${specialNumericValuesColumns(ctx)}` : '' } + FROM categories, summary, categories_summary_min_max, categories_summary_count + WHERE rank >= ${ctx._limit} + GROUP BY + nulls_count, + min_val, + max_val, + count, + categories_count + ${ctx._isFloatColumn ? `${specialNumericValuesColumns(ctx)}` : '' } +`; -var aggregationQueryTpl = dot.template([ - 'SELECT CAST({{=it._column}} AS text) AS category, {{=it._aggregation}} AS value, false as agg,', - ' nulls_count, min_val, max_val, count, categories_count{{?it._isFloatColumn}}, nans_count, infinities_count{{?}}', - 'FROM ({{=it._query}}) _cdb_aggregation_all, summary, categories_summary_min_max, categories_summary_count', - 'GROUP BY category, nulls_count, min_val, max_val, count,', - ' categories_count{{?it._isFloatColumn}}, nans_count, infinities_count{{?}}', - 'ORDER BY value DESC' -].join('\n')); +const aggregationQueryTpl = ctx => ` + SELECT + CAST(${ctx._column} AS text) AS category, + ${ctx._aggregation} AS value, + false as agg, + nulls_count, + min_val, + max_val, + count, + categories_count + ${ctx._isFloatColumn ? `${specialNumericValuesColumns(ctx)}` : '' } + FROM (${ctx._query}) _cdb_aggregation_all, summary, categories_summary_min_max, categories_summary_count + GROUP BY + category, + nulls_count, + min_val, + max_val, + count, + categories_count + ${ctx._isFloatColumn ? `${specialNumericValuesColumns(ctx)}` : '' } + ORDER BY value DESC +`; var CATEGORIES_LIMIT = 6; @@ -242,7 +286,8 @@ Aggregation.prototype.getCategoriesCTESql = function(query, column, aggregation, ].join('\n'); }; -var aggregationFnQueryTpl = dot.template('{{=it._aggregationFn}}({{=it._aggregationColumn}})'); +const aggregationFnQueryTpl = ctx => `${ctx._aggregationFn}(${ctx._aggregationColumn})`; + Aggregation.prototype.getAggregationSql = function() { return aggregationFnQueryTpl({ _aggregationFn: this.aggregation, @@ -290,32 +335,33 @@ Aggregation.prototype.format = function(result) { }; }; -var filterCategoriesQueryTpl = dot.template([ - 'SELECT {{=it._column}} AS category, {{=it._value}} AS value', - 'FROM ({{=it._query}}) _cdb_aggregation_search', - 'WHERE CAST({{=it._column}} as text) ILIKE {{=it._userQuery}}', - 'GROUP BY {{=it._column}}' -].join('\n')); - -var searchQueryTpl = dot.template([ - 'WITH', - 'search_unfiltered AS (', - ' {{=it._searchUnfiltered}}', - '),', - 'search_filtered AS (', - ' {{=it._searchFiltered}}', - '),', - 'search_union AS (', - ' SELECT * FROM search_unfiltered', - ' UNION ALL', - ' SELECT * FROM search_filtered', - ')', - 'SELECT category, sum(value) AS value', - 'FROM search_union', - 'GROUP BY category', - 'ORDER BY value desc' -].join('\n')); +const filterCategoriesQueryTpl = ctx => ` + SELECT + ${ctx._column} AS category, + ${ctx._value} AS value + FROM (${ctx._query}) _cdb_aggregation_search + WHERE CAST(${ctx._column} as text) ILIKE ${ctx._userQuery} + GROUP BY ${ctx._column} +`; +const searchQueryTpl = ctx => ` + WITH + search_unfiltered AS ( + ${ctx._searchUnfiltered} + ), + search_filtered AS ( + ${ctx._searchFiltered} + ), + search_union AS ( + SELECT * FROM search_unfiltered + UNION ALL + SELECT * FROM search_filtered + ) + SELECT category, sum(value) AS value + FROM search_union + GROUP BY category + ORDER BY value desc +`; Aggregation.prototype.search = function(psql, userQuery, callback) { var self = this; From 33e8657e35e090dbeedff19e06ba39bed746f06a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Wed, 13 Sep 2017 18:40:09 +0200 Subject: [PATCH 093/159] Declare constants with const keyword --- lib/cartodb/models/dataview/aggregation.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/cartodb/models/dataview/aggregation.js b/lib/cartodb/models/dataview/aggregation.js index 1a4fc3ed..8ba8621f 100644 --- a/lib/cartodb/models/dataview/aggregation.js +++ b/lib/cartodb/models/dataview/aggregation.js @@ -131,9 +131,9 @@ const aggregationQueryTpl = ctx => ` ORDER BY value DESC `; -var CATEGORIES_LIMIT = 6; +const CATEGORIES_LIMIT = 6; -var VALID_OPERATIONS = { +const VALID_OPERATIONS = { count: [], sum: ['aggregationColumn'], avg: ['aggregationColumn'], @@ -141,7 +141,7 @@ var VALID_OPERATIONS = { max: ['aggregationColumn'] }; -var TYPE = 'aggregation'; +const TYPE = 'aggregation'; /** { From 306df5be5a2d665ddf6500b65987c3712929de9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Wed, 13 Sep 2017 19:16:08 +0200 Subject: [PATCH 094/159] Replace underscore's function by ES6 equivalents --- lib/cartodb/models/dataview/aggregation.js | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/lib/cartodb/models/dataview/aggregation.js b/lib/cartodb/models/dataview/aggregation.js index 8ba8621f..5cddac18 100644 --- a/lib/cartodb/models/dataview/aggregation.js +++ b/lib/cartodb/models/dataview/aggregation.js @@ -1,4 +1,3 @@ -var _ = require('underscore'); var BaseWidget = require('./base'); var debug = require('debug')('windshaft:widget:aggregation'); @@ -153,11 +152,13 @@ const TYPE = 'aggregation'; } */ function Aggregation(query, options, queries) { - if (!_.isString(options.column)) { + options = options || {}; + + if (typeof options.column !== 'string') { throw new Error('Aggregation expects `column` in widget options'); } - if (!_.isString(options.aggregation)) { + if (typeof options.aggregation !== 'string') { throw new Error('Aggregation expects `aggregation` operation in widget options'); } @@ -166,7 +167,8 @@ function Aggregation(query, options, queries) { } var requiredOptions = VALID_OPERATIONS[options.aggregation]; - var missingOptions = _.difference(requiredOptions, Object.keys(options)); + var missingOptions = requiredOptions.filter(requiredOption => !options.hasOwnProperty(requiredOption)); + if (missingOptions.length > 0) { throw new Error( "Aggregation '" + options.aggregation + "' is missing some options: " + missingOptions.join(',') @@ -315,11 +317,7 @@ Aggregation.prototype.format = function(result) { minValue = firstRow.min_val; maxValue = firstRow.max_val; categoriesCount = firstRow.categories_count; - - result.rows.forEach(function(row) { - categories.push(_.omit(row, 'count', 'nulls_count', 'min_val', - 'max_val', 'categories_count', 'nans_count', 'infinities_count')); - }); + categories = result.rows.map(({ category, value, agg }) => ({ category, value, agg })); } return { From 4518b7cb6e7436d18acea77c9dcce8c98d49e59a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Wed, 13 Sep 2017 19:17:16 +0200 Subject: [PATCH 095/159] Declare requirement with const keyword --- lib/cartodb/models/dataview/aggregation.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/cartodb/models/dataview/aggregation.js b/lib/cartodb/models/dataview/aggregation.js index 5cddac18..e8b431b4 100644 --- a/lib/cartodb/models/dataview/aggregation.js +++ b/lib/cartodb/models/dataview/aggregation.js @@ -1,5 +1,5 @@ -var BaseWidget = require('./base'); -var debug = require('debug')('windshaft:widget:aggregation'); +const BaseWidget = require('./base'); +const debug = require('debug')('windshaft:widget:aggregation'); const filteredQueryTpl = ctx => ` filtered_source AS ( From ee471184b91f59e04653a0c0ea3c4ca024135432 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Wed, 13 Sep 2017 19:19:25 +0200 Subject: [PATCH 096/159] Use default values for input params --- lib/cartodb/models/dataview/aggregation.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/cartodb/models/dataview/aggregation.js b/lib/cartodb/models/dataview/aggregation.js index e8b431b4..b2cfd212 100644 --- a/lib/cartodb/models/dataview/aggregation.js +++ b/lib/cartodb/models/dataview/aggregation.js @@ -151,9 +151,7 @@ const TYPE = 'aggregation'; } } */ -function Aggregation(query, options, queries) { - options = options || {}; - +function Aggregation(query, options = {}, queries = {}) { if (typeof options.column !== 'string') { throw new Error('Aggregation expects `column` in widget options'); } From 4d1a53c20f1abfef89cd554b2e4f4a13d7b76685 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Wed, 13 Sep 2017 19:27:25 +0200 Subject: [PATCH 097/159] Use string templates to build error message --- lib/cartodb/models/dataview/aggregation.js | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/lib/cartodb/models/dataview/aggregation.js b/lib/cartodb/models/dataview/aggregation.js index b2cfd212..71394211 100644 --- a/lib/cartodb/models/dataview/aggregation.js +++ b/lib/cartodb/models/dataview/aggregation.js @@ -153,24 +153,22 @@ const TYPE = 'aggregation'; */ function Aggregation(query, options = {}, queries = {}) { if (typeof options.column !== 'string') { - throw new Error('Aggregation expects `column` in widget options'); + throw new Error(`Aggregation expects 'column' in widget options`); } if (typeof options.aggregation !== 'string') { - throw new Error('Aggregation expects `aggregation` operation in widget options'); + throw new Error(`Aggregation expects 'aggregation' operation in widget options`); } if (!VALID_OPERATIONS[options.aggregation]) { - throw new Error("Aggregation does not support '" + options.aggregation + "' operation"); + throw new Error(`Aggregation does not support '${options.aggregation}' operation`); } var requiredOptions = VALID_OPERATIONS[options.aggregation]; var missingOptions = requiredOptions.filter(requiredOption => !options.hasOwnProperty(requiredOption)); if (missingOptions.length > 0) { - throw new Error( - "Aggregation '" + options.aggregation + "' is missing some options: " + missingOptions.join(',') - ); + throw new Error(`Aggregation '${options.aggregation}' is missing some options: ${missingOptions.join(',')}`); } BaseWidget.apply(this); @@ -305,7 +303,6 @@ Aggregation.prototype.format = function(result) { var maxValue = 0; var categoriesCount = 0; - if (result.rows.length) { var firstRow = result.rows[0]; count = firstRow.count; From 75645e2d7af3dcc2b9ca79a6197381c40cad156d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Wed, 13 Sep 2017 19:34:09 +0200 Subject: [PATCH 098/159] Use string templates to build categories CTE sql --- lib/cartodb/models/dataview/aggregation.js | 28 ++++++++++------------ 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/lib/cartodb/models/dataview/aggregation.js b/lib/cartodb/models/dataview/aggregation.js index 71394211..43dff8c0 100644 --- a/lib/cartodb/models/dataview/aggregation.js +++ b/lib/cartodb/models/dataview/aggregation.js @@ -251,37 +251,35 @@ Aggregation.prototype.sql = function(psql, override, callback) { }; Aggregation.prototype.getCategoriesCTESql = function(query, column, aggregation, aggregationColumn, isFloatColumn) { - return [ - "WITH", - [ - filteredQueryTpl({ + return ` + WITH + ${filteredQueryTpl({ _isFloatColumn: isFloatColumn, _query: this.query, _column: this.column, _aggregationColumn: aggregation !== 'count' ? aggregationColumn : null - }), - summaryQueryTpl({ + })}, + ${summaryQueryTpl({ _isFloatColumn: isFloatColumn, _query: query, _column: column, _aggregationColumn: aggregation !== 'count' ? aggregationColumn : null - }), - rankedCategoriesQueryTpl({ + })}, + ${rankedCategoriesQueryTpl({ _query: query, _column: column, _aggregation: this.getAggregationSql(), _aggregationColumn: aggregation !== 'count' ? aggregationColumn : null - }), - categoriesSummaryMinMaxQueryTpl({ + })}, + ${categoriesSummaryMinMaxQueryTpl({ _query: query, _column: column - }), - categoriesSummaryCountQueryTpl({ + })}, + ${categoriesSummaryCountQueryTpl({ _query: query, _column: column - }) - ].join(',\n') - ].join('\n'); + })} + `; }; const aggregationFnQueryTpl = ctx => `${ctx._aggregationFn}(${ctx._aggregationColumn})`; From 2f4f719f558f7e41701ffa713c9fc77e1c32cdbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Wed, 13 Sep 2017 19:42:25 +0200 Subject: [PATCH 099/159] Use object properties in class method instead of passing them as parameters --- lib/cartodb/models/dataview/aggregation.js | 28 +++++++++++----------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/cartodb/models/dataview/aggregation.js b/lib/cartodb/models/dataview/aggregation.js index 43dff8c0..ed0261ae 100644 --- a/lib/cartodb/models/dataview/aggregation.js +++ b/lib/cartodb/models/dataview/aggregation.js @@ -250,34 +250,34 @@ Aggregation.prototype.sql = function(psql, override, callback) { return callback(null, aggregationSql); }; -Aggregation.prototype.getCategoriesCTESql = function(query, column, aggregation, aggregationColumn, isFloatColumn) { +Aggregation.prototype.getCategoriesCTESql = function() { return ` WITH ${filteredQueryTpl({ - _isFloatColumn: isFloatColumn, + _isFloatColumn: this._isFloatColumn, _query: this.query, _column: this.column, - _aggregationColumn: aggregation !== 'count' ? aggregationColumn : null + _aggregationColumn: this.aggregation !== 'count' ? this.aggregationColumn : null })}, ${summaryQueryTpl({ - _isFloatColumn: isFloatColumn, - _query: query, - _column: column, - _aggregationColumn: aggregation !== 'count' ? aggregationColumn : null + _isFloatColumn: this._isFloatColumn, + _query: this.query, + _column: this.column, + _aggregationColumn: this.aggregation !== 'count' ? this.aggregationColumn : null })}, ${rankedCategoriesQueryTpl({ - _query: query, - _column: column, + _query: this.query, + _column: this.column, _aggregation: this.getAggregationSql(), - _aggregationColumn: aggregation !== 'count' ? aggregationColumn : null + _aggregationColumn: this.aggregation !== 'count' ? this.aggregationColumn : null })}, ${categoriesSummaryMinMaxQueryTpl({ - _query: query, - _column: column + _query: this.query, + _column: this.column })}, ${categoriesSummaryCountQueryTpl({ - _query: query, - _column: column + _query: this.query, + _column: this.column })} `; }; From a434015d5b3e020175dc38e248aece34c13b1484 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Thu, 14 Sep 2017 11:27:03 +0200 Subject: [PATCH 100/159] Move categories CTE template out of aggregation class --- lib/cartodb/models/dataview/aggregation.js | 111 ++++++++++----------- 1 file changed, 55 insertions(+), 56 deletions(-) diff --git a/lib/cartodb/models/dataview/aggregation.js b/lib/cartodb/models/dataview/aggregation.js index ed0261ae..627eb73a 100644 --- a/lib/cartodb/models/dataview/aggregation.js +++ b/lib/cartodb/models/dataview/aggregation.js @@ -130,6 +130,41 @@ const aggregationQueryTpl = ctx => ` ORDER BY value DESC `; +const categoriesCTESqlTpl = ctx => ` + WITH + ${filteredQueryTpl({ + _isFloatColumn: ctx.isFloatColumn, + _query: ctx.query, + _column: ctx.column, + _aggregationColumn: ctx.aggregation !== 'count' ? ctx.aggregationColumn : null + })}, + ${summaryQueryTpl({ + _isFloatColumn: ctx.isFloatColumn, + _query: ctx.query, + _column: ctx.column, + _aggregationColumn: ctx.aggregation !== 'count' ? ctx.aggregationColumn : null + })}, + ${rankedCategoriesQueryTpl({ + _query: ctx.query, + _column: ctx.column, + _aggregation: aggregationFnQueryTpl({ + aggregation: ctx.aggregation, + aggregationColumn: ctx.aggregationColumn || 1 + }), + _aggregationColumn: ctx.aggregation !== 'count' ? ctx.aggregationColumn : null + })}, + ${categoriesSummaryMinMaxQueryTpl({ + _query: ctx.query, + _column: ctx.column + })}, + ${categoriesSummaryCountQueryTpl({ + _query: ctx.query, + _column: ctx.column + })} +`; + +const aggregationFnQueryTpl = ctx => `${ctx.aggregation}(${ctx.aggregationColumn})`; + const CATEGORIES_LIMIT = 6; const VALID_OPERATIONS = { @@ -209,32 +244,37 @@ Aggregation.prototype.sql = function(psql, override, callback) { var aggregationSql; + + if (!!override.ownFilter) { aggregationSql = [ - this.getCategoriesCTESql( - _query, - this.column, - this.aggregation, - this.aggregationColumn, - this._isFloatColumn - ), + categoriesCTESqlTpl({ + query: _query, + column: this.column, + aggregation: this.aggregation, + aggregationColumn: this.aggregationColumn, + isFloatColumn: this._isFloatColumn + }), aggregationQueryTpl({ _isFloatColumn: this._isFloatColumn, _query: _query, _column: this.column, - _aggregation: this.getAggregationSql(), + _aggregation: aggregationFnQueryTpl({ + aggregation: this.aggregation, + aggregationColumn: this.aggregationColumn || 1 + }), _limit: CATEGORIES_LIMIT }) ].join('\n'); } else { aggregationSql = [ - this.getCategoriesCTESql( - _query, - this.column, - this.aggregation, - this.aggregationColumn, - this._isFloatColumn - ), + categoriesCTESqlTpl({ + query: _query, + column: this.column, + aggregation: this.aggregation, + aggregationColumn: this.aggregationColumn, + isFloatColumn: this._isFloatColumn + }), rankedAggregationQueryTpl({ _isFloatColumn: this._isFloatColumn, _query: _query, @@ -250,47 +290,6 @@ Aggregation.prototype.sql = function(psql, override, callback) { return callback(null, aggregationSql); }; -Aggregation.prototype.getCategoriesCTESql = function() { - return ` - WITH - ${filteredQueryTpl({ - _isFloatColumn: this._isFloatColumn, - _query: this.query, - _column: this.column, - _aggregationColumn: this.aggregation !== 'count' ? this.aggregationColumn : null - })}, - ${summaryQueryTpl({ - _isFloatColumn: this._isFloatColumn, - _query: this.query, - _column: this.column, - _aggregationColumn: this.aggregation !== 'count' ? this.aggregationColumn : null - })}, - ${rankedCategoriesQueryTpl({ - _query: this.query, - _column: this.column, - _aggregation: this.getAggregationSql(), - _aggregationColumn: this.aggregation !== 'count' ? this.aggregationColumn : null - })}, - ${categoriesSummaryMinMaxQueryTpl({ - _query: this.query, - _column: this.column - })}, - ${categoriesSummaryCountQueryTpl({ - _query: this.query, - _column: this.column - })} - `; -}; - -const aggregationFnQueryTpl = ctx => `${ctx._aggregationFn}(${ctx._aggregationColumn})`; - -Aggregation.prototype.getAggregationSql = function() { - return aggregationFnQueryTpl({ - _aggregationFn: this.aggregation, - _aggregationColumn: this.aggregationColumn || 1 - }); -}; - Aggregation.prototype.format = function(result) { var categories = []; var count = 0; From 3682740f08ffd9fd43afd0df92003e08010c021f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Thu, 14 Sep 2017 12:01:41 +0200 Subject: [PATCH 101/159] Build aggregation query with string templates avoiding to join all inner templates --- lib/cartodb/models/dataview/aggregation.js | 67 ++++++++-------------- 1 file changed, 25 insertions(+), 42 deletions(-) diff --git a/lib/cartodb/models/dataview/aggregation.js b/lib/cartodb/models/dataview/aggregation.js index 627eb73a..2ba36ede 100644 --- a/lib/cartodb/models/dataview/aggregation.js +++ b/lib/cartodb/models/dataview/aggregation.js @@ -240,50 +240,33 @@ Aggregation.prototype.sql = function(psql, override, callback) { return null; } - var _query = this.query; - - var aggregationSql; - - - - if (!!override.ownFilter) { - aggregationSql = [ - categoriesCTESqlTpl({ - query: _query, - column: this.column, + var aggregationSql = ` + ${categoriesCTESqlTpl({ + query: this.query, + column: this.column, + aggregation: this.aggregation, + aggregationColumn: this.aggregationColumn, + isFloatColumn: this._isFloatColumn + })} + ${!!override.ownFilter ? + `${aggregationQueryTpl({ + _isFloatColumn: this._isFloatColumn, + _query: this.query, + _column: this.column, + _aggregation: aggregationFnQueryTpl({ aggregation: this.aggregation, - aggregationColumn: this.aggregationColumn, - isFloatColumn: this._isFloatColumn + aggregationColumn: this.aggregationColumn || 1 }), - aggregationQueryTpl({ - _isFloatColumn: this._isFloatColumn, - _query: _query, - _column: this.column, - _aggregation: aggregationFnQueryTpl({ - aggregation: this.aggregation, - aggregationColumn: this.aggregationColumn || 1 - }), - _limit: CATEGORIES_LIMIT - }) - ].join('\n'); - } else { - aggregationSql = [ - categoriesCTESqlTpl({ - query: _query, - column: this.column, - aggregation: this.aggregation, - aggregationColumn: this.aggregationColumn, - isFloatColumn: this._isFloatColumn - }), - rankedAggregationQueryTpl({ - _isFloatColumn: this._isFloatColumn, - _query: _query, - _column: this.column, - _aggregationFn: this.aggregation !== 'count' ? this.aggregation : 'sum', - _limit: CATEGORIES_LIMIT - }) - ].join('\n'); - } + _limit: CATEGORIES_LIMIT + })}` : + `${rankedAggregationQueryTpl({ + _isFloatColumn: this._isFloatColumn, + _query: this.query, + _column: this.column, + _aggregationFn: this.aggregation !== 'count' ? this.aggregation : 'sum', + _limit: CATEGORIES_LIMIT + })}`} + `; debug(aggregationSql); From 65dca454f497abf868e05876d2c9f059b27948f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Thu, 14 Sep 2017 12:18:03 +0200 Subject: [PATCH 102/159] Move aggragation query to its own query template --- lib/cartodb/models/dataview/aggregation.js | 65 +++++++++++++--------- 1 file changed, 38 insertions(+), 27 deletions(-) diff --git a/lib/cartodb/models/dataview/aggregation.js b/lib/cartodb/models/dataview/aggregation.js index 2ba36ede..b22d2345 100644 --- a/lib/cartodb/models/dataview/aggregation.js +++ b/lib/cartodb/models/dataview/aggregation.js @@ -165,6 +165,36 @@ const categoriesCTESqlTpl = ctx => ` const aggregationFnQueryTpl = ctx => `${ctx.aggregation}(${ctx.aggregationColumn})`; +const aggregationSqlTpl = ctx => ` + ${categoriesCTESqlTpl({ + query: ctx.query, + column: ctx.column, + aggregation: ctx.aggregation, + aggregationColumn: ctx.aggregationColumn, + isFloatColumn: ctx.isFloatColumn + })} + ${!!ctx.override.ownFilter ? + `${aggregationQueryTpl({ + _isFloatColumn: ctx.isFloatColumn, + _query: ctx.query, + _column: ctx.column, + _aggregation: aggregationFnQueryTpl({ + aggregation: ctx.aggregation, + aggregationColumn: ctx.aggregationColumn || 1 + }), + _limit: CATEGORIES_LIMIT + }) + }` : + `${rankedAggregationQueryTpl({ + _isFloatColumn: ctx.isFloatColumn, + _query: ctx.query, + _column: ctx.column, + _aggregationFn: ctx.aggregation !== 'count' ? ctx.aggregation : 'sum', + _limit: CATEGORIES_LIMIT + })} + `} +`; + const CATEGORIES_LIMIT = 6; const VALID_OPERATIONS = { @@ -240,33 +270,14 @@ Aggregation.prototype.sql = function(psql, override, callback) { return null; } - var aggregationSql = ` - ${categoriesCTESqlTpl({ - query: this.query, - column: this.column, - aggregation: this.aggregation, - aggregationColumn: this.aggregationColumn, - isFloatColumn: this._isFloatColumn - })} - ${!!override.ownFilter ? - `${aggregationQueryTpl({ - _isFloatColumn: this._isFloatColumn, - _query: this.query, - _column: this.column, - _aggregation: aggregationFnQueryTpl({ - aggregation: this.aggregation, - aggregationColumn: this.aggregationColumn || 1 - }), - _limit: CATEGORIES_LIMIT - })}` : - `${rankedAggregationQueryTpl({ - _isFloatColumn: this._isFloatColumn, - _query: this.query, - _column: this.column, - _aggregationFn: this.aggregation !== 'count' ? this.aggregation : 'sum', - _limit: CATEGORIES_LIMIT - })}`} - `; + var aggregationSql = aggregationSqlTpl({ + override: override, + query: this.query, + column: this.column, + aggregation: this.aggregation, + aggregationColumn: this.aggregationColumn, + isFloatColumn: this._isFloatColumn + }); debug(aggregationSql); From b1508af007833d88459424efb1f1a445bff0d912 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Thu, 14 Sep 2017 13:14:12 +0200 Subject: [PATCH 103/159] Remove prefix to context's props in order to share it throught all templates avoiding to duplicate passing custom params again and again --- lib/cartodb/models/dataview/aggregation.js | 131 +++++++++------------ 1 file changed, 54 insertions(+), 77 deletions(-) diff --git a/lib/cartodb/models/dataview/aggregation.js b/lib/cartodb/models/dataview/aggregation.js index b22d2345..be54ec7d 100644 --- a/lib/cartodb/models/dataview/aggregation.js +++ b/lib/cartodb/models/dataview/aggregation.js @@ -4,14 +4,14 @@ const debug = require('debug')('windshaft:widget:aggregation'); const filteredQueryTpl = ctx => ` filtered_source AS ( SELECT * - FROM (${ctx._query}) _cdb_filtered_source - ${ctx._aggregationColumn && ctx._isFloatColumn ? ` + FROM (${ctx.query}) _cdb_filtered_source + ${ctx.aggregationColumn && ctx.isFloatColumn ? ` WHERE - ${ctx._aggregationColumn} != 'infinity'::float + ${ctx.aggregationColumn} != 'infinity'::float AND - ${ctx._aggregationColumn} != '-infinity'::float + ${ctx.aggregationColumn} != '-infinity'::float AND - ${ctx._aggregationColumn} != 'NaN'::float` : + ${ctx.aggregationColumn} != 'NaN'::float` : '' } ) @@ -21,31 +21,31 @@ const summaryQueryTpl = ctx => ` summary AS ( SELECT count(1) AS count, - sum(CASE WHEN ${ctx._column} IS NULL THEN 1 ELSE 0 END) AS nulls_count - ${ctx._isFloatColumn ? `, + sum(CASE WHEN ${ctx.column} IS NULL THEN 1 ELSE 0 END) AS nulls_count + ${ctx.isFloatColumn ? `, sum( CASE - WHEN ${ctx._aggregationColumn} = 'infinity'::float OR ${ctx._aggregationColumn} = '-infinity'::float + WHEN ${ctx.aggregationColumn} = 'infinity'::float OR ${ctx.aggregationColumn} = '-infinity'::float THEN 1 ELSE 0 END ) AS infinities_count, - sum(CASE WHEN ${ctx._aggregationColumn} = 'NaN'::float THEN 1 ELSE 0 END) AS nans_count` : + sum(CASE WHEN ${ctx.aggregationColumn} = 'NaN'::float THEN 1 ELSE 0 END) AS nans_count` : '' } - FROM (${ctx._query}) _cdb_aggregation_nulls + FROM (${ctx.query}) _cdb_aggregation_nulls ) `; const rankedCategoriesQueryTpl = ctx => ` categories AS( SELECT - ${ctx._column} AS category, - ${ctx._aggregation} AS value, - row_number() OVER (ORDER BY ${ctx._aggregation} desc) as rank + ${ctx.column} AS category, + ${ctx.aggregationFn} AS value, + row_number() OVER (ORDER BY ${ctx.aggregationFn} desc) as rank FROM filtered_source - ${ctx._aggregationColumn !== null ? `WHERE ${ctx._aggregationColumn} IS NOT NULL` : ''} - GROUP BY ${ctx._column} + ${ctx.aggregationColumn !== null ? `WHERE ${ctx.aggregationColumn} IS NOT NULL` : ''} + GROUP BY ${ctx.column} ORDER BY 2 DESC ) `; @@ -63,9 +63,9 @@ const categoriesSummaryCountQueryTpl = ctx => ` categories_summary_count AS( SELECT count(1) AS categories_count FROM ( - SELECT ${ctx._column} AS category - FROM (${ctx._query}) _cdb_categories - GROUP BY ${ctx._column} + SELECT ${ctx.column} AS category + FROM (${ctx.query}) _cdb_categories + GROUP BY ${ctx.column} ) _cdb_categories_count ) `; @@ -82,43 +82,43 @@ const rankedAggregationQueryTpl = ctx => ` max_val, count, categories_count - ${ctx._isFloatColumn ? `${specialNumericValuesColumns(ctx)}` : '' } + ${ctx.isFloatColumn ? `${specialNumericValuesColumns(ctx)}` : '' } FROM categories, summary, categories_summary_min_max, categories_summary_count - WHERE rank < ${ctx._limit} + WHERE rank < ${ctx.limit} UNION ALL SELECT 'Other' category, - ${ctx._aggregationFn}(value) as value, + ${ctx.aggregation !== 'count' ? ctx.aggregation : 'sum'}(value) as value, true as agg, nulls_count, min_val, max_val, count, categories_count - ${ctx._isFloatColumn ? `${specialNumericValuesColumns(ctx)}` : '' } + ${ctx.isFloatColumn ? `${specialNumericValuesColumns(ctx)}` : '' } FROM categories, summary, categories_summary_min_max, categories_summary_count - WHERE rank >= ${ctx._limit} + WHERE rank >= ${ctx.limit} GROUP BY nulls_count, min_val, max_val, count, categories_count - ${ctx._isFloatColumn ? `${specialNumericValuesColumns(ctx)}` : '' } + ${ctx.isFloatColumn ? `${specialNumericValuesColumns(ctx)}` : '' } `; const aggregationQueryTpl = ctx => ` SELECT - CAST(${ctx._column} AS text) AS category, - ${ctx._aggregation} AS value, + CAST(${ctx.column} AS text) AS category, + ${ctx.aggregationFn} AS value, false as agg, nulls_count, min_val, max_val, count, categories_count - ${ctx._isFloatColumn ? `${specialNumericValuesColumns(ctx)}` : '' } - FROM (${ctx._query}) _cdb_aggregation_all, summary, categories_summary_min_max, categories_summary_count + ${ctx.isFloatColumn ? `${specialNumericValuesColumns(ctx)}` : '' } + FROM (${ctx.query}) _cdb_aggregation_all, summary, categories_summary_min_max, categories_summary_count GROUP BY category, nulls_count, @@ -126,73 +126,45 @@ const aggregationQueryTpl = ctx => ` max_val, count, categories_count - ${ctx._isFloatColumn ? `${specialNumericValuesColumns(ctx)}` : '' } + ${ctx.isFloatColumn ? `${specialNumericValuesColumns(ctx)}` : '' } ORDER BY value DESC `; const categoriesCTESqlTpl = ctx => ` WITH ${filteredQueryTpl({ - _isFloatColumn: ctx.isFloatColumn, - _query: ctx.query, - _column: ctx.column, - _aggregationColumn: ctx.aggregation !== 'count' ? ctx.aggregationColumn : null + isFloatColumn: ctx.isFloatColumn, + query: ctx.query, + column: ctx.column, + aggregationColumn: ctx.aggregationColumn })}, ${summaryQueryTpl({ - _isFloatColumn: ctx.isFloatColumn, - _query: ctx.query, - _column: ctx.column, - _aggregationColumn: ctx.aggregation !== 'count' ? ctx.aggregationColumn : null + isFloatColumn: ctx.isFloatColumn, + query: ctx.query, + column: ctx.column, + aggregationColumn: ctx.aggregationColumn })}, ${rankedCategoriesQueryTpl({ - _query: ctx.query, - _column: ctx.column, - _aggregation: aggregationFnQueryTpl({ - aggregation: ctx.aggregation, - aggregationColumn: ctx.aggregationColumn || 1 - }), - _aggregationColumn: ctx.aggregation !== 'count' ? ctx.aggregationColumn : null + query: ctx.query, + column: ctx.column, + aggregationFn: ctx.aggregationFn, + aggregationColumn: ctx.aggregationColumn })}, ${categoriesSummaryMinMaxQueryTpl({ - _query: ctx.query, - _column: ctx.column + query: ctx.query, + column: ctx.column })}, ${categoriesSummaryCountQueryTpl({ - _query: ctx.query, - _column: ctx.column + query: ctx.query, + column: ctx.column })} `; const aggregationFnQueryTpl = ctx => `${ctx.aggregation}(${ctx.aggregationColumn})`; const aggregationSqlTpl = ctx => ` - ${categoriesCTESqlTpl({ - query: ctx.query, - column: ctx.column, - aggregation: ctx.aggregation, - aggregationColumn: ctx.aggregationColumn, - isFloatColumn: ctx.isFloatColumn - })} - ${!!ctx.override.ownFilter ? - `${aggregationQueryTpl({ - _isFloatColumn: ctx.isFloatColumn, - _query: ctx.query, - _column: ctx.column, - _aggregation: aggregationFnQueryTpl({ - aggregation: ctx.aggregation, - aggregationColumn: ctx.aggregationColumn || 1 - }), - _limit: CATEGORIES_LIMIT - }) - }` : - `${rankedAggregationQueryTpl({ - _isFloatColumn: ctx.isFloatColumn, - _query: ctx.query, - _column: ctx.column, - _aggregationFn: ctx.aggregation !== 'count' ? ctx.aggregation : 'sum', - _limit: CATEGORIES_LIMIT - })} - `} + ${categoriesCTESqlTpl(ctx)} + ${!!ctx.override.ownFilter ? `${aggregationQueryTpl(ctx)}` : `${rankedAggregationQueryTpl(ctx)}`} `; const CATEGORIES_LIMIT = 6; @@ -275,8 +247,13 @@ Aggregation.prototype.sql = function(psql, override, callback) { query: this.query, column: this.column, aggregation: this.aggregation, - aggregationColumn: this.aggregationColumn, - isFloatColumn: this._isFloatColumn + aggregationColumn: this.aggregation !== 'count' ? this.aggregationColumn : null, + aggregationFn: aggregationFnQueryTpl({ + aggregation: this.aggregation, + aggregationColumn: this.aggregationColumn || 1 + }), + isFloatColumn: this._isFloatColumn, + limit: CATEGORIES_LIMIT }); debug(aggregationSql); From 132fce84c57f247d6ac30ebf92fc9ebb7d169021 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Thu, 14 Sep 2017 15:07:30 +0200 Subject: [PATCH 104/159] Remove string template in the middle --- lib/cartodb/models/dataview/aggregation.js | 41 +++++----------------- 1 file changed, 8 insertions(+), 33 deletions(-) diff --git a/lib/cartodb/models/dataview/aggregation.js b/lib/cartodb/models/dataview/aggregation.js index be54ec7d..6f87aba1 100644 --- a/lib/cartodb/models/dataview/aggregation.js +++ b/lib/cartodb/models/dataview/aggregation.js @@ -130,40 +130,15 @@ const aggregationQueryTpl = ctx => ` ORDER BY value DESC `; -const categoriesCTESqlTpl = ctx => ` - WITH - ${filteredQueryTpl({ - isFloatColumn: ctx.isFloatColumn, - query: ctx.query, - column: ctx.column, - aggregationColumn: ctx.aggregationColumn - })}, - ${summaryQueryTpl({ - isFloatColumn: ctx.isFloatColumn, - query: ctx.query, - column: ctx.column, - aggregationColumn: ctx.aggregationColumn - })}, - ${rankedCategoriesQueryTpl({ - query: ctx.query, - column: ctx.column, - aggregationFn: ctx.aggregationFn, - aggregationColumn: ctx.aggregationColumn - })}, - ${categoriesSummaryMinMaxQueryTpl({ - query: ctx.query, - column: ctx.column - })}, - ${categoriesSummaryCountQueryTpl({ - query: ctx.query, - column: ctx.column - })} -`; - const aggregationFnQueryTpl = ctx => `${ctx.aggregation}(${ctx.aggregationColumn})`; -const aggregationSqlTpl = ctx => ` - ${categoriesCTESqlTpl(ctx)} +const aggregationDataviewQueryTpl = ctx => ` + WITH + ${filteredQueryTpl(ctx)}, + ${summaryQueryTpl(ctx)}, + ${rankedCategoriesQueryTpl(ctx)}, + ${categoriesSummaryMinMaxQueryTpl(ctx)}, + ${categoriesSummaryCountQueryTpl(ctx)} ${!!ctx.override.ownFilter ? `${aggregationQueryTpl(ctx)}` : `${rankedAggregationQueryTpl(ctx)}`} `; @@ -242,7 +217,7 @@ Aggregation.prototype.sql = function(psql, override, callback) { return null; } - var aggregationSql = aggregationSqlTpl({ + var aggregationSql = aggregationDataviewQueryTpl({ override: override, query: this.query, column: this.column, From e2ab48bee2630050a8636776ebe069238c4fe398 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Thu, 14 Sep 2017 15:22:58 +0200 Subject: [PATCH 105/159] Remove prefix to context's properties --- lib/cartodb/models/dataview/aggregation.js | 40 ++++++++++++---------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/lib/cartodb/models/dataview/aggregation.js b/lib/cartodb/models/dataview/aggregation.js index 6f87aba1..575c7202 100644 --- a/lib/cartodb/models/dataview/aggregation.js +++ b/lib/cartodb/models/dataview/aggregation.js @@ -273,20 +273,20 @@ Aggregation.prototype.format = function(result) { const filterCategoriesQueryTpl = ctx => ` SELECT - ${ctx._column} AS category, - ${ctx._value} AS value - FROM (${ctx._query}) _cdb_aggregation_search - WHERE CAST(${ctx._column} as text) ILIKE ${ctx._userQuery} - GROUP BY ${ctx._column} + ${ctx.column} AS category, + ${ctx.value} AS value + FROM (${ctx.query}) _cdb_aggregation_search + WHERE CAST(${ctx.column} as text) ILIKE ${ctx.userQuery} + GROUP BY ${ctx.column} `; const searchQueryTpl = ctx => ` WITH search_unfiltered AS ( - ${ctx._searchUnfiltered} + ${ctx.searchUnfiltered} ), search_filtered AS ( - ${ctx._searchFiltered} + ${ctx.searchFiltered} ), search_union AS ( SELECT * FROM search_unfiltered @@ -302,26 +302,28 @@ const searchQueryTpl = ctx => ` Aggregation.prototype.search = function(psql, userQuery, callback) { var self = this; - var _userQuery = psql.escapeLiteral('%' + userQuery + '%'); - var _value = this.aggregation !== 'count' && this.aggregationColumn ? + var escapedUserQuery = psql.escapeLiteral('%' + userQuery + '%'); + var value = this.aggregation !== 'count' && this.aggregationColumn ? this.aggregation + '(' + this.aggregationColumn + ')' : 'count(1)'; // TODO unfiltered will be wrong as filters are already applied at this point var query = searchQueryTpl({ - _searchUnfiltered: filterCategoriesQueryTpl({ - _query: this.query, - _column: this.column, - _value: '0', - _userQuery: _userQuery + searchUnfiltered: filterCategoriesQueryTpl({ + query: this.query, + column: this.column, + value: '0', + userQuery: escapedUserQuery }), - _searchFiltered: filterCategoriesQueryTpl({ - _query: this.query, - _column: this.column, - _value: _value, - _userQuery: _userQuery + searchFiltered: filterCategoriesQueryTpl({ + query: this.query, + column: this.column, + value: value, + userQuery: escapedUserQuery }) }); + debug(query); + psql.query(query, function(err, result) { if (err) { return callback(err, result); From 0a894da0df1cf6da599e85623500ab6cda5b099b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Thu, 14 Sep 2017 15:27:57 +0200 Subject: [PATCH 106/159] Remove prefix to context's properties --- lib/cartodb/models/dataview/aggregation.js | 30 ++++++++++++---------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/lib/cartodb/models/dataview/aggregation.js b/lib/cartodb/models/dataview/aggregation.js index 575c7202..ecf9419c 100644 --- a/lib/cartodb/models/dataview/aggregation.js +++ b/lib/cartodb/models/dataview/aggregation.js @@ -283,10 +283,20 @@ const filterCategoriesQueryTpl = ctx => ` const searchQueryTpl = ctx => ` WITH search_unfiltered AS ( - ${ctx.searchUnfiltered} + ${filterCategoriesQueryTpl({ + query: ctx.query, + column: ctx.column, + value: '0', + userQuery: escapedUserQuery + })} ), search_filtered AS ( - ${ctx.searchFiltered} + ${filterCategoriesQueryTpl({ + query: ctx.query, + column: ctx.column, + value: value, + userQuery: escapedUserQuery + })} ), search_union AS ( SELECT * FROM search_unfiltered @@ -308,18 +318,10 @@ Aggregation.prototype.search = function(psql, userQuery, callback) { // TODO unfiltered will be wrong as filters are already applied at this point var query = searchQueryTpl({ - searchUnfiltered: filterCategoriesQueryTpl({ - query: this.query, - column: this.column, - value: '0', - userQuery: escapedUserQuery - }), - searchFiltered: filterCategoriesQueryTpl({ - query: this.query, - column: this.column, - value: value, - userQuery: escapedUserQuery - }) + query: this.query, + column: this.column, + value: value, + userQuery: escapedUserQuery }); debug(query); From 02949003a96b6eaec2bee1257bd8f26353e49c11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Thu, 14 Sep 2017 15:48:54 +0200 Subject: [PATCH 107/159] Build search query in two steps --- lib/cartodb/models/dataview/aggregation.js | 30 ++++++++++------------ 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/lib/cartodb/models/dataview/aggregation.js b/lib/cartodb/models/dataview/aggregation.js index ecf9419c..575c7202 100644 --- a/lib/cartodb/models/dataview/aggregation.js +++ b/lib/cartodb/models/dataview/aggregation.js @@ -283,20 +283,10 @@ const filterCategoriesQueryTpl = ctx => ` const searchQueryTpl = ctx => ` WITH search_unfiltered AS ( - ${filterCategoriesQueryTpl({ - query: ctx.query, - column: ctx.column, - value: '0', - userQuery: escapedUserQuery - })} + ${ctx.searchUnfiltered} ), search_filtered AS ( - ${filterCategoriesQueryTpl({ - query: ctx.query, - column: ctx.column, - value: value, - userQuery: escapedUserQuery - })} + ${ctx.searchFiltered} ), search_union AS ( SELECT * FROM search_unfiltered @@ -318,10 +308,18 @@ Aggregation.prototype.search = function(psql, userQuery, callback) { // TODO unfiltered will be wrong as filters are already applied at this point var query = searchQueryTpl({ - query: this.query, - column: this.column, - value: value, - userQuery: escapedUserQuery + searchUnfiltered: filterCategoriesQueryTpl({ + query: this.query, + column: this.column, + value: '0', + userQuery: escapedUserQuery + }), + searchFiltered: filterCategoriesQueryTpl({ + query: this.query, + column: this.column, + value: value, + userQuery: escapedUserQuery + }) }); debug(query); From 086eff01a907e7dc69039a47e51075914ee961f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Thu, 14 Sep 2017 15:57:18 +0200 Subject: [PATCH 108/159] Use ES6 class syntax --- lib/cartodb/models/dataview/aggregation.js | 285 ++++++++++----------- 1 file changed, 142 insertions(+), 143 deletions(-) diff --git a/lib/cartodb/models/dataview/aggregation.js b/lib/cartodb/models/dataview/aggregation.js index 575c7202..039cbea0 100644 --- a/lib/cartodb/models/dataview/aggregation.js +++ b/lib/cartodb/models/dataview/aggregation.js @@ -163,112 +163,158 @@ const TYPE = 'aggregation'; } } */ -function Aggregation(query, options = {}, queries = {}) { - if (typeof options.column !== 'string') { - throw new Error(`Aggregation expects 'column' in widget options`); +module.exports = class Aggregation extends BaseWidget { + constructor (query, options = {}, queries = {}) { + super(); + + if (typeof options.column !== 'string') { + throw new Error(`Aggregation expects 'column' in widget options`); + } + + if (typeof options.aggregation !== 'string') { + throw new Error(`Aggregation expects 'aggregation' operation in widget options`); + } + + if (!VALID_OPERATIONS[options.aggregation]) { + throw new Error(`Aggregation does not support '${options.aggregation}' operation`); + } + + var requiredOptions = VALID_OPERATIONS[options.aggregation]; + var missingOptions = requiredOptions.filter(requiredOption => !options.hasOwnProperty(requiredOption)); + + if (missingOptions.length > 0) { + throw new Error( + `Aggregation '${options.aggregation}' is missing some options: ${missingOptions.join(',')}` + ); + } + + this.query = query; + this.queries = queries; + this.column = options.column; + this.aggregation = options.aggregation; + this.aggregationColumn = options.aggregationColumn; + this._isFloatColumn = null; } - if (typeof options.aggregation !== 'string') { - throw new Error(`Aggregation expects 'aggregation' operation in widget options`); - } + sql (psql, override, callback) { + var self = this; - if (!VALID_OPERATIONS[options.aggregation]) { - throw new Error(`Aggregation does not support '${options.aggregation}' operation`); - } + if (!callback) { + callback = override; + override = {}; + } - var requiredOptions = VALID_OPERATIONS[options.aggregation]; - var missingOptions = requiredOptions.filter(requiredOption => !options.hasOwnProperty(requiredOption)); + if (this.aggregationColumn && this._isFloatColumn === null) { + this._isFloatColumn = false; + this.getColumnType(psql, this.aggregationColumn, this.queries.no_filters, function (err, type) { + if (!err && !!type) { + self._isFloatColumn = type.float; + } + self.sql(psql, override, callback); + }); + return null; + } - if (missingOptions.length > 0) { - throw new Error(`Aggregation '${options.aggregation}' is missing some options: ${missingOptions.join(',')}`); - } - - BaseWidget.apply(this); - - this.query = query; - this.queries = queries; - this.column = options.column; - this.aggregation = options.aggregation; - this.aggregationColumn = options.aggregationColumn; - this._isFloatColumn = null; -} - -Aggregation.prototype = new BaseWidget(); -Aggregation.prototype.constructor = Aggregation; - -module.exports = Aggregation; - -Aggregation.prototype.sql = function(psql, override, callback) { - var self = this; - - if (!callback) { - callback = override; - override = {}; - } - - if (this.aggregationColumn && this._isFloatColumn === null) { - this._isFloatColumn = false; - this.getColumnType(psql, this.aggregationColumn, this.queries.no_filters, function (err, type) { - if (!err && !!type) { - self._isFloatColumn = type.float; - } - self.sql(psql, override, callback); - }); - return null; - } - - var aggregationSql = aggregationDataviewQueryTpl({ - override: override, - query: this.query, - column: this.column, - aggregation: this.aggregation, - aggregationColumn: this.aggregation !== 'count' ? this.aggregationColumn : null, - aggregationFn: aggregationFnQueryTpl({ + var aggregationSql = aggregationDataviewQueryTpl({ + override: override, + query: this.query, + column: this.column, aggregation: this.aggregation, - aggregationColumn: this.aggregationColumn || 1 - }), - isFloatColumn: this._isFloatColumn, - limit: CATEGORIES_LIMIT - }); + aggregationColumn: this.aggregation !== 'count' ? this.aggregationColumn : null, + aggregationFn: aggregationFnQueryTpl({ + aggregation: this.aggregation, + aggregationColumn: this.aggregationColumn || 1 + }), + isFloatColumn: this._isFloatColumn, + limit: CATEGORIES_LIMIT + }); - debug(aggregationSql); + debug(aggregationSql); - return callback(null, aggregationSql); -}; - -Aggregation.prototype.format = function(result) { - var categories = []; - var count = 0; - var nulls = 0; - var nans = 0; - var infinities = 0; - var minValue = 0; - var maxValue = 0; - var categoriesCount = 0; - - if (result.rows.length) { - var firstRow = result.rows[0]; - count = firstRow.count; - nulls = firstRow.nulls_count; - nans = firstRow.nans_count; - infinities = firstRow.infinities_count; - minValue = firstRow.min_val; - maxValue = firstRow.max_val; - categoriesCount = firstRow.categories_count; - categories = result.rows.map(({ category, value, agg }) => ({ category, value, agg })); + return callback(null, aggregationSql); } - return { - aggregation: this.aggregation, - count: count, - nulls: nulls, - nans: nans, - infinities: infinities, - min: minValue, - max: maxValue, - categoriesCount: categoriesCount, - categories: categories - }; + format (result) { + var categories = []; + var count = 0; + var nulls = 0; + var nans = 0; + var infinities = 0; + var minValue = 0; + var maxValue = 0; + var categoriesCount = 0; + + if (result.rows.length) { + var firstRow = result.rows[0]; + count = firstRow.count; + nulls = firstRow.nulls_count; + nans = firstRow.nans_count; + infinities = firstRow.infinities_count; + minValue = firstRow.min_val; + maxValue = firstRow.max_val; + categoriesCount = firstRow.categories_count; + categories = result.rows.map(({ category, value, agg }) => ({ category, value, agg })); + } + + return { + aggregation: this.aggregation, + count: count, + nulls: nulls, + nans: nans, + infinities: infinities, + min: minValue, + max: maxValue, + categoriesCount: categoriesCount, + categories: categories + }; + } + + search (psql, userQuery, callback) { + var self = this; + + var escapedUserQuery = psql.escapeLiteral('%' + userQuery + '%'); + var value = this.aggregation !== 'count' && this.aggregationColumn ? + this.aggregation + '(' + this.aggregationColumn + ')' : 'count(1)'; + + // TODO unfiltered will be wrong as filters are already applied at this point + var query = searchQueryTpl({ + searchUnfiltered: filterCategoriesQueryTpl({ + query: this.query, + column: this.column, + value: '0', + userQuery: escapedUserQuery + }), + searchFiltered: filterCategoriesQueryTpl({ + query: this.query, + column: this.column, + value: value, + userQuery: escapedUserQuery + }) + }); + + debug(query); + + psql.query(query, function(err, result) { + if (err) { + return callback(err, result); + } + + return callback(null, {type: self.getType(), categories: result.rows }); + }, true); // use read-only transaction + } + + getType () { + return TYPE; + } + + toString () { + return JSON.stringify({ + _type: TYPE, + _query: this.query, + _column: this.column, + _aggregation: this.aggregation + }); + } }; const filterCategoriesQueryTpl = ctx => ` @@ -298,50 +344,3 @@ const searchQueryTpl = ctx => ` GROUP BY category ORDER BY value desc `; - -Aggregation.prototype.search = function(psql, userQuery, callback) { - var self = this; - - var escapedUserQuery = psql.escapeLiteral('%' + userQuery + '%'); - var value = this.aggregation !== 'count' && this.aggregationColumn ? - this.aggregation + '(' + this.aggregationColumn + ')' : 'count(1)'; - - // TODO unfiltered will be wrong as filters are already applied at this point - var query = searchQueryTpl({ - searchUnfiltered: filterCategoriesQueryTpl({ - query: this.query, - column: this.column, - value: '0', - userQuery: escapedUserQuery - }), - searchFiltered: filterCategoriesQueryTpl({ - query: this.query, - column: this.column, - value: value, - userQuery: escapedUserQuery - }) - }); - - debug(query); - - psql.query(query, function(err, result) { - if (err) { - return callback(err, result); - } - - return callback(null, {type: self.getType(), categories: result.rows }); - }, true); // use read-only transaction -}; - -Aggregation.prototype.getType = function() { - return TYPE; -}; - -Aggregation.prototype.toString = function() { - return JSON.stringify({ - _type: TYPE, - _query: this.query, - _column: this.column, - _aggregation: this.aggregation - }); -}; From 46254eaf743f7851878a0a7baef52a342379b342 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Thu, 14 Sep 2017 15:59:15 +0200 Subject: [PATCH 109/159] rename BaseWidget by BaseDataview --- lib/cartodb/models/dataview/aggregation.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/cartodb/models/dataview/aggregation.js b/lib/cartodb/models/dataview/aggregation.js index 039cbea0..f0ea5608 100644 --- a/lib/cartodb/models/dataview/aggregation.js +++ b/lib/cartodb/models/dataview/aggregation.js @@ -1,4 +1,4 @@ -const BaseWidget = require('./base'); +const BaseDataview = require('./base'); const debug = require('debug')('windshaft:widget:aggregation'); const filteredQueryTpl = ctx => ` @@ -163,7 +163,7 @@ const TYPE = 'aggregation'; } } */ -module.exports = class Aggregation extends BaseWidget { +module.exports = class Aggregation extends BaseDataview { constructor (query, options = {}, queries = {}) { super(); @@ -176,7 +176,7 @@ module.exports = class Aggregation extends BaseWidget { } if (!VALID_OPERATIONS[options.aggregation]) { - throw new Error(`Aggregation does not support '${options.aggregation}' operation`); + throw new Error(`Aggregation does not support '${options.aggregation}' operation`); } var requiredOptions = VALID_OPERATIONS[options.aggregation]; From cd13107a4dbefada5ac6e0fdbf27346261c18cae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Thu, 14 Sep 2017 16:08:12 +0200 Subject: [PATCH 110/159] Use ES6 let & const to declare variables --- lib/cartodb/models/dataview/aggregation.js | 34 +++++++++++----------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/lib/cartodb/models/dataview/aggregation.js b/lib/cartodb/models/dataview/aggregation.js index f0ea5608..b1e56f28 100644 --- a/lib/cartodb/models/dataview/aggregation.js +++ b/lib/cartodb/models/dataview/aggregation.js @@ -179,8 +179,8 @@ module.exports = class Aggregation extends BaseDataview { throw new Error(`Aggregation does not support '${options.aggregation}' operation`); } - var requiredOptions = VALID_OPERATIONS[options.aggregation]; - var missingOptions = requiredOptions.filter(requiredOption => !options.hasOwnProperty(requiredOption)); + const requiredOptions = VALID_OPERATIONS[options.aggregation]; + const missingOptions = requiredOptions.filter(requiredOption => !options.hasOwnProperty(requiredOption)); if (missingOptions.length > 0) { throw new Error( @@ -197,7 +197,7 @@ module.exports = class Aggregation extends BaseDataview { } sql (psql, override, callback) { - var self = this; + const self = this; if (!callback) { callback = override; @@ -215,7 +215,7 @@ module.exports = class Aggregation extends BaseDataview { return null; } - var aggregationSql = aggregationDataviewQueryTpl({ + const aggregationSql = aggregationDataviewQueryTpl({ override: override, query: this.query, column: this.column, @@ -235,17 +235,17 @@ module.exports = class Aggregation extends BaseDataview { } format (result) { - var categories = []; - var count = 0; - var nulls = 0; - var nans = 0; - var infinities = 0; - var minValue = 0; - var maxValue = 0; - var categoriesCount = 0; + let categories = []; + let count = 0; + let nulls = 0; + let nans = 0; + let infinities = 0; + let minValue = 0; + let maxValue = 0; + let categoriesCount = 0; if (result.rows.length) { - var firstRow = result.rows[0]; + const firstRow = result.rows[0]; count = firstRow.count; nulls = firstRow.nulls_count; nans = firstRow.nans_count; @@ -270,14 +270,14 @@ module.exports = class Aggregation extends BaseDataview { } search (psql, userQuery, callback) { - var self = this; + const self = this; - var escapedUserQuery = psql.escapeLiteral('%' + userQuery + '%'); - var value = this.aggregation !== 'count' && this.aggregationColumn ? + const escapedUserQuery = psql.escapeLiteral('%' + userQuery + '%'); + const value = this.aggregation !== 'count' && this.aggregationColumn ? this.aggregation + '(' + this.aggregationColumn + ')' : 'count(1)'; // TODO unfiltered will be wrong as filters are already applied at this point - var query = searchQueryTpl({ + const query = searchQueryTpl({ searchUnfiltered: filterCategoriesQueryTpl({ query: this.query, column: this.column, From b05083bcfc09ebf6b1326bcb394905a8c46ef196 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Thu, 14 Sep 2017 16:09:44 +0200 Subject: [PATCH 111/159] Move search's templates along aggregation's templates --- lib/cartodb/models/dataview/aggregation.js | 56 +++++++++++----------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/lib/cartodb/models/dataview/aggregation.js b/lib/cartodb/models/dataview/aggregation.js index b1e56f28..d51edf04 100644 --- a/lib/cartodb/models/dataview/aggregation.js +++ b/lib/cartodb/models/dataview/aggregation.js @@ -142,6 +142,34 @@ const aggregationDataviewQueryTpl = ctx => ` ${!!ctx.override.ownFilter ? `${aggregationQueryTpl(ctx)}` : `${rankedAggregationQueryTpl(ctx)}`} `; +const filterCategoriesQueryTpl = ctx => ` + SELECT + ${ctx.column} AS category, + ${ctx.value} AS value + FROM (${ctx.query}) _cdb_aggregation_search + WHERE CAST(${ctx.column} as text) ILIKE ${ctx.userQuery} + GROUP BY ${ctx.column} +`; + +const searchQueryTpl = ctx => ` + WITH + search_unfiltered AS ( + ${ctx.searchUnfiltered} + ), + search_filtered AS ( + ${ctx.searchFiltered} + ), + search_union AS ( + SELECT * FROM search_unfiltered + UNION ALL + SELECT * FROM search_filtered + ) + SELECT category, sum(value) AS value + FROM search_union + GROUP BY category + ORDER BY value desc +`; + const CATEGORIES_LIMIT = 6; const VALID_OPERATIONS = { @@ -316,31 +344,3 @@ module.exports = class Aggregation extends BaseDataview { }); } }; - -const filterCategoriesQueryTpl = ctx => ` - SELECT - ${ctx.column} AS category, - ${ctx.value} AS value - FROM (${ctx.query}) _cdb_aggregation_search - WHERE CAST(${ctx.column} as text) ILIKE ${ctx.userQuery} - GROUP BY ${ctx.column} -`; - -const searchQueryTpl = ctx => ` - WITH - search_unfiltered AS ( - ${ctx.searchUnfiltered} - ), - search_filtered AS ( - ${ctx.searchFiltered} - ), - search_union AS ( - SELECT * FROM search_unfiltered - UNION ALL - SELECT * FROM search_filtered - ) - SELECT category, sum(value) AS value - FROM search_union - GROUP BY category - ORDER BY value desc -`; From 746292610aeec58b901e569a9d2110c2c0fa6ea5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Thu, 14 Sep 2017 16:10:23 +0200 Subject: [PATCH 112/159] Rename debug namespace --- lib/cartodb/models/dataview/aggregation.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cartodb/models/dataview/aggregation.js b/lib/cartodb/models/dataview/aggregation.js index d51edf04..b6f54263 100644 --- a/lib/cartodb/models/dataview/aggregation.js +++ b/lib/cartodb/models/dataview/aggregation.js @@ -1,5 +1,5 @@ const BaseDataview = require('./base'); -const debug = require('debug')('windshaft:widget:aggregation'); +const debug = require('debug')('windshaft:dataview:aggregation'); const filteredQueryTpl = ctx => ` filtered_source AS ( From cc2cf78264bcc6459ec8610ff12621ec90823ff0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Thu, 14 Sep 2017 16:12:39 +0200 Subject: [PATCH 113/159] Rename all 'widget' ocurrences by 'dataview' --- lib/cartodb/models/dataview/aggregation.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/cartodb/models/dataview/aggregation.js b/lib/cartodb/models/dataview/aggregation.js index b6f54263..80f77062 100644 --- a/lib/cartodb/models/dataview/aggregation.js +++ b/lib/cartodb/models/dataview/aggregation.js @@ -196,11 +196,11 @@ module.exports = class Aggregation extends BaseDataview { super(); if (typeof options.column !== 'string') { - throw new Error(`Aggregation expects 'column' in widget options`); + throw new Error(`Aggregation expects 'column' in dataview options`); } if (typeof options.aggregation !== 'string') { - throw new Error(`Aggregation expects 'aggregation' operation in widget options`); + throw new Error(`Aggregation expects 'aggregation' operation in dataview options`); } if (!VALID_OPERATIONS[options.aggregation]) { From c2e99219effef22af3bdc2c3b0718e6f9c8045cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Thu, 14 Sep 2017 16:30:46 +0200 Subject: [PATCH 114/159] Use ES6 goodies to refactor format method --- lib/cartodb/models/dataview/aggregation.js | 43 ++++++++-------------- 1 file changed, 16 insertions(+), 27 deletions(-) diff --git a/lib/cartodb/models/dataview/aggregation.js b/lib/cartodb/models/dataview/aggregation.js index 80f77062..e60697cb 100644 --- a/lib/cartodb/models/dataview/aggregation.js +++ b/lib/cartodb/models/dataview/aggregation.js @@ -263,37 +263,26 @@ module.exports = class Aggregation extends BaseDataview { } format (result) { - let categories = []; - let count = 0; - let nulls = 0; - let nans = 0; - let infinities = 0; - let minValue = 0; - let maxValue = 0; - let categoriesCount = 0; - - if (result.rows.length) { - const firstRow = result.rows[0]; - count = firstRow.count; - nulls = firstRow.nulls_count; - nans = firstRow.nans_count; - infinities = firstRow.infinities_count; - minValue = firstRow.min_val; - maxValue = firstRow.max_val; - categoriesCount = firstRow.categories_count; - categories = result.rows.map(({ category, value, agg }) => ({ category, value, agg })); - } + const { + count = 0, + nulls_count = 0, + nans_count = 0, + infinities_count = 0, + min_val = 0, + max_val = 0, + categories_count = 0 + } = result.rows[0] || {}; return { aggregation: this.aggregation, count: count, - nulls: nulls, - nans: nans, - infinities: infinities, - min: minValue, - max: maxValue, - categoriesCount: categoriesCount, - categories: categories + nulls: nulls_count, + nans: nans_count, + infinities: infinities_count, + min: min_val, + max: max_val, + categoriesCount: categories_count, + categories: result.rows.map(({ category, value, agg }) => ({ category, value, agg })) }; } From 46860541fe035c5627b8576741bcf3bf721d1977 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Thu, 14 Sep 2017 16:42:25 +0200 Subject: [PATCH 115/159] Apply extract method to validate input options --- lib/cartodb/models/dataview/aggregation.js | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/lib/cartodb/models/dataview/aggregation.js b/lib/cartodb/models/dataview/aggregation.js index e60697cb..c7626736 100644 --- a/lib/cartodb/models/dataview/aggregation.js +++ b/lib/cartodb/models/dataview/aggregation.js @@ -195,6 +195,17 @@ module.exports = class Aggregation extends BaseDataview { constructor (query, options = {}, queries = {}) { super(); + this._checkOptions(options); + + this.query = query; + this.queries = queries; + this.column = options.column; + this.aggregation = options.aggregation; + this.aggregationColumn = options.aggregationColumn; + this._isFloatColumn = null; + } + + _checkOptions () { if (typeof options.column !== 'string') { throw new Error(`Aggregation expects 'column' in dataview options`); } @@ -215,13 +226,6 @@ module.exports = class Aggregation extends BaseDataview { `Aggregation '${options.aggregation}' is missing some options: ${missingOptions.join(',')}` ); } - - this.query = query; - this.queries = queries; - this.column = options.column; - this.aggregation = options.aggregation; - this.aggregationColumn = options.aggregationColumn; - this._isFloatColumn = null; } sql (psql, override, callback) { From 38e2c040d175a4171ed469fb9f2fc210d93da5bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Thu, 14 Sep 2017 16:45:45 +0200 Subject: [PATCH 116/159] Use template string to escape literals --- lib/cartodb/models/dataview/aggregation.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/cartodb/models/dataview/aggregation.js b/lib/cartodb/models/dataview/aggregation.js index c7626736..29eebe06 100644 --- a/lib/cartodb/models/dataview/aggregation.js +++ b/lib/cartodb/models/dataview/aggregation.js @@ -292,8 +292,7 @@ module.exports = class Aggregation extends BaseDataview { search (psql, userQuery, callback) { const self = this; - - const escapedUserQuery = psql.escapeLiteral('%' + userQuery + '%'); + const escapedUserQuery = psql.escapeLiteral(`%${userQuery}%`); const value = this.aggregation !== 'count' && this.aggregationColumn ? this.aggregation + '(' + this.aggregationColumn + ')' : 'count(1)'; From 1f3b0beddfa5120d56a7337a0fd5c3de23a1f3f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Thu, 14 Sep 2017 16:47:05 +0200 Subject: [PATCH 117/159] Fix missing parameter --- lib/cartodb/models/dataview/aggregation.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/cartodb/models/dataview/aggregation.js b/lib/cartodb/models/dataview/aggregation.js index 29eebe06..f621e536 100644 --- a/lib/cartodb/models/dataview/aggregation.js +++ b/lib/cartodb/models/dataview/aggregation.js @@ -205,7 +205,7 @@ module.exports = class Aggregation extends BaseDataview { this._isFloatColumn = null; } - _checkOptions () { + _checkOptions (options) { if (typeof options.column !== 'string') { throw new Error(`Aggregation expects 'column' in dataview options`); } @@ -294,7 +294,8 @@ module.exports = class Aggregation extends BaseDataview { const self = this; const escapedUserQuery = psql.escapeLiteral(`%${userQuery}%`); const value = this.aggregation !== 'count' && this.aggregationColumn ? - this.aggregation + '(' + this.aggregationColumn + ')' : 'count(1)'; + `${this.aggregation}(${this.aggregationColumn})` : + 'count(1)'; // TODO unfiltered will be wrong as filters are already applied at this point const query = searchQueryTpl({ From 51907b95459b653af790f7c137d5edc9bf867906 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Thu, 14 Sep 2017 16:56:55 +0200 Subject: [PATCH 118/159] Apply extract method to condition --- lib/cartodb/models/dataview/aggregation.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/cartodb/models/dataview/aggregation.js b/lib/cartodb/models/dataview/aggregation.js index f621e536..7f6c198a 100644 --- a/lib/cartodb/models/dataview/aggregation.js +++ b/lib/cartodb/models/dataview/aggregation.js @@ -236,7 +236,7 @@ module.exports = class Aggregation extends BaseDataview { override = {}; } - if (this.aggregationColumn && this._isFloatColumn === null) { + if (this._shouldCheckColumnType()) { this._isFloatColumn = false; this.getColumnType(psql, this.aggregationColumn, this.queries.no_filters, function (err, type) { if (!err && !!type) { @@ -266,6 +266,10 @@ module.exports = class Aggregation extends BaseDataview { return callback(null, aggregationSql); } + _shouldCheckColumnType () { + return this.aggregationColumn && this._isFloatColumn === null; + } + format (result) { const { count = 0, From 9a4794ee10f6aeef8782c837aa0bccd649f158fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Thu, 14 Sep 2017 17:09:55 +0200 Subject: [PATCH 119/159] Remove dot requirement and use template strings to build list aggregation query --- lib/cartodb/models/dataview/list.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/cartodb/models/dataview/list.js b/lib/cartodb/models/dataview/list.js index 33993777..b241819e 100644 --- a/lib/cartodb/models/dataview/list.js +++ b/lib/cartodb/models/dataview/list.js @@ -1,11 +1,8 @@ -var dot = require('dot'); -dot.templateSettings.strip = false; - var BaseWidget = require('./base'); var TYPE = 'list'; -var listSqlTpl = dot.template('select {{=it._columns}} from ({{=it._query}}) as _cdb_list'); +var listSqlTpl = ctx => `select ${ctx._columns} from (${ctx._query}) as _cdb_list`; /** { From cdc56e703c11f01ed074e814be446e49684b6112 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Thu, 14 Sep 2017 17:10:51 +0200 Subject: [PATCH 120/159] Rename BaseWidget by BaseDataview --- lib/cartodb/models/dataview/list.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/cartodb/models/dataview/list.js b/lib/cartodb/models/dataview/list.js index b241819e..064a4cdc 100644 --- a/lib/cartodb/models/dataview/list.js +++ b/lib/cartodb/models/dataview/list.js @@ -1,4 +1,4 @@ -var BaseWidget = require('./base'); +var BaseDataview = require('./base'); var TYPE = 'list'; @@ -20,13 +20,13 @@ function List(query, options) { throw new Error('List expects `columns` array in widget options'); } - BaseWidget.apply(this); + BaseDataview.apply(this); this.query = query; this.columns = options.columns; } -List.prototype = new BaseWidget(); +List.prototype = new BaseDataview(); List.prototype.constructor = List; module.exports = List; From c8ff61c531d02717b5a159d1cb87b58c2cb368e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Thu, 14 Sep 2017 17:15:43 +0200 Subject: [PATCH 121/159] Use ES6 class syntax --- lib/cartodb/models/dataview/list.js | 78 ++++++++++++++--------------- 1 file changed, 38 insertions(+), 40 deletions(-) diff --git a/lib/cartodb/models/dataview/list.js b/lib/cartodb/models/dataview/list.js index 064a4cdc..cb895429 100644 --- a/lib/cartodb/models/dataview/list.js +++ b/lib/cartodb/models/dataview/list.js @@ -12,52 +12,50 @@ var listSqlTpl = ctx => `select ${ctx._columns} from (${ctx._query}) as _cdb_lis } } */ +module.exports = class List extends BaseDataview{ + constructor (query, options = {}) { + super(); -function List(query, options) { - options = options || {}; + this._checkOptions(options); - if (!Array.isArray(options.columns)) { - throw new Error('List expects `columns` array in widget options'); + this.query = query; + this.columns = options.columns; } - BaseDataview.apply(this); - - this.query = query; - this.columns = options.columns; -} - -List.prototype = new BaseDataview(); -List.prototype.constructor = List; - -module.exports = List; - -List.prototype.sql = function(psql, override, callback) { - if (!callback) { - callback = override; + _checkOptions (options) { + if (!Array.isArray(options.columns)) { + throw new Error('List expects `columns` array in widget options'); + } } - var listSql = listSqlTpl({ - _query: this.query, - _columns: this.columns.join(', ') - }); + sql (psql, override, callback) { + if (!callback) { + callback = override; + } - return callback(null, listSql); -}; + var listSql = listSqlTpl({ + _query: this.query, + _columns: this.columns.join(', ') + }); -List.prototype.format = function(result) { - return { - rows: result.rows + return callback(null, listSql); + } + + format (result) { + return { + rows: result.rows + }; + } + + getType () { + return TYPE; + } + + toString () { + return JSON.stringify({ + _type: TYPE, + _query: this.query, + _columns: this.columns.join(', ') + }); }; -}; - -List.prototype.getType = function() { - return TYPE; -}; - -List.prototype.toString = function() { - return JSON.stringify({ - _type: TYPE, - _query: this.query, - _columns: this.columns.join(', ') - }); -}; +} From 050e9776d1355e9c24fed37d71fe9ef32b583dc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Thu, 14 Sep 2017 17:18:20 +0200 Subject: [PATCH 122/159] Use const for requirements, constants and variables that are initialized once --- lib/cartodb/models/dataview/list.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/cartodb/models/dataview/list.js b/lib/cartodb/models/dataview/list.js index cb895429..d3b92e16 100644 --- a/lib/cartodb/models/dataview/list.js +++ b/lib/cartodb/models/dataview/list.js @@ -1,8 +1,8 @@ -var BaseDataview = require('./base'); +const BaseDataview = require('./base'); -var TYPE = 'list'; +const TYPE = 'list'; -var listSqlTpl = ctx => `select ${ctx._columns} from (${ctx._query}) as _cdb_list`; +const listSqlTpl = ctx => `select ${ctx._columns} from (${ctx._query}) as _cdb_list`; /** { @@ -33,7 +33,7 @@ module.exports = class List extends BaseDataview{ callback = override; } - var listSql = listSqlTpl({ + const listSql = listSqlTpl({ _query: this.query, _columns: this.columns.join(', ') }); From d328b534a5622f76489a12177565dd55f4fae0ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Thu, 14 Sep 2017 17:19:16 +0200 Subject: [PATCH 123/159] Replace widget word by dataview --- lib/cartodb/models/dataview/list.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cartodb/models/dataview/list.js b/lib/cartodb/models/dataview/list.js index d3b92e16..05cee5f7 100644 --- a/lib/cartodb/models/dataview/list.js +++ b/lib/cartodb/models/dataview/list.js @@ -24,7 +24,7 @@ module.exports = class List extends BaseDataview{ _checkOptions (options) { if (!Array.isArray(options.columns)) { - throw new Error('List expects `columns` array in widget options'); + throw new Error('List expects `columns` array in dataview options'); } } From d0e2c9f898e6dff28a1fc92f5d194e916f89d124 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Thu, 14 Sep 2017 17:21:21 +0200 Subject: [PATCH 124/159] Use debug module to print sql for debugging purposes --- lib/cartodb/models/dataview/list.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/cartodb/models/dataview/list.js b/lib/cartodb/models/dataview/list.js index 05cee5f7..b8d90d1b 100644 --- a/lib/cartodb/models/dataview/list.js +++ b/lib/cartodb/models/dataview/list.js @@ -1,4 +1,5 @@ const BaseDataview = require('./base'); +const debug = require('debug')('windshaft:dataview:list'); const TYPE = 'list'; @@ -12,7 +13,7 @@ const listSqlTpl = ctx => `select ${ctx._columns} from (${ctx._query}) as _cdb_l } } */ -module.exports = class List extends BaseDataview{ +module.exports = class List extends BaseDataview { constructor (query, options = {}) { super(); @@ -38,6 +39,8 @@ module.exports = class List extends BaseDataview{ _columns: this.columns.join(', ') }); + debug(listSql); + return callback(null, listSql); } From 2a0b15f085d9c29a954544e15a2fb39dc580d35b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Thu, 14 Sep 2017 17:22:34 +0200 Subject: [PATCH 125/159] Remove prefix '_' while passing context param to templates --- lib/cartodb/models/dataview/list.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/cartodb/models/dataview/list.js b/lib/cartodb/models/dataview/list.js index b8d90d1b..db90d706 100644 --- a/lib/cartodb/models/dataview/list.js +++ b/lib/cartodb/models/dataview/list.js @@ -3,7 +3,7 @@ const debug = require('debug')('windshaft:dataview:list'); const TYPE = 'list'; -const listSqlTpl = ctx => `select ${ctx._columns} from (${ctx._query}) as _cdb_list`; +const listSqlTpl = ctx => `select ${ctx.columns} from (${ctx.query}) as _cdb_list`; /** { @@ -35,8 +35,8 @@ module.exports = class List extends BaseDataview { } const listSql = listSqlTpl({ - _query: this.query, - _columns: this.columns.join(', ') + query: this.query, + columns: this.columns.join(', ') }); debug(listSql); From dbb23bf9f087147a923f3916d5bd5ecf5d5ba3e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Thu, 14 Sep 2017 17:24:13 +0200 Subject: [PATCH 126/159] Remove jshint's complaints --- lib/cartodb/models/dataview/list.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/cartodb/models/dataview/list.js b/lib/cartodb/models/dataview/list.js index db90d706..a54c660f 100644 --- a/lib/cartodb/models/dataview/list.js +++ b/lib/cartodb/models/dataview/list.js @@ -60,5 +60,5 @@ module.exports = class List extends BaseDataview { _query: this.query, _columns: this.columns.join(', ') }); - }; -} + } +}; From dcb9b8ec5213b570d53b789fedc60109c247900e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Thu, 14 Sep 2017 17:56:17 +0200 Subject: [PATCH 127/159] Rename BaseWidget by BaseDataview --- lib/cartodb/models/dataview/formula.js | 51 +++++++++++++++----------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/lib/cartodb/models/dataview/formula.js b/lib/cartodb/models/dataview/formula.js index 7ec356b7..6781fba3 100644 --- a/lib/cartodb/models/dataview/formula.js +++ b/lib/cartodb/models/dataview/formula.js @@ -1,26 +1,35 @@ var _ = require('underscore'); -var BaseWidget = require('./base'); +var BaseDataview = require('./base'); var debug = require('debug')('windshaft:widget:formula'); -var dot = require('dot'); -dot.templateSettings.strip = false; +const countInfinitiesQueryTpl = ctx => ` + SELECT count(1) FROM (${ctx._query}) __cdb_formula_infinities + WHERE ${ctx._column} = 'infinity'::float OR ${ctx._column} = '-infinity'::float +`; -var formulaQueryTpl = dot.template([ - 'SELECT', - ' {{=it._operation}}({{=it._column}}) AS result,', - ' (SELECT count(1) FROM ({{=it._query}}) _cdb_formula_nulls WHERE {{=it._column}} IS NULL) AS nulls_count', - ' {{?it._isFloatColumn}},(SELECT count(1) FROM ({{=it._query}}) _cdb_formula_nulls', - ' WHERE {{=it._column}} = \'infinity\'::float OR {{=it._column}} = \'-infinity\'::float) AS infinities_count', - ' ,(SELECT count(1) FROM ({{=it._query}}) _cdb_formula_nulls', - ' WHERE {{=it._column}} = \'NaN\'::float) AS nans_count{{?}}', - 'FROM ({{=it._query}}) _cdb_formula', - '{{?it._isFloatColumn && it._operation !== \'count\'}}WHERE', - ' {{=it._column}} != \'infinity\'::float', - 'AND', - ' {{=it._column}} != \'-infinity\'::float', - 'AND', - ' {{=it._column}} != \'NaN\'::float{{?}}' -].join('\n')); +const countNansQueryTpl = ctx => ` + SELECT count(1) FROM (${ctx._query}) __cdb_formula_nans + WHERE ${ctx._column} = 'NaN'::float +`; + +const filterOutSpecialNumericValuesTpl = ctx => ` + WHERE + ${ctx._column} != 'infinity'::float + AND + ${ctx._column} != '-infinity'::float + AND + ${ctx._column} != 'NaN'::float +`; + +const formulaQueryTpl = ctx => ` + SELECT + ${ctx._operation}(${ctx._column}) AS result, + (SELECT count(1) FROM (${ctx._query}) _cdb_formula_nulls WHERE ${ctx._column} IS NULL) AS nulls_count + ${ctx._isFloatColumn ? `,(${countInfinitiesQueryTpl(ctx)}) AS infinities_count` : ''} + ${ctx._isFloatColumn ? `,(${countNansQueryTpl(ctx)}) AS nans_count` : ''} + FROM (${ctx._query}) __cdb_formula + ${ctx._isFloatColumn && ctx._operation !== 'count' ? `${filterOutSpecialNumericValuesTpl(ctx)}` : ''} +`; var VALID_OPERATIONS = { count: true, @@ -54,7 +63,7 @@ function Formula(query, options, queries) { throw new Error('Formula expects `column` in widget options'); } - BaseWidget.apply(this); + BaseDataview.apply(this); this.query = query; this.queries = queries; @@ -63,7 +72,7 @@ function Formula(query, options, queries) { this._isFloatColumn = null; } -Formula.prototype = new BaseWidget(); +Formula.prototype = new BaseDataview(); Formula.prototype.constructor = Formula; module.exports = Formula; From 1063d81c1bb64a71c1ccc14f0626005b7887f3cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Thu, 14 Sep 2017 17:56:40 +0200 Subject: [PATCH 128/159] Rename debug namespace --- lib/cartodb/models/dataview/formula.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cartodb/models/dataview/formula.js b/lib/cartodb/models/dataview/formula.js index 6781fba3..54bbe430 100644 --- a/lib/cartodb/models/dataview/formula.js +++ b/lib/cartodb/models/dataview/formula.js @@ -1,6 +1,6 @@ var _ = require('underscore'); var BaseDataview = require('./base'); -var debug = require('debug')('windshaft:widget:formula'); +var debug = require('debug')('windshaft:dataview:formula'); const countInfinitiesQueryTpl = ctx => ` SELECT count(1) FROM (${ctx._query}) __cdb_formula_infinities From a4ecc18f2fb98db46b3b8ab400f1fc5cee4836d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Thu, 14 Sep 2017 17:57:24 +0200 Subject: [PATCH 129/159] Use default values for constructor's arguments --- lib/cartodb/models/dataview/formula.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cartodb/models/dataview/formula.js b/lib/cartodb/models/dataview/formula.js index 54bbe430..4c7f185b 100644 --- a/lib/cartodb/models/dataview/formula.js +++ b/lib/cartodb/models/dataview/formula.js @@ -50,7 +50,7 @@ var TYPE = 'formula'; } } */ -function Formula(query, options, queries) { +function Formula(query, options = {}, queries = {}) { if (!_.isString(options.operation)) { throw new Error('Formula expects `operation` in widget options'); } From 9c64d674b3522f80c812f7676fb1c1aa4c3c5dbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Thu, 14 Sep 2017 18:02:13 +0200 Subject: [PATCH 130/159] Do not use underscore --- lib/cartodb/models/dataview/formula.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/cartodb/models/dataview/formula.js b/lib/cartodb/models/dataview/formula.js index 4c7f185b..36b5369b 100644 --- a/lib/cartodb/models/dataview/formula.js +++ b/lib/cartodb/models/dataview/formula.js @@ -1,4 +1,3 @@ -var _ = require('underscore'); var BaseDataview = require('./base'); var debug = require('debug')('windshaft:dataview:formula'); @@ -51,7 +50,7 @@ var TYPE = 'formula'; } */ function Formula(query, options = {}, queries = {}) { - if (!_.isString(options.operation)) { + if (typeof options.operation !== 'string') { throw new Error('Formula expects `operation` in widget options'); } @@ -59,7 +58,7 @@ function Formula(query, options = {}, queries = {}) { throw new Error("Formula does not support '" + options.operation + "' operation"); } - if (options.operation !== 'count' && !_.isString(options.column)) { + if (options.operation !== 'count' && typeof options.column !== 'string') { throw new Error('Formula expects `column` in widget options'); } From 2437288d9d1675f226a6c8e0aa6f7c4dc233d7ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Fri, 15 Sep 2017 10:37:51 +0200 Subject: [PATCH 131/159] Replace widget word by dataview --- lib/cartodb/models/dataview/formula.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/cartodb/models/dataview/formula.js b/lib/cartodb/models/dataview/formula.js index 36b5369b..b96e05b1 100644 --- a/lib/cartodb/models/dataview/formula.js +++ b/lib/cartodb/models/dataview/formula.js @@ -51,15 +51,15 @@ var TYPE = 'formula'; */ function Formula(query, options = {}, queries = {}) { if (typeof options.operation !== 'string') { - throw new Error('Formula expects `operation` in widget options'); + throw new Error(`Formula expects 'operation' in dataview options`); } if (!VALID_OPERATIONS[options.operation]) { - throw new Error("Formula does not support '" + options.operation + "' operation"); + throw new Error(`Formula does not support '${options.operation}' operation`) } if (options.operation !== 'count' && typeof options.column !== 'string') { - throw new Error('Formula expects `column` in widget options'); + throw new Error(`Formula expects 'column' in dataview options`); } BaseDataview.apply(this); From 68dfed8b85df674685c7e3569f2d2697e2f88bae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Fri, 15 Sep 2017 10:48:44 +0200 Subject: [PATCH 132/159] Use ES6 class syntax --- lib/cartodb/models/dataview/formula.js | 146 ++++++++++++------------- 1 file changed, 71 insertions(+), 75 deletions(-) diff --git a/lib/cartodb/models/dataview/formula.js b/lib/cartodb/models/dataview/formula.js index b96e05b1..ba6725e3 100644 --- a/lib/cartodb/models/dataview/formula.js +++ b/lib/cartodb/models/dataview/formula.js @@ -49,93 +49,89 @@ var TYPE = 'formula'; } } */ -function Formula(query, options = {}, queries = {}) { - if (typeof options.operation !== 'string') { - throw new Error(`Formula expects 'operation' in dataview options`); +module.exports = class Formula extends BaseDataview { + constructor (query, options = {}, queries = {}) { + if (typeof options.operation !== 'string') { + throw new Error(`Formula expects 'operation' in dataview options`); + } + + if (!VALID_OPERATIONS[options.operation]) { + throw new Error(`Formula does not support '${options.operation}' operation`) + } + + if (options.operation !== 'count' && typeof options.column !== 'string') { + throw new Error(`Formula expects 'column' in dataview options`); + } + + this.query = query; + this.queries = queries; + this.column = options.column || '1'; + this.operation = options.operation; + this._isFloatColumn = null; } - if (!VALID_OPERATIONS[options.operation]) { - throw new Error(`Formula does not support '${options.operation}' operation`) - } - if (options.operation !== 'count' && typeof options.column !== 'string') { - throw new Error(`Formula expects 'column' in dataview options`); - } + sql (psql, override, callback) { + var self = this; - BaseDataview.apply(this); + if (!callback) { + callback = override; + override = {}; + } - this.query = query; - this.queries = queries; - this.column = options.column || '1'; - this.operation = options.operation; - this._isFloatColumn = null; -} + if (this._isFloatColumn === null) { + this._isFloatColumn = false; + this.getColumnType(psql, this.column, this.queries.no_filters, function (err, type) { + if (!err && !!type) { + self._isFloatColumn = type.float; + } + self.sql(psql, override, callback); + }); + return null; + } -Formula.prototype = new BaseDataview(); -Formula.prototype.constructor = Formula; - -module.exports = Formula; - -Formula.prototype.sql = function(psql, override, callback) { - var self = this; - - if (!callback) { - callback = override; - override = {}; - } - - if (this._isFloatColumn === null) { - this._isFloatColumn = false; - this.getColumnType(psql, this.column, this.queries.no_filters, function (err, type) { - if (!err && !!type) { - self._isFloatColumn = type.float; - } - self.sql(psql, override, callback); + var formulaSql = formulaQueryTpl({ + _isFloatColumn: this._isFloatColumn, + _query: this.query, + _operation: this.operation, + _column: this.column }); - return null; + + debug(formulaSql); + + return callback(null, formulaSql); } - var formulaSql = formulaQueryTpl({ - _isFloatColumn: this._isFloatColumn, - _query: this.query, - _operation: this.operation, - _column: this.column - }); + format (result) { + var formattedResult = { + operation: this.operation, + result: 0, + nulls: 0, + nans: 0, + infinities: 0 + }; - debug(formulaSql); + if (result.rows.length) { + formattedResult.operation = this.operation; + formattedResult.result = result.rows[0].result; + formattedResult.nulls = result.rows[0].nulls_count; + formattedResult.nans = result.rows[0].nans_count; + formattedResult.infinities = result.rows[0].infinities_count; + } - return callback(null, formulaSql); -}; + return formattedResult; + } -Formula.prototype.format = function(result) { - var formattedResult = { - operation: this.operation, - result: 0, - nulls: 0, - nans: 0, - infinities: 0 + getType () { + return TYPE; }; - if (result.rows.length) { - formattedResult.operation = this.operation; - formattedResult.result = result.rows[0].result; - formattedResult.nulls = result.rows[0].nulls_count; - formattedResult.nans = result.rows[0].nans_count; - formattedResult.infinities = result.rows[0].infinities_count; + toString () { + return JSON.stringify({ + _type: TYPE, + _query: this.query, + _column: this.column, + _operation: this.operation + }); } - - return formattedResult; -}; - -Formula.prototype.getType = function() { - return TYPE; -}; - -Formula.prototype.toString = function() { - return JSON.stringify({ - _type: TYPE, - _query: this.query, - _column: this.column, - _operation: this.operation - }); -}; +} From ecbc7a28e769bb273556b56d8730121b5c8c185b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Fri, 15 Sep 2017 10:49:20 +0200 Subject: [PATCH 133/159] Declare constants with const keyword --- lib/cartodb/models/dataview/formula.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/cartodb/models/dataview/formula.js b/lib/cartodb/models/dataview/formula.js index ba6725e3..efaeb0c6 100644 --- a/lib/cartodb/models/dataview/formula.js +++ b/lib/cartodb/models/dataview/formula.js @@ -30,7 +30,7 @@ const formulaQueryTpl = ctx => ` ${ctx._isFloatColumn && ctx._operation !== 'count' ? `${filterOutSpecialNumericValuesTpl(ctx)}` : ''} `; -var VALID_OPERATIONS = { +const VALID_OPERATIONS = { count: true, avg: true, sum: true, @@ -38,7 +38,7 @@ var VALID_OPERATIONS = { max: true }; -var TYPE = 'formula'; +const TYPE = 'formula'; /** { From c00a93f4144abf7eccaf9c7691c2138218866950 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Fri, 15 Sep 2017 10:58:11 +0200 Subject: [PATCH 134/159] Use destruturing assignment to format the formula result --- lib/cartodb/models/dataview/formula.js | 27 ++++++++++++-------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/lib/cartodb/models/dataview/formula.js b/lib/cartodb/models/dataview/formula.js index efaeb0c6..2172beab 100644 --- a/lib/cartodb/models/dataview/formula.js +++ b/lib/cartodb/models/dataview/formula.js @@ -103,23 +103,20 @@ module.exports = class Formula extends BaseDataview { } format (result) { - var formattedResult = { + const { + result = 0, + nulls_count = 0, + nans_count = 0, + infinities_count = 0 + } = result.rows[0] || {}; + + return { operation: this.operation, - result: 0, - nulls: 0, - nans: 0, - infinities: 0 + result, + nulls: nulls_count, + nans: nans_count, + infinities: infinities_count }; - - if (result.rows.length) { - formattedResult.operation = this.operation; - formattedResult.result = result.rows[0].result; - formattedResult.nulls = result.rows[0].nulls_count; - formattedResult.nans = result.rows[0].nans_count; - formattedResult.infinities = result.rows[0].infinities_count; - } - - return formattedResult; } getType () { From 9771979b8fc391dc396f3c45be1effb6659503d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Fri, 15 Sep 2017 10:59:07 +0200 Subject: [PATCH 135/159] Missing call to super class in constructor --- lib/cartodb/models/dataview/formula.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/cartodb/models/dataview/formula.js b/lib/cartodb/models/dataview/formula.js index 2172beab..c2528a91 100644 --- a/lib/cartodb/models/dataview/formula.js +++ b/lib/cartodb/models/dataview/formula.js @@ -51,6 +51,8 @@ const TYPE = 'formula'; */ module.exports = class Formula extends BaseDataview { constructor (query, options = {}, queries = {}) { + super(); + if (typeof options.operation !== 'string') { throw new Error(`Formula expects 'operation' in dataview options`); } From c7ed3d34e899ba6b76ee96755d85b140ab0c5725 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Fri, 15 Sep 2017 11:35:06 +0200 Subject: [PATCH 136/159] Use const instead of var to declare variables --- lib/cartodb/models/dataview/formula.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/cartodb/models/dataview/formula.js b/lib/cartodb/models/dataview/formula.js index c2528a91..1ce0050b 100644 --- a/lib/cartodb/models/dataview/formula.js +++ b/lib/cartodb/models/dataview/formula.js @@ -1,5 +1,5 @@ -var BaseDataview = require('./base'); -var debug = require('debug')('windshaft:dataview:formula'); +const BaseDataview = require('./base'); +const debug = require('debug')('windshaft:dataview:formula'); const countInfinitiesQueryTpl = ctx => ` SELECT count(1) FROM (${ctx._query}) __cdb_formula_infinities @@ -58,7 +58,7 @@ module.exports = class Formula extends BaseDataview { } if (!VALID_OPERATIONS[options.operation]) { - throw new Error(`Formula does not support '${options.operation}' operation`) + throw new Error(`Formula does not support '${options.operation}' operation`); } if (options.operation !== 'count' && typeof options.column !== 'string') { @@ -74,7 +74,7 @@ module.exports = class Formula extends BaseDataview { sql (psql, override, callback) { - var self = this; + const self = this; if (!callback) { callback = override; @@ -92,7 +92,7 @@ module.exports = class Formula extends BaseDataview { return null; } - var formulaSql = formulaQueryTpl({ + const formulaSql = formulaQueryTpl({ _isFloatColumn: this._isFloatColumn, _query: this.query, _operation: this.operation, @@ -123,7 +123,7 @@ module.exports = class Formula extends BaseDataview { getType () { return TYPE; - }; + } toString () { return JSON.stringify({ @@ -133,4 +133,4 @@ module.exports = class Formula extends BaseDataview { _operation: this.operation }); } -} +}; From 419b29e60900cf5e6100835822899f7cc5fa3d0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Fri, 15 Sep 2017 11:39:06 +0200 Subject: [PATCH 137/159] Do not prefix with '_' template context --- lib/cartodb/models/dataview/formula.js | 34 +++++++++++++------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/lib/cartodb/models/dataview/formula.js b/lib/cartodb/models/dataview/formula.js index 1ce0050b..b6faeb55 100644 --- a/lib/cartodb/models/dataview/formula.js +++ b/lib/cartodb/models/dataview/formula.js @@ -2,32 +2,32 @@ const BaseDataview = require('./base'); const debug = require('debug')('windshaft:dataview:formula'); const countInfinitiesQueryTpl = ctx => ` - SELECT count(1) FROM (${ctx._query}) __cdb_formula_infinities - WHERE ${ctx._column} = 'infinity'::float OR ${ctx._column} = '-infinity'::float + SELECT count(1) FROM (${ctx.query}) __cdb_formula_infinities + WHERE ${ctx.column} = 'infinity'::float OR ${ctx.column} = '-infinity'::float `; const countNansQueryTpl = ctx => ` - SELECT count(1) FROM (${ctx._query}) __cdb_formula_nans - WHERE ${ctx._column} = 'NaN'::float + SELECT count(1) FROM (${ctx.query}) __cdb_formula_nans + WHERE ${ctx.column} = 'NaN'::float `; const filterOutSpecialNumericValuesTpl = ctx => ` WHERE - ${ctx._column} != 'infinity'::float + ${ctx.column} != 'infinity'::float AND - ${ctx._column} != '-infinity'::float + ${ctx.column} != '-infinity'::float AND - ${ctx._column} != 'NaN'::float + ${ctx.column} != 'NaN'::float `; const formulaQueryTpl = ctx => ` SELECT - ${ctx._operation}(${ctx._column}) AS result, - (SELECT count(1) FROM (${ctx._query}) _cdb_formula_nulls WHERE ${ctx._column} IS NULL) AS nulls_count - ${ctx._isFloatColumn ? `,(${countInfinitiesQueryTpl(ctx)}) AS infinities_count` : ''} - ${ctx._isFloatColumn ? `,(${countNansQueryTpl(ctx)}) AS nans_count` : ''} - FROM (${ctx._query}) __cdb_formula - ${ctx._isFloatColumn && ctx._operation !== 'count' ? `${filterOutSpecialNumericValuesTpl(ctx)}` : ''} + ${ctx.operation}(${ctx.column}) AS result, + (SELECT count(1) FROM (${ctx.query}) _cdb_formula_nulls WHERE ${ctx.column} IS NULL) AS nulls_count + ${ctx.isFloatColumn ? `,(${countInfinitiesQueryTpl(ctx)}) AS infinities_count` : ''} + ${ctx.isFloatColumn ? `,(${countNansQueryTpl(ctx)}) AS nans_count` : ''} + FROM (${ctx.query}) __cdb_formula + ${ctx.isFloatColumn && ctx.operation !== 'count' ? `${filterOutSpecialNumericValuesTpl(ctx)}` : ''} `; const VALID_OPERATIONS = { @@ -93,10 +93,10 @@ module.exports = class Formula extends BaseDataview { } const formulaSql = formulaQueryTpl({ - _isFloatColumn: this._isFloatColumn, - _query: this.query, - _operation: this.operation, - _column: this.column + isFloatColumn: this._isFloatColumn, + query: this.query, + operation: this.operation, + column: this.column }); debug(formulaSql); From d4bb4edd1d27fba4c2ab533035c70c2762cbaf1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Fri, 15 Sep 2017 11:44:20 +0200 Subject: [PATCH 138/159] Applyy extract method to check input options --- lib/cartodb/models/dataview/formula.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/lib/cartodb/models/dataview/formula.js b/lib/cartodb/models/dataview/formula.js index b6faeb55..b167b87e 100644 --- a/lib/cartodb/models/dataview/formula.js +++ b/lib/cartodb/models/dataview/formula.js @@ -53,6 +53,16 @@ module.exports = class Formula extends BaseDataview { constructor (query, options = {}, queries = {}) { super(); + this._checkOptions(options); + + this.query = query; + this.queries = queries; + this.column = options.column || '1'; + this.operation = options.operation; + this._isFloatColumn = null; + } + + _checkOptions (options) { if (typeof options.operation !== 'string') { throw new Error(`Formula expects 'operation' in dataview options`); } @@ -64,12 +74,6 @@ module.exports = class Formula extends BaseDataview { if (options.operation !== 'count' && typeof options.column !== 'string') { throw new Error(`Formula expects 'column' in dataview options`); } - - this.query = query; - this.queries = queries; - this.column = options.column || '1'; - this.operation = options.operation; - this._isFloatColumn = null; } From 64c3e68303c76bc913917b6fba672cdf2a41200a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Fri, 15 Sep 2017 14:48:54 +0200 Subject: [PATCH 139/159] Fix double declaration of 'result' --- lib/cartodb/models/dataview/formula.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/cartodb/models/dataview/formula.js b/lib/cartodb/models/dataview/formula.js index b167b87e..1ad08684 100644 --- a/lib/cartodb/models/dataview/formula.js +++ b/lib/cartodb/models/dataview/formula.js @@ -108,13 +108,13 @@ module.exports = class Formula extends BaseDataview { return callback(null, formulaSql); } - format (result) { + format (res) { const { result = 0, nulls_count = 0, nans_count = 0, infinities_count = 0 - } = result.rows[0] || {}; + } = res.rows[0] || {}; return { operation: this.operation, From 61ea05d1c2b73cf0d0663bc4d4648cc83f13c5f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Fri, 15 Sep 2017 14:51:02 +0200 Subject: [PATCH 140/159] Do not assign a value by default for special float values counters --- lib/cartodb/models/dataview/formula.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/cartodb/models/dataview/formula.js b/lib/cartodb/models/dataview/formula.js index 1ad08684..c0829fa5 100644 --- a/lib/cartodb/models/dataview/formula.js +++ b/lib/cartodb/models/dataview/formula.js @@ -112,8 +112,8 @@ module.exports = class Formula extends BaseDataview { const { result = 0, nulls_count = 0, - nans_count = 0, - infinities_count = 0 + nans_count, + infinities_count } = res.rows[0] || {}; return { From f63fab40ed5ae6715f3f5b85ebc24b2bcd8b1ce5 Mon Sep 17 00:00:00 2001 From: Simon Date: Mon, 18 Sep 2017 11:34:18 +0200 Subject: [PATCH 141/159] Removing 'self' vars using arrow functions --- lib/cartodb/models/dataview/aggregation.js | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/lib/cartodb/models/dataview/aggregation.js b/lib/cartodb/models/dataview/aggregation.js index 7f6c198a..b4c59af2 100644 --- a/lib/cartodb/models/dataview/aggregation.js +++ b/lib/cartodb/models/dataview/aggregation.js @@ -229,8 +229,6 @@ module.exports = class Aggregation extends BaseDataview { } sql (psql, override, callback) { - const self = this; - if (!callback) { callback = override; override = {}; @@ -238,11 +236,11 @@ module.exports = class Aggregation extends BaseDataview { if (this._shouldCheckColumnType()) { this._isFloatColumn = false; - this.getColumnType(psql, this.aggregationColumn, this.queries.no_filters, function (err, type) { + this.getColumnType(psql, this.aggregationColumn, this.queries.no_filters, (err, type) => { if (!err && !!type) { - self._isFloatColumn = type.float; + this._isFloatColumn = type.float; } - self.sql(psql, override, callback); + this.sql(psql, override, callback); }); return null; } @@ -295,7 +293,6 @@ module.exports = class Aggregation extends BaseDataview { } search (psql, userQuery, callback) { - const self = this; const escapedUserQuery = psql.escapeLiteral(`%${userQuery}%`); const value = this.aggregation !== 'count' && this.aggregationColumn ? `${this.aggregation}(${this.aggregationColumn})` : @@ -319,12 +316,12 @@ module.exports = class Aggregation extends BaseDataview { debug(query); - psql.query(query, function(err, result) { + psql.query(query, (err, result) => { if (err) { return callback(err, result); } - return callback(null, {type: self.getType(), categories: result.rows }); + return callback(null, {type: this.getType(), categories: result.rows }); }, true); // use read-only transaction } From 222cfb90fdbc949160656b7a75dde1e44f5250c7 Mon Sep 17 00:00:00 2001 From: Simon Date: Mon, 18 Sep 2017 12:20:59 +0200 Subject: [PATCH 142/159] Removing 'self' vars using arrow functions --- lib/cartodb/models/dataview/formula.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/cartodb/models/dataview/formula.js b/lib/cartodb/models/dataview/formula.js index c0829fa5..daaeaf0c 100644 --- a/lib/cartodb/models/dataview/formula.js +++ b/lib/cartodb/models/dataview/formula.js @@ -78,8 +78,6 @@ module.exports = class Formula extends BaseDataview { sql (psql, override, callback) { - const self = this; - if (!callback) { callback = override; override = {}; @@ -87,11 +85,11 @@ module.exports = class Formula extends BaseDataview { if (this._isFloatColumn === null) { this._isFloatColumn = false; - this.getColumnType(psql, this.column, this.queries.no_filters, function (err, type) { + this.getColumnType(psql, this.column, this.queries.no_filters, (err, type) => { if (!err && !!type) { - self._isFloatColumn = type.float; + this._isFloatColumn = type.float; } - self.sql(psql, override, callback); + this.sql(psql, override, callback); }); return null; } From a5d9bfa0ec261abe178776c4140fa1e0ffacee57 Mon Sep 17 00:00:00 2001 From: Raul Ochoa Date: Mon, 18 Sep 2017 10:34:04 +0000 Subject: [PATCH 143/159] Upgrades windshaft to 3.3.2 --- NEWS.md | 1 + package.json | 2 +- yarn.lock | 359 +++++++++++++++++++++++++++++++++++++-------------- 3 files changed, 265 insertions(+), 97 deletions(-) diff --git a/NEWS.md b/NEWS.md index 15720ece..0aad89fd 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,6 +2,7 @@ ## 3.12.10 Released 2017-mm-dd + - Upgrades windshaft to [3.3.2](https://github.com/CartoDB/windshaft/releases/tag/3.3.2). ## 3.12.9 Released 2017-09-07 diff --git a/package.json b/package.json index 12430490..7e4f4b52 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "step-profiler": "~0.3.0", "turbo-carto": "0.19.2", "underscore": "~1.6.0", - "windshaft": "3.3.1", + "windshaft": "3.3.2", "yargs": "~5.0.0" }, "devDependencies": { diff --git a/yarn.lock b/yarn.lock index 8270d25a..a9ba816b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,7 +2,7 @@ # yarn lockfile v1 -abaculus@cartodb/abaculus#2.0.3-cdb1: +"abaculus@github:cartodb/abaculus#2.0.3-cdb1": version "2.0.3-cdb1" resolved "https://codeload.github.com/cartodb/abaculus/tar.gz/f5f34e1c80cdd8d49edd1d6fe3b2220ab2e23aaf" dependencies: @@ -10,7 +10,11 @@ abaculus@cartodb/abaculus#2.0.3-cdb1: mapnik "~3.5.0" sphericalmercator "1.0.x" -abbrev@1, abbrev@1.0.x: +abbrev@1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.0.tgz#d0554c2256636e2f56e7c2e5ad183f859428d81f" + +abbrev@1.0.x: version "1.0.9" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135" @@ -48,10 +52,6 @@ ansi-styles@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" -ap@~0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/ap/-/ap-0.2.0.tgz#ae0942600b29912f0d2b14ec60c45e8f330b6110" - aproba@^1.0.3: version "1.1.2" resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.1.2.tgz#45c6629094de4e96f693ef7eab74ae079c240fc1" @@ -205,7 +205,7 @@ camshaft@0.58.1: dot "^1.0.3" request "^2.69.0" -canvas@cartodb/node-canvas#1.6.2-cdb2: +"canvas@github:cartodb/node-canvas#1.6.2-cdb2": version "1.6.2-cdb2" resolved "https://codeload.github.com/cartodb/node-canvas/tar.gz/8acf04557005c633f9e68524488a2657c04f3766" dependencies: @@ -223,15 +223,15 @@ carto@0.16.3: semver "^5.1.0" yargs "^4.2.0" -carto@CartoDB/carto#0.15.1-cdb1: +"carto@github:cartodb/carto#0.15.1-cdb1": version "0.15.1-cdb1" - resolved "https://codeload.github.com/CartoDB/carto/tar.gz/8050ec843f1f32a6469e5d1cf49602773015d398" + resolved "https://codeload.github.com/cartodb/carto/tar.gz/8050ec843f1f32a6469e5d1cf49602773015d398" dependencies: mapnik-reference "~6.0.2" optimist "~0.6.0" underscore "~1.6.0" -carto@cartodb/carto#0.15.1-cdb3: +"carto@github:cartodb/carto#0.15.1-cdb3": version "0.15.1-cdb3" resolved "https://codeload.github.com/cartodb/carto/tar.gz/945f5efb74fd1af1f5e1f69f409f9567f94fb5a7" dependencies: @@ -343,12 +343,16 @@ combined-stream@^1.0.5, combined-stream@~1.0.5: dependencies: delayed-stream "~1.0.0" -commander@2.9.0, commander@^2.9.0: +commander@2.9.0: version "2.9.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4" dependencies: graceful-readlink ">= 1.0.0" +commander@^2.9.0: + version "2.11.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563" + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -368,8 +372,8 @@ content-disposition@0.5.1: resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.1.tgz#87476c6a67c8daa87e32e87616df883ba7fb071b" content-type@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.2.tgz#b7d113aee7a8dd27bd21133c4dc2529df1721eed" + version "1.0.4" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" cookie-signature@1.0.6: version "1.0.6" @@ -403,7 +407,7 @@ date-now@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b" -debug@2.2.0, debug@^2.2.0, debug@~2.2.0: +debug@2.2.0, debug@~2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da" dependencies: @@ -421,6 +425,12 @@ debug@^1.0.4: dependencies: ms "2.0.0" +debug@^2.2.0: + version "2.6.8" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc" + dependencies: + ms "2.0.0" + decamelize@^1.0.0, decamelize@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" @@ -431,7 +441,7 @@ deep-eql@^0.1.3: dependencies: type-detect "0.1.1" -deep-equal@^1.0.0: +deep-equal@^1.0.0, deep-equal@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" @@ -443,6 +453,17 @@ deep-is@~0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" +define-properties@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.2.tgz#83a73f2fea569898fb737193c8f873caf6d45c94" + dependencies: + foreach "^2.0.5" + object-keys "^1.0.8" + +defined@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" + delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -491,7 +512,11 @@ domutils@1.5: dom-serializer "0" domelementtype "1" -dot@^1.0.3, dot@~1.0.2: +dot@^1.0.3: + version "1.1.2" + resolved "https://registry.yarnpkg.com/dot/-/dot-1.1.2.tgz#c7377019fc4e550798928b2b9afeb66abfa1f2f9" + +dot@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/dot/-/dot-1.0.3.tgz#f8750bfb6b03c7664eb0e6cb1eb4c66419af9427" @@ -525,6 +550,24 @@ error-ex@^1.2.0: dependencies: is-arrayish "^0.2.1" +es-abstract@^1.5.0: + version "1.8.2" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.8.2.tgz#25103263dc4decbda60e0c737ca32313518027ee" + dependencies: + es-to-primitive "^1.1.1" + function-bind "^1.1.1" + has "^1.0.1" + is-callable "^1.1.3" + is-regex "^1.0.4" + +es-to-primitive@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.1.1.tgz#45355248a88979034b6792e19bb81f2b7975dd0d" + dependencies: + is-callable "^1.1.1" + is-date-object "^1.0.1" + is-symbol "^1.0.1" + es6-promise@3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-3.1.2.tgz#795e25ceb47f7babb263d151afbedd92d18e6a07" @@ -636,6 +679,16 @@ find-up@^1.0.0: path-exists "^2.0.0" pinkie-promise "^2.0.0" +for-each@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.2.tgz#2c40450b9348e97f281322593ba96704b9abd4d4" + dependencies: + is-function "~1.0.0" + +foreach@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" + forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" @@ -649,8 +702,8 @@ form-data@~2.1.1: mime-types "^2.1.12" forwarded@~0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.0.tgz#19ef9874c4ae1c297bcf078fde63a09b66a84363" + version "0.1.2" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" fresh@0.3.0: version "0.3.0" @@ -677,6 +730,10 @@ fstream@^1.0.0, fstream@^1.0.10, fstream@^1.0.2: mkdirp ">=0.5 0" rimraf "2" +function-bind@^1.0.2, function-bind@^1.1.1, function-bind@~1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + gauge@~2.7.3: version "2.7.4" resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" @@ -691,11 +748,11 @@ gauge@~2.7.3: wide-align "^1.1.0" gdal@~0.9.2: - version "0.9.4" - resolved "https://registry.yarnpkg.com/gdal/-/gdal-0.9.4.tgz#6dad4abb8ffb3e0d51150fb7935ad7c622c81818" + version "0.9.6" + resolved "https://registry.yarnpkg.com/gdal/-/gdal-0.9.6.tgz#0cf75d830d35847b4274368b10e04a925321a0ba" dependencies: - nan "~2.5.0" - node-pre-gyp "~0.6.27" + nan "~2.6.2" + node-pre-gyp "~0.6.36" generate-function@^2.0.0: version "2.0.0" @@ -733,7 +790,7 @@ getpass@^0.1.1: dependencies: assert-plus "^1.0.0" -glob@7.1.1, glob@^7.0.5, glob@^7.1.1: +glob@7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8" dependencies: @@ -764,6 +821,17 @@ glob@^6.0.1: once "^1.3.0" path-is-absolute "^1.0.0" +glob@^7.0.5, glob@^7.1.1, glob@~7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + graceful-fs@^4.1.2: version "4.1.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" @@ -834,6 +902,12 @@ has-unicode@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" +has@^1.0.1, has@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.1.tgz#8461733f538b0837c9361e39a9ab9e9704dc2f28" + dependencies: + function-bind "^1.0.2" + hawk@~3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4" @@ -928,15 +1002,27 @@ is-builtin-module@^1.0.0: dependencies: builtin-modules "^1.0.0" +is-callable@^1.1.1, is-callable@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.3.tgz#86eb75392805ddc33af71c92a0eedf74ee7604b2" + +is-date-object@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16" + is-fullwidth-code-point@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" dependencies: number-is-nan "^1.0.0" +is-function@~1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-function/-/is-function-1.0.1.tgz#12cfb98b65b57dd3d193a3121f5f6e2f437602b5" + is-my-json-valid@^2.12.4: - version "2.16.0" - resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.16.0.tgz#f079dd9bfdae65ee2038aae8acbc86ab109e3693" + version "2.16.1" + resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.16.1.tgz#5a846777e2c2620d1e69104e5d3a03b1f6088f11" dependencies: generate-function "^2.0.0" generate-object-property "^1.1.0" @@ -947,6 +1033,16 @@ is-property@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84" +is-regex@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491" + dependencies: + has "^1.0.1" + +is-symbol@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.1.tgz#3cc59f00025194b6ab2e38dbae6689256b660572" + is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" @@ -991,16 +1087,16 @@ istanbul@~0.4.3: wordwrap "^1.0.0" js-base64@^2.1.9: - version "2.1.9" - resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.1.9.tgz#f0e80ae039a4bd654b5f281fc93f04a914a7fcce" + version "2.3.2" + resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.3.2.tgz#a79a923666372b580f8e27f51845c6f7e8fbfbaf" js-string-escape@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/js-string-escape/-/js-string-escape-1.0.1.tgz#e2625badbc0d67c7533e9edc1068c587ae4137ef" js-yaml@3.x, js-yaml@^3.4.6: - version "3.9.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.9.1.tgz#08775cebdfdd359209f0d2acd383c8f86a6904a0" + version "3.10.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.10.0.tgz#2e78441646bd4682e963f22b6e92823c309c62dc" dependencies: argparse "^1.0.7" esprima "^4.0.0" @@ -1231,17 +1327,17 @@ millstone@0.6.17: underscore "~1.6.0" zipfile "~0.5.5" -mime-db@~1.29.0: - version "1.29.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.29.0.tgz#48d26d235589651704ac5916ca06001914266878" +mime-db@~1.30.0: + version "1.30.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.30.0.tgz#74c643da2dd9d6a45399963465b26d5ca7d71f01" mime-types@^2.1.12, mime-types@~2.1.15, mime-types@~2.1.6, mime-types@~2.1.7: - version "2.1.16" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.16.tgz#2b858a52e5ecd516db897ac2be87487830698e23" + version "2.1.17" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.17.tgz#09d7a393f03e995a79f8af857b70a9e0ab16557a" dependencies: - mime-db "~1.29.0" + mime-db "~1.30.0" -mime@1.3.4, mime@~1.3.4: +mime@1.3.4: version "1.3.4" resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.4.tgz#115f9e3b6b3daf2959983cb38f149a2d40eb5d53" @@ -1249,20 +1345,28 @@ mime@~1.2.11: version "1.2.11" resolved "https://registry.yarnpkg.com/mime/-/mime-1.2.11.tgz#58203eed86e3a5ef17aed2b7d9ebd47f0a60dd10" -"minimatch@2 || 3", minimatch@^3.0.0, minimatch@^3.0.2, minimatch@~3.0.2: +mime@~1.3.4: + version "1.3.6" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.6.tgz#591d84d3653a6b0b4a3b9df8de5aa8108e72e5e0" + +"minimatch@2 || 3", minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.4, minimatch@~3.0.2: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" dependencies: brace-expansion "^1.1.7" -minimist@0.0.8, minimist@~0.0.1: +minimist@0.0.8: version "0.0.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" -minimist@^1.2.0: +minimist@^1.2.0, minimist@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" +minimist@~0.0.1: + version "0.0.10" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" + minimist@~0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.2.0.tgz#4dffe525dae2b864c66c2e23c6271d7afdecefce" @@ -1313,17 +1417,17 @@ mv@~2: ncp "~2.0.0" rimraf "~2.4.0" -nan@^2.0.8, nan@^2.3.4, nan@^2.4.0, nan@~2.6.2: - version "2.6.2" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.6.2.tgz#e4ff34e6c95fdfb5aecc08de6596f43605a7db45" +nan@^2.0.8, nan@^2.3.4, nan@^2.4.0, nan@~2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.7.0.tgz#d95bf721ec877e08db276ed3fc6eb78f9083ad46" nan@~2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.4.0.tgz#fb3c59d45fe4effe215f0b890f8adf6eb32d2232" -nan@~2.5.0: - version "2.5.1" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.5.1.tgz#d5b01691253326a97a2bbee9e61c55d8d60351e2" +nan@~2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.6.2.tgz#e4ff34e6c95fdfb5aecc08de6596f43605a7db45" ncp@~2.0.0: version "2.0.0" @@ -1344,9 +1448,9 @@ nock@~2.11.0: mkdirp "^0.5.0" propagate "0.3.x" -node-pre-gyp@~0.6.27, node-pre-gyp@~0.6.30, node-pre-gyp@~0.6.36: - version "0.6.36" - resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.36.tgz#db604112cb74e0d477554e9b505b17abddfab786" +node-pre-gyp@~0.6.30, node-pre-gyp@~0.6.36, node-pre-gyp@~0.6.37: + version "0.6.37" + resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.37.tgz#3c872b236b2e266e4140578fe1ee88f693323a05" dependencies: mkdirp "^0.5.1" nopt "^4.0.1" @@ -1355,6 +1459,7 @@ node-pre-gyp@~0.6.27, node-pre-gyp@~0.6.30, node-pre-gyp@~0.6.36: request "^2.81.0" rimraf "^2.6.1" semver "^5.3.0" + tape "^4.6.3" tar "^2.2.1" tar-pack "^3.4.0" @@ -1409,6 +1514,14 @@ object-assign@^4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" +object-inspect@~1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.3.0.tgz#5b1eb8e6742e2ee83342a637034d844928ba2f6d" + +object-keys@^1.0.8: + version "1.0.11" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.11.tgz#c54601778ad560f1142ce0e01bcca8b56d13426d" + object-keys@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-0.4.0.tgz#28a6aae7428dd2c3a92f3d95f21335dd204e0336" @@ -1475,8 +1588,8 @@ parse-json@^2.2.0: error-ex "^1.2.0" parseurl@~1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.1.tgz#c8ab8c9223ba34888aa64a297b28853bec18da56" + version "1.3.2" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3" path-exists@^2.0.0: version "2.1.0" @@ -1488,6 +1601,10 @@ path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" +path-parse@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1" + path-to-regexp@0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" @@ -1516,16 +1633,15 @@ pg-pool@1.*: object-assign "4.1.0" pg-types@1.*: - version "1.12.0" - resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-1.12.0.tgz#8ad3b7b897e3fd463e62de241ad5fc640b4a66f0" + version "1.12.1" + resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-1.12.1.tgz#d64087e3903b58ffaad279e7595c52208a14c3d2" dependencies: - ap "~0.2.0" postgres-array "~1.0.0" postgres-bytea "~1.0.0" postgres-date "~1.0.0" postgres-interval "^1.1.0" -pg@cartodb/node-postgres#6.1.6-cdb1: +"pg@github:cartodb/node-postgres#6.1.6-cdb1": version "6.1.6" resolved "https://codeload.github.com/cartodb/node-postgres/tar.gz/3eef52dd1e655f658a4ee8ac5697688b3ecfed44" dependencies: @@ -1700,7 +1816,7 @@ read-pkg@^1.0.0: normalize-package-data "^2.3.2" path-type "^1.0.0" -readable-stream@1.1, readable-stream@~1.1.9: +readable-stream@1.1: version "1.1.13" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.13.tgz#f6eef764f514c89e2b9e23146a75ba106756d23e" dependencies: @@ -1730,6 +1846,15 @@ readable-stream@~1.0.2: isarray "0.0.1" string_decoder "~0.10.x" +readable-stream@~1.1.9: + version "1.1.14" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "0.0.1" + string_decoder "~0.10.x" + redis-mpool@0.4.1, redis-mpool@~0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/redis-mpool/-/redis-mpool-0.4.1.tgz#d917c0a4ed57a1291a9c6eb35434e6c0b7046f80" @@ -1747,32 +1872,7 @@ repeat-string@^1.5.2: version "1.6.1" resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" -request@2.x, request@^2.55.0, request@~2.79.0: - version "2.79.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.79.0.tgz#4dfe5bf6be8b8cdc37fcf93e04b65577722710de" - dependencies: - aws-sign2 "~0.6.0" - aws4 "^1.2.1" - caseless "~0.11.0" - combined-stream "~1.0.5" - extend "~3.0.0" - forever-agent "~0.6.1" - form-data "~2.1.1" - har-validator "~2.0.6" - hawk "~3.1.3" - http-signature "~1.1.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.7" - oauth-sign "~0.8.1" - qs "~6.3.0" - stringstream "~0.0.4" - tough-cookie "~2.3.0" - tunnel-agent "~0.4.1" - uuid "^3.0.0" - -request@^2.69.0, request@^2.81.0: +request@2.x, request@^2.55.0, request@^2.69.0, request@^2.81.0: version "2.81.0" resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0" dependencies: @@ -1799,6 +1899,31 @@ request@^2.69.0, request@^2.81.0: tunnel-agent "^0.6.0" uuid "^3.0.0" +request@~2.79.0: + version "2.79.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.79.0.tgz#4dfe5bf6be8b8cdc37fcf93e04b65577722710de" + dependencies: + aws-sign2 "~0.6.0" + aws4 "^1.2.1" + caseless "~0.11.0" + combined-stream "~1.0.5" + extend "~3.0.0" + forever-agent "~0.6.1" + form-data "~2.1.1" + har-validator "~2.0.6" + hawk "~3.1.3" + http-signature "~1.1.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.7" + oauth-sign "~0.8.1" + qs "~6.3.0" + stringstream "~0.0.4" + tough-cookie "~2.3.0" + tunnel-agent "~0.4.1" + uuid "^3.0.0" + require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" @@ -1811,6 +1936,18 @@ resolve@1.1.x: version "1.1.7" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" +resolve@~1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.4.0.tgz#a75be01c53da25d934a98ebd0e4c4a7312f92a86" + dependencies: + path-parse "^1.0.5" + +resumer@~0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/resumer/-/resumer-0.0.0.tgz#f1e8f461e4064ba39e82af3cdc2a8c893d076759" + dependencies: + through "~2.3.4" + right-align@^0.1.1: version "0.1.3" resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef" @@ -1818,8 +1955,8 @@ right-align@^0.1.1: align-text "^0.1.1" rimraf@2, rimraf@^2.5.1, rimraf@^2.6.1: - version "2.6.1" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.1.tgz#c2338ec643df7a1b7fe5c54fa86f57428a55f33d" + version "2.6.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" dependencies: glob "^7.0.5" @@ -1928,8 +2065,8 @@ source-map@^0.4.4: amdefine ">=0.0.4" source-map@^0.5.1, source-map@^0.5.6, source-map@~0.5.1: - version "0.5.6" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" source-map@~0.2.0: version "0.2.0" @@ -1955,10 +2092,14 @@ speedometer@~0.1.2: version "0.1.4" resolved "https://registry.yarnpkg.com/speedometer/-/speedometer-0.1.4.tgz#9876dbd2a169d3115402d48e6ea6329c8816a50d" -sphericalmercator@1.0.4, sphericalmercator@1.0.x, sphericalmercator@~1.0.1, sphericalmercator@~1.0.4: +sphericalmercator@1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/sphericalmercator/-/sphericalmercator-1.0.4.tgz#baad4e34187f06e87f2e92fc1280199fa1b01d4e" +sphericalmercator@1.0.x, sphericalmercator@~1.0.1, sphericalmercator@~1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/sphericalmercator/-/sphericalmercator-1.0.5.tgz#ddc5a049e360e000d0fad9fc22c4071882584980" + split@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/split/-/split-1.0.1.tgz#605bd9be303aa59fb35f9229fbea0ddec9ea07d9" @@ -1970,11 +2111,11 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" "sqlite3@2.x || 3.x": - version "3.1.9" - resolved "https://registry.yarnpkg.com/sqlite3/-/sqlite3-3.1.9.tgz#b2e7fbaa348380318d3834323918c3c351b8bf18" + version "3.1.11" + resolved "https://registry.yarnpkg.com/sqlite3/-/sqlite3-3.1.11.tgz#84421b181b3e9f4939b7a3d712263d7946cfc96d" dependencies: - nan "~2.6.2" - node-pre-gyp "~0.6.36" + nan "~2.7.0" + node-pre-gyp "~0.6.37" srs@1.x: version "1.2.0" @@ -2026,6 +2167,14 @@ string-width@^1.0.1, string-width@^1.0.2: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" +string.prototype.trim@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.1.2.tgz#d04de2c89e137f4d7d206f086b5ed2fae6be8cea" + dependencies: + define-properties "^1.1.2" + es-abstract "^1.5.0" + function-bind "^1.0.2" + string_decoder@~0.10.x: version "0.10.31" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" @@ -2076,6 +2225,24 @@ supports-color@^3.1.0, supports-color@^3.1.2, supports-color@^3.2.3: dependencies: has-flag "^1.0.0" +tape@^4.6.3: + version "4.8.0" + resolved "https://registry.yarnpkg.com/tape/-/tape-4.8.0.tgz#f6a9fec41cc50a1de50fa33603ab580991f6068e" + dependencies: + deep-equal "~1.0.1" + defined "~1.0.0" + for-each "~0.3.2" + function-bind "~1.1.0" + glob "~7.1.2" + has "~1.0.1" + inherits "~2.0.3" + minimist "~1.2.0" + object-inspect "~1.3.0" + resolve "~1.4.0" + resumer "~0.0.0" + string.prototype.trim "~1.1.2" + through "~2.3.8" + tar-pack@^3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/tar-pack/-/tar-pack-3.4.0.tgz#23be2d7f671a8339376cbdb0b8fe3fdebf317984" @@ -2104,11 +2271,11 @@ through2@~0.2.3: readable-stream "~1.1.9" xtend "~2.1.1" -through@2: +through@2, through@~2.3.4, through@~2.3.8: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" -tilelive-bridge@cartodb/tilelive-bridge#2.3.1-cdb4: +"tilelive-bridge@github:cartodb/tilelive-bridge#2.3.1-cdb4": version "2.3.1-cdb4" resolved "https://codeload.github.com/cartodb/tilelive-bridge/tar.gz/faa2b638da2d119b78281575d40255cb523f6ca6" dependencies: @@ -2116,7 +2283,7 @@ tilelive-bridge@cartodb/tilelive-bridge#2.3.1-cdb4: mapnik-pool "~0.1.3" sphericalmercator "1.0.x" -tilelive-mapnik@cartodb/tilelive-mapnik#0.6.18-cdb3: +"tilelive-mapnik@github:cartodb/tilelive-mapnik#0.6.18-cdb3": version "0.6.18-cdb3" resolved "https://codeload.github.com/cartodb/tilelive-mapnik/tar.gz/23bd1c31dd57d0b76c86b9f1eaf62462b3c17d01" dependencies: @@ -2281,9 +2448,9 @@ window-size@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.2.0.tgz#b4315bb4214a3d7058ebeee892e13fa24d98b075" -windshaft@3.3.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/windshaft/-/windshaft-3.3.1.tgz#b5557fa6b0cfa13920904f57206b86f7aa054f74" +windshaft@3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/windshaft/-/windshaft-3.3.2.tgz#72efe0dbc0d8d4bcba4211fdabd15dd2e0799df9" dependencies: abaculus cartodb/abaculus#2.0.3-cdb1 canvas cartodb/node-canvas#1.6.2-cdb2 From f133d983e85dfe9831cfb3d7d56653028f8c575f Mon Sep 17 00:00:00 2001 From: Raul Ochoa Date: Mon, 18 Sep 2017 12:46:40 +0000 Subject: [PATCH 144/159] Release 3.12.10 --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 0aad89fd..037467f8 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,7 +1,7 @@ # Changelog ## 3.12.10 -Released 2017-mm-dd +Released 2017-09-18 - Upgrades windshaft to [3.3.2](https://github.com/CartoDB/windshaft/releases/tag/3.3.2). ## 3.12.9 From 33121871b0df4d90f8d6712301e6509f57b5618e Mon Sep 17 00:00:00 2001 From: Raul Ochoa Date: Mon, 18 Sep 2017 12:47:37 +0000 Subject: [PATCH 145/159] Stubs next version --- NEWS.md | 3 +++ package.json | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 037467f8..2efa11b6 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,8 @@ # Changelog +## 3.12.11 +Released 2017-mm-dd + ## 3.12.10 Released 2017-09-18 - Upgrades windshaft to [3.3.2](https://github.com/CartoDB/windshaft/releases/tag/3.3.2). diff --git a/package.json b/package.json index 7e4f4b52..9c70ad72 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "windshaft-cartodb", - "version": "3.12.10", + "version": "3.12.11", "description": "A map tile server for CartoDB", "keywords": [ "cartodb" From 5cc31cabe2166b3a7599fb9eb3326905f94f46a4 Mon Sep 17 00:00:00 2001 From: Raul Ochoa Date: Fri, 22 Sep 2017 15:41:58 +0000 Subject: [PATCH 146/159] Fix and enable some old tests related to analyses use cases --- .../analysis/analysis-layers-use-cases.js | 716 ++++++++---------- 1 file changed, 306 insertions(+), 410 deletions(-) diff --git a/test/acceptance/analysis/analysis-layers-use-cases.js b/test/acceptance/analysis/analysis-layers-use-cases.js index 83c2f535..b08c264a 100644 --- a/test/acceptance/analysis/analysis-layers-use-cases.js +++ b/test/acceptance/analysis/analysis-layers-use-cases.js @@ -5,34 +5,34 @@ var TestClient = require('../../support/test-client'); var dot = require('dot'); var debug = require('debug')('windshaft:cartodb:test'); -describe('analysis-layers use cases', function() { +describe('analysis-layers use cases', function () { - var multitypeStyleTemplate = dot.template([ - "#points['mapnik::geometry_type'=1] {", - " marker-fill-opacity: {{=it._opacity}};", - " marker-line-color: #FFF;", - " marker-line-width: 0.5;", - " marker-line-opacity: {{=it._opacity}};", - " marker-placement: point;", - " marker-type: ellipse;", - " marker-width: 8;", - " marker-fill: {{=it._color}};", - " marker-allow-overlap: true;", - "}", - "#lines['mapnik::geometry_type'=2] {", - " line-color: {{=it._color}};", - " line-width: 2;", - " line-opacity: {{=it._opacity}};", - "}", - "#polygons['mapnik::geometry_type'=3] {", - " polygon-fill: {{=it._color}};", - " polygon-opacity: {{=it._opacity}};", - " line-color: #FFF;", - " line-width: 0.5;", - " line-opacity: {{=it._opacity}};", - "}" - ].join('\n')); + var multitypeStyleTemplate = dot.template( + `#points['mapnik::geometry_type'=1] { + marker-fill-opacity: {{=it._opacity}}; + marker-line-color: #FFF; + marker-line-width: 0.5; + marker-line-opacity: {{=it._opacity}}; + marker-placement: point; + marker-type: ellipse; + marker-width: 8; + marker-fill: {{=it._color}}; + marker-allow-overlap: true; + } + #lines['mapnik::geometry_type'=2] { + line-color: {{=it._color}}; + line-width: 2; + line-opacity: {{=it._opacity}}; + } + #polygons['mapnik::geometry_type'=3] { + polygon-fill: {{=it._color}}; + polygon-opacity: {{=it._opacity}}; + line-color: #FFF; + line-width: 0.5; + line-opacity: {{=it._opacity}}; + }` + ); function cartocss(color, opacity) { @@ -47,18 +47,53 @@ describe('analysis-layers use cases', function() { version: '1.5.0', layers: layers, dataviews: dataviews || {}, - analysis: analysis || [] + analyses: analysis || [] }; } - function analysisDef(analysis) { - return JSON.stringify(analysis); - } - var DEFAULT_MULTITYPE_STYLE = cartocss(); var TILE_ANALYSIS_TABLES = { z: 14, x: 8023, y: 6177 }; + var pointInPolygonDef = { + id: 'a1', + type: 'point-in-polygon', + params: { + points_source: { + type: 'source', + params: { + query: 'select * from analysis_rent_listings' + } + }, + polygons_source: { + type: 'buffer', + params: { + source: { + type: 'source', + params: { + query: 'select * from analysis_banks' + } + }, + radius: 250 + } + } + } + }; + + var bufferDef = { + id: 'b1', + type: 'buffer', + params: { + source: { + type: 'source', + params: { + query: 'select * from analysis_banks' + } + }, + radius: 250 + } + }; + var useCases = [ { desc: '1 mapnik layer', @@ -68,7 +103,7 @@ describe('analysis-layers use cases', function() { { type: 'cartodb', options: { - sql: "select * from analysis_rent_listings", + sql: 'select * from analysis_rent_listings', cartocss: DEFAULT_MULTITYPE_STYLE, cartocss_version: '2.3.0' } @@ -83,7 +118,7 @@ describe('analysis-layers use cases', function() { { type: 'cartodb', options: { - sql: "select * from analysis_banks", + sql: 'select * from analysis_banks', cartocss: cartocss('#2167AB'), cartocss_version: '2.3.0' } @@ -91,7 +126,7 @@ describe('analysis-layers use cases', function() { { type: 'cartodb', options: { - sql: "select * from analysis_rent_listings", + sql: 'select * from analysis_rent_listings', cartocss: DEFAULT_MULTITYPE_STYLE, cartocss_version: '2.3.0' } @@ -105,30 +140,27 @@ describe('analysis-layers use cases', function() { { type: 'cartodb', options: { - sql: "select * from analysis_rent_listings", + sql: 'select * from analysis_rent_listings', cartocss: DEFAULT_MULTITYPE_STYLE, cartocss_version: '2.3.0' } }, { - type: 'analysis', + type: 'cartodb', options: { - def: analysisDef({ - "type": "buffer", - "params": { - "source": { - "type": "source", - "params": { - "query": "select * from analysis_banks" - } - }, - "radius": 250 - } - }), - cartocss: cartocss('black', 0.5) + source: { + id: 'b1' + }, + cartocss: DEFAULT_MULTITYPE_STYLE, + cartocss_version: '2.3.0' } } - ]) + ], + {}, + [ + bufferDef + ] + ) }, { @@ -137,235 +169,98 @@ describe('analysis-layers use cases', function() { { type: 'cartodb', options: { - sql: "select * from analysis_rent_listings", + sql: 'select * from analysis_rent_listings', cartocss: DEFAULT_MULTITYPE_STYLE, cartocss_version: '2.3.0' } }, { - type: 'analysis', + type: 'cartodb', options: { - def: analysisDef({ - "type": "point-in-polygon", - "params": { - "pointsSource": { - "type": "source", - "params": { - "query": "select * from analysis_rent_listings" - } - }, - "polygonsSource": { - "type": "buffer", - "params": { - "source": { - "type": "source", - "params": { - "query": "select * from analysis_banks" - } - }, - "radius": 250 - } - } - } - }), - cartocss: cartocss('green', 1.0) + source: { + id: 'a1' + }, + cartocss: DEFAULT_MULTITYPE_STYLE, + cartocss_version: '2.3.0' } } - ]) + ], + {}, + [ + pointInPolygonDef + ] + ) }, { desc: 'point-in-polygon from buffer atm-machines and rent listings + rent listings', - mapConfig: mapConfig([ - { - type: 'analysis', - options: { - def: analysisDef({ - "type": "point-in-polygon", - "params": { - "pointsSource": { - "type": "source", - "params": { - "query": "select * from analysis_rent_listings" - } - }, - "polygonsSource": { - "type": "buffer", - "params": { - "source": { - "type": "source", - "params": { - "query": "select * from analysis_banks" - } - }, - "radius": 250 - } - } - } - }), - cartocss: cartocss('green', 1.0) - } - }, - { - type: 'cartodb', - options: { - sql: "select * from analysis_rent_listings", - cartocss: DEFAULT_MULTITYPE_STYLE, - cartocss_version: '2.3.0' - } - } - ]) - }, - - { - desc: 'buffer + point-in-polygon from buffer atm-machines and rent listings + rent listings', - mapConfig: mapConfig([ - { - type: 'cartodb', - options: { - sql: "select * from analysis_rent_listings", - cartocss: DEFAULT_MULTITYPE_STYLE, - cartocss_version: '2.3.0' - } - }, - { - type: 'analysis', - options: { - def: analysisDef({ - "type": "buffer", - "params": { - "source": { - "type": "source", - "params": { - "query": "select * from analysis_banks" - } - }, - "radius": 300 - } - }), - cartocss: cartocss('magenta', 0.5) - } - }, - { - type: 'analysis', - options: { - def: analysisDef({ - "type": "point-in-polygon", - "params": { - "pointsSource": { - "type": "source", - "params": { - "query": "select * from analysis_rent_listings" - } - }, - "polygonsSource": { - "type": "buffer", - "params": { - "source": { - "type": "source", - "params": { - "query": "select * from analysis_banks" - } - }, - "radius": 300 - } - } - } - }), - cartocss: cartocss('green', 1.0) - } - } - ]) - }, - - { - skip: true, - desc: 'buffer + point-in-polygon from buffer atm-machines and rent listings + rent listings', - mapConfig: mapConfig([ - { - type: 'cartodb', - options: { - "source": { id: "a" }, - "cartocss": DEFAULT_MULTITYPE_STYLE, - "cartocss_version": "2.3.0" - } - }, - { - type: 'cartodb', - options: { - "source": { id: "b1" }, - "cartocss": cartocss('green', 1.0), - "cartocss_version": "2.3.0" - } - }, - { - type: 'cartodb', - options: { - "source": { id: "b2" }, - "cartocss": cartocss('magenta', 0.5), - "cartocss_version": "2.3.0" - } - } - ], - [ - { - id: "b2", - options: { - def: analysisDef({ - "type": "count-in-polygon", - "id": "a0", - "params": { - "columnName": 'count_airbnb', - "pointsSource": { - "type": "source", - "params": { - query: "select * from analysis_rent_listings" - }, - dataviews: { - price_histogram: { - type: 'histogram', - options: { - column: 'price' - } - } - } - }, - "polygonsSource": { - "id": "b1", - "type": "buffer", - "params": { - "source": { - "id": "b0", - "type": "source", - "params": { - query: "select * from analysis_banks" - } - }, - "radius": 250 - }, - dataviews: { - bank_category: { - type: 'aggregation', - options: { - column: 'bank' - } - } - } - } + mapConfig: mapConfig( + [ + { + type: 'cartodb', + options: { + source: { + id: 'a1' }, - dataviews: { - count_histogram: { - type: 'histogram', - options: { - column: 'count_airbnb' - } - } - } - }), - cartocss: cartocss('green', 1.0) + cartocss: DEFAULT_MULTITYPE_STYLE, + cartocss_version: '2.3.0' + } + }, + { + type: 'cartodb', + options: { + sql: 'select * from analysis_rent_listings', + cartocss: DEFAULT_MULTITYPE_STYLE, + cartocss_version: '2.3.0' + } } - } - ]) + ], + {}, + [ + pointInPolygonDef + ] + ) + }, + + { + desc: 'buffer + point-in-polygon from buffer atm-machines and rent listings + rent listings', + mapConfig: mapConfig( + [ + { + type: 'cartodb', + options: { + sql: 'select * from analysis_rent_listings', + cartocss: DEFAULT_MULTITYPE_STYLE, + cartocss_version: '2.3.0' + } + }, + { + type: 'cartodb', + options: { + source: { + id: 'a1' + }, + cartocss: DEFAULT_MULTITYPE_STYLE, + cartocss_version: '2.3.0' + } + }, + { + type: 'cartodb', + options: { + source: { + id: 'b1' + }, + cartocss: DEFAULT_MULTITYPE_STYLE, + cartocss_version: '2.3.0' + } + } + ], + {}, + [ + bufferDef, + pointInPolygonDef + ] + ) }, { @@ -377,55 +272,55 @@ describe('analysis-layers use cases', function() { { type: 'cartodb', options: { - "source": { id: "b0" }, - "cartocss": [ - "#distribution_centers {", - " marker-fill-opacity: 1.0;", - " marker-line-color: #FFF;", - " marker-line-width: 0.5;", - " marker-line-opacity: 0.7;", - " marker-placement: point;", - " marker-type: ellipse;", - " marker-width: 8;", - " marker-fill: blue;", - " marker-allow-overlap: true;", - "}" + 'source': { id: 'b0' }, + 'cartocss': [ + '#distribution_centers {', + ' marker-fill-opacity: 1.0;', + ' marker-line-color: #FFF;', + ' marker-line-width: 0.5;', + ' marker-line-opacity: 0.7;', + ' marker-placement: point;', + ' marker-type: ellipse;', + ' marker-width: 8;', + ' marker-fill: blue;', + ' marker-allow-overlap: true;', + '}' ].join('\n'), - "cartocss_version": "2.3.0" + 'cartocss_version': '2.3.0' } }, { type: 'cartodb', options: { - "source": { id: "a0" }, - "cartocss": [ - "#shops {", - " marker-fill-opacity: 1.0;", - " marker-line-color: #FFF;", - " marker-line-width: 0.5;", - " marker-line-opacity: 0.7;", - " marker-placement: point;", - " marker-type: ellipse;", - " marker-width: 8;", - " marker-fill: red;", - " marker-allow-overlap: true;", - "}" + 'source': { id: 'a0' }, + 'cartocss': [ + '#shops {', + ' marker-fill-opacity: 1.0;', + ' marker-line-color: #FFF;', + ' marker-line-width: 0.5;', + ' marker-line-opacity: 0.7;', + ' marker-placement: point;', + ' marker-type: ellipse;', + ' marker-width: 8;', + ' marker-fill: red;', + ' marker-allow-overlap: true;', + '}' ].join('\n'), - "cartocss_version": "2.3.0" + 'cartocss_version': '2.3.0' } }, { type: 'cartodb', options: { - "source": { id: "a1" }, - "cartocss": [ - "#routing {", - " line-color: ramp([routing_time], colorbrewer(Reds));", - " line-width: ramp([routing_time], 2, 8);", - " line-opacity: 1.0;", - "}" + 'source': { id: 'a1' }, + 'cartocss': [ + '#routing {', + ' line-color: ramp([routing_time], colorbrewer(Reds));', + ' line-width: ramp([routing_time], 2, 8);', + ' line-opacity: 1.0;', + '}' ].join('\n'), - "cartocss_version": "2.3.0" + 'cartocss_version': '2.3.0' } } ], @@ -485,91 +380,92 @@ describe('analysis-layers use cases', function() { skip: true, desc: 'II. Population analysis', mapConfig: mapConfig( - // layers - [ + // layers + [ + { + type: 'cartodb', + options: { + 'source': { id: 'a2' }, + 'cartocss': [ + '#count_in_polygon {', + ' polygon-opacity: 1.0', + ' line-color: #FFF;', + ' line-width: 0.5;', + ' line-opacity: 0.7', + ' polygon-fill: ramp([estimated_people], colorbrewer(Reds));', + '}' + ].join('\n'), + 'cartocss_version': '2.3.0' + } + }, + { + type: 'cartodb', + options: { + 'source': { id: 'a0' }, + 'cartocss': DEFAULT_MULTITYPE_STYLE, + 'cartocss_version': '2.3.0' + } + } + ], + // dataviews { - type: 'cartodb', - options: { - "source": { id: "a2" }, - "cartocss": [ - "#count_in_polygon {", - " polygon-opacity: 1.0", - " line-color: #FFF;", - " line-width: 0.5;", - " line-opacity: 0.7", - " polygon-fill: ramp([estimated_people], colorbrewer(Reds));", - "}" - ].join('\n'), - "cartocss_version": "2.3.0" + total_population_formula: { + 'source': { id: 'a3' }, + type: 'formula', + options: { + column: 'total_population', + operation: 'sum' + } + }, + people_histogram: { // this injects a range filter at `a2` node output + 'source': { id: 'a2' }, + type: 'histogram', + options: { + column: 'estimated_people' + } + }, + subway_line_category: { // this injects a category filter at `a0` node output + 'source': { id: 'a0' }, + type: 'aggregation', + options: { + column: 'subway_line' + } } }, - { - type: 'cartodb', - options: { - "source": { id: "a0" }, - "cartocss": DEFAULT_MULTITYPE_STYLE, - "cartocss_version": "2.3.0" - } - } - ], - // dataviews - { - total_population_formula: { - "source": { id: "a3" }, - type: 'formula', - options: { - column: 'total_population', - operation: 'sum' - } - }, - people_histogram: { // this injects a range filter at `a2` node output - "source": { id: "a2" }, - type: 'histogram', - options: { - column: 'estimated_people' - } - }, - subway_line_category: { // this injects a category filter at `a0` node output - "source": { id: "a0" }, - type: 'aggregation', - options: { - column: 'subway_line' - } - } - }, - // analysis - [ - { - id: 'a3', - // this will union the polygons, produce just one polygon, and calculate the total population for it - type: 'total-population', - params: { - columnName: 'total_population', - source: { - id: 'a2', - type: 'estimated-population', - params: { - columnName: 'estimated_people', - source: { - id: 'a1', - type: 'trade-area', - params: { - source: { - "id": "a0", - "type": "source", - "params": { - query: "select * from subway_stops" - } - }, - kind: 'walk', - time: 300 + // analysis + [ + { + id: 'a3', + // this will union the polygons, produce just one polygon, + // and calculate the total population for it + type: 'total-population', + params: { + columnName: 'total_population', + source: { + id: 'a2', + type: 'estimated-population', + params: { + columnName: 'estimated_people', + source: { + id: 'a1', + type: 'trade-area', + params: { + source: { + 'id': 'a0', + 'type': 'source', + 'params': { + query: 'select * from subway_stops' + } + }, + kind: 'walk', + time: 300 + } } } } } } - } - ]) + ]) }, { @@ -581,38 +477,38 @@ describe('analysis-layers use cases', function() { { type: 'cartodb', options: { - "source": { id: "a1" }, - "cartocss": [ - "#count_in_polygon {", - " polygon-opacity: 1.0", - " line-color: #FFF;", - " line-width: 0.5;", - " line-opacity: 0.7", - " polygon-fill: ramp([count_people], colorbrewer(Reds));", - "}" + 'source': { id: 'a1' }, + 'cartocss': [ + '#count_in_polygon {', + ' polygon-opacity: 1.0', + ' line-color: #FFF;', + ' line-width: 0.5;', + ' line-opacity: 0.7', + ' polygon-fill: ramp([count_people], colorbrewer(Reds));', + '}' ].join('\n'), - "cartocss_version": "2.3.0" + 'cartocss_version': '2.3.0' } } ], // dataviews { age_histogram: { - "source": { id: "a0" }, + 'source': { id: 'a0' }, type: 'histogram', options: { column: 'age' } }, income_histogram: { - "source": { id: "a0" }, + 'source': { id: 'a0' }, type: 'histogram', options: { column: 'income' } }, gender_category: { - "source": { id: "a0" }, + 'source': { id: 'a0' }, type: 'aggregation', options: { column: 'gender' @@ -622,22 +518,22 @@ describe('analysis-layers use cases', function() { // analysis [ { - "id": "a1", - "type": "count-in-polygon", - "params": { - "columnName": 'count_people', - "pointsSource": { - "id": 'a0', - "type": "source", - "params": { - query: "select the_geom, age, gender, income from people" + 'id': 'a1', + 'type': 'count-in-polygon', + 'params': { + 'columnName': 'count_people', + 'pointsSource': { + 'id': 'a0', + 'type': 'source', + 'params': { + query: 'select the_geom, age, gender, income from people' } }, - "polygonsSource": { - "id": "b0", - "type": "source", - "params": { - query: "select * from postal_codes" + 'polygonsSource': { + 'id': 'b0', + 'type': 'source', + 'params': { + query: 'select * from postal_codes' } } } @@ -648,20 +544,20 @@ describe('analysis-layers use cases', function() { ]; - useCases.forEach(function(useCase, imageIdx) { + useCases.forEach(function (useCase) { if (!!useCase.skip) { - debug(JSON.stringify(useCase.mapConfig, null, 4)); + return debug(JSON.stringify(useCase.mapConfig, null, 4)); } - it.skip('should implement use case: "' + useCase.desc + '"', function(done) { + it(`should implement use case: '${useCase.desc}'`, function (done) { var testClient = new TestClient(useCase.mapConfig, 1234); var tile = useCase.tile || TILE_ANALYSIS_TABLES; - testClient.getTile(tile.z, tile.x, tile.y, function(err, res, image) { + testClient.getTile(tile.z, tile.x, tile.y, function (err, res, image) { assert.ok(!err, err); - image.save('/tmp/tests/' + imageIdx + '---' + useCase.desc.replace(/\s/g, '-') + '.png'); + //image.save('/tmp/tests/' + imageIdx + '---' + useCase.desc.replace(/\s/g, '-') + '.png'); assert.equal(image.width(), 256); From 3134f40eac32eaa7a302a69d0c1cf81fa9f114db Mon Sep 17 00:00:00 2001 From: Raul Ochoa Date: Fri, 22 Sep 2017 15:42:52 +0000 Subject: [PATCH 147/159] Remove advanced use cases that no longer make sense --- .../analysis/analysis-layers-use-cases.js | 280 ------------------ 1 file changed, 280 deletions(-) diff --git a/test/acceptance/analysis/analysis-layers-use-cases.js b/test/acceptance/analysis/analysis-layers-use-cases.js index b08c264a..2f087626 100644 --- a/test/acceptance/analysis/analysis-layers-use-cases.js +++ b/test/acceptance/analysis/analysis-layers-use-cases.js @@ -261,287 +261,7 @@ describe('analysis-layers use cases', function () { pointInPolygonDef ] ) - }, - - { - skip: true, - desc: 'I. Distribution centers', - mapConfig: mapConfig( - // layers - [ - { - type: 'cartodb', - options: { - 'source': { id: 'b0' }, - 'cartocss': [ - '#distribution_centers {', - ' marker-fill-opacity: 1.0;', - ' marker-line-color: #FFF;', - ' marker-line-width: 0.5;', - ' marker-line-opacity: 0.7;', - ' marker-placement: point;', - ' marker-type: ellipse;', - ' marker-width: 8;', - ' marker-fill: blue;', - ' marker-allow-overlap: true;', - '}' - ].join('\n'), - 'cartocss_version': '2.3.0' - } - }, - { - type: 'cartodb', - options: { - 'source': { id: 'a0' }, - 'cartocss': [ - '#shops {', - ' marker-fill-opacity: 1.0;', - ' marker-line-color: #FFF;', - ' marker-line-width: 0.5;', - ' marker-line-opacity: 0.7;', - ' marker-placement: point;', - ' marker-type: ellipse;', - ' marker-width: 8;', - ' marker-fill: red;', - ' marker-allow-overlap: true;', - '}' - ].join('\n'), - 'cartocss_version': '2.3.0' - } - }, - { - type: 'cartodb', - options: { - 'source': { id: 'a1' }, - 'cartocss': [ - '#routing {', - ' line-color: ramp([routing_time], colorbrewer(Reds));', - ' line-width: ramp([routing_time], 2, 8);', - ' line-opacity: 1.0;', - '}' - ].join('\n'), - 'cartocss_version': '2.3.0' - } - } - ], - // dataviews - { - distribution_center_name_category: { - source: { id: 'b0' }, - type: 'aggregation', - options: { - column: 'name' - } - }, - time_histogram: { - source: { id: 'a1' }, - type: 'histogram', - options: { - column: 'routing_time' - } - }, - distance_histogram: { - source: { id: 'a1' }, - type: 'histogram', - options: { - column: 'routing_distance' - } - } - }, - // analysis - [ - { - id: 'a1', - type: 'routing-n-to-n', - params: { - // distanceColumn: 'routing_distance', - // timeColumn: 'routing_time', - originSource: { - id: 'b0', - type: 'source', - params: { - query: 'select * from distribution_centers' - } - }, - destinationSource: { - id: 'a0', - type: 'source', - params: { - query: 'select * from shops' - } - } - } - } - ] - ) - }, - - { - skip: true, - desc: 'II. Population analysis', - mapConfig: mapConfig( - // layers - [ - { - type: 'cartodb', - options: { - 'source': { id: 'a2' }, - 'cartocss': [ - '#count_in_polygon {', - ' polygon-opacity: 1.0', - ' line-color: #FFF;', - ' line-width: 0.5;', - ' line-opacity: 0.7', - ' polygon-fill: ramp([estimated_people], colorbrewer(Reds));', - '}' - ].join('\n'), - 'cartocss_version': '2.3.0' - } - }, - { - type: 'cartodb', - options: { - 'source': { id: 'a0' }, - 'cartocss': DEFAULT_MULTITYPE_STYLE, - 'cartocss_version': '2.3.0' - } - } - ], - // dataviews - { - total_population_formula: { - 'source': { id: 'a3' }, - type: 'formula', - options: { - column: 'total_population', - operation: 'sum' - } - }, - people_histogram: { // this injects a range filter at `a2` node output - 'source': { id: 'a2' }, - type: 'histogram', - options: { - column: 'estimated_people' - } - }, - subway_line_category: { // this injects a category filter at `a0` node output - 'source': { id: 'a0' }, - type: 'aggregation', - options: { - column: 'subway_line' - } - } - }, - // analysis - [ - { - id: 'a3', - // this will union the polygons, produce just one polygon, - // and calculate the total population for it - type: 'total-population', - params: { - columnName: 'total_population', - source: { - id: 'a2', - type: 'estimated-population', - params: { - columnName: 'estimated_people', - source: { - id: 'a1', - type: 'trade-area', - params: { - source: { - 'id': 'a0', - 'type': 'source', - 'params': { - query: 'select * from subway_stops' - } - }, - kind: 'walk', - time: 300 - } - } - } - } - } - } - ]) - }, - - { - skip: true, - desc: 'III. Point in polygon', - mapConfig: mapConfig( - // layers - [ - { - type: 'cartodb', - options: { - 'source': { id: 'a1' }, - 'cartocss': [ - '#count_in_polygon {', - ' polygon-opacity: 1.0', - ' line-color: #FFF;', - ' line-width: 0.5;', - ' line-opacity: 0.7', - ' polygon-fill: ramp([count_people], colorbrewer(Reds));', - '}' - ].join('\n'), - 'cartocss_version': '2.3.0' - } - } - ], - // dataviews - { - age_histogram: { - 'source': { id: 'a0' }, - type: 'histogram', - options: { - column: 'age' - } - }, - income_histogram: { - 'source': { id: 'a0' }, - type: 'histogram', - options: { - column: 'income' - } - }, - gender_category: { - 'source': { id: 'a0' }, - type: 'aggregation', - options: { - column: 'gender' - } - } - }, - // analysis - [ - { - 'id': 'a1', - 'type': 'count-in-polygon', - 'params': { - 'columnName': 'count_people', - 'pointsSource': { - 'id': 'a0', - 'type': 'source', - 'params': { - query: 'select the_geom, age, gender, income from people' - } - }, - 'polygonsSource': { - 'id': 'b0', - 'type': 'source', - 'params': { - query: 'select * from postal_codes' - } - } - } - } - ] - ) } - ]; useCases.forEach(function (useCase) { From c734f43643e75f5cae15fa84ad658e497eb4326c Mon Sep 17 00:00:00 2001 From: Raul Ochoa Date: Mon, 25 Sep 2017 11:11:05 +0000 Subject: [PATCH 148/159] Upgrades camshaft, cartodb-query-tables, and turbo-carto Better support for query variables. --- NEWS.md | 4 +++- package.json | 8 ++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/NEWS.md b/NEWS.md index 2efa11b6..2971a93e 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,7 +1,9 @@ # Changelog -## 3.12.11 +## 3.13.0 Released 2017-mm-dd + - Upgrades camshaft, cartodb-query-tables, and turbo-carto: better support for query variables. + ## 3.12.10 Released 2017-09-18 diff --git a/package.json b/package.json index 9c70ad72..ea4f49e7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "windshaft-cartodb", - "version": "3.12.11", + "version": "3.13.0", "description": "A map tile server for CartoDB", "keywords": [ "cartodb" @@ -23,9 +23,9 @@ ], "dependencies": { "body-parser": "~1.14.0", - "camshaft": "0.58.1", + "camshaft": "0.59.0", "cartodb-psql": "0.10.1", - "cartodb-query-tables": "0.2.0", + "cartodb-query-tables": "0.3.0", "cartodb-redis": "0.14.0", "debug": "~2.2.0", "dot": "~1.0.2", @@ -40,7 +40,7 @@ "request": "~2.79.0", "step": "~0.0.6", "step-profiler": "~0.3.0", - "turbo-carto": "0.19.2", + "turbo-carto": "0.20.0", "underscore": "~1.6.0", "windshaft": "3.3.2", "yargs": "~5.0.0" From d3dafc8a408ba89dad1931114005b8cb246a0ba8 Mon Sep 17 00:00:00 2001 From: Raul Ochoa Date: Mon, 25 Sep 2017 11:24:33 +0000 Subject: [PATCH 149/159] Regenerate lock file --- yarn.lock | 315 +++++++++++++++++++++++++++--------------------------- 1 file changed, 157 insertions(+), 158 deletions(-) diff --git a/yarn.lock b/yarn.lock index a9ba816b..8fbdeeb4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -32,6 +32,15 @@ ajv@^4.9.1: co "^4.6.0" json-stable-stringify "^1.0.1" +ajv@^5.1.0: + version "5.2.2" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.2.2.tgz#47c68d69e86f5d953103b0074a9430dc63da5e39" + dependencies: + co "^4.6.0" + fast-deep-equal "^1.0.0" + json-schema-traverse "^0.3.0" + json-stable-stringify "^1.0.1" + align-text@^0.1.1, align-text@^0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117" @@ -53,8 +62,8 @@ ansi-styles@^2.2.1: resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" aproba@^1.0.3: - version "1.1.2" - resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.1.2.tgz#45c6629094de4e96f693ef7eab74ae079c240fc1" + version "1.2.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" are-we-there-yet@~1.1.2: version "1.1.4" @@ -105,7 +114,11 @@ aws-sign2@~0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f" -aws4@^1.2.1: +aws-sign2@~0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" + +aws4@^1.2.1, aws4@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e" @@ -150,6 +163,18 @@ boom@2.x.x: dependencies: hoek "2.x.x" +boom@4.x.x: + version "4.3.1" + resolved "https://registry.yarnpkg.com/boom/-/boom-4.3.1.tgz#4f8a3005cb4a7e3889f749030fd25b96e01d2e31" + dependencies: + hoek "4.x.x" + +boom@5.x.x: + version "5.2.0" + resolved "https://registry.yarnpkg.com/boom/-/boom-5.2.0.tgz#5dd9da6ee3a5f302077436290cb717d3f4a54e02" + dependencies: + hoek "4.x.x" + brace-expansion@^1.1.7: version "1.1.8" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292" @@ -194,9 +219,9 @@ camelcase@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" -camshaft@0.58.1: - version "0.58.1" - resolved "https://registry.yarnpkg.com/camshaft/-/camshaft-0.58.1.tgz#e4156580683f624212ea3020e59790ad006f24cc" +camshaft@0.59.0: + version "0.59.0" + resolved "https://registry.yarnpkg.com/camshaft/-/camshaft-0.59.0.tgz#699c0834116ec87e067d42e11eea9540bddc6383" dependencies: async "^1.5.2" bunyan "1.8.1" @@ -253,9 +278,9 @@ cartodb-psql@0.10.1, cartodb-psql@^0.10.1: pg cartodb/node-postgres#6.1.6-cdb1 underscore "~1.6.0" -cartodb-query-tables@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/cartodb-query-tables/-/cartodb-query-tables-0.2.0.tgz#b4d672accde04da5b890a5d56a87b761fa7eec44" +cartodb-query-tables@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/cartodb-query-tables/-/cartodb-query-tables-0.3.0.tgz#56e18d869666eb2e8e2cb57d0baf3acc923f8756" cartodb-redis@0.14.0: version "0.14.0" @@ -393,6 +418,12 @@ cryptiles@2.x.x: dependencies: boom "2.x.x" +cryptiles@3.x.x: + version "3.1.2" + resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-3.1.2.tgz#a89fbb220f5ce25ec56e8c4aa8a4fd7b5b0d29fe" + dependencies: + boom "5.x.x" + d3-queue@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/d3-queue/-/d3-queue-2.0.3.tgz#07fbda3acae5358a9c5299aaf880adf0953ed2c2" @@ -426,8 +457,8 @@ debug@^1.0.4: ms "2.0.0" debug@^2.2.0: - version "2.6.8" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc" + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" dependencies: ms "2.0.0" @@ -441,7 +472,7 @@ deep-eql@^0.1.3: dependencies: type-detect "0.1.1" -deep-equal@^1.0.0, deep-equal@~1.0.1: +deep-equal@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" @@ -453,17 +484,6 @@ deep-is@~0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" -define-properties@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.2.tgz#83a73f2fea569898fb737193c8f873caf6d45c94" - dependencies: - foreach "^2.0.5" - object-keys "^1.0.8" - -defined@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" - delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -550,24 +570,6 @@ error-ex@^1.2.0: dependencies: is-arrayish "^0.2.1" -es-abstract@^1.5.0: - version "1.8.2" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.8.2.tgz#25103263dc4decbda60e0c737ca32313518027ee" - dependencies: - es-to-primitive "^1.1.1" - function-bind "^1.1.1" - has "^1.0.1" - is-callable "^1.1.3" - is-regex "^1.0.4" - -es-to-primitive@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.1.1.tgz#45355248a88979034b6792e19bb81f2b7975dd0d" - dependencies: - is-callable "^1.1.1" - is-date-object "^1.0.1" - is-symbol "^1.0.1" - es6-promise@3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-3.1.2.tgz#795e25ceb47f7babb263d151afbedd92d18e6a07" @@ -645,7 +647,7 @@ express@~4.13.3: utils-merge "1.0.0" vary "~1.0.1" -extend@~3.0.0: +extend@~3.0.0, extend@~3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" @@ -653,6 +655,10 @@ extsprintf@1.3.0, extsprintf@^1.2.0: version "1.3.0" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" +fast-deep-equal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff" + fast-levenshtein@~2.0.4: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" @@ -679,16 +685,6 @@ find-up@^1.0.0: path-exists "^2.0.0" pinkie-promise "^2.0.0" -for-each@~0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.2.tgz#2c40450b9348e97f281322593ba96704b9abd4d4" - dependencies: - is-function "~1.0.0" - -foreach@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" - forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" @@ -701,6 +697,14 @@ form-data@~2.1.1: combined-stream "^1.0.5" mime-types "^2.1.12" +form-data@~2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.1.tgz#6fb94fbd71885306d73d15cc497fe4cc4ecd44bf" + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.5" + mime-types "^2.1.12" + forwarded@~0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" @@ -730,10 +734,6 @@ fstream@^1.0.0, fstream@^1.0.10, fstream@^1.0.2: mkdirp ">=0.5 0" rimraf "2" -function-bind@^1.0.2, function-bind@^1.1.1, function-bind@~1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" - gauge@~2.7.3: version "2.7.4" resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" @@ -821,7 +821,7 @@ glob@^6.0.1: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.0.5, glob@^7.1.1, glob@~7.1.2: +glob@^7.0.5, glob@^7.1.1: version "7.1.2" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" dependencies: @@ -872,6 +872,10 @@ har-schema@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e" +har-schema@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" + har-validator@~2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-2.0.6.tgz#cdcbc08188265ad119b6a5a7c8ab70eecfb5d27d" @@ -888,6 +892,13 @@ har-validator@~4.2.1: ajv "^4.9.1" har-schema "^1.0.5" +har-validator@~5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.0.3.tgz#ba402c266194f15956ef15e0fcf242993f6a7dfd" + dependencies: + ajv "^5.1.0" + har-schema "^2.0.0" + has-ansi@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" @@ -902,13 +913,7 @@ has-unicode@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" -has@^1.0.1, has@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.1.tgz#8461733f538b0837c9361e39a9ab9e9704dc2f28" - dependencies: - function-bind "^1.0.2" - -hawk@~3.1.3: +hawk@3.1.3, hawk@~3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4" dependencies: @@ -917,6 +922,15 @@ hawk@~3.1.3: hoek "2.x.x" sntp "1.x.x" +hawk@~6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/hawk/-/hawk-6.0.2.tgz#af4d914eb065f9b5ce4d9d11c1cb2126eecc3038" + dependencies: + boom "4.x.x" + cryptiles "3.x.x" + hoek "4.x.x" + sntp "2.x.x" + hiredis@~0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/hiredis/-/hiredis-0.5.0.tgz#db03a98becd7003d13c260043aceecfacdf59b87" @@ -928,6 +942,10 @@ hoek@2.x.x: version "2.16.3" resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed" +hoek@4.x.x: + version "4.2.0" + resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.0.tgz#72d9d0754f7fe25ca2d01ad8f8f9a9449a89526d" + hosted-git-info@^2.1.4: version "2.5.0" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.5.0.tgz#6d60e34b3abbc8313062c3b798ef8d901a07af3c" @@ -957,6 +975,14 @@ http-signature@~1.1.0: jsprim "^1.2.2" sshpk "^1.7.0" +http-signature@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" + dependencies: + assert-plus "^1.0.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + husl@^6.0.1: version "6.0.6" resolved "https://registry.yarnpkg.com/husl/-/husl-6.0.6.tgz#f71b3e45d2000d6406432a9cc17a4b7e0c5b800d" @@ -1002,24 +1028,12 @@ is-builtin-module@^1.0.0: dependencies: builtin-modules "^1.0.0" -is-callable@^1.1.1, is-callable@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.3.tgz#86eb75392805ddc33af71c92a0eedf74ee7604b2" - -is-date-object@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16" - is-fullwidth-code-point@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" dependencies: number-is-nan "^1.0.0" -is-function@~1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-function/-/is-function-1.0.1.tgz#12cfb98b65b57dd3d193a3121f5f6e2f437602b5" - is-my-json-valid@^2.12.4: version "2.16.1" resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.16.1.tgz#5a846777e2c2620d1e69104e5d3a03b1f6088f11" @@ -1033,16 +1047,6 @@ is-property@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84" -is-regex@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491" - dependencies: - has "^1.0.1" - -is-symbol@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.1.tgz#3cc59f00025194b6ab2e38dbae6689256b660572" - is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" @@ -1118,6 +1122,10 @@ jshint@~2.9.4: shelljs "0.3.x" strip-json-comments "1.0.x" +json-schema-traverse@^0.3.0: + version "0.3.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340" + json-schema@0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" @@ -1331,7 +1339,7 @@ mime-db@~1.30.0: version "1.30.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.30.0.tgz#74c643da2dd9d6a45399963465b26d5ca7d71f01" -mime-types@^2.1.12, mime-types@~2.1.15, mime-types@~2.1.6, mime-types@~2.1.7: +mime-types@^2.1.12, mime-types@~2.1.15, mime-types@~2.1.17, mime-types@~2.1.6, mime-types@~2.1.7: version "2.1.17" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.17.tgz#09d7a393f03e995a79f8af857b70a9e0ab16557a" dependencies: @@ -1359,7 +1367,7 @@ minimist@0.0.8: version "0.0.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" -minimist@^1.2.0, minimist@~1.2.0: +minimist@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" @@ -1448,18 +1456,18 @@ nock@~2.11.0: mkdirp "^0.5.0" propagate "0.3.x" -node-pre-gyp@~0.6.30, node-pre-gyp@~0.6.36, node-pre-gyp@~0.6.37: - version "0.6.37" - resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.37.tgz#3c872b236b2e266e4140578fe1ee88f693323a05" +node-pre-gyp@~0.6.30, node-pre-gyp@~0.6.36, node-pre-gyp@~0.6.38: + version "0.6.38" + resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.38.tgz#e92a20f83416415bb4086f6d1fb78b3da73d113d" dependencies: + hawk "3.1.3" mkdirp "^0.5.1" nopt "^4.0.1" npmlog "^4.0.2" rc "^1.1.7" - request "^2.81.0" + request "2.81.0" rimraf "^2.6.1" semver "^5.3.0" - tape "^4.6.3" tar "^2.2.1" tar-pack "^3.4.0" @@ -1502,7 +1510,7 @@ number-is-nan@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" -oauth-sign@~0.8.1: +oauth-sign@~0.8.1, oauth-sign@~0.8.2: version "0.8.2" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" @@ -1514,14 +1522,6 @@ object-assign@^4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" -object-inspect@~1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.3.0.tgz#5b1eb8e6742e2ee83342a637034d844928ba2f6d" - -object-keys@^1.0.8: - version "1.0.11" - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.11.tgz#c54601778ad560f1142ce0e01bcca8b56d13426d" - object-keys@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-0.4.0.tgz#28a6aae7428dd2c3a92f3d95f21335dd204e0336" @@ -1601,10 +1601,6 @@ path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" -path-parse@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1" - path-to-regexp@0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" @@ -1621,6 +1617,10 @@ performance-now@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5" +performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" + pg-connection-string@0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-0.1.3.tgz#da1847b20940e42ee1492beaf65d49d91b245df7" @@ -1776,6 +1776,10 @@ qs@~6.4.0: version "6.4.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" +qs@~6.5.1: + version "6.5.1" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" + queue-async@~1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/queue-async/-/queue-async-1.0.7.tgz#22ae0a1dac4a92f5bcd4634f993c682a2a810945" @@ -1872,7 +1876,7 @@ repeat-string@^1.5.2: version "1.6.1" resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" -request@2.x, request@^2.55.0, request@^2.69.0, request@^2.81.0: +request@2.81.0: version "2.81.0" resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0" dependencies: @@ -1899,6 +1903,33 @@ request@2.x, request@^2.55.0, request@^2.69.0, request@^2.81.0: tunnel-agent "^0.6.0" uuid "^3.0.0" +request@2.x, request@^2.55.0, request@^2.69.0: + version "2.82.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.82.0.tgz#2ba8a92cd7ac45660ea2b10a53ae67cd247516ea" + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.6.0" + caseless "~0.12.0" + combined-stream "~1.0.5" + extend "~3.0.1" + forever-agent "~0.6.1" + form-data "~2.3.1" + har-validator "~5.0.3" + hawk "~6.0.2" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.17" + oauth-sign "~0.8.2" + performance-now "^2.1.0" + qs "~6.5.1" + safe-buffer "^5.1.1" + stringstream "~0.0.5" + tough-cookie "~2.3.2" + tunnel-agent "^0.6.0" + uuid "^3.1.0" + request@~2.79.0: version "2.79.0" resolved "https://registry.yarnpkg.com/request/-/request-2.79.0.tgz#4dfe5bf6be8b8cdc37fcf93e04b65577722710de" @@ -1936,18 +1967,6 @@ resolve@1.1.x: version "1.1.7" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" -resolve@~1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.4.0.tgz#a75be01c53da25d934a98ebd0e4c4a7312f92a86" - dependencies: - path-parse "^1.0.5" - -resumer@~0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/resumer/-/resumer-0.0.0.tgz#f1e8f461e4064ba39e82af3cdc2a8c893d076759" - dependencies: - through "~2.3.4" - right-align@^0.1.1: version "0.1.3" resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef" @@ -1966,7 +1985,7 @@ rimraf@~2.4.0: dependencies: glob "^6.0.1" -safe-buffer@^5.0.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1: +safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" @@ -2058,6 +2077,12 @@ sntp@1.x.x: dependencies: hoek "2.x.x" +sntp@2.x.x: + version "2.0.2" + resolved "https://registry.yarnpkg.com/sntp/-/sntp-2.0.2.tgz#5064110f0af85f7cfdb7d6b67a40028ce52b4b2b" + dependencies: + hoek "4.x.x" + source-map@^0.4.4: version "0.4.4" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b" @@ -2111,11 +2136,11 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" "sqlite3@2.x || 3.x": - version "3.1.11" - resolved "https://registry.yarnpkg.com/sqlite3/-/sqlite3-3.1.11.tgz#84421b181b3e9f4939b7a3d712263d7946cfc96d" + version "3.1.12" + resolved "https://registry.yarnpkg.com/sqlite3/-/sqlite3-3.1.12.tgz#2b3a14b17162e39e8aa6e1e2487a41d0795396d8" dependencies: nan "~2.7.0" - node-pre-gyp "~0.6.37" + node-pre-gyp "~0.6.38" srs@1.x: version "1.2.0" @@ -2167,14 +2192,6 @@ string-width@^1.0.1, string-width@^1.0.2: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" -string.prototype.trim@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.1.2.tgz#d04de2c89e137f4d7d206f086b5ed2fae6be8cea" - dependencies: - define-properties "^1.1.2" - es-abstract "^1.5.0" - function-bind "^1.0.2" - string_decoder@~0.10.x: version "0.10.31" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" @@ -2185,7 +2202,7 @@ string_decoder@~1.0.3: dependencies: safe-buffer "~5.1.0" -stringstream@~0.0.4: +stringstream@~0.0.4, stringstream@~0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878" @@ -2225,24 +2242,6 @@ supports-color@^3.1.0, supports-color@^3.1.2, supports-color@^3.2.3: dependencies: has-flag "^1.0.0" -tape@^4.6.3: - version "4.8.0" - resolved "https://registry.yarnpkg.com/tape/-/tape-4.8.0.tgz#f6a9fec41cc50a1de50fa33603ab580991f6068e" - dependencies: - deep-equal "~1.0.1" - defined "~1.0.0" - for-each "~0.3.2" - function-bind "~1.1.0" - glob "~7.1.2" - has "~1.0.1" - inherits "~2.0.3" - minimist "~1.2.0" - object-inspect "~1.3.0" - resolve "~1.4.0" - resumer "~0.0.0" - string.prototype.trim "~1.1.2" - through "~2.3.8" - tar-pack@^3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/tar-pack/-/tar-pack-3.4.0.tgz#23be2d7f671a8339376cbdb0b8fe3fdebf317984" @@ -2271,7 +2270,7 @@ through2@~0.2.3: readable-stream "~1.1.9" xtend "~2.1.1" -through@2, through@~2.3.4, through@~2.3.8: +through@2: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" @@ -2308,9 +2307,9 @@ torque.js@~2.11.0: dependencies: carto CartoDB/carto#0.15.1-cdb1 -tough-cookie@~2.3.0: - version "2.3.2" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.2.tgz#f081f76e4c85720e6c37a5faced737150d84072a" +tough-cookie@~2.3.0, tough-cookie@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.3.tgz#0b618a5565b6dea90bf3425d04d55edc475a7561" dependencies: punycode "^1.4.1" @@ -2324,9 +2323,9 @@ tunnel-agent@~0.4.1: version "0.4.3" resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.4.3.tgz#6373db76909fe570e08d73583365ed828a74eeeb" -turbo-carto@0.19.2: - version "0.19.2" - resolved "https://registry.yarnpkg.com/turbo-carto/-/turbo-carto-0.19.2.tgz#062d68e59f89377f0cfa69a2717c047fe95e32fd" +turbo-carto@0.20.0: + version "0.20.0" + resolved "https://registry.yarnpkg.com/turbo-carto/-/turbo-carto-0.20.0.tgz#ebeb4620f3e26ed3cce53a9ff30f087f46a2e3fd" dependencies: cartocolor "4.0.0" colorbrewer "1.0.0" @@ -2401,7 +2400,7 @@ utils-merge@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.0.tgz#0294fb922bb9375153541c4f7096231f287c8af8" -uuid@^3.0.0: +uuid@^3.0.0, uuid@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04" From 66380197f464dae66a127a7a846c38606227fcfa Mon Sep 17 00:00:00 2001 From: Raul Ochoa Date: Mon, 25 Sep 2017 14:53:17 +0000 Subject: [PATCH 150/159] Do not explode by - token --- app.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/app.js b/app.js index 6eb1ef3a..8305a1eb 100755 --- a/app.js +++ b/app.js @@ -5,12 +5,11 @@ var fs = require('fs'); var _ = require('underscore'); var semver = require('semver'); -var ver = process.versions.node; -ver = ver.split('-')[0]; // explode and truncate tag from version +var nodejsVersion = process.versions.node; // jshint undef:false -if (!semver.satisfies(ver, '>6.9.0')) { - console.log('Node version ' + ver + ' is not supported, please use Node.js 6.9 or higher.'); +if (!semver.satisfies(nodejsVersion, '>6.9.0')) { + console.log(`Node version ${nodejsVersion} is not supported, please use Node.js 6.9 or higher.`); process.exit(1); } From 3023111896217bd69f3a1d28b2f3f9a999f89a84 Mon Sep 17 00:00:00 2001 From: Raul Ochoa Date: Mon, 25 Sep 2017 14:55:17 +0000 Subject: [PATCH 151/159] Reuse existing log methods --- app.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/app.js b/app.js index 8305a1eb..c1388b92 100755 --- a/app.js +++ b/app.js @@ -5,18 +5,17 @@ var fs = require('fs'); var _ = require('underscore'); var semver = require('semver'); -var nodejsVersion = process.versions.node; - // jshint undef:false -if (!semver.satisfies(nodejsVersion, '>6.9.0')) { - console.log(`Node version ${nodejsVersion} is not supported, please use Node.js 6.9 or higher.`); - process.exit(1); -} - var log = console.log.bind(console); var logError = console.error.bind(console); // jshint undef:true +var nodejsVersion = process.versions.node; +if (!semver.satisfies(nodejsVersion, '>6.9.0')) { + logError(`Node version ${nodejsVersion} is not supported, please use Node.js 6.9 or higher.`); + process.exit(1); +} + var argv = require('yargs') .usage('Usage: $0 [options]') .help('h') From ac42223439b64f2a78113f1d9bd1c59fb2776f2f Mon Sep 17 00:00:00 2001 From: Raul Ochoa Date: Mon, 25 Sep 2017 15:34:16 +0000 Subject: [PATCH 152/159] Allow 6.9.0 version --- app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app.js b/app.js index c1388b92..9270cac0 100755 --- a/app.js +++ b/app.js @@ -11,7 +11,7 @@ var logError = console.error.bind(console); // jshint undef:true var nodejsVersion = process.versions.node; -if (!semver.satisfies(nodejsVersion, '>6.9.0')) { +if (!semver.satisfies(nodejsVersion, '>=6.9.0')) { logError(`Node version ${nodejsVersion} is not supported, please use Node.js 6.9 or higher.`); process.exit(1); } From c9f0902703d76e633f580ef349037d0d2e879214 Mon Sep 17 00:00:00 2001 From: Javier Goizueta Date: Wed, 27 Sep 2017 18:44:06 +0200 Subject: [PATCH 153/159] Upgrade camshsft to 0.59.1 This fixes duplicate column names in aggregate-intersection analysis --- NEWS.md | 3 +-- package.json | 2 +- yarn.lock | 6 +++--- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/NEWS.md b/NEWS.md index 8208c0f5..c20ae985 100644 --- a/NEWS.md +++ b/NEWS.md @@ -4,10 +4,9 @@ Released 2017-mm-dd - Upgrades camshaft, cartodb-query-tables, and turbo-carto: better support for query variables. - Bugfixes: - Bounding box parameter ignored in static named maps #735. - + - camhaft 0.59.1 fixes duplicate columns in aggregate-intersection analysis ## 3.12.10 Released 2017-09-18 diff --git a/package.json b/package.json index a3ca339d..ba7422b4 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ ], "dependencies": { "body-parser": "~1.14.0", - "camshaft": "0.59.0", + "camshaft": "0.59.1", "cartodb-psql": "0.10.1", "cartodb-query-tables": "0.3.0", "cartodb-redis": "0.14.0", diff --git a/yarn.lock b/yarn.lock index 8400e973..e1ad9ba7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -219,9 +219,9 @@ camelcase@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" -camshaft@0.59.0: - version "0.59.0" - resolved "https://registry.yarnpkg.com/camshaft/-/camshaft-0.59.0.tgz#699c0834116ec87e067d42e11eea9540bddc6383" +camshaft@0.59.1: + version "0.59.1" + resolved "https://registry.yarnpkg.com/camshaft/-/camshaft-0.59.1.tgz#a2e87fa4a0236655160cd1a6a9e966cbbf86dd43" dependencies: async "^1.5.2" bunyan "1.8.1" From cacb92b0c4aa40b962b6fe7ac11580d94d251afe Mon Sep 17 00:00:00 2001 From: Eneko Lakasta Date: Thu, 28 Sep 2017 18:24:50 +0200 Subject: [PATCH 154/159] upgrade request module --- package.json | 2 +- yarn.lock | 125 ++++++++++++++++++++------------------------------- 2 files changed, 50 insertions(+), 77 deletions(-) diff --git a/package.json b/package.json index ba7422b4..58bc500d 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "node-statsd": "~0.0.7", "queue-async": "~1.0.7", "redis-mpool": "0.4.1", - "request": "~2.79.0", + "request": "^2.83.0", "semver": "~5.3.0", "step": "~0.0.6", "step-profiler": "~0.3.0", diff --git a/yarn.lock b/yarn.lock index e1ad9ba7..25a7fa2c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,7 +2,7 @@ # yarn lockfile v1 -"abaculus@github:cartodb/abaculus#2.0.3-cdb1": +abaculus@cartodb/abaculus#2.0.3-cdb1: version "2.0.3-cdb1" resolved "https://codeload.github.com/cartodb/abaculus/tar.gz/f5f34e1c80cdd8d49edd1d6fe3b2220ab2e23aaf" dependencies: @@ -10,11 +10,7 @@ mapnik "~3.5.0" sphericalmercator "1.0.x" -abbrev@1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.0.tgz#d0554c2256636e2f56e7c2e5ad183f859428d81f" - -abbrev@1.0.x: +abbrev@1, abbrev@1.0.x: version "1.0.9" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135" @@ -230,7 +226,7 @@ camshaft@0.59.1: dot "^1.0.3" request "^2.69.0" -"canvas@github:cartodb/node-canvas#1.6.2-cdb2": +canvas@cartodb/node-canvas#1.6.2-cdb2: version "1.6.2-cdb2" resolved "https://codeload.github.com/cartodb/node-canvas/tar.gz/8acf04557005c633f9e68524488a2657c04f3766" dependencies: @@ -248,15 +244,15 @@ carto@0.16.3: semver "^5.1.0" yargs "^4.2.0" -"carto@github:cartodb/carto#0.15.1-cdb1": +carto@CartoDB/carto#0.15.1-cdb1: version "0.15.1-cdb1" - resolved "https://codeload.github.com/cartodb/carto/tar.gz/8050ec843f1f32a6469e5d1cf49602773015d398" + resolved "https://codeload.github.com/CartoDB/carto/tar.gz/8050ec843f1f32a6469e5d1cf49602773015d398" dependencies: mapnik-reference "~6.0.2" optimist "~0.6.0" underscore "~1.6.0" -"carto@github:cartodb/carto#0.15.1-cdb3": +carto@cartodb/carto#0.15.1-cdb3: version "0.15.1-cdb3" resolved "https://codeload.github.com/cartodb/carto/tar.gz/945f5efb74fd1af1f5e1f69f409f9567f94fb5a7" dependencies: @@ -368,16 +364,12 @@ combined-stream@^1.0.5, combined-stream@~1.0.5: dependencies: delayed-stream "~1.0.0" -commander@2.9.0: +commander@2.9.0, commander@^2.9.0: version "2.9.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4" dependencies: graceful-readlink ">= 1.0.0" -commander@^2.9.0: - version "2.11.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563" - concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -438,7 +430,7 @@ date-now@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b" -debug@2.2.0, debug@~2.2.0: +debug@2.2.0, debug@^2.2.0, debug@~2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da" dependencies: @@ -456,12 +448,6 @@ debug@^1.0.4: dependencies: ms "2.0.0" -debug@^2.2.0: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - dependencies: - ms "2.0.0" - decamelize@^1.0.0, decamelize@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" @@ -532,11 +518,7 @@ domutils@1.5: dom-serializer "0" domelementtype "1" -dot@^1.0.3: - version "1.1.2" - resolved "https://registry.yarnpkg.com/dot/-/dot-1.1.2.tgz#c7377019fc4e550798928b2b9afeb66abfa1f2f9" - -dot@~1.0.2: +dot@^1.0.3, dot@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/dot/-/dot-1.0.3.tgz#f8750bfb6b03c7664eb0e6cb1eb4c66419af9427" @@ -790,7 +772,7 @@ getpass@^0.1.1: dependencies: assert-plus "^1.0.0" -glob@7.1.1: +glob@7.1.1, glob@^7.0.5, glob@^7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8" dependencies: @@ -821,17 +803,6 @@ glob@^6.0.1: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.0.5, glob@^7.1.1: - version "7.1.2" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - graceful-fs@^4.1.2: version "4.1.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" @@ -1345,7 +1316,7 @@ mime-types@^2.1.12, mime-types@~2.1.15, mime-types@~2.1.17, mime-types@~2.1.6, m dependencies: mime-db "~1.30.0" -mime@1.3.4: +mime@1.3.4, mime@~1.3.4: version "1.3.4" resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.4.tgz#115f9e3b6b3daf2959983cb38f149a2d40eb5d53" @@ -1353,17 +1324,13 @@ mime@~1.2.11: version "1.2.11" resolved "https://registry.yarnpkg.com/mime/-/mime-1.2.11.tgz#58203eed86e3a5ef17aed2b7d9ebd47f0a60dd10" -mime@~1.3.4: - version "1.3.6" - resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.6.tgz#591d84d3653a6b0b4a3b9df8de5aa8108e72e5e0" - -"minimatch@2 || 3", minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.4, minimatch@~3.0.2: +"minimatch@2 || 3", minimatch@^3.0.0, minimatch@^3.0.2, minimatch@~3.0.2: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" dependencies: brace-expansion "^1.1.7" -minimist@0.0.8: +minimist@0.0.8, minimist@~0.0.1: version "0.0.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" @@ -1371,10 +1338,6 @@ minimist@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" -minimist@~0.0.1: - version "0.0.10" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" - minimist@~0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.2.0.tgz#4dffe525dae2b864c66c2e23c6271d7afdecefce" @@ -1641,7 +1604,7 @@ pg-types@1.*: postgres-date "~1.0.0" postgres-interval "^1.1.0" -"pg@github:cartodb/node-postgres#6.1.6-cdb1": +pg@cartodb/node-postgres#6.1.6-cdb1: version "6.1.6" resolved "https://codeload.github.com/cartodb/node-postgres/tar.gz/3eef52dd1e655f658a4ee8ac5697688b3ecfed44" dependencies: @@ -1820,7 +1783,7 @@ read-pkg@^1.0.0: normalize-package-data "^2.3.2" path-type "^1.0.0" -readable-stream@1.1: +readable-stream@1.1, readable-stream@~1.1.9: version "1.1.13" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.13.tgz#f6eef764f514c89e2b9e23146a75ba106756d23e" dependencies: @@ -1850,15 +1813,6 @@ readable-stream@~1.0.2: isarray "0.0.1" string_decoder "~0.10.x" -readable-stream@~1.1.9: - version "1.1.14" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "0.0.1" - string_decoder "~0.10.x" - redis-mpool@0.4.1, redis-mpool@~0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/redis-mpool/-/redis-mpool-0.4.1.tgz#d917c0a4ed57a1291a9c6eb35434e6c0b7046f80" @@ -1930,6 +1884,33 @@ request@2.x, request@^2.55.0, request@^2.69.0: tunnel-agent "^0.6.0" uuid "^3.1.0" +request@^2.83.0: + version "2.83.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.83.0.tgz#ca0b65da02ed62935887808e6f510381034e3356" + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.6.0" + caseless "~0.12.0" + combined-stream "~1.0.5" + extend "~3.0.1" + forever-agent "~0.6.1" + form-data "~2.3.1" + har-validator "~5.0.3" + hawk "~6.0.2" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.17" + oauth-sign "~0.8.2" + performance-now "^2.1.0" + qs "~6.5.1" + safe-buffer "^5.1.1" + stringstream "~0.0.5" + tough-cookie "~2.3.3" + tunnel-agent "^0.6.0" + uuid "^3.1.0" + request@~2.79.0: version "2.79.0" resolved "https://registry.yarnpkg.com/request/-/request-2.79.0.tgz#4dfe5bf6be8b8cdc37fcf93e04b65577722710de" @@ -1993,9 +1974,9 @@ safe-json-stringify@~1: version "1.0.4" resolved "https://registry.yarnpkg.com/safe-json-stringify/-/safe-json-stringify-1.0.4.tgz#81a098f447e4bbc3ff3312a243521bc060ef5911" -"semver@2 || 3 || 4 || 5", semver@^5.1.0, semver@^5.3.0: - version "5.4.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e" +"semver@2 || 3 || 4 || 5", semver@^5.1.0, semver@^5.3.0, semver@~5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" semver@4.3.2: version "4.3.2" @@ -2009,10 +1990,6 @@ semver@~5.0.3: version "5.0.3" resolved "https://registry.yarnpkg.com/semver/-/semver-5.0.3.tgz#77466de589cd5d3c95f138aa78bc569a3cb5d27a" -semver@~5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" - send@0.13.1: version "0.13.1" resolved "https://registry.yarnpkg.com/send/-/send-0.13.1.tgz#a30d5f4c82c8a9bae9ad00a1d9b1bdbe6f199ed7" @@ -2117,14 +2094,10 @@ speedometer@~0.1.2: version "0.1.4" resolved "https://registry.yarnpkg.com/speedometer/-/speedometer-0.1.4.tgz#9876dbd2a169d3115402d48e6ea6329c8816a50d" -sphericalmercator@1.0.4: +sphericalmercator@1.0.4, sphericalmercator@1.0.x, sphericalmercator@~1.0.1, sphericalmercator@~1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/sphericalmercator/-/sphericalmercator-1.0.4.tgz#baad4e34187f06e87f2e92fc1280199fa1b01d4e" -sphericalmercator@1.0.x, sphericalmercator@~1.0.1, sphericalmercator@~1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/sphericalmercator/-/sphericalmercator-1.0.5.tgz#ddc5a049e360e000d0fad9fc22c4071882584980" - split@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/split/-/split-1.0.1.tgz#605bd9be303aa59fb35f9229fbea0ddec9ea07d9" @@ -2274,7 +2247,7 @@ through@2: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" -"tilelive-bridge@github:cartodb/tilelive-bridge#2.3.1-cdb4": +tilelive-bridge@cartodb/tilelive-bridge#2.3.1-cdb4: version "2.3.1-cdb4" resolved "https://codeload.github.com/cartodb/tilelive-bridge/tar.gz/faa2b638da2d119b78281575d40255cb523f6ca6" dependencies: @@ -2282,7 +2255,7 @@ through@2: mapnik-pool "~0.1.3" sphericalmercator "1.0.x" -"tilelive-mapnik@github:cartodb/tilelive-mapnik#0.6.18-cdb3": +tilelive-mapnik@cartodb/tilelive-mapnik#0.6.18-cdb3: version "0.6.18-cdb3" resolved "https://codeload.github.com/cartodb/tilelive-mapnik/tar.gz/23bd1c31dd57d0b76c86b9f1eaf62462b3c17d01" dependencies: @@ -2307,7 +2280,7 @@ torque.js@~2.11.0: dependencies: carto CartoDB/carto#0.15.1-cdb1 -tough-cookie@~2.3.0, tough-cookie@~2.3.2: +tough-cookie@~2.3.0, tough-cookie@~2.3.2, tough-cookie@~2.3.3: version "2.3.3" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.3.tgz#0b618a5565b6dea90bf3425d04d55edc475a7561" dependencies: From 52402c033304d842eea24038789e949e27d0bd61 Mon Sep 17 00:00:00 2001 From: Eneko Lakasta Date: Thu, 28 Sep 2017 18:25:40 +0200 Subject: [PATCH 155/159] Revert "upgrade request module" This reverts commit cacb92b0c4aa40b962b6fe7ac11580d94d251afe. --- package.json | 2 +- yarn.lock | 125 +++++++++++++++++++++++++++++++-------------------- 2 files changed, 77 insertions(+), 50 deletions(-) diff --git a/package.json b/package.json index 58bc500d..ba7422b4 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "node-statsd": "~0.0.7", "queue-async": "~1.0.7", "redis-mpool": "0.4.1", - "request": "^2.83.0", + "request": "~2.79.0", "semver": "~5.3.0", "step": "~0.0.6", "step-profiler": "~0.3.0", diff --git a/yarn.lock b/yarn.lock index 25a7fa2c..e1ad9ba7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,7 +2,7 @@ # yarn lockfile v1 -abaculus@cartodb/abaculus#2.0.3-cdb1: +"abaculus@github:cartodb/abaculus#2.0.3-cdb1": version "2.0.3-cdb1" resolved "https://codeload.github.com/cartodb/abaculus/tar.gz/f5f34e1c80cdd8d49edd1d6fe3b2220ab2e23aaf" dependencies: @@ -10,7 +10,11 @@ abaculus@cartodb/abaculus#2.0.3-cdb1: mapnik "~3.5.0" sphericalmercator "1.0.x" -abbrev@1, abbrev@1.0.x: +abbrev@1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.0.tgz#d0554c2256636e2f56e7c2e5ad183f859428d81f" + +abbrev@1.0.x: version "1.0.9" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135" @@ -226,7 +230,7 @@ camshaft@0.59.1: dot "^1.0.3" request "^2.69.0" -canvas@cartodb/node-canvas#1.6.2-cdb2: +"canvas@github:cartodb/node-canvas#1.6.2-cdb2": version "1.6.2-cdb2" resolved "https://codeload.github.com/cartodb/node-canvas/tar.gz/8acf04557005c633f9e68524488a2657c04f3766" dependencies: @@ -244,15 +248,15 @@ carto@0.16.3: semver "^5.1.0" yargs "^4.2.0" -carto@CartoDB/carto#0.15.1-cdb1: +"carto@github:cartodb/carto#0.15.1-cdb1": version "0.15.1-cdb1" - resolved "https://codeload.github.com/CartoDB/carto/tar.gz/8050ec843f1f32a6469e5d1cf49602773015d398" + resolved "https://codeload.github.com/cartodb/carto/tar.gz/8050ec843f1f32a6469e5d1cf49602773015d398" dependencies: mapnik-reference "~6.0.2" optimist "~0.6.0" underscore "~1.6.0" -carto@cartodb/carto#0.15.1-cdb3: +"carto@github:cartodb/carto#0.15.1-cdb3": version "0.15.1-cdb3" resolved "https://codeload.github.com/cartodb/carto/tar.gz/945f5efb74fd1af1f5e1f69f409f9567f94fb5a7" dependencies: @@ -364,12 +368,16 @@ combined-stream@^1.0.5, combined-stream@~1.0.5: dependencies: delayed-stream "~1.0.0" -commander@2.9.0, commander@^2.9.0: +commander@2.9.0: version "2.9.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4" dependencies: graceful-readlink ">= 1.0.0" +commander@^2.9.0: + version "2.11.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563" + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -430,7 +438,7 @@ date-now@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b" -debug@2.2.0, debug@^2.2.0, debug@~2.2.0: +debug@2.2.0, debug@~2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da" dependencies: @@ -448,6 +456,12 @@ debug@^1.0.4: dependencies: ms "2.0.0" +debug@^2.2.0: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + dependencies: + ms "2.0.0" + decamelize@^1.0.0, decamelize@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" @@ -518,7 +532,11 @@ domutils@1.5: dom-serializer "0" domelementtype "1" -dot@^1.0.3, dot@~1.0.2: +dot@^1.0.3: + version "1.1.2" + resolved "https://registry.yarnpkg.com/dot/-/dot-1.1.2.tgz#c7377019fc4e550798928b2b9afeb66abfa1f2f9" + +dot@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/dot/-/dot-1.0.3.tgz#f8750bfb6b03c7664eb0e6cb1eb4c66419af9427" @@ -772,7 +790,7 @@ getpass@^0.1.1: dependencies: assert-plus "^1.0.0" -glob@7.1.1, glob@^7.0.5, glob@^7.1.1: +glob@7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8" dependencies: @@ -803,6 +821,17 @@ glob@^6.0.1: once "^1.3.0" path-is-absolute "^1.0.0" +glob@^7.0.5, glob@^7.1.1: + version "7.1.2" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + graceful-fs@^4.1.2: version "4.1.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" @@ -1316,7 +1345,7 @@ mime-types@^2.1.12, mime-types@~2.1.15, mime-types@~2.1.17, mime-types@~2.1.6, m dependencies: mime-db "~1.30.0" -mime@1.3.4, mime@~1.3.4: +mime@1.3.4: version "1.3.4" resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.4.tgz#115f9e3b6b3daf2959983cb38f149a2d40eb5d53" @@ -1324,13 +1353,17 @@ mime@~1.2.11: version "1.2.11" resolved "https://registry.yarnpkg.com/mime/-/mime-1.2.11.tgz#58203eed86e3a5ef17aed2b7d9ebd47f0a60dd10" -"minimatch@2 || 3", minimatch@^3.0.0, minimatch@^3.0.2, minimatch@~3.0.2: +mime@~1.3.4: + version "1.3.6" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.6.tgz#591d84d3653a6b0b4a3b9df8de5aa8108e72e5e0" + +"minimatch@2 || 3", minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.4, minimatch@~3.0.2: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" dependencies: brace-expansion "^1.1.7" -minimist@0.0.8, minimist@~0.0.1: +minimist@0.0.8: version "0.0.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" @@ -1338,6 +1371,10 @@ minimist@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" +minimist@~0.0.1: + version "0.0.10" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" + minimist@~0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.2.0.tgz#4dffe525dae2b864c66c2e23c6271d7afdecefce" @@ -1604,7 +1641,7 @@ pg-types@1.*: postgres-date "~1.0.0" postgres-interval "^1.1.0" -pg@cartodb/node-postgres#6.1.6-cdb1: +"pg@github:cartodb/node-postgres#6.1.6-cdb1": version "6.1.6" resolved "https://codeload.github.com/cartodb/node-postgres/tar.gz/3eef52dd1e655f658a4ee8ac5697688b3ecfed44" dependencies: @@ -1783,7 +1820,7 @@ read-pkg@^1.0.0: normalize-package-data "^2.3.2" path-type "^1.0.0" -readable-stream@1.1, readable-stream@~1.1.9: +readable-stream@1.1: version "1.1.13" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.13.tgz#f6eef764f514c89e2b9e23146a75ba106756d23e" dependencies: @@ -1813,6 +1850,15 @@ readable-stream@~1.0.2: isarray "0.0.1" string_decoder "~0.10.x" +readable-stream@~1.1.9: + version "1.1.14" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "0.0.1" + string_decoder "~0.10.x" + redis-mpool@0.4.1, redis-mpool@~0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/redis-mpool/-/redis-mpool-0.4.1.tgz#d917c0a4ed57a1291a9c6eb35434e6c0b7046f80" @@ -1884,33 +1930,6 @@ request@2.x, request@^2.55.0, request@^2.69.0: tunnel-agent "^0.6.0" uuid "^3.1.0" -request@^2.83.0: - version "2.83.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.83.0.tgz#ca0b65da02ed62935887808e6f510381034e3356" - dependencies: - aws-sign2 "~0.7.0" - aws4 "^1.6.0" - caseless "~0.12.0" - combined-stream "~1.0.5" - extend "~3.0.1" - forever-agent "~0.6.1" - form-data "~2.3.1" - har-validator "~5.0.3" - hawk "~6.0.2" - http-signature "~1.2.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.17" - oauth-sign "~0.8.2" - performance-now "^2.1.0" - qs "~6.5.1" - safe-buffer "^5.1.1" - stringstream "~0.0.5" - tough-cookie "~2.3.3" - tunnel-agent "^0.6.0" - uuid "^3.1.0" - request@~2.79.0: version "2.79.0" resolved "https://registry.yarnpkg.com/request/-/request-2.79.0.tgz#4dfe5bf6be8b8cdc37fcf93e04b65577722710de" @@ -1974,9 +1993,9 @@ safe-json-stringify@~1: version "1.0.4" resolved "https://registry.yarnpkg.com/safe-json-stringify/-/safe-json-stringify-1.0.4.tgz#81a098f447e4bbc3ff3312a243521bc060ef5911" -"semver@2 || 3 || 4 || 5", semver@^5.1.0, semver@^5.3.0, semver@~5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" +"semver@2 || 3 || 4 || 5", semver@^5.1.0, semver@^5.3.0: + version "5.4.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e" semver@4.3.2: version "4.3.2" @@ -1990,6 +2009,10 @@ semver@~5.0.3: version "5.0.3" resolved "https://registry.yarnpkg.com/semver/-/semver-5.0.3.tgz#77466de589cd5d3c95f138aa78bc569a3cb5d27a" +semver@~5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" + send@0.13.1: version "0.13.1" resolved "https://registry.yarnpkg.com/send/-/send-0.13.1.tgz#a30d5f4c82c8a9bae9ad00a1d9b1bdbe6f199ed7" @@ -2094,10 +2117,14 @@ speedometer@~0.1.2: version "0.1.4" resolved "https://registry.yarnpkg.com/speedometer/-/speedometer-0.1.4.tgz#9876dbd2a169d3115402d48e6ea6329c8816a50d" -sphericalmercator@1.0.4, sphericalmercator@1.0.x, sphericalmercator@~1.0.1, sphericalmercator@~1.0.4: +sphericalmercator@1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/sphericalmercator/-/sphericalmercator-1.0.4.tgz#baad4e34187f06e87f2e92fc1280199fa1b01d4e" +sphericalmercator@1.0.x, sphericalmercator@~1.0.1, sphericalmercator@~1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/sphericalmercator/-/sphericalmercator-1.0.5.tgz#ddc5a049e360e000d0fad9fc22c4071882584980" + split@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/split/-/split-1.0.1.tgz#605bd9be303aa59fb35f9229fbea0ddec9ea07d9" @@ -2247,7 +2274,7 @@ through@2: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" -tilelive-bridge@cartodb/tilelive-bridge#2.3.1-cdb4: +"tilelive-bridge@github:cartodb/tilelive-bridge#2.3.1-cdb4": version "2.3.1-cdb4" resolved "https://codeload.github.com/cartodb/tilelive-bridge/tar.gz/faa2b638da2d119b78281575d40255cb523f6ca6" dependencies: @@ -2255,7 +2282,7 @@ tilelive-bridge@cartodb/tilelive-bridge#2.3.1-cdb4: mapnik-pool "~0.1.3" sphericalmercator "1.0.x" -tilelive-mapnik@cartodb/tilelive-mapnik#0.6.18-cdb3: +"tilelive-mapnik@github:cartodb/tilelive-mapnik#0.6.18-cdb3": version "0.6.18-cdb3" resolved "https://codeload.github.com/cartodb/tilelive-mapnik/tar.gz/23bd1c31dd57d0b76c86b9f1eaf62462b3c17d01" dependencies: @@ -2280,7 +2307,7 @@ torque.js@~2.11.0: dependencies: carto CartoDB/carto#0.15.1-cdb1 -tough-cookie@~2.3.0, tough-cookie@~2.3.2, tough-cookie@~2.3.3: +tough-cookie@~2.3.0, tough-cookie@~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.3.tgz#0b618a5565b6dea90bf3425d04d55edc475a7561" dependencies: From 45dece65f2677f015c9c05dde2177a28853a1590 Mon Sep 17 00:00:00 2001 From: Rafa de la Torre Date: Mon, 2 Oct 2017 14:45:43 +0200 Subject: [PATCH 156/159] Update 3.13.0 release date in NEWS.md --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index c20ae985..452f80dc 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,7 +1,7 @@ # Changelog ## 3.13.0 -Released 2017-mm-dd +Released 2017-10-02 - Upgrades camshaft, cartodb-query-tables, and turbo-carto: better support for query variables. Bugfixes: From b8783a6447a08e126d926399b92c10efed29d8d8 Mon Sep 17 00:00:00 2001 From: Rafa de la Torre Date: Mon, 2 Oct 2017 14:49:01 +0200 Subject: [PATCH 157/159] Stub NEWS/package for next version --- NEWS.md | 6 ++++++ package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 452f80dc..182707e5 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,11 @@ # Changelog +## 3.13.1 +Released 2017-mm-dd + +Bugfixes: + - + ## 3.13.0 Released 2017-10-02 - Upgrades camshaft, cartodb-query-tables, and turbo-carto: better support for query variables. diff --git a/package.json b/package.json index ba7422b4..c533b40e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "windshaft-cartodb", - "version": "3.13.0", + "version": "3.13.1", "description": "A map tile server for CartoDB", "keywords": [ "cartodb" From 0a7506e4b2aac2dee3e382bc8331870db976c1e8 Mon Sep 17 00:00:00 2001 From: Rafa de la Torre Date: Tue, 3 Oct 2017 12:10:43 +0200 Subject: [PATCH 158/159] Update yarn version to v0.27.5 --- INSTALL.md | 2 +- NEWS.md | 1 + package.json | 2 +- yarn.lock | 16 ++++++++-------- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index 2de2cb09..11cf7db8 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -5,7 +5,7 @@ Make sure that you have the requirements needed. These are - Core - Node.js >=6.9.x - - yarn >=0.21.3 + - yarn >=0.27.5 - PostgreSQL >8.3.x, PostGIS >1.5.x - Redis >2.4.0 (http://www.redis.io) - Mapnik >3.x. See [Installing Mapnik](https://github.com/CartoDB/Windshaft#installing-mapnik). diff --git a/NEWS.md b/NEWS.md index 182707e5..139c9a3f 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,6 +2,7 @@ ## 3.13.1 Released 2017-mm-dd + - Upgrades yarn minimum version requirement to v0.27.5 Bugfixes: - diff --git a/package.json b/package.json index c533b40e..6532a04e 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,6 @@ }, "engines": { "node": ">=6.9", - "yarn": "^0.21.3" + "yarn": "^0.27.5" } } diff --git a/yarn.lock b/yarn.lock index e1ad9ba7..62fc9315 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,7 +2,7 @@ # yarn lockfile v1 -"abaculus@github:cartodb/abaculus#2.0.3-cdb1": +abaculus@cartodb/abaculus#2.0.3-cdb1: version "2.0.3-cdb1" resolved "https://codeload.github.com/cartodb/abaculus/tar.gz/f5f34e1c80cdd8d49edd1d6fe3b2220ab2e23aaf" dependencies: @@ -230,7 +230,7 @@ camshaft@0.59.1: dot "^1.0.3" request "^2.69.0" -"canvas@github:cartodb/node-canvas#1.6.2-cdb2": +canvas@cartodb/node-canvas#1.6.2-cdb2: version "1.6.2-cdb2" resolved "https://codeload.github.com/cartodb/node-canvas/tar.gz/8acf04557005c633f9e68524488a2657c04f3766" dependencies: @@ -248,15 +248,15 @@ carto@0.16.3: semver "^5.1.0" yargs "^4.2.0" -"carto@github:cartodb/carto#0.15.1-cdb1": +carto@CartoDB/carto#0.15.1-cdb1: version "0.15.1-cdb1" - resolved "https://codeload.github.com/cartodb/carto/tar.gz/8050ec843f1f32a6469e5d1cf49602773015d398" + resolved "https://codeload.github.com/CartoDB/carto/tar.gz/8050ec843f1f32a6469e5d1cf49602773015d398" dependencies: mapnik-reference "~6.0.2" optimist "~0.6.0" underscore "~1.6.0" -"carto@github:cartodb/carto#0.15.1-cdb3": +carto@cartodb/carto#0.15.1-cdb3: version "0.15.1-cdb3" resolved "https://codeload.github.com/cartodb/carto/tar.gz/945f5efb74fd1af1f5e1f69f409f9567f94fb5a7" dependencies: @@ -1641,7 +1641,7 @@ pg-types@1.*: postgres-date "~1.0.0" postgres-interval "^1.1.0" -"pg@github:cartodb/node-postgres#6.1.6-cdb1": +pg@cartodb/node-postgres#6.1.6-cdb1: version "6.1.6" resolved "https://codeload.github.com/cartodb/node-postgres/tar.gz/3eef52dd1e655f658a4ee8ac5697688b3ecfed44" dependencies: @@ -2274,7 +2274,7 @@ through@2: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" -"tilelive-bridge@github:cartodb/tilelive-bridge#2.3.1-cdb4": +tilelive-bridge@cartodb/tilelive-bridge#2.3.1-cdb4: version "2.3.1-cdb4" resolved "https://codeload.github.com/cartodb/tilelive-bridge/tar.gz/faa2b638da2d119b78281575d40255cb523f6ca6" dependencies: @@ -2282,7 +2282,7 @@ through@2: mapnik-pool "~0.1.3" sphericalmercator "1.0.x" -"tilelive-mapnik@github:cartodb/tilelive-mapnik#0.6.18-cdb3": +tilelive-mapnik@cartodb/tilelive-mapnik#0.6.18-cdb3: version "0.6.18-cdb3" resolved "https://codeload.github.com/cartodb/tilelive-mapnik/tar.gz/23bd1c31dd57d0b76c86b9f1eaf62462b3c17d01" dependencies: From 89172f280f6032a7f3c3b14a9c99b847902c325d Mon Sep 17 00:00:00 2001 From: Rafa de la Torre Date: Tue, 3 Oct 2017 12:43:09 +0200 Subject: [PATCH 159/159] Change yarn dep version range specification In theory the caret `^` should work but as rochoa pointed out it does not. So changing it (also for the sake of clarity). --- INSTALL.md | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index 11cf7db8..45654b0b 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -5,7 +5,7 @@ Make sure that you have the requirements needed. These are - Core - Node.js >=6.9.x - - yarn >=0.27.5 + - yarn >=0.27.5 <1.0.0 - PostgreSQL >8.3.x, PostGIS >1.5.x - Redis >2.4.0 (http://www.redis.io) - Mapnik >3.x. See [Installing Mapnik](https://github.com/CartoDB/Windshaft#installing-mapnik). diff --git a/package.json b/package.json index 6532a04e..fd857e96 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,6 @@ }, "engines": { "node": ">=6.9", - "yarn": "^0.27.5" + "yarn": ">=0.27.5 <1.0.0" } }