1: <?php
2: /*
3: You may not change or alter any portion of this comment or credits
4: of supporting developers from this source code or any supporting source code
5: which is considered copyrighted (c) material of the original comment or credit authors.
6:
7: This program is distributed in the hope that it will be useful,
8: but WITHOUT ANY WARRANTY; without even the implied warranty of
9: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10: */
11:
12: namespace Xmf\Database;
13:
14: use Xmf\Language;
15:
16: /**
17: * Xmf\Database\Tables
18: *
19: * inspired by Yii CDbMigration
20: *
21: * Build a work queue of database changes needed to implement new and
22: * changed tables. Define table(s) you are dealing with and any desired
23: * change(s). If the changes are already in place (i.e. the new column
24: * already exists) no work is added. Then executeQueue() to process the
25: * whole set.
26: *
27: * @category Xmf\Database\Tables
28: * @package Xmf
29: * @author Richard Griffith <richard@geekwright.com>
30: * @copyright 2011-2023 XOOPS Project (https://xoops.org)
31: * @license GNU GPL 2 or later (https://www.gnu.org/licenses/gpl-2.0.html)
32: * @link https://xoops.org
33: */
34: class Tables
35: {
36: /**
37: * @var \XoopsDatabase
38: */
39: protected $db;
40:
41: /**
42: * @var string
43: */
44: protected $databaseName;
45:
46: /**
47: * @var array Tables
48: */
49: protected $tables;
50:
51: /**
52: * @var array Work queue
53: */
54: protected $queue;
55:
56: /**
57: * @var string last error message
58: */
59: protected $lastError;
60:
61: /**
62: * @var int last error number
63: */
64: protected $lastErrNo;
65:
66: /**
67: * Constructor
68: *
69: */
70: public function __construct()
71: {
72: Language::load('xmf');
73:
74: $this->db = \XoopsDatabaseFactory::getDatabaseConnection();
75: $this->databaseName = XOOPS_DB_NAME;
76: $this->resetQueue();
77: }
78:
79: /**
80: * Return a table name, prefixed with site table prefix
81: *
82: * @param string $table table name to contain prefix
83: *
84: * @return string table name with prefix
85: */
86: protected function name($table)
87: {
88: return $this->db->prefix($table);
89: }
90:
91: /**
92: * Add new column for table to the work queue
93: *
94: * @param string $table table to contain the column
95: * @param string $column name of column to add
96: * @param string $attributes column_definition
97: *
98: * @return bool true if no errors, false if errors encountered
99: */
100: public function addColumn($table, $column, $attributes)
101: {
102: $columnDef = array(
103: 'name' => $column,
104: 'attributes' => $attributes
105: );
106:
107: // Find table def.
108: if (isset($this->tables[$table])) {
109: $tableDef = &$this->tables[$table];
110: // Is this on a table we are adding?
111: if (isset($tableDef['create']) && $tableDef['create']) {
112: array_push($tableDef['columns'], $columnDef);
113: } else {
114: foreach ($tableDef['columns'] as $col) {
115: if (strcasecmp($col['name'], $column) == 0) {
116: return true;
117: }
118: }
119: $this->queue[] = "ALTER TABLE `{$tableDef['name']}`"
120: . " ADD COLUMN `{$column}` {$columnDef['attributes']}";
121: array_push($tableDef['columns'], $columnDef);
122: }
123: } else {
124: return $this->tableNotEstablished();
125: }
126:
127: return true; // exists or is added to queue
128: }
129:
130: /**
131: * Add new primary key definition for table to work queue
132: *
133: * @param string $table table
134: * @param string $column column or comma separated list of columns
135: * to use as primary key
136: *
137: * @return bool true if no errors, false if errors encountered
138: */
139: public function addPrimaryKey($table, $column)
140: {
141: $columns = str_getcsv(str_replace(' ', '', $column));
142: $columnList = '';
143: $firstComma = '';
144: foreach ($columns as $col) {
145: $columnList .= "{$firstComma}`{$col}`";
146: $firstComma = ', ';
147: }
148: if (isset($this->tables[$table])) {
149: if (isset($this->tables[$table]['create']) && $this->tables[$table]['create']) {
150: $this->tables[$table]['keys']['PRIMARY']['columns'] = $columnList;
151: } else {
152: $this->queue[] = "ALTER TABLE `{$this->tables[$table]['name']}` ADD PRIMARY KEY({$columnList})";
153: }
154: } else {
155: return $this->tableNotEstablished();
156: }
157:
158: return true;
159: }
160:
161: /**
162: * Add new index definition for index to work queue
163: *
164: * @param string $name name of index to add
165: * @param string $table table indexed
166: * @param string $column column or a comma separated list of columns
167: * to use as the key
168: * @param bool $unique true if index is to be unique
169: *
170: * @return bool true if no errors, false if errors encountered
171: */
172: public function addIndex($name, $table, $column, $unique = false)
173: {
174: $columns = str_getcsv($column);
175: $columnList = '';
176: $firstComma = '';
177: foreach ($columns as $col) {
178: $columnList .= $firstComma . $this->quoteIndexColumnName($col);
179: $firstComma = ', ';
180: }
181: if (isset($this->tables[$table])) {
182: if (isset($this->tables[$table]['create']) && $this->tables[$table]['create']) {
183: $this->tables[$table]['keys'][$name]['columns'] = $columnList;
184: $this->tables[$table]['keys'][$name]['unique'] = (bool) $unique;
185: } else {
186: $add = ($unique ? 'ADD UNIQUE INDEX' : 'ADD INDEX');
187: $this->queue[] = "ALTER TABLE `{$this->tables[$table]['name']}` {$add} `{$name}` ({$columnList})";
188: }
189: } else {
190: return $this->tableNotEstablished();
191: }
192:
193: return true;
194: }
195:
196: /**
197: * Backtick quote the column names used in index creation.
198: *
199: * Handles prefix indexed columns specified as name(length) - i.e. name(20).
200: *
201: * @param string $columnName column name to quote with optional prefix length
202: *
203: * @return string
204: */
205: protected function quoteIndexColumnName($columnName)
206: {
207: $column = str_replace(' ', '', $columnName);
208: $length = '';
209:
210: $lengthPosition = strpos($column, '(');
211: if ($lengthPosition) {
212: $length = ' ' . substr($column, $lengthPosition);
213: $column = substr($column, 0, $lengthPosition);
214: }
215: $quotedName = "`{$column}`{$length}";
216:
217: return $quotedName;
218: }
219:
220: /**
221: * Load table schema from database, or starts new empty schema if
222: * table does not exist
223: *
224: * @param string $table table
225: *
226: * @return bool true if no errors, false if errors encountered
227: */
228: public function addTable($table)
229: {
230: if (isset($this->tables[$table])) {
231: return true;
232: }
233: $tableDef = $this->getTable($table);
234: if (is_array($tableDef)) {
235: $this->tables[$table] = $tableDef;
236:
237: return true;
238: } else {
239: if ($tableDef === true) {
240: $tableDef = array(
241: 'name' => $this->name($table),
242: 'options' => 'ENGINE=InnoDB',
243: 'columns' => array(),
244: 'keys' => array(),
245: 'create' => true,
246: );
247: $this->tables[$table] = $tableDef;
248:
249: $this->queue[] = array('createtable' => $table);
250:
251: return true;
252: } else {
253: return false;
254: }
255: }
256: }
257:
258: /**
259: * AddTable only if it exists
260: *
261: * @param string $table table
262: *
263: * @return bool true if table exists, false otherwise
264: */
265: public function useTable($table)
266: {
267: if (isset($this->tables[$table])) {
268: return true;
269: }
270: $tableDef = $this->getTable($table);
271: if (is_array($tableDef)) {
272: $this->tables[$table] = $tableDef;
273: return true;
274: }
275: return false;
276: }
277:
278: /**
279: * Get column attributes
280: *
281: * @param string $table table containing the column
282: * @param string $column column to alter
283: *
284: * @return string|false attribute string, or false if error encountered
285: */
286: public function getColumnAttributes($table, $column)
287: {
288: // Find table def.
289: if (isset($this->tables[$table])) {
290: $tableDef = $this->tables[$table];
291: // loop through and find the column
292: foreach ($tableDef['columns'] as $col) {
293: if (strcasecmp($col['name'], $column) === 0) {
294: return $col['attributes'];
295: }
296: }
297: }
298:
299: return false;
300: }
301:
302: /**
303: * Get indexes for a table
304: *
305: * @param string $table get indexes for this named table
306: *
307: * @return array|false array of indexes, or false if error encountered
308: */
309: public function getTableIndexes($table)
310: {
311: // Find table def.
312: if (isset($this->tables[$table]) && isset($this->tables[$table]['keys'])) {
313: return $this->tables[$table]['keys'];
314: }
315:
316: return false;
317: }
318:
319: /**
320: * Add alter column operation to the work queue
321: *
322: * @param string $table table containing the column
323: * @param string $column column to alter
324: * @param string $attributes new column_definition
325: * @param string $newName new name for column, blank to keep same
326: *
327: * @return bool true if no errors, false if errors encountered
328: */
329: public function alterColumn($table, $column, $attributes, $newName = '')
330: {
331: if (empty($newName)) {
332: $newName = $column;
333: }
334: // Find table def.
335: if (isset($this->tables[$table])) {
336: $tableDef = &$this->tables[$table];
337: // Is this on a table we are adding?
338: if (isset($tableDef['create']) && $tableDef['create']) {
339: // loop through and find the column
340: foreach ($tableDef['columns'] as &$col) {
341: if (strcasecmp($col['name'], $column) == 0) {
342: $col['name'] = $newName;
343: $col['attributes'] = $attributes;
344: break;
345: }
346: }
347:
348: return true;
349: } else {
350: $this->queue[] = "ALTER TABLE `{$tableDef['name']}` " .
351: "CHANGE COLUMN `{$column}` `{$newName}` {$attributes} ";
352: // loop through and find the column
353: foreach ($tableDef['columns'] as &$col) {
354: if (strcasecmp($col['name'], $column) == 0) {
355: $col['name'] = $newName;
356: $col['attributes'] = $attributes;
357: break;
358: }
359: }
360: }
361: } else {
362: return $this->tableNotEstablished();
363: }
364:
365: return true;
366: }
367:
368: /**
369: * Loads table schema from database, and adds newTable with that
370: * schema to the queue
371: *
372: * @param string $table existing table
373: * @param string $newTable new table
374: * @param bool $withData true to copy data, false for schema only
375: *
376: * @return bool true if no errors, false if errors encountered
377: */
378: public function copyTable($table, $newTable, $withData = false)
379: {
380: if (isset($this->tables[$newTable])) {
381: return true;
382: }
383: $tableDef = $this->getTable($table);
384: $copy = $this->name($newTable);
385: $original = $this->name($table);
386:
387: if (is_array($tableDef)) {
388: $tableDef['name'] = $copy;
389: if ($withData) {
390: $this->queue[] = "CREATE TABLE `{$copy}` LIKE `{$original}` ;";
391: $this->queue[] = "INSERT INTO `{$copy}` SELECT * FROM `{$original}` ;";
392: } else {
393: $tableDef['create'] = true;
394: $this->queue[] = array('createtable' => $newTable);
395: }
396: $this->tables[$newTable] = $tableDef;
397:
398: return true;
399: } else {
400: return false;
401: }
402: }
403:
404: /**
405: * Add drop column operation to the work queue
406: *
407: * @param string $table table containing the column
408: * @param string $column column to drop
409: *
410: * @return bool true if no errors, false if errors encountered
411: */
412: public function dropColumn($table, $column)
413: {
414: // Find table def.
415: if (isset($this->tables[$table])) {
416: $tableDef = $this->tables[$table];
417: $this->queue[] = "ALTER TABLE `{$tableDef['name']}` DROP COLUMN `{$column}`";
418: } else {
419: return $this->tableNotEstablished();
420: }
421:
422: return true;
423: }
424:
425: /**
426: * Add drop index operation to the work queue
427: *
428: * @param string $name name of index to drop
429: * @param string $table table indexed
430: *
431: * @return bool true if no errors, false if errors encountered
432: */
433: public function dropIndex($name, $table)
434: {
435: if (isset($this->tables[$table])) {
436: $tableDef = $this->tables[$table];
437: $this->queue[] = "ALTER TABLE `{$tableDef['name']}` DROP INDEX `{$name}`";
438: } else {
439: return $this->tableNotEstablished();
440: }
441:
442: return true;
443: }
444:
445: /**
446: * Add drop for all (non-PRIMARY) keys for a table to the work
447: * queue. This can be used to clean up indexes with automatic names.
448: *
449: * @param string $table table indexed
450: *
451: * @return bool true if no errors, false if errors encountered
452: */
453: public function dropIndexes($table)
454: {
455: // Find table def.
456: if (isset($this->tables[$table])) {
457: $tableDef = &$this->tables[$table];
458: // Is this on a table we are adding?
459: if (isset($tableDef['create']) && $tableDef['create']) {
460: // strip everything but the PRIMARY from definition
461: foreach ($tableDef['keys'] as $keyName => $key) {
462: if ($keyName !== 'PRIMARY') {
463: unset($tableDef['keys'][$keyName]);
464: }
465: }
466: } else {
467: // build drops to strip everything but the PRIMARY
468: foreach ($tableDef['keys'] as $keyName => $key) {
469: if ($keyName !== 'PRIMARY') {
470: $this->queue[] = "ALTER TABLE `{$tableDef['name']}` DROP INDEX {$keyName}";
471: }
472: }
473: }
474: } else {
475: return $this->tableNotEstablished();
476: }
477:
478: return true;
479: }
480:
481: /**
482: * Add drop of PRIMARY key for a table to the work queue
483: *
484: * @param string $table table
485: *
486: * @return bool true if no errors, false if errors encountered
487: */
488: public function dropPrimaryKey($table)
489: {
490: if (isset($this->tables[$table])) {
491: $tableDef = $this->tables[$table];
492: $this->queue[] = "ALTER TABLE `{$tableDef['name']}` DROP PRIMARY KEY ";
493: } else {
494: return $this->tableNotEstablished();
495: }
496:
497: return true;
498: }
499:
500: /**
501: * Add drop of table to the work queue
502: *
503: * @param string $table table
504: *
505: * @return bool true if no errors, false if errors encountered
506: */
507: public function dropTable($table)
508: {
509: if (isset($this->tables[$table])) {
510: $tableDef = $this->tables[$table];
511: $this->queue[] = "DROP TABLE `{$tableDef['name']}` ";
512: unset($this->tables[$table]);
513: }
514: // no table is not an error since we are dropping it anyway
515: return true;
516: }
517:
518:
519: /**
520: * Add rename table operation to the work queue
521: *
522: * @param string $table table
523: * @param string $newName new table name
524: *
525: * @return bool true if no errors, false if errors encountered
526: */
527: public function renameTable($table, $newName)
528: {
529: if (isset($this->tables[$table])) {
530: $tableDef = $this->tables[$table];
531: $newTable = $this->name($newName);
532: $this->queue[] = "ALTER TABLE `{$tableDef['name']}` RENAME TO `{$newTable}`";
533: $tableDef['name'] = $newTable;
534: $this->tables[$newName] = $tableDef;
535: unset($this->tables[$table]);
536: } else {
537: return $this->tableNotEstablished();
538: }
539:
540: return true;
541: }
542:
543: /**
544: * Add alter table table_options (ENGINE, DEFAULT CHARSET, etc.)
545: * to work queue
546: *
547: * @param string $table table
548: * @param string $options table_options
549: *
550: * @return bool true if no errors, false if errors encountered
551: */
552: public function setTableOptions($table, $options)
553: {
554: if (isset($this->tables[$table])) {
555: $tableDef = &$this->tables[$table];
556: // Is this on a table we are adding?
557: if (isset($tableDef['create']) && $tableDef['create']) {
558: $tableDef['options'] = $options;
559: return true;
560: } else {
561: $this->queue[] = "ALTER TABLE `{$tableDef['name']}` {$options} ";
562: $tableDef['options'] = $options;
563: return true;
564: }
565: } else {
566: return $this->tableNotEstablished();
567: }
568: }
569:
570:
571: /**
572: * Clear the work queue
573: *
574: * @return void
575: */
576: public function resetQueue()
577: {
578: $this->tables = array();
579: $this->queue = array();
580: }
581:
582: /**
583: * Executes the work queue
584: *
585: * @param bool $force true to force updates even if this is a 'GET' request
586: *
587: * @return bool true if no errors, false if errors encountered
588: */
589: public function executeQueue($force = false)
590: {
591: $this->expandQueue();
592: foreach ($this->queue as &$ddl) {
593: if (is_array($ddl)) {
594: if (isset($ddl['createtable'])) {
595: $ddl = $this->renderTableCreate($ddl['createtable']);
596: }
597: }
598: $result = $this->execSql($ddl, $force);
599: if (!$result) {
600: $this->lastError = $this->db->error();
601: $this->lastErrNo = $this->db->errno();
602:
603: return false;
604: }
605: }
606:
607: return true;
608: }
609:
610:
611: /**
612: * Create a DELETE statement and add it to the work queue
613: *
614: * @param string $table table
615: * @param string|CriteriaElement $criteria string where clause or object criteria
616: *
617: * @return bool true if no errors, false if errors encountered
618: */
619: public function delete($table, $criteria)
620: {
621: if (isset($this->tables[$table])) {
622: $tableDef = $this->tables[$table];
623: $where = '';
624: if (is_scalar($criteria)) {
625: $where = $criteria;
626: } elseif (is_object($criteria)) {
627: /** @var \CriteriaCompo $criteria */
628: $where = $criteria->renderWhere();
629: }
630: $this->queue[] = "DELETE FROM `{$tableDef['name']}` {$where}";
631: } else {
632: return $this->tableNotEstablished();
633: }
634:
635: return true;
636: }
637:
638: /**
639: * Create an INSERT SQL statement and add it to the work queue.
640: *
641: * @param string $table table
642: * @param array $columns array of 'column'=>'value' entries
643: * @param boolean $quoteValue true to quote values, false if caller handles quoting
644: *
645: * @return boolean true if no errors, false if errors encountered
646: */
647: public function insert($table, $columns, $quoteValue = true)
648: {
649: if (isset($this->tables[$table])) {
650: $tableDef = $this->tables[$table];
651: $colSql = '';
652: $valSql = '';
653: foreach ($tableDef['columns'] as $col) {
654: $comma = empty($colSql) ? '' : ', ';
655: if (isset($columns[$col['name']])) {
656: $colSql .= "{$comma}`{$col['name']}`";
657: $valSql .= $comma
658: . ($quoteValue ? $this->db->quote($columns[$col['name']]) : $columns[$col['name']]);
659: }
660: }
661: $sql = "INSERT INTO `{$tableDef['name']}` ({$colSql}) VALUES({$valSql})";
662: $this->queue[] = $sql;
663:
664: return true;
665: } else {
666: return $this->tableNotEstablished();
667: }
668: }
669:
670: /**
671: * Create an UPDATE SQL statement and add it to the work queue
672: *
673: * @param string $table table
674: * @param array $columns array of 'column'=>'value' entries
675: * @param string|CriteriaElement $criteria string where clause or object criteria
676: * @param boolean $quoteValue true to quote values, false if caller handles quoting
677: *
678: * @return boolean true if no errors, false if errors encountered
679: */
680: public function update($table, $columns, $criteria, $quoteValue = true)
681: {
682: if (isset($this->tables[$table])) {
683: $tableDef = $this->tables[$table];
684: $where = '';
685: if (is_scalar($criteria)) {
686: $where = $criteria;
687: } elseif (is_object($criteria)) {
688: /** @var \CriteriaCompo $criteria */
689: $where = $criteria->renderWhere();
690: }
691: $colSql = '';
692: foreach ($tableDef['columns'] as $col) {
693: $comma = empty($colSql) ? '' : ', ';
694: if (isset($columns[$col['name']])) {
695: $colSql .= "{$comma}`{$col['name']}` = "
696: . ($quoteValue ? $this->db->quote($columns[$col['name']]) : $columns[$col['name']]);
697: }
698: }
699: $sql = "UPDATE `{$tableDef['name']}` SET {$colSql} {$where}";
700: $this->queue[] = $sql;
701:
702: return true;
703: } else {
704: return $this->tableNotEstablished();
705: }
706: }
707:
708: /**
709: * Add statement to remove all rows from a table to the work queue
710: *
711: * @param string $table table
712: *
713: * @return bool true if no errors, false if errors encountered
714: */
715: public function truncate($table)
716: {
717: if (isset($this->tables[$table])) {
718: $tableDef = $this->tables[$table];
719: $this->queue[] = "TRUNCATE TABLE `{$tableDef['name']}`";
720: } else {
721: return $this->tableNotEstablished();
722: }
723:
724: return true;
725: }
726:
727:
728:
729: /**
730: * return SQL to create the table
731: *
732: * This method does NOT modify the work queue
733: *
734: * @param string $table table
735: * @param bool $prefixed true to return with table name prefixed
736: *
737: * @return string|false string SQL to create table, or false if errors encountered
738: */
739: protected function renderTableCreate($table, $prefixed = false)
740: {
741: if (isset($this->tables[$table])) {
742: $tableDef = $this->tables[$table];
743: $tableName = ($prefixed ? $tableDef['name'] : $table);
744: $sql = "CREATE TABLE `{$tableName}` (";
745: $firstComma = '';
746: foreach ($tableDef['columns'] as $col) {
747: $sql .= "{$firstComma}\n `{$col['name']}` {$col['attributes']}";
748: $firstComma = ',';
749: }
750: $keySql = '';
751: foreach ($tableDef['keys'] as $keyName => $key) {
752: if ($keyName === 'PRIMARY') {
753: $keySql .= ",\n PRIMARY KEY ({$key['columns']})";
754: } else {
755: $unique = $key['unique'] ? 'UNIQUE ' : '';
756: $keySql .= ",\n {$unique}KEY {$keyName} ({$key['columns']})";
757: }
758: }
759: $sql .= $keySql;
760: $sql .= "\n) {$tableDef['options']}";
761:
762: return $sql;
763: } else {
764: return $this->tableNotEstablished();
765: }
766: }
767:
768: /**
769: * execute an SQL statement
770: *
771: * @param string $sql SQL statement to execute
772: * @param bool $force true to use force updates even in safe requests
773: *
774: * @return mixed result resource if no error,
775: * true if no error but no result
776: * false if error encountered.
777: * Any error message is in $this->lastError;
778: */
779: protected function execSql($sql, $force = false)
780: {
781: if ($force) {
782: $result = $this->db->queryF($sql);
783: } else {
784: $result = $this->db->query($sql);
785: }
786:
787: if (!$result) {
788: $this->lastError = $this->db->error();
789: $this->lastErrNo = $this->db->errno();
790: }
791:
792: return $result;
793: }
794:
795: /**
796: * fetch the next row of a result set
797: *
798: * @param resource $result as returned by query
799: *
800: * @return mixed false on error
801: */
802: protected function fetch($result)
803: {
804: return $this->db->fetchArray($result);
805: }
806:
807: /**
808: * create default value clause for DDL
809: *
810: * @param string|null $default the default value to be quoted
811: *
812: * @return string the correctly quoted default value
813: */
814: protected function quoteDefaultClause($default)
815: {
816: // . (($column['COLUMN_DEFAULT'] === null) ? '' : " DEFAULT '" . $column['COLUMN_DEFAULT'] . "' ")
817: // no default specified
818: if (null===$default) {
819: return '';
820: }
821:
822: // functions should not be quoted
823: // this section will need expanded when XOOPS minimum is no longer a mysql 5 version
824: // Until mysql 8, only allowed function is CURRENT_TIMESTAMP
825: if ($default === 'CURRENT_TIMESTAMP') {
826: return ' DEFAULT CURRENT_TIMESTAMP ';
827: }
828:
829: // surround default with quotes
830: return " DEFAULT '{$default}' ";
831: }
832:
833: /**
834: * get table definition from INFORMATION_SCHEMA
835: *
836: * @param string $table table
837: *
838: * @return array|bool table definition array if table exists, true if table not defined, or
839: * false on error. Error message in $this->lastError;
840: */
841: protected function getTable($table)
842: {
843: $tableDef = array();
844:
845: $sql = 'SELECT TABLE_NAME, ENGINE, CHARACTER_SET_NAME ';
846: $sql .= ' FROM `INFORMATION_SCHEMA`.`TABLES` t, ';
847: $sql .= ' `INFORMATION_SCHEMA`.`COLLATIONS` c ';
848: $sql .= ' WHERE t.TABLE_SCHEMA = \'' . $this->databaseName . '\' ';
849: $sql .= ' AND t.TABLE_NAME = \'' . $this->name($table) . '\' ';
850: $sql .= ' AND t.TABLE_COLLATION = c.COLLATION_NAME ';
851:
852: $result = $this->execSql($sql);
853: if (!$result) {
854: return false;
855: }
856: $tableSchema = $this->fetch($result);
857: if (empty($tableSchema)) {
858: return true;
859: }
860: $tableDef['name'] = $tableSchema['TABLE_NAME'];
861: $tableDef['options'] = 'ENGINE=' . $tableSchema['ENGINE'] . ' '
862: . 'DEFAULT CHARSET=' . $tableSchema['CHARACTER_SET_NAME'];
863:
864: $sql = 'SELECT * ';
865: $sql .= ' FROM `INFORMATION_SCHEMA`.`COLUMNS` ';
866: $sql .= ' WHERE TABLE_SCHEMA = \'' . $this->databaseName . '\' ';
867: $sql .= ' AND TABLE_NAME = \'' . $this->name($table) . '\' ';
868: $sql .= ' ORDER BY `ORDINAL_POSITION` ';
869:
870: $result = $this->execSql($sql);
871:
872: while ($column = $this->fetch($result)) {
873: $attributes = ' ' . $column['COLUMN_TYPE'] . ' '
874: . (($column['IS_NULLABLE'] === 'NO') ? ' NOT NULL ' : '')
875: . $this->quoteDefaultClause($column['COLUMN_DEFAULT'])
876: //. $column['EXTRA'];
877: . str_replace('DEFAULT_GENERATED ', '', $column['EXTRA']);
878:
879: $columnDef = array(
880: 'name' => $column['COLUMN_NAME'],
881: 'attributes' => $attributes
882: );
883:
884: $tableDef['columns'][] = $columnDef;
885: };
886:
887: $sql = 'SELECT `INDEX_NAME`, `SEQ_IN_INDEX`, `NON_UNIQUE`, ';
888: $sql .= ' `COLUMN_NAME`, `SUB_PART` ';
889: $sql .= ' FROM `INFORMATION_SCHEMA`.`STATISTICS` ';
890: $sql .= ' WHERE TABLE_SCHEMA = \'' . $this->databaseName . '\' ';
891: $sql .= ' AND TABLE_NAME = \'' . $this->name($table) . '\' ';
892: $sql .= ' ORDER BY `INDEX_NAME`, `SEQ_IN_INDEX` ';
893:
894: $result = $this->execSql($sql);
895:
896: $lastKey = '';
897: $keyCols = '';
898: $keyUnique = false;
899: while ($key = $this->fetch($result)) {
900: if ($lastKey != $key['INDEX_NAME']) {
901: if (!empty($lastKey)) {
902: $tableDef['keys'][$lastKey]['columns'] = $keyCols;
903: $tableDef['keys'][$lastKey]['unique'] = $keyUnique;
904: }
905: $lastKey = $key['INDEX_NAME'];
906: $keyCols = $key['COLUMN_NAME'];
907: if (!empty($key['SUB_PART'])) {
908: $keyCols .= ' (' . $key['SUB_PART'] . ')';
909: }
910: $keyUnique = !$key['NON_UNIQUE'];
911: } else {
912: $keyCols .= ', ' . $key['COLUMN_NAME'];
913: if (!empty($key['SUB_PART'])) {
914: $keyCols .= ' (' . $key['SUB_PART'] . ')';
915: }
916: }
917: };
918: if (!empty($lastKey)) {
919: $tableDef['keys'][$lastKey]['columns'] = $keyCols;
920: $tableDef['keys'][$lastKey]['unique'] = $keyUnique;
921: }
922:
923: return $tableDef;
924: }
925:
926: /**
927: * During processing, tables to be created are put in the queue as
928: * an array('createtable' => tablename) since the definition is not
929: * complete. This method will expand those references to the full
930: * ddl to create the table.
931: *
932: * @return void
933: */
934: protected function expandQueue()
935: {
936: foreach ($this->queue as &$ddl) {
937: if (is_array($ddl)) {
938: if (isset($ddl['createtable'])) {
939: $ddl = $this->renderTableCreate($ddl['createtable'], true);
940: }
941: }
942: }
943: }
944:
945: /**
946: * Return message from last error encountered
947: *
948: * @return string last error message
949: */
950: public function getLastError()
951: {
952: return $this->lastError;
953: }
954:
955: /**
956: * Return code from last error encountered
957: *
958: * @return int last error number
959: */
960: public function getLastErrNo()
961: {
962: return $this->lastErrNo;
963: }
964:
965: /**
966: * dumpTables - utility function to dump raw tables array
967: *
968: * @return array tables
969: */
970: public function dumpTables()
971: {
972: return $this->tables;
973: }
974:
975: /**
976: * dumpQueue - utility function to dump the work queue
977: *
978: * @return array work queue
979: */
980: public function dumpQueue()
981: {
982: $this->expandQueue();
983:
984: return $this->queue;
985: }
986:
987: /**
988: * addToQueue - utility function to add a statement to the work queue
989: *
990: * @param string $sql an SQL/DDL statement to add
991: *
992: * @return void
993: */
994: public function addToQueue($sql)
995: {
996: $this->queue[] = $sql;
997: }
998:
999: /**
1000: * Set lastError as table not established
1001: *
1002: * @return false
1003: */
1004: protected function tableNotEstablished()
1005: {
1006: $this->lastError = _DB_XMF_TABLE_IS_NOT_DEFINED;
1007: $this->lastErrNo = -1;
1008: return false;
1009: }
1010: }
1011: