Table.class.php 63 KB

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