Passed
Pull Request — master (#287)
by Sergei
13:55 queued 09:29
created

DMLQueryBuilder::insertWithReturningPks()   A

Complexity

Conditions 5
Paths 10

Size

Total Lines 20
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 5

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
eloc 12
nc 10
nop 3
dl 0
loc 20
ccs 13
cts 13
cp 1
crap 5
rs 9.5555
c 1
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\Mssql;
6
7
use JsonException;
8
use Yiisoft\Db\Constraint\Constraint;
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\Query\QueryInterface;
15
use Yiisoft\Db\QueryBuilder\AbstractDMLQueryBuilder;
16
17
use function implode;
18
19
/**
20
 * Implements a DML (Data Manipulation Language) SQL statements for MSSQL Server.
21
 */
22
final class DMLQueryBuilder extends AbstractDMLQueryBuilder
23
{
24
    /**
25
     * @throws Exception
26
     * @throws InvalidArgumentException
27
     * @throws InvalidConfigException
28
     * @throws NotSupportedException
29
     */
30 13
    public function insertWithReturningPks(string $table, QueryInterface|array $columns, array &$params = []): string
31
    {
32 13
        $insertedCols = [];
33 13
        $primaryKeys = $this->schema->getTableSchema($table)?->getPrimaryKey() ?? [];
34
35 13
        foreach ($primaryKeys as $primaryKey) {
36 12
            $quotedName = $this->quoter->quoteColumnName($primaryKey);
37 12
            $insertedCols[] = 'INSERTED.' . $quotedName;
38
        }
39
40 13
        if (empty($insertedCols)) {
41 1
            return $this->insert($table, $columns, $params);
42
        }
43
44 12
        [$names, $placeholders, $values, $params] = $this->prepareInsertValues($table, $columns, $params);
45
46 12
        return 'INSERT INTO ' . $this->quoter->quoteTableName($table)
47 12
            . (!empty($names) ? ' (' . implode(', ', $names) . ')' : '')
48 12
            . ' OUTPUT ' . implode(', ', $insertedCols)
49 12
            . (!empty($placeholders) ? ' VALUES (' . implode(', ', $placeholders) . ')' : ' ' . $values);
50
    }
51
52
    /**
53
     * @throws InvalidArgumentException
54
     */
55 5
    public function resetSequence(string $table, int|string $value = null): string
56
    {
57 5
        $tableSchema = $this->schema->getTableSchema($table);
58
59 5
        if ($tableSchema === null) {
60 1
            throw new InvalidArgumentException("Table not found: '$table'.");
61
        }
62
63 4
        $sequenceName = $tableSchema->getSequenceName();
64
65 4
        if ($sequenceName === null) {
66 1
            throw new InvalidArgumentException("There is not sequence associated with table '$table'.'");
67
        }
68
69 3
        $tableName = $this->quoter->quoteTableName($table);
70
71 3
        if ($value === null) {
72 3
            return "DBCC CHECKIDENT ('$tableName', RESEED, 0) WITH NO_INFOMSGS;DBCC CHECKIDENT ('$tableName', RESEED)";
73
        }
74
75 1
        return "DBCC CHECKIDENT ('$tableName', RESEED, $value)";
76
    }
77
78
    /**
79
     * @throws Exception
80
     * @throws InvalidArgumentException
81
     * @throws InvalidConfigException
82
     * @throws JsonException
83
     * @throws NotSupportedException
84
     */
85 37
    public function upsert(
86
        string $table,
87
        QueryInterface|array $insertColumns,
88
        bool|array $updateColumns,
89
        array &$params = []
90
    ): string {
91
        /** @psalm-var Constraint[] $constraints */
92 37
        $constraints = [];
93
94 37
        [$uniqueNames, $insertNames, $updateNames] = $this->prepareUpsertColumns(
95 37
            $table,
96 37
            $insertColumns,
97 37
            $updateColumns,
98 37
            $constraints
99 37
        );
100
101 37
        if (empty($uniqueNames)) {
102 2
            return $this->insert($table, $insertColumns, $params);
103
        }
104
105 35
        $onCondition = ['or'];
106 35
        $quotedTableName = $this->quoter->quoteTableName($table);
107
108 35
        foreach ($constraints as $constraint) {
109 35
            $constraintCondition = ['and'];
110 35
            $columnNames = (array) $constraint->getColumnNames();
111
112
            /** @psalm-var string[] $columnNames */
113 35
            foreach ($columnNames as $name) {
114 35
                $quotedName = $this->quoter->quoteColumnName($name);
115 35
                $constraintCondition[] = "$quotedTableName.$quotedName=[EXCLUDED].$quotedName";
116
            }
117
118 35
            $onCondition[] = $constraintCondition;
119
        }
120
121 35
        $on = $this->queryBuilder->buildCondition($onCondition, $params);
122
123 35
        [, $placeholders, $values, $params] = $this->prepareInsertValues($table, $insertColumns, $params);
124
125 35
        $mergeSql = 'MERGE ' . $quotedTableName . ' WITH (HOLDLOCK) USING ('
126 35
            . (!empty($placeholders) ? 'VALUES (' . implode(', ', $placeholders) . ')' : $values)
127 35
            . ') AS [EXCLUDED] (' . implode(', ', $insertNames) . ') ' . "ON ($on)";
128
129 35
        $insertValues = [];
130
131 35
        foreach ($insertNames as $quotedName) {
132 35
            $insertValues[] = '[EXCLUDED].' . $quotedName;
133
        }
134
135 35
        $insertSql = 'INSERT (' . implode(', ', $insertNames) . ') VALUES (' . implode(', ', $insertValues) . ')';
136
137 35
        if ($updateColumns === false || $updateNames === []) {
0 ignored issues
show
introduced by
The condition $updateNames === array() is always false.
Loading history...
138
            /** there are no columns to update */
139 14
            return "$mergeSql WHEN NOT MATCHED THEN $insertSql;";
140
        }
141
142 21
        if ($updateColumns === true) {
0 ignored issues
show
introduced by
The condition $updateColumns === true is always false.
Loading history...
143 10
            $updateColumns = [];
144
145
            /** @psalm-var string[] $updateNames */
146 10
            foreach ($updateNames as $quotedName) {
147 10
                $updateColumns[$quotedName] = new Expression('[EXCLUDED].' . $quotedName);
148
            }
149
        }
150
151 21
        [$updates, $params] = $this->prepareUpdateSets($table, $updateColumns, $params);
152
153 21
        return "$mergeSql WHEN MATCHED THEN UPDATE SET " . implode(', ', $updates)
154 21
            . " WHEN NOT MATCHED THEN $insertSql;";
155
    }
156
}
157