├── .editorconfig ├── .github └── workflows │ ├── main.yml │ └── release.yml ├── .gitignore ├── LICENSE ├── META.json ├── Makefile ├── README.md ├── README.meson_msvc ├── TODO.md ├── _config.yml ├── examples ├── custom_scan_function.md └── custom_scan_function.sql ├── expected ├── README ├── plpgsql_check_active-14.out ├── plpgsql_check_active-15.out ├── plpgsql_check_active-16.out ├── plpgsql_check_active-17.out ├── plpgsql_check_active-18.out ├── plpgsql_check_active.out ├── plpgsql_check_active_2.out ├── plpgsql_check_active_3.out ├── plpgsql_check_passive-14.out ├── plpgsql_check_passive-15.out ├── plpgsql_check_passive-16.out ├── plpgsql_check_passive-17.out ├── plpgsql_check_passive-18.out ├── plpgsql_check_passive.out └── plpgsql_check_passive_2.out ├── install_bindist.py ├── meson.build ├── msvc ├── plpgsql_check.sln └── plpgsql_check │ ├── plpgsql_check.vcxproj │ ├── plpgsql_check.vcxproj.filters │ └── plpgsql_check.vcxproj.user ├── plpgsql_check--2.8.sql ├── plpgsql_check.control ├── postgresql14-plpgsql_check.spec ├── postgresql15-plpgsql_check.spec ├── postgresql16-plpgsql_check.spec ├── postgresql17-plpgsql_check.spec ├── postgresql18-plpgsql_check.spec ├── sql ├── plpgsql_check_active-14.sql ├── plpgsql_check_active-15.sql ├── plpgsql_check_active-16.sql ├── plpgsql_check_active-17.sql ├── plpgsql_check_active-18.sql ├── plpgsql_check_active.sql ├── plpgsql_check_passive-14.sql ├── plpgsql_check_passive-15.sql ├── plpgsql_check_passive-16.sql ├── plpgsql_check_passive-17.sql ├── plpgsql_check_passive-18.sql └── plpgsql_check_passive.sql └── src ├── assign.c ├── catalog.c ├── check_expr.c ├── check_function.c ├── cursors_leaks.c ├── expr_walk.c ├── format.c ├── parser.c ├── pldbgapi2.c ├── plpgsql_check.c ├── plpgsql_check.h ├── plpgsql_check_builtins.h ├── pragma.c ├── profiler.c ├── report.c ├── stmtwalk.c ├── tablefunc.c ├── tracer.c └── typdesc.c /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.{c,h,l,y,pl,pm}] 4 | indent_style = tab 5 | indent_size = tab 6 | tab_width = 4 7 | 8 | [*.{sgml,xml}] 9 | indent_style = space 10 | indent_size = 1 11 | 12 | [*.xsl] 13 | indent_style = space 14 | indent_size = 2 15 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | pull_request: 9 | 10 | jobs: 11 | test: 12 | strategy: 13 | matrix: 14 | pg: [17, 16, 15, 14] 15 | name: 🐘 PostgreSQL ${{ matrix.pg }} 16 | runs-on: ubuntu-latest 17 | container: pgxn/pgxn-tools 18 | steps: 19 | - run: pg-start ${{ matrix.pg }} 20 | - uses: actions/checkout@v2 21 | - run: pg-build-test 22 | 23 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | tags: [v*] 5 | jobs: 6 | release: 7 | name: Release on PGXN 8 | runs-on: ubuntu-latest 9 | container: pgxn/pgxn-tools 10 | env: 11 | PGXN_USERNAME: ${{ secrets.PGXN_USERNAME }} 12 | PGXN_PASSWORD: ${{ secrets.PGXN_PASSWORD }} 13 | steps: 14 | - name: Check out the repo 15 | uses: actions/checkout@v4 16 | - name: Bundle the Release 17 | id: bundle 18 | run: pgxn-bundle 19 | - name: Release on PGXN 20 | run: pgxn-release 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Global excludes across all subdirectories 2 | *.o 3 | *.so 4 | *.so.[0-9] 5 | *.so.[0-9].[0-9] 6 | *.sl 7 | *.sl.[0-9] 8 | *.sl.[0-9].[0-9] 9 | *.dylib 10 | *.dll 11 | *.a 12 | *.mo 13 | *.bc 14 | objfiles.txt 15 | .deps/ 16 | *.gcno 17 | *.gcda 18 | *.gcov 19 | *.gcov.out 20 | lcov.info 21 | *.vcproj 22 | *.vcxproj 23 | win32ver.rc 24 | *.exe 25 | lib*dll.def 26 | lib*.pc 27 | /results 28 | regression.diffs 29 | regression.out 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | # Licence 2 | 3 | Copyright (c) Pavel Stehule (pavel.stehule@gmail.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | # Note 24 | 25 | If you like it, send a postcard to address 26 | 27 | Pavel Stehule 28 | Skalice 12 29 | 256 01 Benesov u Prahy 30 | Czech Republic 31 | 32 | 33 | I invite any questions, comments, bug reports, patches on mail address pavel.stehule@gmail.com 34 | -------------------------------------------------------------------------------- /META.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "plpgsql_check", 3 | "abstract": "Additional tools for plpgsql functions validation", 4 | "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", 5 | "version": "2.8.1", 6 | "maintainer": "Pavel STEHULE ", 7 | "license": "bsd", 8 | "provides": { 9 | "plpgsql_check": { 10 | "abstract": "Additional tools for plpgsql functions validation", 11 | "file": "sql/plpgsql_check_active.sql", 12 | "docfile": "README.md", 13 | "version": "2.8.1" 14 | } 15 | }, 16 | "prereqs": { 17 | "runtime": { 18 | "requires": { 19 | "PostgreSQL": "14.0.0", 20 | "plpgsql": 0 21 | } 22 | } 23 | }, 24 | "resources": { 25 | "bugtracker": { 26 | "web": "https://github.com/okbob/plpgsql_check/issues/" 27 | }, 28 | "repository": { 29 | "url": "git://github.com/okbob/plpgsql_check.git", 30 | "web": "https://github.com/okbob/plpgsql_check/", 31 | "type": "git" 32 | } 33 | }, 34 | "generated_by": "David E. Wheeler", 35 | "meta-spec": { 36 | "version": "1.0.0", 37 | "url": "http://pgxn.org/meta/spec.txt" 38 | }, 39 | "tags": [ 40 | "plpgsql", 41 | "analyzer", 42 | "validate", 43 | "development", 44 | "profiler" 45 | ] 46 | } 47 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # $PostgreSQL: pgsql/contrib/plpgsql_check/Makefile 2 | 3 | MODULE_big = plpgsql_check 4 | OBJS = $(patsubst %.c,%.o,$(wildcard src/*.c)) 5 | DATA = plpgsql_check--2.8.sql 6 | EXTENSION = plpgsql_check 7 | 8 | ifndef MAJORVERSION 9 | MAJORVERSION := $(basename $(VERSION)) 10 | endif 11 | 12 | REGRESS_OPTS = --dbname=$(PL_TESTDB) 13 | REGRESS = plpgsql_check_passive plpgsql_check_active plpgsql_check_active-$(MAJORVERSION) plpgsql_check_passive-$(MAJORVERSION) 14 | 15 | ifdef NO_PGXS 16 | subdir = contrib/plpgsql_check 17 | top_builddir = ../.. 18 | include $(top_builddir)/src/Makefile.global 19 | include $(top_srcdir)/contrib/contrib-global.mk 20 | else 21 | PG_CONFIG ?= pg_config 22 | PGXS := $(shell $(PG_CONFIG) --pgxs) 23 | include $(PGXS) 24 | endif 25 | 26 | # temorary fix of compilation with gcc 15 27 | override CFLAGS += -Wno-error=incompatible-pointer-types -I$(top_builddir)/src/pl/plpgsql/src -Wall -g 28 | -------------------------------------------------------------------------------- /README.meson_msvc: -------------------------------------------------------------------------------- 1 | Build on Microsoft Windows with Microsoft Visual Studio and meson 2 | 3 | 1. Install 4 | - Microsft Visual Studio with C and C++ support 5 | - Meson 6 | - PostgreSQL 7 | 8 | 2. Run x64 Native Tools Command Prompt for VS 20xx (*** ensure x64 ***) 9 | 10 | 3. Set PATH 11 | 12 | set PATH=%PATH%;C:\Program Files\PostgreSQL\16\bin;C:\Program Files\Meson 13 | 14 | 4. execute in plpgsql_check source directory (buildtype should be release or plain, 15 | attention - the release buildtype inject dependency on vc_redist.x64.exe) 16 | 17 | meson setup --wipe --buildtype release plain 18 | cd build 19 | ninja 20 | sudo ninja install (on linux) 21 | ninja test (on linux) 22 | # ninja bindist 23 | 24 | 5. tests on MSWIN requires installation DiffUtils from gnuwin32 25 | 26 | set PATH=%PATH%;C:\Program Files (x86)\GnuWin32\bin 27 | set PGPORT= 28 | set PGUSER= 29 | set PGPASSWORD= 30 | ninja test 31 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | - possibility to specify locality of storage for profiling (local, shared) 2 | - possibility to export profiles in format for pprof https://github.com/google/pprof 3 | - possibility to export call stack (short [parent, current], full [entry point, current]) 4 | - possibility to show critical path (function or statement level) 5 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /examples/custom_scan_function.md: -------------------------------------------------------------------------------- 1 | custom_scan_function 2 | ==================== 3 | 4 | This creates a custom function, as an example, that changes the output to better fit my needs inside of my IDE (DataGrip) 5 | 6 | # Usage 7 | Through a macro that parses the current function, I call this with ONE routine: 8 | * `select * from plpgsql_check_custom('plpgsql_check_custom'::regproc);` 9 | 10 | 11 | Without that parameter, it runs for all routines! There are more parameters to easily control the intensity, or run for a set of schemas or filter messages! 12 | This is a starting point... Make it your own. 13 | 14 | # Why the timestamp 15 | FWIW, I return the timestamp ts with the message because I have 10+ windows open, each with it's own code+output. And after fixing a bunch of stuff in another window, that timestamp ALSO tells me how "dated" the scan is. It also confirms it refreshed (sometimes it runs so fast, and gives you the same output, you are not sure if it actually refreshed!). The great part is that once you have a RECORD() type setup for output, adding more columns is easy. 16 | 17 | # Why the procedure name a row and not a column 18 | Honestly, we have horribly long names: long_schema_name.Really_Long_package_name.Really_long_function_name()! 19 | While they are clear and make coding easier, it is quickly a waste of screen real-estate. I would rather have *one* long column in my output. 20 | It's a personal preference. And that is the beauty of PG and of this tool. 21 | 22 | # Motivation 23 | Finally, for output, the custom message that made me do this is given as an example below. The message from the base level was NOT enough. 24 | It only tells you what the code is trying to do. It does not make it clear WHICH parameter is likely wrong. So through a bit of introspection (Thanks to Pavel), 25 | I was able to add the full parameter details (including DEF it that parameter has a DEF value). As well as the expected TYPE... 26 | 27 | # Output 28 |
29 | `
30 | #### 
31 | #### Error in: schema.pkg_name$update_user() at Line: 16       PARAMETER TYPING ISSUE?
32 | #### 
33 | #### Param Name           Flow/DEF  (your code)    Definition     
34 | #### ==========           ========  ===========    ==========     
35 | #### eid                  IN        bigint         bigint         
36 | #### pid                  IN        bigint         bigint         
37 | #### typ                  IN        character varyibigint         
38 | #### val                  IN        bigint         text           
39 | #### addrmv               IN        bigint         integer        
40 | #### 
41 | `
42 | 43 | # Future Ideas 44 | Now that this actually exists, I have a few more ideas. I will actually see how to integrate this better with my DataGrip. 45 | I would love to make this work in psql, as a setting: 46 | `\set PLPGSQL_CHECK_ALTERED_ROUTINES ON` 47 | 48 | So, whenever I successfully compile (create or replace) a routine... Then this would run and output the issues it finds! 49 | 50 | There are a couple of additional things I would like to do. Some error messages give a line number, but it does not match up. 51 | I would love to do some code introspection, and extract the LINE in question (or better yet, the field in the case of FETCH INTO ... mismatch) 52 | -------------------------------------------------------------------------------- /examples/custom_scan_function.sql: -------------------------------------------------------------------------------- 1 | CREATE FUNCTION plpgsql_check_custom(funcoid oid DEFAULT NULL::oid, warns boolean DEFAULT false, stop_fatal boolean DEFAULT true, msg_like text DEFAULT '%'::text, proname_regex text DEFAULT '^(.*)$'::text, schema_regex text DEFAULT '^(.*)$'::text) 2 | RETURNS TABLE(ts character, check_msg text) 3 | LANGUAGE plpgsql 4 | AS 5 | $$ 6 | DECLARE 7 | -- This cursor drives the process (plpgsql_check_function()) does all the work! 8 | -- The filters are simple enough to filter down the messages, or the procedure name, and to control the INTENSITY of the LINTING 9 | -- You get the source... Make it your own! I wanted something I could use flexibly 10 | msgs CURSOR (func oid, warnings boolean , fatals boolean) FOR SELECT * 11 | FROM (SELECT p.oid, 12 | p.prokind, 13 | n.nspname || '.' || p.proname || '()' AS proname, 14 | public.plpgsql_check_function( 15 | funcoid => p.oid::regproc 16 | , fatal_errors := fatals 17 | , extra_warnings := warnings 18 | , performance_warnings := warnings /* set these 3 to false for initial pass */ 19 | , all_warnings := warnings)::text AS err 20 | FROM pg_catalog.pg_namespace n 21 | JOIN pg_catalog.pg_proc p ON pronamespace = n.oid 22 | JOIN pg_catalog.pg_language l ON p.prolang = l.oid 23 | WHERE l.lanname = 'plpgsql' 24 | AND p.prorettype <> 2279 /* not a trigger */ 25 | AND n.nspname <> 'public' 26 | AND p.prokind IN ('p', 'f') -- Only function and procedures 27 | AND p.oid = COALESCE(func, p.oid) 28 | AND p.proname OPERATOR (pg_catalog.~) proname_regex 29 | AND n.nspname OPERATOR (pg_catalog.~) schema_regex) q1 30 | WHERE q1.err LIKE msg_like; 31 | thisproc text := ''; -- Used so we only waste ONE line outputting what function we are working on, as opposed to a COLUMN 32 | errmsg text; -- The error message: "error:42883:42:assignment:function schem.function(integer, unknown, unknown, unknown, unknown, unknown, unknown) does not exist" 33 | a_txt text[]; -- Used to pars errmsg 34 | fdecl text; -- Declaration after parsing 35 | fname text; -- Before the parens 36 | foid oid; -- Function OID to lookup the named parameters 37 | parm1 text; -- between the parens 38 | pos INT; -- Simple position of ( for parsing 39 | a_p1 text[]; -- Array of Params from the users code 40 | has_in boolean; -- is IN/OUT present in any parameters 41 | names text; -- Function Signature with Parameter Names 42 | a_name text[]; -- string_to_aarray( names, ', ' ) -- [IN/OUT/INOUT] FLDNAME type [DEFAULT ...] 43 | a_pname text[]; -- Name ONLY of the field name 44 | n_off INT; -- Offset into the array for stuff 45 | str_out text; -- Messages to send out, with Chr(10) separating them! 46 | flow_def text; -- Should we default to IN all the time for flow 47 | flow text; -- IN/INOUT/OUT + DEF 48 | BEGIN 49 | ts := TO_CHAR(NOW(), 'HH24:MI:SS'); -- this is constant (Maybe a waste of the column, but forces a TABLE() return in case you want to add more columns, etc! 50 | FOR msg IN msgs(funcoid, warns, stop_fatal) 51 | LOOP 52 | str_out := ''; -- Start Fresh, and add strings as we go, for one final RETURN NEXT! 53 | IF thisproc <> msg.proname THEN -- Return a header! 54 | IF thisproc <> '' THEN 55 | check_msg := ''; 56 | RETURN NEXT; -- Blank line between different functions! 57 | END IF; 58 | thisproc := msg.proname; 59 | check_msg := CONCAT('===========> PROCESSING: ', thisproc); -- While REDUNDANT on 42883 Errors, it separates ALL functions from each other! 60 | RETURN NEXT; 61 | END IF; 62 | check_msg := msg.err; 63 | RETURN NEXT; 64 | errmsg := msg.err; 65 | IF errmsg LIKE 'error:42883:%' THEN 66 | -- SELECT '{}','{}','{}','{}','{}','{}' INTO a_txt, a_p1, a_p2, a_name, a_pname, a_flow; -- Produces plpgsql_check() warnings! 67 | a_txt := '{}'; 68 | a_p1 := '{}'; 69 | a_name := '{}'; 70 | a_pname := '{}'; 71 | 72 | str_out := '#### '; 73 | -- RETURN NEXT; 74 | IF RIGHT(errmsg, 14) = 'does not exist' THEN errmsg := LEFT(errmsg, -15); END IF; 75 | a_txt := STRING_TO_ARRAY(errmsg, ':'); 76 | IF CARDINALITY(a_txt) <> 5 THEN 77 | check_msg := str_out || chr(10) || '######## ==> details unavailable, parsing error <=== #########'::TEXT; 78 | RETURN NEXT; 79 | CONTINUE; 80 | END IF; 81 | fdecl := a_txt[5]; 82 | pos := POSITION('(' IN fdecl); 83 | IF pos = 0 THEN 84 | check_msg := str_out || chr(10) || '######## ==> details unavailable, parsing error(2) <=== #########'::TEXT; 85 | RETURN NEXT; 86 | CONTINUE; 87 | END IF; 88 | fname := LEFT(fdecl, pos - 1); -- exclude the paren 89 | fname := SUBSTR(fname, POSITION(' ' IN fname) + 1); 90 | parm1 := TRIM(SUBSTR(fdecl, pos, POSITION(')' IN fdecl) - pos + 1)); 91 | -- RETURN NEXT (ts , concat('#### ', fdecl )); -- Really Just Debug! 92 | BEGIN 93 | foid := TO_REGPROC(fname)::oid; -- This function will not throw an exception, just returns NULL 94 | -- REPLACES the error block 95 | IF foid IS NULL THEN 96 | check_msg := '#### Either No Such function or No Paramters!'; 97 | RETURN NEXT; 98 | CONTINUE; 99 | END IF; 100 | 101 | str_out := str_out || chr(10) || CONCAT('#### ', 'Error in: ', thisproc, ' at Line: ', a_txt[3], ' PARAMETER TYPING ISSUE?') || chr(10) || '#### '; 102 | a_p1 := STRING_TO_ARRAY(SUBSTRING(parm1, 2, LENGTH(parm1) - 2), ', '); -- These are just the types 103 | 104 | SELECT (POSITION('IN ' IN args) + POSITION('OUT ' IN args) )> 0 as tagged, args into has_in, names FROM 105 | (SELECT pg_catalog.PG_GET_FUNCTION_ARGUMENTS(foid) as args) t; 106 | 107 | a_name := STRING_TO_ARRAY(names, ', '); -- Separate these out! has_in is set for us 108 | 109 | /* We have an array of [INOUT] varname type [DEFAULT xxx] | And an array of the users param types param1 We will OUTPUT: 110 | Parameter Name [35], INOUT+DEF[10], P1_TYPE[15], OUR_TYPE \n */ 111 | str_out := CONCAT(str_out, chr(10), '#### ', rpad('Param Name',20), ' ', rpad('Flow/DEF',10), rpad('(your code)',15), rpad('Definition',15) ); 112 | str_out := CONCAT(str_out, chr(10), '#### ', rpad('==========',20), ' ', rpad('========',10), rpad('===========',15), rpad('==========',15) ); 113 | IF has_in THEN 114 | n_off := 1; 115 | flow_def := NULL; 116 | ELSE 117 | n_off := 0; 118 | flow_def := 'IN '; -- We have to force the display of IN, just for consistency. 119 | END IF; 120 | FOR x IN 1 .. CARDINALITY(a_name) 121 | LOOP 122 | a_pname := STRING_TO_ARRAY(a_name[x], ' '); -- Parse into an array 123 | -- RAISE NOTICE 'a_pname 1 %, 2 %, 3 %', a_pname[1], a_pname[2], a_pname[3]; 124 | flow := COALESCE(flow_def, a_pname[1]) || CASE WHEN POSITION('DEFAULT' IN a_name[x])=0 THEN '' ELSE ' DEF' END; 125 | str_out := CONCAT(str_out, chr(10), '#### ', rpad(a_pname[1+n_off],20), ' ',rpad(flow,10), rpad(coalesce(a_p1[x],'???'),15), rpad(a_pname[2+n_off],15) ); 126 | END LOOP; 127 | EXCEPTION 128 | WHEN OTHERS THEN 129 | str_out := str_out || chr(10) || CONCAT('==== ERROR: ', SQLERRM, ' Unexpected Exception!'); 130 | END; 131 | str_out := str_out || chr(10) || '#### '; 132 | ELSE 133 | CONTINUE; -- Nothing to do, not our message 134 | END IF; 135 | check_msg := str_out; 136 | RETURN NEXT; 137 | END LOOP; 138 | IF thisproc='' AND funcoid is not null THEN 139 | check_msg := 'No Messages Returned for: ' || funcoid::regproc; 140 | RETURN NEXT; 141 | END IF; 142 | RETURN; 143 | END 144 | $$; 145 | -------------------------------------------------------------------------------- /expected/README: -------------------------------------------------------------------------------- 1 | plpgsql_check_passive.out PostgreSQL 14, 15, 16 2 | plpgsql_check_passive_2.out PostgreSQL 18 3 | plpgsql_check_active_2.out PostgreSQL 14, 15, 16 4 | plpgsql_check_active_3.out PostgreSQL 18 5 | plpgsql_check_active.out PostgreSQL 17 6 | -------------------------------------------------------------------------------- /expected/plpgsql_check_active-14.out: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okbob/plpgsql_check/0206bebdaf8276c5748ba8f3b27aa75ef7a40eb1/expected/plpgsql_check_active-14.out -------------------------------------------------------------------------------- /expected/plpgsql_check_active-15.out: -------------------------------------------------------------------------------- 1 | LOAD 'plpgsql'; 2 | CREATE EXTENSION IF NOT EXISTS plpgsql_check; 3 | NOTICE: extension "plpgsql_check" already exists, skipping 4 | set client_min_messages to notice; 5 | create or replace function fxtest() 6 | returns void as $$ 7 | declare 8 | v_sqlstate text; 9 | v_message text; 10 | v_context text; 11 | begin 12 | get stacked diagnostics 13 | v_sqlstate = returned_sqlstate, 14 | v_message = message_text, 15 | v_context = pg_exception_context; 16 | end; 17 | $$ language plpgsql; 18 | select * from plpgsql_check_function('fxtest'); 19 | plpgsql_check_function 20 | ----------------------------------------------------------------------------------------------------------- 21 | error:0Z002:7:GET STACKED DIAGNOSTICS:GET STACKED DIAGNOSTICS cannot be used outside an exception handler 22 | (1 row) 23 | 24 | drop function fxtest(); 25 | create or replace procedure prtest() 26 | as $$ 27 | begin 28 | commit; 29 | end; 30 | $$ language plpgsql; 31 | select * from plpgsql_check_function('prtest'); --ok 32 | plpgsql_check_function 33 | ------------------------ 34 | (0 rows) 35 | 36 | create or replace procedure prtest() 37 | as $$ 38 | begin 39 | begin 40 | begin 41 | commit; 42 | end; 43 | end; 44 | exception when others then 45 | raise; 46 | end; 47 | $$ language plpgsql; 48 | select * from plpgsql_check_function('prtest'); --error 49 | plpgsql_check_function 50 | --------------------------------------------------------------------- 51 | error:2D000:5:COMMIT:cannot commit while a subtransaction is active 52 | (1 row) 53 | 54 | create or replace procedure prtest() 55 | as $$ 56 | begin 57 | raise exception 'error'; 58 | exception when others then 59 | begin 60 | begin 61 | commit; 62 | end; 63 | end; 64 | end; 65 | $$ language plpgsql; 66 | select * from plpgsql_check_function('prtest'); --ok 67 | plpgsql_check_function 68 | ------------------------ 69 | (0 rows) 70 | 71 | drop procedure prtest(); 72 | create function return_constant_refcursor() returns refcursor as $$ 73 | declare 74 | rc constant refcursor; 75 | begin 76 | open rc for select a from rc_test; 77 | return rc; 78 | end 79 | $$ language plpgsql; 80 | create table rc_test(a int); 81 | select * from plpgsql_check_function('return_constant_refcursor'); 82 | plpgsql_check_function 83 | ------------------------------------------------------- 84 | error:22005:5:OPEN:variable "rc" is declared CONSTANT 85 | (1 row) 86 | 87 | drop table rc_test; 88 | drop function return_constant_refcursor(); 89 | create procedure p1(a int, out b int) 90 | as $$ 91 | begin 92 | b := a + 10; 93 | end; 94 | $$ language plpgsql; 95 | create function f1() 96 | returns void as $$ 97 | declare b constant int; 98 | begin 99 | call p1(10, b); 100 | end; 101 | $$ language plpgsql; 102 | select * from plpgsql_check_function('f1'); 103 | plpgsql_check_function 104 | ------------------------------------------------------ 105 | error:22005:4:CALL:variable "b" is declared CONSTANT 106 | (1 row) 107 | 108 | drop function f1(); 109 | drop procedure p1(int, int); 110 | create or replace function f1() 111 | returns int as $$ 112 | declare c constant int default 100; 113 | begin 114 | return c; 115 | end; 116 | $$ language plpgsql; 117 | -- should be ok 118 | select * from plpgsql_check_function('f1'); 119 | plpgsql_check_function 120 | ------------------------ 121 | (0 rows) 122 | 123 | -------------------------------------------------------------------------------- /expected/plpgsql_check_active-16.out: -------------------------------------------------------------------------------- 1 | LOAD 'plpgsql'; 2 | CREATE EXTENSION IF NOT EXISTS plpgsql_check; 3 | NOTICE: extension "plpgsql_check" already exists, skipping 4 | set client_min_messages to notice; 5 | create or replace function fxtest() 6 | returns void as $$ 7 | declare 8 | v_sqlstate text; 9 | v_message text; 10 | v_context text; 11 | begin 12 | get stacked diagnostics 13 | v_sqlstate = returned_sqlstate, 14 | v_message = message_text, 15 | v_context = pg_exception_context; 16 | end; 17 | $$ language plpgsql; 18 | select * from plpgsql_check_function('fxtest'); 19 | plpgsql_check_function 20 | ----------------------------------------------------------------------------------------------------------- 21 | error:0Z002:7:GET STACKED DIAGNOSTICS:GET STACKED DIAGNOSTICS cannot be used outside an exception handler 22 | (1 row) 23 | 24 | drop function fxtest(); 25 | create or replace procedure prtest() 26 | as $$ 27 | begin 28 | commit; 29 | end; 30 | $$ language plpgsql; 31 | select * from plpgsql_check_function('prtest'); --ok 32 | plpgsql_check_function 33 | ------------------------ 34 | (0 rows) 35 | 36 | create or replace procedure prtest() 37 | as $$ 38 | begin 39 | begin 40 | begin 41 | commit; 42 | end; 43 | end; 44 | exception when others then 45 | raise; 46 | end; 47 | $$ language plpgsql; 48 | select * from plpgsql_check_function('prtest'); --error 49 | plpgsql_check_function 50 | --------------------------------------------------------------------- 51 | error:2D000:5:COMMIT:cannot commit while a subtransaction is active 52 | (1 row) 53 | 54 | create or replace procedure prtest() 55 | as $$ 56 | begin 57 | raise exception 'error'; 58 | exception when others then 59 | begin 60 | begin 61 | commit; 62 | end; 63 | end; 64 | end; 65 | $$ language plpgsql; 66 | select * from plpgsql_check_function('prtest'); --ok 67 | plpgsql_check_function 68 | ------------------------ 69 | (0 rows) 70 | 71 | drop procedure prtest(); 72 | create function return_constant_refcursor() returns refcursor as $$ 73 | declare 74 | rc constant refcursor; 75 | begin 76 | open rc for select a from rc_test; 77 | return rc; 78 | end 79 | $$ language plpgsql; 80 | create table rc_test(a int); 81 | select * from plpgsql_check_function('return_constant_refcursor'); 82 | plpgsql_check_function 83 | ------------------------------------------------------- 84 | error:22005:5:OPEN:variable "rc" is declared CONSTANT 85 | (1 row) 86 | 87 | drop table rc_test; 88 | drop function return_constant_refcursor(); 89 | create procedure p1(a int, out b int) 90 | as $$ 91 | begin 92 | b := a + 10; 93 | end; 94 | $$ language plpgsql; 95 | create function f1() 96 | returns void as $$ 97 | declare b constant int; 98 | begin 99 | call p1(10, b); 100 | end; 101 | $$ language plpgsql; 102 | select * from plpgsql_check_function('f1'); 103 | plpgsql_check_function 104 | ------------------------------------------------------ 105 | error:22005:4:CALL:variable "b" is declared CONSTANT 106 | (1 row) 107 | 108 | drop function f1(); 109 | drop procedure p1(int, int); 110 | create or replace function f1() 111 | returns int as $$ 112 | declare c constant int default 100; 113 | begin 114 | return c; 115 | end; 116 | $$ language plpgsql; 117 | -- should be ok 118 | select * from plpgsql_check_function('f1'); 119 | plpgsql_check_function 120 | ------------------------ 121 | (0 rows) 122 | 123 | drop function f1(); 124 | -- do not raise false warning 125 | create or replace function test_function() 126 | returns text as $$ 127 | declare s text; 128 | begin 129 | get diagnostics s = PG_CONTEXT; 130 | return s; 131 | end; 132 | $$ language plpgsql; 133 | create or replace procedure test_procedure() 134 | as $$ 135 | begin 136 | null; 137 | end; 138 | $$ language plpgsql; 139 | -- should be without any warnings 140 | select * from plpgsql_check_function('test_function', performance_warnings=>true); 141 | plpgsql_check_function 142 | ------------------------ 143 | (0 rows) 144 | 145 | select * from plpgsql_check_function('test_procedure', performance_warnings=>true); 146 | plpgsql_check_function 147 | ------------------------ 148 | (0 rows) 149 | 150 | drop function test_function(); 151 | drop procedure test_procedure(); 152 | -- detect dependecy in CALL statement 153 | create or replace function fx1_dep(int) 154 | returns int as $$ 155 | begin 156 | return $1; 157 | end; 158 | $$ language plpgsql; 159 | create or replace procedure px1_dep(int) 160 | as $$ 161 | begin 162 | end; 163 | $$ language plpgsql; 164 | create or replace function test_function() 165 | returns void as $$ 166 | begin 167 | call px1_dep(fx1_dep(10)); 168 | end; 169 | $$ language plpgsql; 170 | select type, schema, name, params from plpgsql_show_dependency_tb('test_function'); 171 | type | schema | name | params 172 | -----------+--------+---------+----------- 173 | FUNCTION | public | fx1_dep | (integer) 174 | PROCEDURE | public | px1_dep | (integer) 175 | (2 rows) 176 | 177 | drop function test_function(); 178 | drop procedure px1_dep(int); 179 | drop function fx1_dep(int); 180 | -------------------------------------------------------------------------------- /expected/plpgsql_check_active-17.out: -------------------------------------------------------------------------------- 1 | LOAD 'plpgsql'; 2 | CREATE EXTENSION IF NOT EXISTS plpgsql_check; 3 | NOTICE: extension "plpgsql_check" already exists, skipping 4 | set client_min_messages to notice; 5 | create or replace function fxtest() 6 | returns void as $$ 7 | declare 8 | v_sqlstate text; 9 | v_message text; 10 | v_context text; 11 | begin 12 | get stacked diagnostics 13 | v_sqlstate = returned_sqlstate, 14 | v_message = message_text, 15 | v_context = pg_exception_context; 16 | end; 17 | $$ language plpgsql; 18 | select * from plpgsql_check_function('fxtest'); 19 | plpgsql_check_function 20 | ----------------------------------------------------------------------------------------------------------- 21 | error:0Z002:7:GET STACKED DIAGNOSTICS:GET STACKED DIAGNOSTICS cannot be used outside an exception handler 22 | (1 row) 23 | 24 | drop function fxtest(); 25 | create or replace procedure prtest() 26 | as $$ 27 | begin 28 | commit; 29 | end; 30 | $$ language plpgsql; 31 | select * from plpgsql_check_function('prtest'); --ok 32 | plpgsql_check_function 33 | ------------------------ 34 | (0 rows) 35 | 36 | create or replace procedure prtest() 37 | as $$ 38 | begin 39 | begin 40 | begin 41 | commit; 42 | end; 43 | end; 44 | exception when others then 45 | raise; 46 | end; 47 | $$ language plpgsql; 48 | select * from plpgsql_check_function('prtest'); --error 49 | plpgsql_check_function 50 | --------------------------------------------------------------------- 51 | error:2D000:5:COMMIT:cannot commit while a subtransaction is active 52 | (1 row) 53 | 54 | create or replace procedure prtest() 55 | as $$ 56 | begin 57 | raise exception 'error'; 58 | exception when others then 59 | begin 60 | begin 61 | commit; 62 | end; 63 | end; 64 | end; 65 | $$ language plpgsql; 66 | select * from plpgsql_check_function('prtest'); --ok 67 | plpgsql_check_function 68 | ------------------------ 69 | (0 rows) 70 | 71 | drop procedure prtest(); 72 | create function return_constant_refcursor() returns refcursor as $$ 73 | declare 74 | rc constant refcursor; 75 | begin 76 | open rc for select a from rc_test; 77 | return rc; 78 | end 79 | $$ language plpgsql; 80 | create table rc_test(a int); 81 | select * from plpgsql_check_function('return_constant_refcursor'); 82 | plpgsql_check_function 83 | ------------------------------------------------------- 84 | error:22005:5:OPEN:variable "rc" is declared CONSTANT 85 | (1 row) 86 | 87 | drop table rc_test; 88 | drop function return_constant_refcursor(); 89 | create procedure p1(a int, out b int) 90 | as $$ 91 | begin 92 | b := a + 10; 93 | end; 94 | $$ language plpgsql; 95 | create function f1() 96 | returns void as $$ 97 | declare b constant int; 98 | begin 99 | call p1(10, b); 100 | end; 101 | $$ language plpgsql; 102 | select * from plpgsql_check_function('f1'); 103 | plpgsql_check_function 104 | ------------------------------------------------------ 105 | error:22005:4:CALL:variable "b" is declared CONSTANT 106 | (1 row) 107 | 108 | drop function f1(); 109 | drop procedure p1(int, int); 110 | create or replace function f1() 111 | returns int as $$ 112 | declare c constant int default 100; 113 | begin 114 | return c; 115 | end; 116 | $$ language plpgsql; 117 | -- should be ok 118 | select * from plpgsql_check_function('f1'); 119 | plpgsql_check_function 120 | ------------------------ 121 | (0 rows) 122 | 123 | drop function f1(); 124 | -- do not raise false warning 125 | create or replace function test_function() 126 | returns text as $$ 127 | declare s text; 128 | begin 129 | get diagnostics s = PG_CONTEXT; 130 | return s; 131 | end; 132 | $$ language plpgsql; 133 | create or replace procedure test_procedure() 134 | as $$ 135 | begin 136 | null; 137 | end; 138 | $$ language plpgsql; 139 | -- should be without any warnings 140 | select * from plpgsql_check_function('test_function', performance_warnings=>true); 141 | plpgsql_check_function 142 | ------------------------ 143 | (0 rows) 144 | 145 | select * from plpgsql_check_function('test_procedure', performance_warnings=>true); 146 | plpgsql_check_function 147 | ------------------------ 148 | (0 rows) 149 | 150 | drop function test_function(); 151 | drop procedure test_procedure(); 152 | -- detect dependecy in CALL statement 153 | create or replace function fx1_dep(int) 154 | returns int as $$ 155 | begin 156 | return $1; 157 | end; 158 | $$ language plpgsql; 159 | create or replace procedure px1_dep(int) 160 | as $$ 161 | begin 162 | end; 163 | $$ language plpgsql; 164 | create or replace function test_function() 165 | returns void as $$ 166 | begin 167 | call px1_dep(fx1_dep(10)); 168 | end; 169 | $$ language plpgsql; 170 | select type, schema, name, params from plpgsql_show_dependency_tb('test_function'); 171 | type | schema | name | params 172 | -----------+--------+---------+----------- 173 | FUNCTION | public | fx1_dep | (integer) 174 | PROCEDURE | public | px1_dep | (integer) 175 | (2 rows) 176 | 177 | drop function test_function(); 178 | drop procedure px1_dep(int); 179 | drop function fx1_dep(int); 180 | -------------------------------------------------------------------------------- /expected/plpgsql_check_active-18.out: -------------------------------------------------------------------------------- 1 | LOAD 'plpgsql'; 2 | CREATE EXTENSION IF NOT EXISTS plpgsql_check; 3 | NOTICE: extension "plpgsql_check" already exists, skipping 4 | set client_min_messages to notice; 5 | create or replace function fxtest() 6 | returns void as $$ 7 | declare 8 | v_sqlstate text; 9 | v_message text; 10 | v_context text; 11 | begin 12 | get stacked diagnostics 13 | v_sqlstate = returned_sqlstate, 14 | v_message = message_text, 15 | v_context = pg_exception_context; 16 | end; 17 | $$ language plpgsql; 18 | select * from plpgsql_check_function('fxtest'); 19 | plpgsql_check_function 20 | ----------------------------------------------------------------------------------------------------------- 21 | error:0Z002:7:GET STACKED DIAGNOSTICS:GET STACKED DIAGNOSTICS cannot be used outside an exception handler 22 | (1 row) 23 | 24 | drop function fxtest(); 25 | create or replace procedure prtest() 26 | as $$ 27 | begin 28 | commit; 29 | end; 30 | $$ language plpgsql; 31 | select * from plpgsql_check_function('prtest'); --ok 32 | plpgsql_check_function 33 | ------------------------ 34 | (0 rows) 35 | 36 | create or replace procedure prtest() 37 | as $$ 38 | begin 39 | begin 40 | begin 41 | commit; 42 | end; 43 | end; 44 | exception when others then 45 | raise; 46 | end; 47 | $$ language plpgsql; 48 | select * from plpgsql_check_function('prtest'); --error 49 | plpgsql_check_function 50 | --------------------------------------------------------------------- 51 | error:2D000:5:COMMIT:cannot commit while a subtransaction is active 52 | (1 row) 53 | 54 | create or replace procedure prtest() 55 | as $$ 56 | begin 57 | raise exception 'error'; 58 | exception when others then 59 | begin 60 | begin 61 | commit; 62 | end; 63 | end; 64 | end; 65 | $$ language plpgsql; 66 | select * from plpgsql_check_function('prtest'); --ok 67 | plpgsql_check_function 68 | ------------------------ 69 | (0 rows) 70 | 71 | drop procedure prtest(); 72 | create function return_constant_refcursor() returns refcursor as $$ 73 | declare 74 | rc constant refcursor; 75 | begin 76 | open rc for select a from rc_test; 77 | return rc; 78 | end 79 | $$ language plpgsql; 80 | create table rc_test(a int); 81 | select * from plpgsql_check_function('return_constant_refcursor'); 82 | plpgsql_check_function 83 | ------------------------------------------------------- 84 | error:22005:5:OPEN:variable "rc" is declared CONSTANT 85 | (1 row) 86 | 87 | drop table rc_test; 88 | drop function return_constant_refcursor(); 89 | create procedure p1(a int, out b int) 90 | as $$ 91 | begin 92 | b := a + 10; 93 | end; 94 | $$ language plpgsql; 95 | create function f1() 96 | returns void as $$ 97 | declare b constant int; 98 | begin 99 | call p1(10, b); 100 | end; 101 | $$ language plpgsql; 102 | select * from plpgsql_check_function('f1'); 103 | plpgsql_check_function 104 | ------------------------------------------------------ 105 | error:22005:4:CALL:variable "b" is declared CONSTANT 106 | (1 row) 107 | 108 | drop function f1(); 109 | drop procedure p1(int, int); 110 | create or replace function f1() 111 | returns int as $$ 112 | declare c constant int default 100; 113 | begin 114 | return c; 115 | end; 116 | $$ language plpgsql; 117 | -- should be ok 118 | select * from plpgsql_check_function('f1'); 119 | plpgsql_check_function 120 | ------------------------ 121 | (0 rows) 122 | 123 | drop function f1(); 124 | -- do not raise false warning 125 | create or replace function test_function() 126 | returns text as $$ 127 | declare s text; 128 | begin 129 | get diagnostics s = PG_CONTEXT; 130 | return s; 131 | end; 132 | $$ language plpgsql; 133 | create or replace procedure test_procedure() 134 | as $$ 135 | begin 136 | null; 137 | end; 138 | $$ language plpgsql; 139 | -- should be without any warnings 140 | select * from plpgsql_check_function('test_function', performance_warnings=>true); 141 | plpgsql_check_function 142 | ------------------------ 143 | (0 rows) 144 | 145 | select * from plpgsql_check_function('test_procedure', performance_warnings=>true); 146 | plpgsql_check_function 147 | ------------------------ 148 | (0 rows) 149 | 150 | drop function test_function(); 151 | drop procedure test_procedure(); 152 | -- detect dependecy in CALL statement 153 | create or replace function fx1_dep(int) 154 | returns int as $$ 155 | begin 156 | return $1; 157 | end; 158 | $$ language plpgsql; 159 | create or replace procedure px1_dep(int) 160 | as $$ 161 | begin 162 | end; 163 | $$ language plpgsql; 164 | create or replace function test_function() 165 | returns void as $$ 166 | begin 167 | call px1_dep(fx1_dep(10)); 168 | end; 169 | $$ language plpgsql; 170 | select type, schema, name, params from plpgsql_show_dependency_tb('test_function'); 171 | type | schema | name | params 172 | -----------+--------+---------+----------- 173 | FUNCTION | public | fx1_dep | (integer) 174 | PROCEDURE | public | px1_dep | (integer) 175 | (2 rows) 176 | 177 | drop function test_function(); 178 | drop procedure px1_dep(int); 179 | drop function fx1_dep(int); 180 | -------------------------------------------------------------------------------- /expected/plpgsql_check_passive-14.out: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okbob/plpgsql_check/0206bebdaf8276c5748ba8f3b27aa75ef7a40eb1/expected/plpgsql_check_passive-14.out -------------------------------------------------------------------------------- /expected/plpgsql_check_passive-15.out: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okbob/plpgsql_check/0206bebdaf8276c5748ba8f3b27aa75ef7a40eb1/expected/plpgsql_check_passive-15.out -------------------------------------------------------------------------------- /expected/plpgsql_check_passive-16.out: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okbob/plpgsql_check/0206bebdaf8276c5748ba8f3b27aa75ef7a40eb1/expected/plpgsql_check_passive-16.out -------------------------------------------------------------------------------- /expected/plpgsql_check_passive-17.out: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okbob/plpgsql_check/0206bebdaf8276c5748ba8f3b27aa75ef7a40eb1/expected/plpgsql_check_passive-17.out -------------------------------------------------------------------------------- /expected/plpgsql_check_passive-18.out: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okbob/plpgsql_check/0206bebdaf8276c5748ba8f3b27aa75ef7a40eb1/expected/plpgsql_check_passive-18.out -------------------------------------------------------------------------------- /expected/plpgsql_check_passive.out: -------------------------------------------------------------------------------- 1 | load 'plpgsql'; 2 | load 'plpgsql_check'; 3 | set client_min_messages to notice; 4 | -- enforce context's displaying 5 | -- emulate pre 9.6 behave 6 | \set SHOW_CONTEXT always 7 | set plpgsql_check.mode = 'every_start'; 8 | create table t1(a int, b int); 9 | create function f1() 10 | returns void as $$ 11 | begin 12 | if false then 13 | update t1 set c = 30; 14 | end if; 15 | end; 16 | $$ language plpgsql; 17 | select f1(); 18 | ERROR: column "c" of relation "t1" does not exist 19 | LINE 1: update t1 set c = 30 20 | ^ 21 | QUERY: update t1 set c = 30 22 | CONTEXT: PL/pgSQL function f1() line 4 at SQL statement 23 | drop function f1(); 24 | create function f1() 25 | returns void as $$ 26 | begin 27 | if false then 28 | insert into t1 values(10,20); 29 | update t1 set a = 10; 30 | delete from t1; 31 | end if; 32 | end; 33 | $$ language plpgsql stable; 34 | select f1(); 35 | ERROR: INSERT is not allowed in a non volatile function 36 | LINE 1: insert into t1 values(10,20) 37 | ^ 38 | QUERY: insert into t1 values(10,20) 39 | CONTEXT: PL/pgSQL function f1() line 4 at SQL statement 40 | drop function f1(); 41 | create function g1(out a int, out b int) 42 | as $$ 43 | select 10,20; 44 | $$ language sql; 45 | create function f1() 46 | returns void as $$ 47 | declare r record; 48 | begin 49 | r := g1(); 50 | if false then 51 | raise notice '%', r.c; 52 | end if; 53 | end; 54 | $$ language plpgsql; 55 | select f1(); 56 | ERROR: record "r" has no field "c" 57 | CONTEXT: SQL expression "r.c" 58 | PL/pgSQL function f1() line 6 at RAISE 59 | drop function f1(); 60 | drop function g1(); 61 | create function g1(out a int, out b int) 62 | returns setof record as $$ 63 | select * from t1; 64 | $$ language sql; 65 | create function f1() 66 | returns void as $$ 67 | declare r record; 68 | begin 69 | for r in select * from g1() 70 | loop 71 | raise notice '%', r.c; 72 | end loop; 73 | end; 74 | $$ language plpgsql; 75 | select f1(); 76 | ERROR: record "r" has no field "c" 77 | CONTEXT: SQL expression "r.c" 78 | PL/pgSQL function f1() line 6 at RAISE 79 | create or replace function f1() 80 | returns void as $$ 81 | declare r record; 82 | begin 83 | for r in select * from g1() 84 | loop 85 | r.c := 20; 86 | end loop; 87 | end; 88 | $$ language plpgsql; 89 | select f1(); 90 | ERROR: record "r" has no field "c" 91 | CONTEXT: PL/pgSQL function f1() line 6 at assignment to field "c" of variable "r" declared on line 2 92 | drop function f1(); 93 | drop function g1(); 94 | create function f1() 95 | returns int as $$ 96 | declare r int; 97 | begin 98 | if false then 99 | r := a + b; 100 | end if; 101 | return r; 102 | end; 103 | $$ language plpgsql; 104 | select f1(); 105 | ERROR: column "a" does not exist 106 | LINE 1: r := a + b 107 | ^ 108 | QUERY: r := a + b 109 | CONTEXT: PL/pgSQL function f1() line 5 at assignment to variable "r" declared on line 2 110 | drop function f1(); 111 | create or replace function f1() 112 | returns void as $$ 113 | declare r int[]; 114 | begin 115 | if false then 116 | r[c+10] := 20; 117 | end if; 118 | end; 119 | $$ language plpgsql; 120 | select f1(); 121 | ERROR: column "c" does not exist 122 | LINE 1: r[c+10] := 20 123 | ^ 124 | QUERY: r[c+10] := 20 125 | CONTEXT: PL/pgSQL function f1() line 5 at assignment to variable "r" declared on line 2 126 | drop function f1(); 127 | create or replace function f1() 128 | returns void as $$ 129 | declare r int; 130 | begin 131 | if false then 132 | r[10] := 20; 133 | end if; 134 | end; 135 | $$ language plpgsql; 136 | select f1(); 137 | ERROR: cannot subscript type integer because it does not support subscripting 138 | LINE 1: r[10] := 20 139 | ^ 140 | QUERY: r[10] := 20 141 | CONTEXT: PL/pgSQL function f1() line 5 at assignment to variable "r" declared on line 2 142 | drop function f1(); 143 | create or replace function f1() 144 | returns void as $$ 145 | begin 146 | if false then 147 | insert into badbadtable values(10,20); 148 | end if; 149 | return; 150 | end; 151 | $$ language plpgsql; 152 | set plpgsql_check.mode = 'fresh_start'; 153 | select f1(); 154 | ERROR: relation "badbadtable" does not exist 155 | LINE 1: insert into badbadtable values(10,20) 156 | ^ 157 | QUERY: insert into badbadtable values(10,20) 158 | CONTEXT: PL/pgSQL function f1() line 4 at SQL statement 159 | -- should not raise exception there 160 | select f1(); 161 | f1 162 | ---- 163 | 164 | (1 row) 165 | 166 | create or replace function f1() 167 | returns void as $$ 168 | begin 169 | if false then 170 | insert into badbadtable values(10,20); 171 | end if; 172 | return; 173 | end; 174 | $$ language plpgsql; 175 | -- after refreshing it should to raise exception again 176 | select f1(); 177 | ERROR: relation "badbadtable" does not exist 178 | LINE 1: insert into badbadtable values(10,20) 179 | ^ 180 | QUERY: insert into badbadtable values(10,20) 181 | CONTEXT: PL/pgSQL function f1() line 4 at SQL statement 182 | set plpgsql_check.mode = 'every_start'; 183 | -- should to raise warning only 184 | set plpgsql_check.fatal_errors = false; 185 | select f1(); 186 | WARNING: relation "badbadtable" does not exist 187 | LINE 1: insert into badbadtable values(10,20) 188 | ^ 189 | QUERY: insert into badbadtable values(10,20) 190 | CONTEXT: PL/pgSQL function f1() line 4 at SQL statement 191 | PL/pgSQL function f1() line 4 at SQL statement 192 | f1 193 | ---- 194 | 195 | (1 row) 196 | 197 | drop function f1(); 198 | create function f1() 199 | returns setof t1 as $$ 200 | begin 201 | if false then 202 | return query select a,a,a from t1; 203 | return; 204 | end if; 205 | end; 206 | $$ language plpgsql; 207 | select * from f1(); 208 | WARNING: structure of query does not match function result type 209 | DETAIL: Number of returned columns (3) does not match expected column count (2). 210 | CONTEXT: PL/pgSQL function f1() line 4 at RETURN QUERY 211 | PL/pgSQL function f1() line 4 at RETURN QUERY 212 | a | b 213 | ---+--- 214 | (0 rows) 215 | 216 | drop function f1(); 217 | create function f1() 218 | returns setof t1 as $$ 219 | begin 220 | if false then 221 | return query select a, b::numeric from t1; 222 | return; 223 | end if; 224 | end; 225 | $$ language plpgsql; 226 | select * from f1(); 227 | WARNING: structure of query does not match function result type 228 | DETAIL: Returned type numeric does not match expected type integer in column 2. 229 | CONTEXT: PL/pgSQL function f1() line 4 at RETURN QUERY 230 | PL/pgSQL function f1() line 4 at RETURN QUERY 231 | a | b 232 | ---+--- 233 | (0 rows) 234 | 235 | drop function f1(); 236 | drop table t1; 237 | do $$ 238 | declare 239 | begin 240 | if false then 241 | for i in 1,3..(2) loop 242 | raise notice 'foo %', i; 243 | end loop; 244 | end if; 245 | end; 246 | $$; 247 | WARNING: query "1,3" returned 2 columns 248 | CONTEXT: PL/pgSQL function inline_code_block line 5 at FOR with integer loop variable 249 | PL/pgSQL function inline_code_block line 5 at FOR with integer loop variable 250 | -- tests designed for 9.2 251 | set check_function_bodies to off; 252 | create or replace function f1() 253 | returns void as $$ 254 | begin 255 | if false then 256 | raise notice '%', 1, 2; 257 | end if; 258 | end; 259 | $$ language plpgsql; 260 | select f1(); 261 | ERROR: too many parameters specified for RAISE 262 | CONTEXT: compilation of PL/pgSQL function "f1" near line 4 263 | drop function f1(); 264 | create or replace function f1() 265 | returns void as $$ 266 | begin 267 | if false then 268 | raise notice '% %'; 269 | end if; 270 | end; 271 | $$ language plpgsql; 272 | select f1(); 273 | ERROR: too few parameters specified for RAISE 274 | CONTEXT: compilation of PL/pgSQL function "f1" near line 4 275 | drop function f1(); 276 | create type _exception_type as ( 277 | state text, 278 | message text, 279 | detail text); 280 | create or replace function f1() 281 | returns void as $$ 282 | declare 283 | _exception record; 284 | begin 285 | _exception := NULL::_exception_type; 286 | exception when others then 287 | get stacked diagnostics 288 | _exception.state = RETURNED_SQLSTATE, 289 | _exception.message = MESSAGE_TEXT, 290 | _exception.detail = PG_EXCEPTION_DETAIL, 291 | _exception.hint = PG_EXCEPTION_HINT; 292 | end; 293 | $$ language plpgsql; 294 | select f1(); 295 | WARNING: record "_exception" has no field "hint" 296 | CONTEXT: PL/pgSQL function f1() line 7 at GET STACKED DIAGNOSTICS 297 | PL/pgSQL function f1() line 7 at GET STACKED DIAGNOSTICS 298 | f1 299 | ---- 300 | 301 | (1 row) 302 | 303 | drop function f1(); 304 | drop type _exception_type; 305 | create table footab(a int, b int, c int); 306 | create or replace function footab_trig_func() 307 | returns trigger as $$ 308 | declare x int; 309 | begin 310 | if false then 311 | -- should be ok; 312 | select count(*) from newtab into x; 313 | 314 | -- should fail; 315 | select count(*) from newtab where d = 10 into x; 316 | end if; 317 | return null; 318 | end; 319 | $$ language plpgsql; 320 | create trigger footab_trigger 321 | after insert on footab 322 | referencing new table as newtab 323 | for each statement execute procedure footab_trig_func(); 324 | -- should to fail 325 | insert into footab values(1,2,3); 326 | WARNING: column "d" does not exist 327 | LINE 1: select count(*) from newtab where d = 10 328 | ^ 329 | QUERY: select count(*) from newtab where d = 10 330 | CONTEXT: PL/pgSQL function footab_trig_func() line 9 at SQL statement 331 | PL/pgSQL function footab_trig_func() line 9 at SQL statement 332 | create or replace function footab_trig_func() 333 | returns trigger as $$ 334 | declare x int; 335 | begin 336 | if false then 337 | -- should be ok; 338 | select count(*) from newtab into x; 339 | end if; 340 | return null; 341 | end; 342 | $$ language plpgsql; 343 | -- should be ok 344 | insert into footab values(1,2,3); 345 | drop table footab; 346 | drop function footab_trig_func(); 347 | set plpgsql_check.mode = 'every_start'; 348 | create or replace procedure proc_test() 349 | as $$ 350 | begin 351 | commit; 352 | end; 353 | $$ language plpgsql; 354 | call proc_test(); 355 | drop procedure proc_test(); 356 | -- should not to crash 357 | set plpgsql_check.mode = 'fresh_start'; 358 | do $$ 359 | begin 360 | end; 361 | $$; 362 | -------------------------------------------------------------------------------- /expected/plpgsql_check_passive_2.out: -------------------------------------------------------------------------------- 1 | load 'plpgsql'; 2 | load 'plpgsql_check'; 3 | set client_min_messages to notice; 4 | -- enforce context's displaying 5 | -- emulate pre 9.6 behave 6 | \set SHOW_CONTEXT always 7 | set plpgsql_check.mode = 'every_start'; 8 | create table t1(a int, b int); 9 | create function f1() 10 | returns void as $$ 11 | begin 12 | if false then 13 | update t1 set c = 30; 14 | end if; 15 | end; 16 | $$ language plpgsql; 17 | select f1(); 18 | ERROR: column "c" of relation "t1" does not exist 19 | LINE 1: update t1 set c = 30 20 | ^ 21 | QUERY: update t1 set c = 30 22 | CONTEXT: PL/pgSQL function f1() line 4 at SQL statement 23 | drop function f1(); 24 | create function f1() 25 | returns void as $$ 26 | begin 27 | if false then 28 | insert into t1 values(10,20); 29 | update t1 set a = 10; 30 | delete from t1; 31 | end if; 32 | end; 33 | $$ language plpgsql stable; 34 | select f1(); 35 | ERROR: INSERT is not allowed in a non volatile function 36 | LINE 1: insert into t1 values(10,20) 37 | ^ 38 | QUERY: insert into t1 values(10,20) 39 | CONTEXT: PL/pgSQL function f1() line 4 at SQL statement 40 | drop function f1(); 41 | create function g1(out a int, out b int) 42 | as $$ 43 | select 10,20; 44 | $$ language sql; 45 | create function f1() 46 | returns void as $$ 47 | declare r record; 48 | begin 49 | r := g1(); 50 | if false then 51 | raise notice '%', r.c; 52 | end if; 53 | end; 54 | $$ language plpgsql; 55 | select f1(); 56 | ERROR: record "r" has no field "c" 57 | CONTEXT: PL/pgSQL expression "r.c" 58 | PL/pgSQL function f1() line 6 at RAISE 59 | drop function f1(); 60 | drop function g1(); 61 | create function g1(out a int, out b int) 62 | returns setof record as $$ 63 | select * from t1; 64 | $$ language sql; 65 | create function f1() 66 | returns void as $$ 67 | declare r record; 68 | begin 69 | for r in select * from g1() 70 | loop 71 | raise notice '%', r.c; 72 | end loop; 73 | end; 74 | $$ language plpgsql; 75 | select f1(); 76 | ERROR: record "r" has no field "c" 77 | CONTEXT: PL/pgSQL expression "r.c" 78 | PL/pgSQL function f1() line 6 at RAISE 79 | create or replace function f1() 80 | returns void as $$ 81 | declare r record; 82 | begin 83 | for r in select * from g1() 84 | loop 85 | r.c := 20; 86 | end loop; 87 | end; 88 | $$ language plpgsql; 89 | select f1(); 90 | ERROR: record "r" has no field "c" 91 | CONTEXT: PL/pgSQL function f1() line 6 at assignment to field "c" of variable "r" declared on line 2 92 | drop function f1(); 93 | drop function g1(); 94 | create function f1() 95 | returns int as $$ 96 | declare r int; 97 | begin 98 | if false then 99 | r := a + b; 100 | end if; 101 | return r; 102 | end; 103 | $$ language plpgsql; 104 | select f1(); 105 | ERROR: column "a" does not exist 106 | LINE 1: r := a + b 107 | ^ 108 | QUERY: r := a + b 109 | CONTEXT: PL/pgSQL function f1() line 5 at assignment to variable "r" declared on line 2 110 | drop function f1(); 111 | create or replace function f1() 112 | returns void as $$ 113 | declare r int[]; 114 | begin 115 | if false then 116 | r[c+10] := 20; 117 | end if; 118 | end; 119 | $$ language plpgsql; 120 | select f1(); 121 | ERROR: column "c" does not exist 122 | LINE 1: r[c+10] := 20 123 | ^ 124 | QUERY: r[c+10] := 20 125 | CONTEXT: PL/pgSQL function f1() line 5 at assignment to variable "r" declared on line 2 126 | drop function f1(); 127 | create or replace function f1() 128 | returns void as $$ 129 | declare r int; 130 | begin 131 | if false then 132 | r[10] := 20; 133 | end if; 134 | end; 135 | $$ language plpgsql; 136 | select f1(); 137 | ERROR: cannot subscript type integer because it does not support subscripting 138 | LINE 1: r[10] := 20 139 | ^ 140 | QUERY: r[10] := 20 141 | CONTEXT: PL/pgSQL function f1() line 5 at assignment to variable "r" declared on line 2 142 | drop function f1(); 143 | create or replace function f1() 144 | returns void as $$ 145 | begin 146 | if false then 147 | insert into badbadtable values(10,20); 148 | end if; 149 | return; 150 | end; 151 | $$ language plpgsql; 152 | set plpgsql_check.mode = 'fresh_start'; 153 | select f1(); 154 | ERROR: relation "badbadtable" does not exist 155 | LINE 1: insert into badbadtable values(10,20) 156 | ^ 157 | QUERY: insert into badbadtable values(10,20) 158 | CONTEXT: PL/pgSQL function f1() line 4 at SQL statement 159 | -- should not raise exception there 160 | select f1(); 161 | f1 162 | ---- 163 | 164 | (1 row) 165 | 166 | create or replace function f1() 167 | returns void as $$ 168 | begin 169 | if false then 170 | insert into badbadtable values(10,20); 171 | end if; 172 | return; 173 | end; 174 | $$ language plpgsql; 175 | -- after refreshing it should to raise exception again 176 | select f1(); 177 | ERROR: relation "badbadtable" does not exist 178 | LINE 1: insert into badbadtable values(10,20) 179 | ^ 180 | QUERY: insert into badbadtable values(10,20) 181 | CONTEXT: PL/pgSQL function f1() line 4 at SQL statement 182 | set plpgsql_check.mode = 'every_start'; 183 | -- should to raise warning only 184 | set plpgsql_check.fatal_errors = false; 185 | select f1(); 186 | WARNING: relation "badbadtable" does not exist 187 | LINE 1: insert into badbadtable values(10,20) 188 | ^ 189 | QUERY: insert into badbadtable values(10,20) 190 | CONTEXT: PL/pgSQL function f1() line 4 at SQL statement 191 | PL/pgSQL function f1() line 4 at SQL statement 192 | f1 193 | ---- 194 | 195 | (1 row) 196 | 197 | drop function f1(); 198 | create function f1() 199 | returns setof t1 as $$ 200 | begin 201 | if false then 202 | return query select a,a,a from t1; 203 | return; 204 | end if; 205 | end; 206 | $$ language plpgsql; 207 | select * from f1(); 208 | WARNING: structure of query does not match function result type 209 | DETAIL: Number of returned columns (3) does not match expected column count (2). 210 | CONTEXT: PL/pgSQL function f1() line 4 at RETURN QUERY 211 | PL/pgSQL function f1() line 4 at RETURN QUERY 212 | a | b 213 | ---+--- 214 | (0 rows) 215 | 216 | drop function f1(); 217 | create function f1() 218 | returns setof t1 as $$ 219 | begin 220 | if false then 221 | return query select a, b::numeric from t1; 222 | return; 223 | end if; 224 | end; 225 | $$ language plpgsql; 226 | select * from f1(); 227 | WARNING: structure of query does not match function result type 228 | DETAIL: Returned type numeric does not match expected type integer in column "b" (position 2). 229 | CONTEXT: PL/pgSQL function f1() line 4 at RETURN QUERY 230 | PL/pgSQL function f1() line 4 at RETURN QUERY 231 | a | b 232 | ---+--- 233 | (0 rows) 234 | 235 | drop function f1(); 236 | drop table t1; 237 | do $$ 238 | declare 239 | begin 240 | if false then 241 | for i in 1,3..(2) loop 242 | raise notice 'foo %', i; 243 | end loop; 244 | end if; 245 | end; 246 | $$; 247 | WARNING: query "1,3" returned 2 columns 248 | CONTEXT: PL/pgSQL function inline_code_block line 5 at FOR with integer loop variable 249 | PL/pgSQL function inline_code_block line 5 at FOR with integer loop variable 250 | -- tests designed for 9.2 251 | set check_function_bodies to off; 252 | create or replace function f1() 253 | returns void as $$ 254 | begin 255 | if false then 256 | raise notice '%', 1, 2; 257 | end if; 258 | end; 259 | $$ language plpgsql; 260 | select f1(); 261 | ERROR: too many parameters specified for RAISE 262 | CONTEXT: compilation of PL/pgSQL function "f1" near line 4 263 | drop function f1(); 264 | create or replace function f1() 265 | returns void as $$ 266 | begin 267 | if false then 268 | raise notice '% %'; 269 | end if; 270 | end; 271 | $$ language plpgsql; 272 | select f1(); 273 | ERROR: too few parameters specified for RAISE 274 | CONTEXT: compilation of PL/pgSQL function "f1" near line 4 275 | drop function f1(); 276 | create type _exception_type as ( 277 | state text, 278 | message text, 279 | detail text); 280 | create or replace function f1() 281 | returns void as $$ 282 | declare 283 | _exception record; 284 | begin 285 | _exception := NULL::_exception_type; 286 | exception when others then 287 | get stacked diagnostics 288 | _exception.state = RETURNED_SQLSTATE, 289 | _exception.message = MESSAGE_TEXT, 290 | _exception.detail = PG_EXCEPTION_DETAIL, 291 | _exception.hint = PG_EXCEPTION_HINT; 292 | end; 293 | $$ language plpgsql; 294 | select f1(); 295 | WARNING: record "_exception" has no field "hint" 296 | CONTEXT: PL/pgSQL function f1() line 7 at GET STACKED DIAGNOSTICS 297 | PL/pgSQL function f1() line 7 at GET STACKED DIAGNOSTICS 298 | f1 299 | ---- 300 | 301 | (1 row) 302 | 303 | drop function f1(); 304 | drop type _exception_type; 305 | create table footab(a int, b int, c int); 306 | create or replace function footab_trig_func() 307 | returns trigger as $$ 308 | declare x int; 309 | begin 310 | if false then 311 | -- should be ok; 312 | select count(*) from newtab into x; 313 | 314 | -- should fail; 315 | select count(*) from newtab where d = 10 into x; 316 | end if; 317 | return null; 318 | end; 319 | $$ language plpgsql; 320 | create trigger footab_trigger 321 | after insert on footab 322 | referencing new table as newtab 323 | for each statement execute procedure footab_trig_func(); 324 | -- should to fail 325 | insert into footab values(1,2,3); 326 | WARNING: column "d" does not exist 327 | LINE 1: select count(*) from newtab where d = 10 328 | ^ 329 | QUERY: select count(*) from newtab where d = 10 330 | CONTEXT: PL/pgSQL function footab_trig_func() line 9 at SQL statement 331 | PL/pgSQL function footab_trig_func() line 9 at SQL statement 332 | create or replace function footab_trig_func() 333 | returns trigger as $$ 334 | declare x int; 335 | begin 336 | if false then 337 | -- should be ok; 338 | select count(*) from newtab into x; 339 | end if; 340 | return null; 341 | end; 342 | $$ language plpgsql; 343 | -- should be ok 344 | insert into footab values(1,2,3); 345 | drop table footab; 346 | drop function footab_trig_func(); 347 | set plpgsql_check.mode = 'every_start'; 348 | create or replace procedure proc_test() 349 | as $$ 350 | begin 351 | commit; 352 | end; 353 | $$ language plpgsql; 354 | call proc_test(); 355 | drop procedure proc_test(); 356 | -- should not to crash 357 | set plpgsql_check.mode = 'fresh_start'; 358 | do $$ 359 | begin 360 | end; 361 | $$; 362 | -------------------------------------------------------------------------------- /install_bindist.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import shutil 4 | 5 | os.makedirs(sys.argv[2], exist_ok=True) 6 | shutil.copy(sys.argv[3], os.path.join(sys.argv[2], sys.argv[4])) 7 | 8 | for f in sys.argv[5:]: 9 | shutil.copy(os.path.join(sys.argv[1], f), os.path.join(sys.argv[2], f)) -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('plpgsql_check', ['c'], version: '2.8.1') 2 | 3 | pg_config = find_program('pg_config') 4 | 5 | bindir = run_command(pg_config, '--bindir', check: true).stdout().strip() 6 | includedir_server = run_command(pg_config, '--includedir-server', check: true).stdout().strip() 7 | includedir = run_command(pg_config, '--includedir', check: true).stdout().strip() 8 | pkglibdir = run_command(pg_config, '--pkglibdir', check: true).stdout().strip() 9 | sharedir = run_command(pg_config, '--sharedir', check: true).stdout().strip() 10 | libdir = run_command(pg_config, '--libdir', check: true).stdout().strip() 11 | 12 | versionstring = run_command(pg_config, '--version', check: true).stdout().strip() 13 | pg_version = versionstring.split(' ')[1] 14 | 15 | if pg_version.endswith('devel') 16 | pg_version_arr = [pg_version.split('devel')[0], '0'] 17 | elif pg_version.contains('beta') 18 | pg_version_arr = [pg_version.split('beta')[0], '0'] 19 | elif pg_version.contains('rc') 20 | pg_version_arr = [pg_version.split('rc')[0], '0'] 21 | else 22 | pg_version_arr = pg_version.split('.') 23 | endif 24 | 25 | pg_version_major = pg_version_arr[0].to_int() 26 | pg_version_minor = pg_version_arr[1].to_int() 27 | pg_version_num = (pg_version_major * 10000) + pg_version_minor 28 | 29 | module_version_minor = meson.project_version() 30 | module_version_arr = module_version_minor.split('.') 31 | module_version = module_version_arr[0] + '.' + module_version_arr[1] 32 | module_name = meson.project_name() 33 | 34 | sources = files( 35 | 'src/assign.c', 36 | 'src/cursors_leaks.c', 37 | 'src/format.c', 38 | 'src/check_function.c', 39 | 'src/pldbgapi2.c', 40 | 'src/pragma.c', 41 | 'src/report.c', 42 | 'src/tablefunc.c', 43 | 'src/typdesc.c', 44 | 'src/catalog.c', 45 | 'src/expr_walk.c', 46 | 'src/check_expr.c', 47 | 'src/parser.c', 48 | 'src/plpgsql_check.c', 49 | 'src/profiler.c', 50 | 'src/stmtwalk.c', 51 | 'src/tracer.c', 52 | ) 53 | 54 | data = [ module_name + '.control', 55 | module_name + '--' + module_version + '.sql' ] 56 | 57 | tests = [ 'plpgsql_check_active', 58 | 'plpgsql_check_passive', 59 | 'plpgsql_check_active-' + pg_version_major.to_string(), 60 | 'plpgsql_check_passive-' + pg_version_major.to_string() ] 61 | 62 | compilerName = meson.get_compiler('c').get_id() 63 | 64 | if meson.get_compiler('c').get_id() == 'msvc' 65 | incdir = [includedir_server / 'port/win32_msvc', 66 | includedir_server / 'port/win32', 67 | includedir_server, 68 | includedir] 69 | postgres_lib = meson.get_compiler('c').find_library( 70 | 'postgres', 71 | dirs: libdir, 72 | static: true, 73 | required: true 74 | ) 75 | else 76 | incdir = [ includedir_server ] 77 | postgres_lib = [] 78 | endif 79 | 80 | module_lib = shared_module( 81 | module_name, 82 | sources, 83 | include_directories: incdir, 84 | install: true, 85 | install_dir: pkglibdir, 86 | name_prefix: '', 87 | dependencies: postgres_lib, 88 | ) 89 | 90 | install_data(data, install_dir: sharedir / 'extension') 91 | 92 | fs = import('fs') 93 | bindistdir = fs.expanduser('~' / module_name / module_name + '-' + 94 | '-'.join( [ module_version_minor, 95 | target_machine.cpu(), 96 | target_machine.system() ] )) 97 | 98 | lib_name = fs.name(module_lib.full_path()) 99 | lib_suffix = lib_name.split('.')[1] 100 | 101 | python_exe = find_program('python3', 'python') 102 | 103 | custom_target('bindist', 104 | depends: module_lib, 105 | input: module_lib, 106 | output: 'bindist', 107 | command: [ python_exe, 108 | meson.current_source_dir() / 'install_bindist.py', 109 | meson.current_source_dir(), 110 | bindistdir, 111 | module_lib.full_path(), 112 | fs.stem(lib_name) + '_' + pg_version_major.to_string() + '.' + lib_suffix, 113 | data ], 114 | install: false, 115 | build_by_default: false) 116 | 117 | pg_regress = find_program( 118 | 'pg_regress', 119 | dirs: [pkglibdir / 'pgxs/src/test/regress'] 120 | ) 121 | 122 | test('regress', 123 | pg_regress, 124 | args: [ '--bindir', bindir, 125 | '--inputdir', meson.current_source_dir(), 126 | ] + tests, 127 | ) 128 | -------------------------------------------------------------------------------- /msvc/plpgsql_check.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 11.00 3 | # Visual C++ Express 2010 4 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "plpgsql_check", "plpgsql_check\plpgsql_check.vcxproj", "{7ED1E443-C8B4-4CF1-93AE-580AE10F5F95}" 5 | EndProject 6 | Global 7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 8 | 9.2|Win32 = 9.2|Win32 9 | 9.2|x64 = 9.2|x64 10 | 9.3|Win32 = 9.3|Win32 11 | 9.3|x64 = 9.3|x64 12 | 9.4|Win32 = 9.4|Win32 13 | 9.4|x64 = 9.4|x64 14 | Debug|Win32 = Debug|Win32 15 | Debug|x64 = Debug|x64 16 | Release|Win32 = Release|Win32 17 | Release|x64 = Release|x64 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {7ED1E443-C8B4-4CF1-93AE-580AE10F5F95}.9.2|Win32.ActiveCfg = 9.2|Win32 21 | {7ED1E443-C8B4-4CF1-93AE-580AE10F5F95}.9.2|Win32.Build.0 = 9.2|Win32 22 | {7ED1E443-C8B4-4CF1-93AE-580AE10F5F95}.9.2|x64.ActiveCfg = 9.2|x64 23 | {7ED1E443-C8B4-4CF1-93AE-580AE10F5F95}.9.2|x64.Build.0 = 9.2|x64 24 | {7ED1E443-C8B4-4CF1-93AE-580AE10F5F95}.9.3|Win32.ActiveCfg = 9.3|Win32 25 | {7ED1E443-C8B4-4CF1-93AE-580AE10F5F95}.9.3|Win32.Build.0 = 9.3|Win32 26 | {7ED1E443-C8B4-4CF1-93AE-580AE10F5F95}.9.3|x64.ActiveCfg = 9.3|x64 27 | {7ED1E443-C8B4-4CF1-93AE-580AE10F5F95}.9.3|x64.Build.0 = 9.3|x64 28 | {7ED1E443-C8B4-4CF1-93AE-580AE10F5F95}.9.4|Win32.ActiveCfg = 9.4|Win32 29 | {7ED1E443-C8B4-4CF1-93AE-580AE10F5F95}.9.4|Win32.Build.0 = 9.4|Win32 30 | {7ED1E443-C8B4-4CF1-93AE-580AE10F5F95}.9.4|x64.ActiveCfg = 9.4|x64 31 | {7ED1E443-C8B4-4CF1-93AE-580AE10F5F95}.9.4|x64.Build.0 = 9.4|x64 32 | {7ED1E443-C8B4-4CF1-93AE-580AE10F5F95}.Debug|Win32.ActiveCfg = 9.4|x64 33 | {7ED1E443-C8B4-4CF1-93AE-580AE10F5F95}.Debug|x64.ActiveCfg = 9.4|x64 34 | {7ED1E443-C8B4-4CF1-93AE-580AE10F5F95}.Debug|x64.Build.0 = 9.4|x64 35 | {7ED1E443-C8B4-4CF1-93AE-580AE10F5F95}.Release|Win32.ActiveCfg = 9.4|x64 36 | {7ED1E443-C8B4-4CF1-93AE-580AE10F5F95}.Release|x64.ActiveCfg = 9.4|x64 37 | {7ED1E443-C8B4-4CF1-93AE-580AE10F5F95}.Release|x64.Build.0 = 9.4|x64 38 | EndGlobalSection 39 | GlobalSection(SolutionProperties) = preSolution 40 | HideSolutionNode = FALSE 41 | EndGlobalSection 42 | EndGlobal 43 | -------------------------------------------------------------------------------- /msvc/plpgsql_check/plpgsql_check.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 9.2 6 | Win32 7 | 8 | 9 | 9.2 10 | x64 11 | 12 | 13 | 9.3 14 | Win32 15 | 16 | 17 | 9.3 18 | x64 19 | 20 | 21 | 9.4 22 | Win32 23 | 24 | 25 | 9.4 26 | x64 27 | 28 | 29 | 30 | {7ED1E443-C8B4-4CF1-93AE-580AE10F5F95} 31 | plpgsql_check 32 | 33 | 34 | 35 | DynamicLibrary 36 | false 37 | true 38 | MultiByte 39 | Windows7.1SDK 40 | 41 | 42 | DynamicLibrary 43 | false 44 | true 45 | MultiByte 46 | Windows7.1SDK 47 | 48 | 49 | DynamicLibrary 50 | false 51 | true 52 | MultiByte 53 | Windows7.1SDK 54 | 55 | 56 | DynamicLibrary 57 | false 58 | true 59 | MultiByte 60 | Windows7.1SDK 61 | 62 | 63 | DynamicLibrary 64 | false 65 | true 66 | MultiByte 67 | Windows7.1SDK 68 | 69 | 70 | DynamicLibrary 71 | false 72 | true 73 | MultiByte 74 | Windows7.1SDK 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | false 100 | 101 | 102 | false 103 | 104 | 105 | false 106 | 107 | 108 | false 109 | 110 | 111 | false 112 | 113 | 114 | false 115 | 116 | 117 | 118 | Level3 119 | MaxSpeed 120 | true 121 | true 122 | WIN32;%(PreprocessorDefinitions) 123 | false 124 | CompileAsC 125 | 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) 126 | 127 | 128 | true 129 | true 130 | true 131 | postgres.lib;plpgsql.lib;%(AdditionalDependencies) 132 | C:\Program Files %28x86%29\PostgreSQL\9.4\lib;%(AdditionalLibraryDirectories) 133 | 134 | 135 | false 136 | 137 | 138 | 139 | 140 | Level3 141 | MaxSpeed 142 | true 143 | true 144 | WIN32;%(PreprocessorDefinitions) 145 | false 146 | CompileAsC 147 | 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) 148 | 149 | 150 | true 151 | true 152 | true 153 | postgres.lib;plpgsql.lib;%(AdditionalDependencies) 154 | C:\Program Files %28x86%29\PostgreSQL\9.2\lib;%(AdditionalLibraryDirectories) 155 | 156 | 157 | false 158 | 159 | 160 | 161 | 162 | Level3 163 | MaxSpeed 164 | true 165 | true 166 | WIN32;%(PreprocessorDefinitions) 167 | false 168 | CompileAsC 169 | 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) 170 | 171 | 172 | true 173 | true 174 | true 175 | postgres.lib;plpgsql.lib;%(AdditionalDependencies) 176 | C:\Program Files\PostgreSQL\9.4\lib;%(AdditionalLibraryDirectories) 177 | 178 | 179 | false 180 | 181 | 182 | 183 | 184 | Level3 185 | MaxSpeed 186 | true 187 | true 188 | WIN32;%(PreprocessorDefinitions) 189 | false 190 | CompileAsC 191 | 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) 192 | 193 | 194 | true 195 | true 196 | true 197 | postgres.lib;plpgsql.lib;%(AdditionalDependencies) 198 | C:\Program Files\PostgreSQL\9.2\lib;%(AdditionalLibraryDirectories) 199 | 200 | 201 | false 202 | 203 | 204 | 205 | 206 | Level3 207 | MaxSpeed 208 | true 209 | true 210 | WIN32;%(PreprocessorDefinitions) 211 | false 212 | CompileAsC 213 | 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) 214 | 215 | 216 | true 217 | true 218 | true 219 | postgres.lib;plpgsql.lib;%(AdditionalDependencies) 220 | C:\Program Files %28x86%29\PostgreSQL\9.3\lib;%(AdditionalLibraryDirectories) 221 | 222 | 223 | false 224 | 225 | 226 | 227 | 228 | Level3 229 | MaxSpeed 230 | true 231 | true 232 | WIN32;%(PreprocessorDefinitions) 233 | false 234 | CompileAsC 235 | 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) 236 | 237 | 238 | true 239 | true 240 | true 241 | postgres.lib;plpgsql.lib;%(AdditionalDependencies) 242 | C:\Program Files\PostgreSQL\9.3\lib;%(AdditionalLibraryDirectories) 243 | 244 | 245 | false 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | -------------------------------------------------------------------------------- /msvc/plpgsql_check/plpgsql_check.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | 23 | 24 | Header Files 25 | 26 | 27 | -------------------------------------------------------------------------------- /msvc/plpgsql_check/plpgsql_check.vcxproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | -------------------------------------------------------------------------------- /plpgsql_check--2.8.sql: -------------------------------------------------------------------------------- 1 | -- complain if script is sourced in psql, rather than via CREATE EXTENSION 2 | \echo Use "CREATE EXTENSION plpgsql_check" to load this file. \quit 3 | 4 | CREATE FUNCTION plpgsql_check_function_tb(funcoid regprocedure, 5 | relid regclass DEFAULT 0, 6 | fatal_errors boolean DEFAULT true, 7 | other_warnings boolean DEFAULT true, 8 | performance_warnings boolean DEFAULT false, 9 | extra_warnings boolean DEFAULT true, 10 | security_warnings boolean DEFAULT false, 11 | compatibility_warnings boolean DEFAULT false, 12 | oldtable name DEFAULT null, 13 | newtable name DEFAULT null, 14 | anyelememttype regtype DEFAULT 'int', 15 | anyenumtype regtype DEFAULT '-', 16 | anyrangetype regtype DEFAULT 'int4range', 17 | anycompatibletype regtype DEFAULT 'int', 18 | anycompatiblerangetype regtype DEFAULT 'int4range', 19 | without_warnings boolean DEFAULT false, 20 | all_warnings boolean DEFAULT false, 21 | use_incomment_options boolean DEFAULT true, 22 | incomment_options_usage_warning boolean DEFAULT false, 23 | constant_tracing boolean DEFAULT true) 24 | RETURNS TABLE(functionid regproc, 25 | lineno int, 26 | statement text, 27 | sqlstate text, 28 | message text, 29 | detail text, 30 | hint text, 31 | level text, 32 | "position" int, 33 | query text, 34 | context text) 35 | AS 'MODULE_PATHNAME','plpgsql_check_function_tb' 36 | LANGUAGE C; 37 | 38 | CREATE FUNCTION plpgsql_check_function(funcoid regprocedure, 39 | relid regclass DEFAULT 0, 40 | format text DEFAULT 'text', 41 | fatal_errors boolean DEFAULT true, 42 | other_warnings boolean DEFAULT true, 43 | performance_warnings boolean DEFAULT false, 44 | extra_warnings boolean DEFAULT true, 45 | security_warnings boolean DEFAULT false, 46 | compatibility_warnings boolean DEFAULT false, 47 | oldtable name DEFAULT null, 48 | newtable name DEFAULT null, 49 | anyelememttype regtype DEFAULT 'int', 50 | anyenumtype regtype DEFAULT '-', 51 | anyrangetype regtype DEFAULT 'int4range', 52 | anycompatibletype regtype DEFAULT 'int', 53 | anycompatiblerangetype regtype DEFAULT 'int4range', 54 | without_warnings boolean DEFAULT false, 55 | all_warnings boolean DEFAULT false, 56 | use_incomment_options boolean DEFAULT true, 57 | incomment_options_usage_warning boolean DEFAULT false, 58 | constant_tracing boolean DEFAULT true) 59 | RETURNS SETOF text 60 | AS 'MODULE_PATHNAME','plpgsql_check_function' 61 | LANGUAGE C; 62 | 63 | CREATE FUNCTION plpgsql_check_function_tb(name text, 64 | relid regclass DEFAULT 0, 65 | fatal_errors boolean DEFAULT true, 66 | other_warnings boolean DEFAULT true, 67 | performance_warnings boolean DEFAULT false, 68 | extra_warnings boolean DEFAULT true, 69 | security_warnings boolean DEFAULT false, 70 | compatibility_warnings boolean DEFAULT false, 71 | oldtable name DEFAULT null, 72 | newtable name DEFAULT null, 73 | anyelememttype regtype DEFAULT 'int', 74 | anyenumtype regtype DEFAULT '-', 75 | anyrangetype regtype DEFAULT 'int4range', 76 | anycompatibletype regtype DEFAULT 'int', 77 | anycompatiblerangetype regtype DEFAULT 'int4range', 78 | without_warnings boolean DEFAULT false, 79 | all_warnings boolean DEFAULT false, 80 | use_incomment_options boolean DEFAULT true, 81 | incomment_options_usage_warning boolean DEFAULT false, 82 | constant_tracing boolean DEFAULT true) 83 | RETURNS TABLE(functionid regproc, 84 | lineno int, 85 | statement text, 86 | sqlstate text, 87 | message text, 88 | detail text, 89 | hint text, 90 | level text, 91 | "position" int, 92 | query text, 93 | context text) 94 | AS 'MODULE_PATHNAME','plpgsql_check_function_tb_name' 95 | LANGUAGE C; 96 | 97 | CREATE FUNCTION plpgsql_check_function(name text, 98 | relid regclass DEFAULT 0, 99 | format text DEFAULT 'text', 100 | fatal_errors boolean DEFAULT true, 101 | other_warnings boolean DEFAULT true, 102 | performance_warnings boolean DEFAULT false, 103 | extra_warnings boolean DEFAULT true, 104 | security_warnings boolean DEFAULT false, 105 | compatibility_warnings boolean DEFAULT false, 106 | oldtable name DEFAULT null, 107 | newtable name DEFAULT null, 108 | anyelememttype regtype DEFAULT 'int', 109 | anyenumtype regtype DEFAULT '-', 110 | anyrangetype regtype DEFAULT 'int4range', 111 | anycompatibletype regtype DEFAULT 'int', 112 | anycompatiblerangetype regtype DEFAULT 'int4range', 113 | without_warnings boolean DEFAULT false, 114 | all_warnings boolean DEFAULT false, 115 | use_incomment_options boolean DEFAULT true, 116 | incomment_options_usage_warning boolean DEFAULT false, 117 | constant_tracing boolean DEFAULT true) 118 | RETURNS SETOF text 119 | AS 'MODULE_PATHNAME','plpgsql_check_function_name' 120 | LANGUAGE C; 121 | 122 | CREATE FUNCTION __plpgsql_show_dependency_tb(funcoid regprocedure, 123 | relid regclass DEFAULT 0, 124 | anyelememttype regtype DEFAULT 'int', 125 | anyenumtype regtype DEFAULT '-', 126 | anyrangetype regtype DEFAULT 'int4range', 127 | anycompatibletype regtype DEFAULT 'int', 128 | anycompatiblerangetype regtype DEFAULT 'int4range') 129 | RETURNS TABLE(type text, 130 | oid oid, 131 | schema text, 132 | name text, 133 | params text) 134 | AS 'MODULE_PATHNAME','plpgsql_show_dependency_tb' 135 | LANGUAGE C; 136 | 137 | CREATE FUNCTION __plpgsql_show_dependency_tb(name text, 138 | relid regclass DEFAULT 0, 139 | anyelememttype regtype DEFAULT 'int', 140 | anyenumtype regtype DEFAULT '-', 141 | anyrangetype regtype DEFAULT 'int4range', 142 | anycompatibletype regtype DEFAULT 'int', 143 | anycompatiblerangetype regtype DEFAULT 'int4range') 144 | RETURNS TABLE(type text, 145 | oid oid, 146 | schema text, 147 | name text, 148 | params text) 149 | AS 'MODULE_PATHNAME','plpgsql_show_dependency_tb_name' 150 | LANGUAGE C; 151 | 152 | CREATE FUNCTION plpgsql_show_dependency_tb(funcoid regprocedure, 153 | relid regclass DEFAULT 0, 154 | anyelememttype regtype DEFAULT 'int', 155 | anyenumtype regtype DEFAULT '-', 156 | anyrangetype regtype DEFAULT 'int4range', 157 | anycompatibletype regtype DEFAULT 'int', 158 | anycompatiblerangetype regtype DEFAULT 'int4range') 159 | RETURNS TABLE(type text, 160 | oid oid, 161 | schema text, 162 | name text, 163 | params text) 164 | AS $$ 165 | SELECT * 166 | FROM @extschema@.__plpgsql_show_dependency_tb($1, $2, $3, $4, $5, $6, $7) 167 | ORDER BY 1, 3, 4; 168 | $$ LANGUAGE sql; 169 | 170 | CREATE FUNCTION plpgsql_show_dependency_tb(fnname text, 171 | relid regclass DEFAULT 0, 172 | anyelememttype regtype DEFAULT 'int', 173 | anyenumtype regtype DEFAULT '-', 174 | anyrangetype regtype DEFAULT 'int4range', 175 | anycompatibletype regtype DEFAULT 'int', 176 | anycompatiblerangetype regtype DEFAULT 'int4range') 177 | RETURNS TABLE(type text, 178 | oid oid, 179 | schema text, 180 | name text, 181 | params text) 182 | AS $$ 183 | SELECT * 184 | FROM @extschema@.__plpgsql_show_dependency_tb($1, $2, $3, $4, $5, $6, $7) 185 | ORDER BY 1, 3, 4; 186 | $$ LANGUAGE sql; 187 | 188 | CREATE FUNCTION plpgsql_profiler_function_tb(funcoid regprocedure) 189 | RETURNS TABLE(lineno int, 190 | stmt_lineno int, 191 | queryids int8[], 192 | cmds_on_row int, 193 | exec_stmts int8, 194 | exec_stmts_err int8, 195 | total_time double precision, 196 | avg_time double precision, 197 | max_time double precision[], 198 | processed_rows int8[], 199 | source text) 200 | AS 'MODULE_PATHNAME','plpgsql_profiler_function_tb' 201 | LANGUAGE C STRICT; 202 | 203 | CREATE FUNCTION plpgsql_profiler_function_tb(name text) 204 | RETURNS TABLE(lineno int, 205 | stmt_lineno int, 206 | queryids int8[], 207 | cmds_on_row int, 208 | exec_stmts int8, 209 | exec_stmts_err int8, 210 | total_time double precision, 211 | avg_time double precision, 212 | max_time double precision[], 213 | processed_rows int8[], 214 | source text) 215 | AS 'MODULE_PATHNAME','plpgsql_profiler_function_tb_name' 216 | LANGUAGE C STRICT; 217 | 218 | CREATE FUNCTION plpgsql_profiler_function_statements_tb(funcoid regprocedure) 219 | RETURNS TABLE(stmtid int, 220 | parent_stmtid int, 221 | parent_note text, 222 | block_num int, 223 | lineno int, 224 | queryid int8, 225 | exec_stmts int8, 226 | exec_stmts_err int8, 227 | total_time double precision, 228 | avg_time double precision, 229 | max_time double precision, 230 | processed_rows int8, 231 | stmtname text) 232 | AS 'MODULE_PATHNAME','plpgsql_profiler_function_statements_tb' 233 | LANGUAGE C STRICT; 234 | 235 | CREATE FUNCTION plpgsql_profiler_function_statements_tb(name text) 236 | RETURNS TABLE(stmtid int, 237 | parent_stmtid int, 238 | parent_note text, 239 | block_num int, 240 | lineno int, 241 | queryid int8, 242 | exec_stmts int8, 243 | exec_stmts_err int8, 244 | total_time double precision, 245 | avg_time double precision, 246 | max_time double precision, 247 | processed_rows int8, 248 | stmtname text) 249 | AS 'MODULE_PATHNAME','plpgsql_profiler_function_statements_tb_name' 250 | LANGUAGE C STRICT; 251 | 252 | CREATE FUNCTION plpgsql_profiler_install_fake_queryid_hook() 253 | RETURNS void AS 'MODULE_PATHNAME','plpgsql_profiler_install_fake_queryid_hook' 254 | LANGUAGE C STRICT; 255 | 256 | CREATE FUNCTION plpgsql_profiler_remove_fake_queryid_hook() 257 | RETURNS void AS 'MODULE_PATHNAME','plpgsql_profiler_remove_fake_queryid_hook' 258 | LANGUAGE C STRICT; 259 | 260 | CREATE FUNCTION plpgsql_profiler_reset_all() 261 | RETURNS void AS 'MODULE_PATHNAME','plpgsql_profiler_reset_all' 262 | LANGUAGE C STRICT; 263 | 264 | CREATE FUNCTION plpgsql_profiler_reset(funcoid regprocedure) 265 | RETURNS void AS 'MODULE_PATHNAME','plpgsql_profiler_reset' 266 | LANGUAGE C STRICT; 267 | 268 | CREATE OR REPLACE FUNCTION plpgsql_coverage_statements(funcoid regprocedure) 269 | RETURNS double precision AS 'MODULE_PATHNAME', 'plpgsql_coverage_statements' 270 | LANGUAGE C; 271 | 272 | CREATE OR REPLACE FUNCTION plpgsql_coverage_statements(name text) 273 | RETURNS double precision AS 'MODULE_PATHNAME', 'plpgsql_coverage_statements_name' 274 | LANGUAGE C; 275 | 276 | CREATE OR REPLACE FUNCTION plpgsql_coverage_branches(funcoid regprocedure) 277 | RETURNS double precision AS 'MODULE_PATHNAME', 'plpgsql_coverage_branches' 278 | LANGUAGE C; 279 | 280 | CREATE OR REPLACE FUNCTION plpgsql_coverage_branches(name text) 281 | RETURNS double precision AS 'MODULE_PATHNAME', 'plpgsql_coverage_branches_name' 282 | LANGUAGE C; 283 | 284 | CREATE OR REPLACE FUNCTION plpgsql_check_pragma(VARIADIC name text[]) 285 | RETURNS integer AS 'MODULE_PATHNAME', 'plpgsql_check_pragma' 286 | LANGUAGE C VOLATILE; 287 | 288 | CREATE OR REPLACE FUNCTION plpgsql_profiler_functions_all() 289 | RETURNS TABLE(funcoid regprocedure, 290 | exec_count int8, 291 | exec_stmts_err int8, 292 | total_time double precision, 293 | avg_time double precision, 294 | stddev_time double precision, 295 | min_time double precision, 296 | max_time double precision) 297 | AS 'MODULE_PATHNAME','plpgsql_profiler_functions_all_tb' 298 | LANGUAGE C STRICT; 299 | 300 | CREATE OR REPLACE FUNCTION plpgsql_check_profiler(enable boolean DEFAULT NULL) 301 | RETURNS boolean AS 'MODULE_PATHNAME', 'plpgsql_check_profiler_ctrl' 302 | LANGUAGE C VOLATILE; 303 | 304 | CREATE OR REPLACE FUNCTION plpgsql_check_tracer(enable boolean DEFAULT NULL, verbosity text DEFAULT NULL) 305 | RETURNS boolean AS 'MODULE_PATHNAME', 'plpgsql_check_tracer_ctrl' 306 | LANGUAGE C VOLATILE; -------------------------------------------------------------------------------- /plpgsql_check.control: -------------------------------------------------------------------------------- 1 | # plpgsql_check extension 2 | comment = 'extended check for plpgsql functions' 3 | default_version = '2.8' 4 | module_pathname = '$libdir/plpgsql_check' 5 | relocatable = false 6 | requires = 'plpgsql' 7 | -------------------------------------------------------------------------------- /postgresql14-plpgsql_check.spec: -------------------------------------------------------------------------------- 1 | %global pgmajorversion 14 2 | %global pginstdir /usr/pgsql-14 3 | %global sname plpgsql_check 4 | 5 | Name: %{sname}_%{pgmajorversion} 6 | Version: 2.8.1 7 | Release: 1%{?dist} 8 | Summary: Additional tools for plpgsql functions validation 9 | 10 | Group: Applications/Databases 11 | License: BSD 12 | URL: https://github.com/okbob/plpgsql_check/archive/v%{version}.zip 13 | Source0: plpgsql_check-%{version}.zip 14 | BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) 15 | 16 | BuildRequires: postgresql%{pgmajorversion}-devel 17 | Requires: postgresql%{pgmajorversion} 18 | 19 | %description 20 | The plpgsql_check is PostgreSQL extension with functionality for direct 21 | or indirect extra validation of functions in plpgsql language. It verifies 22 | a validity of SQL identifiers used in plpgsql code. It try to identify 23 | a performance issues. 24 | 25 | %prep 26 | %setup -q -n %{sname}-%{version} 27 | 28 | 29 | %build 30 | PATH="%{pginstdir}/bin;$PATH" ; export PATH 31 | CFLAGS="${CFLAGS:-%optflags}" ; export CFLAGS 32 | make USE_PGXS=1 PG_CONFIG=%{pginstdir}/bin/pg_config %{?_smp_mflags} 33 | 34 | %install 35 | rm -rf %{buildroot} 36 | make install DESTDIR=%{buildroot} PG_CONFIG=%{pginstdir}/bin/pg_config %{?_smp_mflags} 37 | 38 | %clean 39 | rm -rf %{buildroot} 40 | 41 | %files 42 | %defattr(644,root,root,755) 43 | %doc README.md 44 | %{pginstdir}/lib/plpgsql_check.so 45 | %{pginstdir}/share/extension/plpgsql_check--2.1.sql 46 | %{pginstdir}/share/extension/plpgsql_check.control 47 | %{pginstdir}/lib/bitcode/*.bc 48 | %{pginstdir}/lib/bitcode/plpgsql_check/src/*.bc 49 | %{pginstdir}/share/extension/*.control 50 | 51 | %changelog 52 | * Tue Mar 18 2025 - Pavel Stehule 2.8.0 53 | - remove support for PostgreSQL 12 and 13 54 | 55 | * Wed Dec 6 2023 - Pavel Stehule 2.7.0 56 | - unclosed cursors detection 57 | 58 | * Tue Oct 31 2023 - Pavel Stehule 2.6.0 59 | - simple constant tracing support 60 | 61 | * Sat Apr 29 2023 - Pavel Stehule 2.4.0 62 | - remove support for PostgreSQL 10 and 11 63 | 64 | * Wed Jan 11 2023 - Pavel Stehule 2.3.0 65 | - possibility to detect compatibility issues (obsolete setting of refcursor) 66 | 67 | * Tue Sep 20 2022 - Pavel Stehule 2.2.0 68 | - possibility to use in comment options 69 | 70 | * Wed Dec 29 2021 - Pavel Stehule 2.1.0 71 | - possibility to count statement's aborted execution 72 | - possibility to count "unfinished" statements due exception 73 | 74 | * Mon Sep 27 2021 - Pavel Stehule 2.0.0 75 | - pragma type for setting type to record variable 76 | - pragma table for creating ephemeral table 77 | 78 | * Mon Jun 21 2021 - Pavel Stehule 1.17.0 79 | - remove support for PostgreSQL 9.5 and 9.6 80 | 81 | * Sat Mar 6 2021 - Pavel Stehule 1.16.0 82 | - plpgsql_profiler_functions_all 83 | 84 | * Mon Nov 16 2020 - Pavel Stehule 1.14.0 85 | - queryid can be displayed in profiler's reports (Julien Rouhaud) 86 | - new profiler's GUC plpgsql_check.profiler_max_shared_chunks (Julien Rouhaud) 87 | - few minor bugfixes 88 | 89 | * Fri Aug 14 2020 - Pavel Stehule 1.13.0 90 | - tracer 91 | - pragma support to control checks, warnings and tracing 92 | 93 | * Thu Jul 2 2020 - Pavel STEHULE 1.11.0 94 | - possibility to check functions with arguments of polymorphic type 95 | - possibility to specify type used as real type instead polymorphic type 96 | 97 | * Fri Jun 05 2020 - Pavel STEHULE 1.10.0 98 | - deduction record type structure from result of polymorphic function 99 | 100 | * Mon Apr 27 2020 - Pavel STEHULE 1.9.1 101 | - minor bugfixes 102 | 103 | * Mon Mar 30 2020 - Pavel STEHULE 1.9.0 104 | - statement and branch coverage metrics 105 | - remove support for Postgres 9.4 106 | 107 | * Mon Jan 06 2020 - Pavel STEHULE 1.8.2 108 | - fix of compilation issue 109 | 110 | * Sun Jan 05 2020 - Pavel STEHULE 1.8.1 111 | - cleaner detection function oid from name or signature 112 | 113 | * Sun Dec 29 2019 - Pavel STEHULE 1.8.0 114 | - use Postgres tool for calling functions from plpgsql library instead dynamic linking 115 | - it solve issues related to dependency plpgsq_check on plpgsql 116 | 117 | * Mon Sep 23 2019 - Pavel STEHULE 1.7.6 118 | - fix false alarm - multiple plans in EXECUTE statement, and possible crash 119 | 120 | * Tue Sep 10 2019 - Pavel STEHULE 1.7.5 121 | - allow some work on tables with rules 122 | 123 | * Wed Jul 24 2019 - Pavel STEHULE 1.7.3 124 | - profiler bugfixes 125 | 126 | * Tue May 21 2019 - Pavel STEHULE 1.7.2 127 | - profiler bugfixes 128 | 129 | * Fri Apr 26 2019 - Pavel STEHULE 1.7.1 130 | - bugfixes 131 | 132 | * Wed Apr 17 2019 - Pavel STEHULE 1.7.0 133 | - check of format of fmt string of "format" function 134 | - better check of dynamic SQL when it is const string 135 | - check of SQL injection vulnerability of stmt expression at EXECUTE stmt 136 | 137 | * Sun Dec 23 2018 - Pavel STEHULE 1.4.2-1 138 | - metada fix 139 | 140 | * Fri Dec 21 2018 - Pavel STEHULE 1.4.1-1 141 | - minor bugfixes 142 | 143 | * Sun Dec 2 2018 - Pavel STEHULE 1.4.0-1 144 | - possible to show function's dependency on functions and tables 145 | - integrated profiler 146 | - bug fixes (almost false alarms) 147 | 148 | * Wed Jun 6 2018 - Pavel STEHULE 1.2.3-1 149 | - PostgreSQL 11 support 150 | - detect hidden casts in expressions 151 | 152 | * Thu Oct 26 2017 - Pavel STEHULE 1.2.2-1 153 | - never read variables detection 154 | - fix false alarm on MOVE command 155 | 156 | * Fri Sep 15 2017 - Pavel STEHULE 1.2.1-1 157 | - missing RETURN detection 158 | - fix some bugs and false alarms 159 | - PostgreSQL 11 support 160 | 161 | * Fri Nov 11 2016 - Pavel STEHULE 1.2.0-1 162 | - support extra warnings - shadowed variables 163 | 164 | * Thu Aug 25 2016 - Pavel STEHULE 1.0.5-1 165 | - minor fixes, support for PostgreSQL 10 166 | 167 | * Fri Apr 15 2016 - Pavel STEHULE 1.0.4-1 168 | - support for PostgreSQL 9.6 169 | 170 | * Mon Oct 12 2015 - Pavel STEHULE 1.0.3-1 171 | - fix false alarms of unused cursor variables 172 | - fix regress tests 173 | 174 | * Thu Jul 09 2015 - Pavel STEHULE 1.0.2-2 175 | - bugfix release 176 | 177 | * Fri Dec 19 2014 - Pavel STEHULE 0.9.3-1 178 | - fix a broken record field type checking 179 | - add check for assign to array field 180 | 181 | * Mon Aug 25 2014 - Pavel STEHULE 0.9.1-1 182 | - Initial packaging 183 | -------------------------------------------------------------------------------- /postgresql15-plpgsql_check.spec: -------------------------------------------------------------------------------- 1 | %global pgmajorversion 15 2 | %global pginstdir /usr/pgsql-15 3 | %global sname plpgsql_check 4 | 5 | Name: %{sname}_%{pgmajorversion} 6 | Version: 2.8.1 7 | Release: 1%{?dist} 8 | Summary: Additional tools for plpgsql functions validation 9 | 10 | Group: Applications/Databases 11 | License: BSD 12 | URL: https://github.com/okbob/plpgsql_check/archive/v%{version}.zip 13 | Source0: plpgsql_check-%{version}.zip 14 | BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) 15 | 16 | BuildRequires: postgresql%{pgmajorversion}-devel 17 | Requires: postgresql%{pgmajorversion} 18 | 19 | %description 20 | The plpgsql_check is PostgreSQL extension with functionality for direct 21 | or indirect extra validation of functions in plpgsql language. It verifies 22 | a validity of SQL identifiers used in plpgsql code. It try to identify 23 | a performance issues. 24 | 25 | %prep 26 | %setup -q -n %{sname}-%{version} 27 | 28 | 29 | %build 30 | PATH="%{pginstdir}/bin;$PATH" ; export PATH 31 | CFLAGS="${CFLAGS:-%optflags}" ; export CFLAGS 32 | make USE_PGXS=1 PG_CONFIG=%{pginstdir}/bin/pg_config %{?_smp_mflags} 33 | 34 | %install 35 | rm -rf %{buildroot} 36 | make install DESTDIR=%{buildroot} PG_CONFIG=%{pginstdir}/bin/pg_config %{?_smp_mflags} 37 | 38 | %clean 39 | rm -rf %{buildroot} 40 | 41 | %files 42 | %defattr(644,root,root,755) 43 | %doc README.md 44 | %{pginstdir}/lib/plpgsql_check.so 45 | %{pginstdir}/share/extension/plpgsql_check--2.1.sql 46 | %{pginstdir}/share/extension/plpgsql_check.control 47 | %{pginstdir}/lib/bitcode/*.bc 48 | %{pginstdir}/lib/bitcode/plpgsql_check/src/*.bc 49 | %{pginstdir}/share/extension/*.control 50 | 51 | %changelog 52 | * Tue Mar 18 2025 - Pavel Stehule 2.8.0 53 | - remove support for PostgreSQL 12 and 13 54 | 55 | * Wed Dec 6 2023 - Pavel Stehule 2.7.0 56 | - unclosed cursors detection 57 | 58 | * Tue Oct 31 2023 - Pavel Stehule 2.6.0 59 | - simple constant tracing support 60 | 61 | * Sat Apr 29 2023 - Pavel Stehule 2.4.0 62 | - remove support for PostgreSQL 10 and 11 63 | 64 | * Wed Jan 11 2023 - Pavel Stehule 2.3.0 65 | - possibility to detect compatibility issues (obsolete setting of refcursor) 66 | 67 | * Tue Sep 20 2022 - Pavel Stehule 2.2.0 68 | - possibility to use in comment options 69 | 70 | * Wed Dec 29 2021 - Pavel Stehule 2.1.0 71 | - possibility to count statement's aborted execution 72 | - possibility to count "unfinished" statements due exception 73 | 74 | * Mon Sep 27 2021 - Pavel Stehule 2.0.0 75 | - pragma type for setting type to record variable 76 | - pragma table for creating ephemeral table 77 | 78 | * Mon Jun 21 2021 - Pavel Stehule 1.17.0 79 | - remove support for PostgreSQL 9.5 and 9.6 80 | 81 | * Sat Mar 6 2021 - Pavel Stehule 1.16.0 82 | - plpgsql_profiler_functions_all 83 | 84 | * Mon Nov 16 2020 - Pavel Stehule 1.14.0 85 | - queryid can be displayed in profiler's reports (Julien Rouhaud) 86 | - new profiler's GUC plpgsql_check.profiler_max_shared_chunks (Julien Rouhaud) 87 | - few minor bugfixes 88 | 89 | * Fri Aug 14 2020 - Pavel Stehule 1.13.0 90 | - tracer 91 | - pragma support to control checks, warnings and tracing 92 | 93 | * Thu Jul 2 2020 - Pavel STEHULE 1.11.0 94 | - possibility to check functions with arguments of polymorphic type 95 | - possibility to specify type used as real type instead polymorphic type 96 | 97 | * Fri Jun 05 2020 - Pavel STEHULE 1.10.0 98 | - deduction record type structure from result of polymorphic function 99 | 100 | * Mon Apr 27 2020 - Pavel STEHULE 1.9.1 101 | - minor bugfixes 102 | 103 | * Mon Mar 30 2020 - Pavel STEHULE 1.9.0 104 | - statement and branch coverage metrics 105 | - remove support for Postgres 9.4 106 | 107 | * Mon Jan 06 2020 - Pavel STEHULE 1.8.2 108 | - fix of compilation issue 109 | 110 | * Sun Jan 05 2020 - Pavel STEHULE 1.8.1 111 | - cleaner detection function oid from name or signature 112 | 113 | * Sun Dec 29 2019 - Pavel STEHULE 1.8.0 114 | - use Postgres tool for calling functions from plpgsql library instead dynamic linking 115 | - it solve issues related to dependency plpgsq_check on plpgsql 116 | 117 | * Mon Sep 23 2019 - Pavel STEHULE 1.7.6 118 | - fix false alarm - multiple plans in EXECUTE statement, and possible crash 119 | 120 | * Tue Sep 10 2019 - Pavel STEHULE 1.7.5 121 | - allow some work on tables with rules 122 | 123 | * Wed Jul 24 2019 - Pavel STEHULE 1.7.3 124 | - profiler bugfixes 125 | 126 | * Tue May 21 2019 - Pavel STEHULE 1.7.2 127 | - profiler bugfixes 128 | 129 | * Fri Apr 26 2019 - Pavel STEHULE 1.7.1 130 | - bugfixes 131 | 132 | * Wed Apr 17 2019 - Pavel STEHULE 1.7.0 133 | - check of format of fmt string of "format" function 134 | - better check of dynamic SQL when it is const string 135 | - check of SQL injection vulnerability of stmt expression at EXECUTE stmt 136 | 137 | * Sun Dec 23 2018 - Pavel STEHULE 1.4.2-1 138 | - metada fix 139 | 140 | * Fri Dec 21 2018 - Pavel STEHULE 1.4.1-1 141 | - minor bugfixes 142 | 143 | * Sun Dec 2 2018 - Pavel STEHULE 1.4.0-1 144 | - possible to show function's dependency on functions and tables 145 | - integrated profiler 146 | - bug fixes (almost false alarms) 147 | 148 | * Wed Jun 6 2018 - Pavel STEHULE 1.2.3-1 149 | - PostgreSQL 11 support 150 | - detect hidden casts in expressions 151 | 152 | * Thu Oct 26 2017 - Pavel STEHULE 1.2.2-1 153 | - never read variables detection 154 | - fix false alarm on MOVE command 155 | 156 | * Fri Sep 15 2017 - Pavel STEHULE 1.2.1-1 157 | - missing RETURN detection 158 | - fix some bugs and false alarms 159 | - PostgreSQL 11 support 160 | 161 | * Fri Nov 11 2016 - Pavel STEHULE 1.2.0-1 162 | - support extra warnings - shadowed variables 163 | 164 | * Thu Aug 25 2016 - Pavel STEHULE 1.0.5-1 165 | - minor fixes, support for PostgreSQL 10 166 | 167 | * Fri Apr 15 2016 - Pavel STEHULE 1.0.4-1 168 | - support for PostgreSQL 9.6 169 | 170 | * Mon Oct 12 2015 - Pavel STEHULE 1.0.3-1 171 | - fix false alarms of unused cursor variables 172 | - fix regress tests 173 | 174 | * Thu Jul 09 2015 - Pavel STEHULE 1.0.2-2 175 | - bugfix release 176 | 177 | * Fri Dec 19 2014 - Pavel STEHULE 0.9.3-1 178 | - fix a broken record field type checking 179 | - add check for assign to array field 180 | 181 | * Mon Aug 25 2014 - Pavel STEHULE 0.9.1-1 182 | - Initial packaging 183 | -------------------------------------------------------------------------------- /postgresql16-plpgsql_check.spec: -------------------------------------------------------------------------------- 1 | %global pgmajorversion 16 2 | %global pginstdir /usr/pgsql-16 3 | %global sname plpgsql_check 4 | 5 | Name: %{sname}_%{pgmajorversion} 6 | Version: 2.8.1 7 | Release: 1%{?dist} 8 | Summary: Additional tools for plpgsql functions validation 9 | 10 | Group: Applications/Databases 11 | License: BSD 12 | URL: https://github.com/okbob/plpgsql_check/archive/v%{version}.zip 13 | Source0: plpgsql_check-%{version}.zip 14 | BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) 15 | 16 | BuildRequires: postgresql%{pgmajorversion}-devel 17 | Requires: postgresql%{pgmajorversion} 18 | 19 | %description 20 | The plpgsql_check is PostgreSQL extension with functionality for direct 21 | or indirect extra validation of functions in plpgsql language. It verifies 22 | a validity of SQL identifiers used in plpgsql code. It try to identify 23 | a performance issues. 24 | 25 | %prep 26 | %setup -q -n %{sname}-%{version} 27 | 28 | 29 | %build 30 | PATH="%{pginstdir}/bin;$PATH" ; export PATH 31 | CFLAGS="${CFLAGS:-%optflags}" ; export CFLAGS 32 | make USE_PGXS=1 PG_CONFIG=%{pginstdir}/bin/pg_config %{?_smp_mflags} 33 | 34 | %install 35 | rm -rf %{buildroot} 36 | make install DESTDIR=%{buildroot} PG_CONFIG=%{pginstdir}/bin/pg_config %{?_smp_mflags} 37 | 38 | %clean 39 | rm -rf %{buildroot} 40 | 41 | %files 42 | %defattr(644,root,root,755) 43 | %doc README.md 44 | %{pginstdir}/lib/plpgsql_check.so 45 | %{pginstdir}/share/extension/plpgsql_check--2.1.sql 46 | %{pginstdir}/share/extension/plpgsql_check.control 47 | %{pginstdir}/lib/bitcode/*.bc 48 | %{pginstdir}/lib/bitcode/plpgsql_check/src/*.bc 49 | %{pginstdir}/share/extension/*.control 50 | 51 | %changelog 52 | * Tue Mar 18 2025 - Pavel Stehule 2.8.0 53 | - remove support for PostgreSQL 12 and 13 54 | 55 | * Wed Dec 6 2023 - Pavel Stehule 2.7.0 56 | - unclosed cursors detection 57 | 58 | * Tue Oct 31 2023 - Pavel Stehule 2.6.0 59 | - simple constant tracing support 60 | 61 | * Sat Apr 29 2023 - Pavel Stehule 2.4.0 62 | - remove support for PostgreSQL 10 and 11 63 | 64 | * Wed Jan 11 2023 - Pavel Stehule 2.3.0 65 | - possibility to detect compatibility issues (obsolete setting of refcursor) 66 | 67 | * Tue Sep 20 2022 - Pavel Stehule 2.2.0 68 | - possibility to use in comment options 69 | 70 | * Wed Dec 29 2021 - Pavel Stehule 2.1.0 71 | - possibility to count statement's aborted execution 72 | - possibility to count "unfinished" statements due exception 73 | 74 | * Mon Sep 27 2021 - Pavel Stehule 2.0.0 75 | - pragma type for setting type to record variable 76 | - pragma table for creating ephemeral table 77 | 78 | * Mon Jun 21 2021 - Pavel Stehule 1.17.0 79 | - remove support for PostgreSQL 9.5 and 9.6 80 | 81 | * Sat Mar 6 2021 - Pavel Stehule 1.16.0 82 | - plpgsql_profiler_functions_all 83 | 84 | * Mon Nov 16 2020 - Pavel Stehule 1.14.0 85 | - queryid can be displayed in profiler's reports (Julien Rouhaud) 86 | - new profiler's GUC plpgsql_check.profiler_max_shared_chunks (Julien Rouhaud) 87 | - few minor bugfixes 88 | 89 | * Fri Aug 14 2020 - Pavel Stehule 1.13.0 90 | - tracer 91 | - pragma support to control checks, warnings and tracing 92 | 93 | * Thu Jul 2 2020 - Pavel STEHULE 1.11.0 94 | - possibility to check functions with arguments of polymorphic type 95 | - possibility to specify type used as real type instead polymorphic type 96 | 97 | * Fri Jun 05 2020 - Pavel STEHULE 1.10.0 98 | - deduction record type structure from result of polymorphic function 99 | 100 | * Mon Apr 27 2020 - Pavel STEHULE 1.9.1 101 | - minor bugfixes 102 | 103 | * Mon Mar 30 2020 - Pavel STEHULE 1.9.0 104 | - statement and branch coverage metrics 105 | - remove support for Postgres 9.4 106 | 107 | * Mon Jan 06 2020 - Pavel STEHULE 1.8.2 108 | - fix of compilation issue 109 | 110 | * Sun Jan 05 2020 - Pavel STEHULE 1.8.1 111 | - cleaner detection function oid from name or signature 112 | 113 | * Sun Dec 29 2019 - Pavel STEHULE 1.8.0 114 | - use Postgres tool for calling functions from plpgsql library instead dynamic linking 115 | - it solve issues related to dependency plpgsq_check on plpgsql 116 | 117 | * Mon Sep 23 2019 - Pavel STEHULE 1.7.6 118 | - fix false alarm - multiple plans in EXECUTE statement, and possible crash 119 | 120 | * Tue Sep 10 2019 - Pavel STEHULE 1.7.5 121 | - allow some work on tables with rules 122 | 123 | * Wed Jul 24 2019 - Pavel STEHULE 1.7.3 124 | - profiler bugfixes 125 | 126 | * Tue May 21 2019 - Pavel STEHULE 1.7.2 127 | - profiler bugfixes 128 | 129 | * Fri Apr 26 2019 - Pavel STEHULE 1.7.1 130 | - bugfixes 131 | 132 | * Wed Apr 17 2019 - Pavel STEHULE 1.7.0 133 | - check of format of fmt string of "format" function 134 | - better check of dynamic SQL when it is const string 135 | - check of SQL injection vulnerability of stmt expression at EXECUTE stmt 136 | 137 | * Sun Dec 23 2018 - Pavel STEHULE 1.4.2-1 138 | - metada fix 139 | 140 | * Fri Dec 21 2018 - Pavel STEHULE 1.4.1-1 141 | - minor bugfixes 142 | 143 | * Sun Dec 2 2018 - Pavel STEHULE 1.4.0-1 144 | - possible to show function's dependency on functions and tables 145 | - integrated profiler 146 | - bug fixes (almost false alarms) 147 | 148 | * Wed Jun 6 2018 - Pavel STEHULE 1.2.3-1 149 | - PostgreSQL 11 support 150 | - detect hidden casts in expressions 151 | 152 | * Thu Oct 26 2017 - Pavel STEHULE 1.2.2-1 153 | - never read variables detection 154 | - fix false alarm on MOVE command 155 | 156 | * Fri Sep 15 2017 - Pavel STEHULE 1.2.1-1 157 | - missing RETURN detection 158 | - fix some bugs and false alarms 159 | - PostgreSQL 11 support 160 | 161 | * Fri Nov 11 2016 - Pavel STEHULE 1.2.0-1 162 | - support extra warnings - shadowed variables 163 | 164 | * Thu Aug 25 2016 - Pavel STEHULE 1.0.5-1 165 | - minor fixes, support for PostgreSQL 10 166 | 167 | * Fri Apr 15 2016 - Pavel STEHULE 1.0.4-1 168 | - support for PostgreSQL 9.6 169 | 170 | * Mon Oct 12 2015 - Pavel STEHULE 1.0.3-1 171 | - fix false alarms of unused cursor variables 172 | - fix regress tests 173 | 174 | * Thu Jul 09 2015 - Pavel STEHULE 1.0.2-2 175 | - bugfix release 176 | 177 | * Fri Dec 19 2014 - Pavel STEHULE 0.9.3-1 178 | - fix a broken record field type checking 179 | - add check for assign to array field 180 | 181 | * Mon Aug 25 2014 - Pavel STEHULE 0.9.1-1 182 | - Initial packaging 183 | -------------------------------------------------------------------------------- /postgresql17-plpgsql_check.spec: -------------------------------------------------------------------------------- 1 | %global pgmajorversion 17 2 | %global pginstdir /usr/pgsql-17 3 | %global sname plpgsql_check 4 | 5 | Name: %{sname}_%{pgmajorversion} 6 | Version: 2.8.1 7 | Release: 1%{?dist} 8 | Summary: Additional tools for plpgsql functions validation 9 | 10 | Group: Applications/Databases 11 | License: BSD 12 | URL: https://github.com/okbob/plpgsql_check/archive/v%{version}.zip 13 | Source0: plpgsql_check-%{version}.zip 14 | BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) 15 | 16 | BuildRequires: postgresql%{pgmajorversion}-devel 17 | Requires: postgresql%{pgmajorversion} 18 | 19 | %description 20 | The plpgsql_check is PostgreSQL extension with functionality for direct 21 | or indirect extra validation of functions in plpgsql language. It verifies 22 | a validity of SQL identifiers used in plpgsql code. It try to identify 23 | a performance issues. 24 | 25 | %prep 26 | %setup -q -n %{sname}-%{version} 27 | 28 | 29 | %build 30 | PATH="%{pginstdir}/bin;$PATH" ; export PATH 31 | CFLAGS="${CFLAGS:-%optflags}" ; export CFLAGS 32 | make USE_PGXS=1 PG_CONFIG=%{pginstdir}/bin/pg_config %{?_smp_mflags} 33 | 34 | %install 35 | rm -rf %{buildroot} 36 | make install DESTDIR=%{buildroot} PG_CONFIG=%{pginstdir}/bin/pg_config %{?_smp_mflags} 37 | 38 | %clean 39 | rm -rf %{buildroot} 40 | 41 | %files 42 | %defattr(644,root,root,755) 43 | %doc README.md 44 | %{pginstdir}/lib/plpgsql_check.so 45 | %{pginstdir}/share/extension/plpgsql_check--2.1.sql 46 | %{pginstdir}/share/extension/plpgsql_check.control 47 | %{pginstdir}/lib/bitcode/*.bc 48 | %{pginstdir}/lib/bitcode/plpgsql_check/src/*.bc 49 | %{pginstdir}/share/extension/*.control 50 | 51 | %changelog 52 | * Tue Mar 18 2025 - Pavel Stehule 2.8.0 53 | - remove support for PostgreSQL 12 and 13 54 | 55 | * Wed Dec 6 2023 - Pavel Stehule 2.7.0 56 | - unclosed cursors detection 57 | 58 | * Tue Oct 31 2023 - Pavel Stehule 2.6.0 59 | - simple constant tracing support 60 | 61 | * Sat Apr 29 2023 - Pavel Stehule 2.4.0 62 | - remove support for PostgreSQL 10 and 11 63 | 64 | * Wed Jan 11 2023 - Pavel Stehule 2.3.0 65 | - possibility to detect compatibility issues (obsolete setting of refcursor) 66 | 67 | * Tue Sep 20 2022 - Pavel Stehule 2.2.0 68 | - possibility to use in comment options 69 | 70 | * Wed Dec 29 2021 - Pavel Stehule 2.1.0 71 | - possibility to count statement's aborted execution 72 | - possibility to count "unfinished" statements due exception 73 | 74 | * Mon Sep 27 2021 - Pavel Stehule 2.0.0 75 | - pragma type for setting type to record variable 76 | - pragma table for creating ephemeral table 77 | 78 | * Mon Jun 21 2021 - Pavel Stehule 1.17.0 79 | - remove support for PostgreSQL 9.5 and 9.6 80 | 81 | * Sat Mar 6 2021 - Pavel Stehule 1.16.0 82 | - plpgsql_profiler_functions_all 83 | 84 | * Mon Nov 16 2020 - Pavel Stehule 1.14.0 85 | - queryid can be displayed in profiler's reports (Julien Rouhaud) 86 | - new profiler's GUC plpgsql_check.profiler_max_shared_chunks (Julien Rouhaud) 87 | - few minor bugfixes 88 | 89 | * Fri Aug 14 2020 - Pavel Stehule 1.13.0 90 | - tracer 91 | - pragma support to control checks, warnings and tracing 92 | 93 | * Thu Jul 2 2020 - Pavel STEHULE 1.11.0 94 | - possibility to check functions with arguments of polymorphic type 95 | - possibility to specify type used as real type instead polymorphic type 96 | 97 | * Fri Jun 05 2020 - Pavel STEHULE 1.10.0 98 | - deduction record type structure from result of polymorphic function 99 | 100 | * Mon Apr 27 2020 - Pavel STEHULE 1.9.1 101 | - minor bugfixes 102 | 103 | * Mon Mar 30 2020 - Pavel STEHULE 1.9.0 104 | - statement and branch coverage metrics 105 | - remove support for Postgres 9.4 106 | 107 | * Mon Jan 06 2020 - Pavel STEHULE 1.8.2 108 | - fix of compilation issue 109 | 110 | * Sun Jan 05 2020 - Pavel STEHULE 1.8.1 111 | - cleaner detection function oid from name or signature 112 | 113 | * Sun Dec 29 2019 - Pavel STEHULE 1.8.0 114 | - use Postgres tool for calling functions from plpgsql library instead dynamic linking 115 | - it solve issues related to dependency plpgsq_check on plpgsql 116 | 117 | * Mon Sep 23 2019 - Pavel STEHULE 1.7.6 118 | - fix false alarm - multiple plans in EXECUTE statement, and possible crash 119 | 120 | * Tue Sep 10 2019 - Pavel STEHULE 1.7.5 121 | - allow some work on tables with rules 122 | 123 | * Wed Jul 24 2019 - Pavel STEHULE 1.7.3 124 | - profiler bugfixes 125 | 126 | * Tue May 21 2019 - Pavel STEHULE 1.7.2 127 | - profiler bugfixes 128 | 129 | * Fri Apr 26 2019 - Pavel STEHULE 1.7.1 130 | - bugfixes 131 | 132 | * Wed Apr 17 2019 - Pavel STEHULE 1.7.0 133 | - check of format of fmt string of "format" function 134 | - better check of dynamic SQL when it is const string 135 | - check of SQL injection vulnerability of stmt expression at EXECUTE stmt 136 | 137 | * Sun Dec 23 2018 - Pavel STEHULE 1.4.2-1 138 | - metada fix 139 | 140 | * Fri Dec 21 2018 - Pavel STEHULE 1.4.1-1 141 | - minor bugfixes 142 | 143 | * Sun Dec 2 2018 - Pavel STEHULE 1.4.0-1 144 | - possible to show function's dependency on functions and tables 145 | - integrated profiler 146 | - bug fixes (almost false alarms) 147 | 148 | * Wed Jun 6 2018 - Pavel STEHULE 1.2.3-1 149 | - PostgreSQL 11 support 150 | - detect hidden casts in expressions 151 | 152 | * Thu Oct 26 2017 - Pavel STEHULE 1.2.2-1 153 | - never read variables detection 154 | - fix false alarm on MOVE command 155 | 156 | * Fri Sep 15 2017 - Pavel STEHULE 1.2.1-1 157 | - missing RETURN detection 158 | - fix some bugs and false alarms 159 | - PostgreSQL 11 support 160 | 161 | * Fri Nov 11 2016 - Pavel STEHULE 1.2.0-1 162 | - support extra warnings - shadowed variables 163 | 164 | * Thu Aug 25 2016 - Pavel STEHULE 1.0.5-1 165 | - minor fixes, support for PostgreSQL 10 166 | 167 | * Fri Apr 15 2016 - Pavel STEHULE 1.0.4-1 168 | - support for PostgreSQL 9.6 169 | 170 | * Mon Oct 12 2015 - Pavel STEHULE 1.0.3-1 171 | - fix false alarms of unused cursor variables 172 | - fix regress tests 173 | 174 | * Thu Jul 09 2015 - Pavel STEHULE 1.0.2-2 175 | - bugfix release 176 | 177 | * Fri Dec 19 2014 - Pavel STEHULE 0.9.3-1 178 | - fix a broken record field type checking 179 | - add check for assign to array field 180 | 181 | * Mon Aug 25 2014 - Pavel STEHULE 0.9.1-1 182 | - Initial packaging 183 | -------------------------------------------------------------------------------- /postgresql18-plpgsql_check.spec: -------------------------------------------------------------------------------- 1 | %global pgmajorversion 18 2 | %global pginstdir /usr/pgsql-18 3 | %global sname plpgsql_check 4 | 5 | Name: %{sname}_%{pgmajorversion} 6 | Version: 2.8.1 7 | Release: 1%{?dist} 8 | Summary: Additional tools for plpgsql functions validation 9 | 10 | Group: Applications/Databases 11 | License: BSD 12 | URL: https://github.com/okbob/plpgsql_check/archive/v%{version}.zip 13 | Source0: plpgsql_check-%{version}.zip 14 | BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) 15 | 16 | BuildRequires: postgresql%{pgmajorversion}-devel 17 | Requires: postgresql%{pgmajorversion} 18 | 19 | %description 20 | The plpgsql_check is PostgreSQL extension with functionality for direct 21 | or indirect extra validation of functions in plpgsql language. It verifies 22 | a validity of SQL identifiers used in plpgsql code. It try to identify 23 | a performance issues. 24 | 25 | %prep 26 | %setup -q -n %{sname}-%{version} 27 | 28 | 29 | %build 30 | PATH="%{pginstdir}/bin;$PATH" ; export PATH 31 | CFLAGS="${CFLAGS:-%optflags}" ; export CFLAGS 32 | make USE_PGXS=1 PG_CONFIG=%{pginstdir}/bin/pg_config %{?_smp_mflags} 33 | 34 | %install 35 | rm -rf %{buildroot} 36 | make install DESTDIR=%{buildroot} PG_CONFIG=%{pginstdir}/bin/pg_config %{?_smp_mflags} 37 | 38 | %clean 39 | rm -rf %{buildroot} 40 | 41 | %files 42 | %defattr(644,root,root,755) 43 | %doc README.md 44 | %{pginstdir}/lib/plpgsql_check.so 45 | %{pginstdir}/share/extension/plpgsql_check--2.1.sql 46 | %{pginstdir}/share/extension/plpgsql_check.control 47 | %{pginstdir}/lib/bitcode/*.bc 48 | %{pginstdir}/lib/bitcode/plpgsql_check/src/*.bc 49 | %{pginstdir}/share/extension/*.control 50 | 51 | %changelog 52 | * Tue Mar 18 2025 - Pavel Stehule 2.8.0 53 | - remove support for PostgreSQL 12 and 13 54 | 55 | * Wed Dec 6 2023 - Pavel Stehule 2.7.0 56 | - unclosed cursors detection 57 | 58 | * Tue Oct 31 2023 - Pavel Stehule 2.6.0 59 | - simple constant tracing support 60 | 61 | * Sat Apr 29 2023 - Pavel Stehule 2.4.0 62 | - remove support for PostgreSQL 10 and 11 63 | 64 | * Wed Jan 11 2023 - Pavel Stehule 2.3.0 65 | - possibility to detect compatibility issues (obsolete setting of refcursor) 66 | 67 | * Tue Sep 20 2022 - Pavel Stehule 2.2.0 68 | - possibility to use in comment options 69 | 70 | * Wed Dec 29 2021 - Pavel Stehule 2.1.0 71 | - possibility to count statement's aborted execution 72 | - possibility to count "unfinished" statements due exception 73 | 74 | * Mon Sep 27 2021 - Pavel Stehule 2.0.0 75 | - pragma type for setting type to record variable 76 | - pragma table for creating ephemeral table 77 | 78 | * Mon Jun 21 2021 - Pavel Stehule 1.17.0 79 | - remove support for PostgreSQL 9.5 and 9.6 80 | 81 | * Sat Mar 6 2021 - Pavel Stehule 1.16.0 82 | - plpgsql_profiler_functions_all 83 | 84 | * Mon Nov 16 2020 - Pavel Stehule 1.14.0 85 | - queryid can be displayed in profiler's reports (Julien Rouhaud) 86 | - new profiler's GUC plpgsql_check.profiler_max_shared_chunks (Julien Rouhaud) 87 | - few minor bugfixes 88 | 89 | * Fri Aug 14 2020 - Pavel Stehule 1.13.0 90 | - tracer 91 | - pragma support to control checks, warnings and tracing 92 | 93 | * Thu Jul 2 2020 - Pavel STEHULE 1.11.0 94 | - possibility to check functions with arguments of polymorphic type 95 | - possibility to specify type used as real type instead polymorphic type 96 | 97 | * Fri Jun 05 2020 - Pavel STEHULE 1.10.0 98 | - deduction record type structure from result of polymorphic function 99 | 100 | * Mon Apr 27 2020 - Pavel STEHULE 1.9.1 101 | - minor bugfixes 102 | 103 | * Mon Mar 30 2020 - Pavel STEHULE 1.9.0 104 | - statement and branch coverage metrics 105 | - remove support for Postgres 9.4 106 | 107 | * Mon Jan 06 2020 - Pavel STEHULE 1.8.2 108 | - fix of compilation issue 109 | 110 | * Sun Jan 05 2020 - Pavel STEHULE 1.8.1 111 | - cleaner detection function oid from name or signature 112 | 113 | * Sun Dec 29 2019 - Pavel STEHULE 1.8.0 114 | - use Postgres tool for calling functions from plpgsql library instead dynamic linking 115 | - it solve issues related to dependency plpgsq_check on plpgsql 116 | 117 | * Mon Sep 23 2019 - Pavel STEHULE 1.7.6 118 | - fix false alarm - multiple plans in EXECUTE statement, and possible crash 119 | 120 | * Tue Sep 10 2019 - Pavel STEHULE 1.7.5 121 | - allow some work on tables with rules 122 | 123 | * Wed Jul 24 2019 - Pavel STEHULE 1.7.3 124 | - profiler bugfixes 125 | 126 | * Tue May 21 2019 - Pavel STEHULE 1.7.2 127 | - profiler bugfixes 128 | 129 | * Fri Apr 26 2019 - Pavel STEHULE 1.7.1 130 | - bugfixes 131 | 132 | * Wed Apr 17 2019 - Pavel STEHULE 1.7.0 133 | - check of format of fmt string of "format" function 134 | - better check of dynamic SQL when it is const string 135 | - check of SQL injection vulnerability of stmt expression at EXECUTE stmt 136 | 137 | * Sun Dec 23 2018 - Pavel STEHULE 1.4.2-1 138 | - metada fix 139 | 140 | * Fri Dec 21 2018 - Pavel STEHULE 1.4.1-1 141 | - minor bugfixes 142 | 143 | * Sun Dec 2 2018 - Pavel STEHULE 1.4.0-1 144 | - possible to show function's dependency on functions and tables 145 | - integrated profiler 146 | - bug fixes (almost false alarms) 147 | 148 | * Wed Jun 6 2018 - Pavel STEHULE 1.2.3-1 149 | - PostgreSQL 11 support 150 | - detect hidden casts in expressions 151 | 152 | * Thu Oct 26 2017 - Pavel STEHULE 1.2.2-1 153 | - never read variables detection 154 | - fix false alarm on MOVE command 155 | 156 | * Fri Sep 15 2017 - Pavel STEHULE 1.2.1-1 157 | - missing RETURN detection 158 | - fix some bugs and false alarms 159 | - PostgreSQL 11 support 160 | 161 | * Fri Nov 11 2016 - Pavel STEHULE 1.2.0-1 162 | - support extra warnings - shadowed variables 163 | 164 | * Thu Aug 25 2016 - Pavel STEHULE 1.0.5-1 165 | - minor fixes, support for PostgreSQL 10 166 | 167 | * Fri Apr 15 2016 - Pavel STEHULE 1.0.4-1 168 | - support for PostgreSQL 9.6 169 | 170 | * Mon Oct 12 2015 - Pavel STEHULE 1.0.3-1 171 | - fix false alarms of unused cursor variables 172 | - fix regress tests 173 | 174 | * Thu Jul 09 2015 - Pavel STEHULE 1.0.2-2 175 | - bugfix release 176 | 177 | * Fri Dec 19 2014 - Pavel STEHULE 0.9.3-1 178 | - fix a broken record field type checking 179 | - add check for assign to array field 180 | 181 | * Mon Aug 25 2014 - Pavel STEHULE 0.9.1-1 182 | - Initial packaging 183 | -------------------------------------------------------------------------------- /sql/plpgsql_check_active-14.sql: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okbob/plpgsql_check/0206bebdaf8276c5748ba8f3b27aa75ef7a40eb1/sql/plpgsql_check_active-14.sql -------------------------------------------------------------------------------- /sql/plpgsql_check_active-15.sql: -------------------------------------------------------------------------------- 1 | LOAD 'plpgsql'; 2 | CREATE EXTENSION IF NOT EXISTS plpgsql_check; 3 | set client_min_messages to notice; 4 | 5 | create or replace function fxtest() 6 | returns void as $$ 7 | declare 8 | v_sqlstate text; 9 | v_message text; 10 | v_context text; 11 | begin 12 | get stacked diagnostics 13 | v_sqlstate = returned_sqlstate, 14 | v_message = message_text, 15 | v_context = pg_exception_context; 16 | end; 17 | $$ language plpgsql; 18 | 19 | select * from plpgsql_check_function('fxtest'); 20 | 21 | drop function fxtest(); 22 | 23 | create or replace procedure prtest() 24 | as $$ 25 | begin 26 | commit; 27 | end; 28 | $$ language plpgsql; 29 | 30 | select * from plpgsql_check_function('prtest'); --ok 31 | 32 | create or replace procedure prtest() 33 | as $$ 34 | begin 35 | begin 36 | begin 37 | commit; 38 | end; 39 | end; 40 | exception when others then 41 | raise; 42 | end; 43 | $$ language plpgsql; 44 | 45 | select * from plpgsql_check_function('prtest'); --error 46 | 47 | create or replace procedure prtest() 48 | as $$ 49 | begin 50 | raise exception 'error'; 51 | exception when others then 52 | begin 53 | begin 54 | commit; 55 | end; 56 | end; 57 | end; 58 | $$ language plpgsql; 59 | 60 | select * from plpgsql_check_function('prtest'); --ok 61 | 62 | drop procedure prtest(); 63 | 64 | create function return_constant_refcursor() returns refcursor as $$ 65 | declare 66 | rc constant refcursor; 67 | begin 68 | open rc for select a from rc_test; 69 | return rc; 70 | end 71 | $$ language plpgsql; 72 | 73 | create table rc_test(a int); 74 | 75 | select * from plpgsql_check_function('return_constant_refcursor'); 76 | 77 | drop table rc_test; 78 | drop function return_constant_refcursor(); 79 | 80 | create procedure p1(a int, out b int) 81 | as $$ 82 | begin 83 | b := a + 10; 84 | end; 85 | $$ language plpgsql; 86 | 87 | create function f1() 88 | returns void as $$ 89 | declare b constant int; 90 | begin 91 | call p1(10, b); 92 | end; 93 | $$ language plpgsql; 94 | 95 | select * from plpgsql_check_function('f1'); 96 | 97 | drop function f1(); 98 | drop procedure p1(int, int); 99 | 100 | create or replace function f1() 101 | returns int as $$ 102 | declare c constant int default 100; 103 | begin 104 | return c; 105 | end; 106 | $$ language plpgsql; 107 | 108 | -- should be ok 109 | select * from plpgsql_check_function('f1'); 110 | -------------------------------------------------------------------------------- /sql/plpgsql_check_active-16.sql: -------------------------------------------------------------------------------- 1 | LOAD 'plpgsql'; 2 | CREATE EXTENSION IF NOT EXISTS plpgsql_check; 3 | set client_min_messages to notice; 4 | 5 | create or replace function fxtest() 6 | returns void as $$ 7 | declare 8 | v_sqlstate text; 9 | v_message text; 10 | v_context text; 11 | begin 12 | get stacked diagnostics 13 | v_sqlstate = returned_sqlstate, 14 | v_message = message_text, 15 | v_context = pg_exception_context; 16 | end; 17 | $$ language plpgsql; 18 | 19 | select * from plpgsql_check_function('fxtest'); 20 | 21 | drop function fxtest(); 22 | 23 | create or replace procedure prtest() 24 | as $$ 25 | begin 26 | commit; 27 | end; 28 | $$ language plpgsql; 29 | 30 | select * from plpgsql_check_function('prtest'); --ok 31 | 32 | create or replace procedure prtest() 33 | as $$ 34 | begin 35 | begin 36 | begin 37 | commit; 38 | end; 39 | end; 40 | exception when others then 41 | raise; 42 | end; 43 | $$ language plpgsql; 44 | 45 | select * from plpgsql_check_function('prtest'); --error 46 | 47 | create or replace procedure prtest() 48 | as $$ 49 | begin 50 | raise exception 'error'; 51 | exception when others then 52 | begin 53 | begin 54 | commit; 55 | end; 56 | end; 57 | end; 58 | $$ language plpgsql; 59 | 60 | select * from plpgsql_check_function('prtest'); --ok 61 | 62 | drop procedure prtest(); 63 | 64 | create function return_constant_refcursor() returns refcursor as $$ 65 | declare 66 | rc constant refcursor; 67 | begin 68 | open rc for select a from rc_test; 69 | return rc; 70 | end 71 | $$ language plpgsql; 72 | 73 | create table rc_test(a int); 74 | 75 | select * from plpgsql_check_function('return_constant_refcursor'); 76 | 77 | drop table rc_test; 78 | drop function return_constant_refcursor(); 79 | 80 | create procedure p1(a int, out b int) 81 | as $$ 82 | begin 83 | b := a + 10; 84 | end; 85 | $$ language plpgsql; 86 | 87 | create function f1() 88 | returns void as $$ 89 | declare b constant int; 90 | begin 91 | call p1(10, b); 92 | end; 93 | $$ language plpgsql; 94 | 95 | select * from plpgsql_check_function('f1'); 96 | 97 | drop function f1(); 98 | drop procedure p1(int, int); 99 | 100 | create or replace function f1() 101 | returns int as $$ 102 | declare c constant int default 100; 103 | begin 104 | return c; 105 | end; 106 | $$ language plpgsql; 107 | 108 | -- should be ok 109 | select * from plpgsql_check_function('f1'); 110 | 111 | drop function f1(); 112 | 113 | -- do not raise false warning 114 | create or replace function test_function() 115 | returns text as $$ 116 | declare s text; 117 | begin 118 | get diagnostics s = PG_CONTEXT; 119 | return s; 120 | end; 121 | $$ language plpgsql; 122 | 123 | create or replace procedure test_procedure() 124 | as $$ 125 | begin 126 | null; 127 | end; 128 | $$ language plpgsql; 129 | 130 | -- should be without any warnings 131 | select * from plpgsql_check_function('test_function', performance_warnings=>true); 132 | select * from plpgsql_check_function('test_procedure', performance_warnings=>true); 133 | 134 | drop function test_function(); 135 | drop procedure test_procedure(); 136 | 137 | -- detect dependecy in CALL statement 138 | create or replace function fx1_dep(int) 139 | returns int as $$ 140 | begin 141 | return $1; 142 | end; 143 | $$ language plpgsql; 144 | 145 | create or replace procedure px1_dep(int) 146 | as $$ 147 | begin 148 | end; 149 | $$ language plpgsql; 150 | 151 | create or replace function test_function() 152 | returns void as $$ 153 | begin 154 | call px1_dep(fx1_dep(10)); 155 | end; 156 | $$ language plpgsql; 157 | 158 | select type, schema, name, params from plpgsql_show_dependency_tb('test_function'); 159 | 160 | drop function test_function(); 161 | drop procedure px1_dep(int); 162 | drop function fx1_dep(int); -------------------------------------------------------------------------------- /sql/plpgsql_check_active-17.sql: -------------------------------------------------------------------------------- 1 | LOAD 'plpgsql'; 2 | CREATE EXTENSION IF NOT EXISTS plpgsql_check; 3 | set client_min_messages to notice; 4 | 5 | create or replace function fxtest() 6 | returns void as $$ 7 | declare 8 | v_sqlstate text; 9 | v_message text; 10 | v_context text; 11 | begin 12 | get stacked diagnostics 13 | v_sqlstate = returned_sqlstate, 14 | v_message = message_text, 15 | v_context = pg_exception_context; 16 | end; 17 | $$ language plpgsql; 18 | 19 | select * from plpgsql_check_function('fxtest'); 20 | 21 | drop function fxtest(); 22 | 23 | create or replace procedure prtest() 24 | as $$ 25 | begin 26 | commit; 27 | end; 28 | $$ language plpgsql; 29 | 30 | select * from plpgsql_check_function('prtest'); --ok 31 | 32 | create or replace procedure prtest() 33 | as $$ 34 | begin 35 | begin 36 | begin 37 | commit; 38 | end; 39 | end; 40 | exception when others then 41 | raise; 42 | end; 43 | $$ language plpgsql; 44 | 45 | select * from plpgsql_check_function('prtest'); --error 46 | 47 | create or replace procedure prtest() 48 | as $$ 49 | begin 50 | raise exception 'error'; 51 | exception when others then 52 | begin 53 | begin 54 | commit; 55 | end; 56 | end; 57 | end; 58 | $$ language plpgsql; 59 | 60 | select * from plpgsql_check_function('prtest'); --ok 61 | 62 | drop procedure prtest(); 63 | 64 | create function return_constant_refcursor() returns refcursor as $$ 65 | declare 66 | rc constant refcursor; 67 | begin 68 | open rc for select a from rc_test; 69 | return rc; 70 | end 71 | $$ language plpgsql; 72 | 73 | create table rc_test(a int); 74 | 75 | select * from plpgsql_check_function('return_constant_refcursor'); 76 | 77 | drop table rc_test; 78 | drop function return_constant_refcursor(); 79 | 80 | create procedure p1(a int, out b int) 81 | as $$ 82 | begin 83 | b := a + 10; 84 | end; 85 | $$ language plpgsql; 86 | 87 | create function f1() 88 | returns void as $$ 89 | declare b constant int; 90 | begin 91 | call p1(10, b); 92 | end; 93 | $$ language plpgsql; 94 | 95 | select * from plpgsql_check_function('f1'); 96 | 97 | drop function f1(); 98 | drop procedure p1(int, int); 99 | 100 | create or replace function f1() 101 | returns int as $$ 102 | declare c constant int default 100; 103 | begin 104 | return c; 105 | end; 106 | $$ language plpgsql; 107 | 108 | -- should be ok 109 | select * from plpgsql_check_function('f1'); 110 | 111 | drop function f1(); 112 | 113 | -- do not raise false warning 114 | create or replace function test_function() 115 | returns text as $$ 116 | declare s text; 117 | begin 118 | get diagnostics s = PG_CONTEXT; 119 | return s; 120 | end; 121 | $$ language plpgsql; 122 | 123 | create or replace procedure test_procedure() 124 | as $$ 125 | begin 126 | null; 127 | end; 128 | $$ language plpgsql; 129 | 130 | -- should be without any warnings 131 | select * from plpgsql_check_function('test_function', performance_warnings=>true); 132 | select * from plpgsql_check_function('test_procedure', performance_warnings=>true); 133 | 134 | drop function test_function(); 135 | drop procedure test_procedure(); 136 | 137 | -- detect dependecy in CALL statement 138 | create or replace function fx1_dep(int) 139 | returns int as $$ 140 | begin 141 | return $1; 142 | end; 143 | $$ language plpgsql; 144 | 145 | create or replace procedure px1_dep(int) 146 | as $$ 147 | begin 148 | end; 149 | $$ language plpgsql; 150 | 151 | create or replace function test_function() 152 | returns void as $$ 153 | begin 154 | call px1_dep(fx1_dep(10)); 155 | end; 156 | $$ language plpgsql; 157 | 158 | select type, schema, name, params from plpgsql_show_dependency_tb('test_function'); 159 | 160 | drop function test_function(); 161 | drop procedure px1_dep(int); 162 | drop function fx1_dep(int); -------------------------------------------------------------------------------- /sql/plpgsql_check_active-18.sql: -------------------------------------------------------------------------------- 1 | LOAD 'plpgsql'; 2 | CREATE EXTENSION IF NOT EXISTS plpgsql_check; 3 | set client_min_messages to notice; 4 | 5 | create or replace function fxtest() 6 | returns void as $$ 7 | declare 8 | v_sqlstate text; 9 | v_message text; 10 | v_context text; 11 | begin 12 | get stacked diagnostics 13 | v_sqlstate = returned_sqlstate, 14 | v_message = message_text, 15 | v_context = pg_exception_context; 16 | end; 17 | $$ language plpgsql; 18 | 19 | select * from plpgsql_check_function('fxtest'); 20 | 21 | drop function fxtest(); 22 | 23 | create or replace procedure prtest() 24 | as $$ 25 | begin 26 | commit; 27 | end; 28 | $$ language plpgsql; 29 | 30 | select * from plpgsql_check_function('prtest'); --ok 31 | 32 | create or replace procedure prtest() 33 | as $$ 34 | begin 35 | begin 36 | begin 37 | commit; 38 | end; 39 | end; 40 | exception when others then 41 | raise; 42 | end; 43 | $$ language plpgsql; 44 | 45 | select * from plpgsql_check_function('prtest'); --error 46 | 47 | create or replace procedure prtest() 48 | as $$ 49 | begin 50 | raise exception 'error'; 51 | exception when others then 52 | begin 53 | begin 54 | commit; 55 | end; 56 | end; 57 | end; 58 | $$ language plpgsql; 59 | 60 | select * from plpgsql_check_function('prtest'); --ok 61 | 62 | drop procedure prtest(); 63 | 64 | create function return_constant_refcursor() returns refcursor as $$ 65 | declare 66 | rc constant refcursor; 67 | begin 68 | open rc for select a from rc_test; 69 | return rc; 70 | end 71 | $$ language plpgsql; 72 | 73 | create table rc_test(a int); 74 | 75 | select * from plpgsql_check_function('return_constant_refcursor'); 76 | 77 | drop table rc_test; 78 | drop function return_constant_refcursor(); 79 | 80 | create procedure p1(a int, out b int) 81 | as $$ 82 | begin 83 | b := a + 10; 84 | end; 85 | $$ language plpgsql; 86 | 87 | create function f1() 88 | returns void as $$ 89 | declare b constant int; 90 | begin 91 | call p1(10, b); 92 | end; 93 | $$ language plpgsql; 94 | 95 | select * from plpgsql_check_function('f1'); 96 | 97 | drop function f1(); 98 | drop procedure p1(int, int); 99 | 100 | create or replace function f1() 101 | returns int as $$ 102 | declare c constant int default 100; 103 | begin 104 | return c; 105 | end; 106 | $$ language plpgsql; 107 | 108 | -- should be ok 109 | select * from plpgsql_check_function('f1'); 110 | 111 | drop function f1(); 112 | 113 | -- do not raise false warning 114 | create or replace function test_function() 115 | returns text as $$ 116 | declare s text; 117 | begin 118 | get diagnostics s = PG_CONTEXT; 119 | return s; 120 | end; 121 | $$ language plpgsql; 122 | 123 | create or replace procedure test_procedure() 124 | as $$ 125 | begin 126 | null; 127 | end; 128 | $$ language plpgsql; 129 | 130 | -- should be without any warnings 131 | select * from plpgsql_check_function('test_function', performance_warnings=>true); 132 | select * from plpgsql_check_function('test_procedure', performance_warnings=>true); 133 | 134 | drop function test_function(); 135 | drop procedure test_procedure(); 136 | 137 | -- detect dependecy in CALL statement 138 | create or replace function fx1_dep(int) 139 | returns int as $$ 140 | begin 141 | return $1; 142 | end; 143 | $$ language plpgsql; 144 | 145 | create or replace procedure px1_dep(int) 146 | as $$ 147 | begin 148 | end; 149 | $$ language plpgsql; 150 | 151 | create or replace function test_function() 152 | returns void as $$ 153 | begin 154 | call px1_dep(fx1_dep(10)); 155 | end; 156 | $$ language plpgsql; 157 | 158 | select type, schema, name, params from plpgsql_show_dependency_tb('test_function'); 159 | 160 | drop function test_function(); 161 | drop procedure px1_dep(int); 162 | drop function fx1_dep(int); -------------------------------------------------------------------------------- /sql/plpgsql_check_passive-14.sql: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okbob/plpgsql_check/0206bebdaf8276c5748ba8f3b27aa75ef7a40eb1/sql/plpgsql_check_passive-14.sql -------------------------------------------------------------------------------- /sql/plpgsql_check_passive-15.sql: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okbob/plpgsql_check/0206bebdaf8276c5748ba8f3b27aa75ef7a40eb1/sql/plpgsql_check_passive-15.sql -------------------------------------------------------------------------------- /sql/plpgsql_check_passive-16.sql: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okbob/plpgsql_check/0206bebdaf8276c5748ba8f3b27aa75ef7a40eb1/sql/plpgsql_check_passive-16.sql -------------------------------------------------------------------------------- /sql/plpgsql_check_passive-17.sql: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okbob/plpgsql_check/0206bebdaf8276c5748ba8f3b27aa75ef7a40eb1/sql/plpgsql_check_passive-17.sql -------------------------------------------------------------------------------- /sql/plpgsql_check_passive-18.sql: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okbob/plpgsql_check/0206bebdaf8276c5748ba8f3b27aa75ef7a40eb1/sql/plpgsql_check_passive-18.sql -------------------------------------------------------------------------------- /sql/plpgsql_check_passive.sql: -------------------------------------------------------------------------------- 1 | load 'plpgsql'; 2 | load 'plpgsql_check'; 3 | set client_min_messages to notice; 4 | 5 | -- enforce context's displaying 6 | -- emulate pre 9.6 behave 7 | 8 | \set SHOW_CONTEXT always 9 | 10 | set plpgsql_check.mode = 'every_start'; 11 | 12 | create table t1(a int, b int); 13 | 14 | create function f1() 15 | returns void as $$ 16 | begin 17 | if false then 18 | update t1 set c = 30; 19 | end if; 20 | end; 21 | $$ language plpgsql; 22 | 23 | select f1(); 24 | 25 | drop function f1(); 26 | 27 | create function f1() 28 | returns void as $$ 29 | begin 30 | if false then 31 | insert into t1 values(10,20); 32 | update t1 set a = 10; 33 | delete from t1; 34 | end if; 35 | end; 36 | $$ language plpgsql stable; 37 | 38 | select f1(); 39 | 40 | drop function f1(); 41 | 42 | create function g1(out a int, out b int) 43 | as $$ 44 | select 10,20; 45 | $$ language sql; 46 | 47 | create function f1() 48 | returns void as $$ 49 | declare r record; 50 | begin 51 | r := g1(); 52 | if false then 53 | raise notice '%', r.c; 54 | end if; 55 | end; 56 | $$ language plpgsql; 57 | 58 | select f1(); 59 | 60 | drop function f1(); 61 | drop function g1(); 62 | 63 | create function g1(out a int, out b int) 64 | returns setof record as $$ 65 | select * from t1; 66 | $$ language sql; 67 | 68 | create function f1() 69 | returns void as $$ 70 | declare r record; 71 | begin 72 | for r in select * from g1() 73 | loop 74 | raise notice '%', r.c; 75 | end loop; 76 | end; 77 | $$ language plpgsql; 78 | 79 | select f1(); 80 | 81 | create or replace function f1() 82 | returns void as $$ 83 | declare r record; 84 | begin 85 | for r in select * from g1() 86 | loop 87 | r.c := 20; 88 | end loop; 89 | end; 90 | $$ language plpgsql; 91 | 92 | select f1(); 93 | 94 | drop function f1(); 95 | drop function g1(); 96 | 97 | create function f1() 98 | returns int as $$ 99 | declare r int; 100 | begin 101 | if false then 102 | r := a + b; 103 | end if; 104 | return r; 105 | end; 106 | $$ language plpgsql; 107 | 108 | select f1(); 109 | 110 | drop function f1(); 111 | 112 | create or replace function f1() 113 | returns void as $$ 114 | declare r int[]; 115 | begin 116 | if false then 117 | r[c+10] := 20; 118 | end if; 119 | end; 120 | $$ language plpgsql; 121 | 122 | select f1(); 123 | 124 | drop function f1(); 125 | 126 | 127 | create or replace function f1() 128 | returns void as $$ 129 | declare r int; 130 | begin 131 | if false then 132 | r[10] := 20; 133 | end if; 134 | end; 135 | $$ language plpgsql; 136 | 137 | select f1(); 138 | 139 | drop function f1(); 140 | 141 | create or replace function f1() 142 | returns void as $$ 143 | begin 144 | if false then 145 | insert into badbadtable values(10,20); 146 | end if; 147 | return; 148 | end; 149 | $$ language plpgsql; 150 | 151 | set plpgsql_check.mode = 'fresh_start'; 152 | 153 | select f1(); 154 | -- should not raise exception there 155 | select f1(); 156 | 157 | create or replace function f1() 158 | returns void as $$ 159 | begin 160 | if false then 161 | insert into badbadtable values(10,20); 162 | end if; 163 | return; 164 | end; 165 | $$ language plpgsql; 166 | 167 | -- after refreshing it should to raise exception again 168 | select f1(); 169 | 170 | set plpgsql_check.mode = 'every_start'; 171 | 172 | -- should to raise warning only 173 | set plpgsql_check.fatal_errors = false; 174 | select f1(); 175 | 176 | drop function f1(); 177 | 178 | create function f1() 179 | returns setof t1 as $$ 180 | begin 181 | if false then 182 | return query select a,a,a from t1; 183 | return; 184 | end if; 185 | end; 186 | $$ language plpgsql; 187 | 188 | select * from f1(); 189 | 190 | drop function f1(); 191 | 192 | create function f1() 193 | returns setof t1 as $$ 194 | begin 195 | if false then 196 | return query select a, b::numeric from t1; 197 | return; 198 | end if; 199 | end; 200 | $$ language plpgsql; 201 | 202 | select * from f1(); 203 | 204 | drop function f1(); 205 | 206 | drop table t1; 207 | 208 | do $$ 209 | declare 210 | begin 211 | if false then 212 | for i in 1,3..(2) loop 213 | raise notice 'foo %', i; 214 | end loop; 215 | end if; 216 | end; 217 | $$; 218 | 219 | -- tests designed for 9.2 220 | set check_function_bodies to off; 221 | 222 | create or replace function f1() 223 | returns void as $$ 224 | begin 225 | if false then 226 | raise notice '%', 1, 2; 227 | end if; 228 | end; 229 | $$ language plpgsql; 230 | 231 | select f1(); 232 | 233 | drop function f1(); 234 | 235 | create or replace function f1() 236 | returns void as $$ 237 | begin 238 | if false then 239 | raise notice '% %'; 240 | end if; 241 | end; 242 | $$ language plpgsql; 243 | 244 | select f1(); 245 | 246 | drop function f1(); 247 | 248 | create type _exception_type as ( 249 | state text, 250 | message text, 251 | detail text); 252 | 253 | create or replace function f1() 254 | returns void as $$ 255 | declare 256 | _exception record; 257 | begin 258 | _exception := NULL::_exception_type; 259 | exception when others then 260 | get stacked diagnostics 261 | _exception.state = RETURNED_SQLSTATE, 262 | _exception.message = MESSAGE_TEXT, 263 | _exception.detail = PG_EXCEPTION_DETAIL, 264 | _exception.hint = PG_EXCEPTION_HINT; 265 | end; 266 | $$ language plpgsql; 267 | 268 | select f1(); 269 | 270 | drop function f1(); 271 | drop type _exception_type; 272 | 273 | create table footab(a int, b int, c int); 274 | 275 | create or replace function footab_trig_func() 276 | returns trigger as $$ 277 | declare x int; 278 | begin 279 | if false then 280 | -- should be ok; 281 | select count(*) from newtab into x; 282 | 283 | -- should fail; 284 | select count(*) from newtab where d = 10 into x; 285 | end if; 286 | return null; 287 | end; 288 | $$ language plpgsql; 289 | 290 | create trigger footab_trigger 291 | after insert on footab 292 | referencing new table as newtab 293 | for each statement execute procedure footab_trig_func(); 294 | 295 | -- should to fail 296 | insert into footab values(1,2,3); 297 | 298 | create or replace function footab_trig_func() 299 | returns trigger as $$ 300 | declare x int; 301 | begin 302 | if false then 303 | -- should be ok; 304 | select count(*) from newtab into x; 305 | end if; 306 | return null; 307 | end; 308 | $$ language plpgsql; 309 | 310 | -- should be ok 311 | insert into footab values(1,2,3); 312 | 313 | drop table footab; 314 | drop function footab_trig_func(); 315 | 316 | set plpgsql_check.mode = 'every_start'; 317 | 318 | create or replace procedure proc_test() 319 | as $$ 320 | begin 321 | commit; 322 | end; 323 | $$ language plpgsql; 324 | 325 | call proc_test(); 326 | 327 | drop procedure proc_test(); 328 | 329 | -- should not to crash 330 | set plpgsql_check.mode = 'fresh_start'; 331 | do $$ 332 | begin 333 | end; 334 | $$; 335 | -------------------------------------------------------------------------------- /src/catalog.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * catalog.c 4 | * 5 | * routines for working with Postgres's catalog and caches 6 | * 7 | * by Pavel Stehule 2013-2025 8 | * 9 | *------------------------------------------------------------------------- 10 | */ 11 | 12 | #include "plpgsql_check.h" 13 | 14 | #include "catalog/pg_extension.h" 15 | #include "catalog/pg_language.h" 16 | #include "catalog/pg_operator.h" 17 | #include "catalog/pg_proc.h" 18 | #include "catalog/pg_type.h" 19 | #include "commands/extension.h" 20 | #include "commands/proclang.h" 21 | #include "utils/builtins.h" 22 | #include "utils/catcache.h" 23 | #include "utils/fmgroids.h" 24 | #include "utils/lsyscache.h" 25 | #include "utils/syscache.h" 26 | #include "utils/regproc.h" 27 | 28 | #include "catalog/pg_proc.h" 29 | #include "utils/syscache.h" 30 | 31 | /* 32 | * Pre pg 18 doesn't support system cache for pg_extension, 33 | * we need to manipulate with table on low level. 34 | */ 35 | #if PG_VERSION_NUM < 180000 36 | 37 | #include "access/genam.h" 38 | 39 | #include "access/htup_details.h" 40 | #include "access/table.h" 41 | #include "catalog/indexing.h" 42 | #include "utils/rel.h" 43 | 44 | #endif 45 | 46 | static Oid plpgsql_check_PLpgSQLlanguageId = InvalidOid; 47 | 48 | /* 49 | * Prepare metadata necessary for plpgsql_check 50 | */ 51 | void 52 | plpgsql_check_get_function_info(plpgsql_check_info *cinfo) 53 | { 54 | Form_pg_proc proc; 55 | char functyptype; 56 | 57 | proc = (Form_pg_proc) GETSTRUCT(cinfo->proctuple); 58 | 59 | functyptype = get_typtype(proc->prorettype); 60 | 61 | cinfo->trigtype = PLPGSQL_NOT_TRIGGER; 62 | cinfo->is_procedure = proc->prokind == PROKIND_PROCEDURE; 63 | 64 | /* 65 | * Disallow pseudotype result except for TRIGGER, RECORD, VOID, or 66 | * polymorphic 67 | */ 68 | if (functyptype == TYPTYPE_PSEUDO) 69 | { 70 | /* we assume OPAQUE with no arguments means a trigger */ 71 | if (proc->prorettype == TRIGGEROID) 72 | cinfo->trigtype = PLPGSQL_DML_TRIGGER; 73 | else if (proc->prorettype == EVENT_TRIGGEROID) 74 | cinfo->trigtype = PLPGSQL_EVENT_TRIGGER; 75 | else if (proc->prorettype != RECORDOID && 76 | proc->prorettype != VOIDOID && 77 | !IsPolymorphicType(proc->prorettype)) 78 | ereport(ERROR, 79 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), 80 | errmsg("PL/pgSQL functions cannot return type %s", 81 | format_type_be(proc->prorettype)))); 82 | } 83 | 84 | 85 | cinfo->volatility = proc->provolatile; 86 | cinfo->rettype = proc->prorettype; 87 | } 88 | 89 | char * 90 | plpgsql_check_get_src(HeapTuple procTuple) 91 | { 92 | Datum prosrcdatum; 93 | bool isnull; 94 | 95 | prosrcdatum = SysCacheGetAttr(PROCOID, procTuple, 96 | Anum_pg_proc_prosrc, &isnull); 97 | if (isnull) 98 | elog(ERROR, "null prosrc"); 99 | 100 | return TextDatumGetCString(prosrcdatum); 101 | } 102 | 103 | /* 104 | * Process necessary checking before code checking 105 | * a) disallow other than plpgsql check function, 106 | * b) when function is trigger function, then reloid must be defined 107 | */ 108 | void 109 | plpgsql_check_precheck_conditions(plpgsql_check_info *cinfo) 110 | { 111 | Form_pg_proc proc; 112 | char *funcname; 113 | 114 | proc = (Form_pg_proc) GETSTRUCT(cinfo->proctuple); 115 | funcname = format_procedure(cinfo->fn_oid); 116 | 117 | /* 118 | * The plpgsql_check can be loaded by shared_proload_libraries. That means 119 | * so in init time the access to system catalog can be impossible. So 120 | * plpgsql_check_PLpgSQLlanguageId should be initialized here. 121 | */ 122 | if (!OidIsValid(plpgsql_check_PLpgSQLlanguageId)) 123 | plpgsql_check_PLpgSQLlanguageId = get_language_oid("plpgsql", false); 124 | 125 | /* used language must be plpgsql */ 126 | if (proc->prolang != plpgsql_check_PLpgSQLlanguageId) 127 | ereport(ERROR, 128 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), 129 | errmsg("%s is not a plpgsql function", funcname))); 130 | 131 | /* profiler doesn't require trigger data check */ 132 | if (!cinfo->show_profile) 133 | { 134 | /* dml trigger needs valid relid, others not */ 135 | if (cinfo->trigtype == PLPGSQL_DML_TRIGGER) 136 | { 137 | if (!OidIsValid(cinfo->relid)) 138 | ereport(ERROR, 139 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), 140 | errmsg("missing trigger relation"), 141 | errhint("Trigger relation oid must be valid"))); 142 | } 143 | else 144 | { 145 | if (OidIsValid(cinfo->relid)) 146 | ereport(ERROR, 147 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), 148 | errmsg("function is not trigger"), 149 | errhint("Trigger relation oid must not be valid for non dml trigger function."))); 150 | } 151 | } 152 | 153 | pfree(funcname); 154 | } 155 | 156 | #if PG_VERSION_NUM < 160000 157 | 158 | /* 159 | * plpgsql_check_get_extension_schema - given an extension OID, fetch its extnamespace 160 | * 161 | * Returns InvalidOid if no such extension. 162 | */ 163 | static Oid 164 | get_extension_schema(Oid ext_oid) 165 | { 166 | Oid result; 167 | Relation rel; 168 | SysScanDesc scandesc; 169 | HeapTuple tuple; 170 | ScanKeyData entry[1]; 171 | 172 | rel = table_open(ExtensionRelationId, AccessShareLock); 173 | 174 | ScanKeyInit(&entry[0], 175 | Anum_pg_extension_oid, 176 | BTEqualStrategyNumber, F_OIDEQ, 177 | ObjectIdGetDatum(ext_oid)); 178 | 179 | scandesc = systable_beginscan(rel, ExtensionOidIndexId, true, 180 | NULL, 1, entry); 181 | 182 | tuple = systable_getnext(scandesc); 183 | 184 | /* We assume that there can be at most one matching tuple */ 185 | if (HeapTupleIsValid(tuple)) 186 | result = ((Form_pg_extension) GETSTRUCT(tuple))->extnamespace; 187 | else 188 | result = InvalidOid; 189 | 190 | systable_endscan(scandesc); 191 | 192 | table_close(rel, AccessShareLock); 193 | 194 | return result; 195 | } 196 | 197 | #endif 198 | 199 | #if PG_VERSION_NUM < 180000 200 | 201 | /* 202 | * get_extension_version - given an extension OID, look up the version 203 | * 204 | * Returns a palloc'd string, or NULL if no such extension. 205 | */ 206 | char * 207 | get_extension_version(Oid ext_oid) 208 | { 209 | char *result; 210 | Relation rel; 211 | SysScanDesc scandesc; 212 | HeapTuple tuple; 213 | ScanKeyData entry[1]; 214 | 215 | rel = table_open(ExtensionRelationId, AccessShareLock); 216 | 217 | ScanKeyInit(&entry[0], 218 | Anum_pg_extension_oid, 219 | BTEqualStrategyNumber, F_OIDEQ, 220 | ObjectIdGetDatum(ext_oid)); 221 | 222 | scandesc = systable_beginscan(rel, ExtensionOidIndexId, true, 223 | NULL, 1, entry); 224 | 225 | tuple = systable_getnext(scandesc); 226 | 227 | /* We assume that there can be at most one matching tuple */ 228 | if (HeapTupleIsValid(tuple)) 229 | { 230 | Datum datum; 231 | bool isnull; 232 | 233 | datum = heap_getattr(tuple, Anum_pg_extension_extversion, 234 | RelationGetDescr(rel), &isnull); 235 | 236 | if (isnull) 237 | elog(ERROR, "extversion is null"); 238 | 239 | result = text_to_cstring(DatumGetTextPP(datum)); 240 | } 241 | else 242 | result = NULL; 243 | 244 | systable_endscan(scandesc); 245 | 246 | table_close(rel, AccessShareLock); 247 | 248 | return result; 249 | } 250 | 251 | #else 252 | 253 | /* 254 | * Returns a palloc'd string, or NULL if no such extension. 255 | * Use extension syscache from PostgreSQL 18+ 256 | */ 257 | char * 258 | get_extension_version2(Oid ext_oid) 259 | { 260 | HeapTuple extTuple; 261 | Datum extversiondatum; 262 | char *result; 263 | bool isnull; 264 | 265 | extTuple = SearchSysCache1(EXTENSIONOID, ObjectIdGetDatum(ext_oid)); 266 | if (!HeapTupleIsValid(extTuple)) 267 | elog(ERROR, "cache lookup failed for extension %u", ext_oid); 268 | 269 | extversiondatum = SysCacheGetAttr(EXTENSIONOID, extTuple, 270 | Anum_pg_extension_extversion, &isnull); 271 | 272 | if (isnull) 273 | elog(ERROR, "extversion is null"); 274 | 275 | result = TextDatumGetCString(extversiondatum); 276 | 277 | ReleaseSysCache(extTuple); 278 | 279 | return result; 280 | } 281 | 282 | #endif 283 | 284 | /* 285 | * Returns oid of pragma function. It is used for elimination 286 | * pragma function from volatility tests. 287 | */ 288 | Oid 289 | plpgsql_check_pragma_func_oid(void) 290 | { 291 | Oid result = InvalidOid; 292 | Oid extoid; 293 | 294 | extoid = get_extension_oid("plpgsql_check", true); 295 | 296 | if (OidIsValid(extoid)) 297 | { 298 | CatCList *catlist; 299 | Oid schemaoid; 300 | int i; 301 | 302 | schemaoid = get_extension_schema(extoid); 303 | 304 | /* Search syscache by name only */ 305 | catlist = SearchSysCacheList1(PROCNAMEARGSNSP, CStringGetDatum("plpgsql_check_pragma")); 306 | 307 | for (i = 0; i < catlist->n_members; i++) 308 | { 309 | HeapTuple proctup = &catlist->members[i]->tuple; 310 | Form_pg_proc procform = (Form_pg_proc) GETSTRUCT(proctup); 311 | 312 | /* Consider only procs in specified namespace */ 313 | if (procform->pronamespace != schemaoid) 314 | continue; 315 | 316 | result = procform->oid; 317 | break; 318 | } 319 | 320 | ReleaseSysCacheList(catlist); 321 | } 322 | 323 | return result; 324 | } 325 | 326 | /* 327 | * Returns true, if function specified by oid is plpgsql function. 328 | */ 329 | bool 330 | plpgsql_check_is_plpgsql_function(Oid foid) 331 | { 332 | HeapTuple procTuple; 333 | Form_pg_proc procStruct; 334 | bool result; 335 | 336 | procTuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(foid)); 337 | if (!HeapTupleIsValid(procTuple)) 338 | return false; 339 | 340 | procStruct = (Form_pg_proc) GETSTRUCT(procTuple); 341 | 342 | /* 343 | * The plpgsql_check can be loaded by shared_proload_libraries. That means 344 | * so in init time the access to system catalog can be impossible. So 345 | * plpgsql_check_PLpgSQLlanguageId should be initialized here. 346 | */ 347 | if (!OidIsValid(plpgsql_check_PLpgSQLlanguageId)) 348 | plpgsql_check_PLpgSQLlanguageId = get_language_oid("plpgsql", false); 349 | 350 | result = procStruct->prolang == plpgsql_check_PLpgSQLlanguageId; 351 | 352 | ReleaseSysCache(procTuple); 353 | 354 | return result; 355 | } 356 | 357 | /* 358 | * plpgsql_check_get_op_namespace 359 | * returns the name space of the operator with the given opno 360 | */ 361 | Oid 362 | plpgsql_check_get_op_namespace(Oid opno) 363 | { 364 | HeapTuple tp; 365 | 366 | tp = SearchSysCache1(OPEROID, ObjectIdGetDatum(opno)); 367 | if (HeapTupleIsValid(tp)) 368 | { 369 | Form_pg_operator optup = (Form_pg_operator) GETSTRUCT(tp); 370 | ReleaseSysCache(tp); 371 | return optup->oprnamespace; 372 | } 373 | else 374 | return InvalidOid; 375 | } 376 | -------------------------------------------------------------------------------- /src/cursors_leaks.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * cursors_leak.c 4 | * 5 | * detection unclosed cursors code 6 | * 7 | * by Pavel Stehule 2013-2025 8 | * 9 | *------------------------------------------------------------------------- 10 | */ 11 | 12 | #include "plpgsql_check.h" 13 | #include "plpgsql_check_builtins.h" 14 | 15 | #include "storage/proc.h" 16 | #include "utils/builtins.h" 17 | #include "utils/guc.h" 18 | #include "utils/memutils.h" 19 | 20 | #if PG_VERSION_NUM >= 180000 21 | 22 | #include "utils/funccache.h" 23 | 24 | #endif 25 | 26 | bool plpgsql_check_cursors_leaks = true; 27 | bool plpgsql_check_cursors_leaks_strict = false; 28 | int plpgsql_check_cursors_leaks_level = WARNING; 29 | 30 | 31 | #define MAX_NAMES_PER_STATEMENT 20 32 | 33 | typedef struct 34 | { 35 | int stmtid; 36 | int rec_level; 37 | char *curname; 38 | } CursorTrace; 39 | 40 | typedef struct 41 | { 42 | Oid fn_oid; 43 | TransactionId fn_xmin; 44 | } FunctionTraceKey; 45 | 46 | typedef struct 47 | { 48 | FunctionTraceKey key; 49 | 50 | int ncursors; 51 | int cursors_size; 52 | CursorTrace *cursors_traces; 53 | } FunctionTrace; 54 | 55 | typedef struct 56 | { 57 | FunctionTrace *ftrace; 58 | LocalTransactionId lxid; 59 | } CursorLeaksPlugin2Info; 60 | 61 | static LocalTransactionId traces_lxid = InvalidLocalTransactionId; 62 | static HTAB *traces = NULL; 63 | static MemoryContext traces_mcxt = NULL; 64 | 65 | static void func_setup(PLpgSQL_execstate *estate, PLpgSQL_function *func, void **plugin2_info); 66 | static void func_end(PLpgSQL_execstate *estate, PLpgSQL_function *func, void **plugin2_info); 67 | static void stmt_end(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt, void **plugin2_info); 68 | 69 | static plpgsql_check_plugin2 cursors_leaks_plugin2 = { func_setup, NULL, func_end, NULL, 70 | NULL, stmt_end, NULL, NULL, NULL, NULL, NULL, NULL }; 71 | 72 | #if PG_VERSION_NUM >= 170000 73 | 74 | #define CURRENT_LXID (MyProc->vxid.lxid) 75 | 76 | #else 77 | 78 | #define CURRENT_LXID (MyProc->lxid) 79 | 80 | #endif 81 | 82 | static FunctionTrace * 83 | get_function_trace(PLpgSQL_function *func) 84 | { 85 | bool found; 86 | FunctionTrace *ftrace; 87 | FunctionTraceKey key; 88 | 89 | if (traces == NULL || traces_lxid != CURRENT_LXID) 90 | { 91 | HASHCTL ctl; 92 | 93 | traces_mcxt = AllocSetContextCreate(TopTransactionContext, 94 | "plpgsql_check - trace cursors", 95 | ALLOCSET_DEFAULT_SIZES); 96 | 97 | memset(&ctl, 0, sizeof(ctl)); 98 | ctl.keysize = sizeof(FunctionTraceKey); 99 | ctl.entrysize = sizeof(FunctionTrace); 100 | ctl.hcxt = traces_mcxt; 101 | 102 | traces = hash_create("plpgsql_checj - cursors leaks detection", 103 | FUNCS_PER_USER, 104 | &ctl, 105 | HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); 106 | 107 | traces_lxid = CURRENT_LXID; 108 | } 109 | 110 | key.fn_oid = func->fn_oid; 111 | 112 | #if PG_VERSION_NUM >= 180000 113 | 114 | key.fn_xmin = func->cfunc.fn_xmin; 115 | 116 | #else 117 | 118 | key.fn_xmin = func->fn_xmin; 119 | 120 | #endif 121 | 122 | ftrace = (FunctionTrace *) hash_search(traces, 123 | (void *) &key, 124 | HASH_ENTER, 125 | &found); 126 | 127 | if (!found) 128 | { 129 | ftrace->key.fn_oid = func->fn_oid; 130 | 131 | #if PG_VERSION_NUM >= 180000 132 | 133 | ftrace->key.fn_xmin = func->cfunc.fn_xmin; 134 | 135 | #else 136 | 137 | ftrace->key.fn_xmin = func->fn_xmin; 138 | 139 | #endif 140 | 141 | ftrace->ncursors = 0; 142 | ftrace->cursors_size = 0; 143 | ftrace->cursors_traces = NULL; 144 | } 145 | 146 | return ftrace; 147 | } 148 | 149 | 150 | static void 151 | func_setup(PLpgSQL_execstate *estate, PLpgSQL_function *func, void **plugin2_info) 152 | { 153 | if (plpgsql_check_cursors_leaks) 154 | { 155 | CursorLeaksPlugin2Info *pinfo; 156 | MemoryContext fn_mcxt; 157 | 158 | fn_mcxt = plpgsql_check_get_current_fn_mcxt(); 159 | pinfo = MemoryContextAlloc(fn_mcxt, sizeof(CursorLeaksPlugin2Info)); 160 | 161 | pinfo->ftrace = get_function_trace(func); 162 | pinfo->lxid = CURRENT_LXID; 163 | 164 | *plugin2_info = pinfo; 165 | } 166 | else 167 | *plugin2_info = NULL; 168 | } 169 | 170 | static void 171 | func_end(PLpgSQL_execstate *estate, 172 | PLpgSQL_function *func, 173 | void **plugin2_info) 174 | { 175 | CursorLeaksPlugin2Info *pinfo = *plugin2_info; 176 | FunctionTrace *ftrace; 177 | int i; 178 | 179 | if (!pinfo || pinfo->lxid != CURRENT_LXID) 180 | return; 181 | 182 | ftrace = pinfo->ftrace; 183 | 184 | for (i = 0; i < ftrace->ncursors; i++) 185 | { 186 | CursorTrace *ct = &ftrace->cursors_traces[i]; 187 | 188 | /* 189 | * Iterate over traced cursors. Remove slots for tracing 190 | * immediately, when traced cursor is closed already. 191 | */ 192 | #if PG_VERSION_NUM >= 180000 193 | 194 | if (ct->curname && ct->rec_level == func->cfunc.use_count) 195 | 196 | #else 197 | 198 | if (ct->curname && ct->rec_level == func->use_count) 199 | 200 | #endif 201 | 202 | { 203 | if (SPI_cursor_find(ct->curname)) 204 | { 205 | if (plpgsql_check_cursors_leaks_strict) 206 | { 207 | char *context; 208 | 209 | context = GetErrorContextStack(); 210 | 211 | ereport(plpgsql_check_cursors_leaks_level, 212 | errcode(ERRCODE_INVALID_CURSOR_STATE), 213 | errmsg("cursor is not closed"), 214 | errdetail("%s", context)); 215 | pfree(context); 216 | 217 | pfree(ct->curname); 218 | ct->stmtid = -1; 219 | ct->curname = NULL; 220 | } 221 | } 222 | else 223 | { 224 | pfree(ct->curname); 225 | ct->stmtid = -1; 226 | ct->curname = NULL; 227 | } 228 | } 229 | } 230 | } 231 | 232 | static void 233 | stmt_end(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt, void **plugin2_info) 234 | { 235 | CursorLeaksPlugin2Info *pinfo = *plugin2_info; 236 | FunctionTrace *ftrace; 237 | 238 | if (!pinfo) 239 | return; 240 | 241 | if (traces_lxid != CURRENT_LXID || 242 | pinfo->lxid != CURRENT_LXID) 243 | { 244 | pinfo->ftrace = get_function_trace(estate->func); 245 | pinfo->lxid = CURRENT_LXID; 246 | } 247 | 248 | ftrace = pinfo->ftrace; 249 | 250 | if (stmt->cmd_type == PLPGSQL_STMT_OPEN) 251 | { 252 | int i; 253 | int cursors_for_current_stmt = 0; 254 | int free_slot = -1; 255 | PLpgSQL_var *curvar; 256 | char *curname; 257 | 258 | curvar = (PLpgSQL_var *) (estate->datums[((PLpgSQL_stmt_open *) stmt)->curvar]); 259 | 260 | Assert(!curvar->isnull); 261 | curname = TextDatumGetCString(curvar->value); 262 | 263 | for (i = 0; i < ftrace->ncursors; i++) 264 | { 265 | CursorTrace *ct = &ftrace->cursors_traces[i]; 266 | 267 | if (ct->curname && ct->stmtid == stmt->stmtid) 268 | { 269 | /* 270 | * PLpgSQL open statements reuses portal name and does check 271 | * already used portal with already used portal name. So when 272 | * the traced name and name in cursor variable is same, we should 273 | * not to do this check. This eliminate false alarms. 274 | */ 275 | if (strcmp(curname, ct->curname) == 0) 276 | { 277 | pfree(curname); 278 | return; 279 | } 280 | 281 | if (SPI_cursor_find(ct->curname)) 282 | { 283 | #if PG_VERSION_NUM >= 180000 284 | 285 | if (estate->func->cfunc.use_count == 1 && !plpgsql_check_cursors_leaks_strict) 286 | 287 | #else 288 | 289 | if (estate->func->use_count == 1 && !plpgsql_check_cursors_leaks_strict) 290 | 291 | #endif 292 | 293 | { 294 | char *context; 295 | 296 | context = GetErrorContextStack(); 297 | 298 | ereport(plpgsql_check_cursors_leaks_level, 299 | errcode(ERRCODE_INVALID_CURSOR_STATE), 300 | errmsg("cursor \"%s\" is not closed", curvar->refname), 301 | errdetail("%s", context)); 302 | 303 | pfree(context); 304 | 305 | pfree(ct->curname); 306 | ct->stmtid = -1; 307 | ct->curname = NULL; 308 | } 309 | else 310 | { 311 | cursors_for_current_stmt += 1; 312 | } 313 | } 314 | else 315 | { 316 | pfree(ct->curname); 317 | ct->stmtid = -1; 318 | ct->curname = NULL; 319 | } 320 | } 321 | 322 | if (ct->stmtid == -1 && free_slot == -1) 323 | free_slot = i; 324 | } 325 | 326 | if (cursors_for_current_stmt < MAX_NAMES_PER_STATEMENT) 327 | { 328 | MemoryContext oldcxt; 329 | CursorTrace *ct = NULL; 330 | 331 | oldcxt = MemoryContextSwitchTo(traces_mcxt); 332 | 333 | if (free_slot != -1) 334 | ct = &ftrace->cursors_traces[free_slot]; 335 | else 336 | { 337 | if (ftrace->ncursors == ftrace->cursors_size) 338 | { 339 | if (ftrace->cursors_size > 0) 340 | { 341 | ftrace->cursors_size += 10; 342 | ftrace->cursors_traces = repalloc_array(ftrace->cursors_traces, 343 | CursorTrace, 344 | ftrace->cursors_size); 345 | } 346 | else 347 | { 348 | ftrace->cursors_size = 10; 349 | ftrace->cursors_traces = palloc_array(CursorTrace, 350 | ftrace->cursors_size); 351 | } 352 | } 353 | 354 | ct = &ftrace->cursors_traces[ftrace->ncursors++]; 355 | } 356 | 357 | ct->stmtid = stmt->stmtid; 358 | 359 | #if PG_VERSION_NUM >= 180000 360 | 361 | ct->rec_level = estate->func->cfunc.use_count; 362 | 363 | #else 364 | 365 | ct->rec_level = estate->func->use_count; 366 | 367 | #endif 368 | 369 | ct->curname = pstrdup(curname); 370 | 371 | MemoryContextSwitchTo(oldcxt); 372 | } 373 | 374 | pfree(curname); 375 | } 376 | } 377 | 378 | void 379 | plpgsql_check_cursors_leaks_init(void) 380 | { 381 | plpgsql_check_register_pldbgapi2_plugin(&cursors_leaks_plugin2); 382 | } 383 | -------------------------------------------------------------------------------- /src/plpgsql_check.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * plpgsql_check.c 4 | * 5 | * enhanced checks for plpgsql functions 6 | * 7 | * by Pavel Stehule 2013-2025 8 | * 9 | *------------------------------------------------------------------------- 10 | * 11 | * Notes: 12 | * 13 | * 1) Secondary hash table for function signature is necessary due holding is_checked 14 | * attribute - this protection against unwanted repeated check. 15 | * 16 | * 2) Reusing some plpgsql_xxx functions requires full run-time environment. It is 17 | * emulated by fake expression context and fake fceinfo (these are created when 18 | * active checking is used) - see: setup_fake_fcinfo, setup_cstate. 19 | * 20 | * 3) The environment is referenced by stored execution plans. The actual plan should 21 | * not be linked with fake environment. All expressions created in checking time 22 | * should be relased by release_exprs(cstate.exprs) function. 23 | * 24 | */ 25 | 26 | #include "plpgsql_check.h" 27 | #include "plpgsql_check_builtins.h" 28 | 29 | #include "catalog/dependency.h" 30 | #include "catalog/pg_proc.h" 31 | #include "commands/extension.h" 32 | 33 | #include "storage/lwlock.h" 34 | #include "storage/shmem.h" 35 | #include "utils/guc.h" 36 | #include "utils/memutils.h" 37 | 38 | #if PG_VERSION_NUM >= 180000 39 | 40 | #include "utils/inval.h" 41 | #include "utils/syscache.h" 42 | 43 | #endif 44 | 45 | #ifdef PG_MODULE_MAGIC 46 | PG_MODULE_MAGIC; 47 | #endif 48 | 49 | PLpgSQL_plugin **plpgsql_check_plugin_var_ptr; 50 | 51 | static const struct config_enum_entry plpgsql_check_mode_options[] = { 52 | {"disabled", PLPGSQL_CHECK_MODE_DISABLED, false}, 53 | {"by_function", PLPGSQL_CHECK_MODE_BY_FUNCTION, false}, 54 | {"fresh_start", PLPGSQL_CHECK_MODE_FRESH_START, false}, 55 | {"every_start", PLPGSQL_CHECK_MODE_EVERY_START, false}, 56 | {NULL, 0, false} 57 | }; 58 | 59 | static const struct config_enum_entry tracer_verbosity_options[] = { 60 | {"terse", PGERROR_TERSE, false}, 61 | {"default", PGERROR_DEFAULT, false}, 62 | {"verbose", PGERROR_VERBOSE, false}, 63 | {NULL, 0, false} 64 | }; 65 | 66 | static const struct config_enum_entry tracer_level_options[] = { 67 | {"debug5", DEBUG5, false}, 68 | {"debug4", DEBUG4, false}, 69 | {"debug3", DEBUG3, false}, 70 | {"debug2", DEBUG2, false}, 71 | {"debug1", DEBUG1, false}, 72 | {"debug", DEBUG2, true}, 73 | {"info", INFO, false}, 74 | {"notice", NOTICE, false}, 75 | {"log", LOG, false}, 76 | {NULL, 0, false} 77 | }; 78 | 79 | static const struct config_enum_entry cursors_leaks_level_options[] = { 80 | {"notice", NOTICE, false}, 81 | {"WARNING", WARNING, false}, 82 | {"ERROR", ERROR, false}, 83 | {NULL, 0, false} 84 | }; 85 | 86 | 87 | void _PG_init(void); 88 | 89 | #if PG_VERSION_NUM < 150000 90 | 91 | void _PG_fini(void); 92 | 93 | #endif 94 | 95 | #if PG_VERSION_NUM >= 150000 96 | 97 | shmem_request_hook_type plpgsql_check_prev_shmem_request_hook = NULL; 98 | 99 | #endif 100 | 101 | shmem_startup_hook_type plpgsql_check_prev_shmem_startup_hook = NULL; 102 | 103 | bool plpgsql_check_regress_test_mode; 104 | 105 | /* 106 | * Links to function in plpgsql module 107 | */ 108 | plpgsql_check__build_datatype_t plpgsql_check__build_datatype_p; 109 | plpgsql_check__compile_t plpgsql_check__compile_p; 110 | plpgsql_check__parser_setup_t plpgsql_check__parser_setup_p; 111 | plpgsql_check__stmt_typename_t plpgsql_check__stmt_typename_p; 112 | plpgsql_check__exec_get_datum_type_t plpgsql_check__exec_get_datum_type_p; 113 | plpgsql_check__recognize_err_condition_t plpgsql_check__recognize_err_condition_p; 114 | plpgsql_check__ns_lookup_t plpgsql_check__ns_lookup_p; 115 | 116 | static bool is_expected_extversion = false; 117 | 118 | #if PG_VERSION_NUM >= 180000 119 | 120 | static void 121 | pg_extension_cache_callback(Datum arg, int cacheid, uint32 hashvalue) 122 | { 123 | is_expected_extversion = false; 124 | } 125 | 126 | #endif 127 | 128 | /* 129 | * load_external_function retursn PGFunctions - we need generic function, so 130 | * it is not 100% correct, but in used context it is not a problem. 131 | */ 132 | #define LOAD_EXTERNAL_FUNCTION(file, funcname) ((void *) (load_external_function(file, funcname, true, NULL))) 133 | 134 | #define EXPECTED_EXTVERSION "2.8" 135 | 136 | void 137 | plpgsql_check_check_ext_version(Oid fn_oid) 138 | { 139 | if (!is_expected_extversion) 140 | { 141 | Oid extoid; 142 | char *extver; 143 | 144 | extoid = getExtensionOfObject(ProcedureRelationId, fn_oid); 145 | Assert(OidIsValid(extoid)); 146 | 147 | #if PG_VERSION_NUM >= 180000 148 | 149 | extver = get_extension_version2(extoid); 150 | 151 | #else 152 | 153 | extver = get_extension_version(extoid); 154 | 155 | #endif 156 | 157 | Assert(extver); 158 | 159 | if (strcmp(EXPECTED_EXTVERSION, extver) != 0) 160 | { 161 | char *extname = get_extension_name(extoid); 162 | 163 | ereport(ERROR, 164 | (errmsg("extension \"%s\" is not updated in system catalog", extname), 165 | errdetail("version \"%s\" is required, version \"%s\" is installed", EXPECTED_EXTVERSION, extver), 166 | errhint("execute \"ALTER EXTENSION %s UPDATE TO '%s'\"", extname, EXPECTED_EXTVERSION))); 167 | } 168 | else 169 | { 170 | pfree(extver); 171 | is_expected_extversion = true; 172 | } 173 | } 174 | } 175 | 176 | /* 177 | * Module initialization 178 | * 179 | * join to PLpgSQL executor 180 | * 181 | */ 182 | void 183 | _PG_init(void) 184 | { 185 | 186 | /* Be sure we do initialization only once (should be redundant now) */ 187 | static bool inited = false; 188 | 189 | if (inited) 190 | return; 191 | 192 | pg_bindtextdomain(TEXTDOMAIN); 193 | 194 | AssertVariableIsOfType(&plpgsql_build_datatype, plpgsql_check__build_datatype_t); 195 | plpgsql_check__build_datatype_p = (plpgsql_check__build_datatype_t) 196 | LOAD_EXTERNAL_FUNCTION("$libdir/plpgsql", "plpgsql_build_datatype"); 197 | 198 | AssertVariableIsOfType(&plpgsql_compile, plpgsql_check__compile_t); 199 | plpgsql_check__compile_p = (plpgsql_check__compile_t) 200 | LOAD_EXTERNAL_FUNCTION("$libdir/plpgsql", "plpgsql_compile"); 201 | 202 | AssertVariableIsOfType(&plpgsql_parser_setup, plpgsql_check__parser_setup_t); 203 | plpgsql_check__parser_setup_p = (plpgsql_check__parser_setup_t) 204 | LOAD_EXTERNAL_FUNCTION("$libdir/plpgsql", "plpgsql_parser_setup"); 205 | 206 | AssertVariableIsOfType(&plpgsql_stmt_typename, plpgsql_check__stmt_typename_t); 207 | plpgsql_check__stmt_typename_p = (plpgsql_check__stmt_typename_t) 208 | LOAD_EXTERNAL_FUNCTION("$libdir/plpgsql", "plpgsql_stmt_typename"); 209 | 210 | AssertVariableIsOfType(&plpgsql_exec_get_datum_type, plpgsql_check__exec_get_datum_type_t); 211 | plpgsql_check__exec_get_datum_type_p = (plpgsql_check__exec_get_datum_type_t) 212 | LOAD_EXTERNAL_FUNCTION("$libdir/plpgsql", "plpgsql_exec_get_datum_type"); 213 | 214 | AssertVariableIsOfType(&plpgsql_recognize_err_condition, plpgsql_check__recognize_err_condition_t); 215 | plpgsql_check__recognize_err_condition_p = (plpgsql_check__recognize_err_condition_t) 216 | LOAD_EXTERNAL_FUNCTION("$libdir/plpgsql", "plpgsql_recognize_err_condition"); 217 | 218 | AssertVariableIsOfType(&plpgsql_ns_lookup, plpgsql_check__ns_lookup_t); 219 | plpgsql_check__ns_lookup_p = (plpgsql_check__ns_lookup_t) 220 | LOAD_EXTERNAL_FUNCTION("$libdir/plpgsql", "plpgsql_ns_lookup"); 221 | 222 | DefineCustomBoolVariable("plpgsql_check.regress_test_mode", 223 | "reduces volatile output", 224 | NULL, 225 | &plpgsql_check_regress_test_mode, 226 | false, 227 | PGC_USERSET, 0, 228 | NULL, NULL, NULL); 229 | 230 | DefineCustomEnumVariable("plpgsql_check.mode", 231 | "choose a mode for enhanced checking", 232 | NULL, 233 | &plpgsql_check_mode, 234 | PLPGSQL_CHECK_MODE_BY_FUNCTION, 235 | plpgsql_check_mode_options, 236 | PGC_USERSET, 0, 237 | NULL, NULL, NULL); 238 | 239 | DefineCustomBoolVariable("plpgsql_check.show_nonperformance_extra_warnings", 240 | "when is true, then extra warning (except performance warnings) are showed", 241 | NULL, 242 | &plpgsql_check_extra_warnings, 243 | false, 244 | PGC_USERSET, 0, 245 | NULL, NULL, NULL); 246 | 247 | DefineCustomBoolVariable("plpgsql_check.show_nonperformance_warnings", 248 | "when is true, then warning (except performance warnings) are showed", 249 | NULL, 250 | &plpgsql_check_other_warnings, 251 | false, 252 | PGC_USERSET, 0, 253 | NULL, NULL, NULL); 254 | 255 | DefineCustomBoolVariable("plpgsql_check.show_performance_warnings", 256 | "when is true, then performance warnings are showed", 257 | NULL, 258 | &plpgsql_check_performance_warnings, 259 | false, 260 | PGC_USERSET, 0, 261 | NULL, NULL, NULL); 262 | 263 | DefineCustomBoolVariable("plpgsql_check.compatibility_warnings", 264 | "when is true, then compatibility warnings are showed", 265 | NULL, 266 | &plpgsql_check_compatibility_warnings, 267 | false, 268 | PGC_USERSET, 0, 269 | NULL, NULL, NULL); 270 | 271 | DefineCustomBoolVariable("plpgsql_check.constants_tracing", 272 | "when is true, the variables with constant value can be used like constant", 273 | NULL, 274 | &plpgsql_check_constants_tracing, 275 | true, 276 | PGC_USERSET, 0, 277 | NULL, NULL, NULL); 278 | 279 | DefineCustomBoolVariable("plpgsql_check.fatal_errors", 280 | "when is true, then plpgsql check stops execution on detected error", 281 | NULL, 282 | &plpgsql_check_fatal_errors, 283 | true, 284 | PGC_USERSET, 0, 285 | NULL, NULL, NULL); 286 | 287 | DefineCustomBoolVariable("plpgsql_check.profiler", 288 | "when is true, then function execution profile is updated", 289 | NULL, 290 | &plpgsql_check_profiler, 291 | false, 292 | PGC_USERSET, 0, 293 | NULL, NULL, NULL); 294 | 295 | DefineCustomBoolVariable("plpgsql_check.enable_tracer", 296 | "when is true, then tracer's functionality is enabled", 297 | NULL, 298 | &plpgsql_check_enable_tracer, 299 | false, 300 | PGC_SUSET, 0, 301 | NULL, NULL, NULL); 302 | 303 | DefineCustomBoolVariable("plpgsql_check.tracer", 304 | "when is true, then function is traced", 305 | NULL, 306 | &plpgsql_check_tracer, 307 | false, 308 | PGC_USERSET, 0, 309 | NULL, NULL, NULL); 310 | 311 | DefineCustomBoolVariable("plpgsql_check.trace_assert", 312 | "when is true, then statement ASSERT is traced", 313 | NULL, 314 | &plpgsql_check_trace_assert, 315 | false, 316 | PGC_USERSET, 0, 317 | NULL, NULL, NULL); 318 | 319 | DefineCustomBoolVariable("plpgsql_check.tracer_test_mode", 320 | "when is true, then output of tracer is in regress test possible format", 321 | NULL, 322 | &plpgsql_check_tracer_test_mode, 323 | false, 324 | PGC_USERSET, 0, 325 | NULL, NULL, NULL); 326 | 327 | DefineCustomBoolVariable("plpgsql_check.tracer_show_nsubxids", 328 | "when is true, then the tracer shows number of current subxids", 329 | NULL, 330 | &plpgsql_check_tracer_show_nsubxids, 331 | false, 332 | PGC_USERSET, 0, 333 | NULL, NULL, NULL); 334 | 335 | DefineCustomEnumVariable("plpgsql_check.tracer_verbosity", 336 | "sets the verbosity of tracer", 337 | NULL, 338 | (int *) &plpgsql_check_tracer_verbosity, 339 | PGERROR_DEFAULT, 340 | tracer_verbosity_options, 341 | PGC_USERSET, 0, 342 | NULL, NULL, NULL); 343 | 344 | DefineCustomEnumVariable("plpgsql_check.trace_assert_verbosity", 345 | "sets the verbosity of trace ASSERT statement", 346 | NULL, 347 | (int *) &plpgsql_check_trace_assert_verbosity, 348 | PGERROR_DEFAULT, 349 | tracer_verbosity_options, 350 | PGC_USERSET, 0, 351 | NULL, NULL, NULL); 352 | 353 | DefineCustomEnumVariable("plpgsql_check.tracer_errlevel", 354 | "sets an error level of tracer's messages", 355 | NULL, 356 | (int *) &plpgsql_check_tracer_errlevel, 357 | NOTICE, 358 | tracer_level_options, 359 | PGC_USERSET, 0, 360 | NULL, NULL, NULL); 361 | 362 | DefineCustomIntVariable("plpgsql_check.tracer_variable_max_length", 363 | "Maximum output length of content of variables in bytes", 364 | NULL, 365 | &plpgsql_check_tracer_variable_max_length, 366 | 1024, 367 | 10, 2048, 368 | PGC_USERSET, 0, 369 | NULL, NULL, NULL); 370 | 371 | DefineCustomEnumVariable("plpgsql_check.cursors_leaks_errlevel", 372 | "sets an error level of detection of unclosed cursors", 373 | NULL, 374 | (int *) &plpgsql_check_cursors_leaks_level, 375 | WARNING, 376 | cursors_leaks_level_options, 377 | PGC_USERSET, 0, 378 | NULL, NULL, NULL); 379 | 380 | DefineCustomBoolVariable("plpgsql_check.cursors_leaks", 381 | "when is true, then detection of unclosed cursors is active", 382 | NULL, 383 | &plpgsql_check_cursors_leaks, 384 | true, 385 | PGC_USERSET, 0, 386 | NULL, NULL, NULL); 387 | 388 | DefineCustomBoolVariable("plpgsql_check.strict_cursors_leaks", 389 | "when is true, then detection of unclosed cursors is executed immediately when function is finished", 390 | NULL, 391 | &plpgsql_check_cursors_leaks_strict, 392 | false, 393 | PGC_USERSET, 0, 394 | NULL, NULL, NULL); 395 | 396 | EmitWarningsOnPlaceholders("plpgsql_check"); 397 | 398 | plpgsql_check_HashTableInit(); 399 | plpgsql_check_profiler_init_hash_tables(); 400 | 401 | /* Use shared memory when we can register more for self */ 402 | if (process_shared_preload_libraries_in_progress) 403 | { 404 | 405 | DefineCustomIntVariable("plpgsql_check.profiler_max_shared_chunks", 406 | "maximum numbers of statements chunks in shared memory", 407 | NULL, 408 | &plpgsql_check_profiler_max_shared_chunks, 409 | 15000, 50, 100000, 410 | PGC_POSTMASTER, 0, 411 | NULL, NULL, NULL); 412 | 413 | #if PG_VERSION_NUM < 150000 414 | 415 | /* 416 | * If you change code here, don't forget to also report the 417 | * modifications in plpgsql_check_profiler_shmem_request() for pg15 and 418 | * later. 419 | */ 420 | RequestAddinShmemSpace(plpgsql_check_shmem_size()); 421 | 422 | RequestNamedLWLockTranche("plpgsql_check profiler", 1); 423 | RequestNamedLWLockTranche("plpgsql_check fstats", 1); 424 | 425 | #endif 426 | 427 | /* 428 | * Install hooks. 429 | */ 430 | #if PG_VERSION_NUM >= 150000 431 | 432 | plpgsql_check_prev_shmem_request_hook = shmem_request_hook; 433 | shmem_request_hook = plpgsql_check_profiler_shmem_request; 434 | 435 | #endif 436 | 437 | plpgsql_check_prev_shmem_startup_hook = shmem_startup_hook; 438 | shmem_startup_hook = plpgsql_check_profiler_shmem_startup; 439 | } 440 | 441 | plpgsql_check_init_pldbgapi2(); 442 | plpgsql_check_passive_check_init(); 443 | plpgsql_check_profiler_init(); 444 | plpgsql_check_tracer_init(); 445 | plpgsql_check_cursors_leaks_init(); 446 | 447 | 448 | #if PG_VERSION_NUM >= 180000 449 | 450 | CacheRegisterSyscacheCallback(EXTENSIONOID, 451 | pg_extension_cache_callback, 452 | (Datum) 0); 453 | 454 | #endif 455 | inited = true; 456 | } 457 | 458 | -------------------------------------------------------------------------------- /src/plpgsql_check_builtins.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef PLPGSQL_CHECK_BUILTINS 3 | #define PLPGSQL_CHECK_BUILTINS 4 | 5 | #ifdef _MSC_VER 6 | /* 7 | * _PG_init should be exported, but PGDLLEXPORT cannot be used due 8 | * collision with _PG_init from plpgsql.h 9 | */ 10 | #ifdef _M_X64 11 | #pragma comment( linker, "/export:_PG_init" ) 12 | #else 13 | #pragma comment( linker, "/export:_PG_init=__PG_init" ) 14 | #endif 15 | #endif 16 | 17 | #ifndef PGDLLEXPORT 18 | #ifdef _MSC_VER 19 | #define PGDLLEXPORT __declspec(dllexport) 20 | 21 | /* 22 | * PG_MODULE_MAGIC and PG_FUNCTION_INFO_V1 macros are broken for MSVC. 23 | * So, we redefine them. 24 | */ 25 | 26 | #undef PG_MODULE_MAGIC 27 | #define PG_MODULE_MAGIC \ 28 | extern PGDLLEXPORT const Pg_magic_struct *PG_MAGIC_FUNCTION_NAME(void); \ 29 | const Pg_magic_struct * \ 30 | PG_MAGIC_FUNCTION_NAME(void) \ 31 | { \ 32 | static const Pg_magic_struct Pg_magic_data = PG_MODULE_MAGIC_DATA; \ 33 | return &Pg_magic_data; \ 34 | } \ 35 | extern int no_such_variable 36 | 37 | #undef PG_FUNCTION_INFO_V1 38 | #define PG_FUNCTION_INFO_V1(funcname) \ 39 | extern PGDLLEXPORT const Pg_finfo_record * CppConcat(pg_finfo_,funcname)(void); \ 40 | const Pg_finfo_record * \ 41 | CppConcat(pg_finfo_,funcname) (void) \ 42 | { \ 43 | static const Pg_finfo_record my_finfo = { 1 }; \ 44 | return &my_finfo; \ 45 | } \ 46 | extern int no_such_variable 47 | 48 | #else 49 | #define PGDLLEXPORT PGDLLIMPORT 50 | #endif 51 | #endif 52 | 53 | /* 54 | * Interface 55 | * 56 | */ 57 | extern void _PG_init(void); 58 | 59 | extern PGDLLEXPORT Datum plpgsql_check_function_tb(PG_FUNCTION_ARGS); 60 | extern PGDLLEXPORT Datum plpgsql_check_function(PG_FUNCTION_ARGS); 61 | extern PGDLLEXPORT Datum plpgsql_show_dependency_tb(PG_FUNCTION_ARGS); 62 | extern PGDLLEXPORT Datum plpgsql_check_function_tb_name(PG_FUNCTION_ARGS); 63 | extern PGDLLEXPORT Datum plpgsql_check_function_name(PG_FUNCTION_ARGS); 64 | extern PGDLLEXPORT Datum plpgsql_show_dependency_tb_name(PG_FUNCTION_ARGS); 65 | extern PGDLLEXPORT Datum plpgsql_profiler_reset(PG_FUNCTION_ARGS); 66 | extern PGDLLEXPORT Datum plpgsql_profiler_reset_all(PG_FUNCTION_ARGS); 67 | extern PGDLLEXPORT Datum plpgsql_profiler_function_tb(PG_FUNCTION_ARGS); 68 | extern PGDLLEXPORT Datum plpgsql_profiler_function_statements_tb(PG_FUNCTION_ARGS); 69 | extern PGDLLEXPORT Datum plpgsql_profiler_function_tb_name(PG_FUNCTION_ARGS); 70 | extern PGDLLEXPORT Datum plpgsql_profiler_function_statements_tb_name(PG_FUNCTION_ARGS); 71 | extern PGDLLEXPORT Datum plpgsql_profiler_functions_all_tb(PG_FUNCTION_ARGS); 72 | extern PGDLLEXPORT Datum plpgsql_coverage_statements(PG_FUNCTION_ARGS); 73 | extern PGDLLEXPORT Datum plpgsql_coverage_branches(PG_FUNCTION_ARGS); 74 | extern PGDLLEXPORT Datum plpgsql_coverage_statements_name(PG_FUNCTION_ARGS); 75 | extern PGDLLEXPORT Datum plpgsql_coverage_branches_name(PG_FUNCTION_ARGS); 76 | extern PGDLLEXPORT Datum plpgsql_check_pragma(PG_FUNCTION_ARGS); 77 | extern PGDLLEXPORT Datum plpgsql_profiler_install_fake_queryid_hook(PG_FUNCTION_ARGS); 78 | extern PGDLLEXPORT Datum plpgsql_profiler_remove_fake_queryid_hook(PG_FUNCTION_ARGS); 79 | extern PGDLLEXPORT Datum plpgsql_check_profiler_ctrl(PG_FUNCTION_ARGS); 80 | extern PGDLLEXPORT Datum plpgsql_check_tracer_ctrl(PG_FUNCTION_ARGS); 81 | 82 | #endif 83 | -------------------------------------------------------------------------------- /src/pragma.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * pragma.c 4 | * 5 | * pragma related code 6 | * 7 | * by Pavel Stehule 2013-2025 8 | * 9 | *------------------------------------------------------------------------- 10 | */ 11 | 12 | #include "plpgsql_check.h" 13 | #include "plpgsql_check_builtins.h" 14 | 15 | #include "utils/builtins.h" 16 | #include "utils/array.h" 17 | #include "parser/scansup.h" 18 | 19 | #include "tcop/tcopprot.h" 20 | #include "tcop/utility.h" 21 | 22 | #ifdef _MSC_VER 23 | 24 | #define strcasecmp _stricmp 25 | #define strncasecmp _strnicmp 26 | 27 | #endif 28 | 29 | PG_FUNCTION_INFO_V1(plpgsql_check_pragma); 30 | 31 | static void 32 | runtime_pragma_apply(char *pragma_str) 33 | { 34 | while (scanner_isspace(*pragma_str)) 35 | pragma_str++; 36 | 37 | if (strncasecmp(pragma_str, "STATUS:", 7) == 0) 38 | { 39 | pragma_str += 7; 40 | 41 | while (scanner_isspace(*pragma_str)) 42 | pragma_str++; 43 | 44 | if (strcasecmp(pragma_str, "TRACER") == 0) 45 | elog(NOTICE, "tracer is %s", 46 | plpgsql_check_tracer ? "enabled" : "disabled"); 47 | } 48 | else if (strncasecmp(pragma_str, "ENABLE:", 7) == 0) 49 | { 50 | pragma_str += 7; 51 | 52 | while (scanner_isspace(*pragma_str)) 53 | pragma_str++; 54 | 55 | if (strcasecmp(pragma_str, "TRACER") == 0) 56 | plpgsql_check_tracer = true; 57 | } 58 | else if (strncasecmp(pragma_str, "DISABLE:", 8) == 0) 59 | { 60 | pragma_str += 8; 61 | 62 | while (scanner_isspace(*pragma_str)) 63 | pragma_str++; 64 | 65 | if (strcasecmp(pragma_str, "TRACER") == 0) 66 | plpgsql_check_tracer = false; 67 | } 68 | } 69 | 70 | static bool 71 | pragma_apply(PLpgSQL_checkstate *cstate, 72 | plpgsql_check_pragma_vector *pv, 73 | char *pragma_str, 74 | PLpgSQL_nsitem *ns, 75 | int lineno) 76 | { 77 | bool is_valid = true; 78 | 79 | Assert(cstate); 80 | 81 | while (scanner_isspace(*pragma_str)) 82 | pragma_str++; 83 | 84 | if (strncasecmp(pragma_str, "ECHO:", 5) == 0) 85 | { 86 | elog(NOTICE, "%s", plpgsql_check_process_echo_string(pragma_str + 5, cstate->cinfo)); 87 | } 88 | else if (strncasecmp(pragma_str, "STATUS:", 7) == 0) 89 | { 90 | pragma_str += 7; 91 | 92 | while (scanner_isspace(*pragma_str)) 93 | pragma_str++; 94 | 95 | if (strcasecmp(pragma_str, "CHECK") == 0) 96 | elog(NOTICE, "check is %s", 97 | pv->disable_check ? "disabled" : "enabled"); 98 | else if (strcasecmp(pragma_str, "TRACER") == 0) 99 | elog(NOTICE, "tracer is %s", 100 | pv->disable_tracer ? "disabled" : "enabled"); 101 | else if (strcasecmp(pragma_str, "OTHER_WARNINGS") == 0) 102 | elog(NOTICE, "other_warnings is %s", 103 | pv->disable_other_warnings ? "disabled" : "enabled"); 104 | else if (strcasecmp(pragma_str, "PERFORMANCE_WARNINGS") == 0) 105 | elog(NOTICE, "performance_warnings is %s", 106 | pv->disable_performance_warnings ? "disabled" : "enabled"); 107 | else if (strcasecmp(pragma_str, "EXTRA_WARNINGS") == 0) 108 | elog(NOTICE, "extra_warnings is %s", 109 | pv->disable_extra_warnings ? "disabled" : "enabled"); 110 | else if (strcasecmp(pragma_str, "SECURITY_WARNINGS") == 0) 111 | elog(NOTICE, "security_warnings is %s", 112 | pv->disable_security_warnings ? "disabled" : "enabled"); 113 | else if (strcasecmp(pragma_str, "COMPATIBILITY_WARNINGS") == 0) 114 | elog(NOTICE, "compatibility_warnings is %s", 115 | pv->disable_compatibility_warnings ? "disabled" : "enabled"); 116 | else if (strcasecmp(pragma_str, "CONSTANTS_TRANCING") == 0) 117 | elog(NOTICE, "constants_traising is %s", 118 | pv->disable_constants_tracing ? "disabled" : "enabled"); 119 | else 120 | { 121 | elog(WARNING, "unsuported pragma: %s", pragma_str); 122 | is_valid = false; 123 | } 124 | } 125 | else if (strncasecmp(pragma_str, "ENABLE:", 7) == 0) 126 | { 127 | pragma_str += 7; 128 | 129 | while (scanner_isspace(*pragma_str)) 130 | pragma_str++; 131 | 132 | if (strcasecmp(pragma_str, "CHECK") == 0) 133 | pv->disable_check = false; 134 | else if (strcasecmp(pragma_str, "TRACER") == 0) 135 | pv->disable_tracer = false; 136 | else if (strcasecmp(pragma_str, "OTHER_WARNINGS") == 0) 137 | pv->disable_other_warnings = false; 138 | else if (strcasecmp(pragma_str, "PERFORMANCE_WARNINGS") == 0) 139 | pv->disable_performance_warnings = false; 140 | else if (strcasecmp(pragma_str, "EXTRA_WARNINGS") == 0) 141 | pv->disable_extra_warnings = false; 142 | else if (strcasecmp(pragma_str, "SECURITY_WARNINGS") == 0) 143 | pv->disable_security_warnings = false; 144 | else if (strcasecmp(pragma_str, "COMPATIBILITY_WARNINGS") == 0) 145 | pv->disable_compatibility_warnings = false; 146 | else if (strcasecmp(pragma_str, "CONSTANTS_TRACING") == 0) 147 | pv->disable_constants_tracing = false; 148 | else 149 | { 150 | elog(WARNING, "unsuported pragma: %s", pragma_str); 151 | is_valid = false; 152 | } 153 | } 154 | else if (strncasecmp(pragma_str, "DISABLE:", 8) == 0) 155 | { 156 | pragma_str += 8; 157 | 158 | while (scanner_isspace(*pragma_str)) 159 | pragma_str++; 160 | 161 | if (strcasecmp(pragma_str, "CHECK") == 0) 162 | pv->disable_check = true; 163 | else if (strcasecmp(pragma_str, "TRACER") == 0) 164 | pv->disable_tracer = true; 165 | else if (strcasecmp(pragma_str, "OTHER_WARNINGS") == 0) 166 | pv->disable_other_warnings = true; 167 | else if (strcasecmp(pragma_str, "PERFORMANCE_WARNINGS") == 0) 168 | pv->disable_performance_warnings = true; 169 | else if (strcasecmp(pragma_str, "EXTRA_WARNINGS") == 0) 170 | pv->disable_extra_warnings = true; 171 | else if (strcasecmp(pragma_str, "SECURITY_WARNINGS") == 0) 172 | pv->disable_security_warnings = true; 173 | else if (strcasecmp(pragma_str, "COMPATIBILITY_WARNINGS") == 0) 174 | pv->disable_compatibility_warnings = true; 175 | else if (strcasecmp(pragma_str, "CONSTANTS_TRACING") == 0) 176 | pv->disable_constants_tracing = true; 177 | else 178 | elog(WARNING, "unsuported pragma: %s", pragma_str); 179 | } 180 | else if (strncasecmp(pragma_str, "TYPE:", 5) == 0) 181 | { 182 | is_valid = plpgsql_check_pragma_type(cstate, pragma_str + 5, ns, lineno); 183 | } 184 | else if (strncasecmp(pragma_str, "TABLE:", 6) == 0) 185 | { 186 | is_valid = plpgsql_check_pragma_table(cstate, pragma_str + 6, lineno); 187 | } 188 | else if (strncasecmp(pragma_str, "SEQUENCE:", 6) == 0) 189 | { 190 | is_valid = plpgsql_check_pragma_sequence(cstate, pragma_str + 9, lineno); 191 | } 192 | else if (strncasecmp(pragma_str, "ASSERT-SCHEMA:", 14) == 0) 193 | { 194 | is_valid = plpgsql_check_pragma_assert(cstate, 195 | PLPGSQL_CHECK_PRAGMA_ASSERT_SCHEMA, 196 | pragma_str + 14, ns, lineno); 197 | } 198 | else if (strncasecmp(pragma_str, "ASSERT-TABLE:", 13) == 0) 199 | { 200 | is_valid = plpgsql_check_pragma_assert(cstate, 201 | PLPGSQL_CHECK_PRAGMA_ASSERT_TABLE, 202 | pragma_str + 13, ns, lineno); 203 | } 204 | else if (strncasecmp(pragma_str, "ASSERT-COLUMN:", 14) == 0) 205 | { 206 | is_valid = plpgsql_check_pragma_assert(cstate, 207 | PLPGSQL_CHECK_PRAGMA_ASSERT_COLUMN, 208 | pragma_str + 14, ns, lineno); 209 | } 210 | else 211 | { 212 | elog(WARNING, "unsupported pragma: %s", pragma_str); 213 | is_valid = false; 214 | } 215 | 216 | return is_valid; 217 | } 218 | 219 | 220 | /* 221 | * Implementation of pragma function. There are two different 222 | * use cases - 1) it is used for static analyze by plpgsql_check, 223 | * where arguments are read from parse tree. 224 | * 2) it is used for controlling of code tracing in runtime. 225 | * arguments, are processed as usual for variadic text function. 226 | */ 227 | Datum 228 | plpgsql_check_pragma(PG_FUNCTION_ARGS) 229 | { 230 | ArrayType *array; 231 | ArrayIterator iter; 232 | bool isnull; 233 | Datum value; 234 | 235 | if (PG_ARGISNULL(0)) 236 | PG_RETURN_INT32(0); 237 | 238 | array = PG_GETARG_ARRAYTYPE_P(0); 239 | 240 | iter = array_create_iterator(array, 0, NULL); 241 | 242 | while (array_iterate(iter, &value, &isnull)) 243 | { 244 | char *pragma_str; 245 | 246 | if (isnull) 247 | continue; 248 | 249 | pragma_str = TextDatumGetCString(value); 250 | 251 | runtime_pragma_apply(pragma_str); 252 | pfree(pragma_str); 253 | } 254 | 255 | array_free_iterator(iter); 256 | 257 | PG_RETURN_INT32(1); 258 | } 259 | 260 | void 261 | plpgsql_check_pragma_apply(PLpgSQL_checkstate *cstate, char *pragma_str, PLpgSQL_nsitem *ns, int lineno) 262 | { 263 | if (pragma_apply(cstate, &(cstate->pragma_vector), pragma_str, ns, lineno)) 264 | cstate->was_pragma = true; 265 | } 266 | -------------------------------------------------------------------------------- /src/report.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * report.c 4 | * 5 | * last stage checks 6 | * 7 | * by Pavel Stehule 2013-2025 8 | * 9 | *------------------------------------------------------------------------- 10 | */ 11 | 12 | #include "plpgsql_check.h" 13 | 14 | #include "catalog/pg_proc.h" 15 | #include "catalog/pg_type.h" 16 | 17 | static bool datum_is_used(PLpgSQL_checkstate *cstate, int dno, bool write); 18 | static bool datum_is_explicit(PLpgSQL_checkstate *cstate, int dno); 19 | 20 | /* 21 | * Returns true, when variable is internal (automatic) 22 | * 23 | */ 24 | static bool 25 | is_internal(char *refname, int lineno) 26 | { 27 | if (lineno <= 0) 28 | return true; 29 | if (refname == NULL) 30 | return true; 31 | if (strcmp(refname, "*internal*") == 0) 32 | return true; 33 | if (strcmp(refname, "(unnamed row)") == 0) 34 | return true; 35 | return false; 36 | } 37 | 38 | bool 39 | is_internal_variable(PLpgSQL_checkstate *cstate, PLpgSQL_variable *var) 40 | { 41 | if (bms_is_member(var->dno, cstate->auto_variables)) 42 | return true; 43 | 44 | return is_internal(var->refname, var->lineno); 45 | } 46 | 47 | /* 48 | * returns refname of PLpgSQL_datum. When refname is generated, 49 | * then return null too, although refname is not null. 50 | */ 51 | char * 52 | plpgsql_check_datum_get_refname(PLpgSQL_checkstate *cstate, PLpgSQL_datum *d) 53 | { 54 | char *refname; 55 | int lineno; 56 | 57 | switch (d->dtype) 58 | { 59 | case PLPGSQL_DTYPE_VAR: 60 | refname = ((PLpgSQL_var *) d)->refname; 61 | lineno = ((PLpgSQL_var *) d)->lineno; 62 | break; 63 | 64 | case PLPGSQL_DTYPE_ROW: 65 | refname = ((PLpgSQL_row *) d)->refname; 66 | lineno = ((PLpgSQL_row *) d)->lineno; 67 | break; 68 | 69 | case PLPGSQL_DTYPE_REC: 70 | refname = ((PLpgSQL_rec *) d)->refname; 71 | lineno = ((PLpgSQL_rec *) d)->lineno; 72 | break; 73 | 74 | default: 75 | refname = NULL; 76 | lineno = -1; 77 | } 78 | 79 | /* 80 | * This routine is used for shadowing check. 81 | * We would to check auto variables too 82 | */ 83 | if (bms_is_member(d->dno, cstate->auto_variables)) 84 | return refname; 85 | 86 | /* 87 | * PostgreSQL 12 started use "(unnamed row)" name for internal 88 | * variables. Hide this name too (lineno is -1). 89 | */ 90 | if (is_internal(refname, lineno)) 91 | return NULL; 92 | 93 | return refname; 94 | } 95 | 96 | /* 97 | * Returns true if dno is explicitly declared. It should not be used 98 | * for arguments. 99 | */ 100 | bool 101 | datum_is_explicit(PLpgSQL_checkstate *cstate, int dno) 102 | { 103 | PLpgSQL_execstate *estate = cstate->estate; 104 | 105 | if (bms_is_member(dno, cstate->auto_variables)) 106 | return false; 107 | 108 | switch (estate->datums[dno]->dtype) 109 | { 110 | case PLPGSQL_DTYPE_VAR: 111 | { 112 | PLpgSQL_variable *var = (PLpgSQL_variable *) estate->datums[dno]; 113 | return !is_internal(var->refname, var->lineno); 114 | } 115 | 116 | case PLPGSQL_DTYPE_ROW: 117 | { 118 | PLpgSQL_row *row = (PLpgSQL_row *) estate->datums[dno]; 119 | return !is_internal(row->refname, row->lineno); 120 | } 121 | case PLPGSQL_DTYPE_REC: 122 | { 123 | PLpgSQL_rec *rec = (PLpgSQL_rec *) estate->datums[dno]; 124 | return !is_internal(rec->refname, rec->lineno); 125 | } 126 | 127 | default: 128 | return false; 129 | } 130 | } 131 | 132 | /* 133 | * returns true, when datum or some child is used 134 | */ 135 | static bool 136 | datum_is_used(PLpgSQL_checkstate *cstate, int dno, bool write) 137 | { 138 | PLpgSQL_execstate *estate = cstate->estate; 139 | 140 | switch (estate->datums[dno]->dtype) 141 | { 142 | case PLPGSQL_DTYPE_VAR: 143 | { 144 | return bms_is_member(dno, 145 | write ? cstate->modif_variables : cstate->used_variables); 146 | } 147 | break; 148 | 149 | case PLPGSQL_DTYPE_ROW: 150 | { 151 | PLpgSQL_row *row = (PLpgSQL_row *) estate->datums[dno]; 152 | int i; 153 | 154 | if (bms_is_member(dno, 155 | write ? cstate->modif_variables : cstate->used_variables)) 156 | return true; 157 | 158 | for (i = 0; i < row->nfields; i++) 159 | { 160 | if (row->varnos[i] < 0) 161 | continue; 162 | 163 | if (datum_is_used(cstate, row->varnos[i], write)) 164 | return true; 165 | } 166 | 167 | return false; 168 | } 169 | break; 170 | 171 | case PLPGSQL_DTYPE_REC: 172 | { 173 | PLpgSQL_rec *rec = (PLpgSQL_rec *) estate->datums[dno]; 174 | int i; 175 | 176 | if (bms_is_member(dno, 177 | write ? cstate->modif_variables : cstate->used_variables)) 178 | return true; 179 | 180 | /* search any used recfield with related recparentno */ 181 | for (i = 0; i < estate->ndatums; i++) 182 | { 183 | if (estate->datums[i]->dtype == PLPGSQL_DTYPE_RECFIELD) 184 | { 185 | PLpgSQL_recfield *recfield = (PLpgSQL_recfield *) estate->datums[i]; 186 | 187 | if (recfield->recparentno == rec->dno 188 | && datum_is_used(cstate, i, write)) 189 | return true; 190 | } 191 | } 192 | } 193 | break; 194 | 195 | case PLPGSQL_DTYPE_RECFIELD: 196 | return bms_is_member(dno, 197 | write ? cstate->modif_variables : cstate->used_variables); 198 | 199 | default: 200 | return false; 201 | } 202 | 203 | return false; 204 | } 205 | 206 | /* 207 | * Reports all unused variables explicitly DECLAREd by the user. Ignores 208 | * special variables created by PL/PgSQL. 209 | */ 210 | void 211 | plpgsql_check_report_unused_variables(PLpgSQL_checkstate *cstate) 212 | { 213 | int i; 214 | PLpgSQL_execstate *estate = cstate->estate; 215 | 216 | /* now, there are no active plpgsql statement */ 217 | estate->err_stmt = NULL; 218 | 219 | for (i = 0; i < estate->ndatums; i++) 220 | if (datum_is_explicit(cstate, i) && 221 | !(datum_is_used(cstate, i, false) || datum_is_used(cstate, i, true))) 222 | { 223 | PLpgSQL_variable *var = (PLpgSQL_variable *) estate->datums[i]; 224 | StringInfoData message; 225 | 226 | initStringInfo(&message); 227 | 228 | appendStringInfo(&message, UNUSED_VARIABLE_TEXT, var->refname); 229 | plpgsql_check_put_error(cstate, 230 | 0, var->lineno, 231 | message.data, 232 | NULL, 233 | NULL, 234 | PLPGSQL_CHECK_WARNING_OTHERS, 235 | 0, NULL, NULL); 236 | 237 | pfree(message.data); 238 | message.data = NULL; 239 | } 240 | 241 | if (cstate->cinfo->extra_warnings) 242 | { 243 | PLpgSQL_function *func = estate->func; 244 | 245 | /* check never read variables */ 246 | for (i = 0; i < estate->ndatums; i++) 247 | { 248 | if (datum_is_explicit(cstate, i) 249 | && !datum_is_used(cstate, i, false) && datum_is_used(cstate, i, true)) 250 | { 251 | PLpgSQL_variable *var = (PLpgSQL_variable *) estate->datums[i]; 252 | StringInfoData message; 253 | 254 | initStringInfo(&message); 255 | 256 | appendStringInfo(&message, NEVER_READ_VARIABLE_TEXT, var->refname); 257 | plpgsql_check_put_error(cstate, 258 | 0, var->lineno, 259 | message.data, 260 | NULL, 261 | NULL, 262 | PLPGSQL_CHECK_WARNING_EXTRA, 263 | 0, NULL, NULL); 264 | 265 | pfree(message.data); 266 | message.data = NULL; 267 | } 268 | } 269 | 270 | /* check IN parameters */ 271 | for (i = 0; i < func->fn_nargs; i++) 272 | { 273 | int varno = func->fn_argvarnos[i]; 274 | 275 | bool is_read = datum_is_used(cstate, varno, false); 276 | bool is_write = datum_is_used(cstate, varno, true); 277 | 278 | if (!is_read && !is_write) 279 | { 280 | PLpgSQL_variable *var = (PLpgSQL_variable *) estate->datums[varno]; 281 | StringInfoData message; 282 | 283 | initStringInfo(&message); 284 | 285 | appendStringInfo(&message, UNUSED_PARAMETER_TEXT, var->refname); 286 | plpgsql_check_put_error(cstate, 287 | 0, 0, 288 | message.data, 289 | NULL, 290 | NULL, 291 | PLPGSQL_CHECK_WARNING_EXTRA, 292 | 0, NULL, NULL); 293 | 294 | pfree(message.data); 295 | message.data = NULL; 296 | } 297 | else if (!is_read) 298 | { 299 | bool is_inout_procedure_param = false; 300 | 301 | /* 302 | * procedure doesn't support only OUT parameters. Don't raise 303 | * warning if INOUT parameter is just modified in procedures. 304 | */ 305 | is_inout_procedure_param = cstate->cinfo->is_procedure 306 | && bms_is_member(varno, cstate->out_variables); 307 | 308 | if (!is_inout_procedure_param) 309 | { 310 | PLpgSQL_variable *var = (PLpgSQL_variable *) estate->datums[varno]; 311 | StringInfoData message; 312 | 313 | initStringInfo(&message); 314 | 315 | appendStringInfo(&message, NEVER_READ_PARAMETER_TEXT, var->refname); 316 | plpgsql_check_put_error(cstate, 317 | 0, 0, 318 | message.data, 319 | NULL, 320 | NULL, 321 | PLPGSQL_CHECK_WARNING_EXTRA, 322 | 0, NULL, NULL); 323 | 324 | pfree(message.data); 325 | message.data = NULL; 326 | } 327 | } 328 | } 329 | 330 | /* are there some OUT parameters (expect modification)? */ 331 | if (func->out_param_varno != -1 && !cstate->found_return_query) 332 | { 333 | int varno = func->out_param_varno; 334 | PLpgSQL_variable *var = (PLpgSQL_variable *) estate->datums[varno]; 335 | 336 | if (var->dtype == PLPGSQL_DTYPE_ROW && is_internal_variable(cstate, var)) 337 | { 338 | /* this function has more OUT parameters */ 339 | PLpgSQL_row *row = (PLpgSQL_row*) var; 340 | int fnum; 341 | 342 | for (fnum = 0; fnum < row->nfields; fnum++) 343 | { 344 | int varno2 = row->varnos[fnum]; 345 | PLpgSQL_variable *var2 = (PLpgSQL_variable *) estate->datums[varno2]; 346 | StringInfoData message; 347 | 348 | if (var2->dtype == PLPGSQL_DTYPE_ROW || 349 | var2->dtype == PLPGSQL_DTYPE_REC) 350 | { 351 | /* 352 | * The result of function with more OUT variables (and one 353 | * should be an composite), is not possible simply assign to 354 | * outer variables. The related expression cannot be "simple" 355 | * expression, and then an evaluation is 10x slower. So there 356 | * is warning 357 | */ 358 | initStringInfo(&message); 359 | appendStringInfo(&message, 360 | OUT_COMPOSITE_IS_NOT_SINGLE_TEXT, var2->refname); 361 | plpgsql_check_put_error(cstate, 362 | 0, 0, 363 | message.data, 364 | NULL, 365 | NULL, 366 | PLPGSQL_CHECK_WARNING_EXTRA, 367 | 0, NULL, NULL); 368 | 369 | pfree(message.data); 370 | message.data = NULL; 371 | } 372 | 373 | if (!datum_is_used(cstate, varno2, true)) 374 | { 375 | const char *fmt = cstate->found_return_dyn_query ? 376 | MAYBE_UNMODIFIED_VARIABLE_TEXT : UNMODIFIED_VARIABLE_TEXT; 377 | 378 | const char *detail = cstate->found_return_dyn_query ? 379 | "cannot to determine result of dynamic SQL" : NULL; 380 | 381 | initStringInfo(&message); 382 | appendStringInfo(&message, fmt, var2->refname); 383 | plpgsql_check_put_error(cstate, 384 | 0, 0, 385 | message.data, 386 | detail, 387 | NULL, 388 | PLPGSQL_CHECK_WARNING_EXTRA, 389 | 0, NULL, NULL); 390 | 391 | pfree(message.data); 392 | message.data = NULL; 393 | } 394 | } 395 | } 396 | else 397 | { 398 | if (!datum_is_used(cstate, varno, true)) 399 | { 400 | StringInfoData message; 401 | 402 | const char *fmt = cstate->found_return_dyn_query ? 403 | MAYBE_UNMODIFIED_VARIABLE_TEXT : UNMODIFIED_VARIABLE_TEXT; 404 | 405 | const char *detail = cstate->found_return_dyn_query ? 406 | "cannot to determine result of dynamic SQL" : NULL; 407 | 408 | initStringInfo(&message); 409 | 410 | appendStringInfo(&message, fmt, var->refname); 411 | plpgsql_check_put_error(cstate, 412 | 0, 0, 413 | message.data, 414 | detail, 415 | NULL, 416 | PLPGSQL_CHECK_WARNING_EXTRA, 417 | 0, NULL, NULL); 418 | 419 | pfree(message.data); 420 | message.data = NULL; 421 | } 422 | } 423 | } 424 | } 425 | } 426 | 427 | /* 428 | * Report too high volatility 429 | * 430 | */ 431 | void 432 | plpgsql_check_report_too_high_volatility(PLpgSQL_checkstate *cstate) 433 | { 434 | if (cstate->cinfo->performance_warnings && !cstate->skip_volatility_check) 435 | { 436 | char *current = NULL; 437 | char *should_be = NULL; 438 | bool raise_warning = false; 439 | 440 | if (cstate->volatility == PROVOLATILE_IMMUTABLE && 441 | (cstate->decl_volatility == PROVOLATILE_VOLATILE || 442 | cstate->decl_volatility == PROVOLATILE_STABLE)) 443 | { 444 | should_be = "IMMUTABLE"; 445 | current = cstate->decl_volatility == PROVOLATILE_VOLATILE ? 446 | "VOLATILE" : "STABLE"; 447 | raise_warning = true; 448 | } 449 | else if (cstate->volatility == PROVOLATILE_STABLE && 450 | (cstate->decl_volatility == PROVOLATILE_VOLATILE)) 451 | { 452 | if (cstate->cinfo->rettype != VOIDOID) 453 | { 454 | should_be = "STABLE"; 455 | current = "VOLATILE"; 456 | raise_warning = true; 457 | } 458 | } 459 | 460 | if (raise_warning) 461 | { 462 | StringInfoData message; 463 | 464 | initStringInfo(&message); 465 | 466 | appendStringInfo(&message, "routine is marked as %s, should be %s", current, should_be); 467 | plpgsql_check_put_error(cstate, 468 | 0, -1, 469 | message.data, 470 | cstate->has_execute_stmt ? "attention: cannot to determine volatility of used dynamic SQL" : NULL, 471 | "When you fix this issue, please, recheck other functions that uses this function.", 472 | PLPGSQL_CHECK_WARNING_PERFORMANCE, 473 | 0, NULL, NULL); 474 | 475 | pfree(message.data); 476 | message.data = NULL; 477 | } 478 | } 479 | } 480 | --------------------------------------------------------------------------------