<?php
/* vim: set expandtab tabstop=4 shiftwidth=4: */
// +----------------------------------------------------------------------+
// | Copyright (c) 2002-2004 Brent Cook |
// +----------------------------------------------------------------------+
// | This library is free software; you can redistribute it and/or |
// | modify it under the terms of the GNU Lesser General Public |
// | License as published by the Free Software Foundation; either |
// | version 2.1 of the License, or (at your option) any later version. |
// | |
// | This library is distributed in the hope that it will be useful, |
// | but WITHOUT ANY WARRANTY; without even the implied warranty of |
// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
// | Lesser General Public License for more details. |
// | |
// | You should have received a copy of the GNU Lesser General Public |
// | License along with this library; if not, write to the Free Software |
// | Foundation, Inc., 59 Temple Place, Suite 330,Boston,MA 02111-1307 USA|
// +----------------------------------------------------------------------+
// | Authors: Brent Cook <hide@address.com> |
// | Jason Pell <hide@address.com> |
// | Lauren Matheson <hide@address.com> |
// | John Griffin <hide@address.com> |
// +----------------------------------------------------------------------+
//
// $Id: Parser.php,v 1.23 2004/05/11 05:09:02 busterb Exp $
//
require_once PEAR_DIR . 'PEAR.php';
require_once PEAR_DIR . 'SQL/Lexer.php';
/**
* A sql parser
*
* @author Brent Cook <hide@address.com>
* @version 0.5
* @access public
* @package SQL_Parser
*/
class SQL_Parser
{
var $lexer;
var $token;
// symbol definitions
var $functions = array();
var $types = array();
var $symbols = array();
var $operators = array();
var $synonyms = array();
var $dialects = array("ANSI", "MySQL");
// {{{ function SQL_Parser($string = null)
function SQL_Parser($string = null, $dialect = "ANSI") {
$this->setDialect($dialect);
if (is_string($string)) {
$this->lexer = new Lexer($string, 1);
$this->lexer->symbols =& $this->symbols;
}
}
// }}}
// {{{ function setDialect($dialect)
function setDialect($dialect) {
if (in_array($dialect, $this->dialects)) {
include PEAR_DIR . 'SQL/Dialect_'.$dialect.'.php';
$this->types = array_flip($dialect['types']);
$this->functions = array_flip($dialect['functions']);
$this->operators = array_flip($dialect['operators']);
$this->commands = array_flip($dialect['commands']);
$this->synonyms = $dialect['synonyms'];
$this->symbols = array_merge(
$this->types,
$this->functions,
$this->operators,
$this->commands,
array_flip($dialect['reserved']),
array_flip($dialect['conjunctions']));
} else {
return $this->raiseError('Unknown SQL dialect:'.$dialect);
}
}
// }}}
// {{{ getParams(&$values, &$types)
function getParams(&$values, &$types) {
$values = array();
$types = array();
while ($this->token != ')') {
$this->getTok();
if ($this->isVal() || ($this->token == 'ident') || ($this->token == 'sys_var')) {
$values[] = $this->lexer->tokText;
$types[] = $this->token;
} elseif ($this->token == ')') {
return false;
} else {
return $this->raiseError('Expected a value');
}
$this->getTok();
if (($this->token != ',') && ($this->token != ')')) {
return $this->raiseError('Expected , or )');
}
}
}
// }}}
// {{{ raiseError($message)
function raiseError($message) {
$end = 0;
if ($this->lexer->string != '') {
while (($this->lexer->lineBegin+$end < $this->lexer->stringLen)
&& ($this->lexer->string{$this->lexer->lineBegin+$end} != "\n")){
++$end;
}
}
$message = 'Parse error: '.$message.' on line '.
($this->lexer->lineNo+1)."\n";
$message .= substr($this->lexer->string, $this->lexer->lineBegin, $end)."\n";
$length = is_null($this->token) ? 0 : strlen($this->lexer->tokText);
$message .= str_repeat(' ', abs($this->lexer->tokPtr -
$this->lexer->lineBegin - $length))."^";
$message .= ' found: "'.$this->lexer->tokText.'"';
return PEAR::raiseError($message);
}
// }}}
// {{{ isType()
function isType() {
return isset($this->types[$this->token]);
}
// }}}
// {{{ isVal()
function isVal() {
return (($this->token == 'real_val') ||
($this->token == 'int_val') ||
($this->token == 'text_val') ||
($this->token == 'null'));
}
// }}}
// {{{ isFunc()
function isFunc() {
return isset($this->functions[$this->token]);
}
// }}}
// {{{ isCommand()
function isCommand() {
return isset($this->commands[$this->token]);
}
// }}}
// {{{ isReserved()
function isReserved() {
return isset($this->symbols[$this->token]);
}
// }}}
// {{{ isOperator()
function isOperator() {
return isset($this->operators[$this->token]);
}
// }}}
// {{{ getTok()
function getTok() {
$this->token = $this->lexer->lex();
//echo $this->token."\t".$this->lexer->tokText."\n";
}
// }}}
// {{{ &parseFieldOptions()
function parseFieldOptions()
{
// parse field options
$namedConstraint = false;
$options = array();
while (($this->token != ',') && ($this->token != ')') &&
($this->token != null)) {
$option = $this->token;
$haveValue = true;
switch ($option) {
case 'constraint':
$this->getTok();
if ($this->token = 'ident') {
$constraintName = $this->lexer->tokText;
$namedConstraint = true;
$haveValue = false;
} else {
return $this->raiseError('Expected a constraint name');
}
break;
case 'default':
$this->getTok();
if ($this->isVal()) {
$constraintOpts = array('type'=>'default_value',
'value'=>$this->lexer->tokText);
} elseif ($this->isFunc()) {
$results = $this->parseFunctionOpts();
if (PEAR::isError($results)) {
return $results;
}
$results['type'] = 'default_function';
$constraintOpts = $results;
} else {
return $this->raiseError('Expected default value');
}
break;
case 'primary':
$this->getTok();
if ($this->token == 'key') {
$constraintOpts = array('type'=>'primary_key',
'value'=>true);
} else {
return $this->raiseError('Expected "key"');
}
break;
case 'not':
$this->getTok();
if ($this->token == 'null') {
$constraintOpts = array('type'=>'not_null',
'value' => true);
} else {
return $this->raiseError('Expected "null"');
}
break;
case 'check':
$this->getTok();
if ($this->token != '(') {
return $this->raiseError('Expected (');
}
$results = $this->parseSearchClause();
if (PEAR::isError($results)) {
return $results;
}
$results['type'] = 'check';
$constraintOpts = $results;
if ($this->token != ')') {
return $this->raiseError('Expected )');
}
break;
case 'unique':
$this->getTok();
if ($this->token != '(') {
return $this->raiseError('Expected (');
}
$constraintOpts = array('type'=>'unique');
$this->getTok();
while ($this->token != ')') {
if ($this->token != 'ident') {
return $this->raiseError('Expected an identifier');
}
$constraintOpts['column_names'][] = $this->lexer->tokText;
$this->getTok();
if (($this->token != ')') && ($this->token != ',')) {
return $this->raiseError('Expected ) or ,');
}
}
if ($this->token != ')') {
return $this->raiseError('Expected )');
}
break;
case 'month': case 'year': case 'day': case 'hour':
case 'minute': case 'second':
$intervals = array(
array('month'=>0,
'year'=>1),
array('second'=>0,
'minute'=>1,
'hour'=>2,
'day'=>3));
foreach ($intervals as $class) {
if (isset($class[$option])) {
$constraintOpts = array('quantum_1'=>$this->token);
$this->getTok();
if ($this->token == 'to') {
$this->getTok();
if (!isset($class[$this->token])) {
return $this->raiseError(
'Expected interval quanta');
}
if ($class[$this->token] >=
$class[$constraintOpts['quantum_1']]) {
return $this->raiseError($this->token.
' is not smaller than '.
$constraintOpts['quantum_1']);
}
$constraintOpts['quantum_2'] = $this->token;
} else {
$this->lexer->unget();
}
break;
}
}
if (!isset($constraintOpts['quantum_1'])) {
return $this->raiseError('Expected interval quanta');
}
$constraintOpts['type'] = 'values';
break;
case 'null':
$haveValue = false;
break;
default:
return $this->raiseError('Unexpected token '
.$this->lexer->tokText);
}
if ($haveValue) {
if ($namedConstraint) {
$options['constraints'][$constraintName] = $constraintOpts;
$namedConstraint = false;
} else {
$options['constraints'][] = $constraintOpts;
}
}
$this->getTok();
}
return $options;
}
// }}}
// {{{ parseSearchClause()
function parseSearchClause($subSearch = false)
{
$clause = array();
// parse the first argument
$this->getTok();
if ($this->token == 'not') {
$clause['neg'] = true;
$this->getTok();
}
$foundSubclause = false;
if ($this->token == '(') {
$clause['arg_1']['value'] = $this->parseSearchClause(true);
$clause['arg_1']['type'] = 'subclause';
if ($this->token != ')') {
return $this->raiseError('Expected ")"');
}
$foundSubclause = true;
} else if ($this->isReserved()) {
return $this->raiseError('Expected a column name or value');
} else {
$clause['arg_1']['value'] = $this->lexer->tokText;
$clause['arg_1']['type'] = $this->token;
}
// parse the operator
if (!$foundSubclause) {
$this->getTok();
if (!$this->isOperator()) {
return $this->raiseError('Expected an operator');
}
$clause['op'] = $this->token;
$this->getTok();
switch ($clause['op']) {
case 'is':
// parse for 'is' operator
if ($this->token == 'not') {
$clause['neg'] = true;
$this->getTok();
}
if ($this->token != 'null') {
return $this->raiseError('Expected "null"');
}
$clause['arg_2']['value'] = '';
$clause['arg_2']['type'] = $this->token;
break;
case 'not':
// parse for 'not in' operator
if ($this->token != 'in') {
return $this->raiseError('Expected "in"');
}
$clause['op'] = $this->token;
$clause['neg'] = true;
$this->getTok();
case 'in':
// parse for 'in' operator
if ($this->token != '(') {
return $this->raiseError('Expected "("');
}
// read the subset
$this->getTok();
// is this a subselect?
if ($this->token == 'select') {
$clause['arg_2']['value'] = $this->parseSelect(true);
$clause['arg_2']['type'] = 'command';
} else {
$this->lexer->pushBack();
// parse the set
$result = $this->getParams($clause['arg_2']['value'],
$clause['arg_2']['type']);
if (PEAR::isError($result)) {
return $result;
}
}
if ($this->token != ')') {
return $this->raiseError('Expected ")"');
}
break;
case 'and': case 'or':
$this->lexer->unget();
break;
default:
// parse for in-fix binary operators
if ($this->isReserved()) {
return $this->raiseError('Expected a column name or value');
}
if ($this->token == '(') {
$clause['arg_2']['value'] = $this->parseSearchClause(true);
$clause['arg_2']['type'] = 'subclause';
$this->getTok();
if ($this->token != ')') {
return $this->raiseError('Expected ")"');
}
} else {
$clause['arg_2']['value'] = $this->lexer->tokText;
$clause['arg_2']['type'] = $this->token;
}
}
}
$this->getTok();
if (($this->token == 'and') || ($this->token == 'or')) {
$op = $this->token;
$subClause = $this->parseSearchClause($subSearch);
if (PEAR::isError($subClause)) {
return $subClause;
} else {
$clause = array('arg_1' => $clause,
'op' => $op,
'arg_2' => $subClause);
}
} else {
$this->lexer->unget();
}
return $clause;
}
// }}}
// {{{ parseFieldList()
function parseFieldList()
{
$this->getTok();
if ($this->token != '(') {
return $this->raiseError('Expected (');
}
$fields = array();
while (1) {
// parse field identifier
$this->getTok();
if ($this->token == 'ident') {
$name = $this->lexer->tokText;
} elseif ($this->token == ')') {
return $fields;
} else {
return $this->raiseError('Expected identifier');
}
// parse field type
$this->getTok();
if ($this->isType($this->token)) {
$type = $this->token;
} else {
return $this->raiseError('Expected a valid type');
}
$this->getTok();
// handle special case two-word types
if ($this->token == 'precision') {
// double precision == double
if ($type == 'double') {
return $this->raiseError('Unexpected token');
}
$this->getTok();
} elseif ($this->token == 'varying') {
// character varying() == varchar()
if ($type == 'character') {
$type == 'varchar';
$this->getTok();
} else {
return $this->raiseError('Unexpected token');
}
}
$fields[$name]['type'] = $this->synonyms[$type];
// parse type parameters
if ($this->token == '(') {
$results = $this->getParams($values, $types);
if (PEAR::isError($results)) {
return $results;
}
switch ($fields[$name]['type']) {
case 'numeric':
if (isset($values[1])) {
if ($types[1] != 'int_val') {
return $this->raiseError('Expected an integer');
}
$fields[$name]['decimals'] = $values[1];
}
case 'float':
if ($types[0] != 'int_val') {
return $this->raiseError('Expected an integer');
}
$fields[$name]['length'] = $values[0];
break;
case 'char': case 'varchar':
case 'integer': case 'int':
if (sizeof($values) != 1) {
return $this->raiseError('Expected 1 parameter');
}
if ($types[0] != 'int_val') {
return $this->raiseError('Expected an integer');
}
$fields[$name]['length'] = $values[0];
break;
case 'set': case 'enum':
if (!sizeof($values)) {
return $this->raiseError('Expected a domain');
}
$fields[$name]['domain'] = $values;
break;
default:
if (sizeof($values)) {
return $this->raiseError('Unexpected )');
}
}
$this->getTok();
}
$options = $this->parseFieldOptions();
if (PEAR::isError($options)) {
return $options;
}
$fields[$name] += $options;
if ($this->token == ')') {
return $fields;
} elseif (is_null($this->token)) {
return $this->raiseError('Expected )');
}
}
}
// }}}
// {{{ parseFunctionOpts()
function parseFunctionOpts()
{
$function = $this->token;
$opts['name'] = $function;
$this->getTok();
if ($this->token != '(') {
return $this->raiseError('Expected "("');
}
switch ($function) {
case 'count':
$this->getTok();
switch ($this->token) {
case 'distinct':
$opts['distinct'] = true;
$this->getTok();
if ($this->token != 'ident') {
return $this->raiseError('Expected a column name');
}
case 'ident': case '*':
$opts['arg'][] = $this->lexer->tokText;
break;
default:
return $this->raiseError('Invalid argument');
}
break;
case 'concat':
$this->getTok();
while ($this->token != ')') {
switch ($this->token) {
case 'ident': case 'text_val':
$opts['arg'][] = $this->lexer->tokText;
break;
case ',':
// do nothing
break;
default:
return $this->raiseError('Expected a string or a column name');
}
$this->getTok();
}
$this->lexer->pushBack();
break;
case 'avg': case 'min': case 'max': case 'sum':
default:
$this->getTok();
$opts['arg'] = $this->lexer->tokText;
break;
}
$this->getTok();
if ($this->token != ')') {
return $this->raiseError('Expected ")"');
}
// check for an alias
$this->getTok();
if ($this->token == ',' || $this->token == 'from') {
$this->lexer->pushBack();
} elseif ($this->token == 'as') {
$this->getTok();
if ($this->token == 'ident' ) {
$opts['alias'] = $this->lexer->tokText;
} else {
return $this->raiseError('Expected column alias');
}
} else {
if ($this->token == 'ident' ) {
$opts['alias'] = $this->lexer->tokText;
} else {
return $this->raiseError('Expected column alias, from or comma');
}
}
return $opts;
}
// }}}
// {{{ parseCreate()
function parseCreate() {
$this->getTok();
switch ($this->token) {
case 'table':
$tree = array('command' => 'create_table');
$this->getTok();
if ($this->token == 'ident') {
$tree['table_names'][] = $this->lexer->tokText;
$fields = $this->parseFieldList();
if (PEAR::isError($fields)) {
return $fields;
}
$tree['column_defs'] = $fields;
// $tree['column_names'] = array_keys($fields);
} else {
return $this->raiseError('Expected table name');
}
break;
case 'index':
$tree = array('command' => 'create_index');
break;
case 'constraint':
$tree = array('command' => 'create_constraint');
break;
case 'sequence':
$tree = array('command' => 'create_sequence');
break;
default:
return $this->raiseError('Unknown object to create');
}
return $tree;
}
// }}}
// {{{ parseInsert()
function parseInsert() {
$this->getTok();
if ($this->token == 'into') {
$tree = array('command' => 'insert');
$this->getTok();
if ($this->token == 'ident') {
$tree['table_names'][] = $this->lexer->tokText;
$this->getTok();
} else {
return $this->raiseError('Expected table name');
}
if ($this->token == '(') {
$results = $this->getParams($values, $types);
if (PEAR::isError($results)) {
return $results;
} else {
if (sizeof($values)) {
$tree['column_names'] = $values;
}
}
$this->getTok();
}
if ($this->token == 'values') {
$this->getTok();
$results = $this->getParams($values, $types);
if (PEAR::isError($results)) {
return $results;
} else {
if (isset($tree['column_defs']) &&
(sizeof($tree['column_defs']) != sizeof($values))) {
return $this->raiseError('field/value mismatch');
}
if (sizeof($values)) {
foreach ($values as $key=>$value) {
$values[$key] = array('value'=>$value,
'type'=>$types[$key]);
}
$tree['values'] = $values;
} else {
return $this->raiseError('No fields to insert');
}
}
} else {
return $this->raiseError('Expected "values"');
}
} else {
return $this->raiseError('Expected "into"');
}
return $tree;
}
// }}}
// {{{ parseUpdate()
function parseUpdate() {
$this->getTok();
if ($this->token == 'ident') {
$tree = array('command' => 'update');
$tree['table_names'][] = $this->lexer->tokText;
} else {
return $this->raiseError('Expected table name');
}
$this->getTok();
if ($this->token != 'set') {
return $this->raiseError('Expected "set"');
}
while (true) {
$this->getTok();
if ($this->token != 'ident') {
return $this->raiseError('Expected a column name');
}
$tree['column_names'][] = $this->lexer->tokText;
$this->getTok();
if ($this->token != '=') {
return $this->raiseError('Expected =');
}
$this->getTok();
if (!$this->isVal($this->token)) {
return $this->raiseError('Expected a value');
}
$tree['values'][] = array('value'=>$this->lexer->tokText,
'type'=>$this->token);
$this->getTok();
if ($this->token == 'where') {
$clause = $this->parseSearchClause();
if (PEAR::isError($clause)) {
return $clause;
}
$tree['where_clause'] = $clause;
break;
} elseif ($this->token != ',') {
return $this->raiseError('Expected "where" or ","');
}
}
return $tree;
}
// }}}
// {{{ parseDelete()
function parseDelete() {
$this->getTok();
if ($this->token != 'from') {
return $this->raiseError('Expected "from"');
}
$tree = array('command' => 'delete');
$this->getTok();
if ($this->token != 'ident') {
return $this->raiseError('Expected a table name');
}
$tree['table_names'][] = $this->lexer->tokText;
$this->getTok();
if ($this->token != 'where') {
return $this->raiseError('Expected "where"');
}
$clause = $this->parseSearchClause();
if (PEAR::isError($clause)) {
return $clause;
}
$tree['where_clause'] = $clause;
return $tree;
}
// }}}
// {{{ parseDrop()
function parseDrop() {
$this->getTok();
switch ($this->token) {
case 'table':
$tree = array('command' => 'drop_table');
$this->getTok();
if ($this->token != 'ident') {
return $this->raiseError('Expected a table name');
}
$tree['table_names'][] = $this->lexer->tokText;
$this->getTok();
if (($this->token == 'restrict') ||
($this->token == 'cascade')) {
$tree['drop_behavior'] = $this->token;
}
$this->getTok();
if (!is_null($this->token)) {
return $this->raiseError('Unexpected token');
}
return $tree;
break;
case 'index':
$tree = array('command' => 'drop_index');
break;
case 'constraint':
$tree = array('command' => 'drop_constraint');
break;
case 'sequence':
$tree = array('command' => 'drop_sequence');
break;
default:
return $this->raiseError('Unknown object to drop');
}
return $tree;
}
// }}}
// {{{ parseSelect()
function parseSelect($subSelect = false) {
$tree = array('command' => 'select');
$this->getTok();
if (($this->token == 'distinct') || ($this->token == 'all')) {
$tree['set_quantifier'] = $this->token;
$this->getTok();
}
if ($this->token == '*') {
$tree['column_names'][] = '*';
$this->getTok();
} elseif ($this->token == 'ident' || $this->isFunc()) {
while ($this->token != 'from') {
if ($this->token == 'ident') {
$prevTok = $this->token;
$prevTokText = $this->lexer->tokText;
$this->getTok();
if ($this->token == '.') {
$columnTable = $prevTokText;
$this->getTok();
$prevTok = $this->token;
$prevTokText = $this->lexer->tokText;
} else {
$columnTable = '';
}
if ($prevTok == 'ident') {
$columnName = $prevTokText;
} else {
return $this->raiseError('Expected column name');
}
if ($this->token == 'as') {
$this->getTok();
if ($this->token == 'ident' ) {
$columnAlias = $this->lexer->tokText;
} else {
return $this->raiseError('Expected column alias');
}
} elseif ($this->token == 'ident') {
$columnAlias = $this->lexer->tokText;
} else {
$columnAlias = '';
}
$tree['column_tables'][] = $columnTable;
$tree['column_names'][] = $columnName;
$tree['column_aliases'][] = $columnAlias;
if ($this->token != 'from') {
$this->getTok();
}
if ($this->token == ',') {
$this->getTok();
}
} elseif ($this->isFunc()) {
if (!isset($tree['set_quantifier'])) {
$result = $this->parseFunctionOpts();
if (PEAR::isError($result)) {
return $result;
}
$tree['set_function'][] = $result;
$this->getTok();
if ($this->token == 'as') {
$this->getTok();
if ($this->token == 'ident' ) {
$columnAlias = $this->lexer->tokText;
} else {
return $this->raiseError('Expected column alias');
}
} else {
$columnAlias = '';
}
} else {
return $this->raiseError('Cannot use "'.
$tree['set_quantifier'].'" with '.$this->token);
}
} elseif ($this->token == ',') {
$this->getTok();
} else {
return $this->raiseError('Unexpected token "'.$this->token.'"');
}
}
} else {
return $this->raiseError('Expected columns or a set function');
}
if ($this->token != 'from') {
return $this->raiseError('Expected "from"');
}
$this->getTok();
while ($this->token == 'ident') {
$tree['table_names'][] = $this->lexer->tokText;
$this->getTok();
if ($this->token == 'ident') {
$tree['table_aliases'][] = $this->lexer->tokText;
$this->getTok();
} elseif ($this->token == 'as') {
$this->getTok();
if ($this->token == 'ident') {
$tree['table_aliases'][] = $this->lexer->tokText;
} else {
return $this->raiseError('Expected table alias');
}
$this->getTok();
} else {
$tree['table_aliases'][] = '';
}
if ($this->token == 'on') {
$clause = $this->parseSearchClause();
if (PEAR::isError($clause)) {
return $clause;
}
$tree['table_join_clause'][] = $clause;
} else {
$tree['table_join_clause'][] = '';
}
if ($this->token == ',') {
$tree['table_join'][] = ',';
$this->getTok();
} elseif ($this->token == 'join') {
$tree['table_join'][] = 'join';
$this->getTok();
} elseif (($this->token == 'cross') ||
($this->token == 'inner')) {
$join = $this->lexer->tokText;
$this->getTok();
if ($this->token != 'join') {
return $this->raiseError('Expected token "join"');
}
$tree['table_join'][] = $join.' join';
$this->getTok();
} elseif (($this->token == 'left') ||
($this->token == 'right')) {
$join = $this->lexer->tokText;
$this->getTok();
if ($this->token == 'join') {
$tree['table_join'][] = $join.' join';
} elseif ($this->token == 'outer') {
$join .= ' outer';
$this->getTok();
if ($this->token == 'join') {
$tree['table_join'][] = $join.' join';
} else {
return $this->raiseError('Expected token "join"');
}
} else {
return $this->raiseError('Expected token "outer" or "join"');
}
$this->getTok();
} elseif ($this->token == 'natural') {
$join = $this->lexer->tokText;
$this->getTok();
if ($this->token == 'join') {
$tree['table_join'][] = $join.' join';
} elseif (($this->token == 'left') ||
($this->token == 'right')) {
$join .= ' '.$this->token;
$this->getTok();
if ($this->token == 'join') {
$tree['table_join'][] = $join.' join';
} elseif ($this->token == 'outer') {
$join .= ' '.$this->token;
$this->getTok();
if ($this->token == 'join') {
$tree['table_join'][] = $join.' join';
} else {
return $this->raiseError('Expected token "join" or "outer"');
}
} else {
return $this->raiseError('Expected token "join" or "outer"');
}
} else {
return $this->raiseError('Expected token "left", "right" or "join"');
}
$this->getTok();
} elseif (($this->token == 'where') ||
($this->token == 'order') ||
($this->token == 'limit') ||
(is_null($this->token))) {
break;
}
}
while (!is_null($this->token) && (!$subSelect || $this->token != ')')
&& $this->token != ')') {
switch ($this->token) {
case 'where':
$clause = $this->parseSearchClause();
if (PEAR::isError($clause)) {
return $clause;
}
$tree['where_clause'] = $clause;
break;
case 'order':
$this->getTok();
if ($this->token != 'by') {
return $this->raiseError('Expected "by"');
}
$this->getTok();
while ($this->token == 'ident') {
$col = $this->lexer->tokText;
$this->getTok();
if (isset($this->synonyms[$this->token])) {
$order = $this->synonyms[$this->token];
if (($order != 'asc') && ($order != 'desc')) {
return $this->raiseError('Unexpected token');
}
$this->getTok();
} else {
$order = 'asc';
}
if ($this->token == ',') {
$this->getTok();
}
$tree['sort_order'][$col] = $order;
}
break;
case 'limit':
$this->getTok();
if ($this->token != 'int_val') {
return $this->raiseError('Expected an integer value');
}
$length = $this->lexer->tokText;
$start = 0;
$this->getTok();
if ($this->token == ',') {
$this->getTok();
if ($this->token != 'int_val') {
return $this->raiseError('Expected an integer value');
}
$start = $length;
$length = $this->lexer->tokText;
$this->getTok();
}
$tree['limit_clause'] = array('start'=>$start,
'length'=>$length);
break;
case 'group':
$this->getTok();
if ($this->token != 'by') {
return $this->raiseError('Expected "by"');
}
$this->getTok();
while ($this->token == 'ident') {
$col = $this->lexer->tokText;
$this->getTok();
if ($this->token == ',') {
$this->getTok();
}
$tree['group_by'][] = $col;
}
break;
default:
return $this->raiseError('Unexpected clause');
}
}
return $tree;
}
// }}}
// {{{ parse($string)
function parse($string = null)
{
if (is_string($string)) {
// Initialize the Lexer with a 3-level look-back buffer
$this->lexer = new Lexer($string, 3);
$this->lexer->symbols =& $this->symbols;
} else {
if (!is_object($this->lexer)) {
return $this->raiseError('No initial string specified');
}
}
// get query action
$this->getTok();
switch ($this->token) {
case null:
// null == end of string
return $this->raiseError('Nothing to do');
case 'select':
return $this->parseSelect();
case 'update':
return $this->parseUpdate();
case 'insert':
return $this->parseInsert();
case 'delete':
return $this->parseDelete();
case 'create':
return $this->parseCreate();
case 'drop':
return $this->parseDrop();
default:
return $this->raiseError('Unknown action :'.$this->token);
}
}
// }}}
}
?>