492 lines
18 KiB
JavaScript
492 lines
18 KiB
JavaScript
'use strict';
|
|
|
|
require('../../support/test-helper');
|
|
var assert = require('../../support/assert');
|
|
var TestClient = require('../../support/test-client');
|
|
|
|
describe('aggregations happy cases', function () {
|
|
afterEach(function (done) {
|
|
if (this.testClient) {
|
|
this.testClient.drain(done);
|
|
} else {
|
|
done();
|
|
}
|
|
});
|
|
|
|
function aggregationOperationMapConfig (operation, query, column, aggregationColumn) {
|
|
column = column || 'adm0name';
|
|
aggregationColumn = aggregationColumn || 'pop_max';
|
|
query = query || 'select * from populated_places_simple_reduced';
|
|
|
|
var mapConfig = {
|
|
version: '1.5.0',
|
|
layers: [
|
|
{
|
|
type: 'mapnik',
|
|
options: {
|
|
sql: query,
|
|
cartocss: '#layer0 { marker-fill: red; marker-width: 10; }',
|
|
cartocss_version: '2.0.1',
|
|
widgets: {}
|
|
}
|
|
}
|
|
]
|
|
};
|
|
|
|
mapConfig.layers[0].options.widgets[column] = {
|
|
type: 'aggregation',
|
|
options: {
|
|
column: column,
|
|
aggregation: operation,
|
|
aggregationColumn: aggregationColumn
|
|
}
|
|
};
|
|
|
|
return mapConfig;
|
|
}
|
|
|
|
var operations = ['count', 'sum', 'avg', 'max', 'min'];
|
|
|
|
operations.forEach(function (operation) {
|
|
it('should be able to use "' + operation + '" as aggregation operation', function (done) {
|
|
this.testClient = new TestClient(aggregationOperationMapConfig(operation));
|
|
this.testClient.getDataview('adm0name', { own_filter: 0 }, function (err, aggregation) {
|
|
assert.ok(!err, err);
|
|
assert.ok(aggregation);
|
|
|
|
assert.equal(aggregation.type, 'aggregation');
|
|
assert.equal(aggregation.aggregation, operation);
|
|
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
var query = [
|
|
'select 1 as val, \'a\' as cat, ST_Transform(ST_SetSRID(ST_MakePoint(0,0),4326),3857) as the_geom_webmercator',
|
|
'select null, \'b\', ST_Transform(ST_SetSRID(ST_MakePoint(0,1),4326),3857)',
|
|
'select null, \'b\', ST_Transform(ST_SetSRID(ST_MakePoint(1,0),4326),3857)',
|
|
'select null, null, ST_Transform(ST_SetSRID(ST_MakePoint(1,1),4326),3857)'
|
|
].join(' UNION ALL ');
|
|
|
|
operations.forEach(function (operation) {
|
|
var description = 'should handle NULL values in category and aggregation columns using "' +
|
|
operation + '" as aggregation operation';
|
|
|
|
it(description, function (done) {
|
|
this.testClient = new TestClient(aggregationOperationMapConfig(operation, query, 'cat', 'val'));
|
|
this.testClient.getDataview('cat', { own_filter: 0 }, function (err, aggregation) {
|
|
assert.ifError(err);
|
|
|
|
assert.ok(aggregation);
|
|
assert.equal(aggregation.type, 'aggregation');
|
|
assert.ok(aggregation.categories);
|
|
assert.equal(aggregation.categoriesCount, 3);
|
|
assert.equal(aggregation.count, 4);
|
|
assert.equal(aggregation.nulls, 1);
|
|
|
|
var hasNullCategory = false;
|
|
aggregation.categories.forEach(function (category) {
|
|
if (category.category === null) {
|
|
hasNullCategory = true;
|
|
}
|
|
});
|
|
|
|
assert.ok(!hasNullCategory, 'aggregation has category NULL');
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
var operations_and_values = { count: 9, sum: 45, avg: 5, max: 9, min: 1 };
|
|
|
|
var query_other = [
|
|
'select generate_series(1,3) as val, \'other_a\' as cat, NULL as the_geom_webmercator',
|
|
'select generate_series(4,6) as val, \'other_b\' as cat, NULL as the_geom_webmercator',
|
|
'select generate_series(7,9) as val, \'other_c\' as cat, NULL as the_geom_webmercator',
|
|
'select generate_series(10,12) as val, \'category_1\' as cat, NULL as the_geom_webmercator',
|
|
'select generate_series(10,12) as val, \'category_2\' as cat, NULL as the_geom_webmercator',
|
|
'select generate_series(10,12) as val, \'category_3\' as cat, NULL as the_geom_webmercator',
|
|
'select generate_series(10,12) as val, \'category_4\' as cat, NULL as the_geom_webmercator',
|
|
'select generate_series(10,12) as val, \'category_5\' as cat, NULL as the_geom_webmercator'
|
|
].join(' UNION ALL ');
|
|
|
|
Object.keys(operations_and_values).forEach(function (operation) {
|
|
var description = 'should aggregate OTHER category using "' + operation + '"';
|
|
|
|
it(description, function (done) {
|
|
this.testClient = new TestClient(aggregationOperationMapConfig(operation, query_other, 'cat', 'val'));
|
|
this.testClient.getDataview('cat', { own_filter: 0 }, function (err, aggregation) {
|
|
assert.ifError(err);
|
|
|
|
assert.ok(aggregation);
|
|
assert.equal(aggregation.type, 'aggregation');
|
|
assert.ok(aggregation.categories);
|
|
assert.equal(aggregation.categoriesCount, 8);
|
|
assert.equal(aggregation.count, 24);
|
|
assert.equal(aggregation.nulls, 0);
|
|
|
|
var aggregated_categories = aggregation.categories.filter(function (category) {
|
|
return category.agg === true;
|
|
});
|
|
assert.equal(aggregated_categories.length, 1);
|
|
assert.equal(aggregated_categories[0].value, operations_and_values[operation]);
|
|
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
var widgetSearchExpects = {
|
|
count: [{ category: 'other_a', value: 3 }],
|
|
sum: [{ category: 'other_a', value: 6 }],
|
|
avg: [{ category: 'other_a', value: 2 }],
|
|
max: [{ category: 'other_a', value: 3 }],
|
|
min: [{ category: 'other_a', value: 1 }]
|
|
};
|
|
|
|
Object.keys(operations_and_values).forEach(function (operation) {
|
|
var description = 'should search OTHER category using "' + operation + '"';
|
|
|
|
it(description, function (done) {
|
|
this.testClient = new TestClient(aggregationOperationMapConfig(operation, query_other, 'cat', 'val'));
|
|
this.testClient.widgetSearch('cat', 'other_a', function (err, res, searchResult) {
|
|
assert.ifError(err);
|
|
|
|
assert.ok(searchResult);
|
|
assert.equal(searchResult.type, 'aggregation');
|
|
|
|
assert.equal(searchResult.categories.length, 1);
|
|
assert.deepEqual(
|
|
searchResult.categories,
|
|
widgetSearchExpects[operation]
|
|
);
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('aggregation-dataview: special float values', function () {
|
|
afterEach(function (done) {
|
|
if (this.testClient) {
|
|
this.testClient.drain(done);
|
|
} else {
|
|
done();
|
|
}
|
|
});
|
|
|
|
function createMapConfig (layers, dataviews, analysis) {
|
|
return {
|
|
version: '1.5.0',
|
|
layers: layers,
|
|
dataviews: dataviews || {},
|
|
analyses: analysis || []
|
|
};
|
|
}
|
|
|
|
var mapConfig = createMapConfig(
|
|
[
|
|
{
|
|
type: 'cartodb',
|
|
options: {
|
|
source: {
|
|
id: 'a0'
|
|
},
|
|
cartocss: '#points { marker-width: 10; marker-fill: red; }',
|
|
cartocss_version: '2.3.0'
|
|
}
|
|
}
|
|
],
|
|
{
|
|
val_aggregation: {
|
|
source: {
|
|
id: 'a0'
|
|
},
|
|
type: 'aggregation',
|
|
options: {
|
|
column: 'cat',
|
|
aggregation: 'avg',
|
|
aggregationColumn: 'val'
|
|
}
|
|
},
|
|
sum_aggregation_numeric: {
|
|
source: {
|
|
id: 'a1'
|
|
},
|
|
type: 'aggregation',
|
|
options: {
|
|
column: 'cat',
|
|
aggregation: 'sum',
|
|
aggregationColumn: 'val'
|
|
}
|
|
}
|
|
},
|
|
[
|
|
{
|
|
id: 'a0',
|
|
type: 'source',
|
|
params: {
|
|
query: [
|
|
'SELECT',
|
|
' null::geometry the_geom_webmercator,',
|
|
' CASE',
|
|
' WHEN x % 4 = 0 THEN \'infinity\'::float',
|
|
' WHEN x % 4 = 1 THEN \'-infinity\'::float',
|
|
' WHEN x % 4 = 2 THEN \'NaN\'::float',
|
|
' ELSE x',
|
|
' END AS val,',
|
|
' CASE',
|
|
' WHEN x % 2 = 0 THEN \'category_1\'',
|
|
' ELSE \'category_2\'',
|
|
' END AS cat',
|
|
'FROM generate_series(1, 1000) x'
|
|
].join('\n')
|
|
}
|
|
}, {
|
|
id: 'a1',
|
|
type: 'source',
|
|
params: {
|
|
query: [
|
|
'SELECT',
|
|
' null::geometry the_geom_webmercator,',
|
|
' CASE',
|
|
' WHEN x % 3 = 0 THEN \'NaN\'::numeric',
|
|
' WHEN x % 3 = 1 THEN x',
|
|
' ELSE x',
|
|
' END AS val,',
|
|
' CASE',
|
|
' WHEN x % 2 = 0 THEN \'category_1\'',
|
|
' ELSE \'category_2\'',
|
|
' END AS cat',
|
|
'FROM generate_series(1, 1000) x'
|
|
].join('\n')
|
|
}
|
|
}
|
|
]
|
|
);
|
|
|
|
// Source a0
|
|
// -----------------------------------------------
|
|
// the_geom_webmercator | val | cat
|
|
// ----------------------+-----------+------------
|
|
// | -Infinity | category_2
|
|
// | NaN | category_1
|
|
// | 3 | category_2
|
|
// | Infinity | category_1
|
|
// | -Infinity | category_2
|
|
// | NaN | category_1
|
|
// | 7 | category_2
|
|
// | Infinity | category_1
|
|
// | -Infinity | category_2
|
|
// | NaN | category_1
|
|
// | 11 | category_2
|
|
// | " | "
|
|
|
|
var filters = [{ own_filter: 0 }, {}];
|
|
filters.forEach(function (filter) {
|
|
it('should handle special float values using filter: ' + JSON.stringify(filter), function (done) {
|
|
this.testClient = new TestClient(mapConfig, 1234);
|
|
this.testClient.getDataview('val_aggregation', filter, function (err, dataview) {
|
|
assert.ifError(err);
|
|
assert.ok(dataview.infinities === (250 + 250));
|
|
assert.ok(dataview.nans === 250);
|
|
assert.ok(dataview.categories.length === 1);
|
|
dataview.categories.forEach(function (category) {
|
|
assert.ok(category.category === 'category_2');
|
|
assert.ok(category.value === 501);
|
|
});
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('should handle special numeric values using filter: ' + JSON.stringify(filter), function (done) {
|
|
this.testClient = new TestClient(mapConfig, 1234);
|
|
this.testClient.getDataview('sum_aggregation_numeric', filter, function (err, dataview) {
|
|
assert.ifError(err);
|
|
assert.ok(dataview.nans === 333);
|
|
assert.ok(dataview.categories.length === 2);
|
|
dataview.categories.forEach(function (category) {
|
|
assert.ok(category.value !== null);
|
|
});
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('aggregation dataview tuned by categories query param', function () {
|
|
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: {
|
|
categories: {
|
|
source: {
|
|
id: 'a0'
|
|
},
|
|
type: 'aggregation',
|
|
options: {
|
|
column: 'cat',
|
|
aggregation: 'sum',
|
|
aggregationColumn: 'val'
|
|
}
|
|
}
|
|
},
|
|
analyses: [
|
|
{
|
|
id: 'a0',
|
|
type: 'source',
|
|
params: {
|
|
query: `
|
|
SELECT
|
|
null::geometry the_geom_webmercator,
|
|
CASE
|
|
WHEN x % 4 = 0 THEN 1
|
|
WHEN x % 4 = 1 THEN 2
|
|
WHEN x % 4 = 2 THEN 3
|
|
ELSE 4
|
|
END AS val,
|
|
CASE
|
|
WHEN x % 4 = 0 THEN 'category_1'
|
|
WHEN x % 4 = 1 THEN 'category_2'
|
|
WHEN x % 4 = 2 THEN 'category_3'
|
|
ELSE 'category_4'
|
|
END AS cat
|
|
FROM generate_series(1, 1000) x
|
|
`
|
|
}
|
|
}
|
|
]
|
|
};
|
|
|
|
beforeEach(function () {
|
|
this.testClient = new TestClient(mapConfig, 1234);
|
|
});
|
|
|
|
afterEach(function (done) {
|
|
this.testClient.drain(done);
|
|
});
|
|
|
|
var scenarios = [
|
|
{
|
|
params: { own_filter: 0, categories: -1 },
|
|
categoriesExpected: 4
|
|
},
|
|
{
|
|
params: { own_filter: 0, categories: 0 },
|
|
categoriesExpected: 4
|
|
},
|
|
{
|
|
params: { own_filter: 0, categories: 1 },
|
|
categoriesExpected: 1
|
|
},
|
|
{
|
|
params: { own_filter: 0, categories: 2 },
|
|
categoriesExpected: 2
|
|
},
|
|
{
|
|
params: { own_filter: 0, categories: 4 },
|
|
categoriesExpected: 4
|
|
},
|
|
{
|
|
params: { own_filter: 0, categories: 5 },
|
|
categoriesExpected: 4
|
|
}
|
|
];
|
|
|
|
scenarios.forEach(function (scenario) {
|
|
it(`should handle cartegories to customize aggregations: ${JSON.stringify(scenario.params)}`, function (done) {
|
|
this.testClient.getDataview('categories', scenario.params, (err, dataview) => {
|
|
assert.ifError(err);
|
|
assert.equal(dataview.categories.length, scenario.categoriesExpected);
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Count aggregation', function () {
|
|
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: {
|
|
categories: {
|
|
source: {
|
|
id: 'a0'
|
|
},
|
|
type: 'aggregation',
|
|
options: {
|
|
column: 'cat',
|
|
aggregation: 'count'
|
|
}
|
|
}
|
|
},
|
|
analyses: [
|
|
{
|
|
id: 'a0',
|
|
type: 'source',
|
|
params: {
|
|
query: `
|
|
SELECT
|
|
null::geometry the_geom_webmercator,
|
|
CASE
|
|
WHEN x % 4 = 0 THEN 1
|
|
WHEN x % 4 = 1 THEN 2
|
|
WHEN x % 4 = 2 THEN 3
|
|
ELSE null
|
|
END AS val,
|
|
CASE
|
|
WHEN x % 4 = 0 THEN 'category_1'
|
|
WHEN x % 4 = 1 THEN 'category_2'
|
|
WHEN x % 4 = 2 THEN 'category_3'
|
|
ELSE null
|
|
END AS cat
|
|
FROM generate_series(1, 1000) x
|
|
`
|
|
}
|
|
}
|
|
]
|
|
};
|
|
|
|
it('should handle null values correctly when aggregationColumn isn\'t provided', function (done) {
|
|
this.testClient = new TestClient(mapConfig, 1234);
|
|
this.testClient.getDataview('categories', { own_filter: 0, categories: 0 }, (err, dataview) => {
|
|
assert.ifError(err);
|
|
assert.equal(dataview.categories.length, 3);
|
|
this.testClient.drain(done);
|
|
});
|
|
});
|
|
|
|
it('should handle null values correctly when aggregationColumn is provided', function (done) {
|
|
mapConfig.dataviews.categories.options.aggregationColumn = 'val';
|
|
this.testClient = new TestClient(mapConfig, 1234);
|
|
this.testClient.getDataview('categories', { own_filter: 0, categories: 0 }, (err, dataview) => {
|
|
assert.ifError(err);
|
|
assert.equal(dataview.categories.length, 3);
|
|
this.testClient.drain(done);
|
|
});
|
|
});
|
|
});
|