Skip to content

NamedParameterJdbcTemplate fails on PostgreSQL array [SPR-16340] #20887

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
spring-projects-issues opened this issue Jan 2, 2018 · 5 comments
Closed
Labels
in: data Issues in data modules (jdbc, orm, oxm, tx) status: superseded An issue that has been superseded by another type: enhancement A general enhancement

Comments

@spring-projects-issues
Copy link
Collaborator

spring-projects-issues commented Jan 2, 2018

Nikita Khateev opened SPR-16340 and commented

There is a possible bug while parsing SQL query for PostgreSQL DB. If you will try to execute a named query like this:

UPDATE table SET name = array[:param]

The name of param will be parsed as 'param]', which is incorrect. However, if you will try to execute this query:

UPDATE table SET name = array[:param ]

It will parse param name correctly.


Affects: 5.0.2

Issue Links:

Referenced from: pull request #1637

@spring-projects-issues
Copy link
Collaborator Author

Juergen Hoeller commented

Thanks for raising this! I ended up up with a different implementation, refactoring the separator detection in NamedParameterUtils to avoid iterating over even more separator characters now. That said, I've been reusing your test and attributed you as an author in NamedParameterJdbcTemplateTests.

@spring-projects-issues
Copy link
Collaborator Author

Juergen Hoeller commented

Reopening due to some test failures in Spring Integration...

@spring-projects-issues
Copy link
Collaborator Author

Artem Bilan commented

Yes, after this change Spring Integration JDBC module has started failing: https://build.spring.io/browse/INT-FATS5IC-381

The main reason that we use BeanPropertySqlParameterSourceFactory by default with the treatment of param names as property accessors to the underlying BeanPropertySqlParameterSource with its BeanWrapper.
So, we can have a query like:

insert into foos (id, status, name) values (:headers[$foo_id], 0, :payload)

The :headers\[$foo_id] means to get a headers property against org.springframework.messaging.Message and perform key evaluation in a map style via [] operator:

/**
 * Marker that indicates the start of a property key for an
 * indexed or mapped property like "person.addresses[0]".
 */
String PROPERTY_KEY_PREFIX = "[";

In the end I would like to say that it looks like the value is evaluated correctly, only the problem that our param "name" isn't replaced properly i the final query:

org.springframework.messaging.MessageHandlingException: error occurred in message handler [org.springframework.integration.jdbc.JdbcMessageHandler#0]; nested exception is org.springframework.jdbc.BadSqlGrammarException: PreparedStatementCallback; bad SQL grammar [insert into foos (id, status, name) values (?[$foo_id], 0, ?)]; nested exception is org.h2.jdbc.JdbcSQLException: Syntax error in SQL statement "INSERT INTO FOOS (ID, STATUS, NAME) VALUES (?[$FOO_ID[*]], 0, ?) "; expected "[, ::, *, /, %, +, -, ||, ~, !~, NOT, LIKE, ILIKE, REGEXP, IS, IN, BETWEEN, AND, OR, ]"; SQL statement:
insert into foos (id, status, name) values (?[$foo_id], 0, ?) [42001-196], failedMessage=GenericMessage [payload=foo, headers={$foo_id=abc, id=2333574d-d76e-0f39-9500-e581f54edb38, timestamp=1515642860349}]
	at org.springframework.integration.handler.AbstractMessageHandler.handleMessage(AbstractMessageHandler.java:153)
	at org.springframework.integration.dispatcher.AbstractDispatcher.tryOptimizedDispatch(AbstractDispatcher.java:116)
	at org.springframework.integration.dispatcher.UnicastingDispatcher.doDispatch(UnicastingDispatcher.java:132)
	at org.springframework.integration.dispatcher.UnicastingDispatcher.dispatch(UnicastingDispatcher.java:105)
	at org.springframework.integration.channel.AbstractSubscribableChannel.doSend(AbstractSubscribableChannel.java:73)
	at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:438)
	at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:388)
	at org.springframework.integration.jdbc.config.JdbcMessageHandlerParserTests.testDollarHeaderOutboundChannelAdapter(JdbcMessageHandlerParserTests.java:79)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecuter.runTestClass(JUnitTestClassExecuter.java:114)
	at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecuter.execute(JUnitTestClassExecuter.java:57)
	at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassProcessor.processTestClass(JUnitTestClassProcessor.java:66)
	at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:51)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
	at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:32)
	at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:93)
	at com.sun.proxy.$Proxy1.processTestClass(Unknown Source)
	at org.gradle.api.internal.tasks.testing.worker.TestWorker.processTestClass(TestWorker.java:108)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
	at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:146)
	at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:128)
	at org.gradle.internal.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:404)
	at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:63)
	at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:46)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:55)
	at java.lang.Thread.run(Thread.java:748)
Caused by: org.springframework.jdbc.BadSqlGrammarException: PreparedStatementCallback; bad SQL grammar [insert into foos (id, status, name) values (?[$foo_id], 0, ?)]; nested exception is org.h2.jdbc.JdbcSQLException: Syntax error in SQL statement "INSERT INTO FOOS (ID, STATUS, NAME) VALUES (?[$FOO_ID[*]], 0, ?) "; expected "[, ::, *, /, %, +, -, ||, ~, !~, NOT, LIKE, ILIKE, REGEXP, IS, IN, BETWEEN, AND, OR, ]"; SQL statement:
insert into foos (id, status, name) values (?[$foo_id], 0, ?) [42001-196]
	at org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator.doTranslate(SQLErrorCodeSQLExceptionTranslator.java:235)
	at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:72)
	at org.springframework.jdbc.core.JdbcTemplate.translateException(JdbcTemplate.java:1402)
	at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:620)
	at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:850)
	at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:871)
	at org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate.update(NamedParameterJdbcTemplate.java:316)
	at org.springframework.integration.jdbc.JdbcMessageHandler.executeUpdateQuery(JdbcMessageHandler.java:198)
	at org.springframework.integration.jdbc.JdbcMessageHandler.handleMessageInternal(JdbcMessageHandler.java:153)
	at org.springframework.integration.handler.AbstractMessageHandler.handleMessage(AbstractMessageHandler.java:141)
	... 54 more
Caused by: org.h2.jdbc.JdbcSQLException: Syntax error in SQL statement "INSERT INTO FOOS (ID, STATUS, NAME) VALUES (?[$FOO_ID[*]], 0, ?) "; expected "[, ::, *, /, %, +, -, ||, ~, !~, NOT, LIKE, ILIKE, REGEXP, IS, IN, BETWEEN, AND, OR, ]"; SQL statement:
insert into foos (id, status, name) values (?[$foo_id], 0, ?) [42001-196]
	at org.h2.message.DbException.getJdbcSQLException(DbException.java:345)
	at org.h2.message.DbException.getSyntaxError(DbException.java:205)
	at org.h2.command.Parser.getSyntaxError(Parser.java:541)
	at org.h2.command.Parser.read(Parser.java:3258)
	at org.h2.command.Parser.readTerm(Parser.java:3048)
	at org.h2.command.Parser.readFactor(Parser.java:2390)
	at org.h2.command.Parser.readSum(Parser.java:2377)
	at org.h2.command.Parser.readConcat(Parser.java:2347)
	at org.h2.command.Parser.readCondition(Parser.java:2178)
	at org.h2.command.Parser.readAnd(Parser.java:2150)
	at org.h2.command.Parser.readExpression(Parser.java:2142)
	at org.h2.command.Parser.parseInsert(Parser.java:1093)
	at org.h2.command.Parser.parsePrepared(Parser.java:417)
	at org.h2.command.Parser.parse(Parser.java:321)
	at org.h2.command.Parser.parse(Parser.java:297)
	at org.h2.command.Parser.prepareCommand(Parser.java:258)
	at org.h2.engine.Session.prepareLocal(Session.java:578)
	at org.h2.engine.Session.prepareCommand(Session.java:519)
	at org.h2.jdbc.JdbcConnection.prepareCommand(JdbcConnection.java:1204)
	at org.h2.jdbc.JdbcPreparedStatement.<init>(JdbcPreparedStatement.java:73)
	at org.h2.jdbc.JdbcConnection.prepareStatement(JdbcConnection.java:288)
	at org.springframework.jdbc.core.PreparedStatementCreatorFactory$PreparedStatementCreatorImpl.createPreparedStatement(PreparedStatementCreatorFactory.java:230)
	at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:603)
	... 60 more

Thanks

@spring-projects-issues
Copy link
Collaborator Author

Juergen Hoeller commented

I'm afraid we'll have to back this out and reconsider it for 5.1 instead, possibly in combination with a smarter parsing algorithm.

@sbrannen
Copy link
Member

sbrannen commented Nov 2, 2023

This appears to have been fixed in #27716 and #27925.

If you still encounter this issue with Spring Framework 6.0.13 (or a later version), please create a new issue.

Thanks

@sbrannen sbrannen closed this as not planned Won't fix, can't repro, duplicate, stale Nov 2, 2023
@sbrannen sbrannen added the status: superseded An issue that has been superseded by another label Nov 2, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: data Issues in data modules (jdbc, orm, oxm, tx) status: superseded An issue that has been superseded by another type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

2 participants