Completed
Push — master ( 10bb60...177540 )
by Sébastien
03:45
created

MysqliMetadataReader::setupCapabilities()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 14
ccs 8
cts 8
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 8
nc 2
nop 0
crap 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Soluble\Metadata\Reader;
6
7
use Soluble\Metadata\ColumnsMetadata;
8
use Soluble\Metadata\Exception;
9
use Soluble\Metadata\Reader\Capability\ReaderCapabilityInterface;
10
use Soluble\Metadata\Reader\Mapping\MysqliMapping;
11
use Soluble\Datatype\Column;
12
13
class MysqliMetadataReader extends AbstractMetadataReader
14
{
15
    /**
16
     * @var \mysqli
17
     */
18
    protected $mysqli;
19
20
    /**
21
     * @var array
22
     */
23
    protected static $metadata_cache = [];
24
25
    /**
26
     * @param \mysqli $mysqli
27
     */
28 14
    public function __construct(\mysqli $mysqli)
29
    {
30 14
        $this->mysqli = $mysqli;
31 14
        $this->setupCapabilities();
32 14
    }
33
34 14
    protected function setupCapabilities(): void
35
    {
36
        $caps = [
37 14
            ReaderCapabilityInterface::DETECT_GROUP_FUNCTION,
38 14
            ReaderCapabilityInterface::DETECT_PRIMARY_KEY,
39 14
            ReaderCapabilityInterface::DETECT_NUMERIC_UNSIGNED,
40 14
            ReaderCapabilityInterface::DETECT_AUTOINCREMENT,
41
            //ReaderCapabilityInterface::DETECT_COLUMN_DEFAULT,
42
            //ReaderCapabilityInterface::DETECT_CHAR_MAX_LENGTH,
43
        ];
44 14
        foreach ($caps as $cap) {
45 14
            $this->addCapability($cap);
46
        }
47 14
    }
48
49
    /**
50
     * {@inheritdoc}
51
     *
52
     * @throws \Soluble\Metadata\Exception\ConnectionException
53
     * @throws \Soluble\Metadata\Exception\EmptyQueryException
54
     * @throws \Soluble\Metadata\Exception\InvalidQueryException
55
     */
56 8
    protected function readColumnsMetadata(string $sql): ColumnsMetadata
57
    {
58 8
        $metadata = new ColumnsMetadata();
59 8
        $fields = $this->readFields($sql);
60 4
        $type_map = MysqliMapping::getDatatypeMapping();
61
62 4
        foreach ($fields as $idx => $field) {
63 4
            $name = $field->orgname === '' ? $field->name : $field->orgname;
64 4
            $tableName = $field->orgtable;
65 4
            $schemaName = $field->db;
66
67 4
            $type = $field->type;
68
69 4
            if (!$type_map->offsetExists($type)) {
70
                $msg = "Cannot get type for field '$name'. Mapping for native type [$type] cannot be resolved into a valid type for driver: " . __CLASS__;
71
                throw new Exception\UnsupportedTypeException($msg);
72
            }
73
74 4
            $datatype = $type_map->offsetGet($type);
75
76 4
            $column = Column\Type::createColumnDefinition($datatype['type'], $name, $tableName, $schemaName);
77
78 4
            $column->setAlias($field->name);
79 4
            $column->setTableAlias($field->table);
80 4
            $column->setCatalog($field->catalog);
81 4
            $column->setOrdinalPosition($idx + 1);
82 4
            $column->setDataType($datatype['type']);
83 4
            $column->setIsNullable(!($field->flags & MYSQLI_NOT_NULL_FLAG) > 0 && ($field->orgtable !== '' || $field->orgtable !== null));
84 4
            $column->setIsPrimary(($field->flags & MYSQLI_PRI_KEY_FLAG) > 0);
85
86 4
            $column->setColumnDefault($field->def);
87
88 4
            if (($field->flags & MYSQLI_SET_FLAG) > 0) {
89 1
                $column->setNativeDataType('SET');
90 4
            } elseif (($field->flags & MYSQLI_ENUM_FLAG) > 0) {
91 1
                $column->setNativeDataType('ENUM');
92
            } else {
93 4
                $column->setNativeDataType($datatype['native']);
94
            }
95
96 4
            if ($field->table === '' || $field->table === null) {
97 1
                $column->setIsGroup(($field->flags & MYSQLI_GROUP_FLAG) > 0);
98
            }
99
100 4
            if ($column instanceof Column\Definition\NumericColumnInterface) {
101 4
                $column->setNumericUnsigned(($field->flags & MYSQLI_UNSIGNED_FLAG) > 0);
102
            }
103
104 4
            if ($column instanceof Column\Definition\IntegerColumn) {
105 4
                $column->setIsAutoIncrement(($field->flags & MYSQLI_AUTO_INCREMENT_FLAG) > 0);
106 3
            } elseif ($column instanceof Column\Definition\DecimalColumn) {
107
                // salary DECIMAL(5,2)
108
                // In this example, 5 is the precision and 2 is the scale.
109
                // Standard SQL requires that DECIMAL(5,2) be able to store any value
110
                // with five digits and two decimals, so values that can be stored in
111
                // the salary column range from -999.99 to 999.99.
112 2
                $column->setNumericScale($field->length - $field->decimals + 1);
113 2
                $column->setNumericPrecision($field->decimals);
114
            }
115
116 4
            if ($column instanceof Column\Definition\StringColumn) {
117 3
                $column->setCharacterMaximumLength($field->length);
118
            }
119
120 4
            if ($column instanceof Column\Definition\BlobColumn) {
121 1
                $column->setCharacterOctetLength($field->length);
122
            }
123
124 4
            $alias = $column->getAlias();
125
126 4
            if ($metadata->offsetExists($alias)) {
127 1
                $prev_column = $metadata->offsetGet($alias);
128 1
                $prev_def = $prev_column->toArray();
129 1
                $curr_def = $column->toArray();
130 1
                if ($prev_def['dataType'] !== $curr_def['dataType'] || $prev_def['nativeDataType'] !== $curr_def['nativeDataType']) {
131 1
                    throw new Exception\AmbiguousColumnException("Cannot get column metadata, non unique column found '$alias' in query with different definitions.");
132
                }
133
134
                // If the the previous definition, was a prev_def
135
                /*
136
                if ($prev_def['isPrimary']) {
137
                    $column = $prev_column;
138
                }*/
139
            }
140
141 4
            $metadata->offsetSet($alias, $column);
142
        }
143
144 3
        return $metadata;
145
    }
146
147
    /**
148
     * @throws Exception\ConnectionException
149
     * @throws \Soluble\Metadata\Exception\EmptyQueryException
150
     * @throws \Soluble\Metadata\Exception\InvalidQueryException
151
     */
152 8
    protected function readFields(string $sql): array
153
    {
154 8
        if (trim($sql) === '') {
155 1
            throw new Exception\EmptyQueryException('Cannot read fields for an empty query');
156
        }
157
158 7
        $sql = $this->getEmptiedQuery($sql);
159 7
        $stmt = $this->mysqli->prepare($sql);
160
161 7
        if (!$stmt) {
162 3
            $message = $this->mysqli->error;
163 3
            throw new Exception\InvalidQueryException(
164 3
                sprintf('Invalid query: %s (%s)', $sql, $message)
165
            );
166
        }
167 4
        $stmt->execute();
168
169 4
        $result = $stmt->result_metadata();
170 4
        $metaFields = $result->fetch_fields();
171 4
        $result->close();
172 4
        $stmt->close();
173
174 4
        return $metaFields;
175
    }
176
}
177