pax_global_header00006660000000000000000000000064146541053230014515gustar00rootroot0000000000000052 comment=26933b0d5fa6c4a750ecd91d0e8500121a6f97ab plpgsql_check-2.7.8/000077500000000000000000000000001465410532300143525ustar00rootroot00000000000000plpgsql_check-2.7.8/.editorconfig000066400000000000000000000002671465410532300170340ustar00rootroot00000000000000root = true [*.{c,h,l,y,pl,pm}] indent_style = tab indent_size = tab tab_width = 4 [*.{sgml,xml}] indent_style = space indent_size = 1 [*.xsl] indent_style = space indent_size = 2 plpgsql_check-2.7.8/.gitignore000066400000000000000000000004561465410532300163470ustar00rootroot00000000000000# Global excludes across all subdirectories *.o *.so *.so.[0-9] *.so.[0-9].[0-9] *.sl *.sl.[0-9] *.sl.[0-9].[0-9] *.dylib *.dll *.a *.mo *.bc objfiles.txt .deps/ *.gcno *.gcda *.gcov *.gcov.out lcov.info *.vcproj *.vcxproj win32ver.rc *.exe lib*dll.def lib*.pc /results regression.diffs regression.out plpgsql_check-2.7.8/LICENSE000066400000000000000000000024651465410532300153660ustar00rootroot00000000000000# Licence Copyright (c) Pavel Stehule (pavel.stehule@gmail.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # Note If you like it, send a postcard to address Pavel Stehule Skalice 12 256 01 Benesov u Prahy Czech Republic I invite any questions, comments, bug reports, patches on mail address pavel.stehule@gmail.com plpgsql_check-2.7.8/META.json000066400000000000000000000027041465410532300157760ustar00rootroot00000000000000{ "name": "plpgsql_check", "abstract": "Additional tools for plpgsql functions validation", "description": "The plpgsql_check is PostgreSQL extension with functionality for direct or indirect extra validation of functions in plpgsql language. It verifies a validity of SQL identifiers used in plpgsql code. It try to identify a performance issues. Modern versions has integrated profiler. The table and function dependencies can be displayed", "version": "2.7.8", "maintainer": "Pavel STEHULE ", "license": "bsd", "provides": { "plpgsql_check": { "abstract": "Additional tools for plpgsql functions validation", "file": "sql/plpgsql_check_active.sql", "docfile": "README.md", "version": "2.7.8" } }, "prereqs": { "runtime": { "requires": { "PostgreSQL": "12.0.0", "plpgsql": 0 } } }, "resources": { "bugtracker": { "web": "https://github.com/okbob/plpgsql_check/issues/" }, "repository": { "url": "git://github.com/okbob/plpgsql_check.git", "web": "https://github.com/okbob/plpgsql_check/", "type": "git" } }, "generated_by": "David E. Wheeler", "meta-spec": { "version": "1.0.0", "url": "http://pgxn.org/meta/spec.txt" }, "tags": [ "plpgsql", "analyzer", "validate", "development", "profiler" ] } plpgsql_check-2.7.8/Makefile000066400000000000000000000013521465410532300160130ustar00rootroot00000000000000# $PostgreSQL: pgsql/contrib/plpgsql_check/Makefile MODULE_big = plpgsql_check OBJS = $(patsubst %.c,%.o,$(wildcard src/*.c)) DATA = plpgsql_check--2.7.sql EXTENSION = plpgsql_check ifndef MAJORVERSION MAJORVERSION := $(basename $(VERSION)) endif REGRESS_OPTS = --dbname=$(PL_TESTDB) REGRESS = plpgsql_check_passive plpgsql_check_active plpgsql_check_active-$(MAJORVERSION) plpgsql_check_passive-$(MAJORVERSION) override CFLAGS += -g ifdef NO_PGXS subdir = contrib/plpgsql_check top_builddir = ../.. include $(top_builddir)/src/Makefile.global include $(top_srcdir)/contrib/contrib-global.mk else PG_CONFIG = pg_config PGXS := $(shell $(PG_CONFIG) --pgxs) include $(PGXS) endif override CFLAGS += -I$(top_builddir)/src/pl/plpgsql/src -Wall plpgsql_check-2.7.8/README.md000066400000000000000000001366121465410532300156420ustar00rootroot00000000000000[![Stand With Ukraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner2-direct.svg)](https://stand-with-ukraine.pp.ua) plpgsql_check ============= This extension is a full linter for plpgsql for PostgreSQL. It leverages only the internal PostgreSQL parser/evaluator so you see exactly the errors would occur at runtime. Furthermore, it parses the SQL inside your routines and finds errors not usually found during the "CREATE PROCEDURE/FUNCTION" command. You can control the levels of many warnings and hints. Finally, you can add PRAGAMA type markers to turn off/on many aspects allowing you to hide messages you already know about, or to remind you to come back for deeper cleaning later. I founded this project, because I wanted to publish the code I wrote for the two years, when I tried to write enhanced checking for PostgreSQL upstream. It was not fully successful - integration into upstream requires some larger plpgsql refactoring. But the code is fully functional and can be used in production (and it is used in production). So, I created this extension to be available for all plpgsql developers. If if you want to join our group to help the further development of this extension, register yourself at that [postgresql extension hacking](https://groups.google.com/forum/#!forum/postgresql-extensions-hacking) google group. # Features * checks fields of referenced database objects and types inside embedded SQL * validates you are using the correct types for function parameters * identifies unused variables and function arguments, unmodified OUT arguments * partial detection of dead code (code after an RETURN command) * detection of missing RETURN command in function (common after exception handlers, complex logic) * tries to identify unwanted hidden casts, which can be a performance issue like unused indexes * ability to collect relations and functions used by function * ability to check EXECUTE statements against SQL injection vulnerability I invite any ideas, patches, bugreports. PostgreSQL PostgreSQL 12 - 16 are supported. The SQL statements inside PL/pgSQL functions are checked by the validator for semantic errors. These errors can be found by calling the plpgsql_check_function: # Active mode postgres=# CREATE EXTENSION plpgsql_check; LOAD postgres=# CREATE TABLE t1(a int, b int); CREATE TABLE postgres=# CREATE OR REPLACE FUNCTION public.f1() RETURNS void LANGUAGE plpgsql AS $function$ DECLARE r record; BEGIN FOR r IN SELECT * FROM t1 LOOP RAISE NOTICE '%', r.c; -- there is bug - table t1 missing "c" column END LOOP; END; $function$; CREATE FUNCTION postgres=# select f1(); -- execution doesn't find a bug due to empty table t1 f1 ──── (1 row) postgres=# \x Expanded display is on. postgres=# select * from plpgsql_check_function_tb('f1()'); ─[ RECORD 1 ]─────────────────────────── functionid │ f1 lineno │ 6 statement │ RAISE sqlstate │ 42703 message │ record "r" has no field "c" detail │ [null] hint │ [null] level │ error position │ 0 query │ [null] postgres=# \sf+ f1 CREATE OR REPLACE FUNCTION public.f1() RETURNS void LANGUAGE plpgsql 1 AS $function$ 2 DECLARE r record; 3 BEGIN 4 FOR r IN SELECT * FROM t1 5 LOOP 6 RAISE NOTICE '%', r.c; -- there is bug - table t1 missing "c" column 7 END LOOP; 8 END; 9 $function$ Function plpgsql_check_function() has three possible output formats: text, json or xml select * from plpgsql_check_function('f1()', fatal_errors := false); plpgsql_check_function ------------------------------------------------------------------------ error:42703:4:SQL statement:column "c" of relation "t1" does not exist Query: update t1 set c = 30 -- ^ error:42P01:7:RAISE:missing FROM-clause entry for table "r" Query: SELECT r.c -- ^ error:42601:7:RAISE:too few parameters specified for RAISE (7 rows) postgres=# select * from plpgsql_check_function('fx()', format:='xml'); plpgsql_check_function ──────────────────────────────────────────────────────────────── error42P01relation "foo111" does not existRETURNSELECT (select a from foo111) (1 row) ## Arguments You can set level of warnings via function's parameters: ### Mandatory argument * `funcoid oid` - function name or function signature - functions require a function specification. Any function in PostgreSQL can be specified by Oid or by name or by signature. When you know oid or complete function's signature, you can use a regprocedure type parameter like `'fx()'::regprocedure` or `16799::regprocedure`. Possible alternative is using a name only, when function's name is unique - like `'fx'`. When the name is not unique or the function doesn't exists it raises a error. ### Optional arguments * `relid DEFAULT 0` - oid of relation assigned with trigger function. It is necessary to check any trigger function. You are sending the table in that the trigger operates on. * `fatal_errors boolean DEFAULT true` - stop on first error (prevents massive error reports) * `other_warnings boolean DEFAULT true` - show warnings like different attributes number in assignmenet on left and right side, variable overlaps function's parameter, unused variables, unwanted casting, etc. * `extra_warnings boolean DEFAULT true` - show warnings like missing `RETURN`, shadowed variables, dead code, never read (unused) function's parameter, unmodified variables, modified auto variables, etc. * `performance_warnings boolean DEFAULT false` - performance related warnings like declared type with type modifier, casting, implicit casts in where clause (can be the reason why an index is not used), etc. * `security_warnings boolean DEFAULT false` - security related checks like SQL injection vulnerability detection * `compatibility_warnings boolean DEFAULT false` - compatibility related checks like obsolete explicit setting internal cursor names in refcursor's or cursor's variables. * `anyelementtype regtype DEFAULT 'int'` - an actual type to be used when testing the anyelement type * `anyenumtype regtype DEFAULT '-'` - an actual type to be used when testing the anyenum type * `anyrangetype regtype DEFAULT 'int4range'` - an actual type to be used when testing the anyrange type * `anycompatibletype DEFAULT 'int'` - an actual type to be used when testing the anycompatible type * `anycompatiblerangetype DEFAULT 'int4range'` - an actual range type to be used when testing the anycompatible range type * `without_warnings DEFAULT false` - disable all warnings (Ignores all xxxx_warning parameters, a quick override) * `all_warnings DEFAULT false` - enable all warnings (Ignores other xxx_warning parameters, a quick positive) * `newtable DEFAULT NULL`, `oldtable DEFAULT NULL` - the names of NEW or OLD transition tables. These parameters are required when transition tables are used in trigger functions. * `use_incomment_options DEFAULT true` - when it is true, then in-comment options are active * `incomment_options_usage_warning DEFAULT false` - when it is true, then the warning is raised when in-comment option is used. * `constant_tracing boolean DEFAULT true` - when it is true, then the variable that holds some constant content, can be used like constant (it is work only in some simple cases, and the content of variable should not be ambigonuous). ## Triggers When you want to check any trigger, you have to enter a relation that will be used together with trigger function CREATE TABLE bar(a int, b int); postgres=# \sf+ foo_trg CREATE OR REPLACE FUNCTION public.foo_trg() RETURNS trigger LANGUAGE plpgsql 1 AS $function$ 2 BEGIN 3 NEW.c := NEW.a + NEW.b; 4 RETURN NEW; 5 END; 6 $function$ Missing relation specification postgres=# select * from plpgsql_check_function('foo_trg()'); ERROR: missing trigger relation HINT: Trigger relation oid must be valid Correct trigger checking (with specified relation) postgres=# select * from plpgsql_check_function('foo_trg()', 'bar'); plpgsql_check_function -------------------------------------------------------- error:42703:3:assignment:record "new" has no field "c" (1 row) For triggers with transitive tables you can set the `oldtable` and `newtable` parameters: create or replace function footab_trig_func() returns trigger as $$ declare x int; begin if false then -- should be ok; select count(*) from newtab into x; -- should fail; select count(*) from newtab where d = 10 into x; end if; return null; end; $$ language plpgsql; select * from plpgsql_check_function('footab_trig_func','footab', newtable := 'newtab'); ## In-comment options plpgsql_check allows persistent setting written in comments. These options are taken from function's source code before checking. The syntax is: @plpgsql_check_option: optioname [=] value [, optname [=] value ...] The settings from comment options has top high priority, but generally it can be disabled by option `use_incomment_options` to `false`. Example: create or replace function fx(anyelement) returns text as $$ begin /* * rewrite default polymorphic type to text * @plpgsql_check_options: anyelementtype = text */ return $1; end; $$ language plpgsql; ## Checking all of your code You can use the plpgsql_check_function for mass checking of functions/procedures and mass checking of triggers. Please, test following queries: -- check all nontrigger plpgsql functions SELECT p.oid, p.proname, plpgsql_check_function(p.oid) FROM pg_catalog.pg_namespace n JOIN pg_catalog.pg_proc p ON pronamespace = n.oid JOIN pg_catalog.pg_language l ON p.prolang = l.oid WHERE l.lanname = 'plpgsql' AND p.prorettype <> 2279; or -- check all trigger plpgsql functions SELECT p.proname, tgrelid::regclass, cf.* FROM pg_proc p JOIN pg_trigger t ON t.tgfoid = p.oid JOIN pg_language l ON p.prolang = l.oid JOIN pg_namespace n ON p.pronamespace = n.oid, LATERAL plpgsql_check_function(p.oid, t.tgrelid) cf WHERE n.nspname = 'public' and l.lanname = 'plpgsql'; or -- check all plpgsql functions (functions or trigger functions with defined triggers) SELECT (pcf).functionid::regprocedure, (pcf).lineno, (pcf).statement, (pcf).sqlstate, (pcf).message, (pcf).detail, (pcf).hint, (pcf).level, (pcf)."position", (pcf).query, (pcf).context FROM ( SELECT plpgsql_check_function_tb(pg_proc.oid, COALESCE(pg_trigger.tgrelid, 0)) AS pcf FROM pg_proc LEFT JOIN pg_trigger ON (pg_trigger.tgfoid = pg_proc.oid) WHERE prolang = (SELECT lang.oid FROM pg_language lang WHERE lang.lanname = 'plpgsql') AND pronamespace <> (SELECT nsp.oid FROM pg_namespace nsp WHERE nsp.nspname = 'pg_catalog') AND -- ignore unused triggers (pg_proc.prorettype <> (SELECT typ.oid FROM pg_type typ WHERE typ.typname = 'trigger') OR pg_trigger.tgfoid IS NOT NULL) OFFSET 0 ) ss ORDER BY (pcf).functionid::regprocedure::text, (pcf).lineno; # Passive mode (only recommended for development or preproduction) Functions can be checked upon execution - plpgsql_check module must be loaded (via postgresql.conf). ## Configuration Settings plpgsql_check.mode = [ disabled | by_function | fresh_start | every_start ] plpgsql_check.fatal_errors = [ yes | no ] plpgsql_check.show_nonperformance_warnings = false plpgsql_check.show_performance_warnings = false Default mode is by_function, that means that the enhanced check is done only in active mode - by calling the plpgsql_check_function. `fresh_start` means cold start (first the function is called). You can enable passive mode by load 'plpgsql'; -- 1.1 and higher doesn't need it load 'plpgsql_check'; set plpgsql_check.mode = 'every_start'; -- This scans all code before it is executed SELECT fx(10); -- run functions - function is checked before runtime starts it # Compatibility warnings ## Assigning string to refcursor variable PostgreSQL cursor's and refcursor's variables are enhanced string variables that holds unique name of related portal (internal structure of Postgres that is used for cursor's implementation). Until PostgreSQL 16, the the portal had same name like name of cursor variable. PostgreSQL 16 and higher change this mechanism and by default related portal will be named by some unique name. It solves some issues with cursors in nested blocks or when cursor is used in recursive called function. With mentioned change, the refcursor's variable should to take value from another refcursor variable or from some cursor variable (when cursor is opened). -- obsolete pattern DECLARE cur CURSOR FOR SELECT 1; rcur refcursor; BEGIN rcur := 'cur'; OPEN cur; ... -- new pattern DECLARE cur CURSOR FOR SELECT 1; rcur refcursor; BEGIN OPEN cur; rcur := cur; ... When `compatibility_warnings` flag is active, then `plpgsql_check` try to identify some fishy assigning to refcursor's variable or returning of refcursor's values: CREATE OR REPLACE FUNCTION public.foo() RETURNS refcursor AS $$ declare c cursor for select 1; r refcursor; begin open c; r := 'c'; return r; end; $$ LANGUAGE plpgsql; select * from plpgsql_check_function('foo', extra_warnings =>false, compatibility_warnings => true); ┌───────────────────────────────────────────────────────────────────────────────────┐ │ plpgsql_check_function │ ╞═══════════════════════════════════════════════════════════════════════════════════╡ │ compatibility:00000:6:assignment:obsolete setting of refcursor or cursor variable │ │ Detail: Internal name of cursor should not be specified by users. │ │ Context: at assignment to variable "r" declared on line 3 │ └───────────────────────────────────────────────────────────────────────────────────┘ (3 rows) # Limits plpgsql_check should find almost all errors on really static code. When developers use PLpgSQL's dynamic features like dynamic SQL or record data type, then false positives are possible. These should be rare - in well written code - and then the affected function should be redesigned or plpgsql_check should be disabled for this function. CREATE OR REPLACE FUNCTION f1() RETURNS void AS $$ DECLARE r record; BEGIN FOR r IN EXECUTE 'SELECT * FROM t1' LOOP RAISE NOTICE '%', r.c; END LOOP; END; $$ LANGUAGE plpgsql SET plpgsql.enable_check TO false; A usage of plpgsql_check adds a small overhead (when passive mode is enabled) and you should use that setting only in development or preproduction environments. ## Dynamic SQL This module doesn't check queries that are assembled in runtime. It is not possible to identify results of dynamic queries - so plpgsql_check cannot to set correct type to record variables and cannot to check a dependent SQLs and expressions. When type of record's variable is not know, you can assign it explicitly with pragma `type`: DECLARE r record; BEGIN EXECUTE format('SELECT * FROM %I', _tablename) INTO r; PERFORM plpgsql_check_pragma('type: r (id int, processed bool)'); IF NOT r.processed THEN ... Attention: The SQL injection check can detect only some SQL injection vulnerabilities. This tool cannot be used for security audit! Some issues will not be detected. This check can raise false alarms too - probably when variable is sanitized by other command or when the value is of some composite type. ## Refcursors plpgsql_check cannot be used to detect structure of referenced cursors. A reference on cursor in PLpgSQL is implemented as name of global cursor. In check time, the name is not known (not in all possibilities), and global cursor doesn't exist. It is a significant issue for any static analysis. PLpgSQL cannot know how to set the correct type for the record variables and cannot to check the dependent SQL statements and expressions. A solution is the same for dynamic SQL. Don't use record variable as target when you use refcursor type or disable plpgsql_check for these functions. CREATE OR REPLACE FUNCTION foo(refcur_var refcursor) RETURNS void AS $$ DECLARE rec_var record; BEGIN FETCH refcur_var INTO rec_var; -- this is STOP for plpgsql_check RAISE NOTICE '%', rec_var; -- record rec_var is not assigned yet error In this case a record type should not be used (use known rowtype instead): CREATE OR REPLACE FUNCTION foo(refcur_var refcursor) RETURNS void AS $$ DECLARE rec_var some_rowtype; BEGIN FETCH refcur_var INTO rec_var; RAISE NOTICE '%', rec_var; ## Temporary tables plpgsql_check cannot verify queries over temporary tables that are created in plpgsql's function runtime. For this use case it is necessary to create a fake temp table or disable plpgsql_check for this function. In reality temp tables are stored in own (per user) schema with higher priority than persistent tables. So you can do (with following trick safetly): CREATE OR REPLACE FUNCTION public.disable_dml() RETURNS trigger LANGUAGE plpgsql AS $function$ BEGIN RAISE EXCEPTION SQLSTATE '42P01' USING message = format('this instance of %I table doesn''t allow any DML operation', TG_TABLE_NAME), hint = format('you should use "CREATE TEMP TABLE %1$I(LIKE %1$I INCLUDING ALL);" statement', TG_TABLE_NAME); RETURN NULL; END; $function$; CREATE TABLE foo(a int, b int); -- doesn't hold data, ever CREATE TRIGGER foo_disable_dml BEFORE INSERT OR UPDATE OR DELETE ON foo EXECUTE PROCEDURE disable_dml(); postgres=# INSERT INTO foo VALUES(10,20); ERROR: this instance of foo table doesn't allow any DML operation HINT: you should to run "CREATE TEMP TABLE foo(LIKE foo INCLUDING ALL);" statement postgres=# CREATE TABLE postgres=# INSERT INTO foo VALUES(10,20); INSERT 0 1 This trick emulates GLOBAL TEMP tables partially and it allows a statical validation. Other possibility is using a [template foreign data wrapper] (https://github.com/okbob/template_fdw) You can use pragma `table` and create ephemeral table: BEGIN CREATE TEMP TABLE xxx(a int); PERFORM plpgsql_check_pragma('table: xxx(a int)'); INSERT INTO xxx VALUES(10); PERFORM plpgsql_check_pragma('table: [pg_temp].zzz(like schemaname.table1 including all)'); ... # Dependency list A function plpgsql_show_dependency_tb will show all functions, operators and relations used inside processed function: postgres=# select * from plpgsql_show_dependency_tb('testfunc(int,float)'); ┌──────────┬───────┬────────┬─────────┬────────────────────────────┐ │ type │ oid │ schema │ name │ params │ ╞══════════╪═══════╪════════╪═════════╪════════════════════════════╡ │ FUNCTION │ 36008 │ public │ myfunc1 │ (integer,double precision) │ │ FUNCTION │ 35999 │ public │ myfunc2 │ (integer,double precision) │ │ OPERATOR │ 36007 │ public │ ** │ (integer,integer) │ │ RELATION │ 36005 │ public │ myview │ │ │ RELATION │ 36002 │ public │ mytable │ │ └──────────┴───────┴────────┴─────────┴────────────────────────────┘ (4 rows) Optional arguments of plpgsql_show_dependency_tb are `relid`, `anyelementtype`, `enumtype`, `anyrangetype`, `anycompatibletype` and `anycompatiblerangetype`. # Profiler The plpgsql_check contains simple profiler of plpgsql functions and procedures. It can work with/without access to shared memory. It depends on `shared_preload_libraries` config. When plpgsql_check is initialized by `shared_preload_libraries`, then it can allocate shared memory, and function's profiles are stored there. When plpgsql_check cannot to allocate shared memory, the profile is stored in session memory. Due to dependencies, `shared_preload_libraries` should to contain `plpgsql` first postgres=# show shared_preload_libraries ; ┌──────────────────────────┐ │ shared_preload_libraries │ ╞══════════════════════════╡ │ plpgsql,plpgsql_check │ └──────────────────────────┘ (1 row) The profiler is active when GUC `plpgsql_check.profiler` is on. The profiler doesn't require shared memory, but if there is not enough shared memory, then the profiler is limited just to active session. The profiler can be activated by calling function `plpgsql_check_profiler(true)` and disabled by calling same function with `false` argument (or with literals `on`, `off`). When plpgsql_check is initialized by `shared_preload_libraries`, another GUC is available to configure the amount of shared memory used by the profiler: `plpgsql_check.profiler_max_shared_chunks`. This defines the maximum number of statements chunk that can be stored in shared memory. For each plpgsql function (or procedure), the whole content is split into chunks of 30 statements. If needed, multiple chunks can be used to store the whole content of a single function. A single chunk is 1704 bytes. The default value for this GUC is 15000, which should be enough for big projects containing hundreds of thousands of statements in plpgsql, and will consume about 24MB of memory. If your project doesn't require that much number of chunks, you can set this parameter to a smaller number in order to decrease the memory usage. The minimum value is 50 (which should consume about 83kB of memory), and the maximum value is 100000 (which should consume about 163MB of memory). Changing this parameter requires a PostgreSQL restart. The profiler will also retrieve the query identifier for each instruction that contains an expression or optimizable statement. Note that this requires pg_stat_statements, or another similar third-party extension), to be installed. There are some limitations to the query identifier retrieval: * if a plpgsql expression contains underlying statements, only the top level query identifier will be retrieved * the profiler doesn't compute query identifier by itself but relies on external extension, such as pg_stat_statements, for that. It means that depending on the external extension behavior, you may not be able to see a query identifier for some statements. That's for instance the case with DDL statements, as pg_stat_statements doesn't expose the query identifier for such queries. * a query identifier is retrieved only for instructions containing expressions. This means that plpgsql_profiler_function_tb() function can report less query identifier than instructions on a single line. Attention: An update of shared profiles can decrease performance on servers under higher load. The profile can be displayed by function `plpgsql_profiler_function_tb`: postgres=# select lineno, avg_time, source from plpgsql_profiler_function_tb('fx(int)'); ┌────────┬──────────┬───────────────────────────────────────────────────────────────────┐ │ lineno │ avg_time │ source │ ╞════════╪══════════╪═══════════════════════════════════════════════════════════════════╡ │ 1 │ │ │ │ 2 │ │ declare result int = 0; │ │ 3 │ 0.075 │ begin │ │ 4 │ 0.202 │ for i in 1..$1 loop │ │ 5 │ 0.005 │ select result + i into result; select result + i into result; │ │ 6 │ │ end loop; │ │ 7 │ 0 │ return result; │ │ 8 │ │ end; │ └────────┴──────────┴───────────────────────────────────────────────────────────────────┘ (9 rows) The times in the result are in miliseconds. The profile per statements (not per line) can be displayed by function plpgsql_profiler_function_statements_tb: CREATE OR REPLACE FUNCTION public.fx1(a integer) RETURNS integer LANGUAGE plpgsql 1 AS $function$ 2 begin 3 if a > 10 then 4 raise notice 'ahoj'; 5 return -1; 6 else 7 raise notice 'nazdar'; 8 return 1; 9 end if; 10 end; 11 $function$ postgres=# select stmtid, parent_stmtid, parent_note, lineno, exec_stmts, stmtname from plpgsql_profiler_function_statements_tb('fx1'); ┌────────┬───────────────┬─────────────┬────────┬────────────┬─────────────────┐ │ stmtid │ parent_stmtid │ parent_note │ lineno │ exec_stmts │ stmtname │ ╞════════╪═══════════════╪═════════════╪════════╪════════════╪═════════════════╡ │ 0 │ ∅ │ ∅ │ 2 │ 0 │ statement block │ │ 1 │ 0 │ body │ 3 │ 0 │ IF │ │ 2 │ 1 │ then body │ 4 │ 0 │ RAISE │ │ 3 │ 1 │ then body │ 5 │ 0 │ RETURN │ │ 4 │ 1 │ else body │ 7 │ 0 │ RAISE │ │ 5 │ 1 │ else body │ 8 │ 0 │ RETURN │ └────────┴───────────────┴─────────────┴────────┴────────────┴─────────────────┘ (6 rows) All stored profiles can be displayed by calling function `plpgsql_profiler_functions_all`: postgres=# select * from plpgsql_profiler_functions_all(); ┌───────────────────────┬────────────┬────────────┬──────────┬─────────────┬──────────┬──────────┐ │ funcoid │ exec_count │ total_time │ avg_time │ stddev_time │ min_time │ max_time │ ╞═══════════════════════╪════════════╪════════════╪══════════╪═════════════╪══════════╪══════════╡ │ fxx(double precision) │ 1 │ 0.01 │ 0.01 │ 0.00 │ 0.01 │ 0.01 │ └───────────────────────┴────────────┴────────────┴──────────┴─────────────┴──────────┴──────────┘ (1 row) There are two functions for cleaning stored profiles: `plpgsql_profiler_reset_all()` and `plpgsql_profiler_reset(regprocedure)`. ## Coverage metrics plpgsql_check provides two functions: * `plpgsql_coverage_statements(name)` * `plpgsql_coverage_branches(name)` ## Note There is another very good PLpgSQL profiler - https://github.com/glynastill/plprofiler My extension is designed to be simple for use and practical. Nothing more or less. plprofiler is more complex. It builds call graphs and from this graph it can create flame graph of execution times. Both extensions can be used together with the builtin PostgreSQL's feature - tracking functions. set track_functions to 'pl'; ... select * from pg_stat_user_functions; # Tracer plpgsql_check provides a tracing possibility - in this mode you can see notices on start or end functions (terse and default verbosity) and start or end statements (verbose verbosity). For default and verbose verbosity the content of function arguments is displayed. The content of related variables are displayed when verbosity is verbose. postgres=# do $$ begin perform fx(10,null, 'now', e'stěhule'); end; $$; NOTICE: #0 ->> start of inline_code_block (Oid=0) NOTICE: #2 ->> start of function fx(integer,integer,date,text) (Oid=16405) NOTICE: #2 call by inline_code_block line 1 at PERFORM NOTICE: #2 "a" => '10', "b" => null, "c" => '2020-08-03', "d" => 'stěhule' NOTICE: #4 ->> start of function fx(integer) (Oid=16404) NOTICE: #4 call by fx(integer,integer,date,text) line 1 at PERFORM NOTICE: #4 "a" => '10' NOTICE: #4 <<- end of function fx (elapsed time=0.098 ms) NOTICE: #2 <<- end of function fx (elapsed time=0.399 ms) NOTICE: #0 <<- end of block (elapsed time=0.754 ms) The number after `#` is a execution frame counter (this number is related to depth of error context stack). It allows to pair start and end of function. Tracing is enabled by setting `plpgsql_check.tracer` to `on`. Attention - enabling this behaviour has significant negative impact on performance (unlike the profiler). You can set a level for output used by tracer `plpgsql_check.tracer_errlevel` (default is `notice`). The output content is limited by length specified by `plpgsql_check.tracer_variable_max_length` configuration variable. The tracer can be activated by calling function `plpgsql_check_tracer(true)` and disabled by calling same function with `false` argument (or with literals `on`, `off`). First, the usage of tracer should be explicitly enabled by superuser by setting `set plpgsql_check.enable_tracer to on;` or `plpgsql_check.enable_tracer to on` in `postgresql.conf`. This is a security safeguard. The tracer shows content of plpgsql's variables, and then some security sensitive information can be displayed to an unprivileged user (when he runs security definer function). Second, the extension `plpgsql_check` should be loaded. It can be done by execution of some `plpgsql_check` function or explicitly by command `load 'plpgsql_check';`. You can use configuration's option `shared_preload_libraries`, `local_preload_libraries` or `session_preload_libraries`. In terse verbose mode the output is reduced: postgres=# set plpgsql_check.tracer_verbosity TO terse; SET postgres=# do $$ begin perform fx(10,null, 'now', e'stěhule'); end; $$; NOTICE: #0 start of inline code block (oid=0) NOTICE: #2 start of fx (oid=16405) NOTICE: #4 start of fx (oid=16404) NOTICE: #4 end of fx NOTICE: #2 end of fx NOTICE: #0 end of inline code block In verbose mode the output is extended about statement details: postgres=# do $$ begin perform fx(10,null, 'now', e'stěhule'); end; $$; NOTICE: #0 ->> start of block inline_code_block (oid=0) NOTICE: #0.1 1 --> start of PERFORM NOTICE: #2 ->> start of function fx(integer,integer,date,text) (oid=16405) NOTICE: #2 call by inline_code_block line 1 at PERFORM NOTICE: #2 "a" => '10', "b" => null, "c" => '2020-08-04', "d" => 'stěhule' NOTICE: #2.1 1 --> start of PERFORM NOTICE: #2.1 "a" => '10' NOTICE: #4 ->> start of function fx(integer) (oid=16404) NOTICE: #4 call by fx(integer,integer,date,text) line 1 at PERFORM NOTICE: #4 "a" => '10' NOTICE: #4.1 6 --> start of assignment NOTICE: #4.1 "a" => '10', "b" => '20' NOTICE: #4.1 <-- end of assignment (elapsed time=0.076 ms) NOTICE: #4.1 "res" => '130' NOTICE: #4.2 7 --> start of RETURN NOTICE: #4.2 "res" => '130' NOTICE: #4.2 <-- end of RETURN (elapsed time=0.054 ms) NOTICE: #4 <<- end of function fx (elapsed time=0.373 ms) NOTICE: #2.1 <-- end of PERFORM (elapsed time=0.589 ms) NOTICE: #2 <<- end of function fx (elapsed time=0.727 ms) NOTICE: #0.1 <-- end of PERFORM (elapsed time=1.147 ms) NOTICE: #0 <<- end of block (elapsed time=1.286 ms) A special feature of tracer is tracing of the `ASSERT` statement when `plpgsql_check.trace_assert` is `on`. When `plpgsql_check.trace_assert_verbosity` is `DEFAULT`, then all function's or procedure's variables are displayed when assert expression is false. When this configuration is `VERBOSE` then all variables from all plpgsql frames are displayed. This behaviour is independent on `plpgsql.check_asserts` value. It can be used, although the assertions are disabled in plpgsql runtime. postgres=# set plpgsql_check.tracer to off; postgres=# set plpgsql_check.trace_assert_verbosity TO verbose; postgres=# do $$ begin perform fx(10,null, 'now', e'stěhule'); end; $$; NOTICE: #4 PLpgSQL assert expression (false) on line 12 of fx(integer) is false NOTICE: "a" => '10', "res" => null, "b" => '20' NOTICE: #2 PL/pgSQL function fx(integer,integer,date,text) line 1 at PERFORM NOTICE: "a" => '10', "b" => null, "c" => '2020-08-05', "d" => 'stěhule' NOTICE: #0 PL/pgSQL function inline_code_block line 1 at PERFORM ERROR: assertion failed CONTEXT: PL/pgSQL function fx(integer) line 12 at ASSERT SQL statement "SELECT fx(a)" PL/pgSQL function fx(integer,integer,date,text) line 1 at PERFORM SQL statement "SELECT fx(10,null, 'now', e'stěhule')" PL/pgSQL function inline_code_block line 1 at PERFORM postgres=# set plpgsql.check_asserts to off; SET postgres=# do $$ begin perform fx(10,null, 'now', e'stěhule'); end; $$; NOTICE: #4 PLpgSQL assert expression (false) on line 12 of fx(integer) is false NOTICE: "a" => '10', "res" => null, "b" => '20' NOTICE: #2 PL/pgSQL function fx(integer,integer,date,text) line 1 at PERFORM NOTICE: "a" => '10', "b" => null, "c" => '2020-08-05', "d" => 'stěhule' NOTICE: #0 PL/pgSQL function inline_code_block line 1 at PERFORM DO Tracer can show usage of subtransaction buffer id (`nxids`). The displayed `tnl` number is transaction nesting level number (for plpgsql it depends on deep of blocks with exception's handlers). ## Detection of unclosed cursors PLpgSQL's cursors are just names of SQL cursors. The life cycle of SQL cursors is not joined with scope of related plpgsql's cursor variable. SQL cursors are cloased by self at transaction end, but for long transaction and too much opened cursors it can be too late. It is better to close cursor explicitly when cursor is not necessary (by CLOSE statement). Without it the significant memory issues are possible. When OPEN statement try to use cursor that is not closed yet, the warning is raised. This feature can be disabled by setting `plpgsql_check.cursors_leaks to off`. This check is not active, when routine is called recusively The unclosed cursors can be checked immediately when function is finished. This check is disabled by default, and should be enabled by `plpgsql_check.strict_cursors_leaks to on`. Any unclosed cursor is reported once. ## Using with plugin_debugger If you use `plugin_debugger` (plpgsql debugger) together with `plpgsql_check`, then `plpgsql_check` should be initialized after `plugin_debugger` (because `plugin_debugger` doesn't support the sharing of PL/pgSQL's debug API). For example (`postgresql.conf`): shared_preload_libraries = 'plugin_debugger,plpgsql,plpgsql_check' ## Attention - SECURITY Tracer prints content of variables or function arguments. For security definer function, this content can hold security sensitive data. This is reason why tracer is disabled by default and should be enabled only with super user rights `plpgsql_check.enable_tracer`. # Pragma You can configure plpgsql_check behaviour inside a checked function with "pragma" function. This is a analogy of PL/SQL or ADA language of PRAGMA feature. PLpgSQL doesn't support PRAGMA, but plpgsql_check detects function named `plpgsql_check_pragma` and takes options from the parameters of this function. These plpgsql_check options are valid to the end of this group of statements. CREATE OR REPLACE FUNCTION test() RETURNS void AS $$ BEGIN ... -- for following statements disable check PERFORM plpgsql_check_pragma('disable:check'); ... -- enable check again PERFORM plpgsql_check_pragma('enable:check'); ... END; $$ LANGUAGE plpgsql; The function `plpgsql_check_pragma` is immutable function that returns one. It is defined by `plpgsql_check` extension. You can declare alternative `plpgsql_check_pragma` function like: CREATE OR REPLACE FUNCTION plpgsql_check_pragma(VARIADIC args[]) RETURNS int AS $$ SELECT 1 $$ LANGUAGE sql IMMUTABLE; Using pragma function in declaration part of top block sets options on function level too. CREATE OR REPLACE FUNCTION test() RETURNS void AS $$ DECLARE aux int := plpgsql_check_pragma('disable:extra_warnings'); ... Shorter syntax for pragma is supported too: CREATE OR REPLACE FUNCTION test() RETURNS void AS $$ DECLARE r record; BEGIN PERFORM 'PRAGMA:TYPE:r (a int, b int)'; PERFORM 'PRAGMA:TABLE: x (like pg_class)'; ... ## Supported pragmas * `echo:str` - print string (for testing). Inside string, there can be used "variables": @@id, @@name, @@signature * `status:check`,`status:tracer`, `status:other_warnings`, `status:performance_warnings`, `status:extra_warnings`,`status:security_warnings` This outputs the current value (e.g. other_warnings enabled) * `enable:check`,`enable:tracer`, `enable:other_warnings`, `enable:performance_warnings`, `enable:extra_warnings`,`enable:security_warnings` * `disable:check`,`disable:tracer`, `disable:other_warnings`, `disable:performance_warnings`, `disable:extra_warnings`,`disable:security_warnings` This can be used to disable the Hint in returning from an anyelement function. Just put the pragma before the RETURN statement. * `type:varname typename` or `type:varname (fieldname type, ...)` - set type to variable of record type * `table: name (column_name type, ...)` or `table: name (like tablename)` - create ephemeral temporary table (if you want to specify schema, then only `pg_temp` schema is allowed. * `sequence: name` - create ephemeral temporary sequence * `assert-schema: varname` - check-time assertation - ensure so schema specified by variable is valid * `assert-table: [ varname_schema, ] , varname` - ensure so table name specified by variables (by constant tracing) is valid * `assert-column: [varname_schema, ], varname_table , varname` - ensure so column spefified by variables is valid Pragmas `enable:tracer` and `disable:tracer`are active for Postgres 12 and higher # Update plpgsql_check doesn't support update (of plpgsql_check). You should to drop this before install new version of this extension. # Compilation You need a development environment for PostgreSQL extensions: make clean make install result: [pavel@localhost plpgsql_check]$ make USE_PGXS=1 clean rm -f plpgsql_check.so libplpgsql_check.a libplpgsql_check.pc rm -f plpgsql_check.o rm -rf results/ regression.diffs regression.out tmp_check/ log/ [pavel@localhost plpgsql_check]$ make USE_PGXS=1 all clang -O2 -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Wendif-labels -Wmissing-format-attribute -Wformat-security -fno-strict-aliasing -fwrapv -fpic -I/usr/local/pgsql/lib/pgxs/src/makefiles/../../src/pl/plpgsql/src -I. -I./ -I/usr/local/pgsql/include/server -I/usr/local/pgsql/include/internal -D_GNU_SOURCE -c -o plpgsql_check.o plpgsql_check.c clang -O2 -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Wendif-labels -Wmissing-format-attribute -Wformat-security -fno-strict-aliasing -fwrapv -fpic -I/usr/local/pgsql/lib/pgxs/src/makefiles/../../src/pl/plpgsql/src -shared -o plpgsql_check.so plpgsql_check.o -L/usr/local/pgsql/lib -Wl,--as-needed -Wl,-rpath,'/usr/local/pgsql/lib',--enable-new-dtags [pavel@localhost plpgsql_check]$ su root Password: ******* [root@localhost plpgsql_check]# make USE_PGXS=1 install /usr/bin/mkdir -p '/usr/local/pgsql/lib' /usr/bin/mkdir -p '/usr/local/pgsql/share/extension' /usr/bin/mkdir -p '/usr/local/pgsql/share/extension' /usr/bin/install -c -m 755 plpgsql_check.so '/usr/local/pgsql/lib/plpgsql_check.so' /usr/bin/install -c -m 644 plpgsql_check.control '/usr/local/pgsql/share/extension/' /usr/bin/install -c -m 644 plpgsql_check--0.9.sql '/usr/local/pgsql/share/extension/' [root@localhost plpgsql_check]# exit [pavel@localhost plpgsql_check]$ make USE_PGXS=1 installcheck /usr/local/pgsql/lib/pgxs/src/makefiles/../../src/test/regress/pg_regress --inputdir=./ --psqldir='/usr/local/pgsql/bin' --dbname=pl_regression --load-language=plpgsql --dbname=contrib_regression plpgsql_check_passive plpgsql_check_active plpgsql_check_active-9.5 (using postmaster on Unix socket, default port) ============== dropping database "contrib_regression" ============== DROP DATABASE ============== creating database "contrib_regression" ============== CREATE DATABASE ALTER DATABASE ============== installing plpgsql ============== CREATE LANGUAGE ============== running regression test queries ============== test plpgsql_check_passive ... ok test plpgsql_check_active ... ok test plpgsql_check_active-9.5 ... ok ===================== All 3 tests passed. ===================== ## Compilation on Ubuntu Sometimes successful compilation can require libicu-dev package (PostgreSQL 10 and higher - when pg was compiled with ICU support) sudo apt install libicu-dev ## Compilation plpgsql_check on Windows You can check precompiled dll libraries http://okbob.blogspot.cz/2015/02/plpgsqlcheck-is-available-for-microsoft.html, http://okbob.blogspot.com/2023/10/compiled-dll-of-plpgsqlcheck-254-and.html or compile by self: 1. Download and install PostgreSQL for Win32 from http://www.enterprisedb.com 2. Download and install Microsoft Visual C++ Express 3. Read tutorial http://blog.2ndquadrant.com/compiling-postgresql-extensions-visual-studio-windows 4. Build plpgsql_check.dll 5. Install plugin 1. copy `plpgsql_check.dll` to `PostgreSQL\14\lib` 2. copy `plpgsql_check.control` and `plpgsql_check--2.1.sql` to `PostgreSQL\14\share\extension` ## Checked on * gcc on Linux (against all supported PostgreSQL) * clang 3.4 on Linux (against PostgreSQL 10) * for success regress tests the PostgreSQL 10 or higher is required Compilation against PostgreSQL 10 requires libICU! # Licence Copyright (c) Pavel Stehule (pavel.stehule@gmail.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # Note If you like it, send a postcard to address Pavel Stehule Skalice 12 256 01 Benesov u Prahy Czech Republic I invite any questions, comments, bug reports, patches on mail address pavel.stehule@gmail.com plpgsql_check-2.7.8/TODO.md000066400000000000000000000004741465410532300154460ustar00rootroot00000000000000- possibility to specify locality of storage for profiling (local, shared) - possibility to export profiles in format for pprof https://github.com/google/pprof - possibility to export call stack (short [parent, current], full [entry point, current]) - possibility to show critical path (function or statement level) plpgsql_check-2.7.8/_config.yml000066400000000000000000000000321465410532300164740ustar00rootroot00000000000000theme: jekyll-theme-caymanplpgsql_check-2.7.8/examples/000077500000000000000000000000001465410532300161705ustar00rootroot00000000000000plpgsql_check-2.7.8/examples/custom_scan_function.md000066400000000000000000000057721465410532300227500ustar00rootroot00000000000000custom_scan_function ==================== This creates a custom function, as an example, that changes the output to better fit my needs inside of my IDE (DataGrip) # Usage Through a macro that parses the current function, I call this with ONE routine: * `select * from plpgsql_check_custom('plpgsql_check_custom'::regproc);` Without that parameter, it runs for all routines! There are more parameters to easily control the intensity, or run for a set of schemas or filter messages! This is a starting point... Make it your own. # Why the timestamp FWIW, I return the timestamp ts with the message because I have 10+ windows open, each with it's own code+output. And after fixing a bunch of stuff in another window, that timestamp ALSO tells me how "dated" the scan is. It also confirms it refreshed (sometimes it runs so fast, and gives you the same output, you are not sure if it actually refreshed!). The great part is that once you have a RECORD() type setup for output, adding more columns is easy. # Why the procedure name a row and not a column Honestly, we have horribly long names: long_schema_name.Really_Long_package_name.Really_long_function_name()! While they are clear and make coding easier, it is quickly a waste of screen real-estate. I would rather have *one* long column in my output. It's a personal preference. And that is the beauty of PG and of this tool. # Motivation Finally, for output, the custom message that made me do this is given as an example below. The message from the base level was NOT enough. It only tells you what the code is trying to do. It does not make it clear WHICH parameter is likely wrong. So through a bit of introspection (Thanks to Pavel), I was able to add the full parameter details (including DEF it that parameter has a DEF value). As well as the expected TYPE... # Output
`
#### 
#### Error in: schema.pkg_name$update_user() at Line: 16       PARAMETER TYPING ISSUE?
#### 
#### Param Name           Flow/DEF  (your code)    Definition     
#### ==========           ========  ===========    ==========     
#### eid                  IN        bigint         bigint         
#### pid                  IN        bigint         bigint         
#### typ                  IN        character varyibigint         
#### val                  IN        bigint         text           
#### addrmv               IN        bigint         integer        
#### 
`
# Future Ideas Now that this actually exists, I have a few more ideas. I will actually see how to integrate this better with my DataGrip. I would love to make this work in psql, as a setting: `\set PLPGSQL_CHECK_ALTERED_ROUTINES ON` So, whenever I successfully compile (create or replace) a routine... Then this would run and output the issues it finds! There are a couple of additional things I would like to do. Some error messages give a line number, but it does not match up. I would love to do some code introspection, and extract the LINE in question (or better yet, the field in the case of FETCH INTO ... mismatch) plpgsql_check-2.7.8/examples/custom_scan_function.sql000066400000000000000000000205321465410532300231360ustar00rootroot00000000000000CREATE FUNCTION plpgsql_check_custom(funcoid oid DEFAULT NULL::oid, warns boolean DEFAULT false, stop_fatal boolean DEFAULT true, msg_like text DEFAULT '%'::text, proname_regex text DEFAULT '^(.*)$'::text, schema_regex text DEFAULT '^(.*)$'::text) RETURNS TABLE(ts character, check_msg text) LANGUAGE plpgsql AS $$ DECLARE -- This cursor drives the process (plpgsql_check_function()) does all the work! -- The filters are simple enough to filter down the messages, or the procedure name, and to control the INTENSITY of the LINTING -- You get the source... Make it your own! I wanted something I could use flexibly msgs CURSOR (func oid, warnings boolean , fatals boolean) FOR SELECT * FROM (SELECT p.oid, p.prokind, n.nspname || '.' || p.proname || '()' AS proname, public.plpgsql_check_function( funcoid => p.oid::regproc , fatal_errors := fatals , extra_warnings := warnings , performance_warnings := warnings /* set these 3 to false for initial pass */ , all_warnings := warnings)::text AS err FROM pg_catalog.pg_namespace n JOIN pg_catalog.pg_proc p ON pronamespace = n.oid JOIN pg_catalog.pg_language l ON p.prolang = l.oid WHERE l.lanname = 'plpgsql' AND p.prorettype <> 2279 /* not a trigger */ AND n.nspname <> 'public' AND p.prokind IN ('p', 'f') -- Only function and procedures AND p.oid = COALESCE(func, p.oid) AND p.proname OPERATOR (pg_catalog.~) proname_regex AND n.nspname OPERATOR (pg_catalog.~) schema_regex) q1 WHERE q1.err LIKE msg_like; thisproc text := ''; -- Used so we only waste ONE line outputting what function we are working on, as opposed to a COLUMN errmsg text; -- The error message: "error:42883:42:assignment:function schem.function(integer, unknown, unknown, unknown, unknown, unknown, unknown) does not exist" a_txt text[]; -- Used to pars errmsg fdecl text; -- Declaration after parsing fname text; -- Before the parens foid oid; -- Function OID to lookup the named parameters parm1 text; -- between the parens pos INT; -- Simple position of ( for parsing a_p1 text[]; -- Array of Params from the users code has_in boolean; -- is IN/OUT present in any parameters names text; -- Function Signature with Parameter Names a_name text[]; -- string_to_aarray( names, ', ' ) -- [IN/OUT/INOUT] FLDNAME type [DEFAULT ...] a_pname text[]; -- Name ONLY of the field name n_off INT; -- Offset into the array for stuff str_out text; -- Messages to send out, with Chr(10) separating them! flow_def text; -- Should we default to IN all the time for flow flow text; -- IN/INOUT/OUT + DEF BEGIN ts := TO_CHAR(NOW(), 'HH24:MI:SS'); -- this is constant (Maybe a waste of the column, but forces a TABLE() return in case you want to add more columns, etc! FOR msg IN msgs(funcoid, warns, stop_fatal) LOOP str_out := ''; -- Start Fresh, and add strings as we go, for one final RETURN NEXT! IF thisproc <> msg.proname THEN -- Return a header! IF thisproc <> '' THEN check_msg := ''; RETURN NEXT; -- Blank line between different functions! END IF; thisproc := msg.proname; check_msg := CONCAT('===========> PROCESSING: ', thisproc); -- While REDUNDANT on 42883 Errors, it separates ALL functions from each other! RETURN NEXT; END IF; check_msg := msg.err; RETURN NEXT; errmsg := msg.err; IF errmsg LIKE 'error:42883:%' THEN -- SELECT '{}','{}','{}','{}','{}','{}' INTO a_txt, a_p1, a_p2, a_name, a_pname, a_flow; -- Produces plpgsql_check() warnings! a_txt := '{}'; a_p1 := '{}'; a_name := '{}'; a_pname := '{}'; str_out := '#### '; -- RETURN NEXT; IF RIGHT(errmsg, 14) = 'does not exist' THEN errmsg := LEFT(errmsg, -15); END IF; a_txt := STRING_TO_ARRAY(errmsg, ':'); IF CARDINALITY(a_txt) <> 5 THEN check_msg := str_out || chr(10) || '######## ==> details unavailable, parsing error <=== #########'::TEXT; RETURN NEXT; CONTINUE; END IF; fdecl := a_txt[5]; pos := POSITION('(' IN fdecl); IF pos = 0 THEN check_msg := str_out || chr(10) || '######## ==> details unavailable, parsing error(2) <=== #########'::TEXT; RETURN NEXT; CONTINUE; END IF; fname := LEFT(fdecl, pos - 1); -- exclude the paren fname := SUBSTR(fname, POSITION(' ' IN fname) + 1); parm1 := TRIM(SUBSTR(fdecl, pos, POSITION(')' IN fdecl) - pos + 1)); -- RETURN NEXT (ts , concat('#### ', fdecl )); -- Really Just Debug! BEGIN foid := TO_REGPROC(fname)::oid; -- This function will not throw an exception, just returns NULL -- REPLACES the error block IF foid IS NULL THEN check_msg := '#### Either No Such function or No Paramters!'; RETURN NEXT; CONTINUE; END IF; str_out := str_out || chr(10) || CONCAT('#### ', 'Error in: ', thisproc, ' at Line: ', a_txt[3], ' PARAMETER TYPING ISSUE?') || chr(10) || '#### '; a_p1 := STRING_TO_ARRAY(SUBSTRING(parm1, 2, LENGTH(parm1) - 2), ', '); -- These are just the types SELECT (POSITION('IN ' IN args) + POSITION('OUT ' IN args) )> 0 as tagged, args into has_in, names FROM (SELECT pg_catalog.PG_GET_FUNCTION_ARGUMENTS(foid) as args) t; a_name := STRING_TO_ARRAY(names, ', '); -- Separate these out! has_in is set for us /* We have an array of [INOUT] varname type [DEFAULT xxx] | And an array of the users param types param1 We will OUTPUT: Parameter Name [35], INOUT+DEF[10], P1_TYPE[15], OUR_TYPE \n */ str_out := CONCAT(str_out, chr(10), '#### ', rpad('Param Name',20), ' ', rpad('Flow/DEF',10), rpad('(your code)',15), rpad('Definition',15) ); str_out := CONCAT(str_out, chr(10), '#### ', rpad('==========',20), ' ', rpad('========',10), rpad('===========',15), rpad('==========',15) ); IF has_in THEN n_off := 1; flow_def := NULL; ELSE n_off := 0; flow_def := 'IN '; -- We have to force the display of IN, just for consistency. END IF; FOR x IN 1 .. CARDINALITY(a_name) LOOP a_pname := STRING_TO_ARRAY(a_name[x], ' '); -- Parse into an array -- RAISE NOTICE 'a_pname 1 %, 2 %, 3 %', a_pname[1], a_pname[2], a_pname[3]; flow := COALESCE(flow_def, a_pname[1]) || CASE WHEN POSITION('DEFAULT' IN a_name[x])=0 THEN '' ELSE ' DEF' END; str_out := CONCAT(str_out, chr(10), '#### ', rpad(a_pname[1+n_off],20), ' ',rpad(flow,10), rpad(coalesce(a_p1[x],'???'),15), rpad(a_pname[2+n_off],15) ); END LOOP; EXCEPTION WHEN OTHERS THEN str_out := str_out || chr(10) || CONCAT('==== ERROR: ', SQLERRM, ' Unexpected Exception!'); END; str_out := str_out || chr(10) || '#### '; ELSE CONTINUE; -- Nothing to do, not our message END IF; check_msg := str_out; RETURN NEXT; END LOOP; IF thisproc='' AND funcoid is not null THEN check_msg := 'No Messages Returned for: ' || funcoid::regproc; RETURN NEXT; END IF; RETURN; END $$; plpgsql_check-2.7.8/expected/000077500000000000000000000000001465410532300161535ustar00rootroot00000000000000plpgsql_check-2.7.8/expected/README000066400000000000000000000003551465410532300170360ustar00rootroot00000000000000plpgsql_check_passive.out PostgreSQL 14, 15, 16 plpgsql_check_passive_1.out PostgreSQL 12, 13 plpgsql_check_active_1.out PostgreSQL 12, 13 plpgsql_check_active_2.out PostgreSQL 14, 15, 16 plpgsql_check_active.out PostgreSQL 17 plpgsql_check-2.7.8/expected/plpgsql_check_active-12.out000066400000000000000000000000001465410532300232640ustar00rootroot00000000000000plpgsql_check-2.7.8/expected/plpgsql_check_active-13.out000066400000000000000000000000001465410532300232650ustar00rootroot00000000000000plpgsql_check-2.7.8/expected/plpgsql_check_active-14.out000066400000000000000000000000001465410532300232660ustar00rootroot00000000000000plpgsql_check-2.7.8/expected/plpgsql_check_active-15.out000066400000000000000000000057301465410532300233060ustar00rootroot00000000000000LOAD 'plpgsql'; CREATE EXTENSION IF NOT EXISTS plpgsql_check; NOTICE: extension "plpgsql_check" already exists, skipping set client_min_messages to notice; create or replace function fxtest() returns void as $$ declare v_sqlstate text; v_message text; v_context text; begin get stacked diagnostics v_sqlstate = returned_sqlstate, v_message = message_text, v_context = pg_exception_context; end; $$ language plpgsql; select * from plpgsql_check_function('fxtest'); plpgsql_check_function ----------------------------------------------------------------------------------------------------------- error:0Z002:7:GET STACKED DIAGNOSTICS:GET STACKED DIAGNOSTICS cannot be used outside an exception handler (1 row) drop function fxtest(); create or replace procedure prtest() as $$ begin commit; end; $$ language plpgsql; select * from plpgsql_check_function('prtest'); --ok plpgsql_check_function ------------------------ (0 rows) create or replace procedure prtest() as $$ begin begin begin commit; end; end; exception when others then raise; end; $$ language plpgsql; select * from plpgsql_check_function('prtest'); --error plpgsql_check_function --------------------------------------------------------------------- error:2D000:5:COMMIT:cannot commit while a subtransaction is active (1 row) create or replace procedure prtest() as $$ begin raise exception 'error'; exception when others then begin begin commit; end; end; end; $$ language plpgsql; select * from plpgsql_check_function('prtest'); --ok plpgsql_check_function ------------------------ (0 rows) drop procedure prtest(); create function return_constant_refcursor() returns refcursor as $$ declare rc constant refcursor; begin open rc for select a from rc_test; return rc; end $$ language plpgsql; create table rc_test(a int); select * from plpgsql_check_function('return_constant_refcursor'); plpgsql_check_function ------------------------------------------------------- error:22005:5:OPEN:variable "rc" is declared CONSTANT (1 row) drop table rc_test; drop function return_constant_refcursor(); create procedure p1(a int, out b int) as $$ begin b := a + 10; end; $$ language plpgsql; create function f1() returns void as $$ declare b constant int; begin call p1(10, b); end; $$ language plpgsql; select * from plpgsql_check_function('f1'); plpgsql_check_function ------------------------------------------------------ error:22005:4:CALL:variable "b" is declared CONSTANT (1 row) drop function f1(); drop procedure p1(int, int); create or replace function f1() returns int as $$ declare c constant int default 100; begin return c; end; $$ language plpgsql; -- should be ok select * from plpgsql_check_function('f1'); plpgsql_check_function ------------------------ (0 rows) plpgsql_check-2.7.8/expected/plpgsql_check_active-16.out000066400000000000000000000104711465410532300233050ustar00rootroot00000000000000LOAD 'plpgsql'; CREATE EXTENSION IF NOT EXISTS plpgsql_check; NOTICE: extension "plpgsql_check" already exists, skipping set client_min_messages to notice; create or replace function fxtest() returns void as $$ declare v_sqlstate text; v_message text; v_context text; begin get stacked diagnostics v_sqlstate = returned_sqlstate, v_message = message_text, v_context = pg_exception_context; end; $$ language plpgsql; select * from plpgsql_check_function('fxtest'); plpgsql_check_function ----------------------------------------------------------------------------------------------------------- error:0Z002:7:GET STACKED DIAGNOSTICS:GET STACKED DIAGNOSTICS cannot be used outside an exception handler (1 row) drop function fxtest(); create or replace procedure prtest() as $$ begin commit; end; $$ language plpgsql; select * from plpgsql_check_function('prtest'); --ok plpgsql_check_function ------------------------ (0 rows) create or replace procedure prtest() as $$ begin begin begin commit; end; end; exception when others then raise; end; $$ language plpgsql; select * from plpgsql_check_function('prtest'); --error plpgsql_check_function --------------------------------------------------------------------- error:2D000:5:COMMIT:cannot commit while a subtransaction is active (1 row) create or replace procedure prtest() as $$ begin raise exception 'error'; exception when others then begin begin commit; end; end; end; $$ language plpgsql; select * from plpgsql_check_function('prtest'); --ok plpgsql_check_function ------------------------ (0 rows) drop procedure prtest(); create function return_constant_refcursor() returns refcursor as $$ declare rc constant refcursor; begin open rc for select a from rc_test; return rc; end $$ language plpgsql; create table rc_test(a int); select * from plpgsql_check_function('return_constant_refcursor'); plpgsql_check_function ------------------------------------------------------- error:22005:5:OPEN:variable "rc" is declared CONSTANT (1 row) drop table rc_test; drop function return_constant_refcursor(); create procedure p1(a int, out b int) as $$ begin b := a + 10; end; $$ language plpgsql; create function f1() returns void as $$ declare b constant int; begin call p1(10, b); end; $$ language plpgsql; select * from plpgsql_check_function('f1'); plpgsql_check_function ------------------------------------------------------ error:22005:4:CALL:variable "b" is declared CONSTANT (1 row) drop function f1(); drop procedure p1(int, int); create or replace function f1() returns int as $$ declare c constant int default 100; begin return c; end; $$ language plpgsql; -- should be ok select * from plpgsql_check_function('f1'); plpgsql_check_function ------------------------ (0 rows) drop function f1(); -- do not raise false warning create or replace function test_function() returns text as $$ declare s text; begin get diagnostics s = PG_CONTEXT; return s; end; $$ language plpgsql; create or replace procedure test_procedure() as $$ begin null; end; $$ language plpgsql; -- should be without any warnings select * from plpgsql_check_function('test_function', performance_warnings=>true); plpgsql_check_function ------------------------ (0 rows) select * from plpgsql_check_function('test_procedure', performance_warnings=>true); plpgsql_check_function ------------------------ (0 rows) drop function test_function(); drop procedure test_procedure(); -- detect dependecy in CALL statement create or replace function fx1_dep(int) returns int as $$ begin return $1; end; $$ language plpgsql; create or replace procedure px1_dep(int) as $$ begin end; $$ language plpgsql; create or replace function test_function() returns void as $$ begin call px1_dep(fx1_dep(10)); end; $$ language plpgsql; select type, schema, name, params from plpgsql_show_dependency_tb('test_function'); type | schema | name | params -----------+--------+---------+----------- FUNCTION | public | fx1_dep | (integer) PROCEDURE | public | px1_dep | (integer) (2 rows) drop function test_function(); drop procedure px1_dep(int); drop function fx1_dep(int); plpgsql_check-2.7.8/expected/plpgsql_check_active-17.out000066400000000000000000000104711465410532300233060ustar00rootroot00000000000000LOAD 'plpgsql'; CREATE EXTENSION IF NOT EXISTS plpgsql_check; NOTICE: extension "plpgsql_check" already exists, skipping set client_min_messages to notice; create or replace function fxtest() returns void as $$ declare v_sqlstate text; v_message text; v_context text; begin get stacked diagnostics v_sqlstate = returned_sqlstate, v_message = message_text, v_context = pg_exception_context; end; $$ language plpgsql; select * from plpgsql_check_function('fxtest'); plpgsql_check_function ----------------------------------------------------------------------------------------------------------- error:0Z002:7:GET STACKED DIAGNOSTICS:GET STACKED DIAGNOSTICS cannot be used outside an exception handler (1 row) drop function fxtest(); create or replace procedure prtest() as $$ begin commit; end; $$ language plpgsql; select * from plpgsql_check_function('prtest'); --ok plpgsql_check_function ------------------------ (0 rows) create or replace procedure prtest() as $$ begin begin begin commit; end; end; exception when others then raise; end; $$ language plpgsql; select * from plpgsql_check_function('prtest'); --error plpgsql_check_function --------------------------------------------------------------------- error:2D000:5:COMMIT:cannot commit while a subtransaction is active (1 row) create or replace procedure prtest() as $$ begin raise exception 'error'; exception when others then begin begin commit; end; end; end; $$ language plpgsql; select * from plpgsql_check_function('prtest'); --ok plpgsql_check_function ------------------------ (0 rows) drop procedure prtest(); create function return_constant_refcursor() returns refcursor as $$ declare rc constant refcursor; begin open rc for select a from rc_test; return rc; end $$ language plpgsql; create table rc_test(a int); select * from plpgsql_check_function('return_constant_refcursor'); plpgsql_check_function ------------------------------------------------------- error:22005:5:OPEN:variable "rc" is declared CONSTANT (1 row) drop table rc_test; drop function return_constant_refcursor(); create procedure p1(a int, out b int) as $$ begin b := a + 10; end; $$ language plpgsql; create function f1() returns void as $$ declare b constant int; begin call p1(10, b); end; $$ language plpgsql; select * from plpgsql_check_function('f1'); plpgsql_check_function ------------------------------------------------------ error:22005:4:CALL:variable "b" is declared CONSTANT (1 row) drop function f1(); drop procedure p1(int, int); create or replace function f1() returns int as $$ declare c constant int default 100; begin return c; end; $$ language plpgsql; -- should be ok select * from plpgsql_check_function('f1'); plpgsql_check_function ------------------------ (0 rows) drop function f1(); -- do not raise false warning create or replace function test_function() returns text as $$ declare s text; begin get diagnostics s = PG_CONTEXT; return s; end; $$ language plpgsql; create or replace procedure test_procedure() as $$ begin null; end; $$ language plpgsql; -- should be without any warnings select * from plpgsql_check_function('test_function', performance_warnings=>true); plpgsql_check_function ------------------------ (0 rows) select * from plpgsql_check_function('test_procedure', performance_warnings=>true); plpgsql_check_function ------------------------ (0 rows) drop function test_function(); drop procedure test_procedure(); -- detect dependecy in CALL statement create or replace function fx1_dep(int) returns int as $$ begin return $1; end; $$ language plpgsql; create or replace procedure px1_dep(int) as $$ begin end; $$ language plpgsql; create or replace function test_function() returns void as $$ begin call px1_dep(fx1_dep(10)); end; $$ language plpgsql; select type, schema, name, params from plpgsql_show_dependency_tb('test_function'); type | schema | name | params -----------+--------+---------+----------- FUNCTION | public | fx1_dep | (integer) PROCEDURE | public | px1_dep | (integer) (2 rows) drop function test_function(); drop procedure px1_dep(int); drop function fx1_dep(int); plpgsql_check-2.7.8/expected/plpgsql_check_active-18.out000066400000000000000000000104711465410532300233070ustar00rootroot00000000000000LOAD 'plpgsql'; CREATE EXTENSION IF NOT EXISTS plpgsql_check; NOTICE: extension "plpgsql_check" already exists, skipping set client_min_messages to notice; create or replace function fxtest() returns void as $$ declare v_sqlstate text; v_message text; v_context text; begin get stacked diagnostics v_sqlstate = returned_sqlstate, v_message = message_text, v_context = pg_exception_context; end; $$ language plpgsql; select * from plpgsql_check_function('fxtest'); plpgsql_check_function ----------------------------------------------------------------------------------------------------------- error:0Z002:7:GET STACKED DIAGNOSTICS:GET STACKED DIAGNOSTICS cannot be used outside an exception handler (1 row) drop function fxtest(); create or replace procedure prtest() as $$ begin commit; end; $$ language plpgsql; select * from plpgsql_check_function('prtest'); --ok plpgsql_check_function ------------------------ (0 rows) create or replace procedure prtest() as $$ begin begin begin commit; end; end; exception when others then raise; end; $$ language plpgsql; select * from plpgsql_check_function('prtest'); --error plpgsql_check_function --------------------------------------------------------------------- error:2D000:5:COMMIT:cannot commit while a subtransaction is active (1 row) create or replace procedure prtest() as $$ begin raise exception 'error'; exception when others then begin begin commit; end; end; end; $$ language plpgsql; select * from plpgsql_check_function('prtest'); --ok plpgsql_check_function ------------------------ (0 rows) drop procedure prtest(); create function return_constant_refcursor() returns refcursor as $$ declare rc constant refcursor; begin open rc for select a from rc_test; return rc; end $$ language plpgsql; create table rc_test(a int); select * from plpgsql_check_function('return_constant_refcursor'); plpgsql_check_function ------------------------------------------------------- error:22005:5:OPEN:variable "rc" is declared CONSTANT (1 row) drop table rc_test; drop function return_constant_refcursor(); create procedure p1(a int, out b int) as $$ begin b := a + 10; end; $$ language plpgsql; create function f1() returns void as $$ declare b constant int; begin call p1(10, b); end; $$ language plpgsql; select * from plpgsql_check_function('f1'); plpgsql_check_function ------------------------------------------------------ error:22005:4:CALL:variable "b" is declared CONSTANT (1 row) drop function f1(); drop procedure p1(int, int); create or replace function f1() returns int as $$ declare c constant int default 100; begin return c; end; $$ language plpgsql; -- should be ok select * from plpgsql_check_function('f1'); plpgsql_check_function ------------------------ (0 rows) drop function f1(); -- do not raise false warning create or replace function test_function() returns text as $$ declare s text; begin get diagnostics s = PG_CONTEXT; return s; end; $$ language plpgsql; create or replace procedure test_procedure() as $$ begin null; end; $$ language plpgsql; -- should be without any warnings select * from plpgsql_check_function('test_function', performance_warnings=>true); plpgsql_check_function ------------------------ (0 rows) select * from plpgsql_check_function('test_procedure', performance_warnings=>true); plpgsql_check_function ------------------------ (0 rows) drop function test_function(); drop procedure test_procedure(); -- detect dependecy in CALL statement create or replace function fx1_dep(int) returns int as $$ begin return $1; end; $$ language plpgsql; create or replace procedure px1_dep(int) as $$ begin end; $$ language plpgsql; create or replace function test_function() returns void as $$ begin call px1_dep(fx1_dep(10)); end; $$ language plpgsql; select type, schema, name, params from plpgsql_show_dependency_tb('test_function'); type | schema | name | params -----------+--------+---------+----------- FUNCTION | public | fx1_dep | (integer) PROCEDURE | public | px1_dep | (integer) (2 rows) drop function test_function(); drop procedure px1_dep(int); drop function fx1_dep(int); plpgsql_check-2.7.8/expected/plpgsql_check_active.out000066400000000000000000012613501465410532300230660ustar00rootroot00000000000000load 'plpgsql'; create extension if not exists plpgsql_check; set client_min_messages to notice; set plpgsql_check.regress_test_mode = true; -- -- check function statement tests -- --should fail - is not plpgsql select * from plpgsql_check_function_tb('session_user()'); ERROR: "session_user"() is not a plpgsql function create table t1(a int, b int); create table pa (id int, pa_id character varying(32), status character varying(60)); create table ml(ml_id character varying(32), status_from character varying(60), pa_id character varying(32), xyz int); create function f1() returns void as $$ begin if false then update t1 set c = 30; end if; if false then raise notice '%', r.c; end if; end; $$ language plpgsql; select f1(); f1 ---- (1 row) select * from plpgsql_check_function_tb('f1()', fatal_errors := true); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+---------------+----------+--------------------------------------------+--------+------+-------+----------+----------------------+--------- f1 | 4 | SQL statement | 42703 | column "c" of relation "t1" does not exist | | | error | 15 | update t1 set c = 30 | (1 row) select * from plpgsql_check_function_tb('f1()', fatal_errors := false); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+---------------+----------+--------------------------------------------+--------+------+-------+----------+----------------------+--------- f1 | 4 | SQL statement | 42703 | column "c" of relation "t1" does not exist | | | error | 15 | update t1 set c = 30 | f1 | 7 | RAISE | 42P01 | missing FROM-clause entry for table "r" | | | error | 1 | r.c | (2 rows) select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+---------------+----------+--------------------------------------------+--------+------+-------+----------+----------------------+--------- f1 | 4 | SQL statement | 42703 | column "c" of relation "t1" does not exist | | | error | 15 | update t1 set c = 30 | (1 row) drop function f1(); create function f1() returns void as $$ begin if false then insert into t1 values(10,20); update t1 set a = 10; delete from t1; end if; end; $$ language plpgsql stable; select f1(); f1 ---- (1 row) select * from plpgsql_check_function_tb('f1()', fatal_errors := false); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+---------------+----------+--------------------------------------------------+--------+------+-------+----------+------------------------------+--------- f1 | 4 | SQL statement | 0A000 | INSERT is not allowed in a non volatile function | | | error | 1 | insert into t1 values(10,20) | f1 | 5 | SQL statement | 0A000 | UPDATE is not allowed in a non volatile function | | | error | 1 | update t1 set a = 10 | f1 | 6 | SQL statement | 0A000 | DELETE is not allowed in a non volatile function | | | error | 1 | delete from t1 | (3 rows) drop function f1(); -- profiler check set plpgsql_check.profiler to on; create function f1() returns void as $$ begin if false then insert into t1 values(10,20); update t1 set a = 10; delete from t1; end if; end; $$ language plpgsql; select lineno, stmt_lineno, exec_stmts, source from plpgsql_profiler_function_tb('f1()'); lineno | stmt_lineno | exec_stmts | source --------+-------------+------------+----------------------------------- 1 | | | 2 | | | begin 3 | | | if false then 4 | | | insert into t1 values(10,20); 5 | | | update t1 set a = 10; 6 | | | delete from t1; 7 | | | end if; 8 | | | end; (8 rows) select f1(); f1 ---- (1 row) select lineno, stmt_lineno, exec_stmts, source from plpgsql_profiler_function_tb('f1()'); lineno | stmt_lineno | exec_stmts | source --------+-------------+------------+----------------------------------- 1 | | | 2 | 2 | 1 | begin 3 | 3 | 1 | if false then 4 | 4 | 0 | insert into t1 values(10,20); 5 | 5 | 0 | update t1 set a = 10; 6 | 6 | 0 | delete from t1; 7 | | | end if; 8 | | | end; (8 rows) select plpgsql_profiler_reset('f1()'); plpgsql_profiler_reset ------------------------ (1 row) select lineno, stmt_lineno, exec_stmts, source from plpgsql_profiler_function_tb('f1()'); lineno | stmt_lineno | exec_stmts | source --------+-------------+------------+----------------------------------- 1 | | | 2 | | | begin 3 | | | if false then 4 | | | insert into t1 values(10,20); 5 | | | update t1 set a = 10; 6 | | | delete from t1; 7 | | | end if; 8 | | | end; (8 rows) select f1(); f1 ---- (1 row) select lineno, stmt_lineno, exec_stmts, source from plpgsql_profiler_function_tb('f1()'); lineno | stmt_lineno | exec_stmts | source --------+-------------+------------+----------------------------------- 1 | | | 2 | 2 | 1 | begin 3 | 3 | 1 | if false then 4 | 4 | 0 | insert into t1 values(10,20); 5 | 5 | 0 | update t1 set a = 10; 6 | 6 | 0 | delete from t1; 7 | | | end if; 8 | | | end; (8 rows) select plpgsql_profiler_reset_all(); plpgsql_profiler_reset_all ---------------------------- (1 row) select lineno, stmt_lineno, exec_stmts, source from plpgsql_profiler_function_tb('f1()'); lineno | stmt_lineno | exec_stmts | source --------+-------------+------------+----------------------------------- 1 | | | 2 | | | begin 3 | | | if false then 4 | | | insert into t1 values(10,20); 5 | | | update t1 set a = 10; 6 | | | delete from t1; 7 | | | end if; 8 | | | end; (8 rows) drop function f1(); create or replace function f1() returns void as $$ begin raise notice '1'; exception when others then raise notice '2'; end; $$ language plpgsql; select f1(); NOTICE: 1 f1 ---- (1 row) select lineno, stmt_lineno, exec_stmts, source from plpgsql_profiler_function_tb('f1()'); lineno | stmt_lineno | exec_stmts | source --------+-------------+------------+---------------------------- 1 | | | 2 | 2 | 1 | begin 3 | 3 | 1 | raise notice '1'; 4 | | | exception when others then 5 | 5 | 0 | raise notice '2'; 6 | | | end; (6 rows) drop function f1(); -- test queryid retrieval create function f1() returns void as $$ declare t1 text = 't1'; begin insert into t1 values(10,20); EXECUTE 'update ' || 't1' || ' set a = 10'; EXECUTE 'delete from ' || t1; end; $$ language plpgsql; select plpgsql_profiler_reset_all(); plpgsql_profiler_reset_all ---------------------------- (1 row) select plpgsql_profiler_install_fake_queryid_hook(); plpgsql_profiler_install_fake_queryid_hook -------------------------------------------- (1 row) select f1(); f1 ---- (1 row) select queryids, lineno, stmt_lineno, exec_stmts, source from plpgsql_profiler_function_tb('f1()'); queryids | lineno | stmt_lineno | exec_stmts | source ----------+--------+-------------+------------+------------------------------------------------ | 1 | | | | 2 | | | declare | 3 | | | t1 text = 't1'; | 4 | 4 | 1 | begin {3} | 5 | 5 | 1 | insert into t1 values(10,20); {2} | 6 | 6 | 1 | EXECUTE 'update ' || 't1' || ' set a = 10'; {4} | 7 | 7 | 1 | EXECUTE 'delete from ' || t1; | 8 | | | end; (8 rows) select plpgsql_profiler_remove_fake_queryid_hook(); plpgsql_profiler_remove_fake_queryid_hook ------------------------------------------- (1 row) drop function f1(); set plpgsql_check.profiler to off; create function f1() returns void as $$ declare r record; begin if false then for r in update t1 set a = a + 1 returning * loop raise notice '%', r.a; end loop; end if; end; $$ language plpgsql; select f1(); f1 ---- (1 row) select * from plpgsql_check_function_tb('f1()', fatal_errors := false); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+---------+--------+------+-------+----------+-------+--------- (0 rows) drop function f1(); create function f1() returns void as $$ declare r record; begin if false then for r in update t1 set a = a + 1 returning * loop raise notice '%', r.a; end loop; end if; end; $$ language plpgsql stable; select f1(); f1 ---- (1 row) select * from plpgsql_check_function_tb('f1()', fatal_errors := false); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+----------------------+----------+--------------------------------------------------+--------+------+-------+----------+-------------------------------------+--------- f1 | 5 | FOR over SELECT rows | 0A000 | UPDATE is not allowed in a non volatile function | | | error | 1 | update t1 set a = a + 1 returning * | (1 row) drop function f1(); create function g1(out a int, out b int) as $$ select 10,20; $$ language sql; create function f1() returns void as $$ declare r record; begin r := g1(); if false then raise notice '%', r.c; end if; end; $$ language plpgsql; select f1(); f1 ---- (1 row) select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+-----------------------------+--------+------+-------+----------+-------+---------------------- f1 | 6 | RAISE | 42703 | record "r" has no field "c" | | | error | | | SQL expression "r.c" (1 row) select f1(); f1 ---- (1 row) drop function f1(); drop function g1(); create function g1(out a int, out b int) returns setof record as $$ select * from t1; $$ language sql; create function f1() returns void as $$ declare r record; begin for r in select * from g1() loop raise notice '%', r.c; end loop; end; $$ language plpgsql; select f1(); f1 ---- (1 row) select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+-----------------------------+--------+------+-------+----------+-------+---------------------- f1 | 6 | RAISE | 42703 | record "r" has no field "c" | | | error | | | SQL expression "r.c" (1 row) select f1(); f1 ---- (1 row) create or replace function f1() returns void as $$ declare r record; begin for r in select * from g1() loop r.c := 20; end loop; end; $$ language plpgsql; select f1(); f1 ---- (1 row) select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+------------+----------+-----------------------------+--------+------+-------+----------+-------+--------------------------------------------------------------- f1 | 6 | assignment | 42703 | record "r" has no field "c" | | | error | | | at assignment to field "c" of variable "r" declared on line 2 (1 row) select f1(); f1 ---- (1 row) drop function f1(); drop function g1(); create function f1() returns int as $$ declare r int; begin if false then r := a + b; end if; return r; end; $$ language plpgsql; select f1(); f1 ---- (1 row) select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+------------+----------+---------------------------+--------+------+-------+----------+------------+-------------------------------------------------- f1 | 5 | assignment | 42703 | column "a" does not exist | | | error | 6 | r := a + b | at assignment to variable "r" declared on line 2 (1 row) select f1(); f1 ---- (1 row) drop function f1(); create or replace function f1() returns void as $$ declare r int[]; begin if false then r[c+10] := 20; end if; end; $$ language plpgsql; select f1(); f1 ---- (1 row) select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+------------+----------+---------------------------+--------+------+-------+----------+---------------+-------------------------------------------------- f1 | 5 | assignment | 42703 | column "c" does not exist | | | error | 3 | r[c+10] := 20 | at assignment to variable "r" declared on line 2 (1 row) select f1(); f1 ---- (1 row) drop function f1(); create or replace function f1() returns void as $$ declare r int; begin if false then r[10] := 20; end if; end; $$ language plpgsql set search_path = public; select f1(); f1 ---- (1 row) select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+------------+----------+------------------------------------------------------------------------+--------+------+-------+----------+-------------+-------------------------------------------------- f1 | 5 | assignment | 42804 | cannot subscript type integer because it does not support subscripting | | | error | 1 | r[10] := 20 | at assignment to variable "r" declared on line 2 (1 row) select f1(); f1 ---- (1 row) drop function f1(); create or replace function f1_trg() returns trigger as $$ begin if new.a > 10 then raise notice '%', new.b; raise notice '%', new.c; end if; return new; end; $$ language plpgsql; create trigger t1_f1 before insert on t1 for each row execute procedure f1_trg(); insert into t1 values(6,30); select * from plpgsql_check_function_tb('f1_trg()','t1'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+-------------------------------+--------+------+-------+----------+-------+------------------------ f1_trg | 5 | RAISE | 42703 | record "new" has no field "c" | | | error | | | SQL expression "new.c" (1 row) insert into t1 values(6,30); create or replace function f1_trg() returns trigger as $$ begin new.a := new.a + 10; new.b := new.b + 10; new.c := 30; return new; end; $$ language plpgsql; -- should to fail select * from plpgsql_check_function_tb('f1_trg()','t1'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+------------+----------+-------------------------------+--------+------+-------+----------+-------+----------------------------------------------------------------- f1_trg | 5 | assignment | 42703 | record "new" has no field "c" | | | error | | | at assignment to field "c" of variable "new" declared on line 0 (1 row) -- should to fail but not crash insert into t1 values(6,30); ERROR: record "new" has no field "c" CONTEXT: PL/pgSQL assignment "new.c := 30" PL/pgSQL function f1_trg() line 5 at assignment create or replace function f1_trg() returns trigger as $$ begin new.a := new.a + 10; new.b := new.b + 10; return new; end; $$ language plpgsql; -- ok select * from plpgsql_check_function_tb('f1_trg()', 't1'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+---------+--------+------+-------+----------+-------+--------- (0 rows) -- ok insert into t1 values(6,30); create or replace function f1_trg() returns trigger as $$ begin new.a := new.a + 10; new.b := new.b + 10; return null; end; $$ language plpgsql; -- ok select * from plpgsql_check_function_tb('f1_trg()', 't1'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+---------+--------+------+-------+----------+-------+--------- (0 rows) insert into t1 values(60,300); select * from t1; a | b ----+---- 6 | 30 6 | 30 16 | 40 (3 rows) insert into t1 values(600,30); select * from t1; a | b ----+---- 6 | 30 6 | 30 16 | 40 (3 rows) drop trigger t1_f1 on t1; drop function f1_trg(); -- test of showing caret on correct place for multiline queries create or replace function f1() returns void as $$ begin select var from foo; end; $$ language plpgsql; select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+---------------+----------+-------------------------------+--------+------+-------+----------+--------+--------- f1 | 3 | SQL statement | 42P01 | relation "foo" does not exist | | | error | 23 | select+| | | | | | | | | | var +| | | | | | | | | | from+| | | | | | | | | | foo | (1 row) drop function f1(); create or replace function f1() returns int as $$ begin return (select a from t1 where hh = 20); end; $$ language plpgsql; select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+----------------------------+--------+------+-------+----------+----------------------------+--------- f1 | 3 | RETURN | 42703 | column "hh" does not exist | | | error | 50 | (select a +| | | | | | | | | | from t1 +| | | | | | | | | | where hh = 20) | (1 row) create or replace function f1() returns int as $$ begin return (select a from txxxxxxx where hh = 20); end; $$ language plpgsql; select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+------------------------------------+--------+------+-------+----------+----------------------------+--------- f1 | 3 | RETURN | 42P01 | relation "txxxxxxx" does not exist | | | error | 29 | (select a +| | | | | | | | | | from txxxxxxx+| | | | | | | | | | where hh = 20) | (1 row) drop function f1(); drop table t1; -- raise warnings when target row has different number of attributies in -- SELECT INTO statement create or replace function f1() returns void as $$ declare a1 int; a2 int; begin select 10,20 into a1,a2; end; $$ language plpgsql; -- should be ok select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+--------------------------+--------+------+---------------+----------+-------+--------- f1 | 2 | DECLARE | 00000 | never read variable "a1" | | | warning extra | | | f1 | 2 | DECLARE | 00000 | never read variable "a2" | | | warning extra | | | (2 rows) create or replace function f1() returns void as $$ declare a1 int; begin select 10,20 into a1; end; $$ language plpgsql; -- raise warning select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+---------------+----------+------------------------------------------+---------------------------------------------------------------+-------------------------------------------------+---------------+----------+-------+--------- f1 | 4 | SQL statement | 00000 | too many attributes for target variables | There are less target variables than output columns in query. | Check target variables in SELECT INTO statement | warning | | | f1 | 2 | DECLARE | 00000 | never read variable "a1" | | | warning extra | | | (2 rows) create or replace function f1() returns void as $$ declare a1 int; a2 int; begin select 10 into a1,a2; end; $$ language plpgsql; -- raise warning select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+---------------+----------+-----------------------------------------+---------------------------------------------------------------+--------------------------------------------------+---------------+----------+-------+--------- f1 | 4 | SQL statement | 00000 | too few attributes for target variables | There are more target variables than output columns in query. | Check target variables in SELECT INTO statement. | warning | | | f1 | 2 | DECLARE | 00000 | never read variable "a1" | | | warning extra | | | f1 | 2 | DECLARE | 00000 | never read variable "a2" | | | warning extra | | | (3 rows) -- bogus code set check_function_bodies to off; create or replace function f1() returns void as $$ adasdfsadf $$ language plpgsql; select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+--------------------------------------+--------+------+-------+----------+------------+--------------------------------------------------- f1 | | | 42601 | syntax error at or near "adasdfsadf" | | | error | 2 | +| compilation of PL/pgSQL function "f1" near line 1 | | | | | | | | | adasdfsadf+| | | | | | | | | | | (1 row) drop function f1(); create table t1(a int, b int); create function g1(out a int, out b int) as $$ select 10,20; $$ language sql; create function f1() returns void as $$ declare r record; begin r := g1(); if false then raise notice '%', r.c; end if; end; $$ language plpgsql; select f1(); f1 ---- (1 row) select * from plpgsql_check_function('f1()'); plpgsql_check_function ------------------------------------------------- error:42703:6:RAISE:record "r" has no field "c" Context: SQL expression "r.c" (2 rows) select f1(); f1 ---- (1 row) drop function f1(); drop function g1(); create function g1(out a int, out b int) returns setof record as $$ select * from t1; $$ language sql; create function f1() returns void as $$ declare r record; begin for r in select * from g1() loop raise notice '%', r.c; end loop; end; $$ language plpgsql; select f1(); f1 ---- (1 row) select * from plpgsql_check_function('f1()'); plpgsql_check_function ------------------------------------------------- error:42703:6:RAISE:record "r" has no field "c" Context: SQL expression "r.c" (2 rows) select f1(); f1 ---- (1 row) create or replace function f1() returns void as $$ declare r record; begin for r in select * from g1() loop r.c := 20; end loop; end; $$ language plpgsql; select f1(); f1 ---- (1 row) select * from plpgsql_check_function('f1()'); plpgsql_check_function ------------------------------------------------------------------------ error:42703:6:assignment:record "r" has no field "c" Context: at assignment to field "c" of variable "r" declared on line 2 (2 rows) select f1(); f1 ---- (1 row) drop function f1(); drop function g1(); create function f1() returns int as $$ declare r int; begin if false then r := a + b; end if; return r; end; $$ language plpgsql; select f1(); f1 ---- (1 row) select * from plpgsql_check_function('f1()'); plpgsql_check_function ----------------------------------------------------------- error:42703:5:assignment:column "a" does not exist Query: r := a + b -- ^ Context: at assignment to variable "r" declared on line 2 (4 rows) select f1(); f1 ---- (1 row) drop function f1(); create or replace function f1() returns void as $$ declare r int[]; begin if false then r[c+10] := 20; end if; end; $$ language plpgsql; select f1(); f1 ---- (1 row) select * from plpgsql_check_function('f1()'); plpgsql_check_function ----------------------------------------------------------- error:42703:5:assignment:column "c" does not exist Query: r[c+10] := 20 -- ^ Context: at assignment to variable "r" declared on line 2 (4 rows) select f1(); f1 ---- (1 row) drop function f1(); create or replace function f1() returns void as $$ declare r int; begin if false then r[10] := 20; end if; end; $$ language plpgsql set search_path = public; select f1(); f1 ---- (1 row) select * from plpgsql_check_function('f1()'); plpgsql_check_function ------------------------------------------------------------------------------------------------- error:42804:5:assignment:cannot subscript type integer because it does not support subscripting Query: r[10] := 20 -- ^ Context: at assignment to variable "r" declared on line 2 (4 rows) select f1(); f1 ---- (1 row) drop function f1(); create or replace function f1_trg() returns trigger as $$ begin if new.a > 10 then raise notice '%', new.b; raise notice '%', new.c; end if; return new; end; $$ language plpgsql; create trigger t1_f1 before insert on t1 for each row execute procedure f1_trg(); insert into t1 values(6,30); select * from plpgsql_check_function('f1_trg()','t1'); plpgsql_check_function --------------------------------------------------- error:42703:5:RAISE:record "new" has no field "c" Context: SQL expression "new.c" (2 rows) insert into t1 values(6,30); create or replace function f1_trg() returns trigger as $$ begin new.a := new.a + 10; new.b := new.b + 10; new.c := 30; return new; end; $$ language plpgsql; -- should to fail select * from plpgsql_check_function('f1_trg()','t1'); plpgsql_check_function -------------------------------------------------------------------------- error:42703:5:assignment:record "new" has no field "c" Context: at assignment to field "c" of variable "new" declared on line 0 (2 rows) -- should to fail but not crash insert into t1 values(6,30); ERROR: record "new" has no field "c" CONTEXT: PL/pgSQL assignment "new.c := 30" PL/pgSQL function f1_trg() line 5 at assignment create or replace function f1_trg() returns trigger as $$ begin new.a := new.a + 10; new.b := new.b + 10; return new; end; $$ language plpgsql; -- ok select * from plpgsql_check_function('f1_trg()', 't1'); plpgsql_check_function ------------------------ (0 rows) -- ok insert into t1 values(6,30); select * from t1; a | b ----+---- 6 | 30 6 | 30 16 | 40 (3 rows) drop trigger t1_f1 on t1; drop function f1_trg(); -- test of showing caret on correct place for multiline queries create or replace function f1() returns void as $$ begin select var from foo; end; $$ language plpgsql; select * from plpgsql_check_function('f1()'); plpgsql_check_function ----------------------------------------------------------- error:42P01:3:SQL statement:relation "foo" does not exist Query: select var from foo -- ^ (6 rows) drop function f1(); create or replace function f1() returns int as $$ begin return (select a from t1 where hh = 20); end; $$ language plpgsql; select * from plpgsql_check_function('f1()'); plpgsql_check_function ------------------------------------------------- error:42703:3:RETURN:column "hh" does not exist Query: (select a from t1 where hh = 20) -- ^ (5 rows) create or replace function f1() returns int as $$ begin return (select a from txxxxxxx where hh = 20); end; $$ language plpgsql; select * from plpgsql_check_function('f1()'); plpgsql_check_function --------------------------------------------------------- error:42P01:3:RETURN:relation "txxxxxxx" does not exist Query: (select a from txxxxxxx -- ^ where hh = 20) (5 rows) drop function f1(); drop table t1; -- raise warnings when target row has different number of attributies in -- SELECT INTO statement create or replace function f1() returns void as $$ declare a1 int; a2 int; begin select 10,20 into a1,a2; end; $$ language plpgsql; -- should be ok select * from plpgsql_check_function('f1()'); plpgsql_check_function -------------------------------------------------------- warning extra:00000:2:DECLARE:never read variable "a1" warning extra:00000:2:DECLARE:never read variable "a2" (2 rows) create or replace function f1() returns void as $$ declare a1 int; begin select 10,20 into a1; end; $$ language plpgsql; -- raise warning select * from plpgsql_check_function('f1()'); plpgsql_check_function ------------------------------------------------------------------------ warning:00000:4:SQL statement:too many attributes for target variables Detail: There are less target variables than output columns in query. Hint: Check target variables in SELECT INTO statement warning extra:00000:2:DECLARE:never read variable "a1" (4 rows) create or replace function f1() returns void as $$ declare a1 int; a2 int; begin select 10 into a1,a2; end; $$ language plpgsql; -- raise warning select * from plpgsql_check_function('f1()'); plpgsql_check_function ----------------------------------------------------------------------- warning:00000:4:SQL statement:too few attributes for target variables Detail: There are more target variables than output columns in query. Hint: Check target variables in SELECT INTO statement. warning extra:00000:2:DECLARE:never read variable "a1" warning extra:00000:2:DECLARE:never read variable "a2" (5 rows) -- bogus code set check_function_bodies to off; create or replace function f1() returns void as $$ adasdfsadf $$ language plpgsql; select * from plpgsql_check_function('f1()'); plpgsql_check_function ------------------------------------------------------------ error:42601:syntax error at or near "adasdfsadf" Query: adasdfsadf -- ^ Context: compilation of PL/pgSQL function "f1" near line 1 (6 rows) drop function f1(); create table f1tbl(a int, b int); -- unused variables create or replace function f1(_input1 int) returns table(_output1 int, _output2 int) as $$ declare _f1 int; _f2 int; _f3 int; _f4 int; _f5 int; _r record; _tbl f1tbl; begin if true then _f1 := 1; end if; select 1, 2 into _f3, _f4; perform 1 where _f5 is null; select 1 into _r; select 1, 2 into _tbl; -- check that SQLSTATE and SQLERRM don't raise false positives begin exception when raise_exception then end; end $$ language plpgsql; select * from plpgsql_check_function('f1(int)'); plpgsql_check_function ---------------------------------------------------------- warning:00000:4:DECLARE:unused variable "_f2" warning extra:00000:3:DECLARE:never read variable "_f1" warning extra:00000:5:DECLARE:never read variable "_f3" warning extra:00000:6:DECLARE:never read variable "_f4" warning extra:00000:8:DECLARE:never read variable "_r" warning extra:00000:9:DECLARE:never read variable "_tbl" warning extra:00000:unused parameter "_input1" warning extra:00000:unmodified OUT variable "_output1" warning extra:00000:unmodified OUT variable "_output2" (9 rows) drop function f1(int); drop table f1tbl; -- check that NEW and OLD are not reported unused create table f1tbl(); create or replace function f1() returns trigger as $$ begin return null; end $$ language plpgsql; select * from plpgsql_check_function('f1()', 'f1tbl'); plpgsql_check_function ------------------------ (0 rows) drop function f1(); drop table f1tbl; create table tabret(a int, b int); insert into tabret values(10,10); create or replace function f1() returns int as $$ begin return (select a from tabret); end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------------- performance:00000:routine is marked as VOLATILE, should be STABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (2 rows) create or replace function f1() returns int as $$ begin return (select a::numeric from tabret); end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------------- performance:42804:3:RETURN:target type is different type than source type Detail: cast "numeric" value to "integer" type Hint: Hidden casting can be a performance issue. performance:00000:routine is marked as VOLATILE, should be STABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (5 rows) create or replace function f1() returns int as $$ begin return (select a, b from tabret); end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------- error:42601:3:RETURN:subquery must return only one column Query: (select a, b from tabret) -- ^ (3 rows) drop function f1(); create or replace function f1() returns table(ax int, bx int) as $$ begin return query select * from tabret; return; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------------- performance:00000:routine is marked as VOLATILE, should be STABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (2 rows) drop function f1(); create or replace function f1() returns table(ax numeric, bx numeric) as $$ begin return query select * from tabret; return; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------- error:42804:3:RETURN QUERY:structure of query does not match function result type Detail: Returned type integer does not match expected type numeric in column 1. (2 rows) drop function f1(); create or replace function f1() returns setof tabret as $$ begin return query select * from tabret; return; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------------- performance:00000:routine is marked as VOLATILE, should be STABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (2 rows) create or replace function f1() returns setof tabret as $$ begin return query select a from tabret; return; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------- error:42804:3:RETURN QUERY:structure of query does not match function result type Detail: Number of returned columns (1) does not match expected column count (2). (2 rows) create or replace function f1() returns setof tabret as $$ begin return query select a::numeric,b::numeric from tabret; return; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------- error:42804:3:RETURN QUERY:structure of query does not match function result type Detail: Returned type numeric does not match expected type integer in column 1. (2 rows) drop function f1(); create or replace function f1(a int) returns setof numeric as $$ begin return query select a; end $$ language plpgsql; select * from plpgsql_check_function('f1(int)', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------- error:42804:2:RETURN QUERY:structure of query does not match function result type Detail: Returned type integer does not match expected type numeric in column 1. (2 rows) drop function f1(int); drop table tabret; create or replace function f1() returns void as $$ declare intval integer; begin intval := null; -- ok intval := 1; -- OK intval := '1'; -- OK intval := text '1'; -- not OK intval := current_date; -- not OK select 1 into intval; -- OK select '1' into intval; -- OK select text '1' into intval; -- not OK end $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); plpgsql_check_function -------------------------------------------------------------------------------------- warning:42804:9:assignment:target type is different type than source type Detail: cast "date" value to "integer" type Hint: There are no possible explicit coercion between those types, possibly bug! Context: at assignment to variable "intval" declared on line 3 warning:42804:12:SQL statement:target type is different type than source type Detail: cast "text" value to "integer" type Hint: The input expression type does not have an assignment cast to the target type. warning:42804:13:SQL statement:target type is different type than source type Detail: cast "text" value to "integer" type Hint: The input expression type does not have an assignment cast to the target type. warning extra:00000:3:DECLARE:never read variable "intval" (11 rows) drop function f1(); create or replace function f1() returns int as $$ begin return 1; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------------- performance:00000:routine is marked as VOLATILE, should be IMMUTABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (2 rows) create or replace function f1() returns int as $$ begin return 1::numeric; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------------- performance:42804:3:RETURN:target type is different type than source type Detail: cast "numeric" value to "integer" type Hint: Hidden casting can be a performance issue. performance:00000:routine is marked as VOLATILE, should be IMMUTABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (5 rows) create or replace function f1() returns int as $$ begin return null; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------------- performance:00000:routine is marked as VOLATILE, should be IMMUTABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (2 rows) create or replace function f1() returns int as $$ begin return current_date; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------------- warning:42804:3:RETURN:target type is different type than source type Detail: cast "date" value to "integer" type Hint: There are no possible explicit coercion between those types, possibly bug! performance:00000:routine is marked as VOLATILE, should be STABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (5 rows) create or replace function f1() returns int as $$ declare a int; begin return a; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------------- performance:00000:routine is marked as VOLATILE, should be IMMUTABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (2 rows) create or replace function f1() returns int as $$ declare a numeric; begin return a; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------------- performance:42804:4:RETURN:target type is different type than source type Detail: cast "numeric" value to "integer" type Hint: Hidden casting can be a performance issue. performance:00000:routine is marked as VOLATILE, should be IMMUTABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (5 rows) drop function f1(); create or replace function f1() returns setof int as $$ begin return next 1; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------------- performance:00000:routine is marked as VOLATILE, should be IMMUTABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (2 rows) create or replace function f1() returns setof int as $$ begin return next 1::numeric; -- tolerant, doesn't use tupmap end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------------- performance:42804:3:RETURN NEXT:target type is different type than source type Detail: cast "numeric" value to "integer" type Hint: Hidden casting can be a performance issue. performance:00000:routine is marked as VOLATILE, should be IMMUTABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (5 rows) drop function f1(); create type t1 as (a int, b int, c int); create type t2 as (a int, b numeric); create or replace function fx() returns t2 as $$ declare x t1; begin return x; end; $$ language plpgsql; select * from plpgsql_check_function('fx()', performance_warnings := true); plpgsql_check_function --------------------------------------------------------------------------------- error:42804:4:RETURN:returned record type does not match expected record type Detail: Returned type integer does not match expected type numeric in column 2. (2 rows) create or replace function fx() returns t2 as $$ declare x t2; begin return x; end; $$ language plpgsql; select * from plpgsql_check_function('fx()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------------- performance:00000:routine is marked as VOLATILE, should be IMMUTABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (2 rows) drop function fx(); create or replace function fx() returns setof t2 as $$ declare x t1; begin return next x; return; end; $$ language plpgsql; select * from plpgsql_check_function('fx()', performance_warnings := true); plpgsql_check_function --------------------------------------------------------------------------------- error:42804:4:RETURN NEXT:wrong record type supplied in RETURN NEXT Detail: Returned type integer does not match expected type numeric in column 2. (2 rows) create or replace function fx() returns setof t2 as $$ declare x t2; begin return next x; return; end; $$ language plpgsql; select * from plpgsql_check_function('fx()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------------- performance:00000:routine is marked as VOLATILE, should be IMMUTABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (2 rows) drop function fx(); create or replace function fx2(_id int, _pa_id varchar(32), _status varchar(60)) returns void as $$ declare begin insert into pa values(_id, _pa_id, _status); exception when OTHERS then raise notice '%', 'some message'; raise exception '%', sqlerrm; end $$ language plpgsql; select * from plpgsql_check_function('fx2(int, varchar, varchar)', performance_warnings := true); plpgsql_check_function ------------------------ (0 rows) create or replace function fx2(_id int, _pa_id varchar(32), _status varchar(60)) returns void as $$ declare begin insert into pa values(_id, _pa_id, _status) returning *; exception when OTHERS then raise notice '%', 'some message'; raise exception '%', sqlerrm; end $$ language plpgsql; select * from plpgsql_check_function('fx2(int, varchar, varchar)', performance_warnings := true); plpgsql_check_function ---------------------------------------------------------------------- error:42601:4:SQL statement:query has no destination for result data (1 row) create or replace function fx2(_id int, _pa_id varchar(32), _status varchar(60)) returns void as $$ declare begin SELECT * FROM pa LIMIT 1; exception when OTHERS then raise notice '%', 'some message'; raise exception '%', sqlerrm; end $$ language plpgsql; select * from plpgsql_check_function('fx2(int, varchar, varchar)', performance_warnings := true); plpgsql_check_function ---------------------------------------------------------------------- error:42601:4:SQL statement:query has no destination for result data (1 row) drop function fx2(int, varchar, varchar); create or replace function foreach_array_loop() returns void as $body$ declare arr text[]; el text; begin arr := array['1111','2222','3333']; foreach el in array arr loop raise notice '%', el; end loop; end; $body$ language 'plpgsql' stable; select * from plpgsql_check_function_tb('foreach_array_loop()', performance_warnings := true); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context --------------------+--------+-----------+----------+--------------------------------------------------+--------+-----------------------------------------------------------------------------------+-------------+----------+-------+--------- foreach_array_loop | | | 00000 | routine is marked as STABLE, should be IMMUTABLE | | When you fix this issue, please, recheck other functions that uses this function. | performance | | | (1 row) create or replace function foreach_array_loop() returns void as $body$ declare arr text[]; el int; begin arr := array['1111','2222','3333']; foreach el in array arr loop raise notice '%', el; end loop; end; $body$ language 'plpgsql' stable; select * from plpgsql_check_function_tb('foreach_array_loop()', performance_warnings := true); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context --------------------+--------+--------------------+----------+--------------------------------------------------+-------------------------------------+-----------------------------------------------------------------------------------+-------------+----------+-------+--------- foreach_array_loop | 7 | FOREACH over array | 42804 | target type is different type than source type | cast "text" value to "integer" type | The input expression type does not have an assignment cast to the target type. | warning | | | foreach_array_loop | | | 00000 | routine is marked as STABLE, should be IMMUTABLE | | When you fix this issue, please, recheck other functions that uses this function. | performance | | | (2 rows) create or replace function foreach_array_loop() returns void as $body$ declare arr date[]; el int; begin arr := array['2014-01-01','2015-01-01','2016-01-01']::date[]; foreach el in array arr loop raise notice '%', el; end loop; end; $body$ language 'plpgsql' stable; select * from plpgsql_check_function_tb('foreach_array_loop()', performance_warnings := true); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context --------------------+--------+--------------------+----------+--------------------------------------------------+-------------------------------------+-----------------------------------------------------------------------------------+-------------+----------+-------+--------- foreach_array_loop | 7 | FOREACH over array | 42804 | target type is different type than source type | cast "date" value to "integer" type | There are no possible explicit coercion between those types, possibly bug! | warning | | | foreach_array_loop | | | 00000 | routine is marked as STABLE, should be IMMUTABLE | | When you fix this issue, please, recheck other functions that uses this function. | performance | | | (2 rows) create or replace function foreach_array_loop() returns void as $body$ declare el text; begin foreach el in array array['1111','2222','3333'] loop raise notice '%', el; end loop; end; $body$ language 'plpgsql' stable; select * from plpgsql_check_function_tb('foreach_array_loop()', performance_warnings := true); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context --------------------+--------+-----------+----------+--------------------------------------------------+--------+-----------------------------------------------------------------------------------+-------------+----------+-------+--------- foreach_array_loop | | | 00000 | routine is marked as STABLE, should be IMMUTABLE | | When you fix this issue, please, recheck other functions that uses this function. | performance | | | (1 row) create or replace function foreach_array_loop() returns void as $body$ declare el int; begin foreach el in array array['1111','2222','3333'] loop raise notice '%', el; end loop; end; $body$ language 'plpgsql' stable; select * from plpgsql_check_function_tb('foreach_array_loop()', performance_warnings := true); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context --------------------+--------+--------------------+----------+--------------------------------------------------+-------------------------------------+-----------------------------------------------------------------------------------+-------------+----------+-------+--------- foreach_array_loop | 5 | FOREACH over array | 42804 | target type is different type than source type | cast "text" value to "integer" type | The input expression type does not have an assignment cast to the target type. | warning | | | foreach_array_loop | | | 00000 | routine is marked as STABLE, should be IMMUTABLE | | When you fix this issue, please, recheck other functions that uses this function. | performance | | | (2 rows) create or replace function foreach_array_loop() returns void as $body$ declare el int; begin foreach el in array array['2014-01-01','2015-01-01','2016-01-01']::date[] loop raise notice '%', el; end loop; end; $body$ language 'plpgsql' stable; select * from plpgsql_check_function_tb('foreach_array_loop()', performance_warnings := true); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context --------------------+--------+--------------------+----------+--------------------------------------------------+-------------------------------------+-----------------------------------------------------------------------------------+-------------+----------+-------+--------- foreach_array_loop | 5 | FOREACH over array | 42804 | target type is different type than source type | cast "date" value to "integer" type | There are no possible explicit coercion between those types, possibly bug! | warning | | | foreach_array_loop | | | 00000 | routine is marked as STABLE, should be IMMUTABLE | | When you fix this issue, please, recheck other functions that uses this function. | performance | | | (2 rows) drop function foreach_array_loop(); create or replace function scan_rows(int[]) returns void AS $$ declare x int[]; begin foreach x slice 1 in array $1 loop raise notice 'row = %', x; end loop; end; $$ language plpgsql; select * from plpgsql_check_function_tb('scan_rows(int[])', performance_warnings := true); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+----------------------------------------------------+--------+-----------------------------------------------------------------------------------+-------------+----------+-------+--------- scan_rows | | | 00000 | routine is marked as VOLATILE, should be IMMUTABLE | | When you fix this issue, please, recheck other functions that uses this function. | performance | | | (1 row) create or replace function scan_rows(int[]) returns void AS $$ declare x int[]; begin foreach x in array $1 loop raise notice 'row = %', x; end loop; end; $$ language plpgsql; select * from plpgsql_check_function_tb('scan_rows(int[])', performance_warnings := true); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+--------------------+----------+----------------------------------------------------+------------------------------------------+-----------------------------------------------------------------------------------+-------------+----------+-------+--------- scan_rows | 5 | FOREACH over array | 42804 | target type is different type than source type | cast "integer" value to "integer[]" type | There are no possible explicit coercion between those types, possibly bug! | warning | | | scan_rows | | | 00000 | routine is marked as VOLATILE, should be IMMUTABLE | | When you fix this issue, please, recheck other functions that uses this function. | performance | | | (2 rows) drop function scan_rows(int[]); drop function fx(); ERROR: function fx() does not exist drop type t1; drop type t2; create table t1(a int, b int); create table t2(a int, b int, c int); create table t3(a numeric, b int); insert into t1 values(10,20),(30,40); create or replace function fx() returns int as $$ declare s int default 0; r t1; begin foreach r in array (select array_agg(t1) from t1) loop s := r.a + r.b; end loop; return s; end; $$ language plpgsql; select * from plpgsql_check_function_tb('fx()', performance_warnings := true); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+-------------------------------------------------+--------+-----------------------------------------------------------------------------------+-------------+----------+-------+--------- fx | | | 00000 | routine is marked as VOLATILE, should be STABLE | | When you fix this issue, please, recheck other functions that uses this function. | performance | | | (1 row) create or replace function fx() returns int as $$ declare s int default 0; r t1; c t1[]; begin c := (select array_agg(t1) from t1); foreach r in array c loop s := r.a + r.b; end loop; return s; end; $$ language plpgsql; select * from plpgsql_check_function_tb('fx()', performance_warnings := true); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+-------------------------------------------------+--------+-----------------------------------------------------------------------------------+-------------+----------+-------+--------- fx | | | 00000 | routine is marked as VOLATILE, should be STABLE | | When you fix this issue, please, recheck other functions that uses this function. | performance | | | (1 row) create or replace function fx() returns int as $$ declare s int default 0; r t1; c t1[]; begin select array_agg(t1) into c from t1; foreach r in array c loop s := r.a + r.b; end loop; return s; end; $$ language plpgsql; select * from plpgsql_check_function_tb('fx()', performance_warnings := true); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+-------------------------------------------------+--------+-----------------------------------------------------------------------------------+-------------+----------+-------+--------- fx | | | 00000 | routine is marked as VOLATILE, should be STABLE | | When you fix this issue, please, recheck other functions that uses this function. | performance | | | (1 row) create or replace function fx() returns int as $$ declare s int default 0; r t1; c t1[]; begin select array_agg(t1) into c from t1; for i in array_lower(c, 1) .. array_upper(c, 1) loop r := c[i]; s := r.a + r.b; end loop; return s; end; $$ language plpgsql; select * from plpgsql_check_function_tb('fx()', performance_warnings := true); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+-------------------------------------------------+--------+-----------------------------------------------------------------------------------+-------------+----------+-------+--------- fx | | | 00000 | routine is marked as VOLATILE, should be STABLE | | When you fix this issue, please, recheck other functions that uses this function. | performance | | | (1 row) create or replace function fx() returns int as $$ declare s int default 0; c t1[]; begin select array_agg(t1) into c from t1; for i in array_lower(c, 1) .. array_upper(c, 1) loop s := (c[i]).a + (c[i]).b; end loop; return s; end; $$ language plpgsql; select * from plpgsql_check_function_tb('fx()', performance_warnings := true); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+-------------------------------------------------+--------+-----------------------------------------------------------------------------------+-------------+----------+-------+--------- fx | | | 00000 | routine is marked as VOLATILE, should be STABLE | | When you fix this issue, please, recheck other functions that uses this function. | performance | | | (1 row) create or replace function fx() returns int as $$ declare s int default 0; r record; c t1[]; begin select array_agg(t1) into c from t1; for i in array_lower(c, 1) .. array_upper(c, 1) loop r := c[i]; s := r.a + r.b; end loop; return s; end; $$ language plpgsql; select * from plpgsql_check_function_tb('fx()', performance_warnings := true); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+-------------------------------------------------+--------+-----------------------------------------------------------------------------------+-------------+----------+-------+--------- fx | | | 00000 | routine is marked as VOLATILE, should be STABLE | | When you fix this issue, please, recheck other functions that uses this function. | performance | | | (1 row) create or replace function fx() returns int as $$ declare s int default 0; r record; c t1[]; begin select array_agg(t1) into c from t1; for i in array_lower(c, 1) .. array_upper(c, 1) loop r := c[i]; s := r.a + r.b + r.c; end loop; return s; end; $$ language plpgsql; select * from plpgsql_check_function_tb('fx()', performance_warnings := true); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+------------+----------+-----------------------------+--------+------+-------+----------+-------+-------------------------------------------- fx | 11 | assignment | 42703 | record "r" has no field "c" | | | error | | | PL/pgSQL assignment "s := r.a + r.b + r.c" (1 row) create or replace function fx() returns int as $$ declare s int default 0; r t2; begin foreach r in array (select array_agg(t1) from t1) loop s := r.a + r.b; end loop; return s; end; $$ language plpgsql; select * from plpgsql_check_function_tb('fx()', performance_warnings := true); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+--------------------+----------+-------------------------------------------------+--------+-----------------------------------------------------------------------------------+-------------+----------+-------+--------- fx | 6 | FOREACH over array | 00000 | too few attributes for composite variable | | | warning | | | fx | | | 00000 | routine is marked as VOLATILE, should be STABLE | | When you fix this issue, please, recheck other functions that uses this function. | performance | | | (2 rows) create or replace function fx() returns int as $$ declare s int default 0; r t3; begin foreach r in array (select array_agg(t1) from t1) loop s := r.a + r.b; end loop; return s; end; $$ language plpgsql; select * from plpgsql_check_function_tb('fx()', performance_warnings := true); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+--------------------+----------+-------------------------------------------------+----------------------------------------+-----------------------------------------------------------------------------------+-------------+----------+-------+-------------------------------------------------- fx | 6 | FOREACH over array | 42804 | target type is different type than source type | cast "integer" value to "numeric" type | Hidden casting can be a performance issue. | performance | | | fx | 8 | assignment | 42804 | target type is different type than source type | cast "numeric" value to "integer" type | Hidden casting can be a performance issue. | performance | | | at assignment to variable "s" declared on line 3 fx | | | 00000 | routine is marked as VOLATILE, should be STABLE | | When you fix this issue, please, recheck other functions that uses this function. | performance | | | (3 rows) drop function fx(); drop table t1; -- mscottie issue #13 create table test ( a text, b integer, c uuid ); create function before_insert_test() returns trigger language plpgsql as $$ begin select a into NEW.a from test where b = 1; select b into NEW.b from test where b = 1; select null::uuid into NEW.c from test where b = 1; return new; end; $$; select * from plpgsql_check_function_tb('before_insert_test()','test'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+---------+--------+------+-------+----------+-------+--------- (0 rows) create or replace function before_insert_test() returns trigger language plpgsql as $$ begin NEW.a := (select a from test where b = 1); NEW.b := (select b from test where b = 1); NEW.c := (select c from test where b = 1); return new; end; $$; select * from plpgsql_check_function_tb('before_insert_test()','test', fatal_errors := false); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+---------+--------+------+-------+----------+-------+--------- (0 rows) create or replace function before_insert_test() returns trigger language plpgsql as $$ begin NEW.a := 'Hello'::text; NEW.b := 10; NEW.c := null::uuid; return new; end; $$; select * from plpgsql_check_function_tb('before_insert_test()','test', fatal_errors := false); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+---------+--------+------+-------+----------+-------+--------- (0 rows) drop function before_insert_test(); create or replace function fx() returns void as $$ declare NEW test; OLD test; begin select null::uuid into NEW.c from test where b = 1; end; $$ language plpgsql; select * from plpgsql_check_function_tb('fx()', performance_warnings := true, fatal_errors := false); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+---------------------------+--------+------+---------------+----------+-------+--------- fx | 2 | DECLARE | 00000 | unused variable "old" | | | warning | | | fx | 2 | DECLARE | 00000 | never read variable "new" | | | warning extra | | | (2 rows) drop function fx(); create or replace function fx() returns void as $$ declare NEW test; begin NEW.a := 'Hello'::text; NEW.b := 10; NEW.c := null::uuid; end; $$ language plpgsql; select * from plpgsql_check_function_tb('fx()', performance_warnings := true, fatal_errors := false); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+----------------------------------------------------+--------+-----------------------------------------------------------------------------------+---------------+----------+-------+--------- fx | 2 | DECLARE | 00000 | never read variable "new" | | | warning extra | | | fx | | | 00000 | routine is marked as VOLATILE, should be IMMUTABLE | | When you fix this issue, please, recheck other functions that uses this function. | performance | | | (2 rows) drop function fx(); drop table test; create or replace function fx() returns void as $$ declare s int; sa int[]; sd date; bs int[]; begin sa[10] := s; sa[10] := sd; s := bs[10]; end; $$ language plpgsql; select * from plpgsql_check_function_tb('fx()', performance_warnings := true, fatal_errors := false); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+------------+----------+------------------------------------------------+-------------------------------------+----------------------------------------------------------------------------+---------------+----------+-------+--------------------------------------------------- fx | 9 | assignment | 42804 | target type is different type than source type | cast "date" value to "integer" type | There are no possible explicit coercion between those types, possibly bug! | warning | | | at assignment to variable "sa" declared on line 4 fx | 4 | DECLARE | 00000 | never read variable "sa" | | | warning extra | | | (2 rows) drop function fx(); create type t as (t text); create or replace function fx() returns void as $$ declare _t t; _tt t[]; _txt text; begin _t.t := 'ABC'; -- correct warning "unknown" _tt[1] := _t; _txt := _t; end; $$ language plpgsql; select * from plpgsql_check_function('fx()', performance_warnings := true); plpgsql_check_function ------------------------------------------------------------------------------- performance:42804:7:assignment:target type is different type than source type Detail: cast "t" value to "text" type Hint: Hidden casting can be a performance issue. Context: at assignment to variable "_txt" declared on line 3 warning extra:00000:2:DECLARE:never read variable "_tt" warning extra:00000:3:DECLARE:never read variable "_txt" (6 rows) drop function fx(); create or replace function fx() returns void as $$ declare _t1 t; _t2 t; begin _t1.t := 'ABC'::text; _t2 := _t1; raise notice '% %', _t2, _t2.t; end; $$ language plpgsql; select * from plpgsql_check_function('fx()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------------- performance:00000:routine is marked as VOLATILE, should be IMMUTABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (2 rows) drop function fx(); create or replace function fx(out _tt t[]) as $$ declare _t t; begin _t.t := 'ABC'::text; _tt[1] := _t; end; $$ language plpgsql; select * from plpgsql_check_function('fx()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------------- performance:00000:routine is marked as VOLATILE, should be IMMUTABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (2 rows) drop function fx(); drop type t; create or replace function fx() returns int as $$ declare x int; begin perform 1; return 10; end; $$ language plpgsql; select * from plpgsql_check_function('fx()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------------- warning:00000:2:DECLARE:unused variable "x" performance:00000:routine is marked as VOLATILE, should be IMMUTABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (3 rows) drop function fx(); create table t(i int); create function test_t(OUT t) returns t AS $$ begin $1 := null; end; $$ language plpgsql; select test_t(); test_t -------- (1 row) select * from test_t(); i --- (1 row) select * from plpgsql_check_function('test_t()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------------- performance:00000:routine is marked as VOLATILE, should be IMMUTABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (2 rows) create or replace function fx() returns void as $$ declare c cursor for select * from t; x varchar; begin open c; fetch c into x; close c; end; $$ language plpgsql; select test_t(); test_t -------- (1 row) select * from test_t(); i --- (1 row) select * from plpgsql_check_function('fx()', performance_warnings := true, fatal_errors := false); plpgsql_check_function -------------------------------------------------------------------------- performance:42804:7:FETCH:target type is different type than source type Detail: cast "integer" value to "character varying" type Hint: Hidden casting can be a performance issue. warning extra:00000:4:DECLARE:never read variable "x" (4 rows) drop function fx(); create or replace function fx() returns void as $$ declare c cursor for select * from t; x int; begin open c; fetch c into x; close c; end; $$ language plpgsql; select test_t(); test_t -------- (1 row) select * from test_t(); i --- (1 row) select * from plpgsql_check_function('fx()', performance_warnings := true, fatal_errors := false); plpgsql_check_function ------------------------------------------------------- warning extra:00000:4:DECLARE:never read variable "x" (1 row) drop function fx(); create or replace function fx() returns void as $$ declare c cursor for select * from t; begin for r in c loop raise notice '%', r.a; end loop; end; $$ language plpgsql; select test_t(); test_t -------- (1 row) select * from test_t(); i --- (1 row) select * from plpgsql_check_function('fx()', performance_warnings := true, fatal_errors := false); plpgsql_check_function ------------------------------------------------------- error:42703:6:RAISE:record "r" has no field "a" Context: SQL expression "r.a" warning extra:00000:5:DECLARE:never read variable "r" (3 rows) drop function fx(); create or replace function fx() returns void as $$ declare c cursor for select * from t; begin for r in c loop raise notice '%', r.i; end loop; end; $$ language plpgsql; select test_t(); test_t -------- (1 row) select * from test_t(); i --- (1 row) select * from plpgsql_check_function('fx()', performance_warnings := true, fatal_errors := false); plpgsql_check_function ------------------------ (0 rows) drop function fx(); create table foo(a int, b int); create or replace function fx() returns void as $$ declare f1 int; f2 int; begin select 1, 2 into f1; select 1 into f1, f2; select a b into f1, f2 from foo; end; $$ language plpgsql; select fx(); fx ---- (1 row) select * from plpgsql_check_function('fx()', performance_warnings := true, fatal_errors := false); plpgsql_check_function ------------------------------------------------------------------------ warning:00000:4:SQL statement:too many attributes for target variables Detail: There are less target variables than output columns in query. Hint: Check target variables in SELECT INTO statement warning:00000:5:SQL statement:too few attributes for target variables Detail: There are more target variables than output columns in query. Hint: Check target variables in SELECT INTO statement. warning:00000:6:SQL statement:too few attributes for target variables Detail: There are more target variables than output columns in query. Hint: Check target variables in SELECT INTO statement. warning extra:00000:2:DECLARE:never read variable "f1" warning extra:00000:2:DECLARE:never read variable "f2" (11 rows) drop function fx(); drop table foo; create or replace function fx() returns void as $$ declare d date; begin d := (select 1 from pg_class limit 1); raise notice '%', d; end; $$ language plpgsql; select fx(); ERROR: invalid input syntax for type date: "1" CONTEXT: PL/pgSQL assignment "d := (select 1 from pg_class limit 1)" PL/pgSQL function fx() line 4 at assignment select * from plpgsql_check_function('fx()', performance_warnings := true, fatal_errors := false); plpgsql_check_function ---------------------------------------------------------------------------------- warning:42804:4:assignment:target type is different type than source type Detail: cast "integer" value to "date" type Hint: There are no possible explicit coercion between those types, possibly bug! Context: at assignment to variable "d" declared on line 2 (4 rows) drop function fx(); create table tab_1(i int); create or replace function fx(a int) returns setof int as $$ declare c refcursor; r record; begin open c for select i from tab_1 where i = a; loop fetch c into r; if not found then exit; end if; return next r.i; end loop; end; $$ language plpgsql; select * from plpgsql_check_function('fx(int)', performance_warnings := true, fatal_errors := false); plpgsql_check_function ----------------------------------------------------------------------------------------- performance:00000:routine is marked as VOLATILE, should be STABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (2 rows) create or replace function fx(a int) returns setof int as $$ declare c refcursor; r record; begin open c for select i from tab_1 where i = a; loop fetch c into r; if not found then exit; end if; return next r.x; end loop; end; $$ language plpgsql; select * from plpgsql_check_function('fx(int)', performance_warnings := true, fatal_errors := false); plpgsql_check_function ----------------------------------------------------------------------------------------- error:42703:12:RETURN NEXT:record "r" has no field "x" Context: SQL expression "r.x" warning extra:00000:4:DECLARE:never read variable "r" performance:00000:routine is marked as VOLATILE, should be STABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (5 rows) drop function fx(int); drop table tab_1; create or replace function fxx() returns void as $$ begin rollback; end; $$ language plpgsql; select fxx(); ERROR: invalid transaction termination CONTEXT: PL/pgSQL function fxx() line 3 at ROLLBACK select * from plpgsql_check_function('fxx()'); plpgsql_check_function -------------------------------------------------------- error:2D000:3:ROLLBACK:invalid transaction termination (1 row) drop function fxx(); create or replace function fxx() returns void as $$ declare x int; begin declare x int; begin end; end; $$ language plpgsql; select * from plpgsql_check_function('fxx()'); plpgsql_check_function ------------------------------------------------------------------------------------------ warning extra:00000:5:statement block:variable "x" shadows a previously defined variable Hint: SET plpgsql.extra_warnings TO 'shadowed_variables' warning:00000:2:DECLARE:unused variable "x" warning:00000:4:DECLARE:unused variable "x" (4 rows) select * from plpgsql_check_function('fxx()', extra_warnings := false); plpgsql_check_function --------------------------------------------- warning:00000:2:DECLARE:unused variable "x" warning:00000:4:DECLARE:unused variable "x" (2 rows) drop function fxx(); create or replace function fxx(in a int, in b int, out c int, out d int) as $$ begin c := a; end; $$ language plpgsql; select * from plpgsql_check_function('fxx(int, int)'); plpgsql_check_function ------------------------------------------------- warning extra:00000:unused parameter "b" warning extra:00000:unmodified OUT variable "d" (2 rows) create or replace function fxx(in a int, in b int, out c int, out d int) as $$ begin c := d; end; $$ language plpgsql; select * from plpgsql_check_function('fxx(int, int)'); plpgsql_check_function ------------------------------------------------- warning extra:00000:unused parameter "a" warning extra:00000:unused parameter "b" warning extra:00000:unmodified OUT variable "d" (3 rows) create type ct as (a int, b int); create or replace function fxx(a ct, b ct, OUT c ct, OUT d ct) as $$ begin c.a := a.a; end; $$ language plpgsql; select * from plpgsql_check_function('fxx(ct, ct)'); plpgsql_check_function ----------------------------------------------------------------------- warning extra:00000:unused parameter "b" warning extra:00000:composite OUT variable "c" is not single argument warning extra:00000:composite OUT variable "d" is not single argument warning extra:00000:unmodified OUT variable "d" (4 rows) create or replace function fxx(a ct, b ct, OUT c ct, OUT d ct) as $$ begin c.a := d.a; end; $$ language plpgsql; select * from plpgsql_check_function('fxx(ct, ct)'); plpgsql_check_function ----------------------------------------------------------------------- warning extra:00000:unused parameter "a" warning extra:00000:unused parameter "b" warning extra:00000:composite OUT variable "c" is not single argument warning extra:00000:composite OUT variable "d" is not single argument warning extra:00000:unmodified OUT variable "d" (5 rows) create or replace function tx(a int) returns int as $$ declare a int; ax int; begin declare ax int; begin ax := 10; end; a := 10; return 20; end; $$ language plpgsql; select * from plpgsql_check_function('tx(int)'); plpgsql_check_function ------------------------------------------------------------------------------------------- warning:00000:3:statement block:parameter "a" is shadowed Detail: Local variable shadows function parameter. warning extra:00000:5:statement block:variable "ax" shadows a previously defined variable Hint: SET plpgsql.extra_warnings TO 'shadowed_variables' warning:00000:2:DECLARE:unused variable "ax" warning extra:00000:2:DECLARE:never read variable "a" warning extra:00000:4:DECLARE:never read variable "ax" warning extra:00000:unused parameter "a" (8 rows) create type xt as (a int, b int, c int); create or replace function fx_xt(out x xt) as $$ declare l xt; a int; begin return; end; $$ language plpgsql; select * from plpgsql_check_function('fx_xt()'); plpgsql_check_function ------------------------------------------------- warning:00000:2:DECLARE:unused variable "l" warning:00000:3:DECLARE:unused variable "a" warning extra:00000:unmodified OUT variable "x" (3 rows) drop function fx_xt(); create or replace function fx_xt(out x xt) as $$ declare l xt; a int; begin x.c := 1000; return; end; $$ language plpgsql; select * from plpgsql_check_function('fx_xt()'); plpgsql_check_function --------------------------------------------- warning:00000:2:DECLARE:unused variable "l" warning:00000:3:DECLARE:unused variable "a" (2 rows) drop function fx_xt(); create or replace function fx_xt(out x xt, out y xt) as $$ declare c1 xt; c2 xt; begin return; end; $$ language plpgsql; select * from plpgsql_check_function('fx_xt()'); plpgsql_check_function ----------------------------------------------------------------------- warning:00000:2:DECLARE:unused variable "c1" warning:00000:2:DECLARE:unused variable "c2" warning extra:00000:composite OUT variable "x" is not single argument warning extra:00000:unmodified OUT variable "x" warning extra:00000:composite OUT variable "y" is not single argument warning extra:00000:unmodified OUT variable "y" (6 rows) drop function fx_xt(); create or replace function fx_xt(out x xt, out y xt) as $$ declare c1 xt; c2 xt; begin x.a := 100; y := row(10,20,30); return; end; $$ language plpgsql; select * from plpgsql_check_function('fx_xt()'); plpgsql_check_function ----------------------------------------------------------------------- warning:00000:2:DECLARE:unused variable "c1" warning:00000:2:DECLARE:unused variable "c2" warning extra:00000:composite OUT variable "x" is not single argument warning extra:00000:composite OUT variable "y" is not single argument (4 rows) drop function fx_xt(); create or replace function fx_xt(out x xt, out z int) as $$ begin return; end; $$ language plpgsql; select * from plpgsql_check_function('fx_xt()'); plpgsql_check_function ----------------------------------------------------------------------- warning extra:00000:composite OUT variable "x" is not single argument warning extra:00000:unmodified OUT variable "x" warning extra:00000:unmodified OUT variable "z" (3 rows) drop function fx_xt(); drop type xt; -- missing RETURN create or replace function fx_flow() returns int as $$ begin raise notice 'kuku'; end; $$ language plpgsql; select fx_flow(); NOTICE: kuku ERROR: control reached end of function without RETURN CONTEXT: PL/pgSQL function fx_flow() select * from plpgsql_check_function('fx_flow()'); plpgsql_check_function ------------------------------------------------------------ error:2F005:control reached end of function without RETURN (1 row) -- ok create or replace function fx_flow() returns int as $$ declare a int; begin if a > 10 then return a; end if; return 10; end; $$ language plpgsql; select * from plpgsql_check_function('fx_flow()'); plpgsql_check_function ------------------------ (0 rows) -- dead code create or replace function fx_flow() returns int as $$ declare a int; begin if a > 10 then return a; else return a + 1; end if; return 10; end; $$ language plpgsql; select * from plpgsql_check_function('fx_flow()'); plpgsql_check_function ----------------------------------------------- warning extra:00000:9:RETURN:unreachable code (1 row) -- missing return create or replace function fx_flow() returns int as $$ declare a int; begin if a > 10 then return a; end if; end; $$ language plpgsql; select * from plpgsql_check_function('fx_flow()'); plpgsql_check_function -------------------------------------------------------------------- warning extra:2F005:control reached end of function without RETURN (1 row) drop function fx_flow(); create or replace function fx_flow(in p_param1 integer) returns text as $$ declare z1 text; begin if p_param1 is not null then z1 := '1111'; return z1; else z1 := '222222'; end if; return z1; end; $$ language plpgsql stable; select * from plpgsql_check_function_tb('fx_flow(integer)'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+---------+--------+------+-------+----------+-------+--------- (0 rows) create or replace function fx_flow(in p_param1 integer) returns text as $$ declare z1 text; begin if p_param1 is not null then z1 := '1111'; return z1; else z1 := '222222'; raise exception 'stop'; end if; return z1; end; $$ language plpgsql stable; select * from plpgsql_check_function_tb('fx_flow(integer)'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+------------------+--------+------+---------------+----------+-------+--------- fx_flow | 12 | RETURN | 00000 | unreachable code | | | warning extra | | | (1 row) drop function fx_flow(); ERROR: function fx_flow() does not exist drop function fx(int); ERROR: function fx(integer) does not exist create or replace function fx(x int) returns table(y int) as $$ begin return query select x union select x; end $$ language plpgsql; select * from fx(10); y ---- 10 (1 row) select * from plpgsql_check_function_tb('fx(int)'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+---------+--------+------+-------+----------+-------+--------- (0 rows) drop function fx(int); create or replace function fx(x int) returns table(y int, z int) as $$ begin return query select x,x+1 union select x, x+1; end $$ language plpgsql; select * from fx(10); y | z ----+---- 10 | 11 (1 row) select * from plpgsql_check_function_tb('fx(int)'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+---------+--------+------+-------+----------+-------+--------- (0 rows) drop function fx(int); create table xx(a int); create or replace function fx(x int) returns int as $$ declare _a int; begin begin select a from xx into strict _a where a = x; return _a; exception when others then null; end; return -1; end; $$ language plpgsql; select * from plpgsql_check_function_tb('fx(int)'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+---------+--------+------+-------+----------+-------+--------- (0 rows) drop table xx; create or replace function fx(x int) returns int as $$ begin begin if (x > 0) then raise exception 'xxx' using errcode = 'XX888'; else raise exception 'yyy' using errcode = 'YY888'; end if; return -1; -- dead code; end; return -1; end; $$ language plpgsql; select * from plpgsql_check_function_tb('fx(int)'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+------------------+--------+------+---------------+----------+-------+--------- fx | 9 | RETURN | 00000 | unreachable code | | | warning extra | | | fx | 11 | RETURN | 00000 | unreachable code | | | warning extra | | | (2 rows) create or replace function fx(x int) returns int as $$ begin begin if (x > 0) then raise exception 'xxx' using errcode = 'XX888'; else raise exception 'yyy' using errcode = 'YY888'; end if; exception when sqlstate 'XX888' then null; when sqlstate 'YY888' then null; end; end; -- missing return; $$ language plpgsql; select * from plpgsql_check_function_tb('fx(int)'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+------------------------------------------------+--------+------+-------+----------+-------+--------- fx | | | 2F005 | control reached end of function without RETURN | | | error | | | (1 row) create or replace function fx(x int) returns int as $$ begin begin if (x > 0) then raise exception 'xxx' using errcode = 'XX888'; else raise exception 'yyy' using errcode = 'YY888'; end if; exception when others then return 10; end; end; -- ok now $$ language plpgsql; select * from plpgsql_check_function_tb('fx(int)'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+---------+--------+------+-------+----------+-------+--------- (0 rows) --false alarm reported by Filip Zach create type testtype as (id integer); create or replace function fx() returns testtype as $$ begin return row(1); end; $$ language plpgsql; select * from fx(); id ---- 1 (1 row) select fx(); fx ----- (1) (1 row) select * from plpgsql_check_function('fx()'); plpgsql_check_function ------------------------ (0 rows) drop function fx(); create function out1(OUT f1 int, OUT f2 int) returns setof record as $$ begin for f1, f2 in execute $q$ select 1, 2 $q$ loop return next; end loop; end $$ language plpgsql; select * from plpgsql_check_function('out1()'); plpgsql_check_function ------------------------ (0 rows) drop function out1(); create function out1(OUT f1 int, OUT f2 int) returns setof record as $$ begin for f1, f2 in select 1, 2 loop return next; end loop; end $$ language plpgsql; select * from plpgsql_check_function('out1()'); plpgsql_check_function ------------------------ (0 rows) drop function out1(); -- never read variable detection create function a() returns int as $$ declare foo int; begin foo := 2; return 1; end; $$ language plpgsql; select * from plpgsql_check_function('a()'); plpgsql_check_function --------------------------------------------------------- warning extra:00000:2:DECLARE:never read variable "foo" (1 row) drop function a(); -- issue #29 false unused variable create or replace function f1(in p_cursor refcursor) returns void as $body$ declare z_offset integer; begin z_offset := 10; move absolute z_offset from p_cursor; end; $body$ language 'plpgsql' stable; select * from plpgsql_check_function_tb('f1(refcursor)'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+---------+--------+------+-------+----------+-------+--------- (0 rows) drop function f1(refcursor); -- issue #30 segfault due NULL refname create or replace function test(a varchar) returns void as $$ declare x cursor (_a varchar) for select _a; begin open x(a); end; $$ language plpgsql; select * from plpgsql_check_function_tb('test(varchar)'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+-------------------------+--------+------+---------------+----------+-------+--------- test | 2 | DECLARE | 00000 | never read variable "x" | | | warning extra | | | (1 row) drop function test(varchar); create or replace function test() returns void as $$ declare x numeric; begin x := NULL; end; $$ language plpgsql; select * from plpgsql_check_function('test()'); plpgsql_check_function ------------------------------------------------------- warning extra:00000:2:DECLARE:never read variable "x" (1 row) drop function test(); create table testtable(a int); create or replace function test() returns int as $$ declare r testtable; begin select * into r from testtable; return r.a; end; $$ language plpgsql; select * from plpgsql_check_function('test()'); plpgsql_check_function ------------------------ (0 rows) set check_function_bodies to on; drop table testtable; create table testtable(a int, b int); create or replace function test() returns int as $$ declare r testtable; begin select * into r from testtable; return r.a; end; $$ language plpgsql; alter table testtable drop column b; -- expected false alarm on PostgreSQL 10 and older -- there is not possibility to enforce recompilation -- before checking. select * from plpgsql_check_function('test()'); plpgsql_check_function ------------------------ (0 rows) drop function test(); -- issue #32 create table bigtable(id bigint, v varchar); create or replace function test() returns void as $$ declare r record; _id numeric; begin select * into r from bigtable where id = _id; for r in select * from bigtable where _id = id loop end loop; if (exists(select * from bigtable where id = _id)) then end if; end; $$ language plpgsql; select test(); test ------ (1 row) -- should to show performance warnings select * from plpgsql_check_function('test()', performance_warnings := true); plpgsql_check_function ------------------------------------------------------------------------------------------------------------------------------- performance:42804:6:SQL statement:implicit cast of attribute caused by different PLpgSQL variable type in WHERE clause Query: select * from bigtable where id = _id -- ^ Detail: An index of some attribute cannot be used, when variable, used in predicate, has not right type like a attribute Hint: Check a variable type - int versus numeric performance:42804:7:FOR over SELECT rows:implicit cast of attribute caused by different PLpgSQL variable type in WHERE clause Query: select * from bigtable where _id = id -- ^ Detail: An index of some attribute cannot be used, when variable, used in predicate, has not right type like a attribute Hint: Check a variable type - int versus numeric performance:42804:10:IF:implicit cast of attribute caused by different PLpgSQL variable type in WHERE clause Query: (exists(select * from bigtable where id = _id)) -- ^ Detail: An index of some attribute cannot be used, when variable, used in predicate, has not right type like a attribute Hint: Check a variable type - int versus numeric warning extra:00000:3:DECLARE:never read variable "r" (16 rows) create or replace function test() returns void as $$ declare r record; _id bigint; begin select * into r from bigtable where id = _id; for r in select * from bigtable where _id = id loop end loop; if (exists(select * from bigtable where id = _id)) then end if; end; $$ language plpgsql; -- there are not any performance issue now select * from plpgsql_check_function('test()', performance_warnings := true); plpgsql_check_function ------------------------------------------------------- warning extra:00000:3:DECLARE:never read variable "r" (1 row) -- nextval, currval and setval test create table test_table(); create or replace function testseq() returns void as $$ begin perform nextval('test_table'); perform currval('test_table'); perform setval('test_table', 10); perform setval('test_table', 10, true); end; $$ language plpgsql; -- should to fail select testseq(); ERROR: cannot open relation "test_table" DETAIL: This operation is not supported for tables. CONTEXT: SQL statement "SELECT nextval('test_table')" PL/pgSQL function testseq() line 3 at PERFORM select * from plpgsql_check_function('testseq()', fatal_errors := false); plpgsql_check_function ------------------------------------------------------ error:42809:3:PERFORM:"test_table" is not a sequence Query: SELECT nextval('test_table') -- ^ error:42809:4:PERFORM:"test_table" is not a sequence Query: SELECT currval('test_table') -- ^ error:42809:5:PERFORM:"test_table" is not a sequence Query: SELECT setval('test_table', 10) -- ^ error:42809:6:PERFORM:"test_table" is not a sequence Query: SELECT setval('test_table', 10, true) -- ^ (12 rows) drop function testseq(); drop table test_table; -- tests designed for PostgreSQL 9.2 set check_function_bodies to off; create table t1(a int, b int); create function f1() returns void as $$ begin if false then update t1 set c = 30; end if; if false then raise notice '%', r.c; end if; end; $$ language plpgsql; select f1(); f1 ---- (1 row) select * from plpgsql_check_function_tb('f1()', fatal_errors := true); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+---------------+----------+--------------------------------------------+--------+------+-------+----------+----------------------+--------- f1 | 4 | SQL statement | 42703 | column "c" of relation "t1" does not exist | | | error | 15 | update t1 set c = 30 | (1 row) select * from plpgsql_check_function_tb('f1()', fatal_errors := false); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+---------------+----------+--------------------------------------------+--------+------+-------+----------+----------------------+--------- f1 | 4 | SQL statement | 42703 | column "c" of relation "t1" does not exist | | | error | 15 | update t1 set c = 30 | f1 | 7 | RAISE | 42P01 | missing FROM-clause entry for table "r" | | | error | 1 | r.c | (2 rows) select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+---------------+----------+--------------------------------------------+--------+------+-------+----------+----------------------+--------- f1 | 4 | SQL statement | 42703 | column "c" of relation "t1" does not exist | | | error | 15 | update t1 set c = 30 | (1 row) select f1(); f1 ---- (1 row) drop function f1(); create or replace function f1() returns void as $$ begin if false then raise notice '%', 1, 2; end if; end; $$ language plpgsql; select f1(); ERROR: too many parameters specified for RAISE CONTEXT: compilation of PL/pgSQL function "f1" near line 4 select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+-----------------------------------------+--------+------+-------+----------+-------+--------------------------------------------------- f1 | | | 42601 | too many parameters specified for RAISE | | | error | | | compilation of PL/pgSQL function "f1" near line 4 (1 row) select f1(); ERROR: too many parameters specified for RAISE CONTEXT: compilation of PL/pgSQL function "f1" near line 4 drop function f1(); create or replace function f1() returns void as $$ begin if false then raise notice '% %'; end if; end; $$ language plpgsql; select f1(); ERROR: too few parameters specified for RAISE CONTEXT: compilation of PL/pgSQL function "f1" near line 4 select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+----------------------------------------+--------+------+-------+----------+-------+--------------------------------------------------- f1 | | | 42601 | too few parameters specified for RAISE | | | error | | | compilation of PL/pgSQL function "f1" near line 4 (1 row) select f1(); ERROR: too few parameters specified for RAISE CONTEXT: compilation of PL/pgSQL function "f1" near line 4 drop function f1(); create or replace function ml_trg() returns trigger as $$ #option dump declare begin if TG_OP = 'INSERT' then if NEW.status_from IS NULL then begin -- performance issue only select status into NEW.status_from from pa where pa_id = NEW.pa_id; -- nonexist target value select status into NEW.status_from_xxx from pa where pa_id = NEW.pa_id; exception when DATA_EXCEPTION then new.status_from := 'DE'; end; end if; end if; if TG_OP = 'DELETE' then return OLD; else return NEW; end if; exception when OTHERS then NULL; if TG_OP = 'DELETE' then return OLD; else return NEW; end if; end; $$ language plpgsql; select * from plpgsql_check_function('ml_trg()', 'ml', performance_warnings := true); plpgsql_check_function -------------------------------------------------------------------------- error:42703:13:SQL statement:record "new" has no field "status_from_xxx" (1 row) create or replace function fx2() returns void as $$ declare _pa pa; begin select pa.id into _pa.id from pa limit 1; select pa.pa_id into _pa.pa_id from pa limit 1; end; $$ language plpgsql; select * from plpgsql_check_function('fx2()', performance_warnings := true); plpgsql_check_function --------------------------------------------------------- warning extra:00000:2:DECLARE:never read variable "_pa" (1 row) drop function fx2(); create or replace function fx2() returns void as $$ declare _pa pa; begin _pa.id := (select pa.id from pa limit 1); _pa.pa_id := (select pa.pa_id from pa limit 1); end; $$ language plpgsql; select * from plpgsql_check_function('fx2()', performance_warnings := true); plpgsql_check_function --------------------------------------------------------- warning extra:00000:2:DECLARE:never read variable "_pa" (1 row) drop function fx2(); create or replace function test_lab() returns void as $$ begin <> for a in 1..3 loop <> BEGIN <> for b in 8..9 loop if a=2 then continue sub; end if; raise notice '% %', a, b; end loop inner; END sub; end loop outer; end; $$ language plpgsql; select test_lab(); ERROR: block label "sub" cannot be used in CONTINUE LINE 10: continue sub; ^ QUERY: begin <> for a in 1..3 loop <> BEGIN <> for b in 8..9 loop if a=2 then continue sub; end if; raise notice '% %', a, b; end loop inner; END sub; end loop outer; end; CONTEXT: compilation of PL/pgSQL function "test_lab" near line 10 select * from plpgsql_check_function('test_lab()', performance_warnings := true); plpgsql_check_function ------------------------------------------------------------------- error:42601:block label "sub" cannot be used in CONTINUE Query: begin <> for a in 1..3 loop <> BEGIN <> for b in 8..9 loop if a=2 then continue sub; -- ^ end if; raise notice '% %', a, b; end loop inner; END sub; end loop outer; end; Context: compilation of PL/pgSQL function "test_lab" near line 10 (20 rows) create or replace function test_lab() returns void as $$ begin continue; end; $$ language plpgsql; select test_lab(); ERROR: CONTINUE cannot be used outside a loop LINE 3: continue; ^ QUERY: begin continue; end; CONTEXT: compilation of PL/pgSQL function "test_lab" near line 3 select * from plpgsql_check_function('test_lab()', performance_warnings := true); plpgsql_check_function ------------------------------------------------------------------ error:42601:CONTINUE cannot be used outside a loop Query: begin continue; -- ^ end; Context: compilation of PL/pgSQL function "test_lab" near line 3 (8 rows) create type _exception_type as ( state text, message text, detail text); create or replace function f1() returns void as $$ declare _exception record; begin _exception := NULL::_exception_type; exception when others then get stacked diagnostics _exception.state = RETURNED_SQLSTATE, _exception.message = MESSAGE_TEXT, _exception.detail = PG_EXCEPTION_DETAIL, _exception.hint = PG_EXCEPTION_HINT; end; $$ language plpgsql; select f1(); f1 ---- (1 row) select * from plpgsql_check_function('f1()'); plpgsql_check_function ------------------------------------------------------------------------------- error:42703:7:GET STACKED DIAGNOSTICS:record "_exception" has no field "hint" (1 row) drop function f1(); drop type _exception_type; drop table t1; create function myfunc1(a int, b float) returns integer as $$ begin end $$ language plpgsql; create function myfunc2(a int, b float) returns integer as $$ begin end $$ language plpgsql; create function myfunc3(a int, b float) returns integer as $$ begin end $$ language plpgsql; create function myfunc4(a int, b float) returns integer as $$ begin end $$ language plpgsql; create function opfunc1(a int, b float) returns integer as $$ begin end $$ language plpgsql; create operator *** (procedure = opfunc1, leftarg = int, rightarg = float); create table mytable(a int); create table myview as select * from mytable; create function testfunc(a int, b float) returns void as $$ declare x integer; begin raise notice '%', myfunc1(a, b); x := myfunc2(a, b) operator(public.***) 1; perform myfunc3(m.a, b) from myview m; insert into mytable select myfunc4(a, b); end; $$ language plpgsql; select * from plpgsql_check_function('testfunc(int,float)'); plpgsql_check_function ------------------------------------------------------- warning extra:00000:2:DECLARE:never read variable "x" (1 row) select type, schema, name, params from plpgsql_show_dependency_tb('testfunc(int,float)'); type | schema | name | params ----------+--------+---------+---------------------------- FUNCTION | public | myfunc1 | (integer,double precision) FUNCTION | public | myfunc2 | (integer,double precision) FUNCTION | public | myfunc3 | (integer,double precision) FUNCTION | public | myfunc4 | (integer,double precision) OPERATOR | public | *** | (integer,double precision) RELATION | public | mytable | RELATION | public | myview | (7 rows) drop function testfunc(int, float); drop function myfunc1(int, float); drop function myfunc2(int, float); drop function myfunc3(int, float); drop function myfunc4(int, float); drop table mytable; drop view myview; ERROR: "myview" is not a view HINT: Use DROP TABLE to remove a table. -- issue #34 create or replace function testcase() returns bool as $$ declare x int; begin set local search_path to public, test; case x when 1 then return true; else return false; end case; end; $$ language plpgsql; -- should not to raise warning select * from plpgsql_check_function('testcase()'); plpgsql_check_function ------------------------ (0 rows) drop function testcase(); -- Adam's Bartoszewicz example create or replace function public.test12() returns refcursor language plpgsql as $body$ declare rc refcursor; begin open rc scroll for select pc.* from pg_cast pc; return rc; end; $body$; -- should not returns false alarm select * from plpgsql_check_function('test12()'); plpgsql_check_function ------------------------ (0 rows) drop function public.test12(); -- should to show performance warning on bad flag create or replace function flag_test1(int) returns int as $$ begin return $1 + 10; end; $$ language plpgsql stable; create table fufu(a int); create or replace function flag_test2(int) returns int as $$ begin return (select * from fufu limit 1); end; $$ language plpgsql volatile; select * from plpgsql_check_function('flag_test1(int)', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------------- performance:00000:routine is marked as STABLE, should be IMMUTABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (2 rows) select * from plpgsql_check_function('flag_test2(int)', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------------- warning extra:00000:unused parameter "$1" performance:00000:routine is marked as VOLATILE, should be STABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (3 rows) drop table fufu; drop function flag_test1(int); drop function flag_test2(int); create or replace function rrecord01() returns setof record as $$ begin return query select 1,2; end; $$ language plpgsql; create or replace function rrecord02() returns record as $$ begin return row(10,20,30); end; $$ language plpgsql; create type record03 as (a int, b int); create or replace function rrecord03() returns record03 as $$ declare r record; begin r := row(1); return r; end; $$ language plpgsql; -- should not to raise false alarms select * from plpgsql_check_function('rrecord01'); plpgsql_check_function ------------------------ (0 rows) select * from plpgsql_check_function('rrecord02'); plpgsql_check_function ------------------------ (0 rows) -- should detect different return but still detect return select * from plpgsql_check_function('rrecord03', fatal_errors => false); plpgsql_check_function ---------------------------------------------------------------------------------- error:42804:5:RETURN:returned record type does not match expected record type Detail: Number of returned columns (1) does not match expected column count (2). (2 rows) drop function rrecord01(); drop function rrecord02(); drop function rrecord03(); drop type record03; create or replace function bugfunc01() returns void as $$ declare cvar cursor(a int, b int) for select a + b from generate_series(1,b); begin for t in cvar(1,3) loop raise notice '%', t; end loop; end; $$ language plpgsql; select bugfunc01(); NOTICE: (4) NOTICE: (4) NOTICE: (4) bugfunc01 ----------- (1 row) select * from plpgsql_check_function('bugfunc01'); plpgsql_check_function ------------------------ (0 rows) create or replace function bugfunc02() returns void as $$ declare cvar cursor(a int, b int) for select a + b from generate_series(1,b); begin open cvar(10,20); close cvar; end; $$ language plpgsql; select bugfunc02(); bugfunc02 ----------- (1 row) select * from plpgsql_check_function('bugfunc02'); plpgsql_check_function ------------------------ (0 rows) create or replace function bugfunc03() returns void as $$ declare cvar cursor(a int, b int) for select a + b from not_exists_table; begin open cvar(10,20); close cvar; end; $$ language plpgsql; select bugfunc03(); ERROR: relation "not_exists_table" does not exist LINE 1: select a + b from not_exists_table ^ QUERY: select a + b from not_exists_table CONTEXT: PL/pgSQL function bugfunc03() line 5 at OPEN select * from plpgsql_check_function('bugfunc03'); plpgsql_check_function --------------------------------------------------------------- error:42P01:5:OPEN:relation "not_exists_table" does not exist Query: select a + b from not_exists_table -- ^ (3 rows) create or replace function f1(out cr refcursor) as $$ begin end; $$ language plpgsql; -- should to raise warning select * from plpgsql_check_function('f1()'); plpgsql_check_function -------------------------------------------------- warning extra:00000:unmodified OUT variable "cr" (1 row) create or replace function f1(out cr refcursor) as $$ begin open cr for select 1; end; $$ language plpgsql; -- should not to raise warning, see issue #43 select * from plpgsql_check_function('f1()'); plpgsql_check_function ------------------------ (0 rows) drop function f1(); create table testt(a int); create or replace function testt_trg_func() returns trigger as $$ begin return new; end; $$ language plpgsql; create trigger testt_trg before insert or update on testt for each row execute procedure testt_trg_func(); create or replace function maintaince_function() returns void as $$ begin alter table testt disable trigger testt_trg; alter table testt enable trigger testt_trg; end; $$ language plpgsql; -- should not to crash select * from plpgsql_check_function_tb('maintaince_function()', 0, true, true, true); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+---------+--------+------+-------+----------+-------+--------- (0 rows) drop function maintaince_function(); drop trigger testt_trg on testt; drop function testt_trg_func(); drop table testt; create or replace function test_crash() returns void as $$ declare ec int default buggyfunc(10); begin select * into ec from buggytab; end; $$ language plpgsql; -- should not to crash select * from plpgsql_check_function('test_crash', fatal_errors := false); plpgsql_check_function --------------------------------------------------------------------------------------------------------- error:42883:4:statement block:function buggyfunc(integer) does not exist Query: buggyfunc(10) -- ^ Hint: No function matches the given name and argument types. You might need to add explicit type casts. Context: during statement block local variable "ec" initialization on line 3 error:42P01:5:SQL statement:relation "buggytab" does not exist Query: select * from buggytab -- ^ warning extra:00000:3:DECLARE:never read variable "ec" (9 rows) select * from plpgsql_check_function('test_crash', fatal_errors := true); plpgsql_check_function --------------------------------------------------------------------------------------------------------- error:42883:4:statement block:function buggyfunc(integer) does not exist Query: buggyfunc(10) -- ^ Hint: No function matches the given name and argument types. You might need to add explicit type casts. Context: during statement block local variable "ec" initialization on line 3 (5 rows) drop function test_crash(); -- fix false alarm reported by Piotr Stepniewski create or replace function public.fx() returns void language plpgsql as $function$ begin raise exception 'xxx'; end; $function$; -- show raise nothing select * from plpgsql_check_function('fx()'); plpgsql_check_function ------------------------ (0 rows) create table errtab( message text, code character(5) ); create or replace function public.fx() returns void language plpgsql as $function$ declare var errtab%rowtype; begin raise exception using message = var.message, errcode = var.code; end; $function$; -- should not to crash select * from plpgsql_check_function('fx()'); plpgsql_check_function ------------------------ (0 rows) create or replace function public.fx() returns void language plpgsql as $function$ declare var errtab%rowtype; begin raise exception using message = var.message, errcode = var.code, hint = var.hint; end; $function$; -- should not to crash select * from plpgsql_check_function('fx()'); plpgsql_check_function ------------------------------------------------------ error:42703:5:RAISE:record "var" has no field "hint" Context: SQL expression "var.hint" (2 rows) drop function fx(); create or replace function foo_format(a text, b text) returns void as $$ declare s text; begin s := format('%s'); -- should to raise error s := format('%s %10s', a, b); -- should be ok s := format('%s %s', a, b, a); -- should to raise warning s := format('%s %d', a, b); -- should to raise error raise notice '%', s; end; $$ language plpgsql; select * from plpgsql_check_function('foo_format', fatal_errors := false); plpgsql_check_function ------------------------------------------------------------------- error:22023:4:assignment:too few arguments for format() Query: s := format('%s') -- ^ Context: at assignment to variable "s" declared on line 2 warning:00000:6:assignment:unused parameters of function "format" Query: s := format('%s %s', a, b, a) -- ^ Context: at assignment to variable "s" declared on line 2 error:22023:7:assignment:unrecognized format() type specifier "d" Query: s := format('%s %d', a, b) -- ^ Context: at assignment to variable "s" declared on line 2 (12 rows) drop function foo_format(text, text); create or replace function dyn_sql_1() returns void as $$ declare v varchar; n int; begin execute 'select ' || n; -- ok execute 'select ' || quote_literal(v); -- ok execute 'select ' || v; -- vulnerable execute format('select * from %I', v); -- ok execute format('select * from %s', v); -- vulnerable execute 'select $1' using v; -- ok execute 'select 1'; -- ok execute 'select 1' using v; -- warning execute 'select $1'; -- error end; $$ language plpgsql; select * from plpgsql_check_function('dyn_sql_1', security_warnings := true, fatal_errors := false); plpgsql_check_function ------------------------------------------------------------------------------------------ security:00000:8:EXECUTE:text type variable is not sanitized Query: 'select ' || v -- ^ Detail: The EXECUTE expression is SQL injection vulnerable. Hint: Use quote_ident, quote_literal or format function to secure variable. security:00000:10:EXECUTE:text type variable is not sanitized Query: format('select * from %s', v) -- ^ Detail: The EXECUTE expression is SQL injection vulnerable. Hint: Use quote_ident, quote_literal or format function to secure variable. warning:00000:13:EXECUTE:values passed to EXECUTE statement by USING clause was not used error:42P02:14:EXECUTE:there is no parameter $1 Query: select $1 -- ^ (14 rows) drop function dyn_sql_1(); create type tp as (a int, b int); create or replace function dyn_sql_2() returns void as $$ declare r tp; result int; begin select 10 a, 20 b into r; raise notice '%', r.a; execute 'select $1.a + $1.b' into result using r; execute 'select $1.c' into result using r; -- error raise notice '%', result; end; $$ language plpgsql; select * from plpgsql_check_function('dyn_sql_2', security_warnings := true); plpgsql_check_function ------------------------------------------------------------ error:42703:9:EXECUTE:column "c" not found in data type tp Query: select $1.c -- ^ (3 rows) drop function dyn_sql_2(); drop type tp; /* * Should not to work * * note: plpgsql doesn't support passing some necessary details for record * type. The parser setup for dynamic SQL column doesn't use ref hooks, and * then it cannot to pass TupleDesc info to query anyway. */ create or replace function dyn_sql_2() returns void as $$ declare r record; result int; begin select 10 a, 20 b into r; raise notice '%', r.a; execute 'select $1.a + $1.b' into result using r; raise notice '%', result; end; $$ language plpgsql; select dyn_sql_2(); --should to fail NOTICE: 10 ERROR: could not identify column "a" in record data type LINE 1: select $1.a + $1.b ^ QUERY: select $1.a + $1.b CONTEXT: PL/pgSQL function dyn_sql_2() line 8 at EXECUTE select * from plpgsql_check_function('dyn_sql_2', security_warnings := true); plpgsql_check_function ------------------------------------------------------------------------- error:42703:8:EXECUTE:could not identify column "a" in record data type Query: select $1.a + $1.b -- ^ (3 rows) drop function dyn_sql_2(); create or replace function dyn_sql_3() returns void as $$ declare r int; begin execute 'select $1' into r using 1; raise notice '%', r; end $$ language plpgsql; select dyn_sql_3(); NOTICE: 1 dyn_sql_3 ----------- (1 row) -- should be ok select * from plpgsql_check_function('dyn_sql_3'); plpgsql_check_function ------------------------ (0 rows) create or replace function dyn_sql_3() returns void as $$ declare r record; begin execute 'select $1 as a, $2 as b' into r using 1, 2; raise notice '% %', r.a, r.b; end $$ language plpgsql; select dyn_sql_3(); NOTICE: 1 2 dyn_sql_3 ----------- (1 row) -- should be ok select * from plpgsql_check_function('dyn_sql_3'); plpgsql_check_function ------------------------ (0 rows) create or replace function dyn_sql_3() returns void as $$ declare r record; begin execute 'create table foo(a int)' into r using 1, 2; raise notice '% %', r.a, r.b; end $$ language plpgsql; -- raise a error select * from plpgsql_check_function('dyn_sql_3'); plpgsql_check_function ----------------------------------------------------------------------------------------- warning:00000:4:EXECUTE:values passed to EXECUTE statement by USING clause was not used error:XX000:4:EXECUTE:expression does not return data (2 rows) create or replace function dyn_sql_3() returns void as $$ declare r1 int; r2 int; begin execute 'select 1' into r1, r2 using 1, 2; raise notice '% %', r1, r2; end $$ language plpgsql; -- raise a error select * from plpgsql_check_function('dyn_sql_3'); plpgsql_check_function ----------------------------------------------------------------------------------------- warning:00000:4:EXECUTE:values passed to EXECUTE statement by USING clause was not used warning:00000:4:EXECUTE:too few attributes for target variables Detail: There are more target variables than output columns in query. Hint: Check target variables in SELECT INTO statement. (4 rows) drop function dyn_sql_3(); create or replace function dyn_sql_3() returns void as $$ declare r record; begin for r in execute 'select 1 as a, 2 as b' loop raise notice '%', r.a; end loop; end $$ language plpgsql; -- should be ok select * from plpgsql_check_function('dyn_sql_3'); plpgsql_check_function ------------------------ (0 rows) drop function dyn_sql_3(); create or replace function dyn_sql_3() returns void as $$ declare r record; begin for r in execute 'select 1 as a, 2 as b' loop raise notice '%', r.c; end loop; end $$ language plpgsql; -- should be error select * from plpgsql_check_function('dyn_sql_3'); plpgsql_check_function ------------------------------------------------- error:42703:6:RAISE:record "r" has no field "c" Context: SQL expression "r.c" (2 rows) drop function dyn_sql_3(); create or replace function dyn_sql_3() returns void as $$ declare r record; v text = 'select 10 a, 20 b't; begin for r in execute v loop raise notice '%', r.a; end loop; end $$ language plpgsql; -- should be ok select * from plpgsql_check_function('dyn_sql_3'); plpgsql_check_function ------------------------ (0 rows) drop function dyn_sql_3(); create or replace function dyn_sql_4() returns table(ax int, bx int) as $$ begin return query execute 'select 10, 20'; return; end; $$ language plpgsql; -- should be ok select * from plpgsql_check_function('dyn_sql_4()'); plpgsql_check_function ------------------------ (0 rows) create or replace function dyn_sql_4() returns table(ax int, bx int) as $$ begin return query execute 'select 10, 20, 30'; return; end; $$ language plpgsql; select * from dyn_sql_4(); ERROR: structure of query does not match function result type DETAIL: Number of returned columns (3) does not match expected column count (2). CONTEXT: SQL statement "select 10, 20, 30" PL/pgSQL function dyn_sql_4() line 3 at RETURN QUERY -- should be error select * from plpgsql_check_function('dyn_sql_4()'); plpgsql_check_function ----------------------------------------------------------------------------------- error:42804:3:RETURN QUERY:structure of query does not match function result type Detail: Number of returned columns (3) does not match expected column count (2). (2 rows) drop function dyn_sql_4(); create or replace function test_bug(text) returns regproc as $$ begin return $1::regproc; exception when undefined_function or invalid_name then raise; end; $$ language plpgsql; -- should not raise a exception select * from plpgsql_check_function('test_bug'); plpgsql_check_function ------------------------ (0 rows) create or replace function test_bug(text) returns regproc as $$ begin return $1::regproc; exception when undefined_function or invalid_name then raise notice '%', $1; -- bug end; $$ language plpgsql; select test_bug('kuku'); -- should to fail NOTICE: kuku ERROR: control reached end of function without RETURN CONTEXT: PL/pgSQL function test_bug(text) select * from plpgsql_check_function('test_bug'); plpgsql_check_function -------------------------------------------------------------------- warning extra:2F005:control reached end of function without RETURN (1 row) drop function test_bug(text); create or replace function test_bug(text) returns regproc as $$ begin return $1::regproc; exception when undefined_function or invalid_name then raise notice '%', $1; return NULL; end; $$ language plpgsql; select test_bug('kuku'); -- should be ok NOTICE: kuku test_bug ---------- (1 row) select * from plpgsql_check_function('test_bug'); plpgsql_check_function ------------------------ (0 rows) drop function test_bug(text); create or replace function foo(a text, b text) returns void as $$ begin -- unsecure execute 'select ' || a; a := quote_literal(a); -- is safe now execute 'select ' || a; a := a || b; -- it is unsecure again execute 'select ' || a; end; $$ language plpgsql; \sf+ foo(text, text) CREATE OR REPLACE FUNCTION public.foo(a text, b text) RETURNS void LANGUAGE plpgsql 1 AS $function$ 2 begin 3 -- unsecure 4 execute 'select ' || a; 5 a := quote_literal(a); -- is safe now 6 execute 'select ' || a; 7 a := a || b; -- it is unsecure again 8 execute 'select ' || a; 9 end; 10 $function$ -- should to raise two warnings select * from plpgsql_check_function('foo', security_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------- security:00000:4:EXECUTE:text type variable is not sanitized Query: 'select ' || a -- ^ Detail: The EXECUTE expression is SQL injection vulnerable. Hint: Use quote_ident, quote_literal or format function to secure variable. security:00000:8:EXECUTE:text type variable is not sanitized Query: 'select ' || a -- ^ Detail: The EXECUTE expression is SQL injection vulnerable. Hint: Use quote_ident, quote_literal or format function to secure variable. (10 rows) drop function foo(text, text); -- test of very long function inside profiler create or replace function longfx(int) returns int as $$ declare s int default 0; j int default 0; r record; begin begin while j < 10 loop for i in 1..1 loop for r in select * from generate_series(1,1) loop s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; end loop; end loop; j := j + 1; end loop; exception when others then raise 'reraised exception %', sqlerrm; end; return $1; end; $$ language plpgsql; select lineno, stmt_lineno, exec_stmts, source from plpgsql_profiler_function_tb('longfx'); lineno | stmt_lineno | exec_stmts | source --------+-------------+------------+----------------------------------------------------- 1 | | | 2 | | | declare 3 | | | s int default 0; 4 | | | j int default 0; 5 | | | r record; 6 | | | begin 7 | | | begin 8 | | | while j < 10 9 | | | loop 10 | | | for i in 1..1 11 | | | loop 12 | | | for r in select * from generate_series(1,1) 13 | | | loop 14 | | | s := s + 1; 15 | | | s := s + 1; 16 | | | s := s + 1; 17 | | | s := s + 1; 18 | | | s := s + 1; 19 | | | s := s + 1; 20 | | | s := s + 1; 21 | | | s := s + 1; 22 | | | s := s + 1; 23 | | | s := s + 1; 24 | | | s := s + 1; 25 | | | s := s + 1; 26 | | | s := s + 1; 27 | | | s := s + 1; 28 | | | s := s + 1; 29 | | | s := s + 1; 30 | | | s := s + 1; 31 | | | s := s + 1; 32 | | | s := s + 1; 33 | | | s := s + 1; 34 | | | s := s + 1; 35 | | | s := s + 1; 36 | | | s := s + 1; 37 | | | s := s + 1; 38 | | | s := s + 1; 39 | | | s := s + 1; 40 | | | s := s + 1; 41 | | | s := s + 1; 42 | | | s := s + 1; 43 | | | s := s + 1; 44 | | | s := s + 1; 45 | | | s := s + 1; 46 | | | s := s + 1; 47 | | | s := s + 1; 48 | | | s := s + 1; 49 | | | s := s + 1; 50 | | | s := s + 1; 51 | | | s := s + 1; 52 | | | s := s + 1; 53 | | | s := s + 1; 54 | | | s := s + 1; 55 | | | s := s + 1; 56 | | | s := s + 1; 57 | | | s := s + 1; 58 | | | s := s + 1; 59 | | | s := s + 1; 60 | | | s := s + 1; 61 | | | s := s + 1; 62 | | | s := s + 1; 63 | | | s := s + 1; 64 | | | s := s + 1; 65 | | | s := s + 1; 66 | | | s := s + 1; 67 | | | s := s + 1; 68 | | | s := s + 1; 69 | | | s := s + 1; 70 | | | s := s + 1; 71 | | | s := s + 1; 72 | | | s := s + 1; 73 | | | s := s + 1; 74 | | | s := s + 1; 75 | | | s := s + 1; 76 | | | s := s + 1; 77 | | | s := s + 1; 78 | | | s := s + 1; 79 | | | s := s + 1; 80 | | | s := s + 1; 81 | | | s := s + 1; 82 | | | s := s + 1; 83 | | | s := s + 1; 84 | | | s := s + 1; 85 | | | s := s + 1; 86 | | | s := s + 1; 87 | | | s := s + 1; 88 | | | s := s + 1; 89 | | | s := s + 1; 90 | | | s := s + 1; 91 | | | s := s + 1; 92 | | | s := s + 1; 93 | | | s := s + 1; 94 | | | s := s + 1; 95 | | | s := s + 1; 96 | | | s := s + 1; 97 | | | s := s + 1; 98 | | | s := s + 1; 99 | | | s := s + 1; 100 | | | s := s + 1; 101 | | | s := s + 1; 102 | | | s := s + 1; 103 | | | s := s + 1; 104 | | | s := s + 1; 105 | | | s := s + 1; 106 | | | s := s + 1; 107 | | | s := s + 1; 108 | | | s := s + 1; 109 | | | s := s + 1; 110 | | | s := s + 1; 111 | | | s := s + 1; 112 | | | s := s + 1; 113 | | | s := s + 1; 114 | | | s := s + 1; 115 | | | s := s + 1; 116 | | | s := s + 1; 117 | | | s := s + 1; 118 | | | s := s + 1; 119 | | | s := s + 1; 120 | | | s := s + 1; 121 | | | s := s + 1; 122 | | | s := s + 1; 123 | | | s := s + 1; 124 | | | s := s + 1; 125 | | | s := s + 1; 126 | | | s := s + 1; 127 | | | s := s + 1; 128 | | | s := s + 1; 129 | | | s := s + 1; 130 | | | s := s + 1; 131 | | | s := s + 1; 132 | | | s := s + 1; 133 | | | s := s + 1; 134 | | | s := s + 1; 135 | | | s := s + 1; 136 | | | s := s + 1; 137 | | | s := s + 1; 138 | | | s := s + 1; 139 | | | s := s + 1; 140 | | | s := s + 1; 141 | | | s := s + 1; 142 | | | s := s + 1; 143 | | | s := s + 1; 144 | | | s := s + 1; 145 | | | s := s + 1; 146 | | | s := s + 1; 147 | | | s := s + 1; 148 | | | s := s + 1; 149 | | | s := s + 1; 150 | | | s := s + 1; 151 | | | s := s + 1; 152 | | | s := s + 1; 153 | | | s := s + 1; 154 | | | s := s + 1; 155 | | | s := s + 1; 156 | | | s := s + 1; 157 | | | s := s + 1; 158 | | | s := s + 1; 159 | | | s := s + 1; 160 | | | s := s + 1; 161 | | | s := s + 1; 162 | | | s := s + 1; 163 | | | s := s + 1; 164 | | | s := s + 1; 165 | | | s := s + 1; 166 | | | s := s + 1; 167 | | | s := s + 1; 168 | | | s := s + 1; 169 | | | s := s + 1; 170 | | | s := s + 1; 171 | | | s := s + 1; 172 | | | s := s + 1; 173 | | | s := s + 1; 174 | | | s := s + 1; 175 | | | s := s + 1; 176 | | | s := s + 1; 177 | | | s := s + 1; 178 | | | s := s + 1; 179 | | | s := s + 1; 180 | | | s := s + 1; 181 | | | s := s + 1; 182 | | | s := s + 1; 183 | | | s := s + 1; 184 | | | s := s + 1; 185 | | | s := s + 1; 186 | | | s := s + 1; 187 | | | s := s + 1; 188 | | | s := s + 1; 189 | | | s := s + 1; 190 | | | s := s + 1; 191 | | | s := s + 1; 192 | | | s := s + 1; 193 | | | s := s + 1; 194 | | | s := s + 1; 195 | | | s := s + 1; 196 | | | s := s + 1; 197 | | | s := s + 1; 198 | | | s := s + 1; 199 | | | s := s + 1; 200 | | | s := s + 1; 201 | | | s := s + 1; 202 | | | s := s + 1; 203 | | | s := s + 1; 204 | | | s := s + 1; 205 | | | s := s + 1; 206 | | | s := s + 1; 207 | | | s := s + 1; 208 | | | s := s + 1; 209 | | | s := s + 1; 210 | | | s := s + 1; 211 | | | s := s + 1; 212 | | | s := s + 1; 213 | | | s := s + 1; 214 | | | s := s + 1; 215 | | | s := s + 1; 216 | | | s := s + 1; 217 | | | s := s + 1; 218 | | | s := s + 1; 219 | | | s := s + 1; 220 | | | s := s + 1; 221 | | | s := s + 1; 222 | | | s := s + 1; 223 | | | s := s + 1; 224 | | | s := s + 1; 225 | | | s := s + 1; 226 | | | s := s + 1; 227 | | | s := s + 1; 228 | | | s := s + 1; 229 | | | s := s + 1; 230 | | | s := s + 1; 231 | | | s := s + 1; 232 | | | s := s + 1; 233 | | | s := s + 1; 234 | | | s := s + 1; 235 | | | s := s + 1; 236 | | | s := s + 1; 237 | | | s := s + 1; 238 | | | s := s + 1; 239 | | | s := s + 1; 240 | | | s := s + 1; 241 | | | s := s + 1; 242 | | | s := s + 1; 243 | | | s := s + 1; 244 | | | s := s + 1; 245 | | | s := s + 1; 246 | | | s := s + 1; 247 | | | s := s + 1; 248 | | | s := s + 1; 249 | | | s := s + 1; 250 | | | s := s + 1; 251 | | | s := s + 1; 252 | | | s := s + 1; 253 | | | s := s + 1; 254 | | | s := s + 1; 255 | | | s := s + 1; 256 | | | s := s + 1; 257 | | | s := s + 1; 258 | | | s := s + 1; 259 | | | s := s + 1; 260 | | | s := s + 1; 261 | | | s := s + 1; 262 | | | s := s + 1; 263 | | | s := s + 1; 264 | | | s := s + 1; 265 | | | s := s + 1; 266 | | | s := s + 1; 267 | | | s := s + 1; 268 | | | s := s + 1; 269 | | | s := s + 1; 270 | | | s := s + 1; 271 | | | s := s + 1; 272 | | | s := s + 1; 273 | | | s := s + 1; 274 | | | s := s + 1; 275 | | | s := s + 1; 276 | | | s := s + 1; 277 | | | s := s + 1; 278 | | | s := s + 1; 279 | | | s := s + 1; 280 | | | s := s + 1; 281 | | | s := s + 1; 282 | | | s := s + 1; 283 | | | s := s + 1; 284 | | | s := s + 1; 285 | | | s := s + 1; 286 | | | s := s + 1; 287 | | | s := s + 1; 288 | | | s := s + 1; 289 | | | s := s + 1; 290 | | | s := s + 1; 291 | | | s := s + 1; 292 | | | s := s + 1; 293 | | | s := s + 1; 294 | | | s := s + 1; 295 | | | s := s + 1; 296 | | | s := s + 1; 297 | | | s := s + 1; 298 | | | s := s + 1; 299 | | | s := s + 1; 300 | | | s := s + 1; 301 | | | s := s + 1; 302 | | | s := s + 1; 303 | | | s := s + 1; 304 | | | s := s + 1; 305 | | | s := s + 1; 306 | | | s := s + 1; 307 | | | s := s + 1; 308 | | | s := s + 1; 309 | | | s := s + 1; 310 | | | s := s + 1; 311 | | | s := s + 1; 312 | | | s := s + 1; 313 | | | s := s + 1; 314 | | | s := s + 1; 315 | | | s := s + 1; 316 | | | s := s + 1; 317 | | | s := s + 1; 318 | | | s := s + 1; 319 | | | s := s + 1; 320 | | | s := s + 1; 321 | | | s := s + 1; 322 | | | s := s + 1; 323 | | | s := s + 1; 324 | | | s := s + 1; 325 | | | s := s + 1; 326 | | | s := s + 1; 327 | | | s := s + 1; 328 | | | s := s + 1; 329 | | | s := s + 1; 330 | | | s := s + 1; 331 | | | s := s + 1; 332 | | | s := s + 1; 333 | | | s := s + 1; 334 | | | s := s + 1; 335 | | | s := s + 1; 336 | | | s := s + 1; 337 | | | s := s + 1; 338 | | | s := s + 1; 339 | | | s := s + 1; 340 | | | s := s + 1; 341 | | | s := s + 1; 342 | | | s := s + 1; 343 | | | s := s + 1; 344 | | | s := s + 1; 345 | | | s := s + 1; 346 | | | s := s + 1; 347 | | | s := s + 1; 348 | | | s := s + 1; 349 | | | s := s + 1; 350 | | | s := s + 1; 351 | | | s := s + 1; 352 | | | s := s + 1; 353 | | | s := s + 1; 354 | | | s := s + 1; 355 | | | s := s + 1; 356 | | | s := s + 1; 357 | | | s := s + 1; 358 | | | s := s + 1; 359 | | | s := s + 1; 360 | | | s := s + 1; 361 | | | s := s + 1; 362 | | | s := s + 1; 363 | | | s := s + 1; 364 | | | s := s + 1; 365 | | | s := s + 1; 366 | | | s := s + 1; 367 | | | s := s + 1; 368 | | | s := s + 1; 369 | | | s := s + 1; 370 | | | s := s + 1; 371 | | | s := s + 1; 372 | | | s := s + 1; 373 | | | s := s + 1; 374 | | | s := s + 1; 375 | | | s := s + 1; 376 | | | s := s + 1; 377 | | | s := s + 1; 378 | | | s := s + 1; 379 | | | s := s + 1; 380 | | | s := s + 1; 381 | | | s := s + 1; 382 | | | s := s + 1; 383 | | | s := s + 1; 384 | | | s := s + 1; 385 | | | s := s + 1; 386 | | | s := s + 1; 387 | | | s := s + 1; 388 | | | s := s + 1; 389 | | | s := s + 1; 390 | | | s := s + 1; 391 | | | s := s + 1; 392 | | | s := s + 1; 393 | | | s := s + 1; 394 | | | s := s + 1; 395 | | | s := s + 1; 396 | | | s := s + 1; 397 | | | s := s + 1; 398 | | | s := s + 1; 399 | | | s := s + 1; 400 | | | s := s + 1; 401 | | | s := s + 1; 402 | | | s := s + 1; 403 | | | s := s + 1; 404 | | | s := s + 1; 405 | | | s := s + 1; 406 | | | s := s + 1; 407 | | | s := s + 1; 408 | | | s := s + 1; 409 | | | s := s + 1; 410 | | | s := s + 1; 411 | | | s := s + 1; 412 | | | s := s + 1; 413 | | | s := s + 1; 414 | | | s := s + 1; 415 | | | s := s + 1; 416 | | | s := s + 1; 417 | | | s := s + 1; 418 | | | s := s + 1; 419 | | | s := s + 1; 420 | | | s := s + 1; 421 | | | s := s + 1; 422 | | | s := s + 1; 423 | | | s := s + 1; 424 | | | s := s + 1; 425 | | | s := s + 1; 426 | | | s := s + 1; 427 | | | s := s + 1; 428 | | | s := s + 1; 429 | | | s := s + 1; 430 | | | s := s + 1; 431 | | | s := s + 1; 432 | | | s := s + 1; 433 | | | s := s + 1; 434 | | | s := s + 1; 435 | | | s := s + 1; 436 | | | s := s + 1; 437 | | | s := s + 1; 438 | | | s := s + 1; 439 | | | s := s + 1; 440 | | | s := s + 1; 441 | | | s := s + 1; 442 | | | s := s + 1; 443 | | | s := s + 1; 444 | | | s := s + 1; 445 | | | s := s + 1; 446 | | | s := s + 1; 447 | | | s := s + 1; 448 | | | s := s + 1; 449 | | | s := s + 1; 450 | | | s := s + 1; 451 | | | s := s + 1; 452 | | | s := s + 1; 453 | | | s := s + 1; 454 | | | s := s + 1; 455 | | | s := s + 1; 456 | | | s := s + 1; 457 | | | s := s + 1; 458 | | | s := s + 1; 459 | | | s := s + 1; 460 | | | s := s + 1; 461 | | | s := s + 1; 462 | | | s := s + 1; 463 | | | s := s + 1; 464 | | | s := s + 1; 465 | | | s := s + 1; 466 | | | s := s + 1; 467 | | | s := s + 1; 468 | | | s := s + 1; 469 | | | s := s + 1; 470 | | | s := s + 1; 471 | | | s := s + 1; 472 | | | s := s + 1; 473 | | | s := s + 1; 474 | | | s := s + 1; 475 | | | s := s + 1; 476 | | | s := s + 1; 477 | | | s := s + 1; 478 | | | s := s + 1; 479 | | | s := s + 1; 480 | | | s := s + 1; 481 | | | s := s + 1; 482 | | | s := s + 1; 483 | | | s := s + 1; 484 | | | s := s + 1; 485 | | | s := s + 1; 486 | | | s := s + 1; 487 | | | s := s + 1; 488 | | | s := s + 1; 489 | | | s := s + 1; 490 | | | s := s + 1; 491 | | | s := s + 1; 492 | | | s := s + 1; 493 | | | s := s + 1; 494 | | | s := s + 1; 495 | | | s := s + 1; 496 | | | s := s + 1; 497 | | | s := s + 1; 498 | | | s := s + 1; 499 | | | s := s + 1; 500 | | | s := s + 1; 501 | | | s := s + 1; 502 | | | s := s + 1; 503 | | | s := s + 1; 504 | | | s := s + 1; 505 | | | s := s + 1; 506 | | | s := s + 1; 507 | | | s := s + 1; 508 | | | s := s + 1; 509 | | | s := s + 1; 510 | | | s := s + 1; 511 | | | s := s + 1; 512 | | | s := s + 1; 513 | | | s := s + 1; 514 | | | s := s + 1; 515 | | | s := s + 1; 516 | | | s := s + 1; 517 | | | s := s + 1; 518 | | | s := s + 1; 519 | | | s := s + 1; 520 | | | s := s + 1; 521 | | | s := s + 1; 522 | | | s := s + 1; 523 | | | s := s + 1; 524 | | | s := s + 1; 525 | | | s := s + 1; 526 | | | s := s + 1; 527 | | | s := s + 1; 528 | | | s := s + 1; 529 | | | s := s + 1; 530 | | | s := s + 1; 531 | | | s := s + 1; 532 | | | s := s + 1; 533 | | | s := s + 1; 534 | | | s := s + 1; 535 | | | s := s + 1; 536 | | | s := s + 1; 537 | | | s := s + 1; 538 | | | s := s + 1; 539 | | | s := s + 1; 540 | | | s := s + 1; 541 | | | s := s + 1; 542 | | | s := s + 1; 543 | | | s := s + 1; 544 | | | s := s + 1; 545 | | | s := s + 1; 546 | | | s := s + 1; 547 | | | s := s + 1; 548 | | | s := s + 1; 549 | | | s := s + 1; 550 | | | s := s + 1; 551 | | | s := s + 1; 552 | | | s := s + 1; 553 | | | s := s + 1; 554 | | | s := s + 1; 555 | | | s := s + 1; 556 | | | s := s + 1; 557 | | | s := s + 1; 558 | | | s := s + 1; 559 | | | s := s + 1; 560 | | | s := s + 1; 561 | | | s := s + 1; 562 | | | s := s + 1; 563 | | | s := s + 1; 564 | | | s := s + 1; 565 | | | s := s + 1; 566 | | | s := s + 1; 567 | | | s := s + 1; 568 | | | s := s + 1; 569 | | | s := s + 1; 570 | | | s := s + 1; 571 | | | s := s + 1; 572 | | | s := s + 1; 573 | | | s := s + 1; 574 | | | s := s + 1; 575 | | | s := s + 1; 576 | | | s := s + 1; 577 | | | s := s + 1; 578 | | | s := s + 1; 579 | | | s := s + 1; 580 | | | s := s + 1; 581 | | | s := s + 1; 582 | | | s := s + 1; 583 | | | s := s + 1; 584 | | | s := s + 1; 585 | | | s := s + 1; 586 | | | s := s + 1; 587 | | | s := s + 1; 588 | | | s := s + 1; 589 | | | s := s + 1; 590 | | | s := s + 1; 591 | | | s := s + 1; 592 | | | s := s + 1; 593 | | | s := s + 1; 594 | | | s := s + 1; 595 | | | s := s + 1; 596 | | | s := s + 1; 597 | | | s := s + 1; 598 | | | s := s + 1; 599 | | | s := s + 1; 600 | | | s := s + 1; 601 | | | s := s + 1; 602 | | | s := s + 1; 603 | | | s := s + 1; 604 | | | s := s + 1; 605 | | | s := s + 1; 606 | | | s := s + 1; 607 | | | s := s + 1; 608 | | | s := s + 1; 609 | | | s := s + 1; 610 | | | s := s + 1; 611 | | | s := s + 1; 612 | | | s := s + 1; 613 | | | s := s + 1; 614 | | | s := s + 1; 615 | | | s := s + 1; 616 | | | s := s + 1; 617 | | | s := s + 1; 618 | | | s := s + 1; 619 | | | s := s + 1; 620 | | | s := s + 1; 621 | | | s := s + 1; 622 | | | s := s + 1; 623 | | | s := s + 1; 624 | | | s := s + 1; 625 | | | s := s + 1; 626 | | | s := s + 1; 627 | | | s := s + 1; 628 | | | s := s + 1; 629 | | | s := s + 1; 630 | | | s := s + 1; 631 | | | s := s + 1; 632 | | | s := s + 1; 633 | | | s := s + 1; 634 | | | s := s + 1; 635 | | | s := s + 1; 636 | | | s := s + 1; 637 | | | s := s + 1; 638 | | | s := s + 1; 639 | | | s := s + 1; 640 | | | s := s + 1; 641 | | | s := s + 1; 642 | | | s := s + 1; 643 | | | s := s + 1; 644 | | | s := s + 1; 645 | | | s := s + 1; 646 | | | s := s + 1; 647 | | | s := s + 1; 648 | | | s := s + 1; 649 | | | s := s + 1; 650 | | | s := s + 1; 651 | | | s := s + 1; 652 | | | s := s + 1; 653 | | | s := s + 1; 654 | | | s := s + 1; 655 | | | s := s + 1; 656 | | | s := s + 1; 657 | | | s := s + 1; 658 | | | s := s + 1; 659 | | | s := s + 1; 660 | | | s := s + 1; 661 | | | s := s + 1; 662 | | | s := s + 1; 663 | | | s := s + 1; 664 | | | s := s + 1; 665 | | | s := s + 1; 666 | | | s := s + 1; 667 | | | s := s + 1; 668 | | | s := s + 1; 669 | | | s := s + 1; 670 | | | s := s + 1; 671 | | | s := s + 1; 672 | | | s := s + 1; 673 | | | s := s + 1; 674 | | | s := s + 1; 675 | | | s := s + 1; 676 | | | s := s + 1; 677 | | | s := s + 1; 678 | | | s := s + 1; 679 | | | s := s + 1; 680 | | | s := s + 1; 681 | | | s := s + 1; 682 | | | s := s + 1; 683 | | | s := s + 1; 684 | | | s := s + 1; 685 | | | s := s + 1; 686 | | | s := s + 1; 687 | | | s := s + 1; 688 | | | s := s + 1; 689 | | | s := s + 1; 690 | | | s := s + 1; 691 | | | s := s + 1; 692 | | | s := s + 1; 693 | | | s := s + 1; 694 | | | s := s + 1; 695 | | | s := s + 1; 696 | | | s := s + 1; 697 | | | s := s + 1; 698 | | | s := s + 1; 699 | | | s := s + 1; 700 | | | s := s + 1; 701 | | | s := s + 1; 702 | | | s := s + 1; 703 | | | s := s + 1; 704 | | | s := s + 1; 705 | | | s := s + 1; 706 | | | s := s + 1; 707 | | | s := s + 1; 708 | | | s := s + 1; 709 | | | s := s + 1; 710 | | | s := s + 1; 711 | | | s := s + 1; 712 | | | s := s + 1; 713 | | | s := s + 1; 714 | | | s := s + 1; 715 | | | s := s + 1; 716 | | | s := s + 1; 717 | | | s := s + 1; 718 | | | s := s + 1; 719 | | | s := s + 1; 720 | | | s := s + 1; 721 | | | s := s + 1; 722 | | | s := s + 1; 723 | | | s := s + 1; 724 | | | s := s + 1; 725 | | | s := s + 1; 726 | | | s := s + 1; 727 | | | s := s + 1; 728 | | | s := s + 1; 729 | | | s := s + 1; 730 | | | s := s + 1; 731 | | | s := s + 1; 732 | | | s := s + 1; 733 | | | s := s + 1; 734 | | | s := s + 1; 735 | | | s := s + 1; 736 | | | s := s + 1; 737 | | | s := s + 1; 738 | | | s := s + 1; 739 | | | s := s + 1; 740 | | | s := s + 1; 741 | | | s := s + 1; 742 | | | s := s + 1; 743 | | | s := s + 1; 744 | | | s := s + 1; 745 | | | s := s + 1; 746 | | | s := s + 1; 747 | | | s := s + 1; 748 | | | s := s + 1; 749 | | | s := s + 1; 750 | | | s := s + 1; 751 | | | s := s + 1; 752 | | | s := s + 1; 753 | | | s := s + 1; 754 | | | s := s + 1; 755 | | | s := s + 1; 756 | | | s := s + 1; 757 | | | s := s + 1; 758 | | | s := s + 1; 759 | | | s := s + 1; 760 | | | s := s + 1; 761 | | | s := s + 1; 762 | | | s := s + 1; 763 | | | s := s + 1; 764 | | | s := s + 1; 765 | | | s := s + 1; 766 | | | s := s + 1; 767 | | | s := s + 1; 768 | | | s := s + 1; 769 | | | s := s + 1; 770 | | | s := s + 1; 771 | | | s := s + 1; 772 | | | s := s + 1; 773 | | | s := s + 1; 774 | | | s := s + 1; 775 | | | s := s + 1; 776 | | | s := s + 1; 777 | | | s := s + 1; 778 | | | s := s + 1; 779 | | | s := s + 1; 780 | | | s := s + 1; 781 | | | s := s + 1; 782 | | | s := s + 1; 783 | | | s := s + 1; 784 | | | s := s + 1; 785 | | | s := s + 1; 786 | | | s := s + 1; 787 | | | s := s + 1; 788 | | | s := s + 1; 789 | | | s := s + 1; 790 | | | s := s + 1; 791 | | | s := s + 1; 792 | | | s := s + 1; 793 | | | s := s + 1; 794 | | | s := s + 1; 795 | | | s := s + 1; 796 | | | s := s + 1; 797 | | | s := s + 1; 798 | | | s := s + 1; 799 | | | s := s + 1; 800 | | | s := s + 1; 801 | | | s := s + 1; 802 | | | s := s + 1; 803 | | | s := s + 1; 804 | | | s := s + 1; 805 | | | s := s + 1; 806 | | | s := s + 1; 807 | | | s := s + 1; 808 | | | s := s + 1; 809 | | | s := s + 1; 810 | | | s := s + 1; 811 | | | s := s + 1; 812 | | | s := s + 1; 813 | | | s := s + 1; 814 | | | s := s + 1; 815 | | | s := s + 1; 816 | | | s := s + 1; 817 | | | s := s + 1; 818 | | | s := s + 1; 819 | | | s := s + 1; 820 | | | s := s + 1; 821 | | | s := s + 1; 822 | | | s := s + 1; 823 | | | s := s + 1; 824 | | | s := s + 1; 825 | | | s := s + 1; 826 | | | s := s + 1; 827 | | | s := s + 1; 828 | | | s := s + 1; 829 | | | s := s + 1; 830 | | | s := s + 1; 831 | | | s := s + 1; 832 | | | s := s + 1; 833 | | | s := s + 1; 834 | | | s := s + 1; 835 | | | s := s + 1; 836 | | | s := s + 1; 837 | | | s := s + 1; 838 | | | s := s + 1; 839 | | | s := s + 1; 840 | | | s := s + 1; 841 | | | s := s + 1; 842 | | | s := s + 1; 843 | | | s := s + 1; 844 | | | s := s + 1; 845 | | | s := s + 1; 846 | | | s := s + 1; 847 | | | s := s + 1; 848 | | | s := s + 1; 849 | | | s := s + 1; 850 | | | s := s + 1; 851 | | | s := s + 1; 852 | | | s := s + 1; 853 | | | s := s + 1; 854 | | | s := s + 1; 855 | | | s := s + 1; 856 | | | s := s + 1; 857 | | | s := s + 1; 858 | | | s := s + 1; 859 | | | s := s + 1; 860 | | | s := s + 1; 861 | | | s := s + 1; 862 | | | s := s + 1; 863 | | | s := s + 1; 864 | | | s := s + 1; 865 | | | s := s + 1; 866 | | | s := s + 1; 867 | | | s := s + 1; 868 | | | s := s + 1; 869 | | | s := s + 1; 870 | | | s := s + 1; 871 | | | s := s + 1; 872 | | | s := s + 1; 873 | | | s := s + 1; 874 | | | s := s + 1; 875 | | | s := s + 1; 876 | | | s := s + 1; 877 | | | s := s + 1; 878 | | | s := s + 1; 879 | | | s := s + 1; 880 | | | s := s + 1; 881 | | | s := s + 1; 882 | | | s := s + 1; 883 | | | s := s + 1; 884 | | | s := s + 1; 885 | | | s := s + 1; 886 | | | s := s + 1; 887 | | | s := s + 1; 888 | | | s := s + 1; 889 | | | s := s + 1; 890 | | | s := s + 1; 891 | | | s := s + 1; 892 | | | s := s + 1; 893 | | | s := s + 1; 894 | | | s := s + 1; 895 | | | s := s + 1; 896 | | | s := s + 1; 897 | | | s := s + 1; 898 | | | s := s + 1; 899 | | | s := s + 1; 900 | | | s := s + 1; 901 | | | s := s + 1; 902 | | | s := s + 1; 903 | | | s := s + 1; 904 | | | s := s + 1; 905 | | | s := s + 1; 906 | | | s := s + 1; 907 | | | s := s + 1; 908 | | | s := s + 1; 909 | | | s := s + 1; 910 | | | s := s + 1; 911 | | | s := s + 1; 912 | | | s := s + 1; 913 | | | s := s + 1; 914 | | | s := s + 1; 915 | | | s := s + 1; 916 | | | s := s + 1; 917 | | | s := s + 1; 918 | | | s := s + 1; 919 | | | s := s + 1; 920 | | | s := s + 1; 921 | | | s := s + 1; 922 | | | s := s + 1; 923 | | | s := s + 1; 924 | | | s := s + 1; 925 | | | s := s + 1; 926 | | | s := s + 1; 927 | | | s := s + 1; 928 | | | s := s + 1; 929 | | | s := s + 1; 930 | | | s := s + 1; 931 | | | s := s + 1; 932 | | | s := s + 1; 933 | | | s := s + 1; 934 | | | s := s + 1; 935 | | | s := s + 1; 936 | | | s := s + 1; 937 | | | s := s + 1; 938 | | | s := s + 1; 939 | | | s := s + 1; 940 | | | s := s + 1; 941 | | | s := s + 1; 942 | | | s := s + 1; 943 | | | s := s + 1; 944 | | | s := s + 1; 945 | | | s := s + 1; 946 | | | s := s + 1; 947 | | | s := s + 1; 948 | | | s := s + 1; 949 | | | s := s + 1; 950 | | | s := s + 1; 951 | | | s := s + 1; 952 | | | s := s + 1; 953 | | | s := s + 1; 954 | | | s := s + 1; 955 | | | s := s + 1; 956 | | | s := s + 1; 957 | | | s := s + 1; 958 | | | s := s + 1; 959 | | | s := s + 1; 960 | | | s := s + 1; 961 | | | s := s + 1; 962 | | | s := s + 1; 963 | | | s := s + 1; 964 | | | s := s + 1; 965 | | | s := s + 1; 966 | | | s := s + 1; 967 | | | s := s + 1; 968 | | | s := s + 1; 969 | | | s := s + 1; 970 | | | s := s + 1; 971 | | | s := s + 1; 972 | | | s := s + 1; 973 | | | s := s + 1; 974 | | | s := s + 1; 975 | | | s := s + 1; 976 | | | s := s + 1; 977 | | | s := s + 1; 978 | | | s := s + 1; 979 | | | s := s + 1; 980 | | | s := s + 1; 981 | | | s := s + 1; 982 | | | s := s + 1; 983 | | | s := s + 1; 984 | | | s := s + 1; 985 | | | s := s + 1; 986 | | | s := s + 1; 987 | | | s := s + 1; 988 | | | s := s + 1; 989 | | | s := s + 1; 990 | | | s := s + 1; 991 | | | s := s + 1; 992 | | | s := s + 1; 993 | | | s := s + 1; 994 | | | s := s + 1; 995 | | | s := s + 1; 996 | | | s := s + 1; 997 | | | s := s + 1; 998 | | | s := s + 1; 999 | | | s := s + 1; 1000 | | | s := s + 1; 1001 | | | s := s + 1; 1002 | | | s := s + 1; 1003 | | | s := s + 1; 1004 | | | s := s + 1; 1005 | | | s := s + 1; 1006 | | | s := s + 1; 1007 | | | s := s + 1; 1008 | | | s := s + 1; 1009 | | | s := s + 1; 1010 | | | s := s + 1; 1011 | | | s := s + 1; 1012 | | | s := s + 1; 1013 | | | s := s + 1; 1014 | | | s := s + 1; 1015 | | | s := s + 1; 1016 | | | s := s + 1; 1017 | | | s := s + 1; 1018 | | | s := s + 1; 1019 | | | s := s + 1; 1020 | | | s := s + 1; 1021 | | | s := s + 1; 1022 | | | s := s + 1; 1023 | | | s := s + 1; 1024 | | | s := s + 1; 1025 | | | s := s + 1; 1026 | | | s := s + 1; 1027 | | | s := s + 1; 1028 | | | s := s + 1; 1029 | | | s := s + 1; 1030 | | | s := s + 1; 1031 | | | s := s + 1; 1032 | | | s := s + 1; 1033 | | | s := s + 1; 1034 | | | s := s + 1; 1035 | | | s := s + 1; 1036 | | | s := s + 1; 1037 | | | s := s + 1; 1038 | | | end loop; 1039 | | | end loop; 1040 | | | j := j + 1; 1041 | | | end loop; 1042 | | | exception when others then 1043 | | | raise 'reraised exception %', sqlerrm; 1044 | | | end; 1045 | | | return $1; 1046 | | | end; (1046 rows) set plpgsql_check.profiler = on; select longfx(10); longfx -------- 10 (1 row) select longfx(10); longfx -------- 10 (1 row) set plpgsql_check.profiler = off; select longfx(10); longfx -------- 10 (1 row) select lineno, stmt_lineno, exec_stmts, source from plpgsql_profiler_function_tb('longfx'); lineno | stmt_lineno | exec_stmts | source --------+-------------+------------+----------------------------------------------------- 1 | | | 2 | | | declare 3 | | | s int default 0; 4 | | | j int default 0; 5 | | | r record; 6 | 6 | 2 | begin 7 | 7 | 2 | begin 8 | 8 | 2 | while j < 10 9 | | | loop 10 | 10 | 20 | for i in 1..1 11 | | | loop 12 | 12 | 20 | for r in select * from generate_series(1,1) 13 | | | loop 14 | 14 | 20 | s := s + 1; 15 | 15 | 20 | s := s + 1; 16 | 16 | 20 | s := s + 1; 17 | 17 | 20 | s := s + 1; 18 | 18 | 20 | s := s + 1; 19 | 19 | 20 | s := s + 1; 20 | 20 | 20 | s := s + 1; 21 | 21 | 20 | s := s + 1; 22 | 22 | 20 | s := s + 1; 23 | 23 | 20 | s := s + 1; 24 | 24 | 20 | s := s + 1; 25 | 25 | 20 | s := s + 1; 26 | 26 | 20 | s := s + 1; 27 | 27 | 20 | s := s + 1; 28 | 28 | 20 | s := s + 1; 29 | 29 | 20 | s := s + 1; 30 | 30 | 20 | s := s + 1; 31 | 31 | 20 | s := s + 1; 32 | 32 | 20 | s := s + 1; 33 | 33 | 20 | s := s + 1; 34 | 34 | 20 | s := s + 1; 35 | 35 | 20 | s := s + 1; 36 | 36 | 20 | s := s + 1; 37 | 37 | 20 | s := s + 1; 38 | 38 | 20 | s := s + 1; 39 | 39 | 20 | s := s + 1; 40 | 40 | 20 | s := s + 1; 41 | 41 | 20 | s := s + 1; 42 | 42 | 20 | s := s + 1; 43 | 43 | 20 | s := s + 1; 44 | 44 | 20 | s := s + 1; 45 | 45 | 20 | s := s + 1; 46 | 46 | 20 | s := s + 1; 47 | 47 | 20 | s := s + 1; 48 | 48 | 20 | s := s + 1; 49 | 49 | 20 | s := s + 1; 50 | 50 | 20 | s := s + 1; 51 | 51 | 20 | s := s + 1; 52 | 52 | 20 | s := s + 1; 53 | 53 | 20 | s := s + 1; 54 | 54 | 20 | s := s + 1; 55 | 55 | 20 | s := s + 1; 56 | 56 | 20 | s := s + 1; 57 | 57 | 20 | s := s + 1; 58 | 58 | 20 | s := s + 1; 59 | 59 | 20 | s := s + 1; 60 | 60 | 20 | s := s + 1; 61 | 61 | 20 | s := s + 1; 62 | 62 | 20 | s := s + 1; 63 | 63 | 20 | s := s + 1; 64 | 64 | 20 | s := s + 1; 65 | 65 | 20 | s := s + 1; 66 | 66 | 20 | s := s + 1; 67 | 67 | 20 | s := s + 1; 68 | 68 | 20 | s := s + 1; 69 | 69 | 20 | s := s + 1; 70 | 70 | 20 | s := s + 1; 71 | 71 | 20 | s := s + 1; 72 | 72 | 20 | s := s + 1; 73 | 73 | 20 | s := s + 1; 74 | 74 | 20 | s := s + 1; 75 | 75 | 20 | s := s + 1; 76 | 76 | 20 | s := s + 1; 77 | 77 | 20 | s := s + 1; 78 | 78 | 20 | s := s + 1; 79 | 79 | 20 | s := s + 1; 80 | 80 | 20 | s := s + 1; 81 | 81 | 20 | s := s + 1; 82 | 82 | 20 | s := s + 1; 83 | 83 | 20 | s := s + 1; 84 | 84 | 20 | s := s + 1; 85 | 85 | 20 | s := s + 1; 86 | 86 | 20 | s := s + 1; 87 | 87 | 20 | s := s + 1; 88 | 88 | 20 | s := s + 1; 89 | 89 | 20 | s := s + 1; 90 | 90 | 20 | s := s + 1; 91 | 91 | 20 | s := s + 1; 92 | 92 | 20 | s := s + 1; 93 | 93 | 20 | s := s + 1; 94 | 94 | 20 | s := s + 1; 95 | 95 | 20 | s := s + 1; 96 | 96 | 20 | s := s + 1; 97 | 97 | 20 | s := s + 1; 98 | 98 | 20 | s := s + 1; 99 | 99 | 20 | s := s + 1; 100 | 100 | 20 | s := s + 1; 101 | 101 | 20 | s := s + 1; 102 | 102 | 20 | s := s + 1; 103 | 103 | 20 | s := s + 1; 104 | 104 | 20 | s := s + 1; 105 | 105 | 20 | s := s + 1; 106 | 106 | 20 | s := s + 1; 107 | 107 | 20 | s := s + 1; 108 | 108 | 20 | s := s + 1; 109 | 109 | 20 | s := s + 1; 110 | 110 | 20 | s := s + 1; 111 | 111 | 20 | s := s + 1; 112 | 112 | 20 | s := s + 1; 113 | 113 | 20 | s := s + 1; 114 | 114 | 20 | s := s + 1; 115 | 115 | 20 | s := s + 1; 116 | 116 | 20 | s := s + 1; 117 | 117 | 20 | s := s + 1; 118 | 118 | 20 | s := s + 1; 119 | 119 | 20 | s := s + 1; 120 | 120 | 20 | s := s + 1; 121 | 121 | 20 | s := s + 1; 122 | 122 | 20 | s := s + 1; 123 | 123 | 20 | s := s + 1; 124 | 124 | 20 | s := s + 1; 125 | 125 | 20 | s := s + 1; 126 | 126 | 20 | s := s + 1; 127 | 127 | 20 | s := s + 1; 128 | 128 | 20 | s := s + 1; 129 | 129 | 20 | s := s + 1; 130 | 130 | 20 | s := s + 1; 131 | 131 | 20 | s := s + 1; 132 | 132 | 20 | s := s + 1; 133 | 133 | 20 | s := s + 1; 134 | 134 | 20 | s := s + 1; 135 | 135 | 20 | s := s + 1; 136 | 136 | 20 | s := s + 1; 137 | 137 | 20 | s := s + 1; 138 | 138 | 20 | s := s + 1; 139 | 139 | 20 | s := s + 1; 140 | 140 | 20 | s := s + 1; 141 | 141 | 20 | s := s + 1; 142 | 142 | 20 | s := s + 1; 143 | 143 | 20 | s := s + 1; 144 | 144 | 20 | s := s + 1; 145 | 145 | 20 | s := s + 1; 146 | 146 | 20 | s := s + 1; 147 | 147 | 20 | s := s + 1; 148 | 148 | 20 | s := s + 1; 149 | 149 | 20 | s := s + 1; 150 | 150 | 20 | s := s + 1; 151 | 151 | 20 | s := s + 1; 152 | 152 | 20 | s := s + 1; 153 | 153 | 20 | s := s + 1; 154 | 154 | 20 | s := s + 1; 155 | 155 | 20 | s := s + 1; 156 | 156 | 20 | s := s + 1; 157 | 157 | 20 | s := s + 1; 158 | 158 | 20 | s := s + 1; 159 | 159 | 20 | s := s + 1; 160 | 160 | 20 | s := s + 1; 161 | 161 | 20 | s := s + 1; 162 | 162 | 20 | s := s + 1; 163 | 163 | 20 | s := s + 1; 164 | 164 | 20 | s := s + 1; 165 | 165 | 20 | s := s + 1; 166 | 166 | 20 | s := s + 1; 167 | 167 | 20 | s := s + 1; 168 | 168 | 20 | s := s + 1; 169 | 169 | 20 | s := s + 1; 170 | 170 | 20 | s := s + 1; 171 | 171 | 20 | s := s + 1; 172 | 172 | 20 | s := s + 1; 173 | 173 | 20 | s := s + 1; 174 | 174 | 20 | s := s + 1; 175 | 175 | 20 | s := s + 1; 176 | 176 | 20 | s := s + 1; 177 | 177 | 20 | s := s + 1; 178 | 178 | 20 | s := s + 1; 179 | 179 | 20 | s := s + 1; 180 | 180 | 20 | s := s + 1; 181 | 181 | 20 | s := s + 1; 182 | 182 | 20 | s := s + 1; 183 | 183 | 20 | s := s + 1; 184 | 184 | 20 | s := s + 1; 185 | 185 | 20 | s := s + 1; 186 | 186 | 20 | s := s + 1; 187 | 187 | 20 | s := s + 1; 188 | 188 | 20 | s := s + 1; 189 | 189 | 20 | s := s + 1; 190 | 190 | 20 | s := s + 1; 191 | 191 | 20 | s := s + 1; 192 | 192 | 20 | s := s + 1; 193 | 193 | 20 | s := s + 1; 194 | 194 | 20 | s := s + 1; 195 | 195 | 20 | s := s + 1; 196 | 196 | 20 | s := s + 1; 197 | 197 | 20 | s := s + 1; 198 | 198 | 20 | s := s + 1; 199 | 199 | 20 | s := s + 1; 200 | 200 | 20 | s := s + 1; 201 | 201 | 20 | s := s + 1; 202 | 202 | 20 | s := s + 1; 203 | 203 | 20 | s := s + 1; 204 | 204 | 20 | s := s + 1; 205 | 205 | 20 | s := s + 1; 206 | 206 | 20 | s := s + 1; 207 | 207 | 20 | s := s + 1; 208 | 208 | 20 | s := s + 1; 209 | 209 | 20 | s := s + 1; 210 | 210 | 20 | s := s + 1; 211 | 211 | 20 | s := s + 1; 212 | 212 | 20 | s := s + 1; 213 | 213 | 20 | s := s + 1; 214 | 214 | 20 | s := s + 1; 215 | 215 | 20 | s := s + 1; 216 | 216 | 20 | s := s + 1; 217 | 217 | 20 | s := s + 1; 218 | 218 | 20 | s := s + 1; 219 | 219 | 20 | s := s + 1; 220 | 220 | 20 | s := s + 1; 221 | 221 | 20 | s := s + 1; 222 | 222 | 20 | s := s + 1; 223 | 223 | 20 | s := s + 1; 224 | 224 | 20 | s := s + 1; 225 | 225 | 20 | s := s + 1; 226 | 226 | 20 | s := s + 1; 227 | 227 | 20 | s := s + 1; 228 | 228 | 20 | s := s + 1; 229 | 229 | 20 | s := s + 1; 230 | 230 | 20 | s := s + 1; 231 | 231 | 20 | s := s + 1; 232 | 232 | 20 | s := s + 1; 233 | 233 | 20 | s := s + 1; 234 | 234 | 20 | s := s + 1; 235 | 235 | 20 | s := s + 1; 236 | 236 | 20 | s := s + 1; 237 | 237 | 20 | s := s + 1; 238 | 238 | 20 | s := s + 1; 239 | 239 | 20 | s := s + 1; 240 | 240 | 20 | s := s + 1; 241 | 241 | 20 | s := s + 1; 242 | 242 | 20 | s := s + 1; 243 | 243 | 20 | s := s + 1; 244 | 244 | 20 | s := s + 1; 245 | 245 | 20 | s := s + 1; 246 | 246 | 20 | s := s + 1; 247 | 247 | 20 | s := s + 1; 248 | 248 | 20 | s := s + 1; 249 | 249 | 20 | s := s + 1; 250 | 250 | 20 | s := s + 1; 251 | 251 | 20 | s := s + 1; 252 | 252 | 20 | s := s + 1; 253 | 253 | 20 | s := s + 1; 254 | 254 | 20 | s := s + 1; 255 | 255 | 20 | s := s + 1; 256 | 256 | 20 | s := s + 1; 257 | 257 | 20 | s := s + 1; 258 | 258 | 20 | s := s + 1; 259 | 259 | 20 | s := s + 1; 260 | 260 | 20 | s := s + 1; 261 | 261 | 20 | s := s + 1; 262 | 262 | 20 | s := s + 1; 263 | 263 | 20 | s := s + 1; 264 | 264 | 20 | s := s + 1; 265 | 265 | 20 | s := s + 1; 266 | 266 | 20 | s := s + 1; 267 | 267 | 20 | s := s + 1; 268 | 268 | 20 | s := s + 1; 269 | 269 | 20 | s := s + 1; 270 | 270 | 20 | s := s + 1; 271 | 271 | 20 | s := s + 1; 272 | 272 | 20 | s := s + 1; 273 | 273 | 20 | s := s + 1; 274 | 274 | 20 | s := s + 1; 275 | 275 | 20 | s := s + 1; 276 | 276 | 20 | s := s + 1; 277 | 277 | 20 | s := s + 1; 278 | 278 | 20 | s := s + 1; 279 | 279 | 20 | s := s + 1; 280 | 280 | 20 | s := s + 1; 281 | 281 | 20 | s := s + 1; 282 | 282 | 20 | s := s + 1; 283 | 283 | 20 | s := s + 1; 284 | 284 | 20 | s := s + 1; 285 | 285 | 20 | s := s + 1; 286 | 286 | 20 | s := s + 1; 287 | 287 | 20 | s := s + 1; 288 | 288 | 20 | s := s + 1; 289 | 289 | 20 | s := s + 1; 290 | 290 | 20 | s := s + 1; 291 | 291 | 20 | s := s + 1; 292 | 292 | 20 | s := s + 1; 293 | 293 | 20 | s := s + 1; 294 | 294 | 20 | s := s + 1; 295 | 295 | 20 | s := s + 1; 296 | 296 | 20 | s := s + 1; 297 | 297 | 20 | s := s + 1; 298 | 298 | 20 | s := s + 1; 299 | 299 | 20 | s := s + 1; 300 | 300 | 20 | s := s + 1; 301 | 301 | 20 | s := s + 1; 302 | 302 | 20 | s := s + 1; 303 | 303 | 20 | s := s + 1; 304 | 304 | 20 | s := s + 1; 305 | 305 | 20 | s := s + 1; 306 | 306 | 20 | s := s + 1; 307 | 307 | 20 | s := s + 1; 308 | 308 | 20 | s := s + 1; 309 | 309 | 20 | s := s + 1; 310 | 310 | 20 | s := s + 1; 311 | 311 | 20 | s := s + 1; 312 | 312 | 20 | s := s + 1; 313 | 313 | 20 | s := s + 1; 314 | 314 | 20 | s := s + 1; 315 | 315 | 20 | s := s + 1; 316 | 316 | 20 | s := s + 1; 317 | 317 | 20 | s := s + 1; 318 | 318 | 20 | s := s + 1; 319 | 319 | 20 | s := s + 1; 320 | 320 | 20 | s := s + 1; 321 | 321 | 20 | s := s + 1; 322 | 322 | 20 | s := s + 1; 323 | 323 | 20 | s := s + 1; 324 | 324 | 20 | s := s + 1; 325 | 325 | 20 | s := s + 1; 326 | 326 | 20 | s := s + 1; 327 | 327 | 20 | s := s + 1; 328 | 328 | 20 | s := s + 1; 329 | 329 | 20 | s := s + 1; 330 | 330 | 20 | s := s + 1; 331 | 331 | 20 | s := s + 1; 332 | 332 | 20 | s := s + 1; 333 | 333 | 20 | s := s + 1; 334 | 334 | 20 | s := s + 1; 335 | 335 | 20 | s := s + 1; 336 | 336 | 20 | s := s + 1; 337 | 337 | 20 | s := s + 1; 338 | 338 | 20 | s := s + 1; 339 | 339 | 20 | s := s + 1; 340 | 340 | 20 | s := s + 1; 341 | 341 | 20 | s := s + 1; 342 | 342 | 20 | s := s + 1; 343 | 343 | 20 | s := s + 1; 344 | 344 | 20 | s := s + 1; 345 | 345 | 20 | s := s + 1; 346 | 346 | 20 | s := s + 1; 347 | 347 | 20 | s := s + 1; 348 | 348 | 20 | s := s + 1; 349 | 349 | 20 | s := s + 1; 350 | 350 | 20 | s := s + 1; 351 | 351 | 20 | s := s + 1; 352 | 352 | 20 | s := s + 1; 353 | 353 | 20 | s := s + 1; 354 | 354 | 20 | s := s + 1; 355 | 355 | 20 | s := s + 1; 356 | 356 | 20 | s := s + 1; 357 | 357 | 20 | s := s + 1; 358 | 358 | 20 | s := s + 1; 359 | 359 | 20 | s := s + 1; 360 | 360 | 20 | s := s + 1; 361 | 361 | 20 | s := s + 1; 362 | 362 | 20 | s := s + 1; 363 | 363 | 20 | s := s + 1; 364 | 364 | 20 | s := s + 1; 365 | 365 | 20 | s := s + 1; 366 | 366 | 20 | s := s + 1; 367 | 367 | 20 | s := s + 1; 368 | 368 | 20 | s := s + 1; 369 | 369 | 20 | s := s + 1; 370 | 370 | 20 | s := s + 1; 371 | 371 | 20 | s := s + 1; 372 | 372 | 20 | s := s + 1; 373 | 373 | 20 | s := s + 1; 374 | 374 | 20 | s := s + 1; 375 | 375 | 20 | s := s + 1; 376 | 376 | 20 | s := s + 1; 377 | 377 | 20 | s := s + 1; 378 | 378 | 20 | s := s + 1; 379 | 379 | 20 | s := s + 1; 380 | 380 | 20 | s := s + 1; 381 | 381 | 20 | s := s + 1; 382 | 382 | 20 | s := s + 1; 383 | 383 | 20 | s := s + 1; 384 | 384 | 20 | s := s + 1; 385 | 385 | 20 | s := s + 1; 386 | 386 | 20 | s := s + 1; 387 | 387 | 20 | s := s + 1; 388 | 388 | 20 | s := s + 1; 389 | 389 | 20 | s := s + 1; 390 | 390 | 20 | s := s + 1; 391 | 391 | 20 | s := s + 1; 392 | 392 | 20 | s := s + 1; 393 | 393 | 20 | s := s + 1; 394 | 394 | 20 | s := s + 1; 395 | 395 | 20 | s := s + 1; 396 | 396 | 20 | s := s + 1; 397 | 397 | 20 | s := s + 1; 398 | 398 | 20 | s := s + 1; 399 | 399 | 20 | s := s + 1; 400 | 400 | 20 | s := s + 1; 401 | 401 | 20 | s := s + 1; 402 | 402 | 20 | s := s + 1; 403 | 403 | 20 | s := s + 1; 404 | 404 | 20 | s := s + 1; 405 | 405 | 20 | s := s + 1; 406 | 406 | 20 | s := s + 1; 407 | 407 | 20 | s := s + 1; 408 | 408 | 20 | s := s + 1; 409 | 409 | 20 | s := s + 1; 410 | 410 | 20 | s := s + 1; 411 | 411 | 20 | s := s + 1; 412 | 412 | 20 | s := s + 1; 413 | 413 | 20 | s := s + 1; 414 | 414 | 20 | s := s + 1; 415 | 415 | 20 | s := s + 1; 416 | 416 | 20 | s := s + 1; 417 | 417 | 20 | s := s + 1; 418 | 418 | 20 | s := s + 1; 419 | 419 | 20 | s := s + 1; 420 | 420 | 20 | s := s + 1; 421 | 421 | 20 | s := s + 1; 422 | 422 | 20 | s := s + 1; 423 | 423 | 20 | s := s + 1; 424 | 424 | 20 | s := s + 1; 425 | 425 | 20 | s := s + 1; 426 | 426 | 20 | s := s + 1; 427 | 427 | 20 | s := s + 1; 428 | 428 | 20 | s := s + 1; 429 | 429 | 20 | s := s + 1; 430 | 430 | 20 | s := s + 1; 431 | 431 | 20 | s := s + 1; 432 | 432 | 20 | s := s + 1; 433 | 433 | 20 | s := s + 1; 434 | 434 | 20 | s := s + 1; 435 | 435 | 20 | s := s + 1; 436 | 436 | 20 | s := s + 1; 437 | 437 | 20 | s := s + 1; 438 | 438 | 20 | s := s + 1; 439 | 439 | 20 | s := s + 1; 440 | 440 | 20 | s := s + 1; 441 | 441 | 20 | s := s + 1; 442 | 442 | 20 | s := s + 1; 443 | 443 | 20 | s := s + 1; 444 | 444 | 20 | s := s + 1; 445 | 445 | 20 | s := s + 1; 446 | 446 | 20 | s := s + 1; 447 | 447 | 20 | s := s + 1; 448 | 448 | 20 | s := s + 1; 449 | 449 | 20 | s := s + 1; 450 | 450 | 20 | s := s + 1; 451 | 451 | 20 | s := s + 1; 452 | 452 | 20 | s := s + 1; 453 | 453 | 20 | s := s + 1; 454 | 454 | 20 | s := s + 1; 455 | 455 | 20 | s := s + 1; 456 | 456 | 20 | s := s + 1; 457 | 457 | 20 | s := s + 1; 458 | 458 | 20 | s := s + 1; 459 | 459 | 20 | s := s + 1; 460 | 460 | 20 | s := s + 1; 461 | 461 | 20 | s := s + 1; 462 | 462 | 20 | s := s + 1; 463 | 463 | 20 | s := s + 1; 464 | 464 | 20 | s := s + 1; 465 | 465 | 20 | s := s + 1; 466 | 466 | 20 | s := s + 1; 467 | 467 | 20 | s := s + 1; 468 | 468 | 20 | s := s + 1; 469 | 469 | 20 | s := s + 1; 470 | 470 | 20 | s := s + 1; 471 | 471 | 20 | s := s + 1; 472 | 472 | 20 | s := s + 1; 473 | 473 | 20 | s := s + 1; 474 | 474 | 20 | s := s + 1; 475 | 475 | 20 | s := s + 1; 476 | 476 | 20 | s := s + 1; 477 | 477 | 20 | s := s + 1; 478 | 478 | 20 | s := s + 1; 479 | 479 | 20 | s := s + 1; 480 | 480 | 20 | s := s + 1; 481 | 481 | 20 | s := s + 1; 482 | 482 | 20 | s := s + 1; 483 | 483 | 20 | s := s + 1; 484 | 484 | 20 | s := s + 1; 485 | 485 | 20 | s := s + 1; 486 | 486 | 20 | s := s + 1; 487 | 487 | 20 | s := s + 1; 488 | 488 | 20 | s := s + 1; 489 | 489 | 20 | s := s + 1; 490 | 490 | 20 | s := s + 1; 491 | 491 | 20 | s := s + 1; 492 | 492 | 20 | s := s + 1; 493 | 493 | 20 | s := s + 1; 494 | 494 | 20 | s := s + 1; 495 | 495 | 20 | s := s + 1; 496 | 496 | 20 | s := s + 1; 497 | 497 | 20 | s := s + 1; 498 | 498 | 20 | s := s + 1; 499 | 499 | 20 | s := s + 1; 500 | 500 | 20 | s := s + 1; 501 | 501 | 20 | s := s + 1; 502 | 502 | 20 | s := s + 1; 503 | 503 | 20 | s := s + 1; 504 | 504 | 20 | s := s + 1; 505 | 505 | 20 | s := s + 1; 506 | 506 | 20 | s := s + 1; 507 | 507 | 20 | s := s + 1; 508 | 508 | 20 | s := s + 1; 509 | 509 | 20 | s := s + 1; 510 | 510 | 20 | s := s + 1; 511 | 511 | 20 | s := s + 1; 512 | 512 | 20 | s := s + 1; 513 | 513 | 20 | s := s + 1; 514 | 514 | 20 | s := s + 1; 515 | 515 | 20 | s := s + 1; 516 | 516 | 20 | s := s + 1; 517 | 517 | 20 | s := s + 1; 518 | 518 | 20 | s := s + 1; 519 | 519 | 20 | s := s + 1; 520 | 520 | 20 | s := s + 1; 521 | 521 | 20 | s := s + 1; 522 | 522 | 20 | s := s + 1; 523 | 523 | 20 | s := s + 1; 524 | 524 | 20 | s := s + 1; 525 | 525 | 20 | s := s + 1; 526 | 526 | 20 | s := s + 1; 527 | 527 | 20 | s := s + 1; 528 | 528 | 20 | s := s + 1; 529 | 529 | 20 | s := s + 1; 530 | 530 | 20 | s := s + 1; 531 | 531 | 20 | s := s + 1; 532 | 532 | 20 | s := s + 1; 533 | 533 | 20 | s := s + 1; 534 | 534 | 20 | s := s + 1; 535 | 535 | 20 | s := s + 1; 536 | 536 | 20 | s := s + 1; 537 | 537 | 20 | s := s + 1; 538 | 538 | 20 | s := s + 1; 539 | 539 | 20 | s := s + 1; 540 | 540 | 20 | s := s + 1; 541 | 541 | 20 | s := s + 1; 542 | 542 | 20 | s := s + 1; 543 | 543 | 20 | s := s + 1; 544 | 544 | 20 | s := s + 1; 545 | 545 | 20 | s := s + 1; 546 | 546 | 20 | s := s + 1; 547 | 547 | 20 | s := s + 1; 548 | 548 | 20 | s := s + 1; 549 | 549 | 20 | s := s + 1; 550 | 550 | 20 | s := s + 1; 551 | 551 | 20 | s := s + 1; 552 | 552 | 20 | s := s + 1; 553 | 553 | 20 | s := s + 1; 554 | 554 | 20 | s := s + 1; 555 | 555 | 20 | s := s + 1; 556 | 556 | 20 | s := s + 1; 557 | 557 | 20 | s := s + 1; 558 | 558 | 20 | s := s + 1; 559 | 559 | 20 | s := s + 1; 560 | 560 | 20 | s := s + 1; 561 | 561 | 20 | s := s + 1; 562 | 562 | 20 | s := s + 1; 563 | 563 | 20 | s := s + 1; 564 | 564 | 20 | s := s + 1; 565 | 565 | 20 | s := s + 1; 566 | 566 | 20 | s := s + 1; 567 | 567 | 20 | s := s + 1; 568 | 568 | 20 | s := s + 1; 569 | 569 | 20 | s := s + 1; 570 | 570 | 20 | s := s + 1; 571 | 571 | 20 | s := s + 1; 572 | 572 | 20 | s := s + 1; 573 | 573 | 20 | s := s + 1; 574 | 574 | 20 | s := s + 1; 575 | 575 | 20 | s := s + 1; 576 | 576 | 20 | s := s + 1; 577 | 577 | 20 | s := s + 1; 578 | 578 | 20 | s := s + 1; 579 | 579 | 20 | s := s + 1; 580 | 580 | 20 | s := s + 1; 581 | 581 | 20 | s := s + 1; 582 | 582 | 20 | s := s + 1; 583 | 583 | 20 | s := s + 1; 584 | 584 | 20 | s := s + 1; 585 | 585 | 20 | s := s + 1; 586 | 586 | 20 | s := s + 1; 587 | 587 | 20 | s := s + 1; 588 | 588 | 20 | s := s + 1; 589 | 589 | 20 | s := s + 1; 590 | 590 | 20 | s := s + 1; 591 | 591 | 20 | s := s + 1; 592 | 592 | 20 | s := s + 1; 593 | 593 | 20 | s := s + 1; 594 | 594 | 20 | s := s + 1; 595 | 595 | 20 | s := s + 1; 596 | 596 | 20 | s := s + 1; 597 | 597 | 20 | s := s + 1; 598 | 598 | 20 | s := s + 1; 599 | 599 | 20 | s := s + 1; 600 | 600 | 20 | s := s + 1; 601 | 601 | 20 | s := s + 1; 602 | 602 | 20 | s := s + 1; 603 | 603 | 20 | s := s + 1; 604 | 604 | 20 | s := s + 1; 605 | 605 | 20 | s := s + 1; 606 | 606 | 20 | s := s + 1; 607 | 607 | 20 | s := s + 1; 608 | 608 | 20 | s := s + 1; 609 | 609 | 20 | s := s + 1; 610 | 610 | 20 | s := s + 1; 611 | 611 | 20 | s := s + 1; 612 | 612 | 20 | s := s + 1; 613 | 613 | 20 | s := s + 1; 614 | 614 | 20 | s := s + 1; 615 | 615 | 20 | s := s + 1; 616 | 616 | 20 | s := s + 1; 617 | 617 | 20 | s := s + 1; 618 | 618 | 20 | s := s + 1; 619 | 619 | 20 | s := s + 1; 620 | 620 | 20 | s := s + 1; 621 | 621 | 20 | s := s + 1; 622 | 622 | 20 | s := s + 1; 623 | 623 | 20 | s := s + 1; 624 | 624 | 20 | s := s + 1; 625 | 625 | 20 | s := s + 1; 626 | 626 | 20 | s := s + 1; 627 | 627 | 20 | s := s + 1; 628 | 628 | 20 | s := s + 1; 629 | 629 | 20 | s := s + 1; 630 | 630 | 20 | s := s + 1; 631 | 631 | 20 | s := s + 1; 632 | 632 | 20 | s := s + 1; 633 | 633 | 20 | s := s + 1; 634 | 634 | 20 | s := s + 1; 635 | 635 | 20 | s := s + 1; 636 | 636 | 20 | s := s + 1; 637 | 637 | 20 | s := s + 1; 638 | 638 | 20 | s := s + 1; 639 | 639 | 20 | s := s + 1; 640 | 640 | 20 | s := s + 1; 641 | 641 | 20 | s := s + 1; 642 | 642 | 20 | s := s + 1; 643 | 643 | 20 | s := s + 1; 644 | 644 | 20 | s := s + 1; 645 | 645 | 20 | s := s + 1; 646 | 646 | 20 | s := s + 1; 647 | 647 | 20 | s := s + 1; 648 | 648 | 20 | s := s + 1; 649 | 649 | 20 | s := s + 1; 650 | 650 | 20 | s := s + 1; 651 | 651 | 20 | s := s + 1; 652 | 652 | 20 | s := s + 1; 653 | 653 | 20 | s := s + 1; 654 | 654 | 20 | s := s + 1; 655 | 655 | 20 | s := s + 1; 656 | 656 | 20 | s := s + 1; 657 | 657 | 20 | s := s + 1; 658 | 658 | 20 | s := s + 1; 659 | 659 | 20 | s := s + 1; 660 | 660 | 20 | s := s + 1; 661 | 661 | 20 | s := s + 1; 662 | 662 | 20 | s := s + 1; 663 | 663 | 20 | s := s + 1; 664 | 664 | 20 | s := s + 1; 665 | 665 | 20 | s := s + 1; 666 | 666 | 20 | s := s + 1; 667 | 667 | 20 | s := s + 1; 668 | 668 | 20 | s := s + 1; 669 | 669 | 20 | s := s + 1; 670 | 670 | 20 | s := s + 1; 671 | 671 | 20 | s := s + 1; 672 | 672 | 20 | s := s + 1; 673 | 673 | 20 | s := s + 1; 674 | 674 | 20 | s := s + 1; 675 | 675 | 20 | s := s + 1; 676 | 676 | 20 | s := s + 1; 677 | 677 | 20 | s := s + 1; 678 | 678 | 20 | s := s + 1; 679 | 679 | 20 | s := s + 1; 680 | 680 | 20 | s := s + 1; 681 | 681 | 20 | s := s + 1; 682 | 682 | 20 | s := s + 1; 683 | 683 | 20 | s := s + 1; 684 | 684 | 20 | s := s + 1; 685 | 685 | 20 | s := s + 1; 686 | 686 | 20 | s := s + 1; 687 | 687 | 20 | s := s + 1; 688 | 688 | 20 | s := s + 1; 689 | 689 | 20 | s := s + 1; 690 | 690 | 20 | s := s + 1; 691 | 691 | 20 | s := s + 1; 692 | 692 | 20 | s := s + 1; 693 | 693 | 20 | s := s + 1; 694 | 694 | 20 | s := s + 1; 695 | 695 | 20 | s := s + 1; 696 | 696 | 20 | s := s + 1; 697 | 697 | 20 | s := s + 1; 698 | 698 | 20 | s := s + 1; 699 | 699 | 20 | s := s + 1; 700 | 700 | 20 | s := s + 1; 701 | 701 | 20 | s := s + 1; 702 | 702 | 20 | s := s + 1; 703 | 703 | 20 | s := s + 1; 704 | 704 | 20 | s := s + 1; 705 | 705 | 20 | s := s + 1; 706 | 706 | 20 | s := s + 1; 707 | 707 | 20 | s := s + 1; 708 | 708 | 20 | s := s + 1; 709 | 709 | 20 | s := s + 1; 710 | 710 | 20 | s := s + 1; 711 | 711 | 20 | s := s + 1; 712 | 712 | 20 | s := s + 1; 713 | 713 | 20 | s := s + 1; 714 | 714 | 20 | s := s + 1; 715 | 715 | 20 | s := s + 1; 716 | 716 | 20 | s := s + 1; 717 | 717 | 20 | s := s + 1; 718 | 718 | 20 | s := s + 1; 719 | 719 | 20 | s := s + 1; 720 | 720 | 20 | s := s + 1; 721 | 721 | 20 | s := s + 1; 722 | 722 | 20 | s := s + 1; 723 | 723 | 20 | s := s + 1; 724 | 724 | 20 | s := s + 1; 725 | 725 | 20 | s := s + 1; 726 | 726 | 20 | s := s + 1; 727 | 727 | 20 | s := s + 1; 728 | 728 | 20 | s := s + 1; 729 | 729 | 20 | s := s + 1; 730 | 730 | 20 | s := s + 1; 731 | 731 | 20 | s := s + 1; 732 | 732 | 20 | s := s + 1; 733 | 733 | 20 | s := s + 1; 734 | 734 | 20 | s := s + 1; 735 | 735 | 20 | s := s + 1; 736 | 736 | 20 | s := s + 1; 737 | 737 | 20 | s := s + 1; 738 | 738 | 20 | s := s + 1; 739 | 739 | 20 | s := s + 1; 740 | 740 | 20 | s := s + 1; 741 | 741 | 20 | s := s + 1; 742 | 742 | 20 | s := s + 1; 743 | 743 | 20 | s := s + 1; 744 | 744 | 20 | s := s + 1; 745 | 745 | 20 | s := s + 1; 746 | 746 | 20 | s := s + 1; 747 | 747 | 20 | s := s + 1; 748 | 748 | 20 | s := s + 1; 749 | 749 | 20 | s := s + 1; 750 | 750 | 20 | s := s + 1; 751 | 751 | 20 | s := s + 1; 752 | 752 | 20 | s := s + 1; 753 | 753 | 20 | s := s + 1; 754 | 754 | 20 | s := s + 1; 755 | 755 | 20 | s := s + 1; 756 | 756 | 20 | s := s + 1; 757 | 757 | 20 | s := s + 1; 758 | 758 | 20 | s := s + 1; 759 | 759 | 20 | s := s + 1; 760 | 760 | 20 | s := s + 1; 761 | 761 | 20 | s := s + 1; 762 | 762 | 20 | s := s + 1; 763 | 763 | 20 | s := s + 1; 764 | 764 | 20 | s := s + 1; 765 | 765 | 20 | s := s + 1; 766 | 766 | 20 | s := s + 1; 767 | 767 | 20 | s := s + 1; 768 | 768 | 20 | s := s + 1; 769 | 769 | 20 | s := s + 1; 770 | 770 | 20 | s := s + 1; 771 | 771 | 20 | s := s + 1; 772 | 772 | 20 | s := s + 1; 773 | 773 | 20 | s := s + 1; 774 | 774 | 20 | s := s + 1; 775 | 775 | 20 | s := s + 1; 776 | 776 | 20 | s := s + 1; 777 | 777 | 20 | s := s + 1; 778 | 778 | 20 | s := s + 1; 779 | 779 | 20 | s := s + 1; 780 | 780 | 20 | s := s + 1; 781 | 781 | 20 | s := s + 1; 782 | 782 | 20 | s := s + 1; 783 | 783 | 20 | s := s + 1; 784 | 784 | 20 | s := s + 1; 785 | 785 | 20 | s := s + 1; 786 | 786 | 20 | s := s + 1; 787 | 787 | 20 | s := s + 1; 788 | 788 | 20 | s := s + 1; 789 | 789 | 20 | s := s + 1; 790 | 790 | 20 | s := s + 1; 791 | 791 | 20 | s := s + 1; 792 | 792 | 20 | s := s + 1; 793 | 793 | 20 | s := s + 1; 794 | 794 | 20 | s := s + 1; 795 | 795 | 20 | s := s + 1; 796 | 796 | 20 | s := s + 1; 797 | 797 | 20 | s := s + 1; 798 | 798 | 20 | s := s + 1; 799 | 799 | 20 | s := s + 1; 800 | 800 | 20 | s := s + 1; 801 | 801 | 20 | s := s + 1; 802 | 802 | 20 | s := s + 1; 803 | 803 | 20 | s := s + 1; 804 | 804 | 20 | s := s + 1; 805 | 805 | 20 | s := s + 1; 806 | 806 | 20 | s := s + 1; 807 | 807 | 20 | s := s + 1; 808 | 808 | 20 | s := s + 1; 809 | 809 | 20 | s := s + 1; 810 | 810 | 20 | s := s + 1; 811 | 811 | 20 | s := s + 1; 812 | 812 | 20 | s := s + 1; 813 | 813 | 20 | s := s + 1; 814 | 814 | 20 | s := s + 1; 815 | 815 | 20 | s := s + 1; 816 | 816 | 20 | s := s + 1; 817 | 817 | 20 | s := s + 1; 818 | 818 | 20 | s := s + 1; 819 | 819 | 20 | s := s + 1; 820 | 820 | 20 | s := s + 1; 821 | 821 | 20 | s := s + 1; 822 | 822 | 20 | s := s + 1; 823 | 823 | 20 | s := s + 1; 824 | 824 | 20 | s := s + 1; 825 | 825 | 20 | s := s + 1; 826 | 826 | 20 | s := s + 1; 827 | 827 | 20 | s := s + 1; 828 | 828 | 20 | s := s + 1; 829 | 829 | 20 | s := s + 1; 830 | 830 | 20 | s := s + 1; 831 | 831 | 20 | s := s + 1; 832 | 832 | 20 | s := s + 1; 833 | 833 | 20 | s := s + 1; 834 | 834 | 20 | s := s + 1; 835 | 835 | 20 | s := s + 1; 836 | 836 | 20 | s := s + 1; 837 | 837 | 20 | s := s + 1; 838 | 838 | 20 | s := s + 1; 839 | 839 | 20 | s := s + 1; 840 | 840 | 20 | s := s + 1; 841 | 841 | 20 | s := s + 1; 842 | 842 | 20 | s := s + 1; 843 | 843 | 20 | s := s + 1; 844 | 844 | 20 | s := s + 1; 845 | 845 | 20 | s := s + 1; 846 | 846 | 20 | s := s + 1; 847 | 847 | 20 | s := s + 1; 848 | 848 | 20 | s := s + 1; 849 | 849 | 20 | s := s + 1; 850 | 850 | 20 | s := s + 1; 851 | 851 | 20 | s := s + 1; 852 | 852 | 20 | s := s + 1; 853 | 853 | 20 | s := s + 1; 854 | 854 | 20 | s := s + 1; 855 | 855 | 20 | s := s + 1; 856 | 856 | 20 | s := s + 1; 857 | 857 | 20 | s := s + 1; 858 | 858 | 20 | s := s + 1; 859 | 859 | 20 | s := s + 1; 860 | 860 | 20 | s := s + 1; 861 | 861 | 20 | s := s + 1; 862 | 862 | 20 | s := s + 1; 863 | 863 | 20 | s := s + 1; 864 | 864 | 20 | s := s + 1; 865 | 865 | 20 | s := s + 1; 866 | 866 | 20 | s := s + 1; 867 | 867 | 20 | s := s + 1; 868 | 868 | 20 | s := s + 1; 869 | 869 | 20 | s := s + 1; 870 | 870 | 20 | s := s + 1; 871 | 871 | 20 | s := s + 1; 872 | 872 | 20 | s := s + 1; 873 | 873 | 20 | s := s + 1; 874 | 874 | 20 | s := s + 1; 875 | 875 | 20 | s := s + 1; 876 | 876 | 20 | s := s + 1; 877 | 877 | 20 | s := s + 1; 878 | 878 | 20 | s := s + 1; 879 | 879 | 20 | s := s + 1; 880 | 880 | 20 | s := s + 1; 881 | 881 | 20 | s := s + 1; 882 | 882 | 20 | s := s + 1; 883 | 883 | 20 | s := s + 1; 884 | 884 | 20 | s := s + 1; 885 | 885 | 20 | s := s + 1; 886 | 886 | 20 | s := s + 1; 887 | 887 | 20 | s := s + 1; 888 | 888 | 20 | s := s + 1; 889 | 889 | 20 | s := s + 1; 890 | 890 | 20 | s := s + 1; 891 | 891 | 20 | s := s + 1; 892 | 892 | 20 | s := s + 1; 893 | 893 | 20 | s := s + 1; 894 | 894 | 20 | s := s + 1; 895 | 895 | 20 | s := s + 1; 896 | 896 | 20 | s := s + 1; 897 | 897 | 20 | s := s + 1; 898 | 898 | 20 | s := s + 1; 899 | 899 | 20 | s := s + 1; 900 | 900 | 20 | s := s + 1; 901 | 901 | 20 | s := s + 1; 902 | 902 | 20 | s := s + 1; 903 | 903 | 20 | s := s + 1; 904 | 904 | 20 | s := s + 1; 905 | 905 | 20 | s := s + 1; 906 | 906 | 20 | s := s + 1; 907 | 907 | 20 | s := s + 1; 908 | 908 | 20 | s := s + 1; 909 | 909 | 20 | s := s + 1; 910 | 910 | 20 | s := s + 1; 911 | 911 | 20 | s := s + 1; 912 | 912 | 20 | s := s + 1; 913 | 913 | 20 | s := s + 1; 914 | 914 | 20 | s := s + 1; 915 | 915 | 20 | s := s + 1; 916 | 916 | 20 | s := s + 1; 917 | 917 | 20 | s := s + 1; 918 | 918 | 20 | s := s + 1; 919 | 919 | 20 | s := s + 1; 920 | 920 | 20 | s := s + 1; 921 | 921 | 20 | s := s + 1; 922 | 922 | 20 | s := s + 1; 923 | 923 | 20 | s := s + 1; 924 | 924 | 20 | s := s + 1; 925 | 925 | 20 | s := s + 1; 926 | 926 | 20 | s := s + 1; 927 | 927 | 20 | s := s + 1; 928 | 928 | 20 | s := s + 1; 929 | 929 | 20 | s := s + 1; 930 | 930 | 20 | s := s + 1; 931 | 931 | 20 | s := s + 1; 932 | 932 | 20 | s := s + 1; 933 | 933 | 20 | s := s + 1; 934 | 934 | 20 | s := s + 1; 935 | 935 | 20 | s := s + 1; 936 | 936 | 20 | s := s + 1; 937 | 937 | 20 | s := s + 1; 938 | 938 | 20 | s := s + 1; 939 | 939 | 20 | s := s + 1; 940 | 940 | 20 | s := s + 1; 941 | 941 | 20 | s := s + 1; 942 | 942 | 20 | s := s + 1; 943 | 943 | 20 | s := s + 1; 944 | 944 | 20 | s := s + 1; 945 | 945 | 20 | s := s + 1; 946 | 946 | 20 | s := s + 1; 947 | 947 | 20 | s := s + 1; 948 | 948 | 20 | s := s + 1; 949 | 949 | 20 | s := s + 1; 950 | 950 | 20 | s := s + 1; 951 | 951 | 20 | s := s + 1; 952 | 952 | 20 | s := s + 1; 953 | 953 | 20 | s := s + 1; 954 | 954 | 20 | s := s + 1; 955 | 955 | 20 | s := s + 1; 956 | 956 | 20 | s := s + 1; 957 | 957 | 20 | s := s + 1; 958 | 958 | 20 | s := s + 1; 959 | 959 | 20 | s := s + 1; 960 | 960 | 20 | s := s + 1; 961 | 961 | 20 | s := s + 1; 962 | 962 | 20 | s := s + 1; 963 | 963 | 20 | s := s + 1; 964 | 964 | 20 | s := s + 1; 965 | 965 | 20 | s := s + 1; 966 | 966 | 20 | s := s + 1; 967 | 967 | 20 | s := s + 1; 968 | 968 | 20 | s := s + 1; 969 | 969 | 20 | s := s + 1; 970 | 970 | 20 | s := s + 1; 971 | 971 | 20 | s := s + 1; 972 | 972 | 20 | s := s + 1; 973 | 973 | 20 | s := s + 1; 974 | 974 | 20 | s := s + 1; 975 | 975 | 20 | s := s + 1; 976 | 976 | 20 | s := s + 1; 977 | 977 | 20 | s := s + 1; 978 | 978 | 20 | s := s + 1; 979 | 979 | 20 | s := s + 1; 980 | 980 | 20 | s := s + 1; 981 | 981 | 20 | s := s + 1; 982 | 982 | 20 | s := s + 1; 983 | 983 | 20 | s := s + 1; 984 | 984 | 20 | s := s + 1; 985 | 985 | 20 | s := s + 1; 986 | 986 | 20 | s := s + 1; 987 | 987 | 20 | s := s + 1; 988 | 988 | 20 | s := s + 1; 989 | 989 | 20 | s := s + 1; 990 | 990 | 20 | s := s + 1; 991 | 991 | 20 | s := s + 1; 992 | 992 | 20 | s := s + 1; 993 | 993 | 20 | s := s + 1; 994 | 994 | 20 | s := s + 1; 995 | 995 | 20 | s := s + 1; 996 | 996 | 20 | s := s + 1; 997 | 997 | 20 | s := s + 1; 998 | 998 | 20 | s := s + 1; 999 | 999 | 20 | s := s + 1; 1000 | 1000 | 20 | s := s + 1; 1001 | 1001 | 20 | s := s + 1; 1002 | 1002 | 20 | s := s + 1; 1003 | 1003 | 20 | s := s + 1; 1004 | 1004 | 20 | s := s + 1; 1005 | 1005 | 20 | s := s + 1; 1006 | 1006 | 20 | s := s + 1; 1007 | 1007 | 20 | s := s + 1; 1008 | 1008 | 20 | s := s + 1; 1009 | 1009 | 20 | s := s + 1; 1010 | 1010 | 20 | s := s + 1; 1011 | 1011 | 20 | s := s + 1; 1012 | 1012 | 20 | s := s + 1; 1013 | 1013 | 20 | s := s + 1; 1014 | 1014 | 20 | s := s + 1; 1015 | 1015 | 20 | s := s + 1; 1016 | 1016 | 20 | s := s + 1; 1017 | 1017 | 20 | s := s + 1; 1018 | 1018 | 20 | s := s + 1; 1019 | 1019 | 20 | s := s + 1; 1020 | 1020 | 20 | s := s + 1; 1021 | 1021 | 20 | s := s + 1; 1022 | 1022 | 20 | s := s + 1; 1023 | 1023 | 20 | s := s + 1; 1024 | 1024 | 20 | s := s + 1; 1025 | 1025 | 20 | s := s + 1; 1026 | 1026 | 20 | s := s + 1; 1027 | 1027 | 20 | s := s + 1; 1028 | 1028 | 20 | s := s + 1; 1029 | 1029 | 20 | s := s + 1; 1030 | 1030 | 20 | s := s + 1; 1031 | 1031 | 20 | s := s + 1; 1032 | 1032 | 20 | s := s + 1; 1033 | 1033 | 20 | s := s + 1; 1034 | 1034 | 20 | s := s + 1; 1035 | 1035 | 20 | s := s + 1; 1036 | 1036 | 20 | s := s + 1; 1037 | 1037 | 20 | s := s + 1; 1038 | | | end loop; 1039 | | | end loop; 1040 | 1040 | 20 | j := j + 1; 1041 | | | end loop; 1042 | | | exception when others then 1043 | 1043 | 0 | raise 'reraised exception %', sqlerrm; 1044 | | | end; 1045 | 1045 | 2 | return $1; 1046 | | | end; (1046 rows) select funcoid, exec_count from plpgsql_profiler_functions_all(); funcoid | exec_count -----------------+------------ longfx(integer) | 2 (1 row) create table testr(a int); create rule testr_rule as on insert to testr do nothing; create or replace function fx_testr() returns void as $$ begin insert into testr values(20); end; $$ language plpgsql; -- allow some rules on tables select fx_testr(); fx_testr ---------- (1 row) select * from plpgsql_check_function_tb('fx_testr'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+---------+--------+------+-------+----------+-------+--------- (0 rows) drop function fx_testr(); drop table testr; -- coverage tests set plpgsql_check.profiler to on; create or replace function covtest(int) returns int as $$ declare a int = $1; begin a := a + 1; if a < 10 then a := a + 1; end if; a := a + 1; return a; end; $$ language plpgsql; set plpgsql_check.profiler to on; select covtest(10); covtest --------- 12 (1 row) select stmtid, exec_stmts, stmtname from plpgsql_profiler_function_statements_tb('covtest'); stmtid | exec_stmts | stmtname --------+------------+----------------- 1 | 1 | statement block 2 | 1 | assignment 3 | 1 | IF 4 | 0 | assignment 5 | 1 | assignment 6 | 1 | RETURN (6 rows) select plpgsql_coverage_statements('covtest'); plpgsql_coverage_statements ----------------------------- 0.8333333333333334 (1 row) select plpgsql_coverage_branches('covtest'); plpgsql_coverage_branches --------------------------- 0.5 (1 row) select covtest(1); covtest --------- 4 (1 row) select stmtid, exec_stmts, stmtname from plpgsql_profiler_function_statements_tb('covtest'); stmtid | exec_stmts | stmtname --------+------------+----------------- 1 | 2 | statement block 2 | 2 | assignment 3 | 2 | IF 4 | 1 | assignment 5 | 2 | assignment 6 | 2 | RETURN (6 rows) select plpgsql_coverage_statements('covtest'); plpgsql_coverage_statements ----------------------------- 1 (1 row) select plpgsql_coverage_branches('covtest'); plpgsql_coverage_branches --------------------------- 1 (1 row) set plpgsql_check.profiler to off; create or replace function f() returns void as $$ declare r1 record; r2 record; begin select 10 as a, 20 as b into r1; r2 := json_populate_record(r1, '{}'); raise notice '%', r2.a; end; $$ language plpgsql; select * from plpgsql_check_function('f'); plpgsql_check_function ------------------------ (0 rows) -- fix issue #63 create or replace function distinct_array(arr anyarray) returns anyarray as $$ begin return array(select distinct e from unnest(arr) as e); end; $$ language plpgsql immutable; select plpgsql_check_function('distinct_array(anyarray)'); plpgsql_check_function ------------------------ (0 rows) drop function distinct_array(anyarray); -- tracer test set plpgsql_check.enable_tracer to on; set plpgsql_check.tracer to on; set plpgsql_check.tracer_test_mode = true; \set VERBOSITY terse create or replace function fxo(a int, b int, c date, d numeric) returns void as $$ begin insert into tracer_tab values(a,b,c,d); end; $$ language plpgsql; create table tracer_tab(a int, b int, c date, d numeric); create or replace function tracer_tab_trg_fx() returns trigger as $$ begin return new; end; $$ language plpgsql; create trigger tracer_tab_trg before insert on tracer_tab for each row execute procedure tracer_tab_trg_fx(); select fxo(10,20,'20200815', 3.14); NOTICE: #0 ->> start of function fxo(integer,integer,date,numeric) (oid=0, tnl=1) NOTICE: #0 "a" => '10', "b" => '20', "c" => '08-15-2020', "d" => '3.14' NOTICE: #2 ->> start of function tracer_tab_trg_fx() (oid=0, tnl=1) NOTICE: #2 context: SQL statement "insert into tracer_tab values(a,b,c,d)" NOTICE: #2 triggered by before row insert trigger NOTICE: #2 "new" => '(10,20,08-15-2020,3.14)' NOTICE: #2 <<- end of function tracer_tab_trg_fx (elapsed time=0.010 ms) NOTICE: #0 <<- end of function fxo (elapsed time=0.010 ms) fxo ----- (1 row) select fxo(11,21,'20200816', 6.28); NOTICE: #0 ->> start of function fxo(integer,integer,date,numeric) (oid=0, tnl=1) NOTICE: #0 "a" => '11', "b" => '21', "c" => '08-16-2020', "d" => '6.28' NOTICE: #2 ->> start of function tracer_tab_trg_fx() (oid=0, tnl=1) NOTICE: #2 context: SQL statement "insert into tracer_tab values(a,b,c,d)" NOTICE: #2 triggered by before row insert trigger NOTICE: #2 "new" => '(11,21,08-16-2020,6.28)' NOTICE: #2 <<- end of function tracer_tab_trg_fx (elapsed time=0.010 ms) NOTICE: #0 <<- end of function fxo (elapsed time=0.010 ms) fxo ----- (1 row) set plpgsql_check.enable_tracer to off; set plpgsql_check.tracer to off; drop table tracer_tab cascade; drop function tracer_tab_trg_fx(); drop function fxo(int, int, date, numeric); create or replace function foo_trg_func() returns trigger as $$ begin -- bad function, RETURN is missing end; $$ language plpgsql; create table foo(a int); create trigger foo_trg before insert for each row execute procedure foo_trg_func(); ERROR: syntax error at or near "for" at character 38 -- should to print error select * from plpgsql_check_function('foo_trg_func', 'foo'); plpgsql_check_function ------------------------------------------------------------ error:2F005:control reached end of function without RETURN (1 row) drop table foo; drop function foo_trg_func(); -- check event trigger function create or replace function f1() returns event_trigger as $$ BEGIN RAISE NOTICE 'test_event_trigger: % %', tg_event, tg_tag; END $$ language plpgsql; select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+---------+--------+------+-------+----------+-------+--------- (0 rows) -- should fail create or replace function f1() returns event_trigger as $$ BEGIN RAISE NOTICE 'test_event_trigger: % %', tg_event, tg_tagX; END $$ language plpgsql; select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+---------------------------------+--------+------+-------+----------+---------+--------- f1 | 3 | RAISE | 42703 | column "tg_tagx" does not exist | | | error | 1 | tg_tagX | (1 row) drop function f1(); -- check event trigger function create or replace function f1() returns event_trigger as $$ BEGIN RAISE NOTICE 'test_event_trigger: % %', tg_event, tg_tag; END $$ language plpgsql; select * from plpgsql_check_function('f1()'); plpgsql_check_function ------------------------ (0 rows) -- should fail create or replace function f1() returns event_trigger as $$ BEGIN RAISE NOTICE 'test_event_trigger: % %', tg_event, tg_tagX; END $$ language plpgsql; select * from plpgsql_check_function('f1()'); plpgsql_check_function ----------------------------------------------------- error:42703:3:RAISE:column "tg_tagx" does not exist Query: tg_tagX -- ^ (3 rows) drop function f1(); create table t1tab(a int, b int); create or replace function f1() returns setof t1tab as $$ begin return next (10,20); return; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings => true); plpgsql_check_function ----------------------------------------------------------------------------------------- performance:00000:routine is marked as VOLATILE, should be IMMUTABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (2 rows) create or replace function f1() returns setof t1tab as $$ begin return next (10::numeric,20); return; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings => true); plpgsql_check_function ------------------------------------------------------------------------------------ error:42804:3:RETURN NEXT:returned record type does not match expected record type Detail: Returned type numeric does not match expected type integer in column 1. (2 rows) create or replace function f1() returns setof t1tab as $$ declare a int; b int; begin return next (a,b); return; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings => true); plpgsql_check_function ----------------------------------------------------------------------------------------- performance:00000:routine is marked as VOLATILE, should be IMMUTABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (2 rows) create or replace function f1() returns setof t1tab as $$ declare a numeric; b int; begin return next (a,b::numeric); return; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings => true); plpgsql_check_function ------------------------------------------------------------------------------------ error:42804:4:RETURN NEXT:returned record type does not match expected record type Detail: Returned type numeric does not match expected type integer in column 1. (2 rows) drop function f1(); create table t1(a int, b int); create or replace function fx() returns t2 as $$ begin return (10,20,30)::t1; end; $$ language plpgsql; select * from plpgsql_check_function('fx()', performance_warnings => true); plpgsql_check_function ---------------------------------------------------- error:42846:3:RETURN:cannot cast type record to t1 Query: (10,20,30)::t1 -- ^ Detail: Input has too many columns. (4 rows) drop function fx(); drop table t1tab; drop table t1; create or replace function fx() returns void as $$ begin assert exists(select * from foo); assert false, (select boo from boo limit 1); end; $$ language plpgsql; select * from plpgsql_check_function('fx()', fatal_errors => false); plpgsql_check_function ---------------------------------------------------- error:42P01:3:ASSERT:relation "foo" does not exist Query: exists(select * from foo) -- ^ error:42P01:4:ASSERT:relation "boo" does not exist Query: (select boo from boo limit 1) -- ^ (6 rows) create or replace function ml_trg() returns trigger as $$ #option dump declare begin if TG_OP = 'INSERT' then if NEW.status_from IS NULL then begin -- performance issue only select status into NEW.status_from from pa where pa_id = NEW.pa_id; -- nonexist target value select status into NEW.status_from_xxx from pa where pa_id = NEW.pa_id; exception when DATA_EXCEPTION then new.status_from := 'DE'; end; end if; end if; if TG_OP = 'DELETE' then return OLD; else return NEW; end if; exception when OTHERS then NULL; if TG_OP = 'DELETE' then return OLD; else return NEW; end if; end; $$ language plpgsql; select * from plpgsql_check_function('ml_trg()', 'ml', performance_warnings := true); plpgsql_check_function -------------------------------------------------------------------------- error:42703:13:SQL statement:record "new" has no field "status_from_xxx" (1 row) create or replace function fx2() returns void as $$ declare _pa pa; begin select pa.id into _pa.id from pa limit 1; select pa.pa_id into _pa.pa_id from pa limit 1; end; $$ language plpgsql; select * from plpgsql_check_function('fx2()', performance_warnings := true); plpgsql_check_function --------------------------------------------------------- warning extra:00000:2:DECLARE:never read variable "_pa" (1 row) drop function fx2(); create or replace function fx2() returns void as $$ declare _pa pa; begin _pa.id := (select pa.id from pa limit 1); _pa.pa_id := (select pa.pa_id from pa limit 1); end; $$ language plpgsql; select * from plpgsql_check_function('fx2()', performance_warnings := true); plpgsql_check_function --------------------------------------------------------- warning extra:00000:2:DECLARE:never read variable "_pa" (1 row) drop function fx2(); create type _exception_type as ( state text, message text, detail text); create or replace function f1() returns void as $$ declare _exception record; begin _exception := NULL::_exception_type; exception when others then get stacked diagnostics _exception.state = RETURNED_SQLSTATE, _exception.message = MESSAGE_TEXT, _exception.detail = PG_EXCEPTION_DETAIL, _exception.hint = PG_EXCEPTION_HINT; end; $$ language plpgsql; select f1(); f1 ---- (1 row) select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-------------------------+----------+-----------------------------------------+--------+------+-------+----------+-------+--------- f1 | 7 | GET STACKED DIAGNOSTICS | 42703 | record "_exception" has no field "hint" | | | error | | | (1 row) create or replace function f1() returns void as $$ declare _exception _exception_type; begin _exception := NULL::_exception_type; exception when others then get stacked diagnostics _exception.state = RETURNED_SQLSTATE, _exception.message = MESSAGE_TEXT, _exception.detail = PG_EXCEPTION_DETAIL; end; $$ language plpgsql; select f1(); f1 ---- (1 row) select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+----------------------------------+--------+------+---------------+----------+-------+--------- f1 | 3 | DECLARE | 00000 | never read variable "_exception" | | | warning extra | | | (1 row) drop function f1(); drop type _exception_type; create type _exception_type as ( state text, message text, detail text); create or replace function f1() returns void as $$ declare _exception record; begin _exception := NULL::_exception_type; exception when others then get stacked diagnostics _exception.state = RETURNED_SQLSTATE, _exception.message = MESSAGE_TEXT, _exception.detail = PG_EXCEPTION_DETAIL, _exception.hint = PG_EXCEPTION_HINT; end; $$ language plpgsql; select f1(); f1 ---- (1 row) select * from plpgsql_check_function('f1()'); plpgsql_check_function ------------------------------------------------------------------------------- error:42703:7:GET STACKED DIAGNOSTICS:record "_exception" has no field "hint" (1 row) drop function f1(); drop type _exception_type; create table footab(a int, b int, c int); create or replace function footab_trig_func() returns trigger as $$ declare x int; begin if false then -- should be ok; select count(*) from newtab into x; -- should fail; select count(*) from newtab where d = 10 into x; end if; return null; end; $$ language plpgsql; select * from plpgsql_check_function('footab_trig_func','footab', newtable := 'newtab'); plpgsql_check_function ------------------------------------------------------- error:42703:9:SQL statement:column "d" does not exist Query: select count(*) from newtab where d = 10 -- ^ (3 rows) drop table footab; drop function footab_trig_func(); create or replace function df1(anyelement) returns anyelement as $$ begin return $1; end; $$ language plpgsql; create or replace function df2(anyelement, jsonb) returns anyelement as $$ begin return $1; end; $$ language plpgsql; create or replace function t1() returns void as $$ declare r record; begin r := df1(r); end; $$ language plpgsql; select * from plpgsql_check_function('t1()'); plpgsql_check_function ------------------------ (0 rows) create or replace function t1() returns void as $$ declare r record; begin r := df2(r, '{}'); end; $$ language plpgsql; select * from plpgsql_check_function('t1()'); plpgsql_check_function ------------------------ (0 rows) create or replace function t1() returns void as $$ declare r1 record; r2 record; begin select 10 as a, 20 as b into r1; r2 := df1(r1); raise notice '%', r2.a; end; $$ language plpgsql; select * from plpgsql_check_function('t1()'); plpgsql_check_function ------------------------ (0 rows) create or replace function t1() returns void as $$ declare r1 record; r2 record; begin select 10 as a, 20 as b into r1; r2 := df2(r1, '{}'); raise notice '%', r2.a; end; $$ language plpgsql; select * from plpgsql_check_function('t1()'); plpgsql_check_function ------------------------ (0 rows) create or replace function df1(anyelement) returns anyelement as $$ select $1 $$ language sql; create or replace function df22(jsonb, anyelement) returns anyelement as $$ select $2; $$ language sql; create or replace function t1() returns void as $$ declare r1 record; r2 record; begin select 10 as a, 20 as b into r1; r2 := df1(r1); raise notice '%', r2.a; end; $$ language plpgsql; select * from plpgsql_check_function('t1()'); plpgsql_check_function ------------------------ (0 rows) create or replace function t1() returns void as $$ declare r1 record; r2 record; begin select 10 as a, 20 as b into r1; r2 := df22('{}', r1); raise notice '%', r2.a; end; $$ language plpgsql; select * from plpgsql_check_function('t1()'); plpgsql_check_function ------------------------ (0 rows) drop function df1(anyelement); drop function df2(anyelement, jsonb); drop function df22(jsonb, anyelement); drop function t1(); create or replace function dyntest() returns void as $$ begin execute 'drop table if exists xxx; create table xxx(a int)'; end; $$ language plpgsql; -- should be ok select * from plpgsql_check_function('dyntest'); plpgsql_check_function ------------------------ (0 rows) create or replace function dyntest() returns void as $$ declare x int; begin execute 'drop table if exists xxx; create table xxx(a int)' into x; end; $$ language plpgsql; -- should to report error select * from plpgsql_check_function('dyntest'); plpgsql_check_function ------------------------------------------------------- error:XX000:4:EXECUTE:expression does not return data (1 row) drop function dyntest(); -- should to report error create type typ2 as (a int, b int); create or replace function broken_into() returns void as $$ declare v typ2; begin -- should to fail select (10,20)::typ2 into v; -- should be ok select ((10,20)::typ2).* into v; -- should to fail execute 'select (10,20)::typ2' into v; -- should be ok execute 'select ((10,20)::typ2).*' into v; end; $$ language plpgsql; select * from plpgsql_check_function('broken_into', fatal_errors => false); plpgsql_check_function ------------------------------------------------------------------------------------------------------------ error:42804:5:SQL statement:cannot cast composite value of "typ2" type to a scalar value of "integer" type warning:00000:5:SQL statement:too few attributes for composite variable error:42804:9:EXECUTE:cannot cast composite value of "typ2" type to a scalar value of "integer" type warning:00000:9:EXECUTE:too few attributes for composite variable warning extra:00000:2:DECLARE:never read variable "v" (5 rows) drop function broken_into(); drop type typ2; -- check output in xml or json formats CREATE OR REPLACE FUNCTION test_function() RETURNS void LANGUAGE plpgsql AS $function$ begin insert into non_existing_table values (1); end $function$; select * from plpgsql_check_function('test_function', format => 'xml'); plpgsql_check_function ---------------------------------------------------------------------------- + + error + 42P01 + relation "non_existing_table" does not exist + SQL statement + insert into non_existing_table values (1)+ + (1 row) select * from plpgsql_check_function('test_function', format => 'json'); plpgsql_check_function ----------------------------------------------------------------- { "issues":[ + { + "level":"error", + "message":"relation \"non_existing_table\" does not exist",+ "statement":{ + "lineNumber":"3", + "text":"SQL statement" + }, + "query":{ + "position":"13", + "text":"insert into non_existing_table values (1)" + }, + "sqlState":"42P01" + } + + ] + } (1 row) drop function test_function(); -- test settype pragma create or replace function test_function() returns void as $$ declare r record; begin raise notice '%', r.a; end; $$ language plpgsql; -- should to detect error select * from plpgsql_check_function('test_function'); plpgsql_check_function ---------------------------------------------------------------------------- error:55000:4:RAISE:record "r" is not assigned yet Detail: The tuple structure of a not-yet-assigned record is indeterminate. Context: SQL expression "r.a" (3 rows) create type ctype as (a int, b int); create or replace function test_function() returns void as $$ declare r record; begin perform plpgsql_check_pragma('type: r ctype'); raise notice '%', r.a; end; $$ language plpgsql; -- should to be ok select * from plpgsql_check_function('test_function'); plpgsql_check_function ------------------------ (0 rows) create or replace function test_function() returns void as $$ <>declare r record; begin perform plpgsql_check_pragma('type: x.r public."ctype"'); raise notice '%', r.a; end; $$ language plpgsql; -- should to be ok select * from plpgsql_check_function('test_function'); plpgsql_check_function ------------------------ (0 rows) create or replace function test_function() returns void as $$ <>declare r record; begin perform plpgsql_check_pragma('type: "x".r (a int, b int)'); raise notice '%', r.a; end; $$ language plpgsql; -- should to be ok select * from plpgsql_check_function('test_function'); plpgsql_check_function ------------------------ (0 rows) create or replace function test_function() returns void as $$ <>declare r record; begin perform plpgsql_check_pragma('type: "x".r (a int, b int'); raise notice '%', r.a; end; $$ language plpgsql; -- should to be ok select * from plpgsql_check_function('test_function'); WARNING: Pragma "type" on line 4 is not processed. plpgsql_check_function ---------------------------------------------------------------------------- error:55000:5:RAISE:record "r" is not assigned yet Detail: The tuple structure of a not-yet-assigned record is indeterminate. Context: SQL expression "r.a" (3 rows) create or replace function test_function() returns void as $$ <>declare r record; begin perform plpgsql_check_pragma('type: "x".r (a int, b int)x'); raise notice '%', r.a; end; $$ language plpgsql; -- should to be ok select * from plpgsql_check_function('test_function'); WARNING: Pragma "type" on line 4 is not processed. plpgsql_check_function ---------------------------------------------------------------------------- error:55000:5:RAISE:record "r" is not assigned yet Detail: The tuple structure of a not-yet-assigned record is indeterminate. Context: SQL expression "r.a" (3 rows) drop function test_function(); drop type ctype; create or replace function test_function() returns void as $$ declare r pg_class; begin create temp table foo(like pg_class); select * from foo into r; end; $$ language plpgsql; -- should to raise an error select * from plpgsql_check_function('test_function'); plpgsql_check_function ----------------------------------------------------------- error:42P01:5:SQL statement:relation "foo" does not exist Query: select * from foo -- ^ (3 rows) create or replace function test_function() returns void as $$ declare r record; begin create temp table foo(like pg_class); perform plpgsql_check_pragma('table: foo(like pg_class)'); select * from foo into r; raise notice '%', r.relname; end; $$ language plpgsql; -- should be ok select * from plpgsql_check_function('test_function'); plpgsql_check_function ------------------------ (0 rows) drop function test_function(); -- now plpgsql_check can do some other checks when statement EXECUTE -- contains only format function with constant fmt. create or replace function test_function() returns void as $$ begin execute format('create table zzz %I(a int, b int)', 'zzz'); end; $$ language plpgsql; -- should to detect bad expression select * from plpgsql_check_function('test_function'); plpgsql_check_function ----------------------------------------------------- error:42601:3:EXECUTE:syntax error at or near "zzz" (1 row) -- should to correctly detect type create or replace function test_function() returns void as $$ declare r record; begin execute format('select %L::date + 1 as x', current_date) into r; raise notice '%', extract(dow from r.x); end; $$ language plpgsql; -- should be ok select * from plpgsql_check_function('test_function'); plpgsql_check_function ------------------------ (0 rows) -- should not to crash create or replace function test_function() returns void as $$ declare r record; begin r := null; end; $$ language plpgsql; select * from plpgsql_check_function('test_function'); plpgsql_check_function ------------------------------------------------------- warning extra:00000:2:DECLARE:never read variable "r" (1 row) drop function test_function(); -- aborted function has profile too create or replace function test_function(a int) returns int as $$ begin if (a > 5) then a := a + 10; return a; else raise exception 'a < 5'; end if; end; $$ language plpgsql; set plpgsql_check.profiler to on; select test_function(1); ERROR: a < 5 select test_function(10); test_function --------------- 20 (1 row) select lineno, exec_stmts, exec_stmts_err, source from plpgsql_profiler_function_tb('test_function'); lineno | exec_stmts | exec_stmts_err | source --------+------------+----------------+------------------------------ 1 | | | 2 | 2 | 1 | begin 3 | 2 | 1 | if (a > 5) then 4 | 1 | 0 | a := a + 10; 5 | 1 | 0 | return a; 6 | | | else 7 | 1 | 1 | raise exception 'a < 5'; 8 | | | end if; 9 | | | end; (9 rows) create or replace function test_function1(a int) returns int as $$ begin if (a > 5) then a := a + 10; return a; else raise exception 'a < 5'; end if; exeception when others then raise notice 'do warning'; return -1; end; $$ language plpgsql; select test_function1(1); ERROR: a < 5 select test_function1(10); test_function1 ---------------- 20 (1 row) select lineno, exec_stmts, exec_stmts_err, source from plpgsql_profiler_function_tb('test_function1'); lineno | exec_stmts | exec_stmts_err | source --------+------------+----------------+-------------------------------- 1 | | | 2 | 2 | 1 | begin 3 | 2 | 1 | if (a > 5) then 4 | 1 | 0 | a := a + 10; 5 | 1 | 0 | return a; 6 | | | else 7 | 1 | 1 | raise exception 'a < 5'; 8 | | | end if; 9 | 0 | 0 | exeception when others then 10 | | | raise notice 'do warning'; 11 | 0 | 0 | return -1; 12 | | | end; (12 rows) drop function test_function(int); drop function test_function1(int); set plpgsql_check.profiler to off; -- ignores syntax errors when literals placehodlers are used create function test_function() returns void as $$ begin execute format('do %L', 'begin end'); end $$ language plpgsql; select * from plpgsql_check_function('test_function'); plpgsql_check_function ------------------------ (0 rows) drop function test_function(); load 'plpgsql_check'; drop type testtype cascade; create type testtype as (a int, b int); create function test_function() returns record as $$ declare r record; begin r := (10,20); if false then return r; end if; return null; end; $$ language plpgsql; create function test_function33() returns record as $$ declare r testtype; begin r := (10,20); if false then return r; end if; return null; end; $$ language plpgsql; -- should not to raise false alarm due check against fake result type select plpgsql_check_function('test_function'); plpgsql_check_function ------------------------ (0 rows) select plpgsql_check_function('test_function33'); plpgsql_check_function ------------------------ (0 rows) -- try to check in passive mode set plpgsql_check.mode = 'every_start'; select test_function(); test_function --------------- (1 row) select test_function33(); test_function33 ----------------- (1 row) select * from test_function() as (a int, b int); a | b ---+--- | (1 row) select * from test_function33() as (a int, b int); a | b ---+--- | (1 row) -- should to identify error select * from test_function() as (a int, b int, c int); ERROR: returned record type does not match expected record type select * from test_function33() as (a int, b int, c int); ERROR: returned record type does not match expected record type drop function test_function(); drop function test_function33(); drop type testtype; set plpgsql_check.mode to default; -- should not to raise false alarm create type c1 as ( a text ); create table t1 ( a c1, b c1 ); insert into t1 (values ('(abc)', '(def)')); alter table t1 drop column a; create or replace function test_function() returns t1 as $$ declare myrow t1%rowtype; begin select * into myrow from t1 limit 1; return myrow; end; $$ language plpgsql; select * from test_function(); b ------- (def) (1 row) select * from plpgsql_check_function('public.test_function()'); plpgsql_check_function ------------------------ (0 rows) drop function test_function(); drop table t1; drop type c1; -- compatibility warnings create or replace function foo01() returns refcursor as $$ declare c cursor for select 1; r refcursor; begin open c; r := c; return r; end; $$ language plpgsql; -- no warnings select * from plpgsql_check_function('foo01', compatibility_warnings => true); plpgsql_check_function ------------------------ (0 rows) create or replace function foo01() returns refcursor as $$ declare c cursor for select 1; r refcursor; begin open c; r := 'c'; return r; end; $$ language plpgsql; -- warning select * from plpgsql_check_function('foo01', extra_warnings => false, compatibility_warnings => true); plpgsql_check_function ----------------------------------------------------------------------------------- compatibility:00000:7:assignment:obsolete setting of refcursor or cursor variable Detail: Internal name of cursor should not be specified by users. Context: at assignment to variable "r" declared on line 4 (3 rows) create or replace function foo01() returns refcursor as $$ declare c cursor for select 1; r refcursor; begin open c; r := c; return 'c'; end; $$ language plpgsql; -- warning select * from plpgsql_check_function('foo01', extra_warnings => false, compatibility_warnings => true); plpgsql_check_function -------------------------------------------------------------------------------------- compatibility:00000:8:RETURN:obsolete setting of refcursor or cursor variable Detail: Internal name of cursor should not be specified by users. warning:42804:8:RETURN:target type is different type than source type Detail: cast "text" value to "refcursor" type Hint: The input expression type does not have an assignment cast to the target type. (5 rows) -- pragma sequence test create or replace function test_function() returns void as $$ begin perform plpgsql_check_pragma('sequence: xx'); perform nextval('pg_temp.xy'); perform nextval('pg_temp.xx'); end $$ language plpgsql; select * from plpgsql_check_function('test_function'); plpgsql_check_function ------------------------------------------------------------ error:42P01:4:PERFORM:relation "pg_temp.xy" does not exist Query: SELECT nextval('pg_temp.xy') -- ^ (3 rows) drop function test_function(); create table t1(x int); create or replace function f1_trg() returns trigger as $$ declare x int; begin return x; end; $$ language plpgsql; create trigger t1_f1 before insert on t1 for each row execute procedure f1_trg(); -- raise error select * from plpgsql_check_function('f1_trg', 't1'); plpgsql_check_function ----------------------------------------------------------------------------------------------- error:42804:4:RETURN:cannot return non-composite value from function returning composite type (1 row) drop trigger t1_f1 on t1; drop table t1; drop function f1_trg; create function test_function() returns void as $$ declare a int; b int; c int; d char; begin c := a + d; end; $$ language plpgsql; -- only b should be marked as unused variable select * from plpgsql_check_function('test_function', fatal_errors := false); plpgsql_check_function --------------------------------------------------------------------------------------------------------- error:42883:6:assignment:operator does not exist: integer + character Query: c := a + d -- ^ Hint: No operator matches the given name and argument types. You might need to add explicit type casts. Context: at assignment to variable "c" declared on line 4 warning:00000:3:DECLARE:unused variable "b" warning extra:00000:4:DECLARE:never read variable "c" (7 rows) drop function test_function(); -- from plpgsql_check_active-12 create or replace procedure proc(a int) as $$ begin end; $$ language plpgsql; call proc(10); select * from plpgsql_check_function('proc(int)'); plpgsql_check_function ------------------------------------------ warning extra:00000:unused parameter "a" (1 row) create or replace procedure testproc() as $$ begin call proc(10); end; $$ language plpgsql; call testproc(); select * from plpgsql_check_function('testproc()'); plpgsql_check_function ------------------------ (0 rows) -- should to fail create or replace procedure testproc() as $$ begin call proc((select count(*) from pg_class)); end; $$ language plpgsql; call testproc(); ERROR: cannot use subquery in CALL argument at character 11 select * from plpgsql_check_function('testproc()'); plpgsql_check_function --------------------------------------------------------- error:0A000:3:CALL:cannot use subquery in CALL argument Query: call proc((select count(*) from pg_class)) -- ^ (3 rows) drop procedure proc(int); create procedure proc(in a int, inout b int, in c int) as $$ begin end; $$ language plpgsql; select * from plpgsql_check_function('proc(int,int, int)'); plpgsql_check_function ------------------------------------------------- warning extra:00000:unused parameter "a" warning extra:00000:unused parameter "b" warning extra:00000:unused parameter "c" warning extra:00000:unmodified OUT variable "b" (4 rows) create or replace procedure proc(in a int, inout b int, in c int) as $$ begin b := a + c; end; $$ language plpgsql; select * from plpgsql_check_function('proc(int,int, int)'); plpgsql_check_function ------------------------ (0 rows) create or replace procedure testproc() as $$ declare r int; begin call proc(10, r, 20); end; $$ language plpgsql; call testproc(); select * from plpgsql_check_function('testproc()'); plpgsql_check_function ------------------------ (0 rows) -- should to fail create or replace procedure testproc() as $$ declare r int; begin call proc(10, r + 10, 20); end; $$ language plpgsql; call testproc(); ERROR: procedure parameter "b" is an output parameter but corresponding argument is not writable select * from plpgsql_check_function('testproc()'); plpgsql_check_function -------------------------------------------------------------------------------------------------------------- error:42601:4:CALL:procedure parameter "b" is an output parameter but corresponding argument is not writable (1 row) create or replace procedure testproc(inout r int) as $$ begin call proc(10, r, 20); end; $$ language plpgsql; call testproc(10); r ---- 30 (1 row) select * from plpgsql_check_function('testproc(int)'); plpgsql_check_function ------------------------ (0 rows) drop procedure testproc(int); -- should to raise warnings create or replace procedure testproc2(in p1 int, inout p2 int, in p3 int, inout p4 int) as $$ begin raise notice '% %', p1, p3; end; $$ language plpgsql; select * from plpgsql_check_function('testproc2'); plpgsql_check_function -------------------------------------------------- warning extra:00000:unused parameter "p2" warning extra:00000:unused parameter "p4" warning extra:00000:unmodified OUT variable "p2" warning extra:00000:unmodified OUT variable "p4" (4 rows) drop procedure testproc2; -- should be ok create or replace procedure testproc3(in p1 int, inout p2 int, in p3 int, inout p4 int) as $$ begin p2 := p1; p4 := p3; end; $$ language plpgsql; select * from plpgsql_check_function('testproc3'); plpgsql_check_function ------------------------ (0 rows) drop procedure testproc3; /* * Test pragma */ create or replace function test_pragma() returns void language plpgsql as $$ declare r record; begin perform plpgsql_check_pragma('disable:check'); raise notice '%', r.y; perform plpgsql_check_pragma('enable:check'); select 10 as a, 20 as b into r; raise notice '%', r.a; raise notice '%', r.x; end; $$; select * from plpgsql_check_function('test_pragma'); plpgsql_check_function ------------------------------------------------- error:42703:9:RAISE:record "r" has no field "x" Context: SQL expression "r.x" (2 rows) create or replace function test_pragma() returns void language plpgsql as $$ declare r record; begin if false then -- check is disabled just for if body perform plpgsql_check_pragma('disable:check'); raise notice '%', r.y; end if; select 10 as a, 20 as b into r; raise notice '%', r.a; raise notice '%', r.x; end; $$; select * from plpgsql_check_function('test_pragma'); plpgsql_check_function -------------------------------------------------- error:42703:11:RAISE:record "r" has no field "x" Context: SQL expression "r.x" (2 rows) drop function test_pragma(); create or replace function nested_trace_test(a int) returns int as $$ begin return a + 1; end; $$ language plpgsql; create or replace function trace_test(b int) returns int as $$ declare r int default 0; begin for i in 1..b loop r := nested_trace_test(r); end loop; return r; end; $$ language plpgsql; select trace_test(3); trace_test ------------ 3 (1 row) set plpgsql_check.enable_tracer to on; set plpgsql_check.tracer to on; set plpgsql_check.tracer_test_mode = true; select trace_test(3); NOTICE: #0 ->> start of function trace_test(integer) (oid=0, tnl=1) NOTICE: #0 "b" => '3' NOTICE: #1 ->> start of function nested_trace_test(integer) (oid=0, tnl=1) NOTICE: #1 context: PL/pgSQL function trace_test(integer) line 6 at assignment NOTICE: #1 "a" => '0' NOTICE: #1 <<- end of function nested_trace_test (elapsed time=0.010 ms) NOTICE: #1 ->> start of function nested_trace_test(integer) (oid=0, tnl=1) NOTICE: #1 context: PL/pgSQL function trace_test(integer) line 6 at assignment NOTICE: #1 "a" => '1' NOTICE: #1 <<- end of function nested_trace_test (elapsed time=0.010 ms) NOTICE: #1 ->> start of function nested_trace_test(integer) (oid=0, tnl=1) NOTICE: #1 context: PL/pgSQL function trace_test(integer) line 6 at assignment NOTICE: #1 "a" => '2' NOTICE: #1 <<- end of function nested_trace_test (elapsed time=0.010 ms) NOTICE: #0 <<- end of function trace_test (elapsed time=0.010 ms) trace_test ------------ 3 (1 row) set plpgsql_check.tracer_verbosity TO verbose; select trace_test(3); NOTICE: #0 ->> start of function trace_test(integer) (oid=0, tnl=1) NOTICE: #0 "b" => '3' NOTICE: #0.1 3 --> start of statement block (tnl=1) NOTICE: #0.2 4 --> start of FOR with integer loop variable (tnl=1) NOTICE: #0.3 6 --> start of assignment r := nested_trace_test(r) (tnl=1) NOTICE: #0.3 "r" => '0' NOTICE: #1 ->> start of function nested_trace_test(integer) (oid=0, tnl=1) NOTICE: #1 context: PL/pgSQL function trace_test(integer) line 6 at assignment NOTICE: #1 "a" => '0' NOTICE: #1.1 2 --> start of statement block (tnl=1) NOTICE: #1.2 3 --> start of RETURN (expr='a + 1') (tnl=1) NOTICE: #1.2 "a" => '0' NOTICE: #1.1 <-- end of RETURN (elapsed time=0.010 ms) NOTICE: #1.2 <-- end of statement block (elapsed time=0.010 ms) NOTICE: #1 <<- end of function nested_trace_test (elapsed time=0.010 ms) NOTICE: #0.2 <-- end of assignment (elapsed time=0.010 ms) NOTICE: #0.3 "r" => '1' NOTICE: #0.3 6 --> start of assignment r := nested_trace_test(r) (tnl=1) NOTICE: #0.3 "r" => '1' NOTICE: #1 ->> start of function nested_trace_test(integer) (oid=0, tnl=1) NOTICE: #1 context: PL/pgSQL function trace_test(integer) line 6 at assignment NOTICE: #1 "a" => '1' NOTICE: #1.1 2 --> start of statement block (tnl=1) NOTICE: #1.2 3 --> start of RETURN (expr='a + 1') (tnl=1) NOTICE: #1.2 "a" => '1' NOTICE: #1.1 <-- end of RETURN (elapsed time=0.010 ms) NOTICE: #1.2 <-- end of statement block (elapsed time=0.010 ms) NOTICE: #1 <<- end of function nested_trace_test (elapsed time=0.010 ms) NOTICE: #0.2 <-- end of assignment (elapsed time=0.010 ms) NOTICE: #0.3 "r" => '2' NOTICE: #0.3 6 --> start of assignment r := nested_trace_test(r) (tnl=1) NOTICE: #0.3 "r" => '2' NOTICE: #1 ->> start of function nested_trace_test(integer) (oid=0, tnl=1) NOTICE: #1 context: PL/pgSQL function trace_test(integer) line 6 at assignment NOTICE: #1 "a" => '2' NOTICE: #1.1 2 --> start of statement block (tnl=1) NOTICE: #1.2 3 --> start of RETURN (expr='a + 1') (tnl=1) NOTICE: #1.2 "a" => '2' NOTICE: #1.1 <-- end of RETURN (elapsed time=0.010 ms) NOTICE: #1.2 <-- end of statement block (elapsed time=0.010 ms) NOTICE: #1 <<- end of function nested_trace_test (elapsed time=0.010 ms) NOTICE: #0.2 <-- end of assignment (elapsed time=0.010 ms) NOTICE: #0.3 "r" => '3' NOTICE: #0.1 <-- end of FOR with integer loop variable (elapsed time=0.010 ms) NOTICE: #0.4 8 --> start of RETURN (tnl=1) NOTICE: #0.4 "r" => '3' NOTICE: #0.3 <-- end of RETURN (elapsed time=0.010 ms) NOTICE: #0.4 <-- end of statement block (elapsed time=0.010 ms) NOTICE: #0 <<- end of function trace_test (elapsed time=0.010 ms) trace_test ------------ 3 (1 row) create or replace function trace_test(b int) returns int as $$ declare r int default 0; begin for i in 1..b loop perform plpgsql_check_pragma('disable:tracer'); r := nested_trace_test(r); end loop; return r; end; $$ language plpgsql; select trace_test(3); NOTICE: #0 ->> start of function trace_test(integer) (oid=0, tnl=1) NOTICE: #0 "b" => '3' NOTICE: #0.1 3 --> start of statement block (tnl=1) NOTICE: #0.2 4 --> start of FOR with integer loop variable (tnl=1) NOTICE: #0.3 6 --> start of perform plpgsql_check_pragma('disable: .. (tnl=1) NOTICE: #0.2 <-- end of PERFORM (elapsed time=0.010 ms) NOTICE: #0.1 <-- end of FOR with integer loop variable (elapsed time=0.010 ms) NOTICE: #0.5 9 --> start of RETURN (tnl=1) NOTICE: #0.5 "r" => '3' NOTICE: #0.4 <-- end of RETURN (elapsed time=0.010 ms) NOTICE: #0.5 <-- end of statement block (elapsed time=0.010 ms) NOTICE: #0 <<- end of function trace_test (elapsed time=0.010 ms) trace_test ------------ 3 (1 row) create or replace function nested_trace_test(a int) returns int as $$ begin perform plpgsql_check_pragma('enable:tracer'); return a + 1; end; $$ language plpgsql; select trace_test(3); NOTICE: #0 ->> start of function trace_test(integer) (oid=0, tnl=1) NOTICE: #0 "b" => '3' NOTICE: #0.1 3 --> start of statement block (tnl=1) NOTICE: #0.2 4 --> start of FOR with integer loop variable (tnl=1) NOTICE: #0.3 6 --> start of perform plpgsql_check_pragma('disable: .. (tnl=1) NOTICE: #0.2 <-- end of PERFORM (elapsed time=0.010 ms) NOTICE: #1.3 4 --> start of RETURN (expr='a + 1') (tnl=1) NOTICE: #1.3 "a" => '0' NOTICE: #1.2 <-- end of RETURN (elapsed time=0.010 ms) NOTICE: #1.3 4 --> start of RETURN (expr='a + 1') (tnl=1) NOTICE: #1.3 "a" => '1' NOTICE: #1.2 <-- end of RETURN (elapsed time=0.010 ms) NOTICE: #1.3 4 --> start of RETURN (expr='a + 1') (tnl=1) NOTICE: #1.3 "a" => '2' NOTICE: #1.2 <-- end of RETURN (elapsed time=0.010 ms) NOTICE: #0.1 <-- end of FOR with integer loop variable (elapsed time=0.010 ms) NOTICE: #0.5 9 --> start of RETURN (tnl=1) NOTICE: #0.5 "r" => '3' NOTICE: #0.4 <-- end of RETURN (elapsed time=0.010 ms) NOTICE: #0.5 <-- end of statement block (elapsed time=0.010 ms) NOTICE: #0 <<- end of function trace_test (elapsed time=0.010 ms) trace_test ------------ 3 (1 row) drop function trace_test(int); drop function nested_trace_test(int); create or replace function trace_test(int) returns int as $$ declare r int default 0; begin for i in 1..$1 loop r := r + 1; end loop; r := r + 10; return r; end; $$ language plpgsql; select trace_test(4); NOTICE: #0 ->> start of function trace_test(integer) (oid=0, tnl=1) NOTICE: #0 "$1" => '4' NOTICE: #0.1 3 --> start of statement block (tnl=1) NOTICE: #0.2 4 --> start of FOR with integer loop variable (tnl=1) NOTICE: #0.3 5 --> start of assignment r := r + 1 (tnl=1) NOTICE: #0.3 "r" => '0' NOTICE: #0.2 <-- end of assignment (elapsed time=0.010 ms) NOTICE: #0.3 "r" => '1' NOTICE: #0.3 5 --> start of assignment r := r + 1 (tnl=1) NOTICE: #0.3 "r" => '1' NOTICE: #0.2 <-- end of assignment (elapsed time=0.010 ms) NOTICE: #0.3 "r" => '2' NOTICE: #0.3 5 --> start of assignment r := r + 1 (tnl=1) NOTICE: #0.3 "r" => '2' NOTICE: #0.2 <-- end of assignment (elapsed time=0.010 ms) NOTICE: #0.3 "r" => '3' NOTICE: #0.3 5 --> start of assignment r := r + 1 (tnl=1) NOTICE: #0.3 "r" => '3' NOTICE: #0.2 <-- end of assignment (elapsed time=0.010 ms) NOTICE: #0.3 "r" => '4' NOTICE: #0.1 <-- end of FOR with integer loop variable (elapsed time=0.010 ms) NOTICE: #0.4 7 --> start of assignment r := r + 10 (tnl=1) NOTICE: #0.4 "r" => '4' NOTICE: #0.3 <-- end of assignment (elapsed time=0.010 ms) NOTICE: #0.4 "r" => '14' NOTICE: #0.5 8 --> start of RETURN (tnl=1) NOTICE: #0.5 "r" => '14' NOTICE: #0.4 <-- end of RETURN (elapsed time=0.010 ms) NOTICE: #0.5 <-- end of statement block (elapsed time=0.010 ms) NOTICE: #0 <<- end of function trace_test (elapsed time=0.010 ms) trace_test ------------ 14 (1 row) create or replace function trace_test(int) returns int as $$ declare r int default 0; begin for i in 1..$1 loop perform plpgsql_check_pragma('disable:tracer'); r := r + 1; end loop; r := r + 10; return r; end; $$ language plpgsql; select trace_test(4); NOTICE: #0 ->> start of function trace_test(integer) (oid=0, tnl=1) NOTICE: #0 "$1" => '4' NOTICE: #0.1 3 --> start of statement block (tnl=1) NOTICE: #0.2 4 --> start of FOR with integer loop variable (tnl=1) NOTICE: #0.3 5 --> start of perform plpgsql_check_pragma('disable: .. (tnl=1) NOTICE: #0.2 <-- end of PERFORM (elapsed time=0.010 ms) NOTICE: #0.1 <-- end of FOR with integer loop variable (elapsed time=0.010 ms) NOTICE: #0.5 8 --> start of assignment r := r + 10 (tnl=1) NOTICE: #0.5 "r" => '4' NOTICE: #0.4 <-- end of assignment (elapsed time=0.010 ms) NOTICE: #0.5 "r" => '14' NOTICE: #0.6 9 --> start of RETURN (tnl=1) NOTICE: #0.6 "r" => '14' NOTICE: #0.5 <-- end of RETURN (elapsed time=0.010 ms) NOTICE: #0.6 <-- end of statement block (elapsed time=0.010 ms) NOTICE: #0 <<- end of function trace_test (elapsed time=0.010 ms) trace_test ------------ 14 (1 row) create or replace function trace_test(int) returns int as $$ declare r int default 0; begin perform plpgsql_check_pragma('disable:tracer'); for i in 1..$1 loop r := r + 1; end loop; perform plpgsql_check_pragma('enable:tracer'); r := r + 10; return r; end; $$ language plpgsql; select trace_test(4); NOTICE: #0 ->> start of function trace_test(integer) (oid=0, tnl=1) NOTICE: #0 "$1" => '4' NOTICE: #0.1 3 --> start of statement block (tnl=1) NOTICE: #0.2 4 --> start of perform plpgsql_check_pragma('disable: .. (tnl=1) NOTICE: #0.1 <-- end of PERFORM (elapsed time=0.010 ms) NOTICE: #0.6 12 --> start of assignment r := r + 10 (tnl=1) NOTICE: #0.6 "r" => '4' NOTICE: #0.5 <-- end of assignment (elapsed time=0.010 ms) NOTICE: #0.6 "r" => '14' NOTICE: #0.7 13 --> start of RETURN (tnl=1) NOTICE: #0.7 "r" => '14' NOTICE: #0.6 <-- end of RETURN (elapsed time=0.010 ms) NOTICE: #0.7 <-- end of statement block (elapsed time=0.010 ms) NOTICE: #0 <<- end of function trace_test (elapsed time=0.010 ms) trace_test ------------ 14 (1 row) drop function trace_test(int); create or replace function public.fx1() returns table(i integer, j integer, found boolean) as $$ begin for i in 1..10 loop for j in 1..10 loop return next; end loop; end loop; end; $$ language plpgsql immutable; select * from plpgsql_check_function('fx1'); plpgsql_check_function -------------------------------------------------------------------------- warning:00000:2:statement block:parameter "found" is shadowed Detail: Local auto variable shadows function parameter. warning:00000:3:FOR with integer loop variable:parameter "i" is shadowed Detail: Local auto variable shadows function parameter. warning:00000:5:FOR with integer loop variable:parameter "j" is shadowed Detail: Local auto variable shadows function parameter. warning extra:00000:unmodified OUT variable "i" warning extra:00000:unmodified OUT variable "j" warning extra:00000:unmodified OUT variable "found" (9 rows) drop function fx1; -- tracer test set plpgsql_check.enable_tracer to on; select plpgsql_check_tracer(true); NOTICE: tracer is active NOTICE: tracer verbosity is verbose plpgsql_check_tracer ---------------------- t (1 row) create role plpgsql_check_test_role; DO $$ begin begin -- should to fail create role plpgsql_check_test_role; exception when duplicate_object then -- Role already exists -- the exception handler is empty (#156) end; end; $$; NOTICE: #0 ->> start of block inline_code_block (oid=0, tnl=1) NOTICE: #0.1 2 --> start of statement block (tnl=1) NOTICE: #0.2 3 --> start of statement block (tnl=1) NOTICE: #0.3 5 --> start of SQL statement (query='create role plpgsql_check_test ..') (tnl=2) NOTICE: #0.1 <-- end of SQL statement (elapsed time=0.010 ms) aborted NOTICE: #0.2 <-- end of statement block (elapsed time=0.010 ms) NOTICE: #0.3 <-- end of statement block (elapsed time=0.010 ms) NOTICE: #0 <<- end of block (elapsed time=0.010 ms) drop role plpgsql_check_test_role; set plpgsql_check.enable_tracer to off; select plpgsql_check_tracer(false); NOTICE: tracer is not active NOTICE: tracer verbosity is verbose plpgsql_check_tracer ---------------------- f (1 row) -- tracing constants -- issue #159 create or replace function tabret_dynamic() returns table (id integer, val text) as $$ begin return query execute 'select id, val from (values (1, ''a''), (2, ''b'')) as v(id, val)'; end; $$ language plpgsql immutable; -- should be ok select * from plpgsql_check_function('tabret_dynamic()'); plpgsql_check_function ------------------------ (0 rows) create or replace function tabret_dynamic() returns table (id integer, val text) as $$ declare z_query text; begin z_query := 'select id, val from (values (1, ''a''), (2, ''b'')) as v(id, val)'; execute z_query into id, val; return next; end; $$ language plpgsql immutable; -- should be ok select * from plpgsql_check_function('tabret_dynamic()'); plpgsql_check_function ------------------------ (0 rows) create or replace function tabret_dynamic() returns table (id integer, val text) as $$ declare z_query text; begin z_query := 'select id, val from (values (1, ''a''), (2, ''b'')) as v(id, val)'; for id, val in execute z_query loop return next; end loop; end; $$ language plpgsql immutable; -- should be ok select * from plpgsql_check_function('tabret_dynamic()'); plpgsql_check_function ------------------------ (0 rows) create or replace function tabret_dynamic() returns table (id integer, val text) as $$ declare z_query text; begin z_query := 'select id, val from (values (1, ''a''), (2, ''b'')) as v(id, val)'; return query execute z_query; end; $$ language plpgsql immutable; -- should be ok select * from plpgsql_check_function('tabret_dynamic()'); plpgsql_check_function ------------------------ (0 rows) drop function tabret_dynamic; -- should not to crash on empty string, or comment create or replace function dynamic_emptystr() returns void as $$ begin execute ''; end; $$ language plpgsql; -- should be ok select * from plpgsql_check_function('dynamic_emptystr()'); plpgsql_check_function ------------------------ (0 rows) create or replace function dynamic_emptystr() returns void as $$ declare x int; begin execute '--' into x; end; $$ language plpgsql; -- should not to crash, no result error select * from plpgsql_check_function('dynamic_emptystr()'); plpgsql_check_function ------------------------------------------------------- error:XX000:4:EXECUTE:expression does not return data (1 row) drop function dynamic_emptystr(); -- unclosed cursor detection create or replace function fx() returns void as $$ declare c refcursor; begin open c for select * from pg_class; end; $$ language plpgsql; do $$ begin perform fx(); -- should to show warning perform fx(); end; $$; WARNING: cursor "c" is not closed set plpgsql_check.strict_cursors_leaks to on; do $$ begin -- should to show warning perform fx(); -- should to show warning perform fx(); end; $$; WARNING: cursor is not closed WARNING: cursor is not closed create or replace function fx() returns void as $$ declare c refcursor; begin open c for select * from pg_class; close c; end; $$ language plpgsql; -- without warnings do $$ begin perform fx(); perform fx(); end; $$; set plpgsql_check.strict_cursors_leaks to off; drop function fx(); create table public.testt(a int, b int); create or replace function fx() returns void as $$ declare v1 varchar default 'public'; v2 varchar default 'testt'; v3 varchar default 'a'; begin raise notice '%', format('%I.%I.%I', v1, v2, v3); perform 'pragma:assert-schema: v1'; perform 'pragma:assert-table: v1, v2'; perform 'pragma:assert-column: v1, v2, v3'; perform 'pragma:assert-table: v2'; perform 'pragma:assert-column: v2, v3'; end; $$ language plpgsql; select * from plpgsql_check_function('fx()'); plpgsql_check_function ------------------------ (0 rows) create table public.testt(a int, b int); ERROR: relation "testt" already exists create or replace function fx() returns void as $$ declare v1 varchar default 'public'; v2 varchar default 'testt'; v3 varchar default 'x'; begin raise notice '%', format('%I.%I.%I', v1, v2, v3); perform 'pragma:assert-column: v1, v2, v3'; end; $$ language plpgsql; select * from plpgsql_check_function('fx()'); plpgsql_check_function ------------------------------------------------------------------------------ error:42703:8:PERFORM:column "x" of relation "public"."testt" does not exist (1 row) drop function fx(); drop table public.testt; create or replace function fx() returns void as $$ declare v_sql varchar default ''; i int; begin -- we don't support constant tracing from queries select 'bla bla bla' into v_sql; execute v_sql into i; raise notice '%', i; end; $$ language plpgsql; -- should be ok select * from plpgsql_check_function('fx()'); plpgsql_check_function ------------------------ (0 rows) drop function fx(); -- should not to raise warning do $$ declare c cursor for select * from generate_series(1,10); t int; begin for i in 1..2 loop open c; loop fetch c into t; exit when not found; raise notice '%', t; end loop; close c; end loop; end; $$; NOTICE: 1 NOTICE: 2 NOTICE: 3 NOTICE: 4 NOTICE: 5 NOTICE: 6 NOTICE: 7 NOTICE: 8 NOTICE: 9 NOTICE: 10 NOTICE: 1 NOTICE: 2 NOTICE: 3 NOTICE: 4 NOTICE: 5 NOTICE: 6 NOTICE: 7 NOTICE: 8 NOTICE: 9 NOTICE: 10 -- should not raise warning create or replace function fx(p text) returns void as $$ begin execute format($_$select '%1$I'$_$, p); end; $$ language plpgsql; -- should be ok select * from plpgsql_check_function('fx(text)'); plpgsql_check_function ------------------------ (0 rows) drop function fx(text); create or replace function fx() returns void as $$ declare p varchar; begin p := 'ahoj'; execute format($_$select '%1$I'$_$, p); end; $$ language plpgsql; -- should be ok select * from plpgsql_check_function('fx()'); plpgsql_check_function ------------------------ (0 rows) drop function fx(); -- should not crash create or replace procedure p1() as $$ begin commit; end; $$ language plpgsql; set plpgsql_check.cursors_leaks to on; do $$ declare c cursor for select 1; begin open c; call p1(); end $$; set plpgsql_check.cursors_leaks to default; drop procedure p1; plpgsql_check-2.7.8/expected/plpgsql_check_active_1.out000066400000000000000000012632241465410532300233100ustar00rootroot00000000000000load 'plpgsql'; create extension if not exists plpgsql_check; set client_min_messages to notice; set plpgsql_check.regress_test_mode = true; -- -- check function statement tests -- --should fail - is not plpgsql select * from plpgsql_check_function_tb('session_user()'); ERROR: "session_user"() is not a plpgsql function create table t1(a int, b int); create table pa (id int, pa_id character varying(32), status character varying(60)); create table ml(ml_id character varying(32), status_from character varying(60), pa_id character varying(32), xyz int); create function f1() returns void as $$ begin if false then update t1 set c = 30; end if; if false then raise notice '%', r.c; end if; end; $$ language plpgsql; select f1(); f1 ---- (1 row) select * from plpgsql_check_function_tb('f1()', fatal_errors := true); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+---------------+----------+--------------------------------------------+--------+------+-------+----------+----------------------+--------- f1 | 4 | SQL statement | 42703 | column "c" of relation "t1" does not exist | | | error | 15 | update t1 set c = 30 | (1 row) select * from plpgsql_check_function_tb('f1()', fatal_errors := false); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+---------------+----------+--------------------------------------------+--------+------+-------+----------+----------------------+--------- f1 | 4 | SQL statement | 42703 | column "c" of relation "t1" does not exist | | | error | 15 | update t1 set c = 30 | f1 | 7 | RAISE | 42P01 | missing FROM-clause entry for table "r" | | | error | 8 | SELECT r.c | (2 rows) select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+---------------+----------+--------------------------------------------+--------+------+-------+----------+----------------------+--------- f1 | 4 | SQL statement | 42703 | column "c" of relation "t1" does not exist | | | error | 15 | update t1 set c = 30 | (1 row) drop function f1(); create function f1() returns void as $$ begin if false then insert into t1 values(10,20); update t1 set a = 10; delete from t1; end if; end; $$ language plpgsql stable; select f1(); f1 ---- (1 row) select * from plpgsql_check_function_tb('f1()', fatal_errors := false); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+---------------+----------+--------------------------------------------------+--------+------+-------+----------+------------------------------+--------- f1 | 4 | SQL statement | 0A000 | INSERT is not allowed in a non volatile function | | | error | 1 | insert into t1 values(10,20) | f1 | 5 | SQL statement | 0A000 | UPDATE is not allowed in a non volatile function | | | error | 1 | update t1 set a = 10 | f1 | 6 | SQL statement | 0A000 | DELETE is not allowed in a non volatile function | | | error | 1 | delete from t1 | (3 rows) drop function f1(); -- profiler check set plpgsql_check.profiler to on; create function f1() returns void as $$ begin if false then insert into t1 values(10,20); update t1 set a = 10; delete from t1; end if; end; $$ language plpgsql; select lineno, stmt_lineno, exec_stmts, source from plpgsql_profiler_function_tb('f1()'); lineno | stmt_lineno | exec_stmts | source --------+-------------+------------+----------------------------------- 1 | | | 2 | | | begin 3 | | | if false then 4 | | | insert into t1 values(10,20); 5 | | | update t1 set a = 10; 6 | | | delete from t1; 7 | | | end if; 8 | | | end; (8 rows) select f1(); f1 ---- (1 row) select lineno, stmt_lineno, exec_stmts, source from plpgsql_profiler_function_tb('f1()'); lineno | stmt_lineno | exec_stmts | source --------+-------------+------------+----------------------------------- 1 | | | 2 | 2 | 1 | begin 3 | 3 | 1 | if false then 4 | 4 | 0 | insert into t1 values(10,20); 5 | 5 | 0 | update t1 set a = 10; 6 | 6 | 0 | delete from t1; 7 | | | end if; 8 | | | end; (8 rows) select plpgsql_profiler_reset('f1()'); plpgsql_profiler_reset ------------------------ (1 row) select lineno, stmt_lineno, exec_stmts, source from plpgsql_profiler_function_tb('f1()'); lineno | stmt_lineno | exec_stmts | source --------+-------------+------------+----------------------------------- 1 | | | 2 | | | begin 3 | | | if false then 4 | | | insert into t1 values(10,20); 5 | | | update t1 set a = 10; 6 | | | delete from t1; 7 | | | end if; 8 | | | end; (8 rows) select f1(); f1 ---- (1 row) select lineno, stmt_lineno, exec_stmts, source from plpgsql_profiler_function_tb('f1()'); lineno | stmt_lineno | exec_stmts | source --------+-------------+------------+----------------------------------- 1 | | | 2 | 2 | 1 | begin 3 | 3 | 1 | if false then 4 | 4 | 0 | insert into t1 values(10,20); 5 | 5 | 0 | update t1 set a = 10; 6 | 6 | 0 | delete from t1; 7 | | | end if; 8 | | | end; (8 rows) select plpgsql_profiler_reset_all(); plpgsql_profiler_reset_all ---------------------------- (1 row) select lineno, stmt_lineno, exec_stmts, source from plpgsql_profiler_function_tb('f1()'); lineno | stmt_lineno | exec_stmts | source --------+-------------+------------+----------------------------------- 1 | | | 2 | | | begin 3 | | | if false then 4 | | | insert into t1 values(10,20); 5 | | | update t1 set a = 10; 6 | | | delete from t1; 7 | | | end if; 8 | | | end; (8 rows) drop function f1(); create or replace function f1() returns void as $$ begin raise notice '1'; exception when others then raise notice '2'; end; $$ language plpgsql; select f1(); NOTICE: 1 f1 ---- (1 row) select lineno, stmt_lineno, exec_stmts, source from plpgsql_profiler_function_tb('f1()'); lineno | stmt_lineno | exec_stmts | source --------+-------------+------------+---------------------------- 1 | | | 2 | 2 | 1 | begin 3 | 3 | 1 | raise notice '1'; 4 | | | exception when others then 5 | 5 | 0 | raise notice '2'; 6 | | | end; (6 rows) drop function f1(); -- test queryid retrieval create function f1() returns void as $$ declare t1 text = 't1'; begin insert into t1 values(10,20); EXECUTE 'update ' || 't1' || ' set a = 10'; EXECUTE 'delete from ' || t1; end; $$ language plpgsql; select plpgsql_profiler_reset_all(); plpgsql_profiler_reset_all ---------------------------- (1 row) select plpgsql_profiler_install_fake_queryid_hook(); plpgsql_profiler_install_fake_queryid_hook -------------------------------------------- (1 row) select f1(); f1 ---- (1 row) select queryids, lineno, stmt_lineno, exec_stmts, source from plpgsql_profiler_function_tb('f1()'); queryids | lineno | stmt_lineno | exec_stmts | source ----------+--------+-------------+------------+------------------------------------------------ | 1 | | | | 2 | | | declare | 3 | | | t1 text = 't1'; | 4 | 4 | 1 | begin {3} | 5 | 5 | 1 | insert into t1 values(10,20); {2} | 6 | 6 | 1 | EXECUTE 'update ' || 't1' || ' set a = 10'; {4} | 7 | 7 | 1 | EXECUTE 'delete from ' || t1; | 8 | | | end; (8 rows) select plpgsql_profiler_remove_fake_queryid_hook(); plpgsql_profiler_remove_fake_queryid_hook ------------------------------------------- (1 row) drop function f1(); set plpgsql_check.profiler to off; create function f1() returns void as $$ declare r record; begin if false then for r in update t1 set a = a + 1 returning * loop raise notice '%', r.a; end loop; end if; end; $$ language plpgsql; select f1(); f1 ---- (1 row) select * from plpgsql_check_function_tb('f1()', fatal_errors := false); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+---------+--------+------+-------+----------+-------+--------- (0 rows) drop function f1(); create function f1() returns void as $$ declare r record; begin if false then for r in update t1 set a = a + 1 returning * loop raise notice '%', r.a; end loop; end if; end; $$ language plpgsql stable; select f1(); f1 ---- (1 row) select * from plpgsql_check_function_tb('f1()', fatal_errors := false); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+----------------------+----------+--------------------------------------------------+--------+------+-------+----------+-------------------------------------+--------- f1 | 5 | FOR over SELECT rows | 0A000 | UPDATE is not allowed in a non volatile function | | | error | 1 | update t1 set a = a + 1 returning * | (1 row) drop function f1(); create function g1(out a int, out b int) as $$ select 10,20; $$ language sql; create function f1() returns void as $$ declare r record; begin r := g1(); if false then raise notice '%', r.c; end if; end; $$ language plpgsql; select f1(); f1 ---- (1 row) select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+-----------------------------+--------+------+-------+----------+-------+---------------------------- f1 | 6 | RAISE | 42703 | record "r" has no field "c" | | | error | | | SQL statement "SELECT r.c" (1 row) select f1(); f1 ---- (1 row) drop function f1(); drop function g1(); create function g1(out a int, out b int) returns setof record as $$ select * from t1; $$ language sql; create function f1() returns void as $$ declare r record; begin for r in select * from g1() loop raise notice '%', r.c; end loop; end; $$ language plpgsql; select f1(); f1 ---- (1 row) select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+-----------------------------+--------+------+-------+----------+-------+---------------------------- f1 | 6 | RAISE | 42703 | record "r" has no field "c" | | | error | | | SQL statement "SELECT r.c" (1 row) select f1(); f1 ---- (1 row) create or replace function f1() returns void as $$ declare r record; begin for r in select * from g1() loop r.c := 20; end loop; end; $$ language plpgsql; select f1(); f1 ---- (1 row) select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+------------+----------+-----------------------------+--------+------+-------+----------+-------+--------------------------------------------------------------- f1 | 6 | assignment | 42703 | record "r" has no field "c" | | | error | | | at assignment to field "c" of variable "r" declared on line 2 (1 row) select f1(); f1 ---- (1 row) drop function f1(); drop function g1(); create function f1() returns int as $$ declare r int; begin if false then r := a + b; end if; return r; end; $$ language plpgsql; select f1(); f1 ---- (1 row) select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+------------+----------+---------------------------+--------+------+-------+----------+--------------+-------------------------------------------------- f1 | 5 | assignment | 42703 | column "a" does not exist | | | error | 8 | SELECT a + b | at assignment to variable "r" declared on line 2 (1 row) select f1(); f1 ---- (1 row) drop function f1(); create or replace function f1() returns void as $$ declare r int[]; begin if false then r[c+10] := 20; end if; end; $$ language plpgsql; select f1(); f1 ---- (1 row) select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+------------+----------+---------------------------+--------+------+-------+----------+-------------+------------------------------------------------------------- f1 | 5 | assignment | 42703 | column "c" does not exist | | | error | 8 | SELECT c+10 | at assignment to element of variable "r" declared on line 2 (1 row) select f1(); f1 ---- (1 row) drop function f1(); create or replace function f1() returns void as $$ declare r int; begin if false then r[10] := 20; end if; end; $$ language plpgsql set search_path = public; select f1(); f1 ---- (1 row) select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+------------+----------+------------------------------------+--------+------+-------+----------+-------+------------------------------------------------------------- f1 | 5 | assignment | 42804 | subscripted object is not an array | | | error | | | at assignment to element of variable "r" declared on line 2 (1 row) select f1(); f1 ---- (1 row) drop function f1(); create or replace function f1_trg() returns trigger as $$ begin if new.a > 10 then raise notice '%', new.b; raise notice '%', new.c; end if; return new; end; $$ language plpgsql; create trigger t1_f1 before insert on t1 for each row execute procedure f1_trg(); insert into t1 values(6,30); select * from plpgsql_check_function_tb('f1_trg()','t1'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+-------------------------------+--------+------+-------+----------+-------+------------------------------ f1_trg | 5 | RAISE | 42703 | record "new" has no field "c" | | | error | | | SQL statement "SELECT new.c" (1 row) insert into t1 values(6,30); create or replace function f1_trg() returns trigger as $$ begin new.a := new.a + 10; new.b := new.b + 10; new.c := 30; return new; end; $$ language plpgsql; -- should to fail select * from plpgsql_check_function_tb('f1_trg()','t1'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+------------+----------+-------------------------------+--------+------+-------+----------+-------+----------------------------------------------------------------- f1_trg | 5 | assignment | 42703 | record "new" has no field "c" | | | error | | | at assignment to field "c" of variable "new" declared on line 0 (1 row) -- should to fail but not crash insert into t1 values(6,30); ERROR: record "new" has no field "c" CONTEXT: PL/pgSQL function f1_trg() line 5 at assignment create or replace function f1_trg() returns trigger as $$ begin new.a := new.a + 10; new.b := new.b + 10; return new; end; $$ language plpgsql; -- ok select * from plpgsql_check_function_tb('f1_trg()', 't1'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+---------+--------+------+-------+----------+-------+--------- (0 rows) -- ok insert into t1 values(6,30); create or replace function f1_trg() returns trigger as $$ begin new.a := new.a + 10; new.b := new.b + 10; return null; end; $$ language plpgsql; -- ok select * from plpgsql_check_function_tb('f1_trg()', 't1'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+---------+--------+------+-------+----------+-------+--------- (0 rows) insert into t1 values(60,300); select * from t1; a | b ----+---- 6 | 30 6 | 30 16 | 40 (3 rows) insert into t1 values(600,30); select * from t1; a | b ----+---- 6 | 30 6 | 30 16 | 40 (3 rows) drop trigger t1_f1 on t1; drop function f1_trg(); -- test of showing caret on correct place for multiline queries create or replace function f1() returns void as $$ begin select var from foo; end; $$ language plpgsql; select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+---------------+----------+-------------------------------+--------+------+-------+----------+--------+--------- f1 | 3 | SQL statement | 42P01 | relation "foo" does not exist | | | error | 23 | select+| | | | | | | | | | var +| | | | | | | | | | from+| | | | | | | | | | foo | (1 row) drop function f1(); create or replace function f1() returns int as $$ begin return (select a from t1 where hh = 20); end; $$ language plpgsql; select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+----------------------------+--------+------+-------+----------+----------------------------+--------- f1 | 3 | RETURN | 42703 | column "hh" does not exist | | | error | 57 | SELECT (select a +| | | | | | | | | | from t1 +| | | | | | | | | | where hh = 20) | (1 row) create or replace function f1() returns int as $$ begin return (select a from txxxxxxx where hh = 20); end; $$ language plpgsql; select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+------------------------------------+--------+------+-------+----------+----------------------------+--------- f1 | 3 | RETURN | 42P01 | relation "txxxxxxx" does not exist | | | error | 36 | SELECT (select a +| | | | | | | | | | from txxxxxxx+| | | | | | | | | | where hh = 20) | (1 row) drop function f1(); drop table t1; -- raise warnings when target row has different number of attributies in -- SELECT INTO statement create or replace function f1() returns void as $$ declare a1 int; a2 int; begin select 10,20 into a1,a2; end; $$ language plpgsql; -- should be ok select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+--------------------------+--------+------+---------------+----------+-------+--------- f1 | 2 | DECLARE | 00000 | never read variable "a1" | | | warning extra | | | f1 | 2 | DECLARE | 00000 | never read variable "a2" | | | warning extra | | | (2 rows) create or replace function f1() returns void as $$ declare a1 int; begin select 10,20 into a1; end; $$ language plpgsql; -- raise warning select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+---------------+----------+------------------------------------------+---------------------------------------------------------------+-------------------------------------------------+---------------+----------+-------+--------- f1 | 4 | SQL statement | 00000 | too many attributes for target variables | There are less target variables than output columns in query. | Check target variables in SELECT INTO statement | warning | | | f1 | 2 | DECLARE | 00000 | never read variable "a1" | | | warning extra | | | (2 rows) create or replace function f1() returns void as $$ declare a1 int; a2 int; begin select 10 into a1,a2; end; $$ language plpgsql; -- raise warning select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+---------------+----------+-----------------------------------------+---------------------------------------------------------------+--------------------------------------------------+---------------+----------+-------+--------- f1 | 4 | SQL statement | 00000 | too few attributes for target variables | There are more target variables than output columns in query. | Check target variables in SELECT INTO statement. | warning | | | f1 | 2 | DECLARE | 00000 | never read variable "a1" | | | warning extra | | | f1 | 2 | DECLARE | 00000 | never read variable "a2" | | | warning extra | | | (3 rows) -- bogus code set check_function_bodies to off; create or replace function f1() returns void as $$ adasdfsadf $$ language plpgsql; select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+--------------------------------------+--------+------+-------+----------+------------+--------------------------------------------------- f1 | | | 42601 | syntax error at or near "adasdfsadf" | | | error | 2 | +| compilation of PL/pgSQL function "f1" near line 1 | | | | | | | | | adasdfsadf+| | | | | | | | | | | (1 row) drop function f1(); create table t1(a int, b int); create function g1(out a int, out b int) as $$ select 10,20; $$ language sql; create function f1() returns void as $$ declare r record; begin r := g1(); if false then raise notice '%', r.c; end if; end; $$ language plpgsql; select f1(); f1 ---- (1 row) select * from plpgsql_check_function('f1()'); plpgsql_check_function ------------------------------------------------- error:42703:6:RAISE:record "r" has no field "c" Context: SQL statement "SELECT r.c" (2 rows) select f1(); f1 ---- (1 row) drop function f1(); drop function g1(); create function g1(out a int, out b int) returns setof record as $$ select * from t1; $$ language sql; create function f1() returns void as $$ declare r record; begin for r in select * from g1() loop raise notice '%', r.c; end loop; end; $$ language plpgsql; select f1(); f1 ---- (1 row) select * from plpgsql_check_function('f1()'); plpgsql_check_function ------------------------------------------------- error:42703:6:RAISE:record "r" has no field "c" Context: SQL statement "SELECT r.c" (2 rows) select f1(); f1 ---- (1 row) create or replace function f1() returns void as $$ declare r record; begin for r in select * from g1() loop r.c := 20; end loop; end; $$ language plpgsql; select f1(); f1 ---- (1 row) select * from plpgsql_check_function('f1()'); plpgsql_check_function ------------------------------------------------------------------------ error:42703:6:assignment:record "r" has no field "c" Context: at assignment to field "c" of variable "r" declared on line 2 (2 rows) select f1(); f1 ---- (1 row) drop function f1(); drop function g1(); create function f1() returns int as $$ declare r int; begin if false then r := a + b; end if; return r; end; $$ language plpgsql; select f1(); f1 ---- (1 row) select * from plpgsql_check_function('f1()'); plpgsql_check_function ----------------------------------------------------------- error:42703:5:assignment:column "a" does not exist Query: SELECT a + b -- ^ Context: at assignment to variable "r" declared on line 2 (4 rows) select f1(); f1 ---- (1 row) drop function f1(); create or replace function f1() returns void as $$ declare r int[]; begin if false then r[c+10] := 20; end if; end; $$ language plpgsql; select f1(); f1 ---- (1 row) select * from plpgsql_check_function('f1()'); plpgsql_check_function ---------------------------------------------------------------------- error:42703:5:assignment:column "c" does not exist Query: SELECT c+10 -- ^ Context: at assignment to element of variable "r" declared on line 2 (4 rows) select f1(); f1 ---- (1 row) drop function f1(); create or replace function f1() returns void as $$ declare r int; begin if false then r[10] := 20; end if; end; $$ language plpgsql set search_path = public; select f1(); f1 ---- (1 row) select * from plpgsql_check_function('f1()'); plpgsql_check_function ---------------------------------------------------------------------- error:42804:5:assignment:subscripted object is not an array Context: at assignment to element of variable "r" declared on line 2 (2 rows) select f1(); f1 ---- (1 row) drop function f1(); create or replace function f1_trg() returns trigger as $$ begin if new.a > 10 then raise notice '%', new.b; raise notice '%', new.c; end if; return new; end; $$ language plpgsql; create trigger t1_f1 before insert on t1 for each row execute procedure f1_trg(); insert into t1 values(6,30); select * from plpgsql_check_function('f1_trg()','t1'); plpgsql_check_function --------------------------------------------------- error:42703:5:RAISE:record "new" has no field "c" Context: SQL statement "SELECT new.c" (2 rows) insert into t1 values(6,30); create or replace function f1_trg() returns trigger as $$ begin new.a := new.a + 10; new.b := new.b + 10; new.c := 30; return new; end; $$ language plpgsql; -- should to fail select * from plpgsql_check_function('f1_trg()','t1'); plpgsql_check_function -------------------------------------------------------------------------- error:42703:5:assignment:record "new" has no field "c" Context: at assignment to field "c" of variable "new" declared on line 0 (2 rows) -- should to fail but not crash insert into t1 values(6,30); ERROR: record "new" has no field "c" CONTEXT: PL/pgSQL function f1_trg() line 5 at assignment create or replace function f1_trg() returns trigger as $$ begin new.a := new.a + 10; new.b := new.b + 10; return new; end; $$ language plpgsql; -- ok select * from plpgsql_check_function('f1_trg()', 't1'); plpgsql_check_function ------------------------ (0 rows) -- ok insert into t1 values(6,30); select * from t1; a | b ----+---- 6 | 30 6 | 30 16 | 40 (3 rows) drop trigger t1_f1 on t1; drop function f1_trg(); -- test of showing caret on correct place for multiline queries create or replace function f1() returns void as $$ begin select var from foo; end; $$ language plpgsql; select * from plpgsql_check_function('f1()'); plpgsql_check_function ----------------------------------------------------------- error:42P01:3:SQL statement:relation "foo" does not exist Query: select var from foo -- ^ (6 rows) drop function f1(); create or replace function f1() returns int as $$ begin return (select a from t1 where hh = 20); end; $$ language plpgsql; select * from plpgsql_check_function('f1()'); plpgsql_check_function ------------------------------------------------- error:42703:3:RETURN:column "hh" does not exist Query: SELECT (select a from t1 where hh = 20) -- ^ (5 rows) create or replace function f1() returns int as $$ begin return (select a from txxxxxxx where hh = 20); end; $$ language plpgsql; select * from plpgsql_check_function('f1()'); plpgsql_check_function --------------------------------------------------------- error:42P01:3:RETURN:relation "txxxxxxx" does not exist Query: SELECT (select a from txxxxxxx -- ^ where hh = 20) (5 rows) drop function f1(); drop table t1; -- raise warnings when target row has different number of attributies in -- SELECT INTO statement create or replace function f1() returns void as $$ declare a1 int; a2 int; begin select 10,20 into a1,a2; end; $$ language plpgsql; -- should be ok select * from plpgsql_check_function('f1()'); plpgsql_check_function -------------------------------------------------------- warning extra:00000:2:DECLARE:never read variable "a1" warning extra:00000:2:DECLARE:never read variable "a2" (2 rows) create or replace function f1() returns void as $$ declare a1 int; begin select 10,20 into a1; end; $$ language plpgsql; -- raise warning select * from plpgsql_check_function('f1()'); plpgsql_check_function ------------------------------------------------------------------------ warning:00000:4:SQL statement:too many attributes for target variables Detail: There are less target variables than output columns in query. Hint: Check target variables in SELECT INTO statement warning extra:00000:2:DECLARE:never read variable "a1" (4 rows) create or replace function f1() returns void as $$ declare a1 int; a2 int; begin select 10 into a1,a2; end; $$ language plpgsql; -- raise warning select * from plpgsql_check_function('f1()'); plpgsql_check_function ----------------------------------------------------------------------- warning:00000:4:SQL statement:too few attributes for target variables Detail: There are more target variables than output columns in query. Hint: Check target variables in SELECT INTO statement. warning extra:00000:2:DECLARE:never read variable "a1" warning extra:00000:2:DECLARE:never read variable "a2" (5 rows) -- bogus code set check_function_bodies to off; create or replace function f1() returns void as $$ adasdfsadf $$ language plpgsql; select * from plpgsql_check_function('f1()'); plpgsql_check_function ------------------------------------------------------------ error:42601:syntax error at or near "adasdfsadf" Query: adasdfsadf -- ^ Context: compilation of PL/pgSQL function "f1" near line 1 (6 rows) drop function f1(); create table f1tbl(a int, b int); -- unused variables create or replace function f1(_input1 int) returns table(_output1 int, _output2 int) as $$ declare _f1 int; _f2 int; _f3 int; _f4 int; _f5 int; _r record; _tbl f1tbl; begin if true then _f1 := 1; end if; select 1, 2 into _f3, _f4; perform 1 where _f5 is null; select 1 into _r; select 1, 2 into _tbl; -- check that SQLSTATE and SQLERRM don't raise false positives begin exception when raise_exception then end; end $$ language plpgsql; select * from plpgsql_check_function('f1(int)'); plpgsql_check_function ---------------------------------------------------------- warning:00000:4:DECLARE:unused variable "_f2" warning extra:00000:3:DECLARE:never read variable "_f1" warning extra:00000:5:DECLARE:never read variable "_f3" warning extra:00000:6:DECLARE:never read variable "_f4" warning extra:00000:8:DECLARE:never read variable "_r" warning extra:00000:9:DECLARE:never read variable "_tbl" warning extra:00000:unused parameter "_input1" warning extra:00000:unmodified OUT variable "_output1" warning extra:00000:unmodified OUT variable "_output2" (9 rows) drop function f1(int); drop table f1tbl; -- check that NEW and OLD are not reported unused create table f1tbl(); create or replace function f1() returns trigger as $$ begin return null; end $$ language plpgsql; select * from plpgsql_check_function('f1()', 'f1tbl'); plpgsql_check_function ------------------------ (0 rows) drop function f1(); drop table f1tbl; create table tabret(a int, b int); insert into tabret values(10,10); create or replace function f1() returns int as $$ begin return (select a from tabret); end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------------- performance:00000:routine is marked as VOLATILE, should be STABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (2 rows) create or replace function f1() returns int as $$ begin return (select a::numeric from tabret); end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------------- performance:42804:3:RETURN:target type is different type than source type Detail: cast "numeric" value to "integer" type Hint: Hidden casting can be a performance issue. performance:00000:routine is marked as VOLATILE, should be STABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (5 rows) create or replace function f1() returns int as $$ begin return (select a, b from tabret); end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------- error:42601:3:RETURN:subquery must return only one column Query: SELECT (select a, b from tabret) -- ^ (3 rows) drop function f1(); create or replace function f1() returns table(ax int, bx int) as $$ begin return query select * from tabret; return; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------------- performance:00000:routine is marked as VOLATILE, should be STABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (2 rows) drop function f1(); create or replace function f1() returns table(ax numeric, bx numeric) as $$ begin return query select * from tabret; return; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------- error:42804:3:RETURN QUERY:structure of query does not match function result type Detail: Returned type integer does not match expected type numeric in column 1. (2 rows) drop function f1(); create or replace function f1() returns setof tabret as $$ begin return query select * from tabret; return; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------------- performance:00000:routine is marked as VOLATILE, should be STABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (2 rows) create or replace function f1() returns setof tabret as $$ begin return query select a from tabret; return; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------- error:42804:3:RETURN QUERY:structure of query does not match function result type Detail: Number of returned columns (1) does not match expected column count (2). (2 rows) create or replace function f1() returns setof tabret as $$ begin return query select a::numeric,b::numeric from tabret; return; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------- error:42804:3:RETURN QUERY:structure of query does not match function result type Detail: Returned type numeric does not match expected type integer in column 1. (2 rows) drop function f1(); create or replace function f1(a int) returns setof numeric as $$ begin return query select a; end $$ language plpgsql; select * from plpgsql_check_function('f1(int)', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------- error:42804:2:RETURN QUERY:structure of query does not match function result type Detail: Returned type integer does not match expected type numeric in column 1. (2 rows) drop function f1(int); drop table tabret; create or replace function f1() returns void as $$ declare intval integer; begin intval := null; -- ok intval := 1; -- OK intval := '1'; -- OK intval := text '1'; -- not OK intval := current_date; -- not OK select 1 into intval; -- OK select '1' into intval; -- OK select text '1' into intval; -- not OK end $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); plpgsql_check_function -------------------------------------------------------------------------------------- warning:42804:7:assignment:target type is different type than source type Detail: cast "text" value to "integer" type Hint: The input expression type does not have an assignment cast to the target type. Context: at assignment to variable "intval" declared on line 3 warning:42804:8:assignment:target type is different type than source type Detail: cast "text" value to "integer" type Hint: The input expression type does not have an assignment cast to the target type. Context: at assignment to variable "intval" declared on line 3 warning:42804:9:assignment:target type is different type than source type Detail: cast "date" value to "integer" type Hint: There are no possible explicit coercion between those types, possibly bug! Context: at assignment to variable "intval" declared on line 3 warning:42804:12:SQL statement:target type is different type than source type Detail: cast "text" value to "integer" type Hint: The input expression type does not have an assignment cast to the target type. warning:42804:13:SQL statement:target type is different type than source type Detail: cast "text" value to "integer" type Hint: The input expression type does not have an assignment cast to the target type. warning extra:00000:3:DECLARE:never read variable "intval" (19 rows) drop function f1(); create or replace function f1() returns int as $$ begin return 1; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------------- performance:00000:routine is marked as VOLATILE, should be IMMUTABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (2 rows) create or replace function f1() returns int as $$ begin return 1::numeric; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------------- performance:42804:3:RETURN:target type is different type than source type Detail: cast "numeric" value to "integer" type Hint: Hidden casting can be a performance issue. performance:00000:routine is marked as VOLATILE, should be IMMUTABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (5 rows) create or replace function f1() returns int as $$ begin return null; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------------- performance:00000:routine is marked as VOLATILE, should be IMMUTABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (2 rows) create or replace function f1() returns int as $$ begin return current_date; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------------- warning:42804:3:RETURN:target type is different type than source type Detail: cast "date" value to "integer" type Hint: There are no possible explicit coercion between those types, possibly bug! performance:00000:routine is marked as VOLATILE, should be STABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (5 rows) create or replace function f1() returns int as $$ declare a int; begin return a; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------------- performance:00000:routine is marked as VOLATILE, should be IMMUTABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (2 rows) create or replace function f1() returns int as $$ declare a numeric; begin return a; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------------- performance:42804:4:RETURN:target type is different type than source type Detail: cast "numeric" value to "integer" type Hint: Hidden casting can be a performance issue. performance:00000:routine is marked as VOLATILE, should be IMMUTABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (5 rows) drop function f1(); create or replace function f1() returns setof int as $$ begin return next 1; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------------- performance:00000:routine is marked as VOLATILE, should be IMMUTABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (2 rows) create or replace function f1() returns setof int as $$ begin return next 1::numeric; -- tolerant, doesn't use tupmap end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------------- performance:42804:3:RETURN NEXT:target type is different type than source type Detail: cast "numeric" value to "integer" type Hint: Hidden casting can be a performance issue. performance:00000:routine is marked as VOLATILE, should be IMMUTABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (5 rows) drop function f1(); create type t1 as (a int, b int, c int); create type t2 as (a int, b numeric); create or replace function fx() returns t2 as $$ declare x t1; begin return x; end; $$ language plpgsql; select * from plpgsql_check_function('fx()', performance_warnings := true); plpgsql_check_function --------------------------------------------------------------------------------- error:42804:4:RETURN:returned record type does not match expected record type Detail: Returned type integer does not match expected type numeric in column 2. (2 rows) create or replace function fx() returns t2 as $$ declare x t2; begin return x; end; $$ language plpgsql; select * from plpgsql_check_function('fx()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------------- performance:00000:routine is marked as VOLATILE, should be IMMUTABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (2 rows) drop function fx(); create or replace function fx() returns setof t2 as $$ declare x t1; begin return next x; return; end; $$ language plpgsql; select * from plpgsql_check_function('fx()', performance_warnings := true); plpgsql_check_function --------------------------------------------------------------------------------- error:42804:4:RETURN NEXT:wrong record type supplied in RETURN NEXT Detail: Returned type integer does not match expected type numeric in column 2. (2 rows) create or replace function fx() returns setof t2 as $$ declare x t2; begin return next x; return; end; $$ language plpgsql; select * from plpgsql_check_function('fx()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------------- performance:00000:routine is marked as VOLATILE, should be IMMUTABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (2 rows) drop function fx(); create or replace function fx2(_id int, _pa_id varchar(32), _status varchar(60)) returns void as $$ declare begin insert into pa values(_id, _pa_id, _status); exception when OTHERS then raise notice '%', 'some message'; raise exception '%', sqlerrm; end $$ language plpgsql; select * from plpgsql_check_function('fx2(int, varchar, varchar)', performance_warnings := true); plpgsql_check_function ------------------------ (0 rows) create or replace function fx2(_id int, _pa_id varchar(32), _status varchar(60)) returns void as $$ declare begin insert into pa values(_id, _pa_id, _status) returning *; exception when OTHERS then raise notice '%', 'some message'; raise exception '%', sqlerrm; end $$ language plpgsql; select * from plpgsql_check_function('fx2(int, varchar, varchar)', performance_warnings := true); plpgsql_check_function ---------------------------------------------------------------------- error:42601:4:SQL statement:query has no destination for result data (1 row) create or replace function fx2(_id int, _pa_id varchar(32), _status varchar(60)) returns void as $$ declare begin SELECT * FROM pa LIMIT 1; exception when OTHERS then raise notice '%', 'some message'; raise exception '%', sqlerrm; end $$ language plpgsql; select * from plpgsql_check_function('fx2(int, varchar, varchar)', performance_warnings := true); plpgsql_check_function ---------------------------------------------------------------------- error:42601:4:SQL statement:query has no destination for result data (1 row) drop function fx2(int, varchar, varchar); create or replace function foreach_array_loop() returns void as $body$ declare arr text[]; el text; begin arr := array['1111','2222','3333']; foreach el in array arr loop raise notice '%', el; end loop; end; $body$ language 'plpgsql' stable; select * from plpgsql_check_function_tb('foreach_array_loop()', performance_warnings := true); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context --------------------+--------+-----------+----------+--------------------------------------------------+--------+-----------------------------------------------------------------------------------+-------------+----------+-------+--------- foreach_array_loop | | | 00000 | routine is marked as STABLE, should be IMMUTABLE | | When you fix this issue, please, recheck other functions that uses this function. | performance | | | (1 row) create or replace function foreach_array_loop() returns void as $body$ declare arr text[]; el int; begin arr := array['1111','2222','3333']; foreach el in array arr loop raise notice '%', el; end loop; end; $body$ language 'plpgsql' stable; select * from plpgsql_check_function_tb('foreach_array_loop()', performance_warnings := true); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context --------------------+--------+--------------------+----------+--------------------------------------------------+-------------------------------------+-----------------------------------------------------------------------------------+-------------+----------+-------+--------- foreach_array_loop | 7 | FOREACH over array | 42804 | target type is different type than source type | cast "text" value to "integer" type | The input expression type does not have an assignment cast to the target type. | warning | | | foreach_array_loop | | | 00000 | routine is marked as STABLE, should be IMMUTABLE | | When you fix this issue, please, recheck other functions that uses this function. | performance | | | (2 rows) create or replace function foreach_array_loop() returns void as $body$ declare arr date[]; el int; begin arr := array['2014-01-01','2015-01-01','2016-01-01']::date[]; foreach el in array arr loop raise notice '%', el; end loop; end; $body$ language 'plpgsql' stable; select * from plpgsql_check_function_tb('foreach_array_loop()', performance_warnings := true); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context --------------------+--------+--------------------+----------+--------------------------------------------------+-------------------------------------+-----------------------------------------------------------------------------------+-------------+----------+-------+--------- foreach_array_loop | 7 | FOREACH over array | 42804 | target type is different type than source type | cast "date" value to "integer" type | There are no possible explicit coercion between those types, possibly bug! | warning | | | foreach_array_loop | | | 00000 | routine is marked as STABLE, should be IMMUTABLE | | When you fix this issue, please, recheck other functions that uses this function. | performance | | | (2 rows) create or replace function foreach_array_loop() returns void as $body$ declare el text; begin foreach el in array array['1111','2222','3333'] loop raise notice '%', el; end loop; end; $body$ language 'plpgsql' stable; select * from plpgsql_check_function_tb('foreach_array_loop()', performance_warnings := true); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context --------------------+--------+-----------+----------+--------------------------------------------------+--------+-----------------------------------------------------------------------------------+-------------+----------+-------+--------- foreach_array_loop | | | 00000 | routine is marked as STABLE, should be IMMUTABLE | | When you fix this issue, please, recheck other functions that uses this function. | performance | | | (1 row) create or replace function foreach_array_loop() returns void as $body$ declare el int; begin foreach el in array array['1111','2222','3333'] loop raise notice '%', el; end loop; end; $body$ language 'plpgsql' stable; select * from plpgsql_check_function_tb('foreach_array_loop()', performance_warnings := true); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context --------------------+--------+--------------------+----------+--------------------------------------------------+-------------------------------------+-----------------------------------------------------------------------------------+-------------+----------+-------+--------- foreach_array_loop | 5 | FOREACH over array | 42804 | target type is different type than source type | cast "text" value to "integer" type | The input expression type does not have an assignment cast to the target type. | warning | | | foreach_array_loop | | | 00000 | routine is marked as STABLE, should be IMMUTABLE | | When you fix this issue, please, recheck other functions that uses this function. | performance | | | (2 rows) create or replace function foreach_array_loop() returns void as $body$ declare el int; begin foreach el in array array['2014-01-01','2015-01-01','2016-01-01']::date[] loop raise notice '%', el; end loop; end; $body$ language 'plpgsql' stable; select * from plpgsql_check_function_tb('foreach_array_loop()', performance_warnings := true); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context --------------------+--------+--------------------+----------+--------------------------------------------------+-------------------------------------+-----------------------------------------------------------------------------------+-------------+----------+-------+--------- foreach_array_loop | 5 | FOREACH over array | 42804 | target type is different type than source type | cast "date" value to "integer" type | There are no possible explicit coercion between those types, possibly bug! | warning | | | foreach_array_loop | | | 00000 | routine is marked as STABLE, should be IMMUTABLE | | When you fix this issue, please, recheck other functions that uses this function. | performance | | | (2 rows) drop function foreach_array_loop(); create or replace function scan_rows(int[]) returns void AS $$ declare x int[]; begin foreach x slice 1 in array $1 loop raise notice 'row = %', x; end loop; end; $$ language plpgsql; select * from plpgsql_check_function_tb('scan_rows(int[])', performance_warnings := true); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+----------------------------------------------------+--------+-----------------------------------------------------------------------------------+-------------+----------+-------+--------- scan_rows | | | 00000 | routine is marked as VOLATILE, should be IMMUTABLE | | When you fix this issue, please, recheck other functions that uses this function. | performance | | | (1 row) create or replace function scan_rows(int[]) returns void AS $$ declare x int[]; begin foreach x in array $1 loop raise notice 'row = %', x; end loop; end; $$ language plpgsql; select * from plpgsql_check_function_tb('scan_rows(int[])', performance_warnings := true); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+--------------------+----------+----------------------------------------------------+------------------------------------------+-----------------------------------------------------------------------------------+-------------+----------+-------+--------- scan_rows | 5 | FOREACH over array | 42804 | target type is different type than source type | cast "integer" value to "integer[]" type | There are no possible explicit coercion between those types, possibly bug! | warning | | | scan_rows | | | 00000 | routine is marked as VOLATILE, should be IMMUTABLE | | When you fix this issue, please, recheck other functions that uses this function. | performance | | | (2 rows) drop function scan_rows(int[]); drop function fx(); ERROR: function fx() does not exist drop type t1; drop type t2; create table t1(a int, b int); create table t2(a int, b int, c int); create table t3(a numeric, b int); insert into t1 values(10,20),(30,40); create or replace function fx() returns int as $$ declare s int default 0; r t1; begin foreach r in array (select array_agg(t1) from t1) loop s := r.a + r.b; end loop; return s; end; $$ language plpgsql; select * from plpgsql_check_function_tb('fx()', performance_warnings := true); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+-------------------------------------------------+--------+-----------------------------------------------------------------------------------+-------------+----------+-------+--------- fx | | | 00000 | routine is marked as VOLATILE, should be STABLE | | When you fix this issue, please, recheck other functions that uses this function. | performance | | | (1 row) create or replace function fx() returns int as $$ declare s int default 0; r t1; c t1[]; begin c := (select array_agg(t1) from t1); foreach r in array c loop s := r.a + r.b; end loop; return s; end; $$ language plpgsql; select * from plpgsql_check_function_tb('fx()', performance_warnings := true); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+-------------------------------------------------+--------+-----------------------------------------------------------------------------------+-------------+----------+-------+--------- fx | | | 00000 | routine is marked as VOLATILE, should be STABLE | | When you fix this issue, please, recheck other functions that uses this function. | performance | | | (1 row) create or replace function fx() returns int as $$ declare s int default 0; r t1; c t1[]; begin select array_agg(t1) into c from t1; foreach r in array c loop s := r.a + r.b; end loop; return s; end; $$ language plpgsql; select * from plpgsql_check_function_tb('fx()', performance_warnings := true); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+-------------------------------------------------+--------+-----------------------------------------------------------------------------------+-------------+----------+-------+--------- fx | | | 00000 | routine is marked as VOLATILE, should be STABLE | | When you fix this issue, please, recheck other functions that uses this function. | performance | | | (1 row) create or replace function fx() returns int as $$ declare s int default 0; r t1; c t1[]; begin select array_agg(t1) into c from t1; for i in array_lower(c, 1) .. array_upper(c, 1) loop r := c[i]; s := r.a + r.b; end loop; return s; end; $$ language plpgsql; select * from plpgsql_check_function_tb('fx()', performance_warnings := true); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+-------------------------------------------------+--------+-----------------------------------------------------------------------------------+-------------+----------+-------+--------- fx | | | 00000 | routine is marked as VOLATILE, should be STABLE | | When you fix this issue, please, recheck other functions that uses this function. | performance | | | (1 row) create or replace function fx() returns int as $$ declare s int default 0; c t1[]; begin select array_agg(t1) into c from t1; for i in array_lower(c, 1) .. array_upper(c, 1) loop s := (c[i]).a + (c[i]).b; end loop; return s; end; $$ language plpgsql; select * from plpgsql_check_function_tb('fx()', performance_warnings := true); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+-------------------------------------------------+--------+-----------------------------------------------------------------------------------+-------------+----------+-------+--------- fx | | | 00000 | routine is marked as VOLATILE, should be STABLE | | When you fix this issue, please, recheck other functions that uses this function. | performance | | | (1 row) create or replace function fx() returns int as $$ declare s int default 0; r record; c t1[]; begin select array_agg(t1) into c from t1; for i in array_lower(c, 1) .. array_upper(c, 1) loop r := c[i]; s := r.a + r.b; end loop; return s; end; $$ language plpgsql; select * from plpgsql_check_function_tb('fx()', performance_warnings := true); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+-------------------------------------------------+--------+-----------------------------------------------------------------------------------+-------------+----------+-------+--------- fx | | | 00000 | routine is marked as VOLATILE, should be STABLE | | When you fix this issue, please, recheck other functions that uses this function. | performance | | | (1 row) create or replace function fx() returns int as $$ declare s int default 0; r record; c t1[]; begin select array_agg(t1) into c from t1; for i in array_lower(c, 1) .. array_upper(c, 1) loop r := c[i]; s := r.a + r.b + r.c; end loop; return s; end; $$ language plpgsql; select * from plpgsql_check_function_tb('fx()', performance_warnings := true); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+------------+----------+-----------------------------+--------+------+-------+----------+-------+---------------------------------------- fx | 11 | assignment | 42703 | record "r" has no field "c" | | | error | | | SQL statement "SELECT r.a + r.b + r.c" (1 row) create or replace function fx() returns int as $$ declare s int default 0; r t2; begin foreach r in array (select array_agg(t1) from t1) loop s := r.a + r.b; end loop; return s; end; $$ language plpgsql; select * from plpgsql_check_function_tb('fx()', performance_warnings := true); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+--------------------+----------+-------------------------------------------------+--------+-----------------------------------------------------------------------------------+-------------+----------+-------+--------- fx | 6 | FOREACH over array | 00000 | too few attributes for composite variable | | | warning | | | fx | | | 00000 | routine is marked as VOLATILE, should be STABLE | | When you fix this issue, please, recheck other functions that uses this function. | performance | | | (2 rows) create or replace function fx() returns int as $$ declare s int default 0; r t3; begin foreach r in array (select array_agg(t1) from t1) loop s := r.a + r.b; end loop; return s; end; $$ language plpgsql; select * from plpgsql_check_function_tb('fx()', performance_warnings := true); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+--------------------+----------+-------------------------------------------------+----------------------------------------+-----------------------------------------------------------------------------------+-------------+----------+-------+-------------------------------------------------- fx | 6 | FOREACH over array | 42804 | target type is different type than source type | cast "integer" value to "numeric" type | Hidden casting can be a performance issue. | performance | | | fx | 8 | assignment | 42804 | target type is different type than source type | cast "numeric" value to "integer" type | Hidden casting can be a performance issue. | performance | | | at assignment to variable "s" declared on line 3 fx | | | 00000 | routine is marked as VOLATILE, should be STABLE | | When you fix this issue, please, recheck other functions that uses this function. | performance | | | (3 rows) drop function fx(); drop table t1; -- mscottie issue #13 create table test ( a text, b integer, c uuid ); create function before_insert_test() returns trigger language plpgsql as $$ begin select a into NEW.a from test where b = 1; select b into NEW.b from test where b = 1; select null::uuid into NEW.c from test where b = 1; return new; end; $$; select * from plpgsql_check_function_tb('before_insert_test()','test'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+---------+--------+------+-------+----------+-------+--------- (0 rows) create or replace function before_insert_test() returns trigger language plpgsql as $$ begin NEW.a := (select a from test where b = 1); NEW.b := (select b from test where b = 1); NEW.c := (select c from test where b = 1); return new; end; $$; select * from plpgsql_check_function_tb('before_insert_test()','test', fatal_errors := false); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+---------+--------+------+-------+----------+-------+--------- (0 rows) create or replace function before_insert_test() returns trigger language plpgsql as $$ begin NEW.a := 'Hello'::text; NEW.b := 10; NEW.c := null::uuid; return new; end; $$; select * from plpgsql_check_function_tb('before_insert_test()','test', fatal_errors := false); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+---------+--------+------+-------+----------+-------+--------- (0 rows) drop function before_insert_test(); create or replace function fx() returns void as $$ declare NEW test; OLD test; begin select null::uuid into NEW.c from test where b = 1; end; $$ language plpgsql; select * from plpgsql_check_function_tb('fx()', performance_warnings := true, fatal_errors := false); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+---------------------------+--------+------+---------------+----------+-------+--------- fx | 2 | DECLARE | 00000 | unused variable "old" | | | warning | | | fx | 2 | DECLARE | 00000 | never read variable "new" | | | warning extra | | | (2 rows) drop function fx(); create or replace function fx() returns void as $$ declare NEW test; begin NEW.a := 'Hello'::text; NEW.b := 10; NEW.c := null::uuid; end; $$ language plpgsql; select * from plpgsql_check_function_tb('fx()', performance_warnings := true, fatal_errors := false); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+----------------------------------------------------+--------+-----------------------------------------------------------------------------------+---------------+----------+-------+--------- fx | 2 | DECLARE | 00000 | never read variable "new" | | | warning extra | | | fx | | | 00000 | routine is marked as VOLATILE, should be IMMUTABLE | | When you fix this issue, please, recheck other functions that uses this function. | performance | | | (2 rows) drop function fx(); drop table test; create or replace function fx() returns void as $$ declare s int; sa int[]; sd date; bs int[]; begin sa[10] := s; sa[10] := sd; s := bs[10]; end; $$ language plpgsql; select * from plpgsql_check_function_tb('fx()', performance_warnings := true, fatal_errors := false); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+------------+----------+----------------------------------------------------+-------------------------------------+-----------------------------------------------------------------------------------+---------------+----------+-------+-------------------------------------------------------------- fx | 9 | assignment | 42804 | target type is different type than source type | cast "date" value to "integer" type | There are no possible explicit coercion between those types, possibly bug! | warning | | | at assignment to element of variable "sa" declared on line 4 fx | 4 | DECLARE | 00000 | never read variable "sa" | | | warning extra | | | fx | | | 00000 | routine is marked as VOLATILE, should be IMMUTABLE | | When you fix this issue, please, recheck other functions that uses this function. | performance | | | (3 rows) drop function fx(); create type t as (t text); create or replace function fx() returns void as $$ declare _t t; _tt t[]; _txt text; begin _t.t := 'ABC'; -- correct warning "unknown" _tt[1] := _t; _txt := _t; end; $$ language plpgsql; select * from plpgsql_check_function('fx()', performance_warnings := true); plpgsql_check_function --------------------------------------------------------------------------------------------------- error:42804:7:assignment:cannot cast composite value of "t" type to a scalar value of "text" type Context: at assignment to variable "_txt" declared on line 3 (2 rows) drop function fx(); create or replace function fx() returns void as $$ declare _t1 t; _t2 t; begin _t1.t := 'ABC'::text; _t2 := _t1; raise notice '% %', _t2, _t2.t; end; $$ language plpgsql; select * from plpgsql_check_function('fx()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------------- performance:00000:routine is marked as VOLATILE, should be IMMUTABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (2 rows) drop function fx(); create or replace function fx(out _tt t[]) as $$ declare _t t; begin _t.t := 'ABC'::text; _tt[1] := _t; end; $$ language plpgsql; select * from plpgsql_check_function('fx()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------------- performance:00000:routine is marked as VOLATILE, should be IMMUTABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (2 rows) drop function fx(); drop type t; create or replace function fx() returns int as $$ declare x int; begin perform 1; return 10; end; $$ language plpgsql; select * from plpgsql_check_function('fx()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------------- warning:00000:2:DECLARE:unused variable "x" performance:00000:routine is marked as VOLATILE, should be IMMUTABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (3 rows) drop function fx(); create table t(i int); create function test_t(OUT t) returns t AS $$ begin $1 := null; end; $$ language plpgsql; select test_t(); test_t -------- (1 row) select * from test_t(); i --- (1 row) select * from plpgsql_check_function('test_t()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------------- performance:00000:routine is marked as VOLATILE, should be IMMUTABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (2 rows) create or replace function fx() returns void as $$ declare c cursor for select * from t; x varchar; begin open c; fetch c into x; close c; end; $$ language plpgsql; select test_t(); test_t -------- (1 row) select * from test_t(); i --- (1 row) select * from plpgsql_check_function('fx()', performance_warnings := true, fatal_errors := false); plpgsql_check_function -------------------------------------------------------------------------- performance:42804:7:FETCH:target type is different type than source type Detail: cast "integer" value to "character varying" type Hint: Hidden casting can be a performance issue. warning extra:00000:4:DECLARE:never read variable "x" (4 rows) drop function fx(); create or replace function fx() returns void as $$ declare c cursor for select * from t; x int; begin open c; fetch c into x; close c; end; $$ language plpgsql; select test_t(); test_t -------- (1 row) select * from test_t(); i --- (1 row) select * from plpgsql_check_function('fx()', performance_warnings := true, fatal_errors := false); plpgsql_check_function ------------------------------------------------------- warning extra:00000:4:DECLARE:never read variable "x" (1 row) drop function fx(); create or replace function fx() returns void as $$ declare c cursor for select * from t; begin for r in c loop raise notice '%', r.a; end loop; end; $$ language plpgsql; select test_t(); test_t -------- (1 row) select * from test_t(); i --- (1 row) select * from plpgsql_check_function('fx()', performance_warnings := true, fatal_errors := false); plpgsql_check_function ------------------------------------------------------- error:42703:6:RAISE:record "r" has no field "a" Context: SQL statement "SELECT r.a" warning extra:00000:5:DECLARE:never read variable "r" (3 rows) drop function fx(); create or replace function fx() returns void as $$ declare c cursor for select * from t; begin for r in c loop raise notice '%', r.i; end loop; end; $$ language plpgsql; select test_t(); test_t -------- (1 row) select * from test_t(); i --- (1 row) select * from plpgsql_check_function('fx()', performance_warnings := true, fatal_errors := false); plpgsql_check_function ------------------------ (0 rows) drop function fx(); create table foo(a int, b int); create or replace function fx() returns void as $$ declare f1 int; f2 int; begin select 1, 2 into f1; select 1 into f1, f2; select a b into f1, f2 from foo; end; $$ language plpgsql; select fx(); fx ---- (1 row) select * from plpgsql_check_function('fx()', performance_warnings := true, fatal_errors := false); plpgsql_check_function ------------------------------------------------------------------------ warning:00000:4:SQL statement:too many attributes for target variables Detail: There are less target variables than output columns in query. Hint: Check target variables in SELECT INTO statement warning:00000:5:SQL statement:too few attributes for target variables Detail: There are more target variables than output columns in query. Hint: Check target variables in SELECT INTO statement. warning:00000:6:SQL statement:too few attributes for target variables Detail: There are more target variables than output columns in query. Hint: Check target variables in SELECT INTO statement. warning extra:00000:2:DECLARE:never read variable "f1" warning extra:00000:2:DECLARE:never read variable "f2" (11 rows) drop function fx(); drop table foo; create or replace function fx() returns void as $$ declare d date; begin d := (select 1 from pg_class limit 1); raise notice '%', d; end; $$ language plpgsql; select fx(); ERROR: invalid input syntax for type date: "1" CONTEXT: PL/pgSQL function fx() line 4 at assignment select * from plpgsql_check_function('fx()', performance_warnings := true, fatal_errors := false); plpgsql_check_function ---------------------------------------------------------------------------------- warning:42804:4:assignment:target type is different type than source type Detail: cast "integer" value to "date" type Hint: There are no possible explicit coercion between those types, possibly bug! Context: at assignment to variable "d" declared on line 2 (4 rows) drop function fx(); create table tab_1(i int); create or replace function fx(a int) returns setof int as $$ declare c refcursor; r record; begin open c for select i from tab_1 where i = a; loop fetch c into r; if not found then exit; end if; return next r.i; end loop; end; $$ language plpgsql; select * from plpgsql_check_function('fx(int)', performance_warnings := true, fatal_errors := false); plpgsql_check_function ----------------------------------------------------------------------------------------- performance:00000:routine is marked as VOLATILE, should be STABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (2 rows) create or replace function fx(a int) returns setof int as $$ declare c refcursor; r record; begin open c for select i from tab_1 where i = a; loop fetch c into r; if not found then exit; end if; return next r.x; end loop; end; $$ language plpgsql; select * from plpgsql_check_function('fx(int)', performance_warnings := true, fatal_errors := false); plpgsql_check_function ----------------------------------------------------------------------------------------- error:42703:12:RETURN NEXT:record "r" has no field "x" Context: SQL statement "SELECT r.x" warning extra:00000:4:DECLARE:never read variable "r" performance:00000:routine is marked as VOLATILE, should be STABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (5 rows) drop function fx(int); drop table tab_1; create or replace function fxx() returns void as $$ begin rollback; end; $$ language plpgsql; select fxx(); ERROR: invalid transaction termination CONTEXT: PL/pgSQL function fxx() line 3 at ROLLBACK select * from plpgsql_check_function('fxx()'); plpgsql_check_function -------------------------------------------------------- error:2D000:3:ROLLBACK:invalid transaction termination (1 row) drop function fxx(); create or replace function fxx() returns void as $$ declare x int; begin declare x int; begin end; end; $$ language plpgsql; select * from plpgsql_check_function('fxx()'); plpgsql_check_function ------------------------------------------------------------------------------------------ warning extra:00000:5:statement block:variable "x" shadows a previously defined variable Hint: SET plpgsql.extra_warnings TO 'shadowed_variables' warning:00000:2:DECLARE:unused variable "x" warning:00000:4:DECLARE:unused variable "x" (4 rows) select * from plpgsql_check_function('fxx()', extra_warnings := false); plpgsql_check_function --------------------------------------------- warning:00000:2:DECLARE:unused variable "x" warning:00000:4:DECLARE:unused variable "x" (2 rows) drop function fxx(); create or replace function fxx(in a int, in b int, out c int, out d int) as $$ begin c := a; end; $$ language plpgsql; select * from plpgsql_check_function('fxx(int, int)'); plpgsql_check_function ------------------------------------------------- warning extra:00000:unused parameter "b" warning extra:00000:unmodified OUT variable "d" (2 rows) create or replace function fxx(in a int, in b int, out c int, out d int) as $$ begin c := d; end; $$ language plpgsql; select * from plpgsql_check_function('fxx(int, int)'); plpgsql_check_function ------------------------------------------------- warning extra:00000:unused parameter "a" warning extra:00000:unused parameter "b" warning extra:00000:unmodified OUT variable "d" (3 rows) create type ct as (a int, b int); create or replace function fxx(a ct, b ct, OUT c ct, OUT d ct) as $$ begin c.a := a.a; end; $$ language plpgsql; select * from plpgsql_check_function('fxx(ct, ct)'); plpgsql_check_function ----------------------------------------------------------------------- warning extra:00000:unused parameter "b" warning extra:00000:composite OUT variable "c" is not single argument warning extra:00000:composite OUT variable "d" is not single argument warning extra:00000:unmodified OUT variable "d" (4 rows) create or replace function fxx(a ct, b ct, OUT c ct, OUT d ct) as $$ begin c.a := d.a; end; $$ language plpgsql; select * from plpgsql_check_function('fxx(ct, ct)'); plpgsql_check_function ----------------------------------------------------------------------- warning extra:00000:unused parameter "a" warning extra:00000:unused parameter "b" warning extra:00000:composite OUT variable "c" is not single argument warning extra:00000:composite OUT variable "d" is not single argument warning extra:00000:unmodified OUT variable "d" (5 rows) create or replace function tx(a int) returns int as $$ declare a int; ax int; begin declare ax int; begin ax := 10; end; a := 10; return 20; end; $$ language plpgsql; select * from plpgsql_check_function('tx(int)'); plpgsql_check_function ------------------------------------------------------------------------------------------- warning:00000:3:statement block:parameter "a" is shadowed Detail: Local variable shadows function parameter. warning extra:00000:5:statement block:variable "ax" shadows a previously defined variable Hint: SET plpgsql.extra_warnings TO 'shadowed_variables' warning:00000:2:DECLARE:unused variable "ax" warning extra:00000:2:DECLARE:never read variable "a" warning extra:00000:4:DECLARE:never read variable "ax" warning extra:00000:unused parameter "a" (8 rows) create type xt as (a int, b int, c int); create or replace function fx_xt(out x xt) as $$ declare l xt; a int; begin return; end; $$ language plpgsql; select * from plpgsql_check_function('fx_xt()'); plpgsql_check_function ------------------------------------------------- warning:00000:2:DECLARE:unused variable "l" warning:00000:3:DECLARE:unused variable "a" warning extra:00000:unmodified OUT variable "x" (3 rows) drop function fx_xt(); create or replace function fx_xt(out x xt) as $$ declare l xt; a int; begin x.c := 1000; return; end; $$ language plpgsql; select * from plpgsql_check_function('fx_xt()'); plpgsql_check_function --------------------------------------------- warning:00000:2:DECLARE:unused variable "l" warning:00000:3:DECLARE:unused variable "a" (2 rows) drop function fx_xt(); create or replace function fx_xt(out x xt, out y xt) as $$ declare c1 xt; c2 xt; begin return; end; $$ language plpgsql; select * from plpgsql_check_function('fx_xt()'); plpgsql_check_function ----------------------------------------------------------------------- warning:00000:2:DECLARE:unused variable "c1" warning:00000:2:DECLARE:unused variable "c2" warning extra:00000:composite OUT variable "x" is not single argument warning extra:00000:unmodified OUT variable "x" warning extra:00000:composite OUT variable "y" is not single argument warning extra:00000:unmodified OUT variable "y" (6 rows) drop function fx_xt(); create or replace function fx_xt(out x xt, out y xt) as $$ declare c1 xt; c2 xt; begin x.a := 100; y := row(10,20,30); return; end; $$ language plpgsql; select * from plpgsql_check_function('fx_xt()'); plpgsql_check_function ----------------------------------------------------------------------- warning:00000:2:DECLARE:unused variable "c1" warning:00000:2:DECLARE:unused variable "c2" warning extra:00000:composite OUT variable "x" is not single argument warning extra:00000:composite OUT variable "y" is not single argument (4 rows) drop function fx_xt(); create or replace function fx_xt(out x xt, out z int) as $$ begin return; end; $$ language plpgsql; select * from plpgsql_check_function('fx_xt()'); plpgsql_check_function ----------------------------------------------------------------------- warning extra:00000:composite OUT variable "x" is not single argument warning extra:00000:unmodified OUT variable "x" warning extra:00000:unmodified OUT variable "z" (3 rows) drop function fx_xt(); drop type xt; -- missing RETURN create or replace function fx_flow() returns int as $$ begin raise notice 'kuku'; end; $$ language plpgsql; select fx_flow(); NOTICE: kuku ERROR: control reached end of function without RETURN CONTEXT: PL/pgSQL function fx_flow() select * from plpgsql_check_function('fx_flow()'); plpgsql_check_function ------------------------------------------------------------ error:2F005:control reached end of function without RETURN (1 row) -- ok create or replace function fx_flow() returns int as $$ declare a int; begin if a > 10 then return a; end if; return 10; end; $$ language plpgsql; select * from plpgsql_check_function('fx_flow()'); plpgsql_check_function ------------------------ (0 rows) -- dead code create or replace function fx_flow() returns int as $$ declare a int; begin if a > 10 then return a; else return a + 1; end if; return 10; end; $$ language plpgsql; select * from plpgsql_check_function('fx_flow()'); plpgsql_check_function ----------------------------------------------- warning extra:00000:9:RETURN:unreachable code (1 row) -- missing return create or replace function fx_flow() returns int as $$ declare a int; begin if a > 10 then return a; end if; end; $$ language plpgsql; select * from plpgsql_check_function('fx_flow()'); plpgsql_check_function -------------------------------------------------------------------- warning extra:2F005:control reached end of function without RETURN (1 row) drop function fx_flow(); create or replace function fx_flow(in p_param1 integer) returns text as $$ declare z1 text; begin if p_param1 is not null then z1 := '1111'; return z1; else z1 := '222222'; end if; return z1; end; $$ language plpgsql stable; select * from plpgsql_check_function_tb('fx_flow(integer)'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+---------+--------+------+-------+----------+-------+--------- (0 rows) create or replace function fx_flow(in p_param1 integer) returns text as $$ declare z1 text; begin if p_param1 is not null then z1 := '1111'; return z1; else z1 := '222222'; raise exception 'stop'; end if; return z1; end; $$ language plpgsql stable; select * from plpgsql_check_function_tb('fx_flow(integer)'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+------------------+--------+------+---------------+----------+-------+--------- fx_flow | 12 | RETURN | 00000 | unreachable code | | | warning extra | | | (1 row) drop function fx_flow(); ERROR: function fx_flow() does not exist drop function fx(int); ERROR: function fx(integer) does not exist create or replace function fx(x int) returns table(y int) as $$ begin return query select x union select x; end $$ language plpgsql; select * from fx(10); y ---- 10 (1 row) select * from plpgsql_check_function_tb('fx(int)'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+---------+--------+------+-------+----------+-------+--------- (0 rows) drop function fx(int); create or replace function fx(x int) returns table(y int, z int) as $$ begin return query select x,x+1 union select x, x+1; end $$ language plpgsql; select * from fx(10); y | z ----+---- 10 | 11 (1 row) select * from plpgsql_check_function_tb('fx(int)'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+---------+--------+------+-------+----------+-------+--------- (0 rows) drop function fx(int); create table xx(a int); create or replace function fx(x int) returns int as $$ declare _a int; begin begin select a from xx into strict _a where a = x; return _a; exception when others then null; end; return -1; end; $$ language plpgsql; select * from plpgsql_check_function_tb('fx(int)'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+---------+--------+------+-------+----------+-------+--------- (0 rows) drop table xx; create or replace function fx(x int) returns int as $$ begin begin if (x > 0) then raise exception 'xxx' using errcode = 'XX888'; else raise exception 'yyy' using errcode = 'YY888'; end if; return -1; -- dead code; end; return -1; end; $$ language plpgsql; select * from plpgsql_check_function_tb('fx(int)'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+------------------+--------+------+---------------+----------+-------+--------- fx | 9 | RETURN | 00000 | unreachable code | | | warning extra | | | fx | 11 | RETURN | 00000 | unreachable code | | | warning extra | | | (2 rows) create or replace function fx(x int) returns int as $$ begin begin if (x > 0) then raise exception 'xxx' using errcode = 'XX888'; else raise exception 'yyy' using errcode = 'YY888'; end if; exception when sqlstate 'XX888' then null; when sqlstate 'YY888' then null; end; end; -- missing return; $$ language plpgsql; select * from plpgsql_check_function_tb('fx(int)'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+------------------------------------------------+--------+------+-------+----------+-------+--------- fx | | | 2F005 | control reached end of function without RETURN | | | error | | | (1 row) create or replace function fx(x int) returns int as $$ begin begin if (x > 0) then raise exception 'xxx' using errcode = 'XX888'; else raise exception 'yyy' using errcode = 'YY888'; end if; exception when others then return 10; end; end; -- ok now $$ language plpgsql; select * from plpgsql_check_function_tb('fx(int)'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+---------+--------+------+-------+----------+-------+--------- (0 rows) --false alarm reported by Filip Zach create type testtype as (id integer); create or replace function fx() returns testtype as $$ begin return row(1); end; $$ language plpgsql; select * from fx(); id ---- 1 (1 row) select fx(); fx ----- (1) (1 row) select * from plpgsql_check_function('fx()'); plpgsql_check_function ------------------------ (0 rows) drop function fx(); create function out1(OUT f1 int, OUT f2 int) returns setof record as $$ begin for f1, f2 in execute $q$ select 1, 2 $q$ loop return next; end loop; end $$ language plpgsql; select * from plpgsql_check_function('out1()'); plpgsql_check_function ------------------------ (0 rows) drop function out1(); create function out1(OUT f1 int, OUT f2 int) returns setof record as $$ begin for f1, f2 in select 1, 2 loop return next; end loop; end $$ language plpgsql; select * from plpgsql_check_function('out1()'); plpgsql_check_function ------------------------ (0 rows) drop function out1(); -- never read variable detection create function a() returns int as $$ declare foo int; begin foo := 2; return 1; end; $$ language plpgsql; select * from plpgsql_check_function('a()'); plpgsql_check_function --------------------------------------------------------- warning extra:00000:2:DECLARE:never read variable "foo" (1 row) drop function a(); -- issue #29 false unused variable create or replace function f1(in p_cursor refcursor) returns void as $body$ declare z_offset integer; begin z_offset := 10; move absolute z_offset from p_cursor; end; $body$ language 'plpgsql' stable; select * from plpgsql_check_function_tb('f1(refcursor)'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+---------+--------+------+-------+----------+-------+--------- (0 rows) drop function f1(refcursor); -- issue #30 segfault due NULL refname create or replace function test(a varchar) returns void as $$ declare x cursor (_a varchar) for select _a; begin open x(a); end; $$ language plpgsql; select * from plpgsql_check_function_tb('test(varchar)'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+-------------------------+--------+------+---------------+----------+-------+--------- test | 2 | DECLARE | 00000 | never read variable "x" | | | warning extra | | | (1 row) drop function test(varchar); create or replace function test() returns void as $$ declare x numeric; begin x := NULL; end; $$ language plpgsql; select * from plpgsql_check_function('test()'); plpgsql_check_function ------------------------------------------------------- warning extra:00000:2:DECLARE:never read variable "x" (1 row) drop function test(); create table testtable(a int); create or replace function test() returns int as $$ declare r testtable; begin select * into r from testtable; return r.a; end; $$ language plpgsql; select * from plpgsql_check_function('test()'); plpgsql_check_function ------------------------ (0 rows) set check_function_bodies to on; drop table testtable; create table testtable(a int, b int); create or replace function test() returns int as $$ declare r testtable; begin select * into r from testtable; return r.a; end; $$ language plpgsql; alter table testtable drop column b; -- expected false alarm on PostgreSQL 10 and older -- there is not possibility to enforce recompilation -- before checking. select * from plpgsql_check_function('test()'); plpgsql_check_function ------------------------ (0 rows) drop function test(); -- issue #32 create table bigtable(id bigint, v varchar); create or replace function test() returns void as $$ declare r record; _id numeric; begin select * into r from bigtable where id = _id; for r in select * from bigtable where _id = id loop end loop; if (exists(select * from bigtable where id = _id)) then end if; end; $$ language plpgsql; select test(); test ------ (1 row) -- should to show performance warnings select * from plpgsql_check_function('test()', performance_warnings := true); plpgsql_check_function ------------------------------------------------------------------------------------------------------------------------------- performance:42804:6:SQL statement:implicit cast of attribute caused by different PLpgSQL variable type in WHERE clause Query: select * from bigtable where id = _id -- ^ Detail: An index of some attribute cannot be used, when variable, used in predicate, has not right type like a attribute Hint: Check a variable type - int versus numeric performance:42804:7:FOR over SELECT rows:implicit cast of attribute caused by different PLpgSQL variable type in WHERE clause Query: select * from bigtable where _id = id -- ^ Detail: An index of some attribute cannot be used, when variable, used in predicate, has not right type like a attribute Hint: Check a variable type - int versus numeric performance:42804:10:IF:implicit cast of attribute caused by different PLpgSQL variable type in WHERE clause Query: SELECT (exists(select * from bigtable where id = _id)) -- ^ Detail: An index of some attribute cannot be used, when variable, used in predicate, has not right type like a attribute Hint: Check a variable type - int versus numeric warning extra:00000:3:DECLARE:never read variable "r" (16 rows) create or replace function test() returns void as $$ declare r record; _id bigint; begin select * into r from bigtable where id = _id; for r in select * from bigtable where _id = id loop end loop; if (exists(select * from bigtable where id = _id)) then end if; end; $$ language plpgsql; -- there are not any performance issue now select * from plpgsql_check_function('test()', performance_warnings := true); plpgsql_check_function ------------------------------------------------------- warning extra:00000:3:DECLARE:never read variable "r" (1 row) -- nextval, currval and setval test create table test_table(); create or replace function testseq() returns void as $$ begin perform nextval('test_table'); perform currval('test_table'); perform setval('test_table', 10); perform setval('test_table', 10, true); end; $$ language plpgsql; -- should to fail select testseq(); ERROR: "test_table" is not a sequence CONTEXT: SQL statement "SELECT nextval('test_table')" PL/pgSQL function testseq() line 3 at PERFORM select * from plpgsql_check_function('testseq()', fatal_errors := false); plpgsql_check_function ------------------------------------------------------ error:42809:3:PERFORM:"test_table" is not a sequence Query: SELECT nextval('test_table') -- ^ error:42809:4:PERFORM:"test_table" is not a sequence Query: SELECT currval('test_table') -- ^ error:42809:5:PERFORM:"test_table" is not a sequence Query: SELECT setval('test_table', 10) -- ^ error:42809:6:PERFORM:"test_table" is not a sequence Query: SELECT setval('test_table', 10, true) -- ^ (12 rows) drop function testseq(); drop table test_table; -- tests designed for PostgreSQL 9.2 set check_function_bodies to off; create table t1(a int, b int); create function f1() returns void as $$ begin if false then update t1 set c = 30; end if; if false then raise notice '%', r.c; end if; end; $$ language plpgsql; select f1(); f1 ---- (1 row) select * from plpgsql_check_function_tb('f1()', fatal_errors := true); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+---------------+----------+--------------------------------------------+--------+------+-------+----------+----------------------+--------- f1 | 4 | SQL statement | 42703 | column "c" of relation "t1" does not exist | | | error | 15 | update t1 set c = 30 | (1 row) select * from plpgsql_check_function_tb('f1()', fatal_errors := false); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+---------------+----------+--------------------------------------------+--------+------+-------+----------+----------------------+--------- f1 | 4 | SQL statement | 42703 | column "c" of relation "t1" does not exist | | | error | 15 | update t1 set c = 30 | f1 | 7 | RAISE | 42P01 | missing FROM-clause entry for table "r" | | | error | 8 | SELECT r.c | (2 rows) select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+---------------+----------+--------------------------------------------+--------+------+-------+----------+----------------------+--------- f1 | 4 | SQL statement | 42703 | column "c" of relation "t1" does not exist | | | error | 15 | update t1 set c = 30 | (1 row) select f1(); f1 ---- (1 row) drop function f1(); create or replace function f1() returns void as $$ begin if false then raise notice '%', 1, 2; end if; end; $$ language plpgsql; select f1(); ERROR: too many parameters specified for RAISE CONTEXT: compilation of PL/pgSQL function "f1" near line 4 select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+-----------------------------------------+--------+------+-------+----------+-------+--------------------------------------------------- f1 | | | 42601 | too many parameters specified for RAISE | | | error | | | compilation of PL/pgSQL function "f1" near line 4 (1 row) select f1(); ERROR: too many parameters specified for RAISE CONTEXT: compilation of PL/pgSQL function "f1" near line 4 drop function f1(); create or replace function f1() returns void as $$ begin if false then raise notice '% %'; end if; end; $$ language plpgsql; select f1(); ERROR: too few parameters specified for RAISE CONTEXT: compilation of PL/pgSQL function "f1" near line 4 select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+----------------------------------------+--------+------+-------+----------+-------+--------------------------------------------------- f1 | | | 42601 | too few parameters specified for RAISE | | | error | | | compilation of PL/pgSQL function "f1" near line 4 (1 row) select f1(); ERROR: too few parameters specified for RAISE CONTEXT: compilation of PL/pgSQL function "f1" near line 4 drop function f1(); create or replace function ml_trg() returns trigger as $$ #option dump declare begin if TG_OP = 'INSERT' then if NEW.status_from IS NULL then begin -- performance issue only select status into NEW.status_from from pa where pa_id = NEW.pa_id; -- nonexist target value select status into NEW.status_from_xxx from pa where pa_id = NEW.pa_id; exception when DATA_EXCEPTION then new.status_from := 'DE'; end; end if; end if; if TG_OP = 'DELETE' then return OLD; else return NEW; end if; exception when OTHERS then NULL; if TG_OP = 'DELETE' then return OLD; else return NEW; end if; end; $$ language plpgsql; select * from plpgsql_check_function('ml_trg()', 'ml', performance_warnings := true); plpgsql_check_function -------------------------------------------------------------------------- error:42703:13:SQL statement:record "new" has no field "status_from_xxx" (1 row) create or replace function fx2() returns void as $$ declare _pa pa; begin select pa.id into _pa.id from pa limit 1; select pa.pa_id into _pa.pa_id from pa limit 1; end; $$ language plpgsql; select * from plpgsql_check_function('fx2()', performance_warnings := true); plpgsql_check_function --------------------------------------------------------- warning extra:00000:2:DECLARE:never read variable "_pa" (1 row) drop function fx2(); create or replace function fx2() returns void as $$ declare _pa pa; begin _pa.id := (select pa.id from pa limit 1); _pa.pa_id := (select pa.pa_id from pa limit 1); end; $$ language plpgsql; select * from plpgsql_check_function('fx2()', performance_warnings := true); plpgsql_check_function --------------------------------------------------------- warning extra:00000:2:DECLARE:never read variable "_pa" (1 row) drop function fx2(); create or replace function test_lab() returns void as $$ begin <> for a in 1..3 loop <> BEGIN <> for b in 8..9 loop if a=2 then continue sub; end if; raise notice '% %', a, b; end loop inner; END sub; end loop outer; end; $$ language plpgsql; select test_lab(); ERROR: block label "sub" cannot be used in CONTINUE LINE 10: continue sub; ^ QUERY: begin <> for a in 1..3 loop <> BEGIN <> for b in 8..9 loop if a=2 then continue sub; end if; raise notice '% %', a, b; end loop inner; END sub; end loop outer; end; CONTEXT: compilation of PL/pgSQL function "test_lab" near line 10 select * from plpgsql_check_function('test_lab()', performance_warnings := true); plpgsql_check_function ------------------------------------------------------------------- error:42601:block label "sub" cannot be used in CONTINUE Query: begin <> for a in 1..3 loop <> BEGIN <> for b in 8..9 loop if a=2 then continue sub; -- ^ end if; raise notice '% %', a, b; end loop inner; END sub; end loop outer; end; Context: compilation of PL/pgSQL function "test_lab" near line 10 (20 rows) create or replace function test_lab() returns void as $$ begin continue; end; $$ language plpgsql; select test_lab(); ERROR: CONTINUE cannot be used outside a loop LINE 3: continue; ^ QUERY: begin continue; end; CONTEXT: compilation of PL/pgSQL function "test_lab" near line 3 select * from plpgsql_check_function('test_lab()', performance_warnings := true); plpgsql_check_function ------------------------------------------------------------------ error:42601:CONTINUE cannot be used outside a loop Query: begin continue; -- ^ end; Context: compilation of PL/pgSQL function "test_lab" near line 3 (8 rows) create type _exception_type as ( state text, message text, detail text); create or replace function f1() returns void as $$ declare _exception record; begin _exception := NULL::_exception_type; exception when others then get stacked diagnostics _exception.state = RETURNED_SQLSTATE, _exception.message = MESSAGE_TEXT, _exception.detail = PG_EXCEPTION_DETAIL, _exception.hint = PG_EXCEPTION_HINT; end; $$ language plpgsql; select f1(); f1 ---- (1 row) select * from plpgsql_check_function('f1()'); plpgsql_check_function ------------------------------------------------------------------------------- error:42703:7:GET STACKED DIAGNOSTICS:record "_exception" has no field "hint" (1 row) drop function f1(); drop type _exception_type; drop table t1; create function myfunc1(a int, b float) returns integer as $$ begin end $$ language plpgsql; create function myfunc2(a int, b float) returns integer as $$ begin end $$ language plpgsql; create function myfunc3(a int, b float) returns integer as $$ begin end $$ language plpgsql; create function myfunc4(a int, b float) returns integer as $$ begin end $$ language plpgsql; create function opfunc1(a int, b float) returns integer as $$ begin end $$ language plpgsql; create operator *** (procedure = opfunc1, leftarg = int, rightarg = float); create table mytable(a int); create table myview as select * from mytable; create function testfunc(a int, b float) returns void as $$ declare x integer; begin raise notice '%', myfunc1(a, b); x := myfunc2(a, b) operator(public.***) 1; perform myfunc3(m.a, b) from myview m; insert into mytable select myfunc4(a, b); end; $$ language plpgsql; select * from plpgsql_check_function('testfunc(int,float)'); plpgsql_check_function ------------------------------------------------------- warning extra:00000:2:DECLARE:never read variable "x" (1 row) select type, schema, name, params from plpgsql_show_dependency_tb('testfunc(int,float)'); type | schema | name | params ----------+--------+---------+---------------------------- FUNCTION | public | myfunc1 | (integer,double precision) FUNCTION | public | myfunc2 | (integer,double precision) FUNCTION | public | myfunc3 | (integer,double precision) FUNCTION | public | myfunc4 | (integer,double precision) OPERATOR | public | *** | (integer,double precision) RELATION | public | mytable | RELATION | public | myview | (7 rows) drop function testfunc(int, float); drop function myfunc1(int, float); drop function myfunc2(int, float); drop function myfunc3(int, float); drop function myfunc4(int, float); drop table mytable; drop view myview; ERROR: "myview" is not a view HINT: Use DROP TABLE to remove a table. -- issue #34 create or replace function testcase() returns bool as $$ declare x int; begin set local search_path to public, test; case x when 1 then return true; else return false; end case; end; $$ language plpgsql; -- should not to raise warning select * from plpgsql_check_function('testcase()'); plpgsql_check_function ------------------------ (0 rows) drop function testcase(); -- Adam's Bartoszewicz example create or replace function public.test12() returns refcursor language plpgsql as $body$ declare rc refcursor; begin open rc scroll for select pc.* from pg_cast pc; return rc; end; $body$; -- should not returns false alarm select * from plpgsql_check_function('test12()'); plpgsql_check_function ------------------------ (0 rows) drop function public.test12(); -- should to show performance warning on bad flag create or replace function flag_test1(int) returns int as $$ begin return $1 + 10; end; $$ language plpgsql stable; create table fufu(a int); create or replace function flag_test2(int) returns int as $$ begin return (select * from fufu limit 1); end; $$ language plpgsql volatile; select * from plpgsql_check_function('flag_test1(int)', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------------- performance:00000:routine is marked as STABLE, should be IMMUTABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (2 rows) select * from plpgsql_check_function('flag_test2(int)', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------------- warning extra:00000:unused parameter "$1" performance:00000:routine is marked as VOLATILE, should be STABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (3 rows) drop table fufu; drop function flag_test1(int); drop function flag_test2(int); create or replace function rrecord01() returns setof record as $$ begin return query select 1,2; end; $$ language plpgsql; create or replace function rrecord02() returns record as $$ begin return row(10,20,30); end; $$ language plpgsql; create type record03 as (a int, b int); create or replace function rrecord03() returns record03 as $$ declare r record; begin r := row(1); return r; end; $$ language plpgsql; -- should not to raise false alarms select * from plpgsql_check_function('rrecord01'); plpgsql_check_function ------------------------ (0 rows) select * from plpgsql_check_function('rrecord02'); plpgsql_check_function ------------------------ (0 rows) -- should detect different return but still detect return select * from plpgsql_check_function('rrecord03', fatal_errors => false); plpgsql_check_function ---------------------------------------------------------------------------------- error:42804:5:RETURN:returned record type does not match expected record type Detail: Number of returned columns (1) does not match expected column count (2). (2 rows) drop function rrecord01(); drop function rrecord02(); drop function rrecord03(); drop type record03; create or replace function bugfunc01() returns void as $$ declare cvar cursor(a int, b int) for select a + b from generate_series(1,b); begin for t in cvar(1,3) loop raise notice '%', t; end loop; end; $$ language plpgsql; select bugfunc01(); NOTICE: (4) NOTICE: (4) NOTICE: (4) bugfunc01 ----------- (1 row) select * from plpgsql_check_function('bugfunc01'); plpgsql_check_function ------------------------ (0 rows) create or replace function bugfunc02() returns void as $$ declare cvar cursor(a int, b int) for select a + b from generate_series(1,b); begin open cvar(10,20); close cvar; end; $$ language plpgsql; select bugfunc02(); bugfunc02 ----------- (1 row) select * from plpgsql_check_function('bugfunc02'); plpgsql_check_function ------------------------ (0 rows) create or replace function bugfunc03() returns void as $$ declare cvar cursor(a int, b int) for select a + b from not_exists_table; begin open cvar(10,20); close cvar; end; $$ language plpgsql; select bugfunc03(); ERROR: relation "not_exists_table" does not exist LINE 1: select a + b from not_exists_table ^ QUERY: select a + b from not_exists_table CONTEXT: PL/pgSQL function bugfunc03() line 5 at OPEN select * from plpgsql_check_function('bugfunc03'); plpgsql_check_function --------------------------------------------------------------- error:42P01:5:OPEN:relation "not_exists_table" does not exist Query: select a + b from not_exists_table -- ^ (3 rows) create or replace function f1(out cr refcursor) as $$ begin end; $$ language plpgsql; -- should to raise warning select * from plpgsql_check_function('f1()'); plpgsql_check_function -------------------------------------------------- warning extra:00000:unmodified OUT variable "cr" (1 row) create or replace function f1(out cr refcursor) as $$ begin open cr for select 1; end; $$ language plpgsql; -- should not to raise warning, see issue #43 select * from plpgsql_check_function('f1()'); plpgsql_check_function ------------------------ (0 rows) drop function f1(); create table testt(a int); create or replace function testt_trg_func() returns trigger as $$ begin return new; end; $$ language plpgsql; create trigger testt_trg before insert or update on testt for each row execute procedure testt_trg_func(); create or replace function maintaince_function() returns void as $$ begin alter table testt disable trigger testt_trg; alter table testt enable trigger testt_trg; end; $$ language plpgsql; -- should not to crash select * from plpgsql_check_function_tb('maintaince_function()', 0, true, true, true); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+---------+--------+------+-------+----------+-------+--------- (0 rows) drop function maintaince_function(); drop trigger testt_trg on testt; drop function testt_trg_func(); drop table testt; create or replace function test_crash() returns void as $$ declare ec int default buggyfunc(10); begin select * into ec from buggytab; end; $$ language plpgsql; -- should not to crash select * from plpgsql_check_function('test_crash', fatal_errors := false); plpgsql_check_function --------------------------------------------------------------------------------------------------------- error:42883:4:statement block:function buggyfunc(integer) does not exist Query: SELECT buggyfunc(10) -- ^ Hint: No function matches the given name and argument types. You might need to add explicit type casts. Context: during statement block local variable "ec" initialization on line 3 error:42P01:5:SQL statement:relation "buggytab" does not exist Query: select * from buggytab -- ^ warning extra:00000:3:DECLARE:never read variable "ec" (9 rows) select * from plpgsql_check_function('test_crash', fatal_errors := true); plpgsql_check_function --------------------------------------------------------------------------------------------------------- error:42883:4:statement block:function buggyfunc(integer) does not exist Query: SELECT buggyfunc(10) -- ^ Hint: No function matches the given name and argument types. You might need to add explicit type casts. Context: during statement block local variable "ec" initialization on line 3 (5 rows) drop function test_crash(); -- fix false alarm reported by Piotr Stepniewski create or replace function public.fx() returns void language plpgsql as $function$ begin raise exception 'xxx'; end; $function$; -- show raise nothing select * from plpgsql_check_function('fx()'); plpgsql_check_function ------------------------ (0 rows) create table errtab( message text, code character(5) ); create or replace function public.fx() returns void language plpgsql as $function$ declare var errtab%rowtype; begin raise exception using message = var.message, errcode = var.code; end; $function$; -- should not to crash select * from plpgsql_check_function('fx()'); plpgsql_check_function ------------------------ (0 rows) create or replace function public.fx() returns void language plpgsql as $function$ declare var errtab%rowtype; begin raise exception using message = var.message, errcode = var.code, hint = var.hint; end; $function$; -- should not to crash select * from plpgsql_check_function('fx()'); plpgsql_check_function ------------------------------------------------------ error:42703:5:RAISE:record "var" has no field "hint" Context: SQL statement "SELECT var.hint" (2 rows) drop function fx(); create or replace function foo_format(a text, b text) returns void as $$ declare s text; begin s := format('%s'); -- should to raise error s := format('%s %10s', a, b); -- should be ok s := format('%s %s', a, b, a); -- should to raise warning s := format('%s %d', a, b); -- should to raise error raise notice '%', s; end; $$ language plpgsql; select * from plpgsql_check_function('foo_format', fatal_errors := false); plpgsql_check_function ------------------------------------------------------------------- error:22023:4:assignment:too few arguments for format() Query: SELECT format('%s') -- ^ Context: at assignment to variable "s" declared on line 2 warning:00000:6:assignment:unused parameters of function "format" Query: SELECT format('%s %s', a, b, a) -- ^ Context: at assignment to variable "s" declared on line 2 error:22023:7:assignment:unrecognized format() type specifier "d" Query: SELECT format('%s %d', a, b) -- ^ Context: at assignment to variable "s" declared on line 2 (12 rows) drop function foo_format(text, text); create or replace function dyn_sql_1() returns void as $$ declare v varchar; n int; begin execute 'select ' || n; -- ok execute 'select ' || quote_literal(v); -- ok execute 'select ' || v; -- vulnerable execute format('select * from %I', v); -- ok execute format('select * from %s', v); -- vulnerable execute 'select $1' using v; -- ok execute 'select 1'; -- ok execute 'select 1' using v; -- warning execute 'select $1'; -- error end; $$ language plpgsql; select * from plpgsql_check_function('dyn_sql_1', security_warnings := true, fatal_errors := false); plpgsql_check_function ------------------------------------------------------------------------------------------ security:00000:8:EXECUTE:text type variable is not sanitized Query: SELECT 'select ' || v -- ^ Detail: The EXECUTE expression is SQL injection vulnerable. Hint: Use quote_ident, quote_literal or format function to secure variable. security:00000:10:EXECUTE:text type variable is not sanitized Query: SELECT format('select * from %s', v) -- ^ Detail: The EXECUTE expression is SQL injection vulnerable. Hint: Use quote_ident, quote_literal or format function to secure variable. warning:00000:13:EXECUTE:values passed to EXECUTE statement by USING clause was not used error:42P02:14:EXECUTE:there is no parameter $1 Query: select $1 -- ^ (14 rows) drop function dyn_sql_1(); create type tp as (a int, b int); create or replace function dyn_sql_2() returns void as $$ declare r tp; result int; begin select 10 a, 20 b into r; raise notice '%', r.a; execute 'select $1.a + $1.b' into result using r; execute 'select $1.c' into result using r; -- error raise notice '%', result; end; $$ language plpgsql; select * from plpgsql_check_function('dyn_sql_2', security_warnings := true); plpgsql_check_function ------------------------------------------------------------ error:42703:9:EXECUTE:column "c" not found in data type tp Query: select $1.c -- ^ (3 rows) drop function dyn_sql_2(); drop type tp; /* * Should not to work * * note: plpgsql doesn't support passing some necessary details for record * type. The parser setup for dynamic SQL column doesn't use ref hooks, and * then it cannot to pass TupleDesc info to query anyway. */ create or replace function dyn_sql_2() returns void as $$ declare r record; result int; begin select 10 a, 20 b into r; raise notice '%', r.a; execute 'select $1.a + $1.b' into result using r; raise notice '%', result; end; $$ language plpgsql; select dyn_sql_2(); --should to fail NOTICE: 10 ERROR: could not identify column "a" in record data type LINE 1: select $1.a + $1.b ^ QUERY: select $1.a + $1.b CONTEXT: PL/pgSQL function dyn_sql_2() line 8 at EXECUTE select * from plpgsql_check_function('dyn_sql_2', security_warnings := true); plpgsql_check_function ------------------------------------------------------------------------- error:42703:8:EXECUTE:could not identify column "a" in record data type Query: select $1.a + $1.b -- ^ (3 rows) drop function dyn_sql_2(); create or replace function dyn_sql_3() returns void as $$ declare r int; begin execute 'select $1' into r using 1; raise notice '%', r; end $$ language plpgsql; select dyn_sql_3(); NOTICE: 1 dyn_sql_3 ----------- (1 row) -- should be ok select * from plpgsql_check_function('dyn_sql_3'); plpgsql_check_function ------------------------ (0 rows) create or replace function dyn_sql_3() returns void as $$ declare r record; begin execute 'select $1 as a, $2 as b' into r using 1, 2; raise notice '% %', r.a, r.b; end $$ language plpgsql; select dyn_sql_3(); NOTICE: 1 2 dyn_sql_3 ----------- (1 row) -- should be ok select * from plpgsql_check_function('dyn_sql_3'); plpgsql_check_function ------------------------ (0 rows) create or replace function dyn_sql_3() returns void as $$ declare r record; begin execute 'create table foo(a int)' into r using 1, 2; raise notice '% %', r.a, r.b; end $$ language plpgsql; -- raise a error select * from plpgsql_check_function('dyn_sql_3'); plpgsql_check_function ----------------------------------------------------------------------------------------- warning:00000:4:EXECUTE:values passed to EXECUTE statement by USING clause was not used error:XX000:4:EXECUTE:expression does not return data (2 rows) create or replace function dyn_sql_3() returns void as $$ declare r1 int; r2 int; begin execute 'select 1' into r1, r2 using 1, 2; raise notice '% %', r1, r2; end $$ language plpgsql; -- raise a error select * from plpgsql_check_function('dyn_sql_3'); plpgsql_check_function ----------------------------------------------------------------------------------------- warning:00000:4:EXECUTE:values passed to EXECUTE statement by USING clause was not used warning:00000:4:EXECUTE:too few attributes for target variables Detail: There are more target variables than output columns in query. Hint: Check target variables in SELECT INTO statement. (4 rows) drop function dyn_sql_3(); create or replace function dyn_sql_3() returns void as $$ declare r record; begin for r in execute 'select 1 as a, 2 as b' loop raise notice '%', r.a; end loop; end $$ language plpgsql; -- should be ok select * from plpgsql_check_function('dyn_sql_3'); plpgsql_check_function ------------------------ (0 rows) drop function dyn_sql_3(); create or replace function dyn_sql_3() returns void as $$ declare r record; begin for r in execute 'select 1 as a, 2 as b' loop raise notice '%', r.c; end loop; end $$ language plpgsql; -- should be error select * from plpgsql_check_function('dyn_sql_3'); plpgsql_check_function ------------------------------------------------- error:42703:6:RAISE:record "r" has no field "c" Context: SQL statement "SELECT r.c" (2 rows) drop function dyn_sql_3(); create or replace function dyn_sql_3() returns void as $$ declare r record; v text = 'select 10 a, 20 b't; begin for r in execute v loop raise notice '%', r.a; end loop; end $$ language plpgsql; -- should be ok select * from plpgsql_check_function('dyn_sql_3'); plpgsql_check_function ------------------------ (0 rows) drop function dyn_sql_3(); create or replace function dyn_sql_4() returns table(ax int, bx int) as $$ begin return query execute 'select 10, 20'; return; end; $$ language plpgsql; -- should be ok select * from plpgsql_check_function('dyn_sql_4()'); plpgsql_check_function ------------------------ (0 rows) create or replace function dyn_sql_4() returns table(ax int, bx int) as $$ begin return query execute 'select 10, 20, 30'; return; end; $$ language plpgsql; select * from dyn_sql_4(); ERROR: structure of query does not match function result type DETAIL: Number of returned columns (3) does not match expected column count (2). CONTEXT: PL/pgSQL function dyn_sql_4() line 3 at RETURN QUERY -- should be error select * from plpgsql_check_function('dyn_sql_4()'); plpgsql_check_function ----------------------------------------------------------------------------------- error:42804:3:RETURN QUERY:structure of query does not match function result type Detail: Number of returned columns (3) does not match expected column count (2). (2 rows) drop function dyn_sql_4(); create or replace function test_bug(text) returns regproc as $$ begin return $1::regproc; exception when undefined_function or invalid_name then raise; end; $$ language plpgsql; -- should not raise a exception select * from plpgsql_check_function('test_bug'); plpgsql_check_function ------------------------ (0 rows) create or replace function test_bug(text) returns regproc as $$ begin return $1::regproc; exception when undefined_function or invalid_name then raise notice '%', $1; -- bug end; $$ language plpgsql; select test_bug('kuku'); -- should to fail NOTICE: kuku ERROR: control reached end of function without RETURN CONTEXT: PL/pgSQL function test_bug(text) select * from plpgsql_check_function('test_bug'); plpgsql_check_function -------------------------------------------------------------------- warning extra:2F005:control reached end of function without RETURN (1 row) drop function test_bug(text); create or replace function test_bug(text) returns regproc as $$ begin return $1::regproc; exception when undefined_function or invalid_name then raise notice '%', $1; return NULL; end; $$ language plpgsql; select test_bug('kuku'); -- should be ok NOTICE: kuku test_bug ---------- (1 row) select * from plpgsql_check_function('test_bug'); plpgsql_check_function ------------------------ (0 rows) drop function test_bug(text); create or replace function foo(a text, b text) returns void as $$ begin -- unsecure execute 'select ' || a; a := quote_literal(a); -- is safe now execute 'select ' || a; a := a || b; -- it is unsecure again execute 'select ' || a; end; $$ language plpgsql; \sf+ foo(text, text) CREATE OR REPLACE FUNCTION public.foo(a text, b text) RETURNS void LANGUAGE plpgsql 1 AS $function$ 2 begin 3 -- unsecure 4 execute 'select ' || a; 5 a := quote_literal(a); -- is safe now 6 execute 'select ' || a; 7 a := a || b; -- it is unsecure again 8 execute 'select ' || a; 9 end; 10 $function$ -- should to raise two warnings select * from plpgsql_check_function('foo', security_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------- security:00000:4:EXECUTE:text type variable is not sanitized Query: SELECT 'select ' || a -- ^ Detail: The EXECUTE expression is SQL injection vulnerable. Hint: Use quote_ident, quote_literal or format function to secure variable. security:00000:8:EXECUTE:text type variable is not sanitized Query: SELECT 'select ' || a -- ^ Detail: The EXECUTE expression is SQL injection vulnerable. Hint: Use quote_ident, quote_literal or format function to secure variable. (10 rows) drop function foo(text, text); -- test of very long function inside profiler create or replace function longfx(int) returns int as $$ declare s int default 0; j int default 0; r record; begin begin while j < 10 loop for i in 1..1 loop for r in select * from generate_series(1,1) loop s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; end loop; end loop; j := j + 1; end loop; exception when others then raise 'reraised exception %', sqlerrm; end; return $1; end; $$ language plpgsql; select lineno, stmt_lineno, exec_stmts, source from plpgsql_profiler_function_tb('longfx'); lineno | stmt_lineno | exec_stmts | source --------+-------------+------------+----------------------------------------------------- 1 | | | 2 | | | declare 3 | | | s int default 0; 4 | | | j int default 0; 5 | | | r record; 6 | | | begin 7 | | | begin 8 | | | while j < 10 9 | | | loop 10 | | | for i in 1..1 11 | | | loop 12 | | | for r in select * from generate_series(1,1) 13 | | | loop 14 | | | s := s + 1; 15 | | | s := s + 1; 16 | | | s := s + 1; 17 | | | s := s + 1; 18 | | | s := s + 1; 19 | | | s := s + 1; 20 | | | s := s + 1; 21 | | | s := s + 1; 22 | | | s := s + 1; 23 | | | s := s + 1; 24 | | | s := s + 1; 25 | | | s := s + 1; 26 | | | s := s + 1; 27 | | | s := s + 1; 28 | | | s := s + 1; 29 | | | s := s + 1; 30 | | | s := s + 1; 31 | | | s := s + 1; 32 | | | s := s + 1; 33 | | | s := s + 1; 34 | | | s := s + 1; 35 | | | s := s + 1; 36 | | | s := s + 1; 37 | | | s := s + 1; 38 | | | s := s + 1; 39 | | | s := s + 1; 40 | | | s := s + 1; 41 | | | s := s + 1; 42 | | | s := s + 1; 43 | | | s := s + 1; 44 | | | s := s + 1; 45 | | | s := s + 1; 46 | | | s := s + 1; 47 | | | s := s + 1; 48 | | | s := s + 1; 49 | | | s := s + 1; 50 | | | s := s + 1; 51 | | | s := s + 1; 52 | | | s := s + 1; 53 | | | s := s + 1; 54 | | | s := s + 1; 55 | | | s := s + 1; 56 | | | s := s + 1; 57 | | | s := s + 1; 58 | | | s := s + 1; 59 | | | s := s + 1; 60 | | | s := s + 1; 61 | | | s := s + 1; 62 | | | s := s + 1; 63 | | | s := s + 1; 64 | | | s := s + 1; 65 | | | s := s + 1; 66 | | | s := s + 1; 67 | | | s := s + 1; 68 | | | s := s + 1; 69 | | | s := s + 1; 70 | | | s := s + 1; 71 | | | s := s + 1; 72 | | | s := s + 1; 73 | | | s := s + 1; 74 | | | s := s + 1; 75 | | | s := s + 1; 76 | | | s := s + 1; 77 | | | s := s + 1; 78 | | | s := s + 1; 79 | | | s := s + 1; 80 | | | s := s + 1; 81 | | | s := s + 1; 82 | | | s := s + 1; 83 | | | s := s + 1; 84 | | | s := s + 1; 85 | | | s := s + 1; 86 | | | s := s + 1; 87 | | | s := s + 1; 88 | | | s := s + 1; 89 | | | s := s + 1; 90 | | | s := s + 1; 91 | | | s := s + 1; 92 | | | s := s + 1; 93 | | | s := s + 1; 94 | | | s := s + 1; 95 | | | s := s + 1; 96 | | | s := s + 1; 97 | | | s := s + 1; 98 | | | s := s + 1; 99 | | | s := s + 1; 100 | | | s := s + 1; 101 | | | s := s + 1; 102 | | | s := s + 1; 103 | | | s := s + 1; 104 | | | s := s + 1; 105 | | | s := s + 1; 106 | | | s := s + 1; 107 | | | s := s + 1; 108 | | | s := s + 1; 109 | | | s := s + 1; 110 | | | s := s + 1; 111 | | | s := s + 1; 112 | | | s := s + 1; 113 | | | s := s + 1; 114 | | | s := s + 1; 115 | | | s := s + 1; 116 | | | s := s + 1; 117 | | | s := s + 1; 118 | | | s := s + 1; 119 | | | s := s + 1; 120 | | | s := s + 1; 121 | | | s := s + 1; 122 | | | s := s + 1; 123 | | | s := s + 1; 124 | | | s := s + 1; 125 | | | s := s + 1; 126 | | | s := s + 1; 127 | | | s := s + 1; 128 | | | s := s + 1; 129 | | | s := s + 1; 130 | | | s := s + 1; 131 | | | s := s + 1; 132 | | | s := s + 1; 133 | | | s := s + 1; 134 | | | s := s + 1; 135 | | | s := s + 1; 136 | | | s := s + 1; 137 | | | s := s + 1; 138 | | | s := s + 1; 139 | | | s := s + 1; 140 | | | s := s + 1; 141 | | | s := s + 1; 142 | | | s := s + 1; 143 | | | s := s + 1; 144 | | | s := s + 1; 145 | | | s := s + 1; 146 | | | s := s + 1; 147 | | | s := s + 1; 148 | | | s := s + 1; 149 | | | s := s + 1; 150 | | | s := s + 1; 151 | | | s := s + 1; 152 | | | s := s + 1; 153 | | | s := s + 1; 154 | | | s := s + 1; 155 | | | s := s + 1; 156 | | | s := s + 1; 157 | | | s := s + 1; 158 | | | s := s + 1; 159 | | | s := s + 1; 160 | | | s := s + 1; 161 | | | s := s + 1; 162 | | | s := s + 1; 163 | | | s := s + 1; 164 | | | s := s + 1; 165 | | | s := s + 1; 166 | | | s := s + 1; 167 | | | s := s + 1; 168 | | | s := s + 1; 169 | | | s := s + 1; 170 | | | s := s + 1; 171 | | | s := s + 1; 172 | | | s := s + 1; 173 | | | s := s + 1; 174 | | | s := s + 1; 175 | | | s := s + 1; 176 | | | s := s + 1; 177 | | | s := s + 1; 178 | | | s := s + 1; 179 | | | s := s + 1; 180 | | | s := s + 1; 181 | | | s := s + 1; 182 | | | s := s + 1; 183 | | | s := s + 1; 184 | | | s := s + 1; 185 | | | s := s + 1; 186 | | | s := s + 1; 187 | | | s := s + 1; 188 | | | s := s + 1; 189 | | | s := s + 1; 190 | | | s := s + 1; 191 | | | s := s + 1; 192 | | | s := s + 1; 193 | | | s := s + 1; 194 | | | s := s + 1; 195 | | | s := s + 1; 196 | | | s := s + 1; 197 | | | s := s + 1; 198 | | | s := s + 1; 199 | | | s := s + 1; 200 | | | s := s + 1; 201 | | | s := s + 1; 202 | | | s := s + 1; 203 | | | s := s + 1; 204 | | | s := s + 1; 205 | | | s := s + 1; 206 | | | s := s + 1; 207 | | | s := s + 1; 208 | | | s := s + 1; 209 | | | s := s + 1; 210 | | | s := s + 1; 211 | | | s := s + 1; 212 | | | s := s + 1; 213 | | | s := s + 1; 214 | | | s := s + 1; 215 | | | s := s + 1; 216 | | | s := s + 1; 217 | | | s := s + 1; 218 | | | s := s + 1; 219 | | | s := s + 1; 220 | | | s := s + 1; 221 | | | s := s + 1; 222 | | | s := s + 1; 223 | | | s := s + 1; 224 | | | s := s + 1; 225 | | | s := s + 1; 226 | | | s := s + 1; 227 | | | s := s + 1; 228 | | | s := s + 1; 229 | | | s := s + 1; 230 | | | s := s + 1; 231 | | | s := s + 1; 232 | | | s := s + 1; 233 | | | s := s + 1; 234 | | | s := s + 1; 235 | | | s := s + 1; 236 | | | s := s + 1; 237 | | | s := s + 1; 238 | | | s := s + 1; 239 | | | s := s + 1; 240 | | | s := s + 1; 241 | | | s := s + 1; 242 | | | s := s + 1; 243 | | | s := s + 1; 244 | | | s := s + 1; 245 | | | s := s + 1; 246 | | | s := s + 1; 247 | | | s := s + 1; 248 | | | s := s + 1; 249 | | | s := s + 1; 250 | | | s := s + 1; 251 | | | s := s + 1; 252 | | | s := s + 1; 253 | | | s := s + 1; 254 | | | s := s + 1; 255 | | | s := s + 1; 256 | | | s := s + 1; 257 | | | s := s + 1; 258 | | | s := s + 1; 259 | | | s := s + 1; 260 | | | s := s + 1; 261 | | | s := s + 1; 262 | | | s := s + 1; 263 | | | s := s + 1; 264 | | | s := s + 1; 265 | | | s := s + 1; 266 | | | s := s + 1; 267 | | | s := s + 1; 268 | | | s := s + 1; 269 | | | s := s + 1; 270 | | | s := s + 1; 271 | | | s := s + 1; 272 | | | s := s + 1; 273 | | | s := s + 1; 274 | | | s := s + 1; 275 | | | s := s + 1; 276 | | | s := s + 1; 277 | | | s := s + 1; 278 | | | s := s + 1; 279 | | | s := s + 1; 280 | | | s := s + 1; 281 | | | s := s + 1; 282 | | | s := s + 1; 283 | | | s := s + 1; 284 | | | s := s + 1; 285 | | | s := s + 1; 286 | | | s := s + 1; 287 | | | s := s + 1; 288 | | | s := s + 1; 289 | | | s := s + 1; 290 | | | s := s + 1; 291 | | | s := s + 1; 292 | | | s := s + 1; 293 | | | s := s + 1; 294 | | | s := s + 1; 295 | | | s := s + 1; 296 | | | s := s + 1; 297 | | | s := s + 1; 298 | | | s := s + 1; 299 | | | s := s + 1; 300 | | | s := s + 1; 301 | | | s := s + 1; 302 | | | s := s + 1; 303 | | | s := s + 1; 304 | | | s := s + 1; 305 | | | s := s + 1; 306 | | | s := s + 1; 307 | | | s := s + 1; 308 | | | s := s + 1; 309 | | | s := s + 1; 310 | | | s := s + 1; 311 | | | s := s + 1; 312 | | | s := s + 1; 313 | | | s := s + 1; 314 | | | s := s + 1; 315 | | | s := s + 1; 316 | | | s := s + 1; 317 | | | s := s + 1; 318 | | | s := s + 1; 319 | | | s := s + 1; 320 | | | s := s + 1; 321 | | | s := s + 1; 322 | | | s := s + 1; 323 | | | s := s + 1; 324 | | | s := s + 1; 325 | | | s := s + 1; 326 | | | s := s + 1; 327 | | | s := s + 1; 328 | | | s := s + 1; 329 | | | s := s + 1; 330 | | | s := s + 1; 331 | | | s := s + 1; 332 | | | s := s + 1; 333 | | | s := s + 1; 334 | | | s := s + 1; 335 | | | s := s + 1; 336 | | | s := s + 1; 337 | | | s := s + 1; 338 | | | s := s + 1; 339 | | | s := s + 1; 340 | | | s := s + 1; 341 | | | s := s + 1; 342 | | | s := s + 1; 343 | | | s := s + 1; 344 | | | s := s + 1; 345 | | | s := s + 1; 346 | | | s := s + 1; 347 | | | s := s + 1; 348 | | | s := s + 1; 349 | | | s := s + 1; 350 | | | s := s + 1; 351 | | | s := s + 1; 352 | | | s := s + 1; 353 | | | s := s + 1; 354 | | | s := s + 1; 355 | | | s := s + 1; 356 | | | s := s + 1; 357 | | | s := s + 1; 358 | | | s := s + 1; 359 | | | s := s + 1; 360 | | | s := s + 1; 361 | | | s := s + 1; 362 | | | s := s + 1; 363 | | | s := s + 1; 364 | | | s := s + 1; 365 | | | s := s + 1; 366 | | | s := s + 1; 367 | | | s := s + 1; 368 | | | s := s + 1; 369 | | | s := s + 1; 370 | | | s := s + 1; 371 | | | s := s + 1; 372 | | | s := s + 1; 373 | | | s := s + 1; 374 | | | s := s + 1; 375 | | | s := s + 1; 376 | | | s := s + 1; 377 | | | s := s + 1; 378 | | | s := s + 1; 379 | | | s := s + 1; 380 | | | s := s + 1; 381 | | | s := s + 1; 382 | | | s := s + 1; 383 | | | s := s + 1; 384 | | | s := s + 1; 385 | | | s := s + 1; 386 | | | s := s + 1; 387 | | | s := s + 1; 388 | | | s := s + 1; 389 | | | s := s + 1; 390 | | | s := s + 1; 391 | | | s := s + 1; 392 | | | s := s + 1; 393 | | | s := s + 1; 394 | | | s := s + 1; 395 | | | s := s + 1; 396 | | | s := s + 1; 397 | | | s := s + 1; 398 | | | s := s + 1; 399 | | | s := s + 1; 400 | | | s := s + 1; 401 | | | s := s + 1; 402 | | | s := s + 1; 403 | | | s := s + 1; 404 | | | s := s + 1; 405 | | | s := s + 1; 406 | | | s := s + 1; 407 | | | s := s + 1; 408 | | | s := s + 1; 409 | | | s := s + 1; 410 | | | s := s + 1; 411 | | | s := s + 1; 412 | | | s := s + 1; 413 | | | s := s + 1; 414 | | | s := s + 1; 415 | | | s := s + 1; 416 | | | s := s + 1; 417 | | | s := s + 1; 418 | | | s := s + 1; 419 | | | s := s + 1; 420 | | | s := s + 1; 421 | | | s := s + 1; 422 | | | s := s + 1; 423 | | | s := s + 1; 424 | | | s := s + 1; 425 | | | s := s + 1; 426 | | | s := s + 1; 427 | | | s := s + 1; 428 | | | s := s + 1; 429 | | | s := s + 1; 430 | | | s := s + 1; 431 | | | s := s + 1; 432 | | | s := s + 1; 433 | | | s := s + 1; 434 | | | s := s + 1; 435 | | | s := s + 1; 436 | | | s := s + 1; 437 | | | s := s + 1; 438 | | | s := s + 1; 439 | | | s := s + 1; 440 | | | s := s + 1; 441 | | | s := s + 1; 442 | | | s := s + 1; 443 | | | s := s + 1; 444 | | | s := s + 1; 445 | | | s := s + 1; 446 | | | s := s + 1; 447 | | | s := s + 1; 448 | | | s := s + 1; 449 | | | s := s + 1; 450 | | | s := s + 1; 451 | | | s := s + 1; 452 | | | s := s + 1; 453 | | | s := s + 1; 454 | | | s := s + 1; 455 | | | s := s + 1; 456 | | | s := s + 1; 457 | | | s := s + 1; 458 | | | s := s + 1; 459 | | | s := s + 1; 460 | | | s := s + 1; 461 | | | s := s + 1; 462 | | | s := s + 1; 463 | | | s := s + 1; 464 | | | s := s + 1; 465 | | | s := s + 1; 466 | | | s := s + 1; 467 | | | s := s + 1; 468 | | | s := s + 1; 469 | | | s := s + 1; 470 | | | s := s + 1; 471 | | | s := s + 1; 472 | | | s := s + 1; 473 | | | s := s + 1; 474 | | | s := s + 1; 475 | | | s := s + 1; 476 | | | s := s + 1; 477 | | | s := s + 1; 478 | | | s := s + 1; 479 | | | s := s + 1; 480 | | | s := s + 1; 481 | | | s := s + 1; 482 | | | s := s + 1; 483 | | | s := s + 1; 484 | | | s := s + 1; 485 | | | s := s + 1; 486 | | | s := s + 1; 487 | | | s := s + 1; 488 | | | s := s + 1; 489 | | | s := s + 1; 490 | | | s := s + 1; 491 | | | s := s + 1; 492 | | | s := s + 1; 493 | | | s := s + 1; 494 | | | s := s + 1; 495 | | | s := s + 1; 496 | | | s := s + 1; 497 | | | s := s + 1; 498 | | | s := s + 1; 499 | | | s := s + 1; 500 | | | s := s + 1; 501 | | | s := s + 1; 502 | | | s := s + 1; 503 | | | s := s + 1; 504 | | | s := s + 1; 505 | | | s := s + 1; 506 | | | s := s + 1; 507 | | | s := s + 1; 508 | | | s := s + 1; 509 | | | s := s + 1; 510 | | | s := s + 1; 511 | | | s := s + 1; 512 | | | s := s + 1; 513 | | | s := s + 1; 514 | | | s := s + 1; 515 | | | s := s + 1; 516 | | | s := s + 1; 517 | | | s := s + 1; 518 | | | s := s + 1; 519 | | | s := s + 1; 520 | | | s := s + 1; 521 | | | s := s + 1; 522 | | | s := s + 1; 523 | | | s := s + 1; 524 | | | s := s + 1; 525 | | | s := s + 1; 526 | | | s := s + 1; 527 | | | s := s + 1; 528 | | | s := s + 1; 529 | | | s := s + 1; 530 | | | s := s + 1; 531 | | | s := s + 1; 532 | | | s := s + 1; 533 | | | s := s + 1; 534 | | | s := s + 1; 535 | | | s := s + 1; 536 | | | s := s + 1; 537 | | | s := s + 1; 538 | | | s := s + 1; 539 | | | s := s + 1; 540 | | | s := s + 1; 541 | | | s := s + 1; 542 | | | s := s + 1; 543 | | | s := s + 1; 544 | | | s := s + 1; 545 | | | s := s + 1; 546 | | | s := s + 1; 547 | | | s := s + 1; 548 | | | s := s + 1; 549 | | | s := s + 1; 550 | | | s := s + 1; 551 | | | s := s + 1; 552 | | | s := s + 1; 553 | | | s := s + 1; 554 | | | s := s + 1; 555 | | | s := s + 1; 556 | | | s := s + 1; 557 | | | s := s + 1; 558 | | | s := s + 1; 559 | | | s := s + 1; 560 | | | s := s + 1; 561 | | | s := s + 1; 562 | | | s := s + 1; 563 | | | s := s + 1; 564 | | | s := s + 1; 565 | | | s := s + 1; 566 | | | s := s + 1; 567 | | | s := s + 1; 568 | | | s := s + 1; 569 | | | s := s + 1; 570 | | | s := s + 1; 571 | | | s := s + 1; 572 | | | s := s + 1; 573 | | | s := s + 1; 574 | | | s := s + 1; 575 | | | s := s + 1; 576 | | | s := s + 1; 577 | | | s := s + 1; 578 | | | s := s + 1; 579 | | | s := s + 1; 580 | | | s := s + 1; 581 | | | s := s + 1; 582 | | | s := s + 1; 583 | | | s := s + 1; 584 | | | s := s + 1; 585 | | | s := s + 1; 586 | | | s := s + 1; 587 | | | s := s + 1; 588 | | | s := s + 1; 589 | | | s := s + 1; 590 | | | s := s + 1; 591 | | | s := s + 1; 592 | | | s := s + 1; 593 | | | s := s + 1; 594 | | | s := s + 1; 595 | | | s := s + 1; 596 | | | s := s + 1; 597 | | | s := s + 1; 598 | | | s := s + 1; 599 | | | s := s + 1; 600 | | | s := s + 1; 601 | | | s := s + 1; 602 | | | s := s + 1; 603 | | | s := s + 1; 604 | | | s := s + 1; 605 | | | s := s + 1; 606 | | | s := s + 1; 607 | | | s := s + 1; 608 | | | s := s + 1; 609 | | | s := s + 1; 610 | | | s := s + 1; 611 | | | s := s + 1; 612 | | | s := s + 1; 613 | | | s := s + 1; 614 | | | s := s + 1; 615 | | | s := s + 1; 616 | | | s := s + 1; 617 | | | s := s + 1; 618 | | | s := s + 1; 619 | | | s := s + 1; 620 | | | s := s + 1; 621 | | | s := s + 1; 622 | | | s := s + 1; 623 | | | s := s + 1; 624 | | | s := s + 1; 625 | | | s := s + 1; 626 | | | s := s + 1; 627 | | | s := s + 1; 628 | | | s := s + 1; 629 | | | s := s + 1; 630 | | | s := s + 1; 631 | | | s := s + 1; 632 | | | s := s + 1; 633 | | | s := s + 1; 634 | | | s := s + 1; 635 | | | s := s + 1; 636 | | | s := s + 1; 637 | | | s := s + 1; 638 | | | s := s + 1; 639 | | | s := s + 1; 640 | | | s := s + 1; 641 | | | s := s + 1; 642 | | | s := s + 1; 643 | | | s := s + 1; 644 | | | s := s + 1; 645 | | | s := s + 1; 646 | | | s := s + 1; 647 | | | s := s + 1; 648 | | | s := s + 1; 649 | | | s := s + 1; 650 | | | s := s + 1; 651 | | | s := s + 1; 652 | | | s := s + 1; 653 | | | s := s + 1; 654 | | | s := s + 1; 655 | | | s := s + 1; 656 | | | s := s + 1; 657 | | | s := s + 1; 658 | | | s := s + 1; 659 | | | s := s + 1; 660 | | | s := s + 1; 661 | | | s := s + 1; 662 | | | s := s + 1; 663 | | | s := s + 1; 664 | | | s := s + 1; 665 | | | s := s + 1; 666 | | | s := s + 1; 667 | | | s := s + 1; 668 | | | s := s + 1; 669 | | | s := s + 1; 670 | | | s := s + 1; 671 | | | s := s + 1; 672 | | | s := s + 1; 673 | | | s := s + 1; 674 | | | s := s + 1; 675 | | | s := s + 1; 676 | | | s := s + 1; 677 | | | s := s + 1; 678 | | | s := s + 1; 679 | | | s := s + 1; 680 | | | s := s + 1; 681 | | | s := s + 1; 682 | | | s := s + 1; 683 | | | s := s + 1; 684 | | | s := s + 1; 685 | | | s := s + 1; 686 | | | s := s + 1; 687 | | | s := s + 1; 688 | | | s := s + 1; 689 | | | s := s + 1; 690 | | | s := s + 1; 691 | | | s := s + 1; 692 | | | s := s + 1; 693 | | | s := s + 1; 694 | | | s := s + 1; 695 | | | s := s + 1; 696 | | | s := s + 1; 697 | | | s := s + 1; 698 | | | s := s + 1; 699 | | | s := s + 1; 700 | | | s := s + 1; 701 | | | s := s + 1; 702 | | | s := s + 1; 703 | | | s := s + 1; 704 | | | s := s + 1; 705 | | | s := s + 1; 706 | | | s := s + 1; 707 | | | s := s + 1; 708 | | | s := s + 1; 709 | | | s := s + 1; 710 | | | s := s + 1; 711 | | | s := s + 1; 712 | | | s := s + 1; 713 | | | s := s + 1; 714 | | | s := s + 1; 715 | | | s := s + 1; 716 | | | s := s + 1; 717 | | | s := s + 1; 718 | | | s := s + 1; 719 | | | s := s + 1; 720 | | | s := s + 1; 721 | | | s := s + 1; 722 | | | s := s + 1; 723 | | | s := s + 1; 724 | | | s := s + 1; 725 | | | s := s + 1; 726 | | | s := s + 1; 727 | | | s := s + 1; 728 | | | s := s + 1; 729 | | | s := s + 1; 730 | | | s := s + 1; 731 | | | s := s + 1; 732 | | | s := s + 1; 733 | | | s := s + 1; 734 | | | s := s + 1; 735 | | | s := s + 1; 736 | | | s := s + 1; 737 | | | s := s + 1; 738 | | | s := s + 1; 739 | | | s := s + 1; 740 | | | s := s + 1; 741 | | | s := s + 1; 742 | | | s := s + 1; 743 | | | s := s + 1; 744 | | | s := s + 1; 745 | | | s := s + 1; 746 | | | s := s + 1; 747 | | | s := s + 1; 748 | | | s := s + 1; 749 | | | s := s + 1; 750 | | | s := s + 1; 751 | | | s := s + 1; 752 | | | s := s + 1; 753 | | | s := s + 1; 754 | | | s := s + 1; 755 | | | s := s + 1; 756 | | | s := s + 1; 757 | | | s := s + 1; 758 | | | s := s + 1; 759 | | | s := s + 1; 760 | | | s := s + 1; 761 | | | s := s + 1; 762 | | | s := s + 1; 763 | | | s := s + 1; 764 | | | s := s + 1; 765 | | | s := s + 1; 766 | | | s := s + 1; 767 | | | s := s + 1; 768 | | | s := s + 1; 769 | | | s := s + 1; 770 | | | s := s + 1; 771 | | | s := s + 1; 772 | | | s := s + 1; 773 | | | s := s + 1; 774 | | | s := s + 1; 775 | | | s := s + 1; 776 | | | s := s + 1; 777 | | | s := s + 1; 778 | | | s := s + 1; 779 | | | s := s + 1; 780 | | | s := s + 1; 781 | | | s := s + 1; 782 | | | s := s + 1; 783 | | | s := s + 1; 784 | | | s := s + 1; 785 | | | s := s + 1; 786 | | | s := s + 1; 787 | | | s := s + 1; 788 | | | s := s + 1; 789 | | | s := s + 1; 790 | | | s := s + 1; 791 | | | s := s + 1; 792 | | | s := s + 1; 793 | | | s := s + 1; 794 | | | s := s + 1; 795 | | | s := s + 1; 796 | | | s := s + 1; 797 | | | s := s + 1; 798 | | | s := s + 1; 799 | | | s := s + 1; 800 | | | s := s + 1; 801 | | | s := s + 1; 802 | | | s := s + 1; 803 | | | s := s + 1; 804 | | | s := s + 1; 805 | | | s := s + 1; 806 | | | s := s + 1; 807 | | | s := s + 1; 808 | | | s := s + 1; 809 | | | s := s + 1; 810 | | | s := s + 1; 811 | | | s := s + 1; 812 | | | s := s + 1; 813 | | | s := s + 1; 814 | | | s := s + 1; 815 | | | s := s + 1; 816 | | | s := s + 1; 817 | | | s := s + 1; 818 | | | s := s + 1; 819 | | | s := s + 1; 820 | | | s := s + 1; 821 | | | s := s + 1; 822 | | | s := s + 1; 823 | | | s := s + 1; 824 | | | s := s + 1; 825 | | | s := s + 1; 826 | | | s := s + 1; 827 | | | s := s + 1; 828 | | | s := s + 1; 829 | | | s := s + 1; 830 | | | s := s + 1; 831 | | | s := s + 1; 832 | | | s := s + 1; 833 | | | s := s + 1; 834 | | | s := s + 1; 835 | | | s := s + 1; 836 | | | s := s + 1; 837 | | | s := s + 1; 838 | | | s := s + 1; 839 | | | s := s + 1; 840 | | | s := s + 1; 841 | | | s := s + 1; 842 | | | s := s + 1; 843 | | | s := s + 1; 844 | | | s := s + 1; 845 | | | s := s + 1; 846 | | | s := s + 1; 847 | | | s := s + 1; 848 | | | s := s + 1; 849 | | | s := s + 1; 850 | | | s := s + 1; 851 | | | s := s + 1; 852 | | | s := s + 1; 853 | | | s := s + 1; 854 | | | s := s + 1; 855 | | | s := s + 1; 856 | | | s := s + 1; 857 | | | s := s + 1; 858 | | | s := s + 1; 859 | | | s := s + 1; 860 | | | s := s + 1; 861 | | | s := s + 1; 862 | | | s := s + 1; 863 | | | s := s + 1; 864 | | | s := s + 1; 865 | | | s := s + 1; 866 | | | s := s + 1; 867 | | | s := s + 1; 868 | | | s := s + 1; 869 | | | s := s + 1; 870 | | | s := s + 1; 871 | | | s := s + 1; 872 | | | s := s + 1; 873 | | | s := s + 1; 874 | | | s := s + 1; 875 | | | s := s + 1; 876 | | | s := s + 1; 877 | | | s := s + 1; 878 | | | s := s + 1; 879 | | | s := s + 1; 880 | | | s := s + 1; 881 | | | s := s + 1; 882 | | | s := s + 1; 883 | | | s := s + 1; 884 | | | s := s + 1; 885 | | | s := s + 1; 886 | | | s := s + 1; 887 | | | s := s + 1; 888 | | | s := s + 1; 889 | | | s := s + 1; 890 | | | s := s + 1; 891 | | | s := s + 1; 892 | | | s := s + 1; 893 | | | s := s + 1; 894 | | | s := s + 1; 895 | | | s := s + 1; 896 | | | s := s + 1; 897 | | | s := s + 1; 898 | | | s := s + 1; 899 | | | s := s + 1; 900 | | | s := s + 1; 901 | | | s := s + 1; 902 | | | s := s + 1; 903 | | | s := s + 1; 904 | | | s := s + 1; 905 | | | s := s + 1; 906 | | | s := s + 1; 907 | | | s := s + 1; 908 | | | s := s + 1; 909 | | | s := s + 1; 910 | | | s := s + 1; 911 | | | s := s + 1; 912 | | | s := s + 1; 913 | | | s := s + 1; 914 | | | s := s + 1; 915 | | | s := s + 1; 916 | | | s := s + 1; 917 | | | s := s + 1; 918 | | | s := s + 1; 919 | | | s := s + 1; 920 | | | s := s + 1; 921 | | | s := s + 1; 922 | | | s := s + 1; 923 | | | s := s + 1; 924 | | | s := s + 1; 925 | | | s := s + 1; 926 | | | s := s + 1; 927 | | | s := s + 1; 928 | | | s := s + 1; 929 | | | s := s + 1; 930 | | | s := s + 1; 931 | | | s := s + 1; 932 | | | s := s + 1; 933 | | | s := s + 1; 934 | | | s := s + 1; 935 | | | s := s + 1; 936 | | | s := s + 1; 937 | | | s := s + 1; 938 | | | s := s + 1; 939 | | | s := s + 1; 940 | | | s := s + 1; 941 | | | s := s + 1; 942 | | | s := s + 1; 943 | | | s := s + 1; 944 | | | s := s + 1; 945 | | | s := s + 1; 946 | | | s := s + 1; 947 | | | s := s + 1; 948 | | | s := s + 1; 949 | | | s := s + 1; 950 | | | s := s + 1; 951 | | | s := s + 1; 952 | | | s := s + 1; 953 | | | s := s + 1; 954 | | | s := s + 1; 955 | | | s := s + 1; 956 | | | s := s + 1; 957 | | | s := s + 1; 958 | | | s := s + 1; 959 | | | s := s + 1; 960 | | | s := s + 1; 961 | | | s := s + 1; 962 | | | s := s + 1; 963 | | | s := s + 1; 964 | | | s := s + 1; 965 | | | s := s + 1; 966 | | | s := s + 1; 967 | | | s := s + 1; 968 | | | s := s + 1; 969 | | | s := s + 1; 970 | | | s := s + 1; 971 | | | s := s + 1; 972 | | | s := s + 1; 973 | | | s := s + 1; 974 | | | s := s + 1; 975 | | | s := s + 1; 976 | | | s := s + 1; 977 | | | s := s + 1; 978 | | | s := s + 1; 979 | | | s := s + 1; 980 | | | s := s + 1; 981 | | | s := s + 1; 982 | | | s := s + 1; 983 | | | s := s + 1; 984 | | | s := s + 1; 985 | | | s := s + 1; 986 | | | s := s + 1; 987 | | | s := s + 1; 988 | | | s := s + 1; 989 | | | s := s + 1; 990 | | | s := s + 1; 991 | | | s := s + 1; 992 | | | s := s + 1; 993 | | | s := s + 1; 994 | | | s := s + 1; 995 | | | s := s + 1; 996 | | | s := s + 1; 997 | | | s := s + 1; 998 | | | s := s + 1; 999 | | | s := s + 1; 1000 | | | s := s + 1; 1001 | | | s := s + 1; 1002 | | | s := s + 1; 1003 | | | s := s + 1; 1004 | | | s := s + 1; 1005 | | | s := s + 1; 1006 | | | s := s + 1; 1007 | | | s := s + 1; 1008 | | | s := s + 1; 1009 | | | s := s + 1; 1010 | | | s := s + 1; 1011 | | | s := s + 1; 1012 | | | s := s + 1; 1013 | | | s := s + 1; 1014 | | | s := s + 1; 1015 | | | s := s + 1; 1016 | | | s := s + 1; 1017 | | | s := s + 1; 1018 | | | s := s + 1; 1019 | | | s := s + 1; 1020 | | | s := s + 1; 1021 | | | s := s + 1; 1022 | | | s := s + 1; 1023 | | | s := s + 1; 1024 | | | s := s + 1; 1025 | | | s := s + 1; 1026 | | | s := s + 1; 1027 | | | s := s + 1; 1028 | | | s := s + 1; 1029 | | | s := s + 1; 1030 | | | s := s + 1; 1031 | | | s := s + 1; 1032 | | | s := s + 1; 1033 | | | s := s + 1; 1034 | | | s := s + 1; 1035 | | | s := s + 1; 1036 | | | s := s + 1; 1037 | | | s := s + 1; 1038 | | | end loop; 1039 | | | end loop; 1040 | | | j := j + 1; 1041 | | | end loop; 1042 | | | exception when others then 1043 | | | raise 'reraised exception %', sqlerrm; 1044 | | | end; 1045 | | | return $1; 1046 | | | end; (1046 rows) set plpgsql_check.profiler = on; select longfx(10); longfx -------- 10 (1 row) select longfx(10); longfx -------- 10 (1 row) set plpgsql_check.profiler = off; select longfx(10); longfx -------- 10 (1 row) select lineno, stmt_lineno, exec_stmts, source from plpgsql_profiler_function_tb('longfx'); lineno | stmt_lineno | exec_stmts | source --------+-------------+------------+----------------------------------------------------- 1 | | | 2 | | | declare 3 | | | s int default 0; 4 | | | j int default 0; 5 | | | r record; 6 | 6 | 2 | begin 7 | 7 | 2 | begin 8 | 8 | 2 | while j < 10 9 | | | loop 10 | 10 | 20 | for i in 1..1 11 | | | loop 12 | 12 | 20 | for r in select * from generate_series(1,1) 13 | | | loop 14 | 14 | 20 | s := s + 1; 15 | 15 | 20 | s := s + 1; 16 | 16 | 20 | s := s + 1; 17 | 17 | 20 | s := s + 1; 18 | 18 | 20 | s := s + 1; 19 | 19 | 20 | s := s + 1; 20 | 20 | 20 | s := s + 1; 21 | 21 | 20 | s := s + 1; 22 | 22 | 20 | s := s + 1; 23 | 23 | 20 | s := s + 1; 24 | 24 | 20 | s := s + 1; 25 | 25 | 20 | s := s + 1; 26 | 26 | 20 | s := s + 1; 27 | 27 | 20 | s := s + 1; 28 | 28 | 20 | s := s + 1; 29 | 29 | 20 | s := s + 1; 30 | 30 | 20 | s := s + 1; 31 | 31 | 20 | s := s + 1; 32 | 32 | 20 | s := s + 1; 33 | 33 | 20 | s := s + 1; 34 | 34 | 20 | s := s + 1; 35 | 35 | 20 | s := s + 1; 36 | 36 | 20 | s := s + 1; 37 | 37 | 20 | s := s + 1; 38 | 38 | 20 | s := s + 1; 39 | 39 | 20 | s := s + 1; 40 | 40 | 20 | s := s + 1; 41 | 41 | 20 | s := s + 1; 42 | 42 | 20 | s := s + 1; 43 | 43 | 20 | s := s + 1; 44 | 44 | 20 | s := s + 1; 45 | 45 | 20 | s := s + 1; 46 | 46 | 20 | s := s + 1; 47 | 47 | 20 | s := s + 1; 48 | 48 | 20 | s := s + 1; 49 | 49 | 20 | s := s + 1; 50 | 50 | 20 | s := s + 1; 51 | 51 | 20 | s := s + 1; 52 | 52 | 20 | s := s + 1; 53 | 53 | 20 | s := s + 1; 54 | 54 | 20 | s := s + 1; 55 | 55 | 20 | s := s + 1; 56 | 56 | 20 | s := s + 1; 57 | 57 | 20 | s := s + 1; 58 | 58 | 20 | s := s + 1; 59 | 59 | 20 | s := s + 1; 60 | 60 | 20 | s := s + 1; 61 | 61 | 20 | s := s + 1; 62 | 62 | 20 | s := s + 1; 63 | 63 | 20 | s := s + 1; 64 | 64 | 20 | s := s + 1; 65 | 65 | 20 | s := s + 1; 66 | 66 | 20 | s := s + 1; 67 | 67 | 20 | s := s + 1; 68 | 68 | 20 | s := s + 1; 69 | 69 | 20 | s := s + 1; 70 | 70 | 20 | s := s + 1; 71 | 71 | 20 | s := s + 1; 72 | 72 | 20 | s := s + 1; 73 | 73 | 20 | s := s + 1; 74 | 74 | 20 | s := s + 1; 75 | 75 | 20 | s := s + 1; 76 | 76 | 20 | s := s + 1; 77 | 77 | 20 | s := s + 1; 78 | 78 | 20 | s := s + 1; 79 | 79 | 20 | s := s + 1; 80 | 80 | 20 | s := s + 1; 81 | 81 | 20 | s := s + 1; 82 | 82 | 20 | s := s + 1; 83 | 83 | 20 | s := s + 1; 84 | 84 | 20 | s := s + 1; 85 | 85 | 20 | s := s + 1; 86 | 86 | 20 | s := s + 1; 87 | 87 | 20 | s := s + 1; 88 | 88 | 20 | s := s + 1; 89 | 89 | 20 | s := s + 1; 90 | 90 | 20 | s := s + 1; 91 | 91 | 20 | s := s + 1; 92 | 92 | 20 | s := s + 1; 93 | 93 | 20 | s := s + 1; 94 | 94 | 20 | s := s + 1; 95 | 95 | 20 | s := s + 1; 96 | 96 | 20 | s := s + 1; 97 | 97 | 20 | s := s + 1; 98 | 98 | 20 | s := s + 1; 99 | 99 | 20 | s := s + 1; 100 | 100 | 20 | s := s + 1; 101 | 101 | 20 | s := s + 1; 102 | 102 | 20 | s := s + 1; 103 | 103 | 20 | s := s + 1; 104 | 104 | 20 | s := s + 1; 105 | 105 | 20 | s := s + 1; 106 | 106 | 20 | s := s + 1; 107 | 107 | 20 | s := s + 1; 108 | 108 | 20 | s := s + 1; 109 | 109 | 20 | s := s + 1; 110 | 110 | 20 | s := s + 1; 111 | 111 | 20 | s := s + 1; 112 | 112 | 20 | s := s + 1; 113 | 113 | 20 | s := s + 1; 114 | 114 | 20 | s := s + 1; 115 | 115 | 20 | s := s + 1; 116 | 116 | 20 | s := s + 1; 117 | 117 | 20 | s := s + 1; 118 | 118 | 20 | s := s + 1; 119 | 119 | 20 | s := s + 1; 120 | 120 | 20 | s := s + 1; 121 | 121 | 20 | s := s + 1; 122 | 122 | 20 | s := s + 1; 123 | 123 | 20 | s := s + 1; 124 | 124 | 20 | s := s + 1; 125 | 125 | 20 | s := s + 1; 126 | 126 | 20 | s := s + 1; 127 | 127 | 20 | s := s + 1; 128 | 128 | 20 | s := s + 1; 129 | 129 | 20 | s := s + 1; 130 | 130 | 20 | s := s + 1; 131 | 131 | 20 | s := s + 1; 132 | 132 | 20 | s := s + 1; 133 | 133 | 20 | s := s + 1; 134 | 134 | 20 | s := s + 1; 135 | 135 | 20 | s := s + 1; 136 | 136 | 20 | s := s + 1; 137 | 137 | 20 | s := s + 1; 138 | 138 | 20 | s := s + 1; 139 | 139 | 20 | s := s + 1; 140 | 140 | 20 | s := s + 1; 141 | 141 | 20 | s := s + 1; 142 | 142 | 20 | s := s + 1; 143 | 143 | 20 | s := s + 1; 144 | 144 | 20 | s := s + 1; 145 | 145 | 20 | s := s + 1; 146 | 146 | 20 | s := s + 1; 147 | 147 | 20 | s := s + 1; 148 | 148 | 20 | s := s + 1; 149 | 149 | 20 | s := s + 1; 150 | 150 | 20 | s := s + 1; 151 | 151 | 20 | s := s + 1; 152 | 152 | 20 | s := s + 1; 153 | 153 | 20 | s := s + 1; 154 | 154 | 20 | s := s + 1; 155 | 155 | 20 | s := s + 1; 156 | 156 | 20 | s := s + 1; 157 | 157 | 20 | s := s + 1; 158 | 158 | 20 | s := s + 1; 159 | 159 | 20 | s := s + 1; 160 | 160 | 20 | s := s + 1; 161 | 161 | 20 | s := s + 1; 162 | 162 | 20 | s := s + 1; 163 | 163 | 20 | s := s + 1; 164 | 164 | 20 | s := s + 1; 165 | 165 | 20 | s := s + 1; 166 | 166 | 20 | s := s + 1; 167 | 167 | 20 | s := s + 1; 168 | 168 | 20 | s := s + 1; 169 | 169 | 20 | s := s + 1; 170 | 170 | 20 | s := s + 1; 171 | 171 | 20 | s := s + 1; 172 | 172 | 20 | s := s + 1; 173 | 173 | 20 | s := s + 1; 174 | 174 | 20 | s := s + 1; 175 | 175 | 20 | s := s + 1; 176 | 176 | 20 | s := s + 1; 177 | 177 | 20 | s := s + 1; 178 | 178 | 20 | s := s + 1; 179 | 179 | 20 | s := s + 1; 180 | 180 | 20 | s := s + 1; 181 | 181 | 20 | s := s + 1; 182 | 182 | 20 | s := s + 1; 183 | 183 | 20 | s := s + 1; 184 | 184 | 20 | s := s + 1; 185 | 185 | 20 | s := s + 1; 186 | 186 | 20 | s := s + 1; 187 | 187 | 20 | s := s + 1; 188 | 188 | 20 | s := s + 1; 189 | 189 | 20 | s := s + 1; 190 | 190 | 20 | s := s + 1; 191 | 191 | 20 | s := s + 1; 192 | 192 | 20 | s := s + 1; 193 | 193 | 20 | s := s + 1; 194 | 194 | 20 | s := s + 1; 195 | 195 | 20 | s := s + 1; 196 | 196 | 20 | s := s + 1; 197 | 197 | 20 | s := s + 1; 198 | 198 | 20 | s := s + 1; 199 | 199 | 20 | s := s + 1; 200 | 200 | 20 | s := s + 1; 201 | 201 | 20 | s := s + 1; 202 | 202 | 20 | s := s + 1; 203 | 203 | 20 | s := s + 1; 204 | 204 | 20 | s := s + 1; 205 | 205 | 20 | s := s + 1; 206 | 206 | 20 | s := s + 1; 207 | 207 | 20 | s := s + 1; 208 | 208 | 20 | s := s + 1; 209 | 209 | 20 | s := s + 1; 210 | 210 | 20 | s := s + 1; 211 | 211 | 20 | s := s + 1; 212 | 212 | 20 | s := s + 1; 213 | 213 | 20 | s := s + 1; 214 | 214 | 20 | s := s + 1; 215 | 215 | 20 | s := s + 1; 216 | 216 | 20 | s := s + 1; 217 | 217 | 20 | s := s + 1; 218 | 218 | 20 | s := s + 1; 219 | 219 | 20 | s := s + 1; 220 | 220 | 20 | s := s + 1; 221 | 221 | 20 | s := s + 1; 222 | 222 | 20 | s := s + 1; 223 | 223 | 20 | s := s + 1; 224 | 224 | 20 | s := s + 1; 225 | 225 | 20 | s := s + 1; 226 | 226 | 20 | s := s + 1; 227 | 227 | 20 | s := s + 1; 228 | 228 | 20 | s := s + 1; 229 | 229 | 20 | s := s + 1; 230 | 230 | 20 | s := s + 1; 231 | 231 | 20 | s := s + 1; 232 | 232 | 20 | s := s + 1; 233 | 233 | 20 | s := s + 1; 234 | 234 | 20 | s := s + 1; 235 | 235 | 20 | s := s + 1; 236 | 236 | 20 | s := s + 1; 237 | 237 | 20 | s := s + 1; 238 | 238 | 20 | s := s + 1; 239 | 239 | 20 | s := s + 1; 240 | 240 | 20 | s := s + 1; 241 | 241 | 20 | s := s + 1; 242 | 242 | 20 | s := s + 1; 243 | 243 | 20 | s := s + 1; 244 | 244 | 20 | s := s + 1; 245 | 245 | 20 | s := s + 1; 246 | 246 | 20 | s := s + 1; 247 | 247 | 20 | s := s + 1; 248 | 248 | 20 | s := s + 1; 249 | 249 | 20 | s := s + 1; 250 | 250 | 20 | s := s + 1; 251 | 251 | 20 | s := s + 1; 252 | 252 | 20 | s := s + 1; 253 | 253 | 20 | s := s + 1; 254 | 254 | 20 | s := s + 1; 255 | 255 | 20 | s := s + 1; 256 | 256 | 20 | s := s + 1; 257 | 257 | 20 | s := s + 1; 258 | 258 | 20 | s := s + 1; 259 | 259 | 20 | s := s + 1; 260 | 260 | 20 | s := s + 1; 261 | 261 | 20 | s := s + 1; 262 | 262 | 20 | s := s + 1; 263 | 263 | 20 | s := s + 1; 264 | 264 | 20 | s := s + 1; 265 | 265 | 20 | s := s + 1; 266 | 266 | 20 | s := s + 1; 267 | 267 | 20 | s := s + 1; 268 | 268 | 20 | s := s + 1; 269 | 269 | 20 | s := s + 1; 270 | 270 | 20 | s := s + 1; 271 | 271 | 20 | s := s + 1; 272 | 272 | 20 | s := s + 1; 273 | 273 | 20 | s := s + 1; 274 | 274 | 20 | s := s + 1; 275 | 275 | 20 | s := s + 1; 276 | 276 | 20 | s := s + 1; 277 | 277 | 20 | s := s + 1; 278 | 278 | 20 | s := s + 1; 279 | 279 | 20 | s := s + 1; 280 | 280 | 20 | s := s + 1; 281 | 281 | 20 | s := s + 1; 282 | 282 | 20 | s := s + 1; 283 | 283 | 20 | s := s + 1; 284 | 284 | 20 | s := s + 1; 285 | 285 | 20 | s := s + 1; 286 | 286 | 20 | s := s + 1; 287 | 287 | 20 | s := s + 1; 288 | 288 | 20 | s := s + 1; 289 | 289 | 20 | s := s + 1; 290 | 290 | 20 | s := s + 1; 291 | 291 | 20 | s := s + 1; 292 | 292 | 20 | s := s + 1; 293 | 293 | 20 | s := s + 1; 294 | 294 | 20 | s := s + 1; 295 | 295 | 20 | s := s + 1; 296 | 296 | 20 | s := s + 1; 297 | 297 | 20 | s := s + 1; 298 | 298 | 20 | s := s + 1; 299 | 299 | 20 | s := s + 1; 300 | 300 | 20 | s := s + 1; 301 | 301 | 20 | s := s + 1; 302 | 302 | 20 | s := s + 1; 303 | 303 | 20 | s := s + 1; 304 | 304 | 20 | s := s + 1; 305 | 305 | 20 | s := s + 1; 306 | 306 | 20 | s := s + 1; 307 | 307 | 20 | s := s + 1; 308 | 308 | 20 | s := s + 1; 309 | 309 | 20 | s := s + 1; 310 | 310 | 20 | s := s + 1; 311 | 311 | 20 | s := s + 1; 312 | 312 | 20 | s := s + 1; 313 | 313 | 20 | s := s + 1; 314 | 314 | 20 | s := s + 1; 315 | 315 | 20 | s := s + 1; 316 | 316 | 20 | s := s + 1; 317 | 317 | 20 | s := s + 1; 318 | 318 | 20 | s := s + 1; 319 | 319 | 20 | s := s + 1; 320 | 320 | 20 | s := s + 1; 321 | 321 | 20 | s := s + 1; 322 | 322 | 20 | s := s + 1; 323 | 323 | 20 | s := s + 1; 324 | 324 | 20 | s := s + 1; 325 | 325 | 20 | s := s + 1; 326 | 326 | 20 | s := s + 1; 327 | 327 | 20 | s := s + 1; 328 | 328 | 20 | s := s + 1; 329 | 329 | 20 | s := s + 1; 330 | 330 | 20 | s := s + 1; 331 | 331 | 20 | s := s + 1; 332 | 332 | 20 | s := s + 1; 333 | 333 | 20 | s := s + 1; 334 | 334 | 20 | s := s + 1; 335 | 335 | 20 | s := s + 1; 336 | 336 | 20 | s := s + 1; 337 | 337 | 20 | s := s + 1; 338 | 338 | 20 | s := s + 1; 339 | 339 | 20 | s := s + 1; 340 | 340 | 20 | s := s + 1; 341 | 341 | 20 | s := s + 1; 342 | 342 | 20 | s := s + 1; 343 | 343 | 20 | s := s + 1; 344 | 344 | 20 | s := s + 1; 345 | 345 | 20 | s := s + 1; 346 | 346 | 20 | s := s + 1; 347 | 347 | 20 | s := s + 1; 348 | 348 | 20 | s := s + 1; 349 | 349 | 20 | s := s + 1; 350 | 350 | 20 | s := s + 1; 351 | 351 | 20 | s := s + 1; 352 | 352 | 20 | s := s + 1; 353 | 353 | 20 | s := s + 1; 354 | 354 | 20 | s := s + 1; 355 | 355 | 20 | s := s + 1; 356 | 356 | 20 | s := s + 1; 357 | 357 | 20 | s := s + 1; 358 | 358 | 20 | s := s + 1; 359 | 359 | 20 | s := s + 1; 360 | 360 | 20 | s := s + 1; 361 | 361 | 20 | s := s + 1; 362 | 362 | 20 | s := s + 1; 363 | 363 | 20 | s := s + 1; 364 | 364 | 20 | s := s + 1; 365 | 365 | 20 | s := s + 1; 366 | 366 | 20 | s := s + 1; 367 | 367 | 20 | s := s + 1; 368 | 368 | 20 | s := s + 1; 369 | 369 | 20 | s := s + 1; 370 | 370 | 20 | s := s + 1; 371 | 371 | 20 | s := s + 1; 372 | 372 | 20 | s := s + 1; 373 | 373 | 20 | s := s + 1; 374 | 374 | 20 | s := s + 1; 375 | 375 | 20 | s := s + 1; 376 | 376 | 20 | s := s + 1; 377 | 377 | 20 | s := s + 1; 378 | 378 | 20 | s := s + 1; 379 | 379 | 20 | s := s + 1; 380 | 380 | 20 | s := s + 1; 381 | 381 | 20 | s := s + 1; 382 | 382 | 20 | s := s + 1; 383 | 383 | 20 | s := s + 1; 384 | 384 | 20 | s := s + 1; 385 | 385 | 20 | s := s + 1; 386 | 386 | 20 | s := s + 1; 387 | 387 | 20 | s := s + 1; 388 | 388 | 20 | s := s + 1; 389 | 389 | 20 | s := s + 1; 390 | 390 | 20 | s := s + 1; 391 | 391 | 20 | s := s + 1; 392 | 392 | 20 | s := s + 1; 393 | 393 | 20 | s := s + 1; 394 | 394 | 20 | s := s + 1; 395 | 395 | 20 | s := s + 1; 396 | 396 | 20 | s := s + 1; 397 | 397 | 20 | s := s + 1; 398 | 398 | 20 | s := s + 1; 399 | 399 | 20 | s := s + 1; 400 | 400 | 20 | s := s + 1; 401 | 401 | 20 | s := s + 1; 402 | 402 | 20 | s := s + 1; 403 | 403 | 20 | s := s + 1; 404 | 404 | 20 | s := s + 1; 405 | 405 | 20 | s := s + 1; 406 | 406 | 20 | s := s + 1; 407 | 407 | 20 | s := s + 1; 408 | 408 | 20 | s := s + 1; 409 | 409 | 20 | s := s + 1; 410 | 410 | 20 | s := s + 1; 411 | 411 | 20 | s := s + 1; 412 | 412 | 20 | s := s + 1; 413 | 413 | 20 | s := s + 1; 414 | 414 | 20 | s := s + 1; 415 | 415 | 20 | s := s + 1; 416 | 416 | 20 | s := s + 1; 417 | 417 | 20 | s := s + 1; 418 | 418 | 20 | s := s + 1; 419 | 419 | 20 | s := s + 1; 420 | 420 | 20 | s := s + 1; 421 | 421 | 20 | s := s + 1; 422 | 422 | 20 | s := s + 1; 423 | 423 | 20 | s := s + 1; 424 | 424 | 20 | s := s + 1; 425 | 425 | 20 | s := s + 1; 426 | 426 | 20 | s := s + 1; 427 | 427 | 20 | s := s + 1; 428 | 428 | 20 | s := s + 1; 429 | 429 | 20 | s := s + 1; 430 | 430 | 20 | s := s + 1; 431 | 431 | 20 | s := s + 1; 432 | 432 | 20 | s := s + 1; 433 | 433 | 20 | s := s + 1; 434 | 434 | 20 | s := s + 1; 435 | 435 | 20 | s := s + 1; 436 | 436 | 20 | s := s + 1; 437 | 437 | 20 | s := s + 1; 438 | 438 | 20 | s := s + 1; 439 | 439 | 20 | s := s + 1; 440 | 440 | 20 | s := s + 1; 441 | 441 | 20 | s := s + 1; 442 | 442 | 20 | s := s + 1; 443 | 443 | 20 | s := s + 1; 444 | 444 | 20 | s := s + 1; 445 | 445 | 20 | s := s + 1; 446 | 446 | 20 | s := s + 1; 447 | 447 | 20 | s := s + 1; 448 | 448 | 20 | s := s + 1; 449 | 449 | 20 | s := s + 1; 450 | 450 | 20 | s := s + 1; 451 | 451 | 20 | s := s + 1; 452 | 452 | 20 | s := s + 1; 453 | 453 | 20 | s := s + 1; 454 | 454 | 20 | s := s + 1; 455 | 455 | 20 | s := s + 1; 456 | 456 | 20 | s := s + 1; 457 | 457 | 20 | s := s + 1; 458 | 458 | 20 | s := s + 1; 459 | 459 | 20 | s := s + 1; 460 | 460 | 20 | s := s + 1; 461 | 461 | 20 | s := s + 1; 462 | 462 | 20 | s := s + 1; 463 | 463 | 20 | s := s + 1; 464 | 464 | 20 | s := s + 1; 465 | 465 | 20 | s := s + 1; 466 | 466 | 20 | s := s + 1; 467 | 467 | 20 | s := s + 1; 468 | 468 | 20 | s := s + 1; 469 | 469 | 20 | s := s + 1; 470 | 470 | 20 | s := s + 1; 471 | 471 | 20 | s := s + 1; 472 | 472 | 20 | s := s + 1; 473 | 473 | 20 | s := s + 1; 474 | 474 | 20 | s := s + 1; 475 | 475 | 20 | s := s + 1; 476 | 476 | 20 | s := s + 1; 477 | 477 | 20 | s := s + 1; 478 | 478 | 20 | s := s + 1; 479 | 479 | 20 | s := s + 1; 480 | 480 | 20 | s := s + 1; 481 | 481 | 20 | s := s + 1; 482 | 482 | 20 | s := s + 1; 483 | 483 | 20 | s := s + 1; 484 | 484 | 20 | s := s + 1; 485 | 485 | 20 | s := s + 1; 486 | 486 | 20 | s := s + 1; 487 | 487 | 20 | s := s + 1; 488 | 488 | 20 | s := s + 1; 489 | 489 | 20 | s := s + 1; 490 | 490 | 20 | s := s + 1; 491 | 491 | 20 | s := s + 1; 492 | 492 | 20 | s := s + 1; 493 | 493 | 20 | s := s + 1; 494 | 494 | 20 | s := s + 1; 495 | 495 | 20 | s := s + 1; 496 | 496 | 20 | s := s + 1; 497 | 497 | 20 | s := s + 1; 498 | 498 | 20 | s := s + 1; 499 | 499 | 20 | s := s + 1; 500 | 500 | 20 | s := s + 1; 501 | 501 | 20 | s := s + 1; 502 | 502 | 20 | s := s + 1; 503 | 503 | 20 | s := s + 1; 504 | 504 | 20 | s := s + 1; 505 | 505 | 20 | s := s + 1; 506 | 506 | 20 | s := s + 1; 507 | 507 | 20 | s := s + 1; 508 | 508 | 20 | s := s + 1; 509 | 509 | 20 | s := s + 1; 510 | 510 | 20 | s := s + 1; 511 | 511 | 20 | s := s + 1; 512 | 512 | 20 | s := s + 1; 513 | 513 | 20 | s := s + 1; 514 | 514 | 20 | s := s + 1; 515 | 515 | 20 | s := s + 1; 516 | 516 | 20 | s := s + 1; 517 | 517 | 20 | s := s + 1; 518 | 518 | 20 | s := s + 1; 519 | 519 | 20 | s := s + 1; 520 | 520 | 20 | s := s + 1; 521 | 521 | 20 | s := s + 1; 522 | 522 | 20 | s := s + 1; 523 | 523 | 20 | s := s + 1; 524 | 524 | 20 | s := s + 1; 525 | 525 | 20 | s := s + 1; 526 | 526 | 20 | s := s + 1; 527 | 527 | 20 | s := s + 1; 528 | 528 | 20 | s := s + 1; 529 | 529 | 20 | s := s + 1; 530 | 530 | 20 | s := s + 1; 531 | 531 | 20 | s := s + 1; 532 | 532 | 20 | s := s + 1; 533 | 533 | 20 | s := s + 1; 534 | 534 | 20 | s := s + 1; 535 | 535 | 20 | s := s + 1; 536 | 536 | 20 | s := s + 1; 537 | 537 | 20 | s := s + 1; 538 | 538 | 20 | s := s + 1; 539 | 539 | 20 | s := s + 1; 540 | 540 | 20 | s := s + 1; 541 | 541 | 20 | s := s + 1; 542 | 542 | 20 | s := s + 1; 543 | 543 | 20 | s := s + 1; 544 | 544 | 20 | s := s + 1; 545 | 545 | 20 | s := s + 1; 546 | 546 | 20 | s := s + 1; 547 | 547 | 20 | s := s + 1; 548 | 548 | 20 | s := s + 1; 549 | 549 | 20 | s := s + 1; 550 | 550 | 20 | s := s + 1; 551 | 551 | 20 | s := s + 1; 552 | 552 | 20 | s := s + 1; 553 | 553 | 20 | s := s + 1; 554 | 554 | 20 | s := s + 1; 555 | 555 | 20 | s := s + 1; 556 | 556 | 20 | s := s + 1; 557 | 557 | 20 | s := s + 1; 558 | 558 | 20 | s := s + 1; 559 | 559 | 20 | s := s + 1; 560 | 560 | 20 | s := s + 1; 561 | 561 | 20 | s := s + 1; 562 | 562 | 20 | s := s + 1; 563 | 563 | 20 | s := s + 1; 564 | 564 | 20 | s := s + 1; 565 | 565 | 20 | s := s + 1; 566 | 566 | 20 | s := s + 1; 567 | 567 | 20 | s := s + 1; 568 | 568 | 20 | s := s + 1; 569 | 569 | 20 | s := s + 1; 570 | 570 | 20 | s := s + 1; 571 | 571 | 20 | s := s + 1; 572 | 572 | 20 | s := s + 1; 573 | 573 | 20 | s := s + 1; 574 | 574 | 20 | s := s + 1; 575 | 575 | 20 | s := s + 1; 576 | 576 | 20 | s := s + 1; 577 | 577 | 20 | s := s + 1; 578 | 578 | 20 | s := s + 1; 579 | 579 | 20 | s := s + 1; 580 | 580 | 20 | s := s + 1; 581 | 581 | 20 | s := s + 1; 582 | 582 | 20 | s := s + 1; 583 | 583 | 20 | s := s + 1; 584 | 584 | 20 | s := s + 1; 585 | 585 | 20 | s := s + 1; 586 | 586 | 20 | s := s + 1; 587 | 587 | 20 | s := s + 1; 588 | 588 | 20 | s := s + 1; 589 | 589 | 20 | s := s + 1; 590 | 590 | 20 | s := s + 1; 591 | 591 | 20 | s := s + 1; 592 | 592 | 20 | s := s + 1; 593 | 593 | 20 | s := s + 1; 594 | 594 | 20 | s := s + 1; 595 | 595 | 20 | s := s + 1; 596 | 596 | 20 | s := s + 1; 597 | 597 | 20 | s := s + 1; 598 | 598 | 20 | s := s + 1; 599 | 599 | 20 | s := s + 1; 600 | 600 | 20 | s := s + 1; 601 | 601 | 20 | s := s + 1; 602 | 602 | 20 | s := s + 1; 603 | 603 | 20 | s := s + 1; 604 | 604 | 20 | s := s + 1; 605 | 605 | 20 | s := s + 1; 606 | 606 | 20 | s := s + 1; 607 | 607 | 20 | s := s + 1; 608 | 608 | 20 | s := s + 1; 609 | 609 | 20 | s := s + 1; 610 | 610 | 20 | s := s + 1; 611 | 611 | 20 | s := s + 1; 612 | 612 | 20 | s := s + 1; 613 | 613 | 20 | s := s + 1; 614 | 614 | 20 | s := s + 1; 615 | 615 | 20 | s := s + 1; 616 | 616 | 20 | s := s + 1; 617 | 617 | 20 | s := s + 1; 618 | 618 | 20 | s := s + 1; 619 | 619 | 20 | s := s + 1; 620 | 620 | 20 | s := s + 1; 621 | 621 | 20 | s := s + 1; 622 | 622 | 20 | s := s + 1; 623 | 623 | 20 | s := s + 1; 624 | 624 | 20 | s := s + 1; 625 | 625 | 20 | s := s + 1; 626 | 626 | 20 | s := s + 1; 627 | 627 | 20 | s := s + 1; 628 | 628 | 20 | s := s + 1; 629 | 629 | 20 | s := s + 1; 630 | 630 | 20 | s := s + 1; 631 | 631 | 20 | s := s + 1; 632 | 632 | 20 | s := s + 1; 633 | 633 | 20 | s := s + 1; 634 | 634 | 20 | s := s + 1; 635 | 635 | 20 | s := s + 1; 636 | 636 | 20 | s := s + 1; 637 | 637 | 20 | s := s + 1; 638 | 638 | 20 | s := s + 1; 639 | 639 | 20 | s := s + 1; 640 | 640 | 20 | s := s + 1; 641 | 641 | 20 | s := s + 1; 642 | 642 | 20 | s := s + 1; 643 | 643 | 20 | s := s + 1; 644 | 644 | 20 | s := s + 1; 645 | 645 | 20 | s := s + 1; 646 | 646 | 20 | s := s + 1; 647 | 647 | 20 | s := s + 1; 648 | 648 | 20 | s := s + 1; 649 | 649 | 20 | s := s + 1; 650 | 650 | 20 | s := s + 1; 651 | 651 | 20 | s := s + 1; 652 | 652 | 20 | s := s + 1; 653 | 653 | 20 | s := s + 1; 654 | 654 | 20 | s := s + 1; 655 | 655 | 20 | s := s + 1; 656 | 656 | 20 | s := s + 1; 657 | 657 | 20 | s := s + 1; 658 | 658 | 20 | s := s + 1; 659 | 659 | 20 | s := s + 1; 660 | 660 | 20 | s := s + 1; 661 | 661 | 20 | s := s + 1; 662 | 662 | 20 | s := s + 1; 663 | 663 | 20 | s := s + 1; 664 | 664 | 20 | s := s + 1; 665 | 665 | 20 | s := s + 1; 666 | 666 | 20 | s := s + 1; 667 | 667 | 20 | s := s + 1; 668 | 668 | 20 | s := s + 1; 669 | 669 | 20 | s := s + 1; 670 | 670 | 20 | s := s + 1; 671 | 671 | 20 | s := s + 1; 672 | 672 | 20 | s := s + 1; 673 | 673 | 20 | s := s + 1; 674 | 674 | 20 | s := s + 1; 675 | 675 | 20 | s := s + 1; 676 | 676 | 20 | s := s + 1; 677 | 677 | 20 | s := s + 1; 678 | 678 | 20 | s := s + 1; 679 | 679 | 20 | s := s + 1; 680 | 680 | 20 | s := s + 1; 681 | 681 | 20 | s := s + 1; 682 | 682 | 20 | s := s + 1; 683 | 683 | 20 | s := s + 1; 684 | 684 | 20 | s := s + 1; 685 | 685 | 20 | s := s + 1; 686 | 686 | 20 | s := s + 1; 687 | 687 | 20 | s := s + 1; 688 | 688 | 20 | s := s + 1; 689 | 689 | 20 | s := s + 1; 690 | 690 | 20 | s := s + 1; 691 | 691 | 20 | s := s + 1; 692 | 692 | 20 | s := s + 1; 693 | 693 | 20 | s := s + 1; 694 | 694 | 20 | s := s + 1; 695 | 695 | 20 | s := s + 1; 696 | 696 | 20 | s := s + 1; 697 | 697 | 20 | s := s + 1; 698 | 698 | 20 | s := s + 1; 699 | 699 | 20 | s := s + 1; 700 | 700 | 20 | s := s + 1; 701 | 701 | 20 | s := s + 1; 702 | 702 | 20 | s := s + 1; 703 | 703 | 20 | s := s + 1; 704 | 704 | 20 | s := s + 1; 705 | 705 | 20 | s := s + 1; 706 | 706 | 20 | s := s + 1; 707 | 707 | 20 | s := s + 1; 708 | 708 | 20 | s := s + 1; 709 | 709 | 20 | s := s + 1; 710 | 710 | 20 | s := s + 1; 711 | 711 | 20 | s := s + 1; 712 | 712 | 20 | s := s + 1; 713 | 713 | 20 | s := s + 1; 714 | 714 | 20 | s := s + 1; 715 | 715 | 20 | s := s + 1; 716 | 716 | 20 | s := s + 1; 717 | 717 | 20 | s := s + 1; 718 | 718 | 20 | s := s + 1; 719 | 719 | 20 | s := s + 1; 720 | 720 | 20 | s := s + 1; 721 | 721 | 20 | s := s + 1; 722 | 722 | 20 | s := s + 1; 723 | 723 | 20 | s := s + 1; 724 | 724 | 20 | s := s + 1; 725 | 725 | 20 | s := s + 1; 726 | 726 | 20 | s := s + 1; 727 | 727 | 20 | s := s + 1; 728 | 728 | 20 | s := s + 1; 729 | 729 | 20 | s := s + 1; 730 | 730 | 20 | s := s + 1; 731 | 731 | 20 | s := s + 1; 732 | 732 | 20 | s := s + 1; 733 | 733 | 20 | s := s + 1; 734 | 734 | 20 | s := s + 1; 735 | 735 | 20 | s := s + 1; 736 | 736 | 20 | s := s + 1; 737 | 737 | 20 | s := s + 1; 738 | 738 | 20 | s := s + 1; 739 | 739 | 20 | s := s + 1; 740 | 740 | 20 | s := s + 1; 741 | 741 | 20 | s := s + 1; 742 | 742 | 20 | s := s + 1; 743 | 743 | 20 | s := s + 1; 744 | 744 | 20 | s := s + 1; 745 | 745 | 20 | s := s + 1; 746 | 746 | 20 | s := s + 1; 747 | 747 | 20 | s := s + 1; 748 | 748 | 20 | s := s + 1; 749 | 749 | 20 | s := s + 1; 750 | 750 | 20 | s := s + 1; 751 | 751 | 20 | s := s + 1; 752 | 752 | 20 | s := s + 1; 753 | 753 | 20 | s := s + 1; 754 | 754 | 20 | s := s + 1; 755 | 755 | 20 | s := s + 1; 756 | 756 | 20 | s := s + 1; 757 | 757 | 20 | s := s + 1; 758 | 758 | 20 | s := s + 1; 759 | 759 | 20 | s := s + 1; 760 | 760 | 20 | s := s + 1; 761 | 761 | 20 | s := s + 1; 762 | 762 | 20 | s := s + 1; 763 | 763 | 20 | s := s + 1; 764 | 764 | 20 | s := s + 1; 765 | 765 | 20 | s := s + 1; 766 | 766 | 20 | s := s + 1; 767 | 767 | 20 | s := s + 1; 768 | 768 | 20 | s := s + 1; 769 | 769 | 20 | s := s + 1; 770 | 770 | 20 | s := s + 1; 771 | 771 | 20 | s := s + 1; 772 | 772 | 20 | s := s + 1; 773 | 773 | 20 | s := s + 1; 774 | 774 | 20 | s := s + 1; 775 | 775 | 20 | s := s + 1; 776 | 776 | 20 | s := s + 1; 777 | 777 | 20 | s := s + 1; 778 | 778 | 20 | s := s + 1; 779 | 779 | 20 | s := s + 1; 780 | 780 | 20 | s := s + 1; 781 | 781 | 20 | s := s + 1; 782 | 782 | 20 | s := s + 1; 783 | 783 | 20 | s := s + 1; 784 | 784 | 20 | s := s + 1; 785 | 785 | 20 | s := s + 1; 786 | 786 | 20 | s := s + 1; 787 | 787 | 20 | s := s + 1; 788 | 788 | 20 | s := s + 1; 789 | 789 | 20 | s := s + 1; 790 | 790 | 20 | s := s + 1; 791 | 791 | 20 | s := s + 1; 792 | 792 | 20 | s := s + 1; 793 | 793 | 20 | s := s + 1; 794 | 794 | 20 | s := s + 1; 795 | 795 | 20 | s := s + 1; 796 | 796 | 20 | s := s + 1; 797 | 797 | 20 | s := s + 1; 798 | 798 | 20 | s := s + 1; 799 | 799 | 20 | s := s + 1; 800 | 800 | 20 | s := s + 1; 801 | 801 | 20 | s := s + 1; 802 | 802 | 20 | s := s + 1; 803 | 803 | 20 | s := s + 1; 804 | 804 | 20 | s := s + 1; 805 | 805 | 20 | s := s + 1; 806 | 806 | 20 | s := s + 1; 807 | 807 | 20 | s := s + 1; 808 | 808 | 20 | s := s + 1; 809 | 809 | 20 | s := s + 1; 810 | 810 | 20 | s := s + 1; 811 | 811 | 20 | s := s + 1; 812 | 812 | 20 | s := s + 1; 813 | 813 | 20 | s := s + 1; 814 | 814 | 20 | s := s + 1; 815 | 815 | 20 | s := s + 1; 816 | 816 | 20 | s := s + 1; 817 | 817 | 20 | s := s + 1; 818 | 818 | 20 | s := s + 1; 819 | 819 | 20 | s := s + 1; 820 | 820 | 20 | s := s + 1; 821 | 821 | 20 | s := s + 1; 822 | 822 | 20 | s := s + 1; 823 | 823 | 20 | s := s + 1; 824 | 824 | 20 | s := s + 1; 825 | 825 | 20 | s := s + 1; 826 | 826 | 20 | s := s + 1; 827 | 827 | 20 | s := s + 1; 828 | 828 | 20 | s := s + 1; 829 | 829 | 20 | s := s + 1; 830 | 830 | 20 | s := s + 1; 831 | 831 | 20 | s := s + 1; 832 | 832 | 20 | s := s + 1; 833 | 833 | 20 | s := s + 1; 834 | 834 | 20 | s := s + 1; 835 | 835 | 20 | s := s + 1; 836 | 836 | 20 | s := s + 1; 837 | 837 | 20 | s := s + 1; 838 | 838 | 20 | s := s + 1; 839 | 839 | 20 | s := s + 1; 840 | 840 | 20 | s := s + 1; 841 | 841 | 20 | s := s + 1; 842 | 842 | 20 | s := s + 1; 843 | 843 | 20 | s := s + 1; 844 | 844 | 20 | s := s + 1; 845 | 845 | 20 | s := s + 1; 846 | 846 | 20 | s := s + 1; 847 | 847 | 20 | s := s + 1; 848 | 848 | 20 | s := s + 1; 849 | 849 | 20 | s := s + 1; 850 | 850 | 20 | s := s + 1; 851 | 851 | 20 | s := s + 1; 852 | 852 | 20 | s := s + 1; 853 | 853 | 20 | s := s + 1; 854 | 854 | 20 | s := s + 1; 855 | 855 | 20 | s := s + 1; 856 | 856 | 20 | s := s + 1; 857 | 857 | 20 | s := s + 1; 858 | 858 | 20 | s := s + 1; 859 | 859 | 20 | s := s + 1; 860 | 860 | 20 | s := s + 1; 861 | 861 | 20 | s := s + 1; 862 | 862 | 20 | s := s + 1; 863 | 863 | 20 | s := s + 1; 864 | 864 | 20 | s := s + 1; 865 | 865 | 20 | s := s + 1; 866 | 866 | 20 | s := s + 1; 867 | 867 | 20 | s := s + 1; 868 | 868 | 20 | s := s + 1; 869 | 869 | 20 | s := s + 1; 870 | 870 | 20 | s := s + 1; 871 | 871 | 20 | s := s + 1; 872 | 872 | 20 | s := s + 1; 873 | 873 | 20 | s := s + 1; 874 | 874 | 20 | s := s + 1; 875 | 875 | 20 | s := s + 1; 876 | 876 | 20 | s := s + 1; 877 | 877 | 20 | s := s + 1; 878 | 878 | 20 | s := s + 1; 879 | 879 | 20 | s := s + 1; 880 | 880 | 20 | s := s + 1; 881 | 881 | 20 | s := s + 1; 882 | 882 | 20 | s := s + 1; 883 | 883 | 20 | s := s + 1; 884 | 884 | 20 | s := s + 1; 885 | 885 | 20 | s := s + 1; 886 | 886 | 20 | s := s + 1; 887 | 887 | 20 | s := s + 1; 888 | 888 | 20 | s := s + 1; 889 | 889 | 20 | s := s + 1; 890 | 890 | 20 | s := s + 1; 891 | 891 | 20 | s := s + 1; 892 | 892 | 20 | s := s + 1; 893 | 893 | 20 | s := s + 1; 894 | 894 | 20 | s := s + 1; 895 | 895 | 20 | s := s + 1; 896 | 896 | 20 | s := s + 1; 897 | 897 | 20 | s := s + 1; 898 | 898 | 20 | s := s + 1; 899 | 899 | 20 | s := s + 1; 900 | 900 | 20 | s := s + 1; 901 | 901 | 20 | s := s + 1; 902 | 902 | 20 | s := s + 1; 903 | 903 | 20 | s := s + 1; 904 | 904 | 20 | s := s + 1; 905 | 905 | 20 | s := s + 1; 906 | 906 | 20 | s := s + 1; 907 | 907 | 20 | s := s + 1; 908 | 908 | 20 | s := s + 1; 909 | 909 | 20 | s := s + 1; 910 | 910 | 20 | s := s + 1; 911 | 911 | 20 | s := s + 1; 912 | 912 | 20 | s := s + 1; 913 | 913 | 20 | s := s + 1; 914 | 914 | 20 | s := s + 1; 915 | 915 | 20 | s := s + 1; 916 | 916 | 20 | s := s + 1; 917 | 917 | 20 | s := s + 1; 918 | 918 | 20 | s := s + 1; 919 | 919 | 20 | s := s + 1; 920 | 920 | 20 | s := s + 1; 921 | 921 | 20 | s := s + 1; 922 | 922 | 20 | s := s + 1; 923 | 923 | 20 | s := s + 1; 924 | 924 | 20 | s := s + 1; 925 | 925 | 20 | s := s + 1; 926 | 926 | 20 | s := s + 1; 927 | 927 | 20 | s := s + 1; 928 | 928 | 20 | s := s + 1; 929 | 929 | 20 | s := s + 1; 930 | 930 | 20 | s := s + 1; 931 | 931 | 20 | s := s + 1; 932 | 932 | 20 | s := s + 1; 933 | 933 | 20 | s := s + 1; 934 | 934 | 20 | s := s + 1; 935 | 935 | 20 | s := s + 1; 936 | 936 | 20 | s := s + 1; 937 | 937 | 20 | s := s + 1; 938 | 938 | 20 | s := s + 1; 939 | 939 | 20 | s := s + 1; 940 | 940 | 20 | s := s + 1; 941 | 941 | 20 | s := s + 1; 942 | 942 | 20 | s := s + 1; 943 | 943 | 20 | s := s + 1; 944 | 944 | 20 | s := s + 1; 945 | 945 | 20 | s := s + 1; 946 | 946 | 20 | s := s + 1; 947 | 947 | 20 | s := s + 1; 948 | 948 | 20 | s := s + 1; 949 | 949 | 20 | s := s + 1; 950 | 950 | 20 | s := s + 1; 951 | 951 | 20 | s := s + 1; 952 | 952 | 20 | s := s + 1; 953 | 953 | 20 | s := s + 1; 954 | 954 | 20 | s := s + 1; 955 | 955 | 20 | s := s + 1; 956 | 956 | 20 | s := s + 1; 957 | 957 | 20 | s := s + 1; 958 | 958 | 20 | s := s + 1; 959 | 959 | 20 | s := s + 1; 960 | 960 | 20 | s := s + 1; 961 | 961 | 20 | s := s + 1; 962 | 962 | 20 | s := s + 1; 963 | 963 | 20 | s := s + 1; 964 | 964 | 20 | s := s + 1; 965 | 965 | 20 | s := s + 1; 966 | 966 | 20 | s := s + 1; 967 | 967 | 20 | s := s + 1; 968 | 968 | 20 | s := s + 1; 969 | 969 | 20 | s := s + 1; 970 | 970 | 20 | s := s + 1; 971 | 971 | 20 | s := s + 1; 972 | 972 | 20 | s := s + 1; 973 | 973 | 20 | s := s + 1; 974 | 974 | 20 | s := s + 1; 975 | 975 | 20 | s := s + 1; 976 | 976 | 20 | s := s + 1; 977 | 977 | 20 | s := s + 1; 978 | 978 | 20 | s := s + 1; 979 | 979 | 20 | s := s + 1; 980 | 980 | 20 | s := s + 1; 981 | 981 | 20 | s := s + 1; 982 | 982 | 20 | s := s + 1; 983 | 983 | 20 | s := s + 1; 984 | 984 | 20 | s := s + 1; 985 | 985 | 20 | s := s + 1; 986 | 986 | 20 | s := s + 1; 987 | 987 | 20 | s := s + 1; 988 | 988 | 20 | s := s + 1; 989 | 989 | 20 | s := s + 1; 990 | 990 | 20 | s := s + 1; 991 | 991 | 20 | s := s + 1; 992 | 992 | 20 | s := s + 1; 993 | 993 | 20 | s := s + 1; 994 | 994 | 20 | s := s + 1; 995 | 995 | 20 | s := s + 1; 996 | 996 | 20 | s := s + 1; 997 | 997 | 20 | s := s + 1; 998 | 998 | 20 | s := s + 1; 999 | 999 | 20 | s := s + 1; 1000 | 1000 | 20 | s := s + 1; 1001 | 1001 | 20 | s := s + 1; 1002 | 1002 | 20 | s := s + 1; 1003 | 1003 | 20 | s := s + 1; 1004 | 1004 | 20 | s := s + 1; 1005 | 1005 | 20 | s := s + 1; 1006 | 1006 | 20 | s := s + 1; 1007 | 1007 | 20 | s := s + 1; 1008 | 1008 | 20 | s := s + 1; 1009 | 1009 | 20 | s := s + 1; 1010 | 1010 | 20 | s := s + 1; 1011 | 1011 | 20 | s := s + 1; 1012 | 1012 | 20 | s := s + 1; 1013 | 1013 | 20 | s := s + 1; 1014 | 1014 | 20 | s := s + 1; 1015 | 1015 | 20 | s := s + 1; 1016 | 1016 | 20 | s := s + 1; 1017 | 1017 | 20 | s := s + 1; 1018 | 1018 | 20 | s := s + 1; 1019 | 1019 | 20 | s := s + 1; 1020 | 1020 | 20 | s := s + 1; 1021 | 1021 | 20 | s := s + 1; 1022 | 1022 | 20 | s := s + 1; 1023 | 1023 | 20 | s := s + 1; 1024 | 1024 | 20 | s := s + 1; 1025 | 1025 | 20 | s := s + 1; 1026 | 1026 | 20 | s := s + 1; 1027 | 1027 | 20 | s := s + 1; 1028 | 1028 | 20 | s := s + 1; 1029 | 1029 | 20 | s := s + 1; 1030 | 1030 | 20 | s := s + 1; 1031 | 1031 | 20 | s := s + 1; 1032 | 1032 | 20 | s := s + 1; 1033 | 1033 | 20 | s := s + 1; 1034 | 1034 | 20 | s := s + 1; 1035 | 1035 | 20 | s := s + 1; 1036 | 1036 | 20 | s := s + 1; 1037 | 1037 | 20 | s := s + 1; 1038 | | | end loop; 1039 | | | end loop; 1040 | 1040 | 20 | j := j + 1; 1041 | | | end loop; 1042 | | | exception when others then 1043 | 1043 | 0 | raise 'reraised exception %', sqlerrm; 1044 | | | end; 1045 | 1045 | 2 | return $1; 1046 | | | end; (1046 rows) select funcoid, exec_count from plpgsql_profiler_functions_all(); funcoid | exec_count -----------------+------------ longfx(integer) | 2 (1 row) create table testr(a int); create rule testr_rule as on insert to testr do nothing; create or replace function fx_testr() returns void as $$ begin insert into testr values(20); end; $$ language plpgsql; -- allow some rules on tables select fx_testr(); fx_testr ---------- (1 row) select * from plpgsql_check_function_tb('fx_testr'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+---------+--------+------+-------+----------+-------+--------- (0 rows) drop function fx_testr(); drop table testr; -- coverage tests set plpgsql_check.profiler to on; create or replace function covtest(int) returns int as $$ declare a int = $1; begin a := a + 1; if a < 10 then a := a + 1; end if; a := a + 1; return a; end; $$ language plpgsql; set plpgsql_check.profiler to on; select covtest(10); covtest --------- 12 (1 row) select stmtid, exec_stmts, stmtname from plpgsql_profiler_function_statements_tb('covtest'); stmtid | exec_stmts | stmtname --------+------------+----------------- 1 | 1 | statement block 2 | 1 | assignment 3 | 1 | IF 4 | 0 | assignment 5 | 1 | assignment 6 | 1 | RETURN (6 rows) select plpgsql_coverage_statements('covtest'); plpgsql_coverage_statements ----------------------------- 0.8333333333333334 (1 row) select plpgsql_coverage_branches('covtest'); plpgsql_coverage_branches --------------------------- 0.5 (1 row) select covtest(1); covtest --------- 4 (1 row) select stmtid, exec_stmts, stmtname from plpgsql_profiler_function_statements_tb('covtest'); stmtid | exec_stmts | stmtname --------+------------+----------------- 1 | 2 | statement block 2 | 2 | assignment 3 | 2 | IF 4 | 1 | assignment 5 | 2 | assignment 6 | 2 | RETURN (6 rows) select plpgsql_coverage_statements('covtest'); plpgsql_coverage_statements ----------------------------- 1 (1 row) select plpgsql_coverage_branches('covtest'); plpgsql_coverage_branches --------------------------- 1 (1 row) set plpgsql_check.profiler to off; create or replace function f() returns void as $$ declare r1 record; r2 record; begin select 10 as a, 20 as b into r1; r2 := json_populate_record(r1, '{}'); raise notice '%', r2.a; end; $$ language plpgsql; select * from plpgsql_check_function('f'); plpgsql_check_function ------------------------ (0 rows) -- fix issue #63 create or replace function distinct_array(arr anyarray) returns anyarray as $$ begin return array(select distinct e from unnest(arr) as e); end; $$ language plpgsql immutable; select plpgsql_check_function('distinct_array(anyarray)'); plpgsql_check_function ------------------------ (0 rows) drop function distinct_array(anyarray); -- tracer test set plpgsql_check.enable_tracer to on; set plpgsql_check.tracer to on; set plpgsql_check.tracer_test_mode = true; \set VERBOSITY terse create or replace function fxo(a int, b int, c date, d numeric) returns void as $$ begin insert into tracer_tab values(a,b,c,d); end; $$ language plpgsql; create table tracer_tab(a int, b int, c date, d numeric); create or replace function tracer_tab_trg_fx() returns trigger as $$ begin return new; end; $$ language plpgsql; create trigger tracer_tab_trg before insert on tracer_tab for each row execute procedure tracer_tab_trg_fx(); select fxo(10,20,'20200815', 3.14); NOTICE: #0 ->> start of function fxo(integer,integer,date,numeric) (oid=0, tnl=1) NOTICE: #0 "a" => '10', "b" => '20', "c" => '08-15-2020', "d" => '3.14' NOTICE: #2 ->> start of function tracer_tab_trg_fx() (oid=0, tnl=1) NOTICE: #2 context: SQL statement "insert into tracer_tab values(a,b,c,d)" NOTICE: #2 triggered by before row insert trigger NOTICE: #2 "new" => '(10,20,08-15-2020,3.14)' NOTICE: #2 <<- end of function tracer_tab_trg_fx (elapsed time=0.010 ms) NOTICE: #0 <<- end of function fxo (elapsed time=0.010 ms) fxo ----- (1 row) select fxo(11,21,'20200816', 6.28); NOTICE: #0 ->> start of function fxo(integer,integer,date,numeric) (oid=0, tnl=1) NOTICE: #0 "a" => '11', "b" => '21', "c" => '08-16-2020', "d" => '6.28' NOTICE: #2 ->> start of function tracer_tab_trg_fx() (oid=0, tnl=1) NOTICE: #2 context: SQL statement "insert into tracer_tab values(a,b,c,d)" NOTICE: #2 triggered by before row insert trigger NOTICE: #2 "new" => '(11,21,08-16-2020,6.28)' NOTICE: #2 <<- end of function tracer_tab_trg_fx (elapsed time=0.010 ms) NOTICE: #0 <<- end of function fxo (elapsed time=0.010 ms) fxo ----- (1 row) set plpgsql_check.enable_tracer to off; set plpgsql_check.tracer to off; drop table tracer_tab cascade; drop function tracer_tab_trg_fx(); drop function fxo(int, int, date, numeric); create or replace function foo_trg_func() returns trigger as $$ begin -- bad function, RETURN is missing end; $$ language plpgsql; create table foo(a int); create trigger foo_trg before insert for each row execute procedure foo_trg_func(); ERROR: syntax error at or near "for" at character 38 -- should to print error select * from plpgsql_check_function('foo_trg_func', 'foo'); plpgsql_check_function ------------------------------------------------------------ error:2F005:control reached end of function without RETURN (1 row) drop table foo; drop function foo_trg_func(); -- check event trigger function create or replace function f1() returns event_trigger as $$ BEGIN RAISE NOTICE 'test_event_trigger: % %', tg_event, tg_tag; END $$ language plpgsql; select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+---------+--------+------+-------+----------+-------+--------- (0 rows) -- should fail create or replace function f1() returns event_trigger as $$ BEGIN RAISE NOTICE 'test_event_trigger: % %', tg_event, tg_tagX; END $$ language plpgsql; select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+---------------------------------+--------+------+-------+----------+----------------+--------- f1 | 3 | RAISE | 42703 | column "tg_tagx" does not exist | | | error | 8 | SELECT tg_tagX | (1 row) drop function f1(); -- check event trigger function create or replace function f1() returns event_trigger as $$ BEGIN RAISE NOTICE 'test_event_trigger: % %', tg_event, tg_tag; END $$ language plpgsql; select * from plpgsql_check_function('f1()'); plpgsql_check_function ------------------------ (0 rows) -- should fail create or replace function f1() returns event_trigger as $$ BEGIN RAISE NOTICE 'test_event_trigger: % %', tg_event, tg_tagX; END $$ language plpgsql; select * from plpgsql_check_function('f1()'); plpgsql_check_function ----------------------------------------------------- error:42703:3:RAISE:column "tg_tagx" does not exist Query: SELECT tg_tagX -- ^ (3 rows) drop function f1(); create table t1tab(a int, b int); create or replace function f1() returns setof t1tab as $$ begin return next (10,20); return; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings => true); plpgsql_check_function ----------------------------------------------------------------------------------------- performance:00000:routine is marked as VOLATILE, should be IMMUTABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (2 rows) create or replace function f1() returns setof t1tab as $$ begin return next (10::numeric,20); return; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings => true); plpgsql_check_function ------------------------------------------------------------------------------------ error:42804:3:RETURN NEXT:returned record type does not match expected record type Detail: Returned type numeric does not match expected type integer in column 1. (2 rows) create or replace function f1() returns setof t1tab as $$ declare a int; b int; begin return next (a,b); return; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings => true); plpgsql_check_function ----------------------------------------------------------------------------------------- performance:00000:routine is marked as VOLATILE, should be IMMUTABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (2 rows) create or replace function f1() returns setof t1tab as $$ declare a numeric; b int; begin return next (a,b::numeric); return; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings => true); plpgsql_check_function ------------------------------------------------------------------------------------ error:42804:4:RETURN NEXT:returned record type does not match expected record type Detail: Returned type numeric does not match expected type integer in column 1. (2 rows) drop function f1(); create table t1(a int, b int); create or replace function fx() returns t2 as $$ begin return (10,20,30)::t1; end; $$ language plpgsql; select * from plpgsql_check_function('fx()', performance_warnings => true); plpgsql_check_function ---------------------------------------------------- error:42846:3:RETURN:cannot cast type record to t1 Query: SELECT (10,20,30)::t1 -- ^ Detail: Input has too many columns. (4 rows) drop function fx(); drop table t1tab; drop table t1; create or replace function fx() returns void as $$ begin assert exists(select * from foo); assert false, (select boo from boo limit 1); end; $$ language plpgsql; select * from plpgsql_check_function('fx()', fatal_errors => false); plpgsql_check_function ---------------------------------------------------- error:42P01:3:ASSERT:relation "foo" does not exist Query: SELECT exists(select * from foo) -- ^ error:42P01:4:ASSERT:relation "boo" does not exist Query: SELECT (select boo from boo limit 1) -- ^ (6 rows) create or replace function ml_trg() returns trigger as $$ #option dump declare begin if TG_OP = 'INSERT' then if NEW.status_from IS NULL then begin -- performance issue only select status into NEW.status_from from pa where pa_id = NEW.pa_id; -- nonexist target value select status into NEW.status_from_xxx from pa where pa_id = NEW.pa_id; exception when DATA_EXCEPTION then new.status_from := 'DE'; end; end if; end if; if TG_OP = 'DELETE' then return OLD; else return NEW; end if; exception when OTHERS then NULL; if TG_OP = 'DELETE' then return OLD; else return NEW; end if; end; $$ language plpgsql; select * from plpgsql_check_function('ml_trg()', 'ml', performance_warnings := true); plpgsql_check_function -------------------------------------------------------------------------- error:42703:13:SQL statement:record "new" has no field "status_from_xxx" (1 row) create or replace function fx2() returns void as $$ declare _pa pa; begin select pa.id into _pa.id from pa limit 1; select pa.pa_id into _pa.pa_id from pa limit 1; end; $$ language plpgsql; select * from plpgsql_check_function('fx2()', performance_warnings := true); plpgsql_check_function --------------------------------------------------------- warning extra:00000:2:DECLARE:never read variable "_pa" (1 row) drop function fx2(); create or replace function fx2() returns void as $$ declare _pa pa; begin _pa.id := (select pa.id from pa limit 1); _pa.pa_id := (select pa.pa_id from pa limit 1); end; $$ language plpgsql; select * from plpgsql_check_function('fx2()', performance_warnings := true); plpgsql_check_function --------------------------------------------------------- warning extra:00000:2:DECLARE:never read variable "_pa" (1 row) drop function fx2(); create type _exception_type as ( state text, message text, detail text); create or replace function f1() returns void as $$ declare _exception record; begin _exception := NULL::_exception_type; exception when others then get stacked diagnostics _exception.state = RETURNED_SQLSTATE, _exception.message = MESSAGE_TEXT, _exception.detail = PG_EXCEPTION_DETAIL, _exception.hint = PG_EXCEPTION_HINT; end; $$ language plpgsql; select f1(); f1 ---- (1 row) select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-------------------------+----------+-----------------------------------------+--------+------+-------+----------+-------+--------- f1 | 7 | GET STACKED DIAGNOSTICS | 42703 | record "_exception" has no field "hint" | | | error | | | (1 row) create or replace function f1() returns void as $$ declare _exception _exception_type; begin _exception := NULL::_exception_type; exception when others then get stacked diagnostics _exception.state = RETURNED_SQLSTATE, _exception.message = MESSAGE_TEXT, _exception.detail = PG_EXCEPTION_DETAIL; end; $$ language plpgsql; select f1(); f1 ---- (1 row) select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+----------------------------------+--------+------+---------------+----------+-------+--------- f1 | 3 | DECLARE | 00000 | never read variable "_exception" | | | warning extra | | | (1 row) drop function f1(); drop type _exception_type; create type _exception_type as ( state text, message text, detail text); create or replace function f1() returns void as $$ declare _exception record; begin _exception := NULL::_exception_type; exception when others then get stacked diagnostics _exception.state = RETURNED_SQLSTATE, _exception.message = MESSAGE_TEXT, _exception.detail = PG_EXCEPTION_DETAIL, _exception.hint = PG_EXCEPTION_HINT; end; $$ language plpgsql; select f1(); f1 ---- (1 row) select * from plpgsql_check_function('f1()'); plpgsql_check_function ------------------------------------------------------------------------------- error:42703:7:GET STACKED DIAGNOSTICS:record "_exception" has no field "hint" (1 row) drop function f1(); drop type _exception_type; create table footab(a int, b int, c int); create or replace function footab_trig_func() returns trigger as $$ declare x int; begin if false then -- should be ok; select count(*) from newtab into x; -- should fail; select count(*) from newtab where d = 10 into x; end if; return null; end; $$ language plpgsql; select * from plpgsql_check_function('footab_trig_func','footab', newtable := 'newtab'); plpgsql_check_function ------------------------------------------------------- error:42703:9:SQL statement:column "d" does not exist Query: select count(*) from newtab where d = 10 -- ^ (3 rows) drop table footab; drop function footab_trig_func(); create or replace function df1(anyelement) returns anyelement as $$ begin return $1; end; $$ language plpgsql; create or replace function df2(anyelement, jsonb) returns anyelement as $$ begin return $1; end; $$ language plpgsql; create or replace function t1() returns void as $$ declare r record; begin r := df1(r); end; $$ language plpgsql; select * from plpgsql_check_function('t1()'); plpgsql_check_function ------------------------ (0 rows) create or replace function t1() returns void as $$ declare r record; begin r := df2(r, '{}'); end; $$ language plpgsql; select * from plpgsql_check_function('t1()'); plpgsql_check_function ------------------------ (0 rows) create or replace function t1() returns void as $$ declare r1 record; r2 record; begin select 10 as a, 20 as b into r1; r2 := df1(r1); raise notice '%', r2.a; end; $$ language plpgsql; select * from plpgsql_check_function('t1()'); plpgsql_check_function ------------------------ (0 rows) create or replace function t1() returns void as $$ declare r1 record; r2 record; begin select 10 as a, 20 as b into r1; r2 := df2(r1, '{}'); raise notice '%', r2.a; end; $$ language plpgsql; select * from plpgsql_check_function('t1()'); plpgsql_check_function ------------------------ (0 rows) create or replace function df1(anyelement) returns anyelement as $$ select $1 $$ language sql; create or replace function df22(jsonb, anyelement) returns anyelement as $$ select $2; $$ language sql; create or replace function t1() returns void as $$ declare r1 record; r2 record; begin select 10 as a, 20 as b into r1; r2 := df1(r1); raise notice '%', r2.a; end; $$ language plpgsql; select * from plpgsql_check_function('t1()'); plpgsql_check_function ------------------------ (0 rows) create or replace function t1() returns void as $$ declare r1 record; r2 record; begin select 10 as a, 20 as b into r1; r2 := df22('{}', r1); raise notice '%', r2.a; end; $$ language plpgsql; select * from plpgsql_check_function('t1()'); plpgsql_check_function ------------------------ (0 rows) drop function df1(anyelement); drop function df2(anyelement, jsonb); drop function df22(jsonb, anyelement); drop function t1(); create or replace function dyntest() returns void as $$ begin execute 'drop table if exists xxx; create table xxx(a int)'; end; $$ language plpgsql; -- should be ok select * from plpgsql_check_function('dyntest'); plpgsql_check_function ------------------------ (0 rows) create or replace function dyntest() returns void as $$ declare x int; begin execute 'drop table if exists xxx; create table xxx(a int)' into x; end; $$ language plpgsql; -- should to report error select * from plpgsql_check_function('dyntest'); plpgsql_check_function ------------------------------------------------------- error:XX000:4:EXECUTE:expression does not return data (1 row) drop function dyntest(); -- should to report error create type typ2 as (a int, b int); create or replace function broken_into() returns void as $$ declare v typ2; begin -- should to fail select (10,20)::typ2 into v; -- should be ok select ((10,20)::typ2).* into v; -- should to fail execute 'select (10,20)::typ2' into v; -- should be ok execute 'select ((10,20)::typ2).*' into v; end; $$ language plpgsql; select * from plpgsql_check_function('broken_into', fatal_errors => false); plpgsql_check_function ------------------------------------------------------------------------------------------------------------ error:42804:5:SQL statement:cannot cast composite value of "typ2" type to a scalar value of "integer" type warning:00000:5:SQL statement:too few attributes for composite variable error:42804:9:EXECUTE:cannot cast composite value of "typ2" type to a scalar value of "integer" type warning:00000:9:EXECUTE:too few attributes for composite variable warning extra:00000:2:DECLARE:never read variable "v" (5 rows) drop function broken_into(); drop type typ2; -- check output in xml or json formats CREATE OR REPLACE FUNCTION test_function() RETURNS void LANGUAGE plpgsql AS $function$ begin insert into non_existing_table values (1); end $function$; select * from plpgsql_check_function('test_function', format => 'xml'); plpgsql_check_function ---------------------------------------------------------------------------- + + error + 42P01 + relation "non_existing_table" does not exist + SQL statement + insert into non_existing_table values (1)+ + (1 row) select * from plpgsql_check_function('test_function', format => 'json'); plpgsql_check_function ----------------------------------------------------------------- { "issues":[ + { + "level":"error", + "message":"relation \"non_existing_table\" does not exist",+ "statement":{ + "lineNumber":"3", + "text":"SQL statement" + }, + "query":{ + "position":"13", + "text":"insert into non_existing_table values (1)" + }, + "sqlState":"42P01" + } + + ] + } (1 row) drop function test_function(); -- test settype pragma create or replace function test_function() returns void as $$ declare r record; begin raise notice '%', r.a; end; $$ language plpgsql; -- should to detect error select * from plpgsql_check_function('test_function'); plpgsql_check_function ---------------------------------------------------------------------------- error:55000:4:RAISE:record "r" is not assigned yet Detail: The tuple structure of a not-yet-assigned record is indeterminate. Context: SQL statement "SELECT r.a" (3 rows) create type ctype as (a int, b int); create or replace function test_function() returns void as $$ declare r record; begin perform plpgsql_check_pragma('type: r ctype'); raise notice '%', r.a; end; $$ language plpgsql; -- should to be ok select * from plpgsql_check_function('test_function'); plpgsql_check_function ------------------------ (0 rows) create or replace function test_function() returns void as $$ <>declare r record; begin perform plpgsql_check_pragma('type: x.r public."ctype"'); raise notice '%', r.a; end; $$ language plpgsql; -- should to be ok select * from plpgsql_check_function('test_function'); plpgsql_check_function ------------------------ (0 rows) create or replace function test_function() returns void as $$ <>declare r record; begin perform plpgsql_check_pragma('type: "x".r (a int, b int)'); raise notice '%', r.a; end; $$ language plpgsql; -- should to be ok select * from plpgsql_check_function('test_function'); plpgsql_check_function ------------------------ (0 rows) create or replace function test_function() returns void as $$ <>declare r record; begin perform plpgsql_check_pragma('type: "x".r (a int, b int'); raise notice '%', r.a; end; $$ language plpgsql; -- should to be ok select * from plpgsql_check_function('test_function'); WARNING: Pragma "type" on line 4 is not processed. plpgsql_check_function ---------------------------------------------------------------------------- error:55000:5:RAISE:record "r" is not assigned yet Detail: The tuple structure of a not-yet-assigned record is indeterminate. Context: SQL statement "SELECT r.a" (3 rows) create or replace function test_function() returns void as $$ <>declare r record; begin perform plpgsql_check_pragma('type: "x".r (a int, b int)x'); raise notice '%', r.a; end; $$ language plpgsql; -- should to be ok select * from plpgsql_check_function('test_function'); WARNING: Pragma "type" on line 4 is not processed. plpgsql_check_function ---------------------------------------------------------------------------- error:55000:5:RAISE:record "r" is not assigned yet Detail: The tuple structure of a not-yet-assigned record is indeterminate. Context: SQL statement "SELECT r.a" (3 rows) drop function test_function(); drop type ctype; create or replace function test_function() returns void as $$ declare r pg_class; begin create temp table foo(like pg_class); select * from foo into r; end; $$ language plpgsql; -- should to raise an error select * from plpgsql_check_function('test_function'); plpgsql_check_function ----------------------------------------------------------- error:42P01:5:SQL statement:relation "foo" does not exist Query: select * from foo -- ^ (3 rows) create or replace function test_function() returns void as $$ declare r record; begin create temp table foo(like pg_class); perform plpgsql_check_pragma('table: foo(like pg_class)'); select * from foo into r; raise notice '%', r.relname; end; $$ language plpgsql; -- should be ok select * from plpgsql_check_function('test_function'); plpgsql_check_function ------------------------ (0 rows) drop function test_function(); -- now plpgsql_check can do some other checks when statement EXECUTE -- contains only format function with constant fmt. create or replace function test_function() returns void as $$ begin execute format('create table zzz %I(a int, b int)', 'zzz'); end; $$ language plpgsql; -- should to detect bad expression select * from plpgsql_check_function('test_function'); plpgsql_check_function ----------------------------------------------------- error:42601:3:EXECUTE:syntax error at or near "zzz" (1 row) -- should to correctly detect type create or replace function test_function() returns void as $$ declare r record; begin execute format('select %L::date + 1 as x', current_date) into r; raise notice '%', extract(dow from r.x); end; $$ language plpgsql; -- should be ok select * from plpgsql_check_function('test_function'); plpgsql_check_function ------------------------ (0 rows) -- should not to crash create or replace function test_function() returns void as $$ declare r record; begin r := null; end; $$ language plpgsql; select * from plpgsql_check_function('test_function'); plpgsql_check_function ------------------------------------------------------- warning extra:00000:2:DECLARE:never read variable "r" (1 row) drop function test_function(); -- aborted function has profile too create or replace function test_function(a int) returns int as $$ begin if (a > 5) then a := a + 10; return a; else raise exception 'a < 5'; end if; end; $$ language plpgsql; set plpgsql_check.profiler to on; select test_function(1); ERROR: a < 5 select test_function(10); test_function --------------- 20 (1 row) select lineno, exec_stmts, exec_stmts_err, source from plpgsql_profiler_function_tb('test_function'); lineno | exec_stmts | exec_stmts_err | source --------+------------+----------------+------------------------------ 1 | | | 2 | 2 | 1 | begin 3 | 2 | 1 | if (a > 5) then 4 | 1 | 0 | a := a + 10; 5 | 1 | 0 | return a; 6 | | | else 7 | 1 | 1 | raise exception 'a < 5'; 8 | | | end if; 9 | | | end; (9 rows) create or replace function test_function1(a int) returns int as $$ begin if (a > 5) then a := a + 10; return a; else raise exception 'a < 5'; end if; exeception when others then raise notice 'do warning'; return -1; end; $$ language plpgsql; select test_function1(1); ERROR: a < 5 select test_function1(10); test_function1 ---------------- 20 (1 row) select lineno, exec_stmts, exec_stmts_err, source from plpgsql_profiler_function_tb('test_function1'); lineno | exec_stmts | exec_stmts_err | source --------+------------+----------------+-------------------------------- 1 | | | 2 | 2 | 1 | begin 3 | 2 | 1 | if (a > 5) then 4 | 1 | 0 | a := a + 10; 5 | 1 | 0 | return a; 6 | | | else 7 | 1 | 1 | raise exception 'a < 5'; 8 | | | end if; 9 | 0 | 0 | exeception when others then 10 | | | raise notice 'do warning'; 11 | 0 | 0 | return -1; 12 | | | end; (12 rows) drop function test_function(int); drop function test_function1(int); set plpgsql_check.profiler to off; -- ignores syntax errors when literals placehodlers are used create function test_function() returns void as $$ begin execute format('do %L', 'begin end'); end $$ language plpgsql; select * from plpgsql_check_function('test_function'); plpgsql_check_function ------------------------ (0 rows) drop function test_function(); load 'plpgsql_check'; drop type testtype cascade; create type testtype as (a int, b int); create function test_function() returns record as $$ declare r record; begin r := (10,20); if false then return r; end if; return null; end; $$ language plpgsql; create function test_function33() returns record as $$ declare r testtype; begin r := (10,20); if false then return r; end if; return null; end; $$ language plpgsql; -- should not to raise false alarm due check against fake result type select plpgsql_check_function('test_function'); plpgsql_check_function ------------------------ (0 rows) select plpgsql_check_function('test_function33'); plpgsql_check_function ------------------------ (0 rows) -- try to check in passive mode set plpgsql_check.mode = 'every_start'; select test_function(); test_function --------------- (1 row) select test_function33(); test_function33 ----------------- (1 row) select * from test_function() as (a int, b int); a | b ---+--- | (1 row) select * from test_function33() as (a int, b int); a | b ---+--- | (1 row) -- should to identify error select * from test_function() as (a int, b int, c int); ERROR: returned record type does not match expected record type select * from test_function33() as (a int, b int, c int); ERROR: returned record type does not match expected record type drop function test_function(); drop function test_function33(); drop type testtype; set plpgsql_check.mode to default; -- should not to raise false alarm create type c1 as ( a text ); create table t1 ( a c1, b c1 ); insert into t1 (values ('(abc)', '(def)')); alter table t1 drop column a; create or replace function test_function() returns t1 as $$ declare myrow t1%rowtype; begin select * into myrow from t1 limit 1; return myrow; end; $$ language plpgsql; select * from test_function(); b ------- (def) (1 row) select * from plpgsql_check_function('public.test_function()'); plpgsql_check_function ------------------------ (0 rows) drop function test_function(); drop table t1; drop type c1; -- compatibility warnings create or replace function foo01() returns refcursor as $$ declare c cursor for select 1; r refcursor; begin open c; r := c; return r; end; $$ language plpgsql; -- no warnings select * from plpgsql_check_function('foo01', compatibility_warnings => true); plpgsql_check_function ------------------------ (0 rows) create or replace function foo01() returns refcursor as $$ declare c cursor for select 1; r refcursor; begin open c; r := 'c'; return r; end; $$ language plpgsql; -- warning select * from plpgsql_check_function('foo01', extra_warnings => false, compatibility_warnings => true); plpgsql_check_function -------------------------------------------------------------------------------------- compatibility:00000:7:assignment:obsolete setting of refcursor or cursor variable Detail: Internal name of cursor should not be specified by users. Context: at assignment to variable "r" declared on line 4 warning:42804:7:assignment:target type is different type than source type Detail: cast "text" value to "refcursor" type Hint: The input expression type does not have an assignment cast to the target type. Context: at assignment to variable "r" declared on line 4 (7 rows) create or replace function foo01() returns refcursor as $$ declare c cursor for select 1; r refcursor; begin open c; r := c; return 'c'; end; $$ language plpgsql; -- warning select * from plpgsql_check_function('foo01', extra_warnings => false, compatibility_warnings => true); plpgsql_check_function -------------------------------------------------------------------------------------- compatibility:00000:8:RETURN:obsolete setting of refcursor or cursor variable Detail: Internal name of cursor should not be specified by users. warning:42804:8:RETURN:target type is different type than source type Detail: cast "text" value to "refcursor" type Hint: The input expression type does not have an assignment cast to the target type. (5 rows) -- pragma sequence test create or replace function test_function() returns void as $$ begin perform plpgsql_check_pragma('sequence: xx'); perform nextval('pg_temp.xy'); perform nextval('pg_temp.xx'); end $$ language plpgsql; select * from plpgsql_check_function('test_function'); plpgsql_check_function ------------------------------------------------------------ error:42P01:4:PERFORM:relation "pg_temp.xy" does not exist Query: SELECT nextval('pg_temp.xy') -- ^ (3 rows) drop function test_function(); create table t1(x int); create or replace function f1_trg() returns trigger as $$ declare x int; begin return x; end; $$ language plpgsql; create trigger t1_f1 before insert on t1 for each row execute procedure f1_trg(); -- raise error select * from plpgsql_check_function('f1_trg', 't1'); plpgsql_check_function ----------------------------------------------------------------------------------------------- error:42804:4:RETURN:cannot return non-composite value from function returning composite type (1 row) drop trigger t1_f1 on t1; drop table t1; drop function f1_trg; create function test_function() returns void as $$ declare a int; b int; c int; d char; begin c := a + d; end; $$ language plpgsql; -- only b should be marked as unused variable select * from plpgsql_check_function('test_function', fatal_errors := false); plpgsql_check_function --------------------------------------------------------------------------------------------------------- error:42883:6:assignment:operator does not exist: integer + character Query: SELECT a + d -- ^ Hint: No operator matches the given name and argument types. You might need to add explicit type casts. Context: at assignment to variable "c" declared on line 4 warning:00000:3:DECLARE:unused variable "b" warning extra:00000:4:DECLARE:never read variable "c" (7 rows) drop function test_function(); -- from plpgsql_check_active-12 create or replace procedure proc(a int) as $$ begin end; $$ language plpgsql; call proc(10); select * from plpgsql_check_function('proc(int)'); plpgsql_check_function ------------------------------------------ warning extra:00000:unused parameter "a" (1 row) create or replace procedure testproc() as $$ begin call proc(10); end; $$ language plpgsql; call testproc(); select * from plpgsql_check_function('testproc()'); plpgsql_check_function ------------------------ (0 rows) -- should to fail create or replace procedure testproc() as $$ begin call proc((select count(*) from pg_class)); end; $$ language plpgsql; call testproc(); ERROR: cannot use subquery in CALL argument at character 11 select * from plpgsql_check_function('testproc()'); plpgsql_check_function --------------------------------------------------------- error:0A000:3:CALL:cannot use subquery in CALL argument Query: CALL proc((select count(*) from pg_class)) -- ^ (3 rows) drop procedure proc(int); create procedure proc(in a int, inout b int, in c int) as $$ begin end; $$ language plpgsql; select * from plpgsql_check_function('proc(int,int, int)'); plpgsql_check_function ------------------------------------------------- warning extra:00000:unused parameter "a" warning extra:00000:unused parameter "b" warning extra:00000:unused parameter "c" warning extra:00000:unmodified OUT variable "b" (4 rows) create or replace procedure proc(in a int, inout b int, in c int) as $$ begin b := a + c; end; $$ language plpgsql; select * from plpgsql_check_function('proc(int,int, int)'); plpgsql_check_function ------------------------ (0 rows) create or replace procedure testproc() as $$ declare r int; begin call proc(10, r, 20); end; $$ language plpgsql; call testproc(); select * from plpgsql_check_function('testproc()'); plpgsql_check_function ------------------------ (0 rows) -- should to fail create or replace procedure testproc() as $$ declare r int; begin call proc(10, r + 10, 20); end; $$ language plpgsql; call testproc(); ERROR: procedure parameter "b" is an output parameter but corresponding argument is not writable select * from plpgsql_check_function('testproc()'); plpgsql_check_function -------------------------------------------------------------------------------------------------------------- error:42601:4:CALL:procedure parameter "b" is an output parameter but corresponding argument is not writable (1 row) create or replace procedure testproc(inout r int) as $$ begin call proc(10, r, 20); end; $$ language plpgsql; call testproc(10); r ---- 30 (1 row) select * from plpgsql_check_function('testproc(int)'); plpgsql_check_function ------------------------ (0 rows) drop procedure testproc(int); -- should to raise warnings create or replace procedure testproc2(in p1 int, inout p2 int, in p3 int, inout p4 int) as $$ begin raise notice '% %', p1, p3; end; $$ language plpgsql; select * from plpgsql_check_function('testproc2'); plpgsql_check_function -------------------------------------------------- warning extra:00000:unused parameter "p2" warning extra:00000:unused parameter "p4" warning extra:00000:unmodified OUT variable "p2" warning extra:00000:unmodified OUT variable "p4" (4 rows) drop procedure testproc2; -- should be ok create or replace procedure testproc3(in p1 int, inout p2 int, in p3 int, inout p4 int) as $$ begin p2 := p1; p4 := p3; end; $$ language plpgsql; select * from plpgsql_check_function('testproc3'); plpgsql_check_function ------------------------ (0 rows) drop procedure testproc3; /* * Test pragma */ create or replace function test_pragma() returns void language plpgsql as $$ declare r record; begin perform plpgsql_check_pragma('disable:check'); raise notice '%', r.y; perform plpgsql_check_pragma('enable:check'); select 10 as a, 20 as b into r; raise notice '%', r.a; raise notice '%', r.x; end; $$; select * from plpgsql_check_function('test_pragma'); plpgsql_check_function ------------------------------------------------- error:42703:9:RAISE:record "r" has no field "x" Context: SQL statement "SELECT r.x" (2 rows) create or replace function test_pragma() returns void language plpgsql as $$ declare r record; begin if false then -- check is disabled just for if body perform plpgsql_check_pragma('disable:check'); raise notice '%', r.y; end if; select 10 as a, 20 as b into r; raise notice '%', r.a; raise notice '%', r.x; end; $$; select * from plpgsql_check_function('test_pragma'); plpgsql_check_function -------------------------------------------------- error:42703:11:RAISE:record "r" has no field "x" Context: SQL statement "SELECT r.x" (2 rows) drop function test_pragma(); create or replace function nested_trace_test(a int) returns int as $$ begin return a + 1; end; $$ language plpgsql; create or replace function trace_test(b int) returns int as $$ declare r int default 0; begin for i in 1..b loop r := nested_trace_test(r); end loop; return r; end; $$ language plpgsql; select trace_test(3); trace_test ------------ 3 (1 row) set plpgsql_check.enable_tracer to on; set plpgsql_check.tracer to on; set plpgsql_check.tracer_test_mode = true; select trace_test(3); NOTICE: #0 ->> start of function trace_test(integer) (oid=0, tnl=1) NOTICE: #0 "b" => '3' NOTICE: #1 ->> start of function nested_trace_test(integer) (oid=0, tnl=1) NOTICE: #1 context: PL/pgSQL function trace_test(integer) line 6 at assignment NOTICE: #1 "a" => '0' NOTICE: #1 <<- end of function nested_trace_test (elapsed time=0.010 ms) NOTICE: #1 ->> start of function nested_trace_test(integer) (oid=0, tnl=1) NOTICE: #1 context: PL/pgSQL function trace_test(integer) line 6 at assignment NOTICE: #1 "a" => '1' NOTICE: #1 <<- end of function nested_trace_test (elapsed time=0.010 ms) NOTICE: #1 ->> start of function nested_trace_test(integer) (oid=0, tnl=1) NOTICE: #1 context: PL/pgSQL function trace_test(integer) line 6 at assignment NOTICE: #1 "a" => '2' NOTICE: #1 <<- end of function nested_trace_test (elapsed time=0.010 ms) NOTICE: #0 <<- end of function trace_test (elapsed time=0.010 ms) trace_test ------------ 3 (1 row) set plpgsql_check.tracer_verbosity TO verbose; select trace_test(3); NOTICE: #0 ->> start of function trace_test(integer) (oid=0, tnl=1) NOTICE: #0 "b" => '3' NOTICE: #0.1 3 --> start of statement block (tnl=1) NOTICE: #0.2 4 --> start of FOR with integer loop variable (tnl=1) NOTICE: #0.3 6 --> start of assignment nested_trace_test(r) (tnl=1) NOTICE: #0.3 "r" => '0' NOTICE: #1 ->> start of function nested_trace_test(integer) (oid=0, tnl=1) NOTICE: #1 context: PL/pgSQL function trace_test(integer) line 6 at assignment NOTICE: #1 "a" => '0' NOTICE: #1.1 2 --> start of statement block (tnl=1) NOTICE: #1.2 3 --> start of RETURN (expr='a + 1') (tnl=1) NOTICE: #1.2 "a" => '0' NOTICE: #1.1 <-- end of RETURN (elapsed time=0.010 ms) NOTICE: #1.2 <-- end of statement block (elapsed time=0.010 ms) NOTICE: #1 <<- end of function nested_trace_test (elapsed time=0.010 ms) NOTICE: #0.2 <-- end of assignment (elapsed time=0.010 ms) NOTICE: #0.3 "r" => '1' NOTICE: #0.3 6 --> start of assignment nested_trace_test(r) (tnl=1) NOTICE: #0.3 "r" => '1' NOTICE: #1 ->> start of function nested_trace_test(integer) (oid=0, tnl=1) NOTICE: #1 context: PL/pgSQL function trace_test(integer) line 6 at assignment NOTICE: #1 "a" => '1' NOTICE: #1.1 2 --> start of statement block (tnl=1) NOTICE: #1.2 3 --> start of RETURN (expr='a + 1') (tnl=1) NOTICE: #1.2 "a" => '1' NOTICE: #1.1 <-- end of RETURN (elapsed time=0.010 ms) NOTICE: #1.2 <-- end of statement block (elapsed time=0.010 ms) NOTICE: #1 <<- end of function nested_trace_test (elapsed time=0.010 ms) NOTICE: #0.2 <-- end of assignment (elapsed time=0.010 ms) NOTICE: #0.3 "r" => '2' NOTICE: #0.3 6 --> start of assignment nested_trace_test(r) (tnl=1) NOTICE: #0.3 "r" => '2' NOTICE: #1 ->> start of function nested_trace_test(integer) (oid=0, tnl=1) NOTICE: #1 context: PL/pgSQL function trace_test(integer) line 6 at assignment NOTICE: #1 "a" => '2' NOTICE: #1.1 2 --> start of statement block (tnl=1) NOTICE: #1.2 3 --> start of RETURN (expr='a + 1') (tnl=1) NOTICE: #1.2 "a" => '2' NOTICE: #1.1 <-- end of RETURN (elapsed time=0.010 ms) NOTICE: #1.2 <-- end of statement block (elapsed time=0.010 ms) NOTICE: #1 <<- end of function nested_trace_test (elapsed time=0.010 ms) NOTICE: #0.2 <-- end of assignment (elapsed time=0.010 ms) NOTICE: #0.3 "r" => '3' NOTICE: #0.1 <-- end of FOR with integer loop variable (elapsed time=0.010 ms) NOTICE: #0.4 8 --> start of RETURN (tnl=1) NOTICE: #0.4 "r" => '3' NOTICE: #0.3 <-- end of RETURN (elapsed time=0.010 ms) NOTICE: #0.4 <-- end of statement block (elapsed time=0.010 ms) NOTICE: #0 <<- end of function trace_test (elapsed time=0.010 ms) trace_test ------------ 3 (1 row) create or replace function trace_test(b int) returns int as $$ declare r int default 0; begin for i in 1..b loop perform plpgsql_check_pragma('disable:tracer'); r := nested_trace_test(r); end loop; return r; end; $$ language plpgsql; select trace_test(3); NOTICE: #0 ->> start of function trace_test(integer) (oid=0, tnl=1) NOTICE: #0 "b" => '3' NOTICE: #0.1 3 --> start of statement block (tnl=1) NOTICE: #0.2 4 --> start of FOR with integer loop variable (tnl=1) NOTICE: #0.3 6 --> start of perform plpgsql_check_pragma('disable: .. (tnl=1) NOTICE: #0.2 <-- end of PERFORM (elapsed time=0.010 ms) NOTICE: #0.1 <-- end of FOR with integer loop variable (elapsed time=0.010 ms) NOTICE: #0.5 9 --> start of RETURN (tnl=1) NOTICE: #0.5 "r" => '3' NOTICE: #0.4 <-- end of RETURN (elapsed time=0.010 ms) NOTICE: #0.5 <-- end of statement block (elapsed time=0.010 ms) NOTICE: #0 <<- end of function trace_test (elapsed time=0.010 ms) trace_test ------------ 3 (1 row) create or replace function nested_trace_test(a int) returns int as $$ begin perform plpgsql_check_pragma('enable:tracer'); return a + 1; end; $$ language plpgsql; select trace_test(3); NOTICE: #0 ->> start of function trace_test(integer) (oid=0, tnl=1) NOTICE: #0 "b" => '3' NOTICE: #0.1 3 --> start of statement block (tnl=1) NOTICE: #0.2 4 --> start of FOR with integer loop variable (tnl=1) NOTICE: #0.3 6 --> start of perform plpgsql_check_pragma('disable: .. (tnl=1) NOTICE: #0.2 <-- end of PERFORM (elapsed time=0.010 ms) NOTICE: #1.3 4 --> start of RETURN (expr='a + 1') (tnl=1) NOTICE: #1.3 "a" => '0' NOTICE: #1.2 <-- end of RETURN (elapsed time=0.010 ms) NOTICE: #1.3 4 --> start of RETURN (expr='a + 1') (tnl=1) NOTICE: #1.3 "a" => '1' NOTICE: #1.2 <-- end of RETURN (elapsed time=0.010 ms) NOTICE: #1.3 4 --> start of RETURN (expr='a + 1') (tnl=1) NOTICE: #1.3 "a" => '2' NOTICE: #1.2 <-- end of RETURN (elapsed time=0.010 ms) NOTICE: #0.1 <-- end of FOR with integer loop variable (elapsed time=0.010 ms) NOTICE: #0.5 9 --> start of RETURN (tnl=1) NOTICE: #0.5 "r" => '3' NOTICE: #0.4 <-- end of RETURN (elapsed time=0.010 ms) NOTICE: #0.5 <-- end of statement block (elapsed time=0.010 ms) NOTICE: #0 <<- end of function trace_test (elapsed time=0.010 ms) trace_test ------------ 3 (1 row) drop function trace_test(int); drop function nested_trace_test(int); create or replace function trace_test(int) returns int as $$ declare r int default 0; begin for i in 1..$1 loop r := r + 1; end loop; r := r + 10; return r; end; $$ language plpgsql; select trace_test(4); NOTICE: #0 ->> start of function trace_test(integer) (oid=0, tnl=1) NOTICE: #0 "$1" => '4' NOTICE: #0.1 3 --> start of statement block (tnl=1) NOTICE: #0.2 4 --> start of FOR with integer loop variable (tnl=1) NOTICE: #0.3 5 --> start of assignment r + 1 (tnl=1) NOTICE: #0.3 "r" => '0' NOTICE: #0.2 <-- end of assignment (elapsed time=0.010 ms) NOTICE: #0.3 "r" => '1' NOTICE: #0.3 5 --> start of assignment r + 1 (tnl=1) NOTICE: #0.3 "r" => '1' NOTICE: #0.2 <-- end of assignment (elapsed time=0.010 ms) NOTICE: #0.3 "r" => '2' NOTICE: #0.3 5 --> start of assignment r + 1 (tnl=1) NOTICE: #0.3 "r" => '2' NOTICE: #0.2 <-- end of assignment (elapsed time=0.010 ms) NOTICE: #0.3 "r" => '3' NOTICE: #0.3 5 --> start of assignment r + 1 (tnl=1) NOTICE: #0.3 "r" => '3' NOTICE: #0.2 <-- end of assignment (elapsed time=0.010 ms) NOTICE: #0.3 "r" => '4' NOTICE: #0.1 <-- end of FOR with integer loop variable (elapsed time=0.010 ms) NOTICE: #0.4 7 --> start of assignment r + 10 (tnl=1) NOTICE: #0.4 "r" => '4' NOTICE: #0.3 <-- end of assignment (elapsed time=0.010 ms) NOTICE: #0.4 "r" => '14' NOTICE: #0.5 8 --> start of RETURN (tnl=1) NOTICE: #0.5 "r" => '14' NOTICE: #0.4 <-- end of RETURN (elapsed time=0.010 ms) NOTICE: #0.5 <-- end of statement block (elapsed time=0.010 ms) NOTICE: #0 <<- end of function trace_test (elapsed time=0.010 ms) trace_test ------------ 14 (1 row) create or replace function trace_test(int) returns int as $$ declare r int default 0; begin for i in 1..$1 loop perform plpgsql_check_pragma('disable:tracer'); r := r + 1; end loop; r := r + 10; return r; end; $$ language plpgsql; select trace_test(4); NOTICE: #0 ->> start of function trace_test(integer) (oid=0, tnl=1) NOTICE: #0 "$1" => '4' NOTICE: #0.1 3 --> start of statement block (tnl=1) NOTICE: #0.2 4 --> start of FOR with integer loop variable (tnl=1) NOTICE: #0.3 5 --> start of perform plpgsql_check_pragma('disable: .. (tnl=1) NOTICE: #0.2 <-- end of PERFORM (elapsed time=0.010 ms) NOTICE: #0.1 <-- end of FOR with integer loop variable (elapsed time=0.010 ms) NOTICE: #0.5 8 --> start of assignment r + 10 (tnl=1) NOTICE: #0.5 "r" => '4' NOTICE: #0.4 <-- end of assignment (elapsed time=0.010 ms) NOTICE: #0.5 "r" => '14' NOTICE: #0.6 9 --> start of RETURN (tnl=1) NOTICE: #0.6 "r" => '14' NOTICE: #0.5 <-- end of RETURN (elapsed time=0.010 ms) NOTICE: #0.6 <-- end of statement block (elapsed time=0.010 ms) NOTICE: #0 <<- end of function trace_test (elapsed time=0.010 ms) trace_test ------------ 14 (1 row) create or replace function trace_test(int) returns int as $$ declare r int default 0; begin perform plpgsql_check_pragma('disable:tracer'); for i in 1..$1 loop r := r + 1; end loop; perform plpgsql_check_pragma('enable:tracer'); r := r + 10; return r; end; $$ language plpgsql; select trace_test(4); NOTICE: #0 ->> start of function trace_test(integer) (oid=0, tnl=1) NOTICE: #0 "$1" => '4' NOTICE: #0.1 3 --> start of statement block (tnl=1) NOTICE: #0.2 4 --> start of perform plpgsql_check_pragma('disable: .. (tnl=1) NOTICE: #0.1 <-- end of PERFORM (elapsed time=0.010 ms) NOTICE: #0.6 12 --> start of assignment r + 10 (tnl=1) NOTICE: #0.6 "r" => '4' NOTICE: #0.5 <-- end of assignment (elapsed time=0.010 ms) NOTICE: #0.6 "r" => '14' NOTICE: #0.7 13 --> start of RETURN (tnl=1) NOTICE: #0.7 "r" => '14' NOTICE: #0.6 <-- end of RETURN (elapsed time=0.010 ms) NOTICE: #0.7 <-- end of statement block (elapsed time=0.010 ms) NOTICE: #0 <<- end of function trace_test (elapsed time=0.010 ms) trace_test ------------ 14 (1 row) drop function trace_test(int); create or replace function public.fx1() returns table(i integer, j integer, found boolean) as $$ begin for i in 1..10 loop for j in 1..10 loop return next; end loop; end loop; end; $$ language plpgsql immutable; select * from plpgsql_check_function('fx1'); plpgsql_check_function -------------------------------------------------------------------------- warning:00000:2:statement block:parameter "found" is shadowed Detail: Local auto variable shadows function parameter. warning:00000:3:FOR with integer loop variable:parameter "i" is shadowed Detail: Local auto variable shadows function parameter. warning:00000:5:FOR with integer loop variable:parameter "j" is shadowed Detail: Local auto variable shadows function parameter. warning extra:00000:unmodified OUT variable "i" warning extra:00000:unmodified OUT variable "j" warning extra:00000:unmodified OUT variable "found" (9 rows) drop function fx1; -- tracer test set plpgsql_check.enable_tracer to on; select plpgsql_check_tracer(true); NOTICE: tracer is active NOTICE: tracer verbosity is verbose plpgsql_check_tracer ---------------------- t (1 row) create role plpgsql_check_test_role; DO $$ begin begin -- should to fail create role plpgsql_check_test_role; exception when duplicate_object then -- Role already exists -- the exception handler is empty (#156) end; end; $$; NOTICE: #0 ->> start of block inline_code_block (oid=0, tnl=1) NOTICE: #0.1 2 --> start of statement block (tnl=1) NOTICE: #0.2 3 --> start of statement block (tnl=1) NOTICE: #0.3 5 --> start of SQL statement (query='create role plpgsql_check_test ..') (tnl=2) NOTICE: #0.1 <-- end of SQL statement (elapsed time=0.010 ms) aborted NOTICE: #0.2 <-- end of statement block (elapsed time=0.010 ms) NOTICE: #0.3 <-- end of statement block (elapsed time=0.010 ms) NOTICE: #0 <<- end of block (elapsed time=0.010 ms) drop role plpgsql_check_test_role; set plpgsql_check.enable_tracer to off; select plpgsql_check_tracer(false); NOTICE: tracer is not active NOTICE: tracer verbosity is verbose plpgsql_check_tracer ---------------------- f (1 row) -- tracing constants -- issue #159 create or replace function tabret_dynamic() returns table (id integer, val text) as $$ begin return query execute 'select id, val from (values (1, ''a''), (2, ''b'')) as v(id, val)'; end; $$ language plpgsql immutable; -- should be ok select * from plpgsql_check_function('tabret_dynamic()'); plpgsql_check_function ------------------------ (0 rows) create or replace function tabret_dynamic() returns table (id integer, val text) as $$ declare z_query text; begin z_query := 'select id, val from (values (1, ''a''), (2, ''b'')) as v(id, val)'; execute z_query into id, val; return next; end; $$ language plpgsql immutable; -- should be ok select * from plpgsql_check_function('tabret_dynamic()'); plpgsql_check_function ------------------------ (0 rows) create or replace function tabret_dynamic() returns table (id integer, val text) as $$ declare z_query text; begin z_query := 'select id, val from (values (1, ''a''), (2, ''b'')) as v(id, val)'; for id, val in execute z_query loop return next; end loop; end; $$ language plpgsql immutable; -- should be ok select * from plpgsql_check_function('tabret_dynamic()'); plpgsql_check_function ------------------------ (0 rows) create or replace function tabret_dynamic() returns table (id integer, val text) as $$ declare z_query text; begin z_query := 'select id, val from (values (1, ''a''), (2, ''b'')) as v(id, val)'; return query execute z_query; end; $$ language plpgsql immutable; -- should be ok select * from plpgsql_check_function('tabret_dynamic()'); plpgsql_check_function ------------------------ (0 rows) drop function tabret_dynamic; -- should not to crash on empty string, or comment create or replace function dynamic_emptystr() returns void as $$ begin execute ''; end; $$ language plpgsql; -- should be ok select * from plpgsql_check_function('dynamic_emptystr()'); plpgsql_check_function ------------------------ (0 rows) create or replace function dynamic_emptystr() returns void as $$ declare x int; begin execute '--' into x; end; $$ language plpgsql; -- should not to crash, no result error select * from plpgsql_check_function('dynamic_emptystr()'); plpgsql_check_function ------------------------------------------------------- error:XX000:4:EXECUTE:expression does not return data (1 row) drop function dynamic_emptystr(); -- unclosed cursor detection create or replace function fx() returns void as $$ declare c refcursor; begin open c for select * from pg_class; end; $$ language plpgsql; do $$ begin perform fx(); -- should to show warning perform fx(); end; $$; WARNING: cursor "c" is not closed set plpgsql_check.strict_cursors_leaks to on; do $$ begin -- should to show warning perform fx(); -- should to show warning perform fx(); end; $$; WARNING: cursor is not closed WARNING: cursor is not closed create or replace function fx() returns void as $$ declare c refcursor; begin open c for select * from pg_class; close c; end; $$ language plpgsql; -- without warnings do $$ begin perform fx(); perform fx(); end; $$; set plpgsql_check.strict_cursors_leaks to off; drop function fx(); create table public.testt(a int, b int); create or replace function fx() returns void as $$ declare v1 varchar default 'public'; v2 varchar default 'testt'; v3 varchar default 'a'; begin raise notice '%', format('%I.%I.%I', v1, v2, v3); perform 'pragma:assert-schema: v1'; perform 'pragma:assert-table: v1, v2'; perform 'pragma:assert-column: v1, v2, v3'; perform 'pragma:assert-table: v2'; perform 'pragma:assert-column: v2, v3'; end; $$ language plpgsql; select * from plpgsql_check_function('fx()'); plpgsql_check_function ------------------------ (0 rows) create table public.testt(a int, b int); ERROR: relation "testt" already exists create or replace function fx() returns void as $$ declare v1 varchar default 'public'; v2 varchar default 'testt'; v3 varchar default 'x'; begin raise notice '%', format('%I.%I.%I', v1, v2, v3); perform 'pragma:assert-column: v1, v2, v3'; end; $$ language plpgsql; select * from plpgsql_check_function('fx()'); plpgsql_check_function ------------------------------------------------------------------------------ error:42703:8:PERFORM:column "x" of relation "public"."testt" does not exist (1 row) drop function fx(); drop table public.testt; create or replace function fx() returns void as $$ declare v_sql varchar default ''; i int; begin -- we don't support constant tracing from queries select 'bla bla bla' into v_sql; execute v_sql into i; raise notice '%', i; end; $$ language plpgsql; -- should be ok select * from plpgsql_check_function('fx()'); plpgsql_check_function ------------------------ (0 rows) drop function fx(); -- should not to raise warning do $$ declare c cursor for select * from generate_series(1,10); t int; begin for i in 1..2 loop open c; loop fetch c into t; exit when not found; raise notice '%', t; end loop; close c; end loop; end; $$; NOTICE: 1 NOTICE: 2 NOTICE: 3 NOTICE: 4 NOTICE: 5 NOTICE: 6 NOTICE: 7 NOTICE: 8 NOTICE: 9 NOTICE: 10 NOTICE: 1 NOTICE: 2 NOTICE: 3 NOTICE: 4 NOTICE: 5 NOTICE: 6 NOTICE: 7 NOTICE: 8 NOTICE: 9 NOTICE: 10 -- should not raise warning create or replace function fx(p text) returns void as $$ begin execute format($_$select '%1$I'$_$, p); end; $$ language plpgsql; -- should be ok select * from plpgsql_check_function('fx(text)'); plpgsql_check_function ------------------------ (0 rows) drop function fx(text); create or replace function fx() returns void as $$ declare p varchar; begin p := 'ahoj'; execute format($_$select '%1$I'$_$, p); end; $$ language plpgsql; -- should be ok select * from plpgsql_check_function('fx()'); plpgsql_check_function ------------------------ (0 rows) drop function fx(); -- should not crash create or replace procedure p1() as $$ begin commit; end; $$ language plpgsql; set plpgsql_check.cursors_leaks to on; do $$ declare c cursor for select 1; begin open c; call p1(); end $$; set plpgsql_check.cursors_leaks to default; drop procedure p1; plpgsql_check-2.7.8/expected/plpgsql_check_active_2.out000066400000000000000000012612601465410532300233070ustar00rootroot00000000000000load 'plpgsql'; create extension if not exists plpgsql_check; set client_min_messages to notice; set plpgsql_check.regress_test_mode = true; -- -- check function statement tests -- --should fail - is not plpgsql select * from plpgsql_check_function_tb('session_user()'); ERROR: "session_user"() is not a plpgsql function create table t1(a int, b int); create table pa (id int, pa_id character varying(32), status character varying(60)); create table ml(ml_id character varying(32), status_from character varying(60), pa_id character varying(32), xyz int); create function f1() returns void as $$ begin if false then update t1 set c = 30; end if; if false then raise notice '%', r.c; end if; end; $$ language plpgsql; select f1(); f1 ---- (1 row) select * from plpgsql_check_function_tb('f1()', fatal_errors := true); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+---------------+----------+--------------------------------------------+--------+------+-------+----------+----------------------+--------- f1 | 4 | SQL statement | 42703 | column "c" of relation "t1" does not exist | | | error | 15 | update t1 set c = 30 | (1 row) select * from plpgsql_check_function_tb('f1()', fatal_errors := false); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+---------------+----------+--------------------------------------------+--------+------+-------+----------+----------------------+--------- f1 | 4 | SQL statement | 42703 | column "c" of relation "t1" does not exist | | | error | 15 | update t1 set c = 30 | f1 | 7 | RAISE | 42P01 | missing FROM-clause entry for table "r" | | | error | 1 | r.c | (2 rows) select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+---------------+----------+--------------------------------------------+--------+------+-------+----------+----------------------+--------- f1 | 4 | SQL statement | 42703 | column "c" of relation "t1" does not exist | | | error | 15 | update t1 set c = 30 | (1 row) drop function f1(); create function f1() returns void as $$ begin if false then insert into t1 values(10,20); update t1 set a = 10; delete from t1; end if; end; $$ language plpgsql stable; select f1(); f1 ---- (1 row) select * from plpgsql_check_function_tb('f1()', fatal_errors := false); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+---------------+----------+--------------------------------------------------+--------+------+-------+----------+------------------------------+--------- f1 | 4 | SQL statement | 0A000 | INSERT is not allowed in a non volatile function | | | error | 1 | insert into t1 values(10,20) | f1 | 5 | SQL statement | 0A000 | UPDATE is not allowed in a non volatile function | | | error | 1 | update t1 set a = 10 | f1 | 6 | SQL statement | 0A000 | DELETE is not allowed in a non volatile function | | | error | 1 | delete from t1 | (3 rows) drop function f1(); -- profiler check set plpgsql_check.profiler to on; create function f1() returns void as $$ begin if false then insert into t1 values(10,20); update t1 set a = 10; delete from t1; end if; end; $$ language plpgsql; select lineno, stmt_lineno, exec_stmts, source from plpgsql_profiler_function_tb('f1()'); lineno | stmt_lineno | exec_stmts | source --------+-------------+------------+----------------------------------- 1 | | | 2 | | | begin 3 | | | if false then 4 | | | insert into t1 values(10,20); 5 | | | update t1 set a = 10; 6 | | | delete from t1; 7 | | | end if; 8 | | | end; (8 rows) select f1(); f1 ---- (1 row) select lineno, stmt_lineno, exec_stmts, source from plpgsql_profiler_function_tb('f1()'); lineno | stmt_lineno | exec_stmts | source --------+-------------+------------+----------------------------------- 1 | | | 2 | 2 | 1 | begin 3 | 3 | 1 | if false then 4 | 4 | 0 | insert into t1 values(10,20); 5 | 5 | 0 | update t1 set a = 10; 6 | 6 | 0 | delete from t1; 7 | | | end if; 8 | | | end; (8 rows) select plpgsql_profiler_reset('f1()'); plpgsql_profiler_reset ------------------------ (1 row) select lineno, stmt_lineno, exec_stmts, source from plpgsql_profiler_function_tb('f1()'); lineno | stmt_lineno | exec_stmts | source --------+-------------+------------+----------------------------------- 1 | | | 2 | | | begin 3 | | | if false then 4 | | | insert into t1 values(10,20); 5 | | | update t1 set a = 10; 6 | | | delete from t1; 7 | | | end if; 8 | | | end; (8 rows) select f1(); f1 ---- (1 row) select lineno, stmt_lineno, exec_stmts, source from plpgsql_profiler_function_tb('f1()'); lineno | stmt_lineno | exec_stmts | source --------+-------------+------------+----------------------------------- 1 | | | 2 | 2 | 1 | begin 3 | 3 | 1 | if false then 4 | 4 | 0 | insert into t1 values(10,20); 5 | 5 | 0 | update t1 set a = 10; 6 | 6 | 0 | delete from t1; 7 | | | end if; 8 | | | end; (8 rows) select plpgsql_profiler_reset_all(); plpgsql_profiler_reset_all ---------------------------- (1 row) select lineno, stmt_lineno, exec_stmts, source from plpgsql_profiler_function_tb('f1()'); lineno | stmt_lineno | exec_stmts | source --------+-------------+------------+----------------------------------- 1 | | | 2 | | | begin 3 | | | if false then 4 | | | insert into t1 values(10,20); 5 | | | update t1 set a = 10; 6 | | | delete from t1; 7 | | | end if; 8 | | | end; (8 rows) drop function f1(); create or replace function f1() returns void as $$ begin raise notice '1'; exception when others then raise notice '2'; end; $$ language plpgsql; select f1(); NOTICE: 1 f1 ---- (1 row) select lineno, stmt_lineno, exec_stmts, source from plpgsql_profiler_function_tb('f1()'); lineno | stmt_lineno | exec_stmts | source --------+-------------+------------+---------------------------- 1 | | | 2 | 2 | 1 | begin 3 | 3 | 1 | raise notice '1'; 4 | | | exception when others then 5 | 5 | 0 | raise notice '2'; 6 | | | end; (6 rows) drop function f1(); -- test queryid retrieval create function f1() returns void as $$ declare t1 text = 't1'; begin insert into t1 values(10,20); EXECUTE 'update ' || 't1' || ' set a = 10'; EXECUTE 'delete from ' || t1; end; $$ language plpgsql; select plpgsql_profiler_reset_all(); plpgsql_profiler_reset_all ---------------------------- (1 row) select plpgsql_profiler_install_fake_queryid_hook(); plpgsql_profiler_install_fake_queryid_hook -------------------------------------------- (1 row) select f1(); f1 ---- (1 row) select queryids, lineno, stmt_lineno, exec_stmts, source from plpgsql_profiler_function_tb('f1()'); queryids | lineno | stmt_lineno | exec_stmts | source ----------+--------+-------------+------------+------------------------------------------------ | 1 | | | | 2 | | | declare | 3 | | | t1 text = 't1'; | 4 | 4 | 1 | begin {3} | 5 | 5 | 1 | insert into t1 values(10,20); {2} | 6 | 6 | 1 | EXECUTE 'update ' || 't1' || ' set a = 10'; {4} | 7 | 7 | 1 | EXECUTE 'delete from ' || t1; | 8 | | | end; (8 rows) select plpgsql_profiler_remove_fake_queryid_hook(); plpgsql_profiler_remove_fake_queryid_hook ------------------------------------------- (1 row) drop function f1(); set plpgsql_check.profiler to off; create function f1() returns void as $$ declare r record; begin if false then for r in update t1 set a = a + 1 returning * loop raise notice '%', r.a; end loop; end if; end; $$ language plpgsql; select f1(); f1 ---- (1 row) select * from plpgsql_check_function_tb('f1()', fatal_errors := false); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+---------+--------+------+-------+----------+-------+--------- (0 rows) drop function f1(); create function f1() returns void as $$ declare r record; begin if false then for r in update t1 set a = a + 1 returning * loop raise notice '%', r.a; end loop; end if; end; $$ language plpgsql stable; select f1(); f1 ---- (1 row) select * from plpgsql_check_function_tb('f1()', fatal_errors := false); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+----------------------+----------+--------------------------------------------------+--------+------+-------+----------+-------------------------------------+--------- f1 | 5 | FOR over SELECT rows | 0A000 | UPDATE is not allowed in a non volatile function | | | error | 1 | update t1 set a = a + 1 returning * | (1 row) drop function f1(); create function g1(out a int, out b int) as $$ select 10,20; $$ language sql; create function f1() returns void as $$ declare r record; begin r := g1(); if false then raise notice '%', r.c; end if; end; $$ language plpgsql; select f1(); f1 ---- (1 row) select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+-----------------------------+--------+------+-------+----------+-------+---------------------- f1 | 6 | RAISE | 42703 | record "r" has no field "c" | | | error | | | SQL expression "r.c" (1 row) select f1(); f1 ---- (1 row) drop function f1(); drop function g1(); create function g1(out a int, out b int) returns setof record as $$ select * from t1; $$ language sql; create function f1() returns void as $$ declare r record; begin for r in select * from g1() loop raise notice '%', r.c; end loop; end; $$ language plpgsql; select f1(); f1 ---- (1 row) select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+-----------------------------+--------+------+-------+----------+-------+---------------------- f1 | 6 | RAISE | 42703 | record "r" has no field "c" | | | error | | | SQL expression "r.c" (1 row) select f1(); f1 ---- (1 row) create or replace function f1() returns void as $$ declare r record; begin for r in select * from g1() loop r.c := 20; end loop; end; $$ language plpgsql; select f1(); f1 ---- (1 row) select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+------------+----------+-----------------------------+--------+------+-------+----------+-------+--------------------------------------------------------------- f1 | 6 | assignment | 42703 | record "r" has no field "c" | | | error | | | at assignment to field "c" of variable "r" declared on line 2 (1 row) select f1(); f1 ---- (1 row) drop function f1(); drop function g1(); create function f1() returns int as $$ declare r int; begin if false then r := a + b; end if; return r; end; $$ language plpgsql; select f1(); f1 ---- (1 row) select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+------------+----------+---------------------------+--------+------+-------+----------+------------+-------------------------------------------------- f1 | 5 | assignment | 42703 | column "a" does not exist | | | error | 6 | r := a + b | at assignment to variable "r" declared on line 2 (1 row) select f1(); f1 ---- (1 row) drop function f1(); create or replace function f1() returns void as $$ declare r int[]; begin if false then r[c+10] := 20; end if; end; $$ language plpgsql; select f1(); f1 ---- (1 row) select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+------------+----------+---------------------------+--------+------+-------+----------+---------------+-------------------------------------------------- f1 | 5 | assignment | 42703 | column "c" does not exist | | | error | 3 | r[c+10] := 20 | at assignment to variable "r" declared on line 2 (1 row) select f1(); f1 ---- (1 row) drop function f1(); create or replace function f1() returns void as $$ declare r int; begin if false then r[10] := 20; end if; end; $$ language plpgsql set search_path = public; select f1(); f1 ---- (1 row) select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+------------+----------+------------------------------------------------------------------------+--------+------+-------+----------+-------------+-------------------------------------------------- f1 | 5 | assignment | 42804 | cannot subscript type integer because it does not support subscripting | | | error | 1 | r[10] := 20 | at assignment to variable "r" declared on line 2 (1 row) select f1(); f1 ---- (1 row) drop function f1(); create or replace function f1_trg() returns trigger as $$ begin if new.a > 10 then raise notice '%', new.b; raise notice '%', new.c; end if; return new; end; $$ language plpgsql; create trigger t1_f1 before insert on t1 for each row execute procedure f1_trg(); insert into t1 values(6,30); select * from plpgsql_check_function_tb('f1_trg()','t1'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+-------------------------------+--------+------+-------+----------+-------+------------------------ f1_trg | 5 | RAISE | 42703 | record "new" has no field "c" | | | error | | | SQL expression "new.c" (1 row) insert into t1 values(6,30); create or replace function f1_trg() returns trigger as $$ begin new.a := new.a + 10; new.b := new.b + 10; new.c := 30; return new; end; $$ language plpgsql; -- should to fail select * from plpgsql_check_function_tb('f1_trg()','t1'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+------------+----------+-------------------------------+--------+------+-------+----------+-------+----------------------------------------------------------------- f1_trg | 5 | assignment | 42703 | record "new" has no field "c" | | | error | | | at assignment to field "c" of variable "new" declared on line 0 (1 row) -- should to fail but not crash insert into t1 values(6,30); ERROR: record "new" has no field "c" CONTEXT: PL/pgSQL assignment "new.c := 30" PL/pgSQL function f1_trg() line 5 at assignment create or replace function f1_trg() returns trigger as $$ begin new.a := new.a + 10; new.b := new.b + 10; return new; end; $$ language plpgsql; -- ok select * from plpgsql_check_function_tb('f1_trg()', 't1'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+---------+--------+------+-------+----------+-------+--------- (0 rows) -- ok insert into t1 values(6,30); create or replace function f1_trg() returns trigger as $$ begin new.a := new.a + 10; new.b := new.b + 10; return null; end; $$ language plpgsql; -- ok select * from plpgsql_check_function_tb('f1_trg()', 't1'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+---------+--------+------+-------+----------+-------+--------- (0 rows) insert into t1 values(60,300); select * from t1; a | b ----+---- 6 | 30 6 | 30 16 | 40 (3 rows) insert into t1 values(600,30); select * from t1; a | b ----+---- 6 | 30 6 | 30 16 | 40 (3 rows) drop trigger t1_f1 on t1; drop function f1_trg(); -- test of showing caret on correct place for multiline queries create or replace function f1() returns void as $$ begin select var from foo; end; $$ language plpgsql; select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+---------------+----------+-------------------------------+--------+------+-------+----------+--------+--------- f1 | 3 | SQL statement | 42P01 | relation "foo" does not exist | | | error | 23 | select+| | | | | | | | | | var +| | | | | | | | | | from+| | | | | | | | | | foo | (1 row) drop function f1(); create or replace function f1() returns int as $$ begin return (select a from t1 where hh = 20); end; $$ language plpgsql; select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+----------------------------+--------+------+-------+----------+----------------------------+--------- f1 | 3 | RETURN | 42703 | column "hh" does not exist | | | error | 50 | (select a +| | | | | | | | | | from t1 +| | | | | | | | | | where hh = 20) | (1 row) create or replace function f1() returns int as $$ begin return (select a from txxxxxxx where hh = 20); end; $$ language plpgsql; select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+------------------------------------+--------+------+-------+----------+----------------------------+--------- f1 | 3 | RETURN | 42P01 | relation "txxxxxxx" does not exist | | | error | 29 | (select a +| | | | | | | | | | from txxxxxxx+| | | | | | | | | | where hh = 20) | (1 row) drop function f1(); drop table t1; -- raise warnings when target row has different number of attributies in -- SELECT INTO statement create or replace function f1() returns void as $$ declare a1 int; a2 int; begin select 10,20 into a1,a2; end; $$ language plpgsql; -- should be ok select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+--------------------------+--------+------+---------------+----------+-------+--------- f1 | 2 | DECLARE | 00000 | never read variable "a1" | | | warning extra | | | f1 | 2 | DECLARE | 00000 | never read variable "a2" | | | warning extra | | | (2 rows) create or replace function f1() returns void as $$ declare a1 int; begin select 10,20 into a1; end; $$ language plpgsql; -- raise warning select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+---------------+----------+------------------------------------------+---------------------------------------------------------------+-------------------------------------------------+---------------+----------+-------+--------- f1 | 4 | SQL statement | 00000 | too many attributes for target variables | There are less target variables than output columns in query. | Check target variables in SELECT INTO statement | warning | | | f1 | 2 | DECLARE | 00000 | never read variable "a1" | | | warning extra | | | (2 rows) create or replace function f1() returns void as $$ declare a1 int; a2 int; begin select 10 into a1,a2; end; $$ language plpgsql; -- raise warning select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+---------------+----------+-----------------------------------------+---------------------------------------------------------------+--------------------------------------------------+---------------+----------+-------+--------- f1 | 4 | SQL statement | 00000 | too few attributes for target variables | There are more target variables than output columns in query. | Check target variables in SELECT INTO statement. | warning | | | f1 | 2 | DECLARE | 00000 | never read variable "a1" | | | warning extra | | | f1 | 2 | DECLARE | 00000 | never read variable "a2" | | | warning extra | | | (3 rows) -- bogus code set check_function_bodies to off; create or replace function f1() returns void as $$ adasdfsadf $$ language plpgsql; select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+--------------------------------------+--------+------+-------+----------+------------+--------------------------------------------------- f1 | | | 42601 | syntax error at or near "adasdfsadf" | | | error | 2 | +| compilation of PL/pgSQL function "f1" near line 1 | | | | | | | | | adasdfsadf+| | | | | | | | | | | (1 row) drop function f1(); create table t1(a int, b int); create function g1(out a int, out b int) as $$ select 10,20; $$ language sql; create function f1() returns void as $$ declare r record; begin r := g1(); if false then raise notice '%', r.c; end if; end; $$ language plpgsql; select f1(); f1 ---- (1 row) select * from plpgsql_check_function('f1()'); plpgsql_check_function ------------------------------------------------- error:42703:6:RAISE:record "r" has no field "c" Context: SQL expression "r.c" (2 rows) select f1(); f1 ---- (1 row) drop function f1(); drop function g1(); create function g1(out a int, out b int) returns setof record as $$ select * from t1; $$ language sql; create function f1() returns void as $$ declare r record; begin for r in select * from g1() loop raise notice '%', r.c; end loop; end; $$ language plpgsql; select f1(); f1 ---- (1 row) select * from plpgsql_check_function('f1()'); plpgsql_check_function ------------------------------------------------- error:42703:6:RAISE:record "r" has no field "c" Context: SQL expression "r.c" (2 rows) select f1(); f1 ---- (1 row) create or replace function f1() returns void as $$ declare r record; begin for r in select * from g1() loop r.c := 20; end loop; end; $$ language plpgsql; select f1(); f1 ---- (1 row) select * from plpgsql_check_function('f1()'); plpgsql_check_function ------------------------------------------------------------------------ error:42703:6:assignment:record "r" has no field "c" Context: at assignment to field "c" of variable "r" declared on line 2 (2 rows) select f1(); f1 ---- (1 row) drop function f1(); drop function g1(); create function f1() returns int as $$ declare r int; begin if false then r := a + b; end if; return r; end; $$ language plpgsql; select f1(); f1 ---- (1 row) select * from plpgsql_check_function('f1()'); plpgsql_check_function ----------------------------------------------------------- error:42703:5:assignment:column "a" does not exist Query: r := a + b -- ^ Context: at assignment to variable "r" declared on line 2 (4 rows) select f1(); f1 ---- (1 row) drop function f1(); create or replace function f1() returns void as $$ declare r int[]; begin if false then r[c+10] := 20; end if; end; $$ language plpgsql; select f1(); f1 ---- (1 row) select * from plpgsql_check_function('f1()'); plpgsql_check_function ----------------------------------------------------------- error:42703:5:assignment:column "c" does not exist Query: r[c+10] := 20 -- ^ Context: at assignment to variable "r" declared on line 2 (4 rows) select f1(); f1 ---- (1 row) drop function f1(); create or replace function f1() returns void as $$ declare r int; begin if false then r[10] := 20; end if; end; $$ language plpgsql set search_path = public; select f1(); f1 ---- (1 row) select * from plpgsql_check_function('f1()'); plpgsql_check_function ------------------------------------------------------------------------------------------------- error:42804:5:assignment:cannot subscript type integer because it does not support subscripting Query: r[10] := 20 -- ^ Context: at assignment to variable "r" declared on line 2 (4 rows) select f1(); f1 ---- (1 row) drop function f1(); create or replace function f1_trg() returns trigger as $$ begin if new.a > 10 then raise notice '%', new.b; raise notice '%', new.c; end if; return new; end; $$ language plpgsql; create trigger t1_f1 before insert on t1 for each row execute procedure f1_trg(); insert into t1 values(6,30); select * from plpgsql_check_function('f1_trg()','t1'); plpgsql_check_function --------------------------------------------------- error:42703:5:RAISE:record "new" has no field "c" Context: SQL expression "new.c" (2 rows) insert into t1 values(6,30); create or replace function f1_trg() returns trigger as $$ begin new.a := new.a + 10; new.b := new.b + 10; new.c := 30; return new; end; $$ language plpgsql; -- should to fail select * from plpgsql_check_function('f1_trg()','t1'); plpgsql_check_function -------------------------------------------------------------------------- error:42703:5:assignment:record "new" has no field "c" Context: at assignment to field "c" of variable "new" declared on line 0 (2 rows) -- should to fail but not crash insert into t1 values(6,30); ERROR: record "new" has no field "c" CONTEXT: PL/pgSQL assignment "new.c := 30" PL/pgSQL function f1_trg() line 5 at assignment create or replace function f1_trg() returns trigger as $$ begin new.a := new.a + 10; new.b := new.b + 10; return new; end; $$ language plpgsql; -- ok select * from plpgsql_check_function('f1_trg()', 't1'); plpgsql_check_function ------------------------ (0 rows) -- ok insert into t1 values(6,30); select * from t1; a | b ----+---- 6 | 30 6 | 30 16 | 40 (3 rows) drop trigger t1_f1 on t1; drop function f1_trg(); -- test of showing caret on correct place for multiline queries create or replace function f1() returns void as $$ begin select var from foo; end; $$ language plpgsql; select * from plpgsql_check_function('f1()'); plpgsql_check_function ----------------------------------------------------------- error:42P01:3:SQL statement:relation "foo" does not exist Query: select var from foo -- ^ (6 rows) drop function f1(); create or replace function f1() returns int as $$ begin return (select a from t1 where hh = 20); end; $$ language plpgsql; select * from plpgsql_check_function('f1()'); plpgsql_check_function ------------------------------------------------- error:42703:3:RETURN:column "hh" does not exist Query: (select a from t1 where hh = 20) -- ^ (5 rows) create or replace function f1() returns int as $$ begin return (select a from txxxxxxx where hh = 20); end; $$ language plpgsql; select * from plpgsql_check_function('f1()'); plpgsql_check_function --------------------------------------------------------- error:42P01:3:RETURN:relation "txxxxxxx" does not exist Query: (select a from txxxxxxx -- ^ where hh = 20) (5 rows) drop function f1(); drop table t1; -- raise warnings when target row has different number of attributies in -- SELECT INTO statement create or replace function f1() returns void as $$ declare a1 int; a2 int; begin select 10,20 into a1,a2; end; $$ language plpgsql; -- should be ok select * from plpgsql_check_function('f1()'); plpgsql_check_function -------------------------------------------------------- warning extra:00000:2:DECLARE:never read variable "a1" warning extra:00000:2:DECLARE:never read variable "a2" (2 rows) create or replace function f1() returns void as $$ declare a1 int; begin select 10,20 into a1; end; $$ language plpgsql; -- raise warning select * from plpgsql_check_function('f1()'); plpgsql_check_function ------------------------------------------------------------------------ warning:00000:4:SQL statement:too many attributes for target variables Detail: There are less target variables than output columns in query. Hint: Check target variables in SELECT INTO statement warning extra:00000:2:DECLARE:never read variable "a1" (4 rows) create or replace function f1() returns void as $$ declare a1 int; a2 int; begin select 10 into a1,a2; end; $$ language plpgsql; -- raise warning select * from plpgsql_check_function('f1()'); plpgsql_check_function ----------------------------------------------------------------------- warning:00000:4:SQL statement:too few attributes for target variables Detail: There are more target variables than output columns in query. Hint: Check target variables in SELECT INTO statement. warning extra:00000:2:DECLARE:never read variable "a1" warning extra:00000:2:DECLARE:never read variable "a2" (5 rows) -- bogus code set check_function_bodies to off; create or replace function f1() returns void as $$ adasdfsadf $$ language plpgsql; select * from plpgsql_check_function('f1()'); plpgsql_check_function ------------------------------------------------------------ error:42601:syntax error at or near "adasdfsadf" Query: adasdfsadf -- ^ Context: compilation of PL/pgSQL function "f1" near line 1 (6 rows) drop function f1(); create table f1tbl(a int, b int); -- unused variables create or replace function f1(_input1 int) returns table(_output1 int, _output2 int) as $$ declare _f1 int; _f2 int; _f3 int; _f4 int; _f5 int; _r record; _tbl f1tbl; begin if true then _f1 := 1; end if; select 1, 2 into _f3, _f4; perform 1 where _f5 is null; select 1 into _r; select 1, 2 into _tbl; -- check that SQLSTATE and SQLERRM don't raise false positives begin exception when raise_exception then end; end $$ language plpgsql; select * from plpgsql_check_function('f1(int)'); plpgsql_check_function ---------------------------------------------------------- warning:00000:4:DECLARE:unused variable "_f2" warning extra:00000:3:DECLARE:never read variable "_f1" warning extra:00000:5:DECLARE:never read variable "_f3" warning extra:00000:6:DECLARE:never read variable "_f4" warning extra:00000:8:DECLARE:never read variable "_r" warning extra:00000:9:DECLARE:never read variable "_tbl" warning extra:00000:unused parameter "_input1" warning extra:00000:unmodified OUT variable "_output1" warning extra:00000:unmodified OUT variable "_output2" (9 rows) drop function f1(int); drop table f1tbl; -- check that NEW and OLD are not reported unused create table f1tbl(); create or replace function f1() returns trigger as $$ begin return null; end $$ language plpgsql; select * from plpgsql_check_function('f1()', 'f1tbl'); plpgsql_check_function ------------------------ (0 rows) drop function f1(); drop table f1tbl; create table tabret(a int, b int); insert into tabret values(10,10); create or replace function f1() returns int as $$ begin return (select a from tabret); end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------------- performance:00000:routine is marked as VOLATILE, should be STABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (2 rows) create or replace function f1() returns int as $$ begin return (select a::numeric from tabret); end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------------- performance:42804:3:RETURN:target type is different type than source type Detail: cast "numeric" value to "integer" type Hint: Hidden casting can be a performance issue. performance:00000:routine is marked as VOLATILE, should be STABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (5 rows) create or replace function f1() returns int as $$ begin return (select a, b from tabret); end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------- error:42601:3:RETURN:subquery must return only one column Query: (select a, b from tabret) -- ^ (3 rows) drop function f1(); create or replace function f1() returns table(ax int, bx int) as $$ begin return query select * from tabret; return; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------------- performance:00000:routine is marked as VOLATILE, should be STABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (2 rows) drop function f1(); create or replace function f1() returns table(ax numeric, bx numeric) as $$ begin return query select * from tabret; return; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------- error:42804:3:RETURN QUERY:structure of query does not match function result type Detail: Returned type integer does not match expected type numeric in column 1. (2 rows) drop function f1(); create or replace function f1() returns setof tabret as $$ begin return query select * from tabret; return; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------------- performance:00000:routine is marked as VOLATILE, should be STABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (2 rows) create or replace function f1() returns setof tabret as $$ begin return query select a from tabret; return; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------- error:42804:3:RETURN QUERY:structure of query does not match function result type Detail: Number of returned columns (1) does not match expected column count (2). (2 rows) create or replace function f1() returns setof tabret as $$ begin return query select a::numeric,b::numeric from tabret; return; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------- error:42804:3:RETURN QUERY:structure of query does not match function result type Detail: Returned type numeric does not match expected type integer in column 1. (2 rows) drop function f1(); create or replace function f1(a int) returns setof numeric as $$ begin return query select a; end $$ language plpgsql; select * from plpgsql_check_function('f1(int)', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------- error:42804:2:RETURN QUERY:structure of query does not match function result type Detail: Returned type integer does not match expected type numeric in column 1. (2 rows) drop function f1(int); drop table tabret; create or replace function f1() returns void as $$ declare intval integer; begin intval := null; -- ok intval := 1; -- OK intval := '1'; -- OK intval := text '1'; -- not OK intval := current_date; -- not OK select 1 into intval; -- OK select '1' into intval; -- OK select text '1' into intval; -- not OK end $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); plpgsql_check_function -------------------------------------------------------------------------------------- warning:42804:9:assignment:target type is different type than source type Detail: cast "date" value to "integer" type Hint: There are no possible explicit coercion between those types, possibly bug! Context: at assignment to variable "intval" declared on line 3 warning:42804:12:SQL statement:target type is different type than source type Detail: cast "text" value to "integer" type Hint: The input expression type does not have an assignment cast to the target type. warning:42804:13:SQL statement:target type is different type than source type Detail: cast "text" value to "integer" type Hint: The input expression type does not have an assignment cast to the target type. warning extra:00000:3:DECLARE:never read variable "intval" (11 rows) drop function f1(); create or replace function f1() returns int as $$ begin return 1; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------------- performance:00000:routine is marked as VOLATILE, should be IMMUTABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (2 rows) create or replace function f1() returns int as $$ begin return 1::numeric; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------------- performance:42804:3:RETURN:target type is different type than source type Detail: cast "numeric" value to "integer" type Hint: Hidden casting can be a performance issue. performance:00000:routine is marked as VOLATILE, should be IMMUTABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (5 rows) create or replace function f1() returns int as $$ begin return null; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------------- performance:00000:routine is marked as VOLATILE, should be IMMUTABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (2 rows) create or replace function f1() returns int as $$ begin return current_date; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------------- warning:42804:3:RETURN:target type is different type than source type Detail: cast "date" value to "integer" type Hint: There are no possible explicit coercion between those types, possibly bug! performance:00000:routine is marked as VOLATILE, should be STABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (5 rows) create or replace function f1() returns int as $$ declare a int; begin return a; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------------- performance:00000:routine is marked as VOLATILE, should be IMMUTABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (2 rows) create or replace function f1() returns int as $$ declare a numeric; begin return a; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------------- performance:42804:4:RETURN:target type is different type than source type Detail: cast "numeric" value to "integer" type Hint: Hidden casting can be a performance issue. performance:00000:routine is marked as VOLATILE, should be IMMUTABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (5 rows) drop function f1(); create or replace function f1() returns setof int as $$ begin return next 1; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------------- performance:00000:routine is marked as VOLATILE, should be IMMUTABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (2 rows) create or replace function f1() returns setof int as $$ begin return next 1::numeric; -- tolerant, doesn't use tupmap end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------------- performance:42804:3:RETURN NEXT:target type is different type than source type Detail: cast "numeric" value to "integer" type Hint: Hidden casting can be a performance issue. performance:00000:routine is marked as VOLATILE, should be IMMUTABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (5 rows) drop function f1(); create type t1 as (a int, b int, c int); create type t2 as (a int, b numeric); create or replace function fx() returns t2 as $$ declare x t1; begin return x; end; $$ language plpgsql; select * from plpgsql_check_function('fx()', performance_warnings := true); plpgsql_check_function --------------------------------------------------------------------------------- error:42804:4:RETURN:returned record type does not match expected record type Detail: Returned type integer does not match expected type numeric in column 2. (2 rows) create or replace function fx() returns t2 as $$ declare x t2; begin return x; end; $$ language plpgsql; select * from plpgsql_check_function('fx()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------------- performance:00000:routine is marked as VOLATILE, should be IMMUTABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (2 rows) drop function fx(); create or replace function fx() returns setof t2 as $$ declare x t1; begin return next x; return; end; $$ language plpgsql; select * from plpgsql_check_function('fx()', performance_warnings := true); plpgsql_check_function --------------------------------------------------------------------------------- error:42804:4:RETURN NEXT:wrong record type supplied in RETURN NEXT Detail: Returned type integer does not match expected type numeric in column 2. (2 rows) create or replace function fx() returns setof t2 as $$ declare x t2; begin return next x; return; end; $$ language plpgsql; select * from plpgsql_check_function('fx()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------------- performance:00000:routine is marked as VOLATILE, should be IMMUTABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (2 rows) drop function fx(); create or replace function fx2(_id int, _pa_id varchar(32), _status varchar(60)) returns void as $$ declare begin insert into pa values(_id, _pa_id, _status); exception when OTHERS then raise notice '%', 'some message'; raise exception '%', sqlerrm; end $$ language plpgsql; select * from plpgsql_check_function('fx2(int, varchar, varchar)', performance_warnings := true); plpgsql_check_function ------------------------ (0 rows) create or replace function fx2(_id int, _pa_id varchar(32), _status varchar(60)) returns void as $$ declare begin insert into pa values(_id, _pa_id, _status) returning *; exception when OTHERS then raise notice '%', 'some message'; raise exception '%', sqlerrm; end $$ language plpgsql; select * from plpgsql_check_function('fx2(int, varchar, varchar)', performance_warnings := true); plpgsql_check_function ---------------------------------------------------------------------- error:42601:4:SQL statement:query has no destination for result data (1 row) create or replace function fx2(_id int, _pa_id varchar(32), _status varchar(60)) returns void as $$ declare begin SELECT * FROM pa LIMIT 1; exception when OTHERS then raise notice '%', 'some message'; raise exception '%', sqlerrm; end $$ language plpgsql; select * from plpgsql_check_function('fx2(int, varchar, varchar)', performance_warnings := true); plpgsql_check_function ---------------------------------------------------------------------- error:42601:4:SQL statement:query has no destination for result data (1 row) drop function fx2(int, varchar, varchar); create or replace function foreach_array_loop() returns void as $body$ declare arr text[]; el text; begin arr := array['1111','2222','3333']; foreach el in array arr loop raise notice '%', el; end loop; end; $body$ language 'plpgsql' stable; select * from plpgsql_check_function_tb('foreach_array_loop()', performance_warnings := true); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context --------------------+--------+-----------+----------+--------------------------------------------------+--------+-----------------------------------------------------------------------------------+-------------+----------+-------+--------- foreach_array_loop | | | 00000 | routine is marked as STABLE, should be IMMUTABLE | | When you fix this issue, please, recheck other functions that uses this function. | performance | | | (1 row) create or replace function foreach_array_loop() returns void as $body$ declare arr text[]; el int; begin arr := array['1111','2222','3333']; foreach el in array arr loop raise notice '%', el; end loop; end; $body$ language 'plpgsql' stable; select * from plpgsql_check_function_tb('foreach_array_loop()', performance_warnings := true); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context --------------------+--------+--------------------+----------+--------------------------------------------------+-------------------------------------+-----------------------------------------------------------------------------------+-------------+----------+-------+--------- foreach_array_loop | 7 | FOREACH over array | 42804 | target type is different type than source type | cast "text" value to "integer" type | The input expression type does not have an assignment cast to the target type. | warning | | | foreach_array_loop | | | 00000 | routine is marked as STABLE, should be IMMUTABLE | | When you fix this issue, please, recheck other functions that uses this function. | performance | | | (2 rows) create or replace function foreach_array_loop() returns void as $body$ declare arr date[]; el int; begin arr := array['2014-01-01','2015-01-01','2016-01-01']::date[]; foreach el in array arr loop raise notice '%', el; end loop; end; $body$ language 'plpgsql' stable; select * from plpgsql_check_function_tb('foreach_array_loop()', performance_warnings := true); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context --------------------+--------+--------------------+----------+--------------------------------------------------+-------------------------------------+-----------------------------------------------------------------------------------+-------------+----------+-------+--------- foreach_array_loop | 7 | FOREACH over array | 42804 | target type is different type than source type | cast "date" value to "integer" type | There are no possible explicit coercion between those types, possibly bug! | warning | | | foreach_array_loop | | | 00000 | routine is marked as STABLE, should be IMMUTABLE | | When you fix this issue, please, recheck other functions that uses this function. | performance | | | (2 rows) create or replace function foreach_array_loop() returns void as $body$ declare el text; begin foreach el in array array['1111','2222','3333'] loop raise notice '%', el; end loop; end; $body$ language 'plpgsql' stable; select * from plpgsql_check_function_tb('foreach_array_loop()', performance_warnings := true); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context --------------------+--------+-----------+----------+--------------------------------------------------+--------+-----------------------------------------------------------------------------------+-------------+----------+-------+--------- foreach_array_loop | | | 00000 | routine is marked as STABLE, should be IMMUTABLE | | When you fix this issue, please, recheck other functions that uses this function. | performance | | | (1 row) create or replace function foreach_array_loop() returns void as $body$ declare el int; begin foreach el in array array['1111','2222','3333'] loop raise notice '%', el; end loop; end; $body$ language 'plpgsql' stable; select * from plpgsql_check_function_tb('foreach_array_loop()', performance_warnings := true); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context --------------------+--------+--------------------+----------+--------------------------------------------------+-------------------------------------+-----------------------------------------------------------------------------------+-------------+----------+-------+--------- foreach_array_loop | 5 | FOREACH over array | 42804 | target type is different type than source type | cast "text" value to "integer" type | The input expression type does not have an assignment cast to the target type. | warning | | | foreach_array_loop | | | 00000 | routine is marked as STABLE, should be IMMUTABLE | | When you fix this issue, please, recheck other functions that uses this function. | performance | | | (2 rows) create or replace function foreach_array_loop() returns void as $body$ declare el int; begin foreach el in array array['2014-01-01','2015-01-01','2016-01-01']::date[] loop raise notice '%', el; end loop; end; $body$ language 'plpgsql' stable; select * from plpgsql_check_function_tb('foreach_array_loop()', performance_warnings := true); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context --------------------+--------+--------------------+----------+--------------------------------------------------+-------------------------------------+-----------------------------------------------------------------------------------+-------------+----------+-------+--------- foreach_array_loop | 5 | FOREACH over array | 42804 | target type is different type than source type | cast "date" value to "integer" type | There are no possible explicit coercion between those types, possibly bug! | warning | | | foreach_array_loop | | | 00000 | routine is marked as STABLE, should be IMMUTABLE | | When you fix this issue, please, recheck other functions that uses this function. | performance | | | (2 rows) drop function foreach_array_loop(); create or replace function scan_rows(int[]) returns void AS $$ declare x int[]; begin foreach x slice 1 in array $1 loop raise notice 'row = %', x; end loop; end; $$ language plpgsql; select * from plpgsql_check_function_tb('scan_rows(int[])', performance_warnings := true); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+----------------------------------------------------+--------+-----------------------------------------------------------------------------------+-------------+----------+-------+--------- scan_rows | | | 00000 | routine is marked as VOLATILE, should be IMMUTABLE | | When you fix this issue, please, recheck other functions that uses this function. | performance | | | (1 row) create or replace function scan_rows(int[]) returns void AS $$ declare x int[]; begin foreach x in array $1 loop raise notice 'row = %', x; end loop; end; $$ language plpgsql; select * from plpgsql_check_function_tb('scan_rows(int[])', performance_warnings := true); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+--------------------+----------+----------------------------------------------------+------------------------------------------+-----------------------------------------------------------------------------------+-------------+----------+-------+--------- scan_rows | 5 | FOREACH over array | 42804 | target type is different type than source type | cast "integer" value to "integer[]" type | There are no possible explicit coercion between those types, possibly bug! | warning | | | scan_rows | | | 00000 | routine is marked as VOLATILE, should be IMMUTABLE | | When you fix this issue, please, recheck other functions that uses this function. | performance | | | (2 rows) drop function scan_rows(int[]); drop function fx(); ERROR: function fx() does not exist drop type t1; drop type t2; create table t1(a int, b int); create table t2(a int, b int, c int); create table t3(a numeric, b int); insert into t1 values(10,20),(30,40); create or replace function fx() returns int as $$ declare s int default 0; r t1; begin foreach r in array (select array_agg(t1) from t1) loop s := r.a + r.b; end loop; return s; end; $$ language plpgsql; select * from plpgsql_check_function_tb('fx()', performance_warnings := true); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+-------------------------------------------------+--------+-----------------------------------------------------------------------------------+-------------+----------+-------+--------- fx | | | 00000 | routine is marked as VOLATILE, should be STABLE | | When you fix this issue, please, recheck other functions that uses this function. | performance | | | (1 row) create or replace function fx() returns int as $$ declare s int default 0; r t1; c t1[]; begin c := (select array_agg(t1) from t1); foreach r in array c loop s := r.a + r.b; end loop; return s; end; $$ language plpgsql; select * from plpgsql_check_function_tb('fx()', performance_warnings := true); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+-------------------------------------------------+--------+-----------------------------------------------------------------------------------+-------------+----------+-------+--------- fx | | | 00000 | routine is marked as VOLATILE, should be STABLE | | When you fix this issue, please, recheck other functions that uses this function. | performance | | | (1 row) create or replace function fx() returns int as $$ declare s int default 0; r t1; c t1[]; begin select array_agg(t1) into c from t1; foreach r in array c loop s := r.a + r.b; end loop; return s; end; $$ language plpgsql; select * from plpgsql_check_function_tb('fx()', performance_warnings := true); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+-------------------------------------------------+--------+-----------------------------------------------------------------------------------+-------------+----------+-------+--------- fx | | | 00000 | routine is marked as VOLATILE, should be STABLE | | When you fix this issue, please, recheck other functions that uses this function. | performance | | | (1 row) create or replace function fx() returns int as $$ declare s int default 0; r t1; c t1[]; begin select array_agg(t1) into c from t1; for i in array_lower(c, 1) .. array_upper(c, 1) loop r := c[i]; s := r.a + r.b; end loop; return s; end; $$ language plpgsql; select * from plpgsql_check_function_tb('fx()', performance_warnings := true); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+-------------------------------------------------+--------+-----------------------------------------------------------------------------------+-------------+----------+-------+--------- fx | | | 00000 | routine is marked as VOLATILE, should be STABLE | | When you fix this issue, please, recheck other functions that uses this function. | performance | | | (1 row) create or replace function fx() returns int as $$ declare s int default 0; c t1[]; begin select array_agg(t1) into c from t1; for i in array_lower(c, 1) .. array_upper(c, 1) loop s := (c[i]).a + (c[i]).b; end loop; return s; end; $$ language plpgsql; select * from plpgsql_check_function_tb('fx()', performance_warnings := true); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+-------------------------------------------------+--------+-----------------------------------------------------------------------------------+-------------+----------+-------+--------- fx | | | 00000 | routine is marked as VOLATILE, should be STABLE | | When you fix this issue, please, recheck other functions that uses this function. | performance | | | (1 row) create or replace function fx() returns int as $$ declare s int default 0; r record; c t1[]; begin select array_agg(t1) into c from t1; for i in array_lower(c, 1) .. array_upper(c, 1) loop r := c[i]; s := r.a + r.b; end loop; return s; end; $$ language plpgsql; select * from plpgsql_check_function_tb('fx()', performance_warnings := true); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+-------------------------------------------------+--------+-----------------------------------------------------------------------------------+-------------+----------+-------+--------- fx | | | 00000 | routine is marked as VOLATILE, should be STABLE | | When you fix this issue, please, recheck other functions that uses this function. | performance | | | (1 row) create or replace function fx() returns int as $$ declare s int default 0; r record; c t1[]; begin select array_agg(t1) into c from t1; for i in array_lower(c, 1) .. array_upper(c, 1) loop r := c[i]; s := r.a + r.b + r.c; end loop; return s; end; $$ language plpgsql; select * from plpgsql_check_function_tb('fx()', performance_warnings := true); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+------------+----------+-----------------------------+--------+------+-------+----------+-------+-------------------------------------------- fx | 11 | assignment | 42703 | record "r" has no field "c" | | | error | | | PL/pgSQL assignment "s := r.a + r.b + r.c" (1 row) create or replace function fx() returns int as $$ declare s int default 0; r t2; begin foreach r in array (select array_agg(t1) from t1) loop s := r.a + r.b; end loop; return s; end; $$ language plpgsql; select * from plpgsql_check_function_tb('fx()', performance_warnings := true); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+--------------------+----------+-------------------------------------------------+--------+-----------------------------------------------------------------------------------+-------------+----------+-------+--------- fx | 6 | FOREACH over array | 00000 | too few attributes for composite variable | | | warning | | | fx | | | 00000 | routine is marked as VOLATILE, should be STABLE | | When you fix this issue, please, recheck other functions that uses this function. | performance | | | (2 rows) create or replace function fx() returns int as $$ declare s int default 0; r t3; begin foreach r in array (select array_agg(t1) from t1) loop s := r.a + r.b; end loop; return s; end; $$ language plpgsql; select * from plpgsql_check_function_tb('fx()', performance_warnings := true); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+--------------------+----------+-------------------------------------------------+----------------------------------------+-----------------------------------------------------------------------------------+-------------+----------+-------+-------------------------------------------------- fx | 6 | FOREACH over array | 42804 | target type is different type than source type | cast "integer" value to "numeric" type | Hidden casting can be a performance issue. | performance | | | fx | 8 | assignment | 42804 | target type is different type than source type | cast "numeric" value to "integer" type | Hidden casting can be a performance issue. | performance | | | at assignment to variable "s" declared on line 3 fx | | | 00000 | routine is marked as VOLATILE, should be STABLE | | When you fix this issue, please, recheck other functions that uses this function. | performance | | | (3 rows) drop function fx(); drop table t1; -- mscottie issue #13 create table test ( a text, b integer, c uuid ); create function before_insert_test() returns trigger language plpgsql as $$ begin select a into NEW.a from test where b = 1; select b into NEW.b from test where b = 1; select null::uuid into NEW.c from test where b = 1; return new; end; $$; select * from plpgsql_check_function_tb('before_insert_test()','test'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+---------+--------+------+-------+----------+-------+--------- (0 rows) create or replace function before_insert_test() returns trigger language plpgsql as $$ begin NEW.a := (select a from test where b = 1); NEW.b := (select b from test where b = 1); NEW.c := (select c from test where b = 1); return new; end; $$; select * from plpgsql_check_function_tb('before_insert_test()','test', fatal_errors := false); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+---------+--------+------+-------+----------+-------+--------- (0 rows) create or replace function before_insert_test() returns trigger language plpgsql as $$ begin NEW.a := 'Hello'::text; NEW.b := 10; NEW.c := null::uuid; return new; end; $$; select * from plpgsql_check_function_tb('before_insert_test()','test', fatal_errors := false); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+---------+--------+------+-------+----------+-------+--------- (0 rows) drop function before_insert_test(); create or replace function fx() returns void as $$ declare NEW test; OLD test; begin select null::uuid into NEW.c from test where b = 1; end; $$ language plpgsql; select * from plpgsql_check_function_tb('fx()', performance_warnings := true, fatal_errors := false); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+---------------------------+--------+------+---------------+----------+-------+--------- fx | 2 | DECLARE | 00000 | unused variable "old" | | | warning | | | fx | 2 | DECLARE | 00000 | never read variable "new" | | | warning extra | | | (2 rows) drop function fx(); create or replace function fx() returns void as $$ declare NEW test; begin NEW.a := 'Hello'::text; NEW.b := 10; NEW.c := null::uuid; end; $$ language plpgsql; select * from plpgsql_check_function_tb('fx()', performance_warnings := true, fatal_errors := false); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+----------------------------------------------------+--------+-----------------------------------------------------------------------------------+---------------+----------+-------+--------- fx | 2 | DECLARE | 00000 | never read variable "new" | | | warning extra | | | fx | | | 00000 | routine is marked as VOLATILE, should be IMMUTABLE | | When you fix this issue, please, recheck other functions that uses this function. | performance | | | (2 rows) drop function fx(); drop table test; create or replace function fx() returns void as $$ declare s int; sa int[]; sd date; bs int[]; begin sa[10] := s; sa[10] := sd; s := bs[10]; end; $$ language plpgsql; select * from plpgsql_check_function_tb('fx()', performance_warnings := true, fatal_errors := false); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+------------+----------+------------------------------------------------+-------------------------------------+----------------------------------------------------------------------------+---------------+----------+-------+--------------------------------------------------- fx | 9 | assignment | 42804 | target type is different type than source type | cast "date" value to "integer" type | There are no possible explicit coercion between those types, possibly bug! | warning | | | at assignment to variable "sa" declared on line 4 fx | 4 | DECLARE | 00000 | never read variable "sa" | | | warning extra | | | (2 rows) drop function fx(); create type t as (t text); create or replace function fx() returns void as $$ declare _t t; _tt t[]; _txt text; begin _t.t := 'ABC'; -- correct warning "unknown" _tt[1] := _t; _txt := _t; end; $$ language plpgsql; select * from plpgsql_check_function('fx()', performance_warnings := true); plpgsql_check_function ------------------------------------------------------------------------------- performance:42804:7:assignment:target type is different type than source type Detail: cast "t" value to "text" type Hint: Hidden casting can be a performance issue. Context: at assignment to variable "_txt" declared on line 3 warning extra:00000:2:DECLARE:never read variable "_tt" warning extra:00000:3:DECLARE:never read variable "_txt" (6 rows) drop function fx(); create or replace function fx() returns void as $$ declare _t1 t; _t2 t; begin _t1.t := 'ABC'::text; _t2 := _t1; raise notice '% %', _t2, _t2.t; end; $$ language plpgsql; select * from plpgsql_check_function('fx()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------------- performance:00000:routine is marked as VOLATILE, should be IMMUTABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (2 rows) drop function fx(); create or replace function fx(out _tt t[]) as $$ declare _t t; begin _t.t := 'ABC'::text; _tt[1] := _t; end; $$ language plpgsql; select * from plpgsql_check_function('fx()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------------- performance:00000:routine is marked as VOLATILE, should be IMMUTABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (2 rows) drop function fx(); drop type t; create or replace function fx() returns int as $$ declare x int; begin perform 1; return 10; end; $$ language plpgsql; select * from plpgsql_check_function('fx()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------------- warning:00000:2:DECLARE:unused variable "x" performance:00000:routine is marked as VOLATILE, should be IMMUTABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (3 rows) drop function fx(); create table t(i int); create function test_t(OUT t) returns t AS $$ begin $1 := null; end; $$ language plpgsql; select test_t(); test_t -------- (1 row) select * from test_t(); i --- (1 row) select * from plpgsql_check_function('test_t()', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------------- performance:00000:routine is marked as VOLATILE, should be IMMUTABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (2 rows) create or replace function fx() returns void as $$ declare c cursor for select * from t; x varchar; begin open c; fetch c into x; close c; end; $$ language plpgsql; select test_t(); test_t -------- (1 row) select * from test_t(); i --- (1 row) select * from plpgsql_check_function('fx()', performance_warnings := true, fatal_errors := false); plpgsql_check_function -------------------------------------------------------------------------- performance:42804:7:FETCH:target type is different type than source type Detail: cast "integer" value to "character varying" type Hint: Hidden casting can be a performance issue. warning extra:00000:4:DECLARE:never read variable "x" (4 rows) drop function fx(); create or replace function fx() returns void as $$ declare c cursor for select * from t; x int; begin open c; fetch c into x; close c; end; $$ language plpgsql; select test_t(); test_t -------- (1 row) select * from test_t(); i --- (1 row) select * from plpgsql_check_function('fx()', performance_warnings := true, fatal_errors := false); plpgsql_check_function ------------------------------------------------------- warning extra:00000:4:DECLARE:never read variable "x" (1 row) drop function fx(); create or replace function fx() returns void as $$ declare c cursor for select * from t; begin for r in c loop raise notice '%', r.a; end loop; end; $$ language plpgsql; select test_t(); test_t -------- (1 row) select * from test_t(); i --- (1 row) select * from plpgsql_check_function('fx()', performance_warnings := true, fatal_errors := false); plpgsql_check_function ------------------------------------------------------- error:42703:6:RAISE:record "r" has no field "a" Context: SQL expression "r.a" warning extra:00000:5:DECLARE:never read variable "r" (3 rows) drop function fx(); create or replace function fx() returns void as $$ declare c cursor for select * from t; begin for r in c loop raise notice '%', r.i; end loop; end; $$ language plpgsql; select test_t(); test_t -------- (1 row) select * from test_t(); i --- (1 row) select * from plpgsql_check_function('fx()', performance_warnings := true, fatal_errors := false); plpgsql_check_function ------------------------ (0 rows) drop function fx(); create table foo(a int, b int); create or replace function fx() returns void as $$ declare f1 int; f2 int; begin select 1, 2 into f1; select 1 into f1, f2; select a b into f1, f2 from foo; end; $$ language plpgsql; select fx(); fx ---- (1 row) select * from plpgsql_check_function('fx()', performance_warnings := true, fatal_errors := false); plpgsql_check_function ------------------------------------------------------------------------ warning:00000:4:SQL statement:too many attributes for target variables Detail: There are less target variables than output columns in query. Hint: Check target variables in SELECT INTO statement warning:00000:5:SQL statement:too few attributes for target variables Detail: There are more target variables than output columns in query. Hint: Check target variables in SELECT INTO statement. warning:00000:6:SQL statement:too few attributes for target variables Detail: There are more target variables than output columns in query. Hint: Check target variables in SELECT INTO statement. warning extra:00000:2:DECLARE:never read variable "f1" warning extra:00000:2:DECLARE:never read variable "f2" (11 rows) drop function fx(); drop table foo; create or replace function fx() returns void as $$ declare d date; begin d := (select 1 from pg_class limit 1); raise notice '%', d; end; $$ language plpgsql; select fx(); ERROR: invalid input syntax for type date: "1" CONTEXT: PL/pgSQL assignment "d := (select 1 from pg_class limit 1)" PL/pgSQL function fx() line 4 at assignment select * from plpgsql_check_function('fx()', performance_warnings := true, fatal_errors := false); plpgsql_check_function ---------------------------------------------------------------------------------- warning:42804:4:assignment:target type is different type than source type Detail: cast "integer" value to "date" type Hint: There are no possible explicit coercion between those types, possibly bug! Context: at assignment to variable "d" declared on line 2 (4 rows) drop function fx(); create table tab_1(i int); create or replace function fx(a int) returns setof int as $$ declare c refcursor; r record; begin open c for select i from tab_1 where i = a; loop fetch c into r; if not found then exit; end if; return next r.i; end loop; end; $$ language plpgsql; select * from plpgsql_check_function('fx(int)', performance_warnings := true, fatal_errors := false); plpgsql_check_function ----------------------------------------------------------------------------------------- performance:00000:routine is marked as VOLATILE, should be STABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (2 rows) create or replace function fx(a int) returns setof int as $$ declare c refcursor; r record; begin open c for select i from tab_1 where i = a; loop fetch c into r; if not found then exit; end if; return next r.x; end loop; end; $$ language plpgsql; select * from plpgsql_check_function('fx(int)', performance_warnings := true, fatal_errors := false); plpgsql_check_function ----------------------------------------------------------------------------------------- error:42703:12:RETURN NEXT:record "r" has no field "x" Context: SQL expression "r.x" warning extra:00000:4:DECLARE:never read variable "r" performance:00000:routine is marked as VOLATILE, should be STABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (5 rows) drop function fx(int); drop table tab_1; create or replace function fxx() returns void as $$ begin rollback; end; $$ language plpgsql; select fxx(); ERROR: invalid transaction termination CONTEXT: PL/pgSQL function fxx() line 3 at ROLLBACK select * from plpgsql_check_function('fxx()'); plpgsql_check_function -------------------------------------------------------- error:2D000:3:ROLLBACK:invalid transaction termination (1 row) drop function fxx(); create or replace function fxx() returns void as $$ declare x int; begin declare x int; begin end; end; $$ language plpgsql; select * from plpgsql_check_function('fxx()'); plpgsql_check_function ------------------------------------------------------------------------------------------ warning extra:00000:5:statement block:variable "x" shadows a previously defined variable Hint: SET plpgsql.extra_warnings TO 'shadowed_variables' warning:00000:2:DECLARE:unused variable "x" warning:00000:4:DECLARE:unused variable "x" (4 rows) select * from plpgsql_check_function('fxx()', extra_warnings := false); plpgsql_check_function --------------------------------------------- warning:00000:2:DECLARE:unused variable "x" warning:00000:4:DECLARE:unused variable "x" (2 rows) drop function fxx(); create or replace function fxx(in a int, in b int, out c int, out d int) as $$ begin c := a; end; $$ language plpgsql; select * from plpgsql_check_function('fxx(int, int)'); plpgsql_check_function ------------------------------------------------- warning extra:00000:unused parameter "b" warning extra:00000:unmodified OUT variable "d" (2 rows) create or replace function fxx(in a int, in b int, out c int, out d int) as $$ begin c := d; end; $$ language plpgsql; select * from plpgsql_check_function('fxx(int, int)'); plpgsql_check_function ------------------------------------------------- warning extra:00000:unused parameter "a" warning extra:00000:unused parameter "b" warning extra:00000:unmodified OUT variable "d" (3 rows) create type ct as (a int, b int); create or replace function fxx(a ct, b ct, OUT c ct, OUT d ct) as $$ begin c.a := a.a; end; $$ language plpgsql; select * from plpgsql_check_function('fxx(ct, ct)'); plpgsql_check_function ----------------------------------------------------------------------- warning extra:00000:unused parameter "b" warning extra:00000:composite OUT variable "c" is not single argument warning extra:00000:composite OUT variable "d" is not single argument warning extra:00000:unmodified OUT variable "d" (4 rows) create or replace function fxx(a ct, b ct, OUT c ct, OUT d ct) as $$ begin c.a := d.a; end; $$ language plpgsql; select * from plpgsql_check_function('fxx(ct, ct)'); plpgsql_check_function ----------------------------------------------------------------------- warning extra:00000:unused parameter "a" warning extra:00000:unused parameter "b" warning extra:00000:composite OUT variable "c" is not single argument warning extra:00000:composite OUT variable "d" is not single argument warning extra:00000:unmodified OUT variable "d" (5 rows) create or replace function tx(a int) returns int as $$ declare a int; ax int; begin declare ax int; begin ax := 10; end; a := 10; return 20; end; $$ language plpgsql; select * from plpgsql_check_function('tx(int)'); plpgsql_check_function ------------------------------------------------------------------------------------------- warning:00000:3:statement block:parameter "a" is shadowed Detail: Local variable shadows function parameter. warning extra:00000:5:statement block:variable "ax" shadows a previously defined variable Hint: SET plpgsql.extra_warnings TO 'shadowed_variables' warning:00000:2:DECLARE:unused variable "ax" warning extra:00000:2:DECLARE:never read variable "a" warning extra:00000:4:DECLARE:never read variable "ax" warning extra:00000:unused parameter "a" (8 rows) create type xt as (a int, b int, c int); create or replace function fx_xt(out x xt) as $$ declare l xt; a int; begin return; end; $$ language plpgsql; select * from plpgsql_check_function('fx_xt()'); plpgsql_check_function ------------------------------------------------- warning:00000:2:DECLARE:unused variable "l" warning:00000:3:DECLARE:unused variable "a" warning extra:00000:unmodified OUT variable "x" (3 rows) drop function fx_xt(); create or replace function fx_xt(out x xt) as $$ declare l xt; a int; begin x.c := 1000; return; end; $$ language plpgsql; select * from plpgsql_check_function('fx_xt()'); plpgsql_check_function --------------------------------------------- warning:00000:2:DECLARE:unused variable "l" warning:00000:3:DECLARE:unused variable "a" (2 rows) drop function fx_xt(); create or replace function fx_xt(out x xt, out y xt) as $$ declare c1 xt; c2 xt; begin return; end; $$ language plpgsql; select * from plpgsql_check_function('fx_xt()'); plpgsql_check_function ----------------------------------------------------------------------- warning:00000:2:DECLARE:unused variable "c1" warning:00000:2:DECLARE:unused variable "c2" warning extra:00000:composite OUT variable "x" is not single argument warning extra:00000:unmodified OUT variable "x" warning extra:00000:composite OUT variable "y" is not single argument warning extra:00000:unmodified OUT variable "y" (6 rows) drop function fx_xt(); create or replace function fx_xt(out x xt, out y xt) as $$ declare c1 xt; c2 xt; begin x.a := 100; y := row(10,20,30); return; end; $$ language plpgsql; select * from plpgsql_check_function('fx_xt()'); plpgsql_check_function ----------------------------------------------------------------------- warning:00000:2:DECLARE:unused variable "c1" warning:00000:2:DECLARE:unused variable "c2" warning extra:00000:composite OUT variable "x" is not single argument warning extra:00000:composite OUT variable "y" is not single argument (4 rows) drop function fx_xt(); create or replace function fx_xt(out x xt, out z int) as $$ begin return; end; $$ language plpgsql; select * from plpgsql_check_function('fx_xt()'); plpgsql_check_function ----------------------------------------------------------------------- warning extra:00000:composite OUT variable "x" is not single argument warning extra:00000:unmodified OUT variable "x" warning extra:00000:unmodified OUT variable "z" (3 rows) drop function fx_xt(); drop type xt; -- missing RETURN create or replace function fx_flow() returns int as $$ begin raise notice 'kuku'; end; $$ language plpgsql; select fx_flow(); NOTICE: kuku ERROR: control reached end of function without RETURN CONTEXT: PL/pgSQL function fx_flow() select * from plpgsql_check_function('fx_flow()'); plpgsql_check_function ------------------------------------------------------------ error:2F005:control reached end of function without RETURN (1 row) -- ok create or replace function fx_flow() returns int as $$ declare a int; begin if a > 10 then return a; end if; return 10; end; $$ language plpgsql; select * from plpgsql_check_function('fx_flow()'); plpgsql_check_function ------------------------ (0 rows) -- dead code create or replace function fx_flow() returns int as $$ declare a int; begin if a > 10 then return a; else return a + 1; end if; return 10; end; $$ language plpgsql; select * from plpgsql_check_function('fx_flow()'); plpgsql_check_function ----------------------------------------------- warning extra:00000:9:RETURN:unreachable code (1 row) -- missing return create or replace function fx_flow() returns int as $$ declare a int; begin if a > 10 then return a; end if; end; $$ language plpgsql; select * from plpgsql_check_function('fx_flow()'); plpgsql_check_function -------------------------------------------------------------------- warning extra:2F005:control reached end of function without RETURN (1 row) drop function fx_flow(); create or replace function fx_flow(in p_param1 integer) returns text as $$ declare z1 text; begin if p_param1 is not null then z1 := '1111'; return z1; else z1 := '222222'; end if; return z1; end; $$ language plpgsql stable; select * from plpgsql_check_function_tb('fx_flow(integer)'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+---------+--------+------+-------+----------+-------+--------- (0 rows) create or replace function fx_flow(in p_param1 integer) returns text as $$ declare z1 text; begin if p_param1 is not null then z1 := '1111'; return z1; else z1 := '222222'; raise exception 'stop'; end if; return z1; end; $$ language plpgsql stable; select * from plpgsql_check_function_tb('fx_flow(integer)'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+------------------+--------+------+---------------+----------+-------+--------- fx_flow | 12 | RETURN | 00000 | unreachable code | | | warning extra | | | (1 row) drop function fx_flow(); ERROR: function fx_flow() does not exist drop function fx(int); ERROR: function fx(integer) does not exist create or replace function fx(x int) returns table(y int) as $$ begin return query select x union select x; end $$ language plpgsql; select * from fx(10); y ---- 10 (1 row) select * from plpgsql_check_function_tb('fx(int)'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+---------+--------+------+-------+----------+-------+--------- (0 rows) drop function fx(int); create or replace function fx(x int) returns table(y int, z int) as $$ begin return query select x,x+1 union select x, x+1; end $$ language plpgsql; select * from fx(10); y | z ----+---- 10 | 11 (1 row) select * from plpgsql_check_function_tb('fx(int)'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+---------+--------+------+-------+----------+-------+--------- (0 rows) drop function fx(int); create table xx(a int); create or replace function fx(x int) returns int as $$ declare _a int; begin begin select a from xx into strict _a where a = x; return _a; exception when others then null; end; return -1; end; $$ language plpgsql; select * from plpgsql_check_function_tb('fx(int)'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+---------+--------+------+-------+----------+-------+--------- (0 rows) drop table xx; create or replace function fx(x int) returns int as $$ begin begin if (x > 0) then raise exception 'xxx' using errcode = 'XX888'; else raise exception 'yyy' using errcode = 'YY888'; end if; return -1; -- dead code; end; return -1; end; $$ language plpgsql; select * from plpgsql_check_function_tb('fx(int)'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+------------------+--------+------+---------------+----------+-------+--------- fx | 9 | RETURN | 00000 | unreachable code | | | warning extra | | | fx | 11 | RETURN | 00000 | unreachable code | | | warning extra | | | (2 rows) create or replace function fx(x int) returns int as $$ begin begin if (x > 0) then raise exception 'xxx' using errcode = 'XX888'; else raise exception 'yyy' using errcode = 'YY888'; end if; exception when sqlstate 'XX888' then null; when sqlstate 'YY888' then null; end; end; -- missing return; $$ language plpgsql; select * from plpgsql_check_function_tb('fx(int)'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+------------------------------------------------+--------+------+-------+----------+-------+--------- fx | | | 2F005 | control reached end of function without RETURN | | | error | | | (1 row) create or replace function fx(x int) returns int as $$ begin begin if (x > 0) then raise exception 'xxx' using errcode = 'XX888'; else raise exception 'yyy' using errcode = 'YY888'; end if; exception when others then return 10; end; end; -- ok now $$ language plpgsql; select * from plpgsql_check_function_tb('fx(int)'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+---------+--------+------+-------+----------+-------+--------- (0 rows) --false alarm reported by Filip Zach create type testtype as (id integer); create or replace function fx() returns testtype as $$ begin return row(1); end; $$ language plpgsql; select * from fx(); id ---- 1 (1 row) select fx(); fx ----- (1) (1 row) select * from plpgsql_check_function('fx()'); plpgsql_check_function ------------------------ (0 rows) drop function fx(); create function out1(OUT f1 int, OUT f2 int) returns setof record as $$ begin for f1, f2 in execute $q$ select 1, 2 $q$ loop return next; end loop; end $$ language plpgsql; select * from plpgsql_check_function('out1()'); plpgsql_check_function ------------------------ (0 rows) drop function out1(); create function out1(OUT f1 int, OUT f2 int) returns setof record as $$ begin for f1, f2 in select 1, 2 loop return next; end loop; end $$ language plpgsql; select * from plpgsql_check_function('out1()'); plpgsql_check_function ------------------------ (0 rows) drop function out1(); -- never read variable detection create function a() returns int as $$ declare foo int; begin foo := 2; return 1; end; $$ language plpgsql; select * from plpgsql_check_function('a()'); plpgsql_check_function --------------------------------------------------------- warning extra:00000:2:DECLARE:never read variable "foo" (1 row) drop function a(); -- issue #29 false unused variable create or replace function f1(in p_cursor refcursor) returns void as $body$ declare z_offset integer; begin z_offset := 10; move absolute z_offset from p_cursor; end; $body$ language 'plpgsql' stable; select * from plpgsql_check_function_tb('f1(refcursor)'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+---------+--------+------+-------+----------+-------+--------- (0 rows) drop function f1(refcursor); -- issue #30 segfault due NULL refname create or replace function test(a varchar) returns void as $$ declare x cursor (_a varchar) for select _a; begin open x(a); end; $$ language plpgsql; select * from plpgsql_check_function_tb('test(varchar)'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+-------------------------+--------+------+---------------+----------+-------+--------- test | 2 | DECLARE | 00000 | never read variable "x" | | | warning extra | | | (1 row) drop function test(varchar); create or replace function test() returns void as $$ declare x numeric; begin x := NULL; end; $$ language plpgsql; select * from plpgsql_check_function('test()'); plpgsql_check_function ------------------------------------------------------- warning extra:00000:2:DECLARE:never read variable "x" (1 row) drop function test(); create table testtable(a int); create or replace function test() returns int as $$ declare r testtable; begin select * into r from testtable; return r.a; end; $$ language plpgsql; select * from plpgsql_check_function('test()'); plpgsql_check_function ------------------------ (0 rows) set check_function_bodies to on; drop table testtable; create table testtable(a int, b int); create or replace function test() returns int as $$ declare r testtable; begin select * into r from testtable; return r.a; end; $$ language plpgsql; alter table testtable drop column b; -- expected false alarm on PostgreSQL 10 and older -- there is not possibility to enforce recompilation -- before checking. select * from plpgsql_check_function('test()'); plpgsql_check_function ------------------------ (0 rows) drop function test(); -- issue #32 create table bigtable(id bigint, v varchar); create or replace function test() returns void as $$ declare r record; _id numeric; begin select * into r from bigtable where id = _id; for r in select * from bigtable where _id = id loop end loop; if (exists(select * from bigtable where id = _id)) then end if; end; $$ language plpgsql; select test(); test ------ (1 row) -- should to show performance warnings select * from plpgsql_check_function('test()', performance_warnings := true); plpgsql_check_function ------------------------------------------------------------------------------------------------------------------------------- performance:42804:6:SQL statement:implicit cast of attribute caused by different PLpgSQL variable type in WHERE clause Query: select * from bigtable where id = _id -- ^ Detail: An index of some attribute cannot be used, when variable, used in predicate, has not right type like a attribute Hint: Check a variable type - int versus numeric performance:42804:7:FOR over SELECT rows:implicit cast of attribute caused by different PLpgSQL variable type in WHERE clause Query: select * from bigtable where _id = id -- ^ Detail: An index of some attribute cannot be used, when variable, used in predicate, has not right type like a attribute Hint: Check a variable type - int versus numeric performance:42804:10:IF:implicit cast of attribute caused by different PLpgSQL variable type in WHERE clause Query: (exists(select * from bigtable where id = _id)) -- ^ Detail: An index of some attribute cannot be used, when variable, used in predicate, has not right type like a attribute Hint: Check a variable type - int versus numeric warning extra:00000:3:DECLARE:never read variable "r" (16 rows) create or replace function test() returns void as $$ declare r record; _id bigint; begin select * into r from bigtable where id = _id; for r in select * from bigtable where _id = id loop end loop; if (exists(select * from bigtable where id = _id)) then end if; end; $$ language plpgsql; -- there are not any performance issue now select * from plpgsql_check_function('test()', performance_warnings := true); plpgsql_check_function ------------------------------------------------------- warning extra:00000:3:DECLARE:never read variable "r" (1 row) -- nextval, currval and setval test create table test_table(); create or replace function testseq() returns void as $$ begin perform nextval('test_table'); perform currval('test_table'); perform setval('test_table', 10); perform setval('test_table', 10, true); end; $$ language plpgsql; -- should to fail select testseq(); ERROR: "test_table" is not a sequence CONTEXT: SQL statement "SELECT nextval('test_table')" PL/pgSQL function testseq() line 3 at PERFORM select * from plpgsql_check_function('testseq()', fatal_errors := false); plpgsql_check_function ------------------------------------------------------ error:42809:3:PERFORM:"test_table" is not a sequence Query: SELECT nextval('test_table') -- ^ error:42809:4:PERFORM:"test_table" is not a sequence Query: SELECT currval('test_table') -- ^ error:42809:5:PERFORM:"test_table" is not a sequence Query: SELECT setval('test_table', 10) -- ^ error:42809:6:PERFORM:"test_table" is not a sequence Query: SELECT setval('test_table', 10, true) -- ^ (12 rows) drop function testseq(); drop table test_table; -- tests designed for PostgreSQL 9.2 set check_function_bodies to off; create table t1(a int, b int); create function f1() returns void as $$ begin if false then update t1 set c = 30; end if; if false then raise notice '%', r.c; end if; end; $$ language plpgsql; select f1(); f1 ---- (1 row) select * from plpgsql_check_function_tb('f1()', fatal_errors := true); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+---------------+----------+--------------------------------------------+--------+------+-------+----------+----------------------+--------- f1 | 4 | SQL statement | 42703 | column "c" of relation "t1" does not exist | | | error | 15 | update t1 set c = 30 | (1 row) select * from plpgsql_check_function_tb('f1()', fatal_errors := false); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+---------------+----------+--------------------------------------------+--------+------+-------+----------+----------------------+--------- f1 | 4 | SQL statement | 42703 | column "c" of relation "t1" does not exist | | | error | 15 | update t1 set c = 30 | f1 | 7 | RAISE | 42P01 | missing FROM-clause entry for table "r" | | | error | 1 | r.c | (2 rows) select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+---------------+----------+--------------------------------------------+--------+------+-------+----------+----------------------+--------- f1 | 4 | SQL statement | 42703 | column "c" of relation "t1" does not exist | | | error | 15 | update t1 set c = 30 | (1 row) select f1(); f1 ---- (1 row) drop function f1(); create or replace function f1() returns void as $$ begin if false then raise notice '%', 1, 2; end if; end; $$ language plpgsql; select f1(); ERROR: too many parameters specified for RAISE CONTEXT: compilation of PL/pgSQL function "f1" near line 4 select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+-----------------------------------------+--------+------+-------+----------+-------+--------------------------------------------------- f1 | | | 42601 | too many parameters specified for RAISE | | | error | | | compilation of PL/pgSQL function "f1" near line 4 (1 row) select f1(); ERROR: too many parameters specified for RAISE CONTEXT: compilation of PL/pgSQL function "f1" near line 4 drop function f1(); create or replace function f1() returns void as $$ begin if false then raise notice '% %'; end if; end; $$ language plpgsql; select f1(); ERROR: too few parameters specified for RAISE CONTEXT: compilation of PL/pgSQL function "f1" near line 4 select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+----------------------------------------+--------+------+-------+----------+-------+--------------------------------------------------- f1 | | | 42601 | too few parameters specified for RAISE | | | error | | | compilation of PL/pgSQL function "f1" near line 4 (1 row) select f1(); ERROR: too few parameters specified for RAISE CONTEXT: compilation of PL/pgSQL function "f1" near line 4 drop function f1(); create or replace function ml_trg() returns trigger as $$ #option dump declare begin if TG_OP = 'INSERT' then if NEW.status_from IS NULL then begin -- performance issue only select status into NEW.status_from from pa where pa_id = NEW.pa_id; -- nonexist target value select status into NEW.status_from_xxx from pa where pa_id = NEW.pa_id; exception when DATA_EXCEPTION then new.status_from := 'DE'; end; end if; end if; if TG_OP = 'DELETE' then return OLD; else return NEW; end if; exception when OTHERS then NULL; if TG_OP = 'DELETE' then return OLD; else return NEW; end if; end; $$ language plpgsql; select * from plpgsql_check_function('ml_trg()', 'ml', performance_warnings := true); plpgsql_check_function -------------------------------------------------------------------------- error:42703:13:SQL statement:record "new" has no field "status_from_xxx" (1 row) create or replace function fx2() returns void as $$ declare _pa pa; begin select pa.id into _pa.id from pa limit 1; select pa.pa_id into _pa.pa_id from pa limit 1; end; $$ language plpgsql; select * from plpgsql_check_function('fx2()', performance_warnings := true); plpgsql_check_function --------------------------------------------------------- warning extra:00000:2:DECLARE:never read variable "_pa" (1 row) drop function fx2(); create or replace function fx2() returns void as $$ declare _pa pa; begin _pa.id := (select pa.id from pa limit 1); _pa.pa_id := (select pa.pa_id from pa limit 1); end; $$ language plpgsql; select * from plpgsql_check_function('fx2()', performance_warnings := true); plpgsql_check_function --------------------------------------------------------- warning extra:00000:2:DECLARE:never read variable "_pa" (1 row) drop function fx2(); create or replace function test_lab() returns void as $$ begin <> for a in 1..3 loop <> BEGIN <> for b in 8..9 loop if a=2 then continue sub; end if; raise notice '% %', a, b; end loop inner; END sub; end loop outer; end; $$ language plpgsql; select test_lab(); ERROR: block label "sub" cannot be used in CONTINUE LINE 10: continue sub; ^ QUERY: begin <> for a in 1..3 loop <> BEGIN <> for b in 8..9 loop if a=2 then continue sub; end if; raise notice '% %', a, b; end loop inner; END sub; end loop outer; end; CONTEXT: compilation of PL/pgSQL function "test_lab" near line 10 select * from plpgsql_check_function('test_lab()', performance_warnings := true); plpgsql_check_function ------------------------------------------------------------------- error:42601:block label "sub" cannot be used in CONTINUE Query: begin <> for a in 1..3 loop <> BEGIN <> for b in 8..9 loop if a=2 then continue sub; -- ^ end if; raise notice '% %', a, b; end loop inner; END sub; end loop outer; end; Context: compilation of PL/pgSQL function "test_lab" near line 10 (20 rows) create or replace function test_lab() returns void as $$ begin continue; end; $$ language plpgsql; select test_lab(); ERROR: CONTINUE cannot be used outside a loop LINE 3: continue; ^ QUERY: begin continue; end; CONTEXT: compilation of PL/pgSQL function "test_lab" near line 3 select * from plpgsql_check_function('test_lab()', performance_warnings := true); plpgsql_check_function ------------------------------------------------------------------ error:42601:CONTINUE cannot be used outside a loop Query: begin continue; -- ^ end; Context: compilation of PL/pgSQL function "test_lab" near line 3 (8 rows) create type _exception_type as ( state text, message text, detail text); create or replace function f1() returns void as $$ declare _exception record; begin _exception := NULL::_exception_type; exception when others then get stacked diagnostics _exception.state = RETURNED_SQLSTATE, _exception.message = MESSAGE_TEXT, _exception.detail = PG_EXCEPTION_DETAIL, _exception.hint = PG_EXCEPTION_HINT; end; $$ language plpgsql; select f1(); f1 ---- (1 row) select * from plpgsql_check_function('f1()'); plpgsql_check_function ------------------------------------------------------------------------------- error:42703:7:GET STACKED DIAGNOSTICS:record "_exception" has no field "hint" (1 row) drop function f1(); drop type _exception_type; drop table t1; create function myfunc1(a int, b float) returns integer as $$ begin end $$ language plpgsql; create function myfunc2(a int, b float) returns integer as $$ begin end $$ language plpgsql; create function myfunc3(a int, b float) returns integer as $$ begin end $$ language plpgsql; create function myfunc4(a int, b float) returns integer as $$ begin end $$ language plpgsql; create function opfunc1(a int, b float) returns integer as $$ begin end $$ language plpgsql; create operator *** (procedure = opfunc1, leftarg = int, rightarg = float); create table mytable(a int); create table myview as select * from mytable; create function testfunc(a int, b float) returns void as $$ declare x integer; begin raise notice '%', myfunc1(a, b); x := myfunc2(a, b) operator(public.***) 1; perform myfunc3(m.a, b) from myview m; insert into mytable select myfunc4(a, b); end; $$ language plpgsql; select * from plpgsql_check_function('testfunc(int,float)'); plpgsql_check_function ------------------------------------------------------- warning extra:00000:2:DECLARE:never read variable "x" (1 row) select type, schema, name, params from plpgsql_show_dependency_tb('testfunc(int,float)'); type | schema | name | params ----------+--------+---------+---------------------------- FUNCTION | public | myfunc1 | (integer,double precision) FUNCTION | public | myfunc2 | (integer,double precision) FUNCTION | public | myfunc3 | (integer,double precision) FUNCTION | public | myfunc4 | (integer,double precision) OPERATOR | public | *** | (integer,double precision) RELATION | public | mytable | RELATION | public | myview | (7 rows) drop function testfunc(int, float); drop function myfunc1(int, float); drop function myfunc2(int, float); drop function myfunc3(int, float); drop function myfunc4(int, float); drop table mytable; drop view myview; ERROR: "myview" is not a view HINT: Use DROP TABLE to remove a table. -- issue #34 create or replace function testcase() returns bool as $$ declare x int; begin set local search_path to public, test; case x when 1 then return true; else return false; end case; end; $$ language plpgsql; -- should not to raise warning select * from plpgsql_check_function('testcase()'); plpgsql_check_function ------------------------ (0 rows) drop function testcase(); -- Adam's Bartoszewicz example create or replace function public.test12() returns refcursor language plpgsql as $body$ declare rc refcursor; begin open rc scroll for select pc.* from pg_cast pc; return rc; end; $body$; -- should not returns false alarm select * from plpgsql_check_function('test12()'); plpgsql_check_function ------------------------ (0 rows) drop function public.test12(); -- should to show performance warning on bad flag create or replace function flag_test1(int) returns int as $$ begin return $1 + 10; end; $$ language plpgsql stable; create table fufu(a int); create or replace function flag_test2(int) returns int as $$ begin return (select * from fufu limit 1); end; $$ language plpgsql volatile; select * from plpgsql_check_function('flag_test1(int)', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------------- performance:00000:routine is marked as STABLE, should be IMMUTABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (2 rows) select * from plpgsql_check_function('flag_test2(int)', performance_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------------------- warning extra:00000:unused parameter "$1" performance:00000:routine is marked as VOLATILE, should be STABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (3 rows) drop table fufu; drop function flag_test1(int); drop function flag_test2(int); create or replace function rrecord01() returns setof record as $$ begin return query select 1,2; end; $$ language plpgsql; create or replace function rrecord02() returns record as $$ begin return row(10,20,30); end; $$ language plpgsql; create type record03 as (a int, b int); create or replace function rrecord03() returns record03 as $$ declare r record; begin r := row(1); return r; end; $$ language plpgsql; -- should not to raise false alarms select * from plpgsql_check_function('rrecord01'); plpgsql_check_function ------------------------ (0 rows) select * from plpgsql_check_function('rrecord02'); plpgsql_check_function ------------------------ (0 rows) -- should detect different return but still detect return select * from plpgsql_check_function('rrecord03', fatal_errors => false); plpgsql_check_function ---------------------------------------------------------------------------------- error:42804:5:RETURN:returned record type does not match expected record type Detail: Number of returned columns (1) does not match expected column count (2). (2 rows) drop function rrecord01(); drop function rrecord02(); drop function rrecord03(); drop type record03; create or replace function bugfunc01() returns void as $$ declare cvar cursor(a int, b int) for select a + b from generate_series(1,b); begin for t in cvar(1,3) loop raise notice '%', t; end loop; end; $$ language plpgsql; select bugfunc01(); NOTICE: (4) NOTICE: (4) NOTICE: (4) bugfunc01 ----------- (1 row) select * from plpgsql_check_function('bugfunc01'); plpgsql_check_function ------------------------ (0 rows) create or replace function bugfunc02() returns void as $$ declare cvar cursor(a int, b int) for select a + b from generate_series(1,b); begin open cvar(10,20); close cvar; end; $$ language plpgsql; select bugfunc02(); bugfunc02 ----------- (1 row) select * from plpgsql_check_function('bugfunc02'); plpgsql_check_function ------------------------ (0 rows) create or replace function bugfunc03() returns void as $$ declare cvar cursor(a int, b int) for select a + b from not_exists_table; begin open cvar(10,20); close cvar; end; $$ language plpgsql; select bugfunc03(); ERROR: relation "not_exists_table" does not exist LINE 1: select a + b from not_exists_table ^ QUERY: select a + b from not_exists_table CONTEXT: PL/pgSQL function bugfunc03() line 5 at OPEN select * from plpgsql_check_function('bugfunc03'); plpgsql_check_function --------------------------------------------------------------- error:42P01:5:OPEN:relation "not_exists_table" does not exist Query: select a + b from not_exists_table -- ^ (3 rows) create or replace function f1(out cr refcursor) as $$ begin end; $$ language plpgsql; -- should to raise warning select * from plpgsql_check_function('f1()'); plpgsql_check_function -------------------------------------------------- warning extra:00000:unmodified OUT variable "cr" (1 row) create or replace function f1(out cr refcursor) as $$ begin open cr for select 1; end; $$ language plpgsql; -- should not to raise warning, see issue #43 select * from plpgsql_check_function('f1()'); plpgsql_check_function ------------------------ (0 rows) drop function f1(); create table testt(a int); create or replace function testt_trg_func() returns trigger as $$ begin return new; end; $$ language plpgsql; create trigger testt_trg before insert or update on testt for each row execute procedure testt_trg_func(); create or replace function maintaince_function() returns void as $$ begin alter table testt disable trigger testt_trg; alter table testt enable trigger testt_trg; end; $$ language plpgsql; -- should not to crash select * from plpgsql_check_function_tb('maintaince_function()', 0, true, true, true); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+---------+--------+------+-------+----------+-------+--------- (0 rows) drop function maintaince_function(); drop trigger testt_trg on testt; drop function testt_trg_func(); drop table testt; create or replace function test_crash() returns void as $$ declare ec int default buggyfunc(10); begin select * into ec from buggytab; end; $$ language plpgsql; -- should not to crash select * from plpgsql_check_function('test_crash', fatal_errors := false); plpgsql_check_function --------------------------------------------------------------------------------------------------------- error:42883:4:statement block:function buggyfunc(integer) does not exist Query: buggyfunc(10) -- ^ Hint: No function matches the given name and argument types. You might need to add explicit type casts. Context: during statement block local variable "ec" initialization on line 3 error:42P01:5:SQL statement:relation "buggytab" does not exist Query: select * from buggytab -- ^ warning extra:00000:3:DECLARE:never read variable "ec" (9 rows) select * from plpgsql_check_function('test_crash', fatal_errors := true); plpgsql_check_function --------------------------------------------------------------------------------------------------------- error:42883:4:statement block:function buggyfunc(integer) does not exist Query: buggyfunc(10) -- ^ Hint: No function matches the given name and argument types. You might need to add explicit type casts. Context: during statement block local variable "ec" initialization on line 3 (5 rows) drop function test_crash(); -- fix false alarm reported by Piotr Stepniewski create or replace function public.fx() returns void language plpgsql as $function$ begin raise exception 'xxx'; end; $function$; -- show raise nothing select * from plpgsql_check_function('fx()'); plpgsql_check_function ------------------------ (0 rows) create table errtab( message text, code character(5) ); create or replace function public.fx() returns void language plpgsql as $function$ declare var errtab%rowtype; begin raise exception using message = var.message, errcode = var.code; end; $function$; -- should not to crash select * from plpgsql_check_function('fx()'); plpgsql_check_function ------------------------ (0 rows) create or replace function public.fx() returns void language plpgsql as $function$ declare var errtab%rowtype; begin raise exception using message = var.message, errcode = var.code, hint = var.hint; end; $function$; -- should not to crash select * from plpgsql_check_function('fx()'); plpgsql_check_function ------------------------------------------------------ error:42703:5:RAISE:record "var" has no field "hint" Context: SQL expression "var.hint" (2 rows) drop function fx(); create or replace function foo_format(a text, b text) returns void as $$ declare s text; begin s := format('%s'); -- should to raise error s := format('%s %10s', a, b); -- should be ok s := format('%s %s', a, b, a); -- should to raise warning s := format('%s %d', a, b); -- should to raise error raise notice '%', s; end; $$ language plpgsql; select * from plpgsql_check_function('foo_format', fatal_errors := false); plpgsql_check_function ------------------------------------------------------------------- error:22023:4:assignment:too few arguments for format() Query: s := format('%s') -- ^ Context: at assignment to variable "s" declared on line 2 warning:00000:6:assignment:unused parameters of function "format" Query: s := format('%s %s', a, b, a) -- ^ Context: at assignment to variable "s" declared on line 2 error:22023:7:assignment:unrecognized format() type specifier "d" Query: s := format('%s %d', a, b) -- ^ Context: at assignment to variable "s" declared on line 2 (12 rows) drop function foo_format(text, text); create or replace function dyn_sql_1() returns void as $$ declare v varchar; n int; begin execute 'select ' || n; -- ok execute 'select ' || quote_literal(v); -- ok execute 'select ' || v; -- vulnerable execute format('select * from %I', v); -- ok execute format('select * from %s', v); -- vulnerable execute 'select $1' using v; -- ok execute 'select 1'; -- ok execute 'select 1' using v; -- warning execute 'select $1'; -- error end; $$ language plpgsql; select * from plpgsql_check_function('dyn_sql_1', security_warnings := true, fatal_errors := false); plpgsql_check_function ------------------------------------------------------------------------------------------ security:00000:8:EXECUTE:text type variable is not sanitized Query: 'select ' || v -- ^ Detail: The EXECUTE expression is SQL injection vulnerable. Hint: Use quote_ident, quote_literal or format function to secure variable. security:00000:10:EXECUTE:text type variable is not sanitized Query: format('select * from %s', v) -- ^ Detail: The EXECUTE expression is SQL injection vulnerable. Hint: Use quote_ident, quote_literal or format function to secure variable. warning:00000:13:EXECUTE:values passed to EXECUTE statement by USING clause was not used error:42P02:14:EXECUTE:there is no parameter $1 Query: select $1 -- ^ (14 rows) drop function dyn_sql_1(); create type tp as (a int, b int); create or replace function dyn_sql_2() returns void as $$ declare r tp; result int; begin select 10 a, 20 b into r; raise notice '%', r.a; execute 'select $1.a + $1.b' into result using r; execute 'select $1.c' into result using r; -- error raise notice '%', result; end; $$ language plpgsql; select * from plpgsql_check_function('dyn_sql_2', security_warnings := true); plpgsql_check_function ------------------------------------------------------------ error:42703:9:EXECUTE:column "c" not found in data type tp Query: select $1.c -- ^ (3 rows) drop function dyn_sql_2(); drop type tp; /* * Should not to work * * note: plpgsql doesn't support passing some necessary details for record * type. The parser setup for dynamic SQL column doesn't use ref hooks, and * then it cannot to pass TupleDesc info to query anyway. */ create or replace function dyn_sql_2() returns void as $$ declare r record; result int; begin select 10 a, 20 b into r; raise notice '%', r.a; execute 'select $1.a + $1.b' into result using r; raise notice '%', result; end; $$ language plpgsql; select dyn_sql_2(); --should to fail NOTICE: 10 ERROR: could not identify column "a" in record data type LINE 1: select $1.a + $1.b ^ QUERY: select $1.a + $1.b CONTEXT: PL/pgSQL function dyn_sql_2() line 8 at EXECUTE select * from plpgsql_check_function('dyn_sql_2', security_warnings := true); plpgsql_check_function ------------------------------------------------------------------------- error:42703:8:EXECUTE:could not identify column "a" in record data type Query: select $1.a + $1.b -- ^ (3 rows) drop function dyn_sql_2(); create or replace function dyn_sql_3() returns void as $$ declare r int; begin execute 'select $1' into r using 1; raise notice '%', r; end $$ language plpgsql; select dyn_sql_3(); NOTICE: 1 dyn_sql_3 ----------- (1 row) -- should be ok select * from plpgsql_check_function('dyn_sql_3'); plpgsql_check_function ------------------------ (0 rows) create or replace function dyn_sql_3() returns void as $$ declare r record; begin execute 'select $1 as a, $2 as b' into r using 1, 2; raise notice '% %', r.a, r.b; end $$ language plpgsql; select dyn_sql_3(); NOTICE: 1 2 dyn_sql_3 ----------- (1 row) -- should be ok select * from plpgsql_check_function('dyn_sql_3'); plpgsql_check_function ------------------------ (0 rows) create or replace function dyn_sql_3() returns void as $$ declare r record; begin execute 'create table foo(a int)' into r using 1, 2; raise notice '% %', r.a, r.b; end $$ language plpgsql; -- raise a error select * from plpgsql_check_function('dyn_sql_3'); plpgsql_check_function ----------------------------------------------------------------------------------------- warning:00000:4:EXECUTE:values passed to EXECUTE statement by USING clause was not used error:XX000:4:EXECUTE:expression does not return data (2 rows) create or replace function dyn_sql_3() returns void as $$ declare r1 int; r2 int; begin execute 'select 1' into r1, r2 using 1, 2; raise notice '% %', r1, r2; end $$ language plpgsql; -- raise a error select * from plpgsql_check_function('dyn_sql_3'); plpgsql_check_function ----------------------------------------------------------------------------------------- warning:00000:4:EXECUTE:values passed to EXECUTE statement by USING clause was not used warning:00000:4:EXECUTE:too few attributes for target variables Detail: There are more target variables than output columns in query. Hint: Check target variables in SELECT INTO statement. (4 rows) drop function dyn_sql_3(); create or replace function dyn_sql_3() returns void as $$ declare r record; begin for r in execute 'select 1 as a, 2 as b' loop raise notice '%', r.a; end loop; end $$ language plpgsql; -- should be ok select * from plpgsql_check_function('dyn_sql_3'); plpgsql_check_function ------------------------ (0 rows) drop function dyn_sql_3(); create or replace function dyn_sql_3() returns void as $$ declare r record; begin for r in execute 'select 1 as a, 2 as b' loop raise notice '%', r.c; end loop; end $$ language plpgsql; -- should be error select * from plpgsql_check_function('dyn_sql_3'); plpgsql_check_function ------------------------------------------------- error:42703:6:RAISE:record "r" has no field "c" Context: SQL expression "r.c" (2 rows) drop function dyn_sql_3(); create or replace function dyn_sql_3() returns void as $$ declare r record; v text = 'select 10 a, 20 b't; begin for r in execute v loop raise notice '%', r.a; end loop; end $$ language plpgsql; -- should be ok select * from plpgsql_check_function('dyn_sql_3'); plpgsql_check_function ------------------------ (0 rows) drop function dyn_sql_3(); create or replace function dyn_sql_4() returns table(ax int, bx int) as $$ begin return query execute 'select 10, 20'; return; end; $$ language plpgsql; -- should be ok select * from plpgsql_check_function('dyn_sql_4()'); plpgsql_check_function ------------------------ (0 rows) create or replace function dyn_sql_4() returns table(ax int, bx int) as $$ begin return query execute 'select 10, 20, 30'; return; end; $$ language plpgsql; select * from dyn_sql_4(); ERROR: structure of query does not match function result type DETAIL: Number of returned columns (3) does not match expected column count (2). CONTEXT: SQL statement "select 10, 20, 30" PL/pgSQL function dyn_sql_4() line 3 at RETURN QUERY -- should be error select * from plpgsql_check_function('dyn_sql_4()'); plpgsql_check_function ----------------------------------------------------------------------------------- error:42804:3:RETURN QUERY:structure of query does not match function result type Detail: Number of returned columns (3) does not match expected column count (2). (2 rows) drop function dyn_sql_4(); create or replace function test_bug(text) returns regproc as $$ begin return $1::regproc; exception when undefined_function or invalid_name then raise; end; $$ language plpgsql; -- should not raise a exception select * from plpgsql_check_function('test_bug'); plpgsql_check_function ------------------------ (0 rows) create or replace function test_bug(text) returns regproc as $$ begin return $1::regproc; exception when undefined_function or invalid_name then raise notice '%', $1; -- bug end; $$ language plpgsql; select test_bug('kuku'); -- should to fail NOTICE: kuku ERROR: control reached end of function without RETURN CONTEXT: PL/pgSQL function test_bug(text) select * from plpgsql_check_function('test_bug'); plpgsql_check_function -------------------------------------------------------------------- warning extra:2F005:control reached end of function without RETURN (1 row) drop function test_bug(text); create or replace function test_bug(text) returns regproc as $$ begin return $1::regproc; exception when undefined_function or invalid_name then raise notice '%', $1; return NULL; end; $$ language plpgsql; select test_bug('kuku'); -- should be ok NOTICE: kuku test_bug ---------- (1 row) select * from plpgsql_check_function('test_bug'); plpgsql_check_function ------------------------ (0 rows) drop function test_bug(text); create or replace function foo(a text, b text) returns void as $$ begin -- unsecure execute 'select ' || a; a := quote_literal(a); -- is safe now execute 'select ' || a; a := a || b; -- it is unsecure again execute 'select ' || a; end; $$ language plpgsql; \sf+ foo(text, text) CREATE OR REPLACE FUNCTION public.foo(a text, b text) RETURNS void LANGUAGE plpgsql 1 AS $function$ 2 begin 3 -- unsecure 4 execute 'select ' || a; 5 a := quote_literal(a); -- is safe now 6 execute 'select ' || a; 7 a := a || b; -- it is unsecure again 8 execute 'select ' || a; 9 end; 10 $function$ -- should to raise two warnings select * from plpgsql_check_function('foo', security_warnings := true); plpgsql_check_function ----------------------------------------------------------------------------- security:00000:4:EXECUTE:text type variable is not sanitized Query: 'select ' || a -- ^ Detail: The EXECUTE expression is SQL injection vulnerable. Hint: Use quote_ident, quote_literal or format function to secure variable. security:00000:8:EXECUTE:text type variable is not sanitized Query: 'select ' || a -- ^ Detail: The EXECUTE expression is SQL injection vulnerable. Hint: Use quote_ident, quote_literal or format function to secure variable. (10 rows) drop function foo(text, text); -- test of very long function inside profiler create or replace function longfx(int) returns int as $$ declare s int default 0; j int default 0; r record; begin begin while j < 10 loop for i in 1..1 loop for r in select * from generate_series(1,1) loop s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; end loop; end loop; j := j + 1; end loop; exception when others then raise 'reraised exception %', sqlerrm; end; return $1; end; $$ language plpgsql; select lineno, stmt_lineno, exec_stmts, source from plpgsql_profiler_function_tb('longfx'); lineno | stmt_lineno | exec_stmts | source --------+-------------+------------+----------------------------------------------------- 1 | | | 2 | | | declare 3 | | | s int default 0; 4 | | | j int default 0; 5 | | | r record; 6 | | | begin 7 | | | begin 8 | | | while j < 10 9 | | | loop 10 | | | for i in 1..1 11 | | | loop 12 | | | for r in select * from generate_series(1,1) 13 | | | loop 14 | | | s := s + 1; 15 | | | s := s + 1; 16 | | | s := s + 1; 17 | | | s := s + 1; 18 | | | s := s + 1; 19 | | | s := s + 1; 20 | | | s := s + 1; 21 | | | s := s + 1; 22 | | | s := s + 1; 23 | | | s := s + 1; 24 | | | s := s + 1; 25 | | | s := s + 1; 26 | | | s := s + 1; 27 | | | s := s + 1; 28 | | | s := s + 1; 29 | | | s := s + 1; 30 | | | s := s + 1; 31 | | | s := s + 1; 32 | | | s := s + 1; 33 | | | s := s + 1; 34 | | | s := s + 1; 35 | | | s := s + 1; 36 | | | s := s + 1; 37 | | | s := s + 1; 38 | | | s := s + 1; 39 | | | s := s + 1; 40 | | | s := s + 1; 41 | | | s := s + 1; 42 | | | s := s + 1; 43 | | | s := s + 1; 44 | | | s := s + 1; 45 | | | s := s + 1; 46 | | | s := s + 1; 47 | | | s := s + 1; 48 | | | s := s + 1; 49 | | | s := s + 1; 50 | | | s := s + 1; 51 | | | s := s + 1; 52 | | | s := s + 1; 53 | | | s := s + 1; 54 | | | s := s + 1; 55 | | | s := s + 1; 56 | | | s := s + 1; 57 | | | s := s + 1; 58 | | | s := s + 1; 59 | | | s := s + 1; 60 | | | s := s + 1; 61 | | | s := s + 1; 62 | | | s := s + 1; 63 | | | s := s + 1; 64 | | | s := s + 1; 65 | | | s := s + 1; 66 | | | s := s + 1; 67 | | | s := s + 1; 68 | | | s := s + 1; 69 | | | s := s + 1; 70 | | | s := s + 1; 71 | | | s := s + 1; 72 | | | s := s + 1; 73 | | | s := s + 1; 74 | | | s := s + 1; 75 | | | s := s + 1; 76 | | | s := s + 1; 77 | | | s := s + 1; 78 | | | s := s + 1; 79 | | | s := s + 1; 80 | | | s := s + 1; 81 | | | s := s + 1; 82 | | | s := s + 1; 83 | | | s := s + 1; 84 | | | s := s + 1; 85 | | | s := s + 1; 86 | | | s := s + 1; 87 | | | s := s + 1; 88 | | | s := s + 1; 89 | | | s := s + 1; 90 | | | s := s + 1; 91 | | | s := s + 1; 92 | | | s := s + 1; 93 | | | s := s + 1; 94 | | | s := s + 1; 95 | | | s := s + 1; 96 | | | s := s + 1; 97 | | | s := s + 1; 98 | | | s := s + 1; 99 | | | s := s + 1; 100 | | | s := s + 1; 101 | | | s := s + 1; 102 | | | s := s + 1; 103 | | | s := s + 1; 104 | | | s := s + 1; 105 | | | s := s + 1; 106 | | | s := s + 1; 107 | | | s := s + 1; 108 | | | s := s + 1; 109 | | | s := s + 1; 110 | | | s := s + 1; 111 | | | s := s + 1; 112 | | | s := s + 1; 113 | | | s := s + 1; 114 | | | s := s + 1; 115 | | | s := s + 1; 116 | | | s := s + 1; 117 | | | s := s + 1; 118 | | | s := s + 1; 119 | | | s := s + 1; 120 | | | s := s + 1; 121 | | | s := s + 1; 122 | | | s := s + 1; 123 | | | s := s + 1; 124 | | | s := s + 1; 125 | | | s := s + 1; 126 | | | s := s + 1; 127 | | | s := s + 1; 128 | | | s := s + 1; 129 | | | s := s + 1; 130 | | | s := s + 1; 131 | | | s := s + 1; 132 | | | s := s + 1; 133 | | | s := s + 1; 134 | | | s := s + 1; 135 | | | s := s + 1; 136 | | | s := s + 1; 137 | | | s := s + 1; 138 | | | s := s + 1; 139 | | | s := s + 1; 140 | | | s := s + 1; 141 | | | s := s + 1; 142 | | | s := s + 1; 143 | | | s := s + 1; 144 | | | s := s + 1; 145 | | | s := s + 1; 146 | | | s := s + 1; 147 | | | s := s + 1; 148 | | | s := s + 1; 149 | | | s := s + 1; 150 | | | s := s + 1; 151 | | | s := s + 1; 152 | | | s := s + 1; 153 | | | s := s + 1; 154 | | | s := s + 1; 155 | | | s := s + 1; 156 | | | s := s + 1; 157 | | | s := s + 1; 158 | | | s := s + 1; 159 | | | s := s + 1; 160 | | | s := s + 1; 161 | | | s := s + 1; 162 | | | s := s + 1; 163 | | | s := s + 1; 164 | | | s := s + 1; 165 | | | s := s + 1; 166 | | | s := s + 1; 167 | | | s := s + 1; 168 | | | s := s + 1; 169 | | | s := s + 1; 170 | | | s := s + 1; 171 | | | s := s + 1; 172 | | | s := s + 1; 173 | | | s := s + 1; 174 | | | s := s + 1; 175 | | | s := s + 1; 176 | | | s := s + 1; 177 | | | s := s + 1; 178 | | | s := s + 1; 179 | | | s := s + 1; 180 | | | s := s + 1; 181 | | | s := s + 1; 182 | | | s := s + 1; 183 | | | s := s + 1; 184 | | | s := s + 1; 185 | | | s := s + 1; 186 | | | s := s + 1; 187 | | | s := s + 1; 188 | | | s := s + 1; 189 | | | s := s + 1; 190 | | | s := s + 1; 191 | | | s := s + 1; 192 | | | s := s + 1; 193 | | | s := s + 1; 194 | | | s := s + 1; 195 | | | s := s + 1; 196 | | | s := s + 1; 197 | | | s := s + 1; 198 | | | s := s + 1; 199 | | | s := s + 1; 200 | | | s := s + 1; 201 | | | s := s + 1; 202 | | | s := s + 1; 203 | | | s := s + 1; 204 | | | s := s + 1; 205 | | | s := s + 1; 206 | | | s := s + 1; 207 | | | s := s + 1; 208 | | | s := s + 1; 209 | | | s := s + 1; 210 | | | s := s + 1; 211 | | | s := s + 1; 212 | | | s := s + 1; 213 | | | s := s + 1; 214 | | | s := s + 1; 215 | | | s := s + 1; 216 | | | s := s + 1; 217 | | | s := s + 1; 218 | | | s := s + 1; 219 | | | s := s + 1; 220 | | | s := s + 1; 221 | | | s := s + 1; 222 | | | s := s + 1; 223 | | | s := s + 1; 224 | | | s := s + 1; 225 | | | s := s + 1; 226 | | | s := s + 1; 227 | | | s := s + 1; 228 | | | s := s + 1; 229 | | | s := s + 1; 230 | | | s := s + 1; 231 | | | s := s + 1; 232 | | | s := s + 1; 233 | | | s := s + 1; 234 | | | s := s + 1; 235 | | | s := s + 1; 236 | | | s := s + 1; 237 | | | s := s + 1; 238 | | | s := s + 1; 239 | | | s := s + 1; 240 | | | s := s + 1; 241 | | | s := s + 1; 242 | | | s := s + 1; 243 | | | s := s + 1; 244 | | | s := s + 1; 245 | | | s := s + 1; 246 | | | s := s + 1; 247 | | | s := s + 1; 248 | | | s := s + 1; 249 | | | s := s + 1; 250 | | | s := s + 1; 251 | | | s := s + 1; 252 | | | s := s + 1; 253 | | | s := s + 1; 254 | | | s := s + 1; 255 | | | s := s + 1; 256 | | | s := s + 1; 257 | | | s := s + 1; 258 | | | s := s + 1; 259 | | | s := s + 1; 260 | | | s := s + 1; 261 | | | s := s + 1; 262 | | | s := s + 1; 263 | | | s := s + 1; 264 | | | s := s + 1; 265 | | | s := s + 1; 266 | | | s := s + 1; 267 | | | s := s + 1; 268 | | | s := s + 1; 269 | | | s := s + 1; 270 | | | s := s + 1; 271 | | | s := s + 1; 272 | | | s := s + 1; 273 | | | s := s + 1; 274 | | | s := s + 1; 275 | | | s := s + 1; 276 | | | s := s + 1; 277 | | | s := s + 1; 278 | | | s := s + 1; 279 | | | s := s + 1; 280 | | | s := s + 1; 281 | | | s := s + 1; 282 | | | s := s + 1; 283 | | | s := s + 1; 284 | | | s := s + 1; 285 | | | s := s + 1; 286 | | | s := s + 1; 287 | | | s := s + 1; 288 | | | s := s + 1; 289 | | | s := s + 1; 290 | | | s := s + 1; 291 | | | s := s + 1; 292 | | | s := s + 1; 293 | | | s := s + 1; 294 | | | s := s + 1; 295 | | | s := s + 1; 296 | | | s := s + 1; 297 | | | s := s + 1; 298 | | | s := s + 1; 299 | | | s := s + 1; 300 | | | s := s + 1; 301 | | | s := s + 1; 302 | | | s := s + 1; 303 | | | s := s + 1; 304 | | | s := s + 1; 305 | | | s := s + 1; 306 | | | s := s + 1; 307 | | | s := s + 1; 308 | | | s := s + 1; 309 | | | s := s + 1; 310 | | | s := s + 1; 311 | | | s := s + 1; 312 | | | s := s + 1; 313 | | | s := s + 1; 314 | | | s := s + 1; 315 | | | s := s + 1; 316 | | | s := s + 1; 317 | | | s := s + 1; 318 | | | s := s + 1; 319 | | | s := s + 1; 320 | | | s := s + 1; 321 | | | s := s + 1; 322 | | | s := s + 1; 323 | | | s := s + 1; 324 | | | s := s + 1; 325 | | | s := s + 1; 326 | | | s := s + 1; 327 | | | s := s + 1; 328 | | | s := s + 1; 329 | | | s := s + 1; 330 | | | s := s + 1; 331 | | | s := s + 1; 332 | | | s := s + 1; 333 | | | s := s + 1; 334 | | | s := s + 1; 335 | | | s := s + 1; 336 | | | s := s + 1; 337 | | | s := s + 1; 338 | | | s := s + 1; 339 | | | s := s + 1; 340 | | | s := s + 1; 341 | | | s := s + 1; 342 | | | s := s + 1; 343 | | | s := s + 1; 344 | | | s := s + 1; 345 | | | s := s + 1; 346 | | | s := s + 1; 347 | | | s := s + 1; 348 | | | s := s + 1; 349 | | | s := s + 1; 350 | | | s := s + 1; 351 | | | s := s + 1; 352 | | | s := s + 1; 353 | | | s := s + 1; 354 | | | s := s + 1; 355 | | | s := s + 1; 356 | | | s := s + 1; 357 | | | s := s + 1; 358 | | | s := s + 1; 359 | | | s := s + 1; 360 | | | s := s + 1; 361 | | | s := s + 1; 362 | | | s := s + 1; 363 | | | s := s + 1; 364 | | | s := s + 1; 365 | | | s := s + 1; 366 | | | s := s + 1; 367 | | | s := s + 1; 368 | | | s := s + 1; 369 | | | s := s + 1; 370 | | | s := s + 1; 371 | | | s := s + 1; 372 | | | s := s + 1; 373 | | | s := s + 1; 374 | | | s := s + 1; 375 | | | s := s + 1; 376 | | | s := s + 1; 377 | | | s := s + 1; 378 | | | s := s + 1; 379 | | | s := s + 1; 380 | | | s := s + 1; 381 | | | s := s + 1; 382 | | | s := s + 1; 383 | | | s := s + 1; 384 | | | s := s + 1; 385 | | | s := s + 1; 386 | | | s := s + 1; 387 | | | s := s + 1; 388 | | | s := s + 1; 389 | | | s := s + 1; 390 | | | s := s + 1; 391 | | | s := s + 1; 392 | | | s := s + 1; 393 | | | s := s + 1; 394 | | | s := s + 1; 395 | | | s := s + 1; 396 | | | s := s + 1; 397 | | | s := s + 1; 398 | | | s := s + 1; 399 | | | s := s + 1; 400 | | | s := s + 1; 401 | | | s := s + 1; 402 | | | s := s + 1; 403 | | | s := s + 1; 404 | | | s := s + 1; 405 | | | s := s + 1; 406 | | | s := s + 1; 407 | | | s := s + 1; 408 | | | s := s + 1; 409 | | | s := s + 1; 410 | | | s := s + 1; 411 | | | s := s + 1; 412 | | | s := s + 1; 413 | | | s := s + 1; 414 | | | s := s + 1; 415 | | | s := s + 1; 416 | | | s := s + 1; 417 | | | s := s + 1; 418 | | | s := s + 1; 419 | | | s := s + 1; 420 | | | s := s + 1; 421 | | | s := s + 1; 422 | | | s := s + 1; 423 | | | s := s + 1; 424 | | | s := s + 1; 425 | | | s := s + 1; 426 | | | s := s + 1; 427 | | | s := s + 1; 428 | | | s := s + 1; 429 | | | s := s + 1; 430 | | | s := s + 1; 431 | | | s := s + 1; 432 | | | s := s + 1; 433 | | | s := s + 1; 434 | | | s := s + 1; 435 | | | s := s + 1; 436 | | | s := s + 1; 437 | | | s := s + 1; 438 | | | s := s + 1; 439 | | | s := s + 1; 440 | | | s := s + 1; 441 | | | s := s + 1; 442 | | | s := s + 1; 443 | | | s := s + 1; 444 | | | s := s + 1; 445 | | | s := s + 1; 446 | | | s := s + 1; 447 | | | s := s + 1; 448 | | | s := s + 1; 449 | | | s := s + 1; 450 | | | s := s + 1; 451 | | | s := s + 1; 452 | | | s := s + 1; 453 | | | s := s + 1; 454 | | | s := s + 1; 455 | | | s := s + 1; 456 | | | s := s + 1; 457 | | | s := s + 1; 458 | | | s := s + 1; 459 | | | s := s + 1; 460 | | | s := s + 1; 461 | | | s := s + 1; 462 | | | s := s + 1; 463 | | | s := s + 1; 464 | | | s := s + 1; 465 | | | s := s + 1; 466 | | | s := s + 1; 467 | | | s := s + 1; 468 | | | s := s + 1; 469 | | | s := s + 1; 470 | | | s := s + 1; 471 | | | s := s + 1; 472 | | | s := s + 1; 473 | | | s := s + 1; 474 | | | s := s + 1; 475 | | | s := s + 1; 476 | | | s := s + 1; 477 | | | s := s + 1; 478 | | | s := s + 1; 479 | | | s := s + 1; 480 | | | s := s + 1; 481 | | | s := s + 1; 482 | | | s := s + 1; 483 | | | s := s + 1; 484 | | | s := s + 1; 485 | | | s := s + 1; 486 | | | s := s + 1; 487 | | | s := s + 1; 488 | | | s := s + 1; 489 | | | s := s + 1; 490 | | | s := s + 1; 491 | | | s := s + 1; 492 | | | s := s + 1; 493 | | | s := s + 1; 494 | | | s := s + 1; 495 | | | s := s + 1; 496 | | | s := s + 1; 497 | | | s := s + 1; 498 | | | s := s + 1; 499 | | | s := s + 1; 500 | | | s := s + 1; 501 | | | s := s + 1; 502 | | | s := s + 1; 503 | | | s := s + 1; 504 | | | s := s + 1; 505 | | | s := s + 1; 506 | | | s := s + 1; 507 | | | s := s + 1; 508 | | | s := s + 1; 509 | | | s := s + 1; 510 | | | s := s + 1; 511 | | | s := s + 1; 512 | | | s := s + 1; 513 | | | s := s + 1; 514 | | | s := s + 1; 515 | | | s := s + 1; 516 | | | s := s + 1; 517 | | | s := s + 1; 518 | | | s := s + 1; 519 | | | s := s + 1; 520 | | | s := s + 1; 521 | | | s := s + 1; 522 | | | s := s + 1; 523 | | | s := s + 1; 524 | | | s := s + 1; 525 | | | s := s + 1; 526 | | | s := s + 1; 527 | | | s := s + 1; 528 | | | s := s + 1; 529 | | | s := s + 1; 530 | | | s := s + 1; 531 | | | s := s + 1; 532 | | | s := s + 1; 533 | | | s := s + 1; 534 | | | s := s + 1; 535 | | | s := s + 1; 536 | | | s := s + 1; 537 | | | s := s + 1; 538 | | | s := s + 1; 539 | | | s := s + 1; 540 | | | s := s + 1; 541 | | | s := s + 1; 542 | | | s := s + 1; 543 | | | s := s + 1; 544 | | | s := s + 1; 545 | | | s := s + 1; 546 | | | s := s + 1; 547 | | | s := s + 1; 548 | | | s := s + 1; 549 | | | s := s + 1; 550 | | | s := s + 1; 551 | | | s := s + 1; 552 | | | s := s + 1; 553 | | | s := s + 1; 554 | | | s := s + 1; 555 | | | s := s + 1; 556 | | | s := s + 1; 557 | | | s := s + 1; 558 | | | s := s + 1; 559 | | | s := s + 1; 560 | | | s := s + 1; 561 | | | s := s + 1; 562 | | | s := s + 1; 563 | | | s := s + 1; 564 | | | s := s + 1; 565 | | | s := s + 1; 566 | | | s := s + 1; 567 | | | s := s + 1; 568 | | | s := s + 1; 569 | | | s := s + 1; 570 | | | s := s + 1; 571 | | | s := s + 1; 572 | | | s := s + 1; 573 | | | s := s + 1; 574 | | | s := s + 1; 575 | | | s := s + 1; 576 | | | s := s + 1; 577 | | | s := s + 1; 578 | | | s := s + 1; 579 | | | s := s + 1; 580 | | | s := s + 1; 581 | | | s := s + 1; 582 | | | s := s + 1; 583 | | | s := s + 1; 584 | | | s := s + 1; 585 | | | s := s + 1; 586 | | | s := s + 1; 587 | | | s := s + 1; 588 | | | s := s + 1; 589 | | | s := s + 1; 590 | | | s := s + 1; 591 | | | s := s + 1; 592 | | | s := s + 1; 593 | | | s := s + 1; 594 | | | s := s + 1; 595 | | | s := s + 1; 596 | | | s := s + 1; 597 | | | s := s + 1; 598 | | | s := s + 1; 599 | | | s := s + 1; 600 | | | s := s + 1; 601 | | | s := s + 1; 602 | | | s := s + 1; 603 | | | s := s + 1; 604 | | | s := s + 1; 605 | | | s := s + 1; 606 | | | s := s + 1; 607 | | | s := s + 1; 608 | | | s := s + 1; 609 | | | s := s + 1; 610 | | | s := s + 1; 611 | | | s := s + 1; 612 | | | s := s + 1; 613 | | | s := s + 1; 614 | | | s := s + 1; 615 | | | s := s + 1; 616 | | | s := s + 1; 617 | | | s := s + 1; 618 | | | s := s + 1; 619 | | | s := s + 1; 620 | | | s := s + 1; 621 | | | s := s + 1; 622 | | | s := s + 1; 623 | | | s := s + 1; 624 | | | s := s + 1; 625 | | | s := s + 1; 626 | | | s := s + 1; 627 | | | s := s + 1; 628 | | | s := s + 1; 629 | | | s := s + 1; 630 | | | s := s + 1; 631 | | | s := s + 1; 632 | | | s := s + 1; 633 | | | s := s + 1; 634 | | | s := s + 1; 635 | | | s := s + 1; 636 | | | s := s + 1; 637 | | | s := s + 1; 638 | | | s := s + 1; 639 | | | s := s + 1; 640 | | | s := s + 1; 641 | | | s := s + 1; 642 | | | s := s + 1; 643 | | | s := s + 1; 644 | | | s := s + 1; 645 | | | s := s + 1; 646 | | | s := s + 1; 647 | | | s := s + 1; 648 | | | s := s + 1; 649 | | | s := s + 1; 650 | | | s := s + 1; 651 | | | s := s + 1; 652 | | | s := s + 1; 653 | | | s := s + 1; 654 | | | s := s + 1; 655 | | | s := s + 1; 656 | | | s := s + 1; 657 | | | s := s + 1; 658 | | | s := s + 1; 659 | | | s := s + 1; 660 | | | s := s + 1; 661 | | | s := s + 1; 662 | | | s := s + 1; 663 | | | s := s + 1; 664 | | | s := s + 1; 665 | | | s := s + 1; 666 | | | s := s + 1; 667 | | | s := s + 1; 668 | | | s := s + 1; 669 | | | s := s + 1; 670 | | | s := s + 1; 671 | | | s := s + 1; 672 | | | s := s + 1; 673 | | | s := s + 1; 674 | | | s := s + 1; 675 | | | s := s + 1; 676 | | | s := s + 1; 677 | | | s := s + 1; 678 | | | s := s + 1; 679 | | | s := s + 1; 680 | | | s := s + 1; 681 | | | s := s + 1; 682 | | | s := s + 1; 683 | | | s := s + 1; 684 | | | s := s + 1; 685 | | | s := s + 1; 686 | | | s := s + 1; 687 | | | s := s + 1; 688 | | | s := s + 1; 689 | | | s := s + 1; 690 | | | s := s + 1; 691 | | | s := s + 1; 692 | | | s := s + 1; 693 | | | s := s + 1; 694 | | | s := s + 1; 695 | | | s := s + 1; 696 | | | s := s + 1; 697 | | | s := s + 1; 698 | | | s := s + 1; 699 | | | s := s + 1; 700 | | | s := s + 1; 701 | | | s := s + 1; 702 | | | s := s + 1; 703 | | | s := s + 1; 704 | | | s := s + 1; 705 | | | s := s + 1; 706 | | | s := s + 1; 707 | | | s := s + 1; 708 | | | s := s + 1; 709 | | | s := s + 1; 710 | | | s := s + 1; 711 | | | s := s + 1; 712 | | | s := s + 1; 713 | | | s := s + 1; 714 | | | s := s + 1; 715 | | | s := s + 1; 716 | | | s := s + 1; 717 | | | s := s + 1; 718 | | | s := s + 1; 719 | | | s := s + 1; 720 | | | s := s + 1; 721 | | | s := s + 1; 722 | | | s := s + 1; 723 | | | s := s + 1; 724 | | | s := s + 1; 725 | | | s := s + 1; 726 | | | s := s + 1; 727 | | | s := s + 1; 728 | | | s := s + 1; 729 | | | s := s + 1; 730 | | | s := s + 1; 731 | | | s := s + 1; 732 | | | s := s + 1; 733 | | | s := s + 1; 734 | | | s := s + 1; 735 | | | s := s + 1; 736 | | | s := s + 1; 737 | | | s := s + 1; 738 | | | s := s + 1; 739 | | | s := s + 1; 740 | | | s := s + 1; 741 | | | s := s + 1; 742 | | | s := s + 1; 743 | | | s := s + 1; 744 | | | s := s + 1; 745 | | | s := s + 1; 746 | | | s := s + 1; 747 | | | s := s + 1; 748 | | | s := s + 1; 749 | | | s := s + 1; 750 | | | s := s + 1; 751 | | | s := s + 1; 752 | | | s := s + 1; 753 | | | s := s + 1; 754 | | | s := s + 1; 755 | | | s := s + 1; 756 | | | s := s + 1; 757 | | | s := s + 1; 758 | | | s := s + 1; 759 | | | s := s + 1; 760 | | | s := s + 1; 761 | | | s := s + 1; 762 | | | s := s + 1; 763 | | | s := s + 1; 764 | | | s := s + 1; 765 | | | s := s + 1; 766 | | | s := s + 1; 767 | | | s := s + 1; 768 | | | s := s + 1; 769 | | | s := s + 1; 770 | | | s := s + 1; 771 | | | s := s + 1; 772 | | | s := s + 1; 773 | | | s := s + 1; 774 | | | s := s + 1; 775 | | | s := s + 1; 776 | | | s := s + 1; 777 | | | s := s + 1; 778 | | | s := s + 1; 779 | | | s := s + 1; 780 | | | s := s + 1; 781 | | | s := s + 1; 782 | | | s := s + 1; 783 | | | s := s + 1; 784 | | | s := s + 1; 785 | | | s := s + 1; 786 | | | s := s + 1; 787 | | | s := s + 1; 788 | | | s := s + 1; 789 | | | s := s + 1; 790 | | | s := s + 1; 791 | | | s := s + 1; 792 | | | s := s + 1; 793 | | | s := s + 1; 794 | | | s := s + 1; 795 | | | s := s + 1; 796 | | | s := s + 1; 797 | | | s := s + 1; 798 | | | s := s + 1; 799 | | | s := s + 1; 800 | | | s := s + 1; 801 | | | s := s + 1; 802 | | | s := s + 1; 803 | | | s := s + 1; 804 | | | s := s + 1; 805 | | | s := s + 1; 806 | | | s := s + 1; 807 | | | s := s + 1; 808 | | | s := s + 1; 809 | | | s := s + 1; 810 | | | s := s + 1; 811 | | | s := s + 1; 812 | | | s := s + 1; 813 | | | s := s + 1; 814 | | | s := s + 1; 815 | | | s := s + 1; 816 | | | s := s + 1; 817 | | | s := s + 1; 818 | | | s := s + 1; 819 | | | s := s + 1; 820 | | | s := s + 1; 821 | | | s := s + 1; 822 | | | s := s + 1; 823 | | | s := s + 1; 824 | | | s := s + 1; 825 | | | s := s + 1; 826 | | | s := s + 1; 827 | | | s := s + 1; 828 | | | s := s + 1; 829 | | | s := s + 1; 830 | | | s := s + 1; 831 | | | s := s + 1; 832 | | | s := s + 1; 833 | | | s := s + 1; 834 | | | s := s + 1; 835 | | | s := s + 1; 836 | | | s := s + 1; 837 | | | s := s + 1; 838 | | | s := s + 1; 839 | | | s := s + 1; 840 | | | s := s + 1; 841 | | | s := s + 1; 842 | | | s := s + 1; 843 | | | s := s + 1; 844 | | | s := s + 1; 845 | | | s := s + 1; 846 | | | s := s + 1; 847 | | | s := s + 1; 848 | | | s := s + 1; 849 | | | s := s + 1; 850 | | | s := s + 1; 851 | | | s := s + 1; 852 | | | s := s + 1; 853 | | | s := s + 1; 854 | | | s := s + 1; 855 | | | s := s + 1; 856 | | | s := s + 1; 857 | | | s := s + 1; 858 | | | s := s + 1; 859 | | | s := s + 1; 860 | | | s := s + 1; 861 | | | s := s + 1; 862 | | | s := s + 1; 863 | | | s := s + 1; 864 | | | s := s + 1; 865 | | | s := s + 1; 866 | | | s := s + 1; 867 | | | s := s + 1; 868 | | | s := s + 1; 869 | | | s := s + 1; 870 | | | s := s + 1; 871 | | | s := s + 1; 872 | | | s := s + 1; 873 | | | s := s + 1; 874 | | | s := s + 1; 875 | | | s := s + 1; 876 | | | s := s + 1; 877 | | | s := s + 1; 878 | | | s := s + 1; 879 | | | s := s + 1; 880 | | | s := s + 1; 881 | | | s := s + 1; 882 | | | s := s + 1; 883 | | | s := s + 1; 884 | | | s := s + 1; 885 | | | s := s + 1; 886 | | | s := s + 1; 887 | | | s := s + 1; 888 | | | s := s + 1; 889 | | | s := s + 1; 890 | | | s := s + 1; 891 | | | s := s + 1; 892 | | | s := s + 1; 893 | | | s := s + 1; 894 | | | s := s + 1; 895 | | | s := s + 1; 896 | | | s := s + 1; 897 | | | s := s + 1; 898 | | | s := s + 1; 899 | | | s := s + 1; 900 | | | s := s + 1; 901 | | | s := s + 1; 902 | | | s := s + 1; 903 | | | s := s + 1; 904 | | | s := s + 1; 905 | | | s := s + 1; 906 | | | s := s + 1; 907 | | | s := s + 1; 908 | | | s := s + 1; 909 | | | s := s + 1; 910 | | | s := s + 1; 911 | | | s := s + 1; 912 | | | s := s + 1; 913 | | | s := s + 1; 914 | | | s := s + 1; 915 | | | s := s + 1; 916 | | | s := s + 1; 917 | | | s := s + 1; 918 | | | s := s + 1; 919 | | | s := s + 1; 920 | | | s := s + 1; 921 | | | s := s + 1; 922 | | | s := s + 1; 923 | | | s := s + 1; 924 | | | s := s + 1; 925 | | | s := s + 1; 926 | | | s := s + 1; 927 | | | s := s + 1; 928 | | | s := s + 1; 929 | | | s := s + 1; 930 | | | s := s + 1; 931 | | | s := s + 1; 932 | | | s := s + 1; 933 | | | s := s + 1; 934 | | | s := s + 1; 935 | | | s := s + 1; 936 | | | s := s + 1; 937 | | | s := s + 1; 938 | | | s := s + 1; 939 | | | s := s + 1; 940 | | | s := s + 1; 941 | | | s := s + 1; 942 | | | s := s + 1; 943 | | | s := s + 1; 944 | | | s := s + 1; 945 | | | s := s + 1; 946 | | | s := s + 1; 947 | | | s := s + 1; 948 | | | s := s + 1; 949 | | | s := s + 1; 950 | | | s := s + 1; 951 | | | s := s + 1; 952 | | | s := s + 1; 953 | | | s := s + 1; 954 | | | s := s + 1; 955 | | | s := s + 1; 956 | | | s := s + 1; 957 | | | s := s + 1; 958 | | | s := s + 1; 959 | | | s := s + 1; 960 | | | s := s + 1; 961 | | | s := s + 1; 962 | | | s := s + 1; 963 | | | s := s + 1; 964 | | | s := s + 1; 965 | | | s := s + 1; 966 | | | s := s + 1; 967 | | | s := s + 1; 968 | | | s := s + 1; 969 | | | s := s + 1; 970 | | | s := s + 1; 971 | | | s := s + 1; 972 | | | s := s + 1; 973 | | | s := s + 1; 974 | | | s := s + 1; 975 | | | s := s + 1; 976 | | | s := s + 1; 977 | | | s := s + 1; 978 | | | s := s + 1; 979 | | | s := s + 1; 980 | | | s := s + 1; 981 | | | s := s + 1; 982 | | | s := s + 1; 983 | | | s := s + 1; 984 | | | s := s + 1; 985 | | | s := s + 1; 986 | | | s := s + 1; 987 | | | s := s + 1; 988 | | | s := s + 1; 989 | | | s := s + 1; 990 | | | s := s + 1; 991 | | | s := s + 1; 992 | | | s := s + 1; 993 | | | s := s + 1; 994 | | | s := s + 1; 995 | | | s := s + 1; 996 | | | s := s + 1; 997 | | | s := s + 1; 998 | | | s := s + 1; 999 | | | s := s + 1; 1000 | | | s := s + 1; 1001 | | | s := s + 1; 1002 | | | s := s + 1; 1003 | | | s := s + 1; 1004 | | | s := s + 1; 1005 | | | s := s + 1; 1006 | | | s := s + 1; 1007 | | | s := s + 1; 1008 | | | s := s + 1; 1009 | | | s := s + 1; 1010 | | | s := s + 1; 1011 | | | s := s + 1; 1012 | | | s := s + 1; 1013 | | | s := s + 1; 1014 | | | s := s + 1; 1015 | | | s := s + 1; 1016 | | | s := s + 1; 1017 | | | s := s + 1; 1018 | | | s := s + 1; 1019 | | | s := s + 1; 1020 | | | s := s + 1; 1021 | | | s := s + 1; 1022 | | | s := s + 1; 1023 | | | s := s + 1; 1024 | | | s := s + 1; 1025 | | | s := s + 1; 1026 | | | s := s + 1; 1027 | | | s := s + 1; 1028 | | | s := s + 1; 1029 | | | s := s + 1; 1030 | | | s := s + 1; 1031 | | | s := s + 1; 1032 | | | s := s + 1; 1033 | | | s := s + 1; 1034 | | | s := s + 1; 1035 | | | s := s + 1; 1036 | | | s := s + 1; 1037 | | | s := s + 1; 1038 | | | end loop; 1039 | | | end loop; 1040 | | | j := j + 1; 1041 | | | end loop; 1042 | | | exception when others then 1043 | | | raise 'reraised exception %', sqlerrm; 1044 | | | end; 1045 | | | return $1; 1046 | | | end; (1046 rows) set plpgsql_check.profiler = on; select longfx(10); longfx -------- 10 (1 row) select longfx(10); longfx -------- 10 (1 row) set plpgsql_check.profiler = off; select longfx(10); longfx -------- 10 (1 row) select lineno, stmt_lineno, exec_stmts, source from plpgsql_profiler_function_tb('longfx'); lineno | stmt_lineno | exec_stmts | source --------+-------------+------------+----------------------------------------------------- 1 | | | 2 | | | declare 3 | | | s int default 0; 4 | | | j int default 0; 5 | | | r record; 6 | 6 | 2 | begin 7 | 7 | 2 | begin 8 | 8 | 2 | while j < 10 9 | | | loop 10 | 10 | 20 | for i in 1..1 11 | | | loop 12 | 12 | 20 | for r in select * from generate_series(1,1) 13 | | | loop 14 | 14 | 20 | s := s + 1; 15 | 15 | 20 | s := s + 1; 16 | 16 | 20 | s := s + 1; 17 | 17 | 20 | s := s + 1; 18 | 18 | 20 | s := s + 1; 19 | 19 | 20 | s := s + 1; 20 | 20 | 20 | s := s + 1; 21 | 21 | 20 | s := s + 1; 22 | 22 | 20 | s := s + 1; 23 | 23 | 20 | s := s + 1; 24 | 24 | 20 | s := s + 1; 25 | 25 | 20 | s := s + 1; 26 | 26 | 20 | s := s + 1; 27 | 27 | 20 | s := s + 1; 28 | 28 | 20 | s := s + 1; 29 | 29 | 20 | s := s + 1; 30 | 30 | 20 | s := s + 1; 31 | 31 | 20 | s := s + 1; 32 | 32 | 20 | s := s + 1; 33 | 33 | 20 | s := s + 1; 34 | 34 | 20 | s := s + 1; 35 | 35 | 20 | s := s + 1; 36 | 36 | 20 | s := s + 1; 37 | 37 | 20 | s := s + 1; 38 | 38 | 20 | s := s + 1; 39 | 39 | 20 | s := s + 1; 40 | 40 | 20 | s := s + 1; 41 | 41 | 20 | s := s + 1; 42 | 42 | 20 | s := s + 1; 43 | 43 | 20 | s := s + 1; 44 | 44 | 20 | s := s + 1; 45 | 45 | 20 | s := s + 1; 46 | 46 | 20 | s := s + 1; 47 | 47 | 20 | s := s + 1; 48 | 48 | 20 | s := s + 1; 49 | 49 | 20 | s := s + 1; 50 | 50 | 20 | s := s + 1; 51 | 51 | 20 | s := s + 1; 52 | 52 | 20 | s := s + 1; 53 | 53 | 20 | s := s + 1; 54 | 54 | 20 | s := s + 1; 55 | 55 | 20 | s := s + 1; 56 | 56 | 20 | s := s + 1; 57 | 57 | 20 | s := s + 1; 58 | 58 | 20 | s := s + 1; 59 | 59 | 20 | s := s + 1; 60 | 60 | 20 | s := s + 1; 61 | 61 | 20 | s := s + 1; 62 | 62 | 20 | s := s + 1; 63 | 63 | 20 | s := s + 1; 64 | 64 | 20 | s := s + 1; 65 | 65 | 20 | s := s + 1; 66 | 66 | 20 | s := s + 1; 67 | 67 | 20 | s := s + 1; 68 | 68 | 20 | s := s + 1; 69 | 69 | 20 | s := s + 1; 70 | 70 | 20 | s := s + 1; 71 | 71 | 20 | s := s + 1; 72 | 72 | 20 | s := s + 1; 73 | 73 | 20 | s := s + 1; 74 | 74 | 20 | s := s + 1; 75 | 75 | 20 | s := s + 1; 76 | 76 | 20 | s := s + 1; 77 | 77 | 20 | s := s + 1; 78 | 78 | 20 | s := s + 1; 79 | 79 | 20 | s := s + 1; 80 | 80 | 20 | s := s + 1; 81 | 81 | 20 | s := s + 1; 82 | 82 | 20 | s := s + 1; 83 | 83 | 20 | s := s + 1; 84 | 84 | 20 | s := s + 1; 85 | 85 | 20 | s := s + 1; 86 | 86 | 20 | s := s + 1; 87 | 87 | 20 | s := s + 1; 88 | 88 | 20 | s := s + 1; 89 | 89 | 20 | s := s + 1; 90 | 90 | 20 | s := s + 1; 91 | 91 | 20 | s := s + 1; 92 | 92 | 20 | s := s + 1; 93 | 93 | 20 | s := s + 1; 94 | 94 | 20 | s := s + 1; 95 | 95 | 20 | s := s + 1; 96 | 96 | 20 | s := s + 1; 97 | 97 | 20 | s := s + 1; 98 | 98 | 20 | s := s + 1; 99 | 99 | 20 | s := s + 1; 100 | 100 | 20 | s := s + 1; 101 | 101 | 20 | s := s + 1; 102 | 102 | 20 | s := s + 1; 103 | 103 | 20 | s := s + 1; 104 | 104 | 20 | s := s + 1; 105 | 105 | 20 | s := s + 1; 106 | 106 | 20 | s := s + 1; 107 | 107 | 20 | s := s + 1; 108 | 108 | 20 | s := s + 1; 109 | 109 | 20 | s := s + 1; 110 | 110 | 20 | s := s + 1; 111 | 111 | 20 | s := s + 1; 112 | 112 | 20 | s := s + 1; 113 | 113 | 20 | s := s + 1; 114 | 114 | 20 | s := s + 1; 115 | 115 | 20 | s := s + 1; 116 | 116 | 20 | s := s + 1; 117 | 117 | 20 | s := s + 1; 118 | 118 | 20 | s := s + 1; 119 | 119 | 20 | s := s + 1; 120 | 120 | 20 | s := s + 1; 121 | 121 | 20 | s := s + 1; 122 | 122 | 20 | s := s + 1; 123 | 123 | 20 | s := s + 1; 124 | 124 | 20 | s := s + 1; 125 | 125 | 20 | s := s + 1; 126 | 126 | 20 | s := s + 1; 127 | 127 | 20 | s := s + 1; 128 | 128 | 20 | s := s + 1; 129 | 129 | 20 | s := s + 1; 130 | 130 | 20 | s := s + 1; 131 | 131 | 20 | s := s + 1; 132 | 132 | 20 | s := s + 1; 133 | 133 | 20 | s := s + 1; 134 | 134 | 20 | s := s + 1; 135 | 135 | 20 | s := s + 1; 136 | 136 | 20 | s := s + 1; 137 | 137 | 20 | s := s + 1; 138 | 138 | 20 | s := s + 1; 139 | 139 | 20 | s := s + 1; 140 | 140 | 20 | s := s + 1; 141 | 141 | 20 | s := s + 1; 142 | 142 | 20 | s := s + 1; 143 | 143 | 20 | s := s + 1; 144 | 144 | 20 | s := s + 1; 145 | 145 | 20 | s := s + 1; 146 | 146 | 20 | s := s + 1; 147 | 147 | 20 | s := s + 1; 148 | 148 | 20 | s := s + 1; 149 | 149 | 20 | s := s + 1; 150 | 150 | 20 | s := s + 1; 151 | 151 | 20 | s := s + 1; 152 | 152 | 20 | s := s + 1; 153 | 153 | 20 | s := s + 1; 154 | 154 | 20 | s := s + 1; 155 | 155 | 20 | s := s + 1; 156 | 156 | 20 | s := s + 1; 157 | 157 | 20 | s := s + 1; 158 | 158 | 20 | s := s + 1; 159 | 159 | 20 | s := s + 1; 160 | 160 | 20 | s := s + 1; 161 | 161 | 20 | s := s + 1; 162 | 162 | 20 | s := s + 1; 163 | 163 | 20 | s := s + 1; 164 | 164 | 20 | s := s + 1; 165 | 165 | 20 | s := s + 1; 166 | 166 | 20 | s := s + 1; 167 | 167 | 20 | s := s + 1; 168 | 168 | 20 | s := s + 1; 169 | 169 | 20 | s := s + 1; 170 | 170 | 20 | s := s + 1; 171 | 171 | 20 | s := s + 1; 172 | 172 | 20 | s := s + 1; 173 | 173 | 20 | s := s + 1; 174 | 174 | 20 | s := s + 1; 175 | 175 | 20 | s := s + 1; 176 | 176 | 20 | s := s + 1; 177 | 177 | 20 | s := s + 1; 178 | 178 | 20 | s := s + 1; 179 | 179 | 20 | s := s + 1; 180 | 180 | 20 | s := s + 1; 181 | 181 | 20 | s := s + 1; 182 | 182 | 20 | s := s + 1; 183 | 183 | 20 | s := s + 1; 184 | 184 | 20 | s := s + 1; 185 | 185 | 20 | s := s + 1; 186 | 186 | 20 | s := s + 1; 187 | 187 | 20 | s := s + 1; 188 | 188 | 20 | s := s + 1; 189 | 189 | 20 | s := s + 1; 190 | 190 | 20 | s := s + 1; 191 | 191 | 20 | s := s + 1; 192 | 192 | 20 | s := s + 1; 193 | 193 | 20 | s := s + 1; 194 | 194 | 20 | s := s + 1; 195 | 195 | 20 | s := s + 1; 196 | 196 | 20 | s := s + 1; 197 | 197 | 20 | s := s + 1; 198 | 198 | 20 | s := s + 1; 199 | 199 | 20 | s := s + 1; 200 | 200 | 20 | s := s + 1; 201 | 201 | 20 | s := s + 1; 202 | 202 | 20 | s := s + 1; 203 | 203 | 20 | s := s + 1; 204 | 204 | 20 | s := s + 1; 205 | 205 | 20 | s := s + 1; 206 | 206 | 20 | s := s + 1; 207 | 207 | 20 | s := s + 1; 208 | 208 | 20 | s := s + 1; 209 | 209 | 20 | s := s + 1; 210 | 210 | 20 | s := s + 1; 211 | 211 | 20 | s := s + 1; 212 | 212 | 20 | s := s + 1; 213 | 213 | 20 | s := s + 1; 214 | 214 | 20 | s := s + 1; 215 | 215 | 20 | s := s + 1; 216 | 216 | 20 | s := s + 1; 217 | 217 | 20 | s := s + 1; 218 | 218 | 20 | s := s + 1; 219 | 219 | 20 | s := s + 1; 220 | 220 | 20 | s := s + 1; 221 | 221 | 20 | s := s + 1; 222 | 222 | 20 | s := s + 1; 223 | 223 | 20 | s := s + 1; 224 | 224 | 20 | s := s + 1; 225 | 225 | 20 | s := s + 1; 226 | 226 | 20 | s := s + 1; 227 | 227 | 20 | s := s + 1; 228 | 228 | 20 | s := s + 1; 229 | 229 | 20 | s := s + 1; 230 | 230 | 20 | s := s + 1; 231 | 231 | 20 | s := s + 1; 232 | 232 | 20 | s := s + 1; 233 | 233 | 20 | s := s + 1; 234 | 234 | 20 | s := s + 1; 235 | 235 | 20 | s := s + 1; 236 | 236 | 20 | s := s + 1; 237 | 237 | 20 | s := s + 1; 238 | 238 | 20 | s := s + 1; 239 | 239 | 20 | s := s + 1; 240 | 240 | 20 | s := s + 1; 241 | 241 | 20 | s := s + 1; 242 | 242 | 20 | s := s + 1; 243 | 243 | 20 | s := s + 1; 244 | 244 | 20 | s := s + 1; 245 | 245 | 20 | s := s + 1; 246 | 246 | 20 | s := s + 1; 247 | 247 | 20 | s := s + 1; 248 | 248 | 20 | s := s + 1; 249 | 249 | 20 | s := s + 1; 250 | 250 | 20 | s := s + 1; 251 | 251 | 20 | s := s + 1; 252 | 252 | 20 | s := s + 1; 253 | 253 | 20 | s := s + 1; 254 | 254 | 20 | s := s + 1; 255 | 255 | 20 | s := s + 1; 256 | 256 | 20 | s := s + 1; 257 | 257 | 20 | s := s + 1; 258 | 258 | 20 | s := s + 1; 259 | 259 | 20 | s := s + 1; 260 | 260 | 20 | s := s + 1; 261 | 261 | 20 | s := s + 1; 262 | 262 | 20 | s := s + 1; 263 | 263 | 20 | s := s + 1; 264 | 264 | 20 | s := s + 1; 265 | 265 | 20 | s := s + 1; 266 | 266 | 20 | s := s + 1; 267 | 267 | 20 | s := s + 1; 268 | 268 | 20 | s := s + 1; 269 | 269 | 20 | s := s + 1; 270 | 270 | 20 | s := s + 1; 271 | 271 | 20 | s := s + 1; 272 | 272 | 20 | s := s + 1; 273 | 273 | 20 | s := s + 1; 274 | 274 | 20 | s := s + 1; 275 | 275 | 20 | s := s + 1; 276 | 276 | 20 | s := s + 1; 277 | 277 | 20 | s := s + 1; 278 | 278 | 20 | s := s + 1; 279 | 279 | 20 | s := s + 1; 280 | 280 | 20 | s := s + 1; 281 | 281 | 20 | s := s + 1; 282 | 282 | 20 | s := s + 1; 283 | 283 | 20 | s := s + 1; 284 | 284 | 20 | s := s + 1; 285 | 285 | 20 | s := s + 1; 286 | 286 | 20 | s := s + 1; 287 | 287 | 20 | s := s + 1; 288 | 288 | 20 | s := s + 1; 289 | 289 | 20 | s := s + 1; 290 | 290 | 20 | s := s + 1; 291 | 291 | 20 | s := s + 1; 292 | 292 | 20 | s := s + 1; 293 | 293 | 20 | s := s + 1; 294 | 294 | 20 | s := s + 1; 295 | 295 | 20 | s := s + 1; 296 | 296 | 20 | s := s + 1; 297 | 297 | 20 | s := s + 1; 298 | 298 | 20 | s := s + 1; 299 | 299 | 20 | s := s + 1; 300 | 300 | 20 | s := s + 1; 301 | 301 | 20 | s := s + 1; 302 | 302 | 20 | s := s + 1; 303 | 303 | 20 | s := s + 1; 304 | 304 | 20 | s := s + 1; 305 | 305 | 20 | s := s + 1; 306 | 306 | 20 | s := s + 1; 307 | 307 | 20 | s := s + 1; 308 | 308 | 20 | s := s + 1; 309 | 309 | 20 | s := s + 1; 310 | 310 | 20 | s := s + 1; 311 | 311 | 20 | s := s + 1; 312 | 312 | 20 | s := s + 1; 313 | 313 | 20 | s := s + 1; 314 | 314 | 20 | s := s + 1; 315 | 315 | 20 | s := s + 1; 316 | 316 | 20 | s := s + 1; 317 | 317 | 20 | s := s + 1; 318 | 318 | 20 | s := s + 1; 319 | 319 | 20 | s := s + 1; 320 | 320 | 20 | s := s + 1; 321 | 321 | 20 | s := s + 1; 322 | 322 | 20 | s := s + 1; 323 | 323 | 20 | s := s + 1; 324 | 324 | 20 | s := s + 1; 325 | 325 | 20 | s := s + 1; 326 | 326 | 20 | s := s + 1; 327 | 327 | 20 | s := s + 1; 328 | 328 | 20 | s := s + 1; 329 | 329 | 20 | s := s + 1; 330 | 330 | 20 | s := s + 1; 331 | 331 | 20 | s := s + 1; 332 | 332 | 20 | s := s + 1; 333 | 333 | 20 | s := s + 1; 334 | 334 | 20 | s := s + 1; 335 | 335 | 20 | s := s + 1; 336 | 336 | 20 | s := s + 1; 337 | 337 | 20 | s := s + 1; 338 | 338 | 20 | s := s + 1; 339 | 339 | 20 | s := s + 1; 340 | 340 | 20 | s := s + 1; 341 | 341 | 20 | s := s + 1; 342 | 342 | 20 | s := s + 1; 343 | 343 | 20 | s := s + 1; 344 | 344 | 20 | s := s + 1; 345 | 345 | 20 | s := s + 1; 346 | 346 | 20 | s := s + 1; 347 | 347 | 20 | s := s + 1; 348 | 348 | 20 | s := s + 1; 349 | 349 | 20 | s := s + 1; 350 | 350 | 20 | s := s + 1; 351 | 351 | 20 | s := s + 1; 352 | 352 | 20 | s := s + 1; 353 | 353 | 20 | s := s + 1; 354 | 354 | 20 | s := s + 1; 355 | 355 | 20 | s := s + 1; 356 | 356 | 20 | s := s + 1; 357 | 357 | 20 | s := s + 1; 358 | 358 | 20 | s := s + 1; 359 | 359 | 20 | s := s + 1; 360 | 360 | 20 | s := s + 1; 361 | 361 | 20 | s := s + 1; 362 | 362 | 20 | s := s + 1; 363 | 363 | 20 | s := s + 1; 364 | 364 | 20 | s := s + 1; 365 | 365 | 20 | s := s + 1; 366 | 366 | 20 | s := s + 1; 367 | 367 | 20 | s := s + 1; 368 | 368 | 20 | s := s + 1; 369 | 369 | 20 | s := s + 1; 370 | 370 | 20 | s := s + 1; 371 | 371 | 20 | s := s + 1; 372 | 372 | 20 | s := s + 1; 373 | 373 | 20 | s := s + 1; 374 | 374 | 20 | s := s + 1; 375 | 375 | 20 | s := s + 1; 376 | 376 | 20 | s := s + 1; 377 | 377 | 20 | s := s + 1; 378 | 378 | 20 | s := s + 1; 379 | 379 | 20 | s := s + 1; 380 | 380 | 20 | s := s + 1; 381 | 381 | 20 | s := s + 1; 382 | 382 | 20 | s := s + 1; 383 | 383 | 20 | s := s + 1; 384 | 384 | 20 | s := s + 1; 385 | 385 | 20 | s := s + 1; 386 | 386 | 20 | s := s + 1; 387 | 387 | 20 | s := s + 1; 388 | 388 | 20 | s := s + 1; 389 | 389 | 20 | s := s + 1; 390 | 390 | 20 | s := s + 1; 391 | 391 | 20 | s := s + 1; 392 | 392 | 20 | s := s + 1; 393 | 393 | 20 | s := s + 1; 394 | 394 | 20 | s := s + 1; 395 | 395 | 20 | s := s + 1; 396 | 396 | 20 | s := s + 1; 397 | 397 | 20 | s := s + 1; 398 | 398 | 20 | s := s + 1; 399 | 399 | 20 | s := s + 1; 400 | 400 | 20 | s := s + 1; 401 | 401 | 20 | s := s + 1; 402 | 402 | 20 | s := s + 1; 403 | 403 | 20 | s := s + 1; 404 | 404 | 20 | s := s + 1; 405 | 405 | 20 | s := s + 1; 406 | 406 | 20 | s := s + 1; 407 | 407 | 20 | s := s + 1; 408 | 408 | 20 | s := s + 1; 409 | 409 | 20 | s := s + 1; 410 | 410 | 20 | s := s + 1; 411 | 411 | 20 | s := s + 1; 412 | 412 | 20 | s := s + 1; 413 | 413 | 20 | s := s + 1; 414 | 414 | 20 | s := s + 1; 415 | 415 | 20 | s := s + 1; 416 | 416 | 20 | s := s + 1; 417 | 417 | 20 | s := s + 1; 418 | 418 | 20 | s := s + 1; 419 | 419 | 20 | s := s + 1; 420 | 420 | 20 | s := s + 1; 421 | 421 | 20 | s := s + 1; 422 | 422 | 20 | s := s + 1; 423 | 423 | 20 | s := s + 1; 424 | 424 | 20 | s := s + 1; 425 | 425 | 20 | s := s + 1; 426 | 426 | 20 | s := s + 1; 427 | 427 | 20 | s := s + 1; 428 | 428 | 20 | s := s + 1; 429 | 429 | 20 | s := s + 1; 430 | 430 | 20 | s := s + 1; 431 | 431 | 20 | s := s + 1; 432 | 432 | 20 | s := s + 1; 433 | 433 | 20 | s := s + 1; 434 | 434 | 20 | s := s + 1; 435 | 435 | 20 | s := s + 1; 436 | 436 | 20 | s := s + 1; 437 | 437 | 20 | s := s + 1; 438 | 438 | 20 | s := s + 1; 439 | 439 | 20 | s := s + 1; 440 | 440 | 20 | s := s + 1; 441 | 441 | 20 | s := s + 1; 442 | 442 | 20 | s := s + 1; 443 | 443 | 20 | s := s + 1; 444 | 444 | 20 | s := s + 1; 445 | 445 | 20 | s := s + 1; 446 | 446 | 20 | s := s + 1; 447 | 447 | 20 | s := s + 1; 448 | 448 | 20 | s := s + 1; 449 | 449 | 20 | s := s + 1; 450 | 450 | 20 | s := s + 1; 451 | 451 | 20 | s := s + 1; 452 | 452 | 20 | s := s + 1; 453 | 453 | 20 | s := s + 1; 454 | 454 | 20 | s := s + 1; 455 | 455 | 20 | s := s + 1; 456 | 456 | 20 | s := s + 1; 457 | 457 | 20 | s := s + 1; 458 | 458 | 20 | s := s + 1; 459 | 459 | 20 | s := s + 1; 460 | 460 | 20 | s := s + 1; 461 | 461 | 20 | s := s + 1; 462 | 462 | 20 | s := s + 1; 463 | 463 | 20 | s := s + 1; 464 | 464 | 20 | s := s + 1; 465 | 465 | 20 | s := s + 1; 466 | 466 | 20 | s := s + 1; 467 | 467 | 20 | s := s + 1; 468 | 468 | 20 | s := s + 1; 469 | 469 | 20 | s := s + 1; 470 | 470 | 20 | s := s + 1; 471 | 471 | 20 | s := s + 1; 472 | 472 | 20 | s := s + 1; 473 | 473 | 20 | s := s + 1; 474 | 474 | 20 | s := s + 1; 475 | 475 | 20 | s := s + 1; 476 | 476 | 20 | s := s + 1; 477 | 477 | 20 | s := s + 1; 478 | 478 | 20 | s := s + 1; 479 | 479 | 20 | s := s + 1; 480 | 480 | 20 | s := s + 1; 481 | 481 | 20 | s := s + 1; 482 | 482 | 20 | s := s + 1; 483 | 483 | 20 | s := s + 1; 484 | 484 | 20 | s := s + 1; 485 | 485 | 20 | s := s + 1; 486 | 486 | 20 | s := s + 1; 487 | 487 | 20 | s := s + 1; 488 | 488 | 20 | s := s + 1; 489 | 489 | 20 | s := s + 1; 490 | 490 | 20 | s := s + 1; 491 | 491 | 20 | s := s + 1; 492 | 492 | 20 | s := s + 1; 493 | 493 | 20 | s := s + 1; 494 | 494 | 20 | s := s + 1; 495 | 495 | 20 | s := s + 1; 496 | 496 | 20 | s := s + 1; 497 | 497 | 20 | s := s + 1; 498 | 498 | 20 | s := s + 1; 499 | 499 | 20 | s := s + 1; 500 | 500 | 20 | s := s + 1; 501 | 501 | 20 | s := s + 1; 502 | 502 | 20 | s := s + 1; 503 | 503 | 20 | s := s + 1; 504 | 504 | 20 | s := s + 1; 505 | 505 | 20 | s := s + 1; 506 | 506 | 20 | s := s + 1; 507 | 507 | 20 | s := s + 1; 508 | 508 | 20 | s := s + 1; 509 | 509 | 20 | s := s + 1; 510 | 510 | 20 | s := s + 1; 511 | 511 | 20 | s := s + 1; 512 | 512 | 20 | s := s + 1; 513 | 513 | 20 | s := s + 1; 514 | 514 | 20 | s := s + 1; 515 | 515 | 20 | s := s + 1; 516 | 516 | 20 | s := s + 1; 517 | 517 | 20 | s := s + 1; 518 | 518 | 20 | s := s + 1; 519 | 519 | 20 | s := s + 1; 520 | 520 | 20 | s := s + 1; 521 | 521 | 20 | s := s + 1; 522 | 522 | 20 | s := s + 1; 523 | 523 | 20 | s := s + 1; 524 | 524 | 20 | s := s + 1; 525 | 525 | 20 | s := s + 1; 526 | 526 | 20 | s := s + 1; 527 | 527 | 20 | s := s + 1; 528 | 528 | 20 | s := s + 1; 529 | 529 | 20 | s := s + 1; 530 | 530 | 20 | s := s + 1; 531 | 531 | 20 | s := s + 1; 532 | 532 | 20 | s := s + 1; 533 | 533 | 20 | s := s + 1; 534 | 534 | 20 | s := s + 1; 535 | 535 | 20 | s := s + 1; 536 | 536 | 20 | s := s + 1; 537 | 537 | 20 | s := s + 1; 538 | 538 | 20 | s := s + 1; 539 | 539 | 20 | s := s + 1; 540 | 540 | 20 | s := s + 1; 541 | 541 | 20 | s := s + 1; 542 | 542 | 20 | s := s + 1; 543 | 543 | 20 | s := s + 1; 544 | 544 | 20 | s := s + 1; 545 | 545 | 20 | s := s + 1; 546 | 546 | 20 | s := s + 1; 547 | 547 | 20 | s := s + 1; 548 | 548 | 20 | s := s + 1; 549 | 549 | 20 | s := s + 1; 550 | 550 | 20 | s := s + 1; 551 | 551 | 20 | s := s + 1; 552 | 552 | 20 | s := s + 1; 553 | 553 | 20 | s := s + 1; 554 | 554 | 20 | s := s + 1; 555 | 555 | 20 | s := s + 1; 556 | 556 | 20 | s := s + 1; 557 | 557 | 20 | s := s + 1; 558 | 558 | 20 | s := s + 1; 559 | 559 | 20 | s := s + 1; 560 | 560 | 20 | s := s + 1; 561 | 561 | 20 | s := s + 1; 562 | 562 | 20 | s := s + 1; 563 | 563 | 20 | s := s + 1; 564 | 564 | 20 | s := s + 1; 565 | 565 | 20 | s := s + 1; 566 | 566 | 20 | s := s + 1; 567 | 567 | 20 | s := s + 1; 568 | 568 | 20 | s := s + 1; 569 | 569 | 20 | s := s + 1; 570 | 570 | 20 | s := s + 1; 571 | 571 | 20 | s := s + 1; 572 | 572 | 20 | s := s + 1; 573 | 573 | 20 | s := s + 1; 574 | 574 | 20 | s := s + 1; 575 | 575 | 20 | s := s + 1; 576 | 576 | 20 | s := s + 1; 577 | 577 | 20 | s := s + 1; 578 | 578 | 20 | s := s + 1; 579 | 579 | 20 | s := s + 1; 580 | 580 | 20 | s := s + 1; 581 | 581 | 20 | s := s + 1; 582 | 582 | 20 | s := s + 1; 583 | 583 | 20 | s := s + 1; 584 | 584 | 20 | s := s + 1; 585 | 585 | 20 | s := s + 1; 586 | 586 | 20 | s := s + 1; 587 | 587 | 20 | s := s + 1; 588 | 588 | 20 | s := s + 1; 589 | 589 | 20 | s := s + 1; 590 | 590 | 20 | s := s + 1; 591 | 591 | 20 | s := s + 1; 592 | 592 | 20 | s := s + 1; 593 | 593 | 20 | s := s + 1; 594 | 594 | 20 | s := s + 1; 595 | 595 | 20 | s := s + 1; 596 | 596 | 20 | s := s + 1; 597 | 597 | 20 | s := s + 1; 598 | 598 | 20 | s := s + 1; 599 | 599 | 20 | s := s + 1; 600 | 600 | 20 | s := s + 1; 601 | 601 | 20 | s := s + 1; 602 | 602 | 20 | s := s + 1; 603 | 603 | 20 | s := s + 1; 604 | 604 | 20 | s := s + 1; 605 | 605 | 20 | s := s + 1; 606 | 606 | 20 | s := s + 1; 607 | 607 | 20 | s := s + 1; 608 | 608 | 20 | s := s + 1; 609 | 609 | 20 | s := s + 1; 610 | 610 | 20 | s := s + 1; 611 | 611 | 20 | s := s + 1; 612 | 612 | 20 | s := s + 1; 613 | 613 | 20 | s := s + 1; 614 | 614 | 20 | s := s + 1; 615 | 615 | 20 | s := s + 1; 616 | 616 | 20 | s := s + 1; 617 | 617 | 20 | s := s + 1; 618 | 618 | 20 | s := s + 1; 619 | 619 | 20 | s := s + 1; 620 | 620 | 20 | s := s + 1; 621 | 621 | 20 | s := s + 1; 622 | 622 | 20 | s := s + 1; 623 | 623 | 20 | s := s + 1; 624 | 624 | 20 | s := s + 1; 625 | 625 | 20 | s := s + 1; 626 | 626 | 20 | s := s + 1; 627 | 627 | 20 | s := s + 1; 628 | 628 | 20 | s := s + 1; 629 | 629 | 20 | s := s + 1; 630 | 630 | 20 | s := s + 1; 631 | 631 | 20 | s := s + 1; 632 | 632 | 20 | s := s + 1; 633 | 633 | 20 | s := s + 1; 634 | 634 | 20 | s := s + 1; 635 | 635 | 20 | s := s + 1; 636 | 636 | 20 | s := s + 1; 637 | 637 | 20 | s := s + 1; 638 | 638 | 20 | s := s + 1; 639 | 639 | 20 | s := s + 1; 640 | 640 | 20 | s := s + 1; 641 | 641 | 20 | s := s + 1; 642 | 642 | 20 | s := s + 1; 643 | 643 | 20 | s := s + 1; 644 | 644 | 20 | s := s + 1; 645 | 645 | 20 | s := s + 1; 646 | 646 | 20 | s := s + 1; 647 | 647 | 20 | s := s + 1; 648 | 648 | 20 | s := s + 1; 649 | 649 | 20 | s := s + 1; 650 | 650 | 20 | s := s + 1; 651 | 651 | 20 | s := s + 1; 652 | 652 | 20 | s := s + 1; 653 | 653 | 20 | s := s + 1; 654 | 654 | 20 | s := s + 1; 655 | 655 | 20 | s := s + 1; 656 | 656 | 20 | s := s + 1; 657 | 657 | 20 | s := s + 1; 658 | 658 | 20 | s := s + 1; 659 | 659 | 20 | s := s + 1; 660 | 660 | 20 | s := s + 1; 661 | 661 | 20 | s := s + 1; 662 | 662 | 20 | s := s + 1; 663 | 663 | 20 | s := s + 1; 664 | 664 | 20 | s := s + 1; 665 | 665 | 20 | s := s + 1; 666 | 666 | 20 | s := s + 1; 667 | 667 | 20 | s := s + 1; 668 | 668 | 20 | s := s + 1; 669 | 669 | 20 | s := s + 1; 670 | 670 | 20 | s := s + 1; 671 | 671 | 20 | s := s + 1; 672 | 672 | 20 | s := s + 1; 673 | 673 | 20 | s := s + 1; 674 | 674 | 20 | s := s + 1; 675 | 675 | 20 | s := s + 1; 676 | 676 | 20 | s := s + 1; 677 | 677 | 20 | s := s + 1; 678 | 678 | 20 | s := s + 1; 679 | 679 | 20 | s := s + 1; 680 | 680 | 20 | s := s + 1; 681 | 681 | 20 | s := s + 1; 682 | 682 | 20 | s := s + 1; 683 | 683 | 20 | s := s + 1; 684 | 684 | 20 | s := s + 1; 685 | 685 | 20 | s := s + 1; 686 | 686 | 20 | s := s + 1; 687 | 687 | 20 | s := s + 1; 688 | 688 | 20 | s := s + 1; 689 | 689 | 20 | s := s + 1; 690 | 690 | 20 | s := s + 1; 691 | 691 | 20 | s := s + 1; 692 | 692 | 20 | s := s + 1; 693 | 693 | 20 | s := s + 1; 694 | 694 | 20 | s := s + 1; 695 | 695 | 20 | s := s + 1; 696 | 696 | 20 | s := s + 1; 697 | 697 | 20 | s := s + 1; 698 | 698 | 20 | s := s + 1; 699 | 699 | 20 | s := s + 1; 700 | 700 | 20 | s := s + 1; 701 | 701 | 20 | s := s + 1; 702 | 702 | 20 | s := s + 1; 703 | 703 | 20 | s := s + 1; 704 | 704 | 20 | s := s + 1; 705 | 705 | 20 | s := s + 1; 706 | 706 | 20 | s := s + 1; 707 | 707 | 20 | s := s + 1; 708 | 708 | 20 | s := s + 1; 709 | 709 | 20 | s := s + 1; 710 | 710 | 20 | s := s + 1; 711 | 711 | 20 | s := s + 1; 712 | 712 | 20 | s := s + 1; 713 | 713 | 20 | s := s + 1; 714 | 714 | 20 | s := s + 1; 715 | 715 | 20 | s := s + 1; 716 | 716 | 20 | s := s + 1; 717 | 717 | 20 | s := s + 1; 718 | 718 | 20 | s := s + 1; 719 | 719 | 20 | s := s + 1; 720 | 720 | 20 | s := s + 1; 721 | 721 | 20 | s := s + 1; 722 | 722 | 20 | s := s + 1; 723 | 723 | 20 | s := s + 1; 724 | 724 | 20 | s := s + 1; 725 | 725 | 20 | s := s + 1; 726 | 726 | 20 | s := s + 1; 727 | 727 | 20 | s := s + 1; 728 | 728 | 20 | s := s + 1; 729 | 729 | 20 | s := s + 1; 730 | 730 | 20 | s := s + 1; 731 | 731 | 20 | s := s + 1; 732 | 732 | 20 | s := s + 1; 733 | 733 | 20 | s := s + 1; 734 | 734 | 20 | s := s + 1; 735 | 735 | 20 | s := s + 1; 736 | 736 | 20 | s := s + 1; 737 | 737 | 20 | s := s + 1; 738 | 738 | 20 | s := s + 1; 739 | 739 | 20 | s := s + 1; 740 | 740 | 20 | s := s + 1; 741 | 741 | 20 | s := s + 1; 742 | 742 | 20 | s := s + 1; 743 | 743 | 20 | s := s + 1; 744 | 744 | 20 | s := s + 1; 745 | 745 | 20 | s := s + 1; 746 | 746 | 20 | s := s + 1; 747 | 747 | 20 | s := s + 1; 748 | 748 | 20 | s := s + 1; 749 | 749 | 20 | s := s + 1; 750 | 750 | 20 | s := s + 1; 751 | 751 | 20 | s := s + 1; 752 | 752 | 20 | s := s + 1; 753 | 753 | 20 | s := s + 1; 754 | 754 | 20 | s := s + 1; 755 | 755 | 20 | s := s + 1; 756 | 756 | 20 | s := s + 1; 757 | 757 | 20 | s := s + 1; 758 | 758 | 20 | s := s + 1; 759 | 759 | 20 | s := s + 1; 760 | 760 | 20 | s := s + 1; 761 | 761 | 20 | s := s + 1; 762 | 762 | 20 | s := s + 1; 763 | 763 | 20 | s := s + 1; 764 | 764 | 20 | s := s + 1; 765 | 765 | 20 | s := s + 1; 766 | 766 | 20 | s := s + 1; 767 | 767 | 20 | s := s + 1; 768 | 768 | 20 | s := s + 1; 769 | 769 | 20 | s := s + 1; 770 | 770 | 20 | s := s + 1; 771 | 771 | 20 | s := s + 1; 772 | 772 | 20 | s := s + 1; 773 | 773 | 20 | s := s + 1; 774 | 774 | 20 | s := s + 1; 775 | 775 | 20 | s := s + 1; 776 | 776 | 20 | s := s + 1; 777 | 777 | 20 | s := s + 1; 778 | 778 | 20 | s := s + 1; 779 | 779 | 20 | s := s + 1; 780 | 780 | 20 | s := s + 1; 781 | 781 | 20 | s := s + 1; 782 | 782 | 20 | s := s + 1; 783 | 783 | 20 | s := s + 1; 784 | 784 | 20 | s := s + 1; 785 | 785 | 20 | s := s + 1; 786 | 786 | 20 | s := s + 1; 787 | 787 | 20 | s := s + 1; 788 | 788 | 20 | s := s + 1; 789 | 789 | 20 | s := s + 1; 790 | 790 | 20 | s := s + 1; 791 | 791 | 20 | s := s + 1; 792 | 792 | 20 | s := s + 1; 793 | 793 | 20 | s := s + 1; 794 | 794 | 20 | s := s + 1; 795 | 795 | 20 | s := s + 1; 796 | 796 | 20 | s := s + 1; 797 | 797 | 20 | s := s + 1; 798 | 798 | 20 | s := s + 1; 799 | 799 | 20 | s := s + 1; 800 | 800 | 20 | s := s + 1; 801 | 801 | 20 | s := s + 1; 802 | 802 | 20 | s := s + 1; 803 | 803 | 20 | s := s + 1; 804 | 804 | 20 | s := s + 1; 805 | 805 | 20 | s := s + 1; 806 | 806 | 20 | s := s + 1; 807 | 807 | 20 | s := s + 1; 808 | 808 | 20 | s := s + 1; 809 | 809 | 20 | s := s + 1; 810 | 810 | 20 | s := s + 1; 811 | 811 | 20 | s := s + 1; 812 | 812 | 20 | s := s + 1; 813 | 813 | 20 | s := s + 1; 814 | 814 | 20 | s := s + 1; 815 | 815 | 20 | s := s + 1; 816 | 816 | 20 | s := s + 1; 817 | 817 | 20 | s := s + 1; 818 | 818 | 20 | s := s + 1; 819 | 819 | 20 | s := s + 1; 820 | 820 | 20 | s := s + 1; 821 | 821 | 20 | s := s + 1; 822 | 822 | 20 | s := s + 1; 823 | 823 | 20 | s := s + 1; 824 | 824 | 20 | s := s + 1; 825 | 825 | 20 | s := s + 1; 826 | 826 | 20 | s := s + 1; 827 | 827 | 20 | s := s + 1; 828 | 828 | 20 | s := s + 1; 829 | 829 | 20 | s := s + 1; 830 | 830 | 20 | s := s + 1; 831 | 831 | 20 | s := s + 1; 832 | 832 | 20 | s := s + 1; 833 | 833 | 20 | s := s + 1; 834 | 834 | 20 | s := s + 1; 835 | 835 | 20 | s := s + 1; 836 | 836 | 20 | s := s + 1; 837 | 837 | 20 | s := s + 1; 838 | 838 | 20 | s := s + 1; 839 | 839 | 20 | s := s + 1; 840 | 840 | 20 | s := s + 1; 841 | 841 | 20 | s := s + 1; 842 | 842 | 20 | s := s + 1; 843 | 843 | 20 | s := s + 1; 844 | 844 | 20 | s := s + 1; 845 | 845 | 20 | s := s + 1; 846 | 846 | 20 | s := s + 1; 847 | 847 | 20 | s := s + 1; 848 | 848 | 20 | s := s + 1; 849 | 849 | 20 | s := s + 1; 850 | 850 | 20 | s := s + 1; 851 | 851 | 20 | s := s + 1; 852 | 852 | 20 | s := s + 1; 853 | 853 | 20 | s := s + 1; 854 | 854 | 20 | s := s + 1; 855 | 855 | 20 | s := s + 1; 856 | 856 | 20 | s := s + 1; 857 | 857 | 20 | s := s + 1; 858 | 858 | 20 | s := s + 1; 859 | 859 | 20 | s := s + 1; 860 | 860 | 20 | s := s + 1; 861 | 861 | 20 | s := s + 1; 862 | 862 | 20 | s := s + 1; 863 | 863 | 20 | s := s + 1; 864 | 864 | 20 | s := s + 1; 865 | 865 | 20 | s := s + 1; 866 | 866 | 20 | s := s + 1; 867 | 867 | 20 | s := s + 1; 868 | 868 | 20 | s := s + 1; 869 | 869 | 20 | s := s + 1; 870 | 870 | 20 | s := s + 1; 871 | 871 | 20 | s := s + 1; 872 | 872 | 20 | s := s + 1; 873 | 873 | 20 | s := s + 1; 874 | 874 | 20 | s := s + 1; 875 | 875 | 20 | s := s + 1; 876 | 876 | 20 | s := s + 1; 877 | 877 | 20 | s := s + 1; 878 | 878 | 20 | s := s + 1; 879 | 879 | 20 | s := s + 1; 880 | 880 | 20 | s := s + 1; 881 | 881 | 20 | s := s + 1; 882 | 882 | 20 | s := s + 1; 883 | 883 | 20 | s := s + 1; 884 | 884 | 20 | s := s + 1; 885 | 885 | 20 | s := s + 1; 886 | 886 | 20 | s := s + 1; 887 | 887 | 20 | s := s + 1; 888 | 888 | 20 | s := s + 1; 889 | 889 | 20 | s := s + 1; 890 | 890 | 20 | s := s + 1; 891 | 891 | 20 | s := s + 1; 892 | 892 | 20 | s := s + 1; 893 | 893 | 20 | s := s + 1; 894 | 894 | 20 | s := s + 1; 895 | 895 | 20 | s := s + 1; 896 | 896 | 20 | s := s + 1; 897 | 897 | 20 | s := s + 1; 898 | 898 | 20 | s := s + 1; 899 | 899 | 20 | s := s + 1; 900 | 900 | 20 | s := s + 1; 901 | 901 | 20 | s := s + 1; 902 | 902 | 20 | s := s + 1; 903 | 903 | 20 | s := s + 1; 904 | 904 | 20 | s := s + 1; 905 | 905 | 20 | s := s + 1; 906 | 906 | 20 | s := s + 1; 907 | 907 | 20 | s := s + 1; 908 | 908 | 20 | s := s + 1; 909 | 909 | 20 | s := s + 1; 910 | 910 | 20 | s := s + 1; 911 | 911 | 20 | s := s + 1; 912 | 912 | 20 | s := s + 1; 913 | 913 | 20 | s := s + 1; 914 | 914 | 20 | s := s + 1; 915 | 915 | 20 | s := s + 1; 916 | 916 | 20 | s := s + 1; 917 | 917 | 20 | s := s + 1; 918 | 918 | 20 | s := s + 1; 919 | 919 | 20 | s := s + 1; 920 | 920 | 20 | s := s + 1; 921 | 921 | 20 | s := s + 1; 922 | 922 | 20 | s := s + 1; 923 | 923 | 20 | s := s + 1; 924 | 924 | 20 | s := s + 1; 925 | 925 | 20 | s := s + 1; 926 | 926 | 20 | s := s + 1; 927 | 927 | 20 | s := s + 1; 928 | 928 | 20 | s := s + 1; 929 | 929 | 20 | s := s + 1; 930 | 930 | 20 | s := s + 1; 931 | 931 | 20 | s := s + 1; 932 | 932 | 20 | s := s + 1; 933 | 933 | 20 | s := s + 1; 934 | 934 | 20 | s := s + 1; 935 | 935 | 20 | s := s + 1; 936 | 936 | 20 | s := s + 1; 937 | 937 | 20 | s := s + 1; 938 | 938 | 20 | s := s + 1; 939 | 939 | 20 | s := s + 1; 940 | 940 | 20 | s := s + 1; 941 | 941 | 20 | s := s + 1; 942 | 942 | 20 | s := s + 1; 943 | 943 | 20 | s := s + 1; 944 | 944 | 20 | s := s + 1; 945 | 945 | 20 | s := s + 1; 946 | 946 | 20 | s := s + 1; 947 | 947 | 20 | s := s + 1; 948 | 948 | 20 | s := s + 1; 949 | 949 | 20 | s := s + 1; 950 | 950 | 20 | s := s + 1; 951 | 951 | 20 | s := s + 1; 952 | 952 | 20 | s := s + 1; 953 | 953 | 20 | s := s + 1; 954 | 954 | 20 | s := s + 1; 955 | 955 | 20 | s := s + 1; 956 | 956 | 20 | s := s + 1; 957 | 957 | 20 | s := s + 1; 958 | 958 | 20 | s := s + 1; 959 | 959 | 20 | s := s + 1; 960 | 960 | 20 | s := s + 1; 961 | 961 | 20 | s := s + 1; 962 | 962 | 20 | s := s + 1; 963 | 963 | 20 | s := s + 1; 964 | 964 | 20 | s := s + 1; 965 | 965 | 20 | s := s + 1; 966 | 966 | 20 | s := s + 1; 967 | 967 | 20 | s := s + 1; 968 | 968 | 20 | s := s + 1; 969 | 969 | 20 | s := s + 1; 970 | 970 | 20 | s := s + 1; 971 | 971 | 20 | s := s + 1; 972 | 972 | 20 | s := s + 1; 973 | 973 | 20 | s := s + 1; 974 | 974 | 20 | s := s + 1; 975 | 975 | 20 | s := s + 1; 976 | 976 | 20 | s := s + 1; 977 | 977 | 20 | s := s + 1; 978 | 978 | 20 | s := s + 1; 979 | 979 | 20 | s := s + 1; 980 | 980 | 20 | s := s + 1; 981 | 981 | 20 | s := s + 1; 982 | 982 | 20 | s := s + 1; 983 | 983 | 20 | s := s + 1; 984 | 984 | 20 | s := s + 1; 985 | 985 | 20 | s := s + 1; 986 | 986 | 20 | s := s + 1; 987 | 987 | 20 | s := s + 1; 988 | 988 | 20 | s := s + 1; 989 | 989 | 20 | s := s + 1; 990 | 990 | 20 | s := s + 1; 991 | 991 | 20 | s := s + 1; 992 | 992 | 20 | s := s + 1; 993 | 993 | 20 | s := s + 1; 994 | 994 | 20 | s := s + 1; 995 | 995 | 20 | s := s + 1; 996 | 996 | 20 | s := s + 1; 997 | 997 | 20 | s := s + 1; 998 | 998 | 20 | s := s + 1; 999 | 999 | 20 | s := s + 1; 1000 | 1000 | 20 | s := s + 1; 1001 | 1001 | 20 | s := s + 1; 1002 | 1002 | 20 | s := s + 1; 1003 | 1003 | 20 | s := s + 1; 1004 | 1004 | 20 | s := s + 1; 1005 | 1005 | 20 | s := s + 1; 1006 | 1006 | 20 | s := s + 1; 1007 | 1007 | 20 | s := s + 1; 1008 | 1008 | 20 | s := s + 1; 1009 | 1009 | 20 | s := s + 1; 1010 | 1010 | 20 | s := s + 1; 1011 | 1011 | 20 | s := s + 1; 1012 | 1012 | 20 | s := s + 1; 1013 | 1013 | 20 | s := s + 1; 1014 | 1014 | 20 | s := s + 1; 1015 | 1015 | 20 | s := s + 1; 1016 | 1016 | 20 | s := s + 1; 1017 | 1017 | 20 | s := s + 1; 1018 | 1018 | 20 | s := s + 1; 1019 | 1019 | 20 | s := s + 1; 1020 | 1020 | 20 | s := s + 1; 1021 | 1021 | 20 | s := s + 1; 1022 | 1022 | 20 | s := s + 1; 1023 | 1023 | 20 | s := s + 1; 1024 | 1024 | 20 | s := s + 1; 1025 | 1025 | 20 | s := s + 1; 1026 | 1026 | 20 | s := s + 1; 1027 | 1027 | 20 | s := s + 1; 1028 | 1028 | 20 | s := s + 1; 1029 | 1029 | 20 | s := s + 1; 1030 | 1030 | 20 | s := s + 1; 1031 | 1031 | 20 | s := s + 1; 1032 | 1032 | 20 | s := s + 1; 1033 | 1033 | 20 | s := s + 1; 1034 | 1034 | 20 | s := s + 1; 1035 | 1035 | 20 | s := s + 1; 1036 | 1036 | 20 | s := s + 1; 1037 | 1037 | 20 | s := s + 1; 1038 | | | end loop; 1039 | | | end loop; 1040 | 1040 | 20 | j := j + 1; 1041 | | | end loop; 1042 | | | exception when others then 1043 | 1043 | 0 | raise 'reraised exception %', sqlerrm; 1044 | | | end; 1045 | 1045 | 2 | return $1; 1046 | | | end; (1046 rows) select funcoid, exec_count from plpgsql_profiler_functions_all(); funcoid | exec_count -----------------+------------ longfx(integer) | 2 (1 row) create table testr(a int); create rule testr_rule as on insert to testr do nothing; create or replace function fx_testr() returns void as $$ begin insert into testr values(20); end; $$ language plpgsql; -- allow some rules on tables select fx_testr(); fx_testr ---------- (1 row) select * from plpgsql_check_function_tb('fx_testr'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+---------+--------+------+-------+----------+-------+--------- (0 rows) drop function fx_testr(); drop table testr; -- coverage tests set plpgsql_check.profiler to on; create or replace function covtest(int) returns int as $$ declare a int = $1; begin a := a + 1; if a < 10 then a := a + 1; end if; a := a + 1; return a; end; $$ language plpgsql; set plpgsql_check.profiler to on; select covtest(10); covtest --------- 12 (1 row) select stmtid, exec_stmts, stmtname from plpgsql_profiler_function_statements_tb('covtest'); stmtid | exec_stmts | stmtname --------+------------+----------------- 1 | 1 | statement block 2 | 1 | assignment 3 | 1 | IF 4 | 0 | assignment 5 | 1 | assignment 6 | 1 | RETURN (6 rows) select plpgsql_coverage_statements('covtest'); plpgsql_coverage_statements ----------------------------- 0.8333333333333334 (1 row) select plpgsql_coverage_branches('covtest'); plpgsql_coverage_branches --------------------------- 0.5 (1 row) select covtest(1); covtest --------- 4 (1 row) select stmtid, exec_stmts, stmtname from plpgsql_profiler_function_statements_tb('covtest'); stmtid | exec_stmts | stmtname --------+------------+----------------- 1 | 2 | statement block 2 | 2 | assignment 3 | 2 | IF 4 | 1 | assignment 5 | 2 | assignment 6 | 2 | RETURN (6 rows) select plpgsql_coverage_statements('covtest'); plpgsql_coverage_statements ----------------------------- 1 (1 row) select plpgsql_coverage_branches('covtest'); plpgsql_coverage_branches --------------------------- 1 (1 row) set plpgsql_check.profiler to off; create or replace function f() returns void as $$ declare r1 record; r2 record; begin select 10 as a, 20 as b into r1; r2 := json_populate_record(r1, '{}'); raise notice '%', r2.a; end; $$ language plpgsql; select * from plpgsql_check_function('f'); plpgsql_check_function ------------------------ (0 rows) -- fix issue #63 create or replace function distinct_array(arr anyarray) returns anyarray as $$ begin return array(select distinct e from unnest(arr) as e); end; $$ language plpgsql immutable; select plpgsql_check_function('distinct_array(anyarray)'); plpgsql_check_function ------------------------ (0 rows) drop function distinct_array(anyarray); -- tracer test set plpgsql_check.enable_tracer to on; set plpgsql_check.tracer to on; set plpgsql_check.tracer_test_mode = true; \set VERBOSITY terse create or replace function fxo(a int, b int, c date, d numeric) returns void as $$ begin insert into tracer_tab values(a,b,c,d); end; $$ language plpgsql; create table tracer_tab(a int, b int, c date, d numeric); create or replace function tracer_tab_trg_fx() returns trigger as $$ begin return new; end; $$ language plpgsql; create trigger tracer_tab_trg before insert on tracer_tab for each row execute procedure tracer_tab_trg_fx(); select fxo(10,20,'20200815', 3.14); NOTICE: #0 ->> start of function fxo(integer,integer,date,numeric) (oid=0, tnl=1) NOTICE: #0 "a" => '10', "b" => '20', "c" => '08-15-2020', "d" => '3.14' NOTICE: #2 ->> start of function tracer_tab_trg_fx() (oid=0, tnl=1) NOTICE: #2 context: SQL statement "insert into tracer_tab values(a,b,c,d)" NOTICE: #2 triggered by before row insert trigger NOTICE: #2 "new" => '(10,20,08-15-2020,3.14)' NOTICE: #2 <<- end of function tracer_tab_trg_fx (elapsed time=0.010 ms) NOTICE: #0 <<- end of function fxo (elapsed time=0.010 ms) fxo ----- (1 row) select fxo(11,21,'20200816', 6.28); NOTICE: #0 ->> start of function fxo(integer,integer,date,numeric) (oid=0, tnl=1) NOTICE: #0 "a" => '11', "b" => '21', "c" => '08-16-2020', "d" => '6.28' NOTICE: #2 ->> start of function tracer_tab_trg_fx() (oid=0, tnl=1) NOTICE: #2 context: SQL statement "insert into tracer_tab values(a,b,c,d)" NOTICE: #2 triggered by before row insert trigger NOTICE: #2 "new" => '(11,21,08-16-2020,6.28)' NOTICE: #2 <<- end of function tracer_tab_trg_fx (elapsed time=0.010 ms) NOTICE: #0 <<- end of function fxo (elapsed time=0.010 ms) fxo ----- (1 row) set plpgsql_check.enable_tracer to off; set plpgsql_check.tracer to off; drop table tracer_tab cascade; drop function tracer_tab_trg_fx(); drop function fxo(int, int, date, numeric); create or replace function foo_trg_func() returns trigger as $$ begin -- bad function, RETURN is missing end; $$ language plpgsql; create table foo(a int); create trigger foo_trg before insert for each row execute procedure foo_trg_func(); ERROR: syntax error at or near "for" at character 38 -- should to print error select * from plpgsql_check_function('foo_trg_func', 'foo'); plpgsql_check_function ------------------------------------------------------------ error:2F005:control reached end of function without RETURN (1 row) drop table foo; drop function foo_trg_func(); -- check event trigger function create or replace function f1() returns event_trigger as $$ BEGIN RAISE NOTICE 'test_event_trigger: % %', tg_event, tg_tag; END $$ language plpgsql; select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+---------+--------+------+-------+----------+-------+--------- (0 rows) -- should fail create or replace function f1() returns event_trigger as $$ BEGIN RAISE NOTICE 'test_event_trigger: % %', tg_event, tg_tagX; END $$ language plpgsql; select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+---------------------------------+--------+------+-------+----------+---------+--------- f1 | 3 | RAISE | 42703 | column "tg_tagx" does not exist | | | error | 1 | tg_tagX | (1 row) drop function f1(); -- check event trigger function create or replace function f1() returns event_trigger as $$ BEGIN RAISE NOTICE 'test_event_trigger: % %', tg_event, tg_tag; END $$ language plpgsql; select * from plpgsql_check_function('f1()'); plpgsql_check_function ------------------------ (0 rows) -- should fail create or replace function f1() returns event_trigger as $$ BEGIN RAISE NOTICE 'test_event_trigger: % %', tg_event, tg_tagX; END $$ language plpgsql; select * from plpgsql_check_function('f1()'); plpgsql_check_function ----------------------------------------------------- error:42703:3:RAISE:column "tg_tagx" does not exist Query: tg_tagX -- ^ (3 rows) drop function f1(); create table t1tab(a int, b int); create or replace function f1() returns setof t1tab as $$ begin return next (10,20); return; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings => true); plpgsql_check_function ----------------------------------------------------------------------------------------- performance:00000:routine is marked as VOLATILE, should be IMMUTABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (2 rows) create or replace function f1() returns setof t1tab as $$ begin return next (10::numeric,20); return; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings => true); plpgsql_check_function ------------------------------------------------------------------------------------ error:42804:3:RETURN NEXT:returned record type does not match expected record type Detail: Returned type numeric does not match expected type integer in column 1. (2 rows) create or replace function f1() returns setof t1tab as $$ declare a int; b int; begin return next (a,b); return; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings => true); plpgsql_check_function ----------------------------------------------------------------------------------------- performance:00000:routine is marked as VOLATILE, should be IMMUTABLE Hint: When you fix this issue, please, recheck other functions that uses this function. (2 rows) create or replace function f1() returns setof t1tab as $$ declare a numeric; b int; begin return next (a,b::numeric); return; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings => true); plpgsql_check_function ------------------------------------------------------------------------------------ error:42804:4:RETURN NEXT:returned record type does not match expected record type Detail: Returned type numeric does not match expected type integer in column 1. (2 rows) drop function f1(); create table t1(a int, b int); create or replace function fx() returns t2 as $$ begin return (10,20,30)::t1; end; $$ language plpgsql; select * from plpgsql_check_function('fx()', performance_warnings => true); plpgsql_check_function ---------------------------------------------------- error:42846:3:RETURN:cannot cast type record to t1 Query: (10,20,30)::t1 -- ^ Detail: Input has too many columns. (4 rows) drop function fx(); drop table t1tab; drop table t1; create or replace function fx() returns void as $$ begin assert exists(select * from foo); assert false, (select boo from boo limit 1); end; $$ language plpgsql; select * from plpgsql_check_function('fx()', fatal_errors => false); plpgsql_check_function ---------------------------------------------------- error:42P01:3:ASSERT:relation "foo" does not exist Query: exists(select * from foo) -- ^ error:42P01:4:ASSERT:relation "boo" does not exist Query: (select boo from boo limit 1) -- ^ (6 rows) create or replace function ml_trg() returns trigger as $$ #option dump declare begin if TG_OP = 'INSERT' then if NEW.status_from IS NULL then begin -- performance issue only select status into NEW.status_from from pa where pa_id = NEW.pa_id; -- nonexist target value select status into NEW.status_from_xxx from pa where pa_id = NEW.pa_id; exception when DATA_EXCEPTION then new.status_from := 'DE'; end; end if; end if; if TG_OP = 'DELETE' then return OLD; else return NEW; end if; exception when OTHERS then NULL; if TG_OP = 'DELETE' then return OLD; else return NEW; end if; end; $$ language plpgsql; select * from plpgsql_check_function('ml_trg()', 'ml', performance_warnings := true); plpgsql_check_function -------------------------------------------------------------------------- error:42703:13:SQL statement:record "new" has no field "status_from_xxx" (1 row) create or replace function fx2() returns void as $$ declare _pa pa; begin select pa.id into _pa.id from pa limit 1; select pa.pa_id into _pa.pa_id from pa limit 1; end; $$ language plpgsql; select * from plpgsql_check_function('fx2()', performance_warnings := true); plpgsql_check_function --------------------------------------------------------- warning extra:00000:2:DECLARE:never read variable "_pa" (1 row) drop function fx2(); create or replace function fx2() returns void as $$ declare _pa pa; begin _pa.id := (select pa.id from pa limit 1); _pa.pa_id := (select pa.pa_id from pa limit 1); end; $$ language plpgsql; select * from plpgsql_check_function('fx2()', performance_warnings := true); plpgsql_check_function --------------------------------------------------------- warning extra:00000:2:DECLARE:never read variable "_pa" (1 row) drop function fx2(); create type _exception_type as ( state text, message text, detail text); create or replace function f1() returns void as $$ declare _exception record; begin _exception := NULL::_exception_type; exception when others then get stacked diagnostics _exception.state = RETURNED_SQLSTATE, _exception.message = MESSAGE_TEXT, _exception.detail = PG_EXCEPTION_DETAIL, _exception.hint = PG_EXCEPTION_HINT; end; $$ language plpgsql; select f1(); f1 ---- (1 row) select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-------------------------+----------+-----------------------------------------+--------+------+-------+----------+-------+--------- f1 | 7 | GET STACKED DIAGNOSTICS | 42703 | record "_exception" has no field "hint" | | | error | | | (1 row) create or replace function f1() returns void as $$ declare _exception _exception_type; begin _exception := NULL::_exception_type; exception when others then get stacked diagnostics _exception.state = RETURNED_SQLSTATE, _exception.message = MESSAGE_TEXT, _exception.detail = PG_EXCEPTION_DETAIL; end; $$ language plpgsql; select f1(); f1 ---- (1 row) select * from plpgsql_check_function_tb('f1()'); functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------+--------+-----------+----------+----------------------------------+--------+------+---------------+----------+-------+--------- f1 | 3 | DECLARE | 00000 | never read variable "_exception" | | | warning extra | | | (1 row) drop function f1(); drop type _exception_type; create type _exception_type as ( state text, message text, detail text); create or replace function f1() returns void as $$ declare _exception record; begin _exception := NULL::_exception_type; exception when others then get stacked diagnostics _exception.state = RETURNED_SQLSTATE, _exception.message = MESSAGE_TEXT, _exception.detail = PG_EXCEPTION_DETAIL, _exception.hint = PG_EXCEPTION_HINT; end; $$ language plpgsql; select f1(); f1 ---- (1 row) select * from plpgsql_check_function('f1()'); plpgsql_check_function ------------------------------------------------------------------------------- error:42703:7:GET STACKED DIAGNOSTICS:record "_exception" has no field "hint" (1 row) drop function f1(); drop type _exception_type; create table footab(a int, b int, c int); create or replace function footab_trig_func() returns trigger as $$ declare x int; begin if false then -- should be ok; select count(*) from newtab into x; -- should fail; select count(*) from newtab where d = 10 into x; end if; return null; end; $$ language plpgsql; select * from plpgsql_check_function('footab_trig_func','footab', newtable := 'newtab'); plpgsql_check_function ------------------------------------------------------- error:42703:9:SQL statement:column "d" does not exist Query: select count(*) from newtab where d = 10 -- ^ (3 rows) drop table footab; drop function footab_trig_func(); create or replace function df1(anyelement) returns anyelement as $$ begin return $1; end; $$ language plpgsql; create or replace function df2(anyelement, jsonb) returns anyelement as $$ begin return $1; end; $$ language plpgsql; create or replace function t1() returns void as $$ declare r record; begin r := df1(r); end; $$ language plpgsql; select * from plpgsql_check_function('t1()'); plpgsql_check_function ------------------------ (0 rows) create or replace function t1() returns void as $$ declare r record; begin r := df2(r, '{}'); end; $$ language plpgsql; select * from plpgsql_check_function('t1()'); plpgsql_check_function ------------------------ (0 rows) create or replace function t1() returns void as $$ declare r1 record; r2 record; begin select 10 as a, 20 as b into r1; r2 := df1(r1); raise notice '%', r2.a; end; $$ language plpgsql; select * from plpgsql_check_function('t1()'); plpgsql_check_function ------------------------ (0 rows) create or replace function t1() returns void as $$ declare r1 record; r2 record; begin select 10 as a, 20 as b into r1; r2 := df2(r1, '{}'); raise notice '%', r2.a; end; $$ language plpgsql; select * from plpgsql_check_function('t1()'); plpgsql_check_function ------------------------ (0 rows) create or replace function df1(anyelement) returns anyelement as $$ select $1 $$ language sql; create or replace function df22(jsonb, anyelement) returns anyelement as $$ select $2; $$ language sql; create or replace function t1() returns void as $$ declare r1 record; r2 record; begin select 10 as a, 20 as b into r1; r2 := df1(r1); raise notice '%', r2.a; end; $$ language plpgsql; select * from plpgsql_check_function('t1()'); plpgsql_check_function ------------------------ (0 rows) create or replace function t1() returns void as $$ declare r1 record; r2 record; begin select 10 as a, 20 as b into r1; r2 := df22('{}', r1); raise notice '%', r2.a; end; $$ language plpgsql; select * from plpgsql_check_function('t1()'); plpgsql_check_function ------------------------ (0 rows) drop function df1(anyelement); drop function df2(anyelement, jsonb); drop function df22(jsonb, anyelement); drop function t1(); create or replace function dyntest() returns void as $$ begin execute 'drop table if exists xxx; create table xxx(a int)'; end; $$ language plpgsql; -- should be ok select * from plpgsql_check_function('dyntest'); plpgsql_check_function ------------------------ (0 rows) create or replace function dyntest() returns void as $$ declare x int; begin execute 'drop table if exists xxx; create table xxx(a int)' into x; end; $$ language plpgsql; -- should to report error select * from plpgsql_check_function('dyntest'); plpgsql_check_function ------------------------------------------------------- error:XX000:4:EXECUTE:expression does not return data (1 row) drop function dyntest(); -- should to report error create type typ2 as (a int, b int); create or replace function broken_into() returns void as $$ declare v typ2; begin -- should to fail select (10,20)::typ2 into v; -- should be ok select ((10,20)::typ2).* into v; -- should to fail execute 'select (10,20)::typ2' into v; -- should be ok execute 'select ((10,20)::typ2).*' into v; end; $$ language plpgsql; select * from plpgsql_check_function('broken_into', fatal_errors => false); plpgsql_check_function ------------------------------------------------------------------------------------------------------------ error:42804:5:SQL statement:cannot cast composite value of "typ2" type to a scalar value of "integer" type warning:00000:5:SQL statement:too few attributes for composite variable error:42804:9:EXECUTE:cannot cast composite value of "typ2" type to a scalar value of "integer" type warning:00000:9:EXECUTE:too few attributes for composite variable warning extra:00000:2:DECLARE:never read variable "v" (5 rows) drop function broken_into(); drop type typ2; -- check output in xml or json formats CREATE OR REPLACE FUNCTION test_function() RETURNS void LANGUAGE plpgsql AS $function$ begin insert into non_existing_table values (1); end $function$; select * from plpgsql_check_function('test_function', format => 'xml'); plpgsql_check_function ---------------------------------------------------------------------------- + + error + 42P01 + relation "non_existing_table" does not exist + SQL statement + insert into non_existing_table values (1)+ + (1 row) select * from plpgsql_check_function('test_function', format => 'json'); plpgsql_check_function ----------------------------------------------------------------- { "issues":[ + { + "level":"error", + "message":"relation \"non_existing_table\" does not exist",+ "statement":{ + "lineNumber":"3", + "text":"SQL statement" + }, + "query":{ + "position":"13", + "text":"insert into non_existing_table values (1)" + }, + "sqlState":"42P01" + } + + ] + } (1 row) drop function test_function(); -- test settype pragma create or replace function test_function() returns void as $$ declare r record; begin raise notice '%', r.a; end; $$ language plpgsql; -- should to detect error select * from plpgsql_check_function('test_function'); plpgsql_check_function ---------------------------------------------------------------------------- error:55000:4:RAISE:record "r" is not assigned yet Detail: The tuple structure of a not-yet-assigned record is indeterminate. Context: SQL expression "r.a" (3 rows) create type ctype as (a int, b int); create or replace function test_function() returns void as $$ declare r record; begin perform plpgsql_check_pragma('type: r ctype'); raise notice '%', r.a; end; $$ language plpgsql; -- should to be ok select * from plpgsql_check_function('test_function'); plpgsql_check_function ------------------------ (0 rows) create or replace function test_function() returns void as $$ <>declare r record; begin perform plpgsql_check_pragma('type: x.r public."ctype"'); raise notice '%', r.a; end; $$ language plpgsql; -- should to be ok select * from plpgsql_check_function('test_function'); plpgsql_check_function ------------------------ (0 rows) create or replace function test_function() returns void as $$ <>declare r record; begin perform plpgsql_check_pragma('type: "x".r (a int, b int)'); raise notice '%', r.a; end; $$ language plpgsql; -- should to be ok select * from plpgsql_check_function('test_function'); plpgsql_check_function ------------------------ (0 rows) create or replace function test_function() returns void as $$ <>declare r record; begin perform plpgsql_check_pragma('type: "x".r (a int, b int'); raise notice '%', r.a; end; $$ language plpgsql; -- should to be ok select * from plpgsql_check_function('test_function'); WARNING: Pragma "type" on line 4 is not processed. plpgsql_check_function ---------------------------------------------------------------------------- error:55000:5:RAISE:record "r" is not assigned yet Detail: The tuple structure of a not-yet-assigned record is indeterminate. Context: SQL expression "r.a" (3 rows) create or replace function test_function() returns void as $$ <>declare r record; begin perform plpgsql_check_pragma('type: "x".r (a int, b int)x'); raise notice '%', r.a; end; $$ language plpgsql; -- should to be ok select * from plpgsql_check_function('test_function'); WARNING: Pragma "type" on line 4 is not processed. plpgsql_check_function ---------------------------------------------------------------------------- error:55000:5:RAISE:record "r" is not assigned yet Detail: The tuple structure of a not-yet-assigned record is indeterminate. Context: SQL expression "r.a" (3 rows) drop function test_function(); drop type ctype; create or replace function test_function() returns void as $$ declare r pg_class; begin create temp table foo(like pg_class); select * from foo into r; end; $$ language plpgsql; -- should to raise an error select * from plpgsql_check_function('test_function'); plpgsql_check_function ----------------------------------------------------------- error:42P01:5:SQL statement:relation "foo" does not exist Query: select * from foo -- ^ (3 rows) create or replace function test_function() returns void as $$ declare r record; begin create temp table foo(like pg_class); perform plpgsql_check_pragma('table: foo(like pg_class)'); select * from foo into r; raise notice '%', r.relname; end; $$ language plpgsql; -- should be ok select * from plpgsql_check_function('test_function'); plpgsql_check_function ------------------------ (0 rows) drop function test_function(); -- now plpgsql_check can do some other checks when statement EXECUTE -- contains only format function with constant fmt. create or replace function test_function() returns void as $$ begin execute format('create table zzz %I(a int, b int)', 'zzz'); end; $$ language plpgsql; -- should to detect bad expression select * from plpgsql_check_function('test_function'); plpgsql_check_function ----------------------------------------------------- error:42601:3:EXECUTE:syntax error at or near "zzz" (1 row) -- should to correctly detect type create or replace function test_function() returns void as $$ declare r record; begin execute format('select %L::date + 1 as x', current_date) into r; raise notice '%', extract(dow from r.x); end; $$ language plpgsql; -- should be ok select * from plpgsql_check_function('test_function'); plpgsql_check_function ------------------------ (0 rows) -- should not to crash create or replace function test_function() returns void as $$ declare r record; begin r := null; end; $$ language plpgsql; select * from plpgsql_check_function('test_function'); plpgsql_check_function ------------------------------------------------------- warning extra:00000:2:DECLARE:never read variable "r" (1 row) drop function test_function(); -- aborted function has profile too create or replace function test_function(a int) returns int as $$ begin if (a > 5) then a := a + 10; return a; else raise exception 'a < 5'; end if; end; $$ language plpgsql; set plpgsql_check.profiler to on; select test_function(1); ERROR: a < 5 select test_function(10); test_function --------------- 20 (1 row) select lineno, exec_stmts, exec_stmts_err, source from plpgsql_profiler_function_tb('test_function'); lineno | exec_stmts | exec_stmts_err | source --------+------------+----------------+------------------------------ 1 | | | 2 | 2 | 1 | begin 3 | 2 | 1 | if (a > 5) then 4 | 1 | 0 | a := a + 10; 5 | 1 | 0 | return a; 6 | | | else 7 | 1 | 1 | raise exception 'a < 5'; 8 | | | end if; 9 | | | end; (9 rows) create or replace function test_function1(a int) returns int as $$ begin if (a > 5) then a := a + 10; return a; else raise exception 'a < 5'; end if; exeception when others then raise notice 'do warning'; return -1; end; $$ language plpgsql; select test_function1(1); ERROR: a < 5 select test_function1(10); test_function1 ---------------- 20 (1 row) select lineno, exec_stmts, exec_stmts_err, source from plpgsql_profiler_function_tb('test_function1'); lineno | exec_stmts | exec_stmts_err | source --------+------------+----------------+-------------------------------- 1 | | | 2 | 2 | 1 | begin 3 | 2 | 1 | if (a > 5) then 4 | 1 | 0 | a := a + 10; 5 | 1 | 0 | return a; 6 | | | else 7 | 1 | 1 | raise exception 'a < 5'; 8 | | | end if; 9 | 0 | 0 | exeception when others then 10 | | | raise notice 'do warning'; 11 | 0 | 0 | return -1; 12 | | | end; (12 rows) drop function test_function(int); drop function test_function1(int); set plpgsql_check.profiler to off; -- ignores syntax errors when literals placehodlers are used create function test_function() returns void as $$ begin execute format('do %L', 'begin end'); end $$ language plpgsql; select * from plpgsql_check_function('test_function'); plpgsql_check_function ------------------------ (0 rows) drop function test_function(); load 'plpgsql_check'; drop type testtype cascade; create type testtype as (a int, b int); create function test_function() returns record as $$ declare r record; begin r := (10,20); if false then return r; end if; return null; end; $$ language plpgsql; create function test_function33() returns record as $$ declare r testtype; begin r := (10,20); if false then return r; end if; return null; end; $$ language plpgsql; -- should not to raise false alarm due check against fake result type select plpgsql_check_function('test_function'); plpgsql_check_function ------------------------ (0 rows) select plpgsql_check_function('test_function33'); plpgsql_check_function ------------------------ (0 rows) -- try to check in passive mode set plpgsql_check.mode = 'every_start'; select test_function(); test_function --------------- (1 row) select test_function33(); test_function33 ----------------- (1 row) select * from test_function() as (a int, b int); a | b ---+--- | (1 row) select * from test_function33() as (a int, b int); a | b ---+--- | (1 row) -- should to identify error select * from test_function() as (a int, b int, c int); ERROR: returned record type does not match expected record type select * from test_function33() as (a int, b int, c int); ERROR: returned record type does not match expected record type drop function test_function(); drop function test_function33(); drop type testtype; set plpgsql_check.mode to default; -- should not to raise false alarm create type c1 as ( a text ); create table t1 ( a c1, b c1 ); insert into t1 (values ('(abc)', '(def)')); alter table t1 drop column a; create or replace function test_function() returns t1 as $$ declare myrow t1%rowtype; begin select * into myrow from t1 limit 1; return myrow; end; $$ language plpgsql; select * from test_function(); b ------- (def) (1 row) select * from plpgsql_check_function('public.test_function()'); plpgsql_check_function ------------------------ (0 rows) drop function test_function(); drop table t1; drop type c1; -- compatibility warnings create or replace function foo01() returns refcursor as $$ declare c cursor for select 1; r refcursor; begin open c; r := c; return r; end; $$ language plpgsql; -- no warnings select * from plpgsql_check_function('foo01', compatibility_warnings => true); plpgsql_check_function ------------------------ (0 rows) create or replace function foo01() returns refcursor as $$ declare c cursor for select 1; r refcursor; begin open c; r := 'c'; return r; end; $$ language plpgsql; -- warning select * from plpgsql_check_function('foo01', extra_warnings => false, compatibility_warnings => true); plpgsql_check_function ----------------------------------------------------------------------------------- compatibility:00000:7:assignment:obsolete setting of refcursor or cursor variable Detail: Internal name of cursor should not be specified by users. Context: at assignment to variable "r" declared on line 4 (3 rows) create or replace function foo01() returns refcursor as $$ declare c cursor for select 1; r refcursor; begin open c; r := c; return 'c'; end; $$ language plpgsql; -- warning select * from plpgsql_check_function('foo01', extra_warnings => false, compatibility_warnings => true); plpgsql_check_function -------------------------------------------------------------------------------------- compatibility:00000:8:RETURN:obsolete setting of refcursor or cursor variable Detail: Internal name of cursor should not be specified by users. warning:42804:8:RETURN:target type is different type than source type Detail: cast "text" value to "refcursor" type Hint: The input expression type does not have an assignment cast to the target type. (5 rows) -- pragma sequence test create or replace function test_function() returns void as $$ begin perform plpgsql_check_pragma('sequence: xx'); perform nextval('pg_temp.xy'); perform nextval('pg_temp.xx'); end $$ language plpgsql; select * from plpgsql_check_function('test_function'); plpgsql_check_function ------------------------------------------------------------ error:42P01:4:PERFORM:relation "pg_temp.xy" does not exist Query: SELECT nextval('pg_temp.xy') -- ^ (3 rows) drop function test_function(); create table t1(x int); create or replace function f1_trg() returns trigger as $$ declare x int; begin return x; end; $$ language plpgsql; create trigger t1_f1 before insert on t1 for each row execute procedure f1_trg(); -- raise error select * from plpgsql_check_function('f1_trg', 't1'); plpgsql_check_function ----------------------------------------------------------------------------------------------- error:42804:4:RETURN:cannot return non-composite value from function returning composite type (1 row) drop trigger t1_f1 on t1; drop table t1; drop function f1_trg; create function test_function() returns void as $$ declare a int; b int; c int; d char; begin c := a + d; end; $$ language plpgsql; -- only b should be marked as unused variable select * from plpgsql_check_function('test_function', fatal_errors := false); plpgsql_check_function --------------------------------------------------------------------------------------------------------- error:42883:6:assignment:operator does not exist: integer + character Query: c := a + d -- ^ Hint: No operator matches the given name and argument types. You might need to add explicit type casts. Context: at assignment to variable "c" declared on line 4 warning:00000:3:DECLARE:unused variable "b" warning extra:00000:4:DECLARE:never read variable "c" (7 rows) drop function test_function(); -- from plpgsql_check_active-12 create or replace procedure proc(a int) as $$ begin end; $$ language plpgsql; call proc(10); select * from plpgsql_check_function('proc(int)'); plpgsql_check_function ------------------------------------------ warning extra:00000:unused parameter "a" (1 row) create or replace procedure testproc() as $$ begin call proc(10); end; $$ language plpgsql; call testproc(); select * from plpgsql_check_function('testproc()'); plpgsql_check_function ------------------------ (0 rows) -- should to fail create or replace procedure testproc() as $$ begin call proc((select count(*) from pg_class)); end; $$ language plpgsql; call testproc(); ERROR: cannot use subquery in CALL argument at character 11 select * from plpgsql_check_function('testproc()'); plpgsql_check_function --------------------------------------------------------- error:0A000:3:CALL:cannot use subquery in CALL argument Query: call proc((select count(*) from pg_class)) -- ^ (3 rows) drop procedure proc(int); create procedure proc(in a int, inout b int, in c int) as $$ begin end; $$ language plpgsql; select * from plpgsql_check_function('proc(int,int, int)'); plpgsql_check_function ------------------------------------------------- warning extra:00000:unused parameter "a" warning extra:00000:unused parameter "b" warning extra:00000:unused parameter "c" warning extra:00000:unmodified OUT variable "b" (4 rows) create or replace procedure proc(in a int, inout b int, in c int) as $$ begin b := a + c; end; $$ language plpgsql; select * from plpgsql_check_function('proc(int,int, int)'); plpgsql_check_function ------------------------ (0 rows) create or replace procedure testproc() as $$ declare r int; begin call proc(10, r, 20); end; $$ language plpgsql; call testproc(); select * from plpgsql_check_function('testproc()'); plpgsql_check_function ------------------------ (0 rows) -- should to fail create or replace procedure testproc() as $$ declare r int; begin call proc(10, r + 10, 20); end; $$ language plpgsql; call testproc(); ERROR: procedure parameter "b" is an output parameter but corresponding argument is not writable select * from plpgsql_check_function('testproc()'); plpgsql_check_function -------------------------------------------------------------------------------------------------------------- error:42601:4:CALL:procedure parameter "b" is an output parameter but corresponding argument is not writable (1 row) create or replace procedure testproc(inout r int) as $$ begin call proc(10, r, 20); end; $$ language plpgsql; call testproc(10); r ---- 30 (1 row) select * from plpgsql_check_function('testproc(int)'); plpgsql_check_function ------------------------ (0 rows) drop procedure testproc(int); -- should to raise warnings create or replace procedure testproc2(in p1 int, inout p2 int, in p3 int, inout p4 int) as $$ begin raise notice '% %', p1, p3; end; $$ language plpgsql; select * from plpgsql_check_function('testproc2'); plpgsql_check_function -------------------------------------------------- warning extra:00000:unused parameter "p2" warning extra:00000:unused parameter "p4" warning extra:00000:unmodified OUT variable "p2" warning extra:00000:unmodified OUT variable "p4" (4 rows) drop procedure testproc2; -- should be ok create or replace procedure testproc3(in p1 int, inout p2 int, in p3 int, inout p4 int) as $$ begin p2 := p1; p4 := p3; end; $$ language plpgsql; select * from plpgsql_check_function('testproc3'); plpgsql_check_function ------------------------ (0 rows) drop procedure testproc3; /* * Test pragma */ create or replace function test_pragma() returns void language plpgsql as $$ declare r record; begin perform plpgsql_check_pragma('disable:check'); raise notice '%', r.y; perform plpgsql_check_pragma('enable:check'); select 10 as a, 20 as b into r; raise notice '%', r.a; raise notice '%', r.x; end; $$; select * from plpgsql_check_function('test_pragma'); plpgsql_check_function ------------------------------------------------- error:42703:9:RAISE:record "r" has no field "x" Context: SQL expression "r.x" (2 rows) create or replace function test_pragma() returns void language plpgsql as $$ declare r record; begin if false then -- check is disabled just for if body perform plpgsql_check_pragma('disable:check'); raise notice '%', r.y; end if; select 10 as a, 20 as b into r; raise notice '%', r.a; raise notice '%', r.x; end; $$; select * from plpgsql_check_function('test_pragma'); plpgsql_check_function -------------------------------------------------- error:42703:11:RAISE:record "r" has no field "x" Context: SQL expression "r.x" (2 rows) drop function test_pragma(); create or replace function nested_trace_test(a int) returns int as $$ begin return a + 1; end; $$ language plpgsql; create or replace function trace_test(b int) returns int as $$ declare r int default 0; begin for i in 1..b loop r := nested_trace_test(r); end loop; return r; end; $$ language plpgsql; select trace_test(3); trace_test ------------ 3 (1 row) set plpgsql_check.enable_tracer to on; set plpgsql_check.tracer to on; set plpgsql_check.tracer_test_mode = true; select trace_test(3); NOTICE: #0 ->> start of function trace_test(integer) (oid=0, tnl=1) NOTICE: #0 "b" => '3' NOTICE: #1 ->> start of function nested_trace_test(integer) (oid=0, tnl=1) NOTICE: #1 context: PL/pgSQL function trace_test(integer) line 6 at assignment NOTICE: #1 "a" => '0' NOTICE: #1 <<- end of function nested_trace_test (elapsed time=0.010 ms) NOTICE: #1 ->> start of function nested_trace_test(integer) (oid=0, tnl=1) NOTICE: #1 context: PL/pgSQL function trace_test(integer) line 6 at assignment NOTICE: #1 "a" => '1' NOTICE: #1 <<- end of function nested_trace_test (elapsed time=0.010 ms) NOTICE: #1 ->> start of function nested_trace_test(integer) (oid=0, tnl=1) NOTICE: #1 context: PL/pgSQL function trace_test(integer) line 6 at assignment NOTICE: #1 "a" => '2' NOTICE: #1 <<- end of function nested_trace_test (elapsed time=0.010 ms) NOTICE: #0 <<- end of function trace_test (elapsed time=0.010 ms) trace_test ------------ 3 (1 row) set plpgsql_check.tracer_verbosity TO verbose; select trace_test(3); NOTICE: #0 ->> start of function trace_test(integer) (oid=0, tnl=1) NOTICE: #0 "b" => '3' NOTICE: #0.1 3 --> start of statement block (tnl=1) NOTICE: #0.2 4 --> start of FOR with integer loop variable (tnl=1) NOTICE: #0.3 6 --> start of assignment r := nested_trace_test(r) (tnl=1) NOTICE: #0.3 "r" => '0' NOTICE: #1 ->> start of function nested_trace_test(integer) (oid=0, tnl=1) NOTICE: #1 context: PL/pgSQL function trace_test(integer) line 6 at assignment NOTICE: #1 "a" => '0' NOTICE: #1.1 2 --> start of statement block (tnl=1) NOTICE: #1.2 3 --> start of RETURN (expr='a + 1') (tnl=1) NOTICE: #1.2 "a" => '0' NOTICE: #1.1 <-- end of RETURN (elapsed time=0.010 ms) NOTICE: #1.2 <-- end of statement block (elapsed time=0.010 ms) NOTICE: #1 <<- end of function nested_trace_test (elapsed time=0.010 ms) NOTICE: #0.2 <-- end of assignment (elapsed time=0.010 ms) NOTICE: #0.3 "r" => '1' NOTICE: #0.3 6 --> start of assignment r := nested_trace_test(r) (tnl=1) NOTICE: #0.3 "r" => '1' NOTICE: #1 ->> start of function nested_trace_test(integer) (oid=0, tnl=1) NOTICE: #1 context: PL/pgSQL function trace_test(integer) line 6 at assignment NOTICE: #1 "a" => '1' NOTICE: #1.1 2 --> start of statement block (tnl=1) NOTICE: #1.2 3 --> start of RETURN (expr='a + 1') (tnl=1) NOTICE: #1.2 "a" => '1' NOTICE: #1.1 <-- end of RETURN (elapsed time=0.010 ms) NOTICE: #1.2 <-- end of statement block (elapsed time=0.010 ms) NOTICE: #1 <<- end of function nested_trace_test (elapsed time=0.010 ms) NOTICE: #0.2 <-- end of assignment (elapsed time=0.010 ms) NOTICE: #0.3 "r" => '2' NOTICE: #0.3 6 --> start of assignment r := nested_trace_test(r) (tnl=1) NOTICE: #0.3 "r" => '2' NOTICE: #1 ->> start of function nested_trace_test(integer) (oid=0, tnl=1) NOTICE: #1 context: PL/pgSQL function trace_test(integer) line 6 at assignment NOTICE: #1 "a" => '2' NOTICE: #1.1 2 --> start of statement block (tnl=1) NOTICE: #1.2 3 --> start of RETURN (expr='a + 1') (tnl=1) NOTICE: #1.2 "a" => '2' NOTICE: #1.1 <-- end of RETURN (elapsed time=0.010 ms) NOTICE: #1.2 <-- end of statement block (elapsed time=0.010 ms) NOTICE: #1 <<- end of function nested_trace_test (elapsed time=0.010 ms) NOTICE: #0.2 <-- end of assignment (elapsed time=0.010 ms) NOTICE: #0.3 "r" => '3' NOTICE: #0.1 <-- end of FOR with integer loop variable (elapsed time=0.010 ms) NOTICE: #0.4 8 --> start of RETURN (tnl=1) NOTICE: #0.4 "r" => '3' NOTICE: #0.3 <-- end of RETURN (elapsed time=0.010 ms) NOTICE: #0.4 <-- end of statement block (elapsed time=0.010 ms) NOTICE: #0 <<- end of function trace_test (elapsed time=0.010 ms) trace_test ------------ 3 (1 row) create or replace function trace_test(b int) returns int as $$ declare r int default 0; begin for i in 1..b loop perform plpgsql_check_pragma('disable:tracer'); r := nested_trace_test(r); end loop; return r; end; $$ language plpgsql; select trace_test(3); NOTICE: #0 ->> start of function trace_test(integer) (oid=0, tnl=1) NOTICE: #0 "b" => '3' NOTICE: #0.1 3 --> start of statement block (tnl=1) NOTICE: #0.2 4 --> start of FOR with integer loop variable (tnl=1) NOTICE: #0.3 6 --> start of perform plpgsql_check_pragma('disable: .. (tnl=1) NOTICE: #0.2 <-- end of PERFORM (elapsed time=0.010 ms) NOTICE: #0.1 <-- end of FOR with integer loop variable (elapsed time=0.010 ms) NOTICE: #0.5 9 --> start of RETURN (tnl=1) NOTICE: #0.5 "r" => '3' NOTICE: #0.4 <-- end of RETURN (elapsed time=0.010 ms) NOTICE: #0.5 <-- end of statement block (elapsed time=0.010 ms) NOTICE: #0 <<- end of function trace_test (elapsed time=0.010 ms) trace_test ------------ 3 (1 row) create or replace function nested_trace_test(a int) returns int as $$ begin perform plpgsql_check_pragma('enable:tracer'); return a + 1; end; $$ language plpgsql; select trace_test(3); NOTICE: #0 ->> start of function trace_test(integer) (oid=0, tnl=1) NOTICE: #0 "b" => '3' NOTICE: #0.1 3 --> start of statement block (tnl=1) NOTICE: #0.2 4 --> start of FOR with integer loop variable (tnl=1) NOTICE: #0.3 6 --> start of perform plpgsql_check_pragma('disable: .. (tnl=1) NOTICE: #0.2 <-- end of PERFORM (elapsed time=0.010 ms) NOTICE: #1.3 4 --> start of RETURN (expr='a + 1') (tnl=1) NOTICE: #1.3 "a" => '0' NOTICE: #1.2 <-- end of RETURN (elapsed time=0.010 ms) NOTICE: #1.3 4 --> start of RETURN (expr='a + 1') (tnl=1) NOTICE: #1.3 "a" => '1' NOTICE: #1.2 <-- end of RETURN (elapsed time=0.010 ms) NOTICE: #1.3 4 --> start of RETURN (expr='a + 1') (tnl=1) NOTICE: #1.3 "a" => '2' NOTICE: #1.2 <-- end of RETURN (elapsed time=0.010 ms) NOTICE: #0.1 <-- end of FOR with integer loop variable (elapsed time=0.010 ms) NOTICE: #0.5 9 --> start of RETURN (tnl=1) NOTICE: #0.5 "r" => '3' NOTICE: #0.4 <-- end of RETURN (elapsed time=0.010 ms) NOTICE: #0.5 <-- end of statement block (elapsed time=0.010 ms) NOTICE: #0 <<- end of function trace_test (elapsed time=0.010 ms) trace_test ------------ 3 (1 row) drop function trace_test(int); drop function nested_trace_test(int); create or replace function trace_test(int) returns int as $$ declare r int default 0; begin for i in 1..$1 loop r := r + 1; end loop; r := r + 10; return r; end; $$ language plpgsql; select trace_test(4); NOTICE: #0 ->> start of function trace_test(integer) (oid=0, tnl=1) NOTICE: #0 "$1" => '4' NOTICE: #0.1 3 --> start of statement block (tnl=1) NOTICE: #0.2 4 --> start of FOR with integer loop variable (tnl=1) NOTICE: #0.3 5 --> start of assignment r := r + 1 (tnl=1) NOTICE: #0.3 "r" => '0' NOTICE: #0.2 <-- end of assignment (elapsed time=0.010 ms) NOTICE: #0.3 "r" => '1' NOTICE: #0.3 5 --> start of assignment r := r + 1 (tnl=1) NOTICE: #0.3 "r" => '1' NOTICE: #0.2 <-- end of assignment (elapsed time=0.010 ms) NOTICE: #0.3 "r" => '2' NOTICE: #0.3 5 --> start of assignment r := r + 1 (tnl=1) NOTICE: #0.3 "r" => '2' NOTICE: #0.2 <-- end of assignment (elapsed time=0.010 ms) NOTICE: #0.3 "r" => '3' NOTICE: #0.3 5 --> start of assignment r := r + 1 (tnl=1) NOTICE: #0.3 "r" => '3' NOTICE: #0.2 <-- end of assignment (elapsed time=0.010 ms) NOTICE: #0.3 "r" => '4' NOTICE: #0.1 <-- end of FOR with integer loop variable (elapsed time=0.010 ms) NOTICE: #0.4 7 --> start of assignment r := r + 10 (tnl=1) NOTICE: #0.4 "r" => '4' NOTICE: #0.3 <-- end of assignment (elapsed time=0.010 ms) NOTICE: #0.4 "r" => '14' NOTICE: #0.5 8 --> start of RETURN (tnl=1) NOTICE: #0.5 "r" => '14' NOTICE: #0.4 <-- end of RETURN (elapsed time=0.010 ms) NOTICE: #0.5 <-- end of statement block (elapsed time=0.010 ms) NOTICE: #0 <<- end of function trace_test (elapsed time=0.010 ms) trace_test ------------ 14 (1 row) create or replace function trace_test(int) returns int as $$ declare r int default 0; begin for i in 1..$1 loop perform plpgsql_check_pragma('disable:tracer'); r := r + 1; end loop; r := r + 10; return r; end; $$ language plpgsql; select trace_test(4); NOTICE: #0 ->> start of function trace_test(integer) (oid=0, tnl=1) NOTICE: #0 "$1" => '4' NOTICE: #0.1 3 --> start of statement block (tnl=1) NOTICE: #0.2 4 --> start of FOR with integer loop variable (tnl=1) NOTICE: #0.3 5 --> start of perform plpgsql_check_pragma('disable: .. (tnl=1) NOTICE: #0.2 <-- end of PERFORM (elapsed time=0.010 ms) NOTICE: #0.1 <-- end of FOR with integer loop variable (elapsed time=0.010 ms) NOTICE: #0.5 8 --> start of assignment r := r + 10 (tnl=1) NOTICE: #0.5 "r" => '4' NOTICE: #0.4 <-- end of assignment (elapsed time=0.010 ms) NOTICE: #0.5 "r" => '14' NOTICE: #0.6 9 --> start of RETURN (tnl=1) NOTICE: #0.6 "r" => '14' NOTICE: #0.5 <-- end of RETURN (elapsed time=0.010 ms) NOTICE: #0.6 <-- end of statement block (elapsed time=0.010 ms) NOTICE: #0 <<- end of function trace_test (elapsed time=0.010 ms) trace_test ------------ 14 (1 row) create or replace function trace_test(int) returns int as $$ declare r int default 0; begin perform plpgsql_check_pragma('disable:tracer'); for i in 1..$1 loop r := r + 1; end loop; perform plpgsql_check_pragma('enable:tracer'); r := r + 10; return r; end; $$ language plpgsql; select trace_test(4); NOTICE: #0 ->> start of function trace_test(integer) (oid=0, tnl=1) NOTICE: #0 "$1" => '4' NOTICE: #0.1 3 --> start of statement block (tnl=1) NOTICE: #0.2 4 --> start of perform plpgsql_check_pragma('disable: .. (tnl=1) NOTICE: #0.1 <-- end of PERFORM (elapsed time=0.010 ms) NOTICE: #0.6 12 --> start of assignment r := r + 10 (tnl=1) NOTICE: #0.6 "r" => '4' NOTICE: #0.5 <-- end of assignment (elapsed time=0.010 ms) NOTICE: #0.6 "r" => '14' NOTICE: #0.7 13 --> start of RETURN (tnl=1) NOTICE: #0.7 "r" => '14' NOTICE: #0.6 <-- end of RETURN (elapsed time=0.010 ms) NOTICE: #0.7 <-- end of statement block (elapsed time=0.010 ms) NOTICE: #0 <<- end of function trace_test (elapsed time=0.010 ms) trace_test ------------ 14 (1 row) drop function trace_test(int); create or replace function public.fx1() returns table(i integer, j integer, found boolean) as $$ begin for i in 1..10 loop for j in 1..10 loop return next; end loop; end loop; end; $$ language plpgsql immutable; select * from plpgsql_check_function('fx1'); plpgsql_check_function -------------------------------------------------------------------------- warning:00000:2:statement block:parameter "found" is shadowed Detail: Local auto variable shadows function parameter. warning:00000:3:FOR with integer loop variable:parameter "i" is shadowed Detail: Local auto variable shadows function parameter. warning:00000:5:FOR with integer loop variable:parameter "j" is shadowed Detail: Local auto variable shadows function parameter. warning extra:00000:unmodified OUT variable "i" warning extra:00000:unmodified OUT variable "j" warning extra:00000:unmodified OUT variable "found" (9 rows) drop function fx1; -- tracer test set plpgsql_check.enable_tracer to on; select plpgsql_check_tracer(true); NOTICE: tracer is active NOTICE: tracer verbosity is verbose plpgsql_check_tracer ---------------------- t (1 row) create role plpgsql_check_test_role; DO $$ begin begin -- should to fail create role plpgsql_check_test_role; exception when duplicate_object then -- Role already exists -- the exception handler is empty (#156) end; end; $$; NOTICE: #0 ->> start of block inline_code_block (oid=0, tnl=1) NOTICE: #0.1 2 --> start of statement block (tnl=1) NOTICE: #0.2 3 --> start of statement block (tnl=1) NOTICE: #0.3 5 --> start of SQL statement (query='create role plpgsql_check_test ..') (tnl=2) NOTICE: #0.1 <-- end of SQL statement (elapsed time=0.010 ms) aborted NOTICE: #0.2 <-- end of statement block (elapsed time=0.010 ms) NOTICE: #0.3 <-- end of statement block (elapsed time=0.010 ms) NOTICE: #0 <<- end of block (elapsed time=0.010 ms) drop role plpgsql_check_test_role; set plpgsql_check.enable_tracer to off; select plpgsql_check_tracer(false); NOTICE: tracer is not active NOTICE: tracer verbosity is verbose plpgsql_check_tracer ---------------------- f (1 row) -- tracing constants -- issue #159 create or replace function tabret_dynamic() returns table (id integer, val text) as $$ begin return query execute 'select id, val from (values (1, ''a''), (2, ''b'')) as v(id, val)'; end; $$ language plpgsql immutable; -- should be ok select * from plpgsql_check_function('tabret_dynamic()'); plpgsql_check_function ------------------------ (0 rows) create or replace function tabret_dynamic() returns table (id integer, val text) as $$ declare z_query text; begin z_query := 'select id, val from (values (1, ''a''), (2, ''b'')) as v(id, val)'; execute z_query into id, val; return next; end; $$ language plpgsql immutable; -- should be ok select * from plpgsql_check_function('tabret_dynamic()'); plpgsql_check_function ------------------------ (0 rows) create or replace function tabret_dynamic() returns table (id integer, val text) as $$ declare z_query text; begin z_query := 'select id, val from (values (1, ''a''), (2, ''b'')) as v(id, val)'; for id, val in execute z_query loop return next; end loop; end; $$ language plpgsql immutable; -- should be ok select * from plpgsql_check_function('tabret_dynamic()'); plpgsql_check_function ------------------------ (0 rows) create or replace function tabret_dynamic() returns table (id integer, val text) as $$ declare z_query text; begin z_query := 'select id, val from (values (1, ''a''), (2, ''b'')) as v(id, val)'; return query execute z_query; end; $$ language plpgsql immutable; -- should be ok select * from plpgsql_check_function('tabret_dynamic()'); plpgsql_check_function ------------------------ (0 rows) drop function tabret_dynamic; -- should not to crash on empty string, or comment create or replace function dynamic_emptystr() returns void as $$ begin execute ''; end; $$ language plpgsql; -- should be ok select * from plpgsql_check_function('dynamic_emptystr()'); plpgsql_check_function ------------------------ (0 rows) create or replace function dynamic_emptystr() returns void as $$ declare x int; begin execute '--' into x; end; $$ language plpgsql; -- should not to crash, no result error select * from plpgsql_check_function('dynamic_emptystr()'); plpgsql_check_function ------------------------------------------------------- error:XX000:4:EXECUTE:expression does not return data (1 row) drop function dynamic_emptystr(); -- unclosed cursor detection create or replace function fx() returns void as $$ declare c refcursor; begin open c for select * from pg_class; end; $$ language plpgsql; do $$ begin perform fx(); -- should to show warning perform fx(); end; $$; WARNING: cursor "c" is not closed set plpgsql_check.strict_cursors_leaks to on; do $$ begin -- should to show warning perform fx(); -- should to show warning perform fx(); end; $$; WARNING: cursor is not closed WARNING: cursor is not closed create or replace function fx() returns void as $$ declare c refcursor; begin open c for select * from pg_class; close c; end; $$ language plpgsql; -- without warnings do $$ begin perform fx(); perform fx(); end; $$; set plpgsql_check.strict_cursors_leaks to off; drop function fx(); create table public.testt(a int, b int); create or replace function fx() returns void as $$ declare v1 varchar default 'public'; v2 varchar default 'testt'; v3 varchar default 'a'; begin raise notice '%', format('%I.%I.%I', v1, v2, v3); perform 'pragma:assert-schema: v1'; perform 'pragma:assert-table: v1, v2'; perform 'pragma:assert-column: v1, v2, v3'; perform 'pragma:assert-table: v2'; perform 'pragma:assert-column: v2, v3'; end; $$ language plpgsql; select * from plpgsql_check_function('fx()'); plpgsql_check_function ------------------------ (0 rows) create table public.testt(a int, b int); ERROR: relation "testt" already exists create or replace function fx() returns void as $$ declare v1 varchar default 'public'; v2 varchar default 'testt'; v3 varchar default 'x'; begin raise notice '%', format('%I.%I.%I', v1, v2, v3); perform 'pragma:assert-column: v1, v2, v3'; end; $$ language plpgsql; select * from plpgsql_check_function('fx()'); plpgsql_check_function ------------------------------------------------------------------------------ error:42703:8:PERFORM:column "x" of relation "public"."testt" does not exist (1 row) drop function fx(); drop table public.testt; create or replace function fx() returns void as $$ declare v_sql varchar default ''; i int; begin -- we don't support constant tracing from queries select 'bla bla bla' into v_sql; execute v_sql into i; raise notice '%', i; end; $$ language plpgsql; -- should be ok select * from plpgsql_check_function('fx()'); plpgsql_check_function ------------------------ (0 rows) drop function fx(); -- should not to raise warning do $$ declare c cursor for select * from generate_series(1,10); t int; begin for i in 1..2 loop open c; loop fetch c into t; exit when not found; raise notice '%', t; end loop; close c; end loop; end; $$; NOTICE: 1 NOTICE: 2 NOTICE: 3 NOTICE: 4 NOTICE: 5 NOTICE: 6 NOTICE: 7 NOTICE: 8 NOTICE: 9 NOTICE: 10 NOTICE: 1 NOTICE: 2 NOTICE: 3 NOTICE: 4 NOTICE: 5 NOTICE: 6 NOTICE: 7 NOTICE: 8 NOTICE: 9 NOTICE: 10 -- should not raise warning create or replace function fx(p text) returns void as $$ begin execute format($_$select '%1$I'$_$, p); end; $$ language plpgsql; -- should be ok select * from plpgsql_check_function('fx(text)'); plpgsql_check_function ------------------------ (0 rows) drop function fx(text); create or replace function fx() returns void as $$ declare p varchar; begin p := 'ahoj'; execute format($_$select '%1$I'$_$, p); end; $$ language plpgsql; -- should be ok select * from plpgsql_check_function('fx()'); plpgsql_check_function ------------------------ (0 rows) drop function fx(); -- should not crash create or replace procedure p1() as $$ begin commit; end; $$ language plpgsql; set plpgsql_check.cursors_leaks to on; do $$ declare c cursor for select 1; begin open c; call p1(); end $$; set plpgsql_check.cursors_leaks to default; drop procedure p1; plpgsql_check-2.7.8/expected/plpgsql_check_passive-12.out000066400000000000000000000000001465410532300234630ustar00rootroot00000000000000plpgsql_check-2.7.8/expected/plpgsql_check_passive-13.out000066400000000000000000000000001465410532300234640ustar00rootroot00000000000000plpgsql_check-2.7.8/expected/plpgsql_check_passive-14.out000066400000000000000000000000001465410532300234650ustar00rootroot00000000000000plpgsql_check-2.7.8/expected/plpgsql_check_passive-15.out000066400000000000000000000000001465410532300234660ustar00rootroot00000000000000plpgsql_check-2.7.8/expected/plpgsql_check_passive-16.out000066400000000000000000000000001465410532300234670ustar00rootroot00000000000000plpgsql_check-2.7.8/expected/plpgsql_check_passive-17.out000066400000000000000000000000001465410532300234700ustar00rootroot00000000000000plpgsql_check-2.7.8/expected/plpgsql_check_passive-18.out000066400000000000000000000000001465410532300234710ustar00rootroot00000000000000plpgsql_check-2.7.8/expected/plpgsql_check_passive.out000066400000000000000000000207151465410532300232620ustar00rootroot00000000000000load 'plpgsql'; load 'plpgsql_check'; set client_min_messages to notice; -- enforce context's displaying -- emulate pre 9.6 behave \set SHOW_CONTEXT always set plpgsql_check.mode = 'every_start'; create table t1(a int, b int); create function f1() returns void as $$ begin if false then update t1 set c = 30; end if; end; $$ language plpgsql; select f1(); ERROR: column "c" of relation "t1" does not exist LINE 1: update t1 set c = 30 ^ QUERY: update t1 set c = 30 CONTEXT: PL/pgSQL function f1() line 4 at SQL statement drop function f1(); create function f1() returns void as $$ begin if false then insert into t1 values(10,20); update t1 set a = 10; delete from t1; end if; end; $$ language plpgsql stable; select f1(); ERROR: INSERT is not allowed in a non volatile function LINE 1: insert into t1 values(10,20) ^ QUERY: insert into t1 values(10,20) CONTEXT: PL/pgSQL function f1() line 4 at SQL statement drop function f1(); create function g1(out a int, out b int) as $$ select 10,20; $$ language sql; create function f1() returns void as $$ declare r record; begin r := g1(); if false then raise notice '%', r.c; end if; end; $$ language plpgsql; select f1(); ERROR: record "r" has no field "c" CONTEXT: SQL expression "r.c" PL/pgSQL function f1() line 6 at RAISE drop function f1(); drop function g1(); create function g1(out a int, out b int) returns setof record as $$ select * from t1; $$ language sql; create function f1() returns void as $$ declare r record; begin for r in select * from g1() loop raise notice '%', r.c; end loop; end; $$ language plpgsql; select f1(); ERROR: record "r" has no field "c" CONTEXT: SQL expression "r.c" PL/pgSQL function f1() line 6 at RAISE create or replace function f1() returns void as $$ declare r record; begin for r in select * from g1() loop r.c := 20; end loop; end; $$ language plpgsql; select f1(); ERROR: record "r" has no field "c" CONTEXT: PL/pgSQL function f1() line 6 at assignment to field "c" of variable "r" declared on line 2 drop function f1(); drop function g1(); create function f1() returns int as $$ declare r int; begin if false then r := a + b; end if; return r; end; $$ language plpgsql; select f1(); ERROR: column "a" does not exist LINE 1: r := a + b ^ QUERY: r := a + b CONTEXT: PL/pgSQL function f1() line 5 at assignment to variable "r" declared on line 2 drop function f1(); create or replace function f1() returns void as $$ declare r int[]; begin if false then r[c+10] := 20; end if; end; $$ language plpgsql; select f1(); ERROR: column "c" does not exist LINE 1: r[c+10] := 20 ^ QUERY: r[c+10] := 20 CONTEXT: PL/pgSQL function f1() line 5 at assignment to variable "r" declared on line 2 drop function f1(); create or replace function f1() returns void as $$ declare r int; begin if false then r[10] := 20; end if; end; $$ language plpgsql; select f1(); ERROR: cannot subscript type integer because it does not support subscripting LINE 1: r[10] := 20 ^ QUERY: r[10] := 20 CONTEXT: PL/pgSQL function f1() line 5 at assignment to variable "r" declared on line 2 drop function f1(); create or replace function f1() returns void as $$ begin if false then insert into badbadtable values(10,20); end if; return; end; $$ language plpgsql; set plpgsql_check.mode = 'fresh_start'; select f1(); ERROR: relation "badbadtable" does not exist LINE 1: insert into badbadtable values(10,20) ^ QUERY: insert into badbadtable values(10,20) CONTEXT: PL/pgSQL function f1() line 4 at SQL statement -- should not raise exception there select f1(); f1 ---- (1 row) create or replace function f1() returns void as $$ begin if false then insert into badbadtable values(10,20); end if; return; end; $$ language plpgsql; -- after refreshing it should to raise exception again select f1(); ERROR: relation "badbadtable" does not exist LINE 1: insert into badbadtable values(10,20) ^ QUERY: insert into badbadtable values(10,20) CONTEXT: PL/pgSQL function f1() line 4 at SQL statement set plpgsql_check.mode = 'every_start'; -- should to raise warning only set plpgsql_check.fatal_errors = false; select f1(); WARNING: relation "badbadtable" does not exist LINE 1: insert into badbadtable values(10,20) ^ QUERY: insert into badbadtable values(10,20) CONTEXT: PL/pgSQL function f1() line 4 at SQL statement PL/pgSQL function f1() line 4 at SQL statement f1 ---- (1 row) drop function f1(); create function f1() returns setof t1 as $$ begin if false then return query select a,a,a from t1; return; end if; end; $$ language plpgsql; select * from f1(); WARNING: structure of query does not match function result type DETAIL: Number of returned columns (3) does not match expected column count (2). CONTEXT: PL/pgSQL function f1() line 4 at RETURN QUERY PL/pgSQL function f1() line 4 at RETURN QUERY a | b ---+--- (0 rows) drop function f1(); create function f1() returns setof t1 as $$ begin if false then return query select a, b::numeric from t1; return; end if; end; $$ language plpgsql; select * from f1(); WARNING: structure of query does not match function result type DETAIL: Returned type numeric does not match expected type integer in column 2. CONTEXT: PL/pgSQL function f1() line 4 at RETURN QUERY PL/pgSQL function f1() line 4 at RETURN QUERY a | b ---+--- (0 rows) drop function f1(); drop table t1; do $$ declare begin if false then for i in 1,3..(2) loop raise notice 'foo %', i; end loop; end if; end; $$; WARNING: query "1,3" returned 2 columns CONTEXT: PL/pgSQL function inline_code_block line 5 at FOR with integer loop variable PL/pgSQL function inline_code_block line 5 at FOR with integer loop variable -- tests designed for 9.2 set check_function_bodies to off; create or replace function f1() returns void as $$ begin if false then raise notice '%', 1, 2; end if; end; $$ language plpgsql; select f1(); ERROR: too many parameters specified for RAISE CONTEXT: compilation of PL/pgSQL function "f1" near line 4 drop function f1(); create or replace function f1() returns void as $$ begin if false then raise notice '% %'; end if; end; $$ language plpgsql; select f1(); ERROR: too few parameters specified for RAISE CONTEXT: compilation of PL/pgSQL function "f1" near line 4 drop function f1(); create type _exception_type as ( state text, message text, detail text); create or replace function f1() returns void as $$ declare _exception record; begin _exception := NULL::_exception_type; exception when others then get stacked diagnostics _exception.state = RETURNED_SQLSTATE, _exception.message = MESSAGE_TEXT, _exception.detail = PG_EXCEPTION_DETAIL, _exception.hint = PG_EXCEPTION_HINT; end; $$ language plpgsql; select f1(); WARNING: record "_exception" has no field "hint" CONTEXT: PL/pgSQL function f1() line 7 at GET STACKED DIAGNOSTICS PL/pgSQL function f1() line 7 at GET STACKED DIAGNOSTICS f1 ---- (1 row) drop function f1(); drop type _exception_type; create table footab(a int, b int, c int); create or replace function footab_trig_func() returns trigger as $$ declare x int; begin if false then -- should be ok; select count(*) from newtab into x; -- should fail; select count(*) from newtab where d = 10 into x; end if; return null; end; $$ language plpgsql; create trigger footab_trigger after insert on footab referencing new table as newtab for each statement execute procedure footab_trig_func(); -- should to fail insert into footab values(1,2,3); WARNING: column "d" does not exist LINE 1: select count(*) from newtab where d = 10 ^ QUERY: select count(*) from newtab where d = 10 CONTEXT: PL/pgSQL function footab_trig_func() line 9 at SQL statement PL/pgSQL function footab_trig_func() line 9 at SQL statement create or replace function footab_trig_func() returns trigger as $$ declare x int; begin if false then -- should be ok; select count(*) from newtab into x; end if; return null; end; $$ language plpgsql; -- should be ok insert into footab values(1,2,3); drop table footab; drop function footab_trig_func(); set plpgsql_check.mode = 'every_start'; create or replace procedure proc_test() as $$ begin commit; end; $$ language plpgsql; call proc_test(); drop procedure proc_test(); -- should not to crash set plpgsql_check.mode = 'fresh_start'; do $$ begin end; $$; plpgsql_check-2.7.8/expected/plpgsql_check_passive_1.out000066400000000000000000000206471465410532300235060ustar00rootroot00000000000000load 'plpgsql'; load 'plpgsql_check'; set client_min_messages to notice; -- enforce context's displaying -- emulate pre 9.6 behave \set SHOW_CONTEXT always set plpgsql_check.mode = 'every_start'; create table t1(a int, b int); create function f1() returns void as $$ begin if false then update t1 set c = 30; end if; end; $$ language plpgsql; select f1(); ERROR: column "c" of relation "t1" does not exist LINE 1: update t1 set c = 30 ^ QUERY: update t1 set c = 30 CONTEXT: PL/pgSQL function f1() line 4 at SQL statement drop function f1(); create function f1() returns void as $$ begin if false then insert into t1 values(10,20); update t1 set a = 10; delete from t1; end if; end; $$ language plpgsql stable; select f1(); ERROR: INSERT is not allowed in a non volatile function LINE 1: insert into t1 values(10,20) ^ QUERY: insert into t1 values(10,20) CONTEXT: PL/pgSQL function f1() line 4 at SQL statement drop function f1(); create function g1(out a int, out b int) as $$ select 10,20; $$ language sql; create function f1() returns void as $$ declare r record; begin r := g1(); if false then raise notice '%', r.c; end if; end; $$ language plpgsql; select f1(); ERROR: record "r" has no field "c" CONTEXT: SQL statement "SELECT r.c" PL/pgSQL function f1() line 6 at RAISE drop function f1(); drop function g1(); create function g1(out a int, out b int) returns setof record as $$ select * from t1; $$ language sql; create function f1() returns void as $$ declare r record; begin for r in select * from g1() loop raise notice '%', r.c; end loop; end; $$ language plpgsql; select f1(); ERROR: record "r" has no field "c" CONTEXT: SQL statement "SELECT r.c" PL/pgSQL function f1() line 6 at RAISE create or replace function f1() returns void as $$ declare r record; begin for r in select * from g1() loop r.c := 20; end loop; end; $$ language plpgsql; select f1(); ERROR: record "r" has no field "c" CONTEXT: PL/pgSQL function f1() line 6 at assignment to field "c" of variable "r" declared on line 2 drop function f1(); drop function g1(); create function f1() returns int as $$ declare r int; begin if false then r := a + b; end if; return r; end; $$ language plpgsql; select f1(); ERROR: column "a" does not exist LINE 1: SELECT a + b ^ QUERY: SELECT a + b CONTEXT: PL/pgSQL function f1() line 5 at assignment to variable "r" declared on line 2 drop function f1(); create or replace function f1() returns void as $$ declare r int[]; begin if false then r[c+10] := 20; end if; end; $$ language plpgsql; select f1(); ERROR: column "c" does not exist LINE 1: SELECT c+10 ^ QUERY: SELECT c+10 CONTEXT: PL/pgSQL function f1() line 5 at assignment to element of variable "r" declared on line 2 drop function f1(); create or replace function f1() returns void as $$ declare r int; begin if false then r[10] := 20; end if; end; $$ language plpgsql; select f1(); ERROR: subscripted object is not an array CONTEXT: PL/pgSQL function f1() line 5 at assignment to element of variable "r" declared on line 2 drop function f1(); create or replace function f1() returns void as $$ begin if false then insert into badbadtable values(10,20); end if; return; end; $$ language plpgsql; set plpgsql_check.mode = 'fresh_start'; select f1(); ERROR: relation "badbadtable" does not exist LINE 1: insert into badbadtable values(10,20) ^ QUERY: insert into badbadtable values(10,20) CONTEXT: PL/pgSQL function f1() line 4 at SQL statement -- should not raise exception there select f1(); f1 ---- (1 row) create or replace function f1() returns void as $$ begin if false then insert into badbadtable values(10,20); end if; return; end; $$ language plpgsql; -- after refreshing it should to raise exception again select f1(); ERROR: relation "badbadtable" does not exist LINE 1: insert into badbadtable values(10,20) ^ QUERY: insert into badbadtable values(10,20) CONTEXT: PL/pgSQL function f1() line 4 at SQL statement set plpgsql_check.mode = 'every_start'; -- should to raise warning only set plpgsql_check.fatal_errors = false; select f1(); WARNING: relation "badbadtable" does not exist LINE 1: insert into badbadtable values(10,20) ^ QUERY: insert into badbadtable values(10,20) CONTEXT: PL/pgSQL function f1() line 4 at SQL statement PL/pgSQL function f1() line 4 at SQL statement f1 ---- (1 row) drop function f1(); create function f1() returns setof t1 as $$ begin if false then return query select a,a,a from t1; return; end if; end; $$ language plpgsql; select * from f1(); WARNING: structure of query does not match function result type DETAIL: Number of returned columns (3) does not match expected column count (2). CONTEXT: PL/pgSQL function f1() line 4 at RETURN QUERY PL/pgSQL function f1() line 4 at RETURN QUERY a | b ---+--- (0 rows) drop function f1(); create function f1() returns setof t1 as $$ begin if false then return query select a, b::numeric from t1; return; end if; end; $$ language plpgsql; select * from f1(); WARNING: structure of query does not match function result type DETAIL: Returned type numeric does not match expected type integer in column 2. CONTEXT: PL/pgSQL function f1() line 4 at RETURN QUERY PL/pgSQL function f1() line 4 at RETURN QUERY a | b ---+--- (0 rows) drop function f1(); drop table t1; do $$ declare begin if false then for i in 1,3..(2) loop raise notice 'foo %', i; end loop; end if; end; $$; WARNING: query "SELECT 1,3" returned 2 columns CONTEXT: PL/pgSQL function inline_code_block line 5 at FOR with integer loop variable PL/pgSQL function inline_code_block line 5 at FOR with integer loop variable -- tests designed for 9.2 set check_function_bodies to off; create or replace function f1() returns void as $$ begin if false then raise notice '%', 1, 2; end if; end; $$ language plpgsql; select f1(); ERROR: too many parameters specified for RAISE CONTEXT: compilation of PL/pgSQL function "f1" near line 4 drop function f1(); create or replace function f1() returns void as $$ begin if false then raise notice '% %'; end if; end; $$ language plpgsql; select f1(); ERROR: too few parameters specified for RAISE CONTEXT: compilation of PL/pgSQL function "f1" near line 4 drop function f1(); create type _exception_type as ( state text, message text, detail text); create or replace function f1() returns void as $$ declare _exception record; begin _exception := NULL::_exception_type; exception when others then get stacked diagnostics _exception.state = RETURNED_SQLSTATE, _exception.message = MESSAGE_TEXT, _exception.detail = PG_EXCEPTION_DETAIL, _exception.hint = PG_EXCEPTION_HINT; end; $$ language plpgsql; select f1(); WARNING: record "_exception" has no field "hint" CONTEXT: PL/pgSQL function f1() line 7 at GET STACKED DIAGNOSTICS PL/pgSQL function f1() line 7 at GET STACKED DIAGNOSTICS f1 ---- (1 row) drop function f1(); drop type _exception_type; create table footab(a int, b int, c int); create or replace function footab_trig_func() returns trigger as $$ declare x int; begin if false then -- should be ok; select count(*) from newtab into x; -- should fail; select count(*) from newtab where d = 10 into x; end if; return null; end; $$ language plpgsql; create trigger footab_trigger after insert on footab referencing new table as newtab for each statement execute procedure footab_trig_func(); -- should to fail insert into footab values(1,2,3); WARNING: column "d" does not exist LINE 1: select count(*) from newtab where d = 10 ^ QUERY: select count(*) from newtab where d = 10 CONTEXT: PL/pgSQL function footab_trig_func() line 9 at SQL statement PL/pgSQL function footab_trig_func() line 9 at SQL statement create or replace function footab_trig_func() returns trigger as $$ declare x int; begin if false then -- should be ok; select count(*) from newtab into x; end if; return null; end; $$ language plpgsql; -- should be ok insert into footab values(1,2,3); drop table footab; drop function footab_trig_func(); set plpgsql_check.mode = 'every_start'; create or replace procedure proc_test() as $$ begin commit; end; $$ language plpgsql; call proc_test(); drop procedure proc_test(); -- should not to crash set plpgsql_check.mode = 'fresh_start'; do $$ begin end; $$; plpgsql_check-2.7.8/msvc/000077500000000000000000000000001465410532300153225ustar00rootroot00000000000000plpgsql_check-2.7.8/msvc/plpgsql_check.sln000066400000000000000000000040351465410532300206610ustar00rootroot00000000000000 Microsoft Visual Studio Solution File, Format Version 11.00 # Visual C++ Express 2010 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "plpgsql_check", "plpgsql_check\plpgsql_check.vcxproj", "{7ED1E443-C8B4-4CF1-93AE-580AE10F5F95}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution 9.2|Win32 = 9.2|Win32 9.2|x64 = 9.2|x64 9.3|Win32 = 9.3|Win32 9.3|x64 = 9.3|x64 9.4|Win32 = 9.4|Win32 9.4|x64 = 9.4|x64 Debug|Win32 = Debug|Win32 Debug|x64 = Debug|x64 Release|Win32 = Release|Win32 Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {7ED1E443-C8B4-4CF1-93AE-580AE10F5F95}.9.2|Win32.ActiveCfg = 9.2|Win32 {7ED1E443-C8B4-4CF1-93AE-580AE10F5F95}.9.2|Win32.Build.0 = 9.2|Win32 {7ED1E443-C8B4-4CF1-93AE-580AE10F5F95}.9.2|x64.ActiveCfg = 9.2|x64 {7ED1E443-C8B4-4CF1-93AE-580AE10F5F95}.9.2|x64.Build.0 = 9.2|x64 {7ED1E443-C8B4-4CF1-93AE-580AE10F5F95}.9.3|Win32.ActiveCfg = 9.3|Win32 {7ED1E443-C8B4-4CF1-93AE-580AE10F5F95}.9.3|Win32.Build.0 = 9.3|Win32 {7ED1E443-C8B4-4CF1-93AE-580AE10F5F95}.9.3|x64.ActiveCfg = 9.3|x64 {7ED1E443-C8B4-4CF1-93AE-580AE10F5F95}.9.3|x64.Build.0 = 9.3|x64 {7ED1E443-C8B4-4CF1-93AE-580AE10F5F95}.9.4|Win32.ActiveCfg = 9.4|Win32 {7ED1E443-C8B4-4CF1-93AE-580AE10F5F95}.9.4|Win32.Build.0 = 9.4|Win32 {7ED1E443-C8B4-4CF1-93AE-580AE10F5F95}.9.4|x64.ActiveCfg = 9.4|x64 {7ED1E443-C8B4-4CF1-93AE-580AE10F5F95}.9.4|x64.Build.0 = 9.4|x64 {7ED1E443-C8B4-4CF1-93AE-580AE10F5F95}.Debug|Win32.ActiveCfg = 9.4|x64 {7ED1E443-C8B4-4CF1-93AE-580AE10F5F95}.Debug|x64.ActiveCfg = 9.4|x64 {7ED1E443-C8B4-4CF1-93AE-580AE10F5F95}.Debug|x64.Build.0 = 9.4|x64 {7ED1E443-C8B4-4CF1-93AE-580AE10F5F95}.Release|Win32.ActiveCfg = 9.4|x64 {7ED1E443-C8B4-4CF1-93AE-580AE10F5F95}.Release|x64.ActiveCfg = 9.4|x64 {7ED1E443-C8B4-4CF1-93AE-580AE10F5F95}.Release|x64.Build.0 = 9.4|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection EndGlobal plpgsql_check-2.7.8/msvc/plpgsql_check/000077500000000000000000000000001465410532300201415ustar00rootroot00000000000000plpgsql_check-2.7.8/msvc/plpgsql_check/plpgsql_check.vcxproj000066400000000000000000000356241465410532300244070ustar00rootroot00000000000000 9.2 Win32 9.2 x64 9.3 Win32 9.3 x64 9.4 Win32 9.4 x64 {7ED1E443-C8B4-4CF1-93AE-580AE10F5F95} plpgsql_check DynamicLibrary false true MultiByte Windows7.1SDK DynamicLibrary false true MultiByte Windows7.1SDK DynamicLibrary false true MultiByte Windows7.1SDK DynamicLibrary false true MultiByte Windows7.1SDK DynamicLibrary false true MultiByte Windows7.1SDK DynamicLibrary false true MultiByte Windows7.1SDK false false false false false false Level3 MaxSpeed true true WIN32;%(PreprocessorDefinitions) false CompileAsC C:\Program Files %28x86%29\PostgreSQL\9.4\include\server\port\win32_msvc;C:\Program Files %28x86%29\PostgreSQL\9.4\include\server\port\win32;C:\Program Files %28x86%29\PostgreSQL\9.4\include\server;C:\Program Files %28x86%29\PostgreSQL\9.4\include;%(AdditionalIncludeDirectories) true true true postgres.lib;plpgsql.lib;%(AdditionalDependencies) C:\Program Files %28x86%29\PostgreSQL\9.4\lib;%(AdditionalLibraryDirectories) false Level3 MaxSpeed true true WIN32;%(PreprocessorDefinitions) false CompileAsC C:\Program Files %28x86%29\PostgreSQL\9.2\include\server\port\win32_msvc;C:\Program Files %28x86%29\PostgreSQL\9.2\include\server\port\win32;C:\Program Files %28x86%29\PostgreSQL\9.2\include\server;C:\Program Files %28x86%29\PostgreSQL\9.2\include;%(AdditionalIncludeDirectories) true true true postgres.lib;plpgsql.lib;%(AdditionalDependencies) C:\Program Files %28x86%29\PostgreSQL\9.2\lib;%(AdditionalLibraryDirectories) false Level3 MaxSpeed true true WIN32;%(PreprocessorDefinitions) false CompileAsC C:\Program Files\PostgreSQL\9.4\include\server\port\win32_msvc;C:\Program Files\PostgreSQL\9.4\include\server\port\win32;C:\Program Files\PostgreSQL\9.4\include\server;C:\Program Files\PostgreSQL\9.4\include;%(AdditionalIncludeDirectories) true true true postgres.lib;plpgsql.lib;%(AdditionalDependencies) C:\Program Files\PostgreSQL\9.4\lib;%(AdditionalLibraryDirectories) false Level3 MaxSpeed true true WIN32;%(PreprocessorDefinitions) false CompileAsC C:\Program Files\PostgreSQL\9.2\include\server\port\win32_msvc;C:\Program Files\PostgreSQL\9.2\include\server\port\win32;C:\Program Files\PostgreSQL\9.2\include\server;C:\Program Files\PostgreSQL\9.2\include;%(AdditionalIncludeDirectories) true true true postgres.lib;plpgsql.lib;%(AdditionalDependencies) C:\Program Files\PostgreSQL\9.2\lib;%(AdditionalLibraryDirectories) false Level3 MaxSpeed true true WIN32;%(PreprocessorDefinitions) false CompileAsC C:\Program Files %28x86%29\PostgreSQL\9.3\include\server\port\win32_msvc;C:\Program Files %28x86%29\PostgreSQL\9.3\include\server\port\win32;C:\Program Files %28x86%29\PostgreSQL\9.3\include\server;C:\Program Files %28x86%29\PostgreSQL\9.3\include;%(AdditionalIncludeDirectories) true true true postgres.lib;plpgsql.lib;%(AdditionalDependencies) C:\Program Files %28x86%29\PostgreSQL\9.3\lib;%(AdditionalLibraryDirectories) false Level3 MaxSpeed true true WIN32;%(PreprocessorDefinitions) false CompileAsC C:\Program Files\PostgreSQL\9.3\include\server\port\win32_msvc;C:\Program Files\PostgreSQL\9.3\include\server\port\win32;C:\Program Files\PostgreSQL\9.3\include\server;C:\Program Files\PostgreSQL\9.3\include;%(AdditionalIncludeDirectories) true true true postgres.lib;plpgsql.lib;%(AdditionalDependencies) C:\Program Files\PostgreSQL\9.3\lib;%(AdditionalLibraryDirectories) false plpgsql_check-2.7.8/msvc/plpgsql_check/plpgsql_check.vcxproj.filters000066400000000000000000000021561465410532300260500ustar00rootroot00000000000000 {4FC737F1-C7A5-4376-A066-2A32D752A2FF} cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx {93995380-89BD-4b04-88EB-625FBE52EBFB} h;hpp;hxx;hm;inl;inc;xsd {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms Source Files Header Files plpgsql_check-2.7.8/msvc/plpgsql_check/plpgsql_check.vcxproj.user000066400000000000000000000002171465410532300253520ustar00rootroot00000000000000 plpgsql_check-2.7.8/plpgsql_check--2.7.sql000066400000000000000000000350341465410532300203000ustar00rootroot00000000000000-- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION plpgsql_check" to load this file. \quit CREATE FUNCTION plpgsql_check_function_tb(funcoid regprocedure, relid regclass DEFAULT 0, fatal_errors boolean DEFAULT true, other_warnings boolean DEFAULT true, performance_warnings boolean DEFAULT false, extra_warnings boolean DEFAULT true, security_warnings boolean DEFAULT false, compatibility_warnings boolean DEFAULT false, oldtable name DEFAULT null, newtable name DEFAULT null, anyelememttype regtype DEFAULT 'int', anyenumtype regtype DEFAULT '-', anyrangetype regtype DEFAULT 'int4range', anycompatibletype regtype DEFAULT 'int', anycompatiblerangetype regtype DEFAULT 'int4range', without_warnings boolean DEFAULT false, all_warnings boolean DEFAULT false, use_incomment_options boolean DEFAULT true, incomment_options_usage_warning boolean DEFAULT false, constant_tracing boolean DEFAULT true) RETURNS TABLE(functionid regproc, lineno int, statement text, sqlstate text, message text, detail text, hint text, level text, "position" int, query text, context text) AS 'MODULE_PATHNAME','plpgsql_check_function_tb' LANGUAGE C; CREATE FUNCTION plpgsql_check_function(funcoid regprocedure, relid regclass DEFAULT 0, format text DEFAULT 'text', fatal_errors boolean DEFAULT true, other_warnings boolean DEFAULT true, performance_warnings boolean DEFAULT false, extra_warnings boolean DEFAULT true, security_warnings boolean DEFAULT false, compatibility_warnings boolean DEFAULT false, oldtable name DEFAULT null, newtable name DEFAULT null, anyelememttype regtype DEFAULT 'int', anyenumtype regtype DEFAULT '-', anyrangetype regtype DEFAULT 'int4range', anycompatibletype regtype DEFAULT 'int', anycompatiblerangetype regtype DEFAULT 'int4range', without_warnings boolean DEFAULT false, all_warnings boolean DEFAULT false, use_incomment_options boolean DEFAULT true, incomment_options_usage_warning boolean DEFAULT false, constant_tracing boolean DEFAULT true) RETURNS SETOF text AS 'MODULE_PATHNAME','plpgsql_check_function' LANGUAGE C; CREATE FUNCTION plpgsql_check_function_tb(name text, relid regclass DEFAULT 0, fatal_errors boolean DEFAULT true, other_warnings boolean DEFAULT true, performance_warnings boolean DEFAULT false, extra_warnings boolean DEFAULT true, security_warnings boolean DEFAULT false, compatibility_warnings boolean DEFAULT false, oldtable name DEFAULT null, newtable name DEFAULT null, anyelememttype regtype DEFAULT 'int', anyenumtype regtype DEFAULT '-', anyrangetype regtype DEFAULT 'int4range', anycompatibletype regtype DEFAULT 'int', anycompatiblerangetype regtype DEFAULT 'int4range', without_warnings boolean DEFAULT false, all_warnings boolean DEFAULT false, use_incomment_options boolean DEFAULT true, incomment_options_usage_warning boolean DEFAULT false, constant_tracing boolean DEFAULT true) RETURNS TABLE(functionid regproc, lineno int, statement text, sqlstate text, message text, detail text, hint text, level text, "position" int, query text, context text) AS 'MODULE_PATHNAME','plpgsql_check_function_tb_name' LANGUAGE C; CREATE FUNCTION plpgsql_check_function(name text, relid regclass DEFAULT 0, format text DEFAULT 'text', fatal_errors boolean DEFAULT true, other_warnings boolean DEFAULT true, performance_warnings boolean DEFAULT false, extra_warnings boolean DEFAULT true, security_warnings boolean DEFAULT false, compatibility_warnings boolean DEFAULT false, oldtable name DEFAULT null, newtable name DEFAULT null, anyelememttype regtype DEFAULT 'int', anyenumtype regtype DEFAULT '-', anyrangetype regtype DEFAULT 'int4range', anycompatibletype regtype DEFAULT 'int', anycompatiblerangetype regtype DEFAULT 'int4range', without_warnings boolean DEFAULT false, all_warnings boolean DEFAULT false, use_incomment_options boolean DEFAULT true, incomment_options_usage_warning boolean DEFAULT false, constant_tracing boolean DEFAULT true) RETURNS SETOF text AS 'MODULE_PATHNAME','plpgsql_check_function_name' LANGUAGE C; CREATE FUNCTION __plpgsql_show_dependency_tb(funcoid regprocedure, relid regclass DEFAULT 0, anyelememttype regtype DEFAULT 'int', anyenumtype regtype DEFAULT '-', anyrangetype regtype DEFAULT 'int4range', anycompatibletype regtype DEFAULT 'int', anycompatiblerangetype regtype DEFAULT 'int4range') RETURNS TABLE(type text, oid oid, schema text, name text, params text) AS 'MODULE_PATHNAME','plpgsql_show_dependency_tb' LANGUAGE C; CREATE FUNCTION __plpgsql_show_dependency_tb(name text, relid regclass DEFAULT 0, anyelememttype regtype DEFAULT 'int', anyenumtype regtype DEFAULT '-', anyrangetype regtype DEFAULT 'int4range', anycompatibletype regtype DEFAULT 'int', anycompatiblerangetype regtype DEFAULT 'int4range') RETURNS TABLE(type text, oid oid, schema text, name text, params text) AS 'MODULE_PATHNAME','plpgsql_show_dependency_tb_name' LANGUAGE C; CREATE FUNCTION plpgsql_show_dependency_tb(funcoid regprocedure, relid regclass DEFAULT 0, anyelememttype regtype DEFAULT 'int', anyenumtype regtype DEFAULT '-', anyrangetype regtype DEFAULT 'int4range', anycompatibletype regtype DEFAULT 'int', anycompatiblerangetype regtype DEFAULT 'int4range') RETURNS TABLE(type text, oid oid, schema text, name text, params text) AS $$ SELECT * FROM @extschema@.__plpgsql_show_dependency_tb($1, $2, $3, $4, $5, $6, $7) ORDER BY 1, 3, 4; $$ LANGUAGE sql; CREATE FUNCTION plpgsql_show_dependency_tb(fnname text, relid regclass DEFAULT 0, anyelememttype regtype DEFAULT 'int', anyenumtype regtype DEFAULT '-', anyrangetype regtype DEFAULT 'int4range', anycompatibletype regtype DEFAULT 'int', anycompatiblerangetype regtype DEFAULT 'int4range') RETURNS TABLE(type text, oid oid, schema text, name text, params text) AS $$ SELECT * FROM @extschema@.__plpgsql_show_dependency_tb($1, $2, $3, $4, $5, $6, $7) ORDER BY 1, 3, 4; $$ LANGUAGE sql; CREATE FUNCTION plpgsql_profiler_function_tb(funcoid regprocedure) RETURNS TABLE(lineno int, stmt_lineno int, queryids int8[], cmds_on_row int, exec_stmts int8, exec_stmts_err int8, total_time double precision, avg_time double precision, max_time double precision[], processed_rows int8[], source text) AS 'MODULE_PATHNAME','plpgsql_profiler_function_tb' LANGUAGE C STRICT; CREATE FUNCTION plpgsql_profiler_function_tb(name text) RETURNS TABLE(lineno int, stmt_lineno int, queryids int8[], cmds_on_row int, exec_stmts int8, exec_stmts_err int8, total_time double precision, avg_time double precision, max_time double precision[], processed_rows int8[], source text) AS 'MODULE_PATHNAME','plpgsql_profiler_function_tb_name' LANGUAGE C STRICT; CREATE FUNCTION plpgsql_profiler_function_statements_tb(funcoid regprocedure) RETURNS TABLE(stmtid int, parent_stmtid int, parent_note text, block_num int, lineno int, queryid int8, exec_stmts int8, exec_stmts_err int8, total_time double precision, avg_time double precision, max_time double precision, processed_rows int8, stmtname text) AS 'MODULE_PATHNAME','plpgsql_profiler_function_statements_tb' LANGUAGE C STRICT; CREATE FUNCTION plpgsql_profiler_function_statements_tb(name text) RETURNS TABLE(stmtid int, parent_stmtid int, parent_note text, block_num int, lineno int, queryid int8, exec_stmts int8, exec_stmts_err int8, total_time double precision, avg_time double precision, max_time double precision, processed_rows int8, stmtname text) AS 'MODULE_PATHNAME','plpgsql_profiler_function_statements_tb_name' LANGUAGE C STRICT; CREATE FUNCTION plpgsql_profiler_install_fake_queryid_hook() RETURNS void AS 'MODULE_PATHNAME','plpgsql_profiler_install_fake_queryid_hook' LANGUAGE C STRICT; CREATE FUNCTION plpgsql_profiler_remove_fake_queryid_hook() RETURNS void AS 'MODULE_PATHNAME','plpgsql_profiler_remove_fake_queryid_hook' LANGUAGE C STRICT; CREATE FUNCTION plpgsql_profiler_reset_all() RETURNS void AS 'MODULE_PATHNAME','plpgsql_profiler_reset_all' LANGUAGE C STRICT; CREATE FUNCTION plpgsql_profiler_reset(funcoid regprocedure) RETURNS void AS 'MODULE_PATHNAME','plpgsql_profiler_reset' LANGUAGE C STRICT; CREATE OR REPLACE FUNCTION plpgsql_coverage_statements(funcoid regprocedure) RETURNS double precision AS 'MODULE_PATHNAME', 'plpgsql_coverage_statements' LANGUAGE C; CREATE OR REPLACE FUNCTION plpgsql_coverage_statements(name text) RETURNS double precision AS 'MODULE_PATHNAME', 'plpgsql_coverage_statements_name' LANGUAGE C; CREATE OR REPLACE FUNCTION plpgsql_coverage_branches(funcoid regprocedure) RETURNS double precision AS 'MODULE_PATHNAME', 'plpgsql_coverage_branches' LANGUAGE C; CREATE OR REPLACE FUNCTION plpgsql_coverage_branches(name text) RETURNS double precision AS 'MODULE_PATHNAME', 'plpgsql_coverage_branches_name' LANGUAGE C; CREATE OR REPLACE FUNCTION plpgsql_check_pragma(VARIADIC name text[]) RETURNS integer AS 'MODULE_PATHNAME', 'plpgsql_check_pragma' LANGUAGE C VOLATILE; CREATE OR REPLACE FUNCTION plpgsql_profiler_functions_all() RETURNS TABLE(funcoid regprocedure, exec_count int8, exec_stmts_err int8, total_time double precision, avg_time double precision, stddev_time double precision, min_time double precision, max_time double precision) AS 'MODULE_PATHNAME','plpgsql_profiler_functions_all_tb' LANGUAGE C STRICT; CREATE OR REPLACE FUNCTION plpgsql_check_profiler(enable boolean DEFAULT NULL) RETURNS boolean AS 'MODULE_PATHNAME', 'plpgsql_check_profiler_ctrl' LANGUAGE C VOLATILE; CREATE OR REPLACE FUNCTION plpgsql_check_tracer(enable boolean DEFAULT NULL, verbosity text DEFAULT NULL) RETURNS boolean AS 'MODULE_PATHNAME', 'plpgsql_check_tracer_ctrl' LANGUAGE C VOLATILE;plpgsql_check-2.7.8/plpgsql_check.control000066400000000000000000000002661465410532300205770ustar00rootroot00000000000000# plpgsql_check extension comment = 'extended check for plpgsql functions' default_version = '2.7' module_pathname = '$libdir/plpgsql_check' relocatable = false requires = 'plpgsql' plpgsql_check-2.7.8/postgresql12-plpgsql_check.spec000066400000000000000000000141431465410532300224140ustar00rootroot00000000000000%global pgmajorversion 12 %global pginstdir /usr/pgsql-12 %global sname plpgsql_check Name: %{sname}_%{pgmajorversion} Version: 2.7.8 Release: 1%{?dist} Summary: Additional tools for plpgsql functions validation Group: Applications/Databases License: BSD URL: https://github.com/okbob/plpgsql_check/archive/v%{version}.zip Source0: plpgsql_check-%{version}.zip BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) BuildRequires: postgresql%{pgmajorversion}-devel Requires: postgresql%{pgmajorversion} %description The plpgsql_check is PostgreSQL extension with functionality for direct or indirect extra validation of functions in plpgsql language. It verifies a validity of SQL identifiers used in plpgsql code. It try to identify a performance issues. %prep %setup -q -n %{sname}-%{version} %build PATH="%{pginstdir}/bin;$PATH" ; export PATH CFLAGS="${CFLAGS:-%optflags}" ; export CFLAGS make USE_PGXS=1 PG_CONFIG=%{pginstdir}/bin/pg_config %{?_smp_mflags} %install rm -rf %{buildroot} make install DESTDIR=%{buildroot} PG_CONFIG=%{pginstdir}/bin/pg_config %{?_smp_mflags} %clean rm -rf %{buildroot} %files %defattr(644,root,root,755) %doc README.md %{pginstdir}/lib/plpgsql_check.so %{pginstdir}/share/extension/plpgsql_check--2.1.sql %{pginstdir}/share/extension/plpgsql_check.control %{pginstdir}/lib/bitcode/*.bc %{pginstdir}/lib/bitcode/plpgsql_check/src/*.bc %{pginstdir}/share/extension/*.control %changelog * Wed Dec 6 2023 - Pavel Stehule 2.7.0 - unclosed cursors detection * Tue Oct 31 2023 - Pavel Stehule 2.6.0 - simple constant tracing support * Sat Apr 29 2023 - Pavel Stehule 2.4.0 - remove support for PostgreSQL 10 and 11 * Wed Jan 11 2023 - Pavel Stehule 2.3.0 - possibility to detect compatibility issues (obsolete setting of refcursor) * Tue Sep 20 2022 - Pavel Stehule 2.2.0 - possibility to use in comment options * Wed Dec 29 2021 - Pavel Stehule 2.1.0 - possibility to count statement's aborted execution - possibility to count "unfinished" statements due exception * Mon Sep 27 2021 - Pavel Stehule 2.0.0 - pragma type for setting type to record variable - pragma table for creating ephemeral table * Mon Jun 21 2021 - Pavel Stehule 1.17.0 - remove support for PostgreSQL 9.5 and 9.6 * Sat Mar 6 2021 - Pavel Stehule 1.16.0 - plpgsql_profiler_functions_all * Mon Nov 16 2020 - Pavel Stehule 1.14.0 - queryid can be displayed in profiler's reports (Julien Rouhaud) - new profiler's GUC plpgsql_check.profiler_max_shared_chunks (Julien Rouhaud) - few minor bugfixes * Fri Aug 14 2020 - Pavel Stehule 1.13.0 - tracer - pragma support to control checks, warnings and tracing * Thu Jul 2 2020 - Pavel STEHULE 1.11.0 - possibility to check functions with arguments of polymorphic type - possibility to specify type used as real type instead polymorphic type * Fri Jun 05 2020 - Pavel STEHULE 1.10.0 - deduction record type structure from result of polymorphic function * Mon Apr 27 2020 - Pavel STEHULE 1.9.1 - minor bugfixes * Mon Mar 30 2020 - Pavel STEHULE 1.9.0 - statement and branch coverage metrics - remove support for Postgres 9.4 * Mon Jan 06 2020 - Pavel STEHULE 1.8.2 - fix of compilation issue * Sun Jan 05 2020 - Pavel STEHULE 1.8.1 - cleaner detection function oid from name or signature * Sun Dec 29 2019 - Pavel STEHULE 1.8.0 - use Postgres tool for calling functions from plpgsql library instead dynamic linking - it solve issues related to dependency plpgsq_check on plpgsql * Mon Sep 23 2019 - Pavel STEHULE 1.7.6 - fix false alarm - multiple plans in EXECUTE statement, and possible crash * Tue Sep 10 2019 - Pavel STEHULE 1.7.5 - allow some work on tables with rules * Wed Jul 24 2019 - Pavel STEHULE 1.7.3 - profiler bugfixes * Tue May 21 2019 - Pavel STEHULE 1.7.2 - profiler bugfixes * Fri Apr 26 2019 - Pavel STEHULE 1.7.1 - bugfixes * Wed Apr 17 2019 - Pavel STEHULE 1.7.0 - check of format of fmt string of "format" function - better check of dynamic SQL when it is const string - check of SQL injection vulnerability of stmt expression at EXECUTE stmt * Sun Dec 23 2018 - Pavel STEHULE 1.4.2-1 - metada fix * Fri Dec 21 2018 - Pavel STEHULE 1.4.1-1 - minor bugfixes * Sun Dec 2 2018 - Pavel STEHULE 1.4.0-1 - possible to show function's dependency on functions and tables - integrated profiler - bug fixes (almost false alarms) * Wed Jun 6 2018 - Pavel STEHULE 1.2.3-1 - PostgreSQL 11 support - detect hidden casts in expressions * Thu Oct 26 2017 - Pavel STEHULE 1.2.2-1 - never read variables detection - fix false alarm on MOVE command * Fri Sep 15 2017 - Pavel STEHULE 1.2.1-1 - missing RETURN detection - fix some bugs and false alarms - PostgreSQL 11 support * Fri Nov 11 2016 - Pavel STEHULE 1.2.0-1 - support extra warnings - shadowed variables * Thu Aug 25 2016 - Pavel STEHULE 1.0.5-1 - minor fixes, support for PostgreSQL 10 * Fri Apr 15 2016 - Pavel STEHULE 1.0.4-1 - support for PostgreSQL 9.6 * Mon Oct 12 2015 - Pavel STEHULE 1.0.3-1 - fix false alarms of unused cursor variables - fix regress tests * Thu Jul 09 2015 - Pavel STEHULE 1.0.2-2 - bugfix release * Fri Dec 19 2014 - Pavel STEHULE 0.9.3-1 - fix a broken record field type checking - add check for assign to array field * Mon Aug 25 2014 - Pavel STEHULE 0.9.1-1 - Initial packaging plpgsql_check-2.7.8/postgresql13-plpgsql_check.spec000066400000000000000000000141431465410532300224150ustar00rootroot00000000000000%global pgmajorversion 13 %global pginstdir /usr/pgsql-13 %global sname plpgsql_check Name: %{sname}_%{pgmajorversion} Version: 2.7.8 Release: 1%{?dist} Summary: Additional tools for plpgsql functions validation Group: Applications/Databases License: BSD URL: https://github.com/okbob/plpgsql_check/archive/v%{version}.zip Source0: plpgsql_check-%{version}.zip BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) BuildRequires: postgresql%{pgmajorversion}-devel Requires: postgresql%{pgmajorversion} %description The plpgsql_check is PostgreSQL extension with functionality for direct or indirect extra validation of functions in plpgsql language. It verifies a validity of SQL identifiers used in plpgsql code. It try to identify a performance issues. %prep %setup -q -n %{sname}-%{version} %build PATH="%{pginstdir}/bin;$PATH" ; export PATH CFLAGS="${CFLAGS:-%optflags}" ; export CFLAGS make USE_PGXS=1 PG_CONFIG=%{pginstdir}/bin/pg_config %{?_smp_mflags} %install rm -rf %{buildroot} make install DESTDIR=%{buildroot} PG_CONFIG=%{pginstdir}/bin/pg_config %{?_smp_mflags} %clean rm -rf %{buildroot} %files %defattr(644,root,root,755) %doc README.md %{pginstdir}/lib/plpgsql_check.so %{pginstdir}/share/extension/plpgsql_check--2.1.sql %{pginstdir}/share/extension/plpgsql_check.control %{pginstdir}/lib/bitcode/*.bc %{pginstdir}/lib/bitcode/plpgsql_check/src/*.bc %{pginstdir}/share/extension/*.control %changelog * Wed Dec 6 2023 - Pavel Stehule 2.7.0 - unclosed cursors detection * Tue Oct 31 2023 - Pavel Stehule 2.6.0 - simple constant tracing support * Sat Apr 29 2023 - Pavel Stehule 2.4.0 - remove support for PostgreSQL 10 and 11 * Wed Jan 11 2023 - Pavel Stehule 2.3.0 - possibility to detect compatibility issues (obsolete setting of refcursor) * Tue Sep 20 2022 - Pavel Stehule 2.2.0 - possibility to use in comment options * Wed Dec 29 2021 - Pavel Stehule 2.1.0 - possibility to count statement's aborted execution - possibility to count "unfinished" statements due exception * Mon Sep 27 2021 - Pavel Stehule 2.0.0 - pragma type for setting type to record variable - pragma table for creating ephemeral table * Mon Jun 21 2021 - Pavel Stehule 1.17.0 - remove support for PostgreSQL 9.5 and 9.6 * Sat Mar 6 2021 - Pavel Stehule 1.16.0 - plpgsql_profiler_functions_all * Mon Nov 16 2020 - Pavel Stehule 1.14.0 - queryid can be displayed in profiler's reports (Julien Rouhaud) - new profiler's GUC plpgsql_check.profiler_max_shared_chunks (Julien Rouhaud) - few minor bugfixes * Fri Aug 14 2020 - Pavel Stehule 1.13.0 - tracer - pragma support to control checks, warnings and tracing * Thu Jul 2 2020 - Pavel STEHULE 1.11.0 - possibility to check functions with arguments of polymorphic type - possibility to specify type used as real type instead polymorphic type * Fri Jun 05 2020 - Pavel STEHULE 1.10.0 - deduction record type structure from result of polymorphic function * Mon Apr 27 2020 - Pavel STEHULE 1.9.1 - minor bugfixes * Mon Mar 30 2020 - Pavel STEHULE 1.9.0 - statement and branch coverage metrics - remove support for Postgres 9.4 * Mon Jan 06 2020 - Pavel STEHULE 1.8.2 - fix of compilation issue * Sun Jan 05 2020 - Pavel STEHULE 1.8.1 - cleaner detection function oid from name or signature * Sun Dec 29 2019 - Pavel STEHULE 1.8.0 - use Postgres tool for calling functions from plpgsql library instead dynamic linking - it solve issues related to dependency plpgsq_check on plpgsql * Mon Sep 23 2019 - Pavel STEHULE 1.7.6 - fix false alarm - multiple plans in EXECUTE statement, and possible crash * Tue Sep 10 2019 - Pavel STEHULE 1.7.5 - allow some work on tables with rules * Wed Jul 24 2019 - Pavel STEHULE 1.7.3 - profiler bugfixes * Tue May 21 2019 - Pavel STEHULE 1.7.2 - profiler bugfixes * Fri Apr 26 2019 - Pavel STEHULE 1.7.1 - bugfixes * Wed Apr 17 2019 - Pavel STEHULE 1.7.0 - check of format of fmt string of "format" function - better check of dynamic SQL when it is const string - check of SQL injection vulnerability of stmt expression at EXECUTE stmt * Sun Dec 23 2018 - Pavel STEHULE 1.4.2-1 - metada fix * Fri Dec 21 2018 - Pavel STEHULE 1.4.1-1 - minor bugfixes * Sun Dec 2 2018 - Pavel STEHULE 1.4.0-1 - possible to show function's dependency on functions and tables - integrated profiler - bug fixes (almost false alarms) * Wed Jun 6 2018 - Pavel STEHULE 1.2.3-1 - PostgreSQL 11 support - detect hidden casts in expressions * Thu Oct 26 2017 - Pavel STEHULE 1.2.2-1 - never read variables detection - fix false alarm on MOVE command * Fri Sep 15 2017 - Pavel STEHULE 1.2.1-1 - missing RETURN detection - fix some bugs and false alarms - PostgreSQL 11 support * Fri Nov 11 2016 - Pavel STEHULE 1.2.0-1 - support extra warnings - shadowed variables * Thu Aug 25 2016 - Pavel STEHULE 1.0.5-1 - minor fixes, support for PostgreSQL 10 * Fri Apr 15 2016 - Pavel STEHULE 1.0.4-1 - support for PostgreSQL 9.6 * Mon Oct 12 2015 - Pavel STEHULE 1.0.3-1 - fix false alarms of unused cursor variables - fix regress tests * Thu Jul 09 2015 - Pavel STEHULE 1.0.2-2 - bugfix release * Fri Dec 19 2014 - Pavel STEHULE 0.9.3-1 - fix a broken record field type checking - add check for assign to array field * Mon Aug 25 2014 - Pavel STEHULE 0.9.1-1 - Initial packaging plpgsql_check-2.7.8/postgresql14-plpgsql_check.spec000066400000000000000000000141431465410532300224160ustar00rootroot00000000000000%global pgmajorversion 14 %global pginstdir /usr/pgsql-14 %global sname plpgsql_check Name: %{sname}_%{pgmajorversion} Version: 2.7.8 Release: 1%{?dist} Summary: Additional tools for plpgsql functions validation Group: Applications/Databases License: BSD URL: https://github.com/okbob/plpgsql_check/archive/v%{version}.zip Source0: plpgsql_check-%{version}.zip BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) BuildRequires: postgresql%{pgmajorversion}-devel Requires: postgresql%{pgmajorversion} %description The plpgsql_check is PostgreSQL extension with functionality for direct or indirect extra validation of functions in plpgsql language. It verifies a validity of SQL identifiers used in plpgsql code. It try to identify a performance issues. %prep %setup -q -n %{sname}-%{version} %build PATH="%{pginstdir}/bin;$PATH" ; export PATH CFLAGS="${CFLAGS:-%optflags}" ; export CFLAGS make USE_PGXS=1 PG_CONFIG=%{pginstdir}/bin/pg_config %{?_smp_mflags} %install rm -rf %{buildroot} make install DESTDIR=%{buildroot} PG_CONFIG=%{pginstdir}/bin/pg_config %{?_smp_mflags} %clean rm -rf %{buildroot} %files %defattr(644,root,root,755) %doc README.md %{pginstdir}/lib/plpgsql_check.so %{pginstdir}/share/extension/plpgsql_check--2.1.sql %{pginstdir}/share/extension/plpgsql_check.control %{pginstdir}/lib/bitcode/*.bc %{pginstdir}/lib/bitcode/plpgsql_check/src/*.bc %{pginstdir}/share/extension/*.control %changelog * Wed Dec 6 2023 - Pavel Stehule 2.7.0 - unclosed cursors detection * Tue Oct 31 2023 - Pavel Stehule 2.6.0 - simple constant tracing support * Sat Apr 29 2023 - Pavel Stehule 2.4.0 - remove support for PostgreSQL 10 and 11 * Wed Jan 11 2023 - Pavel Stehule 2.3.0 - possibility to detect compatibility issues (obsolete setting of refcursor) * Tue Sep 20 2022 - Pavel Stehule 2.2.0 - possibility to use in comment options * Wed Dec 29 2021 - Pavel Stehule 2.1.0 - possibility to count statement's aborted execution - possibility to count "unfinished" statements due exception * Mon Sep 27 2021 - Pavel Stehule 2.0.0 - pragma type for setting type to record variable - pragma table for creating ephemeral table * Mon Jun 21 2021 - Pavel Stehule 1.17.0 - remove support for PostgreSQL 9.5 and 9.6 * Sat Mar 6 2021 - Pavel Stehule 1.16.0 - plpgsql_profiler_functions_all * Mon Nov 16 2020 - Pavel Stehule 1.14.0 - queryid can be displayed in profiler's reports (Julien Rouhaud) - new profiler's GUC plpgsql_check.profiler_max_shared_chunks (Julien Rouhaud) - few minor bugfixes * Fri Aug 14 2020 - Pavel Stehule 1.13.0 - tracer - pragma support to control checks, warnings and tracing * Thu Jul 2 2020 - Pavel STEHULE 1.11.0 - possibility to check functions with arguments of polymorphic type - possibility to specify type used as real type instead polymorphic type * Fri Jun 05 2020 - Pavel STEHULE 1.10.0 - deduction record type structure from result of polymorphic function * Mon Apr 27 2020 - Pavel STEHULE 1.9.1 - minor bugfixes * Mon Mar 30 2020 - Pavel STEHULE 1.9.0 - statement and branch coverage metrics - remove support for Postgres 9.4 * Mon Jan 06 2020 - Pavel STEHULE 1.8.2 - fix of compilation issue * Sun Jan 05 2020 - Pavel STEHULE 1.8.1 - cleaner detection function oid from name or signature * Sun Dec 29 2019 - Pavel STEHULE 1.8.0 - use Postgres tool for calling functions from plpgsql library instead dynamic linking - it solve issues related to dependency plpgsq_check on plpgsql * Mon Sep 23 2019 - Pavel STEHULE 1.7.6 - fix false alarm - multiple plans in EXECUTE statement, and possible crash * Tue Sep 10 2019 - Pavel STEHULE 1.7.5 - allow some work on tables with rules * Wed Jul 24 2019 - Pavel STEHULE 1.7.3 - profiler bugfixes * Tue May 21 2019 - Pavel STEHULE 1.7.2 - profiler bugfixes * Fri Apr 26 2019 - Pavel STEHULE 1.7.1 - bugfixes * Wed Apr 17 2019 - Pavel STEHULE 1.7.0 - check of format of fmt string of "format" function - better check of dynamic SQL when it is const string - check of SQL injection vulnerability of stmt expression at EXECUTE stmt * Sun Dec 23 2018 - Pavel STEHULE 1.4.2-1 - metada fix * Fri Dec 21 2018 - Pavel STEHULE 1.4.1-1 - minor bugfixes * Sun Dec 2 2018 - Pavel STEHULE 1.4.0-1 - possible to show function's dependency on functions and tables - integrated profiler - bug fixes (almost false alarms) * Wed Jun 6 2018 - Pavel STEHULE 1.2.3-1 - PostgreSQL 11 support - detect hidden casts in expressions * Thu Oct 26 2017 - Pavel STEHULE 1.2.2-1 - never read variables detection - fix false alarm on MOVE command * Fri Sep 15 2017 - Pavel STEHULE 1.2.1-1 - missing RETURN detection - fix some bugs and false alarms - PostgreSQL 11 support * Fri Nov 11 2016 - Pavel STEHULE 1.2.0-1 - support extra warnings - shadowed variables * Thu Aug 25 2016 - Pavel STEHULE 1.0.5-1 - minor fixes, support for PostgreSQL 10 * Fri Apr 15 2016 - Pavel STEHULE 1.0.4-1 - support for PostgreSQL 9.6 * Mon Oct 12 2015 - Pavel STEHULE 1.0.3-1 - fix false alarms of unused cursor variables - fix regress tests * Thu Jul 09 2015 - Pavel STEHULE 1.0.2-2 - bugfix release * Fri Dec 19 2014 - Pavel STEHULE 0.9.3-1 - fix a broken record field type checking - add check for assign to array field * Mon Aug 25 2014 - Pavel STEHULE 0.9.1-1 - Initial packaging plpgsql_check-2.7.8/postgresql15-plpgsql_check.spec000066400000000000000000000141431465410532300224170ustar00rootroot00000000000000%global pgmajorversion 15 %global pginstdir /usr/pgsql-15 %global sname plpgsql_check Name: %{sname}_%{pgmajorversion} Version: 2.7.8 Release: 1%{?dist} Summary: Additional tools for plpgsql functions validation Group: Applications/Databases License: BSD URL: https://github.com/okbob/plpgsql_check/archive/v%{version}.zip Source0: plpgsql_check-%{version}.zip BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) BuildRequires: postgresql%{pgmajorversion}-devel Requires: postgresql%{pgmajorversion} %description The plpgsql_check is PostgreSQL extension with functionality for direct or indirect extra validation of functions in plpgsql language. It verifies a validity of SQL identifiers used in plpgsql code. It try to identify a performance issues. %prep %setup -q -n %{sname}-%{version} %build PATH="%{pginstdir}/bin;$PATH" ; export PATH CFLAGS="${CFLAGS:-%optflags}" ; export CFLAGS make USE_PGXS=1 PG_CONFIG=%{pginstdir}/bin/pg_config %{?_smp_mflags} %install rm -rf %{buildroot} make install DESTDIR=%{buildroot} PG_CONFIG=%{pginstdir}/bin/pg_config %{?_smp_mflags} %clean rm -rf %{buildroot} %files %defattr(644,root,root,755) %doc README.md %{pginstdir}/lib/plpgsql_check.so %{pginstdir}/share/extension/plpgsql_check--2.1.sql %{pginstdir}/share/extension/plpgsql_check.control %{pginstdir}/lib/bitcode/*.bc %{pginstdir}/lib/bitcode/plpgsql_check/src/*.bc %{pginstdir}/share/extension/*.control %changelog * Wed Dec 6 2023 - Pavel Stehule 2.7.0 - unclosed cursors detection * Tue Oct 31 2023 - Pavel Stehule 2.6.0 - simple constant tracing support * Sat Apr 29 2023 - Pavel Stehule 2.4.0 - remove support for PostgreSQL 10 and 11 * Wed Jan 11 2023 - Pavel Stehule 2.3.0 - possibility to detect compatibility issues (obsolete setting of refcursor) * Tue Sep 20 2022 - Pavel Stehule 2.2.0 - possibility to use in comment options * Wed Dec 29 2021 - Pavel Stehule 2.1.0 - possibility to count statement's aborted execution - possibility to count "unfinished" statements due exception * Mon Sep 27 2021 - Pavel Stehule 2.0.0 - pragma type for setting type to record variable - pragma table for creating ephemeral table * Mon Jun 21 2021 - Pavel Stehule 1.17.0 - remove support for PostgreSQL 9.5 and 9.6 * Sat Mar 6 2021 - Pavel Stehule 1.16.0 - plpgsql_profiler_functions_all * Mon Nov 16 2020 - Pavel Stehule 1.14.0 - queryid can be displayed in profiler's reports (Julien Rouhaud) - new profiler's GUC plpgsql_check.profiler_max_shared_chunks (Julien Rouhaud) - few minor bugfixes * Fri Aug 14 2020 - Pavel Stehule 1.13.0 - tracer - pragma support to control checks, warnings and tracing * Thu Jul 2 2020 - Pavel STEHULE 1.11.0 - possibility to check functions with arguments of polymorphic type - possibility to specify type used as real type instead polymorphic type * Fri Jun 05 2020 - Pavel STEHULE 1.10.0 - deduction record type structure from result of polymorphic function * Mon Apr 27 2020 - Pavel STEHULE 1.9.1 - minor bugfixes * Mon Mar 30 2020 - Pavel STEHULE 1.9.0 - statement and branch coverage metrics - remove support for Postgres 9.4 * Mon Jan 06 2020 - Pavel STEHULE 1.8.2 - fix of compilation issue * Sun Jan 05 2020 - Pavel STEHULE 1.8.1 - cleaner detection function oid from name or signature * Sun Dec 29 2019 - Pavel STEHULE 1.8.0 - use Postgres tool for calling functions from plpgsql library instead dynamic linking - it solve issues related to dependency plpgsq_check on plpgsql * Mon Sep 23 2019 - Pavel STEHULE 1.7.6 - fix false alarm - multiple plans in EXECUTE statement, and possible crash * Tue Sep 10 2019 - Pavel STEHULE 1.7.5 - allow some work on tables with rules * Wed Jul 24 2019 - Pavel STEHULE 1.7.3 - profiler bugfixes * Tue May 21 2019 - Pavel STEHULE 1.7.2 - profiler bugfixes * Fri Apr 26 2019 - Pavel STEHULE 1.7.1 - bugfixes * Wed Apr 17 2019 - Pavel STEHULE 1.7.0 - check of format of fmt string of "format" function - better check of dynamic SQL when it is const string - check of SQL injection vulnerability of stmt expression at EXECUTE stmt * Sun Dec 23 2018 - Pavel STEHULE 1.4.2-1 - metada fix * Fri Dec 21 2018 - Pavel STEHULE 1.4.1-1 - minor bugfixes * Sun Dec 2 2018 - Pavel STEHULE 1.4.0-1 - possible to show function's dependency on functions and tables - integrated profiler - bug fixes (almost false alarms) * Wed Jun 6 2018 - Pavel STEHULE 1.2.3-1 - PostgreSQL 11 support - detect hidden casts in expressions * Thu Oct 26 2017 - Pavel STEHULE 1.2.2-1 - never read variables detection - fix false alarm on MOVE command * Fri Sep 15 2017 - Pavel STEHULE 1.2.1-1 - missing RETURN detection - fix some bugs and false alarms - PostgreSQL 11 support * Fri Nov 11 2016 - Pavel STEHULE 1.2.0-1 - support extra warnings - shadowed variables * Thu Aug 25 2016 - Pavel STEHULE 1.0.5-1 - minor fixes, support for PostgreSQL 10 * Fri Apr 15 2016 - Pavel STEHULE 1.0.4-1 - support for PostgreSQL 9.6 * Mon Oct 12 2015 - Pavel STEHULE 1.0.3-1 - fix false alarms of unused cursor variables - fix regress tests * Thu Jul 09 2015 - Pavel STEHULE 1.0.2-2 - bugfix release * Fri Dec 19 2014 - Pavel STEHULE 0.9.3-1 - fix a broken record field type checking - add check for assign to array field * Mon Aug 25 2014 - Pavel STEHULE 0.9.1-1 - Initial packaging plpgsql_check-2.7.8/postgresql16-plpgsql_check.spec000066400000000000000000000141431465410532300224200ustar00rootroot00000000000000%global pgmajorversion 16 %global pginstdir /usr/pgsql-16 %global sname plpgsql_check Name: %{sname}_%{pgmajorversion} Version: 2.7.8 Release: 1%{?dist} Summary: Additional tools for plpgsql functions validation Group: Applications/Databases License: BSD URL: https://github.com/okbob/plpgsql_check/archive/v%{version}.zip Source0: plpgsql_check-%{version}.zip BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) BuildRequires: postgresql%{pgmajorversion}-devel Requires: postgresql%{pgmajorversion} %description The plpgsql_check is PostgreSQL extension with functionality for direct or indirect extra validation of functions in plpgsql language. It verifies a validity of SQL identifiers used in plpgsql code. It try to identify a performance issues. %prep %setup -q -n %{sname}-%{version} %build PATH="%{pginstdir}/bin;$PATH" ; export PATH CFLAGS="${CFLAGS:-%optflags}" ; export CFLAGS make USE_PGXS=1 PG_CONFIG=%{pginstdir}/bin/pg_config %{?_smp_mflags} %install rm -rf %{buildroot} make install DESTDIR=%{buildroot} PG_CONFIG=%{pginstdir}/bin/pg_config %{?_smp_mflags} %clean rm -rf %{buildroot} %files %defattr(644,root,root,755) %doc README.md %{pginstdir}/lib/plpgsql_check.so %{pginstdir}/share/extension/plpgsql_check--2.1.sql %{pginstdir}/share/extension/plpgsql_check.control %{pginstdir}/lib/bitcode/*.bc %{pginstdir}/lib/bitcode/plpgsql_check/src/*.bc %{pginstdir}/share/extension/*.control %changelog * Wed Dec 6 2023 - Pavel Stehule 2.7.0 - unclosed cursors detection * Tue Oct 31 2023 - Pavel Stehule 2.6.0 - simple constant tracing support * Sat Apr 29 2023 - Pavel Stehule 2.4.0 - remove support for PostgreSQL 10 and 11 * Wed Jan 11 2023 - Pavel Stehule 2.3.0 - possibility to detect compatibility issues (obsolete setting of refcursor) * Tue Sep 20 2022 - Pavel Stehule 2.2.0 - possibility to use in comment options * Wed Dec 29 2021 - Pavel Stehule 2.1.0 - possibility to count statement's aborted execution - possibility to count "unfinished" statements due exception * Mon Sep 27 2021 - Pavel Stehule 2.0.0 - pragma type for setting type to record variable - pragma table for creating ephemeral table * Mon Jun 21 2021 - Pavel Stehule 1.17.0 - remove support for PostgreSQL 9.5 and 9.6 * Sat Mar 6 2021 - Pavel Stehule 1.16.0 - plpgsql_profiler_functions_all * Mon Nov 16 2020 - Pavel Stehule 1.14.0 - queryid can be displayed in profiler's reports (Julien Rouhaud) - new profiler's GUC plpgsql_check.profiler_max_shared_chunks (Julien Rouhaud) - few minor bugfixes * Fri Aug 14 2020 - Pavel Stehule 1.13.0 - tracer - pragma support to control checks, warnings and tracing * Thu Jul 2 2020 - Pavel STEHULE 1.11.0 - possibility to check functions with arguments of polymorphic type - possibility to specify type used as real type instead polymorphic type * Fri Jun 05 2020 - Pavel STEHULE 1.10.0 - deduction record type structure from result of polymorphic function * Mon Apr 27 2020 - Pavel STEHULE 1.9.1 - minor bugfixes * Mon Mar 30 2020 - Pavel STEHULE 1.9.0 - statement and branch coverage metrics - remove support for Postgres 9.4 * Mon Jan 06 2020 - Pavel STEHULE 1.8.2 - fix of compilation issue * Sun Jan 05 2020 - Pavel STEHULE 1.8.1 - cleaner detection function oid from name or signature * Sun Dec 29 2019 - Pavel STEHULE 1.8.0 - use Postgres tool for calling functions from plpgsql library instead dynamic linking - it solve issues related to dependency plpgsq_check on plpgsql * Mon Sep 23 2019 - Pavel STEHULE 1.7.6 - fix false alarm - multiple plans in EXECUTE statement, and possible crash * Tue Sep 10 2019 - Pavel STEHULE 1.7.5 - allow some work on tables with rules * Wed Jul 24 2019 - Pavel STEHULE 1.7.3 - profiler bugfixes * Tue May 21 2019 - Pavel STEHULE 1.7.2 - profiler bugfixes * Fri Apr 26 2019 - Pavel STEHULE 1.7.1 - bugfixes * Wed Apr 17 2019 - Pavel STEHULE 1.7.0 - check of format of fmt string of "format" function - better check of dynamic SQL when it is const string - check of SQL injection vulnerability of stmt expression at EXECUTE stmt * Sun Dec 23 2018 - Pavel STEHULE 1.4.2-1 - metada fix * Fri Dec 21 2018 - Pavel STEHULE 1.4.1-1 - minor bugfixes * Sun Dec 2 2018 - Pavel STEHULE 1.4.0-1 - possible to show function's dependency on functions and tables - integrated profiler - bug fixes (almost false alarms) * Wed Jun 6 2018 - Pavel STEHULE 1.2.3-1 - PostgreSQL 11 support - detect hidden casts in expressions * Thu Oct 26 2017 - Pavel STEHULE 1.2.2-1 - never read variables detection - fix false alarm on MOVE command * Fri Sep 15 2017 - Pavel STEHULE 1.2.1-1 - missing RETURN detection - fix some bugs and false alarms - PostgreSQL 11 support * Fri Nov 11 2016 - Pavel STEHULE 1.2.0-1 - support extra warnings - shadowed variables * Thu Aug 25 2016 - Pavel STEHULE 1.0.5-1 - minor fixes, support for PostgreSQL 10 * Fri Apr 15 2016 - Pavel STEHULE 1.0.4-1 - support for PostgreSQL 9.6 * Mon Oct 12 2015 - Pavel STEHULE 1.0.3-1 - fix false alarms of unused cursor variables - fix regress tests * Thu Jul 09 2015 - Pavel STEHULE 1.0.2-2 - bugfix release * Fri Dec 19 2014 - Pavel STEHULE 0.9.3-1 - fix a broken record field type checking - add check for assign to array field * Mon Aug 25 2014 - Pavel STEHULE 0.9.1-1 - Initial packaging plpgsql_check-2.7.8/postgresql17-plpgsql_check.spec000066400000000000000000000141431465410532300224210ustar00rootroot00000000000000%global pgmajorversion 17 %global pginstdir /usr/pgsql-17 %global sname plpgsql_check Name: %{sname}_%{pgmajorversion} Version: 2.7.8 Release: 1%{?dist} Summary: Additional tools for plpgsql functions validation Group: Applications/Databases License: BSD URL: https://github.com/okbob/plpgsql_check/archive/v%{version}.zip Source0: plpgsql_check-%{version}.zip BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) BuildRequires: postgresql%{pgmajorversion}-devel Requires: postgresql%{pgmajorversion} %description The plpgsql_check is PostgreSQL extension with functionality for direct or indirect extra validation of functions in plpgsql language. It verifies a validity of SQL identifiers used in plpgsql code. It try to identify a performance issues. %prep %setup -q -n %{sname}-%{version} %build PATH="%{pginstdir}/bin;$PATH" ; export PATH CFLAGS="${CFLAGS:-%optflags}" ; export CFLAGS make USE_PGXS=1 PG_CONFIG=%{pginstdir}/bin/pg_config %{?_smp_mflags} %install rm -rf %{buildroot} make install DESTDIR=%{buildroot} PG_CONFIG=%{pginstdir}/bin/pg_config %{?_smp_mflags} %clean rm -rf %{buildroot} %files %defattr(644,root,root,755) %doc README.md %{pginstdir}/lib/plpgsql_check.so %{pginstdir}/share/extension/plpgsql_check--2.1.sql %{pginstdir}/share/extension/plpgsql_check.control %{pginstdir}/lib/bitcode/*.bc %{pginstdir}/lib/bitcode/plpgsql_check/src/*.bc %{pginstdir}/share/extension/*.control %changelog * Wed Dec 6 2023 - Pavel Stehule 2.7.0 - unclosed cursors detection * Tue Oct 31 2023 - Pavel Stehule 2.6.0 - simple constant tracing support * Sat Apr 29 2023 - Pavel Stehule 2.4.0 - remove support for PostgreSQL 10 and 11 * Wed Jan 11 2023 - Pavel Stehule 2.3.0 - possibility to detect compatibility issues (obsolete setting of refcursor) * Tue Sep 20 2022 - Pavel Stehule 2.2.0 - possibility to use in comment options * Wed Dec 29 2021 - Pavel Stehule 2.1.0 - possibility to count statement's aborted execution - possibility to count "unfinished" statements due exception * Mon Sep 27 2021 - Pavel Stehule 2.0.0 - pragma type for setting type to record variable - pragma table for creating ephemeral table * Mon Jun 21 2021 - Pavel Stehule 1.17.0 - remove support for PostgreSQL 9.5 and 9.6 * Sat Mar 6 2021 - Pavel Stehule 1.16.0 - plpgsql_profiler_functions_all * Mon Nov 16 2020 - Pavel Stehule 1.14.0 - queryid can be displayed in profiler's reports (Julien Rouhaud) - new profiler's GUC plpgsql_check.profiler_max_shared_chunks (Julien Rouhaud) - few minor bugfixes * Fri Aug 14 2020 - Pavel Stehule 1.13.0 - tracer - pragma support to control checks, warnings and tracing * Thu Jul 2 2020 - Pavel STEHULE 1.11.0 - possibility to check functions with arguments of polymorphic type - possibility to specify type used as real type instead polymorphic type * Fri Jun 05 2020 - Pavel STEHULE 1.10.0 - deduction record type structure from result of polymorphic function * Mon Apr 27 2020 - Pavel STEHULE 1.9.1 - minor bugfixes * Mon Mar 30 2020 - Pavel STEHULE 1.9.0 - statement and branch coverage metrics - remove support for Postgres 9.4 * Mon Jan 06 2020 - Pavel STEHULE 1.8.2 - fix of compilation issue * Sun Jan 05 2020 - Pavel STEHULE 1.8.1 - cleaner detection function oid from name or signature * Sun Dec 29 2019 - Pavel STEHULE 1.8.0 - use Postgres tool for calling functions from plpgsql library instead dynamic linking - it solve issues related to dependency plpgsq_check on plpgsql * Mon Sep 23 2019 - Pavel STEHULE 1.7.6 - fix false alarm - multiple plans in EXECUTE statement, and possible crash * Tue Sep 10 2019 - Pavel STEHULE 1.7.5 - allow some work on tables with rules * Wed Jul 24 2019 - Pavel STEHULE 1.7.3 - profiler bugfixes * Tue May 21 2019 - Pavel STEHULE 1.7.2 - profiler bugfixes * Fri Apr 26 2019 - Pavel STEHULE 1.7.1 - bugfixes * Wed Apr 17 2019 - Pavel STEHULE 1.7.0 - check of format of fmt string of "format" function - better check of dynamic SQL when it is const string - check of SQL injection vulnerability of stmt expression at EXECUTE stmt * Sun Dec 23 2018 - Pavel STEHULE 1.4.2-1 - metada fix * Fri Dec 21 2018 - Pavel STEHULE 1.4.1-1 - minor bugfixes * Sun Dec 2 2018 - Pavel STEHULE 1.4.0-1 - possible to show function's dependency on functions and tables - integrated profiler - bug fixes (almost false alarms) * Wed Jun 6 2018 - Pavel STEHULE 1.2.3-1 - PostgreSQL 11 support - detect hidden casts in expressions * Thu Oct 26 2017 - Pavel STEHULE 1.2.2-1 - never read variables detection - fix false alarm on MOVE command * Fri Sep 15 2017 - Pavel STEHULE 1.2.1-1 - missing RETURN detection - fix some bugs and false alarms - PostgreSQL 11 support * Fri Nov 11 2016 - Pavel STEHULE 1.2.0-1 - support extra warnings - shadowed variables * Thu Aug 25 2016 - Pavel STEHULE 1.0.5-1 - minor fixes, support for PostgreSQL 10 * Fri Apr 15 2016 - Pavel STEHULE 1.0.4-1 - support for PostgreSQL 9.6 * Mon Oct 12 2015 - Pavel STEHULE 1.0.3-1 - fix false alarms of unused cursor variables - fix regress tests * Thu Jul 09 2015 - Pavel STEHULE 1.0.2-2 - bugfix release * Fri Dec 19 2014 - Pavel STEHULE 0.9.3-1 - fix a broken record field type checking - add check for assign to array field * Mon Aug 25 2014 - Pavel STEHULE 0.9.1-1 - Initial packaging plpgsql_check-2.7.8/postgresql18-plpgsql_check.spec000066400000000000000000000141431465410532300224220ustar00rootroot00000000000000%global pgmajorversion 18 %global pginstdir /usr/pgsql-18 %global sname plpgsql_check Name: %{sname}_%{pgmajorversion} Version: 2.7.8 Release: 1%{?dist} Summary: Additional tools for plpgsql functions validation Group: Applications/Databases License: BSD URL: https://github.com/okbob/plpgsql_check/archive/v%{version}.zip Source0: plpgsql_check-%{version}.zip BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) BuildRequires: postgresql%{pgmajorversion}-devel Requires: postgresql%{pgmajorversion} %description The plpgsql_check is PostgreSQL extension with functionality for direct or indirect extra validation of functions in plpgsql language. It verifies a validity of SQL identifiers used in plpgsql code. It try to identify a performance issues. %prep %setup -q -n %{sname}-%{version} %build PATH="%{pginstdir}/bin;$PATH" ; export PATH CFLAGS="${CFLAGS:-%optflags}" ; export CFLAGS make USE_PGXS=1 PG_CONFIG=%{pginstdir}/bin/pg_config %{?_smp_mflags} %install rm -rf %{buildroot} make install DESTDIR=%{buildroot} PG_CONFIG=%{pginstdir}/bin/pg_config %{?_smp_mflags} %clean rm -rf %{buildroot} %files %defattr(644,root,root,755) %doc README.md %{pginstdir}/lib/plpgsql_check.so %{pginstdir}/share/extension/plpgsql_check--2.1.sql %{pginstdir}/share/extension/plpgsql_check.control %{pginstdir}/lib/bitcode/*.bc %{pginstdir}/lib/bitcode/plpgsql_check/src/*.bc %{pginstdir}/share/extension/*.control %changelog * Wed Dec 6 2023 - Pavel Stehule 2.7.0 - unclosed cursors detection * Tue Oct 31 2023 - Pavel Stehule 2.6.0 - simple constant tracing support * Sat Apr 29 2023 - Pavel Stehule 2.4.0 - remove support for PostgreSQL 10 and 11 * Wed Jan 11 2023 - Pavel Stehule 2.3.0 - possibility to detect compatibility issues (obsolete setting of refcursor) * Tue Sep 20 2022 - Pavel Stehule 2.2.0 - possibility to use in comment options * Wed Dec 29 2021 - Pavel Stehule 2.1.0 - possibility to count statement's aborted execution - possibility to count "unfinished" statements due exception * Mon Sep 27 2021 - Pavel Stehule 2.0.0 - pragma type for setting type to record variable - pragma table for creating ephemeral table * Mon Jun 21 2021 - Pavel Stehule 1.17.0 - remove support for PostgreSQL 9.5 and 9.6 * Sat Mar 6 2021 - Pavel Stehule 1.16.0 - plpgsql_profiler_functions_all * Mon Nov 16 2020 - Pavel Stehule 1.14.0 - queryid can be displayed in profiler's reports (Julien Rouhaud) - new profiler's GUC plpgsql_check.profiler_max_shared_chunks (Julien Rouhaud) - few minor bugfixes * Fri Aug 14 2020 - Pavel Stehule 1.13.0 - tracer - pragma support to control checks, warnings and tracing * Thu Jul 2 2020 - Pavel STEHULE 1.11.0 - possibility to check functions with arguments of polymorphic type - possibility to specify type used as real type instead polymorphic type * Fri Jun 05 2020 - Pavel STEHULE 1.10.0 - deduction record type structure from result of polymorphic function * Mon Apr 27 2020 - Pavel STEHULE 1.9.1 - minor bugfixes * Mon Mar 30 2020 - Pavel STEHULE 1.9.0 - statement and branch coverage metrics - remove support for Postgres 9.4 * Mon Jan 06 2020 - Pavel STEHULE 1.8.2 - fix of compilation issue * Sun Jan 05 2020 - Pavel STEHULE 1.8.1 - cleaner detection function oid from name or signature * Sun Dec 29 2019 - Pavel STEHULE 1.8.0 - use Postgres tool for calling functions from plpgsql library instead dynamic linking - it solve issues related to dependency plpgsq_check on plpgsql * Mon Sep 23 2019 - Pavel STEHULE 1.7.6 - fix false alarm - multiple plans in EXECUTE statement, and possible crash * Tue Sep 10 2019 - Pavel STEHULE 1.7.5 - allow some work on tables with rules * Wed Jul 24 2019 - Pavel STEHULE 1.7.3 - profiler bugfixes * Tue May 21 2019 - Pavel STEHULE 1.7.2 - profiler bugfixes * Fri Apr 26 2019 - Pavel STEHULE 1.7.1 - bugfixes * Wed Apr 17 2019 - Pavel STEHULE 1.7.0 - check of format of fmt string of "format" function - better check of dynamic SQL when it is const string - check of SQL injection vulnerability of stmt expression at EXECUTE stmt * Sun Dec 23 2018 - Pavel STEHULE 1.4.2-1 - metada fix * Fri Dec 21 2018 - Pavel STEHULE 1.4.1-1 - minor bugfixes * Sun Dec 2 2018 - Pavel STEHULE 1.4.0-1 - possible to show function's dependency on functions and tables - integrated profiler - bug fixes (almost false alarms) * Wed Jun 6 2018 - Pavel STEHULE 1.2.3-1 - PostgreSQL 11 support - detect hidden casts in expressions * Thu Oct 26 2017 - Pavel STEHULE 1.2.2-1 - never read variables detection - fix false alarm on MOVE command * Fri Sep 15 2017 - Pavel STEHULE 1.2.1-1 - missing RETURN detection - fix some bugs and false alarms - PostgreSQL 11 support * Fri Nov 11 2016 - Pavel STEHULE 1.2.0-1 - support extra warnings - shadowed variables * Thu Aug 25 2016 - Pavel STEHULE 1.0.5-1 - minor fixes, support for PostgreSQL 10 * Fri Apr 15 2016 - Pavel STEHULE 1.0.4-1 - support for PostgreSQL 9.6 * Mon Oct 12 2015 - Pavel STEHULE 1.0.3-1 - fix false alarms of unused cursor variables - fix regress tests * Thu Jul 09 2015 - Pavel STEHULE 1.0.2-2 - bugfix release * Fri Dec 19 2014 - Pavel STEHULE 0.9.3-1 - fix a broken record field type checking - add check for assign to array field * Mon Aug 25 2014 - Pavel STEHULE 0.9.1-1 - Initial packaging plpgsql_check-2.7.8/sql/000077500000000000000000000000001465410532300151515ustar00rootroot00000000000000plpgsql_check-2.7.8/sql/plpgsql_check_active-12.sql000066400000000000000000000000001465410532300222520ustar00rootroot00000000000000plpgsql_check-2.7.8/sql/plpgsql_check_active-13.sql000066400000000000000000000000001465410532300222530ustar00rootroot00000000000000plpgsql_check-2.7.8/sql/plpgsql_check_active-14.sql000066400000000000000000000000001465410532300222540ustar00rootroot00000000000000plpgsql_check-2.7.8/sql/plpgsql_check_active-15.sql000066400000000000000000000035711465410532300222750ustar00rootroot00000000000000LOAD 'plpgsql'; CREATE EXTENSION IF NOT EXISTS plpgsql_check; set client_min_messages to notice; create or replace function fxtest() returns void as $$ declare v_sqlstate text; v_message text; v_context text; begin get stacked diagnostics v_sqlstate = returned_sqlstate, v_message = message_text, v_context = pg_exception_context; end; $$ language plpgsql; select * from plpgsql_check_function('fxtest'); drop function fxtest(); create or replace procedure prtest() as $$ begin commit; end; $$ language plpgsql; select * from plpgsql_check_function('prtest'); --ok create or replace procedure prtest() as $$ begin begin begin commit; end; end; exception when others then raise; end; $$ language plpgsql; select * from plpgsql_check_function('prtest'); --error create or replace procedure prtest() as $$ begin raise exception 'error'; exception when others then begin begin commit; end; end; end; $$ language plpgsql; select * from plpgsql_check_function('prtest'); --ok drop procedure prtest(); create function return_constant_refcursor() returns refcursor as $$ declare rc constant refcursor; begin open rc for select a from rc_test; return rc; end $$ language plpgsql; create table rc_test(a int); select * from plpgsql_check_function('return_constant_refcursor'); drop table rc_test; drop function return_constant_refcursor(); create procedure p1(a int, out b int) as $$ begin b := a + 10; end; $$ language plpgsql; create function f1() returns void as $$ declare b constant int; begin call p1(10, b); end; $$ language plpgsql; select * from plpgsql_check_function('f1'); drop function f1(); drop procedure p1(int, int); create or replace function f1() returns int as $$ declare c constant int default 100; begin return c; end; $$ language plpgsql; -- should be ok select * from plpgsql_check_function('f1'); plpgsql_check-2.7.8/sql/plpgsql_check_active-16.sql000066400000000000000000000056671465410532300223060ustar00rootroot00000000000000LOAD 'plpgsql'; CREATE EXTENSION IF NOT EXISTS plpgsql_check; set client_min_messages to notice; create or replace function fxtest() returns void as $$ declare v_sqlstate text; v_message text; v_context text; begin get stacked diagnostics v_sqlstate = returned_sqlstate, v_message = message_text, v_context = pg_exception_context; end; $$ language plpgsql; select * from plpgsql_check_function('fxtest'); drop function fxtest(); create or replace procedure prtest() as $$ begin commit; end; $$ language plpgsql; select * from plpgsql_check_function('prtest'); --ok create or replace procedure prtest() as $$ begin begin begin commit; end; end; exception when others then raise; end; $$ language plpgsql; select * from plpgsql_check_function('prtest'); --error create or replace procedure prtest() as $$ begin raise exception 'error'; exception when others then begin begin commit; end; end; end; $$ language plpgsql; select * from plpgsql_check_function('prtest'); --ok drop procedure prtest(); create function return_constant_refcursor() returns refcursor as $$ declare rc constant refcursor; begin open rc for select a from rc_test; return rc; end $$ language plpgsql; create table rc_test(a int); select * from plpgsql_check_function('return_constant_refcursor'); drop table rc_test; drop function return_constant_refcursor(); create procedure p1(a int, out b int) as $$ begin b := a + 10; end; $$ language plpgsql; create function f1() returns void as $$ declare b constant int; begin call p1(10, b); end; $$ language plpgsql; select * from plpgsql_check_function('f1'); drop function f1(); drop procedure p1(int, int); create or replace function f1() returns int as $$ declare c constant int default 100; begin return c; end; $$ language plpgsql; -- should be ok select * from plpgsql_check_function('f1'); drop function f1(); -- do not raise false warning create or replace function test_function() returns text as $$ declare s text; begin get diagnostics s = PG_CONTEXT; return s; end; $$ language plpgsql; create or replace procedure test_procedure() as $$ begin null; end; $$ language plpgsql; -- should be without any warnings select * from plpgsql_check_function('test_function', performance_warnings=>true); select * from plpgsql_check_function('test_procedure', performance_warnings=>true); drop function test_function(); drop procedure test_procedure(); -- detect dependecy in CALL statement create or replace function fx1_dep(int) returns int as $$ begin return $1; end; $$ language plpgsql; create or replace procedure px1_dep(int) as $$ begin end; $$ language plpgsql; create or replace function test_function() returns void as $$ begin call px1_dep(fx1_dep(10)); end; $$ language plpgsql; select type, schema, name, params from plpgsql_show_dependency_tb('test_function'); drop function test_function(); drop procedure px1_dep(int); drop function fx1_dep(int);plpgsql_check-2.7.8/sql/plpgsql_check_active-17.sql000066400000000000000000000056671465410532300223070ustar00rootroot00000000000000LOAD 'plpgsql'; CREATE EXTENSION IF NOT EXISTS plpgsql_check; set client_min_messages to notice; create or replace function fxtest() returns void as $$ declare v_sqlstate text; v_message text; v_context text; begin get stacked diagnostics v_sqlstate = returned_sqlstate, v_message = message_text, v_context = pg_exception_context; end; $$ language plpgsql; select * from plpgsql_check_function('fxtest'); drop function fxtest(); create or replace procedure prtest() as $$ begin commit; end; $$ language plpgsql; select * from plpgsql_check_function('prtest'); --ok create or replace procedure prtest() as $$ begin begin begin commit; end; end; exception when others then raise; end; $$ language plpgsql; select * from plpgsql_check_function('prtest'); --error create or replace procedure prtest() as $$ begin raise exception 'error'; exception when others then begin begin commit; end; end; end; $$ language plpgsql; select * from plpgsql_check_function('prtest'); --ok drop procedure prtest(); create function return_constant_refcursor() returns refcursor as $$ declare rc constant refcursor; begin open rc for select a from rc_test; return rc; end $$ language plpgsql; create table rc_test(a int); select * from plpgsql_check_function('return_constant_refcursor'); drop table rc_test; drop function return_constant_refcursor(); create procedure p1(a int, out b int) as $$ begin b := a + 10; end; $$ language plpgsql; create function f1() returns void as $$ declare b constant int; begin call p1(10, b); end; $$ language plpgsql; select * from plpgsql_check_function('f1'); drop function f1(); drop procedure p1(int, int); create or replace function f1() returns int as $$ declare c constant int default 100; begin return c; end; $$ language plpgsql; -- should be ok select * from plpgsql_check_function('f1'); drop function f1(); -- do not raise false warning create or replace function test_function() returns text as $$ declare s text; begin get diagnostics s = PG_CONTEXT; return s; end; $$ language plpgsql; create or replace procedure test_procedure() as $$ begin null; end; $$ language plpgsql; -- should be without any warnings select * from plpgsql_check_function('test_function', performance_warnings=>true); select * from plpgsql_check_function('test_procedure', performance_warnings=>true); drop function test_function(); drop procedure test_procedure(); -- detect dependecy in CALL statement create or replace function fx1_dep(int) returns int as $$ begin return $1; end; $$ language plpgsql; create or replace procedure px1_dep(int) as $$ begin end; $$ language plpgsql; create or replace function test_function() returns void as $$ begin call px1_dep(fx1_dep(10)); end; $$ language plpgsql; select type, schema, name, params from plpgsql_show_dependency_tb('test_function'); drop function test_function(); drop procedure px1_dep(int); drop function fx1_dep(int);plpgsql_check-2.7.8/sql/plpgsql_check_active-18.sql000066400000000000000000000056671465410532300223100ustar00rootroot00000000000000LOAD 'plpgsql'; CREATE EXTENSION IF NOT EXISTS plpgsql_check; set client_min_messages to notice; create or replace function fxtest() returns void as $$ declare v_sqlstate text; v_message text; v_context text; begin get stacked diagnostics v_sqlstate = returned_sqlstate, v_message = message_text, v_context = pg_exception_context; end; $$ language plpgsql; select * from plpgsql_check_function('fxtest'); drop function fxtest(); create or replace procedure prtest() as $$ begin commit; end; $$ language plpgsql; select * from plpgsql_check_function('prtest'); --ok create or replace procedure prtest() as $$ begin begin begin commit; end; end; exception when others then raise; end; $$ language plpgsql; select * from plpgsql_check_function('prtest'); --error create or replace procedure prtest() as $$ begin raise exception 'error'; exception when others then begin begin commit; end; end; end; $$ language plpgsql; select * from plpgsql_check_function('prtest'); --ok drop procedure prtest(); create function return_constant_refcursor() returns refcursor as $$ declare rc constant refcursor; begin open rc for select a from rc_test; return rc; end $$ language plpgsql; create table rc_test(a int); select * from plpgsql_check_function('return_constant_refcursor'); drop table rc_test; drop function return_constant_refcursor(); create procedure p1(a int, out b int) as $$ begin b := a + 10; end; $$ language plpgsql; create function f1() returns void as $$ declare b constant int; begin call p1(10, b); end; $$ language plpgsql; select * from plpgsql_check_function('f1'); drop function f1(); drop procedure p1(int, int); create or replace function f1() returns int as $$ declare c constant int default 100; begin return c; end; $$ language plpgsql; -- should be ok select * from plpgsql_check_function('f1'); drop function f1(); -- do not raise false warning create or replace function test_function() returns text as $$ declare s text; begin get diagnostics s = PG_CONTEXT; return s; end; $$ language plpgsql; create or replace procedure test_procedure() as $$ begin null; end; $$ language plpgsql; -- should be without any warnings select * from plpgsql_check_function('test_function', performance_warnings=>true); select * from plpgsql_check_function('test_procedure', performance_warnings=>true); drop function test_function(); drop procedure test_procedure(); -- detect dependecy in CALL statement create or replace function fx1_dep(int) returns int as $$ begin return $1; end; $$ language plpgsql; create or replace procedure px1_dep(int) as $$ begin end; $$ language plpgsql; create or replace function test_function() returns void as $$ begin call px1_dep(fx1_dep(10)); end; $$ language plpgsql; select type, schema, name, params from plpgsql_show_dependency_tb('test_function'); drop function test_function(); drop procedure px1_dep(int); drop function fx1_dep(int);plpgsql_check-2.7.8/sql/plpgsql_check_active.sql000066400000000000000000003323031465410532300220500ustar00rootroot00000000000000load 'plpgsql'; create extension if not exists plpgsql_check; set client_min_messages to notice; set plpgsql_check.regress_test_mode = true; -- -- check function statement tests -- --should fail - is not plpgsql select * from plpgsql_check_function_tb('session_user()'); create table t1(a int, b int); create table pa (id int, pa_id character varying(32), status character varying(60)); create table ml(ml_id character varying(32), status_from character varying(60), pa_id character varying(32), xyz int); create function f1() returns void as $$ begin if false then update t1 set c = 30; end if; if false then raise notice '%', r.c; end if; end; $$ language plpgsql; select f1(); select * from plpgsql_check_function_tb('f1()', fatal_errors := true); select * from plpgsql_check_function_tb('f1()', fatal_errors := false); select * from plpgsql_check_function_tb('f1()'); drop function f1(); create function f1() returns void as $$ begin if false then insert into t1 values(10,20); update t1 set a = 10; delete from t1; end if; end; $$ language plpgsql stable; select f1(); select * from plpgsql_check_function_tb('f1()', fatal_errors := false); drop function f1(); -- profiler check set plpgsql_check.profiler to on; create function f1() returns void as $$ begin if false then insert into t1 values(10,20); update t1 set a = 10; delete from t1; end if; end; $$ language plpgsql; select lineno, stmt_lineno, exec_stmts, source from plpgsql_profiler_function_tb('f1()'); select f1(); select lineno, stmt_lineno, exec_stmts, source from plpgsql_profiler_function_tb('f1()'); select plpgsql_profiler_reset('f1()'); select lineno, stmt_lineno, exec_stmts, source from plpgsql_profiler_function_tb('f1()'); select f1(); select lineno, stmt_lineno, exec_stmts, source from plpgsql_profiler_function_tb('f1()'); select plpgsql_profiler_reset_all(); select lineno, stmt_lineno, exec_stmts, source from plpgsql_profiler_function_tb('f1()'); drop function f1(); create or replace function f1() returns void as $$ begin raise notice '1'; exception when others then raise notice '2'; end; $$ language plpgsql; select f1(); select lineno, stmt_lineno, exec_stmts, source from plpgsql_profiler_function_tb('f1()'); drop function f1(); -- test queryid retrieval create function f1() returns void as $$ declare t1 text = 't1'; begin insert into t1 values(10,20); EXECUTE 'update ' || 't1' || ' set a = 10'; EXECUTE 'delete from ' || t1; end; $$ language plpgsql; select plpgsql_profiler_reset_all(); select plpgsql_profiler_install_fake_queryid_hook(); select f1(); select queryids, lineno, stmt_lineno, exec_stmts, source from plpgsql_profiler_function_tb('f1()'); select plpgsql_profiler_remove_fake_queryid_hook(); drop function f1(); set plpgsql_check.profiler to off; create function f1() returns void as $$ declare r record; begin if false then for r in update t1 set a = a + 1 returning * loop raise notice '%', r.a; end loop; end if; end; $$ language plpgsql; select f1(); select * from plpgsql_check_function_tb('f1()', fatal_errors := false); drop function f1(); create function f1() returns void as $$ declare r record; begin if false then for r in update t1 set a = a + 1 returning * loop raise notice '%', r.a; end loop; end if; end; $$ language plpgsql stable; select f1(); select * from plpgsql_check_function_tb('f1()', fatal_errors := false); drop function f1(); create function g1(out a int, out b int) as $$ select 10,20; $$ language sql; create function f1() returns void as $$ declare r record; begin r := g1(); if false then raise notice '%', r.c; end if; end; $$ language plpgsql; select f1(); select * from plpgsql_check_function_tb('f1()'); select f1(); drop function f1(); drop function g1(); create function g1(out a int, out b int) returns setof record as $$ select * from t1; $$ language sql; create function f1() returns void as $$ declare r record; begin for r in select * from g1() loop raise notice '%', r.c; end loop; end; $$ language plpgsql; select f1(); select * from plpgsql_check_function_tb('f1()'); select f1(); create or replace function f1() returns void as $$ declare r record; begin for r in select * from g1() loop r.c := 20; end loop; end; $$ language plpgsql; select f1(); select * from plpgsql_check_function_tb('f1()'); select f1(); drop function f1(); drop function g1(); create function f1() returns int as $$ declare r int; begin if false then r := a + b; end if; return r; end; $$ language plpgsql; select f1(); select * from plpgsql_check_function_tb('f1()'); select f1(); drop function f1(); create or replace function f1() returns void as $$ declare r int[]; begin if false then r[c+10] := 20; end if; end; $$ language plpgsql; select f1(); select * from plpgsql_check_function_tb('f1()'); select f1(); drop function f1(); create or replace function f1() returns void as $$ declare r int; begin if false then r[10] := 20; end if; end; $$ language plpgsql set search_path = public; select f1(); select * from plpgsql_check_function_tb('f1()'); select f1(); drop function f1(); create or replace function f1_trg() returns trigger as $$ begin if new.a > 10 then raise notice '%', new.b; raise notice '%', new.c; end if; return new; end; $$ language plpgsql; create trigger t1_f1 before insert on t1 for each row execute procedure f1_trg(); insert into t1 values(6,30); select * from plpgsql_check_function_tb('f1_trg()','t1'); insert into t1 values(6,30); create or replace function f1_trg() returns trigger as $$ begin new.a := new.a + 10; new.b := new.b + 10; new.c := 30; return new; end; $$ language plpgsql; -- should to fail select * from plpgsql_check_function_tb('f1_trg()','t1'); -- should to fail but not crash insert into t1 values(6,30); create or replace function f1_trg() returns trigger as $$ begin new.a := new.a + 10; new.b := new.b + 10; return new; end; $$ language plpgsql; -- ok select * from plpgsql_check_function_tb('f1_trg()', 't1'); -- ok insert into t1 values(6,30); create or replace function f1_trg() returns trigger as $$ begin new.a := new.a + 10; new.b := new.b + 10; return null; end; $$ language plpgsql; -- ok select * from plpgsql_check_function_tb('f1_trg()', 't1'); insert into t1 values(60,300); select * from t1; insert into t1 values(600,30); select * from t1; drop trigger t1_f1 on t1; drop function f1_trg(); -- test of showing caret on correct place for multiline queries create or replace function f1() returns void as $$ begin select var from foo; end; $$ language plpgsql; select * from plpgsql_check_function_tb('f1()'); drop function f1(); create or replace function f1() returns int as $$ begin return (select a from t1 where hh = 20); end; $$ language plpgsql; select * from plpgsql_check_function_tb('f1()'); create or replace function f1() returns int as $$ begin return (select a from txxxxxxx where hh = 20); end; $$ language plpgsql; select * from plpgsql_check_function_tb('f1()'); drop function f1(); drop table t1; -- raise warnings when target row has different number of attributies in -- SELECT INTO statement create or replace function f1() returns void as $$ declare a1 int; a2 int; begin select 10,20 into a1,a2; end; $$ language plpgsql; -- should be ok select * from plpgsql_check_function_tb('f1()'); create or replace function f1() returns void as $$ declare a1 int; begin select 10,20 into a1; end; $$ language plpgsql; -- raise warning select * from plpgsql_check_function_tb('f1()'); create or replace function f1() returns void as $$ declare a1 int; a2 int; begin select 10 into a1,a2; end; $$ language plpgsql; -- raise warning select * from plpgsql_check_function_tb('f1()'); -- bogus code set check_function_bodies to off; create or replace function f1() returns void as $$ adasdfsadf $$ language plpgsql; select * from plpgsql_check_function_tb('f1()'); drop function f1(); create table t1(a int, b int); create function g1(out a int, out b int) as $$ select 10,20; $$ language sql; create function f1() returns void as $$ declare r record; begin r := g1(); if false then raise notice '%', r.c; end if; end; $$ language plpgsql; select f1(); select * from plpgsql_check_function('f1()'); select f1(); drop function f1(); drop function g1(); create function g1(out a int, out b int) returns setof record as $$ select * from t1; $$ language sql; create function f1() returns void as $$ declare r record; begin for r in select * from g1() loop raise notice '%', r.c; end loop; end; $$ language plpgsql; select f1(); select * from plpgsql_check_function('f1()'); select f1(); create or replace function f1() returns void as $$ declare r record; begin for r in select * from g1() loop r.c := 20; end loop; end; $$ language plpgsql; select f1(); select * from plpgsql_check_function('f1()'); select f1(); drop function f1(); drop function g1(); create function f1() returns int as $$ declare r int; begin if false then r := a + b; end if; return r; end; $$ language plpgsql; select f1(); select * from plpgsql_check_function('f1()'); select f1(); drop function f1(); create or replace function f1() returns void as $$ declare r int[]; begin if false then r[c+10] := 20; end if; end; $$ language plpgsql; select f1(); select * from plpgsql_check_function('f1()'); select f1(); drop function f1(); create or replace function f1() returns void as $$ declare r int; begin if false then r[10] := 20; end if; end; $$ language plpgsql set search_path = public; select f1(); select * from plpgsql_check_function('f1()'); select f1(); drop function f1(); create or replace function f1_trg() returns trigger as $$ begin if new.a > 10 then raise notice '%', new.b; raise notice '%', new.c; end if; return new; end; $$ language plpgsql; create trigger t1_f1 before insert on t1 for each row execute procedure f1_trg(); insert into t1 values(6,30); select * from plpgsql_check_function('f1_trg()','t1'); insert into t1 values(6,30); create or replace function f1_trg() returns trigger as $$ begin new.a := new.a + 10; new.b := new.b + 10; new.c := 30; return new; end; $$ language plpgsql; -- should to fail select * from plpgsql_check_function('f1_trg()','t1'); -- should to fail but not crash insert into t1 values(6,30); create or replace function f1_trg() returns trigger as $$ begin new.a := new.a + 10; new.b := new.b + 10; return new; end; $$ language plpgsql; -- ok select * from plpgsql_check_function('f1_trg()', 't1'); -- ok insert into t1 values(6,30); select * from t1; drop trigger t1_f1 on t1; drop function f1_trg(); -- test of showing caret on correct place for multiline queries create or replace function f1() returns void as $$ begin select var from foo; end; $$ language plpgsql; select * from plpgsql_check_function('f1()'); drop function f1(); create or replace function f1() returns int as $$ begin return (select a from t1 where hh = 20); end; $$ language plpgsql; select * from plpgsql_check_function('f1()'); create or replace function f1() returns int as $$ begin return (select a from txxxxxxx where hh = 20); end; $$ language plpgsql; select * from plpgsql_check_function('f1()'); drop function f1(); drop table t1; -- raise warnings when target row has different number of attributies in -- SELECT INTO statement create or replace function f1() returns void as $$ declare a1 int; a2 int; begin select 10,20 into a1,a2; end; $$ language plpgsql; -- should be ok select * from plpgsql_check_function('f1()'); create or replace function f1() returns void as $$ declare a1 int; begin select 10,20 into a1; end; $$ language plpgsql; -- raise warning select * from plpgsql_check_function('f1()'); create or replace function f1() returns void as $$ declare a1 int; a2 int; begin select 10 into a1,a2; end; $$ language plpgsql; -- raise warning select * from plpgsql_check_function('f1()'); -- bogus code set check_function_bodies to off; create or replace function f1() returns void as $$ adasdfsadf $$ language plpgsql; select * from plpgsql_check_function('f1()'); drop function f1(); create table f1tbl(a int, b int); -- unused variables create or replace function f1(_input1 int) returns table(_output1 int, _output2 int) as $$ declare _f1 int; _f2 int; _f3 int; _f4 int; _f5 int; _r record; _tbl f1tbl; begin if true then _f1 := 1; end if; select 1, 2 into _f3, _f4; perform 1 where _f5 is null; select 1 into _r; select 1, 2 into _tbl; -- check that SQLSTATE and SQLERRM don't raise false positives begin exception when raise_exception then end; end $$ language plpgsql; select * from plpgsql_check_function('f1(int)'); drop function f1(int); drop table f1tbl; -- check that NEW and OLD are not reported unused create table f1tbl(); create or replace function f1() returns trigger as $$ begin return null; end $$ language plpgsql; select * from plpgsql_check_function('f1()', 'f1tbl'); drop function f1(); drop table f1tbl; create table tabret(a int, b int); insert into tabret values(10,10); create or replace function f1() returns int as $$ begin return (select a from tabret); end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); create or replace function f1() returns int as $$ begin return (select a::numeric from tabret); end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); create or replace function f1() returns int as $$ begin return (select a, b from tabret); end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); drop function f1(); create or replace function f1() returns table(ax int, bx int) as $$ begin return query select * from tabret; return; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); drop function f1(); create or replace function f1() returns table(ax numeric, bx numeric) as $$ begin return query select * from tabret; return; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); drop function f1(); create or replace function f1() returns setof tabret as $$ begin return query select * from tabret; return; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); create or replace function f1() returns setof tabret as $$ begin return query select a from tabret; return; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); create or replace function f1() returns setof tabret as $$ begin return query select a::numeric,b::numeric from tabret; return; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); drop function f1(); create or replace function f1(a int) returns setof numeric as $$ begin return query select a; end $$ language plpgsql; select * from plpgsql_check_function('f1(int)', performance_warnings := true); drop function f1(int); drop table tabret; create or replace function f1() returns void as $$ declare intval integer; begin intval := null; -- ok intval := 1; -- OK intval := '1'; -- OK intval := text '1'; -- not OK intval := current_date; -- not OK select 1 into intval; -- OK select '1' into intval; -- OK select text '1' into intval; -- not OK end $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); drop function f1(); create or replace function f1() returns int as $$ begin return 1; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); create or replace function f1() returns int as $$ begin return 1::numeric; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); create or replace function f1() returns int as $$ begin return null; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); create or replace function f1() returns int as $$ begin return current_date; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); create or replace function f1() returns int as $$ declare a int; begin return a; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); create or replace function f1() returns int as $$ declare a numeric; begin return a; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); drop function f1(); create or replace function f1() returns setof int as $$ begin return next 1; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); create or replace function f1() returns setof int as $$ begin return next 1::numeric; -- tolerant, doesn't use tupmap end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings := true); drop function f1(); create type t1 as (a int, b int, c int); create type t2 as (a int, b numeric); create or replace function fx() returns t2 as $$ declare x t1; begin return x; end; $$ language plpgsql; select * from plpgsql_check_function('fx()', performance_warnings := true); create or replace function fx() returns t2 as $$ declare x t2; begin return x; end; $$ language plpgsql; select * from plpgsql_check_function('fx()', performance_warnings := true); drop function fx(); create or replace function fx() returns setof t2 as $$ declare x t1; begin return next x; return; end; $$ language plpgsql; select * from plpgsql_check_function('fx()', performance_warnings := true); create or replace function fx() returns setof t2 as $$ declare x t2; begin return next x; return; end; $$ language plpgsql; select * from plpgsql_check_function('fx()', performance_warnings := true); drop function fx(); create or replace function fx2(_id int, _pa_id varchar(32), _status varchar(60)) returns void as $$ declare begin insert into pa values(_id, _pa_id, _status); exception when OTHERS then raise notice '%', 'some message'; raise exception '%', sqlerrm; end $$ language plpgsql; select * from plpgsql_check_function('fx2(int, varchar, varchar)', performance_warnings := true); create or replace function fx2(_id int, _pa_id varchar(32), _status varchar(60)) returns void as $$ declare begin insert into pa values(_id, _pa_id, _status) returning *; exception when OTHERS then raise notice '%', 'some message'; raise exception '%', sqlerrm; end $$ language plpgsql; select * from plpgsql_check_function('fx2(int, varchar, varchar)', performance_warnings := true); create or replace function fx2(_id int, _pa_id varchar(32), _status varchar(60)) returns void as $$ declare begin SELECT * FROM pa LIMIT 1; exception when OTHERS then raise notice '%', 'some message'; raise exception '%', sqlerrm; end $$ language plpgsql; select * from plpgsql_check_function('fx2(int, varchar, varchar)', performance_warnings := true); drop function fx2(int, varchar, varchar); create or replace function foreach_array_loop() returns void as $body$ declare arr text[]; el text; begin arr := array['1111','2222','3333']; foreach el in array arr loop raise notice '%', el; end loop; end; $body$ language 'plpgsql' stable; select * from plpgsql_check_function_tb('foreach_array_loop()', performance_warnings := true); create or replace function foreach_array_loop() returns void as $body$ declare arr text[]; el int; begin arr := array['1111','2222','3333']; foreach el in array arr loop raise notice '%', el; end loop; end; $body$ language 'plpgsql' stable; select * from plpgsql_check_function_tb('foreach_array_loop()', performance_warnings := true); create or replace function foreach_array_loop() returns void as $body$ declare arr date[]; el int; begin arr := array['2014-01-01','2015-01-01','2016-01-01']::date[]; foreach el in array arr loop raise notice '%', el; end loop; end; $body$ language 'plpgsql' stable; select * from plpgsql_check_function_tb('foreach_array_loop()', performance_warnings := true); create or replace function foreach_array_loop() returns void as $body$ declare el text; begin foreach el in array array['1111','2222','3333'] loop raise notice '%', el; end loop; end; $body$ language 'plpgsql' stable; select * from plpgsql_check_function_tb('foreach_array_loop()', performance_warnings := true); create or replace function foreach_array_loop() returns void as $body$ declare el int; begin foreach el in array array['1111','2222','3333'] loop raise notice '%', el; end loop; end; $body$ language 'plpgsql' stable; select * from plpgsql_check_function_tb('foreach_array_loop()', performance_warnings := true); create or replace function foreach_array_loop() returns void as $body$ declare el int; begin foreach el in array array['2014-01-01','2015-01-01','2016-01-01']::date[] loop raise notice '%', el; end loop; end; $body$ language 'plpgsql' stable; select * from plpgsql_check_function_tb('foreach_array_loop()', performance_warnings := true); drop function foreach_array_loop(); create or replace function scan_rows(int[]) returns void AS $$ declare x int[]; begin foreach x slice 1 in array $1 loop raise notice 'row = %', x; end loop; end; $$ language plpgsql; select * from plpgsql_check_function_tb('scan_rows(int[])', performance_warnings := true); create or replace function scan_rows(int[]) returns void AS $$ declare x int[]; begin foreach x in array $1 loop raise notice 'row = %', x; end loop; end; $$ language plpgsql; select * from plpgsql_check_function_tb('scan_rows(int[])', performance_warnings := true); drop function scan_rows(int[]); drop function fx(); drop type t1; drop type t2; create table t1(a int, b int); create table t2(a int, b int, c int); create table t3(a numeric, b int); insert into t1 values(10,20),(30,40); create or replace function fx() returns int as $$ declare s int default 0; r t1; begin foreach r in array (select array_agg(t1) from t1) loop s := r.a + r.b; end loop; return s; end; $$ language plpgsql; select * from plpgsql_check_function_tb('fx()', performance_warnings := true); create or replace function fx() returns int as $$ declare s int default 0; r t1; c t1[]; begin c := (select array_agg(t1) from t1); foreach r in array c loop s := r.a + r.b; end loop; return s; end; $$ language plpgsql; select * from plpgsql_check_function_tb('fx()', performance_warnings := true); create or replace function fx() returns int as $$ declare s int default 0; r t1; c t1[]; begin select array_agg(t1) into c from t1; foreach r in array c loop s := r.a + r.b; end loop; return s; end; $$ language plpgsql; select * from plpgsql_check_function_tb('fx()', performance_warnings := true); create or replace function fx() returns int as $$ declare s int default 0; r t1; c t1[]; begin select array_agg(t1) into c from t1; for i in array_lower(c, 1) .. array_upper(c, 1) loop r := c[i]; s := r.a + r.b; end loop; return s; end; $$ language plpgsql; select * from plpgsql_check_function_tb('fx()', performance_warnings := true); create or replace function fx() returns int as $$ declare s int default 0; c t1[]; begin select array_agg(t1) into c from t1; for i in array_lower(c, 1) .. array_upper(c, 1) loop s := (c[i]).a + (c[i]).b; end loop; return s; end; $$ language plpgsql; select * from plpgsql_check_function_tb('fx()', performance_warnings := true); create or replace function fx() returns int as $$ declare s int default 0; r record; c t1[]; begin select array_agg(t1) into c from t1; for i in array_lower(c, 1) .. array_upper(c, 1) loop r := c[i]; s := r.a + r.b; end loop; return s; end; $$ language plpgsql; select * from plpgsql_check_function_tb('fx()', performance_warnings := true); create or replace function fx() returns int as $$ declare s int default 0; r record; c t1[]; begin select array_agg(t1) into c from t1; for i in array_lower(c, 1) .. array_upper(c, 1) loop r := c[i]; s := r.a + r.b + r.c; end loop; return s; end; $$ language plpgsql; select * from plpgsql_check_function_tb('fx()', performance_warnings := true); create or replace function fx() returns int as $$ declare s int default 0; r t2; begin foreach r in array (select array_agg(t1) from t1) loop s := r.a + r.b; end loop; return s; end; $$ language plpgsql; select * from plpgsql_check_function_tb('fx()', performance_warnings := true); create or replace function fx() returns int as $$ declare s int default 0; r t3; begin foreach r in array (select array_agg(t1) from t1) loop s := r.a + r.b; end loop; return s; end; $$ language plpgsql; select * from plpgsql_check_function_tb('fx()', performance_warnings := true); drop function fx(); drop table t1; -- mscottie issue #13 create table test ( a text, b integer, c uuid ); create function before_insert_test() returns trigger language plpgsql as $$ begin select a into NEW.a from test where b = 1; select b into NEW.b from test where b = 1; select null::uuid into NEW.c from test where b = 1; return new; end; $$; select * from plpgsql_check_function_tb('before_insert_test()','test'); create or replace function before_insert_test() returns trigger language plpgsql as $$ begin NEW.a := (select a from test where b = 1); NEW.b := (select b from test where b = 1); NEW.c := (select c from test where b = 1); return new; end; $$; select * from plpgsql_check_function_tb('before_insert_test()','test', fatal_errors := false); create or replace function before_insert_test() returns trigger language plpgsql as $$ begin NEW.a := 'Hello'::text; NEW.b := 10; NEW.c := null::uuid; return new; end; $$; select * from plpgsql_check_function_tb('before_insert_test()','test', fatal_errors := false); drop function before_insert_test(); create or replace function fx() returns void as $$ declare NEW test; OLD test; begin select null::uuid into NEW.c from test where b = 1; end; $$ language plpgsql; select * from plpgsql_check_function_tb('fx()', performance_warnings := true, fatal_errors := false); drop function fx(); create or replace function fx() returns void as $$ declare NEW test; begin NEW.a := 'Hello'::text; NEW.b := 10; NEW.c := null::uuid; end; $$ language plpgsql; select * from plpgsql_check_function_tb('fx()', performance_warnings := true, fatal_errors := false); drop function fx(); drop table test; create or replace function fx() returns void as $$ declare s int; sa int[]; sd date; bs int[]; begin sa[10] := s; sa[10] := sd; s := bs[10]; end; $$ language plpgsql; select * from plpgsql_check_function_tb('fx()', performance_warnings := true, fatal_errors := false); drop function fx(); create type t as (t text); create or replace function fx() returns void as $$ declare _t t; _tt t[]; _txt text; begin _t.t := 'ABC'; -- correct warning "unknown" _tt[1] := _t; _txt := _t; end; $$ language plpgsql; select * from plpgsql_check_function('fx()', performance_warnings := true); drop function fx(); create or replace function fx() returns void as $$ declare _t1 t; _t2 t; begin _t1.t := 'ABC'::text; _t2 := _t1; raise notice '% %', _t2, _t2.t; end; $$ language plpgsql; select * from plpgsql_check_function('fx()', performance_warnings := true); drop function fx(); create or replace function fx(out _tt t[]) as $$ declare _t t; begin _t.t := 'ABC'::text; _tt[1] := _t; end; $$ language plpgsql; select * from plpgsql_check_function('fx()', performance_warnings := true); drop function fx(); drop type t; create or replace function fx() returns int as $$ declare x int; begin perform 1; return 10; end; $$ language plpgsql; select * from plpgsql_check_function('fx()', performance_warnings := true); drop function fx(); create table t(i int); create function test_t(OUT t) returns t AS $$ begin $1 := null; end; $$ language plpgsql; select test_t(); select * from test_t(); select * from plpgsql_check_function('test_t()', performance_warnings := true); create or replace function fx() returns void as $$ declare c cursor for select * from t; x varchar; begin open c; fetch c into x; close c; end; $$ language plpgsql; select test_t(); select * from test_t(); select * from plpgsql_check_function('fx()', performance_warnings := true, fatal_errors := false); drop function fx(); create or replace function fx() returns void as $$ declare c cursor for select * from t; x int; begin open c; fetch c into x; close c; end; $$ language plpgsql; select test_t(); select * from test_t(); select * from plpgsql_check_function('fx()', performance_warnings := true, fatal_errors := false); drop function fx(); create or replace function fx() returns void as $$ declare c cursor for select * from t; begin for r in c loop raise notice '%', r.a; end loop; end; $$ language plpgsql; select test_t(); select * from test_t(); select * from plpgsql_check_function('fx()', performance_warnings := true, fatal_errors := false); drop function fx(); create or replace function fx() returns void as $$ declare c cursor for select * from t; begin for r in c loop raise notice '%', r.i; end loop; end; $$ language plpgsql; select test_t(); select * from test_t(); select * from plpgsql_check_function('fx()', performance_warnings := true, fatal_errors := false); drop function fx(); create table foo(a int, b int); create or replace function fx() returns void as $$ declare f1 int; f2 int; begin select 1, 2 into f1; select 1 into f1, f2; select a b into f1, f2 from foo; end; $$ language plpgsql; select fx(); select * from plpgsql_check_function('fx()', performance_warnings := true, fatal_errors := false); drop function fx(); drop table foo; create or replace function fx() returns void as $$ declare d date; begin d := (select 1 from pg_class limit 1); raise notice '%', d; end; $$ language plpgsql; select fx(); select * from plpgsql_check_function('fx()', performance_warnings := true, fatal_errors := false); drop function fx(); create table tab_1(i int); create or replace function fx(a int) returns setof int as $$ declare c refcursor; r record; begin open c for select i from tab_1 where i = a; loop fetch c into r; if not found then exit; end if; return next r.i; end loop; end; $$ language plpgsql; select * from plpgsql_check_function('fx(int)', performance_warnings := true, fatal_errors := false); create or replace function fx(a int) returns setof int as $$ declare c refcursor; r record; begin open c for select i from tab_1 where i = a; loop fetch c into r; if not found then exit; end if; return next r.x; end loop; end; $$ language plpgsql; select * from plpgsql_check_function('fx(int)', performance_warnings := true, fatal_errors := false); drop function fx(int); drop table tab_1; create or replace function fxx() returns void as $$ begin rollback; end; $$ language plpgsql; select fxx(); select * from plpgsql_check_function('fxx()'); drop function fxx(); create or replace function fxx() returns void as $$ declare x int; begin declare x int; begin end; end; $$ language plpgsql; select * from plpgsql_check_function('fxx()'); select * from plpgsql_check_function('fxx()', extra_warnings := false); drop function fxx(); create or replace function fxx(in a int, in b int, out c int, out d int) as $$ begin c := a; end; $$ language plpgsql; select * from plpgsql_check_function('fxx(int, int)'); create or replace function fxx(in a int, in b int, out c int, out d int) as $$ begin c := d; end; $$ language plpgsql; select * from plpgsql_check_function('fxx(int, int)'); create type ct as (a int, b int); create or replace function fxx(a ct, b ct, OUT c ct, OUT d ct) as $$ begin c.a := a.a; end; $$ language plpgsql; select * from plpgsql_check_function('fxx(ct, ct)'); create or replace function fxx(a ct, b ct, OUT c ct, OUT d ct) as $$ begin c.a := d.a; end; $$ language plpgsql; select * from plpgsql_check_function('fxx(ct, ct)'); create or replace function tx(a int) returns int as $$ declare a int; ax int; begin declare ax int; begin ax := 10; end; a := 10; return 20; end; $$ language plpgsql; select * from plpgsql_check_function('tx(int)'); create type xt as (a int, b int, c int); create or replace function fx_xt(out x xt) as $$ declare l xt; a int; begin return; end; $$ language plpgsql; select * from plpgsql_check_function('fx_xt()'); drop function fx_xt(); create or replace function fx_xt(out x xt) as $$ declare l xt; a int; begin x.c := 1000; return; end; $$ language plpgsql; select * from plpgsql_check_function('fx_xt()'); drop function fx_xt(); create or replace function fx_xt(out x xt, out y xt) as $$ declare c1 xt; c2 xt; begin return; end; $$ language plpgsql; select * from plpgsql_check_function('fx_xt()'); drop function fx_xt(); create or replace function fx_xt(out x xt, out y xt) as $$ declare c1 xt; c2 xt; begin x.a := 100; y := row(10,20,30); return; end; $$ language plpgsql; select * from plpgsql_check_function('fx_xt()'); drop function fx_xt(); create or replace function fx_xt(out x xt, out z int) as $$ begin return; end; $$ language plpgsql; select * from plpgsql_check_function('fx_xt()'); drop function fx_xt(); drop type xt; -- missing RETURN create or replace function fx_flow() returns int as $$ begin raise notice 'kuku'; end; $$ language plpgsql; select fx_flow(); select * from plpgsql_check_function('fx_flow()'); -- ok create or replace function fx_flow() returns int as $$ declare a int; begin if a > 10 then return a; end if; return 10; end; $$ language plpgsql; select * from plpgsql_check_function('fx_flow()'); -- dead code create or replace function fx_flow() returns int as $$ declare a int; begin if a > 10 then return a; else return a + 1; end if; return 10; end; $$ language plpgsql; select * from plpgsql_check_function('fx_flow()'); -- missing return create or replace function fx_flow() returns int as $$ declare a int; begin if a > 10 then return a; end if; end; $$ language plpgsql; select * from plpgsql_check_function('fx_flow()'); drop function fx_flow(); create or replace function fx_flow(in p_param1 integer) returns text as $$ declare z1 text; begin if p_param1 is not null then z1 := '1111'; return z1; else z1 := '222222'; end if; return z1; end; $$ language plpgsql stable; select * from plpgsql_check_function_tb('fx_flow(integer)'); create or replace function fx_flow(in p_param1 integer) returns text as $$ declare z1 text; begin if p_param1 is not null then z1 := '1111'; return z1; else z1 := '222222'; raise exception 'stop'; end if; return z1; end; $$ language plpgsql stable; select * from plpgsql_check_function_tb('fx_flow(integer)'); drop function fx_flow(); drop function fx(int); create or replace function fx(x int) returns table(y int) as $$ begin return query select x union select x; end $$ language plpgsql; select * from fx(10); select * from plpgsql_check_function_tb('fx(int)'); drop function fx(int); create or replace function fx(x int) returns table(y int, z int) as $$ begin return query select x,x+1 union select x, x+1; end $$ language plpgsql; select * from fx(10); select * from plpgsql_check_function_tb('fx(int)'); drop function fx(int); create table xx(a int); create or replace function fx(x int) returns int as $$ declare _a int; begin begin select a from xx into strict _a where a = x; return _a; exception when others then null; end; return -1; end; $$ language plpgsql; select * from plpgsql_check_function_tb('fx(int)'); drop table xx; create or replace function fx(x int) returns int as $$ begin begin if (x > 0) then raise exception 'xxx' using errcode = 'XX888'; else raise exception 'yyy' using errcode = 'YY888'; end if; return -1; -- dead code; end; return -1; end; $$ language plpgsql; select * from plpgsql_check_function_tb('fx(int)'); create or replace function fx(x int) returns int as $$ begin begin if (x > 0) then raise exception 'xxx' using errcode = 'XX888'; else raise exception 'yyy' using errcode = 'YY888'; end if; exception when sqlstate 'XX888' then null; when sqlstate 'YY888' then null; end; end; -- missing return; $$ language plpgsql; select * from plpgsql_check_function_tb('fx(int)'); create or replace function fx(x int) returns int as $$ begin begin if (x > 0) then raise exception 'xxx' using errcode = 'XX888'; else raise exception 'yyy' using errcode = 'YY888'; end if; exception when others then return 10; end; end; -- ok now $$ language plpgsql; select * from plpgsql_check_function_tb('fx(int)'); --false alarm reported by Filip Zach create type testtype as (id integer); create or replace function fx() returns testtype as $$ begin return row(1); end; $$ language plpgsql; select * from fx(); select fx(); select * from plpgsql_check_function('fx()'); drop function fx(); create function out1(OUT f1 int, OUT f2 int) returns setof record as $$ begin for f1, f2 in execute $q$ select 1, 2 $q$ loop return next; end loop; end $$ language plpgsql; select * from plpgsql_check_function('out1()'); drop function out1(); create function out1(OUT f1 int, OUT f2 int) returns setof record as $$ begin for f1, f2 in select 1, 2 loop return next; end loop; end $$ language plpgsql; select * from plpgsql_check_function('out1()'); drop function out1(); -- never read variable detection create function a() returns int as $$ declare foo int; begin foo := 2; return 1; end; $$ language plpgsql; select * from plpgsql_check_function('a()'); drop function a(); -- issue #29 false unused variable create or replace function f1(in p_cursor refcursor) returns void as $body$ declare z_offset integer; begin z_offset := 10; move absolute z_offset from p_cursor; end; $body$ language 'plpgsql' stable; select * from plpgsql_check_function_tb('f1(refcursor)'); drop function f1(refcursor); -- issue #30 segfault due NULL refname create or replace function test(a varchar) returns void as $$ declare x cursor (_a varchar) for select _a; begin open x(a); end; $$ language plpgsql; select * from plpgsql_check_function_tb('test(varchar)'); drop function test(varchar); create or replace function test() returns void as $$ declare x numeric; begin x := NULL; end; $$ language plpgsql; select * from plpgsql_check_function('test()'); drop function test(); create table testtable(a int); create or replace function test() returns int as $$ declare r testtable; begin select * into r from testtable; return r.a; end; $$ language plpgsql; select * from plpgsql_check_function('test()'); set check_function_bodies to on; drop table testtable; create table testtable(a int, b int); create or replace function test() returns int as $$ declare r testtable; begin select * into r from testtable; return r.a; end; $$ language plpgsql; alter table testtable drop column b; -- expected false alarm on PostgreSQL 10 and older -- there is not possibility to enforce recompilation -- before checking. select * from plpgsql_check_function('test()'); drop function test(); -- issue #32 create table bigtable(id bigint, v varchar); create or replace function test() returns void as $$ declare r record; _id numeric; begin select * into r from bigtable where id = _id; for r in select * from bigtable where _id = id loop end loop; if (exists(select * from bigtable where id = _id)) then end if; end; $$ language plpgsql; select test(); -- should to show performance warnings select * from plpgsql_check_function('test()', performance_warnings := true); create or replace function test() returns void as $$ declare r record; _id bigint; begin select * into r from bigtable where id = _id; for r in select * from bigtable where _id = id loop end loop; if (exists(select * from bigtable where id = _id)) then end if; end; $$ language plpgsql; -- there are not any performance issue now select * from plpgsql_check_function('test()', performance_warnings := true); -- nextval, currval and setval test create table test_table(); create or replace function testseq() returns void as $$ begin perform nextval('test_table'); perform currval('test_table'); perform setval('test_table', 10); perform setval('test_table', 10, true); end; $$ language plpgsql; -- should to fail select testseq(); select * from plpgsql_check_function('testseq()', fatal_errors := false); drop function testseq(); drop table test_table; -- tests designed for PostgreSQL 9.2 set check_function_bodies to off; create table t1(a int, b int); create function f1() returns void as $$ begin if false then update t1 set c = 30; end if; if false then raise notice '%', r.c; end if; end; $$ language plpgsql; select f1(); select * from plpgsql_check_function_tb('f1()', fatal_errors := true); select * from plpgsql_check_function_tb('f1()', fatal_errors := false); select * from plpgsql_check_function_tb('f1()'); select f1(); drop function f1(); create or replace function f1() returns void as $$ begin if false then raise notice '%', 1, 2; end if; end; $$ language plpgsql; select f1(); select * from plpgsql_check_function_tb('f1()'); select f1(); drop function f1(); create or replace function f1() returns void as $$ begin if false then raise notice '% %'; end if; end; $$ language plpgsql; select f1(); select * from plpgsql_check_function_tb('f1()'); select f1(); drop function f1(); create or replace function ml_trg() returns trigger as $$ #option dump declare begin if TG_OP = 'INSERT' then if NEW.status_from IS NULL then begin -- performance issue only select status into NEW.status_from from pa where pa_id = NEW.pa_id; -- nonexist target value select status into NEW.status_from_xxx from pa where pa_id = NEW.pa_id; exception when DATA_EXCEPTION then new.status_from := 'DE'; end; end if; end if; if TG_OP = 'DELETE' then return OLD; else return NEW; end if; exception when OTHERS then NULL; if TG_OP = 'DELETE' then return OLD; else return NEW; end if; end; $$ language plpgsql; select * from plpgsql_check_function('ml_trg()', 'ml', performance_warnings := true); create or replace function fx2() returns void as $$ declare _pa pa; begin select pa.id into _pa.id from pa limit 1; select pa.pa_id into _pa.pa_id from pa limit 1; end; $$ language plpgsql; select * from plpgsql_check_function('fx2()', performance_warnings := true); drop function fx2(); create or replace function fx2() returns void as $$ declare _pa pa; begin _pa.id := (select pa.id from pa limit 1); _pa.pa_id := (select pa.pa_id from pa limit 1); end; $$ language plpgsql; select * from plpgsql_check_function('fx2()', performance_warnings := true); drop function fx2(); create or replace function test_lab() returns void as $$ begin <> for a in 1..3 loop <> BEGIN <> for b in 8..9 loop if a=2 then continue sub; end if; raise notice '% %', a, b; end loop inner; END sub; end loop outer; end; $$ language plpgsql; select test_lab(); select * from plpgsql_check_function('test_lab()', performance_warnings := true); create or replace function test_lab() returns void as $$ begin continue; end; $$ language plpgsql; select test_lab(); select * from plpgsql_check_function('test_lab()', performance_warnings := true); create type _exception_type as ( state text, message text, detail text); create or replace function f1() returns void as $$ declare _exception record; begin _exception := NULL::_exception_type; exception when others then get stacked diagnostics _exception.state = RETURNED_SQLSTATE, _exception.message = MESSAGE_TEXT, _exception.detail = PG_EXCEPTION_DETAIL, _exception.hint = PG_EXCEPTION_HINT; end; $$ language plpgsql; select f1(); select * from plpgsql_check_function('f1()'); drop function f1(); drop type _exception_type; drop table t1; create function myfunc1(a int, b float) returns integer as $$ begin end $$ language plpgsql; create function myfunc2(a int, b float) returns integer as $$ begin end $$ language plpgsql; create function myfunc3(a int, b float) returns integer as $$ begin end $$ language plpgsql; create function myfunc4(a int, b float) returns integer as $$ begin end $$ language plpgsql; create function opfunc1(a int, b float) returns integer as $$ begin end $$ language plpgsql; create operator *** (procedure = opfunc1, leftarg = int, rightarg = float); create table mytable(a int); create table myview as select * from mytable; create function testfunc(a int, b float) returns void as $$ declare x integer; begin raise notice '%', myfunc1(a, b); x := myfunc2(a, b) operator(public.***) 1; perform myfunc3(m.a, b) from myview m; insert into mytable select myfunc4(a, b); end; $$ language plpgsql; select * from plpgsql_check_function('testfunc(int,float)'); select type, schema, name, params from plpgsql_show_dependency_tb('testfunc(int,float)'); drop function testfunc(int, float); drop function myfunc1(int, float); drop function myfunc2(int, float); drop function myfunc3(int, float); drop function myfunc4(int, float); drop table mytable; drop view myview; -- issue #34 create or replace function testcase() returns bool as $$ declare x int; begin set local search_path to public, test; case x when 1 then return true; else return false; end case; end; $$ language plpgsql; -- should not to raise warning select * from plpgsql_check_function('testcase()'); drop function testcase(); -- Adam's Bartoszewicz example create or replace function public.test12() returns refcursor language plpgsql as $body$ declare rc refcursor; begin open rc scroll for select pc.* from pg_cast pc; return rc; end; $body$; -- should not returns false alarm select * from plpgsql_check_function('test12()'); drop function public.test12(); -- should to show performance warning on bad flag create or replace function flag_test1(int) returns int as $$ begin return $1 + 10; end; $$ language plpgsql stable; create table fufu(a int); create or replace function flag_test2(int) returns int as $$ begin return (select * from fufu limit 1); end; $$ language plpgsql volatile; select * from plpgsql_check_function('flag_test1(int)', performance_warnings := true); select * from plpgsql_check_function('flag_test2(int)', performance_warnings := true); drop table fufu; drop function flag_test1(int); drop function flag_test2(int); create or replace function rrecord01() returns setof record as $$ begin return query select 1,2; end; $$ language plpgsql; create or replace function rrecord02() returns record as $$ begin return row(10,20,30); end; $$ language plpgsql; create type record03 as (a int, b int); create or replace function rrecord03() returns record03 as $$ declare r record; begin r := row(1); return r; end; $$ language plpgsql; -- should not to raise false alarms select * from plpgsql_check_function('rrecord01'); select * from plpgsql_check_function('rrecord02'); -- should detect different return but still detect return select * from plpgsql_check_function('rrecord03', fatal_errors => false); drop function rrecord01(); drop function rrecord02(); drop function rrecord03(); drop type record03; create or replace function bugfunc01() returns void as $$ declare cvar cursor(a int, b int) for select a + b from generate_series(1,b); begin for t in cvar(1,3) loop raise notice '%', t; end loop; end; $$ language plpgsql; select bugfunc01(); select * from plpgsql_check_function('bugfunc01'); create or replace function bugfunc02() returns void as $$ declare cvar cursor(a int, b int) for select a + b from generate_series(1,b); begin open cvar(10,20); close cvar; end; $$ language plpgsql; select bugfunc02(); select * from plpgsql_check_function('bugfunc02'); create or replace function bugfunc03() returns void as $$ declare cvar cursor(a int, b int) for select a + b from not_exists_table; begin open cvar(10,20); close cvar; end; $$ language plpgsql; select bugfunc03(); select * from plpgsql_check_function('bugfunc03'); create or replace function f1(out cr refcursor) as $$ begin end; $$ language plpgsql; -- should to raise warning select * from plpgsql_check_function('f1()'); create or replace function f1(out cr refcursor) as $$ begin open cr for select 1; end; $$ language plpgsql; -- should not to raise warning, see issue #43 select * from plpgsql_check_function('f1()'); drop function f1(); create table testt(a int); create or replace function testt_trg_func() returns trigger as $$ begin return new; end; $$ language plpgsql; create trigger testt_trg before insert or update on testt for each row execute procedure testt_trg_func(); create or replace function maintaince_function() returns void as $$ begin alter table testt disable trigger testt_trg; alter table testt enable trigger testt_trg; end; $$ language plpgsql; -- should not to crash select * from plpgsql_check_function_tb('maintaince_function()', 0, true, true, true); drop function maintaince_function(); drop trigger testt_trg on testt; drop function testt_trg_func(); drop table testt; create or replace function test_crash() returns void as $$ declare ec int default buggyfunc(10); begin select * into ec from buggytab; end; $$ language plpgsql; -- should not to crash select * from plpgsql_check_function('test_crash', fatal_errors := false); select * from plpgsql_check_function('test_crash', fatal_errors := true); drop function test_crash(); -- fix false alarm reported by Piotr Stepniewski create or replace function public.fx() returns void language plpgsql as $function$ begin raise exception 'xxx'; end; $function$; -- show raise nothing select * from plpgsql_check_function('fx()'); create table errtab( message text, code character(5) ); create or replace function public.fx() returns void language plpgsql as $function$ declare var errtab%rowtype; begin raise exception using message = var.message, errcode = var.code; end; $function$; -- should not to crash select * from plpgsql_check_function('fx()'); create or replace function public.fx() returns void language plpgsql as $function$ declare var errtab%rowtype; begin raise exception using message = var.message, errcode = var.code, hint = var.hint; end; $function$; -- should not to crash select * from plpgsql_check_function('fx()'); drop function fx(); create or replace function foo_format(a text, b text) returns void as $$ declare s text; begin s := format('%s'); -- should to raise error s := format('%s %10s', a, b); -- should be ok s := format('%s %s', a, b, a); -- should to raise warning s := format('%s %d', a, b); -- should to raise error raise notice '%', s; end; $$ language plpgsql; select * from plpgsql_check_function('foo_format', fatal_errors := false); drop function foo_format(text, text); create or replace function dyn_sql_1() returns void as $$ declare v varchar; n int; begin execute 'select ' || n; -- ok execute 'select ' || quote_literal(v); -- ok execute 'select ' || v; -- vulnerable execute format('select * from %I', v); -- ok execute format('select * from %s', v); -- vulnerable execute 'select $1' using v; -- ok execute 'select 1'; -- ok execute 'select 1' using v; -- warning execute 'select $1'; -- error end; $$ language plpgsql; select * from plpgsql_check_function('dyn_sql_1', security_warnings := true, fatal_errors := false); drop function dyn_sql_1(); create type tp as (a int, b int); create or replace function dyn_sql_2() returns void as $$ declare r tp; result int; begin select 10 a, 20 b into r; raise notice '%', r.a; execute 'select $1.a + $1.b' into result using r; execute 'select $1.c' into result using r; -- error raise notice '%', result; end; $$ language plpgsql; select * from plpgsql_check_function('dyn_sql_2', security_warnings := true); drop function dyn_sql_2(); drop type tp; /* * Should not to work * * note: plpgsql doesn't support passing some necessary details for record * type. The parser setup for dynamic SQL column doesn't use ref hooks, and * then it cannot to pass TupleDesc info to query anyway. */ create or replace function dyn_sql_2() returns void as $$ declare r record; result int; begin select 10 a, 20 b into r; raise notice '%', r.a; execute 'select $1.a + $1.b' into result using r; raise notice '%', result; end; $$ language plpgsql; select dyn_sql_2(); --should to fail select * from plpgsql_check_function('dyn_sql_2', security_warnings := true); drop function dyn_sql_2(); create or replace function dyn_sql_3() returns void as $$ declare r int; begin execute 'select $1' into r using 1; raise notice '%', r; end $$ language plpgsql; select dyn_sql_3(); -- should be ok select * from plpgsql_check_function('dyn_sql_3'); create or replace function dyn_sql_3() returns void as $$ declare r record; begin execute 'select $1 as a, $2 as b' into r using 1, 2; raise notice '% %', r.a, r.b; end $$ language plpgsql; select dyn_sql_3(); -- should be ok select * from plpgsql_check_function('dyn_sql_3'); create or replace function dyn_sql_3() returns void as $$ declare r record; begin execute 'create table foo(a int)' into r using 1, 2; raise notice '% %', r.a, r.b; end $$ language plpgsql; -- raise a error select * from plpgsql_check_function('dyn_sql_3'); create or replace function dyn_sql_3() returns void as $$ declare r1 int; r2 int; begin execute 'select 1' into r1, r2 using 1, 2; raise notice '% %', r1, r2; end $$ language plpgsql; -- raise a error select * from plpgsql_check_function('dyn_sql_3'); drop function dyn_sql_3(); create or replace function dyn_sql_3() returns void as $$ declare r record; begin for r in execute 'select 1 as a, 2 as b' loop raise notice '%', r.a; end loop; end $$ language plpgsql; -- should be ok select * from plpgsql_check_function('dyn_sql_3'); drop function dyn_sql_3(); create or replace function dyn_sql_3() returns void as $$ declare r record; begin for r in execute 'select 1 as a, 2 as b' loop raise notice '%', r.c; end loop; end $$ language plpgsql; -- should be error select * from plpgsql_check_function('dyn_sql_3'); drop function dyn_sql_3(); create or replace function dyn_sql_3() returns void as $$ declare r record; v text = 'select 10 a, 20 b't; begin for r in execute v loop raise notice '%', r.a; end loop; end $$ language plpgsql; -- should be ok select * from plpgsql_check_function('dyn_sql_3'); drop function dyn_sql_3(); create or replace function dyn_sql_4() returns table(ax int, bx int) as $$ begin return query execute 'select 10, 20'; return; end; $$ language plpgsql; -- should be ok select * from plpgsql_check_function('dyn_sql_4()'); create or replace function dyn_sql_4() returns table(ax int, bx int) as $$ begin return query execute 'select 10, 20, 30'; return; end; $$ language plpgsql; select * from dyn_sql_4(); -- should be error select * from plpgsql_check_function('dyn_sql_4()'); drop function dyn_sql_4(); create or replace function test_bug(text) returns regproc as $$ begin return $1::regproc; exception when undefined_function or invalid_name then raise; end; $$ language plpgsql; -- should not raise a exception select * from plpgsql_check_function('test_bug'); create or replace function test_bug(text) returns regproc as $$ begin return $1::regproc; exception when undefined_function or invalid_name then raise notice '%', $1; -- bug end; $$ language plpgsql; select test_bug('kuku'); -- should to fail select * from plpgsql_check_function('test_bug'); drop function test_bug(text); create or replace function test_bug(text) returns regproc as $$ begin return $1::regproc; exception when undefined_function or invalid_name then raise notice '%', $1; return NULL; end; $$ language plpgsql; select test_bug('kuku'); -- should be ok select * from plpgsql_check_function('test_bug'); drop function test_bug(text); create or replace function foo(a text, b text) returns void as $$ begin -- unsecure execute 'select ' || a; a := quote_literal(a); -- is safe now execute 'select ' || a; a := a || b; -- it is unsecure again execute 'select ' || a; end; $$ language plpgsql; \sf+ foo(text, text) -- should to raise two warnings select * from plpgsql_check_function('foo', security_warnings := true); drop function foo(text, text); -- test of very long function inside profiler create or replace function longfx(int) returns int as $$ declare s int default 0; j int default 0; r record; begin begin while j < 10 loop for i in 1..1 loop for r in select * from generate_series(1,1) loop s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; s := s + 1; end loop; end loop; j := j + 1; end loop; exception when others then raise 'reraised exception %', sqlerrm; end; return $1; end; $$ language plpgsql; select lineno, stmt_lineno, exec_stmts, source from plpgsql_profiler_function_tb('longfx'); set plpgsql_check.profiler = on; select longfx(10); select longfx(10); set plpgsql_check.profiler = off; select longfx(10); select lineno, stmt_lineno, exec_stmts, source from plpgsql_profiler_function_tb('longfx'); select funcoid, exec_count from plpgsql_profiler_functions_all(); create table testr(a int); create rule testr_rule as on insert to testr do nothing; create or replace function fx_testr() returns void as $$ begin insert into testr values(20); end; $$ language plpgsql; -- allow some rules on tables select fx_testr(); select * from plpgsql_check_function_tb('fx_testr'); drop function fx_testr(); drop table testr; -- coverage tests set plpgsql_check.profiler to on; create or replace function covtest(int) returns int as $$ declare a int = $1; begin a := a + 1; if a < 10 then a := a + 1; end if; a := a + 1; return a; end; $$ language plpgsql; set plpgsql_check.profiler to on; select covtest(10); select stmtid, exec_stmts, stmtname from plpgsql_profiler_function_statements_tb('covtest'); select plpgsql_coverage_statements('covtest'); select plpgsql_coverage_branches('covtest'); select covtest(1); select stmtid, exec_stmts, stmtname from plpgsql_profiler_function_statements_tb('covtest'); select plpgsql_coverage_statements('covtest'); select plpgsql_coverage_branches('covtest'); set plpgsql_check.profiler to off; create or replace function f() returns void as $$ declare r1 record; r2 record; begin select 10 as a, 20 as b into r1; r2 := json_populate_record(r1, '{}'); raise notice '%', r2.a; end; $$ language plpgsql; select * from plpgsql_check_function('f'); -- fix issue #63 create or replace function distinct_array(arr anyarray) returns anyarray as $$ begin return array(select distinct e from unnest(arr) as e); end; $$ language plpgsql immutable; select plpgsql_check_function('distinct_array(anyarray)'); drop function distinct_array(anyarray); -- tracer test set plpgsql_check.enable_tracer to on; set plpgsql_check.tracer to on; set plpgsql_check.tracer_test_mode = true; \set VERBOSITY terse create or replace function fxo(a int, b int, c date, d numeric) returns void as $$ begin insert into tracer_tab values(a,b,c,d); end; $$ language plpgsql; create table tracer_tab(a int, b int, c date, d numeric); create or replace function tracer_tab_trg_fx() returns trigger as $$ begin return new; end; $$ language plpgsql; create trigger tracer_tab_trg before insert on tracer_tab for each row execute procedure tracer_tab_trg_fx(); select fxo(10,20,'20200815', 3.14); select fxo(11,21,'20200816', 6.28); set plpgsql_check.enable_tracer to off; set plpgsql_check.tracer to off; drop table tracer_tab cascade; drop function tracer_tab_trg_fx(); drop function fxo(int, int, date, numeric); create or replace function foo_trg_func() returns trigger as $$ begin -- bad function, RETURN is missing end; $$ language plpgsql; create table foo(a int); create trigger foo_trg before insert for each row execute procedure foo_trg_func(); -- should to print error select * from plpgsql_check_function('foo_trg_func', 'foo'); drop table foo; drop function foo_trg_func(); -- check event trigger function create or replace function f1() returns event_trigger as $$ BEGIN RAISE NOTICE 'test_event_trigger: % %', tg_event, tg_tag; END $$ language plpgsql; select * from plpgsql_check_function_tb('f1()'); -- should fail create or replace function f1() returns event_trigger as $$ BEGIN RAISE NOTICE 'test_event_trigger: % %', tg_event, tg_tagX; END $$ language plpgsql; select * from plpgsql_check_function_tb('f1()'); drop function f1(); -- check event trigger function create or replace function f1() returns event_trigger as $$ BEGIN RAISE NOTICE 'test_event_trigger: % %', tg_event, tg_tag; END $$ language plpgsql; select * from plpgsql_check_function('f1()'); -- should fail create or replace function f1() returns event_trigger as $$ BEGIN RAISE NOTICE 'test_event_trigger: % %', tg_event, tg_tagX; END $$ language plpgsql; select * from plpgsql_check_function('f1()'); drop function f1(); create table t1tab(a int, b int); create or replace function f1() returns setof t1tab as $$ begin return next (10,20); return; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings => true); create or replace function f1() returns setof t1tab as $$ begin return next (10::numeric,20); return; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings => true); create or replace function f1() returns setof t1tab as $$ declare a int; b int; begin return next (a,b); return; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings => true); create or replace function f1() returns setof t1tab as $$ declare a numeric; b int; begin return next (a,b::numeric); return; end; $$ language plpgsql; select * from plpgsql_check_function('f1()', performance_warnings => true); drop function f1(); create table t1(a int, b int); create or replace function fx() returns t2 as $$ begin return (10,20,30)::t1; end; $$ language plpgsql; select * from plpgsql_check_function('fx()', performance_warnings => true); drop function fx(); drop table t1tab; drop table t1; create or replace function fx() returns void as $$ begin assert exists(select * from foo); assert false, (select boo from boo limit 1); end; $$ language plpgsql; select * from plpgsql_check_function('fx()', fatal_errors => false); create or replace function ml_trg() returns trigger as $$ #option dump declare begin if TG_OP = 'INSERT' then if NEW.status_from IS NULL then begin -- performance issue only select status into NEW.status_from from pa where pa_id = NEW.pa_id; -- nonexist target value select status into NEW.status_from_xxx from pa where pa_id = NEW.pa_id; exception when DATA_EXCEPTION then new.status_from := 'DE'; end; end if; end if; if TG_OP = 'DELETE' then return OLD; else return NEW; end if; exception when OTHERS then NULL; if TG_OP = 'DELETE' then return OLD; else return NEW; end if; end; $$ language plpgsql; select * from plpgsql_check_function('ml_trg()', 'ml', performance_warnings := true); create or replace function fx2() returns void as $$ declare _pa pa; begin select pa.id into _pa.id from pa limit 1; select pa.pa_id into _pa.pa_id from pa limit 1; end; $$ language plpgsql; select * from plpgsql_check_function('fx2()', performance_warnings := true); drop function fx2(); create or replace function fx2() returns void as $$ declare _pa pa; begin _pa.id := (select pa.id from pa limit 1); _pa.pa_id := (select pa.pa_id from pa limit 1); end; $$ language plpgsql; select * from plpgsql_check_function('fx2()', performance_warnings := true); drop function fx2(); create type _exception_type as ( state text, message text, detail text); create or replace function f1() returns void as $$ declare _exception record; begin _exception := NULL::_exception_type; exception when others then get stacked diagnostics _exception.state = RETURNED_SQLSTATE, _exception.message = MESSAGE_TEXT, _exception.detail = PG_EXCEPTION_DETAIL, _exception.hint = PG_EXCEPTION_HINT; end; $$ language plpgsql; select f1(); select * from plpgsql_check_function_tb('f1()'); create or replace function f1() returns void as $$ declare _exception _exception_type; begin _exception := NULL::_exception_type; exception when others then get stacked diagnostics _exception.state = RETURNED_SQLSTATE, _exception.message = MESSAGE_TEXT, _exception.detail = PG_EXCEPTION_DETAIL; end; $$ language plpgsql; select f1(); select * from plpgsql_check_function_tb('f1()'); drop function f1(); drop type _exception_type; create type _exception_type as ( state text, message text, detail text); create or replace function f1() returns void as $$ declare _exception record; begin _exception := NULL::_exception_type; exception when others then get stacked diagnostics _exception.state = RETURNED_SQLSTATE, _exception.message = MESSAGE_TEXT, _exception.detail = PG_EXCEPTION_DETAIL, _exception.hint = PG_EXCEPTION_HINT; end; $$ language plpgsql; select f1(); select * from plpgsql_check_function('f1()'); drop function f1(); drop type _exception_type; create table footab(a int, b int, c int); create or replace function footab_trig_func() returns trigger as $$ declare x int; begin if false then -- should be ok; select count(*) from newtab into x; -- should fail; select count(*) from newtab where d = 10 into x; end if; return null; end; $$ language plpgsql; select * from plpgsql_check_function('footab_trig_func','footab', newtable := 'newtab'); drop table footab; drop function footab_trig_func(); create or replace function df1(anyelement) returns anyelement as $$ begin return $1; end; $$ language plpgsql; create or replace function df2(anyelement, jsonb) returns anyelement as $$ begin return $1; end; $$ language plpgsql; create or replace function t1() returns void as $$ declare r record; begin r := df1(r); end; $$ language plpgsql; select * from plpgsql_check_function('t1()'); create or replace function t1() returns void as $$ declare r record; begin r := df2(r, '{}'); end; $$ language plpgsql; select * from plpgsql_check_function('t1()'); create or replace function t1() returns void as $$ declare r1 record; r2 record; begin select 10 as a, 20 as b into r1; r2 := df1(r1); raise notice '%', r2.a; end; $$ language plpgsql; select * from plpgsql_check_function('t1()'); create or replace function t1() returns void as $$ declare r1 record; r2 record; begin select 10 as a, 20 as b into r1; r2 := df2(r1, '{}'); raise notice '%', r2.a; end; $$ language plpgsql; select * from plpgsql_check_function('t1()'); create or replace function df1(anyelement) returns anyelement as $$ select $1 $$ language sql; create or replace function df22(jsonb, anyelement) returns anyelement as $$ select $2; $$ language sql; create or replace function t1() returns void as $$ declare r1 record; r2 record; begin select 10 as a, 20 as b into r1; r2 := df1(r1); raise notice '%', r2.a; end; $$ language plpgsql; select * from plpgsql_check_function('t1()'); create or replace function t1() returns void as $$ declare r1 record; r2 record; begin select 10 as a, 20 as b into r1; r2 := df22('{}', r1); raise notice '%', r2.a; end; $$ language plpgsql; select * from plpgsql_check_function('t1()'); drop function df1(anyelement); drop function df2(anyelement, jsonb); drop function df22(jsonb, anyelement); drop function t1(); create or replace function dyntest() returns void as $$ begin execute 'drop table if exists xxx; create table xxx(a int)'; end; $$ language plpgsql; -- should be ok select * from plpgsql_check_function('dyntest'); create or replace function dyntest() returns void as $$ declare x int; begin execute 'drop table if exists xxx; create table xxx(a int)' into x; end; $$ language plpgsql; -- should to report error select * from plpgsql_check_function('dyntest'); drop function dyntest(); -- should to report error create type typ2 as (a int, b int); create or replace function broken_into() returns void as $$ declare v typ2; begin -- should to fail select (10,20)::typ2 into v; -- should be ok select ((10,20)::typ2).* into v; -- should to fail execute 'select (10,20)::typ2' into v; -- should be ok execute 'select ((10,20)::typ2).*' into v; end; $$ language plpgsql; select * from plpgsql_check_function('broken_into', fatal_errors => false); drop function broken_into(); drop type typ2; -- check output in xml or json formats CREATE OR REPLACE FUNCTION test_function() RETURNS void LANGUAGE plpgsql AS $function$ begin insert into non_existing_table values (1); end $function$; select * from plpgsql_check_function('test_function', format => 'xml'); select * from plpgsql_check_function('test_function', format => 'json'); drop function test_function(); -- test settype pragma create or replace function test_function() returns void as $$ declare r record; begin raise notice '%', r.a; end; $$ language plpgsql; -- should to detect error select * from plpgsql_check_function('test_function'); create type ctype as (a int, b int); create or replace function test_function() returns void as $$ declare r record; begin perform plpgsql_check_pragma('type: r ctype'); raise notice '%', r.a; end; $$ language plpgsql; -- should to be ok select * from plpgsql_check_function('test_function'); create or replace function test_function() returns void as $$ <>declare r record; begin perform plpgsql_check_pragma('type: x.r public."ctype"'); raise notice '%', r.a; end; $$ language plpgsql; -- should to be ok select * from plpgsql_check_function('test_function'); create or replace function test_function() returns void as $$ <>declare r record; begin perform plpgsql_check_pragma('type: "x".r (a int, b int)'); raise notice '%', r.a; end; $$ language plpgsql; -- should to be ok select * from plpgsql_check_function('test_function'); create or replace function test_function() returns void as $$ <>declare r record; begin perform plpgsql_check_pragma('type: "x".r (a int, b int'); raise notice '%', r.a; end; $$ language plpgsql; -- should to be ok select * from plpgsql_check_function('test_function'); create or replace function test_function() returns void as $$ <>declare r record; begin perform plpgsql_check_pragma('type: "x".r (a int, b int)x'); raise notice '%', r.a; end; $$ language plpgsql; -- should to be ok select * from plpgsql_check_function('test_function'); drop function test_function(); drop type ctype; create or replace function test_function() returns void as $$ declare r pg_class; begin create temp table foo(like pg_class); select * from foo into r; end; $$ language plpgsql; -- should to raise an error select * from plpgsql_check_function('test_function'); create or replace function test_function() returns void as $$ declare r record; begin create temp table foo(like pg_class); perform plpgsql_check_pragma('table: foo(like pg_class)'); select * from foo into r; raise notice '%', r.relname; end; $$ language plpgsql; -- should be ok select * from plpgsql_check_function('test_function'); drop function test_function(); -- now plpgsql_check can do some other checks when statement EXECUTE -- contains only format function with constant fmt. create or replace function test_function() returns void as $$ begin execute format('create table zzz %I(a int, b int)', 'zzz'); end; $$ language plpgsql; -- should to detect bad expression select * from plpgsql_check_function('test_function'); -- should to correctly detect type create or replace function test_function() returns void as $$ declare r record; begin execute format('select %L::date + 1 as x', current_date) into r; raise notice '%', extract(dow from r.x); end; $$ language plpgsql; -- should be ok select * from plpgsql_check_function('test_function'); -- should not to crash create or replace function test_function() returns void as $$ declare r record; begin r := null; end; $$ language plpgsql; select * from plpgsql_check_function('test_function'); drop function test_function(); -- aborted function has profile too create or replace function test_function(a int) returns int as $$ begin if (a > 5) then a := a + 10; return a; else raise exception 'a < 5'; end if; end; $$ language plpgsql; set plpgsql_check.profiler to on; select test_function(1); select test_function(10); select lineno, exec_stmts, exec_stmts_err, source from plpgsql_profiler_function_tb('test_function'); create or replace function test_function1(a int) returns int as $$ begin if (a > 5) then a := a + 10; return a; else raise exception 'a < 5'; end if; exeception when others then raise notice 'do warning'; return -1; end; $$ language plpgsql; select test_function1(1); select test_function1(10); select lineno, exec_stmts, exec_stmts_err, source from plpgsql_profiler_function_tb('test_function1'); drop function test_function(int); drop function test_function1(int); set plpgsql_check.profiler to off; -- ignores syntax errors when literals placehodlers are used create function test_function() returns void as $$ begin execute format('do %L', 'begin end'); end $$ language plpgsql; select * from plpgsql_check_function('test_function'); drop function test_function(); load 'plpgsql_check'; drop type testtype cascade; create type testtype as (a int, b int); create function test_function() returns record as $$ declare r record; begin r := (10,20); if false then return r; end if; return null; end; $$ language plpgsql; create function test_function33() returns record as $$ declare r testtype; begin r := (10,20); if false then return r; end if; return null; end; $$ language plpgsql; -- should not to raise false alarm due check against fake result type select plpgsql_check_function('test_function'); select plpgsql_check_function('test_function33'); -- try to check in passive mode set plpgsql_check.mode = 'every_start'; select test_function(); select test_function33(); select * from test_function() as (a int, b int); select * from test_function33() as (a int, b int); -- should to identify error select * from test_function() as (a int, b int, c int); select * from test_function33() as (a int, b int, c int); drop function test_function(); drop function test_function33(); drop type testtype; set plpgsql_check.mode to default; -- should not to raise false alarm create type c1 as ( a text ); create table t1 ( a c1, b c1 ); insert into t1 (values ('(abc)', '(def)')); alter table t1 drop column a; create or replace function test_function() returns t1 as $$ declare myrow t1%rowtype; begin select * into myrow from t1 limit 1; return myrow; end; $$ language plpgsql; select * from test_function(); select * from plpgsql_check_function('public.test_function()'); drop function test_function(); drop table t1; drop type c1; -- compatibility warnings create or replace function foo01() returns refcursor as $$ declare c cursor for select 1; r refcursor; begin open c; r := c; return r; end; $$ language plpgsql; -- no warnings select * from plpgsql_check_function('foo01', compatibility_warnings => true); create or replace function foo01() returns refcursor as $$ declare c cursor for select 1; r refcursor; begin open c; r := 'c'; return r; end; $$ language plpgsql; -- warning select * from plpgsql_check_function('foo01', extra_warnings => false, compatibility_warnings => true); create or replace function foo01() returns refcursor as $$ declare c cursor for select 1; r refcursor; begin open c; r := c; return 'c'; end; $$ language plpgsql; -- warning select * from plpgsql_check_function('foo01', extra_warnings => false, compatibility_warnings => true); -- pragma sequence test create or replace function test_function() returns void as $$ begin perform plpgsql_check_pragma('sequence: xx'); perform nextval('pg_temp.xy'); perform nextval('pg_temp.xx'); end $$ language plpgsql; select * from plpgsql_check_function('test_function'); drop function test_function(); create table t1(x int); create or replace function f1_trg() returns trigger as $$ declare x int; begin return x; end; $$ language plpgsql; create trigger t1_f1 before insert on t1 for each row execute procedure f1_trg(); -- raise error select * from plpgsql_check_function('f1_trg', 't1'); drop trigger t1_f1 on t1; drop table t1; drop function f1_trg; create function test_function() returns void as $$ declare a int; b int; c int; d char; begin c := a + d; end; $$ language plpgsql; -- only b should be marked as unused variable select * from plpgsql_check_function('test_function', fatal_errors := false); drop function test_function(); -- from plpgsql_check_active-12 create or replace procedure proc(a int) as $$ begin end; $$ language plpgsql; call proc(10); select * from plpgsql_check_function('proc(int)'); create or replace procedure testproc() as $$ begin call proc(10); end; $$ language plpgsql; call testproc(); select * from plpgsql_check_function('testproc()'); -- should to fail create or replace procedure testproc() as $$ begin call proc((select count(*) from pg_class)); end; $$ language plpgsql; call testproc(); select * from plpgsql_check_function('testproc()'); drop procedure proc(int); create procedure proc(in a int, inout b int, in c int) as $$ begin end; $$ language plpgsql; select * from plpgsql_check_function('proc(int,int, int)'); create or replace procedure proc(in a int, inout b int, in c int) as $$ begin b := a + c; end; $$ language plpgsql; select * from plpgsql_check_function('proc(int,int, int)'); create or replace procedure testproc() as $$ declare r int; begin call proc(10, r, 20); end; $$ language plpgsql; call testproc(); select * from plpgsql_check_function('testproc()'); -- should to fail create or replace procedure testproc() as $$ declare r int; begin call proc(10, r + 10, 20); end; $$ language plpgsql; call testproc(); select * from plpgsql_check_function('testproc()'); create or replace procedure testproc(inout r int) as $$ begin call proc(10, r, 20); end; $$ language plpgsql; call testproc(10); select * from plpgsql_check_function('testproc(int)'); drop procedure testproc(int); -- should to raise warnings create or replace procedure testproc2(in p1 int, inout p2 int, in p3 int, inout p4 int) as $$ begin raise notice '% %', p1, p3; end; $$ language plpgsql; select * from plpgsql_check_function('testproc2'); drop procedure testproc2; -- should be ok create or replace procedure testproc3(in p1 int, inout p2 int, in p3 int, inout p4 int) as $$ begin p2 := p1; p4 := p3; end; $$ language plpgsql; select * from plpgsql_check_function('testproc3'); drop procedure testproc3; /* * Test pragma */ create or replace function test_pragma() returns void language plpgsql as $$ declare r record; begin perform plpgsql_check_pragma('disable:check'); raise notice '%', r.y; perform plpgsql_check_pragma('enable:check'); select 10 as a, 20 as b into r; raise notice '%', r.a; raise notice '%', r.x; end; $$; select * from plpgsql_check_function('test_pragma'); create or replace function test_pragma() returns void language plpgsql as $$ declare r record; begin if false then -- check is disabled just for if body perform plpgsql_check_pragma('disable:check'); raise notice '%', r.y; end if; select 10 as a, 20 as b into r; raise notice '%', r.a; raise notice '%', r.x; end; $$; select * from plpgsql_check_function('test_pragma'); drop function test_pragma(); create or replace function nested_trace_test(a int) returns int as $$ begin return a + 1; end; $$ language plpgsql; create or replace function trace_test(b int) returns int as $$ declare r int default 0; begin for i in 1..b loop r := nested_trace_test(r); end loop; return r; end; $$ language plpgsql; select trace_test(3); set plpgsql_check.enable_tracer to on; set plpgsql_check.tracer to on; set plpgsql_check.tracer_test_mode = true; select trace_test(3); set plpgsql_check.tracer_verbosity TO verbose; select trace_test(3); create or replace function trace_test(b int) returns int as $$ declare r int default 0; begin for i in 1..b loop perform plpgsql_check_pragma('disable:tracer'); r := nested_trace_test(r); end loop; return r; end; $$ language plpgsql; select trace_test(3); create or replace function nested_trace_test(a int) returns int as $$ begin perform plpgsql_check_pragma('enable:tracer'); return a + 1; end; $$ language plpgsql; select trace_test(3); drop function trace_test(int); drop function nested_trace_test(int); create or replace function trace_test(int) returns int as $$ declare r int default 0; begin for i in 1..$1 loop r := r + 1; end loop; r := r + 10; return r; end; $$ language plpgsql; select trace_test(4); create or replace function trace_test(int) returns int as $$ declare r int default 0; begin for i in 1..$1 loop perform plpgsql_check_pragma('disable:tracer'); r := r + 1; end loop; r := r + 10; return r; end; $$ language plpgsql; select trace_test(4); create or replace function trace_test(int) returns int as $$ declare r int default 0; begin perform plpgsql_check_pragma('disable:tracer'); for i in 1..$1 loop r := r + 1; end loop; perform plpgsql_check_pragma('enable:tracer'); r := r + 10; return r; end; $$ language plpgsql; select trace_test(4); drop function trace_test(int); create or replace function public.fx1() returns table(i integer, j integer, found boolean) as $$ begin for i in 1..10 loop for j in 1..10 loop return next; end loop; end loop; end; $$ language plpgsql immutable; select * from plpgsql_check_function('fx1'); drop function fx1; -- tracer test set plpgsql_check.enable_tracer to on; select plpgsql_check_tracer(true); create role plpgsql_check_test_role; DO $$ begin begin -- should to fail create role plpgsql_check_test_role; exception when duplicate_object then -- Role already exists -- the exception handler is empty (#156) end; end; $$; drop role plpgsql_check_test_role; set plpgsql_check.enable_tracer to off; select plpgsql_check_tracer(false); -- tracing constants -- issue #159 create or replace function tabret_dynamic() returns table (id integer, val text) as $$ begin return query execute 'select id, val from (values (1, ''a''), (2, ''b'')) as v(id, val)'; end; $$ language plpgsql immutable; -- should be ok select * from plpgsql_check_function('tabret_dynamic()'); create or replace function tabret_dynamic() returns table (id integer, val text) as $$ declare z_query text; begin z_query := 'select id, val from (values (1, ''a''), (2, ''b'')) as v(id, val)'; execute z_query into id, val; return next; end; $$ language plpgsql immutable; -- should be ok select * from plpgsql_check_function('tabret_dynamic()'); create or replace function tabret_dynamic() returns table (id integer, val text) as $$ declare z_query text; begin z_query := 'select id, val from (values (1, ''a''), (2, ''b'')) as v(id, val)'; for id, val in execute z_query loop return next; end loop; end; $$ language plpgsql immutable; -- should be ok select * from plpgsql_check_function('tabret_dynamic()'); create or replace function tabret_dynamic() returns table (id integer, val text) as $$ declare z_query text; begin z_query := 'select id, val from (values (1, ''a''), (2, ''b'')) as v(id, val)'; return query execute z_query; end; $$ language plpgsql immutable; -- should be ok select * from plpgsql_check_function('tabret_dynamic()'); drop function tabret_dynamic; -- should not to crash on empty string, or comment create or replace function dynamic_emptystr() returns void as $$ begin execute ''; end; $$ language plpgsql; -- should be ok select * from plpgsql_check_function('dynamic_emptystr()'); create or replace function dynamic_emptystr() returns void as $$ declare x int; begin execute '--' into x; end; $$ language plpgsql; -- should not to crash, no result error select * from plpgsql_check_function('dynamic_emptystr()'); drop function dynamic_emptystr(); -- unclosed cursor detection create or replace function fx() returns void as $$ declare c refcursor; begin open c for select * from pg_class; end; $$ language plpgsql; do $$ begin perform fx(); -- should to show warning perform fx(); end; $$; set plpgsql_check.strict_cursors_leaks to on; do $$ begin -- should to show warning perform fx(); -- should to show warning perform fx(); end; $$; create or replace function fx() returns void as $$ declare c refcursor; begin open c for select * from pg_class; close c; end; $$ language plpgsql; -- without warnings do $$ begin perform fx(); perform fx(); end; $$; set plpgsql_check.strict_cursors_leaks to off; drop function fx(); create table public.testt(a int, b int); create or replace function fx() returns void as $$ declare v1 varchar default 'public'; v2 varchar default 'testt'; v3 varchar default 'a'; begin raise notice '%', format('%I.%I.%I', v1, v2, v3); perform 'pragma:assert-schema: v1'; perform 'pragma:assert-table: v1, v2'; perform 'pragma:assert-column: v1, v2, v3'; perform 'pragma:assert-table: v2'; perform 'pragma:assert-column: v2, v3'; end; $$ language plpgsql; select * from plpgsql_check_function('fx()'); create table public.testt(a int, b int); create or replace function fx() returns void as $$ declare v1 varchar default 'public'; v2 varchar default 'testt'; v3 varchar default 'x'; begin raise notice '%', format('%I.%I.%I', v1, v2, v3); perform 'pragma:assert-column: v1, v2, v3'; end; $$ language plpgsql; select * from plpgsql_check_function('fx()'); drop function fx(); drop table public.testt; create or replace function fx() returns void as $$ declare v_sql varchar default ''; i int; begin -- we don't support constant tracing from queries select 'bla bla bla' into v_sql; execute v_sql into i; raise notice '%', i; end; $$ language plpgsql; -- should be ok select * from plpgsql_check_function('fx()'); drop function fx(); -- should not to raise warning do $$ declare c cursor for select * from generate_series(1,10); t int; begin for i in 1..2 loop open c; loop fetch c into t; exit when not found; raise notice '%', t; end loop; close c; end loop; end; $$; -- should not raise warning create or replace function fx(p text) returns void as $$ begin execute format($_$select '%1$I'$_$, p); end; $$ language plpgsql; -- should be ok select * from plpgsql_check_function('fx(text)'); drop function fx(text); create or replace function fx() returns void as $$ declare p varchar; begin p := 'ahoj'; execute format($_$select '%1$I'$_$, p); end; $$ language plpgsql; -- should be ok select * from plpgsql_check_function('fx()'); drop function fx(); -- should not crash create or replace procedure p1() as $$ begin commit; end; $$ language plpgsql; set plpgsql_check.cursors_leaks to on; do $$ declare c cursor for select 1; begin open c; call p1(); end $$; set plpgsql_check.cursors_leaks to default; drop procedure p1; plpgsql_check-2.7.8/sql/plpgsql_check_passive-12.sql000066400000000000000000000000001465410532300224510ustar00rootroot00000000000000plpgsql_check-2.7.8/sql/plpgsql_check_passive-13.sql000066400000000000000000000000001465410532300224520ustar00rootroot00000000000000plpgsql_check-2.7.8/sql/plpgsql_check_passive-14.sql000066400000000000000000000000001465410532300224530ustar00rootroot00000000000000plpgsql_check-2.7.8/sql/plpgsql_check_passive-15.sql000066400000000000000000000000001465410532300224540ustar00rootroot00000000000000plpgsql_check-2.7.8/sql/plpgsql_check_passive-16.sql000066400000000000000000000000001465410532300224550ustar00rootroot00000000000000plpgsql_check-2.7.8/sql/plpgsql_check_passive-17.sql000066400000000000000000000000001465410532300224560ustar00rootroot00000000000000plpgsql_check-2.7.8/sql/plpgsql_check_passive-18.sql000066400000000000000000000000001465410532300224570ustar00rootroot00000000000000plpgsql_check-2.7.8/sql/plpgsql_check_passive.sql000066400000000000000000000121131465410532300222410ustar00rootroot00000000000000load 'plpgsql'; load 'plpgsql_check'; set client_min_messages to notice; -- enforce context's displaying -- emulate pre 9.6 behave \set SHOW_CONTEXT always set plpgsql_check.mode = 'every_start'; create table t1(a int, b int); create function f1() returns void as $$ begin if false then update t1 set c = 30; end if; end; $$ language plpgsql; select f1(); drop function f1(); create function f1() returns void as $$ begin if false then insert into t1 values(10,20); update t1 set a = 10; delete from t1; end if; end; $$ language plpgsql stable; select f1(); drop function f1(); create function g1(out a int, out b int) as $$ select 10,20; $$ language sql; create function f1() returns void as $$ declare r record; begin r := g1(); if false then raise notice '%', r.c; end if; end; $$ language plpgsql; select f1(); drop function f1(); drop function g1(); create function g1(out a int, out b int) returns setof record as $$ select * from t1; $$ language sql; create function f1() returns void as $$ declare r record; begin for r in select * from g1() loop raise notice '%', r.c; end loop; end; $$ language plpgsql; select f1(); create or replace function f1() returns void as $$ declare r record; begin for r in select * from g1() loop r.c := 20; end loop; end; $$ language plpgsql; select f1(); drop function f1(); drop function g1(); create function f1() returns int as $$ declare r int; begin if false then r := a + b; end if; return r; end; $$ language plpgsql; select f1(); drop function f1(); create or replace function f1() returns void as $$ declare r int[]; begin if false then r[c+10] := 20; end if; end; $$ language plpgsql; select f1(); drop function f1(); create or replace function f1() returns void as $$ declare r int; begin if false then r[10] := 20; end if; end; $$ language plpgsql; select f1(); drop function f1(); create or replace function f1() returns void as $$ begin if false then insert into badbadtable values(10,20); end if; return; end; $$ language plpgsql; set plpgsql_check.mode = 'fresh_start'; select f1(); -- should not raise exception there select f1(); create or replace function f1() returns void as $$ begin if false then insert into badbadtable values(10,20); end if; return; end; $$ language plpgsql; -- after refreshing it should to raise exception again select f1(); set plpgsql_check.mode = 'every_start'; -- should to raise warning only set plpgsql_check.fatal_errors = false; select f1(); drop function f1(); create function f1() returns setof t1 as $$ begin if false then return query select a,a,a from t1; return; end if; end; $$ language plpgsql; select * from f1(); drop function f1(); create function f1() returns setof t1 as $$ begin if false then return query select a, b::numeric from t1; return; end if; end; $$ language plpgsql; select * from f1(); drop function f1(); drop table t1; do $$ declare begin if false then for i in 1,3..(2) loop raise notice 'foo %', i; end loop; end if; end; $$; -- tests designed for 9.2 set check_function_bodies to off; create or replace function f1() returns void as $$ begin if false then raise notice '%', 1, 2; end if; end; $$ language plpgsql; select f1(); drop function f1(); create or replace function f1() returns void as $$ begin if false then raise notice '% %'; end if; end; $$ language plpgsql; select f1(); drop function f1(); create type _exception_type as ( state text, message text, detail text); create or replace function f1() returns void as $$ declare _exception record; begin _exception := NULL::_exception_type; exception when others then get stacked diagnostics _exception.state = RETURNED_SQLSTATE, _exception.message = MESSAGE_TEXT, _exception.detail = PG_EXCEPTION_DETAIL, _exception.hint = PG_EXCEPTION_HINT; end; $$ language plpgsql; select f1(); drop function f1(); drop type _exception_type; create table footab(a int, b int, c int); create or replace function footab_trig_func() returns trigger as $$ declare x int; begin if false then -- should be ok; select count(*) from newtab into x; -- should fail; select count(*) from newtab where d = 10 into x; end if; return null; end; $$ language plpgsql; create trigger footab_trigger after insert on footab referencing new table as newtab for each statement execute procedure footab_trig_func(); -- should to fail insert into footab values(1,2,3); create or replace function footab_trig_func() returns trigger as $$ declare x int; begin if false then -- should be ok; select count(*) from newtab into x; end if; return null; end; $$ language plpgsql; -- should be ok insert into footab values(1,2,3); drop table footab; drop function footab_trig_func(); set plpgsql_check.mode = 'every_start'; create or replace procedure proc_test() as $$ begin commit; end; $$ language plpgsql; call proc_test(); drop procedure proc_test(); -- should not to crash set plpgsql_check.mode = 'fresh_start'; do $$ begin end; $$; plpgsql_check-2.7.8/src/000077500000000000000000000000001465410532300151415ustar00rootroot00000000000000plpgsql_check-2.7.8/src/assign.c000066400000000000000000000457741465410532300166120ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * assign.c * * assign types to record variables * * by Pavel Stehule 2013-2024 * *------------------------------------------------------------------------- */ #include "plpgsql_check.h" #include "access/htup_details.h" #include "catalog/pg_type.h" #include "parser/parse_coerce.h" #include "utils/builtins.h" #include "utils/lsyscache.h" #include "utils/typcache.h" #define get_eval_mcontext(estate) \ ((estate)->eval_econtext->ecxt_per_tuple_memory) #define eval_mcontext_alloc(estate, sz) \ MemoryContextAlloc(get_eval_mcontext(estate), sz) #define eval_mcontext_alloc0(estate, sz) \ MemoryContextAllocZero(get_eval_mcontext(estate), sz) static bool compatible_tupdescs(TupleDesc src_tupdesc, TupleDesc dst_tupdesc); /* * Mark variable as used */ void plpgsql_check_record_variable_usage(PLpgSQL_checkstate *cstate, int dno, bool write) { if (dno >= 0) { if (!write) cstate->used_variables = bms_add_member(cstate->used_variables, dno); else { cstate->modif_variables = bms_add_member(cstate->modif_variables, dno); /* raise extra warning when protected variable is modified */ if (bms_is_member(dno, cstate->protected_variables)) { PLpgSQL_variable *var = (PLpgSQL_variable *) cstate->estate->datums[dno]; StringInfoData message; initStringInfo(&message); appendStringInfo(&message, "auto varible \"%s\" should not be modified by user", var->refname); plpgsql_check_put_error(cstate, 0, var->lineno, message.data, NULL, NULL, PLPGSQL_CHECK_WARNING_EXTRA, 0, NULL, NULL); pfree(message.data); } } } } void plpgsql_check_row_or_rec(PLpgSQL_checkstate *cstate, PLpgSQL_row *row, PLpgSQL_rec *rec) { if (row != NULL) { int fnum; for (fnum = 0; fnum < row->nfields; fnum++) { /* skip dropped columns */ if (row->varnos[fnum] < 0) continue; plpgsql_check_target(cstate, row->varnos[fnum], NULL, NULL); } plpgsql_check_record_variable_usage(cstate, row->dno, true); } else if (rec != NULL) { /* * There are no checks done on records currently; just record that the * variable is not unused. */ plpgsql_check_record_variable_usage(cstate, rec->dno, true); } } void plpgsql_check_is_assignable(PLpgSQL_execstate *estate, int dno) { PLpgSQL_datum *datum; Assert(dno >= 0 && dno < estate->ndatums); datum = estate->datums[dno]; switch (datum->dtype) { case PLPGSQL_DTYPE_VAR: case PLPGSQL_DTYPE_PROMISE: case PLPGSQL_DTYPE_REC: if (((PLpgSQL_variable *) datum)->isconst) ereport(ERROR, (errcode(ERRCODE_ERROR_IN_ASSIGNMENT), errmsg("variable \"%s\" is declared CONSTANT", ((PLpgSQL_variable *) datum)->refname))); break; case PLPGSQL_DTYPE_ROW: /* always assignable; member vars were checked at compile time */ break; case PLPGSQL_DTYPE_RECFIELD: /* assignable if parent record is */ plpgsql_check_is_assignable(estate, ((PLpgSQL_recfield *) datum)->recparentno); break; #if PG_VERSION_NUM < 140000 case PLPGSQL_DTYPE_ARRAYELEM: /* assignable if parent record is */ plpgsql_check_is_assignable(estate, ((PLpgSQL_arrayelem *) datum)->arrayparentno); break; #endif default: elog(ERROR, "unrecognized dtype: %d", datum->dtype); break; } } /* * Verify lvalue It doesn't repeat a checks that are done. Checks a subscript * expressions, verify a validity of record's fields. */ void plpgsql_check_target(PLpgSQL_checkstate *cstate, int varno, Oid *expected_typoid, int *expected_typmod) { PLpgSQL_datum *target = cstate->estate->datums[varno]; /* * The target should be not constant, but we can allow assignment to * constant variable at block statement - it is using default value. */ if (cstate->estate->err_stmt->cmd_type != PLPGSQL_STMT_BLOCK) plpgsql_check_is_assignable(cstate->estate, varno); plpgsql_check_record_variable_usage(cstate, varno, true); switch (target->dtype) { case PLPGSQL_DTYPE_VAR: { PLpgSQL_var *var = (PLpgSQL_var *) target; PLpgSQL_type *tp = var->datatype; if (expected_typoid != NULL) *expected_typoid = tp->typoid; if (expected_typmod != NULL) *expected_typmod = tp->atttypmod; } break; case PLPGSQL_DTYPE_REC: { PLpgSQL_rec *rec = (PLpgSQL_rec *) target; plpgsql_check_recvar_info(rec, expected_typoid, expected_typmod); } break; case PLPGSQL_DTYPE_ROW: { PLpgSQL_row *row = (PLpgSQL_row *) target; if (row->rowtupdesc != NULL) { if (expected_typoid != NULL) *expected_typoid = row->rowtupdesc->tdtypeid; if (expected_typmod != NULL) *expected_typmod = row->rowtupdesc->tdtypmod; } else { if (expected_typoid != NULL) *expected_typoid = RECORDOID; if (expected_typmod != NULL) *expected_typmod = -1; } plpgsql_check_row_or_rec(cstate, row, NULL); } break; case PLPGSQL_DTYPE_RECFIELD: { PLpgSQL_recfield *recfield = (PLpgSQL_recfield *) target; PLpgSQL_rec *rec; int fno; rec = (PLpgSQL_rec *) (cstate->estate->datums[recfield->recparentno]); /* * Check that there is already a tuple in the record. We need * that because records don't have any predefined field * structure. */ if (!HeapTupleIsValid(recvar_tuple(rec))) ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("record \"%s\" is not assigned to tuple structure", rec->refname))); /* * Get the number of the records field to change and the * number of attributes in the tuple. Note: disallow system * column names because the code below won't cope. */ fno = SPI_fnumber(recvar_tupdesc(rec), recfield->fieldname); if (fno <= 0) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN), errmsg("record \"%s\" has no field \"%s\"", rec->refname, recfield->fieldname))); if (expected_typoid) *expected_typoid = SPI_gettypeid(recvar_tupdesc(rec), fno); if (expected_typmod) *expected_typmod = TupleDescAttr(recvar_tupdesc(rec), fno - 1)->atttypmod; } break; #if PG_VERSION_NUM < 140000 case PLPGSQL_DTYPE_ARRAYELEM: { /* * Target is an element of an array */ int nsubscripts; /* * To handle constructs like x[1][2] := something, we have to * be prepared to deal with a chain of arrayelem datums. Chase * back to find the base array datum, and save the subscript * expressions as we go. (We are scanning right to left here, * but want to evaluate the subscripts left-to-right to * minimize surprises.) */ nsubscripts = 0; do { PLpgSQL_arrayelem *arrayelem = (PLpgSQL_arrayelem *) target; if (nsubscripts++ >= MAXDIM) ereport(ERROR, (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)", nsubscripts + 1, MAXDIM))); plpgsql_check_expr(cstate, arrayelem->subscript); target = cstate->estate->datums[arrayelem->arrayparentno]; } while (target->dtype == PLPGSQL_DTYPE_ARRAYELEM); if (expected_typoid || expected_typmod) { int arraytypmod; Oid arrayelemtypeid; Oid arraytypeid; plpgsql_check_target(cstate, target->dno, &arraytypeid, &arraytypmod); /* * If target is domain over array, reduce to base type */ arraytypeid = getBaseType(arraytypeid); arrayelemtypeid = get_element_type(arraytypeid); if (!OidIsValid(arrayelemtypeid)) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("subscripted object is not an array"))); if (expected_typoid) *expected_typoid = arrayelemtypeid; if (expected_typmod) *expected_typmod = arraytypmod; } plpgsql_check_record_variable_usage(cstate, target->dno, true); } break; #endif default: ; /* nope */ } } /* * Check so target can accept typoid value * */ void plpgsql_check_assign_to_target_type(PLpgSQL_checkstate *cstate, Oid target_typoid, int32 target_typmod, Oid value_typoid, bool isnull) { /* not used yet */ (void) target_typmod; /* the overhead UNKONWNOID --> TEXT is low */ if ((target_typoid == value_typoid) || (target_typoid == TEXTOID && value_typoid == UNKNOWNOID)) return; if (type_is_rowtype(value_typoid) && !type_is_rowtype(target_typoid)) { StringInfoData str; initStringInfo(&str); appendStringInfo(&str, "cannot cast composite value of \"%s\" type to a scalar value of \"%s\" type", format_type_be(value_typoid), format_type_be(target_typoid)); plpgsql_check_put_error(cstate, ERRCODE_DATATYPE_MISMATCH, 0, str.data, NULL, NULL, PLPGSQL_CHECK_ERROR, 0, NULL, NULL); } else if (!isnull) { StringInfoData str; initStringInfo(&str); appendStringInfo(&str, "cast \"%s\" value to \"%s\" type", format_type_be(value_typoid), format_type_be(target_typoid)); /* accent warning when cast is without supported explicit casting */ if (!can_coerce_type(1, &value_typoid, &target_typoid, COERCION_EXPLICIT)) plpgsql_check_put_error(cstate, ERRCODE_DATATYPE_MISMATCH, 0, "target type is different type than source type", str.data, "There are no possible explicit coercion between those types, possibly bug!", PLPGSQL_CHECK_WARNING_OTHERS, 0, NULL, NULL); else if (!can_coerce_type(1, &value_typoid, &target_typoid, COERCION_ASSIGNMENT)) plpgsql_check_put_error(cstate, ERRCODE_DATATYPE_MISMATCH, 0, "target type is different type than source type", str.data, "The input expression type does not have an assignment cast to the target type.", PLPGSQL_CHECK_WARNING_OTHERS, 0, NULL, NULL); else { /* highly probably only performance issue */ plpgsql_check_put_error(cstate, ERRCODE_DATATYPE_MISMATCH, 0, "target type is different type than source type", str.data, "Hidden casting can be a performance issue.", PLPGSQL_CHECK_WARNING_PERFORMANCE, 0, NULL, NULL); } pfree(str.data); } } /* * Assign a tuple descriptor to variable specified by dno */ void plpgsql_check_assign_tupdesc_dno(PLpgSQL_checkstate *cstate, int varno, TupleDesc tupdesc, bool isnull) { PLpgSQL_datum *target = cstate->estate->datums[varno]; switch (target->dtype) { case PLPGSQL_DTYPE_VAR: { PLpgSQL_var *var = (PLpgSQL_var *) target; plpgsql_check_assign_to_target_type(cstate, var->datatype->typoid, var->datatype->atttypmod, TupleDescAttr(tupdesc, 0)->atttypid, isnull); } break; case PLPGSQL_DTYPE_ROW: plpgsql_check_assign_tupdesc_row_or_rec(cstate, (PLpgSQL_row *) target, NULL, tupdesc, isnull); break; case PLPGSQL_DTYPE_REC: plpgsql_check_assign_tupdesc_row_or_rec(cstate, NULL, (PLpgSQL_rec *) target, tupdesc, isnull); break; case PLPGSQL_DTYPE_RECFIELD: { Oid typoid; int typmod; plpgsql_check_target(cstate, varno, &typoid, &typmod); plpgsql_check_assign_to_target_type(cstate, typoid, typmod, TupleDescAttr(tupdesc, 0)->atttypid, isnull); } break; #if PG_VERSION_NUM < 140000 case PLPGSQL_DTYPE_ARRAYELEM: { Oid expected_typoid; int expected_typmod; plpgsql_check_target(cstate, varno, &expected_typoid, &expected_typmod); /* When target is composite type, then source is expanded already */ if (type_is_rowtype(expected_typoid)) { PLpgSQL_rec rec; plpgsql_check_recval_init(&rec); PG_TRY(); { plpgsql_check_recval_assign_tupdesc(cstate, &rec, lookup_rowtype_tupdesc_noerror(expected_typoid, expected_typmod, true), isnull); plpgsql_check_assign_tupdesc_row_or_rec(cstate, NULL, &rec, tupdesc, isnull); plpgsql_check_recval_release(&rec); } PG_CATCH(); { plpgsql_check_recval_release(&rec); PG_RE_THROW(); } PG_END_TRY(); } else plpgsql_check_assign_to_target_type(cstate, expected_typoid, expected_typmod, TupleDescAttr(tupdesc, 0)->atttypid, isnull); } break; #endif default: ; /* nope */ } } /* * We have to assign TupleDesc to all used record variables step by step. We * would to use a exec routines for query preprocessing, so we must to create * a typed NULL value, and this value is assigned to record variable. */ void plpgsql_check_assign_tupdesc_row_or_rec(PLpgSQL_checkstate *cstate, PLpgSQL_row *row, PLpgSQL_rec *rec, TupleDesc tupdesc, bool isnull) { if (tupdesc == NULL) { plpgsql_check_put_error(cstate, 0, 0, "tuple descriptor is empty", NULL, NULL, PLPGSQL_CHECK_WARNING_OTHERS, 0, NULL, NULL); return; } /* * row variable has assigned TupleDesc already, so don't be processed here */ if (rec != NULL) { PLpgSQL_rec *target = (PLpgSQL_rec *) (cstate->estate->datums[rec->dno]); plpgsql_check_recval_release(target); plpgsql_check_recval_assign_tupdesc(cstate, target, tupdesc, isnull); } else if (row != NULL) { int td_natts = tupdesc->natts; int fnum; int anum; anum = 0; for (fnum = 0; fnum < row->nfields; fnum++) { if (row->varnos[fnum] < 0) continue; /* skip dropped column in row struct */ while (anum < td_natts && TupleDescAttr(tupdesc, anum)->attisdropped) anum++; /* skip dropped column in tuple */ if (anum < td_natts) { Oid valtype = SPI_gettypeid(tupdesc, anum + 1); PLpgSQL_datum *target = cstate->estate->datums[row->varnos[fnum]]; switch (target->dtype) { case PLPGSQL_DTYPE_VAR: { PLpgSQL_var *var = (PLpgSQL_var *) target; plpgsql_check_assign_to_target_type(cstate, var->datatype->typoid, var->datatype->atttypmod, valtype, isnull); } break; case PLPGSQL_DTYPE_RECFIELD: { Oid expected_typoid; int expected_typmod; plpgsql_check_target(cstate, target->dno, &expected_typoid, &expected_typmod); plpgsql_check_assign_to_target_type(cstate, expected_typoid, expected_typmod, valtype, isnull); } break; default: ; /* nope */ } anum++; } } } } /* * recval_init, recval_release, recval_assign_tupdesc * * a set of functions designed to better portability between PostgreSQL 11 * with expanded records support and older PostgreSQL versions. */ void plpgsql_check_recval_init(PLpgSQL_rec *rec) { Assert(rec->dtype == PLPGSQL_DTYPE_REC); rec->erh = NULL; } void plpgsql_check_recval_release(PLpgSQL_rec *rec) { Assert(rec->dtype == PLPGSQL_DTYPE_REC); if (rec->erh) DeleteExpandedObject(ExpandedRecordGetDatum(rec->erh)); rec->erh = NULL; } /* * is_null is true, when we assign NULL expression and type should not be checked. */ void plpgsql_check_recval_assign_tupdesc(PLpgSQL_checkstate *cstate, PLpgSQL_rec *rec, TupleDesc tupdesc, bool is_null) { PLpgSQL_execstate *estate = cstate->estate; ExpandedRecordHeader *newerh; MemoryContext mcontext; TupleDesc var_tupdesc; Datum *newvalues; bool *newnulls; char *chunk; int vtd_natts; int i; mcontext = get_eval_mcontext(estate); plpgsql_check_recval_release(rec); /* * code is reduced version of make_expanded_record_for_rec */ if (rec->rectypeid != RECORDOID) { newerh = make_expanded_record_from_typeid(rec->rectypeid, -1, mcontext); } else { if (!tupdesc) return; newerh = make_expanded_record_from_tupdesc(tupdesc, mcontext); } /* * code is reduced version of exec_move_row_from_field */ var_tupdesc = expanded_record_get_tupdesc(newerh); vtd_natts = var_tupdesc->natts; if (!is_null && tupdesc != NULL && !compatible_tupdescs(var_tupdesc, tupdesc)) { int attn1 = 0; int attn2 = 0; int target_nfields = 0; int src_nfields = 0; bool src_field_is_valid = false; bool target_field_is_valid = false; Form_pg_attribute sattr = NULL; Form_pg_attribute tattr = NULL; while (attn1 < var_tupdesc->natts || attn2 < tupdesc->natts) { if (!target_field_is_valid && attn1 < var_tupdesc->natts) { tattr = TupleDescAttr(var_tupdesc, attn1); if (tattr->attisdropped) { attn1 += 1; continue; } target_field_is_valid = true; target_nfields += 1; } if (!src_field_is_valid && attn2 < tupdesc->natts) { sattr = TupleDescAttr(tupdesc, attn2); if (sattr->attisdropped) { attn2 += 1; continue; } src_field_is_valid = true; src_nfields += 1; } if (src_field_is_valid && target_field_is_valid) { plpgsql_check_assign_to_target_type(cstate, tattr->atttypid, tattr->atttypmod, sattr->atttypid, false); /* try to search next tuple of fields */ src_field_is_valid = false; target_field_is_valid = false; attn1 += 1; attn2 += 1; } else break; } if (src_nfields < target_nfields) plpgsql_check_put_error(cstate, 0, 0, "too few attributes for composite variable", NULL, NULL, PLPGSQL_CHECK_WARNING_OTHERS, 0, NULL, NULL); else if (src_nfields > target_nfields) plpgsql_check_put_error(cstate, 0, 0, "too many attributes for composite variable", NULL, NULL, PLPGSQL_CHECK_WARNING_OTHERS, 0, NULL, NULL); } chunk = eval_mcontext_alloc(estate, vtd_natts * (sizeof(Datum) + sizeof(bool))); newvalues = (Datum *) chunk; newnulls = (bool *) (chunk + vtd_natts * sizeof(Datum)); for (i = 0; i < vtd_natts; i++) { newvalues[i] = (Datum) 0; newnulls[i] = true; } expanded_record_set_fields(newerh, newvalues, newnulls, true); TransferExpandedRecord(newerh, estate->datum_context); rec->erh = newerh; } /* * compatible_tupdescs: detect whether two tupdescs are physically compatible * * TRUE indicates that a tuple satisfying src_tupdesc can be used directly as * a value for a composite variable using dst_tupdesc. */ static bool compatible_tupdescs(TupleDesc src_tupdesc, TupleDesc dst_tupdesc) { int i; /* Possibly we could allow src_tupdesc to have extra columns? */ if (dst_tupdesc->natts != src_tupdesc->natts) return false; for (i = 0; i < dst_tupdesc->natts; i++) { Form_pg_attribute dattr = TupleDescAttr(dst_tupdesc, i); Form_pg_attribute sattr = TupleDescAttr(src_tupdesc, i); if (dattr->attisdropped != sattr->attisdropped) return false; if (!dattr->attisdropped) { /* Normal columns must match by type and typmod */ if (dattr->atttypid != sattr->atttypid || (dattr->atttypmod >= 0 && dattr->atttypmod != sattr->atttypmod)) return false; } else { /* Dropped columns are OK as long as length/alignment match */ if (dattr->attlen != sattr->attlen || dattr->attalign != sattr->attalign) return false; } } return true; } plpgsql_check-2.7.8/src/catalog.c000066400000000000000000000201451465410532300167210ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * catalog.c * * routines for working with Postgres's catalog and caches * * by Pavel Stehule 2013-2024 * *------------------------------------------------------------------------- */ #include "plpgsql_check.h" #include "access/genam.h" #include "access/htup_details.h" #include "access/table.h" #include "catalog/pg_extension.h" #include "catalog/indexing.h" #include "catalog/pg_language.h" #include "catalog/pg_operator.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "commands/extension.h" #include "commands/proclang.h" #include "utils/builtins.h" #include "utils/catcache.h" #include "utils/fmgroids.h" #include "utils/lsyscache.h" #include "utils/syscache.h" #include "utils/regproc.h" #include "utils/rel.h" #include "catalog/pg_proc.h" #include "utils/syscache.h" static Oid plpgsql_check_PLpgSQLlanguageId = InvalidOid; /* * Fix - change of typename in Postgres 14 */ bool plpgsql_check_is_eventtriggeroid(Oid typoid) { #if PG_VERSION_NUM >= 140000 return typoid == EVENT_TRIGGEROID; #else return typoid == EVTTRIGGEROID; #endif } /* * Prepare metadata necessary for plpgsql_check */ void plpgsql_check_get_function_info(plpgsql_check_info *cinfo) { Form_pg_proc proc; char functyptype; proc = (Form_pg_proc) GETSTRUCT(cinfo->proctuple); functyptype = get_typtype(proc->prorettype); cinfo->trigtype = PLPGSQL_NOT_TRIGGER; cinfo->is_procedure = proc->prokind == PROKIND_PROCEDURE; /* * Disallow pseudotype result except for TRIGGER, RECORD, VOID, or * polymorphic */ if (functyptype == TYPTYPE_PSEUDO) { /* we assume OPAQUE with no arguments means a trigger */ if (proc->prorettype == TRIGGEROID #if PG_VERSION_NUM < 130000 || (proc->prorettype == OPAQUEOID && proc->pronargs == 0) #endif ) cinfo->trigtype = PLPGSQL_DML_TRIGGER; else if (plpgsql_check_is_eventtriggeroid(proc->prorettype)) cinfo->trigtype = PLPGSQL_EVENT_TRIGGER; else if (proc->prorettype != RECORDOID && proc->prorettype != VOIDOID && !IsPolymorphicType(proc->prorettype)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("PL/pgSQL functions cannot return type %s", format_type_be(proc->prorettype)))); } cinfo->volatility = proc->provolatile; cinfo->rettype = proc->prorettype; } char * plpgsql_check_get_src(HeapTuple procTuple) { Datum prosrcdatum; bool isnull; prosrcdatum = SysCacheGetAttr(PROCOID, procTuple, Anum_pg_proc_prosrc, &isnull); if (isnull) elog(ERROR, "null prosrc"); return TextDatumGetCString(prosrcdatum); } /* * Process necessary checking before code checking * a) disallow other than plpgsql check function, * b) when function is trigger function, then reloid must be defined */ void plpgsql_check_precheck_conditions(plpgsql_check_info *cinfo) { Form_pg_proc proc; char *funcname; proc = (Form_pg_proc) GETSTRUCT(cinfo->proctuple); funcname = format_procedure(cinfo->fn_oid); /* * The plpgsql_check can be loaded by shared_proload_libraries. That means * so in init time the access to system catalog can be impossible. So * plpgsql_check_PLpgSQLlanguageId should be initialized here. */ if (!OidIsValid(plpgsql_check_PLpgSQLlanguageId)) plpgsql_check_PLpgSQLlanguageId = get_language_oid("plpgsql", false); /* used language must be plpgsql */ if (proc->prolang != plpgsql_check_PLpgSQLlanguageId) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("%s is not a plpgsql function", funcname))); /* profiler doesn't require trigger data check */ if (!cinfo->show_profile) { /* dml trigger needs valid relid, others not */ if (cinfo->trigtype == PLPGSQL_DML_TRIGGER) { if (!OidIsValid(cinfo->relid)) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("missing trigger relation"), errhint("Trigger relation oid must be valid"))); } else { if (OidIsValid(cinfo->relid)) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("function is not trigger"), errhint("Trigger relation oid must not be valid for non dml trigger function."))); } } pfree(funcname); } #if PG_VERSION_NUM < 160000 /* * plpgsql_check_get_extension_schema - given an extension OID, fetch its extnamespace * * Returns InvalidOid if no such extension. */ static Oid get_extension_schema(Oid ext_oid) { Oid result; Relation rel; SysScanDesc scandesc; HeapTuple tuple; ScanKeyData entry[1]; rel = table_open(ExtensionRelationId, AccessShareLock); ScanKeyInit(&entry[0], Anum_pg_extension_oid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(ext_oid)); scandesc = systable_beginscan(rel, ExtensionOidIndexId, true, NULL, 1, entry); tuple = systable_getnext(scandesc); /* We assume that there can be at most one matching tuple */ if (HeapTupleIsValid(tuple)) result = ((Form_pg_extension) GETSTRUCT(tuple))->extnamespace; else result = InvalidOid; systable_endscan(scandesc); table_close(rel, AccessShareLock); return result; } #endif /* * get_extension_version - given an extension OID, look up the version * * Returns a palloc'd string, or NULL if no such extension. */ char * get_extension_version(Oid ext_oid) { char *result; Relation rel; SysScanDesc scandesc; HeapTuple tuple; ScanKeyData entry[1]; rel = table_open(ExtensionRelationId, AccessShareLock); ScanKeyInit(&entry[0], Anum_pg_extension_oid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(ext_oid)); scandesc = systable_beginscan(rel, ExtensionOidIndexId, true, NULL, 1, entry); tuple = systable_getnext(scandesc); /* We assume that there can be at most one matching tuple */ if (HeapTupleIsValid(tuple)) { Datum datum; bool isnull; datum = heap_getattr(tuple, Anum_pg_extension_extversion, RelationGetDescr(rel), &isnull); if (isnull) elog(ERROR, "extversion is null"); result = text_to_cstring(DatumGetTextPP(datum)); } else result = NULL; systable_endscan(scandesc); table_close(rel, AccessShareLock); return result; } /* * Returns oid of pragma function. It is used for elimination * pragma function from volatility tests. */ Oid plpgsql_check_pragma_func_oid(void) { Oid result = InvalidOid; Oid extoid; extoid = get_extension_oid("plpgsql_check", true); if (OidIsValid(extoid)) { CatCList *catlist; Oid schemaoid; int i; schemaoid = get_extension_schema(extoid); /* Search syscache by name only */ catlist = SearchSysCacheList1(PROCNAMEARGSNSP, CStringGetDatum("plpgsql_check_pragma")); for (i = 0; i < catlist->n_members; i++) { HeapTuple proctup = &catlist->members[i]->tuple; Form_pg_proc procform = (Form_pg_proc) GETSTRUCT(proctup); /* Consider only procs in specified namespace */ if (procform->pronamespace != schemaoid) continue; result = procform->oid; break; } ReleaseSysCacheList(catlist); } return result; } /* * Returns true, if function specified by oid is plpgsql function. */ bool plpgsql_check_is_plpgsql_function(Oid foid) { HeapTuple procTuple; Form_pg_proc procStruct; bool result; procTuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(foid)); if (!HeapTupleIsValid(procTuple)) return false; procStruct = (Form_pg_proc) GETSTRUCT(procTuple); /* * The plpgsql_check can be loaded by shared_proload_libraries. That means * so in init time the access to system catalog can be impossible. So * plpgsql_check_PLpgSQLlanguageId should be initialized here. */ if (!OidIsValid(plpgsql_check_PLpgSQLlanguageId)) plpgsql_check_PLpgSQLlanguageId = get_language_oid("plpgsql", false); result = procStruct->prolang == plpgsql_check_PLpgSQLlanguageId; ReleaseSysCache(procTuple); return result; } /* * plpgsql_check_get_op_namespace * returns the name space of the operator with the given opno */ Oid plpgsql_check_get_op_namespace(Oid opno) { HeapTuple tp; tp = SearchSysCache1(OPEROID, ObjectIdGetDatum(opno)); if (HeapTupleIsValid(tp)) { Form_pg_operator optup = (Form_pg_operator) GETSTRUCT(tp); ReleaseSysCache(tp); return optup->oprnamespace; } else return InvalidOid; } plpgsql_check-2.7.8/src/check_expr.c000066400000000000000000001274051465410532300174310ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * check_expr.c * * routines for enforce plans for every expr/query and * related checks over these plans. * * by Pavel Stehule 2013-2024 * *------------------------------------------------------------------------- */ #include "plpgsql_check.h" #include "access/tupconvert.h" #include "catalog/namespace.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "executor/spi_priv.h" #include "optimizer/clauses.h" #include "nodes/print.h" #include "optimizer/optimizer.h" #include "parser/parse_node.h" #if PG_VERSION_NUM >= 140000 #include "nodes/nodeFuncs.h" #include "parser/parse_coerce.h" #include "utils/builtins.h" #endif #include "tcop/utility.h" #include "utils/lsyscache.h" static void collect_volatility(PLpgSQL_checkstate *cstate, Query *query); static Query * ExprGetQuery(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr, CachedPlanSource *plansource); static CachedPlan * get_cached_plan(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr, bool *has_result_desc); static void plan_checks(PLpgSQL_checkstate *cstate, CachedPlan *cplan, char *query_str); static void prohibit_write_plan(PLpgSQL_checkstate *cstate, CachedPlan *cplan, char *query_str); static void prohibit_transaction_stmt(PLpgSQL_checkstate *cstate, CachedPlan *cplan, char *query_str); static void check_fishy_qual(PLpgSQL_checkstate *cstate, CachedPlan *cplan, char *query_str); static Const * expr_get_const(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr); static bool is_const_null_expr(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr); static void force_plan_checks(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr); static int RowGetValidFields(PLpgSQL_row *row); static int TupleDescNVatts(TupleDesc tupdesc); static PreParseColumnRefHook p_pre_columnref_hook = NULL; static PostParseColumnRefHook p_post_columnref_hook = NULL; static ParseParamRefHook p_paramref_hook = NULL; /* * Following routines wraps plpgsql sql parser's hooks. These wrappers * are used for identification of not used variables. Identification based * on plan postprocessing can raise false alarms when plan is not created * due some errors. */ static void parserhook_wrapper_update_used_variables(ParseState *pstate, Node *node) { if (node && IsA(node, Param)) { Param *p = (Param *) node; if (p->paramkind == PARAM_EXTERN) { PLpgSQL_checkstate *cstate; PLpgSQL_expr *expr; int dno = p->paramid - 1; expr = (PLpgSQL_expr *) pstate->p_ref_hook_state; Assert(expr); cstate = expr->func->cur_estate->plugin_info; if (!cstate || cstate->ci_magic != CI_MAGIC) return; if (bms_is_member(dno, expr->paramnos)) { #if PG_VERSION_NUM >= 140000 /* * PostgreSQL 14 and higher uses SQL parser for parsing * left side of assign statement too. We should to eliminate * it from used_variables (read) if we want to get warning * NEVER READ. */ if (dno != expr->target_param) #endif { MemoryContext oldcxt = MemoryContextSwitchTo(cstate->check_cxt); cstate->used_variables = bms_add_member(cstate->used_variables, dno); MemoryContextSwitchTo(oldcxt); } } } } } static Node * pre_column_ref(ParseState *pstate, ColumnRef *cref) { Node *result; result = p_pre_columnref_hook(pstate, cref); parserhook_wrapper_update_used_variables(pstate, result); return result; } static Node * post_column_ref(ParseState *pstate, ColumnRef *cref, Node *var) { Node *result; result = p_post_columnref_hook(pstate, cref, var); parserhook_wrapper_update_used_variables(pstate, result); return result; } static Node * param_ref(ParseState *pstate, ParamRef *pref) { Node *result; result = p_paramref_hook(pstate, pref); parserhook_wrapper_update_used_variables(pstate, result); return result; } static void plpgsql_parser_setup_wrapper(struct ParseState *pstate, PLpgSQL_expr *expr) { plpgsql_check__parser_setup_p(pstate, expr); /* * save previous hooks. * * The prepare_plan routine can be called recursively in passive mode. * the planner can try to execute any immutable functions with constant * parameters. Then saving old hooks to global variables is not 100% * correct, but the addresses should be same always. */ p_pre_columnref_hook = pstate->p_pre_columnref_hook; p_post_columnref_hook = pstate->p_post_columnref_hook; p_paramref_hook = pstate->p_paramref_hook; /* set new hooks */ pstate->p_pre_columnref_hook = pre_column_ref; pstate->p_post_columnref_hook = post_column_ref; pstate->p_paramref_hook = param_ref; } static void _prepare_plan(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr, int cursorOptions, ParserSetupHook parser_setup, void *arg) { SPIPlanPtr plan; if (expr->plan == NULL) { MemoryContext old_cxt; void *old_plugin_info; #if PG_VERSION_NUM >= 140000 SPIPrepareOptions options; memset(&options, 0, sizeof(options)); options.parserSetup = parser_setup ? parser_setup : (ParserSetupHook) plpgsql_parser_setup_wrapper; options.parserSetupArg = arg ? arg : (void *) expr; options.parseMode = expr->parseMode; options.cursorOptions = cursorOptions; #endif /* * The grammar can't conveniently set expr->func while building the parse * tree, so make sure it's set before parser hooks need it. */ expr->func = cstate->estate->func; old_plugin_info = expr->func->cur_estate->plugin_info; expr->func->cur_estate->plugin_info = cstate; PG_TRY(); { #if PG_VERSION_NUM >= 140000 /* * Generate and save the plan */ plan = SPI_prepare_extended(expr->query, &options); #else /* * Generate and save the plan */ plan = SPI_prepare_params(expr->query, parser_setup ? parser_setup : (ParserSetupHook) plpgsql_parser_setup_wrapper, arg ? arg : (void *) expr, cursorOptions); #endif expr->func->cur_estate->plugin_info = old_plugin_info; } PG_CATCH(); { expr->func->cur_estate->plugin_info = old_plugin_info; PG_RE_THROW(); } PG_END_TRY(); if (plan == NULL) { /* Some SPI errors deserve specific error messages */ switch (SPI_result) { case SPI_ERROR_COPY: ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot COPY to/from client in PL/pgSQL"))); break; case SPI_ERROR_TRANSACTION: ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot begin/end transactions in PL/pgSQL"), errhint("Use a BEGIN block with an EXCEPTION clause instead."))); break; default: elog(ERROR, "SPI_prepare_params failed for \"%s\": %s", expr->query, SPI_result_code_string(SPI_result)); } } /* * Save prepared plan to plpgsql_check state context. It will be * released on end of check, and it should be valid to this time. */ old_cxt = MemoryContextSwitchTo(cstate->check_cxt); expr->plan = SPI_saveplan(plan); /* This plan should be released later */ cstate->exprs = lappend(cstate->exprs, expr); MemoryContextSwitchTo(old_cxt); SPI_freeplan(plan); } } /* * Generate a prepared plan - this is simplified copy from pl_exec.c Is not * necessary to check simple plan. The planning is forced when plancache is * not valid. */ static void prepare_plan(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr, int cursorOptions, ParserSetupHook parser_setup, void *arg) { Query *query; CachedPlanSource *plansource = NULL; do { _prepare_plan(cstate, expr, cursorOptions, parser_setup, arg); plansource = plpgsql_check_get_plan_source(cstate, expr->plan); if (!plansource) return; if (!plansource->is_valid) expr->plan = NULL; } while (!plansource->is_valid); query = ExprGetQuery(cstate, expr, plansource); if (!query) return; /* there checks are common on every expr/query */ plpgsql_check_funcexpr(cstate, query, expr->query); collect_volatility(cstate, query); plpgsql_check_detect_dependency(cstate, query); } /* * Update function's volatility flag by query */ static void collect_volatility(PLpgSQL_checkstate *cstate, Query *query) { if (cstate->skip_volatility_check || cstate->volatility == PROVOLATILE_VOLATILE || !cstate->cinfo->performance_warnings) return; if (query->commandType == CMD_SELECT) { if (!query->hasModifyingCTE && !query->hasForUpdate) { /* there is chance so query will be immutable */ if (plpgsql_check_contain_volatile_functions((Node *) query, cstate)) cstate->volatility = PROVOLATILE_VOLATILE; else if (!plpgsql_check_contain_mutable_functions((Node *) query, cstate)) { /* * when level is still immutable, check if there * are not reference to tables. */ if (cstate->volatility == PROVOLATILE_IMMUTABLE) { if (plpgsql_check_has_rtable(query)) cstate->volatility = PROVOLATILE_STABLE; } } else cstate->volatility = PROVOLATILE_STABLE; } else cstate->volatility = PROVOLATILE_VOLATILE; } else /* not read only statements requare VOLATILE flag */ cstate->volatility = PROVOLATILE_VOLATILE; } /* * Validate plan and returns related node. */ CachedPlanSource * plpgsql_check_get_plan_source(PLpgSQL_checkstate *cstate, SPIPlanPtr plan) { CachedPlanSource *plansource = NULL; int nplans; if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC) elog(ERROR, "cached plan is not valid plan"); cstate->has_mp = false; nplans = list_length(plan->plancache_list); if (nplans > 1) { /* * We can allow multiple plans for commands executed by * EXECUTE command. Result of last plan is result. But * it can be allowed only in main query - not in parameters. */ if (cstate->allow_mp) { /* take last */ plansource = (CachedPlanSource *) llast(plan->plancache_list); cstate->has_mp = true; } else elog(ERROR, "plan is not single execution plany"); } else if (nplans == 1) plansource = (CachedPlanSource *) linitial(plan->plancache_list); return plansource; } /* * Returns Query node for expression * */ static Query * ExprGetQuery(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr, CachedPlanSource *plansource) { Query *result = NULL; Assert(plansource); Assert(plansource->is_valid); if (!plansource->query_list) elog(ERROR, "missing plan in plancache source"); /* * query_list has more fields, when rules are used. There * can be combination INSERT; NOTIFY */ if (list_length(plansource->query_list) > 1) { ListCell *lc; CmdType first_ctype = CMD_UNKNOWN; bool first = true; foreach (lc, plansource->query_list) { Query *query = (Query *) lfirst(lc); if (first) { first = false; first_ctype = query->commandType; result = query; } else { /* * When current command is SELECT, then first command * should be SELECT too */ if (query->commandType == CMD_SELECT) { if (first_ctype != CMD_SELECT) ereport(ERROR, (errmsg("there is not single query"), errdetail("plpgsql_check cannot detect result type"), errhint("Probably there are some unsupported (by plpgsql_check) rules on related tables"))); result = query; } } } } else result = linitial(plansource->query_list); cstate->was_pragma = false; /* the test of PRAGMA function call */ if (result->commandType == CMD_SELECT) { if (plansource->raw_parse_tree && plansource->raw_parse_tree->stmt && IsA(plansource->raw_parse_tree->stmt, SelectStmt)) { SelectStmt *selectStmt = (SelectStmt *) plansource->raw_parse_tree->stmt; if (selectStmt->targetList && IsA(linitial(selectStmt->targetList), ResTarget)) { ResTarget *rt = (ResTarget *) linitial(selectStmt->targetList); if (rt->val && IsA(rt->val, A_Const)) { A_Const *ac = (A_Const *) rt->val; bool is_perform_stmt; char *str = NULL; is_perform_stmt = (cstate->estate && cstate->estate->err_stmt && cstate->estate->err_stmt->cmd_type == PLPGSQL_STMT_PERFORM); #if PG_VERSION_NUM < 150000 if (ac->val.type == T_String) str = strVal(&(ac->val)); #else if (!ac->isnull && IsA(&ac->val, String)) str = strVal(&(ac->val)); #endif if (str && is_perform_stmt) { while (*str == ' ') str++; if (strncasecmp(str, "pragma:", 7) == 0) { cstate->was_pragma = true; plpgsql_check_pragma_apply(cstate, str + 7, expr->ns, cstate->estate->err_stmt->lineno); } } } else if (rt->val && IsA(rt->val, FuncCall)) { char *funcname; char *schemaname; FuncCall *fc = (FuncCall *) rt->val; DeconstructQualifiedName(fc->funcname, &schemaname, &funcname); if (strcmp(funcname, "plpgsql_check_pragma") == 0) { ListCell *lc; cstate->was_pragma = true; foreach(lc, fc->args) { Node *arg = (Node *) lfirst(lc); if (IsA(arg, A_Const)) { A_Const *ac = (A_Const *) arg; #if PG_VERSION_NUM < 150000 if (ac->val.type == T_String) plpgsql_check_pragma_apply(cstate, strVal(&(ac->val)), expr->ns, cstate->estate->err_stmt->lineno); #else if (!ac->isnull && IsA(&ac->val, String)) plpgsql_check_pragma_apply(cstate, strVal(&(ac->val)), expr->ns, cstate->estate->err_stmt->lineno); #endif } } } } } } } return result; } /* * Operations that requires cached plan * */ /* * Returns cached plan from plan cache. * */ static CachedPlan * get_cached_plan(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr, bool *has_result_desc) { CachedPlanSource *plansource = NULL; CachedPlan *cplan; plansource = plpgsql_check_get_plan_source(cstate, expr->plan); if (!plansource) { *has_result_desc = false; return NULL; } *has_result_desc = plansource->resultDesc ? true : false; #if PG_VERSION_NUM >= 140000 cplan = GetCachedPlan(plansource, NULL, NULL, NULL); #else cplan = GetCachedPlan(plansource, NULL, true, NULL); #endif return cplan; } /* * Process common checks on cached plan * */ static void plan_checks(PLpgSQL_checkstate *cstate, CachedPlan *cplan, char *query_str) { /* disallow write op in read only function */ prohibit_write_plan(cstate, cplan, query_str); /* detect bad casts in quals */ check_fishy_qual(cstate, cplan, query_str); /* disallow BEGIN TRANS, COMMIT, ROLLBACK, .. */ prohibit_transaction_stmt(cstate, cplan, query_str); } /* * Raise a error when plan is not read only */ static void prohibit_write_plan(PLpgSQL_checkstate *cstate, CachedPlan *cplan, char *query_str) { ListCell *lc; if (!cstate->estate->readonly_func) return; foreach(lc, cplan->stmt_list) { PlannedStmt *pstmt = (PlannedStmt *) lfirst(lc); if (!CommandIsReadOnly(pstmt)) { StringInfoData message; initStringInfo(&message); #if PG_VERSION_NUM >= 130000 appendStringInfo(&message, "%s is not allowed in a non volatile function", GetCommandTagName(CreateCommandTag((Node *) pstmt))); #else appendStringInfo(&message, "%s is not allowed in a non volatile function", CreateCommandTag((Node *) pstmt)); #endif plpgsql_check_put_error(cstate, ERRCODE_FEATURE_NOT_SUPPORTED, 0, message.data, NULL, NULL, PLPGSQL_CHECK_ERROR, 0, query_str, NULL); pfree(message.data); message.data = NULL; } } } /* * Raise a error when plan is a transactional statement */ static void prohibit_transaction_stmt(PLpgSQL_checkstate *cstate, CachedPlan *cplan, char *query_str) { ListCell *lc; foreach(lc, cplan->stmt_list) { Node *pstmt = (Node *) lfirst(lc); /* PostgtreSQL 10 can have one level of nesting more */ if (IsA(pstmt, PlannedStmt)) { PlannedStmt *planstmt = (PlannedStmt *) pstmt; if (planstmt->commandType == CMD_UTILITY) pstmt = (Node *) planstmt->utilityStmt; } if (IsA(pstmt, TransactionStmt)) { plpgsql_check_put_error(cstate, ERRCODE_FEATURE_NOT_SUPPORTED, 0, "cannot begin/end transactions in PL/pgSQL", NULL, "Use a BEGIN block with an EXCEPTION clause instead.", PLPGSQL_CHECK_ERROR, 0, query_str, NULL); } } } /* * Raise a performance warning when plan hash fishy qual */ static void check_fishy_qual(PLpgSQL_checkstate *cstate, CachedPlan *cplan, char *query_str) { ListCell *lc; if (!cstate->cinfo->performance_warnings) return; foreach(lc, cplan->stmt_list) { Param *param; PlannedStmt *pstmt = (PlannedStmt *) lfirst(lc); Plan *plan = NULL; /* Only plans can contains fishy quals */ if(!IsA(pstmt, PlannedStmt)) continue; plan = pstmt->planTree; if (plpgsql_check_qual_has_fishy_cast(pstmt, plan, ¶m)) { plpgsql_check_put_error(cstate, ERRCODE_DATATYPE_MISMATCH, 0, "implicit cast of attribute caused by different PLpgSQL variable type in WHERE clause", "An index of some attribute cannot be used, when variable, used in predicate, has not right type like a attribute", "Check a variable type - int versus numeric", PLPGSQL_CHECK_WARNING_PERFORMANCE, param->location, query_str, NULL); } } } Node * plpgsql_check_expr_get_node(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr, bool force_plan_checks) { PlannedStmt *_stmt; CachedPlan *cplan; Node *result = NULL; bool has_result_desc; cplan = get_cached_plan(cstate, expr, &has_result_desc); if (!has_result_desc) elog(ERROR, "expression does not return data"); /* do all checks for this plan, reduce a access to plan cache */ if (force_plan_checks) plan_checks(cstate, cplan, expr->query); _stmt = (PlannedStmt *) linitial(cplan->stmt_list); if (has_result_desc && IsA(_stmt, PlannedStmt) &&_stmt->commandType == CMD_SELECT) { Plan *_plan; _plan = _stmt->planTree; if ((IsA(_plan, Result) || IsA(_plan, ProjectSet)) && list_length(_plan->targetlist) == 1) { TargetEntry *tle; tle = (TargetEntry *) linitial(_plan->targetlist); result = (Node *) tle->expr; } } #if PG_VERSION_NUM >= 140000 ReleaseCachedPlan(cplan, NULL); #else ReleaseCachedPlan(cplan, true); #endif return result; } /* * Returns Const Value from expression if it is possible. * * Ensure all plan related checks on expression. * */ static Const * expr_get_const(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr) { Node *node = plpgsql_check_expr_get_node(cstate, expr, true); if (node && node->type == T_Const) return (Const *) node; return NULL; } /* * Returns true, when expr is constant NULL * */ static bool is_const_null_expr(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr) { Const *c; c = expr_get_const(cstate, expr); return c && c->constisnull ? true : false; } char * plpgsql_check_const_to_string(Node *node, int *location) { if (IsA(node, Const)) { Const *c = (Const *) node; if (location) *location = c->location; if (!c->constisnull) { Oid typoutput; bool typisvarlena; getTypeOutputInfo(c->consttype, &typoutput, &typisvarlena); return OidOutputFunctionCall(typoutput, c->constvalue); } } return NULL; } char * plpgsql_check_get_tracked_const(PLpgSQL_checkstate *cstate, Node *node) { if (!cstate->strconstvars) return NULL; if (cstate->pragma_vector.disable_constants_tracing) return NULL; if (IsA(node, Param)) { Param *p = (Param *) node; if (p->paramkind == PARAM_EXTERN && p->paramid > 0 && p->location != -1) { int dno = p->paramid - 1; if (cstate->strconstvars[dno]) return cstate->strconstvars[dno]; } } else if (IsA(node, CoerceViaIO)) { CoerceViaIO *coerce = (CoerceViaIO *) node; bool typispreferred; char typcategory; get_type_category_preferred(coerce->resulttype, &typcategory, &typispreferred); if (typcategory == 'S') return plpgsql_check_get_tracked_const(cstate, (Node *) coerce->arg); } return NULL; } char * plpgsql_check_get_const_string(PLpgSQL_checkstate *cstate, Node *node, int *location) { char *str; if (location) *location = -1; str = plpgsql_check_const_to_string(node, location); if (!str) str = plpgsql_check_get_tracked_const(cstate, node); return str; } /* * Returns string for any not null constant. isnull is true, * when constant is null. * */ char * plpgsql_check_expr_get_string(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr, int *location) { Node *node = plpgsql_check_expr_get_node(cstate, expr, true); if (!node) return NULL; return plpgsql_check_get_const_string(cstate, node, location); } static void force_plan_checks(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr) { CachedPlan *cplan; bool has_result_desc; cplan = get_cached_plan(cstate, expr, &has_result_desc); if (!cplan) return; /* do all checks for this plan, reduce a access to plan cache */ plan_checks(cstate, cplan, expr->query); #if PG_VERSION_NUM >= 140000 ReleaseCachedPlan(cplan, NULL); #else ReleaseCachedPlan(cplan, true); #endif } /* * No casts, no other checks * */ void plpgsql_check_expr_generic(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr) { prepare_plan(cstate, expr, 0, NULL, NULL); force_plan_checks(cstate, expr); } void plpgsql_check_expr_generic_with_parser_setup(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr, ParserSetupHook parser_setup, void *arg) { prepare_plan(cstate, expr, 0, parser_setup, arg); force_plan_checks(cstate, expr); } /* * Top level checks - forces prepare_plan, protected by subtransaction. * */ /* * Verify to possible cast to bool, integer, .. * */ void plpgsql_check_expr_with_scalar_type(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr, Oid expected_typoid, bool required) { ResourceOwner oldowner; MemoryContext oldCxt = CurrentMemoryContext; if (!expr) { if (required) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("required expression is empty"))); return; } oldowner = CurrentResourceOwner; BeginInternalSubTransaction(NULL); MemoryContextSwitchTo(oldCxt); PG_TRY(); { TupleDesc tupdesc; bool is_immutable_null; prepare_plan(cstate, expr, 0, NULL, NULL); /* record all variables used by the query */ cstate->used_variables = bms_add_members(cstate->used_variables, expr->paramnos); tupdesc = plpgsql_check_expr_get_desc(cstate, expr, false, true, true, NULL); is_immutable_null = is_const_null_expr(cstate, expr); if (tupdesc) { /* when we know value or type */ if (!is_immutable_null) plpgsql_check_assign_to_target_type(cstate, expected_typoid, -1, TupleDescAttr(tupdesc, 0)->atttypid, is_immutable_null); ReleaseTupleDesc(tupdesc); } ReleaseCurrentSubTransaction(); MemoryContextSwitchTo(oldCxt); CurrentResourceOwner = oldowner; } PG_CATCH(); { ErrorData *edata; MemoryContextSwitchTo(oldCxt); edata = CopyErrorData(); FlushErrorState(); RollbackAndReleaseCurrentSubTransaction(); MemoryContextSwitchTo(oldCxt); CurrentResourceOwner = oldowner; /* * If fatal_errors is true, we just propagate the error up to the * highest level. Otherwise the error is appended to our current list * of errors, and we continue checking. */ if (cstate->cinfo->fatal_errors) ReThrowError(edata); else plpgsql_check_put_error_edata(cstate, edata); MemoryContextSwitchTo(oldCxt); } PG_END_TRY(); } /* * Checks used for RETURN QUERY * */ void plpgsql_check_returned_expr(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr, bool is_expression) { PLpgSQL_execstate *estate = cstate->estate; PLpgSQL_function *func = estate->func; bool is_return_query = !is_expression; ResourceOwner oldowner; MemoryContext oldCxt = CurrentMemoryContext; oldowner = CurrentResourceOwner; BeginInternalSubTransaction(NULL); MemoryContextSwitchTo(oldCxt); PG_TRY(); { TupleDesc tupdesc; bool is_immutable_null; Oid first_level_typ = InvalidOid; prepare_plan(cstate, expr, 0, NULL, NULL); /* record all variables used by the query, should be after prepare_plan */ cstate->used_variables = bms_add_members(cstate->used_variables, expr->paramnos); tupdesc = plpgsql_check_expr_get_desc(cstate, expr, false, true, is_expression, &first_level_typ); is_immutable_null = is_const_null_expr(cstate, expr); /* try to identify obsolete return refcursor's value */ if (cstate->estate->func->fn_rettype == REFCURSOROID && cstate->cinfo->compatibility_warnings) { Node *node = plpgsql_check_expr_get_node(cstate, expr, false); bool is_ok = true; if (IsA((Node *) node, Const)) { /* only NULL constant argument is ok */ if (!((Const *) node)->constisnull) is_ok = false; } else if (IsA((Node *) node, Param)) { /* only variable of refcursor is ok */ if (((Param *) node)->paramtype != REFCURSOROID) is_ok = false; } else is_ok = false; if (!is_ok) plpgsql_check_put_error(cstate, 0, 0, "obsolete setting of refcursor or cursor variable", "Internal name of cursor should not be specified by users.", NULL, PLPGSQL_CHECK_WARNING_COMPATIBILITY, 0, NULL, NULL); } if (tupdesc) { /* enforce check for trigger function - result must be composit */ if (func->fn_retistuple && is_expression && !(type_is_rowtype(TupleDescAttr(tupdesc, 0)->atttypid) || type_is_rowtype(first_level_typ) || tupdesc->natts > 1)) { /* but we should to allow NULL */ if (!is_immutable_null) plpgsql_check_put_error(cstate, ERRCODE_DATATYPE_MISMATCH, 0, "cannot return non-composite value from function returning composite type", NULL, NULL, PLPGSQL_CHECK_ERROR, 0, NULL, NULL); } /* tupmap is used when function returns tuple or RETURN QUERY was used */ else if (func->fn_retistuple || is_return_query) { /* should to know expected result */ if (!cstate->fake_rtd && estate->rsi && IsA(estate->rsi, ReturnSetInfo)) { TupleDesc rettupdesc = estate->rsi->expectedDesc; TupleConversionMap *tupmap ; tupmap = convert_tuples_by_position(tupdesc, rettupdesc, !is_expression ? gettext_noop("structure of query does not match function result type") : gettext_noop("returned record type does not match expected record type")); if (tupmap) free_conversion_map(tupmap); } } else { /* returns scalar */ if (!IsPolymorphicType(func->fn_rettype)) { plpgsql_check_assign_to_target_type(cstate, func->fn_rettype, -1, TupleDescAttr(tupdesc, 0)->atttypid, is_immutable_null); } } ReleaseTupleDesc(tupdesc); } ReleaseCurrentSubTransaction(); MemoryContextSwitchTo(oldCxt); CurrentResourceOwner = oldowner; } PG_CATCH(); { ErrorData *edata; MemoryContextSwitchTo(oldCxt); edata = CopyErrorData(); FlushErrorState(); RollbackAndReleaseCurrentSubTransaction(); MemoryContextSwitchTo(oldCxt); CurrentResourceOwner = oldowner; /* * If fatal_errors is true, we just propagate the error up to the * highest level. Otherwise the error is appended to our current list * of errors, and we continue checking. */ if (cstate->cinfo->fatal_errors) ReThrowError(edata); else plpgsql_check_put_error_edata(cstate, edata); MemoryContextSwitchTo(oldCxt); } PG_END_TRY(); } /* * Release all string constants related to variables from row variable */ static void free_string_constant(PLpgSQL_checkstate *cstate, PLpgSQL_row *row) { int fnum; for (fnum = 0; fnum < row->nfields; fnum++) { PLpgSQL_datum *field; int varno = row->varnos[fnum]; /* skip dropped fields */ if (varno < 0) continue; /* the assigned value is not constant, reset current */ if (cstate->strconstvars && cstate->strconstvars[varno]) { pfree(cstate->strconstvars[varno]); cstate->strconstvars[varno] = NULL; } field = cstate->estate->datums[varno]; if (field->dtype == PLPGSQL_DTYPE_ROW) free_string_constant(cstate, (PLpgSQL_row *) field); } } /* * Check expression as rvalue - on right in assign statement. It is used for * only expression check too - when target is unknown. * */ void plpgsql_check_expr_as_rvalue(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr, PLpgSQL_rec *targetrec, PLpgSQL_row *targetrow, int targetdno, bool use_element_type, bool is_expression) { ResourceOwner oldowner; MemoryContext oldCxt = CurrentMemoryContext; TupleDesc tupdesc; bool is_immutable_null; volatile bool expand = true; Oid first_level_typoid; Oid expected_typoid = InvalidOid; int expected_typmod = InvalidOid; if (targetdno != -1) { plpgsql_check_target(cstate, targetdno, &expected_typoid, &expected_typmod); /* * When target variable is not compossite, then we should not * to expand result tupdesc. */ if (!type_is_rowtype(expected_typoid)) expand = false; #if PG_VERSION_NUM >= 140000 expr->target_param = targetdno; } else expr->target_param = -1; #else } #endif /* * SELECT INTO for composite target type doesn't do * expand. */ if (targetrec || targetrow) { if (cstate->estate) { PLpgSQL_stmt *stmt = cstate->estate->err_stmt; if (stmt && (stmt->cmd_type == PLPGSQL_STMT_EXECSQL || stmt->cmd_type == PLPGSQL_STMT_DYNEXECUTE)) { expand = false; } } } oldowner = CurrentResourceOwner; BeginInternalSubTransaction(NULL); MemoryContextSwitchTo(oldCxt); PG_TRY(); { prepare_plan(cstate, expr, 0, NULL, NULL); /* record all variables used by the query */ #if PG_VERSION_NUM >= 140000 if (expr->target_param != -1) { int target_dno = expr->target_param; Node *node; Oid target_typoid = InvalidOid; Oid value_typoid = InvalidOid; node = plpgsql_check_expr_get_node(cstate, expr, false); if (bms_is_member(target_dno, expr->paramnos)) { /* recheck if target_dno is really used on right side of assignment */ if (!plpgsql_check_vardno_is_used_for_reading(node, target_dno)) { Bitmapset *paramnos; /* create set without target_param */ paramnos = bms_copy(expr->paramnos); paramnos = bms_del_member(paramnos, expr->target_param); cstate->used_variables = bms_add_members(cstate->used_variables, paramnos); bms_free(paramnos); } else cstate->used_variables = bms_add_members(cstate->used_variables, expr->paramnos); } else cstate->used_variables = bms_add_members(cstate->used_variables, expr->paramnos); if (node && IsA(node, SubscriptingRef)) node = (Node *) ((SubscriptingRef *) node)->refassgnexpr; /* check implicit coercion */ if (node && IsA(node, FuncExpr)) { FuncExpr *fexpr = (FuncExpr *) node; if (fexpr->funcformat == COERCE_IMPLICIT_CAST) { target_typoid = fexpr->funcresulttype; value_typoid = exprType(linitial(fexpr->args)); } } else if (node && IsA(node, CoerceViaIO)) { CoerceViaIO *cexpr = (CoerceViaIO *) node; if (cexpr->coerceformat == COERCE_IMPLICIT_CAST) { target_typoid = cexpr->resulttype; value_typoid = exprType((Node *) cexpr->arg); } } if (target_typoid != value_typoid) { StringInfoData str; initStringInfo(&str); appendStringInfo(&str, "cast \"%s\" value to \"%s\" type", format_type_be(value_typoid), format_type_be(target_typoid)); /* accent warning when cast is without supported explicit casting */ if (!can_coerce_type(1, &value_typoid, &target_typoid, COERCION_EXPLICIT)) plpgsql_check_put_error(cstate, ERRCODE_DATATYPE_MISMATCH, 0, "target type is different type than source type", str.data, "There are no possible explicit coercion between those types, possibly bug!", PLPGSQL_CHECK_WARNING_OTHERS, 0, NULL, NULL); else if (!can_coerce_type(1, &value_typoid, &target_typoid, COERCION_ASSIGNMENT)) plpgsql_check_put_error(cstate, ERRCODE_DATATYPE_MISMATCH, 0, "target type is different type than source type", str.data, "The input expression type does not have an assignment cast to the target type.", PLPGSQL_CHECK_WARNING_OTHERS, 0, NULL, NULL); else plpgsql_check_put_error(cstate, ERRCODE_DATATYPE_MISMATCH, 0, "target type is different type than source type", str.data, "Hidden casting can be a performance issue.", PLPGSQL_CHECK_WARNING_PERFORMANCE, 0, NULL, NULL); pfree(str.data); } } else cstate->used_variables = bms_add_members(cstate->used_variables, expr->paramnos); #else cstate->used_variables = bms_add_members(cstate->used_variables, expr->paramnos); #endif /* * there is a possibility to call a plpgsql_pragma like default for some aux * variable. When we detect this case, then we mark target variable as used * variable. */ if (cstate->was_pragma && targetdno != -1) cstate->used_variables = bms_add_member(cstate->used_variables, targetdno); tupdesc = plpgsql_check_expr_get_desc(cstate, expr, use_element_type, expand, is_expression, &first_level_typoid); is_immutable_null = is_const_null_expr(cstate, expr); /* try to identify obsolete setting of refcursor's variables */ if (cstate->cinfo->compatibility_warnings && targetdno != -1) { PLpgSQL_var *var = (PLpgSQL_var *) cstate->estate->datums[targetdno]; if (var->dtype == PLPGSQL_DTYPE_VAR && var->datatype->typoid == REFCURSOROID) { Node *node = plpgsql_check_expr_get_node(cstate, expr, false); bool is_declare_cursor; bool is_ok = true; is_declare_cursor = cstate->estate->err_stmt && cstate->estate->err_stmt->cmd_type == PLPGSQL_STMT_BLOCK && var->cursor_explicit_expr; if (IsA((Node *) node, Const)) { /* only NULL constant argument is ok */ if (!((Const *) node)->constisnull) is_ok = false; } else if (IsA((Node *) node, Param)) { /* only variable of refcursor is ok */ if (((Param *) node)->paramtype != REFCURSOROID) is_ok = false; } else is_ok = false; /* * when the assignment is still ok, check an immutability of bound cursor */ if (is_ok && var->cursor_explicit_expr) { if (!is_immutable_null) is_ok = false; } /* Don't raise warnings for old DECLARE CURSOR AS stmts */ if (!is_ok && !is_declare_cursor) plpgsql_check_put_error(cstate, 0, 0, "obsolete setting of refcursor or cursor variable", "Internal name of cursor should not be specified by users.", NULL, PLPGSQL_CHECK_WARNING_COMPATIBILITY, 0, NULL, NULL); } } /* try to detect safe variables */ if (cstate->cinfo->security_warnings && targetdno != -1) { PLpgSQL_var *var = (PLpgSQL_var *) cstate->estate->datums[targetdno]; if (var->dtype == PLPGSQL_DTYPE_VAR) { bool typispreferred; char typcategory; get_type_category_preferred(var->datatype->typoid, &typcategory, &typispreferred); if (typcategory == 'S') { Node *node = plpgsql_check_expr_get_node(cstate, expr, false); int location; if (plpgsql_check_is_sql_injection_vulnerable(cstate, expr, node, &location)) cstate->safe_variables = bms_del_member(cstate->safe_variables, targetdno); else cstate->safe_variables = bms_add_member(cstate->safe_variables, targetdno); } } } if (cstate->cinfo->constants_tracing && targetrow) { /* * We cannot to cut constants with results now, but we have to * reset all possible string constants saved in variables from * row variable. */ free_string_constant(cstate, targetrow); } else if (cstate->cinfo->constants_tracing && targetdno != -1) { char *str; str = plpgsql_check_expr_get_string(cstate, expr, NULL); if (str) { PLpgSQL_stmt_stack_item *current = cstate->top_stmt_stack; MemoryContext oldcxt = MemoryContextSwitchTo(cstate->check_cxt); char *prev_val; Assert(cstate->top_stmt_stack); if (!cstate->strconstvars) cstate->strconstvars = palloc0(sizeof(char *) * cstate->estate->ndatums); /* * We need to do copy string first. There is an possibility to self * reference, and then we need to first copy, and after that free. */ prev_val = cstate->strconstvars[targetdno]; cstate->strconstvars[targetdno] = pstrdup(str); if (prev_val) pfree(prev_val); current->invalidate_strconstvars = bms_add_member(current->invalidate_strconstvars, targetdno); MemoryContextSwitchTo(oldcxt); } else { /* the assigned value is not constant, reset current */ if (cstate->strconstvars && cstate->strconstvars[targetdno]) { pfree(cstate->strconstvars[targetdno]); cstate->strconstvars[targetdno] = NULL; } } } if (expected_typoid != InvalidOid && type_is_rowtype(expected_typoid) && first_level_typoid != InvalidOid) { /* simple error, scalar source to composite target */ if (!type_is_rowtype(first_level_typoid) && !is_immutable_null) { plpgsql_check_put_error(cstate, ERRCODE_DATATYPE_MISMATCH, 0, "cannot assign scalar variable to composite target", NULL, NULL, PLPGSQL_CHECK_ERROR, 0, NULL, NULL); goto no_other_check; } /* simple ok, target and source composite types are same */ if (type_is_rowtype(first_level_typoid) && first_level_typoid != RECORDOID && first_level_typoid == expected_typoid) goto no_other_check; } if (tupdesc) { if (targetrow != NULL || targetrec != NULL) plpgsql_check_assign_tupdesc_row_or_rec(cstate, targetrow, targetrec, tupdesc, is_immutable_null); if (targetdno != -1) plpgsql_check_assign_tupdesc_dno(cstate, targetdno, tupdesc, is_immutable_null); if (targetrow) { if (RowGetValidFields(targetrow) > TupleDescNVatts(tupdesc)) plpgsql_check_put_error(cstate, 0, 0, "too few attributes for target variables", "There are more target variables than output columns in query.", "Check target variables in SELECT INTO statement.", PLPGSQL_CHECK_WARNING_OTHERS, 0, NULL, NULL); else if (RowGetValidFields(targetrow) < TupleDescNVatts(tupdesc)) plpgsql_check_put_error(cstate, 0, 0, "too many attributes for target variables", "There are less target variables than output columns in query.", "Check target variables in SELECT INTO statement", PLPGSQL_CHECK_WARNING_OTHERS, 0, NULL, NULL); } } no_other_check: if (tupdesc) ReleaseTupleDesc(tupdesc); ReleaseCurrentSubTransaction(); MemoryContextSwitchTo(oldCxt); CurrentResourceOwner = oldowner; } PG_CATCH(); { ErrorData *edata; MemoryContextSwitchTo(oldCxt); edata = CopyErrorData(); FlushErrorState(); RollbackAndReleaseCurrentSubTransaction(); MemoryContextSwitchTo(oldCxt); CurrentResourceOwner = oldowner; /* * If fatal_errors is true, we just propagate the error up to the * highest level. Otherwise the error is appended to our current list * of errors, and we continue checking. */ if (cstate->cinfo->fatal_errors) ReThrowError(edata); else plpgsql_check_put_error_edata(cstate, edata); MemoryContextSwitchTo(oldCxt); } PG_END_TRY(); } /* * Check a SQL statement, should not to return data * */ void plpgsql_check_expr_as_sqlstmt_nodata(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr) { if (expr && plpgsql_check_expr_as_sqlstmt(cstate, expr)) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("query has no destination for result data"))); } /* * Check a SQL statement, should to return data * */ void plpgsql_check_expr_as_sqlstmt_data(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr) { if (expr && !plpgsql_check_expr_as_sqlstmt(cstate, expr)) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("query does not return data"))); } /* * Check a SQL statement, can (not) returs data. Returns true * when statement returns data - we are able to get tuple descriptor. */ bool plpgsql_check_expr_as_sqlstmt(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr) { ResourceOwner oldowner; MemoryContext oldCxt = CurrentMemoryContext; volatile bool result = false; if (!expr) return true; oldowner = CurrentResourceOwner; BeginInternalSubTransaction(NULL); MemoryContextSwitchTo(oldCxt); PG_TRY(); { TupleDesc tupdesc; prepare_plan(cstate, expr, 0, NULL, NULL); /* record all variables used by the query */ cstate->used_variables = bms_add_members(cstate->used_variables, expr->paramnos); force_plan_checks(cstate, expr); tupdesc = plpgsql_check_expr_get_desc(cstate, expr, false, false, false, NULL); if (tupdesc) { result = true; ReleaseTupleDesc(tupdesc); } ReleaseCurrentSubTransaction(); MemoryContextSwitchTo(oldCxt); CurrentResourceOwner = oldowner; } PG_CATCH(); { ErrorData *edata; MemoryContextSwitchTo(oldCxt); edata = CopyErrorData(); FlushErrorState(); RollbackAndReleaseCurrentSubTransaction(); MemoryContextSwitchTo(oldCxt); CurrentResourceOwner = oldowner; /* * If fatal_errors is true, we just propagate the error up to the * highest level. Otherwise the error is appended to our current list * of errors, and we continue checking. */ if (cstate->cinfo->fatal_errors) ReThrowError(edata); else plpgsql_check_put_error_edata(cstate, edata); MemoryContextSwitchTo(oldCxt); } PG_END_TRY(); return result; } /* * Verify an assignment of 'expr' to 'target' with possible slices * * it is used in FOREACH ARRAY where SLICE change a target type * */ void plpgsql_check_assignment_with_possible_slices(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr, PLpgSQL_rec *targetrec, PLpgSQL_row *targetrow, int targetdno, bool use_element_type) { bool is_expression = (targetrec == NULL && targetrow == NULL); if (expr) plpgsql_check_expr_as_rvalue(cstate, expr, targetrec, targetrow, targetdno, use_element_type, is_expression); } /* * Verify a expression * */ void plpgsql_check_expr(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr) { if (expr) plpgsql_check_expr_as_rvalue(cstate, expr, NULL, NULL, -1, false, true); } /* * Verify an assignment of 'expr' to 'target' * */ void plpgsql_check_assignment(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr, PLpgSQL_rec *targetrec, PLpgSQL_row *targetrow, int targetdno) { bool is_expression = (targetrec == NULL && targetrow == NULL); plpgsql_check_expr_as_rvalue(cstate, expr, targetrec, targetrow, targetdno, false, is_expression); } void plpgsql_check_assignment_to_variable(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr, PLpgSQL_variable *targetvar, int targetdno) { if (targetvar != NULL) { if (targetvar->dtype == PLPGSQL_DTYPE_ROW) plpgsql_check_expr_as_rvalue(cstate, expr, NULL, (PLpgSQL_row *) targetvar, targetdno, false, false); else if (targetvar->dtype == PLPGSQL_DTYPE_REC) plpgsql_check_expr_as_rvalue(cstate, expr, (PLpgSQL_rec *) targetvar, NULL, targetdno, false, false); else elog(ERROR, "unsupported target variable type"); } else plpgsql_check_expr_as_rvalue(cstate, expr, NULL, NULL, targetdno, false, true); } /* * row->nfields can cound dropped columns. When this behave can raise * false alarms, we should to count fields more precisely. */ static int RowGetValidFields(PLpgSQL_row *row) { int i; int result = 0; for (i = 0; i < row->nfields; i++) { if (row->varnos[i] != -1) result += 1; } return result; } /* * returns number of valid fields */ static int TupleDescNVatts(TupleDesc tupdesc) { int natts = 0; int i; for (i = 0; i < tupdesc->natts; i++) { if (!TupleDescAttr(tupdesc, i)->attisdropped) natts += 1; } return natts; } plpgsql_check-2.7.8/src/check_function.c000066400000000000000000001053471465410532300203010ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * check_function.c * * workhorse functionality of this extension - expression * and query validator * * by Pavel Stehule 2013-2024 * *------------------------------------------------------------------------- */ #include "plpgsql_check.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "nodes/makefuncs.h" #include "utils/guc.h" #include "utils/memutils.h" #include "utils/lsyscache.h" #include "utils/rel.h" #include "utils/syscache.h" #include "utils/typcache.h" #include "access/heapam.h" static HTAB *plpgsql_check_HashTable = NULL; bool plpgsql_check_other_warnings = false; bool plpgsql_check_extra_warnings = false; bool plpgsql_check_performance_warnings = false; bool plpgsql_check_compatibility_warnings = false; bool plpgsql_check_fatal_errors = true; bool plpgsql_check_constants_tracing = true; int plpgsql_check_mode = PLPGSQL_CHECK_MODE_BY_FUNCTION; /* ---------- * Hash table for checked functions * ---------- */ typedef struct plpgsql_hashent { PLpgSQL_func_hashkey key; TransactionId fn_xmin; ItemPointerData fn_tid; bool is_checked; } plpgsql_check_HashEnt; static void function_check(PLpgSQL_function *func, PLpgSQL_checkstate *cstate); static void trigger_check(PLpgSQL_function *func, Node *tdata, PLpgSQL_checkstate *cstate); static void release_exprs(List *exprs); static int load_configuration(HeapTuple procTuple, bool *reload_config); static void init_datum_dno(PLpgSQL_checkstate *cstate, int dno, bool is_auto, bool is_protected); static PLpgSQL_datum * copy_plpgsql_datum(PLpgSQL_checkstate *cstate, PLpgSQL_datum *datum); static void setup_estate(PLpgSQL_execstate *estate, PLpgSQL_function *func, ReturnSetInfo *rsi, plpgsql_check_info *cinfo); static void setup_cstate(PLpgSQL_checkstate *cstate, plpgsql_check_result_info *result_info, plpgsql_check_info *cinfo, bool is_active_mode, bool fake_rtd); static void passive_check_func_beg(PLpgSQL_execstate *estate, PLpgSQL_function *func, void **plugin2_info); static plpgsql_check_plugin2 check_plugin2 = { NULL, passive_check_func_beg, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; /* * Prepare list of OUT variables for later report */ static void collect_out_variables(PLpgSQL_function *func, PLpgSQL_checkstate *cstate) { cstate->out_variables = NULL; if (func->out_param_varno != -1) { int varno = func->out_param_varno; PLpgSQL_variable *var = (PLpgSQL_variable *) func->datums[varno]; if (var->dtype == PLPGSQL_DTYPE_ROW && is_internal_variable(cstate, var)) { /* this function has more OUT parameters */ PLpgSQL_row *row = (PLpgSQL_row*) var; int fnum; for (fnum = 0; fnum < row->nfields; fnum++) cstate->out_variables = bms_add_member(cstate->out_variables, row->varnos[fnum]); } else cstate->out_variables = bms_add_member(cstate->out_variables, varno); } } /* * Returns true, when routine should be closed by RETURN statement * */ static bool return_is_required(plpgsql_check_info *cinfo) { if (cinfo->is_procedure) return false; if (cinfo->rettype == VOIDOID) return false; return true; } /* * own implementation - active mode * */ void plpgsql_check_function_internal(plpgsql_check_result_info *ri, plpgsql_check_info *cinfo) { PLpgSQL_checkstate cstate; PLpgSQL_function *volatile function = NULL; bool reload_config; LOCAL_FCINFO(fake_fcinfo, 0); FmgrInfo flinfo; TriggerData trigdata; EventTriggerData etrigdata; Trigger tg_trigger; int rc; ResourceOwner oldowner; PLpgSQL_execstate *cur_estate = NULL; MemoryContext old_cxt; PLpgSQL_execstate estate; ReturnSetInfo rsinfo; bool fake_rtd; /* * Connect to SPI manager */ if ((rc = SPI_connect()) != SPI_OK_CONNECT) elog(ERROR, "SPI_connect failed: %s", SPI_result_code_string(rc)); plpgsql_check_setup_fcinfo(cinfo, &flinfo, fake_fcinfo, &rsinfo, &trigdata, &etrigdata, &tg_trigger, &fake_rtd); setup_cstate(&cstate, ri, cinfo, true, fake_rtd); old_cxt = MemoryContextSwitchTo(cstate.check_cxt); /* * Copy argument names for later check, only when other warnings are required. * Argument names are used for check parameter versus local variable collision. */ if (cinfo->other_warnings) { int numargs; Oid *argtypes; char **argnames; char *argmodes; numargs = get_func_arg_info(cinfo->proctuple, &argtypes, &argnames, &argmodes); if (argnames != NULL) { int i; for (i = 0; i < numargs; i++) { if (argnames[i][0] != '\0') cstate.argnames = lappend(cstate.argnames, argnames[i]); } } } oldowner = CurrentResourceOwner; PG_TRY(); { int save_nestlevel = 0; BeginInternalSubTransaction(NULL); MemoryContextSwitchTo(cstate.check_cxt); save_nestlevel = load_configuration(cinfo->proctuple, &reload_config); /* have to wait for this decision to loaded configuration */ if (plpgsql_check_mode != PLPGSQL_CHECK_MODE_DISABLED) { /* Get a compiled function */ function = plpgsql_check__compile_p(fake_fcinfo, false); collect_out_variables(function, &cstate); /* Must save and restore prior value of cur_estate */ cur_estate = function->cur_estate; /* recheck trigtype */ Assert(function->fn_is_trigger == cinfo->trigtype); setup_estate(&estate, function, (ReturnSetInfo *) fake_fcinfo->resultinfo, cinfo); cstate.estate = &estate; /* * Mark the function as busy, ensure higher than zero usage. There is no * reason for protection function against delete, but I afraid of asserts. */ function->use_count++; /* Create a fake runtime environment and process check */ switch (cinfo->trigtype) { case PLPGSQL_DML_TRIGGER: trigger_check(function, (Node *) &trigdata, &cstate); break; case PLPGSQL_EVENT_TRIGGER: trigger_check(function, (Node *) &etrigdata, &cstate); break; case PLPGSQL_NOT_TRIGGER: function_check(function, &cstate); break; } function->cur_estate = cur_estate; function->use_count--; } else elog(NOTICE, "plpgsql_check is disabled"); /* * reload back a GUC. XXX: isn't this done automatically by subxact * rollback? */ if (reload_config) AtEOXact_GUC(true, save_nestlevel); RollbackAndReleaseCurrentSubTransaction(); MemoryContextSwitchTo(cstate.check_cxt); CurrentResourceOwner = oldowner; if (OidIsValid(cinfo->relid)) relation_close(trigdata.tg_relation, AccessShareLock); release_exprs(cstate.exprs); } PG_CATCH(); { ErrorData *edata; MemoryContextSwitchTo(cstate.check_cxt); edata = CopyErrorData(); FlushErrorState(); RollbackAndReleaseCurrentSubTransaction(); MemoryContextSwitchTo(cstate.check_cxt); CurrentResourceOwner = oldowner; if (OidIsValid(cinfo->relid)) relation_close(trigdata.tg_relation, AccessShareLock); if (function) { function->cur_estate = cur_estate; function->use_count--; release_exprs(cstate.exprs); } plpgsql_check_put_error_edata(&cstate, edata); } PG_END_TRY(); MemoryContextSwitchTo(old_cxt); MemoryContextDelete(cstate.check_cxt); /* * Disconnect from SPI manager */ if ((rc = SPI_finish()) != SPI_OK_FINISH) elog(ERROR, "SPI_finish failed: %s", SPI_result_code_string(rc)); } /* * plpgsql_check_on_func_beg - passive mode * * callback function - called by plgsql executor, when function is started * and local variables are initialized. * */ static void passive_check_func_beg(PLpgSQL_execstate *estate, PLpgSQL_function *func, void **plugin2_info) { const char *err_text = estate->err_text; int closing; List *exceptions; if (plpgsql_check_mode == PLPGSQL_CHECK_MODE_FRESH_START || plpgsql_check_mode == PLPGSQL_CHECK_MODE_EVERY_START) { int i; PLpgSQL_rec *saved_records; PLpgSQL_var *saved_vars; MemoryContext oldcontext, old_cxt; ResourceOwner oldowner; plpgsql_check_result_info ri; plpgsql_check_info cinfo; PLpgSQL_checkstate cstate; /* * don't allow repeated execution on checked function * when it is not requsted. */ if (plpgsql_check_mode == PLPGSQL_CHECK_MODE_FRESH_START && plpgsql_check_is_checked(func)) return; plpgsql_check_mark_as_checked(func); memset(&ri, 0, sizeof(ri)); plpgsql_check_info_init(&cinfo, func->fn_oid); if (OidIsValid(func->fn_oid)) { cinfo.proctuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(func->fn_oid)); if (!HeapTupleIsValid(cinfo.proctuple)) elog(ERROR, "cache lookup failed for function %u", func->fn_oid); plpgsql_check_get_function_info(&cinfo); ReleaseSysCache(cinfo.proctuple); cinfo.proctuple = NULL; cinfo.fn_oid = func->fn_oid; } else cinfo.volatility = PROVOLATILE_VOLATILE; cinfo.fatal_errors = plpgsql_check_fatal_errors, cinfo.other_warnings = plpgsql_check_other_warnings, cinfo.performance_warnings = plpgsql_check_performance_warnings, cinfo.extra_warnings = plpgsql_check_extra_warnings, cinfo.compatibility_warnings = plpgsql_check_compatibility_warnings; cinfo.constants_tracing = plpgsql_check_constants_tracing; ri.format = PLPGSQL_CHECK_FORMAT_ELOG; setup_cstate(&cstate, &ri, &cinfo, false, false); collect_out_variables(func, &cstate); /* use real estate */ cstate.estate = estate; old_cxt = MemoryContextSwitchTo(cstate.check_cxt); /* * During the check stage a rec and vars variables are modified, so we should * to save their content */ saved_records = palloc(sizeof(PLpgSQL_rec) * estate->ndatums); saved_vars = palloc(sizeof(PLpgSQL_var) * estate->ndatums); for (i = 0; i < estate->ndatums; i++) { if (estate->datums[i]->dtype == PLPGSQL_DTYPE_REC) { PLpgSQL_rec *rec = (PLpgSQL_rec *) estate->datums[i]; memcpy(&saved_records[i], rec, sizeof(PLpgSQL_rec)); if (rec->erh) { /* work with dummy copy */ rec->erh = make_expanded_record_from_exprecord(rec->erh, cstate.check_cxt); } } else if (estate->datums[i]->dtype == PLPGSQL_DTYPE_VAR) { PLpgSQL_var *var = (PLpgSQL_var *) estate->datums[i]; saved_vars[i].value = var->value; saved_vars[i].isnull = var->isnull; saved_vars[i].freeval = var->freeval; var->freeval = false; } } estate->err_text = NULL; /* * Raised exception should be trapped in outer functtion. Protection * against outer trap is QUERY_CANCELED exception. */ oldcontext = CurrentMemoryContext; oldowner = CurrentResourceOwner; PG_TRY(); { /* * Now check the toplevel block of statements */ plpgsql_check_stmt(&cstate, (PLpgSQL_stmt *) func->action, &closing, &exceptions); estate->err_stmt = NULL; if (!cstate.stop_check) { if (closing != PLPGSQL_CHECK_CLOSED && closing != PLPGSQL_CHECK_CLOSED_BY_EXCEPTIONS && return_is_required(cstate.cinfo)) plpgsql_check_put_error(&cstate, ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT, 0, "control reached end of function without RETURN", NULL, NULL, closing == PLPGSQL_CHECK_UNCLOSED ? PLPGSQL_CHECK_ERROR : PLPGSQL_CHECK_WARNING_EXTRA, 0, NULL, NULL); plpgsql_check_report_unused_variables(&cstate); plpgsql_check_report_too_high_volatility(&cstate); } release_exprs(cstate.exprs); } PG_CATCH(); { ErrorData *edata; /* Save error info */ MemoryContextSwitchTo(oldcontext); edata = CopyErrorData(); FlushErrorState(); CurrentResourceOwner = oldowner; release_exprs(cstate.exprs); edata->sqlerrcode = ERRCODE_QUERY_CANCELED; ReThrowError(edata); } PG_END_TRY(); estate->err_text = err_text; estate->err_stmt = NULL; /* return back a original rec variables */ for (i = 0; i < estate->ndatums; i++) { if (estate->datums[i]->dtype == PLPGSQL_DTYPE_REC) { PLpgSQL_rec *rec = (PLpgSQL_rec *) estate->datums[i]; memcpy(rec, &saved_records[i], sizeof(PLpgSQL_rec)); } else if (estate->datums[i]->dtype == PLPGSQL_DTYPE_VAR) { PLpgSQL_var *var = (PLpgSQL_var *) estate->datums[i]; var->value = saved_vars[i].value; var->isnull = saved_vars[i].isnull; var->freeval = saved_vars[i].freeval; } } MemoryContextSwitchTo(old_cxt); MemoryContextDelete(cstate.check_cxt); } } void plpgsql_check_passive_check_init(void) { plpgsql_check_register_pldbgapi2_plugin(&check_plugin2); } /* * Check function - it prepare variables and starts a prepare plan walker * */ static void function_check(PLpgSQL_function *func, PLpgSQL_checkstate *cstate) { int i; int closing = PLPGSQL_CHECK_UNCLOSED; List *exceptions; ListCell *lc; /* * Make local execution copies of all the datums */ for (i = 0; i < cstate->estate->ndatums; i++) cstate->estate->datums[i] = copy_plpgsql_datum(cstate, func->datums[i]); init_datum_dno(cstate, cstate->estate->found_varno, true, true); /* * check function's parameters to not be reserved keywords */ foreach(lc, cstate->argnames) { char *argname = (char *) lfirst(lc); if (plpgsql_check_is_reserved_keyword(argname)) { StringInfoData str; initStringInfo(&str); appendStringInfo(&str, "name of parameter \"%s\" is reserved keyword", argname); plpgsql_check_put_error(cstate, 0, 0, str.data, "The reserved keyword was used as parameter name.", NULL, PLPGSQL_CHECK_WARNING_OTHERS, 0, NULL, NULL); pfree(str.data); } } /* * Store the actual call argument values (fake) into the appropriate * variables */ for (i = 0; i < func->fn_nargs; i++) { init_datum_dno(cstate, func->fn_argvarnos[i], false, false); } /* * Now check the toplevel block of statements */ plpgsql_check_stmt(cstate, (PLpgSQL_stmt *) func->action, &closing, &exceptions); /* clean state values - next errors are not related to any command */ cstate->estate->err_stmt = NULL; if (!cstate->stop_check) { if (closing != PLPGSQL_CHECK_CLOSED && closing != PLPGSQL_CHECK_CLOSED_BY_EXCEPTIONS && return_is_required(cstate->cinfo)) plpgsql_check_put_error(cstate, ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT, 0, "control reached end of function without RETURN", NULL, NULL, closing == PLPGSQL_CHECK_UNCLOSED ? PLPGSQL_CHECK_ERROR : PLPGSQL_CHECK_WARNING_EXTRA, 0, NULL, NULL); plpgsql_check_report_unused_variables(cstate); plpgsql_check_report_too_high_volatility(cstate); } } /* * Check trigger - prepare fake environments for testing trigger * */ static void trigger_check(PLpgSQL_function *func, Node *tdata, PLpgSQL_checkstate *cstate) { int i; int closing = PLPGSQL_CHECK_UNCLOSED; List *exceptions; /* * Make local execution copies of all the datums */ for (i = 0; i < cstate->estate->ndatums; i++) cstate->estate->datums[i] = copy_plpgsql_datum(cstate, func->datums[i]); init_datum_dno(cstate, cstate->estate->found_varno, true, true); if (IsA(tdata, TriggerData)) { PLpgSQL_rec *rec_new, *rec_old; TriggerData *trigdata = (TriggerData *) tdata; /* * Put the OLD and NEW tuples into record variables * * We make the tupdescs available in both records even though only one * may have a value. This allows parsing of record references to * succeed in functions that are used for multiple trigger types. For * example, we might have a test like "if (TG_OP = 'INSERT' and * NEW.foo = 'xyz')", which should parse regardless of the current * trigger type. */ /* * find all PROMISE VARIABLES and initit their */ for (i = 0; i < func->ndatums; i++) { PLpgSQL_datum *datum = func->datums[i]; if (datum->dtype == PLPGSQL_DTYPE_PROMISE) init_datum_dno(cstate, datum->dno, true, datum->dno != func->new_varno && datum->dno != func->old_varno); } rec_new = (PLpgSQL_rec *) (cstate->estate->datums[func->new_varno]); plpgsql_check_recval_assign_tupdesc(cstate, rec_new, trigdata->tg_relation->rd_att, false); rec_old = (PLpgSQL_rec *) (cstate->estate->datums[func->old_varno]); plpgsql_check_recval_assign_tupdesc(cstate, rec_old, trigdata->tg_relation->rd_att, false); } else if (IsA(tdata, EventTriggerData)) { /* do nothing */ } else elog(ERROR, "unexpected environment"); /* * Now check the toplevel block of statements */ plpgsql_check_stmt(cstate, (PLpgSQL_stmt *) func->action, &closing, &exceptions); /* clean state values - next errors are not related to any command */ cstate->estate->err_stmt = NULL; if (!cstate->stop_check) { if (closing != PLPGSQL_CHECK_CLOSED && closing != PLPGSQL_CHECK_CLOSED_BY_EXCEPTIONS && return_is_required(cstate->cinfo)) plpgsql_check_put_error(cstate, ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT, 0, "control reached end of function without RETURN", NULL, NULL, closing == PLPGSQL_CHECK_UNCLOSED ? PLPGSQL_CHECK_ERROR : PLPGSQL_CHECK_WARNING_EXTRA, 0, NULL, NULL); plpgsql_check_report_unused_variables(cstate); plpgsql_check_report_too_high_volatility(cstate); } } /* * Returns true when some fields is polymorphics */ static bool is_polymorphic_tupdesc(TupleDesc tupdesc) { int i; for (i = 0; i < tupdesc->natts; i++) if (IsPolymorphicType(TupleDescAttr(tupdesc, i)->atttypid)) return true; return false; } /* * Replaces polymorphic types by real type */ static Oid replace_polymorphic_type(plpgsql_check_info *cinfo, Oid typ, Oid anyelement_array_oid, bool is_array_anyelement, Oid anycompatible_array_oid, bool is_array_anycompatible, bool is_variadic) { /* quite compiler warnings */ (void) anycompatible_array_oid; (void) is_array_anycompatible; if (OidIsValid(typ) && IsPolymorphicType(typ)) { switch (typ) { case ANYELEMENTOID: typ = is_variadic ? anyelement_array_oid : cinfo->anyelementoid; break; case ANYNONARRAYOID: if (is_array_anyelement) elog(ERROR, "anyelement type is a array (expected nonarray)"); typ = is_variadic ? anyelement_array_oid : cinfo->anyelementoid; break; case ANYENUMOID: /* XXX dubious */ if (!OidIsValid(cinfo->anyenumoid)) elog(ERROR, "anyenumtype option should be specified (anyenum type is used)"); if (!type_is_enum(cinfo->anyenumoid)) elog(ERROR, "type specified by anyenumtype option is not enum"); typ = cinfo->anyenumoid; break; case ANYARRAYOID: typ = anyelement_array_oid; break; case ANYRANGEOID: typ = is_variadic ? get_array_type(cinfo->anyrangeoid) : cinfo->anyrangeoid; break; #if PG_VERSION_NUM >= 130000 case ANYCOMPATIBLEOID: typ = is_variadic ? anycompatible_array_oid : cinfo->anycompatibleoid; break; case ANYCOMPATIBLENONARRAYOID: if (is_array_anycompatible) elog(ERROR, "anycompatible type is a array (expected nonarray)"); typ = is_variadic ? anycompatible_array_oid : cinfo->anycompatibleoid; break; case ANYCOMPATIBLEARRAYOID: typ = anycompatible_array_oid; break; case ANYCOMPATIBLERANGEOID: typ = is_variadic ? get_array_type(cinfo->anycompatiblerangeoid) : cinfo->anycompatiblerangeoid; break; #endif default: /* fallback */ typ = is_variadic ? INT4ARRAYOID : INT4OID; } } return typ; } /* * Set up a fake fcinfo with just enough info to satisfy plpgsql_compile(). * * There should be a different real argtypes for polymorphic params. * * When output fake_rtd is true, then we should to not compare result fields, * because we know nothing about expected result. */ void plpgsql_check_setup_fcinfo(plpgsql_check_info *cinfo, FmgrInfo *flinfo, FunctionCallInfo fcinfo, ReturnSetInfo *rsinfo, TriggerData *trigdata, EventTriggerData *etrigdata, Trigger *tg_trigger, bool *fake_rtd) { TupleDesc resultTupleDesc; int nargs; Oid *argtypes; char **argnames; char *argmodes; Oid rettype; bool found_polymorphic = false; *fake_rtd = false; /* clean structures */ MemSet(fcinfo, 0, SizeForFunctionCallInfo(0)); MemSet(flinfo, 0, sizeof(FmgrInfo)); MemSet(rsinfo, 0, sizeof(ReturnSetInfo)); fcinfo->flinfo = flinfo; flinfo->fn_oid = cinfo->fn_oid; flinfo->fn_mcxt = CurrentMemoryContext; rettype = cinfo->rettype; if (cinfo->trigtype == PLPGSQL_DML_TRIGGER) { Assert(trigdata != NULL); MemSet(trigdata, 0, sizeof(TriggerData)); MemSet(tg_trigger, 0, sizeof(Trigger)); trigdata->type = T_TriggerData; trigdata->tg_trigger = tg_trigger; fcinfo->context = (Node *) trigdata; if (OidIsValid(cinfo->relid)) trigdata->tg_relation = relation_open(cinfo->relid, AccessShareLock); } else if (cinfo->trigtype == PLPGSQL_EVENT_TRIGGER) { MemSet(etrigdata, 0, sizeof(etrigdata)); etrigdata->type = T_EventTriggerData; fcinfo->context = (Node *) etrigdata; } /* prepare call expression - used for polymorphic arguments */ nargs = get_func_arg_info(cinfo->proctuple, &argtypes, &argnames, &argmodes); if (nargs > 0) { int i; for (i = 0; i < nargs; i++) { Oid argtype = InvalidOid; if (argmodes) { if (argmodes[i] == FUNC_PARAM_IN || argmodes[i] == FUNC_PARAM_INOUT || argmodes[i] == FUNC_PARAM_VARIADIC) argtype = argtypes[i]; } else argtype = argtypes[i]; if (OidIsValid(argtype) && IsPolymorphicType(argtype)) { found_polymorphic = true; break; } } if (found_polymorphic) { List *args = NIL; Oid anyelement_array_oid; Oid anyelement_base_oid; bool is_array_anyelement; Oid anycompatible_array_oid; Oid anycompatible_base_oid; bool is_array_anycompatible; anyelement_array_oid = get_array_type(cinfo->anyelementoid); anyelement_base_oid = getBaseType(cinfo->anyelementoid); is_array_anyelement = OidIsValid(get_element_type(anyelement_base_oid)); #if PG_VERSION_NUM >= 130000 anycompatible_array_oid = get_array_type(cinfo->anycompatibleoid); anycompatible_base_oid = getBaseType(cinfo->anycompatibleoid); is_array_anycompatible = OidIsValid(get_element_type(anycompatible_base_oid)); #else anycompatible_array_oid = InvalidOid; anycompatible_base_oid = InvalidOid; is_array_anycompatible = false; (void) anycompatible_base_oid; #endif /* * when polymorphic types are used, then we need to build fake fn_expr, * to be in plpgsql_resolve_polymorphic_argtypes happy. */ for (i = 0; i < nargs; i++) { bool is_variadic = false; Oid argtype = InvalidOid; if (argmodes) { if (argmodes[i] == FUNC_PARAM_IN || argmodes[i] == FUNC_PARAM_INOUT || argmodes[i] == FUNC_PARAM_VARIADIC) { argtype = argtypes[i]; if (argmodes[i] == FUNC_PARAM_VARIADIC) is_variadic = true; } } else argtype = argtypes[i]; if (OidIsValid(argtype)) { argtype = replace_polymorphic_type(cinfo, argtype, anyelement_array_oid, is_array_anyelement, anycompatible_array_oid, is_array_anycompatible, is_variadic); args = lappend(args, makeNullConst(argtype, -1, InvalidOid)); } } rettype = replace_polymorphic_type(cinfo, rettype, anyelement_array_oid, is_array_anyelement, anycompatible_array_oid, is_array_anycompatible, false); fcinfo->flinfo->fn_expr = (Node *) makeFuncExpr(cinfo->fn_oid, rettype, args, InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); } } if (argtypes) pfree(argtypes); if (argnames) pfree(argnames); if (argmodes) pfree(argmodes); /* * prepare ReturnSetInfo * * necessary for RETURN NEXT and RETURN QUERY * */ resultTupleDesc = build_function_result_tupdesc_t(cinfo->proctuple); if (resultTupleDesc) { /* we cannot to solve polymorphic params now */ if (is_polymorphic_tupdesc(resultTupleDesc)) { FreeTupleDesc(resultTupleDesc); resultTupleDesc = NULL; } } else if (cinfo->rettype == TRIGGEROID #if PG_VERSION_NUM < 130000 || cinfo->rettype == OPAQUEOID #endif ) { /* trigger - return value should be ROW or RECORD based on relid */ if (trigdata->tg_relation) resultTupleDesc = CreateTupleDescCopy(trigdata->tg_relation->rd_att); } else if (!IsPolymorphicType(cinfo->rettype)) { if (get_typtype(cinfo->rettype) == TYPTYPE_COMPOSITE) resultTupleDesc = lookup_rowtype_tupdesc_copy(cinfo->rettype, -1); else { *fake_rtd = cinfo->rettype == RECORDOID; resultTupleDesc = CreateTemplateTupleDesc(1); TupleDescInitEntry(resultTupleDesc, (AttrNumber) 1, "__result__", cinfo->rettype, -1, 0); resultTupleDesc = BlessTupleDesc(resultTupleDesc); } } else { if (IsPolymorphicType(cinfo->rettype)) { /* * ensure replacament of polymorphic rettype, but this * error is checked in validation stage, so this case * should not be possible. */ if (IsPolymorphicType(rettype)) elog(ERROR, "return type is still polymorphic"); } } if (resultTupleDesc) { fcinfo->resultinfo = (Node *) rsinfo; rsinfo->type = T_ReturnSetInfo; rsinfo->expectedDesc = resultTupleDesc; rsinfo->allowedModes = (int) (SFRM_ValuePerCall | SFRM_Materialize); rsinfo->returnMode = SFRM_ValuePerCall; /* * ExprContext is created inside CurrentMemoryContext, * without any additional source allocation. It is released * on end of transaction. */ rsinfo->econtext = CreateStandaloneExprContext(); } } /* ---------- * Initialize a plpgsql fake execution state * ---------- */ static void setup_estate(PLpgSQL_execstate *estate, PLpgSQL_function *func, ReturnSetInfo *rsi, plpgsql_check_info *cinfo) { /* this link will be restored at exit from plpgsql_call_handler */ func->cur_estate = estate; estate->func = func; estate->retval = (Datum) 0; estate->retisnull = true; estate->rettype = InvalidOid; estate->fn_rettype = func->fn_rettype; estate->retistuple = func->fn_retistuple; estate->retisset = func->fn_retset; estate->readonly_func = func->fn_readonly; estate->eval_econtext = makeNode(ExprContext); estate->eval_econtext->ecxt_per_tuple_memory = AllocSetContextCreate(CurrentMemoryContext, "ExprContext", ALLOCSET_DEFAULT_SIZES); estate->datum_context = CurrentMemoryContext; estate->exitlabel = NULL; estate->cur_error = NULL; estate->tuple_store = NULL; if (rsi) { estate->tuple_store_cxt = rsi->econtext->ecxt_per_query_memory; estate->tuple_store_owner = CurrentResourceOwner; estate->tuple_store_desc = rsi->expectedDesc; } else { estate->tuple_store_cxt = NULL; estate->tuple_store_owner = NULL; } estate->rsi = rsi; estate->found_varno = func->found_varno; estate->ndatums = func->ndatums; estate->datums = palloc(sizeof(PLpgSQL_datum *) * estate->ndatums); /* caller is expected to fill the datums array */ estate->eval_tuptable = NULL; estate->eval_processed = 0; if (cinfo->oldtable) { EphemeralNamedRelation enr = palloc(sizeof(EphemeralNamedRelationData)); int rc PG_USED_FOR_ASSERTS_ONLY; enr->md.name = cinfo->oldtable; enr->md.reliddesc = cinfo->relid; enr->md.tupdesc = NULL; enr->md.enrtype = ENR_NAMED_TUPLESTORE; enr->md.enrtuples = 0; enr->reldata = NULL; rc = SPI_register_relation(enr); Assert(rc >= 0); } if (cinfo->newtable) { EphemeralNamedRelation enr = palloc(sizeof(EphemeralNamedRelationData)); int rc PG_USED_FOR_ASSERTS_ONLY; enr->md.name = cinfo->newtable; enr->md.reliddesc = cinfo->relid; enr->md.tupdesc = NULL; enr->md.enrtype = ENR_NAMED_TUPLESTORE; enr->md.enrtuples = 0; enr->reldata = NULL; rc = SPI_register_relation(enr); Assert(rc >= 0); } estate->err_stmt = NULL; estate->err_text = NULL; estate->plugin_info = NULL; } /* * prepare PLpgSQL_checkstate structure * */ static void setup_cstate(PLpgSQL_checkstate *cstate, plpgsql_check_result_info *result_info, plpgsql_check_info *cinfo, bool is_active_mode, bool fake_rtd) { cstate->ci_magic = CI_MAGIC; cstate->decl_volatility = cinfo->volatility; cstate->has_execute_stmt = false; cstate->volatility = PROVOLATILE_IMMUTABLE; cstate->skip_volatility_check = (cinfo->rettype == TRIGGEROID || #if PG_VERSION_NUM < 130000 cinfo->rettype == OPAQUEOID || #endif plpgsql_check_is_eventtriggeroid(cinfo->rettype) || cinfo->is_procedure); cstate->estate = NULL; cstate->result_info = result_info; cstate->cinfo = cinfo; cstate->argnames = NIL; cstate->exprs = NIL; cstate->used_variables = NULL; cstate->modif_variables = NULL; cstate->out_variables = NULL; cstate->top_stmt_stack = NULL; cstate->is_active_mode = is_active_mode; cstate->func_oids = NULL; cstate->rel_oids = NULL; cstate->check_cxt = AllocSetContextCreate(CurrentMemoryContext, "plpgsql_check temporary cxt", ALLOCSET_DEFAULT_SIZES); cstate->found_return_query = false; cstate->found_return_dyn_query = false; cstate->fake_rtd = fake_rtd; cstate->safe_variables = NULL; cstate->protected_variables = NULL; cstate->auto_variables = NULL; cstate->typed_variables = NULL; cstate->stop_check = false; cstate->allow_mp = false; cstate->pragma_vector.disable_check = false; cstate->pragma_vector.disable_other_warnings = false; cstate->pragma_vector.disable_performance_warnings = false; cstate->pragma_vector.disable_extra_warnings = false; cstate->pragma_vector.disable_security_warnings = false; cstate->pragma_vector.disable_compatibility_warnings = false; cstate->pragma_vector.disable_constants_tracing = false; /* try to find oid of plpgsql_check pragma function */ cstate->pragma_foid = plpgsql_check_pragma_func_oid(); /* for simple string constants tracing */ cstate->strconstvars = NULL; } /* * Loads function's configuration * * Before checking function we have to load configuration related to * function. This is function manager job, but we don't use it for checking. */ static int load_configuration(HeapTuple procTuple, bool *reload_config) { Datum datum; bool isnull; int new_nest_level; *reload_config = false; new_nest_level = 0; datum = SysCacheGetAttr(PROCOID, procTuple, Anum_pg_proc_proconfig, &isnull); if (!isnull) { ArrayType *set_items; /* Set per-function configuration parameters */ set_items = DatumGetArrayTypeP(datum); if (set_items != NULL) { /* Need a new GUC nesting level */ new_nest_level = NewGUCNestLevel(); *reload_config = true; ProcessGUCArray(set_items, (superuser() ? PGC_SUSET : PGC_USERSET), PGC_S_SESSION, GUC_ACTION_SAVE); } } return new_nest_level; } /* * Release all plans created in check time * */ static void release_exprs(List *exprs) { ListCell *l; foreach(l, exprs) { PLpgSQL_expr *expr = (PLpgSQL_expr *) lfirst(l); SPI_freeplan(expr->plan); expr->plan = NULL; } } /* * Initialize plpgsql datum to NULL. This routine is used only for function * and trigger parameters so it should not support all dtypes. * */ static void init_datum_dno(PLpgSQL_checkstate *cstate, int dno, bool is_auto, bool is_protected) { switch (cstate->estate->datums[dno]->dtype) { case PLPGSQL_DTYPE_PROMISE: case PLPGSQL_DTYPE_VAR: { PLpgSQL_var *var = (PLpgSQL_var *) cstate->estate->datums[dno]; var->value = (Datum) 0; var->isnull = true; var->freeval = false; } break; case PLPGSQL_DTYPE_REC: { PLpgSQL_rec *rec = (PLpgSQL_rec *) cstate->estate->datums[dno]; plpgsql_check_recval_init(rec); plpgsql_check_recval_assign_tupdesc(cstate, rec, NULL, false); } break; case PLPGSQL_DTYPE_ROW: { PLpgSQL_row *row = (PLpgSQL_row *) cstate->estate->datums[dno]; int fnum; for (fnum = 0; fnum < row->nfields; fnum++) { if (row->varnos[fnum] < 0) continue; /* skip dropped column in row struct */ init_datum_dno(cstate, row->varnos[fnum], is_auto, is_protected); } } break; default: elog(ERROR, "unexpected dtype: %d", cstate->estate->datums[dno]->dtype); } if (is_protected) cstate->protected_variables = bms_add_member(cstate->protected_variables, dno); if (is_auto) cstate->auto_variables = bms_add_member(cstate->auto_variables, dno); } /* * initializing local execution variables * */ static PLpgSQL_datum * copy_plpgsql_datum(PLpgSQL_checkstate *cstate, PLpgSQL_datum *datum) { PLpgSQL_datum *result; switch (datum->dtype) { case PLPGSQL_DTYPE_VAR: case PLPGSQL_DTYPE_PROMISE: { PLpgSQL_var *new = palloc(sizeof(PLpgSQL_var)); memcpy(new, datum, sizeof(PLpgSQL_var)); /* Ensure the value is null (possibly not needed?) */ new->value = 0; new->isnull = true; new->freeval = false; result = (PLpgSQL_datum *) new; } break; case PLPGSQL_DTYPE_REC: { PLpgSQL_rec *new = palloc(sizeof(PLpgSQL_rec)); memcpy(new, datum, sizeof(PLpgSQL_rec)); /* Ensure the value is well initialized with correct type */ plpgsql_check_recval_init(new); plpgsql_check_recval_assign_tupdesc(cstate, new, NULL, false); result = (PLpgSQL_datum *) new; } break; case PLPGSQL_DTYPE_ROW: case PLPGSQL_DTYPE_RECFIELD: #if PG_VERSION_NUM < 140000 case PLPGSQL_DTYPE_ARRAYELEM: #endif /* * These datum records are read-only at runtime, so no need to * copy them (well, ARRAYELEM contains some cached type data, but * we'd just as soon centralize the caching anyway) */ result = datum; break; default: elog(ERROR, "unrecognized dtype: %d", datum->dtype); result = NULL; /* keep compiler quiet */ break; } return result; } void plpgsql_check_HashTableInit(void) { HASHCTL ctl; /* don't allow double-initialization */ Assert(plpgsql_check_HashTable == NULL); memset(&ctl, 0, sizeof(ctl)); ctl.keysize = sizeof(PLpgSQL_func_hashkey); ctl.entrysize = sizeof(plpgsql_check_HashEnt); plpgsql_check_HashTable = hash_create("plpgsql_check function cache", FUNCS_PER_USER, &ctl, HASH_ELEM | HASH_BLOBS); } /* * Returns true, when function is marked as checked already * */ bool plpgsql_check_is_checked(PLpgSQL_function *func) { plpgsql_check_HashEnt *hentry; if (!func->fn_hashkey) return false; hentry = (plpgsql_check_HashEnt *) hash_search(plpgsql_check_HashTable, (void *) func->fn_hashkey, HASH_FIND, NULL); if (hentry != NULL && hentry->fn_xmin == func->fn_xmin && ItemPointerEquals(&hentry->fn_tid, &func->fn_tid)) return hentry->is_checked; return false; } /* * Protect function agains repeated checking * */ void plpgsql_check_mark_as_checked(PLpgSQL_function *func) { /* don't try to mark anonymous code blocks */ if (func->fn_oid != InvalidOid) { plpgsql_check_HashEnt *hentry; bool found; hentry = (plpgsql_check_HashEnt *) hash_search(plpgsql_check_HashTable, (void *) func->fn_hashkey, HASH_ENTER, &found); hentry->fn_xmin = func->fn_xmin; hentry->fn_tid = func->fn_tid; hentry->is_checked = true; } } plpgsql_check-2.7.8/src/cursors_leaks.c000066400000000000000000000161451465410532300201730ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * cursors_leak.c * * detection unclosed cursors code * * by Pavel Stehule 2013-2024 * *------------------------------------------------------------------------- */ #include "plpgsql_check.h" #include "plpgsql_check_builtins.h" #include "storage/proc.h" #include "utils/builtins.h" #include "utils/guc.h" #include "utils/memutils.h" bool plpgsql_check_cursors_leaks = true; bool plpgsql_check_cursors_leaks_strict = false; int plpgsql_check_cursors_leaks_level = WARNING; #define MAX_NAMES_PER_STATEMENT 20 typedef struct { int stmtid; int rec_level; char *curname; } CursorTrace; typedef struct { Oid fn_oid; TransactionId fn_xmin; } FunctionTraceKey; typedef struct { FunctionTraceKey key; int ncursors; int cursors_size; CursorTrace *cursors_traces; } FunctionTrace; typedef struct { FunctionTrace *ftrace; LocalTransactionId lxid; } CursorLeaksPlugin2Info; MemoryContextCallback contextCallback; static LocalTransactionId traces_lxid = InvalidLocalTransactionId; static HTAB *traces = NULL; static MemoryContext traces_mcxt = NULL; static void func_setup(PLpgSQL_execstate *estate, PLpgSQL_function *func, void **plugin2_info); static void func_end(PLpgSQL_execstate *estate, PLpgSQL_function *func, void **plugin2_info); static void stmt_end(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt, void **plugin2_info); static plpgsql_check_plugin2 cursors_leaks_plugin2 = { func_setup, NULL, func_end, NULL, NULL, stmt_end, NULL, NULL, NULL, NULL, NULL, NULL }; #if PG_VERSION_NUM >= 170000 #define CURRENT_LXID (MyProc->vxid.lxid) #else #define CURRENT_LXID (MyProc->lxid) #endif static FunctionTrace * get_function_trace(PLpgSQL_function *func) { bool found; FunctionTrace *ftrace; FunctionTraceKey key; if (traces == NULL || traces_lxid != CURRENT_LXID) { HASHCTL ctl; traces_mcxt = AllocSetContextCreate(TopTransactionContext, "plpgsql_check - trace cursors", ALLOCSET_DEFAULT_SIZES); memset(&ctl, 0, sizeof(ctl)); ctl.keysize = sizeof(FunctionTraceKey); ctl.entrysize = sizeof(FunctionTrace); ctl.hcxt = traces_mcxt; traces = hash_create("plpgsql_checj - cursors leaks detection", FUNCS_PER_USER, &ctl, HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); traces_lxid = CURRENT_LXID; } key.fn_oid = func->fn_oid; key.fn_xmin = func->fn_xmin; ftrace = (FunctionTrace *) hash_search(traces, (void *) &key, HASH_ENTER, &found); if (!found) { ftrace->key.fn_oid = func->fn_oid; ftrace->key.fn_xmin = func->fn_xmin; ftrace->ncursors = 0; ftrace->cursors_size = 0; ftrace->cursors_traces = NULL; } return ftrace; } static void func_setup(PLpgSQL_execstate *estate, PLpgSQL_function *func, void **plugin2_info) { if (plpgsql_check_cursors_leaks) { CursorLeaksPlugin2Info *pinfo; MemoryContext fn_mcxt; fn_mcxt = plpgsql_check_get_current_fn_mcxt(); pinfo = MemoryContextAlloc(fn_mcxt, sizeof(CursorLeaksPlugin2Info)); pinfo->ftrace = get_function_trace(func); pinfo->lxid = CURRENT_LXID; *plugin2_info = pinfo; } else *plugin2_info = NULL; } static void func_end(PLpgSQL_execstate *estate, PLpgSQL_function *func, void **plugin2_info) { CursorLeaksPlugin2Info *pinfo = *plugin2_info; FunctionTrace *ftrace; int i; if (!pinfo || pinfo->lxid != CURRENT_LXID) return; ftrace = pinfo->ftrace; for (i = 0; i < ftrace->ncursors; i++) { CursorTrace *ct = &ftrace->cursors_traces[i]; /* * Iterate over traced cursors. Remove slots for tracing * immediately, when traced cursor is closed already. */ if (ct->curname && ct->rec_level == func->use_count) { if (SPI_cursor_find(ct->curname)) { if (plpgsql_check_cursors_leaks_strict) { char *context; context = GetErrorContextStack(); ereport(plpgsql_check_cursors_leaks_level, errcode(ERRCODE_INVALID_CURSOR_STATE), errmsg("cursor is not closed"), errdetail("%s", context)); pfree(context); pfree(ct->curname); ct->stmtid = -1; ct->curname = NULL; } } else { pfree(ct->curname); ct->stmtid = -1; ct->curname = NULL; } } } } static void stmt_end(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt, void **plugin2_info) { CursorLeaksPlugin2Info *pinfo = *plugin2_info; FunctionTrace *ftrace; if (!pinfo) return; if (traces_lxid != CURRENT_LXID || pinfo->lxid != CURRENT_LXID) { pinfo->ftrace = get_function_trace(estate->func); pinfo->lxid = CURRENT_LXID; } ftrace = pinfo->ftrace; if (stmt->cmd_type == PLPGSQL_STMT_OPEN) { int i; int cursors_for_current_stmt = 0; int free_slot = -1; PLpgSQL_var *curvar; char *curname; curvar = (PLpgSQL_var *) (estate->datums[((PLpgSQL_stmt_open *) stmt)->curvar]); Assert(!curvar->isnull); curname = TextDatumGetCString(curvar->value); for (i = 0; i < ftrace->ncursors; i++) { CursorTrace *ct = &ftrace->cursors_traces[i]; if (ct->curname && ct->stmtid == stmt->stmtid) { /* * PLpgSQL open statements reuses portal name and does check * already used portal with already used portal name. So when * the traced name and name in cursor variable is same, we should * not to do this check. This eliminate false alarms. */ if (strcmp(curname, ct->curname) == 0) { pfree(curname); return; } if (SPI_cursor_find(ct->curname)) { if (estate->func->use_count == 1 && !plpgsql_check_cursors_leaks_strict) { char *context; context = GetErrorContextStack(); ereport(plpgsql_check_cursors_leaks_level, errcode(ERRCODE_INVALID_CURSOR_STATE), errmsg("cursor \"%s\" is not closed", curvar->refname), errdetail("%s", context)); pfree(context); pfree(ct->curname); ct->stmtid = -1; ct->curname = NULL; } else { cursors_for_current_stmt += 1; } } else { pfree(ct->curname); ct->stmtid = -1; ct->curname = NULL; } } if (ct->stmtid == -1 && free_slot == -1) free_slot = i; } if (cursors_for_current_stmt < MAX_NAMES_PER_STATEMENT) { MemoryContext oldcxt; CursorTrace *ct = NULL; oldcxt = MemoryContextSwitchTo(traces_mcxt); if (free_slot != -1) ct = &ftrace->cursors_traces[free_slot]; else { if (ftrace->ncursors == ftrace->cursors_size) { if (ftrace->cursors_size > 0) { ftrace->cursors_size += 10; ftrace->cursors_traces = repalloc_array(ftrace->cursors_traces, CursorTrace, ftrace->cursors_size); } else { ftrace->cursors_size = 10; ftrace->cursors_traces = palloc_array(CursorTrace, ftrace->cursors_size); } } ct = &ftrace->cursors_traces[ftrace->ncursors++]; } ct->stmtid = stmt->stmtid; ct->rec_level = estate->func->use_count; ct->curname = pstrdup(curname); MemoryContextSwitchTo(oldcxt); } pfree(curname); } } void plpgsql_check_cursors_leaks_init(void) { plpgsql_check_register_pldbgapi2_plugin(&cursors_leaks_plugin2); } plpgsql_check-2.7.8/src/expr_walk.c000066400000000000000000000675751465410532300173250ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * expr_walk.c * * set of Query/Expr walkers * * by Pavel Stehule 2013-2024 * *------------------------------------------------------------------------- */ #include "plpgsql_check.h" #include "nodes/nodeFuncs.h" #include "catalog/pg_class.h" #include "catalog/pg_namespace.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "nodes/nodeFuncs.h" #include "utils/builtins.h" #include "utils/lsyscache.h" typedef struct { PLpgSQL_checkstate *cstate; PLpgSQL_expr *expr; char *query_str; } check_funcexpr_walker_params; static int check_fmt_string(const char *fmt, List *args, int location, check_funcexpr_walker_params *wp, bool *is_error, int *unsafe_expr_location, bool no_error); /* * Send to ouput all not yet displayed relations, operators and functions. */ static bool detect_dependency_walker(Node *node, void *context) { PLpgSQL_checkstate *cstate = (PLpgSQL_checkstate *) context; plpgsql_check_result_info *ri = cstate->result_info; if (node == NULL) return false; if (IsA(node, Query)) { Query *query = (Query *) node; ListCell *lc; foreach(lc, query->rtable) { RangeTblEntry *rt = (RangeTblEntry *) lfirst(lc); if (rt->rtekind == RTE_RELATION) { if (!bms_is_member(rt->relid, cstate->rel_oids)) { plpgsql_check_put_dependency(ri, "RELATION", rt->relid, get_namespace_name(get_rel_namespace(rt->relid)), get_rel_name(rt->relid), NULL); cstate->rel_oids = bms_add_member(cstate->rel_oids, rt->relid); } } } if (query->utilityStmt && IsA(query->utilityStmt, CallStmt)) { CallStmt *callstmt = (CallStmt *) query->utilityStmt; detect_dependency_walker((Node *) callstmt->funcexpr, context); } return query_tree_walker((Query *) node, detect_dependency_walker, context, 0); } if (IsA(node, FuncExpr) ) { FuncExpr *fexpr = (FuncExpr *) node; if (get_func_namespace(fexpr->funcid) != PG_CATALOG_NAMESPACE) { if (!bms_is_member(fexpr->funcid, cstate->func_oids)) { StringInfoData str; ListCell *lc; bool is_first = true; char prokind = get_func_prokind(fexpr->funcid); initStringInfo(&str); appendStringInfoChar(&str, '('); foreach(lc, fexpr->args) { Node *expr = (Node *) lfirst(lc); if (!is_first) appendStringInfoChar(&str, ','); else is_first = false; appendStringInfoString(&str, format_type_be(exprType(expr))); } appendStringInfoChar(&str, ')'); plpgsql_check_put_dependency(ri, prokind == PROKIND_PROCEDURE ? "PROCEDURE" : "FUNCTION", fexpr->funcid, get_namespace_name(get_func_namespace(fexpr->funcid)), get_func_name(fexpr->funcid), str.data); pfree(str.data); cstate->func_oids = bms_add_member(cstate->func_oids, fexpr->funcid); } } } if (IsA(node, OpExpr)) { OpExpr *opexpr = (OpExpr *) node; if (plpgsql_check_get_op_namespace(opexpr->opno) != PG_CATALOG_NAMESPACE) { StringInfoData str; Oid lefttype; Oid righttype; op_input_types(opexpr->opno, &lefttype, &righttype); initStringInfo(&str); appendStringInfoChar(&str, '('); if (lefttype != InvalidOid) appendStringInfoString(&str, format_type_be(lefttype)); else appendStringInfoChar(&str, '-'); appendStringInfoChar(&str, ','); if (righttype != InvalidOid) appendStringInfoString(&str, format_type_be(righttype)); else appendStringInfoChar(&str, '-'); appendStringInfoChar(&str, ')'); plpgsql_check_put_dependency(ri, "OPERATOR", opexpr->opno, get_namespace_name(plpgsql_check_get_op_namespace(opexpr->opno)), get_opname(opexpr->opno), str.data); pfree(str.data); } } return expression_tree_walker(node, detect_dependency_walker, context); } void plpgsql_check_detect_dependency(PLpgSQL_checkstate *cstate, Query *query) { if (cstate->result_info->format != PLPGSQL_SHOW_DEPENDENCY_FORMAT_TABULAR) return; detect_dependency_walker((Node *) query, cstate); } /* * When sequence related functions has constant oid parameter, then ensure, so * this oid is related to some sequnce object. * */ static bool check_funcexpr_walker(Node *node, void *context) { if (node == NULL) return false; if (IsA(node, Query)) { return query_tree_walker((Query *) node, check_funcexpr_walker, context, 0); } if (IsA(node, FuncExpr)) { FuncExpr *fexpr = (FuncExpr *) node; switch (fexpr->funcid) { case NEXTVAL_OID: case CURRVAL_OID: case SETVAL_OID: case SETVAL2_OID: { Node *first_arg = linitial(fexpr->args); int location = fexpr->location; if (first_arg && IsA(first_arg, Const)) { Const *c = (Const *) first_arg; if (c->consttype == REGCLASSOID && !c->constisnull) { Oid classid; if (c->location != -1) location = c->location; classid = DatumGetObjectId(c->constvalue); if (get_rel_relkind(classid) != RELKIND_SEQUENCE) { char message[1024]; check_funcexpr_walker_params *wp; wp = (check_funcexpr_walker_params *) context; snprintf(message, sizeof(message), "\"%s\" is not a sequence", get_rel_name(classid)); plpgsql_check_put_error(wp->cstate, ERRCODE_WRONG_OBJECT_TYPE, 0, message, NULL, NULL, PLPGSQL_CHECK_ERROR, location, wp->query_str, NULL); } } } } break; case FORMAT_0PARAM_OID: case FORMAT_NPARAM_OID: { /* We can do check only when first argument is constant */ Node *first_arg = linitial(fexpr->args); if (first_arg && IsA(first_arg, Const)) { Const *c = (Const *) first_arg; if (c->consttype == TEXTOID && !c->constisnull) { char *fmt = TextDatumGetCString(c->constvalue); check_funcexpr_walker_params *wp; int required_nargs; bool is_error; wp = (check_funcexpr_walker_params *) context; required_nargs = check_fmt_string(fmt, fexpr->args, c->location, wp, &is_error, NULL, false); if (!is_error && required_nargs != -1) { if (required_nargs + 1 != list_length(fexpr->args)) plpgsql_check_put_error(wp->cstate, 0, 0, "unused parameters of function \"format\"", NULL, NULL, PLPGSQL_CHECK_WARNING_OTHERS, c->location, wp->query_str, NULL); } } } } } } return expression_tree_walker(node, check_funcexpr_walker, context); } void plpgsql_check_funcexpr(PLpgSQL_checkstate *cstate, Query *query, char *query_str) { check_funcexpr_walker_params wp; wp.cstate = cstate; wp.query_str = query_str; wp.expr = NULL; check_funcexpr_walker((Node *) query, &wp); } /* * Aux functions for checking format string of function format */ /* * Support macros for text_format() */ #define ADVANCE_PARSE_POINTER(ptr,end_ptr) \ do { \ if (++(ptr) >= (end_ptr)) \ { \ if (wp) \ plpgsql_check_put_error(wp->cstate, \ ERRCODE_INVALID_PARAMETER_VALUE, 0, \ "unterminated format() type specifier", \ NULL, \ "For a single \"%%\" use \"%%%%\".", \ PLPGSQL_CHECK_ERROR, \ location, \ wp->query_str, NULL); \ *is_error = true; \ } \ } while (0) /* * Parse contiguous digits as a decimal number. * * Returns true if some digits could be parsed. * The value is returned into *value, and *ptr is advanced to the next * character to be parsed. */ static bool text_format_parse_digits(const char **ptr, const char *end_ptr, int *value, int location, check_funcexpr_walker_params *wp, bool *is_error) { bool found = false; const char *cp = *ptr; int val = 0; *is_error = false; while (*cp >= '0' && *cp <= '9') { int newval = val * 10 + (*cp - '0'); if (newval / 10 != val) /* overflow? */ { if (wp) plpgsql_check_put_error(wp->cstate, ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE, 0, "number is out of range", NULL, NULL, PLPGSQL_CHECK_ERROR, location, wp->query_str, NULL); *is_error = true; return false; } val = newval; ADVANCE_PARSE_POINTER(cp, end_ptr); if (*is_error) return false; found = true; } *ptr = cp; *value = val; return found; } /* * reduced code from varlena.c */ static const char * text_format_parse_format(const char *start_ptr, const char *end_ptr, int *argpos, int *widthpos, int location, check_funcexpr_walker_params *wp, bool *is_error) { const char *cp = start_ptr; int n; bool found; /* set defaults for output parameters */ *argpos = -1; *widthpos = -1; *is_error = false; /* try to identify first number */ found = text_format_parse_digits(&cp, end_ptr, &n, location, wp, is_error); if (*is_error) return NULL; if (found) { if (*cp != '$') { /* Must be just a width and a type, so we're done */ return cp; } /* The number was argument position */ *argpos = n; /* Explicit 0 for argument index is immediately refused */ if (n == 0) { if (wp) plpgsql_check_put_error(wp->cstate, ERRCODE_INVALID_PARAMETER_VALUE, 0, "format specifies argument 0, but arguments are numbered from 1", NULL, NULL, PLPGSQL_CHECK_ERROR, location, wp->query_str, NULL); *is_error = true; return NULL; } ADVANCE_PARSE_POINTER(cp, end_ptr); if (*is_error) return NULL; } /* Handle flags (only minus is supported now) */ while (*cp == '-') { ADVANCE_PARSE_POINTER(cp, end_ptr); if (*is_error) return NULL; } if (*cp == '*') { /* Handle indirect width */ ADVANCE_PARSE_POINTER(cp, end_ptr); if (*is_error) return NULL; found = text_format_parse_digits(&cp, end_ptr, &n, location, wp, is_error); if (*is_error) return NULL; if (found) { /* number in this position must be closed by $ */ if (*cp != '$') { if (wp) plpgsql_check_put_error(wp->cstate, ERRCODE_INVALID_PARAMETER_VALUE, 0, "width argument position must be ended by \"$\"", NULL, NULL, PLPGSQL_CHECK_ERROR, location, wp->query_str, NULL); *is_error = true; return NULL; } /* The number was width argument position */ *widthpos = n; /* Explicit 0 for argument index is immediately refused */ if (n == 0) { if (wp) plpgsql_check_put_error(wp->cstate, ERRCODE_INVALID_PARAMETER_VALUE, 0, "format specifies argument 0, but arguments are numbered from 1", NULL, NULL, PLPGSQL_CHECK_ERROR, location, wp->query_str, NULL); *is_error = true; return NULL; } ADVANCE_PARSE_POINTER(cp, end_ptr); if (*is_error) return NULL; } else *widthpos = 0; /* width's argument position is unspecified */ } else { /* Check for direct width specification */ (void) text_format_parse_digits(&cp, end_ptr, &n, location, wp, is_error); if (*is_error) return NULL; } /* cp should now be pointing at type character */ return cp; } #define TOO_FEW_ARGUMENTS_CHECK(arg, nargs) \ if ((arg) > (nargs)) \ { \ if (wp) \ plpgsql_check_put_error(wp->cstate, \ ERRCODE_INVALID_PARAMETER_VALUE, 0, \ "too few arguments for format()", \ NULL, \ NULL, \ PLPGSQL_CHECK_ERROR, \ location, \ wp->query_str, NULL); \ *is_error = true; \ return -1; \ } /* * Returns string based on expected result of "format" function. * Try to use traced constant, when are available. */ char * plpgsql_check_get_formatted_string(PLpgSQL_checkstate *cstate, const char *fmt, List *args, bool *found_ident_placeholder, bool *found_literal_placeholder, bool *expr_is_const) { StringInfoData sinfo; const char *cp; const char *end_ptr = fmt + strlen(fmt); int nargs = list_length(args); int arg = 1; int _arg; bool is_error; *found_ident_placeholder = false; *found_literal_placeholder = false; *expr_is_const = true; initStringInfo(&sinfo); /* Scan format string, looking for conversion specifiers. */ for (cp = fmt; cp < end_ptr; cp++) { int argpos; int widthpos; if (*cp != '%') { appendStringInfoChar(&sinfo, *cp); continue; } if (++cp >= end_ptr) { pfree(sinfo.data); return NULL; } if (*cp == '%') { appendStringInfoChar(&sinfo, '%'); continue; } /* Parse the optional portions of the format specifier */ cp = text_format_parse_format(cp, end_ptr, &argpos, &widthpos, -1, NULL, &is_error); if (is_error || strchr("sIL", *cp) == NULL) { pfree(sinfo.data); return NULL; } if (widthpos >= 0) { if (widthpos > 0) { if (widthpos > nargs) { pfree(sinfo.data); return NULL; } } else { if (++arg > nargs) { pfree(sinfo.data); return NULL; } } } _arg = argpos >= 1 ? argpos + 1: arg + 1; if (_arg <= nargs) { char *str; str = plpgsql_check_get_const_string(cstate, list_nth(args, _arg - 1), NULL); if (*cp == 'I') { if (!str) { appendStringInfoString(&sinfo, "\"%I\""); *found_ident_placeholder = true; *expr_is_const = false; } else appendStringInfoString(&sinfo, quote_identifier(str)); } else if (*cp == 'L') { if (!str) { /* * Original idea was used external parameter, * but external parameters requires known type, * so most safe value is NULL instead. */ appendStringInfoString(&sinfo, " null "); *found_literal_placeholder = true; *expr_is_const = false; } else { char *qstr = quote_literal_cstr(str); appendStringInfoString(&sinfo, qstr); pfree(qstr); } } else { if (!str) { pfree(sinfo.data); *expr_is_const = false; return NULL; } else appendStringInfoString(&sinfo, str); } } if (argpos >= 1) { if (argpos > nargs) { pfree(sinfo.data); return NULL; } } else { if (++arg > nargs) { pfree(sinfo.data); return NULL; } } } return sinfo.data; } /* * Returns number of rquired arguments or -1 when we cannot detect this number. * When no_error is true, then this function doesn't raise a error or warning. */ static int check_fmt_string(const char *fmt, List *args, int location, check_funcexpr_walker_params *wp, bool *is_error, int *unsafe_expr_location, bool no_error) { const char *cp; const char *end_ptr = fmt + strlen(fmt); int nargs = list_length(args); int required_nargs = 0; int arg = 1; /* Scan format string, looking for conversion specifiers. */ for (cp = fmt; cp < end_ptr; cp++) { int argpos; int widthpos; if (*cp != '%') continue; ADVANCE_PARSE_POINTER(cp, end_ptr); if (*cp == '%') continue; /* Parse the optional portions of the format specifier */ cp = text_format_parse_format(cp, end_ptr, &argpos, &widthpos, location, wp, is_error); if (*is_error) return -1; /* * Next we should see the main conversion specifier. Whether or not * an argument position was present, it's known that at least one * character remains in the string at this point. Experience suggests * that it's worth checking that that character is one of the expected * ones before we try to fetch arguments, so as to produce the least * confusing response to a mis-formatted specifier. */ if (strchr("sIL", *cp) == NULL) { StringInfoData sinfo; initStringInfo(&sinfo); appendStringInfo(&sinfo, "unrecognized format() type specifier \"%c\"", *cp); if (!no_error) plpgsql_check_put_error(wp->cstate, ERRCODE_INVALID_PARAMETER_VALUE, 0, sinfo.data, NULL, NULL, PLPGSQL_CHECK_ERROR, location, wp->query_str, NULL); pfree(sinfo.data); *is_error = true; return -1; } if (widthpos >= 0) { if (widthpos > 0) { TOO_FEW_ARGUMENTS_CHECK(widthpos, nargs); required_nargs = -1; } else { TOO_FEW_ARGUMENTS_CHECK(++arg, nargs); if (required_nargs != -1) required_nargs += 1; } } /* Check safety of argument againt SQL injection when it is required */ if (unsafe_expr_location) { if (*cp == 's') { int argn = argpos >= 1 ? argpos : arg + 1; /* this is usually called after format check, but better be safe*/ if (argn <= nargs) { if (plpgsql_check_is_sql_injection_vulnerable(wp->cstate, wp->expr, list_nth(args, argn - 1), unsafe_expr_location)) { /* found vulnerability, stop */ *is_error = false; return -1; } } } } if (argpos >= 1) { TOO_FEW_ARGUMENTS_CHECK(argpos, nargs); required_nargs = -1; } else { TOO_FEW_ARGUMENTS_CHECK(++arg, nargs); if (required_nargs != -1) required_nargs += 1; } } return required_nargs; } /* * Try to detect relations in query */ static bool has_rtable_walker(Node *node, void *context) { if (node == NULL) return false; if (IsA(node, Query)) { Query *query = (Query *) node; bool has_relation = false; ListCell *lc; foreach (lc, query->rtable) { RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc); if (rte->rtekind == RTE_RELATION) { has_relation = true; break; } } if (has_relation) { return true; } else return query_tree_walker(query, has_rtable_walker, context, 0); } return expression_tree_walker(node, has_rtable_walker, context); } /* * Returns true, if query use any relation */ bool plpgsql_check_has_rtable(Query *query) { return has_rtable_walker((Node *) query, NULL); } /* * Try to identify constraint where variable from one side is implicitly casted to * parameter type of second side. It can symptom of parameter wrong type. * */ static bool contain_fishy_cast_walker(Node *node, void *context) { if (node == NULL) return false; if (IsA(node, OpExpr)) { OpExpr *opexpr = (OpExpr *) node; if (!opexpr->opretset && opexpr->opresulttype == BOOLOID && list_length(opexpr->args) == 2) { Node *l1 = linitial(opexpr->args); Node *l2 = lsecond(opexpr->args); Param *param = NULL; FuncExpr *fexpr = NULL; if (IsA(l1, Param)) param = (Param *) l1; else if (IsA(l1, FuncExpr)) fexpr = (FuncExpr *) l1; if (IsA(l2, Param)) param = (Param *) l2; else if (IsA(l2, FuncExpr)) fexpr = (FuncExpr *) l2; if (param != NULL && fexpr != NULL) { if (param->paramkind != PARAM_EXTERN) return false; if (fexpr->funcformat != COERCE_IMPLICIT_CAST || fexpr->funcretset || list_length(fexpr->args) != 1 || param->paramtype != fexpr->funcresulttype) return false; if (!IsA(linitial(fexpr->args), Var)) return false; *((Param **) context) = param; return true; } } } return expression_tree_walker(node, contain_fishy_cast_walker, context); } bool plpgsql_check_qual_has_fishy_cast(PlannedStmt *plannedstmt, Plan *plan, Param **param) { ListCell *lc; if (plan == NULL) return false; if (contain_fishy_cast_walker((Node *) plan->qual, param)) return true; if (plpgsql_check_qual_has_fishy_cast(plannedstmt, innerPlan(plan), param)) return true; if (plpgsql_check_qual_has_fishy_cast(plannedstmt, outerPlan(plan), param)) return true; foreach(lc, plan->initPlan) { SubPlan *subplan = (SubPlan *) lfirst(lc); Plan *s_plan = exec_subplan_get_plan(plannedstmt, subplan); if (plpgsql_check_qual_has_fishy_cast(plannedstmt, s_plan, param)) return true; } return false; } #define QUOTE_IDENT_OID 1282 #define QUOTE_LITERAL_OID 1283 #define QUOTE_NULLABLE_OID 1289 /* * Recursive iterate to deep and search extern params with typcategory "S", and check * if this value is sanitized. Flag is_safe is true, when result is safe. */ bool plpgsql_check_is_sql_injection_vulnerable(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr, Node *node, int *location) { if (IsA(node, FuncExpr)) { FuncExpr *fexpr = (FuncExpr *) node; bool is_vulnerable = false; ListCell *lc; foreach(lc, fexpr->args) { Node *arg = lfirst(lc); if (plpgsql_check_is_sql_injection_vulnerable(cstate, expr, arg, location)) { is_vulnerable = true; break; } } if (is_vulnerable) { bool typispreferred; char typcategory; get_type_category_preferred(fexpr->funcresulttype, &typcategory, &typispreferred); if (typcategory == 'S') { switch (fexpr->funcid) { case QUOTE_IDENT_OID: case QUOTE_LITERAL_OID: case QUOTE_NULLABLE_OID: return false; case FORMAT_0PARAM_OID: case FORMAT_NPARAM_OID: { /* We can do check only when first argument is constant */ char *fmt; int loc; fmt = plpgsql_check_get_const_string(cstate, linitial(fexpr->args), &loc); if (fmt) { check_funcexpr_walker_params wp; bool is_error; wp.cstate = cstate; wp.expr = expr; wp.query_str = expr->query; *location = -1; check_fmt_string(fmt, fexpr->args, loc, &wp, &is_error, location, true); /* only in this case, "format" function obviously sanitize parameters */ if (!is_error) return *location != -1; } } break; default: /* do nothing */ ; } return true; } } return false; } else if (IsA(node, OpExpr)) { OpExpr *op = (OpExpr *) node; bool is_vulnerable = false; ListCell *lc; foreach(lc, op->args) { Node *arg = lfirst(lc); if (plpgsql_check_is_sql_injection_vulnerable(cstate, expr, arg, location)) { is_vulnerable = true; break; } } if (is_vulnerable) { bool typispreferred; char typcategory; get_type_category_preferred(op->opresulttype, &typcategory, &typispreferred); if (typcategory == 'S') { char *opname = get_opname(op->opno); bool result = false; if (opname) { result = strcmp(opname, "||") == 0; pfree(opname); } return result; } } return false; } else if (IsA(node, NamedArgExpr)) { return plpgsql_check_is_sql_injection_vulnerable(cstate, expr, (Node *) ((NamedArgExpr *) node)->arg, location); } else if (IsA(node, RelabelType)) { return plpgsql_check_is_sql_injection_vulnerable(cstate, expr, (Node *) ((RelabelType *) node)->arg, location); } else if (IsA(node, Param)) { Param *p = (Param *) node; if (p->paramkind == PARAM_EXTERN || p->paramkind == PARAM_EXEC) { bool typispreferred; char typcategory; get_type_category_preferred(p->paramtype, &typcategory, &typispreferred); if (typcategory == 'S') { if (p->paramkind == PARAM_EXTERN && p->paramid > 0 && p->location != -1) { int dno = p->paramid - 1; /* * When paramid looks well and related datum is variable with same * type, then we can check, if this variable has sanitized content * already. */ if (expr && bms_is_member(dno, expr->paramnos)) { PLpgSQL_var *var = (PLpgSQL_var *) cstate->estate->datums[dno]; if (var->dtype == PLPGSQL_DTYPE_VAR) { if (var->datatype->typoid == p->paramtype) { if (bms_is_member(dno, cstate->safe_variables)) return false; } } } } *location = p->location; return true; } } return false; } else return false; } /* * These checker function returns volatility or immutability of any function. * Special case is plpgsql_check_pragma function. This function is vollatile, * but we should to fake flags to be immutable - becase we would not to change * result of analyzes and we know so this function has not any negative side * effect. */ static bool contain_volatile_functions_checker(Oid func_id, void *context) { PLpgSQL_checkstate *cstate = (PLpgSQL_checkstate *) context; if (func_id == cstate->pragma_foid) return false; return (func_volatile(func_id) == PROVOLATILE_VOLATILE); } static bool contain_mutable_functions_checker(Oid func_id, void *context) { PLpgSQL_checkstate *cstate = (PLpgSQL_checkstate *) context; if (func_id == cstate->pragma_foid) return false; return (func_volatile(func_id) != PROVOLATILE_IMMUTABLE); } static bool contain_volatile_functions_walker(Node *node, void *context) { if (node == NULL) return false; /* Check for volatile functions in node itself */ if (check_functions_in_node(node, contain_volatile_functions_checker, context)) return true; if (IsA(node, NextValueExpr)) { /* NextValueExpr is volatile */ return true; } /* * See notes in contain_mutable_functions_walker about why we treat * MinMaxExpr, XmlExpr, and CoerceToDomain as immutable, while * SQLValueFunction is stable. Hence, none of them are of interest here. */ /* Recurse to check arguments */ if (IsA(node, Query)) { /* Recurse into subselects */ return query_tree_walker((Query *) node, contain_volatile_functions_walker, context, 0); } return expression_tree_walker(node, contain_volatile_functions_walker, context); } static bool contain_mutable_functions_walker(Node *node, void *context) { if (node == NULL) return false; /* Check for mutable functions in node itself */ if (check_functions_in_node(node, contain_mutable_functions_checker, context)) return true; if (IsA(node, SQLValueFunction)) { /* all variants of SQLValueFunction are stable */ return true; } if (IsA(node, NextValueExpr)) { /* NextValueExpr is volatile */ return true; } /* * It should be safe to treat MinMaxExpr as immutable, because it will * depend on a non-cross-type btree comparison function, and those should * always be immutable. Treating XmlExpr as immutable is more dubious, * and treating CoerceToDomain as immutable is outright dangerous. But we * have done so historically, and changing this would probably cause more * problems than it would fix. In practice, if you have a non-immutable * domain constraint you are in for pain anyhow. */ /* Recurse to check arguments */ if (IsA(node, Query)) { /* Recurse into subselects */ return query_tree_walker((Query *) node, contain_mutable_functions_walker, context, 0); } return expression_tree_walker(node, contain_mutable_functions_walker, context); } /* * This is same like Postgres buildin function, but it ignores used * plpgsql_check pragma function */ bool plpgsql_check_contain_volatile_functions(Node *clause, PLpgSQL_checkstate *cstate) { return contain_volatile_functions_walker(clause, cstate); } bool plpgsql_check_contain_mutable_functions(Node *clause, PLpgSQL_checkstate *cstate) { return contain_mutable_functions_walker(clause, cstate); } #if PG_VERSION_NUM >= 140000 static bool has_external_param_with_paramid(Node *node, void *context) { if (node == NULL) return false; if (IsA(node, Param)) { Param *p = (Param *) node; if (p->paramkind == PARAM_EXTERN && p->paramid > 0 && p->location != -1) { int dno = p->paramid - 1; if (dno == *((int *) context)) return true; } } return expression_tree_walker(node, has_external_param_with_paramid, context); } /* * This walker is used for checking usage target_param elsewhere than top subscripting * node. */ bool plpgsql_check_vardno_is_used_for_reading(Node *node, int dno) { if (node == NULL) return false; if (IsA(node, SubscriptingRef)) node = (Node *) ((SubscriptingRef *) node)->refassgnexpr; return has_external_param_with_paramid(node, (void *) &dno); } #endif plpgsql_check-2.7.8/src/format.c000066400000000000000000000716311465410532300166050ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * format.c * * error/warning message formatting * * by Pavel Stehule 2013-2024 * *------------------------------------------------------------------------- */ #include #include "plpgsql_check.h" #include "access/htup_details.h" #include "mb/pg_wchar.h" #include "tsearch/ts_locale.h" #include "utils/builtins.h" #include "utils/json.h" #include "utils/xml.h" static void put_text_line(plpgsql_check_result_info *ri, const char *message, int len); static const char * error_level_str(int level); static void init_tag(plpgsql_check_result_info *ri, Oid fn_oid); static void close_and_save(plpgsql_check_result_info *ri); static void put_error_text(plpgsql_check_result_info *ri, PLpgSQL_execstate *estate, int sqlerrcode, int lineno, const char *message, const char *detail, const char *hint, int level, int position, const char *query, const char *context); static void format_error_xml(StringInfo str, PLpgSQL_execstate *estate, int sqlerrcode, int lineno, const char *message, const char *detail, const char *hint, int level, int position, const char *query, const char *context); static void format_error_json(StringInfo str, PLpgSQL_execstate *estate, int sqlerrcode, int lineno, const char *message, const char *detail, const char *hint, int level, int position, const char *query, const char *context); static void put_error_tabular(plpgsql_check_result_info *ri, PLpgSQL_execstate *estate, Oid fn_oid, int sqlerrcode, int lineno, const char *message, const char *detail, const char *hint, int level, int position, const char *query, const char *context); /* * columns of plpgsql_check_function_table result * */ #define Natts_result 11 #define Anum_result_functionid 0 #define Anum_result_lineno 1 #define Anum_result_statement 2 #define Anum_result_sqlstate 3 #define Anum_result_message 4 #define Anum_result_detail 5 #define Anum_result_hint 6 #define Anum_result_level 7 #define Anum_result_position 8 #define Anum_result_query 9 #define Anum_result_context 10 /* * columns of plpgsql_show_dependency_tb result * */ #define Natts_dependency 5 #define Anum_dependency_type 0 #define Anum_dependency_oid 1 #define Anum_dependency_schema 2 #define Anum_dependency_name 3 #define Anum_dependency_params 4 /* * columns of plpgsql_profiler_function_tb result * */ #define Natts_profiler 11 #define Anum_profiler_lineno 0 #define Anum_profiler_stmt_lineno 1 #define Anum_profiler_queryid 2 #define Anum_profiler_cmds_on_row 3 #define Anum_profiler_exec_count 4 #define Anum_profiler_exec_count_err 5 #define Anum_profiler_total_time 6 #define Anum_profiler_avg_time 7 #define Anum_profiler_max_time 8 #define Anum_profiler_processed_rows 9 #define Anum_profiler_source 10 /* * columns of plpgsql_profiler_function_statements_tb result * */ #define Natts_profiler_statements 13 #define Anum_profiler_statements_stmtid 0 #define Anum_profiler_statements_parent_stmtid 1 #define Anum_profiler_statements_parent_note 2 #define Anum_profiler_statements_block_num 3 #define Anum_profiler_statements_lineno 4 #define Anum_profiler_statements_queryid 5 #define Anum_profiler_statements_exec_stmts 6 #define Anum_profiler_statements_exec_stmts_err 7 #define Anum_profiler_statements_total_time 8 #define Anum_profiler_statements_avg_time 9 #define Anum_profiler_statements_max_time 10 #define Anum_profiler_statements_processed_rows 11 #define Anum_profiler_statements_stmtname 12 /* * columns of plpgsql_profiler_functions_all_tb result * */ #define Natts_profiler_functions_all_tb 8 #define Anum_profiler_functions_all_funcoid 0 #define Anum_profiler_functions_all_exec_count 1 #define Anum_profiler_functions_all_exec_count_err 2 #define Anum_profiler_functions_all_total_time 3 #define Anum_profiler_functions_all_avg_time 4 #define Anum_profiler_functions_all_stddev_time 5 #define Anum_profiler_functions_all_min_time 6 #define Anum_profiler_functions_all_max_time 7 #define SET_RESULT_NULL(anum) \ do { \ values[(anum)] = (Datum) 0; \ nulls[(anum)] = true; \ } while (0) #define SET_RESULT(anum, value) \ do { \ values[(anum)] = (value); \ nulls[(anum)] = false; \ } while(0) #define SET_RESULT_TEXT(anum, str) \ do { \ if (str != NULL) \ { \ SET_RESULT((anum), CStringGetTextDatum((str))); \ } \ else \ { \ SET_RESULT_NULL(anum); \ } \ } while (0) #define SET_RESULT_INT32(anum, ival) SET_RESULT((anum), Int32GetDatum((ival))) #define SET_RESULT_INT64(anum, ival) SET_RESULT((anum), Int64GetDatum((ival))) #if PG_VERSION_NUM >= 110000 #define SET_RESULT_QUERYID(anum, ival) SET_RESULT((anum), UInt64GetDatum((ival))) #else #define SET_RESULT_QUERYID(anum, ival) SET_RESULT((anum), UInt32GetDatum((ival))) #endif #define SET_RESULT_OID(anum, oid) SET_RESULT((anum), ObjectIdGetDatum((oid))) #define SET_RESULT_FLOAT8(anum, fval) SET_RESULT((anum), Float8GetDatum(fval)) /* * Translate name of format to number of format * */ int plpgsql_check_format_num(char *format_str) { int result; char *format_lower_str = lowerstr(format_str); if (strcmp(format_lower_str, "text") == 0) result = PLPGSQL_CHECK_FORMAT_TEXT; else if (strcmp(format_lower_str, "xml") == 0) result = PLPGSQL_CHECK_FORMAT_XML; else if (strcmp(format_lower_str, "json") == 0) result = PLPGSQL_CHECK_FORMAT_JSON; else ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("unrecognize format: \"%s\"", format_str), errhint("Only \"text\", \"xml\" and \"json\" formats are supported."))); return result; } /* * Prepare storage and formats for result * */ void plpgsql_check_init_ri(plpgsql_check_result_info *ri, int format, ReturnSetInfo *rsinfo) { int natts; MemoryContext per_query_ctx; MemoryContext oldctx; ri->format = format; ri->sinfo = NULL; switch (format) { case PLPGSQL_CHECK_FORMAT_TEXT: case PLPGSQL_CHECK_FORMAT_XML: case PLPGSQL_CHECK_FORMAT_JSON: natts = 1; break; case PLPGSQL_CHECK_FORMAT_TABULAR: natts = Natts_result; break; case PLPGSQL_SHOW_DEPENDENCY_FORMAT_TABULAR: natts = Natts_dependency; break; case PLPGSQL_SHOW_PROFILE_TABULAR: natts = Natts_profiler; break; case PLPGSQL_SHOW_PROFILE_STATEMENTS_TABULAR: natts = Natts_profiler_statements; break; case PLPGSQL_SHOW_PROFILE_FUNCTIONS_ALL_TABULAR: natts = Natts_profiler_functions_all_tb; break; default: elog(ERROR, "unknown format %d", format); } ri->init_tag = format == PLPGSQL_CHECK_FORMAT_XML || format == PLPGSQL_CHECK_FORMAT_JSON; /* need to build tuplestore in query context */ per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; oldctx = MemoryContextSwitchTo(per_query_ctx); ri->tupdesc = CreateTupleDescCopy(rsinfo->expectedDesc); ri->tuple_store = tuplestore_begin_heap(false, false, work_mem); ri->query_ctx = per_query_ctx; MemoryContextSwitchTo(oldctx); /* simple check of target */ if (ri->tupdesc->natts != natts) elog(ERROR, "unexpected returning columns (%d instead %d)", ri->tupdesc->natts, natts); /* prepare rsinfo for this tuplestore result */ rsinfo->returnMode = SFRM_Materialize; rsinfo->setResult = ri->tuple_store; rsinfo->setDesc = ri->tupdesc; } /* * When result is not empty, finalize result and close tuplestore. * */ void plpgsql_check_finalize_ri(plpgsql_check_result_info *ri) { if (ri->sinfo) { close_and_save(ri); pfree(ri->sinfo->data); pfree(ri->sinfo); ri->sinfo = NULL; } } /* * error message processing router */ static void plpgsql_check_put_error_internal(PLpgSQL_checkstate *cstate, int sqlerrcode, int lineno, const char *message, const char *detail, const char *hint, int level, int position, const char *query, const char *context) { plpgsql_check_result_info *ri = cstate->result_info; PLpgSQL_execstate *estate = cstate->estate; if (context == NULL && estate && estate->err_text) context = estate->err_text; /* ignore warnings when is not requested */ if ((level == PLPGSQL_CHECK_WARNING_PERFORMANCE && !cstate->cinfo->performance_warnings) || (level == PLPGSQL_CHECK_WARNING_OTHERS && !cstate->cinfo->other_warnings) || (level == PLPGSQL_CHECK_WARNING_EXTRA && !cstate->cinfo->extra_warnings) || (level == PLPGSQL_CHECK_WARNING_SECURITY && !cstate->cinfo->security_warnings) || (level == PLPGSQL_CHECK_WARNING_COMPATIBILITY && !cstate->cinfo->compatibility_warnings)) return; if ((level == PLPGSQL_CHECK_WARNING_PERFORMANCE && cstate->pragma_vector.disable_performance_warnings) || (level == PLPGSQL_CHECK_WARNING_OTHERS && cstate->pragma_vector.disable_other_warnings) || (level == PLPGSQL_CHECK_WARNING_EXTRA && cstate->pragma_vector.disable_extra_warnings) || (level == PLPGSQL_CHECK_WARNING_SECURITY && cstate->pragma_vector.disable_security_warnings) || (level == PLPGSQL_CHECK_WARNING_COMPATIBILITY && cstate->pragma_vector.disable_compatibility_warnings)) return; if (cstate->pragma_vector.disable_check) return; if (ri->init_tag) { init_tag(ri, cstate->cinfo->fn_oid); ri->init_tag = false; } if (ri->tuple_store) { switch (ri->format) { case PLPGSQL_CHECK_FORMAT_TABULAR: put_error_tabular(ri, estate, cstate->cinfo->fn_oid, sqlerrcode, lineno, message, detail, hint, level, position, query, context); break; case PLPGSQL_CHECK_FORMAT_TEXT: put_error_text(ri, estate, sqlerrcode, lineno, message, detail, hint, level, position, query, context); break; case PLPGSQL_CHECK_FORMAT_XML: format_error_xml(ri->sinfo, estate, sqlerrcode, lineno, message, detail, hint, level, position, query, context); break; case PLPGSQL_CHECK_FORMAT_JSON: format_error_json(ri->sinfo, estate, sqlerrcode, lineno, message, detail, hint, level, position, query, context); break; } /* stop checking if it is necessary */ if (level == PLPGSQL_CHECK_ERROR && cstate->cinfo->fatal_errors) cstate->stop_check = true; } else { int elevel; /* * when a passive mode is active and fatal_errors is false, then * raise warning everytime. */ if (!cstate->is_active_mode && !cstate->cinfo->fatal_errors) elevel = WARNING; else elevel = level == PLPGSQL_CHECK_ERROR ? ERROR : WARNING; /* use error fields as parameters of postgres exception */ ereport(elevel, (sqlerrcode ? errcode(sqlerrcode) : 0, errmsg_internal("%s", message), (detail != NULL) ? errdetail_internal("%s", detail) : 0, (hint != NULL) ? errhint("%s", hint) : 0, (query != NULL) ? internalerrquery(query) : 0, (position != 0) ? internalerrposition(position) : 0, (context != NULL) ? errcontext("%s", context) : 0)); } } /* * store edata */ void plpgsql_check_put_error_edata(PLpgSQL_checkstate *cstate, ErrorData *edata) { plpgsql_check_put_error_internal(cstate, edata->sqlerrcode, edata->lineno, edata->message, edata->detail, edata->hint, PLPGSQL_CHECK_ERROR, edata->internalpos, edata->internalquery, edata->context); } void plpgsql_check_put_error(PLpgSQL_checkstate *cstate, int sqlerrcode, int lineno, const char *message, const char *detail, const char *hint, int level, int position, const char *query, const char *context) { /* * Trapped internal errors has transformed position. The plpgsql_check * errors (and warnings) have to have same transformation for position * to correct display caret (for trapped and reraised, and raised errors) */ if (position != -1 && query) position = pg_mbstrlen_with_len(query, position) + 1; plpgsql_check_put_error_internal(cstate, sqlerrcode, lineno, message, detail, hint, level, position, query, context); } /* * Append text line (StringInfo) to one column tuple store * */ static void put_text_line(plpgsql_check_result_info *ri, const char *message, int len) { Datum value; bool isnull = false; HeapTuple tuple; if (len >= 0) value = PointerGetDatum(cstring_to_text_with_len(message, len)); else value = PointerGetDatum(cstring_to_text(message)); tuple = heap_form_tuple(ri->tupdesc, &value, &isnull); tuplestore_puttuple(ri->tuple_store, tuple); } static const char * error_level_str(int level) { switch (level) { case PLPGSQL_CHECK_ERROR: return "error"; case PLPGSQL_CHECK_WARNING_OTHERS: return "warning"; case PLPGSQL_CHECK_WARNING_EXTRA: return "warning extra"; case PLPGSQL_CHECK_WARNING_PERFORMANCE: return "performance"; case PLPGSQL_CHECK_WARNING_SECURITY: return "security"; case PLPGSQL_CHECK_WARNING_COMPATIBILITY: return "compatibility"; default: return "???"; } } /* * collects errors and warnings in plain text format */ static void put_error_text(plpgsql_check_result_info *ri, PLpgSQL_execstate *estate, int sqlerrcode, int lineno, const char *message, const char *detail, const char *hint, int level, int position, const char *query, const char *context) { StringInfoData sinfo; const char *level_str = error_level_str(level); Assert(message != NULL); initStringInfo(&sinfo); /* lineno should be valid for actual statements */ if (estate != NULL && estate->err_stmt != NULL && estate->err_stmt->lineno > 0) appendStringInfo(&sinfo, "%s:%s:%d:%s:%s", level_str, unpack_sql_state(sqlerrcode), estate->err_stmt->lineno, plpgsql_check__stmt_typename_p(estate->err_stmt), message); else if (strncmp(message, UNUSED_VARIABLE_TEXT, UNUSED_VARIABLE_TEXT_CHECK_LENGTH) == 0) { appendStringInfo(&sinfo, "%s:%s:%d:%s:%s", level_str, unpack_sql_state(sqlerrcode), lineno, "DECLARE", message); } else if (strncmp(message, NEVER_READ_VARIABLE_TEXT, NEVER_READ_VARIABLE_TEXT_CHECK_LENGTH) == 0) { appendStringInfo(&sinfo, "%s:%s:%d:%s:%s", level_str, unpack_sql_state(sqlerrcode), lineno, "DECLARE", message); } else { appendStringInfo(&sinfo, "%s:%s:%s", level_str, unpack_sql_state(sqlerrcode), message); } put_text_line(ri, sinfo.data, sinfo.len); resetStringInfo(&sinfo); if (query != NULL) { char *query_line; /* pointer to beginning of current line */ int line_caret_pos; bool is_first_line = true; char *_query = pstrdup(query); char *ptr; ptr = _query; query_line = ptr; line_caret_pos = position; while (*ptr != '\0') { /* search end of lines and replace '\n' by '\0' */ if (*ptr == '\n') { *ptr = '\0'; if (is_first_line) { appendStringInfo(&sinfo, "Query: %s", query_line); is_first_line = false; } else appendStringInfo(&sinfo, " %s", query_line); put_text_line(ri, sinfo.data, sinfo.len); resetStringInfo(&sinfo); if (line_caret_pos > 0 && position == 0) { appendStringInfo(&sinfo, "-- %*s", line_caret_pos, "^"); put_text_line(ri, sinfo.data, sinfo.len); resetStringInfo(&sinfo); line_caret_pos = 0; } /* store caret position offset for next line */ if (position > 1) line_caret_pos = position - 1; /* go to next line */ query_line = ptr + 1; } ptr += pg_mblen(ptr); if (position > 0) position--; } /* flush last line */ if (query_line != NULL) { if (is_first_line) appendStringInfo(&sinfo, "Query: %s", query_line); else appendStringInfo(&sinfo, " %s", query_line); put_text_line(ri, sinfo.data, sinfo.len); resetStringInfo(&sinfo); if (line_caret_pos > 0 && position == 0) { appendStringInfo(&sinfo, "-- %*s", line_caret_pos, "^"); put_text_line(ri, sinfo.data, sinfo.len); resetStringInfo(&sinfo); } } pfree(_query); } if (detail != NULL) { appendStringInfo(&sinfo, "Detail: %s", detail); put_text_line(ri, sinfo.data, sinfo.len); resetStringInfo(&sinfo); } if (hint != NULL) { appendStringInfo(&sinfo, "Hint: %s", hint); put_text_line(ri, sinfo.data, sinfo.len); resetStringInfo(&sinfo); } if (context != NULL) { appendStringInfo(&sinfo, "Context: %s", context); put_text_line(ri, sinfo.data, sinfo.len); resetStringInfo(&sinfo); } pfree(sinfo.data); } /* * Initialize StringInfo buffer with top tag * */ static void init_tag(plpgsql_check_result_info *ri, Oid fn_oid) { /* XML format requires StringInfo buffer */ if (ri->format == PLPGSQL_CHECK_FORMAT_XML || ri->format == PLPGSQL_CHECK_FORMAT_JSON) { if (ri->sinfo != NULL) resetStringInfo(ri->sinfo); else { MemoryContext oldcxt; /* StringInfo should be created in some longer life context */ oldcxt = MemoryContextSwitchTo(ri->query_ctx); ri->sinfo = makeStringInfo(); MemoryContextSwitchTo(oldcxt); } if (ri->format == PLPGSQL_CHECK_FORMAT_XML) { /* create a initial tag */ if (plpgsql_check_regress_test_mode) appendStringInfo(ri->sinfo, "\n"); else appendStringInfo(ri->sinfo, "\n", fn_oid); } else if (ri->format == PLPGSQL_CHECK_FORMAT_JSON) { /* create a initial tag */ if (plpgsql_check_regress_test_mode) appendStringInfo(ri->sinfo, "{ \"issues\":[\n"); else appendStringInfo(ri->sinfo, "{ \"function\":\"%d\",\n\"issues\":[\n", fn_oid); } } } /* * Append close tag and store document * */ static void close_and_save(plpgsql_check_result_info *ri) { if (ri->format == PLPGSQL_CHECK_FORMAT_XML) { appendStringInfoString(ri->sinfo, ""); put_text_line(ri, ri->sinfo->data, ri->sinfo->len); } else if (ri->format == PLPGSQL_CHECK_FORMAT_JSON) { if (ri->sinfo->len > 1 && ri->sinfo->data[ri->sinfo->len -1] == ',') { ri->sinfo->data[ri->sinfo->len - 1] = '\n'; } appendStringInfoString(ri->sinfo, "\n]\n}"); put_text_line(ri, ri->sinfo->data, ri->sinfo->len); } } /* * format_error_xml formats and collects a identifided issues */ static void format_error_xml(StringInfo str, PLpgSQL_execstate *estate, int sqlerrcode, int lineno, const char *message, const char *detail, const char *hint, int level, int position, const char *query, const char *context) { const char *level_str = error_level_str(level); Assert(message != NULL); /* flush tag */ appendStringInfoString(str, " \n"); appendStringInfo(str, " %s\n", level_str); appendStringInfo(str, " %s\n", unpack_sql_state(sqlerrcode)); appendStringInfo(str, " %s\n", escape_xml(message)); if (estate != NULL && estate->err_stmt != NULL) appendStringInfo(str, " %s\n", estate->err_stmt->lineno, plpgsql_check__stmt_typename_p(estate->err_stmt)); else if (strcmp(message, "unused declared variable") == 0) appendStringInfo(str, " DECLARE\n", lineno); if (hint != NULL) appendStringInfo(str, " %s\n", escape_xml(hint)); if (detail != NULL) appendStringInfo(str, " %s\n", escape_xml(detail)); if (query != NULL) appendStringInfo(str, " %s\n", position, escape_xml(query)); if (context != NULL) appendStringInfo(str, " %s\n", escape_xml(context)); /* flush closing tag */ appendStringInfoString(str, " \n"); } /* * format_error_json formats and collects a identifided issues */ static void format_error_json(StringInfo str, PLpgSQL_execstate *estate, int sqlerrcode, int lineno, const char *message, const char *detail, const char *hint, int level, int position, const char *query, const char *context) { const char *level_str = error_level_str(level); StringInfoData sinfo; /*Holds escaped json*/ Assert(message != NULL); initStringInfo(&sinfo); /* flush tag */ appendStringInfoString(str, " {\n"); appendStringInfo(str, " \"level\":\"%s\",\n", level_str); escape_json(&sinfo, message); appendStringInfo(str, " \"message\":%s,\n", sinfo.data); if (estate != NULL && estate->err_stmt != NULL) appendStringInfo(str, " \"statement\":{\n\"lineNumber\":\"%d\",\n\"text\":\"%s\"\n},\n", estate->err_stmt->lineno, plpgsql_check__stmt_typename_p(estate->err_stmt)); else if (strcmp(message, "unused declared variable") == 0) appendStringInfo(str, " \"statement\":{\n\"lineNumber\":\"%d\",\n\"text\":\"DECLARE\"\n},", lineno); if (hint != NULL) { resetStringInfo(&sinfo); escape_json(&sinfo, hint); appendStringInfo(str, " \"hint\":%s,\n", sinfo.data); } if (detail != NULL) { resetStringInfo(&sinfo); escape_json(&sinfo, detail); appendStringInfo(str, " \"detail\":%s,\n", sinfo.data); } if (query != NULL) { resetStringInfo(&sinfo); escape_json(&sinfo, query); appendStringInfo(str, " \"query\":{\n\"position\":\"%d\",\n\"text\":%s\n},\n", position, sinfo.data); } if (context != NULL) { resetStringInfo(&sinfo); escape_json(&sinfo, context); appendStringInfo(str, " \"context\":%s,\n", sinfo.data); } /* placing this property last as to avoid a trailing comma*/ appendStringInfo(str, " \"sqlState\":\"%s\"\n", unpack_sql_state(sqlerrcode)); /* flush closing tag. Needs comman jus in case there is more than one issue. Comma removed in epilog */ appendStringInfoString(str, " },"); } /* * store error fields to result tuplestore * */ static void put_error_tabular(plpgsql_check_result_info *ri, PLpgSQL_execstate *estate, Oid fn_oid, int sqlerrcode, int lineno, const char *message, const char *detail, const char *hint, int level, int position, const char *query, const char *context) { Datum values[Natts_result]; bool nulls[Natts_result]; Assert(ri->tuple_store); Assert(ri->tupdesc); Assert(message != NULL); SET_RESULT_OID(Anum_result_functionid, fn_oid); /* lineno should be valid */ if (estate != NULL && estate->err_stmt != NULL && estate->err_stmt->lineno > 0) { /* use lineno based on err_stmt */ SET_RESULT_INT32(Anum_result_lineno, estate->err_stmt->lineno); SET_RESULT_TEXT(Anum_result_statement, plpgsql_check__stmt_typename_p(estate->err_stmt)); } else if (strncmp(message, UNUSED_VARIABLE_TEXT, UNUSED_VARIABLE_TEXT_CHECK_LENGTH) == 0) { SET_RESULT_INT32(Anum_result_lineno, lineno); SET_RESULT(Anum_result_statement, CStringGetTextDatum("DECLARE")); } else if (strncmp(message, NEVER_READ_VARIABLE_TEXT, NEVER_READ_VARIABLE_TEXT_CHECK_LENGTH) == 0) { SET_RESULT_INT32(Anum_result_lineno, lineno); SET_RESULT(Anum_result_statement, CStringGetTextDatum("DECLARE")); } else { SET_RESULT_NULL(Anum_result_lineno); SET_RESULT_NULL(Anum_result_statement); } SET_RESULT_TEXT(Anum_result_sqlstate, unpack_sql_state(sqlerrcode)); SET_RESULT_TEXT(Anum_result_message, message); SET_RESULT_TEXT(Anum_result_detail, detail); SET_RESULT_TEXT(Anum_result_hint, hint); SET_RESULT_TEXT(Anum_result_level, error_level_str(level)); if (position != 0) SET_RESULT_INT32(Anum_result_position, position); else SET_RESULT_NULL(Anum_result_position); SET_RESULT_TEXT(Anum_result_query, query); SET_RESULT_TEXT(Anum_result_context, context); tuplestore_putvalues(ri->tuple_store, ri->tupdesc, values, nulls); } /* * Store one output row of dependency view to result tuplestore * */ void plpgsql_check_put_dependency(plpgsql_check_result_info *ri, char *type, Oid oid, char *schema, char *name, char *params) { Datum values[Natts_dependency]; bool nulls[Natts_dependency]; Assert(ri->tuple_store); Assert(ri->tupdesc); SET_RESULT_TEXT(Anum_dependency_type, type); SET_RESULT_OID(Anum_dependency_oid, oid); SET_RESULT_TEXT(Anum_dependency_schema, schema); SET_RESULT_TEXT(Anum_dependency_name, name); SET_RESULT_TEXT(Anum_dependency_params, params); tuplestore_putvalues(ri->tuple_store, ri->tupdesc, values, nulls); } /* * Store one output row of profiler to result tuplestore * */ void plpgsql_check_put_profile(plpgsql_check_result_info *ri, Datum queryids_array, int lineno, int stmt_lineno, int cmds_on_row, int64 exec_count, int64 exec_count_err, int64 us_total, Datum max_time_array, Datum processed_rows_array, char *source_row) { Datum values[Natts_profiler]; bool nulls[Natts_profiler]; Assert(ri->tuple_store); Assert(ri->tupdesc); SET_RESULT_NULL(Anum_profiler_stmt_lineno); SET_RESULT_NULL(Anum_profiler_queryid); SET_RESULT_NULL(Anum_profiler_exec_count); SET_RESULT_NULL(Anum_profiler_exec_count_err); SET_RESULT_NULL(Anum_profiler_total_time); SET_RESULT_NULL(Anum_profiler_avg_time); SET_RESULT_NULL(Anum_profiler_max_time); SET_RESULT_NULL(Anum_profiler_processed_rows); SET_RESULT_NULL(Anum_profiler_source); SET_RESULT_NULL(Anum_profiler_cmds_on_row); SET_RESULT_INT32(Anum_profiler_lineno, lineno); SET_RESULT_TEXT(Anum_profiler_source, source_row); if (stmt_lineno > 0) { SET_RESULT_INT32(Anum_profiler_stmt_lineno, stmt_lineno); if (queryids_array != (Datum) 0) SET_RESULT(Anum_profiler_queryid, queryids_array); SET_RESULT_INT32(Anum_profiler_cmds_on_row, cmds_on_row); SET_RESULT_INT64(Anum_profiler_exec_count, exec_count); SET_RESULT_INT64(Anum_profiler_exec_count_err, exec_count_err); SET_RESULT_FLOAT8(Anum_profiler_total_time, us_total / 1000.0); SET_RESULT_FLOAT8(Anum_profiler_avg_time, ceil(((float8) us_total) / exec_count) / 1000.0); SET_RESULT(Anum_profiler_max_time, max_time_array); SET_RESULT(Anum_profiler_processed_rows, processed_rows_array); } tuplestore_putvalues(ri->tuple_store, ri->tupdesc, values, nulls); } /* * Store one output row of profiler to result tuplestore in statement * oriented format * */ void plpgsql_check_put_profile_statement(plpgsql_check_result_info *ri, pc_queryid queryid, int stmtid, int parent_stmtid, const char *parent_note, int block_num, int lineno, int64 exec_stmts, int64 exec_stmts_err, double total_time, double max_time, int64 processed_rows, char *stmtname) { Datum values[Natts_profiler_statements]; bool nulls[Natts_profiler_statements]; Assert(ri->tuple_store); Assert(ri->tupdesc); /* ignore invisible statements */ if (lineno <= 0) return; SET_RESULT_INT32(Anum_profiler_statements_stmtid, stmtid); SET_RESULT_INT32(Anum_profiler_statements_block_num, block_num); SET_RESULT_INT32(Anum_profiler_statements_lineno, lineno); if (queryid == NOQUERYID) SET_RESULT_NULL(Anum_profiler_statements_queryid); else SET_RESULT_QUERYID(Anum_profiler_statements_queryid, queryid); SET_RESULT_INT64(Anum_profiler_statements_exec_stmts, exec_stmts); SET_RESULT_INT64(Anum_profiler_statements_exec_stmts_err, exec_stmts_err); SET_RESULT_INT64(Anum_profiler_statements_processed_rows, processed_rows); SET_RESULT_FLOAT8(Anum_profiler_statements_total_time, total_time / 1000.0); SET_RESULT_FLOAT8(Anum_profiler_statements_total_time, total_time / 1000.0); SET_RESULT_FLOAT8(Anum_profiler_statements_max_time, max_time / 1000.0); SET_RESULT_TEXT(Anum_profiler_statements_stmtname, stmtname); if (parent_note) SET_RESULT_TEXT(Anum_profiler_statements_parent_note, parent_note); else SET_RESULT_NULL(Anum_profiler_statements_parent_note); /* set nullable field */ if (parent_stmtid == -1) SET_RESULT_NULL(Anum_profiler_statements_parent_stmtid); else SET_RESULT_INT32(Anum_profiler_statements_parent_stmtid, parent_stmtid); if (exec_stmts > 0) SET_RESULT_FLOAT8(Anum_profiler_statements_avg_time, ceil(((float8) total_time) / exec_stmts) / 1000.0); else SET_RESULT_NULL(Anum_profiler_statements_avg_time); tuplestore_putvalues(ri->tuple_store, ri->tupdesc, values, nulls); } void plpgsql_check_put_profiler_functions_all_tb(plpgsql_check_result_info *ri, Oid funcoid, int64 exec_count, int64 exec_count_err, double total_time, double avg_time, double stddev_time, double min_time, double max_time) { Datum values[Natts_profiler_functions_all_tb]; bool nulls[Natts_profiler_functions_all_tb]; Assert(ri->tuple_store); Assert(ri->tupdesc); SET_RESULT_OID(Anum_profiler_functions_all_funcoid, funcoid); SET_RESULT_INT64(Anum_profiler_functions_all_exec_count, exec_count); SET_RESULT_INT64(Anum_profiler_functions_all_exec_count_err, exec_count_err); SET_RESULT_FLOAT8(Anum_profiler_functions_all_total_time, total_time / 1000.0); SET_RESULT_FLOAT8(Anum_profiler_functions_all_avg_time, avg_time / 1000.0); SET_RESULT_FLOAT8(Anum_profiler_functions_all_stddev_time, stddev_time / 1000.0); SET_RESULT_FLOAT8(Anum_profiler_functions_all_min_time, min_time / 1000.0); SET_RESULT_FLOAT8(Anum_profiler_functions_all_max_time, max_time / 1000.0); tuplestore_putvalues(ri->tuple_store, ri->tupdesc, values, nulls); } plpgsql_check-2.7.8/src/parser.c000066400000000000000000001253541465410532300166130ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * parse_name.c * * parse function signature * parse identifier, and type name * * by Pavel Stehule 2013-2024 * *------------------------------------------------------------------------- */ #include "plpgsql_check.h" #include #include "catalog/namespace.h" #include "parser/scansup.h" #include "parser/parse_type.h" #include "utils/builtins.h" #include "utils/lsyscache.h" #include "utils/regproc.h" /* * Originaly this structure was named TokenType, but this is in collision * with name from NT SDK. So it is renamed to PragmaTokenType. */ typedef struct { int value; const char *substr; size_t size; } PragmaTokenType; typedef struct { const char *str; PragmaTokenType saved_token; bool saved_token_is_valid; } TokenizerState; static char *make_string(PragmaTokenType *token); static const char *tagstr = "@plpgsql_check_options:"; #define PRAGMA_TOKEN_IDENTIF 128 #define PRAGMA_TOKEN_QIDENTIF 129 #define PRAGMA_TOKEN_NUMBER 130 #define PRAGMA_TOKEN_STRING 131 #ifdef _MSC_VER static void * memmem(const void *haystack, size_t haystack_len, const void * const needle, const size_t needle_len) { if (haystack == NULL) return NULL; // or assert(haystack != NULL); if (haystack_len == 0) return NULL; if (needle == NULL) return NULL; // or assert(needle != NULL); if (needle_len == 0) return NULL; for (const char *h = haystack; haystack_len >= needle_len; ++h, --haystack_len) { if (!memcmp(h, needle, needle_len)) { return (void *) h; } } return NULL; } #endif /* * Is character a valid identifier start? * Must match scan.l's {ident_start} character class. */ static bool is_ident_start(unsigned char c) { /* Underscores and ASCII letters are OK */ if (c == '_') return true; if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) return true; /* Any high-bit-set character is OK (might be part of a multibyte char) */ if (IS_HIGHBIT_SET(c)) return true; return false; } /* * Is character a valid identifier continuation? * Must match scan.l's {ident_cont} character class. */ static bool is_ident_cont(unsigned char c) { /* Can be digit or dollar sign ... */ if ((c >= '0' && c <= '9') || c == '$') return true; /* ... or an identifier start character */ return is_ident_start(c); } /* * parse_ident - returns list of Strings when input is valid name. * Returns NIL, when input string is signature. Can raise a error, * when input is not valid identifier. */ static List * parse_name_or_signature(char *qualname, bool *is_signature) { char *nextp; char *rawname; bool after_dot = false; List *result = NIL; /* We need a modifiable copy of the input string. */ rawname = pstrdup(qualname); /* * The code below scribbles on qualname_str in some cases, so we should * reconvert qualname if we need to show the original string in error * messages. */ nextp = rawname; /* skip leading whitespace */ while (scanner_isspace(*nextp)) nextp++; for (;;) { char *curname; bool missing_ident = true; if (*nextp == '"') { char *endp; curname = nextp + 1; for (;;) { endp = strchr(nextp + 1, '"'); if (!endp) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("string is not a valid identifier: \"%s\"", qualname), errdetail("String has unclosed double quotes."))); if (endp[1] != '"') break; memmove(endp, endp + 1, strlen(endp)); nextp = endp; } nextp = endp + 1; *endp = '\0'; if (endp - curname == 0) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("string is not a valid identifier: \"%s\"", qualname), errdetail("Quoted identifier must not be empty."))); truncate_identifier(curname, strlen(curname), true); result = lappend(result, makeString(curname)); missing_ident = false; } else if (is_ident_start((unsigned char) *nextp)) { char *downname; size_t len; curname = nextp++; while (is_ident_cont((unsigned char) *nextp)) nextp++; len = nextp - curname; /* * We don't implicitly truncate identifiers. This is useful for * allowing the user to check for specific parts of the identifier * being too long. It's easy enough for the user to get the * truncated names by casting our output to name[]. */ downname = downcase_truncate_identifier(curname, (int) len, false); result = lappend(result, makeString(downname)); missing_ident = false; } if (missing_ident) { /* Different error messages based on where we failed. */ if (*nextp == '.') ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("string is not a valid identifier: \"%s\"", qualname), errdetail("No valid identifier before \".\"."))); else if (after_dot) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("string is not a valid identifier: \"%s\"", qualname), errdetail("No valid identifier after \".\"."))); else ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("string is not a valid identifier: \"%s\"", qualname))); } while (scanner_isspace(*nextp)) nextp++; if (*nextp == '.') { after_dot = true; nextp++; while (scanner_isspace(*nextp)) nextp++; } else if (*nextp == '\0') { break; } else if (*nextp == '(') { *is_signature = true; return NIL; } else ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("string is not a valid identifier: \"%s\"", qualname))); } *is_signature = false; return result; } /* * Returns Oid of function specified by name or by signature * */ Oid plpgsql_check_parse_name_or_signature(char *name_or_signature) { List *names; bool is_signature; names = parse_name_or_signature(name_or_signature, &is_signature); if (!is_signature) { FuncCandidateList clist; clist = FuncnameGetCandidates(names, -1, NIL, false, false, #if PG_VERSION_NUM >= 140000 false, #endif true); if (clist == NULL) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_FUNCTION), errmsg("function \"%s\" does not exist", name_or_signature))); else if (clist->next != NULL) ereport(ERROR, (errcode(ERRCODE_AMBIGUOUS_FUNCTION), errmsg("more than one function named \"%s\"", name_or_signature))); return clist->oid; } return DatumGetObjectId(DirectFunctionCall1(regprocedurein, CStringGetDatum(name_or_signature))); } /* * Tokenize text. Only one error is possible here - unclosed double quotes. * Returns NULL, when found EOL or on error. */ static PragmaTokenType * get_token(TokenizerState *state, PragmaTokenType *token) { if (state->saved_token_is_valid) { state->saved_token_is_valid = false; return &state->saved_token; } /* skip inital spaces */ while (scanner_isspace(*state->str)) state->str++; if (!*state->str) return NULL; if (isdigit(*state->str)) { bool have_dot = false; token->value = PRAGMA_TOKEN_NUMBER; token->substr = state->str++; while (isdigit(*state->str) || (*state->str == '.')) { if (*state->str == '.') { if (!have_dot) have_dot = true; else break; } state->str += 1; } } else if (*state->str == '"') { bool is_error = true; token->value = PRAGMA_TOKEN_QIDENTIF; token->substr = state->str++; is_error = true; while (*state->str) { if (*state->str == '"') { state->str += 1; if (*state->str != '"') { is_error = false; break; } } state->str += 1; } if (is_error) elog(ERROR, "Syntax error (unclosed quoted identifier)"); } else if (*state->str == '\'') { bool is_error = true; token->value = PRAGMA_TOKEN_STRING; token->substr = state->str++; is_error = true; while (*state->str) { if (*state->str == '\'') { state->str += 1; if (*state->str != '\'') { is_error = false; break; } } state->str += 1; } if (is_error) elog(ERROR, "Syntax error (unclosed quoted identifier)"); } else if (is_ident_start(*state->str)) { token->value = PRAGMA_TOKEN_IDENTIF; token->substr = state->str++; while (is_ident_cont(*state->str)) state->str += 1; } else token->value = *state->str++; token->size = state->str - token->substr; return token; } static void unget_token(TokenizerState *state, PragmaTokenType *token) { if (token) { state->saved_token.value = token->value; state->saved_token.substr = token->substr; state->saved_token.size = token->size; state->saved_token_is_valid = true; } else state->saved_token_is_valid = false; } static bool token_is_keyword(PragmaTokenType *token, const char *str) { if (!token) return false; if (token->value == PRAGMA_TOKEN_IDENTIF && token->size == strlen(str) && strncasecmp(token->substr, str, token->size) == 0) return true; return false; } /* * Returns true if all tokens was read */ static bool tokenizer_eol(TokenizerState *state) { if (state->saved_token_is_valid) return false; while (*state->str) { if (!isspace(*state->str)) return false; state->str += 1; } return true; } static void initialize_tokenizer(TokenizerState *state, const char *str) { state->str = str; state->saved_token_is_valid = false; } static char * make_ident(PragmaTokenType *token) { if (token->value == PRAGMA_TOKEN_IDENTIF) { return downcase_truncate_identifier(token->substr, (int) token->size, false); } else if (token->value == PRAGMA_TOKEN_QIDENTIF) { char *result = palloc(token->size); const char *ptr = token->substr + 1; char *write_ptr; size_t n = token->size - 2; write_ptr = result; while (n-- > 0) { *write_ptr++ = *ptr; if (*ptr++ == '"') { ptr += 1; n -= 1; } } *write_ptr = '\0'; truncate_identifier(result, (int) (write_ptr - result), false); return result; } else if (token->value == PRAGMA_TOKEN_STRING) { char *str = make_string(token); /* does same conversion like varchar->name */ truncate_identifier(str, (int) strlen(str), false); return str; } return NULL; } static char * make_string(PragmaTokenType *token) { if (token->value == PRAGMA_TOKEN_IDENTIF || token->value == PRAGMA_TOKEN_QIDENTIF) return make_ident(token); else if (token->value == PRAGMA_TOKEN_NUMBER) return pnstrdup(token->substr, token->size); else if (token->value == PRAGMA_TOKEN_STRING) { char *result = palloc(token->size); const char *ptr = token->substr + 1; char *write_ptr; size_t n = token->size - 2; write_ptr = result; while (n-- > 0) { *write_ptr++ = *ptr; if (*ptr++ == '\'') { ptr += 1; n -= 1; } } *write_ptr = '\0'; return result; } return NULL; } /* * Returns list of strings used in qualified identifiers */ static List * get_qualified_identifier(TokenizerState *state, List *result) { bool read_atleast_one = false; while (1) { PragmaTokenType token, *_token; _token = get_token(state, &token); if (!_token) break; if (_token->value != PRAGMA_TOKEN_IDENTIF && _token->value != PRAGMA_TOKEN_QIDENTIF) elog(ERROR, "Syntax error (expected identifier)"); result = lappend(result, make_ident(_token)); read_atleast_one = true; _token = get_token(state, &token); if (!_token) break; if (_token->value != '.') { unget_token(state, _token); break; } } if (!read_atleast_one) elog(ERROR, "Syntax error (expected identifier)"); return result; } /* * Set start position and length of qualified identifier. Returns true, * if parsed identifier is valid. */ static void parse_qualified_identifier(TokenizerState *state, const char **startptr, size_t *size) { bool read_atleast_one = false; const char *_startptr = *startptr; size_t _size = 0; while (1) { PragmaTokenType token, *_token; _token = get_token(state, &token); if (!_token) break; if (_token->value != PRAGMA_TOKEN_IDENTIF && _token->value != PRAGMA_TOKEN_QIDENTIF) elog(ERROR, "Syntax error (expected identifier)"); if (!_startptr) { _startptr = _token->substr; _size = _token->size; } else _size = _token->substr - _startptr + _token->size; read_atleast_one = true; _token = get_token(state, &token); if (!_token) break; if (_token->value != '.') { unget_token(state, _token); break; } } if (!read_atleast_one) elog(ERROR, "Syntax error (expected identifier)"); *startptr = _startptr; *size = _size; } /* * When rectype is not allowed, then composite type is allowed only * on top level. */ static Oid get_type_internal(TokenizerState *state, int32 *typmod, bool allow_rectype, bool istop) { PragmaTokenType token, *_token; const char *typename_start = NULL; size_t typename_length = 0; const char *typestr; TypeName *typeName = NULL; Oid typtype; _token = get_token(state, &token); if (!_token) elog(ERROR, "Syntax error (expected identifier)"); if (_token->value == '(') { TupleDesc resultTupleDesc; List *names = NIL; List *types = NIL; List *typmods = NIL; List *collations = NIL; if (!allow_rectype && !istop) elog(ERROR, "Cannot to create table with pseudo-type record."); _token = get_token(state, &token); if (token_is_keyword(_token, "like")) { typtype = get_type_internal(state, typmod, allow_rectype, false); if (!type_is_rowtype(typtype)) elog(ERROR, "\"%s\" is not composite type", format_type_be(typtype)); _token = get_token(state, &token); if (!_token || _token->value != ')') elog(ERROR, "Syntax error (expected \")\")"); return typtype; } else unget_token(state, _token); while (1) { Oid _typtype; int32 _typmod; _token = get_token(state, &token); if (!_token || (_token->value != PRAGMA_TOKEN_IDENTIF && _token->value != PRAGMA_TOKEN_QIDENTIF)) elog(ERROR, "Syntax error (expected identifier)"); names = lappend(names, makeString(make_ident(_token))); _typtype = get_type_internal(state, &_typmod, allow_rectype, false); types = lappend_oid(types, _typtype); typmods = lappend_int(typmods, _typmod); collations = lappend_oid(collations, InvalidOid); _token = get_token(state, &token); if (!_token) elog(ERROR, "Syntax error (unclosed composite type definition - expected \")\")"); if (_token->value == ')') { break; } else if (_token->value != ',') elog(ERROR, "Syntax error (expected \",\")"); } resultTupleDesc = BuildDescFromLists(names, types, typmods, collations); resultTupleDesc = BlessTupleDesc(resultTupleDesc); *typmod = resultTupleDesc->tdtypmod; return resultTupleDesc->tdtypeid; } else if (_token->value == PRAGMA_TOKEN_QIDENTIF) { unget_token(state, _token); parse_qualified_identifier(state, &typename_start, &typename_length); } else if (_token->value == PRAGMA_TOKEN_IDENTIF) { PragmaTokenType token2, *_token2; _token2 = get_token(state, &token2); if (_token2) { if (_token2->value == '.') { typename_start = _token->substr; typename_length = _token->size; parse_qualified_identifier(state, &typename_start, &typename_length); } else { /* multi word type name */ typename_start = _token->substr; typename_length = _token->size; while (_token2 && _token2->value == PRAGMA_TOKEN_IDENTIF) { typename_length = _token2->substr + _token2->size - typename_start; _token2 = get_token(state, &token2); } unget_token(state, _token2); } } else { typename_start = _token->substr; typename_length = _token->size; } } else elog(ERROR, "Syntax error (expected identifier)"); /* get typmod */ _token = get_token(state, &token); if (_token) { if (_token->value == '(') { while (1) { _token = get_token(state, &token); if (!_token || _token->value != PRAGMA_TOKEN_NUMBER) elog(ERROR, "Syntax error (expected number for typmod specification)"); _token = get_token(state, &token); if (!_token) elog(ERROR, "Syntax error (unclosed typmod specification)"); if (_token->value == ')') { break; } else if (_token->value != ',') elog(ERROR, "Syntax error (expected \",\" in typmod list)"); } typename_length = _token->substr + _token->size - typename_start; } else unget_token(state, _token); } /* get array symbols */ _token = get_token(state, &token); if (_token) { if (_token->value == '[') { _token = get_token(state, &token); if (_token && _token->value == PRAGMA_TOKEN_NUMBER) _token = get_token(state, &token); if (!_token) elog(ERROR, "Syntax error (unclosed array specification)"); if (_token->value != ']') elog(ERROR, "Syntax error (expected \"]\")"); typename_length = _token->substr + _token->size - typename_start; } else unget_token(state, _token); } typestr = pnstrdup(typename_start, typename_length); #if PG_VERSION_NUM >= 160000 typeName = typeStringToTypeName(typestr, NULL); #else typeName = typeStringToTypeName(typestr); #endif typenameTypeIdAndMod(NULL, typeName, &typtype, typmod); return typtype; } static Oid get_type(TokenizerState *state, int32 *typmod, bool allow_rectype) { return get_type_internal(state, typmod, allow_rectype, true); } static int get_varno(PLpgSQL_nsitem *cur_ns, List *names) { char *name1 = NULL; char *name2 = NULL; char *name3 = NULL; int names_used; PLpgSQL_nsitem *nsitem; switch (list_length(names)) { case 1: { name1 = (char *) linitial(names); break; } case 2: { name1 = (char *) linitial(names); name2 = (char *) lsecond(names); break; } case 3: { name1 = (char *) linitial(names); name2 = (char *) lsecond(names); name3 = (char *) lthird(names); break; } default: return -1; } nsitem = plpgsql_check__ns_lookup_p(cur_ns, false, name1, name2, name3, &names_used); return nsitem ? nsitem->itemno : -1; } static char * get_name(List *names) { bool is_first = true; ListCell *lc; StringInfoData sinfo; initStringInfo(&sinfo); foreach(lc, names) { if (is_first) is_first = false; else appendStringInfoChar(&sinfo, '.'); appendStringInfo(&sinfo, "\"%s\"", (char *) lfirst(lc)); } return sinfo.data; } static const char * pragma_assert_name(PragmaAssertType pat) { switch (pat) { case PLPGSQL_CHECK_PRAGMA_ASSERT_SCHEMA: return "assert-schema"; case PLPGSQL_CHECK_PRAGMA_ASSERT_TABLE: return "assert-table"; case PLPGSQL_CHECK_PRAGMA_ASSERT_COLUMN: return "assert-column"; } return NULL; } static Oid check_var_schema(PLpgSQL_checkstate *cstate, int dno) { return get_namespace_oid(cstate->strconstvars[dno], true); } static Oid check_var_table(PLpgSQL_checkstate *cstate, int dno1, int dno2) { char *relname = cstate->strconstvars[dno2]; Oid relid = InvalidOid; if (dno1 != -1) relid = get_relname_relid(relname, check_var_schema(cstate, dno1)); else relid = RelnameGetRelid(relname); if (!OidIsValid(relid)) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_TABLE), errmsg("table \"%s\" does not exist", relname))); return relid; } static AttrNumber check_var_column(PLpgSQL_checkstate *cstate, int dno1, int dno2, int dno3) { char *attname = cstate->strconstvars[dno3]; Oid relid = check_var_table(cstate, dno1, dno2); AttrNumber attnum; attnum = get_attnum(relid, attname); if (attnum == InvalidAttrNumber) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN), errmsg("column \"%s\" of relation \"%s\".\"%s\" does not exist", attname, get_namespace_name(get_rel_namespace(relid)), get_rel_name(relid)))); return attnum; } bool plpgsql_check_pragma_assert(PLpgSQL_checkstate *cstate, PragmaAssertType pat, const char *str, PLpgSQL_nsitem *ns, int lineno) { MemoryContext oldCxt; ResourceOwner oldowner; volatile int dno[3]; volatile int nvars = 0; volatile bool result = true; /* * namespace is available only in compile check mode, and only in this mode * this pragma can be used. */ if (!ns || !cstate) return true; oldCxt = CurrentMemoryContext; oldowner = CurrentResourceOwner; BeginInternalSubTransaction(NULL); MemoryContextSwitchTo(cstate->check_cxt); PG_TRY(); { TokenizerState tstate; int i; List *names; initialize_tokenizer(&tstate, str); for (i = 0; i < 3; i++) { if (i > 0) { PragmaTokenType token, *_token; _token = get_token(&tstate, &token); if (_token->value != ',') elog(ERROR, "Syntax error (expected \",\")"); } names = get_qualified_identifier(&tstate, NULL); if ((dno[i] = get_varno(ns, names)) == -1) elog(ERROR, "Cannot to find variable %s used in \"%s\" pragma", get_name(names), pragma_assert_name(pat)); if (!cstate->strconstvars || !cstate->strconstvars[dno[i]]) elog(ERROR, "Variable %s has not assigned constant", get_name(names)); nvars += 1; if (tokenizer_eol(&tstate)) break; } if (!tokenizer_eol(&tstate)) elog(ERROR, "Syntax error (unexpected chars after variable)"); if ((pat == PLPGSQL_CHECK_PRAGMA_ASSERT_SCHEMA && nvars > 1) || (pat == PLPGSQL_CHECK_PRAGMA_ASSERT_TABLE && nvars > 2) || (pat == PLPGSQL_CHECK_PRAGMA_ASSERT_COLUMN && nvars > 3)) elog(ERROR, "too much variables for \"%s\" pragma", pragma_assert_name(pat)); RollbackAndReleaseCurrentSubTransaction(); MemoryContextSwitchTo(oldCxt); CurrentResourceOwner = oldowner; } PG_CATCH(); { ErrorData *edata; MemoryContextSwitchTo(cstate->check_cxt); edata = CopyErrorData(); FlushErrorState(); MemoryContextSwitchTo(oldCxt); FlushErrorState(); RollbackAndReleaseCurrentSubTransaction(); MemoryContextSwitchTo(oldCxt); CurrentResourceOwner = oldowner; /* raise warning (errors in pragma can be ignored instead */ ereport(WARNING, (errmsg("\"%s\" on line %d is not processed.", pragma_assert_name(pat), lineno), errdetail("%s", edata->message))); result = false; } PG_END_TRY(); if (!result) return false; if (pat == PLPGSQL_CHECK_PRAGMA_ASSERT_SCHEMA) { (void) check_var_schema(cstate, dno[0]); } else if (pat == PLPGSQL_CHECK_PRAGMA_ASSERT_TABLE) { if (nvars == 1) (void) check_var_table(cstate, -1, dno[0]); else (void) check_var_table(cstate, dno[0], dno[1]); } else if (pat == PLPGSQL_CHECK_PRAGMA_ASSERT_COLUMN) { if (nvars == 2) (void) check_var_column(cstate, -1, dno[0], dno[1]); else (void) check_var_column(cstate, dno[0], dno[1], dno[2]); } return result; } bool plpgsql_check_pragma_type(PLpgSQL_checkstate *cstate, const char *str, PLpgSQL_nsitem *ns, int lineno) { MemoryContext oldCxt; ResourceOwner oldowner; volatile bool result = true; /* * namespace is available only in compile check mode, and only in this mode * this pragma can be used. */ if (!ns || !cstate) return true; oldCxt = CurrentMemoryContext; oldowner = CurrentResourceOwner; BeginInternalSubTransaction(NULL); MemoryContextSwitchTo(cstate->check_cxt); PG_TRY(); { TokenizerState tstate; int target_dno; PLpgSQL_datum *target; List *names; Oid typtype; int32 typmod; TupleDesc typtupdesc; initialize_tokenizer(&tstate, str); names = get_qualified_identifier(&tstate, NULL); if ((target_dno = get_varno(ns, names)) == -1) elog(ERROR, "Cannot to find variable %s used in settype pragma", get_name(names)); target = cstate->estate->datums[target_dno]; if (target->dtype != PLPGSQL_DTYPE_REC) elog(ERROR, "Pragma \"settype\" can be applied only on variable of record type"); typtype = get_type(&tstate, &typmod, true); if (!tokenizer_eol(&tstate)) elog(ERROR, "Syntax error (unexpected chars after type specification)"); typtupdesc = lookup_rowtype_tupdesc_copy(typtype, typmod); plpgsql_check_assign_tupdesc_dno(cstate, target_dno, typtupdesc, false); cstate->typed_variables = bms_add_member(cstate->typed_variables, target_dno); RollbackAndReleaseCurrentSubTransaction(); MemoryContextSwitchTo(oldCxt); CurrentResourceOwner = oldowner; } PG_CATCH(); { ErrorData *edata; MemoryContextSwitchTo(cstate->check_cxt); edata = CopyErrorData(); FlushErrorState(); MemoryContextSwitchTo(oldCxt); FlushErrorState(); RollbackAndReleaseCurrentSubTransaction(); MemoryContextSwitchTo(oldCxt); CurrentResourceOwner = oldowner; /* raise warning (errors in pragma can be ignored instead */ ereport(WARNING, (errmsg("Pragma \"type\" on line %d is not processed.", lineno), errdetail("%s", edata->message))); result = false; } PG_END_TRY(); return result; } /* * Unfortunately the ephemeral tables introduced in PostgreSQL 10 cannot be * used for this purpose, because any DML operations are prohibited, and others * DML catalogue operations doesn't calculate with Ephemeral space. */ bool plpgsql_check_pragma_table(PLpgSQL_checkstate *cstate, const char *str, int lineno) { MemoryContext oldCxt; ResourceOwner oldowner; volatile bool result = true; if (!cstate) return true; oldCxt = CurrentMemoryContext; oldowner = CurrentResourceOwner; BeginInternalSubTransaction(NULL); MemoryContextSwitchTo(cstate->check_cxt); PG_TRY(); { TokenizerState tstate; PragmaTokenType token, *_token; PragmaTokenType token2, *_token2; StringInfoData query; int32 typmod; initialize_tokenizer(&tstate, str); _token = get_token(&tstate, &token); if (!_token || (_token->value != PRAGMA_TOKEN_IDENTIF && _token->value != PRAGMA_TOKEN_QIDENTIF)) elog(ERROR, "Syntax error (expected identifier)"); _token2 = get_token(&tstate, &token2); if (_token2 && _token2->value == '.') { char *nsname = make_ident(_token); if (strcmp(nsname, "pg_temp") != 0) elog(ERROR, "schema \"%s\" cannot be used in pragma \"table\" (only \"pg_temp\" schema is allowed)", nsname); _token = get_token(&tstate, &token); if (!_token || (_token->value != PRAGMA_TOKEN_IDENTIF && _token->value != PRAGMA_TOKEN_QIDENTIF)) elog(ERROR, "Syntax error (expected identifier)"); _token2 = get_token(&tstate, &token2); } if (!_token2 || _token2->value != '(') elog(ERROR, "Syntax error (expected table specification)"); unget_token(&tstate, _token2); (void) get_type(&tstate, &typmod, false); if (!tokenizer_eol(&tstate)) elog(ERROR, "Syntax error (unexpected chars after table specification)"); /* In this case we use parser just for syntax check and security check */ initStringInfo(&query); appendStringInfoString(&query, "CREATE TEMP TABLE "); appendStringInfoString(&query, str); if (SPI_execute(query.data, false, 0) != SPI_OK_UTILITY) elog(NOTICE, "Cannot to create temporary table"); ReleaseCurrentSubTransaction(); MemoryContextSwitchTo(oldCxt); CurrentResourceOwner = oldowner; } PG_CATCH(); { ErrorData *edata; MemoryContextSwitchTo(cstate->check_cxt); edata = CopyErrorData(); FlushErrorState(); MemoryContextSwitchTo(oldCxt); FlushErrorState(); RollbackAndReleaseCurrentSubTransaction(); MemoryContextSwitchTo(oldCxt); CurrentResourceOwner = oldowner; /* raise warning (errors in pragma can be ignored instead */ ereport(WARNING, (errmsg("Pragma \"table\" on line %d is not processed.", lineno), errdetail("%s", edata->message))); result = false; } PG_END_TRY(); return result; } /* * An sequence can be temporary too, so there should be related PRAGMA */ bool plpgsql_check_pragma_sequence(PLpgSQL_checkstate *cstate, const char *str, int lineno) { MemoryContext oldCxt; ResourceOwner oldowner; volatile bool result = true; if (!cstate) return true; oldCxt = CurrentMemoryContext; oldowner = CurrentResourceOwner; BeginInternalSubTransaction(NULL); MemoryContextSwitchTo(cstate->check_cxt); PG_TRY(); { TokenizerState tstate; PragmaTokenType token, *_token; PragmaTokenType token2, *_token2; StringInfoData query; initialize_tokenizer(&tstate, str); _token = get_token(&tstate, &token); if (!_token || (_token->value != PRAGMA_TOKEN_IDENTIF && _token->value != PRAGMA_TOKEN_QIDENTIF)) elog(ERROR, "Syntax error (expected identifier)"); _token2 = get_token(&tstate, &token2); if (_token2 && _token2->value == '.') { char *nsname = make_ident(_token); if (strcmp(nsname, "pg_temp") != 0) elog(ERROR, "schema \"%s\" cannot be used in pragma \"sequence\" (only \"pg_temp\" schema is allowed)", nsname); _token = get_token(&tstate, &token); if (!_token || (_token->value != PRAGMA_TOKEN_IDENTIF && _token->value != PRAGMA_TOKEN_QIDENTIF)) elog(ERROR, "Syntax error (expected identifier)"); (void) get_token(&tstate, &token2); } if (!tokenizer_eol(&tstate)) elog(ERROR, "Syntax error (unexpected chars after sequence name)"); /* In this case we use parser just for syntax check and security check */ initStringInfo(&query); appendStringInfoString(&query, "CREATE TEMP SEQUENCE "); appendStringInfoString(&query, str); if (SPI_execute(query.data, false, 0) != SPI_OK_UTILITY) elog(NOTICE, "Cannot to create temporary sequence"); ReleaseCurrentSubTransaction(); MemoryContextSwitchTo(oldCxt); CurrentResourceOwner = oldowner; } PG_CATCH(); { ErrorData *edata; MemoryContextSwitchTo(cstate->check_cxt); edata = CopyErrorData(); FlushErrorState(); MemoryContextSwitchTo(oldCxt); FlushErrorState(); RollbackAndReleaseCurrentSubTransaction(); MemoryContextSwitchTo(oldCxt); CurrentResourceOwner = oldowner; /* raise warning (errors in pragma can be ignored instead */ ereport(WARNING, (errmsg("Pragma \"sequence\" on line %d is not processed.", lineno), errdetail("%s", edata->message))); result = false; } PG_END_TRY(); return result; } static bool get_boolean_comment_option(TokenizerState *tstate, const char *name, plpgsql_check_info *cinfo) { PragmaTokenType token, *_token; _token = get_token(tstate, &token); if (!_token) return true; if (_token->value == ',') { unget_token(tstate, _token); return true; } if (_token->value == '=') { _token = get_token(tstate, &token); if (!_token) elog(ERROR, "syntax error in comment option \"%s\" (fnoid: %u) (expected boolean value after \"=\")", name, cinfo->fn_oid); } if (token_is_keyword(_token, "true") || token_is_keyword(_token, "yes") || token_is_keyword(_token, "t") || token_is_keyword(_token, "on")) return true; else if (token_is_keyword(_token, "false") || token_is_keyword(_token, "no") || token_is_keyword(_token, "f") || token_is_keyword(_token, "off")) return false; else elog(ERROR, "syntax error in comment option \"%s\" (fnoid: %u) (expected boolean value)", name, cinfo->fn_oid); /* fix warning C4715 on msvc */ Assert(0); return false; } static char * get_name_comment_option(TokenizerState *tstate, const char *name, plpgsql_check_info *cinfo) { PragmaTokenType token, *_token; _token = get_token(tstate, &token); if (!_token) elog(ERROR, "syntax error in comment option \"%s\" (fnoid: %u) (expected option's argument of name type)", name, cinfo->fn_oid); if (_token->value == '=') { _token = get_token(tstate, &token); if (!_token) elog(ERROR, "syntax error in comment option \"%s\" (fnoid: %u) (expected name value after \"=\")", name, cinfo->fn_oid); } if (_token->value == PRAGMA_TOKEN_IDENTIF || _token->value == PRAGMA_TOKEN_QIDENTIF || _token->value == PRAGMA_TOKEN_STRING) { return pstrdup(make_ident(_token)); } else elog(ERROR, "syntax error in comment option \"%s\" (fnoid: %u) (expected SQL identifier as argument)", name, cinfo->fn_oid); /* fix warning C4715 on msvc */ Assert(0); return NULL; } static Oid get_type_comment_option(TokenizerState *tstate, const char *name, plpgsql_check_info *cinfo) { PragmaTokenType token, *_token; _token = get_token(tstate, &token); if (!_token) elog(ERROR, "syntax error in comment option \"%s\" (fnoid: %u) (expected option's argument of type name)", name, cinfo->fn_oid); if (_token->value == '=') { _token = get_token(tstate, &token); if (!_token) elog(ERROR, "syntax error in comment option \"%s\" (fnoid: %u) (expected type name after \"=\")", name, cinfo->fn_oid); } if (_token->value == PRAGMA_TOKEN_IDENTIF || _token->value == PRAGMA_TOKEN_QIDENTIF) { const char *typname_start = NULL; size_t typname_length; char *typestr; Oid typid; int32 typmod; unget_token(tstate, _token); parse_qualified_identifier(tstate, &typname_start, &typname_length); typestr = pnstrdup(typname_start, typname_length); parseTypeString(typestr, &typid, &typmod, false); return typid; } else elog(ERROR, "syntax error in comment option \"%s\" (fnoid: %u) (expected type identifier)", name, cinfo->fn_oid); /* fix warning C4715 on msvc */ Assert(0); return InvalidOid; } static Oid get_table_comment_option(TokenizerState *tstate, const char *name, plpgsql_check_info *cinfo) { PragmaTokenType token, *_token; _token = get_token(tstate, &token); if (!_token) elog(ERROR, "syntax error in comment option \"%s\" (fnoid: %u) (expected option's argument of table name)", name, cinfo->fn_oid); if (_token->value == '=') { _token = get_token(tstate, &token); if (!_token) elog(ERROR, "syntax error in comment option \"%s\" (fnoid: %u) (expected table name after \"=\")", name, cinfo->fn_oid); } if (_token->value == PRAGMA_TOKEN_IDENTIF || _token->value == PRAGMA_TOKEN_QIDENTIF) { List *names; const char *tablename_start = NULL; size_t tablename_length; char *tablenamestr; Oid result; unget_token(tstate, _token); parse_qualified_identifier(tstate, &tablename_start, &tablename_length); tablenamestr = pnstrdup(tablename_start, tablename_length); #if PG_VERSION_NUM >= 160000 names = stringToQualifiedNameList(tablenamestr, NULL); #else names = stringToQualifiedNameList(tablenamestr); #endif /* We might not even have permissions on this relation; don't lock it. */ result = RangeVarGetRelid(makeRangeVarFromNameList(names), NoLock, false); return result; } else elog(ERROR, "syntax error in comment option \"%s\" (fnoid: %u) (expected table identifier)", name, cinfo->fn_oid); /* fix warning C4715 on msvc */ Assert(0); return InvalidOid; } static bool is_keyword(char *str, size_t bytes, const char *keyword) { if (bytes != strlen(keyword)) return false; if (strncasecmp(str, keyword, bytes) != 0) return false; return true; } char * plpgsql_check_process_echo_string(char *str, plpgsql_check_info *cinfo) { StringInfoData sinfo; initStringInfo(&sinfo); while (*str) { if (*str == '@' && str[1] == '@') { char *start; size_t bytes; str += 2; start = str; while (*str && isalpha(*str)) { str += 1; } bytes = str - start; if (is_keyword(start, bytes, "id")) { appendStringInfo(&sinfo, "%u", cinfo->fn_oid); } else if (is_keyword(start, bytes, "name")) { appendStringInfoString(&sinfo, get_func_name(cinfo->fn_oid)); } else if (is_keyword(start, bytes, "signature")) { appendStringInfoString(&sinfo, format_procedure(cinfo->fn_oid)); } else appendStringInfo(&sinfo, "@@%.*s", (int) bytes, start); } else appendStringInfoChar(&sinfo, *str++); } return sinfo.data; } static void comment_options_parser(char *str, plpgsql_check_info *cinfo) { TokenizerState tstate; PragmaTokenType token, *_token; initialize_tokenizer(&tstate, str); do { _token = get_token(&tstate, &token); if (!_token || (_token->value != PRAGMA_TOKEN_IDENTIF)) elog(ERROR, "Syntax error (fnoid: %u) (expected option name)", cinfo->fn_oid); if (cinfo->incomment_options_usage_warning) elog(WARNING, "comment option \"%s\" is used (oid: %u)", make_ident(_token), cinfo->fn_oid); if (token_is_keyword(_token, "relid")) { cinfo->relid = get_table_comment_option(&tstate, "relid", cinfo); } else if (token_is_keyword(_token, "fatal_errors")) { cinfo->fatal_errors = get_boolean_comment_option(&tstate, "fatal_errors", cinfo); } else if (token_is_keyword(_token, "other_warnings")) { cinfo->other_warnings = get_boolean_comment_option(&tstate, "other_warnings", cinfo); } else if (token_is_keyword(_token, "extra_warnings")) { cinfo->extra_warnings = get_boolean_comment_option(&tstate, "extra_warnings", cinfo); } else if (token_is_keyword(_token, "performance_warnings")) { cinfo->performance_warnings = get_boolean_comment_option(&tstate, "performance_warnings", cinfo); } else if (token_is_keyword(_token, "security_warnings")) { cinfo->security_warnings = get_boolean_comment_option(&tstate, "security_warnings", cinfo); } else if (token_is_keyword(_token, "compatibility_warnings")) { cinfo->compatibility_warnings = get_boolean_comment_option(&tstate, "compatibility_warnings", cinfo); } else if (token_is_keyword(_token, "anyelementtype")) { cinfo->anyelementoid = get_type_comment_option(&tstate, "anyelementtype",cinfo); } else if (token_is_keyword(_token, "anyenumtype")) { cinfo->anyenumoid = get_type_comment_option(&tstate, "anyenumtype", cinfo); } else if (token_is_keyword(_token, "anyrangetype")) { cinfo->anyrangeoid = get_type_comment_option(&tstate, "anyrangetype", cinfo); if (!type_is_range(cinfo->anyrangeoid)) elog(ERROR, "the type specified by \"anyrangetype\" comment option is not range (fnoid: %u)", cinfo->fn_oid); } else if (token_is_keyword(_token, "anycompatibletype")) { cinfo->anycompatibleoid = get_type_comment_option(&tstate, "anycompatibletype", cinfo); } else if (token_is_keyword(_token, "anycompatiblerangetype")) { cinfo->anycompatiblerangeoid = get_type_comment_option(&tstate, "anycompatiblerangetype", cinfo); if (!type_is_range(cinfo->anycompatiblerangeoid)) elog(ERROR, "the type specified by \"anycompatiblerangetype\" comment option is not range (fnoid: %u)", cinfo->fn_oid); } else if (token_is_keyword(_token, "without_warnings")) { cinfo->without_warnings = get_boolean_comment_option(&tstate, "without_warnings", cinfo); } else if (token_is_keyword(_token, "all_warnings")) { cinfo->all_warnings = get_boolean_comment_option(&tstate, "all_warnings", cinfo); } else if (token_is_keyword(_token, "newtable")) { cinfo->newtable = get_name_comment_option(&tstate, "newtable", cinfo); } else if (token_is_keyword(_token, "oldtable")) { cinfo->oldtable = get_name_comment_option(&tstate, "oldtable", cinfo); } else if (token_is_keyword(_token, "echo")) { _token = get_token(&tstate, &token); if (!_token) elog(ERROR, "missing argument of option \"echo\""); if (_token->value == '=') { _token = get_token(&tstate, &token); if (!_token) elog(ERROR, "expected value after \"=\""); } if (_token->value == PRAGMA_TOKEN_IDENTIF) elog(NOTICE, "comment option \"echo\" is %s", plpgsql_check_process_echo_string(make_string(_token), cinfo)); else if (_token->value == PRAGMA_TOKEN_QIDENTIF) elog(NOTICE, "comment option \"echo\" is \"%s\"", plpgsql_check_process_echo_string(make_string(_token), cinfo)); else if (_token->value == PRAGMA_TOKEN_NUMBER) elog(NOTICE, "comment option \"echo\" is %s", plpgsql_check_process_echo_string(make_string(_token), cinfo)); else if (_token->value == PRAGMA_TOKEN_STRING) elog(NOTICE, "comment option \"echo\" is '%s'", plpgsql_check_process_echo_string(make_string(_token), cinfo)); else elog(NOTICE, "comment option \"echo\" is '%c'", _token->value); } else elog(ERROR, "unsupported option \"%.*s\" specified by \"@plpgsql_check_options\" (fnoid: %u)", (int) _token->size, _token->substr, cinfo->fn_oid); _token = get_token(&tstate, &token); if (!_token) break; if (_token->value != ',') elog(ERROR, "expected \",\" or end of line on line with \"@plpgsql_check_options\" options (fnoid: %u)", cinfo->fn_oid); } while (_token); } static void comment_options_parsecontent(char *str, size_t bytes, plpgsql_check_info *cinfo) { char *endchar = str + bytes; do { char *ptr, *optsline; bool found_eol; str += strlen(tagstr); Assert(str <= endchar); /* find end of line */ ptr = str; found_eol = false; while (ptr < endchar && *ptr) { if (*ptr == '\n') { found_eol = true; break; } ptr += 1; } optsline = pnstrdup(str, found_eol ? ptr - str : endchar - str); comment_options_parser(optsline, cinfo); pfree(optsline); if (!found_eol || ptr >= endchar) break; str = memmem(ptr + 1, endchar - (ptr + 1), tagstr, strlen(tagstr)); } while (str); } static char * search_comment_options_linecomment(char *src, plpgsql_check_info *cinfo) { char *start = src; while (*src) { if (*src == '\n') { char *tag; tag = memmem(start, src - start, tagstr, strlen(tagstr)); if (tag) comment_options_parsecontent(tag, src - tag, cinfo); return src + 1; } src += 1; } return src; } static char * search_comment_options_multilinecomment(char *src, plpgsql_check_info *cinfo) { char *start = src; while (*src) { if (*src == '*' && src[1] == '/') { char *tag; tag = memmem(start, src - start, tagstr, strlen(tagstr)); if (tag) comment_options_parsecontent(tag, src - tag, cinfo); return src + 1; } src += 1; } return src; } /* * Try to read plpgsql_check options. * */ void plpgsql_check_search_comment_options(plpgsql_check_info *cinfo) { char *src = plpgsql_check_get_src(cinfo->proctuple); cinfo->all_warnings = false; cinfo->without_warnings = false; while (*src) { if (*src == '-' && src[1] == '-') src = search_comment_options_linecomment(src + 2, cinfo); else if (*src == '/' && src[1] == '*') src = search_comment_options_multilinecomment(src + 2, cinfo); else if (*src == '\'') { src++; while (*src) { if (*src++ == '\'') { if (*src == '\'') src += 1; else break; } } } else if (*src == '"') { src++; while (*src) { if (*src++ == '"') { if (*src == '"') src += 1; else break; } } } else if (*src == '$') { char *start = src++; bool is_custom_string = false; while (*src) { if (isblank(*src)) { is_custom_string = false; break; } else if (*src == '$') { is_custom_string = true; break; } src += 1; } if (is_custom_string) { size_t cust_str_length = 0; cust_str_length = src - start + 1; next_char: src += 1; while (*src) { size_t i; for (i = 0; i < cust_str_length; i++) { if (src[i] != start[i]) goto next_char; } /* found complete custom string separator */ src += cust_str_length; break; } } else src = start + 1; } else src += 1; } if (cinfo->all_warnings && cinfo->without_warnings) elog(ERROR, "all_warnings and without_warnings cannot be used together (fnoid: %u)", cinfo->fn_oid); if (cinfo->all_warnings) plpgsql_check_set_all_warnings(cinfo); else if (cinfo->without_warnings) plpgsql_check_set_without_warnings(cinfo); } plpgsql_check-2.7.8/src/pldbgapi2.c000066400000000000000000001027621465410532300171610ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * pldbgapi2 * * enhanced debug API for plpgsql * * by Pavel Stehule 2013-2024 * *------------------------------------------------------------------------- * * Notes: * PL debug API has few issues related to plpgsql profiler and tracer: * * 1. Only one extension that use this API can be active * * 2. Doesn't catch an application's exceptions, and cannot to handle an * exceptions in applications. * * pldbgapi2 does new interfaces based on pl debug API and fmgr API, and try * to solve these issues. It can be used by more at the same time plugins, * and allows to set hooks on end of execution of statement or function at * aborted state. * */ #include "postgres.h" #include "plpgsql.h" #include "fmgr.h" #include "catalog/pg_proc.h" #include "catalog/pg_language.h" #include "commands/proclang.h" #include "utils/inval.h" #include "utils/lsyscache.h" #include "utils/syscache.h" #include "utils/palloc.h" #include "plpgsql_check.h" #define MAX_PLDBGAPI2_PLUGINS 10 #define INITIAL_PLDBGAPI2_STMT_STACK_SIZE 32 #define FMGR_CACHE_MAGIC 2023071110 #define PLUGIN_INFO_MAGIC 2023071111 static Oid PLpgSQLlanguageId = InvalidOid; static Oid PLpgSQLinlineFunc = InvalidOid; typedef struct func_info_hashkey { Oid fn_oid; TransactionId fn_xmin; ItemPointerData fn_tid; } func_info_hashkey; typedef struct func_info_entry { func_info_hashkey key; uint32 hashValue; char *fn_name; char *fn_signature; plpgsql_check_plugin2_stmt_info *stmts_info; int *stmtid_map; int nstatements; int use_count; bool is_valid; } func_info_entry; static HTAB *func_info_HashTable = NULL; typedef struct fmgr_cache { int magic; Oid funcid; bool is_plpgsql; Datum arg; } fmgr_cache; typedef struct fmgr_plpgsql_cache { int magic; Oid funcid; bool is_plpgsql; Datum arg; void *plugin2_info[MAX_PLDBGAPI2_PLUGINS]; MemoryContext fn_mcxt; int *stmtid_stack; int stmtid_stack_size; int current_stmtid_stack_size; func_info_entry *func_info; } fmgr_plpgsql_cache; static fmgr_plpgsql_cache *last_fmgr_plpgsql_cache = NULL; static needs_fmgr_hook_type prev_needs_fmgr_hook = NULL; static fmgr_hook_type prev_fmgr_hook = NULL; static plpgsql_check_plugin2 *plpgsql_plugins2[MAX_PLDBGAPI2_PLUGINS]; static int nplpgsql_plugins2 = 0; static void pldbgapi2_func_setup(PLpgSQL_execstate *estate, PLpgSQL_function *func); static void pldbgapi2_func_beg(PLpgSQL_execstate *estate, PLpgSQL_function *func); static void pldbgapi2_func_end(PLpgSQL_execstate *estate, PLpgSQL_function *func); static void pldbgapi2_stmt_beg(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt); static void pldbgapi2_stmt_end(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt); static PLpgSQL_plugin pldbgapi2_plugin = { pldbgapi2_func_setup, pldbgapi2_func_beg, pldbgapi2_func_end, pldbgapi2_stmt_beg, pldbgapi2_stmt_end, #if PG_VERSION_NUM >= 150000 NULL, NULL, NULL, NULL, NULL }; #else NULL, NULL }; #endif static PLpgSQL_plugin *prev_plpgsql_plugin = NULL; MemoryContext pldbgapi2_mcxt = NULL; typedef struct pldbgapi2_plugin_info { int magic; fmgr_plpgsql_cache *fcache_plpgsql; void *prev_plugin_info; } pldbgapi2_plugin_info; static func_info_entry *get_func_info(PLpgSQL_function *func); static fmgr_plpgsql_cache *current_fmgr_plpgsql_cache; plpgsql_check_plugin2_stmt_info * plpgsql_check_get_current_stmt_info(int stmtid) { Assert(current_fmgr_plpgsql_cache); Assert(current_fmgr_plpgsql_cache->func_info); Assert(stmtid <= current_fmgr_plpgsql_cache->func_info->nstatements); return &(current_fmgr_plpgsql_cache->func_info->stmts_info[stmtid - 1]); } plpgsql_check_plugin2_stmt_info * plpgsql_check_get_current_stmts_info(void) { Assert(current_fmgr_plpgsql_cache); Assert(current_fmgr_plpgsql_cache->func_info); Assert(current_fmgr_plpgsql_cache->func_info->use_count > 0); return current_fmgr_plpgsql_cache->func_info->stmts_info; } /* * It is used outside pldbapi2 plugins. This is used by output functions, * so we don't need to solve effectivity too much. Instead handling use_count * returns copy. */ plpgsql_check_plugin2_stmt_info * plpgsql_check_get_stmts_info(PLpgSQL_function *func) { func_info_entry *func_info; plpgsql_check_plugin2_stmt_info *stmts_info; size_t bytes; func_info = get_func_info(func); bytes = func->nstatements * sizeof(plpgsql_check_plugin2_stmt_info); stmts_info = palloc(bytes); memcpy(stmts_info, func_info->stmts_info, bytes); return stmts_info; } int * plpgsql_check_get_current_stmtid_map(void) { Assert(current_fmgr_plpgsql_cache); Assert(current_fmgr_plpgsql_cache->func_info); Assert(current_fmgr_plpgsql_cache->func_info->use_count > 0); return current_fmgr_plpgsql_cache->func_info->stmtid_map; } int * plpgsql_check_get_stmtid_map(PLpgSQL_function *func) { func_info_entry *func_info; int *stmtid_map; size_t bytes; func_info = get_func_info(func); bytes = func->nstatements * sizeof(int); stmtid_map = palloc(bytes); memcpy(stmtid_map, func_info->stmtid_map, bytes); return stmtid_map; } char * plpgsql_check_get_current_func_info_name(void) { Assert(current_fmgr_plpgsql_cache); Assert(current_fmgr_plpgsql_cache->func_info); Assert(current_fmgr_plpgsql_cache->func_info->use_count > 0); return current_fmgr_plpgsql_cache->func_info->fn_name; } char * plpgsql_check_get_current_func_info_signature(void) { Assert(current_fmgr_plpgsql_cache); Assert(current_fmgr_plpgsql_cache->func_info); Assert(current_fmgr_plpgsql_cache->func_info->use_count > 0); Assert(current_fmgr_plpgsql_cache->func_info->fn_signature); return current_fmgr_plpgsql_cache->func_info->fn_signature; } MemoryContext plpgsql_check_get_current_fn_mcxt(void) { Assert(current_fmgr_plpgsql_cache); Assert(current_fmgr_plpgsql_cache->func_info); Assert(current_fmgr_plpgsql_cache->func_info->use_count > 0); return current_fmgr_plpgsql_cache->fn_mcxt; } static void func_info_init_hashkey(func_info_hashkey *hk, PLpgSQL_function *func) { memset(hk, 0, sizeof(func_info_hashkey)); hk->fn_oid = func->fn_oid; hk->fn_xmin = func->fn_xmin; hk->fn_tid = func->fn_tid; } /* * Hash table for function profiling metadata. */ static void func_info_HashTableInit(void) { HASHCTL ctl; Assert(func_info_HashTable == NULL); memset(&ctl, 0, sizeof(ctl)); ctl.keysize = sizeof(func_info_hashkey); ctl.entrysize = sizeof(func_info_entry); ctl.hcxt = pldbgapi2_mcxt; func_info_HashTable = hash_create("plpgsql_check function pldbgapi2 statements info cache", FUNCS_PER_USER, &ctl, HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); } static void init_hash_tables(void) { if (pldbgapi2_mcxt) { MemoryContextReset(pldbgapi2_mcxt); func_info_HashTable = NULL; } else { pldbgapi2_mcxt = AllocSetContextCreate(TopMemoryContext, "plpgsql_check - pldbgapi2 context", ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); } func_info_HashTableInit(); } static void set_stmt_info(PLpgSQL_stmt *stmt, plpgsql_check_plugin2_stmt_info *stmts_info, int *stmtid_map, int level, int *natural_id, int parent_id); static void set_stmts_info(List *stmts, plpgsql_check_plugin2_stmt_info *stmts_info, int *stmtid_map, int level, int *natural_id, int parent_id) { ListCell *lc; foreach(lc, stmts) { set_stmt_info((PLpgSQL_stmt *) lfirst(lc), stmts_info, stmtid_map, level, natural_id, parent_id); } } static void set_stmt_info(PLpgSQL_stmt *stmt, plpgsql_check_plugin2_stmt_info *stmts_info, int *stmtid_map, int level, int *natural_id, int parent_id) { ListCell *lc; int stmtid_idx = stmt->stmtid - 1; bool is_invisible = stmt->lineno < 1; Assert(stmts_info); /* level is used for indentation */ stmts_info[stmtid_idx].level = level; /* natural_id is displayed insted stmtid */ stmts_info[stmtid_idx].natural_id = ++(*natural_id); /* * natural id to parser id map allows to use natural statement order * for saving metrics and their presentation without necessity to * iterate over statement tree */ stmtid_map[stmts_info[stmtid_idx].natural_id - 1] = stmt->stmtid; /* * parent_id is used for synchronization stmts stack * after handled exception */ stmts_info[stmtid_idx].parent_id = parent_id; /* * persistent stmt type name can be used by tracer * when syntax tree can be unaccessable */ stmts_info[stmtid_idx].typname = plpgsql_check__stmt_typename_p(stmt); /* used for skipping printing invisible block statement */ stmts_info[stmtid_idx].is_invisible = is_invisible; /* by default any statements is not a container of other statements */ stmts_info[stmtid_idx].is_container = false; switch (stmt->cmd_type) { case PLPGSQL_STMT_BLOCK: { PLpgSQL_stmt_block *stmt_block = (PLpgSQL_stmt_block *) stmt; set_stmts_info(stmt_block->body, stmts_info, stmtid_map, !is_invisible ? level + 1 : level, natural_id, stmt->stmtid); if (stmt_block->exceptions) { foreach(lc, stmt_block->exceptions->exc_list) { set_stmts_info(((PLpgSQL_exception *) lfirst(lc))->action, stmts_info, stmtid_map, !is_invisible ? level + 1 : level, natural_id, stmt->stmtid); } } stmts_info[stmtid_idx].is_container = true; } break; case PLPGSQL_STMT_IF: { PLpgSQL_stmt_if *stmt_if = (PLpgSQL_stmt_if *) stmt; set_stmts_info(stmt_if->then_body, stmts_info, stmtid_map, level + 1, natural_id, stmt->stmtid); foreach(lc, stmt_if->elsif_list) { set_stmts_info(((PLpgSQL_if_elsif *) lfirst(lc))->stmts, stmts_info, stmtid_map, level + 1, natural_id, stmt->stmtid); } set_stmts_info(stmt_if->else_body, stmts_info, stmtid_map, level + 1, natural_id, stmt->stmtid); stmts_info[stmtid_idx].is_container = true; } break; case PLPGSQL_STMT_CASE: { PLpgSQL_stmt_case *stmt_case = (PLpgSQL_stmt_case *) stmt; foreach(lc, stmt_case->case_when_list) { set_stmts_info(((PLpgSQL_case_when *) lfirst(lc))->stmts, stmts_info, stmtid_map, level + 1, natural_id, stmt->stmtid); } set_stmts_info(stmt_case->else_stmts, stmts_info, stmtid_map, level + 1, natural_id, stmt->stmtid); stmts_info[stmtid_idx].is_container = true; } break; case PLPGSQL_STMT_LOOP: set_stmts_info(((PLpgSQL_stmt_loop *) stmt)->body, stmts_info, stmtid_map, level + 1, natural_id, stmt->stmtid); stmts_info[stmtid_idx].is_container = true; break; case PLPGSQL_STMT_FORI: set_stmts_info(((PLpgSQL_stmt_fori *) stmt)->body, stmts_info, stmtid_map, level + 1, natural_id, stmt->stmtid); stmts_info[stmtid_idx].is_container = true; break; case PLPGSQL_STMT_FORS: set_stmts_info(((PLpgSQL_stmt_fors *) stmt)->body, stmts_info, stmtid_map, level + 1, natural_id, stmt->stmtid); stmts_info[stmtid_idx].is_container = true; break; case PLPGSQL_STMT_FORC: set_stmts_info(((PLpgSQL_stmt_forc *) stmt)->body, stmts_info, stmtid_map, level + 1, natural_id, stmt->stmtid); stmts_info[stmtid_idx].is_container = true; break; case PLPGSQL_STMT_DYNFORS: set_stmts_info(((PLpgSQL_stmt_dynfors *) stmt)->body, stmts_info, stmtid_map, level + 1, natural_id, stmt->stmtid); stmts_info[stmtid_idx].is_container = true; break; case PLPGSQL_STMT_FOREACH_A: set_stmts_info(((PLpgSQL_stmt_foreach_a *) stmt)->body, stmts_info, stmtid_map, level + 1, natural_id, stmt->stmtid); stmts_info[stmtid_idx].is_container = true; break; case PLPGSQL_STMT_WHILE: set_stmts_info(((PLpgSQL_stmt_while *) stmt)->body, stmts_info, stmtid_map, level + 1, natural_id, stmt->stmtid); stmts_info[stmtid_idx].is_container = true; break; default: stmts_info[stmtid_idx].is_container = false; break; } } /* * Returns oid of used language */ static Oid get_func_lang(Oid funcid) { HeapTuple procTuple; Oid result; procTuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid)); if (!HeapTupleIsValid(procTuple)) elog(ERROR, "cache lookup failed for function %u", funcid); result = ((Form_pg_proc) GETSTRUCT(procTuple))->prolang; ReleaseSysCache(procTuple); return result; } /* * Set PLpgSQLlanguageId and PLpgSQLinlineFunc */ static void set_plpgsql_info(void) { HeapTuple languageTuple; Form_pg_language languageStruct; languageTuple = SearchSysCache1(LANGNAME, PointerGetDatum("plpgsql")); if (!HeapTupleIsValid(languageTuple)) elog(ERROR, "language \"plpgsql\" does not exist"); languageStruct = (Form_pg_language) GETSTRUCT(languageTuple); PLpgSQLlanguageId = languageStruct->oid; PLpgSQLinlineFunc = languageStruct->laninline; ReleaseSysCache(languageTuple); } /* * All plpgsql functions needs fmgr hook. We need to process abort state. */ static bool pldbgapi2_needs_fmgr_hook(Oid fn_oid) { if (prev_needs_fmgr_hook && (*prev_needs_fmgr_hook) (fn_oid)) return true; /* * We need to delay initialization of PLpgSQLlanguageId. If library * was initialized too early, the system catalog is not accessable. */ if (!OidIsValid(PLpgSQLlanguageId)) set_plpgsql_info(); /* * code of DO statements is executed by execution of function * laninline. We need to fmgr hook for plpgsql_inline_handler too. */ if (fn_oid == PLpgSQLinlineFunc) return true; return get_func_lang(fn_oid) == PLpgSQLlanguageId; } static void pldbgapi2_fmgr_hook(FmgrHookEventType event, FmgrInfo *flinfo, Datum *private) { fmgr_cache *fcache = (fmgr_cache *) DatumGetPointer(*private); bool is_pldbgapi2_fcache = false; switch (event) { case FHET_START: if (!fcache) { if (!OidIsValid(PLpgSQLlanguageId)) set_plpgsql_info(); if (get_func_lang(flinfo->fn_oid) == PLpgSQLlanguageId || flinfo->fn_oid == PLpgSQLinlineFunc) { MemoryContext oldcxt = MemoryContextSwitchTo(flinfo->fn_mcxt); fmgr_plpgsql_cache *fcache_plpgsql = NULL; fcache_plpgsql = palloc0(sizeof(fmgr_plpgsql_cache)); fcache_plpgsql->magic = FMGR_CACHE_MAGIC; fcache_plpgsql->funcid = flinfo->fn_oid; fcache_plpgsql->is_plpgsql = true; fcache_plpgsql->fn_mcxt = flinfo->fn_mcxt; fcache_plpgsql->stmtid_stack = palloc_array(int, INITIAL_PLDBGAPI2_STMT_STACK_SIZE); fcache_plpgsql->stmtid_stack_size = INITIAL_PLDBGAPI2_STMT_STACK_SIZE; fcache_plpgsql->current_stmtid_stack_size = 0; MemoryContextSwitchTo(oldcxt); fcache = (fmgr_cache *) fcache_plpgsql; } else { fcache = MemoryContextAlloc(flinfo->fn_mcxt, sizeof(fmgr_cache)); fcache->magic = FMGR_CACHE_MAGIC; fcache->funcid = flinfo->fn_oid; fcache->is_plpgsql = false; fcache->arg = (Datum) 0; } *private = PointerGetDatum(fcache); } if (fcache && fcache->magic != FMGR_CACHE_MAGIC) elog(ERROR, "unexpected fmgr_hook cache magic number"); is_pldbgapi2_fcache = true; if (fcache->is_plpgsql) { fmgr_plpgsql_cache *fcache_plpgsql = (fmgr_plpgsql_cache *) fcache; last_fmgr_plpgsql_cache = fcache_plpgsql; fcache_plpgsql->current_stmtid_stack_size = 0; } else last_fmgr_plpgsql_cache = NULL; break; case FHET_END: case FHET_ABORT: /* * Unfortunately, the fmgr hook can be redirected inside security definer * function, and then there can be possible to so FHET_END or FHET_ABORT * are called with private for previous plugin. In this case, the best * solution is probably do nothing, and skip processing to previous * plugin. */ is_pldbgapi2_fcache = (fcache && fcache->magic == FMGR_CACHE_MAGIC); if (is_pldbgapi2_fcache && event == FHET_ABORT && fcache->is_plpgsql) { fmgr_plpgsql_cache *fcache_plpgsql = (fmgr_plpgsql_cache *) fcache; int sp; int i; Oid fn_oid; Assert(fcache_plpgsql->funcid == flinfo->fn_oid); fn_oid = flinfo->fn_oid != PLpgSQLinlineFunc ? flinfo->fn_oid : InvalidOid; current_fmgr_plpgsql_cache = fcache_plpgsql; for (sp = fcache_plpgsql->current_stmtid_stack_size; sp > 0; sp--) { int stmtid = fcache_plpgsql->stmtid_stack[sp - 1]; for (i = 0; i < nplpgsql_plugins2; i++) { if (plpgsql_plugins2[i]->stmt_end2_aborted) (plpgsql_plugins2[i]->stmt_end2_aborted)(fn_oid, stmtid, &fcache_plpgsql->plugin2_info[i]); } } for (i = 0; i < nplpgsql_plugins2; i++) { if (plpgsql_plugins2[i]->func_end2_aborted) (plpgsql_plugins2[i]->func_end2_aborted)(fn_oid, &fcache_plpgsql->plugin2_info[i]); } current_fmgr_plpgsql_cache = NULL; if (fcache_plpgsql->func_info) { Assert(fcache_plpgsql->func_info->use_count > 0); fcache_plpgsql->func_info->use_count--; } } break; } if (prev_fmgr_hook) (*prev_fmgr_hook) (event, flinfo, is_pldbgapi2_fcache ? &fcache->arg : private); } static func_info_entry * get_func_info(PLpgSQL_function *func) { func_info_entry *func_info; bool persistent_func_info; bool found_func_info_entry; func_info_hashkey hk; if (OidIsValid(func->fn_oid)) { func_info_init_hashkey(&hk, func); func_info = (func_info_entry *) hash_search(func_info_HashTable, (void *) &hk, HASH_ENTER, &found_func_info_entry); if (found_func_info_entry && !func_info->is_valid) { pfree(func_info->fn_name); pfree(func_info->fn_signature); pfree(func_info->stmts_info); pfree(func_info->stmtid_map); if (hash_search(func_info_HashTable, &func_info->key, HASH_REMOVE, NULL) == NULL) elog(ERROR, "hash table corrupted"); found_func_info_entry = false; } persistent_func_info = true; } else { /* one shot sie for anonymous blocks */ func_info = palloc(sizeof(func_info_entry)); persistent_func_info = false; found_func_info_entry = false; } if (!found_func_info_entry) { char *fn_name; int natural_id = 0; fn_name = get_func_name(func->fn_oid); /* * Unfortunately, in this moment the called function can be dropped. * get_func_name forces sinval message processing, and all related * metadata will be lost. So get_func_name can return NULL. In this * case we can use signature from plpgsql's cache. That is protected * until function execution's end. */ if (!fn_name) fn_name = func->fn_signature; if (persistent_func_info) { MemoryContext oldcxt; oldcxt = MemoryContextSwitchTo(pldbgapi2_mcxt); Assert(fn_name); func_info->hashValue = GetSysCacheHashValue1(PROCOID, ObjectIdGetDatum(func->fn_oid)); func_info->fn_name = pstrdup(fn_name); func_info->fn_signature = pstrdup(func->fn_signature); func_info->stmts_info = palloc(func->nstatements * sizeof(plpgsql_check_plugin2_stmt_info)); func_info->stmtid_map = palloc(func->nstatements * sizeof(int)); func_info->use_count = 0; MemoryContextSwitchTo(oldcxt); } else { func_info->fn_name = fn_name; func_info->fn_signature = pstrdup(func->fn_signature); func_info->stmts_info = palloc(func->nstatements * sizeof(plpgsql_check_plugin2_stmt_info)); func_info->stmtid_map = palloc(func->nstatements * sizeof(int)); } func_info->nstatements = func->nstatements; func_info->use_count = 0; func_info->is_valid = true; set_stmt_info((PLpgSQL_stmt *) func->action, func_info->stmts_info, func_info->stmtid_map, 1, &natural_id, 0); } func_info->nstatements = func->nstatements; return func_info; } static void pldbgapi2_func_setup(PLpgSQL_execstate *estate, PLpgSQL_function *func) { fmgr_plpgsql_cache *fcache_plpgsql = last_fmgr_plpgsql_cache; pldbgapi2_plugin_info *plugin_info; MemoryContext oldcxt; func_info_entry *func_info; int i; Assert(fcache_plpgsql->magic == FMGR_CACHE_MAGIC); Assert(fcache_plpgsql); Assert(fcache_plpgsql->is_plpgsql); #ifdef USE_ASSERT_CHECKING if (fcache_plpgsql->funcid != PLpgSQLinlineFunc) { Assert(fcache_plpgsql->funcid == func->fn_oid); Assert(fcache_plpgsql->funcid == estate->func->fn_oid); } else { Assert(!OidIsValid(func->fn_oid)); Assert(!OidIsValid(estate->func->fn_oid)); } #endif plugin_info = MemoryContextAlloc(fcache_plpgsql->fn_mcxt, sizeof(pldbgapi2_plugin_info)); plugin_info->magic = PLUGIN_INFO_MAGIC; plugin_info->fcache_plpgsql = fcache_plpgsql; plugin_info->prev_plugin_info = NULL; func_info = get_func_info(func); /* protect func_info against sinval */ func_info->use_count++; fcache_plpgsql->func_info = func_info; estate->plugin_info = plugin_info; current_fmgr_plpgsql_cache = fcache_plpgsql; for (i = 0; i < nplpgsql_plugins2; i++) { fcache_plpgsql->plugin2_info[i] = NULL; plpgsql_plugins2[i]->error_callback = pldbgapi2_plugin.error_callback; plpgsql_plugins2[i]->assign_expr = pldbgapi2_plugin.assign_expr; #if PG_VERSION_NUM >= 150000 plpgsql_plugins2[i]->assign_value = pldbgapi2_plugin.assign_value; plpgsql_plugins2[i]->eval_datum = pldbgapi2_plugin.eval_datum; plpgsql_plugins2[i]->cast_value = pldbgapi2_plugin.cast_value; #else plpgsql_plugins2[i]->assign_value = NULL; plpgsql_plugins2[i]->eval_datum = NULL; plpgsql_plugins2[i]->cast_value = NULL; #endif oldcxt = MemoryContextSwitchTo(fcache_plpgsql->fn_mcxt); if (plpgsql_plugins2[i]->func_setup2) (plpgsql_plugins2[i]->func_setup2)(estate, func, &fcache_plpgsql->plugin2_info[i]); MemoryContextSwitchTo(oldcxt); } if (prev_plpgsql_plugin) { prev_plpgsql_plugin->error_callback = pldbgapi2_plugin.error_callback; prev_plpgsql_plugin->assign_expr = pldbgapi2_plugin.assign_expr; #if PG_VERSION_NUM >= 150000 prev_plpgsql_plugin->assign_value = pldbgapi2_plugin.assign_value; prev_plpgsql_plugin->eval_datum = pldbgapi2_plugin.eval_datum; prev_plpgsql_plugin->cast_value = pldbgapi2_plugin.cast_value; #endif if (prev_plpgsql_plugin->func_setup) { PG_TRY(); { (prev_plpgsql_plugin->func_setup)(estate, func); plugin_info->prev_plugin_info = estate->plugin_info; estate->plugin_info = plugin_info; } PG_CATCH(); { plugin_info->prev_plugin_info = estate->plugin_info; estate->plugin_info = plugin_info; PG_RE_THROW(); } PG_END_TRY(); } } estate->plugin_info = plugin_info; current_fmgr_plpgsql_cache = NULL; } static void pldbgapi2_func_beg(PLpgSQL_execstate *estate, PLpgSQL_function *func) { pldbgapi2_plugin_info *plugin_info = estate->plugin_info; fmgr_plpgsql_cache *fcache_plpgsql; int i; Assert(plugin_info); if (plugin_info->magic != PLUGIN_INFO_MAGIC) ereport(ERROR, (errmsg("bad magic number of pldbgapi2 plpgsql debug api hook"), errdetail("Some extension using pl debug api does not work correctly."))); fcache_plpgsql = plugin_info->fcache_plpgsql; Assert(fcache_plpgsql->magic == FMGR_CACHE_MAGIC); Assert(fcache_plpgsql); Assert(fcache_plpgsql->is_plpgsql); #ifdef USE_ASSERT_CHECKING if (fcache_plpgsql->funcid != PLpgSQLinlineFunc) { Assert(fcache_plpgsql->funcid == func->fn_oid); Assert(fcache_plpgsql->funcid == estate->func->fn_oid); } else { Assert(!OidIsValid(func->fn_oid)); Assert(!OidIsValid(estate->func->fn_oid)); } #endif current_fmgr_plpgsql_cache = fcache_plpgsql; for (i = 0; i < nplpgsql_plugins2; i++) { if (plpgsql_plugins2[i]->func_beg2) (plpgsql_plugins2[i]->func_beg2)(estate, func, &fcache_plpgsql->plugin2_info[i]); } current_fmgr_plpgsql_cache = NULL; if (prev_plpgsql_plugin && prev_plpgsql_plugin->func_beg) { PG_TRY(); { estate->plugin_info = plugin_info->prev_plugin_info; (prev_plpgsql_plugin->func_beg)(estate, func); plugin_info->prev_plugin_info = estate->plugin_info; estate->plugin_info = plugin_info; } PG_CATCH(); { plugin_info->prev_plugin_info = estate->plugin_info; estate->plugin_info = plugin_info; PG_RE_THROW(); } PG_END_TRY(); } } static void pldbgapi2_func_end(PLpgSQL_execstate *estate, PLpgSQL_function *func) { pldbgapi2_plugin_info *plugin_info = estate->plugin_info; fmgr_plpgsql_cache *fcache_plpgsql; int i; /* * When extension is installed by EXECUTE 'CREATE EXTENSION plpgsql_check', * then plpgsql debug API is activated, but plugin info is NULL, or maybe * there can be plugin info for ather plugin, because plpgsql_check was not * correctly initialized. */ if (!plugin_info || plugin_info->magic != PLUGIN_INFO_MAGIC) return; fcache_plpgsql = plugin_info->fcache_plpgsql; Assert(fcache_plpgsql); Assert(fcache_plpgsql->magic == FMGR_CACHE_MAGIC); Assert(fcache_plpgsql->is_plpgsql); #ifdef USE_ASSERT_CHECKING if (fcache_plpgsql->funcid != PLpgSQLinlineFunc) { Assert(fcache_plpgsql->funcid == func->fn_oid); Assert(fcache_plpgsql->funcid == estate->func->fn_oid); } else Assert(!OidIsValid(estate->func->fn_oid)); #endif current_fmgr_plpgsql_cache = fcache_plpgsql; for (i = 0; i < nplpgsql_plugins2; i++) { if (plpgsql_plugins2[i]->func_end2) (plpgsql_plugins2[i]->func_end2)(estate, func, &fcache_plpgsql->plugin2_info[i]); } current_fmgr_plpgsql_cache = NULL; Assert(fcache_plpgsql->func_info); Assert(fcache_plpgsql->func_info->use_count > 0); fcache_plpgsql->func_info->use_count--; if (prev_plpgsql_plugin && prev_plpgsql_plugin->func_end) { PG_TRY(); { estate->plugin_info = plugin_info->prev_plugin_info; (prev_plpgsql_plugin->func_end)(estate, func); plugin_info->prev_plugin_info = estate->plugin_info; estate->plugin_info = plugin_info; } PG_CATCH(); { plugin_info->prev_plugin_info = estate->plugin_info; estate->plugin_info = plugin_info; PG_RE_THROW(); } PG_END_TRY(); } } static void pldbgapi2_stmt_beg(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt) { pldbgapi2_plugin_info *plugin_info = estate->plugin_info; fmgr_plpgsql_cache *fcache_plpgsql; int i; int parent_id = 0; /* * When extension is installed by EXECUTE 'CREATE EXTENSION plpgsql_check', * then plpgsql debug API is activated, but plugin info is NULL, or maybe * there can be plugin info for ather plugin, because plpgsql_check was not * correctly initialized. */ if (!plugin_info || plugin_info->magic != PLUGIN_INFO_MAGIC) return; fcache_plpgsql = plugin_info->fcache_plpgsql; Assert(fcache_plpgsql); Assert(fcache_plpgsql->magic == FMGR_CACHE_MAGIC); Assert(fcache_plpgsql->is_plpgsql); #ifdef USE_ASSERT_CHECKING if (fcache_plpgsql->funcid != PLpgSQLinlineFunc) Assert(fcache_plpgsql->funcid == estate->func->fn_oid); else Assert(!OidIsValid(estate->func->fn_oid)); #endif current_fmgr_plpgsql_cache = fcache_plpgsql; if (fcache_plpgsql->current_stmtid_stack_size > 0) { parent_id = fcache_plpgsql->func_info->stmts_info[stmt->stmtid - 1].parent_id; /* solve handled exception */ while (fcache_plpgsql->current_stmtid_stack_size > 0 && fcache_plpgsql->stmtid_stack[fcache_plpgsql->current_stmtid_stack_size - 1] != parent_id) { int stmtid = fcache_plpgsql->stmtid_stack[fcache_plpgsql->current_stmtid_stack_size - 1]; for (i = 0; i < nplpgsql_plugins2; i++) { if (plpgsql_plugins2[i]->stmt_end2_aborted) (plpgsql_plugins2[i]->stmt_end2_aborted)(estate->func->fn_oid, stmtid, &fcache_plpgsql->plugin2_info[i]); } fcache_plpgsql->current_stmtid_stack_size -= 1; } } if (parent_id && fcache_plpgsql->stmtid_stack[fcache_plpgsql->current_stmtid_stack_size - 1] != parent_id) elog(ERROR, "cannot find parent statement on pldbgapi2 call stack"); /* * We want to close broken statements before we start execution of * exception handler. This needs more work than closing broken statements * after an exception handler, but it simplify calculation of execution times. * We need to chec, if stack has an expected value (should be parent statement, * and if not, then we are in exception handler. */ if (fcache_plpgsql->current_stmtid_stack_size >= fcache_plpgsql->stmtid_stack_size) { fcache_plpgsql->stmtid_stack_size *= 2; fcache_plpgsql->stmtid_stack = repalloc_array(fcache_plpgsql->stmtid_stack, int, fcache_plpgsql->stmtid_stack_size); } fcache_plpgsql->stmtid_stack[fcache_plpgsql->current_stmtid_stack_size++] = stmt->stmtid; for (i = 0; i < nplpgsql_plugins2; i++) { if (plpgsql_plugins2[i]->stmt_beg2) (plpgsql_plugins2[i]->stmt_beg2)(estate, stmt, &fcache_plpgsql->plugin2_info[i]); } current_fmgr_plpgsql_cache = NULL; if (prev_plpgsql_plugin && prev_plpgsql_plugin->stmt_beg) { PG_TRY(); { estate->plugin_info = plugin_info->prev_plugin_info; (prev_plpgsql_plugin->stmt_beg)(estate, stmt); plugin_info->prev_plugin_info = estate->plugin_info; estate->plugin_info = plugin_info; } PG_CATCH(); { plugin_info->prev_plugin_info = estate->plugin_info; estate->plugin_info = plugin_info; PG_RE_THROW(); } PG_END_TRY(); } } static void pldbgapi2_stmt_end(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt) { pldbgapi2_plugin_info *plugin_info = estate->plugin_info; fmgr_plpgsql_cache *fcache_plpgsql; int i; /* * When extension is installed by EXECUTE 'CREATE EXTENSION plpgsql_check', * then plpgsql debug API is activated, but plugin info is NULL, or maybe * there can be plugin info for ather plugin, because plpgsql_check was not * correctly initialized. */ if (!plugin_info || plugin_info->magic != PLUGIN_INFO_MAGIC) return; fcache_plpgsql = plugin_info->fcache_plpgsql; Assert(fcache_plpgsql); Assert(fcache_plpgsql->magic == FMGR_CACHE_MAGIC); Assert(fcache_plpgsql->is_plpgsql); #ifdef USE_ASSERT_CHECKING if (fcache_plpgsql->funcid != PLpgSQLinlineFunc) Assert(fcache_plpgsql->funcid == estate->func->fn_oid); else Assert(!OidIsValid(estate->func->fn_oid)); #endif Assert(fcache_plpgsql->current_stmtid_stack_size > 0); fcache_plpgsql->current_stmtid_stack_size -= 1; current_fmgr_plpgsql_cache = fcache_plpgsql; /* * The exception handler can be empty (see issue #156). In this case * the statement on stack can be different, then current statemnt, and * we should to fix stack. */ if (stmt->cmd_type == PLPGSQL_STMT_BLOCK) { while (fcache_plpgsql->current_stmtid_stack_size > 0 && fcache_plpgsql->stmtid_stack[fcache_plpgsql->current_stmtid_stack_size] != stmt->stmtid) { int stmtid = fcache_plpgsql->stmtid_stack[fcache_plpgsql->current_stmtid_stack_size]; for (i = 0; i < nplpgsql_plugins2; i++) { if (plpgsql_plugins2[i]->stmt_end2_aborted) (plpgsql_plugins2[i]->stmt_end2_aborted)(estate->func->fn_oid, stmtid, &fcache_plpgsql->plugin2_info[i]); } fcache_plpgsql->current_stmtid_stack_size -= 1; } } if (fcache_plpgsql->stmtid_stack[fcache_plpgsql->current_stmtid_stack_size] != stmt->stmtid) elog(ERROR, "pldbgapi2 statement call stack is broken"); for (i = 0; i < nplpgsql_plugins2; i++) { if (plpgsql_plugins2[i]->stmt_end2) (plpgsql_plugins2[i]->stmt_end2)(estate, stmt, &fcache_plpgsql->plugin2_info[i]); } current_fmgr_plpgsql_cache = NULL; if (prev_plpgsql_plugin && prev_plpgsql_plugin->stmt_end) { PG_TRY(); { estate->plugin_info = plugin_info->prev_plugin_info; (prev_plpgsql_plugin->stmt_end)(estate, stmt); plugin_info->prev_plugin_info = estate->plugin_info; estate->plugin_info = plugin_info; } PG_CATCH(); { plugin_info->prev_plugin_info = estate->plugin_info; estate->plugin_info = plugin_info; PG_RE_THROW(); } PG_END_TRY(); } } void plpgsql_check_register_pldbgapi2_plugin(plpgsql_check_plugin2 *plugin2) { if (nplpgsql_plugins2 < MAX_PLDBGAPI2_PLUGINS) plpgsql_plugins2[nplpgsql_plugins2++] = plugin2; else elog(ERROR, "too much pldbgapi2 plugins"); } static void func_info_CacheObjectCallback(Datum arg, int cacheid, uint32 hashValue) { HASH_SEQ_STATUS status; func_info_entry *func_info; Assert(func_info_HashTable); /* Currently we just flush all entries; hard to be smarter ... */ hash_seq_init(&status, func_info_HashTable); while ((func_info = (func_info_entry *) hash_seq_search(&status)) != NULL) { if (hashValue == 0 || func_info->hashValue == hashValue) func_info->is_valid = false; if (!func_info->is_valid && func_info->use_count == 0) { pfree(func_info->fn_name); pfree(func_info->fn_signature); pfree(func_info->stmts_info); pfree(func_info->stmtid_map); if (hash_search(func_info_HashTable, &func_info->key, HASH_REMOVE, NULL) == NULL) elog(ERROR, "hash table corrupted"); } } } void plpgsql_check_init_pldbgapi2(void) { PLpgSQL_plugin **plugin_ptr; static bool inited = false; if (inited) return; prev_needs_fmgr_hook = needs_fmgr_hook; prev_fmgr_hook = fmgr_hook; needs_fmgr_hook = pldbgapi2_needs_fmgr_hook; fmgr_hook = pldbgapi2_fmgr_hook; plugin_ptr = (PLpgSQL_plugin **)find_rendezvous_variable("PLpgSQL_plugin"); prev_plpgsql_plugin = *plugin_ptr; *plugin_ptr = &pldbgapi2_plugin; init_hash_tables(); CacheRegisterSyscacheCallback(PROCOID, func_info_CacheObjectCallback, (Datum) 0); inited = true; } #if PG_VERSION_NUM < 150000 void plpgsql_check_finish_pldbgapi2(void) { PLpgSQL_plugin **plugin_ptr; needs_fmgr_hook = prev_needs_fmgr_hook; fmgr_hook = prev_fmgr_hook; plugin_ptr = (PLpgSQL_plugin **)find_rendezvous_variable("PLpgSQL_plugin"); *plugin_ptr = prev_plpgsql_plugin; } #endif plpgsql_check-2.7.8/src/plpgsql_check.c000066400000000000000000000314071465410532300201310ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * plpgsql_check.c * * enhanced checks for plpgsql functions * * by Pavel Stehule 2013-2024 * *------------------------------------------------------------------------- * * Notes: * * 1) Secondary hash table for function signature is necessary due holding is_checked * attribute - this protection against unwanted repeated check. * * 2) Reusing some plpgsql_xxx functions requires full run-time environment. It is * emulated by fake expression context and fake fceinfo (these are created when * active checking is used) - see: setup_fake_fcinfo, setup_cstate. * * 3) The environment is referenced by stored execution plans. The actual plan should * not be linked with fake environment. All expressions created in checking time * should be relased by release_exprs(cstate.exprs) function. * */ #include "plpgsql_check.h" #include "plpgsql_check_builtins.h" #include "catalog/dependency.h" #include "catalog/pg_proc.h" #include "commands/extension.h" #include "storage/lwlock.h" #include "storage/shmem.h" #include "utils/guc.h" #include "utils/memutils.h" #ifdef PG_MODULE_MAGIC PG_MODULE_MAGIC; #endif PLpgSQL_plugin **plpgsql_check_plugin_var_ptr; PLpgSQL_plugin *prev_plpgsql_plugin; static const struct config_enum_entry plpgsql_check_mode_options[] = { {"disabled", PLPGSQL_CHECK_MODE_DISABLED, false}, {"by_function", PLPGSQL_CHECK_MODE_BY_FUNCTION, false}, {"fresh_start", PLPGSQL_CHECK_MODE_FRESH_START, false}, {"every_start", PLPGSQL_CHECK_MODE_EVERY_START, false}, {NULL, 0, false} }; static const struct config_enum_entry tracer_verbosity_options[] = { {"terse", PGERROR_TERSE, false}, {"default", PGERROR_DEFAULT, false}, {"verbose", PGERROR_VERBOSE, false}, {NULL, 0, false} }; static const struct config_enum_entry tracer_level_options[] = { {"debug5", DEBUG5, false}, {"debug4", DEBUG4, false}, {"debug3", DEBUG3, false}, {"debug2", DEBUG2, false}, {"debug1", DEBUG1, false}, {"debug", DEBUG2, true}, {"info", INFO, false}, {"notice", NOTICE, false}, {"log", LOG, false}, {NULL, 0, false} }; static const struct config_enum_entry cursors_leaks_level_options[] = { {"notice", NOTICE, false}, {"WARNING", WARNING, false}, {"ERROR", ERROR, false}, {NULL, 0, false} }; void _PG_init(void); #if PG_VERSION_NUM < 150000 void _PG_fini(void); #endif #if PG_VERSION_NUM >= 150000 shmem_request_hook_type plpgsql_check_prev_shmem_request_hook = NULL; #endif shmem_startup_hook_type plpgsql_check_prev_shmem_startup_hook = NULL; bool plpgsql_check_regress_test_mode; /* * Links to function in plpgsql module */ plpgsql_check__build_datatype_t plpgsql_check__build_datatype_p; plpgsql_check__compile_t plpgsql_check__compile_p; plpgsql_check__parser_setup_t plpgsql_check__parser_setup_p; plpgsql_check__stmt_typename_t plpgsql_check__stmt_typename_p; plpgsql_check__exec_get_datum_type_t plpgsql_check__exec_get_datum_type_p; plpgsql_check__recognize_err_condition_t plpgsql_check__recognize_err_condition_p; plpgsql_check__ns_lookup_t plpgsql_check__ns_lookup_p; static bool is_expected_extversion = false; /* * load_external_function retursn PGFunctions - we need generic function, so * it is not 100% correct, but in used context it is not a problem. */ #define LOAD_EXTERNAL_FUNCTION(file, funcname) ((void *) (load_external_function(file, funcname, true, NULL))) #define EXPECTED_EXTVERSION "2.7" void plpgsql_check_check_ext_version(Oid fn_oid) { if (!is_expected_extversion) { Oid extoid; char *extver; extoid = getExtensionOfObject(ProcedureRelationId, fn_oid); Assert(OidIsValid(extoid)); extver = get_extension_version(extoid); Assert(extver); if (strcmp(EXPECTED_EXTVERSION, extver) != 0) { char *extname = get_extension_name(extoid); ereport(ERROR, (errmsg("extension \"%s\" is not updated in system catalog", extname), errdetail("version \"%s\" is required, version \"%s\" is installed", EXPECTED_EXTVERSION, extver), errhint("execute \"ALTER EXTENSION %s UPDATE TO '%s'\"", extname, EXPECTED_EXTVERSION))); } else { pfree(extver); is_expected_extversion = true; } } } /* * Module initialization * * join to PLpgSQL executor * */ void _PG_init(void) { /* Be sure we do initialization only once (should be redundant now) */ static bool inited = false; if (inited) return; pg_bindtextdomain(TEXTDOMAIN); AssertVariableIsOfType(&plpgsql_build_datatype, plpgsql_check__build_datatype_t); plpgsql_check__build_datatype_p = (plpgsql_check__build_datatype_t) LOAD_EXTERNAL_FUNCTION("$libdir/plpgsql", "plpgsql_build_datatype"); AssertVariableIsOfType(&plpgsql_compile, plpgsql_check__compile_t); plpgsql_check__compile_p = (plpgsql_check__compile_t) LOAD_EXTERNAL_FUNCTION("$libdir/plpgsql", "plpgsql_compile"); AssertVariableIsOfType(&plpgsql_parser_setup, plpgsql_check__parser_setup_t); plpgsql_check__parser_setup_p = (plpgsql_check__parser_setup_t) LOAD_EXTERNAL_FUNCTION("$libdir/plpgsql", "plpgsql_parser_setup"); AssertVariableIsOfType(&plpgsql_stmt_typename, plpgsql_check__stmt_typename_t); plpgsql_check__stmt_typename_p = (plpgsql_check__stmt_typename_t) LOAD_EXTERNAL_FUNCTION("$libdir/plpgsql", "plpgsql_stmt_typename"); AssertVariableIsOfType(&plpgsql_exec_get_datum_type, plpgsql_check__exec_get_datum_type_t); plpgsql_check__exec_get_datum_type_p = (plpgsql_check__exec_get_datum_type_t) LOAD_EXTERNAL_FUNCTION("$libdir/plpgsql", "plpgsql_exec_get_datum_type"); AssertVariableIsOfType(&plpgsql_recognize_err_condition, plpgsql_check__recognize_err_condition_t); plpgsql_check__recognize_err_condition_p = (plpgsql_check__recognize_err_condition_t) LOAD_EXTERNAL_FUNCTION("$libdir/plpgsql", "plpgsql_recognize_err_condition"); AssertVariableIsOfType(&plpgsql_ns_lookup, plpgsql_check__ns_lookup_t); plpgsql_check__ns_lookup_p = (plpgsql_check__ns_lookup_t) LOAD_EXTERNAL_FUNCTION("$libdir/plpgsql", "plpgsql_ns_lookup"); DefineCustomBoolVariable("plpgsql_check.regress_test_mode", "reduces volatile output", NULL, &plpgsql_check_regress_test_mode, false, PGC_USERSET, 0, NULL, NULL, NULL); DefineCustomEnumVariable("plpgsql_check.mode", "choose a mode for enhanced checking", NULL, &plpgsql_check_mode, PLPGSQL_CHECK_MODE_BY_FUNCTION, plpgsql_check_mode_options, PGC_USERSET, 0, NULL, NULL, NULL); DefineCustomBoolVariable("plpgsql_check.show_nonperformance_extra_warnings", "when is true, then extra warning (except performance warnings) are showed", NULL, &plpgsql_check_extra_warnings, false, PGC_USERSET, 0, NULL, NULL, NULL); DefineCustomBoolVariable("plpgsql_check.show_nonperformance_warnings", "when is true, then warning (except performance warnings) are showed", NULL, &plpgsql_check_other_warnings, false, PGC_USERSET, 0, NULL, NULL, NULL); DefineCustomBoolVariable("plpgsql_check.show_performance_warnings", "when is true, then performance warnings are showed", NULL, &plpgsql_check_performance_warnings, false, PGC_USERSET, 0, NULL, NULL, NULL); DefineCustomBoolVariable("plpgsql_check.compatibility_warnings", "when is true, then compatibility warnings are showed", NULL, &plpgsql_check_compatibility_warnings, false, PGC_USERSET, 0, NULL, NULL, NULL); DefineCustomBoolVariable("plpgsql_check.constants_tracing", "when is true, the variables with constant value can be used like constant", NULL, &plpgsql_check_constants_tracing, true, PGC_USERSET, 0, NULL, NULL, NULL); DefineCustomBoolVariable("plpgsql_check.fatal_errors", "when is true, then plpgsql check stops execution on detected error", NULL, &plpgsql_check_fatal_errors, true, PGC_USERSET, 0, NULL, NULL, NULL); DefineCustomBoolVariable("plpgsql_check.profiler", "when is true, then function execution profile is updated", NULL, &plpgsql_check_profiler, false, PGC_USERSET, 0, NULL, NULL, NULL); DefineCustomBoolVariable("plpgsql_check.enable_tracer", "when is true, then tracer's functionality is enabled", NULL, &plpgsql_check_enable_tracer, false, PGC_SUSET, 0, NULL, NULL, NULL); DefineCustomBoolVariable("plpgsql_check.tracer", "when is true, then function is traced", NULL, &plpgsql_check_tracer, false, PGC_USERSET, 0, NULL, NULL, NULL); DefineCustomBoolVariable("plpgsql_check.trace_assert", "when is true, then statement ASSERT is traced", NULL, &plpgsql_check_trace_assert, false, PGC_USERSET, 0, NULL, NULL, NULL); DefineCustomBoolVariable("plpgsql_check.tracer_test_mode", "when is true, then output of tracer is in regress test possible format", NULL, &plpgsql_check_tracer_test_mode, false, PGC_USERSET, 0, NULL, NULL, NULL); DefineCustomBoolVariable("plpgsql_check.tracer_show_nsubxids", "when is true, then the tracer shows number of current subxids", NULL, &plpgsql_check_tracer_show_nsubxids, false, PGC_USERSET, 0, NULL, NULL, NULL); DefineCustomEnumVariable("plpgsql_check.tracer_verbosity", "sets the verbosity of tracer", NULL, (int *) &plpgsql_check_tracer_verbosity, PGERROR_DEFAULT, tracer_verbosity_options, PGC_USERSET, 0, NULL, NULL, NULL); DefineCustomEnumVariable("plpgsql_check.trace_assert_verbosity", "sets the verbosity of trace ASSERT statement", NULL, (int *) &plpgsql_check_trace_assert_verbosity, PGERROR_DEFAULT, tracer_verbosity_options, PGC_USERSET, 0, NULL, NULL, NULL); DefineCustomEnumVariable("plpgsql_check.tracer_errlevel", "sets an error level of tracer's messages", NULL, (int *) &plpgsql_check_tracer_errlevel, NOTICE, tracer_level_options, PGC_USERSET, 0, NULL, NULL, NULL); DefineCustomIntVariable("plpgsql_check.tracer_variable_max_length", "Maximum output length of content of variables in bytes", NULL, &plpgsql_check_tracer_variable_max_length, 1024, 10, 2048, PGC_USERSET, 0, NULL, NULL, NULL); DefineCustomEnumVariable("plpgsql_check.cursors_leaks_errlevel", "sets an error level of detection of unclosed cursors", NULL, (int *) &plpgsql_check_cursors_leaks_level, WARNING, cursors_leaks_level_options, PGC_USERSET, 0, NULL, NULL, NULL); DefineCustomBoolVariable("plpgsql_check.cursors_leaks", "when is true, then detection of unclosed cursors is active", NULL, &plpgsql_check_cursors_leaks, true, PGC_USERSET, 0, NULL, NULL, NULL); DefineCustomBoolVariable("plpgsql_check.strict_cursors_leaks", "when is true, then detection of unclosed cursors is executed immediately when function is finished", NULL, &plpgsql_check_cursors_leaks_strict, false, PGC_USERSET, 0, NULL, NULL, NULL); EmitWarningsOnPlaceholders("plpgsql_check"); plpgsql_check_HashTableInit(); plpgsql_check_profiler_init_hash_tables(); /* Use shared memory when we can register more for self */ if (process_shared_preload_libraries_in_progress) { DefineCustomIntVariable("plpgsql_check.profiler_max_shared_chunks", "maximum numbers of statements chunks in shared memory", NULL, &plpgsql_check_profiler_max_shared_chunks, 15000, 50, 100000, PGC_POSTMASTER, 0, NULL, NULL, NULL); #if PG_VERSION_NUM < 150000 /* * If you change code here, don't forget to also report the * modifications in plpgsql_check_profiler_shmem_request() for pg15 and * later. */ RequestAddinShmemSpace(plpgsql_check_shmem_size()); RequestNamedLWLockTranche("plpgsql_check profiler", 1); RequestNamedLWLockTranche("plpgsql_check fstats", 1); #endif /* * Install hooks. */ #if PG_VERSION_NUM >= 150000 plpgsql_check_prev_shmem_request_hook = shmem_request_hook; shmem_request_hook = plpgsql_check_profiler_shmem_request; #endif plpgsql_check_prev_shmem_startup_hook = shmem_startup_hook; shmem_startup_hook = plpgsql_check_profiler_shmem_startup; } plpgsql_check_init_pldbgapi2(); plpgsql_check_passive_check_init(); plpgsql_check_profiler_init(); plpgsql_check_tracer_init(); plpgsql_check_cursors_leaks_init(); inited = true; } plpgsql_check-2.7.8/src/plpgsql_check.h000066400000000000000000000555341465410532300201450ustar00rootroot00000000000000#include "postgres.h" #include "plpgsql.h" #include "funcapi.h" #include "miscadmin.h" #include "access/tupdesc.h" #include "storage/ipc.h" typedef uint64 pc_queryid; #define NOQUERYID (UINT64CONST(0)) #if PG_VERSION_NUM < 150000 #define parse_analyze_fixedparams parse_analyze #endif enum { PLPGSQL_CHECK_ERROR, PLPGSQL_CHECK_WARNING_OTHERS, PLPGSQL_CHECK_WARNING_EXTRA, /* check shadowed variables */ PLPGSQL_CHECK_WARNING_PERFORMANCE, /* invisible cast check */ PLPGSQL_CHECK_WARNING_SECURITY, /* sql injection check */ PLPGSQL_CHECK_WARNING_COMPATIBILITY /* obsolete setting of cursor's or refcursor's variable */ }; enum { PLPGSQL_CHECK_FORMAT_ELOG, PLPGSQL_CHECK_FORMAT_TEXT, PLPGSQL_CHECK_FORMAT_TABULAR, PLPGSQL_CHECK_FORMAT_XML, PLPGSQL_CHECK_FORMAT_JSON, PLPGSQL_SHOW_DEPENDENCY_FORMAT_TABULAR, PLPGSQL_SHOW_PROFILE_TABULAR, PLPGSQL_SHOW_PROFILE_STATEMENTS_TABULAR, PLPGSQL_SHOW_PROFILE_FUNCTIONS_ALL_TABULAR }; enum { PLPGSQL_CHECK_MODE_DISABLED, /* all functionality is disabled */ PLPGSQL_CHECK_MODE_BY_FUNCTION, /* checking is allowed via CHECK function only (default) */ PLPGSQL_CHECK_MODE_FRESH_START, /* check only when function is called first time */ PLPGSQL_CHECK_MODE_EVERY_START /* check on every start */ }; enum { PLPGSQL_CHECK_CLOSED, PLPGSQL_CHECK_CLOSED_BY_EXCEPTIONS, PLPGSQL_CHECK_POSSIBLY_CLOSED, PLPGSQL_CHECK_UNCLOSED, PLPGSQL_CHECK_UNKNOWN }; typedef enum { PLPGSQL_CHECK_STMT_WALKER_COUNT_EXEC_TIME, PLPGSQL_CHECK_STMT_WALKER_PREPARE_RESULT, PLPGSQL_CHECK_STMT_WALKER_COLLECT_COVERAGE } profiler_stmt_walker_mode; typedef enum { PLPGSQL_CHECK_PRAGMA_ASSERT_SCHEMA, PLPGSQL_CHECK_PRAGMA_ASSERT_TABLE, PLPGSQL_CHECK_PRAGMA_ASSERT_COLUMN, } PragmaAssertType; typedef struct PLpgSQL_stmt_stack_item { PLpgSQL_stmt *stmt; char *label; struct PLpgSQL_stmt_stack_item *outer; bool is_exception_handler; Bitmapset *invalidate_strconstvars; } PLpgSQL_stmt_stack_item; typedef struct PLpgSQL_statements { struct PLpgSQL_statements *outer; Bitmapset *invalidate_strconstvars; } PLpgSQL_statements; typedef struct plpgsql_check_result_info { int format; /* produced / expected format */ Tuplestorestate *tuple_store; /* target tuple store */ TupleDesc tupdesc; /* target tuple store tuple descriptor */ MemoryContext query_ctx; /* memory context for string operations */ StringInfo sinfo; /* buffer for multi line one value output formats */ bool init_tag; /* true, when init tag should be created */ } plpgsql_check_result_info; typedef struct plpgsql_check_info { HeapTuple proctuple; bool is_procedure; Oid fn_oid; Oid rettype; char volatility; Oid relid; Oid anyelementoid; Oid anyenumoid; Oid anyrangeoid; Oid anycompatibleoid; Oid anycompatiblerangeoid; PLpgSQL_trigtype trigtype; char *src; bool fatal_errors; bool other_warnings; bool performance_warnings; bool extra_warnings; bool security_warnings; bool compatibility_warnings; bool constants_tracing; bool show_profile; bool all_warnings; bool without_warnings; char *oldtable; char *newtable; bool incomment_options_usage_warning; } plpgsql_check_info; typedef struct { unsigned int disable_check : 1; unsigned int disable_tracer : 1; /* has not any effect - it's runtime */ unsigned int disable_other_warnings : 1; unsigned int disable_performance_warnings : 1; unsigned int disable_extra_warnings : 1; unsigned int disable_security_warnings : 1; unsigned int disable_compatibility_warnings : 1; unsigned int disable_constants_tracing : 1; } plpgsql_check_pragma_vector; #define CI_MAGIC 2023042922 typedef struct PLpgSQL_checkstate { int ci_magic; List *argnames; /* function arg names */ char decl_volatility; /* declared function volatility */ char volatility; /* detected function volatility */ bool has_execute_stmt; /* detected dynamic SQL, disable volatility check */ bool skip_volatility_check; /* don't do this test for trigger */ PLpgSQL_execstate *estate; /* check state is estate extension */ MemoryContext check_cxt; List *exprs; /* list of all expression created by checker */ bool is_active_mode; /* true, when checking is started by plpgsql_check_function */ Bitmapset *used_variables; /* track which variables have been used; bit per varno */ Bitmapset *modif_variables; /* track which variables had been changed; bit per varno */ PLpgSQL_stmt_stack_item *top_stmt_stack; /* list of known labels + related command */ bool found_return_query; /* true, when code contains RETURN query */ bool found_return_dyn_query; /* true, when code contains RETURN QUERY EXECUTE */ Bitmapset *func_oids; /* list of used (and displayed) functions */ Bitmapset *rel_oids; /* list of used (and displayed) relations */ bool fake_rtd; /* true when functions returns record */ plpgsql_check_result_info *result_info; plpgsql_check_info *cinfo; Bitmapset *safe_variables; /* track which variables are safe against sql injection */ Bitmapset *out_variables; /* what variables are used as OUT variables */ Bitmapset *protected_variables; /* what variables should be assigned internal only */ Bitmapset *auto_variables; /* variables initialized, used by runtime */ Bitmapset *typed_variables; /* record variables with assigned type by pragma TYPE */ bool stop_check; /* true after error when fatal_errors option is active */ bool allow_mp; /* true, when multiple plans in plancache are allowed */ bool has_mp; /* true, when multiple plan was used */ bool was_pragma; /* true, when last expression was a plpgsql_check pragma */ plpgsql_check_pragma_vector pragma_vector; Oid pragma_foid; /* oid of plpgsql_check pragma function */ char **strconstvars; /* the values of string variables where the value is constant */ PLpgSQL_statements *top_stmts; /* pointer to current statement group */ } PLpgSQL_checkstate; typedef struct { int statements; int branches; int executed_statements; int executed_branches; } coverage_state; /* * function from assign.c */ extern void plpgsql_check_record_variable_usage(PLpgSQL_checkstate *cstate, int dno, bool write); extern void plpgsql_check_row_or_rec(PLpgSQL_checkstate *cstate, PLpgSQL_row *row, PLpgSQL_rec *rec); extern void plpgsql_check_target(PLpgSQL_checkstate *cstate, int varno, Oid *expected_typoid, int *expected_typmod); extern void plpgsql_check_assign_to_target_type(PLpgSQL_checkstate *cstate, Oid target_typoid, int32 target_typmod, Oid value_typoid, bool isnull); extern void plpgsql_check_assign_tupdesc_dno(PLpgSQL_checkstate *cstate, int varno, TupleDesc tupdesc, bool isnull); extern void plpgsql_check_assign_tupdesc_row_or_rec(PLpgSQL_checkstate *cstate, PLpgSQL_row *row, PLpgSQL_rec *rec, TupleDesc tupdesc, bool isnull); extern void plpgsql_check_recval_assign_tupdesc(PLpgSQL_checkstate *cstate, PLpgSQL_rec *rec, TupleDesc tupdesc, bool is_null); extern void plpgsql_check_recval_init(PLpgSQL_rec *rec); extern void plpgsql_check_recval_release(PLpgSQL_rec *rec); extern void plpgsql_check_is_assignable(PLpgSQL_execstate *estate, int dno); /* * functions from format.c */ extern int plpgsql_check_format_num(char *format_str); extern void plpgsql_check_init_ri(plpgsql_check_result_info *ri, int format, ReturnSetInfo *rsinfo); extern void plpgsql_check_finalize_ri(plpgsql_check_result_info *ri); extern void plpgsql_check_put_error(PLpgSQL_checkstate *cstate, int sqlerrcode, int lineno, const char *message, const char *detail, const char *hint, int level, int position, const char *query, const char *context); extern void plpgsql_check_put_error_edata(PLpgSQL_checkstate *cstate, ErrorData *edata); extern void plpgsql_check_put_dependency(plpgsql_check_result_info *ri, char *type, Oid oid, char *schema, char *name, char *params); extern void plpgsql_check_put_profile(plpgsql_check_result_info *ri, Datum queryids_array, int lineno, int stmt_lineno, int cmds_on_row, int64 exec_count, int64 exec_count_err, int64 us_total, Datum max_time_array, Datum processed_rows_array, char *source_row); extern void plpgsql_check_put_profile_statement(plpgsql_check_result_info *ri, pc_queryid queryid, int stmtid, int parent_stmtid, const char *parent_note, int block_num, int lineno, int64 exec_stmts, int64 exec_count_err, double total_time, double max_time, int64 processed_rows, char *stmtname); extern void plpgsql_check_put_profiler_functions_all_tb(plpgsql_check_result_info *ri, Oid funcoid, int64 exec_count, int64 exec_count_err, double total_time, double avg_time, double stddev_time, double min_time, double max_time); /* * function from catalog.c */ extern bool plpgsql_check_is_eventtriggeroid(Oid typoid); extern void plpgsql_check_get_function_info(plpgsql_check_info *cinfo); extern void plpgsql_check_precheck_conditions(plpgsql_check_info *cinfo); extern char *plpgsql_check_get_src(HeapTuple procTuple); extern Oid plpgsql_check_pragma_func_oid(void); extern bool plpgsql_check_is_plpgsql_function(Oid foid); extern Oid plpgsql_check_get_op_namespace(Oid opno); extern char *get_extension_version(Oid ext_oid); /* * functions from tablefunc.c */ extern void plpgsql_check_info_init(plpgsql_check_info *cinfo, Oid fn_oid); extern void plpgsql_check_set_all_warnings(plpgsql_check_info *cinfo); extern void plpgsql_check_set_without_warnings(plpgsql_check_info *cinfo); /* * functions from check_function.c */ extern void plpgsql_check_function_internal(plpgsql_check_result_info *ri, plpgsql_check_info *cinfo); extern void plpgsql_check_on_func_beg(PLpgSQL_execstate * estate, PLpgSQL_function * func); extern void plpgsql_check_HashTableInit(void); extern bool plpgsql_check_is_checked(PLpgSQL_function *func); extern void plpgsql_check_mark_as_checked(PLpgSQL_function *func); extern void plpgsql_check_setup_fcinfo(plpgsql_check_info *cinfo, FmgrInfo *flinfo, FunctionCallInfo fcinfo, ReturnSetInfo *rsinfo, TriggerData *trigdata, EventTriggerData *etrigdata, Trigger *tg_trigger, bool *fake_rtd); extern bool plpgsql_check_other_warnings; extern bool plpgsql_check_extra_warnings; extern bool plpgsql_check_performance_warnings; extern bool plpgsql_check_compatibility_warnings; extern bool plpgsql_check_fatal_errors; extern bool plpgsql_check_constants_tracing; extern int plpgsql_check_mode; /* * functions from expr_walk.c */ extern void plpgsql_check_detect_dependency(PLpgSQL_checkstate *cstate, Query *query); extern bool plpgsql_check_has_rtable(Query *query); extern bool plpgsql_check_qual_has_fishy_cast(PlannedStmt *plannedstmt, Plan *plan, Param **param); extern void plpgsql_check_funcexpr(PLpgSQL_checkstate *cstate, Query *query, char *query_str); extern bool plpgsql_check_is_sql_injection_vulnerable(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr, Node *node, int *location); extern bool plpgsql_check_contain_volatile_functions(Node *clause, PLpgSQL_checkstate *cstate); extern bool plpgsql_check_contain_mutable_functions(Node *clause, PLpgSQL_checkstate *cstate); extern bool plpgsql_check_vardno_is_used_for_reading(Node *node, int dno); extern char *plpgsql_check_get_formatted_string(PLpgSQL_checkstate *cstate, const char *fmt, List *args, bool *found_ident_placeholder, bool *found_literal_placeholder, bool *expr_is_const); /* * functions from check_expr.c */ extern char *plpgsql_check_expr_get_string(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr, int *location); extern char *plpgsql_check_get_tracked_const(PLpgSQL_checkstate *cstate, Node *node); extern char *plpgsql_check_get_const_string(PLpgSQL_checkstate *cstate, Node *node, int *location); extern void plpgsql_check_expr_with_scalar_type(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr, Oid expected_typoid, bool required); extern void plpgsql_check_returned_expr(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr, bool is_expression); extern void plpgsql_check_expr_as_rvalue(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr, PLpgSQL_rec *targetrec, PLpgSQL_row *targetrow, int targetdno, bool use_element_type, bool is_expression); extern void plpgsql_check_expr(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr); extern void plpgsql_check_assignment_with_possible_slices(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr, PLpgSQL_rec *targetrec, PLpgSQL_row *targetrow, int targetdno, bool use_element_type); extern void plpgsql_check_expr_as_sqlstmt_nodata(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr); extern void plpgsql_check_expr_as_sqlstmt_data(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr); extern bool plpgsql_check_expr_as_sqlstmt(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr); extern void plpgsql_check_assignment(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr, PLpgSQL_rec *targetrec, PLpgSQL_row *targetrow, int targetdno); extern void plpgsql_check_expr_generic(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr); extern void plpgsql_check_expr_generic_with_parser_setup(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr, ParserSetupHook parser_setup, void *arg); extern Node *plpgsql_check_expr_get_node(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr, bool force_plan_checks); extern char *plpgsql_check_const_to_string(Node *node, int *location); extern CachedPlanSource *plpgsql_check_get_plan_source(PLpgSQL_checkstate *cstate, SPIPlanPtr plan); extern void plpgsql_check_assignment_to_variable(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr, PLpgSQL_variable *targetvar, int targetdno); /* * functions from report.c */ extern char * plpgsql_check_datum_get_refname(PLpgSQL_checkstate *cstate, PLpgSQL_datum *d); extern void plpgsql_check_report_unused_variables(PLpgSQL_checkstate *cstate); extern void plpgsql_check_report_too_high_volatility(PLpgSQL_checkstate *cstate); extern bool is_internal_variable(PLpgSQL_checkstate *cstate, PLpgSQL_variable *var); /* * functions from stmtwalk.c */ extern bool plpgsql_check_is_reserved_keyword(char *name); extern void plpgsql_check_stmt(PLpgSQL_checkstate *cstate, PLpgSQL_stmt *stmt, int *closing, List **exceptions); /* * functions from typdesc.c */ extern TupleDesc plpgsql_check_expr_get_desc(PLpgSQL_checkstate *cstate, PLpgSQL_expr *query, bool use_element_type, bool expand_record, bool is_expression, Oid *first_level_typoid); extern void plpgsql_check_recvar_info(PLpgSQL_rec *rec, Oid *typoid, int32 *typmod); extern PLpgSQL_row * plpgsql_check_CallExprGetRowTarget(PLpgSQL_checkstate *cstate, PLpgSQL_expr *CallExpr); /* * functions from parser.c */ extern Oid plpgsql_check_parse_name_or_signature(char *name_or_signature); extern bool plpgsql_check_pragma_type(PLpgSQL_checkstate *cstate, const char *str, PLpgSQL_nsitem *ns, int lineno); extern bool plpgsql_check_pragma_table(PLpgSQL_checkstate *cstate, const char *str, int lineno); extern bool plpgsql_check_pragma_sequence(PLpgSQL_checkstate *cstate, const char *str, int lineno); extern bool plpgsql_check_pragma_assert(PLpgSQL_checkstate *cstate, PragmaAssertType pat, const char *str, PLpgSQL_nsitem *ns, int lineno); extern void plpgsql_check_search_comment_options(plpgsql_check_info *cinfo); extern char *plpgsql_check_process_echo_string(char *str, plpgsql_check_info *cinfo); /* * functions from profiler.c */ extern bool plpgsql_check_profiler; extern int plpgsql_check_profiler_max_shared_chunks; extern needs_fmgr_hook_type plpgsql_check_next_needs_fmgr_hook; extern fmgr_hook_type plpgsql_check_next_fmgr_hook; #if PG_VERSION_NUM >= 150000 extern void plpgsql_check_profiler_shmem_request(void); #endif extern void plpgsql_check_profiler_shmem_startup(void); extern Size plpgsql_check_shmem_size(void); extern void plpgsql_check_profiler_init_hash_tables(void); extern void plpgsql_check_iterate_over_profile(plpgsql_check_info *cinfo, profiler_stmt_walker_mode mode, plpgsql_check_result_info *ri, coverage_state *cs); extern void plpgsql_check_profiler_show_profile(plpgsql_check_result_info *ri, plpgsql_check_info *cinfo); extern void plpgsql_check_profiler_iterate_over_all_profiles(plpgsql_check_result_info *ri); extern void plpgsql_check_init_trace_info(PLpgSQL_execstate *estate); extern bool plpgsql_check_get_trace_info(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt, PLpgSQL_execstate **outer_estate, int *frame_num, int *level, instr_time *start_time); extern void plpgsql_check_get_trace_stmt_info(PLpgSQL_execstate *estate, int stmt_id, instr_time **start_time); extern bool *plpgsql_check_get_disable_tracer_on_stack(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt); extern void plpgsql_check_profiler_init(void); /* * functions and variables from tracer.c */ extern bool plpgsql_check_enable_tracer; extern bool plpgsql_check_tracer; extern bool plpgsql_check_trace_assert; extern bool plpgsql_check_tracer_test_mode; extern int plpgsql_check_tracer_variable_max_length; extern int plpgsql_check_tracer_errlevel; extern PGErrorVerbosity plpgsql_check_tracer_verbosity; extern PGErrorVerbosity plpgsql_check_trace_assert_verbosity; extern bool plpgsql_check_regress_test_mode; extern bool plpgsql_check_tracer_show_nsubxids; extern void plpgsql_check_trace_assert_on_stmt_beg(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt); extern void plpgsql_check_set_stmt_group_number(PLpgSQL_stmt *stmt, int *group_numbers, int *parent_group_numbers, int sgn, int *cgn, int psgn); extern void plpgsql_check_tracer_init(void); /* * variables from pragma.c */ extern void plpgsql_check_pragma_apply(PLpgSQL_checkstate *cstate, char *pragma_str, PLpgSQL_nsitem *ns, int lineno); /* * pldbgapi2 statement plugin2 info. This info is created * when function is first started and it is cached in session. */ typedef struct plpgsql_check_plugin2_stmt_info { int level; int natural_id; int parent_id; const char *typname; bool is_invisible; bool is_container; } plpgsql_check_plugin2_stmt_info; /* * functions from pldbgapi2 */ typedef struct plpgsql_check_plugin2 { /* Function pointers set up by the plugin */ void (*func_setup2) (PLpgSQL_execstate *estate, PLpgSQL_function *func, void **plugin2_info); void (*func_beg2) (PLpgSQL_execstate *estate, PLpgSQL_function *func, void **plugin2_info); void (*func_end2) (PLpgSQL_execstate *estate, PLpgSQL_function *func, void **plugin2_info); void (*func_end2_aborted) (Oid fn_oid, void **plugin2_info); void (*stmt_beg2) (PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt, void **plugin2_info); void (*stmt_end2) (PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt, void **plugin2_info); void (*stmt_end2_aborted) (Oid fn_oid, int stmtid, void **plugin2_info); /* Function pointers set by PL/pgSQL itself */ void (*error_callback) (void *arg); void (*assign_expr) (PLpgSQL_execstate *estate, PLpgSQL_datum *target, PLpgSQL_expr *expr); void (*assign_value) (PLpgSQL_execstate *estate, PLpgSQL_datum *target, Datum value, bool isNull, Oid valtype, int32 valtypmod); void (*eval_datum) (PLpgSQL_execstate *estate, PLpgSQL_datum *datum, Oid *typeId, int32 *typetypmod, Datum *value, bool *isnull); Datum (*cast_value) (PLpgSQL_execstate *estate, Datum value, bool *isnull, Oid valtype, int32 valtypmod, Oid reqtype, int32 reqtypmod); } plpgsql_check_plugin2; extern void plpgsql_check_register_pldbgapi2_plugin(plpgsql_check_plugin2 *plugin2); extern void plpgsql_check_init_pldbgapi2(void); extern plpgsql_check_plugin2_stmt_info *plpgsql_check_get_current_stmt_info(int stmtid); extern plpgsql_check_plugin2_stmt_info *plpgsql_check_get_current_stmts_info(void); extern plpgsql_check_plugin2_stmt_info *plpgsql_check_get_stmts_info(PLpgSQL_function *func); extern int *plpgsql_check_get_current_stmtid_map(void); extern int *plpgsql_check_get_stmtid_map(PLpgSQL_function *func); extern char *plpgsql_check_get_current_func_info_name(void); extern char *plpgsql_check_get_current_func_info_signature(void); extern MemoryContext plpgsql_check_get_current_fn_mcxt(void); #if PG_VERSION_NUM < 150000 extern void plpgsql_check_finish_pldbgapi2(void); #endif /* * functions from cursors_leaks.c */ extern bool plpgsql_check_cursors_leaks; extern bool plpgsql_check_cursors_leaks_strict; extern int plpgsql_check_cursors_leaks_level; extern void plpgsql_check_cursors_leaks_init(void); /* * functions from plpgsql_check.c */ #if PG_VERSION_NUM >= 150000 extern shmem_request_hook_type plpgsql_check_prev_shmem_request_hook; #endif extern shmem_startup_hook_type plpgsql_check_prev_shmem_startup_hook; extern PLpgSQL_plugin **plpgsql_check_plugin_var_ptr; extern void plpgsql_check_check_ext_version(Oid fn_oid); extern void plpgsql_check_passive_check_init(void); /* * Links to function in plpgsql module */ typedef PLpgSQL_type *(*plpgsql_check__build_datatype_t) (Oid typeOid, int32 typmod, Oid collation, TypeName *origtypname); extern plpgsql_check__build_datatype_t plpgsql_check__build_datatype_p; typedef PLpgSQL_function *(*plpgsql_check__compile_t) (FunctionCallInfo fcinfo, bool forValidator); extern plpgsql_check__compile_t plpgsql_check__compile_p; typedef void (*plpgsql_check__parser_setup_t) (struct ParseState *pstate, PLpgSQL_expr *expr); extern plpgsql_check__parser_setup_t plpgsql_check__parser_setup_p; typedef const char *(*plpgsql_check__stmt_typename_t) (PLpgSQL_stmt *stmt); extern plpgsql_check__stmt_typename_t plpgsql_check__stmt_typename_p; typedef Oid (*plpgsql_check__exec_get_datum_type_t) (PLpgSQL_execstate *estate, PLpgSQL_datum *datum); extern plpgsql_check__exec_get_datum_type_t plpgsql_check__exec_get_datum_type_p; typedef int (*plpgsql_check__recognize_err_condition_t) (const char *condname, bool allow_sqlstate); extern plpgsql_check__recognize_err_condition_t plpgsql_check__recognize_err_condition_p; typedef PLpgSQL_nsitem *(*plpgsql_check__ns_lookup_t) (PLpgSQL_nsitem *ns_cur, bool localmode, const char *name1, const char *name2, const char *name3, int *names_used); extern plpgsql_check__ns_lookup_t plpgsql_check__ns_lookup_p; #define NEVER_READ_VARIABLE_TEXT "never read variable \"%s\"" #define NEVER_READ_VARIABLE_TEXT_CHECK_LENGTH 19 #define UNUSED_PARAMETER_TEXT "unused parameter \"%s\"" #define NEVER_READ_PARAMETER_TEXT "parameter \"%s\" is never read" #define UNMODIFIED_VARIABLE_TEXT "unmodified OUT variable \"%s\"" #define OUT_COMPOSITE_IS_NOT_SINGLE_TEXT "composite OUT variable \"%s\" is not single argument" #define UNUSED_VARIABLE_TEXT "unused variable \"%s\"" #define UNUSED_VARIABLE_TEXT_CHECK_LENGTH 15 #define MAYBE_UNMODIFIED_VARIABLE_TEXT "OUT variable \"%s\" is maybe unmodified" /* * Expecting persistent oid of nextval, currval and setval functions. * Ensured by regress tests. */ #define NEXTVAL_OID 1574 #define CURRVAL_OID 1575 #define SETVAL_OID 1576 #define SETVAL2_OID 1765 #define FORMAT_0PARAM_OID 3540 #define FORMAT_NPARAM_OID 3539 #ifndef TupleDescAttr #define TupleDescAttr(tupdesc, i) ((tupdesc)->attrs[(i)]) #endif #define recvar_tuple(rec) (rec->erh ? expanded_record_get_tuple(rec->erh) : NULL) #define recvar_tupdesc(rec) (rec->erh ? expanded_record_fetch_tupdesc(rec->erh) : NULL) #define FUNCS_PER_USER 128 /* initial table size */ #ifdef _MSC_VER #define strcasecmp _stricmp #define strncasecmp _strnicmp #endif plpgsql_check-2.7.8/src/plpgsql_check_builtins.h000066400000000000000000000055351465410532300220520ustar00rootroot00000000000000 #ifndef PLPGSQL_CHECK_BUILTINS #define PLPGSQL_CHECK_BUILTINS #ifdef _MSC_VER /* * _PG_init should be exported, but PGDLLEXPORT cannot be used due * collision with _PG_init from plpgsql.h */ #ifdef _M_X64 #pragma comment( linker, "/export:_PG_init" ) #else #pragma comment( linker, "/export:_PG_init=__PG_init" ) #endif #endif #ifndef PGDLLEXPORT #ifdef _MSC_VER #define PGDLLEXPORT __declspec(dllexport) /* * PG_MODULE_MAGIC and PG_FUNCTION_INFO_V1 macros are broken for MSVC. * So, we redefine them. */ #undef PG_MODULE_MAGIC #define PG_MODULE_MAGIC \ extern PGDLLEXPORT const Pg_magic_struct *PG_MAGIC_FUNCTION_NAME(void); \ const Pg_magic_struct * \ PG_MAGIC_FUNCTION_NAME(void) \ { \ static const Pg_magic_struct Pg_magic_data = PG_MODULE_MAGIC_DATA; \ return &Pg_magic_data; \ } \ extern int no_such_variable #undef PG_FUNCTION_INFO_V1 #define PG_FUNCTION_INFO_V1(funcname) \ extern PGDLLEXPORT const Pg_finfo_record * CppConcat(pg_finfo_,funcname)(void); \ const Pg_finfo_record * \ CppConcat(pg_finfo_,funcname) (void) \ { \ static const Pg_finfo_record my_finfo = { 1 }; \ return &my_finfo; \ } \ extern int no_such_variable #else #define PGDLLEXPORT PGDLLIMPORT #endif #endif /* * Interface * */ extern void _PG_init(void); extern PGDLLEXPORT Datum plpgsql_check_function_tb(PG_FUNCTION_ARGS); extern PGDLLEXPORT Datum plpgsql_check_function(PG_FUNCTION_ARGS); extern PGDLLEXPORT Datum plpgsql_show_dependency_tb(PG_FUNCTION_ARGS); extern PGDLLEXPORT Datum plpgsql_check_function_tb_name(PG_FUNCTION_ARGS); extern PGDLLEXPORT Datum plpgsql_check_function_name(PG_FUNCTION_ARGS); extern PGDLLEXPORT Datum plpgsql_show_dependency_tb_name(PG_FUNCTION_ARGS); extern PGDLLEXPORT Datum plpgsql_profiler_reset(PG_FUNCTION_ARGS); extern PGDLLEXPORT Datum plpgsql_profiler_reset_all(PG_FUNCTION_ARGS); extern PGDLLEXPORT Datum plpgsql_profiler_function_tb(PG_FUNCTION_ARGS); extern PGDLLEXPORT Datum plpgsql_profiler_function_statements_tb(PG_FUNCTION_ARGS); extern PGDLLEXPORT Datum plpgsql_profiler_function_tb_name(PG_FUNCTION_ARGS); extern PGDLLEXPORT Datum plpgsql_profiler_function_statements_tb_name(PG_FUNCTION_ARGS); extern PGDLLEXPORT Datum plpgsql_profiler_functions_all_tb(PG_FUNCTION_ARGS); extern PGDLLEXPORT Datum plpgsql_coverage_statements(PG_FUNCTION_ARGS); extern PGDLLEXPORT Datum plpgsql_coverage_branches(PG_FUNCTION_ARGS); extern PGDLLEXPORT Datum plpgsql_coverage_statements_name(PG_FUNCTION_ARGS); extern PGDLLEXPORT Datum plpgsql_coverage_branches_name(PG_FUNCTION_ARGS); extern PGDLLEXPORT Datum plpgsql_check_pragma(PG_FUNCTION_ARGS); extern PGDLLEXPORT Datum plpgsql_profiler_install_fake_queryid_hook(PG_FUNCTION_ARGS); extern PGDLLEXPORT Datum plpgsql_profiler_remove_fake_queryid_hook(PG_FUNCTION_ARGS); extern PGDLLEXPORT Datum plpgsql_check_profiler_ctrl(PG_FUNCTION_ARGS); extern PGDLLEXPORT Datum plpgsql_check_tracer_ctrl(PG_FUNCTION_ARGS); #endif plpgsql_check-2.7.8/src/pragma.c000066400000000000000000000163521465410532300165630ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * pragma.c * * pragma related code * * by Pavel Stehule 2013-2024 * *------------------------------------------------------------------------- */ #include "plpgsql_check.h" #include "plpgsql_check_builtins.h" #include "utils/builtins.h" #include "utils/array.h" #include "parser/scansup.h" #include "tcop/tcopprot.h" #include "tcop/utility.h" #ifdef _MSC_VER #define strcasecmp _stricmp #define strncasecmp _strnicmp #endif PG_FUNCTION_INFO_V1(plpgsql_check_pragma); static void runtime_pragma_apply(char *pragma_str) { while (scanner_isspace(*pragma_str)) pragma_str++; if (strncasecmp(pragma_str, "STATUS:", 7) == 0) { pragma_str += 7; while (scanner_isspace(*pragma_str)) pragma_str++; if (strcasecmp(pragma_str, "TRACER") == 0) elog(NOTICE, "tracer is %s", plpgsql_check_tracer ? "enabled" : "disabled"); } else if (strncasecmp(pragma_str, "ENABLE:", 7) == 0) { pragma_str += 7; while (scanner_isspace(*pragma_str)) pragma_str++; if (strcasecmp(pragma_str, "TRACER") == 0) plpgsql_check_tracer = true; } else if (strncasecmp(pragma_str, "DISABLE:", 8) == 0) { pragma_str += 8; while (scanner_isspace(*pragma_str)) pragma_str++; if (strcasecmp(pragma_str, "TRACER") == 0) plpgsql_check_tracer = false; } } static bool pragma_apply(PLpgSQL_checkstate *cstate, plpgsql_check_pragma_vector *pv, char *pragma_str, PLpgSQL_nsitem *ns, int lineno) { bool is_valid = true; Assert(cstate); while (scanner_isspace(*pragma_str)) pragma_str++; if (strncasecmp(pragma_str, "ECHO:", 5) == 0) { elog(NOTICE, "%s", plpgsql_check_process_echo_string(pragma_str + 5, cstate->cinfo)); } else if (strncasecmp(pragma_str, "STATUS:", 7) == 0) { pragma_str += 7; while (scanner_isspace(*pragma_str)) pragma_str++; if (strcasecmp(pragma_str, "CHECK") == 0) elog(NOTICE, "check is %s", pv->disable_check ? "disabled" : "enabled"); else if (strcasecmp(pragma_str, "TRACER") == 0) elog(NOTICE, "tracer is %s", pv->disable_tracer ? "disabled" : "enabled"); else if (strcasecmp(pragma_str, "OTHER_WARNINGS") == 0) elog(NOTICE, "other_warnings is %s", pv->disable_other_warnings ? "disabled" : "enabled"); else if (strcasecmp(pragma_str, "PERFORMANCE_WARNINGS") == 0) elog(NOTICE, "performance_warnings is %s", pv->disable_performance_warnings ? "disabled" : "enabled"); else if (strcasecmp(pragma_str, "EXTRA_WARNINGS") == 0) elog(NOTICE, "extra_warnings is %s", pv->disable_extra_warnings ? "disabled" : "enabled"); else if (strcasecmp(pragma_str, "SECURITY_WARNINGS") == 0) elog(NOTICE, "security_warnings is %s", pv->disable_security_warnings ? "disabled" : "enabled"); else if (strcasecmp(pragma_str, "COMPATIBILITY_WARNINGS") == 0) elog(NOTICE, "compatibility_warnings is %s", pv->disable_compatibility_warnings ? "disabled" : "enabled"); else if (strcasecmp(pragma_str, "CONSTANTS_TRANCING") == 0) elog(NOTICE, "constants_traising is %s", pv->disable_constants_tracing ? "disabled" : "enabled"); else { elog(WARNING, "unsuported pragma: %s", pragma_str); is_valid = false; } } else if (strncasecmp(pragma_str, "ENABLE:", 7) == 0) { pragma_str += 7; while (scanner_isspace(*pragma_str)) pragma_str++; if (strcasecmp(pragma_str, "CHECK") == 0) pv->disable_check = false; else if (strcasecmp(pragma_str, "TRACER") == 0) pv->disable_tracer = false; else if (strcasecmp(pragma_str, "OTHER_WARNINGS") == 0) pv->disable_other_warnings = false; else if (strcasecmp(pragma_str, "PERFORMANCE_WARNINGS") == 0) pv->disable_performance_warnings = false; else if (strcasecmp(pragma_str, "EXTRA_WARNINGS") == 0) pv->disable_extra_warnings = false; else if (strcasecmp(pragma_str, "SECURITY_WARNINGS") == 0) pv->disable_security_warnings = false; else if (strcasecmp(pragma_str, "COMPATIBILITY_WARNINGS") == 0) pv->disable_compatibility_warnings = false; else if (strcasecmp(pragma_str, "CONSTANTS_TRACING") == 0) pv->disable_constants_tracing = false; else { elog(WARNING, "unsuported pragma: %s", pragma_str); is_valid = false; } } else if (strncasecmp(pragma_str, "DISABLE:", 8) == 0) { pragma_str += 8; while (scanner_isspace(*pragma_str)) pragma_str++; if (strcasecmp(pragma_str, "CHECK") == 0) pv->disable_check = true; else if (strcasecmp(pragma_str, "TRACER") == 0) pv->disable_tracer = true; else if (strcasecmp(pragma_str, "OTHER_WARNINGS") == 0) pv->disable_other_warnings = true; else if (strcasecmp(pragma_str, "PERFORMANCE_WARNINGS") == 0) pv->disable_performance_warnings = true; else if (strcasecmp(pragma_str, "EXTRA_WARNINGS") == 0) pv->disable_extra_warnings = true; else if (strcasecmp(pragma_str, "SECURITY_WARNINGS") == 0) pv->disable_security_warnings = true; else if (strcasecmp(pragma_str, "COMPATIBILITY_WARNINGS") == 0) pv->disable_compatibility_warnings = true; else if (strcasecmp(pragma_str, "CONSTANTS_TRACING") == 0) pv->disable_constants_tracing = true; else elog(WARNING, "unsuported pragma: %s", pragma_str); } else if (strncasecmp(pragma_str, "TYPE:", 5) == 0) { is_valid = plpgsql_check_pragma_type(cstate, pragma_str + 5, ns, lineno); } else if (strncasecmp(pragma_str, "TABLE:", 6) == 0) { is_valid = plpgsql_check_pragma_table(cstate, pragma_str + 6, lineno); } else if (strncasecmp(pragma_str, "SEQUENCE:", 6) == 0) { is_valid = plpgsql_check_pragma_sequence(cstate, pragma_str + 9, lineno); } else if (strncasecmp(pragma_str, "ASSERT-SCHEMA:", 14) == 0) { is_valid = plpgsql_check_pragma_assert(cstate, PLPGSQL_CHECK_PRAGMA_ASSERT_SCHEMA, pragma_str + 14, ns, lineno); } else if (strncasecmp(pragma_str, "ASSERT-TABLE:", 13) == 0) { is_valid = plpgsql_check_pragma_assert(cstate, PLPGSQL_CHECK_PRAGMA_ASSERT_TABLE, pragma_str + 13, ns, lineno); } else if (strncasecmp(pragma_str, "ASSERT-COLUMN:", 14) == 0) { is_valid = plpgsql_check_pragma_assert(cstate, PLPGSQL_CHECK_PRAGMA_ASSERT_COLUMN, pragma_str + 14, ns, lineno); } else { elog(WARNING, "unsupported pragma: %s", pragma_str); is_valid = false; } return is_valid; } /* * Implementation of pragma function. There are two different * use cases - 1) it is used for static analyze by plpgsql_check, * where arguments are read from parse tree. * 2) it is used for controlling of code tracing in runtime. * arguments, are processed as usual for variadic text function. */ Datum plpgsql_check_pragma(PG_FUNCTION_ARGS) { ArrayType *array; ArrayIterator iter; bool isnull; Datum value; if (PG_ARGISNULL(0)) PG_RETURN_INT32(0); array = PG_GETARG_ARRAYTYPE_P(0); iter = array_create_iterator(array, 0, NULL); while (array_iterate(iter, &value, &isnull)) { char *pragma_str; if (isnull) continue; pragma_str = TextDatumGetCString(value); runtime_pragma_apply(pragma_str); pfree(pragma_str); } array_free_iterator(iter); PG_RETURN_INT32(1); } void plpgsql_check_pragma_apply(PLpgSQL_checkstate *cstate, char *pragma_str, PLpgSQL_nsitem *ns, int lineno) { if (pragma_apply(cstate, &(cstate->pragma_vector), pragma_str, ns, lineno)) cstate->was_pragma = true; } plpgsql_check-2.7.8/src/profiler.c000066400000000000000000001557271465410532300171500ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * profiler.c * * profiler accessories code * * by Pavel Stehule 2013-2024 * *------------------------------------------------------------------------- */ #include "plpgsql_check.h" #include "plpgsql_check_builtins.h" #include "access/htup_details.h" #include "catalog/pg_type.h" #include "commands/dbcommands.h" #include "nodes/pg_list.h" #include "parser/analyze.h" #include "storage/lwlock.h" #include "storage/shmem.h" #include "tcop/tcopprot.h" #include "utils/array.h" #include "utils/builtins.h" #include "utils/memutils.h" #include "utils/lsyscache.h" #include "utils/snapmgr.h" #include "utils/syscache.h" #include "utils/float.h" #include /* * Any instance of plpgsql function will have a own profile. * When function will be dropped, then related profile should * be removed from shared memory. * * The local profile is created when function is initialized, * and it is stored in plugin_info field. When function is finished, * data from local profile is merged to shared profile. */ typedef struct profiler_hashkey { Oid fn_oid; Oid db_oid; TransactionId fn_xmin; ItemPointerData fn_tid; int16 chunk_num; } profiler_hashkey; /* * We want to collect data about execution on function level. The * possible issue can be more different profiles. On this level * the version of function is not important, so the hash key is * composed only from fn_oid and db_oid */ typedef struct fstats_hashkey { Oid fn_oid; Oid db_oid; } fstats_hashkey; typedef struct fstats { fstats_hashkey key; slock_t mutex; uint64 exec_count; uint64 exec_count_err; uint64 total_time; double total_time_xx; uint64 min_time; uint64 max_time; } fstats; /* * This is used as cache for types of expressions of USING clause * (EXECUTE like statements). */ typedef struct { int nparams; Oid paramtypes[FLEXIBLE_ARRAY_MEMBER]; } query_params; /* * Attention - the commands that can contains nestested commands * has attached own time and nested statements time too. */ typedef struct profiler_stmt { int lineno; pc_queryid queryid; uint64 us_max; uint64 us_total; uint64 rows; uint64 exec_count; uint64 exec_count_err; instr_time start_time; instr_time total; bool has_queryid; query_params *qparams; } profiler_stmt; typedef struct profiler_stmt_reduced { int lineno; pc_queryid queryid; uint64 us_max; uint64 us_total; uint64 rows; uint64 exec_count; uint64 exec_count_err; bool has_queryid; } profiler_stmt_reduced; #define STATEMENTS_PER_CHUNK 30 /* * The shared profile will be stored as set of chunks */ typedef struct profiler_stmt_chunk { profiler_hashkey key; slock_t mutex; /* only first chunk require mutex */ profiler_stmt_reduced stmts[STATEMENTS_PER_CHUNK]; } profiler_stmt_chunk; typedef struct profiler_shared_state { LWLock *lock; LWLock *fstats_lock; } profiler_shared_state; /* * This structure is used as plpgsql extension parameter */ typedef struct profiler_info { profiler_stmt *stmts; int nstatements; instr_time start_time; PLpgSQL_function *func; } profiler_info; typedef struct profiler_iterator { profiler_hashkey key; plpgsql_check_result_info *ri; HTAB *chunks; profiler_stmt_chunk *current_chunk; int current_statement; } profiler_iterator; typedef struct { int stmtid; int64 nested_us_time; int64 nested_exec_count; profiler_iterator *pi; coverage_state *cs; int *stmtid_map; plpgsql_check_plugin2_stmt_info *stmts_info; } profiler_stmt_walker_options; enum { COVERAGE_STATEMENTS, COVERAGE_BRANCHES }; /* * should be enough for project of 300K PLpgSQL rows. * It should to take about 24.4MB of shared memory. */ int plpgsql_check_profiler_max_shared_chunks = 15000; PG_FUNCTION_INFO_V1(plpgsql_check_profiler_ctrl); PG_FUNCTION_INFO_V1(plpgsql_profiler_reset_all); PG_FUNCTION_INFO_V1(plpgsql_profiler_reset); PG_FUNCTION_INFO_V1(plpgsql_coverage_statements); PG_FUNCTION_INFO_V1(plpgsql_coverage_branches); PG_FUNCTION_INFO_V1(plpgsql_coverage_statements_name); PG_FUNCTION_INFO_V1(plpgsql_coverage_branches_name); PG_FUNCTION_INFO_V1(plpgsql_profiler_install_fake_queryid_hook); PG_FUNCTION_INFO_V1(plpgsql_profiler_remove_fake_queryid_hook); static void update_persistent_profile(profiler_info *pinfo, PLpgSQL_function *func, const int *stmtid_map); static PLpgSQL_expr *profiler_get_expr(PLpgSQL_stmt *stmt, bool *dynamic, List **params); static pc_queryid profiler_get_queryid(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt, bool *has_queryid, query_params **qparams); #if PG_VERSION_NUM >= 140000 static void profiler_fake_queryid_hook(ParseState *pstate, Query *query, JumbleState *jstate); #else static void profiler_fake_queryid_hook(ParseState *pstate, Query *query); #endif static void stmts_walker(profiler_info *pinfo, profiler_stmt_walker_mode, List *stmts, PLpgSQL_stmt *parent_stmt, const char *description, profiler_stmt_walker_options *opts); static void profiler_stmt_walker(profiler_info *pinfo, profiler_stmt_walker_mode mode, PLpgSQL_stmt *stmt, PLpgSQL_stmt *parent_stmt, const char *description, int stmt_block_num, profiler_stmt_walker_options *opts); static void profiler_func_setup(PLpgSQL_execstate *estate, PLpgSQL_function *func, void **plugin2_info); static void profiler_func_end(PLpgSQL_execstate *estate, PLpgSQL_function *func, void **plugin2_info); static void profiler_func_end_aborted(Oid fn_oid, void **plugin2_info); static void profiler_stmt_beg(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt, void **plugin2_info); static void profiler_stmt_end(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt, void **plugin2_info); static void profiler_stmt_end_aborted(Oid fn_oid, int stmtid, void **plugin2_info); static plpgsql_check_plugin2 profiler_plugin2 = { profiler_func_setup, NULL, profiler_func_end, profiler_func_end_aborted, profiler_stmt_beg, profiler_stmt_end, profiler_stmt_end_aborted, NULL, NULL, NULL, NULL, NULL }; static HTAB *profiler_HashTable = NULL; static HTAB *shared_profiler_chunks_HashTable = NULL; static HTAB *profiler_chunks_HashTable = NULL; static HTAB *fstats_HashTable = NULL; static HTAB *shared_fstats_HashTable = NULL; static post_parse_analyze_hook_type prev_post_parse_analyze_hook = NULL; static profiler_shared_state *profiler_ss = NULL; static MemoryContext profiler_mcxt = NULL; static MemoryContext profiler_queryid_mcxt = NULL; bool plpgsql_check_profiler = false; /* * Use the Youngs-Cramer algorithm to incorporate the new value into the * transition values. */ static void eval_stddev_accum(uint64 *_N, uint64 *_Sx, float8 *_Sxx, uint64 newval) { uint64 N = *_N; uint64 Sx = *_Sx; float8 Sxx = *_Sxx; float8 tmp; /* * Use the Youngs-Cramer algorithm to incorporate the new value into the * transition values. */ N += 1; Sx += newval; if (N > 1) { tmp = ((float8 ) newval) * ((float8) N) - ((float8) Sx); Sxx += tmp * tmp / (N * (N - 1)); if (isinf(Sxx)) Sxx = get_float8_nan(); } else Sxx = 0.0; *_N = N; *_Sx = Sx; *_Sxx = Sxx; } static profiler_stmt_reduced * get_stmt_profile_next(profiler_iterator *pi) { if (pi->current_chunk) { if (pi->current_statement >= STATEMENTS_PER_CHUNK) { bool found; pi->key.chunk_num += 1; pi->current_chunk = (profiler_stmt_chunk *) hash_search(pi->chunks, (void *) &pi->key, HASH_FIND, &found); if (!found) elog(ERROR, "broken consistency of plpgsql_check profiler chunks"); pi->current_statement = 0; } return &pi->current_chunk->stmts[pi->current_statement++]; } return NULL; } /* * Calculate required size of shared memory for chunks * */ Size plpgsql_check_shmem_size(void) { Size num_bytes = 0; num_bytes = MAXALIGN(sizeof(profiler_shared_state)); num_bytes = add_size(num_bytes, hash_estimate_size(plpgsql_check_profiler_max_shared_chunks, sizeof(profiler_stmt_chunk))); return num_bytes; } /* * Request additional shared memory resources. * * If you change code here, don't forget to also report the modifications in * _PG_init() for pg14 and below. */ #if PG_VERSION_NUM >= 150000 void plpgsql_check_profiler_shmem_request(void) { if (plpgsql_check_prev_shmem_request_hook) plpgsql_check_prev_shmem_request_hook(); RequestAddinShmemSpace(plpgsql_check_shmem_size()); RequestNamedLWLockTranche("plpgsql_check profiler", 1); RequestNamedLWLockTranche("plpgsql_check fstats", 1); } #endif /* * Initialize shared memory used like permanent profile storage. * No other parts use shared memory, so this code is completly here. * */ void plpgsql_check_profiler_shmem_startup(void) { bool found; HASHCTL info; shared_profiler_chunks_HashTable = NULL; shared_fstats_HashTable = NULL; if (plpgsql_check_prev_shmem_startup_hook) plpgsql_check_prev_shmem_startup_hook(); /* * Create or attach to the shared memory state, including hash table */ LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE); profiler_ss = ShmemInitStruct("plpgsql_check profiler state", sizeof(profiler_shared_state), &found); if (!found) { profiler_ss->lock = &(GetNamedLWLockTranche("plpgsql_check profiler"))->lock; profiler_ss->fstats_lock = &(GetNamedLWLockTranche("plpgsql_check fstats"))->lock; } memset(&info, 0, sizeof(info)); info.keysize = sizeof(profiler_hashkey); info.entrysize = sizeof(profiler_stmt_chunk); shared_profiler_chunks_HashTable = ShmemInitHash("plpgsql_check profiler chunks", plpgsql_check_profiler_max_shared_chunks, plpgsql_check_profiler_max_shared_chunks, &info, HASH_ELEM | HASH_BLOBS); memset(&info, 0, sizeof(info)); info.keysize = sizeof(fstats_hashkey); info.entrysize = sizeof(fstats); shared_fstats_HashTable = ShmemInitHash("plpgsql_check fstats", 500, 1000, &info, HASH_ELEM | HASH_BLOBS); LWLockRelease(AddinShmemInitLock); } /* * Profiler implementation */ static void profiler_init_hashkey(profiler_hashkey *hk, PLpgSQL_function *func) { memset(hk, 0, sizeof(profiler_hashkey)); hk->db_oid = MyDatabaseId; hk->fn_oid = func->fn_oid; hk->fn_xmin = func->fn_xmin; hk->fn_tid = func->fn_tid; hk->chunk_num = 1; } /* * Hash table for local function profiles. When shared memory is not available * because plpgsql_check was not loaded by shared_proload_libraries, then function * profiles is stored in local profile chunks. A format is same for shared profiles. */ static void profiler_chunks_HashTableInit(void) { HASHCTL ctl; Assert(profiler_chunks_HashTable == NULL); memset(&ctl, 0, sizeof(ctl)); ctl.keysize = sizeof(profiler_hashkey); ctl.entrysize = sizeof(profiler_stmt_chunk); ctl.hcxt = profiler_mcxt; profiler_chunks_HashTable = hash_create("plpgsql_check function profiler local chunks", FUNCS_PER_USER, &ctl, HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); } static void fstats_init_hashkey(fstats_hashkey *fhk, Oid fn_oid) { memset(fhk, 0, sizeof(fstats_hashkey)); fhk->db_oid = MyDatabaseId; fhk->fn_oid = fn_oid; } static void fstats_HashTableInit(void) { HASHCTL ctl; Assert(fstats_HashTable == NULL); memset(&ctl, 0, sizeof(ctl)); ctl.keysize = sizeof(fstats_hashkey); ctl.entrysize = sizeof(fstats); ctl.hcxt = profiler_mcxt; fstats_HashTable = hash_create("plpgsql_check function execution statistics", FUNCS_PER_USER, &ctl, HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); } void plpgsql_check_profiler_init_hash_tables(void) { if (profiler_mcxt) { MemoryContextReset(profiler_mcxt); profiler_HashTable = NULL; profiler_chunks_HashTable = NULL; fstats_HashTable = NULL; } else { profiler_mcxt = AllocSetContextCreate(TopMemoryContext, "plpgsql_check - profiler context", ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); } profiler_chunks_HashTableInit(); fstats_HashTableInit(); } /* * Increase a branch couter - used for branch coverage */ static void increment_branch_counter(coverage_state *cs, int64 executed) { Assert(cs); cs->branches += 1; cs->executed_branches += executed > 0 ? 1 : 0; } #define IS_PLPGSQL_STMT(stmt, typ) (stmt->cmd_type == typ) static bool is_cycle(PLpgSQL_stmt *stmt) { switch (stmt->cmd_type) { case PLPGSQL_STMT_LOOP: case PLPGSQL_STMT_FORI: case PLPGSQL_STMT_FORS: case PLPGSQL_STMT_FORC: case PLPGSQL_STMT_DYNFORS: case PLPGSQL_STMT_FOREACH_A: case PLPGSQL_STMT_WHILE: return true; default: return false; } } /* * Returns statements assigned to cycle's body */ static List * get_cycle_body(PLpgSQL_stmt *stmt) { List *stmts; switch (stmt->cmd_type) { case PLPGSQL_STMT_WHILE: stmts = ((PLpgSQL_stmt_while *) stmt)->body; break; case PLPGSQL_STMT_LOOP: stmts = ((PLpgSQL_stmt_loop *) stmt)->body; break; case PLPGSQL_STMT_FORI: stmts = ((PLpgSQL_stmt_fori *) stmt)->body; break; case PLPGSQL_STMT_FORS: stmts = ((PLpgSQL_stmt_fors *) stmt)->body; break; case PLPGSQL_STMT_FORC: stmts = ((PLpgSQL_stmt_forc *) stmt)->body; break; case PLPGSQL_STMT_DYNFORS: stmts = ((PLpgSQL_stmt_dynfors *) stmt)->body; break; case PLPGSQL_STMT_FOREACH_A: stmts = ((PLpgSQL_stmt_foreach_a *) stmt)->body; break; default: stmts = NIL; break; } return stmts; } /* * profiler_stmt_walker - iterator over plpgsql statements. * * This function is designed for three different purposes: * * a) iterate over all commends and finalize total time * as measured total time substract child total time. * b) iterate over all commands and prepare result for * plpgsql_profiler_function_statements_tb function. * c) iterate over all commands to collect code coverage * metrics */ static void profiler_stmt_walker(profiler_info *pinfo, profiler_stmt_walker_mode mode, PLpgSQL_stmt *stmt, PLpgSQL_stmt *parent_stmt, const char *description, int stmt_block_num, profiler_stmt_walker_options *opts) { profiler_stmt *pstmt = NULL; bool count_exec_time_mode = mode == PLPGSQL_CHECK_STMT_WALKER_COUNT_EXEC_TIME; bool prepare_result_mode = mode == PLPGSQL_CHECK_STMT_WALKER_PREPARE_RESULT; bool collect_coverage_mode = mode == PLPGSQL_CHECK_STMT_WALKER_COLLECT_COVERAGE; int64 nested_us_time = 0; int64 exec_count = 0; int stmtid = -1; char strbuf[100]; int n = 0; List *stmts; ListCell *lc; stmtid = stmt->stmtid - 1; if (count_exec_time_mode) { /* * Get statement info from function execution context * by statement id. */ pstmt = &pinfo->stmts[stmtid]; pstmt->lineno = stmt->lineno; } else { profiler_stmt_reduced *ppstmt = NULL; Assert(opts->pi); /* * When iterator is used, then id of iterator's current statement * have to be same like stmtid of stmt. When function was not executed * in active profile mode, then we have not any stored chunk, and * iterator returns 0 stmtid. */ Assert(!opts->pi->current_chunk || (opts->stmtid_map[opts->pi->current_statement] - 1) == stmtid); /* * Get persistent statement info stored in shared memory * or in session memory by iterator. */ ppstmt = get_stmt_profile_next(opts->pi); if (prepare_result_mode && opts->pi->ri) { plpgsql_check_plugin2_stmt_info *sinfo; int parent_natural_stmtid = -1; parent_natural_stmtid = parent_stmt ? opts->stmts_info[parent_stmt->stmtid - 1].natural_id : -1; sinfo = &opts->stmts_info[stmt->stmtid - 1]; plpgsql_check_put_profile_statement(opts->pi->ri, ppstmt ? ppstmt->queryid : NOQUERYID, sinfo->natural_id, parent_natural_stmtid, description, stmt_block_num, stmt->lineno, ppstmt ? ppstmt->exec_count : 0, ppstmt ? ppstmt->exec_count_err : 0, ppstmt ? ppstmt->us_total : 0.0, ppstmt ? ppstmt->us_max : 0.0, ppstmt ? ppstmt->rows : 0, (char *) sinfo->typname); } else if (collect_coverage_mode) { /* save statement exec count */ exec_count = ppstmt ? ppstmt->exec_count : 0; /* ignore invisible BLOCK */ if (stmt->lineno != -1) { opts->cs->statements += 1; opts->cs->executed_statements += exec_count > 0 ? 1 : 0; } } } if (is_cycle(stmt)) { stmts = get_cycle_body(stmt); stmts_walker(pinfo, mode, stmts, stmt, "loop body", opts); if (collect_coverage_mode) increment_branch_counter(opts->cs, opts->nested_exec_count); } else if (IS_PLPGSQL_STMT(stmt, PLPGSQL_STMT_IF)) { PLpgSQL_stmt_if *stmt_if = (PLpgSQL_stmt_if *) stmt; int64 all_nested_branches_exec_count = 0; /* * Note: when if statement has not else path, then * we have to calculate deduce number of execution * of implicit else path manually. We know number * of execution of IF statement and we can subtract * an number of execution of nested paths. */ stmts_walker(pinfo, mode, stmt_if->then_body, stmt, "then body", opts); if (count_exec_time_mode) { nested_us_time = opts->nested_us_time; } else if (collect_coverage_mode) { increment_branch_counter(opts->cs, opts->nested_exec_count); all_nested_branches_exec_count += opts->nested_exec_count; } foreach(lc, stmt_if->elsif_list) { stmts = ((PLpgSQL_if_elsif *) lfirst(lc))->stmts; sprintf(strbuf, "elsif %d", ++n); stmts_walker(pinfo, mode, stmts, stmt, strbuf, opts); if (count_exec_time_mode) nested_us_time += opts->nested_us_time; else if (collect_coverage_mode) { increment_branch_counter(opts->cs, opts->nested_exec_count); all_nested_branches_exec_count += opts->nested_exec_count; } } if (stmt_if->else_body) { stmts_walker(pinfo, mode, stmt_if->else_body, stmt, "else body", opts); if (count_exec_time_mode) nested_us_time += opts->nested_us_time; else if (collect_coverage_mode) increment_branch_counter(opts->cs, opts->nested_exec_count); } else { /* calculate exec_count for implicit else path */ if (collect_coverage_mode) { int64 else_exec_count = exec_count - all_nested_branches_exec_count; increment_branch_counter(opts->cs, else_exec_count); } } } else if (IS_PLPGSQL_STMT(stmt, PLPGSQL_STMT_CASE)) { PLpgSQL_stmt_case *stmt_case = (PLpgSQL_stmt_case *) stmt; foreach(lc, stmt_case->case_when_list) { stmts = ((PLpgSQL_case_when *) lfirst(lc))->stmts; sprintf(strbuf, "case when %d", ++n); stmts_walker(pinfo, mode, stmts, stmt, strbuf, opts); if (count_exec_time_mode) nested_us_time = opts->nested_us_time; else if (collect_coverage_mode) increment_branch_counter(opts->cs, opts->nested_exec_count); } stmts_walker(pinfo, mode, stmt_case->else_stmts, stmt, "case else", opts); if (count_exec_time_mode) nested_us_time = opts->nested_us_time; else if (collect_coverage_mode) increment_branch_counter(opts->cs, opts->nested_exec_count); } else if (IS_PLPGSQL_STMT(stmt, PLPGSQL_STMT_BLOCK)) { PLpgSQL_stmt_block *stmt_block = (PLpgSQL_stmt_block *) stmt; stmts_walker(pinfo, mode, stmt_block->body, stmt, "body", opts); if (count_exec_time_mode) nested_us_time = opts->nested_us_time; if (stmt_block->exceptions) { if (collect_coverage_mode) increment_branch_counter(opts->cs, opts->nested_exec_count); foreach(lc, stmt_block->exceptions->exc_list) { stmts = ((PLpgSQL_exception *) lfirst(lc))->action; sprintf(strbuf, "exception %d", ++n); stmts_walker(pinfo, mode, stmts, stmt, strbuf, opts); if (count_exec_time_mode) nested_us_time += opts->nested_us_time; else if (collect_coverage_mode) increment_branch_counter(opts->cs, opts->nested_exec_count); } } } if (count_exec_time_mode) { Assert (pstmt); opts->nested_us_time = pstmt->us_total; pstmt->us_total -= nested_us_time; /* * When max time is unknown, but statement was executed only * only once, we can se max time. */ if (pstmt->exec_count == 1 && pstmt->us_max == 1) pstmt->us_max = pstmt->us_total; } else if (collect_coverage_mode) { opts->nested_exec_count = exec_count; } } /* * Enable, disable, show state profiler */ Datum plpgsql_check_profiler_ctrl(PG_FUNCTION_ARGS) { char *optstr; bool result; #define OPTNAME "plpgsql_check.profiler" if (!PG_ARGISNULL(0)) { bool optval = PG_GETARG_BOOL(0); (void) set_config_option(OPTNAME, optval ? "on" : "off", (superuser() ? PGC_SUSET : PGC_USERSET), PGC_S_SESSION, GUC_ACTION_SET, true, 0, false); } optstr = GetConfigOptionByName(OPTNAME, NULL, false); if (strcmp(optstr, "on") == 0) { elog(NOTICE, "profiler is active"); result = true; } else { elog(NOTICE, "profiler is not active"); result = false; }; PG_RETURN_BOOL(result); } /* * clean all chunks used by profiler */ Datum plpgsql_profiler_reset_all(PG_FUNCTION_ARGS) { /*be compiler quite */ (void) fcinfo; if (shared_profiler_chunks_HashTable) { HASH_SEQ_STATUS hash_seq; profiler_stmt_chunk *chunk; fstats *fstats_entry; LWLockAcquire(profiler_ss->lock, LW_EXCLUSIVE); hash_seq_init(&hash_seq, shared_profiler_chunks_HashTable); while ((chunk = hash_seq_search(&hash_seq)) != NULL) { hash_search(shared_profiler_chunks_HashTable, &(chunk->key), HASH_REMOVE, NULL); } LWLockRelease(profiler_ss->lock); Assert(shared_fstats_HashTable); LWLockAcquire(profiler_ss->fstats_lock, LW_EXCLUSIVE); hash_seq_init(&hash_seq, shared_fstats_HashTable); while ((fstats_entry = hash_seq_search(&hash_seq)) != NULL) { hash_search(shared_fstats_HashTable, &(fstats_entry->key), HASH_REMOVE, NULL); } LWLockRelease(profiler_ss->fstats_lock); } plpgsql_check_profiler_init_hash_tables(); PG_RETURN_VOID(); } /* * Clean chunks related to some function */ Datum plpgsql_profiler_reset(PG_FUNCTION_ARGS) { Oid funcoid = PG_GETARG_OID(0); profiler_hashkey hk; fstats_hashkey fhk; HTAB *chunks; HeapTuple procTuple; bool found; bool shared_chunks; procTuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcoid)); if (!HeapTupleIsValid(procTuple)) elog(ERROR, "cache lookup failed for function %u", funcoid); /* ensure correct complete content of hash key */ memset(&hk, 0, sizeof(profiler_hashkey)); hk.fn_oid = funcoid; hk.db_oid = MyDatabaseId; hk.fn_xmin = HeapTupleHeaderGetRawXmin(procTuple->t_data); hk.fn_tid = procTuple->t_self; hk.chunk_num = 1; ReleaseSysCache(procTuple); if (shared_profiler_chunks_HashTable) { LWLockAcquire(profiler_ss->lock, LW_EXCLUSIVE); chunks = shared_profiler_chunks_HashTable; shared_chunks = true; } else { chunks = profiler_chunks_HashTable; shared_chunks = false; } for(;;) { hash_search(chunks, (void *) &hk, HASH_REMOVE, &found); if (!found) break; hk.chunk_num += 1; } if (shared_chunks) LWLockRelease(profiler_ss->lock); fstats_init_hashkey(&fhk, funcoid); if (shared_fstats_HashTable) { LWLockAcquire(profiler_ss->fstats_lock, LW_EXCLUSIVE); hash_search(shared_fstats_HashTable, (void *) &fhk, HASH_REMOVE, NULL); LWLockRelease(profiler_ss->fstats_lock); } else hash_search(fstats_HashTable, (void *) &fhk, HASH_REMOVE, NULL); PG_RETURN_VOID(); } /* * Prepare environment for reading profile and calculation of coverage metric */ static double coverage_internal(Oid fnoid, int coverage_type) { plpgsql_check_info cinfo; coverage_state cs; memset(&cs, 0, sizeof(cs)); plpgsql_check_info_init(&cinfo, fnoid); cinfo.show_profile = true; cinfo.proctuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(cinfo.fn_oid)); if (!HeapTupleIsValid(cinfo.proctuple)) elog(ERROR, "cache lookup failed for function %u", cinfo.fn_oid); plpgsql_check_get_function_info(&cinfo); plpgsql_check_precheck_conditions(&cinfo); plpgsql_check_iterate_over_profile(&cinfo, PLPGSQL_CHECK_STMT_WALKER_COLLECT_COVERAGE, NULL, &cs); ReleaseSysCache(cinfo.proctuple); if (coverage_type == COVERAGE_STATEMENTS) { if (cs.statements > 0) return (double) cs.executed_statements / (double) cs.statements; else return (double) 1.0; } else { if (cs.branches > 0) return (double) cs.executed_branches / (double) cs.branches; else return (double) 1.0; } } Datum plpgsql_coverage_statements_name(PG_FUNCTION_ARGS) { Oid fnoid; char *name_or_signature; if (PG_ARGISNULL(0)) elog(ERROR, "the first argument should not be null"); name_or_signature = text_to_cstring(PG_GETARG_TEXT_PP(0)); fnoid = plpgsql_check_parse_name_or_signature(name_or_signature); PG_RETURN_FLOAT8(coverage_internal(fnoid, COVERAGE_STATEMENTS)); } Datum plpgsql_coverage_branches_name(PG_FUNCTION_ARGS) { Oid fnoid; char *name_or_signature; if (PG_ARGISNULL(0)) elog(ERROR, "the first argument should not be null"); name_or_signature = text_to_cstring(PG_GETARG_TEXT_PP(0)); fnoid = plpgsql_check_parse_name_or_signature(name_or_signature); PG_RETURN_FLOAT8(coverage_internal(fnoid, COVERAGE_BRANCHES)); } Datum plpgsql_coverage_statements(PG_FUNCTION_ARGS) { Oid fnoid; if (PG_ARGISNULL(0)) elog(ERROR, "the first argument should not be null"); fnoid = PG_GETARG_OID(0); PG_RETURN_FLOAT8(coverage_internal(fnoid, COVERAGE_STATEMENTS)); } Datum plpgsql_coverage_branches(PG_FUNCTION_ARGS) { Oid fnoid; if (PG_ARGISNULL(0)) elog(ERROR, "the first argument should not be null"); fnoid = PG_GETARG_OID(0); PG_RETURN_FLOAT8(coverage_internal(fnoid, COVERAGE_BRANCHES)); } Datum plpgsql_profiler_install_fake_queryid_hook(PG_FUNCTION_ARGS) { (void) fcinfo; if (post_parse_analyze_hook == profiler_fake_queryid_hook) PG_RETURN_VOID(); if (post_parse_analyze_hook == NULL) prev_post_parse_analyze_hook = post_parse_analyze_hook; post_parse_analyze_hook = profiler_fake_queryid_hook; PG_RETURN_VOID(); } Datum plpgsql_profiler_remove_fake_queryid_hook(PG_FUNCTION_ARGS) { (void) fcinfo; if (post_parse_analyze_hook == profiler_fake_queryid_hook) { post_parse_analyze_hook = prev_post_parse_analyze_hook; prev_post_parse_analyze_hook = NULL; } PG_RETURN_VOID(); } static void update_persistent_fstats(PLpgSQL_function *func, uint64 elapsed) { HTAB *fstats_ht; bool htab_is_shared; fstats_hashkey fhk; fstats *fstats_item; bool found; bool use_spinlock = false; fstats_init_hashkey(&fhk, func->fn_oid); /* try to find first chunk in shared (or local) memory */ if (shared_fstats_HashTable) { LWLockAcquire(profiler_ss->fstats_lock, LW_SHARED); fstats_ht = shared_fstats_HashTable; htab_is_shared = true; } else { fstats_ht = fstats_HashTable; htab_is_shared = false; } fstats_item = (fstats *) hash_search(fstats_ht, (void *) &fhk, HASH_FIND, &found); if (!found) { if (htab_is_shared) { LWLockRelease(profiler_ss->fstats_lock); LWLockAcquire(profiler_ss->fstats_lock, LW_EXCLUSIVE); } fstats_item = (fstats *) hash_search(fstats_ht, (void *) &fhk, HASH_ENTER, &found); } if (!fstats_item) elog(ERROR, "cannot to insert new entry to profiler's function statistics"); if (htab_is_shared) { if (found) { SpinLockAcquire(&fstats_item->mutex); use_spinlock = true; } else SpinLockInit(&fstats_item->mutex); } if (!found) { fstats_item->exec_count = 0; fstats_item->exec_count_err = 0; fstats_item->total_time = 0; fstats_item->total_time_xx = 0.0; fstats_item->min_time = elapsed; fstats_item->max_time = elapsed; } else { fstats_item->min_time = fstats_item->min_time < elapsed ? fstats_item->min_time : elapsed; fstats_item->max_time = fstats_item->max_time > elapsed ? fstats_item->max_time : elapsed; } eval_stddev_accum(&fstats_item->exec_count, &fstats_item->total_time, &fstats_item->total_time_xx, elapsed); if (use_spinlock) SpinLockRelease(&fstats_item->mutex); if (htab_is_shared) LWLockRelease(profiler_ss->fstats_lock); } static void update_persistent_profile(profiler_info *pinfo, PLpgSQL_function *func, const int *stmtid_map) { profiler_hashkey hk; profiler_stmt_chunk *chunk = NULL; bool found; HTAB *chunks; bool shared_chunks; volatile profiler_stmt_chunk *chunk_with_mutex = NULL; if (shared_profiler_chunks_HashTable) { chunks = shared_profiler_chunks_HashTable; LWLockAcquire(profiler_ss->lock, LW_SHARED); shared_chunks = true; } else { chunks = profiler_chunks_HashTable; shared_chunks = false; } profiler_init_hashkey(&hk, func) ; /* don't need too strong lock for reading shared memory */ chunk = (profiler_stmt_chunk *) hash_search(chunks, (void *) &hk, HASH_FIND, &found); /* We need exclusive lock, when we want to add new chunk */ if (!found && shared_chunks) { LWLockRelease(profiler_ss->lock); LWLockAcquire(profiler_ss->lock, LW_EXCLUSIVE); /* repeat searching under exclusive lock */ chunk = (profiler_stmt_chunk *) hash_search(chunks, (void *) &hk, HASH_FIND, &found); } if (!found) { int stmt_counter = 0; int i; /* aftre increment first chunk will be created with chunk number 1 */ hk.chunk_num = 0; /* we should to enter empty chunks first */ for (i = 0; i < func->nstatements; i++) { volatile profiler_stmt_reduced *prstmt; profiler_stmt *pstmt; /* * We need to store statement statistics to chunks in natural order * next statistics should be related to statement on same or higher * line. Unfortunately buildin stmtid has inverse order based on * bison parser processing * statement should to be on same or higher line) * */ int n = stmtid_map[i] - 1; /* Skip gaps in reorder map */ if (n == -1) continue; pstmt = &pinfo->stmts[n]; if (hk.chunk_num == 0 || stmt_counter >= STATEMENTS_PER_CHUNK) { hk.chunk_num += 1; chunk = (profiler_stmt_chunk *) hash_search(chunks, (void *) &hk, HASH_ENTER, &found); if (found) elog(ERROR, "broken consistency of plpgsql_check profiler chunks"); if (hk.chunk_num == 1 && shared_chunks) SpinLockInit(&chunk->mutex); stmt_counter = 0; } prstmt = &chunk->stmts[stmt_counter++]; prstmt->lineno = pstmt->lineno; prstmt->queryid = pstmt->queryid; prstmt->has_queryid = pstmt->has_queryid; prstmt->us_max = pstmt->us_max; prstmt->us_total = pstmt->us_total; prstmt->rows = pstmt->rows; prstmt->exec_count = pstmt->exec_count; prstmt->exec_count_err = pstmt->exec_count_err; } /* clean unused stmts in chunk */ while (stmt_counter < STATEMENTS_PER_CHUNK) chunk->stmts[stmt_counter++].lineno = -1; if (shared_chunks) LWLockRelease(profiler_ss->lock); return; } /* * Now we know, so there is already profile, and we have all necessary locks. * Teoreticaly, we can reuse existing chunk, but inside PG_TRY block is better * to take this value again to fix warning - "might be clobbered by 'longjmp" */ PG_TRY(); { profiler_stmt_chunk *_chunk = NULL; HTAB *_chunks; int stmt_counter = 0; int i = 0; _chunks = shared_chunks ? shared_profiler_chunks_HashTable : profiler_chunks_HashTable; profiler_init_hashkey(&hk, func) ; /* search chunk again */ _chunk = (profiler_stmt_chunk *) hash_search(_chunks, (void *) &hk, HASH_FIND, &found); if (shared_chunks) { chunk_with_mutex = _chunk; SpinLockAcquire(&chunk_with_mutex->mutex); } else chunk_with_mutex = NULL; hk.chunk_num = 1; stmt_counter = 0; /* there is a profiler chunk already */ for (i = 0; i < func->nstatements; i++) { volatile profiler_stmt_reduced *prstmt; profiler_stmt *pstmt; /* * We need to store statement statistics to chunks in natural order * (next statistics should be related to statement on same or higher * line). Unfortunately buildin stmtid has inverse order based on * bison parser processing statement. */ int n = stmtid_map[i] - 1; /* Skip gaps in reorder map */ if (n == -1) continue; pstmt = &pinfo->stmts[n]; if (stmt_counter >= STATEMENTS_PER_CHUNK) { hk.chunk_num += 1; _chunk = (profiler_stmt_chunk *) hash_search(_chunks, (void *) &hk, HASH_FIND, &found); if (!found) elog(ERROR, "broken consistency of plpgsql_check profiler chunks"); stmt_counter = 0; } prstmt = &_chunk->stmts[stmt_counter++]; if (prstmt->lineno != pstmt->lineno) elog(ERROR, "broken consistency of plpgsql_check profiler chunks %d %d", prstmt->lineno, pstmt->lineno); if (prstmt->us_max < pstmt->us_max) prstmt->us_max = pstmt->us_max; prstmt->us_total += pstmt->us_total; prstmt->rows += pstmt->rows; prstmt->exec_count += pstmt->exec_count; prstmt->exec_count_err += pstmt->exec_count_err; } } PG_CATCH(); { if (chunk_with_mutex) SpinLockRelease(&chunk_with_mutex->mutex); PG_RE_THROW(); } PG_END_TRY(); if (chunk_with_mutex) SpinLockRelease(&chunk_with_mutex->mutex); if (shared_chunks) LWLockRelease(profiler_ss->lock); } /* * Iterate over list of statements */ static void stmts_walker(profiler_info *pinfo, profiler_stmt_walker_mode mode, List *stmts, PLpgSQL_stmt *parent_stmt, const char *description, profiler_stmt_walker_options *opts) { bool count_exec_time = mode == PLPGSQL_CHECK_STMT_WALKER_COUNT_EXEC_TIME; bool collect_coverage = mode == PLPGSQL_CHECK_STMT_WALKER_COLLECT_COVERAGE; int64 nested_us_time = 0; int64 nested_exec_count = 0; int stmt_block_num = 1; ListCell *lc; foreach(lc, stmts) { PLpgSQL_stmt *stmt = (PLpgSQL_stmt *) lfirst(lc); profiler_stmt_walker(pinfo, mode, stmt, parent_stmt, description, stmt_block_num, opts); /* add stmt execution time to total execution time */ if (count_exec_time) nested_us_time += opts->nested_us_time; /* * For calculation of coverage we need a numbers of nested statements * execution. Usually or statements in list has same number of execution. * But it should not be true for some reasons (after RETURN or some exception). * I am not sure if following simplification is accurate, but maybe. I use * number of execution of first statement in block like number of execution * all statements in list. */ if (collect_coverage && stmt_block_num == 1) nested_exec_count = opts->nested_exec_count; stmt_block_num += 1; } if (count_exec_time) opts->nested_us_time = nested_us_time; if (collect_coverage) opts->nested_exec_count = nested_exec_count; } /* * Given a PLpgSQL_stmt, return the underlying PLpgSQL_expr that may contain a * queryid. */ static PLpgSQL_expr * profiler_get_expr(PLpgSQL_stmt *stmt, bool *dynamic, List **params) { PLpgSQL_expr *expr = NULL; *params = NIL; *dynamic = false; switch(stmt->cmd_type) { case PLPGSQL_STMT_ASSIGN: expr = ((PLpgSQL_stmt_assign *) stmt)->expr; break; case PLPGSQL_STMT_PERFORM: expr = ((PLpgSQL_stmt_perform *) stmt)->expr; break; case PLPGSQL_STMT_CALL: expr = ((PLpgSQL_stmt_call *) stmt)->expr; break; #if PG_VERSION_NUM < 140000 case PLPGSQL_STMT_SET: expr = ((PLpgSQL_stmt_set *) stmt)->expr; break; #endif /* PG_VERSION_NUM < 140000 */ case PLPGSQL_STMT_IF: expr = ((PLpgSQL_stmt_if *) stmt)->cond; break; case PLPGSQL_STMT_CASE: expr = ((PLpgSQL_stmt_case *) stmt)->t_expr; break; case PLPGSQL_STMT_WHILE: expr = ((PLpgSQL_stmt_while *) stmt)->cond; break; case PLPGSQL_STMT_FORC: expr = ((PLpgSQL_stmt_forc *) stmt)->argquery; break; case PLPGSQL_STMT_FORS: expr = ((PLpgSQL_stmt_fors *) stmt)->query; break; case PLPGSQL_STMT_DYNFORS: expr = ((PLpgSQL_stmt_dynfors *) stmt)->query; *params = ((PLpgSQL_stmt_dynfors *) stmt)->params; *dynamic = true; break; case PLPGSQL_STMT_FOREACH_A: expr = ((PLpgSQL_stmt_foreach_a *) stmt)->expr; break; case PLPGSQL_STMT_FETCH: expr = ((PLpgSQL_stmt_fetch *) stmt)->expr; break; case PLPGSQL_STMT_EXIT: expr = ((PLpgSQL_stmt_exit *) stmt)->cond; break; case PLPGSQL_STMT_RETURN: expr = ((PLpgSQL_stmt_return *) stmt)->expr; break; case PLPGSQL_STMT_RETURN_NEXT: expr = ((PLpgSQL_stmt_return_next *) stmt)->expr; break; case PLPGSQL_STMT_RETURN_QUERY: { PLpgSQL_stmt_return_query *q; q = (PLpgSQL_stmt_return_query *) stmt; if (q->query) expr = q->query; else { expr = q->dynquery; *params = q->params; *dynamic = true; } } break; case PLPGSQL_STMT_ASSERT: expr = ((PLpgSQL_stmt_assert *) stmt)->cond; break; case PLPGSQL_STMT_EXECSQL: expr = ((PLpgSQL_stmt_execsql *) stmt)->sqlstmt; break; case PLPGSQL_STMT_DYNEXECUTE: expr = ((PLpgSQL_stmt_dynexecute *) stmt)->query; *params = ((PLpgSQL_stmt_dynexecute *) stmt)->params; *dynamic = true; break; case PLPGSQL_STMT_OPEN: { PLpgSQL_stmt_open *o; o = (PLpgSQL_stmt_open *) stmt; if (o->query) expr = o->query; else if (o->dynquery) { expr = o->dynquery; *params = o->params; *dynamic = true; } else expr = o->argquery; } case PLPGSQL_STMT_BLOCK: case PLPGSQL_STMT_COMMIT: case PLPGSQL_STMT_ROLLBACK: case PLPGSQL_STMT_GETDIAG: case PLPGSQL_STMT_LOOP: case PLPGSQL_STMT_FORI: case PLPGSQL_STMT_RAISE: case PLPGSQL_STMT_CLOSE: break; } return expr; } static pc_queryid profiler_get_dyn_queryid(PLpgSQL_execstate *estate, PLpgSQL_expr *expr, query_params *qparams) { MemoryContext oldcxt; Query *query; RawStmt *parsetree; bool snapshot_set; List *parsetree_list; PLpgSQL_var result; PLpgSQL_type typ; char *query_string = NULL; Oid *paramtypes = NULL; int nparams = 0; if (qparams) { paramtypes = qparams->paramtypes; nparams = qparams->nparams; } memset(&result, 0, sizeof(result)); memset(&typ, 0, sizeof(typ)); result.dtype = PLPGSQL_DTYPE_VAR; result.refname = "*auxstorage*"; result.datatype = &typ; typ.typoid = TEXTOID; typ.ttype = PLPGSQL_TTYPE_SCALAR; typ.typlen = -1; typ.typbyval = false; typ.typtype = 'b'; if (profiler_queryid_mcxt == NULL) profiler_queryid_mcxt = AllocSetContextCreate(TopMemoryContext, "plpgsql_check - profiler queryid context", ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); oldcxt = MemoryContextSwitchTo(profiler_queryid_mcxt); MemoryContextSwitchTo(oldcxt); profiler_plugin2.assign_expr(estate, (PLpgSQL_datum *) &result, expr); query_string = TextDatumGetCString(result.value); /* * Do basic parsing of the query or queries (this should be safe even if * we are in aborted transaction state!) */ parsetree_list = pg_parse_query(query_string); /* * There should not be more than one query, silently ignore rather than * error out in that case. */ if (list_length(parsetree_list) > 1) { MemoryContextSwitchTo(oldcxt); MemoryContextReset(profiler_queryid_mcxt); return NOQUERYID; } /* Run through the raw parsetree and process it. */ parsetree = (RawStmt *) linitial(parsetree_list); snapshot_set = false; /* * Set up a snapshot if parse analysis/planning will need one. */ if (analyze_requires_snapshot(parsetree)) { PushActiveSnapshot(GetTransactionSnapshot()); snapshot_set = true; } query = parse_analyze_fixedparams(parsetree, query_string, paramtypes, nparams, NULL); if (snapshot_set) PopActiveSnapshot(); MemoryContextSwitchTo(oldcxt); MemoryContextReset(profiler_queryid_mcxt); return query->queryId; } /* * Returns result type of already executed (has assigned plan) expression */ static bool get_expr_type(PLpgSQL_expr *expr, Oid *result_type) { if (expr) { SPIPlanPtr ptr = expr->plan; if (ptr) { List *plan_sources = SPI_plan_get_plan_sources(ptr); if (plan_sources && list_length(plan_sources) == 1) { CachedPlanSource *plan_source; TupleDesc tupdesc; plan_source = (CachedPlanSource *) linitial(plan_sources); tupdesc = plan_source->resultDesc; if (tupdesc->natts == 1) { *result_type = TupleDescAttr(tupdesc, 0)->atttypid; return true; } } } } return false; } /* Return the first queryid found in the given PLpgSQL_stmt, if any. */ static pc_queryid profiler_get_queryid(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt, bool *has_queryid, query_params **qparams) { PLpgSQL_expr *expr; bool dynamic; List *params; List *plan_sources; expr = profiler_get_expr(stmt, &dynamic, ¶ms); *has_queryid = (expr != NULL); /* fast leaving, when expression has not assigned plan */ if (!expr || !expr->plan) return NOQUERYID; if (dynamic) { Assert(expr); if (params && !*qparams) { query_params *qps = NULL; int nparams = list_length(params); int paramno = 0; MemoryContext oldcxt; ListCell *lc; /* build array of Oid used like dynamic query parameters */ oldcxt = MemoryContextSwitchTo(profiler_mcxt); qps = (query_params *) palloc(sizeof(Oid) * nparams + sizeof(int)); MemoryContextSwitchTo(oldcxt); foreach(lc, params) { PLpgSQL_expr *param_expr = (PLpgSQL_expr *) lfirst(lc); if (!get_expr_type(param_expr, &qps->paramtypes[paramno++])) { free(qps); return NOQUERYID; } } qps->nparams = nparams; *qparams = qps; } return profiler_get_dyn_queryid(estate, expr, *qparams); } plan_sources = SPI_plan_get_plan_sources(expr->plan); if (plan_sources) { CachedPlanSource *plan_source = (CachedPlanSource *) linitial(plan_sources); if (plan_source->query_list) { Query *q = linitial_node(Query, plan_source->query_list); return q->queryId; } } return NOQUERYID; } /* * Generate simple queryid for testing purpose. * DO NOT USE IN PRODUCTION. */ #if PG_VERSION_NUM >= 140000 static void profiler_fake_queryid_hook(ParseState *pstate, Query *query, JumbleState *jstate) { (void) jstate; #else static void profiler_fake_queryid_hook(ParseState *pstate, Query *query) { #endif (void) pstate; Assert(query->queryId == NOQUERYID); query->queryId = query->commandType; } /* * Prepare tuplestore with function profile * */ void plpgsql_check_iterate_over_profile(plpgsql_check_info *cinfo, profiler_stmt_walker_mode mode, plpgsql_check_result_info *ri, coverage_state *cs) { LOCAL_FCINFO(fake_fcinfo, 0); PLpgSQL_function *func; FmgrInfo flinfo; TriggerData trigdata; EventTriggerData etrigdata; Trigger tg_trigger; ReturnSetInfo rsinfo; bool fake_rtd; profiler_info pinfo; profiler_stmt_chunk *first_chunk = NULL; profiler_iterator pi; volatile bool unlock_mutex = false; bool shared_chunks; profiler_stmt_walker_options opts; memset(&opts, 0, sizeof(profiler_stmt_walker_options)); memset(&pi, 0, sizeof(profiler_iterator)); pi.key.fn_oid = cinfo->fn_oid; pi.key.db_oid = MyDatabaseId; pi.key.fn_xmin = HeapTupleHeaderGetRawXmin(cinfo->proctuple->t_data); pi.key.fn_tid = cinfo->proctuple->t_self; pi.key.chunk_num = 1; pi.ri = ri; /* try to find first chunk in shared (or local) memory */ if (shared_profiler_chunks_HashTable) { LWLockAcquire(profiler_ss->lock, LW_SHARED); pi.chunks = shared_profiler_chunks_HashTable; shared_chunks = true; } else { pi.chunks = profiler_chunks_HashTable; shared_chunks = false; } pi.current_chunk = first_chunk = (profiler_stmt_chunk *) hash_search(pi.chunks, (void *) &pi.key, HASH_FIND, NULL); PG_TRY(); { if (shared_chunks && first_chunk) { SpinLockAcquire(&first_chunk->mutex); unlock_mutex = true; } plpgsql_check_setup_fcinfo(cinfo, &flinfo, fake_fcinfo, &rsinfo, &trigdata, &etrigdata, &tg_trigger, &fake_rtd); func = plpgsql_check__compile_p(fake_fcinfo, false); opts.stmtid_map = plpgsql_check_get_stmtid_map(func); opts.stmts_info = plpgsql_check_get_stmts_info(func); opts.pi = π opts.cs = cs; pinfo.func = func; pinfo.nstatements = 0; pinfo.stmts = NULL; profiler_stmt_walker(&pinfo, mode, (PLpgSQL_stmt *) func->action, NULL, NULL, 1, &opts); pfree(opts.stmtid_map); pfree(opts.stmts_info); } PG_CATCH(); { if (unlock_mutex) SpinLockRelease(&first_chunk->mutex); PG_RE_THROW(); } PG_END_TRY(); if (unlock_mutex) SpinLockRelease(&first_chunk->mutex); if (shared_chunks) LWLockRelease(profiler_ss->lock); } /* * Prepare tuplestore with function profile * */ void plpgsql_check_profiler_show_profile(plpgsql_check_result_info *ri, plpgsql_check_info *cinfo) { profiler_hashkey hk; bool found; HTAB *chunks; bool shared_chunks; volatile profiler_stmt_chunk *first_chunk = NULL; volatile bool unlock_mutex = false; /* ensure correct complete content of hash key */ memset(&hk, 0, sizeof(profiler_hashkey)); hk.fn_oid = cinfo->fn_oid; hk.db_oid = MyDatabaseId; hk.fn_xmin = HeapTupleHeaderGetRawXmin(cinfo->proctuple->t_data); hk.fn_tid = cinfo->proctuple->t_self; hk.chunk_num = 1; /* try to find first chunk in shared (or local) memory */ if (shared_profiler_chunks_HashTable) { LWLockAcquire(profiler_ss->lock, LW_SHARED); chunks = shared_profiler_chunks_HashTable; shared_chunks = true; } else { chunks = profiler_chunks_HashTable; shared_chunks = false; } PG_TRY(); { char *prosrc = cinfo->src; profiler_stmt_chunk *chunk = NULL; int lineno = 1; int current_statement = 0; chunk = (profiler_stmt_chunk *) hash_search(chunks, (void *) &hk, HASH_FIND, &found); if (shared_chunks && chunk) { first_chunk = chunk; SpinLockAcquire(&first_chunk->mutex); unlock_mutex = true; } /* iterate over source code rows */ while (*prosrc) { char *lineend = NULL; char *linebeg = NULL; int stmt_lineno = -1; int64 us_total = 0; int64 exec_count = 0; int64 exec_count_err = 0; Datum queryids_array = (Datum) 0; Datum max_time_array = (Datum) 0; Datum processed_rows_array = (Datum) 0; int cmds_on_row = 0; lineend = prosrc; linebeg = prosrc; /* find lineend */ while (*lineend != '\0' && *lineend != '\n') lineend += 1; if (*lineend == '\n') { *lineend = '\0'; prosrc = lineend + 1; } else prosrc = lineend; if (chunk) { ArrayBuildState *queryids_abs = NULL; ArrayBuildState *max_time_abs = NULL; ArrayBuildState *processed_rows_abs = NULL; int queryids_on_row = 0; queryids_abs = initArrayResult(INT8OID, CurrentMemoryContext, true); max_time_abs = initArrayResult(FLOAT8OID, CurrentMemoryContext, true); processed_rows_abs = initArrayResult(INT8OID, CurrentMemoryContext, true); /* process all statements on this line */ for(;;) { /* ensure so access to chunks is correct */ if (current_statement >= STATEMENTS_PER_CHUNK) { hk.chunk_num += 1; chunk = (profiler_stmt_chunk *) hash_search(chunks, (void *) &hk, HASH_FIND, &found); if (!found) { chunk = NULL; break; } current_statement = 0; } Assert(chunk != NULL); /* skip invisible statements if any */ if (chunk->stmts[current_statement].lineno < lineno) { current_statement += 1; continue; } else if (chunk->stmts[current_statement].lineno == lineno) { profiler_stmt_reduced *prstmt = &chunk->stmts[current_statement]; us_total += prstmt->us_total; exec_count += prstmt->exec_count; exec_count_err += prstmt->exec_count_err; stmt_lineno = lineno; if (prstmt->has_queryid) { if (prstmt->queryid != NOQUERYID) { queryids_abs = accumArrayResult(queryids_abs, Int64GetDatum((int64) prstmt->queryid), prstmt->queryid == NOQUERYID, INT8OID, CurrentMemoryContext); queryids_on_row += 1; } } max_time_abs = accumArrayResult(max_time_abs, Float8GetDatum(prstmt->us_max / 1000.0), false, FLOAT8OID, CurrentMemoryContext); processed_rows_abs = accumArrayResult(processed_rows_abs, Int64GetDatum(prstmt->rows), false, INT8OID, CurrentMemoryContext); cmds_on_row += 1; current_statement += 1; continue; } else break; } if (queryids_on_row > 0) queryids_array = makeArrayResult(queryids_abs, CurrentMemoryContext); if (cmds_on_row > 0) { max_time_array = makeArrayResult(max_time_abs, CurrentMemoryContext); processed_rows_array = makeArrayResult(processed_rows_abs, CurrentMemoryContext); } } plpgsql_check_put_profile(ri, queryids_array, lineno, stmt_lineno, cmds_on_row, exec_count, exec_count_err, us_total, max_time_array, processed_rows_array, (char *) linebeg); lineno += 1; } } PG_CATCH(); { if (unlock_mutex) SpinLockRelease(&first_chunk->mutex); PG_RE_THROW(); } PG_END_TRY(); if (unlock_mutex) SpinLockRelease(&first_chunk->mutex); if (shared_chunks) LWLockRelease(profiler_ss->lock); } /* * Try to search profile pattern for function. Creates profile pattern when * it doesn't exists. */ static void profiler_func_setup(PLpgSQL_execstate *estate, PLpgSQL_function *func, void **plugin2_info) { if (plpgsql_check_profiler && OidIsValid(func->fn_oid)) { profiler_info *pinfo; pinfo = palloc0(sizeof(profiler_info)); pinfo->nstatements = func->nstatements; pinfo->stmts = palloc0(func->nstatements * sizeof(profiler_stmt)); INSTR_TIME_SET_CURRENT(pinfo->start_time); pinfo->func = func; *plugin2_info = pinfo; } } static void _profiler_func_end(profiler_info *pinfo, Oid fn_oid, bool is_aborted) { int entry_stmtid; instr_time end_time; uint64 elapsed; profiler_stmt_walker_options opts; int *stmtid_map; Assert(pinfo); Assert(pinfo->func); Assert(pinfo->func->fn_oid == fn_oid); entry_stmtid = pinfo->func->action->stmtid - 1; memset(&opts, 0, sizeof(profiler_stmt_walker_options)); INSTR_TIME_SET_CURRENT(end_time); INSTR_TIME_SUBTRACT(end_time, pinfo->start_time); elapsed = INSTR_TIME_GET_MICROSEC(end_time); if (pinfo->stmts[entry_stmtid].exec_count == 0) { pinfo->stmts[entry_stmtid].exec_count = 1; pinfo->stmts[entry_stmtid].exec_count_err = is_aborted ? 1 : 0; pinfo->stmts[entry_stmtid].us_total = elapsed; pinfo->stmts[entry_stmtid].us_max = elapsed; } stmtid_map = plpgsql_check_get_current_stmtid_map(); opts.stmts_info = plpgsql_check_get_current_stmts_info(); opts.stmtid_map = stmtid_map; /* finalize profile - get result profile */ profiler_stmt_walker(pinfo, PLPGSQL_CHECK_STMT_WALKER_COUNT_EXEC_TIME, (PLpgSQL_stmt *) pinfo->func->action, NULL, NULL, 1, &opts); update_persistent_profile(pinfo, pinfo->func, stmtid_map); update_persistent_fstats(pinfo->func, elapsed); } static void profiler_func_end(PLpgSQL_execstate *estate, PLpgSQL_function *func, void **plugin2_info) { profiler_info *pinfo = *plugin2_info; if (!pinfo) return; Assert(pinfo->func == func); _profiler_func_end(pinfo, func->fn_oid, false); } static void profiler_func_end_aborted(Oid fn_oid, void **plugin2_info) { profiler_info *pinfo = *plugin2_info; if (!pinfo) return; _profiler_func_end(pinfo, fn_oid, true); } static void profiler_stmt_beg(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt, void **plugin2_info) { profiler_info *pinfo = (profiler_info *) *plugin2_info; if (pinfo) { INSTR_TIME_SET_CURRENT(pinfo->stmts[stmt->stmtid - 1].start_time); } } static void _profiler_stmt_end(profiler_stmt *pstmt, bool is_aborted) { instr_time end_time; uint64 elapsed; instr_time end_time2; INSTR_TIME_SET_CURRENT(end_time); end_time2 = end_time; INSTR_TIME_ACCUM_DIFF(pstmt->total, end_time, pstmt->start_time); INSTR_TIME_SUBTRACT(end_time2, pstmt->start_time); elapsed = INSTR_TIME_GET_MICROSEC(end_time2); if (elapsed > pstmt->us_max) pstmt->us_max = elapsed; pstmt->us_total = INSTR_TIME_GET_MICROSEC(pstmt->total); pstmt->exec_count_err += is_aborted ? 1 : 0; pstmt->exec_count++; } /* * Cleaning mode is used for closing unfinished statements after an exception. */ static void profiler_stmt_end(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt, void **plugin2_info) { profiler_info *pinfo = *plugin2_info; if (pinfo) { profiler_stmt *pstmt = &pinfo->stmts[stmt->stmtid - 1]; /* * We can get query id only if stmt_end is not executed * in cleaning mode, because we need to execute expression */ if (pstmt->queryid == NOQUERYID) pstmt->queryid = profiler_get_queryid(estate, stmt, &pstmt->has_queryid, &pstmt->qparams); _profiler_stmt_end(pstmt, false); } } static void profiler_stmt_end_aborted(Oid fn_oid, int stmtid, void **plugin2_info) { profiler_info *pinfo = *plugin2_info; if (pinfo) { profiler_stmt *pstmt = &pinfo->stmts[stmtid - 1]; _profiler_stmt_end(pstmt, true); } } void plpgsql_check_profiler_iterate_over_all_profiles(plpgsql_check_result_info *ri) { HASH_SEQ_STATUS seqstatus; fstats *fstats_item; HTAB *fstats_ht; bool htab_is_shared; /* try to find first chunk in shared (or local) memory */ if (shared_fstats_HashTable) { LWLockAcquire(profiler_ss->fstats_lock, LW_SHARED); fstats_ht = shared_fstats_HashTable; htab_is_shared = true; } else { fstats_ht = fstats_HashTable; htab_is_shared = false; } hash_seq_init(&seqstatus, fstats_ht); while ((fstats_item = (fstats *) hash_seq_search(&seqstatus)) != NULL) { Oid fn_oid, db_oid; uint64 exec_count, exec_count_err, total_time, min_time, max_time; float8 total_time_xx; HeapTuple tp; if (htab_is_shared) SpinLockAcquire(&fstats_item->mutex); fn_oid = fstats_item->key.fn_oid; db_oid = fstats_item->key.db_oid; exec_count = fstats_item->exec_count; exec_count_err = fstats_item->exec_count_err; total_time = fstats_item->total_time; total_time_xx = fstats_item->total_time_xx; min_time = fstats_item->min_time; max_time = fstats_item->max_time; if (htab_is_shared) SpinLockRelease(&fstats_item->mutex); /* * only function's statistics for current database can be displayed here, * Oid of functions from other databases has unassigned oids to current * system catalogue. */ if (db_oid != MyDatabaseId) continue; /* check if function has name */ tp = SearchSysCache1(PROCOID, ObjectIdGetDatum(fn_oid)); if (!HeapTupleIsValid(tp)) continue; ReleaseSysCache(tp); plpgsql_check_put_profiler_functions_all_tb(ri, fn_oid, exec_count, exec_count_err, (double) total_time, ceil(total_time / ((double) exec_count)), ceil(sqrt(total_time_xx / exec_count)), (double) min_time, (double) max_time); } if (htab_is_shared) LWLockRelease(profiler_ss->fstats_lock); } /* * Register plpgsql plugin2 for profiler */ void plpgsql_check_profiler_init(void) { plpgsql_check_register_pldbgapi2_plugin(&profiler_plugin2); } plpgsql_check-2.7.8/src/report.c000066400000000000000000000265511465410532300166310ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * report.c * * last stage checks * * by Pavel Stehule 2013-2024 * *------------------------------------------------------------------------- */ #include "plpgsql_check.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" static bool datum_is_used(PLpgSQL_checkstate *cstate, int dno, bool write); static bool datum_is_explicit(PLpgSQL_checkstate *cstate, int dno); /* * Returns true, when variable is internal (automatic) * */ static bool is_internal(char *refname, int lineno) { if (lineno <= 0) return true; if (refname == NULL) return true; if (strcmp(refname, "*internal*") == 0) return true; if (strcmp(refname, "(unnamed row)") == 0) return true; return false; } bool is_internal_variable(PLpgSQL_checkstate *cstate, PLpgSQL_variable *var) { if (bms_is_member(var->dno, cstate->auto_variables)) return true; return is_internal(var->refname, var->lineno); } /* * returns refname of PLpgSQL_datum. When refname is generated, * then return null too, although refname is not null. */ char * plpgsql_check_datum_get_refname(PLpgSQL_checkstate *cstate, PLpgSQL_datum *d) { char *refname; int lineno; switch (d->dtype) { case PLPGSQL_DTYPE_VAR: refname = ((PLpgSQL_var *) d)->refname; lineno = ((PLpgSQL_var *) d)->lineno; break; case PLPGSQL_DTYPE_ROW: refname = ((PLpgSQL_row *) d)->refname; lineno = ((PLpgSQL_row *) d)->lineno; break; case PLPGSQL_DTYPE_REC: refname = ((PLpgSQL_rec *) d)->refname; lineno = ((PLpgSQL_rec *) d)->lineno; break; default: refname = NULL; lineno = -1; } /* * This routine is used for shadowing check. * We would to check auto variables too */ if (bms_is_member(d->dno, cstate->auto_variables)) return refname; /* * PostgreSQL 12 started use "(unnamed row)" name for internal * variables. Hide this name too (lineno is -1). */ if (is_internal(refname, lineno)) return NULL; return refname; } /* * Returns true if dno is explicitly declared. It should not be used * for arguments. */ bool datum_is_explicit(PLpgSQL_checkstate *cstate, int dno) { PLpgSQL_execstate *estate = cstate->estate; if (bms_is_member(dno, cstate->auto_variables)) return false; switch (estate->datums[dno]->dtype) { case PLPGSQL_DTYPE_VAR: { PLpgSQL_variable *var = (PLpgSQL_variable *) estate->datums[dno]; return !is_internal(var->refname, var->lineno); } case PLPGSQL_DTYPE_ROW: { PLpgSQL_row *row = (PLpgSQL_row *) estate->datums[dno]; return !is_internal(row->refname, row->lineno); } case PLPGSQL_DTYPE_REC: { PLpgSQL_rec *rec = (PLpgSQL_rec *) estate->datums[dno]; return !is_internal(rec->refname, rec->lineno); } default: return false; } } /* * returns true, when datum or some child is used */ static bool datum_is_used(PLpgSQL_checkstate *cstate, int dno, bool write) { PLpgSQL_execstate *estate = cstate->estate; switch (estate->datums[dno]->dtype) { case PLPGSQL_DTYPE_VAR: { return bms_is_member(dno, write ? cstate->modif_variables : cstate->used_variables); } break; case PLPGSQL_DTYPE_ROW: { PLpgSQL_row *row = (PLpgSQL_row *) estate->datums[dno]; int i; if (bms_is_member(dno, write ? cstate->modif_variables : cstate->used_variables)) return true; for (i = 0; i < row->nfields; i++) { if (row->varnos[i] < 0) continue; if (datum_is_used(cstate, row->varnos[i], write)) return true; } return false; } break; case PLPGSQL_DTYPE_REC: { PLpgSQL_rec *rec = (PLpgSQL_rec *) estate->datums[dno]; int i; if (bms_is_member(dno, write ? cstate->modif_variables : cstate->used_variables)) return true; /* search any used recfield with related recparentno */ for (i = 0; i < estate->ndatums; i++) { if (estate->datums[i]->dtype == PLPGSQL_DTYPE_RECFIELD) { PLpgSQL_recfield *recfield = (PLpgSQL_recfield *) estate->datums[i]; if (recfield->recparentno == rec->dno && datum_is_used(cstate, i, write)) return true; } } } break; case PLPGSQL_DTYPE_RECFIELD: return bms_is_member(dno, write ? cstate->modif_variables : cstate->used_variables); default: return false; } return false; } /* * Reports all unused variables explicitly DECLAREd by the user. Ignores * special variables created by PL/PgSQL. */ void plpgsql_check_report_unused_variables(PLpgSQL_checkstate *cstate) { int i; PLpgSQL_execstate *estate = cstate->estate; /* now, there are no active plpgsql statement */ estate->err_stmt = NULL; for (i = 0; i < estate->ndatums; i++) if (datum_is_explicit(cstate, i) && !(datum_is_used(cstate, i, false) || datum_is_used(cstate, i, true))) { PLpgSQL_variable *var = (PLpgSQL_variable *) estate->datums[i]; StringInfoData message; initStringInfo(&message); appendStringInfo(&message, UNUSED_VARIABLE_TEXT, var->refname); plpgsql_check_put_error(cstate, 0, var->lineno, message.data, NULL, NULL, PLPGSQL_CHECK_WARNING_OTHERS, 0, NULL, NULL); pfree(message.data); message.data = NULL; } if (cstate->cinfo->extra_warnings) { PLpgSQL_function *func = estate->func; /* check never read variables */ for (i = 0; i < estate->ndatums; i++) { if (datum_is_explicit(cstate, i) && !datum_is_used(cstate, i, false) && datum_is_used(cstate, i, true)) { PLpgSQL_variable *var = (PLpgSQL_variable *) estate->datums[i]; StringInfoData message; initStringInfo(&message); appendStringInfo(&message, NEVER_READ_VARIABLE_TEXT, var->refname); plpgsql_check_put_error(cstate, 0, var->lineno, message.data, NULL, NULL, PLPGSQL_CHECK_WARNING_EXTRA, 0, NULL, NULL); pfree(message.data); message.data = NULL; } } /* check IN parameters */ for (i = 0; i < func->fn_nargs; i++) { int varno = func->fn_argvarnos[i]; bool is_read = datum_is_used(cstate, varno, false); bool is_write = datum_is_used(cstate, varno, true); if (!is_read && !is_write) { PLpgSQL_variable *var = (PLpgSQL_variable *) estate->datums[varno]; StringInfoData message; initStringInfo(&message); appendStringInfo(&message, UNUSED_PARAMETER_TEXT, var->refname); plpgsql_check_put_error(cstate, 0, 0, message.data, NULL, NULL, PLPGSQL_CHECK_WARNING_EXTRA, 0, NULL, NULL); pfree(message.data); message.data = NULL; } else if (!is_read) { bool is_inout_procedure_param = false; /* * procedure doesn't support only OUT parameters. Don't raise * warning if INOUT parameter is just modified in procedures. */ is_inout_procedure_param = cstate->cinfo->is_procedure && bms_is_member(varno, cstate->out_variables); if (!is_inout_procedure_param) { PLpgSQL_variable *var = (PLpgSQL_variable *) estate->datums[varno]; StringInfoData message; initStringInfo(&message); appendStringInfo(&message, NEVER_READ_PARAMETER_TEXT, var->refname); plpgsql_check_put_error(cstate, 0, 0, message.data, NULL, NULL, PLPGSQL_CHECK_WARNING_EXTRA, 0, NULL, NULL); pfree(message.data); message.data = NULL; } } } /* are there some OUT parameters (expect modification)? */ if (func->out_param_varno != -1 && !cstate->found_return_query) { int varno = func->out_param_varno; PLpgSQL_variable *var = (PLpgSQL_variable *) estate->datums[varno]; if (var->dtype == PLPGSQL_DTYPE_ROW && is_internal_variable(cstate, var)) { /* this function has more OUT parameters */ PLpgSQL_row *row = (PLpgSQL_row*) var; int fnum; for (fnum = 0; fnum < row->nfields; fnum++) { int varno2 = row->varnos[fnum]; PLpgSQL_variable *var2 = (PLpgSQL_variable *) estate->datums[varno2]; StringInfoData message; if (var2->dtype == PLPGSQL_DTYPE_ROW || var2->dtype == PLPGSQL_DTYPE_REC) { /* * The result of function with more OUT variables (and one * should be an composite), is not possible simply assign to * outer variables. The related expression cannot be "simple" * expression, and then an evaluation is 10x slower. So there * is warning */ initStringInfo(&message); appendStringInfo(&message, OUT_COMPOSITE_IS_NOT_SINGLE_TEXT, var2->refname); plpgsql_check_put_error(cstate, 0, 0, message.data, NULL, NULL, PLPGSQL_CHECK_WARNING_EXTRA, 0, NULL, NULL); pfree(message.data); message.data = NULL; } if (!datum_is_used(cstate, varno2, true)) { const char *fmt = cstate->found_return_dyn_query ? MAYBE_UNMODIFIED_VARIABLE_TEXT : UNMODIFIED_VARIABLE_TEXT; const char *detail = cstate->found_return_dyn_query ? "cannot to determine result of dynamic SQL" : NULL; initStringInfo(&message); appendStringInfo(&message, fmt, var2->refname); plpgsql_check_put_error(cstate, 0, 0, message.data, detail, NULL, PLPGSQL_CHECK_WARNING_EXTRA, 0, NULL, NULL); pfree(message.data); message.data = NULL; } } } else { if (!datum_is_used(cstate, varno, true)) { StringInfoData message; const char *fmt = cstate->found_return_dyn_query ? MAYBE_UNMODIFIED_VARIABLE_TEXT : UNMODIFIED_VARIABLE_TEXT; const char *detail = cstate->found_return_dyn_query ? "cannot to determine result of dynamic SQL" : NULL; initStringInfo(&message); appendStringInfo(&message, fmt, var->refname); plpgsql_check_put_error(cstate, 0, 0, message.data, detail, NULL, PLPGSQL_CHECK_WARNING_EXTRA, 0, NULL, NULL); pfree(message.data); message.data = NULL; } } } } } /* * Report too high volatility * */ void plpgsql_check_report_too_high_volatility(PLpgSQL_checkstate *cstate) { if (cstate->cinfo->performance_warnings && !cstate->skip_volatility_check) { char *current = NULL; char *should_be = NULL; bool raise_warning = false; if (cstate->volatility == PROVOLATILE_IMMUTABLE && (cstate->decl_volatility == PROVOLATILE_VOLATILE || cstate->decl_volatility == PROVOLATILE_STABLE)) { should_be = "IMMUTABLE"; current = cstate->decl_volatility == PROVOLATILE_VOLATILE ? "VOLATILE" : "STABLE"; raise_warning = true; } else if (cstate->volatility == PROVOLATILE_STABLE && (cstate->decl_volatility == PROVOLATILE_VOLATILE)) { if (cstate->cinfo->rettype != VOIDOID) { should_be = "STABLE"; current = "VOLATILE"; raise_warning = true; } } if (raise_warning) { StringInfoData message; initStringInfo(&message); appendStringInfo(&message, "routine is marked as %s, should be %s", current, should_be); plpgsql_check_put_error(cstate, 0, -1, message.data, cstate->has_execute_stmt ? "attention: cannot to determine volatility of used dynamic SQL" : NULL, "When you fix this issue, please, recheck other functions that uses this function.", PLPGSQL_CHECK_WARNING_PERFORMANCE, 0, NULL, NULL); pfree(message.data); message.data = NULL; } } } plpgsql_check-2.7.8/src/stmtwalk.c000066400000000000000000001562561465410532300171720ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * stmtwalk.c * * iteration over plpgsql statements loop * * by Pavel Stehule 2013-2024 * *------------------------------------------------------------------------- */ #include "plpgsql_check.h" #include "access/tupconvert.h" #include "catalog/pg_collation.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "nodes/nodeFuncs.h" #include "nodes/value.h" #include "parser/parse_node.h" #include "parser/parser.h" #include "common/keywords.h" static void check_stmts(PLpgSQL_checkstate *cstate, List *stmts, int *closing, List **exceptions); static PLpgSQL_stmt_stack_item * push_stmt_to_stmt_stack(PLpgSQL_checkstate *cstate); static void pop_stmt_from_stmt_stack(PLpgSQL_checkstate *cstate); static bool is_any_loop_stmt(PLpgSQL_stmt *stmt); static bool is_inside_exception_handler(PLpgSQL_stmt_stack_item *current); static PLpgSQL_stmt * find_nearest_loop(PLpgSQL_stmt_stack_item *current); static PLpgSQL_stmt * find_stmt_with_label(char *label, PLpgSQL_stmt_stack_item *current); static int possibly_closed(int c); static int merge_closing(int c, int c_local, List **exceptions, List *exceptions_local, int err_code); static bool exception_matches_conditions(int sqlerrstate, PLpgSQL_condition *cond); static bool found_shadowed_variable(char *varname, PLpgSQL_stmt_stack_item *current, PLpgSQL_checkstate *cstate); static void check_dynamic_sql(PLpgSQL_checkstate *cstate, PLpgSQL_stmt *stmt, PLpgSQL_expr *query, bool into, PLpgSQL_variable *target, List *params); static bool is_inside_protected_block(PLpgSQL_stmt_stack_item *current); static void check_variable(PLpgSQL_checkstate *cstate, PLpgSQL_variable *var) { /* leave quickly when var is not defined */ if (var == NULL) return; if (var->dtype == PLPGSQL_DTYPE_ROW) { PLpgSQL_row *row = (PLpgSQL_row *) var; int fnum; for (fnum = 0; fnum < row->nfields; fnum++) { /* skip dropped columns */ if (row->varnos[fnum] < 0) continue; plpgsql_check_target(cstate, row->varnos[fnum], NULL, NULL); } plpgsql_check_record_variable_usage(cstate, row->dno, true); return; } if (var->dtype == PLPGSQL_DTYPE_REC) { PLpgSQL_rec *rec = (PLpgSQL_rec *) var; /* * There are no checks done on records currently; just record that the * variable is not unused. */ plpgsql_check_record_variable_usage(cstate, rec->dno, true); return; } elog(ERROR, "unsupported dtype %d", var->dtype); } bool plpgsql_check_is_reserved_keyword(char *name) { int i; for (i = 0; i < ScanKeywords.num_keywords; i++) { if (ScanKeywordCategories[i] == RESERVED_KEYWORD) { char *value; value = unconstify(char *, GetScanKeyword(i, &ScanKeywords)); if (strcmp(name, value) == 0) return true; } } return false; } /* * does warning checks - variable shadowing, function's argument shadowing and * using keywords as variable's name */ static void check_variable_name(PLpgSQL_checkstate *cstate, PLpgSQL_stmt_stack_item *outer_stmt_stack, int dno) { char *refname; PLpgSQL_datum *d = cstate->estate->func->datums[dno]; refname = plpgsql_check_datum_get_refname(cstate, d); if (refname != NULL) { ListCell *l; bool is_auto = bms_is_member(d->dno, cstate->auto_variables); if (plpgsql_check_is_reserved_keyword(refname)) { StringInfoData str; initStringInfo(&str); appendStringInfo(&str, "name of variable \"%s\" is reserved keyword", refname); plpgsql_check_put_error(cstate, 0, 0, str.data, "The reserved keyword was used as variable name.", NULL, PLPGSQL_CHECK_WARNING_OTHERS, 0, NULL, NULL); pfree(str.data); } foreach(l, cstate->argnames) { char *argname = (char *) lfirst(l); if (strcmp(argname, refname) == 0) { StringInfoData str; initStringInfo(&str); appendStringInfo(&str, "parameter \"%s\" is shadowed", refname); plpgsql_check_put_error(cstate, 0, 0, str.data, is_auto ? "Local auto variable shadows function parameter." : "Local variable shadows function parameter.", NULL, PLPGSQL_CHECK_WARNING_OTHERS, 0, NULL, NULL); pfree(str.data); } } if (found_shadowed_variable(refname, outer_stmt_stack, cstate)) { StringInfoData str; initStringInfo(&str); appendStringInfo(&str, "%svariable \"%s\" shadows a previously defined variable", is_auto ? "auto " : "", refname); plpgsql_check_put_error(cstate, 0, 0, str.data, NULL, "SET plpgsql.extra_warnings TO 'shadowed_variables'", PLPGSQL_CHECK_WARNING_EXTRA, 0, NULL, NULL); pfree(str.data); } } } /* * walk over all plpgsql statements - search and check expressions * */ void plpgsql_check_stmt(PLpgSQL_checkstate *cstate, PLpgSQL_stmt *stmt, int *closing, List **exceptions) { TupleDesc tupdesc = NULL; PLpgSQL_function *func; ResourceOwner oldowner; MemoryContext oldCxt = CurrentMemoryContext; PLpgSQL_stmt_stack_item *outer_stmt_stack; plpgsql_check_pragma_vector pragma_vector; if (stmt == NULL) return; if (cstate->stop_check) return; cstate->estate->err_stmt = stmt; cstate->was_pragma = false; func = cstate->estate->func; pragma_vector = cstate->pragma_vector; /* * Attention - returns NULL, when there are not any outer level */ outer_stmt_stack = push_stmt_to_stmt_stack(cstate); oldowner = CurrentResourceOwner; BeginInternalSubTransaction(NULL); MemoryContextSwitchTo(oldCxt); PG_TRY(); { switch (stmt->cmd_type) { case PLPGSQL_STMT_BLOCK: { PLpgSQL_stmt_block *stmt_block = (PLpgSQL_stmt_block *) stmt; int i; for (i = 0; i < stmt_block->n_initvars; i++) { PLpgSQL_datum *d = func->datums[stmt_block->initvarnos[i]]; if (d->dtype == PLPGSQL_DTYPE_VAR || d->dtype == PLPGSQL_DTYPE_ROW || d->dtype == PLPGSQL_DTYPE_REC) { PLpgSQL_variable *var = (PLpgSQL_variable *) d; StringInfoData str; initStringInfo(&str); appendStringInfo(&str, "during statement block local variable \"%s\" initialization on line %d", var->refname, var->lineno); cstate->estate->err_text = str.data; if (var->default_val) plpgsql_check_assignment(cstate, var->default_val, NULL, NULL, var->dno); cstate->estate->err_text = NULL; pfree(str.data); } check_variable_name(cstate, outer_stmt_stack, d->dno); } check_variable_name(cstate, outer_stmt_stack, cstate->estate->found_varno); check_stmts(cstate, stmt_block->body, closing, exceptions); if (stmt_block->exceptions) { int closing_local; List *exceptions_local = NIL; int closing_handlers = PLPGSQL_CHECK_UNKNOWN; List *exceptions_transformed = NIL; cstate->top_stmt_stack->is_exception_handler = true; if (*closing == PLPGSQL_CHECK_CLOSED_BY_EXCEPTIONS) { ListCell *lc; ListCell *l; int errn = 0; int *err_codes = NULL; int nerr_codes = 0; /* copy errcodes to a array */ nerr_codes = list_length(*exceptions); err_codes = palloc(sizeof(int) * nerr_codes); foreach(lc, *exceptions) { err_codes[errn++] = lfirst_int(lc); } foreach(l, stmt_block->exceptions->exc_list) { PLpgSQL_exception *exception = (PLpgSQL_exception *) lfirst(l); /* RETURN in exception handler ~ is possible closing */ check_stmts(cstate, exception->action, &closing_local, &exceptions_local); if (*exceptions != NIL) { int idx; for (idx = 0; idx < nerr_codes; idx++) { int err_code = err_codes[idx]; if (err_code != -1 && exception_matches_conditions(err_code, exception->conditions)) { closing_handlers = merge_closing(closing_handlers, closing_local, &exceptions_transformed, exceptions_local, err_code); *exceptions = list_delete_int(*exceptions, err_code); err_codes[idx] = -1; } } } } Assert(err_codes != NULL); pfree(err_codes); if (closing_handlers != PLPGSQL_CHECK_UNKNOWN) { *closing = closing_handlers; if (closing_handlers == PLPGSQL_CHECK_CLOSED_BY_EXCEPTIONS) *exceptions = list_concat_unique_int(*exceptions, exceptions_transformed); else *exceptions = NIL; } } else { ListCell *l; closing_handlers = *closing; foreach(l, stmt_block->exceptions->exc_list) { PLpgSQL_exception *exception = (PLpgSQL_exception *) lfirst(l); /* RETURN in exception handler ~ it is possible closing only */ check_stmts(cstate, exception->action, &closing_local, &exceptions_local); closing_handlers = merge_closing(closing_handlers, closing_local, &exceptions_transformed, exceptions_local, -1); } *closing = closing_handlers; if (closing_handlers == PLPGSQL_CHECK_CLOSED_BY_EXCEPTIONS) *exceptions = exceptions_transformed; else *exceptions = NIL; } /* * Mark the hidden variables SQLSTATE and SQLERRM used * even if they actually weren't. Not using them * should practically never be a sign of a problem, so * there's no point in annoying the user. */ plpgsql_check_record_variable_usage(cstate, stmt_block->exceptions->sqlstate_varno, false); plpgsql_check_record_variable_usage(cstate, stmt_block->exceptions->sqlerrm_varno, false); } } break; case PLPGSQL_STMT_ASSERT: { PLpgSQL_stmt_assert *stmt_assert = (PLpgSQL_stmt_assert *) stmt; /* * Should or should not to depends on plpgsql_check_asserts? * I am thinking, so any code (active or inactive) should be valid, * so I ignore plpgsql_check_asserts option. */ plpgsql_check_expr_with_scalar_type(cstate, stmt_assert->cond, BOOLOID, true); if (stmt_assert->message != NULL) plpgsql_check_expr(cstate, stmt_assert->message); } break; case PLPGSQL_STMT_ASSIGN: { PLpgSQL_stmt_assign *stmt_assign = (PLpgSQL_stmt_assign *) stmt; PLpgSQL_datum *d = (PLpgSQL_datum *) cstate->estate->datums[stmt_assign->varno]; StringInfoData str; initStringInfo(&str); if (d->dtype == PLPGSQL_DTYPE_VAR || d->dtype == PLPGSQL_DTYPE_ROW || d->dtype == PLPGSQL_DTYPE_REC) { PLpgSQL_variable *var = (PLpgSQL_variable *) d; appendStringInfo(&str, "at assignment to variable \"%s\" declared on line %d", var->refname, var->lineno); cstate->estate->err_text = str.data; } else if (d->dtype == PLPGSQL_DTYPE_RECFIELD) { PLpgSQL_recfield *recfield = (PLpgSQL_recfield *) d; PLpgSQL_variable *var = (PLpgSQL_variable *) cstate->estate->datums[recfield->recparentno]; appendStringInfo(&str, "at assignment to field \"%s\" of variable \"%s\" declared on line %d", recfield->fieldname, var->refname, var->lineno); cstate->estate->err_text = str.data; } #if PG_VERSION_NUM < 140000 else if (d->dtype == PLPGSQL_DTYPE_ARRAYELEM) { PLpgSQL_arrayelem *elem = (PLpgSQL_arrayelem *) d; PLpgSQL_variable *var = (PLpgSQL_variable *) cstate->estate->datums[elem->arrayparentno]; appendStringInfo(&str, "at assignment to element of variable \"%s\" declared on line %d", var->refname, var->lineno); cstate->estate->err_text = str.data; } #endif plpgsql_check_assignment(cstate, stmt_assign->expr, NULL, NULL, stmt_assign->varno); pfree(str.data); cstate->estate->err_text = NULL; } break; case PLPGSQL_STMT_IF: { PLpgSQL_stmt_if *stmt_if = (PLpgSQL_stmt_if *) stmt; ListCell *l; int closing_local; int closing_all_paths = PLPGSQL_CHECK_UNKNOWN; List *exceptions_local; plpgsql_check_expr_with_scalar_type(cstate, stmt_if->cond, BOOLOID, true); check_stmts(cstate, stmt_if->then_body, &closing_local, &exceptions_local); closing_all_paths = merge_closing(closing_all_paths, closing_local, exceptions, exceptions_local, -1); foreach(l, stmt_if->elsif_list) { PLpgSQL_if_elsif *elif = (PLpgSQL_if_elsif *) lfirst(l); plpgsql_check_expr_with_scalar_type(cstate, elif->cond, BOOLOID, true); check_stmts(cstate, elif->stmts, &closing_local, &exceptions_local); closing_all_paths = merge_closing(closing_all_paths, closing_local, exceptions, exceptions_local, -1); } check_stmts(cstate, stmt_if->else_body, &closing_local, &exceptions_local); closing_all_paths = merge_closing(closing_all_paths, closing_local, exceptions, exceptions_local, -1); if (stmt_if->else_body != NULL) *closing = closing_all_paths; else if (closing_all_paths == PLPGSQL_CHECK_UNCLOSED) *closing = PLPGSQL_CHECK_UNCLOSED; else *closing = PLPGSQL_CHECK_POSSIBLY_CLOSED; } break; case PLPGSQL_STMT_CASE: { PLpgSQL_stmt_case *stmt_case = (PLpgSQL_stmt_case *) stmt; int closing_local; List *exceptions_local; ListCell *l; int closing_all_paths = PLPGSQL_CHECK_UNKNOWN; if (stmt_case->t_expr != NULL) { PLpgSQL_var *t_var = (PLpgSQL_var *) cstate->estate->datums[stmt_case->t_varno]; Oid result_oid; /* * we need to set hidden variable type */ plpgsql_check_expr_generic(cstate, stmt_case->t_expr); /* record all variables used by the query */ cstate->used_variables = bms_add_members(cstate->used_variables, stmt_case->t_expr->paramnos); tupdesc = plpgsql_check_expr_get_desc(cstate, stmt_case->t_expr, false, /* no element type */ true, /* expand record */ true, /* is expression */ NULL); result_oid = TupleDescAttr(tupdesc, 0)->atttypid; /* * When expected datatype is different from real, * change it. Note that what we're modifying here is * an execution copy of the datum, so this doesn't * affect the originally stored function parse tree. */ if (t_var->datatype->typoid != result_oid) t_var->datatype = plpgsql_check__build_datatype_p(result_oid, -1, cstate->estate->func->fn_input_collation, t_var->datatype->origtypname); ReleaseTupleDesc(tupdesc); } foreach(l, stmt_case->case_when_list) { PLpgSQL_case_when *cwt = (PLpgSQL_case_when *) lfirst(l); plpgsql_check_expr(cstate, cwt->expr); check_stmts(cstate, cwt->stmts, &closing_local, &exceptions_local); closing_all_paths = merge_closing(closing_all_paths, closing_local, exceptions, exceptions_local, -1); } if (stmt_case->else_stmts) { check_stmts(cstate, stmt_case->else_stmts, &closing_local, &exceptions_local); *closing = merge_closing(closing_all_paths, closing_local, exceptions, exceptions_local, -1); } else /* is not ensured all path evaluation */ *closing = possibly_closed(closing_all_paths); } break; case PLPGSQL_STMT_LOOP: check_stmts(cstate, ((PLpgSQL_stmt_loop *) stmt)->body, closing, exceptions); break; case PLPGSQL_STMT_WHILE: { PLpgSQL_stmt_while *stmt_while = (PLpgSQL_stmt_while *) stmt; int closing_local; List *exceptions_local; plpgsql_check_expr_with_scalar_type(cstate, stmt_while->cond, BOOLOID, true); /* * When is not guaranteed execution (possible zero loops), * then ignore closing info from body. */ check_stmts(cstate, stmt_while->body, &closing_local, &exceptions_local); *closing = possibly_closed(closing_local); } break; case PLPGSQL_STMT_FORI: { PLpgSQL_stmt_fori *stmt_fori = (PLpgSQL_stmt_fori *) stmt; int dno = stmt_fori->var->dno; int closing_local; List *exceptions_local; /* prepare plan if desn't exist yet */ plpgsql_check_assignment(cstate, stmt_fori->lower, NULL, NULL, dno); plpgsql_check_assignment(cstate, stmt_fori->upper, NULL, NULL, dno); if (stmt_fori->step) plpgsql_check_assignment(cstate, stmt_fori->step, NULL, NULL, dno); /* this variable should not be updated */ cstate->protected_variables = bms_add_member(cstate->protected_variables, dno); cstate->auto_variables = bms_add_member(cstate->auto_variables, dno); check_variable_name(cstate, outer_stmt_stack, dno); check_stmts(cstate, stmt_fori->body, &closing_local, &exceptions_local); *closing = possibly_closed(closing_local); } break; case PLPGSQL_STMT_FORS: { PLpgSQL_stmt_fors *stmt_fors = (PLpgSQL_stmt_fors *) stmt; int closing_local; List *exceptions_local; check_variable(cstate, stmt_fors->var); /* we need to set hidden variable type */ plpgsql_check_assignment_to_variable(cstate, stmt_fors->query, stmt_fors->var, -1); check_stmts(cstate, stmt_fors->body, &closing_local, &exceptions_local); *closing = possibly_closed(closing_local); } break; case PLPGSQL_STMT_FORC: { PLpgSQL_stmt_forc *stmt_forc = (PLpgSQL_stmt_forc *) stmt; PLpgSQL_var *var = (PLpgSQL_var *) func->datums[stmt_forc->curvar]; int closing_local; List *exceptions_local; check_variable(cstate, stmt_forc->var); plpgsql_check_expr_as_sqlstmt_data(cstate, stmt_forc->argquery); if (var->cursor_explicit_expr != NULL) plpgsql_check_assignment_to_variable(cstate, var->cursor_explicit_expr, stmt_forc->var, -1); check_stmts(cstate, stmt_forc->body, &closing_local, &exceptions_local); *closing = possibly_closed(closing_local); cstate->used_variables = bms_add_member(cstate->used_variables, stmt_forc->curvar); } break; case PLPGSQL_STMT_DYNFORS: { PLpgSQL_stmt_dynfors *stmt_dynfors = (PLpgSQL_stmt_dynfors *) stmt; int closing_local; List *exceptions_local; check_dynamic_sql(cstate, stmt, stmt_dynfors->query, true, stmt_dynfors->var, stmt_dynfors->params); check_stmts(cstate, stmt_dynfors->body, &closing_local, &exceptions_local); *closing = possibly_closed(closing_local); } break; case PLPGSQL_STMT_FOREACH_A: { PLpgSQL_stmt_foreach_a *stmt_foreach_a = (PLpgSQL_stmt_foreach_a *) stmt; bool use_element_type; int closing_local; List *exceptions_local; plpgsql_check_target(cstate, stmt_foreach_a->varno, NULL, NULL); /* * When slice > 0, then result and target are a array. * We shoudl to disable a array element refencing. */ use_element_type = stmt_foreach_a->slice == 0; plpgsql_check_assignment_with_possible_slices(cstate, stmt_foreach_a->expr, NULL, NULL, stmt_foreach_a->varno, use_element_type); check_stmts(cstate, stmt_foreach_a->body, &closing_local, &exceptions_local); *closing = possibly_closed(closing_local); } break; case PLPGSQL_STMT_EXIT: { PLpgSQL_stmt_exit *stmt_exit = (PLpgSQL_stmt_exit *) stmt; plpgsql_check_expr_with_scalar_type(cstate, stmt_exit->cond, BOOLOID, false); if (stmt_exit->label != NULL) { PLpgSQL_stmt *labeled_stmt = find_stmt_with_label(stmt_exit->label, outer_stmt_stack); if (labeled_stmt == NULL) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("label \"%s\" does not exist", stmt_exit->label))); /* CONTINUE only allows loop labels */ if (!is_any_loop_stmt(labeled_stmt) && !stmt_exit->is_exit) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("block label \"%s\" cannot be used in CONTINUE", stmt_exit->label))); } else { if (find_nearest_loop(outer_stmt_stack) == NULL) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("%s cannot be used outside a loop", plpgsql_check__stmt_typename_p((PLpgSQL_stmt *) stmt_exit)))); } } break; case PLPGSQL_STMT_PERFORM: plpgsql_check_expr_as_sqlstmt(cstate, ((PLpgSQL_stmt_perform *) stmt)->expr); /* * Note: if you want to raise warning when used expressions returns * some value (other than VOID type), change previous command plpgsql_check_expr * to following check_expr_with_expected_scalar_type. This should be * not enabled by default, because PERFORM can be used with reason * to ignore result. * * check_expr_with_expected_scalar_type(cstate, * ((PLpgSQL_stmt_perform *) stmt)->expr, * VOIDOID, * true); */ break; case PLPGSQL_STMT_RETURN: { PLpgSQL_stmt_return *stmt_rt = (PLpgSQL_stmt_return *) stmt; *closing = PLPGSQL_CHECK_CLOSED; if (stmt_rt->retvarno >= 0) { PLpgSQL_datum *retvar = cstate->estate->datums[stmt_rt->retvarno]; PLpgSQL_execstate *estate = cstate->estate; cstate->used_variables = bms_add_member(cstate->used_variables, stmt_rt->retvarno); switch (retvar->dtype) { case PLPGSQL_DTYPE_VAR: { PLpgSQL_var *var = (PLpgSQL_var *) retvar; Oid rettype = cstate->estate->func->fn_rettype; if (cstate->estate->retistuple) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("cannot return non-composite value from function returning composite type"))); plpgsql_check_assign_to_target_type(cstate, rettype, -1, var->datatype->typoid, false); } break; case PLPGSQL_DTYPE_REC: { PLpgSQL_rec *rec = (PLpgSQL_rec *) retvar; /* don't do next check, when result tuple desc is fake */ if (recvar_tupdesc(rec) && !cstate->fake_rtd && estate->rsi && IsA(estate->rsi, ReturnSetInfo)) { TupleDesc rettupdesc = estate->rsi->expectedDesc; TupleConversionMap *tupmap ; tupmap = convert_tuples_by_position(recvar_tupdesc(rec), rettupdesc, gettext_noop("returned record type does not match expected record type")); if (tupmap) free_conversion_map(tupmap); } } break; case PLPGSQL_DTYPE_ROW: { PLpgSQL_row *row = (PLpgSQL_row *) retvar; if (row->rowtupdesc && !cstate->fake_rtd && estate->rsi && IsA(estate->rsi, ReturnSetInfo)) { TupleDesc rettupdesc = estate->rsi->expectedDesc; TupleConversionMap *tupmap ; tupmap = convert_tuples_by_position(row->rowtupdesc, rettupdesc, gettext_noop("returned record type does not match expected record type")); if (tupmap) free_conversion_map(tupmap); } } break; default: ; /* nope */ } if (cstate->estate->func->fn_rettype == REFCURSOROID && cstate->cinfo->compatibility_warnings) { if (!(retvar->dtype == PLPGSQL_DTYPE_VAR && ((PLpgSQL_var *) retvar)->datatype->typoid == REFCURSOROID)) { plpgsql_check_put_error(cstate, 0, 0, "obsolete setting of refcursor or cursor variable", "Internal name of cursor should not be specified by users.", NULL, PLPGSQL_CHECK_WARNING_COMPATIBILITY, 0, NULL, NULL); } } } if (stmt_rt->expr) plpgsql_check_returned_expr(cstate, stmt_rt->expr, true); } break; case PLPGSQL_STMT_RETURN_NEXT: { PLpgSQL_stmt_return_next *stmt_rn = (PLpgSQL_stmt_return_next *) stmt; if (stmt_rn->retvarno >= 0) { PLpgSQL_datum *retvar = cstate->estate->datums[stmt_rn->retvarno]; PLpgSQL_execstate *estate = cstate->estate; int natts; cstate->used_variables = bms_add_member(cstate->used_variables, stmt_rn->retvarno); if (!estate->retisset) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("cannot use RETURN NEXT in a non-SETOF function"))); tupdesc = estate->tuple_store_desc; natts = tupdesc ? tupdesc->natts : 0; switch (retvar->dtype) { case PLPGSQL_DTYPE_VAR: { PLpgSQL_var *var = (PLpgSQL_var *) retvar; if (natts > 1) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("wrong result type supplied in RETURN NEXT"))); plpgsql_check_assign_to_target_type(cstate, cstate->estate->func->fn_rettype, -1, var->datatype->typoid, false); } break; case PLPGSQL_DTYPE_REC: { PLpgSQL_rec *rec = (PLpgSQL_rec *) retvar; if (!HeapTupleIsValid(recvar_tuple(rec))) ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("record \"%s\" is not assigned yet", rec->refname), errdetail("The tuple structure of a not-yet-assigned" " record is indeterminate."))); if (tupdesc) { TupleConversionMap *tupmap; tupmap = convert_tuples_by_position(recvar_tupdesc(rec), tupdesc, gettext_noop("wrong record type supplied in RETURN NEXT")); if (tupmap) free_conversion_map(tupmap); } } break; case PLPGSQL_DTYPE_ROW: { if (tupdesc) { bool row_is_valid_result = true; PLpgSQL_row *row = (PLpgSQL_row *) retvar; if (row->nfields == natts) { int i; for (i = 0; i < natts; i++) { PLpgSQL_var *var; if (TupleDescAttr(tupdesc, i)->attisdropped) continue; if (row->varnos[i] < 0) elog(ERROR, "dropped rowtype entry for non-dropped column"); var = (PLpgSQL_var *) (cstate->estate->datums[row->varnos[i]]); if (var->datatype->typoid != TupleDescAttr(tupdesc, i)->atttypid) { row_is_valid_result = false; break; } } } else row_is_valid_result = false; if (!row_is_valid_result) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("wrong record type supplied in RETURN NEXT"))); } } break; default: ; /* nope */ } } if (stmt_rn->expr) plpgsql_check_returned_expr(cstate, stmt_rn->expr, true); } break; case PLPGSQL_STMT_RETURN_QUERY: { PLpgSQL_stmt_return_query *stmt_rq = (PLpgSQL_stmt_return_query *) stmt; if (stmt_rq->query) { plpgsql_check_returned_expr(cstate, stmt_rq->query, false); cstate->found_return_query = true; } if (stmt_rq->dynquery) { check_dynamic_sql(cstate, stmt, stmt_rq->dynquery, false, NULL, stmt_rq->params); } } break; case PLPGSQL_STMT_RAISE: { PLpgSQL_stmt_raise *stmt_raise = (PLpgSQL_stmt_raise *) stmt; ListCell *current_param; ListCell *l; char *cp; int err_code = 0; if (stmt_raise->condname != NULL) err_code = plpgsql_check__recognize_err_condition_p(stmt_raise->condname, true); foreach(l, stmt_raise->params) { plpgsql_check_expr(cstate, (PLpgSQL_expr *) lfirst(l)); } foreach(l, stmt_raise->options) { PLpgSQL_raise_option *opt = (PLpgSQL_raise_option *) lfirst(l); plpgsql_check_expr(cstate, opt->expr); if (opt->opt_type == PLPGSQL_RAISEOPTION_ERRCODE) { char *value; value = plpgsql_check_expr_get_string(cstate, opt->expr, NULL); if (value != NULL) err_code = plpgsql_check__recognize_err_condition_p(value, true); else err_code = -1; /* cannot be calculated now */ } } current_param = list_head(stmt_raise->params); /* ensure any single % has a own parameter */ if (stmt_raise->message != NULL) { for (cp = stmt_raise->message; *cp; cp++) { if (cp[0] == '%') { if (cp[1] == '%') { cp++; continue; } if (current_param == NULL) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("too few parameters specified for RAISE"))); #if PG_VERSION_NUM >= 130000 current_param = lnext(stmt_raise->params, current_param); #else current_param = lnext(current_param); #endif } } } if (current_param != NULL) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("too many parameters specified for RAISE"))); if (stmt_raise->elog_level >= ERROR) { *closing = PLPGSQL_CHECK_CLOSED_BY_EXCEPTIONS; if (err_code == 0) err_code = ERRCODE_RAISE_EXCEPTION; else if (err_code == -1) err_code = 0; /* cannot be calculated */ *exceptions = list_make1_int(err_code); } /* without any parameters it is reRAISE */ if (stmt_raise->condname == NULL && stmt_raise->message == NULL && stmt_raise->options == NIL) { *closing = PLPGSQL_CHECK_CLOSED_BY_EXCEPTIONS; /* should be enhanced in future */ *exceptions = list_make1_int(-2); /* reRAISE */ } } break; case PLPGSQL_STMT_EXECSQL: { PLpgSQL_stmt_execsql *stmt_execsql = (PLpgSQL_stmt_execsql *) stmt; if (stmt_execsql->into) { check_variable(cstate, stmt_execsql->target); plpgsql_check_assignment_to_variable(cstate, stmt_execsql->sqlstmt, stmt_execsql->target, -1); } else /* only statement */ plpgsql_check_expr_as_sqlstmt_nodata(cstate, stmt_execsql->sqlstmt); } break; case PLPGSQL_STMT_DYNEXECUTE: { PLpgSQL_stmt_dynexecute *stmt_dynexecute = (PLpgSQL_stmt_dynexecute *) stmt; check_dynamic_sql(cstate, stmt, stmt_dynexecute->query, stmt_dynexecute->into, stmt_dynexecute->target, stmt_dynexecute->params); } break; case PLPGSQL_STMT_OPEN: { PLpgSQL_stmt_open *stmt_open = (PLpgSQL_stmt_open *) stmt; PLpgSQL_var *var = (PLpgSQL_var *) (cstate->estate->datums[stmt_open->curvar]); plpgsql_check_expr_as_sqlstmt_data(cstate, var->cursor_explicit_expr); plpgsql_check_expr_as_sqlstmt_data(cstate, stmt_open->query); if (stmt_open->query != NULL) var->cursor_explicit_expr = stmt_open->query; plpgsql_check_expr_as_sqlstmt_data(cstate, stmt_open->argquery); if (stmt_open->dynquery) { check_dynamic_sql(cstate, stmt, stmt_open->dynquery, false, NULL, stmt_open->params); } plpgsql_check_target(cstate, stmt_open->curvar, NULL, NULL); cstate->modif_variables = bms_add_member(cstate->modif_variables, stmt_open->curvar); } break; case PLPGSQL_STMT_GETDIAG: { PLpgSQL_stmt_getdiag *stmt_getdiag = (PLpgSQL_stmt_getdiag *) stmt; ListCell *lc; foreach(lc, stmt_getdiag->diag_items) { PLpgSQL_diag_item *diag_item = (PLpgSQL_diag_item *) lfirst(lc); plpgsql_check_target(cstate, diag_item->target, NULL, NULL); /* * Using GET DIAGNOSTICS stack = PG_CONTEXT is like using * other VOLATILE function. */ if (!cstate->skip_volatility_check && cstate->cinfo->performance_warnings && !stmt_getdiag->is_stacked) { if (diag_item->kind == PLPGSQL_GETDIAG_CONTEXT) cstate->volatility = PROVOLATILE_VOLATILE; } } if (stmt_getdiag->is_stacked && !is_inside_exception_handler(outer_stmt_stack)) { ereport(ERROR, (errcode(ERRCODE_STACKED_DIAGNOSTICS_ACCESSED_WITHOUT_ACTIVE_HANDLER), errmsg("GET STACKED DIAGNOSTICS cannot be used outside an exception handler"))); } } break; case PLPGSQL_STMT_FETCH: { PLpgSQL_stmt_fetch *stmt_fetch = (PLpgSQL_stmt_fetch *) stmt; PLpgSQL_var *var = (PLpgSQL_var *) (cstate->estate->datums[stmt_fetch->curvar]); check_variable(cstate, stmt_fetch->target); if (var != NULL && var->cursor_explicit_expr != NULL) plpgsql_check_assignment_to_variable(cstate, var->cursor_explicit_expr, stmt_fetch->target, -1); plpgsql_check_expr(cstate, stmt_fetch->expr); cstate->used_variables = bms_add_member(cstate->used_variables, stmt_fetch->curvar); } break; case PLPGSQL_STMT_CLOSE: cstate->used_variables = bms_add_member(cstate->used_variables, ((PLpgSQL_stmt_close *) stmt)->curvar); break; #if PG_VERSION_NUM < 140000 case PLPGSQL_STMT_SET: /* * We can not check this now, syntax should be ok. * The expression there has not plan. */ break; #endif /* PG_VERSION_NUM < 140000 */ case PLPGSQL_STMT_COMMIT: case PLPGSQL_STMT_ROLLBACK: /* These commands are allowed only in procedures */ if (!cstate->cinfo->is_procedure) ereport(ERROR, (errcode(ERRCODE_INVALID_TRANSACTION_TERMINATION), errmsg("invalid transaction termination"))); if (is_inside_protected_block(outer_stmt_stack)) { if (stmt->cmd_type == PLPGSQL_STMT_COMMIT) ereport(ERROR, (errcode(ERRCODE_INVALID_TRANSACTION_TERMINATION), errmsg("cannot commit while a subtransaction is active"))); else ereport(ERROR, (errcode(ERRCODE_INVALID_TRANSACTION_TERMINATION), errmsg("cannot roll back while a subtransaction is active"))); } break; case PLPGSQL_STMT_CALL: { PLpgSQL_stmt_call *stmt_call = (PLpgSQL_stmt_call *) stmt; PLpgSQL_row *target; bool has_data; has_data = plpgsql_check_expr_as_sqlstmt(cstate, stmt_call->expr); /* any check_expr_xxx should be called before CallExprGetRowTarget */ target = plpgsql_check_CallExprGetRowTarget(cstate, stmt_call->expr); if (has_data != (target != NULL)) elog(ERROR, "plpgsql internal error, broken CALL statement"); if (target != NULL) { check_variable(cstate, (PLpgSQL_variable *) target); plpgsql_check_assignment_to_variable(cstate, stmt_call->expr, (PLpgSQL_variable *) target, -1); pfree(target->varnos); pfree(target); } } break; default: elog(ERROR, "unrecognized cmd_type: %d", stmt->cmd_type); } pop_stmt_from_stmt_stack(cstate); ReleaseCurrentSubTransaction(); MemoryContextSwitchTo(oldCxt); CurrentResourceOwner = oldowner; } PG_CATCH(); { ErrorData *edata; MemoryContextSwitchTo(oldCxt); edata = CopyErrorData(); FlushErrorState(); RollbackAndReleaseCurrentSubTransaction(); MemoryContextSwitchTo(oldCxt); CurrentResourceOwner = oldowner; pop_stmt_from_stmt_stack(cstate); if (!cstate->pragma_vector.disable_check) { /* * If fatal_errors is true, we just propagate the error up to the * highest level. Otherwise the error is appended to our current list * of errors, and we continue checking. */ if (cstate->cinfo->fatal_errors) ReThrowError(edata); else plpgsql_check_put_error_edata(cstate, edata); } MemoryContextSwitchTo(oldCxt); } PG_END_TRY(); if (!cstate->was_pragma) cstate->pragma_vector = pragma_vector; else cstate->was_pragma = false; } static void invalidate_strconstvars(PLpgSQL_checkstate *cstate) { /* * We cannot to safely use string constant when we leave related * path (maybe we can, but it needs deeper analyze ensure so we * will provess all possible variants). */ if (cstate->top_stmts->invalidate_strconstvars) { int dno = -1; Assert(cstate->strconstvars); while ((dno = bms_next_member(cstate->top_stmts->invalidate_strconstvars, dno)) >= 0) { /* * there is an possibility so string was invalided in some * nested node. If still it is valid, invalidate it. */ if (cstate->strconstvars[dno]) { pfree(cstate->strconstvars[dno]); cstate->strconstvars[dno] = NULL; } } pfree(cstate->top_stmts->invalidate_strconstvars); } } /* * Ensure check for all statements in list * */ static void check_stmts(PLpgSQL_checkstate *cstate, List *stmts, int *closing, List **exceptions) { int closing_local; List *exceptions_local; plpgsql_check_pragma_vector prev_pragma_vector = cstate->pragma_vector; PLpgSQL_statements current_stmts; *closing = PLPGSQL_CHECK_UNCLOSED; *exceptions = NIL; current_stmts.outer = cstate->top_stmts; current_stmts.invalidate_strconstvars = NULL; cstate->top_stmts = ¤t_stmts; PG_TRY(); { ListCell *lc; bool dead_code_alert = false; foreach(lc, stmts) { PLpgSQL_stmt *stmt = (PLpgSQL_stmt *) lfirst(lc); closing_local = PLPGSQL_CHECK_UNCLOSED; exceptions_local = NIL; plpgsql_check_stmt(cstate, stmt, &closing_local, &exceptions_local); /* raise dead_code_alert only for visible statements */ if (dead_code_alert && stmt->lineno > 0) { plpgsql_check_put_error(cstate, 0, stmt->lineno, "unreachable code", NULL, NULL, PLPGSQL_CHECK_WARNING_EXTRA, 0, NULL, NULL); /* don't raise this warning every line */ dead_code_alert = false; } if (closing_local == PLPGSQL_CHECK_CLOSED) { dead_code_alert = true; *closing = PLPGSQL_CHECK_CLOSED; *exceptions = NIL; } else if (closing_local == PLPGSQL_CHECK_CLOSED_BY_EXCEPTIONS) { dead_code_alert = true; if (*closing == PLPGSQL_CHECK_UNCLOSED || *closing == PLPGSQL_CHECK_POSSIBLY_CLOSED || *closing == PLPGSQL_CHECK_CLOSED_BY_EXCEPTIONS) { *closing = PLPGSQL_CHECK_CLOSED_BY_EXCEPTIONS; *exceptions = exceptions_local; } } else if (closing_local == PLPGSQL_CHECK_POSSIBLY_CLOSED) { if (*closing == PLPGSQL_CHECK_UNCLOSED) { *closing = PLPGSQL_CHECK_POSSIBLY_CLOSED; *exceptions = NIL; } } } invalidate_strconstvars(cstate); cstate->top_stmts = current_stmts.outer; } PG_CATCH(); { cstate->pragma_vector = prev_pragma_vector; cstate->was_pragma = false; invalidate_strconstvars(cstate); cstate->top_stmts = current_stmts.outer; PG_RE_THROW(); } PG_END_TRY(); } /* * Add label to stack of labels */ static PLpgSQL_stmt_stack_item * push_stmt_to_stmt_stack(PLpgSQL_checkstate *cstate) { PLpgSQL_stmt *stmt = cstate->estate->err_stmt; PLpgSQL_stmt_stack_item *stmt_stack_item; PLpgSQL_stmt_stack_item *current = cstate->top_stmt_stack; stmt_stack_item = (PLpgSQL_stmt_stack_item *) palloc0(sizeof(PLpgSQL_stmt_stack_item)); stmt_stack_item->stmt = stmt; switch (stmt->cmd_type) { case PLPGSQL_STMT_BLOCK: stmt_stack_item->label = ((PLpgSQL_stmt_block *) stmt)->label; break; case PLPGSQL_STMT_EXIT: stmt_stack_item->label = ((PLpgSQL_stmt_exit *) stmt)->label; break; case PLPGSQL_STMT_LOOP: stmt_stack_item->label = ((PLpgSQL_stmt_loop *) stmt)->label; break; case PLPGSQL_STMT_WHILE: stmt_stack_item->label = ((PLpgSQL_stmt_while *) stmt)->label; break; case PLPGSQL_STMT_FORI: stmt_stack_item->label = ((PLpgSQL_stmt_fori *) stmt)->label; break; case PLPGSQL_STMT_FORS: stmt_stack_item->label = ((PLpgSQL_stmt_fors *) stmt)->label; break; case PLPGSQL_STMT_FORC: stmt_stack_item->label = ((PLpgSQL_stmt_forc *) stmt)->label; break; case PLPGSQL_STMT_DYNFORS: stmt_stack_item->label = ((PLpgSQL_stmt_dynfors *) stmt)->label; break; case PLPGSQL_STMT_FOREACH_A: stmt_stack_item->label = ((PLpgSQL_stmt_foreach_a *) stmt)->label; break; default: stmt_stack_item->label = NULL; } stmt_stack_item->outer = current; cstate->top_stmt_stack = stmt_stack_item; return current; } static void pop_stmt_from_stmt_stack(PLpgSQL_checkstate *cstate) { PLpgSQL_stmt_stack_item *current = cstate->top_stmt_stack; Assert(cstate->top_stmt_stack != NULL); cstate->top_stmt_stack = current->outer; pfree(current); } /* * Returns true, when stmt is any loop statement */ static bool is_any_loop_stmt(PLpgSQL_stmt *stmt) { switch (stmt->cmd_type) { case PLPGSQL_STMT_LOOP: case PLPGSQL_STMT_WHILE: case PLPGSQL_STMT_FORI: case PLPGSQL_STMT_FORS: case PLPGSQL_STMT_FORC: case PLPGSQL_STMT_DYNFORS: case PLPGSQL_STMT_FOREACH_A: return true; default: return false; } } /* * Searching a any statement related to CONTINUE/EXIT statement. * label cannot be NULL. */ static PLpgSQL_stmt * find_stmt_with_label(char *label, PLpgSQL_stmt_stack_item *current) { while (current != NULL) { if (current->label != NULL && strcmp(current->label, label) == 0) return current->stmt; current = current->outer; } return NULL; } static PLpgSQL_stmt * find_nearest_loop(PLpgSQL_stmt_stack_item *current) { while (current != NULL) { if (is_any_loop_stmt(current->stmt)) return current->stmt; current = current->outer; } return NULL; } /* * Returns true, when some outer block handles exceptions. * It is used for check of correct usage of COMMIT or ROLLBACK. */ static bool is_inside_protected_block(PLpgSQL_stmt_stack_item *current) { while (current != NULL) { if (current->stmt->cmd_type == PLPGSQL_STMT_BLOCK) { PLpgSQL_stmt_block *stmt_block = (PLpgSQL_stmt_block *) current->stmt; if (stmt_block->exceptions && !current->is_exception_handler) return true; } current = current->outer; } return false; } /* * This is used for check of correct usage GET STACKED DIAGNOSTICS */ static bool is_inside_exception_handler(PLpgSQL_stmt_stack_item *current) { while (current != NULL) { if (current->stmt->cmd_type == PLPGSQL_STMT_BLOCK) { PLpgSQL_stmt_block *stmt_block = (PLpgSQL_stmt_block *) current->stmt; if (stmt_block->exceptions && current->is_exception_handler) return true; } current = current->outer; } return false; } /* * returns false, when a variable doesn't shadows any other variable */ static bool found_shadowed_variable(char *varname, PLpgSQL_stmt_stack_item *current, PLpgSQL_checkstate *cstate) { while (current != NULL) { if (current->stmt->cmd_type == PLPGSQL_STMT_BLOCK) { PLpgSQL_stmt_block *stmt_block = (PLpgSQL_stmt_block *) current->stmt; int i; for (i = 0; i < stmt_block->n_initvars; i++) { char *refname; PLpgSQL_datum *d; d = cstate->estate->func->datums[stmt_block->initvarnos[i]]; refname = plpgsql_check_datum_get_refname(cstate, d); if (refname != NULL && strcmp(refname, varname) == 0) return true; } } current = current->outer; } return false; } /* * Reduce ending states of execution paths. * */ static int possibly_closed(int c) { switch (c) { case PLPGSQL_CHECK_CLOSED: case PLPGSQL_CHECK_CLOSED_BY_EXCEPTIONS: case PLPGSQL_CHECK_POSSIBLY_CLOSED: return PLPGSQL_CHECK_POSSIBLY_CLOSED; default: return PLPGSQL_CHECK_UNCLOSED; } } /* * Deduce ending state of execution paths. * */ static int merge_closing(int c, int c_local, List **exceptions, List *exceptions_local, int err_code) { *exceptions = NIL; if (c == PLPGSQL_CHECK_UNKNOWN) { if (c_local == PLPGSQL_CHECK_CLOSED_BY_EXCEPTIONS) *exceptions = exceptions_local; return c_local; } if (c_local == PLPGSQL_CHECK_UNKNOWN) return c; if (c == c_local) { if (c == PLPGSQL_CHECK_CLOSED_BY_EXCEPTIONS) { if (err_code != -1) { ListCell *lc; /* replace reRAISE symbol (-2) by real err_code */ foreach(lc, exceptions_local) { int t_err_code = lfirst_int(lc); *exceptions = list_append_unique_int(*exceptions, t_err_code != -2 ? t_err_code : err_code); } } else *exceptions = list_concat_unique_int(*exceptions, exceptions_local); } return c_local; } if (c == PLPGSQL_CHECK_CLOSED || c_local == PLPGSQL_CHECK_CLOSED) { if (c == PLPGSQL_CHECK_CLOSED_BY_EXCEPTIONS || c_local == PLPGSQL_CHECK_CLOSED_BY_EXCEPTIONS) return PLPGSQL_CHECK_CLOSED; } return PLPGSQL_CHECK_POSSIBLY_CLOSED; } /* * Returns true, if exception with sqlerrstate is handled. * */ static bool exception_matches_conditions(int sqlerrstate, PLpgSQL_condition *cond) { for (; cond != NULL; cond = cond->next) { int _sqlerrstate = cond->sqlerrstate; /* * OTHERS matches everything *except* query-canceled and * assert-failure. If you're foolish enough, you can match those * explicitly. */ if (_sqlerrstate == 0) { if (sqlerrstate != ERRCODE_QUERY_CANCELED && sqlerrstate != ERRCODE_ASSERT_FAILURE) return true; } /* Exact match? */ else if (sqlerrstate == _sqlerrstate) return true; /* Category match? */ else if (ERRCODE_IS_CATEGORY(_sqlerrstate) && ERRCODE_TO_CATEGORY(sqlerrstate) == _sqlerrstate) return true; } return false; } /* * Dynamic SQL processing. * * When dynamic query is constant, we can do same work like with * static SQL. */ typedef struct { List *args; PLpgSQL_checkstate *cstate; bool use_params; } DynSQLParams; static Node * dynsql_param_ref(ParseState *pstate, ParamRef *pref) { DynSQLParams *params = (DynSQLParams *) pstate->p_ref_hook_state; List *args = params->args; int nargs = list_length(args); Param *param = NULL; PLpgSQL_expr *expr; TupleDesc tupdesc; if (pref->number < 1 || pref->number > nargs) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_PARAMETER), errmsg("there is no parameter $%d", pref->number), parser_errposition(pstate, pref->location))); expr = (PLpgSQL_expr *) list_nth(args, pref->number - 1); tupdesc = plpgsql_check_expr_get_desc(params->cstate, expr, false, false, true, NULL); if (tupdesc) { param = makeNode(Param); param->paramkind = PARAM_EXTERN; param->paramid = pref->number; param->paramtype = TupleDescAttr(tupdesc, 0)->atttypid; param->location = pref->location; /* * SPI_execute_with_args doesn't allow pass typmod. */ param->paramtypmod = -1; param->paramcollid = InvalidOid; ReleaseTupleDesc(tupdesc); } else elog(ERROR, "cannot to detect type of $%d parameter", pref->number); params->use_params = true; return (Node *) param; } /* * Dynamic query requires own setup. In reality it is executed by * different SPI, here we need to emulate different environment. * Parameters are not mapped to function parameters, but to USING * clause expressions. */ static void dynsql_parser_setup(struct ParseState *pstate, DynSQLParams *params) { pstate->p_pre_columnref_hook = NULL; pstate->p_post_columnref_hook = NULL; pstate->p_paramref_hook = dynsql_param_ref; pstate->p_ref_hook_state = (void *) params; } /* * Returns true if record variable has assigned some type */ static bool has_assigned_tupdesc(PLpgSQL_checkstate *cstate, PLpgSQL_rec *rec) { PLpgSQL_rec *target; Assert(rec); target = (PLpgSQL_rec *) (cstate->estate->datums[rec->dno]); Assert(rec->dtype == PLPGSQL_DTYPE_REC); if (recvar_tupdesc(target)) return true; return false; } static void check_dynamic_sql(PLpgSQL_checkstate *cstate, PLpgSQL_stmt *stmt, PLpgSQL_expr *query, bool into, PLpgSQL_variable *target, List *params) { Node *expr_node; ListCell *l; int loc = -1; char *dynquery = NULL; bool prev_has_execute_stmt = cstate->has_execute_stmt; volatile bool expr_is_const = false; volatile bool raise_unknown_rec_warning = false; volatile bool known_type_of_dynexpr = false; /* * possible checks: * * 1. When expression is string literal, then we can check this query similary * like cursor query with parameters. When this query has not a parameters, * and it is not DDL, DML, then we can raise a performance warning'. * * 2. When expression is real expression, then we should to check any string * kind parameters if are sanitized by functions quote_ident, qoute_literal, * or format. * * 3. When expression is based on calling format function, and there are used * only placeholders %I and %L, then we can try to check syntax of embeded * query. */ cstate->has_execute_stmt = true; foreach(l, params) { plpgsql_check_expr(cstate, (PLpgSQL_expr *) lfirst(l)); } plpgsql_check_expr(cstate, query); expr_node = plpgsql_check_expr_get_node(cstate, query, false); if (IsA(expr_node, FuncExpr)) { FuncExpr *fexpr = (FuncExpr *) expr_node; if (fexpr->funcid == FORMAT_0PARAM_OID || fexpr->funcid == FORMAT_NPARAM_OID) { char *fmt = NULL; bool found_ident_placeholder = false; bool found_literal_placeholder = false; bool _expr_is_const; if (fexpr->args) fmt = plpgsql_check_get_const_string(cstate, linitial(fexpr->args), NULL); if (fmt) { char *fstr; fstr = plpgsql_check_get_formatted_string(cstate, fmt, fexpr->args, &found_ident_placeholder, &found_literal_placeholder, &_expr_is_const); /* fix passing 'volatile bool *' to parameter of type 'bool *' discards qualifiers */ expr_is_const = _expr_is_const; if (fstr) { if (!found_literal_placeholder) { #if PG_VERSION_NUM >= 140000 /* in this case we can do only basic parser check */ raw_parser(fstr, RAW_PARSE_DEFAULT); #else raw_parser(fstr); #endif } if (!found_ident_placeholder) dynquery = fstr; } } } } else { dynquery = plpgsql_check_get_const_string(cstate, expr_node, NULL); expr_is_const = (dynquery != NULL); } if (dynquery) { PLpgSQL_expr *dynexpr = NULL; DynSQLParams dsp; volatile bool is_mp; volatile bool is_ok = true; dynexpr = palloc0(sizeof(PLpgSQL_expr)); #if PG_VERSION_NUM >= 140000 dynexpr->expr_rw_param = NULL; #else dynexpr->rwparam = -1; #endif dynexpr->query = dynquery; dsp.args = params; dsp.cstate = cstate; dsp.use_params = false; /* * When dynquery is not really constant, then there are * possible false alarms because we try to replace string * literal by parameter, so we can use it just for type * detection when check is ok. */ if (expr_is_const) { PG_TRY(); { cstate->allow_mp = true; plpgsql_check_expr_generic_with_parser_setup(cstate, dynexpr, (ParserSetupHook) dynsql_parser_setup, &dsp); is_mp = cstate->has_mp; cstate->has_mp = false; } PG_CATCH(); { cstate->allow_mp = false; cstate->has_mp = false; PG_RE_THROW(); } PG_END_TRY(); } else { MemoryContext oldCxt; ResourceOwner oldowner; /* * When dynquery is not really constant, then there are * possible false alarms because we try to replace string * literal by parameter, so we can use it just for type * detection when check is ok. */ oldCxt = CurrentMemoryContext; oldowner = CurrentResourceOwner; BeginInternalSubTransaction(NULL); MemoryContextSwitchTo(cstate->check_cxt); PG_TRY(); { cstate->allow_mp = true; plpgsql_check_expr_generic_with_parser_setup(cstate, dynexpr, (ParserSetupHook) dynsql_parser_setup, &dsp); is_mp = cstate->has_mp; cstate->has_mp = false; RollbackAndReleaseCurrentSubTransaction(); MemoryContextSwitchTo(oldCxt); CurrentResourceOwner = oldowner; } PG_CATCH(); { is_ok = false; cstate->allow_mp = false; cstate->has_mp = false; MemoryContextSwitchTo(oldCxt); FlushErrorState(); RollbackAndReleaseCurrentSubTransaction(); MemoryContextSwitchTo(oldCxt); CurrentResourceOwner = oldowner; } PG_END_TRY(); } if (is_ok && expr_is_const && !is_mp && (!params || !dsp.use_params)) { /* probably useless dynamic command */ plpgsql_check_put_error(cstate, 0, 0, "immutable expression without parameters found", "the EXECUTE command is not necessary probably", "Don't use dynamic SQL when you can use static SQL.", PLPGSQL_CHECK_WARNING_PERFORMANCE, 0, NULL, NULL); } if (is_ok && params && !dsp.use_params) { plpgsql_check_put_error(cstate, 0, 0, "values passed to EXECUTE statement by USING clause was not used", NULL, NULL, PLPGSQL_CHECK_WARNING_OTHERS, 0, NULL, NULL); } if (is_ok && dynexpr->plan) { known_type_of_dynexpr = true; if (stmt->cmd_type == PLPGSQL_STMT_RETURN_QUERY) { plpgsql_check_returned_expr(cstate, dynexpr, false); cstate->found_return_query = true; } else if (into) { check_variable(cstate, target); plpgsql_check_assignment_to_variable(cstate, dynexpr, target, -1); } } /* this is not real dynamic SQL statement */ if (!is_mp) cstate->has_execute_stmt = prev_has_execute_stmt; } if (!expr_is_const) { /* * execute string is not constant (is not safe), * but we can check sanitize parameters. */ if (cstate->cinfo->security_warnings && plpgsql_check_is_sql_injection_vulnerable(cstate, query, expr_node, &loc)) { if (loc != -1) plpgsql_check_put_error(cstate, 0, 0, "text type variable is not sanitized", "The EXECUTE expression is SQL injection vulnerable.", "Use quote_ident, quote_literal or format function to secure variable.", PLPGSQL_CHECK_WARNING_SECURITY, loc, query->query, NULL); else plpgsql_check_put_error(cstate, 0, 0, "the expression is not SQL injection safe", "Cannot ensure so dynamic EXECUTE statement is SQL injection secure.", "Use quote_ident, quote_literal or format function to secure variable.", PLPGSQL_CHECK_WARNING_SECURITY, -1, query->query, NULL); } /* in this case we don't know number of output columns */ if (stmt->cmd_type == PLPGSQL_STMT_RETURN_QUERY && !known_type_of_dynexpr) { cstate->found_return_dyn_query = true; } /* * In this case, we don't know a result type, and we should * to raise warning about this situation. */ if (into && !known_type_of_dynexpr) { if (target->dtype == PLPGSQL_DTYPE_REC) raise_unknown_rec_warning = true; } } /* recheck if target rec var has assigned tupdesc */ if (into) { Assert(target); check_variable(cstate, target); if (raise_unknown_rec_warning || (target->dtype == PLPGSQL_DTYPE_REC && !has_assigned_tupdesc(cstate, (PLpgSQL_rec *) target))) { if (!bms_is_member(target->dno, cstate->typed_variables)) plpgsql_check_put_error(cstate, 0, 0, "cannot determinate a result of dynamic SQL", "There is a risk of related false alarms.", "Don't use dynamic SQL and record type together, when you would check function.", PLPGSQL_CHECK_WARNING_OTHERS, 0, NULL, NULL); } } } plpgsql_check-2.7.8/src/tablefunc.c000066400000000000000000000424531465410532300172600ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * tablefunc.c * * top functions - display results in table format * * by Pavel Stehule 2013-2024 * *------------------------------------------------------------------------- */ #include "plpgsql_check.h" #include "plpgsql_check_builtins.h" #include "utils/builtins.h" #include "utils/syscache.h" static void SetReturningFunctionCheck(ReturnSetInfo *rsinfo); PG_FUNCTION_INFO_V1(plpgsql_check_function); PG_FUNCTION_INFO_V1(plpgsql_check_function_tb); PG_FUNCTION_INFO_V1(plpgsql_show_dependency_tb); PG_FUNCTION_INFO_V1(plpgsql_profiler_function_tb); PG_FUNCTION_INFO_V1(plpgsql_profiler_function_statements_tb); PG_FUNCTION_INFO_V1(plpgsql_check_function_name); PG_FUNCTION_INFO_V1(plpgsql_check_function_tb_name); PG_FUNCTION_INFO_V1(plpgsql_show_dependency_tb_name); PG_FUNCTION_INFO_V1(plpgsql_profiler_function_tb_name); PG_FUNCTION_INFO_V1(plpgsql_profiler_function_statements_tb_name); PG_FUNCTION_INFO_V1(plpgsql_profiler_functions_all_tb); #define ERR_NULL_OPTION(option) ereport(ERROR, \ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), \ errmsg("the option \"" option "\" is NULL"), \ errhint("this option should not be NULL"))) /* * Validate function result description * */ static void SetReturningFunctionCheck(ReturnSetInfo *rsinfo) { /* check to see if caller supports us returning a tuplestore */ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("set-valued function called in context that cannot accept a set"))); if (!(rsinfo->allowedModes & SFRM_Materialize)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("materialize mode required, but it is not allowed in this context"))); } void plpgsql_check_info_init(plpgsql_check_info *cinfo, Oid fn_oid) { memset(cinfo, 0, sizeof(*cinfo)); cinfo->fn_oid = fn_oid; } void plpgsql_check_set_all_warnings(plpgsql_check_info *cinfo) { cinfo->other_warnings = true; cinfo->performance_warnings = true; cinfo->extra_warnings = true; cinfo->security_warnings = true; cinfo->compatibility_warnings = true; } void plpgsql_check_set_without_warnings(plpgsql_check_info *cinfo) { cinfo->other_warnings = false; cinfo->performance_warnings = false; cinfo->extra_warnings = false; cinfo->security_warnings = false; cinfo->compatibility_warnings = false; } /* * plpgsql_check_function * * Extended check with formatted text output * */ static Datum check_function_internal(Oid fnoid, FunctionCallInfo fcinfo) { plpgsql_check_info cinfo; plpgsql_check_result_info ri; ReturnSetInfo *rsinfo; ErrorContextCallback *prev_errorcontext; int format; plpgsql_check_check_ext_version(fcinfo->flinfo->fn_oid); Assert(PG_NARGS() == 21); /* check to see if caller supports us returning a tuplestore */ rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; SetReturningFunctionCheck(rsinfo); if (PG_ARGISNULL(1)) ERR_NULL_OPTION("relid"); if (PG_ARGISNULL(2)) ERR_NULL_OPTION("format"); if (PG_ARGISNULL(3)) ERR_NULL_OPTION("fatal_errors"); if (PG_ARGISNULL(4)) ERR_NULL_OPTION("other_warnings"); if (PG_ARGISNULL(5)) ERR_NULL_OPTION("performance warnings"); if (PG_ARGISNULL(6)) ERR_NULL_OPTION("extra_warnings"); if (PG_ARGISNULL(7)) ERR_NULL_OPTION("security_warnings"); if (PG_ARGISNULL(8)) ERR_NULL_OPTION("compatibility_warnings"); if (PG_ARGISNULL(11)) ERR_NULL_OPTION("anyelementtype"); if (PG_ARGISNULL(12)) ERR_NULL_OPTION("anyenumtype"); if (PG_ARGISNULL(13)) ERR_NULL_OPTION("anyrangetype"); if (PG_ARGISNULL(14)) ERR_NULL_OPTION("anycompatibletype"); if (PG_ARGISNULL(15)) ERR_NULL_OPTION("anycompatiblerangetype"); if (PG_ARGISNULL(16)) ERR_NULL_OPTION("without_warnings"); if (PG_ARGISNULL(17)) ERR_NULL_OPTION("all_warnings"); if (PG_ARGISNULL(18)) ERR_NULL_OPTION("use_incomment_options"); if (PG_ARGISNULL(19)) ERR_NULL_OPTION("incomment_options_usage_warning"); if (PG_ARGISNULL(20)) ERR_NULL_OPTION("constants_tracing"); format = plpgsql_check_format_num(text_to_cstring(PG_GETARG_TEXT_PP(2))); plpgsql_check_info_init(&cinfo, fnoid); cinfo.relid = PG_GETARG_OID(1); cinfo.fatal_errors = PG_GETARG_BOOL(3); cinfo.other_warnings = PG_GETARG_BOOL(4); cinfo.performance_warnings = PG_GETARG_BOOL(5); cinfo.extra_warnings = PG_GETARG_BOOL(6); cinfo.security_warnings = PG_GETARG_BOOL(7); cinfo.compatibility_warnings = PG_GETARG_BOOL(8); cinfo.incomment_options_usage_warning = PG_GETARG_BOOL(19); cinfo.constants_tracing = PG_GETARG_BOOL(20); /* without_warnings */ if (PG_GETARG_BOOL(16)) { if (PG_GETARG_BOOL(17)) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("without_warnings and all_warnings cannot be true same time"))); plpgsql_check_set_without_warnings(&cinfo); } /* all warnings */ else if (PG_GETARG_BOOL(17)) { if (PG_GETARG_BOOL(16)) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("without_warnings and all_warnings cannot be true same time"))); plpgsql_check_set_all_warnings(&cinfo); } if (PG_ARGISNULL(9)) cinfo.oldtable = NULL; else cinfo.oldtable = NameStr(*(PG_GETARG_NAME(9))); if (PG_ARGISNULL(10)) cinfo.newtable = NULL; else cinfo.newtable = NameStr(*(PG_GETARG_NAME(10))); if ((cinfo.oldtable || cinfo.newtable) && !OidIsValid(cinfo.relid)) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("missing description of oldtable or newtable"), errhint("Parameter relid is a empty."))); cinfo.anyelementoid = PG_GETARG_OID(11); cinfo.anyenumoid = PG_GETARG_OID(12); cinfo.anyrangeoid = PG_GETARG_OID(13); cinfo.anycompatibleoid = PG_GETARG_OID(14); cinfo.anycompatiblerangeoid = PG_GETARG_OID(15); cinfo.proctuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(cinfo.fn_oid)); if (!HeapTupleIsValid(cinfo.proctuple)) elog(ERROR, "cache lookup failed for function %u", cinfo.fn_oid); plpgsql_check_get_function_info(&cinfo); plpgsql_check_precheck_conditions(&cinfo); if (PG_GETARG_BOOL(18)) plpgsql_check_search_comment_options(&cinfo); /* Envelope outer plpgsql function is not interesting */ prev_errorcontext = error_context_stack; error_context_stack = NULL; plpgsql_check_init_ri(&ri, format, rsinfo); plpgsql_check_function_internal(&ri, &cinfo); plpgsql_check_finalize_ri(&ri); error_context_stack = prev_errorcontext; ReleaseSysCache(cinfo.proctuple); return (Datum) 0; } /* * plpgsql_check_function_tb * * It ensure a detailed validation and returns result as multicolumn table * */ static Datum check_function_tb_internal(Oid fnoid, FunctionCallInfo fcinfo) { plpgsql_check_info cinfo; plpgsql_check_result_info ri; ReturnSetInfo *rsinfo; ErrorContextCallback *prev_errorcontext; plpgsql_check_check_ext_version(fcinfo->flinfo->fn_oid); Assert(PG_NARGS() == 20); /* check to see if caller supports us returning a tuplestore */ rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; SetReturningFunctionCheck(rsinfo); if (PG_ARGISNULL(1)) ERR_NULL_OPTION("relid"); if (PG_ARGISNULL(2)) ERR_NULL_OPTION("fatal_errors"); if (PG_ARGISNULL(3)) ERR_NULL_OPTION("other_warnings"); if (PG_ARGISNULL(4)) ERR_NULL_OPTION("performance_warnings"); if (PG_ARGISNULL(5)) ERR_NULL_OPTION("extra_warnings"); if (PG_ARGISNULL(6)) ERR_NULL_OPTION("security_warnings"); if (PG_ARGISNULL(7)) ERR_NULL_OPTION("compatibility_warnings"); if (PG_ARGISNULL(10)) ERR_NULL_OPTION("anyelementtype"); if (PG_ARGISNULL(11)) ERR_NULL_OPTION("anyenumtype"); if (PG_ARGISNULL(12)) ERR_NULL_OPTION("anyrangetype"); if (PG_ARGISNULL(13)) ERR_NULL_OPTION("anycompatibletype"); if (PG_ARGISNULL(14)) ERR_NULL_OPTION("anycompatiblerangetype"); if (PG_ARGISNULL(15)) ERR_NULL_OPTION("without_warnings"); if (PG_ARGISNULL(16)) ERR_NULL_OPTION("all_warnings"); if (PG_ARGISNULL(17)) ERR_NULL_OPTION("use_incomment_options"); if (PG_ARGISNULL(18)) ERR_NULL_OPTION("incomment_options_usage_warning"); if (PG_ARGISNULL(19)) ERR_NULL_OPTION("constants_tracing"); plpgsql_check_info_init(&cinfo, fnoid); cinfo.relid = PG_GETARG_OID(1); cinfo.fatal_errors = PG_GETARG_BOOL(2); cinfo.other_warnings = PG_GETARG_BOOL(3); cinfo.performance_warnings = PG_GETARG_BOOL(4); cinfo.extra_warnings = PG_GETARG_BOOL(5); cinfo.security_warnings = PG_GETARG_BOOL(6); cinfo.compatibility_warnings = PG_GETARG_BOOL(7); cinfo.incomment_options_usage_warning = PG_GETARG_BOOL(18); cinfo.constants_tracing = PG_GETARG_BOOL(19); /* without_warnings */ if (PG_GETARG_BOOL(15)) { if (PG_GETARG_BOOL(16)) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("without_warnings and all_warnings cannot be true same time"))); plpgsql_check_set_without_warnings(&cinfo); } /* all warnings */ else if (PG_GETARG_BOOL(16)) { if (PG_GETARG_BOOL(15)) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("without_warnings and all_warnings cannot be true same time"))); plpgsql_check_set_all_warnings(&cinfo); } cinfo.anyelementoid = PG_GETARG_OID(10); cinfo.anyenumoid = PG_GETARG_OID(11); cinfo.anyrangeoid = PG_GETARG_OID(12); cinfo.anycompatibleoid = PG_GETARG_OID(13); cinfo.anycompatiblerangeoid = PG_GETARG_OID(14); if (PG_ARGISNULL(8)) cinfo.oldtable = NULL; else cinfo.oldtable = NameStr(*(PG_GETARG_NAME(8))); if (PG_ARGISNULL(9)) cinfo.newtable = NULL; else cinfo.newtable = NameStr(*(PG_GETARG_NAME(9))); if ((cinfo.oldtable || cinfo.newtable) && !OidIsValid(cinfo.relid)) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("missing description of oldtable or newtable"), errhint("Parameter relid is a empty."))); cinfo.proctuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(cinfo.fn_oid)); if (!HeapTupleIsValid(cinfo.proctuple)) elog(ERROR, "cache lookup failed for function %u", cinfo.fn_oid); plpgsql_check_get_function_info(&cinfo); plpgsql_check_precheck_conditions(&cinfo); if (PG_GETARG_BOOL(17)) plpgsql_check_search_comment_options(&cinfo); /* Envelope outer plpgsql function is not interesting */ prev_errorcontext = error_context_stack; error_context_stack = NULL; plpgsql_check_init_ri(&ri, PLPGSQL_CHECK_FORMAT_TABULAR, rsinfo); plpgsql_check_function_internal(&ri, &cinfo); plpgsql_check_finalize_ri(&ri); error_context_stack = prev_errorcontext; ReleaseSysCache(cinfo.proctuple); return (Datum) 0; } /* * plpgsql_show_dependency_tb * * Prepare tuplestore and start check function in mode dependency detection * */ static Datum show_dependency_tb_internal(Oid fnoid, FunctionCallInfo fcinfo) { plpgsql_check_info cinfo; plpgsql_check_result_info ri; ReturnSetInfo *rsinfo; plpgsql_check_check_ext_version(fcinfo->flinfo->fn_oid); Assert(PG_NARGS() == 7); /* check to see if caller supports us returning a tuplestore */ rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; SetReturningFunctionCheck(rsinfo); if (PG_ARGISNULL(1)) ERR_NULL_OPTION("relid"); if (PG_ARGISNULL(2)) ERR_NULL_OPTION("anyelementtype"); if (PG_ARGISNULL(3)) ERR_NULL_OPTION("anyenumtype"); if (PG_ARGISNULL(4)) ERR_NULL_OPTION("anyrangetype"); if (PG_ARGISNULL(5)) ERR_NULL_OPTION("anycompatibletype"); if (PG_ARGISNULL(6)) ERR_NULL_OPTION("anycompatiblerangetype"); plpgsql_check_info_init(&cinfo, fnoid); cinfo.relid = PG_GETARG_OID(1); cinfo.fatal_errors = false; cinfo.other_warnings = false; cinfo.performance_warnings = false; cinfo.extra_warnings = false; cinfo.compatibility_warnings = false; cinfo.anyelementoid = PG_GETARG_OID(2); cinfo.anyenumoid = PG_GETARG_OID(3); cinfo.anyrangeoid = PG_GETARG_OID(4); cinfo.anycompatibleoid = PG_GETARG_OID(5); cinfo.anycompatiblerangeoid = PG_GETARG_OID(6); cinfo.proctuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(cinfo.fn_oid)); if (!HeapTupleIsValid(cinfo.proctuple)) elog(ERROR, "cache lookup failed for function %u", cinfo.fn_oid); plpgsql_check_get_function_info(&cinfo); plpgsql_check_precheck_conditions(&cinfo); plpgsql_check_init_ri(&ri, PLPGSQL_SHOW_DEPENDENCY_FORMAT_TABULAR, rsinfo); plpgsql_check_function_internal(&ri, &cinfo); plpgsql_check_finalize_ri(&ri); ReleaseSysCache(cinfo.proctuple); return (Datum) 0; } /* * Displaying a function profile */ static Datum profiler_function_tb_internal(Oid fnoid, FunctionCallInfo fcinfo) { plpgsql_check_info cinfo; plpgsql_check_result_info ri; ReturnSetInfo *rsinfo; plpgsql_check_check_ext_version(fcinfo->flinfo->fn_oid); Assert(PG_NARGS() == 1); /* check to see if caller supports us returning a tuplestore */ rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; SetReturningFunctionCheck(rsinfo); plpgsql_check_info_init(&cinfo, fnoid); cinfo.show_profile = true; cinfo.proctuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(cinfo.fn_oid)); if (!HeapTupleIsValid(cinfo.proctuple)) elog(ERROR, "cache lookup failed for function %u", cinfo.fn_oid); plpgsql_check_get_function_info(&cinfo); plpgsql_check_precheck_conditions(&cinfo); cinfo.src = plpgsql_check_get_src(cinfo.proctuple); plpgsql_check_init_ri(&ri, PLPGSQL_SHOW_PROFILE_TABULAR, rsinfo); plpgsql_check_profiler_show_profile(&ri, &cinfo); plpgsql_check_finalize_ri(&ri); pfree(cinfo.src); ReleaseSysCache(cinfo.proctuple); return (Datum) 0; } /* * Displaying a function profile */ static Datum profiler_function_statements_tb_internal(Oid fnoid, FunctionCallInfo fcinfo) { plpgsql_check_info cinfo; plpgsql_check_result_info ri; ReturnSetInfo *rsinfo; plpgsql_check_check_ext_version(fcinfo->flinfo->fn_oid); Assert(PG_NARGS() == 1); /* check to see if caller supports us returning a tuplestore */ rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; SetReturningFunctionCheck(rsinfo); plpgsql_check_info_init(&cinfo, fnoid); cinfo.show_profile = true; cinfo.proctuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(cinfo.fn_oid)); if (!HeapTupleIsValid(cinfo.proctuple)) elog(ERROR, "cache lookup failed for function %u", cinfo.fn_oid); plpgsql_check_get_function_info(&cinfo); plpgsql_check_precheck_conditions(&cinfo); plpgsql_check_init_ri(&ri, PLPGSQL_SHOW_PROFILE_STATEMENTS_TABULAR, rsinfo); plpgsql_check_iterate_over_profile(&cinfo, PLPGSQL_CHECK_STMT_WALKER_PREPARE_RESULT, &ri, NULL); plpgsql_check_finalize_ri(&ri); ReleaseSysCache(cinfo.proctuple); return (Datum) 0; } /* * Public functions */ Datum plpgsql_check_function(PG_FUNCTION_ARGS) { Oid fnoid; if (PG_ARGISNULL(0)) ERR_NULL_OPTION("funcoid"); fnoid = PG_GETARG_OID(0); return check_function_internal(fnoid, fcinfo); } Datum plpgsql_check_function_tb(PG_FUNCTION_ARGS) { Oid fnoid; if (PG_ARGISNULL(0)) ERR_NULL_OPTION("funcoid"); fnoid = PG_GETARG_OID(0); return check_function_tb_internal(fnoid, fcinfo); } Datum plpgsql_show_dependency_tb(PG_FUNCTION_ARGS) { Oid fnoid; if (PG_ARGISNULL(0)) ERR_NULL_OPTION("funcoid"); fnoid = PG_GETARG_OID(0); return show_dependency_tb_internal(fnoid, fcinfo); } Datum plpgsql_profiler_function_tb(PG_FUNCTION_ARGS) { Oid fnoid; if (PG_ARGISNULL(0)) ERR_NULL_OPTION("funcoid"); fnoid = PG_GETARG_OID(0); return profiler_function_tb_internal(fnoid, fcinfo); } Datum plpgsql_profiler_function_statements_tb(PG_FUNCTION_ARGS) { Oid fnoid; if (PG_ARGISNULL(0)) ERR_NULL_OPTION("funcoid"); fnoid = PG_GETARG_OID(0); return profiler_function_statements_tb_internal(fnoid, fcinfo); } Datum plpgsql_check_function_name(PG_FUNCTION_ARGS) { Oid fnoid; char *name_or_signature; if (PG_ARGISNULL(0)) ERR_NULL_OPTION("name"); name_or_signature = text_to_cstring(PG_GETARG_TEXT_PP(0)); fnoid = plpgsql_check_parse_name_or_signature(name_or_signature); return check_function_internal(fnoid, fcinfo); } Datum plpgsql_check_function_tb_name(PG_FUNCTION_ARGS) { Oid fnoid; char *name_or_signature; if (PG_ARGISNULL(0)) ERR_NULL_OPTION("name"); name_or_signature = text_to_cstring(PG_GETARG_TEXT_PP(0)); fnoid = plpgsql_check_parse_name_or_signature(name_or_signature); return check_function_tb_internal(fnoid, fcinfo); } Datum plpgsql_show_dependency_tb_name(PG_FUNCTION_ARGS) { Oid fnoid; char *name_or_signature; if (PG_ARGISNULL(0)) ERR_NULL_OPTION("name"); name_or_signature = text_to_cstring(PG_GETARG_TEXT_PP(0)); fnoid = plpgsql_check_parse_name_or_signature(name_or_signature); return show_dependency_tb_internal(fnoid, fcinfo); } Datum plpgsql_profiler_function_tb_name(PG_FUNCTION_ARGS) { Oid fnoid; char *name_or_signature; if (PG_ARGISNULL(0)) ERR_NULL_OPTION("name"); name_or_signature = text_to_cstring(PG_GETARG_TEXT_PP(0)); fnoid = plpgsql_check_parse_name_or_signature(name_or_signature); return profiler_function_tb_internal(fnoid, fcinfo); } Datum plpgsql_profiler_function_statements_tb_name(PG_FUNCTION_ARGS) { Oid fnoid; char *name_or_signature; if (PG_ARGISNULL(0)) ERR_NULL_OPTION("name"); name_or_signature = text_to_cstring(PG_GETARG_TEXT_PP(0)); fnoid = plpgsql_check_parse_name_or_signature(name_or_signature); return profiler_function_statements_tb_internal(fnoid, fcinfo); } Datum plpgsql_profiler_functions_all_tb(PG_FUNCTION_ARGS) { plpgsql_check_result_info ri; ReturnSetInfo *rsinfo; /* check to see if caller supports us returning a tuplestore */ rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; SetReturningFunctionCheck(rsinfo); plpgsql_check_init_ri(&ri, PLPGSQL_SHOW_PROFILE_FUNCTIONS_ALL_TABULAR, rsinfo); plpgsql_check_profiler_iterate_over_all_profiles(&ri); return (Datum) 0; } plpgsql_check-2.7.8/src/tracer.c000066400000000000000000001047101465410532300165700ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * tracer.c * * tracer related code * * by Pavel Stehule 2013-2024 * *------------------------------------------------------------------------- */ #include "plpgsql_check.h" #include "plpgsql_check_builtins.h" #include "catalog/pg_type.h" #include "mb/pg_wchar.h" #include "storage/proc.h" #include "utils/builtins.h" #include "utils/guc.h" #include "utils/lsyscache.h" #include "utils/memutils.h" #include "utils/syscache.h" bool plpgsql_check_enable_tracer = false; bool plpgsql_check_tracer = false; bool plpgsql_check_trace_assert = false; /* the output is modified for regress tests */ bool plpgsql_check_tracer_test_mode = false; bool plpgsql_check_tracer_show_nsubxids = false; PGErrorVerbosity plpgsql_check_tracer_verbosity = PGERROR_DEFAULT; PGErrorVerbosity plpgsql_check_trace_assert_verbosity = PGERROR_DEFAULT; int plpgsql_check_tracer_errlevel = NOTICE; int plpgsql_check_tracer_variable_max_length = 1024; static void print_datum(PLpgSQL_execstate *estate, PLpgSQL_datum *dtm, char *frame, int level); static char *convert_plpgsql_datum_to_string(PLpgSQL_execstate *estate, PLpgSQL_datum *dtm, bool *isnull, char **refname); PG_FUNCTION_INFO_V1(plpgsql_check_tracer_ctrl); #if PG_VERSION_NUM >= 140000 #define STREXPR_START 0 #else #define STREXPR_START 7 #endif /* * This structure is used as pldbgapi2 extension parameter */ typedef struct tracer_info { Oid fn_oid; int frame_num; char *fn_name; char *fn_signature; instr_time start_time; instr_time *stmts_start_time; bool *stmts_tracer_state; /* true when function is traced from func_beg */ bool is_traced; } tracer_info; static void trace_assert(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt, tracer_info *tinfo); static void tracer_func_setup(PLpgSQL_execstate *estate, PLpgSQL_function *func, void **plugin2_info); static void tracer_func_beg(PLpgSQL_execstate *estate, PLpgSQL_function *func, void **plugin2_info); static void tracer_func_end(PLpgSQL_execstate *estate, PLpgSQL_function *func, void **plugin2_info); static void tracer_func_end_aborted(Oid fn_oid, void **plugin2_info); static void tracer_stmt_beg(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt, void **plugin2_info); static void tracer_stmt_end(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt, void **plugin2_info); static void tracer_stmt_end_aborted(Oid fn_oid, int stmtid, void **plugin2_info); static plpgsql_check_plugin2 tracer_plugin2 = { tracer_func_setup, tracer_func_beg, tracer_func_end, tracer_func_end_aborted, tracer_stmt_beg, tracer_stmt_end, tracer_stmt_end_aborted, NULL, NULL, NULL, NULL, NULL }; /* * Convert binary value to text */ static char * convert_value_to_string(PLpgSQL_execstate *estate, Datum value, Oid valtype) { char *result; MemoryContext oldcontext; Oid typoutput; bool typIsVarlena; oldcontext = MemoryContextSwitchTo(estate->eval_econtext->ecxt_per_tuple_memory); getTypeOutputInfo(valtype, &typoutput, &typIsVarlena); result = OidOutputFunctionCall(typoutput, value); MemoryContextSwitchTo(oldcontext); return result; } static void StringInfoPrintRow(StringInfo ds, PLpgSQL_execstate *estate, PLpgSQL_row *row) { bool isfirst = true; int i; bool isnull; char *refname; appendStringInfoChar(ds, '('); for (i = 0; i < row->nfields; i++) { char *str; str = convert_plpgsql_datum_to_string(estate, estate->datums[row->varnos[i]], &isnull, &refname); if (!isfirst) appendStringInfoChar(ds, ','); else isfirst = false; if (!isnull) { if (*str) appendStringInfoString(ds, str); else appendStringInfoString(ds, "\"\""); pfree(str); } else appendStringInfoString(ds, ""); } appendStringInfoChar(ds, ')'); } static char * convert_plpgsql_datum_to_string(PLpgSQL_execstate *estate, PLpgSQL_datum *dtm, bool *isnull, char **refname) { *isnull = true; *refname = NULL; switch (dtm->dtype) { case PLPGSQL_DTYPE_VAR: { PLpgSQL_var *var = (PLpgSQL_var *) dtm; *refname = var->refname; if (!var->isnull) { *isnull = false; return convert_value_to_string(estate, var->value, var->datatype->typoid); } else return NULL; } break; case PLPGSQL_DTYPE_REC: { PLpgSQL_rec *rec = (PLpgSQL_rec *) dtm; *refname = rec->refname; if (rec->erh && !ExpandedRecordIsEmpty(rec->erh)) { *isnull = false; return convert_value_to_string(estate, ExpandedRecordGetDatum(rec->erh), rec->rectypeid); } else return NULL; } break; case PLPGSQL_DTYPE_ROW: { PLpgSQL_row *row = (PLpgSQL_row *) dtm; StringInfoData ds; *isnull = false; *refname = row->refname; initStringInfo(&ds); StringInfoPrintRow(&ds, estate, row); return ds.data; } default: ; } return NULL; } /* * Trim string value to n bytes */ static void trim_string(char *str, int n) { size_t l = strlen(str); if (l <= (size_t) n) return; if (pg_database_encoding_max_length() == 1) { str[n] = '\0'; return; } while (n > 0) { int mbl = pg_mblen(str); if (mbl > n) break; str += mbl; n -= mbl; } *str = '\0'; } /* * Print function's arguments */ static void print_func_args(PLpgSQL_execstate *estate, PLpgSQL_function *func, int frame_num, int level) { int i; int indent = level * 2 + (plpgsql_check_tracer_verbosity == PGERROR_VERBOSE ? 6 : 0); int frame_width = plpgsql_check_tracer_verbosity == PGERROR_VERBOSE ? 6 : 3; StringInfoData ds; initStringInfo(&ds); if (func->fn_is_trigger == PLPGSQL_DML_TRIGGER) { TriggerData *td = estate->trigdata; const char *trgtyp; const char *trgtime; const char *trgcmd; int rec_new_varno = func->new_varno; int rec_old_varno = func->old_varno; char buffer[20]; Assert(estate->trigdata); trgtyp = TRIGGER_FIRED_FOR_ROW(td->tg_event) ? "row" : "statement"; trgtime = TRIGGER_FIRED_BEFORE(td->tg_event) ? "before" : "after"; if (TRIGGER_FIRED_BY_INSERT(td->tg_event)) { trgcmd = " insert"; rec_old_varno = -1; } else if (TRIGGER_FIRED_BY_UPDATE(td->tg_event)) { trgcmd = " update"; } else if (TRIGGER_FIRED_BY_DELETE(td->tg_event)) { trgcmd = " delete"; rec_new_varno = -1; } else { trgcmd = ""; } elog(plpgsql_check_tracer_errlevel, "#%-*d%*s triggered by %s %s%s trigger", frame_width, frame_num, indent + 4, "", trgtime, trgtyp, trgcmd); sprintf(buffer, "%d", frame_num); if (rec_new_varno != -1) print_datum(estate, estate->datums[rec_new_varno], buffer, level); if (rec_old_varno != -1) print_datum(estate, estate->datums[rec_new_varno], buffer, level); } if (func->fn_is_trigger == PLPGSQL_EVENT_TRIGGER) { Assert(estate->evtrigdata); elog(plpgsql_check_tracer_errlevel, "#%-*d%*s triggered by event trigger", frame_width, frame_num, indent + 4, ""); } /* print value of arguments */ for (i = 0; i < func->fn_nargs; i++) { int n = func->fn_argvarnos[i]; bool isnull; char *refname; char *str; str = convert_plpgsql_datum_to_string(estate, estate->datums[n], &isnull, &refname); if (refname) { if (!isnull) { /* * when this output is too long or contains new line, print it * separately */ if (((int) strlen(str)) > plpgsql_check_tracer_variable_max_length || strchr(str, '\n') != NULL) { if (*ds.data) { elog(plpgsql_check_tracer_errlevel, "#%-*d%*s %s", frame_width, frame_num, indent + 4, "", ds.data); resetStringInfo(&ds); } trim_string(str, plpgsql_check_tracer_variable_max_length); elog(plpgsql_check_tracer_errlevel, "#%-*d%*s \"%s\" => '%s'", frame_width, frame_num, indent + 4, "", refname, str); } else { if (*ds.data) appendStringInfoString(&ds, ", "); appendStringInfo(&ds, "\"%s\" => '%s'", refname, str); } } else { if (*ds.data) appendStringInfoString(&ds, ", "); appendStringInfo(&ds, "\"%s\" => null", refname); } } if (str) pfree(str); /*print too long lines immediately */ if (ds.len > plpgsql_check_tracer_variable_max_length) { elog(plpgsql_check_tracer_errlevel, "#%-*d%*s %s", frame_width, frame_num, indent + 4, "", ds.data); resetStringInfo(&ds); } } if (*ds.data) elog(plpgsql_check_tracer_errlevel, "#%-*d%*s %s", frame_width, frame_num, indent + 4, "", ds.data); pfree(ds.data); } /* * Print expression's arguments */ static void print_expr_args(PLpgSQL_execstate *estate, PLpgSQL_expr *expr, char *frame, int level) { int dno; int indent = level * 2 + (plpgsql_check_tracer_verbosity == PGERROR_VERBOSE ? 6 : 0); int frame_width = plpgsql_check_tracer_verbosity == PGERROR_VERBOSE ? 6 : 3; StringInfoData ds; initStringInfo(&ds); /* * When expression hasn't assigned plan, then we cannot to show a related * variables. So we can enforce creating plan. */ if (!expr->plan) { SPIPlanPtr plan; #if PG_VERSION_NUM >= 140000 SPIPrepareOptions options; memset(&options, 0, sizeof(options)); options.parserSetup = (ParserSetupHook) plpgsql_check__parser_setup_p; options.parserSetupArg = (void *) expr; options.parseMode = expr->parseMode; options.cursorOptions = 0; #endif expr->func = estate->func; #if PG_VERSION_NUM >= 140000 /* * Generate and save the plan */ plan = SPI_prepare_extended(expr->query, &options); #else /* * Generate the plan (enforce expr query parsing) and throw plan */ plan = SPI_prepare_params(expr->query, (ParserSetupHook) plpgsql_check__parser_setup_p, (void *) expr, 0); #endif SPI_freeplan(plan); } /* print value of arguments */ dno = -1; while ((dno = bms_next_member(expr->paramnos, dno)) >= 0) { bool isnull; char *refname; char *str; str = convert_plpgsql_datum_to_string(estate, estate->datums[dno], &isnull, &refname); if (refname) { if (!isnull) { /* * when this output is too long or contains new line, print it * separately */ if (((int) strlen(str)) > plpgsql_check_tracer_variable_max_length || strchr(str, '\n') != NULL) { if (*ds.data) { elog(plpgsql_check_tracer_errlevel, "#%-*s%*s %s", frame_width, frame, indent + 4, "", ds.data); resetStringInfo(&ds); } trim_string(str, plpgsql_check_tracer_variable_max_length); elog(plpgsql_check_tracer_errlevel, "#%-*s%*s \"%s\" => '%s'", frame_width, frame, indent + 4, "", refname, str); } else { if (*ds.data) appendStringInfoString(&ds, ", "); appendStringInfo(&ds, "\"%s\" => '%s'", refname, str); } } else { if (*ds.data) appendStringInfoString(&ds, ", "); appendStringInfo(&ds, "\"%s\" => null", refname); } } if (str) pfree(str); /*print too long lines immediately */ if (ds.len > plpgsql_check_tracer_variable_max_length) { elog(plpgsql_check_tracer_errlevel, "#%-*s%*s %s", frame_width, frame, indent + 4, "", ds.data); resetStringInfo(&ds); } } if (*ds.data) elog(plpgsql_check_tracer_errlevel, "#%-*s%*s %s", frame_width, frame, indent + 4, "", ds.data); pfree(ds.data); } /* * Print expression's arguments */ static void print_assert_args(PLpgSQL_execstate *estate, PLpgSQL_stmt_assert *stmt) { int dno; StringInfoData ds; initStringInfo(&ds); /* * When expression hasn't assigned plan, then we cannot to show a related * variables. So we can enforce creating plan. */ if (!stmt->cond->plan) { SPIPlanPtr plan; #if PG_VERSION_NUM >= 140000 SPIPrepareOptions options; memset(&options, 0, sizeof(options)); options.parserSetup = (ParserSetupHook) plpgsql_check__parser_setup_p; options.parserSetupArg = (void *) stmt->cond; options.parseMode = stmt->cond->parseMode; options.cursorOptions = 0; #endif stmt->cond->func = estate->func; #if PG_VERSION_NUM >= 140000 /* * Generate and save the plan */ plan = SPI_prepare_extended((void *) stmt->cond->query, &options); #else /* * Generate the plan (enforce expr query parsing) and throw plan */ plan = SPI_prepare_params(stmt->cond->query, (ParserSetupHook) plpgsql_check__parser_setup_p, (void *) stmt->cond, 0); #endif SPI_freeplan(plan); } /* print value of arguments */ dno = -1; while ((dno = bms_next_member(stmt->cond->paramnos, dno)) >= 0) { bool isnull; char *refname; char *str; str = convert_plpgsql_datum_to_string(estate, estate->datums[dno], &isnull, &refname); if (refname) { if (!isnull) { /* * when this output is too long or contains new line, print it * separately */ if (((int) strlen(str)) > plpgsql_check_tracer_variable_max_length || strchr(str, '\n') != NULL) { if (*ds.data) { elog(plpgsql_check_tracer_errlevel, " %s", ds.data); resetStringInfo(&ds); } trim_string(str, plpgsql_check_tracer_variable_max_length); elog(plpgsql_check_tracer_errlevel, " \"%s\" => '%s'", refname, str); } else { if (*ds.data) appendStringInfoString(&ds, ", "); appendStringInfo(&ds, "\"%s\" => '%s'", refname, str); } } else { if (*ds.data) appendStringInfoString(&ds, ", "); appendStringInfo(&ds, "\"%s\" => null", refname); } } if (str) pfree(str); /*print too long lines immediately */ if (ds.len > plpgsql_check_tracer_variable_max_length) { elog(plpgsql_check_tracer_errlevel, " %s", ds.data); resetStringInfo(&ds); } } if (*ds.data) elog(plpgsql_check_tracer_errlevel, " %s", ds.data); pfree(ds.data); } /* * Print all frame variables */ static void print_all_variables(PLpgSQL_execstate *estate) { int dno; StringInfoData ds; bool indent = 1; initStringInfo(&ds); for (dno = 0; dno < estate->ndatums; dno++) { bool isnull; char *refname; char *str; if (dno == estate->found_varno) continue; str = convert_plpgsql_datum_to_string(estate, estate->datums[dno], &isnull, &refname); if (strcmp(refname, "*internal*") == 0 || strcmp(refname, "(unnamed row)") == 0) refname = NULL; if (refname) { if (!isnull) { /* * when this output is too long or contains new line, print it * separately */ if (((int) strlen(str)) > plpgsql_check_tracer_variable_max_length || strchr(str, '\n') != NULL) { if (*ds.data) { elog(plpgsql_check_tracer_errlevel, "%*s%s", indent, "", ds.data); indent = 2; resetStringInfo(&ds); } trim_string(str, plpgsql_check_tracer_variable_max_length); elog(plpgsql_check_tracer_errlevel, "%*s \"%s\" => '%s'", indent, "", refname, str); indent = 2; } else { if (*ds.data) appendStringInfoString(&ds, ", "); appendStringInfo(&ds, "\"%s\" => '%s'", refname, str); } } else { if (*ds.data) appendStringInfoString(&ds, ", "); appendStringInfo(&ds, "\"%s\" => null", refname); } } if (str) pfree(str); /*print too long lines immediately */ if (ds.len > plpgsql_check_tracer_variable_max_length) { elog(plpgsql_check_tracer_errlevel, "%*s%s", indent, "", ds.data); indent = 2; resetStringInfo(&ds); } } if (*ds.data) elog(plpgsql_check_tracer_errlevel, "%*s%s", indent, "", ds.data); pfree(ds.data); } /* * Print plpgsql datum */ static void print_datum(PLpgSQL_execstate *estate, PLpgSQL_datum *dtm, char *frame, int level) { int indent = level * 2 + (plpgsql_check_tracer_verbosity == PGERROR_VERBOSE ? 6 : 0); int frame_width = plpgsql_check_tracer_verbosity == PGERROR_VERBOSE ? 6 : 3; bool isnull; char *refname; char *str; str = convert_plpgsql_datum_to_string(estate, dtm, &isnull, &refname); if (refname) { if (!isnull) { trim_string(str, plpgsql_check_tracer_variable_max_length); elog(plpgsql_check_tracer_errlevel, "#%-*s%*s \"%s\" => '%s'", frame_width, frame, indent + 4, "", refname, str); } else elog(plpgsql_check_tracer_errlevel, "#%-*s%*s \"%s\" => null", frame_width, frame, indent + 4, "", refname); } if (str) pfree(str); } static void tracer_func_setup(PLpgSQL_execstate *estate, PLpgSQL_function *func, void **plugin2_info) { tracer_info *tinfo = NULL; if (plpgsql_check_enable_tracer) { tinfo = palloc0(sizeof(tracer_info)); tinfo->stmts_start_time = palloc0(sizeof(instr_time) * func->nstatements); tinfo->stmts_tracer_state = palloc(sizeof(bool) * func->nstatements); tinfo->fn_oid = func->fn_oid; tinfo->fn_name = plpgsql_check_get_current_func_info_name(); tinfo->fn_signature = plpgsql_check_get_current_func_info_signature(); INSTR_TIME_SET_CURRENT(tinfo->start_time); } *plugin2_info = tinfo; } /* * get_caller_estate - try to returns near outer estate */ static void get_outer_info(char **errcontextstr, int *frame_num) { ErrorContextCallback *econtext; MemoryContext oldcxt = CurrentMemoryContext; *errcontextstr = NULL; *frame_num = 0; for (econtext = error_context_stack->previous; econtext != NULL; econtext = econtext->previous) { *frame_num += 1; } if (plpgsql_check_tracer_verbosity >= PGERROR_DEFAULT && error_context_stack->previous) { ErrorData *edata; econtext = error_context_stack->previous; #if PG_VERSION_NUM >= 130000 errstart(ERROR, TEXTDOMAIN); #else errstart(ERROR, __FILE__, __LINE__, PG_FUNCNAME_MACRO, TEXTDOMAIN); #endif MemoryContextSwitchTo(oldcxt); (*econtext->callback)(econtext->arg); edata = CopyErrorData(); FlushErrorState(); if (edata->context) *errcontextstr = edata->context; MemoryContextSwitchTo(oldcxt); } } /* * Tracer event routines */ static void tracer_func_beg(PLpgSQL_execstate *estate, PLpgSQL_function *func, void **plugin2_info) { tracer_info *tinfo = *plugin2_info; char *caller_errcontext = NULL; Oid fn_oid; int indent; int frame_width; char buffer[30]; if (!tinfo) return; fn_oid = plpgsql_check_tracer_test_mode ? 0 : func->fn_oid; get_outer_info(&caller_errcontext, &tinfo->frame_num); if (!plpgsql_check_tracer) return; indent = tinfo->frame_num * 2 + (plpgsql_check_tracer_verbosity == PGERROR_VERBOSE ? 6 : 0); frame_width = plpgsql_check_tracer_verbosity == PGERROR_VERBOSE ? 6 : 3; if (plpgsql_check_tracer_show_nsubxids) { #if PG_VERSION_NUM >= 140000 if (MyProc->subxidStatus.overflowed) snprintf(buffer, 30, ", nxids=OF"); else snprintf(buffer, 30, ", nxids=%d", MyProc->subxidStatus.count); #else if (MyPgXact->overflowed) snprintf(buffer, 30, ", nxids=OF"); else snprintf(buffer, 30, ", nxids=%d", MyPgXact->nxids); #endif } else buffer[0] = '\0'; if (plpgsql_check_tracer_verbosity >= PGERROR_DEFAULT) elog(plpgsql_check_tracer_errlevel, "#%-*d%*s ->> start of %s%s (oid=%u, tnl=%d%s)", frame_width, tinfo->frame_num, indent, "", func->fn_oid ? "function " : "block ", func->fn_signature, fn_oid, GetCurrentTransactionNestLevel(), buffer); else elog(plpgsql_check_tracer_errlevel, "#%-*d start of %s (oid=%u, tnl=%d%s)", frame_width, tinfo->frame_num, func->fn_oid ? get_func_name(func->fn_oid) : "inline code block", fn_oid, GetCurrentTransactionNestLevel(), buffer); if (plpgsql_check_tracer_verbosity >= PGERROR_DEFAULT) { if (caller_errcontext) { elog(plpgsql_check_tracer_errlevel, "#%-*d%*s context: %s", frame_width, tinfo->frame_num, indent + 4, " ", caller_errcontext); pfree(caller_errcontext); } print_func_args(estate, func, tinfo->frame_num, tinfo->frame_num); } tinfo->is_traced = true; } /* * workhorse for tracer_func_end and tracer_func_end_aborted */ static void _tracer_func_end(tracer_info *tinfo, bool is_aborted) { instr_time end_time; uint64 elapsed; int indent; int frame_width; const char *aborted; Assert(tinfo); if (!tinfo->is_traced || !plpgsql_check_tracer) return; indent = tinfo->frame_num * 2 + (plpgsql_check_tracer_verbosity == PGERROR_VERBOSE ? 6 : 0); frame_width = plpgsql_check_tracer_verbosity == PGERROR_VERBOSE ? 6 : 3; aborted = is_aborted ? " aborted" : ""; INSTR_TIME_SET_CURRENT(end_time); INSTR_TIME_SUBTRACT(end_time, tinfo->start_time); elapsed = INSTR_TIME_GET_MICROSEC(end_time); /* For output in regress tests use immutable time 0.010 ms */ if (plpgsql_check_tracer_test_mode) elapsed = 10; if (plpgsql_check_tracer_verbosity >= PGERROR_DEFAULT) { if (OidIsValid(tinfo->fn_oid)) elog(plpgsql_check_tracer_errlevel, "#%-*d%*s <<- end of function %s (elapsed time=%.3f ms)%s", frame_width, tinfo->frame_num, indent, "", tinfo->fn_name, elapsed / 1000.0, aborted); else elog(plpgsql_check_tracer_errlevel, "#%-*d%*s <<- end of block (elapsed time=%.3f ms)%s", frame_width, tinfo->frame_num, indent, "", elapsed / 1000.0, aborted); } else elog(plpgsql_check_tracer_errlevel, "#%-3d end of %s%s", tinfo->frame_num, tinfo->fn_oid ? tinfo->fn_name : "inline code block", aborted); } static void tracer_func_end(PLpgSQL_execstate *estate, PLpgSQL_function *func, void **plugin2_info) { tracer_info *tinfo = *plugin2_info; if (!tinfo) return; Assert(tinfo->fn_oid == func->fn_oid); _tracer_func_end(tinfo, false); } static void tracer_func_end_aborted(Oid fn_oid, void **plugin2_info) { tracer_info *tinfo = *plugin2_info; if (!tinfo) return; Assert(tinfo->fn_oid == fn_oid); _tracer_func_end(tinfo, true); } static char * copy_string_part(char *dest, char *src, int n) { char *retval = dest; while (*src && n > 0) { int mbl = pg_mblen(src); memcpy(dest, src, mbl); src += mbl; dest += mbl; n -= mbl; } if (*src) { memcpy(dest, " ...", 3); dest += 3; } *dest = '\0'; return retval; } static void tracer_stmt_beg(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt, void **plugin2_info) { tracer_info *tinfo = *plugin2_info; plpgsql_check_plugin2_stmt_info *sinfo; int total_level; char buffer[20]; if (!tinfo) return; sinfo = plpgsql_check_get_current_stmt_info(stmt->stmtid); /* save current tracer state (enabled | disabled) */ tinfo->stmts_tracer_state[stmt->stmtid - 1] = plpgsql_check_tracer; /* don't trace invisible statements */ if (sinfo->is_invisible || !plpgsql_check_tracer) return; if (stmt->cmd_type == PLPGSQL_STMT_ASSERT && plpgsql_check_trace_assert) trace_assert(estate, stmt, tinfo); total_level = tinfo->frame_num + sinfo->level; if (plpgsql_check_tracer_show_nsubxids) { #if PG_VERSION_NUM >= 140000 if (MyProc->subxidStatus.overflowed) snprintf(buffer, 20, " (tnl=%d, nxids=OF)", GetCurrentTransactionNestLevel()); else snprintf(buffer, 20, " (tnl=%d, nxids=%d)", GetCurrentTransactionNestLevel(), MyProc->subxidStatus.count); #else if (MyPgXact->overflowed) snprintf(buffer, 20, " (tnl=%d, nxids=OF)", GetCurrentTransactionNestLevel()); else snprintf(buffer, 20, " (tnl=%d, nxids=%d)", GetCurrentTransactionNestLevel(), MyPgXact->nxids); #endif } else snprintf(buffer, 20, " (tnl=%d)", GetCurrentTransactionNestLevel()); if (plpgsql_check_tracer_verbosity == PGERROR_VERBOSE) { int indent = (tinfo->frame_num + sinfo->level) * 2; int frame_width = 6; char printbuf[20]; char exprbuf[200]; PLpgSQL_expr *expr = NULL; char *exprname = NULL; int retvarno = -1; bool is_assignment = false; bool is_perform = false; switch (stmt->cmd_type) { case PLPGSQL_STMT_PERFORM: expr = ((PLpgSQL_stmt_perform *) stmt)->expr; exprname = "perform"; is_perform = true; break; case PLPGSQL_STMT_ASSIGN: { PLpgSQL_stmt_assign *stmt_assign = (PLpgSQL_stmt_assign *) stmt; #if PG_VERSION_NUM >= 140000 PLpgSQL_datum *target = estate->datums[stmt_assign->varno]; expr = stmt_assign->expr; if (target->dtype == PLPGSQL_DTYPE_VAR) expr->target_param = target->dno; else expr->target_param = -1; #else expr = stmt_assign->expr; #endif exprname = "expr"; is_assignment = true; } break; case PLPGSQL_STMT_RETURN: expr = ((PLpgSQL_stmt_return *) stmt)->expr; retvarno = ((PLpgSQL_stmt_return *) stmt)->retvarno; exprname = "expr"; break; case PLPGSQL_STMT_ASSERT: expr = ((PLpgSQL_stmt_assert *) stmt)->cond; exprname = "expr"; break; case PLPGSQL_STMT_CALL: expr = ((PLpgSQL_stmt_call *) stmt)->expr; exprname = "expr"; break; case PLPGSQL_STMT_EXECSQL: expr = ((PLpgSQL_stmt_execsql *) stmt)->sqlstmt; exprname = "query"; break; case PLPGSQL_STMT_IF: expr = ((PLpgSQL_stmt_if *) stmt)->cond; exprname = "cond"; default: ; } INSTR_TIME_SET_CURRENT(tinfo->stmts_start_time[stmt->stmtid - 1]); snprintf(printbuf, 20, "%d.%d", tinfo->frame_num, sinfo->natural_id); if (expr) { int startpos; #if PG_VERSION_NUM >= 140000 if (strcmp(exprname, "perform") == 0) { startpos = 7; exprname = "expr"; } else startpos = 0; #else if (strcmp(exprname, "perform") == 0) { startpos = 7; exprname = "expr"; } else if (strcmp(exprname, "query") == 0) startpos = 0; else startpos = STREXPR_START; #endif if (is_assignment) { elog(plpgsql_check_tracer_errlevel, "#%-*s %4d %*s --> start of assignment %s%s", frame_width, printbuf, stmt->lineno, indent, "", copy_string_part(exprbuf, expr->query + startpos, 30), buffer); } else if (is_perform) { elog(plpgsql_check_tracer_errlevel, "#%-*s %4d %*s --> start of perform %s%s", frame_width, printbuf, stmt->lineno, indent, "", copy_string_part(exprbuf, expr->query + startpos, 30), buffer); } else { elog(plpgsql_check_tracer_errlevel, "#%-*s %4d %*s --> start of %s (%s='%s')%s", frame_width, printbuf, stmt->lineno, indent, "", plpgsql_check__stmt_typename_p(stmt), exprname, copy_string_part(exprbuf, expr->query + startpos, 30), buffer); } } else elog(plpgsql_check_tracer_errlevel, "#%-*s %4d %*s --> start of %s%s", frame_width, printbuf, stmt->lineno, indent, "", plpgsql_check__stmt_typename_p(stmt), buffer); if (expr) print_expr_args(estate, expr, printbuf, total_level); if (retvarno >= 0) print_datum(estate, estate->datums[retvarno], printbuf, total_level); switch (stmt->cmd_type) { case PLPGSQL_STMT_IF: { PLpgSQL_stmt_if *stmt_if = (PLpgSQL_stmt_if *) stmt; ListCell *lc; foreach (lc, stmt_if->elsif_list) { PLpgSQL_if_elsif *ifelseif = (PLpgSQL_if_elsif *) lfirst(lc); elog(plpgsql_check_tracer_errlevel, "#%-*s %4d %*s ELSEIF (expr='%s')", frame_width, printbuf, ifelseif->lineno, indent, "", copy_string_part(exprbuf, ifelseif->cond->query + STREXPR_START, 30)); print_expr_args(estate, ifelseif->cond, printbuf, total_level); } break; } default: ; } } } static void _tracer_stmt_end(tracer_info *tinfo, plpgsql_check_plugin2_stmt_info *sinfo, int stmtid, bool is_aborted) { const char *aborted = is_aborted ? " aborted" : ""; Assert(tinfo); Assert(sinfo); /* don't trace invisible statements */ if (sinfo->is_invisible) { if (sinfo->is_container) /* restore tracer state (enabled | disabled) */ plpgsql_check_tracer = tinfo->stmts_tracer_state[stmtid - 1]; return; } if (tinfo->stmts_tracer_state[stmtid - 1] && plpgsql_check_tracer_verbosity == PGERROR_VERBOSE) { int indent = (tinfo->frame_num + sinfo->level) * 2; int frame_width = 6; char printbuf[20]; uint64 elapsed = 0; if (!INSTR_TIME_IS_ZERO(tinfo->stmts_start_time[stmtid - 1])) { instr_time end_time; INSTR_TIME_SET_CURRENT(end_time); INSTR_TIME_SUBTRACT(end_time, tinfo->stmts_start_time[stmtid - 1]); elapsed = INSTR_TIME_GET_MICROSEC(end_time); if (plpgsql_check_tracer_test_mode) elapsed = 10; } snprintf(printbuf, 20, "%d.%d", tinfo->frame_num, stmtid); elog(plpgsql_check_tracer_errlevel, "#%-*s %*s <-- end of %s (elapsed time=%.3f ms)%s", frame_width, printbuf, indent, "", sinfo->typname, elapsed/1000.0, aborted); } if (sinfo->is_container) { /* restore tracer state (enabled | disabled) */ plpgsql_check_tracer = tinfo->stmts_tracer_state[stmtid - 1]; } } static void tracer_stmt_end(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt, void **plugin2_info) { tracer_info *tinfo = *plugin2_info; plpgsql_check_plugin2_stmt_info *sinfo; if (!tinfo) return; sinfo = plpgsql_check_get_current_stmt_info(stmt->stmtid); _tracer_stmt_end(tinfo, sinfo, stmt->stmtid, false); if (!plpgsql_check_tracer) return; if (plpgsql_check_tracer_verbosity == PGERROR_VERBOSE && stmt->cmd_type == PLPGSQL_STMT_ASSIGN && !sinfo->is_invisible) { char printbuf[20]; snprintf(printbuf, 20, "%d.%d", tinfo->frame_num, sinfo->natural_id); print_datum(estate, estate->datums[((PLpgSQL_stmt_assign *) stmt)->varno], printbuf, tinfo->frame_num + sinfo->level); } } static void tracer_stmt_end_aborted(Oid fn_oid, int stmtid, void **plugin2_info) { tracer_info *tinfo = *plugin2_info; plpgsql_check_plugin2_stmt_info *sinfo; if (!tinfo) return; sinfo = plpgsql_check_get_current_stmt_info(stmtid); _tracer_stmt_end(tinfo, sinfo, stmtid, true); } static void trace_assert(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt, tracer_info *tinfo) { PLpgSQL_var result; PLpgSQL_type typ; char exprbuf[200]; PLpgSQL_stmt_assert *stmt_assert = (PLpgSQL_stmt_assert *) stmt; memset(&result, 0, sizeof(result)); memset(&typ, 0, sizeof(typ)); result.dtype = PLPGSQL_DTYPE_VAR; result.refname = "*auxstorage*"; result.datatype = &typ; result.value = (Datum) 5; typ.typoid = BOOLOID; typ.ttype = PLPGSQL_TTYPE_SCALAR; typ.typlen = 1; typ.typbyval = true; typ.typtype = 'b'; tracer_plugin2.assign_expr(estate, (PLpgSQL_datum *) &result, stmt_assert->cond); if ((bool) result.value) { if (plpgsql_check_trace_assert_verbosity >= PGERROR_DEFAULT) { elog(plpgsql_check_tracer_errlevel, "PLpgSQL assert expression (%s) on line %d of %s is true", copy_string_part(exprbuf, stmt_assert->cond->query + STREXPR_START, 30), stmt->lineno, estate->func->fn_signature); print_assert_args(estate, stmt_assert); } } else { ErrorContextCallback *econtext; int frame_num = tinfo->frame_num; elog(plpgsql_check_tracer_errlevel, "#%d PLpgSQL assert expression (%s) on line %d of %s is false", frame_num, copy_string_part(exprbuf, stmt_assert->cond->query + STREXPR_START, 30), stmt->lineno, estate->func->fn_signature); print_all_variables(estate); /* Show stack and all variables in verbose mode */ if (plpgsql_check_trace_assert_verbosity >= PGERROR_DEFAULT) { for (econtext = error_context_stack->previous; econtext != NULL; econtext = econtext->previous) { frame_num -= 1; /* * We detect PLpgSQL related estate by known error callback function. * This is inspirated by PLDebugger. */ if (econtext->callback == (*plpgsql_check_plugin_var_ptr)->error_callback) { PLpgSQL_execstate *oestate = (PLpgSQL_execstate *) econtext->arg; if (oestate->err_stmt) elog(plpgsql_check_tracer_errlevel, "#%d PL/pgSQL function %s line %d at %s", frame_num, oestate->func->fn_signature, oestate->err_stmt->lineno, plpgsql_check__stmt_typename_p(oestate->err_stmt)); else elog(plpgsql_check_tracer_errlevel, "#%d PLpgSQL function %s", frame_num, oestate->func->fn_signature); if (plpgsql_check_trace_assert_verbosity == PGERROR_VERBOSE) print_all_variables(oestate); } } } } } void plpgsql_check_tracer_init(void) { plpgsql_check_register_pldbgapi2_plugin(&tracer_plugin2); } /* * Enable, disable, show state tracer */ Datum plpgsql_check_tracer_ctrl(PG_FUNCTION_ARGS) { char *optstr; bool result; #define OPTNAME_1 "plpgsql_check.tracer" #define OPTNAME_2 "plpgsql_check.tracer_verbosity" if (!PG_ARGISNULL(0)) { bool optval = PG_GETARG_BOOL(0); (void) set_config_option(OPTNAME_1, optval ? "on" : "off", (superuser() ? PGC_SUSET : PGC_USERSET), PGC_S_SESSION, GUC_ACTION_SET, true, 0, false); } if (!PG_ARGISNULL(1)) { char *optval = TextDatumGetCString(PG_GETARG_DATUM(1)); (void) set_config_option(OPTNAME_2, optval, (superuser() ? PGC_SUSET : PGC_USERSET), PGC_S_SESSION, GUC_ACTION_SET, true, 0, false); } optstr = GetConfigOptionByName(OPTNAME_1, NULL, false); if (strcmp(optstr, "on") == 0) { elog(NOTICE, "tracer is active"); result = true; } else { elog(NOTICE, "tracer is not active"); result = false; } optstr = GetConfigOptionByName(OPTNAME_2, NULL, false); elog(NOTICE, "tracer verbosity is %s", optstr); if (result && !plpgsql_check_enable_tracer) ereport(NOTICE, (errmsg("tracer is still blocked"), errdetail("The tracer should be enabled by the superuser for security reasons."), errhint("Execute \"set plpgsql_check.enable_tracer to on\" (superuser only)."))); PG_RETURN_BOOL(result); } plpgsql_check-2.7.8/src/typdesc.c000066400000000000000000000417651465410532300167750ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * typdesc.c * * deduction result tupdesc from expression * * by Pavel Stehule 2013-2024 * *------------------------------------------------------------------------- */ #include "plpgsql_check.h" #include "access/htup_details.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "executor/spi_priv.h" #include "nodes/nodeFuncs.h" #include "optimizer/clauses.h" #include "optimizer/optimizer.h" #include "utils/builtins.h" #include "utils/lsyscache.h" #include "utils/syscache.h" #include "utils/typcache.h" /* * Try to calculate procedure row target from used INOUT variables * */ PLpgSQL_row * plpgsql_check_CallExprGetRowTarget(PLpgSQL_checkstate *cstate, PLpgSQL_expr *CallExpr) { Node *node; FuncExpr *funcexpr; PLpgSQL_row *result = NULL; if (CallExpr->plan != NULL) { PLpgSQL_row *row; CachedPlanSource *plansource; CallStmt *stmt; HeapTuple tuple; Oid *argtypes; char **argnames; char *argmodes; int i; int nfields = 0; int numargs; #if PG_VERSION_NUM < 140000 List *funcargs; ListCell *lc; #endif plansource = plpgsql_check_get_plan_source(cstate, CallExpr->plan); /* * Get the original CallStmt */ node = linitial_node(Query, plansource->query_list)->utilityStmt; if (!IsA(node, CallStmt)) elog(ERROR, "returned row from not a CallStmt"); stmt = castNode(CallStmt, node); funcexpr = stmt->funcexpr; /* * Get the argument modes */ tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcexpr->funcid)); if (!HeapTupleIsValid(tuple)) elog(ERROR, "cache lookup failed for function %u", funcexpr->funcid); #if PG_VERSION_NUM < 140000 /* Extract function arguments, and expand any named-arg notation */ funcargs = expand_function_arguments(funcexpr->args, funcexpr->funcresulttype, tuple); get_func_arg_info(tuple, &argtypes, &argnames, &argmodes); numargs = list_length(funcargs); #else /* * Get the argument names and modes, so that we can deliver on-point error * messages when something is wrong. */ numargs = get_func_arg_info(tuple, &argtypes, &argnames, &argmodes); #endif ReleaseSysCache(tuple); row = (PLpgSQL_row *) palloc0(sizeof(PLpgSQL_row)); row->dtype = PLPGSQL_DTYPE_ROW; row->refname = NULL; row->dno = -1; row->lineno = -1; row->varnos = (int *) palloc(numargs * sizeof(int)); #if PG_VERSION_NUM < 140000 /* * Construct row */ i = 0; foreach(lc, funcargs) { Node *n = lfirst(lc); if (argmodes && (argmodes[i] == PROARGMODE_INOUT || argmodes[i] == PROARGMODE_OUT)) { if (IsA(n, Param)) { Param *param = (Param *) n; /* paramid is offset by 1 (see make_datum_param()) */ row->varnos[nfields++] = param->paramid - 1; plpgsql_check_is_assignable(cstate->estate, param->paramid - 1); } else { /* report error using parameter name, if available */ if (argnames && argnames[i] && argnames[i][0]) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("procedure parameter \"%s\" is an output parameter but corresponding argument is not writable", argnames[i]))); else ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("procedure parameter %d is an output parameter but corresponding argument is not writable", i + 1))); } } i++; } #else /* * Examine procedure's argument list. Each output arg position should be * an unadorned plpgsql variable (Datum), which we can insert into the row * Datum. */ nfields = 0; for (i = 0; i < numargs; i++) { if (argmodes && (argmodes[i] == PROARGMODE_INOUT || argmodes[i] == PROARGMODE_OUT)) { Node *n = list_nth(stmt->outargs, nfields); if (IsA(n, Param)) { Param *param = (Param *) n; int dno; /* paramid is offset by 1 (see make_datum_param()) */ dno = param->paramid - 1; /* must check assignability now, because grammar can't */ plpgsql_check_is_assignable(cstate->estate, dno); row->varnos[nfields++] = dno; } else { /* report error using parameter name, if available */ if (argnames && argnames[i] && argnames[i][0]) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("procedure parameter \"%s\" is an output parameter but corresponding argument is not writable", argnames[i]))); else ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("procedure parameter %d is an output parameter but corresponding argument is not writable", i + 1))); } } } Assert(nfields == list_length(stmt->outargs)); #endif row->nfields = nfields; /* Don't return empty row variable */ if (nfields > 0) { result = row; } else { pfree(row->varnos); pfree(row); } } else elog(ERROR, "there are no plan for query: \"%s\"", CallExpr->query); return result; } /* * Returns typoid, typmod associated with record variable */ void plpgsql_check_recvar_info(PLpgSQL_rec *rec, Oid *typoid, int32 *typmod) { if (rec->dtype != PLPGSQL_DTYPE_REC) elog(ERROR, "variable is not record type"); if (rec->rectypeid != RECORDOID) { if (typoid != NULL) *typoid = rec->rectypeid; if (typmod != NULL) *typmod = -1; } else if (recvar_tupdesc(rec) != NULL) { TupleDesc tdesc = recvar_tupdesc(rec); BlessTupleDesc(tdesc); if (typoid != NULL) *typoid = tdesc->tdtypeid; if (typmod != NULL) *typmod = tdesc->tdtypmod; } else { if (typoid != NULL) *typoid = RECORDOID; if (typmod != NULL) *typmod = -1; } } static TupleDesc param_get_desc(PLpgSQL_checkstate *cstate, Param *p) { TupleDesc rettupdesc = NULL; if (!type_is_rowtype(p->paramtype)) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("function does not return composite type, is not possible to identify composite type"))); if (p->paramkind == PARAM_EXTERN && p->paramid > 0 && p->location != -1) { int dno; PLpgSQL_var *var; /* * When paramid looks well and related datum is variable with same * type, then we can check, if this variable has sanitized content * already. */ dno = p->paramid - 1; var = (PLpgSQL_var *) cstate->estate->datums[dno]; if (!var->datatype || !OidIsValid(var->datatype->typoid) || var->datatype->typoid == 0xFFFFFFFF || var->datatype->typoid == p->paramtype) { TupleDesc rectupdesc; if (var->dtype == PLPGSQL_DTYPE_REC) { PLpgSQL_rec *rec = (PLpgSQL_rec *) var; Oid typoid; int32 typmod; plpgsql_check_recvar_info(rec, &typoid, &typmod); rectupdesc = lookup_rowtype_tupdesc_noerror(typoid, typmod, true); if (rectupdesc) { rettupdesc = CreateTupleDescCopy(rectupdesc); ReleaseTupleDesc(rectupdesc); } } else { rectupdesc = lookup_rowtype_tupdesc_noerror(p->paramtype, p->paramtypmod, true); if (rectupdesc != NULL) { rettupdesc = CreateTupleDescCopy(rectupdesc); ReleaseTupleDesc(rectupdesc); } } } } return rettupdesc; } /* * Try to deduce result tuple descriptor from polymorphic function * like fce(.., anyelement, ..) returns anyelement */ static TupleDesc pofce_get_desc(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr, FuncExpr *fn) { HeapTuple func_tuple; Form_pg_proc procStruct; Oid fnoid = fn->funcid; TupleDesc result = NULL; func_tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(fnoid)); if (!HeapTupleIsValid(func_tuple)) elog(ERROR, "cache lookup failed for function %u", fnoid); procStruct = (Form_pg_proc) GETSTRUCT(func_tuple); if (procStruct->prorettype == ANYELEMENTOID) { Oid *argtypes; char *argmodes; char **argnames; int pronallargs; int i; pronallargs = get_func_arg_info(func_tuple, &argtypes, &argnames, &argmodes); for (i = 0; i < pronallargs; i++) { if (argmodes && (argmodes[i] != PROARGMODE_IN && argmodes[i] != PROARGMODE_INOUT)) continue; if (argtypes[i] == ANYELEMENTOID) { if (IsA(list_nth(fn->args, i), Param)) { Param *p = (Param *) list_nth(fn->args, i); if (p->paramkind == PARAM_EXTERN && p->paramid > 0 && p->location != -1) { int dno = p->paramid - 1; /* * When paramid looks well and related datum is variable with same * type, then we can check, if this variable has sanitized content * already. */ if (expr && bms_is_member(dno, expr->paramnos)) { PLpgSQL_var *var = (PLpgSQL_var *) cstate->estate->datums[dno]; /* * When we know a datatype, then we expect eq with param type. * But sometimes a Oid of datatype is not valid - record type * for some older releases. What is worse - sometimes Oid is 0 * or FFFFFFFF. */ if (var->dtype == PLPGSQL_DTYPE_REC && (!var->datatype || !OidIsValid(var->datatype->typoid) || var->datatype->typoid == 0xFFFFFFFF || var->datatype->typoid == p->paramtype)) { PLpgSQL_rec *rec = (PLpgSQL_rec *) var; Oid typoid; int32 typmod; TupleDesc rectupdesc; plpgsql_check_recvar_info(rec, &typoid, &typmod); rectupdesc = lookup_rowtype_tupdesc_noerror(typoid, typmod, true); if (rectupdesc) { result = CreateTupleDescCopy(rectupdesc); ReleaseTupleDesc(rectupdesc); break; } } } } } } } if (argtypes) pfree(argtypes); if (argnames) pfree(argnames); if (argmodes) pfree(argmodes); } ReleaseSysCache(func_tuple); return result; } /* * Returns a tuple descriptor based on existing plan, When error is detected * returns null. Does hardwork when result is based on record type. * */ TupleDesc plpgsql_check_expr_get_desc(PLpgSQL_checkstate *cstate, PLpgSQL_expr *query, bool use_element_type, bool expand_record, bool is_expression, Oid *first_level_typoid) { TupleDesc tupdesc = NULL; CachedPlanSource *plansource = NULL; if (query->plan != NULL) { plansource = plpgsql_check_get_plan_source(cstate, query->plan); /* * The EXECUTE command can sucessfully execute empty string. * Then the plansource is empty - NULL. */ if (!plansource) return NULL; if (!plansource->resultDesc) { if (is_expression) elog(ERROR, "query returns no result"); else return NULL; } tupdesc = CreateTupleDescCopy(plansource->resultDesc); } else elog(ERROR, "there are no plan for query: \"%s\"", query->query); if (is_expression && tupdesc->natts != 1) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("query \"%s\" returned %d columns", query->query, tupdesc->natts))); /* * try to get a element type, when result is a array (used with FOREACH * ARRAY stmt) */ if (use_element_type) { Oid elemtype; /* result should be a array */ if (is_expression && tupdesc->natts != 1) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("query \"%s\" returned %d columns", query->query, tupdesc->natts))); /* check the type of the expression - must be an array */ elemtype = get_element_type(TupleDescAttr(tupdesc, 0)->atttypid); if (!OidIsValid(elemtype)) { ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("FOREACH expression must yield an array, not type %s", format_type_be(TupleDescAttr(tupdesc, 0)->atttypid)))); FreeTupleDesc(tupdesc); } if (is_expression && first_level_typoid != NULL) *first_level_typoid = elemtype; /* when elemtype is not composity, prepare single field tupdesc */ if (!type_is_rowtype(elemtype)) { TupleDesc rettupdesc; rettupdesc = CreateTemplateTupleDesc(1); TupleDescInitEntry(rettupdesc, 1, "__array_element__", elemtype, -1, 0); FreeTupleDesc(tupdesc); BlessTupleDesc(rettupdesc); tupdesc = rettupdesc; } else { TupleDesc elemtupdesc; elemtupdesc = lookup_rowtype_tupdesc_noerror(elemtype, -1, true); if (elemtupdesc != NULL) { FreeTupleDesc(tupdesc); tupdesc = CreateTupleDescCopy(elemtupdesc); ReleaseTupleDesc(elemtupdesc); } } } else { if (is_expression && first_level_typoid != NULL) *first_level_typoid = TupleDescAttr(tupdesc, 0)->atttypid; } /* * One spacial case is when record is assigned to composite type, then we * should to unpack composite type. */ if (tupdesc->tdtypeid == RECORDOID && tupdesc->tdtypmod == -1 && tupdesc->natts == 1 && expand_record) { TupleDesc unpack_tupdesc; unpack_tupdesc = lookup_rowtype_tupdesc_noerror(TupleDescAttr(tupdesc, 0)->atttypid, TupleDescAttr(tupdesc, 0)->atttypmod, true); if (unpack_tupdesc != NULL) { FreeTupleDesc(tupdesc); tupdesc = CreateTupleDescCopy(unpack_tupdesc); ReleaseTupleDesc(unpack_tupdesc); } } /* * There is special case, when returned tupdesc contains only unpined * record: rec := func_with_out_parameters(). IN this case we must to dig * more deep - we have to find oid of function and get their parameters, * * This is support for assign statement recvar := * func_with_out_parameters(..) * * XXX: Why don't we always do that? */ if (tupdesc->tdtypeid == RECORDOID && tupdesc->tdtypmod == -1 && tupdesc->natts == 1 && TupleDescAttr(tupdesc, 0)->atttypid == RECORDOID && TupleDescAttr(tupdesc, 0)->atttypmod == -1 && expand_record) { PlannedStmt *_stmt; Plan *_plan; TargetEntry *tle; CachedPlan *cplan; /* * When tupdesc is related to unpined record, we will try to check * plan if it is just function call and if it is then we can try to * derive a tupledes from function's description. */ #if PG_VERSION_NUM >= 140000 cplan = GetCachedPlan(plansource, NULL, NULL, NULL); #else cplan = GetCachedPlan(plansource, NULL, true, NULL); #endif _stmt = (PlannedStmt *) linitial(cplan->stmt_list); if (IsA(_stmt, PlannedStmt) &&_stmt->commandType == CMD_SELECT) { _plan = _stmt->planTree; if (IsA(_plan, Result) &&list_length(_plan->targetlist) == 1) { tle = (TargetEntry *) linitial(_plan->targetlist); switch (((Node *) tle->expr)->type) { case T_FuncExpr: { FuncExpr *fn = (FuncExpr *) tle->expr; FmgrInfo flinfo; LOCAL_FCINFO(fcinfo, 0); TupleDesc rd; Oid rt; TypeFuncClass tfc; fmgr_info(fn->funcid, &flinfo); flinfo.fn_expr = (Node *) fn; fcinfo->flinfo = &flinfo; fcinfo->resultinfo = NULL; tfc = get_call_result_type(fcinfo, &rt, &rd); if (tfc == TYPEFUNC_SCALAR || tfc == TYPEFUNC_OTHER) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("function does not return composite type, is not possible to identify composite type"))); FreeTupleDesc(tupdesc); if (rd) { BlessTupleDesc(rd); tupdesc = rd; } else { /* * for polymorphic function we can determine record typmod (and tupdesc) * from arguments. */ tupdesc = pofce_get_desc(cstate, query, fn); } } break; case T_RowExpr: { RowExpr *row = (RowExpr *) tle->expr; ListCell *lc_colname; ListCell *lc_arg; TupleDesc rettupdesc; int i = 1; rettupdesc = CreateTemplateTupleDesc(list_length(row->args)); forboth (lc_colname, row->colnames, lc_arg, row->args) { Node *arg = lfirst(lc_arg); char *name = strVal(lfirst(lc_colname)); TupleDescInitEntry(rettupdesc, i, name, exprType(arg), exprTypmod(arg), 0); i++; } FreeTupleDesc(tupdesc); BlessTupleDesc(rettupdesc); tupdesc = rettupdesc; } break; case T_Const: { Const *c = (Const *) tle->expr; FreeTupleDesc(tupdesc); #if PG_VERSION_NUM >= 140000 if (c->consttype == RECORDOID && c->consttypmod == -1 && !c->constisnull) #else if (c->consttype == RECORDOID && c->consttypmod == -1) #endif { Oid tupType; int32 tupTypmod; HeapTupleHeader rec = DatumGetHeapTupleHeader(c->constvalue); tupType = HeapTupleHeaderGetTypeId(rec); tupTypmod = HeapTupleHeaderGetTypMod(rec); tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); } else tupdesc = NULL; } break; case T_Param: { Param *p = (Param *) tle->expr; if (!type_is_rowtype(p->paramtype)) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("function does not return composite type, is not possible to identify composite type"))); FreeTupleDesc(tupdesc); tupdesc = param_get_desc(cstate, p); } break; default: /* cannot to take tupdesc */ FreeTupleDesc(tupdesc); tupdesc = NULL; } } } #if PG_VERSION_NUM >= 140000 ReleaseCachedPlan(cplan, NULL); #else ReleaseCachedPlan(cplan, true); #endif } return tupdesc; }