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