Fix syntax error editing SQL functions/procedures with a 'return' substring in the body (#10061)

The SQL-standard body detection used a regex that matched 'return'
anywhere in the body, so a plain SQL body containing a RETURNING clause
(or an identifier like 'returned_value') was wrongly treated as a
SQL-standard (BEGIN ATOMIC / RETURN) body. The generated CREATE OR
REPLACE statement then dropped the AS $BODY$ ... $BODY$ wrapper,
producing invalid SQL and a syntax error on save.

Anchor the RETURN form to the start of the body so only genuine
SQL-standard bodies are detected. Add unit tests for the detection.

Closes #10059
This commit is contained in:
Dave Page
2026-06-12 15:25:34 +05:30
committed by GitHub
parent 458dec3571
commit ed9bf2f249
3 changed files with 79 additions and 2 deletions
+1
View File
@@ -55,3 +55,4 @@ Bug fixes
| `Issue #9984 <https://github.com/pgadmin-org/pgadmin4/issues/9984>`_ - Fix the Docker entrypoint mishandling a quoted PGADMIN_CONFIG_CONFIG_DATABASE_URI, which caused a SQLAlchemy parse error and silently skipped PGADMIN_DEFAULT_EMAIL/PASSWORD setup.
| `Issue #9987 <https://github.com/pgadmin-org/pgadmin4/issues/9987>`_ - Fix "AttributeError: 'PgAdmin' object has no attribute 'login_manager'" crash when running setup.py user-management commands (add-user, update-user) from the CLI.
| `Issue #9988 <https://github.com/pgadmin-org/pgadmin4/issues/9988>`_ - Provide an actionable error when 'openid' is in OAUTH2_SCOPE but OAUTH2_SERVER_METADATA_URL is not set, instead of a cryptic Authlib failure.
| `Issue #10059 <https://github.com/pgadmin-org/pgadmin4/issues/10059>`_ - Fix the generated SQL for editing a SQL-language function/procedure whose body contains the word "return" (e.g. a RETURNING clause), which was wrongly treated as a SQL-standard body and produced a statement without the AS $BODY$ wrapper.
@@ -1759,10 +1759,13 @@ class FunctionView(PGChildNodeView, DataTypeReader, SchemaDiffObjectCompare):
r"^.*(?:\"|\')(?=.*(atomic)).*$"]
# valid regex, these combination a must in definition to detect a
# standard sql or pure sql
# standard sql or pure sql. The RETURN form must start the body
# (as a keyword) so that a plain SQL body which merely contains the
# substring "return" (e.g. a "RETURNING" clause or an identifier
# like "returned_value") is not mistaken for a SQL-standard body.
valid_match = [
r"(?=.*begin)(.+?(\n)+)(?=.*atomic)|(?=.*begin)(?=.*atomic)",
r"(?=return)"
r"\A\s*return\b"
]
is_func_def_sql_std = False
@@ -0,0 +1,73 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2026, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
from pgadmin.utils.route import BaseTestGenerator
from .. import FunctionView
class TestIsFunctionDefSqlStandard(BaseTestGenerator):
"""
Unit tests for FunctionView._is_function_def_sql_standard.
This guards against a regression (issue #10059) where a plain SQL
function/procedure body that merely contained the substring "return"
(e.g. a "RETURNING" clause) was wrongly detected as a SQL-standard
body, causing the generated CREATE OR REPLACE statement to drop the
"AS $BODY$ ... $BODY$" wrapper and fail with a syntax error.
"""
scenarios = [
('SQL-standard BEGIN ATOMIC body is detected', dict(
data=dict(lanname='sql',
prosrc='\nBEGIN ATOMIC\n INSERT INTO t VALUES (1);\nEND'
),
expected=True
)),
('SQL-standard RETURN body is detected', dict(
data=dict(lanname='sql', prosrc='RETURN $1 + $2'),
expected=True
)),
('SQL-standard RETURN body with leading whitespace is detected',
dict(
data=dict(lanname='sql', prosrc='\n return a;'),
expected=True
)),
('Plain SQL body with a RETURNING clause is not SQL-standard', dict(
data=dict(lanname='sql',
prosrc='INSERT INTO t(a) VALUES (1) RETURNING a;'),
expected=False
)),
('Plain SQL body referencing a "returned" identifier is not '
'SQL-standard', dict(
data=dict(lanname='sql',
prosrc='SELECT my_returned_value FROM t;'),
expected=False
)),
('Plain SQL body is not SQL-standard', dict(
data=dict(lanname='sql', prosrc='SELECT 1;'),
expected=False
)),
('RETURN must start the body, not merely a later line', dict(
data=dict(lanname='sql', prosrc='SELECT 1;\nreturn x;'),
expected=False
)),
('Non-sql language is never SQL-standard', dict(
data=dict(lanname='plpgsql',
prosrc='BEGIN\n RETURN;\nEND;'),
expected=False
)),
('Empty body is not SQL-standard', dict(
data=dict(lanname='sql', prosrc=''),
expected=False
)),
]
def runTest(self):
result = FunctionView._is_function_def_sql_standard(self.data)
self.assertEqual(result, self.expected)