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