1: <?php
2:
3: 4: 5: 6:
7:
8: namespace Nette\Database\Drivers;
9:
10: use Nette;
11:
12:
13: 14: 15:
16: class MySqlDriver implements Nette\Database\ISupplementalDriver
17: {
18: use Nette\SmartObject;
19:
20: const ERROR_ACCESS_DENIED = 1045;
21:
22: const ERROR_DUPLICATE_ENTRY = 1062;
23:
24: const ERROR_DATA_TRUNCATED = 1265;
25:
26:
27: private $connection;
28:
29:
30: 31: 32: 33: 34:
35: public function __construct(Nette\Database\Connection $connection, array $options)
36: {
37: $this->connection = $connection;
38: $charset = isset($options['charset'])
39: ? $options['charset']
40: : (version_compare($connection->getPdo()->getAttribute(\PDO::ATTR_SERVER_VERSION), '5.5.3', '>=') ? 'utf8mb4' : 'utf8');
41: if ($charset) {
42: $connection->query("SET NAMES '$charset'");
43: }
44: if (isset($options['sqlmode'])) {
45: $connection->query("SET sql_mode='$options[sqlmode]'");
46: }
47: }
48:
49:
50: 51: 52:
53: public function convertException(\PDOException $e)
54: {
55: $code = isset($e->errorInfo[1]) ? $e->errorInfo[1] : null;
56: if (in_array($code, [1216, 1217, 1451, 1452, 1701], true)) {
57: return Nette\Database\ForeignKeyConstraintViolationException::from($e);
58:
59: } elseif (in_array($code, [1062, 1557, 1569, 1586], true)) {
60: return Nette\Database\UniqueConstraintViolationException::from($e);
61:
62: } elseif ($code >= 2001 && $code <= 2028) {
63: return Nette\Database\ConnectionException::from($e);
64:
65: } elseif (in_array($code, [1048, 1121, 1138, 1171, 1252, 1263, 1566], true)) {
66: return Nette\Database\NotNullConstraintViolationException::from($e);
67:
68: } else {
69: return Nette\Database\DriverException::from($e);
70: }
71: }
72:
73:
74:
75:
76:
77: 78: 79:
80: public function delimite($name)
81: {
82:
83: return '`' . str_replace('`', '``', $name) . '`';
84: }
85:
86:
87: 88: 89:
90: public function formatBool($value)
91: {
92: return $value ? '1' : '0';
93: }
94:
95:
96: 97: 98:
99: public function formatDateTime( $value)
100: {
101: return $value->format("'Y-m-d H:i:s'");
102: }
103:
104:
105: 106: 107:
108: public function formatDateInterval(\DateInterval $value)
109: {
110: return $value->format("'%r%h:%I:%S'");
111: }
112:
113:
114: 115: 116:
117: public function formatLike($value, $pos)
118: {
119: $value = str_replace('\\', '\\\\', $value);
120: $value = addcslashes(substr($this->connection->quote($value), 1, -1), '%_');
121: return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
122: }
123:
124:
125: 126: 127:
128: public function applyLimit(&$sql, $limit, $offset)
129: {
130: if ($limit < 0 || $offset < 0) {
131: throw new Nette\InvalidArgumentException('Negative offset or limit.');
132:
133: } elseif ($limit !== null || $offset) {
134:
135: $sql .= ' LIMIT ' . ($limit === null ? '18446744073709551615' : (int) $limit)
136: . ($offset ? ' OFFSET ' . (int) $offset : '');
137: }
138: }
139:
140:
141: 142: 143:
144: public function normalizeRow($row)
145: {
146: return $row;
147: }
148:
149:
150:
151:
152:
153: 154: 155:
156: public function getTables()
157: {
158: $tables = [];
159: foreach ($this->connection->query('SHOW FULL TABLES') as $row) {
160: $tables[] = [
161: 'name' => $row[0],
162: 'view' => isset($row[1]) && $row[1] === 'VIEW',
163: ];
164: }
165: return $tables;
166: }
167:
168:
169: 170: 171:
172: public function getColumns($table)
173: {
174: $columns = [];
175: foreach ($this->connection->query('SHOW FULL COLUMNS FROM ' . $this->delimite($table)) as $row) {
176: $type = explode('(', $row['Type']);
177: $columns[] = [
178: 'name' => $row['Field'],
179: 'table' => $table,
180: 'nativetype' => strtoupper($type[0]),
181: 'size' => isset($type[1]) ? (int) $type[1] : null,
182: 'unsigned' => (bool) strstr($row['Type'], 'unsigned'),
183: 'nullable' => $row['Null'] === 'YES',
184: 'default' => $row['Default'],
185: 'autoincrement' => $row['Extra'] === 'auto_increment',
186: 'primary' => $row['Key'] === 'PRI',
187: 'vendor' => (array) $row,
188: ];
189: }
190: return $columns;
191: }
192:
193:
194: 195: 196:
197: public function getIndexes($table)
198: {
199: $indexes = [];
200: foreach ($this->connection->query('SHOW INDEX FROM ' . $this->delimite($table)) as $row) {
201: $indexes[$row['Key_name']]['name'] = $row['Key_name'];
202: $indexes[$row['Key_name']]['unique'] = !$row['Non_unique'];
203: $indexes[$row['Key_name']]['primary'] = $row['Key_name'] === 'PRIMARY';
204: $indexes[$row['Key_name']]['columns'][$row['Seq_in_index'] - 1] = $row['Column_name'];
205: }
206: return array_values($indexes);
207: }
208:
209:
210: 211: 212:
213: public function getForeignKeys($table)
214: {
215: $keys = [];
216: $query = 'SELECT CONSTRAINT_NAME, COLUMN_NAME, REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME FROM information_schema.KEY_COLUMN_USAGE '
217: . 'WHERE TABLE_SCHEMA = DATABASE() AND REFERENCED_TABLE_NAME IS NOT NULL AND TABLE_NAME = ' . $this->connection->quote($table);
218:
219: foreach ($this->connection->query($query) as $id => $row) {
220: $keys[$id]['name'] = $row['CONSTRAINT_NAME'];
221: $keys[$id]['local'] = $row['COLUMN_NAME'];
222: $keys[$id]['table'] = $row['REFERENCED_TABLE_NAME'];
223: $keys[$id]['foreign'] = $row['REFERENCED_COLUMN_NAME'];
224: }
225:
226: return array_values($keys);
227: }
228:
229:
230: 231: 232:
233: public function getColumnTypes(\PDOStatement $statement)
234: {
235: $types = [];
236: $count = $statement->columnCount();
237: for ($col = 0; $col < $count; $col++) {
238: $meta = $statement->getColumnMeta($col);
239: if (isset($meta['native_type'])) {
240: $types[$meta['name']] = $type = Nette\Database\Helpers::detectType($meta['native_type']);
241: if ($type === Nette\Database\IStructure::FIELD_TIME) {
242: $types[$meta['name']] = Nette\Database\IStructure::FIELD_TIME_INTERVAL;
243: }
244: }
245: }
246: return $types;
247: }
248:
249:
250: 251: 252: 253:
254: public function isSupported($item)
255: {
256:
257:
258:
259:
260: return $item === self::SUPPORT_SELECT_UNGROUPED_COLUMNS || $item === self::SUPPORT_MULTI_COLUMN_AS_OR_COND;
261: }
262: }
263: