Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7c322d9411 | ||
|
|
bd35d4e78a | ||
|
|
81ff0152c0 | ||
|
|
8a07f9f57e | ||
|
|
ca367d0fe7 | ||
|
|
bb21270aab | ||
|
|
22f3a54fbf | ||
|
|
6644711969 | ||
|
|
989df4a8a4 | ||
|
|
d5423c88ea | ||
|
|
5838b7a455 | ||
|
|
86e8cedfab | ||
|
|
93c31c5433 | ||
|
|
ef276bd51e | ||
|
|
7ac3784f32 | ||
|
|
c0a24108ba | ||
|
|
ae9b8a0380 | ||
|
|
f008c74419 | ||
|
|
4a646d4700 | ||
|
|
657b262d92 | ||
|
|
988412fc07 | ||
|
|
5ba72b4894 |
@@ -1,9 +1,10 @@
|
||||
sudo: false
|
||||
|
||||
addons:
|
||||
postgresql: "9.3"
|
||||
postgresql: "9.4"
|
||||
apt:
|
||||
packages:
|
||||
- postgresql-plpython-9.4
|
||||
- pkg-config
|
||||
- libcairo2-dev
|
||||
- libjpeg8-dev
|
||||
|
||||
9
NEWS.md
9
NEWS.md
@@ -1,5 +1,14 @@
|
||||
# Changelog
|
||||
|
||||
## 2.44.1
|
||||
|
||||
Released 2016-06-01
|
||||
|
||||
Improvements:
|
||||
- Extend overviews support to histogram and aggregation dataviews
|
||||
- Test improvements
|
||||
|
||||
|
||||
## 2.44.0
|
||||
|
||||
Released 2016-05-31
|
||||
|
||||
141
lib/cartodb/models/dataview/overviews/aggregation.js
Normal file
141
lib/cartodb/models/dataview/overviews/aggregation.js
Normal file
@@ -0,0 +1,141 @@
|
||||
var BaseOverviewsDataview = require('./base');
|
||||
var BaseDataview = require('../aggregation');
|
||||
|
||||
var dot = require('dot');
|
||||
dot.templateSettings.strip = false;
|
||||
|
||||
var summaryQueryTpl = dot.template([
|
||||
'summary AS (',
|
||||
' SELECT',
|
||||
' sum(_feature_count) AS count,',
|
||||
' sum(CASE WHEN {{=it._column}} IS NULL THEN 1 ELSE 0 END) AS nulls_count',
|
||||
' FROM ({{=it._query}}) _cdb_aggregation_nulls',
|
||||
')'
|
||||
].join('\n'));
|
||||
|
||||
var rankedCategoriesQueryTpl = dot.template([
|
||||
'categories AS(',
|
||||
' SELECT {{=it._column}} AS category, {{=it._aggregation}} AS value,',
|
||||
' row_number() OVER (ORDER BY {{=it._aggregation}} desc) as rank',
|
||||
' FROM ({{=it._query}}) _cdb_aggregation_all',
|
||||
' GROUP BY {{=it._column}}',
|
||||
' ORDER BY 2 DESC',
|
||||
')'
|
||||
].join('\n'));
|
||||
|
||||
var categoriesSummaryQueryTpl = dot.template([
|
||||
'categories_summary AS(',
|
||||
' SELECT count(1) categories_count, max(value) max_val, min(value) min_val',
|
||||
' FROM categories',
|
||||
')'
|
||||
].join('\n'));
|
||||
|
||||
var rankedAggregationQueryTpl = dot.template([
|
||||
'SELECT CAST(category AS text), value, false as agg, nulls_count, min_val, max_val, count, categories_count',
|
||||
' FROM categories, summary, categories_summary',
|
||||
' WHERE rank < {{=it._limit}}',
|
||||
'UNION ALL',
|
||||
'SELECT \'Other\' category, sum(value), true as agg, nulls_count, min_val, max_val, count, categories_count',
|
||||
' FROM categories, summary, categories_summary',
|
||||
' WHERE rank >= {{=it._limit}}',
|
||||
'GROUP BY nulls_count, min_val, max_val, count, categories_count'
|
||||
].join('\n'));
|
||||
|
||||
var aggregationQueryTpl = dot.template([
|
||||
'SELECT CAST({{=it._column}} AS text) AS category, {{=it._aggregation}} AS value, false as agg,',
|
||||
' nulls_count, min_val, max_val, count, categories_count',
|
||||
'FROM ({{=it._query}}) _cdb_aggregation_all, summary, categories_summary',
|
||||
'GROUP BY category, nulls_count, min_val, max_val, count, categories_count',
|
||||
'ORDER BY value DESC'
|
||||
].join('\n'));
|
||||
|
||||
var CATEGORIES_LIMIT = 6;
|
||||
|
||||
function Aggregation(query, options, queryRewriter, queryRewriteData, params) {
|
||||
BaseOverviewsDataview.call(this, query, options, BaseDataview, queryRewriter, queryRewriteData, params);
|
||||
|
||||
this.query = query;
|
||||
this.column = options.column;
|
||||
this.aggregation = options.aggregation;
|
||||
this.aggregationColumn = options.aggregationColumn;
|
||||
}
|
||||
|
||||
Aggregation.prototype = Object.create(BaseOverviewsDataview.prototype);
|
||||
Aggregation.prototype.constructor = Aggregation;
|
||||
|
||||
module.exports = Aggregation;
|
||||
|
||||
Aggregation.prototype.sql = function(psql, filters, override, callback) {
|
||||
if (!callback) {
|
||||
callback = override;
|
||||
override = {};
|
||||
}
|
||||
|
||||
var _query = this.rewrittenQuery(this.query);
|
||||
|
||||
var aggregationSql;
|
||||
if (!!override.ownFilter) {
|
||||
aggregationSql = [
|
||||
"WITH",
|
||||
[
|
||||
summaryQueryTpl({
|
||||
_query: _query,
|
||||
_column: this.column
|
||||
}),
|
||||
rankedCategoriesQueryTpl({
|
||||
_query: _query,
|
||||
_column: this.column,
|
||||
_aggregation: this.getAggregationSql()
|
||||
}),
|
||||
categoriesSummaryQueryTpl({
|
||||
_query: _query,
|
||||
_column: this.column
|
||||
})
|
||||
].join(',\n'),
|
||||
aggregationQueryTpl({
|
||||
_query: _query,
|
||||
_column: this.column,
|
||||
_aggregation: this.getAggregationSql(),
|
||||
_limit: CATEGORIES_LIMIT
|
||||
})
|
||||
].join('\n');
|
||||
} else {
|
||||
aggregationSql = [
|
||||
"WITH",
|
||||
[
|
||||
summaryQueryTpl({
|
||||
_query: _query,
|
||||
_column: this.column
|
||||
}),
|
||||
rankedCategoriesQueryTpl({
|
||||
_query: _query,
|
||||
_column: this.column,
|
||||
_aggregation: this.getAggregationSql()
|
||||
}),
|
||||
categoriesSummaryQueryTpl({
|
||||
_query: _query,
|
||||
_column: this.column
|
||||
})
|
||||
].join(',\n'),
|
||||
rankedAggregationQueryTpl({
|
||||
_query: _query,
|
||||
_column: this.column,
|
||||
_limit: CATEGORIES_LIMIT
|
||||
})
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
return callback(null, aggregationSql);
|
||||
};
|
||||
|
||||
var aggregationFnQueryTpl = {
|
||||
count: dot.template('sum(_feature_count)'),
|
||||
sum: dot.template('sum({{=it._aggregationColumn}}*_feature_count)')
|
||||
};
|
||||
|
||||
Aggregation.prototype.getAggregationSql = function() {
|
||||
return aggregationFnQueryTpl[this.aggregation]({
|
||||
_aggregationFn: this.aggregation,
|
||||
_aggregationColumn: this.aggregationColumn || 1
|
||||
});
|
||||
};
|
||||
88
lib/cartodb/models/dataview/overviews/base.js
Normal file
88
lib/cartodb/models/dataview/overviews/base.js
Normal file
@@ -0,0 +1,88 @@
|
||||
var _ = require('underscore');
|
||||
var BaseDataview = require('../base');
|
||||
|
||||
function BaseOverviewsDataview(query, queryOptions, BaseDataview, queryRewriter, queryRewriteData, options) {
|
||||
this.BaseDataview = BaseDataview;
|
||||
this.query = query;
|
||||
this.queryOptions = queryOptions;
|
||||
this.queryRewriter = queryRewriter;
|
||||
this.queryRewriteData = queryRewriteData;
|
||||
this.options = options;
|
||||
this.baseDataview = new this.BaseDataview(this.query, this.queryOptions);
|
||||
}
|
||||
|
||||
module.exports = BaseOverviewsDataview;
|
||||
|
||||
BaseOverviewsDataview.prototype = new BaseDataview();
|
||||
BaseOverviewsDataview.prototype.constructor = BaseOverviewsDataview;
|
||||
|
||||
// TODO: parameterized these settings
|
||||
var SETTINGS = {
|
||||
// use overviews as a default fallback strategy
|
||||
defaultOverviews: false,
|
||||
|
||||
// minimum ratio of bounding box size to grid size
|
||||
// (this would ideally be based on the viewport size in pixels)
|
||||
zoomLevelFactor: 1024.0
|
||||
};
|
||||
|
||||
// Compute zoom level so that the the resolution grid size of the
|
||||
// selected overview is smaller (zoomLevelFactor times smaller at least)
|
||||
// than the bounding box size.
|
||||
BaseOverviewsDataview.prototype.zoomLevelForBbox = function(bbox) {
|
||||
var pxPerTile = 256.0;
|
||||
var earthWidth = 360.0;
|
||||
// TODO: now we assume overviews are computed for 1-pixel tolerance;
|
||||
// should use extended overviews metadata to compute this properly.
|
||||
if ( bbox ) {
|
||||
var bboxValues = _.map(bbox.split(','), function(v) { return +v; });
|
||||
var w = Math.abs(bboxValues[2]-bboxValues[0]);
|
||||
var h = Math.abs(bboxValues[3]-bboxValues[1]);
|
||||
var maxDim = Math.min(w, h);
|
||||
|
||||
// Find minimum suitable z
|
||||
// note that the QueryRewirter will use the minimum level overview
|
||||
// of level >= z if it exists, and otherwise the base table
|
||||
var z = Math.ceil(-Math.log(maxDim*pxPerTile/earthWidth/SETTINGS.zoomLevelFactor)/Math.log(2.0));
|
||||
return Math.max(z, 0);
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
BaseOverviewsDataview.prototype.rewrittenQuery = function(query) {
|
||||
var zoom_level = this.zoomLevelForBbox(this.options.bbox);
|
||||
return this.queryRewriter.query(query, this.queryRewriteData, { zoom_level: zoom_level });
|
||||
};
|
||||
|
||||
// Default behaviour
|
||||
BaseOverviewsDataview.prototype.defaultSql = function(psql, filters, override, callback) {
|
||||
var query = this.query;
|
||||
var dataview = this.baseDataview;
|
||||
if ( SETTINGS.defaultOverviews ) {
|
||||
query = this.rewrittenQuery(query);
|
||||
dataview = new this.BaseDataview(query, this.queryOptions);
|
||||
}
|
||||
return dataview.sql(psql, filters, override, callback);
|
||||
};
|
||||
|
||||
// default implementation that can be override in derived classes:
|
||||
|
||||
BaseOverviewsDataview.prototype.sql = function(psql, filters, override, callback) {
|
||||
return this.defaultSql(psql, filters, override, callback);
|
||||
};
|
||||
|
||||
BaseOverviewsDataview.prototype.search = function(psql, userQuery, callback) {
|
||||
return this.baseDataview.search(psql, userQuery, callback);
|
||||
};
|
||||
|
||||
BaseOverviewsDataview.prototype.format = function(result) {
|
||||
return this.baseDataview.format(result);
|
||||
};
|
||||
|
||||
BaseOverviewsDataview.prototype.getType = function() {
|
||||
return this.baseDataview.getType();
|
||||
};
|
||||
|
||||
BaseOverviewsDataview.prototype.toString = function() {
|
||||
return this.baseDataview.toString();
|
||||
};
|
||||
@@ -1,9 +1,6 @@
|
||||
var _ = require('underscore');
|
||||
var BaseWidget = require('../base');
|
||||
var BaseOverviewsDataview = require('./base');
|
||||
var BaseDataview = require('../formula');
|
||||
|
||||
var debug = require('debug')('windshaft:widget:formula:overviews');
|
||||
|
||||
var dot = require('dot');
|
||||
dot.templateSettings.strip = false;
|
||||
|
||||
@@ -29,76 +26,31 @@ var formulaQueryTpls = {
|
||||
};
|
||||
|
||||
function Formula(query, options, queryRewriter, queryRewriteData, params) {
|
||||
this.base_dataview = new BaseDataview(query, options);
|
||||
this.query = query;
|
||||
BaseOverviewsDataview.call(this, query, options, BaseDataview, queryRewriter, queryRewriteData, params);
|
||||
this.column = options.column || '1';
|
||||
this.operation = options.operation;
|
||||
this.queryRewriter = queryRewriter;
|
||||
this.queryRewriteData = queryRewriteData;
|
||||
this.options = params;
|
||||
}
|
||||
|
||||
Formula.prototype = new BaseWidget();
|
||||
Formula.prototype = Object.create(BaseOverviewsDataview.prototype);
|
||||
Formula.prototype.constructor = Formula;
|
||||
|
||||
module.exports = Formula;
|
||||
|
||||
var zoom_level_factor = 100.0;
|
||||
|
||||
// Compute zoom level so that the the resolution grid size of the
|
||||
// selected overview is smaller (zoom_level_factor times smaller at least)
|
||||
// than the bounding box size.
|
||||
function zoom_level_for_bbox(bbox) {
|
||||
var px_per_tile = 256.0;
|
||||
var earth_width = 360.0;
|
||||
// TODO: now we assume overviews are computed for 1-pixel tolerance;
|
||||
// should use extended overviews metadata to compute this properly.
|
||||
if ( bbox ) {
|
||||
var bbox_values = _.map(bbox.split(','), function(v) { return +v; });
|
||||
var w = Math.abs(bbox_values[2]-bbox_values[0]);
|
||||
var h = Math.abs(bbox_values[3]-bbox_values[1]);
|
||||
var max_dim = Math.min(w, h);
|
||||
|
||||
// Find minimum suitable z
|
||||
// note that the QueryRewirter will use the minimum level overview
|
||||
// of level >= z if it exists, and otherwise the base table
|
||||
var z = Math.ceil(-Math.log(max_dim*px_per_tile/earth_width/zoom_level_factor)/Math.log(2.0));
|
||||
return Math.max(z, 0);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
Formula.prototype.sql = function(psql, filters, override, callback) {
|
||||
var _query = this.query;
|
||||
var formulaQueryTpl = formulaQueryTpls[this.operation];
|
||||
|
||||
if ( formulaQueryTpl ) {
|
||||
// supported formula for use with overviews
|
||||
var zoom_level = zoom_level_for_bbox(this.options.bbox);
|
||||
_query = this.queryRewriter.query(_query, this.queryRewriteData, { zoom_level: zoom_level });
|
||||
var formulaSql = formulaQueryTpl({
|
||||
_query: _query,
|
||||
_query: this.rewrittenQuery(this.query),
|
||||
_operation: this.operation,
|
||||
_column: this.column
|
||||
_column: this.column
|
||||
});
|
||||
debug(formulaSql);
|
||||
callback = callback || override;
|
||||
|
||||
return callback(null, formulaSql);
|
||||
}
|
||||
|
||||
// For non supported operations (min, max) we're not using overviews.
|
||||
return this.base_dataview.sql(psql, filters, override, callback);
|
||||
};
|
||||
|
||||
Formula.prototype.format = function(result) {
|
||||
return this.base_dataview.format(result);
|
||||
};
|
||||
|
||||
Formula.prototype.getType = function() {
|
||||
return this.base_dataview.getType();
|
||||
};
|
||||
|
||||
Formula.prototype.toString = function() {
|
||||
return this.base_dataview.toString();
|
||||
// default behaviour
|
||||
return this.defaultSql(psql, filters, override, callback);
|
||||
};
|
||||
|
||||
217
lib/cartodb/models/dataview/overviews/histogram.js
Normal file
217
lib/cartodb/models/dataview/overviews/histogram.js
Normal file
@@ -0,0 +1,217 @@
|
||||
var _ = require('underscore');
|
||||
var BaseOverviewsDataview = require('./base');
|
||||
var BaseDataview = require('../histogram');
|
||||
|
||||
var dot = require('dot');
|
||||
dot.templateSettings.strip = false;
|
||||
|
||||
var columnTypeQueryTpl = dot.template(
|
||||
'SELECT pg_typeof({{=it.column}})::oid FROM ({{=it.query}}) _cdb_histogram_column_type limit 1'
|
||||
);
|
||||
var columnCastTpl = dot.template("date_part('epoch', {{=it.column}})");
|
||||
|
||||
var BIN_MIN_NUMBER = 6;
|
||||
var BIN_MAX_NUMBER = 48;
|
||||
|
||||
var basicsQueryTpl = dot.template([
|
||||
'basics AS (',
|
||||
' SELECT',
|
||||
' max({{=it._column}}) AS max_val, min({{=it._column}}) AS min_val,',
|
||||
' sum({{=it._column}}*_feature_count)/sum(_feature_count) AS avg_val, sum(_feature_count) AS total_rows',
|
||||
' FROM ({{=it._query}}) _cdb_basics',
|
||||
')'
|
||||
].join(' \n'));
|
||||
|
||||
var overrideBasicsQueryTpl = dot.template([
|
||||
'basics AS (',
|
||||
' SELECT',
|
||||
' max({{=it._end}}) AS max_val, min({{=it._start}}) AS min_val,',
|
||||
' sum({{=it._column}}*_feature_count)/sum(_feature_count) AS avg_val, sum(_feature_count) AS total_rows',
|
||||
' FROM ({{=it._query}}) _cdb_basics',
|
||||
')'
|
||||
].join('\n'));
|
||||
|
||||
var iqrQueryTpl = dot.template([
|
||||
'iqrange AS (',
|
||||
' SELECT max(quartile_max) - min(quartile_max) AS 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 ({{=it._query}}) _cdb_rank) _cdb_quartiles',
|
||||
' WHERE quartile = 1 or quartile = 3',
|
||||
' GROUP BY quartile',
|
||||
' ) _cdb_iqr',
|
||||
')'
|
||||
].join('\n'));
|
||||
|
||||
var binsQueryTpl = dot.template([
|
||||
'bins AS (',
|
||||
' SELECT CASE WHEN total_rows = 0 OR iqr = 0',
|
||||
' THEN 1',
|
||||
' ELSE GREATEST(',
|
||||
' LEAST({{=it._minBins}}, CAST(total_rows AS INT)),',
|
||||
' LEAST(',
|
||||
' CAST(((max_val - min_val) / (2 * iqr * power(total_rows, 1/3))) AS INT),',
|
||||
' {{=it._maxBins}}',
|
||||
' )',
|
||||
' )',
|
||||
' END AS bins_number',
|
||||
' FROM basics, iqrange, ({{=it._query}}) _cdb_bins',
|
||||
' LIMIT 1',
|
||||
')'
|
||||
].join('\n'));
|
||||
|
||||
var overrideBinsQueryTpl = dot.template([
|
||||
'bins AS (',
|
||||
' SELECT {{=it._bins}} AS bins_number',
|
||||
')'
|
||||
].join('\n'));
|
||||
|
||||
var nullsQueryTpl = dot.template([
|
||||
'nulls AS (',
|
||||
' SELECT',
|
||||
' count(*) AS nulls_count',
|
||||
' FROM ({{=it._query}}) _cdb_histogram_nulls',
|
||||
' WHERE {{=it._column}} IS NULL',
|
||||
')'
|
||||
].join('\n'));
|
||||
|
||||
var histogramQueryTpl = dot.template([
|
||||
'SELECT',
|
||||
' (max_val - min_val) / cast(bins_number as float) AS bin_width,',
|
||||
' bins_number,',
|
||||
' nulls_count,',
|
||||
' avg_val,',
|
||||
' CASE WHEN min_val = max_val',
|
||||
' THEN 0',
|
||||
' ELSE GREATEST(1, LEAST(WIDTH_BUCKET({{=it._column}}, min_val, max_val, bins_number), bins_number)) - 1',
|
||||
' END AS bin,',
|
||||
' min({{=it._column}})::numeric AS min,',
|
||||
' max({{=it._column}})::numeric AS max,',
|
||||
' sum({{=it._column}}*_feature_count)/sum(_feature_count)::numeric AS avg,',
|
||||
' sum(_feature_count) AS freq',
|
||||
'FROM ({{=it._query}}) _cdb_histogram, basics, nulls, bins',
|
||||
'WHERE {{=it._column}} IS NOT NULL',
|
||||
'GROUP BY bin, bins_number, bin_width, nulls_count, avg_val',
|
||||
'ORDER BY bin'
|
||||
].join('\n'));
|
||||
|
||||
function Histogram(query, options, queryRewriter, queryRewriteData, params) {
|
||||
BaseOverviewsDataview.call(this, query, options, BaseDataview, queryRewriter, queryRewriteData, params);
|
||||
|
||||
this.query = query;
|
||||
this.column = options.column;
|
||||
this.bins = options.bins;
|
||||
|
||||
this._columnType = null;
|
||||
}
|
||||
|
||||
Histogram.prototype = Object.create(BaseOverviewsDataview.prototype);
|
||||
Histogram.prototype.constructor = Histogram;
|
||||
|
||||
module.exports = Histogram;
|
||||
|
||||
|
||||
var DATE_OIDS = {
|
||||
1082: true,
|
||||
1114: true,
|
||||
1184: true
|
||||
};
|
||||
|
||||
Histogram.prototype.sql = function(psql, override, callback) {
|
||||
if (!callback) {
|
||||
callback = override;
|
||||
override = {};
|
||||
}
|
||||
|
||||
var self = this;
|
||||
|
||||
var _column = this.column;
|
||||
|
||||
var columnTypeQuery = columnTypeQueryTpl({
|
||||
column: _column, query: this.rewrittenQuery(this.query)
|
||||
});
|
||||
|
||||
if (this._columnType === null) {
|
||||
psql.query(columnTypeQuery, function(err, result) {
|
||||
// assume numeric, will fail later
|
||||
self._columnType = 'numeric';
|
||||
if (!err && !!result.rows[0]) {
|
||||
var pgType = result.rows[0].pg_typeof;
|
||||
if (DATE_OIDS.hasOwnProperty(pgType)) {
|
||||
self._columnType = 'date';
|
||||
}
|
||||
}
|
||||
self.sql(psql, override, callback);
|
||||
}, true); // use read-only transaction
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this._columnType === 'date') {
|
||||
_column = columnCastTpl({column: _column});
|
||||
}
|
||||
|
||||
var _query = this.rewrittenQuery(this.query);
|
||||
|
||||
var basicsQuery, binsQuery;
|
||||
|
||||
if (override && _.has(override, 'start') && _.has(override, 'end') && _.has(override, 'bins')) {
|
||||
basicsQuery = overrideBasicsQueryTpl({
|
||||
_query: _query,
|
||||
_column: _column,
|
||||
_start: override.start,
|
||||
_end: override.end
|
||||
});
|
||||
|
||||
binsQuery = [
|
||||
overrideBinsQueryTpl({
|
||||
_bins: override.bins
|
||||
})
|
||||
].join(',\n');
|
||||
} else {
|
||||
basicsQuery = basicsQueryTpl({
|
||||
_query: _query,
|
||||
_column: _column
|
||||
});
|
||||
|
||||
if (override && _.has(override, 'bins')) {
|
||||
binsQuery = [
|
||||
overrideBinsQueryTpl({
|
||||
_bins: override.bins
|
||||
})
|
||||
].join(',\n');
|
||||
} else {
|
||||
binsQuery = [
|
||||
iqrQueryTpl({
|
||||
_query: _query,
|
||||
_column: _column
|
||||
}),
|
||||
binsQueryTpl({
|
||||
_query: _query,
|
||||
_minBins: BIN_MIN_NUMBER,
|
||||
_maxBins: BIN_MAX_NUMBER
|
||||
})
|
||||
].join(',\n');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var histogramSql = [
|
||||
"WITH",
|
||||
[
|
||||
basicsQuery,
|
||||
binsQuery,
|
||||
nullsQueryTpl({
|
||||
_query: _query,
|
||||
_column: _column
|
||||
})
|
||||
].join(',\n'),
|
||||
histogramQueryTpl({
|
||||
_query: _query,
|
||||
_column: _column
|
||||
})
|
||||
].join('\n');
|
||||
|
||||
return callback(null, histogramSql);
|
||||
};
|
||||
@@ -1,3 +1,6 @@
|
||||
module.exports = {
|
||||
Formula: require('./formula')
|
||||
Aggregation: require('./aggregation'),
|
||||
Formula: require('./formula'),
|
||||
Histogram: require('./histogram'),
|
||||
List: require('./list')
|
||||
};
|
||||
|
||||
11
lib/cartodb/models/dataview/overviews/list.js
Normal file
11
lib/cartodb/models/dataview/overviews/list.js
Normal file
@@ -0,0 +1,11 @@
|
||||
var BaseOverviewsDataview = require('./base');
|
||||
var BaseDataview = require('../list');
|
||||
|
||||
function List(query, options, queryRewriter, queryRewriteData, params) {
|
||||
BaseOverviewsDataview.call(this, query, options, BaseDataview, queryRewriter, queryRewriteData, params);
|
||||
}
|
||||
|
||||
List.prototype = Object.create(BaseOverviewsDataview.prototype);
|
||||
List.prototype.constructor = List;
|
||||
|
||||
module.exports = List;
|
||||
2
npm-shrinkwrap.json
generated
2
npm-shrinkwrap.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "windshaft-cartodb",
|
||||
"version": "2.44.0",
|
||||
"version": "2.44.1",
|
||||
"dependencies": {
|
||||
"body-parser": {
|
||||
"version": "1.14.2",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "windshaft-cartodb",
|
||||
"version": "2.44.0",
|
||||
"version": "2.44.1",
|
||||
"description": "A map tile server for CartoDB",
|
||||
"keywords": [
|
||||
"cartodb"
|
||||
|
||||
@@ -5,6 +5,7 @@ OPT_CREATE_PGSQL=yes # create the PostgreSQL test environment
|
||||
OPT_DROP_REDIS=yes # drop the redis test environment
|
||||
OPT_DROP_PGSQL=yes # drop the PostgreSQL test environment
|
||||
OPT_COVERAGE=no # run tests with coverage
|
||||
OPT_DOWNLOAD_SQL=yes # download a fresh copy of sql files
|
||||
|
||||
export PGAPPNAME=cartodb_tiler_tester
|
||||
|
||||
@@ -73,6 +74,10 @@ while [ -n "$1" ]; do
|
||||
OPT_CREATE_REDIS=no
|
||||
shift
|
||||
continue
|
||||
elif test "$1" = "--no-sql-download"; then
|
||||
OPT_DOWNLOAD_SQL=no
|
||||
shift
|
||||
continue
|
||||
elif test "$1" = "--with-coverage"; then
|
||||
OPT_COVERAGE=yes
|
||||
shift
|
||||
@@ -113,6 +118,9 @@ fi
|
||||
if test x"$OPT_CREATE_REDIS" != xyes; then
|
||||
PREPARE_DB_OPTS="$PREPARE_DB_OPTS --skip-redis"
|
||||
fi
|
||||
if test x"$OPT_DOWNLOAD_SQL" != xyes; then
|
||||
PREPARE_DB_OPTS="$PREPARE_DB_OPTS --no-sql-download"
|
||||
fi
|
||||
|
||||
echo "Preparing the environment"
|
||||
cd ${BASEDIR}/test/support
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
PREPARE_REDIS=yes
|
||||
PREPARE_PGSQL=yes
|
||||
DOWNLOAD_SQL_FILES=yes
|
||||
|
||||
while [ -n "$1" ]; do
|
||||
if test "$1" = "--skip-pg"; then
|
||||
@@ -20,6 +21,9 @@ while [ -n "$1" ]; do
|
||||
elif test "$1" = "--skip-redis"; then
|
||||
PREPARE_REDIS=no
|
||||
shift; continue
|
||||
elif test "$1" = "--no-sql-download"; then
|
||||
DOWNLOAD_SQL_FILES=no
|
||||
shift; continue
|
||||
fi
|
||||
done
|
||||
|
||||
@@ -71,26 +75,31 @@ if test x"$PREPARE_PGSQL" = xyes; then
|
||||
dropdb "${TEST_DB}"
|
||||
createdb -Ttemplate_postgis -EUTF8 "${TEST_DB}" || die "Could not create test database"
|
||||
|
||||
curl -L -s https://raw.githubusercontent.com/CartoDB/camshaft/master/test/fixtures/cdb_analysis_catalog.sql -o sql/cdb_analysis_catalog.sql
|
||||
cat sql/cdb_analysis_catalog.sql | psql -v ON_ERROR_STOP=1 ${TEST_DB} || exit 1
|
||||
LOCAL_SQL_SCRIPTS='windshaft.test gadm4 ported/populated_places_simple_reduced'
|
||||
REMOTE_SQL_SCRIPTS='CDB_QueryStatements CDB_QueryTables CDB_CartodbfyTable CDB_TableMetadata CDB_ForeignTable CDB_UserTables CDB_ColumnNames CDB_AnalysisCatalog CDB_ZoomFromScale CDB_Overviews CDB_QuantileBins CDB_JenksBins CDB_HeadsTailsBins CDB_EqualIntervalBins CDB_Hexagon CDB_XYZ'
|
||||
|
||||
cat sql/windshaft.test.sql sql/gadm4.sql |
|
||||
sed "s/:PUBLICUSER/${PUBLICUSER}/" |
|
||||
sed "s/:PUBLICPASS/${PUBLICPASS}/" |
|
||||
sed "s/:TESTUSER/${TESTUSER}/" |
|
||||
sed "s/:TESTPASS/${TESTPASS}/" |
|
||||
psql -v ON_ERROR_STOP=1 ${TEST_DB} || exit 1
|
||||
|
||||
cat sql/_CDB_QueryStatements.sql | psql -v ON_ERROR_STOP=1 ${TEST_DB} || exit 1
|
||||
|
||||
SQL_SCRIPTS='CDB_QueryTables CDB_CartodbfyTable CDB_TableMetadata CDB_ForeignTable CDB_UserTables CDB_ColumnNames CDB_ZoomFromScale CDB_Overviews CDB_QuantileBins CDB_JenksBins CDB_HeadsTailsBins CDB_EqualIntervalBins CDB_Hexagon CDB_XYZ'
|
||||
for i in ${SQL_SCRIPTS}
|
||||
CURL_ARGS=""
|
||||
for i in ${REMOTE_SQL_SCRIPTS}
|
||||
do
|
||||
curl -L -s https://github.com/CartoDB/cartodb-postgresql/raw/master/scripts-available/$i.sql -o sql/$i.sql
|
||||
cat sql/$i.sql | sed -e 's/cartodb\./public./g' -e "s/''cartodb''/''public''/g" \
|
||||
| psql -v ON_ERROR_STOP=1 ${TEST_DB} || exit 1
|
||||
CURL_ARGS="${CURL_ARGS}\"https://github.com/CartoDB/cartodb-postgresql/raw/master/scripts-available/$i.sql\" -o sql/$i.sql "
|
||||
done
|
||||
if test x"$DOWNLOAD_SQL_FILES" = xyes; then
|
||||
echo "Downloading and updating: ${REMOTE_SQL_SCRIPTS}"
|
||||
echo ${CURL_ARGS} | xargs curl -L -s
|
||||
fi
|
||||
|
||||
psql -c "CREATE EXTENSION IF NOT EXISTS plpythonu;" ${TEST_DB}
|
||||
ALL_SQL_SCRIPTS="${REMOTE_SQL_SCRIPTS} ${LOCAL_SQL_SCRIPTS}"
|
||||
for i in ${ALL_SQL_SCRIPTS}
|
||||
do
|
||||
cat sql/${i}.sql |
|
||||
sed -e 's/cartodb\./public./g' -e "s/''cartodb''/''public''/g" |
|
||||
sed "s/:PUBLICUSER/${PUBLICUSER}/" |
|
||||
sed "s/:PUBLICPASS/${PUBLICPASS}/" |
|
||||
sed "s/:TESTUSER/${TESTUSER}/" |
|
||||
sed "s/:TESTPASS/${TESTPASS}/" |
|
||||
PGOPTIONS='--client-min-messages=WARNING' psql -q -v ON_ERROR_STOP=1 ${TEST_DB} > /dev/null || exit 1
|
||||
done
|
||||
fi
|
||||
|
||||
if test x"$PREPARE_REDIS" = xyes; then
|
||||
@@ -122,17 +131,3 @@ EOF
|
||||
fi
|
||||
|
||||
echo "Finished preparing data. Ready to run tests"
|
||||
|
||||
|
||||
############################ WINDSHAFT TESTS ############################
|
||||
|
||||
echo "...Configuring Windshaft test database"
|
||||
|
||||
cat sql/ported/populated_places_simple_reduced.sql |
|
||||
sed "s/:PUBLICUSER/${PUBLICUSER}/" |
|
||||
sed "s/:PUBLICPASS/${PUBLICPASS}/" |
|
||||
sed "s/:TESTUSER/${TESTUSER}/" |
|
||||
sed "s/:TESTPASS/${TESTPASS}/" |
|
||||
psql -v ON_ERROR_STOP=1 ${TEST_DB} || exit 1
|
||||
|
||||
echo "...Test database configuration complete"
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
-- DUMMY IMPLEMENTATION
|
||||
-- Ref: https://github.com/CartoDB/cartodb-postgresql/blob/master/scripts-available/CDB_QueryStatements.sql
|
||||
-- Originally implemented in plpython for performance reasons
|
||||
|
||||
-- Return an array of statements found in the given query text
|
||||
--
|
||||
-- Regexp curtesy of Hubert Lubaczewski (depesz)
|
||||
--
|
||||
CREATE OR REPLACE FUNCTION CDB_QueryStatements(query text)
|
||||
RETURNS SETOF TEXT AS $$
|
||||
with matches as (
|
||||
select regexp_matches($1, $regexp$((?:[^'"$;]+|"[^"]*"|'[^']*'|(\$[^$]*\$).*?\2)+)$regexp$, 'g') as m
|
||||
)
|
||||
select btrim(m[1]) from matches
|
||||
$$
|
||||
LANGUAGE SQL IMMUTABLE STRICT;
|
||||
Reference in New Issue
Block a user