Skip to content

Normalize function return parameter lookup in CallMetaDataContext - #37206

Open
junhyeong9812 wants to merge 1 commit into
spring-projects:mainfrom
junhyeong9812:fix/callmetadata-function-return-lookup
Open

Normalize function return parameter lookup in CallMetaDataContext#37206
junhyeong9812 wants to merge 1 commit into
spring-projects:mainfrom
junhyeong9812:fix/callmetadata-function-return-lookup

Conversation

@junhyeong9812

Copy link
Copy Markdown
Contributor

Overview

CallMetaDataContext.reconcileParameters matches the return parameter reported by the database metadata against the declared parameters with lookup keys that do not follow the map's own key rule. When a function has an additional out parameter declared before the return parameter, the return slot is silently bound to the wrong parameter and executeFunction returns the wrong value; on SQL Server the same declaration order with withReturnValue() fails with InvalidDataAccessApiUsageException. This PR normalizes the lookups with the same rule used to build the map.

Problem

The declared parameter map is keyed by lowerCase(provider.parameterNameToUse(name)), so a declared RESULT is stored under result and a SQL Server @out_total under out_total. The return parameter branch, however, looked up getFunctionReturnName() as declared (original case, so it never matched on Oracle) and then fell back to getOutParameterNames().get(0).toLowerCase(), i.e. the first declared out parameter without the provider transformation.

Because the first lookup always missed, the fallback decided the result, and it only happens to be right when the return parameter is the first declared out parameter:

Declaration (Oracle function GET_TOTAL) Before After
SqlOutParameter("RESULT"), SqlOutParameter("out_status") [RESULT, AMOUNT, out_status] unchanged
SqlOutParameter("out_status"), SqlOutParameter("RESULT") [out_status, AMOUNT, out_status], executeFunction returns the out_status value [RESULT, AMOUNT, out_status]

On SQL Server, withProcedureName(...).withReturnValue() with SqlOutParameter("@out_total") declared before SqlOutParameter("RETURN_VALUE") threw Unable to locate declared parameter for function return value - add an SqlOutParameter with name 'return', since "@out_total".toLowerCase() does not match the stored key out_total.

The first-out-parameter fallback itself dates back to the 4.x behavior restored in #25707 and is kept as is; only the lookup keys change.

Fix

The return parameter branch now looks up the metadata-derived name (paramNameToCheck) first, then the function return name, then the first declared out parameter, with all three keys normalized by lowerCase(provider.parameterNameToUse(...)) exactly like the map keys. Parameter names themselves are not modified, so existing call parameter names and call strings are unchanged. Applications that declared an additional out parameter before the return parameter of a function now receive the actual return value under the declared return parameter name instead of the value of that other out parameter. As before, this lookup only decides which declared parameter represents the return slot; whether a return slot is bound at all is still governed by withFunctionName() and withReturnValue().

SimpleJdbcCallTests gains four tests: an Oracle function and a SQL Server procedure with return value, each with the additional out parameter declared before and after the return parameter. The two "before" cases failed prior to this change; the two "after" cases guard the previously working declaration order. The full spring-jdbc test suite passes.

CallMetaDataContext.reconcileParameters keys the map of declared
parameters by lowerCase(provider.parameterNameToUse(name)), but the
branch that matches the return parameter reported by the database
metadata did not apply the same rule. It looked up the function return
name as declared (original case) and fell back to the first declared
out parameter name with a plain toLowerCase, without the provider
transformation that strips the '@' prefix on SQL Server and Sybase.

The first lookup therefore always missed on Oracle, so the fallback
silently used whichever out parameter was declared first. Declaring an
additional out parameter before the return parameter of a function made
that parameter double as the return slot: the declared return parameter
was dropped from the call parameters, the wrong parameter was bound at
position 1, and executeFunction returned the value of the other out
parameter. On SQL Server, a procedure compiled with withReturnValue()
and an '@'-prefixed out parameter declared before the return parameter
failed with InvalidDataAccessApiUsageException because neither lookup
could find the declared parameter.

The return parameter branch now looks up the metadata-derived name
first and normalizes both the function return name and the first out
parameter fallback with the same rule as the declared parameter map.
Tests cover both declaration orders for an Oracle function and for a
SQL Server procedure with a return value.

Signed-off-by: junhyeong9812 <pickjog@gmail.com>
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Aug 27, 2026
@sbrannen sbrannen added the in: data Issues in data modules (jdbc, orm, oxm, tx) label Aug 27, 2026
@sbrannen sbrannen self-assigned this Aug 27, 2026
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: waiting-for-triage An issue we've not yet triaged or decided on

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants