pax_global_header00006660000000000000000000000064137455430610014522gustar00rootroot0000000000000052 comment=223f9150306e00ef7706119a71f0c52385125a99 omnidb-plpgsql-debugger/000077500000000000000000000000001374554306100156005ustar00rootroot00000000000000omnidb-plpgsql-debugger/.gitignore000066400000000000000000000007151374554306100175730ustar00rootroot00000000000000# Prerequisites *.d # Object files *.o *.ko *.obj *.elf # Linker output *.ilk *.map *.exp # Precompiled Headers *.gch *.pch # Libraries *.lib *.a *.la *.lo # Shared objects (inc. Windows DLLs) *.dll *.so *.so.* *.dylib # Executables *.exe *.out *.app *.i*86 *.x86_64 *.hex # Debug files *.dSYM/ *.su *.idb *.pdb # Kernel Module Compile Results *.mod* *.cmd .tmp_versions/ modules.order Module.symvers Mkfile.old dkms.conf # CLion project folder .idea omnidb-plpgsql-debugger/LICENSE000066400000000000000000000020601374554306100166030ustar00rootroot00000000000000MIT License Copyright (c) 2020 The OmniDB Team Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. omnidb-plpgsql-debugger/META.json000066400000000000000000000020751374554306100172250ustar00rootroot00000000000000{ "name": "omnidb_plpgsql_debugger", "abstract": "PostgreSQL extension for enabling PL/pgSQL debugger in OmniDB", "version": "1.0.0", "maintainer": [ "William Ivanski ", "Rafael Thofehrn Castro -c 'CREATE EXTENSION omnidb_plpgsql_debugger' ``` Create sample functions (optional): ```bash psql -d -f sample_functions.sql ``` Grant privileges to each database user that will debug functions (should be done by a superuser): ```bash psql -d -c 'GRANT ALL ON SCHEMA omnidb TO ; GRANT ALL ON ALL TABLES IN SCHEMA omnidb TO ;' ``` Enable passwordless access to each database user that will debug functions. This is needed because the database will create an additional local connection to perform debugging operations. We need to add a rule to *pg_hba.conf* of type `host`, matching the PostgreSQL user and database OmniDB is connected to. The method can be either `trust`, which is insecure and not recommended, or `md5`. - For `trust`, add rules similar to: ```bash # TYPE DATABASE USER ADDRESS METHOD host 127.0.0.1/32 trust host ::1/128 trust ``` - For `md5`, add rules similar to: ```bash # TYPE DATABASE USER ADDRESS METHOD host 127.0.0.1/32 md5 host ::1/128 md5 ``` - Create a `.pgpass` file with a similar content: ```bash localhost:::: ``` More information about how `.pgpass` works can be found here: https://www.postgresql.org/docs/current/libpq-pgpass.html ## Considerations for Debian/Ubuntu and derivatives On Debian, Ubuntu and derivatives, you can install the `omnidb_plpgsql_debugger` extension pre-compiled from Debian PGDG repository. First you need to install the PGDG repository (if not installed already): ```bash sudo echo "deb http://apt.postgresql.org/pub/repos/apt/ $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list sudo wget --quiet -O - https://apt.postgresql.org/pub/repos/apt/ACCC4CF8.asc | apt-key add - sudo apt update ``` Then you install the package for your specific PostgreSQL version like this: ```bash sudo apt install postgresql-X.Y-omnidb ``` Now you can run `CREATE EXTENSION omnidb_plpgsql_debugger` in the database of choice. ## Considerations for Mac OS X To compile the debugger on Mac OS X, first you need to install SDK headers for Mac OS: ```bash sudo installer -pkg /Library/Developer/CommandLineTools/Packages/macOS_SDK_headers_for_macOS_10.14.pkg -target / ``` You also need to install PostgreSQL itself, if not installed. An easy way is to install it from Homebrew. This will also install PostgreSQL headers and libpq. If brew is not installed yet, you can install it like this: ```bash /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" ``` Then: ```bash brew install postgresql ``` Now you can compile the extension. ## Considerations for Windows Compiling PostgreSQL itself or PostgreSQL extensions can be challenging. For more information you can relate to this post: https://www.2ndquadrant.com/en/blog/compiling-postgresql-extensions-visual-studio-windows/ omnidb-plpgsql-debugger/clean.sh000077500000000000000000000000171374554306100172170ustar00rootroot00000000000000rm -f *.o *.so omnidb-plpgsql-debugger/compile.sh000077500000000000000000000010341374554306100175650ustar00rootroot00000000000000#!/bin/bash PGVERSION=13 rm -f *.o *.so gcc -fPIC -c -o omnidb_plpgsql_debugger.o omnidb_plpgsql_debugger.c -lpq -I /usr/include/postgresql -I /usr/include/postgresql/$PGVERSION/server gcc -fPIC -o omnidb_plpgsql_debugger.so omnidb_plpgsql_debugger.o -lpq -shared # debug mode #rm -f *.o *.so #gcc -D DEBUG -fPIC -c -o omnidb_plpgsql_debugger.o omnidb_plpgsql_debugger.c -lpq -I /usr/include/postgresql -I /usr/include/postgresql/$PGVERSION/server #gcc -D DEBUG -fPIC -o omnidb_plpgsql_debugger.so omnidb_plpgsql_debugger.o -lpq -shared omnidb-plpgsql-debugger/deploy/000077500000000000000000000000001374554306100170745ustar00rootroot00000000000000omnidb-plpgsql-debugger/deploy/linux/000077500000000000000000000000001374554306100202335ustar00rootroot00000000000000omnidb-plpgsql-debugger/deploy/linux/build_images.sh000077500000000000000000000002101374554306100232070ustar00rootroot00000000000000#!/bin/bash cd tarbuild docker build -t omnidb/debugger_tarbuild . cd .. cd pkgbuild docker build -t omnidb/debugger_pkgbuild . cd .. omnidb-plpgsql-debugger/deploy/linux/deploy.sh000077500000000000000000000004551374554306100220720ustar00rootroot00000000000000#!/bin/bash docker run -e REPO="git://github.com/OmniDB/plpgsql_debugger" -e VERSION="3.0.0" -v $PWD:/tmp --rm omnidb/debugger_tarbuild docker run -e VERSION="3.0.0" -v $PWD:/tmp --rm omnidb/debugger_pkgbuild sudo chown $USER:$USER *.tar.gz sudo chown $USER:$USER *.deb sudo chown $USER:$USER *.rpm omnidb-plpgsql-debugger/deploy/linux/pkgbuild/000077500000000000000000000000001374554306100220345ustar00rootroot00000000000000omnidb-plpgsql-debugger/deploy/linux/pkgbuild/Dockerfile000066400000000000000000000005241374554306100240270ustar00rootroot00000000000000FROM debian:stretch-slim USER root ENV HOME /root WORKDIR /root SHELL ["/bin/bash", "-c"] ENV DEBIAN_FRONTEND=noninteractive RUN apt-get -y update \ && apt-get -y install rubygems ruby-dev build-essential rpm \ && gem install fpm ARG VERSION="3.0.0" ENV VERSION=$VERSION COPY entrypoint.sh $HOME ENTRYPOINT ["/root/entrypoint.sh"] omnidb-plpgsql-debugger/deploy/linux/pkgbuild/entrypoint.sh000077500000000000000000000027511374554306100246130ustar00rootroot00000000000000#!/bin/bash # Checking environment echo "VERSION=$VERSION" ##################### # Preparing directory ##################### # Extracting tar.gz cd $HOME cp /tmp/omnidb-plpgsql-debugger_$VERSION.tar.gz . tar -xzvf omnidb-plpgsql-debugger_$VERSION.tar.gz mv omnidb-plpgsql-debugger_$VERSION tmp rm -rf omnidb-plpgsql-debugger_$VERSION.tar.gz # Creating directory structure mkdir omnidb-plpgsql-debugger_$VERSION cd omnidb-plpgsql-debugger_$VERSION/ mkdir -p opt/omnidb-plpgsql-debugger cp $HOME/tmp/* opt/omnidb-plpgsql-debugger/ rm -rf $HOME/tmp/ mkdir DEBIAN cat > DEBIAN/control << EOF Package: omnidb-plpgsql-debugger Version: $VERSION Section: base Priority: optional Architecture: amd64 Installed-Size: $(du -s) Maintainer: The OmniDB Team Homepage: http://omnidb.org Description: OmniDB is a very flexible, secure and work-effective environment for multiple DBMS. The OmniDB PL/pgSQL debugger is a PostgreSQL extension that adds debugging capabilities for PL/pgSQL functions and procedures. OmniDB is supported by 2ndQuadrant (http://www.2ndquadrant.com) EOF cd .. ######################## # Generating deb package ######################## dpkg -b omnidb-plpgsql-debugger_$VERSION ######################## # Generating rpm package ######################## fpm -s deb -t rpm omnidb-plpgsql-debugger_$VERSION.deb ############################################# # Moving packages to outside of the container ############################################# mv omnidb*.deb /tmp/ mv omnidb*.rpm /tmp/ omnidb-plpgsql-debugger/deploy/linux/tarbuild/000077500000000000000000000000001374554306100220415ustar00rootroot00000000000000omnidb-plpgsql-debugger/deploy/linux/tarbuild/Dockerfile000066400000000000000000000004431374554306100240340ustar00rootroot00000000000000FROM pgappbuild USER root ENV HOME /root WORKDIR /root SHELL ["/bin/bash", "-c"] ARG REPO="git://github.com/OmniDB/plpgsql_debugger" ARG BRANCH="main" ARG VERSION="3.0.0" ENV REPO=$REPO ENV BRANCH=$BRANCH ENV VERSION=$VERSION COPY entrypoint.sh $HOME ENTRYPOINT ["/root/entrypoint.sh"] omnidb-plpgsql-debugger/deploy/linux/tarbuild/entrypoint.sh000077500000000000000000000057631374554306100246260ustar00rootroot00000000000000#!/bin/bash # Checking environment echo "REPO=$REPO" echo "BRANCH=$BRANCH" echo "VERSION=$VERSION" # Cloning repo git clone $REPO --depth 1 -b $BRANCH omnidb-plpgsql-debugger_$VERSION # Cleaning cd omnidb-plpgsql-debugger_$VERSION/ rm -f *.o *.so # Compiling for PostgreSQL 9.3 rm -f *.o $CC -fPIC -c -o omnidb_plpgsql_debugger.o omnidb_plpgsql_debugger.c -I /usr/local/pg93/include -I /usr/local/pg93/include/postgresql -I /usr/local/pg93/include/postgresql/server $CC -fPIC -o omnidb_plpgsql_debugger_93.so omnidb_plpgsql_debugger.o -L /usr/local/pg93/lib -lpq -shared # Compiling for PostgreSQL 9.4 rm -f *.o $CC -fPIC -c -o omnidb_plpgsql_debugger.o omnidb_plpgsql_debugger.c -I /usr/local/pg94/include -I /usr/local/pg94/include/postgresql -I /usr/local/pg94/include/postgresql/server $CC -fPIC -o omnidb_plpgsql_debugger_94.so omnidb_plpgsql_debugger.o -L /usr/local/pg94/lib -lpq -shared # Compiling for PostgreSQL 9.5 rm -f *.o $CC -fPIC -c -o omnidb_plpgsql_debugger.o omnidb_plpgsql_debugger.c -I /usr/local/pg95/include -I /usr/local/pg95/include/postgresql -I /usr/local/pg95/include/postgresql/server $CC -fPIC -o omnidb_plpgsql_debugger_95.so omnidb_plpgsql_debugger.o -L /usr/local/pg95/lib -lpq -shared # Compiling for PostgreSQL 9.6 rm -f *.o $CC -fPIC -c -o omnidb_plpgsql_debugger.o omnidb_plpgsql_debugger.c -I /usr/local/pg96/include -I /usr/local/pg96/include/postgresql -I /usr/local/pg96/include/postgresql/server $CC -fPIC -o omnidb_plpgsql_debugger_96.so omnidb_plpgsql_debugger.o -L /usr/local/pg96/lib -lpq -shared # Compiling for PostgreSQL 10 rm -f *.o $CC -fPIC -c -o omnidb_plpgsql_debugger.o omnidb_plpgsql_debugger.c -I /usr/local/pg10/include -I /usr/local/pg10/include/postgresql -I /usr/local/pg10/include/postgresql/server $CC -fPIC -o omnidb_plpgsql_debugger_10.so omnidb_plpgsql_debugger.o -L /usr/local/pg10/lib -lpq -shared # Compiling for PostgreSQL 11 rm -f *.o $CC -fPIC -c -o omnidb_plpgsql_debugger.o omnidb_plpgsql_debugger.c -I /usr/local/pg11/include -I /usr/local/pg11/include/postgresql -I /usr/local/pg11/include/postgresql/server $CC -fPIC -o omnidb_plpgsql_debugger_11.so omnidb_plpgsql_debugger.o -L /usr/local/pg11/lib -lpq -shared # Compiling for PostgreSQL 12 rm -f *.o $CC -fPIC -c -o omnidb_plpgsql_debugger.o omnidb_plpgsql_debugger.c -I /usr/local/pg12/include -I /usr/local/pg12/include/postgresql -I /usr/local/pg12/include/postgresql/server $CC -fPIC -o omnidb_plpgsql_debugger_12.so omnidb_plpgsql_debugger.o -L /usr/local/pg12/lib -lpq -shared # Compiling for PostgreSQL 13 rm -f *.o $CC -fPIC -c -o omnidb_plpgsql_debugger.o omnidb_plpgsql_debugger.c -I /usr/local/pg13/include -I /usr/local/pg13/include/postgresql -I /usr/local/pg13/include/postgresql/server $CC -fPIC -o omnidb_plpgsql_debugger_13.so omnidb_plpgsql_debugger.o -L /usr/local/pg13/lib -lpq -shared # Cleaning rm -rf *.o *.sh deploy/ .git/ .gitignore # Building tarball cd .. tar -czvf omnidb-plpgsql-debugger_$VERSION.tar.gz omnidb-plpgsql-debugger_$VERSION/ mv omnidb-plpgsql-debugger_$VERSION.tar.gz /tmp/ omnidb-plpgsql-debugger/omnidb_plpgsql_debugger--1.0.0.sql000066400000000000000000000021551374554306100237110ustar00rootroot00000000000000\echo Use "CREATE EXTENSION omnidb_plpgsql_debugger" to load this file. \quit CREATE SCHEMA omnidb; CREATE FUNCTION omnidb.omnidb_enable_debugger(character varying) RETURNS void AS '$libdir/omnidb_plpgsql_debugger' LANGUAGE C IMMUTABLE STRICT; CREATE TABLE omnidb.contexts ( pid INTEGER NOT NULL PRIMARY KEY, function TEXT, hook TEXT, lineno INTEGER, stmttype TEXT, breakpoint INTEGER NOT NULL, finished BOOLEAN ); CREATE TABLE omnidb.variables ( pid INTEGER NOT NULL, name TEXT, attribute TEXT, vartype TEXT, value TEXT ); ALTER TABLE omnidb.variables ADD CONSTRAINT omnidb_variables_contexts_fk FOREIGN KEY (pid) REFERENCES omnidb.contexts (pid) ON DELETE CASCADE; CREATE TABLE omnidb.statistics ( pid INTEGER NOT NULL, lineno INTEGER, step INTEGER, tstart TIMESTAMP WITHOUT TIME ZONE, tend TIMESTAMP WITHOUT TIME ZONE ); ALTER TABLE omnidb.statistics ADD CONSTRAINT omnidb_statistics_contexts_fk FOREIGN KEY (pid) REFERENCES omnidb.contexts (pid) ON DELETE CASCADE; omnidb-plpgsql-debugger/omnidb_plpgsql_debugger.c000066400000000000000000000476071374554306100226400ustar00rootroot00000000000000/********************************************************************** MIT License Copyright (c) 2020 The OmniDB Team Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. **********************************************************************/ /********************************************************************** * Headers **********************************************************************/ #include #include #include #include "postgres.h" #if PG_VERSION_NUM >= 90300 #include "access/htup_details.h" #endif #include "utils/syscache.h" #if PG_VERSION_NUM >= 110000 #include "utils/expandedrecord.h" #endif #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "plpgsql.h" #include "miscadmin.h" #include "libpq-fe.h" #include "fmgr.h" #include "utils/builtins.h" #include "omnidb_plpgsql_debugger.h" PG_MODULE_MAGIC; /********************************************************************** * Function prototypes **********************************************************************/ void load_plugin( PLpgSQL_plugin * hooks ); static void profiler_init(PLpgSQL_execstate * estate, PLpgSQL_function * func); static void profiler_func_beg(PLpgSQL_execstate * estate, PLpgSQL_function * func); static void profiler_func_end(PLpgSQL_execstate * estate, PLpgSQL_function * func); static void profiler_stmt_beg(PLpgSQL_execstate * estate, PLpgSQL_stmt * stmt); static void profiler_stmt_end(PLpgSQL_execstate * estate, PLpgSQL_stmt * stmt); static char * findProcName(Oid oid); char *decode_stmt_type(int typ); static bool var_is_argument(PLpgSQL_execstate *estate, int i, char **p_argname); static bool var_is_null(PLpgSQL_datum *datum); static char *get_text_val(PLpgSQL_var *var, char **name, char **type); static void update_variables( PLpgSQL_execstate * estate ); /********************************************************************** * Other variables **********************************************************************/ static PLpgSQL_plugin plugin_funcs = { profiler_init, profiler_func_beg, profiler_func_end, profiler_stmt_beg, profiler_stmt_end }; PGconn *omnidb_plugin_conn; bool omnidb_plugin_active = false; unsigned int omnidb_plugin_depth = -1; unsigned int omnidb_plugin_step; int omnidb_plugin_breakpoint; char omnidb_plugin_conninfo[1024]; /********************************************************************** * Function definitions **********************************************************************/ /* ------------------------------------------------------------------- * _PG_init() * ------------------------------------------------------------------*/ void _PG_init( void ) { PLpgSQL_plugin ** var_ptr = (PLpgSQL_plugin **) find_rendezvous_variable("PLpgSQL_plugin"); *var_ptr = &plugin_funcs; } /* ------------------------------------------------------------------- * load_plugin() * ------------------------------------------------------------------*/ void load_plugin( PLpgSQL_plugin * hooks ) { hooks->func_setup = profiler_init; hooks->func_beg = profiler_func_beg; hooks->func_end = profiler_func_end; hooks->stmt_beg = profiler_stmt_beg; hooks->stmt_end = profiler_stmt_end; } /* ------------------------------------------------------------------- * omnidb_enable_debugger() * ------------------------------------------------------------------*/ PG_FUNCTION_INFO_V1(omnidb_enable_debugger); Datum omnidb_enable_debugger(PG_FUNCTION_ARGS) { omnidb_plugin_active = true; sprintf(omnidb_plugin_conninfo, "%s", text_to_cstring(PG_GETARG_TEXT_P(0))); PG_RETURN_VOID(); } /* ------------------------------------------------------------------- * profiler_init() * ------------------------------------------------------------------*/ static void profiler_init( PLpgSQL_execstate * estate, PLpgSQL_function * func ) { #ifdef DEBUG elog(LOG, "omnidb, DECLARE, %s, %i", findProcName(func->fn_oid), MyProcPid); #endif } /* ------------------------------------------------------------------- * profiler_func_beg() * ------------------------------------------------------------------*/ static void profiler_func_beg( PLpgSQL_execstate * estate, PLpgSQL_function * func ) { #ifdef DEBUG elog(LOG, "omnidb, BEGIN, %s, %i", findProcName(func->fn_oid), MyProcPid); #endif if (omnidb_plugin_active) { omnidb_plugin_depth++; //First call if (omnidb_plugin_depth == 0) { omnidb_plugin_conn = PQconnectdb(omnidb_plugin_conninfo); if (PQstatus(omnidb_plugin_conn) != CONNECTION_BAD) { #ifdef DEBUG elog(LOG, "omnidb: Connected to (%s)", omnidb_plugin_conninfo); #endif char select_context[256]; sprintf(select_context, "SELECT pid FROM omnidb.contexts WHERE pid = %i", MyProcPid); PGresult *res = PQexec(omnidb_plugin_conn, select_context); if (PQresultStatus(res) == PGRES_TUPLES_OK && PQntuples(res) == 1) { char update_context[1024]; sprintf(update_context, "UPDATE omnidb.contexts SET function = '%s', hook = 'func_beg', stmttype = 'BEGIN', lineno = NULL where pid = %i", findProcName(func->fn_oid), MyProcPid); PQexec(omnidb_plugin_conn, update_context); #ifdef DEBUG elog(LOG, "omnidb: Debugger active for PID %i", MyProcPid); #endif omnidb_plugin_active = true; omnidb_plugin_step = 0; } else { omnidb_plugin_active = false; #ifdef DEBUG elog(LOG, "omnidb: Debugger not active for PID %i", MyProcPid); #endif } } else { omnidb_plugin_active = false; elog(ERROR, "omnidb: Connection to database failed: %s", PQerrorMessage(omnidb_plugin_conn)); } } else { #ifdef DEBUG elog(LOG, "omnidb: Debugger not active for subcall in PID %i", MyProcPid); #endif } } else { #ifdef DEBUG elog(LOG, "omnidb: Debugger not active for PID %i", MyProcPid); #endif } } /* ------------------------------------------------------------------- * profiler_func_end() * ------------------------------------------------------------------*/ static void profiler_func_end( PLpgSQL_execstate * estate, PLpgSQL_function * func ) { #ifdef DEBUG elog(LOG, "omnidb, END, %s, %i", findProcName(func->fn_oid), MyProcPid); #endif if (omnidb_plugin_active && !omnidb_plugin_depth) { update_variables(estate); char update_context[256]; sprintf(update_context, "UPDATE omnidb.contexts SET finished = true WHERE pid = %i", MyProcPid); PQexec(omnidb_plugin_conn, update_context); char unlock[256]; sprintf(unlock, "SELECT pg_advisory_unlock(%i) FROM omnidb.contexts WHERE pid = %i", MyProcPid, MyProcPid); PQexec(omnidb_plugin_conn, unlock); PQfinish(omnidb_plugin_conn); } else { if (omnidb_plugin_active && omnidb_plugin_depth > 0) omnidb_plugin_depth--; } } /* ------------------------------------------------------------------- * profiler_stmt_beg() * ------------------------------------------------------------------*/ static void profiler_stmt_beg( PLpgSQL_execstate * estate, PLpgSQL_stmt * stmt ) { #ifdef DEBUG elog(LOG, "omnidb, STMT START, line %d, type %s, %s, %i", stmt->lineno, decode_stmt_type(stmt->cmd_type), findProcName(estate->func->fn_oid), MyProcPid); #endif if (omnidb_plugin_active && !omnidb_plugin_depth) { char select_breakpoint[256]; sprintf(select_breakpoint, "SELECT breakpoint FROM omnidb.contexts WHERE pid = %i", MyProcPid); PGresult *res = PQexec(omnidb_plugin_conn, select_breakpoint); if (PQresultStatus(res) == PGRES_TUPLES_OK && PQntuples(res) == 1) omnidb_plugin_breakpoint = atoi(PQgetvalue(res, 0, 0)); else omnidb_plugin_breakpoint = 0; if ((omnidb_plugin_breakpoint == 0 || omnidb_plugin_breakpoint == stmt->lineno) && ((stmt->cmd_type == 11 && stmt->lineno != 0) || (stmt->cmd_type != 11))) { update_variables(estate); char update_context[1024]; sprintf(update_context, "UPDATE omnidb.contexts SET function = '%s', hook = 'stmt_beg', stmttype = '%s', lineno = %d where pid = %i", findProcName(estate->func->fn_oid), decode_stmt_type(stmt->cmd_type), stmt->lineno, MyProcPid); PQexec(omnidb_plugin_conn, update_context); char unlock[256]; sprintf(unlock, "SELECT pg_advisory_unlock(%i) FROM omnidb.contexts WHERE pid = %i", MyProcPid, MyProcPid); PQexec(omnidb_plugin_conn, unlock); char lock[256]; sprintf(lock, "SELECT pg_advisory_lock(%i) FROM omnidb.contexts WHERE pid = %i", MyProcPid, MyProcPid); PQexec(omnidb_plugin_conn, lock); } char insert_statistics[256]; sprintf(insert_statistics, "INSERT INTO omnidb.statistics (pid, lineno, step, tstart, tend) VALUES (%i, %i, %i, now(), NULL)", MyProcPid, stmt->lineno, omnidb_plugin_step); PQexec(omnidb_plugin_conn, insert_statistics); } } /* ------------------------------------------------------------------- * profiler_stmt_end() * ------------------------------------------------------------------*/ static void profiler_stmt_end( PLpgSQL_execstate * estate, PLpgSQL_stmt * stmt ) { #ifdef DEBUG elog(LOG, "omnidb, STMT END, line %d, type %s, %s, %i", stmt->lineno, decode_stmt_type(stmt->cmd_type), findProcName(estate->func->fn_oid), MyProcPid); #endif if (omnidb_plugin_active && !omnidb_plugin_depth) { char update_statistics[256]; sprintf(update_statistics, "UPDATE omnidb.statistics SET tend = now() WHERE pid = %i AND lineno = %i AND step = %i", MyProcPid, stmt->lineno, omnidb_plugin_step); PQexec(omnidb_plugin_conn, update_statistics); omnidb_plugin_step++; } } /* ------------------------------------------------------------------- * findProcName() * ------------------------------------------------------------------*/ static char * findProcName(Oid oid) { HeapTuple procTuple; char *procName; procTuple = SearchSysCache(PROCOID, ObjectIdGetDatum(oid), 0, 0, 0); if(!HeapTupleIsValid(procTuple)) elog(ERROR, "omnidb: cache lookup for proc %u failed", oid); procName = NameStr(((Form_pg_proc) GETSTRUCT(procTuple))->proname); ReleaseSysCache(procTuple); return procName; } /* ------------------------------------------------------------------- * decode_stmt_type() * ------------------------------------------------------------------*/ char *decode_stmt_type(int typ) { char *decoded_type = "unknown"; switch (typ) { case PLPGSQL_STMT_BLOCK: decoded_type = "BLOCK"; break; case PLPGSQL_STMT_ASSIGN: decoded_type = "ASSIGN"; break; case PLPGSQL_STMT_PERFORM: decoded_type = "PERFORM"; break; case PLPGSQL_STMT_GETDIAG: decoded_type = "GETDIAG"; break; case PLPGSQL_STMT_IF: decoded_type = "IF"; break; case PLPGSQL_STMT_CASE: decoded_type = "CASE"; break; case PLPGSQL_STMT_LOOP: decoded_type = "LOOP"; break; case PLPGSQL_STMT_WHILE: decoded_type = "WHILE"; break; case PLPGSQL_STMT_FORI: decoded_type = "FORI"; break; case PLPGSQL_STMT_FORS: decoded_type = "FORS"; break; case PLPGSQL_STMT_FORC: decoded_type = "FORC"; break; case PLPGSQL_STMT_EXIT: decoded_type = "EXIT"; break; case PLPGSQL_STMT_RETURN: decoded_type = "RETURN"; break; case PLPGSQL_STMT_RETURN_NEXT: decoded_type = "RETURN NEXT"; break; case PLPGSQL_STMT_RETURN_QUERY: decoded_type = "RETURN QUERY"; break; case PLPGSQL_STMT_RAISE: decoded_type = "RAISE"; break; case PLPGSQL_STMT_EXECSQL: decoded_type = "EXEC SQL"; break; case PLPGSQL_STMT_DYNEXECUTE: decoded_type = "DYNEXECUTE"; break; case PLPGSQL_STMT_DYNFORS: decoded_type = "DYNFORS"; break; case PLPGSQL_STMT_OPEN: decoded_type = "OPEN"; break; case PLPGSQL_STMT_FETCH: decoded_type = "FETCH"; break; case PLPGSQL_STMT_CLOSE: decoded_type = "CLOSE"; break; } return decoded_type; } /* ------------------------------------------------------------------- * var_is_null() * ------------------------------------------------------------------*/ static bool var_is_null(PLpgSQL_datum *datum) { switch (datum->dtype) { case PLPGSQL_DTYPE_VAR: { PLpgSQL_var *var = (PLpgSQL_var *) datum; if (var->isnull) return true; } break; case PLPGSQL_DTYPE_REC: case PLPGSQL_DTYPE_ROW: return true; default: return true; } return false; } /* ------------------------------------------------------------------- * var_is_null() * ------------------------------------------------------------------*/ static char *get_text_val(PLpgSQL_var *var, char **name, char **type) { HeapTuple typeTup; Form_pg_type typeStruct; FmgrInfo finfo_output; char * text_value = NULL; typeTup = SearchSysCache( TYPEOID, ObjectIdGetDatum( var->datatype->typoid ), 0, 0, 0 ); if( !HeapTupleIsValid( typeTup )) return( NULL ); typeStruct = (Form_pg_type)GETSTRUCT( typeTup ); fmgr_info( typeStruct->typoutput, &finfo_output ); text_value = DatumGetCString( FunctionCall3( &finfo_output, var->value, ObjectIdGetDatum(typeStruct->typelem), Int32GetDatum(-1))); ReleaseSysCache( typeTup ); if( name ) *name = var->refname; if( type ) *type = var->datatype->typname; return( text_value ); } /* ------------------------------------------------------------------- * update_variables() * ------------------------------------------------------------------*/ static void update_variables( PLpgSQL_execstate * estate ) { char delete_variables[256]; sprintf(delete_variables, "DELETE FROM omnidb.variables WHERE pid = %i", MyProcPid); PQexec(omnidb_plugin_conn, delete_variables); int i; for( i = 0; i < estate->ndatums; i++ ) { switch( estate->datums[i]->dtype ) { case PLPGSQL_DTYPE_VAR: { PLpgSQL_var * var = (PLpgSQL_var *) estate->datums[i]; char * val; char * name = var->refname; char * typeName; typeName = var->datatype ? var->datatype->typname : "InvalidType"; if( var_is_null((PLpgSQL_datum *)var )) val = "NULL"; else val = get_text_val( var, NULL, NULL ); #ifdef DEBUG elog(LOG, "omnidb, VAR, (%s %s %s)", name, typeName, val); #endif char insert_variable[1024]; sprintf(insert_variable, "INSERT INTO omnidb.variables (pid, name, attribute, vartype, value) VALUES (%i, '%s', NULL, '%s', '%s')", MyProcPid, name, typeName, val); PQexec(omnidb_plugin_conn, insert_variable); break; } case PLPGSQL_DTYPE_REC: { PLpgSQL_rec * rec = (PLpgSQL_rec *) estate->datums[i]; char * typeName; int att; char * val; #if PG_VERSION_NUM >= 110000 HeapTuple tup; TupleDesc tupdesc; if (rec->erh != NULL) { ExpandedRecordHeader * erh = (ExpandedRecordHeader *) rec->erh; tup = expanded_record_get_tuple(erh); tupdesc = expanded_record_get_tupdesc(erh); if (tupdesc != NULL) { for( att = 0; att < tupdesc->natts; ++att ) { typeName = SPI_gettype( tupdesc, att + 1 ); val = SPI_getvalue( tup, tupdesc, att + 1 ); if (!val) val = "NULL"; #ifdef DEBUG elog(LOG, "omnidb, REC, (%s.%s %s %s)", rec->refname, NameStr( tupdesc->attrs[att].attname ), typeName, val ); #endif char insert_variable[1024]; sprintf(insert_variable, "INSERT INTO omnidb.variables (pid, name, attribute, vartype, value) VALUES (%i, '%s', '%s', '%s', '%s')", MyProcPid, rec->refname, NameStr( tupdesc->attrs[att].attname ), typeName, val); PQexec(omnidb_plugin_conn, insert_variable); if( typeName ) pfree( typeName ); } } } #else if (rec->tupdesc != NULL) { for( att = 0; att < rec->tupdesc->natts; ++att ) { typeName = SPI_gettype( rec->tupdesc, att + 1 ); val = SPI_getvalue( rec->tup, rec->tupdesc, att + 1 ); if (!val) val = "NULL"; #ifdef DEBUG elog(LOG, "omnidb, REC, (%s.%s %s %s)", rec->refname, NameStr( rec->tupdesc->attrs[att]->attname ), typeName, val ); #endif char insert_variable[1024]; sprintf(insert_variable, "INSERT INTO omnidb.variables (pid, name, attribute, vartype, value) VALUES (%i, '%s', '%s', '%s', '%s')", MyProcPid, rec->refname, NameStr( rec->tupdesc->attrs[att]->attname ), typeName, val); PQexec(omnidb_plugin_conn, insert_variable); if( typeName ) pfree( typeName ); } } #endif break; } default: break; } } } omnidb-plpgsql-debugger/omnidb_plpgsql_debugger.control000066400000000000000000000002541374554306100240610ustar00rootroot00000000000000comment = 'PostgreSQL extension for enabling PL/pgSQL debugger in OmniDB' default_version = '1.0.0' module_pathname = '$libdir/omnidb_plpgsql_debugger' relocatable = false omnidb-plpgsql-debugger/omnidb_plpgsql_debugger.h000066400000000000000000000004561374554306100226340ustar00rootroot00000000000000#ifdef _MSC_VER #ifdef _M_X64 #pragma comment( linker, "/export:_PG_init" ) #pragma comment( linker, "/export:omnidb_enable_debugger" ) #else #pragma comment( linker, "/export:__PG_init" ) #pragma comment( linker, "/export:_omnidb_enable_debugger" ) #endif #endif extern void _PG_init(void);omnidb-plpgsql-debugger/sample_functions.sql000066400000000000000000000116601374554306100216760ustar00rootroot00000000000000-- SIMPLE CREATE OR REPLACE FUNCTION omnidb.function_01(text, text) RETURNS text AS $$ BEGIN RETURN $1 || ' ' || $2; END; $$ LANGUAGE plpgsql; -- SELECT omnidb.function_01('hello', 'world'); -- --> 'hello world' --- -- PARAMETER ALIAS CREATE OR REPLACE FUNCTION omnidb.function_02(int, int) RETURNS int AS $$ DECLARE i ALIAS FOR $1; j ALIAS FOR $2; sum int; BEGIN sum := i + j; RETURN sum; END; $$ LANGUAGE plpgsql; -- SELECT omnidb.function_02(41, 1); -- --> 42 --- -- NAMED PARAMETERS CREATE OR REPLACE FUNCTION omnidb.function_03(i int, j int) RETURNS int AS $$ DECLARE sum int; BEGIN sum := i + j; RETURN sum; END; $$ LANGUAGE plpgsql; -- SELECT omnidb.function_03(41, 1); -- --> 42 --- -- CONTROL STRUCTURES: IF CREATE OR REPLACE FUNCTION omnidb.function_04(i int) RETURNS boolean AS $$ DECLARE tmp int; BEGIN tmp := i % 2; IF tmp = 0 THEN RETURN true; ELSE RETURN false; END IF; END; $$ LANGUAGE plpgsql; -- SELECT omnidb.function_04(3); -- --> f -- SELECT omnidb.function_04(32); -- --> t --- -- CONTROL STRUCTURES: FOR ... LOOP CREATE OR REPLACE FUNCTION omnidb.function_05(i numeric) RETURNS numeric AS $$ DECLARE tmp numeric; result numeric; BEGIN result := 1; FOR tmp IN 1 .. i LOOP result := result * tmp; END LOOP; RETURN result; END; $$ LANGUAGE plpgsql; -- SELECT omnidb.function_05(42::numeric); -- --> 1405006117752879898543142606244511569936384000000000 --- -- CONTROL STRUCTURES: WHILE ... LOOP CREATE OR REPLACE FUNCTION omnidb.function_06(i numeric) RETURNS numeric AS $$ DECLARE tmp numeric; result numeric; BEGIN result := 1; tmp := 1; WHILE tmp <= i LOOP result := result * tmp; tmp := tmp + 1; END LOOP; RETURN result; END; $$ LANGUAGE plpgsql; -- SELECT omnidb.function_06(42::numeric); -- --> 1405006117752879898543142606244511569936384000000000 --- -- RECURSIVE CREATE OR REPLACE FUNCTION omnidb.function_07(i numeric) RETURNS numeric AS $$ BEGIN IF i = 0 THEN RETURN 1; ELSIF i = 1 THEN RETURN 1; ELSE RETURN i * omnidb.function_07(i - 1); END IF; END; $$ LANGUAGE plpgsql; -- SELECT omnidb.function_07(42::numeric); -- --> 1405006117752879898543142606244511569936384000000000 --- -- RECORD TYPES CREATE OR REPLACE FUNCTION omnidb.function_08() RETURNS text AS $$ DECLARE tmp RECORD; BEGIN SELECT INTO tmp 1 + 1 AS a, 2 + 2 AS b; RETURN 'a = ' || tmp.a || '; b = ' || tmp.b; END; $$ LANGUAGE plpgsql; -- SELECT omnidb.function_08(); -- --> 'a = 2; b = 4' --- -- PERFORM CREATE TABLE omnidb.foo(x integer); CREATE OR REPLACE FUNCTION omnidb.function_09_aux() RETURNS void AS $$ INSERT INTO omnidb.foo VALUES (41),(42) $$ LANGUAGE sql; CREATE OR REPLACE FUNCTION omnidb.function_09() RETURNS text AS $$ BEGIN PERFORM omnidb.function_09_aux(); RETURN 'OK'; END; $$ LANGUAGE plpgsql; -- SELECT omnidb.function_09(); -- --> 'OK' -- SELECT * FROM omnidb.foo; -- --> 41, 42 --- -- DYNAMIC SQL CREATE OR REPLACE FUNCTION omnidb.function_10(i int) RETURNS omnidb.foo AS $$ DECLARE rec RECORD; BEGIN EXECUTE 'SELECT * FROM omnidb.foo WHERE x = ' || i INTO rec; RETURN rec; END; $$ LANGUAGE plpgsql; -- SELECT * FROM omnidb.function_10(42); -- --> 42 --- -- CURSORS CREATE OR REPLACE FUNCTION omnidb.function_11() RETURNS numeric AS $$ DECLARE tmp RECORD; result numeric; BEGIN result := 0.00; FOR tmp IN SELECT * FROM omnidb.foo LOOP result := result + tmp.x; END LOOP; RETURN result; END; $$ LANGUAGE plpgsql; -- SELECT omnidb.function_11(); -- --> 83 --- -- ALTERNATIVE FUNCTION TERMINATOR CREATE FUNCTION omnidb.function_12(text) RETURNS text AS 'DECLARE str text; ret text; i integer; len integer; BEGIN str := upper($1); ret := ''''; i := 1; len := length(str); WHILE i <= len LOOP ret := ret || substr(str, i, 1) || '' ''; i := i + 1; END LOOP; RETURN ret; END;' LANGUAGE 'plpgsql'; -- SELECT omnidb.function_12('Hello World'); -- --> 'H E L L O W O R L D' --- -- ERROR HANDLING CREATE OR REPLACE FUNCTION omnidb.function_13(a integer, b integer) RETURNS integer AS $$ BEGIN RETURN a + b; EXCEPTION WHEN numeric_value_out_of_range THEN -- do some important stuff RETURN -1; WHEN OTHERS THEN -- do some other important stuff RETURN -1; END; $$ LANGUAGE plpgsql; --- -- NESTED EXCEPTION BLOCKS CREATE TABLE omnidb.bar(a integer, b text); CREATE FUNCTION omnidb.function_14(key integer, data text) RETURNS void AS $$ BEGIN LOOP UPDATE omnidb.bar SET b = data WHERE a = key; IF found THEN RETURN; END IF; BEGIN INSERT INTO omnidb.bar (a, b) VALUES (key, data); RETURN; EXCEPTION WHEN unique_violation THEN -- do nothing END; END LOOP; EXCEPTION WHEN OTHERS THEN -- do something else END; $$ LANGUAGE plpgsql;