Skip to content

Commit

Permalink
SQL conversion of relative forms (#3419)
Browse files Browse the repository at this point in the history
  • Loading branch information
jnsrnhld authored May 8, 2024
1 parent 81cb697 commit f26749d
Show file tree
Hide file tree
Showing 32 changed files with 1,413 additions and 322 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ public Field<Date> addDays(Field<Date> dateColumn, Field<Integer> amountOfDays)
"ADD_DAYS",
Date.class,
dateColumn,
DSL.val(amountOfDays)
amountOfDays
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.bakdata.conquery.sql.conversion.query.ConceptQueryConverter;
import com.bakdata.conquery.sql.conversion.query.EntityDateQueryConverter;
import com.bakdata.conquery.sql.conversion.query.FormConversionHelper;
import com.bakdata.conquery.sql.conversion.query.RelativFormQueryConverter;
import com.bakdata.conquery.sql.conversion.query.SecondaryIdQueryConverter;
import com.bakdata.conquery.sql.conversion.supplier.DateNowSupplier;
import com.bakdata.conquery.sql.conversion.supplier.SystemDateNowSupplier;
Expand Down Expand Up @@ -64,7 +65,8 @@ default List<NodeConverter<? extends Visitable>> getDefaultNodeConverters() {
new ConceptQueryConverter(queryStepTransformer),
new SecondaryIdQueryConverter(),
new AbsoluteFormQueryConverter(formConversionUtil),
new EntityDateQueryConverter(formConversionUtil)
new EntityDateQueryConverter(formConversionUtil),
new RelativFormQueryConverter(formConversionUtil)
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,9 @@ public interface SqlFunctionProvider {
*/
Field<String> daterangeStringExpression(ColumnDateRange columnDateRange);

/**
* Calculates the date distance in the given {@link ChronoUnit} between an exclusive end date and an inclusive start date.
*/
Field<Integer> dateDistance(ChronoUnit datePart, Field<Date> startDate, Field<Date> endDate);

Field<Date> addDays(Field<Date> dateColumn, Field<Integer> amountOfDays);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
package com.bakdata.conquery.sql.conversion.forms;

import java.sql.Date;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;

import com.bakdata.conquery.apiv1.forms.export_form.ExportForm;
import com.bakdata.conquery.models.forms.util.Resolution;
import com.bakdata.conquery.sql.conversion.SharedAliases;
import com.bakdata.conquery.sql.conversion.model.ColumnDateRange;
import com.bakdata.conquery.sql.conversion.model.QueryStep;
import com.bakdata.conquery.sql.conversion.model.Selects;
import com.bakdata.conquery.sql.conversion.model.SqlIdColumns;
import com.bakdata.conquery.sql.conversion.model.select.FieldWrapper;
import com.google.common.base.Preconditions;
import lombok.RequiredArgsConstructor;
import org.jooq.Condition;
import org.jooq.Field;
import org.jooq.Record;
import org.jooq.Table;
import org.jooq.impl.DSL;

@RequiredArgsConstructor
class AbsoluteStratification {

private final int INDEX_START = 1;
private final int INDEX_END = 10_000;

private final QueryStep baseStep;
private final StratificationFunctions stratificationFunctions;

public QueryStep createStratificationTable(List<ExportForm.ResolutionAndAlignment> resolutionAndAlignments) {

QueryStep intSeriesStep = createIntSeriesStep();
QueryStep indexStartStep = createIndexStartStep();

List<QueryStep> resolutionTables = resolutionAndAlignments.stream()
.map(resolutionAndAlignment -> createResolutionTable(indexStartStep, resolutionAndAlignment))
.toList();

List<QueryStep> predecessors = List.of(baseStep, intSeriesStep, indexStartStep);
return StratificationTableFactory.unionResolutionTables(resolutionTables, predecessors);
}

private QueryStep createIntSeriesStep() {

// not actually required, but Selects expect at least 1 SqlIdColumn
Field<Object> rowNumber = DSL.rowNumber().over().coerce(Object.class);
SqlIdColumns ids = new SqlIdColumns(rowNumber);

FieldWrapper<Integer> seriesIndex = new FieldWrapper<>(stratificationFunctions.intSeriesField());

Selects selects = Selects.builder()
.ids(ids)
.sqlSelect(seriesIndex)
.build();

Table<Record> seriesTable = stratificationFunctions.generateIntSeries(INDEX_START, INDEX_END)
.as(SharedAliases.SERIES_INDEX.getAlias());

return QueryStep.builder()
.cteName(FormCteStep.INT_SERIES.getSuffix())
.selects(selects)
.fromTable(seriesTable)
.build();
}

private QueryStep createIndexStartStep() {

Selects baseStepSelects = baseStep.getQualifiedSelects();
Preconditions.checkArgument(baseStepSelects.getStratificationDate().isPresent(), "The base step must have a stratification date set");
ColumnDateRange bounds = baseStepSelects.getStratificationDate().get();

Field<Date> indexStart = stratificationFunctions.absoluteIndexStartDate(bounds).as(SharedAliases.INDEX_START.getAlias());
Field<Date> yearStart = stratificationFunctions.lowerBoundYearStart(bounds).as(SharedAliases.YEAR_START.getAlias());
Field<Date> yearEnd = stratificationFunctions.upperBoundYearEnd(bounds).as(SharedAliases.YEAR_END.getAlias());
Field<Date> yearEndQuarterAligned = stratificationFunctions.upperBoundYearEndQuarterAligned(bounds).as(SharedAliases.YEAR_END_QUARTER_ALIGNED.getAlias());
Field<Date> quarterStart = stratificationFunctions.lowerBoundQuarterStart(bounds).as(SharedAliases.QUARTER_START.getAlias());
Field<Date> quarterEnd = stratificationFunctions.upperBoundQuarterEnd(bounds).as(SharedAliases.QUARTER_END.getAlias());

List<FieldWrapper<Date>> startDates = Stream.of(
indexStart,
yearStart,
yearEnd,
yearEndQuarterAligned,
quarterStart,
quarterEnd
)
.map(FieldWrapper::new)
.toList();

Selects selects = Selects.builder()
.ids(baseStepSelects.getIds())
.stratificationDate(Optional.of(bounds))
.sqlSelects(startDates)
.build();

return QueryStep.builder()
.cteName(FormCteStep.INDEX_START.getSuffix())
.selects(selects)
.fromTable(QueryStep.toTableLike(baseStep.getCteName()))
.build();
}

private QueryStep createResolutionTable(QueryStep indexStartStep, ExportForm.ResolutionAndAlignment resolutionAndAlignment) {
return switch (resolutionAndAlignment.getResolution()) {
case COMPLETE -> createCompleteTable();
case YEARS, QUARTERS, DAYS -> createIntervalTable(indexStartStep, resolutionAndAlignment);
};
}

private QueryStep createCompleteTable() {

Selects baseStepSelects = baseStep.getQualifiedSelects();

// complete range shall have a null index because it spans the complete range, but we set it to 1 to ensure we can join tables on index,
// because a condition involving null in a join (e.g., null = some_value or null = null) always evaluates to false
Field<Integer> index = DSL.field(DSL.val(1, Integer.class)).as(SharedAliases.INDEX.getAlias());
SqlIdColumns ids = baseStepSelects.getIds().withAbsoluteStratification(Resolution.COMPLETE, index);

ColumnDateRange completeRange = baseStepSelects.getStratificationDate().get();

Selects selects = Selects.builder()
.ids(ids)
.stratificationDate(Optional.of(completeRange))
.build();

return QueryStep.builder()
.cteName(FormCteStep.COMPLETE.getSuffix())
.selects(selects)
.fromTable(QueryStep.toTableLike(baseStep.getCteName()))
.build();
}

private QueryStep createIntervalTable(QueryStep indexStartStep, ExportForm.ResolutionAndAlignment resolutionAndAlignment) {

QueryStep countsCte = createCountsCte(indexStartStep, resolutionAndAlignment);
Preconditions.checkArgument(countsCte.getSelects().getStratificationDate().isPresent(), "The countsCte must have a stratification date set");
Selects countsCteSelects = countsCte.getQualifiedSelects();

ColumnDateRange stratificationRange = stratificationFunctions.createStratificationRange(
resolutionAndAlignment,
countsCteSelects.getStratificationDate().get()
);

Field<Integer> index = stratificationFunctions.index(countsCteSelects.getIds(), countsCte.getQualifiedSelects().getStratificationDate());
SqlIdColumns ids = countsCteSelects.getIds().withAbsoluteStratification(resolutionAndAlignment.getResolution(), index);

Selects selects = Selects.builder()
.ids(ids)
.stratificationDate(Optional.ofNullable(stratificationRange))
.build();

Condition stopOnMaxResolutionWindowCount = stratificationFunctions.stopOnMaxResolutionWindowCount(resolutionAndAlignment);

return QueryStep.builder()
.cteName(FormCteStep.stratificationCte(resolutionAndAlignment.getResolution()).getSuffix())
.selects(selects)
.fromTable(QueryStep.toTableLike(countsCte.getCteName()))
.fromTable(QueryStep.toTableLike(FormCteStep.INT_SERIES.getSuffix()))
.conditions(List.of(stopOnMaxResolutionWindowCount))
.predecessor(countsCte)
.build();
}

private QueryStep createCountsCte(QueryStep indexStartStep, ExportForm.ResolutionAndAlignment resolutionAndAlignment) {

Selects indexStartSelects = indexStartStep.getQualifiedSelects();
Preconditions.checkArgument(indexStartSelects.getStratificationDate().isPresent(), "The indexStartStep must have a stratification date set");

Field<Integer> resolutionWindowCount = stratificationFunctions.calculateResolutionWindowCount(
resolutionAndAlignment,
indexStartSelects.getStratificationDate().get()
);

Selects selects = indexStartSelects.toBuilder()
.sqlSelect(new FieldWrapper<>(resolutionWindowCount))
.build();

return QueryStep.builder()
.cteName(FormCteStep.countsCte(resolutionAndAlignment.getResolution()).getSuffix())
.selects(selects)
.fromTable(QueryStep.toTableLike(indexStartStep.getCteName()))
.build();
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package com.bakdata.conquery.sql.conversion.forms;

import com.bakdata.conquery.apiv1.forms.IndexPlacement;
import com.bakdata.conquery.apiv1.forms.export_form.ExportForm;
import com.bakdata.conquery.models.forms.util.CalendarUnit;
import com.bakdata.conquery.models.forms.util.Resolution;

class CombinationNotSupportedException extends RuntimeException {

Expand All @@ -11,4 +14,12 @@ public CombinationNotSupportedException(ExportForm.ResolutionAndAlignment resolu
));
}

public CombinationNotSupportedException(IndexPlacement indexPlacement, CalendarUnit timeUnit) {
super("Combination of index placement %s and time unit %s not supported".formatted(indexPlacement, timeUnit));
}

public CombinationNotSupportedException(CalendarUnit timeUnit, Resolution resolution) {
super("Combination of time unit %s and resolution %s not supported".formatted(timeUnit, resolution));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,21 @@ class FormConstants {
*/
public static Field<Date> INDEX_START = DSL.field(DSL.name(SharedAliases.INDEX_START.getAlias()), Date.class);

/**
* The index date corresponding to the {@link TemporalSamplerFactory} of a relative stratification.
*/
public static Field<Date> INDEX_SELECTOR = DSL.field(DSL.name(SharedAliases.INDEX_SELECTOR.getAlias()), Date.class);

/**
* The index date from which we start when calculating a feature range.
*/
public static Field<Date> INDEX_START_NEGATIVE = DSL.field(DSL.name(SharedAliases.INDEX_START_NEGATIVE.getAlias()), Date.class);

/**
* The index date from which we start when calculating an outcome range.
*/
public static Field<Date> INDEX_START_POSITIVE = DSL.field(DSL.name(SharedAliases.INDEX_START_POSITIVE.getAlias()), Date.class);

/**
* The quarter start of the lower bound of an absolute stratification range. The stratification range this date is referring to can be an
* {@link AbsoluteFormQuery#getDateRange()} or for an {@link EntityDateQuery} the respective entities date range bound by the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ public enum FormCteStep implements CteStep {
UNNEST_ENTITY_DATE_CTE("unnest_entity_date"),
OVERWRITE_BOUNDS("overwrite_bounds"),

// relative form
UNNEST_DATES("unnest_dates"),
INDEX_SELECTOR("index_selector"),
TOTAL_BOUNDS("total_bounds"),

// stratification
INDEX_START("index_start"),
INT_SERIES("int_series"),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.bakdata.conquery.sql.conversion.forms;

public enum FormType {
ABSOLUTE,
ENTITY_DATE,
RELATIVE
}
Loading

0 comments on commit f26749d

Please sign in to comment.