Repository navigation
Conversation
…e result row count - LIST OUT parameter now always outputs a JSON array, even when the query returns a single row (previously a bare string) - non-LIST OUT parameter now takes the last row value when the query returns multiple rows (previously it was left empty) - LONG normalization now applies to any row count
SbloodyS
left a comment
There was a problem hiding this comment.
Do not silently select the last row for a non-LIST OUT parameter
[Evidence] The current documentation defines non-LIST OUT parameters for single-row results and LIST OUT parameters for multi-row results. However, this change silently selects the last row for a non-LIST parameter when multiple rows are returned.
[Evidence] Without an explicit ORDER BY, SQL result order is not guaranteed. Therefore, the “last row” is not deterministic and may pass a plausible but incorrect value to downstream tasks.
[Recommendation] When multiple rows are returned for a non-LIST OUT parameter, throw a TaskException containing the parameter name, type, and row count. The error should tell users to either use a LIST parameter or constrain the query to return one row.
…lti-row results Address review feedback: without ORDER BY the row order is not guaranteed, so silently picking a row may pass a plausible but incorrect value to downstream tasks. A non-LIST OUT parameter now throws a TaskException containing the parameter name, type and row count, telling users to use a LIST parameter or constrain the query to a single row.
|
@SbloodyS Thanks for the review, good catch! Agreed on all three points:
Adopted in 16ed11d: a non-LIST OUT parameter now throws a Also updated the tests ( |
SbloodyS
left a comment
There was a problem hiding this comment.
Preserve the real zero-row result instead of producing [""]
[Evidence] SqlTask#resultProcess converts an empty ResultSet into a synthetic row whose columns contain empty strings. That synthetic row is passed to dealOutParam, so a LIST OUT parameter for a query that returns zero rows becomes [""].
[Evidence] Zero rows and one row containing an empty string have different meanings. Passing [""] downstream may cause a task to process an artificial value that was never returned by the database.
[Evidence] The new tests call dealOutParam directly and therefore do not cover the production path through SqlTask#resultProcess.
[Recommendation] Keep the synthetic empty row only for alert rendering. OUT-parameter processing should receive the actual empty result. Define the zero-row behavior explicitly—preferably [] for a LIST parameter—and add a test covering the complete resultProcess to dealOutParam path.
…eter processing Address review feedback: SqlTask#resultProcess used to serialize the synthetic all-empty-string row (meant for alert rendering) into the task result, so a zero-row query leaked an artificial [""] into a LIST OUT parameter. The synthetic row is now kept only for alert rendering and OUT parameter processing receives the real empty result: - LIST OUT parameter gets an empty JSON array [] - non-LIST OUT parameter fails fast (the old synthetic "" value was never returned by the database) - updated the two resultProcess tests that locked the old behaviour and added an end-to-end resultProcess -> dealOutParam test
|
@SbloodyS Thanks again, another valid catch — adopted in 38b879b:
All |
SbloodyS
left a comment
There was a problem hiding this comment.
Do not serialize an unmatched LIST OUT parameter as JSON null
[Evidence] sqlResultFormat.get(info.getProp()) returns null when the configured OUT parameter name does not match any result column. The LIST branch then serializes that value directly, producing the string null instead of a JSON array.
[Evidence] The method subsequently merges every OUT property into the varPool, so the unmatched parameter is still propagated downstream with a non-array value. This violates the new guarantee that a LIST OUT parameter always contains a JSON array and can still break downstream LIST consumers.
[Evidence] The existing parameter documentation says that an OUT parameter with no matching result column should be discarded.
[Recommendation] Check whether the result column exists before handling its type. Either fail fast with a TaskException containing the unmatched parameter name and available result columns, or exclude the unmatched property from the varPool according to the documented behavior. Do not serialize a missing column as JSON null.
… result column Address review feedback: an unmatched LIST OUT parameter was serialized as the string "null" and still propagated downstream through the varPool, violating the guarantee that a LIST OUT parameter always holds a JSON array. The docs define the OUT parameter name to be consistent with the result column name, so an unmatched name is a configuration error. An OUT parameter (LIST or not) whose name matches no result column now throws a TaskException containing the parameter name and the available result columns.
|
@SbloodyS Good catch on the unmatched-name path too — adopted in ace2234:
|
SbloodyS
left a comment
There was a problem hiding this comment.
Validate LIST OUT parameter names for zero-row results
[Evidence] In the zero-row branch, every LIST OUT parameter is assigned [] before any result-column validation occurs. Because resultProcess serializes an empty result as [], dealOutParam has already lost the column names from ResultSetMetaData.
[Impact] A misspelled LIST parameter therefore succeeds when the query returns zero rows, but the same configuration fails when rows later appear. The promised fail-fast behavior still depends on the result row count.
[Recommendation] Validate OUT parameter names against ResultSetMetaData while the schema is still available, or preserve the column schema alongside the rows passed to dealOutParam.
Update the parameter contract documentation
[Evidence] The new implementation and incompatible-change note say that an unmatched OUT parameter fails the task. However, both docs/docs/en/contribute/backend/mechanism/global-parameter.md and its Chinese counterpart still say that unmatched parameters are discarded.
[Impact] The repository now documents two different contracts for the same behavior.
[Recommendation] Update both global-parameter documents to describe the new fail-fast behavior.
[Remove the dialect-specific and ineffective LIMIT 1 advice
[Evidence] The same error message is used for both zero-row and multi-row results. Adding LIMIT 1 cannot fix a zero-row result, and DolphinScheduler supports databases such as Oracle and SQL Server where that syntax is not portable.
[Recommendation] Use dialect-neutral wording such as: “adjust the query so that it returns exactly one row.”
… for zero-row results Address review feedback: - a zero-row result serializes as [], so dealOutParam loses the column names and a misspelled LIST OUT parameter succeeded until rows appeared; SqlTask#resultProcess now validates the OUT parameter names against the ResultSetMetaData (SqlParameters#checkOutParamColumns) before the schema is gone - update the parameter contract in global-parameter.md (en/zh): the old text said unmatched OUT parameters are discarded, which contradicts the new fail-fast behavior - drop the LIMIT 1 advice from the error message: it cannot fix a zero-row result and is not portable to Oracle/SQL Server, use dialect-neutral wording instead
|
@SbloodyS All three points adopted in 02edcb3:
|
| public void checkOutParamColumns(String[] columnLabels) { | ||
| List<Property> outProperty = getOutProperty(localParams); | ||
| if (CollectionUtils.isEmpty(outProperty) || ArrayUtils.isEmpty(columnLabels)) { | ||
| return; | ||
| } | ||
| Set<String> columns = new HashSet<>(Arrays.asList(columnLabels)); | ||
| for (Property info : outProperty) { | ||
| if (!columns.contains(info.getProp())) { | ||
| throw unmatchedOutParamColumn(info, columns); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
| public void checkOutParamColumns(String[] columnLabels) { | |
| List<Property> outProperty = getOutProperty(localParams); | |
| if (CollectionUtils.isEmpty(outProperty) || ArrayUtils.isEmpty(columnLabels)) { | |
| return; | |
| } | |
| Set<String> columns = new HashSet<>(Arrays.asList(columnLabels)); | |
| for (Property info : outProperty) { | |
| if (!columns.contains(info.getProp())) { | |
| throw unmatchedOutParamColumn(info, columns); | |
| } | |
| } | |
| } | |
| public void checkOutParamColumns(String[] columnLabels) { | |
| List<Property> outProperty = getOutProperty(localParams); | |
| if (CollectionUtils.isEmpty(outProperty)) { | |
| return; | |
| } | |
| if (ArrayUtils.isEmpty(columnLabels)) { | |
| throw xxException() | |
| } | |
| Set<String> columns = new HashSet<>(Arrays.asList(columnLabels)); | |
| for (Property info : outProperty) { | |
| if (!columns.contains(info.getProp())) { | |
| throw unmatchedOutParamColumn(info, columns); | |
| } | |
| } | |
| } |
There was a problem hiding this comment.
Need to throw exception here?
…kOutParamColumns Address review feedback: with OUT parameters to validate, an empty column label array can never match, so silently skipping the validation would let a misconfigured parameter slip through. Missing column information now fails fast with the same unmatched-column error.
|
@ruanwenjun Thanks for the review! You are right — with OUT parameters to validate, missing column information can never match, so silently skipping the validation was a hole in the fail-fast contract. Adopted in c99f36b: Covered by |
|
@yzeng1618 Please fix the format issue. |
…ies lost in rebase
… fix-18750-sql-out-param-format
Was this PR generated or assisted by AI?
YES. The code change, tests and PR description were generated with AI assistance (ZCode agent), under human review and verified locally.
Purpose of the pull request
Fix #18750
In the SQL task, the format of OUT parameters depended on how many rows the query returns:
["user2","user3"](JSON array)null)user2(bare string, not a valid JSON array)This made downstream tasks fail intermittently depending on the data: when a query happened to return exactly one row, a
LISTout parameter was a bare string that cannot be parsed as a JSON array, and when a query returned multiple rows, non-LISTout parameters were never assigned (thenull-valued property was still merged into the varPool). LONG normalization only applied to the single-row branch.Brief change log
SqlParameters#dealOutParamnow collects column values for all rows in a single unified path instead of two row-count-dependent branches:LISTOUT parameter always outputs a JSON array, e.g.["user2"]for a single-row resultLISTOUT parameter takes the last row value (compatible with the previous single-row behaviour)convertOutParamValue)docs/docs/en/guide/upgrade/incompatible.mdanddocs/docs/zh/guide/upgrade/incompatible.mdBehavior changes (also documented in
incompatible.md):LISToutput:user2->["user2"]LISToutput:null-> last row valueVerify this pull request
This change added tests and can be verified as follows:
SqlParametersTest#testDealOutParamValueFormatShouldNotDependOnRowCountto verify LIST/non-LIST output for single-row and multi-row resultsSqlParametersTest#testDealOutParamWithNullValueto pin the existingnull->"null"string behaviourSqlParametersTest#testLongOutParameterNormalizesLastRowValueForMultiLineResultto verify LONG normalization on the multi-row pathmvn test -pl dolphinscheduler-task-plugin/dolphinscheduler-task-api(SqlParametersTest: 20 passed) andmvn test -pl dolphinscheduler-task-plugin/dolphinscheduler-task-sql -am -Dtest=SqlTaskTest(38 passed)