Passed
Push — master ( 873950...1e5375 )
by y
01:29
created

ComparisonTrait::switch()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 2
c 0
b 0
f 0
dl 0
loc 2
rs 10
cc 1
nc 1
nop 1
1
<?php
2
3
namespace Helix\DB\SQL;
4
5
use Helix\DB;
6
use Helix\DB\Select;
7
8
/**
9
 * Produces comparative expressions for the instance.
10
 *
11
 * Because SQLite lacks the `ANY` and `ALL` comparison operators,
12
 * subqueries are instead nested and correlated using `EXISTS` or `NOT EXISTS`.
13
 *
14
 * This also requires that the subquery's first column is referable.
15
 */
16
trait ComparisonTrait {
17
18
    /**
19
     * @var DB
20
     */
21
    protected $db;
22
23
    /**
24
     * `COALESCE($this, ...$values)`
25
     *
26
     * @param array $values
27
     * @return Value
28
     */
29
    public function coalesce (array $values) {
30
        array_unshift($values, $this);
31
        $values = $this->db->quoteList($values);
32
        return $this->db->factory(Value::class, $this->db, "COALESCE({$values})");
33
    }
34
35
    /**
36
     * Null-safe equality.
37
     *
38
     * - Mysql: `$this <=> $arg`, or `$this <=> ANY ($arg)`
39
     * - SQLite: `$this IS $arg`, or `EXISTS (... WHERE $this IS $arg[0])`
40
     *
41
     * @param null|bool|number|string|Select $arg
42
     * @return Predicate
43
     */
44
    public function is ($arg): Predicate {
45
        if ($arg instanceof Select) {
46
            if ($this->db->isSQLite()) {
47
                /** @var Select $sub */
48
                $sub = $this->db->factory(Select::class, $this->db, $arg, [$arg[0]]);
49
                return $sub->where("{$this} IS {$arg[0]}")->isNotEmpty();
50
            }
51
            return $this->db->factory(Predicate::class, "{$this} <=> ANY ({$arg->toSql()})");
52
        }
53
        if ($arg === null or is_bool($arg)) {
54
            $arg = ['' => 'NULL', 1 => 'TRUE', 0 => 'FALSE'][$arg];
55
        }
56
        else {
57
            $arg = $this->db->quote($arg);
58
        }
59
        if ($this->db->isMySQL()) {
60
            return $this->db->factory(Predicate::class, "{$this} <=> {$arg}");
61
        }
62
        return $this->db->factory(Predicate::class, "{$this} IS {$arg}");
63
    }
64
65
    /**
66
     * `$this BETWEEN $min AND $max` (inclusive)
67
     *
68
     * @param number $min
69
     * @param number $max
70
     * @return Predicate
71
     */
72
    public function isBetween ($min, $max) {
73
        $min = $this->db->quote($min);
74
        $max = $this->db->quote($max);
75
        return $this->db->factory(Predicate::class, "{$this} BETWEEN {$min} AND {$max}");
76
    }
77
78
    /**
79
     * `$this = $arg` or `$this IN ($arg)`
80
     *
81
     * @param bool|number|string|array|Select $arg
82
     * @return Predicate
83
     */
84
    public function isEqual ($arg) {
85
        return $this->db->match($this, $arg);
86
    }
87
88
    /**
89
     * `$this IS FALSE`
90
     *
91
     * @return Predicate
92
     */
93
    public function isFalse () {
94
        return $this->db->factory(Predicate::class, "{$this} IS FALSE");
95
    }
96
97
    /**
98
     * `$this > $arg`, or driver-specific subquery comparison.
99
     *
100
     * - MySQL: `$this > ALL ($arg)` or `$this > ANY ($arg)`
101
     * - SQLite:
102
     *      - ALL: `NOT EXISTS (... WHERE $this <= $arg[0])`
103
     *      - ANY: `EXISTS (... WHERE $this > $arg[0])`
104
     *
105
     * @param number|string|Select $arg
106
     * @param string $multi `ALL|ANY`
107
     * @return Predicate
108
     */
109
    public function isGreater ($arg, string $multi = 'ALL') {
110
        if ($arg instanceof Select) {
111
            switch ($this->db) {
112
                case 'sqlite':
113
                    /** @var Select $sub */
114
                    $sub = $this->db->factory(Select::class, $this->db, $arg, [$arg[0]]);
115
                    switch ($multi) {
116
                        case 'ANY':
117
                            return $sub->where("{$this} > {$arg[0]}")->isNotEmpty();
118
                        default:
119
                            return $sub->where("{$this} <= {$arg[0]}")->isEmpty();
120
                    }
121
                default:
122
                    return $this->db->factory(Predicate::class, "{$this} > {$multi} ({$arg->toSql()})");
123
            }
124
        }
125
        return $this->db->factory(Predicate::class, "{$this} > {$this->db->quote($arg)}");
126
    }
127
128
    /**
129
     * `$this >= $arg`, or driver-specific subquery comparison.
130
     *
131
     * - MySQL: `$this >= ALL ($arg)` or `$this >= ANY ($arg)`
132
     * - SQLite:
133
     *      - ALL: `NOT EXISTS (... WHERE $this < $arg[0])`
134
     *      - ANY: `EXISTS (... WHERE $this >= $arg[0])`
135
     *
136
     * @param number|string|Select $arg
137
     * @param string $multi `ALL|ANY`
138
     * @return Predicate
139
     */
140
    public function isGreaterOrEqual ($arg, string $multi = 'ALL') {
141
        if ($arg instanceof Select) {
142
            switch ($this->db) {
143
                case 'sqlite':
144
                    /** @var Select $sub */
145
                    $sub = $this->db->factory(Select::class, $this->db, $arg, [$arg[0]]);
146
                    switch ($multi) {
147
                        case 'ANY':
148
                            return $sub->where("{$this} >= {$arg[0]}")->isNotEmpty();
149
                        default:
150
                            return $sub->where("{$this} < {$arg[0]}")->isEmpty();
151
                    }
152
                default:
153
                    return $this->db->factory(Predicate::class, "{$this} >= {$multi} ({$arg->toSql()})");
154
            }
155
        }
156
        return $this->db->factory(Predicate::class, "{$this} >= {$this->db->quote($arg)}");
157
    }
158
159
    /**
160
     * `$this < $arg`, or driver-specific subquery comparison.
161
     *
162
     * - MySQL: `$this < ALL ($arg)` or `$this < ANY ($arg)`
163
     * - SQLite:
164
     *      - ALL: `NOT EXISTS (... WHERE $this >= $arg[0])`
165
     *      - ANY: `EXISTS (... WHERE $this < $arg[0])`
166
     *
167
     * @param number|string|Select $arg
168
     * @param string $multi `ALL|ANY`
169
     * @return Predicate
170
     */
171
    public function isLess ($arg, string $multi = 'ALL') {
172
        if ($arg instanceof Select) {
173
            switch ($this->db) {
174
                case 'sqlite':
175
                    /** @var Select $sub */
176
                    $sub = $this->db->factory(Select::class, $this->db, $arg, [$arg[0]]);
177
                    switch ($multi) {
178
                        case 'ANY':
179
                            return $sub->where("{$this} < {$arg[0]}")->isNotEmpty();
180
                        default:
181
                            return $sub->where("{$this} >= {$arg[0]}")->isEmpty();
182
                    }
183
                default:
184
                    return $this->db->factory(Predicate::class, "{$this} < {$multi} ({$arg->toSql()})");
185
            }
186
        }
187
        return $this->db->factory(Predicate::class, "{$this} < {$this->db->quote($arg)}");
188
    }
189
190
    /**
191
     * `$this <= $arg`, or driver-specific subquery comparison.
192
     *
193
     * - MySQL: `$this <= ALL ($arg)` or `$this <= ANY ($arg)`
194
     * - SQLite:
195
     *      - ALL: `NOT EXISTS (... WHERE $this > $arg[0])`
196
     *      - ANY: `EXISTS (... WHERE $this <= $arg[0])`
197
     *
198
     * @param number|string|Select $arg
199
     * @param string $multi `ALL|ANY`
200
     * @return Predicate
201
     */
202
    public function isLessOrEqual ($arg, string $multi = 'ALL') {
203
        if ($arg instanceof Select) {
204
            switch ($this->db) {
205
                case 'sqlite':
206
                    /** @var Select $sub */
207
                    $sub = $this->db->factory(Select::class, $this->db, $arg, [$arg[0]]);
208
                    switch ($multi) {
209
                        case 'ANY':
210
                            return $sub->where("{$this} <= {$arg[0]}")->isNotEmpty();
211
                        default:
212
                            return $sub->where("{$this} > {$arg[0]}")->isEmpty();
213
                    }
214
                default:
215
                    return $this->db->factory(Predicate::class, "{$this} <= {$multi} ({$arg->toSql()})");
216
            }
217
        }
218
        return $this->db->factory(Predicate::class, "{$this} <= {$this->db->quote($arg)}");
219
    }
220
221
    /**
222
     * `$this LIKE $pattern`
223
     *
224
     * @param string $pattern
225
     * @return Predicate
226
     */
227
    public function isLike (string $pattern) {
228
        $pattern = $this->db->quote($pattern);
229
        return $this->db->factory(Predicate::class, "{$this} LIKE {$pattern}");
230
    }
231
232
    /**
233
     * Null-safe inequality.
234
     *
235
     * @param null|bool|number|string|Select $arg
236
     * @return Predicate
237
     */
238
    public function isNot ($arg) {
239
        return $this->is($arg)->not();
240
    }
241
242
    /**
243
     * `$this NOT BETWEEN $min AND $max` (inclusive)
244
     *
245
     * @param number $min
246
     * @param number $max
247
     * @return Predicate
248
     */
249
    public function isNotBetween ($min, $max) {
250
        $min = $this->db->quote($min);
251
        $max = $this->db->quote($max);
252
        return $this->db->factory(Predicate::class, "{$this} NOT BETWEEN {$min} AND {$max}");
253
    }
254
255
    /**
256
     * `$this <> $arg` or `$this NOT IN ($arg)`
257
     *
258
     * @param bool|number|string|array|Select $arg
259
     * @return Predicate
260
     */
261
    public function isNotEqual ($arg) {
262
        if ($arg instanceof Select) {
263
            return $this->db->factory(Predicate::class, "{$this} NOT IN ({$arg->toSql()})");
264
        }
265
        if (is_array($arg)) {
266
            return $this->db->factory(Predicate::class, "{$this} NOT IN ({$this->db->quoteList($arg)})");
267
        }
268
        return $this->db->factory(Predicate::class, "{$this} <> {$this->db->quote($arg)}");
269
    }
270
271
    /**
272
     * `$this NOT LIKE $pattern`
273
     *
274
     * @param string $pattern
275
     * @return Predicate
276
     */
277
    public function isNotLike (string $pattern) {
278
        $pattern = $this->db->quote($pattern);
279
        return $this->db->factory(Predicate::class, "{$this} NOT LIKE {$pattern}");
280
    }
281
282
    /**
283
     * `$this IS NOT NULL`
284
     *
285
     * @return Predicate
286
     */
287
    public function isNotNull () {
288
        return $this->db->factory(Predicate::class, "{$this} IS NOT NULL");
289
    }
290
291
    /**
292
     * `$this NOT REGEXP $pattern`
293
     *
294
     * @param string $pattern
295
     * @return Predicate
296
     */
297
    public function isNotRegExp (string $pattern) {
298
        $pattern = $this->db->quote($pattern);
299
        return $this->db->factory(Predicate::class, "{$this} NOT REGEXP {$pattern}");
300
    }
301
302
    /**
303
     * `$this IS NULL`
304
     *
305
     * @return Predicate
306
     */
307
    public function isNull () {
308
        return $this->db->factory(Predicate::class, "{$this} IS NULL");
309
    }
310
311
    /**
312
     * `$this REGEXP $pattern`
313
     *
314
     * @param string $pattern
315
     * @return Predicate
316
     */
317
    public function isRegExp (string $pattern) {
318
        $pattern = $this->db->quote($pattern);
319
        return $this->db->factory(Predicate::class, "{$this} REGEXP {$pattern}");
320
    }
321
322
    /**
323
     * `CASE $this ... END`
324
     *
325
     * > :warning: If `$values` are given, the keys are quoted as literal values.
326
     * > Omit `$values` and use {@link Choice::when()} if you need expressions for the `WHEN` clause.
327
     *
328
     * @param array $values `[when => then]`
329
     * @return Choice
330
     */
331
    public function switch (array $values = []) {
332
        return $this->db->factory(Choice::class, $this->db, "{$this}", $values);
333
    }
334
}