Class CoreExpressionSqlHelper
CoreExpressionSqlHelper provides a couple of utilities to support the conversion of a CoreExpression into an SQL-expression (e.g.
construction of IN-clauses)- Author:
- Karl Eilebrecht
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionprotected static final recordThis avoids that the helper holds a reference to the context while the context knows the helper. -
Field Summary
FieldsModifier and TypeFieldDescriptionprotected final Comparator<de.calamanari.adl.irl.CoreExpression> A comparator that ascendingly orders expressions first aftercomplexityOf(CoreExpression)protected final DataBindingData binding (from context)protected final de.calamanari.adl.ProcessContextReference to variables and flagsprotected final de.calamanari.adl.irl.CoreExpressionreference to the expression currently being converted, required for logic checksprotected final intbinary root node for quick accessprotected final CoreExpressionStatsStatistics about the current expressionprotected final de.calamanari.adl.TimeOutSafety: avoid combinatoric explosion and runawayprotected final de.calamanari.adl.irl.biceps.EncodedExpressionTreeTree from the root expression, not meant to be modified but for comparison -
Constructor Summary
ConstructorsConstructorDescriptionCoreExpressionSqlHelper(de.calamanari.adl.irl.CoreExpression rootExpression, de.calamanari.adl.TimeOut timeout, DataBinding dataBinding, de.calamanari.adl.ProcessContext processContext) Analyzes the given expression, gathers statistics and creates a fresh helper instance. -
Method Summary
Modifier and TypeMethodDescriptionbooleancheckLeftSupersetOfRight(de.calamanari.adl.irl.CoreExpression left, de.calamanari.adl.irl.CoreExpression right) Tests whether the left expression must be true if the right shall be true, in other words right implies left.doublecomplexityOf(de.calamanari.adl.irl.CoreExpression expression) Estimates the complexity of the given expression based on the nesting, type of sub-expressions and the database mappingList<de.calamanari.adl.irl.CoreExpression> consolidateAliasGroupExpressions(List<de.calamanari.adl.irl.CoreExpression> members) Consolidates the given members to prepare the expressions for the creation of a union or an ON-join-condition.List<de.calamanari.adl.irl.CoreExpression> findMinimumRequiredOrCombination(List<de.calamanari.adl.irl.CoreExpression> candidates, int limit) Tries to find the shortest OR-combination of candidates that guarantees the root expression to be true.getStats()Returns the initially gathered statistics about the root expression currently being converted.booleangroupInClauses(de.calamanari.adl.irl.CombinedExpression expression, List<List<de.calamanari.adl.irl.SimpleExpression>> groups, List<de.calamanari.adl.irl.CoreExpression> remaining) This method takes aCombinedExpression(AND vs.booleanisEligibleForBaseQuery(de.calamanari.adl.irl.CoreExpression expression) The query related to an expression is eligible to serve as a base query if it is either a (negated) match against a non-null value (except forisNullQueryingAllowed(String)) or a reference match or a (NOT) IN clause.booleanisExtraExistenceMatchRequired(MatchCondition condition) Tells whether we must add an extra "has any value check".booleanisMultiRowSensitiveMatch(MatchCondition condition) This method checks if there are multi-row sensitive arguments involved in the given expression.booleanisNullQueryingAllowed(String argName) Tells whether we can safely translate IS UNKNOWN into IS NULL for a given argName, and the result could serve as a base query.booleanisPrimaryTableInvolved(de.calamanari.adl.irl.SimpleExpression expression) Determines if the primary table is involved in the SQL that maps to the given expression.booleanisSimpleExpressionOnPrimaryTable(de.calamanari.adl.irl.CoreExpression expression) Determines if the given expression is aSimpleExpressionrelated to the primary table (either value match or by reference, left or right).static booleanisSubNested(de.calamanari.adl.irl.CoreExpression expression) Shorthand for checking if the given expression is aCombinedExpressionthat itself contains further levels ofCombinedExpressionsbooleanisSupersetOfRootExpression(de.calamanari.adl.irl.CoreExpression expression) Performs a logical check if the given expression is required to be true by the root expression resp. if the ID-set covered by the given expression is the same as or a superset of the ID-set covered by the root expression.voidsortByComplexityDescending(List<de.calamanari.adl.irl.CoreExpression> expressions) Sorts the given list by complexity descending, so the most complex expression is at the top.
-
Field Details
-
rootExpression
protected final de.calamanari.adl.irl.CoreExpression rootExpressionreference to the expression currently being converted, required for logic checks -
rootNode
protected final int rootNodebinary root node for quick access -
tree
protected final de.calamanari.adl.irl.biceps.EncodedExpressionTree treeTree from the root expression, not meant to be modified but for comparison -
timeout
protected final de.calamanari.adl.TimeOut timeoutSafety: avoid combinatoric explosion and runaway -
stats
Statistics about the current expression -
dataBinding
Data binding (from context) -
processContext
protected final de.calamanari.adl.ProcessContext processContextReference to variables and flags -
complexityComparator
A comparator that ascendingly orders expressions first aftercomplexityOf(CoreExpression)
-
-
Constructor Details
-
CoreExpressionSqlHelper
public CoreExpressionSqlHelper(de.calamanari.adl.irl.CoreExpression rootExpression, de.calamanari.adl.TimeOut timeout, DataBinding dataBinding, de.calamanari.adl.ProcessContext processContext) Analyzes the given expression, gathers statistics and creates a fresh helper instance.- Parameters:
rootExpression-timeout- if null we will use the default:TimeOut.createDefaultTimeOut(String)dataBinding- physical table bindingprocessContext- (hints will be added to global flags)
-
-
Method Details
-
findMinimumRequiredOrCombination
public List<de.calamanari.adl.irl.CoreExpression> findMinimumRequiredOrCombination(List<de.calamanari.adl.irl.CoreExpression> candidates, int limit) Tries to find the shortest OR-combination of candidates that guarantees the root expression to be true.To avoid combinatoric explosion and excessive execution times it is strongly recommended to set a limit which defines the maximum number of elements in a group (OR) to be tested.
The success of this approach is limited in the same way as
ExpressionLogicHelper.leftImpliesRight(int, int), so it is neither guaranteed that we can find any solution nor that it will be the shortest possible.- Parameters:
candidates-limit- maximum number of elements in a test group (OR members), implicitly limited by the number of candidates- Returns:
- list of candidates that forms an OR that must be true to let the root expression become true, may be empty, not null
-
isSupersetOfRootExpression
public boolean isSupersetOfRootExpression(de.calamanari.adl.irl.CoreExpression expression) Performs a logical check if the given expression is required to be true by the root expression resp. if the ID-set covered by the given expression is the same as or a superset of the ID-set covered by the root expression.This information is required to decide if the expression (resp. the related SQL query) can serve as a base query to start the selection.
Example: If
color = blue AND shape = circleis the root expression then logicallycolor = blueis required by the root expression.
The related SQL-query could start with the sub set of records with blue color and then apply further restrictions (shape = circle).In other words: the given expression cannot be false if the root expression is fulfilled.
- Parameters:
expression-- Returns:
- true if the given expression must be true to fulfill the root expression
-
checkLeftSupersetOfRight
public boolean checkLeftSupersetOfRight(de.calamanari.adl.irl.CoreExpression left, de.calamanari.adl.irl.CoreExpression right) Tests whether the left expression must be true if the right shall be true, in other words right implies left.- Parameters:
left-right-- Returns:
- true if left cannot be false if right shall be true
-
groupInClauses
public boolean groupInClauses(de.calamanari.adl.irl.CombinedExpression expression, List<List<de.calamanari.adl.irl.SimpleExpression>> groups, List<de.calamanari.adl.irl.CoreExpression> remaining) This method takes aCombinedExpression(AND vs. OR) and tries to find groups of members related to the same argName to form an IN (in case of OR) vs. a NOT IN (in case of AND).Each group is represented by list of
SimpleExpression. In case of a given OR the group members are allMatchExpressions (positive), if the expression was an AND, then the group members will beNegationExpressions (negative).Example:
a=1 OR a=2 OR a=3 OR b=6 OR c=7 OR c=8 => groups: (a=1, a=2, a=3), (c=7, c=8); remaining: b=6 a!=1 AND a!=2 AND a!=3 AND b=6 AND c!=7 AND c!=9 AND d=8 => groups: (a!=1, a!=2, a!=3), (c!=7, c!=9); remaining: b=6, d=8Any members of typeCombinedExpressionalways go to the remaining list. This method does not operate recursively.- Parameters:
expression-groups- each list represents a group (IN or NOT IN clause)remaining- all members of the given combined expression that were not assigned to any group- Returns:
- true if at least one group has been identified
-
getStats
Returns the initially gathered statistics about the root expression currently being converted.- Returns:
- expression statistics
-
isExtraExistenceMatchRequired
Tells whether we must add an extra "has any value check".In case of collections (multi-row) a simple condition like
TBL.COLOR != 'blue'turns into the more complicated question NOT has any row withTBL.COLOR = 'blue'.This creates a serious problem: any row with
TBL.COLOR = NULL(either explicitly or via join) gets included in the result (wrong!).
In such a scenario we need an extra check for existence to correctly exclude the NULLs again.- Parameters:
condition-- Returns:
- true if an extra existence match is required
-
isMultiRowSensitiveMatch
This method checks if there are multi-row sensitive arguments involved in the given expression.Example I: IS NULL problem
+------+-------+--------+ | ID | COLOR | SHAPE | +------+-------+--------+ | 1356 | red | circle | +------+-------+--------+ | 1356 | blue | NULL | +------+-------+--------+ | ... | ... | ... |
Let's say you queryTBL.SHAPE IS NULLfor "shape IS UNKNOWN". This does not reflect the truth because there is some other row with shape = circle (so shape is not unknown for 1356). You must ensure that there is not any row with a value for shape.Example II: Accidental condition row-pinning
+------+-------+--------+ | ID | COLOR | SHAPE | +------+-------+--------+ | 1356 | red | circle | +------+-------+--------+ | 1356 | blue | NULL | +------+-------+--------+ | ... | ... | ... |
Let's say there are two conditionsTBL.SHAPE = 'circle'and (different MatchCondition)TBL.COLOR = 'blue'.
If you now writeTBL.SHAPE = 'circle' AND TBL.COLOR = 'blue'the alias would becomeTBL.SHAPE = 'circle' OR TBL.COLOR = 'blue'and the global WHERE would containTBL.SHAPE = 'circle' AND TBL.COLOR = 'blue'. This is wrong, because the data sits on different rows, not a single row of the table meets the combined condition. To address this problem you must INTERSECT the IDs withTBL.SHAPE = 'circle'andTBL.COLOR = 'blue'.Example III: Accidental reference row-pinning
+------+--------+--------+ | ID | SHAPE1 | SHAPE2 | +------+--------+--------+ | 1356 | box | circle | +------+--------+--------+ | 1356 | circle | NULL | +------+--------+--------+ | ... | ... | ... |
This time you query
TBL.SHAPE1 = TBL.SHAPE2. This is incorrect, again because the data does not sit on the same row. It is required to restructure the query to select all IDs which have any match withSHAPE1 = SHAPE2.Example IV: Has not any
+------+-------+--------+ | ID | COLOR | SHAPE | +------+-------+--------+ | 1356 | red | circle | +------+-------+--------+ | 1356 | blue | square | +------+-------+--------+ | ... | ... | ... |
This time you query
TBL.COLOR != 'red'. This is wrong again. Here you must exclude any row with the value 'red'.In all the examples the generated query must consider extra joins to produce correct results.
- Parameters:
condition-- Returns:
- true if any of the involved argNames is marked multi-row sensitive
-
isEligibleForBaseQuery
public boolean isEligibleForBaseQuery(de.calamanari.adl.irl.CoreExpression expression) The query related to an expression is eligible to serve as a base query if it is either a (negated) match against a non-null value (except forisNullQueryingAllowed(String)) or a reference match or a (NOT) IN clause.The record set returned by a base query must be superset of the records the root expression wants to select.
- Parameters:
expression-- Returns:
- true if the given expression can serve as base query
- See Also:
-
isSubNested
public static boolean isSubNested(de.calamanari.adl.irl.CoreExpression expression) Shorthand for checking if the given expression is aCombinedExpressionthat itself contains further levels ofCombinedExpressions- Parameters:
expression-- Returns:
- true if there is further nesting
-
isNullQueryingAllowed
Tells whether we can safely translate IS UNKNOWN into IS NULL for a given argName, and the result could serve as a base query.This is only the case if the argument is not multi-row sensitive and the table contains all rows, because only under these conditions we know there is exactly one row in that table for any valid ID in the base audience. If the value is unknown, the column value will be null, so that a query with
TBL.COL IS NULLwill correctly identify the records we are looking for.- Parameters:
argName-- Returns:
- true if the argument can be queried with IS NULL for a base query
-
complexityOf
public double complexityOf(de.calamanari.adl.irl.CoreExpression expression) Estimates the complexity of the given expression based on the nesting, type of sub-expressions and the database mapping- Parameters:
expression-- Returns:
- complexity value >=1.0
-
consolidateAliasGroupExpressions
public List<de.calamanari.adl.irl.CoreExpression> consolidateAliasGroupExpressions(List<de.calamanari.adl.irl.CoreExpression> members) Consolidates the given members to prepare the expressions for the creation of a union or an ON-join-condition.An SQL UNION is nothing but a logical OR, so we may be able to merge IN-clauses, create these or eliminate redundancies
- Parameters:
members- expressions all related to the same table, to be consolidated for the inclusion in a UNION or an ON-join-condition- Returns:
- consolidated list of expressions
-
sortByComplexityDescending
Sorts the given list by complexity descending, so the most complex expression is at the top.- Parameters:
expressions- to be sorted in descending order- See Also:
-
isSimpleExpressionOnPrimaryTable
public boolean isSimpleExpressionOnPrimaryTable(de.calamanari.adl.irl.CoreExpression expression) Determines if the given expression is aSimpleExpressionrelated to the primary table (either value match or by reference, left or right).- Parameters:
expression- to be checked- Returns:
- true if this expression only involves the primary table
-
isPrimaryTableInvolved
public boolean isPrimaryTableInvolved(de.calamanari.adl.irl.SimpleExpression expression) Determines if the primary table is involved in the SQL that maps to the given expression.- Parameters:
expression- to be checked- Returns:
- true if either the left table or the right table (in case of a reference match) is the primary table
-