Completed
Push — master ( 5be6f2...0a5d5e )
by Sébastien
03:56
created

MysqliMetadataReader::readFields()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 24
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 3

Importance

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