22
22
use MongoDB \BSON \Regex ;
23
23
use MongoDB \BSON \UTCDateTime ;
24
24
use MongoDB \Builder \Accumulator ;
25
- use MongoDB \Builder \BuilderEncoder ;
26
25
use MongoDB \Builder \Expression \FieldPath ;
27
26
use MongoDB \Builder \Variable ;
28
27
use MongoDB \Driver \Cursor ;
@@ -87,7 +86,7 @@ class Builder extends BaseBuilder
87
86
*
88
87
* @var array
89
88
*/
90
- public $ projections ;
89
+ public $ projections = [] ;
91
90
92
91
/**
93
92
* The maximum amount of seconds to allow the query to run.
@@ -283,23 +282,73 @@ public function dump(mixed ...$args)
283
282
return $ this ;
284
283
}
285
284
286
- protected function getPipelineBuilder (): PipelineBuilder
285
+ private function getPipelineBuilder (): PipelineBuilder
287
286
{
288
- $ columns = $ this ->columns ?? [];
289
-
290
287
$ pipelineBuilder = new PipelineBuilder ([], $ this ->collection , $ this ->options );
291
288
289
+ $ wheres = $ this ->compileWheres ();
290
+
291
+ if (count ($ wheres )) {
292
+ $ pipelineBuilder ->match (...$ wheres );
293
+ }
294
+
295
+ // Distinct query
296
+ if ($ this ->distinct ) {
297
+ // Return distinct results directly
298
+ $ column = $ columns [0 ] ?? '_id ' ;
299
+
300
+ $ pipelineBuilder ->group (
301
+ _id: \MongoDB \Builder \Expression::fieldPath ($ column ),
302
+ _document: Accumulator::first (Variable::root ()),
303
+ );
304
+ $ pipelineBuilder ->replaceRoot (
305
+ newRoot: new FieldPath ('_document ' ),
306
+ );
307
+ }
308
+
309
+ if ($ this ->orders ) {
310
+ $ pipelineBuilder ->sort (...$ this ->orders );
311
+ }
312
+
313
+ if ($ this ->offset ) {
314
+ $ pipelineBuilder ->skip ($ this ->offset );
315
+ }
316
+
317
+ if ($ this ->limit ) {
318
+ $ pipelineBuilder ->limit ($ this ->limit );
319
+ }
320
+
321
+ // Normal query
322
+ // Add custom projections.
323
+ if ($ this ->projections || $ this ->columns ) {
324
+ $ columns = in_array ('* ' , $ this ->columns ) ? [] : $ this ->columns ;
325
+ $ projection = array_fill_keys ($ columns , true ) + $ this ->projections ;
326
+ if ($ projection ) {
327
+ $ pipelineBuilder ->project (...$ projection );
328
+ }
329
+ }
330
+
331
+ return $ pipelineBuilder ;
332
+ }
333
+
334
+ /**
335
+ * Return the MongoDB query to be run in the form of an element array like ['method' => [arguments]].
336
+ *
337
+ * Example: ['find' => [['name' => 'John Doe'], ['projection' => ['birthday' => 1]]]]
338
+ *
339
+ * @return array<string, mixed[]>
340
+ */
341
+ public function toMql (): array
342
+ {
343
+ $ columns = $ this ->columns ?? [];
344
+
292
345
// Drop all columns if * is present, MongoDB does not work this way.
293
346
if (in_array ('* ' , $ columns )) {
294
347
$ columns = [];
295
348
}
296
349
297
350
$ wheres = $ this ->compileWheres ();
298
351
299
- if (count ($ wheres )) {
300
- $ pipelineBuilder ->match (...$ wheres );
301
- }
302
-
303
352
// Use MongoDB's aggregation framework when using grouping or aggregation functions.
304
353
if ($ this ->groups || $ this ->aggregate ) {
305
354
$ group = [];
@@ -359,59 +408,60 @@ protected function getPipelineBuilder(): PipelineBuilder
359
408
$ group ['_id ' ] = null ;
360
409
}
361
410
411
+ // Build the aggregation pipeline.
412
+ $ pipeline = [];
413
+ if ($ wheres ) {
414
+ $ pipeline [] = ['$match ' => $ wheres ];
415
+ }
416
+
362
417
// apply unwinds for subdocument array aggregation
363
418
foreach ($ unwinds as $ unwind ) {
364
- $ pipelineBuilder -> unwind ( $ unwind) ;
419
+ $ pipeline [] = [ ' $ unwind' => ' $ ' . $ unwind] ;
365
420
}
366
421
367
422
if ($ group ) {
368
- $ pipelineBuilder -> group (... $ group) ;
423
+ $ pipeline [] = [ ' $ group' => $ group] ;
369
424
}
370
425
371
426
// Apply order and limit
372
427
if ($ this ->orders ) {
373
- $ pipelineBuilder -> sort ( $ this ->orders ) ;
428
+ $ pipeline [] = [ ' $ sort' => $ this ->orders ] ;
374
429
}
375
430
376
431
if ($ this ->offset ) {
377
- $ pipelineBuilder -> skip ( $ this ->offset ) ;
432
+ $ pipeline [] = [ ' $ skip' => $ this ->offset ] ;
378
433
}
379
434
380
435
if ($ this ->limit ) {
381
- $ pipelineBuilder -> limit ( $ this ->limit ) ;
436
+ $ pipeline [] = [ ' $ limit' => $ this ->limit ] ;
382
437
}
383
438
384
439
if ($ this ->projections ) {
385
- $ pipelineBuilder ->project (...$ this ->projections );
440
+ $ pipeline [] = ['$project ' => $ this ->projections ];
441
+ }
442
+
443
+ $ options = [
444
+ 'typeMap ' => ['root ' => 'array ' , 'document ' => 'array ' ],
445
+ ];
446
+
447
+ // Add custom query options
448
+ if (count ($ this ->options )) {
449
+ $ options = array_merge ($ options , $ this ->options );
386
450
}
387
451
388
- return $ pipelineBuilder ;
452
+ $ options = $ this ->inheritConnectionOptions ($ options );
453
+
454
+ return ['aggregate ' => [$ pipeline , $ options ]];
389
455
}
390
456
391
457
// Distinct query
392
458
if ($ this ->distinct ) {
393
459
// Return distinct results directly
394
460
$ column = $ columns [0 ] ?? '_id ' ;
395
461
396
- $ pipelineBuilder ->group (
397
- _id: \MongoDB \Builder \Expression::fieldPath ($ column ),
398
- _document: Accumulator::first (Variable::root ()),
399
- );
400
- $ pipelineBuilder ->replaceRoot (
401
- newRoot: new FieldPath ('_document ' ),
402
- );
403
- }
404
-
405
- if ($ this ->orders ) {
406
- $ pipelineBuilder ->sort (...$ this ->orders );
407
- }
408
-
409
- if ($ this ->offset ) {
410
- $ pipelineBuilder ->skip ($ this ->offset );
411
- }
462
+ $ options = $ this ->inheritConnectionOptions ();
412
463
413
- if ($ this ->limit ) {
414
- $ pipelineBuilder ->limit ($ this ->limit );
464
+ return ['distinct ' => [$ column , $ wheres , $ options ]];
415
465
}
416
466
417
467
// Normal query
@@ -423,39 +473,44 @@ protected function getPipelineBuilder(): PipelineBuilder
423
473
$ projection = array_merge ($ projection , $ this ->projections );
424
474
}
425
475
426
- if ($ projection ) {
427
- $ pipelineBuilder ->project (...$ projection );
428
- }
476
+ $ options = [];
429
477
430
- return $ pipelineBuilder ;
431
- }
478
+ // Apply order, offset, limit and projection
479
+ if ($ this ->timeout ) {
480
+ $ options ['maxTimeMS ' ] = $ this ->timeout * 1000 ;
481
+ }
432
482
433
- /**
434
- * Return the MongoDB query to be run in the form of an element array like ['method' => [arguments]].
435
- *
436
- * Example: ['find' => [['name' => 'John Doe'], ['projection' => ['birthday' => 1]]]]
437
- *
438
- * @return array<string, mixed[]>
439
- */
440
- public function toMql (): array
441
- {
442
- $ encoder = new BuilderEncoder ();
443
- $ pipeline = $ encoder ->encode ($ this ->getPipelineBuilder ()->getPipeline ());
483
+ if ($ this ->orders ) {
484
+ $ options ['sort ' ] = $ this ->orders ;
485
+ }
444
486
445
- $ options = ['typeMap ' => ['root ' => 'array ' , 'document ' => 'array ' ]];
487
+ if ($ this ->offset ) {
488
+ $ options ['skip ' ] = $ this ->offset ;
489
+ }
446
490
447
- if ($ this ->timeout ) {
448
- $ options ['maxTimeMS ' ] = $ this ->timeout * 1000 ;
491
+ if ($ this ->limit ) {
492
+ $ options ['limit ' ] = $ this ->limit ;
449
493
}
450
494
451
495
if ($ this ->hint ) {
452
496
$ options ['hint ' ] = $ this ->hint ;
453
497
}
454
498
455
- $ options = array_merge ($ options , $ this ->options );
499
+ if ($ projection ) {
500
+ $ options ['projection ' ] = $ projection ;
501
+ }
502
+
503
+ // Fix for legacy support, converts the results to arrays instead of objects.
504
+ $ options ['typeMap ' ] = ['root ' => 'array ' , 'document ' => 'array ' ];
505
+
506
+ // Add custom query options
507
+ if (count ($ this ->options )) {
508
+ $ options = array_merge ($ options , $ this ->options );
509
+ }
510
+
456
511
$ options = $ this ->inheritConnectionOptions ($ options );
457
512
458
- return ['aggregate ' => [$ pipeline , $ options ]];
513
+ return ['find ' => [$ wheres , $ options ]];
459
514
}
460
515
461
516
/**
@@ -538,37 +593,32 @@ public function generateCacheKey()
538
593
/** @return ($function === null ? PipelineBuilder : self) */
539
594
public function aggregate ($ function = null , $ columns = [])
540
595
{
541
- $ builder = $ this ->getPipelineBuilder ();
542
-
543
596
if ($ function === null ) {
544
- return $ builder ;
545
- }
546
-
547
- match ($ function ) {
548
- 'count ' => $ builder ->group (
549
- _id: null ,
550
- aggregate: Accumulator::sum (1 ),
551
- ),
552
- 'sum ' => $ builder ->group (
553
- _id: null ,
554
- aggregate: Accumulator::sum (\MongoDB \Builder \Expression::fieldPath ($ columns [0 ])),
555
- ),
556
- 'avg ' => $ builder ->group (
557
- _id: null ,
558
- aggregate: Accumulator::avg (\MongoDB \Builder \Expression::fieldPath ($ columns [0 ])),
559
- ),
560
- 'min ' => $ builder ->group (
561
- _id: null ,
562
- aggregate: Accumulator::min (\MongoDB \Builder \Expression::fieldPath ($ columns [0 ])),
563
- ),
564
- 'max ' => $ builder ->group (
565
- _id: null ,
566
- aggregate: Accumulator::max (\MongoDB \Builder \Expression::fieldPath ($ columns [0 ])),
567
- ),
568
- default => throw new InvalidArgumentException ('Unknown aggregate function: ' . $ function ),
569
- };
597
+ return $ this ->getPipelineBuilder ();
598
+ }
599
+
600
+ $ this ->aggregate = [
601
+ 'function ' => $ function ,
602
+ 'columns ' => $ columns ,
603
+ ];
604
+
605
+ $ previousColumns = $ this ->columns ;
570
606
571
- $ results = $ builder ->get ();
607
+ // We will also back up the select bindings since the select clause will be
608
+ // removed when performing the aggregate function. Once the query is run
609
+ // we will add the bindings back onto this query so they can get used.
610
+ $ previousSelectBindings = $ this ->bindings ['select ' ];
611
+
612
+ $ this ->bindings ['select ' ] = [];
613
+
614
+ $ results = $ this ->get ($ columns );
615
+
616
+ // Once we have executed the query, we will reset the aggregate property so
617
+ // that more select queries can be executed against the database without
618
+ // the aggregate value getting in the way when the grammar builds it.
619
+ $ this ->aggregate = null ;
620
+ $ this ->columns = $ previousColumns ;
621
+ $ this ->bindings ['select ' ] = $ previousSelectBindings ;
572
622
573
623
if (isset ($ results [0 ])) {
574
624
$ result = (array ) $ results [0 ];
@@ -577,19 +627,6 @@ public function aggregate($function = null, $columns = [])
577
627
}
578
628
}
579
629
580
- public function count ($ columns = '* ' ): int
581
- {
582
- if ($ columns !== '* ' ) {
583
- // @todo trigger warning, $columns is ignored
584
- }
585
-
586
- return $ this
587
- ->aggregate ()
588
- ->count ('aggregate ' )
589
- ->get ()
590
- ->value ('aggregate ' , 0 );
591
- }
592
-
593
630
/** @inheritdoc */
594
631
public function exists ()
595
632
{
@@ -963,14 +1000,14 @@ public function runPaginationCountQuery($columns = ['*'])
963
1000
if ($ this ->groups || $ this ->havings ) {
964
1001
$ without = $ this ->unions ? ['orders ' , 'limit ' , 'offset ' ] : ['columns ' , 'orders ' , 'limit ' , 'offset ' ];
965
1002
966
- $ pipelienBuilder = $ this ->cloneWithout ($ without )
1003
+ $ mql = $ this ->cloneWithout ($ without )
967
1004
->cloneWithoutBindings ($ this ->unions ? ['order ' ] : ['select ' , 'order ' ])
968
- ->getPipelineBuilder ();
1005
+ ->toMql ();
969
1006
970
1007
// Adds the $count stage to the pipeline
971
- $ pipelienBuilder -> count ( ' aggregate ') ;
1008
+ $ mql [ ' aggregate ' ][ 0 ][] = [ ' $ count' => ' aggregate '] ;
972
1009
973
- return $ pipelienBuilder -> get ();
1010
+ return $ this -> collection -> aggregate ( $ mql [ ' aggregate ' ][ 0 ], $ mql [ ' aggregate ' ][ 1 ])-> toArray ();
974
1011
}
975
1012
976
1013
return parent ::runPaginationCountQuery ($ columns );
@@ -1173,11 +1210,6 @@ protected function compileWheres(): array
1173
1210
return $ compiled ;
1174
1211
}
1175
1212
1176
- /**
1177
- * @param array $where
1178
- *
1179
- * @return array
1180
- */
1181
1213
protected function compileWhereBasic (array $ where ): array
1182
1214
{
1183
1215
$ column = $ where ['column ' ];
0 commit comments