Finish work on condition parsing

This commit is contained in:
Kijin Sung 2020-06-26 20:56:32 +09:00
parent 6eca8736c1
commit db13d110b8
2 changed files with 154 additions and 51 deletions

View file

@ -5,7 +5,7 @@ namespace Rhymix\Framework\Parsers\DBQuery;
/** /**
* Query class. * Query class.
*/ */
class Query class Query extends VariableBase
{ {
public $name; public $name;
public $alias; public $alias;
@ -89,9 +89,9 @@ class Query
if ($column instanceof self) if ($column instanceof self)
{ {
$subquery = $column->getQueryString($this->_prefix, $this->_args); $subquery = $column->getQueryString($this->_prefix, $this->_args);
foreach ($column->getQueryParams() as $key => $val) foreach ($column->getQueryParams() as $param)
{ {
$this->_params[$key] = $val; $this->_params[] = $param;
} }
$columns[] = sprintf('(%s) AS %s', $subquery, self::quoteName($column->alias)); $columns[] = sprintf('(%s) AS %s', $subquery, self::quoteName($column->alias));
} }
@ -114,28 +114,31 @@ class Query
if ($table instanceof self) if ($table instanceof self)
{ {
$subquery = $table->getQueryString($this->_prefix, $this->_args); $subquery = $table->getQueryString($this->_prefix, $this->_args);
foreach ($table->getQueryParams() as $key => $val) foreach ($table->getQueryParams() as $param)
{ {
$this->_params[$key] = $val; $this->_params[] = $param;
} }
$tables[] = sprintf('(%s) AS `%s`', $subquery, $table->alias); $tables[] = (count($tables) ? ', ' : '') . sprintf('(%s) AS `%s`', $subquery, $table->alias);
} }
else else
{ {
$tabledef = self::quoteName($table->name) . ($table->alias ? (' AS `' . $table->alias . '`') : ''); $tabledef = self::quoteName($table->name) . ($table->alias ? (' AS `' . $table->alias . '`') : '');
if ($table->join_type) if ($table->join_type)
{ {
$tabledef = $table->join_type . ' ' . $tabledef;
$join_where = $this->_arrangeConditions($table->join_conditions); $join_where = $this->_arrangeConditions($table->join_conditions);
if ($join_where !== '') if ($join_where !== '')
{ {
$tabledef = $tabledef . ' ON ' . $join_where; $tabledef = $tabledef . ' ON ' . $join_where;
} }
$tables[] = ' ' . $table->join_type . ' ' . $tabledef;
} }
$tables[] = $tabledef; else
{
$tables[] = (count($tables) ? ', ' : '') . $tabledef;
} }
} }
$result .= ' FROM ' . implode(', ', $tables); }
$result .= ' FROM ' . implode('', $tables);
// Compose the conditions. // Compose the conditions.
if (count($this->conditions)) if (count($this->conditions))
@ -172,31 +175,33 @@ class Query
// Subquery // Subquery
if ($condition instanceof self) if ($condition instanceof self)
{ {
// TODO $condition_string = $this->_parseCondition($condition);
if ($condition_string !== '')
{
$result .= ($result === '' ? '' : (' ' . $condition->pipe . ' ')) . $condition_string;
}
} }
// Condition group // Condition group
elseif ($condition instanceof ConditionGroup) elseif ($condition instanceof ConditionGroup)
{ {
$condition_string = $this->_arrangeConditions($condition->conditions); $condition_string = $this->_arrangeConditions($condition->conditions);
if ($condition_string === '') if ($condition_string !== '')
{ {
continue;
}
$result .= ($result === '' ? '' : (' ' . $condition->pipe . ' ')) . '(' . $condition_string . ')'; $result .= ($result === '' ? '' : (' ' . $condition->pipe . ' ')) . '(' . $condition_string . ')';
} }
}
// Simple condition // Simple condition
else else
{ {
$condition_string = $this->_parseCondition($condition); $condition_string = $this->_parseCondition($condition);
if ($condition_string === '') if ($condition_string !== '')
{ {
continue;
}
$result .= ($result === '' ? '' : (' ' . $condition->pipe . ' ')) . $condition_string; $result .= ($result === '' ? '' : (' ' . $condition->pipe . ' ')) . $condition_string;
} }
} }
}
// Return the WHERE clause. // Return the WHERE clause.
return $result; return $result;
@ -208,12 +213,12 @@ class Query
* @param object $condition * @param object $condition
* @return string * @return string
*/ */
protected function _parseCondition(Condition $condition): string protected function _parseCondition(VariableBase $condition): string
{ {
list($where, $params) = $condition->getQueryStringAndParams($this->_args); list($where, $params) = $condition->getQueryStringAndParams($this->_args, $this->_prefix);
foreach ($params as $key => $val) foreach ($params as $param)
{ {
$this->_params[$key] = $val; $this->_params[] = $param;
} }
return $where; return $where;
} }

View file

@ -17,9 +17,10 @@ class VariableBase
* Convert an operator into real SQL. * Convert an operator into real SQL.
* *
* @param array $args * @param array $args
* @param string $prefix
* @return array * @return array
*/ */
public function getQueryStringAndParams(array $args): array public function getQueryStringAndParams(array $args, string $prefix = ''): array
{ {
// Return if this method is called on an invalid child class. // Return if this method is called on an invalid child class.
if (!isset($this->column) || !isset($this->operation)) if (!isset($this->column) || !isset($this->operation))
@ -27,15 +28,26 @@ class VariableBase
throw new \Rhymix\Framework\Exceptions\QueryError('Invalid invocation of getQueryStringAndParams()'); throw new \Rhymix\Framework\Exceptions\QueryError('Invalid invocation of getQueryStringAndParams()');
} }
// Initialze the return values.
$where = '';
$params = array();
// Process the variable or default value. // Process the variable or default value.
if ($this->var && isset($args[$this->var]) && !empty($args[$this->var])) if ($this->var && isset($args[$this->var]) && (!is_array($args[$this->var]) || count($args[$this->var]) > 1 || $args[$this->var] !== ['']))
{ {
$this->filterValue($args[$this->var]); $this->filterValue($args[$this->var]);
$is_expression = false;
$value = $args[$this->var]; $value = $args[$this->var];
} }
elseif ($this->default !== null) elseif ($this->default !== null)
{ {
$value = $this->getDefaultValue(); list($is_expression, $value) = $this->getDefaultValue();
}
elseif ($this instanceof Query)
{
$is_expression = true;
$value = '(' . $this->getQueryString($prefix, $args) . ') AS ' . Query::quoteName($this->alias);
$params = $this->getQueryParams();
} }
elseif ($this->not_null) elseif ($this->not_null)
{ {
@ -43,13 +55,11 @@ class VariableBase
} }
else else
{ {
return ['', []]; return [$where, $params];
} }
// Quote the column name. // Quote the column name.
$column = Query::quoteName($this->column); $column = Query::quoteName($this->column);
$where = '';
$params = array();
// Prepare the target value. // Prepare the target value.
$list_ops = array('in' => true, 'notin' => true, 'not_in' => true, 'between' => true); $list_ops = array('in' => true, 'notin' => true, 'not_in' => true, 'between' => true);
@ -62,36 +72,93 @@ class VariableBase
switch ($this->operation) switch ($this->operation)
{ {
case 'equal': case 'equal':
$where = sprintf('%s = ?', $column); $where = sprintf('%s = %s', $column, $is_expression ? $value : '?');
$params[] = $value; if (!$is_expression) $params[] = $value;
break;
case 'notequal':
case 'not_equal':
$where = sprintf('%s != %s', $column, $is_expression ? $value : '?');
if (!$is_expression) $params[] = $value;
break;
case 'more':
case 'gte':
$where = sprintf('%s >= %s', $column, $is_expression ? $value : '?');
if (!$is_expression) $params[] = $value;
break;
case 'excess':
case 'gt';
$where = sprintf('%s > %s', $column, $is_expression ? $value : '?');
if (!$is_expression) $params[] = $value;
break;
case 'less':
case 'lte':
$where = sprintf('%s <= %s', $column, $is_expression ? $value : '?');
if (!$is_expression) $params[] = $value;
break;
case 'below':
case 'lt';
$where = sprintf('%s < %s', $column, $is_expression ? $value : '?');
if (!$is_expression) $params[] = $value;
break;
case 'regexp';
$where = sprintf('%s REGEXP %s', $column, $is_expression ? $value : '?');
if (!$is_expression) $params[] = $value;
break;
case 'notregexp';
case 'not_regexp';
$where = sprintf('%s NOT REGEXP %s', $column, $is_expression ? $value : '?');
if (!$is_expression) $params[] = $value;
break; break;
case 'like': case 'like':
$where = sprintf('%s LIKE ?', $column); $where = sprintf('%s LIKE %s', $column, $is_expression ? $value : '?');
$params[] = '%' . $value . '%'; if (!$is_expression) $params[] = '%' . $value . '%';
break; break;
case 'like_prefix': case 'like_prefix':
case 'like_head': case 'like_head':
$where = sprintf('%s LIKE ?', $column); $where = sprintf('%s LIKE %s', $column, $is_expression ? $value : '?');
$params[] = $value . '%'; if (!$is_expression) $params[] = $value . '%';
break; break;
case 'like_suffix': case 'like_suffix':
case 'like_tail': case 'like_tail':
$where = sprintf('%s LIKE ?', $column); $where = sprintf('%s LIKE %s', $column, $is_expression ? $value : '?');
$params[] = '%' . $value; if (!$is_expression) $params[] = '%' . $value;
break; break;
case 'notlike': case 'notlike':
$where = sprintf('%s NOT LIKE ?', $column); $where = sprintf('%s NOT LIKE %s', $column, $is_expression ? $value : '?');
$params[] = '%' . $value . '%'; if (!$is_expression) $params[] = '%' . $value . '%';
break; break;
case 'notlike_prefix': case 'notlike_prefix':
case 'notlike_head': case 'notlike_head':
$where = sprintf('%s NOT LIKE ?', $column); $where = sprintf('%s NOT LIKE %s', $column, $is_expression ? $value : '?');
$params[] = $value . '%'; if (!$is_expression) $params[] = $value . '%';
break; break;
case 'notlike_suffix': case 'notlike_suffix':
case 'notlike_tail': case 'notlike_tail':
$where = sprintf('%s NOT LIKE ?', $column); $where = sprintf('%s NOT LIKE %s', $column, $is_expression ? $value : '?');
$params[] = '%' . $value; if (!$is_expression) $params[] = '%' . $value;
break;
case 'and':
$where = sprintf('%s & %s', $column, $is_expression ? $value : '?');
if (!$is_expression) $params[] = '%' . $value;
break;
case 'or':
$where = sprintf('%s | %s', $column, $is_expression ? $value : '?');
if (!$is_expression) $params[] = '%' . $value;
break;
case 'xor':
$where = sprintf('%s ^ %s', $column, $is_expression ? $value : '?');
if (!$is_expression) $params[] = '%' . $value;
break;
case 'not':
$where = sprintf('%s ~ %s', $column, $is_expression ? $value : '?');
if (!$is_expression) $params[] = '%' . $value;
break;
case 'null':
$where = sprintf('%s IS NULL', $column);
break;
case 'notnull':
case 'not_null':
$where = sprintf('%s IS NOT NULL', $column);
break; break;
case 'in': case 'in':
$count = count($value); $count = count($value);
@ -119,6 +186,37 @@ class VariableBase
$params[] = $item; $params[] = $item;
} }
break; break;
case 'notbetween':
case 'not_between':
$where = sprintf('%s NOT BETWEEN ? AND ?', $column);
foreach ($value as $item)
{
$params[] = $item;
}
break;
case 'search':
$keywords = preg_split('/[\s,]+/', $value, 10, \PREG_SPLIT_NO_EMPTY);
$conditions = array();
$placeholders = implode(', ', array_fill(0, count($keywords), '?'));
foreach ($keywords as $item)
{
if (substr($item, 0, 1) === '-')
{
$conditions[] = sprintf('%s NOT LIKE ?', $column);
$item = substr($item, 1);
}
else
{
$conditions[] = sprintf('%s LIKE ?', $column);
}
$params[] = '%' . str_replace(['\\', '_', '%'], ['\\\\', '\_', '\%'], $item) . '%';
}
$conditions = implode(' AND ', $conditions);
$where = count($keywords) === 1 ? $conditions : "($conditions)";
break;
default:
$where = sprintf('%s = ?', $column);
$params[] = $value;
} }
// Return the complete condition and parameters. // Return the complete condition and parameters.
@ -128,32 +226,32 @@ class VariableBase
/** /**
* Get the default value of this variable. * Get the default value of this variable.
* *
* @return mixed * @return array
*/ */
public function getDefaultValue() public function getDefaultValue()
{ {
// If the default value is a column name, escape it. // If the default value is a column name, escape it.
if (preg_match('/^[a-z0-9_]+(?:\.[a-z0-9_]+)+$/', $this->default)) if (preg_match('/^[a-z0-9_]+(?:\.[a-z0-9_]+)+$/', $this->default))
{ {
return Query::quoteName($this->default); return [true, Query::quoteName($this->default)];
} }
elseif (isset($this->column) && preg_match('/_srl$/', $this->column) && !ctype_digit($this->default)) elseif (isset($this->column) && preg_match('/_srl$/', $this->column) && !ctype_digit($this->default))
{ {
return Query::quoteName($this->default); return [true, Query::quoteName($this->default)];
} }
// If the default value is a function shortcut, return an appropriate value. // If the default value is a function shortcut, return an appropriate value.
switch ($this->default) switch ($this->default)
{ {
case 'ipaddress()': case 'ipaddress()':
return "'" . \RX_CLIENT_IP . "'"; return [false, \RX_CLIENT_IP];
case 'unixtime()': case 'unixtime()':
return time(); return [false, time()];
case 'curdate()': case 'curdate()':
case 'date()': case 'date()':
return "'" . date('YmdHis') . "'"; return [false, date('YmdHis')];
case 'sequence()': case 'sequence()':
return getNextSequence(); return [false, getNextSequence()];
} }
// If the default value is a calculation based on the current value, return a query string. // If the default value is a calculation based on the current value, return a query string.
@ -162,16 +260,16 @@ class VariableBase
switch ($matches[1]) switch ($matches[1])
{ {
case 'plus': case 'plus':
return sprintf('%s + %d', Query::quoteName($this->column), $matches[2]); return [true, sprintf('%s + %d', Query::quoteName($this->column), $matches[2])];
case 'minus': case 'minus':
return sprintf('%s - %d', Query::quoteName($this->column), $matches[2]); return [true, sprintf('%s - %d', Query::quoteName($this->column), $matches[2])];
case 'multiply': case 'multiply':
return sprintf('%s * %d', Query::quoteName($this->column), $matches[2]); return [true, sprintf('%s * %d', Query::quoteName($this->column), $matches[2])];
} }
} }
// Otherwise, just return the literal value. // Otherwise, just return the literal value.
return $this->default; return [false, $this->default];
} }
/** /**