* 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???
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