From 3d8f6576aa3a98ff0ce1f67382304b720d7826e5 Mon Sep 17 00:00:00 2001 From: Javier Goizueta Date: Wed, 18 May 2016 07:48:11 +0200 Subject: [PATCH] Implement category and range filters --- lib/cartodb/models/filter/camshaft.js | 29 ++++++- .../models/filter/camshaft/category.js | 79 +++++++++++++++++++ lib/cartodb/models/filter/camshaft/range.js | 43 ++++++++++ 3 files changed, 149 insertions(+), 2 deletions(-) create mode 100644 lib/cartodb/models/filter/camshaft/category.js create mode 100644 lib/cartodb/models/filter/camshaft/range.js diff --git a/lib/cartodb/models/filter/camshaft.js b/lib/cartodb/models/filter/camshaft.js index 744b9fcd..0eae2681 100644 --- a/lib/cartodb/models/filter/camshaft.js +++ b/lib/cartodb/models/filter/camshaft.js @@ -1,13 +1,38 @@ // this is meant as a hack for development // TODO: reproduce here the filter application of Camshaft -var queryBuilder = require('camshaft/lib/filter/query-builder'); + +var filters = { + category: require('./camshaft/category'), + range: require('./camshaft/range') +}; + +function createFilter(filterDefinition) { + var filterType = filterDefinition.type.toLowerCase(); + if (!filters.hasOwnProperty(filterType)) { + throw new Error('Unknown filter type: ' + filterType); + } + return new filters[filterType](filterDefinition.column, filterDefinition.params); +} function CamshaftFilters(filters) { this.filters = filters; } CamshaftFilters.prototype.sql = function(rawSql) { - return queryBuilder.getSql(rawSql, this.filters); + var filters = this.filters || {}; + var applyFilters = {}; + + return Object.keys(filters) + .filter(function(filterName) { + return applyFilters.hasOwnProperty(filterName) ? applyFilters[filterName] : true; + }) + .map(function(filterName) { + var filterDefinition = filters[filterName]; + return createFilter(filterDefinition); + }) + .reduce(function(sql, filter) { + return filter.sql(sql); + }, rawSql); }; module.exports = CamshaftFilters; diff --git a/lib/cartodb/models/filter/camshaft/category.js b/lib/cartodb/models/filter/camshaft/category.js new file mode 100644 index 00000000..6181de7f --- /dev/null +++ b/lib/cartodb/models/filter/camshaft/category.js @@ -0,0 +1,79 @@ +'use strict'; + +var debug = require('debug')('windshaft:filter:category'); +var dot = require('dot'); +dot.templateSettings.strip = false; + +var filterQueryTpl = dot.template([ + 'SELECT *', + 'FROM ({{=it._sql}}) _camshaft_category_filter', + 'WHERE {{=it._filters}}' +].join('\n')); +var escapeStringTpl = dot.template('$escape_{{=it._i}}${{=it._value}}$escape_{{=it._i}}$'); +var inConditionTpl = dot.template('{{=it._column}} IN ({{=it._values}})'); +var notInConditionTpl = dot.template('{{=it._column}} NOT IN ({{=it._values}})'); + +function Category(column, filterParams) { + this.column = column; + + if (!Array.isArray(filterParams.accept) && !Array.isArray(filterParams.reject)) { + throw new Error('Category filter expects at least one array in accept or reject params'); + } + + if (Array.isArray(filterParams.accept) && Array.isArray(filterParams.reject)) { + if (filterParams.accept.length === 0 && filterParams.reject.length === 0) { + throw new Error( + 'Category filter expects one value either in accept or reject params when both are provided' + ); + } + } + + this.accept = filterParams.accept; + this.reject = filterParams.reject; +} + +module.exports = Category; + +/* + - accept: [] => reject all + - reject: [] => accept all + */ +Category.prototype.sql = function(rawSql) { + var valueFilters = []; + + if (Array.isArray(this.accept)) { + if (this.accept.length > 0) { + valueFilters.push(inConditionTpl({ + _column: this.column, + _values: this.accept.map(function(value, i) { + return Number.isFinite(value) ? value : escapeStringTpl({_i: i, _value: value}); + }).join(',') + })); + } else { + valueFilters.push('0 = 1'); + } + } + + if (Array.isArray(this.reject)) { + if (this.reject.length > 0) { + valueFilters.push(notInConditionTpl({ + _column: this.column, + _values: this.reject.map(function (value, i) { + return Number.isFinite(value) ? value : escapeStringTpl({_i: i, _value: value}); + }).join(',') + })); + } else { + valueFilters.push('1 = 1'); + } + } + + debug(filterQueryTpl({ + _sql: rawSql, + _filters: valueFilters.join(' AND ') + })); + + return filterQueryTpl({ + _sql: rawSql, + _filters: valueFilters.join(' AND ') + }); +}; diff --git a/lib/cartodb/models/filter/camshaft/range.js b/lib/cartodb/models/filter/camshaft/range.js new file mode 100644 index 00000000..f894ca32 --- /dev/null +++ b/lib/cartodb/models/filter/camshaft/range.js @@ -0,0 +1,43 @@ +'use strict'; + +var dot = require('dot'); +dot.templateSettings.strip = false; + +var betweenFilterTpl = dot.template('{{=it._column}} BETWEEN {{=it._min}} AND {{=it._max}}'); +var minFilterTpl = dot.template('{{=it._column}} >= {{=it._min}}'); +var maxFilterTpl = dot.template('{{=it._column}} <= {{=it._max}}'); +var filterQueryTpl = dot.template('SELECT * FROM ({{=it._sql}}) _camshaft_range_filter WHERE {{=it._filter}}'); + +function Range(column, filterParams) { + this.column = column; + + if (!Number.isFinite(filterParams.min) && !Number.isFinite(filterParams.max)) { + throw new Error('Range filter expect to have at least one value in min or max numeric params'); + } + + this.min = filterParams.min; + this.max = filterParams.max; + this.columnType = filterParams.columnType; +} + +module.exports = Range; + +Range.prototype.sql = function(rawSql) { + var minMaxFilter; + if (Number.isFinite(this.min) && Number.isFinite(this.max)) { + minMaxFilter = betweenFilterTpl({ + _column: this.column, + _min: this.min, + _max: this.max + }); + } else if (Number.isFinite(this.min)) { + minMaxFilter = minFilterTpl({ _column: this.column, _min: this.min }); + } else { + minMaxFilter = maxFilterTpl({ _column: this.column, _max: this.max }); + } + + return filterQueryTpl({ + _sql: rawSql, + _filter: minMaxFilter + }); +};