Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Additional PL/pgSQL parsing fixes #263

Merged
merged 7 commits into from
Oct 9, 2024
30 changes: 28 additions & 2 deletions scripts/extract_source.rb
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,8 @@ def write_out
} else if (list_length(origtypname->names) == 2) {
ns = linitial_node(String, origtypname->names)->sval;
ident = lsecond_node(String, origtypname->names)->sval;
if (strcmp(ns, "pg_catalog") != 0)
typ->ttype = PLPGSQL_TTYPE_REC;
}
} else {
typ->typoid = typeOid;
Expand All @@ -570,8 +572,31 @@ def write_out
return typ;
}
))
runner.mock('parse_datatype', 'static PLpgSQL_type * parse_datatype(const char *string, int location) { PLpgSQL_type *typ; typ = (PLpgSQL_type *) palloc0(sizeof(PLpgSQL_type)); typ->typname = pstrdup(string); typ->ttype = strcmp(string, "RECORD") == 0 ? PLPGSQL_TTYPE_REC : PLPGSQL_TTYPE_SCALAR; return typ; }')
runner.mock('get_collation_oid', 'Oid get_collation_oid(List *name, bool missing_ok) { return -1; }')
runner.mock('parse_datatype', %(
#include "catalog/pg_collation_d.h"
static PLpgSQL_type * parse_datatype(const char *string, int location) {
PLpgSQL_type *typ;

/* Ignore trailing spaces */
size_t len = strlen(string);
while (len > 0 && scanner_isspace(string[len - 1])) --len;

typ = (PLpgSQL_type *) palloc0(sizeof(PLpgSQL_type));
typ->typname = pstrdup(string);
typ->ttype = pg_strncasecmp(string, "RECORD", len) == 0 ? PLPGSQL_TTYPE_REC : PLPGSQL_TTYPE_SCALAR;
if (pg_strncasecmp(string, "REFCURSOR", len) == 0 || pg_strncasecmp(string, "CURSOR", len) == 0)
{
typ->typoid = REFCURSOROID;
}
else if (pg_strncasecmp(string, "TEXT", len) == 0)
{
typ->typoid = TEXTOID;
typ->collation = DEFAULT_COLLATION_OID;
}
return typ;
}
))
runner.mock('get_collation_oid', 'Oid get_collation_oid(List *name, bool missing_ok) { return DEFAULT_COLLATION_OID; }')
runner.mock('plpgsql_parse_wordtype', 'PLpgSQL_type * plpgsql_parse_wordtype(char *ident) { return NULL; }')
runner.mock('plpgsql_parse_wordrowtype', 'PLpgSQL_type * plpgsql_parse_wordrowtype(char *ident) { return NULL; }')
runner.mock('plpgsql_parse_cwordtype', 'PLpgSQL_type * plpgsql_parse_cwordtype(List *idents) { return NULL; }')
Expand Down Expand Up @@ -665,6 +690,7 @@ def write_out

# Other required functions
runner.deep_resolve('pg_printf')
runner.deep_resolve('pg_strncasecmp')

# Retain these functions for optional 32-bit support
# (see BITS_PER_BITMAPWORD checks in bitmapset.c)
Expand Down
25 changes: 12 additions & 13 deletions src/pg_query_parse_plpgsql.c
Original file line number Diff line number Diff line change
Expand Up @@ -219,20 +219,19 @@ static PLpgSQL_function *compile_create_function_stmt(CreateFunctionStmt* stmt)
foreach(lc, stmt->parameters)
{
FunctionParameter *param = lfirst_node(FunctionParameter, lc);
char buf[32];
PLpgSQL_type *argdtype;
PLpgSQL_variable *argvariable;
PLpgSQL_nsitem_type argitemtype;
snprintf(buf, sizeof(buf), "$%d", foreach_current_index(lc) + 1);
argdtype = plpgsql_build_datatype(UNKNOWNOID, -1, InvalidOid, param->argType);
argvariable = plpgsql_build_variable(param->name ? param->name : buf, 0, argdtype, false);
if (param->mode == FUNC_PARAM_OUT || param->mode == FUNC_PARAM_INOUT || param->mode == FUNC_PARAM_TABLE)
function->out_param_varno = argvariable->dno;
argitemtype = argvariable->dtype == PLPGSQL_DTYPE_VAR ? PLPGSQL_NSTYPE_VAR : PLPGSQL_NSTYPE_REC;
plpgsql_ns_additem(argitemtype, argvariable->dno, buf);
if (param->name != NULL)
{
char buf[32];
PLpgSQL_type *argdtype;
PLpgSQL_variable *argvariable;
PLpgSQL_nsitem_type argitemtype;
snprintf(buf, sizeof(buf), "$%d", foreach_current_index(lc) + 1);
argdtype = plpgsql_build_datatype(UNKNOWNOID, -1, InvalidOid, param->argType);
argvariable = plpgsql_build_variable(param->name ? param->name : buf, 0, argdtype, false);
argitemtype = argvariable->dtype == PLPGSQL_DTYPE_VAR ? PLPGSQL_NSTYPE_VAR : PLPGSQL_NSTYPE_REC;
plpgsql_ns_additem(argitemtype, argvariable->dno, buf);
if (param->name != NULL)
plpgsql_ns_additem(argitemtype, argvariable->dno, param->name);
}
plpgsql_ns_additem(argitemtype, argvariable->dno, param->name);
}

/* Set up as though in a function returning VOID */
Expand Down
2 changes: 1 addition & 1 deletion src/postgres/src_backend_catalog_namespace.c
Original file line number Diff line number Diff line change
Expand Up @@ -905,7 +905,7 @@ NameListToString(List *names)
* Note that this will only find collations that work with the current
* database's encoding.
*/
Oid get_collation_oid(List *name, bool missing_ok) { return -1; }
Oid get_collation_oid(List *name, bool missing_ok) { return DEFAULT_COLLATION_OID; }


/*
Expand Down
2 changes: 2 additions & 0 deletions src/postgres/src_pl_plpgsql_src_pl_comp.c
Original file line number Diff line number Diff line change
Expand Up @@ -898,6 +898,8 @@ PLpgSQL_type * plpgsql_build_datatype(Oid typeOid, int32 typmod, Oid collation,
} else if (list_length(origtypname->names) == 2) {
ns = linitial_node(String, origtypname->names)->sval;
ident = lsecond_node(String, origtypname->names)->sval;
if (strcmp(ns, "pg_catalog") != 0)
typ->ttype = PLPGSQL_TTYPE_REC;
}
} else {
typ->typoid = typeOid;
Expand Down
25 changes: 24 additions & 1 deletion src/postgres/src_pl_plpgsql_src_pl_gram.c
Original file line number Diff line number Diff line change
Expand Up @@ -6090,7 +6090,30 @@ plpgsql_sql_error_callback(void *arg)
* This is handled the same as in check_sql_expr(), and we likewise
* expect that the given string is a copy from the source text.
*/
static PLpgSQL_type * parse_datatype(const char *string, int location) { PLpgSQL_type *typ; typ = (PLpgSQL_type *) palloc0(sizeof(PLpgSQL_type)); typ->typname = pstrdup(string); typ->ttype = strcmp(string, "RECORD") == 0 ? PLPGSQL_TTYPE_REC : PLPGSQL_TTYPE_SCALAR; return typ; }

#include "catalog/pg_collation_d.h"
static PLpgSQL_type * parse_datatype(const char *string, int location) {
PLpgSQL_type *typ;

/* Ignore trailing spaces */
size_t len = strlen(string);
while (len > 0 && scanner_isspace(string[len - 1])) --len;

typ = (PLpgSQL_type *) palloc0(sizeof(PLpgSQL_type));
typ->typname = pstrdup(string);
typ->ttype = pg_strncasecmp(string, "RECORD", len) == 0 ? PLPGSQL_TTYPE_REC : PLPGSQL_TTYPE_SCALAR;
if (pg_strncasecmp(string, "REFCURSOR", len) == 0 || pg_strncasecmp(string, "CURSOR", len) == 0)
{
typ->typoid = REFCURSOROID;
}
else if (pg_strncasecmp(string, "TEXT", len) == 0)
{
typ->typoid = TEXTOID;
typ->collation = DEFAULT_COLLATION_OID;
}
return typ;
}



/*
Expand Down
28 changes: 28 additions & 0 deletions src/postgres/src_port_pgstrcasecmp.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* Symbols referenced in this file:
* - pg_strcasecmp
* - pg_toupper
* - pg_strncasecmp
*--------------------------------------------------------------------
*/

Expand Down Expand Up @@ -72,7 +73,34 @@ pg_strcasecmp(const char *s1, const char *s2)
* Case-independent comparison of two not-necessarily-null-terminated strings.
* At most n bytes will be examined from each string.
*/
int
pg_strncasecmp(const char *s1, const char *s2, size_t n)
{
while (n-- > 0)
{
unsigned char ch1 = (unsigned char) *s1++;
unsigned char ch2 = (unsigned char) *s2++;

if (ch1 != ch2)
{
if (ch1 >= 'A' && ch1 <= 'Z')
ch1 += 'a' - 'A';
else if (IS_HIGHBIT_SET(ch1) && isupper(ch1))
ch1 = tolower(ch1);

if (ch2 >= 'A' && ch2 <= 'Z')
ch2 += 'a' - 'A';
else if (IS_HIGHBIT_SET(ch2) && isupper(ch2))
ch2 = tolower(ch2);

if (ch1 != ch2)
return (int) ch1 - (int) ch2;
}
if (ch1 == 0)
break;
}
return 0;
}

/*
* Fold a character to upper case.
Expand Down
17 changes: 12 additions & 5 deletions test/plpgsql_samples.expected.json

Large diffs are not rendered by default.

139 changes: 102 additions & 37 deletions test/plpgsql_samples.sql
Original file line number Diff line number Diff line change
Expand Up @@ -76,43 +76,43 @@ END;$$;
-- EXECUTE func_cmd;
-- END;$func$;

-- CREATE OR REPLACE FUNCTION cs_parse_url(
-- v_url IN VARCHAR,
-- v_host OUT VARCHAR, -- This will be passed back
-- v_path OUT VARCHAR, -- This one too
-- v_query OUT VARCHAR) -- And this one
-- AS $$
-- DECLARE
-- a_pos1 INTEGER;
-- a_pos2 INTEGER;
-- BEGIN
-- v_host := NULL;
-- v_path := NULL;
-- v_query := NULL;
-- a_pos1 := instr(v_url, '//');
--
-- IF a_pos1 = 0 THEN
-- RETURN;
-- END IF;
-- a_pos2 := instr(v_url, '/', a_pos1 + 2);
-- IF a_pos2 = 0 THEN
-- v_host := substr(v_url, a_pos1 + 2);
-- v_path := '/';
-- RETURN;
-- END IF;
--
-- v_host := substr(v_url, a_pos1 + 2, a_pos2 - a_pos1 - 2);
-- a_pos1 := instr(v_url, '?', a_pos2 + 1);
--
-- IF a_pos1 = 0 THEN
-- v_path := substr(v_url, a_pos2);
-- RETURN;
-- END IF;
--
-- v_path := substr(v_url, a_pos2, a_pos1 - a_pos2);
-- v_query := substr(v_url, a_pos1 + 1);
-- END;
-- $$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION cs_parse_url(
v_url IN VARCHAR,
v_host OUT VARCHAR, -- This will be passed back
v_path OUT VARCHAR, -- This one too
v_query OUT VARCHAR) -- And this one
AS $$
DECLARE
a_pos1 INTEGER;
a_pos2 INTEGER;
BEGIN
v_host := NULL;
v_path := NULL;
v_query := NULL;
a_pos1 := instr(v_url, '//');

IF a_pos1 = 0 THEN
RETURN;
END IF;
a_pos2 := instr(v_url, '/', a_pos1 + 2);
IF a_pos2 = 0 THEN
v_host := substr(v_url, a_pos1 + 2);
v_path := '/';
RETURN;
END IF;

v_host := substr(v_url, a_pos1 + 2, a_pos2 - a_pos1 - 2);
a_pos1 := instr(v_url, '?', a_pos2 + 1);

IF a_pos1 = 0 THEN
v_path := substr(v_url, a_pos2);
RETURN;
END IF;

v_path := substr(v_url, a_pos2, a_pos1 - a_pos2);
v_query := substr(v_url, a_pos1 + 1);
END;
$$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION cs_create_job(v_job_id integer) RETURNS void AS $$
DECLARE
Expand Down Expand Up @@ -545,3 +545,68 @@ CREATE FUNCTION test_cursor() RETURNS void AS $$
END
$$ language plpgsql;

CREATE FUNCTION public.dz_sumfunc(
IN p_in INTEGER
,OUT p_out public.dz_sumthing
)
AS $BODY$
DECLARE
BEGIN
p_out.sumattribute := p_in;
END;
$BODY$
LANGUAGE plpgsql;

-- Examples from https://www.postgresql.org/docs/16/plpgsql-declarations.html#PLPGSQL-DECLARATION-PARAMETERS
CREATE FUNCTION sales_tax(real) RETURNS real AS $$
DECLARE
subtotal ALIAS FOR $1;
BEGIN
RETURN subtotal * 0.06;
END;
$$ LANGUAGE plpgsql;

-- RETURN NEXT; with no expression https://www.postgresql.org/docs/16/plpgsql-control-structures.html#PLPGSQL-STATEMENTS-RETURNING-RETURN-NEXT
CREATE OR REPLACE FUNCTION public.test_pl(s integer, e integer)
RETURNS TABLE(id INTEGER) AS $$
BEGIN
id := s;
LOOP
EXIT WHEN id>e;
RETURN NEXT;
id := id + 1;
END LOOP;
END
$$
LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION trgfn()
RETURNS trigger AS $$
DECLARE
prior ALIAS FOR old;
updated ALIAS FOR new;
BEGIN
RETURN;
END;
$$
LANGUAGE plpgsql;

-- Example from https://www.postgresql.org/docs/16/plpgsql-cursors.html
CREATE FUNCTION reffunc2() RETURNS refcursor AS '
DECLARE
ref refcursor;
BEGIN
OPEN ref FOR SELECT col FROM test;
RETURN ref;
END;
' LANGUAGE plpgsql;

-- Example from https://www.postgresql.org/docs/16/plpgsql-declarations.html#PLPGSQL-DECLARATION-COLLATION
CREATE FUNCTION less_than(a text, b text) RETURNS boolean AS $$
DECLARE
local_a text COLLATE "en_US" := a;
local_b text := b;
BEGIN
RETURN local_a < local_b;
END;
$$ LANGUAGE plpgsql;
Loading