FieldMetadata::__construct()   B
last analyzed

Complexity

Conditions 8
Paths 14

Size

Total Lines 46
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 32
CRAP Score 8

Importance

Changes 0
Metric Value
cc 8
eloc 31
c 0
b 0
f 0
nc 14
nop 1
dl 0
loc 46
rs 8.1795
ccs 32
cts 32
cp 1
crap 8
1
<?php
2
3
declare(strict_types=1);
4
5
namespace PhpMyAdmin;
6
7
use function define;
8
use function defined;
9
10
use const MYSQLI_BLOB_FLAG;
11
use const MYSQLI_ENUM_FLAG;
12
use const MYSQLI_MULTIPLE_KEY_FLAG;
13
use const MYSQLI_NOT_NULL_FLAG;
14
use const MYSQLI_PRI_KEY_FLAG;
15
use const MYSQLI_SET_FLAG;
16
use const MYSQLI_TYPE_BIT;
17
use const MYSQLI_TYPE_BLOB;
18
use const MYSQLI_TYPE_DATE;
19
use const MYSQLI_TYPE_DATETIME;
20
use const MYSQLI_TYPE_DECIMAL;
21
use const MYSQLI_TYPE_DOUBLE;
22
use const MYSQLI_TYPE_ENUM;
23
use const MYSQLI_TYPE_FLOAT;
24
use const MYSQLI_TYPE_GEOMETRY;
25
use const MYSQLI_TYPE_INT24;
26
use const MYSQLI_TYPE_JSON;
27
use const MYSQLI_TYPE_LONG;
28
use const MYSQLI_TYPE_LONG_BLOB;
29
use const MYSQLI_TYPE_LONGLONG;
30
use const MYSQLI_TYPE_MEDIUM_BLOB;
31
use const MYSQLI_TYPE_NEWDATE;
32
use const MYSQLI_TYPE_NEWDECIMAL;
33
use const MYSQLI_TYPE_NULL;
34
use const MYSQLI_TYPE_SET;
35
use const MYSQLI_TYPE_SHORT;
36
use const MYSQLI_TYPE_STRING;
37
use const MYSQLI_TYPE_TIME;
38
use const MYSQLI_TYPE_TIMESTAMP;
39
use const MYSQLI_TYPE_TINY;
40
use const MYSQLI_TYPE_TINY_BLOB;
41
use const MYSQLI_TYPE_VAR_STRING;
42
use const MYSQLI_TYPE_YEAR;
43
use const MYSQLI_UNIQUE_KEY_FLAG;
44
use const MYSQLI_UNSIGNED_FLAG;
45
use const MYSQLI_ZEROFILL_FLAG;
46
47
// Issue #16043 - client API mysqlnd seem not to have MYSQLI_TYPE_JSON defined
48
if (! defined('MYSQLI_TYPE_JSON')) {
49
    define('MYSQLI_TYPE_JSON', 245);
50
}
51
52
/**
53
 * Handles fields Metadata
54
 *
55
 * NOTE: Getters are not used in all implementations due to the important cost of getters calls
56
 */
57
final class FieldMetadata
58
{
59
    public const TYPE_GEOMETRY = 1;
60
    public const TYPE_BIT = 2;
61
    public const TYPE_JSON = 3;
62
    public const TYPE_REAL = 4;
63
    public const TYPE_INT = 5;
64
    public const TYPE_BLOB = 6;
65
    public const TYPE_UNKNOWN = -1;
66
    public const TYPE_NULL = 7;
67
    public const TYPE_STRING = 8;
68
    public const TYPE_DATE = 9;
69
    public const TYPE_TIME = 10;
70
    public const TYPE_TIMESTAMP = 11;
71
    public const TYPE_DATETIME = 12;
72
    public const TYPE_YEAR = 13;
73
74
    /** @readonly */
75
    public bool $isMultipleKey;
76
77
    /** @readonly */
78
    public bool $isPrimaryKey;
79
80
    /** @readonly */
81
    public bool $isUniqueKey;
82
83
    /** @readonly */
84
    public bool $isNotNull;
85
86
    /** @readonly */
87
    public bool $isUnsigned;
88
89
    /** @readonly */
90
    public bool $isZerofill;
91
92
    /** @readonly */
93
    public bool $isNumeric;
94
95
    /** @readonly */
96
    public bool $isBlob;
97
98
    /** @readonly */
99
    public bool $isBinary;
100
101
    /** @readonly */
102
    public bool $isEnum;
103
104
    /** @readonly */
105
    public bool $isSet;
106
107
    private int|null $mappedType;
108
109
    /** @readonly */
110
    public bool $isMappedTypeBit;
111
112
    /** @readonly */
113
    public bool $isMappedTypeGeometry;
114
115
    /** @readonly */
116
    public bool $isMappedTypeTimestamp;
117
118
    /**
119
     * The column name
120
     *
121
     * @psalm-var non-empty-string
122
     */
123
    public string $name;
124
125
    /**
126
     * The original column name if an alias did exist
127
     */
128
    public string $orgname;
129
130
    /**
131
     * The table name
132
     */
133
    public string $table;
134
135
    /**
136
     * The original table name
137
     */
138
    public string $orgtable;
139
140
    /**
141
     * The charset number
142
     *
143
     * @readonly
144
     */
145
    public int $charsetnr;
146
147
    /**
148
     * The number of decimals used (for integer fields)
149
     *
150
     * @readonly
151
     */
152
    public int $decimals;
153
154
    /**
155
     * The width of the field, as specified in the table definition.
156
     *
157
     * @readonly
158
     */
159
    public int $length;
160
161
    /**
162
     * A field only used by the Results class
163
     */
164
    public string|null $internalMediaType = null;
165
166
    /**
167
     * @psalm-param object{
168
     *     name: non-empty-string,
169
     *     orgname: string,
170
     *     table: string,
171
     *     orgtable: string,
172
     *     max_length: int,
173
     *     length: int,
174
     *     charsetnr: int,
175
     *     flags: int,
176
     *     type: int,
177
     *     decimals: int,
178
     *     db: string,
179
     *     def: string,
180
     *     catalog: string,
181
     * } $field
182
     */
183 20
    public function __construct(object $field)
184
    {
185 20
        $type = $field->type;
186 20
        $this->mappedType = $this->getMappedInternalType($type);
187
188 20
        $flags = $field->flags;
189 20
        $this->isMultipleKey = (bool) ($flags & MYSQLI_MULTIPLE_KEY_FLAG);
190 20
        $this->isPrimaryKey = (bool) ($flags & MYSQLI_PRI_KEY_FLAG);
191 20
        $this->isUniqueKey = (bool) ($flags & MYSQLI_UNIQUE_KEY_FLAG);
192 20
        $this->isNotNull = (bool) ($flags & MYSQLI_NOT_NULL_FLAG);
193 20
        $this->isUnsigned = (bool) ($flags & MYSQLI_UNSIGNED_FLAG);
194 20
        $this->isZerofill = (bool) ($flags & MYSQLI_ZEROFILL_FLAG);
195 20
        $this->isBlob = (bool) ($flags & MYSQLI_BLOB_FLAG);
196 20
        $this->isEnum = (bool) ($flags & MYSQLI_ENUM_FLAG);
197 20
        $this->isSet = (bool) ($flags & MYSQLI_SET_FLAG);
198
199
        // as flags 32768 can be NUM_FLAG or GROUP_FLAG
200
        // reference: https://www.php.net/manual/en/mysqli-result.fetch-fields.php
201
        // so check field type instead of flags
202 20
        $this->isNumeric = $this->isType(self::TYPE_INT) || $this->isType(self::TYPE_REAL);
203
204
        // MYSQLI_PART_KEY_FLAG => 'part_key',
205
        // MYSQLI_TIMESTAMP_FLAG => 'timestamp',
206
        // MYSQLI_AUTO_INCREMENT_FLAG => 'auto_increment',
207
208 20
        $this->isMappedTypeBit = $this->isType(self::TYPE_BIT);
209 20
        $this->isMappedTypeGeometry = $this->isType(self::TYPE_GEOMETRY);
210 20
        $this->isMappedTypeTimestamp = $this->isType(self::TYPE_TIMESTAMP);
211
212 20
        $this->name = $field->name;
213 20
        $this->orgname = $field->orgname;
214 20
        $this->table = $field->table;
215 20
        $this->orgtable = $field->orgtable;
216 20
        $this->charsetnr = $field->charsetnr;
217 20
        $this->decimals = $field->decimals;
218 20
        $this->length = $field->length;
219
220
        // 63 is the number for the MySQL charset "binary"
221 20
        $this->isBinary = (
222 20
            $type === MYSQLI_TYPE_TINY_BLOB ||
223 20
            $type === MYSQLI_TYPE_BLOB ||
224 20
            $type === MYSQLI_TYPE_MEDIUM_BLOB ||
225 20
            $type === MYSQLI_TYPE_LONG_BLOB ||
226 20
            $type === MYSQLI_TYPE_VAR_STRING ||
227 20
            $type === MYSQLI_TYPE_STRING
228 20
        ) && $this->charsetnr == 63;
229
    }
230
231
    /**
232
     * @see https://dev.mysql.com/doc/connectors/en/apis-php-mysqli.constants.html
233
     *
234
     * @psalm-return self::TYPE_*|null
235
     */
236 20
    private function getMappedInternalType(int $type): int|null
237
    {
238 20
        return match ($type) {
239 20
            MYSQLI_TYPE_DECIMAL => self::TYPE_REAL,
240 20
            MYSQLI_TYPE_NEWDECIMAL => self::TYPE_REAL,
241 20
            MYSQLI_TYPE_TINY => self::TYPE_INT,
242 20
            MYSQLI_TYPE_SHORT => self::TYPE_INT,
243 20
            MYSQLI_TYPE_LONG => self::TYPE_INT,
244 20
            MYSQLI_TYPE_FLOAT => self::TYPE_REAL,
245 20
            MYSQLI_TYPE_DOUBLE => self::TYPE_REAL,
246 20
            MYSQLI_TYPE_NULL => self::TYPE_NULL,
247 20
            MYSQLI_TYPE_TIMESTAMP => self::TYPE_TIMESTAMP,
248 20
            MYSQLI_TYPE_LONGLONG => self::TYPE_INT,
249 20
            MYSQLI_TYPE_INT24 => self::TYPE_INT,
250 20
            MYSQLI_TYPE_DATE => self::TYPE_DATE,
251 20
            MYSQLI_TYPE_TIME => self::TYPE_TIME,
252 20
            MYSQLI_TYPE_DATETIME => self::TYPE_DATETIME,
253 20
            MYSQLI_TYPE_YEAR => self::TYPE_YEAR,
254 20
            MYSQLI_TYPE_NEWDATE => self::TYPE_DATE,
255 20
            MYSQLI_TYPE_ENUM => self::TYPE_UNKNOWN,
256 20
            MYSQLI_TYPE_SET => self::TYPE_UNKNOWN,
257 20
            MYSQLI_TYPE_TINY_BLOB => self::TYPE_BLOB,
258 20
            MYSQLI_TYPE_MEDIUM_BLOB => self::TYPE_BLOB,
259 20
            MYSQLI_TYPE_LONG_BLOB => self::TYPE_BLOB,
260 20
            MYSQLI_TYPE_BLOB => self::TYPE_BLOB,
261 20
            MYSQLI_TYPE_VAR_STRING => self::TYPE_STRING,
262 20
            MYSQLI_TYPE_STRING => self::TYPE_STRING,
263
            // MySQL returns MYSQLI_TYPE_STRING for CHAR
264
            // and MYSQLI_TYPE_CHAR === MYSQLI_TYPE_TINY
265
            // so this would override TINYINT and mark all TINYINT as string
266
            // see https://github.com/phpmyadmin/phpmyadmin/issues/8569
267
            //$typeAr[MYSQLI_TYPE_CHAR]        = self::TYPE_STRING;
268 20
            MYSQLI_TYPE_GEOMETRY => self::TYPE_GEOMETRY,
269 20
            MYSQLI_TYPE_BIT => self::TYPE_BIT,
270 20
            MYSQLI_TYPE_JSON => self::TYPE_JSON,
271 20
            default => null,
272 20
        };
273
    }
274
275 20
    public function isNotNull(): bool
276
    {
277 20
        return $this->isNotNull;
278
    }
279
280 8
    public function isNumeric(): bool
281
    {
282 8
        return $this->isNumeric;
283
    }
284
285 20
    public function isBinary(): bool
286
    {
287 20
        return $this->isBinary;
288
    }
289
290 20
    public function isBlob(): bool
291
    {
292 20
        return $this->isBlob;
293
    }
294
295 20
    public function isPrimaryKey(): bool
296
    {
297 20
        return $this->isPrimaryKey;
298
    }
299
300 20
    public function isUniqueKey(): bool
301
    {
302 20
        return $this->isUniqueKey;
303
    }
304
305 20
    public function isMultipleKey(): bool
306
    {
307 20
        return $this->isMultipleKey;
308
    }
309
310 20
    public function isUnsigned(): bool
311
    {
312 20
        return $this->isUnsigned;
313
    }
314
315 20
    public function isZerofill(): bool
316
    {
317 20
        return $this->isZerofill;
318
    }
319
320 20
    public function isEnum(): bool
321
    {
322 20
        return $this->isEnum;
323
    }
324
325 20
    public function isSet(): bool
326
    {
327 20
        return $this->isSet;
328
    }
329
330
    /**
331
     * Checks that it is type DATE/TIME/DATETIME
332
     */
333
    public function isDateTimeType(): bool
334
    {
335
        return $this->isType(self::TYPE_DATE)
336
            || $this->isType(self::TYPE_TIME)
337
            || $this->isType(self::TYPE_DATETIME)
338
            || $this->isType(self::TYPE_TIMESTAMP);
339
    }
340
341
    /**
342
     * Checks that it contains time
343
     * A "DATE" field returns false for example
344
     */
345
    public function isTimeType(): bool
346
    {
347
        return $this->isType(self::TYPE_TIME)
348
            || $this->isType(self::TYPE_TIMESTAMP)
349
            || $this->isType(self::TYPE_DATETIME);
350
    }
351
352
    /**
353
     * Get the mapped type as a string
354
     *
355
     * @return string Empty when nothing could be matched
356
     */
357 16
    public function getMappedType(): string
358
    {
359 16
        return match ($this->mappedType) {
360 16
            self::TYPE_GEOMETRY => 'geometry',
361 16
            self::TYPE_BIT => 'bit',
362 16
            self::TYPE_JSON => 'json',
363 16
            self::TYPE_REAL => 'real',
364 16
            self::TYPE_INT => 'int',
365 16
            self::TYPE_BLOB => 'blob',
366 16
            self::TYPE_UNKNOWN => 'unknown',
367 16
            self::TYPE_NULL => 'null',
368 16
            self::TYPE_STRING => 'string',
369 16
            self::TYPE_DATE => 'date',
370 16
            self::TYPE_TIME => 'time',
371 16
            self::TYPE_TIMESTAMP => 'timestamp',
372 16
            self::TYPE_DATETIME => 'datetime',
373 16
            self::TYPE_YEAR => 'year',
374 16
            default => '',
375 16
        };
376
    }
377
378
    /**
379
     * Check if it is the mapped type
380
     *
381
     * @phpstan-param self::TYPE_* $type
382
     */
383 20
    public function isType(int $type): bool
384
    {
385 20
        return $this->mappedType === $type;
386
    }
387
388
    /**
389
     * Check if it is NOT the mapped type
390
     *
391
     * @phpstan-param self::TYPE_* $type
392
     */
393
    public function isNotType(int $type): bool
394
    {
395
        return $this->mappedType !== $type;
396
    }
397
}
398