Finish off SQL script parser. Fix a keyword eating off-by-one bug and do full psql...
authorchriskl <chriskl>
Sat, 26 Mar 2005 10:47:03 +0000 (10:47 +0000)
committerchriskl <chriskl>
Sat, 26 Mar 2005 10:47:03 +0000 (10:47 +0000)
classes/database/Postgres.php
sql.php

index e750423aca7b5c8169e2e19984006b5b40bf980c..67cc8389d5b9f60b249e9c3f8bd46a495b6ac9af 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.257 2005/03/18 19:51:57 xzilla Exp $
+ * $Id: Postgres.php,v 1.258 2005/03/26 10:47:03 chriskl Exp $
  */
 
 // @@@ THOUGHT: What about inherits? ie. use of ONLY???
@@ -4151,10 +4151,12 @@ class Postgres extends ADODB_base {
         * 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
+        * @param $name Entry in $_FILES to use
+        * @param $callback (optional) Callback function to call with each query,
+                                      its result and line number.\r
+        * @return True for general success, false on any failure.\r
         */\r
-       function executeScript($name) {\r
+       function executeScript($name, $callback = null) {\r
                global $data;\r
 \r
                // This whole function isn't very encapsulated, but hey...\r
@@ -4175,11 +4177,13 @@ class Postgres extends ADODB_base {
                $len = 0;\r
                $i = 0;\r
                $prevlen = 0;\r
-               $thislen = 0;\r
+               $thislen = 0;
+               $lineno = 0;\r
                \r
                // Loop over each line in the file\r
                while (!feof($fd)) {\r
-                       $line = fgets($fd, 32768);\r
+                       $line = fgets($fd, 32768);
+                       $lineno++;\r
                        \r
                        // Nothing left on line? Then ignore...\r
                        if (trim($line) == '') continue;\r
@@ -4234,7 +4238,7 @@ class Postgres extends ADODB_base {
     \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
@@ -4242,13 +4246,13 @@ class Postgres extends ADODB_base {
     \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
+                       else if (substr($line, $i, 1) == '\'' || substr($line, $i, 1) == '"') {
                                $in_quote = substr($line, $i, 1);\r
                    }\r
 \r
@@ -4267,23 +4271,23 @@ class Postgres extends ADODB_base {
     \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
+                       else if (substr($line, $i, 1) == '(') {
                                $paren_level++;\r
                        }\r
     \r
-                       else if (substr($line, $i, 1) == ')' && $paren_level > 0) {\r
+                       else if (substr($line, $i, 1) == ')' && $paren_level > 0) {
                                $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
@@ -4300,14 +4304,17 @@ class Postgres extends ADODB_base {
 \r
                                // Execute the query (supporting 4.1.x PHP...). PHP cannot execute\r
                                // empty queries, unlike libpq
-                               if (function_exists('pg_query'))\r
-                                       $res = pg_query($conn, $query_buf);\r
+                               if (function_exists('pg_query'))
+                                       $res = @pg_query($conn, $query_buf);
                                else\r
-                                       $res = pg_exec($conn, $query_buf);      \r
+                                       $res = @pg_exec($conn, $query_buf);      \r
+                                               // Call the callback function for display
+                                               if ($callback !== null) $callback($query_buf, $res, $lineno);\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
+                                               $copy = fgets($fd, 32768);
+                                               $lineno++;\r
                                                pg_put_line($conn, $copy);\r
                                                if ($copy == "\\.\n" || $copy == "\\.\r\n") {\r
                                                        pg_end_copy($conn);\r
@@ -4330,11 +4337,14 @@ class Postgres extends ADODB_base {
                                // XXX: multibyte here\r
                                else if (ereg('^[_[:alpha:]]$', substr($line, $i, 1))) {
                                        $sub = substr($line, $i, $thislen);
-                                       while (ereg('^[\$_[:alnum:]]$', $sub)) {
+                                       while (ereg('^[\$_A-Za-z0-9]$', $sub)) {
                                                /* keep going while we still have identifier chars */\r
                                                $this->advance_1($i, $prevlen, $thislen);\r
                                                $sub = substr($line, $i, $thislen);
                                        }
+                                       // Since we're now over the next character to be examined, it is necessary
+                                       // to move back one space.
+                                       $i-=$prevlen;
                                }\r
            } // end for\r
 \r
@@ -4359,13 +4369,16 @@ class Postgres extends ADODB_base {
        {\r
                        // Execute the query (supporting 4.1.x PHP...)\r
                        if (function_exists('pg_query'))\r
-                               $res = pg_query($conn, $query_buf);\r
+                               $res = @pg_query($conn, $query_buf);\r
                        else\r
-                               $res = pg_exec($conn, $query_buf);\r
+                               $res = @pg_exec($conn, $query_buf);
+                       // Call the callback function for display
+                       if ($callback !== null) $callback($query_buf, $res, $lineno);\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
+                                       $copy = fgets($fd, 32768);
+                                       $lineno++;\r
                                        pg_put_line($conn, $copy);\r
                                        if ($copy == "\\.\n" || $copy == "\\.\r\n") {\r
                                                pg_end_copy($conn);\r
@@ -4377,7 +4390,7 @@ class Postgres extends ADODB_base {
                \r
                fclose($fd);\r
                \r
-               return new ADORecordSet_empty();\r
+               return true;\r
        }
 
        // Capabilities
diff --git a/sql.php b/sql.php
index cfd7040777da01db369f9f0a1fbf6c4a20ec1faa..b0501e042e826e7aa1547955017b040e1ffce76c 100644 (file)
--- a/sql.php
+++ b/sql.php
@@ -6,7 +6,7 @@
         * how many SQL statements have been strung together with semi-colons
         * @param $query The SQL query string to execute
         *
-        * $Id: sql.php,v 1.29 2005/03/04 08:53:56 chriskl Exp $
+        * $Id: sql.php,v 1.30 2005/03/26 10:47:03 chriskl Exp $
         */
 
        // Prevent timeouts on large exports (non-safe mode only)
        // Include application functions
        include_once('./libraries/lib.inc.php');
 
+       /**
+        * This is a callback function to display the result of each separate query
+        * @param ADORecordSet $rs The recordset returned by the script execetor
+        */
+       function sqlCallback($query, $rs, $lineno) {
+               global $data, $misc, $lang, $_connection;
+               // Check if $rs is false, if so then there was a fatal error
+               if ($rs === false) {
+                       echo htmlspecialchars($_FILES['script']['name']), ':', $lineno, ': ', nl2br(htmlspecialchars($_connection->getLastError())), "<br/>\n";
+               }
+               else {
+                       // Print query results
+                       switch (pg_result_status($rs)) {
+                               case PGSQL_TUPLES_OK:
+                                       // If rows returned, then display the results
+                                       $num_fields = pg_numfields($rs);
+                                       echo "<p><table>\n<tr>";
+                                       for ($k = 0; $k < $num_fields; $k++) {
+                                               echo "<th class=\"data\">", $misc->printVal(pg_fieldname($rs, $k)), "</th>";
+                                       }
+               
+                                       $i = 0;
+                                       $row = pg_fetch_row($rs);
+                                       while ($row !== false) {
+                                               $id = (($i % 2) == 0 ? '1' : '2');
+                                               echo "<tr>\n";
+                                               foreach ($row as $k => $v) {
+                                                       echo "<td class=\"data{$id}\" nowrap=\"nowrap\">", $misc->printVal($v, pg_fieldtype($rs, $k), array('null' => true)), "</td>";
+                                               }                                                       
+                                               echo "</tr>\n";
+                                               $row = pg_fetch_row($rs);
+                                               $i++;
+                                       };
+                                       echo "</table><br/>\n";
+                                       echo $i, " {$lang['strrows']}</p>\n";
+                                       break;
+                               case PGSQL_COMMAND_OK:
+                                       // If we have the command completion tag
+                                       if (version_compare(phpversion(), '4.3', '>=')) {
+                                               echo htmlspecialchars(pg_result_status($rs, PGSQL_STATUS_STRING)), "<br/>\n";
+                                       }
+                                       // Otherwise if any rows have been affected
+                                       elseif ($data->conn->Affected_Rows() > 0) {
+                                               echo $data->conn->Affected_Rows(), " {$lang['strrowsaff']}<br/>\n";
+                                       }
+                                       // Otherwise output nothing...
+                                       break;
+                               case PGSQL_EMPTY_QUERY:
+                                       break;
+                               default:
+                                       break;
+                       }
+               }
+       }
+       
        // Determine explain version of SQL
        if ($data->hasFullExplain() && isset($_POST['explain']) && isset($_POST['query'])) {
                $_POST['query'] = $data->getExplainSQL($_POST['query'], false);
                }
        }
 
-       // Set fetch mode to NUM so that duplicate field names are properly returned
-       $data->conn->setFetchMode(ADODB_FETCH_NUM);
-       
        // May as well try to time the query
        if (function_exists('microtime')) {
                list($usec, $sec) = explode(' ', microtime());
                $start_time = ((float)$usec + (float)$sec);
        }
        else $start_time = null;
-       
        // Execute the query.  If it's a script upload, special handling is necessary
        if (isset($_FILES['script']) && $_FILES['script']['size'] > 0)
-               $rs = $data->executeScript('script');
-       else
+               $data->executeScript('script', 'sqlCallback');
+       else {
+               // Set fetch mode to NUM so that duplicate field names are properly returned
+               $data->conn->setFetchMode(ADODB_FETCH_NUM);
                $rs = $data->conn->Execute($_POST['query']);
 
+               // $rs will only be an object if there is no error
+               if (is_object($rs)) {
+                       // Now, depending on what happened do various things
+       
+                       // First, if rows returned, then display the results
+                       if ($rs->recordCount() > 0) {
+                               echo "<table>\n<tr>";
+                               foreach ($rs->f as $k => $v) {
+                                       $finfo = $rs->fetchField($k);
+                                       echo "<th class=\"data\">", $misc->printVal($finfo->name), "</th>";
+                               }
+       
+                               $i = 0;         
+                               while (!$rs->EOF) {
+                                       $id = (($i % 2) == 0 ? '1' : '2');
+                                       echo "<tr>\n";
+                                       foreach ($rs->f as $k => $v) {
+                                               $finfo = $rs->fetchField($k);
+                                               echo "<td class=\"data{$id}\" nowrap=\"nowrap\">", $misc->printVal($v, $finfo->type, array('null' => true)), "</td>";
+                                       }                                                       
+                                       echo "</tr>\n";
+                                       $rs->moveNext();
+                                       $i++;
+                               }
+                               echo "</table>\n";
+                               echo "<p>", $rs->recordCount(), " {$lang['strrows']}</p>\n";
+                       }
+                       // Otherwise if any rows have been affected
+                       elseif ($data->conn->Affected_Rows() > 0) {
+                               echo "<p>", $data->conn->Affected_Rows(), " {$lang['strrowsaff']}</p>\n";
+                       }
+                       // Otherwise output nothing...
+               }
+       }
+
        // May as well try to time the query
        if ($start_time !== null) {
                list($usec, $sec) = explode(' ', microtime());
        // Reload the browser as we may have made schema changes
        $_reload_browser = true;
 
-       // $rs will only be an object if there is no error
-       if (is_object($rs)) {
-               // Now, depending on what happened do various things
-
-               // First, if rows returned, then display the results
-               if ($rs->recordCount() > 0) {
-                       echo "<table>\n<tr>";
-                       foreach ($rs->f as $k => $v) {
-                               $finfo = $rs->fetchField($k);
-                               echo "<th class=\"data\">", $misc->printVal($finfo->name), "</th>";
-                       }
-
-                       $i = 0;         
-                       while (!$rs->EOF) {
-                               $id = (($i % 2) == 0 ? '1' : '2');
-                               echo "<tr>\n";
-                               foreach ($rs->f as $k => $v) {
-                                       $finfo = $rs->fetchField($k);
-                                       echo "<td class=\"data{$id}\" nowrap=\"nowrap\">", $misc->printVal($v, $finfo->type, array('null' => true)), "</td>";
-                               }                                                       
-                               echo "</tr>\n";
-                               $rs->moveNext();
-                               $i++;
-                       }
-                       echo "</table>\n";
-                       echo "<p>", $rs->recordCount(), " {$lang['strrows']}</p>\n";
-               }
-               // Otherwise if any rows have been affected
-               elseif ($data->conn->Affected_Rows() > 0) {
-                       echo "<p>", $data->conn->Affected_Rows(), " {$lang['strrowsaff']}</p>\n";
-               }
-               // Else say success
-               else echo "<p>{$lang['strsqlexecuted']}</p>\n";
-               
-               // Display duration if we know it
-               if ($duration !== null) {
-                       echo "<p>", sprintf($lang['strruntime'], $duration), "</p>\n";
-               }
+       // Display duration if we know it
+       if ($duration !== null) {
+               echo "<p>", sprintf($lang['strruntime'], $duration), "</p>\n";
        }
-
+       
+       echo "<p>{$lang['strsqlexecuted']}</p>\n";
+       
        echo "<p><a class=\"navlink\" href=\"database.php?database=", urlencode($_REQUEST['database']),
                "&amp;action=sql&amp;query=", urlencode($_POST['query']), "\">{$lang['streditsql']}</a>";
        if ($conf['show_reports'] && isset($rs) && is_object($rs) && $rs->recordCount() > 0) {