2 /* vim: set expandtab sw=4 ts=4 sts=4: */
4 * Holds the PMA_Table class
8 if (! defined('PHPMYADMIN')) {
13 * Handles everything related to tables
15 * @todo make use of PMA_Message and PMA_Error
21 * UI preferences properties
23 const PROP_SORTED_COLUMN
= 'sorted_col';
24 const PROP_COLUMN_ORDER
= 'col_order';
25 const PROP_COLUMN_VISIB
= 'col_visib';
28 * @var string engine (innodb, myisam, bdb, ...)
33 * @var string type (view, base table, system view)
38 * @var array UI preferences
43 * @var array errors occurred
45 var $errors = array();
50 var $messages = array();
53 * @var string table name
55 protected $_name = '';
58 * @var string database name
60 protected $_db_name = '';
63 * @var PMA_DatabaseInterface
70 * @param string $table_name table name
71 * @param string $db_name database name
72 * @param PMA_DatabaseInterface $dbi database interface for the table
74 function __construct($table_name, $db_name, PMA_DatabaseInterface
$dbi = null)
77 $dbi = $GLOBALS['dbi'];
80 $this->_name
= $table_name;
81 $this->_db_name
= $db_name;
87 * @see PMA_Table::getName()
88 * @return string table name
92 return $this->getName();
96 * return the last error
98 * @return string the last error
100 function getLastError()
102 return end($this->errors
);
106 * return the last message
108 * @return string the last message
110 function getLastMessage()
112 return end($this->messages
);
118 * @param boolean $backquoted whether to quote name with backticks ``
120 * @return string table name
122 function getName($backquoted = false)
125 return PMA_Util
::backquote($this->_name
);
131 * returns database name for this table
133 * @param boolean $backquoted whether to quote name with backticks ``
135 * @return string database name for this table
137 function getDbName($backquoted = false)
140 return PMA_Util
::backquote($this->_db_name
);
142 return $this->_db_name
;
146 * returns full name for table, including database name
148 * @param boolean $backquoted whether to quote name with backticks ``
152 function getFullName($backquoted = false)
154 return $this->getDbName($backquoted) . '.'
155 . $this->getName($backquoted);
159 * returns whether the table is actually a view
161 * @return boolean whether the given is a view
163 public function isView()
165 $db = $this->_db_name
;
166 $table = $this->_name
;
167 if (empty($db) ||
empty($table)) {
171 // use cached data or load information with SHOW command
172 if ($this->_dbi
->getCachedTableContent(array($db, $table)) != null
173 ||
$GLOBALS['cfg']['Server']['DisableIS']
175 $type = $this->getStatusInfo('TABLE_TYPE');
176 return $type == 'VIEW' ||
$type == 'SYSTEM VIEW';
179 // information_schema tables are 'SYSTEM VIEW's
180 if ($db == 'information_schema') {
184 // query information_schema
185 $result = $this->_dbi
->fetchResult(
187 FROM information_schema.VIEWS
188 WHERE TABLE_SCHEMA = '" . PMA_Util
::sqlAddSlashes($db) . "'
189 AND TABLE_NAME = '" . PMA_Util
::sqlAddSlashes($table) . "'"
191 return $result ?
true : false;
195 * Returns whether the table is actually an updatable view
197 * @return boolean whether the given is an updatable view
199 public function isUpdatableView()
201 if (empty($this->_db_name
) ||
empty($this->_name
)) {
205 $result = $this->_dbi
->fetchResult(
207 FROM information_schema.VIEWS
208 WHERE TABLE_SCHEMA = '" . PMA_Util
::sqlAddSlashes($this->_db_name
) . "'
209 AND TABLE_NAME = '" . PMA_Util
::sqlAddSlashes($this->_name
) . "'
210 AND IS_UPDATABLE = 'YES'"
212 return $result ?
true : false;
216 * Returns the analysis of 'SHOW CREATE TABLE' query for the table.
217 * In case of a view, the values are taken from the information_schema.
219 * @return array analysis of 'SHOW CREATE TABLE' query for the table
221 public function analyzeStructure()
223 if (empty($this->_db_name
) ||
empty($this->_name
)) {
227 $analyzed_sql = array();
228 if ($this->isView()) {
229 // For a view, 'SHOW CREATE TABLE' returns the definition,
230 // but the structure of the view. So, we try to mock
231 // the result of analyzing 'SHOW CREATE TABLE' query.
232 $analyzed_sql[0] = array();
233 $analyzed_sql[0]['create_table_fields'] = array();
235 $results = $this->_dbi
->fetchResult(
236 "SELECT COLUMN_NAME, DATA_TYPE
237 FROM information_schema.COLUMNS
238 WHERE TABLE_SCHEMA = '" . PMA_Util
::sqlAddSlashes($this->_db_name
)
239 . " AND TABLE_NAME = '" . PMA_Util
::sqlAddSlashes($this->_name
) . "'"
242 foreach ($results as $result) {
243 $analyzed_sql[0]['create_table_fields'][$result['COLUMN_NAME']]
245 'type' => /*overload*/mb_strtoupper($result['DATA_TYPE'])
249 $show_create_table = $this->_dbi
->fetchValue(
251 . PMA_Util
::backquote($this->_db_name
)
252 . '.' . PMA_Util
::backquote($this->_name
),
256 $analyzed_sql = PMA_SQP_analyze(PMA_SQP_parse($show_create_table));
258 return $analyzed_sql;
262 * Checks if this is a merge table
264 * If the ENGINE of the table is MERGE or MRG_MYISAM (alias),
265 * this is a merge table.
268 * @return boolean true if it is a merge table
270 public function isMerge()
273 // if called static, with parameters
274 if (! empty($this->_db_name
) && ! empty($this->_name
)) {
275 $engine = $this->getStatusInfo('ENGINE', null, true);
278 // did we get engine?
279 if (empty($engine)) {
283 // any of known merge engines?
285 /*overload*/mb_strtoupper($engine),
286 array('MERGE', 'MRG_MYISAM')
291 * Returns full table status info, or specific if $info provided
292 * this info is collected from information_schema
294 * @param string $info specific information to be fetched
295 * @param boolean $force_read read new rather than serving from cache
296 * @param boolean $disable_error if true, disables error message
298 * @todo DatabaseInterface::getTablesFull needs to be merged
299 * somehow into this class or at least better documented
303 public function getStatusInfo(
306 $disable_error = false
308 $db = $this->_db_name
;
309 $table = $this->_name
;
311 if (! empty($_SESSION['is_multi_query'])) {
312 $disable_error = true;
315 // sometimes there is only one entry (ExactRows) so
316 // we have to get the table's details
317 if ($this->_dbi
->getCachedTableContent(array($db, $table)) == null
319 ||
count($this->_dbi
->getCachedTableContent(array($db, $table))) == 1
321 $this->_dbi
->getTablesFull($db, $table);
324 if ($this->_dbi
->getCachedTableContent(array($db, $table)) == null) {
325 // happens when we enter the table creation dialog
326 // or when we really did not get any status info, for example
327 // when $table == 'TABLE_NAMES' after the user tried SHOW TABLES
331 if (null === $info) {
332 return $this->_dbi
->getCachedTableContent(array($db, $table));
335 // array_key_exists allows for null values
336 if (!array_key_exists(
337 $info, $this->_dbi
->getCachedTableContent(array($db, $table))
340 if (! $disable_error) {
342 __('Unknown table status:') . ' ' . $info,
349 return $this->_dbi
->getCachedTableContent(array($db, $table, $info));
353 * generates column specification for ALTER or CREATE TABLE syntax
355 * @param string $name name
356 * @param string $type type ('INT', 'VARCHAR', 'BIT', ...)
357 * @param string $length length ('2', '5,2', '', ...)
358 * @param string $attribute attribute
359 * @param string $collation collation
360 * @param bool|string $null with 'NULL' or 'NOT NULL'
361 * @param string $default_type whether default is CURRENT_TIMESTAMP,
362 * NULL, NONE, USER_DEFINED
363 * @param string $default_value default value for USER_DEFINED
365 * @param string $extra 'AUTO_INCREMENT'
366 * @param string $comment field comment
367 * @param string $virtuality virtuality of the column
368 * @param string $expression expression for the virtual column
369 * @param string $move_to new position for column
371 * @todo move into class PMA_Column
372 * @todo on the interface, some js to clear the default value when the
373 * default current_timestamp is checked
375 * @return string field specification
377 static function generateFieldSpec($name, $type, $length = '',
378 $attribute = '', $collation = '', $null = false,
379 $default_type = 'USER_DEFINED', $default_value = '', $extra = '',
380 $comment = '', $virtuality = '', $expression = '', $move_to = ''
382 $is_timestamp = /*overload*/mb_strpos(
383 /*overload*/mb_strtoupper($type),
387 $query = PMA_Util
::backquote($name) . ' ' . $type;
389 // allow the possibility of a length for TIME, DATETIME and TIMESTAMP
390 // (will work on MySQL >= 5.6.4)
392 // MySQL permits a non-standard syntax for FLOAT and DOUBLE,
393 // see http://dev.mysql.com/doc/refman/5.5/en/floating-point-types.html
395 $pattern = '@^(DATE|TINYBLOB|TINYTEXT|BLOB|TEXT|'
396 . 'MEDIUMBLOB|MEDIUMTEXT|LONGBLOB|LONGTEXT|SERIAL|BOOLEAN|UUID)$@i';
397 if ($length != '' && ! preg_match($pattern, $type)) {
398 $query .= '(' . $length . ')';
402 $query .= ' AS (' . $expression . ') ' . $virtuality;
404 if ($attribute != '') {
405 $query .= ' ' . $attribute;
408 $matches = preg_match(
409 '@^(TINYTEXT|TEXT|MEDIUMTEXT|LONGTEXT|VARCHAR|CHAR|ENUM|SET)$@i',
412 if (! empty($collation) && $collation != 'NULL' && $matches) {
413 $query .= PMA_generateCharsetQueryPart($collation, true);
416 if ($null !== false) {
417 if ($null == 'NULL') {
420 $query .= ' NOT NULL';
424 switch ($default_type) {
425 case 'USER_DEFINED' :
426 if ($is_timestamp && $default_value === '0') {
427 // a TIMESTAMP does not accept DEFAULT '0'
428 // but DEFAULT 0 works
429 $query .= ' DEFAULT 0';
430 } elseif ($type == 'BIT') {
431 $query .= ' DEFAULT b\''
432 . preg_replace('/[^01]/', '0', $default_value)
434 } elseif ($type == 'BOOLEAN') {
435 if (preg_match('/^1|T|TRUE|YES$/i', $default_value)) {
436 $query .= ' DEFAULT TRUE';
437 } elseif (preg_match('/^0|F|FALSE|NO$/i', $default_value)) {
438 $query .= ' DEFAULT FALSE';
440 // Invalid BOOLEAN value
441 $query .= ' DEFAULT \''
442 . PMA_Util
::sqlAddSlashes($default_value) . '\'';
444 } elseif ($type == 'BINARY' ||
$type == 'VARBINARY') {
445 $query .= ' DEFAULT 0x' . $default_value;
447 $query .= ' DEFAULT \''
448 . PMA_Util
::sqlAddSlashes($default_value) . '\'';
451 /** @noinspection PhpMissingBreakStatementInspection */
453 // If user uncheck null checkbox and not change default value null,
454 // default value will be ignored.
455 if ($null !== false && $null !== 'NULL') {
458 // else fall-through intended, no break here
459 case 'CURRENT_TIMESTAMP' :
460 $query .= ' DEFAULT ' . $default_type;
467 if (!empty($extra)) {
468 $query .= ' ' . $extra;
471 if (!empty($comment)) {
472 $query .= " COMMENT '" . PMA_Util
::sqlAddSlashes($comment) . "'";
476 if ($move_to == '-first') { // dash can't appear as part of column name
478 } elseif ($move_to != '') {
479 $query .= ' AFTER ' . PMA_Util
::backquote($move_to);
485 * Counts and returns (or displays) the number of records in a table
487 * @param bool $force_exact whether to force an exact count
489 * @return mixed the number of records if "retain" param is true,
492 public function countRecords($force_exact = false)
494 $is_view = $this->isView();
495 $db = $this->_db_name
;
496 $table = $this->_name
;
498 if ($this->_dbi
->getCachedTableContent(array($db, $table, 'ExactRows')) != null) {
499 $row_count = $this->_dbi
->getCachedTableContent(
500 array($db, $table, 'ExactRows')
506 if (! $force_exact) {
507 if (($this->_dbi
->getCachedTableContent(array($db, $table, 'Rows')) == null)
510 $tmp_tables = $this->_dbi
->getTablesFull($db, $table);
511 if (isset($tmp_tables[$table])) {
512 $this->_dbi
->cacheTableContent(
518 if ($this->_dbi
->getCachedTableContent(array($db, $table, 'Rows')) != null) {
519 $row_count = $this->_dbi
->getCachedTableContent(
520 array($db, $table, 'Rows')
526 // for a VIEW, $row_count is always false at this point
527 if (false !== $row_count
528 && $row_count >= $GLOBALS['cfg']['MaxExactCount']
533 // Make an exception for views in I_S and D_D schema in
534 // Drizzle, as these map to in-memory data and should execute
537 ||
(PMA_DRIZZLE
&& $this->_dbi
->isSystemSchema($db))
539 $row_count = $this->_dbi
->fetchValue(
540 'SELECT COUNT(*) FROM ' . PMA_Util
::backquote($db) . '.'
541 . PMA_Util
::backquote($table)
544 // For complex views, even trying to get a partial record
545 // count could bring down a server, so we offer an
546 // alternative: setting MaxExactCountViews to 0 will bypass
547 // completely the record counting for views
549 if ($GLOBALS['cfg']['MaxExactCountViews'] == 0) {
552 // Counting all rows of a VIEW could be too long,
553 // so use a LIMIT clause.
554 // Use try_query because it can fail (when a VIEW is
555 // based on a table that no longer exists)
556 $result = $this->_dbi
->tryQuery(
557 'SELECT 1 FROM ' . PMA_Util
::backquote($db) . '.'
558 . PMA_Util
::backquote($table) . ' LIMIT '
559 . $GLOBALS['cfg']['MaxExactCountViews'],
561 PMA_DatabaseInterface
::QUERY_STORE
563 if (!$this->_dbi
->getError()) {
564 $row_count = $this->_dbi
->numRows($result);
565 $this->_dbi
->freeResult($result);
570 $this->_dbi
->cacheTableContent(array($db, $table, 'ExactRows'), $row_count);
574 } // end of the 'PMA_Table::countRecords()' function
577 * Generates column specification for ALTER syntax
579 * @param string $oldcol old column name
580 * @param string $newcol new column name
581 * @param string $type type ('INT', 'VARCHAR', 'BIT', ...)
582 * @param string $length length ('2', '5,2', '', ...)
583 * @param string $attribute attribute
584 * @param string $collation collation
585 * @param bool|string $null with 'NULL' or 'NOT NULL'
586 * @param string $default_type whether default is CURRENT_TIMESTAMP,
587 * NULL, NONE, USER_DEFINED
588 * @param string $default_value default value for USER_DEFINED default
590 * @param string $extra 'AUTO_INCREMENT'
591 * @param string $comment field comment
592 * @param string $virtuality virtuality of the column
593 * @param string $expression expression for the virtual column
594 * @param string $move_to new position for column
596 * @see PMA_Table::generateFieldSpec()
598 * @return string field specification
600 static public function generateAlter($oldcol, $newcol, $type, $length,
601 $attribute, $collation, $null, $default_type, $default_value,
602 $extra, $comment, $virtuality, $expression, $move_to
604 return PMA_Util
::backquote($oldcol) . ' '
605 . PMA_Table
::generateFieldSpec(
606 $newcol, $type, $length, $attribute,
607 $collation, $null, $default_type, $default_value, $extra,
608 $comment, $virtuality, $expression, $move_to
613 * Inserts existing entries in a PMA_* table by reading a value from an old
616 * @param string $work The array index, which Relation feature to
617 * check ('relwork', 'commwork', ...)
618 * @param string $pma_table The array index, which PMA-table to update
619 * ('bookmark', 'relation', ...)
620 * @param array $get_fields Which fields will be SELECT'ed from the old entry
621 * @param array $where_fields Which fields will be used for the WHERE query
622 * (array('FIELDNAME' => 'FIELDVALUE'))
623 * @param array $new_fields Which fields will be used as new VALUES.
624 * These are the important keys which differ
626 * (array('FIELDNAME' => 'NEW FIELDVALUE'))
628 * @global relation variable
632 static public function duplicateInfo($work, $pma_table, $get_fields,
633 $where_fields, $new_fields
637 if (!isset($GLOBALS['cfgRelation']) ||
!$GLOBALS['cfgRelation'][$work]) {
641 $select_parts = array();
642 $row_fields = array();
643 foreach ($get_fields as $get_field) {
644 $select_parts[] = PMA_Util
::backquote($get_field);
645 $row_fields[$get_field] = 'cc';
648 $where_parts = array();
649 foreach ($where_fields as $_where => $_value) {
650 $where_parts[] = PMA_Util
::backquote($_where) . ' = \''
651 . PMA_Util
::sqlAddSlashes($_value) . '\'';
654 $new_parts = array();
655 $new_value_parts = array();
656 foreach ($new_fields as $_where => $_value) {
657 $new_parts[] = PMA_Util
::backquote($_where);
658 $new_value_parts[] = PMA_Util
::sqlAddSlashes($_value);
661 $table_copy_query = '
662 SELECT ' . implode(', ', $select_parts) . '
663 FROM ' . PMA_Util
::backquote($GLOBALS['cfgRelation']['db']) . '.'
664 . PMA_Util
::backquote($GLOBALS['cfgRelation'][$pma_table]) . '
665 WHERE ' . implode(' AND ', $where_parts);
667 // must use PMA_DatabaseInterface::QUERY_STORE here, since we execute
668 // another query inside the loop
669 $table_copy_rs = PMA_queryAsControlUser(
670 $table_copy_query, true, PMA_DatabaseInterface
::QUERY_STORE
673 while ($table_copy_row = @$GLOBALS['dbi']->fetchAssoc($table_copy_rs)) {
674 $value_parts = array();
675 foreach ($table_copy_row as $_key => $_val) {
676 if (isset($row_fields[$_key]) && $row_fields[$_key] == 'cc') {
677 $value_parts[] = PMA_Util
::sqlAddSlashes($_val);
681 $new_table_query = 'INSERT IGNORE INTO '
682 . PMA_Util
::backquote($GLOBALS['cfgRelation']['db'])
683 . '.' . PMA_Util
::backquote($GLOBALS['cfgRelation'][$pma_table])
684 . ' (' . implode(', ', $select_parts) . ', '
685 . implode(', ', $new_parts) . ') VALUES (\''
686 . implode('\', \'', $value_parts) . '\', \''
687 . implode('\', \'', $new_value_parts) . '\')';
689 PMA_queryAsControlUser($new_table_query);
690 $last_id = $GLOBALS['dbi']->insertId();
693 $GLOBALS['dbi']->freeResult($table_copy_rs);
696 } // end of 'PMA_Table::duplicateInfo()' function
699 * Copies or renames table
701 * @param string $source_db source database
702 * @param string $source_table source table
703 * @param string $target_db target database
704 * @param string $target_table target table
705 * @param string $what what to be moved or copied (data, dataonly)
706 * @param bool $move whether to move
707 * @param string $mode mode
709 * @return bool true if success, false otherwise
711 static public function moveCopy($source_db, $source_table, $target_db,
712 $target_table, $what, $move, $mode
717 // Try moving the tables directly, using native `RENAME` statement.
718 if ($move && $what == 'data') {
719 $tbl = new PMA_Table($source_table, $source_db);
720 if ($tbl->rename($target_table, $target_db)) {
721 $GLOBALS['message'] = $tbl->getLastMessage();
726 // Setting required export settings.
727 $GLOBALS['sql_backquotes'] = 1;
728 $GLOBALS['asfile'] = 1;
730 // Ensuring the target database is valid.
731 if (! $GLOBALS['pma']->databases
->exists($source_db, $target_db)) {
732 if (! $GLOBALS['pma']->databases
->exists($source_db)) {
733 $GLOBALS['message'] = PMA_Message
::rawError(
735 __('Source database `%s` was not found!'),
736 htmlspecialchars($source_db)
740 if (! $GLOBALS['pma']->databases
->exists($target_db)) {
741 $GLOBALS['message'] = PMA_Message
::rawError(
743 __('Target database `%s` was not found!'),
744 htmlspecialchars($target_db)
752 * The full name of source table, quoted.
753 * @var string $source
755 $source = PMA_Util
::backquote($source_db)
756 . '.' . PMA_Util
::backquote($source_table);
758 // If the target database is not specified, the operation is taking
759 // place in the same database.
760 if (! isset($target_db) ||
! /*overload*/mb_strlen($target_db)) {
761 $target_db = $source_db;
764 // Selecting the database could avoid some problems with replicated
765 // databases, when moving table from replicated one to not replicated one.
766 $GLOBALS['dbi']->selectDb($target_db);
769 * The full name of target table, quoted.
770 * @var string $target
772 $target = PMA_Util
::backquote($target_db)
773 . '.' . PMA_Util
::backquote($target_table);
775 // No table is created when this is a data-only operation.
776 if ($what != 'dataonly') {
778 include_once "libraries/plugin_interface.lib.php";
781 * Instance used for exporting the current structure of the table.
785 $export_sql_plugin = PMA_getPlugin(
788 'libraries/plugins/export/',
790 'export_type' => 'table',
791 'single_table' => false,
795 $no_constraints_comments = true;
796 $GLOBALS['sql_constraints_query'] = '';
797 // set the value of global sql_auto_increment variable
798 if (isset($_POST['sql_auto_increment'])) {
799 $GLOBALS['sql_auto_increment'] = $_POST['sql_auto_increment'];
803 * The old structure of the table..
804 * @var string $sql_structure
806 $sql_structure = $export_sql_plugin->getTableDef(
807 $source_db, $source_table, "\n", $err_url, false, false
810 unset($no_constraints_comments);
812 // -----------------------------------------------------------------
813 // Phase 0: Preparing structures used.
816 * The destination where the table is moved or copied to.
817 * @var SqlParser\Components\Expression
819 $destination = new SqlParser\Components\
Expression(
820 $target_db, $target_table, ''
823 // Find server's SQL mode so the builder can generate correct
825 // One of the options that alters the behaviour is `ANSI_QUOTES`.
826 // This is not availabile for Drizzle.
828 SqlParser\Context
::setMode(
829 $GLOBALS['dbi']->fetchValue("SELECT @@sql_mode")
833 // -----------------------------------------------------------------
834 // Phase 1: Dropping existent element of the same name (if exists
837 if (isset($_REQUEST['drop_if_exists'])
838 && $_REQUEST['drop_if_exists'] == 'true'
842 * Drop statement used for building the query.
843 * @var SqlParser\Statements\DropStatement $statement
845 $statement = new SqlParser\Statements\
DropStatement();
847 $tbl = new PMA_Table($target_db, $target_table);
849 $statement->options
= new SqlParser\Components\
OptionsArray(
851 $tbl->isView() ?
'VIEW' : 'TABLE',
856 $statement->fields
= array($destination);
858 // Building the query.
859 $drop_query = $statement->build() . ';';
862 $GLOBALS['dbi']->query($drop_query);
863 $GLOBALS['sql_query'] .= "\n" . $drop_query;
865 // If an existing table gets deleted, maintain any entries for
867 $maintain_relations = true;
870 // -----------------------------------------------------------------
871 // Phase 2: Generating the new query of this structure.
874 * The parser responsible for parsing the old queries.
875 * @var SqlParser\Parser $parser
877 $parser = new SqlParser\
Parser($sql_structure);
879 if (!empty($parser->statements
[0])) {
882 * The CREATE statement of this structure.
883 * @var SqlParser\Statements\CreateStatement $statement
885 $statement = $parser->statements
[0];
887 // Changing the destination.
888 $statement->name
= $destination;
890 // Building back the query.
891 $sql_structure = $statement->build() . ';';
894 $GLOBALS['dbi']->query($sql_structure);
895 $GLOBALS['sql_query'] .= "\n" . $sql_structure;
898 // -----------------------------------------------------------------
899 // Phase 3: Adding constraints.
900 // All constraint names are removed because they must be unique.
902 if (($move ||
isset($GLOBALS['add_constraints']))
903 && !empty($GLOBALS['sql_constraints_query'])
906 $parser = new SqlParser\
Parser($GLOBALS['sql_constraints_query']);
909 * The ALTER statement that generates the constraints.
910 * @var SqlParser\Statements\AlterStatement $statement
912 $statement = $parser->statements
[0];
914 // Changing the altered table to the destination.
915 $statement->table
= $destination;
917 // Removing the name of the constraints.
918 foreach ($statement->altered
as $idx => $altered) {
919 // All constraint names are removed because they must be unique.
920 if ($altered->options
->has('CONSTRAINT')) {
921 $altered->field
= null;
925 // Building back the query.
926 $GLOBALS['sql_constraints_query'] = $statement->build() . ';';
929 if ($mode == 'one_table') {
930 $GLOBALS['dbi']->query($GLOBALS['sql_constraints_query']);
932 $GLOBALS['sql_query'] .= "\n" . $GLOBALS['sql_constraints_query'];
933 if ($mode == 'one_table') {
934 unset($GLOBALS['sql_constraints_query']);
938 // -----------------------------------------------------------------
939 // Phase 4: Adding indexes.
942 if (!empty($GLOBALS['sql_indexes'])) {
944 $parser = new SqlParser\
Parser($GLOBALS['sql_indexes']);
947 * The ALTER statement that generates the indexes.
948 * @var SqlParser\Statements\AlterStatement $statement
950 $statement = $parser->statements
[0];
952 // Changing the altered table to the destination.
953 $statement->table
= $destination;
955 // Removing the name of the constraints.
956 foreach ($statement->altered
as $idx => $altered) {
957 // All constraint names are removed because they must be unique.
958 if ($altered->options
->has('CONSTRAINT')) {
959 $altered->field
= null;
963 // Building back the query.
964 $GLOBALS['sql_indexes'] = $statement->build() . ';';
967 if ($mode == 'one_table' ||
$mode == 'db_copy') {
968 $GLOBALS['dbi']->query($GLOBALS['sql_indexes']);
970 $GLOBALS['sql_query'] .= "\n" . $GLOBALS['sql_indexes'];
971 if ($mode == 'one_table' ||
$mode == 'db_copy') {
972 unset($GLOBALS['sql_indexes']);
976 // -----------------------------------------------------------------
977 // Phase 5: Adding AUTO_INCREMENT.
979 if (! empty($GLOBALS['sql_auto_increments'])) {
980 if ($mode == 'one_table' ||
$mode == 'db_copy') {
982 $parser = new SqlParser\
Parser($GLOBALS['sql_auto_increments']);
985 * The ALTER statement that alters the AUTO_INCREMENT value.
986 * @var SqlParser\Statements\AlterStatement $statement
988 $statement = $parser->statements
[0];
990 // Changing the altered table to the destination.
991 $statement->table
= $destination;
993 // Building back the query.
994 $GLOBALS['sql_auto_increments'] = $statement->build() . ';';
997 $GLOBALS['dbi']->query($GLOBALS['sql_auto_increments']);
998 $GLOBALS['sql_query'] .= "\n" . $GLOBALS['sql_auto_increments'];
999 unset($GLOBALS['sql_auto_increments']);
1003 $GLOBALS['sql_query'] = '';
1006 $_table = new PMA_Table($target_table, $target_db);
1007 // Copy the data unless this is a VIEW
1008 if (($what == 'data' ||
$what == 'dataonly')
1009 && ! $_table->isView()
1011 if (! PMA_DRIZZLE
) {
1012 $sql_set_mode = "SET SQL_MODE='NO_AUTO_VALUE_ON_ZERO'";
1013 $GLOBALS['dbi']->query($sql_set_mode);
1014 $GLOBALS['sql_query'] .= "\n\n" . $sql_set_mode . ';';
1017 $sql_insert_data = 'INSERT INTO ' . $target
1018 . ' SELECT * FROM ' . $source;
1019 $GLOBALS['dbi']->query($sql_insert_data);
1020 $GLOBALS['sql_query'] .= "\n\n" . $sql_insert_data . ';';
1023 PMA_getRelationsParam();
1025 // Drops old table if the user has requested to move it
1028 // This could avoid some problems with replicated databases, when
1029 // moving table from replicated one to not replicated one
1030 $GLOBALS['dbi']->selectDb($source_db);
1032 $_source_table = new PMA_Table($source_table, $source_db);
1033 if ($_source_table->isView()) {
1034 $sql_drop_query = 'DROP VIEW';
1036 $sql_drop_query = 'DROP TABLE';
1038 $sql_drop_query .= ' ' . $source;
1039 $GLOBALS['dbi']->query($sql_drop_query);
1041 // Renable table in configuration storage
1042 PMA_REL_renameTable(
1043 $source_db, $target_db,
1044 $source_table, $target_table
1047 $GLOBALS['sql_query'] .= "\n\n" . $sql_drop_query . ';';
1053 // Create new entries as duplicates from old PMA DBs
1054 if ($what == 'dataonly' ||
isset($maintain_relations)) {
1058 if ($GLOBALS['cfgRelation']['commwork']) {
1059 // Get all comments and MIME-Types for current table
1060 $comments_copy_rs = PMA_queryAsControlUser(
1061 'SELECT column_name, comment'
1062 . ($GLOBALS['cfgRelation']['mimework']
1063 ?
', mimetype, transformation, transformation_options'
1066 . PMA_Util
::backquote($GLOBALS['cfgRelation']['db'])
1068 . PMA_Util
::backquote($GLOBALS['cfgRelation']['column_info'])
1071 . PMA_Util
::sqlAddSlashes($source_db) . '\''
1073 . ' table_name = \''
1074 . PMA_Util
::sqlAddSlashes($source_table) . '\''
1077 // Write every comment as new copied entry. [MIME]
1078 while ($comments_copy_row
1079 = $GLOBALS['dbi']->fetchAssoc($comments_copy_rs)) {
1080 $new_comment_query = 'REPLACE INTO '
1081 . PMA_Util
::backquote($GLOBALS['cfgRelation']['db'])
1082 . '.' . PMA_Util
::backquote(
1083 $GLOBALS['cfgRelation']['column_info']
1085 . ' (db_name, table_name, column_name, comment'
1086 . ($GLOBALS['cfgRelation']['mimework']
1087 ?
', mimetype, transformation, transformation_options'
1089 . ') ' . ' VALUES(' . '\'' . PMA_Util
::sqlAddSlashes($target_db)
1090 . '\',\'' . PMA_Util
::sqlAddSlashes($target_table) . '\',\''
1091 . PMA_Util
::sqlAddSlashes($comments_copy_row['column_name'])
1093 . ($GLOBALS['cfgRelation']['mimework']
1094 ?
',\'' . PMA_Util
::sqlAddSlashes(
1095 $comments_copy_row['comment']
1097 . '\',' . '\'' . PMA_Util
::sqlAddSlashes(
1098 $comments_copy_row['mimetype']
1100 . '\',' . '\'' . PMA_Util
::sqlAddSlashes(
1101 $comments_copy_row['transformation']
1103 . '\',' . '\'' . PMA_Util
::sqlAddSlashes(
1104 $comments_copy_row['transformation_options']
1109 PMA_queryAsControlUser($new_comment_query);
1111 $GLOBALS['dbi']->freeResult($comments_copy_rs);
1112 unset($comments_copy_rs);
1115 // duplicating the bookmarks must not be done here, but
1118 $get_fields = array('display_field');
1119 $where_fields = array(
1120 'db_name' => $source_db,
1121 'table_name' => $source_table
1123 $new_fields = array(
1124 'db_name' => $target_db,
1125 'table_name' => $target_table
1127 PMA_Table
::duplicateInfo(
1136 * @todo revise this code when we support cross-db relations
1138 $get_fields = array(
1143 $where_fields = array(
1144 'master_db' => $source_db,
1145 'master_table' => $source_table
1147 $new_fields = array(
1148 'master_db' => $target_db,
1149 'foreign_db' => $target_db,
1150 'master_table' => $target_table
1152 PMA_Table
::duplicateInfo(
1160 $get_fields = array(
1165 $where_fields = array(
1166 'foreign_db' => $source_db,
1167 'foreign_table' => $source_table
1169 $new_fields = array(
1170 'master_db' => $target_db,
1171 'foreign_db' => $target_db,
1172 'foreign_table' => $target_table
1174 PMA_Table
::duplicateInfo(
1183 * @todo Can't get duplicating PDFs the right way. The
1184 * page numbers always get screwed up independently from
1185 * duplication because the numbers do not seem to be stored on a
1186 * per-database basis. Would the author of pdf support please
1187 * have a look at it?
1189 $get_fields = array('page_descr');
1190 $where_fields = array('db_name' => $source_db);
1191 $new_fields = array('db_name' => $target_db);
1192 $last_id = PMA_Table::duplicateInfo(
1200 if (isset($last_id) && $last_id >= 0) {
1201 $get_fields = array('x', 'y');
1202 $where_fields = array(
1203 'db_name' => $source_db,
1204 'table_name' => $source_table
1206 $new_fields = array(
1207 'db_name' => $target_db,
1208 'table_name' => $target_table,
1209 'pdf_page_number' => $last_id
1211 PMA_Table::duplicateInfo(
1225 * checks if given name is a valid table name,
1226 * currently if not empty, trailing spaces, '.', '/' and '\'
1228 * @param string $table_name name to check
1230 * @todo add check for valid chars in filename on current system/os
1231 * @see http://dev.mysql.com/doc/refman/5.0/en/legal-names.html
1233 * @return boolean whether the string is valid or not
1235 static function isValidName($table_name)
1237 if ($table_name !== trim($table_name)) {
1242 if (! /*overload*/mb_strlen($table_name)) {
1247 if (preg_match('/[.\/\\\\]+/i', $table_name)) {
1248 // illegal char . / \
1258 * @param string $new_name new table name
1259 * @param string $new_db new database name
1261 * @return bool success
1263 function rename($new_name, $new_db = null)
1265 $lowerCaseTableNames = PMA_Util
::cacheGet(
1266 'lower_case_table_names',
1268 return $GLOBALS['dbi']->fetchValue(
1269 "SELECT @@lower_case_table_names"
1273 if ($lowerCaseTableNames) {
1274 $new_name = strtolower($new_name);
1277 if (null !== $new_db && $new_db !== $this->getDbName()) {
1278 // Ensure the target is valid
1279 if (! $GLOBALS['pma']->databases
->exists($new_db)) {
1280 $this->errors
[] = __('Invalid database:') . ' ' . $new_db;
1284 $new_db = $this->getDbName();
1287 $new_table = new PMA_Table($new_name, $new_db);
1289 if ($this->getFullName() === $new_table->getFullName()) {
1293 if (! PMA_Table
::isValidName($new_name)) {
1294 $this->errors
[] = __('Invalid table name:') . ' '
1295 . $new_table->getFullName();
1299 // If the table is moved to a different database drop its triggers first
1300 $triggers = $this->_dbi
->getTriggers(
1301 $this->getDbName(), $this->getName(), ''
1303 $handle_triggers = $this->getDbName() != $new_db && $triggers;
1304 if ($handle_triggers) {
1305 foreach ($triggers as $trigger) {
1306 $sql = 'DROP TRIGGER IF EXISTS '
1307 . PMA_Util
::backquote($this->getDbName())
1308 . '.' . PMA_Util
::backquote($trigger['name']) . ';';
1309 $this->_dbi
->query($sql);
1314 * tested also for a view, in MySQL 5.0.92, 5.1.55 and 5.5.13
1316 $GLOBALS['sql_query'] = '
1317 RENAME TABLE ' . $this->getFullName(true) . '
1318 TO ' . $new_table->getFullName(true) . ';';
1319 // I don't think a specific error message for views is necessary
1320 if (! $this->_dbi
->query($GLOBALS['sql_query'])) {
1321 // Restore triggers in the old database
1322 if ($handle_triggers) {
1323 $this->_dbi
->selectDb($this->getDbName());
1324 foreach ($triggers as $trigger) {
1325 $this->_dbi
->query($trigger['create']);
1328 $this->errors
[] = sprintf(
1329 __('Failed to rename table %1$s to %2$s!'),
1330 $this->getFullName(),
1331 $new_table->getFullName()
1336 $old_name = $this->getName();
1337 $old_db = $this->getDbName();
1338 $this->_name
= $new_name;
1339 $this->_db_name
= $new_db;
1341 // Renable table in configuration storage
1342 PMA_REL_renameTable(
1344 $old_name, $new_name
1347 $this->messages
[] = sprintf(
1348 __('Table %1$s has been renamed to %2$s.'),
1349 htmlspecialchars($old_name),
1350 htmlspecialchars($new_name)
1356 * Get all unique columns
1358 * returns an array with all columns with unique content, in fact these are
1359 * all columns being single indexed in PRIMARY or UNIQUE
1362 * - PRIMARY(id) // id
1363 * - UNIQUE(name) // name
1364 * - PRIMARY(fk_id1, fk_id2) // NONE
1365 * - UNIQUE(x,y) // NONE
1367 * @param bool $backquoted whether to quote name with backticks ``
1368 * @param bool $fullName whether to include full name of the table as a prefix
1372 public function getUniqueColumns($backquoted = true, $fullName = true)
1374 $sql = $this->_dbi
->getTableIndexesSql(
1379 $uniques = $this->_dbi
->fetchResult(
1381 array('Key_name', null),
1386 foreach ($uniques as $index) {
1387 if (count($index) > 1) {
1391 $possible_column = $this->getFullName($backquoted) . '.';
1393 $possible_column = '';
1396 $possible_column .= PMA_Util
::backquote($index[0]);
1398 $possible_column .= $index[0];
1400 // a column might have a primary and an unique index on it
1401 if (! in_array($possible_column, $return)) {
1402 $return[] = $possible_column;
1410 * Get all indexed columns
1412 * returns an array with all columns that make use of an index
1414 * e.g. index(col1, col2) would return col1, col2
1416 * @param bool $backquoted whether to quote name with backticks ``
1417 * @param bool $fullName whether to include full name of the table as a prefix
1421 public function getIndexedColumns($backquoted = true, $fullName = true)
1423 $sql = $this->_dbi
->getTableIndexesSql(
1428 $indexed = $this->_dbi
->fetchResult($sql, 'Column_name', 'Column_name');
1431 foreach ($indexed as $column) {
1432 $return[] = ($fullName ?
$this->getFullName($backquoted) . '.' : '')
1433 . ($backquoted ? PMA_Util
::backquote($column) : $column);
1442 * returns an array with all columns
1444 * @param bool $backquoted whether to quote name with backticks ``
1445 * @param bool $fullName whether to include full name of the table as a prefix
1449 public function getColumns($backquoted = true, $fullName = true)
1451 $sql = 'SHOW COLUMNS FROM ' . $this->getFullName(true);
1452 $indexed = $this->_dbi
->fetchResult($sql, 'Field', 'Field');
1455 foreach ($indexed as $column) {
1456 $return[] = ($fullName ?
$this->getFullName($backquoted) . '.' : '')
1457 . ($backquoted ? PMA_Util
::backquote($column) : $column);
1464 * Get meta info for fields in table
1468 public function getColumnsMeta()
1470 $move_columns_sql_query = sprintf(
1471 'SELECT * FROM %s.%s LIMIT 1',
1472 PMA_Util
::backquote($this->_db_name
),
1473 PMA_Util
::backquote($this->_name
)
1475 $move_columns_sql_result = $this->_dbi
->tryQuery($move_columns_sql_query);
1476 if ($move_columns_sql_result !== false) {
1477 return $this->_dbi
->getFieldsMeta($move_columns_sql_result);
1479 // unsure how to reproduce but it was seen on the reporting server
1485 * Return UI preferences for this table from phpMyAdmin database.
1489 protected function getUiPrefsFromDb()
1491 $cfgRelation = PMA_getRelationsParam();
1492 $pma_table = PMA_Util
::backquote($cfgRelation['db']) . "."
1493 . PMA_Util
::backquote($cfgRelation['table_uiprefs']);
1495 // Read from phpMyAdmin database
1496 $sql_query = " SELECT `prefs` FROM " . $pma_table
1497 . " WHERE `username` = '" . $GLOBALS['cfg']['Server']['user'] . "'"
1498 . " AND `db_name` = '" . PMA_Util
::sqlAddSlashes($this->_db_name
) . "'"
1499 . " AND `table_name` = '" . PMA_Util
::sqlAddSlashes($this->_name
) . "'";
1501 $row = $this->_dbi
->fetchArray(PMA_queryAsControlUser($sql_query));
1502 if (isset($row[0])) {
1503 return json_decode($row[0], true);
1510 * Save this table's UI preferences into phpMyAdmin database.
1512 * @return true|PMA_Message
1514 protected function saveUiPrefsToDb()
1516 $cfgRelation = PMA_getRelationsParam();
1517 $pma_table = PMA_Util
::backquote($cfgRelation['db']) . "."
1518 . PMA_Util
::backquote($cfgRelation['table_uiprefs']);
1520 $secureDbName = PMA_Util
::sqlAddSlashes($this->_db_name
);
1522 $username = $GLOBALS['cfg']['Server']['user'];
1523 $sql_query = " REPLACE INTO " . $pma_table
1524 . " (username, db_name, table_name, prefs) VALUES ('"
1525 . $username . "', '" . $secureDbName
1526 . "', '" . PMA_Util
::sqlAddSlashes($this->_name
) . "', '"
1527 . PMA_Util
::sqlAddSlashes(json_encode($this->uiprefs
)) . "')";
1529 $success = $this->_dbi
->tryQuery($sql_query, $GLOBALS['controllink']);
1532 $message = PMA_Message
::error(
1533 __('Could not save table UI preferences!')
1535 $message->addMessage('<br /><br />');
1536 $message->addMessage(
1537 PMA_Message
::rawError(
1538 $this->_dbi
->getError($GLOBALS['controllink'])
1544 // Remove some old rows in table_uiprefs if it exceeds the configured
1546 $sql_query = 'SELECT COUNT(*) FROM ' . $pma_table;
1547 $rows_count = $this->_dbi
->fetchValue($sql_query);
1548 $max_rows = $GLOBALS['cfg']['Server']['MaxTableUiprefs'];
1549 if ($rows_count > $max_rows) {
1550 $num_rows_to_delete = $rows_count - $max_rows;
1552 = ' DELETE FROM ' . $pma_table .
1553 ' ORDER BY last_update ASC' .
1554 ' LIMIT ' . $num_rows_to_delete;
1555 $success = $this->_dbi
->tryQuery(
1556 $sql_query, $GLOBALS['controllink']
1560 $message = PMA_Message
::error(
1563 'Failed to cleanup table UI preferences (see ' .
1564 '$cfg[\'Servers\'][$i][\'MaxTableUiprefs\'] %s)'
1566 PMA_Util
::showDocu('config', 'cfg_Servers_MaxTableUiprefs')
1569 $message->addMessage('<br /><br />');
1570 $message->addMessage(
1571 PMA_Message
::rawError(
1572 $this->_dbi
->getError($GLOBALS['controllink'])
1584 * Loads the UI preferences for this table.
1585 * If pmadb and table_uiprefs is set, it will load the UI preferences from
1586 * phpMyAdmin database.
1590 protected function loadUiPrefs()
1592 $cfgRelation = PMA_getRelationsParam();
1593 $server_id = $GLOBALS['server'];
1595 // set session variable if it's still undefined
1596 if (!isset($_SESSION['tmpval']['table_uiprefs'][$server_id][$this->_db_name
][$this->_name
])) {
1597 // check whether we can get from pmadb
1598 $_SESSION['tmpval']['table_uiprefs'][$server_id][$this->_db_name
]
1599 [$this->_name
] = $cfgRelation['uiprefswork']
1600 ?
$this->getUiPrefsFromDb()
1603 $this->uiprefs
=& $_SESSION['tmpval']['table_uiprefs'][$server_id]
1604 [$this->_db_name
][$this->_name
];
1608 * Get a property from UI preferences.
1609 * Return false if the property is not found.
1610 * Available property:
1611 * - PROP_SORTED_COLUMN
1612 * - PROP_COLUMN_ORDER
1613 * - PROP_COLUMN_VISIB
1615 * @param string $property property
1619 public function getUiProp($property)
1621 if (! isset($this->uiprefs
)) {
1622 $this->loadUiPrefs();
1625 // do checking based on property
1626 if ($property == self
::PROP_SORTED_COLUMN
) {
1627 if (!isset($this->uiprefs
[$property])) {
1631 if (!isset($_REQUEST['discard_remembered_sort'])) {
1632 // check if the column name exists in this table
1633 $tmp = explode(' ', $this->uiprefs
[$property]);
1635 //remove backquoting from colname
1636 $colname = str_replace('`', '', $colname);
1637 //get the available column name without backquoting
1638 $avail_columns = $this->getColumns(false);
1640 foreach ($avail_columns as $each_col) {
1641 // check if $each_col ends with $colname
1645 /*overload*/mb_strlen($each_col)
1646 - /*overload*/mb_strlen($colname)
1649 return $this->uiprefs
[$property];
1653 // remove the property, since it no longer exists in database
1654 $this->removeUiProp(self
::PROP_SORTED_COLUMN
);
1658 if ($property == self
::PROP_COLUMN_ORDER
1659 ||
$property == self
::PROP_COLUMN_VISIB
1661 if ($this->isView() ||
!isset($this->uiprefs
[$property])) {
1665 // check if the table has not been modified
1666 if ($this->getStatusInfo('Create_time') == $this->uiprefs
['CREATE_TIME']
1668 return $this->uiprefs
[$property];
1671 // remove the property, since the table has been modified
1672 $this->removeUiProp(self
::PROP_COLUMN_ORDER
);
1676 // default behaviour for other property:
1677 return isset($this->uiprefs
[$property]) ?
$this->uiprefs
[$property] : false;
1681 * Set a property from UI preferences.
1682 * If pmadb and table_uiprefs is set, it will save the UI preferences to
1683 * phpMyAdmin database.
1684 * Available property:
1685 * - PROP_SORTED_COLUMN
1686 * - PROP_COLUMN_ORDER
1687 * - PROP_COLUMN_VISIB
1689 * @param string $property Property
1690 * @param mixed $value Value for the property
1691 * @param string $table_create_time Needed for PROP_COLUMN_ORDER
1692 * and PROP_COLUMN_VISIB
1694 * @return boolean|PMA_Message
1696 public function setUiProp($property, $value, $table_create_time = null)
1698 if (! isset($this->uiprefs
)) {
1699 $this->loadUiPrefs();
1701 // we want to save the create time if the property is PROP_COLUMN_ORDER
1702 if (! $this->isView()
1703 && ($property == self
::PROP_COLUMN_ORDER
1704 ||
$property == self
::PROP_COLUMN_VISIB
)
1706 $curr_create_time = $this->getStatusInfo('CREATE_TIME');
1707 if (isset($table_create_time)
1708 && $table_create_time == $curr_create_time
1710 $this->uiprefs
['CREATE_TIME'] = $curr_create_time;
1712 // there is no $table_create_time, or
1713 // supplied $table_create_time is older than current create time,
1715 return PMA_Message
::error(
1718 'Cannot save UI property "%s". The changes made will ' .
1719 'not be persistent after you refresh this page. ' .
1720 'Please check if the table structure has been changed.'
1728 $this->uiprefs
[$property] = $value;
1730 // check if pmadb is set
1731 $cfgRelation = PMA_getRelationsParam();
1732 if ($cfgRelation['uiprefswork']) {
1733 return $this->saveUiprefsToDb();
1739 * Remove a property from UI preferences.
1741 * @param string $property the property
1743 * @return true|PMA_Message
1745 public function removeUiProp($property)
1747 if (! isset($this->uiprefs
)) {
1748 $this->loadUiPrefs();
1750 if (isset($this->uiprefs
[$property])) {
1751 unset($this->uiprefs
[$property]);
1753 // check if pmadb is set
1754 $cfgRelation = PMA_getRelationsParam();
1755 if ($cfgRelation['uiprefswork']) {
1756 return $this->saveUiprefsToDb();
1763 * Get all column names which are MySQL reserved words
1768 public function getReservedColumnNames()
1770 $columns = $this->getColumns(false);
1772 foreach ($columns as $column) {
1773 $temp = explode('.', $column);
1774 $column_name = $temp[2];
1775 if (SqlParser\Context
::isKeyword($column_name, true)) {
1776 $return[] = $column_name;
1783 * Function to get the name and type of the columns of a table
1787 public function getNameAndTypeOfTheColumns()
1790 foreach ($this->_dbi
->getColumnsFull(
1791 $this->_db_name
, $this->_name
1793 if (preg_match('@^(set|enum)\((.+)\)$@i', $row['Type'], $tmp)) {
1794 $tmp[2] = /*overload*/
1796 preg_replace('@([^,])\'\'@', '\\1\\\'', ',' . $tmp[2]), 1
1798 $columns[$row['Field']] = $tmp[1] . '('
1799 . str_replace(',', ', ', $tmp[2]) . ')';
1801 $columns[$row['Field']] = $row['Type'];
1808 * Get index with index name
1810 * @param string $index Index name
1814 public function getIndex($index)
1816 return PMA_Index
::singleton($this->_db_name
, $this->_name
, $index);
1820 * Function to get the sql query for index creation or edit
1822 * @param PMA_Index $index current index
1823 * @param bool &$error whether error occurred or not
1827 public function getSqlQueryForIndexCreateOrEdit($index, &$error)
1829 // $sql_query is the one displayed in the query box
1830 $sql_query = sprintf(
1831 'ALTER TABLE %s.%s',
1832 PMA_Util
::backquote($this->_db_name
),
1833 PMA_Util
::backquote($this->_name
)
1836 // Drops the old index
1837 if (! empty($_REQUEST['old_index'])) {
1838 if ($_REQUEST['old_index'] == 'PRIMARY') {
1839 $sql_query .= ' DROP PRIMARY KEY,';
1841 $sql_query .= sprintf(
1843 PMA_Util
::backquote($_REQUEST['old_index'])
1848 // Builds the new one
1849 switch ($index->getChoice()) {
1851 if ($index->getName() == '') {
1852 $index->setName('PRIMARY');
1853 } elseif ($index->getName() != 'PRIMARY') {
1854 $error = PMA_Message
::error(
1855 __('The name of the primary key must be "PRIMARY"!')
1858 $sql_query .= ' ADD PRIMARY KEY';
1864 if ($index->getName() == 'PRIMARY') {
1865 $error = PMA_Message
::error(
1866 __('Can\'t rename index to PRIMARY!')
1869 $sql_query .= sprintf(
1873 if ($index->getName()) {
1874 $sql_query .= PMA_Util
::backquote($index->getName());
1879 $index_fields = array();
1880 foreach ($index->getColumns() as $key => $column) {
1881 $index_fields[$key] = PMA_Util
::backquote($column->getName());
1882 if ($column->getSubPart()) {
1883 $index_fields[$key] .= '(' . $column->getSubPart() . ')';
1887 if (empty($index_fields)) {
1888 $error = PMA_Message
::error(__('No index parts defined!'));
1890 $sql_query .= ' (' . implode(', ', $index_fields) . ')';
1893 $keyBlockSizes = $index->getKeyBlockSize();
1894 if (! empty($keyBlockSizes)) {
1895 $sql_query .= sprintf(
1896 ' KEY_BLOCK_SIZE = ',
1897 PMA_Util
::sqlAddSlashes($keyBlockSizes)
1901 // specifying index type is allowed only for primary, unique and index only
1902 $type = $index->getType();
1903 if ($index->getChoice() != 'SPATIAL'
1904 && $index->getChoice() != 'FULLTEXT'
1905 && in_array($type, PMA_Index
::getIndexTypes())
1907 $sql_query .= ' USING ' . $type;
1910 $parser = $index->getParser();
1911 if ($index->getChoice() == 'FULLTEXT' && ! empty($parser)) {
1912 $sql_query .= ' WITH PARSER ' . PMA_Util
::sqlAddSlashes($parser);
1915 $comment = $index->getComment();
1916 if (! empty($comment)) {
1917 $sql_query .= sprintf(
1919 PMA_Util
::sqlAddSlashes($comment)
1929 * Function to handle update for display field
1931 * @param string $disp current display field
1932 * @param string $display_field display field
1933 * @param array $cfgRelation configuration relation
1935 * @return boolean True on update succeed or False on failure
1937 public function updateDisplayField($disp, $display_field, $cfgRelation)
1941 if ($display_field == '') {
1942 $upd_query = 'DELETE FROM '
1943 . PMA_Util
::backquote($GLOBALS['cfgRelation']['db'])
1944 . '.' . PMA_Util
::backquote($cfgRelation['table_info'])
1945 . ' WHERE db_name = \''
1946 . PMA_Util
::sqlAddSlashes($this->_db_name
) . '\''
1947 . ' AND table_name = \''
1948 . PMA_Util
::sqlAddSlashes($this->_name
) . '\'';
1949 } elseif ($disp != $display_field) {
1950 $upd_query = 'UPDATE '
1951 . PMA_Util
::backquote($GLOBALS['cfgRelation']['db'])
1952 . '.' . PMA_Util
::backquote($cfgRelation['table_info'])
1953 . ' SET display_field = \''
1954 . PMA_Util
::sqlAddSlashes($display_field) . '\''
1955 . ' WHERE db_name = \''
1956 . PMA_Util
::sqlAddSlashes($this->_db_name
) . '\''
1957 . ' AND table_name = \''
1958 . PMA_Util
::sqlAddSlashes($this->_name
) . '\'';
1960 } elseif ($display_field != '') {
1961 $upd_query = 'INSERT INTO '
1962 . PMA_Util
::backquote($GLOBALS['cfgRelation']['db'])
1963 . '.' . PMA_Util
::backquote($cfgRelation['table_info'])
1964 . '(db_name, table_name, display_field) VALUES('
1965 . '\'' . PMA_Util
::sqlAddSlashes($this->_db_name
) . '\','
1966 . '\'' . PMA_Util
::sqlAddSlashes($this->_name
) . '\','
1967 . '\'' . PMA_Util
::sqlAddSlashes($display_field) . '\')';
1973 $GLOBALS['controllink'],
1983 * Function to get update query for updating internal relations
1985 * @param array $multi_edit_columns_name multi edit column names
1986 * @param array $destination_db destination tables
1987 * @param array $destination_table destination tables
1988 * @param array $destination_column destination columns
1989 * @param array $cfgRelation configuration relation
1990 * @param array|null $existrel db, table, column
1994 public function updateInternalRelations($multi_edit_columns_name,
1995 $destination_db, $destination_table, $destination_column,
1996 $cfgRelation, $existrel
1999 foreach ($destination_db as $master_field_md5 => $foreign_db) {
2001 // Map the fieldname's md5 back to its real name
2002 $master_field = $multi_edit_columns_name[$master_field_md5];
2003 $foreign_table = $destination_table[$master_field_md5];
2004 $foreign_field = $destination_column[$master_field_md5];
2005 if (! empty($foreign_db)
2006 && ! empty($foreign_table)
2007 && ! empty($foreign_field)
2009 if (! isset($existrel[$master_field])) {
2010 $upd_query = 'INSERT INTO '
2011 . PMA_Util
::backquote($GLOBALS['cfgRelation']['db'])
2012 . '.' . PMA_Util
::backquote($cfgRelation['relation'])
2013 . '(master_db, master_table, master_field, foreign_db,'
2014 . ' foreign_table, foreign_field)'
2016 . '\'' . PMA_Util
::sqlAddSlashes($this->_db_name
) . '\', '
2017 . '\'' . PMA_Util
::sqlAddSlashes($this->_name
) . '\', '
2018 . '\'' . PMA_Util
::sqlAddSlashes($master_field) . '\', '
2019 . '\'' . PMA_Util
::sqlAddSlashes($foreign_db) . '\', '
2020 . '\'' . PMA_Util
::sqlAddSlashes($foreign_table) . '\','
2021 . '\'' . PMA_Util
::sqlAddSlashes($foreign_field) . '\')';
2023 } elseif ($existrel[$master_field]['foreign_db'] != $foreign_db
2024 ||
$existrel[$master_field]['foreign_table'] != $foreign_table
2025 ||
$existrel[$master_field]['foreign_field'] != $foreign_field
2027 $upd_query = 'UPDATE '
2028 . PMA_Util
::backquote($GLOBALS['cfgRelation']['db'])
2029 . '.' . PMA_Util
::backquote($cfgRelation['relation'])
2030 . ' SET foreign_db = \''
2031 . PMA_Util
::sqlAddSlashes($foreign_db) . '\', '
2032 . ' foreign_table = \''
2033 . PMA_Util
::sqlAddSlashes($foreign_table) . '\', '
2034 . ' foreign_field = \''
2035 . PMA_Util
::sqlAddSlashes($foreign_field) . '\' '
2036 . ' WHERE master_db = \''
2037 . PMA_Util
::sqlAddSlashes($this->_db_name
) . '\''
2038 . ' AND master_table = \''
2039 . PMA_Util
::sqlAddSlashes($this->_name
) . '\''
2040 . ' AND master_field = \''
2041 . PMA_Util
::sqlAddSlashes($master_field) . '\'';
2042 } // end if... else....
2043 } elseif (isset($existrel[$master_field])) {
2044 $upd_query = 'DELETE FROM '
2045 . PMA_Util
::backquote($GLOBALS['cfgRelation']['db'])
2046 . '.' . PMA_Util
::backquote($cfgRelation['relation'])
2047 . ' WHERE master_db = \''
2048 . PMA_Util
::sqlAddSlashes($this->_db_name
) . '\''
2049 . ' AND master_table = \''
2050 . PMA_Util
::sqlAddSlashes($this->_name
) . '\''
2051 . ' AND master_field = \''
2052 . PMA_Util
::sqlAddSlashes($master_field) . '\'';
2053 } // end if... else....
2055 if (isset($upd_query)) {
2058 $GLOBALS['controllink'],
2069 * Function to handle foreign key updates
2071 * @param array $destination_foreign_db destination foreign database
2072 * @param array $multi_edit_columns_name multi edit column names
2073 * @param array $destination_foreign_table destination foreign table
2074 * @param array $destination_foreign_column destination foreign column
2075 * @param array $options_array options array
2076 * @param string $table current table
2077 * @param array $existrel_foreign db, table, column
2081 public function updateForeignKeys($destination_foreign_db,
2082 $multi_edit_columns_name, $destination_foreign_table,
2083 $destination_foreign_column, $options_array, $table, $existrel_foreign
2086 $preview_sql_data = '';
2087 $display_query = '';
2088 $seen_error = false;
2090 foreach ($destination_foreign_db as $master_field_md5 => $foreign_db) {
2094 // Map the fieldname's md5 back to its real name
2095 $master_field = $multi_edit_columns_name[$master_field_md5];
2097 $foreign_table = $destination_foreign_table[$master_field_md5];
2098 $foreign_field = $destination_foreign_column[$master_field_md5];
2100 if (isset($existrel_foreign[$master_field_md5]['ref_db_name'])) {
2101 $ref_db_name = $existrel_foreign[$master_field_md5]['ref_db_name'];
2103 $ref_db_name = $GLOBALS['db'];
2106 $empty_fields = false;
2107 foreach ($master_field as $key => $one_field) {
2108 if ((! empty($one_field) && empty($foreign_field[$key]))
2109 ||
(empty($one_field) && ! empty($foreign_field[$key]))
2111 $empty_fields = true;
2114 if (empty($one_field) && empty($foreign_field[$key])) {
2115 unset($master_field[$key]);
2116 unset($foreign_field[$key]);
2120 if (! empty($foreign_db)
2121 && ! empty($foreign_table)
2124 if (isset($existrel_foreign[$master_field_md5])) {
2126 = $existrel_foreign[$master_field_md5]['constraint'];
2127 $on_delete = !empty(
2128 $existrel_foreign[$master_field_md5]['on_delete']
2130 ?
$existrel_foreign[$master_field_md5]['on_delete']
2132 $on_update = ! empty(
2133 $existrel_foreign[$master_field_md5]['on_update']
2135 ?
$existrel_foreign[$master_field_md5]['on_update']
2138 if ($ref_db_name != $foreign_db
2139 ||
$existrel_foreign[$master_field_md5]['ref_table_name'] != $foreign_table
2140 ||
$existrel_foreign[$master_field_md5]['ref_index_list'] != $foreign_field
2141 ||
$existrel_foreign[$master_field_md5]['index_list'] != $master_field
2142 ||
$_REQUEST['constraint_name'][$master_field_md5] != $constraint_name
2143 ||
($_REQUEST['on_delete'][$master_field_md5] != $on_delete)
2144 ||
($_REQUEST['on_update'][$master_field_md5] != $on_update)
2146 // another foreign key is already defined for this field
2147 // or an option has been changed for ON DELETE or ON UPDATE
2150 } // end if... else....
2152 // no key defined for this field(s)
2155 } elseif (isset($existrel_foreign[$master_field_md5])) {
2157 } // end if... else....
2159 $tmp_error_drop = false;
2161 $drop_query = 'ALTER TABLE ' . PMA_Util
::backquote($table)
2162 . ' DROP FOREIGN KEY ' . PMA_Util
::backquote($existrel_foreign[$master_field_md5]['constraint']) . ';';
2164 if (! isset($_REQUEST['preview_sql'])) {
2165 $display_query .= $drop_query . "\n";
2166 $this->_dbi
->tryQuery($drop_query);
2167 $tmp_error_drop = $this->_dbi
->getError();
2169 if (! empty($tmp_error_drop)) {
2171 $html_output .= PMA_Util
::mysqlDie(
2172 $tmp_error_drop, $drop_query, false, '', false
2177 $preview_sql_data .= $drop_query . "\n";
2180 $tmp_error_create = false;
2185 $create_query = $this->_getSQLToCreateForeignKey(
2186 $table, $master_field, $foreign_db, $foreign_table, $foreign_field,
2187 $_REQUEST['constraint_name'][$master_field_md5],
2188 $options_array[$_REQUEST['on_delete'][$master_field_md5]],
2189 $options_array[$_REQUEST['on_update'][$master_field_md5]]
2192 if (! isset($_REQUEST['preview_sql'])) {
2193 $display_query .= $create_query . "\n";
2194 $this->_dbi
->tryQuery($create_query);
2195 $tmp_error_create = $this->_dbi
->getError();
2196 if (! empty($tmp_error_create)) {
2199 if (substr($tmp_error_create, 1, 4) == '1005') {
2200 $message = PMA_Message
::error(
2202 'Error creating foreign key on %1$s (check data ' .
2206 $message->addParam(implode(', ', $master_field));
2207 $html_output .= $message->getDisplay();
2209 $html_output .= PMA_Util
::mysqlDie(
2210 $tmp_error_create, $create_query, false, '', false
2213 $html_output .= PMA_Util
::showMySQLDocu(
2214 'InnoDB_foreign_key_constraints'
2218 $preview_sql_data .= $create_query . "\n";
2221 // this is an alteration and the old constraint has been dropped
2222 // without creation of a new one
2223 if ($drop && $create && empty($tmp_error_drop)
2224 && ! empty($tmp_error_create)
2226 // a rollback may be better here
2227 $sql_query_recreate = '# Restoring the dropped constraint...' . "\n";
2228 $sql_query_recreate .= $this->_getSQLToCreateForeignKey(
2231 $existrel_foreign[$master_field_md5]['ref_db_name'],
2232 $existrel_foreign[$master_field_md5]['ref_table_name'],
2233 $existrel_foreign[$master_field_md5]['ref_index_list'],
2234 $existrel_foreign[$master_field_md5]['constraint'],
2235 $options_array[$existrel_foreign[$master_field_md5]['on_delete']],
2236 $options_array[$existrel_foreign[$master_field_md5]['on_update']]
2238 if (! isset($_REQUEST['preview_sql'])) {
2239 $display_query .= $sql_query_recreate . "\n";
2240 $this->_dbi
->tryQuery($sql_query_recreate);
2242 $preview_sql_data .= $sql_query_recreate;
2256 * Returns the SQL query for foreign key constraint creation
2258 * @param string $table table name
2259 * @param array $field field names
2260 * @param string $foreignDb foreign database name
2261 * @param string $foreignTable foreign table name
2262 * @param array $foreignField foreign field names
2263 * @param string $name name of the constraint
2264 * @param string $onDelete on delete action
2265 * @param string $onUpdate on update action
2267 * @return string SQL query for foreign key constraint creation
2269 private function _getSQLToCreateForeignKey(
2279 $sql_query = 'ALTER TABLE ' . PMA_Util
::backquote($table) . ' ADD ';
2280 // if user entered a constraint name
2281 if (! empty($name)) {
2282 $sql_query .= ' CONSTRAINT ' . PMA_Util
::backquote($name);
2285 foreach ($field as $key => $one_field) {
2286 $field[$key] = PMA_Util
::backquote($one_field);
2288 foreach ($foreignField as $key => $one_field) {
2289 $foreignField[$key] = PMA_Util
::backquote($one_field);
2291 $sql_query .= ' FOREIGN KEY (' . implode(', ', $field) . ') REFERENCES '
2292 . ($this->_db_name
!= $foreignDb
2293 ? PMA_Util
::backquote($foreignDb) . '.' : '')
2294 . PMA_Util
::backquote($foreignTable)
2295 . '(' . implode(', ', $foreignField) . ')';
2297 if (! empty($onDelete)) {
2298 $sql_query .= ' ON DELETE ' . $onDelete;
2300 if (! empty($onUpdate)) {
2301 $sql_query .= ' ON UPDATE ' . $onUpdate;
2309 * Returns the generation expression for virtual columns
2311 * @param string $column name of the column
2313 * @return array|boolean associative array of column name and their expressions
2314 * or false on failure
2316 public function getColumnGenerationExpression($column = null)
2318 $serverType = PMA_Util
::getServerType();
2319 if ($serverType == 'MySQL'
2320 && PMA_MYSQL_INT_VERSION
> 50705
2321 && ! $GLOBALS['cfg']['Server']['DisableIS']
2325 `COLUMN_NAME` AS `Field`,
2326 `GENERATION_EXPRESSION` AS `Expression`
2328 `information_schema`.`COLUMNS`
2330 `TABLE_SCHEMA` = '" . PMA_Util
::sqlAddSlashes($this->_db_name
) . "'
2331 AND `TABLE_NAME` = '" . PMA_Util
::sqlAddSlashes($this->_name
) . "'";
2332 if ($column != null) {
2333 $sql .= " AND `COLUMN_NAME` = '" . PMA_Util
::sqlAddSlashes($column)
2336 $columns = $this->_dbi
->fetchResult($sql, 'Field', 'Expression');
2340 $createTable = $this->showCreate();
2341 if (!$createTable) {
2345 $parser = new SqlParser\
Parser($createTable);
2347 * @var SqlParser\Statements\CreateStatement $stmt
2349 $stmt = $parser->statements
[0];
2350 $fields = SqlParser\Utils\Table
::getFields($stmt);
2351 if ($column != null) {
2352 $expression = isset($fields[$column]['expr']) ?
2353 substr($fields[$column]['expr'], 1, -1) : '';
2354 return array($column => $expression);
2358 foreach ($fields as $field => $options) {
2359 if (isset($options['expr'])) {
2360 $ret[$field] = substr($options['expr'], 1, -1);
2367 * Returns the CREATE statement for this table
2371 public function showCreate()
2373 return $this->_dbi
->fetchValue(
2374 'SHOW CREATE TABLE ' . PMA_Util
::backquote($this->_db_name
) . '.'
2375 . PMA_Util
::backquote($this->_name
),
2381 * Returns the real row count for a table
2385 function getRealRowCountTable()
2387 // SQL query to get row count for a table.
2388 $result = $this->_dbi
->fetchSingleRow(
2390 'SELECT COUNT(*) AS %s FROM %s.%s',
2391 PMA_Util
::backquote('row_count'),
2392 PMA_Util
::backquote($this->_db_name
),
2393 PMA_Util
::backquote($this->_name
)
2396 return $result['row_count'];
2400 * Get columns with indexes
2402 * @param int $types types bitmask
2404 * @return array an array of columns
2406 function getColumnsWithIndex($types)
2408 $columns_with_index = array();
2410 PMA_Index
::getFromTableByChoice(
2416 $columns = $index->getColumns();
2417 foreach ($columns as $column_name => $dummy) {
2418 $columns_with_index[] = $column_name;
2421 return $columns_with_index;