pax_global_header00006660000000000000000000000064141747247450014531gustar00rootroot0000000000000052 comment=0512aea8432791c71f38495fb9f8eca4b697692f plpgsql_check-2.1.2/000077500000000000000000000000001417472474500143525ustar00rootroot00000000000000plpgsql_check-2.1.2/.editorconfig000066400000000000000000000002671417472474500170340ustar00rootroot00000000000000root = 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.1.2/.gitignore000066400000000000000000000004111417472474500163360ustar00rootroot00000000000000# 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 objfiles.txt .deps/ *.gcno *.gcda *.gcov *.gcov.out lcov.info *.vcproj *.vcxproj win32ver.rc *.exe lib*dll.def lib*.pc /results plpgsql_check-2.1.2/.travis.yml000066400000000000000000000014011417472474500164570ustar00rootroot00000000000000# run the testsuite on travis-ci.org --- # run once for each of these env: - PGVERSION=10 - PGVERSION=11 - PGVERSION=12 - PGVERSION=13 # - PGVERSION=14 language: C dist: xenial sudo: required before_install: - sudo apt-get update -qq install: # remove all existing clusters - sudo rm -rf /etc/postgresql /var/lib/postgresql # upgrade postgresql-common for new apt.postgresql.org.sh - sudo apt-get install -y postgresql-common - sudo /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh -p -v $PGVERSION -i - sudo apt-get install -y bison flex libicu-dev libssl-dev - sudo -u postgres createuser --superuser $USER script: - make - sudo make install - make installcheck - if test -s regression.diffs; then cat regression.diffs; fi plpgsql_check-2.1.2/LICENSE000066400000000000000000000024651417472474500153660ustar00rootroot00000000000000# 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.1.2/META.json000066400000000000000000000027041417472474500157760ustar00rootroot00000000000000{ "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.1.2", "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.1.2" } }, "prereqs": { "runtime": { "requires": { "PostgreSQL": "10.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.1.2/Makefile000066400000000000000000000015011417472474500160070ustar00rootroot00000000000000# $PostgreSQL: pgsql/contrib/plpgsql_check/Makefile MODULE_big = plpgsql_check OBJS = $(patsubst %.c,%.o,$(wildcard src/*.c)) DATA = plpgsql_check--2.1.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 += -Wextra -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 ifeq ($(PORTNAME), darwin) override CFLAGS += -undefined dynamic_lookup endif override CFLAGS += -I$(top_builddir)/src/pl/plpgsql/src -Wall plpgsql_check-2.1.2/README.md000066400000000000000000001147211417472474500156370ustar00rootroot00000000000000plpgsql_check ============= I founded this project, because I wanted to publish the code I wrote in the last 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 - probably it will not be done in next years (now is Dec 2013). But written 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 you like it and if you would to join to development of this extension, register yourself to [postgresql extension hacking](https://groups.google.com/forum/#!forum/postgresql-extensions-hacking) google group. # Features * check fields of referenced database objects and types inside embedded SQL * using correct types of function parameters * unused variables and function argumens, unmodified OUT argumens * partially detection of dead code (due RETURN command) * detection of missing RETURN command in function * try to identify unwanted hidden casts, that can be performance issue like unused indexes * possibility to collect relations and functions used by function * possibility to check EXECUTE stmt agaist SQL injection vulnerability I invite any ideas, patches, bugreports. plpgsql_check is next generation of plpgsql_lint. It allows to check source code by explicit call plpgsql_check_function. PostgreSQL PostgreSQL 10, 11, 12, 13 and 14 are supported. The SQL statements inside PL/pgSQL functions are checked by validator for semantic errors. These errors can be found by 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 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 arguments * function name or function signature - these functions requires 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 for check of any trigger function. * `fatal_errors boolean DEFAULT true` - stop on first error * `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, .. * `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, .. * `performance_warnings boolean DEFAULT false` - performance related warnings like declared type with type modificator, casting, implicit casts in where clause (can be reason why index is not used), .. * `security_warnings boolean DEFAULT false` - security related checks like SQL injection vulnerability detection * `anyelementtype regtype DEFAULT 'int'` - a real type used instead anyelement type * `anyenumtype regtype DEFAULT '-'` - a real type used instead anyenum type * `anyrangetype regtype DEFAULT 'int4range'` - a real type used instead anyrange type * `anycompatibletype DEFAULT 'int'` - a real type used instead anycompatible type * `anycompatiblerangetype DEFAULT 'int4range'` - a real type used instead anycompatible range type * `without_warnings DEFAULT false` - disable all warnings * `all_warnings DEFAULT false` - enable all warnings * `newtable DEFAULT NULL`, `oldtable DEFAULT NULL` - the names of NEW or OLD transitive tables. These parameters are required when transitive tables are used. ## 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 a `oldtable` or `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'); ## Mass check You can use the plpgsql_check_function for mass check functions and mass check 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 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 Functions should be checked on start - plpgsql_check module must be loaded. ## Configuration 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 plpgsql_check_function. `fresh_start` means cold start. 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'; SELECT fx(10); -- run functions - function is checked before runtime starts it # Limits plpgsql_check should find almost all errors on really static code. When developer use some 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 (in enabled passive mode) and you should use it only in develop or preprod 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 should not be detected. This check can raise false alarms too - probably when variable is sanitized by other command or when value is of some compose type. ## Refcursors plpgsql_check should not 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 significant break for any static analyse. PLpgSQL cannot to set correct type for record variables and cannot to check a dependent SQLs and expressions. A solution is same like 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 to run "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); # Dependency list A function plpgsql_show_dependency_tb can show all functions 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) │ │ RELATION │ 36005 │ public │ myview │ │ │ RELATION │ 36002 │ public │ mytable │ │ └──────────┴───────┴────────┴─────────┴────────────────────────────┘ (4 rows) # Profiler The plpgsql_check contains simple profiler of plpgsql functions and procedures. It can work with/without a access to shared memory. It depends on `shared_preload_libraries` config. When plpgsql_check was 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 momory, the profile is stored in session memory. Due dependencies, `shared_preload_libraries` should to contains `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 are not shared memory, then the profile is limmitted just to active session. 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 hundred 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: A 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 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://bitbucket.org/openscg/plprofiler My extension is designed to be simple for use and practical. Nothing more or less. plprofiler is more complex. It build call graphs and from this graph it can creates flame graph of execution times. Both extensions can be used together with buildin 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 deep of error context stack). It allows to pair start end and 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. 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) Special feature of tracer is tracing of `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 ## 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 behave inside 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 get options from parameters of this function. These plpgsql_check options are valid to end of 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) * `status:check`,`status:tracer`, `status:other_warnings`, `status:performance_warnings`, `status:extra_warnings`,`status:security_warnings` * `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` * `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 ephereal table Pragmas `enable:tracer` and `disable:tracer`are active for Postgres 12 and higher # 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 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. Lern 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.1.2/TODO.md000066400000000000000000000000001417472474500154270ustar00rootroot00000000000000plpgsql_check-2.1.2/_config.yml000066400000000000000000000000321417472474500164740ustar00rootroot00000000000000theme: jekyll-theme-caymanplpgsql_check-2.1.2/expected/000077500000000000000000000000001417472474500161535ustar00rootroot00000000000000plpgsql_check-2.1.2/expected/README000066400000000000000000000004201417472474500170270ustar00rootroot00000000000000plpgsql_check_passive.out PostgreSQL 14 plpgsql_check_passive_1.out PostgreSQL 10, 11, 12, 13 plpgsql_check_active_1.out PostgreSQL 10 plpgsql_check_active_2.out PostgreSQL 11 plpgsql_check_active_3.out PostgreSQL 12, 13 plpgsql_check_active.out PostgreSQL 14 plpgsql_check-2.1.2/expected/plpgsql_check_active-10.out000066400000000000000000000000001417472474500232620ustar00rootroot00000000000000plpgsql_check-2.1.2/expected/plpgsql_check_active-11.out000066400000000000000000000100741417472474500232770ustar00rootroot00000000000000LOAD 'plpgsql'; CREATE EXTENSION IF NOT EXISTS plpgsql_check; NOTICE: extension "plpgsql_check" already exists, skipping set client_min_messages to notice; 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 LINE 1: CALL proc((select count(*) from pg_class)) ^ QUERY: CALL proc((select count(*) from pg_class)) CONTEXT: PL/pgSQL function testproc() line 3 at CALL 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 CONTEXT: PL/pgSQL function testproc() line 4 at CALL 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; plpgsql_check-2.1.2/expected/plpgsql_check_active-12.out000066400000000000000000000407201417472474500233010ustar00rootroot00000000000000LOAD 'plpgsql'; CREATE EXTENSION IF NOT EXISTS plpgsql_check; NOTICE: extension "plpgsql_check" already exists, skipping set client_min_messages to notice; 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 LINE 1: CALL proc((select count(*) from pg_class)) ^ QUERY: CALL proc((select count(*) from pg_class)) CONTEXT: PL/pgSQL function testproc() line 3 at CALL 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 CONTEXT: PL/pgSQL function testproc() line 4 at CALL 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) NOTICE: #0 "b" => '3' NOTICE: #1 ->> start of function nested_trace_test(integer) (oid=0) NOTICE: #1 call by 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) NOTICE: #1 call by 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) NOTICE: #1 call by 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) NOTICE: #0 "b" => '3' NOTICE: #0.1 4 --> start of FOR with integer loop variable NOTICE: #0.2 6 --> start of assignment nested_trace_test(r) NOTICE: #0.2 "r" => '0' NOTICE: #1 ->> start of function nested_trace_test(integer) (oid=0) NOTICE: #1 call by trace_test(integer) line 6 at assignment NOTICE: #1 "a" => '0' NOTICE: #1.1 3 --> start of RETURN (expr='a + 1') NOTICE: #1.1 "a" => '0' NOTICE: #1.1 <-- end of RETURN (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.2 "r" => '1' NOTICE: #0.2 6 --> start of assignment nested_trace_test(r) NOTICE: #0.2 "r" => '1' NOTICE: #1 ->> start of function nested_trace_test(integer) (oid=0) NOTICE: #1 call by trace_test(integer) line 6 at assignment NOTICE: #1 "a" => '1' NOTICE: #1.1 3 --> start of RETURN (expr='a + 1') NOTICE: #1.1 "a" => '1' NOTICE: #1.1 <-- end of RETURN (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.2 "r" => '2' NOTICE: #0.2 6 --> start of assignment nested_trace_test(r) NOTICE: #0.2 "r" => '2' NOTICE: #1 ->> start of function nested_trace_test(integer) (oid=0) NOTICE: #1 call by trace_test(integer) line 6 at assignment NOTICE: #1 "a" => '2' NOTICE: #1.1 3 --> start of RETURN (expr='a + 1') NOTICE: #1.1 "a" => '2' NOTICE: #1.1 <-- end of RETURN (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.2 "r" => '3' NOTICE: #0.1 <-- end of FOR with integer loop variable (elapsed time=0.010 ms) NOTICE: #0.3 8 --> start of RETURN NOTICE: #0.3 "r" => '3' NOTICE: #0.3 <-- end of RETURN (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) NOTICE: #0 "b" => '3' NOTICE: #0.1 4 --> start of FOR with integer loop variable NOTICE: #0.2 6 --> start of perform plpgsql_check_pragma('disable: .. NOTICE: #0.2 <-- end of PERFORM (elapsed time=0.010 ms) NOTICE: #0.2 6 --> start of perform plpgsql_check_pragma('disable: .. NOTICE: #0.2 <-- end of PERFORM (elapsed time=0.010 ms) NOTICE: #0.2 6 --> start of perform plpgsql_check_pragma('disable: .. 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.4 9 --> start of RETURN NOTICE: #0.4 "r" => '3' NOTICE: #0.4 <-- end of RETURN (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) NOTICE: #0 "b" => '3' NOTICE: #0.1 4 --> start of FOR with integer loop variable NOTICE: #0.2 6 --> start of perform plpgsql_check_pragma('disable: .. NOTICE: #0.2 <-- end of PERFORM (elapsed time=0.010 ms) NOTICE: #1.2 4 --> start of RETURN (expr='a + 1') NOTICE: #1.2 "a" => '0' NOTICE: #1.2 <-- end of RETURN (elapsed time=0.010 ms) NOTICE: #0.2 6 --> start of perform plpgsql_check_pragma('disable: .. NOTICE: #0.2 <-- end of PERFORM (elapsed time=0.010 ms) NOTICE: #1.2 4 --> start of RETURN (expr='a + 1') NOTICE: #1.2 "a" => '1' NOTICE: #1.2 <-- end of RETURN (elapsed time=0.010 ms) NOTICE: #0.2 6 --> start of perform plpgsql_check_pragma('disable: .. NOTICE: #0.2 <-- end of PERFORM (elapsed time=0.010 ms) NOTICE: #1.2 4 --> start of RETURN (expr='a + 1') NOTICE: #1.2 "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.4 9 --> start of RETURN NOTICE: #0.4 "r" => '3' NOTICE: #0.4 <-- end of RETURN (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) NOTICE: #0 "$1" => '4' NOTICE: #0.1 4 --> start of FOR with integer loop variable NOTICE: #0.2 5 --> start of assignment r + 1 NOTICE: #0.2 "r" => '0' NOTICE: #0.2 <-- end of assignment (elapsed time=0.010 ms) NOTICE: #0.2 "r" => '1' NOTICE: #0.2 5 --> start of assignment r + 1 NOTICE: #0.2 "r" => '1' NOTICE: #0.2 <-- end of assignment (elapsed time=0.010 ms) NOTICE: #0.2 "r" => '2' NOTICE: #0.2 5 --> start of assignment r + 1 NOTICE: #0.2 "r" => '2' NOTICE: #0.2 <-- end of assignment (elapsed time=0.010 ms) NOTICE: #0.2 "r" => '3' NOTICE: #0.2 5 --> start of assignment r + 1 NOTICE: #0.2 "r" => '3' NOTICE: #0.2 <-- end of assignment (elapsed time=0.010 ms) NOTICE: #0.2 "r" => '4' NOTICE: #0.1 <-- end of FOR with integer loop variable (elapsed time=0.010 ms) NOTICE: #0.3 7 --> start of assignment r + 10 NOTICE: #0.3 "r" => '4' NOTICE: #0.3 <-- end of assignment (elapsed time=0.010 ms) NOTICE: #0.3 "r" => '14' NOTICE: #0.4 8 --> start of RETURN NOTICE: #0.4 "r" => '14' NOTICE: #0.4 <-- end of RETURN (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) NOTICE: #0 "$1" => '4' NOTICE: #0.1 4 --> start of FOR with integer loop variable NOTICE: #0.2 5 --> start of perform plpgsql_check_pragma('disable: .. NOTICE: #0.2 <-- end of PERFORM (elapsed time=0.010 ms) NOTICE: #0.2 5 --> start of perform plpgsql_check_pragma('disable: .. NOTICE: #0.2 <-- end of PERFORM (elapsed time=0.010 ms) NOTICE: #0.2 5 --> start of perform plpgsql_check_pragma('disable: .. NOTICE: #0.2 <-- end of PERFORM (elapsed time=0.010 ms) NOTICE: #0.2 5 --> start of perform plpgsql_check_pragma('disable: .. 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.4 8 --> start of assignment r + 10 NOTICE: #0.4 "r" => '4' NOTICE: #0.4 <-- end of assignment (elapsed time=0.010 ms) NOTICE: #0.4 "r" => '14' NOTICE: #0.5 9 --> start of RETURN NOTICE: #0.5 "r" => '14' NOTICE: #0.5 <-- end of RETURN (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) NOTICE: #0 "$1" => '4' NOTICE: #0.1 4 --> start of perform plpgsql_check_pragma('disable: .. NOTICE: #0.1 <-- end of PERFORM (elapsed time=0.010 ms) NOTICE: #0.5 12 --> start of assignment r + 10 NOTICE: #0.5 "r" => '4' NOTICE: #0.5 <-- end of assignment (elapsed time=0.010 ms) NOTICE: #0.5 "r" => '14' NOTICE: #0.6 13 --> start of RETURN NOTICE: #0.6 "r" => '14' NOTICE: #0.6 <-- end of RETURN (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); plpgsql_check-2.1.2/expected/plpgsql_check_active-13.out000066400000000000000000000407201417472474500233020ustar00rootroot00000000000000LOAD 'plpgsql'; CREATE EXTENSION IF NOT EXISTS plpgsql_check; NOTICE: extension "plpgsql_check" already exists, skipping set client_min_messages to notice; 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 LINE 1: CALL proc((select count(*) from pg_class)) ^ QUERY: CALL proc((select count(*) from pg_class)) CONTEXT: PL/pgSQL function testproc() line 3 at CALL 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 CONTEXT: PL/pgSQL function testproc() line 4 at CALL 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) NOTICE: #0 "b" => '3' NOTICE: #1 ->> start of function nested_trace_test(integer) (oid=0) NOTICE: #1 call by 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) NOTICE: #1 call by 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) NOTICE: #1 call by 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) NOTICE: #0 "b" => '3' NOTICE: #0.1 4 --> start of FOR with integer loop variable NOTICE: #0.2 6 --> start of assignment nested_trace_test(r) NOTICE: #0.2 "r" => '0' NOTICE: #1 ->> start of function nested_trace_test(integer) (oid=0) NOTICE: #1 call by trace_test(integer) line 6 at assignment NOTICE: #1 "a" => '0' NOTICE: #1.1 3 --> start of RETURN (expr='a + 1') NOTICE: #1.1 "a" => '0' NOTICE: #1.1 <-- end of RETURN (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.2 "r" => '1' NOTICE: #0.2 6 --> start of assignment nested_trace_test(r) NOTICE: #0.2 "r" => '1' NOTICE: #1 ->> start of function nested_trace_test(integer) (oid=0) NOTICE: #1 call by trace_test(integer) line 6 at assignment NOTICE: #1 "a" => '1' NOTICE: #1.1 3 --> start of RETURN (expr='a + 1') NOTICE: #1.1 "a" => '1' NOTICE: #1.1 <-- end of RETURN (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.2 "r" => '2' NOTICE: #0.2 6 --> start of assignment nested_trace_test(r) NOTICE: #0.2 "r" => '2' NOTICE: #1 ->> start of function nested_trace_test(integer) (oid=0) NOTICE: #1 call by trace_test(integer) line 6 at assignment NOTICE: #1 "a" => '2' NOTICE: #1.1 3 --> start of RETURN (expr='a + 1') NOTICE: #1.1 "a" => '2' NOTICE: #1.1 <-- end of RETURN (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.2 "r" => '3' NOTICE: #0.1 <-- end of FOR with integer loop variable (elapsed time=0.010 ms) NOTICE: #0.3 8 --> start of RETURN NOTICE: #0.3 "r" => '3' NOTICE: #0.3 <-- end of RETURN (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) NOTICE: #0 "b" => '3' NOTICE: #0.1 4 --> start of FOR with integer loop variable NOTICE: #0.2 6 --> start of perform plpgsql_check_pragma('disable: .. NOTICE: #0.2 <-- end of PERFORM (elapsed time=0.010 ms) NOTICE: #0.2 6 --> start of perform plpgsql_check_pragma('disable: .. NOTICE: #0.2 <-- end of PERFORM (elapsed time=0.010 ms) NOTICE: #0.2 6 --> start of perform plpgsql_check_pragma('disable: .. 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.4 9 --> start of RETURN NOTICE: #0.4 "r" => '3' NOTICE: #0.4 <-- end of RETURN (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) NOTICE: #0 "b" => '3' NOTICE: #0.1 4 --> start of FOR with integer loop variable NOTICE: #0.2 6 --> start of perform plpgsql_check_pragma('disable: .. NOTICE: #0.2 <-- end of PERFORM (elapsed time=0.010 ms) NOTICE: #1.2 4 --> start of RETURN (expr='a + 1') NOTICE: #1.2 "a" => '0' NOTICE: #1.2 <-- end of RETURN (elapsed time=0.010 ms) NOTICE: #0.2 6 --> start of perform plpgsql_check_pragma('disable: .. NOTICE: #0.2 <-- end of PERFORM (elapsed time=0.010 ms) NOTICE: #1.2 4 --> start of RETURN (expr='a + 1') NOTICE: #1.2 "a" => '1' NOTICE: #1.2 <-- end of RETURN (elapsed time=0.010 ms) NOTICE: #0.2 6 --> start of perform plpgsql_check_pragma('disable: .. NOTICE: #0.2 <-- end of PERFORM (elapsed time=0.010 ms) NOTICE: #1.2 4 --> start of RETURN (expr='a + 1') NOTICE: #1.2 "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.4 9 --> start of RETURN NOTICE: #0.4 "r" => '3' NOTICE: #0.4 <-- end of RETURN (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) NOTICE: #0 "$1" => '4' NOTICE: #0.1 4 --> start of FOR with integer loop variable NOTICE: #0.2 5 --> start of assignment r + 1 NOTICE: #0.2 "r" => '0' NOTICE: #0.2 <-- end of assignment (elapsed time=0.010 ms) NOTICE: #0.2 "r" => '1' NOTICE: #0.2 5 --> start of assignment r + 1 NOTICE: #0.2 "r" => '1' NOTICE: #0.2 <-- end of assignment (elapsed time=0.010 ms) NOTICE: #0.2 "r" => '2' NOTICE: #0.2 5 --> start of assignment r + 1 NOTICE: #0.2 "r" => '2' NOTICE: #0.2 <-- end of assignment (elapsed time=0.010 ms) NOTICE: #0.2 "r" => '3' NOTICE: #0.2 5 --> start of assignment r + 1 NOTICE: #0.2 "r" => '3' NOTICE: #0.2 <-- end of assignment (elapsed time=0.010 ms) NOTICE: #0.2 "r" => '4' NOTICE: #0.1 <-- end of FOR with integer loop variable (elapsed time=0.010 ms) NOTICE: #0.3 7 --> start of assignment r + 10 NOTICE: #0.3 "r" => '4' NOTICE: #0.3 <-- end of assignment (elapsed time=0.010 ms) NOTICE: #0.3 "r" => '14' NOTICE: #0.4 8 --> start of RETURN NOTICE: #0.4 "r" => '14' NOTICE: #0.4 <-- end of RETURN (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) NOTICE: #0 "$1" => '4' NOTICE: #0.1 4 --> start of FOR with integer loop variable NOTICE: #0.2 5 --> start of perform plpgsql_check_pragma('disable: .. NOTICE: #0.2 <-- end of PERFORM (elapsed time=0.010 ms) NOTICE: #0.2 5 --> start of perform plpgsql_check_pragma('disable: .. NOTICE: #0.2 <-- end of PERFORM (elapsed time=0.010 ms) NOTICE: #0.2 5 --> start of perform plpgsql_check_pragma('disable: .. NOTICE: #0.2 <-- end of PERFORM (elapsed time=0.010 ms) NOTICE: #0.2 5 --> start of perform plpgsql_check_pragma('disable: .. 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.4 8 --> start of assignment r + 10 NOTICE: #0.4 "r" => '4' NOTICE: #0.4 <-- end of assignment (elapsed time=0.010 ms) NOTICE: #0.4 "r" => '14' NOTICE: #0.5 9 --> start of RETURN NOTICE: #0.5 "r" => '14' NOTICE: #0.5 <-- end of RETURN (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) NOTICE: #0 "$1" => '4' NOTICE: #0.1 4 --> start of perform plpgsql_check_pragma('disable: .. NOTICE: #0.1 <-- end of PERFORM (elapsed time=0.010 ms) NOTICE: #0.5 12 --> start of assignment r + 10 NOTICE: #0.5 "r" => '4' NOTICE: #0.5 <-- end of assignment (elapsed time=0.010 ms) NOTICE: #0.5 "r" => '14' NOTICE: #0.6 13 --> start of RETURN NOTICE: #0.6 "r" => '14' NOTICE: #0.6 <-- end of RETURN (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); plpgsql_check-2.1.2/expected/plpgsql_check_active-14.out000066400000000000000000000407661417472474500233150ustar00rootroot00000000000000LOAD 'plpgsql'; CREATE EXTENSION IF NOT EXISTS plpgsql_check; NOTICE: extension "plpgsql_check" already exists, skipping set client_min_messages to notice; 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 LINE 1: call proc((select count(*) from pg_class)) ^ QUERY: call proc((select count(*) from pg_class)) CONTEXT: PL/pgSQL function testproc() line 3 at CALL 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 CONTEXT: PL/pgSQL function testproc() line 4 at CALL 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) NOTICE: #0 "b" => '3' NOTICE: #1 ->> start of function nested_trace_test(integer) (oid=0) NOTICE: #1 call by 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) NOTICE: #1 call by 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) NOTICE: #1 call by 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) NOTICE: #0 "b" => '3' NOTICE: #0.1 4 --> start of FOR with integer loop variable NOTICE: #0.2 6 --> start of assignment r := nested_trace_test(r) NOTICE: #0.2 "r" => '0' NOTICE: #1 ->> start of function nested_trace_test(integer) (oid=0) NOTICE: #1 call by trace_test(integer) line 6 at assignment NOTICE: #1 "a" => '0' NOTICE: #1.1 3 --> start of RETURN (expr='a + 1') NOTICE: #1.1 "a" => '0' NOTICE: #1.1 <-- end of RETURN (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.2 "r" => '1' NOTICE: #0.2 6 --> start of assignment r := nested_trace_test(r) NOTICE: #0.2 "r" => '1' NOTICE: #1 ->> start of function nested_trace_test(integer) (oid=0) NOTICE: #1 call by trace_test(integer) line 6 at assignment NOTICE: #1 "a" => '1' NOTICE: #1.1 3 --> start of RETURN (expr='a + 1') NOTICE: #1.1 "a" => '1' NOTICE: #1.1 <-- end of RETURN (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.2 "r" => '2' NOTICE: #0.2 6 --> start of assignment r := nested_trace_test(r) NOTICE: #0.2 "r" => '2' NOTICE: #1 ->> start of function nested_trace_test(integer) (oid=0) NOTICE: #1 call by trace_test(integer) line 6 at assignment NOTICE: #1 "a" => '2' NOTICE: #1.1 3 --> start of RETURN (expr='a + 1') NOTICE: #1.1 "a" => '2' NOTICE: #1.1 <-- end of RETURN (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.2 "r" => '3' NOTICE: #0.1 <-- end of FOR with integer loop variable (elapsed time=0.010 ms) NOTICE: #0.3 8 --> start of RETURN NOTICE: #0.3 "r" => '3' NOTICE: #0.3 <-- end of RETURN (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) NOTICE: #0 "b" => '3' NOTICE: #0.1 4 --> start of FOR with integer loop variable NOTICE: #0.2 6 --> start of perform plpgsql_check_pragma('disable: .. NOTICE: #0.2 <-- end of PERFORM (elapsed time=0.010 ms) NOTICE: #0.2 6 --> start of perform plpgsql_check_pragma('disable: .. NOTICE: #0.2 <-- end of PERFORM (elapsed time=0.010 ms) NOTICE: #0.2 6 --> start of perform plpgsql_check_pragma('disable: .. 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.4 9 --> start of RETURN NOTICE: #0.4 "r" => '3' NOTICE: #0.4 <-- end of RETURN (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) NOTICE: #0 "b" => '3' NOTICE: #0.1 4 --> start of FOR with integer loop variable NOTICE: #0.2 6 --> start of perform plpgsql_check_pragma('disable: .. NOTICE: #0.2 <-- end of PERFORM (elapsed time=0.010 ms) NOTICE: #1.2 4 --> start of RETURN (expr='a + 1') NOTICE: #1.2 "a" => '0' NOTICE: #1.2 <-- end of RETURN (elapsed time=0.010 ms) NOTICE: #0.2 6 --> start of perform plpgsql_check_pragma('disable: .. NOTICE: #0.2 <-- end of PERFORM (elapsed time=0.010 ms) NOTICE: #1.2 4 --> start of RETURN (expr='a + 1') NOTICE: #1.2 "a" => '1' NOTICE: #1.2 <-- end of RETURN (elapsed time=0.010 ms) NOTICE: #0.2 6 --> start of perform plpgsql_check_pragma('disable: .. NOTICE: #0.2 <-- end of PERFORM (elapsed time=0.010 ms) NOTICE: #1.2 4 --> start of RETURN (expr='a + 1') NOTICE: #1.2 "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.4 9 --> start of RETURN NOTICE: #0.4 "r" => '3' NOTICE: #0.4 <-- end of RETURN (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) NOTICE: #0 "$1" => '4' NOTICE: #0.1 4 --> start of FOR with integer loop variable NOTICE: #0.2 5 --> start of assignment r := r + 1 NOTICE: #0.2 "r" => '0' NOTICE: #0.2 <-- end of assignment (elapsed time=0.010 ms) NOTICE: #0.2 "r" => '1' NOTICE: #0.2 5 --> start of assignment r := r + 1 NOTICE: #0.2 "r" => '1' NOTICE: #0.2 <-- end of assignment (elapsed time=0.010 ms) NOTICE: #0.2 "r" => '2' NOTICE: #0.2 5 --> start of assignment r := r + 1 NOTICE: #0.2 "r" => '2' NOTICE: #0.2 <-- end of assignment (elapsed time=0.010 ms) NOTICE: #0.2 "r" => '3' NOTICE: #0.2 5 --> start of assignment r := r + 1 NOTICE: #0.2 "r" => '3' NOTICE: #0.2 <-- end of assignment (elapsed time=0.010 ms) NOTICE: #0.2 "r" => '4' NOTICE: #0.1 <-- end of FOR with integer loop variable (elapsed time=0.010 ms) NOTICE: #0.3 7 --> start of assignment r := r + 10 NOTICE: #0.3 "r" => '4' NOTICE: #0.3 <-- end of assignment (elapsed time=0.010 ms) NOTICE: #0.3 "r" => '14' NOTICE: #0.4 8 --> start of RETURN NOTICE: #0.4 "r" => '14' NOTICE: #0.4 <-- end of RETURN (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) NOTICE: #0 "$1" => '4' NOTICE: #0.1 4 --> start of FOR with integer loop variable NOTICE: #0.2 5 --> start of perform plpgsql_check_pragma('disable: .. NOTICE: #0.2 <-- end of PERFORM (elapsed time=0.010 ms) NOTICE: #0.2 5 --> start of perform plpgsql_check_pragma('disable: .. NOTICE: #0.2 <-- end of PERFORM (elapsed time=0.010 ms) NOTICE: #0.2 5 --> start of perform plpgsql_check_pragma('disable: .. NOTICE: #0.2 <-- end of PERFORM (elapsed time=0.010 ms) NOTICE: #0.2 5 --> start of perform plpgsql_check_pragma('disable: .. 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.4 8 --> start of assignment r := r + 10 NOTICE: #0.4 "r" => '4' NOTICE: #0.4 <-- end of assignment (elapsed time=0.010 ms) NOTICE: #0.4 "r" => '14' NOTICE: #0.5 9 --> start of RETURN NOTICE: #0.5 "r" => '14' NOTICE: #0.5 <-- end of RETURN (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) NOTICE: #0 "$1" => '4' NOTICE: #0.1 4 --> start of perform plpgsql_check_pragma('disable: .. NOTICE: #0.1 <-- end of PERFORM (elapsed time=0.010 ms) NOTICE: #0.5 12 --> start of assignment r := r + 10 NOTICE: #0.5 "r" => '4' NOTICE: #0.5 <-- end of assignment (elapsed time=0.010 ms) NOTICE: #0.5 "r" => '14' NOTICE: #0.6 13 --> start of RETURN NOTICE: #0.6 "r" => '14' NOTICE: #0.6 <-- end of RETURN (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); plpgsql_check-2.1.2/expected/plpgsql_check_active-15.out000066400000000000000000000407661417472474500233160ustar00rootroot00000000000000LOAD 'plpgsql'; CREATE EXTENSION IF NOT EXISTS plpgsql_check; NOTICE: extension "plpgsql_check" already exists, skipping set client_min_messages to notice; 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 LINE 1: call proc((select count(*) from pg_class)) ^ QUERY: call proc((select count(*) from pg_class)) CONTEXT: PL/pgSQL function testproc() line 3 at CALL 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 CONTEXT: PL/pgSQL function testproc() line 4 at CALL 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) NOTICE: #0 "b" => '3' NOTICE: #1 ->> start of function nested_trace_test(integer) (oid=0) NOTICE: #1 call by 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) NOTICE: #1 call by 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) NOTICE: #1 call by 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) NOTICE: #0 "b" => '3' NOTICE: #0.1 4 --> start of FOR with integer loop variable NOTICE: #0.2 6 --> start of assignment r := nested_trace_test(r) NOTICE: #0.2 "r" => '0' NOTICE: #1 ->> start of function nested_trace_test(integer) (oid=0) NOTICE: #1 call by trace_test(integer) line 6 at assignment NOTICE: #1 "a" => '0' NOTICE: #1.1 3 --> start of RETURN (expr='a + 1') NOTICE: #1.1 "a" => '0' NOTICE: #1.1 <-- end of RETURN (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.2 "r" => '1' NOTICE: #0.2 6 --> start of assignment r := nested_trace_test(r) NOTICE: #0.2 "r" => '1' NOTICE: #1 ->> start of function nested_trace_test(integer) (oid=0) NOTICE: #1 call by trace_test(integer) line 6 at assignment NOTICE: #1 "a" => '1' NOTICE: #1.1 3 --> start of RETURN (expr='a + 1') NOTICE: #1.1 "a" => '1' NOTICE: #1.1 <-- end of RETURN (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.2 "r" => '2' NOTICE: #0.2 6 --> start of assignment r := nested_trace_test(r) NOTICE: #0.2 "r" => '2' NOTICE: #1 ->> start of function nested_trace_test(integer) (oid=0) NOTICE: #1 call by trace_test(integer) line 6 at assignment NOTICE: #1 "a" => '2' NOTICE: #1.1 3 --> start of RETURN (expr='a + 1') NOTICE: #1.1 "a" => '2' NOTICE: #1.1 <-- end of RETURN (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.2 "r" => '3' NOTICE: #0.1 <-- end of FOR with integer loop variable (elapsed time=0.010 ms) NOTICE: #0.3 8 --> start of RETURN NOTICE: #0.3 "r" => '3' NOTICE: #0.3 <-- end of RETURN (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) NOTICE: #0 "b" => '3' NOTICE: #0.1 4 --> start of FOR with integer loop variable NOTICE: #0.2 6 --> start of perform plpgsql_check_pragma('disable: .. NOTICE: #0.2 <-- end of PERFORM (elapsed time=0.010 ms) NOTICE: #0.2 6 --> start of perform plpgsql_check_pragma('disable: .. NOTICE: #0.2 <-- end of PERFORM (elapsed time=0.010 ms) NOTICE: #0.2 6 --> start of perform plpgsql_check_pragma('disable: .. 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.4 9 --> start of RETURN NOTICE: #0.4 "r" => '3' NOTICE: #0.4 <-- end of RETURN (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) NOTICE: #0 "b" => '3' NOTICE: #0.1 4 --> start of FOR with integer loop variable NOTICE: #0.2 6 --> start of perform plpgsql_check_pragma('disable: .. NOTICE: #0.2 <-- end of PERFORM (elapsed time=0.010 ms) NOTICE: #1.2 4 --> start of RETURN (expr='a + 1') NOTICE: #1.2 "a" => '0' NOTICE: #1.2 <-- end of RETURN (elapsed time=0.010 ms) NOTICE: #0.2 6 --> start of perform plpgsql_check_pragma('disable: .. NOTICE: #0.2 <-- end of PERFORM (elapsed time=0.010 ms) NOTICE: #1.2 4 --> start of RETURN (expr='a + 1') NOTICE: #1.2 "a" => '1' NOTICE: #1.2 <-- end of RETURN (elapsed time=0.010 ms) NOTICE: #0.2 6 --> start of perform plpgsql_check_pragma('disable: .. NOTICE: #0.2 <-- end of PERFORM (elapsed time=0.010 ms) NOTICE: #1.2 4 --> start of RETURN (expr='a + 1') NOTICE: #1.2 "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.4 9 --> start of RETURN NOTICE: #0.4 "r" => '3' NOTICE: #0.4 <-- end of RETURN (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) NOTICE: #0 "$1" => '4' NOTICE: #0.1 4 --> start of FOR with integer loop variable NOTICE: #0.2 5 --> start of assignment r := r + 1 NOTICE: #0.2 "r" => '0' NOTICE: #0.2 <-- end of assignment (elapsed time=0.010 ms) NOTICE: #0.2 "r" => '1' NOTICE: #0.2 5 --> start of assignment r := r + 1 NOTICE: #0.2 "r" => '1' NOTICE: #0.2 <-- end of assignment (elapsed time=0.010 ms) NOTICE: #0.2 "r" => '2' NOTICE: #0.2 5 --> start of assignment r := r + 1 NOTICE: #0.2 "r" => '2' NOTICE: #0.2 <-- end of assignment (elapsed time=0.010 ms) NOTICE: #0.2 "r" => '3' NOTICE: #0.2 5 --> start of assignment r := r + 1 NOTICE: #0.2 "r" => '3' NOTICE: #0.2 <-- end of assignment (elapsed time=0.010 ms) NOTICE: #0.2 "r" => '4' NOTICE: #0.1 <-- end of FOR with integer loop variable (elapsed time=0.010 ms) NOTICE: #0.3 7 --> start of assignment r := r + 10 NOTICE: #0.3 "r" => '4' NOTICE: #0.3 <-- end of assignment (elapsed time=0.010 ms) NOTICE: #0.3 "r" => '14' NOTICE: #0.4 8 --> start of RETURN NOTICE: #0.4 "r" => '14' NOTICE: #0.4 <-- end of RETURN (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) NOTICE: #0 "$1" => '4' NOTICE: #0.1 4 --> start of FOR with integer loop variable NOTICE: #0.2 5 --> start of perform plpgsql_check_pragma('disable: .. NOTICE: #0.2 <-- end of PERFORM (elapsed time=0.010 ms) NOTICE: #0.2 5 --> start of perform plpgsql_check_pragma('disable: .. NOTICE: #0.2 <-- end of PERFORM (elapsed time=0.010 ms) NOTICE: #0.2 5 --> start of perform plpgsql_check_pragma('disable: .. NOTICE: #0.2 <-- end of PERFORM (elapsed time=0.010 ms) NOTICE: #0.2 5 --> start of perform plpgsql_check_pragma('disable: .. 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.4 8 --> start of assignment r := r + 10 NOTICE: #0.4 "r" => '4' NOTICE: #0.4 <-- end of assignment (elapsed time=0.010 ms) NOTICE: #0.4 "r" => '14' NOTICE: #0.5 9 --> start of RETURN NOTICE: #0.5 "r" => '14' NOTICE: #0.5 <-- end of RETURN (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) NOTICE: #0 "$1" => '4' NOTICE: #0.1 4 --> start of perform plpgsql_check_pragma('disable: .. NOTICE: #0.1 <-- end of PERFORM (elapsed time=0.010 ms) NOTICE: #0.5 12 --> start of assignment r := r + 10 NOTICE: #0.5 "r" => '4' NOTICE: #0.5 <-- end of assignment (elapsed time=0.010 ms) NOTICE: #0.5 "r" => '14' NOTICE: #0.6 13 --> start of RETURN NOTICE: #0.6 "r" => '14' NOTICE: #0.6 <-- end of RETURN (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); plpgsql_check-2.1.2/expected/plpgsql_check_active.out000066400000000000000000011567411417472474500230750ustar00rootroot00000000000000load '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(); -- 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 overlapped Detail: Local variable overlap 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 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); 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) RELATION | public | mytable | RELATION | public | myview | (6 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; -- 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) drop function rrecord01(); drop function rrecord02(); 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 select 10 a, 20 b into r; for r in execute v loop raise notice '%', r.a; end loop; end $$ language plpgsql; -- should be warning select * from plpgsql_check_function('dyn_sql_3'); plpgsql_check_function --------------------------------------------------------------------------------------- warning:00000:7:FOR over EXECUTE statement:cannot determinate a result of dynamic SQL Detail: There is a risk of related false alarms. Hint: Don't use dynamic SQL and record type together, when you would check function. (3 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 --------+------------+----------------- 0 | 1 | statement block 1 | 1 | assignment 2 | 1 | IF 3 | 0 | assignment 4 | 1 | assignment 5 | 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 --------+------------+----------------- 0 | 2 | statement block 1 | 2 | assignment 2 | 2 | IF 3 | 1 | assignment 4 | 2 | assignment 5 | 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) NOTICE: #0 "a" => '10', "b" => '20', "c" => '08-15-2020', "d" => '3.14' NOTICE: #2 ->> start of function tracer_tab_trg_fx() (oid=0) NOTICE: #2 call by fxo(integer,integer,date,numeric) line 3 at SQL statement 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) NOTICE: #0 "a" => '11', "b" => '21', "c" => '08-16-2020', "d" => '6.28' NOTICE: #2 ->> start of function tracer_tab_trg_fx() (oid=0) NOTICE: #2 call by fxo(integer,integer,date,numeric) line 3 at SQL statement 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 ""%I"" (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 | 0 | begin 3 | 2 | 0 | 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 | 0 | begin 3 | 2 | 0 | 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; plpgsql_check-2.1.2/expected/plpgsql_check_active_1.out000066400000000000000000011626211417472474500233070ustar00rootroot00000000000000load '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(); -- 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 "$1" warning extra:00000:unmodified OUT variable "$2" warning extra:00000:unmodified OUT variable "$3" (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 (1 row) 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 | | | 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 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: cannot begin/end transactions in PL/pgSQL HINT: Use a BEGIN block with an EXCEPTION clause instead. CONTEXT: PL/pgSQL function fxx() line 3 at SQL statement select * from plpgsql_check_function('fxx()'); plpgsql_check_function ----------------------------------------------------------------------- error:0A000:3:SQL statement:cannot begin/end transactions in PL/pgSQL Query: rollback -- ^ Hint: Use a BEGIN block with an EXCEPTION clause instead. (4 rows) 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 "$2" warning extra:00000:unmodified OUT variable "$4" (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 "$1" warning extra:00000:unused parameter "$2" warning extra:00000:unmodified OUT variable "$4" (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 "$2" warning extra:00000:composite OUT variable "$3" is not single argument warning extra:00000:composite OUT variable "$4" is not single argument warning extra:00000:unmodified OUT variable "$4" (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 "$1" warning extra:00000:unused parameter "$2" warning extra:00000:composite OUT variable "$3" is not single argument warning extra:00000:composite OUT variable "$4" is not single argument warning extra:00000:unmodified OUT variable "$4" (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 overlapped Detail: Local variable overlap 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 "$1" (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 "xt.a" warning extra:00000:unmodified OUT variable "xt.b" warning extra:00000:unmodified OUT variable "xt.c" (5 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" warning extra:00000:unmodified OUT variable "xt.a" warning extra:00000:unmodified OUT variable "xt.b" (4 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 "$1" is not single argument warning extra:00000:unmodified OUT variable "$1" warning extra:00000:composite OUT variable "$2" is not single argument warning extra:00000:unmodified OUT variable "$2" (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 "$1" is not single argument warning extra:00000:composite OUT variable "$2" 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 "$1" is not single argument warning extra:00000:unmodified OUT variable "$1" warning extra:00000:unmodified OUT variable "$2" (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 ----------------------------------------------------------------------- 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. (3 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 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); 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) RELATION | public | mytable | RELATION | public | myview | (6 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; -- 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) drop function rrecord01(); drop function rrecord02(); 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 "$1" (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" Query: SELECT var.hint -- ^ (3 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 select 10 a, 20 b into r; for r in execute v loop raise notice '%', r.a; end loop; end $$ language plpgsql; -- should be warning select * from plpgsql_check_function('dyn_sql_3'); plpgsql_check_function --------------------------------------------------------------------------------------- warning:00000:7:FOR over EXECUTE statement:cannot determinate a result of dynamic SQL Detail: There is a risk of related false alarms. Hint: Don't use dynamic SQL and record type together, when you would check function. (3 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 --------+------------+----------------- 0 | 1 | statement block 1 | 1 | assignment 2 | 1 | IF 3 | 0 | assignment 4 | 1 | assignment 5 | 1 | RETURN (6 rows) select plpgsql_coverage_statements('covtest'); plpgsql_coverage_statements ----------------------------- 0.833333333333333 (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 --------+------------+----------------- 0 | 2 | statement block 1 | 2 | assignment 2 | 2 | IF 3 | 1 | assignment 4 | 2 | assignment 5 | 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) NOTICE: #0 "$1" => '10', "$2" => '20', "$3" => '08-15-2020', "$4" => '3.14' NOTICE: #2 ->> start of function tracer_tab_trg_fx() (oid=0) NOTICE: #2 call by fxo(integer,integer,date,numeric) line 3 at SQL statement 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) NOTICE: #0 "$1" => '11', "$2" => '21', "$3" => '08-16-2020', "$4" => '6.28' NOTICE: #2 ->> start of function tracer_tab_trg_fx() (oid=0) NOTICE: #2 call by fxo(integer,integer,date,numeric) line 3 at SQL statement 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 ---------------------------------------------------------------------------- error:55000:5:assignment:record "r" is not assigned yet Detail: The tuple structure of a not-yet-assigned record is indeterminate. Context: SQL statement "SELECT df1(r)" (3 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 ---------------------------------------------------------------------------- error:55000:5:assignment:record "r" is not assigned yet Detail: The tuple structure of a not-yet-assigned record is indeterminate. Context: SQL statement "SELECT df2(r, '{}')" (3 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 target variables Detail: There are more target variables than output columns in query. Hint: Check target variables in SELECT INTO statement. 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 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 "v" (9 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 ""%I"" (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 | 0 | begin 3 | 2 | 0 | 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 | 0 | begin 3 | 2 | 0 | 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; plpgsql_check-2.1.2/expected/plpgsql_check_active_2.out000066400000000000000000011603261417472474500233100ustar00rootroot00000000000000load '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(); -- 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 overlapped Detail: Local variable overlap 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 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); 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) RELATION | public | mytable | RELATION | public | myview | (6 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; -- 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) drop function rrecord01(); drop function rrecord02(); 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 select 10 a, 20 b into r; for r in execute v loop raise notice '%', r.a; end loop; end $$ language plpgsql; -- should be warning select * from plpgsql_check_function('dyn_sql_3'); plpgsql_check_function --------------------------------------------------------------------------------------- warning:00000:7:FOR over EXECUTE statement:cannot determinate a result of dynamic SQL Detail: There is a risk of related false alarms. Hint: Don't use dynamic SQL and record type together, when you would check function. (3 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 --------+------------+----------------- 0 | 1 | statement block 1 | 1 | assignment 2 | 1 | IF 3 | 0 | assignment 4 | 1 | assignment 5 | 1 | RETURN (6 rows) select plpgsql_coverage_statements('covtest'); plpgsql_coverage_statements ----------------------------- 0.833333333333333 (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 --------+------------+----------------- 0 | 2 | statement block 1 | 2 | assignment 2 | 2 | IF 3 | 1 | assignment 4 | 2 | assignment 5 | 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) NOTICE: #0 "a" => '10', "b" => '20', "c" => '08-15-2020', "d" => '3.14' NOTICE: #2 ->> start of function tracer_tab_trg_fx() (oid=0) NOTICE: #2 call by fxo(integer,integer,date,numeric) line 3 at SQL statement 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) NOTICE: #0 "a" => '11', "b" => '21', "c" => '08-16-2020', "d" => '6.28' NOTICE: #2 ->> start of function tracer_tab_trg_fx() (oid=0) NOTICE: #2 call by fxo(integer,integer,date,numeric) line 3 at SQL statement 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 ""%I"" (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 | 0 | begin 3 | 2 | 0 | 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 | 0 | begin 3 | 2 | 0 | 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; plpgsql_check-2.1.2/expected/plpgsql_check_active_3.out000066400000000000000000011603261417472474500233110ustar00rootroot00000000000000load '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(); -- 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 overlapped Detail: Local variable overlap 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 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); 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) RELATION | public | mytable | RELATION | public | myview | (6 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; -- 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) drop function rrecord01(); drop function rrecord02(); 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 select 10 a, 20 b into r; for r in execute v loop raise notice '%', r.a; end loop; end $$ language plpgsql; -- should be warning select * from plpgsql_check_function('dyn_sql_3'); plpgsql_check_function --------------------------------------------------------------------------------------- warning:00000:7:FOR over EXECUTE statement:cannot determinate a result of dynamic SQL Detail: There is a risk of related false alarms. Hint: Don't use dynamic SQL and record type together, when you would check function. (3 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 --------+------------+----------------- 0 | 1 | statement block 1 | 1 | assignment 2 | 1 | IF 3 | 0 | assignment 4 | 1 | assignment 5 | 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 --------+------------+----------------- 0 | 2 | statement block 1 | 2 | assignment 2 | 2 | IF 3 | 1 | assignment 4 | 2 | assignment 5 | 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) NOTICE: #0 "a" => '10', "b" => '20', "c" => '08-15-2020', "d" => '3.14' NOTICE: #2 ->> start of function tracer_tab_trg_fx() (oid=0) NOTICE: #2 call by fxo(integer,integer,date,numeric) line 3 at SQL statement 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) NOTICE: #0 "a" => '11', "b" => '21', "c" => '08-16-2020', "d" => '6.28' NOTICE: #2 ->> start of function tracer_tab_trg_fx() (oid=0) NOTICE: #2 call by fxo(integer,integer,date,numeric) line 3 at SQL statement 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 ""%I"" (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 | 0 | begin 3 | 2 | 0 | 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 | 0 | begin 3 | 2 | 0 | 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; plpgsql_check-2.1.2/expected/plpgsql_check_passive-10.out000066400000000000000000000000001417472474500234610ustar00rootroot00000000000000plpgsql_check-2.1.2/expected/plpgsql_check_passive-11.out000066400000000000000000000003671417472474500235020ustar00rootroot00000000000000load 'plpgsql'; load 'plpgsql_check'; set client_min_messages to notice; 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(); plpgsql_check-2.1.2/expected/plpgsql_check_passive-12.out000066400000000000000000000003671417472474500235030ustar00rootroot00000000000000load 'plpgsql'; load 'plpgsql_check'; set client_min_messages to notice; 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(); plpgsql_check-2.1.2/expected/plpgsql_check_passive-13.out000066400000000000000000000003671417472474500235040ustar00rootroot00000000000000load 'plpgsql'; load 'plpgsql_check'; set client_min_messages to notice; 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(); plpgsql_check-2.1.2/expected/plpgsql_check_passive-14.out000066400000000000000000000003671417472474500235050ustar00rootroot00000000000000load 'plpgsql'; load 'plpgsql_check'; set client_min_messages to notice; 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(); plpgsql_check-2.1.2/expected/plpgsql_check_passive-15.out000066400000000000000000000003671417472474500235060ustar00rootroot00000000000000load 'plpgsql'; load 'plpgsql_check'; set client_min_messages to notice; 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(); plpgsql_check-2.1.2/expected/plpgsql_check_passive.out000066400000000000000000000203131417472474500232540ustar00rootroot00000000000000load '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(); plpgsql_check-2.1.2/expected/plpgsql_check_passive_1.out000066400000000000000000000202451417472474500235000ustar00rootroot00000000000000load '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(); plpgsql_check-2.1.2/msvc/000077500000000000000000000000001417472474500153225ustar00rootroot00000000000000plpgsql_check-2.1.2/msvc/plpgsql_check.sln000066400000000000000000000040351417472474500206610ustar00rootroot00000000000000 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.1.2/msvc/plpgsql_check/000077500000000000000000000000001417472474500201415ustar00rootroot00000000000000plpgsql_check-2.1.2/msvc/plpgsql_check/plpgsql_check.vcxproj000066400000000000000000000356241417472474500244070ustar00rootroot00000000000000 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.1.2/msvc/plpgsql_check/plpgsql_check.vcxproj.filters000066400000000000000000000021561417472474500260500ustar00rootroot00000000000000 {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.1.2/msvc/plpgsql_check/plpgsql_check.vcxproj.user000066400000000000000000000002171417472474500253520ustar00rootroot00000000000000 plpgsql_check-2.1.2/plpgsql_check--2.1.sql000066400000000000000000000257601417472474500202770ustar00rootroot00000000000000-- 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, 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) 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, 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) 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, 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) 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, 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) RETURNS SETOF text AS 'MODULE_PATHNAME','plpgsql_check_function_name' LANGUAGE C; CREATE FUNCTION __plpgsql_show_dependency_tb(funcoid regprocedure, relid regclass DEFAULT 0) RETURNS TABLE(type text, oid oid, schema text, name text, params text) AS 'MODULE_PATHNAME','plpgsql_show_dependency_tb' LANGUAGE C STRICT; CREATE FUNCTION __plpgsql_show_dependency_tb(name text, relid regclass DEFAULT 0) RETURNS TABLE(type text, oid oid, schema text, name text, params text) AS 'MODULE_PATHNAME','plpgsql_show_dependency_tb_name' LANGUAGE C STRICT; CREATE FUNCTION plpgsql_show_dependency_tb(funcoid regprocedure, relid regclass DEFAULT 0) RETURNS TABLE(type text, oid oid, schema text, name text, params text) AS $$ SELECT * FROM @extschema@.__plpgsql_show_dependency_tb($1, $2) ORDER BY 1, 3, 4; $$ LANGUAGE sql; CREATE FUNCTION plpgsql_show_dependency_tb(fnname text, relid regclass DEFAULT 0) RETURNS TABLE(type text, oid oid, schema text, name text, params text) AS $$ SELECT * FROM @extschema@.__plpgsql_show_dependency_tb($1, $2) 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; plpgsql_check-2.1.2/plpgsql_check.control000066400000000000000000000002661417472474500205770ustar00rootroot00000000000000# plpgsql_check extension comment = 'extended check for plpgsql functions' default_version = '2.1' module_pathname = '$libdir/plpgsql_check' relocatable = false requires = 'plpgsql' plpgsql_check-2.1.2/postgresql10-plpgsql_check.spec000066400000000000000000000125631417472474500224160ustar00rootroot00000000000000%global pgmajorversion 10 %global pginstdir /usr/pgsql-10 %global sname plpgsql_check Name: %{sname}_%{pgmajorversion} Version: 2.1.2 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 USE_PGXS=1 make %{?_smp_mflags} %install rm -rf %{buildroot} make USE_PGXS=1 DESTDIR=%{buildroot} install %clean rm -rf %{buildroot} %files %defattr(644,root,root,755) %doc README.md %{pginstdir}/lib/plpgsql_check.so %{pginstdir}/share/extension/plpgsql_check--2.0.sql %{pginstdir}/share/extension/plpgsql_check.control %changelog * 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 * Fri 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 Now 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.1.2/postgresql11-plpgsql_check.spec000066400000000000000000000125641417472474500224200ustar00rootroot00000000000000%global pgmajorversion 11 %global pginstdir /usr/pgsql-11 %global sname plpgsql_check Name: %{sname}_%{pgmajorversion} Version: 2.1.2 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 USE_PGXS=1 make %{?_smp_mflags} %install rm -rf %{buildroot} make USE_PGXS=1 DESTDIR=%{buildroot} install %clean rm -rf %{buildroot} %files %defattr(644,root,root,755) %doc README.md %{pginstdir}/lib/plpgsql_check.so %{pginstdir}/share/extension/plpgsql_check--2.0.sql %{pginstdir}/share/extension/plpgsql_check.control %changelog * 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 * Fri 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 Now 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.1.2/postgresql12-plpgsql_check.spec000066400000000000000000000125641417472474500224210ustar00rootroot00000000000000%global pgmajorversion 12 %global pginstdir /usr/pgsql-12 %global sname plpgsql_check Name: %{sname}_%{pgmajorversion} Version: 2.1.2 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 USE_PGXS=1 make %{?_smp_mflags} %install rm -rf %{buildroot} make USE_PGXS=1 DESTDIR=%{buildroot} install %clean rm -rf %{buildroot} %files %defattr(644,root,root,755) %doc README.md %{pginstdir}/lib/plpgsql_check.so %{pginstdir}/share/extension/plpgsql_check--2.0.sql %{pginstdir}/share/extension/plpgsql_check.control %changelog * 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 * Fri 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 Now 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.1.2/postgresql13-plpgsql_check.spec000066400000000000000000000125641417472474500224220ustar00rootroot00000000000000%global pgmajorversion 13 %global pginstdir /usr/pgsql-13 %global sname plpgsql_check Name: %{sname}_%{pgmajorversion} Version: 2.1.2 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 USE_PGXS=1 make %{?_smp_mflags} %install rm -rf %{buildroot} make USE_PGXS=1 DESTDIR=%{buildroot} install %clean rm -rf %{buildroot} %files %defattr(644,root,root,755) %doc README.md %{pginstdir}/lib/plpgsql_check.so %{pginstdir}/share/extension/plpgsql_check--2.0.sql %{pginstdir}/share/extension/plpgsql_check.control %changelog * 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 * Fri 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 Now 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.1.2/postgresql14-plpgsql_check.spec000066400000000000000000000125641417472474500224230ustar00rootroot00000000000000%global pgmajorversion 14 %global pginstdir /usr/pgsql-14 %global sname plpgsql_check Name: %{sname}_%{pgmajorversion} Version: 2.1.2 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 USE_PGXS=1 make %{?_smp_mflags} %install rm -rf %{buildroot} make USE_PGXS=1 DESTDIR=%{buildroot} install %clean rm -rf %{buildroot} %files %defattr(644,root,root,755) %doc README.md %{pginstdir}/lib/plpgsql_check.so %{pginstdir}/share/extension/plpgsql_check--2.0.sql %{pginstdir}/share/extension/plpgsql_check.control %changelog * 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 * Fri 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 Now 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.1.2/sql/000077500000000000000000000000001417472474500151515ustar00rootroot00000000000000plpgsql_check-2.1.2/sql/plpgsql_check_active-10.sql000066400000000000000000000000001417472474500222500ustar00rootroot00000000000000plpgsql_check-2.1.2/sql/plpgsql_check_active-11.sql000066400000000000000000000041031417472474500222610ustar00rootroot00000000000000LOAD 'plpgsql'; CREATE EXTENSION IF NOT EXISTS plpgsql_check; set client_min_messages to notice; 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; plpgsql_check-2.1.2/sql/plpgsql_check_active-12.sql000066400000000000000000000114011417472474500222610ustar00rootroot00000000000000LOAD 'plpgsql'; CREATE EXTENSION IF NOT EXISTS plpgsql_check; set client_min_messages to notice; 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); plpgsql_check-2.1.2/sql/plpgsql_check_active-13.sql000066400000000000000000000114011417472474500222620ustar00rootroot00000000000000LOAD 'plpgsql'; CREATE EXTENSION IF NOT EXISTS plpgsql_check; set client_min_messages to notice; 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); plpgsql_check-2.1.2/sql/plpgsql_check_active-14.sql000066400000000000000000000114011417472474500222630ustar00rootroot00000000000000LOAD 'plpgsql'; CREATE EXTENSION IF NOT EXISTS plpgsql_check; set client_min_messages to notice; 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); plpgsql_check-2.1.2/sql/plpgsql_check_active-15.sql000066400000000000000000000114011417472474500222640ustar00rootroot00000000000000LOAD 'plpgsql'; CREATE EXTENSION IF NOT EXISTS plpgsql_check; set client_min_messages to notice; 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); plpgsql_check-2.1.2/sql/plpgsql_check_active.sql000066400000000000000000002747011417472474500220570ustar00rootroot00000000000000load '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(); -- 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 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); 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; -- should not to raise false alarms select * from plpgsql_check_function('rrecord01'); select * from plpgsql_check_function('rrecord02'); drop function rrecord01(); drop function rrecord02(); 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 select 10 a, 20 b into r; for r in execute v loop raise notice '%', r.a; end loop; end $$ language plpgsql; -- should be warning 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; plpgsql_check-2.1.2/sql/plpgsql_check_passive-10.sql000066400000000000000000000000001417472474500224470ustar00rootroot00000000000000plpgsql_check-2.1.2/sql/plpgsql_check_passive-11.sql000066400000000000000000000003731417472474500224650ustar00rootroot00000000000000load 'plpgsql'; load 'plpgsql_check'; set client_min_messages to notice; 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(); plpgsql_check-2.1.2/sql/plpgsql_check_passive-12.sql000066400000000000000000000003731417472474500224660ustar00rootroot00000000000000load 'plpgsql'; load 'plpgsql_check'; set client_min_messages to notice; 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(); plpgsql_check-2.1.2/sql/plpgsql_check_passive-13.sql000066400000000000000000000003731417472474500224670ustar00rootroot00000000000000load 'plpgsql'; load 'plpgsql_check'; set client_min_messages to notice; 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(); plpgsql_check-2.1.2/sql/plpgsql_check_passive-14.sql000066400000000000000000000003731417472474500224700ustar00rootroot00000000000000load 'plpgsql'; load 'plpgsql_check'; set client_min_messages to notice; 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(); plpgsql_check-2.1.2/sql/plpgsql_check_passive-15.sql000066400000000000000000000003731417472474500224710ustar00rootroot00000000000000load 'plpgsql'; load 'plpgsql_check'; set client_min_messages to notice; 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(); plpgsql_check-2.1.2/sql/plpgsql_check_passive.sql000066400000000000000000000115041417472474500222440ustar00rootroot00000000000000load '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(); plpgsql_check-2.1.2/src/000077500000000000000000000000001417472474500151415ustar00rootroot00000000000000plpgsql_check-2.1.2/src/assign.c000066400000000000000000000451261417472474500166010ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * assign.c * * assign types to record variables * * by Pavel Stehule 2013-2021 * *------------------------------------------------------------------------- */ #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" #if PG_VERSION_NUM >= 110000 #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); #endif /* * 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) { int fnum; if (row != NULL) { 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); } } /* * 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]; 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 == TEXTOID && value_typoid == UNKNOWNOID) return; if (type_is_rowtype(value_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 (target_typoid != value_typoid && !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); #if PG_VERSION_NUM >= 110000 rec->erh = NULL; #else rec->tup = NULL; rec->freetup = false; rec->freetupdesc = false; #endif } void plpgsql_check_recval_release(PLpgSQL_rec *rec) { #if PG_VERSION_NUM >= 110000 Assert(rec->dtype == PLPGSQL_DTYPE_REC); if (rec->erh) DeleteExpandedObject(ExpandedRecordGetDatum(rec->erh)); rec->erh = NULL; #else if (rec->freetup) heap_freetuple(rec->tup); if (rec->freetupdesc) FreeTupleDesc(rec->tupdesc); rec->freetup = false; rec->freetupdesc = false; #endif } /* * 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) { #if PG_VERSION_NUM >= 110000 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; #else bool *nulls; HeapTuple tup; (void) cstate; (void) is_null; plpgsql_check_recval_release(rec); if (!tupdesc) return; /* initialize rec by NULLs */ nulls = (bool *) palloc(tupdesc->natts * sizeof(bool)); memset(nulls, true, tupdesc->natts * sizeof(bool)); rec->tupdesc = CreateTupleDescCopy(tupdesc); rec->freetupdesc = true; tup = heap_form_tuple(tupdesc, NULL, nulls); if (HeapTupleIsValid(tup)) { rec->tup = tup; rec->freetup = true; } else elog(ERROR, "cannot to build valid composite value"); #endif } #if PG_VERSION_NUM >= 110000 /* * 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; } #endif plpgsql_check-2.1.2/src/catalog.c000066400000000000000000000165101417472474500167220ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * catalog.c * * routines for working with Postgres's catalog and caches * * by Pavel Stehule 2013-2021 * *------------------------------------------------------------------------- */ #include "plpgsql_check.h" #include "access/genam.h" #include "access/htup_details.h" #if PG_VERSION_NUM >= 120000 #include "access/table.h" #endif #include "catalog/pg_extension.h" #include "catalog/indexing.h" #include "catalog/pg_language.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" #if PG_VERSION_NUM >= 110000 #include "catalog/pg_proc.h" #endif #if PG_VERSION_NUM < 120000 #include "access/sysattr.h" #endif #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(HeapTuple procTuple, Oid *rettype, char *volatility, PLpgSQL_trigtype *trigtype, bool *is_procedure) { Form_pg_proc proc; char functyptype; proc = (Form_pg_proc) GETSTRUCT(procTuple); functyptype = get_typtype(proc->prorettype); *trigtype = PLPGSQL_NOT_TRIGGER; #if PG_VERSION_NUM >= 110000 *is_procedure = proc->prokind == PROKIND_PROCEDURE; #else *is_procedure = false; #endif /* * 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 ) *trigtype = PLPGSQL_DML_TRIGGER; else if (plpgsql_check_is_eventtriggeroid(proc->prorettype)) *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)))); } *volatility = ((Form_pg_proc) GETSTRUCT(procTuple))->provolatile; *rettype = ((Form_pg_proc) GETSTRUCT(procTuple))->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); } /* * 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]; #if PG_VERSION_NUM >= 120000 rel = table_open(ExtensionRelationId, AccessShareLock); ScanKeyInit(&entry[0], Anum_pg_extension_oid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(ext_oid)); #else rel = heap_open(ExtensionRelationId, AccessShareLock); ScanKeyInit(&entry[0], ObjectIdAttributeNumber, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(ext_oid)); #endif 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); #if PG_VERSION_NUM >= 120000 table_close(rel, AccessShareLock); #else heap_close(rel, AccessShareLock); #endif 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; #if PG_VERSION_NUM >= 120000 result = procform->oid; #else result = HeapTupleGetOid(proctup); #endif 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-2.1.2/src/check_expr.c000066400000000000000000001053351417472474500174270ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * check_expr.c * * routines for enforce plans for every expr/query and * related checks over these plans. * * by Pavel Stehule 2013-2021 * *------------------------------------------------------------------------- */ #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" #if PG_VERSION_NUM >= 120000 #include "optimizer/optimizer.h" #endif #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); 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); /* * Generate a prepared plan - this is simplified copy from pl_exec.c Is not * necessary to check simple plan, returns true, when expression is * succesfully prepared. */ static void prepare_plan(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr, int cursorOptions, ParserSetupHook parser_setup, void *arg) { SPIPlanPtr plan; Query *query; if (expr->plan == NULL) { MemoryContext old_cxt; #if PG_VERSION_NUM >= 140000 SPIPrepareOptions options; memset(&options, 0, sizeof(options)); options.parserSetup = parser_setup ? parser_setup : (ParserSetupHook) plpgsql_check__parser_setup_p; 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; #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_check__parser_setup_p, arg ? arg : (void *) expr, cursorOptions); #endif 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); } query = ExprGetQuery(cstate, expr); /* 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; if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC) elog(ERROR, "cached plan is not valid plan"); cstate->has_mp = false; if (list_length(plan->plancache_list) != 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 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; plansource = plpgsql_check_get_plan_source(cstate, expr->plan); /* * 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); *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; TargetEntry *tle; _plan = _stmt->planTree; if (IsA(_plan, Result) &&list_length(_plan->targetlist) == 1) { 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(Const *c) { if (IsA((Node *) c, Const)) { if (!c->constisnull) { Oid typoutput; bool typisvarlena; getTypeOutputInfo(c->consttype, &typoutput, &typisvarlena); return OidOutputFunctionCall(typoutput, c->constvalue); } } return NULL; } /* * 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, bool *isnull) { Const *c; c = expr_get_const(cstate, expr); if (c) { *isnull = c->constisnull; return plpgsql_check_const_to_string(c); } return NULL; } 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); /* 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; SPI_restore_connection(); } 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); /* reconnect spi */ SPI_restore_connection(); } 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); 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; SPI_restore_connection(); } 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); /* reconnect spi */ SPI_restore_connection(); } PG_END_TRY(); } /* * 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 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 (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; SPI_restore_connection(); } 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); /* reconnect spi */ SPI_restore_connection(); } 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; TupleDesc tupdesc; volatile bool result = false; if (!expr) return true; oldowner = CurrentResourceOwner; BeginInternalSubTransaction(NULL); MemoryContextSwitchTo(oldCxt); PG_TRY(); { 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; SPI_restore_connection(); } 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); /* reconnect spi */ SPI_restore_connection(); } 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); } #if PG_VERSION_NUM >= 110000 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); } #endif /* * 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.1.2/src/check_function.c000066400000000000000000001117411417472474500202740ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * check_function.c * * workhorse functionality of this extension - expression * and query validator * * by Pavel Stehule 2013-2021 * *------------------------------------------------------------------------- */ #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" #if PG_VERSION_NUM >= 120000 #include "access/heapam.h" #endif 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_fatal_errors = 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); /* * 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; int save_nestlevel = 0; bool reload_config; #if PG_VERSION_NUM >= 120000 LOCAL_FCINFO(fake_fcinfo, 0); #else FunctionCallInfoData fake_fcinfo_data; FunctionCallInfo fake_fcinfo = &fake_fcinfo_data; #endif 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; int i; numargs = get_func_arg_info(cinfo->proctuple, &argtypes, &argnames, &argmodes); if (argnames != NULL) { for (i = 0; i < numargs; i++) { if (argnames[i][0] != '\0') cstate.argnames = lappend(cstate.argnames, argnames[i]); } } } oldowner = CurrentResourceOwner; PG_TRY(); { 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); SPI_restore_connection(); } 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); /* reconnect spi */ SPI_restore_connection(); } 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. * */ void plpgsql_check_on_func_beg(PLpgSQL_execstate *estate, PLpgSQL_function *func) { const char *err_text = estate->err_text; int closing; List *exceptions; if (plpgsql_check_tracer) plpgsql_check_tracer_on_func_beg(estate, func); 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)); memset(&cinfo, 0, sizeof(cinfo)); if (OidIsValid(func->fn_oid)) { HeapTuple procTuple; procTuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(func->fn_oid)); if (!HeapTupleIsValid(procTuple)) elog(ERROR, "cache lookup failed for function %u", func->fn_oid); plpgsql_check_get_function_info(procTuple, &cinfo.rettype, &cinfo.volatility, &cinfo.trigtype, &cinfo.is_procedure); ReleaseSysCache(procTuple); 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, 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]; #if PG_VERSION_NUM >= 110000 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 saved_records[i].tup = rec->tup; saved_records[i].tupdesc = rec->tupdesc; saved_records[i].freetup = rec->freetup; saved_records[i].freetupdesc = rec->freetupdesc; /* don't release a original tupdesc and original tup */ rec->freetup = false; rec->freetupdesc = false; #endif } 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); } } 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]; #if PG_VERSION_NUM >= 110000 memcpy(rec, &saved_records[i], sizeof(PLpgSQL_rec)); #else if (rec->freetupdesc) FreeTupleDesc(rec->tupdesc); rec->tup = saved_records[i].tup; rec->tupdesc = saved_records[i].tupdesc; rec->freetup = saved_records[i].freetup; rec->freetupdesc = saved_records[i].freetupdesc; #endif } 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); } } /* * 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) { PLpgSQL_rec *rec_new, *rec_old; 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)) { 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. */ #if PG_VERSION_NUM >= 110000 /* * 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 rec_new = (PLpgSQL_rec *) (cstate->estate->datums[func->new_varno]); rec_new->freetup = false; rec_new->freetupdesc = false; plpgsql_check_assign_tupdesc_row_or_rec(cstate, NULL, rec_new, trigdata->tg_relation->rd_att, false); rec_old = (PLpgSQL_rec *) (cstate->estate->datums[func->old_varno]); rec_old->freetup = false; rec_old->freetupdesc = false; plpgsql_check_assign_tupdesc_row_or_rec(cstate, NULL, rec_old, trigdata->tg_relation->rd_att, false); /* * Assign the special tg_ variables */ init_datum_dno(cstate, func->tg_op_varno, true, true); init_datum_dno(cstate, func->tg_name_varno, true, true); init_datum_dno(cstate, func->tg_when_varno, true, true); init_datum_dno(cstate, func->tg_level_varno, true, true); init_datum_dno(cstate, func->tg_relid_varno, true, true); init_datum_dno(cstate, func->tg_relname_varno, true, true); init_datum_dno(cstate, func->tg_table_name_varno, true, true); init_datum_dno(cstate, func->tg_table_schema_varno, true, true); init_datum_dno(cstate, func->tg_nargs_varno, true, true); init_datum_dno(cstate, func->tg_argv_varno, true, true); #endif } else if (IsA(tdata, EventTriggerData)) { #if PG_VERSION_NUM < 110000 init_datum_dno(cstate, func->tg_event_varno, true, true); init_datum_dno(cstate, func->tg_tag_varno, true, true); #endif } 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 */ #if PG_VERSION_NUM >= 120000 MemSet(fcinfo, 0, SizeForFunctionCallInfo(0)); #else MemSet(fcinfo, 0, sizeof(FunctionCallInfoData)); #endif 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; #if PG_VERSION_NUM >= 120000 resultTupleDesc = CreateTemplateTupleDesc(1); #else resultTupleDesc = CreateTemplateTupleDesc(1, false); #endif 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; #if PG_VERSION_NUM < 110000 estate->rettupdesc = NULL; estate->eval_econtext = NULL; #else estate->eval_econtext = makeNode(ExprContext); estate->eval_econtext->ecxt_per_tuple_memory = AllocSetContextCreate(CurrentMemoryContext, "ExprContext", ALLOCSET_DEFAULT_SIZES); estate->datum_context = CurrentMemoryContext; #endif 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; #if PG_VERSION_NUM >= 110000 estate->tuple_store_desc = rsi->expectedDesc; #else if (estate->retisset) estate->rettupdesc = rsi->expectedDesc; #endif } 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 PG_VERSION_NUM < 120000 estate->eval_lastoid = InvalidOid; #endif 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->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)); 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; #if PG_VERSION_NUM >= 110000 cstate->check_cxt = AllocSetContextCreate(CurrentMemoryContext, "plpgsql_check temporary cxt", ALLOCSET_DEFAULT_SIZES); #else cstate->check_cxt = AllocSetContextCreate(CurrentMemoryContext, "plpgsql_check temporary cxt", ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); #endif 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_tracer = 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; /* try to find oid of plpgsql_check pragma function */ cstate->pragma_foid = plpgsql_check_pragma_func_oid(); } /* * 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) { #if PG_VERSION_NUM >= 110000 case PLPGSQL_DTYPE_PROMISE: #endif case PLPGSQL_DTYPE_VAR: { PLpgSQL_var *var = (PLpgSQL_var *) cstate->estate->datums[dno]; var->value = (Datum) 0; var->isnull = true; var->freeval = false; } break; #if PG_VERSION_NUM >= 110000 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; #endif 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: #if PG_VERSION_NUM >= 110000 case PLPGSQL_DTYPE_PROMISE: #endif { 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; 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) { plpgsql_check_HashEnt *hentry; bool found; /* don't try to mark anonymous code blocks */ if (func->fn_oid != InvalidOid) { 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.1.2/src/expr_walk.c000066400000000000000000000601711417472474500173060ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * expr_walk.c * * set of Query/Expr walkers * * by Pavel Stehule 2013-2021 * *------------------------------------------------------------------------- */ #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; typedef bool (*check_function_callback) (Oid func_id, void *context); 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 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); } } } 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; int i = 0; initStringInfo(&str); appendStringInfoChar(&str, '('); foreach(lc, fexpr->args) { Node *expr = (Node *) lfirst(lc); if (i++ > 0) appendStringInfoChar(&str, ','); appendStringInfoString(&str, format_type_be(exprType(expr))); } appendStringInfoChar(&str, ')'); plpgsql_check_put_dependency(ri, "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); } } } 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; int location = -1; switch (fexpr->funcid) { case NEXTVAL_OID: case CURRVAL_OID: case SETVAL_OID: case SETVAL2_OID: { Node *first_arg = linitial(fexpr->args); 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 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 */ 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; bool is_error; wp.cstate = cstate; wp.expr = expr; wp.query_str = expr->query; *location = -1; check_fmt_string(fmt, fexpr->args, c->location, &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.1.2/src/format.c000066400000000000000000000712671417472474500166120ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * format.c * * error/warning message formatting * * by Pavel Stehule 2013-2021 * *------------------------------------------------------------------------- */ #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; } /* clean up and return the tuplestore */ tuplestore_donestoring(ri->tupstore); } /* * 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)) 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)) 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"; 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_TEXT(Anum_result_statement, "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_TEXT(Anum_result_statement, "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.1.2/src/parser.c000066400000000000000000000510421417472474500166030ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * parse_name.c * * parse function signature * parse identifier, and type name * * by Pavel Stehule 2013-2021 * *------------------------------------------------------------------------- */ #include "plpgsql_check.h" #include "catalog/namespace.h" #include "parser/scansup.h" #include "parser/parse_type.h" #include "utils/builtins.h" #include "utils/lsyscache.h" #if PG_VERSION_NUM < 110000 #include "utils/typcache.h" #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 == NULL) 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; int 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, 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))); } #define PRAGMA_TOKEN_IDENTIF 128 #define PRAGMA_TOKEN_QIDENTIF 129 #define PRAGMA_TOKEN_NUMBER 130 /* * 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; /* * 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 (*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 (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, token->size, false); } else if (token->value == PRAGMA_TOKEN_QIDENTIF) { char *result = palloc(token->size); const char *ptr = token->substr + 1; char *write_ptr; int 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, write_ptr - result, false); return result; } return NULL; } /* * Returns list of strings used in qualified identifiers */ static List * get_qualified_identifier(TokenizerState *state, List *result) { PragmaTokenType token, *_token; bool read_atleast_one = false; while (1) { _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, int *size) { PragmaTokenType token, *_token; bool read_atleast_one = false; const char *_startptr = *startptr; int _size = *size; while (1) { _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; int 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); typeName = typeStringToTypeName(typestr); 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; } 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; SPI_restore_connection(); } PG_CATCH(); { ErrorData *edata; MemoryContextSwitchTo(cstate->check_cxt); edata = CopyErrorData(); FlushErrorState(); MemoryContextSwitchTo(oldCxt); FlushErrorState(); RollbackAndReleaseCurrentSubTransaction(); MemoryContextSwitchTo(oldCxt); CurrentResourceOwner = oldowner; /* reconnect spi */ SPI_restore_connection(); /* 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; 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)"); _token = get_token(&tstate, &token); if (!_token || _token->value != '(') elog(ERROR, "Syntax error (expected table specification)"); unget_token(&tstate, _token); (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; SPI_restore_connection(); } PG_CATCH(); { ErrorData *edata; MemoryContextSwitchTo(cstate->check_cxt); edata = CopyErrorData(); FlushErrorState(); MemoryContextSwitchTo(oldCxt); FlushErrorState(); RollbackAndReleaseCurrentSubTransaction(); MemoryContextSwitchTo(oldCxt); CurrentResourceOwner = oldowner; /* reconnect spi */ SPI_restore_connection(); /* 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; } plpgsql_check-2.1.2/src/plpgsql_check.c000066400000000000000000000243371417472474500201350ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * plpgsql_check.c * * enhanced checks for plpgsql functions * * by Pavel Stehule 2013-2021 * *------------------------------------------------------------------------- * * 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 "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; static PLpgSQL_plugin plugin_funcs = { plpgsql_check_profiler_func_init, plpgsql_check_on_func_beg, plpgsql_check_profiler_func_end, plpgsql_check_profiler_stmt_beg, plpgsql_check_profiler_stmt_end, NULL, NULL}; 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} }; void _PG_init(void); void _PG_fini(void); shmem_startup_hook_type 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; /* * 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))) /* * 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"); plpgsql_check_plugin_var_ptr = (PLpgSQL_plugin **) find_rendezvous_variable( "PLpgSQL_plugin"); *plpgsql_check_plugin_var_ptr = &plugin_funcs; 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.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); 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); 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); RequestAddinShmemSpace(plpgsql_check_shmem_size()); RequestNamedLWLockTranche("plpgsql_check profiler", 1); RequestNamedLWLockTranche("plpgsql_check fstats", 1); /* * Install hooks. */ prev_shmem_startup_hook = shmem_startup_hook; shmem_startup_hook = plpgsql_check_profiler_shmem_startup; } plpgsql_check_next_needs_fmgr_hook = needs_fmgr_hook; plpgsql_check_next_fmgr_hook = fmgr_hook; needs_fmgr_hook = plpgsql_check_needs_fmgr_hook; fmgr_hook = plpgsql_check_fmgr_hook; inited = true; } /* * Module unload callback */ void _PG_fini(void) { shmem_startup_hook = prev_shmem_startup_hook; /* Be more correct, and clean rendezvous variable */ *plpgsql_check_plugin_var_ptr = NULL; needs_fmgr_hook = plpgsql_check_next_needs_fmgr_hook; fmgr_hook = plpgsql_check_next_fmgr_hook; } plpgsql_check-2.1.2/src/plpgsql_check.h000066400000000000000000000475541417472474500201500ustar00rootroot00000000000000#include "postgres.h" #include "plpgsql.h" #include "funcapi.h" #include "miscadmin.h" #include "access/tupdesc.h" #include "storage/ipc.h" #if PG_VERSION_NUM >= 110000 typedef uint64 pc_queryid; #define NOQUERYID (UINT64CONST(0)) #else typedef uint32 pc_queryid; #define NOQUERYID (0) #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 */ }; 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_PREPARE_PROFILE, PLPGSQL_CHECK_STMT_WALKER_COUNT_EXEC_TIME, PLPGSQL_CHECK_STMT_WALKER_PREPARE_RESULT, PLPGSQL_CHECK_STMT_WALKER_COLLECT_COVERAGE } profiler_stmt_walker_mode; typedef struct PLpgSQL_stmt_stack_item { PLpgSQL_stmt *stmt; char *label; struct PLpgSQL_stmt_stack_item *outer; } PLpgSQL_stmt_stack_item; 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 show_profile; char *oldtable; char *newtable; } 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; } plpgsql_check_pragma_vector; typedef struct PLpgSQL_checkstate { 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 */ } PLpgSQL_checkstate; typedef struct { int statements; int branches; int executed_statements; int executed_branches; } coverage_state; typedef struct { unsigned long int run_id; int level; } tracer_info; /* * 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); /* * 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(HeapTuple procTuple, Oid *rettype, char *volatility, PLpgSQL_trigtype *trigtype, bool *is_procedure); 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); /* * functions from tablefunc.c */ extern void plpgsql_check_info_init(plpgsql_check_info *cinfo, Oid fn_oid); /* * 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_fatal_errors; 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); /* * functions from check_expr.c */ extern char *plpgsql_check_expr_get_string(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr, bool *isnull); 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(Const *c); extern CachedPlanSource *plpgsql_check_get_plan_source(PLpgSQL_checkstate *cstate, SPIPlanPtr plan); #if PG_VERSION_NUM >= 110000 extern void plpgsql_check_assignment_to_variable(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr, PLpgSQL_variable *targetvar, int targetdno); #endif /* * functions from report.c */ extern char * plpgsql_check_datum_get_refname(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); #if PG_VERSION_NUM >= 110000 extern PLpgSQL_row * plpgsql_check_CallExprGetRowTarget(PLpgSQL_checkstate *cstate, PLpgSQL_expr *CallExpr); #endif /* * 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); /* * 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; 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_profiler_func_init(PLpgSQL_execstate *estate, PLpgSQL_function *func); extern void plpgsql_check_profiler_func_end(PLpgSQL_execstate *estate, PLpgSQL_function *func); extern void plpgsql_check_profiler_stmt_beg(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt); extern void plpgsql_check_profiler_stmt_end(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt); 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 bool plpgsql_check_needs_fmgr_hook(Oid fn_oid); extern void plpgsql_check_fmgr_hook(FmgrHookEventType event, FmgrInfo *flinfo, Datum *private); #if PG_VERSION_NUM >= 120000 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); #endif /* * 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 void plpgsql_check_tracer_on_func_beg(PLpgSQL_execstate *estate, PLpgSQL_function *func); extern void plpgsql_check_tracer_on_func_end(PLpgSQL_execstate *estate, PLpgSQL_function *func); extern void plpgsql_check_tracer_on_stmt_beg(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt); extern void plpgsql_check_tracer_on_stmt_end(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt); 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); /* * variables from pragma.c */ extern void plpgsql_check_pragma_apply(PLpgSQL_checkstate *cstate, char *pragma_str, PLpgSQL_nsitem *ns, int lineno); extern plpgsql_check_pragma_vector plpgsql_check_runtime_pragma_vector; extern bool plpgsql_check_runtime_pragma_vector_changed; /* * functions from plpgsql_check.c */ extern shmem_startup_hook_type prev_shmem_startup_hook; extern PLpgSQL_plugin **plpgsql_check_plugin_var_ptr; #if PG_VERSION_NUM > 110005 #define PLPGSQL_BUILD_DATATYPE_4 1 #endif /* * Links to function in plpgsql module */ #ifdef PLPGSQL_BUILD_DATATYPE_4 typedef PLpgSQL_type *(*plpgsql_check__build_datatype_t) (Oid typeOid, int32 typmod, Oid collation, TypeName *origtypname); #else typedef PLpgSQL_type *(*plpgsql_check__build_datatype_t) (Oid typeOid, int32 typmod, Oid collation); #endif 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 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 MAYBE_UNMODIFIED_VARIABLE_TEXT "OUT variable \"%s\" is maybe unmodified" #define OUT_COMPOSITE_IS_NOT_SINGLE_TEXT "composite OUT variable \"%s\" is not single argument" #define UNSAFE_EXECUTE "the expression used by EXECUTE command is possibly sql injection vulnerable" /* * 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 #if PG_VERSION_NUM >= 110000 #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) #else #define recvar_tuple(rec) (rec->tup) #define recvar_tupdesc(rec) (rec->tupdesc) #endif #if PG_VERSION_NUM >= 100000 #define PLPGSQL_STMT_TYPES #else #define PLPGSQL_STMT_TYPES (enum PLpgSQL_stmt_types) #endif #define FUNCS_PER_USER 128 /* initial table size */ #ifdef _MSC_VER #define strcasecmp _stricmp #define strncasecmp _strnicmp #endif plpgsql_check-2.1.2/src/plpgsql_check_builtins.h000066400000000000000000000053171417472474500220500ustar00rootroot00000000000000 #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); #endif plpgsql_check-2.1.2/src/pragma.c000066400000000000000000000124561417472474500165640ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * pragma.c * * pragma related code * * by Pavel Stehule 2013-2021 * *------------------------------------------------------------------------- */ #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); plpgsql_check_pragma_vector plpgsql_check_runtime_pragma_vector; bool plpgsql_check_runtime_pragma_vector_changed = 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; while (*pragma_str == ' ') pragma_str++; if (strncasecmp(pragma_str, "ECHO:", 5) == 0) { elog(NOTICE, "%s", pragma_str + 5); } else if (strncasecmp(pragma_str, "STATUS:", 7) == 0) { pragma_str += 7; while (*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_other_warnings ? "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 (*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; #if PG_VERSION_NUM < 120000 elog(WARNING, "pragma ENABLE:TRACER is ignored on PostgreSQL 11 and older"); #endif } 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 { elog(WARNING, "unsuported pragma: %s", pragma_str); is_valid = false; } } else if (strncasecmp(pragma_str, "DISABLE:", 8) == 0) { pragma_str += 8; while (*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; #if PG_VERSION_NUM < 120000 elog(WARNING, "pragma DISABLE:TRACER is ignored on PostgreSQL 11 and older"); #endif } 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 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 { 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); pragma_apply(NULL, &plpgsql_check_runtime_pragma_vector, pragma_str, NULL, -1); plpgsql_check_runtime_pragma_vector_changed = true; 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.1.2/src/profiler.c000066400000000000000000002307401417472474500171350ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * profiler.c * * profiler accessories code * * by Pavel Stehule 2013-2021 * *------------------------------------------------------------------------- */ #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" #if PG_VERSION_NUM < 110000 #include "storage/spin.h" #endif #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" #if PG_VERSION_NUM >= 120000 #include "utils/float.h" #endif #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; /* * It is used for fast mapping plpgsql stmt -> stmtid */ #if PG_VERSION_NUM < 120000 typedef struct profiler_map_entry { PLpgSQL_function *function; PLpgSQL_stmt *stmt; int stmtid; struct profiler_map_entry *next; } profiler_map_entry; /* * holds profiland metadata (maps) */ typedef struct profiler_profile { profiler_hashkey key; int nstatements; PLpgSQL_function **mapped_functions; int max_mapped_functions; int n_mapped_functions; int stmts_map_max_lineno; profiler_map_entry *stmts_map; } profiler_profile; #else /* * In current releases we know unique statement id, and we don't * need statement map. Unfortunately this id (stmtid) is not in * order of statement execution. The order depends on order of * processing inside parser. So some deeper statememts has less * number than less deeper statements. When we generate result, * then we need to work with statements in natural order. This * mapping is provided by stmtid_reorder_map. */ typedef struct profiler_profile { profiler_hashkey key; int *stmtid_reorder_map; } profiler_profile; #endif #define PI_MAGIC 2020080110 /* * This structure is used as plpgsql extension parameter */ typedef struct profiler_info { int pi_magic; profiler_profile *profile; profiler_stmt *stmts; PLpgSQL_function *func; instr_time start_time; /* tracer part */ bool trace_info_is_initialized; int frame_num; int level; PLpgSQL_execstate *near_outer_estate; PLpgSQL_execstate *estate; bool disable_tracer; #if PG_VERSION_NUM >= 120000 instr_time *stmt_start_times; int *stmt_group_numbers; int *stmt_parent_group_numbers; bool *stmt_disabled_tracers; bool *pragma_disable_tracer_stack; #endif } 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; } profiler_stmt_walker_options; enum { COVERAGE_STATEMENTS, COVERAGE_BRANCHES }; typedef struct fmgr_hook_private { bool use_plpgsql; Datum next_private; } fmgr_hook_private; #define NESTED_STMTS_STACK_SIZE 64 typedef struct profiler_stack { profiler_info *pinfo; struct profiler_stack *prev_pinfo; /* * The behaviour of estate->err_stmt is different across * pg releases, so the most easy way is hold own err_stmt * initilized in stmt_beg. */ PLpgSQL_stmt *err_stmt; PLpgSQL_stmt *nested_stmts[NESTED_STMTS_STACK_SIZE]; /* * The commands executed under same protected block shares * eval_context. The eval_context is created for every * protected block, but exception handler uses parent * eval_context. The change of eval_context can signalize * an entry/leave to protected section. The nonempty * current error can signalize so we are inside an exception * handler. */ ExprContext *eval_econtext[NESTED_STMTS_STACK_SIZE]; int nested_stmts_count; } profiler_stack; 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; static profiler_stack *top_pinfo = NULL; static ExprContext *curr_eval_econtext = NULL; bool plpgsql_check_profiler = true; needs_fmgr_hook_type plpgsql_check_next_needs_fmgr_hook = NULL; fmgr_hook_type plpgsql_check_next_fmgr_hook = NULL; /* * 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_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); 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 profile_register_stmt(profiler_info *pinfo, profiler_stmt_walker_options *opts, PLpgSQL_stmt *stmt); 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); #if PG_VERSION_NUM < 120000 static void profiler_update_map(profiler_profile *profile, profiler_stmt_walker_options *opts, PLpgSQL_function *function, PLpgSQL_stmt *stmt); static int profiler_get_stmtid(profiler_profile *profile, PLpgSQL_function *function, PLpgSQL_stmt *stmt); #endif /* * 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; } /* * Itereate over Error Context Stack and calculate deep of stack (used like frame number) * and most near outer PLpgSQL estate (detect call statement). This function should be * called from func_beg, where error_context_stack is correctly initialized. */ void plpgsql_check_init_trace_info(PLpgSQL_execstate *estate) { ErrorContextCallback *econtext; profiler_info *pinfo = (profiler_info *) estate->plugin_info; #if PG_VERSION_NUM >= 120000 PLpgSQL_stmt_block *stmt_block = estate->func->action; int tgn; #endif Assert(pinfo && pinfo->pi_magic == PI_MAGIC); for (econtext = error_context_stack->previous; econtext != NULL; econtext = econtext->previous) { pinfo->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 *outer_estate = (PLpgSQL_execstate *) econtext->arg; if (!pinfo->near_outer_estate) pinfo->near_outer_estate = outer_estate; if (pinfo->level == 0 && outer_estate->plugin_info) { profiler_info *outer_pinfo = (profiler_info *) outer_estate->plugin_info; if (outer_pinfo->pi_magic == PI_MAGIC && outer_pinfo->trace_info_is_initialized) { #if PG_VERSION_NUM >= 120000 PLpgSQL_stmt *outer_stmt = outer_estate->err_stmt; if (outer_stmt) { int ogn; ogn = outer_pinfo->stmt_group_numbers[outer_stmt->stmtid - 1]; pinfo->disable_tracer = outer_pinfo->pragma_disable_tracer_stack[ogn]; } #endif pinfo->level = outer_pinfo->level + 1; pinfo->frame_num += outer_pinfo->frame_num; break; } } } } if (plpgsql_check_runtime_pragma_vector_changed) pinfo->disable_tracer = plpgsql_check_runtime_pragma_vector.disable_tracer; #if PG_VERSION_NUM >= 120000 /* set top current group number */ tgn = pinfo->stmt_group_numbers[stmt_block->stmtid - 1]; pinfo->pragma_disable_tracer_stack[tgn] = pinfo->disable_tracer; #endif } #if PG_VERSION_NUM >= 120000 bool * plpgsql_check_get_disable_tracer_on_stack(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt) { profiler_info *pinfo = (profiler_info *) estate->plugin_info; Assert(pinfo && pinfo->pi_magic == PI_MAGIC); /* Allow tracing only when it is explicitly allowed */ if (!plpgsql_check_enable_tracer) return false; if (pinfo->trace_info_is_initialized) return &pinfo->pragma_disable_tracer_stack[stmt->stmtid - 1]; return NULL; } #endif /* * Outer profiler's code fields of profiler info are not available. * This routine reads tracer fields from profiler info. */ 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) { profiler_info *pinfo = (profiler_info *) estate->plugin_info; Assert(pinfo && pinfo->pi_magic == PI_MAGIC); (void) stmt; /* Allow tracing only when it is explicitly allowed */ if (!plpgsql_check_enable_tracer) return false; if (pinfo->trace_info_is_initialized) { #if PG_VERSION_NUM >= 120000 if (stmt && pinfo->stmt_disabled_tracers[stmt->stmtid - 1]) return false; if (!stmt && pinfo->disable_tracer) return false; #endif *outer_estate = pinfo->near_outer_estate; *frame_num = pinfo->frame_num; *level = pinfo->level; *start_time = pinfo->start_time; return true; } else return false; } #if PG_VERSION_NUM >= 120000 /* * Outer profiler's code fields of profiler info are not available. * This routine reads tracer statement fields from profiler info. */ void plpgsql_check_get_trace_stmt_info(PLpgSQL_execstate *estate, int stmt_id, instr_time **start_time) { profiler_info *pinfo = (profiler_info *) estate->plugin_info; Assert(pinfo && pinfo->pi_magic == PI_MAGIC); /* Allow tracing only when it is explicitly allowed */ if (!plpgsql_check_enable_tracer) return; if (pinfo->trace_info_is_initialized) *start_time = &pinfo->stmt_start_times[stmt_id]; else *start_time = NULL; } #endif 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; } /* * 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 (prev_shmem_startup_hook) 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 function profiling metadata. */ static void profiler_localHashTableInit(void) { HASHCTL ctl; Assert(profiler_HashTable == NULL); memset(&ctl, 0, sizeof(ctl)); ctl.keysize = sizeof(profiler_hashkey); ctl.entrysize = sizeof(profiler_profile); ctl.hcxt = profiler_mcxt; profiler_HashTable = hash_create("plpgsql_check function profiler local cache", FUNCS_PER_USER, &ctl, HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); } /* * 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_localHashTableInit(); 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; } /* * Aux routine to reduce differencies between pg releases with and without * statemet id support. */ #if PG_VERSION_NUM < 120000 #define STMTID(stmt) (profiler_get_stmtid(pinfo->profile, pinfo->func, ((PLpgSQL_stmt *) stmt))) #define FUNC_NSTATEMENTS(pinfo) (pinfo->profile->nstatements) #define NATURAL_STMTID(pinfo, id) (id) #else #define STMTID(stmt) (((PLpgSQL_stmt *) stmt)->stmtid - 1) #define FUNC_NSTATEMENTS(pinfo) ((int) pinfo->func->nstatements) static int get_natural_stmtid(profiler_info *pinfo, int id) { int i; for (i = 0; i < FUNC_NSTATEMENTS(pinfo); i++) if (pinfo->profile->stmtid_reorder_map[i] == id) return i; return -1; } #define NATURAL_STMTID(pinfo, id) get_natural_stmtid(pinfo, id) #endif #define IS_PLPGSQL_STMT(stmt, typ) (PLPGSQL_STMT_TYPES stmt->cmd_type == typ) static bool is_cycle(PLpgSQL_stmt *stmt) { switch (PLPGSQL_STMT_TYPES 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 (PLPGSQL_STMT_TYPES 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 two different purposes: * * a) assign unique id to every plpgsql statement and * create statement -> id mapping * b) iterate over all commends and finalize total time * as measured total time substract child total time. * c) iterate over all commands and prepare result for * plpgsql_profiler_function_statements_tb function. * d) 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; profiler_profile *profile = pinfo->profile; bool prepare_profile_mode = mode == PLPGSQL_CHECK_STMT_WALKER_PREPARE_PROFILE; 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 total_us_time = 0; int64 nested_us_time = 0; int64 exec_count = 0; int stmtid = -1; char strbuf[100]; int n = 0; List *stmts; ListCell *lc; Assert(profile); if (prepare_profile_mode) { profile_register_stmt(pinfo, opts, stmt); } else { stmtid = STMTID(stmt); if (count_exec_time_mode) { /* * Get statement info from function execution context * by statement id. */ pstmt = &pinfo->stmts[stmtid]; pstmt->lineno = stmt->lineno; total_us_time = pstmt->us_total; opts->nested_us_time = 0; } 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. */ #if PG_VERSION_NUM < 120000 Assert(!opts->pi->current_chunk || opts->pi->current_statement == stmtid); #else Assert(!opts->pi->current_chunk || profile->stmtid_reorder_map[opts->pi->current_statement] == stmtid); #endif /* * 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) { int parent_stmtid = parent_stmt ? ((int) STMTID(parent_stmt)) : -1; if (opts->pi->ri) { plpgsql_check_put_profile_statement(opts->pi->ri, ppstmt ? ppstmt->queryid : NOQUERYID, NATURAL_STMTID(pinfo, stmtid), NATURAL_STMTID(pinfo, parent_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 *) plpgsql_check__stmt_typename_p(stmt)); } } 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); pstmt->us_total -= opts->nested_us_time; opts->nested_us_time = total_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; } } /* * 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.proctuple, &cinfo.rettype, &cinfo.volatility, &cinfo.trigtype, &cinfo.is_procedure); 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) { #if PG_VERSION_NUM >= 120000 profiler_profile *profile = pinfo->profile; #endif 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(pinfo); i++) { volatile profiler_stmt_reduced *prstmt; profiler_stmt *pstmt; #if PG_VERSION_NUM >= 120000 /* * 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 = profile->stmtid_reorder_map[i]; /* Skip gaps in reorder map */ if (n == -1) continue; pstmt = &pinfo->stmts[n]; #else pstmt = &pinfo->stmts[i]; #endif 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(pinfo); i++) { volatile profiler_stmt_reduced *prstmt; profiler_stmt *pstmt; #if PG_VERSION_NUM >= 120000 /* * 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 = profile->stmtid_reorder_map[i]; /* Skip gaps in reorder map */ if (n == -1) continue; pstmt = &pinfo->stmts[n]; #else pstmt = &pinfo->stmts[i]; #endif 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); } #if PG_VERSION_NUM < 120000 /* * PLpgSQL statements has not unique id. We can assign some unique id * that can be used for statements counters. Fast access to this id * is implemented via map structure. It is a array of lists structure. * * From PostgreSQL 12 we can use stmtid, but still we need map table, * because native stmtid has different order against lineno. But with * native stmtid, a creating map and searching in map is much faster. */ static void profiler_update_map(profiler_profile *profile, profiler_stmt_walker_options *opts, PLpgSQL_function *function, PLpgSQL_stmt *stmt) { int lineno = stmt->lineno; profiler_map_entry *pme; if (lineno > profile->stmts_map_max_lineno) { int lines; int i; /* calculate new size of map */ for (lines = profile->stmts_map_max_lineno; lineno > lines;) if (lines < 10000) lines *= 2; else lines += 10000; profile->stmts_map = repalloc(profile->stmts_map, (lines + 1) * sizeof(profiler_map_entry)); for (i = profile->stmts_map_max_lineno + 1; i <= lines; i++) memset(&profile->stmts_map[i], 0, sizeof(profiler_map_entry)); profile->stmts_map_max_lineno = lines; } pme = &profile->stmts_map[lineno]; if (!pme->stmt) { pme->function = function; pme->stmt = stmt; pme->stmtid = opts->stmtid++; pme->next = NULL; } else { MemoryContext oldcxt; profiler_map_entry *new_pme; oldcxt = MemoryContextSwitchTo(profiler_mcxt); new_pme = palloc0(sizeof(profiler_map_entry)); new_pme->function = function; new_pme->stmt = stmt; new_pme->stmtid = opts->stmtid++; new_pme->next = NULL; while (pme->next) pme = pme->next; pme->next = new_pme; MemoryContextSwitchTo(oldcxt); } } #endif static void profile_register_stmt(profiler_info *pinfo, profiler_stmt_walker_options *opts, PLpgSQL_stmt *stmt) { #if PG_VERSION_NUM < 120000 profiler_update_map(pinfo->profile, opts, pinfo->func, stmt); #else pinfo->profile->stmtid_reorder_map[opts->stmtid++] = stmt->stmtid - 1; #endif } /* * Returns statement id assigned to plpgsql statement. Should be * fast, because lineno is usually unique. */ #if PG_VERSION_NUM < 120000 static int profiler_get_stmtid(profiler_profile *profile, PLpgSQL_function *function, PLpgSQL_stmt *stmt) { int lineno = stmt->lineno; profiler_map_entry *pme; int i; bool found = false; for (i = 0; i < profile->n_mapped_functions; i++) { if (profile->mapped_functions[i] == function) { found = true; break; } } if (!found) elog(ERROR, "Internal error - this compiled function has not created statement map"); if (lineno > profile->stmts_map_max_lineno) elog(ERROR, "broken statement map - too high lineno"); pme = &profile->stmts_map[lineno]; /* pme->stmt should not be null */ if (!pme->stmt) elog(ERROR, "broken statement map - broken format on line: %d", lineno); while (pme && ! (pme->stmt == stmt && pme->function == function)) pme = pme->next; /* we should to find statement */ if (!pme) elog(ERROR, "broken statement map - cannot to find statement on line: %d", lineno); return pme->stmtid; } #endif /* * 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); if (count_exec_time) opts->nested_us_time = 0; if (collect_coverage) opts->nested_exec_count = 0; 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; #if PG_VERSION_NUM >= 110000 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 */ #endif /* PG_VERSION_NUM >= 110000 */ 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_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; *dynamic = true; } else expr = o->argquery; } case PLPGSQL_STMT_BLOCK: case PLPGSQL_STMT_FORS: #if PG_VERSION_NUM >= 110000 case PLPGSQL_STMT_COMMIT: case PLPGSQL_STMT_ROLLBACK: #endif 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); (*plpgsql_check_plugin_var_ptr)->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(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; } /* * This routine does an update or creating new profile. Function's profile * holds statements metadata (statement map). The statement map is necessary * for pre pg 12 releases, where we have not statement id. Instead we have * a structure, that allows mapping statement's pointer to assigned id. * The work is complicated by fact so we can have more different (independent) * tries of statements for one real function. Then we need two fields * (function_ptr, stmt_ptr) for workable searching statement id for all * compiled variant of one function (identified by oid). * * PostgreSQL 12 has assigned statement id, so there is not necessity to * prepare and maintain statement map. On second hand these releases needs * reorder map for reordering statement id to natural order of execution. */ static void prepare_profile(profiler_info *pinfo, profiler_profile *profile, bool init) { profiler_stmt_walker_options opts; PLpgSQL_function *func; int i; #if PG_VERSION_NUM < 120000 bool found = false; #endif Assert(pinfo && pinfo->func); memset(&opts, 0, sizeof(profiler_stmt_walker_options)); func = pinfo->func; pinfo->profile = profile; if (init) { MemoryContext oldcxt; #if PG_VERSION_NUM < 120000 oldcxt = MemoryContextSwitchTo(profiler_mcxt); profile->nstatements = 0; profile->n_mapped_functions = 0; profile->stmts_map_max_lineno = 200; profile->max_mapped_functions = 10; profile->stmts_map = palloc0((profile->stmts_map_max_lineno + 1) * sizeof(profiler_map_entry)); profile->mapped_functions = palloc0(profile->max_mapped_functions * sizeof(PLpgSQL_function *)); MemoryContextSwitchTo(oldcxt); #else oldcxt = MemoryContextSwitchTo(profiler_mcxt); profile->stmtid_reorder_map = palloc0(sizeof(int) * func->nstatements); /* * I found my bug in PLpgSQL runtime - when function statement counter * is incremental 2x for every FOR cycle. Until fix this bug, some entries * of reorder map can be uniinitialized, and we have to detect these entries. */ for (i = 0; i < ((int) func->nstatements); i++) profile->stmtid_reorder_map[i] = -1; MemoryContextSwitchTo(oldcxt); opts.stmtid = 0; profiler_stmt_walker(pinfo, PLPGSQL_CHECK_STMT_WALKER_PREPARE_PROFILE, (PLpgSQL_stmt *) func->action, NULL, NULL, 1, &opts); #endif } #if PG_VERSION_NUM < 120000 /* * Every touched incarnation should to have statement map. */ for (i = 0; i < profile->n_mapped_functions; i++) { if (profile->mapped_functions[i] == func) { found = true; break; } } if (!found) { /* * Ensure correct size of array of pointers to function incarnations with * prepared statement map. */ if (profile->n_mapped_functions == profile->max_mapped_functions) { int new_max_mapped_functions = profile->max_mapped_functions * 2; if (new_max_mapped_functions > 200) elog(ERROR, "too much different incarnations of one function (please, close session)"); profile->mapped_functions = repalloc(profile->mapped_functions, new_max_mapped_functions * sizeof(PLpgSQL_function *)); profile->max_mapped_functions = new_max_mapped_functions; } profile->mapped_functions[profile->n_mapped_functions++] = func; opts.stmtid = 0; profiler_stmt_walker(pinfo, PLPGSQL_CHECK_STMT_WALKER_PREPARE_PROFILE, (PLpgSQL_stmt *) func->action, NULL, NULL, 1, &opts); if (profile->nstatements > 0 && profile->nstatements != opts.stmtid) elog(ERROR, "internal error - unexpected number of statements in different function incarnations (%d <> %d)", opts.stmtid, profile->nstatements); profile->nstatements = opts.stmtid; } #endif } /* * 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) { #if PG_VERSION_NUM >= 120000 LOCAL_FCINFO(fake_fcinfo, 0); #else FunctionCallInfoData fake_fcinfo_data; FunctionCallInfo fake_fcinfo = &fake_fcinfo_data; #endif profiler_profile *profile; profiler_hashkey hk_function; bool found_profile = false; 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(); { PLpgSQL_stmt *stmt; 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); pinfo.func = plpgsql_check__compile_p(fake_fcinfo, false); profiler_init_hashkey(&hk_function, pinfo.func); profile = (profiler_profile *) hash_search(profiler_HashTable, (void *) &hk_function, HASH_ENTER, &found_profile); prepare_profile(&pinfo, profile, !found_profile); opts.pi = π opts.cs = cs; stmt = (PLpgSQL_stmt *) pinfo.func->action; profiler_stmt_walker(&pinfo, mode, stmt, NULL, NULL, 1, &opts); } 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; int queryids_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; 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 (0 && 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); } /* * plpgsql plugin related functions */ static profiler_info * init_profiler_info(profiler_info *pinfo, PLpgSQL_function *func) { if (!pinfo) { pinfo = palloc0(sizeof(profiler_info)); pinfo->pi_magic = PI_MAGIC; pinfo->func = func; } return pinfo; } /* * Try to search profile pattern for function. Creates profile pattern when * it doesn't exists. */ void plpgsql_check_profiler_func_init(PLpgSQL_execstate *estate, PLpgSQL_function *func) { profiler_info *pinfo = NULL; if (plpgsql_check_tracer) { #if PG_VERSION_NUM >= 120000 int group_number_counter = 0; #endif pinfo = init_profiler_info(pinfo, func); pinfo->trace_info_is_initialized = true; #if PG_VERSION_NUM >= 120000 pinfo->stmt_start_times = palloc0(sizeof(instr_time) * func->nstatements); pinfo->stmt_group_numbers = palloc(sizeof(int) * func->nstatements); pinfo->stmt_parent_group_numbers = palloc(sizeof(int) * func->nstatements); pinfo->stmt_disabled_tracers = palloc0(sizeof(int) * func->nstatements); plpgsql_check_set_stmt_group_number((PLpgSQL_stmt *) func->action, pinfo->stmt_group_numbers, pinfo->stmt_parent_group_numbers, 0, &group_number_counter, -1); pinfo->pragma_disable_tracer_stack = palloc(sizeof(bool) * (group_number_counter + 1)); /* now we have not access to outer estate */ pinfo->disable_tracer = false; plpgsql_check_runtime_pragma_vector_changed = false; #endif } if (plpgsql_check_profiler && func->fn_oid != InvalidOid) { profiler_profile *profile; profiler_hashkey hk; bool found; profiler_init_hashkey(&hk, func); profile = (profiler_profile *) hash_search(profiler_HashTable, (void *) &hk, HASH_ENTER, &found); pinfo = init_profiler_info(pinfo, func); prepare_profile(pinfo, profile, !found); pinfo->stmts = palloc0(FUNC_NSTATEMENTS(pinfo) * sizeof(profiler_stmt)); } if (pinfo) { INSTR_TIME_SET_CURRENT(pinfo->start_time); pinfo->estate = estate; } estate->plugin_info = pinfo; if (top_pinfo) { top_pinfo->pinfo = pinfo; curr_eval_econtext = estate->eval_econtext; } } void plpgsql_check_profiler_func_end(PLpgSQL_execstate *estate, PLpgSQL_function *func) { profiler_info *pinfo = NULL; if (estate) pinfo = (profiler_info *) estate->plugin_info; else if (top_pinfo) pinfo = top_pinfo->pinfo; if (plpgsql_check_tracer && pinfo ) { if (estate) plpgsql_check_tracer_on_func_end(estate, func); #if PG_VERSION_NUM >= 120000 pfree(pinfo->stmt_start_times); pfree(pinfo->stmt_group_numbers); pfree(pinfo->stmt_parent_group_numbers); pfree(pinfo->stmt_disabled_tracers); pfree(pinfo->pragma_disable_tracer_stack); #endif } if (plpgsql_check_profiler && pinfo && pinfo->profile && func->fn_oid != InvalidOid) { int entry_stmtid = STMTID(pinfo->func->action); instr_time end_time; uint64 elapsed; profiler_stmt_walker_options opts; 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 = 0; pinfo->stmts[entry_stmtid].us_total = elapsed; pinfo->stmts[entry_stmtid].us_max = elapsed; } /* 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, func); update_persistent_fstats(func, elapsed); pfree(pinfo->stmts); } if ((plpgsql_check_tracer || plpgsql_check_profiler) && pinfo) pfree(pinfo); } void plpgsql_check_profiler_stmt_beg(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt) { profiler_info *pinfo = (profiler_info *) estate->plugin_info; if (top_pinfo && top_pinfo->pinfo) { if (estate->eval_econtext != curr_eval_econtext) { if (estate->cur_error) { int i; bool found = false; /* * detected exception handler. We have to close * all statements from stack, until we find common * eval_context. The reduction is possible, only when * we found common eval_context. We can lost if if this * shared context is over then NESTED_STMTS_STACK_SIZE. */ for (i = top_pinfo->nested_stmts_count - 1; i >= 0; i--) { if (i < NESTED_STMTS_STACK_SIZE) { if (top_pinfo->eval_econtext[i] == estate->eval_econtext) { found = true; break; } } } if (found) { for (i = top_pinfo->nested_stmts_count - 1; i >= 0; i--) { if (i < NESTED_STMTS_STACK_SIZE) { if (top_pinfo->eval_econtext[i] == estate->eval_econtext) { top_pinfo->nested_stmts_count = i + 1; break; } else plpgsql_check_profiler_stmt_end(NULL, top_pinfo->nested_stmts[i]); } } } } curr_eval_econtext = estate->eval_econtext; } if (top_pinfo->nested_stmts_count < NESTED_STMTS_STACK_SIZE) { top_pinfo->nested_stmts[top_pinfo->nested_stmts_count] = stmt; top_pinfo->eval_econtext[top_pinfo->nested_stmts_count] = estate->eval_econtext; } top_pinfo->nested_stmts_count++; top_pinfo->err_stmt = stmt; } if (plpgsql_check_tracer && pinfo) { #if PG_VERSION_NUM >= 120000 int stmtid = stmt->stmtid - 1; int sgn = pinfo->stmt_group_numbers[stmtid]; int pgn = pinfo->stmt_parent_group_numbers[stmtid]; plpgsql_check_runtime_pragma_vector_changed = false; /* * First statement in group has valid parent group number. * We use this number for copy setting from outer group * to nested group. */ if (pgn != -1) { pinfo->pragma_disable_tracer_stack[sgn] = pinfo->pragma_disable_tracer_stack[pgn]; } pinfo->stmt_disabled_tracers[stmtid] = pinfo->pragma_disable_tracer_stack[sgn]; #endif plpgsql_check_tracer_on_stmt_beg(estate, stmt); } if (stmt->cmd_type == PLPGSQL_STMT_ASSERT && plpgsql_check_enable_tracer && plpgsql_check_trace_assert) plpgsql_check_trace_assert_on_stmt_beg(estate, stmt); if (plpgsql_check_profiler && pinfo && pinfo->profile && estate->func->fn_oid != InvalidOid) { int stmtid = STMTID(stmt); profiler_stmt *pstmt = &pinfo->stmts[stmtid]; Assert(pinfo->pi_magic == PI_MAGIC); INSTR_TIME_SET_CURRENT(pstmt->start_time); } } /* * Cleaning mode is used for closing unfinished statements after an exception. */ void plpgsql_check_profiler_stmt_end(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt) { profiler_info *pinfo; bool cleaning_mode = false; bool is_error_stmt = false; if (!estate) { Assert(top_pinfo && top_pinfo->pinfo); pinfo = top_pinfo->pinfo; estate = pinfo->estate; is_error_stmt = top_pinfo->err_stmt == stmt; cleaning_mode = true; } else pinfo = (profiler_info *) estate->plugin_info; if (top_pinfo && top_pinfo->pinfo && !cleaning_mode) { int i; bool found = false; top_pinfo->nested_stmts_count--; /* * try to synchronize counter, we can lost synchronization when * there was handled exception and deeper statement's stack than * NESTED_STMTS_STACK_SIZE. */ for (i = top_pinfo->nested_stmts_count; i >= 0; i--) { if (i < NESTED_STMTS_STACK_SIZE) { if (top_pinfo->nested_stmts[i] == stmt) { found = true; break; } } } if (found) { for (i = top_pinfo->nested_stmts_count; i >= 0; i--) { if (i < NESTED_STMTS_STACK_SIZE) { if (top_pinfo->nested_stmts[i] == stmt) { top_pinfo->nested_stmts_count = i; break; } else plpgsql_check_profiler_stmt_end(NULL, top_pinfo->nested_stmts[i]); } } } top_pinfo->err_stmt = NULL; } if (plpgsql_check_tracer && pinfo) { #if PG_VERSION_NUM >= 120000 int stmtid = stmt->stmtid - 1; if (plpgsql_check_runtime_pragma_vector_changed) { int sgn; sgn = pinfo->stmt_group_numbers[stmtid]; pinfo->pragma_disable_tracer_stack[sgn] = plpgsql_check_runtime_pragma_vector.disable_tracer; } #endif /* These nodes was not executed */ if (!cleaning_mode) plpgsql_check_tracer_on_stmt_end(estate, stmt); } if (plpgsql_check_profiler && pinfo && pinfo->profile && pinfo->func->fn_oid != InvalidOid) { int stmtid = STMTID(stmt); profiler_stmt *pstmt = &pinfo->stmts[stmtid]; instr_time end_time; uint64 elapsed; instr_time end_time2; Assert(pinfo->pi_magic == PI_MAGIC); if (pstmt->queryid == NOQUERYID && estate) pstmt->queryid = profiler_get_queryid(estate, stmt, &pstmt->has_queryid, &pstmt->qparams); 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); if (!cleaning_mode) pstmt->rows += estate->eval_processed; pstmt->exec_count++; if (is_error_stmt) pstmt->exec_count_err++; } } 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); } /* * Used as needs_fmgr_hook. All plpgsql functions * needs this hook when profiler is active. */ bool plpgsql_check_needs_fmgr_hook(Oid fn_oid) { if (plpgsql_check_next_needs_fmgr_hook && (*plpgsql_check_next_needs_fmgr_hook)(fn_oid)) return true; if (!plpgsql_check_profiler) return false; return plpgsql_check_is_plpgsql_function(fn_oid); } void plpgsql_check_fmgr_hook(FmgrHookEventType event, FmgrInfo *flinfo, Datum *private) { fmgr_hook_private *stack; switch (event) { case FHET_START: stack = (fmgr_hook_private *) DatumGetPointer(*private); if (!stack) { MemoryContext oldcxt; oldcxt = MemoryContextSwitchTo(flinfo->fn_mcxt); stack = palloc(sizeof(fmgr_hook_private)); stack->use_plpgsql = plpgsql_check_is_plpgsql_function(flinfo->fn_oid); stack->next_private = 0; MemoryContextSwitchTo(oldcxt); *private = PointerGetDatum(stack); } if (stack->use_plpgsql) { profiler_stack *pstack = palloc0(sizeof(profiler_stack)); pstack->prev_pinfo = top_pinfo; top_pinfo = pstack; } if (plpgsql_check_next_fmgr_hook) (*plpgsql_check_next_fmgr_hook) (event, flinfo, &stack->next_private); break; case FHET_END: case FHET_ABORT: stack = (fmgr_hook_private *) DatumGetPointer(*private); /* * When plpgsql_check is loaded due dependency on some executed function, * then FHET_START and FHET_END (or FHET_ABORT) can be assymetric. So * stack can be NULL. See issue #101 */ if (stack && stack->use_plpgsql) { profiler_stack *pstack = top_pinfo->prev_pinfo; if (event == FHET_ABORT) { profiler_info *pinfo = top_pinfo->pinfo; int i; if (pinfo) { for (i = top_pinfo->nested_stmts_count - 1; i >= 0; i--) { if (i < NESTED_STMTS_STACK_SIZE) plpgsql_check_profiler_stmt_end(NULL, top_pinfo->nested_stmts[i]); } plpgsql_check_profiler_func_end(NULL, pinfo->func); } } pfree(top_pinfo); top_pinfo = pstack; } if (plpgsql_check_next_fmgr_hook) (*plpgsql_check_next_fmgr_hook) (event, flinfo, &stack->next_private); break; } } plpgsql_check-2.1.2/src/report.c000066400000000000000000000262461417472474500166320ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * report.c * * last stage checks * * by Pavel Stehule 2013-2021 * *------------------------------------------------------------------------- */ #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_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; } /* * 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.1.2/src/stmtwalk.c000066400000000000000000001553641417472474500171710ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * stmtwalk.c * * iteration over plpgsql statements loop * * by Pavel Stehule 2013-2021 * *------------------------------------------------------------------------- */ #include "plpgsql_check.h" #include "access/tupconvert.h" #include "catalog/pg_collation.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 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); #if PG_VERSION_NUM >= 110000 static void check_dynamic_sql(PLpgSQL_checkstate *cstate, PLpgSQL_stmt *stmt, PLpgSQL_expr *query, bool into, PLpgSQL_variable *target, List *params); #else static void check_dynamic_sql(PLpgSQL_checkstate *cstate, PLpgSQL_stmt *stmt, PLpgSQL_expr *query, bool into, PLpgSQL_row *row, PLpgSQL_rec *rec, List *params); #endif #if PG_VERSION_NUM >= 110000 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); } #endif bool plpgsql_check_is_reserved_keyword(char *name) { int i; #if PG_VERSION_NUM < 120000 for (i = 0; i < NumScanKeywords; i++) { if (ScanKeywords[i].category == RESERVED_KEYWORD && strcmp(name, ScanKeywords[i].name) == 0) return true; } #else 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; } } #endif return false; } /* * 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; 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 = push_stmt_to_stmt_stack(cstate); oldowner = CurrentResourceOwner; BeginInternalSubTransaction(NULL); MemoryContextSwitchTo(oldCxt); PG_TRY(); { switch (PLPGSQL_STMT_TYPES stmt->cmd_type) { case PLPGSQL_STMT_BLOCK: { PLpgSQL_stmt_block *stmt_block = (PLpgSQL_stmt_block *) stmt; int i; PLpgSQL_datum *d; for (i = 0; i < stmt_block->n_initvars; i++) { char *refname; 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 PG_VERSION_NUM >= 110000 if (var->default_val) plpgsql_check_assignment(cstate, var->default_val, NULL, NULL, var->dno); #else if (d->dtype == PLPGSQL_DTYPE_VAR && ((PLpgSQL_var *) var)->default_val) plpgsql_check_assignment(cstate, ((PLpgSQL_var *) var)->default_val, NULL, NULL, var->dno); #endif cstate->estate->err_text = NULL; pfree(str.data); } refname = plpgsql_check_datum_get_refname(d); if (refname != NULL) { ListCell *l; 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 overlapped", refname); plpgsql_check_put_error(cstate, 0, 0, str.data, "Local variable overlap function parameter.", NULL, PLPGSQL_CHECK_WARNING_OTHERS, 0, NULL, NULL); pfree(str.data); } } if (found_shadowed_variable(refname, outer_stmt, cstate)) { StringInfoData str; initStringInfo(&str); appendStringInfo(&str, "variable \"%s\" shadows a previously defined variable", 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); } } } 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; 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; Oid result_oid; 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]; /* * 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) #ifdef PLPGSQL_BUILD_DATATYPE_4 t_var->datatype = plpgsql_check__build_datatype_p(result_oid, -1, cstate->estate->func->fn_input_collation, t_var->datatype->origtypname); #else t_var->datatype = plpgsql_check__build_datatype_p(result_oid, -1, cstate->estate->func->fn_input_collation); #endif 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_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; #if PG_VERSION_NUM >= 110000 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); #else plpgsql_check_row_or_rec(cstate, stmt_fors->row, stmt_fors->rec); /* we need to set hidden variable type */ plpgsql_check_assignment(cstate, stmt_fors->query, stmt_fors->rec, stmt_fors->row, -1); #endif 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; #if PG_VERSION_NUM >= 110000 check_variable(cstate, stmt_forc->var); #else plpgsql_check_row_or_rec(cstate, stmt_forc->row, stmt_forc->rec); #endif plpgsql_check_expr_as_sqlstmt_data(cstate, stmt_forc->argquery); #if PG_VERSION_NUM >= 110000 if (var->cursor_explicit_expr != NULL) plpgsql_check_assignment_to_variable(cstate, var->cursor_explicit_expr, stmt_forc->var, -1); #else if (var->cursor_explicit_expr != NULL) plpgsql_check_assignment(cstate, var->cursor_explicit_expr, stmt_forc->rec, stmt_forc->row, -1); #endif 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, #if PG_VERSION_NUM >= 110000 stmt_dynfors->var, #else stmt_dynfors->row, stmt_dynfors->rec, #endif 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); 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) == 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; 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; 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 (recvar_tupdesc(rec) && 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 && 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 */ } } *closing = PLPGSQL_CHECK_CLOSED; 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"))); #if PG_VERSION_NUM >= 110000 tupdesc = estate->tuple_store_desc; #else tupdesc = estate->rettupdesc; #endif 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; TupleConversionMap *tupmap; 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) { 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: { PLpgSQL_row *row = (PLpgSQL_row *) retvar; bool row_is_valid_result; row_is_valid_result = true; if (tupdesc) { 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, #if PG_VERSION_NUM >= 110000 NULL, #else NULL, NULL, #endif 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) { bool isnull; char *value; value = plpgsql_check_expr_get_string(cstate, opt->expr, &isnull); 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) { #if PG_VERSION_NUM >= 110000 check_variable(cstate, stmt_execsql->target); plpgsql_check_assignment_to_variable(cstate, stmt_execsql->sqlstmt, stmt_execsql->target, -1); #else plpgsql_check_row_or_rec(cstate, stmt_execsql->row, stmt_execsql->rec); plpgsql_check_assignment(cstate, stmt_execsql->sqlstmt, stmt_execsql->rec, stmt_execsql->row, -1); #endif } 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, #if PG_VERSION_NUM >= 110000 stmt_dynexecute->target, #else stmt_dynexecute->row, stmt_dynexecute->rec, #endif 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]); ListCell *l; 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); plpgsql_check_expr(cstate, stmt_open->dynquery); foreach(l, stmt_open->params) { plpgsql_check_expr(cstate, (PLpgSQL_expr *) lfirst(l)); } 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); } } 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]); #if PG_VERSION_NUM >= 110000 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); #else plpgsql_check_row_or_rec(cstate, stmt_fetch->row, stmt_fetch->rec); if (var != NULL && var->cursor_explicit_expr != NULL) plpgsql_check_assignment(cstate, var->cursor_explicit_expr, stmt_fetch->rec, stmt_fetch->row, -1); #endif 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 >= 110000 #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"))); 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; #endif /* PG_VERSION_NUM >= 110000 */ default: elog(ERROR, "unrecognized cmd_type: %d", stmt->cmd_type); } pop_stmt_from_stmt_stack(cstate); ReleaseCurrentSubTransaction(); MemoryContextSwitchTo(oldCxt); CurrentResourceOwner = oldowner; SPI_restore_connection(); } 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); /* reconnect spi */ SPI_restore_connection(); } PG_END_TRY(); if (!cstate->was_pragma) cstate->pragma_vector = pragma_vector; else cstate->was_pragma = false; } /* * Ensure check for all statements in list * */ static void check_stmts(PLpgSQL_checkstate *cstate, List *stmts, int *closing, List **exceptions) { ListCell *lc; int closing_local; List *exceptions_local; volatile bool dead_code_alert = false; plpgsql_check_pragma_vector prev_pragma_vector = cstate->pragma_vector; *closing = PLPGSQL_CHECK_UNCLOSED; *exceptions = NIL; PG_TRY(); { 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; } } } } PG_CATCH(); { cstate->pragma_vector = prev_pragma_vector; cstate->was_pragma = false; 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 *) palloc(sizeof(PLpgSQL_stmt_stack_item)); stmt_stack_item->stmt = stmt; switch (PLPGSQL_STMT_TYPES 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 (PLPGSQL_STMT_TYPES 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 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; PLpgSQL_datum *d; for (i = 0; i < stmt_block->n_initvars; i++) { char *refname; d = cstate->estate->func->datums[stmt_block->initvarnos[i]]; refname = plpgsql_check_datum_get_refname(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 = (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, #if PG_VERSION_NUM >= 110000 PLpgSQL_variable *target, #else PLpgSQL_row *row, PLpgSQL_rec *rec, #endif 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) { if (fexpr->args && IsA(linitial(fexpr->args), Const)) { StringInfoData sinfo; char c, *fmt; bool subst_is_ok = true; bool found_ident_placeholder = false; bool found_literal_placeholder = false; expr_is_const = fexpr->funcid == FORMAT_0PARAM_OID; fmt = plpgsql_check_const_to_string((Const *) linitial(fexpr->args)); /* * The placeholders can be used only in FORMAT_NPARAM function, * but for simplicity and consistency we check FORMAT_0PARAM and * FORMAT_NPARAM together */ initStringInfo(&sinfo); while ((c = *fmt++)) { if (c == '%') { c = *fmt++; if (c == '%') { appendStringInfoChar(&sinfo, c); } else if (c == 'I') { appendStringInfoString(&sinfo, "\"%I\""); expr_is_const = false; found_ident_placeholder = true; } else if (c == 'L') { /* * Original idea was used external parameter, * but external parameters requires known type, * so most safe value is NULL instead. */ appendStringInfo(&sinfo, " null "); found_literal_placeholder = false; expr_is_const = false; } else { /* * Because %s is used, we know nothing about form * of output string, and has not any sense to continue * in check. */ subst_is_ok = false; expr_is_const = false; break; } } else appendStringInfoChar(&sinfo, c); } if (subst_is_ok) { if (!found_literal_placeholder) { #if PG_VERSION_NUM >= 140000 /* in this case we can do only basic parser check */ raw_parser(sinfo.data, RAW_PARSE_DEFAULT); #else raw_parser(sinfo.data); #endif } if (!found_ident_placeholder) dynquery = sinfo.data; } } } } else if (IsA(expr_node, Const)) { expr_is_const = true; dynquery = plpgsql_check_const_to_string((Const *) expr_node); } 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 < 110000 dynexpr->dtype = PLPGSQL_DTYPE_EXPR; dynexpr->dno = -1; #endif #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; SPI_restore_connection(); } 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) { #if PG_VERSION_NUM >= 110000 check_variable(cstate, target); plpgsql_check_assignment_to_variable(cstate, dynexpr, target, -1); #else plpgsql_check_row_or_rec(cstate, row, rec); plpgsql_check_assignment(cstate, dynexpr, rec, row, -1); #endif } } /* 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 PG_VERSION_NUM >= 110000 if (target->dtype == PLPGSQL_DTYPE_REC) raise_unknown_rec_warning = true; #else if (rec) raise_unknown_rec_warning = true; #endif } } /* recheck if target rec var has assigned tupdesc */ if (into) { #if PG_VERSION_NUM >= 110000 check_variable(cstate, target); if (raise_unknown_rec_warning || (target->dtype == PLPGSQL_DTYPE_REC && !has_assigned_tupdesc(cstate, (PLpgSQL_rec *) target))) #else plpgsql_check_row_or_rec(cstate, row, rec); if (raise_unknown_rec_warning || (rec != NULL && !has_assigned_tupdesc(cstate, rec))) #endif { #if PG_VERSION_NUM >= 110000 if (!bms_is_member(target->dno, cstate->typed_variables)) #else if (!bms_is_member(rec->dno, cstate->typed_variables)) #endif 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.1.2/src/tablefunc.c000066400000000000000000000376561417472474500172710ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * tablefunc.c * * top functions - display results in table format * * by Pavel Stehule 2013-2021 * *------------------------------------------------------------------------- */ #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; } /* * 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; if (PG_NARGS() != 17) elog(ERROR, "unexpected number of parameters, you should to update extension"); /* 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(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"); 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); /* 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"))); cinfo.other_warnings = false; cinfo.performance_warnings = false; cinfo.extra_warnings = false; cinfo.security_warnings = false; } /* all warnings */ else if (PG_GETARG_BOOL(16)) { cinfo.other_warnings = true; cinfo.performance_warnings = true; cinfo.extra_warnings = true; cinfo.security_warnings = true; } 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.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); 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.proctuple, &cinfo.rettype, &cinfo.volatility, &cinfo.trigtype, &cinfo.is_procedure); plpgsql_check_precheck_conditions(&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; if (PG_NARGS() != 16) elog(ERROR, "unexpected number of parameters, you should to update extension"); /* 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(9)) ERR_NULL_OPTION("anyelementtype"); if (PG_ARGISNULL(10)) ERR_NULL_OPTION("anyenumtype"); if (PG_ARGISNULL(11)) ERR_NULL_OPTION("anyrangetype"); if (PG_ARGISNULL(12)) ERR_NULL_OPTION("anycompatibletype"); if (PG_ARGISNULL(13)) ERR_NULL_OPTION("anycompatiblerangetype"); if (PG_ARGISNULL(14)) ERR_NULL_OPTION("without_warnings"); if (PG_ARGISNULL(15)) ERR_NULL_OPTION("all_warnings"); 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); /* without_warnings */ if (PG_GETARG_BOOL(14)) { if (PG_GETARG_BOOL(15)) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("without_warnings and all_warnings cannot be true same time"))); cinfo.other_warnings = false; cinfo.performance_warnings = false; cinfo.extra_warnings = false; cinfo.security_warnings = false; } /* all warnings */ else if (PG_GETARG_BOOL(15)) { cinfo.other_warnings = true; cinfo.performance_warnings = true; cinfo.extra_warnings = true; cinfo.security_warnings = true; } cinfo.anyelementoid = PG_GETARG_OID(9); cinfo.anyenumoid = PG_GETARG_OID(10); cinfo.anyrangeoid = PG_GETARG_OID(11); cinfo.anycompatibleoid = PG_GETARG_OID(12); cinfo.anycompatiblerangeoid = PG_GETARG_OID(13); if (PG_ARGISNULL(7)) cinfo.oldtable = NULL; else cinfo.oldtable = NameStr(*(PG_GETARG_NAME(7))); if (PG_ARGISNULL(8)) cinfo.newtable = NULL; else cinfo.newtable = NameStr(*(PG_GETARG_NAME(8))); 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.proctuple, &cinfo.rettype, &cinfo.volatility, &cinfo.trigtype, &cinfo.is_procedure); plpgsql_check_precheck_conditions(&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; if (PG_NARGS() != 2) elog(ERROR, "unexpected number of parameters, you should to update extension"); /* check to see if caller supports us returning a tuplestore */ rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; SetReturningFunctionCheck(rsinfo); 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.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.proctuple, &cinfo.rettype, &cinfo.volatility, &cinfo.trigtype, &cinfo.is_procedure); 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; if (PG_NARGS() != 1) elog(ERROR, "unexpected number of parameters, you should to update extension"); /* 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.proctuple, &cinfo.rettype, &cinfo.volatility, &cinfo.trigtype, &cinfo.is_procedure); 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; if (PG_NARGS() != 1) elog(ERROR, "unexpected number of parameters, you should to update extension"); /* 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.proctuple, &cinfo.rettype, &cinfo.volatility, &cinfo.trigtype, &cinfo.is_procedure); 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.1.2/src/tracer.c000066400000000000000000001066031417472474500165730ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * tracer.c * * tracer related code * * by Pavel Stehule 2013-2021 * *------------------------------------------------------------------------- */ #include "plpgsql_check.h" #include "plpgsql_check_builtins.h" #include "catalog/pg_type.h" #include "mb/pg_wchar.h" #include "utils/builtins.h" #include "utils/lsyscache.h" #include "utils/memutils.h" #include "utils/syscache.h" #if PG_VERSION_NUM < 110000 #include "access/htup_details.h" #endif 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; 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); #if PG_VERSION_NUM >= 140000 #define STREXPR_START 0 #else #define STREXPR_START 7 #endif #if PG_VERSION_NUM >= 120000 static void set_stmts_group_number(List *stmts, int *group_numbers, int *parent_group_numbers, int sgn, int *cgn, int spgn); /* * sgn - statement group number * cgn - counter for group number * spgn - statemen parent group number */ void plpgsql_check_set_stmt_group_number(PLpgSQL_stmt *stmt, int *group_numbers, int *parent_group_numbers, int sgn, int *cgn, int psgn) { ListCell *lc; int stmtid = stmt->stmtid - 1; group_numbers[stmtid] = sgn; parent_group_numbers[stmtid] = psgn; switch (PLPGSQL_STMT_TYPES stmt->cmd_type) { case PLPGSQL_STMT_BLOCK: { PLpgSQL_stmt_block *stmt_block = (PLpgSQL_stmt_block *) stmt; set_stmts_group_number(stmt_block->body, group_numbers, parent_group_numbers, ++(*cgn), cgn, sgn); if (stmt_block->exceptions) { foreach(lc, stmt_block->exceptions->exc_list) { set_stmts_group_number( ((PLpgSQL_exception *) lfirst(lc))->action, group_numbers, parent_group_numbers, ++(*cgn), cgn, sgn); } } } break; case PLPGSQL_STMT_IF: { PLpgSQL_stmt_if *stmt_if = (PLpgSQL_stmt_if *) stmt; set_stmts_group_number(stmt_if->then_body, group_numbers, parent_group_numbers, ++(*cgn), cgn, sgn); foreach(lc, stmt_if->elsif_list) { set_stmts_group_number(((PLpgSQL_if_elsif *) lfirst(lc))->stmts, group_numbers, parent_group_numbers, ++(*cgn), cgn, sgn); } set_stmts_group_number(stmt_if->else_body, group_numbers, parent_group_numbers, ++(*cgn), cgn, sgn); } break; case PLPGSQL_STMT_CASE: { PLpgSQL_stmt_case *stmt_case = (PLpgSQL_stmt_case *) stmt; foreach(lc, stmt_case->case_when_list) { set_stmts_group_number(((PLpgSQL_case_when *) lfirst(lc))->stmts, group_numbers, parent_group_numbers, ++(*cgn), cgn, sgn); } set_stmts_group_number(stmt_case->else_stmts, group_numbers, parent_group_numbers, ++(*cgn), cgn, sgn); } break; case PLPGSQL_STMT_LOOP: { set_stmts_group_number(((PLpgSQL_stmt_loop *) stmt)->body, group_numbers, parent_group_numbers, ++(*cgn), cgn, sgn); } break; case PLPGSQL_STMT_FORI: { set_stmts_group_number(((PLpgSQL_stmt_fori *) stmt)->body, group_numbers, parent_group_numbers, ++(*cgn), cgn, sgn); } break; case PLPGSQL_STMT_FORS: { set_stmts_group_number(((PLpgSQL_stmt_fors *) stmt)->body, group_numbers, parent_group_numbers, ++(*cgn), cgn, sgn); } break; case PLPGSQL_STMT_FORC: { set_stmts_group_number(((PLpgSQL_stmt_forc *) stmt)->body, group_numbers, parent_group_numbers, ++(*cgn), cgn, sgn); } break; case PLPGSQL_STMT_DYNFORS: { set_stmts_group_number(((PLpgSQL_stmt_dynfors *) stmt)->body, group_numbers, parent_group_numbers, ++(*cgn), cgn, sgn); } break; case PLPGSQL_STMT_FOREACH_A: { set_stmts_group_number(((PLpgSQL_stmt_foreach_a *) stmt)->body, group_numbers, parent_group_numbers, ++(*cgn), cgn, sgn); } break; case PLPGSQL_STMT_WHILE: { set_stmts_group_number(((PLpgSQL_stmt_while *) stmt)->body, group_numbers, parent_group_numbers, ++(*cgn), cgn, sgn); } break; default: ; } } static void set_stmts_group_number(List *stmts, int *group_numbers, int *parent_group_numbers, int sgn, int *cgn, int psgn) { ListCell *lc; bool is_first = true; foreach(lc, stmts) { plpgsql_check_set_stmt_group_number((PLpgSQL_stmt *) lfirst(lc), group_numbers, parent_group_numbers, sgn, cgn, is_first ? psgn : -1); is_first = false; } } #endif /* * 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 *str; char *refname; appendStringInfoChar(ds, '('); for (i = 0; i < row->nfields; i++) { 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 PG_VERSION_NUM < 110000 if (rec->tup && HeapTupleIsValid(rec->tup)) { Datum value; Oid typid; MemoryContext oldcontext; Assert(rec->tupdesc); BlessTupleDesc(rec->tupdesc); *isnull = false; oldcontext = MemoryContextSwitchTo(estate->eval_econtext->ecxt_per_tuple_memory); typid = rec->tupdesc->tdtypeid; value = heap_copy_tuple_as_datum(rec->tup, rec->tupdesc); MemoryContextSwitchTo(oldcontext); return convert_value_to_string(estate, value, typid); } else return NULL; #else if (rec->erh && !ExpandedRecordIsEmpty(rec->erh)) { *isnull = false; return convert_value_to_string(estate, ExpandedRecordGetDatum(rec->erh), rec->rectypeid); } else return NULL; #endif } 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 PG_VERSION_NUM < 110000 if (func->fn_is_trigger == PLPGSQL_DML_TRIGGER) { 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]; PLpgSQL_var *var; char *str; var = (PLpgSQL_var *) estate->datums[func->tg_when_varno]; Assert(!var->isnull); str = TextDatumGetCString(var->value); trgtime = strcmp(str, "BEFORE") == 0 ? "before" : "after"; pfree(str); var = (PLpgSQL_var *) estate->datums[func->tg_level_varno]; Assert(!var->isnull); str = TextDatumGetCString(var->value); trgtyp = strcmp(str, "ROW") == 0 ? "row" : "statement"; pfree(str); var = (PLpgSQL_var *) estate->datums[func->tg_op_varno]; Assert(!var->isnull); str = TextDatumGetCString(var->value); if (strcmp(str, "INSERT") == 0) { trgcmd = " insert"; rec_old_varno = -1; } else if (strcmp(str, "UPDATE") == 0) { trgcmd = " update"; } else if (strcmp(str, "DELETE") == 0) { trgcmd = " delete"; rec_new_varno = -1; } else trgcmd = ""; pfree(str); 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); } else if (func->fn_is_trigger == PLPGSQL_EVENT_TRIGGER) { elog(plpgsql_check_tracer_errlevel, "#%-*d%*s triggered by event trigger", frame_width, frame_num, indent + 4, ""); } #else if (func->fn_is_trigger == PLPGSQL_DML_TRIGGER) { Assert(estate->trigdata); 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]; 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, ""); } #endif /* 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); } /* * Tracer event routines */ void plpgsql_check_tracer_on_func_beg(PLpgSQL_execstate *estate, PLpgSQL_function *func) { PLpgSQL_execstate *outer_estate; int frame_num; int level; instr_time start_time; Oid fn_oid; int indent; int frame_width; Assert(plpgsql_check_tracer); /* Allow tracing only when it is explicitly allowed */ if (!plpgsql_check_enable_tracer) return; fn_oid = plpgsql_check_tracer_test_mode ? 0 : func->fn_oid; /* * initialize plugin's near_outer_estate and level fields * from stacked error contexts. Have to be called here. */ plpgsql_check_init_trace_info(estate); if (plpgsql_check_get_trace_info(estate, NULL, &outer_estate, &frame_num, &level, &start_time)) { (void) start_time; indent = level * 2 + (plpgsql_check_tracer_verbosity == PGERROR_VERBOSE ? 6 : 0); frame_width = plpgsql_check_tracer_verbosity == PGERROR_VERBOSE ? 6 : 3; if (plpgsql_check_tracer_verbosity >= PGERROR_DEFAULT) elog(plpgsql_check_tracer_errlevel, "#%-*d%*s ->> start of %s%s (oid=%u)", frame_width, frame_num, indent, "", func->fn_oid ? "function " : "block ", func->fn_signature, fn_oid); else elog(plpgsql_check_tracer_errlevel, "#%-*d start of %s (oid=%u)", frame_width, frame_num, func->fn_oid ? get_func_name(func->fn_oid) : "inline code block", fn_oid); if (plpgsql_check_tracer_verbosity >= PGERROR_DEFAULT) { if (outer_estate) { if (outer_estate->err_stmt) elog(plpgsql_check_tracer_errlevel, "#%-*d%*s call by %s line %d at %s", frame_width, frame_num, indent + 4, "", outer_estate->func->fn_signature, outer_estate->err_stmt->lineno, plpgsql_check__stmt_typename_p(outer_estate->err_stmt)); else elog(plpgsql_check_tracer_errlevel, "#%-*d%*s call by %s", frame_width, frame_num, indent + 4, " ", outer_estate->func->fn_signature); } print_func_args(estate, func, frame_num, level); } } } void plpgsql_check_tracer_on_func_end(PLpgSQL_execstate *estate, PLpgSQL_function *func) { int level; int frame_num; instr_time start_time; PLpgSQL_execstate *outer_estate; Assert(plpgsql_check_tracer); /* Allow tracing only when it is explicitly allowed */ if (!plpgsql_check_enable_tracer) return; if (plpgsql_check_get_trace_info(estate, NULL, &outer_estate, &frame_num, &level, &start_time)) { instr_time end_time; uint64 elapsed; int indent = level * 2 + (plpgsql_check_tracer_verbosity == PGERROR_VERBOSE ? 6 : 0); int frame_width = plpgsql_check_tracer_verbosity == PGERROR_VERBOSE ? 6 : 3; INSTR_TIME_SET_CURRENT(end_time); INSTR_TIME_SUBTRACT(end_time, 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 (func->fn_oid) elog(plpgsql_check_tracer_errlevel, "#%-*d%*s <<- end of function %s (elapsed time=%.3f ms)", frame_width, frame_num, indent, "", get_func_name(func->fn_oid), elapsed / 1000.0); else elog(plpgsql_check_tracer_errlevel, "#%-*d%*s <<- end of block (elapsed time=%.3f ms)", frame_width, frame_num, indent, "", elapsed / 1000.0); } else elog(plpgsql_check_tracer_errlevel, "#%-3d end of %s", frame_num, func->fn_oid ? get_func_name(func->fn_oid) : "inline code block"); } } 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; } void plpgsql_check_tracer_on_stmt_beg(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt) { Assert(plpgsql_check_tracer); /* don't trace invisible statements */ if (stmt->cmd_type == PLPGSQL_STMT_BLOCK || stmt->lineno < 1) return; if (plpgsql_check_tracer_verbosity == PGERROR_VERBOSE) { PLpgSQL_execstate *outer_estate; int frame_num; int level; instr_time start_time; if (plpgsql_check_get_trace_info(estate, stmt, &outer_estate, &frame_num, &level, &start_time)) { int indent = 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; #if PG_VERSION_NUM >= 110000 case PLPGSQL_STMT_CALL: expr = ((PLpgSQL_stmt_call *) stmt)->expr; exprname = "expr"; break; #endif 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: ; } #if PG_VERSION_NUM >= 120000 instr_time *stmt_start_time; plpgsql_check_get_trace_stmt_info(estate, stmt->stmtid - 1, &stmt_start_time); if (stmt_start_time) INSTR_TIME_SET_CURRENT(*stmt_start_time); snprintf(printbuf, 20, "%d.%d", frame_num, stmt->stmtid); #else snprintf(printbuf, 20, "%d", frame_num); #endif 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", frame_width, printbuf, stmt->lineno, indent, "", copy_string_part(exprbuf, expr->query + startpos, 30)); } else if (is_perform) { elog(plpgsql_check_tracer_errlevel, "#%-*s %4d %*s --> start of perform %s", frame_width, printbuf, stmt->lineno, indent, "", copy_string_part(exprbuf, expr->query + startpos, 30)); } else { elog(plpgsql_check_tracer_errlevel, "#%-*s %4d %*s --> start of %s (%s='%s')", frame_width, printbuf, stmt->lineno, indent, "", plpgsql_check__stmt_typename_p(stmt), exprname, copy_string_part(exprbuf, expr->query + startpos, 30)); } } else elog(plpgsql_check_tracer_errlevel, "#%-*s %4d %*s --> start of %s", frame_width, printbuf, stmt->lineno, indent, "", plpgsql_check__stmt_typename_p(stmt)); if (expr) print_expr_args(estate, expr, printbuf, level); if (retvarno >= 0) print_datum(estate, estate->datums[retvarno], printbuf, 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, level); } break; } default: ; } } } } void plpgsql_check_tracer_on_stmt_end(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt) { Assert(plpgsql_check_tracer); /* don't trace invisible statements */ if (stmt->cmd_type == PLPGSQL_STMT_BLOCK || stmt->lineno < 1) return; if (plpgsql_check_tracer_verbosity == PGERROR_VERBOSE) { PLpgSQL_execstate *outer_estate; int frame_num; int level; instr_time start_time; if (plpgsql_check_get_trace_info(estate, stmt, &outer_estate, &frame_num, &level, &start_time)) { int indent = level * 2; int frame_width = 6; char printbuf[20]; uint64 elapsed = 0; #if PG_VERSION_NUM >= 120000 instr_time *stmt_start_time; plpgsql_check_get_trace_stmt_info(estate, stmt->stmtid - 1, &stmt_start_time); if (stmt_start_time) { instr_time end_time; INSTR_TIME_SET_CURRENT(end_time); INSTR_TIME_SUBTRACT(end_time, *stmt_start_time); elapsed = INSTR_TIME_GET_MICROSEC(end_time); if (plpgsql_check_tracer_test_mode) elapsed = 10; } snprintf(printbuf, 20, "%d.%d", frame_num, stmt->stmtid); #else snprintf(printbuf, 20, "%d", frame_num); #endif elog(plpgsql_check_tracer_errlevel, "#%-*s %*s <-- end of %s (elapsed time=%.3f ms)", frame_width, printbuf, indent, "", plpgsql_check__stmt_typename_p(stmt), elapsed/1000.0); if (stmt->cmd_type == PLPGSQL_STMT_ASSIGN) print_datum(estate, estate->datums[((PLpgSQL_stmt_assign *) stmt)->varno], printbuf, level); } } } void plpgsql_check_trace_assert_on_stmt_beg(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt) { PLpgSQL_var result; PLpgSQL_type typ; char exprbuf[200]; PLpgSQL_stmt_assert *stmt_assert = (PLpgSQL_stmt_assert *) stmt; /* Allow tracing only when it is explicitly allowed */ if (!plpgsql_check_enable_tracer) return; 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'; (*plpgsql_check_plugin_var_ptr)->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 = 0; for (econtext = error_context_stack->previous; econtext != NULL; econtext = econtext->previous) frame_num += 1; 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); } } } } } plpgsql_check-2.1.2/src/typdesc.c000066400000000000000000000371601417472474500167670ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * typdesc.c * * deduction result tupdesc from expression * * by Pavel Stehule 2013-2021 * *------------------------------------------------------------------------- */ #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" #if PG_VERSION_NUM >= 120000 #include "optimizer/optimizer.h" #endif #include "utils/builtins.h" #include "utils/lsyscache.h" #include "utils/syscache.h" #include "utils/typcache.h" #if PG_VERSION_NUM >= 110000 /* * 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; HeapTuple tuple; List *funcargs; Oid *argtypes; char **argnames; char *argmodes; ListCell *lc; int i; int nfields = 0; 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"); funcexpr = castNode(CallStmt, node)->funcexpr; /* * Get the argument modes */ tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcexpr->funcid)); if (!HeapTupleIsValid(tuple)) elog(ERROR, "cache lookup failed for function %u", funcexpr->funcid); /* Extract function arguments, and expand any named-arg notation */ funcargs = expand_function_arguments(funcexpr->args, #if PG_VERSION_NUM >= 140000 true, #endif funcexpr->funcresulttype, tuple); get_func_arg_info(tuple, &argtypes, &argnames, &argmodes); ReleaseSysCache(tuple); row = palloc0(sizeof(PLpgSQL_row)); row->dtype = PLPGSQL_DTYPE_ROW; row->dno = -1; row->refname = NULL; row->lineno = 0; row->varnos = palloc(sizeof(int) * list_length(funcargs)); /* * 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; } 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++; } 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; } #endif /* * 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 PG_VERSION_NUM >= 110000 if (rec->rectypeid != RECORDOID) { if (typoid != NULL) *typoid = rec->rectypeid; if (typmod != NULL) *typmod = -1; } else #endif 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); 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; TupleDesc elemtupdesc; /* 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; #if PG_VERSION_NUM >= 120000 rettupdesc = CreateTemplateTupleDesc(1); #else rettupdesc = CreateTemplateTupleDesc(1, false); #endif TupleDescInitEntry(rettupdesc, 1, "__array_element__", elemtype, -1, 0); FreeTupleDesc(tupdesc); BlessTupleDesc(rettupdesc); tupdesc = rettupdesc; } else { 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; #if PG_VERSION_NUM >= 120000 LOCAL_FCINFO(fcinfo, 0); #else FunctionCallInfoData fcinfo_data; FunctionCallInfo fcinfo = &fcinfo_data; #endif 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; #if PG_VERSION_NUM >= 120000 rettupdesc = CreateTemplateTupleDesc(list_length(row->args)); #else rettupdesc = CreateTemplateTupleDesc(list_length(row->args), false); #endif 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; }