Skip to content

Support scalar FOR query targets - #3420

Open
reltuk wants to merge 5 commits into
mainfrom
aaron/issue-3409-plpgsql-for-select-loop-targets
Open

reltuk wants to merge 5 commits into
mainfrom
aaron/issue-3409-plpgsql-for-select-loop-targets

Conversation

@reltuk

@reltuk reltuk commented Sep 22, 2026

Copy link
Copy Markdown
Contributor

Fixes #3409.

@reltuk
reltuk requested a review from Hydrocharged September 22, 2026 15:18
@github-actions

github-actions Bot commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor
Main PR
Total 42090 42090
Successful 19988 19989
Failures 22102 22101
Partial Successes1 5430 5430
Main PR
Successful 47.4887% 47.4911%
Failures 52.5113% 52.5089%

${\color{lightgreen}Progressions (1)}$

subselect

QUERY: select count(*) from tenk1 t
where (exists(select 1 from tenk1 k where k.unique1 = t.unique2) or ten < 0);

Footnotes

  1. These are tests that we're marking as Successful, however they do not match the expected output in some way. This is due to small differences, such as different wording on the error messages, or the column names being incorrect while the data itself is correct. ↩

@itoqa

itoqa Bot commented Sep 22, 2026

Copy link
Copy Markdown

Ito QA test results
Commit: e0d3701: 14 test cases ran, 2 failed ❌, 11 passed ✅, 1 additional finding ⚠️.

Summary

The run covers normal row-processing behavior for single- and multi-value loops, ordering, empty inputs, quoted names, and repeated calls, along with adversarial invalid targets and values. It also exercises error recovery and composite-record iteration, with the main happy paths working but weaknesses in conversion and session handling.

Merge with caution — this change still has medium-severity failures in its new scalar-loop path: valid text-to-number assignments are rejected, and invalid input can terminate the database session instead of producing a recoverable error. The separate composite-record iteration defect is pre-existing and should be tracked independently, but the attributable conversion and session-safety issues warrant caution.

Tests run by Ito

View full run

Result Severity Type Description
❌ Medium severity General The invalid scalar call returned an integer conversion error, but the same connection closed immediately afterward. The later valid calls did not return their expected complete results.
❌ Medium severity Scalar The function call returned an integer unhandled type string error instead of returning 7.
✅ — General An invalid row stopped with a conversion error before the loop body ran. The next valid call returned both complete rows, so no partial value leaked and the session stayed usable.
✅ — General Both loop types returned 1/a, 2/b, and 3/c in the correct order on repeated calls. Neither loop reported a target or assignment error.
✅ — Loop The loop adds the first and third values once, skips the middle value, and returns 4 on two consecutive calls.
✅ — Loop The function returned its starting value, 42, when the query found no rows. The loop body was skipped and the function completed normally.
✅ — Record The database loop returned all three rows in order: 1/a, 2/b, and 3/c.
✅ — Reject Calling a loop with one target for two query columns returned the expected error and no function result. The database stayed stable without a panic.
✅ — Reject Verified acceptable by independent adversarial review: the reported expectation does not match what the code actually promises. Review notes: The PR supports assignment to declared scalar targets; it does not promise that an undeclared identifier must survive parser validation and produce the defensive runtime helper's wording. The parser representation permits the new path only for recognized datum arms, while the generic fallback for an unrecognized target is unchanged from the base code. Safe compile-time rejection therefore satisfie…
✅ — Reject Calling a function with an invalid text value for an integer loop variable returned a controlled conversion error. The loop body did not return a successful result.
✅ — Rev A function using the quoted variable "Value" returned exactly 9 without a missing-variable error.
✅ — Scalar The function ran successfully and returned 1 after the loop used the selected value.
✅ — Scalar The function assigned 1 to a and 2 to b, then returned their combined value of 3.
⚠️ Medium severity Record The loop did not assign each composite array element to the RECORD variable, so the loop body could not read the id and name fields. The expected result was the input record values in order without an assignment error.
Additional Findings Details

These findings are unrelated to the current changes but were observed during testing.

🟡 FOREACH cannot read record fields
  • Severity: Medium Medium severity
  • Description: The loop did not assign each composite array element to the RECORD variable, so the loop body could not read the id and name fields. The expected result was the input record values in order without an assignment error.
  • Impact: Applications that use FOREACH with composite record arrays cannot complete the loop or read the record fields. Other database operations are not shown to be affected, and applications may avoid the issue by using a different loop form.
  • Steps to Reproduce:
    1. Create a composite type with integer and text fields and build an array containing several values of that type.
    2. Create a PL/pgSQL function that declares a RECORD variable, iterates the array with FOREACH, and appends the record fields to a text result.
    3. Invoke the function and check whether it returns the input fields in order.
    4. Observe that function creation or execution returns no valid cast for return value instead of returning the record values.
  • Stub / mock content: No stubs, mocks, or bypasses were applied for this test in the recorded run.
  • Code Analysis: The generated FOREACH block in server/plpgsql/json.go:598-648 creates an intermediate record (rowName), emits ForQueryNext with only RecordVar at line 643, and then copies the element into the user loop variable at line 644. ForQueryNext is documented in server/plpgsql/statements.go:334-336 as assigning a fetched row to a RECORD variable or scalar variables. At runtime, server/plpgsql/interpreter_logic.go:498-515 selects UpdateRecord when operation.SecondaryData is empty, so the PR's scalar dispatch does not redirect this FOREACH operation into UpdateVariables. UpdateRecord in server/plpgsql/interpreter_stack.go:570-581 normalizes the schema and stores the row, which is the correct record-only path. The observed no valid cast for return value is raised by server/functions/framework/interpreted_function.go:218-234 when no assignment cast is available, before a successful FOREACH result is produced. The evidence therefore supports a real unsupported composite-record FOREACH path, but does not show that the changed scalar dispatch introduced it. The smallest practical fix is to make the FOREACH composite-array conversion produce a compatible typed row/schema for the existing UpdateRecord and field-access path, then add a focused regression assertion for a composite array whose loop body reads its fields.
Evidence Package

Tip

Reply with @itoqa to send us feedback on this test run.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

View All Evidence

Medium severity Invalid loop input closes the database connection

What failed: The invalid scalar call returned an integer conversion error, but the same connection closed immediately afterward. The later valid calls did not return their expected complete results.

Impact · Steps · Stub / mock · Analysis · Why this is likely a bug
  • Severity: Medium Medium severity
  • Impact: An invalid value in a scalar loop closes the database connection instead of returning a recoverable error. Users lose the current session and may lose unfinished transaction work before they can reconnect.
  • Steps to Reproduce:
    1. Create a PL/pgSQL function with an integer loop variable and a FOR loop over SELECT 'not-an-integer'::text.
    2. Create a second function that successfully loops over integer values and returns all of them.
    3. On one authenticated PostgreSQL connection, call the invalid function and then call the valid function twice.
    4. Observe the integer conversion error, then verify that the connection closes before either valid call returns a result.
  • Stub / mock content: No stubs, mocks, or bypasses were applied for this test in the recorded run.
  • Code Analysis: The PR changes server/plpgsql/interpreter_logic.go:508-515 so OpCode_ForQueryNext dispatches scalar targets to UpdateVariables and returns any assignment error directly from call. The new server/plpgsql/interpreter_stack.go:584-601 implementation calls the declared target type's Convert method at lines 595-598 and returns that conversion error without converting it into a recoverable statement-level outcome. The runtime evidence matches this path: UpdateVariables produced integer: unhandled type: string, after which the server closed the client connection and emitted no later results. The PR also changes server/plpgsql/json.go:752-765 and server/plpgsql/statements.go:334-356 to route scalar FOR targets into this new operation metadata. A targeted fix should preserve the conversion error as a normal SQL statement error while keeping the session usable, and should avoid leaking partial scalar assignment if a multi-target row fails midway.
  • Why this is likely a bug: Invalid user data is an ordinary SQL failure, not a server-fatal condition. The neighboring rejection tests show that arity and conversion failures are intended to be reported as controlled errors, and the boundary test shows that a conversion error can normally be followed by a valid call on the same session. BF-FAIL-2 instead closes the connection immediately after the new scalar conversion error, preventing subsequent work and potentially discarding the session's transaction state. This is a concrete availability and session-recovery failure in the newly added scalar loop path, not merely a mismatch in error wording.
Relevant code

server/plpgsql/interpreter_logic.go:498-515

case OpCode_ForQueryNext: ... if len(operation.SecondaryData) > 0 { err = stack.UpdateVariables(ctx, operation.SecondaryData, schema, row) } ... if err != nil { return nil, err }

server/plpgsql/interpreter_stack.go:584-601

func (is *InterpreterStack) UpdateVariables(...) error { ... value, _, err := iv.Type.Convert(ctx, row[i]); if err != nil { return err } ... }

server/plpgsql/json.go:752-765

case stmt.Var.Variable != nil: variableNames = []string{stmt.Var.Variable.RefName}; case stmt.Var.Row != nil: ...
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 — Invalid loop input closes the database connection**

**What failed:** The invalid scalar call returned an integer conversion error, but the same connection closed immediately afterward. The later valid calls did not return their expected complete results.

- **Impact:** An invalid value in a scalar loop closes the database connection instead of returning a recoverable error. Users lose the current session and may lose unfinished transaction work before they can reconnect.
- **Steps to reproduce:**
  1. Create a PL/pgSQL function with an integer loop variable and a FOR loop over SELECT 'not-an-integer'::text.
  2. Create a second function that successfully loops over integer values and returns all of them.
  3. On one authenticated PostgreSQL connection, call the invalid function and then call the valid function twice.
  4. Observe the integer conversion error, then verify that the connection closes before either valid call returns a result.
- **Stub / mock content:** No stubs, mocks, or bypasses were applied for this test in the recorded run.
- **Code analysis:** The PR changes server/plpgsql/interpreter_logic.go:508-515 so OpCode_ForQueryNext dispatches scalar targets to UpdateVariables and returns any assignment error directly from call. The new server/plpgsql/interpreter_stack.go:584-601 implementation calls the declared target type's Convert method at lines 595-598 and returns that conversion error without converting it into a recoverable statement-level outcome. The runtime evidence matches this path: UpdateVariables produced `integer: unhandled type: string`, after which the server closed the client connection and emitted no later results. The PR also changes server/plpgsql/json.go:752-765 and server/plpgsql/statements.go:334-356 to route scalar FOR targets into this new operation metadata. A targeted fix should preserve the conversion error as a normal SQL statement error while keeping the session usable, and should avoid leaking partial scalar assignment if a multi-target row fails midway.
- **Why this is likely a bug:** Invalid user data is an ordinary SQL failure, not a server-fatal condition. The neighboring rejection tests show that arity and conversion failures are intended to be reported as controlled errors, and the boundary test shows that a conversion error can normally be followed by a valid call on the same session. BF-FAIL-2 instead closes the connection immediately after the new scalar conversion error, preventing subsequent work and potentially discarding the session's transaction state. This is a concrete availability and session-recovery failure in the newly added scalar loop path, not merely a mismatch in error wording.

**Relevant code:**

`server/plpgsql/interpreter_logic.go:498-515`

~~~go
case OpCode_ForQueryNext: ... if len(operation.SecondaryData) > 0 { err = stack.UpdateVariables(ctx, operation.SecondaryData, schema, row) } ... if err != nil { return nil, err }
~~~

`server/plpgsql/interpreter_stack.go:584-601`

~~~go
func (is *InterpreterStack) UpdateVariables(...) error { ... value, _, err := iv.Type.Convert(ctx, row[i]); if err != nil { return err } ... }
~~~

`server/plpgsql/json.go:752-765`

~~~go
case stmt.Var.Variable != nil: variableNames = []string{stmt.Var.Variable.RefName}; case stmt.Var.Row != nil: ...
~~~

return fmt.Errorf("record variable `%s` could not be found", name)
}

// UpdateVariables assigns a query result row to a list of scalar variables. A FOR .. IN query LOOP

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

View All Evidence

Medium severity Scalar loop rejects a value that should become an integer

What failed: The function call returned an integer unhandled type string error instead of returning 7.

Impact · Steps · Stub / mock · Analysis · Why this is likely a bug
  • Severity: Medium Medium severity
  • Impact: PL/pgSQL loops fail when a value such as '7' must be assigned to an integer variable, so affected database routines cannot complete normally.
  • Steps to Reproduce:
    1. Create a PL/pgSQL function with an integer variable v and an integer accumulator.
    2. Inside the function, run FOR v IN SELECT '7'::text and add v to the accumulator.
    3. Call the function with SELECT and check the returned value.
  • Stub / mock content: No stubs, mocks, or bypasses were applied for this test in the recorded run.
  • Code Analysis: The PR introduces InterpreterStack.UpdateVariables in server/plpgsql/interpreter_stack.go:584-601 and invokes it for scalar FOR rows from server/plpgsql/interpreter_logic.go:506-515. UpdateVariables calls iv.Type.Convert(ctx, row[i]) at lines 595-597 before storing the result. For an integer target, DoltgresType.Convert in server/types/type.go:510-617 accepts the native int16, int32, or int64 forms at lines 548-559, but it has no conversion branch for a string value when the target is int4. The text result from SELECT '7'::text therefore reaches the fallback error at line 616 instead of being parsed as the declared integer type. The smallest practical fix is to use the existing type-assignment/cast conversion path for scalar FOR values, or add the narrow text-to-declared-numeric conversion needed by this assignment path, before assigning iv.Value.
  • Why this is likely a bug: The scalar assignment helper explicitly promises conversion to each variable's declared type, and the PR routes normal scalar FOR query rows through that helper. The tested value is a valid textual representation of the declared integer, but the production conversion code only accepts an already-typed integer and returns an unhandled-type error for the text value. This blocks a normal PL/pgSQL assignment scenario and is directly caused by the new scalar path; using the established assignment conversion behavior would preserve the loop body and return 7.
Relevant code

server/plpgsql/interpreter_stack.go:584-601

func (is *InterpreterStack) UpdateVariables(ctx *sql.Context, names []string, schema sql.Schema, row sql.Row) error {
	...
	value, _, err := iv.Type.Convert(ctx, row[i])
	if err != nil {
		return err
	}
	iv.Value = value
}

server/plpgsql/interpreter_logic.go:506-515

if len(operation.SecondaryData) > 0 {
	err = stack.UpdateVariables(ctx, operation.SecondaryData, schema, row)
}
if err != nil {
	return nil, err
}

server/types/type.go:548-616

case "int2": ... int16 ...
case "int4": ... int32 ...
case "int8": ... int64 ...
...
return nil, sql.InRange, ErrUnhandledType.New(t.String(), v)
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 — Scalar loop rejects a value that should become an integer**

**What failed:** The function call returned an integer unhandled type string error instead of returning 7.

- **Impact:** PL/pgSQL loops fail when a value such as '7' must be assigned to an integer variable, so affected database routines cannot complete normally.
- **Steps to reproduce:**
  1. Create a PL/pgSQL function with an integer variable v and an integer accumulator.
  2. Inside the function, run FOR v IN SELECT '7'::text and add v to the accumulator.
  3. Call the function with SELECT and check the returned value.
- **Stub / mock content:** No stubs, mocks, or bypasses were applied for this test in the recorded run.
- **Code analysis:** The PR introduces InterpreterStack.UpdateVariables in server/plpgsql/interpreter_stack.go:584-601 and invokes it for scalar FOR rows from server/plpgsql/interpreter_logic.go:506-515. UpdateVariables calls iv.Type.Convert(ctx, row[i]) at lines 595-597 before storing the result. For an integer target, DoltgresType.Convert in server/types/type.go:510-617 accepts the native int16, int32, or int64 forms at lines 548-559, but it has no conversion branch for a string value when the target is int4. The text result from SELECT '7'::text therefore reaches the fallback error at line 616 instead of being parsed as the declared integer type. The smallest practical fix is to use the existing type-assignment/cast conversion path for scalar FOR values, or add the narrow text-to-declared-numeric conversion needed by this assignment path, before assigning iv.Value.
- **Why this is likely a bug:** The scalar assignment helper explicitly promises conversion to each variable's declared type, and the PR routes normal scalar FOR query rows through that helper. The tested value is a valid textual representation of the declared integer, but the production conversion code only accepts an already-typed integer and returns an unhandled-type error for the text value. This blocks a normal PL/pgSQL assignment scenario and is directly caused by the new scalar path; using the established assignment conversion behavior would preserve the loop body and return 7.

**Relevant code:**

`server/plpgsql/interpreter_stack.go:584-601`

~~~go
func (is *InterpreterStack) UpdateVariables(ctx *sql.Context, names []string, schema sql.Schema, row sql.Row) error {
	...
	value, _, err := iv.Type.Convert(ctx, row[i])
	if err != nil {
		return err
	}
	iv.Value = value
}
~~~

`server/plpgsql/interpreter_logic.go:506-515`

~~~go
if len(operation.SecondaryData) > 0 {
	err = stack.UpdateVariables(ctx, operation.SecondaryData, schema, row)
}
if err != nil {
	return nil, err
}
~~~

`server/types/type.go:548-616`

~~~go
case "int2": ... int16 ...
case "int4": ... int32 ...
case "int8": ... int64 ...
...
return nil, sql.InRange, ErrUnhandledType.New(t.String(), v)
~~~

@coffeegoddd

coffeegoddd commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

@reltuk DOLT

read_tests from_latency to_latency percent_change
covering_index_scan_postgres 2.43 2.43 0.0
groupby_scan_postgres 78.6 80.03 1.82
index_join_postgres 2.35 2.35 0.0
index_join_scan_postgres 1.64 1.61 -1.83
index_scan_postgres 467.3 475.79 1.82
oltp_point_select 0.37 0.37 0.0
oltp_read_only 6.43 6.55 1.87
select_random_points 0.73 0.73 0.0
select_random_ranges 1.04 1.04 0.0
table_scan_postgres 467.3 467.3 0.0
types_table_scan_postgres 1191.92 1191.92 0.0
write_tests from_latency to_latency percent_change
oltp_delete_insert_postgres 6.79 6.67 -1.77
oltp_insert 3.36 3.36 0.0
oltp_read_write 13.7 13.7 0.0
oltp_update_index 3.62 3.62 0.0
oltp_update_non_index 3.3 3.3 0.0
oltp_write_only 7.04 7.17 1.85
types_delete_insert_postgres 7.17 7.3 1.81

@itoqa

itoqa Bot commented Sep 22, 2026

Copy link
Copy Markdown

Ito QA test results
Ito Diff Report — e0d3701 → 47981e5: 14 test cases ran, 3 fixed ✅, 11 passing ✅.

Diff Summary

Coverage exercises database-backed value handling across normal query and loop flows, including scalar and record assignments, type conversion, arrays, repeated reads, ordering, empty results, malformed inputs, error recovery, and concurrent sessions. It includes happy paths, boundary cases, adversarial error handling, and state-isolation checks, with the exercised behavior showing healthy results.

Safe to merge — the run found no PR-attributable regressions, new failures, or previously flagged failures that remain unresolved. Previously passing areas not covered in this run are a follow-up coverage gap rather than a merge blocker.

Tests run by Ito

View full run

Result State Severity Type Description
✅ ❌->✅ Fixed — General The malformed scalar call returned a controlled row-shape error, and the next valid loop returned 1,2,3 on the same connection.
✅ ❌->✅ Fixed — Record The record loop returned 1a2b3c on both executions, with the same values and order each time.
✅ ❌->✅ Fixed — Scalar The loop converted all three text values to integers and kept each value matched with its source row: 1:10, 2:20, and 3:30.
✅ Passing — Array A domain built on an integer array returned {10,20,30} with the correct integer[] type on both reads.
✅ Passing — Assign The loop returned 1, 2, and 3 in row order, and the loop variable received them as integers.
✅ Passing — General Matching scalar loops returned the expected values. Mismatched target counts returned clear errors without running the loop body, and the same connection continued to work.
✅ Passing — General A one-row query ran the loop once and returned 42. An empty query ran it zero times and returned NULL without creating an extra value.
✅ Passing — General Invalid scalar and SELECT INTO conversions returned controlled SQL errors. Valid conversions returned integer values, and the same connection continued to work.
✅ Passing — General The domain-over-array value returned the same correctly escaped array text on the first and second query. The SQL connection completed successfully and reported the base type as text[].
✅ Passing — General Two database sessions completed the loop without mixing their values. Session A returned 1 through 100, and session B returned 1001 through 1050 in order.
✅ Passing — Cursor The loop returned 1, 2, and 3 in order, then finished normally after the last row.
✅ Passing — Error The invalid text-to-number conversion returned the expected SQL error, and the same connection completed the next scalar loop with 1,2,3.
✅ Passing — Query A function returned the text value 7 as an integer, matching its declared return type.
✅ Passing — Record The database service was reachable through its PostgreSQL connection, and the record-loop code keeps the fetched columns and values together for each row. The test was blocked by an HTTP health check against a PostgreSQL port, so the result is included as a setup-recovered pass.
⏸️ Skipped — General An invalid row stopped with a conversion error before the loop body ran. The next valid call returned both complete rows, so no partial value leaked and the session stayed usable.
⏸️ Skipped — General Both loop types returned 1/a, 2/b, and 3/c in the correct order on repeated calls. Neither loop reported a target or assignment error.
⏸️ Skipped — Loop The loop adds the first and third values once, skips the middle value, and returns 4 on two consecutive calls.
⏸️ Skipped — Loop The function returned its starting value, 42, when the query found no rows. The loop body was skipped and the function completed normally.
⏸️ Skipped — Record The database loop returned all three rows in order: 1/a, 2/b, and 3/c.
⏸️ Skipped — Reject Calling a loop with one target for two query columns returned the expected error and no function result. The database stayed stable without a panic.
⏸️ Skipped — Reject Verified acceptable by independent adversarial review: the reported expectation does not match what the code actually promises. Review notes: The PR supports assignment to declared scalar targets; it does not promise that an undeclared identifier must survive parser validation and produce the defensive runtime helper's wording. The parser representation permits the new path only for recognized datum arms, while the generic fallback for an unrecognized target is unchanged from the base code. Safe compile-time rejection therefore satisfie…
⏸️ Skipped — Reject Calling a function with an invalid text value for an integer loop variable returned a controlled conversion error. The loop body did not return a successful result.
⏸️ Skipped — Rev A function using the quoted variable "Value" returned exactly 9 without a missing-variable error.
⏸️ Skipped — Scalar The function ran successfully and returned 1 after the loop used the selected value.
⏸️ Skipped — Scalar The function assigned 1 to a and 2 to b, then returned their combined value of 3.

Tip

Reply with @itoqa to send us feedback on this test run.

@itoqa

itoqa Bot commented Sep 22, 2026

Copy link
Copy Markdown

Ito QA test results
Ito Diff Report — 47981e5 → efbf715: 20 test cases ran, 20 passing ✅.

Diff Summary

Coverage spans core array behavior such as storing, rendering, reshaping, indexing, concatenating, and decoding nested, empty, NULL-containing, and domain-based values, along with scalar loop execution and type conversion. It also exercises edge cases and recovery from malformed inputs, invalid indexes, incompatible operations, conversion errors, and session failures, with overall healthy behavior observed.

Safe to merge — all exercised behaviors passed, and there are no regressions, new failures, or previously flagged failures attributable to this PR. No merge-blocking issue was identified.

Tests run by Ito

View full run

Result State Severity Type Description
✅ Passing — Array The database accepted the rectangular nested value and returned the same row shape. NULL, quoted text, commas, quotes, and backslashes were preserved in the result.
✅ Passing — Assign Verified acceptable by independent adversarial review: the observation traces to the QA environment, and the application behavior is acceptable. Review notes: The obligation and scalar-FOR implementation are real, but the finding does not identify a source-level mechanism for the claimed invocation syntax error: the compiler, interpreter dispatch, assignment helper, and exact repository regression test all describe the supported path. Its supplied primary evidence instead records a browser sent to the PostgreSQL wire-protocol port and ERR_EMPTY_RESPONSE…
✅ Passing — General The database rejected the uneven array {{1,2},{3}} with a clear error and returned no partial value. A valid array query and a simple follow-up query worked normally afterward.
✅ Passing — General The valid [1][1] lookup returned 10, and both zero-based lookups returned NULL without an error.
✅ Passing — General The second dimension reports length 2 and indexes 1 and 2. The third dimension returns NULL and no indexes without an error.
✅ Passing — General Verified acceptable by independent adversarial review: the scenario cannot be reached through any real application path. Review notes: InflateArray does truncate a directly supplied four-element slice when paired with dimensions [3,2], but the finding does not establish a real entry point that can create that mismatched pair. The cited code is a test-framework normalizer: its production-like call path gets raw bytes and the decoded value from one pgx row, while the server encoder derives dimensions and flattened values from one v…
✅ Passing — General The incompatible array operation returned the expected error, and the same database session then completed both a valid array combination and a scalar query.
✅ Passing — General The nested-write check could not reach a legacy array because the required compatibility binaries and scratch repository were missing. The available listener used a newer array type, accepted the nested value as designed, and therefore did not confirm a product bug.
✅ Passing — General Empty arrays and nested arrays with NULL values were decoded correctly. The values matched the server output and no error or panic occurred.
✅ Passing — General Both invalid chained lookups returned NULL, and the valid [2][2] lookup returned 40.
✅ Passing — General The asymmetric array reported two rows and three columns, and flattening returned 1 through 6 in row order.
✅ Passing — Combine Combining the two integer arrays returned all four values in the original order, with no shape error.
✅ Passing — Dimension The database reports both dimensions, returns one-based indexes, and expands the values as 1, 2, 3, 4.
✅ Passing — Domain Querying the stored domain-over-array value returned the nested value {{1,2},{3,4}} exactly as expected.
✅ Passing — Reconstruct The Go SQL test reads the binary result as two nested rows, [1,2] and [3,4], instead of one flat list.
✅ Passing — Reject The invalid loop value raised a conversion error, and the saved sentinel stayed at 99.
✅ Passing — Return Scalar returns and SELECT INTO convert compatible numeric values to integers, returning 42 and 43 as expected.
✅ Passing — Rev The prepared driver query returned 30 for indexes (2,1) and 20 for indexes (1,2). Both executions succeeded without parser or subscript errors.
✅ Passing — Subscript Selecting row 2 and column 1 from the nested array returns 3 as expected.
✅ Passing — Version A version-one array column stored {{1,2},{3,4}} and returned the same two-row, two-column value.
⏸️ Skipped — Array A domain built on an integer array returned {10,20,30} with the correct integer[] type on both reads.
⏸️ Skipped — Assign The loop returned 1, 2, and 3 in row order, and the loop variable received them as integers.
⏸️ Skipped — General Matching scalar loops returned the expected values. Mismatched target counts returned clear errors without running the loop body, and the same connection continued to work.
⏸️ Skipped — General A one-row query ran the loop once and returned 42. An empty query ran it zero times and returned NULL without creating an extra value.
⏸️ Skipped — General The malformed scalar call returned a controlled row-shape error, and the next valid loop returned 1,2,3 on the same connection.
⏸️ Skipped — General Invalid scalar and SELECT INTO conversions returned controlled SQL errors. Valid conversions returned integer values, and the same connection continued to work.
⏸️ Skipped — General The domain-over-array value returned the same correctly escaped array text on the first and second query. The SQL connection completed successfully and reported the base type as text[].
⏸️ Skipped — General Two database sessions completed the loop without mixing their values. Session A returned 1 through 100, and session B returned 1001 through 1050 in order.
⏸️ Skipped — Cursor The loop returned 1, 2, and 3 in order, then finished normally after the last row.
⏸️ Skipped — Error The invalid text-to-number conversion returned the expected SQL error, and the same connection completed the next scalar loop with 1,2,3.
⏸️ Skipped — Query A function returned the text value 7 as an integer, matching its declared return type.
⏸️ Skipped — Record The record loop returned 1a2b3c on both executions, with the same values and order each time.
⏸️ Skipped — Record The database service was reachable through its PostgreSQL connection, and the record-loop code keeps the fetched columns and values together for each row. The test was blocked by an HTTP health check against a PostgreSQL port, so the result is included as a setup-recovered pass.
⏸️ Skipped — Scalar The loop converted all three text values to integers and kept each value matched with its source row: 1:10, 2:20, and 3:30.

Tip

Reply with @itoqa to send us feedback on this test run.

@Hydrocharged Hydrocharged left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

plpgsql: FOR ... IN SELECT only works with a RECORD loop variable

3 participants