Hibernate version: 3.2.5.ga
See below for version numbers etc. and cut-and-paste error example -- but a preamble first. I hope my experiences can help other people who run into this kind of problem -- credits appreciated ;-)
I just spent half a day trying to track down the cause of a NullPointerException in the org.hibernate.dialect.Dialect class which occurred while parsing a named HQL query. It turns out eventually that it's because of a difference in SQL dialects between my test database (HSQLDB) and my production database (PostgreSQL).
The natural logarithm function in Postgres is called ln(), and log() refers to base-10 logarithms. In HSQLDB, the natural log function is called log(), and the base-10 version is log10().
My query uses natural logs and was written with Postgres in mind, so contained the ln() function. This caused the HQL query parser to fail with a very unhelpful error (see below).
I see three significant issues here:
1. It's a bit worrying to see NPEs from a product as mature as Hibernate. Ideally, a message like "the function ln() is not recognized" would be good, but more importantly, it makes you wonder about parameter checking.
2. One of the benefits of Hibernate, I thought, was supposed to be its ability to insulate you from the implementation details of the database beneath. But if HSQL function names are just passed directly through to the SQL query, this benefit evaporates. I will have to rewrite my queries if I swap Postgres out for another RDBMS. Also, if I want to keep using HSQLDB for testing, I'll need to maintain two copies of each query that uses logarithms. And maybe other functions too? Who knows!
3. More worryingly still, imagine if I had originally written the query using log(). My tests would have passed, as log() is the correct natural-logs function in HSQLDB. Then I would have deployed my fully-tested app to the production environment, and my queries would have silently started to return the wrong results, because log() in PGSQL means base-10 logarithms. Ouch!
If anyone has any better suggestions about how to solve the problem of needing different HQL queries for test and production, I'd be very grateful. I thought I could work around it by using HSQLDB's CREATE ALIAS function to alias ln to java.lang.Math.log, but this makes absolutely no difference.
Update:
I discovered the configuration property hibernate.query.substitutions and thought that this would provide a workaround for the ln/log problem, surely. So I added this to my module configuration
.setProperty( "hibernate.query.substitutions", "ln log" )
and I know this is being processed, because the debug log now contains this line:
17:19:07,042 INFO SettingsFactory:225 - Query language substitutions: {ln=log}
However, the problem remains! Dialect still barfs on the ln() call when I'm using HSQLDB, completely ignoring the query susbsitution. WTF?
End update
Thanks,
Andrew.
Mapping documents:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="info.cathdb.funcnet_0_1.sessiondb.model.Session">
<meta attribute="class-description">Represents a user's FuncNet session.</meta>
<meta attribute="class-code"> public static final long serialVersionUID = 1L; // Suppress Eclipse warning</meta>
<id column="session_id" type="integer">
<meta attribute="scope-set">protected</meta>
<generator class="increment" />
</id>
<property name="funcnetJobID" type="string" not-null="true" update="true">
<meta attribute="use-in-tostring">true</meta>
<meta attribute="use-in-equals">true</meta>
<meta attribute="scope-set">protected</meta>
</property>
<property name="startTime" type="timestamp" not-null="true">
<meta attribute="scope-set">protected</meta>
</property>
<!-- property name="status" type="info.cathdb.funcnet_0_1.sessiondb.hib3impl.SessionStatusType" / -->
<property name="status" type="string" not-null="true" /><!-- temp workaround -->
<many-to-one column="client_id" name="client" class="info.cathdb.funcnet_0_1.sessiondb.model.Client" not-null="true" cascade="save-update">
<meta attribute="scope-set">protected</meta>
<meta attribute="use-in-tostring">true</meta>
</many-to-one>
<property name="statusMessage" type="string" />
<property name="comments" type="string">
<meta attribute="use-in-tostring">true</meta>
</property>
<property name="enableEmailNotify" type="boolean">
<meta attribute="scope-set">protected</meta>
</property>
<set name="predictors" cascade="all">
<key column="session_id" not-null="true" />
<one-to-many class="info.cathdb.funcnet_0_1.sessiondb.model.PredictorSession" />
</set>
<!-- Queries for score calculation - TODO -->
<query name="info.cathdb.funcnet_0_1.sessiondb.model.pairwiseScores">
<![CDATA[
select prediction.protein1,
prediction.protein2,
-2 * sum( ln( prediction.pValue ) )
from info.cathdb.funcnet_0_1.sessiondb.model.Session session
inner join session.predictors predictorSession
inner join predictorSession.predictions prediction
where session.funcnetJobID = :jobID
group by prediction.protein1,
prediction.protein2
]]>
</query>
</class>
</hibernate-mapping><?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="info.cathdb.funcnet_0_1.sessiondb.model.PredictorSession">
<meta attribute="class-description">Represents a FuncNet user's session on a specific predictor.</meta>
<meta attribute="class-code"> public static final long serialVersionUID = 1L; // Suppress Eclipse warning</meta>
<id column="predictor_session_id" type="integer">
<meta attribute="scope-set">protected</meta>
<generator class="increment" />
</id>
<property name="predictorName" type="string" not-null="true" />
<!-- property name="status" type="info.cathdb.funcnet_0_1.sessiondb.hib3impl.PredictorStatusType" not-null="true" / -->
<property name="status" type="string" not-null="true" /><!-- temp workaround -->
<property name="statusMessage" type="string" />
<set name="predictions" cascade="all">
<key column="predictor_session_id" not-null="true" />
<one-to-many class="info.cathdb.funcnet_0_1.sessiondb.model.Prediction" />
</set>
</class>
</hibernate-mapping><?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="info.cathdb.funcnet_0_1.sessiondb.model.Prediction">
<meta attribute="class-description">Represents a prediction about two proteins from a specific predictor in a specific session.</meta>
<meta attribute="class-code"> public static final long serialVersionUID = 1L; // Suppress Eclipse warning</meta>
<id column="prediction_id" type="integer">
<meta attribute="scope-set">protected</meta>
<generator class="increment" />
</id>
<many-to-one name="predictorSession" class="info.cathdb.funcnet_0_1.sessiondb.model.PredictorSession" unique-key="protein_pair_unique" />
<property name="protein1" type="string" not-null="true" unique-key="protein_pair_unique" />
<property name="protein2" type="string" not-null="true" unique-key="protein_pair_unique" />
<property name="rawScore" type="double" not-null="true" />
<property name="pValue" type="double" not-null="true" />
</class>
</hibernate-mapping>
Code between sessionFactory.openSession() and session.close():
n/a
Full stack trace of any exception that occurs:
java.lang.NullPointerException
at org.hibernate.dialect.Dialect$3.getReturnType(Dialect.java:102)
at org.hibernate.hql.ast.util.SessionFactoryHelper.findFunctionReturnType(SessionFactoryHelper.java:382)
at org.hibernate.hql.ast.tree.AggregateNode.getDataType(AggregateNode.java:21)
at org.hibernate.hql.ast.tree.BinaryArithmeticOperatorNode.initialize(BinaryArithmeticOperatorNode.java:29)
at org.hibernate.hql.ast.HqlSqlWalker.prepareArithmeticOperator(HqlSqlWalker.java:1011)
at org.hibernate.hql.antlr.HqlSqlBaseWalker.arithmeticExpr(HqlSqlBaseWalker.java:2810)
at org.hibernate.hql.antlr.HqlSqlBaseWalker.selectExpr(HqlSqlBaseWalker.java:2006)
at org.hibernate.hql.antlr.HqlSqlBaseWalker.selectExprList(HqlSqlBaseWalker.java:1825)
at org.hibernate.hql.antlr.HqlSqlBaseWalker.selectClause(HqlSqlBaseWalker.java:1394)
at org.hibernate.hql.antlr.HqlSqlBaseWalker.query(HqlSqlBaseWalker.java:553)
at org.hibernate.hql.antlr.HqlSqlBaseWalker.selectStatement(HqlSqlBaseWalker.java:281)
at org.hibernate.hql.antlr.HqlSqlBaseWalker.statement(HqlSqlBaseWalker.java:229)
at org.hibernate.hql.ast.QueryTranslatorImpl.analyze(QueryTranslatorImpl.java:228)
at org.hibernate.hql.ast.QueryTranslatorImpl.doCompile(QueryTranslatorImpl.java:160)
at org.hibernate.hql.ast.QueryTranslatorImpl.compile(QueryTranslatorImpl.java:111)
at org.hibernate.engine.query.HQLQueryPlan.<init>(HQLQueryPlan.java:77)
at org.hibernate.engine.query.HQLQueryPlan.<init>(HQLQueryPlan.java:56)
at org.hibernate.engine.query.QueryPlanCache.getHQLQueryPlan(QueryPlanCache.java:72)
at org.hibernate.impl.SessionFactoryImpl.checkNamedQueries(SessionFactoryImpl.java:402)
at org.hibernate.impl.SessionFactoryImpl.<init>(SessionFactoryImpl.java:352)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1294)
at info.cathdb.funcnet_0_1.sessiondb.hib3impl.HSQLDBModule.configure(HSQLDBModule.java:26)
at com.google.inject.AbstractModule.configure(AbstractModule.java:66)
at com.google.inject.BinderImpl.install(BinderImpl.java:199)
at com.google.inject.Guice.createInjector(Guice.java:77)
at com.google.inject.Guice.createInjector(Guice.java:53)
at com.google.inject.Guice.createInjector(Guice.java:43)
at info.cathdb.funcnet_0_1.sessiondb.hib3impl.RepositoryTest.setUp(RepositoryTest.java:45)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:27)
at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:31)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:73)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:46)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:180)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:41)
at org.junit.runners.ParentRunner$1.evaluate(ParentRunner.java:173)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:31)
at org.junit.runners.ParentRunner.run(ParentRunner.java:220)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:45)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:460)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:673)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:386)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:196)
Name and version of the database you are using:
Test: HSQLDB 1.8.0.7
Production: PostgreSQL 8.3.3
The generated SQL (show_sql=true):
n/a
Debug level Hibernate log excerpt:
14:20:27,720 DEBUG SessionFactoryImpl:392 - Checking 1 named HQL queries
14:20:27,723 DEBUG SessionFactoryImpl:400 - Checking named query: info.cathdb.funcnet_0_1.sessiondb.model.Session.info.cathdb.funcnet_0_1.sessiondb.model.pairwiseScores
14:20:27,727 DEBUG QueryPlanCache:70 - unable to locate HQL query plan in cache; generating (
select prediction.protein1,
prediction.protein2,
-2 * sum( ln( prediction.pValue ) )
from info.cathdb.funcnet_0_1.sessiondb.model.Session session
inner join session.predictors predictorSession
inner join predictorSession.predictions prediction
where session.funcnetJobID = :jobID
group by prediction.protein1,
prediction.protein2
)
14:20:27,823 DEBUG QueryTranslatorImpl:246 - parse() - HQL:
select prediction.protein1,
prediction.protein2,
-2 * sum( ln( prediction.pValue ) )
from info.cathdb.funcnet_0_1.sessiondb.model.Session session
inner join session.predictors predictorSession
inner join predictorSession.predictions prediction
where session.funcnetJobID = :jobID
group by prediction.protein1,
prediction.protein2
14:20:27,900 DEBUG AST:266 - --- HQL AST ---
\-[QUERY] 'query'
+-[SELECT_FROM] 'SELECT_FROM'
| +-[FROM] 'from'
| | +-[RANGE] 'RANGE'
| | | +-[DOT] '.'
| | | | +-[DOT] '.'
| | | | | +-[DOT] '.'
| | | | | | +-[DOT] '.'
| | | | | | | +-[DOT] '.'
| | | | | | | | +-[IDENT] 'info'
| | | | | | | | \-[IDENT] 'cathdb'
| | | | | | | \-[IDENT] 'funcnet_0_1'
| | | | | | \-[IDENT] 'sessiondb'
| | | | | \-[IDENT] 'model'
| | | | \-[IDENT] 'Session'
| | | \-[ALIAS] 'session'
| | +-[JOIN] 'join'
| | | +-[INNER] 'inner'
| | | +-[DOT] '.'
| | | | +-[IDENT] 'session'
| | | | \-[IDENT] 'predictors'
| | | \-[ALIAS] 'predictorSession'
| | \-[JOIN] 'join'
| | +-[INNER] 'inner'
| | +-[DOT] '.'
| | | +-[IDENT] 'predictorSession'
| | | \-[IDENT] 'predictions'
| | \-[ALIAS] 'prediction'
| \-[SELECT] 'select'
| +-[DOT] '.'
| | +-[IDENT] 'prediction'
| | \-[IDENT] 'protein1'
| +-[DOT] '.'
| | +-[IDENT] 'prediction'
| | \-[IDENT] 'protein2'
| \-[STAR] '*'
| +-[UNARY_MINUS] '-'
| | \-[NUM_INT] '2'
| \-[AGGREGATE] 'sum'
| \-[METHOD_CALL] '('
| +-[IDENT] 'ln'
| \-[EXPR_LIST] 'exprList'
| \-[DOT] '.'
| +-[IDENT] 'prediction'
| \-[IDENT] 'pValue'
+-[WHERE] 'where'
| \-[EQ] '='
| +-[DOT] '.'
| | +-[IDENT] 'session'
| | \-[IDENT] 'funcnetJobID'
| \-[COLON] ':'
| \-[IDENT] 'jobID'
\-[GROUP] 'group'
+-[DOT] '.'
| +-[IDENT] 'prediction'
| \-[IDENT] 'protein1'
\-[DOT] '.'
+-[IDENT] 'prediction'
\-[IDENT] 'protein2'
14:20:27,904 DEBUG ErrorCounter:68 - throwQueryException() : no errors
14:20:27,963 DEBUG HqlSqlBaseWalker:111 - select << begin [level=1, statement=select]
14:20:27,992 DEBUG FromElement:108 - FromClause{level=1} : info.cathdb.funcnet_0_1.sessiondb.model.Session (session) -> session0_
14:20:27,999 DEBUG FromReferenceNode:51 - Resolved : session -> session0_.session_id
14:20:28,005 DEBUG FromElement:525 - handling property dereference [info.cathdb.funcnet_0_1.sessiondb.model.Session (session) -> predictors (class)]
14:20:28,009 DEBUG DotNode:568 - getDataType() : predictors -> org.hibernate.type.SetType(info.cathdb.funcnet_0_1.sessiondb.model.Session.predictors)
14:20:28,015 DEBUG FromElementFactory:360 - createEntityAssociation() : One to many - path = session.predictors role = info.cathdb.funcnet_0_1.sessiondb.model.Session.predictors associatedEntityName = info.cathdb.funcnet_0_1.sessiondb.model.PredictorSession
14:20:28,022 DEBUG FromElement:108 - FromClause{level=1} : info.cathdb.funcnet_0_1.sessiondb.model.PredictorSession (predictorSession) -> predictors1_
14:20:28,026 DEBUG FromClause:233 - addJoinByPathMap() : session.predictors -> PredictorSession predictors1_
14:20:28,031 DEBUG DotNode:284 - dereferenceCollection() : Created new FROM element for session.predictors : PredictorSession predictors1_
14:20:28,034 DEBUG FromReferenceNode:51 - Resolved : session.predictors -> .
14:20:28,043 DEBUG HqlSqlWalker:320 - createFromJoinElement() : -- join tree --
\-[JOIN_FRAGMENT] FromElement: 'PredictorSession predictors1_' FromElement{explicit,not a collection join,not a fetch join,fetch non-lazy properties,classAlias=predictorSession,role=info.cathdb.funcnet_0_1.sessiondb.model.Session.predictors,tableName=PredictorSession,tableAlias=predictors1_,origin=Session session0_,colums={session0_.session_id ,className=info.cathdb.funcnet_0_1.sessiondb.model.PredictorSession}}
14:20:28,048 DEBUG FromReferenceNode:51 - Resolved : predictorSession -> predictors1_.predictor_session_id
14:20:28,053 DEBUG FromElement:525 - handling property dereference [info.cathdb.funcnet_0_1.sessiondb.model.PredictorSession (predictorSession) -> predictions (class)]
14:20:28,057 DEBUG DotNode:568 - getDataType() : predictions -> org.hibernate.type.SetType(info.cathdb.funcnet_0_1.sessiondb.model.PredictorSession.predictions)
14:20:28,061 DEBUG FromElementFactory:360 - createEntityAssociation() : One to many - path = predictorSession.predictions role = info.cathdb.funcnet_0_1.sessiondb.model.PredictorSession.predictions associatedEntityName = info.cathdb.funcnet_0_1.sessiondb.model.Prediction
14:20:28,064 DEBUG FromElement:108 - FromClause{level=1} : info.cathdb.funcnet_0_1.sessiondb.model.Prediction (prediction) -> prediction2_
14:20:28,069 DEBUG FromClause:233 - addJoinByPathMap() : predictorSession.predictions -> Prediction prediction2_
14:20:28,073 DEBUG DotNode:284 - dereferenceCollection() : Created new FROM element for predictorSession.predictions : Prediction prediction2_
14:20:28,077 DEBUG FromReferenceNode:51 - Resolved : predictorSession.predictions -> .
14:20:28,081 DEBUG HqlSqlWalker:320 - createFromJoinElement() : -- join tree --
\-[JOIN_FRAGMENT] FromElement: 'Prediction prediction2_' FromElement{explicit,not a collection join,not a fetch join,fetch non-lazy properties,classAlias=prediction,role=info.cathdb.funcnet_0_1.sessiondb.model.PredictorSession.predictions,tableName=Prediction,tableAlias=prediction2_,origin=PredictorSession predictors1_,colums={predictors1_.predictor_session_id ,className=info.cathdb.funcnet_0_1.sessiondb.model.Prediction}}
14:20:28,087 DEBUG FromReferenceNode:51 - Resolved : prediction -> prediction2_.prediction_id
14:20:28,091 DEBUG FromElement:525 - handling property dereference [info.cathdb.funcnet_0_1.sessiondb.model.Prediction (prediction) -> protein1 (class)]
14:20:28,095 DEBUG DotNode:568 - getDataType() : protein1 -> org.hibernate.type.StringType@1384ed5
14:20:28,099 DEBUG FromReferenceNode:51 - Resolved : prediction.protein1 -> prediction2_.protein1
14:20:28,103 DEBUG FromElement:525 - handling property dereference [info.cathdb.funcnet_0_1.sessiondb.model.Prediction (prediction) -> protein1 (class)]
14:20:28,107 DEBUG FromReferenceNode:51 - Resolved : prediction -> prediction2_.prediction_id
14:20:28,111 DEBUG FromElement:525 - handling property dereference [info.cathdb.funcnet_0_1.sessiondb.model.Prediction (prediction) -> protein2 (class)]
14:20:28,115 DEBUG DotNode:568 - getDataType() : protein2 -> org.hibernate.type.StringType@1384ed5
14:20:28,119 DEBUG FromReferenceNode:51 - Resolved : prediction.protein2 -> prediction2_.protein2
14:20:28,123 DEBUG FromElement:525 - handling property dereference [info.cathdb.funcnet_0_1.sessiondb.model.Prediction (prediction) -> protein2 (class)]
14:20:28,140 DEBUG FromReferenceNode:51 - Resolved : prediction -> prediction2_.prediction_id
14:20:28,145 DEBUG FromElement:525 - handling property dereference [info.cathdb.funcnet_0_1.sessiondb.model.Prediction (prediction) -> pValue (class)]
14:20:28,149 DEBUG DotNode:568 - getDataType() : pValue -> org.hibernate.type.DoubleType@85b670
14:20:28,153 DEBUG FromElement:525 - handling property dereference [info.cathdb.funcnet_0_1.sessiondb.model.Prediction (prediction) -> pValue (class)]
14:20:28,158 DEBUG FromReferenceNode:51 - Resolved : prediction.pValue -> prediction2_.pValue
|