Class CoreExpressionSqlHelper

java.lang.Object
de.calamanari.adl.sql.cnv.CoreExpressionSqlHelper

public class CoreExpressionSqlHelper extends Object
The 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 Classes
    Modifier and Type
    Class
    Description
    protected static final record 
    This avoids that the helper holds a reference to the context while the context knows the helper.
  • Field Summary

    Fields
    Modifier and Type
    Field
    Description
    protected final Comparator<de.calamanari.adl.irl.CoreExpression>
    A comparator that ascendingly orders expressions first after complexityOf(CoreExpression)
    protected final DataBinding
    Data binding (from context)
    protected final de.calamanari.adl.ProcessContext
    Reference to variables and flags
    protected final de.calamanari.adl.irl.CoreExpression
    reference to the expression currently being converted, required for logic checks
    protected final int
    binary root node for quick access
    protected final CoreExpressionStats
    Statistics about the current expression
    protected final de.calamanari.adl.TimeOut
    Safety: avoid combinatoric explosion and runaway
    protected final de.calamanari.adl.irl.biceps.EncodedExpressionTree
    Tree from the root expression, not meant to be modified but for comparison
  • Constructor Summary

    Constructors
    Constructor
    Description
    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.
  • Method Summary

    Modifier and Type
    Method
    Description
    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.
    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
    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.
    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.
    Returns the initially gathered statistics about the root expression currently being converted.
    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 a CombinedExpression (AND vs.
    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 for isNullQueryingAllowed(String)) or a reference match or a (NOT) IN clause.
    boolean
    Tells whether we must add an extra "has any value check".
    boolean
    This method checks if there are multi-row sensitive arguments involved in the given expression.
    boolean
    Tells whether we can safely translate IS UNKNOWN into IS NULL for a given argName, and the result could serve as a base query.
    boolean
    isPrimaryTableInvolved(de.calamanari.adl.irl.SimpleExpression expression)
    Determines if the primary table is involved in the SQL that maps to the given expression.
    boolean
    isSimpleExpressionOnPrimaryTable(de.calamanari.adl.irl.CoreExpression expression)
    Determines if the given expression is a SimpleExpression related to the primary table (either value match or by reference, left or right).
    static boolean
    isSubNested(de.calamanari.adl.irl.CoreExpression expression)
    Shorthand for checking if the given expression is a CombinedExpression that itself contains further levels of CombinedExpressions
    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.
    void
    sortByComplexityDescending(List<de.calamanari.adl.irl.CoreExpression> expressions)
    Sorts the given list by complexity descending, so the most complex expression is at the top.

    Methods inherited from class java.lang.Object

    clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
  • Field Details

    • rootExpression

      protected final de.calamanari.adl.irl.CoreExpression rootExpression
      reference to the expression currently being converted, required for logic checks
    • rootNode

      protected final int rootNode
      binary root node for quick access
    • tree

      protected final de.calamanari.adl.irl.biceps.EncodedExpressionTree tree
      Tree from the root expression, not meant to be modified but for comparison
    • timeout

      protected final de.calamanari.adl.TimeOut timeout
      Safety: avoid combinatoric explosion and runaway
    • stats

      protected final CoreExpressionStats stats
      Statistics about the current expression
    • dataBinding

      protected final DataBinding dataBinding
      Data binding (from context)
    • processContext

      protected final de.calamanari.adl.ProcessContext processContext
      Reference to variables and flags
    • complexityComparator

      protected final Comparator<de.calamanari.adl.irl.CoreExpression> complexityComparator
      A comparator that ascendingly orders expressions first after complexityOf(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 binding
      processContext - (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 = circle is the root expression then logically color = blue is 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 a CombinedExpression (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 all MatchExpressions (positive), if the expression was an AND, then the group members will be NegationExpressions (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=8
       
      Any members of type CombinedExpression always 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

      public CoreExpressionStats getStats()
      Returns the initially gathered statistics about the root expression currently being converted.
      Returns:
      expression statistics
    • isExtraExistenceMatchRequired

      public boolean isExtraExistenceMatchRequired(MatchCondition condition)
      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 with TBL.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

      public boolean isMultiRowSensitiveMatch(MatchCondition condition)
      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 query TBL.SHAPE IS NULL for "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 conditions TBL.SHAPE = 'circle' and (different MatchCondition) TBL.COLOR = 'blue'.
      If you now write TBL.SHAPE = 'circle' AND TBL.COLOR = 'blue' the alias would become TBL.SHAPE = 'circle' OR TBL.COLOR = 'blue' and the global WHERE would contain TBL.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 with TBL.SHAPE = 'circle' and TBL.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 with SHAPE1 = 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 for isNullQueryingAllowed(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 a CombinedExpression that itself contains further levels of CombinedExpressions
      Parameters:
      expression -
      Returns:
      true if there is further nesting
    • isNullQueryingAllowed

      public boolean isNullQueryingAllowed(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.

      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 NULL will 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

      public void sortByComplexityDescending(List<de.calamanari.adl.irl.CoreExpression> expressions)
      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 a SimpleExpression related 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