Passed
Push — master ( 13bb95...cb43b1 )
by Glynn
05:40 queued 03:25
created
src/QueryBuilder/QueryBuilderHandler.php 1 patch
Indentation   +1509 added lines, -1509 removed lines patch added patch discarded remove patch
@@ -24,1513 +24,1513 @@
 block discarded – undo
24 24
 
25 25
 class QueryBuilderHandler implements HasConnection
26 26
 {
27
-    /**
28
-     * @method add
29
-     */
30
-    use TablePrefixer;
31
-
32
-    /**
33
-     * @var \Viocon\Container
34
-     */
35
-    protected $container;
36
-
37
-    /**
38
-     * @var Connection
39
-     */
40
-    protected $connection;
41
-
42
-    /**
43
-     * @var array<string, mixed[]|mixed>
44
-     */
45
-    protected $statements = [];
46
-
47
-    /**
48
-     * @var wpdb
49
-     */
50
-    protected $dbInstance;
51
-
52
-    /**
53
-     * @var string|string[]|null
54
-     */
55
-    protected $sqlStatement = null;
56
-
57
-    /**
58
-     * @var string|null
59
-     */
60
-    protected $tablePrefix = null;
61
-
62
-    /**
63
-     * @var WPDBAdapter
64
-     */
65
-    protected $adapterInstance;
66
-
67
-    /**
68
-     * The mode to return results as.
69
-     * Accepts WPDB constants or class names.
70
-     *
71
-     * @var string
72
-     */
73
-    protected $fetchMode;
74
-
75
-    /**
76
-     * Custom args used to construct models for hydrator
77
-     *
78
-     * @var array<int, mixed>|null
79
-     */
80
-    protected $hydratorConstructorArgs;
81
-
82
-    /**
83
-     * Handler for Json Selectors
84
-     *
85
-     * @var JsonHandler
86
-     */
87
-    protected $jsonHandler;
88
-
89
-    /**
90
-     * @param \Pixie\Connection|null $connection
91
-     * @param string $fetchMode
92
-     * @param mixed[] $hydratorConstructorArgs
93
-     *
94
-     * @throws Exception if no connection passed and not previously established
95
-     */
96
-    final public function __construct(
97
-        Connection $connection = null,
98
-        string $fetchMode = \OBJECT,
99
-        ?array $hydratorConstructorArgs = null
100
-    ) {
101
-        if (is_null($connection)) {
102
-            // throws if connection not already established.
103
-            $connection = Connection::getStoredConnection();
104
-        }
105
-
106
-        // Set all dependencies from connection.
107
-        $this->connection = $connection;
108
-        $this->container  = $this->connection->getContainer();
109
-        $this->dbInstance = $this->connection->getDbInstance();
110
-        $this->setAdapterConfig($this->connection->getAdapterConfig());
111
-
112
-        // Set up optional hydration details.
113
-        $this->setFetchMode($fetchMode);
114
-        $this->hydratorConstructorArgs = $hydratorConstructorArgs;
115
-
116
-        // Query builder adapter instance
117
-        $this->adapterInstance = $this->container->build(
118
-            WPDBAdapter::class,
119
-            [$this->connection]
120
-        );
121
-
122
-        // Setup JSON Selector handler.
123
-        $this->jsonHandler = new JsonHandler($connection);
124
-    }
125
-
126
-    /**
127
-     * Sets the config for WPDB
128
-     *
129
-     * @param array<string, mixed> $adapterConfig
130
-     *
131
-     * @return void
132
-     */
133
-    protected function setAdapterConfig(array $adapterConfig): void
134
-    {
135
-        if (isset($adapterConfig[Connection::PREFIX])) {
136
-            $this->tablePrefix = $adapterConfig[Connection::PREFIX];
137
-        }
138
-    }
139
-
140
-    /**
141
-     * Fetch query results as object of specified type
142
-     *
143
-     * @param string $className
144
-     * @param array<int, mixed> $constructorArgs
145
-     * @return static
146
-     */
147
-    public function asObject($className, $constructorArgs = array()): self
148
-    {
149
-        return $this->setFetchMode($className, $constructorArgs);
150
-    }
151
-
152
-    /**
153
-     * Set the fetch mode
154
-     *
155
-     * @param string $mode
156
-     * @param array<int, mixed>|null $constructorArgs
157
-     *
158
-     * @return static
159
-     */
160
-    public function setFetchMode(string $mode, ?array $constructorArgs = null): self
161
-    {
162
-        $this->fetchMode               = $mode;
163
-        $this->hydratorConstructorArgs = $constructorArgs;
164
-
165
-        return $this;
166
-    }
167
-
168
-    /**
169
-     * @param Connection|null $connection
170
-     *
171
-     * @return static
172
-     *
173
-     * @throws Exception
174
-     */
175
-    public function newQuery(Connection $connection = null): self
176
-    {
177
-        if (is_null($connection)) {
178
-            $connection = $this->connection;
179
-        }
180
-
181
-        $newQuery = $this->constructCurrentBuilderClass($connection);
182
-        $newQuery->setFetchMode($this->getFetchMode(), $this->hydratorConstructorArgs);
183
-
184
-        return $newQuery;
185
-    }
186
-
187
-    /**
188
-     * Returns a new instance of the current, with the passed connection.
189
-     *
190
-     * @param \Pixie\Connection $connection
191
-     *
192
-     * @return static
193
-     */
194
-    protected function constructCurrentBuilderClass(Connection $connection): self
195
-    {
196
-        return new static($connection);
197
-    }
198
-
199
-    /**
200
-     * Interpolates a query
201
-     *
202
-     * @param string $query
203
-     * @param array<mixed> $bindings
204
-     * @return string
205
-     */
206
-    public function interpolateQuery(string $query, array $bindings = []): string
207
-    {
208
-        return $this->adapterInstance->interpolateQuery($query, $bindings);
209
-    }
210
-
211
-    /**
212
-     * @param string           $sql
213
-     * @param array<int,mixed> $bindings
214
-     *
215
-     * @return static
216
-     */
217
-    public function query($sql, $bindings = []): self
218
-    {
219
-        list($this->sqlStatement) = $this->statement($sql, $bindings);
220
-
221
-        return $this;
222
-    }
223
-
224
-    /**
225
-     * @param string           $sql
226
-     * @param array<int,mixed> $bindings
227
-     *
228
-     * @return array{0:string, 1:float}
229
-     */
230
-    public function statement(string $sql, $bindings = []): array
231
-    {
232
-        $start        = microtime(true);
233
-        $sqlStatement = empty($bindings) ? $sql : $this->interpolateQuery($sql, $bindings);
234
-
235
-        if (!is_string($sqlStatement)) {
236
-            throw new Exception('Could not interpolate query', 1);
237
-        }
238
-
239
-        return [$sqlStatement, microtime(true) - $start];
240
-    }
241
-
242
-    /**
243
-     * Get all rows
244
-     *
245
-     * @return array<mixed,mixed>|null
246
-     *
247
-     * @throws Exception
248
-     */
249
-    public function get()
250
-    {
251
-        $eventResult = $this->fireEvents('before-select');
252
-        if (!is_null($eventResult)) {
253
-            return $eventResult;
254
-        }
255
-        $executionTime = 0;
256
-        if (is_null($this->sqlStatement)) {
257
-            $queryObject = $this->getQuery('select');
258
-            $statement   = $this->statement(
259
-                $queryObject->getSql(),
260
-                $queryObject->getBindings()
261
-            );
262
-
263
-            $this->sqlStatement = $statement[0];
264
-            $executionTime      = $statement[1];
265
-        }
266
-
267
-        $start  = microtime(true);
268
-        $result = $this->dbInstance()->get_results(
269
-            is_array($this->sqlStatement) ? (end($this->sqlStatement) ?: '') : $this->sqlStatement,
270
-            // If we are using the hydrator, return as OBJECT and let the hydrator map the correct model.
271
-            $this->useHydrator() ? OBJECT : $this->getFetchMode()
272
-        );
273
-        $executionTime += microtime(true) - $start;
274
-        $this->sqlStatement = null;
275
-
276
-        // Ensure we have an array of results.
277
-        if (!is_array($result) && null !== $result) {
278
-            $result = [$result];
279
-        }
280
-
281
-        // Maybe hydrate the results.
282
-        if (null !== $result && $this->useHydrator()) {
283
-            $result = $this->getHydrator()->fromMany($result);
284
-        }
285
-
286
-        $this->fireEvents('after-select', $result, $executionTime);
287
-
288
-        return $result;
289
-    }
290
-
291
-    /**
292
-     * Returns a populated instance of the Hydrator.
293
-     *
294
-     * @return Hydrator
295
-     */
296
-    protected function getHydrator(): Hydrator /* @phpstan-ignore-line */
297
-    {
298
-        $hydrator = new Hydrator($this->getFetchMode(), $this->hydratorConstructorArgs ?? []); /* @phpstan-ignore-line */
299
-
300
-        return $hydrator;
301
-    }
302
-
303
-    /**
304
-     * Checks if the results should be mapped via the hydrator
305
-     *
306
-     * @return bool
307
-     */
308
-    protected function useHydrator(): bool
309
-    {
310
-        return !in_array($this->getFetchMode(), [\ARRAY_A, \ARRAY_N, \OBJECT, \OBJECT_K]);
311
-    }
312
-
313
-    /**
314
-     * Find all matching a simple where condition.
315
-     *
316
-     * Shortcut of ->where('key','=','value')->limit(1)->get();
317
-     *
318
-     * @return \stdClass\array<mixed,mixed>|object|null Can return any object using hydrator
319
-     */
320
-    public function first()
321
-    {
322
-        $this->limit(1);
323
-        $result = $this->get();
324
-
325
-        return empty($result) ? null : $result[0];
326
-    }
327
-
328
-    /**
329
-     * Find all matching a simple where condition.
330
-     *
331
-     * Shortcut of ->where('key','=','value')->get();
332
-     *
333
-     * @param string $fieldName
334
-     * @param mixed $value
335
-     *
336
-     * @return array<mixed,mixed>|null Can return any object using hydrator
337
-     */
338
-    public function findAll($fieldName, $value)
339
-    {
340
-        $this->where($fieldName, '=', $value);
341
-
342
-        return $this->get();
343
-    }
344
-
345
-    /**
346
-     * @param string $fieldName
347
-     * @param mixed $value
348
-     *
349
-     * @return \stdClass\array<mixed,mixed>|object|null Can return any object using hydrator
350
-     */
351
-    public function find($value, $fieldName = 'id')
352
-    {
353
-        $this->where($fieldName, '=', $value);
354
-
355
-        return $this->first();
356
-    }
357
-
358
-    /**
359
-     * @param string $fieldName
360
-     * @param mixed $value
361
-     *
362
-     * @return \stdClass\array<mixed,mixed>|object Can return any object using hydrator
363
-     * @throws Exception If fails to find
364
-     */
365
-    public function findOrFail($value, $fieldName = 'id')
366
-    {
367
-        $result = $this->find($value, $fieldName);
368
-        if (null === $result) {
369
-            throw new Exception("Failed to find {$fieldName}={$value}", 1);
370
-        }
371
-        return $result;
372
-    }
373
-
374
-    /**
375
-     * Used to handle all aggregation method.
376
-     *
377
-     * @see Taken from the pecee-pixie library - https://github.com/skipperbent/pecee-pixie/
378
-     *
379
-     * @param string $type
380
-     * @param string|Raw $field
381
-     *
382
-     * @return float
383
-     */
384
-    protected function aggregate(string $type, $field = '*'): float
385
-    {
386
-        // Parse a raw expression.
387
-        if ($field instanceof Raw) {
388
-            $field = $this->adapterInstance->parseRaw($field);
389
-        }
390
-
391
-        // Potentialy cast field from JSON
392
-        if ($this->jsonHandler->isJsonSelector($field)) {
393
-            $field = $this->jsonHandler->extractAndUnquoteFromJsonSelector($field);
394
-        }
395
-
396
-        // Verify that field exists
397
-        if ('*' !== $field && true === isset($this->statements['selects']) && false === \in_array($field, $this->statements['selects'], true)) {
398
-            throw new \Exception(sprintf('Failed %s query - the column %s hasn\'t been selected in the query.', $type, $field));
399
-        }
400
-
401
-
402
-        if (false === isset($this->statements['tables'])) {
403
-            throw new Exception('No table selected');
404
-        }
405
-
406
-        $count = $this
407
-            ->table($this->subQuery($this, 'count'))
408
-            ->select([$this->raw(sprintf('%s(%s) AS field', strtoupper($type), $field))])
409
-            ->first();
410
-
411
-        return true === isset($count->field) ? (float)$count->field : 0;
412
-    }
413
-
414
-    /**
415
-     * Get count of all the rows for the current query
416
-     *
417
-     * @see Taken from the pecee-pixie library - https://github.com/skipperbent/pecee-pixie/
418
-     *
419
-     * @param string|Raw $field
420
-     *
421
-     * @return int
422
-     *
423
-     * @throws Exception
424
-     */
425
-    public function count($field = '*'): int
426
-    {
427
-        return (int)$this->aggregate('count', $field);
428
-    }
429
-
430
-    /**
431
-     * Get the sum for a field in the current query
432
-     *
433
-     * @see Taken from the pecee-pixie library - https://github.com/skipperbent/pecee-pixie/
434
-     *
435
-     * @param string|Raw $field
436
-     *
437
-     * @return float
438
-     *
439
-     * @throws Exception
440
-     */
441
-    public function sum($field): float
442
-    {
443
-        return $this->aggregate('sum', $field);
444
-    }
445
-
446
-    /**
447
-     * Get the average for a field in the current query
448
-     *
449
-     * @see Taken from the pecee-pixie library - https://github.com/skipperbent/pecee-pixie/
450
-     *
451
-     * @param string|Raw $field
452
-     *
453
-     * @return float
454
-     *
455
-     * @throws Exception
456
-     */
457
-    public function average($field): float
458
-    {
459
-        return $this->aggregate('avg', $field);
460
-    }
461
-
462
-    /**
463
-     * Get the minimum for a field in the current query
464
-     *
465
-     * @see Taken from the pecee-pixie library - https://github.com/skipperbent/pecee-pixie/
466
-     *
467
-     * @param string|Raw $field
468
-     *
469
-     * @return float
470
-     *
471
-     * @throws Exception
472
-     */
473
-    public function min($field): float
474
-    {
475
-        return $this->aggregate('min', $field);
476
-    }
477
-
478
-    /**
479
-     * Get the maximum for a field in the current query
480
-     *
481
-     * @see Taken from the pecee-pixie library - https://github.com/skipperbent/pecee-pixie/
482
-     *
483
-     * @param string|Raw $field
484
-     *
485
-     * @return float
486
-     *
487
-     * @throws Exception
488
-     */
489
-    public function max($field): float
490
-    {
491
-        return $this->aggregate('max', $field);
492
-    }
493
-
494
-    /**
495
-     * @param string $type
496
-     * @param bool|array<mixed, mixed> $dataToBePassed
497
-     *
498
-     * @return mixed
499
-     *
500
-     * @throws Exception
501
-     */
502
-    public function getQuery(string $type = 'select', $dataToBePassed = [])
503
-    {
504
-        $allowedTypes = ['select', 'insert', 'insertignore', 'replace', 'delete', 'update', 'criteriaonly'];
505
-        if (!in_array(strtolower($type), $allowedTypes)) {
506
-            throw new Exception($type . ' is not a known type.', 2);
507
-        }
508
-
509
-        $queryArr = $this->adapterInstance->$type($this->statements, $dataToBePassed);
510
-
511
-        return $this->container->build(
512
-            QueryObject::class,
513
-            [$queryArr['sql'], $queryArr['bindings'], $this->dbInstance]
514
-        );
515
-    }
516
-
517
-    /**
518
-     * @param QueryBuilderHandler $queryBuilder
519
-     * @param string|null $alias
520
-     *
521
-     * @return Raw
522
-     */
523
-    public function subQuery(QueryBuilderHandler $queryBuilder, ?string $alias = null)
524
-    {
525
-        $sql = '(' . $queryBuilder->getQuery()->getRawSql() . ')';
526
-        if (is_string($alias) && 0 !== mb_strlen($alias)) {
527
-            $sql = $sql . ' as ' . $alias;
528
-        }
529
-
530
-        return $queryBuilder->raw($sql);
531
-    }
532
-
533
-    /**
534
-     * Handles the various insert operations based on the type.
535
-     *
536
-     * @param array<int|string, mixed|mixed[]> $data
537
-     * @param string $type
538
-     *
539
-     * @return int|int[]|mixed|null can return a single row id, array of row ids, null (for failed) or any other value short circuited from event
540
-     */
541
-    private function doInsert(array $data, string $type)
542
-    {
543
-        $eventResult = $this->fireEvents('before-insert');
544
-        if (!is_null($eventResult)) {
545
-            return $eventResult;
546
-        }
547
-
548
-        // If first value is not an array () not a batch insert)
549
-        if (!is_array(current($data))) {
550
-            $queryObject = $this->getQuery($type, $data);
551
-
552
-            list($preparedQuery, $executionTime) = $this->statement($queryObject->getSql(), $queryObject->getBindings());
553
-            $this->dbInstance->get_results($preparedQuery);
554
-
555
-            // Check we have a result.
556
-            $return = 1 === $this->dbInstance->rows_affected ? $this->dbInstance->insert_id : null;
557
-        } else {
558
-            // Its a batch insert
559
-            $return        = [];
560
-            $executionTime = 0;
561
-            foreach ($data as $subData) {
562
-                $queryObject = $this->getQuery($type, $subData);
563
-
564
-                list($preparedQuery, $time) = $this->statement($queryObject->getSql(), $queryObject->getBindings());
565
-                $this->dbInstance->get_results($preparedQuery);
566
-                $executionTime += $time;
567
-
568
-                if (1 === $this->dbInstance->rows_affected) {
569
-                    $return[] = $this->dbInstance->insert_id;
570
-                }
571
-            }
572
-        }
573
-
574
-        $this->fireEvents('after-insert', $return, $executionTime);
575
-
576
-        return $return;
577
-    }
578
-
579
-    /**
580
-     * @param array<int|string, mixed|mixed[]> $data either key=>value array for single or array of arrays for bulk
581
-     *
582
-     * @return int|int[]|mixed|null can return a single row id, array of row ids, null (for failed) or any other value short circuited from event
583
-     */
584
-    public function insert($data)
585
-    {
586
-        return $this->doInsert($data, 'insert');
587
-    }
588
-
589
-    /**
590
-     *
591
-     * @param array<int|string, mixed|mixed[]> $data either key=>value array for single or array of arrays for bulk
592
-     *
593
-     * @return int|int[]|mixed|null can return a single row id, array of row ids, null (for failed) or any other value short circuited from event
594
-     */
595
-    public function insertIgnore($data)
596
-    {
597
-        return $this->doInsert($data, 'insertignore');
598
-    }
599
-
600
-    /**
601
-     *
602
-     * @param array<int|string, mixed|mixed[]> $data either key=>value array for single or array of arrays for bulk
603
-     *
604
-     * @return int|int[]|mixed|null can return a single row id, array of row ids, null (for failed) or any other value short circuited from event
605
-     */
606
-    public function replace($data)
607
-    {
608
-        return $this->doInsert($data, 'replace');
609
-    }
610
-
611
-    /**
612
-     * @param array<string, mixed> $data
613
-     *
614
-     * @return int|null Number of row effected, null for none.
615
-     */
616
-    public function update(array $data): ?int
617
-    {
618
-        $eventResult = $this->fireEvents('before-update');
619
-        if (!is_null($eventResult)) {
620
-            return $eventResult;
621
-        }
622
-        $queryObject                         = $this->getQuery('update', $data);
623
-        $r = $this->statement($queryObject->getSql(), $queryObject->getBindings());
624
-        list($preparedQuery, $executionTime) = $r;
625
-        $this->dbInstance()->get_results($preparedQuery);
626
-        $this->fireEvents('after-update', $queryObject, $executionTime);
627
-
628
-        return 0 !== (int) $this->dbInstance()->rows_affected
629
-            ? (int) $this->dbInstance()->rows_affected
630
-            : null;
631
-    }
632
-
633
-    /**
634
-     * Update or Insert based on the attributes.
635
-     *
636
-     * @param array<string, mixed> $attributes Conditions to check
637
-     * @param array<string, mixed> $values     Values to add/update
638
-     *
639
-     * @return int|int[]|null will return row id(s) for insert and null for success/fail on update
640
-     */
641
-    public function updateOrInsert(array $attributes, array $values = [])
642
-    {
643
-        // Check if existing post exists.
644
-        $query = clone $this;
645
-        foreach ($attributes as $column => $value) {
646
-            $query->where($column, $value);
647
-        }
648
-
649
-        // If we have a result, update it.
650
-        if (null !== $query->first()) {
651
-            foreach ($attributes as $column => $value) {
652
-                $this->where($column, $value);
653
-            }
654
-
655
-            return $this->update($values);
656
-        }
657
-
658
-        // Else insert
659
-        return $this->insert($values);
660
-    }
661
-
662
-    /**
663
-     * @param array<string, mixed> $data
664
-     *
665
-     * @return static
666
-     */
667
-    public function onDuplicateKeyUpdate($data)
668
-    {
669
-        $this->addStatement('onduplicate', $data);
670
-
671
-        return $this;
672
-    }
673
-
674
-    /**
675
-     * @return mixed number of rows effected or shortcircuited response
676
-     */
677
-    public function delete()
678
-    {
679
-        $eventResult = $this->fireEvents('before-delete');
680
-        if (!is_null($eventResult)) {
681
-            return $eventResult;
682
-        }
683
-
684
-        $queryObject = $this->getQuery('delete');
685
-
686
-        list($preparedQuery, $executionTime) = $this->statement($queryObject->getSql(), $queryObject->getBindings());
687
-        $this->dbInstance()->get_results($preparedQuery);
688
-        $this->fireEvents('after-delete', $queryObject, $executionTime);
689
-
690
-        return $this->dbInstance()->rows_affected;
691
-    }
692
-
693
-    /**
694
-     * @param string|Raw ...$tables Single table or array of tables
695
-     *
696
-     * @return static
697
-     *
698
-     * @throws Exception
699
-     */
700
-    public function table(...$tables)
701
-    {
702
-        $instance =  $this->constructCurrentBuilderClass($this->connection);
703
-        $this->setFetchMode($this->getFetchMode(), $this->hydratorConstructorArgs);
704
-        $tables = $this->addTablePrefix($tables, false);
705
-        $instance->addStatement('tables', $tables);
706
-
707
-        return $instance;
708
-    }
709
-
710
-    /**
711
-     * @param string|Raw ...$tables Single table or array of tables
712
-     *
713
-     * @return static
714
-     */
715
-    public function from(...$tables): self
716
-    {
717
-        $tables = $this->addTablePrefix($tables, false);
718
-        $this->addStatement('tables', $tables);
719
-
720
-        return $this;
721
-    }
722
-
723
-    /**
724
-     * @param string|string[]|Raw[]|array<string, string> $fields
725
-     *
726
-     * @return static
727
-     */
728
-    public function select($fields): self
729
-    {
730
-        if (!is_array($fields)) {
731
-            $fields = func_get_args();
732
-        }
733
-
734
-        foreach ($fields as $field => $alias) {
735
-            // If we have a JSON expression
736
-            if ($this->jsonHandler->isJsonSelector($field)) {
737
-                $field = $this->jsonHandler->extractAndUnquoteFromJsonSelector($field);
738
-            }
739
-
740
-            // If no alias passed, but field is for JSON. thrown an exception.
741
-            if (is_numeric($field) && is_string($alias) && $this->jsonHandler->isJsonSelector($alias)) {
742
-                throw new Exception("An alias must be used if you wish to select from JSON Object", 1);
743
-            }
744
-
745
-            // Treat each array as a single table, to retain order added
746
-            $field = is_numeric($field)
747
-                ? $field = $alias // If single colum
748
-                : $field = [$field => $alias]; // Has alias
749
-
750
-            $field = $this->addTablePrefix($field);
751
-            $this->addStatement('selects', $field);
752
-        }
753
-
754
-        return $this;
755
-    }
756
-
757
-    /**
758
-     * @param string|string[]|Raw[]|array<string, string> $fields
759
-     *
760
-     * @return static
761
-     */
762
-    public function selectDistinct($fields)
763
-    {
764
-        $this->select($fields);
765
-        $this->addStatement('distinct', true);
766
-
767
-        return $this;
768
-    }
769
-
770
-    /**
771
-     * @param string|string[] $field either the single field or an array of fields
772
-     *
773
-     * @return static
774
-     */
775
-    public function groupBy($field): self
776
-    {
777
-        $field = $this->addTablePrefix($field);
778
-        $this->addStatement('groupBys', $field);
779
-
780
-        return $this;
781
-    }
782
-
783
-    /**
784
-     * @param string|array<string|int, mixed> $fields
785
-     * @param string          $defaultDirection
786
-     *
787
-     * @return static
788
-     */
789
-    public function orderBy($fields, string $defaultDirection = 'ASC'): self
790
-    {
791
-        if (!is_array($fields)) {
792
-            $fields = [$fields];
793
-        }
794
-
795
-        foreach ($fields as $key => $value) {
796
-            $field = $key;
797
-            $type  = $value;
798
-            if (is_int($key)) {
799
-                $field = $value;
800
-                $type  = $defaultDirection;
801
-            }
802
-
803
-            if ($this->jsonHandler->isJsonSelector($field)) {
804
-                $field = $this->jsonHandler->extractAndUnquoteFromJsonSelector($field);
805
-            }
806
-
807
-            if (!$field instanceof Raw) {
808
-                $field = $this->addTablePrefix($field);
809
-            }
810
-            $this->statements['orderBys'][] = compact('field', 'type');
811
-        }
812
-
813
-        return $this;
814
-    }
815
-
816
-    /**
817
-     * @param string|Raw $key The database column which holds the JSON value
818
-     * @param string|Raw|string[] $jsonKey The json key/index to search
819
-     * @param string $defaultDirection
820
-     * @return static
821
-     */
822
-    public function orderByJson($key, $jsonKey, string $defaultDirection = 'ASC'): self
823
-    {
824
-        $key = $this->jsonHandler->jsonExpressionFactory()->extractAndUnquote($key, $jsonKey);
825
-        return $this->orderBy($key, $defaultDirection);
826
-    }
827
-
828
-    /**
829
-     * @param int $limit
830
-     *
831
-     * @return static
832
-     */
833
-    public function limit(int $limit): self
834
-    {
835
-        $this->statements['limit'] = $limit;
836
-
837
-        return $this;
838
-    }
839
-
840
-    /**
841
-     * @param int $offset
842
-     *
843
-     * @return static
844
-     */
845
-    public function offset(int $offset): self
846
-    {
847
-        $this->statements['offset'] = $offset;
848
-
849
-        return $this;
850
-    }
851
-
852
-    /**
853
-     * @param string|string[]|Raw|Raw[]       $key
854
-     * @param string $operator
855
-     * @param mixed $value
856
-     * @param string $joiner
857
-     *
858
-     * @return static
859
-     */
860
-    public function having($key, string $operator, $value, string $joiner = 'AND')
861
-    {
862
-        $key                           = $this->addTablePrefix($key);
863
-        $this->statements['havings'][] = compact('key', 'operator', 'value', 'joiner');
864
-
865
-        return $this;
866
-    }
867
-
868
-    /**
869
-     * @param string|string[]|Raw|Raw[]       $key
870
-     * @param string $operator
871
-     * @param mixed $value
872
-     *
873
-     * @return static
874
-     */
875
-    public function orHaving($key, $operator, $value)
876
-    {
877
-        return $this->having($key, $operator, $value, 'OR');
878
-    }
879
-
880
-    /**
881
-     * @param string|Raw $key
882
-     * @param string|mixed|null $operator Can be used as value, if 3rd arg not passed
883
-     * @param mixed|null $value
884
-     *
885
-     * @return static
886
-     */
887
-    public function where($key, $operator = null, $value = null): self
888
-    {
889
-        // If two params are given then assume operator is =
890
-        if (2 === func_num_args()) {
891
-            $value    = $operator;
892
-            $operator = '=';
893
-        }
894
-
895
-        return $this->whereHandler($key, $operator, $value);
896
-    }
897
-
898
-    /**
899
-     * @param string|Raw|\Closure(QueryBuilderHandler):void $key
900
-     * @param string|mixed|null $operator Can be used as value, if 3rd arg not passed
901
-     * @param mixed|null $value
902
-     *
903
-     * @return static
904
-     */
905
-    public function orWhere($key, $operator = null, $value = null): self
906
-    {
907
-        // If two params are given then assume operator is =
908
-        if (2 === func_num_args()) {
909
-            $value    = $operator;
910
-            $operator = '=';
911
-        }
912
-
913
-        return $this->whereHandler($key, $operator, $value, 'OR');
914
-    }
915
-
916
-    /**
917
-     * @param string|Raw $key
918
-     * @param string|mixed|null $operator Can be used as value, if 3rd arg not passed
919
-     * @param mixed|null $value
920
-     *
921
-     * @return static
922
-     */
923
-    public function whereNot($key, $operator = null, $value = null): self
924
-    {
925
-        // If two params are given then assume operator is =
926
-        if (2 === func_num_args()) {
927
-            $value    = $operator;
928
-            $operator = '=';
929
-        }
930
-
931
-        return $this->whereHandler($key, $operator, $value, 'AND NOT');
932
-    }
933
-
934
-    /**
935
-     * @param string|Raw $key
936
-     * @param string|mixed|null $operator Can be used as value, if 3rd arg not passed
937
-     * @param mixed|null $value
938
-     *
939
-     * @return static
940
-     */
941
-    public function orWhereNot($key, $operator = null, $value = null)
942
-    {
943
-        // If two params are given then assume operator is =
944
-        if (2 === func_num_args()) {
945
-            $value    = $operator;
946
-            $operator = '=';
947
-        }
948
-
949
-        return $this->whereHandler($key, $operator, $value, 'OR NOT');
950
-    }
951
-
952
-    /**
953
-     * @param string|Raw $key
954
-     * @param mixed[]|string|Raw $values
955
-     *
956
-     * @return static
957
-     */
958
-    public function whereIn($key, $values): self
959
-    {
960
-        return $this->whereHandler($key, 'IN', $values, 'AND');
961
-    }
962
-
963
-    /**
964
-     * @param string|Raw $key
965
-     * @param mixed[]|string|Raw $values
966
-     *
967
-     * @return static
968
-     */
969
-    public function whereNotIn($key, $values): self
970
-    {
971
-        return $this->whereHandler($key, 'NOT IN', $values, 'AND');
972
-    }
973
-
974
-    /**
975
-     * @param string|Raw $key
976
-     * @param mixed[]|string|Raw $values
977
-     *
978
-     * @return static
979
-     */
980
-    public function orWhereIn($key, $values): self
981
-    {
982
-        return $this->whereHandler($key, 'IN', $values, 'OR');
983
-    }
984
-
985
-    /**
986
-     * @param string|Raw $key
987
-     * @param mixed[]|string|Raw $values
988
-     *
989
-     * @return static
990
-     */
991
-    public function orWhereNotIn($key, $values): self
992
-    {
993
-        return $this->whereHandler($key, 'NOT IN', $values, 'OR');
994
-    }
995
-
996
-    /**
997
-     * @param string|Raw $key
998
-     * @param mixed $valueFrom
999
-     * @param mixed $valueTo
1000
-     *
1001
-     * @return static
1002
-     */
1003
-    public function whereBetween($key, $valueFrom, $valueTo): self
1004
-    {
1005
-        return $this->whereHandler($key, 'BETWEEN', [$valueFrom, $valueTo], 'AND');
1006
-    }
1007
-
1008
-    /**
1009
-     * @param string|Raw $key
1010
-     * @param mixed $valueFrom
1011
-     * @param mixed $valueTo
1012
-     *
1013
-     * @return static
1014
-     */
1015
-    public function orWhereBetween($key, $valueFrom, $valueTo): self
1016
-    {
1017
-        return $this->whereHandler($key, 'BETWEEN', [$valueFrom, $valueTo], 'OR');
1018
-    }
1019
-
1020
-    /**
1021
-     * Handles all function call based where conditions
1022
-     *
1023
-     * @param string|Raw $key
1024
-     * @param string $function
1025
-     * @param string|mixed|null $operator Can be used as value, if 3rd arg not passed
1026
-     * @param mixed|null $value
1027
-     * @return static
1028
-     */
1029
-    protected function whereFunctionCallHandler($key, $function, $operator, $value): self
1030
-    {
1031
-        $key = \sprintf('%s(%s)', $function, $this->addTablePrefix($key));
1032
-        return $this->where($key, $operator, $value);
1033
-    }
1034
-
1035
-    /**
1036
-     * @param string|Raw $key
1037
-     * @param string|mixed|null $operator Can be used as value, if 3rd arg not passed
1038
-     * @param mixed|null $value
1039
-     * @return self
1040
-     */
1041
-    public function whereMonth($key, $operator = null, $value = null): self
1042
-    {
1043
-        // If two params are given then assume operator is =
1044
-        if (2 === func_num_args()) {
1045
-            $value    = $operator;
1046
-            $operator = '=';
1047
-        }
1048
-        return $this->whereFunctionCallHandler($key, 'MONTH', $operator, $value);
1049
-    }
1050
-
1051
-    /**
1052
-     * @param string|Raw $key
1053
-     * @param string|mixed|null $operator Can be used as value, if 3rd arg not passed
1054
-     * @param mixed|null $value
1055
-     * @return self
1056
-     */
1057
-    public function whereDay($key, $operator = null, $value = null): self
1058
-    {
1059
-        // If two params are given then assume operator is =
1060
-        if (2 === func_num_args()) {
1061
-            $value    = $operator;
1062
-            $operator = '=';
1063
-        }
1064
-        return $this->whereFunctionCallHandler($key, 'DAY', $operator, $value);
1065
-    }
1066
-
1067
-    /**
1068
-     * @param string|Raw $key
1069
-     * @param string|mixed|null $operator Can be used as value, if 3rd arg not passed
1070
-     * @param mixed|null $value
1071
-     * @return self
1072
-     */
1073
-    public function whereYear($key, $operator = null, $value = null): self
1074
-    {
1075
-        // If two params are given then assume operator is =
1076
-        if (2 === func_num_args()) {
1077
-            $value    = $operator;
1078
-            $operator = '=';
1079
-        }
1080
-        return $this->whereFunctionCallHandler($key, 'YEAR', $operator, $value);
1081
-    }
1082
-
1083
-    /**
1084
-     * @param string|Raw $key
1085
-     * @param string|mixed|null $operator Can be used as value, if 3rd arg not passed
1086
-     * @param mixed|null $value
1087
-     * @return self
1088
-     */
1089
-    public function whereDate($key, $operator = null, $value = null): self
1090
-    {
1091
-        // If two params are given then assume operator is =
1092
-        if (2 === func_num_args()) {
1093
-            $value    = $operator;
1094
-            $operator = '=';
1095
-        }
1096
-        return $this->whereFunctionCallHandler($key, 'DATE', $operator, $value);
1097
-    }
1098
-
1099
-    /**
1100
-     * @param string|Raw $key
1101
-     *
1102
-     * @return static
1103
-     */
1104
-    public function whereNull($key): self
1105
-    {
1106
-        return $this->whereNullHandler($key);
1107
-    }
1108
-
1109
-    /**
1110
-     * @param string|Raw $key
1111
-     *
1112
-     * @return static
1113
-     */
1114
-    public function whereNotNull($key): self
1115
-    {
1116
-        return $this->whereNullHandler($key, 'NOT');
1117
-    }
1118
-
1119
-    /**
1120
-     * @param string|Raw $key
1121
-     *
1122
-     * @return static
1123
-     */
1124
-    public function orWhereNull($key): self
1125
-    {
1126
-        return $this->whereNullHandler($key, '', 'or');
1127
-    }
1128
-
1129
-    /**
1130
-     * @param string|Raw $key
1131
-     *
1132
-     * @return static
1133
-     */
1134
-    public function orWhereNotNull($key): self
1135
-    {
1136
-        return $this->whereNullHandler($key, 'NOT', 'or');
1137
-    }
1138
-
1139
-    /**
1140
-     * @param string|Raw $key
1141
-     * @param string $prefix
1142
-     * @param string $operator
1143
-     *
1144
-     * @return static
1145
-     */
1146
-    protected function whereNullHandler($key, string $prefix = '', $operator = ''): self
1147
-    {
1148
-        $prefix = 0 === mb_strlen($prefix) ? '' : " {$prefix}";
1149
-
1150
-        if ($key instanceof Raw) {
1151
-            $key = $this->adapterInstance->parseRaw($key);
1152
-        }
1153
-
1154
-        $key = $this->addTablePrefix($key);
1155
-        if ($key instanceof Closure) {
1156
-            throw new Exception('Key used for whereNull condition must be a string or raw exrpession.', 1);
1157
-        }
1158
-
1159
-        return $this->{$operator . 'Where'}($this->raw("{$key} IS{$prefix} NULL"));
1160
-    }
1161
-
1162
-
1163
-    /**
1164
-     * Runs a transaction
1165
-     *
1166
-     * @param \Closure(Transaction):void $callback
1167
-     *
1168
-     * @return static
1169
-     */
1170
-    public function transaction(Closure $callback): self
1171
-    {
1172
-        try {
1173
-            // Begin the transaction
1174
-            $this->dbInstance->query('START TRANSACTION');
1175
-
1176
-            // Get the Transaction class
1177
-            $transaction = $this->container->build(Transaction::class, [$this->connection]);
1178
-
1179
-            $this->handleTransactionCall($callback, $transaction);
1180
-
1181
-            // If no errors have been thrown or the transaction wasn't completed within
1182
-            $this->dbInstance->query('COMMIT');
1183
-
1184
-            return $this;
1185
-        } catch (TransactionHaltException $e) {
1186
-            // Commit or rollback behavior has been handled in the closure, so exit
1187
-            return $this;
1188
-        } catch (\Exception $e) {
1189
-            // something happened, rollback changes
1190
-            $this->dbInstance->query('ROLLBACK');
1191
-
1192
-            return $this;
1193
-        }
1194
-    }
1195
-
1196
-    /**
1197
-     * Handles the transaction call.
1198
-     * Catches any WPDB Errors (printed)
1199
-     *
1200
-     * @param Closure    $callback
1201
-     * @param Transaction $transaction
1202
-     *
1203
-     * @return void
1204
-     * @throws Exception
1205
-     */
1206
-    protected function handleTransactionCall(Closure $callback, Transaction $transaction): void
1207
-    {
1208
-        try {
1209
-            ob_start();
1210
-            $callback($transaction);
1211
-            $output = ob_get_clean() ?: '';
1212
-        } catch (Throwable $th) {
1213
-            ob_end_clean();
1214
-            throw $th;
1215
-        }
1216
-
1217
-        // If we caught an error, throw an exception.
1218
-        if (0 !== mb_strlen($output)) {
1219
-            throw new Exception($output);
1220
-        }
1221
-    }
1222
-
1223
-    /*************************************************************************/
1224
-    /*************************************************************************/
1225
-    /*************************************************************************/
1226
-    /**                              JOIN JOIN                              **/
1227
-    /**                                 JOIN                                **/
1228
-    /**                              JOIN JOIN                              **/
1229
-    /*************************************************************************/
1230
-    /*************************************************************************/
1231
-    /*************************************************************************/
1232
-
1233
-    /**
1234
-     * @param string|Raw $table
1235
-     * @param string|Raw|Closure $key
1236
-     * @param string|null $operator
1237
-     * @param mixed $value
1238
-     * @param string $type
1239
-     *
1240
-     * @return static
1241
-     */
1242
-    public function join($table, $key, ?string $operator = null, $value = null, $type = 'inner')
1243
-    {
1244
-        // Potentially cast key from JSON
1245
-        if ($this->jsonHandler->isJsonSelector($key)) {
1246
-            /** @var string $key */
1247
-            $key = $this->jsonHandler->extractAndUnquoteFromJsonSelector($key); /** @phpstan-ignore-line */
1248
-        }
1249
-
1250
-        // Potentially cast value from json
1251
-        if ($this->jsonHandler->isJsonSelector($value)) {
1252
-            /** @var string $value */
1253
-            $value = $this->jsonHandler->extractAndUnquoteFromJsonSelector($value);
1254
-        }
1255
-
1256
-        if (!$key instanceof Closure) {
1257
-            $key = function ($joinBuilder) use ($key, $operator, $value) {
1258
-                $joinBuilder->on($key, $operator, $value);
1259
-            };
1260
-        }
1261
-
1262
-        // Build a new JoinBuilder class, keep it by reference so any changes made
1263
-        // in the closure should reflect here
1264
-        $joinBuilder = $this->container->build(JoinBuilder::class, [$this->connection]);
1265
-        $joinBuilder = &$joinBuilder;
1266
-        // Call the closure with our new joinBuilder object
1267
-        $key($joinBuilder);
1268
-        $table = $this->addTablePrefix($table, false);
1269
-        // Get the criteria only query from the joinBuilder object
1270
-        $this->statements['joins'][] = compact('type', 'table', 'joinBuilder');
1271
-        return $this;
1272
-    }
1273
-
1274
-    /**
1275
-     * @param string|Raw $table
1276
-     * @param string|Raw|Closure $key
1277
-     * @param string|null $operator
1278
-     * @param mixed $value
1279
-     *
1280
-     * @return static
1281
-     */
1282
-    public function leftJoin($table, $key, $operator = null, $value = null)
1283
-    {
1284
-        return $this->join($table, $key, $operator, $value, 'left');
1285
-    }
1286
-
1287
-    /**
1288
-     * @param string|Raw $table
1289
-     * @param string|Raw|Closure $key
1290
-     * @param string|null $operator
1291
-     * @param mixed $value
1292
-     *
1293
-     * @return static
1294
-     */
1295
-    public function rightJoin($table, $key, $operator = null, $value = null)
1296
-    {
1297
-        return $this->join($table, $key, $operator, $value, 'right');
1298
-    }
1299
-
1300
-    /**
1301
-     * @param string|Raw $table
1302
-     * @param string|Raw|Closure $key
1303
-     * @param string|null $operator
1304
-     * @param mixed $value
1305
-     *
1306
-     * @return static
1307
-     */
1308
-    public function innerJoin($table, $key, $operator = null, $value = null)
1309
-    {
1310
-        return $this->join($table, $key, $operator, $value, 'inner');
1311
-    }
1312
-
1313
-    /**
1314
-     * @param string|Raw $table
1315
-     * @param string|Raw|Closure $key
1316
-     * @param string|null $operator
1317
-     * @param mixed $value
1318
-     *
1319
-     * @return static
1320
-     */
1321
-    public function crossJoin($table, $key, $operator = null, $value = null)
1322
-    {
1323
-        return $this->join($table, $key, $operator, $value, 'cross');
1324
-    }
1325
-
1326
-    /**
1327
-     * @param string|Raw $table
1328
-     * @param string|Raw|Closure $key
1329
-     * @param string|null $operator
1330
-     * @param mixed $value
1331
-     *
1332
-     * @return static
1333
-     */
1334
-    public function outerJoin($table, $key, $operator = null, $value = null)
1335
-    {
1336
-        return $this->join($table, $key, $operator, $value, 'full outer');
1337
-    }
1338
-
1339
-    /**
1340
-     * Shortcut to join 2 tables on the same key name with equals
1341
-     *
1342
-     * @param string $table
1343
-     * @param string $key
1344
-     * @param string $type
1345
-     * @return self
1346
-     * @throws Exception If base table is set as more than 1 or 0
1347
-     */
1348
-    public function joinUsing(string $table, string $key, string $type = 'INNER'): self
1349
-    {
1350
-        if (!array_key_exists('tables', $this->statements) || count($this->statements['tables']) !== 1) {
1351
-            throw new Exception("JoinUsing can only be used with a single table set as the base of the query", 1);
1352
-        }
1353
-        $baseTable = end($this->statements['tables']);
1354
-
1355
-        // Potentialy cast key from JSON
1356
-        if ($this->jsonHandler->isJsonSelector($key)) {
1357
-            $key = $this->jsonHandler->extractAndUnquoteFromJsonSelector($key);
1358
-        }
1359
-
1360
-        $remoteKey = $table = $this->addTablePrefix("{$table}.{$key}", true);
1361
-        $localKey = $table = $this->addTablePrefix("{$baseTable}.{$key}", true);
1362
-        return $this->join($table, $remoteKey, '=', $localKey, $type);
1363
-    }
1364
-
1365
-    /**
1366
-     * Add a raw query
1367
-     *
1368
-     * @param string|Raw $value
1369
-     * @param mixed|mixed[] $bindings
1370
-     *
1371
-     * @return Raw
1372
-     */
1373
-    public function raw($value, $bindings = []): Raw
1374
-    {
1375
-        return new Raw($value, $bindings);
1376
-    }
1377
-
1378
-    /**
1379
-     * Return wpdb instance
1380
-     *
1381
-     * @return wpdb
1382
-     */
1383
-    public function dbInstance(): wpdb
1384
-    {
1385
-        return $this->dbInstance;
1386
-    }
1387
-
1388
-    /**
1389
-     * @param Connection $connection
1390
-     *
1391
-     * @return static
1392
-     */
1393
-    public function setConnection(Connection $connection): self
1394
-    {
1395
-        $this->connection = $connection;
1396
-
1397
-        return $this;
1398
-    }
1399
-
1400
-    /**
1401
-     * @return Connection
1402
-     */
1403
-    public function getConnection(): Connection
1404
-    {
1405
-        return $this->connection;
1406
-    }
1407
-
1408
-    /**
1409
-     * @param string|Raw|Closure $key
1410
-     * @param string|null      $operator
1411
-     * @param mixed|null       $value
1412
-     * @param string $joiner
1413
-     *
1414
-     * @return static
1415
-     */
1416
-    protected function whereHandler($key, $operator = null, $value = null, $joiner = 'AND')
1417
-    {
1418
-        $key = $this->addTablePrefix($key);
1419
-        if ($key instanceof Raw) {
1420
-            $key = $this->adapterInstance->parseRaw($key);
1421
-        }
1422
-
1423
-        if ($this->jsonHandler->isJsonSelector($key)) {
1424
-            $key = $this->jsonHandler->extractAndUnquoteFromJsonSelector($key);
1425
-        }
1426
-
1427
-        $this->statements['wheres'][] = compact('key', 'operator', 'value', 'joiner');
1428
-        return $this;
1429
-    }
1430
-
1431
-
1432
-
1433
-    /**
1434
-     * @param string $key
1435
-     * @param mixed|mixed[]|bool $value
1436
-     *
1437
-     * @return void
1438
-     */
1439
-    protected function addStatement($key, $value)
1440
-    {
1441
-        if (!is_array($value)) {
1442
-            $value = [$value];
1443
-        }
1444
-
1445
-        if (!array_key_exists($key, $this->statements)) {
1446
-            $this->statements[$key] = $value;
1447
-        } else {
1448
-            $this->statements[$key] = array_merge($this->statements[$key], $value);
1449
-        }
1450
-    }
1451
-
1452
-    /**
1453
-     * @param string $event
1454
-     * @param string|Raw $table
1455
-     *
1456
-     * @return callable|null
1457
-     */
1458
-    public function getEvent(string $event, $table = ':any'): ?callable
1459
-    {
1460
-        return $this->connection->getEventHandler()->getEvent($event, $table);
1461
-    }
1462
-
1463
-    /**
1464
-     * @param string $event
1465
-     * @param string|Raw $table
1466
-     * @param Closure $action
1467
-     *
1468
-     * @return void
1469
-     */
1470
-    public function registerEvent($event, $table, Closure $action): void
1471
-    {
1472
-        $table = $table ?: ':any';
1473
-
1474
-        if (':any' != $table) {
1475
-            $table = $this->addTablePrefix($table, false);
1476
-        }
1477
-
1478
-        $this->connection->getEventHandler()->registerEvent($event, $table, $action);
1479
-    }
1480
-
1481
-    /**
1482
-     * @param string $event
1483
-     * @param string|Raw $table
1484
-     *
1485
-     * @return void
1486
-     */
1487
-    public function removeEvent(string $event, $table = ':any')
1488
-    {
1489
-        if (':any' != $table) {
1490
-            $table = $this->addTablePrefix($table, false);
1491
-        }
1492
-
1493
-        $this->connection->getEventHandler()->removeEvent($event, $table);
1494
-    }
1495
-
1496
-    /**
1497
-     * @param string $event
1498
-     *
1499
-     * @return mixed
1500
-     */
1501
-    public function fireEvents(string $event)
1502
-    {
1503
-        $params = func_get_args(); // @todo Replace this with an easier to read alteratnive
1504
-        array_unshift($params, $this);
1505
-
1506
-        return call_user_func_array([$this->connection->getEventHandler(), 'fireEvents'], $params);
1507
-    }
1508
-
1509
-    /**
1510
-     * @return array<string, mixed[]>
1511
-     */
1512
-    public function getStatements()
1513
-    {
1514
-        return $this->statements;
1515
-    }
1516
-
1517
-    /**
1518
-     * @return string will return WPDB Fetch mode
1519
-     */
1520
-    public function getFetchMode()
1521
-    {
1522
-        return null !== $this->fetchMode
1523
-            ? $this->fetchMode
1524
-            : \OBJECT;
1525
-    }
1526
-
1527
-    /**
1528
-     * Returns an NEW instance of the JSON builder populated with the same connection and hydrator details.
1529
-     *
1530
-     * @return JsonQueryBuilder
1531
-     */
1532
-    public function jsonBuilder(): JsonQueryBuilder
1533
-    {
1534
-        return new JsonQueryBuilder($this->getConnection(), $this->getFetchMode(), $this->hydratorConstructorArgs);
1535
-    }
27
+	/**
28
+	 * @method add
29
+	 */
30
+	use TablePrefixer;
31
+
32
+	/**
33
+	 * @var \Viocon\Container
34
+	 */
35
+	protected $container;
36
+
37
+	/**
38
+	 * @var Connection
39
+	 */
40
+	protected $connection;
41
+
42
+	/**
43
+	 * @var array<string, mixed[]|mixed>
44
+	 */
45
+	protected $statements = [];
46
+
47
+	/**
48
+	 * @var wpdb
49
+	 */
50
+	protected $dbInstance;
51
+
52
+	/**
53
+	 * @var string|string[]|null
54
+	 */
55
+	protected $sqlStatement = null;
56
+
57
+	/**
58
+	 * @var string|null
59
+	 */
60
+	protected $tablePrefix = null;
61
+
62
+	/**
63
+	 * @var WPDBAdapter
64
+	 */
65
+	protected $adapterInstance;
66
+
67
+	/**
68
+	 * The mode to return results as.
69
+	 * Accepts WPDB constants or class names.
70
+	 *
71
+	 * @var string
72
+	 */
73
+	protected $fetchMode;
74
+
75
+	/**
76
+	 * Custom args used to construct models for hydrator
77
+	 *
78
+	 * @var array<int, mixed>|null
79
+	 */
80
+	protected $hydratorConstructorArgs;
81
+
82
+	/**
83
+	 * Handler for Json Selectors
84
+	 *
85
+	 * @var JsonHandler
86
+	 */
87
+	protected $jsonHandler;
88
+
89
+	/**
90
+	 * @param \Pixie\Connection|null $connection
91
+	 * @param string $fetchMode
92
+	 * @param mixed[] $hydratorConstructorArgs
93
+	 *
94
+	 * @throws Exception if no connection passed and not previously established
95
+	 */
96
+	final public function __construct(
97
+		Connection $connection = null,
98
+		string $fetchMode = \OBJECT,
99
+		?array $hydratorConstructorArgs = null
100
+	) {
101
+		if (is_null($connection)) {
102
+			// throws if connection not already established.
103
+			$connection = Connection::getStoredConnection();
104
+		}
105
+
106
+		// Set all dependencies from connection.
107
+		$this->connection = $connection;
108
+		$this->container  = $this->connection->getContainer();
109
+		$this->dbInstance = $this->connection->getDbInstance();
110
+		$this->setAdapterConfig($this->connection->getAdapterConfig());
111
+
112
+		// Set up optional hydration details.
113
+		$this->setFetchMode($fetchMode);
114
+		$this->hydratorConstructorArgs = $hydratorConstructorArgs;
115
+
116
+		// Query builder adapter instance
117
+		$this->adapterInstance = $this->container->build(
118
+			WPDBAdapter::class,
119
+			[$this->connection]
120
+		);
121
+
122
+		// Setup JSON Selector handler.
123
+		$this->jsonHandler = new JsonHandler($connection);
124
+	}
125
+
126
+	/**
127
+	 * Sets the config for WPDB
128
+	 *
129
+	 * @param array<string, mixed> $adapterConfig
130
+	 *
131
+	 * @return void
132
+	 */
133
+	protected function setAdapterConfig(array $adapterConfig): void
134
+	{
135
+		if (isset($adapterConfig[Connection::PREFIX])) {
136
+			$this->tablePrefix = $adapterConfig[Connection::PREFIX];
137
+		}
138
+	}
139
+
140
+	/**
141
+	 * Fetch query results as object of specified type
142
+	 *
143
+	 * @param string $className
144
+	 * @param array<int, mixed> $constructorArgs
145
+	 * @return static
146
+	 */
147
+	public function asObject($className, $constructorArgs = array()): self
148
+	{
149
+		return $this->setFetchMode($className, $constructorArgs);
150
+	}
151
+
152
+	/**
153
+	 * Set the fetch mode
154
+	 *
155
+	 * @param string $mode
156
+	 * @param array<int, mixed>|null $constructorArgs
157
+	 *
158
+	 * @return static
159
+	 */
160
+	public function setFetchMode(string $mode, ?array $constructorArgs = null): self
161
+	{
162
+		$this->fetchMode               = $mode;
163
+		$this->hydratorConstructorArgs = $constructorArgs;
164
+
165
+		return $this;
166
+	}
167
+
168
+	/**
169
+	 * @param Connection|null $connection
170
+	 *
171
+	 * @return static
172
+	 *
173
+	 * @throws Exception
174
+	 */
175
+	public function newQuery(Connection $connection = null): self
176
+	{
177
+		if (is_null($connection)) {
178
+			$connection = $this->connection;
179
+		}
180
+
181
+		$newQuery = $this->constructCurrentBuilderClass($connection);
182
+		$newQuery->setFetchMode($this->getFetchMode(), $this->hydratorConstructorArgs);
183
+
184
+		return $newQuery;
185
+	}
186
+
187
+	/**
188
+	 * Returns a new instance of the current, with the passed connection.
189
+	 *
190
+	 * @param \Pixie\Connection $connection
191
+	 *
192
+	 * @return static
193
+	 */
194
+	protected function constructCurrentBuilderClass(Connection $connection): self
195
+	{
196
+		return new static($connection);
197
+	}
198
+
199
+	/**
200
+	 * Interpolates a query
201
+	 *
202
+	 * @param string $query
203
+	 * @param array<mixed> $bindings
204
+	 * @return string
205
+	 */
206
+	public function interpolateQuery(string $query, array $bindings = []): string
207
+	{
208
+		return $this->adapterInstance->interpolateQuery($query, $bindings);
209
+	}
210
+
211
+	/**
212
+	 * @param string           $sql
213
+	 * @param array<int,mixed> $bindings
214
+	 *
215
+	 * @return static
216
+	 */
217
+	public function query($sql, $bindings = []): self
218
+	{
219
+		list($this->sqlStatement) = $this->statement($sql, $bindings);
220
+
221
+		return $this;
222
+	}
223
+
224
+	/**
225
+	 * @param string           $sql
226
+	 * @param array<int,mixed> $bindings
227
+	 *
228
+	 * @return array{0:string, 1:float}
229
+	 */
230
+	public function statement(string $sql, $bindings = []): array
231
+	{
232
+		$start        = microtime(true);
233
+		$sqlStatement = empty($bindings) ? $sql : $this->interpolateQuery($sql, $bindings);
234
+
235
+		if (!is_string($sqlStatement)) {
236
+			throw new Exception('Could not interpolate query', 1);
237
+		}
238
+
239
+		return [$sqlStatement, microtime(true) - $start];
240
+	}
241
+
242
+	/**
243
+	 * Get all rows
244
+	 *
245
+	 * @return array<mixed,mixed>|null
246
+	 *
247
+	 * @throws Exception
248
+	 */
249
+	public function get()
250
+	{
251
+		$eventResult = $this->fireEvents('before-select');
252
+		if (!is_null($eventResult)) {
253
+			return $eventResult;
254
+		}
255
+		$executionTime = 0;
256
+		if (is_null($this->sqlStatement)) {
257
+			$queryObject = $this->getQuery('select');
258
+			$statement   = $this->statement(
259
+				$queryObject->getSql(),
260
+				$queryObject->getBindings()
261
+			);
262
+
263
+			$this->sqlStatement = $statement[0];
264
+			$executionTime      = $statement[1];
265
+		}
266
+
267
+		$start  = microtime(true);
268
+		$result = $this->dbInstance()->get_results(
269
+			is_array($this->sqlStatement) ? (end($this->sqlStatement) ?: '') : $this->sqlStatement,
270
+			// If we are using the hydrator, return as OBJECT and let the hydrator map the correct model.
271
+			$this->useHydrator() ? OBJECT : $this->getFetchMode()
272
+		);
273
+		$executionTime += microtime(true) - $start;
274
+		$this->sqlStatement = null;
275
+
276
+		// Ensure we have an array of results.
277
+		if (!is_array($result) && null !== $result) {
278
+			$result = [$result];
279
+		}
280
+
281
+		// Maybe hydrate the results.
282
+		if (null !== $result && $this->useHydrator()) {
283
+			$result = $this->getHydrator()->fromMany($result);
284
+		}
285
+
286
+		$this->fireEvents('after-select', $result, $executionTime);
287
+
288
+		return $result;
289
+	}
290
+
291
+	/**
292
+	 * Returns a populated instance of the Hydrator.
293
+	 *
294
+	 * @return Hydrator
295
+	 */
296
+	protected function getHydrator(): Hydrator /* @phpstan-ignore-line */
297
+	{
298
+		$hydrator = new Hydrator($this->getFetchMode(), $this->hydratorConstructorArgs ?? []); /* @phpstan-ignore-line */
299
+
300
+		return $hydrator;
301
+	}
302
+
303
+	/**
304
+	 * Checks if the results should be mapped via the hydrator
305
+	 *
306
+	 * @return bool
307
+	 */
308
+	protected function useHydrator(): bool
309
+	{
310
+		return !in_array($this->getFetchMode(), [\ARRAY_A, \ARRAY_N, \OBJECT, \OBJECT_K]);
311
+	}
312
+
313
+	/**
314
+	 * Find all matching a simple where condition.
315
+	 *
316
+	 * Shortcut of ->where('key','=','value')->limit(1)->get();
317
+	 *
318
+	 * @return \stdClass\array<mixed,mixed>|object|null Can return any object using hydrator
319
+	 */
320
+	public function first()
321
+	{
322
+		$this->limit(1);
323
+		$result = $this->get();
324
+
325
+		return empty($result) ? null : $result[0];
326
+	}
327
+
328
+	/**
329
+	 * Find all matching a simple where condition.
330
+	 *
331
+	 * Shortcut of ->where('key','=','value')->get();
332
+	 *
333
+	 * @param string $fieldName
334
+	 * @param mixed $value
335
+	 *
336
+	 * @return array<mixed,mixed>|null Can return any object using hydrator
337
+	 */
338
+	public function findAll($fieldName, $value)
339
+	{
340
+		$this->where($fieldName, '=', $value);
341
+
342
+		return $this->get();
343
+	}
344
+
345
+	/**
346
+	 * @param string $fieldName
347
+	 * @param mixed $value
348
+	 *
349
+	 * @return \stdClass\array<mixed,mixed>|object|null Can return any object using hydrator
350
+	 */
351
+	public function find($value, $fieldName = 'id')
352
+	{
353
+		$this->where($fieldName, '=', $value);
354
+
355
+		return $this->first();
356
+	}
357
+
358
+	/**
359
+	 * @param string $fieldName
360
+	 * @param mixed $value
361
+	 *
362
+	 * @return \stdClass\array<mixed,mixed>|object Can return any object using hydrator
363
+	 * @throws Exception If fails to find
364
+	 */
365
+	public function findOrFail($value, $fieldName = 'id')
366
+	{
367
+		$result = $this->find($value, $fieldName);
368
+		if (null === $result) {
369
+			throw new Exception("Failed to find {$fieldName}={$value}", 1);
370
+		}
371
+		return $result;
372
+	}
373
+
374
+	/**
375
+	 * Used to handle all aggregation method.
376
+	 *
377
+	 * @see Taken from the pecee-pixie library - https://github.com/skipperbent/pecee-pixie/
378
+	 *
379
+	 * @param string $type
380
+	 * @param string|Raw $field
381
+	 *
382
+	 * @return float
383
+	 */
384
+	protected function aggregate(string $type, $field = '*'): float
385
+	{
386
+		// Parse a raw expression.
387
+		if ($field instanceof Raw) {
388
+			$field = $this->adapterInstance->parseRaw($field);
389
+		}
390
+
391
+		// Potentialy cast field from JSON
392
+		if ($this->jsonHandler->isJsonSelector($field)) {
393
+			$field = $this->jsonHandler->extractAndUnquoteFromJsonSelector($field);
394
+		}
395
+
396
+		// Verify that field exists
397
+		if ('*' !== $field && true === isset($this->statements['selects']) && false === \in_array($field, $this->statements['selects'], true)) {
398
+			throw new \Exception(sprintf('Failed %s query - the column %s hasn\'t been selected in the query.', $type, $field));
399
+		}
400
+
401
+
402
+		if (false === isset($this->statements['tables'])) {
403
+			throw new Exception('No table selected');
404
+		}
405
+
406
+		$count = $this
407
+			->table($this->subQuery($this, 'count'))
408
+			->select([$this->raw(sprintf('%s(%s) AS field', strtoupper($type), $field))])
409
+			->first();
410
+
411
+		return true === isset($count->field) ? (float)$count->field : 0;
412
+	}
413
+
414
+	/**
415
+	 * Get count of all the rows for the current query
416
+	 *
417
+	 * @see Taken from the pecee-pixie library - https://github.com/skipperbent/pecee-pixie/
418
+	 *
419
+	 * @param string|Raw $field
420
+	 *
421
+	 * @return int
422
+	 *
423
+	 * @throws Exception
424
+	 */
425
+	public function count($field = '*'): int
426
+	{
427
+		return (int)$this->aggregate('count', $field);
428
+	}
429
+
430
+	/**
431
+	 * Get the sum for a field in the current query
432
+	 *
433
+	 * @see Taken from the pecee-pixie library - https://github.com/skipperbent/pecee-pixie/
434
+	 *
435
+	 * @param string|Raw $field
436
+	 *
437
+	 * @return float
438
+	 *
439
+	 * @throws Exception
440
+	 */
441
+	public function sum($field): float
442
+	{
443
+		return $this->aggregate('sum', $field);
444
+	}
445
+
446
+	/**
447
+	 * Get the average for a field in the current query
448
+	 *
449
+	 * @see Taken from the pecee-pixie library - https://github.com/skipperbent/pecee-pixie/
450
+	 *
451
+	 * @param string|Raw $field
452
+	 *
453
+	 * @return float
454
+	 *
455
+	 * @throws Exception
456
+	 */
457
+	public function average($field): float
458
+	{
459
+		return $this->aggregate('avg', $field);
460
+	}
461
+
462
+	/**
463
+	 * Get the minimum for a field in the current query
464
+	 *
465
+	 * @see Taken from the pecee-pixie library - https://github.com/skipperbent/pecee-pixie/
466
+	 *
467
+	 * @param string|Raw $field
468
+	 *
469
+	 * @return float
470
+	 *
471
+	 * @throws Exception
472
+	 */
473
+	public function min($field): float
474
+	{
475
+		return $this->aggregate('min', $field);
476
+	}
477
+
478
+	/**
479
+	 * Get the maximum for a field in the current query
480
+	 *
481
+	 * @see Taken from the pecee-pixie library - https://github.com/skipperbent/pecee-pixie/
482
+	 *
483
+	 * @param string|Raw $field
484
+	 *
485
+	 * @return float
486
+	 *
487
+	 * @throws Exception
488
+	 */
489
+	public function max($field): float
490
+	{
491
+		return $this->aggregate('max', $field);
492
+	}
493
+
494
+	/**
495
+	 * @param string $type
496
+	 * @param bool|array<mixed, mixed> $dataToBePassed
497
+	 *
498
+	 * @return mixed
499
+	 *
500
+	 * @throws Exception
501
+	 */
502
+	public function getQuery(string $type = 'select', $dataToBePassed = [])
503
+	{
504
+		$allowedTypes = ['select', 'insert', 'insertignore', 'replace', 'delete', 'update', 'criteriaonly'];
505
+		if (!in_array(strtolower($type), $allowedTypes)) {
506
+			throw new Exception($type . ' is not a known type.', 2);
507
+		}
508
+
509
+		$queryArr = $this->adapterInstance->$type($this->statements, $dataToBePassed);
510
+
511
+		return $this->container->build(
512
+			QueryObject::class,
513
+			[$queryArr['sql'], $queryArr['bindings'], $this->dbInstance]
514
+		);
515
+	}
516
+
517
+	/**
518
+	 * @param QueryBuilderHandler $queryBuilder
519
+	 * @param string|null $alias
520
+	 *
521
+	 * @return Raw
522
+	 */
523
+	public function subQuery(QueryBuilderHandler $queryBuilder, ?string $alias = null)
524
+	{
525
+		$sql = '(' . $queryBuilder->getQuery()->getRawSql() . ')';
526
+		if (is_string($alias) && 0 !== mb_strlen($alias)) {
527
+			$sql = $sql . ' as ' . $alias;
528
+		}
529
+
530
+		return $queryBuilder->raw($sql);
531
+	}
532
+
533
+	/**
534
+	 * Handles the various insert operations based on the type.
535
+	 *
536
+	 * @param array<int|string, mixed|mixed[]> $data
537
+	 * @param string $type
538
+	 *
539
+	 * @return int|int[]|mixed|null can return a single row id, array of row ids, null (for failed) or any other value short circuited from event
540
+	 */
541
+	private function doInsert(array $data, string $type)
542
+	{
543
+		$eventResult = $this->fireEvents('before-insert');
544
+		if (!is_null($eventResult)) {
545
+			return $eventResult;
546
+		}
547
+
548
+		// If first value is not an array () not a batch insert)
549
+		if (!is_array(current($data))) {
550
+			$queryObject = $this->getQuery($type, $data);
551
+
552
+			list($preparedQuery, $executionTime) = $this->statement($queryObject->getSql(), $queryObject->getBindings());
553
+			$this->dbInstance->get_results($preparedQuery);
554
+
555
+			// Check we have a result.
556
+			$return = 1 === $this->dbInstance->rows_affected ? $this->dbInstance->insert_id : null;
557
+		} else {
558
+			// Its a batch insert
559
+			$return        = [];
560
+			$executionTime = 0;
561
+			foreach ($data as $subData) {
562
+				$queryObject = $this->getQuery($type, $subData);
563
+
564
+				list($preparedQuery, $time) = $this->statement($queryObject->getSql(), $queryObject->getBindings());
565
+				$this->dbInstance->get_results($preparedQuery);
566
+				$executionTime += $time;
567
+
568
+				if (1 === $this->dbInstance->rows_affected) {
569
+					$return[] = $this->dbInstance->insert_id;
570
+				}
571
+			}
572
+		}
573
+
574
+		$this->fireEvents('after-insert', $return, $executionTime);
575
+
576
+		return $return;
577
+	}
578
+
579
+	/**
580
+	 * @param array<int|string, mixed|mixed[]> $data either key=>value array for single or array of arrays for bulk
581
+	 *
582
+	 * @return int|int[]|mixed|null can return a single row id, array of row ids, null (for failed) or any other value short circuited from event
583
+	 */
584
+	public function insert($data)
585
+	{
586
+		return $this->doInsert($data, 'insert');
587
+	}
588
+
589
+	/**
590
+	 *
591
+	 * @param array<int|string, mixed|mixed[]> $data either key=>value array for single or array of arrays for bulk
592
+	 *
593
+	 * @return int|int[]|mixed|null can return a single row id, array of row ids, null (for failed) or any other value short circuited from event
594
+	 */
595
+	public function insertIgnore($data)
596
+	{
597
+		return $this->doInsert($data, 'insertignore');
598
+	}
599
+
600
+	/**
601
+	 *
602
+	 * @param array<int|string, mixed|mixed[]> $data either key=>value array for single or array of arrays for bulk
603
+	 *
604
+	 * @return int|int[]|mixed|null can return a single row id, array of row ids, null (for failed) or any other value short circuited from event
605
+	 */
606
+	public function replace($data)
607
+	{
608
+		return $this->doInsert($data, 'replace');
609
+	}
610
+
611
+	/**
612
+	 * @param array<string, mixed> $data
613
+	 *
614
+	 * @return int|null Number of row effected, null for none.
615
+	 */
616
+	public function update(array $data): ?int
617
+	{
618
+		$eventResult = $this->fireEvents('before-update');
619
+		if (!is_null($eventResult)) {
620
+			return $eventResult;
621
+		}
622
+		$queryObject                         = $this->getQuery('update', $data);
623
+		$r = $this->statement($queryObject->getSql(), $queryObject->getBindings());
624
+		list($preparedQuery, $executionTime) = $r;
625
+		$this->dbInstance()->get_results($preparedQuery);
626
+		$this->fireEvents('after-update', $queryObject, $executionTime);
627
+
628
+		return 0 !== (int) $this->dbInstance()->rows_affected
629
+			? (int) $this->dbInstance()->rows_affected
630
+			: null;
631
+	}
632
+
633
+	/**
634
+	 * Update or Insert based on the attributes.
635
+	 *
636
+	 * @param array<string, mixed> $attributes Conditions to check
637
+	 * @param array<string, mixed> $values     Values to add/update
638
+	 *
639
+	 * @return int|int[]|null will return row id(s) for insert and null for success/fail on update
640
+	 */
641
+	public function updateOrInsert(array $attributes, array $values = [])
642
+	{
643
+		// Check if existing post exists.
644
+		$query = clone $this;
645
+		foreach ($attributes as $column => $value) {
646
+			$query->where($column, $value);
647
+		}
648
+
649
+		// If we have a result, update it.
650
+		if (null !== $query->first()) {
651
+			foreach ($attributes as $column => $value) {
652
+				$this->where($column, $value);
653
+			}
654
+
655
+			return $this->update($values);
656
+		}
657
+
658
+		// Else insert
659
+		return $this->insert($values);
660
+	}
661
+
662
+	/**
663
+	 * @param array<string, mixed> $data
664
+	 *
665
+	 * @return static
666
+	 */
667
+	public function onDuplicateKeyUpdate($data)
668
+	{
669
+		$this->addStatement('onduplicate', $data);
670
+
671
+		return $this;
672
+	}
673
+
674
+	/**
675
+	 * @return mixed number of rows effected or shortcircuited response
676
+	 */
677
+	public function delete()
678
+	{
679
+		$eventResult = $this->fireEvents('before-delete');
680
+		if (!is_null($eventResult)) {
681
+			return $eventResult;
682
+		}
683
+
684
+		$queryObject = $this->getQuery('delete');
685
+
686
+		list($preparedQuery, $executionTime) = $this->statement($queryObject->getSql(), $queryObject->getBindings());
687
+		$this->dbInstance()->get_results($preparedQuery);
688
+		$this->fireEvents('after-delete', $queryObject, $executionTime);
689
+
690
+		return $this->dbInstance()->rows_affected;
691
+	}
692
+
693
+	/**
694
+	 * @param string|Raw ...$tables Single table or array of tables
695
+	 *
696
+	 * @return static
697
+	 *
698
+	 * @throws Exception
699
+	 */
700
+	public function table(...$tables)
701
+	{
702
+		$instance =  $this->constructCurrentBuilderClass($this->connection);
703
+		$this->setFetchMode($this->getFetchMode(), $this->hydratorConstructorArgs);
704
+		$tables = $this->addTablePrefix($tables, false);
705
+		$instance->addStatement('tables', $tables);
706
+
707
+		return $instance;
708
+	}
709
+
710
+	/**
711
+	 * @param string|Raw ...$tables Single table or array of tables
712
+	 *
713
+	 * @return static
714
+	 */
715
+	public function from(...$tables): self
716
+	{
717
+		$tables = $this->addTablePrefix($tables, false);
718
+		$this->addStatement('tables', $tables);
719
+
720
+		return $this;
721
+	}
722
+
723
+	/**
724
+	 * @param string|string[]|Raw[]|array<string, string> $fields
725
+	 *
726
+	 * @return static
727
+	 */
728
+	public function select($fields): self
729
+	{
730
+		if (!is_array($fields)) {
731
+			$fields = func_get_args();
732
+		}
733
+
734
+		foreach ($fields as $field => $alias) {
735
+			// If we have a JSON expression
736
+			if ($this->jsonHandler->isJsonSelector($field)) {
737
+				$field = $this->jsonHandler->extractAndUnquoteFromJsonSelector($field);
738
+			}
739
+
740
+			// If no alias passed, but field is for JSON. thrown an exception.
741
+			if (is_numeric($field) && is_string($alias) && $this->jsonHandler->isJsonSelector($alias)) {
742
+				throw new Exception("An alias must be used if you wish to select from JSON Object", 1);
743
+			}
744
+
745
+			// Treat each array as a single table, to retain order added
746
+			$field = is_numeric($field)
747
+				? $field = $alias // If single colum
748
+				: $field = [$field => $alias]; // Has alias
749
+
750
+			$field = $this->addTablePrefix($field);
751
+			$this->addStatement('selects', $field);
752
+		}
753
+
754
+		return $this;
755
+	}
756
+
757
+	/**
758
+	 * @param string|string[]|Raw[]|array<string, string> $fields
759
+	 *
760
+	 * @return static
761
+	 */
762
+	public function selectDistinct($fields)
763
+	{
764
+		$this->select($fields);
765
+		$this->addStatement('distinct', true);
766
+
767
+		return $this;
768
+	}
769
+
770
+	/**
771
+	 * @param string|string[] $field either the single field or an array of fields
772
+	 *
773
+	 * @return static
774
+	 */
775
+	public function groupBy($field): self
776
+	{
777
+		$field = $this->addTablePrefix($field);
778
+		$this->addStatement('groupBys', $field);
779
+
780
+		return $this;
781
+	}
782
+
783
+	/**
784
+	 * @param string|array<string|int, mixed> $fields
785
+	 * @param string          $defaultDirection
786
+	 *
787
+	 * @return static
788
+	 */
789
+	public function orderBy($fields, string $defaultDirection = 'ASC'): self
790
+	{
791
+		if (!is_array($fields)) {
792
+			$fields = [$fields];
793
+		}
794
+
795
+		foreach ($fields as $key => $value) {
796
+			$field = $key;
797
+			$type  = $value;
798
+			if (is_int($key)) {
799
+				$field = $value;
800
+				$type  = $defaultDirection;
801
+			}
802
+
803
+			if ($this->jsonHandler->isJsonSelector($field)) {
804
+				$field = $this->jsonHandler->extractAndUnquoteFromJsonSelector($field);
805
+			}
806
+
807
+			if (!$field instanceof Raw) {
808
+				$field = $this->addTablePrefix($field);
809
+			}
810
+			$this->statements['orderBys'][] = compact('field', 'type');
811
+		}
812
+
813
+		return $this;
814
+	}
815
+
816
+	/**
817
+	 * @param string|Raw $key The database column which holds the JSON value
818
+	 * @param string|Raw|string[] $jsonKey The json key/index to search
819
+	 * @param string $defaultDirection
820
+	 * @return static
821
+	 */
822
+	public function orderByJson($key, $jsonKey, string $defaultDirection = 'ASC'): self
823
+	{
824
+		$key = $this->jsonHandler->jsonExpressionFactory()->extractAndUnquote($key, $jsonKey);
825
+		return $this->orderBy($key, $defaultDirection);
826
+	}
827
+
828
+	/**
829
+	 * @param int $limit
830
+	 *
831
+	 * @return static
832
+	 */
833
+	public function limit(int $limit): self
834
+	{
835
+		$this->statements['limit'] = $limit;
836
+
837
+		return $this;
838
+	}
839
+
840
+	/**
841
+	 * @param int $offset
842
+	 *
843
+	 * @return static
844
+	 */
845
+	public function offset(int $offset): self
846
+	{
847
+		$this->statements['offset'] = $offset;
848
+
849
+		return $this;
850
+	}
851
+
852
+	/**
853
+	 * @param string|string[]|Raw|Raw[]       $key
854
+	 * @param string $operator
855
+	 * @param mixed $value
856
+	 * @param string $joiner
857
+	 *
858
+	 * @return static
859
+	 */
860
+	public function having($key, string $operator, $value, string $joiner = 'AND')
861
+	{
862
+		$key                           = $this->addTablePrefix($key);
863
+		$this->statements['havings'][] = compact('key', 'operator', 'value', 'joiner');
864
+
865
+		return $this;
866
+	}
867
+
868
+	/**
869
+	 * @param string|string[]|Raw|Raw[]       $key
870
+	 * @param string $operator
871
+	 * @param mixed $value
872
+	 *
873
+	 * @return static
874
+	 */
875
+	public function orHaving($key, $operator, $value)
876
+	{
877
+		return $this->having($key, $operator, $value, 'OR');
878
+	}
879
+
880
+	/**
881
+	 * @param string|Raw $key
882
+	 * @param string|mixed|null $operator Can be used as value, if 3rd arg not passed
883
+	 * @param mixed|null $value
884
+	 *
885
+	 * @return static
886
+	 */
887
+	public function where($key, $operator = null, $value = null): self
888
+	{
889
+		// If two params are given then assume operator is =
890
+		if (2 === func_num_args()) {
891
+			$value    = $operator;
892
+			$operator = '=';
893
+		}
894
+
895
+		return $this->whereHandler($key, $operator, $value);
896
+	}
897
+
898
+	/**
899
+	 * @param string|Raw|\Closure(QueryBuilderHandler):void $key
900
+	 * @param string|mixed|null $operator Can be used as value, if 3rd arg not passed
901
+	 * @param mixed|null $value
902
+	 *
903
+	 * @return static
904
+	 */
905
+	public function orWhere($key, $operator = null, $value = null): self
906
+	{
907
+		// If two params are given then assume operator is =
908
+		if (2 === func_num_args()) {
909
+			$value    = $operator;
910
+			$operator = '=';
911
+		}
912
+
913
+		return $this->whereHandler($key, $operator, $value, 'OR');
914
+	}
915
+
916
+	/**
917
+	 * @param string|Raw $key
918
+	 * @param string|mixed|null $operator Can be used as value, if 3rd arg not passed
919
+	 * @param mixed|null $value
920
+	 *
921
+	 * @return static
922
+	 */
923
+	public function whereNot($key, $operator = null, $value = null): self
924
+	{
925
+		// If two params are given then assume operator is =
926
+		if (2 === func_num_args()) {
927
+			$value    = $operator;
928
+			$operator = '=';
929
+		}
930
+
931
+		return $this->whereHandler($key, $operator, $value, 'AND NOT');
932
+	}
933
+
934
+	/**
935
+	 * @param string|Raw $key
936
+	 * @param string|mixed|null $operator Can be used as value, if 3rd arg not passed
937
+	 * @param mixed|null $value
938
+	 *
939
+	 * @return static
940
+	 */
941
+	public function orWhereNot($key, $operator = null, $value = null)
942
+	{
943
+		// If two params are given then assume operator is =
944
+		if (2 === func_num_args()) {
945
+			$value    = $operator;
946
+			$operator = '=';
947
+		}
948
+
949
+		return $this->whereHandler($key, $operator, $value, 'OR NOT');
950
+	}
951
+
952
+	/**
953
+	 * @param string|Raw $key
954
+	 * @param mixed[]|string|Raw $values
955
+	 *
956
+	 * @return static
957
+	 */
958
+	public function whereIn($key, $values): self
959
+	{
960
+		return $this->whereHandler($key, 'IN', $values, 'AND');
961
+	}
962
+
963
+	/**
964
+	 * @param string|Raw $key
965
+	 * @param mixed[]|string|Raw $values
966
+	 *
967
+	 * @return static
968
+	 */
969
+	public function whereNotIn($key, $values): self
970
+	{
971
+		return $this->whereHandler($key, 'NOT IN', $values, 'AND');
972
+	}
973
+
974
+	/**
975
+	 * @param string|Raw $key
976
+	 * @param mixed[]|string|Raw $values
977
+	 *
978
+	 * @return static
979
+	 */
980
+	public function orWhereIn($key, $values): self
981
+	{
982
+		return $this->whereHandler($key, 'IN', $values, 'OR');
983
+	}
984
+
985
+	/**
986
+	 * @param string|Raw $key
987
+	 * @param mixed[]|string|Raw $values
988
+	 *
989
+	 * @return static
990
+	 */
991
+	public function orWhereNotIn($key, $values): self
992
+	{
993
+		return $this->whereHandler($key, 'NOT IN', $values, 'OR');
994
+	}
995
+
996
+	/**
997
+	 * @param string|Raw $key
998
+	 * @param mixed $valueFrom
999
+	 * @param mixed $valueTo
1000
+	 *
1001
+	 * @return static
1002
+	 */
1003
+	public function whereBetween($key, $valueFrom, $valueTo): self
1004
+	{
1005
+		return $this->whereHandler($key, 'BETWEEN', [$valueFrom, $valueTo], 'AND');
1006
+	}
1007
+
1008
+	/**
1009
+	 * @param string|Raw $key
1010
+	 * @param mixed $valueFrom
1011
+	 * @param mixed $valueTo
1012
+	 *
1013
+	 * @return static
1014
+	 */
1015
+	public function orWhereBetween($key, $valueFrom, $valueTo): self
1016
+	{
1017
+		return $this->whereHandler($key, 'BETWEEN', [$valueFrom, $valueTo], 'OR');
1018
+	}
1019
+
1020
+	/**
1021
+	 * Handles all function call based where conditions
1022
+	 *
1023
+	 * @param string|Raw $key
1024
+	 * @param string $function
1025
+	 * @param string|mixed|null $operator Can be used as value, if 3rd arg not passed
1026
+	 * @param mixed|null $value
1027
+	 * @return static
1028
+	 */
1029
+	protected function whereFunctionCallHandler($key, $function, $operator, $value): self
1030
+	{
1031
+		$key = \sprintf('%s(%s)', $function, $this->addTablePrefix($key));
1032
+		return $this->where($key, $operator, $value);
1033
+	}
1034
+
1035
+	/**
1036
+	 * @param string|Raw $key
1037
+	 * @param string|mixed|null $operator Can be used as value, if 3rd arg not passed
1038
+	 * @param mixed|null $value
1039
+	 * @return self
1040
+	 */
1041
+	public function whereMonth($key, $operator = null, $value = null): self
1042
+	{
1043
+		// If two params are given then assume operator is =
1044
+		if (2 === func_num_args()) {
1045
+			$value    = $operator;
1046
+			$operator = '=';
1047
+		}
1048
+		return $this->whereFunctionCallHandler($key, 'MONTH', $operator, $value);
1049
+	}
1050
+
1051
+	/**
1052
+	 * @param string|Raw $key
1053
+	 * @param string|mixed|null $operator Can be used as value, if 3rd arg not passed
1054
+	 * @param mixed|null $value
1055
+	 * @return self
1056
+	 */
1057
+	public function whereDay($key, $operator = null, $value = null): self
1058
+	{
1059
+		// If two params are given then assume operator is =
1060
+		if (2 === func_num_args()) {
1061
+			$value    = $operator;
1062
+			$operator = '=';
1063
+		}
1064
+		return $this->whereFunctionCallHandler($key, 'DAY', $operator, $value);
1065
+	}
1066
+
1067
+	/**
1068
+	 * @param string|Raw $key
1069
+	 * @param string|mixed|null $operator Can be used as value, if 3rd arg not passed
1070
+	 * @param mixed|null $value
1071
+	 * @return self
1072
+	 */
1073
+	public function whereYear($key, $operator = null, $value = null): self
1074
+	{
1075
+		// If two params are given then assume operator is =
1076
+		if (2 === func_num_args()) {
1077
+			$value    = $operator;
1078
+			$operator = '=';
1079
+		}
1080
+		return $this->whereFunctionCallHandler($key, 'YEAR', $operator, $value);
1081
+	}
1082
+
1083
+	/**
1084
+	 * @param string|Raw $key
1085
+	 * @param string|mixed|null $operator Can be used as value, if 3rd arg not passed
1086
+	 * @param mixed|null $value
1087
+	 * @return self
1088
+	 */
1089
+	public function whereDate($key, $operator = null, $value = null): self
1090
+	{
1091
+		// If two params are given then assume operator is =
1092
+		if (2 === func_num_args()) {
1093
+			$value    = $operator;
1094
+			$operator = '=';
1095
+		}
1096
+		return $this->whereFunctionCallHandler($key, 'DATE', $operator, $value);
1097
+	}
1098
+
1099
+	/**
1100
+	 * @param string|Raw $key
1101
+	 *
1102
+	 * @return static
1103
+	 */
1104
+	public function whereNull($key): self
1105
+	{
1106
+		return $this->whereNullHandler($key);
1107
+	}
1108
+
1109
+	/**
1110
+	 * @param string|Raw $key
1111
+	 *
1112
+	 * @return static
1113
+	 */
1114
+	public function whereNotNull($key): self
1115
+	{
1116
+		return $this->whereNullHandler($key, 'NOT');
1117
+	}
1118
+
1119
+	/**
1120
+	 * @param string|Raw $key
1121
+	 *
1122
+	 * @return static
1123
+	 */
1124
+	public function orWhereNull($key): self
1125
+	{
1126
+		return $this->whereNullHandler($key, '', 'or');
1127
+	}
1128
+
1129
+	/**
1130
+	 * @param string|Raw $key
1131
+	 *
1132
+	 * @return static
1133
+	 */
1134
+	public function orWhereNotNull($key): self
1135
+	{
1136
+		return $this->whereNullHandler($key, 'NOT', 'or');
1137
+	}
1138
+
1139
+	/**
1140
+	 * @param string|Raw $key
1141
+	 * @param string $prefix
1142
+	 * @param string $operator
1143
+	 *
1144
+	 * @return static
1145
+	 */
1146
+	protected function whereNullHandler($key, string $prefix = '', $operator = ''): self
1147
+	{
1148
+		$prefix = 0 === mb_strlen($prefix) ? '' : " {$prefix}";
1149
+
1150
+		if ($key instanceof Raw) {
1151
+			$key = $this->adapterInstance->parseRaw($key);
1152
+		}
1153
+
1154
+		$key = $this->addTablePrefix($key);
1155
+		if ($key instanceof Closure) {
1156
+			throw new Exception('Key used for whereNull condition must be a string or raw exrpession.', 1);
1157
+		}
1158
+
1159
+		return $this->{$operator . 'Where'}($this->raw("{$key} IS{$prefix} NULL"));
1160
+	}
1161
+
1162
+
1163
+	/**
1164
+	 * Runs a transaction
1165
+	 *
1166
+	 * @param \Closure(Transaction):void $callback
1167
+	 *
1168
+	 * @return static
1169
+	 */
1170
+	public function transaction(Closure $callback): self
1171
+	{
1172
+		try {
1173
+			// Begin the transaction
1174
+			$this->dbInstance->query('START TRANSACTION');
1175
+
1176
+			// Get the Transaction class
1177
+			$transaction = $this->container->build(Transaction::class, [$this->connection]);
1178
+
1179
+			$this->handleTransactionCall($callback, $transaction);
1180
+
1181
+			// If no errors have been thrown or the transaction wasn't completed within
1182
+			$this->dbInstance->query('COMMIT');
1183
+
1184
+			return $this;
1185
+		} catch (TransactionHaltException $e) {
1186
+			// Commit or rollback behavior has been handled in the closure, so exit
1187
+			return $this;
1188
+		} catch (\Exception $e) {
1189
+			// something happened, rollback changes
1190
+			$this->dbInstance->query('ROLLBACK');
1191
+
1192
+			return $this;
1193
+		}
1194
+	}
1195
+
1196
+	/**
1197
+	 * Handles the transaction call.
1198
+	 * Catches any WPDB Errors (printed)
1199
+	 *
1200
+	 * @param Closure    $callback
1201
+	 * @param Transaction $transaction
1202
+	 *
1203
+	 * @return void
1204
+	 * @throws Exception
1205
+	 */
1206
+	protected function handleTransactionCall(Closure $callback, Transaction $transaction): void
1207
+	{
1208
+		try {
1209
+			ob_start();
1210
+			$callback($transaction);
1211
+			$output = ob_get_clean() ?: '';
1212
+		} catch (Throwable $th) {
1213
+			ob_end_clean();
1214
+			throw $th;
1215
+		}
1216
+
1217
+		// If we caught an error, throw an exception.
1218
+		if (0 !== mb_strlen($output)) {
1219
+			throw new Exception($output);
1220
+		}
1221
+	}
1222
+
1223
+	/*************************************************************************/
1224
+	/*************************************************************************/
1225
+	/*************************************************************************/
1226
+	/**                              JOIN JOIN                              **/
1227
+	/**                                 JOIN                                **/
1228
+	/**                              JOIN JOIN                              **/
1229
+	/*************************************************************************/
1230
+	/*************************************************************************/
1231
+	/*************************************************************************/
1232
+
1233
+	/**
1234
+	 * @param string|Raw $table
1235
+	 * @param string|Raw|Closure $key
1236
+	 * @param string|null $operator
1237
+	 * @param mixed $value
1238
+	 * @param string $type
1239
+	 *
1240
+	 * @return static
1241
+	 */
1242
+	public function join($table, $key, ?string $operator = null, $value = null, $type = 'inner')
1243
+	{
1244
+		// Potentially cast key from JSON
1245
+		if ($this->jsonHandler->isJsonSelector($key)) {
1246
+			/** @var string $key */
1247
+			$key = $this->jsonHandler->extractAndUnquoteFromJsonSelector($key); /** @phpstan-ignore-line */
1248
+		}
1249
+
1250
+		// Potentially cast value from json
1251
+		if ($this->jsonHandler->isJsonSelector($value)) {
1252
+			/** @var string $value */
1253
+			$value = $this->jsonHandler->extractAndUnquoteFromJsonSelector($value);
1254
+		}
1255
+
1256
+		if (!$key instanceof Closure) {
1257
+			$key = function ($joinBuilder) use ($key, $operator, $value) {
1258
+				$joinBuilder->on($key, $operator, $value);
1259
+			};
1260
+		}
1261
+
1262
+		// Build a new JoinBuilder class, keep it by reference so any changes made
1263
+		// in the closure should reflect here
1264
+		$joinBuilder = $this->container->build(JoinBuilder::class, [$this->connection]);
1265
+		$joinBuilder = &$joinBuilder;
1266
+		// Call the closure with our new joinBuilder object
1267
+		$key($joinBuilder);
1268
+		$table = $this->addTablePrefix($table, false);
1269
+		// Get the criteria only query from the joinBuilder object
1270
+		$this->statements['joins'][] = compact('type', 'table', 'joinBuilder');
1271
+		return $this;
1272
+	}
1273
+
1274
+	/**
1275
+	 * @param string|Raw $table
1276
+	 * @param string|Raw|Closure $key
1277
+	 * @param string|null $operator
1278
+	 * @param mixed $value
1279
+	 *
1280
+	 * @return static
1281
+	 */
1282
+	public function leftJoin($table, $key, $operator = null, $value = null)
1283
+	{
1284
+		return $this->join($table, $key, $operator, $value, 'left');
1285
+	}
1286
+
1287
+	/**
1288
+	 * @param string|Raw $table
1289
+	 * @param string|Raw|Closure $key
1290
+	 * @param string|null $operator
1291
+	 * @param mixed $value
1292
+	 *
1293
+	 * @return static
1294
+	 */
1295
+	public function rightJoin($table, $key, $operator = null, $value = null)
1296
+	{
1297
+		return $this->join($table, $key, $operator, $value, 'right');
1298
+	}
1299
+
1300
+	/**
1301
+	 * @param string|Raw $table
1302
+	 * @param string|Raw|Closure $key
1303
+	 * @param string|null $operator
1304
+	 * @param mixed $value
1305
+	 *
1306
+	 * @return static
1307
+	 */
1308
+	public function innerJoin($table, $key, $operator = null, $value = null)
1309
+	{
1310
+		return $this->join($table, $key, $operator, $value, 'inner');
1311
+	}
1312
+
1313
+	/**
1314
+	 * @param string|Raw $table
1315
+	 * @param string|Raw|Closure $key
1316
+	 * @param string|null $operator
1317
+	 * @param mixed $value
1318
+	 *
1319
+	 * @return static
1320
+	 */
1321
+	public function crossJoin($table, $key, $operator = null, $value = null)
1322
+	{
1323
+		return $this->join($table, $key, $operator, $value, 'cross');
1324
+	}
1325
+
1326
+	/**
1327
+	 * @param string|Raw $table
1328
+	 * @param string|Raw|Closure $key
1329
+	 * @param string|null $operator
1330
+	 * @param mixed $value
1331
+	 *
1332
+	 * @return static
1333
+	 */
1334
+	public function outerJoin($table, $key, $operator = null, $value = null)
1335
+	{
1336
+		return $this->join($table, $key, $operator, $value, 'full outer');
1337
+	}
1338
+
1339
+	/**
1340
+	 * Shortcut to join 2 tables on the same key name with equals
1341
+	 *
1342
+	 * @param string $table
1343
+	 * @param string $key
1344
+	 * @param string $type
1345
+	 * @return self
1346
+	 * @throws Exception If base table is set as more than 1 or 0
1347
+	 */
1348
+	public function joinUsing(string $table, string $key, string $type = 'INNER'): self
1349
+	{
1350
+		if (!array_key_exists('tables', $this->statements) || count($this->statements['tables']) !== 1) {
1351
+			throw new Exception("JoinUsing can only be used with a single table set as the base of the query", 1);
1352
+		}
1353
+		$baseTable = end($this->statements['tables']);
1354
+
1355
+		// Potentialy cast key from JSON
1356
+		if ($this->jsonHandler->isJsonSelector($key)) {
1357
+			$key = $this->jsonHandler->extractAndUnquoteFromJsonSelector($key);
1358
+		}
1359
+
1360
+		$remoteKey = $table = $this->addTablePrefix("{$table}.{$key}", true);
1361
+		$localKey = $table = $this->addTablePrefix("{$baseTable}.{$key}", true);
1362
+		return $this->join($table, $remoteKey, '=', $localKey, $type);
1363
+	}
1364
+
1365
+	/**
1366
+	 * Add a raw query
1367
+	 *
1368
+	 * @param string|Raw $value
1369
+	 * @param mixed|mixed[] $bindings
1370
+	 *
1371
+	 * @return Raw
1372
+	 */
1373
+	public function raw($value, $bindings = []): Raw
1374
+	{
1375
+		return new Raw($value, $bindings);
1376
+	}
1377
+
1378
+	/**
1379
+	 * Return wpdb instance
1380
+	 *
1381
+	 * @return wpdb
1382
+	 */
1383
+	public function dbInstance(): wpdb
1384
+	{
1385
+		return $this->dbInstance;
1386
+	}
1387
+
1388
+	/**
1389
+	 * @param Connection $connection
1390
+	 *
1391
+	 * @return static
1392
+	 */
1393
+	public function setConnection(Connection $connection): self
1394
+	{
1395
+		$this->connection = $connection;
1396
+
1397
+		return $this;
1398
+	}
1399
+
1400
+	/**
1401
+	 * @return Connection
1402
+	 */
1403
+	public function getConnection(): Connection
1404
+	{
1405
+		return $this->connection;
1406
+	}
1407
+
1408
+	/**
1409
+	 * @param string|Raw|Closure $key
1410
+	 * @param string|null      $operator
1411
+	 * @param mixed|null       $value
1412
+	 * @param string $joiner
1413
+	 *
1414
+	 * @return static
1415
+	 */
1416
+	protected function whereHandler($key, $operator = null, $value = null, $joiner = 'AND')
1417
+	{
1418
+		$key = $this->addTablePrefix($key);
1419
+		if ($key instanceof Raw) {
1420
+			$key = $this->adapterInstance->parseRaw($key);
1421
+		}
1422
+
1423
+		if ($this->jsonHandler->isJsonSelector($key)) {
1424
+			$key = $this->jsonHandler->extractAndUnquoteFromJsonSelector($key);
1425
+		}
1426
+
1427
+		$this->statements['wheres'][] = compact('key', 'operator', 'value', 'joiner');
1428
+		return $this;
1429
+	}
1430
+
1431
+
1432
+
1433
+	/**
1434
+	 * @param string $key
1435
+	 * @param mixed|mixed[]|bool $value
1436
+	 *
1437
+	 * @return void
1438
+	 */
1439
+	protected function addStatement($key, $value)
1440
+	{
1441
+		if (!is_array($value)) {
1442
+			$value = [$value];
1443
+		}
1444
+
1445
+		if (!array_key_exists($key, $this->statements)) {
1446
+			$this->statements[$key] = $value;
1447
+		} else {
1448
+			$this->statements[$key] = array_merge($this->statements[$key], $value);
1449
+		}
1450
+	}
1451
+
1452
+	/**
1453
+	 * @param string $event
1454
+	 * @param string|Raw $table
1455
+	 *
1456
+	 * @return callable|null
1457
+	 */
1458
+	public function getEvent(string $event, $table = ':any'): ?callable
1459
+	{
1460
+		return $this->connection->getEventHandler()->getEvent($event, $table);
1461
+	}
1462
+
1463
+	/**
1464
+	 * @param string $event
1465
+	 * @param string|Raw $table
1466
+	 * @param Closure $action
1467
+	 *
1468
+	 * @return void
1469
+	 */
1470
+	public function registerEvent($event, $table, Closure $action): void
1471
+	{
1472
+		$table = $table ?: ':any';
1473
+
1474
+		if (':any' != $table) {
1475
+			$table = $this->addTablePrefix($table, false);
1476
+		}
1477
+
1478
+		$this->connection->getEventHandler()->registerEvent($event, $table, $action);
1479
+	}
1480
+
1481
+	/**
1482
+	 * @param string $event
1483
+	 * @param string|Raw $table
1484
+	 *
1485
+	 * @return void
1486
+	 */
1487
+	public function removeEvent(string $event, $table = ':any')
1488
+	{
1489
+		if (':any' != $table) {
1490
+			$table = $this->addTablePrefix($table, false);
1491
+		}
1492
+
1493
+		$this->connection->getEventHandler()->removeEvent($event, $table);
1494
+	}
1495
+
1496
+	/**
1497
+	 * @param string $event
1498
+	 *
1499
+	 * @return mixed
1500
+	 */
1501
+	public function fireEvents(string $event)
1502
+	{
1503
+		$params = func_get_args(); // @todo Replace this with an easier to read alteratnive
1504
+		array_unshift($params, $this);
1505
+
1506
+		return call_user_func_array([$this->connection->getEventHandler(), 'fireEvents'], $params);
1507
+	}
1508
+
1509
+	/**
1510
+	 * @return array<string, mixed[]>
1511
+	 */
1512
+	public function getStatements()
1513
+	{
1514
+		return $this->statements;
1515
+	}
1516
+
1517
+	/**
1518
+	 * @return string will return WPDB Fetch mode
1519
+	 */
1520
+	public function getFetchMode()
1521
+	{
1522
+		return null !== $this->fetchMode
1523
+			? $this->fetchMode
1524
+			: \OBJECT;
1525
+	}
1526
+
1527
+	/**
1528
+	 * Returns an NEW instance of the JSON builder populated with the same connection and hydrator details.
1529
+	 *
1530
+	 * @return JsonQueryBuilder
1531
+	 */
1532
+	public function jsonBuilder(): JsonQueryBuilder
1533
+	{
1534
+		return new JsonQueryBuilder($this->getConnection(), $this->getFetchMode(), $this->hydratorConstructorArgs);
1535
+	}
1536 1536
 }
Please login to merge, or discard this patch.
src/loader.php 2 patches
Indentation   +22 added lines, -22 removed lines patch added patch discarded remove patch
@@ -27,69 +27,69 @@
 block discarded – undo
27 27
 
28 28
 // Generated code start...
29 29
 if (!interface_exists(Pixie\HasConnection::class)) {
30
-    require_once __DIR__ . '/HasConnection.php';
30
+	require_once __DIR__ . '/HasConnection.php';
31 31
 }
32 32
 if (!trait_exists(Pixie\QueryBuilder\TablePrefixer::class)) {
33
-    require_once __DIR__ . '/QueryBuilder/TablePrefixer.php';
33
+	require_once __DIR__ . '/QueryBuilder/TablePrefixer.php';
34 34
 }
35 35
 if (!class_exists(Pixie\Binding::class)) {
36
-    require_once __DIR__ . '/Binding.php';
36
+	require_once __DIR__ . '/Binding.php';
37 37
 }
38 38
 if (!class_exists(Pixie\QueryBuilder\QueryBuilderHandler::class)) {
39
-    require_once __DIR__ . '/QueryBuilder/QueryBuilderHandler.php';
39
+	require_once __DIR__ . '/QueryBuilder/QueryBuilderHandler.php';
40 40
 }
41 41
 if (!class_exists(Pixie\QueryBuilder\Transaction::class)) {
42
-    require_once __DIR__ . '/QueryBuilder/Transaction.php';
42
+	require_once __DIR__ . '/QueryBuilder/Transaction.php';
43 43
 }
44 44
 if (!class_exists(Pixie\QueryBuilder\Raw::class)) {
45
-    require_once __DIR__ . '/QueryBuilder/Raw.php';
45
+	require_once __DIR__ . '/QueryBuilder/Raw.php';
46 46
 }
47 47
 if (!class_exists(Pixie\JSON\JsonHandler::class)) {
48
-    require_once __DIR__ . '/JSON/JsonHandler.php';
48
+	require_once __DIR__ . '/JSON/JsonHandler.php';
49 49
 }
50 50
 if (!class_exists(Pixie\EventHandler::class)) {
51
-    require_once __DIR__ . '/EventHandler.php';
51
+	require_once __DIR__ . '/EventHandler.php';
52 52
 }
53 53
 if (!class_exists(Pixie\QueryBuilder\QueryObject::class)) {
54
-    require_once __DIR__ . '/QueryBuilder/QueryObject.php';
54
+	require_once __DIR__ . '/QueryBuilder/QueryObject.php';
55 55
 }
56 56
 if (!class_exists(Pixie\QueryBuilder\NestedCriteria::class)) {
57
-    require_once __DIR__ . '/QueryBuilder/NestedCriteria.php';
57
+	require_once __DIR__ . '/QueryBuilder/NestedCriteria.php';
58 58
 }
59 59
 if (!class_exists(Pixie\QueryBuilder\JoinBuilder::class)) {
60
-    require_once __DIR__ . '/QueryBuilder/JoinBuilder.php';
60
+	require_once __DIR__ . '/QueryBuilder/JoinBuilder.php';
61 61
 }
62 62
 if (!class_exists(Pixie\Event::class)) {
63
-    require_once __DIR__ . '/Event.php';
63
+	require_once __DIR__ . '/Event.php';
64 64
 }
65 65
 if (!class_exists(Pixie\JSON\JsonSelectorHandler::class)) {
66
-    require_once __DIR__ . '/JSON/JsonSelectorHandler.php';
66
+	require_once __DIR__ . '/JSON/JsonSelectorHandler.php';
67 67
 }
68 68
 if (!class_exists(Pixie\JSON\JsonSelector::class)) {
69
-    require_once __DIR__ . '/JSON/JsonSelector.php';
69
+	require_once __DIR__ . '/JSON/JsonSelector.php';
70 70
 }
71 71
 if (!class_exists(Pixie\Connection::class)) {
72
-    require_once __DIR__ . '/Connection.php';
72
+	require_once __DIR__ . '/Connection.php';
73 73
 }
74 74
 if (!class_exists(Pixie\QueryBuilder\JsonQueryBuilder::class)) {
75
-    require_once __DIR__ . '/QueryBuilder/JsonQueryBuilder.php';
75
+	require_once __DIR__ . '/QueryBuilder/JsonQueryBuilder.php';
76 76
 }
77 77
 if (!class_exists(Pixie\Hydration\Hydrator::class)) {
78
-    require_once __DIR__ . '/Hydration/Hydrator.php';
78
+	require_once __DIR__ . '/Hydration/Hydrator.php';
79 79
 }
80 80
 if (!class_exists(Pixie\QueryBuilder\WPDBAdapter::class)) {
81
-    require_once __DIR__ . '/QueryBuilder/WPDBAdapter.php';
81
+	require_once __DIR__ . '/QueryBuilder/WPDBAdapter.php';
82 82
 }
83 83
 if (!class_exists(Pixie\Exception::class)) {
84
-    require_once __DIR__ . '/Exception.php';
84
+	require_once __DIR__ . '/Exception.php';
85 85
 }
86 86
 if (!class_exists(Pixie\QueryBuilder\TransactionHaltException::class)) {
87
-    require_once __DIR__ . '/QueryBuilder/TransactionHaltException.php';
87
+	require_once __DIR__ . '/QueryBuilder/TransactionHaltException.php';
88 88
 }
89 89
 if (!class_exists(Pixie\AliasFacade::class)) {
90
-    require_once __DIR__ . '/AliasFacade.php';
90
+	require_once __DIR__ . '/AliasFacade.php';
91 91
 }
92 92
 if (!class_exists(Pixie\JSON\JsonExpressionFactory::class)) {
93
-    require_once __DIR__ . '/JSON/JsonExpressionFactory.php';
93
+	require_once __DIR__ . '/JSON/JsonExpressionFactory.php';
94 94
 }
95 95
 // CREATED ON Fri 15th April 2022
Please login to merge, or discard this patch.
Spacing   +44 added lines, -44 removed lines patch added patch discarded remove patch
@@ -26,70 +26,70 @@
 block discarded – undo
26 26
  */
27 27
 
28 28
 // Generated code start...
29
-if (!interface_exists(Pixie\HasConnection::class)) {
30
-    require_once __DIR__ . '/HasConnection.php';
29
+if ( ! interface_exists(Pixie\HasConnection::class)) {
30
+    require_once __DIR__.'/HasConnection.php';
31 31
 }
32
-if (!trait_exists(Pixie\QueryBuilder\TablePrefixer::class)) {
33
-    require_once __DIR__ . '/QueryBuilder/TablePrefixer.php';
32
+if ( ! trait_exists(Pixie\QueryBuilder\TablePrefixer::class)) {
33
+    require_once __DIR__.'/QueryBuilder/TablePrefixer.php';
34 34
 }
35
-if (!class_exists(Pixie\Binding::class)) {
36
-    require_once __DIR__ . '/Binding.php';
35
+if ( ! class_exists(Pixie\Binding::class)) {
36
+    require_once __DIR__.'/Binding.php';
37 37
 }
38
-if (!class_exists(Pixie\QueryBuilder\QueryBuilderHandler::class)) {
39
-    require_once __DIR__ . '/QueryBuilder/QueryBuilderHandler.php';
38
+if ( ! class_exists(Pixie\QueryBuilder\QueryBuilderHandler::class)) {
39
+    require_once __DIR__.'/QueryBuilder/QueryBuilderHandler.php';
40 40
 }
41
-if (!class_exists(Pixie\QueryBuilder\Transaction::class)) {
42
-    require_once __DIR__ . '/QueryBuilder/Transaction.php';
41
+if ( ! class_exists(Pixie\QueryBuilder\Transaction::class)) {
42
+    require_once __DIR__.'/QueryBuilder/Transaction.php';
43 43
 }
44
-if (!class_exists(Pixie\QueryBuilder\Raw::class)) {
45
-    require_once __DIR__ . '/QueryBuilder/Raw.php';
44
+if ( ! class_exists(Pixie\QueryBuilder\Raw::class)) {
45
+    require_once __DIR__.'/QueryBuilder/Raw.php';
46 46
 }
47
-if (!class_exists(Pixie\JSON\JsonHandler::class)) {
48
-    require_once __DIR__ . '/JSON/JsonHandler.php';
47
+if ( ! class_exists(Pixie\JSON\JsonHandler::class)) {
48
+    require_once __DIR__.'/JSON/JsonHandler.php';
49 49
 }
50
-if (!class_exists(Pixie\EventHandler::class)) {
51
-    require_once __DIR__ . '/EventHandler.php';
50
+if ( ! class_exists(Pixie\EventHandler::class)) {
51
+    require_once __DIR__.'/EventHandler.php';
52 52
 }
53
-if (!class_exists(Pixie\QueryBuilder\QueryObject::class)) {
54
-    require_once __DIR__ . '/QueryBuilder/QueryObject.php';
53
+if ( ! class_exists(Pixie\QueryBuilder\QueryObject::class)) {
54
+    require_once __DIR__.'/QueryBuilder/QueryObject.php';
55 55
 }
56
-if (!class_exists(Pixie\QueryBuilder\NestedCriteria::class)) {
57
-    require_once __DIR__ . '/QueryBuilder/NestedCriteria.php';
56
+if ( ! class_exists(Pixie\QueryBuilder\NestedCriteria::class)) {
57
+    require_once __DIR__.'/QueryBuilder/NestedCriteria.php';
58 58
 }
59
-if (!class_exists(Pixie\QueryBuilder\JoinBuilder::class)) {
60
-    require_once __DIR__ . '/QueryBuilder/JoinBuilder.php';
59
+if ( ! class_exists(Pixie\QueryBuilder\JoinBuilder::class)) {
60
+    require_once __DIR__.'/QueryBuilder/JoinBuilder.php';
61 61
 }
62
-if (!class_exists(Pixie\Event::class)) {
63
-    require_once __DIR__ . '/Event.php';
62
+if ( ! class_exists(Pixie\Event::class)) {
63
+    require_once __DIR__.'/Event.php';
64 64
 }
65
-if (!class_exists(Pixie\JSON\JsonSelectorHandler::class)) {
66
-    require_once __DIR__ . '/JSON/JsonSelectorHandler.php';
65
+if ( ! class_exists(Pixie\JSON\JsonSelectorHandler::class)) {
66
+    require_once __DIR__.'/JSON/JsonSelectorHandler.php';
67 67
 }
68
-if (!class_exists(Pixie\JSON\JsonSelector::class)) {
69
-    require_once __DIR__ . '/JSON/JsonSelector.php';
68
+if ( ! class_exists(Pixie\JSON\JsonSelector::class)) {
69
+    require_once __DIR__.'/JSON/JsonSelector.php';
70 70
 }
71
-if (!class_exists(Pixie\Connection::class)) {
72
-    require_once __DIR__ . '/Connection.php';
71
+if ( ! class_exists(Pixie\Connection::class)) {
72
+    require_once __DIR__.'/Connection.php';
73 73
 }
74
-if (!class_exists(Pixie\QueryBuilder\JsonQueryBuilder::class)) {
75
-    require_once __DIR__ . '/QueryBuilder/JsonQueryBuilder.php';
74
+if ( ! class_exists(Pixie\QueryBuilder\JsonQueryBuilder::class)) {
75
+    require_once __DIR__.'/QueryBuilder/JsonQueryBuilder.php';
76 76
 }
77
-if (!class_exists(Pixie\Hydration\Hydrator::class)) {
78
-    require_once __DIR__ . '/Hydration/Hydrator.php';
77
+if ( ! class_exists(Pixie\Hydration\Hydrator::class)) {
78
+    require_once __DIR__.'/Hydration/Hydrator.php';
79 79
 }
80
-if (!class_exists(Pixie\QueryBuilder\WPDBAdapter::class)) {
81
-    require_once __DIR__ . '/QueryBuilder/WPDBAdapter.php';
80
+if ( ! class_exists(Pixie\QueryBuilder\WPDBAdapter::class)) {
81
+    require_once __DIR__.'/QueryBuilder/WPDBAdapter.php';
82 82
 }
83
-if (!class_exists(Pixie\Exception::class)) {
84
-    require_once __DIR__ . '/Exception.php';
83
+if ( ! class_exists(Pixie\Exception::class)) {
84
+    require_once __DIR__.'/Exception.php';
85 85
 }
86
-if (!class_exists(Pixie\QueryBuilder\TransactionHaltException::class)) {
87
-    require_once __DIR__ . '/QueryBuilder/TransactionHaltException.php';
86
+if ( ! class_exists(Pixie\QueryBuilder\TransactionHaltException::class)) {
87
+    require_once __DIR__.'/QueryBuilder/TransactionHaltException.php';
88 88
 }
89
-if (!class_exists(Pixie\AliasFacade::class)) {
90
-    require_once __DIR__ . '/AliasFacade.php';
89
+if ( ! class_exists(Pixie\AliasFacade::class)) {
90
+    require_once __DIR__.'/AliasFacade.php';
91 91
 }
92
-if (!class_exists(Pixie\JSON\JsonExpressionFactory::class)) {
93
-    require_once __DIR__ . '/JSON/JsonExpressionFactory.php';
92
+if ( ! class_exists(Pixie\JSON\JsonExpressionFactory::class)) {
93
+    require_once __DIR__.'/JSON/JsonExpressionFactory.php';
94 94
 }
95 95
 // CREATED ON Fri 15th April 2022
Please login to merge, or discard this patch.