Add utility functions to PLPerl:
authorAndrew Dunstan
Wed, 20 Jan 2010 01:08:21 +0000 (01:08 +0000)
committerAndrew Dunstan
Wed, 20 Jan 2010 01:08:21 +0000 (01:08 +0000)
    quote_literal, quote_nullable, quote_ident,
    encode_bytea, decode_bytea, looks_like_number,
    encode_array_literal, encode_array_constructor.
Split SPI.xs into two - SPI.xs now contains only SPI functions. Remainder
are in new Util.xs.
Some more code and documentation cleanup along the way, as well as
adding some CVS markers to files missing them.

Original patch from Tim Bunce, with a little editing from me.

14 files changed:
doc/src/sgml/plperl.sgml
src/pl/plperl/GNUmakefile
src/pl/plperl/SPI.xs
src/pl/plperl/Util.xs [new file with mode: 0644]
src/pl/plperl/expected/plperl_elog.out
src/pl/plperl/expected/plperl_util.out [new file with mode: 0644]
src/pl/plperl/plc_perlboot.pl
src/pl/plperl/plc_safe_bad.pl
src/pl/plperl/plc_safe_ok.pl
src/pl/plperl/plperl.c
src/pl/plperl/plperl.h
src/pl/plperl/spi_internal.c [deleted file]
src/pl/plperl/sql/plperl_util.sql [new file with mode: 0644]
src/pl/plperl/text2macro.pl

index 2db97aa901567f61ef413ec3845ef720e931c17c..d92cbb32144e9ab83362975e430ff23e8a962c88 100644 (file)
@@ -1,4 +1,4 @@
-
+
 
  
   PL/Perl - Perl Procedural Language
@@ -13,7 +13,7 @@
 
   
    PL/Perl is a loadable procedural language that enables you to write
-   PostgreSQL functions in the 
+   PostgreSQL functions in the
    Perl programming language.
   
 
@@ -101,7 +101,7 @@ $$ LANGUAGE plperl;
    linkend="sql-syntax-dollar-quoting">) for the string constant.
    If you choose to use escape string syntax E'',
    you must double the single quote marks (') and backslashes
-   (\) used in the body of the function 
+   (\) used in the body of the function
    (see ).
   
 
@@ -141,13 +141,13 @@ $$ LANGUAGE plperl;
 
 
 CREATE FUNCTION perl_max (integer, integer) RETURNS integer AS $$
-    my ($x,$y) = @_;
-    if (! defined $x) {
-        if (! defined $y) { return undef; }
+    my ($x, $y) = @_;
+    if (not defined $x) {
+        return undef if not defined $y;
         return $y;
     }
-    if (! defined $y) { return $x; }
-    if ($x > $y) { return $x; }
+    return $x if not defined $y;
+    return $x if $x > $y;
     return $y;
 $$ LANGUAGE plperl;
 
@@ -158,32 +158,21 @@ $$ LANGUAGE plperl;
 
   
    Anything in a function argument that is not a reference is
-   a string, which is in the standard PostgreSQL 
+   a string, which is in the standard PostgreSQL
    external text representation for the relevant data type. In the case of
    ordinary numeric or text types, Perl will just do the right thing and
    the programmer will normally not have to worry about it. However, in
-   other cases the argument will need to be converted into a form that is 
-   more usable in Perl. For example, here is how to convert an argument of 
-   type bytea into unescaped binary 
-   data:
-
-
-    my $arg = shift;
-    $arg =~ s!\\(?:\\|(\d{3}))!$1 ? chr(oct($1)) : "\\"!ge;
-
-
+   other cases the argument will need to be converted into a form that is
+   more usable in Perl. For example, the decode_bytea
+   function can be used to convert an argument of
+   type bytea into unescaped binary.
   
 
   
-   Similarly, values passed back to PostgreSQL 
-   must be in the external text representation format. For example, here 
-   is how to escape binary data for a return value of type bytea:
-
-
-    $retval =~ s!(\\|[^ -~])!sprintf("\\%03o",ord($1))!ge;
-    return $retval;
-
-
+   Similarly, values passed back to PostgreSQL
+   must be in the external text representation format. For example, the
+   encode_bytea function can be used to
+   to escape binary data for a return value of type bytea.
   
 
   
@@ -322,7 +311,10 @@ BEGIN { strict->import(); }
   
  
 
+  Built-in Functions
+
   Database Access from PL/Perl
 
   
@@ -340,7 +332,7 @@ BEGIN { strict->import(); }
      spi_query(command)
      spi_fetchrow(cursor)
      spi_prepare(commandargument types)
-     spi_exec_prepared(plan)
+     spi_exec_prepared(planarguments)
      spi_query_prepared(plan [, attributes], arguments)
      spi_cursor_close(cursor)
      spi_freeplan(plan)
@@ -455,19 +447,19 @@ $$ LANGUAGE plperlu;
 SELECT * from lotsa_md5(500);
 
     
-      
+
     
-    spi_preparespi_query_preparedspi_exec_prepared, 
+    spi_preparespi_query_preparedspi_exec_prepared,
     and spi_freeplan implement the same functionality but for prepared queries. Once
     a query plan is prepared by a call to spi_prepare, the plan can be used instead
     of the string query, either in spi_exec_prepared, where the result is the same as returned
     by spi_exec_query, or in spi_query_prepared which returns a cursor
     exactly as spi_query does, which can be later passed to spi_fetchrow.
     
-    
+
     
     The advantage of prepared queries is that is it possible to use one prepared plan for more
-    than one query execution. After the plan is not needed anymore, it can be freed with 
+    than one query execution. After the plan is not needed anymore, it can be freed with
     spi_freeplan:
     
 
@@ -478,7 +470,7 @@ CREATE OR REPLACE FUNCTION init() RETURNS INTEGER AS $$
 $$ LANGUAGE plperl;
 
 CREATE OR REPLACE FUNCTION add_time( INTERVAL ) RETURNS TEXT AS $$
-        return spi_exec_prepared( 
+        return spi_exec_prepared(
                 $_SHARED{my_plan},
                 $_[0],
         )->{rows}->[0]->{now};
@@ -493,7 +485,7 @@ SELECT init();
 SELECT add_time('1 day'), add_time('2 days'), add_time('3 days');
 SELECT done();
 
-  add_time  |  add_time  |  add_time  
+  add_time  |  add_time  |  add_time
 ------------+------------+------------
  2005-12-10 | 2005-12-11 | 2005-12-12
     
@@ -516,7 +508,13 @@ SELECT done();
     
      
     
+   
+
+  Utility functions in PL/Perl
 
+   
     
      
       elog
@@ -545,8 +543,143 @@ SELECT done();
       
      
     
+
+    
+     
+      quote_literal
+      in PL/Perl
+     
+
+     quote_literal(string)
+     
+      
+        Return the given string suitably quoted to be used as a string literal in an SQL
+        statement string. Embedded single-quotes and backslashes are properly doubled.
+        Note that quote_literal returns undef on undef input; if the argument
+        might be undef, quote_nullable is often more suitable.
+      
+     
+    
+
+    
+     
+      quote_nullable
+      in PL/Perl
+     
+
+     quote_nullable(string)
+     
+      
+        Return the given string suitably quoted to be used as a string literal in an SQL
+        statement string; or, if the argument is undef, return the unquoted string "NULL".
+        Embedded single-quotes and backslashes are properly doubled.
+      
+     
+    
+
+    
+     
+      quote_ident
+      in PL/Perl
+     
+
+     quote_ident(string)
+     
+      
+        Return the given string suitably quoted to be used as an identifier in
+        an SQL statement string. Quotes are added only if necessary (i.e., if
+        the string contains non-identifier characters or would be case-folded).
+        Embedded quotes are properly doubled.
+      
+     
+    
+
+    
+     
+      decode_bytea
+      in PL/Perl
+     
+
+     decode_bytea(string)
+     
+      
+        Return the unescaped binary data represented by the contents of the given string,
+        which should be bytea encoded.
+        
+     
+    
+
+    
+     
+      encode_bytea
+      in PL/Perl
+     
+
+     encode_bytea(string)
+     
+      
+        Return the bytea encoded form of the binary data contents of the given string.
+        
+     
+    
+
+    
+     
+      encode_array_literal
+      in PL/Perl
+     
+
+     encode_array_literal(array)
+     encode_array_literal(arraydelimiter)
+     
+      
+        Returns the contents of the referenced array as a string in array literal format
+        (see ).
+        Returns the argument value unaltered if it's not a reference to an array.
+        The delimiter used between elements of the array literal defaults to ""
+        if a delimiter is not specified or is undef.
+        
+     
+    
+
+    
+     
+      encode_array_constructor
+      in PL/Perl
+     
+
+     encode_array_constructor(array)
+     
+      
+        Returns the contents of the referenced array as a string in array constructor format
+        (see ).
+        Individual values are quoted using quote_nullable.
+        Returns the argument value, quoted using quote_nullable,
+        if it's not a reference to an array.
+        
+     
+    
+
+    
+     
+      looks_like_number
+      in PL/Perl
+     
+
+     looks_like_number(string)
+     
+      
+        Returns a true value if the content of the given string looks like a
+        number, according to Perl, returns false otherwise.
+        Returns undef if the argument is undef.  Leading and trailing space is
+        ignored. Inf and Infinity are regarded as numbers.
+        
+     
+    
+
    
   
  
 
  
@@ -587,7 +720,7 @@ CREATE OR REPLACE FUNCTION get_var(name text) RETURNS text AS $$
     return $_SHARED{$_[0]};
 $$ LANGUAGE plperl;
 
-SELECT set_var('sample', 'Hello, PL/Perl!  How's tricks?');
+SELECT set_var('sample', 'Hello, PL/Perl!  How''s tricks?');
 SELECT get_var('sample');
 
   
@@ -701,15 +834,16 @@ $$ LANGUAGE plperl;
       However, not all installations are compiled with the requisite flags.
       If PostgreSQL detects that this is the case then it will
       not start a second interpreter, but instead create an error. In
-      consequence, in such an installation, you cannot use both 
+      consequence, in such an installation, you cannot use both
       PL/PerlU and PL/Perl in the same backend
-      process. The remedy for this is to obtain a Perl installation created
-      with the appropriate flags, namely either usemultiplicity or
-      both usethreads and useithreads. 
-      For more details,see the perlembed manual page.
+      process. The remedy for this is to obtain a Perl installation configured
+      with the appropriate flags, namely either usemultiplicity
+      or useithreads. usemultiplicity is preferred
+      unless you actually need to use threads. For more details, see the
+      perlembed man page.
     
   
-  
+
  
 
  
@@ -718,8 +852,8 @@ $$ LANGUAGE plperl;
   
    PL/Perl can be used to write trigger functions.  In a trigger function,
    the hash reference $_TD contains information about the
-   current trigger event. $_TD is a global variable, 
-   which gets a separate local value for each invocation of the trigger. 
+   current trigger event. $_TD is a global variable,
+   which gets a separate local value for each invocation of the trigger.
    The fields of the $_TD hash reference are:
 
    
@@ -801,7 +935,7 @@ $$ LANGUAGE plperl;
      
       
        Name of the table on which the trigger fired. This has been deprecated,
-       and could be removed in a future release. 
+       and could be removed in a future release.
        Please use $_TD->{table_name} instead.
       
      
index 81c918a1d59d12295bb5a5dda2d5821c50f4fcec..f794f028bec4bd32a2d453355a749203f18c19f1 100644 (file)
@@ -1,5 +1,5 @@
 # Makefile for PL/Perl
-# $PostgreSQL: pgsql/src/pl/plperl/GNUmakefile,v 1.41 2010/01/10 18:10:03 tgl Exp $
+# $PostgreSQL: pgsql/src/pl/plperl/GNUmakefile,v 1.42 2010/01/20 01:08:21 adunstan Exp $
 
 subdir = src/pl/plperl
 top_builddir = ../../..
@@ -34,14 +34,14 @@ rpathdir = $(perl_archlibexp)/CORE
 
 NAME = plperl
 
-OBJS = plperl.o spi_internal.o SPI.o
+OBJS = plperl.o SPI.o Util.o
 
 PERLCHUNKS = plc_perlboot.pl plc_safe_bad.pl plc_safe_ok.pl
 
 SHLIB_LINK = $(perl_embed_ldflags)
 
 REGRESS_OPTS = --dbname=$(PL_TESTDB) --load-language=plperl  --load-language=plperlu
-REGRESS = plperl plperl_trigger plperl_shared plperl_elog plperlu
+REGRESS = plperl plperl_trigger plperl_shared plperl_elog plperl_util plperlu
 # if Perl can support two interpreters in one backend, 
 # test plperl-and-plperlu cases
 ifneq ($(PERL),)
@@ -64,6 +64,9 @@ all: all-lib
 SPI.c: SPI.xs
    $(PERL) $(perl_privlibexp)/ExtUtils/xsubpp -typemap $(perl_privlibexp)/ExtUtils/typemap $< >$@
 
+Util.c: Util.xs
+   $(PERL) $(perl_privlibexp)/ExtUtils/xsubpp -typemap $(perl_privlibexp)/ExtUtils/typemap $< >$@
+
 install: all installdirs install-lib
 
 installdirs: installdirs-lib
@@ -78,7 +81,7 @@ submake:
    $(MAKE) -C $(top_builddir)/src/test/regress pg_regress$(X)
 
 clean distclean maintainer-clean: clean-lib
-   rm -f SPI.c $(OBJS) perlchunks.h
+   rm -f SPI.c Util.c $(OBJS) perlchunks.h
    rm -rf results
    rm -f regression.diffs regression.out
 
index 967ac0adbab0cbf8eadbe7bcf02d06062f925297..9cee19a7f79800afc03b3b25926cee965d543745 100644 (file)
@@ -1,3 +1,12 @@
+/**********************************************************************
+ * PostgreSQL::InServer::SPI
+ *
+ * SPI interface for plperl.  
+ *
+ *    $PostgreSQL: pgsql/src/pl/plperl/SPI.xs,v 1.21 2010/01/20 01:08:21 adunstan Exp $
+ *
+ **********************************************************************/
+
 /* this must be first: */
 #include "postgres.h"
 /* Defined by Perl */
@@ -7,40 +16,6 @@
 #include "plperl.h"
 
 
-/*
- * Implementation of plperl's elog() function
- *
- * If the error level is less than ERROR, we'll just emit the message and
- * return.  When it is ERROR, elog() will longjmp, which we catch and
- * turn into a Perl croak().  Note we are assuming that elog() can't have
- * any internal failures that are so bad as to require a transaction abort.
- *
- * This is out-of-line to suppress "might be clobbered by longjmp" warnings.
- */
-static void
-do_spi_elog(int level, char *message)
-{
-   MemoryContext oldcontext = CurrentMemoryContext;
-
-   PG_TRY();
-   {
-       elog(level, "%s", message);
-   }
-   PG_CATCH();
-   {
-       ErrorData  *edata;
-
-       /* Must reset elog.c's state */
-       MemoryContextSwitchTo(oldcontext);
-       edata = CopyErrorData();
-       FlushErrorState();
-
-       /* Punt the error to Perl */
-       croak("%s", edata->message);
-   }
-   PG_END_TRY();
-}
-
 /*
  * Interface routine to catch ereports and punt them to Perl
  */
@@ -69,40 +44,11 @@ do_plperl_return_next(SV *sv)
 }
 
 
-MODULE = SPI PREFIX = spi_
+MODULE = PostgreSQL::InServer::SPI PREFIX = spi_
 
 PROTOTYPES: ENABLE
 VERSIONCHECK: DISABLE
 
-void
-spi_elog(level, message)
-   int level
-   char* message
-   CODE:
-       if (level > ERROR)      /* no PANIC allowed thanks */
-           level = ERROR;
-       if (level < DEBUG5)
-           level = DEBUG5;
-       do_spi_elog(level, message);
-
-int
-spi_DEBUG()
-
-int
-spi_LOG()
-
-int
-spi_INFO()
-
-int
-spi_NOTICE()
-
-int
-spi_WARNING()
-
-int
-spi_ERROR()
-
 SV*
 spi_spi_exec_query(query, ...)
    char* query;
diff --git a/src/pl/plperl/Util.xs b/src/pl/plperl/Util.xs
new file mode 100644 (file)
index 0000000..e779616
--- /dev/null
@@ -0,0 +1,205 @@
+/**********************************************************************
+ * PostgreSQL::InServer::Util
+ *
+ * $PostgreSQL: pgsql/src/pl/plperl/Util.xs,v 1.1 2010/01/20 01:08:21 adunstan Exp $
+ *
+ * Defines plperl interfaces for general-purpose utilities.
+ * This module is bootstrapped as soon as an interpreter is initialized.
+ * Currently doesn't define a PACKAGE= so all subs are in main:: to avoid
+ * the need for explicit importing.
+ *
+ **********************************************************************/
+
+/* this must be first: */
+#include "postgres.h"
+#include "fmgr.h"
+#include "utils/builtins.h"
+#include "utils/bytea.h"       /* for byteain & byteaout */
+#include "mb/pg_wchar.h"       /* for GetDatabaseEncoding */
+/* Defined by Perl */
+#undef _
+
+/* perl stuff */
+#include "plperl.h"
+
+
+/*
+ * Implementation of plperl's elog() function
+ *
+ * If the error level is less than ERROR, we'll just emit the message and
+ * return.  When it is ERROR, elog() will longjmp, which we catch and
+ * turn into a Perl croak().  Note we are assuming that elog() can't have
+ * any internal failures that are so bad as to require a transaction abort.
+ *
+ * This is out-of-line to suppress "might be clobbered by longjmp" warnings.
+ */
+static void
+do_util_elog(int level, char *message)
+{
+    MemoryContext oldcontext = CurrentMemoryContext;
+
+    PG_TRY();
+    {
+        elog(level, "%s", message);
+    }
+    PG_CATCH();
+    {
+        ErrorData  *edata;
+
+        /* Must reset elog.c's state */
+        MemoryContextSwitchTo(oldcontext);
+        edata = CopyErrorData();
+        FlushErrorState();
+
+        /* Punt the error to Perl */
+        croak("%s", edata->message);
+    }
+    PG_END_TRY();
+}
+
+static SV  *
+newSVstring_len(const char *str, STRLEN len)
+{
+    SV         *sv;
+
+    sv = newSVpvn(str, len);
+#if PERL_BCDVERSION >= 0x5006000L
+    if (GetDatabaseEncoding() == PG_UTF8)
+        SvUTF8_on(sv);
+#endif
+    return sv;
+}
+
+static text *
+sv2text(SV *sv)
+{
+    STRLEN    sv_len;
+    char     *sv_pv;
+
+    if (!sv)
+        sv = &PL_sv_undef;
+    sv_pv = SvPV(sv, sv_len);
+    return cstring_to_text_with_len(sv_pv, sv_len);
+}
+
+MODULE = PostgreSQL::InServer::Util PREFIX = util_
+
+PROTOTYPES: ENABLE
+VERSIONCHECK: DISABLE
+
+int
+_aliased_constants()
+    PROTOTYPE:
+    ALIAS:
+        DEBUG   = DEBUG2
+        LOG     = LOG
+        INFO    = INFO
+        NOTICE  = NOTICE
+        WARNING = WARNING
+        ERROR   = ERROR
+    CODE:
+    /* uses the ALIAS value as the return value */
+    RETVAL = ix;
+    OUTPUT:
+    RETVAL
+
+
+void
+util_elog(level, message)
+    int level
+    char* message
+    CODE:
+        if (level > ERROR)      /* no PANIC allowed thanks */
+            level = ERROR;
+        if (level < DEBUG5)
+            level = DEBUG5;
+        do_util_elog(level, message);
+
+SV *
+util_quote_literal(sv)
+    SV *sv
+    CODE:
+    if (!sv || !SvOK(sv)) {
+        RETVAL = &PL_sv_undef;
+    }
+    else {
+        text *arg = sv2text(sv);
+        text *ret = DatumGetTextP(DirectFunctionCall1(quote_literal, PointerGetDatum(arg)));
+        RETVAL = newSVstring_len(VARDATA(ret), (VARSIZE(ret) - VARHDRSZ));
+    }
+    OUTPUT:
+    RETVAL
+
+SV *
+util_quote_nullable(sv)
+    SV *sv
+    CODE:
+    if (!sv || !SvOK(sv)) 
+   {
+        RETVAL = newSVstring_len("NULL", 4);
+    }
+    else 
+   {
+        text *arg = sv2text(sv);
+        text *ret = DatumGetTextP(DirectFunctionCall1(quote_nullable, PointerGetDatum(arg)));
+        RETVAL = newSVstring_len(VARDATA(ret), (VARSIZE(ret) - VARHDRSZ));
+    }
+    OUTPUT:
+    RETVAL
+
+SV *
+util_quote_ident(sv)
+    SV *sv
+    PREINIT:
+        text *arg;
+        text *ret;
+    CODE:
+        arg = sv2text(sv);
+        ret = DatumGetTextP(DirectFunctionCall1(quote_ident, PointerGetDatum(arg)));
+        RETVAL = newSVstring_len(VARDATA(ret), (VARSIZE(ret) - VARHDRSZ));
+    OUTPUT:
+    RETVAL
+
+SV *
+util_decode_bytea(sv)
+    SV *sv
+    PREINIT:
+        char *arg;
+        text *ret;
+    CODE:
+        arg = SvPV_nolen(sv);
+        ret = DatumGetTextP(DirectFunctionCall1(byteain, PointerGetDatum(arg)));
+        /* not newSVstring_len because this is raw bytes not utf8'able */
+        RETVAL = newSVpvn(VARDATA(ret), (VARSIZE(ret) - VARHDRSZ));
+    OUTPUT:
+    RETVAL
+
+SV *
+util_encode_bytea(sv)
+    SV *sv
+    PREINIT:
+        text *arg;
+        char *ret;
+    CODE:
+        arg = sv2text(sv);
+        ret = DatumGetCString(DirectFunctionCall1(byteaout, PointerGetDatum(arg)));
+        RETVAL = newSVstring_len(ret, strlen(ret));
+    OUTPUT:
+    RETVAL
+
+SV *
+looks_like_number(sv)
+    SV *sv
+    CODE:
+    if (!SvOK(sv))
+        RETVAL = &PL_sv_undef;
+    else if ( looks_like_number(sv) )
+        RETVAL = &PL_sv_yes;
+    else
+        RETVAL = &PL_sv_no;
+    OUTPUT:
+    RETVAL
+
+
+BOOT:
+    items = 0;  /* avoid 'unused variable' warning */
index 1791d3cc314cd122c5150bcf91691d61d515ef11..89497e3236d48b80fe042ea1598b1f9f49b61cc3 100644 (file)
@@ -21,7 +21,6 @@ create or replace function perl_warn(text) returns void language plperl as $$
 $$;
 select perl_warn('implicit elog via warn');
 NOTICE:  implicit elog via warn at line 4.
-
 CONTEXT:  PL/Perl function "perl_warn"
  perl_warn 
 -----------
diff --git a/src/pl/plperl/expected/plperl_util.out b/src/pl/plperl/expected/plperl_util.out
new file mode 100644 (file)
index 0000000..6f16669
--- /dev/null
@@ -0,0 +1,171 @@
+-- test plperl utility functions (defined in Util.xs)
+-- test quote_literal
+create or replace function perl_quote_literal() returns setof text language plperl as $$
+   return_next "undef: ".quote_literal(undef);
+   return_next sprintf"$_: ".quote_literal($_)
+       for q{foo}, q{a'b}, q{a"b}, q{c''d}, q{e\f}, q{};
+   return undef;
+$$;
+select perl_quote_literal();
+ perl_quote_literal 
+--------------------
+ undef: 
+ foo: 'foo'
+ a'b: 'a''b'
+ a"b: 'a"b'
+ c''d: 'c''''d'
+ e\f: E'e\\f'
+ : ''
+(7 rows)
+
+-- test quote_nullable
+create or replace function perl_quote_nullable() returns setof text language plperl as $$
+   return_next "undef: ".quote_nullable(undef);
+   return_next sprintf"$_: ".quote_nullable($_)
+       for q{foo}, q{a'b}, q{a"b}, q{c''d}, q{e\f}, q{};
+   return undef;
+$$;
+select perl_quote_nullable();
+ perl_quote_nullable 
+---------------------
+ undef: NULL
+ foo: 'foo'
+ a'b: 'a''b'
+ a"b: 'a"b'
+ c''d: 'c''''d'
+ e\f: E'e\\f'
+ : ''
+(7 rows)
+
+-- test quote_ident
+create or replace function perl_quote_ident() returns setof text language plperl as $$
+   return_next "undef: ".quote_ident(undef); # generates undef warning if warnings enabled
+   return_next "$_: ".quote_ident($_)
+       for q{foo}, q{a'b}, q{a"b}, q{c''d}, q{e\f}, q{g.h}, q{};
+   return undef;
+$$;
+select perl_quote_ident();
+ perl_quote_ident 
+------------------
+ undef: ""
+ foo: foo
+ a'b: "a'b"
+ a"b: "a""b"
+ c''d: "c''d"
+ e\f: "e\f"
+ g.h: "g.h"
+ : ""
+(8 rows)
+
+-- test decode_bytea
+create or replace function perl_decode_bytea() returns setof text language plperl as $$
+   return_next "undef: ".decode_bytea(undef); # generates undef warning if warnings enabled
+   return_next "$_: ".decode_bytea($_)
+       for q{foo}, q{a\047b}, q{};
+   return undef;
+$$;
+select perl_decode_bytea();
+ perl_decode_bytea 
+-------------------
+ undef: 
+ foo: foo
+ a\047b: a'b
+ : 
+(4 rows)
+
+-- test encode_bytea
+create or replace function perl_encode_bytea() returns setof text language plperl as $$
+   return_next encode_bytea(undef); # generates undef warning if warnings enabled
+   return_next encode_bytea($_)
+       for q{@}, qq{@\x01@}, qq{@\x00@}, q{};
+   return undef;
+$$;
+select perl_encode_bytea();
+ perl_encode_bytea 
+-------------------
+ \x
+ \x40
+ \x400140
+ \x400040
+ \x
+(5 rows)
+
+-- test encode_array_literal
+create or replace function perl_encode_array_literal() returns setof text language plperl as $$
+   return_next encode_array_literal(undef);
+   return_next encode_array_literal(0);
+   return_next encode_array_literal(42);
+   return_next encode_array_literal($_)
+       for [], [0], [1..5], [[]], [[1,2,[3]],4];
+   return_next encode_array_literal($_,'|')
+       for [], [0], [1..5], [[]], [[1,2,[3]],4];
+   return undef;
+$$;
+select perl_encode_array_literal();
+ perl_encode_array_literal 
+---------------------------
+ 0
+ 42
+ {}
+ {"0"}
+ {"1", "2", "3", "4", "5"}
+ {{}}
+ {{"1", "2", {"3"}}, "4"}
+ {}
+ {"0"}
+ {"1"|"2"|"3"|"4"|"5"}
+ {{}}
+ {{"1"|"2"|{"3"}}|"4"}
+(13 rows)
+
+-- test encode_array_constructor
+create or replace function perl_encode_array_constructor() returns setof text language plperl as $$
+   return_next encode_array_constructor(undef);
+   return_next encode_array_constructor(0);
+   return_next encode_array_constructor(42);
+   return_next encode_array_constructor($_)
+       for [], [0], [1..5], [[]], [[1,2,[3]],4];
+   return undef;
+$$;
+select perl_encode_array_constructor();
+      perl_encode_array_constructor      
+-----------------------------------------
+ NULL
+ '0'
+ '42'
+ ARRAY[]
+ ARRAY['0']
+ ARRAY['1', '2', '3', '4', '5']
+ ARRAY[ARRAY[]]
+ ARRAY[ARRAY['1', '2', ARRAY['3']], '4']
+(8 rows)
+
+-- test looks_like_number
+create or replace function perl_looks_like_number() returns setof text language plperl as $$
+   return_next "undef is undef" if not defined looks_like_number(undef);
+   return_next quote_nullable($_).": ". (looks_like_number($_) ? "number" : "not number")
+       for 'foo', 0, 1, 1.3, '+3.e-4',
+           '42 x', # trailing garbage
+           '99  ', # trailing space
+           '  99', # leading space
+           '    ', # only space
+           '';     # empty string
+   return undef;
+$$;
+select perl_looks_like_number();
+ perl_looks_like_number 
+------------------------
+ undef is undef
+ 'foo': not number
+ '0': number
+ '1': number
+ '1.3': number
+ '+3.e-4': number
+ '42 x': not number
+ '99  ': number
+ '  99': number
+ '    ': not number
+ '': not number
+(11 rows)
+
index d2d55184766c5d722c4a108a42bf090d948a6bb6..29f7bed3dc4e442152f1f5ba20e3d9263291b1d7 100644 (file)
@@ -1,24 +1,33 @@
-SPI::bootstrap();
+
+#  $PostgreSQL: pgsql/src/pl/plperl/plc_perlboot.pl,v 1.2 2010/01/20 01:08:21 adunstan Exp $
+
+PostgreSQL::InServer::Util::bootstrap();
+PostgreSQL::InServer::SPI::bootstrap();
+
+use strict;
+use warnings;
 use vars qw(%_SHARED);
 
 sub ::plperl_warn {
    (my $msg = shift) =~ s/\(eval \d+\) //g;
+   chomp $msg;
    &elog(&NOTICE, $msg);
 }
 $SIG{__WARN__} = \&::plperl_warn;
 
 sub ::plperl_die {
    (my $msg = shift) =~ s/\(eval \d+\) //g;
-    die $msg;
+   die $msg;
 }
 $SIG{__DIE__} = \&::plperl_die;
 
+
 sub ::mkunsafefunc {
    my $ret = eval(qq[ sub { $_[0] $_[1] } ]);
    $@ =~ s/\(eval \d+\) //g if $@;
    return $ret;
 }
-
+  
 use strict;
 
 sub ::mk_strict_unsafefunc {
@@ -27,24 +36,36 @@ sub ::mk_strict_unsafefunc {
    return $ret;
 }
 
-sub ::_plperl_to_pg_array {
-  my $arg = shift;
-  ref $arg eq 'ARRAY' || return $arg;
-  my $res = '';
-  my $first = 1;
-  foreach my $elem (@$arg) {
-    $res .= ', ' unless $first; $first = undef;
-    if (ref $elem) {
-      $res .= _plperl_to_pg_array($elem);
-    }
-    elsif (defined($elem)) {
-      my $str = qq($elem);
-      $str =~ s/([\"\\])/\\$1/g;
-      $res .= qq(\"$str\");
-    }
-    else {
-      $res .= 'NULL' ;
-    }
-  }
-  return qq({$res});
+sub ::encode_array_literal {
+   my ($arg, $delim) = @_;
+   return $arg
+       if ref $arg ne 'ARRAY';
+   $delim = ', ' unless defined $delim;
+   my $res = '';
+   foreach my $elem (@$arg) {
+       $res .= $delim if length $res;
+       if (ref $elem) {
+           $res .= ::encode_array_literal($elem, $delim);
+       }
+       elsif (defined $elem) {
+           (my $str = $elem) =~ s/(["\\])/\\$1/g;
+           $res .= qq("$str");
+       }
+       else {
+           $res .= 'NULL';
+       }
+   }
+   return qq({$res});
+}
+
+sub ::encode_array_constructor {
+   my $arg = shift;
+   return quote_nullable($arg)
+       if ref $arg ne 'ARRAY';
+   my $res = join ", ", map {
+       (ref $_) ? ::encode_array_constructor($_)
+                : ::quote_nullable($_)
+   } @$arg;
+   return "ARRAY[$res]";
 }
+
index 838ccc63af5a63779b143696e54b3b6eef7f12c3..4193c8181803c23786cf51e2d0a55e31e31a2089 100644 (file)
@@ -1,3 +1,6 @@
+
+#  $PostgreSQL: pgsql/src/pl/plperl/plc_safe_bad.pl,v 1.2 2010/01/20 01:08:21 adunstan Exp $
+
 use vars qw($PLContainer);
 
 $PLContainer = new Safe('PLPerl');
index 73c5573ba8961247e538d157b7893e7bbc85c58c..cc4d3bdc3fad7b94d085ceb11878b66eecb10454 100644 (file)
@@ -1,3 +1,7 @@
+
+
+#  $PostgreSQL: pgsql/src/pl/plperl/plc_safe_ok.pl,v 1.2 2010/01/20 01:08:21 adunstan Exp $
+
 use vars qw($PLContainer);
 
 $PLContainer = new Safe('PLPerl');
@@ -7,8 +11,11 @@ $PLContainer->permit(qw[:base_math !:base_io sort time]);
 $PLContainer->share(qw[&elog &return_next
    &spi_query &spi_fetchrow &spi_cursor_close &spi_exec_query
    &spi_prepare &spi_exec_prepared &spi_query_prepared &spi_freeplan
-   &_plperl_to_pg_array
    &DEBUG &LOG &INFO &NOTICE &WARNING &ERROR %_SHARED
+   "e_literal "e_nullable "e_ident
+   &encode_bytea &decode_bytea
+   &encode_array_literal &encode_array_constructor
+   &looks_like_number
 ]);
 
 # Load strict into the container.
index 1dd704ffd06bf2c498236e966a31099c2251368a..6daab687c3b8273bb1a8f3c4b74cf200f8b4bbf3 100644 (file)
@@ -1,7 +1,7 @@
 /**********************************************************************
  * plperl.c - perl as a procedural language for PostgreSQL
  *
- *   $PostgreSQL: pgsql/src/pl/plperl/plperl.c,v 1.159 2010/01/09 02:40:50 adunstan Exp $
+ *   $PostgreSQL: pgsql/src/pl/plperl/plperl.c,v 1.160 2010/01/20 01:08:21 adunstan Exp $
  *
  **********************************************************************/
 
@@ -589,12 +589,12 @@ plperl_convert_to_pg_array(SV *src)
    XPUSHs(src);
    PUTBACK;
 
-   count = call_pv("::_plperl_to_pg_array", G_SCALAR);
+   count = perl_call_pv("::encode_array_literal", G_SCALAR);
 
    SPAGAIN;
 
    if (count != 1)
-       elog(ERROR, "unexpected _plperl_to_pg_array failure");
+       elog(ERROR, "unexpected encode_array_literal failure");
 
    rv = POPs;
 
@@ -1089,7 +1089,8 @@ plperl_create_sub(plperl_proc_desc *prodesc, char *s)
  **********************************************************************/
 
 EXTERN_C void boot_DynaLoader(pTHX_ CV *cv);
-EXTERN_C void boot_SPI(pTHX_ CV *cv);
+EXTERN_C void boot_PostgreSQL__InServer__SPI(pTHX_ CV *cv);
+EXTERN_C void boot_PostgreSQL__InServer__Util(pTHX_ CV *cv);
 
 static void
 plperl_init_shared_libs(pTHX)
@@ -1097,7 +1098,10 @@ plperl_init_shared_libs(pTHX)
    char       *file = __FILE__;
 
    newXS("DynaLoader::boot_DynaLoader", boot_DynaLoader, file);
-   newXS("SPI::bootstrap", boot_SPI, file);
+   newXS("PostgreSQL::InServer::SPI::bootstrap",
+         boot_PostgreSQL__InServer__SPI, file);
+   newXS("PostgreSQL::InServer::Util::bootstrap",
+       boot_PostgreSQL__InServer__Util, file);
 }
 
 
index ae1002cdd736e90d9acc43156e9387d4c55596e3..6d58f117ca128f1844d3b0fea323d08180c76ac0 100644 (file)
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
  * Portions Copyright (c) 1995, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/pl/plperl/plperl.h,v 1.10 2010/01/02 16:58:12 momjian Exp $
+ * $PostgreSQL: pgsql/src/pl/plperl/plperl.h,v 1.11 2010/01/20 01:08:21 adunstan Exp $
  */
 
 #ifndef PL_PERL_H
 #include "EXTERN.h"
 #include "perl.h"
 #include "XSUB.h"
-#include "ppport.h"
 
-/* just in case these symbols aren't provided */
-#ifndef pTHX_
-#define pTHX_
-#define pTHX void
-#endif
+/* perl version and platform portability */
+#define NEED_eval_pv
+#define NEED_newRV_noinc
+#define NEED_sv_2pv_flags
+#include "ppport.h"
 
 /* perl may have a different width of "bool", don't buy it */
 #ifdef bool
 #undef bool
 #endif
 
-/* routines from spi_internal.c */
-int            spi_DEBUG(void);
-int            spi_LOG(void);
-int            spi_INFO(void);
-int            spi_NOTICE(void);
-int            spi_WARNING(void);
-int            spi_ERROR(void);
-
-/* routines from plperl.c */
+/* declare routines from plperl.c for access by .xs files */
 HV        *plperl_spi_exec(char *, int);
 void       plperl_return_next(SV *);
 SV        *plperl_spi_query(char *);
diff --git a/src/pl/plperl/spi_internal.c b/src/pl/plperl/spi_internal.c
deleted file mode 100644 (file)
index 5544fbf..0000000
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * $PostgreSQL: pgsql/src/pl/plperl/spi_internal.c,v 1.10 2009/06/11 14:49:14 momjian Exp $
- *
- *
- * This kludge is necessary because of the conflicting
- * definitions of 'DEBUG' between postgres and perl.
- * we'll live.
- */
-
-#include "postgres.h"
-/* Defined by Perl */
-#undef _
-
-/* perl stuff */
-#include "plperl.h"
-
-int
-spi_DEBUG(void)
-{
-   return DEBUG2;
-}
-
-int
-spi_LOG(void)
-{
-   return LOG;
-}
-
-int
-spi_INFO(void)
-{
-   return INFO;
-}
-
-int
-spi_NOTICE(void)
-{
-   return NOTICE;
-}
-
-int
-spi_WARNING(void)
-{
-   return WARNING;
-}
-
-int
-spi_ERROR(void)
-{
-   return ERROR;
-}
diff --git a/src/pl/plperl/sql/plperl_util.sql b/src/pl/plperl/sql/plperl_util.sql
new file mode 100644 (file)
index 0000000..6a810d8
--- /dev/null
@@ -0,0 +1,100 @@
+-- test plperl utility functions (defined in Util.xs)
+
+-- test quote_literal
+
+create or replace function perl_quote_literal() returns setof text language plperl as $$
+   return_next "undef: ".quote_literal(undef);
+   return_next sprintf"$_: ".quote_literal($_)
+       for q{foo}, q{a'b}, q{a"b}, q{c''d}, q{e\f}, q{};
+   return undef;
+$$;
+
+select perl_quote_literal();
+
+-- test quote_nullable
+
+create or replace function perl_quote_nullable() returns setof text language plperl as $$
+   return_next "undef: ".quote_nullable(undef);
+   return_next sprintf"$_: ".quote_nullable($_)
+       for q{foo}, q{a'b}, q{a"b}, q{c''d}, q{e\f}, q{};
+   return undef;
+$$;
+
+select perl_quote_nullable();
+
+-- test quote_ident
+
+create or replace function perl_quote_ident() returns setof text language plperl as $$
+   return_next "undef: ".quote_ident(undef); # generates undef warning if warnings enabled
+   return_next "$_: ".quote_ident($_)
+       for q{foo}, q{a'b}, q{a"b}, q{c''d}, q{e\f}, q{g.h}, q{};
+   return undef;
+$$;
+
+select perl_quote_ident();
+
+-- test decode_bytea
+
+create or replace function perl_decode_bytea() returns setof text language plperl as $$
+   return_next "undef: ".decode_bytea(undef); # generates undef warning if warnings enabled
+   return_next "$_: ".decode_bytea($_)
+       for q{foo}, q{a\047b}, q{};
+   return undef;
+$$;
+
+select perl_decode_bytea();
+
+-- test encode_bytea
+
+create or replace function perl_encode_bytea() returns setof text language plperl as $$
+   return_next encode_bytea(undef); # generates undef warning if warnings enabled
+   return_next encode_bytea($_)
+       for q{@}, qq{@\x01@}, qq{@\x00@}, q{};
+   return undef;
+$$;
+
+select perl_encode_bytea();
+
+-- test encode_array_literal
+
+create or replace function perl_encode_array_literal() returns setof text language plperl as $$
+   return_next encode_array_literal(undef);
+   return_next encode_array_literal(0);
+   return_next encode_array_literal(42);
+   return_next encode_array_literal($_)
+       for [], [0], [1..5], [[]], [[1,2,[3]],4];
+   return_next encode_array_literal($_,'|')
+       for [], [0], [1..5], [[]], [[1,2,[3]],4];
+   return undef;
+$$;
+
+select perl_encode_array_literal();
+
+-- test encode_array_constructor
+
+create or replace function perl_encode_array_constructor() returns setof text language plperl as $$
+   return_next encode_array_constructor(undef);
+   return_next encode_array_constructor(0);
+   return_next encode_array_constructor(42);
+   return_next encode_array_constructor($_)
+       for [], [0], [1..5], [[]], [[1,2,[3]],4];
+   return undef;
+$$;
+
+select perl_encode_array_constructor();
+
+-- test looks_like_number
+
+create or replace function perl_looks_like_number() returns setof text language plperl as $$
+   return_next "undef is undef" if not defined looks_like_number(undef);
+   return_next quote_nullable($_).": ". (looks_like_number($_) ? "number" : "not number")
+       for 'foo', 0, 1, 1.3, '+3.e-4',
+           '42 x', # trailing garbage
+           '99  ', # trailing space
+           '  99', # leading space
+           '    ', # only space
+           '';     # empty string
+   return undef;
+$$;
+
+select perl_looks_like_number();
index 1628e8688d84f991913414a273e6ed39ce983631..7e13ea5b276332af65dd0732b0c4cd74291365de 100644 (file)
@@ -1,3 +1,6 @@
+
+# $PostgreSQL: pgsql/src/pl/plperl/text2macro.pl,v 1.2 2010/01/20 01:08:21 adunstan Exp $
+
 =head1 NAME
 
 text2macro.pl - convert text files into C string-literal macro definitions