## Code Analysis ### FOREACH generates a record-target fetch `server/plpgsql/json.go:642-645` ```go ForQueryInit{Query: iterateQuery}, ForQueryNext{RecordVar: rowName, GotoOffset: bodySize + 3}, Assignment{VariableName: varName, Expression: rowName + "." + foreachElementField}, ``` The generated FOREACH body fetches each array element through `ForQueryNext` as a record, then reads the generated record field into the user loop variable. ### The record-only dispatch is preserved `server/plpgsql/statements.go:334-340` ```go // ForQueryNext fetches the next row from the cursor of the scope it runs in and assigns it to either a // RECORD variable or a list of scalar variables. When the cursor is exhausted it jumps forward by // GotoOffset (like an If), exiting the loop. type ForQueryNext struct { RecordVar string VariableNames []string GotoOffset int32 } ``` `server/plpgsql/interpreter_logic.go:498-515` ```go case OpCode_ForQueryNext: schema, row, ok := stack.AdvanceCursor() if !ok { counter = operation.Index - 1 } else { var err error if len(operation.SecondaryData) > 0 { err = stack.UpdateVariables(ctx, operation.SecondaryData, schema, row) } else { err = stack.UpdateRecord(operation.Target, schema, row) } if err != nil { return nil, err } } ``` Because the generated operation has no scalar metadata, it takes the existing `UpdateRecord` branch rather than the scalar-list branch. This supports the report's conclusion that the failing composite-record path is a compatibility defect in the record value/schema handling, not evidence that the scalar dispatch changed this branch. ### The observed cast error is emitted by the return-value conversion path `server/functions/framework/interpreted_function.go:218-234` ```go cast, err := castsColl.GetAssignmentCast(ctx, sourceType, targetType) if err != nil { return nil, err } if !cast.ID.IsValid() { if sourceType.TypCategory == pgtypes.TypeCategory_StringTypes { cast.ID = id.NewCast(sourceType.ID, targetType.ID) cast.UseInOut = true } else { return nil, errors.New("no valid cast for return value") } } return cast.Eval(ctx, val, sourceType, targetType) ``` For the non-string record-shaped value, the missing assignment cast returns the same controlled error captured below before a successful FOREACH result is produced. ## Observed execution ### reproduction request ```sh docker exec -i bash -c 'PGPASSWORD=[REDACTED] psql -h 127.0.0.1 -p 5432 -U postgres -d postgres -v ON_ERROR_STOP=1' <<'SQL' DROP FUNCTION IF EXISTS qa_foreach_record(); CREATE OR REPLACE FUNCTION qa_foreach_record() RETURNS text AS $$ DECLARE r record; values_text text := ''; BEGIN FOREACH r IN ARRAY ARRAY[ROW(1, 'a'), ROW(2, 'b'), ROW(3, 'c')] LOOP values_text := values_text || CASE WHEN values_text = '' THEN '' ELSE ',' END || r.f1 || '/' || r.f2; END LOOP; RETURN values_text; END; $$ LANGUAGE plpgsql; SELECT qa_foreach_record(); SQL ``` ### response ```text ERROR: no valid cast for return value ``` The same record-target form that only counts array elements also returned this error during function creation: ```text ERROR: no valid cast for return value ``` ### result ```text EXIT_STATUS=3 ``` ### Test context The SQL endpoint was reached directly because port 5432 speaks the PostgreSQL wire protocol; the browser `ERR_EMPTY_RESPONSE` was not used as product evidence. The captured record-form output shows the controlled database error before the function could return the expected `1/a,2/b,3/c` fields. ### Result The runtime evidence and source excerpts support the reported RECORD-2 failure: record-target FOREACH execution returns `no valid cast for return value` instead of completing record assignment and exposing the record fields.