Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
36 / 36
100.00% covered (success)
100.00%
9 / 9
CRAP
100.00% covered (success)
100.00%
1 / 1
Statement
100.00% covered (success)
100.00%
36 / 36
100.00% covered (success)
100.00%
9 / 9
18
100.00% covered (success)
100.00%
1 / 1
 options
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 hasOptions
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 renderOptions
n/a
0 / 0
n/a
0 / 0
0
 subquery
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setLimit
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 renderLimit
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
5
 renderIdentifier
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 renderAliasedIdentifier
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
 renderValue
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 renderAssignment
100.00% covered (success)
100.00%
2 / 2
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 Closure;
13use InvalidArgumentException;
14
15/**
16 * Class Statement.
17 *
18 * @see https://mariadb.com/kb/en/data-manipulation/
19 *
20 * @package database
21 */
22abstract class Statement extends \Framework\Database\Statement
23{
24    /**
25     * Sets the statement options.
26     *
27     * @param string $option One of the OPT_* constants
28     * @param string ...$options Each option value must be one of the OPT_* constants
29     *
30     * @return static
31     */
32    public function options(string $option, string ...$options) : static
33    {
34        $this->sql['options'] = [];
35        foreach ([$option, ...$options] as $option) {
36            $this->sql['options'][] = $option;
37        }
38        return $this;
39    }
40
41    /**
42     * Tells if the statement has options set.
43     *
44     * @return bool
45     */
46    protected function hasOptions() : bool
47    {
48        return isset($this->sql['options']);
49    }
50
51    abstract protected function renderOptions() : ?string;
52
53    /**
54     * Returns an SQL part between parentheses.
55     *
56     * @param Closure $subquery A {@see Closure} having the current Manipulation
57     * instance as first argument. The returned value must be scalar
58     *
59     * @see https://mariadb.com/kb/en/subqueries/
60     * @see https://mariadb.com/kb/en/built-in-functions/
61     *
62     * @return string
63     */
64    protected function subquery(Closure $subquery) : string
65    {
66        return '(' . $subquery($this->database) . ')';
67    }
68
69    /**
70     * Sets the LIMIT clause.
71     *
72     * @param int $limit
73     * @param int|null $offset
74     *
75     * @see https://mariadb.com/kb/en/limit/
76     *
77     * @return static
78     */
79    protected function setLimit(int $limit, int $offset = null) : static
80    {
81        $this->sql['limit'] = [
82            'limit' => $limit,
83            'offset' => $offset,
84        ];
85        return $this;
86    }
87
88    /**
89     * Renders the LIMIT clause.
90     *
91     * @return string|null
92     */
93    protected function renderLimit() : ?string
94    {
95        if ( ! isset($this->sql['limit'])) {
96            return null;
97        }
98        if ($this->sql['limit']['limit'] < 1) {
99            throw new InvalidArgumentException('LIMIT must be greater than 0');
100        }
101        $offset = $this->sql['limit']['offset'];
102        if ($offset !== null) {
103            if ($offset < 1) {
104                throw new InvalidArgumentException('LIMIT OFFSET must be greater than 0');
105            }
106            $offset = " OFFSET {$this->sql['limit']['offset']}";
107        }
108        return " LIMIT {$this->sql['limit']['limit']}{$offset}";
109    }
110
111    /**
112     * Renders a column part.
113     *
114     * @param Closure|string $column The column name or a subquery
115     *
116     * @return string
117     */
118    protected function renderIdentifier(Closure | string $column) : string
119    {
120        return $column instanceof Closure
121            ? $this->subquery($column)
122            : $this->database->protectIdentifier($column);
123    }
124
125    /**
126     * Renders a column part with an optional alias name, AS clause.
127     *
128     * @param array<string,Closure|string>|Closure|string $column The column name,
129     * a subquery or an array where the index is the alias and the value is the column/subquery
130     *
131     * @return string
132     */
133    protected function renderAliasedIdentifier(array | Closure | string $column) : string
134    {
135        if (\is_array($column)) {
136            if (\count($column) !== 1) {
137                throw new InvalidArgumentException('Aliased column must have only 1 key');
138            }
139            $alias = (string) \array_key_first($column);
140            return $this->renderIdentifier($column[$alias]) . ' AS '
141                . $this->database->protectIdentifier($alias);
142        }
143        return $this->renderIdentifier($column);
144    }
145
146    /**
147     * Renders a subquery or quote a value.
148     *
149     * @param Closure|float|int|string|null $value A {@see Closure} for
150     * subquery, other types to quote
151     *
152     * @return float|int|string
153     */
154    protected function renderValue(Closure | float | int | string | null $value) : float | int | string
155    {
156        return $value instanceof Closure
157            ? $this->subquery($value)
158            : $this->database->quote($value);
159    }
160
161    /**
162     * Renders an assignment part.
163     *
164     * @param string $identifier Identifier/column name
165     * @param Closure|float|int|string|null $expression Expression/value
166     *
167     * @see Statement::renderValue()
168     * @see https://mariadb.com/kb/en/assignment-operators-assignment-operator/
169     *
170     * @return string
171     */
172    protected function renderAssignment(
173        string $identifier,
174        Closure | float | int | string | null $expression
175    ) : string {
176        return $this->database->protectIdentifier($identifier)
177            . ' = ' . $this->renderValue($expression);
178    }
179}