Committed by David Christensen <[email protected]>

Fixes to the array-marshalling code

Based on a patch by Noah Misch, with revisions Mark Stosberg
---
 Pg.xs        |   13 +++--
 dbdimp.c     |   19 ++-----
 dbdimp.h     |    2 +-
 t/09arrays.t |  154 ++++++++++++++++++++-------------------------------------
 4 files changed, 68 insertions(+), 120 deletions(-)

diff --git a/Pg.xs b/Pg.xs
index 227b177..64903b2 100644
--- a/Pg.xs
+++ b/Pg.xs
@@ -199,15 +199,18 @@ quote(dbh, to_quote_sv, type_sv=Nullsv)
 
                SvGETMAGIC(to_quote_sv);
 
+               /* Reject references other than overloaded objects (presumed
+                 stringifiable) and arrays (will make a PostgreSQL array). */
+               if (SvROK(to_quote_sv) && !SvAMAGIC(to_quote_sv)) {
+                       if (SvTYPE(SvRV(to_quote_sv)) != SVt_PVAV)
+                               croak("Cannot quote a reference");
+                       to_quote_sv = pg_stringify_array(to_quote_sv, ",", 
imp_dbh->pg_server_version);
+               }
+
                /* Null is always returned as "NULL", so we can ignore any type 
given */
                if (!SvOK(to_quote_sv)) {
                        RETVAL = newSVpvn("NULL", 4);
                }
-               else if (SvROK(to_quote_sv) && !SvAMAGIC(to_quote_sv)) {
-                       if (SvTYPE(SvRV(to_quote_sv)) != SVt_PVAV)
-                               croak("Cannot quote a reference");
-                       RETVAL = pg_stringify_array(to_quote_sv, ",", 
imp_dbh->pg_server_version, 1);
-               }
                else {
                        sql_type_info_t *type_info;
                        char *quoted;
diff --git a/dbdimp.c b/dbdimp.c
index b6f1235..da4b576 100644
--- a/dbdimp.c
+++ b/dbdimp.c
@@ -2348,7 +2348,7 @@ int dbd_bind_ph (SV * sth, imp_sth_t * imp_sth, SV * 
ph_name, SV * newvalue, IV
                }
                else if (SvTYPE(SvRV(newvalue)) == SVt_PVAV) {
                        SV * quotedval;
-                       quotedval = 
pg_stringify_array(newvalue,",",imp_dbh->pg_server_version, 0);
+                       quotedval = 
pg_stringify_array(newvalue,",",imp_dbh->pg_server_version);
                        currph->valuelen = sv_len(quotedval);
                        Renew(currph->value, currph->valuelen+1, char); /* 
freed in dbd_st_destroy */
                        Copy(SvUTF8(quotedval) ? SvPVutf8_nolen(quotedval) : 
SvPV_nolen(quotedval),
@@ -2484,7 +2484,7 @@ int dbd_bind_ph (SV * sth, imp_sth_t * imp_sth, SV * 
ph_name, SV * newvalue, IV
 
 
 /* ================================================================== */
-SV * pg_stringify_array(SV *input, const char * array_delim, int 
server_version, int extraquotes) {
+SV * pg_stringify_array(SV *input, const char * array_delim, int 
server_version) {
 
        dTHX;
        AV * toparr;
@@ -2503,14 +2503,12 @@ SV * pg_stringify_array(SV *input, const char * 
array_delim, int server_version,
        if (TSTART) TRC(DBILOGFP, "%sBegin pg_stringify_array\n", THEADER);
 
        toparr = (AV *) SvRV(input);
-       value = extraquotes ? newSVpv("'{", 2) : newSVpv("{", 1);
+       value = newSVpv("{", 1);
 
        /* Empty arrays are easy */
        if (av_len(toparr) < 0) {
                av_clear(toparr);
                sv_catpv(value, "}");
-               if (extraquotes)
-                       sv_catpv(value, "'");
                if (TEND) TRC(DBILOGFP, "%sEnd pg_stringify_array (empty)\n", 
THEADER);
                return value;
        }
@@ -2580,14 +2578,9 @@ SV * pg_stringify_array(SV *input, const char * 
array_delim, int server_version,
                                        SvUTF8_on(value);
                                string = SvPV(svitem, stringlength);
                                while (stringlength--) {
-
-                                       /* If an embedded quote, throw a 
backslash before it */
-                                       if ('\"' == *string)
+                                       /* Escape backslashes and 
double-quotes. */
+                                       if ('\"' == *string || '\\' == *string)
                                                sv_catpvn(value, "\\", 1);
-                                       /* If a backslash, double it up */
-                                       if ('\\' == *string) {
-                                               sv_catpvn(value, "\\\\\\", 3);
-                                       }
                                        sv_catpvn(value, string, 1);
                                        string++;
                                }
@@ -2612,8 +2605,6 @@ SV * pg_stringify_array(SV *input, const char * 
array_delim, int server_version,
        for (xy=0; xy<array_depth; xy++) {
                sv_catpv(value, "}");
        }
-       if (extraquotes)
-               sv_catpv(value, "'");
 
        if (TEND) TRC(DBILOGFP, "%sEnd pg_stringify_array (string: %s)\n", 
THEADER, neatsvpv(value,0));
        return value;
diff --git a/dbdimp.h b/dbdimp.h
index a5176d2..3e73c82 100644
--- a/dbdimp.h
+++ b/dbdimp.h
@@ -190,7 +190,7 @@ int pg_db_getfd (imp_dbh_t * imp_dbh);
 
 SV * pg_db_pg_notifies (SV *dbh, imp_dbh_t *imp_dbh);
 
-SV * pg_stringify_array(SV * input, const char * array_delim, int 
server_version, int extraquotes);
+SV * pg_stringify_array(SV * input, const char * array_delim, int 
server_version);
 
 int pg_quickexec (SV *dbh, const char *sql, const int asyncflag);
 
diff --git a/t/09arrays.t b/t/09arrays.t
index 03a9e1a..62eb6de 100644
--- a/t/09arrays.t
+++ b/t/09arrays.t
@@ -18,7 +18,7 @@ my $dbh = connect_database();
 if (! $dbh) {
        plan skip_all => 'Connection to database failed, cannot continue 
testing';
 }
-plan tests => 257;
+plan tests => 200;
 
 isnt ($dbh, undef, 'Connect to database for array testing');
 
@@ -26,10 +26,6 @@ my ($sth,$result,$t);
 
 my $pgversion = $dbh->{pg_server_version};
 
-if ($pgversion >= 80100) {
-  $dbh->do('SET escape_string_warning = false');
-}
-
 my $SQL = q{DELETE FROM dbd_pg_test WHERE pname = 'Array Testing'};
 my $cleararray = $dbh->prepare($SQL);
 
@@ -52,9 +48,9 @@ $SQL = q{SELECT testarray3 FROM dbd_pg_test WHERE pname= 
'Array Testing'};
 my $getarray_bool = $dbh->prepare($SQL);
 
 $t='Array quoting allows direct insertion into statements';
-$SQL = q{INSERT INTO dbd_pg_test (id,testarray2) VALUES };
+$SQL = q{INSERT INTO dbd_pg_test (id,testarray) VALUES };
 my $quoteid = $dbh->quote(123);
-my $quotearr = $dbh->quote([456]);
+my $quotearr = $dbh->quote(["Quote's Test"]);
 $SQL .= qq{($quoteid, $quotearr)};
 eval {
        $dbh->do($SQL);
@@ -62,8 +58,8 @@ eval {
 is ($@, q{}, $t);
 $dbh->rollback();
 
-## Input
-## Expected
+## Input (eval-able Perl)
+## Expected (ERROR or raw PostgreSQL output)
 ## Name of test
 
 my $array_tests =
@@ -120,51 +116,51 @@ ERROR: must be of equal size
 Unbalanced array
 
 [123]
-{123} quote: {"123"}
+{123}
 Simple 1-D numeric array
 
 ['abc']
-{abc} quote: {"abc"}
+{abc}
 Simple 1-D text array
 
 ['a','b,c']
-{a,"b,c"} quote: {"a","b,c"}
+{a,"b,c"}
 Text array with commas and quotes
 
 ['a','b,}']
-{a,"b,}"} quote: {"a","b,}"}
+{a,"b,}"}
 Text array with commas, escaped closing brace
 
 ['a','b,]']
-{a,"b,]"} quote: {"a","b,]"}
+{a,"b,]"}
 Text array with commas, escaped closing bracket
 
 [1,2]
-{1,2} quote: {"1","2"}
+{1,2}
 Simple 1-D numeric array
 
 [[1]]
-{{1}} quote: {{"1"}}
+{{1}}
 Simple 2-D numeric array
 
 [[1,2]]
-{{1,2}} quote: {{"1","2"}}
+{{1,2}}
 Simple 2-D numeric array
 
 [[[1]]]
-{{{1}}} quote: {{{"1"}}}
+{{{1}}}
 Simple 3-D numeric array
 
 [[["alpha",2],[23,"pop"]]]
-{{{alpha,2},{23,pop}}} quote: {{{"alpha","2"},{"23","pop"}}}
+{{{alpha,2},{23,pop}}}
 3-D mixed array
 
 [[[1,2,3],[4,5,"6"],["seven","8","9"]]]
-{{{1,2,3},{4,5,6},{seven,8,9}}} quote: 
{{{"1","2","3"},{"4","5","6"},{"seven","8","9"}}}
+{{{1,2,3},{4,5,6},{seven,8,9}}}
 3-D mixed array
 
 [q{O'RLY?}]
-{O'RLY?} quote: {"O'RLY?"}
+{O'RLY?}
 Simple single quote
 
 [q{O"RLY?}]
@@ -172,19 +168,19 @@ Simple single quote
 Simple double quote
 
 [[q{O"RLY?}],[q|'Ya' - "really"|],[123]]
-{{"O\"RLY?"},{"'Ya' - \"really\""},{123}} quote: {{"O\"RLY?"},{"'Ya' - 
\"really\""},{"123"}}
+{{"O\"RLY?"},{"'Ya' - \"really\""},{123}}
 Many quotes
 
 ["Single\\\\Backslash"]
-{"Single\\\\\\\\Backslash"} quote: {"Single\\\\\\\\Backslash"}
+{"Single\\\\Backslash"}
 Single backslash testing
 
 ["Double\\\\\\\\Backslash"]
-{"Double\\\\\\\\\\\\\\\\Backslash"} quote: {"Double\\\\\\\\\\\\\\\\Backslash"}
+{"Double\\\\\\\\Backslash"}
 Double backslash testing
 
 [["Test\\\nRun","Quite \"so\""],["back\\\\\\\\slashes are a \"pa\\\\in\"",123] 
]
-{{"Test\\\\\\\\nRun","Quite \"so\""},{"back\\\\\\\\\\\\\\\\slashes are a 
\"pa\\\\\\\\in\"",123}} quote: {{"Test\\\\\\\nRun","Quite 
\"so\""},{"back\\\\\\\\\\\\\\\\slashes are a \"pa\\\\\\\\in\"","123"}}
+{{"Test\\\nRun","Quite \"so\""},{"back\\\\\\\\slashes are a \"pa\\\\in\"",123}}
 Escape party - backslash+newline, two + one
 
 [undef]
@@ -196,25 +192,25 @@ NEED 80200: Simple undef test
 NEED 80200: Simple undef test
 
 [[1,2],[undef,3],["four",undef],[undef,undef]]
-{{1,2},{NULL,3},{four,NULL},{NULL,NULL}} quote: 
{{"1","2"},{NULL,"3"},{"four",NULL},{NULL,NULL}}
+{{1,2},{NULL,3},{four,NULL},{NULL,NULL}}
 NEED 80200: Multiple undef test
 
 !;
 
 ## Note: We silently allow things like this: [[[]],[]]
 
-$dbh->{pg_expand_array} = 0;
+sub safe_getarray {
+       my $ret = eval {
+               $getarray->execute();
+               $getarray->fetchall_arrayref()->[0][0];
+       };
+       $@ || $ret
+}
 
 for my $test (split /\n\n/ => $array_tests) {
        next unless $test =~ /\w/;
        my ($input,$expected,$msg) = split /\n/ => $test;
-       my $qexpected = $expected;
-       if ($expected =~ s/\s*quote:\s*(.+)//) {
-               $qexpected = $1;
-       }
-       if ($qexpected !~ /^ERROR/) {
-               $qexpected = qq{'$qexpected'};
-       }
+       my $perl_input = eval $input;
 
        if ($msg =~ s/NEED (\d+):\s*//) {
                my $ver = $1;
@@ -226,86 +222,45 @@ for my $test (split /\n\n/ => $array_tests) {
                }
        }
 
-       $t="Correct array inserted: $msg : $input";
-       $cleararray->execute();
+       # INSERT via bind values
+       $dbh->rollback;
        eval {
-               $addarray->execute(eval $input);
+               $addarray->execute($perl_input);
        };
        if ($expected =~ /error:\s+(.+)/i) {
-               like ($@, qr{$1}, "Array failed : $msg : $input");
-               like ($@, qr{$1}, "Array failed : $msg : $input");
+               like ($@, qr{$1}, "[bind] Array insert error : $msg : $input");
        }
        else {
-               is ($@, q{}, "Array worked : $msg : $input");
-               $getarray->execute();
-               $result = $getarray->fetchall_arrayref()->[0][0];
-               is ($result, $expected, $t);
-       }
-
-       $t="Array quote worked : $msg : $input";
-       eval {
-               $result = $dbh->quote(eval $input );
-       };
-       if ($qexpected =~ /error:\s+(.+)/i) {
-               my $errmsg = $1;
-               $errmsg =~ s/bind/quote/;
-               like ($@, qr{$errmsg}, "Array quote failed : $msg : $input");
-               like ($@, qr{$errmsg}, "Array quote failed : $msg : $input");
-       }
-       else {
-               is ($@, q{}, $t);
-
-               $t="Correct array quote: $msg : $input";
-               is ($result, $qexpected, $t);
-       }
-
-}
+               is ($@, q{}, "[bind] Array insert success : $msg : $input");
 
+               $t="[bind][!expand] Correct array inserted: $msg : $input";
+               $dbh->{pg_expand_array} = 0;
+               is (safe_getarray, $expected, $t);
 
-## Same thing, but expand the arrays
-$dbh->{pg_expand_array} = 1;
-
-for my $test (split /\n\n/ => $array_tests) {
-       next unless $test =~ /\w/;
-       my ($input,$expected,$msg) = split /\n/ => $test;
-       my $qexpected = $expected;
-       if ($expected =~ s/\s*quote:\s*(.+)//) {
-               $qexpected = $1;
+               $t="[bind][expand] Correct array inserted: $msg : $input";
+               $dbh->{pg_expand_array} = 1;
+               is_deeply (safe_getarray, $perl_input, $t);
        }
 
-       if ($msg =~ s/NEED (\d+):\s*//) {
-               my $ver = $1;
-               if ($pgversion < $ver) {
-                 SKIP: {
-                               skip ('Cannot test NULL arrays unless version 
8.2 or better', 2);
-                       }
-                       next;
-               }
-       }
-
-       $t="Array worked : $msg : $input";
-       $cleararray->execute();
+       # INSERT via `quote' and dynamic SQL
+       $dbh->rollback;
        eval {
-               $addarray->execute(eval $input);
+               $quotearr = $dbh->quote($perl_input);
+               $SQL = qq{INSERT INTO dbd_pg_test(id,pname,testarray) VALUES 
(99,'Array Testing',$quotearr)};
+               $dbh->do($SQL);
        };
        if ($expected =~ /error:\s+(.+)/i) {
-               like ($@, qr{$1}, "Array failed : $msg : $input");
-               like ($@, qr{$1}, "Array failed : $msg : $input");
+               my $errmsg = $1;
+               $errmsg =~ s/bind/quote/;
+               like ($@, qr{$errmsg}, "[quote] Array insert error : $msg : 
$input");
        }
        else {
-               is ($@, q{}, $t);
+               is ($@, q{}, "[quote] Array insert success : $msg : $input");
 
-               $t="Correct array inserted: $msg : $input";
-               $getarray->execute();
-               $result = $getarray->fetchall_arrayref()->[0][0];
-               $qexpected =~ s/{}/{''}/;
-               $qexpected =~ y/{}/[]/;
-               $qexpected =~ s/NULL/undef/g;
-               if ($msg =~ /closing brace/) {
-                       $qexpected =~ s/]"/}"/;
-               }
-               $expected = eval $qexpected;
-               is_deeply ($result, $expected, $t);
+               # No need to recheck !expand case.
+
+               $t="[quote][expand] Correct array inserted: $msg : $input";
+               is_deeply (safe_getarray, $perl_input, $t);
        }
 
        if ($msg =~ /STOP/) {
@@ -315,7 +270,6 @@ for my $test (split /\n\n/ => $array_tests) {
                $dbh->disconnect;
                exit;
        }
-
 }
 
 
-- 
1.7.1

Reply via email to