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

PdoMysqlMetadataReader::readFields()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 21
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 3

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 21
ccs 15
cts 15
cp 1
rs 9.3142
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\Db\Metadata\Column\Exception\UnsupportedDatatypeException;
9
use ArrayObject;
10
use PDO;
11
12
class PdoMysqlMetadataReader extends AbstractMetadataReader
13
{
14
15
    /**
16
     * @var PDO
17
     */
18
    protected $pdo;
19
20
    /**
21
     *
22
     * @var boolean
23
     */
24
    protected $cache_active = true;
25
26
    /**
27
     *
28
     * @var Array
29
     */
30
    protected static $metadata_cache = [];
31
32
    /**
33
     *
34
     * @param PDO $pdo
35
     * @throws Exception\UnsupportedFeatureException
36
     * @throws Exception\UnsupportedDriverException
37
     */
38 6
    public function __construct(PDO $pdo)
39
    {
40 6
        $driver = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
41 6
        if (strtolower($driver) != 'mysql') {
42 1
            throw new Exception\UnsupportedDriverException(__CLASS__ . " supports only pdo_mysql driver, '$driver' given.");
43
        }
44 6
        $this->pdo = $pdo;
45 6
    }
46
47
    /**
48
     *
49
     * {@inheritdoc}
50
     */
51 4
    protected function readColumnsMetadata($sql)
52
    {
53 4
        $metadata = new ColumnsMetadata();
54 4
        $fields = $this->readFields($sql);
55
56 3
        $type_map = $this->getDatatypeMapping();
57
58
59 3
        foreach ($fields as $idx => $field) {
60 3
            $name = $field['name'];
61 3
            $tableName = $field['table'];
62
63 3
            $datatype = strtoupper($field['native_type']);
64
65
66
            //@codeCoverageIgnoreStart
67
            if (!$type_map->offsetExists($datatype)) {
68
                throw new UnsupportedDatatypeException("Datatype '$datatype' not yet supported by " . __CLASS__);
69
            }
70
            //@codeCoverageIgnoreEnd
71
72 3
            $datatype = $type_map->offsetGet($datatype);
73
74 3
            $column = Column\Type::createColumnDefinition($datatype['type'], $name, $tableName, $schemaName = null);
75 3
            $alias = $field['name'];
76
77
78 3
            $column->setAlias($alias);
79 3
            $column->setTableAlias($field['table']);
80
            //$column->setCatalog($field->catalog);
81 3
            $column->setOrdinalPosition($idx + 1);
82 3
            $column->setDataType($datatype['type']);
83 3
            $column->setIsNullable(!in_array('not_null', $field['flags']));
84 3
            $column->setIsPrimary(in_array('primary_key', $field['flags']));
85
            //$column->setColumnDefault($field->def);
86 3
            $column->setNativeDataType($datatype['native']);
87
88
            /*
89
              if ($column instanceof Column\Definition\NumericColumnInterface) {
90
              $column->setNumericUnsigned(($field->flags & MYSQLI_UNSIGNED_FLAG) > 0);
91
              }
92
93
              if ($column instanceof Column\Definition\IntegerColumn) {
94
              $column->setIsAutoIncrement(($field->flags & MYSQLI_AUTO_INCREMENT_FLAG) > 0);
95
              }
96
             */
97 3
            if ($column instanceof Column\Definition\DecimalColumn) {
98
                // salary DECIMAL(5,2)
99
                // In this example, 5 is the precision and 2 is the scale.
100
                // Standard SQL requires that DECIMAL(5,2) be able to store any value
101
                // with five digits and two decimals, so values that can be stored in
102
                // the salary column range from -999.99 to 999.99.
103
104 2
                $column->setNumericUnsigned(false);
105 2
                $column->setNumericPrecision($field['precision']);
106 2
                $column->setNumericScale($field['len'] - $field['precision'] + 1);
107 2
            }
108
109 3
            if ($column instanceof Column\Definition\StringColumn) {
110 3
                $column->setCharacterMaximumLength($field['len']);
111 3
            }
112
113 3
            if ($column instanceof Column\Definition\BlobColumn) {
114 2
                $column->setCharacterOctetLength($field['len']);
115 2
            }
116
117 3
            if ($metadata->offsetExists($alias)) {
118 1
                $prev_column = $metadata->offsetGet($alias);
119 1
                $prev_def = $prev_column->toArray();
120 1
                $curr_def = $column->toArray();
121 1
                if ($prev_def['dataType'] != $curr_def['dataType'] || $prev_def['nativeDataType'] != $curr_def['nativeDataType']) {
122 1
                    throw new Exception\AmbiguousColumnException("Cannot get column metadata, non unique column found '$alias' in query with different definitions.");
123
                }
124
125
                // If the the previous definition, was a prev_def
126
                if ($prev_def['isPrimary']) {
127
                    $column = $prev_column;
128
                }
129
            }
130 3
            $metadata->offsetSet($alias, $column);
131 3
        }
132
133 2
        return $metadata;
134
    }
135
136
    /**
137
     * Read fields from pdo source
138
     *
139
     * @throws Exception\ConnectionException
140
     * @param string $sql
141
     * @return array
142
     */
143 4
    protected function readFields($sql)
144
    {
145 4
        if (trim($sql) == '') {
146 1
            throw new Exception\EmptyQueryException();
147
        }
148
149 3
        $sql = $this->makeQueryEmpty($sql);
150
151 3
        $stmt = $this->pdo->prepare($sql);
152 3
        $stmt->execute();
153 3
        $column_count = $stmt->columnCount();
154 3
        $metaFields = [];
155 3
        for ($i = 0; $i < $column_count; $i++) {
156 3
            $meta = $stmt->getColumnMeta($i);
157 3
            $metaFields[$i] = $meta;
158 3
        }
159
160 3
        $stmt->closeCursor();
161 3
        unset($stmt);
162 3
        return $metaFields;
163
    }
164
165
    /**
166
     *
167
     * @return ArrayObject
168
     */
169 3
    protected function getDatatypeMapping()
170
    {
171 3
        $mapping = new ArrayObject([
172 3
            'STRING' => ['type' => Column\Type::TYPE_STRING, 'native' => 'CHAR'],
173 3
            'VAR_STRING' => ['type' => Column\Type::TYPE_STRING, 'native' => 'VARCHAR'],
174
            // BLOBS ARE CURRENTLY SENT AS TEXT
175
            // I DIDN'T FIND THE WAY TO MAKE THE DIFFERENCE !!!
176 3
            'BLOB' => ['type' => Column\Type::TYPE_BLOB, 'native' => 'BLOB'],
177
            // integer
178 3
            'TINY' => ['type' => Column\Type::TYPE_INTEGER, 'native' => 'TINYINT'],
179 3
            'SHORT' => ['type' => Column\Type::TYPE_INTEGER, 'native' => 'SMALLINT'],
180 3
            'INT24' => ['type' => Column\Type::TYPE_INTEGER, 'native' => 'MEDIUMINT'],
181 3
            'LONG' => ['type' => Column\Type::TYPE_INTEGER, 'native' => 'INTEGER'],
182 3
            'LONGLONG' => ['type' => Column\Type::TYPE_INTEGER, 'native' => 'BIGINT'],
183
            // timestamps
184 3
            'TIMESTAMP' => ['type' => Column\Type::TYPE_DATETIME, 'native' => 'TIMESTAMP'],
185 3
            'DATETIME' => ['type' => Column\Type::TYPE_DATETIME, 'native' => 'DATETIME'],
186
            // dates
187 3
            'DATE' => ['type' => Column\Type::TYPE_DATE, 'native' => 'DATE'],
188 3
            'NEWDATE' => ['type' => Column\Type::TYPE_DATE, 'native' => 'DATE'],
189
            // time
190 3
            'TIME' => ['type' => Column\Type::TYPE_TIME, 'native' => 'TIME'],
191
            // decimals
192 3
            'DECIMAL' => ['type' => Column\Type::TYPE_DECIMAL, 'native' => 'DECIMAL'],
193 3
            'NEWDECIMAL' => ['type' => Column\Type::TYPE_DECIMAL, 'native' => 'DECIMAL'],
194 3
            'FLOAT' => ['type' => Column\Type::TYPE_FLOAT, 'native' => 'FLOAT'],
195 3
            'DOUBLE' => ['type' => Column\Type::TYPE_FLOAT, 'native' => 'DOUBLE'],
196
            // boolean
197 3
            'BIT' => ['type' => Column\Type::TYPE_BIT, 'native' => 'BIT'],
198 3
            'BOOLEAN' => ['type' => Column\Type::TYPE_BOOLEAN, 'native' => 'BOOLEAN'],
199 3
            'GEOMETRY' => ['type' => Column\Type::TYPE_SPATIAL_GEOMETRY, 'native' => null]
200 3
        ]);
201
202
203
        // enum
204
205 3
        return $mapping;
206
    }
207
}
208