Completed
Push — master ( 0e6239...519a4c )
by Sébastien
02:36
created

MysqliMetadataReader::readColumnsMetadata()   F

Complexity

Conditions 17
Paths 1539

Size

Total Lines 95
Code Lines 51

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 53
CRAP Score 17.4593

Importance

Changes 3
Bugs 0 Features 1
Metric Value
c 3
b 0
f 1
dl 0
loc 95
ccs 53
cts 60
cp 0.8833
rs 2
cc 17
eloc 51
nc 1539
nop 1
crap 17.4593

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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