Viewing file: PgsqlPDOQuerySplitter.php (8.78 KB) -rw-rw-rw- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
<?php
/** * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * This software consists of voluntary contributions made by many individuals * and is licensed under the LGPL. For more information please see * <http://phing.info>. * * @version SVN: $Id: PgsqlPDOQuerySplitter.php 1274 2011-08-17 13:38:07Z mrook $ * @package phing.tasks.ext.pdo */
require_once 'phing/tasks/ext/pdo/PDOQuerySplitter.php';
/** * Splits PostgreSQL's dialect of SQL into separate queries * * Unlike DefaultPDOQuerySplitter this uses a lexer instead of regular * expressions. This allows handling complex constructs like C-style comments * (including nested ones) and dollar-quoted strings. * * @author Alexey Borzov <avb@php.net> * @package phing.tasks.ext.pdo * @version $Revision: 1274 $ * @link http://www.phing.info/trac/ticket/499 * @link http://www.postgresql.org/docs/current/interactive/sql-syntax-lexical.html#SQL-SYNTAX-DOLLAR-QUOTING */ class PgsqlPDOQuerySplitter extends PDOQuerySplitter { /**#@+ * Lexer states */ const STATE_NORMAL = 0; const STATE_SINGLE_QUOTED = 1; const STATE_DOUBLE_QUOTED = 2; const STATE_DOLLAR_QUOTED = 3; const STATE_COMMENT_LINEEND = 4; const STATE_COMMENT_MULTILINE = 5; const STATE_BACKSLASH = 6; /**#@-*/
/** * Nesting depth of current multiline comment * @var int */ protected $commentDepth = 0;
/** * Current dollar-quoting "tag" * @var string */ protected $quotingTag = '';
/** * Current lexer state, one of STATE_* constants * @var int */ protected $state = self::STATE_NORMAL;
/** * Whether a backslash was just encountered in quoted string * @var bool */ protected $escape = false;
/** * Current source line being examined * @var string */ protected $line = '';
/** * Position in current source line * @var int */ protected $inputIndex;
/** * Gets next symbol from the input, false if at end * * @return string|bool */ public function getc() { if (!strlen($this->line) || $this->inputIndex >= strlen($this->line)) { if (null === ($line = $this->sqlReader->readLine())) { return false; } $project = $this->parent->getOwningTarget()->getProject(); $this->line = ProjectConfigurator::replaceProperties( $project, $line, $project->getProperties() ) . "\n"; $this->inputIndex = 0; } return $this->line[$this->inputIndex++]; }
/** * Bactracks one symbol on the input * * NB: we don't need ungetc() at the start of the line, so this case is * not handled. */ public function ungetc() { $this->inputIndex--; }
/** * Checks whether symbols after $ are a valid dollar-quoting tag * * @return string|bool Dollar-quoting "tag" if it is present, false otherwise */ protected function checkDollarQuote() { $ch = $this->getc(); if ('$' == $ch) { // empty tag return '';
} elseif (!ctype_alpha($ch) && '_' != $ch) { // not a delimiter $this->ungetc(); return false;
} else { $tag = $ch; while (false !== ($ch = $this->getc())) { if ('$' == $ch) { return $tag;
} elseif (ctype_alnum($ch) || '_' == $ch) { $tag .= $ch;
} else { for ($i = 0; $i < strlen($tag); $i++) { $this->ungetc(); } return false; } } } } public function nextQuery() { $sql = ''; $delimiter = $this->parent->getDelimiter();
while (false !== ($ch = $this->getc())) { switch ($this->state) { case self::STATE_NORMAL: switch ($ch) { case '-': if ('-' == $this->getc()) { $this->state = self::STATE_COMMENT_LINEEND; } else { $this->ungetc(); } break; case '"': $this->state = self::STATE_DOUBLE_QUOTED; break; case "'": $this->state = self::STATE_SINGLE_QUOTED; break; case '/': if ('*' == $this->getc()) { $this->state = self::STATE_COMMENT_MULTILINE; $this->commentDepth = 1; } else { $this->ungetc(); } break; case '$': if (false !== ($tag = $this->checkDollarQuote())) { $this->state = self::STATE_DOLLAR_QUOTED; $this->quotingTag = $tag; $sql .= '$' . $tag . '$'; continue 3; } break; // technically we can use e.g. psql's \g command as delimiter case $delimiter[0]: $hasQuery = true; for ($i = 1; $i < strlen($delimiter); $i++) { if ($delimiter[$i] != $this->getc()) { $hasQuery = false; } } if ($hasQuery) { return $sql; } else { for ($j = 1; $j < $i; $j++) { $this->ungetc(); } } } break;
case self::STATE_COMMENT_LINEEND: if ("\n" == $ch) { $this->state = self::STATE_NORMAL; } break;
case self::STATE_COMMENT_MULTILINE: switch ($ch) { case '/': if ('*' != $this->getc()) { $this->ungetc(); } else { $this->commentDepth++; } break;
case '*': if ('/' != $this->getc()) { $this->ungetc(); } else { $this->commentDepth--; if (0 == $this->commentDepth) { $this->state = self::STATE_NORMAL; continue 3; } } }
case self::STATE_SINGLE_QUOTED: case self::STATE_DOUBLE_QUOTED: if ($this->escape) { $this->escape = false; break; } $quote = $this->state == self::STATE_SINGLE_QUOTED ? "'" : '"'; switch ($ch) { case '\\': $this->escape = true; break; case $quote: if ($quote == $this->getc()) { $sql .= $quote; } else { $this->ungetc(); $this->state = self::STATE_NORMAL; } }
case self::STATE_DOLLAR_QUOTED: if ('$' == $ch && false !== ($tag = $this->checkDollarQuote())) { if ($tag == $this->quotingTag) { $this->state = self::STATE_NORMAL; } $sql .= '$' . $tag . '$'; continue 2; } }
if ($this->state != self::STATE_COMMENT_LINEEND && $this->state != self::STATE_COMMENT_MULTILINE) { $sql .= $ch; } } if ('' !== $sql) { return $sql; } return null; } }
|