Fix max1Row error for set-returning functions over point lookups - #3151
Fix max1Row error for set-returning functions over point lookups#3151zachmu wants to merge 5 commits into
Conversation
Footnotes
|
|
SummaryThe run broadly exercised database query behavior across ordinary lookups, expanding result sets, empty results, repeated and mixed queries, multiple source rows, and recovery after invalid input. It also covered an unsupported lateral query shape that remains a compatibility gap, while the tested non-lateral paths behaved normally. Safe to merge — the only observed failure is a medium-severity, pre-existing limitation unrelated to this PR, with no regression or PR-attributable failure identified. The change’s covered query and recovery behaviors remain healthy, so the lateral limitation is a flag for later rather than a merge blocker. Tests run by ItoAdditional Findings DetailsThese findings are unrelated to the current changes but were observed during testing. 🟡 Lateral queries fail to return generated rows
Evidence PackageTip Reply with @itoqa to send us feedback on this test run. |
|
@zachmu DOLT
|
e8f72d9 to
3a50c41
Compare
GMS no longer sets the max1Row query flag when the plan contains an expression that returns a RowIter, so the rule undoing that flag is no longer needed. Requires a GMS bump to pick up the fix.
Commit: SummaryCoverage spans normal lookups and array expansion, generated values, index metadata, connection stability, boundary sizes, composite keys, and multi-row result handling. Basic and edge-size behaviors work, but more complex array projections expose incorrect duplicate rows and ordering. Merge with caution — this PR is associated with a medium-severity wrong-results defect in array projections with companion fields, making affected query results unreliable. A separate multi-row duplication issue is not attributable to this PR and is a flag for later. Tests run by ItoAdditional Findings DetailsThese findings are unrelated to the current changes but were observed during testing. 🟡 Multi-row lookup duplicates array values
Evidence PackageTip Reply with @itoqa to send us feedback on this test run. |
| return node, transform.SameTree, nil | ||
| } | ||
|
|
||
| containsSRF := false |
There was a problem hiding this comment.
SRF results are duplicated and misordered
What failed: The query should return three rows with source_id 7 and elem values 303, 202, and 101. Instead, each value appears three times for nine total rows, in ascending order, and the second execution returns the same incorrect result.
Impact · Steps · Stub / mock · Analysis · Why this is likely a bug
- Severity: Medium
- Impact: Queries that expand array values alongside other columns can return duplicate rows and the wrong sort order. Users relying on this query shape may receive silently incorrect data until the query is changed or the defect is fixed.
- Steps to Reproduce:
- Create a table with an integer primary key and integer array column.
- Insert one row with ID 7 and array values 101, 202, and 303.
- Run SELECT id AS source_id, unnest(arr) AS elem FROM the_table WHERE id = 7 ORDER BY elem DESC.
- Check the column names, row count, values, and order, then run the same query again in the same session.
- Stub / mock content: Local database authentication was disabled so the test client could connect to the development instance. The table and array data were created specifically for this test; no application response mocks or route interceptions were used.
- Code Analysis: The PR adds server/analyzer/unset_max1row_for_srfs.go and registers UnsetMax1RowForSRFs in server/analyzer/init.go:118-126. The new rule scans expressions in lines 34-40, detects an expression implementing sql.RowIterExpression with ReturnsRowIter() true, and clears QFlagMax1Row at lines 42-44. In server/doltgres_handler.go:392-417, a result with a set QFlagMax1Row uses resultForMax1RowIter, while a cleared flag takes h.resultForDefaultIter. The retest reaches the latter path and returns a three-by-three expansion, so the PR's new flag transition exposes an incorrect SRF projection iteration path when a scalar companion column is present. The requested ORDER BY is also not preserved by that path. The smallest practical fix is to make the default SRF projection iterator consume each source row once and preserve the sort operator's output, or narrowly avoid clearing the flag for this shape until that iterator behavior is fixed; a broad query-engine rewrite is not required.
- Why this is likely a bug: The corrected local query completed twice with valid aliases and stable, reproducible nine-row output, while the fixture contains one source row and three array elements. The duplicate rows are silently wrong rather than a parser or connection failure, and the output contradicts the explicit descending sort requirement. The PR directly changes which handler branch runs by clearing QFlagMax1Row, so this is a production-code defect in the newly enabled SRF execution path rather than an authentication setup artifact.
Relevant code
server/analyzer/unset_max1row_for_srfs.go:34-44
containsSRF := false
transform.InspectExpressions(ctx, node, func(ctx *sql.Context, expr sql.Expression) bool {
if rowIterExpr, ok := expr.(sql.RowIterExpression); ok && rowIterExpr.ReturnsRowIter() {
containsSRF = true
}
return !containsSRF
})
if containsSRF {
qFlags.Unset(sql.QFlagMax1Row)
}server/analyzer/init.go:118-126
analyzer.OnceAfterAll = insertAnalyzerRules(analyzer.OnceAfterAll, analyzer.QuoteDefaultColumnValueNamesId, false,
analyzer.Rule{Id: ruleId_OptimizeFunctions, Apply: OptimizeFunctions},
...
analyzer.Rule{Id: ruleId_UnsetMax1RowForSRFs, Apply: UnsetMax1RowForSRFs},
)server/doltgres_handler.go:403-417
} else if analyzer.FlagIsSet(qFlags, sql.QFlagMax1Row) {
...
r, err = resultForMax1RowIter(...)
} else {
...
r, processedAtLeastOneBatch, err = h.resultForDefaultIter(...)
}Evidence Package
Copy prompt for an agent
Ito QA identified the following failure during automated PR testing. Please investigate and propose a fix.
**Medium severity — SRF results are duplicated and misordered**
**What failed:** The query should return three rows with source_id 7 and elem values 303, 202, and 101. Instead, each value appears three times for nine total rows, in ascending order, and the second execution returns the same incorrect result.
- **Impact:** Queries that expand array values alongside other columns can return duplicate rows and the wrong sort order. Users relying on this query shape may receive silently incorrect data until the query is changed or the defect is fixed.
- **Steps to reproduce:**
1. Create a table with an integer primary key and integer array column.
2. Insert one row with ID 7 and array values 101, 202, and 303.
3. Run SELECT id AS source_id, unnest(arr) AS elem FROM the_table WHERE id = 7 ORDER BY elem DESC.
4. Check the column names, row count, values, and order, then run the same query again in the same session.
- **Stub / mock content:** Local database authentication was disabled so the test client could connect to the development instance. The table and array data were created specifically for this test; no application response mocks or route interceptions were used.
- **Code analysis:** The PR adds server/analyzer/unset_max1row_for_srfs.go and registers UnsetMax1RowForSRFs in server/analyzer/init.go:118-126. The new rule scans expressions in lines 34-40, detects an expression implementing sql.RowIterExpression with ReturnsRowIter() true, and clears QFlagMax1Row at lines 42-44. In server/doltgres_handler.go:392-417, a result with a set QFlagMax1Row uses resultForMax1RowIter, while a cleared flag takes h.resultForDefaultIter. The retest reaches the latter path and returns a three-by-three expansion, so the PR's new flag transition exposes an incorrect SRF projection iteration path when a scalar companion column is present. The requested ORDER BY is also not preserved by that path. The smallest practical fix is to make the default SRF projection iterator consume each source row once and preserve the sort operator's output, or narrowly avoid clearing the flag for this shape until that iterator behavior is fixed; a broad query-engine rewrite is not required.
- **Why this is likely a bug:** The corrected local query completed twice with valid aliases and stable, reproducible nine-row output, while the fixture contains one source row and three array elements. The duplicate rows are silently wrong rather than a parser or connection failure, and the output contradicts the explicit descending sort requirement. The PR directly changes which handler branch runs by clearing QFlagMax1Row, so this is a production-code defect in the newly enabled SRF execution path rather than an authentication setup artifact.
**Relevant code:**
`server/analyzer/unset_max1row_for_srfs.go:34-44`
~~~go
containsSRF := false
transform.InspectExpressions(ctx, node, func(ctx *sql.Context, expr sql.Expression) bool {
if rowIterExpr, ok := expr.(sql.RowIterExpression); ok && rowIterExpr.ReturnsRowIter() {
containsSRF = true
}
return !containsSRF
})
if containsSRF {
qFlags.Unset(sql.QFlagMax1Row)
}
~~~
`server/analyzer/init.go:118-126`
~~~go
analyzer.OnceAfterAll = insertAnalyzerRules(analyzer.OnceAfterAll, analyzer.QuoteDefaultColumnValueNamesId, false,
analyzer.Rule{Id: ruleId_OptimizeFunctions, Apply: OptimizeFunctions},
...
analyzer.Rule{Id: ruleId_UnsetMax1RowForSRFs, Apply: UnsetMax1RowForSRFs},
)
~~~
`server/doltgres_handler.go:403-417`
~~~go
} else if analyzer.FlagIsSet(qFlags, sql.QFlagMax1Row) {
...
r, err = resultForMax1RowIter(...)
} else {
...
r, processedAtLeastOneBatch, err = h.resultForDefaultIter(...)
}
~~~The final projection re-evaluated set-returning expressions already expanded by the projection materialized below the sort, multiplying the output rows and clobbering the sort order. Fixed in GMS; requires a GMS bump to pick up.
…lumn was renamed to ErrFieldNoDefaultValue
|
Diff SummaryCoverage focuses on database lookups and array expansion, including scalar and multi-row results, companion-value alignment, ordering, repeated and concurrent use, retry behavior, connection reuse, and catalog metadata. It includes normal flows plus boundary, protocol-level, concurrency, and regression-oriented checks. Safe to merge — the exercised behaviors are healthy, with no regressions, new failures, or previously identified PR-attributable failures. The remaining previously passing areas were not exercised in this run but present no merge-blocking signal. Tests run by Ito
Tip Reply with @itoqa to send us feedback on this test run. |


Fixes a "result max1Row iterator returned more than one row" error when a set-returning function is projected over a unique-index point lookup.
Fixes #3111.