Skip to content

Commit

Permalink
fix: Strict date range and rollup granularity alignment check
Browse files Browse the repository at this point in the history
Fixes #103
  • Loading branch information
paveltiunov committed Apr 20, 2020
1 parent b57e5db commit deb62b6
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 7 deletions.
2 changes: 1 addition & 1 deletion packages/cubejs-schema-compiler/adapter/BaseFilter.js
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ class BaseFilter extends BaseDimension {
return `${date}T23:59:59.999`;
}
if (!date) {
return moment.tz(date, this.query.timezone).format('YYYY-MM-DD 23:59:59');
return moment.tz(date, this.query.timezone).format('YYYY-MM-DDT23:59:59.999');
}
return moment.tz(date, this.query.timezone).format(moment.HTML5_FMT.DATETIME_LOCAL_MS);
}
Expand Down
59 changes: 58 additions & 1 deletion packages/cubejs-schema-compiler/adapter/BaseQuery.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ class BaseQuery {
this.initUngrouped();
}

cacheValue(key, fn, { contextPropNames, inputProps, cache }) {
cacheValue(key, fn, { contextPropNames, inputProps, cache } = {}) {
const currentContext = this.safeEvaluateSymbolContext();
if (contextPropNames) {
const contextKey = {};
Expand Down Expand Up @@ -540,6 +540,9 @@ class BaseQuery {
if (!granularityB) {
return granularityA;
}
if (granularityA === granularityB) {
return granularityA;
}
const aHierarchy = R.reverse(this.granularityParentHierarchy(granularityA));
const bHierarchy = R.reverse(this.granularityParentHierarchy(granularityB));
let lastIndex = Math.max(
Expand Down Expand Up @@ -1707,6 +1710,60 @@ class BaseQuery {
return this.floorSql(`${this.unixTimestampSql()} / ${this.parseSecondDuration(interval)}`);
}

granularityFor(momentDate) {
const obj = momentDate.toObject();
const weekDay = momentDate.isoWeekday();
if (
obj.months === 0 &&
obj.date === 1 &&
obj.hours === 0 &&
obj.minutes === 0 &&
obj.seconds === 0 &&
obj.milliseconds === 0
) {
return 'year';
} else if (
obj.date === 1 &&
obj.hours === 0 &&
obj.minutes === 0 &&
obj.seconds === 0 &&
obj.milliseconds === 0
) {
return 'month';
} else if (
weekDay === 1 &&
obj.hours === 0 &&
obj.minutes === 0 &&
obj.seconds === 0 &&
obj.milliseconds === 0
) {
return 'week';
} else if (
obj.hours === 0 &&
obj.minutes === 0 &&
obj.seconds === 0 &&
obj.milliseconds === 0
) {
return 'day';
} else if (
obj.minutes === 0 &&
obj.seconds === 0 &&
obj.milliseconds === 0
) {
return 'hour';
} else if (
obj.seconds === 0 &&
obj.milliseconds === 0
) {
return 'minute';
} else if (
obj.milliseconds === 0
) {
return 'second';
}
return 'second'; // TODO return 'millisecond';
}

parseSecondDuration(interval) {
const intervalMatch = interval.match(/^(\d+) (second|minute|hour|day|week)s?$/);
if (!intervalMatch) {
Expand Down
23 changes: 23 additions & 0 deletions packages/cubejs-schema-compiler/adapter/BaseTimeDimension.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,29 @@ class BaseTimeDimension extends BaseFilter {
);
}

dateRangeGranularity() {
if (!this.dateRange) {
return null;
}
const msFrom = moment.tz(this.dateFromFormatted(), this.query.timezone);
const msTo = moment.tz(this.dateToFormatted(), this.query.timezone).add(1, 'ms');
return this.query.minGranularity(
this.query.granularityFor(msFrom),
this.query.granularityFor(msTo),
);
}

rollupGranularity() {
if (!this.rollupGranularityValue) {
this.rollupGranularityValue =
this.query.cacheValue(
['rollupGranularity', this.granularity].concat(this.dateRange),
() => this.query.minGranularity(this.granularity, this.dateRangeGranularity())
);
}
return this.rollupGranularityValue;
}

timeSeries() {
if (!this.dateRange) {
throw new UserError(`Time series queries without dateRange aren't supported`);
Expand Down
5 changes: 3 additions & 2 deletions packages/cubejs-schema-compiler/adapter/PreAggregations.js
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ class PreAggregations {
function sortTimeDimensions(timeDimensions) {
return timeDimensions && R.sortBy(
R.prop(0),
timeDimensions.map(d => [d.dimension, d.granularity || 'day'])
timeDimensions.map(d => [d.dimension, d.rollupGranularity()])
) || [];
}

Expand Down Expand Up @@ -245,7 +245,7 @@ class PreAggregations {
function sortTimeDimensions(timeDimensions) {
return timeDimensions && R.sortBy(
d => d.join('.'),
timeDimensions.map(d => [d.dimension, d.granularity || 'day'])
timeDimensions.map(d => [d.dimension, d.granularity || 'day']) // TODO granularity shouldn't be null?
) || [];
}
// TimeDimension :: [Dimension, Granularity]
Expand Down Expand Up @@ -517,6 +517,7 @@ class PreAggregations {
preAggregationForQuery.preAggregation.measures :
this.evaluateAllReferences(preAggregationForQuery.cube, preAggregationForQuery.preAggregation).measures);

// TODO granularity shouldn't be null?
const rollupGranularity = this.castGranularity(preAggregationForQuery.preAggregation.granularity) || 'day';

return this.query.evaluateSymbolSqlWithContext(
Expand Down
59 changes: 56 additions & 3 deletions packages/cubejs-schema-compiler/test/PreAggregationsTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -667,7 +667,7 @@ describe('PreAggregations', function test() {
timeDimensions: [{
dimension: 'visitors.createdAt',
granularity: 'month',
dateRange: ['2017-01-01', '2017-01-30']
dateRange: ['2017-01-01', '2017-01-31']
}],
order: [{
id: 'visitors.createdAt'
Expand Down Expand Up @@ -814,6 +814,59 @@ describe('PreAggregations', function test() {
});
});

it('not aligned time dimension', () => {
return compiler.compile().then(() => {
const query = new PostgresQuery({ joinGraph, cubeEvaluator, compiler }, {
measures: [
'visitors.checkinsTotal'
],
dimensions: [
'visitors.source'
],
timezone: 'UTC',
preAggregationsSchema: '',
timeDimensions: [{
dimension: 'visitors.createdAt',
granularity: 'hour',
dateRange: ['2017-01-02T00:00:00.000', '2017-01-05T00:15:59.999']
}],
order: [{
id: 'visitors.createdAt'
}],
});

const queryAndParams = query.buildSqlAndParams();
console.log(queryAndParams);
const preAggregationsDescription = query.preAggregations.preAggregationsDescription();
console.log(preAggregationsDescription);
preAggregationsDescription.length.should.be.equal(2);

const queries = tempTablePreAggregations(preAggregationsDescription);

console.log(JSON.stringify(queries.concat(queryAndParams)));

return dbRunner.testQueries(
queries.concat([queryAndParams]).map(q => replaceTableName(q, preAggregationsDescription, 342))
).then(res => {
console.log(JSON.stringify(res));
res.should.be.deepEqual(
[
{
"visitors__source": "some",
"visitors__created_at_hour": "2017-01-03T00:00:00.000Z",
"visitors__checkins_total": "3"
},
{
"visitors__source": "some",
"visitors__created_at_hour": "2017-01-05T00:00:00.000Z",
"visitors__checkins_total": "2"
}
]
);
});
});
});

it('segment', () => {
return compiler.compile().then(() => {
const query = new PostgresQuery({ joinGraph, cubeEvaluator, compiler }, {
Expand All @@ -827,7 +880,7 @@ describe('PreAggregations', function test() {
timeDimensions: [{
dimension: 'visitors.createdAt',
granularity: 'week',
dateRange: ['2016-12-30', '2017-01-05']
dateRange: ['2016-12-26', '2017-01-08']
}],
order: [{
id: 'visitors.createdAt'
Expand Down Expand Up @@ -1041,7 +1094,7 @@ describe('PreAggregations in time hierarchy', function test() {
timeDimensions: [{
dimension: 'visitors.createdAt',
granularity: 'year',
dateRange: ['2016-12-30', '2018-12-30']
dateRange: ['2016-12-01', '2018-12-31']
}],
preAggregationsSchema: '',
order: [],
Expand Down

0 comments on commit deb62b6

Please sign in to comment.