mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2026-08-17 16:34:44 -05:00
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:
@@ -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
|
||||
|
||||
+73
@@ -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)
|
||||
Reference in New Issue
Block a user