From 9b4109fbb702d0ae28694eecbd78617648f0f88a Mon Sep 17 00:00:00 2001 From: Lukas Fittl Date: Mon, 23 Sep 2024 22:49:33 -0700 Subject: [PATCH 1/7] PL/pgSQL: Detect fully qualified argument types outside of pg_catalog as records This ensures the PL/pgSQL lexer detects variable references to such variables as variables, instead of erroring out with "[var.name] is not a known variable". The trade-off here is that we might incorrectly detect custom data types registered outside of pg_catalog as being records, instead of data types. --- scripts/extract_source.rb | 2 ++ src/postgres/src_pl_plpgsql_src_pl_comp.c | 2 ++ test/plpgsql_samples.expected.json | 3 ++- test/plpgsql_samples.sql | 11 +++++++++++ 4 files changed, 17 insertions(+), 1 deletion(-) diff --git a/scripts/extract_source.rb b/scripts/extract_source.rb index 564abcb8..7c5522a2 100644 --- a/scripts/extract_source.rb +++ b/scripts/extract_source.rb @@ -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; diff --git a/src/postgres/src_pl_plpgsql_src_pl_comp.c b/src/postgres/src_pl_plpgsql_src_pl_comp.c index f99fab0e..4d6d537a 100644 --- a/src/postgres/src_pl_plpgsql_src_pl_comp.c +++ b/src/postgres/src_pl_plpgsql_src_pl_comp.c @@ -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; diff --git a/test/plpgsql_samples.expected.json b/test/plpgsql_samples.expected.json index ff5e61ce..8b5c1c07 100644 --- a/test/plpgsql_samples.expected.json +++ b/test/plpgsql_samples.expected.json @@ -20,5 +20,6 @@ {"PLpgSQL_function":{"datums":[{"PLpgSQL_var":{"refname":"str","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"varchar\""}}}},{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}},{"PLpgSQL_rec":{"refname":"v3","dno":2,"lineno":3}},{"PLpgSQL_var":{"refname":"v4","lineno":4,"datatype":{"PLpgSQL_type":{"typname":"integer"}}}},{"PLpgSQL_recfield":{"fieldname":"c1","recparentno":2}}],"action":{"PLpgSQL_stmt_block":{"lineno":5,"body":[{"PLpgSQL_stmt_execsql":{"lineno":6,"sqlstmt":{"PLpgSQL_expr":{"query":"select 1 as c1, 2 as c2","parseMode":0}},"into":true,"target":{"PLpgSQL_rec":{"refname":"v3","dno":2,"lineno":3}}}},{"PLpgSQL_stmt_assign":{"lineno":7,"varno":4,"expr":{"PLpgSQL_expr":{"query":"v3.c1 := 4","parseMode":4}}}},{"PLpgSQL_stmt_return":{}}]}}}}, {"PLpgSQL_function":{"datums":[{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}}],"action":{"PLpgSQL_stmt_block":{"lineno":2,"body":[{"PLpgSQL_stmt_assert":{"lineno":3,"cond":{"PLpgSQL_expr":{"query":"true","parseMode":2}}}},{"PLpgSQL_stmt_assert":{"lineno":4,"cond":{"PLpgSQL_expr":{"query":"now() \u003c '2000-01-01'","parseMode":2}}}},{"PLpgSQL_stmt_assert":{"lineno":5,"cond":{"PLpgSQL_expr":{"query":"false","parseMode":2}},"message":{"PLpgSQL_expr":{"query":"'msg'","parseMode":2}}}},{"PLpgSQL_stmt_assert":{"lineno":6,"cond":{"PLpgSQL_expr":{"query":"false","parseMode":2}},"message":{"PLpgSQL_expr":{"query":"version()","parseMode":2}}}},{"PLpgSQL_stmt_return":{"lineno":8,"expr":{"PLpgSQL_expr":{"query":"1","parseMode":2}}}}]}}}}, {"PLpgSQL_function":{"datums":[{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}},{"PLpgSQL_var":{"refname":"r","lineno":1,"datatype":{"PLpgSQL_type":{"typname":"record"}}}},{"PLpgSQL_row":{"refname":"(unnamed row)","lineno":3,"fields":[{"name":"r","varno":1}]}}],"action":{"PLpgSQL_stmt_block":{"lineno":2,"body":[{"PLpgSQL_stmt_fors":{"lineno":3,"var":{"PLpgSQL_row":{"refname":"(unnamed row)","lineno":3,"fields":[{"name":"r","varno":1}]}},"body":[{"PLpgSQL_stmt_dynexecute":{"lineno":6,"query":{"PLpgSQL_expr":{"query":"'GRANT ALL ON ' || quote_ident(r.table_schema) || '.' || quote_ident(r.table_name) || ' TO webuser'","parseMode":2}}}}],"query":{"PLpgSQL_expr":{"query":"SELECT table_schema, table_name FROM information_schema.tables\n WHERE table_type = 'VIEW' AND table_schema = 'public'","parseMode":0}}}},{"PLpgSQL_stmt_return":{}}]}}}}, -{"PLpgSQL_function":{"datums":[{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}},{"PLpgSQL_var":{"refname":"i","lineno":3,"datatype":{"PLpgSQL_type":{"typname":"INT"}}}},{"PLpgSQL_var":{"refname":"c","lineno":4,"datatype":{"PLpgSQL_type":{"typname":"pg_catalog.refcursor"}},"cursor_explicit_expr":{"PLpgSQL_expr":{"query":"SELECT generate_series(1,10)","parseMode":0}},"cursor_explicit_argrow":-1,"cursor_options":256}},{"PLpgSQL_rec":{"refname":"i","dno":3,"lineno":6}}],"action":{"PLpgSQL_stmt_block":{"lineno":5,"body":[{"PLpgSQL_stmt_forc":{"lineno":6,"var":{"PLpgSQL_rec":{"refname":"i","dno":3,"lineno":6}},"body":[{"PLpgSQL_stmt_raise":{"lineno":7,"elog_level":18,"message":"i is %","params":[{"PLpgSQL_expr":{"query":"i","parseMode":2}}]}}],"curvar":2}},{"PLpgSQL_stmt_return":{}}]}}}} +{"PLpgSQL_function":{"datums":[{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}},{"PLpgSQL_var":{"refname":"i","lineno":3,"datatype":{"PLpgSQL_type":{"typname":"INT"}}}},{"PLpgSQL_var":{"refname":"c","lineno":4,"datatype":{"PLpgSQL_type":{"typname":"pg_catalog.refcursor"}},"cursor_explicit_expr":{"PLpgSQL_expr":{"query":"SELECT generate_series(1,10)","parseMode":0}},"cursor_explicit_argrow":-1,"cursor_options":256}},{"PLpgSQL_rec":{"refname":"i","dno":3,"lineno":6}}],"action":{"PLpgSQL_stmt_block":{"lineno":5,"body":[{"PLpgSQL_stmt_forc":{"lineno":6,"var":{"PLpgSQL_rec":{"refname":"i","dno":3,"lineno":6}},"body":[{"PLpgSQL_stmt_raise":{"lineno":7,"elog_level":18,"message":"i is %","params":[{"PLpgSQL_expr":{"query":"i","parseMode":2}}]}}],"curvar":2}},{"PLpgSQL_stmt_return":{}}]}}}}, +{"PLpgSQL_function":{"datums":[{"PLpgSQL_var":{"refname":"p_in","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.int4"}}}},{"PLpgSQL_rec":{"refname":"p_out","dno":1}},{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}},{"PLpgSQL_recfield":{"fieldname":"sumattribute","recparentno":1}}],"action":{"PLpgSQL_stmt_block":{"lineno":3,"body":[{"PLpgSQL_stmt_assign":{"lineno":4,"varno":3,"expr":{"PLpgSQL_expr":{"query":"p_out.sumattribute := p_in","parseMode":4}}}},{"PLpgSQL_stmt_return":{}}]}}}} ] diff --git a/test/plpgsql_samples.sql b/test/plpgsql_samples.sql index 15c29d13..61ea4c56 100644 --- a/test/plpgsql_samples.sql +++ b/test/plpgsql_samples.sql @@ -545,3 +545,14 @@ 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; From 14eddae5ff600c2675bc122b840f2bbeb3d6b8d5 Mon Sep 17 00:00:00 2001 From: Lukas Fittl Date: Mon, 23 Sep 2024 22:58:13 -0700 Subject: [PATCH 2/7] PL/pgSQL: Fix handling of ALIAS FOR for positional function argument references This makes sure function bodies that contain ALIAS FOR statements to rename positional references like $1 get parsed correctly, instead of erroring out with "variable "$1" does not exist". --- src/pg_query_parse_plpgsql.c | 23 ++++++++++------------- test/plpgsql_samples.expected.json | 7 ++++--- test/plpgsql_samples.sql | 9 +++++++++ 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/src/pg_query_parse_plpgsql.c b/src/pg_query_parse_plpgsql.c index 34f72590..3df19aeb 100644 --- a/src/pg_query_parse_plpgsql.c +++ b/src/pg_query_parse_plpgsql.c @@ -219,20 +219,17 @@ 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); + 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 */ diff --git a/test/plpgsql_samples.expected.json b/test/plpgsql_samples.expected.json index 8b5c1c07..66a539b2 100644 --- a/test/plpgsql_samples.expected.json +++ b/test/plpgsql_samples.expected.json @@ -1,9 +1,9 @@ [ {"PLpgSQL_function":{"datums":[{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}},{"PLpgSQL_var":{"refname":"r","lineno":3,"datatype":{"PLpgSQL_type":{"typname":"foo%rowtype"}}}},{"PLpgSQL_row":{"refname":"(unnamed row)","lineno":5,"fields":[{"name":"r","varno":1}]}}],"action":{"PLpgSQL_stmt_block":{"lineno":4,"body":[{"PLpgSQL_stmt_fors":{"lineno":5,"var":{"PLpgSQL_row":{"refname":"(unnamed row)","lineno":5,"fields":[{"name":"r","varno":1}]}},"body":[{"PLpgSQL_stmt_return_next":{"lineno":9}}],"query":{"PLpgSQL_expr":{"query":"SELECT * FROM foo WHERE fooid \u003e 0","parseMode":0}}}},{"PLpgSQL_stmt_return":{"lineno":11}}]}}}}, -{"PLpgSQL_function":{"datums":[{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}}],"action":{"PLpgSQL_stmt_block":{"lineno":2,"body":[{"PLpgSQL_stmt_return_query":{"lineno":3,"query":{"PLpgSQL_expr":{"query":"SELECT flightid\n FROM flight\n WHERE flightdate \u003e= $1\n AND flightdate \u003c ($1 + 1)","parseMode":0}}}},{"PLpgSQL_stmt_if":{"lineno":10,"cond":{"PLpgSQL_expr":{"query":"NOT FOUND","parseMode":2}},"then_body":[{"PLpgSQL_stmt_raise":{"lineno":11,"elog_level":21,"message":"No flight at %.","params":[{"PLpgSQL_expr":{"query":"$1","parseMode":2}}]}}]}},{"PLpgSQL_stmt_return":{"lineno":14}}]}}}}, +{"PLpgSQL_function":{"datums":[{"PLpgSQL_var":{"refname":"$1","datatype":{"PLpgSQL_type":{"typname":"date"}}}},{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}}],"action":{"PLpgSQL_stmt_block":{"lineno":2,"body":[{"PLpgSQL_stmt_return_query":{"lineno":3,"query":{"PLpgSQL_expr":{"query":"SELECT flightid\n FROM flight\n WHERE flightdate \u003e= $1\n AND flightdate \u003c ($1 + 1)","parseMode":0}}}},{"PLpgSQL_stmt_if":{"lineno":10,"cond":{"PLpgSQL_expr":{"query":"NOT FOUND","parseMode":2}},"then_body":[{"PLpgSQL_stmt_raise":{"lineno":11,"elog_level":21,"message":"No flight at %.","params":[{"PLpgSQL_expr":{"query":"$1","parseMode":2}}]}}]}},{"PLpgSQL_stmt_return":{"lineno":14}}]}}}}, {"PLpgSQL_function":{"datums":[{"PLpgSQL_var":{"refname":"v_name","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"varchar\""}}}},{"PLpgSQL_var":{"refname":"v_version","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"varchar\""}}}},{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}}],"action":{"PLpgSQL_stmt_block":{"lineno":2,"body":[{"PLpgSQL_stmt_if":{"lineno":3,"cond":{"PLpgSQL_expr":{"query":"v_version IS NULL","parseMode":2}},"then_body":[{"PLpgSQL_stmt_return":{"lineno":4,"expr":{"PLpgSQL_expr":{"query":"v_name","parseMode":2}}}}]}},{"PLpgSQL_stmt_return":{"lineno":6,"expr":{"PLpgSQL_expr":{"query":"v_name || '/' || v_version","parseMode":2}}}}]}}}}, {"PLpgSQL_function":{"datums":[{"PLpgSQL_var":{"refname":"v_job_id","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.int4"}}}},{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}},{"PLpgSQL_var":{"refname":"a_running_job_count","lineno":3,"datatype":{"PLpgSQL_type":{"typname":"integer"}}}},{"PLpgSQL_row":{"refname":"(unnamed row)","lineno":7,"fields":[{"name":"a_running_job_count","varno":2}]}},{"PLpgSQL_var":{"refname":"sqlstate","lineno":18,"datatype":{"PLpgSQL_type":{"typname":"pg_catalog.text"}},"isconst":true}},{"PLpgSQL_var":{"refname":"sqlerrm","lineno":18,"datatype":{"PLpgSQL_type":{"typname":"pg_catalog.text"}},"isconst":true}}],"action":{"PLpgSQL_stmt_block":{"lineno":4,"body":[{"PLpgSQL_stmt_execsql":{"lineno":5,"sqlstmt":{"PLpgSQL_expr":{"query":"LOCK TABLE cs_jobs IN EXCLUSIVE MODE","parseMode":0}}}},{"PLpgSQL_stmt_execsql":{"lineno":7,"sqlstmt":{"PLpgSQL_expr":{"query":"SELECT count(*) FROM cs_jobs WHERE end_stamp IS NULL","parseMode":0}},"into":true,"target":{"PLpgSQL_row":{"refname":"(unnamed row)","lineno":7,"fields":[{"name":"a_running_job_count","varno":2}]}}}},{"PLpgSQL_stmt_if":{"lineno":9,"cond":{"PLpgSQL_expr":{"query":"a_running_job_count \u003e 0","parseMode":2}},"then_body":[{"PLpgSQL_stmt_raise":{"lineno":10,"elog_level":21,"message":"Unable to create a new job: a job is currently running"}}]}},{"PLpgSQL_stmt_execsql":{"lineno":13,"sqlstmt":{"PLpgSQL_expr":{"query":"DELETE FROM cs_active_job","parseMode":0}}}},{"PLpgSQL_stmt_execsql":{"lineno":14,"sqlstmt":{"PLpgSQL_expr":{"query":"INSERT INTO cs_active_job(job_id) VALUES (v_job_id)","parseMode":0}}}},{"PLpgSQL_stmt_block":{"lineno":16,"body":[{"PLpgSQL_stmt_execsql":{"lineno":17,"sqlstmt":{"PLpgSQL_expr":{"query":"INSERT INTO cs_jobs (job_id, start_stamp) VALUES (v_job_id, now())","parseMode":0}}}}],"exceptions":{"PLpgSQL_exception_block":{"exc_list":[{"PLpgSQL_exception":{"conditions":[{"PLpgSQL_condition":{"condname":"unique_violation"}}]}}]}}}},{"PLpgSQL_stmt_return":{}}]}}}}, -{"PLpgSQL_function":{"datums":[{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}},{"PLpgSQL_var":{"refname":"pos","lineno":3,"datatype":{"PLpgSQL_type":{"typname":"integer"}}}}],"action":{"PLpgSQL_stmt_block":{"lineno":4,"body":[{"PLpgSQL_stmt_assign":{"lineno":5,"varno":1,"expr":{"PLpgSQL_expr":{"query":"pos:= instr($1, $2, 1)","parseMode":3}}}},{"PLpgSQL_stmt_return":{"lineno":6,"expr":{"PLpgSQL_expr":{"query":"pos","parseMode":2}}}}]}}}}, +{"PLpgSQL_function":{"datums":[{"PLpgSQL_var":{"refname":"$1","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"varchar\""}}}},{"PLpgSQL_var":{"refname":"$2","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"varchar\""}}}},{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}},{"PLpgSQL_var":{"refname":"pos","lineno":3,"datatype":{"PLpgSQL_type":{"typname":"integer"}}}}],"action":{"PLpgSQL_stmt_block":{"lineno":4,"body":[{"PLpgSQL_stmt_assign":{"lineno":5,"varno":3,"expr":{"PLpgSQL_expr":{"query":"pos:= instr($1, $2, 1)","parseMode":3}}}},{"PLpgSQL_stmt_return":{"lineno":6,"expr":{"PLpgSQL_expr":{"query":"pos","parseMode":2}}}}]}}}}, {"PLpgSQL_function":{"datums":[{"PLpgSQL_var":{"refname":"string","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"varchar\""}}}},{"PLpgSQL_var":{"refname":"string_to_search","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"varchar\""}}}},{"PLpgSQL_var":{"refname":"beg_index","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.int4"}}}},{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}},{"PLpgSQL_var":{"refname":"pos","lineno":3,"datatype":{"PLpgSQL_type":{"typname":"integer "}},"notnull":true,"default_val":{"PLpgSQL_expr":{"query":"0","parseMode":2}}}},{"PLpgSQL_var":{"refname":"temp_str","lineno":4,"datatype":{"PLpgSQL_type":{"typname":"varchar"}}}},{"PLpgSQL_var":{"refname":"beg","lineno":5,"datatype":{"PLpgSQL_type":{"typname":"integer"}}}},{"PLpgSQL_var":{"refname":"length","lineno":6,"datatype":{"PLpgSQL_type":{"typname":"integer"}}}},{"PLpgSQL_var":{"refname":"ss_length","lineno":7,"datatype":{"PLpgSQL_type":{"typname":"integer"}}}}],"action":{"PLpgSQL_stmt_block":{"lineno":8,"body":[{"PLpgSQL_stmt_if":{"lineno":9,"cond":{"PLpgSQL_expr":{"query":"beg_index \u003e 0","parseMode":2}},"then_body":[{"PLpgSQL_stmt_assign":{"lineno":10,"varno":5,"expr":{"PLpgSQL_expr":{"query":"temp_str := substring(string FROM beg_index)","parseMode":3}}}},{"PLpgSQL_stmt_assign":{"lineno":11,"varno":4,"expr":{"PLpgSQL_expr":{"query":"pos := position(string_to_search IN temp_str)","parseMode":3}}}},{"PLpgSQL_stmt_if":{"lineno":13,"cond":{"PLpgSQL_expr":{"query":"pos = 0","parseMode":2}},"then_body":[{"PLpgSQL_stmt_return":{"lineno":14,"expr":{"PLpgSQL_expr":{"query":"0","parseMode":2}}}}],"else_body":[{"PLpgSQL_stmt_return":{"lineno":16,"expr":{"PLpgSQL_expr":{"query":"pos + beg_index - 1","parseMode":2}}}}]}}],"elsif_list":[{"PLpgSQL_if_elsif":{"lineno":18,"cond":{"PLpgSQL_expr":{"query":"beg_index \u003c 0","parseMode":2}},"stmts":[{"PLpgSQL_stmt_assign":{"lineno":19,"varno":8,"expr":{"PLpgSQL_expr":{"query":"ss_length := char_length(string_to_search)","parseMode":3}}}},{"PLpgSQL_stmt_assign":{"lineno":20,"varno":7,"expr":{"PLpgSQL_expr":{"query":"length := char_length(string)","parseMode":3}}}},{"PLpgSQL_stmt_assign":{"lineno":21,"varno":6,"expr":{"PLpgSQL_expr":{"query":"beg := length + beg_index - ss_length + 2","parseMode":3}}}},{"PLpgSQL_stmt_while":{"lineno":23,"cond":{"PLpgSQL_expr":{"query":"beg \u003e 0","parseMode":2}},"body":[{"PLpgSQL_stmt_assign":{"lineno":24,"varno":5,"expr":{"PLpgSQL_expr":{"query":"temp_str := substring(string FROM beg FOR ss_length)","parseMode":3}}}},{"PLpgSQL_stmt_assign":{"lineno":25,"varno":4,"expr":{"PLpgSQL_expr":{"query":"pos := position(string_to_search IN temp_str)","parseMode":3}}}},{"PLpgSQL_stmt_if":{"lineno":27,"cond":{"PLpgSQL_expr":{"query":"pos \u003e 0","parseMode":2}},"then_body":[{"PLpgSQL_stmt_return":{"lineno":28,"expr":{"PLpgSQL_expr":{"query":"beg","parseMode":2}}}}]}},{"PLpgSQL_stmt_assign":{"lineno":31,"varno":6,"expr":{"PLpgSQL_expr":{"query":"beg := beg - 1","parseMode":3}}}}]}},{"PLpgSQL_stmt_return":{"lineno":34,"expr":{"PLpgSQL_expr":{"query":"0","parseMode":2}}}}]}}],"else_body":[{"PLpgSQL_stmt_return":{"lineno":36,"expr":{"PLpgSQL_expr":{"query":"0","parseMode":2}}}}]}},{"PLpgSQL_stmt_return":{}}]}}}}, {"PLpgSQL_function":{"datums":[{"PLpgSQL_var":{"refname":"string","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"varchar\""}}}},{"PLpgSQL_var":{"refname":"string_to_search","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"varchar\""}}}},{"PLpgSQL_var":{"refname":"beg_index","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.int4"}}}},{"PLpgSQL_var":{"refname":"occur_index","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.int4"}}}},{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}},{"PLpgSQL_var":{"refname":"pos","lineno":3,"datatype":{"PLpgSQL_type":{"typname":"integer "}},"notnull":true,"default_val":{"PLpgSQL_expr":{"query":"0","parseMode":2}}}},{"PLpgSQL_var":{"refname":"occur_number","lineno":4,"datatype":{"PLpgSQL_type":{"typname":"integer "}},"notnull":true,"default_val":{"PLpgSQL_expr":{"query":"0","parseMode":2}}}},{"PLpgSQL_var":{"refname":"temp_str","lineno":5,"datatype":{"PLpgSQL_type":{"typname":"varchar"}}}},{"PLpgSQL_var":{"refname":"beg","lineno":6,"datatype":{"PLpgSQL_type":{"typname":"integer"}}}},{"PLpgSQL_var":{"refname":"i","lineno":7,"datatype":{"PLpgSQL_type":{"typname":"integer"}}}},{"PLpgSQL_var":{"refname":"length","lineno":8,"datatype":{"PLpgSQL_type":{"typname":"integer"}}}},{"PLpgSQL_var":{"refname":"ss_length","lineno":9,"datatype":{"PLpgSQL_type":{"typname":"integer"}}}},{"PLpgSQL_var":{"refname":"i","lineno":15,"datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"integer\""}}}}],"action":{"PLpgSQL_stmt_block":{"lineno":10,"body":[{"PLpgSQL_stmt_if":{"lineno":11,"cond":{"PLpgSQL_expr":{"query":"beg_index \u003e 0","parseMode":2}},"then_body":[{"PLpgSQL_stmt_assign":{"lineno":12,"varno":8,"expr":{"PLpgSQL_expr":{"query":"beg := beg_index","parseMode":3}}}},{"PLpgSQL_stmt_assign":{"lineno":13,"varno":7,"expr":{"PLpgSQL_expr":{"query":"temp_str := substring(string FROM beg_index)","parseMode":3}}}},{"PLpgSQL_stmt_fori":{"lineno":15,"var":{"PLpgSQL_var":{"refname":"i","lineno":15,"datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"integer\""}}}},"lower":{"PLpgSQL_expr":{"query":"1","parseMode":2}},"upper":{"PLpgSQL_expr":{"query":"occur_index","parseMode":2}},"body":[{"PLpgSQL_stmt_assign":{"lineno":16,"varno":5,"expr":{"PLpgSQL_expr":{"query":"pos := position(string_to_search IN temp_str)","parseMode":3}}}},{"PLpgSQL_stmt_if":{"lineno":18,"cond":{"PLpgSQL_expr":{"query":"i = 1","parseMode":2}},"then_body":[{"PLpgSQL_stmt_assign":{"lineno":19,"varno":8,"expr":{"PLpgSQL_expr":{"query":"beg := beg + pos - 1","parseMode":3}}}}],"else_body":[{"PLpgSQL_stmt_assign":{"lineno":21,"varno":8,"expr":{"PLpgSQL_expr":{"query":"beg := beg + pos","parseMode":3}}}}]}},{"PLpgSQL_stmt_assign":{"lineno":24,"varno":7,"expr":{"PLpgSQL_expr":{"query":"temp_str := substring(string FROM beg + 1)","parseMode":3}}}}]}},{"PLpgSQL_stmt_if":{"lineno":27,"cond":{"PLpgSQL_expr":{"query":"pos = 0","parseMode":2}},"then_body":[{"PLpgSQL_stmt_return":{"lineno":28,"expr":{"PLpgSQL_expr":{"query":"0","parseMode":2}}}}],"else_body":[{"PLpgSQL_stmt_return":{"lineno":30,"expr":{"PLpgSQL_expr":{"query":"beg","parseMode":2}}}}]}}],"elsif_list":[{"PLpgSQL_if_elsif":{"lineno":32,"cond":{"PLpgSQL_expr":{"query":"beg_index \u003c 0","parseMode":2}},"stmts":[{"PLpgSQL_stmt_assign":{"lineno":33,"varno":11,"expr":{"PLpgSQL_expr":{"query":"ss_length := char_length(string_to_search)","parseMode":3}}}},{"PLpgSQL_stmt_assign":{"lineno":34,"varno":10,"expr":{"PLpgSQL_expr":{"query":"length := char_length(string)","parseMode":3}}}},{"PLpgSQL_stmt_assign":{"lineno":35,"varno":8,"expr":{"PLpgSQL_expr":{"query":"beg := length + beg_index - ss_length + 2","parseMode":3}}}},{"PLpgSQL_stmt_while":{"lineno":37,"cond":{"PLpgSQL_expr":{"query":"beg \u003e 0","parseMode":2}},"body":[{"PLpgSQL_stmt_assign":{"lineno":38,"varno":7,"expr":{"PLpgSQL_expr":{"query":"temp_str := substring(string FROM beg FOR ss_length)","parseMode":3}}}},{"PLpgSQL_stmt_assign":{"lineno":39,"varno":5,"expr":{"PLpgSQL_expr":{"query":"pos := position(string_to_search IN temp_str)","parseMode":3}}}},{"PLpgSQL_stmt_if":{"lineno":41,"cond":{"PLpgSQL_expr":{"query":"pos \u003e 0","parseMode":2}},"then_body":[{"PLpgSQL_stmt_assign":{"lineno":42,"varno":6,"expr":{"PLpgSQL_expr":{"query":"occur_number := occur_number + 1","parseMode":3}}}},{"PLpgSQL_stmt_if":{"lineno":44,"cond":{"PLpgSQL_expr":{"query":"occur_number = occur_index","parseMode":2}},"then_body":[{"PLpgSQL_stmt_return":{"lineno":45,"expr":{"PLpgSQL_expr":{"query":"beg","parseMode":2}}}}]}}]}},{"PLpgSQL_stmt_assign":{"lineno":49,"varno":8,"expr":{"PLpgSQL_expr":{"query":"beg := beg - 1","parseMode":3}}}}]}},{"PLpgSQL_stmt_return":{"lineno":52,"expr":{"PLpgSQL_expr":{"query":"0","parseMode":2}}}}]}}],"else_body":[{"PLpgSQL_stmt_return":{"lineno":54,"expr":{"PLpgSQL_expr":{"query":"0","parseMode":2}}}}]}},{"PLpgSQL_stmt_return":{}}]}}}}, {"PLpgSQL_function":{"datums":[{"PLpgSQL_var":{"refname":"enddate","datatype":{"PLpgSQL_type":{"typname":"date"}}}},{"PLpgSQL_var":{"refname":"canceled","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.bool"}}}},{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}}],"action":{"PLpgSQL_stmt_block":{"lineno":1,"body":[{"PLpgSQL_stmt_if":{"lineno":2,"cond":{"PLpgSQL_expr":{"query":"canceled = true","parseMode":2}},"then_body":[{"PLpgSQL_stmt_return":{"lineno":3,"expr":{"PLpgSQL_expr":{"query":"null","parseMode":2}}}}],"else_body":[{"PLpgSQL_stmt_return":{"lineno":5,"expr":{"PLpgSQL_expr":{"query":"endDate","parseMode":2}}}}]}},{"PLpgSQL_stmt_return":{}}]}}}}, @@ -21,5 +21,6 @@ {"PLpgSQL_function":{"datums":[{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}}],"action":{"PLpgSQL_stmt_block":{"lineno":2,"body":[{"PLpgSQL_stmt_assert":{"lineno":3,"cond":{"PLpgSQL_expr":{"query":"true","parseMode":2}}}},{"PLpgSQL_stmt_assert":{"lineno":4,"cond":{"PLpgSQL_expr":{"query":"now() \u003c '2000-01-01'","parseMode":2}}}},{"PLpgSQL_stmt_assert":{"lineno":5,"cond":{"PLpgSQL_expr":{"query":"false","parseMode":2}},"message":{"PLpgSQL_expr":{"query":"'msg'","parseMode":2}}}},{"PLpgSQL_stmt_assert":{"lineno":6,"cond":{"PLpgSQL_expr":{"query":"false","parseMode":2}},"message":{"PLpgSQL_expr":{"query":"version()","parseMode":2}}}},{"PLpgSQL_stmt_return":{"lineno":8,"expr":{"PLpgSQL_expr":{"query":"1","parseMode":2}}}}]}}}}, {"PLpgSQL_function":{"datums":[{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}},{"PLpgSQL_var":{"refname":"r","lineno":1,"datatype":{"PLpgSQL_type":{"typname":"record"}}}},{"PLpgSQL_row":{"refname":"(unnamed row)","lineno":3,"fields":[{"name":"r","varno":1}]}}],"action":{"PLpgSQL_stmt_block":{"lineno":2,"body":[{"PLpgSQL_stmt_fors":{"lineno":3,"var":{"PLpgSQL_row":{"refname":"(unnamed row)","lineno":3,"fields":[{"name":"r","varno":1}]}},"body":[{"PLpgSQL_stmt_dynexecute":{"lineno":6,"query":{"PLpgSQL_expr":{"query":"'GRANT ALL ON ' || quote_ident(r.table_schema) || '.' || quote_ident(r.table_name) || ' TO webuser'","parseMode":2}}}}],"query":{"PLpgSQL_expr":{"query":"SELECT table_schema, table_name FROM information_schema.tables\n WHERE table_type = 'VIEW' AND table_schema = 'public'","parseMode":0}}}},{"PLpgSQL_stmt_return":{}}]}}}}, {"PLpgSQL_function":{"datums":[{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}},{"PLpgSQL_var":{"refname":"i","lineno":3,"datatype":{"PLpgSQL_type":{"typname":"INT"}}}},{"PLpgSQL_var":{"refname":"c","lineno":4,"datatype":{"PLpgSQL_type":{"typname":"pg_catalog.refcursor"}},"cursor_explicit_expr":{"PLpgSQL_expr":{"query":"SELECT generate_series(1,10)","parseMode":0}},"cursor_explicit_argrow":-1,"cursor_options":256}},{"PLpgSQL_rec":{"refname":"i","dno":3,"lineno":6}}],"action":{"PLpgSQL_stmt_block":{"lineno":5,"body":[{"PLpgSQL_stmt_forc":{"lineno":6,"var":{"PLpgSQL_rec":{"refname":"i","dno":3,"lineno":6}},"body":[{"PLpgSQL_stmt_raise":{"lineno":7,"elog_level":18,"message":"i is %","params":[{"PLpgSQL_expr":{"query":"i","parseMode":2}}]}}],"curvar":2}},{"PLpgSQL_stmt_return":{}}]}}}}, -{"PLpgSQL_function":{"datums":[{"PLpgSQL_var":{"refname":"p_in","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.int4"}}}},{"PLpgSQL_rec":{"refname":"p_out","dno":1}},{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}},{"PLpgSQL_recfield":{"fieldname":"sumattribute","recparentno":1}}],"action":{"PLpgSQL_stmt_block":{"lineno":3,"body":[{"PLpgSQL_stmt_assign":{"lineno":4,"varno":3,"expr":{"PLpgSQL_expr":{"query":"p_out.sumattribute := p_in","parseMode":4}}}},{"PLpgSQL_stmt_return":{}}]}}}} +{"PLpgSQL_function":{"datums":[{"PLpgSQL_var":{"refname":"p_in","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.int4"}}}},{"PLpgSQL_rec":{"refname":"p_out","dno":1}},{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}},{"PLpgSQL_recfield":{"fieldname":"sumattribute","recparentno":1}}],"action":{"PLpgSQL_stmt_block":{"lineno":3,"body":[{"PLpgSQL_stmt_assign":{"lineno":4,"varno":3,"expr":{"PLpgSQL_expr":{"query":"p_out.sumattribute := p_in","parseMode":4}}}},{"PLpgSQL_stmt_return":{}}]}}}}, +{"PLpgSQL_function":{"datums":[{"PLpgSQL_var":{"refname":"$1","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.float4"}}}},{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}}],"action":{"PLpgSQL_stmt_block":{"lineno":4,"body":[{"PLpgSQL_stmt_return":{"lineno":5,"expr":{"PLpgSQL_expr":{"query":"subtotal * 0.06","parseMode":2}}}}]}}}} ] diff --git a/test/plpgsql_samples.sql b/test/plpgsql_samples.sql index 61ea4c56..c16f5425 100644 --- a/test/plpgsql_samples.sql +++ b/test/plpgsql_samples.sql @@ -556,3 +556,12 @@ BEGIN 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; From d32d095c6c35425d2c34ac14caa85e7f76c3cec6 Mon Sep 17 00:00:00 2001 From: Lukas Fittl Date: Mon, 23 Sep 2024 23:02:22 -0700 Subject: [PATCH 3/7] PL/pgSQL: Handle implicit RETURN statements by correctly setting out_param_varno This ensures functions with statements like "RETURN NEXT;" or "RETURN;" don't error out. --- src/pg_query_parse_plpgsql.c | 2 ++ test/plpgsql_samples.expected.json | 4 +++- test/plpgsql_samples.sql | 25 +++++++++++++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/pg_query_parse_plpgsql.c b/src/pg_query_parse_plpgsql.c index 3df19aeb..865d69e8 100644 --- a/src/pg_query_parse_plpgsql.c +++ b/src/pg_query_parse_plpgsql.c @@ -226,6 +226,8 @@ static PLpgSQL_function *compile_create_function_stmt(CreateFunctionStmt* stmt) 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) diff --git a/test/plpgsql_samples.expected.json b/test/plpgsql_samples.expected.json index 66a539b2..b0634570 100644 --- a/test/plpgsql_samples.expected.json +++ b/test/plpgsql_samples.expected.json @@ -22,5 +22,7 @@ {"PLpgSQL_function":{"datums":[{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}},{"PLpgSQL_var":{"refname":"r","lineno":1,"datatype":{"PLpgSQL_type":{"typname":"record"}}}},{"PLpgSQL_row":{"refname":"(unnamed row)","lineno":3,"fields":[{"name":"r","varno":1}]}}],"action":{"PLpgSQL_stmt_block":{"lineno":2,"body":[{"PLpgSQL_stmt_fors":{"lineno":3,"var":{"PLpgSQL_row":{"refname":"(unnamed row)","lineno":3,"fields":[{"name":"r","varno":1}]}},"body":[{"PLpgSQL_stmt_dynexecute":{"lineno":6,"query":{"PLpgSQL_expr":{"query":"'GRANT ALL ON ' || quote_ident(r.table_schema) || '.' || quote_ident(r.table_name) || ' TO webuser'","parseMode":2}}}}],"query":{"PLpgSQL_expr":{"query":"SELECT table_schema, table_name FROM information_schema.tables\n WHERE table_type = 'VIEW' AND table_schema = 'public'","parseMode":0}}}},{"PLpgSQL_stmt_return":{}}]}}}}, {"PLpgSQL_function":{"datums":[{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}},{"PLpgSQL_var":{"refname":"i","lineno":3,"datatype":{"PLpgSQL_type":{"typname":"INT"}}}},{"PLpgSQL_var":{"refname":"c","lineno":4,"datatype":{"PLpgSQL_type":{"typname":"pg_catalog.refcursor"}},"cursor_explicit_expr":{"PLpgSQL_expr":{"query":"SELECT generate_series(1,10)","parseMode":0}},"cursor_explicit_argrow":-1,"cursor_options":256}},{"PLpgSQL_rec":{"refname":"i","dno":3,"lineno":6}}],"action":{"PLpgSQL_stmt_block":{"lineno":5,"body":[{"PLpgSQL_stmt_forc":{"lineno":6,"var":{"PLpgSQL_rec":{"refname":"i","dno":3,"lineno":6}},"body":[{"PLpgSQL_stmt_raise":{"lineno":7,"elog_level":18,"message":"i is %","params":[{"PLpgSQL_expr":{"query":"i","parseMode":2}}]}}],"curvar":2}},{"PLpgSQL_stmt_return":{}}]}}}}, {"PLpgSQL_function":{"datums":[{"PLpgSQL_var":{"refname":"p_in","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.int4"}}}},{"PLpgSQL_rec":{"refname":"p_out","dno":1}},{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}},{"PLpgSQL_recfield":{"fieldname":"sumattribute","recparentno":1}}],"action":{"PLpgSQL_stmt_block":{"lineno":3,"body":[{"PLpgSQL_stmt_assign":{"lineno":4,"varno":3,"expr":{"PLpgSQL_expr":{"query":"p_out.sumattribute := p_in","parseMode":4}}}},{"PLpgSQL_stmt_return":{}}]}}}}, -{"PLpgSQL_function":{"datums":[{"PLpgSQL_var":{"refname":"$1","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.float4"}}}},{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}}],"action":{"PLpgSQL_stmt_block":{"lineno":4,"body":[{"PLpgSQL_stmt_return":{"lineno":5,"expr":{"PLpgSQL_expr":{"query":"subtotal * 0.06","parseMode":2}}}}]}}}} +{"PLpgSQL_function":{"datums":[{"PLpgSQL_var":{"refname":"$1","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.float4"}}}},{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}}],"action":{"PLpgSQL_stmt_block":{"lineno":4,"body":[{"PLpgSQL_stmt_return":{"lineno":5,"expr":{"PLpgSQL_expr":{"query":"subtotal * 0.06","parseMode":2}}}}]}}}}, +{"PLpgSQL_function":{"datums":[{"PLpgSQL_var":{"refname":"s","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.int4"}}}},{"PLpgSQL_var":{"refname":"e","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.int4"}}}},{"PLpgSQL_var":{"refname":"id","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.int4"}}}},{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}}],"action":{"PLpgSQL_stmt_block":{"lineno":2,"body":[{"PLpgSQL_stmt_assign":{"lineno":3,"varno":2,"expr":{"PLpgSQL_expr":{"query":"id := s","parseMode":3}}}},{"PLpgSQL_stmt_loop":{"lineno":4,"body":[{"PLpgSQL_stmt_exit":{"lineno":5,"is_exit":true,"cond":{"PLpgSQL_expr":{"query":"id\u003ee","parseMode":2}}}},{"PLpgSQL_stmt_return_next":{"lineno":6}},{"PLpgSQL_stmt_assign":{"lineno":7,"varno":2,"expr":{"PLpgSQL_expr":{"query":"id := id + 1","parseMode":3}}}}]}},{"PLpgSQL_stmt_return":{}}]}}}}, +{"PLpgSQL_function":{"new_varno":1,"old_varno":2,"datums":[{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}},{"PLpgSQL_rec":{"refname":"new","dno":1}},{"PLpgSQL_rec":{"refname":"old","dno":2}}],"action":{"PLpgSQL_stmt_block":{"lineno":5,"body":[{"PLpgSQL_stmt_return":{"lineno":6}}]}}}} ] diff --git a/test/plpgsql_samples.sql b/test/plpgsql_samples.sql index c16f5425..acd35add 100644 --- a/test/plpgsql_samples.sql +++ b/test/plpgsql_samples.sql @@ -565,3 +565,28 @@ 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; From f5ac25b66846eb4831fec5bb9e714c69cc546ffc Mon Sep 17 00:00:00 2001 From: Lukas Fittl Date: Mon, 23 Sep 2024 23:22:36 -0700 Subject: [PATCH 4/7] PL/pgSQL: Fix handling lowercase "record" keyword by using case-insensitive compare --- scripts/extract_source.rb | 10 +++++++++- src/postgres/src_pl_plpgsql_src_pl_gram.c | 10 +++++++++- test/plpgsql_samples.expected.json | 4 ++-- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/scripts/extract_source.rb b/scripts/extract_source.rb index 7c5522a2..fd4d7889 100644 --- a/scripts/extract_source.rb +++ b/scripts/extract_source.rb @@ -572,7 +572,15 @@ 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('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 = pg_strcasecmp(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('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; }') diff --git a/src/postgres/src_pl_plpgsql_src_pl_gram.c b/src/postgres/src_pl_plpgsql_src_pl_gram.c index 0920316e..f72e9cb1 100644 --- a/src/postgres/src_pl_plpgsql_src_pl_gram.c +++ b/src/postgres/src_pl_plpgsql_src_pl_gram.c @@ -6090,7 +6090,15 @@ 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; } + +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 = pg_strcasecmp(string, "RECORD") == 0 ? PLPGSQL_TTYPE_REC : PLPGSQL_TTYPE_SCALAR; + return typ; +} + /* diff --git a/test/plpgsql_samples.expected.json b/test/plpgsql_samples.expected.json index b0634570..da096c21 100644 --- a/test/plpgsql_samples.expected.json +++ b/test/plpgsql_samples.expected.json @@ -10,7 +10,7 @@ {"PLpgSQL_function":{"datums":[{"PLpgSQL_var":{"refname":"date_inscription","datatype":{"PLpgSQL_type":{"typname":"date"}}}},{"PLpgSQL_var":{"refname":"date_observation","datatype":{"PLpgSQL_type":{"typname":"date"}}}},{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}}],"action":{"PLpgSQL_stmt_block":{"lineno":1,"body":[{"PLpgSQL_stmt_return":{"lineno":2,"expr":{"PLpgSQL_expr":{"query":"(calcule_duree(date_inscription,date_observation) + 1)","parseMode":2}}}}]}}}}, {"PLpgSQL_function":{"datums":[{"PLpgSQL_var":{"refname":"origine","datatype":{"PLpgSQL_type":{"typname":"date"}}}},{"PLpgSQL_var":{"refname":"atdate","datatype":{"PLpgSQL_type":{"typname":"date"}}}},{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}},{"PLpgSQL_var":{"refname":"theday","lineno":2,"datatype":{"PLpgSQL_type":{"typname":"INTEGER"}}}},{"PLpgSQL_var":{"refname":"themonth","lineno":3,"datatype":{"PLpgSQL_type":{"typname":"INTEGER"}}}},{"PLpgSQL_var":{"refname":"theyear","lineno":4,"datatype":{"PLpgSQL_type":{"typname":"INTEGER"}}}},{"PLpgSQL_var":{"refname":"theday_now","lineno":5,"datatype":{"PLpgSQL_type":{"typname":"INTEGER"}}}},{"PLpgSQL_var":{"refname":"themonth_now","lineno":6,"datatype":{"PLpgSQL_type":{"typname":"INTEGER"}}}},{"PLpgSQL_var":{"refname":"theyear_now","lineno":7,"datatype":{"PLpgSQL_type":{"typname":"INTEGER"}}}}],"action":{"PLpgSQL_stmt_block":{"lineno":8,"body":[{"PLpgSQL_stmt_assign":{"lineno":9,"varno":3,"expr":{"PLpgSQL_expr":{"query":"theDay := EXTRACT(DAY FROM origine)","parseMode":3}}}},{"PLpgSQL_stmt_assign":{"lineno":10,"varno":4,"expr":{"PLpgSQL_expr":{"query":"theMonth := EXTRACT(MONTH FROM origine)","parseMode":3}}}},{"PLpgSQL_stmt_assign":{"lineno":11,"varno":5,"expr":{"PLpgSQL_expr":{"query":"theYear := EXTRACT(YEAR FROM origine)","parseMode":3}}}},{"PLpgSQL_stmt_assign":{"lineno":13,"varno":6,"expr":{"PLpgSQL_expr":{"query":"theDay_now := EXTRACT(DAY FROM atDate)","parseMode":3}}}},{"PLpgSQL_stmt_assign":{"lineno":14,"varno":7,"expr":{"PLpgSQL_expr":{"query":"theMonth_now := EXTRACT(MONTH FROM atDate)","parseMode":3}}}},{"PLpgSQL_stmt_assign":{"lineno":15,"varno":8,"expr":{"PLpgSQL_expr":{"query":"theYear_now := EXTRACT(YEAR FROM atDate)","parseMode":3}}}},{"PLpgSQL_stmt_assign":{"lineno":17,"varno":5,"expr":{"PLpgSQL_expr":{"query":"theYear := theYear_now - theYear","parseMode":3}}}},{"PLpgSQL_stmt_if":{"lineno":18,"cond":{"PLpgSQL_expr":{"query":"theMonth_now \u003c= theMonth","parseMode":2}},"then_body":[{"PLpgSQL_stmt_if":{"lineno":19,"cond":{"PLpgSQL_expr":{"query":"theMonth = theMonth_now","parseMode":2}},"then_body":[{"PLpgSQL_stmt_if":{"lineno":20,"cond":{"PLpgSQL_expr":{"query":"theDay \u003e theDay_now","parseMode":2}},"then_body":[{"PLpgSQL_stmt_assign":{"lineno":21,"varno":5,"expr":{"PLpgSQL_expr":{"query":"theYear := theYear - 1","parseMode":3}}}}]}}],"else_body":[{"PLpgSQL_stmt_assign":{"lineno":24,"varno":5,"expr":{"PLpgSQL_expr":{"query":"theYear := theYear - 1","parseMode":3}}}}]}}]}},{"PLpgSQL_stmt_return":{"lineno":28,"expr":{"PLpgSQL_expr":{"query":"theYear","parseMode":2}}}}]}}}}, {"PLpgSQL_function":{"datums":[{"PLpgSQL_var":{"refname":"uidmember","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"varchar\""}}}},{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}},{"PLpgSQL_var":{"refname":"memberid","lineno":2,"datatype":{"PLpgSQL_type":{"typname":"int4"}}}},{"PLpgSQL_row":{"refname":"(unnamed row)","lineno":5,"fields":[{"name":"memberid","varno":2}]}}],"action":{"PLpgSQL_stmt_block":{"lineno":4,"body":[{"PLpgSQL_stmt_execsql":{"lineno":5,"sqlstmt":{"PLpgSQL_expr":{"query":"SELECT key FROM\n\t\tmember\n\tWHERE\n\t\tuidmember = uid","parseMode":0}},"into":true,"target":{"PLpgSQL_row":{"refname":"(unnamed row)","lineno":5,"fields":[{"name":"memberid","varno":2}]}}}},{"PLpgSQL_stmt_return":{"lineno":11,"expr":{"PLpgSQL_expr":{"query":"memberID","parseMode":2}}}}]}}}}, -{"PLpgSQL_function":{"datums":[{"PLpgSQL_var":{"refname":"memberid","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.int4"}}}},{"PLpgSQL_var":{"refname":"jobid","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.int4"}}}},{"PLpgSQL_var":{"refname":"jobend","datatype":{"PLpgSQL_type":{"typname":"date"}}}},{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}},{"PLpgSQL_rec":{"refname":"lastjob","dno":4,"lineno":2}},{"PLpgSQL_var":{"refname":"lastemployer","lineno":3,"datatype":{"PLpgSQL_type":{"typname":"record"}}}},{"PLpgSQL_var":{"refname":"updatejob","lineno":4,"datatype":{"PLpgSQL_type":{"typname":"BOOL"}}}},{"PLpgSQL_recfield":{"fieldname":"jobid","recparentno":4}},{"PLpgSQL_row":{"refname":"(unnamed row)","lineno":13,"fields":[{"name":"lastemployer","varno":5}]}}],"action":{"PLpgSQL_stmt_block":{"lineno":5,"body":[{"PLpgSQL_stmt_assign":{"lineno":6,"varno":6,"expr":{"PLpgSQL_expr":{"query":"updateJob := false","parseMode":3}}}},{"PLpgSQL_stmt_execsql":{"lineno":8,"sqlstmt":{"PLpgSQL_expr":{"query":"SELECT * FROM lire_lastJob(memberID) AS (jobID INT,startsupport DATE,jobEnd DATE)","parseMode":0}},"into":true,"target":{"PLpgSQL_rec":{"refname":"lastjob","dno":4,"lineno":2}}}},{"PLpgSQL_stmt_if":{"lineno":9,"cond":{"PLpgSQL_expr":{"query":"lastJob.jobID = jobID","parseMode":2}},"then_body":[{"PLpgSQL_stmt_execsql":{"lineno":10,"sqlstmt":{"PLpgSQL_expr":{"query":"SELECT\n\t\t\tr_perlab.key AS positionHeld,\n\t\t\tr_perlab.endDate AS positionEnd\n\t\t FROM\n\t\t\tr_perlab,\n\t\t\t(SELECT\n\t\t\t\tr_perlab.key_member AS col_memberID,\n\t\t\t\tmax(r_perlab.start) AS startrattachement\n\t\t\tFROM r_perlab\n\t\t\tGROUP BY col_memberID) positions\n\t\tWHERE ((positions.col_memberID = memberID) AND (r_perlab.key_member = positions.col_memberID) AND (r_perlab.start = startrattachement))","parseMode":0}},"into":true,"target":{"PLpgSQL_row":{"refname":"(unnamed row)","lineno":13,"fields":[{"name":"lastemployer","varno":5}]}}}},{"PLpgSQL_stmt_if":{"lineno":23,"cond":{"PLpgSQL_expr":{"query":"lastEmployer.positionHeld IS NOT NULL","parseMode":2}},"then_body":[{"PLpgSQL_stmt_assign":{"lineno":24,"varno":6,"expr":{"PLpgSQL_expr":{"query":"updateJob := true","parseMode":3}}}},{"PLpgSQL_stmt_execsql":{"lineno":25,"sqlstmt":{"PLpgSQL_expr":{"query":"UPDATE r_perlab SET endDate = jobEnd WHERE key = lastEmployer.positionHeld","parseMode":0}}}}]}}]}},{"PLpgSQL_stmt_return":{"lineno":29,"expr":{"PLpgSQL_expr":{"query":"updateJob","parseMode":2}}}}]}}}}, +{"PLpgSQL_function":{"datums":[{"PLpgSQL_var":{"refname":"memberid","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.int4"}}}},{"PLpgSQL_var":{"refname":"jobid","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.int4"}}}},{"PLpgSQL_var":{"refname":"jobend","datatype":{"PLpgSQL_type":{"typname":"date"}}}},{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}},{"PLpgSQL_rec":{"refname":"lastjob","dno":4,"lineno":2}},{"PLpgSQL_rec":{"refname":"lastemployer","dno":5,"lineno":3}},{"PLpgSQL_var":{"refname":"updatejob","lineno":4,"datatype":{"PLpgSQL_type":{"typname":"BOOL"}}}},{"PLpgSQL_recfield":{"fieldname":"jobid","recparentno":4}},{"PLpgSQL_recfield":{"fieldname":"positionheld","recparentno":5}}],"action":{"PLpgSQL_stmt_block":{"lineno":5,"body":[{"PLpgSQL_stmt_assign":{"lineno":6,"varno":6,"expr":{"PLpgSQL_expr":{"query":"updateJob := false","parseMode":3}}}},{"PLpgSQL_stmt_execsql":{"lineno":8,"sqlstmt":{"PLpgSQL_expr":{"query":"SELECT * FROM lire_lastJob(memberID) AS (jobID INT,startsupport DATE,jobEnd DATE)","parseMode":0}},"into":true,"target":{"PLpgSQL_rec":{"refname":"lastjob","dno":4,"lineno":2}}}},{"PLpgSQL_stmt_if":{"lineno":9,"cond":{"PLpgSQL_expr":{"query":"lastJob.jobID = jobID","parseMode":2}},"then_body":[{"PLpgSQL_stmt_execsql":{"lineno":10,"sqlstmt":{"PLpgSQL_expr":{"query":"SELECT\n\t\t\tr_perlab.key AS positionHeld,\n\t\t\tr_perlab.endDate AS positionEnd\n\t\t FROM\n\t\t\tr_perlab,\n\t\t\t(SELECT\n\t\t\t\tr_perlab.key_member AS col_memberID,\n\t\t\t\tmax(r_perlab.start) AS startrattachement\n\t\t\tFROM r_perlab\n\t\t\tGROUP BY col_memberID) positions\n\t\tWHERE ((positions.col_memberID = memberID) AND (r_perlab.key_member = positions.col_memberID) AND (r_perlab.start = startrattachement))","parseMode":0}},"into":true,"target":{"PLpgSQL_rec":{"refname":"lastemployer","dno":5,"lineno":3}}}},{"PLpgSQL_stmt_if":{"lineno":23,"cond":{"PLpgSQL_expr":{"query":"lastEmployer.positionHeld IS NOT NULL","parseMode":2}},"then_body":[{"PLpgSQL_stmt_assign":{"lineno":24,"varno":6,"expr":{"PLpgSQL_expr":{"query":"updateJob := true","parseMode":3}}}},{"PLpgSQL_stmt_execsql":{"lineno":25,"sqlstmt":{"PLpgSQL_expr":{"query":"UPDATE r_perlab SET endDate = jobEnd WHERE key = lastEmployer.positionHeld","parseMode":0}}}}]}}]}},{"PLpgSQL_stmt_return":{"lineno":29,"expr":{"PLpgSQL_expr":{"query":"updateJob","parseMode":2}}}}]}}}}, {"PLpgSQL_function":{"datums":[{"PLpgSQL_var":{"refname":"str","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"varchar\""}}}},{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}},{"PLpgSQL_var":{"refname":"spechar","lineno":4,"datatype":{"PLpgSQL_type":{"typname":"VARCHAR[ ] "}},"default_val":{"PLpgSQL_expr":{"query":"ARRAY['à','â','é','è','ê','ë','ï','î','ô','û','ù','À','Â','É','È','Ê','Ë','Ï','Î','ô','û','ù','ç' ]","parseMode":2}}}},{"PLpgSQL_var":{"refname":"lettres","lineno":5,"datatype":{"PLpgSQL_type":{"typname":"VARCHAR[ ] "}},"default_val":{"PLpgSQL_expr":{"query":"ARRAY['a','a','e','e','e','e','i','i','o','u','u','a','a','e','e','e','e','i','i','o','u','u','c' ]","parseMode":2}}}},{"PLpgSQL_var":{"refname":"resultat","lineno":6,"datatype":{"PLpgSQL_type":{"typname":"VARCHAR"}}}},{"PLpgSQL_var":{"refname":"nbrspechar","lineno":7,"datatype":{"PLpgSQL_type":{"typname":"INTEGER "}},"default_val":{"PLpgSQL_expr":{"query":"23","parseMode":2}}}},{"PLpgSQL_var":{"refname":"i","lineno":12,"datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"integer\""}}}}],"action":{"PLpgSQL_stmt_block":{"lineno":9,"body":[{"PLpgSQL_stmt_if":{"lineno":10,"cond":{"PLpgSQL_expr":{"query":"(str IS NOT NULL)","parseMode":2}},"then_body":[{"PLpgSQL_stmt_assign":{"lineno":11,"varno":4,"expr":{"PLpgSQL_expr":{"query":"resultat := str","parseMode":3}}}},{"PLpgSQL_stmt_fori":{"lineno":12,"var":{"PLpgSQL_var":{"refname":"i","lineno":12,"datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"integer\""}}}},"lower":{"PLpgSQL_expr":{"query":"1","parseMode":2}},"upper":{"PLpgSQL_expr":{"query":"nbrspechar","parseMode":2}},"body":[{"PLpgSQL_stmt_assign":{"lineno":13,"varno":4,"expr":{"PLpgSQL_expr":{"query":"resultat := regexp_replace(resultat,spechar[i],lettres[i],'g')","parseMode":3}}}}]}}]}},{"PLpgSQL_stmt_return":{"lineno":16,"expr":{"PLpgSQL_expr":{"query":"resultat","parseMode":2}}}}]}}}}, {"PLpgSQL_function":{"new_varno":1,"old_varno":2,"datums":[{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}},{"PLpgSQL_rec":{"refname":"new","dno":1}},{"PLpgSQL_rec":{"refname":"old","dno":2}},{"PLpgSQL_recfield":{"fieldname":"name","recparentno":1}}],"action":{"PLpgSQL_stmt_block":{"lineno":3,"body":[{"PLpgSQL_stmt_assign":{"lineno":4,"varno":3,"expr":{"PLpgSQL_expr":{"query":"NEW.name = upper(cleanString(NEW.name))","parseMode":4}}}},{"PLpgSQL_stmt_return":{"lineno":5,"expr":{"PLpgSQL_expr":{"query":"NEW","parseMode":2}}}}]}}}}, {"PLpgSQL_function":{"new_varno":1,"old_varno":2,"datums":[{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}},{"PLpgSQL_rec":{"refname":"new","dno":1}},{"PLpgSQL_rec":{"refname":"old","dno":2}},{"PLpgSQL_recfield":{"fieldname":"key","recparentno":1}}],"action":{"PLpgSQL_stmt_block":{"lineno":2,"body":[{"PLpgSQL_stmt_execsql":{"lineno":3,"sqlstmt":{"PLpgSQL_expr":{"query":"INSERT INTO list(key,date) VALUES(NEW.key,NEW.end)","parseMode":0}}}},{"PLpgSQL_stmt_return":{"lineno":4,"expr":{"PLpgSQL_expr":{"query":"NEW","parseMode":2}}}}]}}}}, @@ -19,7 +19,7 @@ {"PLpgSQL_function":{"datums":[{"PLpgSQL_var":{"refname":"v_name","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"varchar\""}}}},{"PLpgSQL_var":{"refname":"v_version","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"varchar\""}}}},{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}},{"PLpgSQL_var":{"refname":"_a","lineno":3,"datatype":{"PLpgSQL_type":{"typname":"int"}}}}],"action":{"PLpgSQL_stmt_block":{"lineno":5,"body":[{"PLpgSQL_stmt_if":{"lineno":6,"cond":{"PLpgSQL_expr":{"query":"v_version IS NULL","parseMode":2}},"then_body":[{"PLpgSQL_stmt_return":{"lineno":7,"expr":{"PLpgSQL_expr":{"query":"v_name","parseMode":2}}}}]}},{"PLpgSQL_stmt_return":{"lineno":10,"expr":{"PLpgSQL_expr":{"query":"v_name || '/' || v_version","parseMode":2}}}}]}}}}, {"PLpgSQL_function":{"datums":[{"PLpgSQL_var":{"refname":"str","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"varchar\""}}}},{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}},{"PLpgSQL_rec":{"refname":"v3","dno":2,"lineno":3}},{"PLpgSQL_var":{"refname":"v4","lineno":4,"datatype":{"PLpgSQL_type":{"typname":"integer"}}}},{"PLpgSQL_recfield":{"fieldname":"c1","recparentno":2}}],"action":{"PLpgSQL_stmt_block":{"lineno":5,"body":[{"PLpgSQL_stmt_execsql":{"lineno":6,"sqlstmt":{"PLpgSQL_expr":{"query":"select 1 as c1, 2 as c2","parseMode":0}},"into":true,"target":{"PLpgSQL_rec":{"refname":"v3","dno":2,"lineno":3}}}},{"PLpgSQL_stmt_assign":{"lineno":7,"varno":4,"expr":{"PLpgSQL_expr":{"query":"v3.c1 := 4","parseMode":4}}}},{"PLpgSQL_stmt_return":{}}]}}}}, {"PLpgSQL_function":{"datums":[{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}}],"action":{"PLpgSQL_stmt_block":{"lineno":2,"body":[{"PLpgSQL_stmt_assert":{"lineno":3,"cond":{"PLpgSQL_expr":{"query":"true","parseMode":2}}}},{"PLpgSQL_stmt_assert":{"lineno":4,"cond":{"PLpgSQL_expr":{"query":"now() \u003c '2000-01-01'","parseMode":2}}}},{"PLpgSQL_stmt_assert":{"lineno":5,"cond":{"PLpgSQL_expr":{"query":"false","parseMode":2}},"message":{"PLpgSQL_expr":{"query":"'msg'","parseMode":2}}}},{"PLpgSQL_stmt_assert":{"lineno":6,"cond":{"PLpgSQL_expr":{"query":"false","parseMode":2}},"message":{"PLpgSQL_expr":{"query":"version()","parseMode":2}}}},{"PLpgSQL_stmt_return":{"lineno":8,"expr":{"PLpgSQL_expr":{"query":"1","parseMode":2}}}}]}}}}, -{"PLpgSQL_function":{"datums":[{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}},{"PLpgSQL_var":{"refname":"r","lineno":1,"datatype":{"PLpgSQL_type":{"typname":"record"}}}},{"PLpgSQL_row":{"refname":"(unnamed row)","lineno":3,"fields":[{"name":"r","varno":1}]}}],"action":{"PLpgSQL_stmt_block":{"lineno":2,"body":[{"PLpgSQL_stmt_fors":{"lineno":3,"var":{"PLpgSQL_row":{"refname":"(unnamed row)","lineno":3,"fields":[{"name":"r","varno":1}]}},"body":[{"PLpgSQL_stmt_dynexecute":{"lineno":6,"query":{"PLpgSQL_expr":{"query":"'GRANT ALL ON ' || quote_ident(r.table_schema) || '.' || quote_ident(r.table_name) || ' TO webuser'","parseMode":2}}}}],"query":{"PLpgSQL_expr":{"query":"SELECT table_schema, table_name FROM information_schema.tables\n WHERE table_type = 'VIEW' AND table_schema = 'public'","parseMode":0}}}},{"PLpgSQL_stmt_return":{}}]}}}}, +{"PLpgSQL_function":{"datums":[{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}},{"PLpgSQL_rec":{"refname":"r","dno":1,"lineno":1}},{"PLpgSQL_recfield":{"fieldname":"table_schema","recparentno":1}},{"PLpgSQL_recfield":{"fieldname":"table_name","recparentno":1}}],"action":{"PLpgSQL_stmt_block":{"lineno":2,"body":[{"PLpgSQL_stmt_fors":{"lineno":3,"var":{"PLpgSQL_rec":{"refname":"r","dno":1,"lineno":1}},"body":[{"PLpgSQL_stmt_dynexecute":{"lineno":6,"query":{"PLpgSQL_expr":{"query":"'GRANT ALL ON ' || quote_ident(r.table_schema) || '.' || quote_ident(r.table_name) || ' TO webuser'","parseMode":2}}}}],"query":{"PLpgSQL_expr":{"query":"SELECT table_schema, table_name FROM information_schema.tables\n WHERE table_type = 'VIEW' AND table_schema = 'public'","parseMode":0}}}},{"PLpgSQL_stmt_return":{}}]}}}}, {"PLpgSQL_function":{"datums":[{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}},{"PLpgSQL_var":{"refname":"i","lineno":3,"datatype":{"PLpgSQL_type":{"typname":"INT"}}}},{"PLpgSQL_var":{"refname":"c","lineno":4,"datatype":{"PLpgSQL_type":{"typname":"pg_catalog.refcursor"}},"cursor_explicit_expr":{"PLpgSQL_expr":{"query":"SELECT generate_series(1,10)","parseMode":0}},"cursor_explicit_argrow":-1,"cursor_options":256}},{"PLpgSQL_rec":{"refname":"i","dno":3,"lineno":6}}],"action":{"PLpgSQL_stmt_block":{"lineno":5,"body":[{"PLpgSQL_stmt_forc":{"lineno":6,"var":{"PLpgSQL_rec":{"refname":"i","dno":3,"lineno":6}},"body":[{"PLpgSQL_stmt_raise":{"lineno":7,"elog_level":18,"message":"i is %","params":[{"PLpgSQL_expr":{"query":"i","parseMode":2}}]}}],"curvar":2}},{"PLpgSQL_stmt_return":{}}]}}}}, {"PLpgSQL_function":{"datums":[{"PLpgSQL_var":{"refname":"p_in","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.int4"}}}},{"PLpgSQL_rec":{"refname":"p_out","dno":1}},{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}},{"PLpgSQL_recfield":{"fieldname":"sumattribute","recparentno":1}}],"action":{"PLpgSQL_stmt_block":{"lineno":3,"body":[{"PLpgSQL_stmt_assign":{"lineno":4,"varno":3,"expr":{"PLpgSQL_expr":{"query":"p_out.sumattribute := p_in","parseMode":4}}}},{"PLpgSQL_stmt_return":{}}]}}}}, {"PLpgSQL_function":{"datums":[{"PLpgSQL_var":{"refname":"$1","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.float4"}}}},{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}}],"action":{"PLpgSQL_stmt_block":{"lineno":4,"body":[{"PLpgSQL_stmt_return":{"lineno":5,"expr":{"PLpgSQL_expr":{"query":"subtotal * 0.06","parseMode":2}}}}]}}}}, From f9542feee4cb7cd2ad260acd1585702a5f2c3f7f Mon Sep 17 00:00:00 2001 From: Lukas Fittl Date: Mon, 23 Sep 2024 23:25:45 -0700 Subject: [PATCH 5/7] PL/pgSQL: Add support for declaration of cursors --- scripts/extract_source.rb | 4 ++++ src/postgres/src_pl_plpgsql_src_pl_gram.c | 4 ++++ test/plpgsql_samples.expected.json | 3 ++- test/plpgsql_samples.sql | 10 ++++++++++ 4 files changed, 20 insertions(+), 1 deletion(-) diff --git a/scripts/extract_source.rb b/scripts/extract_source.rb index fd4d7889..45f782b7 100644 --- a/scripts/extract_source.rb +++ b/scripts/extract_source.rb @@ -578,6 +578,10 @@ def write_out typ = (PLpgSQL_type *) palloc0(sizeof(PLpgSQL_type)); typ->typname = pstrdup(string); typ->ttype = pg_strcasecmp(string, "RECORD") == 0 ? PLPGSQL_TTYPE_REC : PLPGSQL_TTYPE_SCALAR; + if (pg_strcasecmp(string, "REFCURSOR") == 0 || pg_strcasecmp(string, "CURSOR") == 0) + { + typ->typoid = REFCURSOROID; + } return typ; } )) diff --git a/src/postgres/src_pl_plpgsql_src_pl_gram.c b/src/postgres/src_pl_plpgsql_src_pl_gram.c index f72e9cb1..bc699e9e 100644 --- a/src/postgres/src_pl_plpgsql_src_pl_gram.c +++ b/src/postgres/src_pl_plpgsql_src_pl_gram.c @@ -6096,6 +6096,10 @@ static PLpgSQL_type * parse_datatype(const char *string, int location) { typ = (PLpgSQL_type *) palloc0(sizeof(PLpgSQL_type)); typ->typname = pstrdup(string); typ->ttype = pg_strcasecmp(string, "RECORD") == 0 ? PLPGSQL_TTYPE_REC : PLPGSQL_TTYPE_SCALAR; + if (pg_strcasecmp(string, "REFCURSOR") == 0 || pg_strcasecmp(string, "CURSOR") == 0) + { + typ->typoid = REFCURSOROID; + } return typ; } diff --git a/test/plpgsql_samples.expected.json b/test/plpgsql_samples.expected.json index da096c21..bc713048 100644 --- a/test/plpgsql_samples.expected.json +++ b/test/plpgsql_samples.expected.json @@ -24,5 +24,6 @@ {"PLpgSQL_function":{"datums":[{"PLpgSQL_var":{"refname":"p_in","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.int4"}}}},{"PLpgSQL_rec":{"refname":"p_out","dno":1}},{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}},{"PLpgSQL_recfield":{"fieldname":"sumattribute","recparentno":1}}],"action":{"PLpgSQL_stmt_block":{"lineno":3,"body":[{"PLpgSQL_stmt_assign":{"lineno":4,"varno":3,"expr":{"PLpgSQL_expr":{"query":"p_out.sumattribute := p_in","parseMode":4}}}},{"PLpgSQL_stmt_return":{}}]}}}}, {"PLpgSQL_function":{"datums":[{"PLpgSQL_var":{"refname":"$1","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.float4"}}}},{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}}],"action":{"PLpgSQL_stmt_block":{"lineno":4,"body":[{"PLpgSQL_stmt_return":{"lineno":5,"expr":{"PLpgSQL_expr":{"query":"subtotal * 0.06","parseMode":2}}}}]}}}}, {"PLpgSQL_function":{"datums":[{"PLpgSQL_var":{"refname":"s","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.int4"}}}},{"PLpgSQL_var":{"refname":"e","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.int4"}}}},{"PLpgSQL_var":{"refname":"id","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.int4"}}}},{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}}],"action":{"PLpgSQL_stmt_block":{"lineno":2,"body":[{"PLpgSQL_stmt_assign":{"lineno":3,"varno":2,"expr":{"PLpgSQL_expr":{"query":"id := s","parseMode":3}}}},{"PLpgSQL_stmt_loop":{"lineno":4,"body":[{"PLpgSQL_stmt_exit":{"lineno":5,"is_exit":true,"cond":{"PLpgSQL_expr":{"query":"id\u003ee","parseMode":2}}}},{"PLpgSQL_stmt_return_next":{"lineno":6}},{"PLpgSQL_stmt_assign":{"lineno":7,"varno":2,"expr":{"PLpgSQL_expr":{"query":"id := id + 1","parseMode":3}}}}]}},{"PLpgSQL_stmt_return":{}}]}}}}, -{"PLpgSQL_function":{"new_varno":1,"old_varno":2,"datums":[{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}},{"PLpgSQL_rec":{"refname":"new","dno":1}},{"PLpgSQL_rec":{"refname":"old","dno":2}}],"action":{"PLpgSQL_stmt_block":{"lineno":5,"body":[{"PLpgSQL_stmt_return":{"lineno":6}}]}}}} +{"PLpgSQL_function":{"new_varno":1,"old_varno":2,"datums":[{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}},{"PLpgSQL_rec":{"refname":"new","dno":1}},{"PLpgSQL_rec":{"refname":"old","dno":2}}],"action":{"PLpgSQL_stmt_block":{"lineno":5,"body":[{"PLpgSQL_stmt_return":{"lineno":6}}]}}}}, +{"PLpgSQL_function":{"datums":[{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}},{"PLpgSQL_var":{"refname":"ref","lineno":3,"datatype":{"PLpgSQL_type":{"typname":"refcursor"}}}}],"action":{"PLpgSQL_stmt_block":{"lineno":4,"body":[{"PLpgSQL_stmt_open":{"lineno":5,"curvar":1,"cursor_options":256,"query":{"PLpgSQL_expr":{"query":"SELECT col FROM test","parseMode":0}}}},{"PLpgSQL_stmt_return":{"lineno":6,"expr":{"PLpgSQL_expr":{"query":"ref","parseMode":2}}}}]}}}} ] diff --git a/test/plpgsql_samples.sql b/test/plpgsql_samples.sql index acd35add..a209cd5e 100644 --- a/test/plpgsql_samples.sql +++ b/test/plpgsql_samples.sql @@ -590,3 +590,13 @@ BEGIN 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; From 0472a8aaea5d9514bde73b2e35d76a827d88148d Mon Sep 17 00:00:00 2001 From: Lukas Fittl Date: Tue, 24 Sep 2024 00:40:20 -0700 Subject: [PATCH 6/7] PL/pgSQL: Add support for variables declared with a collation This requires the data type parsing to correctly set the collation field, which for now we do when the type is defined as "text". In passing we also adjust the hardcoded collection in get_collation_oid to use DEFAULT_COLLATION_OID (100), which seems better than using -1. --- scripts/extract_source.rb | 18 ++++++++++--- src/postgres/src_backend_catalog_namespace.c | 2 +- src/postgres/src_pl_plpgsql_src_pl_gram.c | 15 +++++++++-- src/postgres/src_port_pgstrcasecmp.c | 28 ++++++++++++++++++++ test/plpgsql_samples.expected.json | 3 ++- test/plpgsql_samples.sql | 10 +++++++ 6 files changed, 69 insertions(+), 7 deletions(-) diff --git a/scripts/extract_source.rb b/scripts/extract_source.rb index 45f782b7..f9434599 100644 --- a/scripts/extract_source.rb +++ b/scripts/extract_source.rb @@ -573,19 +573,30 @@ def write_out } )) 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_strcasecmp(string, "RECORD") == 0 ? PLPGSQL_TTYPE_REC : PLPGSQL_TTYPE_SCALAR; - if (pg_strcasecmp(string, "REFCURSOR") == 0 || pg_strcasecmp(string, "CURSOR") == 0) + 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 -1; }') +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; }') @@ -679,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) diff --git a/src/postgres/src_backend_catalog_namespace.c b/src/postgres/src_backend_catalog_namespace.c index 8f652a32..1654da68 100644 --- a/src/postgres/src_backend_catalog_namespace.c +++ b/src/postgres/src_backend_catalog_namespace.c @@ -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; } /* diff --git a/src/postgres/src_pl_plpgsql_src_pl_gram.c b/src/postgres/src_pl_plpgsql_src_pl_gram.c index bc699e9e..401ba046 100644 --- a/src/postgres/src_pl_plpgsql_src_pl_gram.c +++ b/src/postgres/src_pl_plpgsql_src_pl_gram.c @@ -6091,15 +6091,26 @@ plpgsql_sql_error_callback(void *arg) * expect that the given string is a copy from the source text. */ +#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_strcasecmp(string, "RECORD") == 0 ? PLPGSQL_TTYPE_REC : PLPGSQL_TTYPE_SCALAR; - if (pg_strcasecmp(string, "REFCURSOR") == 0 || pg_strcasecmp(string, "CURSOR") == 0) + 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; } diff --git a/src/postgres/src_port_pgstrcasecmp.c b/src/postgres/src_port_pgstrcasecmp.c index eb607083..091bbd80 100644 --- a/src/postgres/src_port_pgstrcasecmp.c +++ b/src/postgres/src_port_pgstrcasecmp.c @@ -2,6 +2,7 @@ * Symbols referenced in this file: * - pg_strcasecmp * - pg_toupper + * - pg_strncasecmp *-------------------------------------------------------------------- */ @@ -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. diff --git a/test/plpgsql_samples.expected.json b/test/plpgsql_samples.expected.json index bc713048..99c5bf20 100644 --- a/test/plpgsql_samples.expected.json +++ b/test/plpgsql_samples.expected.json @@ -25,5 +25,6 @@ {"PLpgSQL_function":{"datums":[{"PLpgSQL_var":{"refname":"$1","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.float4"}}}},{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}}],"action":{"PLpgSQL_stmt_block":{"lineno":4,"body":[{"PLpgSQL_stmt_return":{"lineno":5,"expr":{"PLpgSQL_expr":{"query":"subtotal * 0.06","parseMode":2}}}}]}}}}, {"PLpgSQL_function":{"datums":[{"PLpgSQL_var":{"refname":"s","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.int4"}}}},{"PLpgSQL_var":{"refname":"e","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.int4"}}}},{"PLpgSQL_var":{"refname":"id","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.int4"}}}},{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}}],"action":{"PLpgSQL_stmt_block":{"lineno":2,"body":[{"PLpgSQL_stmt_assign":{"lineno":3,"varno":2,"expr":{"PLpgSQL_expr":{"query":"id := s","parseMode":3}}}},{"PLpgSQL_stmt_loop":{"lineno":4,"body":[{"PLpgSQL_stmt_exit":{"lineno":5,"is_exit":true,"cond":{"PLpgSQL_expr":{"query":"id\u003ee","parseMode":2}}}},{"PLpgSQL_stmt_return_next":{"lineno":6}},{"PLpgSQL_stmt_assign":{"lineno":7,"varno":2,"expr":{"PLpgSQL_expr":{"query":"id := id + 1","parseMode":3}}}}]}},{"PLpgSQL_stmt_return":{}}]}}}}, {"PLpgSQL_function":{"new_varno":1,"old_varno":2,"datums":[{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}},{"PLpgSQL_rec":{"refname":"new","dno":1}},{"PLpgSQL_rec":{"refname":"old","dno":2}}],"action":{"PLpgSQL_stmt_block":{"lineno":5,"body":[{"PLpgSQL_stmt_return":{"lineno":6}}]}}}}, -{"PLpgSQL_function":{"datums":[{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}},{"PLpgSQL_var":{"refname":"ref","lineno":3,"datatype":{"PLpgSQL_type":{"typname":"refcursor"}}}}],"action":{"PLpgSQL_stmt_block":{"lineno":4,"body":[{"PLpgSQL_stmt_open":{"lineno":5,"curvar":1,"cursor_options":256,"query":{"PLpgSQL_expr":{"query":"SELECT col FROM test","parseMode":0}}}},{"PLpgSQL_stmt_return":{"lineno":6,"expr":{"PLpgSQL_expr":{"query":"ref","parseMode":2}}}}]}}}} +{"PLpgSQL_function":{"datums":[{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}},{"PLpgSQL_var":{"refname":"ref","lineno":3,"datatype":{"PLpgSQL_type":{"typname":"refcursor"}}}}],"action":{"PLpgSQL_stmt_block":{"lineno":4,"body":[{"PLpgSQL_stmt_open":{"lineno":5,"curvar":1,"cursor_options":256,"query":{"PLpgSQL_expr":{"query":"SELECT col FROM test","parseMode":0}}}},{"PLpgSQL_stmt_return":{"lineno":6,"expr":{"PLpgSQL_expr":{"query":"ref","parseMode":2}}}}]}}}}, +{"PLpgSQL_function":{"datums":[{"PLpgSQL_var":{"refname":"a","datatype":{"PLpgSQL_type":{"typname":"text"}}}},{"PLpgSQL_var":{"refname":"b","datatype":{"PLpgSQL_type":{"typname":"text"}}}},{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}},{"PLpgSQL_var":{"refname":"local_a","lineno":3,"datatype":{"PLpgSQL_type":{"typname":"text "}},"default_val":{"PLpgSQL_expr":{"query":"a","parseMode":2}}}},{"PLpgSQL_var":{"refname":"local_b","lineno":4,"datatype":{"PLpgSQL_type":{"typname":"text "}},"default_val":{"PLpgSQL_expr":{"query":"b","parseMode":2}}}}],"action":{"PLpgSQL_stmt_block":{"lineno":5,"body":[{"PLpgSQL_stmt_return":{"lineno":6,"expr":{"PLpgSQL_expr":{"query":"local_a \u003c local_b","parseMode":2}}}}]}}}} ] diff --git a/test/plpgsql_samples.sql b/test/plpgsql_samples.sql index a209cd5e..61ba0283 100644 --- a/test/plpgsql_samples.sql +++ b/test/plpgsql_samples.sql @@ -600,3 +600,13 @@ BEGIN 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; From 1f49e13b9f5a0fd7f68ea67660baf9173545e359 Mon Sep 17 00:00:00 2001 From: Lukas Fittl Date: Tue, 24 Sep 2024 00:44:03 -0700 Subject: [PATCH 7/7] PL/pgSQL: Uncomment additional test case that no longer fails --- test/plpgsql_samples.expected.json | 1 + test/plpgsql_samples.sql | 74 +++++++++++++++--------------- 2 files changed, 38 insertions(+), 37 deletions(-) diff --git a/test/plpgsql_samples.expected.json b/test/plpgsql_samples.expected.json index 99c5bf20..a921f382 100644 --- a/test/plpgsql_samples.expected.json +++ b/test/plpgsql_samples.expected.json @@ -2,6 +2,7 @@ {"PLpgSQL_function":{"datums":[{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}},{"PLpgSQL_var":{"refname":"r","lineno":3,"datatype":{"PLpgSQL_type":{"typname":"foo%rowtype"}}}},{"PLpgSQL_row":{"refname":"(unnamed row)","lineno":5,"fields":[{"name":"r","varno":1}]}}],"action":{"PLpgSQL_stmt_block":{"lineno":4,"body":[{"PLpgSQL_stmt_fors":{"lineno":5,"var":{"PLpgSQL_row":{"refname":"(unnamed row)","lineno":5,"fields":[{"name":"r","varno":1}]}},"body":[{"PLpgSQL_stmt_return_next":{"lineno":9}}],"query":{"PLpgSQL_expr":{"query":"SELECT * FROM foo WHERE fooid \u003e 0","parseMode":0}}}},{"PLpgSQL_stmt_return":{"lineno":11}}]}}}}, {"PLpgSQL_function":{"datums":[{"PLpgSQL_var":{"refname":"$1","datatype":{"PLpgSQL_type":{"typname":"date"}}}},{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}}],"action":{"PLpgSQL_stmt_block":{"lineno":2,"body":[{"PLpgSQL_stmt_return_query":{"lineno":3,"query":{"PLpgSQL_expr":{"query":"SELECT flightid\n FROM flight\n WHERE flightdate \u003e= $1\n AND flightdate \u003c ($1 + 1)","parseMode":0}}}},{"PLpgSQL_stmt_if":{"lineno":10,"cond":{"PLpgSQL_expr":{"query":"NOT FOUND","parseMode":2}},"then_body":[{"PLpgSQL_stmt_raise":{"lineno":11,"elog_level":21,"message":"No flight at %.","params":[{"PLpgSQL_expr":{"query":"$1","parseMode":2}}]}}]}},{"PLpgSQL_stmt_return":{"lineno":14}}]}}}}, {"PLpgSQL_function":{"datums":[{"PLpgSQL_var":{"refname":"v_name","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"varchar\""}}}},{"PLpgSQL_var":{"refname":"v_version","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"varchar\""}}}},{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}}],"action":{"PLpgSQL_stmt_block":{"lineno":2,"body":[{"PLpgSQL_stmt_if":{"lineno":3,"cond":{"PLpgSQL_expr":{"query":"v_version IS NULL","parseMode":2}},"then_body":[{"PLpgSQL_stmt_return":{"lineno":4,"expr":{"PLpgSQL_expr":{"query":"v_name","parseMode":2}}}}]}},{"PLpgSQL_stmt_return":{"lineno":6,"expr":{"PLpgSQL_expr":{"query":"v_name || '/' || v_version","parseMode":2}}}}]}}}}, +{"PLpgSQL_function":{"datums":[{"PLpgSQL_var":{"refname":"v_url","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"varchar\""}}}},{"PLpgSQL_var":{"refname":"v_host","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"varchar\""}}}},{"PLpgSQL_var":{"refname":"v_path","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"varchar\""}}}},{"PLpgSQL_var":{"refname":"v_query","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"varchar\""}}}},{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}},{"PLpgSQL_var":{"refname":"a_pos1","lineno":3,"datatype":{"PLpgSQL_type":{"typname":"INTEGER"}}}},{"PLpgSQL_var":{"refname":"a_pos2","lineno":4,"datatype":{"PLpgSQL_type":{"typname":"INTEGER"}}}}],"action":{"PLpgSQL_stmt_block":{"lineno":5,"body":[{"PLpgSQL_stmt_assign":{"lineno":6,"varno":1,"expr":{"PLpgSQL_expr":{"query":"v_host := NULL","parseMode":3}}}},{"PLpgSQL_stmt_assign":{"lineno":7,"varno":2,"expr":{"PLpgSQL_expr":{"query":"v_path := NULL","parseMode":3}}}},{"PLpgSQL_stmt_assign":{"lineno":8,"varno":3,"expr":{"PLpgSQL_expr":{"query":"v_query := NULL","parseMode":3}}}},{"PLpgSQL_stmt_assign":{"lineno":9,"varno":5,"expr":{"PLpgSQL_expr":{"query":"a_pos1 := instr(v_url, '//')","parseMode":3}}}},{"PLpgSQL_stmt_if":{"lineno":11,"cond":{"PLpgSQL_expr":{"query":"a_pos1 = 0","parseMode":2}},"then_body":[{"PLpgSQL_stmt_return":{"lineno":12}}]}},{"PLpgSQL_stmt_assign":{"lineno":14,"varno":6,"expr":{"PLpgSQL_expr":{"query":"a_pos2 := instr(v_url, '/', a_pos1 + 2)","parseMode":3}}}},{"PLpgSQL_stmt_if":{"lineno":15,"cond":{"PLpgSQL_expr":{"query":"a_pos2 = 0","parseMode":2}},"then_body":[{"PLpgSQL_stmt_assign":{"lineno":16,"varno":1,"expr":{"PLpgSQL_expr":{"query":"v_host := substr(v_url, a_pos1 + 2)","parseMode":3}}}},{"PLpgSQL_stmt_assign":{"lineno":17,"varno":2,"expr":{"PLpgSQL_expr":{"query":"v_path := '/'","parseMode":3}}}},{"PLpgSQL_stmt_return":{"lineno":18}}]}},{"PLpgSQL_stmt_assign":{"lineno":21,"varno":1,"expr":{"PLpgSQL_expr":{"query":"v_host := substr(v_url, a_pos1 + 2, a_pos2 - a_pos1 - 2)","parseMode":3}}}},{"PLpgSQL_stmt_assign":{"lineno":22,"varno":5,"expr":{"PLpgSQL_expr":{"query":"a_pos1 := instr(v_url, '?', a_pos2 + 1)","parseMode":3}}}},{"PLpgSQL_stmt_if":{"lineno":24,"cond":{"PLpgSQL_expr":{"query":"a_pos1 = 0","parseMode":2}},"then_body":[{"PLpgSQL_stmt_assign":{"lineno":25,"varno":2,"expr":{"PLpgSQL_expr":{"query":"v_path := substr(v_url, a_pos2)","parseMode":3}}}},{"PLpgSQL_stmt_return":{"lineno":26}}]}},{"PLpgSQL_stmt_assign":{"lineno":29,"varno":2,"expr":{"PLpgSQL_expr":{"query":"v_path := substr(v_url, a_pos2, a_pos1 - a_pos2)","parseMode":3}}}},{"PLpgSQL_stmt_assign":{"lineno":30,"varno":3,"expr":{"PLpgSQL_expr":{"query":"v_query := substr(v_url, a_pos1 + 1)","parseMode":3}}}},{"PLpgSQL_stmt_return":{}}]}}}}, {"PLpgSQL_function":{"datums":[{"PLpgSQL_var":{"refname":"v_job_id","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.int4"}}}},{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}},{"PLpgSQL_var":{"refname":"a_running_job_count","lineno":3,"datatype":{"PLpgSQL_type":{"typname":"integer"}}}},{"PLpgSQL_row":{"refname":"(unnamed row)","lineno":7,"fields":[{"name":"a_running_job_count","varno":2}]}},{"PLpgSQL_var":{"refname":"sqlstate","lineno":18,"datatype":{"PLpgSQL_type":{"typname":"pg_catalog.text"}},"isconst":true}},{"PLpgSQL_var":{"refname":"sqlerrm","lineno":18,"datatype":{"PLpgSQL_type":{"typname":"pg_catalog.text"}},"isconst":true}}],"action":{"PLpgSQL_stmt_block":{"lineno":4,"body":[{"PLpgSQL_stmt_execsql":{"lineno":5,"sqlstmt":{"PLpgSQL_expr":{"query":"LOCK TABLE cs_jobs IN EXCLUSIVE MODE","parseMode":0}}}},{"PLpgSQL_stmt_execsql":{"lineno":7,"sqlstmt":{"PLpgSQL_expr":{"query":"SELECT count(*) FROM cs_jobs WHERE end_stamp IS NULL","parseMode":0}},"into":true,"target":{"PLpgSQL_row":{"refname":"(unnamed row)","lineno":7,"fields":[{"name":"a_running_job_count","varno":2}]}}}},{"PLpgSQL_stmt_if":{"lineno":9,"cond":{"PLpgSQL_expr":{"query":"a_running_job_count \u003e 0","parseMode":2}},"then_body":[{"PLpgSQL_stmt_raise":{"lineno":10,"elog_level":21,"message":"Unable to create a new job: a job is currently running"}}]}},{"PLpgSQL_stmt_execsql":{"lineno":13,"sqlstmt":{"PLpgSQL_expr":{"query":"DELETE FROM cs_active_job","parseMode":0}}}},{"PLpgSQL_stmt_execsql":{"lineno":14,"sqlstmt":{"PLpgSQL_expr":{"query":"INSERT INTO cs_active_job(job_id) VALUES (v_job_id)","parseMode":0}}}},{"PLpgSQL_stmt_block":{"lineno":16,"body":[{"PLpgSQL_stmt_execsql":{"lineno":17,"sqlstmt":{"PLpgSQL_expr":{"query":"INSERT INTO cs_jobs (job_id, start_stamp) VALUES (v_job_id, now())","parseMode":0}}}}],"exceptions":{"PLpgSQL_exception_block":{"exc_list":[{"PLpgSQL_exception":{"conditions":[{"PLpgSQL_condition":{"condname":"unique_violation"}}]}}]}}}},{"PLpgSQL_stmt_return":{}}]}}}}, {"PLpgSQL_function":{"datums":[{"PLpgSQL_var":{"refname":"$1","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"varchar\""}}}},{"PLpgSQL_var":{"refname":"$2","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"varchar\""}}}},{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}},{"PLpgSQL_var":{"refname":"pos","lineno":3,"datatype":{"PLpgSQL_type":{"typname":"integer"}}}}],"action":{"PLpgSQL_stmt_block":{"lineno":4,"body":[{"PLpgSQL_stmt_assign":{"lineno":5,"varno":3,"expr":{"PLpgSQL_expr":{"query":"pos:= instr($1, $2, 1)","parseMode":3}}}},{"PLpgSQL_stmt_return":{"lineno":6,"expr":{"PLpgSQL_expr":{"query":"pos","parseMode":2}}}}]}}}}, {"PLpgSQL_function":{"datums":[{"PLpgSQL_var":{"refname":"string","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"varchar\""}}}},{"PLpgSQL_var":{"refname":"string_to_search","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"varchar\""}}}},{"PLpgSQL_var":{"refname":"beg_index","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.int4"}}}},{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}},{"PLpgSQL_var":{"refname":"pos","lineno":3,"datatype":{"PLpgSQL_type":{"typname":"integer "}},"notnull":true,"default_val":{"PLpgSQL_expr":{"query":"0","parseMode":2}}}},{"PLpgSQL_var":{"refname":"temp_str","lineno":4,"datatype":{"PLpgSQL_type":{"typname":"varchar"}}}},{"PLpgSQL_var":{"refname":"beg","lineno":5,"datatype":{"PLpgSQL_type":{"typname":"integer"}}}},{"PLpgSQL_var":{"refname":"length","lineno":6,"datatype":{"PLpgSQL_type":{"typname":"integer"}}}},{"PLpgSQL_var":{"refname":"ss_length","lineno":7,"datatype":{"PLpgSQL_type":{"typname":"integer"}}}}],"action":{"PLpgSQL_stmt_block":{"lineno":8,"body":[{"PLpgSQL_stmt_if":{"lineno":9,"cond":{"PLpgSQL_expr":{"query":"beg_index \u003e 0","parseMode":2}},"then_body":[{"PLpgSQL_stmt_assign":{"lineno":10,"varno":5,"expr":{"PLpgSQL_expr":{"query":"temp_str := substring(string FROM beg_index)","parseMode":3}}}},{"PLpgSQL_stmt_assign":{"lineno":11,"varno":4,"expr":{"PLpgSQL_expr":{"query":"pos := position(string_to_search IN temp_str)","parseMode":3}}}},{"PLpgSQL_stmt_if":{"lineno":13,"cond":{"PLpgSQL_expr":{"query":"pos = 0","parseMode":2}},"then_body":[{"PLpgSQL_stmt_return":{"lineno":14,"expr":{"PLpgSQL_expr":{"query":"0","parseMode":2}}}}],"else_body":[{"PLpgSQL_stmt_return":{"lineno":16,"expr":{"PLpgSQL_expr":{"query":"pos + beg_index - 1","parseMode":2}}}}]}}],"elsif_list":[{"PLpgSQL_if_elsif":{"lineno":18,"cond":{"PLpgSQL_expr":{"query":"beg_index \u003c 0","parseMode":2}},"stmts":[{"PLpgSQL_stmt_assign":{"lineno":19,"varno":8,"expr":{"PLpgSQL_expr":{"query":"ss_length := char_length(string_to_search)","parseMode":3}}}},{"PLpgSQL_stmt_assign":{"lineno":20,"varno":7,"expr":{"PLpgSQL_expr":{"query":"length := char_length(string)","parseMode":3}}}},{"PLpgSQL_stmt_assign":{"lineno":21,"varno":6,"expr":{"PLpgSQL_expr":{"query":"beg := length + beg_index - ss_length + 2","parseMode":3}}}},{"PLpgSQL_stmt_while":{"lineno":23,"cond":{"PLpgSQL_expr":{"query":"beg \u003e 0","parseMode":2}},"body":[{"PLpgSQL_stmt_assign":{"lineno":24,"varno":5,"expr":{"PLpgSQL_expr":{"query":"temp_str := substring(string FROM beg FOR ss_length)","parseMode":3}}}},{"PLpgSQL_stmt_assign":{"lineno":25,"varno":4,"expr":{"PLpgSQL_expr":{"query":"pos := position(string_to_search IN temp_str)","parseMode":3}}}},{"PLpgSQL_stmt_if":{"lineno":27,"cond":{"PLpgSQL_expr":{"query":"pos \u003e 0","parseMode":2}},"then_body":[{"PLpgSQL_stmt_return":{"lineno":28,"expr":{"PLpgSQL_expr":{"query":"beg","parseMode":2}}}}]}},{"PLpgSQL_stmt_assign":{"lineno":31,"varno":6,"expr":{"PLpgSQL_expr":{"query":"beg := beg - 1","parseMode":3}}}}]}},{"PLpgSQL_stmt_return":{"lineno":34,"expr":{"PLpgSQL_expr":{"query":"0","parseMode":2}}}}]}}],"else_body":[{"PLpgSQL_stmt_return":{"lineno":36,"expr":{"PLpgSQL_expr":{"query":"0","parseMode":2}}}}]}},{"PLpgSQL_stmt_return":{}}]}}}}, diff --git a/test/plpgsql_samples.sql b/test/plpgsql_samples.sql index 61ba0283..8862f56a 100644 --- a/test/plpgsql_samples.sql +++ b/test/plpgsql_samples.sql @@ -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