Unify handling of cyclic time groupings

Remove generic cyclic grouping
This commit is contained in:
Javier Goizueta
2018-10-03 18:57:00 +02:00
parent a7d5415f64
commit 96ba075698
2 changed files with 37 additions and 135 deletions
@@ -119,83 +119,13 @@ const aggregateColumnDefs = ctx => {
const aggregateDimensions = ctx => ctx.dimensions || {};
const timeDimensionParameters = definition => {
let group_by_count = definition.step || 1;
let group_by_units;
let group_by_cycle;
switch (definition.group_by) {
case 'second':
case 'minute':
case 'hour':
case 'day':
case 'week':
case 'month':
case 'quarter':
case 'year':
case 'century':
case 'millenium':
group_by_units = definition.group_by;
break;
case 'semester':
group_by_units = 'month';
group_by_count *= 6;
break;
case 'trimester':
group_by_units = 'month';
group_by_count *= 4;
break;
case 'minuteOfHour':
group_by_units = 'minute';
group_by_cycle = 'hour';
break;
case 'hourOfDay':
group_by_units = 'hour';
group_by_cycle = 'day';
break;
case 'dayOfWeek':
group_by_units = 'day';
group_by_cycle = 'week';
break;
case 'dayOfMonth':
group_by_units = 'day';
group_by_cycle = 'month';
break;
case 'dayOfYear':
group_by_units = 'day';
group_by_cycle = 'year';
break;
case 'weekOfYear':
group_by_units = 'week';
group_by_cycle = 'year';
break;
case 'monthOfYear':
group_by_units = 'month';
group_by_cycle = 'year';
break;
case 'quarterOfYear':
group_by_units = 'quarter';
group_by_cycle = 'year';
break;
case 'trimesterOfYear':
group_by_units = 'month';
group_by_count *= 4;
group_by_cycle = 'year';
break;
case 'semesterOfYear':
group_by_units = 'month';
group_by_count *= 6;
group_by_cycle = 'year';
break;
default:
throw new Error(`Invalid time grouping ${definition.group_by}`);
}
// definition.column should correspond to a wrapped date column
return {
time: `to_timestamp("${definition.column}")`,
timezone: definition.timezone || 'utc',
group_by_units,
group_by_count,
group_by_cycle,
epoch: definition.starting
group_by: definition.group_by,
group_by_count: definition.group_by_count || 1,
starting: definition.starting
};
};
@@ -1,24 +1,3 @@
const MONTH_SECONDS = 365.2425 / 12 * 24 * 3600; // PG intervals use 30 * 24 * 3600
const YEAR_SECONDS = 12 * MONTH_SECONDS;
// time unit durations
const usecs = {
second: 1,
minute: 60,
hour: 3600,
day: 24 * 3600,
week: 7 * 24 * 3600,
month: MONTH_SECONDS,
year: YEAR_SECONDS,
quarter: 3 * MONTH_SECONDS,
semester: 6 * MONTH_SECONDS,
trimester: 4 * MONTH_SECONDS,
decade: 12 * YEAR_SECONDS,
century: 100 * YEAR_SECONDS,
millennium: 1000 * YEAR_SECONDS
};
const YEARSPAN = "(date_part('year', $t)-date_part('year', $epoch))";
// Note that SECONDSPAN is not a UTC epoch, but an epoch in the specified TZ,
// so we can use it to compute any multiple of seconds with it without using date_part or date_trunc
@@ -125,14 +104,14 @@ function serialNormalize(u, m) {
}
function cyclicNormalize(u, m, c) {
if (u === 'month' && m === 3) {
u = 'quarter';
if (u === 'monthOfYear' && m === 3) {
u = 'quarterOfYear';
m = 1;
} else if (u === 'month' && m === 6) {
u = 'semester';
} else if (u === 'monthOfYear' && m === 6) {
u = 'semesterOfYear';
m = 1;
} else if (u === 'month' && m === 4) {
u = 'trimester';
} else if (u === 'monthOfYear' && m === 4) {
u = 'trimesterOfYear';
m = 1;
}
if (m !== 1) {
@@ -185,96 +164,89 @@ function epochExpression(epoch) {
return `TIMESTAMP '${epoch}'`;
}
function cyclicSqlExpr(t, tz, u, c, m = 1) {
function cyclicSqlExpr(t, tz, u, m = 1) {
[u, m, c] = cyclicNormalize(u, m, c);
const comb = `${u}/${c}`;
const column = timeExpression(t, tz);
if (m === 1) {
switch (comb) {
case 'day/week':
switch (u) {
case 'dayOfWeek':
// 1 = monday; 7 = sunday;
return `date_part('isodow', ${column})`;
case 'day/month':
case 'dayOfMonth':
// result: 1-31
return `date_part('day', ${column})`;
case 'day/year':
case 'dayOfYear':
// result: 1-366
return `date_part('doy', ${column})`;
case 'hour/day':
case 'hourOfDay':
// result: 0-23
return `date_part('hour', ${column})`;
case 'month/year':
case 'monthOfYear':
// result 1-12
return `date_part('month', ${column})`;
case 'quarter/year':
case 'quarterOfYear':
// result 1-4
return `date_part('quarter', ${column})`;
case 'semester/year':
case 'semesterOfYear':
// result 1-2
return `FLOOR((date_part('month', ${column})-1)/6.0) + 1`;
case 'trimester/year':
case 'trimesterOfYear':
// result 1-3
return `FLOOR((date_part('month', ${column})-1)/4.0) + 1`;
case 'week/year':
case 'weekOfYear':
// result 1-53
return `date_part('week', ${column})`;
case 'minute/hour':
case 'minuteOfHour':
// result 0-59
return `date_part('minute', ${column})`;
}
}
// TODO: remove generic expression, reaching here should error
return genericCyclicSqlExpr(t, u, c, 0, m);
}
function genericCyclicSqlExpr(t, tz, u, c, c_offset = 0, m = 1) {
const usec = usecs[u];
const csec = usecs[c];
const column = timeExpression(t, tz);
return `((FLOOR(date_part('epoch', ${column})/(${usec*m}))*(${usec*m})+${c_offset}) % ${csec})/${usec*m}`;
throw new Error(`Invalid cyclic time grouping ${u}`)
}
function validateParameters(_params) {
return true;
}
function isCyclic(groupBy) {
return groupBy.match(/.+By.+/);
}
function classificationSql(params) {
validateParameters(params);
if (params.group_by_cycle) {
if (isCyclic(params.group_by)) {
// TODO: validate group_by_count === 1, No epoch
return cyclicSqlExpr(
params.time,
params.timezone || 'utc',
params.group_by_units,
params.group_by_cycle,
0,
params.group_by_count || 1
params.timezone,
params.group_by,
params.group_by_count
);
} else if (params.format === 'iso') {
// TODO: validate group_by_count === 1, No epoch
return isoSqlExpr(
params.time,
params.timezone || 'utc',
params.group_by_units,
params.group_by_count || 1
params.timezone,
params.group_by,
params.group_by_count
);
} else {
return serialSqlExpr(
params.time,
params.timezone || 'utc',
params.group_by_units,
params.group_by_count || 1,
params.epoch
params.timezone,
params.group_by,
params.group_by_count,
params.starting
);
}