Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

- removed support (and overhead) for PostgreSQL prior to 8.2

- added support for NULL array elements (in and out)
- plugged security hole courtesy of Jeff Greco <jeff.greco@bluehavenmg.com>
  • Loading branch information...
commit f86bec471117f6014ff351906196aff04eafa9f7 1 parent b15f660
jconway authored
View
24 Makefile
@@ -8,10 +8,6 @@ ifeq ($(PORTNAME), darwin)
DYSUFFIX = dylib
endif
-subdir = contrib/plr
-top_builddir = ../..
-include $(top_builddir)/src/Makefile.global
-
# we can only build PL/R if libR is available
# Since there is no official way to determine this,
# we see if there is a file that is named like a shared library.
@@ -38,22 +34,20 @@ SHLIB_LINK += -L$(r_libdir1x) -L$(r_libdir2x) -lR
DATA_built := plr.sql
DOCS := README.plr
REGRESS := plr
-EXTRA_CLEAN := doc/HTML.index expected/plr.out
+EXTRA_CLEAN := doc/HTML.index
+ifdef USE_PGXS
+PGXS := $(shell pg_config --pgxs)
+include $(PGXS)
+else
+subdir = contrib/plr
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
include $(top_srcdir)/contrib/contrib-global.mk
+endif
override CPPFLAGS := -I$(srcdir) -I$(r_includespec) $(CPPFLAGS)
override CPPFLAGS += -DPKGLIBDIR=\"$(pkglibdir)\" -DDLSUFFIX=\"$(DLSUFFIX)\"
-rpath :=
-
-installcheck: plrinstallcheck
-
-plrinstallcheck:
-ifeq ($(findstring 7.3,$(VERSION)),7.3)
- cp -f $(top_builddir)/$(subdir)/expected/plr.out.7.3 $(top_builddir)/$(subdir)/expected/plr.out
-else
- cp -f $(top_builddir)/$(subdir)/expected/plr.out.7.4 $(top_builddir)/$(subdir)/expected/plr.out
-endif
else # can't build
View
12 README.plr
@@ -2,7 +2,7 @@
* PL/R - PostgreSQL support for R as a
* procedural language (PL)
*
- * Copyright (c) 2003, 2004 by Joseph E. Conway
+ * Copyright (c) 2003-2006 by Joseph E. Conway
* ALL RIGHTS RESERVED
*
* Joe Conway <mail@joeconway.com>
@@ -29,16 +29,8 @@
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
-Version 0.1 (10 February, 2003):
- - Initial release
-Version 0.2 (15 February, 2003):
- - Incorporates fixes for lots of bugs and ommissions, in the code and
- docs. Too many to mention, but faithfully recorded in cvs commit
- messages.
-
-Version 0.5 (5 August, 2003) and afterward:
- - See http://www.joeconway.com/plr/ for release notes
+See http://www.joeconway.com/plr/ for release notes
Notes:
- R headers are required. Download and install R prior to building
View
20 doc/plr.sgml
@@ -3,7 +3,7 @@
<title>PL/R User's Guide - R Procedural Language</title>
<bookinfo>
<copyright>
- <year>2003</year>
+ <year>2003-2006</year>
<holder>Joseph E Conway</holder>
</copyright>
</bookinfo>
@@ -67,14 +67,6 @@
</programlisting>
</para>
- <note>
- <para>
- PL/R should build cleanly with PostgreSQL 7.3.x, 7.4.x, and 8.0.x. and
- R 1.6.2 and newer. The current release passed its regression test with
- R 2.0.1 under Fedora Core 2.
- </para>
- </note>
-
<para>
You can use <literal>plr.sql</literal> (which is created in
<literal>contrib/plr</literal>) to create the language and support
@@ -114,16 +106,24 @@ CREATE LANGUAGE plr HANDLER plr_call_handler;
when it was configured, in order for the libR shared object library to be
available.
</para>
+ </tip>
+
+ <tip>
<para>
Additionally, libR must be findable by your runtime linker.
On Linux, this involves adding an entry in /etc/ld.so.conf for the
location of libR (typically $R_HOME/bin), and then running ldconfig.
Refer to <literal>man ldconfig</literal> or its equivalent for your system.
</para>
+ </tip>
+
+ <tip>
<para>
R_HOME must be defined in the environment of the user under which
PostgreSQL is started, <emphasis>before</emphasis> the postmaster
- is started. Otherwise PL/R will refuse to load.
+ is started. Otherwise PL/R will refuse to load. See plr_environ(),
+ which allows examination of the environment available to the
+ PostgreSQL postmaster process.
</para>
</tip>
View
821 expected/plr.out
@@ -0,0 +1,821 @@
+--
+-- first, define the language and functions. Turn off echoing so that expected file
+-- does not depend on contents of plr.sql.
+--
+\set ECHO none
+-- make typenames available in the global namespace
+select load_r_typenames();
+ load_r_typenames
+------------------
+ OK
+(1 row)
+
+CREATE TABLE plr_modules (
+ modseq int4,
+ modsrc text
+);
+INSERT INTO plr_modules VALUES (0, 'pg.test.module.load <-function(msg) {print(msg)}');
+select reload_plr_modules();
+ reload_plr_modules
+--------------------
+ OK
+(1 row)
+
+--
+-- plr_modules test
+--
+create or replace function pg_test_module_load(text) returns text as 'pg.test.module.load(arg1)' language 'plr';
+select pg_test_module_load('hello world');
+ pg_test_module_load
+---------------------
+ hello world
+(1 row)
+
+--
+-- user defined R function test
+--
+select install_rcmd('pg.test.install <-function(msg) {print(msg)}');
+ install_rcmd
+--------------
+ OK
+(1 row)
+
+create or replace function pg_test_install(text) returns text as 'pg.test.install(arg1)' language 'plr';
+select pg_test_install('hello world');
+ pg_test_install
+-----------------
+ hello world
+(1 row)
+
+--
+-- a variety of plr functions
+--
+create or replace function throw_notice(text) returns text as 'pg.thrownotice(arg1)' language 'plr';
+select throw_notice('hello');
+NOTICE: hello
+ throw_notice
+--------------
+ hello
+(1 row)
+
+create or replace function paste(_text,_text,text) returns text[] as 'paste(arg1,arg2, sep = arg3)' language 'plr';
+select paste('{hello, happy}','{world, birthday}',' ');
+ paste
+----------------------------------
+ {"hello world","happy birthday"}
+(1 row)
+
+create or replace function vec(_float8) returns _float8 as 'arg1' language 'plr';
+select vec('{1.23, 1.32}'::float8[]);
+ vec
+-------------
+ {1.23,1.32}
+(1 row)
+
+create or replace function vec(float, float) returns _float8 as 'c(arg1,arg2)' language 'plr';
+select vec(1.23, 1.32);
+ vec
+-------------
+ {1.23,1.32}
+(1 row)
+
+create or replace function echo(text) returns text as 'print(arg1)' language 'plr';
+select echo('hello');
+ echo
+-------
+ hello
+(1 row)
+
+create or replace function reval(text) returns text as 'eval(parse(text = arg1))' language 'plr';
+select reval('a <- sd(c(1,2,3)); b <- mean(c(1,2,3)); a + b');
+ reval
+-------
+ 3
+(1 row)
+
+create or replace function "commandArgs"() returns text[] as '' language 'plr';
+select "commandArgs"();
+ commandArgs
+---------------------------
+ {PL/R,--silent,--no-save}
+(1 row)
+
+create or replace function vec(float) returns text as 'c(arg1)' language 'plr';
+select vec(1.23);
+ vec
+------
+ 1.23
+(1 row)
+
+create or replace function reval(_text) returns text as 'eval(parse(text = arg1))' language 'plr';
+select round(reval('{"sd(c(1.12,1.23,1.18,1.34))"}'::text[])::numeric,8);
+ round
+------------
+ 0.09322911
+(1 row)
+
+create or replace function print(text) returns text as '' language 'plr';
+select print('hello');
+ print
+-------
+ hello
+(1 row)
+
+create or replace function cube(int) returns float as 'sq <- function(x) {return(x * x)}; return(arg1 * sq(arg1))' language 'plr';
+select cube(3);
+ cube
+------
+ 27
+(1 row)
+
+create or replace function sd(_float8) returns float as 'sd(arg1)' language 'plr';
+select round(sd('{1.23,1.31,1.42,1.27}'::_float8)::numeric,8);
+ round
+------------
+ 0.08180261
+(1 row)
+
+create or replace function sd(_float8) returns float as '' language 'plr';
+select round(sd('{1.23,1.31,1.42,1.27}'::_float8)::numeric,8);
+ round
+------------
+ 0.08180261
+(1 row)
+
+create or replace function mean(_float8) returns float as '' language 'plr';
+select mean('{1.23,1.31,1.42,1.27}'::_float8);
+ mean
+--------
+ 1.3075
+(1 row)
+
+create or replace function sprintf(text,text,text) returns text as 'sprintf(arg1,arg2,arg3)' language 'plr';
+select sprintf('%s is %s feet tall', 'Sven', '7');
+ sprintf
+---------------------
+ Sven is 7 feet tall
+(1 row)
+
+--
+-- test aggregates
+--
+create table foo(f0 int, f1 text, f2 float8) with oids;
+insert into foo values(1,'cat1',1.21);
+insert into foo values(2,'cat1',1.24);
+insert into foo values(3,'cat1',1.18);
+insert into foo values(4,'cat1',1.26);
+insert into foo values(5,'cat1',1.15);
+insert into foo values(6,'cat2',1.15);
+insert into foo values(7,'cat2',1.26);
+insert into foo values(8,'cat2',1.32);
+insert into foo values(9,'cat2',1.30);
+create or replace function r_median(_float8) returns float as 'median(arg1)' language 'plr';
+select r_median('{1.23,1.31,1.42,1.27}'::_float8);
+ r_median
+----------
+ 1.29
+(1 row)
+
+CREATE AGGREGATE median (sfunc = plr_array_accum, basetype = float8, stype = _float8, finalfunc = r_median);
+select f1, median(f2) from foo group by f1 order by f1;
+ f1 | median
+------+--------
+ cat1 | 1.21
+ cat2 | 1.28
+(2 rows)
+
+create or replace function r_gamma(_float8) returns float as 'gamma(arg1)' language 'plr';
+select round(r_gamma('{1.23,1.31,1.42,1.27}'::_float8)::numeric,8);
+ round
+------------
+ 0.91075486
+(1 row)
+
+CREATE AGGREGATE gamma (sfunc = plr_array_accum, basetype = float8, stype = _float8, finalfunc = r_gamma);
+select f1, round(gamma(f2)::numeric,8) from foo group by f1 order by f1;
+ f1 | round
+------+------------
+ cat1 | 0.91557649
+ cat2 | 0.93304093
+(2 rows)
+
+--
+-- test returning vectors, arrays, matricies, and dataframes
+-- as scalars, arrays, and records
+--
+create or replace function test_vt() returns text as 'array(1:10,c(2,5))' language 'plr';
+select test_vt();
+ test_vt
+---------
+ 1
+(1 row)
+
+create or replace function test_vi() returns int as 'array(1:10,c(2,5))' language 'plr';
+select test_vi();
+ test_vi
+---------
+ 1
+(1 row)
+
+create or replace function test_mt() returns text as 'as.matrix(array(1:10,c(2,5)))' language 'plr';
+select test_mt();
+ test_mt
+---------
+ 1
+(1 row)
+
+create or replace function test_mi() returns int as 'as.matrix(array(1:10,c(2,5)))' language 'plr';
+select test_mi();
+ test_mi
+---------
+ 1
+(1 row)
+
+create or replace function test_dt() returns text as 'as.data.frame(array(1:10,c(2,5)))[[1]]' language 'plr';
+select test_dt();
+ test_dt
+---------
+ 1
+(1 row)
+
+create or replace function test_di() returns int as 'as.data.frame(array(1:10,c(2,5)))[[1]]' language 'plr';
+select test_di() as error;
+ error
+-------
+ 1
+(1 row)
+
+create or replace function test_vta() returns text[] as 'array(1:10,c(2,5))' language 'plr';
+select test_vta();
+ test_vta
+----------------------------
+ {{1,3,5,7,9},{2,4,6,8,10}}
+(1 row)
+
+create or replace function test_via() returns int[] as 'array(1:10,c(2,5))' language 'plr';
+select test_via();
+ test_via
+----------------------------
+ {{1,3,5,7,9},{2,4,6,8,10}}
+(1 row)
+
+create or replace function test_mta() returns text[] as 'as.matrix(array(1:10,c(2,5)))' language 'plr';
+select test_mta();
+ test_mta
+----------------------------
+ {{1,3,5,7,9},{2,4,6,8,10}}
+(1 row)
+
+create or replace function test_mia() returns int[] as 'as.matrix(array(1:10,c(2,5)))' language 'plr';
+select test_mia();
+ test_mia
+----------------------------
+ {{1,3,5,7,9},{2,4,6,8,10}}
+(1 row)
+
+create or replace function test_dia() returns int[] as 'as.data.frame(array(1:10,c(2,5)))' language 'plr';
+select test_dia();
+ test_dia
+----------------------------
+ {{1,3,5,7,9},{2,4,6,8,10}}
+(1 row)
+
+create or replace function test_dta() returns text[] as 'as.data.frame(array(1:10,c(2,5)))' language 'plr';
+select test_dta();
+ test_dta
+----------------------------
+ {{1,3,5,7,9},{2,4,6,8,10}}
+(1 row)
+
+create or replace function test_dta1() returns text[] as 'as.data.frame(array(letters[1:10], c(2,5)))' language 'plr';
+select test_dta1();
+ test_dta1
+---------------------------
+ {{a,c,e,g,i},{b,d,f,h,j}}
+(1 row)
+
+create or replace function test_dta2() returns text[] as 'as.data.frame(data.frame(letters[1:10],1:10))' language 'plr';
+select test_dta2();
+ test_dta2
+----------------------------------------------------------------
+ {{a,1},{b,2},{c,3},{d,4},{e,5},{f,6},{g,7},{h,8},{i,9},{j,10}}
+(1 row)
+
+-- generates expected error
+create or replace function test_dia1() returns int[] as 'as.data.frame(array(letters[1:10], c(2,5)))' language 'plr';
+select test_dia1() as error;
+ERROR: invalid input syntax for integer: "a"
+CONTEXT: In PL/R function test_dia1
+create or replace function test_dtup() returns setof record as 'data.frame(letters[1:10],1:10)' language 'plr';
+select * from test_dtup() as t(f1 text, f2 int);
+ f1 | f2
+----+----
+ a | 1
+ b | 2
+ c | 3
+ d | 4
+ e | 5
+ f | 6
+ g | 7
+ h | 8
+ i | 9
+ j | 10
+(10 rows)
+
+create or replace function test_mtup() returns setof record as 'as.matrix(array(1:15,c(5,3)))' language 'plr';
+select * from test_mtup() as t(f1 int, f2 int, f3 int);
+ f1 | f2 | f3
+----+----+----
+ 1 | 6 | 11
+ 2 | 7 | 12
+ 3 | 8 | 13
+ 4 | 9 | 14
+ 5 | 10 | 15
+(5 rows)
+
+create or replace function test_vtup() returns setof record as 'as.vector(array(1:15,c(5,3)))' language 'plr';
+select * from test_vtup() as t(f1 int);
+ f1
+----
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+(15 rows)
+
+create or replace function test_vint() returns setof int as 'as.vector(array(1:15,c(5,3)))' language 'plr';
+select * from test_vint();
+ test_vint
+-----------
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+(15 rows)
+
+--
+-- try again with named tuple types
+--
+CREATE TYPE dtup AS (f1 text, f2 int);
+CREATE TYPE mtup AS (f1 int, f2 int, f3 int);
+CREATE TYPE vtup AS (f1 int);
+create or replace function test_dtup1() returns setof dtup as 'data.frame(letters[1:10],1:10)' language 'plr';
+select * from test_dtup1();
+ f1 | f2
+----+----
+ a | 1
+ b | 2
+ c | 3
+ d | 4
+ e | 5
+ f | 6
+ g | 7
+ h | 8
+ i | 9
+ j | 10
+(10 rows)
+
+create or replace function test_dtup2() returns setof dtup as 'data.frame(c("c","qw","ax","h","k","ax","l","t","b","u"),1:10)' language 'plr';
+select * from test_dtup2();
+ f1 | f2
+----+----
+ c | 1
+ qw | 2
+ ax | 3
+ h | 4
+ k | 5
+ ax | 6
+ l | 7
+ t | 8
+ b | 9
+ u | 10
+(10 rows)
+
+create or replace function test_mtup1() returns setof mtup as 'as.matrix(array(1:15,c(5,3)))' language 'plr';
+select * from test_mtup1();
+ f1 | f2 | f3
+----+----+----
+ 1 | 6 | 11
+ 2 | 7 | 12
+ 3 | 8 | 13
+ 4 | 9 | 14
+ 5 | 10 | 15
+(5 rows)
+
+create or replace function test_vtup1() returns setof vtup as 'as.vector(array(1:15,c(5,3)))' language 'plr';
+select * from test_vtup1();
+ f1
+----
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+(15 rows)
+
+--
+-- test pg R support functions (e.g. SPI_exec)
+--
+create or replace function pg_quote_ident(text) returns text as 'pg.quoteident(arg1)' language 'plr';
+select pg_quote_ident('Hello World');
+ pg_quote_ident
+----------------
+ "Hello World"
+(1 row)
+
+create or replace function pg_quote_literal(text) returns text as 'pg.quoteliteral(arg1)' language 'plr';
+select pg_quote_literal('Hello''World');
+ pg_quote_literal
+------------------
+ 'Hello''World'
+(1 row)
+
+create or replace function test_spi_t(text) returns text as '(pg.spi.exec(arg1))[[1]]' language 'plr';
+select test_spi_t('select oid, typname from pg_type where typname = ''oid'' or typname = ''text''');
+ test_spi_t
+------------
+ 25
+(1 row)
+
+create or replace function test_spi_ta(text) returns text[] as 'pg.spi.exec(arg1)' language 'plr';
+select test_spi_ta('select oid, typname from pg_type where typname = ''oid'' or typname = ''text''');
+ test_spi_ta
+----------------------
+ {{25,text},{26,oid}}
+(1 row)
+
+create or replace function test_spi_tup(text) returns setof record as 'pg.spi.exec(arg1)' language 'plr';
+select * from test_spi_tup('select oid, typname from pg_type where typname = ''oid'' or typname = ''text''') as t(typeid oid, typename name);
+ typeid | typename
+--------+----------
+ 25 | text
+ 26 | oid
+(2 rows)
+
+create or replace function fetch_pgoid(text) returns int as 'pg.reval(arg1)' language 'plr';
+select fetch_pgoid('BYTEAOID');
+ fetch_pgoid
+-------------
+ 17
+(1 row)
+
+create or replace function test_spi_prep(text) returns text as 'sp <<- pg.spi.prepare(arg1, c(NAMEOID, NAMEOID)); print("OK")' language 'plr';
+select test_spi_prep('select oid, typname from pg_type where typname = $1 or typname = $2');
+ test_spi_prep
+---------------
+ OK
+(1 row)
+
+create or replace function test_spi_execp(text, text, text) returns setof record as 'pg.spi.execp(pg.reval(arg1), list(arg2,arg3))' language 'plr';
+select * from test_spi_execp('sp','oid','text') as t(typeid oid, typename name);
+ typeid | typename
+--------+----------
+ 25 | text
+ 26 | oid
+(2 rows)
+
+create or replace function test_spi_lastoid(text) returns text as 'pg.spi.exec(arg1); pg.spi.lastoid()/pg.spi.lastoid()' language 'plr';
+select test_spi_lastoid('insert into foo values(10,''cat3'',3.333)') as "ONE";
+ ONE
+-----
+ 1
+(1 row)
+
+--
+-- test NULL handling
+--
+CREATE OR REPLACE FUNCTION r_test (float8) RETURNS float8 AS 'arg1' LANGUAGE 'plr';
+select r_test(null) is null as "NULL";
+ NULL
+------
+ t
+(1 row)
+
+CREATE OR REPLACE FUNCTION r_max (integer, integer) RETURNS integer AS 'if (is.null(arg1) && is.null(arg2)) return(NA);if (is.null(arg1)) return(arg2);if (is.null(arg2)) return(arg1);if (arg1 > arg2) return(arg1);arg2' LANGUAGE 'plr';
+select r_max(1,2) as "TWO";
+ TWO
+-----
+ 2
+(1 row)
+
+select r_max(null,2) as "TWO";
+ TWO
+-----
+ 2
+(1 row)
+
+select r_max(1,null) as "ONE";
+ ONE
+-----
+ 1
+(1 row)
+
+select r_max(null,null) is null as "NULL";
+ NULL
+------
+ t
+(1 row)
+
+--
+-- test tuple arguments
+--
+create or replace function get_foo(int) returns foo as 'select * from foo where f0 = $1' language 'sql';
+create or replace function test_foo(foo) returns foo as 'return(arg1)' language 'plr';
+select * from test_foo(get_foo(1));
+ f0 | f1 | f2
+----+------+------
+ 1 | cat1 | 1.21
+(1 row)
+
+--
+-- test 2D array argument
+--
+create or replace function test_in_m_tup(_int4) returns setof record as 'arg1' language 'plr';
+select * from test_in_m_tup('{{1,3,5},{2,4,6}}') as t(f1 int, f2 int, f3 int);
+ f1 | f2 | f3
+----+----+----
+ 1 | 3 | 5
+ 2 | 4 | 6
+(2 rows)
+
+--
+-- test 3D array argument
+--
+create or replace function arr3d(_int4,int4,int4,int4) returns int4 as '
+if (arg2 < 1 || arg3 < 1 || arg4 < 1)
+ return(NA)
+if (arg2 > dim(arg1)[1] || arg3 > dim(arg1)[2] || arg4 > dim(arg1)[3])
+ return(NA)
+return(arg1[arg2,arg3,arg4])
+' language 'plr' WITH (isstrict);
+select arr3d('{{{111,112},{121,122},{131,132}},{{211,212},{221,222},{231,232}}}',2,3,1) as "231";
+ 231
+-----
+ 231
+(1 row)
+
+-- for sake of comparison, see what normal pgsql array operations produces
+select f1[2][3][1] as "231" from (select '{{{111,112},{121,122},{131,132}},{{211,212},{221,222},{231,232}}}'::int4[] as f1) as t;
+ 231
+-----
+ 231
+(1 row)
+
+-- out-of-bounds, returns null
+select arr3d('{{{111,112},{121,122},{131,132}},{{211,212},{221,222},{231,232}}}',1,4,1) is null as "NULL";
+ NULL
+------
+ t
+(1 row)
+
+select f1[1][4][1] is null as "NULL" from (select '{{{111,112},{121,122},{131,132}},{{211,212},{221,222},{231,232}}}'::int4[] as f1) as t;
+ NULL
+------
+ t
+(1 row)
+
+select arr3d('{{{111,112},{121,122},{131,132}},{{211,212},{221,222},{231,232}}}',0,1,1) is null as "NULL";
+ NULL
+------
+ t
+(1 row)
+
+select f1[0][1][1] is null as "NULL" from (select '{{{111,112},{121,122},{131,132}},{{211,212},{221,222},{231,232}}}'::int4[] as f1) as t;
+ NULL
+------
+ t
+(1 row)
+
+--
+-- test 3D array return value
+--
+create or replace function arr3d(_int4) returns int4[] as 'return(arg1)' language 'plr' WITH (isstrict);
+select arr3d('{{{111,112},{121,122},{131,132}},{{211,212},{221,222},{231,232}}}');
+ arr3d
+-------------------------------------------------------------------
+ {{{111,112},{121,122},{131,132}},{{211,212},{221,222},{231,232}}}
+(1 row)
+
+--
+-- Trigger support tests
+--
+--
+-- test that NULL return value suppresses the change
+--
+create or replace function rejectfoo() returns trigger as 'return(NULL)' language plr;
+create trigger footrig before insert or update or delete on foo for each row execute procedure rejectfoo();
+select count(*) from foo;
+ count
+-------
+ 10
+(1 row)
+
+insert into foo values(11,'cat99',1.89);
+select count(*) from foo;
+ count
+-------
+ 10
+(1 row)
+
+update foo set f1 = 'zzz';
+select count(*) from foo;
+ count
+-------
+ 10
+(1 row)
+
+delete from foo;
+select count(*) from foo;
+ count
+-------
+ 10
+(1 row)
+
+drop trigger footrig on foo;
+--
+-- test that returning OLD/NEW as appropriate allow the change unmodified
+--
+create or replace function acceptfoo() returns trigger as '
+switch (pg.tg.op, INSERT = return(pg.tg.new), UPDATE = return(pg.tg.new), DELETE = return(pg.tg.old))
+' language plr;
+create trigger footrig before insert or update or delete on foo for each row execute procedure acceptfoo();
+select count(*) from foo;
+ count
+-------
+ 10
+(1 row)
+
+insert into foo values(11,'cat99',1.89);
+select count(*) from foo;
+ count
+-------
+ 11
+(1 row)
+
+update foo set f1 = 'zzz' where f0 = 11;
+select * from foo where f0 = 11;
+ f0 | f1 | f2
+----+-----+------
+ 11 | zzz | 1.89
+(1 row)
+
+delete from foo where f0 = 11;
+select count(*) from foo;
+ count
+-------
+ 10
+(1 row)
+
+drop trigger footrig on foo;
+--
+-- test that returning modifed tuple successfully modifies the result
+--
+create or replace function modfoo() returns trigger as '
+if (pg.tg.op == "INSERT")
+{
+ retval <- pg.tg.new
+ retval$f1 <- "xxx"
+}
+if (pg.tg.op == "UPDATE")
+{
+ retval <- pg.tg.new
+ retval$f1 <- "aaa"
+}
+if (pg.tg.op == "DELETE")
+ retval <- pg.tg.old
+return(retval)
+' language plr;
+create trigger footrig before insert or update or delete on foo for each row execute procedure modfoo();
+select count(*) from foo;
+ count
+-------
+ 10
+(1 row)
+
+insert into foo values(11,'cat99',1.89);
+select * from foo where f0 = 11;
+ f0 | f1 | f2
+----+-----+------
+ 11 | xxx | 1.89
+(1 row)
+
+update foo set f1 = 'zzz' where f0 = 11;
+select * from foo where f0 = 11;
+ f0 | f1 | f2
+----+-----+------
+ 11 | aaa | 1.89
+(1 row)
+
+delete from foo where f0 = 11;
+select count(*) from foo;
+ count
+-------
+ 10
+(1 row)
+
+drop trigger footrig on foo;
+--
+-- test statement level triggers and verify all arguments come
+-- across correctly
+--
+create or replace function foonotice() returns trigger as '
+msg <- paste(pg.tg.name,pg.tg.relname,pg.tg.when,pg.tg.level,pg.tg.op,pg.tg.args[1],pg.tg.args[2])
+pg.thrownotice(msg)
+return(NULL)
+' language plr;
+create trigger footrig after insert or update or delete on foo for each row execute procedure foonotice();
+select count(*) from foo;
+ count
+-------
+ 10
+(1 row)
+
+insert into foo values(11,'cat99',1.89);
+NOTICE: footrig foo AFTER ROW INSERT NA NA
+select count(*) from foo;
+ count
+-------
+ 11
+(1 row)
+
+update foo set f1 = 'zzz' where f0 = 11;
+NOTICE: footrig foo AFTER ROW UPDATE NA NA
+select * from foo where f0 = 11;
+ f0 | f1 | f2
+----+-----+------
+ 11 | zzz | 1.89
+(1 row)
+
+delete from foo where f0 = 11;
+NOTICE: footrig foo AFTER ROW DELETE NA NA
+select count(*) from foo;
+ count
+-------
+ 10
+(1 row)
+
+drop trigger footrig on foo;
+create trigger footrig after insert or update or delete on foo for each statement execute procedure foonotice('hello','world');
+select count(*) from foo;
+ count
+-------
+ 10
+(1 row)
+
+insert into foo values(11,'cat99',1.89);
+NOTICE: footrig foo AFTER STATEMENT INSERT hello world
+select count(*) from foo;
+ count
+-------
+ 11
+(1 row)
+
+update foo set f1 = 'zzz' where f0 = 11;
+NOTICE: footrig foo AFTER STATEMENT UPDATE hello world
+select * from foo where f0 = 11;
+ f0 | f1 | f2
+----+-----+------
+ 11 | zzz | 1.89
+(1 row)
+
+delete from foo where f0 = 11;
+NOTICE: footrig foo AFTER STATEMENT DELETE hello world
+select count(*) from foo;
+ count
+-------
+ 10
+(1 row)
+
+drop trigger footrig on foo;
View
1,692 pg_backend_support.c
@@ -2,7 +2,7 @@
* PL/R - PostgreSQL support for R as a
* procedural language (PL)
*
- * Copyright (c) 2003, 2004 by Joseph E. Conway
+ * Copyright (c) 2003-2006 by Joseph E. Conway
* ALL RIGHTS RESERVED
*
* Joe Conway <mail@joeconway.com>
@@ -36,1645 +36,20 @@
#include "optimizer/clauses.h"
#include "utils/memutils.h"
-/* Example format: "/usr/local/pgsql/lib" */
-#ifndef PKGLIBDIR
-#error "PKGLIBDIR needs to be defined to compile this file."
-#endif
-
-/* GUC variable */
-extern char *Dynamic_library_path;
-
-/* compiled function hash table */
-extern HTAB *plr_HashTable;
-
-/*
- * static declarations
- */
-static char *get_lib_pathstr(Oid funcid);
-static char *expand_dynamic_library_name(const char *name);
-static char *substitute_libpath_macro(const char *name);
-static char *find_in_dynamic_libpath(const char *basename);
-static bool file_exists(const char *name);
-
-#ifdef PG_VERSION_73_COMPAT
-/*************************************************************************
- * working with postgres 7.3 compatible sources
- *************************************************************************/
-#include "postgres.h"
-
-#include <time.h>
-#include <fcntl.h>
-#include <errno.h>
-#include <unistd.h>
-#include <signal.h>
-#include <sys/time.h>
-#include <ctype.h>
-#ifdef HAVE_SYSLOG
-#include <syslog.h>
-#endif
-
-#include "lib/stringinfo.h"
-#include "libpq/libpq.h"
-#include "libpq/pqformat.h"
-#include "mb/pg_wchar.h"
-#include "miscadmin.h"
-#include "storage/ipc.h"
-#include "tcop/tcopprot.h"
-#include "utils/memutils.h"
-#include "utils/guc.h"
-
-bool IsPostmasterEnvironment = false;
-int log_min_messages = NOTICE;
-bool ExitOnAnyError = false;
-
-ArrayType *
-construct_md_array(Datum *elems,
- int ndims,
- int *dims,
- int *lbs,
- Oid elmtype, int elmlen, bool elmbyval, char elmalign)
-{
- int ndatabytes;
- int nbytes;
- ArrayType *array = NULL;
- ArrayType *tmparray = NULL;
- int nelems = ArrayGetNItems(ndims, dims);
-
- /* build up 1-D array */
- tmparray = construct_array(elems, nelems, elmtype, elmlen, elmbyval, elmalign);
-
- if (tmparray != NULL)
- {
- /* convert it to a ndims-array */
- ndatabytes = ARR_SIZE(tmparray) - ARR_OVERHEAD(1);
- nbytes = ndatabytes + ARR_OVERHEAD(ndims);
- array = (ArrayType *) palloc(nbytes);
-
- array->size = nbytes;
- array->ndim = ndims;
- array->flags = 0;
- array->elemtype = elmtype;
- memcpy(ARR_DIMS(array), dims, ndims * sizeof(int));
- memcpy(ARR_LBOUND(array), lbs, ndims * sizeof(int));
- memcpy(ARR_DATA_PTR(array), ARR_DATA_PTR(tmparray), ndatabytes);
-
- pfree(tmparray);
- }
-
- return array;
-}
-
-/*
- * get_element_type
- *
- * Given the type OID, get the typelem (InvalidOid if not an array type).
- *
- * NB: this only considers varlena arrays to be true arrays; InvalidOid is
- * returned if the input is a fixed-length array type.
- */
-Oid
-get_element_type(Oid typid)
-{
- HeapTuple tp;
-
- tp = SearchSysCache(TYPEOID,
- ObjectIdGetDatum(typid),
- 0, 0, 0);
- if (HeapTupleIsValid(tp))
- {
- Form_pg_type typtup = (Form_pg_type) GETSTRUCT(tp);
- Oid result;
-
- if (typtup->typlen == -1)
- result = typtup->typelem;
- else
- result = InvalidOid;
- ReleaseSysCache(tp);
- return result;
- }
- else
- return InvalidOid;
-}
-
-/*
- * get_array_type
- *
- * Given the type OID, get the corresponding array type.
- * Returns InvalidOid if no array type can be found.
- *
- * NB: this only considers varlena arrays to be true arrays.
- */
-Oid
-get_array_type(Oid typid)
-{
- HeapTuple tp;
-
- tp = SearchSysCache(TYPEOID,
- ObjectIdGetDatum(typid),
- 0, 0, 0);
- if (HeapTupleIsValid(tp))
- {
- Form_pg_type typtup = (Form_pg_type) GETSTRUCT(tp);
- char *array_typename;
- Oid namespaceId;
-
- array_typename = makeArrayTypeName(NameStr(typtup->typname));
- namespaceId = typtup->typnamespace;
- ReleaseSysCache(tp);
-
- tp = SearchSysCache(TYPENAMENSP,
- PointerGetDatum(array_typename),
- ObjectIdGetDatum(namespaceId),
- 0, 0);
-
- pfree(array_typename);
-
- if (HeapTupleIsValid(tp))
- {
- Oid result;
-
- typtup = (Form_pg_type) GETSTRUCT(tp);
- if (typtup->typlen == -1 && typtup->typelem == typid)
- result = HeapTupleGetOid(tp);
- else
- result = InvalidOid;
- ReleaseSysCache(tp);
- return result;
- }
- }
- return InvalidOid;
-}
-
-/*
- * get_type_io_data
- *
- * A six-fer: given the type OID, return typlen, typbyval, typalign,
- * typdelim, typelem, and IO function OID. The IO function
- * returned is controlled by IOFuncSelector
- */
-void
-get_type_io_data(Oid typid,
- IOFuncSelector which_func,
- int16 *typlen,
- bool *typbyval,
- char *typalign,
- char *typdelim,
- Oid *typelem,
- Oid *func)
-{
- HeapTuple typeTuple;
- Form_pg_type typeStruct;
-
- typeTuple = SearchSysCache(TYPEOID,
- ObjectIdGetDatum(typid),
- 0, 0, 0);
- if (!HeapTupleIsValid(typeTuple))
- /* internal error */
- elog(ERROR, "cache lookup failed for type %u", typid);
- typeStruct = (Form_pg_type) GETSTRUCT(typeTuple);
-
- *typlen = typeStruct->typlen;
- *typbyval = typeStruct->typbyval;
- *typalign = typeStruct->typalign;
- *typdelim = typeStruct->typdelim;
- *typelem = typeStruct->typelem;
- switch (which_func)
- {
- case IOFunc_input:
- *func = typeStruct->typinput;
- break;
- case IOFunc_output:
- *func = typeStruct->typoutput;
- break;
- default:
- /* should never happen */
- elog(ERROR, "get_type_io_data called with invalid IO function selector");
- }
- ReleaseSysCache(typeTuple);
-}
-
-
-/*-------------------------------------------------------------------------
- * Support routines for extracting info from fn_expr parse tree
- *
- * These are needed by polymorphic functions, which accept multiple possible
- * input types and need help from the parser to know what they've got.
- *-------------------------------------------------------------------------
- */
-
-/*
- * This just returns InvalidOid because in 7.3.x information is not available
- */
-Oid
-get_fn_expr_rettype(FmgrInfo *flinfo)
-{
- return InvalidOid;
-}
-
-/*
- * This just returns InvalidOid because in 7.3.x information is not available
- */
-Oid
-get_fn_expr_argtype(FmgrInfo *flinfo, int argnum)
-{
- return InvalidOid;
-}
-
-
-/*-------------------------------------------------------------------------
- * Support routines for 7.4 style error reporting
- *
- *-------------------------------------------------------------------------
- */
-
-/* Global variables */
-ErrorContextCallback *error_context_stack = NULL;
-
-/* GUC parameters */
-PGErrorVerbosity Log_error_verbosity = PGERROR_VERBOSE;
-bool Log_timestamp = false; /* show timestamps in stderr output */
-bool Log_pid = false; /* show PIDs in stderr output */
-
-#ifdef HAVE_SYSLOG
-/*
- * 0 = only stdout/stderr
- * 1 = stdout+stderr and syslog
- * 2 = syslog only
- * ... in theory anyway
- */
-int Use_syslog = 0;
-char *Syslog_facility; /* openlog() parameters */
-char *Syslog_ident;
-
-static void write_syslog(int level, const char *line);
-
-#else
-
-#define Use_syslog 0
-
-#endif /* HAVE_SYSLOG */
-
-
-/*
- * ErrorData holds the data accumulated during any one ereport() cycle.
- * Any non-NULL pointers must point to palloc'd data in ErrorContext.
- * (The const pointers are an exception; we assume they point at non-freeable
- * constant strings.)
- */
-
-typedef struct ErrorData
-{
- int elevel; /* error level */
- bool output_to_server; /* will report to server log? */
- bool output_to_client; /* will report to client? */
- bool show_funcname; /* true to force funcname inclusion */
- const char *filename; /* __FILE__ of ereport() call */
- int lineno; /* __LINE__ of ereport() call */
- const char *funcname; /* __func__ of ereport() call */
- int sqlerrcode; /* encoded ERRSTATE */
- char *message; /* primary error message */
- char *detail; /* detail error message */
- char *hint; /* hint message */
- char *context; /* context message */
- int cursorpos; /* cursor index into query string */
- int saved_errno; /* errno at entry */
-} ErrorData;
-
-/* We provide a small stack of ErrorData records for re-entrant cases */
-#define ERRORDATA_STACK_SIZE 5
-
-static ErrorData errordata[ERRORDATA_STACK_SIZE];
-
-static int errordata_stack_depth = -1; /* index of topmost active frame */
-
-static int recursion_depth = 0; /* to detect actual recursion */
-
-
-/* Macro for checking errordata_stack_depth is reasonable */
-#define CHECK_STACK_DEPTH() \
- do { \
- if (errordata_stack_depth < 0) \
- { \
- errordata_stack_depth = -1; \
- ereport(ERROR, (errmsg_internal("errstart was not called"))); \
- } \
- } while (0)
-
-static void appendStringInfoString(StringInfo str, const char *s);
-static bool appendStringInfoVA(StringInfo str, const char *fmt, va_list args);
-static void enlargeStringInfo(StringInfo str, int needed);
-static void send_message_to_server_log(ErrorData *edata);
-static void send_message_to_frontend(ErrorData *edata);
-static char *expand_fmt_string(const char *fmt, ErrorData *edata);
-static const char *useful_strerror(int errnum);
-static const char *error_severity(int elevel);
-static const char *print_timestamp(void);
-static const char *print_pid(void);
-
-/*
- * appendStringInfoString
- *
- * Append a null-terminated string to str.
- * Like appendStringInfo(str, "%s", s) but faster.
- */
-static void
-appendStringInfoString(StringInfo str, const char *s)
-{
- appendBinaryStringInfo(str, s, strlen(s));
-}
-
-/*
- * appendStringInfoVA
- *
- * Attempt to format text data under the control of fmt (an sprintf-style
- * format string) and append it to whatever is already in str. If successful
- * return true; if not (because there's not enough space), return false
- * without modifying str. Typically the caller would enlarge str and retry
- * on false return --- see appendStringInfo for standard usage pattern.
- *
- * XXX This API is ugly, but there seems no alternative given the C spec's
- * restrictions on what can portably be done with va_list arguments: you have
- * to redo va_start before you can rescan the argument list, and we can't do
- * that from here.
- */
-static bool
-appendStringInfoVA(StringInfo str, const char *fmt, va_list args)
-{
- int avail,
- nprinted;
-
- Assert(str != NULL);
-
- /*
- * If there's hardly any space, don't bother trying, just fail to make
- * the caller enlarge the buffer first.
- */
- avail = str->maxlen - str->len - 1;
- if (avail < 16)
- return false;
-
- /*
- * Assert check here is to catch buggy vsnprintf that overruns
- * the specified buffer length. Solaris 7 in 64-bit mode is
- * an example of a platform with such a bug.
- */
-#ifdef USE_ASSERT_CHECKING
- str->data[str->maxlen - 1] = '\0';
-#endif
-
- nprinted = vsnprintf(str->data + str->len, avail, fmt, args);
-
- Assert(str->data[str->maxlen - 1] == '\0');
-
- /*
- * Note: some versions of vsnprintf return the number of chars
- * actually stored, but at least one returns -1 on failure. Be
- * conservative about believing whether the print worked.
- */
- if (nprinted >= 0 && nprinted < avail - 1)
- {
- /* Success. Note nprinted does not include trailing null. */
- str->len += nprinted;
- return true;
- }
-
- /* Restore the trailing null so that str is unmodified. */
- str->data[str->len] = '\0';
- return false;
-}
-
-/*
- * enlargeStringInfo
- *
- * Make sure there is enough space for 'needed' more bytes
- * ('needed' does not include the terminating null).
- *
- * External callers usually need not concern themselves with this, since
- * all stringinfo.c routines do it automatically. However, if a caller
- * knows that a StringInfo will eventually become X bytes large, it
- * can save some palloc overhead by enlarging the buffer before starting
- * to store data in it.
- *
- * NB: because we use repalloc() to enlarge the buffer, the string buffer
- * will remain allocated in the same memory context that was current when
- * initStringInfo was called, even if another context is now current.
- * This is the desired and indeed critical behavior!
- */
-static void
-enlargeStringInfo(StringInfo str, int needed)
-{
- int newlen;
-
- needed += str->len + 1; /* total space required now */
- if (needed <= str->maxlen)
- return; /* got enough space already */
-
- /*
- * We don't want to allocate just a little more space with each
- * append; for efficiency, double the buffer size each time it
- * overflows. Actually, we might need to more than double it if
- * 'needed' is big...
- */
- newlen = 2 * str->maxlen;
- while (needed > newlen)
- newlen = 2 * newlen;
-
- str->data = (char *) repalloc(str->data, newlen);
-
- str->maxlen = newlen;
-}
-
-/*
- * errstart --- begin an error-reporting cycle
- *
- * Create a stack entry and store the given parameters in it. Subsequently,
- * errmsg() and perhaps other routines will be called to further populate
- * the stack entry. Finally, errfinish() will be called to actually process
- * the error report.
- *
- * Returns TRUE in normal case. Returns FALSE to short-circuit the error
- * report (if it's a warning or lower and not to be reported anywhere).
- */
-bool
-errstart(int elevel, const char *filename, int lineno,
- const char *funcname)
-{
- ErrorData *edata;
- bool output_to_server = false;
- bool output_to_client = false;
-
- /*
- * First decide whether we need to process this report at all;
- * if it's warning or less and not enabled for logging, just
- * return FALSE without starting up any error logging machinery.
- */
-
- /*
- * Convert initialization errors into fatal errors. This is probably
- * redundant, because Warn_restart_ready won't be set anyway.
- */
- if (elevel == ERROR && IsInitProcessingMode())
- elevel = FATAL;
-
- /*
- * If we are inside a critical section, all errors become PANIC
- * errors. See miscadmin.h.
- */
- if (elevel >= ERROR)
- {
- if (CritSectionCount > 0)
- elevel = PANIC;
- }
-
- /* Determine whether message is enabled for server log output */
- if (IsPostmasterEnvironment)
- {
- /* Complicated because LOG is sorted out-of-order for this purpose */
- if (elevel == LOG || elevel == COMMERROR)
- {
- if (log_min_messages == LOG)
- output_to_server = true;
- else if (log_min_messages < FATAL)
- output_to_server = true;
- }
- else
- {
- /* elevel != LOG */
- if (log_min_messages == LOG)
- {
- if (elevel >= FATAL)
- output_to_server = true;
- }
- /* Neither is LOG */
- else if (elevel >= log_min_messages)
- output_to_server = true;
- }
- }
- else
- {
- /* In bootstrap/standalone case, do not sort LOG out-of-order */
- output_to_server = (elevel >= log_min_messages);
- }
-
- /* Determine whether message is enabled for client output */
- if (whereToSendOutput == Remote && elevel != COMMERROR)
- {
- /*
- * client_min_messages is honored only after we complete the
- * authentication handshake. This is required both for security
- * reasons and because many clients can't handle NOTICE messages
- * during authentication.
- */
- if (ClientAuthInProgress)
- output_to_client = (elevel >= ERROR);
- else
- output_to_client = (elevel >= client_min_messages ||
- elevel == INFO);
- }
-
- /* Skip processing effort if non-error message will not be output */
- if (elevel < ERROR && !output_to_server && !output_to_client)
- return false;
-
- /*
- * Okay, crank up a stack entry to store the info in.
- */
-
- if (recursion_depth++ > 0)
- {
- /*
- * Ooops, error during error processing. Clear ErrorContext and force
- * level up to ERROR or greater, as discussed at top of file. Adjust
- * output decisions too.
- */
- MemoryContextReset(ErrorContext);
- output_to_server = true;
- if (whereToSendOutput == Remote && elevel != COMMERROR)
- output_to_client = true;
- elevel = Max(elevel, ERROR);
- /*
- * If we recurse more than once, the problem might be something
- * broken in a context traceback routine. Abandon them too.
- */
- if (recursion_depth > 2)
- error_context_stack = NULL;
- }
- if (++errordata_stack_depth >= ERRORDATA_STACK_SIZE)
- {
- /* Wups, stack not big enough */
- int i;
-
- elevel = Max(elevel, ERROR);
- /*
- * Don't forget any FATAL/PANIC status on the stack (see comments
- * in errfinish)
- */
- for (i = 0; i < errordata_stack_depth; i++)
- elevel = Max(elevel, errordata[i].elevel);
- /* Clear the stack and try again */
- errordata_stack_depth = -1;
- ereport(elevel, (errmsg_internal("ERRORDATA_STACK_SIZE exceeded")));
- }
-
- /* Initialize data for this error frame */
- edata = &errordata[errordata_stack_depth];
- MemSet(edata, 0, sizeof(ErrorData));
- edata->elevel = elevel;
- edata->output_to_server = output_to_server;
- edata->output_to_client = output_to_client;
- edata->filename = filename;
- edata->lineno = lineno;
- edata->funcname = funcname;
- /* Select default errcode based on elevel */
- if (elevel >= ERROR)
- edata->sqlerrcode = ERRCODE_INTERNAL_ERROR;
- else if (elevel == WARNING)
- edata->sqlerrcode = ERRCODE_WARNING;
- else
- edata->sqlerrcode = ERRCODE_SUCCESSFUL_COMPLETION;
- /* errno is saved here so that error parameter eval can't change it */
- edata->saved_errno = errno;
-
- recursion_depth--;
- return true;
-}
-
-/*
- * errfinish --- end an error-reporting cycle
- *
- * Produce the appropriate error report(s) and pop the error stack.
- *
- * If elevel is ERROR or worse, control does not return to the caller.
- * See elog.h for the error level definitions.
- */
-void
-errfinish(int dummy, ...)
-{
- ErrorData *edata = &errordata[errordata_stack_depth];
- int elevel = edata->elevel;
- MemoryContext oldcontext;
- ErrorContextCallback *econtext;
-
- CHECK_STACK_DEPTH();
-
- /*
- * Call any context callback functions. We can treat ereports occuring
- * in callback functions as re-entrant rather than recursive case, so
- * don't increment recursion_depth yet.
- */
- for (econtext = error_context_stack;
- econtext != NULL;
- econtext = econtext->previous)
- {
- (*econtext->callback) (econtext->arg);
- }
-
- /* Now we are ready to process the error. */
- recursion_depth++;
-
- /*
- * Do processing in ErrorContext, which we hope has enough reserved space
- * to report an error.
- */
- oldcontext = MemoryContextSwitchTo(ErrorContext);
-
- /* Send to server log, if enabled */
- if (edata->output_to_server)
- send_message_to_server_log(edata);
-
- /*
- * Abort any old-style COPY OUT in progress when an error is detected.
- * This hack is necessary because of poor design of old-style copy
- * protocol. Note we must do this even if client is fool enough to
- * have set client_min_messages above ERROR, so don't look at
- * output_to_client.
- */
- if (elevel >= ERROR && whereToSendOutput == Remote)
- pq_endcopyout(true);
-
- /* Send to client, if enabled */
- if (edata->output_to_client)
- send_message_to_frontend(edata);
-
- /* Now free up subsidiary data attached to stack entry, and release it */
- if (edata->message)
- pfree(edata->message);
- if (edata->detail)
- pfree(edata->detail);
- if (edata->hint)
- pfree(edata->hint);
- if (edata->context)
- pfree(edata->context);
-
- MemoryContextSwitchTo(oldcontext);
-
- errordata_stack_depth--;
- recursion_depth--;
-
- /*
- * If the error level is ERROR or more, we are not going to return to
- * caller; therefore, if there is any stacked error already in progress
- * it will be lost. This is more or less okay, except we do not want
- * to have a FATAL or PANIC error downgraded because the reporting process
- * was interrupted by a lower-grade error. So check the stack and make
- * sure we panic if panic is warranted.
- */
- if (elevel >= ERROR)
- {
- int i;
-
- for (i = 0; i <= errordata_stack_depth; i++)
- elevel = Max(elevel, errordata[i].elevel);
-
- /*
- * Also, be sure to reset the stack to empty. We do not clear
- * ErrorContext here, though; PostgresMain does that later on.
- */
- errordata_stack_depth = -1;
- recursion_depth = 0;
- error_context_stack = NULL;
- }
-
- /*
- * Perform error recovery action as specified by elevel.
- */
- if (elevel == ERROR || elevel == FATAL)
- {
- /* Prevent immediate interrupt while entering error recovery */
- ImmediateInterruptOK = false;
-
- /*
- * If we just reported a startup failure, the client will
- * disconnect on receiving it, so don't send any more to the
- * client.
- */
- if (!Warn_restart_ready && whereToSendOutput == Remote)
- whereToSendOutput = None;
-
- /*
- * For a FATAL error, we let proc_exit clean up and exit.
- *
- * There are several other cases in which we treat ERROR as FATAL
- * and go directly to proc_exit:
- *
- * 1. ExitOnAnyError mode switch is set (initdb uses this).
- *
- * 2. we have not yet entered the main backend loop (ie, we are in
- * the postmaster or in backend startup); we have noplace to recover.
- *
- * 3. the error occurred after proc_exit has begun to run. (It's
- * proc_exit's responsibility to see that this doesn't turn into
- * infinite recursion!)
- *
- * In the last case, we exit with nonzero exit code to indicate that
- * something's pretty wrong. We also want to exit with nonzero exit
- * code if not running under the postmaster (for example, if we are
- * being run from the initdb script, we'd better return an error
- * status).
- */
- if (elevel == FATAL ||
- ExitOnAnyError ||
- !Warn_restart_ready ||
- proc_exit_inprogress)
- {
- /*
- * fflush here is just to improve the odds that we get to see
- * the error message, in case things are so hosed that
- * proc_exit crashes. Any other code you might be tempted to
- * add here should probably be in an on_proc_exit callback
- * instead.
- */
- fflush(stdout);
- fflush(stderr);
- proc_exit(proc_exit_inprogress || !IsUnderPostmaster);
- }
-
- /*
- * Guard against infinite loop from errors during error recovery.
- */
- if (InError)
- ereport(PANIC, (errmsg("error during error recovery, giving up")));
- InError = true;
-
- /*
- * Otherwise we can return to the main loop in postgres.c.
- */
- siglongjmp(Warn_restart, 1);
- }
-
- if (elevel >= PANIC)
- {
- /*
- * Serious crash time. Postmaster will observe nonzero process
- * exit status and kill the other backends too.
- *
- * XXX: what if we are *in* the postmaster? abort() won't kill
- * our children...
- */
- ImmediateInterruptOK = false;
- fflush(stdout);
- fflush(stderr);
- abort();
- }
-
- /* We reach here if elevel <= WARNING. OK to return to caller. */
-}
-
-
-/*
- * errcode --- add SQLSTATE error code to the current error
- *
- * The code is expected to be represented as per MAKE_SQLSTATE().
- */
-int
-errcode(int sqlerrcode)
-{
- ErrorData *edata = &errordata[errordata_stack_depth];
-
- /* we don't bother incrementing recursion_depth */
- CHECK_STACK_DEPTH();
-
- edata->sqlerrcode = sqlerrcode;
-
- return 0; /* return value does not matter */
-}
-
-
-/*
- * errcode_for_file_access --- add SQLSTATE error code to the current error
- *
- * The SQLSTATE code is chosen based on the saved errno value. We assume
- * that the failing operation was some type of disk file access.
- *
- * NOTE: the primary error message string should generally include %m
- * when this is used.
- */
-int
-errcode_for_file_access(void)
-{
- ErrorData *edata = &errordata[errordata_stack_depth];
-
- /* we don't bother incrementing recursion_depth */
- CHECK_STACK_DEPTH();
-
- switch (edata->saved_errno)
- {
- /* Permission-denied failures */
- case EPERM: /* Not super-user */
- case EACCES: /* Permission denied */
-#ifdef EROFS
- case EROFS: /* Read only file system */
-#endif
- edata->sqlerrcode = ERRCODE_INSUFFICIENT_PRIVILEGE;
- break;
-
- /* Object not found */
- case ENOENT: /* No such file or directory */
- edata->sqlerrcode = ERRCODE_UNDEFINED_OBJECT;
- break;
-
- /* Duplicate object */
- case EEXIST: /* File exists */
- edata->sqlerrcode = ERRCODE_DUPLICATE_OBJECT;
- break;
-
- /* Wrong object type or state */
- case ENOTDIR: /* Not a directory */
- case EISDIR: /* Is a directory */
- case ENOTEMPTY: /* Directory not empty */
- edata->sqlerrcode = ERRCODE_WRONG_OBJECT_TYPE;
- break;
-
- /* Insufficient resources */
- case ENOSPC: /* No space left on device */
- edata->sqlerrcode = ERRCODE_DISK_FULL;
- break;
-
- case ENFILE: /* File table overflow */
- case EMFILE: /* Too many open files */
- edata->sqlerrcode = ERRCODE_INSUFFICIENT_RESOURCES;
- break;
-
- /* Hardware failure */
- case EIO: /* I/O error */
- edata->sqlerrcode = ERRCODE_IO_ERROR;
- break;
-
- /* All else is classified as internal errors */
- default:
- edata->sqlerrcode = ERRCODE_INTERNAL_ERROR;
- break;
- }
-
- return 0; /* return value does not matter */
-}
-
-/*
- * errcode_for_socket_access --- add SQLSTATE error code to the current error
- *
- * The SQLSTATE code is chosen based on the saved errno value. We assume
- * that the failing operation was some type of socket access.
- *
- * NOTE: the primary error message string should generally include %m
- * when this is used.
- */
-int
-errcode_for_socket_access(void)
-{
- ErrorData *edata = &errordata[errordata_stack_depth];
-
- /* we don't bother incrementing recursion_depth */
- CHECK_STACK_DEPTH();
-
- switch (edata->saved_errno)
- {
- /* Loss of connection */
- case EPIPE:
-#ifdef ECONNRESET
- case ECONNRESET:
-#endif
- edata->sqlerrcode = ERRCODE_CONNECTION_FAILURE;
- break;
-
- /* All else is classified as internal errors */
- default:
- edata->sqlerrcode = ERRCODE_INTERNAL_ERROR;
- break;
- }
-
- return 0; /* return value does not matter */
-}
-
-
-/*
- * This macro handles expansion of a format string and associated parameters;
- * it's common code for errmsg(), errdetail(), etc. Must be called inside
- * a routine that is declared like "const char *fmt, ..." and has an edata
- * pointer set up. The message is assigned to edata->targetfield, or
- * appended to it if appendval is true.
- *
- * Note: we pstrdup the buffer rather than just transferring its storage
- * to the edata field because the buffer might be considerably larger than
- * really necessary.
- */
-#define EVALUATE_MESSAGE(targetfield, appendval) \
- { \
- char *fmtbuf; \
- StringInfoData buf; \
- /* Internationalize the error format string */ \
- fmt = gettext(fmt); \
- /* Expand %m in format string */ \
- fmtbuf = expand_fmt_string(fmt, edata); \
- initStringInfo(&buf); \
- if ((appendval) && edata->targetfield) \
- appendStringInfo(&buf, "%s\n", edata->targetfield); \
- /* Generate actual output --- have to use appendStringInfoVA */ \
- for (;;) \
- { \
- va_list args; \
- bool success; \
- va_start(args, fmt); \
- success = appendStringInfoVA(&buf, fmtbuf, args); \
- va_end(args); \
- if (success) \
- break; \
- enlargeStringInfo(&buf, buf.maxlen); \
- } \
- /* Done with expanded fmt */ \
- pfree(fmtbuf); \
- /* Save the completed message into the stack item */ \
- if (edata->targetfield) \
- pfree(edata->targetfield); \
- edata->targetfield = pstrdup(buf.data); \
- pfree(buf.data); \
- }
-
-
-/*
- * errmsg --- add a primary error message text to the current error
- *
- * In addition to the usual %-escapes recognized by printf, "%m" in
- * fmt is replaced by the error message for the caller's value of errno.
- *
- * Note: no newline is needed at the end of the fmt string, since
- * ereport will provide one for the output methods that need it.
- */
-int
-errmsg(const char *fmt, ...)
-{
- ErrorData *edata = &errordata[errordata_stack_depth];
- MemoryContext oldcontext;
-
- recursion_depth++;
- CHECK_STACK_DEPTH();
- oldcontext = MemoryContextSwitchTo(ErrorContext);
-
- EVALUATE_MESSAGE(message, false);
-
- MemoryContextSwitchTo(oldcontext);
- recursion_depth--;
- return 0; /* return value does not matter */
-}
-
-
-/*
- * errmsg_internal --- add a primary error message text to the current error
- *
- * This is exactly like errmsg() except that strings passed to errmsg_internal
- * are customarily left out of the internationalization message dictionary.
- * This should be used for "can't happen" cases that are probably not worth
- * spending translation effort on.
- */
-int
-errmsg_internal(const char *fmt, ...)
-{
- ErrorData *edata = &errordata[errordata_stack_depth];
- MemoryContext oldcontext;
-
- recursion_depth++;
- CHECK_STACK_DEPTH();
- oldcontext = MemoryContextSwitchTo(ErrorContext);
-
- EVALUATE_MESSAGE(message, false);
-
- MemoryContextSwitchTo(oldcontext);
- recursion_depth--;
- return 0; /* return value does not matter */
-}
-
-
-/*
- * errdetail --- add a detail error message text to the current error
- */
-int
-errdetail(const char *fmt, ...)
-{
- ErrorData *edata = &errordata[errordata_stack_depth];
- MemoryContext oldcontext;
-
- recursion_depth++;
- CHECK_STACK_DEPTH();
- oldcontext = MemoryContextSwitchTo(ErrorContext);
-
- EVALUATE_MESSAGE(detail, false);
-
- MemoryContextSwitchTo(oldcontext);
- recursion_depth--;
- return 0; /* return value does not matter */
-}
-
-
-/*
- * errhint --- add a hint error message text to the current error
- */
-int
-errhint(const char *fmt, ...)
-{
- ErrorData *edata = &errordata[errordata_stack_depth];
- MemoryContext oldcontext;
-
- recursion_depth++;
- CHECK_STACK_DEPTH();
- oldcontext = MemoryContextSwitchTo(ErrorContext);
-
- EVALUATE_MESSAGE(hint, false);
-
- MemoryContextSwitchTo(oldcontext);
- recursion_depth--;
- return 0; /* return value does not matter */
-}
-
-
-/*
- * errcontext --- add a context error message text to the current error
- *
- * Unlike other cases, multiple calls are allowed to build up a stack of
- * context information. We assume earlier calls represent more-closely-nested
- * states.
- */
-int
-errcontext(const char *fmt, ...)
-{
- ErrorData *edata = &errordata[errordata_stack_depth];
- MemoryContext oldcontext;
-
- recursion_depth++;
- CHECK_STACK_DEPTH();
- oldcontext = MemoryContextSwitchTo(ErrorContext);
-
- EVALUATE_MESSAGE(context, true);
-
- MemoryContextSwitchTo(oldcontext);
- recursion_depth--;
- return 0; /* return value does not matter */
-}
-
-
-/*
- * errfunction --- add reporting function name to the current error
- *
- * This is used when backwards compatibility demands that the function
- * name appear in messages sent to old-protocol clients. Note that the
- * passed string is expected to be a non-freeable constant string.
- */
-int
-errfunction(const char *funcname)
-{
- ErrorData *edata = &errordata[errordata_stack_depth];
-
- /* we don't bother incrementing recursion_depth */
- CHECK_STACK_DEPTH();
-
- edata->funcname = funcname;
- edata->show_funcname = true;
-
- return 0; /* return value does not matter */
-}
-
-/*
- * errposition --- add cursor position to the current error
- */
-int
-errposition(int cursorpos)
-{
- ErrorData *edata = &errordata[errordata_stack_depth];
-
- /* we don't bother incrementing recursion_depth */
- CHECK_STACK_DEPTH();
-
- edata->cursorpos = cursorpos;
-
- return 0; /* return value does not matter */
-}
-
-
-/*
- * elog_finish --- finish up for old-style API
- *
- * The elog() macro already called errstart, but with ERROR rather than
- * the true elevel.
- */
-void
-elog_finish(int elevel, const char *fmt, ...)
-{
- ErrorData *edata = &errordata[errordata_stack_depth];
- MemoryContext oldcontext;
-
- CHECK_STACK_DEPTH();
-
- /*
- * We need to redo errstart() because the elog macro had to call it
- * with bogus elevel.
- */
- errordata_stack_depth--;
- errno = edata->saved_errno;
- if (!errstart(elevel, edata->filename, edata->lineno, edata->funcname))
- return; /* nothing to do */
-
- /*
- * Format error message just like errmsg().
- */
- recursion_depth++;
- oldcontext = MemoryContextSwitchTo(ErrorContext);
-
- EVALUATE_MESSAGE(message, false);
-
- MemoryContextSwitchTo(oldcontext);
- recursion_depth--;
-
- /*
- * And let errfinish() finish up.
- */
- errfinish(0);
-}
-
-#ifdef HAVE_SYSLOG
-
-#ifndef PG_SYSLOG_LIMIT
-#define PG_SYSLOG_LIMIT 128
-#endif
-
-/*
- * Write a message line to syslog if the syslog option is set.
- *
- * Our problem here is that many syslog implementations don't handle
- * long messages in an acceptable manner. While this function doesn't
- * help that fact, it does work around by splitting up messages into
- * smaller pieces.
- */
-static void
-write_syslog(int level, const char *line)
-{
- static bool openlog_done = false;
- static unsigned long seq = 0;
- static int syslog_fac = LOG_LOCAL0;
-
- int len = strlen(line);
-
- if (Use_syslog == 0)
- return;
-
- if (!openlog_done)
- {
- if (strcasecmp(Syslog_facility, "LOCAL0") == 0)
- syslog_fac = LOG_LOCAL0;
- if (strcasecmp(Syslog_facility, "LOCAL1") == 0)
- syslog_fac = LOG_LOCAL1;
- if (strcasecmp(Syslog_facility, "LOCAL2") == 0)
- syslog_fac = LOG_LOCAL2;
- if (strcasecmp(Syslog_facility, "LOCAL3") == 0)
- syslog_fac = LOG_LOCAL3;
- if (strcasecmp(Syslog_facility, "LOCAL4") == 0)
- syslog_fac = LOG_LOCAL4;
- if (strcasecmp(Syslog_facility, "LOCAL5") == 0)
- syslog_fac = LOG_LOCAL5;
- if (strcasecmp(Syslog_facility, "LOCAL6") == 0)
- syslog_fac = LOG_LOCAL6;
- if (strcasecmp(Syslog_facility, "LOCAL7") == 0)
- syslog_fac = LOG_LOCAL7;
- openlog(Syslog_ident, LOG_PID | LOG_NDELAY, syslog_fac);
- openlog_done = true;
- }
-
- /*
- * We add a sequence number to each log message to suppress "same"
- * messages.
- */
- seq++;
-
- /* divide into multiple syslog() calls if message is too long */
- /* or if the message contains embedded NewLine(s) '\n' */
- if (len > PG_SYSLOG_LIMIT || strchr(line, '\n') != NULL)
- {
- int chunk_nr = 0;
-
- while (len > 0)
- {
- char buf[PG_SYSLOG_LIMIT + 1];
- int buflen;
- int i;
-
- /* if we start at a newline, move ahead one char */
- if (line[0] == '\n')
- {
- line++;
- len--;
- continue;
- }
-
- strncpy(buf, line, PG_SYSLOG_LIMIT);
- buf[PG_SYSLOG_LIMIT] = '\0';
- if (strchr(buf, '\n') != NULL)
- *strchr(buf, '\n') = '\0';
-
- buflen = strlen(buf);
-
- /* trim to multibyte letter boundary */
- buflen = pg_mbcliplen(buf, buflen, buflen);
- if (buflen <= 0)
- return;
- buf[buflen] = '\0';
-
- /* already word boundary? */
- if (!isspace((unsigned char) line[buflen]) &&
- line[buflen] != '\0')
- {
- /* try to divide at word boundary */
- i = buflen - 1;
- while (i > 0 && !isspace((unsigned char) buf[i]))
- i--;
-
- if (i > 0) /* else couldn't divide word boundary */
- {
- buflen = i;
- buf[i] = '\0';
- }
- }
-
- chunk_nr++;
-
- syslog(level, "[%lu-%d] %s", seq, chunk_nr, buf);
- line += buflen;
- len -= buflen;
- }
- }
- else
- {
- /* message short enough */
- syslog(level, "[%lu] %s", seq, line);
- }
-}
-
-#endif /* HAVE_SYSLOG */
-
-
-/*
- * Write error report to server's log
- */
-static void
-send_message_to_server_log(ErrorData *edata)
-{
- StringInfoData buf;
-
- initStringInfo(&buf);
-
- appendStringInfo(&buf, "%s: ", error_severity(edata->elevel));
-
- if (Log_error_verbosity >= PGERROR_VERBOSE)
- {
- /* unpack MAKE_SQLSTATE code */
- char tbuf[12];
- int ssval;
- int i;
-
- ssval = edata->sqlerrcode;
- for (i = 0; i < 5; i++)
- {
- tbuf[i] = PGUNSIXBIT(ssval);
- ssval >>= 6;
- }
- tbuf[i] = '\0';
- appendStringInfo(&buf, "%s: ", tbuf);
- }
-
- if (edata->message)
- appendStringInfoString(&buf, edata->message);
- else
- appendStringInfoString(&buf, gettext("missing error text"));
-
- if (edata->cursorpos > 0)
- appendStringInfo(&buf, gettext(" at character %d"), edata->cursorpos);
-
- appendStringInfoChar(&buf, '\n');
-
- if (Log_error_verbosity >= PGERROR_DEFAULT)
- {
- if (edata->detail)
- appendStringInfo(&buf, gettext("DETAIL: %s\n"), edata->detail);
- if (edata->hint)
- appendStringInfo(&buf, gettext("HINT: %s\n"), edata->hint);
- if (edata->context)
- appendStringInfo(&buf, gettext("CONTEXT: %s\n"), edata->context);
- if (Log_error_verbosity >= PGERROR_VERBOSE)
- {
- if (edata->funcname && edata->filename)
- appendStringInfo(&buf, gettext("LOCATION: %s, %s:%d\n"),
- edata->funcname, edata->filename,
- edata->lineno);
- else if (edata->filename)
- appendStringInfo(&buf, gettext("LOCATION: %s:%d\n"),
- edata->filename, edata->lineno);
- }
- }
-
- /*
- * If the user wants the query that generated this error logged, do so.
- * We use debug_query_string to get at the query, which is kinda useless
- * for queries triggered by extended query protocol; how to improve?
- */
- if (edata->elevel >= log_min_error_statement && debug_query_string != NULL)
- appendStringInfo(&buf, gettext("STATEMENT: %s\n"),
- debug_query_string);
-
-
-#ifdef HAVE_SYSLOG
- /* Write to syslog, if enabled */
- if (Use_syslog >= 1)
- {
- int syslog_level;
-
- switch (edata->elevel)
- {
- case DEBUG5:
- case DEBUG4:
- case DEBUG3:
- case DEBUG2:
- case DEBUG1:
- syslog_level = LOG_DEBUG;
- break;
- case LOG:
- case COMMERROR:
- case INFO:
- syslog_level = LOG_INFO;
- break;
- case NOTICE:
- case WARNING:
- syslog_level = LOG_NOTICE;
- break;
- case ERROR:
- syslog_level = LOG_WARNING;
- break;
- case FATAL:
- syslog_level = LOG_ERR;
- break;
- case PANIC:
- default:
- syslog_level = LOG_CRIT;
- break;
- }
-
- write_syslog(syslog_level, buf.data);
- }
-#endif /* HAVE_SYSLOG */
-
- /* Write to stderr, if enabled */
- if (Use_syslog <= 1 || whereToSendOutput == Debug)
- {
- /*
- * Timestamp and PID are only used for stderr output --- we assume
- * the syslog daemon will supply them for us in the other case.
- */
- fprintf(stderr, "%s%s%s",
- Log_timestamp ? print_timestamp() : "",
- Log_pid ? print_pid() : "",
- buf.data);
- }
-
- pfree(buf.data);
-}
-
-
-/*
- * Write error report to client
- */
-static void
-send_message_to_frontend(ErrorData *edata)
-{
- /* Old style --- gin up a backwards-compatible message */
- StringInfoData buf;
- StringInfoData msgbuf;
-
- /* 'N' (Notice) is for nonfatal conditions, 'E' is for errors */
- pq_beginmessage(&msgbuf);
- pq_sendbyte(&msgbuf, edata->elevel != ERROR ? 'N' : 'E');
-
- initStringInfo(&buf);
-
- appendStringInfo(&buf, "%s: ", error_severity(edata->elevel));
-
- if (edata->show_funcname && edata->funcname)
- appendStringInfo(&buf, "%s: ", edata->funcname);
-
- if (edata->message)
- appendStringInfo(&buf, "%s", edata->message);
- else
- appendStringInfoString(&buf, gettext("missing error text"));
-
- if (edata->cursorpos > 0)
- appendStringInfo(&buf, gettext(" at character %d"),
- edata->cursorpos);
-
- appendStringInfoChar(&buf, '\n');
-
- if (edata->detail)
- appendStringInfo(&buf, "DETAIL: %s\n", edata->detail);
-
- if (edata->hint)
- appendStringInfo(&buf, "HINT: %s\n", edata->hint);
-
- if (edata->context)
- appendStringInfo(&buf, "CONTEXT: %s\n", edata->context);
-
- pq_sendstring(&msgbuf, buf.data);
-
- pfree(buf.data);
-
- pq_endmessage(&msgbuf);
-
- /*
- * This flush is normally not necessary, since postgres.c will flush
- * out waiting data when control returns to the main loop. But it
- * seems best to leave it here, so that the client has some clue what
- * happened if the backend dies before getting back to the main loop
- * ... error/notice messages should not be a performance-critical path
- * anyway, so an extra flush won't hurt much ...
- */
- pq_flush();
-}
-
-
-/*
- * Support routines for formatting error messages.
- */
-
-
-/*
- * expand_fmt_string --- process special format codes in a format string
- *
- * We must replace %m with the appropriate strerror string, since vsnprintf
- * won't know what to do with it.
- *
- * The result is a palloc'd string.
- */
-static char *
-expand_fmt_string(const char *fmt, ErrorData *edata)
-{
- StringInfoData buf;
- const char *cp;
-
- initStringInfo(&buf);
-
- for (cp = fmt; *cp; cp++)
- {
- if (cp[0] == '%' && cp[1] != '\0')
- {
- cp++;
- if (*cp == 'm')
- {
- /*
- * Replace %m by system error string. If there are any %'s
- * in the string, we'd better double them so that vsnprintf
- * won't misinterpret.
- */
- const char *cp2;
-
- cp2 = useful_strerror(edata->saved_errno);
- for (; *cp2; cp2++)
- {
- if (*cp2 == '%')
- appendStringInfoCharMacro(&buf, '%');
- appendStringInfoCharMacro(&buf, *cp2);
- }
- }
- else
- {
- /* copy % and next char --- this avoids trouble with %%m */
- appendStringInfoCharMacro(&buf, '%');
- appendStringInfoCharMacro(&buf, *cp);
- }
- }
- else
- appendStringInfoCharMacro(&buf, *cp);
- }
-
- return buf.data;
-}
-
-
-/*
- * A slightly cleaned-up version of strerror()
- */
-static const char *
-useful_strerror(int errnum)
-{
- /* this buffer is only used if errno has a bogus value */
- static char errorstr_buf[48];
- const char *str;
-
- str = strerror(errnum);
-
- /*
- * Some strerror()s return an empty string for out-of-range errno.
- * This is ANSI C spec compliant, but not exactly useful.
- */
- if (str == NULL || *str == '\0')
- {
- /*
- * translator: This string will be truncated at 47 characters
- * expanded.
- */
- snprintf(errorstr_buf, sizeof(errorstr_buf),
- gettext("operating system error %d"), errnum);
- str = errorstr_buf;
- }
-
- return str;
-}
-
-
-/*
- * error_severity --- get localized string representing elevel
- */
-static const char *
-error_severity(int elevel)
-{
- const char *prefix;
-
- switch (elevel)
- {
- case DEBUG1:
- case DEBUG2:
- case DEBUG3:
- case DEBUG4:
- case DEBUG5:
- prefix = gettext("DEBUG");
- break;
- case LOG:
- case COMMERROR:
- prefix = gettext("LOG");
- break;
- case INFO:
- prefix = gettext("INFO");
- break;
- case NOTICE:
- prefix = gettext("NOTICE");
- break;
- case WARNING:
- prefix = gettext("WARNING");
- break;
- case ERROR:
- prefix = gettext("ERROR");
- break;
- case FATAL:
- prefix = gettext("FATAL");
- break;
- case PANIC:
- prefix = gettext("PANIC");
- break;
- default:
- prefix = "???";
- break;
- }
-
- return prefix;
-}
-
-
-/*
- * Return a timestamp string like
- *
- * "2000-06-04 13:12:03 "
- */
-static const char *
-print_timestamp(void)
-{
- time_t curtime;
- static char buf[21]; /* format `YYYY-MM-DD HH:MM:SS ' */
-
- curtime = time(NULL);
-
- strftime(buf, sizeof(buf),
- "%Y-%m-%d %H:%M:%S ",
- localtime(&curtime));
-
- return buf;
-}
+/* GUC variable */
+extern char *Dynamic_library_path;
+/* compiled function hash table */
+extern HTAB *plr_HashTable;
/*
- * Return a string like
- *
- * "[123456] "
- *
- * with the current pid.
+ * static declarations
*/
-static const char *
-print_pid(void)
-{
- static char buf[10]; /* allow `[123456] ' */
-
- snprintf(buf, sizeof(buf), "[%d] ", (int) MyProcPid);
- return buf;
-}
-
-
-#else
-/*************************************************************************
- * working with postgres 7.4 compatible sources
- *************************************************************************/
-
-
-#endif /* PG_VERSION_73_COMPAT */
+static char *get_lib_pathstr(Oid funcid);
+static char *expand_dynamic_library_name(const char *name);
+static char *substitute_libpath_macro(const char *name);
+static char *find_in_dynamic_libpath(const char *basename);
+static bool file_exists(const char *name);
/*
* Compute the hashkey for a given function invocation
@@ -1954,49 +329,6 @@ expand_dynamic_library_name(const char *name)
return NULL;
}
-
-#if defined(PG_VERSION_73_COMPAT) || defined(PG_VERSION_74_COMPAT)
-/*
- * Substitute for any macros appearing in the given string.
- * Result is always freshly palloc'd.
- */
-static char *
-substitute_libpath_macro(const char *name)
-{
- size_t macroname_len;
- char *replacement = NULL;
-
- AssertArg(name != NULL);
-
- if (name[0] != '$')
- return pstrdup(name);
-
- macroname_len = strcspn(name + 1, "/") + 1;
-
- if (strncmp(name, "$libdir", macroname_len) == 0)
- replacement = PKGLIBDIR;
- else
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_NAME),
- errmsg("invalid macro name in dynamic library path")));
-
- if (name[macroname_len] == '\0')
- return pstrdup(replacement);
- else
- {
- char *new;
-
- new = palloc(strlen(replacement) + (strlen(name) - macroname_len) + 1);
-
- strcpy(new, replacement);
- strcat(new, name + macroname_len);
-
- return new;
- }
-}
-
-#else
-
/*
* Substitute for any macros appearing in the given string.
* Result is always freshly palloc'd.
@@ -2029,8 +361,6 @@ substitute_libpath_macro(const char *name)
return ret;
}
-#endif /* PG_VERSION_73_COMPAT || PG_VERSION_74_COMPAT */
-