Merge remote-tracking branch 'origin/master' into express-v4.15.5
This commit is contained in:
commit
49fd75f0b6
@ -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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
@ -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,20 +49,11 @@ 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();
|
||||||
}
|
|
||||||
|
|
||||||
if (!VALID_OPERATIONS[options.operation]) {
|
this._checkOptions(options);
|
||||||
throw new Error("Formula does not support '" + options.operation + "' operation");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.operation !== 'count' && !_.isString(options.column)) {
|
|
||||||
throw new Error('Formula expects `column` in widget options');
|
|
||||||
}
|
|
||||||
|
|
||||||
BaseWidget.apply(this);
|
|
||||||
|
|
||||||
this.query = query;
|
this.query = query;
|
||||||
this.queries = queries;
|
this.queries = queries;
|
||||||
@ -63,14 +62,22 @@ function Formula(query, options, queries) {
|
|||||||
this._isFloatColumn = null;
|
this._isFloatColumn = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Formula.prototype = new BaseWidget();
|
_checkOptions (options) {
|
||||||
Formula.prototype.constructor = Formula;
|
if (typeof options.operation !== 'string') {
|
||||||
|
throw new Error(`Formula expects 'operation' in dataview options`);
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = Formula;
|
if (!VALID_OPERATIONS[options.operation]) {
|
||||||
|
throw new Error(`Formula does not support '${options.operation}' operation`);
|
||||||
|
}
|
||||||
|
|
||||||
Formula.prototype.sql = function(psql, override, callback) {
|
if (options.operation !== 'count' && typeof options.column !== 'string') {
|
||||||
var self = this;
|
throw new Error(`Formula expects 'column' in dataview options`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
sql (psql, override, callback) {
|
||||||
if (!callback) {
|
if (!callback) {
|
||||||
callback = override;
|
callback = override;
|
||||||
override = {};
|
override = {};
|
||||||
@ -78,56 +85,54 @@ Formula.prototype.sql = function(psql, override, callback) {
|
|||||||
|
|
||||||
if (this._isFloatColumn === null) {
|
if (this._isFloatColumn === null) {
|
||||||
this._isFloatColumn = false;
|
this._isFloatColumn = false;
|
||||||
this.getColumnType(psql, this.column, this.queries.no_filters, function (err, type) {
|
this.getColumnType(psql, this.column, this.queries.no_filters, (err, type) => {
|
||||||
if (!err && !!type) {
|
if (!err && !!type) {
|
||||||
self._isFloatColumn = type.float;
|
this._isFloatColumn = type.float;
|
||||||
}
|
}
|
||||||
self.sql(psql, override, callback);
|
this.sql(psql, override, callback);
|
||||||
});
|
});
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var formulaSql = formulaQueryTpl({
|
const formulaSql = formulaQueryTpl({
|
||||||
_isFloatColumn: this._isFloatColumn,
|
isFloatColumn: this._isFloatColumn,
|
||||||
_query: this.query,
|
query: this.query,
|
||||||
_operation: this.operation,
|
operation: this.operation,
|
||||||
_column: this.column
|
column: this.column
|
||||||
});
|
});
|
||||||
|
|
||||||
debug(formulaSql);
|
debug(formulaSql);
|
||||||
|
|
||||||
return callback(null, formulaSql);
|
return callback(null, formulaSql);
|
||||||
};
|
|
||||||
|
|
||||||
Formula.prototype.format = function(result) {
|
|
||||||
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;
|
format (res) {
|
||||||
};
|
const {
|
||||||
|
result = 0,
|
||||||
|
nulls_count = 0,
|
||||||
|
nans_count,
|
||||||
|
infinities_count
|
||||||
|
} = res.rows[0] || {};
|
||||||
|
|
||||||
Formula.prototype.getType = function() {
|
return {
|
||||||
|
operation: this.operation,
|
||||||
|
result,
|
||||||
|
nulls: nulls_count,
|
||||||
|
nans: nans_count,
|
||||||
|
infinities: infinities_count
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getType () {
|
||||||
return TYPE;
|
return TYPE;
|
||||||
};
|
}
|
||||||
|
|
||||||
Formula.prototype.toString = function() {
|
toString () {
|
||||||
return JSON.stringify({
|
return JSON.stringify({
|
||||||
_type: TYPE,
|
_type: TYPE,
|
||||||
_query: this.query,
|
_query: this.query,
|
||||||
_column: this.column,
|
_column: this.column,
|
||||||
_operation: this.operation
|
_operation: this.operation
|
||||||
});
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user