Passed
Push — master ( 21771a...749afb )
by Wilmer
18:31 queued 03:34
created

Command   A

Complexity

Total Complexity 15

Size/Duplication

Total Lines 140
Duplicated Lines 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
wmc 15
eloc 46
dl 0
loc 140
rs 10
c 1
b 1
f 0

4 Methods

Rating   Name   Duplication   Size   Complexity  
A queryInternal() 0 27 3
A extractUsedParams() 0 16 4
A splitStatements() 0 23 5
A execute() 0 23 3
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\Sqlite;
6
7
use Throwable;
8
use Yiisoft\Db\Command\Command as BaseCommand;
9
use Yiisoft\Db\Exception\Exception;
10
use Yiisoft\Db\Exception\InvalidConfigException;
11
use Yiisoft\Db\Exception\NotSupportedException;
12
use Yiisoft\Strings\StringHelper;
13
14
use function ltrim;
15
use function preg_match_all;
16
use function strpos;
17
18
final class Command extends BaseCommand
19
{
20
    /**
21
     * Executes the SQL statement.
22
     *
23
     * This method should only be used for executing non-query SQL statement, such as `INSERT`, `DELETE`, `UPDATE` SQLs.
24
     * No result set will be returned.
25
     *
26
     * @throws Exception execution failed.
27
     *
28
     * @return int number of rows affected by the execution.
29
     */
30
    public function execute(): int
31
    {
32
        $sql = $this->getSql();
33
34
        $params = $this->params;
35
36
        $statements = $this->splitStatements($sql, $params);
37
38
        if ($statements === false) {
0 ignored issues
show
introduced by
The condition $statements === false is always true.
Loading history...
39
            return parent::execute();
40
        }
41
42
        $result = 0;
43
44
        foreach ($statements as $statement) {
45
            [$statementSql, $statementParams] = $statement;
46
            $this->setSql($statementSql)->bindValues($statementParams);
47
            $result = parent::execute();
48
        }
49
50
        $this->setSql($sql)->bindValues($params);
51
52
        return $result;
53
    }
54
55
    /**
56
     * Performs the actual DB query of a SQL statement.
57
     *
58
     * @param string $method method of PDOStatement to be called
59
     * @param array|int|null $fetchMode the result fetch mode.
60
     * Please refer to [PHP manual](http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php) for valid fetch
61
     * modes. If this parameter is null, the value set in {@see fetchMode} will be used.
62
     *
63
     *
64
     * @throws Exception if the query causes any problem.
65
     * @throws InvalidConfigException
66
     * @throws NotSupportedException
67
     * @throws Throwable
68
     *
69
     * @return mixed the method execution result.
70
     */
71
    protected function queryInternal(string $method, $fetchMode = null)
72
    {
73
        $sql = $this->getSql();
74
75
        $params = $this->params;
76
77
        $statements = $this->splitStatements($sql, $params);
78
79
        if ($statements === false) {
0 ignored issues
show
introduced by
The condition $statements === false is always true.
Loading history...
80
            return parent::queryInternal($method, $fetchMode);
81
        }
82
83
        [$lastStatementSql, $lastStatementParams] = \array_pop($statements);
84
85
        foreach ($statements as $statement) {
86
            [$statementSql, $statementParams] = $statement;
87
            $this->setSql($statementSql)->bindValues($statementParams);
88
            parent::execute();
89
        }
90
91
        $this->setSql($lastStatementSql)->bindValues($lastStatementParams);
92
93
        $result = parent::queryInternal($method, $fetchMode);
94
95
        $this->setSql($sql)->bindValues($params);
96
97
        return $result;
98
    }
99
100
    /**
101
     * Splits the specified SQL code into individual SQL statements and returns them or `false` if there's a single
102
     * statement.
103
     *
104
     * @param string $sql
105
     * @param array $params
106
     *
107
     * @return string[]|false
108
     */
109
    private function splitStatements($sql, $params)
110
    {
111
        $semicolonIndex = strpos($sql, ';');
112
113
        if ($semicolonIndex === false || $semicolonIndex === StringHelper::byteLength($sql) - 1) {
114
            return false;
115
        }
116
117
        $tokenizer = new SqlTokenizer($sql);
118
119
        $codeToken = $tokenizer->tokenize();
120
121
        if (\count($codeToken->getChildren()) === 1) {
122
            return false;
123
        }
124
125
        $statements = [];
126
127
        foreach ($codeToken->getChildren() as $statement) {
128
            $statements[] = [$statement->getSql(), $this->extractUsedParams($statement, $params)];
129
        }
130
131
        return $statements;
132
    }
133
134
    /**
135
     * Returns named bindings used in the specified statement token.
136
     *
137
     * @param SqlToken $statement
138
     * @param array $params
139
     *
140
     * @return array
141
     */
142
    private function extractUsedParams(SqlToken $statement, $params): array
143
    {
144
        preg_match_all('/(?P<placeholder>[:][a-zA-Z0-9_]+)/', $statement->getSql(), $matches, PREG_SET_ORDER);
145
146
        $result = [];
147
148
        foreach ($matches as $match) {
149
            $phName = ltrim($match['placeholder'], ':');
150
            if (isset($params[$phName])) {
151
                $result[$phName] = $params[$phName];
152
            } elseif (isset($params[':' . $phName])) {
153
                $result[':' . $phName] = $params[':' . $phName];
154
            }
155
        }
156
157
        return $result;
158
    }
159
}
160