diff --git a/include/class.orm.php b/include/class.orm.php index 762e3d2f446774fbd999e44850187b7d80e3e1b2..a9ae5fcf2c96aa6939dbdfdd047210ab29dccffa 100644 --- a/include/class.orm.php +++ b/include/class.orm.php @@ -2366,9 +2366,6 @@ class SqlCompiler { * $Q - (Q) constraint represented in a Q instance * $model - (VerySimpleModel) root model for all the field references in * the Q instance - * $slot - (int) slot for inputs to be placed. Useful to differenciate - * inputs placed in the joins and where clauses for SQL engines - * which do not support named parameters. * * Returns: * (CompiledExpression) object containing the compiled expression (with @@ -2376,13 +2373,14 @@ class SqlCompiler { * of the CompiledExpression will allow the compiler to place the * constraint properly in the WHERE or HAVING clause appropriately. */ - function compileQ(Q $Q, $model, $slot=false) { + function compileQ(Q $Q, $model, $parens=true) { $filter = array(); $type = CompiledExpression::TYPE_WHERE; foreach ($Q->constraints as $field=>$value) { // Handle nested constraints if ($value instanceof Q) { - $filter[] = $T = $this->compileQ($value, $model, $slot); + $filter[] = $T = $this->compileQ($value, $model, + !$Q->isCompatibleWith($value)); // Bubble up HAVING constraints if ($T instanceof CompiledExpression && $T->type == CompiledExpression::TYPE_HAVING) @@ -2408,7 +2406,8 @@ class SqlCompiler { $criteria["{$field}__{$f}"] = $v; } } - $filter[] = $this->compileQ(new Q($criteria), $model, $slot); + $filter[] = $this->compileQ(new Q($criteria), $model, + $Q->ored || $Q->negated); } // Handle simple field = <value> constraints else { @@ -2430,19 +2429,24 @@ class SqlCompiler { $filter[] = sprintf($op, $field, $this->input($value)); } } - $glue = $Q->isOred() ? ' OR ' : ' AND '; + $glue = $Q->ored ? ' OR ' : ' AND '; $clause = implode($glue, $filter); - if (count($filter) > 1) + if (($Q->negated || $parens) && count($filter) > 1) $clause = '(' . $clause . ')'; - if ($Q->isNegated()) + if ($Q->negated) $clause = 'NOT '.$clause; return new CompiledExpression($clause, $type); } function compileConstraints($where, $model) { $constraints = array(); + $prev = $parens = false; foreach ($where as $Q) { - $constraints[] = $this->compileQ($Q, $model); + $parens = $parens || !($prev && $prev->isCompatibleWith($Q)); + $prev = $Q; + } + foreach ($where as $Q) { + $constraints[] = $this->compileQ($Q, $model, $parens); } return $constraints; } @@ -2786,7 +2790,7 @@ class MySqlCompiler extends SqlCompiler { $field = $sort->toSql($this, $model); } else { - if ($sort[0] == '-') { + if ($sort[0] === '-') { $dir = 'DESC'; $sort = substr($sort, 1); } @@ -3354,6 +3358,15 @@ class Q implements Serializable { $this->ored = true; } + /** + * Two neighboring Q's are compatible in a where clause if they have + * the same boolean AND / OR operator. Negated Q's should always use + * parentheses if there is more than one criterion. + */ + function isCompatibleWith(Q $other) { + return $this->ored == $other->ored; + } + function add($constraints) { if (is_array($constraints)) $this->constraints = array_merge($this->constraints, $constraints);