Passed
Branch dev (21c681)
by Wilmer
03:59 queued 45s
created

DMLQueryBuilder   A

Complexity

Total Complexity 11

Size/Duplication

Total Lines 117
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 36
c 1
b 0
f 0
dl 0
loc 117
ccs 36
cts 36
cp 1
rs 10
wmc 11

3 Methods

Rating   Name   Duplication   Size   Complexity  
A resetSequence() 0 26 5
A __construct() 0 3 1
A upsert() 0 36 5
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\DMLQueryBuilder as AbstractDMLQueryBuilder;
16
use Yiisoft\Db\Query\Query;
17
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...
18
19
final class DMLQueryBuilder extends AbstractDMLQueryBuilder
20
{
21 342
    public function __construct(private QueryBuilderInterface $queryBuilder)
22
    {
23 342
        parent::__construct($queryBuilder);
24 342
    }
25
26
    /**
27
     * Creates a SQL statement for resetting the sequence value of a table's primary key.
28
     *
29
     * The sequence will be reset such that the primary key of the next new row inserted will have the specified value
30
     * or 1.
31
     *
32
     * @param string $tableName the name of the table whose primary key sequence will be reset.
33
     * @param array|int|string|null $value the value for the primary key of the next new row inserted. If this is not
34
     * set, the next new row's primary key will have a value 1.
35
     *
36
     * @throws Exception|InvalidArgumentException|InvalidConfigException|Throwable
37
     *
38
     * @return string the SQL statement for resetting sequence.
39
     */
40 3
    public function resetSequence(string $tableName, array|int|string|null $value = null): string
41
    {
42 3
        $table = $this->queryBuilder->schema()->getTableSchema($tableName);
43
44 3
        if ($table !== null && $table->getSequenceName() !== null) {
45 1
            $tableName = $this->queryBuilder->quoter()->quoteTableName($tableName);
46
47 1
            if ($value === null) {
48 1
                $pk = $table->getPrimaryKey();
49 1
                $key = (string) reset($pk);
50 1
                $value = (int) $this->queryBuilder
51 1
                    ->command()
52 1
                    ->setSql("SELECT MAX(`$key`) FROM $tableName")
53 1
                    ->queryScalar() + 1;
54
            } else {
55 1
                $value = (int) $value;
56
            }
57
58 1
            return "ALTER TABLE $tableName AUTO_INCREMENT=$value";
59
        }
60
61 2
        if ($table === null) {
62 1
            throw new InvalidArgumentException("Table not found: $tableName");
63
        }
64
65 1
        throw new InvalidArgumentException("There is no sequence associated with table '$tableName'.");
66
    }
67
68
    /**
69
     * Creates an SQL statement to insert rows into a database table if they do not already exist (matching unique
70
     * constraints), or update them if they do.
71
     *
72
     * For example,
73
     *
74
     * ```php
75
     * $sql = $queryBuilder->upsert('pages', [
76
     *     'name' => 'Front page',
77
     *     'url' => 'http://example.com/', // url is unique
78
     *     'visits' => 0,
79
     * ], [
80
     *     'visits' => new Expression('visits + 1'),
81
     * ], $params);
82
     * ```
83
     *
84
     * The method will properly escape the table and column names.
85
     *
86
     * @param string $table the table that new rows will be inserted into/updated in.
87
     * @param array|Query $insertColumns the column data (name => value) to be inserted into the table or instance of
88
     * {@see Query} to perform `INSERT INTO ... SELECT` SQL statement.
89
     * @param array|bool $updateColumns the column data (name => value) to be updated if they already exist. If `true`
90
     * is passed, the column data will be updated to match the insert column data. If `false` is passed, no update will
91
     * be performed if the column data already exists.
92
     * @param array $params the binding parameters that will be generated by this method. They should be bound to the DB
93
     * command later.
94
     *
95
     * @throws Exception|InvalidConfigException|JsonException|NotSupportedException if this is not supported by the
96
     * underlying DBMS.
97
     *
98
     * @return string the resulting SQL.
99
     */
100 18
    public function upsert(string $table, Query|array $insertColumns, bool|array $updateColumns, array &$params): string
101
    {
102 18
        $insertSql = $this->insert($table, $insertColumns, $params);
103
104
        /** @var array $uniqueNames */
105 18
        [$uniqueNames, , $updateNames] = $this->queryBuilder->prepareUpsertColumns(
106 18
            $table,
107
            $insertColumns,
108
            $updateColumns,
109
        );
110
111 18
        if (empty($uniqueNames)) {
112 3
            return $insertSql;
113
        }
114
115 15
        if ($updateColumns === true) {
0 ignored issues
show
introduced by
The condition $updateColumns === true is always false.
Loading history...
116 4
            $updateColumns = [];
117
            /** @var string $name */
118 4
            foreach ($updateNames as $name) {
119 4
                $updateColumns[$name] = new Expression(
120 4
                    'VALUES(' . $this->queryBuilder->quoter()->quoteColumnName($name) . ')'
121
                );
122
            }
123 11
        } elseif ($updateColumns === false) {
0 ignored issues
show
introduced by
The condition $updateColumns === false is always false.
Loading history...
124 5
            $columnName = (string) reset($uniqueNames);
125 5
            $name = $this->queryBuilder->quoter()->quoteColumnName($columnName);
126 5
            $updateColumns = [$name => new Expression($this->queryBuilder->quoter()->quoteTableName($table) . '.' . $name)];
127
        }
128
129
        /**
130
         *  @psalm-var array<array-key, string> $updates
131
         *  @psalm-var array<string, ExpressionInterface|string> $updateColumns
132
         */
133 15
        [$updates, $params] = $this->queryBuilder->prepareUpdateSets($table, $updateColumns, $params);
134
135 15
        return $insertSql . ' ON DUPLICATE KEY UPDATE ' . implode(', ', $updates);
136
    }
137
}
138