2017-09-15 17:35:06 +08:00
|
|
|
const BaseDataview = require('./base');
|
|
|
|
const debug = require('debug')('windshaft:dataview:formula');
|
2016-03-22 20:10:42 +08:00
|
|
|
|
2017-09-14 23:56:17 +08:00
|
|
|
const countInfinitiesQueryTpl = ctx => `
|
2017-09-15 17:39:06 +08:00
|
|
|
SELECT count(1) FROM (${ctx.query}) __cdb_formula_infinities
|
|
|
|
WHERE ${ctx.column} = 'infinity'::float OR ${ctx.column} = '-infinity'::float
|
2017-09-14 23:56:17 +08:00
|
|
|
`;
|
|
|
|
|
|
|
|
const countNansQueryTpl = ctx => `
|
2017-09-15 17:39:06 +08:00
|
|
|
SELECT count(1) FROM (${ctx.query}) __cdb_formula_nans
|
|
|
|
WHERE ${ctx.column} = 'NaN'::float
|
2017-09-14 23:56:17 +08:00
|
|
|
`;
|
|
|
|
|
|
|
|
const filterOutSpecialNumericValuesTpl = ctx => `
|
|
|
|
WHERE
|
2017-09-15 17:39:06 +08:00
|
|
|
${ctx.column} != 'infinity'::float
|
2017-09-14 23:56:17 +08:00
|
|
|
AND
|
2017-09-15 17:39:06 +08:00
|
|
|
${ctx.column} != '-infinity'::float
|
2017-09-14 23:56:17 +08:00
|
|
|
AND
|
2017-09-15 17:39:06 +08:00
|
|
|
${ctx.column} != 'NaN'::float
|
2017-09-14 23:56:17 +08:00
|
|
|
`;
|
|
|
|
|
|
|
|
const formulaQueryTpl = ctx => `
|
|
|
|
SELECT
|
2017-09-15 17:39:06 +08:00
|
|
|
${ctx.operation}(${ctx.column}) AS result,
|
|
|
|
(SELECT count(1) FROM (${ctx.query}) _cdb_formula_nulls WHERE ${ctx.column} IS NULL) AS nulls_count
|
|
|
|
${ctx.isFloatColumn ? `,(${countInfinitiesQueryTpl(ctx)}) AS infinities_count` : ''}
|
|
|
|
${ctx.isFloatColumn ? `,(${countNansQueryTpl(ctx)}) AS nans_count` : ''}
|
|
|
|
FROM (${ctx.query}) __cdb_formula
|
|
|
|
${ctx.isFloatColumn && ctx.operation !== 'count' ? `${filterOutSpecialNumericValuesTpl(ctx)}` : ''}
|
2017-09-14 23:56:17 +08:00
|
|
|
`;
|
2016-03-22 20:10:42 +08:00
|
|
|
|
2017-09-15 16:49:20 +08:00
|
|
|
const VALID_OPERATIONS = {
|
2016-03-22 20:10:42 +08:00
|
|
|
count: true,
|
|
|
|
avg: true,
|
|
|
|
sum: true,
|
|
|
|
min: true,
|
|
|
|
max: true
|
|
|
|
};
|
|
|
|
|
2017-09-15 16:49:20 +08:00
|
|
|
const TYPE = 'formula';
|
2016-03-22 20:10:42 +08:00
|
|
|
|
|
|
|
/**
|
|
|
|
{
|
|
|
|
type: 'formula',
|
|
|
|
options: {
|
|
|
|
column: 'name',
|
|
|
|
operation: 'count' // count, sum, avg
|
|
|
|
}
|
|
|
|
}
|
|
|
|
*/
|
2017-09-15 16:48:44 +08:00
|
|
|
module.exports = class Formula extends BaseDataview {
|
|
|
|
constructor (query, options = {}, queries = {}) {
|
2017-09-15 16:59:07 +08:00
|
|
|
super();
|
|
|
|
|
2017-09-15 17:44:20 +08:00
|
|
|
this._checkOptions(options);
|
|
|
|
|
|
|
|
this.query = query;
|
|
|
|
this.queries = queries;
|
|
|
|
this.column = options.column || '1';
|
|
|
|
this.operation = options.operation;
|
|
|
|
this._isFloatColumn = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
_checkOptions (options) {
|
2017-09-15 16:48:44 +08:00
|
|
|
if (typeof options.operation !== 'string') {
|
|
|
|
throw new Error(`Formula expects 'operation' in dataview options`);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!VALID_OPERATIONS[options.operation]) {
|
2017-09-15 17:35:06 +08:00
|
|
|
throw new Error(`Formula does not support '${options.operation}' operation`);
|
2017-09-15 16:48:44 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (options.operation !== 'count' && typeof options.column !== 'string') {
|
|
|
|
throw new Error(`Formula expects 'column' in dataview options`);
|
|
|
|
}
|
2016-03-22 20:10:42 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-09-15 16:48:44 +08:00
|
|
|
sql (psql, override, callback) {
|
|
|
|
if (!callback) {
|
|
|
|
callback = override;
|
|
|
|
override = {};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this._isFloatColumn === null) {
|
|
|
|
this._isFloatColumn = false;
|
2017-09-18 18:20:59 +08:00
|
|
|
this.getColumnType(psql, this.column, this.queries.no_filters, (err, type) => {
|
2017-09-15 16:48:44 +08:00
|
|
|
if (!err && !!type) {
|
2017-09-18 18:20:59 +08:00
|
|
|
this._isFloatColumn = type.float;
|
2017-09-15 16:48:44 +08:00
|
|
|
}
|
2017-09-18 18:20:59 +08:00
|
|
|
this.sql(psql, override, callback);
|
2017-09-15 16:48:44 +08:00
|
|
|
});
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2017-09-15 17:35:06 +08:00
|
|
|
const formulaSql = formulaQueryTpl({
|
2017-09-15 17:39:06 +08:00
|
|
|
isFloatColumn: this._isFloatColumn,
|
|
|
|
query: this.query,
|
|
|
|
operation: this.operation,
|
|
|
|
column: this.column
|
2017-09-15 16:48:44 +08:00
|
|
|
});
|
2016-03-22 20:10:42 +08:00
|
|
|
|
2017-09-15 16:48:44 +08:00
|
|
|
debug(formulaSql);
|
2017-06-15 22:31:24 +08:00
|
|
|
|
2017-09-15 16:48:44 +08:00
|
|
|
return callback(null, formulaSql);
|
2016-03-22 20:10:42 +08:00
|
|
|
}
|
|
|
|
|
2017-09-15 20:48:54 +08:00
|
|
|
format (res) {
|
2017-09-15 16:58:11 +08:00
|
|
|
const {
|
|
|
|
result = 0,
|
|
|
|
nulls_count = 0,
|
2017-09-15 20:51:02 +08:00
|
|
|
nans_count,
|
|
|
|
infinities_count
|
2017-09-15 20:48:54 +08:00
|
|
|
} = res.rows[0] || {};
|
2017-09-15 16:58:11 +08:00
|
|
|
|
|
|
|
return {
|
2017-09-15 16:48:44 +08:00
|
|
|
operation: this.operation,
|
2017-09-15 16:58:11 +08:00
|
|
|
result,
|
|
|
|
nulls: nulls_count,
|
|
|
|
nans: nans_count,
|
|
|
|
infinities: infinities_count
|
2017-09-15 16:48:44 +08:00
|
|
|
};
|
2017-06-15 22:31:24 +08:00
|
|
|
}
|
|
|
|
|
2017-09-15 16:48:44 +08:00
|
|
|
getType () {
|
|
|
|
return TYPE;
|
2017-09-15 17:35:06 +08:00
|
|
|
}
|
2016-03-22 20:10:42 +08:00
|
|
|
|
2017-09-15 16:48:44 +08:00
|
|
|
toString () {
|
|
|
|
return JSON.stringify({
|
|
|
|
_type: TYPE,
|
|
|
|
_query: this.query,
|
|
|
|
_column: this.column,
|
|
|
|
_operation: this.operation
|
|
|
|
});
|
2016-03-22 20:10:42 +08:00
|
|
|
}
|
2017-09-15 17:35:06 +08:00
|
|
|
};
|