ConditionTrait::buildAssociativeCondition()   A
last analyzed

Complexity

Conditions 4
Paths 4

Size

Total Lines 20
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
eloc 11
nc 4
nop 3
dl 0
loc 20
ccs 12
cts 12
cp 1
crap 4
rs 9.9
c 0
b 0
f 0
1
<?php
2
/**
3
 * Copyright 2021 Aleksandar Panic
4
 *
5
 * Licensed under the Apache License, Version 2.0 (the "License");
6
 * you may not use this file except in compliance with the License.
7
 * You may obtain a copy of the License at
8
 *
9
 *   http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 */
17
18
namespace ArekX\PQL\Drivers\Pdo\MySql\Builders\Traits;
19
20
use ArekX\PQL\Contracts\StructuredQuery;
21
use ArekX\PQL\Drivers\Pdo\MySql\MySqlQueryBuilderState;
22
use Exception;
23
use UnexpectedValueException;
24
25
trait ConditionTrait
26
{
27
    use SubQueryTrait;
28
    use WrapValueTrait;
29
    use QuoteNameTrait;
30
31
    /**
32
     * Build a condition into a string
33
     *
34
     * Conditions are recursively parsed.
35
     *
36
     * See where() in conditions trait for options which can be passed here.
37
     *
38
     * @param array|StructuredQuery $condition Condition to be built.
39
     * @param MySqlQueryBuilderState $state Query builder state.
40
     * @return string
41
     * @throws Exception
42
     * @see \ArekX\PQL\Sql\Query\Traits\ConditionTrait::where()
43
     */
44 35
    protected function buildCondition(StructuredQuery|array $condition, MySqlQueryBuilderState $state): string
45
    {
46 35
        static $map = null;
47
48 35
        if ($map === null) {
49 34
            $map = [
50 34
                'all' => fn($condition, $state) => $this->buildAssociativeCondition(' AND ', $condition, $state),
51 34
                'any' => fn($condition, $state) => $this->buildAssociativeCondition(' OR ', $condition, $state),
52 34
                'and' => fn($condition, $state) => $this->buildConjunctionCondition(' AND ', $condition, $state),
53 34
                'or' => fn($condition, $state) => $this->buildConjunctionCondition(' OR ', $condition, $state),
54 34
                'not' => fn($condition, $state) => $this->buildUnaryCondition('NOT', $condition, $state),
55 34
                'exists' => fn($condition, $state) => $this->buildUnaryCondition('EXISTS', $condition, $state),
56 34
                'in' => fn($condition, $state) => $this->buildInCondition($condition, $state),
57 34
                'between' => fn($condition, $state) => $this->buildBetweenCondition($condition, $state),
58 34
                'like' => fn($condition, $state) => $this->buildBinaryCondition(' LIKE ', $condition, $state),
59 34
                '=' => fn($condition, $state) => $this->buildBinaryCondition(' = ', $condition, $state),
60 34
                '>' => fn($condition, $state) => $this->buildBinaryCondition(' > ', $condition, $state),
61 34
                '>=' => fn($condition, $state) => $this->buildBinaryCondition(' >= ', $condition, $state),
62 34
                '<' => fn($condition, $state) => $this->buildBinaryCondition(' < ', $condition, $state),
63 34
                '<=' => fn($condition, $state) => $this->buildBinaryCondition(' <= ', $condition, $state),
64 34
                '<>' => fn($condition, $state) => $this->buildBinaryCondition(' <> ', $condition, $state),
65 34
                '!=' => fn($condition, $state) => $this->buildBinaryCondition(' <> ', $condition, $state),
66 34
                'column' => fn($condition) => $this->buildColumnCondition($condition),
67 34
                'value' => fn($condition, $state) => $this->buildValueCondition($condition, $state),
68 34
            ];
69
        }
70
71 35
        if ($condition instanceof StructuredQuery) {
72 18
            return $this->buildSubQuery($condition, $state);
73
        }
74
75 33
        if (empty($map[$condition[0]])) {
76 1
            throw new UnexpectedValueException('Unknown condition: ' . var_export($condition[0], true));
77
        }
78
79 32
        return $map[$condition[0]]($condition, $state);
80
    }
81
82
    /**
83
     * Builder for associative condition.
84
     *
85
     * Builds:
86
     * ```
87
     * ['all', ['key' => 'value']]
88
     * ['any', ['key' => 'value']]
89
     * ```
90
     *
91
     * @param string $glue Glue to be used between checks (AND/OR)
92
     * @param array $condition Condition to be parsed
93
     * @param MySqlQueryBuilderState $state Query builder state
94
     * @return string
95
     */
96 13
    protected function buildAssociativeCondition(string $glue, array $condition, MySqlQueryBuilderState $state): string
97
    {
98 13
        $result = [];
99 13
        foreach ($condition[1] as $key => $value) {
100 13
            $leftSide = $this->quoteName($key);
101
102 13
            if ($value instanceof StructuredQuery) {
103 2
                $result[] = $leftSide . ' IN ' . $this->buildSubQuery($value, $state);
104 2
                continue;
105
            }
106
107 13
            if (is_array($value)) {
108 2
                $result[] = $leftSide . ' IN (' . $this->buildWrapValue($value, $state) . ')';
109 2
                continue;
110
            }
111
112 13
            $result[] = $leftSide . ' = ' . $this->buildWrapValue($value, $state);
113
        }
114
115 13
        return implode($glue, $result);
116
    }
117
118
    /**
119
     * Builder for conjunction conditions
120
     *
121
     * Builds:
122
     * ```
123
     * ['and', expression1, expression2, ..., expressionN]
124
     * ['or', expression1, expression2, ..., expressionN]
125
     * ```
126
     *
127
     * @param string $glue Glue to be used between checks (AND/OR)
128
     * @param array $condition Condition to be parsed
129
     * @param MySqlQueryBuilderState $state Query builder state
130
     * @return string
131
     * @throws Exception
132
     */
133 2
    protected function buildConjunctionCondition(string $glue, array $condition, MySqlQueryBuilderState $state): string
134
    {
135 2
        $result = [];
136
137 2
        $max = count($condition);
138 2
        for ($i = 1; $i < $max; $i++) {
139 2
            $result[] = '(' . $this->buildCondition($condition[$i], $state) . ')';
140
        }
141
142 2
        return implode($glue, $result);
143
    }
144
145
    /**
146
     * Builder for unary conditions
147
     *
148
     * Builds:
149
     * ```
150
     * ['not', expression]
151
     * ['exists', expression]
152
     * ```
153
     *
154
     * @param string $op Resulting operation (NOT/EXISTS)
155
     * @param array $condition Condition to be parsed
156
     * @param MySqlQueryBuilderState $state Query builder state
157
     * @return string
158
     * @throws Exception
159
     */
160 2
    protected function buildUnaryCondition(string $op, array $condition, MySqlQueryBuilderState $state): string
161
    {
162 2
        if ($condition[1] instanceof StructuredQuery) {
163 2
            return $op . ' ' . $this->buildSubQuery($condition[1], $state);
164
        }
165
166 2
        return $op . ' (' . $this->buildCondition($condition[1], $state) . ')';
167
    }
168
169
    /**
170
     * Builder for IN condition
171
     *
172
     * Builds:
173
     * ```
174
     * ['in', leftExpression, rightExpression]
175
     * ```
176
     *
177
     * @param array $condition Condition to be parsed
178
     * @param MySqlQueryBuilderState $state Query builder state
179
     * @return string
180
     * @throws Exception
181
     */
182 1
    protected function buildInCondition(array $condition, MySqlQueryBuilderState $state): string
183
    {
184 1
        $left = $this->buildCondition($condition[1] ?? null, $state);
185
186 1
        $right = $condition[2] ?? [];
187 1
        if ($right instanceof StructuredQuery) {
188 1
            return $left . ' IN ' . $this->buildSubQuery($right, $state);
189
        }
190
191 1
        return $left . ' IN (' . $this->buildCondition($right, $state) . ')';
192
    }
193
194
    /**
195
     * Builder for BETWEEN condition
196
     *
197
     * Builds:
198
     * ```
199
     * ['between', ofExpression, fromExpression, toExpression]
200
     * ```
201
     *
202
     * @param array $condition Condition to be parsed
203
     * @param MySqlQueryBuilderState $state Query builder state
204
     * @return string
205
     * @throws Exception
206
     */
207 1
    protected function buildBetweenCondition(array $condition, MySqlQueryBuilderState $state): string
208
    {
209 1
        $of = $this->buildCondition($condition[1] ?? null, $state);
210 1
        $from = $this->buildCondition($condition[2] ?? null, $state);
211 1
        $to = $this->buildCondition($condition[3] ?? null, $state);
212
213 1
        return $of . ' BETWEEN ' . $from . ' AND ' . $to;
214
    }
215
216
    /**
217
     * Builder for binary conditions
218
     *
219
     * Builds:
220
     * ```
221
     * ['=', leftExpression, rightExpression]
222
     * ['<', leftExpression, rightExpression]
223
     * ['>', leftExpression, rightExpression]
224
     * ['<=', leftExpression, rightExpression]
225
     * ['>=', leftExpression, rightExpression]
226
     * ['!=', leftExpression, rightExpression]
227
     * ['<>', leftExpression, rightExpression]
228
     * ```
229
     *
230
     * @param string $operation Operation to be used
231
     * @param array $condition Condition to be parsed
232
     * @param MySqlQueryBuilderState $state Query builder state
233
     * @return string
234
     * @throws Exception
235
     */
236 9
    protected function buildBinaryCondition(string $operation, array $condition, MySqlQueryBuilderState $state): string
237
    {
238 9
        $left = $this->buildCondition($condition[1] ?? null, $state);
239 9
        $right = $this->buildCondition($condition[2] ?? null, $state);
240
241 9
        return $left . $operation . $right;
242
    }
243
244
    /**
245
     * Builder for column
246
     *
247
     * Builds:
248
     * ```
249
     * ['column', string]
250
     * ```
251
     *
252
     * @param array $condition Condition to be parsed
253
     * @return string
254
     */
255 14
    protected function buildColumnCondition(array $condition): string
256
    {
257 14
        $column = $condition[1] ?? null;
258
259 14
        if (empty($column)) {
260 1
            throw new UnexpectedValueException('Column name must be set.');
261 13
        } elseif (!is_string($column)) {
262 1
            throw new UnexpectedValueException('Column name must be a string.');
263
        }
264
265 12
        return $this->quoteName($column);
266
    }
267
268
269
    /**
270
     * Builder for value (user input)
271
     *
272
     * Builds:
273
     * ```
274
     * ['value', anyValue, type]
275
     * ```
276
     *
277
     * @param array $condition Condition to be parsed
278
     * @param MySqlQueryBuilderState $state Query builder state
279
     * @return string
280
     */
281 16
    protected function buildValueCondition(array $condition, MySqlQueryBuilderState $state): string
282
    {
283 16
        return $this->buildWrapValue(
284 16
            $condition[1] ?? null,
285 16
            $state,
286 16
            $condition[2] ?? null
287 16
        );
288
    }
289
}
290