Identify a query parameter across its occurrences in AstParameterMarker equality

XMLWordPrintableJSON

    • Type: Improvement
    • Resolution: Fixed
    • Priority: Major - P3
    • 1.0.0
    • Affects Version/s: None
    • Component/s: Query
    • None
    • 1
    • None
    • Not Needed
    • None
    • None
    • None
    • None
    • None
    • None

      Problem

      AstParameterMarker is a record whose only component is the JdbcParameterBinder, so record equality is binder identity. Hibernate allocates a separate JdbcParameter per occurrence of a query parameter, which makes two cases indistinguishable: one query parameter used twice, and two different parameters whose surrounding expressions happen to be structurally identical.

      Neither binder identity nor bound value can tell them apart. Measured against a local mongod, for a query using one named parameter in two clauses, the translator receives two JdbcParameter nodes and JdbcParameterBindings returns two distinct JdbcParameterBinding instances for them, both holding the same value. Values cannot serve either, since two different parameters may be bound to equal values and the translated pipeline is cached across executions.

      Fix

      JdbcParameter#getParameterId() is declared on the public SQL AST interface org.hibernate.sql.ast.tree.expression.JdbcParameter, so no cast to an internal class is needed. BaseSqmToSqlAstConverter.resolveSqmParameter reuses the id of an already-resolved parameter's first occurrence, so every occurrence of one query parameter shares an id, and distinct parameters get distinct ids. Measured ids: the same named parameter twice gives 0 and 0; :a and :b give 0 and 1; the same parameter in SELECT and in WHERE gives 0 and 0; ?1 twice gives 0 and 0.

      Carry the id on the marker and define equality on it: when both ids are present, compare ids; otherwise require both to be absent and the binder to be identical. The id is nullable by contract and several implementors supply none, so a null id must not match another null id from a different parameter, and the fallback keeps equals reflexive.

      Why it matters

      Nothing in the translator compares markers today, so there is no user-visible symptom. It will come into play for matching a GROUP BY expression against its occurrences in SELECT, HAVING, and ORDER BY (HIBERNATE-241), which rests on structural equality of translated expressions and therefore reduces to marker equality whenever a grouped expression contains a parameter. Without it, a query such as

      select substring(b.string, 1, :len) from Item b group by substring(b.string, 1, :len)
      

      fails to match its own group key. The failure direction is safe, a rejection rather than a wrong result, but the query is legal and should work.

      Scope

      Add the component, define the equality rule, pass the id at both construction sites, and unit-test the contract, including the null cases. No other behavior changes.

            Assignee:
            Jeffrey Yemin
            Reporter:
            Jeffrey Yemin
            Slav Babanin
            Votes:
            0 Vote for this issue
            Watchers:
            1 Start watching this issue

              Created:
              Updated:
              Resolved: