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