Skip to content

Support the built-in FOUND variable in plpgsql - #3252

Open
reltuk wants to merge 1 commit into
mainfrom
aaron/support-plpgsql-found
Open

Support the built-in FOUND variable in plpgsql#3252
reltuk wants to merge 1 commit into
mainfrom
aaron/support-plpgsql-found

Conversation

@reltuk

@reltuk reltuk commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

FOUND is declared for every function and set by the statements PostgreSQL defines as setting it: INTO clauses, bare DML, PERFORM, FOR..IN..SELECT, RETURN QUERY, and both kinds of FOR loop on exit. A FOR loop reports whether its body ran at all, which it records on its own scope rather than in FOUND, since PostgreSQL leaves FOUND alone while the loop is running. Every way of leaving a scope now routes through a single exitScope, so a loop reports FOUND and closes its cursor whether it ends normally, by EXIT, or by a labelled CONTINUE of an outer loop. A dynamic EXECUTE and a utility statement such as CREATE TABLE deliberately leave FOUND alone, so statements are classified at conversion time and the opcode that dynamic execution shares with static execution is told apart by an operation option.

FOUND is declared for every function and set by the statements PostgreSQL
defines as setting it: INTO clauses, bare DML, PERFORM, FOR..IN..SELECT,
RETURN QUERY, and both kinds of FOR loop on exit. A FOR loop reports
whether its body ran at all, which it records on its own scope rather
than in FOUND, since PostgreSQL leaves FOUND alone while the loop is
running. Every way of leaving a scope now routes through a single
exitScope, so a loop reports FOUND and closes its cursor whether it ends
normally, by EXIT, or by a labelled CONTINUE of an outer loop. A dynamic
EXECUTE and a utility statement such as CREATE TABLE deliberately leave
FOUND alone, so statements are classified at conversion time and the
opcode that dynamic execution shares with static execution is told apart
by an operation option.
@reltuk
reltuk requested a review from Hydrocharged September 1, 2026 15:13
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor
Main PR
Total 42090 42090
Successful 19289 19293
Failures 22801 22797
Partial Successes1 5459 5459
Main PR
Successful 45.8280% 45.8375%
Failures 54.1720% 54.1625%

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

plpgsql

QUERY: select test_found();
QUERY: select * from found_test_tbl;
QUERY: select sp_id_user('userx');

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 1, 2026

Copy link
Copy Markdown

Ito QA test results
Commit: d26ec8a: 19 test cases ran, 19 passed ✅.

Summary

Coverage focused on database business logic for reporting whether operations find or change data, including query and loop behavior, boundary and empty cases, nested control flow, error recovery, dynamic statements, and trigger record handling. Both normal workflows and adversarial edge cases behaved as expected, with no application bugs identified.

Safe to merge — the exercised data-handling, control-flow, error-recovery, and trigger behaviors showed no regressions or PR-attributable failures. No merge blocker was identified.

Tests run by Ito

View full run

Result Severity Type Description
General Operations that affect no rows correctly report false, while matching operations report true.
General Reverse integer loops handle their boundary values correctly. An equal start and end value runs once, while an empty range runs zero times and reports the correct FOUND value.
General The malformed conversion path was contained, and the later zero-row data change reported FOUND as false on repeated calls.
General Common table expression data changes and ordinary data changes correctly report whether they affected or produced rows.
General Trigger records stayed intact, and record targets handled matching, missing, and null successor rows correctly.
General Nested loops handle early exits and labelled continues correctly, including when an inner loop has no matching rows.
Cursor The nested loop was entered twice, and each entry processed both fixture rows. The function returned the expected total of 4 records with no stale cursor results.
Cursor Labelled loop control completed successfully, and the nested loops continued with fresh rows without scope errors.
Found A newly called PL/pgSQL function returned false from FOUND before it ran any other statement. The result confirms that the built-in value starts false and can be read with the expected spelling.
Found A function called with a true value still returned false from FOUND. The built-in value correctly took priority over the parameter with the same name.
Found A function with its own integer named found returned the sentinel value 5 after running. The value was preserved as expected.
Loop Non-empty query and integer loops kept the earlier FOUND value while their bodies ran and reported true after completing an iteration.
Loop Empty query and integer loops finish without running their bodies and report FOUND as false.
Loop PL/pgSQL reports the correct FOUND value after EXIT and labelled CONTINUE leave a loop.
Select Matching SELECT INTO and row-changing statements report true, while empty or zero-change statements report false.
Select A matching row fills the record and reports true. No matching row gives the record null fields and reports false.
Select After a row-producing statement succeeds, running a dynamic statement or creating a table keeps the earlier result unchanged. The same behavior holds when the first statement finds no rows.
Trigger An insert with no related successor completes safely, and an insert with a valid successor is accepted and stored.
Trigger A matching related record kept its expected fields, so the trigger compared the values successfully and saved the new row.

Tip

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

@coffeegoddd

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 77.19 75.82 -1.77
index_join_postgres 2.22 2.22 0.0
index_join_scan_postgres 1.61 1.58 -1.86
index_scan_postgres 458.96 475.79 3.67
oltp_point_select 0.36 0.37 2.78
oltp_read_only 6.32 6.21 -1.74
select_random_points 0.7 0.7 0.0
select_random_ranges 1.01 1.01 0.0
table_scan_postgres 467.3 467.3 0.0
types_table_scan_postgres 1170.65 1170.65 0.0
write_tests from_latency to_latency percent_change
oltp_delete_insert_postgres 6.67 6.67 0.0
oltp_insert 3.36 3.36 0.0
oltp_read_write 13.22 13.22 0.0
oltp_update_index 3.55 3.55 0.0
oltp_update_non_index 3.25 3.25 0.0
oltp_write_only 7.04 7.04 0.0
types_delete_insert_postgres 7.17 7.17 0.0

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.

2 participants