diff --git a/engine.impl/src/main/java/org/eclipse/daanse/dax/engine/impl/mdx/MdxGenerator.java b/engine.impl/src/main/java/org/eclipse/daanse/dax/engine/impl/mdx/MdxGenerator.java
index 3b8d3e8..89b6df4 100644
--- a/engine.impl/src/main/java/org/eclipse/daanse/dax/engine/impl/mdx/MdxGenerator.java
+++ b/engine.impl/src/main/java/org/eclipse/daanse/dax/engine/impl/mdx/MdxGenerator.java
@@ -132,7 +132,7 @@ public static MdxQuery summarize(String cube, Summarize summarize, Map computed = computedMeasures(summarize);
summarize = inFilters(summarize, deepest, kept, mdx);
- String slicer = slicer(sliced, computed, mdx);
+ String slicer = slicer(sliced, computed, deepest.isEmpty(), mdx);
Measures measures = measures(summarize, mdx, sources, !groups.isEmpty());
String keeping = measures.keeping();
@@ -182,16 +182,25 @@ private static Set computedMeasures(Summarize summarize) {
* {@code [Time].CurrentMember.PrevMember}, the others left at their
* default members.
*
+ *
+ * Of measures without groups, as of {@code ROW}, a filter table by
+ * measures beside others is computed within the members they keep, as of
+ * an inner CALCULATETABLE: each is a calculated member of a lower solve
+ * order than its one, which then computes its condition over theirs.
+ *
*
* @param sliced the filter tables, by the hierarchies they are on
* @param computed the measures computed
+ * @param alone whether the measures are computed without groups
* @return the slicer; {@code null} without filter tables
*/
private static String slicer(Map, List> sliced, Set computed,
- StringBuilder mdx) {
+ boolean alone, StringBuilder mdx) {
List sets = new ArrayList<>();
List members = new ArrayList<>();
Set taken = new LinkedHashSet<>();
+ boolean byMeasures = alone && sliced.size() > 1
+ && sliced.values().stream().anyMatch(f -> f.stream().anyMatch(MdxGenerator::byMeasures));
for (Map.Entry, List> filters : sliced.entrySet()) {
String set = intersection(filters.getValue());
// the tuples of several hierarchies are one member of the first
@@ -199,6 +208,9 @@ private static String slicer(Map, List> sliced, Set hierarchy = filters.getKey().stream()
.filter(h -> !taken.contains(h) && computed.stream().anyMatch(m -> m.takesCurrentMember(h)))
.findFirst();
+ if (byMeasures && hierarchy.isEmpty()) {
+ hierarchy = filters.getKey().stream().filter(h -> !taken.contains(h)).findFirst();
+ }
if (hierarchy.isEmpty()) {
sets.add(set);
continue;
@@ -207,7 +219,8 @@ private static String slicer(Map, List> sliced, Set outer = List.copyOf(filters);
addFilters(call.arguments().subList(1, call.arguments().size()), filters, false);
+ List added = filters.stream().filter(f -> !outer.contains(f)).toList();
+ within = added.size() == 1 && filters.size() > 1 ? Optional.of(added.getFirst()) : Optional.empty();
source = call.arguments().get(0);
}
TablePlan table = table(source);
if (filters.isEmpty()) {
return table;
}
- return calculated(table, filters);
+ return calculated(table, filters, within);
}
/**
@@ -1117,10 +1122,13 @@ private void collectColumns(DaxExpression expression, Set columns)
}
/**
+ * @param within the filter table computed within all the others, as the only
+ * one of an inner CALCULATETABLE; empty if none is
* @return the table computed with the filter tables too, as CALCULATETABLE
* computes it: they filter all the cube computes for it
*/
- private static TablePlan calculated(TablePlan table, List filters) throws DaxSemanticException {
+ private static TablePlan calculated(TablePlan table, List filters, Optional within)
+ throws DaxSemanticException {
return switch (table) {
case Summarize summarize -> {
List all = new ArrayList<>(summarize.filters());
@@ -1142,7 +1150,8 @@ private static TablePlan calculated(TablePlan table, List filters) th
}
all = together;
// a filter by a measure would be computed within the others, or they within it
- if (all.size() > 1 && all.stream().anyMatch(Binder::filtersByMeasure)) {
+ if (all.size() > 1 && all.stream().anyMatch(Binder::filtersByMeasure)
+ && !computedWithinOthers(summarize, all, within)) {
throw notSupported("CALCULATETABLE with several filters, of them one by a measure");
}
Summarize calculated = new Summarize(summarize.groupBy(), summarize.measures(), summarize.condition(),
@@ -1168,23 +1177,23 @@ private static TablePlan calculated(TablePlan table, List filters) th
yield calculated;
}
// the rows are computed as before, of the filtered source
- case Filter filter -> new Filter(calculated(filter.source(), filters), filter.condition());
- case TopN topN -> new TopN(calculated(topN.source(), filters), topN.count(), topN.keys(), topN.partition());
- case Sample sample -> new Sample(calculated(sample.source(), filters), sample.count(), sample.keys());
+ case Filter filter -> new Filter(calculated(filter.source(), filters, within), filter.condition());
+ case TopN topN -> new TopN(calculated(topN.source(), filters, within), topN.count(), topN.keys(), topN.partition());
+ case Sample sample -> new Sample(calculated(sample.source(), filters, within), sample.count(), sample.keys());
case Rollup rollup -> {
List levels = new ArrayList<>();
for (TablePlan level : rollup.levels()) {
- levels.add(calculated(level, filters));
+ levels.add(calculated(level, filters, within));
}
yield new Rollup(levels, rollup.keys(), rollup.rolled());
}
case AddColumns addColumns ->
- new AddColumns(calculated(addColumns.source(), filters), addColumns.width(), addColumns.added());
+ new AddColumns(calculated(addColumns.source(), filters, within), addColumns.width(), addColumns.added());
// constants are not filtered
case ConstantTable constant -> constant;
// by a property without its level, the grouping by all its columns filtered as one
case Generate generate when computesByProperties(generate) && grouping(generate).isPresent() ->
- calculated(grouping(generate).get(), filters);
+ calculated(grouping(generate).get(), filters, within);
// the outer rows the filters keep, each joined with the inner ones computed for it
case Generate generate -> {
TablePlan outer = generate.outer();
@@ -1228,6 +1237,25 @@ case Generate generate when computesByProperties(generate) && grouping(generate)
};
}
+ /**
+ * @param filters the filter tables of the measures without groups, as of
+ * {@code ROW}
+ * @param within the filter table computed within all the others
+ * @return whether it is the only one by a measure, on hierarchies none of
+ * the others is: its members are the slicer, computed within those
+ * the others keep, see MdxGenerator
+ */
+ private static boolean computedWithinOthers(Summarize summarize, List filters,
+ Optional within) {
+ if (!summarize.groupBy().isEmpty() || within.isEmpty() || !filters.contains(within.get())) {
+ return false;
+ }
+ Set hierarchies = new HashSet<>();
+ Summarize.filterColumns(within.get()).forEach(c -> hierarchies.add(c.hierarchy()));
+ return filters.stream().filter(f -> !f.equals(within.get())).noneMatch(f -> filtersByMeasure(f)
+ || Summarize.filterColumns(f).stream().anyMatch(c -> hierarchies.contains(c.hierarchy())));
+ }
+
/**
* @param filter a filter table
* @return whether it is on the text of columns of tables other than those
diff --git a/engine.impl/src/test/java/org/eclipse/daanse/dax/engine/impl/mdx/MdxGeneratorTest.java b/engine.impl/src/test/java/org/eclipse/daanse/dax/engine/impl/mdx/MdxGeneratorTest.java
index ee642bb..e8d9327 100644
--- a/engine.impl/src/test/java/org/eclipse/daanse/dax/engine/impl/mdx/MdxGeneratorTest.java
+++ b/engine.impl/src/test/java/org/eclipse/daanse/dax/engine/impl/mdx/MdxGeneratorTest.java
@@ -827,6 +827,46 @@ EVALUATE CALCULATETABLE(ROW("MeasuresEmployeeSalary", 'Measures'[Employee Salary
+ "WHERE ([Employees].[Employees1].[DAX slicer 1])");
}
+ @Test
+ void filterByMeasuresOfAnInnerCalculateTableIsComputedWithinTheOuterFilters() throws Exception {
+ ModelColumn year = new ModelColumn("Time", "Time.Time.Year", "[Time].[Time]", "[Time].[Time].[Year]", 1,
+ DaxType.STRING);
+ TabularModel model = new TabularModel("[Sales]", List.of(new ModelTable("Time", List.of(year)),
+ new ModelTable("Customers", List.of(customerLevel("Name", 4)))),
+ List.of(new ModelMeasure("Profit", "[Measures].[Profit]"),
+ new ModelMeasure("Store Cost", "[Measures].[Store Cost]", true),
+ new ModelMeasure("Unit Sales", "[Measures].[Unit Sales]", true)));
+ String dax = """
+ EVALUATE CALCULATETABLE(CALCULATETABLE(ROW("M", 'Measures'[%1$s]),
+ KEEPFILTERS(FILTER(KEEPFILTERS(VALUES('Time'[Time.Time.Year])),
+ NOT(ISBLANK('Measures'[%2$s]))))),
+ KEEPFILTERS(FILTER(KEEPFILTERS(VALUES('Customers'[Customers.Customers.Name])),
+ 'Customers'[Customers.Customers.Name] = "A. Catherine Binkley")))""";
+ String customer = "MEMBER [Customers].[Customers].[DAX slicer 1] AS Aggregate(Filter("
+ + "[Customers].[Customers].[Name].Members, UCase([Customers].[Customers].CurrentMember.Name) = "
+ + "\"A. CATHERINE BINKLEY\")), SOLVE_ORDER = -200 ";
+ String slicer = " WHERE ([Customers].[Customers].[DAX slicer 1], [Time].[Time].[DAX slicer 2])";
+ // the years where the customer's profit is not BLANK: the customer aggregated within them
+ QueryPlan plan = bindCustomers(model, dax.formatted("Profit", "Profit"));
+ assertThat(MdxGenerator.summarize("[Sales]", (Summarize) plan.evaluates().get(0).table()).text())
+ .isEqualTo("WITH " + customer + "MEMBER [Time].[Time].[DAX slicer 2] AS Aggregate(Filter("
+ + "[Time].[Time].[Year].Members, NOT IsEmpty([Measures].[Profit]))), SOLVE_ORDER = -100 "
+ + "SELECT {[Measures].[Profit]} ON COLUMNS FROM [Sales]" + slicer);
+ // of stored measures too, which take no current member
+ plan = bindCustomers(model, dax.formatted("Store Cost", "Unit Sales"));
+ assertThat(MdxGenerator.summarize("[Sales]", (Summarize) plan.evaluates().get(0).table()).text())
+ .isEqualTo("WITH " + customer + "MEMBER [Time].[Time].[DAX slicer 2] AS Aggregate(Filter("
+ + "[Time].[Time].[Year].Members, NOT IsEmpty([Measures].[Unit Sales]))), SOLVE_ORDER = -100 "
+ + "SELECT {[Measures].[Store Cost]} ON COLUMNS FROM [Sales]" + slicer);
+ // beside the other filter it is computed without it
+ assertThatThrownBy(() -> bindCustomers(model, """
+ EVALUATE CALCULATETABLE(ROW("M", 'Measures'[Profit]),
+ KEEPFILTERS(FILTER(KEEPFILTERS(VALUES('Time'[Time.Time.Year])), NOT(ISBLANK('Measures'[Profit])))),
+ KEEPFILTERS(FILTER(KEEPFILTERS(VALUES('Customers'[Customers.Customers.Name])),
+ 'Customers'[Customers.Customers.Name] = "A. Catherine Binkley")))"""))
+ .hasMessageContaining("CALCULATETABLE with several filters, of them one by a measure");
+ }
+
@Test
void filtersOfGenerateOnColumnsOfItsInnerTableRestrictTheInnerTable() throws Exception {
TabularModel model = new TabularModel("[C]", List.of(new ModelTable("Customers", List.of(