Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
94.81% covered (success)
94.81%
73 / 77
92.31% covered (success)
92.31%
12 / 13
CRAP
0.00% covered (danger)
0.00%
0 / 1
MariaQueryBuilder
94.81% covered (success)
94.81%
73 / 77
92.31% covered (success)
92.31%
12 / 13
57.46
0.00% covered (danger)
0.00%
0 / 1
 columnDefinition
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
4
 primaryKeyDefinition
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
4
 foreignKeyDefinition
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
5
 isTableExist
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 listTables
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 describeTable
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 columnsByTableDescription
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 checkIntSize
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
3
 checkArraySize
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
3
 sqlType
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
16
 sqlAction
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 sqlDefaultValue
76.47% covered (warning)
76.47%
13 / 17
0.00% covered (danger)
0.00%
0 / 1
19.33
 isDateType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace Dynart\Micro\Entities\QueryBuilder;
4
5use Dynart\Micro\Entities\Attribute\Column;
6use Dynart\Micro\Entities\EntityManagerException;
7use Dynart\Micro\Entities\QueryBuilder;
8
9class MariaQueryBuilder extends QueryBuilder {
10
11    const SIMPLE_TYPE_MAP = [
12        Column::TYPE_LONG     => 'bigint',
13        Column::TYPE_INT      => 'int',
14        Column::TYPE_FLOAT    => 'float',
15        Column::TYPE_DOUBLE   => 'double',
16        Column::TYPE_BOOL     => 'tinyint(1)',
17        Column::TYPE_DATE     => 'date',
18        Column::TYPE_TIME     => 'time',
19        Column::TYPE_DATETIME => 'datetime',
20        Column::TYPE_BLOB     => 'blob'
21    ];
22
23    public function columnDefinition(string $columnName, Column $column): string {
24        $parts = [$this->db->escapeName($columnName)];
25        $parts[] = $this->sqlType($column->type, $column->size, $column->fixSize);
26        if ($column->notNull) {
27            $parts[] = 'not null';
28        }
29        if ($column->autoIncrement) {
30            $parts[] = 'auto_increment';
31        }
32        if ($column->default !== null) {
33            $parts[] = "default ".$this->sqlDefaultValue($column->default, $column->type, $column->size);
34        }
35        return join(' ', $parts);
36    }
37
38    public function primaryKeyDefinition(string $className): string {
39        $result = '';
40        $primaryKey = $this->em->primaryKey($className);
41        if (!$primaryKey) {
42            return $result;
43        }
44        $result = 'primary key (';
45        if (is_array($primaryKey)) {
46            $pks = [];
47            foreach ($primaryKey as $pk) {
48                $pks[] = $this->db->escapeName($pk);
49            }
50            $result .= join(', ', $pks);
51        } else {
52            $result .= $this->db->escapeName($primaryKey);
53        }
54        $result .= ')';
55        return $result;
56    }
57
58    public function foreignKeyDefinition(string $columnName, Column $column): string {
59        if ($column->foreignKey === null) {
60            return '';
61        }
62        if (count($column->foreignKey) != 2) {
63            throw new EntityManagerException("Foreign key definition array size must be 2: ".$this->currentColumn());
64        }
65        [$foreignClassName, $foreignColumnName] = $column->foreignKey;
66        $result = 'foreign key ('.$this->db->escapeName($columnName).')'
67            .' references '.$this->em->safeTableName($foreignClassName)
68            .' ('.$this->db->escapeName($foreignColumnName).')';
69        if ($column->onDelete !== null) {
70            $result .= ' on delete '.$this->sqlAction($column->onDelete);
71        }
72        if ($column->onUpdate !== null) {
73            $result .= ' on update '.$this->sqlAction($column->onUpdate);
74        }
75        return $result;
76    }
77
78    public function isTableExist(string $dbNameParam, string $tableNameParam): string {
79        return "select 1 from information_schema.tables where table_schema = $dbNameParam and table_name = $tableNameParam limit 1";
80    }
81
82    public function listTables(): string {
83        return "show tables";
84    }
85
86    public function describeTable(string $className): string {
87        return "describe ".$this->em->tableNameByClass($className);
88    }
89
90    public function columnsByTableDescription(array $data): array {
91        return [];
92    }
93
94    protected function checkIntSize(mixed $size): void {
95        if ($size && !is_int($size)) {
96            throw new EntityManagerException("The size has to be an integer! ".$this->currentColumn());
97        }
98    }
99
100    protected function checkArraySize(mixed $size, int $count): void {
101        if (!is_array($size) || count($size) != $count) {
102            throw new EntityManagerException("The size array has to have $count elements! ".$this->currentColumn());
103        }
104    }
105
106    protected function sqlType(string $type, mixed $size, bool $fixSize): string {
107        switch ($type) {
108            case Column::TYPE_BOOL:
109            case Column::TYPE_DATE:
110            case Column::TYPE_TIME:
111            case Column::TYPE_DATETIME:
112            case Column::TYPE_BLOB:
113                return self::SIMPLE_TYPE_MAP[$type];
114
115            case Column::TYPE_LONG:
116            case Column::TYPE_INT:
117            case Column::TYPE_FLOAT:
118            case Column::TYPE_DOUBLE:
119                $this->checkIntSize($size);
120                $mappedType = self::SIMPLE_TYPE_MAP[$type];
121                return $size ? "$mappedType($size)" : $mappedType;
122
123            case Column::TYPE_NUMERIC:
124                $this->checkArraySize($size, 2);
125                return "decimal($size[0]$size[1])";
126
127            case Column::TYPE_STRING:
128                if (!$size) {
129                    return 'longtext';
130                }
131                $this->checkIntSize($size);
132                return $fixSize ? "char($size)" : "varchar($size)";
133
134            default:
135                throw new EntityManagerException("Unknown type '$type': ".$this->currentColumn());
136        }
137    }
138
139    protected function sqlAction(string $action): string {
140        return match ($action) {
141            Column::ACTION_CASCADE => 'cascade',
142            Column::ACTION_SET_NULL => 'set null',
143            default => throw new EntityManagerException("Unknown action '$action': : ".$this->currentColumn()),
144        };
145    }
146
147    protected function sqlDefaultValue(mixed $value, string $type, int|array $size): string {
148        if ($type == Column::TYPE_BLOB || ($type == Column::TYPE_STRING && !$size)) {
149            throw new EntityManagerException("Text and blob types can't have a default value: ".$this->currentColumn());
150        }
151        if ($value === null) {
152            return 'null';
153        } else if (is_array($value)) {
154            if (count($value) != 1) {
155                throw new EntityManagerException("Raw default value (array) only can have one element: ".$this->currentColumn());
156            }
157            return $value[0];
158        } else if ($type == Column::TYPE_STRING) {
159            return "'".str_replace("'", "\\'", $value)."'";
160        } else if ($this->isDateType($type) && $value == Column::NOW) {
161            switch ($type) {
162                case Column::TYPE_DATETIME:
163                    return 'utc_timestamp()';
164                case Column::TYPE_DATE:
165                    return 'utc_date()';
166                case Column::TYPE_TIME:
167                    return 'utc_time()';
168            }
169        } else if ($type == Column::TYPE_BOOL && is_bool($value)) {
170            return $value ? '1' : '0';
171        }
172        return $value;
173    }
174
175    protected function isDateType(string $type): bool {
176        return in_array($type, [Column::TYPE_DATE, Column::TYPE_TIME, Column::TYPE_DATETIME]);
177    }
178}