Passed
Branch dev (260d54)
by Wilmer
09:57
created

DMLQueryBuilder::upsert()   C

Complexity

Conditions 13
Paths 109

Size

Total Lines 101
Code Lines 54

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 48
CRAP Score 13.0014

Importance

Changes 0
Metric Value
cc 13
eloc 54
nc 109
nop 4
dl 0
loc 101
ccs 48
cts 49
cp 0.9796
crap 13.0014
rs 6.5416
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\Oracle;
6
7
use InvalidArgumentException;
8
use JsonException;
9
use Yiisoft\Db\Constraint\Constraint;
10
use Yiisoft\Db\Exception\Exception;
11
use Yiisoft\Db\Exception\InvalidConfigException;
12
use Yiisoft\Db\Exception\NotSupportedException;
13
use Yiisoft\Db\Expression\Expression;
14
use Yiisoft\Db\Oracle\PDO\QueryBuilderPDOOracle;
15
use Yiisoft\Db\Query\DMLQueryBuilder as AbstractDMLQueryBuilder;
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
use Yiisoft\Db\Query\QueryInterface;
18
19
use function implode;
20
use function ltrim;
21
use function strrpos;
22
23
final class DMLQueryBuilder extends AbstractDMLQueryBuilder
24
{
25 331
    public function __construct(private QueryBuilderInterface $queryBuilder)
26
    {
27 331
        parent::__construct($queryBuilder);
28
    }
29
30
    /**
31
     * @link https://docs.oracle.com/cd/B28359_01/server.111/b28286/statements_9016.htm#SQLRF01606
32
     *
33
     * @param string $table
34
     * @param array|QueryInterface $insertColumns
35
     * @param array|bool $updateColumns
36
     * @param array $params
37
     *
38
     * @throws Exception|InvalidArgumentException|InvalidConfigException|JsonException|NotSupportedException
39
     *
40
     * @return string
41
     */
42 17
    public function upsert(
43
        string $table,
44
        QueryInterface|array $insertColumns,
45
        array|bool $updateColumns,
46
        array &$params = []
47
    ): string {
48 17
        $usingValues = null;
49 17
        $constraints = [];
50
51
        /** @var Constraint[] $constraints */
52 17
        [$uniqueNames, $insertNames, $updateNames] = $this->queryBuilder->prepareUpsertColumns(
53
            $table,
54
            $insertColumns,
55
            $updateColumns,
56
            $constraints
57
        );
58
59 17
        if (empty($uniqueNames)) {
60 3
            return $this->insert($table, $insertColumns, $params);
61
        }
62
63 14
        if ($updateNames === []) {
64
            /** there are no columns to update */
65
            $updateColumns = false;
66
        }
67
68 14
        $onCondition = ['or'];
69 14
        $quotedTableName = $this->queryBuilder->quoter()->quoteTableName($table);
70
71 14
        foreach ($constraints as $constraint) {
72 14
            $columnNames = $constraint->getColumnNames() ?? [];
73 14
            $constraintCondition = ['and'];
74
            /** @psalm-var string[] $columnNames */
75 14
            foreach ($columnNames as $name) {
76 14
                $quotedName = $this->queryBuilder->quoter()->quoteColumnName($name);
77 14
                $constraintCondition[] = "$quotedTableName.$quotedName=\"EXCLUDED\".$quotedName";
78
            }
79
80 14
            $onCondition[] = $constraintCondition;
81
        }
82
83 14
        $on = $this->queryBuilder->buildCondition($onCondition, $params);
84
        /** @psalm-var string[] $placeholders */
85 14
        [, $placeholders, $values, $params] = $this->queryBuilder->prepareInsertValues($table, $insertColumns, $params);
86
87 14
        if (!empty($placeholders)) {
88 6
            $usingSelectValues = [];
89
            /** @psalm-var string[] $insertNames */
90 6
            foreach ($insertNames as $index => $name) {
91 6
                $usingSelectValues[$name] = new Expression($placeholders[$index]);
92
            }
93
94
            /** @var QueryBuilderPDOOracle $usingSubQuery */
95 6
            $usingSubQuery = $this->queryBuilder;
96 6
            $query = $usingSubQuery->query()->select($usingSelectValues)->from('DUAL');
97 6
            [$usingValues, $params] = $this->queryBuilder->build($query, (array) $params);
98
        }
99
100 14
        $insertValues = [];
101 14
        $mergeSql = 'MERGE INTO '
102 14
            . $this->queryBuilder->quoter()->quoteTableName($table)
103
            . ' '
104 14
            . 'USING (' . ($usingValues ?? ltrim((string) $values, ' '))
105
            . ') "EXCLUDED" '
106 14
            . "ON ($on)";
107
108
        /** @psalm-var string[] $insertNames */
109 14
        foreach ($insertNames as $name) {
110 14
            $quotedName = $this->queryBuilder->quoter()->quoteColumnName($name);
111
112 14
            if (strrpos($quotedName, '.') === false) {
113 14
                $quotedName = '"EXCLUDED".' . $quotedName;
114
            }
115
116 14
            $insertValues[] = $quotedName;
117
        }
118
119 14
        $insertSql = 'INSERT (' . implode(', ', $insertNames) . ')' . ' VALUES (' . implode(', ', $insertValues) . ')';
120
121 14
        if ($updateColumns === false) {
122 4
            return "$mergeSql WHEN NOT MATCHED THEN $insertSql";
123
        }
124
125 10
        if ($updateColumns === true) {
0 ignored issues
show
introduced by
The condition $updateColumns === true is always false.
Loading history...
126 4
            $updateColumns = [];
127
            /** @psalm-var string[] $updateNames */
128 4
            foreach ($updateNames as $name) {
129 4
                $quotedName = $this->queryBuilder->quoter()->quoteColumnName($name);
130
131 4
                if (strrpos($quotedName, '.') === false) {
132 4
                    $quotedName = '"EXCLUDED".' . $quotedName;
133
                }
134 4
                $updateColumns[$name] = new Expression($quotedName);
135
            }
136
        }
137
138
        /** @psalm-var string[] $updates */
139 10
        [$updates, $params] = $this->queryBuilder->prepareUpdateSets($table, $updateColumns, (array) $params);
140 10
        $updateSql = 'UPDATE SET ' . implode(', ', $updates);
141
142 10
        return "$mergeSql WHEN MATCHED THEN $updateSql WHEN NOT MATCHED THEN $insertSql";
143
    }
144
}
145