diff --git a/NEWS.md b/NEWS.md index 9ca6d4a4..2efa11b6 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,8 +1,88 @@ # Changelog -## 3.10.2 +## 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). + +## 3.12.9 +Released 2017-09-07 + +Bug fixes: +- Do not use distinct when calculating quantiles. #743 + +## 3.12.8 +Released 2017-09-07 + +Bug fixes: +- Integer out of range in date histograms. (https://github.com/CartoDB/support/issues/962) + +## 3.12.7 +Released 2017-09-01 + + - Upgrades camshaft to [0.58.1](https://github.com/CartoDB/camshaft/releases/tag/0.58.1). + + +## 3.12.6 +Released 2017-08-31 + + - Upgrades camshaft to [0.58.0](https://github.com/CartoDB/camshaft/releases/tag/0.58.0). + + +## 3.12.5 +Released 2017-08-24 + + - Upgrades camshaft to [0.57.0](https://github.com/CartoDB/camshaft/releases/tag/0.57.0). + + +## 3.12.4 +Released 2017-08-23 + +Announcements: + - Upgrades camshaft to [0.56.0](https://github.com/CartoDB/camshaft/releases/tag/0.56.0). + +## 3.12.3 +Released 2017-08-22 + +Announcements: + - Upgrades camshaft to [0.55.8](https://github.com/CartoDB/camshaft/releases/tag/0.55.8). + +## 3.12.2 +Released 2017-08-16 + +Bug fixes: + - Polygon count problems #725. + + +## 3.12.1 +Released 2017-08-13 + - Upgrades cartodb-psql to [0.10.1](https://github.com/CartoDB/node-cartodb-psql/releases/tag/0.10.1). + - Upgrades windshaft to [3.3.1](https://github.com/CartoDB/windshaft/releases/tag/3.3.1). + - Upgrades camshaft to [0.55.7](https://github.com/CartoDB/camshaft/releases/tag/0.55.7). + + +## 3.12.0 +Released 2017-08-10 + +Announcements: + - Apply max tile response time for requests to layergoup, tiles, static maps, attributes and dataviews services #717. + - Upgrades windshaft to [3.3.0](https://github.com/CartoDB/windshaft/releases/tag/3.3.0). + - Upgrades cartodb-redis to [0.14.0](https://github.com/CartoDB/node-cartodb-redis/releases/tag/0.14.0). + + +## 3.11.0 +Released 2017-08-08 + +Announcements: + - Allow to override with any aggregation for histograms instantiated w/o aggregation. + +Bug fixes: + - Apply timezone after truncating the minimun date for each bin to calculate timestamps in time-series. + - Support timestamp with timezones to calculate the number of bins in time-series. + - Fixed issue related to name collision while building time-series query. + ## 3.10.1 Released 2017-08-04 diff --git a/lib/cartodb/api/user_limits_api.js b/lib/cartodb/api/user_limits_api.js index ae9cd46b..0f4f0f4c 100644 --- a/lib/cartodb/api/user_limits_api.js +++ b/lib/cartodb/api/user_limits_api.js @@ -1,3 +1,5 @@ +var step = require('step'); + /** * * @param metadataBackend @@ -13,16 +15,65 @@ function UserLimitsApi(metadataBackend, options) { module.exports = UserLimitsApi; -UserLimitsApi.prototype.getRenderLimits = function (username, callback) { +UserLimitsApi.prototype.getRenderLimits = function (username, apiKey, callback) { var self = this; - this.metadataBackend.getTilerRenderLimit(username, function handleTilerLimits(err, renderLimit) { + + var limits = { + cacheOnTimeout: self.options.limits.cacheOnTimeout || false, + render: self.options.limits.render || 0 + }; + + self.getTimeoutRenderLimit(username, apiKey, function (err, timeoutRenderLimit) { if (err) { return callback(err); } - return callback(null, { - cacheOnTimeout: self.options.limits.cacheOnTimeout || false, - render: renderLimit || self.options.limits.render || 0 - }); + if (timeoutRenderLimit && timeoutRenderLimit.render) { + if (Number.isFinite(timeoutRenderLimit.render)) { + limits.render = timeoutRenderLimit.render; + } + } + + return callback(null, limits); }); }; + +UserLimitsApi.prototype.getTimeoutRenderLimit = function (username, apiKey, callback) { + var self = this; + + step( + function isAuthorized() { + var next = this; + + if (!apiKey) { + return next(null, false); + } + + self.metadataBackend.getUserMapKey(username, function (err, userApiKey) { + if (err) { + return next(err); + } + + return next(null, userApiKey === apiKey); + }); + }, + function getUserTimeoutRenderLimits(err, authorized) { + var next = this; + + if (err) { + return next(err); + } + + self.metadataBackend.getUserTimeoutRenderLimits(username, function (err, timeoutRenderLimit) { + if (err) { + return next(err); + } + + next(null, { + render: authorized ? timeoutRenderLimit.render : timeoutRenderLimit.renderPublic + }); + }); + }, + callback + ); +}; diff --git a/lib/cartodb/backends/turbo-carto-postgres-datasource.js b/lib/cartodb/backends/turbo-carto-postgres-datasource.js index 65109325..b2550eb7 100644 --- a/lib/cartodb/backends/turbo-carto-postgres-datasource.js +++ b/lib/cartodb/backends/turbo-carto-postgres-datasource.js @@ -21,10 +21,10 @@ function createTemplate(method) { } var methods = { - quantiles: 'CDB_QuantileBins(array_agg(distinct({{=it._column}}::numeric)), {{=it._buckets}}) as quantiles', + quantiles: 'CDB_QuantileBins(array_agg({{=it._column}}::numeric), {{=it._buckets}}) as quantiles', equal: 'CDB_EqualIntervalBins(array_agg({{=it._column}}::numeric), {{=it._buckets}}) as equal', - jenks: 'CDB_JenksBins(array_agg(distinct({{=it._column}}::numeric)), {{=it._buckets}}) as jenks', - headtails: 'CDB_HeadsTailsBins(array_agg(distinct({{=it._column}}::numeric)), {{=it._buckets}}) as headtails' + jenks: 'CDB_JenksBins(array_agg({{=it._column}}::numeric), {{=it._buckets}}) as jenks', + headtails: 'CDB_HeadsTailsBins(array_agg({{=it._column}}::numeric), {{=it._buckets}}) as headtails' }; var methodTemplates = Object.keys(methods).reduce(function(methodTemplates, methodName) { diff --git a/lib/cartodb/controllers/base.js b/lib/cartodb/controllers/base.js index 5aab7cb3..f74702e9 100644 --- a/lib/cartodb/controllers/base.js +++ b/lib/cartodb/controllers/base.js @@ -187,6 +187,9 @@ BaseController.prototype.send = function(req, res, body, status, headers) { BaseController.prototype.sendError = function(req, res, err, label) { var allErrors = Array.isArray(err) ? err : [err]; + + allErrors = populateTimeoutErrors(allErrors); + label = label || 'UNKNOWN'; err = allErrors[0] || new Error(label); allErrors[0] = err; @@ -221,6 +224,18 @@ function stripConnectionInfo(message) { .replace(/is the server.*encountered/im, 'encountered'); } +var ERROR_INFO_TO_EXPOSE = { + message: true, + layer: true, + type: true, + analysis: true, + subtype: true +}; + +function shouldBeExposed (prop) { + return !!ERROR_INFO_TO_EXPOSE[prop]; +} + function errorMessage(err) { // See https://github.com/Vizzuality/Windshaft-cartodb/issues/68 var message = (_.isString(err) ? err : err.message) || 'Unknown error'; @@ -239,7 +254,7 @@ function errorMessageWithContext(err) { for (var prop in err) { // type & message are properties from Error's prototype and will be skipped - if (err.hasOwnProperty(prop)) { + if (err.hasOwnProperty(prop) && shouldBeExposed(prop)) { error[prop] = err[prop]; } } @@ -281,5 +296,38 @@ function statusFromErrorMessage(errMsg) { statusCode = 404; } } + return statusCode; } + +function isRenderTimeoutError (err) { + return err.message === 'Render timed out'; +} + +function isDatasourceTimeoutError (err) { + return err.message && err.message.match(/canceling statement due to statement timeout/i); +} + +function isTimeoutError (err) { + return isRenderTimeoutError(err) || isDatasourceTimeoutError(err); +} + +function populateTimeoutErrors (errors) { + return errors.map(function (error) { + if (isRenderTimeoutError(error)) { + error.subtype = 'render'; + } + + if (isDatasourceTimeoutError(error)) { + error.subtype = 'datasource'; + } + + if (isTimeoutError(error)) { + error.message = 'You are over platform\'s limits. Please contact us to know more details'; + error.type = 'limit'; + error.http_status = 429; + } + + return error; + }); +} diff --git a/lib/cartodb/models/dataview/base.js b/lib/cartodb/models/dataview/base.js index 280487b4..29069d37 100644 --- a/lib/cartodb/models/dataview/base.js +++ b/lib/cartodb/models/dataview/base.js @@ -13,7 +13,6 @@ BaseDataview.prototype.getResult = function(psql, override, callback) { } psql.query(query, function(err, result) { - if (err) { return callback(err, result); } diff --git a/lib/cartodb/models/dataview/histogram.js b/lib/cartodb/models/dataview/histogram.js index 76b42c03..6e62ad9d 100644 --- a/lib/cartodb/models/dataview/histogram.js +++ b/lib/cartodb/models/dataview/histogram.js @@ -9,42 +9,42 @@ var columnCastTpl = dot.template("date_part('epoch', {{=it.column}})"); var dateIntervalQueryTpl = dot.template([ 'WITH', - 'dates AS (', + '__cdb_dates AS (', ' SELECT', - ' MAX({{=it.column}}::timestamp) AS _end,', - ' MIN({{=it.column}}::timestamp) AS _start', - ' FROM ({{=it.query}}) _cdb_source', + ' MAX({{=it.column}}::timestamp) AS __cdb_end,', + ' MIN({{=it.column}}::timestamp) AS __cdb_start', + ' FROM ({{=it.query}}) __cdb_source', '),', - 'interval_in_days AS (', + '__cdb_interval_in_days AS (', ' SELECT' , - ' DATE_PART(\'day\', _end - _start) AS days', - ' FROM dates', + ' DATE_PART(\'day\', __cdb_end - __cdb_start) AS __cdb_days', + ' FROM __cdb_dates', '),', - 'interval_in_hours AS (', + '__cdb_interval_in_hours AS (', ' SELECT', - ' days * 24 + DATE_PART(\'hour\', _end - _start) AS hours', - ' FROM interval_in_days, dates', + ' __cdb_days * 24 + DATE_PART(\'hour\', __cdb_end - __cdb_start) AS __cdb_hours', + ' FROM __cdb_interval_in_days, __cdb_dates', '),', - 'interval_in_minutes AS (', + '__cdb_interval_in_minutes AS (', ' SELECT', - ' hours * 60 + DATE_PART(\'minute\', _end - _start) AS minutes', - ' FROM interval_in_hours, dates', + ' __cdb_hours * 60 + DATE_PART(\'minute\', __cdb_end - __cdb_start) AS __cdb_minutes', + ' FROM __cdb_interval_in_hours, __cdb_dates', '),', - 'interval_in_seconds AS (', + '__cdb_interval_in_seconds AS (', ' SELECT', - ' minutes * 60 + DATE_PART(\'second\', _end - _start) AS seconds', - ' FROM interval_in_minutes, dates', + ' __cdb_minutes * 60 + DATE_PART(\'second\', __cdb_end - __cdb_start) AS __cdb_seconds', + ' FROM __cdb_interval_in_minutes, __cdb_dates', ')', 'SELECT', - ' ROUND(days / 365) AS year,', - ' ROUND(days / 90) AS quarter,', - ' ROUND(days / 30) AS month,', - ' ROUND(days / 7) AS week,', - ' days AS day,', - ' hours AS hour,', - ' minutes AS minute,', - ' seconds AS second', - 'FROM interval_in_days, interval_in_hours, interval_in_minutes, interval_in_seconds' + ' 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')); var MAX_INTERVAL_VALUE = 366; @@ -52,9 +52,9 @@ var BIN_MIN_NUMBER = 6; var BIN_MAX_NUMBER = 48; var filteredQueryTpl = dot.template([ - 'filtered_source AS (', + '__cdb_filtered_source AS (', ' SELECT *', - ' FROM ({{=it._query}}) _cdb_filtered_source', + ' FROM ({{=it._query}}) __cdb_filtered_source_query', ' WHERE', ' {{=it._column}} IS NOT NULL', ' {{?it._isFloatColumn}}AND', @@ -67,74 +67,74 @@ var filteredQueryTpl = dot.template([ ].join(' \n')); var basicsQueryTpl = dot.template([ - 'basics AS (', + '__cdb_basics AS (', ' SELECT', - ' max({{=it._column}}) AS max_val, min({{=it._column}}) AS min_val,', - ' avg({{=it._column}}) AS avg_val, count(1) AS total_rows', - ' FROM filtered_source', + ' 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')); var overrideBasicsQueryTpl = dot.template([ - 'basics AS (', + '__cdb_basics AS (', ' SELECT', - ' max({{=it._end}}) AS max_val, min({{=it._start}}) AS min_val,', - ' avg({{=it._column}}) AS avg_val, count(1) AS total_rows', - ' FROM filtered_source', + ' 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')); var iqrQueryTpl = dot.template([ - 'iqrange AS (', - ' SELECT max(quartile_max) - min(quartile_max) AS iqr', + '__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 filtered_source) _cdb_quartiles', + ' FROM __cdb_filtered_source) _cdb_quartiles', ' WHERE quartile = 1 or quartile = 3', ' GROUP BY quartile', - ' ) _cdb_iqr', + ' ) __cdb_iqr', ')' ].join('\n')); var binsQueryTpl = dot.template([ - 'bins AS (', - ' SELECT CASE WHEN total_rows = 0 OR iqr = 0', + '__cdb_bins AS (', + ' SELECT CASE WHEN __cdb_total_rows = 0 OR __cdb_iqr = 0', ' THEN 1', ' ELSE GREATEST(', - ' LEAST({{=it._minBins}}, CAST(total_rows AS INT)),', + ' LEAST({{=it._minBins}}, CAST(__cdb_total_rows AS INT)),', ' LEAST(', - ' CAST(((max_val - min_val) / (2 * iqr * power(total_rows, 1/3))) AS INT),', + ' CAST(((__cdb_max_val - __cdb_min_val) / (2 * __cdb_iqr * power(__cdb_total_rows, 1/3))) AS INT),', ' {{=it._maxBins}}', ' )', ' )', - ' END AS bins_number', - ' FROM basics, iqrange, filtered_source', + ' END AS __cdb_bins_number', + ' FROM __cdb_basics, __cdb_iqrange, __cdb_filtered_source', ' LIMIT 1', ')' ].join('\n')); var overrideBinsQueryTpl = dot.template([ - 'bins AS (', - ' SELECT {{=it._bins}} AS bins_number', + '__cdb_bins AS (', + ' SELECT {{=it._bins}} AS __cdb_bins_number', ')' ].join('\n')); var nullsQueryTpl = dot.template([ - 'nulls AS (', + '__cdb_nulls AS (', ' SELECT', - ' count(*) AS nulls_count', - ' FROM ({{=it._query}}) _cdb_histogram_nulls', + ' count(*) AS __cdb_nulls_count', + ' FROM ({{=it._query}}) __cdb_histogram_nulls', ' WHERE {{=it._column}} IS NULL', ')' ].join('\n')); var infinitiesQueryTpl = dot.template([ - 'infinities AS (', + '__cdb_infinities AS (', ' SELECT', - ' count(*) AS infinities_count', - ' FROM ({{=it._query}}) _cdb_histogram_infinities', + ' count(*) AS __cdb_infinities_count', + ' FROM ({{=it._query}}) __cdb_infinities_query', ' WHERE', ' {{=it._column}} = \'infinity\'::float', ' OR', @@ -143,121 +143,128 @@ var infinitiesQueryTpl = dot.template([ ].join('\n')); var nansQueryTpl = dot.template([ - 'nans AS (', + '__cdb_nans AS (', ' SELECT', - ' count(*) AS nans_count', - ' FROM ({{=it._query}}) _cdb_histogram_infinities', + ' count(*) AS __cdb_nans_count', + ' FROM ({{=it._query}}) __cdb_nans_query', ' WHERE {{=it._column}} = \'NaN\'::float', ')' ].join('\n')); var histogramQueryTpl = dot.template([ 'SELECT', - ' (max_val - min_val) / cast(bins_number as float) AS bin_width,', - ' bins_number,', - ' nulls_count,', - ' {{?it._isFloatColumn}}infinities_count,', - ' nans_count,{{?}}', - ' avg_val,', - ' CASE WHEN min_val = max_val', + ' (__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}}, min_val, max_val, bins_number), bins_number)) - 1', + ' 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 filtered_source, basics, nulls, bins{{?it._isFloatColumn}}, infinities, nans{{?}}', + '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')); var dateBasicsQueryTpl = dot.template([ - 'basics AS (', + '__cdb_basics AS (', ' SELECT', - ' max(date_part(\'epoch\', {{=it._column}})) AS max_val,', - ' min(date_part(\'epoch\', {{=it._column}})) AS min_val,', - ' avg(date_part(\'epoch\', {{=it._column}})) AS avg_val,', + ' 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}} AT TIME ZONE \'{{=it._offset}}\'', - ' )) AS start_date,', - ' max({{=it._column}} AT TIME ZONE \'{{=it._offset}}\') AS end_date,', - ' count(1) AS total_rows', - ' FROM ({{=it._query}}) _cdb_basics', + ' \'{{=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')); var dateOverrideBasicsQueryTpl = dot.template([ - 'basics AS (', + '__cdb_basics AS (', ' SELECT', - ' max({{=it._end}}) AS max_val,', - ' min({{=it._start}}) AS min_val,', - ' avg(date_part(\'epoch\', {{=it._column}})) AS avg_val,', + ' max({{=it._end}})::float AS __cdb_max_val,', + ' min({{=it._start}})::float 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 start_date,', + ' ) AS __cdb_start_date,', ' max(', ' TO_TIMESTAMP({{=it._end}})::timestamp AT TIME ZONE \'{{=it._offset}}\'', - ' ) AS end_date,', - ' count(1) AS total_rows', - ' FROM ({{=it._query}}) _cdb_basics', + ' ) AS __cdb_end_date,', + ' count(1) AS __cdb_total_rows', + ' FROM ({{=it._query}}) __cdb_basics_query', ')' ].join(' \n')); var dateBinsQueryTpl = dot.template([ - 'bins AS (', + '__cdb_bins AS (', ' SELECT', - ' bins_array,', - ' ARRAY_LENGTH(bins_array, 1) AS bins_number', + ' __cdb_bins_array,', + ' ARRAY_LENGTH(__cdb_bins_array, 1) AS __cdb_bins_number', ' FROM (', ' SELECT', ' ARRAY(', ' SELECT GENERATE_SERIES(', - ' start_date::timestamptz,', - ' end_date::timestamptz,', + ' __cdb_start_date::timestamptz,', + ' __cdb_end_date::timestamptz,', ' {{?it._aggregation==="quarter"}}\'3 month\'{{??}}\'1 {{=it._aggregation}}\'{{?}}::interval', ' )', - ' ) AS bins_array', - ' FROM basics', - ' ) _cdb_bins_array', + ' ) AS __cdb_bins_array', + ' FROM __cdb_basics', + ' ) __cdb_bins_array_query', ')' ].join('\n')); var dateHistogramQueryTpl = dot.template([ 'SELECT', - ' (max_val - min_val) / cast(bins_number as float) AS bin_width,', - ' bins_number,', - ' nulls_count,', - ' CASE WHEN min_val = max_val', + ' (__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}}\',', - ' bins_array', + ' __cdb_bins_array', ' ),', - ' bins_number', + ' __cdb_bins_number', ' )) - 1', ' END AS bin,', ' min(', ' date_part(', ' \'epoch\', ', ' date_trunc(', - ' \'{{=it._aggregation}}\', {{=it._column}}::timestamptz', + ' \'{{=it._aggregation}}\', {{=it._column}}::timestamp AT TIME ZONE \'{{=it._offset}}\'', ' ) AT TIME ZONE \'{{=it._offset}}\'', ' )', ' )::numeric AS timestamp,', - ' date_part(\'epoch\', start_date)::numeric AS timestamp_start,', + ' 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, basics, bins, nulls', + '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, avg_val, start_date', + 'GROUP BY bin, bins_number, bin_width, nulls_count, timestamp_start', 'ORDER BY bin' ].join('\n')); @@ -328,12 +335,16 @@ Histogram.prototype.sql = function(psql, override, callback) { this._buildQuery(psql, override, callback); }; +Histogram.prototype.isDateHistogram = function (override) { + return this._columnType === 'date' && (this.aggregation !== undefined || override.aggregation !== undefined); +}; + Histogram.prototype._buildQuery = function (psql, override, callback) { var filteredQuery, basicsQuery, binsQuery; var _column = this.column; var _query = this.query; - if (this._columnType === 'date' && this.aggregation !== undefined) { + if (this.isDateHistogram(override)) { return this._buildDateHistogramQuery(psql, override, callback); } diff --git a/lib/cartodb/models/filter/bbox.js b/lib/cartodb/models/filter/bbox.js index dcf19e55..0f7072ae 100644 --- a/lib/cartodb/models/filter/bbox.js +++ b/lib/cartodb/models/filter/bbox.js @@ -8,7 +8,7 @@ var filterQueryTpl = dot.template([ ].join('\n')); var bboxFilterTpl = dot.template( - '{{=it._column}} && ST_Transform(ST_MakeEnvelope({{=it._bbox}}, 4326), {{=it._srid}})' + 'ST_Intersects({{=it._column}}, ST_Transform(ST_MakeEnvelope({{=it._bbox}}, 4326), {{=it._srid}}))' ); var LATITUDE_MAX_VALUE = 85.0511287798066; diff --git a/lib/cartodb/models/mapconfig/provider/create-layergroup-provider.js b/lib/cartodb/models/mapconfig/provider/create-layergroup-provider.js index af5a775b..340073b5 100644 --- a/lib/cartodb/models/mapconfig/provider/create-layergroup-provider.js +++ b/lib/cartodb/models/mapconfig/provider/create-layergroup-provider.js @@ -26,7 +26,7 @@ CreateLayergroupMapConfigProvider.prototype.getMapConfig = function(callback) { var context = {}; step( function prepareContextLimits() { - self.userLimitsApi.getRenderLimits(self.user, this); + self.userLimitsApi.getRenderLimits(self.user, self.params.api_key, this); }, function handleRenderLimits(err, renderLimits) { assert.ifError(err); diff --git a/lib/cartodb/models/mapconfig/provider/map-store-provider.js b/lib/cartodb/models/mapconfig/provider/map-store-provider.js index c07f9abc..177322d4 100644 --- a/lib/cartodb/models/mapconfig/provider/map-store-provider.js +++ b/lib/cartodb/models/mapconfig/provider/map-store-provider.js @@ -27,7 +27,7 @@ MapStoreMapConfigProvider.prototype.getMapConfig = function(callback) { var context = {}; step( function prepareContextLimits() { - self.userLimitsApi.getRenderLimits(self.user, this); + self.userLimitsApi.getRenderLimits(self.user, self.params.api_key, this); }, function handleRenderLimits(err, renderLimits) { assert.ifError(err); diff --git a/lib/cartodb/models/mapconfig/provider/named-map-provider.js b/lib/cartodb/models/mapconfig/provider/named-map-provider.js index 594ec247..17c66c29 100644 --- a/lib/cartodb/models/mapconfig/provider/named-map-provider.js +++ b/lib/cartodb/models/mapconfig/provider/named-map-provider.js @@ -114,7 +114,7 @@ NamedMapMapConfigProvider.prototype.getMapConfig = function(callback) { function prepareContextLimits(err, _mapConfig) { assert.ifError(err); mapConfig = _mapConfig; - self.userLimitsApi.getRenderLimits(self.owner, this); + self.userLimitsApi.getRenderLimits(self.owner, self.params.api_key, this); }, function cacheAndReturnMapConfig(err, renderLimits) { self.err = err; diff --git a/lib/cartodb/server.js b/lib/cartodb/server.js index deb88e16..d9302276 100644 --- a/lib/cartodb/server.js +++ b/lib/cartodb/server.js @@ -118,8 +118,27 @@ module.exports = function(serverOptions) { var onTileErrorStrategy; if (global.environment.enabledFeatures.onTileErrorStrategy !== false) { onTileErrorStrategy = function onTileErrorStrategy$TimeoutTile(err, tile, headers, stats, format, callback) { - if (err && err.message === 'Render timed out' && format === 'png') { - return callback(null, timeoutErrorTile, { 'Content-Type': 'image/png' }, {}); + + function isRenderTimeoutError (err) { + return err.message === 'Render timed out'; + } + + function isDatasourceTimeoutError (err) { + return err.message && err.message.match(/canceling statement due to statement timeout/i); + } + + function isTimeoutError (err) { + return isRenderTimeoutError(err) || isDatasourceTimeoutError(err); + } + + function isRasterFormat (format) { + return format === 'png' || format === 'jpg'; + } + + if (isTimeoutError(err) && isRasterFormat(format)) { + return callback(null, timeoutErrorTile, { + 'Content-Type': 'image/png', + }, {}); } else { return callback(err, tile, headers, stats); } diff --git a/package.json b/package.json index df934d7a..9c70ad72 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "windshaft-cartodb", - "version": "3.10.2", + "version": "3.12.11", "description": "A map tile server for CartoDB", "keywords": [ "cartodb" @@ -17,14 +17,16 @@ "Simon Tokumine ", "Javi Santana ", "Sandro Santilli ", - "Carlos Matallín " + "Carlos Matallín ", + "Daniel Garcia Aubert ", + "Mario de Frutos " ], "dependencies": { "body-parser": "~1.14.0", - "camshaft": "0.55.6", - "cartodb-psql": "0.8.0", + "camshaft": "0.58.1", + "cartodb-psql": "0.10.1", "cartodb-query-tables": "0.2.0", - "cartodb-redis": "0.13.2", + "cartodb-redis": "0.14.0", "debug": "~2.2.0", "dot": "~1.0.2", "express": "~4.13.3", @@ -40,7 +42,7 @@ "step-profiler": "~0.3.0", "turbo-carto": "0.19.2", "underscore": "~1.6.0", - "windshaft": "3.2.2", + "windshaft": "3.3.2", "yargs": "~5.0.0" }, "devDependencies": { diff --git a/test/acceptance/dataviews/histogram.js b/test/acceptance/dataviews/histogram.js index a75cf2b1..c9e16489 100644 --- a/test/acceptance/dataviews/histogram.js +++ b/test/acceptance/dataviews/histogram.js @@ -332,6 +332,21 @@ describe('histogram-dataview for date column type', function() { }); }); + it('should cast overridden start and end to float to avoid out of range errors ' + test.desc, function (done) { + var params = { + start: -2145916800, + end: 1009843199 + }; + + this.testClient = new TestClient(mapConfig, 1234); + this.testClient.getDataview(test.dataviewId, params, function (err, dataview) { + assert.ok(!err, err); + assert.equal(dataview.type, 'histogram'); + assert.ok(dataview.bin_width > 0, 'Unexpected bin width: ' + dataview.bin_width); + + done(); + }); + }); it('should return same histogram ' + test.desc, function (done) { var params = { @@ -888,7 +903,6 @@ describe('histogram-dates: aggregation input value', function() { }); }); - describe('histogram-dates: timestamp starts at epoch', function() { afterEach(function(done) { @@ -958,3 +972,221 @@ describe('histogram-dates: timestamp starts at epoch', function() { }); }); }); + +describe('histogram-dates: trunc timestamp for each bin respecting user\'s timezone', function() { + + afterEach(function(done) { + if (this.testClient) { + this.testClient.drain(done); + } else { + done(); + } + }); + + var mapConfig = createMapConfig( + [ + { + type: "cartodb", + options: { + source: { + id: "a0" + }, + cartocss: "#points { marker-width: 10; marker-fill: red; }", + cartocss_version: "2.3.0" + } + } + ], + { + timezone_epoch_histogram: { + source: { + id: 'a0' + }, + type: 'histogram', + options: { + column: 'd', + aggregation: 'auto' + } + }, + timezone_epoch_histogram_tz: { + source: { + id: 'a1' + }, + type: 'histogram', + options: { + column: 'd', + aggregation: 'auto' + } + } + }, + [ + { + id: 'a0', + type: 'source', + params: { + query: [ + 'select null::geometry the_geom_webmercator, date AS d', + 'from generate_series(', + '\'1970-01-01 00:00:00\'::timestamp,', + '\'1970-01-01 01:59:00\'::timestamp,', + ' \'1 minute\'::interval', + ') date' + ].join(' ') + } + }, + { + id: 'a1', + type: 'source', + params: { + query: [ + 'select null::geometry the_geom_webmercator, date AS d', + 'from generate_series(', + '\'1970-01-01 00:00:00\'::timestamptz,', + '\'1970-01-01 01:59:00\'::timestamptz,', + ' \'1 minute\'::interval', + ') date' + ].join(' ') + } + } + ] + ); + + var dateHistogramsUseCases = [{ + desc: 'supporting timestamp with offset', + dataviewId: 'timezone_epoch_histogram_tz' + }, { + desc: 'supporting timestamp without offset', + dataviewId: 'timezone_epoch_histogram' + }]; + + dateHistogramsUseCases.forEach(function (test) { + it('should return histogram with two buckets ' + test.desc , function(done) { + this.testClient = new TestClient(mapConfig, 1234); + + const override = { + aggregation: 'day', + offset: '-3600' + }; + + this.testClient.getDataview(test.dataviewId, override, function(err, dataview) { + assert.ifError(err); + + var OFFSET_IN_MINUTES = -1 * 60; // GMT-01 + var initialTimestamp = '1969-12-31T00:00:00-01:00'; + var binsStartInMilliseconds = dataview.bins_start * 1000; + var binsStartFormatted = moment.utc(binsStartInMilliseconds) + .utcOffset(OFFSET_IN_MINUTES) + .format(); + assert.equal(binsStartFormatted, initialTimestamp); + + dataview.bins.forEach(function (bin, index) { + var binTimestampExpected = moment.utc(initialTimestamp) + .utcOffset(OFFSET_IN_MINUTES) + .add(index, override.aggregation) + .format(); + var binsTimestampInMilliseconds = bin.timestamp * 1000; + var binTimestampFormatted = moment.utc(binsTimestampInMilliseconds) + .utcOffset(OFFSET_IN_MINUTES) + .format(); + + assert.equal(binTimestampFormatted, binTimestampExpected); + assert.ok(bin.timestamp <= bin.min, 'bin timestamp < bin min: ' + JSON.stringify(bin)); + assert.ok(bin.min <= bin.max, 'bin min < bin max: ' + JSON.stringify(bin)); + }); + + done(); + }); + }); + }); +}); + + +describe('histogram: be able to override with aggregation for histograms instantiated w/o aggregation', function() { + + afterEach(function(done) { + if (this.testClient) { + this.testClient.drain(done); + } else { + done(); + } + }); + + var mapConfig = createMapConfig( + [ + { + type: "cartodb", + options: { + source: { + id: "a0" + }, + cartocss: "#points { marker-width: 10; marker-fill: red; }", + cartocss_version: "2.3.0" + } + } + ], + { + timezone_epoch_histogram: { + source: { + id: 'a0' + }, + type: 'histogram', + options: { + column: 'd', + } + } + }, + [ + { + id: 'a0', + type: 'source', + params: { + query: [ + 'select null::geometry the_geom_webmercator, date AS d', + 'from generate_series(', + '\'1970-01-01 00:00:00\'::timestamp,', + '\'1970-01-01 01:59:00\'::timestamp,', + ' \'1 minute\'::interval', + ') date' + ].join(' ') + } + } + ] + ); + + it('should apply aggregation to the histogram', function(done) { + this.testClient = new TestClient(mapConfig, 1234); + + const override = { + aggregation: 'day', + offset: '-3600' + }; + + this.testClient.getDataview('timezone_epoch_histogram', override, function(err, dataview) { + assert.ifError(err); + + var OFFSET_IN_MINUTES = -1 * 60; // GMT-01 + var initialTimestamp = '1969-12-31T00:00:00-01:00'; + var binsStartInMilliseconds = dataview.bins_start * 1000; + var binsStartFormatted = moment.utc(binsStartInMilliseconds) + .utcOffset(OFFSET_IN_MINUTES) + .format(); + assert.equal(binsStartFormatted, initialTimestamp); + + dataview.bins.forEach(function (bin, index) { + var binTimestampExpected = moment.utc(initialTimestamp) + .utcOffset(OFFSET_IN_MINUTES) + .add(index, override.aggregation) + .format(); + var binsTimestampInMilliseconds = bin.timestamp * 1000; + var binTimestampFormatted = moment.utc(binsTimestampInMilliseconds) + .utcOffset(OFFSET_IN_MINUTES) + .format(); + + assert.equal(binTimestampFormatted, binTimestampExpected); + assert.ok(bin.timestamp <= bin.min, 'bin timestamp < bin min: ' + JSON.stringify(bin)); + assert.ok(bin.min <= bin.max, 'bin min < bin max: ' + JSON.stringify(bin)); + }); + + done(); + }); + }); +}); diff --git a/test/acceptance/limits.js b/test/acceptance/limits.js deleted file mode 100644 index ffe633ec..00000000 --- a/test/acceptance/limits.js +++ /dev/null @@ -1,311 +0,0 @@ -var testHelper = require('../support/test_helper'); - -var assert = require('../support/assert'); -var _ = require('underscore'); -var redis = require('redis'); - -var CartodbWindshaft = require('../../lib/cartodb/server'); -var serverOptions = require('../../lib/cartodb/server_options'); - -var LayergroupToken = require('../../lib/cartodb/models/layergroup-token'); - -describe('render limits', function() { - - var layergroupUrl = '/api/v1/map'; - - var redisClient = redis.createClient(global.environment.redis.port); - - var server; - var keysToDelete; - beforeEach(function() { - keysToDelete = {}; - server = new CartodbWindshaft(serverOptions); - server.setMaxListeners(0); - }); - - afterEach(function(done) { - testHelper.deleteRedisKeys(keysToDelete, done); - }); - - var user = 'localhost'; - - var pointSleepSql = "SELECT pg_sleep(0.5)," + - " 'SRID=3857;POINT(0 0)'::geometry the_geom_webmercator, 1 cartodb_id"; - var pointCartoCss = '#layer { marker-fill:red; }'; - var polygonSleepSql = "SELECT pg_sleep(0.5)," + - " ST_Buffer('SRID=3857;POINT(0 0)'::geometry, 100000000) the_geom_webmercator, 1 cartodb_id"; - var polygonCartoCss = '#layer { polygon-fill:red; }'; - - function singleLayergroupConfig(sql, cartocss) { - return { - version: '1.0.0', - layers: [ - { - type: 'mapnik', - options: { - sql: sql, - cartocss: cartocss, - cartocss_version: '2.0.1' - } - } - ] - }; - } - - function createRequest(layergroup, userHost) { - return { - url: layergroupUrl, - method: 'POST', - headers: { - host: userHost, - 'Content-Type': 'application/json' - }, - data: JSON.stringify(layergroup) - }; - } - - function withRenderLimit(user, renderLimit, callback) { - redisClient.SELECT(5, function(err) { - if (err) { - return callback(err); - } - var userLimitsKey = 'limits:tiler:' + user; - redisClient.HSET(userLimitsKey, 'render', renderLimit, function(err) { - if (err) { - return callback(err); - } - keysToDelete[userLimitsKey] = 5; - return callback(); - }); - }); - - } - - describe('with onTileErrorStrategy DISABLED', function() { - var onTileErrorStrategyEnabled; - before(function() { - onTileErrorStrategyEnabled = global.environment.enabledFeatures.onTileErrorStrategy; - global.environment.enabledFeatures.onTileErrorStrategy = false; - }); - - after(function() { - global.environment.enabledFeatures.onTileErrorStrategy = onTileErrorStrategyEnabled; - }); - - it("layergroup creation fails if test tile is slow", function(done) { - withRenderLimit(user, 50, function(err) { - if (err) { - return done(err); - } - - var layergroup = singleLayergroupConfig(polygonSleepSql, polygonCartoCss); - assert.response(server, - createRequest(layergroup, user), - { - status: 400 - }, - function(res) { - var parsed = JSON.parse(res.body); - assert.deepEqual(parsed.errors, [ 'Render timed out' ]); - done(); - } - ); - }); - }); - - it("layergroup creation does not fail if user limit is high enough even if test tile is slow", function(done) { - withRenderLimit(user, 5000, function(err) { - if (err) { - return done(err); - } - - var layergroup = singleLayergroupConfig(polygonSleepSql, polygonCartoCss); - assert.response(server, - createRequest(layergroup, user), - { - status: 200 - }, - function(res) { - var parsed = JSON.parse(res.body); - assert.ok(parsed.layergroupid); - keysToDelete['map_cfg|' + LayergroupToken.parse(parsed.layergroupid).token] = 0; - keysToDelete['user:localhost:mapviews:global'] = 5; - done(); - } - ); - }); - }); - - - it("layergroup creation works if test tile is fast but tile request fails if they are slow", function(done) { - withRenderLimit(user, 50, function(err) { - if (err) { - return done(err); - } - - var layergroup = singleLayergroupConfig(pointSleepSql, pointCartoCss); - assert.response(server, - createRequest(layergroup, user), - { - status: 200 - }, - function(res) { - keysToDelete['map_cfg|' + LayergroupToken.parse(JSON.parse(res.body).layergroupid).token] = 0; - keysToDelete['user:localhost:mapviews:global'] = 5; - assert.response(server, - { - url: layergroupUrl + _.template('/<%= layergroupId %>/<%= z %>/<%= x %>/<%= y %>.png', { - layergroupId: JSON.parse(res.body).layergroupid, - z: 0, - x: 0, - y: 0 - }), - method: 'GET', - headers: { - host: 'localhost' - }, - encoding: 'binary' - }, - { - status: 400 - }, - function(res) { - var parsed = JSON.parse(res.body); - assert.deepEqual(parsed.errors, ['Render timed out']); - done(); - } - ); - - } - ); - }); - }); - - it("tile request does not fail if user limit is high enough", function(done) { - withRenderLimit(user, 5000, function(err) { - if (err) { - return done(err); - } - - var layergroup = singleLayergroupConfig(pointSleepSql, pointCartoCss); - assert.response(server, - createRequest(layergroup, user), - { - status: 200 - }, - function(res) { - keysToDelete['map_cfg|' + LayergroupToken.parse(JSON.parse(res.body).layergroupid).token] = 0; - keysToDelete['user:localhost:mapviews:global'] = 5; - assert.response(server, - { - url: layergroupUrl + _.template('/<%= layergroupId %>/<%= z %>/<%= x %>/<%= y %>.png', { - layergroupId: JSON.parse(res.body).layergroupid, - z: 0, - x: 0, - y: 0 - }), - method: 'GET', - headers: { - host: 'localhost' - }, - encoding: 'binary' - }, - { - status: 200, - headers: { - 'Content-Type': 'image/png' - } - }, - function(res, err) { - done(err); - } - ); - - } - ); - }); - }); - - }); - - describe('with onTileErrorStrategy', function() { - - it("layergroup creation works even if test tile is slow", function(done) { - withRenderLimit(user, 50, function(err) { - if (err) { - return done(err); - } - - var layergroup = singleLayergroupConfig(polygonSleepSql, polygonCartoCss); - assert.response(server, - createRequest(layergroup, user), - { - status: 200 - }, - function(res) { - var parsed = JSON.parse(res.body); - assert.ok(parsed.layergroupid); - keysToDelete['map_cfg|' + LayergroupToken.parse(parsed.layergroupid).token] = 0; - keysToDelete['user:localhost:mapviews:global'] = 5; - done(); - } - ); - }); - }); - - it("layergroup creation and tile requests works even if they are slow but returns fallback", function(done) { - withRenderLimit(user, 50, function(err) { - if (err) { - return done(err); - } - - var layergroup = singleLayergroupConfig(pointSleepSql, pointCartoCss); - assert.response(server, - createRequest(layergroup, user), - { - status: 200 - }, - function(res) { - keysToDelete['map_cfg|' + LayergroupToken.parse(JSON.parse(res.body).layergroupid).token] = 0; - keysToDelete['user:localhost:mapviews:global'] = 5; - assert.response(server, - { - url: layergroupUrl + _.template('/<%= layergroupId %>/<%= z %>/<%= x %>/<%= y %>.png', { - layergroupId: JSON.parse(res.body).layergroupid, - z: 0, - x: 0, - y: 0 - }), - method: 'GET', - headers: { - host: 'localhost' - }, - encoding: 'binary' - }, - { - status: 200, - headers: { - 'Content-Type': 'image/png' - } - }, - function(res, err) { - if (err) { - done(err); - } - var referenceImagePath = './test/fixtures/render-timeout-fallback.png'; - assert.imageBufferIsSimilarToFile(res.body, referenceImagePath, 25, - function(imgErr/*, similarity*/) { - done(imgErr); - } - ); - } - ); - - } - ); - }); - }); - - }); - -}); diff --git a/test/acceptance/mvt.js b/test/acceptance/mvt.js index b1ea4455..b5a59cd8 100644 --- a/test/acceptance/mvt.js +++ b/test/acceptance/mvt.js @@ -24,14 +24,24 @@ describe('mvt', function () { desc: 'should get empty mvt with code 204 (no content)', coords: { z: 0, x: 0, y: 0 }, format: 'mvt', - status: 204, + response: { + status: 204, + headers: { + 'Content-Type': undefined + } + }, mapConfig: createMapConfig(TestClient.SQL.EMPTY) }, { desc: 'should get mvt tile with code 200 (ok)', coords: { z: 0, x: 0, y: 0 }, format: 'mvt', - status: 200, + response: { + status: 200, + headers: { + 'Content-Type': 'application/x-protobuf' + } + }, mapConfig: createMapConfig() } ]; @@ -40,12 +50,12 @@ describe('mvt', function () { it(test.desc, done => { const testClient = new TestClient(test.mapConfig, 1234); const { z, x, y } = test.coords; - const { format, status } = test; + const { format, response } = test; - testClient.getTile(z, x, y, { format, status }, (err, res) => { + testClient.getTile(z, x, y, { format, response }, (err, res) => { assert.ifError(err); - assert.equal(res.statusCode, test.status); + assert.equal(res.statusCode, test.response.status); testClient.drain(done); }); }); diff --git a/test/acceptance/user-database-timeout-limit.js b/test/acceptance/user-database-timeout-limit.js new file mode 100644 index 00000000..42c468fd --- /dev/null +++ b/test/acceptance/user-database-timeout-limit.js @@ -0,0 +1,786 @@ +require('../support/test_helper'); + +const assert = require('../support/assert'); +const TestClient = require('../support/test-client'); + +const timeoutErrorTilePath = `${process.cwd()}/assets/render-timeout-fallback.png`; + +const pointSleepSql = ` + SELECT + pg_sleep(0.3), + 'SRID=3857;POINT(0 0)'::geometry the_geom_webmercator, + 1 cartodb_id, + 2 val +`; + +const validationPointSleepSql = ` + SELECT + pg_sleep(0.3), + ST_Transform('SRID=4326;POINT(-180 85.05112877)'::geometry, 3857) the_geom_webmercator, + 1 cartodb_id, + 2 val +`; + +const createMapConfig = ({ + version = '1.6.0', + type = 'cartodb', + sql = pointSleepSql, + cartocss = TestClient.CARTOCSS.POINTS, + cartocss_version = '2.3.0', + interactivity = 'cartodb_id', + countBy = 'cartodb_id', + attributes +} = {}) => ({ + version, + layers: [{ + type, + options: { + source: { + id: 'a0' + }, + cartocss, + cartocss_version, + attributes, + interactivity + } + }], + analyses: [ + { + id: 'a0', + type: 'source', + params: { + query: sql + } + } + ], + dataviews: { + count: { + source: { + id: 'a0' + }, + type: 'formula', + options: { + column: countBy, + operation: 'count' + } + } + } +}); + +const DATASOURCE_TIMEOUT_ERROR = { + errors: ['You are over platform\'s limits. Please contact us to know more details'], + errors_with_context: [{ + type: 'limit', + subtype: 'datasource', + message: 'You are over platform\'s limits. Please contact us to know more details' + }] +}; + +describe('user database timeout limit', function () { + describe('dataview', function () { + beforeEach(function (done) { + const mapconfig = createMapConfig(); + this.testClient = new TestClient(mapconfig, 1234); + this.testClient.setUserDatabaseTimeoutLimit(200, done); + }); + + afterEach(function (done) { + this.testClient.setUserDatabaseTimeoutLimit(0, (err) => { + if (err) { + return done(err); + } + this.testClient.drain(done); + }); + }); + + it('layergroup creation works but dataview request fails due to statement timeout', function (done) { + const params = { + response: { + status: 429, + headers: { + 'Content-Type': 'application/json; charset=utf-8' + } + } + }; + + this.testClient.getDataview('count', params, (err, timeoutError) => { + assert.ifError(err); + + assert.deepEqual(timeoutError, DATASOURCE_TIMEOUT_ERROR); + + done(); + }); + }); + }); + + describe('raster', function () { + describe('while validating in layergroup creation', function () { + beforeEach(function (done) { + const mapconfig = createMapConfig({ sql: validationPointSleepSql }); + this.testClient = new TestClient(mapconfig, 1234); + this.testClient.setUserDatabaseTimeoutLimit(200, done); + }); + + afterEach(function (done) { + this.testClient.setUserDatabaseTimeoutLimit(0, (err) => { + if (err) { + return done(err); + } + this.testClient.drain(done); + }); + }); + + it('fails due to statement timeout', function (done) { + const expectedResponse = { + status: 429, + headers: { + 'Content-Type': 'application/json; charset=utf-8' + } + }; + + this.testClient.getLayergroup(expectedResponse, (err, timeoutError) => { + assert.deepEqual(timeoutError, { + errors: [ 'You are over platform\'s limits. Please contact us to know more details' ], + errors_with_context: [{ + type: 'limit', + subtype: 'datasource', + message: 'You are over platform\'s limits. Please contact us to know more details', + layer: { id: 'layer0', index: 0, type: 'mapnik' } + }] + }); + + done(); + }); + }); + }); + + describe('fetching raster tiles', function () { + describe('with user\'s timeout of 200 ms', function () { + describe('with onTileErrorStrategy ENABLED', function () { + let onTileErrorStrategy; + + beforeEach(function (done) { + onTileErrorStrategy = global.environment.enabledFeatures.onTileErrorStrategy; + global.environment.enabledFeatures.onTileErrorStrategy = true; + + const mapconfig = createMapConfig(); + this.testClient = new TestClient(mapconfig, 1234); + const expectedResponse = { + status: 200, + headers: { + 'Content-Type': 'application/json; charset=utf-8' + } + }; + + this.testClient.setUserDatabaseTimeoutLimit(200, (err) => { + if (err) { + return done(err); + } + + this.testClient.getLayergroup(expectedResponse, (err, res) => { + if (err) { + return done(err); + } + + this.layergroupid = res.layergroupid; + + done(); + }); + }); + }); + + afterEach(function (done) { + global.environment.enabledFeatures.onTileErrorStrategy = onTileErrorStrategy; + this.testClient.setUserDatabaseTimeoutLimit(0, (err) => { + if (err) { + return done(err); + } + this.testClient.drain(done); + }); + }); + + it('"png" fails due to statement timeout', function (done) { + const params = { + layergroupid: this.layergroupid, + format: 'png', + layers: [ 0 ] + }; + + this.testClient.getTile(0, 0, 0, params, (err, res, tile) => { + assert.ifError(err); + + assert.imageIsSimilarToFile(tile, timeoutErrorTilePath, 0.05, (err) => { + assert.ifError(err); + done(); + }); + }); + }); + + it('"static png" fails due to statement timeout', function (done) { + const params = { + layergroupid: this.layergroupid, + zoom: 0, + lat: 0, + lng: 0, + width: 256, + height: 256, + format: 'png' + }; + + this.testClient.getStaticCenter(params, function (err, res, tile) { + assert.ifError(err); + + assert.imageIsSimilarToFile(tile, timeoutErrorTilePath, 0.05, (err) => { + assert.ifError(err); + done(); + }); + }); + }); + }); + + describe('with onTileErrorStrategy DISABLED', function () { + let onTileErrorStrategy; + + beforeEach(function (done) { + onTileErrorStrategy = global.environment.enabledFeatures.onTileErrorStrategy; + global.environment.enabledFeatures.onTileErrorStrategy = false; + + const mapconfig = createMapConfig(); + this.testClient = new TestClient(mapconfig, 1234); + const expectedResponse = { + status: 200, + headers: { + 'Content-Type': 'application/json; charset=utf-8' + } + }; + + this.testClient.setUserDatabaseTimeoutLimit(200, (err) => { + if (err) { + return done(err); + } + + this.testClient.getLayergroup(expectedResponse, (err, res) => { + if (err) { + return done(err); + } + + this.layergroupid = res.layergroupid; + + done(); + }); + }); + }); + + afterEach(function (done) { + global.environment.enabledFeatures.onTileErrorStrategy = onTileErrorStrategy; + + this.testClient.setUserDatabaseTimeoutLimit(0, (err) => { + if (err) { + return done(err); + } + + this.testClient.drain(done); + }); + }); + + it('"png" fails due to statement timeout', function (done) { + const params = { + layergroupid: this.layergroupid, + format: 'png', + layers: [ 0 ], + response: { + status: 429, + headers: { + 'Content-Type': 'application/json; charset=utf-8' + } + } + }; + + this.testClient.getTile(0, 0, 0, params, (err, res, timeoutError) => { + assert.ifError(err); + + assert.deepEqual(timeoutError, DATASOURCE_TIMEOUT_ERROR); + + done(); + }); + }); + + it('"static png" fails due to statement timeout', function (done) { + const params = { + layergroupid: this.layergroupid, + zoom: 0, + lat: 0, + lng: 0, + width: 256, + height: 256, + format: 'png', + response: { + status: 429, + headers: { + 'Content-Type': 'application/json; charset=utf-8' + } + } + }; + + this.testClient.getStaticCenter(params, (err, res, timeoutError) => { + assert.ifError(err); + + assert.deepEqual(timeoutError, DATASOURCE_TIMEOUT_ERROR); + + done(); + }); + }); + }); + }); + }); + }); + + describe('vector', function () { + describe('while validating in layergroup creation', function () { + beforeEach(function (done) { + const mapconfig = createMapConfig({ sql: validationPointSleepSql }); + this.testClient = new TestClient(mapconfig, 1234); + this.testClient.setUserDatabaseTimeoutLimit(200, done); + }); + + afterEach(function (done) { + this.testClient.setUserDatabaseTimeoutLimit(0, (err) => { + if (err) { + return done(err); + } + this.testClient.drain(done); + }); + }); + + it('fails due to statement timeout', function (done) { + const expectedResponse = { + status: 429, + headers: { + 'Content-Type': 'application/json; charset=utf-8' + } + }; + + this.testClient.getLayergroup(expectedResponse, (err, timeoutError) => { + assert.deepEqual(timeoutError, { + errors: [ 'You are over platform\'s limits. Please contact us to know more details' ], + errors_with_context: [{ + type: 'limit', + subtype: 'datasource', + message: 'You are over platform\'s limits. Please contact us to know more details', + layer: { id: 'layer0', index: 0, type: 'mapnik' } + }] + }); + + done(); + }); + }); + }); + + describe('fetching vector tiles', function () { + beforeEach(function (done) { + const mapconfig = createMapConfig(); + this.testClient = new TestClient(mapconfig, 1234); + const expectedResponse = { + status: 200, + headers: { + 'Content-Type': 'application/json; charset=utf-8' + } + }; + + this.testClient.getLayergroup(expectedResponse, (err, res) => { + if (err) { + return done(err); + } + + this.layergroupid = res.layergroupid; + + done(); + }); + }); + + afterEach(function (done) { + this.testClient.drain(done); + }); + + describe('with user\'s timeout of 200 ms', function () { + beforeEach(function (done) { + this.testClient.setUserDatabaseTimeoutLimit(200, done); + }); + + afterEach(function (done) { + this.testClient.setUserDatabaseTimeoutLimit(0, done); + }); + + it('"mvt" fails due to statement timeout', function (done) { + const params = { + layergroupid: this.layergroupid, + format: 'mvt', + layers: [ 0 ], + response: { + status: 429, + headers: { + 'Content-Type': 'application/json; charset=utf-8' + } + } + }; + + this.testClient.getTile(0, 0, 0, params, (err, res, timeoutError) => { + assert.ifError(err); + + assert.deepEqual(timeoutError, DATASOURCE_TIMEOUT_ERROR); + + done(); + }); + }); + }); + }); + }); + + + describe('interactivity', function () { + describe('while validating in layergroup creation', function () { + beforeEach(function (done) { + const mapconfig = createMapConfig({ sql: validationPointSleepSql, interactivity: 'val' }); + this.testClient = new TestClient(mapconfig, 1234); + this.testClient.setUserDatabaseTimeoutLimit(200, done); + }); + + afterEach(function (done) { + this.testClient.setUserDatabaseTimeoutLimit(0, (err) => { + if (err) { + return done(err); + } + this.testClient.drain(done); + }); + }); + + it('fails due to statement timeout', function (done) { + const expectedResponse = { + status: 429, + headers: { + 'Content-Type': 'application/json; charset=utf-8' + } + }; + + this.testClient.getLayergroup(expectedResponse, (err, timeoutError) => { + assert.deepEqual(timeoutError, { + errors: [ 'You are over platform\'s limits. Please contact us to know more details' ], + errors_with_context: [{ + type: 'limit', + subtype: 'datasource', + message: 'You are over platform\'s limits. Please contact us to know more details', + layer: { id: 'layer0', index: 0, type: 'mapnik' } + }] + }); + + done(); + }); + }); + }); + + describe('fetching interactivity tiles', function () { + beforeEach(function (done) { + const mapconfig = createMapConfig({ interactivity: 'val' }); + this.testClient = new TestClient(mapconfig, 1234); + const expectedResponse = { + status: 200, + headers: { + 'Content-Type': 'application/json; charset=utf-8' + } + }; + + this.testClient.getLayergroup(expectedResponse, (err, res) => { + if (err) { + return done(err); + } + + this.layergroupid = res.layergroupid; + + done(); + }); + }); + + afterEach(function (done) { + this.testClient.drain(done); + }); + + describe('with user\'s timeout of 200 ms', function () { + beforeEach(function (done) { + this.testClient.setUserDatabaseTimeoutLimit(200, done); + }); + + afterEach(function (done) { + this.testClient.setUserDatabaseTimeoutLimit(0, done); + }); + + it('"grid.json" fails due to statement timeout', function (done) { + const params = { + layergroupid: this.layergroupid, + format: 'grid.json', + layers: 'mapnik', + response: { + status: 429, + headers: { + 'Content-Type': 'application/json; charset=utf-8' + } + } + }; + + this.testClient.getTile(0, 0, 0, params, (err, res, timeoutError) => { + assert.ifError(err); + + assert.deepEqual(timeoutError, DATASOURCE_TIMEOUT_ERROR); + + done(); + }); + }); + }); + }); + }); + + describe('torque', function () { + describe('while validating in layergroup creation', function () { + beforeEach(function (done) { + const mapconfig = createMapConfig({ + type: 'torque', + cartocss: TestClient.CARTOCSS.TORQUE + }); + this.testClient = new TestClient(mapconfig, 1234); + this.testClient.setUserDatabaseTimeoutLimit(200, done); + }); + + afterEach(function (done) { + this.testClient.setUserDatabaseTimeoutLimit(0, (err) => { + if (err) { + return done(err); + } + this.testClient.drain(done); + }); + }); + + it('fails due to statement timeout', function (done) { + const expectedResponse = { + status: 429, + headers: { + 'Content-Type': 'application/json; charset=utf-8' + } + }; + + this.testClient.getLayergroup(expectedResponse, (err, timeoutError) => { + assert.deepEqual(timeoutError, { + errors: [ 'You are over platform\'s limits. Please contact us to know more details' ], + errors_with_context: [{ + type: 'limit', + subtype: 'datasource', + message: 'You are over platform\'s limits. Please contact us to know more details', + layer: { id: 'torque-layer0', index: 0, type: 'torque' } + }] + }); + + done(); + }); + }); + }); + + describe('fetching torque tiles', function () { + beforeEach(function (done) { + const mapconfig = createMapConfig({ + type: 'torque', + cartocss: TestClient.CARTOCSS.TORQUE + }); + this.testClient = new TestClient(mapconfig, 1234); + const expectedResponse = { + status: 200, + headers: { + 'Content-Type': 'application/json; charset=utf-8' + } + }; + + this.testClient.getLayergroup(expectedResponse, (err, res) => { + if (err) { + return done(err); + } + + this.layergroupid = res.layergroupid; + + done(); + }); + }); + + afterEach(function (done) { + this.testClient.drain(done); + }); + + describe('with user\'s timeout of 200 ms', function () { + beforeEach(function (done) { + this.testClient.setUserDatabaseTimeoutLimit(200, done); + }); + + afterEach(function (done) { + this.testClient.setUserDatabaseTimeoutLimit(0, done); + }); + + it('"torque.json" fails due to statement timeout', function (done) { + const params = { + layergroupid: this.layergroupid, + format: 'torque.json', + layers: [ 0 ], + response: { + status: 429, + headers: { + 'Content-Type': 'application/json; charset=utf-8' + } + } + }; + + this.testClient.getTile(0, 0, 0, params, (err, res, timeoutError) => { + assert.ifError(err); + + assert.deepEqual(timeoutError, DATASOURCE_TIMEOUT_ERROR); + + done(); + }); + }); + + it('".png" fails due to statement timeout', function (done) { + const params = { + layergroupid: this.layergroupid, + format: 'torque.png', + layers: [ 0 ], + response: { + status: 429, + headers: { + 'Content-Type': 'application/json; charset=utf-8' + } + } + }; + + this.testClient.getTile(0, 0, 0, params, (err, res, attributes) => { + assert.ifError(err); + + assert.deepEqual(attributes, DATASOURCE_TIMEOUT_ERROR); + + done(); + }); + }); + }); + }); + }); + + describe('attributes:', function () { + describe('while validating in map instatiation', function () { + beforeEach(function (done) { + const mapconfig = createMapConfig({ + attributes: { + id: 'cartodb_id', + columns: [ 'val' ] + } + }); + this.testClient = new TestClient(mapconfig, 1234); + this.testClient.setUserDatabaseTimeoutLimit(200, done); + }); + + afterEach(function (done) { + this.testClient.setUserDatabaseTimeoutLimit(0, (err) => { + if (err) { + return done(err); + } + this.testClient.drain(done); + }); + }); + + it('layergroup creation fails due to statement timeout', function (done) { + const expectedResponse = { + status: 429, + headers: { + 'Content-Type': 'application/json; charset=utf-8' + } + }; + + this.testClient.getLayergroup(expectedResponse, (err, timeoutError) => { + assert.deepEqual(timeoutError, { + errors: [ 'You are over platform\'s limits. Please contact us to know more details' ], + errors_with_context: [{ + type: 'limit', + subtype: 'datasource', + message: 'You are over platform\'s limits. Please contact us to know more details', + layer: { + id: 'layer0', + index: 0, + type: 'mapnik' + } + }] + }); + + done(); + }); + }); + }); + + describe('fetching by feature id', function () { + beforeEach(function (done) { + const mapconfig = createMapConfig({ + attributes: { + id: 'cartodb_id', + columns: [ 'val' ] + } + }); + + this.testClient = new TestClient(mapconfig, 1234); + + const expectedResponse = { + status: 200, + headers: { + 'Content-Type': 'application/json; charset=utf-8' + } + }; + + this.testClient.getLayergroup(expectedResponse, (err, res) => { + if (err) { + return done(err); + } + + this.layergroupid = res.layergroupid; + + done(); + }); + }); + + afterEach(function (done) { + this.testClient.drain(done); + }); + + describe('with user\'s timeout of 200 ms', function () { + beforeEach(function (done) { + this.testClient.setUserDatabaseTimeoutLimit(200, done); + }); + + afterEach(function (done) { + this.testClient.setUserDatabaseTimeoutLimit(0, done); + }); + + it('fails due to statement timeout', function (done) { + const params = { + layergroupid: this.layergroupid, + featureId: 1, + layer: 0, + response: { + status: 429, + headers: { + 'Content-Type': 'application/json; charset=utf-8' + } + } + }; + + this.testClient.getAttributes(params, (err, res, timeoutError) => { + assert.ifError(err); + + assert.deepEqual(timeoutError, DATASOURCE_TIMEOUT_ERROR); + + done(); + }); + }); + }); + }); + }); +}); diff --git a/test/acceptance/user-render-timeout-limit.js b/test/acceptance/user-render-timeout-limit.js new file mode 100644 index 00000000..45e41f89 --- /dev/null +++ b/test/acceptance/user-render-timeout-limit.js @@ -0,0 +1,394 @@ +require('../support/test_helper'); + +const assert = require('../support/assert'); +const TestClient = require('../support/test-client'); + +const timeoutErrorTilePath = `${process.cwd()}/assets/render-timeout-fallback.png`; + +const pointSleepSql = ` + SELECT + pg_sleep(0.5), + 'SRID=3857;POINT(0 0)'::geometry the_geom_webmercator, + 1 cartodb_id, + 2 val +`; + +// during instatiation we validate tile 30/0/0, creating a point in that tile `pg_sleep` will throw a timeout +const validationPointSleepSql = ` + SELECT + pg_sleep(0.5), + ST_Transform('SRID=4326;POINT(-180 85.05112877)'::geometry, 3857) the_geom_webmercator, + 1 cartodb_id, + 2 val +`; + +const createMapConfig = ({ + version = '1.6.0', + type = 'cartodb', + sql = pointSleepSql, + cartocss = TestClient.CARTOCSS.POINTS, + cartocss_version = '2.3.0', + interactivity = 'cartodb_id', + countBy = 'cartodb_id' +} = {}) => ({ + version, + layers: [{ + type, + options: { + source: { + id: 'a0' + }, + cartocss, + cartocss_version, + interactivity + } + }], + analyses: [ + { + id: 'a0', + type: 'source', + params: { + query: sql + } + } + ], + dataviews: { + count: { + source: { + id: 'a0' + }, + type: 'formula', + options: { + column: countBy, + operation: 'count' + } + } + } +}); + +describe('user render timeout limit', function () { + describe('map instantiation => validation', function () { + beforeEach(function (done) { + const mapconfig = createMapConfig({ sql: validationPointSleepSql }); + this.testClient = new TestClient(mapconfig, 1234); + this.testClient.setUserRenderTimeoutLimit('localhost', 50, done); + }); + + afterEach(function (done) { + this.testClient.setUserRenderTimeoutLimit('localhost', 0, (err) => { + if (err) { + return done(err); + } + this.testClient.drain(done); + }); + }); + + it('layergroup creation fails due to statement timeout', function (done) { + const expectedResponse = { + status: 429, + headers: { + 'Content-Type': 'application/json; charset=utf-8' + } + }; + + this.testClient.getLayergroup(expectedResponse, (err, timeoutError) => { + assert.ifError(err); + + assert.deepEqual(timeoutError, { + errors: ["You are over platform\'s limits. Please contact us to know more details"], + errors_with_context: [{ + type: 'limit', + subtype: 'render', + message: "You are over platform\'s limits. Please contact us to know more details", + layer: { + id: "layer0", + index: 0, + type: "mapnik" + } + }] + }); + + done(); + }); + }); + }); + + describe('raster', function () { + describe('with onTileErrorStrategy ENABLED', function () { + let onTileErrorStrategy; + + beforeEach(function (done) { + onTileErrorStrategy = global.environment.enabledFeatures.onTileErrorStrategy; + global.environment.enabledFeatures.onTileErrorStrategy = true; + + const mapconfig = createMapConfig(); + this.testClient = new TestClient(mapconfig, 1234); + this.testClient.setUserRenderTimeoutLimit('localhost', 50, done); + }); + + afterEach(function (done) { + global.environment.enabledFeatures.onTileErrorStrategy = onTileErrorStrategy; + + this.testClient.setUserRenderTimeoutLimit('localhost', 0, (err) => { + if (err) { + return done(err); + } + this.testClient.drain(done); + }); + }); + + it('layergroup creation works but tile request fails due to render timeout', function (done) { + this.testClient.getTile(0, 0, 0, {}, (err, res, tile) => { + assert.ifError(err); + + assert.imageIsSimilarToFile(tile, timeoutErrorTilePath, 0.05, (err) => { + assert.ifError(err); + done(); + }); + }); + }); + }); + + describe('with onTileErrorStrategy DISABLED', function() { + var onTileErrorStrategy; + + beforeEach(function (done) { + onTileErrorStrategy = global.environment.enabledFeatures.onTileErrorStrategy; + global.environment.enabledFeatures.onTileErrorStrategy = false; + + const mapconfig = createMapConfig(); + this.testClient = new TestClient(mapconfig, 1234); + this.testClient.setUserRenderTimeoutLimit('localhost', 50, done); + }); + + afterEach(function (done) { + global.environment.enabledFeatures.onTileErrorStrategy = onTileErrorStrategy; + + this.testClient.setUserRenderTimeoutLimit('localhost', 0, (err) => { + if (err) { + return done(err); + } + this.testClient.drain(done); + }); + }); + + it('layergroup creation works and render tile fails', function (done) { + var params = { + response: { + status: 429, + headers: { + 'Content-Type': 'application/json; charset=utf-8' + } + } + }; + + this.testClient.getTile(0, 0, 0, params, (err, res, timeoutError) => { + assert.ifError(err); + + assert.deepEqual(timeoutError, { + errors: ["You are over platform\'s limits. Please contact us to know more details"], + errors_with_context: [{ + type: 'limit', + subtype: 'render', + message: "You are over platform\'s limits. Please contact us to know more details" + }] + }); + + done(); + }); + }); + }); + }); + + describe('vector', function () { + beforeEach(function (done) { + const mapconfig = createMapConfig(); + this.testClient = new TestClient(mapconfig, 1234); + this.testClient.setUserRenderTimeoutLimit('localhost', 50, done); + }); + + afterEach(function (done) { + this.testClient.setUserRenderTimeoutLimit('localhost', 0, (err) => { + if (err) { + return done(err); + } + this.testClient.drain(done); + }); + }); + + it('layergroup creation works but vector tile request fails due to render timeout', function (done) { + const params = { + format: 'mvt', + response: { + status: 429, + headers: { + 'Content-Type': 'application/json; charset=utf-8' + } + } + }; + + this.testClient.getTile(0, 0, 0, params, (err, res, tile) => { + assert.ifError(err); + + assert.deepEqual(tile, { + errors: ['You are over platform\'s limits. Please contact us to know more details'], + errors_with_context: [{ + type: 'limit', + subtype: 'render', + message: 'You are over platform\'s limits. Please contact us to know more details' + }] + }); + + done(); + }); + }); + }); + + describe('interativity', function () { + beforeEach(function (done) { + const mapconfig = createMapConfig(); + this.testClient = new TestClient(mapconfig, 1234); + this.testClient.setUserRenderTimeoutLimit('localhost', 50, done); + }); + + afterEach(function (done) { + this.testClient.setUserRenderTimeoutLimit('localhost', 0, (err) => { + if (err) { + return done(err); + } + this.testClient.drain(done); + }); + }); + + it('layergroup creation works but "grid.json" tile request fails due to render timeout', function (done) { + const params = { + layers: 'mapnik', + format: 'grid.json', + response: { + status: 429, + headers: { + 'Content-Type': 'application/json; charset=utf-8' + } + } + }; + + this.testClient.getTile(0, 0, 0, params, (err, res, tile) => { + assert.ifError(err); + + assert.deepEqual(tile, { + errors: ['You are over platform\'s limits. Please contact us to know more details'], + errors_with_context: [{ + type: 'limit', + subtype: 'render', + message: 'You are over platform\'s limits. Please contact us to know more details' + }] + }); + + done(); + }); + }); + }); + + describe('static images', function () { + describe('with onTileErrorStrategy ENABLED', function () { + let onTileErrorStrategy; + + beforeEach(function (done) { + onTileErrorStrategy = global.environment.enabledFeatures.onTileErrorStrategy; + global.environment.enabledFeatures.onTileErrorStrategy = true; + + const mapconfig = createMapConfig(); + this.testClient = new TestClient(mapconfig, 1234); + this.testClient.setUserRenderTimeoutLimit('localhost', 50, done); + }); + + afterEach(function (done) { + global.environment.enabledFeatures.onTileErrorStrategy = onTileErrorStrategy; + + this.testClient.setUserRenderTimeoutLimit('localhost', 0, (err) => { + if (err) { + return done(err); + } + this.testClient.drain(done); + }); + }); + + it('layergroup creation works but static image fails due to render timeout', function (done) { + const params = { + zoom: 0, + lat: 0, + lng: 0, + width: 256, + height: 256, + format: 'png' + }; + + this.testClient.getStaticCenter(params, function (err, res, tile) { + assert.ifError(err); + + assert.imageIsSimilarToFile(tile, timeoutErrorTilePath, 0.05, (err) => { + assert.ifError(err); + done(); + }); + }); + }); + }); + + describe('with onTileErrorStrategy DISABLED', function() { + var onTileErrorStrategy; + + beforeEach(function (done) { + onTileErrorStrategy = global.environment.enabledFeatures.onTileErrorStrategy; + global.environment.enabledFeatures.onTileErrorStrategy = false; + + const mapconfig = createMapConfig(); + this.testClient = new TestClient(mapconfig, 1234); + this.testClient.setUserRenderTimeoutLimit('localhost', 50, done); + }); + + afterEach(function (done) { + global.environment.enabledFeatures.onTileErrorStrategy = onTileErrorStrategy; + + this.testClient.setUserRenderTimeoutLimit('localhost', 0, (err) => { + if (err) { + return done(err); + } + this.testClient.drain(done); + }); + }); + + it('layergroup creation works and render tile fails', function (done) { + const params = { + zoom: 0, + lat: 0, + lng: 0, + width: 256, + height: 256, + format: 'png', + response: { + status: 429, + headers: { + 'Content-Type': 'application/json; charset=utf-8' + } + } + }; + + this.testClient.getStaticCenter(params, function (err, res, timeoutError) { + assert.ifError(err); + + assert.deepEqual(timeoutError, { + errors: ["You are over platform\'s limits. Please contact us to know more details"], + errors_with_context: [{ + type: 'limit', + subtype: 'render', + message: "You are over platform\'s limits. Please contact us to know more details" + }] + }); + + done(); + }); + }); + }); + }); +}); + diff --git a/test/acceptance/widgets/regressions.js b/test/acceptance/widgets/regressions.js index 1d006ac4..74f4544c 100644 --- a/test/acceptance/widgets/regressions.js +++ b/test/acceptance/widgets/regressions.js @@ -218,6 +218,114 @@ describe('widgets-regressions', function() { }); }); + + it('should not count the polygons outside the bounding box', function(done) { + + // $ % $ = not intersecting left triangle + // $$ **VVVVV** %% % = not intersecting right triangle + // $$$ *VVVVV* %%% * = intersecting triangle + // $$$$ ***** %%%% V = bounding box + // $$$$$ *** %%%%% + // $$$$$$ * %%%%%% + // $$$$$$$ %%%%%%% + // $$$$$$$$ %%%%%%%% + + const notIntersectingLeftTriangle = { + type: "Polygon", + coordinates:[[ + [-161.015625,69.28725695167886], + [-162.7734375,-7.710991655433217], + [-40.78125,-8.059229627200192], + [-161.015625,69.28725695167886] + ]] + }; + + const notIntersectingRightTriangle = { + type: "Polygon", + coordinates: [[ + [-29.179687499999996,-7.01366792756663], + [103.71093749999999,-6.664607562172573], + [105.46875,69.16255790810501], + [-29.179687499999996,-7.01366792756663] + ]] + }; + + const intersectingTriangle = { + type: "Polygon", + coordinates:[[ + [-117.42187500000001,68.13885164925573], + [-35.859375,20.96143961409684], + [59.4140625,68.52823492039876], + [-117.42187500000001,68.13885164925573] + ]] + }; + + const query = ` + SELECT + ST_TRANSFORM(ST_SETSRID(ST_GeomFromGeoJSON( + '${JSON.stringify(notIntersectingLeftTriangle)}' + ), 4326), 3857) AS the_geom_webmercator, 1 AS cartodb_id, 'notIntersectingLeftTriangle' AS name + UNION + SELECT + ST_TRANSFORM(ST_SETSRID(ST_GeomFromGeoJSON( + '${JSON.stringify(notIntersectingRightTriangle)}' + ), 4326), 3857), 2, 'notIntersectingRightTriangle' + UNION + SELECT + ST_TRANSFORM(ST_SETSRID(ST_GeomFromGeoJSON( + '${JSON.stringify(intersectingTriangle)}' + ), 4326), 3857), 3, 'intersectingTriangle' + `; + + const mapConfig = { + version: '1.5.0', + layers: [ + { + "type": "cartodb", + "options": { + "source": { + "id": "a0" + }, + "cartocss": "#points { marker-width: 10; marker-fill: red; }", + "cartocss_version": "2.3.0" + } + } + ], + dataviews: { + val_formula: { + source: { + id: 'a0' + }, + type: 'aggregation', + options: { + column: "name", + aggregation: "count", + } + } + }, + analyses: [ + { + "id": "a0", + "type": "source", + "params": { + "query": query + } + } + ] + }; + + this.testClient = new TestClient(mapConfig, 1234); + const params = { + bbox: '-77.34374999999999,45.82879925192134,17.578125,55.97379820507658' + }; + this.testClient.getDataview('val_formula', params, function(err, dataview) { + assert.ifError(err); + assert.equal(dataview.categories.length, 1); + assert.equal(dataview.categories[0].category, 'intersectingTriangle'); + done(); + }); + }); + }); }); diff --git a/test/support/test-client.js b/test/support/test-client.js index e1ed744c..b6f74e23 100644 --- a/test/support/test-client.js +++ b/test/support/test-client.js @@ -3,7 +3,8 @@ var qs = require('querystring'); var step = require('step'); var urlParser = require('url'); - +var PSQL = require('cartodb-psql'); +var _ = require('underscore'); var mapnik = require('windshaft').mapnik; var LayergroupToken = require('../../lib/cartodb/models/layergroup-token'); @@ -14,13 +15,21 @@ var helper = require('./test_helper'); var CartodbWindshaft = require('../../lib/cartodb/server'); var serverOptions = require('../../lib/cartodb/server_options'); serverOptions.analysis.batch.inlineExecution = true; -var server = new CartodbWindshaft(serverOptions); + +const MAPNIK_SUPPORTED_FORMATS = { + 'png': true, + 'png32': true, + 'grid.json': true, + 'geojson': true, + 'mvt': true +} function TestClient(config, apiKey) { this.mapConfig = isMapConfig(config) ? config : null; this.template = isTemplate(config) ? config : null; this.apiKey = apiKey; this.keysToDelete = {}; + this.server = new CartodbWindshaft(serverOptions); } module.exports = TestClient; @@ -72,6 +81,34 @@ module.exports.CARTOCSS = { ' line-width: 0.5;', ' line-opacity: 1;', '}' + ].join('\n'), + + TORQUE: [ + 'Map {', + ' -torque-frame-count: 256;', + ' -torque-animation-duration: 30;', + ' -torque-time-attribute: "cartodb_id";', + ' -torque-aggregation-function: "count(1)";', + ' -torque-resolution: 4;', + ' -torque-data-aggregation: linear;', + '}', + '#layer {', + ' marker-width: 7;', + ' marker-fill: #FFB927;', + ' marker-fill-opacity: 0.9;', + ' marker-line-width: 1;', + ' marker-line-color: #FFF;', + ' marker-line-opacity: 1;', + ' comp-op: lighter;', + '}', + '#layer[frame-offset=1] {', + ' marker-width: 9;', + ' marker-fill-opacity: 0.45;', + '}', + '#layer[frame-offset=2] {', + ' marker-width: 11;', + ' marker-fill-opacity: 0.225;', + '}' ].join('\n') }; @@ -97,7 +134,7 @@ TestClient.prototype.getWidget = function(widgetName, params, callback) { step( function createLayergroup() { var next = this; - assert.response(server, + assert.response(self.server, { url: url, method: 'POST', @@ -156,7 +193,7 @@ TestClient.prototype.getWidget = function(widgetName, params, callback) { url = '/api/v1/map/' + layergroupId + '/0/widget/' + widgetName + '?' + qs.stringify(urlParams); - assert.response(server, + assert.response(self.server, { url: url, method: 'GET', @@ -208,7 +245,7 @@ TestClient.prototype.widgetSearch = function(widgetName, userQuery, params, call step( function createLayergroup() { var next = this; - assert.response(server, + assert.response(self.server, { url: url, method: 'POST', @@ -265,7 +302,7 @@ TestClient.prototype.widgetSearch = function(widgetName, userQuery, params, call } url = '/api/v1/map/' + layergroupId + '/0/widget/' + widgetName + '/search?' + qs.stringify(urlParams); - assert.response(server, + assert.response(self.server, { url: url, method: 'GET', @@ -332,7 +369,7 @@ TestClient.prototype.getDataview = function(dataviewName, params, callback) { step( function createLayergroup() { var next = this; - assert.response(server, + assert.response(self.server, { url: url, method: 'POST', @@ -385,7 +422,7 @@ TestClient.prototype.getDataview = function(dataviewName, params, callback) { } url = '/api/v1/map/' + layergroupId + '/dataview/' + dataviewName + '?' + qs.stringify(urlParams); - assert.response(server, + assert.response(self.server, { url: url, method: 'GET', @@ -404,12 +441,16 @@ TestClient.prototype.getDataview = function(dataviewName, params, callback) { ); }, function finish(err, dataview) { + if (err) { + return callback(err); + } + if (layergroupId) { self.keysToDelete['map_cfg|' + LayergroupToken.parse(layergroupId).token] = 0; self.keysToDelete['user:localhost:mapviews:global'] = 5; } - return callback(err, dataview); + return callback(null, dataview); } ); }; @@ -446,7 +487,7 @@ TestClient.prototype.getFeatureAttributes = function(featureId, layerId, params, step( function createLayergroup() { var next = this; - assert.response(server, + assert.response(self.server, { url: url, method: 'POST', @@ -485,7 +526,7 @@ TestClient.prototype.getFeatureAttributes = function(featureId, layerId, params, url = '/api/v1/map/' + layergroupId + '/' + layerId + '/attributes/' + featureId; - assert.response(server, + assert.response(self.server, { url: url, method: 'GET', @@ -529,6 +570,11 @@ TestClient.prototype.getTile = function(z, x, y, params, callback) { } var layergroupId; + + if (params.layergroupid) { + layergroupId = params.layergroupid + } + step( function createTemplate () { var next = this; @@ -543,7 +589,7 @@ TestClient.prototype.getTile = function(z, x, y, params, callback) { params.placeholders = params.placeholders || {}; - assert.response(server, + assert.response(self.server, { url: urlNamed + '?' + qs.stringify({ api_key: self.apiKey }), method: 'POST', @@ -570,12 +616,16 @@ TestClient.prototype.getTile = function(z, x, y, params, callback) { function createLayergroup(err, templateId) { var next = this; + if (layergroupId) { + return next(null, layergroupId); + } + var data = templateId ? params.placeholders : self.mapConfig var path = templateId ? urlNamed + '/' + templateId + '?' + qs.stringify({api_key: self.apiKey}) : url; - assert.response(server, + assert.response(self.server, { url: path, method: 'POST', @@ -616,6 +666,10 @@ TestClient.prototype.getTile = function(z, x, y, params, callback) { var format = params.format || 'png'; + if (layers === undefined && !MAPNIK_SUPPORTED_FORMATS[format]) { + throw new Error(`Missing layer filter while fetching ${format} tile, review params argument`); + } + url += [z,x,y].join('/'); url += '.' + format; @@ -631,32 +685,30 @@ TestClient.prototype.getTile = function(z, x, y, params, callback) { } }; - var expectedResponse = { - status: params.status || 200, + var expectedResponse = Object.assign({}, { + status: 200, headers: { - 'Content-Type': 'application/json; charset=utf-8' + 'Content-Type': 'image/png' } - }; + }, params.response); + var isPng = format.match(/png$/); if (isPng) { request.encoding = 'binary'; - expectedResponse.headers['Content-Type'] = 'image/png'; } var isMvt = format.match(/mvt$/); if (isMvt) { request.encoding = 'binary'; - if (expectedResponse.status === 200) { expectedResponse.headers['Content-Type'] = 'application/x-protobuf'; - } else if (expectedResponse.status === 204) { - expectedResponse.headers['Content-Type'] = undefined; } } + var isGeojson = format.match(/geojson$/); if (isGeojson) { @@ -671,29 +723,38 @@ TestClient.prototype.getTile = function(z, x, y, params, callback) { expectedResponse.headers['Content-Type'] = 'application/json; charset=utf-8'; } - assert.response(server, request, expectedResponse, function(res, err) { + if (params.contentType) { + expectedResponse.headers['Content-Type'] = 'application/json; charset=utf-8'; + } + + assert.response(self.server, request, expectedResponse, function(res, err) { assert.ifError(err); - var obj; - if (isPng) { - obj = mapnik.Image.fromBytes(new Buffer(res.body, 'binary')); - } - else if (isMvt) { - if (res.body) { - obj = new mapnik.VectorTile(z, x, y); - obj.setDataSync(new Buffer(res.body, 'binary')); - } - } - else { - obj = JSON.parse(res.body); + var body; + switch (res.headers['content-type']) { + case 'image/png': + body = mapnik.Image.fromBytes(new Buffer(res.body, 'binary')); + break; + case 'application/x-protobuf': + body = new mapnik.VectorTile(z, x, y); + body.setDataSync(new Buffer(res.body, 'binary')); + break; + case 'application/json; charset=utf-8': + body = JSON.parse(res.body); + break; + default: + body = res.body + break; } - next(null, res, obj); + next(null, res, body); }); }, function finish(err, res, image) { - self.keysToDelete['map_cfg|' + LayergroupToken.parse(layergroupId).token] = 0; - self.keysToDelete['user:localhost:mapviews:global'] = 5; + if (layergroupId) { + self.keysToDelete['map_cfg|' + LayergroupToken.parse(layergroupId).token] = 0; + self.keysToDelete['user:localhost:mapviews:global'] = 5; + } return callback(err, res, image); } ); @@ -718,7 +779,7 @@ TestClient.prototype.getLayergroup = function(expectedResponse, callback) { url += '?' + qs.stringify({api_key: this.apiKey}); } - assert.response(server, + assert.response(self.server, { url: url, method: 'POST', @@ -743,7 +804,111 @@ TestClient.prototype.getLayergroup = function(expectedResponse, callback) { return callback(err); } - return callback(null, JSON.parse(res.body)); + return callback(null, parsedBody); + } + ); +}; + +TestClient.prototype.getStaticCenter = function (params, callback) { + var self = this; + + let { layergroupid, z, lat, lng, width, height, format } = params + + var url = `/api/v1/map/`; + + if (this.apiKey) { + url += '?' + qs.stringify({api_key: this.apiKey}); + } + + step( + function createLayergroup() { + var next = this; + + if (layergroupid) { + return next(null, layergroupid); + } + + var data = self.mapConfig + var path = url; + + assert.response(self.server, + { + url: path, + method: 'POST', + headers: { + host: 'localhost', + 'Content-Type': 'application/json' + }, + data: JSON.stringify(data) + }, + { + status: 200, + headers: { + 'Content-Type': 'application/json; charset=utf-8' + } + }, + function(res, err) { + if (err) { + return next(err); + } + return next(null, JSON.parse(res.body).layergroupid); + } + ); + }, + function getStaticResult(err, _layergroupid) { + assert.ifError(err); + + var next = this; + + layergroupid = _layergroupid; + + url = `/api/v1/map/static/center/${layergroupid}/${z}/${lat}/${lng}/${width}/${height}.${format}` + + if (self.apiKey) { + url += '?' + qs.stringify({api_key: self.apiKey}); + } + + var request = { + url: url, + encoding: 'binary', + method: 'GET', + headers: { + host: 'localhost' + } + }; + + var expectedResponse = Object.assign({}, { + status: 200, + headers: { + 'Content-Type': 'image/png' + } + }, params.response); + + assert.response(self.server, request, expectedResponse, function(res, err) { + assert.ifError(err); + + var body; + switch (res.headers['content-type']) { + case 'image/png': + body = mapnik.Image.fromBytes(new Buffer(res.body, 'binary')); + break; + case 'application/json; charset=utf-8': + body = JSON.parse(res.body); + break; + default: + body = res.body + break; + } + + next(null, res, body); + }); + }, + function finish(err, res, image) { + if (layergroupid) { + self.keysToDelete['map_cfg|' + LayergroupToken.parse(layergroupid).token] = 0; + self.keysToDelete['user:localhost:mapviews:global'] = 5; + } + return callback(err, res, image); } ); }; @@ -762,7 +927,7 @@ TestClient.prototype.getNodeStatus = function(nodeName, callback) { step( function createLayergroup() { var next = this; - assert.response(server, + assert.response(self.server, { url: url, method: 'POST', @@ -823,7 +988,7 @@ TestClient.prototype.getNodeStatus = function(nodeName, callback) { } }; - assert.response(server, request, expectedResponse, function(res, err) { + assert.response(self.server, request, expectedResponse, function(res, err) { assert.ifError(err); next(null, res, JSON.parse(res.body)); }); @@ -836,11 +1001,119 @@ TestClient.prototype.getNodeStatus = function(nodeName, callback) { ); }; +TestClient.prototype.getAttributes = function(params, callback) { + var self = this; + + if (!Number.isFinite(params.featureId)) { + throw new Error('featureId param must be a number') + } + + if (!Number.isFinite(params.layer)) { + throw new Error('layer param must be a number') + } + + var url = '/api/v1/map'; + + if (this.apiKey) { + url += '?' + qs.stringify({ api_key: this.apiKey }); + } + + var layergroupid; + + if (params.layergroupid) { + layergroupid = params.layergroupid + } + + step( + function createLayergroup() { + var next = this; + + if (layergroupid) { + return next(null, layergroupid); + } + + assert.response(self.server, + { + url: url, + method: 'POST', + headers: { + host: 'localhost', + 'Content-Type': 'application/json' + }, + data: JSON.stringify(self.mapConfig) + }, + { + status: 200, + headers: { + 'Content-Type': 'application/json; charset=utf-8' + } + }, + function(res, err) { + if (err) { + return next(err); + } + var parsedBody = JSON.parse(res.body); + + return next(null, parsedBody.layergroupid); + } + ); + }, + function getAttributes(err, _layergroupid) { + assert.ifError(err); + + var next = this; + + layergroupid = _layergroupid; + + url = `/api/v1/map/${layergroupid}/${params.layer}/attributes/${params.featureId}`; + + if (self.apiKey) { + url += '?' + qs.stringify({api_key: self.apiKey}); + } + + var request = { + url: url, + method: 'GET', + headers: { + host: 'localhost' + } + }; + + var expectedResponse = params.response || { + status: 200, + headers: { + 'Content-Type': 'application/json; charset=utf-8' + } + }; + + assert.response(self.server, request, expectedResponse, function (res, err) { + assert.ifError(err); + + var attributes = JSON.parse(res.body); + + next(null, res, attributes); + }); + }, + function finish(err, res, attributes) { + if (layergroupid) { + self.keysToDelete['map_cfg|' + LayergroupToken.parse(layergroupid).token] = 0; + self.keysToDelete['user:localhost:mapviews:global'] = 5; + } + + return callback(err, res, attributes); + } + ); +}; + TestClient.prototype.drain = function(callback) { helper.deleteRedisKeys(this.keysToDelete, callback); }; module.exports.getStaticMap = function getStaticMap(templateName, params, callback) { + var self = this; + + self.server = new CartodbWindshaft(serverOptions); + if (!callback) { callback = params; params = null; @@ -871,9 +1144,56 @@ module.exports.getStaticMap = function getStaticMap(templateName, params, callba // this could be removed once named maps are invalidated, otherwise you hits the cache var server = new CartodbWindshaft(serverOptions); - assert.response(server, requestOptions, expectedResponse, function (res, err) { + assert.response(self.server, requestOptions, expectedResponse, function (res, err) { helper.deleteRedisKeys({'user:localhost:mapviews:global': 5}, function() { return callback(err, mapnik.Image.fromBytes(new Buffer(res.body, 'binary'))); }); }); }; + +TestClient.prototype.setUserRenderTimeoutLimit = function (user, userTimeoutLimit, callback) { + const userTimeoutLimitsKey = `limits:timeout:${user}`; + const params = [ + userTimeoutLimitsKey, + 'render', userTimeoutLimit, + 'render_public', userTimeoutLimit + ]; + + this.keysToDelete[userTimeoutLimitsKey] = 5; + + helper.configureMetadata('hmset', params, callback); +}; + +TestClient.prototype.setUserDatabaseTimeoutLimit = function (timeoutLimit, callback) { + const dbname = _.template(global.environment.postgres_auth_user, { user_id: 1 }) + '_db'; + const dbuser = _.template(global.environment.postgres_auth_user, { user_id: 1 }) + const pass = _.template(global.environment.postgres_auth_pass, { user_id: 1 }) + const publicuser = global.environment.postgres.user; + + // we need to guarantee all new connections have the new settings + helper.cleanPGPoolConnections() + + const psql = new PSQL({ + user: 'postgres', + dbname: dbname, + host: global.environment.postgres.host, + port: global.environment.postgres.port + }); + + step( + function configureTimeouts () { + const timeoutSQLs = [ + `ALTER ROLE "${publicuser}" SET STATEMENT_TIMEOUT TO ${timeoutLimit}`, + `ALTER ROLE "${dbuser}" SET STATEMENT_TIMEOUT TO ${timeoutLimit}`, + `ALTER DATABASE "${dbname}" SET STATEMENT_TIMEOUT TO ${timeoutLimit}` + ]; + + const group = this.group(); + + timeoutSQLs.forEach(sql => psql.query(sql, group())); + }, + callback + ); +}; + + diff --git a/test/support/test_helper.js b/test/support/test_helper.js index de9a6e3c..49c6c1ee 100644 --- a/test/support/test_helper.js +++ b/test/support/test_helper.js @@ -14,6 +14,7 @@ var lzmaWorker = new LZMA(); var redis = require('redis'); var nock = require('nock'); var log4js = require('log4js'); +var pg = require('pg'); // set environment specific variables global.environment = require(__dirname + '/../../config/environments/test'); @@ -127,6 +128,11 @@ afterEach(function(done) { }); }); +function cleanPGPoolConnections () { + // TODO: this method will be replaced by psql.end + pg.end(); +} + function deleteRedisKeys(keysToDelete, callback) { if (Object.keys(keysToDelete).length === 0) { @@ -166,12 +172,30 @@ function rmdirRecursiveSync(dirname) { } } +function configureMetadata(action, params, callback) { + redisClient.SELECT(5, function (err) { + if (err) { + return callback(err); + } + + redisClient[action](params, function (err) { + if (err) { + return callback(err); + } + + return callback(); + }); + }); +} + module.exports = { deleteRedisKeys: deleteRedisKeys, lzma_compress_to_base64: lzma_compress_to_base64, checkNoCache: checkNoCache, checkSurrogateKey: checkSurrogateKey, checkCache: checkCache, - rmdirRecursiveSync: rmdirRecursiveSync + rmdirRecursiveSync: rmdirRecursiveSync, + configureMetadata, + cleanPGPoolConnections }; diff --git a/test/unit/cartodb/ported/tile_stats.test.js b/test/unit/cartodb/ported/tile_stats.test.js index 45b1fa29..b71f1384 100644 --- a/test/unit/cartodb/ported/tile_stats.test.js +++ b/test/unit/cartodb/ported/tile_stats.test.js @@ -6,10 +6,13 @@ var LayergroupController = require('../../../../lib/cartodb/controllers/layergro describe('tile stats', function() { - after(function() { - global.statsClient = null; + beforeEach(function () { + this.statsClient = global.statsClient; }); + afterEach(function() { + global.statsClient = this.statsClient; + }); it('finalizeGetTileOrGrid does not call statsClient when format is not supported', function() { var expectedCalls = 2, // it will call increment once for the general error diff --git a/yarn.lock b/yarn.lock index 49cbc74e..fa2928f4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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" @@ -109,9 +109,9 @@ aws4@^1.2.1: version "1.6.0" resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e" -balanced-match@^0.4.1: - version "0.4.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838" +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" bcrypt-pbkdf@^1.0.0: version "1.0.1" @@ -120,8 +120,8 @@ bcrypt-pbkdf@^1.0.0: tweetnacl "^0.14.3" bindings@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.2.1.tgz#14ad6113812d2d37d72e67b4cacb4bb726505f11" + version "1.3.0" + resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.3.0.tgz#b346f6ecf6a95f5a815c5839fc7cdb22502f1ed7" block-stream@*: version "0.0.9" @@ -150,11 +150,11 @@ boom@2.x.x: dependencies: hoek "2.x.x" -brace-expansion@^1.0.0: - version "1.1.7" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.7.tgz#3effc3c50e000531fb720eaff80f0ae8ef23cf59" +brace-expansion@^1.1.7: + version "1.1.8" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292" dependencies: - balanced-match "^0.4.1" + balanced-match "^1.0.0" concat-map "0.0.1" browser-stdout@1.3.0: @@ -194,13 +194,13 @@ camelcase@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" -camshaft@0.55.6: - version "0.55.6" - resolved "https://registry.yarnpkg.com/camshaft/-/camshaft-0.55.6.tgz#11af28051c3b911fb023ae1cafb165bbd040f174" +camshaft@0.58.1: + version "0.58.1" + resolved "https://registry.yarnpkg.com/camshaft/-/camshaft-0.58.1.tgz#e4156580683f624212ea3020e59790ad006f24cc" dependencies: async "^1.5.2" bunyan "1.8.1" - cartodb-psql "0.8.0" + cartodb-psql "^0.10.1" debug "^2.2.0" dot "^1.0.3" request "^2.69.0" @@ -245,22 +245,21 @@ cartocolor@4.0.0: dependencies: colorbrewer "1.0.0" -cartodb-psql@0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/cartodb-psql/-/cartodb-psql-0.8.0.tgz#d3811f706dae2c3bc82365c5d25af13c4235ba37" +cartodb-psql@0.10.1, cartodb-psql@^0.10.1: + version "0.10.1" + resolved "https://registry.yarnpkg.com/cartodb-psql/-/cartodb-psql-0.10.1.tgz#0ac947e62fe10b27916df6b7ba6c461953fe3a23" dependencies: debug "~2.2.0" - pg cartodb/node-postgres#6.1.2-cdb1 - step "~0.0.6" + 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-redis@0.13.2: - version "0.13.2" - resolved "https://registry.yarnpkg.com/cartodb-redis/-/cartodb-redis-0.13.2.tgz#de5214fa5c3ab336c4da978133efa8f908b3691c" +cartodb-redis@0.14.0: + version "0.14.0" + resolved "https://registry.yarnpkg.com/cartodb-redis/-/cartodb-redis-0.14.0.tgz#6f82fdb3e5b7c8005dbaccd6172c1706c4378df2" dependencies: dot "~1.0.2" redis-mpool "~0.4.1" @@ -344,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" @@ -369,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" @@ -380,7 +383,7 @@ cookie@0.1.5: version "0.1.5" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.1.5.tgz#6ab9948a4b1ae21952cd2588530a4722d4044d7c" -core-util-is@~1.0.0: +core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" @@ -404,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: @@ -417,10 +420,16 @@ debug@2.6.0: ms "0.7.2" debug@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/debug/-/debug-1.0.4.tgz#5b9c256bd54b6ec02283176fa8a0ede6d154cbf8" + version "1.0.5" + resolved "https://registry.yarnpkg.com/debug/-/debug-1.0.5.tgz#f7241217430f99dec4c2b473eab92228e874c2ac" dependencies: - ms "0.6.2" + 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" @@ -432,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" @@ -444,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" @@ -453,8 +473,8 @@ delegates@^1.0.0: resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" depd@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.0.tgz#e1bd82c6aab6ced965b97b88b17ed3e528ca18c3" + version "1.1.1" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359" destroy@~1.0.4: version "1.0.4" @@ -492,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" @@ -526,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" @@ -553,9 +595,9 @@ esprima@2.7.x, esprima@^2.7.1: version "2.7.3" resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581" -esprima@^3.1.1: - version "3.1.3" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" +esprima@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804" estraverse@^1.9.1: version "1.9.3" @@ -607,9 +649,9 @@ extend@~3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" -extsprintf@1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.0.2.tgz#e1080e0658e300b06294990cc70e1502235fd550" +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-levenshtein@~2.0.4: version "2.0.6" @@ -637,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" @@ -650,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" @@ -678,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" @@ -692,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" @@ -708,7 +764,7 @@ generate-object-property@^1.1.0: dependencies: is-property "^1.0.0" -generic-pool@2.4.3, generic-pool@~2.4.0, generic-pool@~2.4.1: +generic-pool@2.4.3: version "2.4.3" resolved "https://registry.yarnpkg.com/generic-pool/-/generic-pool-2.4.3.tgz#780c36f69dfad05a5a045dd37be7adca11a4f6ff" @@ -720,6 +776,10 @@ generic-pool@~2.2.0, generic-pool@~2.2.1: version "2.2.2" resolved "https://registry.yarnpkg.com/generic-pool/-/generic-pool-2.2.2.tgz#7a89f491d575b42f9f069a0e8e2c6dbaa3c241be" +generic-pool@~2.4.0, generic-pool@~2.4.1: + version "2.4.6" + resolved "https://registry.yarnpkg.com/generic-pool/-/generic-pool-2.4.6.tgz#f1b55e572167dba2fe75d5aa91ebb1e9f72642d7" + get-caller-file@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5" @@ -730,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: @@ -761,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" @@ -788,8 +859,8 @@ growl@1.9.2: resolved "https://registry.yarnpkg.com/growl/-/growl-1.9.2.tgz#0ea7743715db8d8de2c5ede1775e1b45ac85c02f" handlebars@^4.0.1: - version "4.0.8" - resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.8.tgz#22b875cd3f0e6cbea30314f144e82bc7a72ff420" + version "4.0.10" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.10.tgz#3d30c718b09a3d96f23ea4cc1f403c4d3ba9ff4f" dependencies: async "^1.4.0" optimist "^0.6.1" @@ -831,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" @@ -852,8 +929,8 @@ hoek@2.x.x: resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed" hosted-git-info@^2.1.4: - version "2.4.2" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.4.2.tgz#0076b9f46a270506ddbaaea56496897460612a67" + version "2.5.0" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.5.0.tgz#6d60e34b3abbc8313062c3b798ef8d901a07af3c" htmlparser2@3.8.x: version "3.8.3" @@ -925,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" @@ -944,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" @@ -987,30 +1086,28 @@ istanbul@~0.4.3: which "^1.1.1" wordwrap "^1.0.0" -jodid25519@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/jodid25519/-/jodid25519-1.0.2.tgz#06d4912255093419477d425633606e0e90782967" - dependencies: - jsbn "~0.1.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.8.4" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.8.4.tgz#520b4564f86573ba96662af85a8cafa7b4b5a6f6" + version "3.10.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.10.0.tgz#2e78441646bd4682e963f22b6e92823c309c62dc" dependencies: argparse "^1.0.7" - esprima "^3.1.1" + esprima "^4.0.0" jsbn@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" jshint@~2.9.4: - version "2.9.4" - resolved "https://registry.yarnpkg.com/jshint/-/jshint-2.9.4.tgz#5e3ba97848d5290273db514aee47fe24cf592934" + version "2.9.5" + resolved "https://registry.yarnpkg.com/jshint/-/jshint-2.9.5.tgz#1e7252915ce681b40827ee14248c46d34e9aa62c" dependencies: cli "~1.0.0" console-browserify "1.1.x" @@ -1048,13 +1145,13 @@ jsonpointer@^4.0.0: resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9" jsprim@^1.2.2: - version "1.4.0" - resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.0.tgz#a3b87e40298d8c380552d8cc7628a0bb95a22918" + version "1.4.1" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" dependencies: assert-plus "1.0.0" - extsprintf "1.0.2" + extsprintf "1.3.0" json-schema "0.2.3" - verror "1.3.6" + verror "1.10.0" kind-of@^3.0.2: version "3.2.2" @@ -1230,17 +1327,17 @@ millstone@0.6.17: underscore "~1.6.0" zipfile "~0.5.5" -mime-db@~1.27.0: - version "1.27.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.27.0.tgz#820f572296bbd20ec25ed55e5b5de869e5436eb1" +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.15" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.15.tgz#a4ebf5064094569237b8cf70046776d09fc92aed" + version "2.1.17" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.17.tgz#09d7a393f03e995a79f8af857b70a9e0ab16557a" dependencies: - mime-db "~1.27.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" @@ -1248,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: - version "3.0.3" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.3.tgz#2a4e4090b96b2db06a9d7df01055a62a77c9b774" - dependencies: - brace-expansion "^1.0.0" +mime@~1.3.4: + version "1.3.6" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.6.tgz#591d84d3653a6b0b4a3b9df8de5aa8108e72e5e0" -minimist@0.0.8, minimist@~0.0.1: +"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: 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" @@ -1273,8 +1378,8 @@ mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdi minimist "0.0.8" mocha@~3.4.1: - version "3.4.1" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-3.4.1.tgz#a3802b4aa381934cacb38de70cf771621da8f9af" + version "3.4.2" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-3.4.2.tgz#d0ef4d332126dbf18d0d640c9b382dd48be97594" dependencies: browser-stdout "1.3.0" commander "2.9.0" @@ -1292,10 +1397,6 @@ moment@^2.10.6, moment@~2.18.1: version "2.18.1" resolved "https://registry.yarnpkg.com/moment/-/moment-2.18.1.tgz#c36193dd3ce1c2eed2adb7c802dbbc77a81b1c0f" -ms@0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-0.6.2.tgz#d89c2124c6fdc1353d65a8b77bf1aac4b193708c" - ms@0.7.1: version "0.7.1" resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.1.tgz#9cd13c03adbff25b65effde7ce864ee952017098" @@ -1304,6 +1405,10 @@ ms@0.7.2: version "0.7.2" resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.2.tgz#ae25cf2512b3885a1d95d7f037868d8431124765" +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + mv@~2: version "2.1.1" resolved "https://registry.yarnpkg.com/mv/-/mv-2.1.1.tgz#ae6ce0d6f6d5e0a4f7d893798d03c1ea9559b6a2" @@ -1312,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: - 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" @@ -1343,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.31: - 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" @@ -1354,6 +1459,7 @@ node-pre-gyp@~0.6.27, node-pre-gyp@~0.6.30, node-pre-gyp@~0.6.31: 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" @@ -1375,8 +1481,8 @@ nopt@^4.0.1: osenv "^0.1.4" normalize-package-data@^2.3.2: - version "2.3.8" - resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.3.8.tgz#d819eda2a9dedbd1ffa563ea4071d936782295bb" + version "2.4.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f" dependencies: hosted-git-info "^2.1.4" is-builtin-module "^1.0.0" @@ -1400,10 +1506,22 @@ oauth-sign@~0.8.1: version "0.8.2" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" -object-assign@4.1.0, object-assign@^4.1.0: +object-assign@4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.0.tgz#7a3b3d0e98063d43f4c03f2e8ae6cd51a86883a0" +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" @@ -1470,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" @@ -1483,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" @@ -1504,27 +1626,27 @@ pg-connection-string@0.1.3: resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-0.1.3.tgz#da1847b20940e42ee1492beaf65d49d91b245df7" pg-pool@1.*: - version "1.7.1" - resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-1.7.1.tgz#421105cb7469979dcc48d6fc4fe3fe4659437437" + version "1.8.0" + resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-1.8.0.tgz#f7ec73824c37a03f076f51bfdf70e340147c4f37" dependencies: generic-pool "2.4.3" object-assign "4.1.0" pg-types@1.*: - version "1.11.0" - resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-1.11.0.tgz#aae91a82d952b633bb88d006350a166daaf6ea90" + 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.0.0" + postgres-interval "^1.1.0" -pg@cartodb/node-postgres#6.1.2-cdb1: - version "6.1.2" - resolved "https://codeload.github.com/cartodb/node-postgres/tar.gz/3c81aea432ce58d20a795786c58bbb14f68f9689" +pg@cartodb/node-postgres#6.1.6-cdb1: + version "6.1.6" + resolved "https://codeload.github.com/cartodb/node-postgres/tar.gz/3eef52dd1e655f658a4ee8ac5697688b3ecfed44" dependencies: buffer-writer "1.0.1" + js-string-escape "1.0.1" packet-reader "0.2.0" pg-connection-string "0.1.3" pg-pool "1.*" @@ -1533,8 +1655,8 @@ pg@cartodb/node-postgres#6.1.2-cdb1: semver "4.3.2" pgpass@1.x: - version "1.0.1" - resolved "https://registry.yarnpkg.com/pgpass/-/pgpass-1.0.1.tgz#0de8b5bef993295d90a7e17d976f568dcd25d49f" + version "1.0.2" + resolved "https://registry.yarnpkg.com/pgpass/-/pgpass-1.0.2.tgz#2a7bb41b6065b67907e91da1b07c1847c877b306" dependencies: split "^1.0.0" @@ -1597,9 +1719,9 @@ postgres-date@~1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-1.0.3.tgz#e2d89702efdb258ff9d9cee0fe91bd06975257a8" -postgres-interval@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-1.0.2.tgz#7261438d862b412921c6fdb7617668424b73a6ed" +postgres-interval@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-1.1.1.tgz#acdb0f897b4b1c6e496d9d4e0a853e1c428f06f0" dependencies: xtend "^4.0.0" @@ -1694,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: @@ -1724,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" @@ -1741,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.69.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.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: @@ -1793,6 +1899,31 @@ 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" @@ -1805,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" @@ -1812,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" @@ -1832,8 +1975,8 @@ safe-json-stringify@~1: 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.3.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" + version "5.4.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e" semver@4.3.2: version "4.3.2" @@ -1922,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" @@ -1949,13 +2092,17 @@ 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.0" - resolved "https://registry.yarnpkg.com/split/-/split-1.0.0.tgz#c4395ce683abcd254bc28fe1dabb6e5c27dcffae" + version "1.0.1" + resolved "https://registry.yarnpkg.com/split/-/split-1.0.1.tgz#605bd9be303aa59fb35f9229fbea0ddec9ea07d9" dependencies: through "2" @@ -1964,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.8" - resolved "https://registry.yarnpkg.com/sqlite3/-/sqlite3-3.1.8.tgz#4cbcf965d8b901d1b1015cbc7fc415aae157dfaa" + version "3.1.11" + resolved "https://registry.yarnpkg.com/sqlite3/-/sqlite3-3.1.11.tgz#84421b181b3e9f4939b7a3d712263d7946cfc96d" dependencies: - nan "~2.4.0" - node-pre-gyp "~0.6.31" + nan "~2.7.0" + node-pre-gyp "~0.6.37" srs@1.x: version "1.2.0" @@ -1977,8 +2124,8 @@ srs@1.x: gdal "~0.9.2" sshpk@^1.7.0: - version "1.13.0" - resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.13.0.tgz#ff2a3e4fd04497555fed97b39a0fd82fafb3a33c" + version "1.13.1" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.13.1.tgz#512df6da6287144316dc4c18fe1cf1d940739be3" dependencies: asn1 "~0.2.3" assert-plus "^1.0.0" @@ -1987,7 +2134,6 @@ sshpk@^1.7.0: optionalDependencies: bcrypt-pbkdf "^1.0.0" ecc-jsbn "~0.1.1" - jodid25519 "^1.0.0" jsbn "~0.1.0" tweetnacl "~0.14.0" @@ -2021,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" @@ -2071,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" @@ -2099,21 +2271,21 @@ 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-cdb3: - version "2.3.1-cdb3" - resolved "https://codeload.github.com/cartodb/tilelive-bridge/tar.gz/bde83c8dcf4ada40c7c0eb1b477f212e75399d23" +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: mapnik "~3.5.0" mapnik-pool "~0.1.3" sphericalmercator "1.0.x" -tilelive-mapnik@cartodb/tilelive-mapnik#0.6.18-cdb2: - version "0.6.18-cdb2" - resolved "https://codeload.github.com/cartodb/tilelive-mapnik/tar.gz/46f1adefee90f3f46c0ede5e0833f8522634a858" +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: generic-pool "~2.4.0" mapnik "3.5.14" @@ -2189,8 +2361,8 @@ type-is@~1.6.10, type-is@~1.6.6: mime-types "~2.1.15" uglify-js@^2.6: - version "2.8.26" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.26.tgz#3a1db8ae0a0aba7f92e1ddadadbd0293d549f90e" + version "2.8.29" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.29.tgz#29c5733148057bb4e1f75df35b7a9cb72e6a59dd" dependencies: source-map "~0.5.1" yargs "~3.10.0" @@ -2230,8 +2402,8 @@ utils-merge@1.0.0: resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.0.tgz#0294fb922bb9375153541c4f7096231f287c8af8" uuid@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.0.1.tgz#6544bba2dfda8c1cf17e629a3a305e2bb1fee6c1" + version "3.1.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04" validate-npm-package-license@^3.0.1: version "3.0.1" @@ -2244,19 +2416,21 @@ vary@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/vary/-/vary-1.0.1.tgz#99e4981566a286118dfb2b817357df7993376d10" -verror@1.3.6: - version "1.3.6" - resolved "https://registry.yarnpkg.com/verror/-/verror-1.3.6.tgz#cff5df12946d297d2baaefaa2689e25be01c005c" +verror@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" dependencies: - extsprintf "1.0.2" + assert-plus "^1.0.0" + core-util-is "1.0.2" + extsprintf "^1.2.0" which-module@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f" which@^1.1.1: - version "1.2.14" - resolved "https://registry.yarnpkg.com/which/-/which-1.2.14.tgz#9a87c4378f03e827cecaf1acdf56c736c01c14e5" + version "1.3.0" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a" dependencies: isexe "^2.0.0" @@ -2274,14 +2448,14 @@ 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.2.2: - version "3.2.2" - resolved "https://registry.yarnpkg.com/windshaft/-/windshaft-3.2.2.tgz#7afb9d8fd8bba1bf02d39c06e8bbe5a451aad953" +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 carto cartodb/carto#0.15.1-cdb3 - cartodb-psql "0.8.0" + cartodb-psql "^0.10.1" debug "~2.2.0" dot "~1.0.2" grainstore "~1.6.0" @@ -2293,8 +2467,8 @@ windshaft@3.2.2: sphericalmercator "1.0.4" step "~0.0.6" tilelive "5.12.2" - tilelive-bridge cartodb/tilelive-bridge#2.3.1-cdb3 - tilelive-mapnik cartodb/tilelive-mapnik#0.6.18-cdb2 + tilelive-bridge cartodb/tilelive-bridge#2.3.1-cdb4 + tilelive-mapnik cartodb/tilelive-mapnik#0.6.18-cdb3 torque.js "~2.11.0" underscore "~1.6.0"