Cannot use 'like' operand when an AttributeConverter is used to convert the attribute in a String
Description
is followed up by
Activity
Show:

Gavin King February 1, 2024 at 6:08 PM
Awesome, thanks!
Marco Belladelli February 1, 2024 at 3:53 PM
Thanks again @Gavin King, I tried following your suggestions in my pr. Let me know what you think.

Gavin King February 1, 2024 at 2:13 PM
Yeah, I agree that the Duration
case is a bit tricky. But perhaps the solution is as simple as just introducing a DurationJdbcType
.
Marco Belladelli February 1, 2024 at 1:23 PM
Alright then, I will try implementing it through JdbcType
, the only thing I need to figure out is the correct check for assertDuration
.

Gavin King February 1, 2024 at 1:13 PMEdited
[And I would prefer to not fix this particular issue than to add hacks into TypecheckUtil
, for reasons that I’ve clearly documented.]
It seems like it is not possible to use “like” operand on a non-string property even if an
AttributeConverter
is defined, converting it in aString
to be stored in the database.For example, given an entity like this one
@Entity public class TestEntity { public static class SetConverter implements AttributeConverter<Set<String>, String> { @Override public String convertToDatabaseColumn(final Set<String> attribute) { if (attribute != null && !attribute.isEmpty()) { return String.join(",", attribute); } return null; } @Override public Set<String> convertToEntityAttribute(final String dbData) { if (dbData != null) { return Arrays.stream(dbData.split(",")).collect(Collectors.toSet()); } return null; } } @Id @GeneratedValue public Long id; @Convert(converter = SetConverter.class) public Set<String> descriptions; }
It is not possible to execute a query like
entityManager.createQuery( "select e from TestEntity e where e.descriptions like :text", TestEntity.class ) .setParameter("text", "%,P_2,%") .getResultList();
Hibernate raises this exception
java.lang.IllegalArgumentException: org.hibernate.query.SemanticException: Operand of 'like' is of type 'java.util.Set' which is not a string (it is not an instance of 'java.lang.String' or 'char[]') at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:143) at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:167) at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:173) at org.hibernate.internal.AbstractSharedSessionContract.createQuery(AbstractSharedSessionContract.java:848) at org.hibernate.internal.SessionImpl.createQuery(SessionImpl.java:198) at org.hibernate.bugs.JPAUnitTestCase.hhh123Test(JPAUnitTestCase.java:44) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:568) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26) at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27) at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63) at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329) at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293) at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) at org.junit.runners.ParentRunner.run(ParentRunner.java:413) at org.junit.runner.JUnitCore.run(JUnitCore.java:137) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69) at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38) at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11) at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35) at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:232) at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:55) Caused by: org.hibernate.query.SemanticException: Operand of 'like' is of type 'java.util.Set' which is not a string (it is not an instance of 'java.lang.String' or 'char[]') at org.hibernate.query.sqm.internal.TypecheckUtil.assertString(TypecheckUtil.java:513) at org.hibernate.query.sqm.tree.predicate.SqmLikePredicate.<init>(SqmLikePredicate.java:61) at org.hibernate.query.sqm.tree.predicate.SqmLikePredicate.<init>(SqmLikePredicate.java:85) at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitLikePredicate(SemanticQueryBuilder.java:2508) at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitLikePredicate(SemanticQueryBuilder.java:269) at org.hibernate.grammars.hql.HqlParser$LikePredicateContext.accept(HqlParser.java:6113) at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitWhereClause(SemanticQueryBuilder.java:2244) at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitWhereClause(SemanticQueryBuilder.java:269) at org.hibernate.grammars.hql.HqlParser$WhereClauseContext.accept(HqlParser.java:5905) at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitQuery(SemanticQueryBuilder.java:1159) at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitQuerySpecExpression(SemanticQueryBuilder.java:941) at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitQuerySpecExpression(SemanticQueryBuilder.java:269) at org.hibernate.grammars.hql.HqlParser$QuerySpecExpressionContext.accept(HqlParser.java:1869) at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitSimpleQueryGroup(SemanticQueryBuilder.java:926) at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitSimpleQueryGroup(SemanticQueryBuilder.java:269) at org.hibernate.grammars.hql.HqlParser$SimpleQueryGroupContext.accept(HqlParser.java:1740) at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitSelectStatement(SemanticQueryBuilder.java:443) at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitStatement(SemanticQueryBuilder.java:402) at org.hibernate.query.hql.internal.SemanticQueryBuilder.buildSemanticModel(SemanticQueryBuilder.java:311) at org.hibernate.query.hql.internal.StandardHqlTranslator.translate(StandardHqlTranslator.java:71) at org.hibernate.query.internal.QueryInterpretationCacheStandardImpl.createHqlInterpretation(QueryInterpretationCacheStandardImpl.java:165) at org.hibernate.query.internal.QueryInterpretationCacheStandardImpl.resolveHqlInterpretation(QueryInterpretationCacheStandardImpl.java:147) at org.hibernate.internal.AbstractSharedSessionContract.interpretHql(AbstractSharedSessionContract.java:790) at org.hibernate.internal.AbstractSharedSessionContract.createQuery(AbstractSharedSessionContract.java:840) ... 31 more
This is applicable also in case a generic object is converted in a
String
.The problem seems caused by the fact Hibernate is checking just the Java type and not eventually the type on the database, that in this case is changed by the
AttributeConverter
.Probably it is enough in class
org.hibernate.query.sqm.internal.TypecheckUtil
updating theassertString
method to check also the relational java type (something like the example below)public static void assertString(SqmExpression<?> expression) { final SqmExpressible<?> nodeType = expression.getNodeType(); if ( nodeType != null ) { final Class<?> javaType = nodeType.getExpressibleJavaType().getJavaTypeClass(); final Class<?> relationalJavaType = nodeType.getRelationalJavaType().getJavaTypeClass(); if ( javaType != String.class && javaType != char[].class && relationalJavaType != String.class && relationalJavaType != char[].class ) { throw new SemanticException( "Operand of 'like' is of type '" + nodeType.getTypeName() + "' which is not a string (it is not an instance of 'java.lang.String' or 'char[]')" ); } } }
Test Case
https://github.com/stefanogianelli/hibernate-issue-like-with-converter