script parser now supports dollar quoting
authorchriskl <chriskl>
Fri, 11 Mar 2005 08:36:08 +0000 (08:36 +0000)
committerchriskl <chriskl>
Fri, 11 Mar 2005 08:36:08 +0000 (08:36 +0000)
classes/database/Postgres.php

index ee7b00910d1bc2c10151893bcf18c4290d35cdda..9afd6f7232113a58a2e92468a00c7db7882676e2 100755 (executable)
@@ -4,7 +4,7 @@
  * A class that implements the DB interface for Postgres
  * Note: This class uses ADODB and returns RecordSets.
  *
- * $Id: Postgres.php,v 1.255 2005/03/07 09:36:19 chriskl Exp $
+ * $Id: Postgres.php,v 1.256 2005/03/11 08:36:08 chriskl Exp $
  */
 
 // @@@ THOUGHT: What about inherits? ie. use of ONLY???
@@ -4103,213 +4103,264 @@ class Postgres extends ADODB_base {
                return $this->selectSet($sql);
        }
 
-    /**
-     * A private helper method for executeScript that advances the
-     * character by 1.  In psql this is careful to take into account
-     * multibyte languages, but we don't at the moment, so this function
-     * is someone redundant, since it will always advance by 1
-     * @param &$i The current character position in the line
-     * @param &$prevlen Length of previous character (ie. 1)
-     * @param &$thislen Length of current character (ie. 1)     
-     */
-    function advance_1(&$i, &$prevlen, &$thislen) {
-        $prevlen = $thislen;
-        $i += $thislen;
-        $thislen = 1;
-    }
-
-       /** 
-        * Executes an SQL script as a series of SQL statements.  Returns
-        * the result of the final step.  This is a very complicated lexer
-        * based on the REL7_4_STABLE src/bin/psql/mainloop.c lexer in
-        * the PostgreSQL source code.
-        * XXX: It does not handle multibyte languages properly.
-        * @param $name Entry in $_FILES to use
-        * @return Result of final query, false on any failure.
-        */
-       function executeScript($name) {
-               global $data;
-
-               // This whole function isn't very encapsulated, but hey...
-               $conn = $data->conn->_connectionID;
-               if (!is_uploaded_file($_FILES[$name]['tmp_name'])) return false;
-
-               $fd = fopen($_FILES[$name]['tmp_name'], 'r');
-               if (!$fd) return false;
-               
-               // Build up each SQL statement, they can be multiline
-               $query_buf = null;
-               $query_start = 0;
-               $in_quote = 0;
-               $in_xcomment = 0;
-               $bslash_count = 0;
-               $paren_level = 0;
-               $len = 0;
-               $i = 0;
-               $prevlen = 0;
-               $thislen = 0;
-               
-               // Loop over each line in the file
-               while (!feof($fd)) {
-                       $line = fgets($fd, 32768);
-                       
-                       // Nothing left on line? Then ignore...
-                       if (trim($line) == '') continue;
-               
-                   $len = strlen($line);
-                   $query_start = 0;
-
-               /*
-                * Parse line, looking for command separators.
-                *
-                * The current character is at line[i], the prior character at line[i
-                * - prevlen], the next character at line[i + thislen].
-                */
-               $prevlen = 0;
-               $thislen = ($len > 0) ? 1 : 0;
-    
+       /**    \r
+        * Private helper method to detect a valid $foo$ quote delimiter at\r
+        * the start of the parameter dquote\r
+        * @return True if valid, false otherwise\r
+        */    \r
+       function valid_dolquote($dquote) {
+               // XXX: support multibyte\r
+               return (ereg('^[$][$]', $dquote) || ereg('^[$][_[:alpha:]][_[:alnum:]]*[$]', $dquote));\r
+       }\r
+       \r
+       /**\r
+        * A private helper method for executeScript that advances the\r
+        * character by 1.  In psql this is careful to take into account\r
+        * multibyte languages, but we don't at the moment, so this function\r
+        * is someone redundant, since it will always advance by 1\r
+        * @param &$i The current character position in the line\r
+        * @param &$prevlen Length of previous character (ie. 1)\r
+        * @param &$thislen Length of current character (ie. 1)     \r
+        */\r
+       function advance_1(&$i, &$prevlen, &$thislen) {
+               $prevlen = $thislen;\r
+               $i += $thislen;\r
+               $thislen = 1;\r
+       }\r
+\r
+       /** \r
+        * Executes an SQL script as a series of SQL statements.  Returns\r
+        * the result of the final step.  This is a very complicated lexer\r
+        * based on the REL7_4_STABLE src/bin/psql/mainloop.c lexer in\r
+        * the PostgreSQL source code.\r
+        * XXX: It does not handle multibyte languages properly.\r
+        * @param $name Entry in $_FILES to use\r
+        * @return Result of final query, false on any failure.\r
+        */\r
+       function executeScript($name) {\r
+               global $data;\r
+\r
+               // This whole function isn't very encapsulated, but hey...\r
+               $conn = $data->conn->_connectionID;\r
+               if (!is_uploaded_file($_FILES[$name]['tmp_name'])) return false;\r
+\r
+               $fd = fopen($_FILES[$name]['tmp_name'], 'r');\r
+               if (!$fd) return false;\r
+               \r
+               // Build up each SQL statement, they can be multiline\r
+               $query_buf = null;\r
+               $query_start = 0;\r
+               $in_quote = 0;\r
+               $in_xcomment = 0;\r
+               $bslash_count = 0;\r
+               $dol_quote = null;\r
+               $paren_level = 0;\r
+               $len = 0;\r
+               $i = 0;\r
+               $prevlen = 0;\r
+               $thislen = 0;\r
+               \r
+               // Loop over each line in the file\r
+               while (!feof($fd)) {\r
+                       $line = fgets($fd, 32768);\r
+                       \r
+                       // Nothing left on line? Then ignore...\r
+                       if (trim($line) == '') continue;\r
+               \r
+                   $len = strlen($line);\r
+                   $query_start = 0;\r
+\r
+               /*\r
+                * Parse line, looking for command separators.\r
+                *\r
+                * The current character is at line[i], the prior character at line[i\r
+                * - prevlen], the next character at line[i + thislen].\r
+                */\r
+               $prevlen = 0;\r
+               $thislen = ($len > 0) ? 1 : 0;\r
+    \r
                for ($i = 0; $i < $len; $this->advance_1($i, $prevlen, $thislen)) {
-                       /* was the previous character a backslash? */
-                       if ($i > 0 && substr($line, $i - $prevlen, 1) == '\\')
-                               $bslash_count++;
-                       else
-                               $bslash_count = 0;
-    
-                       /*
-                        * It is important to place the in_* test routines before the
-                        * in_* detection routines. i.e. we have to test if we are in
-                        * a quote before testing for comments.
-                        */
-    
-                       /* in quote? */
-                       if ($in_quote != 0)
-                       {
-                               /*
-                                * end of quote if matching non-backslashed character.
-                                * backslashes don't count for double quotes, though.
-                                */
-                               if (substr($line, $i, 1) == $in_quote &&
-                                       ($bslash_count % 2 == 0 || $in_quote == '"'))
-                                       $in_quote = 0;
-                       }
-    
-                       /* start of extended comment? */
-                       else if (substr($line, $i, 2) == '/*')
+                       
+                       /* was the previous character a backslash? */\r
+                       if ($i > 0 && substr($line, $i - $prevlen, 1) == '\\')\r
+                               $bslash_count++;\r
+                       else\r
+                               $bslash_count = 0;\r
+    \r
+                       /*\r
+                        * It is important to place the in_* test routines before the\r
+                        * in_* detection routines. i.e. we have to test if we are in\r
+                        * a quote before testing for comments.\r
+                        */\r
+    \r
+                       /* in quote? */\r
+                       if ($in_quote != 0)\r
                        {
-                               $in_xcomment++;
-                               if ($in_xcomment == 1)
-                                       $this->advance_1($i, $prevlen, $thislen);
-                       }
-    
-                       /* in or end of extended comment? */
-                       else if ($in_xcomment)
-                       {
-                               if (substr($line, $i, 2) == '*/' && !--$in_xcomment)
-                                       $this->advance_1($i, $prevlen, $thislen);
-                       }
-    
-                       /* start of quote? */
-                       else if (substr($line, $i, 1) == '\'' || substr($line, $i, 1) == '"') {
-                               $in_quote = substr($line, $i, 1);
-                   }
-    
-                       /* single-line comment? truncate line */
-                       else if (substr($line, $i, 2) == '--')
-                       {
-                           $line = substr($line, 0, $i); /* remove comment */
-                               break;
-                       }                       
-    
-                       /* count nested parentheses */
-                       else if (substr($line, $i, 1) == '(') {
-                               $paren_level++;
-                       }
-    
-                       else if (substr($line, $i, 1) == ')' && $paren_level > 0) {
-                               $paren_level--;
-                       }
-    
-                       /* semicolon? then send query */
-                       else if (substr($line, $i, 1) == ';' && !$bslash_count && !$paren_level)
-                       {
-                           $subline = substr(substr($line, 0, $i), $query_start);
-                               /* is there anything else on the line? */
-                               if (strspn($subline, " \t\n\r") != strlen($subline))
-                               {
-                                       /*
-                                        * insert a cosmetic newline, if this is not the first
-                                        * line in the buffer
-                                        */
-                                       if (strlen($query_buf) > 0)
-                                           $query_buf .= "\n";
-                                       /* append the line to the query buffer */
-                                       $query_buf .= $subline;
-                                       $query_buf .= ';';
-
-                               // Execute the query (supporting 4.1.x PHP...). PHP cannot execute
+                               /*\r
+                                * end of quote if matching non-backslashed character.\r
+                                * backslashes don't count for double quotes, though.\r
+                                */\r
+                               if (substr($line, $i, 1) == $in_quote &&\r
+                                       ($bslash_count % 2 == 0 || $in_quote == '"'))\r
+                                       $in_quote = 0;\r
+                       }\r
+                               \r
+                               /* in or end of $foo$ type quote? */                            \r
+                               else if ($dol_quote) {
+                                       if (strncmp(substr($line, $i), $dol_quote, strlen($dol_quote)) == 0) {\r
+                                               $this->advance_1($i, $prevlen, $thislen);\r
+                                               while(substr($line, $i, 1) != '$')\r
+                                                       $this->advance_1($i, $prevlen, $thislen);\r
+                                               $dol_quote = null;\r
+                                       }\r
+                               }\r
+    \r
+                       /* start of extended comment? */\r
+                       else if (substr($line, $i, 2) == '/*')\r
+                       {\r
+                               $in_xcomment++;\r
+                               if ($in_xcomment == 1)\r
+                                       $this->advance_1($i, $prevlen, $thislen);\r
+                       }\r
+    \r
+                       /* in or end of extended comment? */\r
+                       else if ($in_xcomment)\r
+                       {\r
+                               if (substr($line, $i, 2) == '*/' && !--$in_xcomment)\r
+                                       $this->advance_1($i, $prevlen, $thislen);\r
+                       }\r
+    \r
+                       /* start of quote? */\r
+                       else if (substr($line, $i, 1) == '\'' || substr($line, $i, 1) == '"') {\r
+                               $in_quote = substr($line, $i, 1);\r
+                   }\r
+\r
+                               /* \r
+                                * start of $foo$ type quote? \r
+                                */\r
+                               else if (!$dol_quote && $this->valid_dolquote(substr($line, $i))) {
+                                       $dol_end = strpos(substr($line, $i + 1), '$');\r
+                                       $dol_quote = substr($line, $i, $dol_end + 1);
+                                       $this->advance_1($i, $prevlen, $thislen);\r
+                                       while (substr($line, $i, 1) != '$') {
+                                               $this->advance_1($i, $prevlen, $thislen);
+                                       }\r
+                                       \r
+                               }\r
+    \r
+                       /* single-line comment? truncate line */\r
+                       else if (substr($line, $i, 2) == '--')\r
+                       {\r
+                           $line = substr($line, 0, $i); /* remove comment */\r
+                               break;\r
+                       }                       \r
+    \r
+                       /* count nested parentheses */\r
+                       else if (substr($line, $i, 1) == '(') {\r
+                               $paren_level++;\r
+                       }\r
+    \r
+                       else if (substr($line, $i, 1) == ')' && $paren_level > 0) {\r
+                               $paren_level--;\r
+                       }\r
+    \r
+                       /* semicolon? then send query */\r
+                       else if (substr($line, $i, 1) == ';' && !$bslash_count && !$paren_level)\r
+                       {\r
+                           $subline = substr(substr($line, 0, $i), $query_start);\r
+                               /* is there anything else on the line? */\r
+                               if (strspn($subline, " \t\n\r") != strlen($subline))\r
+                               {\r
+                                       /*\r
+                                        * insert a cosmetic newline, if this is not the first\r
+                                        * line in the buffer\r
+                                        */\r
+                                       if (strlen($query_buf) > 0)\r
+                                           $query_buf .= "\n";\r
+                                       /* append the line to the query buffer */\r
+                                       $query_buf .= $subline;\r
+                                       $query_buf .= ';';\r
+\r
+                               // Execute the query (supporting 4.1.x PHP...). PHP cannot execute\r
                                // empty queries, unlike libpq
-                               if (function_exists('pg_query'))
-                                       $res = pg_query($conn, $query_buf);
-                               else
-                                       $res = pg_exec($conn, $query_buf);      
-                               // Check for COPY request
-                               if (pg_result_status($res) == 4) { // 4 == PGSQL_COPY_FROM
-                                       while (!feof($fd)) {
-                                               $copy = fgets($fd, 32768);
-                                               pg_put_line($conn, $copy);
-                                               if ($copy == "\\.\n" || $copy == "\\.\r\n") {
-                                                       pg_end_copy($conn);
-                                                       break;
-                                               }
-                                       }
-                               }                               
-                               }
-        
-                                       $query_buf = null;
-                                       $query_start = $i + $thislen;
-                       }
-           } // end for
-
-               /* Put the rest of the line in the query buffer. */
-               $subline = substr($line, $query_start);
-               if ($in_quote || strspn($subline, " \t\n\r") != strlen($subline))
-               {
-                       if (strlen($query_buf) > 0)
-                           $query_buf .= "\n";
-                       $query_buf .= $subline;
-               }
-    
-               $line = null;
-               
-       } // end while
-
-       /*
-        * Process query at the end of file without a semicolon, so long as
-        * it's non-empty.
-        */
-       if (strlen($query_buf) > 0 && strspn($query_buf, " \t\n\r") != strlen($query_buf))
-       {
-                       // Execute the query (supporting 4.1.x PHP...)
-                       if (function_exists('pg_query'))
-                               $res = pg_query($conn, $query_buf);
-                       else
-                               $res = pg_exec($conn, $query_buf);
-                       // Check for COPY request
-                       if (pg_result_status($res) == 4) { // 4 == PGSQL_COPY_FROM
-                               while (!feof($fd)) {
-                                       $copy = fgets($fd, 32768);
-                                       pg_put_line($conn, $copy);
-                                       if ($copy == "\\.\n" || $copy == "\\.\r\n") {
-                                               pg_end_copy($conn);
-                                               break;
+                               if (function_exists('pg_query'))\r
+                                       $res = pg_query($conn, $query_buf);\r
+                               else\r
+                                       $res = pg_exec($conn, $query_buf);      \r
+                               // Check for COPY request\r
+                               if (pg_result_status($res) == 4) { // 4 == PGSQL_COPY_FROM\r
+                                       while (!feof($fd)) {\r
+                                               $copy = fgets($fd, 32768);\r
+                                               pg_put_line($conn, $copy);\r
+                                               if ($copy == "\\.\n" || $copy == "\\.\r\n") {\r
+                                                       pg_end_copy($conn);\r
+                                                       break;\r
+                                               }\r
+                                       }\r
+                               }                               \r
+                               }\r
+        \r
+                                       $query_buf = null;\r
+                                       $query_start = $i + $thislen;\r
+                       }\r
+                       \r
+                       /*\r
+                                * keyword or identifier? \r
+                                * We grab the whole string so that we don't\r
+                                * mistakenly see $foo$ inside an identifier as the start\r
+                                * of a dollar quote.\r
+                                */     \r
+                               // XXX: multibyte here\r
+                               else if (ereg('^[_[:alpha:]]$', substr($line, $i, 1))) {
+                                       $sub = substr($line, $i, $thislen);
+                                       while (ereg('^[\$_[:alnum:]]$', $sub)) {
+                                               /* keep going while we still have identifier chars */\r
+                                               $this->advance_1($i, $prevlen, $thislen);\r
+                                               $sub = substr($line, $i, $thislen);
                                        }
-                               }
-                       }                               
-       }
-               
-               fclose($fd);
-               
-               return new ADORecordSet_empty();
+                               }\r
+           } // end for\r
+\r
+               /* Put the rest of the line in the query buffer. */\r
+               $subline = substr($line, $query_start);                 \r
+               if ($in_quote || $dol_quote || strspn($subline, " \t\n\r") != strlen($subline))\r
+               {
+                       if (strlen($query_buf) > 0)\r
+                           $query_buf .= "\n";\r
+                       $query_buf .= $subline;\r
+               }\r
+    \r
+               $line = null;\r
+               \r
+       } // end while\r
+\r
+       /*\r
+        * Process query at the end of file without a semicolon, so long as\r
+        * it's non-empty.\r
+        */\r
+       if (strlen($query_buf) > 0 && strspn($query_buf, " \t\n\r") != strlen($query_buf))\r
+       {\r
+                       // Execute the query (supporting 4.1.x PHP...)\r
+                       if (function_exists('pg_query'))\r
+                               $res = pg_query($conn, $query_buf);\r
+                       else\r
+                               $res = pg_exec($conn, $query_buf);\r
+                       // Check for COPY request\r
+                       if (pg_result_status($res) == 4) { // 4 == PGSQL_COPY_FROM\r
+                               while (!feof($fd)) {\r
+                                       $copy = fgets($fd, 32768);\r
+                                       pg_put_line($conn, $copy);\r
+                                       if ($copy == "\\.\n" || $copy == "\\.\r\n") {\r
+                                               pg_end_copy($conn);\r
+                                               break;\r
+                                       }\r
+                               }\r
+                       }                               \r
+       }\r
+               \r
+               fclose($fd);\r
+               \r
+               return new ADORecordSet_empty();\r
        }
 
        // Capabilities