Completed
Push — b-having-tests ( 8fbf46...55764e )
by Hung
02:31
created

SphinxQL::setSelect()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 10
Ratio 100 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 10
loc 10
rs 9.4285
cc 2
eloc 6
nc 2
nop 1
1
<?php
2
3
namespace Foolz\SphinxQL;
4
use Foolz\SphinxQL\Drivers\ConnectionInterface;
5
use Foolz\SphinxQL\Exception\SphinxQLException;
6
use Foolz\SphinxQL\Drivers\MultiResultSetInterface;
7
use Foolz\SphinxQL\Drivers\ResultSetInterface;
8
9
/**
10
 * Query Builder class for SphinxQL statements.
11
 * @package Foolz\SphinxQL
12
 */
13
class SphinxQL
14
{
15
    /**
16
     * A non-static connection for the current SphinxQL object
17
     *
18
     * @var ConnectionInterface
19
     */
20
    protected $connection = null;
21
22
    /**
23
     * The last result object.
24
     *
25
     * @var array
26
     */
27
    protected $last_result = null;
28
29
    /**
30
     * The last compiled query.
31
     *
32
     * @var string
33
     */
34
    protected $last_compiled = null;
35
36
    /**
37
     * The last chosen method (select, insert, replace, update, delete).
38
     *
39
     * @var string
40
     */
41
    protected $type = null;
42
43
    /**
44
     * An SQL query that is not yet executed or "compiled"
45
     *
46
     * @var string
47
     */
48
    protected $query = null;
49
50
    /**
51
     * Array of select elements that will be comma separated.
52
     *
53
     * @var array
54
     */
55
    protected $select = array();
56
57
    /**
58
     * From in SphinxQL is the list of indexes that will be used
59
     *
60
     * @var array
61
     */
62
    protected $from = array();
63
64
    /**
65
     * The list of where and parenthesis, must be inserted in order
66
     *
67
     * @var array
68
     */
69
    protected $where = array();
70
71
    /**
72
     * The list of matches for the MATCH function in SphinxQL
73
     *
74
     * @var array
75
     */
76
    protected $match = array();
77
78
    /**
79
     * GROUP BY array to be comma separated
80
     *
81
     * @var array
82
     */
83
    protected $group_by = array();
84
85
    /**
86
     * ORDER BY array
87
     *
88
     * @var array
89
     */
90
    protected $within_group_order_by = array();
91
92
    /**
93
     * The list of where and parenthesis, must be inserted in order
94
     *
95
     * @var array
96
     */
97
    protected $having = array();
98
99
    /**
100
     * ORDER BY array
101
     *
102
     * @var array
103
     */
104
    protected $order_by = array();
105
106
    /**
107
     * When not null it adds an offset
108
     *
109
     * @var null|int
110
     */
111
    protected $offset = null;
112
113
    /**
114
     * When not null it adds a limit
115
     *
116
     * @var null|int
117
     */
118
    protected $limit = null;
119
120
    /**
121
     * Value of INTO query for INSERT or REPLACE
122
     *
123
     * @var null|string
124
     */
125
    protected $into = null;
126
127
    /**
128
     * Array of columns for INSERT or REPLACE
129
     *
130
     * @var array
131
     */
132
    protected $columns = array();
133
134
    /**
135
     * Array OF ARRAYS of values for INSERT or REPLACE
136
     *
137
     * @var array
138
     */
139
    protected $values = array();
140
141
    /**
142
     * Array arrays containing column and value for SET in UPDATE
143
     *
144
     * @var array
145
     */
146
    protected $set = array();
147
148
    /**
149
     * Array of OPTION specific to SphinxQL
150
     *
151
     * @var array
152
     */
153
    protected $options = array();
154
155
    /**
156
     * Array of FACETs
157
     *
158
     * @var Facet[]
159
     */
160
    protected $facets = array();
161
162
    /**
163
     * The reference to the object that queued itself and created this object
164
     *
165
     * @var null|SphinxQL
166
     */
167
    protected $queue_prev = null;
168
169
    /**
170
     * An array of escaped characters for escapeMatch()
171
     * @var array
172
     */
173
    protected $escape_full_chars = array(
174
        '\\' => '\\\\',
175
        '(' => '\(',
176
        ')' => '\)',
177
        '|' => '\|',
178
        '-' => '\-',
179
        '!' => '\!',
180
        '@' => '\@',
181
        '~' => '\~',
182
        '"' => '\"',
183
        '&' => '\&',
184
        '/' => '\/',
185
        '^' => '\^',
186
        '$' => '\$',
187
        '=' => '\=',
188
        '<' => '\<',
189
    );
190
191
    /**
192
     * An array of escaped characters for fullEscapeMatch()
193
     * @var array
194
     */
195
    protected $escape_half_chars = array(
196
        '\\' => '\\\\',
197
        '(' => '\(',
198
        ')' => '\)',
199
        '!' => '\!',
200
        '@' => '\@',
201
        '~' => '\~',
202
        '&' => '\&',
203
        '/' => '\/',
204
        '^' => '\^',
205
        '$' => '\$',
206
        '=' => '\=',
207
        '<' => '\<',
208
    );
209
210
    public function __construct(ConnectionInterface $connection = null, $static = false)
0 ignored issues
show
Unused Code introduced by
The parameter $static is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
211
    {
212
        $this->connection = $connection;
213
    }
214
215
    /**
216
     * Creates and setups a SphinxQL object
217
     *
218
     * @param ConnectionInterface $connection
219
     *
220
     * @return SphinxQL
221
     */
222
    public static function create(ConnectionInterface $connection)
223
    {
224
        return new static($connection);
225
    }
226
227
    /**
228
     * Returns the currently attached connection
229
     *
230
     * @returns ConnectionInterface
231
     */
232
    public function getConnection()
233
    {
234
        return $this->connection;
235
    }
236
237
    /**
238
     * Avoids having the expressions escaped
239
     *
240
     * Examples:
241
     *    $query->where('time', '>', SphinxQL::expr('CURRENT_TIMESTAMP'));
242
     *    // WHERE time > CURRENT_TIMESTAMP
243
     *
244
     * @param string $string The string to keep unaltered
245
     *
246
     * @return Expression The new Expression
247
     */
248
    public static function expr($string = '')
249
    {
250
        return new Expression($string);
251
    }
252
253
    /**
254
     * Runs the query built
255
     *
256
     * @return ResultSetInterface The result of the query
257
     */
258
    public function execute()
259
    {
260
        // pass the object so execute compiles it by itself
261
        return $this->last_result = $this->getConnection()->query($this->compile()->getCompiled());
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->getConnection()->...mpile()->getCompiled()) of type object<Foolz\SphinxQL\Drivers\ResultSetInterface> is incompatible with the declared type array of property $last_result.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
262
    }
263
264
    /**
265
     * Executes a batch of queued queries
266
     *
267
     * @return MultiResultSetInterface The array of results
268
     * @throws SphinxQLException In case no query is in queue
269
     */
270
    public function executeBatch()
271
    {
272
        if (count($this->getQueue()) == 0) {
273
            throw new SphinxQLException('There is no Queue present to execute.');
274
        }
275
276
        $queue = array();
277
278
        foreach ($this->getQueue() as $query) {
279
            $queue[] = $query->compile()->getCompiled();
280
        }
281
282
        return $this->last_result = $this->getConnection()->multiQuery($queue);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->getConnection()->multiQuery($queue) of type object<Foolz\SphinxQL\Dr...ultiResultSetInterface> is incompatible with the declared type array of property $last_result.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
283
    }
284
285
    /**
286
     * Enqueues the current object and returns a new one or the supplied one
287
     *
288
     * @param SphinxQL|null $next
289
     *
290
     * @return SphinxQL A new SphinxQL object with the current object referenced
291
     */
292
    public function enqueue(SphinxQL $next = null)
293
    {
294
        if ($next === null) {
295
            $next = new static($this->getConnection());
296
        }
297
298
        $next->setQueuePrev($this);
299
300
        return $next;
301
    }
302
303
    /**
304
     * Returns the ordered array of enqueued objects
305
     *
306
     * @return SphinxQL[] The ordered array of enqueued objects
307
     */
308
    public function getQueue()
309
    {
310
        $queue = array();
311
        $curr = $this;
312
313
        do {
314
            if ($curr->type != null) {
315
                $queue[] = $curr;
316
            }
317
        } while ($curr = $curr->getQueuePrev());
318
319
        return array_reverse($queue);
320
    }
321
322
    /**
323
     * Gets the enqueued object
324
     *
325
     * @return SphinxQL|null
326
     */
327
    public function getQueuePrev()
328
    {
329
        return $this->queue_prev;
330
    }
331
332
    /**
333
     * Sets the reference to the enqueued object
334
     *
335
     * @param SphinxQL $query The object to set as previous
336
     *
337
     * @return SphinxQL
338
     */
339
    public function setQueuePrev($query)
340
    {
341
        $this->queue_prev = $query;
342
343
        return $this;
344
    }
345
346
    /**
347
     * Returns the result of the last query
348
     *
349
     * @return array The result of the last query
350
     */
351
    public function getResult()
352
    {
353
        return $this->last_result;
354
    }
355
356
    /**
357
     * Returns the latest compiled query
358
     *
359
     * @return string The last compiled query
360
     */
361
    public function getCompiled()
362
    {
363
        return $this->last_compiled;
364
    }
365
366
    /**
367
     * Begins transaction
368
     */
369
    public function transactionBegin()
370
    {
371
        $this->getConnection()->query('BEGIN');
372
    }
373
374
    /**
375
     * Commits transaction
376
     */
377
    public function transactionCommit()
378
    {
379
        $this->getConnection()->query('COMMIT');
380
    }
381
382
    /**
383
     * Rollbacks transaction
384
     */
385
    public function transactionRollback()
386
    {
387
        $this->getConnection()->query('ROLLBACK');
388
    }
389
390
    /**
391
     * Runs the compile function
392
     *
393
     * @return SphinxQL
394
     */
395
    public function compile()
396
    {
397
        switch ($this->type) {
398
            case 'select':
399
                $this->compileSelect();
400
                break;
401
            case 'insert':
402
            case 'replace':
403
                $this->compileInsert();
404
                break;
405
            case 'update':
406
                $this->compileUpdate();
407
                break;
408
            case 'delete':
409
                $this->compileDelete();
410
                break;
411
            case 'query':
412
                $this->compileQuery();
413
                break;
414
        }
415
416
        return $this;
417
    }
418
419
    public function compileQuery()
420
    {
421
        $this->last_compiled = $this->query;
422
423
        return $this;
424
    }
425
426
    /**
427
     * Compiles the MATCH part of the queries
428
     * Used by: SELECT, DELETE, UPDATE
429
     *
430
     * @return string The compiled MATCH
431
     */
432
    public function compileMatch()
433
    {
434
        $query = '';
435
436
        if (!empty($this->match)) {
437
            $query .= 'WHERE MATCH(';
438
439
            $matched = array();
440
441
            foreach ($this->match as $match) {
442
                $pre = '';
443
                if ($match['column'] instanceof \Closure) {
444
                    $sub = new Match($this);
445
                    call_user_func($match['column'], $sub);
446
                    $pre .= $sub->compile()->getCompiled();
447
                } elseif ($match['column'] instanceof Match) {
448
                    $pre .= $match['column']->compile()->getCompiled();
449
                } elseif (empty($match['column'])) {
450
                    $pre .= '';
451
                } elseif (is_array($match['column'])) {
452
                    $pre .= '@('.implode(',', $match['column']).') ';
453
                } else {
454
                    $pre .= '@'.$match['column'].' ';
455
                }
456
457
                if ($match['half']) {
458
                    $pre .= $this->halfEscapeMatch($match['value']);
459
                } else {
460
                    $pre .= $this->escapeMatch($match['value']);
461
                }
462
463
                $matched[] = '('.$pre.')';
464
            }
465
466
            $matched = implode(' ', $matched);
467
            $query .= $this->getConnection()->escape(trim($matched)).') ';
468
        }
469
        return $query;
470
    }
471
472
    /**
473
     * Compiles the WHERE part of the queries
474
     * It interacts with the MATCH() and of course isn't usable stand-alone
475
     * Used by: SELECT, DELETE, UPDATE
476
     *
477
     * @return string The compiled WHERE
478
     */
479
    public function compileWhere()
480
    {
481
        $query = '';
482
483
        if (empty($this->match) && !empty($this->where)) {
484
            $query .= 'WHERE ';
485
        }
486
487
        if (!empty($this->where)) {
488
            foreach ($this->where as $key => $where) {
489
                if ($key > 0 || !empty($this->match)) {
490
                    $query .= 'AND ';
491
                }
492
                $query .= $this->compileFilterCondition($where);
493
            }
494
        }
495
496
        return $query;
497
    }
498
499
    public function compileFilterCondition($filter)
500
    {
501
        $query = '';
502
503
        if (!empty($filter)) {
504
            if (strtoupper($filter['operator']) === 'BETWEEN') {
505
                $query .= $filter['column'];
506
                $query .= ' BETWEEN ';
507
                $query .= $this->getConnection()->quote($filter['value'][0]).' AND '
508
                    .$this->getConnection()->quote($filter['value'][1]).' ';
509
            } else {
510
                // id can't be quoted!
511
                if ($filter['column'] === 'id') {
512
                    $query .= 'id ';
513
                } else {
514
                    $query .= $filter['column'].' ';
515
                }
516
517
                if (in_array(strtoupper($filter['operator']), array('IN', 'NOT IN'), true)) {
518
                    $query .= strtoupper($filter['operator']).' ('.implode(', ', $this->getConnection()->quoteArr($filter['value'])).') ';
519
                } else {
520
                    $query .= $filter['operator'].' '.$this->getConnection()->quote($filter['value']).' ';
521
                }
522
            }
523
        }
524
525
        return $query;
526
    }
527
528
    /**
529
     * Compiles the statements for SELECT
530
     *
531
     * @return SphinxQL
532
     */
533
    public function compileSelect()
534
    {
535
        $query = '';
536
537
        if ($this->type == 'select') {
538
            $query .= 'SELECT ';
539
540 View Code Duplication
            if (!empty($this->select)) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
541
                $query .= implode(', ', $this->select).' ';
542
            } else {
543
                $query .= '* ';
544
            }
545
        }
546
547
        if (!empty($this->from)) {
548
            if ($this->from instanceof \Closure) {
549
                $sub = new static($this->getConnection());
550
                call_user_func($this->from, $sub);
551
                $query .= 'FROM ('.$sub->compile()->getCompiled().') ';
552
            } elseif ($this->from instanceof SphinxQL) {
553
                $query .= 'FROM ('.$this->from->compile()->getCompiled().') ';
554
            } else {
555
                $query .= 'FROM '.implode(', ', $this->from).' ';
556
            }
557
        }
558
559
        $query .= $this->compileMatch().$this->compileWhere();
560
561 View Code Duplication
        if (!empty($this->group_by)) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
562
            $query .= 'GROUP BY '.implode(', ', $this->group_by).' ';
563
        }
564
565 View Code Duplication
        if (!empty($this->within_group_order_by)) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
566
            $query .= 'WITHIN GROUP ORDER BY ';
567
568
            $order_arr = array();
569
570
            foreach ($this->within_group_order_by as $order) {
571
                $order_sub = $order['column'].' ';
572
573
                if ($order['direction'] !== null) {
574
                    $order_sub .= ((strtolower($order['direction']) === 'desc') ? 'DESC' : 'ASC');
575
                }
576
577
                $order_arr[] = $order_sub;
578
            }
579
580
            $query .= implode(', ', $order_arr).' ';
581
        }
582
583
        if (!empty($this->having)) {
584
            $query .= 'HAVING '.$this->compileFilterCondition($this->having);
585
        }
586
587 View Code Duplication
        if (!empty($this->order_by)) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
588
            $query .= 'ORDER BY ';
589
590
            $order_arr = array();
591
592
            foreach ($this->order_by as $order) {
593
                $order_sub = $order['column'].' ';
594
595
                if ($order['direction'] !== null) {
596
                    $order_sub .= ((strtolower($order['direction']) === 'desc') ? 'DESC' : 'ASC');
597
                }
598
599
                $order_arr[] = $order_sub;
600
            }
601
602
            $query .= implode(', ', $order_arr).' ';
603
        }
604
605 View Code Duplication
        if ($this->limit !== null || $this->offset !== null) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
606
            if ($this->offset === null) {
607
                $this->offset = 0;
608
            }
609
610
            if ($this->limit === null) {
611
                $this->limit = 9999999999999;
612
            }
613
614
            $query .= 'LIMIT '.((int) $this->offset).', '.((int) $this->limit).' ';
615
        }
616
617
        if (!empty($this->options)) {
618
            $options = array();
619
620
            foreach ($this->options as $option) {
621
                if ($option['value'] instanceof Expression) {
622
                    $option['value'] = $option['value']->value();
623
                } elseif (is_array($option['value'])) {
624
                    array_walk(
625
                        $option['value'],
626
                        function (&$val, $key) {
627
                            $val = $key.'='.$val;
628
                        }
629
                    );
630
                    $option['value'] = '('.implode(', ', $option['value']).')';
631
                } else {
632
                    $option['value'] = $this->getConnection()->quote($option['value']);
633
                }
634
635
                $options[] = $option['name'].' = '.$option['value'];
636
            }
637
638
            $query .= 'OPTION '.implode(', ', $options).' ';
639
        }
640
641
        if (!empty($this->facets)) {
642
            $facets = array();
643
644
            foreach ($this->facets as $facet) {
645
                // dynamically set the own SphinxQL connection if the Facet doesn't own one
646
                if ($facet->getConnection() === null) {
647
                    $facet->setConnection($this->getConnection());
648
                    $facets[] = $facet->getFacet();
649
                    // go back to the status quo for reuse
650
                    $facet->setConnection();
651
                } else {
652
                    $facets[] = $facet->getFacet();
653
                }
654
            }
655
656
            $query .= implode(' ', $facets);
657
        }
658
659
        $query = trim($query);
660
        $this->last_compiled = $query;
661
662
        return $this;
663
    }
664
665
    /**
666
     * Compiles the statements for INSERT or REPLACE
667
     *
668
     * @return SphinxQL
669
     */
670
    public function compileInsert()
671
    {
672
        if ($this->type == 'insert') {
673
            $query = 'INSERT ';
674
        } else {
675
            $query = 'REPLACE ';
676
        }
677
678
        if ($this->into !== null) {
679
            $query .= 'INTO '.$this->into.' ';
680
        }
681
682 View Code Duplication
        if (!empty($this->columns)) {
683
            $query .= '('.implode(', ', $this->columns).') ';
684
        }
685
686
        if (!empty($this->values)) {
687
            $query .= 'VALUES ';
688
            $query_sub = '';
689
690
            foreach ($this->values as $value) {
691
                $query_sub[] = '('.implode(', ', $this->getConnection()->quoteArr($value)).')';
692
            }
693
694
            $query .= implode(', ', $query_sub);
695
        }
696
697
        $query = trim($query);
698
        $this->last_compiled = $query;
699
700
        return $this;
701
    }
702
703
    /**
704
     * Compiles the statements for UPDATE
705
     *
706
     * @return SphinxQL
707
     */
708
    public function compileUpdate()
709
    {
710
        $query = 'UPDATE ';
711
712
        if ($this->into !== null) {
713
            $query .= $this->into.' ';
714
        }
715
716
        if (!empty($this->set)) {
717
            $query .= 'SET ';
718
719
            $query_sub = array();
720
721
            foreach ($this->set as $column => $value) {
722
                // MVA support
723
                if (is_array($value)) {
724
                    $query_sub[] = $column
725
                        .' = ('.implode(', ', $this->getConnection()->quoteArr($value)).')';
726
                } else {
727
                    $query_sub[] = $column
728
                        .' = '.$this->getConnection()->quote($value);
729
                }
730
            }
731
732
            $query .= implode(', ', $query_sub).' ';
733
        }
734
735
        $query .= $this->compileMatch().$this->compileWhere();
736
737
        $query = trim($query);
738
        $this->last_compiled = $query;
739
740
        return $this;
741
    }
742
743
    /**
744
     * Compiles the statements for DELETE
745
     *
746
     * @return SphinxQL
747
     */
748
    public function compileDelete()
749
    {
750
        $query = 'DELETE ';
751
752
        if (!empty($this->from)) {
753
            $query .= 'FROM '.$this->from[0].' ';
754
        }
755
756
        if (!empty($this->where)) {
757
            $query .= $this->compileWhere();
758
        }
759
760
        $query = trim($query);
761
        $this->last_compiled = $query;
762
763
        return $this;
764
    }
765
766
    /**
767
     * Sets a query to be executed
768
     *
769
     * @param string $sql A SphinxQL query to execute
770
     *
771
     * @return SphinxQL
772
     */
773
    public function query($sql)
774
    {
775
        $this->type = 'query';
776
        $this->query = $sql;
777
778
        return $this;
779
    }
780
781
    /**
782
     * Select the columns
783
     *
784
     * Gets the arguments passed as $sphinxql->select('one', 'two')
785
     * Using it without arguments equals to having '*' as argument
786
     * Using it with array maps values as column names
787
     *
788
     * Examples:
789
     *    $query->select('title');
790
     *    // SELECT title
791
     *
792
     *    $query->select('title', 'author', 'date');
793
     *    // SELECT title, author, date
794
     *
795
     *    $query->select(['id', 'title']);
796
     *    // SELECT id, title
797
     *
798
     * @param array|string $columns Array or multiple string arguments containing column names
799
     *
800
     * @return SphinxQL
801
     */
802 View Code Duplication
    public function select($columns = null)
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
803
    {
804
        $this->reset();
805
        $this->type = 'select';
806
807
        if (is_array($columns)) {
808
            $this->select = $columns;
809
        } else {
810
            $this->select = \func_get_args();
811
        }
812
813
        return $this;
814
    }
815
816
    /**
817
     * Alters which arguments to select
818
     *
819
     * Query is assumed to be in SELECT mode
820
     * See select() for usage
821
     *
822
     * @param array|string $columns Array or multiple string arguments containing column names
823
     *
824
     * @return SphinxQL
825
     */
826 View Code Duplication
    public function setSelect($columns = null)
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
827
    {
828
        if (is_array($columns)) {
829
            $this->select = $columns;
830
        } else {
831
            $this->select = \func_get_args();
832
        }
833
834
        return $this;
835
    }
836
837
    /**
838
     * Get the columns staged to select
839
     *
840
     * @return array
841
     */
842
    public function getSelect()
843
    {
844
        return $this->select;
845
    }
846
847
    /**
848
     * Activates the INSERT mode
849
     *
850
     * @return SphinxQL
851
     */
852
    public function insert()
853
    {
854
        $this->reset();
855
        $this->type = 'insert';
856
857
        return $this;
858
    }
859
860
    /**
861
     * Activates the REPLACE mode
862
     *
863
     * @return SphinxQL
864
     */
865
    public function replace()
866
    {
867
        $this->reset();
868
        $this->type = 'replace';
869
870
        return $this;
871
    }
872
873
    /**
874
     * Activates the UPDATE mode
875
     *
876
     * @param string $index The index to update into
877
     *
878
     * @return SphinxQL
879
     */
880
    public function update($index)
881
    {
882
        $this->reset();
883
        $this->type = 'update';
884
        $this->into($index);
885
886
        return $this;
887
    }
888
889
    /**
890
     * Activates the DELETE mode
891
     *
892
     * @return SphinxQL
893
     */
894
    public function delete()
895
    {
896
        $this->reset();
897
        $this->type = 'delete';
898
899
        return $this;
900
    }
901
902
    /**
903
     * FROM clause (Sphinx-specific since it works with multiple indexes)
904
     * func_get_args()-enabled
905
     *
906
     * @param array $array An array of indexes to use
907
     *
908
     * @return SphinxQL
909
     */
910
    public function from($array = null)
911
    {
912
        if (is_string($array)) {
913
            $this->from = \func_get_args();
914
        }
915
916
        if (is_array($array) || $array instanceof \Closure || $array instanceof SphinxQL) {
917
            $this->from = $array;
918
        }
919
920
        return $this;
921
    }
922
923
    /**
924
     * MATCH clause (Sphinx-specific)
925
     *
926
     * @param mixed    $column The column name (can be array, string, Closure, or Match)
927
     * @param string   $value  The value
928
     * @param boolean  $half  Exclude ", |, - control characters from being escaped
929
     *
930
     * @return SphinxQL
931
     */
932
    public function match($column, $value = null, $half = false)
933
    {
934
        if ($column === '*' || (is_array($column) && in_array('*', $column))) {
935
            $column = array();
936
        }
937
938
        $this->match[] = array('column' => $column, 'value' => $value, 'half' => $half);
939
940
        return $this;
941
    }
942
943
    /**
944
     * WHERE clause
945
     *
946
     * Examples:
947
     *    $query->where('column', 'value');
948
     *    // WHERE column = 'value'
949
     *
950
     *    $query->where('column', '=', 'value');
951
     *    // WHERE column = 'value'
952
     *
953
     *    $query->where('column', '>=', 'value')
954
     *    // WHERE column >= 'value'
955
     *
956
     *    $query->where('column', 'IN', array('value1', 'value2', 'value3'));
957
     *    // WHERE column IN ('value1', 'value2', 'value3')
958
     *
959
     *    $query->where('column', 'BETWEEN', array('value1', 'value2'))
960
     *    // WHERE column BETWEEN 'value1' AND 'value2'
961
     *    // WHERE example BETWEEN 10 AND 100
962
     *
963
     * @param string   $column   The column name
964
     * @param string   $operator The operator to use
965
     * @param string   $value    The value to check against
966
     *
967
     * @return SphinxQL
968
     */
969 View Code Duplication
    public function where($column, $operator, $value = null)
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
970
    {
971
        if ($value === null) {
972
            $value = $operator;
973
            $operator = '=';
974
        }
975
976
        $this->where[] = array(
977
            'column' => $column,
978
            'operator' => $operator,
979
            'value' => $value
980
        );
981
982
        return $this;
983
    }
984
985
    /**
986
     * GROUP BY clause
987
     * Adds to the previously added columns
988
     *
989
     * @param string $column A column to group by
990
     *
991
     * @return SphinxQL
992
     */
993
    public function groupBy($column)
994
    {
995
        $this->group_by[] = $column;
996
997
        return $this;
998
    }
999
1000
    /**
1001
     * WITHIN GROUP ORDER BY clause (SphinxQL-specific)
1002
     * Adds to the previously added columns
1003
     * Works just like a classic ORDER BY
1004
     *
1005
     * @param string $column    The column to group by
1006
     * @param string $direction The group by direction (asc/desc)
1007
     *
1008
     * @return SphinxQL
1009
     */
1010
    public function withinGroupOrderBy($column, $direction = null)
1011
    {
1012
        $this->within_group_order_by[] = array('column' => $column, 'direction' => $direction);
1013
1014
        return $this;
1015
    }
1016
1017
    /**
1018
     * HAVING clause
1019
     *
1020
     * Examples:
1021
     *    $sq->having('column', 'value');
1022
     *    // HAVING column = 'value'
1023
     *
1024
     *    $sq->having('column', '=', 'value');
1025
     *    // HAVING column = 'value'
1026
     *
1027
     *    $sq->having('column', '>=', 'value')
1028
     *    // HAVING column >= 'value'
1029
     *
1030
     *    $sq->having('column', 'IN', array('value1', 'value2', 'value3'));
1031
     *    // HAVING column IN ('value1', 'value2', 'value3')
1032
     *
1033
     *    $sq->having('column', 'BETWEEN', array('value1', 'value2'))
1034
     *    // HAVING column BETWEEN 'value1' AND 'value2'
1035
     *    // HAVING example BETWEEN 10 AND 100
1036
     *
1037
     * @param string   $column   The column name
1038
     * @param string   $operator The operator to use
1039
     * @param string   $value    The value to check against
1040
     *
1041
     * @return SphinxQL The current object
1042
     */
1043 View Code Duplication
    public function having($column, $operator, $value = null)
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1044
    {
1045
        if ($value === null) {
1046
            $value = $operator;
1047
            $operator = '=';
1048
        }
1049
1050
        $this->having = array(
1051
            'column' => $column,
1052
            'operator' => $operator,
1053
            'value' => $value
1054
        );
1055
1056
        return $this;
1057
    }
1058
1059
    /**
1060
     * ORDER BY clause
1061
     * Adds to the previously added columns
1062
     *
1063
     * @param string $column    The column to order on
1064
     * @param string $direction The ordering direction (asc/desc)
1065
     *
1066
     * @return SphinxQL
1067
     */
1068
    public function orderBy($column, $direction = null)
1069
    {
1070
        $this->order_by[] = array('column' => $column, 'direction' => $direction);
1071
1072
        return $this;
1073
    }
1074
1075
    /**
1076
     * LIMIT clause
1077
     * Supports also LIMIT offset, limit
1078
     *
1079
     * @param int      $offset Offset if $limit is specified, else limit
1080
     * @param null|int $limit  The limit to set, null for no limit
1081
     *
1082
     * @return SphinxQL
1083
     */
1084 View Code Duplication
    public function limit($offset, $limit = null)
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1085
    {
1086
        if ($limit === null) {
1087
            $this->limit = (int) $offset;
1088
            return $this;
1089
        }
1090
1091
        $this->offset($offset);
1092
        $this->limit = (int) $limit;
1093
1094
        return $this;
1095
    }
1096
1097
    /**
1098
     * OFFSET clause
1099
     *
1100
     * @param int $offset The offset
1101
     *
1102
     * @return SphinxQL
1103
     */
1104
    public function offset($offset)
1105
    {
1106
        $this->offset = (int) $offset;
1107
1108
        return $this;
1109
    }
1110
1111
    /**
1112
     * OPTION clause (SphinxQL-specific)
1113
     * Used by: SELECT
1114
     *
1115
     * @param string $name  Option name
1116
     * @param string $value Option value
1117
     *
1118
     * @return SphinxQL
1119
     */
1120
    public function option($name, $value)
1121
    {
1122
        $this->options[] = array('name' => $name, 'value' => $value);
1123
1124
        return $this;
1125
    }
1126
1127
    /**
1128
     * INTO clause
1129
     * Used by: INSERT, REPLACE
1130
     *
1131
     * @param string $index The index to insert/replace into
1132
     *
1133
     * @return SphinxQL
1134
     */
1135
    public function into($index)
1136
    {
1137
        $this->into = $index;
1138
1139
        return $this;
1140
    }
1141
1142
    /**
1143
     * Set columns
1144
     * Used in: INSERT, REPLACE
1145
     * func_get_args()-enabled
1146
     *
1147
     * @param array $array The array of columns
1148
     *
1149
     * @return SphinxQL
1150
     */
1151 View Code Duplication
    public function columns($array = array())
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1152
    {
1153
        if (is_array($array)) {
1154
            $this->columns = $array;
1155
        } else {
1156
            $this->columns = \func_get_args();
1157
        }
1158
1159
        return $this;
1160
    }
1161
1162
    /**
1163
     * Set VALUES
1164
     * Used in: INSERT, REPLACE
1165
     * func_get_args()-enabled
1166
     *
1167
     * @param array $array The array of values matching the columns from $this->columns()
1168
     *
1169
     * @return SphinxQL
1170
     */
1171
    public function values($array)
1172
    {
1173
        if (is_array($array)) {
1174
            $this->values[] = $array;
1175
        } else {
1176
            $this->values[] = \func_get_args();
1177
        }
1178
1179
        return $this;
1180
    }
1181
1182
    /**
1183
     * Set column and relative value
1184
     * Used in: INSERT, REPLACE
1185
     *
1186
     * @param string $column The column name
1187
     * @param string $value  The value
1188
     *
1189
     * @return SphinxQL
1190
     */
1191
    public function value($column, $value)
1192
    {
1193
        if ($this->type === 'insert' || $this->type === 'replace') {
1194
            $this->columns[] = $column;
1195
            $this->values[0][] = $value;
1196
        } else {
1197
            $this->set[$column] = $value;
1198
        }
1199
1200
        return $this;
1201
    }
1202
1203
    /**
1204
     * Allows passing an array with the key as column and value as value
1205
     * Used in: INSERT, REPLACE, UPDATE
1206
     *
1207
     * @param array $array Array of key-values
1208
     *
1209
     * @return SphinxQL
1210
     */
1211
    public function set($array)
1212
    {
1213
        if ($this->columns === array_keys($array)) {
1214
            $this->values($array);
1215
        } else {
1216
            foreach ($array as $key => $item) {
1217
                $this->value($key, $item);
1218
            }
1219
        }
1220
1221
        return $this;
1222
    }
1223
1224
    /**
1225
     * Allows passing an array with the key as column and value as value
1226
     * Used in: INSERT, REPLACE, UPDATE
1227
     *
1228
     * @param Facet $facet
1229
     * @return SphinxQL
1230
     */
1231
    public function facet($facet)
1232
    {
1233
        $this->facets[] = $facet;
1234
1235
        return $this;
1236
    }
1237
1238
    /**
1239
     * Sets the characters used for escapeMatch().
1240
     *
1241
     * @param array $array The array of characters to escape
1242
     *
1243
     * @return SphinxQL The escaped characters
1244
     */
1245
    public function setFullEscapeChars($array = array())
1246
    {
1247
        if (!empty($array)) {
1248
            $this->escape_full_chars = $this->compileEscapeChars($array);
1249
        }
1250
1251
        return $this;
1252
    }
1253
1254
    /**
1255
     * Sets the characters used for halfEscapeMatch().
1256
     *
1257
     * @param array $array The array of characters to escape
1258
     *
1259
     * @return SphinxQL The escaped characters
1260
     */
1261
    public function setHalfEscapeChars($array = array())
1262
    {
1263
        if (!empty($array)) {
1264
            $this->escape_half_chars = $this->compileEscapeChars($array);
1265
        }
1266
1267
        return $this;
1268
    }
1269
1270
    /**
1271
     * Compiles an array containing the characters and escaped characters into a key/value configuration.
1272
     *
1273
     * @param array $array The array of characters to escape
1274
     *
1275
     * @return array An array of the characters and it's escaped counterpart
1276
     */
1277
    public function compileEscapeChars($array = array())
1278
    {
1279
        $result = array();
1280
        foreach ($array as $character) {
1281
            $result[$character] = '\\'.$character;
1282
        }
1283
1284
        return $result;
1285
    }
1286
1287
    /**
1288
     * Escapes the query for the MATCH() function
1289
     *
1290
     * @param string $string The string to escape for the MATCH
1291
     *
1292
     * @return string The escaped string
1293
     */
1294
    public function escapeMatch($string)
1295
    {
1296
        if ($string instanceof Expression) {
1297
            return $string->value();
1298
        }
1299
1300
        return mb_strtolower(str_replace(array_keys($this->escape_full_chars), array_values($this->escape_full_chars), $string), 'utf8');
1301
    }
1302
1303
    /**
1304
     * Escapes the query for the MATCH() function
1305
     * Allows some of the control characters to pass through for use with a search field: -, |, "
1306
     * It also does some tricks to wrap/unwrap within " the string and prevents errors
1307
     *
1308
     * @param string $string The string to escape for the MATCH
1309
     *
1310
     * @return string The escaped string
1311
     */
1312
    public function halfEscapeMatch($string)
1313
    {
1314
        if ($string instanceof Expression) {
1315
            return $string->value();
1316
        }
1317
1318
        $string = str_replace(array_keys($this->escape_half_chars), array_values($this->escape_half_chars), $string);
1319
1320
        // this manages to lower the error rate by a lot
1321
        if (mb_substr_count($string, '"', 'utf8') % 2 !== 0) {
1322
            $string .= '"';
1323
        }
1324
1325
        $string = preg_replace('/-[\s-]*-/u', '-', $string);
1326
1327
        $from_to_preg = array(
1328
            '/([-|])\s*$/u'        => '\\\\\1',
1329
            '/\|[\s|]*\|/u'        => '|',
1330
            '/(\S+)-(\S+)/u'       => '\1\-\2',
1331
            '/(\S+)\s+-\s+(\S+)/u' => '\1 \- \2',
1332
        );
1333
1334
        $string = mb_strtolower(preg_replace(array_keys($from_to_preg), array_values($from_to_preg), $string), 'utf8');
1335
1336
        return $string;
1337
    }
1338
1339
    /**
1340
     * Clears the existing query build for new query when using the same SphinxQL instance.
1341
     *
1342
     * @return SphinxQL
1343
     */
1344
    public function reset()
1345
    {
1346
        $this->query = null;
1347
        $this->select = array();
1348
        $this->from = array();
1349
        $this->where = array();
1350
        $this->match = array();
1351
        $this->group_by = array();
1352
        $this->within_group_order_by = array();
1353
        $this->having = array();
1354
        $this->order_by = array();
1355
        $this->offset = null;
1356
        $this->limit = null;
1357
        $this->into = null;
1358
        $this->columns = array();
1359
        $this->values = array();
1360
        $this->set = array();
1361
        $this->options = array();
1362
1363
        return $this;
1364
    }
1365
1366
    public function resetWhere()
1367
    {
1368
        $this->where = array();
1369
1370
        return $this;
1371
    }
1372
1373
    public function resetMatch()
1374
    {
1375
        $this->match = array();
1376
1377
        return $this;
1378
    }
1379
1380
    public function resetGroupBy()
1381
    {
1382
        $this->group_by = array();
1383
1384
        return $this;
1385
    }
1386
1387
    public function resetWithinGroupOrderBy()
1388
    {
1389
        $this->within_group_order_by = array();
1390
1391
        return $this;
1392
    }
1393
1394
    public function resetHaving()
1395
    {
1396
        $this->having = array();
1397
1398
        return $this;
1399
    }
1400
1401
    public function resetOrderBy()
1402
    {
1403
        $this->order_by = array();
1404
1405
        return $this;
1406
    }
1407
1408
    public function resetOptions()
1409
    {
1410
        $this->options = array();
1411
1412
        return $this;
1413
    }
1414
}
1415