3
namespace Upside\Db\SQL;
8
protected string $date_format = 'Y-m-d H:i:s';
11
protected string $wrapper = '"%s"';
14
protected array $params = [];
19
public function select(SQLStatement $select): string
21
$sql = $select->get_distinct() ? 'SELECT DISTINCT ' : 'SELECT ';
22
$sql .= $this->handle_columns($select->get_columns());
23
$sql .= $this->handle_into($select->get_into_table(), $select->get_into_database());
25
$sql .= $this->handle_tables($select->get_tables());
26
$sql .= $this->handle_joins($select->get_joins());
27
$sql .= $this->handle_wheres($select->get_wheres());
28
$sql .= $this->handle_groupings($select->get_group_by());
29
$sql .= $this->handle_orderings($select->get_order());
30
$sql .= $this->handle_havings($select->get_having());
31
$sql .= $this->handle_limit($select->get_limit());
32
$sql .= $this->handle_offset($select->get_offset());
40
public function insert(SQLStatement $insert): string
42
$columns = $this->handle_columns($insert->get_columns());
44
$sql = 'INSERT INTO ';
45
$sql .= $this->handle_tables($insert->get_tables());
46
$sql .= ($columns === '*') ? '' : ' (' . $columns . ')';
47
$sql .= $this->handle_insert_values($insert->get_values());
56
public function update(SQLStatement $update): string
59
$sql .= $this->handle_tables($update->get_tables());
60
$sql .= $this->handle_joins($update->get_joins());
61
$sql .= $this->handle_set_columns($update->get_columns());
62
$sql .= $this->handle_wheres($update->get_wheres());
70
public function delete(SQLStatement $delete): string
72
$sql = 'DELETE ' . $this->handle_tables($delete->get_tables());
73
$sql .= $sql === 'DELETE ' ? 'FROM ' : ' FROM ';
74
$sql .= $this->handle_tables($delete->get_from());
75
$sql .= $this->handle_joins($delete->get_joins());
76
$sql .= $this->handle_wheres($delete->get_wheres());
84
public function get_date_format(): string
86
return $this->date_format;
92
public function set_options(array $options): void
94
foreach ($options as $name => $value) {
95
$this->{$name} = $value;
102
public function params(array $params): string
104
return \implode(', ', \array_map([$this, 'param'], $params));
110
public function columns(array $columns): string
112
return \implode(', ', \array_map([$this, 'wrap'], $columns));
115
public function quote(string $value): string
117
return "'" . \str_replace("'", "''", $value) . "'";
123
public function get_params(): array
125
$params = $this->params;
134
protected function wrap(mixed $value): string
136
if ($value instanceof Expression) {
137
return $this->handle_expressions($value->get_expressions());
142
foreach (\explode('.', $value) as $segment) {
143
if ($segment === '*') {
144
$wrapped[] = $segment;
146
$wrapped[] = \sprintf($this->wrapper, $segment);
150
return \implode('.', $wrapped);
156
protected function param(mixed $value): string
158
if ($value instanceof Expression) {
159
return $this->handle_expressions($value->get_expressions());
162
if ($value instanceof \DateTimeInterface) {
163
$this->params[] = $value->format($this->date_format);
165
$this->params[] = $value;
174
protected function handle_expressions(array $expressions): string
178
foreach ($expressions as $expr) {
179
switch ($expr['type']) {
181
$sql[] = $this->wrap($expr['value']);
184
$sql[] = $expr['value'];
187
$sql[] = $this->param($expr['value']);
191
$expression = $expr['value'];
192
$sql[] = '(' . $this->handle_expressions($expression->get_expressions()) . ')';
195
$sql[] = $this->handle_sql_function($expr['value']);
199
$subquery = $expr['value'];
200
$sql[] = '(' . $this->select($subquery->get_sql_statement()) . ')';
205
return \implode(' ', $sql);
211
protected function handle_sql_function(array $func): string
213
$method = $func['type'] . $func['name'];
215
return $this->{$method}($func);
221
protected function handle_tables(array $tables): string
223
if (empty($tables)) {
229
foreach ($tables as $name => $alias) {
230
if (\is_string($name)) {
231
$sql[] = $this->wrap($name) . ' AS ' . $this->wrap($alias);
233
$sql[] = $this->wrap($alias);
236
return \implode(', ', $sql);
242
protected function handle_columns(array $columns): string
244
if (empty($columns)) {
250
foreach ($columns as $column) {
251
if ($column['alias'] !== null) {
252
$sql[] = $this->wrap($column['name']) . ' AS ' . $this->wrap($column['alias']);
254
$sql[] = $this->wrap($column['name']);
258
return \implode(', ', $sql);
264
protected function handle_into($table, $database): string
266
if ($table === null) {
269
return ' INTO ' . $this->wrap($table) . ($database === null ? '' : ' IN ' . $this->wrap($database));
275
protected function handle_wheres(array $wheres, bool $prefix = true): string
277
if (empty($wheres)) {
281
$sql[] = $this->{$wheres[0]['type']}($wheres[0]);
283
$count = \count($wheres);
285
for ($i = 1; $i < $count; $i++) {
286
$sql[] = $wheres[$i]['separator'] . ' ' . $this->{$wheres[$i]['type']}($wheres[$i]);
289
return ($prefix ? ' WHERE ' : '') . \implode(' ', $sql);
295
protected function handle_groupings(array $grouping): string
297
return empty($grouping) ? '' : ' GROUP BY ' . $this->columns($grouping);
303
protected function handle_joins(array $joins): string
309
foreach ($joins as $join) {
311
$joinObject = $join['join'];
315
$on = $this->handle_join_conditions($joinObject->get_join_conditions());
321
$sql[] = $join['type'] . ' JOIN ' . $this->handle_tables($join['table']) . $on;
324
return ' ' . \implode(' ', $sql);
330
protected function handle_join_conditions(array $conditions): string
332
if (empty($conditions)) {
335
$sql[] = $this->{$conditions[0]['type']}($conditions[0]);
336
$count = \count($conditions);
337
for ($i = 1; $i < $count; $i++) {
338
$sql[] = $conditions[$i]['separator'] . ' ' . $this->{$conditions[$i]['type']}($conditions[$i]);
341
return \implode(' ', $sql);
347
protected function handle_havings(array $havings, bool $prefix = true): string
349
if (empty($havings)) {
353
$sql[] = $this->{$havings[0]['type']}($havings[0]);
355
$count = \count($havings);
357
for ($i = 1; $i < $count; $i++) {
358
$sql[] = $havings[$i]['separator'] . ' ' . $this->{$havings[$i]['type']}($havings[$i]);
361
return ($prefix ? ' HAVING ' : '') . \implode(' ', $sql);
367
protected function handle_orderings(array $ordering): string
369
if (empty($ordering)) {
375
foreach ($ordering as $order) {
376
if ($order['nulls'] !== null) {
377
foreach ($order['columns'] as $column) {
378
$column = $this->columns([$column]);
380
if ($order['nulls'] === 'NULLS FIRST') {
381
$sql[] = '(CASE WHEN ' . $column . ' IS NULL THEN 0 ELSE 1 END)';
383
$sql[] = '(CASE WHEN ' . $column . ' IS NULL THEN 1 ELSE 0 END)';
388
$sql[] = $this->columns($order['columns']) . ' ' . $order['order'];
391
return ' ORDER BY ' . \implode(', ', $sql);
397
protected function handle_set_columns(array $columns): string
399
if (empty($columns)) {
405
foreach ($columns as $column) {
406
$sql[] = $this->wrap($column['column']) . ' = ' . $this->param($column['value']);
409
return ' SET ' . \implode(', ', $sql);
415
protected function handle_insert_values(array $values): string
417
return ' VALUES (' . $this->params($values) . ')';
423
protected function handle_limit(int $limit = 0): string
425
return ($limit === 0) ? '' : ' LIMIT ' . $this->param($limit);
431
protected function handle_offset(int $offset = -1): string
433
return ($offset === -1) ? '' : ' OFFSET ' . $this->param($offset);
436
protected function join_column(array $join): string
438
return $this->wrap($join['column1']) . ' ' . $join['operator'] . ' ' . $this->wrap($join['column2']);
441
protected function join_nested(array $join): string
443
return '(' . $this->handle_join_conditions($join['join']->get_join_conditions()) . ')';
446
protected function join_expression(array $join): string
448
return $this->wrap($join['expression']);
451
protected function where_column(array $where): string
453
return $this->wrap($where['column']) . ' ' . $where['operator'] . ' ' . $this->param($where['value']);
456
protected function where_in(array $where): string
458
return $this->wrap($where['column']) . ' ' . ($where['not'] ? 'NOT IN ' : 'IN ') . '(' . $this->params($where['value']) . ')';
461
protected function where_in_select(array $where): string
463
return $this->wrap($where['column']) . ' ' . ($where['not'] ? 'NOT IN ' : 'IN ') . '(' . $this->select($where['subquery']->get_sql_Statement()) . ')';
466
protected function where_nested(array $where): string
468
return '(' . $this->handle_wheres($where['clause'], false) . ')';
471
protected function where_exists(array $where): string
473
return ($where['not'] ? 'NOT EXISTS ' : 'EXISTS ') . '(' . $this->select($where['subquery']->get_sql_statement()) . ')';
476
protected function where_null(array $where): string
478
return $this->wrap($where['column']) . ' ' . ($where['not'] ? 'IS NOT NULL' : 'IS NULL');
481
protected function where_between(array $where): string
483
return $this->wrap($where['column']) . ' ' . ($where['not'] ? 'NOT BETWEEN' : 'BETWEEN') . ' ' . $this->param($where['value1']) . ' AND ' . $this->param($where['value2']);
486
protected function where_like(array $where): string
488
return $this->wrap($where['column']) . ' ' . ($where['not'] ? 'NOT LIKE' : 'LIKE') . ' ' . $this->param($where['pattern']);
491
protected function where_subquery(array $where): string
493
return $this->wrap($where['column']) . ' ' . $where['operator'] . ' (' . $this->select($where['subquery']->get_sql_statement()) . ')';
496
protected function where_nop(array $where): string
498
return $this->wrap($where['column']);
501
protected function having_condition(array $having): string
503
return $this->wrap($having['aggregate']) . ' ' . $having['operator'] . ' ' . $this->param($having['value']);
506
protected function having_nested(array $having): string
508
return '(' . $this->handle_havings($having['conditions'], false) . ')';
511
protected function having_between(array $having): string
513
return $this->wrap($having['aggregate']) . ($having['not'] ? ' NOT BETWEEN ' : ' BETWEEN ') . $this->param($having['value1']) . ' AND ' . $this->param($having['value2']);
516
protected function having_in_select(array $having): string
518
return $this->wrap($having['aggregate']) . ($having['not'] ? ' NOT IN ' : ' IN ') . '(' . $this->select($having['subquery']->get_sql_statement()) . ')';
521
protected function having_in(array $having): string
523
return $this->wrap($having['aggregate']) . ($having['not'] ? ' NOT IN ' : ' IN ') . '(' . $this->params($having['value']) . ')';
526
protected function aggregate_function_COUNT(array $func): string
528
return 'COUNT(' . ($func['distinct'] ? 'DISTINCT ' : '') . $this->columns($func['column']) . ')';
531
protected function aggregate_function_AVG(array $func): string
533
return 'AVG(' . ($func['distinct'] ? 'DISTINCT ' : '') . $this->wrap($func['column']) . ')';
536
protected function aggregate_function_SUM(array $func): string
538
return 'SUM(' . ($func['distinct'] ? 'DISTINCT ' : '') . $this->wrap($func['column']) . ')';
541
protected function aggregate_function_MIN(array $func): string
543
return 'MIN(' . ($func['distinct'] ? 'DISTINCT ' : '') . $this->wrap($func['column']) . ')';
546
protected function aggregate_function_MAX(array $func): string
548
return 'MAX(' . ($func['distinct'] ? 'DISTINCT ' : '') . $this->wrap($func['column']) . ')';
551
protected function sql_function_UCASE(array $func): string
553
return 'UCASE(' . $this->wrap($func['column']) . ')';
556
protected function sql_function_LCASE(array $func): string
558
return 'LCASE(' . $this->wrap($func['column']) . ')';
561
protected function sql_function_MID(array $func): string
563
return 'MID(' . $this->wrap($func['column']) . ', ' . $this->param($func['start']) . ($func['length'] > 0 ? ', ' . $this->param($func['length']) . ')' : ')');
566
protected function sql_function_LEN(array $func): string
568
return 'LEN(' . $this->wrap($func['column']) . ')';
571
protected function sql_function_ROUND(array $func): string
573
return 'ROUND(' . $this->wrap($func['column']) . ', ' . $this->param($func['decimals']) . ')';
576
protected function sql_function_NOW(array $func): string
581
protected function sql_function_FORMAT(array $func): string
583
return 'FORMAT(' . $this->wrap($func['column']) . ', ' . $this->param($func['format']) . ')';