* A class that implements the DB interface for Postgres
* Note: This class uses ADODB and returns RecordSets.
*
- * $Id: Postgres.php,v 1.254 2005/02/20 04:47:46 mr-russ Exp $
+ * $Id: Postgres.php,v 1.255 2005/03/07 09:36:19 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.
+ * 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)) {
- $sql = fgets($fd, 32768);
- // Check that the query is something...
- if (trim($sql) == '') continue;
+ $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;
+
+ 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) == '/*')
+ {
+ $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
+ // 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, $sql);
+ $res = pg_query($conn, $query_buf);
else
- $res = pg_exec($conn, $sql);
+ $res = pg_exec($conn, $query_buf);
// Check for COPY request
if (pg_result_status($res) == 4) { // 4 == PGSQL_COPY_FROM
while (!feof($fd)) {
break;
}
}
- }
- }
+ }
+ }
fclose($fd);