|string ...$references
68 | *
69 | * @return static
70 | */
71 | public function table(
72 | Closure | array | string $reference,
73 | Closure | array | string ...$references
74 | ) : static {
75 | $this->sql['table'] = [];
76 | foreach ([$reference, ...$references] as $reference) {
77 | $this->sql['table'][] = $reference;
78 | }
79 | return $this;
80 | }
81 |
82 | protected function renderTable() : string
83 | {
84 | if (!isset($this->sql['table'])) {
85 | throw new LogicException('Table references must be set');
86 | }
87 | $tables = [];
88 | foreach ($this->sql['table'] as $table) {
89 | $tables[] = $this->renderAliasedIdentifier($table);
90 | }
91 | return ' ' . \implode(', ', $tables);
92 | }
93 |
94 | /**
95 | * Sets the LIMIT clause.
96 | *
97 | * @param int $limit
98 | *
99 | * @see https://mariadb.com/kb/en/limit/
100 | *
101 | * @return static
102 | */
103 | public function limit(int $limit) : static
104 | {
105 | return $this->setLimit($limit);
106 | }
107 |
108 | protected function renderSetPart() : string
109 | {
110 | if (!$this->hasSet()) {
111 | throw new LogicException('SET statement must be set');
112 | }
113 | return $this->renderSet();
114 | }
115 |
116 | /**
117 | * Renders the UPDATE statement.
118 | *
119 | * @return string
120 | */
121 | public function sql() : string
122 | {
123 | $sql = 'UPDATE' . \PHP_EOL;
124 | $part = $this->renderOptions();
125 | if ($part) {
126 | $sql .= $part . \PHP_EOL;
127 | }
128 | $sql .= $this->renderTable() . \PHP_EOL;
129 | $part = $this->renderJoin();
130 | if ($part) {
131 | $sql .= $part . \PHP_EOL;
132 | }
133 | $sql .= $this->renderSetPart() . \PHP_EOL;
134 | $part = $this->renderWhere();
135 | if ($part) {
136 | $sql .= $part . \PHP_EOL;
137 | }
138 | $part = $this->renderOrderBy();
139 | if ($part) {
140 | $sql .= $part . \PHP_EOL;
141 | }
142 | $part = $this->renderLimit();
143 | if ($part) {
144 | $sql .= $part . \PHP_EOL;
145 | }
146 | return $sql;
147 | }
148 |
149 | /**
150 | * Runs the UPDATE statement.
151 | *
152 | * @return int|string The number of affected rows
153 | */
154 | public function run() : int | string
155 | {
156 | return $this->database->exec($this->sql());
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/src/Debug/DatabaseCollector.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 | namespace Framework\Database\Debug;
11 |
12 | use Framework\Database\Database;
13 | use Framework\Debug\Collector;
14 | use Framework\Debug\Debugger;
15 |
16 | /**
17 | * Class DatabaseCollector.
18 | *
19 | * @package database
20 | */
21 | class DatabaseCollector extends Collector
22 | {
23 | protected Database $database;
24 |
25 | public function setDatabase(Database $database) : static
26 | {
27 | $this->database = $database;
28 | return $this;
29 | }
30 |
31 | public function getServerInfo() : string
32 | {
33 | return $this->database->getConnection()->server_info;
34 | }
35 |
36 | public function getActivities() : array
37 | {
38 | $activities = [];
39 | foreach ($this->getData() as $index => $data) {
40 | $activities[] = [
41 | 'collector' => $this->getName(),
42 | 'class' => static::class,
43 | 'description' => 'Run statement ' . ($index + 1),
44 | 'start' => $data['start'],
45 | 'end' => $data['end'],
46 | ];
47 | }
48 | return $activities;
49 | }
50 |
51 | public function getContents() : string
52 | {
53 | \ob_start();
54 | if (!isset($this->database)) {
55 | echo 'This collector has not been added to a Database instance.
';
56 | return \ob_get_clean(); // @phpstan-ignore-line
57 | }
58 | echo $this->showHeader();
59 | if (!$this->hasData()) {
60 | echo 'Did not run statements.
';
61 | return \ob_get_clean(); // @phpstan-ignore-line
62 | }
63 | $count = \count($this->getData()); ?>
64 | Ran = $count ?> statement= $count === 1 ? '' : 's' ?>
65 | in = $this->getStatementsTime() ?> ms:
66 |
67 |
68 |
69 |
70 | | # |
71 | Time |
72 | Statement |
73 | Rows |
74 |
75 |
76 |
77 | getData() as $index => $item): ?>
78 |
79 | | = $index + 1 ?> |
80 | = Debugger::roundSecondsToMilliseconds($item['end'] - $item['start']) ?> |
81 |
82 | =
83 | \htmlentities($item['statement'])
84 | ?>
85 | |
86 | >= \htmlentities((string) $item['rows']) ?> |
89 |
90 |
91 |
92 |
93 | getData() as $data) {
101 | $total = $data['end'] - $data['start'];
102 | $time += $total;
103 | }
104 | return Debugger::roundSecondsToMilliseconds($time);
105 | }
106 |
107 | protected function showHeader() : string
108 | {
109 | $config = $this->database->getConfig();
110 | \ob_start();
111 | ?>
112 |
113 | Host: = $config['host'] ?? 'localhost' ?>
114 |
115 | getHostInfo(), 'TCP/IP')) {
117 | if (isset($config['port'])) {
118 | ?>
119 | Port: = \htmlentities((string) $config['port']) ?>
120 |
123 | Socket: = \htmlentities($config['socket']) ?>
124 |
127 | Server Info: = \htmlentities($this->getServerInfo()) ?>
128 | database->getConnection()->host_info;
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/src/Manipulation/Replace.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 | namespace Framework\Database\Manipulation;
11 |
12 | use InvalidArgumentException;
13 | use LogicException;
14 |
15 | /**
16 | * Class Replace.
17 | *
18 | * @see https://mariadb.com/kb/en/replace/
19 | *
20 | * @package database
21 | */
22 | class 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 | public const string OPT_DELAYED = 'DELAYED';
32 | /**
33 | * @see https://mariadb.com/kb/en/high_priority-and-low_priority/
34 | */
35 | public const string OPT_LOW_PRIORITY = 'LOW_PRIORITY';
36 |
37 | /**
38 | * @param string $table
39 | *
40 | * @return static
41 | */
42 | public function into(string $table) : static
43 | {
44 | $this->sql['into'] = $table;
45 | return $this;
46 | }
47 |
48 | protected function renderInto() : string
49 | {
50 | if (!isset($this->sql['into'])) {
51 | throw new LogicException('INTO table must be set');
52 | }
53 | return ' INTO ' . $this->renderIdentifier($this->sql['into']);
54 | }
55 |
56 | /**
57 | * @param string $column
58 | * @param string ...$columns
59 | *
60 | * @return static
61 | */
62 | public function columns(string $column, string ...$columns) : static
63 | {
64 | $this->sql['columns'] = [$column, ...$columns];
65 | return $this;
66 | }
67 |
68 | protected function renderColumns() : ?string
69 | {
70 | if (!isset($this->sql['columns'])) {
71 | return null;
72 | }
73 | $columns = [];
74 | foreach ($this->sql['columns'] as $column) {
75 | $columns[] = $this->renderIdentifier($column);
76 | }
77 | $columns = \implode(', ', $columns);
78 | return " ({$columns})";
79 | }
80 |
81 | protected function renderOptions() : ?string
82 | {
83 | if (!$this->hasOptions()) {
84 | return null;
85 | }
86 | $options = $this->sql['options'];
87 | foreach ($options as &$option) {
88 | $input = $option;
89 | $option = \strtoupper($option);
90 | if (!\in_array($option, [
91 | static::OPT_DELAYED,
92 | static::OPT_LOW_PRIORITY,
93 | ], true)) {
94 | throw new InvalidArgumentException("Invalid option: {$input}");
95 | }
96 | }
97 | unset($option);
98 | $intersection = \array_intersect(
99 | $options,
100 | [static::OPT_DELAYED, static::OPT_LOW_PRIORITY]
101 | );
102 | if (\count($intersection) > 1) {
103 | throw new LogicException(
104 | 'Options LOW_PRIORITY and DELAYED can not be used together'
105 | );
106 | }
107 | $options = \implode(' ', $options);
108 | return " {$options}";
109 | }
110 |
111 | protected function checkRowStatementsConflict() : void
112 | {
113 | if (!isset($this->sql['values'])
114 | && !isset($this->sql['select'])
115 | && !$this->hasSet()
116 | ) {
117 | throw new LogicException(
118 | 'The REPLACE INTO must be followed by VALUES, SET or SELECT statement'
119 | );
120 | }
121 | }
122 |
123 | /**
124 | * Renders the REPLACE statement.
125 | *
126 | * @return string
127 | */
128 | public function sql() : string
129 | {
130 | $sql = 'REPLACE' . \PHP_EOL;
131 | $part = $this->renderOptions();
132 | if ($part) {
133 | $sql .= $part . \PHP_EOL;
134 | }
135 | $sql .= $this->renderInto() . \PHP_EOL;
136 | $part = $this->renderColumns();
137 | if ($part) {
138 | $sql .= $part . \PHP_EOL;
139 | }
140 | $this->checkRowStatementsConflict();
141 | $part = $this->renderValues();
142 | if ($part) {
143 | $sql .= $part . \PHP_EOL;
144 | }
145 | $part = $this->renderSetCheckingConflicts();
146 | if ($part) {
147 | $sql .= $part . \PHP_EOL;
148 | }
149 | $part = $this->renderSelect();
150 | if ($part) {
151 | $sql .= $part . \PHP_EOL;
152 | }
153 | return $sql;
154 | }
155 |
156 | /**
157 | * Runs the REPLACE statement.
158 | *
159 | * @return int|string The number of affected rows
160 | */
161 | public function run() : int | string
162 | {
163 | return $this->database->exec($this->sql());
164 | }
165 | }
166 |
--------------------------------------------------------------------------------
/src/Definition/Table/TableDefinition.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 | namespace Framework\Database\Definition\Table;
11 |
12 | use Closure;
13 | use Framework\Database\Database;
14 | use Framework\Database\Definition\Table\Columns\ColumnDefinition;
15 | use Framework\Database\Definition\Table\Indexes\IndexDefinition;
16 |
17 | /**
18 | * Class TableDefinition.
19 | *
20 | * @package database
21 | */
22 | class TableDefinition extends DefinitionPart
23 | {
24 | protected Database $database;
25 | /**
26 | * @var array>
27 | */
28 | protected array $columns = [];
29 | /**
30 | * @var array>
31 | */
32 | protected array $indexes = [];
33 | /**
34 | * @var array
35 | */
36 | protected array $checks = [];
37 | protected ?string $condition = null;
38 |
39 | /**
40 | * TableDefinition constructor.
41 | *
42 | * @param Database $database
43 | * @param string|null $condition
44 | */
45 | public function __construct(Database $database, ?string $condition = null)
46 | {
47 | $this->database = $database;
48 | $this->condition = $condition;
49 | }
50 |
51 | /**
52 | * Adds a column to the Table Definition list.
53 | *
54 | * @param string $name Column name
55 | * @param string|null $changeName New column name. Used on ALTER TABLE CHANGE
56 | *
57 | * @return ColumnDefinition
58 | */
59 | public function column(string $name, ?string $changeName = null) : ColumnDefinition
60 | {
61 | $definition = new ColumnDefinition($this->database);
62 | $this->columns[] = [
63 | 'name' => $name,
64 | 'change_name' => $changeName,
65 | 'definition' => $definition,
66 | ];
67 | return $definition;
68 | }
69 |
70 | /**
71 | * Adds an index to the Table Definition list.
72 | *
73 | * @param string|null $name Index name
74 | *
75 | * @return IndexDefinition
76 | */
77 | public function index(?string $name = null) : IndexDefinition
78 | {
79 | $definition = new IndexDefinition($this->database, $name);
80 | $this->indexes[] = [
81 | 'name' => $name,
82 | 'definition' => $definition,
83 | ];
84 | return $definition;
85 | }
86 |
87 | /**
88 | * Adds a check constraint to the Table Definition list.
89 | *
90 | * @param Closure $expression Must return a string with the check expression.
91 | * The function receives a Database instance in the first parameter.
92 | *
93 | * @return Check
94 | */
95 | public function check(Closure $expression) : Check
96 | {
97 | return $this->checks[] = new Check($this->database, $expression);
98 | }
99 |
100 | protected function renderColumns(?string $prefix = null) : string
101 | {
102 | if ($prefix) {
103 | $prefix .= ' COLUMN';
104 | }
105 | if ($this->condition) {
106 | $prefix .= ' ' . $this->condition;
107 | }
108 | $sql = [];
109 | foreach ($this->columns as $column) {
110 | $name = $this->database->protectIdentifier($column['name']);
111 | $changeName = $column['change_name']
112 | ? ' ' . $this->database->protectIdentifier($column['change_name'])
113 | : null;
114 | $definition = $column['definition']->sql();
115 | $sql[] = " {$prefix} {$name}{$changeName}{$definition}";
116 | }
117 | return \implode(',' . \PHP_EOL, $sql);
118 | }
119 |
120 | protected function renderIndexes(?string $prefix = null) : string
121 | {
122 | $sql = [];
123 | foreach ($this->indexes as $index) {
124 | $definition = $index['definition']->sql();
125 | if ($this->condition) {
126 | $definition = \explode('(', $definition, 2);
127 | $definition = $definition[0] . $this->condition . ' (' . $definition[1];
128 | }
129 | $sql[] = " {$prefix}{$definition}";
130 | }
131 | return \implode(',' . \PHP_EOL, $sql);
132 | }
133 |
134 | protected function renderChecks() : string
135 | {
136 | $sql = [];
137 | foreach ($this->checks as $check) {
138 | $sql[] = ' ' . $check->sql();
139 | }
140 | return \implode(',' . \PHP_EOL, $sql);
141 | }
142 |
143 | protected function sql(?string $prefix = null) : string
144 | {
145 | $sql = $this->renderColumns($prefix);
146 | $part = $this->renderIndexes($prefix);
147 | if ($part) {
148 | $sql .= ',' . \PHP_EOL . $part;
149 | }
150 | $part = $this->renderChecks();
151 | if ($part) {
152 | $sql .= ',' . \PHP_EOL . $part;
153 | }
154 | return $sql;
155 | }
156 | }
157 |
--------------------------------------------------------------------------------
/src/Manipulation/Statement.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 | namespace Framework\Database\Manipulation;
11 |
12 | use Closure;
13 | use InvalidArgumentException;
14 |
15 | /**
16 | * Class Statement.
17 | *
18 | * @see https://mariadb.com/kb/en/data-manipulation/
19 | *
20 | * @package database
21 | */
22 | abstract 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 Closure|array|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(Closure | array | 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 | }
180 |
--------------------------------------------------------------------------------
/src/Definition/Table/Columns/Column.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 | namespace Framework\Database\Definition\Table\Columns;
11 |
12 | use Closure;
13 | use Framework\Database\Database;
14 | use Framework\Database\Definition\Table\DefinitionPart;
15 | use LogicException;
16 |
17 | /**
18 | * Class Column.
19 | *
20 | * @package database
21 | */
22 | abstract class Column extends DefinitionPart
23 | {
24 | protected Database $database;
25 | protected string $type;
26 | /**
27 | * @var array
28 | */
29 | protected array $length;
30 | protected bool $null = false;
31 | protected bool $uniqueKey = false;
32 | protected bool $primaryKey = false;
33 | protected Closure | bool | float | int | string | null $default;
34 | protected Closure $check;
35 | protected ?string $comment;
36 | protected bool $first = false;
37 | protected ?string $after;
38 |
39 | /**
40 | * Column constructor.
41 | *
42 | * @param Database $database
43 | * @param bool|float|int|string|null ...$length
44 | */
45 | public function __construct(Database $database, bool | float | int | string | null ...$length)
46 | {
47 | $this->database = $database;
48 | $this->length = $length;
49 | }
50 |
51 | protected function renderType() : string
52 | {
53 | if (empty($this->type)) {
54 | throw new LogicException('Column type is empty');
55 | }
56 | return ' ' . $this->type;
57 | }
58 |
59 | protected function renderLength() : ?string
60 | {
61 | if (!isset($this->length[0])) {
62 | return null;
63 | }
64 | $length = $this->database->quote($this->length[0]);
65 | return "({$length})";
66 | }
67 |
68 | /**
69 | * @param Closure $expression
70 | *
71 | * @return static
72 | */
73 | public function check(Closure $expression) : static
74 | {
75 | $this->check = $expression;
76 | return $this;
77 | }
78 |
79 | protected function renderCheck() : ?string
80 | {
81 | if (!isset($this->check)) {
82 | return null;
83 | }
84 | return ' CHECK (' . ($this->check)($this->database) . ')';
85 | }
86 |
87 | /**
88 | * @return static
89 | */
90 | public function null() : static
91 | {
92 | $this->null = true;
93 | return $this;
94 | }
95 |
96 | /**
97 | * @return static
98 | */
99 | public function notNull() : static
100 | {
101 | $this->null = false;
102 | return $this;
103 | }
104 |
105 | protected function renderNull() : ?string
106 | {
107 | return $this->null ? ' NULL' : ' NOT NULL';
108 | }
109 |
110 | /**
111 | * @param Closure|bool|float|int|string|null $default
112 | *
113 | * @return static
114 | */
115 | public function default(Closure | bool | float | int | string | null $default) : static
116 | {
117 | $this->default = $default;
118 | return $this;
119 | }
120 |
121 | protected function renderDefault() : ?string
122 | {
123 | if (!isset($this->default)) {
124 | return null;
125 | }
126 | $default = $this->default instanceof Closure
127 | ? '(' . ($this->default)($this->database) . ')'
128 | : $this->database->quote($this->default);
129 | return ' DEFAULT ' . $default;
130 | }
131 |
132 | /**
133 | * @param string $comment
134 | *
135 | * @return static
136 | */
137 | public function comment(string $comment) : static
138 | {
139 | $this->comment = $comment;
140 | return $this;
141 | }
142 |
143 | protected function renderComment() : ?string
144 | {
145 | if (!isset($this->comment)) {
146 | return null;
147 | }
148 | return ' COMMENT ' . $this->database->quote($this->comment);
149 | }
150 |
151 | /**
152 | * @return static
153 | */
154 | public function primaryKey() : static
155 | {
156 | $this->primaryKey = true;
157 | return $this;
158 | }
159 |
160 | protected function renderPrimaryKey() : ?string
161 | {
162 | if (!$this->primaryKey) {
163 | return null;
164 | }
165 | return ' PRIMARY KEY';
166 | }
167 |
168 | /**
169 | * @return static
170 | */
171 | public function uniqueKey() : static
172 | {
173 | $this->uniqueKey = true;
174 | return $this;
175 | }
176 |
177 | protected function renderUniqueKey() : ?string
178 | {
179 | if (!$this->uniqueKey) {
180 | return null;
181 | }
182 | return ' UNIQUE KEY';
183 | }
184 |
185 | /**
186 | * @return static
187 | */
188 | public function first() : static
189 | {
190 | $this->first = true;
191 | return $this;
192 | }
193 |
194 | protected function renderFirst() : ?string
195 | {
196 | if (!$this->first) {
197 | return null;
198 | }
199 | return ' FIRST';
200 | }
201 |
202 | /**
203 | * @param string $column
204 | *
205 | * @return static
206 | */
207 | public function after(string $column) : static
208 | {
209 | $this->after = $column;
210 | return $this;
211 | }
212 |
213 | protected function renderAfter() : ?string
214 | {
215 | if (!isset($this->after)) {
216 | return null;
217 | }
218 | if ($this->first) {
219 | throw new LogicException('Clauses FIRST and AFTER can not be used together');
220 | }
221 | return ' AFTER ' . $this->database->protectIdentifier($this->after);
222 | }
223 |
224 | protected function renderTypeAttributes() : ?string
225 | {
226 | return null;
227 | }
228 |
229 | protected function sql() : string
230 | {
231 | $sql = $this->renderType();
232 | $sql .= $this->renderLength();
233 | $sql .= $this->renderTypeAttributes();
234 | $sql .= $this->renderNull();
235 | $sql .= $this->renderDefault();
236 | $sql .= $this->renderUniqueKey();
237 | $sql .= $this->renderPrimaryKey();
238 | $sql .= $this->renderComment();
239 | $sql .= $this->renderFirst();
240 | $sql .= $this->renderAfter();
241 | $sql .= $this->renderCheck();
242 | return $sql;
243 | }
244 | }
245 |
--------------------------------------------------------------------------------
/src/Result.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 | namespace Framework\Database;
11 |
12 | use Framework\Database\Result\Field;
13 | use LogicException;
14 | use mysqli_result;
15 | use OutOfBoundsException;
16 | use OutOfRangeException;
17 |
18 | /**
19 | * Class Result.
20 | *
21 | * @package database
22 | */
23 | class Result
24 | {
25 | /**
26 | * @var mysqli_result
27 | */
28 | protected mysqli_result $result;
29 | protected bool $buffered;
30 | protected bool $free = false;
31 | protected string $fetchClass = \stdClass::class;
32 | /**
33 | * @var array
34 | */
35 | protected array $fetchConstructor = [];
36 |
37 | /**
38 | * Result constructor.
39 | *
40 | * @param mysqli_result $result
41 | * @param bool $buffered
42 | */
43 | public function __construct(mysqli_result $result, bool $buffered)
44 | {
45 | $this->result = $result;
46 | $this->buffered = $buffered;
47 | }
48 |
49 | public function __destruct()
50 | {
51 | if (!$this->isFree()) {
52 | $this->free();
53 | }
54 | }
55 |
56 | /**
57 | * Frees the memory associated with a result.
58 | */
59 | public function free() : void
60 | {
61 | $this->checkIsFree();
62 | $this->free = true;
63 | $this->result->free();
64 | }
65 |
66 | public function isFree() : bool
67 | {
68 | return $this->free;
69 | }
70 |
71 | protected function checkIsFree() : void
72 | {
73 | if ($this->isFree()) {
74 | throw new LogicException('Result is already free');
75 | }
76 | }
77 |
78 | public function isBuffered() : bool
79 | {
80 | return $this->buffered;
81 | }
82 |
83 | /**
84 | * Adjusts the result pointer to an arbitrary row in the result.
85 | *
86 | * @param int $offset The field offset. Must be between zero and the total
87 | * number of rows minus one
88 | *
89 | * @throws LogicException if is an unbuffered result
90 | * @throws OutOfBoundsException for invalid cursor offset
91 | *
92 | * @return bool
93 | */
94 | public function moveCursor(int $offset) : bool
95 | {
96 | $this->checkIsFree();
97 | if (!$this->isBuffered()) {
98 | throw new LogicException('Cursor cannot be moved on unbuffered results');
99 | }
100 | if ($offset < 0 || ($offset !== 0 && $offset >= $this->result->num_rows)) {
101 | throw new OutOfRangeException(
102 | "Invalid cursor offset: {$offset}"
103 | );
104 | }
105 | return $this->result->data_seek($offset);
106 | }
107 |
108 | /**
109 | * @param string $class
110 | * @param mixed ...$constructor
111 | *
112 | * @return static
113 | */
114 | public function setFetchClass(string $class, mixed ...$constructor) : static
115 | {
116 | $this->fetchClass = $class;
117 | $this->fetchConstructor = $constructor;
118 | return $this;
119 | }
120 |
121 | /**
122 | * Fetches the current row as object and move the cursor to the next.
123 | *
124 | * @param string|null $class
125 | * @param mixed ...$constructor
126 | *
127 | * @return object|null
128 | */
129 | public function fetch(?string $class = null, mixed ...$constructor) : ?object
130 | {
131 | $this->checkIsFree();
132 | $class ??= $this->fetchClass;
133 | $constructor = $constructor ?: $this->fetchConstructor;
134 | if ($constructor) {
135 | // @phpstan-ignore-next-line
136 | return $this->result->fetch_object($class, $constructor);
137 | }
138 | // @phpstan-ignore-next-line
139 | return $this->result->fetch_object($class);
140 | }
141 |
142 | /**
143 | * Fetches all rows as objects.
144 | *
145 | * @param string|null $class
146 | * @param mixed ...$constructor
147 | *
148 | * @return array
149 | */
150 | public function fetchAll(?string $class = null, mixed ...$constructor) : array
151 | {
152 | $this->checkIsFree();
153 | $all = [];
154 | while ($row = $this->fetch($class, ...$constructor)) {
155 | $all[] = $row;
156 | }
157 | return $all;
158 | }
159 |
160 | /**
161 | * Fetches a specific row as object and move the cursor to the next.
162 | *
163 | * @param int $offset
164 | * @param string|null $class
165 | * @param mixed ...$constructor
166 | *
167 | * @return object
168 | */
169 | public function fetchRow(int $offset, ?string $class = null, mixed ...$constructor) : object
170 | {
171 | $this->checkIsFree();
172 | $this->moveCursor($offset);
173 | return $this->fetch($class, ...$constructor);
174 | }
175 |
176 | /**
177 | * Fetches the current row as array and move the cursor to the next.
178 | *
179 | * @return array|null
180 | */
181 | public function fetchArray() : ?array
182 | {
183 | $this->checkIsFree();
184 | return $this->result->fetch_assoc(); // @phpstan-ignore-line
185 | }
186 |
187 | /**
188 | * Fetches all rows as arrays.
189 | *
190 | * @return array>
191 | */
192 | public function fetchArrayAll() : array
193 | {
194 | $this->checkIsFree();
195 | return $this->result->fetch_all(\MYSQLI_ASSOC);
196 | }
197 |
198 | /**
199 | * Fetches a specific row as array and move the cursor to the next.
200 | *
201 | * @param int $offset
202 | *
203 | * @return array
204 | */
205 | public function fetchArrayRow(int $offset) : array
206 | {
207 | $this->checkIsFree();
208 | $this->moveCursor($offset);
209 | return $this->result->fetch_assoc(); // @phpstan-ignore-line
210 | }
211 |
212 | /**
213 | * Gets the number of rows in the result set.
214 | *
215 | * @return int|string
216 | */
217 | public function numRows() : int | string
218 | {
219 | $this->checkIsFree();
220 | return $this->result->num_rows;
221 | }
222 |
223 | /**
224 | * Returns an array of objects representing the fields in a result set.
225 | *
226 | * @return array an array of objects which contains field
227 | * definition information
228 | */
229 | public function fetchFields() : array
230 | {
231 | $this->checkIsFree();
232 | $fields = [];
233 | foreach ($this->result->fetch_fields() as $field) {
234 | $fields[] = new Field($field);
235 | }
236 | return $fields;
237 | }
238 | }
239 |
--------------------------------------------------------------------------------
/src/Manipulation/Insert.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 | namespace Framework\Database\Manipulation;
11 |
12 | use Closure;
13 | use InvalidArgumentException;
14 | use LogicException;
15 |
16 | /**
17 | * Class Insert.
18 | *
19 | * @see https://mariadb.com/kb/en/insert/
20 | *
21 | * @package database
22 | */
23 | class Insert extends Statement
24 | {
25 | use Traits\Select;
26 | use Traits\Set;
27 | use Traits\Values;
28 |
29 | /**
30 | * @see https://mariadb.com/kb/en/insert-delayed/
31 | */
32 | public const string OPT_DELAYED = 'DELAYED';
33 | /**
34 | * Convert errors to warnings, which will not stop inserts of additional rows.
35 | *
36 | * @see https://mariadb.com/kb/en/insert-ignore/
37 | */
38 | public const string OPT_IGNORE = 'IGNORE';
39 | /**
40 | * @see https://mariadb.com/kb/en/high_priority-and-low_priority/
41 | */
42 | public const string OPT_HIGH_PRIORITY = 'HIGH_PRIORITY';
43 | /**
44 | * @see https://mariadb.com/kb/en/high_priority-and-low_priority/
45 | */
46 | public const string OPT_LOW_PRIORITY = 'LOW_PRIORITY';
47 |
48 | protected function renderOptions() : ?string
49 | {
50 | if (!$this->hasOptions()) {
51 | return null;
52 | }
53 | $options = $this->sql['options'];
54 | foreach ($options as &$option) {
55 | $input = $option;
56 | $option = \strtoupper($option);
57 | if (!\in_array($option, [
58 | static::OPT_DELAYED,
59 | static::OPT_IGNORE,
60 | static::OPT_LOW_PRIORITY,
61 | static::OPT_HIGH_PRIORITY,
62 | ], true)) {
63 | throw new InvalidArgumentException("Invalid option: {$input}");
64 | }
65 | }
66 | unset($option);
67 | $intersection = \array_intersect(
68 | $options,
69 | [static::OPT_DELAYED, static::OPT_HIGH_PRIORITY, static::OPT_LOW_PRIORITY]
70 | );
71 | if (\count($intersection) > 1) {
72 | throw new LogicException(
73 | 'Options LOW_PRIORITY, DELAYED or HIGH_PRIORITY can not be used together'
74 | );
75 | }
76 | $options = \implode(' ', $options);
77 | return " {$options}";
78 | }
79 |
80 | /**
81 | * Sets the INTO table.
82 | *
83 | * @param string $table Table name
84 | *
85 | * @return static
86 | */
87 | public function into(string $table) : static
88 | {
89 | $this->sql['into'] = $table;
90 | return $this;
91 | }
92 |
93 | /**
94 | * Renders the "INTO $table" clause.
95 | *
96 | * @throws LogicException if INTO was not set
97 | *
98 | * @return string
99 | */
100 | protected function renderInto() : string
101 | {
102 | if (!isset($this->sql['into'])) {
103 | throw new LogicException('INTO table must be set');
104 | }
105 | return ' INTO ' . $this->renderIdentifier($this->sql['into']);
106 | }
107 |
108 | /**
109 | * Sets the INTO columns.
110 | *
111 | * @param string $column Column name
112 | * @param string ...$columns Extra column names
113 | *
114 | * @return static
115 | */
116 | public function columns(string $column, string ...$columns) : static
117 | {
118 | $this->sql['columns'] = [$column, ...$columns];
119 | return $this;
120 | }
121 |
122 | /**
123 | * Renders the INTO $table "(...$columns)" part.
124 | *
125 | * @return string|null The imploded columns or null if none was set
126 | */
127 | protected function renderColumns() : ?string
128 | {
129 | if (!isset($this->sql['columns'])) {
130 | return null;
131 | }
132 | $columns = [];
133 | foreach ($this->sql['columns'] as $column) {
134 | $columns[] = $this->renderIdentifier($column);
135 | }
136 | $columns = \implode(', ', $columns);
137 | return " ({$columns})";
138 | }
139 |
140 | /**
141 | * Sets the ON DUPLICATE KEY UPDATE part.
142 | *
143 | * @param array|object $columns Column name
144 | * as key/property, column value/expression as value
145 | *
146 | * @see https://mariadb.com/kb/en/insert-on-duplicate-key-update/
147 | *
148 | * @return static
149 | */
150 | public function onDuplicateKeyUpdate(array | object $columns) : static
151 | {
152 | $this->sql['on_duplicate'] = (array) $columns;
153 | return $this;
154 | }
155 |
156 | /**
157 | * Renders the ON DUPLICATE KEY UPDATE part.
158 | *
159 | * @return string|null The part or null if it was not set
160 | */
161 | protected function renderOnDuplicateKeyUpdate() : ?string
162 | {
163 | if (!isset($this->sql['on_duplicate'])) {
164 | return null;
165 | }
166 | $onDuplicate = [];
167 | foreach ($this->sql['on_duplicate'] as $column => $value) {
168 | $onDuplicate[] = $this->renderAssignment($column, $value);
169 | }
170 | $onDuplicate = \implode(', ', $onDuplicate);
171 | return " ON DUPLICATE KEY UPDATE {$onDuplicate}";
172 | }
173 |
174 | /**
175 | * Check for conflicts in the INSERT statement.
176 | *
177 | * @throws LogicException if it has conflicts
178 | */
179 | protected function checkRowStatementsConflict() : void
180 | {
181 | if (!isset($this->sql['values'])
182 | && !isset($this->sql['select'])
183 | && !$this->hasSet()
184 | ) {
185 | throw new LogicException(
186 | 'The INSERT INTO must be followed by VALUES, SET or SELECT statement'
187 | );
188 | }
189 | }
190 |
191 | /**
192 | * Renders the INSERT statement.
193 | *
194 | * @return string
195 | */
196 | public function sql() : string
197 | {
198 | $sql = 'INSERT' . \PHP_EOL;
199 | $part = $this->renderOptions();
200 | if ($part) {
201 | $sql .= $part . \PHP_EOL;
202 | }
203 | $sql .= $this->renderInto() . \PHP_EOL;
204 | $part = $this->renderColumns();
205 | if ($part) {
206 | $sql .= $part . \PHP_EOL;
207 | }
208 | $this->checkRowStatementsConflict();
209 | $part = $this->renderValues();
210 | if ($part) {
211 | $sql .= $part . \PHP_EOL;
212 | }
213 | $part = $this->renderSetCheckingConflicts();
214 | if ($part) {
215 | $sql .= $part . \PHP_EOL;
216 | }
217 | $part = $this->renderSelect();
218 | if ($part) {
219 | $sql .= $part . \PHP_EOL;
220 | }
221 | $part = $this->renderOnDuplicateKeyUpdate();
222 | if ($part) {
223 | $sql .= $part . \PHP_EOL;
224 | }
225 | return $sql;
226 | }
227 |
228 | /**
229 | * Runs the INSERT statement.
230 | *
231 | * @return int|string The number of affected rows
232 | */
233 | public function run() : int | string
234 | {
235 | return $this->database->exec($this->sql());
236 | }
237 | }
238 |
--------------------------------------------------------------------------------
/src/Result/Field.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 | namespace Framework\Database\Result;
11 |
12 | /**
13 | * Class Field.
14 | *
15 | * @package database
16 | */
17 | readonly class Field
18 | {
19 | /**
20 | * The name of the column.
21 | */
22 | public string $name;
23 | /**
24 | * Original column name if an alias was specified.
25 | */
26 | public string $orgname;
27 | /**
28 | * The name of the table this field belongs to (if not calculated).
29 | */
30 | public string $table;
31 | /**
32 | * Original table name if an alias was specified.
33 | */
34 | public string $orgtable;
35 | /**
36 | * Reserved for default value, currently always "".
37 | */
38 | public string $def;
39 | /**
40 | * Database (since PHP 5.3.6).
41 | */
42 | public string $db;
43 | /**
44 | * The catalog name, always "def" (since PHP 5.3.6).
45 | */
46 | public string $catalog;
47 | /**
48 | * The maximum width of the field for the result set. As of PHP 8.1, this
49 | * value is always 0.
50 | */
51 | public int $maxLength;
52 | /**
53 | * The width of the field, in bytes, as specified in the table definition.
54 | * Note that this number (bytes) might differ from your table definition
55 | * value (characters), depending on the character set you use. For example,
56 | * the character set utf8 has 3 bytes per character, so varchar(10) will
57 | * return a length of 30 for utf8 (10*3), but return 10 for latin1 (10*1).
58 | */
59 | public int $length;
60 | /**
61 | * The character set number (id) for the field.
62 | */
63 | public int $charsetnr;
64 | /**
65 | * An integer representing the bit-flags for the field.
66 | */
67 | public int $flags;
68 | /**
69 | * The data type used for this field.
70 | */
71 | public int $type;
72 | /**
73 | * The number of decimals used (for integer fields).
74 | */
75 | public int $decimals;
76 | /**
77 | * The name of the data type used for this field.
78 | */
79 | public ?string $typeName;
80 | /**
81 | * Tells if field is defined as BINARY.
82 | */
83 | public bool $isBinary;
84 | /**
85 | * Tells if field is defined as BLOB.
86 | */
87 | public bool $isBlob;
88 | /**
89 | * Tells if field is defined as ENUM.
90 | */
91 | public bool $isEnum;
92 | /**
93 | * Tells if field is part of GROUP BY.
94 | */
95 | public bool $isGroup;
96 | /**
97 | * Tells if field is defined as NUMERIC.
98 | */
99 | public bool $isNum;
100 | /**
101 | * Tells if field is defined as SET.
102 | */
103 | public bool $isSet;
104 | /**
105 | * Tells if field is defined as TIMESTAMP.
106 | */
107 | public bool $isTimestamp;
108 | /**
109 | * Tells if field is defined as UNSIGNED.
110 | */
111 | public bool $isUnsigned;
112 | /**
113 | * Tells if field is defined as ZEROFILL.
114 | */
115 | public bool $isZerofill;
116 | /**
117 | * Tells if field is defined as AUTO_INCREMENT.
118 | */
119 | public bool $isAutoIncrement;
120 | /**
121 | * Tells if field is part of an index.
122 | */
123 | public bool $isMultipleKey;
124 | /**
125 | * Tells if field is defined as NOT NULL.
126 | */
127 | public bool $isNotNull;
128 | /**
129 | * Tells if field is part of a multi-index.
130 | */
131 | public bool $isPartKey;
132 | /**
133 | * Tells if field is part of a primary index.
134 | */
135 | public bool $isPriKey;
136 | /**
137 | * Tells if field is part of a unique index.
138 | */
139 | public bool $isUniqueKey;
140 | public bool $isNoDefaultValue;
141 | public bool $isOnUpdateNow;
142 |
143 | public function __construct(\stdClass $field)
144 | {
145 | foreach ((array) $field as $key => $value) {
146 | $key = \ucwords($key, '_');
147 | $key = \strtr($key, ['_' => '']);
148 | $key[0] = \strtolower($key[0]);
149 | $this->{$key} = $value;
150 | }
151 | $this->setTypeName();
152 | $this->setFlags();
153 | }
154 |
155 | protected function setTypeName() : void
156 | {
157 | $this->typeName = match ($this->type) {
158 | \MYSQLI_TYPE_BIT => 'bit',
159 | \MYSQLI_TYPE_BLOB => 'blob',
160 | \MYSQLI_TYPE_CHAR => 'char',
161 | \MYSQLI_TYPE_DATE => 'date',
162 | \MYSQLI_TYPE_DATETIME => 'datetime',
163 | \MYSQLI_TYPE_DECIMAL => 'decimal',
164 | \MYSQLI_TYPE_DOUBLE => 'double',
165 | \MYSQLI_TYPE_ENUM => 'enum',
166 | \MYSQLI_TYPE_FLOAT => 'float',
167 | \MYSQLI_TYPE_GEOMETRY => 'geometry',
168 | \MYSQLI_TYPE_INT24 => 'int24',
169 | //\MYSQLI_TYPE_INTERVAL => 'interval',
170 | \MYSQLI_TYPE_JSON => 'json',
171 | \MYSQLI_TYPE_LONG => 'long',
172 | \MYSQLI_TYPE_LONG_BLOB => 'long_blob',
173 | \MYSQLI_TYPE_LONGLONG => 'longlong',
174 | \MYSQLI_TYPE_MEDIUM_BLOB => 'medium_blob',
175 | \MYSQLI_TYPE_NEWDATE => 'newdate',
176 | \MYSQLI_TYPE_NEWDECIMAL => 'newdecimal',
177 | \MYSQLI_TYPE_NULL => 'null',
178 | \MYSQLI_TYPE_SET => 'set',
179 | \MYSQLI_TYPE_SHORT => 'short',
180 | \MYSQLI_TYPE_STRING => 'string',
181 | \MYSQLI_TYPE_TIME => 'time',
182 | \MYSQLI_TYPE_TIMESTAMP => 'timestamp',
183 | //\MYSQLI_TYPE_TINY => 'tiny',
184 | \MYSQLI_TYPE_TINY_BLOB => 'tiny_blob',
185 | \MYSQLI_TYPE_VAR_STRING => 'var_string',
186 | \MYSQLI_TYPE_YEAR => 'year',
187 | default => null
188 | };
189 | }
190 |
191 | protected function setFlags() : void
192 | {
193 | $this->isBinary = (bool) ($this->flags & \MYSQLI_BINARY_FLAG);
194 | $this->isBlob = (bool) ($this->flags & \MYSQLI_BLOB_FLAG);
195 | $this->isEnum = (bool) ($this->flags & \MYSQLI_ENUM_FLAG);
196 | $this->isGroup = (bool) ($this->flags & \MYSQLI_GROUP_FLAG);
197 | $this->isNum = (bool) ($this->flags & \MYSQLI_NUM_FLAG);
198 | $this->isSet = (bool) ($this->flags & \MYSQLI_SET_FLAG);
199 | $this->isTimestamp = (bool) ($this->flags & \MYSQLI_TIMESTAMP_FLAG);
200 | $this->isUnsigned = (bool) ($this->flags & \MYSQLI_UNSIGNED_FLAG);
201 | $this->isZerofill = (bool) ($this->flags & \MYSQLI_ZEROFILL_FLAG);
202 | $this->isAutoIncrement = (bool) ($this->flags & \MYSQLI_AUTO_INCREMENT_FLAG);
203 | $this->isMultipleKey = (bool) ($this->flags & \MYSQLI_MULTIPLE_KEY_FLAG);
204 | $this->isNotNull = (bool) ($this->flags & \MYSQLI_NOT_NULL_FLAG);
205 | $this->isPartKey = (bool) ($this->flags & \MYSQLI_PART_KEY_FLAG);
206 | $this->isPriKey = (bool) ($this->flags & \MYSQLI_PRI_KEY_FLAG);
207 | $this->isUniqueKey = (bool) ($this->flags & \MYSQLI_UNIQUE_KEY_FLAG);
208 | $this->isNoDefaultValue = (bool) ($this->flags & \MYSQLI_NO_DEFAULT_VALUE_FLAG);
209 | $this->isOnUpdateNow = (bool) ($this->flags & \MYSQLI_ON_UPDATE_NOW_FLAG);
210 | }
211 | }
212 |
--------------------------------------------------------------------------------
/src/Manipulation/LoadData.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 | namespace Framework\Database\Manipulation;
11 |
12 | use InvalidArgumentException;
13 | use LogicException;
14 |
15 | /**
16 | * Class LoadData.
17 | *
18 | * @see https://mariadb.com/kb/en/load-data-infile/
19 | *
20 | * @package database
21 | */
22 | class LoadData extends Statement
23 | {
24 | use Traits\Set;
25 |
26 | /**
27 | * @see https://mariadb.com/kb/en/high_priority-and-low_priority/
28 | */
29 | public const string OPT_LOW_PRIORITY = 'LOW_PRIORITY';
30 | /**
31 | * @see https://mariadb.com/kb/en/load-data-infile/#priority-and-concurrency
32 | */
33 | public const string OPT_CONCURRENT = 'CONCURRENT';
34 | /**
35 | * @see https://mariadb.com/kb/en/load-data-infile/#load-data-local-infile
36 | */
37 | public const string OPT_LOCAL = 'LOCAL';
38 |
39 | protected function renderOptions() : ?string
40 | {
41 | if (!$this->hasOptions()) {
42 | return null;
43 | }
44 | $options = $this->sql['options'];
45 | foreach ($options as &$option) {
46 | $input = $option;
47 | $option = \strtoupper($option);
48 | if (!\in_array($option, [
49 | static::OPT_LOW_PRIORITY,
50 | static::OPT_CONCURRENT,
51 | static::OPT_LOCAL,
52 | ], true)) {
53 | throw new InvalidArgumentException("Invalid option: {$input}");
54 | }
55 | }
56 | unset($option);
57 | $intersection = \array_intersect(
58 | $options,
59 | [static::OPT_LOW_PRIORITY, static::OPT_CONCURRENT]
60 | );
61 | if (\count($intersection) > 1) {
62 | throw new LogicException('Options LOW_PRIORITY and CONCURRENT can not be used together');
63 | }
64 | return \implode(' ', $options);
65 | }
66 |
67 | /**
68 | * @param string $filename
69 | *
70 | * @return static
71 | */
72 | public function infile(string $filename) : static
73 | {
74 | $this->sql['infile'] = $filename;
75 | return $this;
76 | }
77 |
78 | protected function renderInfile() : string
79 | {
80 | if (empty($this->sql['infile'])) {
81 | throw new LogicException('INFILE statement is required');
82 | }
83 | $filename = $this->database->quote($this->sql['infile']);
84 | return " INFILE {$filename}";
85 | }
86 |
87 | /**
88 | * @param string $table
89 | *
90 | * @return static
91 | */
92 | public function intoTable(string $table) : static
93 | {
94 | $this->sql['table'] = $table;
95 | return $this;
96 | }
97 |
98 | protected function renderIntoTable() : string
99 | {
100 | if (empty($this->sql['table'])) {
101 | throw new LogicException('Table is required');
102 | }
103 | return ' INTO TABLE ' . $this->database->protectIdentifier($this->sql['table']);
104 | }
105 |
106 | /**
107 | * @param string $charset
108 | *
109 | * @see https://mariadb.com/kb/en/supported-character-sets-and-collations/
110 | *
111 | * @return static
112 | */
113 | public function charset(string $charset) : static
114 | {
115 | $this->sql['charset'] = $charset;
116 | return $this;
117 | }
118 |
119 | protected function renderCharset() : ?string
120 | {
121 | if (!isset($this->sql['charset'])) {
122 | return null;
123 | }
124 | return " CHARACTER SET {$this->sql['charset']}";
125 | }
126 |
127 | /**
128 | * @param string $str
129 | *
130 | * @return static
131 | */
132 | public function columnsTerminatedBy(string $str) : static
133 | {
134 | $this->sql['columns_terminated_by'] = $this->database->quote($str);
135 | return $this;
136 | }
137 |
138 | /**
139 | * @param string $char
140 | * @param bool $optionally
141 | *
142 | * @return static
143 | */
144 | public function columnsEnclosedBy(string $char, bool $optionally = false) : static
145 | {
146 | $this->sql['columns_enclosed_by'] = $this->database->quote($char);
147 | $this->sql['columns_enclosed_by_opt'] = $optionally;
148 | return $this;
149 | }
150 |
151 | /**
152 | * @param string $char
153 | *
154 | * @return static
155 | */
156 | public function columnsEscapedBy(string $char) : static
157 | {
158 | $this->sql['columns_escaped_by'] = $this->database->quote($char);
159 | return $this;
160 | }
161 |
162 | protected function renderColumns() : ?string
163 | {
164 | if (!isset($this->sql['columns_terminated_by'])
165 | && !isset($this->sql['columns_enclosed_by'])
166 | && !isset($this->sql['columns_escaped_by'])) {
167 | return null;
168 | }
169 | $part = ' COLUMNS' . \PHP_EOL;
170 | if (isset($this->sql['columns_terminated_by'])) {
171 | $part .= ' TERMINATED BY ' . $this->sql['columns_terminated_by'] . \PHP_EOL;
172 | }
173 | if (isset($this->sql['columns_enclosed_by'])) {
174 | if (isset($this->sql['columns_enclosed_by_opt'])) {
175 | $part .= ' OPTIONALLY';
176 | }
177 | $part .= ' ENCLOSED BY ' . $this->sql['columns_enclosed_by'] . \PHP_EOL;
178 | }
179 | if (isset($this->sql['columns_escaped_by'])) {
180 | $part .= ' ESCAPED BY ' . $this->sql['columns_escaped_by'] . \PHP_EOL;
181 | }
182 | return $part;
183 | }
184 |
185 | /**
186 | * @param string $str
187 | *
188 | * @return static
189 | */
190 | public function linesStartingBy(string $str) : static
191 | {
192 | $this->sql['lines_starting_by'] = $this->database->quote($str);
193 | return $this;
194 | }
195 |
196 | /**
197 | * @param string $str
198 | *
199 | * @return static
200 | */
201 | public function linesTerminatedBy(string $str) : static
202 | {
203 | $this->sql['lines_terminated_by'] = $this->database->quote($str);
204 | return $this;
205 | }
206 |
207 | protected function renderLines() : ?string
208 | {
209 | if (!isset($this->sql['lines_starting_by'])
210 | && !isset($this->sql['lines_terminated_by'])) {
211 | return null;
212 | }
213 | $part = ' LINES' . \PHP_EOL;
214 | if (isset($this->sql['lines_starting_by'])) {
215 | $part .= ' STARTING BY ' . $this->sql['lines_starting_by'] . \PHP_EOL;
216 | }
217 | if (isset($this->sql['lines_terminated_by'])) {
218 | $part .= ' TERMINATED BY ' . $this->sql['lines_terminated_by'] . \PHP_EOL;
219 | }
220 | return $part;
221 | }
222 |
223 | /**
224 | * @param int $number
225 | *
226 | * @return static
227 | */
228 | public function ignoreLines(int $number) : static
229 | {
230 | $this->sql['ignore_lines'] = $number;
231 | return $this;
232 | }
233 |
234 | protected function renderIgnoreLines() : ?string
235 | {
236 | if (!isset($this->sql['ignore_lines'])) {
237 | return null;
238 | }
239 | return " IGNORE {$this->sql['ignore_lines']} LINES";
240 | }
241 |
242 | /**
243 | * @return string
244 | */
245 | public function sql() : string
246 | {
247 | $sql = 'LOAD DATA' . \PHP_EOL;
248 | $part = $this->renderOptions();
249 | if ($part) {
250 | $sql .= $part . \PHP_EOL;
251 | }
252 | $sql .= $this->renderInfile() . \PHP_EOL;
253 | $sql .= $this->renderIntoTable() . \PHP_EOL;
254 | $part = $this->renderCharset();
255 | if ($part) {
256 | $sql .= $part . \PHP_EOL;
257 | }
258 | $sql .= $this->renderColumns();
259 | $sql .= $this->renderLines();
260 | $part = $this->renderIgnoreLines();
261 | if ($part) {
262 | $sql .= $part . \PHP_EOL;
263 | }
264 | return $sql;
265 | }
266 |
267 | public function run() : int | string
268 | {
269 | return $this->database->exec($this->sql());
270 | }
271 | }
272 |
--------------------------------------------------------------------------------
/src/Definition/Table/Columns/ColumnDefinition.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 | namespace Framework\Database\Definition\Table\Columns;
11 |
12 | use Framework\Database\Database;
13 | use Framework\Database\Definition\Table\Columns\DateTime\DateColumn;
14 | use Framework\Database\Definition\Table\Columns\DateTime\DatetimeColumn;
15 | use Framework\Database\Definition\Table\Columns\DateTime\TimeColumn;
16 | use Framework\Database\Definition\Table\Columns\DateTime\TimestampColumn;
17 | use Framework\Database\Definition\Table\Columns\DateTime\YearColumn;
18 | use Framework\Database\Definition\Table\Columns\Geometry\GeometryCollectionColumn;
19 | use Framework\Database\Definition\Table\Columns\Geometry\GeometryColumn;
20 | use Framework\Database\Definition\Table\Columns\Geometry\LinestringColumn;
21 | use Framework\Database\Definition\Table\Columns\Geometry\MultilinestringColumn;
22 | use Framework\Database\Definition\Table\Columns\Geometry\MultipointColumn;
23 | use Framework\Database\Definition\Table\Columns\Geometry\MultipolygonColumn;
24 | use Framework\Database\Definition\Table\Columns\Geometry\PointColumn;
25 | use Framework\Database\Definition\Table\Columns\Geometry\PolygonColumn;
26 | use Framework\Database\Definition\Table\Columns\Numeric\BigintColumn;
27 | use Framework\Database\Definition\Table\Columns\Numeric\BitColumn;
28 | use Framework\Database\Definition\Table\Columns\Numeric\BooleanColumn;
29 | use Framework\Database\Definition\Table\Columns\Numeric\DecimalColumn;
30 | use Framework\Database\Definition\Table\Columns\Numeric\FloatColumn;
31 | use Framework\Database\Definition\Table\Columns\Numeric\IntColumn;
32 | use Framework\Database\Definition\Table\Columns\Numeric\MediumintColumn;
33 | use Framework\Database\Definition\Table\Columns\Numeric\SmallintColumn;
34 | use Framework\Database\Definition\Table\Columns\Numeric\TinyintColumn;
35 | use Framework\Database\Definition\Table\Columns\String\BinaryColumn;
36 | use Framework\Database\Definition\Table\Columns\String\BlobColumn;
37 | use Framework\Database\Definition\Table\Columns\String\CharColumn;
38 | use Framework\Database\Definition\Table\Columns\String\EnumColumn;
39 | use Framework\Database\Definition\Table\Columns\String\JsonColumn;
40 | use Framework\Database\Definition\Table\Columns\String\LongblobColumn;
41 | use Framework\Database\Definition\Table\Columns\String\LongtextColumn;
42 | use Framework\Database\Definition\Table\Columns\String\MediumblobColumn;
43 | use Framework\Database\Definition\Table\Columns\String\MediumtextColumn;
44 | use Framework\Database\Definition\Table\Columns\String\SetColumn;
45 | use Framework\Database\Definition\Table\Columns\String\TextColumn;
46 | use Framework\Database\Definition\Table\Columns\String\TinyblobColumn;
47 | use Framework\Database\Definition\Table\Columns\String\TinytextColumn;
48 | use Framework\Database\Definition\Table\Columns\String\VarbinaryColumn;
49 | use Framework\Database\Definition\Table\Columns\String\VarcharColumn;
50 | use Framework\Database\Definition\Table\DefinitionPart;
51 |
52 | /**
53 | * Class ColumnDefinition.
54 | *
55 | * @see https://mariadb.com/kb/en/create-table/#index-definitions
56 | *
57 | * @package database
58 | */
59 | class ColumnDefinition extends DefinitionPart
60 | {
61 | protected Database $database;
62 | protected Column $column;
63 |
64 | public function __construct(Database $database)
65 | {
66 | $this->database = $database;
67 | }
68 |
69 | public function int(?int $maximum = null) : IntColumn
70 | {
71 | return $this->column = new IntColumn($this->database, $maximum);
72 | }
73 |
74 | public function bigint(?int $maximum = null) : BigintColumn
75 | {
76 | return $this->column = new BigintColumn($this->database, $maximum);
77 | }
78 |
79 | public function tinyint(?int $maximum = null) : TinyintColumn
80 | {
81 | return $this->column = new TinyintColumn($this->database, $maximum);
82 | }
83 |
84 | public function decimal(?int $maximum = null, ?int $decimals = null) : DecimalColumn
85 | {
86 | return $this->column = new DecimalColumn($this->database, $maximum, $decimals);
87 | }
88 |
89 | public function float(?int $maximum = null, ?int $decimals = null) : FloatColumn
90 | {
91 | return $this->column = new FloatColumn($this->database, $maximum, $decimals);
92 | }
93 |
94 | public function mediumint(?int $maximum = null) : MediumintColumn
95 | {
96 | return $this->column = new MediumintColumn($this->database, $maximum);
97 | }
98 |
99 | public function smallint(?int $maximum = null) : SmallintColumn
100 | {
101 | return $this->column = new SmallintColumn($this->database, $maximum);
102 | }
103 |
104 | public function boolean(?int $maximum = null) : BooleanColumn
105 | {
106 | return $this->column = new BooleanColumn($this->database, $maximum);
107 | }
108 |
109 | public function varchar(?int $maximum = null) : VarcharColumn
110 | {
111 | return $this->column = new VarcharColumn($this->database, $maximum);
112 | }
113 |
114 | public function char(?int $maximum = null) : CharColumn
115 | {
116 | return $this->column = new CharColumn($this->database, $maximum);
117 | }
118 |
119 | public function enum(string $value, string ...$values) : EnumColumn
120 | {
121 | return $this->column = new EnumColumn($this->database, $value, ...$values);
122 | }
123 |
124 | public function set(string $value, string ...$values) : SetColumn
125 | {
126 | return $this->column = new SetColumn($this->database, $value, ...$values);
127 | }
128 |
129 | public function text(?int $maximum = null) : TextColumn
130 | {
131 | return $this->column = new TextColumn($this->database, $maximum);
132 | }
133 |
134 | public function longtext() : LongtextColumn
135 | {
136 | return $this->column = new LongtextColumn($this->database);
137 | }
138 |
139 | public function mediumtext() : MediumtextColumn
140 | {
141 | return $this->column = new MediumtextColumn($this->database);
142 | }
143 |
144 | public function tinytext() : TinytextColumn
145 | {
146 | return $this->column = new TinytextColumn($this->database);
147 | }
148 |
149 | public function json() : JsonColumn
150 | {
151 | return $this->column = new JsonColumn($this->database);
152 | }
153 |
154 | public function blob() : BlobColumn
155 | {
156 | return $this->column = new BlobColumn($this->database);
157 | }
158 |
159 | public function tinyblob() : TinyblobColumn
160 | {
161 | return $this->column = new TinyblobColumn($this->database);
162 | }
163 |
164 | public function mediumblob() : MediumblobColumn
165 | {
166 | return $this->column = new MediumblobColumn($this->database);
167 | }
168 |
169 | public function longblob() : LongblobColumn
170 | {
171 | return $this->column = new LongblobColumn($this->database);
172 | }
173 |
174 | public function bit() : BitColumn
175 | {
176 | return $this->column = new BitColumn($this->database);
177 | }
178 |
179 | public function binary() : BinaryColumn
180 | {
181 | return $this->column = new BinaryColumn($this->database);
182 | }
183 |
184 | public function varbinary() : VarbinaryColumn
185 | {
186 | return $this->column = new VarbinaryColumn($this->database);
187 | }
188 |
189 | public function date() : DateColumn
190 | {
191 | return $this->column = new DateColumn($this->database);
192 | }
193 |
194 | public function time() : TimeColumn
195 | {
196 | return $this->column = new TimeColumn($this->database);
197 | }
198 |
199 | public function datetime() : DatetimeColumn
200 | {
201 | return $this->column = new DatetimeColumn($this->database);
202 | }
203 |
204 | public function timestamp() : TimestampColumn
205 | {
206 | return $this->column = new TimestampColumn($this->database);
207 | }
208 |
209 | public function year() : YearColumn
210 | {
211 | return $this->column = new YearColumn($this->database);
212 | }
213 |
214 | public function geometrycollection() : GeometryCollectionColumn
215 | {
216 | return $this->column = new GeometryCollectionColumn($this->database);
217 | }
218 |
219 | public function geometry() : GeometryColumn
220 | {
221 | return $this->column = new GeometryColumn($this->database);
222 | }
223 |
224 | public function linestring() : LinestringColumn
225 | {
226 | return $this->column = new LinestringColumn($this->database);
227 | }
228 |
229 | public function multilinestring() : MultilinestringColumn
230 | {
231 | return $this->column = new MultilinestringColumn($this->database);
232 | }
233 |
234 | public function multipoint() : MultipointColumn
235 | {
236 | return $this->column = new MultipointColumn($this->database);
237 | }
238 |
239 | public function multipolygon() : MultipolygonColumn
240 | {
241 | return $this->column = new MultipolygonColumn($this->database);
242 | }
243 |
244 | public function point() : PointColumn
245 | {
246 | return $this->column = new PointColumn($this->database);
247 | }
248 |
249 | public function polygon() : PolygonColumn
250 | {
251 | return $this->column = new PolygonColumn($this->database);
252 | }
253 |
254 | protected function sql() : string
255 | {
256 | return $this->column->sql();
257 | }
258 | }
259 |
--------------------------------------------------------------------------------
/.phpstorm.meta.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 | namespace PHPSTORM_META;
11 |
12 | registerArgumentsSet(
13 | 'charsets',
14 | 'armscii8',
15 | 'ascii',
16 | 'big5',
17 | 'binary',
18 | 'cp1250',
19 | 'cp1251',
20 | 'cp1256',
21 | 'cp1257',
22 | 'cp850',
23 | 'cp852',
24 | 'cp866',
25 | 'cp932',
26 | 'dec8',
27 | 'eucjpms',
28 | 'euckr',
29 | 'gb2312',
30 | 'gbk',
31 | 'geostd8',
32 | 'greek',
33 | 'hebrew',
34 | 'hp8',
35 | 'keybcs2',
36 | 'koi8r',
37 | 'koi8u',
38 | 'latin1',
39 | 'latin2',
40 | 'latin5',
41 | 'latin7',
42 | 'macce',
43 | 'macroman',
44 | 'sjis',
45 | 'swe7',
46 | 'tis620',
47 | 'ucs2',
48 | 'ujis',
49 | 'utf16',
50 | 'utf16le',
51 | 'utf32',
52 | 'utf8',
53 | 'utf8mb3',
54 | 'utf8mb4',
55 | );
56 | expectedArguments(
57 | \Framework\Database\Database::setCollations(),
58 | 0,
59 | argumentsSet('charsets')
60 | );
61 | expectedArguments(
62 | \Framework\Database\Definition\AlterSchema::charset(),
63 | 0,
64 | argumentsSet('charsets')
65 | );
66 | expectedArguments(
67 | \Framework\Database\Definition\AlterTable::charset(),
68 | 0,
69 | argumentsSet('charsets')
70 | );
71 | expectedArguments(
72 | \Framework\Database\Definition\AlterTable::convertToCharset(),
73 | 0,
74 | argumentsSet('charsets')
75 | );
76 | expectedArguments(
77 | \Framework\Database\Definition\CreateSchema::charset(),
78 | 0,
79 | argumentsSet('charsets')
80 | );
81 | expectedArguments(
82 | \Framework\Database\Definition\Table\Columns\String\StringDataType::charset(),
83 | 0,
84 | argumentsSet('charsets')
85 | );
86 | expectedArguments(
87 | \Framework\Database\Manipulation\LoadData::charset(),
88 | 0,
89 | argumentsSet('charsets')
90 | );
91 | expectedArguments(
92 | \Framework\Database\Manipulation\Select::intoOutfile(),
93 | 1,
94 | argumentsSet('charsets')
95 | );
96 | registerArgumentsSet(
97 | 'definition_alter_table_algo',
98 | \Framework\Database\Definition\AlterTable::ALGO_COPY,
99 | \Framework\Database\Definition\AlterTable::ALGO_DEFAULT,
100 | \Framework\Database\Definition\AlterTable::ALGO_INPLACE,
101 | \Framework\Database\Definition\AlterTable::ALGO_INSTANT,
102 | \Framework\Database\Definition\AlterTable::ALGO_NOCOPY,
103 | 'COPY',
104 | 'DEFAULT',
105 | 'INPLACE',
106 | 'INSTANT',
107 | 'NOCOPY',
108 | );
109 | expectedArguments(
110 | \Framework\Database\Definition\AlterTable::algorithm(),
111 | 0,
112 | argumentsSet('definition_alter_table_algo')
113 | );
114 | registerArgumentsSet(
115 | 'definition_alter_table_lock',
116 | \Framework\Database\Definition\AlterTable::LOCK_DEFAULT,
117 | \Framework\Database\Definition\AlterTable::LOCK_EXCLUSIVE,
118 | \Framework\Database\Definition\AlterTable::LOCK_NONE,
119 | \Framework\Database\Definition\AlterTable::LOCK_SHARED,
120 | 'DEFAULT',
121 | 'EXCLUSIVE',
122 | 'NONE',
123 | 'SHARED',
124 | );
125 | expectedArguments(
126 | \Framework\Database\Definition\AlterTable::lock(),
127 | 0,
128 | argumentsSet('definition_alter_table_lock')
129 | );
130 | registerArgumentsSet(
131 | 'manipulation_delete_opt',
132 | \Framework\Database\Manipulation\Delete::OPT_IGNORE,
133 | \Framework\Database\Manipulation\Delete::OPT_LOW_PRIORITY,
134 | \Framework\Database\Manipulation\Delete::OPT_QUICK,
135 | 'IGNORE',
136 | 'LOW_PRIORITY',
137 | 'QUICK',
138 | );
139 | expectedArguments(
140 | \Framework\Database\Manipulation\Delete::options(),
141 | 0,
142 | argumentsSet('manipulation_delete_opt')
143 | );
144 | expectedArguments(
145 | \Framework\Database\Manipulation\Delete::options(),
146 | 1,
147 | argumentsSet('manipulation_delete_opt')
148 | );
149 | expectedArguments(
150 | \Framework\Database\Manipulation\Delete::options(),
151 | 2,
152 | argumentsSet('manipulation_delete_opt')
153 | );
154 | registerArgumentsSet(
155 | 'manipulation_insert_opt',
156 | \Framework\Database\Manipulation\Insert::OPT_DELAYED,
157 | \Framework\Database\Manipulation\Insert::OPT_HIGH_PRIORITY,
158 | \Framework\Database\Manipulation\Insert::OPT_IGNORE,
159 | \Framework\Database\Manipulation\Insert::OPT_LOW_PRIORITY,
160 | 'DELAYED',
161 | 'HIGH_PRIORITY',
162 | 'IGNORE',
163 | 'LOW_PRIORITY',
164 | );
165 | expectedArguments(
166 | \Framework\Database\Manipulation\Insert::options(),
167 | 0,
168 | argumentsSet('manipulation_insert_opt')
169 | );
170 | expectedArguments(
171 | \Framework\Database\Manipulation\Insert::options(),
172 | 1,
173 | argumentsSet('manipulation_insert_opt')
174 | );
175 | expectedArguments(
176 | \Framework\Database\Manipulation\Insert::options(),
177 | 2,
178 | argumentsSet('manipulation_insert_opt')
179 | );
180 | registerArgumentsSet(
181 | 'manipulation_load_data_opt',
182 | \Framework\Database\Manipulation\LoadData::OPT_CONCURRENT,
183 | \Framework\Database\Manipulation\LoadData::OPT_LOCAL,
184 | \Framework\Database\Manipulation\LoadData::OPT_LOW_PRIORITY,
185 | 'CONCURRENT',
186 | 'LOCAL',
187 | 'LOW_PRIORITY',
188 | );
189 | expectedArguments(
190 | \Framework\Database\Manipulation\LoadData::options(),
191 | 0,
192 | argumentsSet('manipulation_load_data_opt')
193 | );
194 | expectedArguments(
195 | \Framework\Database\Manipulation\LoadData::options(),
196 | 1,
197 | argumentsSet('manipulation_load_data_opt')
198 | );
199 | expectedArguments(
200 | \Framework\Database\Manipulation\LoadData::options(),
201 | 2,
202 | argumentsSet('manipulation_load_data_opt')
203 | );
204 | registerArgumentsSet(
205 | 'manipulation_replace_opt',
206 | \Framework\Database\Manipulation\Replace::OPT_DELAYED,
207 | \Framework\Database\Manipulation\Replace::OPT_LOW_PRIORITY,
208 | 'DELAYED',
209 | 'LOW_PRIORITY',
210 | );
211 | expectedArguments(
212 | \Framework\Database\Manipulation\Replace::options(),
213 | 0,
214 | argumentsSet('manipulation_replace_opt')
215 | );
216 | expectedArguments(
217 | \Framework\Database\Manipulation\Replace::options(),
218 | 1,
219 | argumentsSet('manipulation_replace_opt')
220 | );
221 | registerArgumentsSet(
222 | 'manipulation_select_opt',
223 | \Framework\Database\Manipulation\Select::OPT_ALL,
224 | \Framework\Database\Manipulation\Select::OPT_DISTINCT,
225 | \Framework\Database\Manipulation\Select::OPT_DISTINCTROW,
226 | \Framework\Database\Manipulation\Select::OPT_HIGH_PRIORITY,
227 | \Framework\Database\Manipulation\Select::OPT_SQL_BIG_RESULT,
228 | \Framework\Database\Manipulation\Select::OPT_SQL_BUFFER_RESULT,
229 | \Framework\Database\Manipulation\Select::OPT_SQL_CACHE,
230 | \Framework\Database\Manipulation\Select::OPT_SQL_CALC_FOUND_ROWS,
231 | \Framework\Database\Manipulation\Select::OPT_SQL_NO_CACHE,
232 | \Framework\Database\Manipulation\Select::OPT_SQL_SMALL_RESULT,
233 | \Framework\Database\Manipulation\Select::OPT_STRAIGHT_JOIN,
234 | 'ALL',
235 | 'DISTINCT',
236 | 'DISTINCTROW',
237 | 'HIGH_PRIORITY',
238 | 'SQL_BIG_RESULT',
239 | 'SQL_BUFFER_RESULT',
240 | 'SQL_CACHE',
241 | 'SQL_CALC_FOUND_ROWS',
242 | 'SQL_NO_CACHE',
243 | 'SQL_SMALL_RESULT',
244 | 'STRAIGHT_JOIN',
245 | );
246 | expectedArguments(
247 | \Framework\Database\Manipulation\Select::options(),
248 | 0,
249 | argumentsSet('manipulation_select_opt')
250 | );
251 | expectedArguments(
252 | \Framework\Database\Manipulation\Select::options(),
253 | 1,
254 | argumentsSet('manipulation_select_opt')
255 | );
256 | expectedArguments(
257 | \Framework\Database\Manipulation\Select::options(),
258 | 2,
259 | argumentsSet('manipulation_select_opt')
260 | );
261 | registerArgumentsSet(
262 | 'manipulation_update_opt',
263 | \Framework\Database\Manipulation\Update::OPT_IGNORE,
264 | \Framework\Database\Manipulation\Update::OPT_LOW_PRIORITY,
265 | 'IGNORE',
266 | 'LOW_PRIORITY',
267 | );
268 | expectedArguments(
269 | \Framework\Database\Manipulation\Update::options(),
270 | 0,
271 | argumentsSet('manipulation_update_opt')
272 | );
273 | expectedArguments(
274 | \Framework\Database\Manipulation\Update::options(),
275 | 1,
276 | argumentsSet('manipulation_update_opt')
277 | );
278 | registerArgumentsSet(
279 | 'manipulation_with_opt',
280 | \Framework\Database\Manipulation\With::OPT_RECURSIVE,
281 | 'RECURSIVE',
282 | );
283 | expectedArguments(
284 | \Framework\Database\Manipulation\With::options(),
285 | 0,
286 | argumentsSet('manipulation_with_opt')
287 | );
288 | registerArgumentsSet(
289 | 'foreign_key_opt',
290 | \Framework\Database\Definition\Table\Indexes\Keys\ForeignKey::OPT_CASCADE,
291 | \Framework\Database\Definition\Table\Indexes\Keys\ForeignKey::OPT_NO_ACTION,
292 | \Framework\Database\Definition\Table\Indexes\Keys\ForeignKey::OPT_RESTRICT,
293 | \Framework\Database\Definition\Table\Indexes\Keys\ForeignKey::OPT_SET_NULL,
294 | 'CASCADE',
295 | 'NO ACTION',
296 | 'RESTRICT',
297 | 'SET NULL',
298 | );
299 | expectedArguments(
300 | \Framework\Database\Definition\Table\Indexes\Keys\ForeignKey::onDelete(),
301 | 0,
302 | argumentsSet('foreign_key_opt')
303 | );
304 | expectedArguments(
305 | \Framework\Database\Definition\Table\Indexes\Keys\ForeignKey::onUpdate(),
306 | 0,
307 | argumentsSet('foreign_key_opt')
308 | );
309 | registerArgumentsSet(
310 | 'table_options',
311 | \Framework\Database\Definition\Table\TableStatement::OPT_AUTO_INCREMENT,
312 | \Framework\Database\Definition\Table\TableStatement::OPT_AVG_ROW_LENGTH,
313 | \Framework\Database\Definition\Table\TableStatement::OPT_CHARSET,
314 | \Framework\Database\Definition\Table\TableStatement::OPT_CHECKSUM,
315 | \Framework\Database\Definition\Table\TableStatement::OPT_COLLATE,
316 | \Framework\Database\Definition\Table\TableStatement::OPT_COMMENT,
317 | \Framework\Database\Definition\Table\TableStatement::OPT_CONNECTION,
318 | \Framework\Database\Definition\Table\TableStatement::OPT_DATA_DIRECTORY,
319 | \Framework\Database\Definition\Table\TableStatement::OPT_DELAY_KEY_WRITE,
320 | \Framework\Database\Definition\Table\TableStatement::OPT_ENCRYPTED,
321 | \Framework\Database\Definition\Table\TableStatement::OPT_ENCRYPTION_KEY_ID,
322 | \Framework\Database\Definition\Table\TableStatement::OPT_ENGINE,
323 | \Framework\Database\Definition\Table\TableStatement::OPT_IETF_QUOTES,
324 | \Framework\Database\Definition\Table\TableStatement::OPT_INDEX_DIRECTORY,
325 | \Framework\Database\Definition\Table\TableStatement::OPT_INSERT_METHOD,
326 | \Framework\Database\Definition\Table\TableStatement::OPT_KEY_BLOCK_SIZE,
327 | \Framework\Database\Definition\Table\TableStatement::OPT_MAX_ROWS,
328 | \Framework\Database\Definition\Table\TableStatement::OPT_MIN_ROWS,
329 | \Framework\Database\Definition\Table\TableStatement::OPT_PACK_KEYS,
330 | \Framework\Database\Definition\Table\TableStatement::OPT_PAGE_CHECKSUM,
331 | \Framework\Database\Definition\Table\TableStatement::OPT_PAGE_COMPRESSED,
332 | \Framework\Database\Definition\Table\TableStatement::OPT_PAGE_COMPRESSION_LEVEL,
333 | \Framework\Database\Definition\Table\TableStatement::OPT_PASSWORD,
334 | \Framework\Database\Definition\Table\TableStatement::OPT_ROW_FORMAT,
335 | \Framework\Database\Definition\Table\TableStatement::OPT_SEQUENCE,
336 | \Framework\Database\Definition\Table\TableStatement::OPT_STATS_AUTO_RECALC,
337 | \Framework\Database\Definition\Table\TableStatement::OPT_STATS_PERSISTENT,
338 | \Framework\Database\Definition\Table\TableStatement::OPT_STATS_SAMPLE_PAGES,
339 | \Framework\Database\Definition\Table\TableStatement::OPT_TABLESPACE,
340 | \Framework\Database\Definition\Table\TableStatement::OPT_TRANSACTIONAL,
341 | \Framework\Database\Definition\Table\TableStatement::OPT_UNION,
342 | \Framework\Database\Definition\Table\TableStatement::OPT_WITH_SYSTEM_VERSIONING,
343 | 'AUTO_INCREMENT',
344 | 'AVG_ROW_LENGTH',
345 | 'CHARSET',
346 | 'CHECKSUM',
347 | 'COLLATE',
348 | 'COMMENT',
349 | 'CONNECTION',
350 | 'DATA DIRECTORY',
351 | 'DELAY_KEY_WRITE',
352 | 'ENCRYPTED',
353 | 'ENCRYPTION_KEY_ID',
354 | 'ENGINE',
355 | 'IETF_QUOTES',
356 | 'INDEX DIRECTORY',
357 | 'INSERT_METHOD',
358 | 'KEY_BLOCK_SIZE',
359 | 'MAX_ROWS',
360 | 'MIN_ROWS',
361 | 'PACK_KEYS',
362 | 'PAGE_CHECKSUM',
363 | 'PAGE_COMPRESSED',
364 | 'PAGE_COMPRESSION_LEVEL',
365 | 'PASSWORD',
366 | 'ROW_FORMAT',
367 | 'SEQUENCE',
368 | 'STATS_AUTO_RECALC',
369 | 'STATS_PERSISTENT',
370 | 'STATS_SAMPLE_PAGES',
371 | 'TABLESPACE',
372 | 'TRANSACTIONAL',
373 | 'UNION',
374 | 'WITH SYSTEM VERSIONING',
375 | );
376 | expectedArguments(
377 | \Framework\Database\Definition\Table\TableStatement::option(),
378 | 0,
379 | argumentsSet('table_options')
380 | );
381 | registerArgumentsSet(
382 | 'operators',
383 | '=',
384 | '<=>',
385 | '!=',
386 | '<>',
387 | '>',
388 | '>=',
389 | '<',
390 | '<=',
391 | 'LIKE',
392 | 'NOT LIKE',
393 | 'IN',
394 | 'NOT IN',
395 | 'BETWEEN',
396 | 'NOT BETWEEN',
397 | 'IS NULL',
398 | 'IS NOT NULL',
399 | );
400 | expectedArguments(
401 | \Framework\Database\Manipulation\Traits\Having::having(),
402 | 1,
403 | argumentsSet('operators')
404 | );
405 | expectedArguments(
406 | \Framework\Database\Manipulation\Traits\Having::orHaving(),
407 | 1,
408 | argumentsSet('operators')
409 | );
410 | expectedArguments(
411 | \Framework\Database\Manipulation\Traits\Where::where(),
412 | 1,
413 | argumentsSet('operators')
414 | );
415 | expectedArguments(
416 | \Framework\Database\Manipulation\Traits\Where::orWhere(),
417 | 1,
418 | argumentsSet('operators')
419 | );
420 | registerArgumentsSet(
421 | 'explain_delete',
422 | \Framework\Database\Manipulation\Delete::EXP_EXTENDED,
423 | \Framework\Database\Manipulation\Delete::EXP_FORMAT_JSON,
424 | \Framework\Database\Manipulation\Delete::EXP_PARTITIONS,
425 | 'EXTENDED',
426 | 'FORMAT=JSON',
427 | 'PARTITIONS',
428 | );
429 | expectedArguments(
430 | \Framework\Database\Manipulation\Delete::explain(),
431 | 0,
432 | argumentsSet('explain_delete')
433 | );
434 | registerArgumentsSet(
435 | 'explain_select',
436 | \Framework\Database\Manipulation\Select::EXP_EXTENDED,
437 | \Framework\Database\Manipulation\Select::EXP_FORMAT_JSON,
438 | \Framework\Database\Manipulation\Select::EXP_PARTITIONS,
439 | 'EXTENDED',
440 | 'FORMAT=JSON',
441 | 'PARTITIONS',
442 | );
443 | expectedArguments(
444 | \Framework\Database\Manipulation\Select::explain(),
445 | 0,
446 | argumentsSet('explain_select')
447 | );
448 | registerArgumentsSet(
449 | 'explain_update',
450 | \Framework\Database\Manipulation\Update::EXP_EXTENDED,
451 | \Framework\Database\Manipulation\Update::EXP_FORMAT_JSON,
452 | \Framework\Database\Manipulation\Update::EXP_PARTITIONS,
453 | 'EXTENDED',
454 | 'FORMAT=JSON',
455 | 'PARTITIONS',
456 | );
457 | expectedArguments(
458 | \Framework\Database\Manipulation\Update::explain(),
459 | 0,
460 | argumentsSet('explain_update')
461 | );
462 |
--------------------------------------------------------------------------------
/src/Definition/Table/TableStatement.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 | namespace Framework\Database\Definition\Table;
11 |
12 | use Framework\Database\Statement;
13 | use InvalidArgumentException;
14 |
15 | /**
16 | * Class TableStatement.
17 | *
18 | * @see https://mariadb.com/kb/en/create-table/#table-options
19 | *
20 | * @package database
21 | */
22 | abstract class TableStatement extends Statement
23 | {
24 | /**
25 | * @see https://mariadb.com/kb/en/create-table/#storage-engine
26 | */
27 | public const string OPT_ENGINE = 'ENGINE';
28 | /**
29 | * @see https://mariadb.com/kb/en/create-table/#auto_increment
30 | */
31 | public const string OPT_AUTO_INCREMENT = 'AUTO_INCREMENT';
32 | /**
33 | * @see https://mariadb.com/kb/en/create-table/#avg_row_length
34 | */
35 | public const string OPT_AVG_ROW_LENGTH = 'AVG_ROW_LENGTH';
36 | /**
37 | * @see https://mariadb.com/kb/en/create-table/#default-character-setcharset
38 | */
39 | public const string OPT_CHARSET = 'CHARSET';
40 | /**
41 | * @see https://mariadb.com/kb/en/create-table/#checksumtable_checksum
42 | */
43 | public const string OPT_CHECKSUM = 'CHECKSUM';
44 | /**
45 | * @see https://mariadb.com/kb/en/create-table/#default-collate
46 | */
47 | public const string OPT_COLLATE = 'COLLATE';
48 | /**
49 | * @see https://mariadb.com/kb/en/create-table/#comment
50 | */
51 | public const string OPT_COMMENT = 'COMMENT';
52 | /**
53 | * @see https://mariadb.com/kb/en/create-table/#connection
54 | */
55 | public const string OPT_CONNECTION = 'CONNECTION';
56 | /**
57 | * @see https://mariadb.com/kb/en/create-table/#data-directoryindex-directory
58 | */
59 | public const string OPT_DATA_DIRECTORY = 'DATA DIRECTORY';
60 | /**
61 | * @see https://mariadb.com/kb/en/create-table/#delay_key_write
62 | */
63 | public const string OPT_DELAY_KEY_WRITE = 'DELAY_KEY_WRITE';
64 | /**
65 | * @see https://mariadb.com/kb/en/create-table/#encrypted
66 | */
67 | public const string OPT_ENCRYPTED = 'ENCRYPTED';
68 | /**
69 | * @see https://mariadb.com/kb/en/create-table/#encryption_key_id
70 | */
71 | public const string OPT_ENCRYPTION_KEY_ID = 'ENCRYPTION_KEY_ID';
72 | /**
73 | * @see https://mariadb.com/kb/en/create-table/#ietf_quotes
74 | */
75 | public const string OPT_IETF_QUOTES = 'IETF_QUOTES';
76 | /**
77 | * @see https://mariadb.com/kb/en/create-table/#data-directoryindex-directory
78 | */
79 | public const string OPT_INDEX_DIRECTORY = 'INDEX DIRECTORY';
80 | /**
81 | * @see https://mariadb.com/kb/en/create-table/#insert_method
82 | */
83 | public const string OPT_INSERT_METHOD = 'INSERT_METHOD';
84 | /**
85 | * @see https://mariadb.com/kb/en/create-table/#key_block_size
86 | */
87 | public const string OPT_KEY_BLOCK_SIZE = 'KEY_BLOCK_SIZE';
88 | /**
89 | * @see https://mariadb.com/kb/en/create-table/#min_rowsmax_rows
90 | */
91 | public const string OPT_MAX_ROWS = 'MAX_ROWS';
92 | /**
93 | * @see https://mariadb.com/kb/en/create-table/#min_rowsmax_rows
94 | */
95 | public const string OPT_MIN_ROWS = 'MIN_ROWS';
96 | /**
97 | * @see https://mariadb.com/kb/en/create-table/#pack_keys
98 | */
99 | public const string OPT_PACK_KEYS = 'PACK_KEYS';
100 | /**
101 | * @see https://mariadb.com/kb/en/create-table/#page_checksum
102 | */
103 | public const string OPT_PAGE_CHECKSUM = 'PAGE_CHECKSUM';
104 | /**
105 | * @see https://mariadb.com/kb/en/create-table/#page_compressed
106 | */
107 | public const string OPT_PAGE_COMPRESSED = 'PAGE_COMPRESSED';
108 | /**
109 | * @see https://mariadb.com/kb/en/create-table/#page_compression_level
110 | */
111 | public const string OPT_PAGE_COMPRESSION_LEVEL = 'PAGE_COMPRESSION_LEVEL';
112 | /**
113 | * @see https://mariadb.com/kb/en/create-table/#password
114 | */
115 | public const string OPT_PASSWORD = 'PASSWORD';
116 | /**
117 | * @see https://mariadb.com/kb/en/create-table/#row_format
118 | */
119 | public const string OPT_ROW_FORMAT = 'ROW_FORMAT';
120 | /**
121 | * @see https://mariadb.com/kb/en/create-table/#sequence
122 | */
123 | public const string OPT_SEQUENCE = 'SEQUENCE';
124 | /**
125 | * @see https://mariadb.com/kb/en/create-table/#stats_auto_recalc
126 | */
127 | public const string OPT_STATS_AUTO_RECALC = 'STATS_AUTO_RECALC';
128 | /**
129 | * @see https://mariadb.com/kb/en/create-table/#stats_persistent
130 | */
131 | public const string OPT_STATS_PERSISTENT = 'STATS_PERSISTENT';
132 | /**
133 | * @see https://mariadb.com/kb/en/create-table/#stats_sample_pages
134 | */
135 | public const string OPT_STATS_SAMPLE_PAGES = 'STATS_SAMPLE_PAGES';
136 | /**
137 | * @see https://mariadb.com/kb/en/create-tablespace/
138 | */
139 | public const string OPT_TABLESPACE = 'TABLESPACE';
140 | /**
141 | * @see https://mariadb.com/kb/en/create-table/#transactional
142 | */
143 | public const string OPT_TRANSACTIONAL = 'TRANSACTIONAL';
144 | /**
145 | * @see https://mariadb.com/kb/en/create-table/#union
146 | */
147 | public const string OPT_UNION = 'UNION';
148 | /**
149 | * @see https://mariadb.com/kb/en/create-table/#with-system-versioning
150 | */
151 | public const string OPT_WITH_SYSTEM_VERSIONING = 'WITH SYSTEM VERSIONING';
152 |
153 | /**
154 | * Adds a table option.
155 | *
156 | * @param string $name
157 | * @param int|string|null $value
158 | *
159 | * @return static
160 | */
161 | public function option(string $name, int | string | null $value = null) : static
162 | {
163 | $this->sql['options'][$name] = $value;
164 | return $this;
165 | }
166 |
167 | /**
168 | * Adds table options.
169 | *
170 | * @param array $options
171 | *
172 | * @return static
173 | */
174 | public function options(array $options) : static
175 | {
176 | foreach ($options as $name => $value) {
177 | $this->option($name, $value);
178 | }
179 | return $this;
180 | }
181 |
182 | protected function renderOptions() : ?string
183 | {
184 | if (!isset($this->sql['options'])) {
185 | return null;
186 | }
187 | $options = [];
188 | foreach ($this->sql['options'] as $name => $value) {
189 | $nameUpper = \strtoupper($name);
190 | $value = (string) $value;
191 | $value = match ($nameUpper) {
192 | static::OPT_ENGINE => $this->makeEngine($value),
193 | static::OPT_AUTO_INCREMENT => $this->makeAutoIncrement($value),
194 | static::OPT_AVG_ROW_LENGTH => $this->makeAvgRowLength($value),
195 | static::OPT_CHARSET => $this->makeCharset($value),
196 | static::OPT_CHECKSUM => $this->makeChecksum($value),
197 | static::OPT_COLLATE => $this->makeCollate($value),
198 | static::OPT_COMMENT => $this->makeComment($value),
199 | static::OPT_CONNECTION => $this->makeConnection($value),
200 | static::OPT_DATA_DIRECTORY => $this->makeDataDirectory($value),
201 | static::OPT_DELAY_KEY_WRITE => $this->makeDelayKeyWrite($value),
202 | static::OPT_ENCRYPTED => $this->makeEncrypted($value),
203 | static::OPT_ENCRYPTION_KEY_ID => $this->makeEncryptionKeyId($value),
204 | static::OPT_IETF_QUOTES => $this->makeIetfQuotes($value),
205 | static::OPT_INDEX_DIRECTORY => $this->makeIndexDirectory($value),
206 | static::OPT_INSERT_METHOD => $this->makeInsertMethod($value),
207 | static::OPT_KEY_BLOCK_SIZE => $this->makeKeyBlockSize($value),
208 | static::OPT_MAX_ROWS => $this->makeMaxRows($value),
209 | static::OPT_MIN_ROWS => $this->makeMinRows($value),
210 | static::OPT_PACK_KEYS => $this->makePackKeys($value),
211 | static::OPT_PAGE_CHECKSUM => $this->makePageChecksum($value),
212 | static::OPT_PAGE_COMPRESSED => $this->makePageCompressed($value),
213 | static::OPT_PAGE_COMPRESSION_LEVEL => $this->makePageCompressionLevel($value),
214 | static::OPT_PASSWORD => $this->makePassword($value),
215 | static::OPT_ROW_FORMAT => $this->makeRowFormat($value),
216 | static::OPT_SEQUENCE => $this->makeSequence($value),
217 | static::OPT_STATS_AUTO_RECALC => $this->makeStatsAutoRecalc($value),
218 | static::OPT_STATS_PERSISTENT => $this->makeStatsPersistent($value),
219 | static::OPT_STATS_SAMPLE_PAGES => $this->makeStatsSamplePages($value),
220 | static::OPT_TABLESPACE => $this->makeTablespace($value),
221 | static::OPT_TRANSACTIONAL => $this->makeTransactional($value),
222 | static::OPT_UNION => $this->makeUnion($value),
223 | static::OPT_WITH_SYSTEM_VERSIONING => '',
224 | default => throw new InvalidArgumentException('Invalid option: ' . $name)
225 | };
226 | $option = $nameUpper;
227 | if ($value !== '') {
228 | $option .= ' = ' . $value;
229 | }
230 | $options[] = $option;
231 | }
232 | return ' ' . \implode(', ', $options);
233 | }
234 |
235 | private function makeEngine(string $value) : string
236 | {
237 | return $this->getValue(static::OPT_ENGINE, $value, [
238 | 'aria' => 'Aria',
239 | 'csv' => 'CSV',
240 | 'innodb' => 'InnoDB',
241 | 'memory' => 'MEMORY',
242 | 'mrg_myisam' => 'MRG_MyISAM',
243 | 'myisam' => 'MyISAM',
244 | 'sequence' => 'SEQUENCE',
245 | ], \strtolower($value), true);
246 | }
247 |
248 | private function makeAutoIncrement(string $value) : string
249 | {
250 | return \is_numeric($value)
251 | ? $value
252 | : throw $this->invalidValue(static::OPT_AUTO_INCREMENT, $value);
253 | }
254 |
255 | private function makeAvgRowLength(string $value) : string
256 | {
257 | if (\is_numeric($value) && $value >= 0) {
258 | return $value;
259 | }
260 | throw $this->invalidValue(static::OPT_AVG_ROW_LENGTH, $value);
261 | }
262 |
263 | private function makeCharset(string $value) : string
264 | {
265 | return $this->getValue(static::OPT_CHARSET, $value, [
266 | 'armscii8',
267 | 'ascii',
268 | 'big5',
269 | 'binary',
270 | 'cp1250',
271 | 'cp1251',
272 | 'cp1256',
273 | 'cp1257',
274 | 'cp850',
275 | 'cp852',
276 | 'cp866',
277 | 'cp932',
278 | 'dec8',
279 | 'eucjpms',
280 | 'euckr',
281 | 'gb2312',
282 | 'gbk',
283 | 'geostd8',
284 | 'greek',
285 | 'hebrew',
286 | 'hp8',
287 | 'keybcs2',
288 | 'koi8r',
289 | 'koi8u',
290 | 'latin1',
291 | 'latin2',
292 | 'latin5',
293 | 'latin7',
294 | 'macce',
295 | 'macroman',
296 | 'sjis',
297 | 'swe7',
298 | 'tis620',
299 | 'ucs2',
300 | 'ujis',
301 | 'utf16',
302 | 'utf16le',
303 | 'utf32',
304 | 'utf8',
305 | 'utf8mb3',
306 | 'utf8mb4',
307 | ], \strtolower($value));
308 | }
309 |
310 | private function makeChecksum(string $value) : string
311 | {
312 | return $this->getValue(static::OPT_CHECKSUM, $value, [
313 | '0',
314 | '1',
315 | ]);
316 | }
317 |
318 | private function makeCollate(string $value) : string
319 | {
320 | return $this->quote($value);
321 | }
322 |
323 | private function makeComment(string $value) : string
324 | {
325 | return $this->quote($value);
326 | }
327 |
328 | private function makeConnection(string $value) : string
329 | {
330 | return $this->quote($value);
331 | }
332 |
333 | private function makeDataDirectory(string $value) : string
334 | {
335 | return $this->quote($value);
336 | }
337 |
338 | private function makeDelayKeyWrite(string $value) : string
339 | {
340 | return $this->getValue(static::OPT_DELAY_KEY_WRITE, $value, [
341 | '0',
342 | '1',
343 | ]);
344 | }
345 |
346 | private function makeIetfQuotes(string $value) : string
347 | {
348 | return $this->getValue(static::OPT_IETF_QUOTES, $value, [
349 | 'NO',
350 | 'YES',
351 | ], \strtoupper($value));
352 | }
353 |
354 | private function makeIndexDirectory(string $value) : string
355 | {
356 | return $this->quote($value);
357 | }
358 |
359 | private function makeInsertMethod(string $value) : string
360 | {
361 | return $this->getValue(static::OPT_INSERT_METHOD, $value, [
362 | 'FIRST',
363 | 'LAST',
364 | 'NO',
365 | ], \strtoupper($value));
366 | }
367 |
368 | private function makeKeyBlockSize(string $value) : string
369 | {
370 | if (\is_numeric($value) && $value >= 0) {
371 | return $value;
372 | }
373 | throw $this->invalidValue(static::OPT_KEY_BLOCK_SIZE, $value);
374 | }
375 |
376 | private function makeEncrypted(string $value) : string
377 | {
378 | return $this->getValue(static::OPT_ENCRYPTED, $value, [
379 | 'NO',
380 | 'YES',
381 | ], \strtoupper($value));
382 | }
383 |
384 | private function makeEncryptionKeyId(string $value) : string
385 | {
386 | if (\is_numeric($value) && $value >= 1) {
387 | return $value;
388 | }
389 | throw $this->invalidValue(static::OPT_ENCRYPTION_KEY_ID, $value);
390 | }
391 |
392 | private function makeMaxRows(string $value) : string
393 | {
394 | if (\is_numeric($value) && $value >= 0) {
395 | return $value;
396 | }
397 | throw $this->invalidValue(static::OPT_MAX_ROWS, $value);
398 | }
399 |
400 | private function makeMinRows(string $value) : string
401 | {
402 | if (\is_numeric($value) && $value >= 0) {
403 | return $value;
404 | }
405 | throw $this->invalidValue(static::OPT_MIN_ROWS, $value);
406 | }
407 |
408 | private function makePackKeys(string $value) : string
409 | {
410 | return $this->getValue(static::OPT_PACK_KEYS, $value, [
411 | '0',
412 | '1',
413 | ]);
414 | }
415 |
416 | private function makePageChecksum(string $value) : string
417 | {
418 | return $this->getValue(static::OPT_PAGE_CHECKSUM, $value, [
419 | '0',
420 | '1',
421 | ]);
422 | }
423 |
424 | private function makePageCompressed(string $value) : string
425 | {
426 | return $this->getValue(
427 | static::OPT_PAGE_COMPRESSED,
428 | $value,
429 | \array_map('strval', \range(0, 9))
430 | );
431 | }
432 |
433 | private function makePageCompressionLevel(string $value) : string
434 | {
435 | return $this->getValue(static::OPT_PAGE_COMPRESSION_LEVEL, $value, [
436 | '0',
437 | '1',
438 | ]);
439 | }
440 |
441 | private function makePassword(string $value) : string
442 | {
443 | return $this->quote($value);
444 | }
445 |
446 | private function makeStatsAutoRecalc(string $value) : string
447 | {
448 | return $this->getValue(static::OPT_STATS_AUTO_RECALC, $value, [
449 | '0',
450 | '1',
451 | 'DEFAULT',
452 | ], \strtoupper($value));
453 | }
454 |
455 | private function makeStatsPersistent(string $value) : string
456 | {
457 | return $this->getValue(static::OPT_STATS_PERSISTENT, $value, [
458 | '0',
459 | '1',
460 | 'DEFAULT',
461 | ], \strtoupper($value));
462 | }
463 |
464 | private function makeStatsSamplePages(string $value) : string
465 | {
466 | if (\strtoupper($value) === 'DEFAULT') {
467 | return 'DEFAULT';
468 | }
469 | if (\is_numeric($value) && $value > 0) {
470 | return $value;
471 | }
472 | throw $this->invalidValue(static::OPT_STATS_SAMPLE_PAGES, $value);
473 | }
474 |
475 | private function makeTablespace(string $value) : string
476 | {
477 | return $this->quote($value);
478 | }
479 |
480 | private function makeRowFormat(string $value) : string
481 | {
482 | return $this->getValue(static::OPT_ROW_FORMAT, $value, [
483 | 'COMPACT',
484 | 'COMPRESSED',
485 | 'DEFAULT',
486 | 'DYNAMIC',
487 | 'FIXED',
488 | 'PAGE',
489 | 'REDUNDANT',
490 | ], \strtoupper($value));
491 | }
492 |
493 | private function makeSequence(string $value) : string
494 | {
495 | if (\is_numeric($value) && $value >= 0) {
496 | return $value;
497 | }
498 | throw $this->invalidValue(static::OPT_SEQUENCE, $value);
499 | }
500 |
501 | private function makeTransactional(string $value) : string
502 | {
503 | return $this->getValue(static::OPT_TRANSACTIONAL, $value, [
504 | '0',
505 | '1',
506 | ]);
507 | }
508 |
509 | private function makeUnion(string $value) : string
510 | {
511 | if ($value === '') {
512 | throw $this->invalidValue(static::OPT_UNION, $value);
513 | }
514 | $tables = \array_map('trim', \explode(',', $value));
515 | foreach ($tables as &$table) {
516 | $table = $this->database->protectIdentifier($table);
517 | }
518 | unset($table);
519 | $tables = \implode(', ', $tables);
520 | return '(' . $tables . ')';
521 | }
522 |
523 | /**
524 | * @param string $optionName
525 | * @param string $originalValue
526 | * @param array $options
527 | * @param string|null $value
528 | * @param bool $getByKey
529 | *
530 | * @return string
531 | */
532 | private function getValue(
533 | string $optionName,
534 | string $originalValue,
535 | array $options,
536 | ?string $value = null,
537 | bool $getByKey = false
538 | ) : string {
539 | $value ??= $originalValue;
540 | if ($getByKey) {
541 | if (isset($options[$value])) {
542 | return $options[$value];
543 | }
544 | } elseif (\in_array($value, $options, true)) {
545 | return $value;
546 | }
547 | throw $this->invalidValue($optionName, $originalValue);
548 | }
549 |
550 | private function invalidValue(string $option, string $value) : InvalidArgumentException
551 | {
552 | return new InvalidArgumentException("Invalid {$option} option value: {$value}");
553 | }
554 |
555 | private function quote(string $value) : string
556 | {
557 | return (string) $this->database->quote($value);
558 | }
559 | }
560 |
--------------------------------------------------------------------------------