Builder.php 22.3 KB
Newer Older
Jens Segers's avatar
Jens Segers committed
1
<?php namespace Jenssegers\Mongodb\Query;
Jens Segers's avatar
Jens Segers committed
2 3

use MongoID;
Jens Segers's avatar
Jens Segers committed
4
use MongoRegex;
5 6
use MongoDate;
use DateTime;
7
use Closure;
Jens Segers's avatar
Jens Segers committed
8

9
use Illuminate\Database\Query\Expression;
Jens Segers's avatar
Jens Segers committed
10 11
use Jenssegers\Mongodb\Connection;

12
class Builder extends \Illuminate\Database\Query\Builder {
Jens Segers's avatar
Jens Segers committed
13

Jens Segers's avatar
Jens Segers committed
14
    /**
Jens Segers's avatar
Jens Segers committed
15 16 17 18
     * The database collection
     *
     * @var MongoCollection
     */
Jens Segers's avatar
Jens Segers committed
19
    protected $collection;
Jens Segers's avatar
Jens Segers committed
20 21

    /**
22 23 24 25 26 27 28 29
     * All of the available clause operators.
     *
     * @var array
     */
    protected $operators = array(
        '=', '<', '>', '<=', '>=', '<>', '!=',
        'like', 'not like', 'between', 'ilike',
        '&', '|', '^', '<<', '>>',
Jens Segers's avatar
Jens Segers committed
30
        'exists', 'type', 'mod', 'where', 'all', 'size', 'regex', 'elemmatch'
31 32 33 34
    );

    /**
     * Operator conversion.
Jens Segers's avatar
Jens Segers committed
35 36 37
     *
     * @var array
     */
Jens's avatar
Jens committed
38
    protected $conversion = array(
Jens Segers's avatar
Jens Segers committed
39
        '='  => '=',
Jens's avatar
Jens committed
40
        '!=' => '$ne',
41
        '<>' => '$ne',
Jens Segers's avatar
Jens Segers committed
42
        '<'  => '$lt',
Jens's avatar
Jens committed
43
        '<=' => '$lte',
Jens Segers's avatar
Jens Segers committed
44
        '>'  => '$gt',
Jens's avatar
Jens committed
45
        '>=' => '$gte',
Jens Segers's avatar
Jens Segers committed
46 47 48
    );

    /**
Jens Segers's avatar
Jens Segers committed
49 50 51 52 53
     * Create a new query builder instance.
     *
     * @param Connection $connection
     * @return void
     */
Jens Segers's avatar
Jens Segers committed
54
    public function __construct(Connection $connection)
Jens Segers's avatar
Jens Segers committed
55
    {
Jens Segers's avatar
Jens Segers committed
56
        $this->connection = $connection;
Jens Segers's avatar
Jens Segers committed
57 58 59 60 61
    }

    /**
     * Execute a query for a single record by ID.
     *
62
     * @param  mixed  $id
Jens Segers's avatar
Jens Segers committed
63 64 65
     * @param  array  $columns
     * @return mixed
     */
66
    public function find($id, $columns = array())
Jens Segers's avatar
Jens Segers committed
67
    {
68
        return $this->where('_id', '=', $this->convertKey($id))->first($columns);
Jens Segers's avatar
Jens Segers committed
69 70 71
    }

    /**
Jens Segers's avatar
Jens Segers committed
72
     * Execute the query as a fresh "select" statement.
Jens Segers's avatar
Jens Segers committed
73
     *
Jens Segers's avatar
Jens Segers committed
74
     * @param  array  $columns
Jens Segers's avatar
Jens Segers committed
75
     * @return array|static[]
Jens Segers's avatar
Jens Segers committed
76
     */
77
    public function getFresh($columns = array())
Jens Segers's avatar
Jens Segers committed
78
    {
79 80
        $start = microtime(true);

Jens Segers's avatar
Jens Segers committed
81 82 83
        // If no columns have been specified for the select statement, we will set them
        // here to either the passed columns, or the standard default of retrieving
        // all of the columns on the table using the "wildcard" column character.
Jens Segers's avatar
Jens Segers committed
84
        if (is_null($this->columns)) $this->columns = $columns;
Jens Segers's avatar
Jens Segers committed
85

86
        // Drop all columns if * is present, MongoDB does not work this way.
87
        if (in_array('*', $this->columns)) $this->columns = array();
Jens Segers's avatar
Jens Segers committed
88

Jens Segers's avatar
Jens Segers committed
89 90
        // Compile wheres
        $wheres = $this->compileWheres();
Jens Segers's avatar
Jens Segers committed
91

92
        // Use MongoDB's aggregation framework when using grouping or aggregation functions.
Jens Segers's avatar
Jens Segers committed
93
        if ($this->groups || $this->aggregate)
Jens Segers's avatar
Jens Segers committed
94
        {
95
            $group = array();
Jens Segers's avatar
Jens Segers committed
96

97
            // Add grouping columns to the $group part of the aggregation pipeline.
98 99 100 101 102
            if ($this->groups)
            {
                foreach ($this->groups as $column)
                {
                    $group['_id'][$column] = '$' . $column;
103

104 105
                    // When grouping, also add the $last operator to each grouped field,
                    // this mimics MySQL's behaviour a bit.
106 107 108 109 110
                    $group[$column] = array('$last' => '$' . $column);
                }
            }
            else
            {
111
                // If we don't use grouping, set the _id to null to prepare the pipeline for
112
                // other aggregation functions.
113
                $group['_id'] = null;
114
            }
Jens Segers's avatar
Jens Segers committed
115

116 117
            // Add aggregation functions to the $group part of the aggregation pipeline,
            // these may override previous aggregations.
Jens Segers's avatar
Jens Segers committed
118 119
            if ($this->aggregate)
            {
Jens Segers's avatar
Jens Segers committed
120 121
                $function = $this->aggregate['function'];

Jens Segers's avatar
Jens Segers committed
122 123
                foreach ($this->aggregate['columns'] as $column)
                {
124
                    // Translate count into sum.
Jens Segers's avatar
Jens Segers committed
125 126
                    if ($function == 'count')
                    {
127
                        $group['aggregate'] = array('$sum' => 1);
Jens Segers's avatar
Jens Segers committed
128
                    }
129
                    // Pass other functions directly.
130 131
                    else
                    {
132
                        $group['aggregate'] = array('$' . $function => '$' . $column);
Jens Segers's avatar
Jens Segers committed
133
                    }
Jens Segers's avatar
Jens Segers committed
134 135 136
                }
            }

137 138 139 140 141 142 143 144 145 146 147 148
            // If no aggregation functions are used, we add the additional select columns
            // to the pipeline here, aggregating them by $last.
            else
            {
                foreach ($this->columns as $column)
                {
                    $key = str_replace('.', '_', $column);
                    $group[$key] = array('$last' => '$' . $column);
                }
            }

            // Build the aggregation pipeline.
149
            $pipeline = array();
150
            if ($wheres) $pipeline[] = array('$match' => $wheres);
Jens Segers's avatar
Jens Segers committed
151 152 153 154
            $pipeline[] = array('$group' => $group);

            // Apply order and limit
            if ($this->orders) $pipeline[] = array('$sort' => $this->orders);
Jens Segers's avatar
Jens Segers committed
155 156
            if ($this->offset) $pipeline[] = array('$skip' => $this->offset);
            if ($this->limit)  $pipeline[] = array('$limit' => $this->limit);
Jens Segers's avatar
Jens Segers committed
157

158
            // Execute aggregation
Jens Segers's avatar
Jens Segers committed
159 160
            $results = $this->collection->aggregate($pipeline);

161 162 163 164 165
            // Log query
            $this->connection->logQuery(
                $this->from . '.aggregate(' . json_encode($pipeline) . ')',
                array(), $this->connection->getElapsedTime($start));

Jens Segers's avatar
Jens Segers committed
166 167
            // Return results
            return $results['result'];
Jens Segers's avatar
Jens Segers committed
168
        }
169 170 171 172 173 174

        // Distinct query
        else if ($this->distinct)
        {
            // Return distinct results directly
            $column = isset($this->columns[0]) ? $this->columns[0] : '_id';
175 176 177 178 179 180 181 182 183 184

            // Execute distinct
            $result = $this->collection->distinct($column, $wheres);

            // Log query
            $this->connection->logQuery(
                $this->from . '.distinct("' . $column . '", ' . json_encode($wheres) . ')',
                array(), $this->connection->getElapsedTime($start));

            return $result;
185
        }
186

187
        // Normal query
Jens Segers's avatar
Jens Segers committed
188 189
        else
        {
Jens Segers's avatar
Jens Segers committed
190 191 192 193 194 195
            $columns = array();
            foreach ($this->columns as $column)
            {
                $columns[$column] = true;
            }

196
            // Execute query and get MongoCursor
Jens Segers's avatar
Jens Segers committed
197
            $cursor = $this->collection->find($wheres, $columns);
Jens Segers's avatar
Jens Segers committed
198

Jens Segers's avatar
Jens Segers committed
199 200 201
            // Apply order, offset and limit
            if ($this->orders) $cursor->sort($this->orders);
            if ($this->offset) $cursor->skip($this->offset);
Jens Segers's avatar
Jens Segers committed
202
            if ($this->limit)  $cursor->limit($this->limit);
Jens Segers's avatar
Jens Segers committed
203

204 205 206 207 208
            // Log query
            $this->connection->logQuery(
                $this->from . '.find(' . json_encode($wheres) . ', ' . json_encode($columns) . ')',
                array(), $this->connection->getElapsedTime($start));

209
            // Return results as an array with numeric keys
210
            return iterator_to_array($cursor, false);
Jens Segers's avatar
Jens Segers committed
211
        }
Jens Segers's avatar
Jens Segers committed
212
    }
Jens Segers's avatar
Jens Segers committed
213

Jens Segers's avatar
Jens Segers committed
214
    /**
215
     * Generate the unique cache key for the current query.
Jens Segers's avatar
Jens Segers committed
216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234
     *
     * @return string
     */
    public function generateCacheKey()
    {
        $key = array(
            'connection' => $this->connection->getName(),
            'collection' => $this->collection->getName(),
            'wheres'     => $this->wheres,
            'columns'    => $this->columns,
            'groups'     => $this->groups,
            'orders'     => $this->orders,
            'offset'     => $this->offset,
            'aggregate'  => $this->aggregate,
        );

        return md5(serialize(array_values($key)));
    }

Jens Segers's avatar
Jens Segers committed
235 236 237 238 239 240 241
    /**
     * Execute an aggregate function on the database.
     *
     * @param  string  $function
     * @param  array   $columns
     * @return mixed
     */
242
    public function aggregate($function, $columns = array())
Jens Segers's avatar
Jens Segers committed
243 244 245 246 247
    {
        $this->aggregate = compact('function', 'columns');

        $results = $this->get($columns);

Jens Segers's avatar
Jens Segers committed
248 249 250 251 252
        // Once we have executed the query, we will reset the aggregate property so
        // that more select queries can be executed against the database without
        // the aggregate value getting in the way when the grammar builds it.
        $this->columns = null; $this->aggregate = null;

Jens Segers's avatar
Jens Segers committed
253
        if (isset($results[0]))
Jens Segers's avatar
Jens Segers committed
254
        {
255 256 257
            $result = (array) $results[0];

            return $result['aggregate'];
Jens Segers's avatar
Jens Segers committed
258 259 260
        }
    }

Jens Segers's avatar
Jens Segers committed
261 262 263
    /**
     * Force the query to only return distinct results.
     *
Jens's avatar
Jens committed
264
     * @return Builder
Jens Segers's avatar
Jens Segers committed
265 266 267 268 269 270 271 272 273 274 275 276 277
     */
    public function distinct($column = false)
    {
        $this->distinct = true;

        if ($column)
        {
            $this->columns = array($column);
        }

        return $this;
    }

Jens Segers's avatar
Jens Segers committed
278
    /**
Jens Segers's avatar
Jens Segers committed
279
     * Add an "order by" clause to the query.
Jens Segers's avatar
Jens Segers committed
280
     *
Jens Segers's avatar
Jens Segers committed
281 282
     * @param  string  $column
     * @param  string  $direction
Jens Segers's avatar
Jens Segers committed
283
     * @return Builder
Jens Segers's avatar
Jens Segers committed
284
     */
285
    public function orderBy($column, $direction = 'asc')
Jens Segers's avatar
Jens Segers committed
286
    {
287 288 289
        $direction = (strtolower($direction) == 'asc' ? 1 : -1);

        if ($column == 'natural')
290
        {
291
            $this->orders['$natural'] = $direction;
292 293 294
        }
        else
        {
295
            $this->orders[$column] = $direction;
296
        }
Jens Segers's avatar
Jens Segers committed
297

Jens Segers's avatar
Jens Segers committed
298
        return $this;
Jens Segers's avatar
Jens Segers committed
299 300
    }

Jens Segers's avatar
Jens Segers committed
301 302 303 304 305 306
    /**
     * Add a where between statement to the query.
     *
     * @param  string  $column
     * @param  array   $values
     * @param  string  $boolean
307 308
     * @param  bool  $not
     * @return Builder
Jens Segers's avatar
Jens Segers committed
309
     */
310
    public function whereBetween($column, array $values, $boolean = 'and', $not = false)
Jens Segers's avatar
Jens Segers committed
311 312 313
    {
        $type = 'between';

314
        $this->wheres[] = compact('column', 'type', 'boolean', 'values', 'not');
Jens Segers's avatar
Jens Segers committed
315 316 317 318

        return $this;
    }

Jens Segers's avatar
Jens Segers committed
319
    /**
Jens Segers's avatar
Jens Segers committed
320
     * Insert a new record into the database.
Jens Segers's avatar
Jens Segers committed
321
     *
Jens Segers's avatar
Jens Segers committed
322 323
     * @param  array  $values
     * @return bool
Jens Segers's avatar
Jens Segers committed
324
     */
Jens Segers's avatar
Jens Segers committed
325
    public function insert(array $values)
Jens Segers's avatar
Jens Segers committed
326
    {
327 328
        $start = microtime(true);

329 330 331 332
        // Since every insert gets treated like a batch insert, we will have to detect
        // if the user is inserting a single document or an array of documents.
        $batch = true;
        foreach ($values as $value)
333
        {
334 335
            // As soon as we find a value that is not an array we assume the user is
            // inserting a single document.
336
            if (!is_array($value))
337 338 339
            {
                $batch = false; break;
            }
340
        }
Jens Segers's avatar
Jens Segers committed
341

342 343
        if (!$batch) $values = array($values);

344
        // Batch insert
345 346 347 348 349 350 351
        $result = $this->collection->batchInsert($values);

        // Log query
        $this->connection->logQuery(
            $this->from . '.batchInsert(' . json_encode($values) . ')',
            array(), $this->connection->getElapsedTime($start));

352
        return (1 == (int) $result['ok']);
Jens Segers's avatar
Jens Segers committed
353 354
    }

Jens Segers's avatar
Jens Segers committed
355 356 357 358 359 360 361 362 363
    /**
     * Insert a new record and get the value of the primary key.
     *
     * @param  array   $values
     * @param  string  $sequence
     * @return int
     */
    public function insertGetId(array $values, $sequence = null)
    {
364 365
        $start = microtime(true);

Jens's avatar
Jens committed
366 367
        $result = $this->collection->insert($values);

368 369 370 371 372
        // Log query
        $this->connection->logQuery(
            $this->from . '.insert(' . json_encode($values) . ')',
            array(), $this->connection->getElapsedTime($start));

Jens's avatar
Jens committed
373 374
        if (1 == (int) $result['ok'])
        {
Jens Segers's avatar
Jens Segers committed
375 376 377 378 379
            if (!$sequence)
            {
                $sequence = '_id';
            }

380 381
            // Return id
            return $values[$sequence];
Jens's avatar
Jens committed
382
        }
Jens Segers's avatar
Jens Segers committed
383 384
    }

Jens Segers's avatar
Jens Segers committed
385
    /**
Jens Segers's avatar
Jens Segers committed
386
     * Update a record in the database.
Jens Segers's avatar
Jens Segers committed
387
     *
Jens Segers's avatar
Jens Segers committed
388
     * @param  array  $values
Jens Segers's avatar
Jens Segers committed
389
     * @param  array  $options
Jens Segers's avatar
Jens Segers committed
390 391
     * @return int
     */
392
    public function update(array $values, array $options = array())
Jens Segers's avatar
Jens Segers committed
393
    {
394
        return $this->performUpdate(array('$set' => $values), $options);
Jens Segers's avatar
Jens Segers committed
395 396
    }

397 398 399 400 401 402 403 404 405 406
    /**
     * Increment a column's value by a given amount.
     *
     * @param  string  $column
     * @param  int     $amount
     * @param  array   $extra
     * @return int
     */
    public function increment($column, $amount = 1, array $extra = array())
    {
Jens Segers's avatar
Jens Segers committed
407
        $query = array(
408
            '$inc' => array($column => $amount)
409
        );
410 411

        if (!empty($extra))
412
        {
413
            $query['$set'] = $extra;
414
        }
415

416 417 418 419 420 421 422
        // Protect
        $this->where(function($query) use ($column)
        {
            $query->where($column, 'exists', false);
            $query->orWhereNotNull($column);
        });

Jens Segers's avatar
Jens Segers committed
423
        return $this->performUpdate($query);
424 425 426 427 428 429 430 431 432 433 434 435 436 437 438
    }

    /**
     * Decrement a column's value by a given amount.
     *
     * @param  string  $column
     * @param  int     $amount
     * @param  array   $extra
     * @return int
     */
    public function decrement($column, $amount = 1, array $extra = array())
    {
        return $this->increment($column, -1 * $amount, $extra);
    }

439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458
    /**
     * Pluck a single column from the database.
     *
     * @param  string  $column
     * @return mixed
     */
    public function pluck($column)
    {
        $result = (array) $this->first(array($column));

        // MongoDB returns the _id field even if you did not ask for it, so we need to
        // remove this from the result.
        if (array_key_exists('_id', $result))
        {
            unset($result['_id']);
        }

        return count($result) > 0 ? reset($result) : null;
    }

Jens Segers's avatar
Jens Segers committed
459
    /**
Jens Segers's avatar
Jens Segers committed
460
     * Delete a record from the database.
Jens Segers's avatar
Jens Segers committed
461
     *
Jens Segers's avatar
Jens Segers committed
462
     * @param  mixed  $id
Jens Segers's avatar
Jens Segers committed
463 464
     * @return int
     */
Jens Segers's avatar
Jens Segers committed
465
    public function delete($id = null)
Jens Segers's avatar
Jens Segers committed
466
    {
467 468 469 470 471 472 473 474 475
        $start = microtime(true);

        $wheres = $this->compileWheres();
        $result = $this->collection->remove($wheres);

        // Log query
        $this->connection->logQuery(
            $this->from . '.remove(' . json_encode($wheres) . ')',
            array(), $this->connection->getElapsedTime($start));
Jens Segers's avatar
Jens Segers committed
476

Jens Segers's avatar
Jens Segers committed
477
        if (1 == (int) $result['ok'])
Jens Segers's avatar
Jens Segers committed
478 479 480 481 482 483 484
        {
            return $result['n'];
        }

        return 0;
    }

Jens Segers's avatar
Jens Segers committed
485 486 487 488 489 490 491 492
    /**
     * Set the collection which the query is targeting.
     *
     * @param  string  $collection
     * @return Builder
     */
    public function from($collection)
    {
Jens's avatar
Jens committed
493
        if ($collection)
Jens Segers's avatar
Jens Segers committed
494
        {
Jens's avatar
Jens committed
495
            $this->collection = $this->connection->getCollection($collection);
Jens Segers's avatar
Jens Segers committed
496
        }
Jens Segers's avatar
Jens Segers committed
497

498
        return parent::from($collection);
Jens Segers's avatar
Jens Segers committed
499 500
    }

Jens Segers's avatar
Jens Segers committed
501 502 503 504 505 506 507
    /**
     * Run a truncate statement on the table.
     *
     * @return void
     */
    public function truncate()
    {
508
        $result = $this->collection->remove();
Jens Segers's avatar
Jens Segers committed
509 510

        return (1 == (int) $result['ok']);
Jens Segers's avatar
Jens Segers committed
511 512
    }

513 514 515 516 517 518
    /**
     * Create a raw database expression.
     *
     * @param  closure  $expression
     * @return mixed
     */
519
    public function raw($expression = null)
520
    {
521
        // Execute the closure on the mongodb collection
522 523 524 525
        if ($expression instanceof Closure)
        {
            return call_user_func($expression, $this->collection);
        }
526

527 528 529 530 531 532 533
        // Create an expression for the given value
        else if (!is_null($expression))
        {
            return new Expression($expression);
        }

        // Quick access to the mongodb collection
534
        return $this->collection;
535 536
    }

537
    /**
Jens Segers's avatar
Jens Segers committed
538
     * Append one or more values to an array.
539
     *
Jens Segers's avatar
Jens Segers committed
540
     * @param  mixed   $column
541 542 543
     * @param  mixed   $value
     * @return int
     */
544
    public function push($column, $value = null, $unique = false)
545
    {
546 547 548
        // Use the addToSet operator in case we only want unique items.
        $operator = $unique ? '$addToSet' : '$push';

549 550
        if (is_array($column))
        {
551
            $query = array($operator => $column);
552 553 554
        }
        else
        {
555
            $query = array($operator => array($column => $value));
556 557 558 559 560
        }

        return $this->performUpdate($query);
    }

Jens Segers's avatar
Jens Segers committed
561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581
    /**
     * Remove one or more values from an array.
     *
     * @param  mixed   $column
     * @param  mixed   $value
     * @return int
     */
    public function pull($column, $value = null)
    {
        if (is_array($column))
        {
            $query = array('$pull' => $column);
        }
        else
        {
            $query = array('$pull' => array($column => $value));
        }

        return $this->performUpdate($query);
    }

Jens Segers's avatar
Jens Segers committed
582 583 584 585 586 587
    /**
     * Remove one or more fields.
     *
     * @param  mixed $columns
     * @return int
     */
588
    public function drop($columns)
Jens Segers's avatar
Jens Segers committed
589 590 591 592 593 594 595 596 597 598 599 600 601 602 603
    {
        if (!is_array($columns)) $columns = array($columns);

        $fields = array();

        foreach ($columns as $column)
        {
            $fields[$column] = 1;
        }

        $query = array('$unset' => $fields);

        return $this->performUpdate($query);
    }

Jens Segers's avatar
Jens Segers committed
604 605 606 607 608 609 610 611 612 613
    /**
     * Get a new instance of the query builder.
     *
     * @return Builder
     */
    public function newQuery()
    {
        return new Builder($this->connection);
    }

614
    /**
Jens Segers's avatar
Jens Segers committed
615
     * Perform an update query.
616 617
     *
     * @param  array  $query
Jens Segers's avatar
Jens Segers committed
618
     * @param  array  $options
619 620
     * @return int
     */
621
    protected function performUpdate($query, array $options = array())
622
    {
623 624
        $start = microtime(true);

625 626 627 628 629 630
        // Default options
        $default = array('multiple' => true);

        // Merge options and override default options
        $options = array_merge($default, $options);

631 632 633 634 635 636 637
        $wheres = $this->compileWheres();
        $result = $this->collection->update($wheres, $query, $options);

        // Log query
        $this->connection->logQuery(
            $this->from . '.update(' . json_encode($wheres) . ', ' . json_encode($query) . ', ' . json_encode($options) . ')',
            array(), $this->connection->getElapsedTime($start));
638 639 640 641 642 643 644 645 646

        if (1 == (int) $result['ok'])
        {
            return $result['n'];
        }

        return 0;
    }

647
    /**
Jens Segers's avatar
Jens Segers committed
648
     * Convert a key to MongoID if needed.
649
     *
650 651 652
     * @param  mixed $id
     * @return mixed
     */
653
    public function convertKey($id)
654 655 656 657 658 659 660 661 662
    {
        if (is_string($id) && strlen($id) === 24 && ctype_xdigit($id))
        {
            return new MongoId($id);
        }

        return $id;
    }

Jens Segers's avatar
Jens Segers committed
663
    /**
Jens Segers's avatar
Jens Segers committed
664 665 666 667
     * Compile the where array.
     *
     * @return array
     */
Jens Segers's avatar
Jens Segers committed
668
    protected function compileWheres()
Jens Segers's avatar
Jens Segers committed
669 670
    {
        if (!$this->wheres) return array();
Jens Segers's avatar
Jens Segers committed
671

Jens Segers's avatar
Jens Segers committed
672
        // The new list of compiled wheres
Jens Segers's avatar
Jens Segers committed
673 674
        $wheres = array();

675
        foreach ($this->wheres as $i => &$where)
Jens Segers's avatar
Jens Segers committed
676
        {
677 678 679 680
            // Make sure the operator is in lowercase
            if (isset($where['operator']))
            {
                $where['operator'] = strtolower($where['operator']);
Jens Segers's avatar
Jens Segers committed
681 682 683 684 685 686

                // Fix elemMatch
                if ($where['operator'] == 'elemmatch')
                {
                    $where['operator'] = 'elemMatch';
                }
687 688
            }

Jens's avatar
Jens committed
689
            // Convert id's
690
            if (isset($where['column']) && $where['column'] == '_id')
Jens's avatar
Jens committed
691
            {
Jens Segers's avatar
Jens Segers committed
692
                // Multiple values
Jens Segers's avatar
Jens Segers committed
693 694 695 696
                if (isset($where['values']))
                {
                    foreach ($where['values'] as &$value)
                    {
697
                        $value = $this->convertKey($value);
Jens Segers's avatar
Jens Segers committed
698 699
                    }
                }
Jens Segers's avatar
Jens Segers committed
700
                // Single value
701
                elseif (isset($where['value']))
Jens Segers's avatar
Jens Segers committed
702
                {
703
                    $where['value'] = $this->convertKey($where['value']);
Jens Segers's avatar
Jens Segers committed
704
                }
Jens's avatar
Jens committed
705 706
            }

707 708 709 710 711 712
            // Convert dates
            if (isset($where['value']) && $where['value'] instanceof DateTime)
            {
                $where['value'] = new MongoDate($where['value']->getTimestamp());
            }

Jens's avatar
Jens committed
713
            // First item of chain
Jens's avatar
Jens committed
714
            if ($i == 0 && count($this->wheres) > 1 && $where['boolean'] == 'and')
Jens's avatar
Jens committed
715 716 717 718 719
            {
                // Copy over boolean value of next item in chain
                $where['boolean'] = $this->wheres[$i+1]['boolean'];
            }

Jens Segers's avatar
Jens Segers committed
720 721 722 723
            // Delegate
            $method = "compileWhere{$where['type']}";
            $compiled = $this->{$method}($where);

Jens Segers's avatar
Jens Segers committed
724 725 726 727 728 729
            // Check for or
            if ($where['boolean'] == 'or')
            {
                $compiled = array('$or' => array($compiled));
            }

Jens's avatar
Jens committed
730 731
            // Merge compiled where
            $wheres = array_merge_recursive($wheres, $compiled);
Jens Segers's avatar
Jens Segers committed
732 733 734 735 736
        }

        return $wheres;
    }

Jens Segers's avatar
Jens Segers committed
737
    protected function compileWhereBasic($where)
Jens Segers's avatar
Jens Segers committed
738 739 740
    {
        extract($where);

Jens Segers's avatar
Jens Segers committed
741 742 743 744 745 746 747 748 749 750 751 752 753
        // Replace like with MongoRegex
        if ($operator == 'like')
        {
            $operator = '=';
            $regex = str_replace('%', '', $value);

            // Prepare regex
            if (substr($value, 0, 1) != '%') $regex = '^' . $regex;
            if (substr($value, -1) != '%')   $regex = $regex . '$';

            $value = new MongoRegex("/$regex/i");
        }

Jens Segers's avatar
Jens Segers committed
754 755
        if (!isset($operator) || $operator == '=')
        {
Jens's avatar
Jens committed
756
            $query = array($column => $value);
Jens Segers's avatar
Jens Segers committed
757
        }
758
        else if (array_key_exists($operator, $this->conversion))
Jens Segers's avatar
Jens Segers committed
759
        {
Jens's avatar
Jens committed
760
            $query = array($column => array($this->conversion[$operator] => $value));
Jens Segers's avatar
Jens Segers committed
761
        }
762 763 764 765
        else
        {
            $query = array($column => array('$' . $operator => $value));
        }
Jens's avatar
Jens committed
766 767

        return $query;
Jens Segers's avatar
Jens Segers committed
768 769
    }

Jens Segers's avatar
Jens Segers committed
770
    protected function compileWhereNested($where)
771 772 773
    {
        extract($where);

Jens Segers's avatar
Jens Segers committed
774
        return $query->compileWheres();
775 776
    }

Jens Segers's avatar
Jens Segers committed
777
    protected function compileWhereIn($where)
Jens Segers's avatar
Jens Segers committed
778
    {
Jens Segers's avatar
Jens Segers committed
779
        extract($where);
Jens's avatar
Jens committed
780

Jens Segers's avatar
Jens Segers committed
781
        return array($column => array('$in' => $values));
Jens Segers's avatar
Jens Segers committed
782
    }
Jens Segers's avatar
Jens Segers committed
783

Jens Segers's avatar
Jens Segers committed
784
    protected function compileWhereNotIn($where)
785 786 787 788 789 790
    {
        extract($where);

        return array($column => array('$nin' => $values));
    }

Jens Segers's avatar
Jens Segers committed
791
    protected function compileWhereNull($where)
Jens's avatar
Jens committed
792
    {
793 794
        $where['operator'] = '=';
        $where['value'] = null;
Jens's avatar
Jens committed
795

796
        return $this->compileWhereBasic($where);
Jens's avatar
Jens committed
797 798
    }

Jens Segers's avatar
Jens Segers committed
799
    protected function compileWhereNotNull($where)
Jens's avatar
Jens committed
800
    {
801 802
        $where['operator'] = '!=';
        $where['value'] = null;
Jens's avatar
Jens committed
803

804
        return $this->compileWhereBasic($where);
Jens's avatar
Jens committed
805 806
    }

Jens Segers's avatar
Jens Segers committed
807
    protected function compileWhereBetween($where)
Jens Segers's avatar
Jens Segers committed
808 809 810
    {
        extract($where);

811 812 813 814 815 816 817 818 819 820 821 822 823 824 825
        if ($not)
        {
            return array(
                '$or' => array(
                    array(
                        $column => array(
                            '$lte' => $values[0]
                        )
                    ),
                    array(
                        $column => array(
                            '$gte' => $values[1]
                        )
                    )
                )
Jens Segers's avatar
Jens Segers committed
826
            );
827 828 829 830 831 832 833 834 835 836
        }
        else
        {
            return array(
                $column => array(
                    '$gte' => $values[0],
                    '$lte' => $values[1]
                )
            );
        }
Jens Segers's avatar
Jens Segers committed
837 838
    }

Jens Segers's avatar
Jens Segers committed
839
    protected function compileWhereRaw($where)
Jens Segers's avatar
Jens Segers committed
840 841 842 843
    {
        return $where['sql'];
    }

Jens Segers's avatar
Jens Segers committed
844 845 846 847 848 849 850 851 852 853 854
    /**
     * Handle dynamic method calls into the method.
     *
     * @param  string  $method
     * @param  array   $parameters
     * @return mixed
     */
    public function __call($method, $parameters)
    {
        if ($method == 'unset')
        {
855
            return call_user_func_array(array($this, 'drop'), $parameters);
Jens Segers's avatar
Jens Segers committed
856 857 858 859 860
        }

        return parent::__call($method, $parameters);
    }

861
}