Completed
Push — master ( 10bb60...177540 )
by Sébastien
03:45
created

PdoMysqlMetadataReader::setupCapabilities()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 14
ccs 5
cts 5
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 5
nc 2
nop 0
crap 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Soluble\Metadata\Reader;
6
7
use Soluble\Metadata\ColumnsMetadata;
8
use Soluble\Metadata\Exception;
9
use Soluble\Datatype\Column;
10
use Soluble\Metadata\Reader\Capability\ReaderCapabilityInterface;
11
use Soluble\Metadata\Reader\Mapping\PdoMysqlMapping;
12
use PDO;
13
14
class PdoMysqlMetadataReader extends AbstractMetadataReader
15
{
16
    /**
17
     * @var PDO
18
     */
19
    protected $pdo;
20
21
    /**
22
     * @var array
23
     */
24
    protected static $metadata_cache = [];
25
26
    /**
27
     * @param PDO $pdo
28
     *
29
     * @throws Exception\UnsupportedFeatureException
30
     * @throws Exception\UnsupportedDriverException
31
     */
32 12
    public function __construct(PDO $pdo)
33
    {
34 12
        $driver = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
35 12
        if (strtolower($driver) !== 'mysql') {
36 1
            throw new Exception\UnsupportedDriverException(__CLASS__ . " supports only pdo_mysql driver, '$driver' given.");
37
        }
38 12
        $this->pdo = $pdo;
39 12
        $this->setupCapabilities();
40 12
    }
41
42 12
    protected function setupCapabilities(): void
43
    {
44
        $caps = [
45 12
            ReaderCapabilityInterface::DETECT_PRIMARY_KEY,
46
            //ReaderCapabilityInterface::DETECT_GROUP_FUNCTION,
47
            //ReaderCapabilityInterface::DETECT_NUMERIC_UNSIGNED,
48
            //ReaderCapabilityInterface::DETECT_AUTOINCREMENT,
49
            //ReaderCapabilityInterface::DETECT_COLUMN_DEFAULT,
50
            //ReaderCapabilityInterface::DETECT_CHAR_MAX_LENGTH,
51
        ];
52 12
        foreach ($caps as $cap) {
53 12
            $this->addCapability($cap);
54
        }
55 12
    }
56
57
    /**
58
     * {@inheritdoc}
59
     *
60
     * @throws \Soluble\Metadata\Exception\ConnectionException
61
     * @throws \Soluble\Metadata\Exception\EmptyQueryException
62
     * @throws \Soluble\Metadata\Exception\InvalidQueryException
63
     */
64 7
    protected function readColumnsMetadata(string $sql): ColumnsMetadata
65
    {
66 7
        $metadata = new ColumnsMetadata();
67 7
        $fields = $this->readFields($sql);
68
69 4
        $type_map = PdoMysqlMapping::getDatatypeMapping();
70
71 4
        foreach ($fields as $idx => $field) {
72 4
            $name = $field['name'];
73 4
            $tableName = $field['table'];
74
75 4
            $type = strtoupper($field['native_type']);
76
77 4
            if (!$type_map->offsetExists($type)) {
78
                $msg = "Cannot get type for field '$name'. Mapping for native type [$type] cannot be resolved into a valid type for driver: " . __CLASS__;
79
                throw new Exception\UnsupportedTypeException($msg);
80
            }
81
82 4
            $datatype = $type_map->offsetGet($type);
83
84 4
            $column = Column\Type::createColumnDefinition($datatype['type'], $name, $tableName, $schemaName = null);
85 4
            $alias = $field['name'];
86
87 4
            $column->setAlias($alias);
88 4
            $column->setTableAlias($field['table']);
89
            //$column->setCatalog($field->catalog);
90 4
            $column->setOrdinalPosition($idx + 1);
91 4
            $column->setDataType($datatype['type']);
92 4
            $column->setIsNullable(!in_array('not_null', $field['flags'], true));
93 4
            $column->setIsPrimary(in_array('primary_key', $field['flags'], true));
94
            //$column->setColumnDefault($field->def);
95 4
            $column->setNativeDataType($datatype['native']);
96
97
            /*
98
              if ($column instanceof Column\Definition\NumericColumnInterface) {
99
              $column->setNumericUnsigned(($field->flags & MYSQLI_UNSIGNED_FLAG) > 0);
100
              }
101
*/
102 4
            if ($column instanceof Column\Definition\IntegerColumn) {
103
                // PDO does not support detection of autoincrement.
104
                // Always false
105
106 4
                $column->setIsAutoIncrement(false);
107
            }
108
109 4
            if ($column instanceof Column\Definition\DecimalColumn) {
110
                // salary DECIMAL(5,2)
111
                // In this example, 5 is the precision and 2 is the scale.
112
                // Standard SQL requires that DECIMAL(5,2) be able to store any value
113
                // with five digits and two decimals, so values that can be stored in
114
                // the salary column range from -999.99 to 999.99.
115
116 3
                $column->setNumericUnsigned(false);
117 3
                $column->setNumericPrecision($field['precision']);
118 3
                $column->setNumericScale($field['len'] - $field['precision'] + 1);
119
            }
120
121 4
            if ($column instanceof Column\Definition\StringColumn) {
122 4
                $column->setCharacterMaximumLength($field['len']);
123
            }
124
125 4
            if ($column instanceof Column\Definition\BlobColumn) {
126 1
                $column->setCharacterOctetLength($field['len']);
127
            }
128
129 4
            if ($metadata->offsetExists($alias)) {
130 1
                $prev_column = $metadata->offsetGet($alias);
131 1
                $prev_def = $prev_column->toArray();
132 1
                $curr_def = $column->toArray();
133 1
                if ($prev_def['dataType'] !== $curr_def['dataType'] || $prev_def['nativeDataType'] !== $curr_def['nativeDataType']) {
134 1
                    throw new Exception\AmbiguousColumnException("Cannot get column metadata, non unique column found '$alias' in query with different definitions.");
135
                }
136
137
                // If the the previous definition, was a prev_def
138
                /*
139
                if ($prev_def['isPrimary']) {
140
                    $column = $prev_column;
141
                }
142
                */
143
            }
144 4
            $metadata->offsetSet($alias, $column);
145
        }
146
147 3
        return $metadata;
148
    }
149
150
    /**
151
     * Read fields from pdo source.
152
     *
153
     * @throws Exception\ConnectionException
154
     * @throws \Soluble\Metadata\Exception\EmptyQueryException
155
     * @throws \Soluble\Metadata\Exception\InvalidQueryException
156
     */
157 7
    protected function readFields(string $sql): array
158
    {
159 7
        if (trim($sql) === '') {
160 1
            throw new Exception\EmptyQueryException('Cannot read fields for an empty query');
161
        }
162
163 6
        $sql = $this->getEmptiedQuery($sql);
164
165 6
        $stmt = $this->pdo->prepare($sql);
166 6
        if ($stmt->execute() !== true) {
167 2
            throw new Exception\InvalidQueryException(
168 2
                sprintf('Invalid query: %s', $sql)
169
            );
170
        }
171
172 4
        $column_count = $stmt->columnCount();
173 4
        $metaFields = [];
174 4
        for ($i = 0; $i < $column_count; ++$i) {
175 4
            $meta = $stmt->getColumnMeta($i);
176 4
            $metaFields[$i] = $meta;
177
        }
178
179 4
        $stmt->closeCursor();
180 4
        unset($stmt);
181
182 4
        return $metaFields;
183
    }
184
}
185