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