@Component public class ChartDataFinder extends Object
This is the class that will find the data matching the criteria supplied as a ChartDefinition, using the Querydsl engine.
A ChartDefinition defines what you need to formulate the query :
MeasureColumns)AxisColumns)Filters)Based on this specification the ChartDataFinder will design the query plan and run it. The rest of this javadoc
is a technical documentation of its internal processes.
Here we explain how these roles will be used in a query :
In a query they naturally fit the role of a where clause. However a regular where clause is a simple case : sometimes you would have it within a Having clause, and in other times it could be a whole subquery.
These columns will be grouped on (in the group by clause). They also appear in the select clause and should of course not be subject to any aggregation function. In the select clause, they will appear first, and keep the same order as in the list defined in the ChartDefinition.
These columns appear in the select clause and will be subject to an aggregation method. The aggregation is specified in the MeasureColumn. They appear in the select clause, after the axis columns, and keep the same order as in the list defined in the ChartDefinition.
Most of the time no specific attribute will be specified : in this case the measure column defaults to the id of the observed entity. For instance consider a measure which aggregation is 'sum' (count). If the user is interested to know how many test cases match the given filters, the aggregation should be made on the test case ids. However if the user picked something more specific - like the test case labels -, the semantics becomes how many different labels exist within the test cases that match the filter. This, of course, is a problem for the tool that will design the ChartDefinition : the current class will just create and process the query.
The global query is composed of one main query and several optional subqueries depending on the filters.
The total domain covered by any possible ChartDefinition is the following :
| Campaign | <-> | Iteration | <-> | IterationTestPlanItem | <-> | TestCase | <-> | (RequirementVersionCoverage) | <-> | RequirementVersion | <-> | Requirement |
| Issue | <-> | Execution | <-- | ^ |
Depending on the ChartDefinition, a main query will be generated as a subset of this domain. The specifics of its construction depend on the "Root entity", "Target entities" and "Support entities", those concepts are defined below.
see QueryPlanner.
The main building blocks that defines the main query are the following :
The main query is thus defined as the minimal subset of the domain that join all the Target entities together via Support Entities, starting with the Root entity. All joins in this query will be inner joins (no left nor right joins).
Clarification about the Scope and the Main Query (custom scopes only, TM 1.14): As of TM 1.14 the Scope is now included in order to force a natural joins on the scoped entity. Indeed, when the user defines a query on which the scoped entity is neither used in a Filter, Axis or Measure, the resulting data is void because the Root Entity or Support entities are indeed out of the scope. This decision is only half satisfactory : the definitive solution would be to actually reify and handle a Domain on which the query should innerjoin on, but for now this trick will avoid the main problem (ie the case of empty resultset). See more with tickets #6260 and #6275
The select clause must of course contain the MeasureColumns with their appropriate aggregation function (like count(distinct ), avg(distinct ) etc). For technical reasons they must also include the AxisColumns, because theses are the column on which a row is grouped by.
Filters are restriction applied on the tuples returned by the main query. At the atomic level a filter is a combination of a column, an comparison operator, and one/several operands. They are translated in the appropriate Querydsl expression, bound together by appropriate logical operators then inserted in the main query.
Mostly they are no more complex than "where" clauses, but in some cases a subquery is required. Filters are treated according to the following process :
Each Filter apply on one column (eg, TestCase.label). Typically, multiple filters will apply on several columns. However one can stack multiple Filters on the same column, (eg TestCase.label = 'bob', TestCase.label = 'mike').
For each entity, the filters are thus combined according to the following rules :
In the simplest cases the filters will be inlined in the main query, if the column is of type ColumnType.ATTRIBUTE
In more complex cases a subquery will be required. The decision is driven by the attribute 'columnType' of the ColumnPrototype
referenced by the filters : if at least one of them is of type ColumnType.CUF or ColumnType.CALCULATED
then one/several subqueries will be used. We need them for the following reasons :
Subqueries have them own Query plan, and are joined with the main query as follow : the bean of the outer query that defines the calculated attribute will join with the root entity of the subquery (usually its axis). We choose to use correlated subqueries (joining them as described above) even when an uncorellated would do fine, because in practice a clause
where exists (select 1 from ... where ... and inner_col = outer_col)will outperform
where bean.id in (select id from .... where ...)by several order of magnitude (especially because in the former the DB can then use the indexed primary keys).
Data will be grouped on each AxisColumn in the given order. Special care is given for columns of
type DataType.DATE : indeed the desired level of aggregation may be day, month etc. We must be watchful
of not grouping together every month of December accross the years. For this reason data grouped by Day will
actually grouped by (year,month,day). Same goes for grouping by month, which actually mean grouping by (year,month)
The result will be an array of array of Object. Each row represents a tuple of (x+m) cells, where x = card(AxisColumn) and y = card(MeasureColumn). The first batch of cells are those of the AxisColumns, the second batch are those of the MeasureColumns.
Note : a more appropriate representation would be one serie per MeasureColumn, with each tuple made of the x axis cells and 1 measure cell. However we prefer to remain agnostic on how the resultset will be interpreted : series might not be the preferred way to consume the data after all.
Additionally, another special filter will be processed : the Scope, ie the subset of RootEntity on which the chart query will be applied to. At runtime it is refined into an Effective Scope, which is the conjunction of :
The Effective Scope will be computed then added to the query after is has been generated. See ScopePlanner for details on what is going on.
| Constructor and Description |
|---|
ChartDataFinder() |
| Modifier and Type | Method and Description |
|---|---|
org.squashtest.tm.domain.chart.ChartSeries |
findData(org.squashtest.tm.domain.chart.ChartDefinition definition,
List<org.squashtest.tm.domain.EntityReference> dynamicScope,
Long dashboardId,
Long milestoneId,
org.squashtest.tm.domain.Workspace workspace) |
Copyright © 2010–2017 Henix, henix.fr. All rights reserved.