doctrine/mongodb

Expr's implementation of equals overwrites other conditions or is itself overwritten by them.

josefsabl opened this issue · 1 comments

Moved from here: doctrine/mongodb-odm#1361

Code like this:

$queryBuilder
    ->field('myField')->equals('456')
    ->field('myField')->in(['123', '234']);

Should obviously generate query like this:

query => array (2)
    myField => array (1)
        "$eq" => "456" (3)
        "$in" => array (2)
            0 => "123" (3)
            1 => "234" (3)

Instead it throws away the first condition. Probably because it uses the shorthand syntax, overwriting the value in an array. Like this.

query => array (2)
    myField => array (1)
        "$in" => array (2)
            0 => "123" (3)
            1 => "234" (3)

The problem seems to be in Expr class in the method

public function equals($value)
{
    if ($this->currentField) {
        $this->query[$this->currentField] = $value;
    } else {
        $this->query = $value;
    }
    return $this;
}

That obviously overwrites query fields. It should instead look like:

public function equals($value)
{
    return $this->operator('$eq', $value);
}

Unfortunately, there is no way to skip the broken Builder->equals() method and write something like Builder->expr->operator("$eq", $value).

The best workaround I found is: Instead of:

    $queryBuilder->equals($value);

Use:

    $expression = $queryBuilder->expr();
    $expression->field($field)->operator('$eq', $value);
    $queryBuilder->addAnd($expression);

Two problems:

  1. The workaround introduces the $field variable potentially breaking encapsulation.
  2. How will it affect performance?

Closing this as per the explanation given in doctrine/mongodb-odm#1361 (comment).

Short summary: due to compatibility problems, an equality check is converted to an $in operator check when another operator is added to the same field. Since there may only be one of each operator per field, subsequent calls to in() will remove the equality check. The solution is to use nested expressions using the addAnd operator. Instead of:

$queryBuilder
    ->field('myField')
    ->equals('foo')
    ->in(['bar', 'foobar']);

You should write

$queryBuilder
    ->addAnd($queryBuilder->expr()->field('myField')->equals('foo'))
    ->addAnd($queryBuilder->expr()->field('myField')->in(['bar', 'foobar']));

This will produce the query that's expected. A fix for this is not trivial until we can reliably use the $eq operator in queries, which means deprecating MongoDB versions < 3.0.