Passed
Push — master ( 222f48...0fa176 )
by y
01:47
created

ComparisonTrait::isBetween()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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