Completed
Pull Request — master (#130)
by
unknown
04:43
created

SphinxQL::resetHaving()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
c 0
b 0
f 0
rs 9.4285
cc 1
eloc 3
nc 1
nop 0
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
     * When not null changes 'GROUP BY' to 'GROUP N BY'
87
     *
88
     * @var null|int
89
     */
90
    protected $group_n_by = null;
91
92
    /**
93
     * ORDER BY array
94
     *
95
     * @var array
96
     */
97
    protected $within_group_order_by = array();
98
99
    /**
100
     * The list of where and parenthesis, must be inserted in order
101
     *
102
     * @var array
103
     */
104
    protected $having = array();
105
106
    /**
107
     * ORDER BY array
108
     *
109
     * @var array
110
     */
111
    protected $order_by = array();
112
113
    /**
114
     * When not null it adds an offset
115
     *
116
     * @var null|int
117
     */
118
    protected $offset = null;
119
120
    /**
121
     * When not null it adds a limit
122
     *
123
     * @var null|int
124
     */
125
    protected $limit = null;
126
127
    /**
128
     * Value of INTO query for INSERT or REPLACE
129
     *
130
     * @var null|string
131
     */
132
    protected $into = null;
133
134
    /**
135
     * Array of columns for INSERT or REPLACE
136
     *
137
     * @var array
138
     */
139
    protected $columns = array();
140
141
    /**
142
     * Array OF ARRAYS of values for INSERT or REPLACE
143
     *
144
     * @var array
145
     */
146
    protected $values = array();
147
148
    /**
149
     * Array arrays containing column and value for SET in UPDATE
150
     *
151
     * @var array
152
     */
153
    protected $set = array();
154
155
    /**
156
     * Array of OPTION specific to SphinxQL
157
     *
158
     * @var array
159
     */
160
    protected $options = array();
161
162
    /**
163
     * Array of FACETs
164
     *
165
     * @var Facet[]
166
     */
167
    protected $facets = array();
168
169
    /**
170
     * The reference to the object that queued itself and created this object
171
     *
172
     * @var null|SphinxQL
173
     */
174
    protected $queue_prev = null;
175
176
    /**
177
     * An array of escaped characters for escapeMatch()
178
     * @var array
179
     */
180
    protected $escape_full_chars = array(
181
        '\\' => '\\\\',
182
        '(' => '\(',
183
        ')' => '\)',
184
        '|' => '\|',
185
        '-' => '\-',
186
        '!' => '\!',
187
        '@' => '\@',
188
        '~' => '\~',
189
        '"' => '\"',
190
        '&' => '\&',
191
        '/' => '\/',
192
        '^' => '\^',
193
        '$' => '\$',
194
        '=' => '\=',
195
        '<' => '\<',
196
    );
197
198
    /**
199
     * An array of escaped characters for fullEscapeMatch()
200
     * @var array
201
     */
202
    protected $escape_half_chars = array(
203
        '\\' => '\\\\',
204
        '(' => '\(',
205
        ')' => '\)',
206
        '!' => '\!',
207
        '@' => '\@',
208
        '~' => '\~',
209
        '&' => '\&',
210
        '/' => '\/',
211
        '^' => '\^',
212
        '$' => '\$',
213
        '=' => '\=',
214
        '<' => '\<',
215
    );
216
217
    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...
218
    {
219
        $this->connection = $connection;
220
    }
221
222
    /**
223
     * Creates and setups a SphinxQL object
224
     *
225
     * @param ConnectionInterface $connection
226
     *
227
     * @return SphinxQL
228
     */
229
    public static function create(ConnectionInterface $connection)
230
    {
231
        return new static($connection);
232
    }
233
234
    /**
235
     * Returns the currently attached connection
236
     *
237
     * @returns ConnectionInterface
238
     */
239
    public function getConnection()
240
    {
241
        return $this->connection;
242
    }
243
244
    /**
245
     * Avoids having the expressions escaped
246
     *
247
     * Examples:
248
     *    $query->where('time', '>', SphinxQL::expr('CURRENT_TIMESTAMP'));
249
     *    // WHERE time > CURRENT_TIMESTAMP
250
     *
251
     * @param string $string The string to keep unaltered
252
     *
253
     * @return Expression The new Expression
254
     */
255
    public static function expr($string = '')
256
    {
257
        return new Expression($string);
258
    }
259
260
    /**
261
     * Runs the query built
262
     *
263
     * @return ResultSetInterface The result of the query
264
     */
265
    public function execute()
266
    {
267
        // pass the object so execute compiles it by itself
268
        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...
269
    }
270
271
    /**
272
     * Executes a batch of queued queries
273
     *
274
     * @return MultiResultSetInterface The array of results
275
     * @throws SphinxQLException In case no query is in queue
276
     */
277
    public function executeBatch()
278
    {
279
        if (count($this->getQueue()) == 0) {
280
            throw new SphinxQLException('There is no Queue present to execute.');
281
        }
282
283
        $queue = array();
284
285
        foreach ($this->getQueue() as $query) {
286
            $queue[] = $query->compile()->getCompiled();
287
        }
288
289
        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...
290
    }
291
292
    /**
293
     * Enqueues the current object and returns a new one or the supplied one
294
     *
295
     * @param SphinxQL|null $next
296
     *
297
     * @return SphinxQL A new SphinxQL object with the current object referenced
298
     */
299
    public function enqueue(SphinxQL $next = null)
300
    {
301
        if ($next === null) {
302
            $next = new static($this->getConnection());
303
        }
304
305
        $next->setQueuePrev($this);
306
307
        return $next;
308
    }
309
310
    /**
311
     * Returns the ordered array of enqueued objects
312
     *
313
     * @return SphinxQL[] The ordered array of enqueued objects
314
     */
315
    public function getQueue()
316
    {
317
        $queue = array();
318
        $curr = $this;
319
320
        do {
321
            if ($curr->type != null) {
322
                $queue[] = $curr;
323
            }
324
        } while ($curr = $curr->getQueuePrev());
325
326
        return array_reverse($queue);
327
    }
328
329
    /**
330
     * Gets the enqueued object
331
     *
332
     * @return SphinxQL|null
333
     */
334
    public function getQueuePrev()
335
    {
336
        return $this->queue_prev;
337
    }
338
339
    /**
340
     * Sets the reference to the enqueued object
341
     *
342
     * @param SphinxQL $query The object to set as previous
343
     *
344
     * @return SphinxQL
345
     */
346
    public function setQueuePrev($query)
347
    {
348
        $this->queue_prev = $query;
349
350
        return $this;
351
    }
352
353
    /**
354
     * Returns the result of the last query
355
     *
356
     * @return array The result of the last query
357
     */
358
    public function getResult()
359
    {
360
        return $this->last_result;
361
    }
362
363
    /**
364
     * Returns the latest compiled query
365
     *
366
     * @return string The last compiled query
367
     */
368
    public function getCompiled()
369
    {
370
        return $this->last_compiled;
371
    }
372
373
    /**
374
     * Begins transaction
375
     */
376
    public function transactionBegin()
377
    {
378
        $this->getConnection()->query('BEGIN');
379
    }
380
381
    /**
382
     * Commits transaction
383
     */
384
    public function transactionCommit()
385
    {
386
        $this->getConnection()->query('COMMIT');
387
    }
388
389
    /**
390
     * Rollbacks transaction
391
     */
392
    public function transactionRollback()
393
    {
394
        $this->getConnection()->query('ROLLBACK');
395
    }
396
397
    /**
398
     * Runs the compile function
399
     *
400
     * @return SphinxQL
401
     */
402
    public function compile()
403
    {
404
        switch ($this->type) {
405
            case 'select':
406
                $this->compileSelect();
407
                break;
408
            case 'insert':
409
            case 'replace':
410
                $this->compileInsert();
411
                break;
412
            case 'update':
413
                $this->compileUpdate();
414
                break;
415
            case 'delete':
416
                $this->compileDelete();
417
                break;
418
            case 'query':
419
                $this->compileQuery();
420
                break;
421
        }
422
423
        return $this;
424
    }
425
426
    public function compileQuery()
427
    {
428
        $this->last_compiled = $this->query;
429
430
        return $this;
431
    }
432
433
    /**
434
     * Compiles the MATCH part of the queries
435
     * Used by: SELECT, DELETE, UPDATE
436
     *
437
     * @return string The compiled MATCH
438
     */
439
    public function compileMatch()
440
    {
441
        $query = '';
442
443
        if (!empty($this->match)) {
444
            $query .= 'WHERE MATCH(';
445
446
            $matched = array();
447
448
            foreach ($this->match as $match) {
449
                $pre = '';
450
                if ($match['column'] instanceof \Closure) {
451
                    $sub = new Match($this);
452
                    call_user_func($match['column'], $sub);
453
                    $pre .= $sub->compile()->getCompiled();
454
                } elseif ($match['column'] instanceof Match) {
455
                    $pre .= $match['column']->compile()->getCompiled();
456
                } elseif (empty($match['column'])) {
457
                    $pre .= '';
458
                } elseif (is_array($match['column'])) {
459
                    $pre .= '@('.implode(',', $match['column']).') ';
460
                } else {
461
                    $pre .= '@'.$match['column'].' ';
462
                }
463
464
                if ($match['half']) {
465
                    $pre .= $this->halfEscapeMatch($match['value']);
466
                } else {
467
                    $pre .= $this->escapeMatch($match['value']);
468
                }
469
470
                if ($pre !== '') {
471
                    $matched[] = '('.$pre.')';
472
                }
473
            }
474
475
            $matched = implode(' ', $matched);
476
            $query .= $this->getConnection()->escape(trim($matched)).') ';
477
        }
478
        return $query;
479
    }
480
481
    /**
482
     * Compiles the WHERE part of the queries
483
     * It interacts with the MATCH() and of course isn't usable stand-alone
484
     * Used by: SELECT, DELETE, UPDATE
485
     *
486
     * @return string The compiled WHERE
487
     */
488
    public function compileWhere()
489
    {
490
        $query = '';
491
492
        if (empty($this->match) && !empty($this->where)) {
493
            $query .= 'WHERE ';
494
        }
495
496
        if (!empty($this->where)) {
497
            foreach ($this->where as $key => $where) {
498
                if ($key > 0 || !empty($this->match)) {
499
                    $query .= 'AND ';
500
                }
501
                $query .= $this->compileFilterCondition($where);
502
            }
503
        }
504
505
        return $query;
506
    }
507
508
    public function compileFilterCondition($filter)
509
    {
510
        $query = '';
511
512
        if (!empty($filter)) {
513
            if (strtoupper($filter['operator']) === 'BETWEEN') {
514
                $query .= $filter['column'];
515
                $query .= ' BETWEEN ';
516
                $query .= $this->getConnection()->quote($filter['value'][0]).' AND '
517
                    .$this->getConnection()->quote($filter['value'][1]).' ';
518
            } else {
519
                // id can't be quoted!
520
                if ($filter['column'] === 'id') {
521
                    $query .= 'id ';
522
                } else {
523
                    $query .= $filter['column'].' ';
524
                }
525
526
                if (in_array(strtoupper($filter['operator']), array('IN', 'NOT IN'), true)) {
527
                    $query .= strtoupper($filter['operator']).' ('.implode(', ', $this->getConnection()->quoteArr($filter['value'])).') ';
528
                } else {
529
                    $query .= $filter['operator'].' '.$this->getConnection()->quote($filter['value']).' ';
530
                }
531
            }
532
        }
533
534
        return $query;
535
    }
536
537
    /**
538
     * Compiles the statements for SELECT
539
     *
540
     * @return SphinxQL
541
     */
542
    public function compileSelect()
543
    {
544
        $query = '';
545
546
        if ($this->type == 'select') {
547
            $query .= 'SELECT ';
548
549 View Code Duplication
            if (!empty($this->select)) {
550
                $query .= implode(', ', $this->select).' ';
551
            } else {
552
                $query .= '* ';
553
            }
554
        }
555
556
        if (!empty($this->from)) {
557
            if ($this->from instanceof \Closure) {
558
                $sub = new static($this->getConnection());
559
                call_user_func($this->from, $sub);
560
                $query .= 'FROM ('.$sub->compile()->getCompiled().') ';
561
            } elseif ($this->from instanceof SphinxQL) {
562
                $query .= 'FROM ('.$this->from->compile()->getCompiled().') ';
563
            } else {
564
                $query .= 'FROM '.implode(', ', $this->from).' ';
565
            }
566
        }
567
568
        $query .= $this->compileMatch().$this->compileWhere();
569
570
        if (!empty($this->group_by)) {
571
            $query .= 'GROUP ';
572
            if ($this->group_n_by !== null) {
573
                $query .= $this->group_n_by.' ';
574
            }
575
            $query .= 'BY '.implode(', ', $this->group_by).' ';
576
        }
577
578
        if (!empty($this->within_group_order_by)) {
579
            $query .= 'WITHIN GROUP ORDER BY ';
580
581
            $order_arr = array();
582
583
            foreach ($this->within_group_order_by as $order) {
584
                $order_sub = $order['column'].' ';
585
586
                if ($order['direction'] !== null) {
587
                    $order_sub .= ((strtolower($order['direction']) === 'desc') ? 'DESC' : 'ASC');
588
                }
589
590
                $order_arr[] = $order_sub;
591
            }
592
593
            $query .= implode(', ', $order_arr).' ';
594
        }
595
596
        if (!empty($this->having)) {
597
            $query .= 'HAVING '.$this->compileFilterCondition($this->having);
598
        }
599
600
        if (!empty($this->order_by)) {
601
            $query .= 'ORDER BY ';
602
603
            $order_arr = array();
604
605
            foreach ($this->order_by as $order) {
606
                $order_sub = $order['column'].' ';
607
608
                if ($order['direction'] !== null) {
609
                    $order_sub .= ((strtolower($order['direction']) === 'desc') ? 'DESC' : 'ASC');
610
                }
611
612
                $order_arr[] = $order_sub;
613
            }
614
615
            $query .= implode(', ', $order_arr).' ';
616
        }
617
618
        if ($this->limit !== null || $this->offset !== null) {
619
            if ($this->offset === null) {
620
                $this->offset = 0;
621
            }
622
623
            if ($this->limit === null) {
624
                $this->limit = 9999999999999;
625
            }
626
627
            $query .= 'LIMIT '.((int) $this->offset).', '.((int) $this->limit).' ';
628
        }
629
630
        if (!empty($this->options)) {
631
            $options = array();
632
633
            foreach ($this->options as $option) {
634
                if ($option['value'] instanceof Expression) {
635
                    $option['value'] = $option['value']->value();
636
                } elseif (is_array($option['value'])) {
637
                    array_walk(
638
                        $option['value'],
639
                        function (&$val, $key) {
640
                            $val = $key.'='.$val;
641
                        }
642
                    );
643
                    $option['value'] = '('.implode(', ', $option['value']).')';
644
                } else {
645
                    $option['value'] = $this->getConnection()->quote($option['value']);
646
                }
647
648
                $options[] = $option['name'].' = '.$option['value'];
649
            }
650
651
            $query .= 'OPTION '.implode(', ', $options).' ';
652
        }
653
654
        if (!empty($this->facets)) {
655
            $facets = array();
656
657
            foreach ($this->facets as $facet) {
658
                // dynamically set the own SphinxQL connection if the Facet doesn't own one
659
                if ($facet->getConnection() === null) {
660
                    $facet->setConnection($this->getConnection());
661
                    $facets[] = $facet->getFacet();
662
                    // go back to the status quo for reuse
663
                    $facet->setConnection();
664
                } else {
665
                    $facets[] = $facet->getFacet();
666
                }
667
            }
668
669
            $query .= implode(' ', $facets);
670
        }
671
672
        $query = trim($query);
673
        $this->last_compiled = $query;
674
675
        return $this;
676
    }
677
678
    /**
679
     * Compiles the statements for INSERT or REPLACE
680
     *
681
     * @return SphinxQL
682
     */
683
    public function compileInsert()
684
    {
685
        if ($this->type == 'insert') {
686
            $query = 'INSERT ';
687
        } else {
688
            $query = 'REPLACE ';
689
        }
690
691
        if ($this->into !== null) {
692
            $query .= 'INTO '.$this->into.' ';
693
        }
694
695 View Code Duplication
        if (!empty($this->columns)) {
696
            $query .= '('.implode(', ', $this->columns).') ';
697
        }
698
699
        if (!empty($this->values)) {
700
            $query .= 'VALUES ';
701
            $query_sub = array();
702
703
            foreach ($this->values as $value) {
704
                $query_sub[] = '('.implode(', ', $this->getConnection()->quoteArr($value)).')';
705
            }
706
707
            $query .= implode(', ', $query_sub);
708
        }
709
710
        $query = trim($query);
711
        $this->last_compiled = $query;
712
713
        return $this;
714
    }
715
716
    /**
717
     * Compiles the statements for UPDATE
718
     *
719
     * @return SphinxQL
720
     */
721
    public function compileUpdate()
722
    {
723
        $query = 'UPDATE ';
724
725
        if ($this->into !== null) {
726
            $query .= $this->into.' ';
727
        }
728
729
        if (!empty($this->set)) {
730
            $query .= 'SET ';
731
732
            $query_sub = array();
733
734
            foreach ($this->set as $column => $value) {
735
                // MVA support
736
                if (is_array($value)) {
737
                    $query_sub[] = $column
738
                        .' = ('.implode(', ', $this->getConnection()->quoteArr($value)).')';
739
                } else {
740
                    $query_sub[] = $column
741
                        .' = '.$this->getConnection()->quote($value);
742
                }
743
            }
744
745
            $query .= implode(', ', $query_sub).' ';
746
        }
747
748
        $query .= $this->compileMatch().$this->compileWhere();
749
750
        $query = trim($query);
751
        $this->last_compiled = $query;
752
753
        return $this;
754
    }
755
756
    /**
757
     * Compiles the statements for DELETE
758
     *
759
     * @return SphinxQL
760
     */
761
    public function compileDelete()
762
    {
763
        $query = 'DELETE ';
764
765
        if (!empty($this->from)) {
766
            $query .= 'FROM '.$this->from[0].' ';
767
        }
768
769
        if (!empty($this->where)) {
770
            $query .= $this->compileWhere();
771
        }
772
773
        $query = trim($query);
774
        $this->last_compiled = $query;
775
776
        return $this;
777
    }
778
779
    /**
780
     * Sets a query to be executed
781
     *
782
     * @param string $sql A SphinxQL query to execute
783
     *
784
     * @return SphinxQL
785
     */
786
    public function query($sql)
787
    {
788
        $this->type = 'query';
789
        $this->query = $sql;
790
791
        return $this;
792
    }
793
794
    /**
795
     * Select the columns
796
     *
797
     * Gets the arguments passed as $sphinxql->select('one', 'two')
798
     * Using it without arguments equals to having '*' as argument
799
     * Using it with array maps values as column names
800
     *
801
     * Examples:
802
     *    $query->select('title');
803
     *    // SELECT title
804
     *
805
     *    $query->select('title', 'author', 'date');
806
     *    // SELECT title, author, date
807
     *
808
     *    $query->select(['id', 'title']);
809
     *    // SELECT id, title
810
     *
811
     * @param array|string $columns Array or multiple string arguments containing column names
812
     *
813
     * @return SphinxQL
814
     */
815
    public function select($columns = null)
816
    {
817
        $this->reset();
818
        $this->type = 'select';
819
820
        if (is_array($columns)) {
821
            $this->select = $columns;
822
        } else {
823
            $this->select = \func_get_args();
824
        }
825
826
        return $this;
827
    }
828
829
    /**
830
     * Alters which arguments to select
831
     *
832
     * Query is assumed to be in SELECT mode
833
     * See select() for usage
834
     *
835
     * @param array|string $columns Array or multiple string arguments containing column names
836
     *
837
     * @return SphinxQL
838
     */
839
    public function setSelect($columns = null)
840
    {
841
        if (is_array($columns)) {
842
            $this->select = $columns;
843
        } else {
844
            $this->select = \func_get_args();
845
        }
846
847
        return $this;
848
    }
849
850
     /**
851
     *
852
     * Get the where array
853
     *
854
     * @return array
855
     */
856
    public function getWhere()
857
    {
858
        return $this->where;
859
    }
860
    
861
    /**
862
     * Sets the where array
863
     *
864
     * @param array $where
865
     *
866
     * @return SphinxQL
867
     */
868
    public function setWhere($where)
869
    {
870
        $this->where = $where;
871
872
        return $this;
873
    }
874
875
    /**
876
     * Get the columns staged to select
877
     *
878
     * @return array
879
     */
880
    public function getSelect()
881
    {
882
        return $this->select;
883
    }
884
885
    /**
886
     * Activates the INSERT mode
887
     *
888
     * @return SphinxQL
889
     */
890
    public function insert()
891
    {
892
        $this->reset();
893
        $this->type = 'insert';
894
895
        return $this;
896
    }
897
898
    /**
899
     * Activates the REPLACE mode
900
     *
901
     * @return SphinxQL
902
     */
903
    public function replace()
904
    {
905
        $this->reset();
906
        $this->type = 'replace';
907
908
        return $this;
909
    }
910
911
    /**
912
     * Activates the UPDATE mode
913
     *
914
     * @param string $index The index to update into
915
     *
916
     * @return SphinxQL
917
     */
918
    public function update($index)
919
    {
920
        $this->reset();
921
        $this->type = 'update';
922
        $this->into($index);
923
924
        return $this;
925
    }
926
927
    /**
928
     * Activates the DELETE mode
929
     *
930
     * @return SphinxQL
931
     */
932
    public function delete()
933
    {
934
        $this->reset();
935
        $this->type = 'delete';
936
937
        return $this;
938
    }
939
940
    /**
941
     * FROM clause (Sphinx-specific since it works with multiple indexes)
942
     * func_get_args()-enabled
943
     *
944
     * @param array $array An array of indexes to use
945
     *
946
     * @return SphinxQL
947
     */
948
    public function from($array = null)
949
    {
950
        if (is_string($array)) {
951
            $this->from = \func_get_args();
952
        }
953
954
        if (is_array($array) || $array instanceof \Closure || $array instanceof SphinxQL) {
955
            $this->from = $array;
956
        }
957
958
        return $this;
959
    }
960
961
    /**
962
     * MATCH clause (Sphinx-specific)
963
     *
964
     * @param mixed    $column The column name (can be array, string, Closure, or Match)
965
     * @param string   $value  The value
966
     * @param boolean  $half  Exclude ", |, - control characters from being escaped
967
     *
968
     * @return SphinxQL
969
     */
970
    public function match($column, $value = null, $half = false)
971
    {
972
        if ($column === '*' || (is_array($column) && in_array('*', $column))) {
973
            $column = array();
974
        }
975
976
        $this->match[] = array('column' => $column, 'value' => $value, 'half' => $half);
977
978
        return $this;
979
    }
980
981
    /**
982
     * WHERE clause
983
     *
984
     * Examples:
985
     *    $query->where('column', 'value');
986
     *    // WHERE column = 'value'
987
     *
988
     *    $query->where('column', '=', 'value');
989
     *    // WHERE column = 'value'
990
     *
991
     *    $query->where('column', '>=', 'value')
992
     *    // WHERE column >= 'value'
993
     *
994
     *    $query->where('column', 'IN', array('value1', 'value2', 'value3'));
995
     *    // WHERE column IN ('value1', 'value2', 'value3')
996
     *
997
     *    $query->where('column', 'BETWEEN', array('value1', 'value2'))
998
     *    // WHERE column BETWEEN 'value1' AND 'value2'
999
     *    // WHERE example BETWEEN 10 AND 100
1000
     *
1001
     * @param string   $column   The column name
1002
     * @param Expression|string|null|bool|array|int|float $operator The operator to use (if value is not null, you can
1003
     *      use only string)
1004
     * @param Expression|string|null|bool|array|int|float $value The value to check against
1005
     *
1006
     * @return SphinxQL
1007
     */
1008
    public function where($column, $operator, $value = null)
1009
    {
1010
        if ($value === null) {
1011
            $value = $operator;
1012
            $operator = '=';
1013
        }
1014
1015
        $this->where[] = array(
1016
            'column' => $column,
1017
            'operator' => $operator,
1018
            'value' => $value
1019
        );
1020
1021
        return $this;
1022
    }
1023
1024
    /**
1025
     * GROUP BY clause
1026
     * Adds to the previously added columns
1027
     *
1028
     * @param string $column A column to group by
1029
     *
1030
     * @return SphinxQL
1031
     */
1032
    public function groupBy($column)
1033
    {
1034
        $this->group_by[] = $column;
1035
1036
        return $this;
1037
    }
1038
1039
    /**
1040
     * GROUP N BY clause (SphinxQL-specific)
1041
     * Changes 'GROUP BY' into 'GROUP N BY'
1042
     *
1043
     * @param int $n Number of items per group
1044
     *
1045
     * @return SphinxQL
1046
     */
1047
    public function groupNBy($n)
1048
    {
1049
        $this->group_n_by = (int) $n;
1050
1051
        return $this;
1052
    }
1053
1054
    /**
1055
     * WITHIN GROUP ORDER BY clause (SphinxQL-specific)
1056
     * Adds to the previously added columns
1057
     * Works just like a classic ORDER BY
1058
     *
1059
     * @param string $column    The column to group by
1060
     * @param string $direction The group by direction (asc/desc)
1061
     *
1062
     * @return SphinxQL
1063
     */
1064
    public function withinGroupOrderBy($column, $direction = null)
1065
    {
1066
        $this->within_group_order_by[] = array('column' => $column, 'direction' => $direction);
1067
1068
        return $this;
1069
    }
1070
1071
    /**
1072
     * HAVING clause
1073
     *
1074
     * Examples:
1075
     *    $sq->having('column', 'value');
1076
     *    // HAVING column = 'value'
1077
     *
1078
     *    $sq->having('column', '=', 'value');
1079
     *    // HAVING column = 'value'
1080
     *
1081
     *    $sq->having('column', '>=', 'value')
1082
     *    // HAVING column >= 'value'
1083
     *
1084
     *    $sq->having('column', 'IN', array('value1', 'value2', 'value3'));
1085
     *    // HAVING column IN ('value1', 'value2', 'value3')
1086
     *
1087
     *    $sq->having('column', 'BETWEEN', array('value1', 'value2'))
1088
     *    // HAVING column BETWEEN 'value1' AND 'value2'
1089
     *    // HAVING example BETWEEN 10 AND 100
1090
     *
1091
     * @param string   $column   The column name
1092
     * @param string   $operator The operator to use
1093
     * @param string   $value    The value to check against
1094
     *
1095
     * @return SphinxQL The current object
1096
     */
1097
    public function having($column, $operator, $value = null)
1098
    {
1099
        if ($value === null) {
1100
            $value = $operator;
1101
            $operator = '=';
1102
        }
1103
1104
        $this->having = array(
1105
            'column' => $column,
1106
            'operator' => $operator,
1107
            'value' => $value
1108
        );
1109
1110
        return $this;
1111
    }
1112
1113
    /**
1114
     * ORDER BY clause
1115
     * Adds to the previously added columns
1116
     *
1117
     * @param string $column    The column to order on
1118
     * @param string $direction The ordering direction (asc/desc)
1119
     *
1120
     * @return SphinxQL
1121
     */
1122
    public function orderBy($column, $direction = null)
1123
    {
1124
        $this->order_by[] = array('column' => $column, 'direction' => $direction);
1125
1126
        return $this;
1127
    }
1128
1129
    /**
1130
     * LIMIT clause
1131
     * Supports also LIMIT offset, limit
1132
     *
1133
     * @param int      $offset Offset if $limit is specified, else limit
1134
     * @param null|int $limit  The limit to set, null for no limit
1135
     *
1136
     * @return SphinxQL
1137
     */
1138
    public function limit($offset, $limit = null)
1139
    {
1140
        if ($limit === null) {
1141
            $this->limit = (int) $offset;
1142
            return $this;
1143
        }
1144
1145
        $this->offset($offset);
1146
        $this->limit = (int) $limit;
1147
1148
        return $this;
1149
    }
1150
1151
    /**
1152
     * OFFSET clause
1153
     *
1154
     * @param int $offset The offset
1155
     *
1156
     * @return SphinxQL
1157
     */
1158
    public function offset($offset)
1159
    {
1160
        $this->offset = (int) $offset;
1161
1162
        return $this;
1163
    }
1164
1165
    /**
1166
     * OPTION clause (SphinxQL-specific)
1167
     * Used by: SELECT
1168
     *
1169
     * @param string $name  Option name
1170
     * @param Expression|array|string|int|bool|float|null $value Option value
1171
     *
1172
     * @return SphinxQL
1173
     */
1174
    public function option($name, $value)
1175
    {
1176
        $this->options[] = array('name' => $name, 'value' => $value);
1177
1178
        return $this;
1179
    }
1180
1181
    /**
1182
     * INTO clause
1183
     * Used by: INSERT, REPLACE
1184
     *
1185
     * @param string $index The index to insert/replace into
1186
     *
1187
     * @return SphinxQL
1188
     */
1189
    public function into($index)
1190
    {
1191
        $this->into = $index;
1192
1193
        return $this;
1194
    }
1195
1196
    /**
1197
     * Set columns
1198
     * Used in: INSERT, REPLACE
1199
     * func_get_args()-enabled
1200
     *
1201
     * @param array $array The array of columns
1202
     *
1203
     * @return SphinxQL
1204
     */
1205
    public function columns($array = array())
1206
    {
1207
        if (is_array($array)) {
1208
            $this->columns = $array;
1209
        } else {
1210
            $this->columns = \func_get_args();
1211
        }
1212
1213
        return $this;
1214
    }
1215
1216
    /**
1217
     * Set VALUES
1218
     * Used in: INSERT, REPLACE
1219
     * func_get_args()-enabled
1220
     *
1221
     * @param array $array The array of values matching the columns from $this->columns()
1222
     *
1223
     * @return SphinxQL
1224
     */
1225
    public function values($array)
1226
    {
1227
        if (is_array($array)) {
1228
            $this->values[] = $array;
1229
        } else {
1230
            $this->values[] = \func_get_args();
1231
        }
1232
1233
        return $this;
1234
    }
1235
1236
    /**
1237
     * Set column and relative value
1238
     * Used in: INSERT, REPLACE
1239
     *
1240
     * @param string $column The column name
1241
     * @param string $value  The value
1242
     *
1243
     * @return SphinxQL
1244
     */
1245
    public function value($column, $value)
1246
    {
1247
        if ($this->type === 'insert' || $this->type === 'replace') {
1248
            $this->columns[] = $column;
1249
            $this->values[0][] = $value;
1250
        } else {
1251
            $this->set[$column] = $value;
1252
        }
1253
1254
        return $this;
1255
    }
1256
1257
    /**
1258
     * Allows passing an array with the key as column and value as value
1259
     * Used in: INSERT, REPLACE, UPDATE
1260
     *
1261
     * @param array $array Array of key-values
1262
     *
1263
     * @return SphinxQL
1264
     */
1265
    public function set($array)
1266
    {
1267
        if ($this->columns === array_keys($array)) {
1268
            $this->values($array);
1269
        } else {
1270
            foreach ($array as $key => $item) {
1271
                $this->value($key, $item);
1272
            }
1273
        }
1274
1275
        return $this;
1276
    }
1277
1278
    /**
1279
     * Allows passing an array with the key as column and value as value
1280
     * Used in: INSERT, REPLACE, UPDATE
1281
     *
1282
     * @param Facet $facet
1283
     * @return SphinxQL
1284
     */
1285
    public function facet($facet)
1286
    {
1287
        $this->facets[] = $facet;
1288
1289
        return $this;
1290
    }
1291
1292
    /**
1293
     * Sets the characters used for escapeMatch().
1294
     *
1295
     * @param array $array The array of characters to escape
1296
     *
1297
     * @return SphinxQL The escaped characters
1298
     */
1299
    public function setFullEscapeChars($array = array())
1300
    {
1301
        if (!empty($array)) {
1302
            $this->escape_full_chars = $this->compileEscapeChars($array);
1303
        }
1304
1305
        return $this;
1306
    }
1307
1308
    /**
1309
     * Sets the characters used for halfEscapeMatch().
1310
     *
1311
     * @param array $array The array of characters to escape
1312
     *
1313
     * @return SphinxQL The escaped characters
1314
     */
1315
    public function setHalfEscapeChars($array = array())
1316
    {
1317
        if (!empty($array)) {
1318
            $this->escape_half_chars = $this->compileEscapeChars($array);
1319
        }
1320
1321
        return $this;
1322
    }
1323
1324
    /**
1325
     * Compiles an array containing the characters and escaped characters into a key/value configuration.
1326
     *
1327
     * @param array $array The array of characters to escape
1328
     *
1329
     * @return array An array of the characters and it's escaped counterpart
1330
     */
1331
    public function compileEscapeChars($array = array())
1332
    {
1333
        $result = array();
1334
        foreach ($array as $character) {
1335
            $result[$character] = '\\'.$character;
1336
        }
1337
1338
        return $result;
1339
    }
1340
1341
    /**
1342
     * Escapes the query for the MATCH() function
1343
     *
1344
     * @param string $string The string to escape for the MATCH
1345
     *
1346
     * @return string The escaped string
1347
     */
1348
    public function escapeMatch($string)
1349
    {
1350
        if ($string instanceof Expression) {
1351
            return $string->value();
1352
        }
1353
1354
        return mb_strtolower(str_replace(array_keys($this->escape_full_chars), array_values($this->escape_full_chars), $string), 'utf8');
1355
    }
1356
1357
    /**
1358
     * Escapes the query for the MATCH() function
1359
     * Allows some of the control characters to pass through for use with a search field: -, |, "
1360
     * It also does some tricks to wrap/unwrap within " the string and prevents errors
1361
     *
1362
     * @param string $string The string to escape for the MATCH
1363
     *
1364
     * @return string The escaped string
1365
     */
1366
    public function halfEscapeMatch($string)
1367
    {
1368
        if ($string instanceof Expression) {
1369
            return $string->value();
1370
        }
1371
1372
        $string = str_replace(array_keys($this->escape_half_chars), array_values($this->escape_half_chars), $string);
1373
1374
        // this manages to lower the error rate by a lot
1375
        if (mb_substr_count($string, '"', 'utf8') % 2 !== 0) {
1376
            $string .= '"';
1377
        }
1378
1379
        $string = preg_replace('/-[\s-]*-/u', '-', $string);
1380
1381
        $from_to_preg = array(
1382
            '/([-|])\s*$/u'        => '\\\\\1',
1383
            '/\|[\s|]*\|/u'        => '|',
1384
            '/(\S+)-(\S+)/u'       => '\1\-\2',
1385
            '/(\S+)\s+-\s+(\S+)/u' => '\1 \- \2',
1386
        );
1387
1388
        $string = mb_strtolower(preg_replace(array_keys($from_to_preg), array_values($from_to_preg), $string), 'utf8');
1389
1390
        return $string;
1391
    }
1392
1393
    /**
1394
     * Clears the existing query build for new query when using the same SphinxQL instance.
1395
     *
1396
     * @return SphinxQL
1397
     */
1398
    public function reset()
1399
    {
1400
        $this->query = null;
1401
        $this->select = array();
1402
        $this->from = array();
1403
        $this->where = array();
1404
        $this->match = array();
1405
        $this->group_by = array();
1406
        $this->group_n_by = null;
1407
        $this->within_group_order_by = array();
1408
        $this->having = array();
1409
        $this->order_by = array();
1410
        $this->offset = null;
1411
        $this->limit = null;
1412
        $this->into = null;
1413
        $this->columns = array();
1414
        $this->values = array();
1415
        $this->set = array();
1416
        $this->options = array();
1417
1418
        return $this;
1419
    }
1420
1421
    public function resetWhere()
1422
    {
1423
        $this->where = array();
1424
1425
        return $this;
1426
    }
1427
1428
    public function resetMatch()
1429
    {
1430
        $this->match = array();
1431
1432
        return $this;
1433
    }
1434
1435
    public function resetGroupBy()
1436
    {
1437
        $this->group_by = array();
1438
        $this->group_n_by = null;
1439
1440
        return $this;
1441
    }
1442
1443
    public function resetWithinGroupOrderBy()
1444
    {
1445
        $this->within_group_order_by = array();
1446
1447
        return $this;
1448
    }
1449
1450
    public function resetHaving()
1451
    {
1452
        $this->having = array();
1453
1454
        return $this;
1455
    }
1456
1457
    public function resetOrderBy()
1458
    {
1459
        $this->order_by = array();
1460
1461
        return $this;
1462
    }
1463
1464
    public function resetOptions()
1465
    {
1466
        $this->options = array();
1467
1468
        return $this;
1469
    }
1470
}
1471