Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
62 / 62
100.00% covered (success)
100.00%
8 / 8
CRAP
100.00% covered (success)
100.00%
1 / 1
Replace
100.00% covered (success)
100.00%
62 / 62
100.00% covered (success)
100.00%
8 / 8
23
100.00% covered (success)
100.00%
1 / 1
 into
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 renderInto
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 columns
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 renderColumns
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
 renderOptions
100.00% covered (success)
100.00%
22 / 22
100.00% covered (success)
100.00%
1 / 1
5
 checkRowStatementsConflict
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
4
 sql
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
1 / 1
6
 run
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php declare(strict_types=1);
2/*
3 * This file is part of Aplus Framework Database Library.
4 *
5 * (c) Natan Felles <natanfelles@gmail.com>
6 *
7 * For the full copyright and license information, please view the LICENSE
8 * file that was distributed with this source code.
9 */
10namespace Framework\Database\Manipulation;
11
12use InvalidArgumentException;
13use LogicException;
14
15/**
16 * Class Replace.
17 *
18 * @see https://mariadb.com/kb/en/replace/
19 *
20 * @package database
21 */
22class Replace extends Statement
23{
24    use Traits\Select;
25    use Traits\Set;
26    use Traits\Values;
27
28    /**
29     * @see https://mariadb.com/kb/en/insert-delayed/
30     *
31     * @var string
32     */
33    public const OPT_DELAYED = 'DELAYED';
34    /**
35     * @see https://mariadb.com/kb/en/high_priority-and-low_priority/
36     *
37     * @var string
38     */
39    public const OPT_LOW_PRIORITY = 'LOW_PRIORITY';
40
41    /**
42     * @param string $table
43     *
44     * @return static
45     */
46    public function into(string $table) : static
47    {
48        $this->sql['into'] = $table;
49        return $this;
50    }
51
52    protected function renderInto() : string
53    {
54        if ( ! isset($this->sql['into'])) {
55            throw new LogicException('INTO table must be set');
56        }
57        return ' INTO ' . $this->renderIdentifier($this->sql['into']);
58    }
59
60    /**
61     * @param string $column
62     * @param string ...$columns
63     *
64     * @return static
65     */
66    public function columns(string $column, string ...$columns) : static
67    {
68        $this->sql['columns'] = [$column, ...$columns];
69        return $this;
70    }
71
72    protected function renderColumns() : ?string
73    {
74        if ( ! isset($this->sql['columns'])) {
75            return null;
76        }
77        $columns = [];
78        foreach ($this->sql['columns'] as $column) {
79            $columns[] = $this->renderIdentifier($column);
80        }
81        $columns = \implode(', ', $columns);
82        return " ({$columns})";
83    }
84
85    protected function renderOptions() : ?string
86    {
87        if ( ! $this->hasOptions()) {
88            return null;
89        }
90        $options = $this->sql['options'];
91        foreach ($options as &$option) {
92            $input = $option;
93            $option = \strtoupper($option);
94            if ( ! \in_array($option, [
95                static::OPT_DELAYED,
96                static::OPT_LOW_PRIORITY,
97            ], true)) {
98                throw new InvalidArgumentException("Invalid option: {$input}");
99            }
100        }
101        unset($option);
102        $intersection = \array_intersect(
103            $options,
104            [static::OPT_DELAYED, static::OPT_LOW_PRIORITY]
105        );
106        if (\count($intersection) > 1) {
107            throw new LogicException(
108                'Options LOW_PRIORITY and DELAYED can not be used together'
109            );
110        }
111        $options = \implode(' ', $options);
112        return " {$options}";
113    }
114
115    protected function checkRowStatementsConflict() : void
116    {
117        if ( ! isset($this->sql['values'])
118            && ! isset($this->sql['select'])
119            && ! $this->hasSet()
120        ) {
121            throw new LogicException(
122                'The REPLACE INTO must be followed by VALUES, SET or SELECT statement'
123            );
124        }
125    }
126
127    /**
128     * Renders the REPLACE statement.
129     *
130     * @return string
131     */
132    public function sql() : string
133    {
134        $sql = 'REPLACE' . \PHP_EOL;
135        $part = $this->renderOptions();
136        if ($part) {
137            $sql .= $part . \PHP_EOL;
138        }
139        $sql .= $this->renderInto() . \PHP_EOL;
140        $part = $this->renderColumns();
141        if ($part) {
142            $sql .= $part . \PHP_EOL;
143        }
144        $this->checkRowStatementsConflict();
145        $part = $this->renderValues();
146        if ($part) {
147            $sql .= $part . \PHP_EOL;
148        }
149        $part = $this->renderSetCheckingConflicts();
150        if ($part) {
151            $sql .= $part . \PHP_EOL;
152        }
153        $part = $this->renderSelect();
154        if ($part) {
155            $sql .= $part . \PHP_EOL;
156        }
157        return $sql;
158    }
159
160    /**
161     * Runs the REPLACE statement.
162     *
163     * @return int|string The number of affected rows
164     */
165    public function run() : int | string
166    {
167        return $this->database->exec($this->sql());
168    }
169}