Hibernate arroja “No data type for node: org.hibernate.hql.ast.tree.AggregateNode”

A pesar de que el problema que se describe puede parecer algo específico para un caso dado, arroja muchas luces sobre cómo Hibernate maneja la relación objeto-relacional y cómo parsea la consulta HQL para su transformación al dialecto SQL específico.
El problema en sí consiste en una excepción al crear un nuevo Query con esta consulta HQL:

from Semestre sem where :fecha - sem.fechaTermino = 
(select min(:fecha - sem.fechaTermino) from Semestre sem 
where (:fecha - sem.fechaTermino) > 0 )

Esta consulta anidada busca aquel semestre cuya fecha de término está más cercana en el pasado a la fecha enviada como parámetro (:fecha que luego será reemplazada).

Al intentar ejecutar esta consulta, Hibernate arroja una excepción con la siguiente información relevante:

11:34:54,218 INFO  [STDOUT] Error al obtener semestres
 para busqueda :No data type for node: 
org.hibernate.hql.ast.tree.AggregateNode 
 \-[AGGREGATE] AggregateNode: 'min'
    \-[MINUS] BinaryArithmeticOperatorNode: '-' {dataType=null}
       +-[NAMED_PARAM] ParameterNode: '?' {name=fecha,
 expectedType=org.hibernate.type.DateType@ebe108}
       \-[DOT] DotNode: 'semestre1_.SEME_FCH_TERMINO' 
{propertyName=fechaTermino,dereferenceType=4,
propertyPath=fechaTermino,path=
sem.fechaTermino,tableAlias=semestre1_,className=
cl.miro.proyecto.Semestre,classAlias=sem}
          +-[ALIAS_REF] IdentNode: 'semestre1_.SEME_CDG'
 {alias=sem,  
              className=cl.miro.proyecto.Semestre,
 tableAlias=semestre1_}
               \-[IDENT] IdentNode: 'fechaTermino'
 {originalText=fechaTermino}

El código utilizado fue descompuesto en partes en lugar de hacer la clásica ejecución en una línea de Hibernate, quedando como se aprecia a continuación:

String query="from Semestre sem where :fecha - sem.fechaTermino = " +
    "(select min(:fecha - sem.fechaTermino) from Semestre sem " +
    "where (:fecha - sem.fechaTermino) > 0 )";
Query consulta=sessionHib.createQuery(query);
consulta=consulta.setDate("fecha", fecha);

Causa

Al revisar el bloque de texto anterior podemos ver la forma jerárquica (y en forma de árbol) en que Hibernate está descomponiendo la consulta HQL entregada, asignando un nodo a cada elemento disponible, incluyendo los puntos (DOT), el FROM, WHERE, etc.

El error se produce en el nodo de más arriba del texto, en este caso el nodo de tipo AggregateNode. Este tipo de nodo, como su nombre lo indica, representa las funciones agregadas tipo MIN(), MAX(), AVG(), etc.

En un principio la línea de investigación del problema fue orientada hacia la posibilidad de que la excepción se gatillaba al momento de ejecutar efectivamente la consulta. En código, al momento de hacer:

List lista=consulta.list();
Sin embargo, la ejecución paso a paso señaló que se gatillaba al momento de crear el Query, confirmando que era el parseo inicial de la consulta y no al momento de ser entregado el parámetro Date con la fecha a verificar.

Luego de revisar bastante documentación, encontramos que el problema está en que en una función agregada como MIN(), Hibernate necesita, al menos en este caso, que en la consulta se especifique un CAST diciéndole de qué tipo de dato será el resultado del min. Esto se hace con la función de HQL CAST(objeto AS tipo_dato_hibernate). De esta manera, la expresión conflictiva es:

… min(:fecha – sem.fechaTermino)…

Solución

En nuestro caso el MIN() devuelve un integer (diferencia de días), por lo que la expresión del mínimo debe quedar:

… cast(min(:fecha – sem.fechaTermino) as integer)…
Este integer es la versión de enteros de Hibernate. Con esto el parser ahora sabe que debe asignar al nodo correspondiente un valor Integer (de java) al momento del análisis, con lo que el parseo total funciona, y así también la consulta.