Table.class.php 58 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668
  1. <?php
  2. /* vim: set expandtab sw=4 ts=4 sts=4: */
  3. /**
  4. * Holds the PMA_Table class
  5. *
  6. * @package PhpMyAdmin
  7. */
  8. if (! defined('PHPMYADMIN')) {
  9. exit;
  10. }
  11. /**
  12. * Handles everything related to tables
  13. *
  14. * @todo make use of PMA_Message and PMA_Error
  15. * @package PhpMyAdmin
  16. */
  17. class PMA_Table
  18. {
  19. /**
  20. * UI preferences properties
  21. */
  22. const PROP_SORTED_COLUMN = 'sorted_col';
  23. const PROP_COLUMN_ORDER = 'col_order';
  24. const PROP_COLUMN_VISIB = 'col_visib';
  25. static $cache = array();
  26. /**
  27. * @var string table name
  28. */
  29. var $name = '';
  30. /**
  31. * @var string database name
  32. */
  33. var $db_name = '';
  34. /**
  35. * @var string engine (innodb, myisam, bdb, ...)
  36. */
  37. var $engine = '';
  38. /**
  39. * @var string type (view, base table, system view)
  40. */
  41. var $type = '';
  42. /**
  43. * @var array settings
  44. */
  45. var $settings = array();
  46. /**
  47. * @var array UI preferences
  48. */
  49. var $uiprefs;
  50. /**
  51. * @var array errors occured
  52. */
  53. var $errors = array();
  54. /**
  55. * @var array messages
  56. */
  57. var $messages = array();
  58. /**
  59. * Constructor
  60. *
  61. * @param string $table_name table name
  62. * @param string $db_name database name
  63. */
  64. function __construct($table_name, $db_name)
  65. {
  66. $this->setName($table_name);
  67. $this->setDbName($db_name);
  68. }
  69. /**
  70. * returns table name
  71. *
  72. * @see PMA_Table::getName()
  73. * @return string table name
  74. */
  75. function __toString()
  76. {
  77. return $this->getName();
  78. }
  79. /**
  80. * return the last error
  81. *
  82. * @return the last error
  83. */
  84. function getLastError()
  85. {
  86. return end($this->errors);
  87. }
  88. /**
  89. * return the last message
  90. *
  91. * @return the last message
  92. */
  93. function getLastMessage()
  94. {
  95. return end($this->messages);
  96. }
  97. /**
  98. * sets table name
  99. *
  100. * @param string $table_name new table name
  101. *
  102. * @return void
  103. */
  104. function setName($table_name)
  105. {
  106. $this->name = $table_name;
  107. }
  108. /**
  109. * returns table name
  110. *
  111. * @param boolean $backquoted whether to quote name with backticks ``
  112. *
  113. * @return string table name
  114. */
  115. function getName($backquoted = false)
  116. {
  117. if ($backquoted) {
  118. return PMA_Util::backquote($this->name);
  119. }
  120. return $this->name;
  121. }
  122. /**
  123. * sets database name for this table
  124. *
  125. * @param string $db_name database name
  126. *
  127. * @return void
  128. */
  129. function setDbName($db_name)
  130. {
  131. $this->db_name = $db_name;
  132. }
  133. /**
  134. * returns database name for this table
  135. *
  136. * @param boolean $backquoted whether to quote name with backticks ``
  137. *
  138. * @return string database name for this table
  139. */
  140. function getDbName($backquoted = false)
  141. {
  142. if ($backquoted) {
  143. return PMA_Util::backquote($this->db_name);
  144. }
  145. return $this->db_name;
  146. }
  147. /**
  148. * returns full name for table, including database name
  149. *
  150. * @param boolean $backquoted whether to quote name with backticks ``
  151. *
  152. * @return string
  153. */
  154. function getFullName($backquoted = false)
  155. {
  156. return $this->getDbName($backquoted) . '.'
  157. . $this->getName($backquoted);
  158. }
  159. /**
  160. * returns whether the table is actually a view
  161. *
  162. * @param string $db database
  163. * @param string $table table
  164. *
  165. * @return whether the given is a view
  166. */
  167. static public function isView($db = null, $table = null)
  168. {
  169. if (empty($db) || empty($table)) {
  170. return false;
  171. }
  172. // use cached data or load information with SHOW command
  173. if (isset(PMA_Table::$cache[$db][$table])
  174. || $GLOBALS['cfg']['Server']['DisableIS']
  175. ) {
  176. $type = PMA_Table::sGetStatusInfo($db, $table, 'TABLE_TYPE');
  177. return $type == 'VIEW';
  178. }
  179. // query information_schema
  180. $result = PMA_DBI_fetch_result(
  181. "SELECT TABLE_NAME
  182. FROM information_schema.VIEWS
  183. WHERE TABLE_SCHEMA = '" . PMA_Util::sqlAddSlashes($db) . "'
  184. AND TABLE_NAME = '" . PMA_Util::sqlAddSlashes($table) . "'"
  185. );
  186. return $result ? true : false;
  187. }
  188. /**
  189. * Returns whether the table is actually an updatable view
  190. *
  191. * @param string $db database
  192. * @param string $table table
  193. *
  194. * @return boolean whether the given is an updatable view
  195. */
  196. static public function isUpdatableView($db = null, $table = null)
  197. {
  198. if (empty($db) || empty($table)) {
  199. return false;
  200. }
  201. $result = PMA_DBI_fetch_result(
  202. "SELECT TABLE_NAME
  203. FROM information_schema.VIEWS
  204. WHERE TABLE_SCHEMA = '" . PMA_Util::sqlAddSlashes($db) . "'
  205. AND TABLE_NAME = '" . PMA_Util::sqlAddSlashes($table) . "'
  206. AND IS_UPDATABLE = 'YES'"
  207. );
  208. return $result ? true : false;
  209. }
  210. /**
  211. * Returns the analysis of 'SHOW CREATE TABLE' query for the table.
  212. * In case of a view, the values are taken from the information_schema.
  213. *
  214. * @param string $db database
  215. * @param string $table table
  216. *
  217. * @return array analysis of 'SHOW CREATE TABLE' query for the table
  218. */
  219. static public function analyzeStructure($db = null, $table = null)
  220. {
  221. if (empty($db) || empty($table)) {
  222. return false;
  223. }
  224. $analyzed_sql = array();
  225. if (self::isView($db, $table)) {
  226. // For a view, 'SHOW CREATE TABLE' returns the definition,
  227. // but the structure of the view. So, we try to mock
  228. // the result of analyzing 'SHOW CREATE TABLE' query.
  229. $analyzed_sql[0] = array();
  230. $analyzed_sql[0]['create_table_fields'] = array();
  231. $results = PMA_DBI_fetch_result(
  232. "SELECT COLUMN_NAME, DATA_TYPE
  233. FROM information_schema.COLUMNS
  234. WHERE TABLE_SCHEMA = '" . PMA_Util::sqlAddSlashes($db) . "'
  235. AND TABLE_NAME = '" . PMA_Util::sqlAddSlashes($table) . "'"
  236. );
  237. foreach ($results as $result) {
  238. $analyzed_sql[0]['create_table_fields'][$result['COLUMN_NAME']]
  239. = array('type' => strtoupper($result['DATA_TYPE']));
  240. }
  241. } else {
  242. $show_create_table = PMA_DBI_fetch_value(
  243. 'SHOW CREATE TABLE ' . PMA_Util::backquote($db) . '.' . PMA_Util::backquote($table),
  244. 0,
  245. 1
  246. );
  247. $analyzed_sql = PMA_SQP_analyze(PMA_SQP_parse($show_create_table));
  248. }
  249. return $analyzed_sql;
  250. }
  251. /**
  252. * sets given $value for given $param
  253. *
  254. * @param string $param name
  255. * @param mixed $value value
  256. *
  257. * @return void
  258. */
  259. function set($param, $value)
  260. {
  261. $this->settings[$param] = $value;
  262. }
  263. /**
  264. * returns value for given setting/param
  265. *
  266. * @param string $param name for value to return
  267. *
  268. * @return mixed value for $param
  269. */
  270. function get($param)
  271. {
  272. if (isset($this->settings[$param])) {
  273. return $this->settings[$param];
  274. }
  275. return null;
  276. }
  277. /**
  278. * Checks if this is a merge table
  279. *
  280. * If the ENGINE of the table is MERGE or MRG_MYISAM (alias),
  281. * this is a merge table.
  282. *
  283. * @param string $db the database name
  284. * @param string $table the table name
  285. *
  286. * @return boolean true if it is a merge table
  287. */
  288. static public function isMerge($db = null, $table = null)
  289. {
  290. $engine = null;
  291. // if called static, with parameters
  292. if (! empty($db) && ! empty($table)) {
  293. $engine = PMA_Table::sGetStatusInfo(
  294. $db, $table, 'ENGINE', null, true
  295. );
  296. }
  297. // did we get engine?
  298. if (empty($engine)) {
  299. return false;
  300. }
  301. // any of known merge engines?
  302. return in_array(strtoupper($engine), array('MERGE', 'MRG_MYISAM'));
  303. }
  304. /**
  305. * Returns tooltip for the table
  306. * Format : <table_comment> (<number_of_rows>)
  307. *
  308. * @param string $db database name
  309. * @param string $table table name
  310. *
  311. * @return string tooltip fot the table
  312. */
  313. static public function sGetToolTip($db, $table)
  314. {
  315. return PMA_Table::sGetStatusInfo($db, $table, 'Comment')
  316. . ' (' . PMA_Table::countRecords($db, $table)
  317. . ' ' . __('Rows') . ')';
  318. }
  319. /**
  320. * Returns full table status info, or specific if $info provided
  321. * this info is collected from information_schema
  322. *
  323. * @param string $db database name
  324. * @param string $table table name
  325. * @param string $info specific information to be fetched
  326. * @param boolean $force_read read new rather than serving from cache
  327. * @param boolean $disable_error if true, disables error message
  328. *
  329. * @todo PMA_DBI_get_tables_full needs to be merged somehow into this class
  330. * or at least better documented
  331. *
  332. * @return mixed
  333. */
  334. static public function sGetStatusInfo($db, $table, $info = null,
  335. $force_read = false, $disable_error = false
  336. ) {
  337. if (! empty($_SESSION['is_multi_query'])) {
  338. $disable_error = true;
  339. }
  340. if (! isset(PMA_Table::$cache[$db][$table]) || $force_read) {
  341. PMA_DBI_get_tables_full($db, $table);
  342. }
  343. if (! isset(PMA_Table::$cache[$db][$table])) {
  344. // happens when we enter the table creation dialog
  345. // or when we really did not get any status info, for example
  346. // when $table == 'TABLE_NAMES' after the user tried SHOW TABLES
  347. return '';
  348. }
  349. if (null === $info) {
  350. return PMA_Table::$cache[$db][$table];
  351. }
  352. // array_key_exists allows for null values
  353. if (!array_key_exists($info, PMA_Table::$cache[$db][$table])) {
  354. if (! $disable_error) {
  355. trigger_error(
  356. __('unknown table status: ') . $info,
  357. E_USER_WARNING
  358. );
  359. }
  360. return false;
  361. }
  362. return PMA_Table::$cache[$db][$table][$info];
  363. }
  364. /**
  365. * generates column specification for ALTER or CREATE TABLE syntax
  366. *
  367. * @param string $name name
  368. * @param string $type type ('INT', 'VARCHAR', 'BIT', ...)
  369. * @param string $index index
  370. * @param string $length length ('2', '5,2', '', ...)
  371. * @param string $attribute attribute
  372. * @param string $collation collation
  373. * @param bool|string $null with 'NULL' or 'NOT NULL'
  374. * @param string $default_type whether default is CURRENT_TIMESTAMP,
  375. * NULL, NONE, USER_DEFINED
  376. * @param string $default_value default value for USER_DEFINED
  377. * default type
  378. * @param string $extra 'AUTO_INCREMENT'
  379. * @param string $comment field comment
  380. * @param array &$field_primary list of fields for PRIMARY KEY
  381. * @param string $move_to new position for column
  382. *
  383. * @todo move into class PMA_Column
  384. * @todo on the interface, some js to clear the default value when the
  385. * default current_timestamp is checked
  386. *
  387. * @return string field specification
  388. */
  389. static function generateFieldSpec($name, $type, $index, $length = '',
  390. $attribute = '', $collation = '', $null = false,
  391. $default_type = 'USER_DEFINED', $default_value = '', $extra = '',
  392. $comment = '', &$field_primary = null, $move_to = ''
  393. ) {
  394. $is_timestamp = strpos(strtoupper($type), 'TIMESTAMP') !== false;
  395. $query = PMA_Util::backquote($name) . ' ' . $type;
  396. if ($length != ''
  397. && ! preg_match(
  398. '@^(DATE|DATETIME|TIME|TINYBLOB|TINYTEXT|BLOB|TEXT|'
  399. . 'MEDIUMBLOB|MEDIUMTEXT|LONGBLOB|LONGTEXT|SERIAL|BOOLEAN|UUID)$@i',
  400. $type
  401. )
  402. ) {
  403. $query .= '(' . $length . ')';
  404. }
  405. if ($attribute != '') {
  406. $query .= ' ' . $attribute;
  407. }
  408. $matches = preg_match(
  409. '@^(TINYTEXT|TEXT|MEDIUMTEXT|LONGTEXT|VARCHAR|CHAR|ENUM|SET)$@i',
  410. $type
  411. );
  412. if (! empty($collation) && $collation != 'NULL' && $matches) {
  413. $query .= PMA_generateCharsetQueryPart($collation);
  414. }
  415. if ($null !== false) {
  416. if ($null == 'NULL') {
  417. $query .= ' NULL';
  418. } else {
  419. $query .= ' NOT NULL';
  420. }
  421. }
  422. switch ($default_type) {
  423. case 'USER_DEFINED' :
  424. if ($is_timestamp && $default_value === '0') {
  425. // a TIMESTAMP does not accept DEFAULT '0'
  426. // but DEFAULT 0 works
  427. $query .= ' DEFAULT 0';
  428. } elseif ($type == 'BIT') {
  429. $query .= ' DEFAULT b\''
  430. . preg_replace('/[^01]/', '0', $default_value)
  431. . '\'';
  432. } elseif ($type == 'BOOLEAN') {
  433. if (preg_match('/^1|T|TRUE|YES$/i', $default_value)) {
  434. $query .= ' DEFAULT TRUE';
  435. } elseif (preg_match('/^0|F|FALSE|NO$/i', $default_value)) {
  436. $query .= ' DEFAULT FALSE';
  437. } else {
  438. // Invalid BOOLEAN value
  439. $query .= ' DEFAULT \''
  440. . PMA_Util::sqlAddSlashes($default_value) . '\'';
  441. }
  442. } else {
  443. $query .= ' DEFAULT \'' . PMA_Util::sqlAddSlashes($default_value) . '\'';
  444. }
  445. break;
  446. case 'NULL' :
  447. //If user uncheck null checkbox and not change default value null,
  448. //default value will be ignored.
  449. if ($null !== false && $null != 'NULL') {
  450. break;
  451. }
  452. case 'CURRENT_TIMESTAMP' :
  453. $query .= ' DEFAULT ' . $default_type;
  454. break;
  455. case 'NONE' :
  456. default :
  457. break;
  458. }
  459. if (!empty($extra)) {
  460. $query .= ' ' . $extra;
  461. // Force an auto_increment field to be part of the primary key
  462. // even if user did not tick the PK box;
  463. if ($extra == 'AUTO_INCREMENT') {
  464. $primary_cnt = count($field_primary);
  465. if (1 == $primary_cnt) {
  466. for ($j = 0; $j < $primary_cnt; $j++) {
  467. if ($field_primary[$j] == $index) {
  468. break;
  469. }
  470. }
  471. if (isset($field_primary[$j]) && $field_primary[$j] == $index) {
  472. $query .= ' PRIMARY KEY';
  473. unset($field_primary[$j]);
  474. }
  475. } else {
  476. // but the PK could contain other columns so do not append
  477. // a PRIMARY KEY clause, just add a member to $field_primary
  478. $found_in_pk = false;
  479. for ($j = 0; $j < $primary_cnt; $j++) {
  480. if ($field_primary[$j] == $index) {
  481. $found_in_pk = true;
  482. break;
  483. }
  484. } // end for
  485. if (! $found_in_pk) {
  486. $field_primary[] = $index;
  487. }
  488. }
  489. } // end if (auto_increment)
  490. }
  491. if (!empty($comment)) {
  492. $query .= " COMMENT '" . PMA_Util::sqlAddSlashes($comment) . "'";
  493. }
  494. // move column
  495. if ($move_to == '-first') { // dash can't appear as part of column name
  496. $query .= ' FIRST';
  497. } elseif ($move_to != '') {
  498. $query .= ' AFTER ' . PMA_Util::backquote($move_to);
  499. }
  500. return $query;
  501. } // end function
  502. /**
  503. * Counts and returns (or displays) the number of records in a table
  504. *
  505. * @param string $db the current database name
  506. * @param string $table the current table name
  507. * @param bool $force_exact whether to force an exact count
  508. * @param bool $is_view whether the table is a view
  509. *
  510. * @return mixed the number of records if "retain" param is true,
  511. * otherwise true
  512. */
  513. static public function countRecords($db, $table, $force_exact = false,
  514. $is_view = null
  515. ) {
  516. if (isset(PMA_Table::$cache[$db][$table]['ExactRows'])) {
  517. $row_count = PMA_Table::$cache[$db][$table]['ExactRows'];
  518. } else {
  519. $row_count = false;
  520. if (null === $is_view) {
  521. $is_view = PMA_Table::isView($db, $table);
  522. }
  523. if (! $force_exact) {
  524. if (! isset(PMA_Table::$cache[$db][$table]['Rows']) && ! $is_view) {
  525. $tmp_tables = PMA_DBI_get_tables_full($db, $table);
  526. if (isset($tmp_tables[$table])) {
  527. PMA_Table::$cache[$db][$table] = $tmp_tables[$table];
  528. }
  529. }
  530. if (isset(PMA_Table::$cache[$db][$table]['Rows'])) {
  531. $row_count = PMA_Table::$cache[$db][$table]['Rows'];
  532. } else {
  533. $row_count = false;
  534. }
  535. }
  536. // for a VIEW, $row_count is always false at this point
  537. if (false === $row_count
  538. || $row_count < $GLOBALS['cfg']['MaxExactCount']
  539. ) {
  540. // Make an exception for views in I_S and D_D schema in
  541. // Drizzle, as these map to in-memory data and should execute
  542. // fast enough
  543. if (! $is_view || (PMA_DRIZZLE && PMA_is_system_schema($db))) {
  544. $row_count = PMA_DBI_fetch_value(
  545. 'SELECT COUNT(*) FROM ' . PMA_Util::backquote($db) . '.'
  546. . PMA_Util::backquote($table)
  547. );
  548. } else {
  549. // For complex views, even trying to get a partial record
  550. // count could bring down a server, so we offer an
  551. // alternative: setting MaxExactCountViews to 0 will bypass
  552. // completely the record counting for views
  553. if ($GLOBALS['cfg']['MaxExactCountViews'] == 0) {
  554. $row_count = 0;
  555. } else {
  556. // Counting all rows of a VIEW could be too long,
  557. // so use a LIMIT clause.
  558. // Use try_query because it can fail (when a VIEW is
  559. // based on a table that no longer exists)
  560. $result = PMA_DBI_try_query(
  561. 'SELECT 1 FROM ' . PMA_Util::backquote($db) . '.'
  562. . PMA_Util::backquote($table) . ' LIMIT '
  563. . $GLOBALS['cfg']['MaxExactCountViews'],
  564. null,
  565. PMA_DBI_QUERY_STORE
  566. );
  567. if (!PMA_DBI_getError()) {
  568. $row_count = PMA_DBI_num_rows($result);
  569. PMA_DBI_free_result($result);
  570. }
  571. }
  572. }
  573. if ($row_count) {
  574. PMA_Table::$cache[$db][$table]['ExactRows'] = $row_count;
  575. }
  576. }
  577. }
  578. return $row_count;
  579. } // end of the 'PMA_Table::countRecords()' function
  580. /**
  581. * Generates column specification for ALTER syntax
  582. *
  583. * @param string $oldcol old column name
  584. * @param string $newcol new column name
  585. * @param string $type type ('INT', 'VARCHAR', 'BIT', ...)
  586. * @param string $length length ('2', '5,2', '', ...)
  587. * @param string $attribute attribute
  588. * @param string $collation collation
  589. * @param bool|string $null with 'NULL' or 'NOT NULL'
  590. * @param string $default_type whether default is CURRENT_TIMESTAMP,
  591. * NULL, NONE, USER_DEFINED
  592. * @param string $default_value default value for USER_DEFINED default
  593. * type
  594. * @param string $extra 'AUTO_INCREMENT'
  595. * @param string $comment field comment
  596. * @param array &$field_primary list of fields for PRIMARY KEY
  597. * @param string $index index
  598. * @param string $move_to new position for column
  599. *
  600. * @see PMA_Table::generateFieldSpec()
  601. *
  602. * @return string field specification
  603. */
  604. static public function generateAlter($oldcol, $newcol, $type, $length,
  605. $attribute, $collation, $null, $default_type, $default_value,
  606. $extra, $comment, &$field_primary, $index, $move_to
  607. ) {
  608. return PMA_Util::backquote($oldcol) . ' '
  609. . PMA_Table::generateFieldSpec(
  610. $newcol, $type, $index, $length, $attribute,
  611. $collation, $null, $default_type, $default_value, $extra,
  612. $comment, $field_primary, $move_to
  613. );
  614. } // end function
  615. /**
  616. * Inserts existing entries in a PMA_* table by reading a value from an old
  617. * entry
  618. *
  619. * @param string $work The array index, which Relation feature to
  620. * check ('relwork', 'commwork', ...)
  621. * @param string $pma_table The array index, which PMA-table to update
  622. * ('bookmark', 'relation', ...)
  623. * @param array $get_fields Which fields will be SELECT'ed from the old entry
  624. * @param array $where_fields Which fields will be used for the WHERE query
  625. * (array('FIELDNAME' => 'FIELDVALUE'))
  626. * @param array $new_fields Which fields will be used as new VALUES.
  627. * These are the important keys which differ
  628. * from the old entry
  629. * (array('FIELDNAME' => 'NEW FIELDVALUE'))
  630. *
  631. * @global relation variable
  632. *
  633. * @return int|true
  634. */
  635. static public function duplicateInfo($work, $pma_table, $get_fields,
  636. $where_fields, $new_fields
  637. ) {
  638. $last_id = -1;
  639. if (isset($GLOBALS['cfgRelation']) && $GLOBALS['cfgRelation'][$work]) {
  640. $select_parts = array();
  641. $row_fields = array();
  642. foreach ($get_fields as $get_field) {
  643. $select_parts[] = PMA_Util::backquote($get_field);
  644. $row_fields[$get_field] = 'cc';
  645. }
  646. $where_parts = array();
  647. foreach ($where_fields as $_where => $_value) {
  648. $where_parts[] = PMA_Util::backquote($_where) . ' = \''
  649. . PMA_Util::sqlAddSlashes($_value) . '\'';
  650. }
  651. $new_parts = array();
  652. $new_value_parts = array();
  653. foreach ($new_fields as $_where => $_value) {
  654. $new_parts[] = PMA_Util::backquote($_where);
  655. $new_value_parts[] = PMA_Util::sqlAddSlashes($_value);
  656. }
  657. $table_copy_query = '
  658. SELECT ' . implode(', ', $select_parts) . '
  659. FROM ' . PMA_Util::backquote($GLOBALS['cfgRelation']['db']) . '.'
  660. . PMA_Util::backquote($GLOBALS['cfgRelation'][$pma_table]) . '
  661. WHERE ' . implode(' AND ', $where_parts);
  662. // must use PMA_DBI_QUERY_STORE here, since we execute another
  663. // query inside the loop
  664. $table_copy_rs = PMA_queryAsControlUser(
  665. $table_copy_query, true, PMA_DBI_QUERY_STORE
  666. );
  667. while ($table_copy_row = @PMA_DBI_fetch_assoc($table_copy_rs)) {
  668. $value_parts = array();
  669. foreach ($table_copy_row as $_key => $_val) {
  670. if (isset($row_fields[$_key]) && $row_fields[$_key] == 'cc') {
  671. $value_parts[] = PMA_Util::sqlAddSlashes($_val);
  672. }
  673. }
  674. $new_table_query = 'INSERT IGNORE INTO '
  675. . PMA_Util::backquote($GLOBALS['cfgRelation']['db'])
  676. . '.' . PMA_Util::backquote($GLOBALS['cfgRelation'][$pma_table]) . '
  677. (' . implode(', ', $select_parts) . ',
  678. ' . implode(', ', $new_parts) . ')
  679. VALUES
  680. (\'' . implode('\', \'', $value_parts) . '\',
  681. \'' . implode('\', \'', $new_value_parts) . '\')';
  682. PMA_queryAsControlUser($new_table_query);
  683. $last_id = PMA_DBI_insert_id();
  684. } // end while
  685. PMA_DBI_free_result($table_copy_rs);
  686. return $last_id;
  687. }
  688. return true;
  689. } // end of 'PMA_Table::duplicateInfo()' function
  690. /**
  691. * Copies or renames table
  692. *
  693. * @param string $source_db source database
  694. * @param string $source_table source table
  695. * @param string $target_db target database
  696. * @param string $target_table target table
  697. * @param string $what what to be moved or copied (data, dataonly)
  698. * @param bool $move whether to move
  699. * @param string $mode mode
  700. *
  701. * @return bool true if success, false otherwise
  702. */
  703. static public function moveCopy($source_db, $source_table, $target_db,
  704. $target_table, $what, $move, $mode
  705. ) {
  706. global $err_url;
  707. /* Try moving table directly */
  708. if ($move && $what == 'data') {
  709. $tbl = new PMA_Table($source_table, $source_db);
  710. $result = $tbl->rename($target_table, $target_db);
  711. if ($result) {
  712. $GLOBALS['message'] = $tbl->getLastMessage();
  713. return true;
  714. }
  715. }
  716. // set export settings we need
  717. $GLOBALS['sql_backquotes'] = 1;
  718. $GLOBALS['asfile'] = 1;
  719. // Ensure the target is valid
  720. if (! $GLOBALS['pma']->databases->exists($source_db, $target_db)) {
  721. if (! $GLOBALS['pma']->databases->exists($source_db)) {
  722. $GLOBALS['message'] = PMA_Message::rawError(
  723. sprintf(
  724. __('Source database `%s` was not found!'),
  725. htmlspecialchars($source_db)
  726. )
  727. );
  728. }
  729. if (! $GLOBALS['pma']->databases->exists($target_db)) {
  730. $GLOBALS['message'] = PMA_Message::rawError(
  731. sprintf(
  732. __('Target database `%s` was not found!'),
  733. htmlspecialchars($target_db)
  734. )
  735. );
  736. }
  737. return false;
  738. }
  739. $source = PMA_Util::backquote($source_db) . '.' . PMA_Util::backquote($source_table);
  740. if (! isset($target_db) || ! strlen($target_db)) {
  741. $target_db = $source_db;
  742. }
  743. // Doing a select_db could avoid some problems with replicated databases,
  744. // when moving table from replicated one to not replicated one
  745. PMA_DBI_select_db($target_db);
  746. $target = PMA_Util::backquote($target_db) . '.' . PMA_Util::backquote($target_table);
  747. // do not create the table if dataonly
  748. if ($what != 'dataonly') {
  749. include_once "libraries/plugin_interface.lib.php";
  750. // get Export SQL instance
  751. $export_sql_plugin = PMA_getPlugin(
  752. "export",
  753. "sql",
  754. 'libraries/plugins/export/',
  755. array(
  756. 'export_type' => 'table',
  757. 'single_table' => isset($single_table)
  758. )
  759. );
  760. $no_constraints_comments = true;
  761. $GLOBALS['sql_constraints_query'] = '';
  762. // set the value of global sql_auto_increment variable
  763. if( isset($_POST['sql_auto_increment']) ) {
  764. $GLOBALS['sql_auto_increment'] = $_POST['sql_auto_increment'];
  765. }
  766. $sql_structure = $export_sql_plugin->getTableDef(
  767. $source_db, $source_table, "\n", $err_url, false, false
  768. );
  769. unset($no_constraints_comments);
  770. $parsed_sql = PMA_SQP_parse($sql_structure);
  771. $analyzed_sql = PMA_SQP_analyze($parsed_sql);
  772. $i = 0;
  773. if (empty($analyzed_sql[0]['create_table_fields'])) {
  774. // this is not a CREATE TABLE, so find the first VIEW
  775. $target_for_view = PMA_Util::backquote($target_db);
  776. while (true) {
  777. if ($parsed_sql[$i]['type'] == 'alpha_reservedWord'
  778. && $parsed_sql[$i]['data'] == 'VIEW'
  779. ) {
  780. break;
  781. }
  782. $i++;
  783. }
  784. }
  785. unset($analyzed_sql);
  786. if (PMA_DRIZZLE) {
  787. $table_delimiter = 'quote_backtick';
  788. } else {
  789. $server_sql_mode = PMA_DBI_fetch_value(
  790. "SHOW VARIABLES LIKE 'sql_mode'",
  791. 0,
  792. 1
  793. );
  794. // ANSI_QUOTES might be a subset of sql_mode, for example
  795. // REAL_AS_FLOAT,PIPES_AS_CONCAT,ANSI_QUOTES,IGNORE_SPACE,ANSI
  796. if (false !== strpos($server_sql_mode, 'ANSI_QUOTES')) {
  797. $table_delimiter = 'quote_double';
  798. } else {
  799. $table_delimiter = 'quote_backtick';
  800. }
  801. unset($server_sql_mode);
  802. }
  803. /* Find table name in query and replace it */
  804. while ($parsed_sql[$i]['type'] != $table_delimiter) {
  805. $i++;
  806. }
  807. /* no need to backquote() */
  808. if (isset($target_for_view)) {
  809. // this a view definition; we just found the first db name
  810. // that follows DEFINER VIEW
  811. // so change it for the new db name
  812. $parsed_sql[$i]['data'] = $target_for_view;
  813. // then we have to find all references to the source db
  814. // and change them to the target db, ensuring we stay into
  815. // the $parsed_sql limits
  816. $last = $parsed_sql['len'] - 1;
  817. $backquoted_source_db = PMA_Util::backquote($source_db);
  818. for (++$i; $i <= $last; $i++) {
  819. if ($parsed_sql[$i]['type'] == $table_delimiter
  820. && $parsed_sql[$i]['data'] == $backquoted_source_db
  821. && $parsed_sql[$i - 1]['type'] != 'punct_qualifier'
  822. ) {
  823. $parsed_sql[$i]['data'] = $target_for_view;
  824. }
  825. }
  826. unset($last,$backquoted_source_db);
  827. } else {
  828. $parsed_sql[$i]['data'] = $target;
  829. }
  830. /* Generate query back */
  831. $sql_structure = PMA_SQP_formatHtml($parsed_sql, 'query_only');
  832. // If table exists, and 'add drop table' is selected: Drop it!
  833. $drop_query = '';
  834. if (isset($_REQUEST['drop_if_exists'])
  835. && $_REQUEST['drop_if_exists'] == 'true'
  836. ) {
  837. if (PMA_Table::isView($target_db, $target_table)) {
  838. $drop_query = 'DROP VIEW';
  839. } else {
  840. $drop_query = 'DROP TABLE';
  841. }
  842. $drop_query .= ' IF EXISTS '
  843. . PMA_Util::backquote($target_db) . '.'
  844. . PMA_Util::backquote($target_table);
  845. PMA_DBI_query($drop_query);
  846. $GLOBALS['sql_query'] .= "\n" . $drop_query . ';';
  847. // If an existing table gets deleted, maintain any
  848. // entries for the PMA_* tables
  849. $maintain_relations = true;
  850. }
  851. @PMA_DBI_query($sql_structure);
  852. $GLOBALS['sql_query'] .= "\n" . $sql_structure . ';';
  853. if (($move || isset($GLOBALS['add_constraints']))
  854. && !empty($GLOBALS['sql_constraints_query'])
  855. ) {
  856. $parsed_sql = PMA_SQP_parse($GLOBALS['sql_constraints_query']);
  857. $i = 0;
  858. // find the first $table_delimiter, it must be the source
  859. // table name
  860. while ($parsed_sql[$i]['type'] != $table_delimiter) {
  861. $i++;
  862. // maybe someday we should guard against going over limit
  863. //if ($i == $parsed_sql['len']) {
  864. // break;
  865. //}
  866. }
  867. // replace it by the target table name, no need
  868. // to backquote()
  869. $parsed_sql[$i]['data'] = $target;
  870. // now we must remove all $table_delimiter that follow a
  871. // CONSTRAINT keyword, because a constraint name must be
  872. // unique in a db
  873. $cnt = $parsed_sql['len'] - 1;
  874. for ($j = $i; $j < $cnt; $j++) {
  875. if ($parsed_sql[$j]['type'] == 'alpha_reservedWord'
  876. && strtoupper($parsed_sql[$j]['data']) == 'CONSTRAINT'
  877. ) {
  878. if ($parsed_sql[$j+1]['type'] == $table_delimiter) {
  879. $parsed_sql[$j+1]['data'] = '';
  880. }
  881. }
  882. }
  883. // Generate query back
  884. $GLOBALS['sql_constraints_query'] = PMA_SQP_formatHtml(
  885. $parsed_sql, 'query_only'
  886. );
  887. if ($mode == 'one_table') {
  888. PMA_DBI_query($GLOBALS['sql_constraints_query']);
  889. }
  890. $GLOBALS['sql_query'] .= "\n" . $GLOBALS['sql_constraints_query'];
  891. if ($mode == 'one_table') {
  892. unset($GLOBALS['sql_constraints_query']);
  893. }
  894. }
  895. } else {
  896. $GLOBALS['sql_query'] = '';
  897. }
  898. // Copy the data unless this is a VIEW
  899. if (($what == 'data' || $what == 'dataonly')
  900. && ! PMA_Table::isView($target_db, $target_table)
  901. ) {
  902. $sql_set_mode = "SET SQL_MODE='NO_AUTO_VALUE_ON_ZERO'";
  903. PMA_DBI_query($sql_set_mode);
  904. $GLOBALS['sql_query'] .= "\n\n" . $sql_set_mode . ';';
  905. $sql_insert_data = 'INSERT INTO ' . $target
  906. . ' SELECT * FROM ' . $source;
  907. PMA_DBI_query($sql_insert_data);
  908. $GLOBALS['sql_query'] .= "\n\n" . $sql_insert_data . ';';
  909. }
  910. $GLOBALS['cfgRelation'] = PMA_getRelationsParam();
  911. // Drops old table if the user has requested to move it
  912. if ($move) {
  913. // This could avoid some problems with replicated databases, when
  914. // moving table from replicated one to not replicated one
  915. PMA_DBI_select_db($source_db);
  916. if (PMA_Table::isView($source_db, $source_table)) {
  917. $sql_drop_query = 'DROP VIEW';
  918. } else {
  919. $sql_drop_query = 'DROP TABLE';
  920. }
  921. $sql_drop_query .= ' ' . $source;
  922. PMA_DBI_query($sql_drop_query);
  923. // Renable table in configuration storage
  924. PMA_REL_renameTable(
  925. $source_db, $target_db,
  926. $source_table, $target_table
  927. );
  928. $GLOBALS['sql_query'] .= "\n\n" . $sql_drop_query . ';';
  929. // end if ($move)
  930. } else {
  931. // we are copying
  932. // Create new entries as duplicates from old PMA DBs
  933. if ($what != 'dataonly' && ! isset($maintain_relations)) {
  934. if ($GLOBALS['cfgRelation']['commwork']) {
  935. // Get all comments and MIME-Types for current table
  936. $comments_copy_query = 'SELECT
  937. column_name, comment' . ($GLOBALS['cfgRelation']['mimework'] ? ', mimetype, transformation, transformation_options' : '') . '
  938. FROM ' . PMA_Util::backquote($GLOBALS['cfgRelation']['db']) . '.' . PMA_Util::backquote($GLOBALS['cfgRelation']['column_info']) . '
  939. WHERE
  940. db_name = \'' . PMA_Util::sqlAddSlashes($source_db) . '\' AND
  941. table_name = \'' . PMA_Util::sqlAddSlashes($source_table) . '\'';
  942. $comments_copy_rs = PMA_queryAsControlUser($comments_copy_query);
  943. // Write every comment as new copied entry. [MIME]
  944. while ($comments_copy_row = PMA_DBI_fetch_assoc($comments_copy_rs)) {
  945. $new_comment_query = 'REPLACE INTO ' . PMA_Util::backquote($GLOBALS['cfgRelation']['db']) . '.' . PMA_Util::backquote($GLOBALS['cfgRelation']['column_info'])
  946. . ' (db_name, table_name, column_name, comment' . ($GLOBALS['cfgRelation']['mimework'] ? ', mimetype, transformation, transformation_options' : '') . ') '
  947. . ' VALUES('
  948. . '\'' . PMA_Util::sqlAddSlashes($target_db) . '\','
  949. . '\'' . PMA_Util::sqlAddSlashes($target_table) . '\','
  950. . '\'' . PMA_Util::sqlAddSlashes($comments_copy_row['column_name']) . '\''
  951. . ($GLOBALS['cfgRelation']['mimework'] ? ',\'' . PMA_Util::sqlAddSlashes($comments_copy_row['comment']) . '\','
  952. . '\'' . PMA_Util::sqlAddSlashes($comments_copy_row['mimetype']) . '\','
  953. . '\'' . PMA_Util::sqlAddSlashes($comments_copy_row['transformation']) . '\','
  954. . '\'' . PMA_Util::sqlAddSlashes($comments_copy_row['transformation_options']) . '\'' : '')
  955. . ')';
  956. PMA_queryAsControlUser($new_comment_query);
  957. } // end while
  958. PMA_DBI_free_result($comments_copy_rs);
  959. unset($comments_copy_rs);
  960. }
  961. // duplicating the bookmarks must not be done here, but
  962. // just once per db
  963. $get_fields = array('display_field');
  964. $where_fields = array(
  965. 'db_name' => $source_db,
  966. 'table_name' => $source_table
  967. );
  968. $new_fields = array(
  969. 'db_name' => $target_db,
  970. 'table_name' => $target_table
  971. );
  972. PMA_Table::duplicateInfo(
  973. 'displaywork',
  974. 'table_info',
  975. $get_fields,
  976. $where_fields,
  977. $new_fields
  978. );
  979. /**
  980. * @todo revise this code when we support cross-db relations
  981. */
  982. $get_fields = array(
  983. 'master_field',
  984. 'foreign_table',
  985. 'foreign_field'
  986. );
  987. $where_fields = array(
  988. 'master_db' => $source_db,
  989. 'master_table' => $source_table
  990. );
  991. $new_fields = array(
  992. 'master_db' => $target_db,
  993. 'foreign_db' => $target_db,
  994. 'master_table' => $target_table
  995. );
  996. PMA_Table::duplicateInfo(
  997. 'relwork',
  998. 'relation',
  999. $get_fields,
  1000. $where_fields,
  1001. $new_fields
  1002. );
  1003. $get_fields = array(
  1004. 'foreign_field',
  1005. 'master_table',
  1006. 'master_field'
  1007. );
  1008. $where_fields = array(
  1009. 'foreign_db' => $source_db,
  1010. 'foreign_table' => $source_table
  1011. );
  1012. $new_fields = array(
  1013. 'master_db' => $target_db,
  1014. 'foreign_db' => $target_db,
  1015. 'foreign_table' => $target_table
  1016. );
  1017. PMA_Table::duplicateInfo(
  1018. 'relwork',
  1019. 'relation',
  1020. $get_fields,
  1021. $where_fields,
  1022. $new_fields
  1023. );
  1024. $get_fields = array('x', 'y', 'v', 'h');
  1025. $where_fields = array(
  1026. 'db_name' => $source_db,
  1027. 'table_name' => $source_table
  1028. );
  1029. $new_fields = array(
  1030. 'db_name' => $target_db,
  1031. 'table_name' => $target_table
  1032. );
  1033. PMA_Table::duplicateInfo(
  1034. 'designerwork',
  1035. 'designer_coords',
  1036. $get_fields,
  1037. $where_fields,
  1038. $new_fields
  1039. );
  1040. /**
  1041. * @todo Can't get duplicating PDFs the right way. The
  1042. * page numbers always get screwed up independently from
  1043. * duplication because the numbers do not seem to be stored on a
  1044. * per-database basis. Would the author of pdf support please
  1045. * have a look at it?
  1046. *
  1047. $get_fields = array('page_descr');
  1048. $where_fields = array('db_name' => $source_db);
  1049. $new_fields = array('db_name' => $target_db);
  1050. $last_id = PMA_Table::duplicateInfo(
  1051. 'pdfwork',
  1052. 'pdf_pages',
  1053. $get_fields,
  1054. $where_fields,
  1055. $new_fields
  1056. );
  1057. if (isset($last_id) && $last_id >= 0) {
  1058. $get_fields = array('x', 'y');
  1059. $where_fields = array(
  1060. 'db_name' => $source_db,
  1061. 'table_name' => $source_table
  1062. );
  1063. $new_fields = array(
  1064. 'db_name' => $target_db,
  1065. 'table_name' => $target_table,
  1066. 'pdf_page_number' => $last_id
  1067. );
  1068. PMA_Table::duplicateInfo(
  1069. 'pdfwork',
  1070. 'table_coords',
  1071. $get_fields,
  1072. $where_fields,
  1073. $new_fields
  1074. );
  1075. }
  1076. */
  1077. }
  1078. }
  1079. return true;
  1080. }
  1081. /**
  1082. * checks if given name is a valid table name,
  1083. * currently if not empty, trailing spaces, '.', '/' and '\'
  1084. *
  1085. * @param string $table_name name to check
  1086. *
  1087. * @todo add check for valid chars in filename on current system/os
  1088. * @see http://dev.mysql.com/doc/refman/5.0/en/legal-names.html
  1089. *
  1090. * @return boolean whether the string is valid or not
  1091. */
  1092. static function isValidName($table_name)
  1093. {
  1094. if ($table_name !== trim($table_name)) {
  1095. // trailing spaces
  1096. return false;
  1097. }
  1098. if (! strlen($table_name)) {
  1099. // zero length
  1100. return false;
  1101. }
  1102. if (preg_match('/[.\/\\\\]+/i', $table_name)) {
  1103. // illegal char . / \
  1104. return false;
  1105. }
  1106. return true;
  1107. }
  1108. /**
  1109. * renames table
  1110. *
  1111. * @param string $new_name new table name
  1112. * @param string $new_db new database name
  1113. *
  1114. * @return bool success
  1115. */
  1116. function rename($new_name, $new_db = null)
  1117. {
  1118. if (null !== $new_db && $new_db !== $this->getDbName()) {
  1119. // Ensure the target is valid
  1120. if (! $GLOBALS['pma']->databases->exists($new_db)) {
  1121. $this->errors[] = __('Invalid database') . ': ' . $new_db;
  1122. return false;
  1123. }
  1124. } else {
  1125. $new_db = $this->getDbName();
  1126. }
  1127. $new_table = new PMA_Table($new_name, $new_db);
  1128. if ($this->getFullName() === $new_table->getFullName()) {
  1129. return true;
  1130. }
  1131. if (! PMA_Table::isValidName($new_name)) {
  1132. $this->errors[] = __('Invalid table name') . ': '
  1133. . $new_table->getFullName();
  1134. return false;
  1135. }
  1136. // If the table is moved to a different database drop its triggers first
  1137. $triggers = PMA_DBI_get_triggers($this->getDbName(), $this->getName(), '');
  1138. $handle_triggers = $this->getDbName() != $new_db && $triggers;
  1139. if ($handle_triggers) {
  1140. foreach ($triggers as $trigger) {
  1141. $sql = 'DROP TRIGGER IF EXISTS ' . PMA_Util::backquote($this->getDbName())
  1142. . '.' . PMA_Util::backquote($trigger['name']) . ';';
  1143. PMA_DBI_query($sql);
  1144. }
  1145. }
  1146. /*
  1147. * tested also for a view, in MySQL 5.0.92, 5.1.55 and 5.5.13
  1148. */
  1149. $GLOBALS['sql_query'] = '
  1150. RENAME TABLE ' . $this->getFullName(true) . '
  1151. TO ' . $new_table->getFullName(true) . ';';
  1152. // I don't think a specific error message for views is necessary
  1153. if (! PMA_DBI_query($GLOBALS['sql_query'])) {
  1154. // Restore triggers in the old database
  1155. if ($handle_triggers) {
  1156. PMA_DBI_select_db($this->getDbName());
  1157. foreach ($triggers as $trigger) {
  1158. PMA_DBI_query($trigger['create']);
  1159. }
  1160. }
  1161. $this->errors[] = sprintf(
  1162. __('Error renaming table %1$s to %2$s'),
  1163. $this->getFullName(),
  1164. $new_table->getFullName()
  1165. );
  1166. return false;
  1167. }
  1168. $old_name = $this->getName();
  1169. $old_db = $this->getDbName();
  1170. $this->setName($new_name);
  1171. $this->setDbName($new_db);
  1172. // Renable table in configuration storage
  1173. PMA_REL_renameTable(
  1174. $old_db, $new_db,
  1175. $old_name, $new_name
  1176. );
  1177. $this->messages[] = sprintf(
  1178. __('Table %1$s has been renamed to %2$s.'),
  1179. htmlspecialchars($old_name),
  1180. htmlspecialchars($new_name)
  1181. );
  1182. return true;
  1183. }
  1184. /**
  1185. * Get all unique columns
  1186. *
  1187. * returns an array with all columns with unqiue content, in fact these are
  1188. * all columns being single indexed in PRIMARY or UNIQUE
  1189. *
  1190. * e.g.
  1191. * - PRIMARY(id) // id
  1192. * - UNIQUE(name) // name
  1193. * - PRIMARY(fk_id1, fk_id2) // NONE
  1194. * - UNIQUE(x,y) // NONE
  1195. *
  1196. * @param bool $backquoted whether to quote name with backticks ``
  1197. *
  1198. * @return array
  1199. */
  1200. public function getUniqueColumns($backquoted = true)
  1201. {
  1202. $sql = PMA_DBI_get_table_indexes_sql(
  1203. $this->getDbName(),
  1204. $this->getName(),
  1205. 'Non_unique = 0'
  1206. );
  1207. $uniques = PMA_DBI_fetch_result(
  1208. $sql,
  1209. array('Key_name', null),
  1210. 'Column_name'
  1211. );
  1212. $return = array();
  1213. foreach ($uniques as $index) {
  1214. if (count($index) > 1) {
  1215. continue;
  1216. }
  1217. $return[] = $this->getFullName($backquoted) . '.'
  1218. . ($backquoted ? PMA_Util::backquote($index[0]) : $index[0]);
  1219. }
  1220. return $return;
  1221. }
  1222. /**
  1223. * Get all indexed columns
  1224. *
  1225. * returns an array with all columns make use of an index, in fact only
  1226. * first columns in an index
  1227. *
  1228. * e.g. index(col1, col2) would only return col1
  1229. *
  1230. * @param bool $backquoted whether to quote name with backticks ``
  1231. *
  1232. * @return array
  1233. */
  1234. public function getIndexedColumns($backquoted = true)
  1235. {
  1236. $sql = PMA_DBI_get_table_indexes_sql(
  1237. $this->getDbName(),
  1238. $this->getName(),
  1239. 'Seq_in_index = 1'
  1240. );
  1241. $indexed = PMA_DBI_fetch_result($sql, 'Column_name', 'Column_name');
  1242. $return = array();
  1243. foreach ($indexed as $column) {
  1244. $return[] = $this->getFullName($backquoted) . '.'
  1245. . ($backquoted ? PMA_Util::backquote($column) : $column);
  1246. }
  1247. return $return;
  1248. }
  1249. /**
  1250. * Get all columns
  1251. *
  1252. * returns an array with all columns
  1253. *
  1254. * @param bool $backquoted whether to quote name with backticks ``
  1255. *
  1256. * @return array
  1257. */
  1258. public function getColumns($backquoted = true)
  1259. {
  1260. $sql = 'SHOW COLUMNS FROM ' . $this->getFullName(true);
  1261. $indexed = PMA_DBI_fetch_result($sql, 'Field', 'Field');
  1262. $return = array();
  1263. foreach ($indexed as $column) {
  1264. $return[] = $this->getFullName($backquoted) . '.'
  1265. . ($backquoted ? PMA_Util::backquote($column) : $column);
  1266. }
  1267. return $return;
  1268. }
  1269. /**
  1270. * Return UI preferences for this table from phpMyAdmin database.
  1271. *
  1272. * @return array
  1273. */
  1274. protected function getUiPrefsFromDb()
  1275. {
  1276. $pma_table = PMA_Util::backquote($GLOBALS['cfg']['Server']['pmadb']) ."."
  1277. . PMA_Util::backquote($GLOBALS['cfg']['Server']['table_uiprefs']);
  1278. // Read from phpMyAdmin database
  1279. $sql_query = " SELECT `prefs` FROM " . $pma_table
  1280. . " WHERE `username` = '" . PMA_Util::sqlAddSlashes($GLOBALS['cfg']['Server']['user']) . "'"
  1281. . " AND `db_name` = '" . PMA_Util::sqlAddSlashes($this->db_name) . "'"
  1282. . " AND `table_name` = '" . PMA_Util::sqlAddSlashes($this->name) . "'";
  1283. $row = PMA_DBI_fetch_array(PMA_queryAsControlUser($sql_query));
  1284. if (isset($row[0])) {
  1285. return json_decode($row[0], true);
  1286. } else {
  1287. return array();
  1288. }
  1289. }
  1290. /**
  1291. * Save this table's UI preferences into phpMyAdmin database.
  1292. *
  1293. * @return true|PMA_Message
  1294. */
  1295. protected function saveUiPrefsToDb()
  1296. {
  1297. $pma_table = PMA_Util::backquote($GLOBALS['cfg']['Server']['pmadb']) . "."
  1298. . PMA_Util::backquote($GLOBALS['cfg']['Server']['table_uiprefs']);
  1299. $username = $GLOBALS['cfg']['Server']['user'];
  1300. $sql_query = " REPLACE INTO " . $pma_table
  1301. . " VALUES ('" . PMA_Util::sqlAddSlashes($username) . "', '" . PMA_Util::sqlAddSlashes($this->db_name)
  1302. . "', '" . PMA_Util::sqlAddSlashes($this->name) . "', '"
  1303. . PMA_Util::sqlAddSlashes(json_encode($this->uiprefs)) . "', NULL)";
  1304. $success = PMA_DBI_try_query($sql_query, $GLOBALS['controllink']);
  1305. if (!$success) {
  1306. $message = PMA_Message::error(__('Could not save table UI preferences'));
  1307. $message->addMessage('<br /><br />');
  1308. $message->addMessage(
  1309. PMA_Message::rawError(PMA_DBI_getError($GLOBALS['controllink']))
  1310. );
  1311. return $message;
  1312. }
  1313. // Remove some old rows in table_uiprefs if it exceeds the configured
  1314. // maximum rows
  1315. $sql_query = 'SELECT COUNT(*) FROM ' . $pma_table;
  1316. $rows_count = PMA_DBI_fetch_value($sql_query);
  1317. $max_rows = $GLOBALS['cfg']['Server']['MaxTableUiprefs'];
  1318. if ($rows_count > $max_rows) {
  1319. $num_rows_to_delete = $rows_count - $max_rows;
  1320. $sql_query
  1321. = ' DELETE FROM ' . $pma_table .
  1322. ' ORDER BY last_update ASC' .
  1323. ' LIMIT ' . $num_rows_to_delete;
  1324. $success = PMA_DBI_try_query($sql_query, $GLOBALS['controllink']);
  1325. if (!$success) {
  1326. $message = PMA_Message::error(
  1327. sprintf(
  1328. __('Failed to cleanup table UI preferences (see $cfg[\'Servers\'][$i][\'MaxTableUiprefs\'] %s)'),
  1329. PMA_Util::showDocu('config', 'cfg_Servers_MaxTableUiprefs')
  1330. )
  1331. );
  1332. $message->addMessage('<br /><br />');
  1333. $message->addMessage(
  1334. PMA_Message::rawError(PMA_DBI_getError($GLOBALS['controllink']))
  1335. );
  1336. print_r($message);
  1337. return $message;
  1338. }
  1339. }
  1340. return true;
  1341. }
  1342. /**
  1343. * Loads the UI preferences for this table.
  1344. * If pmadb and table_uiprefs is set, it will load the UI preferences from
  1345. * phpMyAdmin database.
  1346. *
  1347. * @return void
  1348. */
  1349. protected function loadUiPrefs()
  1350. {
  1351. $server_id = $GLOBALS['server'];
  1352. // set session variable if it's still undefined
  1353. if (! isset($_SESSION['tmp_user_values']['table_uiprefs'][$server_id][$this->db_name][$this->name])) {
  1354. // check whether we can get from pmadb
  1355. $_SESSION['tmp_user_values']['table_uiprefs'][$server_id][$this->db_name][$this->name]
  1356. = (strlen($GLOBALS['cfg']['Server']['pmadb'])
  1357. && strlen($GLOBALS['cfg']['Server']['table_uiprefs']))
  1358. ? $this->getUiPrefsFromDb()
  1359. : array();
  1360. }
  1361. $this->uiprefs =& $_SESSION['tmp_user_values']['table_uiprefs'][$server_id]
  1362. [$this->db_name][$this->name];
  1363. }
  1364. /**
  1365. * Get a property from UI preferences.
  1366. * Return false if the property is not found.
  1367. * Available property:
  1368. * - PROP_SORTED_COLUMN
  1369. * - PROP_COLUMN_ORDER
  1370. * - PROP_COLUMN_VISIB
  1371. *
  1372. * @param string $property property
  1373. *
  1374. * @return mixed
  1375. */
  1376. public function getUiProp($property)
  1377. {
  1378. if (! isset($this->uiprefs)) {
  1379. $this->loadUiPrefs();
  1380. }
  1381. // do checking based on property
  1382. if ($property == self::PROP_SORTED_COLUMN) {
  1383. if (isset($this->uiprefs[$property])) {
  1384. // check if the column name is exist in this table
  1385. $tmp = explode(' ', $this->uiprefs[$property]);
  1386. $colname = $tmp[0];
  1387. $avail_columns = $this->getColumns();
  1388. foreach ($avail_columns as $each_col) {
  1389. // check if $each_col ends with $colname
  1390. if (substr_compare($each_col, $colname, strlen($each_col) - strlen($colname)) === 0) {
  1391. return $this->uiprefs[$property];
  1392. }
  1393. }
  1394. // remove the property, since it is not exist anymore in database
  1395. $this->removeUiProp(self::PROP_SORTED_COLUMN);
  1396. return false;
  1397. } else {
  1398. return false;
  1399. }
  1400. } elseif ($property == self::PROP_COLUMN_ORDER
  1401. || $property == self::PROP_COLUMN_VISIB
  1402. ) {
  1403. if (! PMA_Table::isView($this->db_name, $this->name)
  1404. && isset($this->uiprefs[$property])
  1405. ) {
  1406. // check if the table has not been modified
  1407. if (self::sGetStatusInfo($this->db_name, $this->name, 'Create_time') == $this->uiprefs['CREATE_TIME']) {
  1408. return $this->uiprefs[$property];
  1409. } else {
  1410. // remove the property, since the table has been modified
  1411. $this->removeUiProp(self::PROP_COLUMN_ORDER);
  1412. return false;
  1413. }
  1414. } else {
  1415. return false;
  1416. }
  1417. }
  1418. // default behaviour for other property:
  1419. return isset($this->uiprefs[$property]) ? $this->uiprefs[$property] : false;
  1420. }
  1421. /**
  1422. * Set a property from UI preferences.
  1423. * If pmadb and table_uiprefs is set, it will save the UI preferences to
  1424. * phpMyAdmin database.
  1425. * Available property:
  1426. * - PROP_SORTED_COLUMN
  1427. * - PROP_COLUMN_ORDER
  1428. * - PROP_COLUMN_VISIB
  1429. *
  1430. * @param string $property Property
  1431. * @param mixed $value Value for the property
  1432. * @param string $table_create_time Needed for PROP_COLUMN_ORDER
  1433. * and PROP_COLUMN_VISIB
  1434. *
  1435. * @return boolean|PMA_Message
  1436. */
  1437. public function setUiProp($property, $value, $table_create_time = null)
  1438. {
  1439. if (! isset($this->uiprefs)) {
  1440. $this->loadUiPrefs();
  1441. }
  1442. // we want to save the create time if the property is PROP_COLUMN_ORDER
  1443. if (! PMA_Table::isView($this->db_name, $this->name)
  1444. && ($property == self::PROP_COLUMN_ORDER
  1445. || $property == self::PROP_COLUMN_VISIB)
  1446. ) {
  1447. $curr_create_time = self::sGetStatusInfo(
  1448. $this->db_name,
  1449. $this->name,
  1450. 'CREATE_TIME'
  1451. );
  1452. if (isset($table_create_time)
  1453. && $table_create_time == $curr_create_time
  1454. ) {
  1455. $this->uiprefs['CREATE_TIME'] = $curr_create_time;
  1456. } else {
  1457. // there is no $table_create_time, or
  1458. // supplied $table_create_time is older than current create time,
  1459. // so don't save
  1460. return PMA_Message::error(
  1461. sprintf(
  1462. __('Cannot save UI property "%s". The changes made will not be persistent after you refresh this page. Please check if the table structure has been changed.'),
  1463. $property
  1464. )
  1465. );
  1466. }
  1467. }
  1468. // save the value
  1469. $this->uiprefs[$property] = $value;
  1470. // check if pmadb is set
  1471. if (strlen($GLOBALS['cfg']['Server']['pmadb'])
  1472. && strlen($GLOBALS['cfg']['Server']['table_uiprefs'])
  1473. ) {
  1474. return $this->saveUiprefsToDb();
  1475. }
  1476. return true;
  1477. }
  1478. /**
  1479. * Remove a property from UI preferences.
  1480. *
  1481. * @param string $property the property
  1482. *
  1483. * @return true|PMA_Message
  1484. */
  1485. public function removeUiProp($property)
  1486. {
  1487. if (! isset($this->uiprefs)) {
  1488. $this->loadUiPrefs();
  1489. }
  1490. if (isset($this->uiprefs[$property])) {
  1491. unset($this->uiprefs[$property]);
  1492. // check if pmadb is set
  1493. if (strlen($GLOBALS['cfg']['Server']['pmadb'])
  1494. && strlen($GLOBALS['cfg']['Server']['table_uiprefs'])
  1495. ) {
  1496. return $this->saveUiprefsToDb();
  1497. }
  1498. }
  1499. return true;
  1500. }
  1501. /**
  1502. * Get all column names which are MySQL reserved words
  1503. *
  1504. * @return array
  1505. * @access public
  1506. */
  1507. public function getReservedColumnNames()
  1508. {
  1509. $columns = $this->getColumns($backquoted = false);
  1510. $return = array();
  1511. foreach ($columns as $column) {
  1512. $temp = explode('.', $column);
  1513. $column_name = $temp[2];
  1514. if (PMA_SQP_isKeyWord($column_name)) {
  1515. $return[] = $column_name;
  1516. }
  1517. }
  1518. return $return;
  1519. }
  1520. }
  1521. ?>