Passed
Push — master ( 03d3d0...cea9c0 )
by Alexander
07:36 queued 05:58
created

ArrayExpressionBuilder::getTypeHint()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 16
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 7
c 0
b 0
f 0
nc 2
nop 1
dl 0
loc 16
ccs 8
cts 8
cp 1
crap 2
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\Pgsql;
6
7
use Traversable;
8
use Yiisoft\Db\Exception\Exception;
9
use Yiisoft\Db\Exception\InvalidArgumentException;
10
use Yiisoft\Db\Exception\InvalidConfigException;
11
use Yiisoft\Db\Exception\NotSupportedException;
12
use Yiisoft\Db\Expression\ArrayExpression;
13
use Yiisoft\Db\Expression\ExpressionBuilderInterface;
14
use Yiisoft\Db\Expression\ExpressionBuilderTrait;
15
use Yiisoft\Db\Expression\ExpressionInterface;
16
use Yiisoft\Db\Expression\JsonExpression;
17
use Yiisoft\Db\Query\Query;
18
use Yiisoft\Db\Query\QueryInterface;
19
20
use function get_class;
21
use function implode;
22
use function in_array;
23
use function is_array;
24
use function str_repeat;
25
26
/**
27
 * The class ArrayExpressionBuilder builds {@see ArrayExpression} for PostgreSQL DBMS.
28
 */
29
final class ArrayExpressionBuilder implements ExpressionBuilderInterface
30
{
31
    use ExpressionBuilderTrait;
32
33
    /**
34
     * Method builds the raw SQL from the $expression that will not be additionally escaped or quoted.
35
     *
36
     * @param ExpressionInterface $expression the expression to be built.
37
     * @param array $params the binding parameters.
38
     *
39
     * @throws Exception|InvalidArgumentException|InvalidConfigException|NotSupportedException
40
     *
41
     * @return string the raw SQL that will not be additionally escaped or quoted.
42
     */
43 27
    public function build(ExpressionInterface $expression, array &$params = []): string
44
    {
45
        /**
46
         *  @var ArrayExpression $expression
47
         *  @var array|mixed|QueryInterface $value
48
         */
49 27
        $value = $expression->getValue();
50
51 27
        if ($value === null) {
52 1
            return 'NULL';
53
        }
54
55 27
        if ($value instanceof Query) {
56
            /** @var string $sql */
57 1
            [$sql, $params] = $this->queryBuilder->build($value, $params);
58 1
            return $this->buildSubqueryArray($sql, $expression);
59
        }
60
61 26
        $placeholders = $this->buildPlaceholders($expression, $params);
62
63 26
        return 'ARRAY[' . implode(', ', $placeholders) . ']' . $this->getTypehint($expression);
64
    }
65
66
    /**
67
     * Builds placeholders array out of $expression values.
68
     *
69
     * @param ExpressionInterface $expression
70
     * @param array $params the binding parameters.
71
     *
72
     * @throws Exception|InvalidArgumentException|InvalidConfigException|NotSupportedException
73
     *
74
     * @return array
75
     */
76 26
    protected function buildPlaceholders(ExpressionInterface $expression, array &$params): array
77
    {
78 26
        $placeholders = [];
79
80
        /**
81
         *  @var ArrayExpression $expression
82
         *  @var mixed $value
83
         */
84 26
        $value = $expression->getValue();
85
86 26
        if (!is_array($value) && !$value instanceof Traversable) {
87 2
            return $placeholders;
88
        }
89
90 24
        if ($expression->getDimension() > 1) {
91
            /** @var ExpressionInterface|int $item */
92 3
            foreach ($value as $item) {
93 3
                $placeholders[] = $this->build($this->unnestArrayExpression($expression, $item), $params);
94
            }
95 3
            return $placeholders;
96
        }
97
98
        /** @var ExpressionInterface|int $item */
99 24
        foreach ($value as $item) {
100 23
            if ($item instanceof Query) {
101
                /**
102
                 * @var string $sql
103
                 * @var array $params
104
                 */
105 1
                [$sql, $params] = $this->queryBuilder->build($item, $params);
106 1
                $placeholders[] = $this->buildSubqueryArray($sql, $expression);
107 1
                continue;
108
            }
109
110 22
            $item = $this->typecastValue($expression, $item);
111
112 22
            if ($item instanceof ExpressionInterface) {
113 4
                $placeholders[] = $this->queryBuilder->buildExpression($item, $params);
114 4
                continue;
115
            }
116
117 19
            $placeholders[] = $this->queryBuilder->bindParam($item, $params);
118
        }
119
120 24
        return $placeholders;
121
    }
122
123
    /**
124
     * @param ArrayExpression $expression
125
     * @param array|mixed|QueryInterface $value
126
     *
127
     * @return ArrayExpression
128
     */
129 3
    private function unnestArrayExpression(ArrayExpression $expression, $value): ArrayExpression
130
    {
131 3
        $expressionClass = get_class($expression);
132
133 3
        return new $expressionClass($value, $expression->getType(), $expression->getDimension() - 1);
134
    }
135
136
    /**
137
     * @param ArrayExpression $expression
138
     *
139
     * @return string the typecast expression based on {@see type}.
140
     */
141 27
    protected function getTypeHint(ArrayExpression $expression): string
142
    {
143
        /** @var string|null $type */
144 27
        $type = $expression->getType();
145
146 27
        if ($type === null) {
147 20
            return '';
148
        }
149
150
        /** @var int $dimension */
151 8
        $dimension = $expression->getDimension();
152
153 8
        $result = '::' . $type;
154 8
        $result .= str_repeat('[]', $dimension);
155
156 8
        return $result;
157
    }
158
159
    /**
160
     * Build an array expression from a subquery SQL.
161
     *
162
     * @param string $sql the subquery SQL.
163
     * @param ArrayExpression $expression
164
     *
165
     * @return string the subquery array expression.
166
     */
167 2
    protected function buildSubqueryArray(string $sql, ArrayExpression $expression): string
168
    {
169 2
        return 'ARRAY(' . $sql . ')' . $this->getTypeHint($expression);
170
    }
171
172
    /**
173
     * Casts $value to use in $expression.
174
     *
175
     * @param ArrayExpression $expression
176
     * @param ExpressionInterface|int $value
177
     *
178
     * @return ExpressionInterface|int
179
     */
180 22
    protected function typecastValue(ArrayExpression $expression, $value)
181
    {
182 22
        if ($value instanceof ExpressionInterface) {
183 3
            return $value;
184
        }
185
186 20
        if (in_array($expression->getType(), [Schema::TYPE_JSON, Schema::TYPE_JSONB], true)) {
187 2
            return new JsonExpression($value);
188
        }
189
190 19
        return $value;
191
    }
192
}
193