The sampling probability is now being computed using an estimate of the table row count This could led to too high probabilities (to large samples) if the estimate is not accurate. To avoid potential problems with large samples we've added a LIMIT to the sampling queries.
157 lines
5.0 KiB
JavaScript
157 lines
5.0 KiB
JavaScript
function prepareQuery(sql) {
|
|
var affectedTableRegexCache = {
|
|
bbox: /!bbox!/g,
|
|
scale_denominator: /!scale_denominator!/g,
|
|
pixel_width: /!pixel_width!/g,
|
|
pixel_height: /!pixel_height!/g
|
|
};
|
|
|
|
return sql
|
|
.replace(affectedTableRegexCache.bbox, 'ST_MakeEnvelope(0,0,0,0)')
|
|
.replace(affectedTableRegexCache.scale_denominator, '0')
|
|
.replace(affectedTableRegexCache.pixel_width, '1')
|
|
.replace(affectedTableRegexCache.pixel_height, '1');
|
|
}
|
|
|
|
module.exports.extractTableNames = function extractTableNames(query) {
|
|
return [
|
|
'SELECT * FROM CDB_QueryTablesText($windshaft$',
|
|
prepareQuery(query),
|
|
'$windshaft$) as tablenames'
|
|
].join('');
|
|
};
|
|
|
|
module.exports.getQueryActualRowCount = function (query) {
|
|
return `select COUNT(*) AS rows FROM (${query}) AS __cdb_query`;
|
|
};
|
|
|
|
function getQueryRowEstimation(query) {
|
|
return 'select CDB_EstimateRowCount($windshaft$' + query + '$windshaft$) as rows';
|
|
}
|
|
|
|
module.exports.getQueryRowEstimation = getQueryRowEstimation;
|
|
|
|
module.exports.getAggregationMetadata = ctx => `
|
|
WITH
|
|
rowEstimation AS (
|
|
${getQueryRowEstimation(ctx.query)}
|
|
),
|
|
geometryType AS (
|
|
SELECT ST_GeometryType(${ctx.geometryColumn}) as geom_type
|
|
FROM (${ctx.query}) AS __cdb_query WHERE ${ctx.geometryColumn} IS NOT NULL LIMIT 1
|
|
)
|
|
SELECT
|
|
rows AS count,
|
|
geom_type AS type
|
|
FROM rowEstimation, geometryType;
|
|
`;
|
|
|
|
/** Cast the column to epoch */
|
|
module.exports.columnCastTpl = function columnCastTpl(ctx) {
|
|
return `date_part('epoch', ${ctx.column})`;
|
|
};
|
|
|
|
/** If the column type is float, ignore any non numeric result (infinity / NaN) */
|
|
module.exports.handleFloatColumn = function handleFloatColumn(ctx) {
|
|
return `${!ctx.isFloatColumn ? `${ctx.column}` :
|
|
`nullif(nullif(nullif(${ctx.column}, 'infinity'::float), '-infinity'::float), 'NaN'::float)`
|
|
}`;
|
|
};
|
|
|
|
/** Count NULL appearances */
|
|
module.exports.countNULLs= function countNULLs(ctx) {
|
|
return `sum(CASE WHEN (${ctx.column} IS NULL) THEN 1 ELSE 0 END)`;
|
|
};
|
|
|
|
/** Count only infinity (positive and negative) appearances */
|
|
module.exports.countInfinites = function countInfinites(ctx) {
|
|
return `${!ctx.isFloatColumn ? `0` :
|
|
`sum(CASE WHEN (${ctx.column} = 'infinity'::float OR ${ctx.column} = '-infinity'::float) THEN 1 ELSE 0 END)`
|
|
}`;
|
|
};
|
|
|
|
/** Count only NaNs appearances*/
|
|
module.exports.countNaNs = function countNaNs(ctx) {
|
|
return `${!ctx.isFloatColumn ? `0` :
|
|
`sum(CASE WHEN (${ctx.column} = 'NaN'::float) THEN 1 ELSE 0 END)`
|
|
}`;
|
|
};
|
|
|
|
module.exports.getQueryTopCategories = function(query, column, topN, includeNulls=false) {
|
|
const where = includeNulls ? '' : `WHERE ${column} IS NOT NULL`;
|
|
return `
|
|
SELECT ${column} AS category, COUNT(*) AS frequency
|
|
FROM (${query}) AS __cdb_query
|
|
${where}
|
|
GROUP BY ${column} ORDER BY 2 DESC
|
|
LIMIT ${topN}
|
|
`;
|
|
};
|
|
|
|
module.exports.getQuerySample = function(query, sampleProb, limit = null, randomSeed = 0.5) {
|
|
const singleTable = simpleQueryTable(query);
|
|
if (singleTable) {
|
|
return getTableSample(singleTable.table, singleTable.columns, sampleProb, randomSeed);
|
|
}
|
|
const limitClause = limit ? `LIMIT ${limit}` : '';
|
|
return `
|
|
WITH __cdb_rndseed AS (
|
|
SELECT setseed(${randomSeed})
|
|
)
|
|
SELECT *
|
|
FROM (${query}) AS __cdb_query
|
|
WHERE random() < ${sampleProb}
|
|
${limitClause}
|
|
`;
|
|
};
|
|
|
|
function getTableSample(table, columns, sampleProb, limit = null, randomSeed = 0.5) {
|
|
const limitClause = limit ? `LIMIT ${limit}` : '';
|
|
sampleProb *= 100;
|
|
randomSeed *= Math.pow(2, 31) -1;
|
|
return `
|
|
SELECT ${columns} FROM ${table} TABLESAMPLE BERNOULLI (${sampleProb}) REPEATABLE (${randomSeed}) ${limitClause}
|
|
`;
|
|
}
|
|
|
|
function simpleQueryTable(sql) {
|
|
const basicQuery =
|
|
/\s*SELECT\s+([\*a-z0-9_,\s]+?)\s+FROM\s+((\"[^"]+\"|[a-z0-9_]+)\.)?(\"[^"]+\"|[a-z0-9_]+)\s*;?\s*/i;
|
|
const unwrappedQuery = new RegExp("^"+basicQuery.source+"$", 'i');
|
|
// queries for named maps are wrapped like this:
|
|
var wrappedQuery = new RegExp(
|
|
"^\\s*SELECT\\s+\\*\\s+FROM\\s+\\(" +
|
|
basicQuery.source +
|
|
"\\)\\s+AS\\s+wrapped_query\\s+WHERE\\s+\\d+=1\\s*$",
|
|
'i'
|
|
);
|
|
let match = sql.match(unwrappedQuery);
|
|
if (!match) {
|
|
match = sql.match(wrappedQuery);
|
|
}
|
|
if (match) {
|
|
const columns = match[1];
|
|
const schema = match[3];
|
|
const table = match[4];
|
|
return { table: schema ? `${schema}.${table}` : table, columns };
|
|
}
|
|
return false;
|
|
}
|
|
|
|
module.exports.getQueryGeometryType = function(query, geometryColumn) {
|
|
return `
|
|
SELECT ST_GeometryType(${geometryColumn}) AS geom_type
|
|
FROM (${query}) AS __cdb_query
|
|
WHERE ${geometryColumn} IS NOT NULL
|
|
LIMIT 1
|
|
`;
|
|
};
|
|
|
|
module.exports.getQueryLimited = function(query, limit=0) {
|
|
return `
|
|
SELECT *
|
|
FROM (${query}) AS __cdb_query
|
|
LIMIT ${limit}
|
|
`;
|
|
};
|