Test Failed
Branch master (e718b7)
by Wilmer
27:58 queued 23:58
created

DMLQueryBuilder   A

Complexity

Total Complexity 17

Size/Duplication

Total Lines 181
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 42
c 2
b 0
f 0
dl 0
loc 181
ccs 48
cts 48
cp 1
rs 10
wmc 17

5 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
A resetSequence() 0 24 4
A insertWithReturningPks() 0 3 1
A upsert() 0 40 5
A prepareInsertValues() 0 23 6
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\Mysql;
6
7
use JsonException;
8
use Throwable;
9
use Yiisoft\Db\Exception\Exception;
10
use Yiisoft\Db\Exception\InvalidArgumentException;
11
use Yiisoft\Db\Exception\InvalidConfigException;
12
use Yiisoft\Db\Exception\NotSupportedException;
13
use Yiisoft\Db\Expression\Expression;
14
use Yiisoft\Db\Expression\ExpressionInterface;
15
use Yiisoft\Db\Query\Query;
16
use Yiisoft\Db\Query\QueryInterface;
17
use Yiisoft\Db\QueryBuilder\AbstractDMLQueryBuilder;
18
use Yiisoft\Db\QueryBuilder\QueryBuilderInterface;
19
use Yiisoft\Db\Schema\QuoterInterface;
20
use Yiisoft\Db\Schema\SchemaInterface;
21
22
use function implode;
23
use function reset;
24
25
final class DMLQueryBuilder extends AbstractDMLQueryBuilder
26
{
27 501
    public function __construct(
28
        QueryBuilderInterface $queryBuilder,
29
        private QuoterInterface $quoter,
30
        private SchemaInterface $schema
31
    ) {
32 501
        parent::__construct($queryBuilder, $quoter, $schema);
33
    }
34
35
    /**
36
     * @throws Exception
37
     * @throws InvalidArgumentException
38
     * @throws InvalidConfigException
39
     * @throws NotSupportedException
40
     */
41
    public function insertWithReturningPks(string $table, QueryInterface|array $columns, array &$params = []): string
42
    {
43
        throw new NotSupportedException(__METHOD__ . ' is not supported by Mysql.');
44
    }
45
46
    /**
47
     * Creates a SQL statement for resetting the sequence value of a table's primary key.
48
     *
49 4
     * The sequence will be reset such that the primary key of the next new row inserted will have the specified value
50
     * or 1.
51 4
     *
52
     * @param string $tableName The name of the table whose primary key sequence will be reset.
53 4
     * @param int|string|null $value The value for the primary key of the next new row inserted. If this is not set, the
54 1
     * next new row's primary key will have a value 1.
55
     *
56
     * @throws Exception
57 3
     * @throws InvalidArgumentException
58 3
     * @throws Throwable
59 1
     *
60
     * @return string The SQL statement for resetting sequence.
61
     */
62 2
    public function resetSequence(string $tableName, int|string $value = null): string
63
    {
64 2
        $table = $this->schema->getTableSchema($tableName);
65 1
66
        if ($table === null) {
67
            throw new InvalidArgumentException("Table not found: '$tableName'.");
68 2
        }
69 2
70
        $sequenceName = $table->getSequenceName();
71 2
        if ($sequenceName === null) {
72 2
            throw new InvalidArgumentException("There is not sequence associated with table '$tableName'.");
73
        }
74 2
75
        $tableName = $this->quoter->quoteTableName($tableName);
76
77
        if ($value !== null) {
78
            return 'ALTER TABLE ' . $tableName . ' AUTO_INCREMENT=' . $value . ';';
79
        }
80
81
        $pk = $table->getPrimaryKey();
82
        $key = (string) reset($pk);
83
84
        return "SET @new_autoincrement_value := (SELECT MAX(`$key`) + 1 FROM $tableName);
85
SET @sql = CONCAT('ALTER TABLE $tableName AUTO_INCREMENT =', @new_autoincrement_value);
86
PREPARE autoincrement_stmt FROM @sql;
87
EXECUTE autoincrement_stmt";
88
    }
89
90
    /**
91
     * Creates an SQL statement to insert rows into a database table if they do not already exist (matching unique
92
     * constraints), or update them if they do.
93
     *
94
     * For example,
95
     *
96
     * ```php
97
     * $sql = $queryBuilder->upsert('pages', [
98
     *     'name' => 'Front page',
99
     *     'url' => 'http://example.com/', // url is unique
100
     *     'visits' => 0,
101
     * ], [
102
     *     'visits' => new Expression('visits + 1'),
103
     * ], $params);
104
     * ```
105
     *
106
     * The method will properly escape the table and column names.
107
     *
108
     * @param string $table The table that new rows will be inserted into/updated in.
109 34
     * @param array|QueryInterface $insertColumns The column data (name => value) to be inserted into the table or
110
     * instance of {@see Query} to perform `INSERT INTO ... SELECT` SQL statement.
111
     * @param array|bool $updateColumns The column data (name => value) to be updated if they already exist. If `true`
112
     * is passed, the column data will be updated to match the insert column data. If `false` is passed, no update will
113
     * be performed if the column data already exists.
114
     * @param array $params The binding parameters that will be generated by this method. They should be bound to the DB
115 34
     * command later.
116
     *
117
     * @throws Exception
118 34
     * @throws InvalidConfigException
119 34
     * @throws JsonException
120 34
     * @throws NotSupportedException If this is not supported by the underlying DBMS.
121 34
     *
122 34
     * @return string The resulting SQL.
123
     */
124 34
    public function upsert(
125 2
        string $table,
126
        QueryInterface|array $insertColumns,
127
        bool|array $updateColumns,
128 32
        array &$params
129 10
    ): string {
130
        $insertSql = $this->insert($table, $insertColumns, $params);
131 10
132 8
        /** @var array $uniqueNames */
133 8
        [$uniqueNames, , $updateNames] = $this->prepareUpsertColumns(
134 8
            $table,
135
            $insertColumns,
136
            $updateColumns,
137
        );
138 32
139 14
        if (empty($uniqueNames)) {
140
            return $insertSql;
141
        }
142
143
        if ($updateColumns === true) {
0 ignored issues
show
introduced by
The condition $updateColumns === true is always false.
Loading history...
144
            $updateColumns = [];
145
            /** @var string $name */
146 18
            foreach ($updateNames as $name) {
147
                $updateColumns[$name] = new Expression(
148 18
                    'VALUES(' . $this->quoter->quoteColumnName($name) . ')'
149
                );
150
            }
151
        }
152
153
        if (empty($updateColumns)) {
154
            return str_replace('INSERT INTO', 'INSERT IGNORE INTO', $insertSql);
155
        }
156
157
        /**
158
         *  @psalm-var array<array-key, string> $updates
159
         *  @psalm-var array<string, ExpressionInterface|string> $updateColumns
160
         */
161
        [$updates, $params] = $this->prepareUpdateSets($table, $updateColumns, $params);
162
163
        return $insertSql . ' ON DUPLICATE KEY UPDATE ' . implode(', ', $updates);
164 73
    }
165
166
    /**
167
     * Prepares a `VALUES` part for an `INSERT` SQL statement.
168
     *
169
     * @param string $table The table that new rows will be inserted into.
170 73
     * @param array|QueryInterface $columns The column data (name => value) to be inserted into the table or instance of
171
     * {@see Query|Query} to perform INSERT INTO ... SELECT SQL statement.
172 70
     * @param array $params The binding parameters that will be generated by this method. They should be bound to the DB
173 2
     * command later.
174
     *
175 2
     * @throws Exception
176 2
     * @throws InvalidArgumentException
177 2
     * @throws InvalidConfigException
178 2
     * @throws JsonException
179 2
     * @throws NotSupportedException
180 2
     *
181 2
     * @return array Array of column names, placeholders, values and params.
182
     */
183
    protected function prepareInsertValues(string $table, QueryInterface|array $columns, array $params = []): array
184
    {
185
        /**
186 70
         * @var array $names
187
         * @var array $placeholders
188
         */
189
        [$names, $placeholders, $values, $params] = parent::prepareInsertValues($table, $columns, $params);
190
191
        if (!$columns instanceof QueryInterface && empty($names)) {
0 ignored issues
show
introduced by
$columns is never a sub-type of Yiisoft\Db\Query\QueryInterface.
Loading history...
192
            $tableSchema = $this->schema->getTableSchema($table);
193
194
            if ($tableSchema !== null) {
195
                $columns = $tableSchema->getColumns();
196
                $columns = !empty($tableSchema->getPrimaryKey())
197
                    ? $tableSchema->getPrimaryKey() : [reset($columns)->getName()];
198
                foreach ($columns as $name) {
199
                    $names[] = $this->quoter->quoteColumnName($name);
200
                    $placeholders[] = 'DEFAULT';
201
                }
202
            }
203
        }
204
205
        return [$names, $placeholders, $values, $params];
206
    }
207
}
208