1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18:
19:
20: App::uses('DboSource', 'Model/Datasource');
21:
22: 23: 24: 25: 26: 27: 28:
29: class Mysql extends DboSource {
30:
31: 32: 33: 34: 35:
36: public $description = "MySQL DBO Driver";
37:
38: 39: 40: 41: 42:
43: protected $_baseConfig = array(
44: 'persistent' => true,
45: 'host' => 'localhost',
46: 'login' => 'root',
47: 'password' => '',
48: 'database' => 'cake',
49: 'port' => '3306'
50: );
51:
52: 53: 54: 55: 56:
57: protected $_connection = null;
58:
59: 60: 61: 62: 63:
64: public $startQuote = "`";
65:
66: 67: 68: 69: 70:
71: public $endQuote = "`";
72:
73: 74: 75: 76: 77:
78: protected $_useAlias = true;
79:
80: 81: 82: 83: 84:
85: public $fieldParameters = array(
86: 'charset' => array('value' => 'CHARACTER SET', 'quote' => false, 'join' => ' ', 'column' => false, 'position' => 'beforeDefault'),
87: 'collate' => array('value' => 'COLLATE', 'quote' => false, 'join' => ' ', 'column' => 'Collation', 'position' => 'beforeDefault'),
88: 'comment' => array('value' => 'COMMENT', 'quote' => true, 'join' => ' ', 'column' => 'Comment', 'position' => 'afterDefault')
89: );
90:
91: 92: 93: 94: 95:
96: public $tableParameters = array(
97: 'charset' => array('value' => 'DEFAULT CHARSET', 'quote' => false, 'join' => '=', 'column' => 'charset'),
98: 'collate' => array('value' => 'COLLATE', 'quote' => false, 'join' => '=', 'column' => 'Collation'),
99: 'engine' => array('value' => 'ENGINE', 'quote' => false, 'join' => '=', 'column' => 'Engine')
100: );
101:
102: 103: 104: 105: 106:
107: public $columns = array(
108: 'primary_key' => array('name' => 'NOT NULL AUTO_INCREMENT'),
109: 'string' => array('name' => 'varchar', 'limit' => '255'),
110: 'text' => array('name' => 'text'),
111: 'integer' => array('name' => 'int', 'limit' => '11', 'formatter' => 'intval'),
112: 'float' => array('name' => 'float', 'formatter' => 'floatval'),
113: 'datetime' => array('name' => 'datetime', 'format' => 'Y-m-d H:i:s', 'formatter' => 'date'),
114: 'timestamp' => array('name' => 'timestamp', 'format' => 'Y-m-d H:i:s', 'formatter' => 'date'),
115: 'time' => array('name' => 'time', 'format' => 'H:i:s', 'formatter' => 'date'),
116: 'date' => array('name' => 'date', 'format' => 'Y-m-d', 'formatter' => 'date'),
117: 'binary' => array('name' => 'blob'),
118: 'boolean' => array('name' => 'tinyint', 'limit' => '1')
119: );
120:
121: 122: 123: 124: 125: 126:
127: public function connect() {
128: $config = $this->config;
129: $this->connected = false;
130: try {
131: $flags = array(
132: PDO::ATTR_PERSISTENT => $config['persistent'],
133: PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true,
134: PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
135: );
136: if (!empty($config['encoding'])) {
137: $flags[PDO::MYSQL_ATTR_INIT_COMMAND] = 'SET NAMES ' . $config['encoding'];
138: }
139: if (empty($config['unix_socket'])) {
140: $dsn = "mysql:host={$config['host']};port={$config['port']};dbname={$config['database']}";
141: } else {
142: $dsn = "mysql:unix_socket={$config['unix_socket']};dbname={$config['database']}";
143: }
144: $this->_connection = new PDO(
145: $dsn,
146: $config['login'],
147: $config['password'],
148: $flags
149: );
150: $this->connected = true;
151: } catch (PDOException $e) {
152: throw new MissingConnectionException(array(
153: 'class' => get_class($this),
154: 'message' => $e->getMessage()
155: ));
156: }
157:
158: $this->_useAlias = (bool)version_compare($this->getVersion(), "4.1", ">=");
159:
160: return $this->connected;
161: }
162:
163: 164: 165: 166: 167:
168: public function enabled() {
169: return in_array('mysql', PDO::getAvailableDrivers());
170: }
171:
172: 173: 174: 175: 176: 177:
178: public function listSources($data = null) {
179: $cache = parent::listSources();
180: if ($cache != null) {
181: return $cache;
182: }
183: $result = $this->_execute('SHOW TABLES FROM ' . $this->name($this->config['database']));
184:
185: if (!$result) {
186: $result->closeCursor();
187: return array();
188: } else {
189: $tables = array();
190:
191: while ($line = $result->fetch(PDO::FETCH_NUM)) {
192: $tables[] = $line[0];
193: }
194:
195: $result->closeCursor();
196: parent::listSources($tables);
197: return $tables;
198: }
199: }
200:
201: 202: 203: 204: 205: 206:
207: public function resultSet($results) {
208: $this->map = array();
209: $numFields = $results->columnCount();
210: $index = 0;
211:
212: while ($numFields-- > 0) {
213: $column = $results->getColumnMeta($index);
214: if (empty($column['native_type'])) {
215: $type = ($column['len'] == 1) ? 'boolean' : 'string';
216: } else {
217: $type = $column['native_type'];
218: }
219: if (!empty($column['table']) && strpos($column['name'], $this->virtualFieldSeparator) === false) {
220: $this->map[$index++] = array($column['table'], $column['name'], $type);
221: } else {
222: $this->map[$index++] = array(0, $column['name'], $type);
223: }
224: }
225: }
226:
227: 228: 229: 230: 231:
232: public function fetchResult() {
233: if ($row = $this->_result->fetch(PDO::FETCH_NUM)) {
234: $resultRow = array();
235: foreach ($this->map as $col => $meta) {
236: list($table, $column, $type) = $meta;
237: $resultRow[$table][$column] = $row[$col];
238: if ($type === 'boolean' && $row[$col] !== null) {
239: $resultRow[$table][$column] = $this->boolean($resultRow[$table][$column]);
240: }
241: }
242: return $resultRow;
243: }
244: $this->_result->closeCursor();
245: return false;
246: }
247:
248: 249: 250: 251: 252:
253: public function getEncoding() {
254: return $this->_execute('SHOW VARIABLES LIKE ?', array('character_set_client'))->fetchObject()->Value;
255: }
256:
257: 258: 259: 260: 261: 262:
263: public function getCharsetName($name) {
264: if ((bool)version_compare($this->getVersion(), "5", ">=")) {
265: $r = $this->_execute('SELECT CHARACTER_SET_NAME FROM INFORMATION_SCHEMA.COLLATIONS WHERE COLLATION_NAME = ?', array($name));
266: $cols = $r->fetch(PDO::FETCH_ASSOC);
267:
268: if (isset($cols['CHARACTER_SET_NAME'])) {
269: return $cols['CHARACTER_SET_NAME'];
270: }
271: }
272: return false;
273: }
274:
275: 276: 277: 278: 279: 280: 281:
282: public function describe($model) {
283: $key = $this->fullTableName($model, false);
284: $cache = parent::describe($key);
285: if ($cache != null) {
286: return $cache;
287: }
288: $table = $this->fullTableName($model);
289:
290: $fields = false;
291: $cols = $this->_execute('SHOW FULL COLUMNS FROM ' . $table);
292: if (!$cols) {
293: throw new CakeException(__d('cake_dev', 'Could not describe table for %s', $table));
294: }
295:
296: while ($column = $cols->fetch(PDO::FETCH_OBJ)) {
297: $fields[$column->Field] = array(
298: 'type' => $this->column($column->Type),
299: 'null' => ($column->Null === 'YES' ? true : false),
300: 'default' => $column->Default,
301: 'length' => $this->length($column->Type),
302: );
303: if (!empty($column->Key) && isset($this->index[$column->Key])) {
304: $fields[$column->Field]['key'] = $this->index[$column->Key];
305: }
306: foreach ($this->fieldParameters as $name => $value) {
307: if (!empty($column->{$value['column']})) {
308: $fields[$column->Field][$name] = $column->{$value['column']};
309: }
310: }
311: if (isset($fields[$column->Field]['collate'])) {
312: $charset = $this->getCharsetName($fields[$column->Field]['collate']);
313: if ($charset) {
314: $fields[$column->Field]['charset'] = $charset;
315: }
316: }
317: }
318: $this->_cacheDescription($key, $fields);
319: $cols->closeCursor();
320: return $fields;
321: }
322:
323: 324: 325: 326: 327: 328: 329: 330: 331:
332: public function update(Model $model, $fields = array(), $values = null, $conditions = null) {
333: if (!$this->_useAlias) {
334: return parent::update($model, $fields, $values, $conditions);
335: }
336:
337: if ($values == null) {
338: $combined = $fields;
339: } else {
340: $combined = array_combine($fields, $values);
341: }
342:
343: $alias = $joins = false;
344: $fields = $this->_prepareUpdateFields($model, $combined, empty($conditions), !empty($conditions));
345: $fields = implode(', ', $fields);
346: $table = $this->fullTableName($model);
347:
348: if (!empty($conditions)) {
349: $alias = $this->name($model->alias);
350: if ($model->name == $model->alias) {
351: $joins = implode(' ', $this->_getJoins($model));
352: }
353: }
354: $conditions = $this->conditions($this->defaultConditions($model, $conditions, $alias), true, true, $model);
355:
356: if ($conditions === false) {
357: return false;
358: }
359:
360: if (!$this->execute($this->renderStatement('update', compact('table', 'alias', 'joins', 'fields', 'conditions')))) {
361: $model->onError();
362: return false;
363: }
364: return true;
365: }
366:
367: 368: 369: 370: 371: 372: 373:
374: public function delete(Model $model, $conditions = null) {
375: if (!$this->_useAlias) {
376: return parent::delete($model, $conditions);
377: }
378: $alias = $this->name($model->alias);
379: $table = $this->fullTableName($model);
380: $joins = implode(' ', $this->_getJoins($model));
381:
382: if (empty($conditions)) {
383: $alias = $joins = false;
384: }
385: $complexConditions = false;
386: foreach ((array)$conditions as $key => $value) {
387: if (strpos($key, $model->alias) === false) {
388: $complexConditions = true;
389: break;
390: }
391: }
392: if (!$complexConditions) {
393: $joins = false;
394: }
395:
396: $conditions = $this->conditions($this->defaultConditions($model, $conditions, $alias), true, true, $model);
397: if ($conditions === false) {
398: return false;
399: }
400: if ($this->execute($this->renderStatement('delete', compact('alias', 'table', 'joins', 'conditions'))) === false) {
401: $model->onError();
402: return false;
403: }
404: return true;
405: }
406:
407: 408: 409: 410: 411: 412:
413: public function setEncoding($enc) {
414: return $this->_execute('SET NAMES ' . $enc) !== false;
415: }
416:
417: 418: 419: 420: 421: 422:
423: public function index($model) {
424: $index = array();
425: $table = $this->fullTableName($model);
426: $old = version_compare($this->getVersion(), '4.1', '<=');
427: if ($table) {
428: $indices = $this->_execute('SHOW INDEX FROM ' . $table);
429:
430:
431: while ($idx = $indices->fetch(PDO::FETCH_OBJ)) {
432: if ($old) {
433: $idx = (object)current((array)$idx);
434: }
435: if (!isset($index[$idx->Key_name]['column'])) {
436: $col = array();
437: $index[$idx->Key_name]['column'] = $idx->Column_name;
438: $index[$idx->Key_name]['unique'] = intval($idx->Non_unique == 0);
439: } else {
440: if (!empty($index[$idx->Key_name]['column']) && !is_array($index[$idx->Key_name]['column'])) {
441: $col[] = $index[$idx->Key_name]['column'];
442: }
443: $col[] = $idx->Column_name;
444: $index[$idx->Key_name]['column'] = $col;
445: }
446: if (!empty($idx->Sub_part)) {
447: if (!isset($index[$idx->Key_name]['length'])) {
448: $index[$idx->Key_name]['length'] = array();
449: }
450: $index[$idx->Key_name]['length'][$idx->Column_name] = $idx->Sub_part;
451: }
452: }
453:
454: $indices->closeCursor();
455: }
456: return $index;
457: }
458:
459: 460: 461: 462: 463: 464: 465:
466: public function alterSchema($compare, $table = null) {
467: if (!is_array($compare)) {
468: return false;
469: }
470: $out = '';
471: $colList = array();
472: foreach ($compare as $curTable => $types) {
473: $indexes = $tableParameters = $colList = array();
474: if (!$table || $table == $curTable) {
475: $out .= 'ALTER TABLE ' . $this->fullTableName($curTable) . " \n";
476: foreach ($types as $type => $column) {
477: if (isset($column['indexes'])) {
478: $indexes[$type] = $column['indexes'];
479: unset($column['indexes']);
480: }
481: if (isset($column['tableParameters'])) {
482: $tableParameters[$type] = $column['tableParameters'];
483: unset($column['tableParameters']);
484: }
485: switch ($type) {
486: case 'add':
487: foreach ($column as $field => $col) {
488: $col['name'] = $field;
489: $alter = 'ADD ' . $this->buildColumn($col);
490: if (isset($col['after'])) {
491: $alter .= ' AFTER ' . $this->name($col['after']);
492: }
493: $colList[] = $alter;
494: }
495: break;
496: case 'drop':
497: foreach ($column as $field => $col) {
498: $col['name'] = $field;
499: $colList[] = 'DROP ' . $this->name($field);
500: }
501: break;
502: case 'change':
503: foreach ($column as $field => $col) {
504: if (!isset($col['name'])) {
505: $col['name'] = $field;
506: }
507: $colList[] = 'CHANGE ' . $this->name($field) . ' ' . $this->buildColumn($col);
508: }
509: break;
510: }
511: }
512: $colList = array_merge($colList, $this->_alterIndexes($curTable, $indexes));
513: $colList = array_merge($colList, $this->_alterTableParameters($curTable, $tableParameters));
514: $out .= "\t" . implode(",\n\t", $colList) . ";\n\n";
515: }
516: }
517: return $out;
518: }
519:
520: 521: 522: 523: 524: 525: 526: 527:
528: public function dropSchema(CakeSchema $schema, $table = null) {
529: $out = '';
530: foreach ($schema->tables as $curTable => $columns) {
531: if (!$table || $table === $curTable) {
532: $out .= 'DROP TABLE IF EXISTS ' . $this->fullTableName($curTable) . ";\n";
533: }
534: }
535: return $out;
536: }
537:
538: 539: 540: 541: 542: 543: 544:
545: protected function _alterTableParameters($table, $parameters) {
546: if (isset($parameters['change'])) {
547: return $this->buildTableParameters($parameters['change']);
548: }
549: return array();
550: }
551:
552: 553: 554: 555: 556: 557: 558: 559:
560: public function buildIndex($indexes, $table = null) {
561: $join = array();
562: foreach ($indexes as $name => $value) {
563: $out = '';
564: if ($name === 'PRIMARY') {
565: $out .= 'PRIMARY ';
566: $name = null;
567: } else {
568: if (!empty($value['unique'])) {
569: $out .= 'UNIQUE ';
570: }
571: $name = $this->startQuote . $name . $this->endQuote;
572: }
573:
574: $out .= 'KEY ' . $name . ' (';
575: if (is_array($value['column'])) {
576: if (isset($value['length'])) {
577: $vals = array();
578: foreach ($value['column'] as $column) {
579: $name = $this->name($column);
580: if (isset($value['length'])) {
581: $name .= $this->_buildIndexSubPart($value['length'], $column);
582: }
583: $vals[] = $name;
584: }
585: $out .= implode(', ', $vals);
586: } else {
587: $out .= implode(', ', array_map(array(&$this, 'name'), $value['column']));
588: }
589: } else {
590: $out .= $this->name($value['column']);
591: if (isset($value['length'])) {
592: $out .= $this->_buildIndexSubPart($value['length'], $value['column']);
593: }
594: }
595: $out .= ')';
596: $join[] = $out;
597: }
598: return $join;
599: }
600:
601: 602: 603: 604: 605: 606: 607:
608: protected function _alterIndexes($table, $indexes) {
609: $alter = array();
610: if (isset($indexes['drop'])) {
611: foreach ($indexes['drop'] as $name => $value) {
612: $out = 'DROP ';
613: if ($name == 'PRIMARY') {
614: $out .= 'PRIMARY KEY';
615: } else {
616: $out .= 'KEY ' . $this->startQuote . $name . $this->endQuote;
617: }
618: $alter[] = $out;
619: }
620: }
621: if (isset($indexes['add'])) {
622: $add = $this->buildIndex($indexes['add']);
623: foreach ($add as $index) {
624: $alter[] = 'ADD ' . $index;
625: }
626: }
627: return $alter;
628: }
629:
630: 631: 632: 633: 634: 635: 636:
637: protected function _buildIndexSubPart($lengths, $column) {
638: if (is_null($lengths)) {
639: return '';
640: }
641: if (!isset($lengths[$column])) {
642: return '';
643: }
644: return '(' . $lengths[$column] . ')';
645: }
646:
647: 648: 649: 650: 651: 652:
653: public function listDetailedSources($name = null) {
654: $condition = '';
655: if (is_string($name)) {
656: $condition = ' WHERE name = ' . $this->value($name);
657: }
658: $result = $this->_connection->query('SHOW TABLE STATUS ' . $condition, PDO::FETCH_ASSOC);
659:
660: if (!$result) {
661: $result->closeCursor();
662: return array();
663: } else {
664: $tables = array();
665: foreach ($result as $row) {
666: $tables[$row['Name']] = (array)$row;
667: unset($tables[$row['Name']]['queryString']);
668: if (!empty($row['Collation'])) {
669: $charset = $this->getCharsetName($row['Collation']);
670: if ($charset) {
671: $tables[$row['Name']]['charset'] = $charset;
672: }
673: }
674: }
675: $result->closeCursor();
676: if (is_string($name) && isset($tables[$name])) {
677: return $tables[$name];
678: }
679: return $tables;
680: }
681: }
682:
683: 684: 685: 686: 687: 688:
689: public function column($real) {
690: if (is_array($real)) {
691: $col = $real['name'];
692: if (isset($real['limit'])) {
693: $col .= '(' . $real['limit'] . ')';
694: }
695: return $col;
696: }
697:
698: $col = str_replace(')', '', $real);
699: $limit = $this->length($real);
700: if (strpos($col, '(') !== false) {
701: list($col, $vals) = explode('(', $col);
702: }
703:
704: if (in_array($col, array('date', 'time', 'datetime', 'timestamp'))) {
705: return $col;
706: }
707: if (($col === 'tinyint' && $limit == 1) || $col === 'boolean') {
708: return 'boolean';
709: }
710: if (strpos($col, 'int') !== false) {
711: return 'integer';
712: }
713: if (strpos($col, 'char') !== false || $col === 'tinytext') {
714: return 'string';
715: }
716: if (strpos($col, 'text') !== false) {
717: return 'text';
718: }
719: if (strpos($col, 'blob') !== false || $col === 'binary') {
720: return 'binary';
721: }
722: if (strpos($col, 'float') !== false || strpos($col, 'double') !== false || strpos($col, 'decimal') !== false) {
723: return 'float';
724: }
725: if (strpos($col, 'enum') !== false) {
726: return "enum($vals)";
727: }
728: return 'text';
729: }
730:
731: 732: 733: 734: 735:
736: public function getSchemaName() {
737: return $this->config['database'];
738: }
739:
740: 741: 742: 743: 744:
745: public function nestedTransactionSupported() {
746: return $this->useNestedTransactions && version_compare($this->getVersion(), '4.1', '>=');
747: }
748:
749: }
750: