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