Merge remote-tracking branch 'origin/master' into express-v4.15.5

This commit is contained in:
Raul Ochoa 2017-10-04 09:32:46 +00:00
commit 49fd75f0b6
2 changed files with 145 additions and 152 deletions

View File

@ -1,67 +1,16 @@
var dot = require('dot'); const FLOAT_OIDS = {
dot.templateSettings.strip = false;
function BaseDataview() {}
module.exports = BaseDataview;
BaseDataview.prototype.getResult = function(psql, override, callback) {
var self = this;
this.sql(psql, override, function(err, query) {
if (err) {
return callback(err);
}
psql.query(query, function(err, result) {
if (err) {
return callback(err, result);
}
result = self.format(result, override);
result.type = self.getType();
return callback(null, result);
}, true); // use read-only transaction
});
};
BaseDataview.prototype.search = function(psql, userQuery, callback) {
return callback(null, this.format({ rows: [] }));
};
var FLOAT_OIDS = {
700: true, 700: true,
701: true, 701: true,
1700: true 1700: true
}; };
var DATE_OIDS = { const DATE_OIDS = {
1082: true, 1082: true,
1114: true, 1114: true,
1184: true 1184: true
}; };
var columnTypeQueryTpl = dot.template( const columnTypeQueryTpl = ctx => `SELECT pg_typeof(${ctx.column})::oid FROM (${ctx.query}) _cdb_column_type limit 1`;
'SELECT pg_typeof({{=it.column}})::oid FROM ({{=it.query}}) _cdb_column_type limit 1'
);
BaseDataview.prototype.getColumnType = function (psql, column, query, callback) {
var readOnlyTransaction = true;
var columnTypeQuery = columnTypeQueryTpl({
column: column, query: query
});
psql.query(columnTypeQuery, function(err, result) {
if (err) {
return callback(err);
}
var pgType = result.rows[0].pg_typeof;
callback(null, getPGTypeName(pgType));
}, readOnlyTransaction);
};
function getPGTypeName (pgType) { function getPGTypeName (pgType) {
return { return {
@ -69,3 +18,42 @@ function getPGTypeName (pgType) {
date: DATE_OIDS.hasOwnProperty(pgType) date: DATE_OIDS.hasOwnProperty(pgType)
}; };
} }
module.exports = class BaseDataview {
getResult (psql, override, callback) {
this.sql(psql, override, (err, query) => {
if (err) {
return callback(err);
}
psql.query(query, (err, result) => {
if (err) {
return callback(err, result);
}
result = this.format(result, override);
result.type = this.getType();
return callback(null, result);
}, true); // use read-only transaction
});
}
search (psql, userQuery, callback) {
return callback(null, this.format({ rows: [] }));
}
getColumnType (psql, column, query, callback) {
const readOnlyTransaction = true;
const columnTypeQuery = columnTypeQueryTpl({ column, query });
psql.query(columnTypeQuery, (err, result) => {
if (err) {
return callback(err);
}
const pgType = result.rows[0].pg_typeof;
callback(null, getPGTypeName(pgType));
}, readOnlyTransaction);
}
};

View File

@ -1,28 +1,36 @@
var _ = require('underscore'); const BaseDataview = require('./base');
var BaseWidget = require('./base'); const debug = require('debug')('windshaft:dataview:formula');
var debug = require('debug')('windshaft:widget:formula');
var dot = require('dot'); const countInfinitiesQueryTpl = ctx => `
dot.templateSettings.strip = false; SELECT count(1) FROM (${ctx.query}) __cdb_formula_infinities
WHERE ${ctx.column} = 'infinity'::float OR ${ctx.column} = '-infinity'::float
`;
var formulaQueryTpl = dot.template([ const countNansQueryTpl = ctx => `
'SELECT', SELECT count(1) FROM (${ctx.query}) __cdb_formula_nans
' {{=it._operation}}({{=it._column}}) AS result,', WHERE ${ctx.column} = 'NaN'::float
' (SELECT count(1) FROM ({{=it._query}}) _cdb_formula_nulls WHERE {{=it._column}} IS NULL) AS nulls_count', `;
' {{?it._isFloatColumn}},(SELECT count(1) FROM ({{=it._query}}) _cdb_formula_nulls',
' WHERE {{=it._column}} = \'infinity\'::float OR {{=it._column}} = \'-infinity\'::float) AS infinities_count',
' ,(SELECT count(1) FROM ({{=it._query}}) _cdb_formula_nulls',
' WHERE {{=it._column}} = \'NaN\'::float) AS nans_count{{?}}',
'FROM ({{=it._query}}) _cdb_formula',
'{{?it._isFloatColumn && it._operation !== \'count\'}}WHERE',
' {{=it._column}} != \'infinity\'::float',
'AND',
' {{=it._column}} != \'-infinity\'::float',
'AND',
' {{=it._column}} != \'NaN\'::float{{?}}'
].join('\n'));
var VALID_OPERATIONS = { const filterOutSpecialNumericValuesTpl = ctx => `
WHERE
${ctx.column} != 'infinity'::float
AND
${ctx.column} != '-infinity'::float
AND
${ctx.column} != 'NaN'::float
`;
const formulaQueryTpl = ctx => `
SELECT
${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)}` : ''}
`;
const VALID_OPERATIONS = {
count: true, count: true,
avg: true, avg: true,
sum: true, sum: true,
@ -30,7 +38,7 @@ var VALID_OPERATIONS = {
max: true max: true
}; };
var TYPE = 'formula'; const TYPE = 'formula';
/** /**
{ {
@ -41,93 +49,90 @@ var TYPE = 'formula';
} }
} }
*/ */
function Formula(query, options, queries) { module.exports = class Formula extends BaseDataview {
if (!_.isString(options.operation)) { constructor (query, options = {}, queries = {}) {
throw new Error('Formula expects `operation` in widget options'); super();
this._checkOptions(options);
this.query = query;
this.queries = queries;
this.column = options.column || '1';
this.operation = options.operation;
this._isFloatColumn = null;
} }
if (!VALID_OPERATIONS[options.operation]) { _checkOptions (options) {
throw new Error("Formula does not support '" + options.operation + "' operation"); if (typeof options.operation !== 'string') {
throw new Error(`Formula expects 'operation' in dataview options`);
}
if (!VALID_OPERATIONS[options.operation]) {
throw new Error(`Formula does not support '${options.operation}' operation`);
}
if (options.operation !== 'count' && typeof options.column !== 'string') {
throw new Error(`Formula expects 'column' in dataview options`);
}
} }
if (options.operation !== 'count' && !_.isString(options.column)) {
throw new Error('Formula expects `column` in widget options');
}
BaseWidget.apply(this); sql (psql, override, callback) {
if (!callback) {
callback = override;
override = {};
}
this.query = query; if (this._isFloatColumn === null) {
this.queries = queries; this._isFloatColumn = false;
this.column = options.column || '1'; this.getColumnType(psql, this.column, this.queries.no_filters, (err, type) => {
this.operation = options.operation; if (!err && !!type) {
this._isFloatColumn = null; this._isFloatColumn = type.float;
} }
this.sql(psql, override, callback);
});
return null;
}
Formula.prototype = new BaseWidget(); const formulaSql = formulaQueryTpl({
Formula.prototype.constructor = Formula; isFloatColumn: this._isFloatColumn,
query: this.query,
module.exports = Formula; operation: this.operation,
column: this.column
Formula.prototype.sql = function(psql, override, callback) {
var self = this;
if (!callback) {
callback = override;
override = {};
}
if (this._isFloatColumn === null) {
this._isFloatColumn = false;
this.getColumnType(psql, this.column, this.queries.no_filters, function (err, type) {
if (!err && !!type) {
self._isFloatColumn = type.float;
}
self.sql(psql, override, callback);
}); });
return null;
debug(formulaSql);
return callback(null, formulaSql);
} }
var formulaSql = formulaQueryTpl({ format (res) {
_isFloatColumn: this._isFloatColumn, const {
_query: this.query, result = 0,
_operation: this.operation, nulls_count = 0,
_column: this.column nans_count,
}); infinities_count
} = res.rows[0] || {};
debug(formulaSql); return {
operation: this.operation,
return callback(null, formulaSql); result,
}; nulls: nulls_count,
nans: nans_count,
Formula.prototype.format = function(result) { infinities: infinities_count
var formattedResult = { };
operation: this.operation,
result: 0,
nulls: 0,
nans: 0,
infinities: 0
};
if (result.rows.length) {
formattedResult.operation = this.operation;
formattedResult.result = result.rows[0].result;
formattedResult.nulls = result.rows[0].nulls_count;
formattedResult.nans = result.rows[0].nans_count;
formattedResult.infinities = result.rows[0].infinities_count;
} }
return formattedResult; getType () {
}; return TYPE;
}
Formula.prototype.getType = function() { toString () {
return TYPE; return JSON.stringify({
}; _type: TYPE,
_query: this.query,
Formula.prototype.toString = function() { _column: this.column,
return JSON.stringify({ _operation: this.operation
_type: TYPE, });
_query: this.query, }
_column: this.column,
_operation: this.operation
});
}; };