Completed
Push — master ( 519a4c...c1b59a )
by Sébastien
03:04
created

MysqliMetadataReader::readFields()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 22
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 3

Importance

Changes 3
Bugs 0 Features 0
Metric Value
c 3
b 0
f 0
dl 0
loc 22
ccs 14
cts 14
cp 1
rs 9.2
cc 3
eloc 14
nc 3
nop 1
crap 3
1
<?php
2
3
namespace Soluble\Metadata\Reader;
4
5
use Soluble\Metadata\ColumnsMetadata;
6
use Soluble\Metadata\Exception;
7
use Soluble\Datatype\Column;
8
use Soluble\Datatype\Column\Exception\UnsupportedDatatypeException;
9
use ArrayObject;
10
11
class MysqliMetadataReader extends AbstractMetadataReader
12
{
13
14
    /**
15
     * @var \Mysqli
16
     */
17
    protected $mysqli;
18
19
    /**
20
     *
21
     * @var boolean
22
     */
23
    protected $cache_active = true;
24
25
    /**
26
     *
27
     * @var Array
28
     */
29
    protected static $metadata_cache = [];
30
31
    /**
32
     *
33
     * @param \Mysqli $mysqli
34
     */
35 7
    public function __construct(\Mysqli $mysqli)
36
    {
37 7
        $this->mysqli = $mysqli;
38 7
    }
39
40
    /**
41
     *
42
     * {@inheritdoc}
43
     */
44 6
    protected function readColumnsMetadata($sql)
45
    {
46 6
        $metadata = new ColumnsMetadata();
47 6
        $fields = $this->readFields($sql);
48 4
        $type_map = $this->getDatatypeMapping();
49
50
51 4
        foreach ($fields as $idx => $field) {
52 4
            $name = $field->orgname == '' ? $field->name : $field->orgname;
53 4
            $tableName = $field->orgtable;
54 4
            $schemaName = $field->db;
55
56 4
            $datatype = $field->type;
57
58
59 4
            if (!$type_map->offsetExists($datatype)) {
60
                throw new UnsupportedDatatypeException("Datatype '$datatype' not yet supported by " . __CLASS__);
61
            }
62
63
64 4
            $datatype = $type_map->offsetGet($datatype);
65
66 4
            $column = Column\Type::createColumnDefinition($datatype['type'], $name, $tableName, $schemaName);
67
68 4
            $column->setAlias($field->name);
69 4
            $column->setTableAlias($field->table);
70 4
            $column->setCatalog($field->catalog);
71 4
            $column->setOrdinalPosition($idx + 1);
72 4
            $column->setDataType($datatype['type']);
73 4
            $column->setIsNullable(!($field->flags & MYSQLI_NOT_NULL_FLAG) > 0 && ($field->orgtable != ''));
74 4
            $column->setIsPrimary(($field->flags & MYSQLI_PRI_KEY_FLAG) > 0);
75
76 4
            $column->setColumnDefault($field->def);
77
78 4
            if (($field->flags & MYSQLI_SET_FLAG) > 0) {
79 1
                $column->setNativeDataType('SET');
80 4
            } elseif (($field->flags & MYSQLI_ENUM_FLAG) > 0) {
81 2
                $column->setNativeDataType('ENUM');
82 2
            } else {
83 4
                $column->setNativeDataType($datatype['native']);
84
            }
85
86 4
            if ($field->table == '') {
87
                $column->setIsGroup(($field->flags & MYSQLI_GROUP_FLAG) > 0);
88
            }
89
90 4
            if ($column instanceof Column\Definition\NumericColumnInterface) {
91 4
                $column->setNumericUnsigned(($field->flags & MYSQLI_UNSIGNED_FLAG) > 0);
92 4
            }
93
94 4
            if ($column instanceof Column\Definition\IntegerColumn) {
95 4
                $column->setIsAutoIncrement(($field->flags & MYSQLI_AUTO_INCREMENT_FLAG) > 0);
96 4
            }
97
98 4
            if ($column instanceof Column\Definition\DecimalColumn) {
99
                // salary DECIMAL(5,2)
100
                // In this example, 5 is the precision and 2 is the scale.
101
                // Standard SQL requires that DECIMAL(5,2) be able to store any value
102
                // with five digits and two decimals, so values that can be stored in
103
                // the salary column range from -999.99 to 999.99.
104
105 1
                $column->setNumericScale($field->length - $field->decimals + 1);
106 1
                $column->setNumericPrecision($field->decimals);
107 1
            }
108
109 4
            if ($column instanceof Column\Definition\StringColumn) {
110 3
                $column->setCharacterMaximumLength($field->length);
111 3
            }
112
113 4
            if ($column instanceof Column\Definition\BlobColumn) {
114 1
                $column->setCharacterOctetLength($field->length);
115 1
            }
116
117 4
            $alias = $column->getAlias();
118
119 4
            if ($metadata->offsetExists($alias)) {
120 1
                $prev_column = $metadata->offsetGet($alias);
121 1
                $prev_def = $prev_column->toArray();
122 1
                $curr_def = $column->toArray();
123 1
                if ($prev_def['dataType'] != $curr_def['dataType'] || $prev_def['nativeDataType'] != $curr_def['nativeDataType']) {
124 1
                    throw new Exception\AmbiguousColumnException("Cannot get column metadata, non unique column found '$alias' in query with different definitions.");
125
                }
126
127
                // If the the previous definition, was a prev_def
128
                if ($prev_def['isPrimary']) {
129
                    $column = $prev_column;
130
                }
131
            }
132
133
134 4
            $metadata->offsetSet($alias, $column);
135 4
        }
136
137 3
        return $metadata;
138
    }
139
140
    /**
141
     *
142
     * @param string $sql
143
     * @throws Exception\ConnectionException
144
     * @return array
145
     */
146 6
    protected function readFields($sql)
147
    {
148 6
        if (trim($sql) == '') {
149 1
            throw new Exception\EmptyQueryException(__METHOD__ . ": Error cannot handle empty queries");
150
        }
151
152 5
        $sql = $this->makeQueryEmpty($sql);
153
154 5
        $stmt = $this->mysqli->prepare($sql);
155
156 5
        if (!$stmt) {
157 1
            $message = $this->mysqli->error;
158 1
            throw new Exception\InvalidQueryException(__METHOD__ . ": Error sql is not correct : $message");
159
        }
160 4
        $stmt->execute();
161
162 4
        $result = $stmt->result_metadata();
163 4
        $metaFields = $result->fetch_fields();
164 4
        $result->close();
165 4
        $stmt->close();
166 4
        return $metaFields;
167
    }
168
169
    /**
170
     *
171
     * @return ArrayObject
172
     */
173 4
    protected function getDatatypeMapping()
174
    {
175
        // ALL the following fields are not supported yet
176
        // Maybe todo in a later release or choose to map them to approximative
177
        // types (i.e. MYSQLI_YEAR could be a integer) ?
178
        /*
179
          MYSQLI_TYPE_NULL
180
          MYSQLI_TYPE_YEAR
181
          MYSQLI_TYPE_ENUM
182
          MYSQLI_TYPE_SET
183
          MYSQLI_TYPE_GEOMETRY
184
         */
185
186 4
        $mapping = new ArrayObject([
187 4
            MYSQLI_TYPE_STRING => ['type' => Column\Type::TYPE_STRING, 'native' => 'VARCHAR'],
188 4
            MYSQLI_TYPE_CHAR => ['type' => Column\Type::TYPE_STRING, 'native' => 'CHAR'],
189 4
            MYSQLI_TYPE_VAR_STRING => ['type' => Column\Type::TYPE_STRING, 'native' => 'VARCHAR'],
190 4
            MYSQLI_TYPE_ENUM => ['type' => Column\Type::TYPE_STRING, 'native' => 'ENUM'],
191
            // BLOBS ARE CURRENTLY SENT AS TEXT
192
            // I DIDN'T FIND THE WAY TO MAKE THE DIFFERENCE !!!
193 4
            MYSQLI_TYPE_TINY_BLOB => ['type' => Column\Type::TYPE_BLOB, 'native' => 'TINYBLOB'],
194 4
            MYSQLI_TYPE_MEDIUM_BLOB => ['type' => Column\Type::TYPE_BLOB, 'native' => 'MEDIUMBLOB'],
195 4
            MYSQLI_TYPE_LONG_BLOB => ['type' => Column\Type::TYPE_BLOB, 'native' => 'LONGBLOB'],
196 4
            MYSQLI_TYPE_BLOB => ['type' => Column\Type::TYPE_BLOB, 'native' => 'BLOB'],
197
            // integer
198 4
            MYSQLI_TYPE_TINY => ['type' => Column\Type::TYPE_INTEGER, 'native' => 'TINYINT'],
199 4
            MYSQLI_TYPE_YEAR => ['type' => Column\Type::TYPE_INTEGER, 'native' => 'YEAR'],
200 4
            MYSQLI_TYPE_SHORT => ['type' => Column\Type::TYPE_INTEGER, 'native' => 'SMALLINT'],
201 4
            MYSQLI_TYPE_INT24 => ['type' => Column\Type::TYPE_INTEGER, 'native' => 'MEDIUMINT'],
202 4
            MYSQLI_TYPE_LONG => ['type' => Column\Type::TYPE_INTEGER, 'native' => 'INTEGER'],
203 4
            MYSQLI_TYPE_LONGLONG => ['type' => Column\Type::TYPE_INTEGER, 'native' => 'BIGINT'],
204
            // timestamps
205 4
            MYSQLI_TYPE_TIMESTAMP => ['type' => Column\Type::TYPE_DATETIME, 'native' => 'TIMESTAMP'],
206 4
            MYSQLI_TYPE_DATETIME => ['type' => Column\Type::TYPE_DATETIME, 'native' => 'DATETIME'],
207
            // dates
208 4
            MYSQLI_TYPE_DATE => ['type' => Column\Type::TYPE_DATE, 'native' => 'DATE'],
209 4
            MYSQLI_TYPE_NEWDATE => ['type' => Column\Type::TYPE_DATE, 'native' => 'DATE'],
210
            // time
211 4
            MYSQLI_TYPE_TIME => ['type' => Column\Type::TYPE_TIME, 'native' => 'TIME'],
212
            // decimals
213 4
            MYSQLI_TYPE_DECIMAL => ['type' => Column\Type::TYPE_DECIMAL, 'native' => 'DECIMAL'],
214 4
            MYSQLI_TYPE_NEWDECIMAL => ['type' => Column\Type::TYPE_DECIMAL, 'native' => 'DECIMAL'],
215 4
            MYSQLI_TYPE_FLOAT => ['type' => Column\Type::TYPE_FLOAT, 'native' => 'FLOAT'],
216 4
            MYSQLI_TYPE_DOUBLE => ['type' => Column\Type::TYPE_FLOAT, 'native' => 'DOUBLE'],
217 4
            MYSQLI_TYPE_BIT => ['type' => Column\Type::TYPE_BIT, 'native' => 'BIT'],
218
            //MYSQLI_TYPE_BOOLEAN => array('type' => Column\Type::TYPE_BOOLEAN, 'native' => 'BOOLEAN'),
219 4
            MYSQLI_TYPE_GEOMETRY => ['type' => Column\Type::TYPE_SPATIAL_GEOMETRY, 'native' => null],
220 4
        ]);
221
222
223
        // enum
224
225 4
        return $mapping;
226
    }
227
}
228