Passed
Push — master ( bd418e...873950 )
by y
01:50
created

ComparisonTrait::isNull()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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