Passed
Push — dev ( 749693...3b6d39 )
by Wilmer
16:28 queued 07:00
created

CommandPDOOracle::bindPendingParams()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 19
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 3

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 3
eloc 12
c 2
b 0
f 0
nc 3
nop 0
dl 0
loc 19
ccs 11
cts 11
cp 1
crap 3
rs 9.8666
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\Oracle\PDO;
6
7
use PDO;
8
use PDOException;
9
use Yiisoft\Db\Cache\QueryCache;
10
use Yiisoft\Db\Command\CommandPDO;
11
use Yiisoft\Db\Command\ParamInterface;
12
use Yiisoft\Db\Connection\ConnectionPDOInterface;
13
use Yiisoft\Db\Exception\ConvertException;
14
use Yiisoft\Db\Exception\Exception;
15
use Yiisoft\Db\Query\QueryBuilder;
0 ignored issues
show
Bug introduced by
The type Yiisoft\Db\Query\QueryBuilder was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
16
use Yiisoft\Db\Query\QueryBuilderInterface;
0 ignored issues
show
Bug introduced by
The type Yiisoft\Db\Query\QueryBuilderInterface was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
17
18
use function array_keys;
19
use function count;
20
use function implode;
21
use function strlen;
22
23
/**
24
 * Command represents an Oracle SQL statement to be executed against a database.
25
 */
26
final class CommandPDOOracle extends CommandPDO
27
{
28 331
    public function __construct(private ConnectionPDOInterface $db, QueryCache $queryCache)
29
    {
30 331
        parent::__construct($queryCache);
31
    }
32
33 169
    public function queryBuilder(): QueryBuilderInterface
34
    {
35 169
        return $this->db->getQueryBuilder();
36
    }
37
38 1
    public function insertEx(string $table, array $columns): bool|array
39
    {
40 1
        $params = [];
41 1
        $sql = $this->queryBuilder()->insertEx($table, $columns, $params);
42
43 1
        $tableSchema = $this->queryBuilder()->schema()->getTableSchema($table);
44
45 1
        $returnColumns = $tableSchema?->getPrimaryKey() ?? [];
46 1
        $columnSchemas = $tableSchema?->getColumns() ?? [];
47
48 1
        $returnParams = [];
49 1
        $returning = [];
50 1
        foreach ($returnColumns as $name) {
51 1
            $phName = QueryBuilder::PARAM_PREFIX . (count($params) + count($returnParams));
52
53 1
            $returnParams[$phName] = [
54
                'column' => $name,
55
                'value' => '',
56
            ];
57
58 1
            if (!isset($columnSchemas[$name]) || $columnSchemas[$name]->getPhpType() !== 'integer') {
59
                $returnParams[$phName]['dataType'] = PDO::PARAM_STR;
60
            } else {
61 1
                $returnParams[$phName]['dataType'] = PDO::PARAM_INT;
62
            }
63
64 1
            $returnParams[$phName]['size'] = $columnSchemas[$name]->getSize() ?? -1;
65
66 1
            $returning[] = $this->db->getQuoter()->quoteColumnName($name);
67
        }
68
69 1
        $sql .= ' RETURNING ' . implode(', ', $returning) . ' INTO ' . implode(', ', array_keys($returnParams));
70
71 1
        $this->setSql($sql)->bindValues($params);
72 1
        $this->prepare(false);
73
74
        /** @psalm-var array<string, array{column: string, value: mixed, dataType: int, size: int}> $returnParams */
75 1
        foreach ($returnParams as $name => &$value) {
76 1
            $this->bindParam($name, $value['value'], $value['dataType'], $value['size']);
77
        }
78
79 1
        if (!$this->execute()) {
80
            return false;
81
        }
82
83 1
        $result = [];
84
85 1
        foreach ($returnParams as $value) {
86
            /** @var mixed */
87 1
            $result[$value['column']] = $value['value'];
88
        }
89
90 1
        return $result;
91
    }
92
93 160
    public function prepare(?bool $forRead = null): void
94
    {
95 160
        if (isset($this->pdoStatement)) {
96 6
            $this->bindPendingParams();
97
98 6
            return;
99
        }
100
101 160
        $sql = $this->getSql();
102
103 160
        if ($this->db->getTransaction()) {
104
            /** master is in a transaction. use the same connection. */
105 5
            $forRead = false;
106
        }
107
108 160
        if ($forRead || ($forRead === null && $this->db->getSchema()->isReadQuery($sql))) {
109 156
            $pdo = $this->db->getSlavePdo();
110
        } else {
111 58
            $pdo = $this->db->getMasterPdo();
112
        }
113
114
        try {
115 160
            $this->pdoStatement = $pdo?->prepare($sql);
116 160
            $this->bindPendingParams();
117
        } catch (PDOException $e) {
118
            $message = $e->getMessage() . "\nFailed to prepare SQL: $sql";
119
            /** @var array|null */
120
            $errorInfo = $e->errorInfo ?? null;
121
122
            throw new Exception($message, $errorInfo, $e);
123
        }
124
    }
125
126 160
    protected function bindPendingParams(): void
127
    {
128 160
        $paramsPassedByReference = [];
129
130
        /** @psalm-var ParamInterface[] */
131 160
        $params = $this->params;
132
133 160
        foreach ($params as $name => $value) {
134 140
            if (PDO::PARAM_STR === $value->getType()) {
135
                /** @var mixed */
136 135
                $paramsPassedByReference[$name] = $value->getValue();
137 135
                $this->pdoStatement?->bindParam(
138
                    $name,
139 135
                    $paramsPassedByReference[$name],
140 135
                    $value->getType(),
141 135
                    strlen((string) $value->getValue())
142
                );
143
            } else {
144 22
                $this->pdoStatement?->bindValue($name, $value->getValue(), $value->getType());
145
            }
146
        }
147
    }
148
149 153
    protected function getCacheKey(int $queryMode, string $rawSql): array
150
    {
151
        return [
152 153
            __CLASS__,
153
            $queryMode,
154 153
            $this->db->getDriver()->getDsn(),
155 153
            $this->db->getDriver()->getUsername(),
156
            $rawSql,
157
        ];
158
    }
159
160 159
    protected function internalExecute(?string $rawSql): void
161
    {
162 159
        $attempt = 0;
163
164 159
        while (true) {
165
            try {
166
                if (
167 159
                    ++$attempt === 1
168 159
                    && $this->isolationLevel !== null
169 159
                    && $this->db->getTransaction() === null
170
                ) {
171
                    $this->db->transaction(fn (string $rawSql) => $this->internalExecute($rawSql), $this->isolationLevel);
172
                } else {
173 159
                    $this->pdoStatement?->execute();
174
                }
175 159
                break;
176 6
            } catch (PDOException $e) {
177 6
                $rawSql = $rawSql ?: $this->getRawSql();
178 6
                $e = (new ConvertException($e, $rawSql))->run();
179
180 6
                if ($this->retryHandler === null || !($this->retryHandler)($e, $attempt)) {
181 6
                    throw $e;
182
                }
183
            }
184
        }
185
    }
186
}
187