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

PdoMysqlMetadataReader::readFields()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 27
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 27
c 0
b 0
f 0
ccs 18
cts 18
cp 1
rs 8.5806
cc 4
eloc 16
nc 4
nop 1
crap 4
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\Metadata\Reader\Mapping\PdoMysqlMapping;
9
use PDO;
10
11
class PdoMysqlMetadataReader extends AbstractMetadataReader
12
{
13
    /**
14
     * @var PDO
15
     */
16
    protected $pdo;
17
18
    /**
19
     * @var array
20
     */
21
    protected static $metadata_cache = [];
22
23
    /**
24
     * @param PDO $pdo
25
     *
26
     * @throws Exception\UnsupportedFeatureException
27
     * @throws Exception\UnsupportedDriverException
28
     */
29 15
    public function __construct(PDO $pdo)
30
    {
31 15
        $driver = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
32 15
        if (strtolower($driver) !== 'mysql') {
33 1
            throw new Exception\UnsupportedDriverException(__CLASS__ . " supports only pdo_mysql driver, '$driver' given.");
34
        }
35 15
        $this->pdo = $pdo;
36 15
    }
37
38
    /**
39
     * {@inheritdoc}
40
     *
41
     * @throws \Soluble\Metadata\Exception\ConnectionException
42
     * @throws \Soluble\Metadata\Exception\EmptyQueryException
43
     * @throws \Soluble\Metadata\Exception\InvalidQueryException
44
     */
45 8
    protected function readColumnsMetadata($sql)
46
    {
47 8
        $metadata = new ColumnsMetadata();
48 8
        $fields = $this->readFields($sql);
49
50 5
        $type_map = PdoMysqlMapping::getDatatypeMapping();
51
52 5
        foreach ($fields as $idx => $field) {
53 5
            $name = $field['name'];
54 5
            $tableName = $field['table'];
55
56 5
            $type = strtoupper($field['native_type']);
57
58 5
            if (!$type_map->offsetExists($type)) {
59
                $msg = "Cannot get type for field '$name'. Mapping for native type [$type] cannot be resolved into a valid type for driver: " . __CLASS__;
60
                throw new Exception\UnsupportedTypeException($msg);
61
            }
62
63 5
            $datatype = $type_map->offsetGet($type);
64
65 5
            $column = Column\Type::createColumnDefinition($datatype['type'], $name, $tableName, $schemaName = null);
66 5
            $alias = $field['name'];
67
68 5
            $column->setAlias($alias);
69 5
            $column->setTableAlias($field['table']);
70
            //$column->setCatalog($field->catalog);
71 5
            $column->setOrdinalPosition($idx + 1);
72 5
            $column->setDataType($datatype['type']);
73 5
            $column->setIsNullable(!in_array('not_null', $field['flags'], true));
74 5
            $column->setIsPrimary(in_array('primary_key', $field['flags'], true));
75
            //$column->setColumnDefault($field->def);
76 5
            $column->setNativeDataType($datatype['native']);
77
78
            /*
79
              if ($column instanceof Column\Definition\NumericColumnInterface) {
80
              $column->setNumericUnsigned(($field->flags & MYSQLI_UNSIGNED_FLAG) > 0);
81
              }
82
83
              if ($column instanceof Column\Definition\IntegerColumn) {
84
              $column->setIsAutoIncrement(($field->flags & MYSQLI_AUTO_INCREMENT_FLAG) > 0);
85
              }
86
             */
87 5
            if ($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
94 3
                $column->setNumericUnsigned(false);
95 3
                $column->setNumericPrecision($field['precision']);
96 3
                $column->setNumericScale($field['len'] - $field['precision'] + 1);
97 3
            }
98
99 5
            if ($column instanceof Column\Definition\StringColumn) {
100 5
                $column->setCharacterMaximumLength($field['len']);
101 5
            }
102
103 5
            if ($column instanceof Column\Definition\BlobColumn) {
104 1
                $column->setCharacterOctetLength($field['len']);
105 1
            }
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
     * Read fields from pdo source.
130
     *
131
     * @throws Exception\ConnectionException
132
     *
133
     * @param string $sql
134
     *
135
     * @return array
136
     *
137
     * @throws \Soluble\Metadata\Exception\EmptyQueryException
138
     * @throws \Soluble\Metadata\Exception\InvalidQueryException
139
     */
140 8
    protected function readFields($sql)
141
    {
142 8
        if (trim($sql) === '') {
143 1
            throw new Exception\EmptyQueryException('Cannot read fields for an empty query');
144
        }
145
146 7
        $sql = $this->getEmptiedQuery($sql);
147
148 7
        $stmt = $this->pdo->prepare($sql);
149 7
        if ($stmt->execute() !== true) {
150 2
            throw new Exception\InvalidQueryException(
151 2
                sprintf('Invalid query: %s', $sql)
152 2
            );
153
        }
154
155 5
        $column_count = $stmt->columnCount();
156 5
        $metaFields = [];
157 5
        for ($i = 0; $i < $column_count; ++$i) {
158 5
            $meta = $stmt->getColumnMeta($i);
159 5
            $metaFields[$i] = $meta;
160 5
        }
161
162 5
        $stmt->closeCursor();
163 5
        unset($stmt);
164
165 5
        return $metaFields;
166
    }
167
}
168