Tracker.class.php 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086
  1. <?php
  2. /* vim: set expandtab sw=4 ts=4 sts=4: */
  3. /**
  4. * Tracking changes on databases, tables and views
  5. *
  6. * @package PhpMyAdmin
  7. */
  8. if (! defined('PHPMYADMIN')) {
  9. exit;
  10. }
  11. /**
  12. * This class tracks changes on databases, tables and views.
  13. *
  14. * @package PhpMyAdmin
  15. *
  16. * @todo use stristr instead of strstr
  17. */
  18. class PMA_Tracker
  19. {
  20. /**
  21. * Whether tracking is ready.
  22. */
  23. static protected $enabled = false;
  24. /**
  25. * Defines the internal PMA table which contains tracking data.
  26. *
  27. * @access protected
  28. * @var string
  29. */
  30. static protected $pma_table;
  31. /**
  32. * Defines the usage of DROP TABLE statment in SQL dumps.
  33. *
  34. * @access protected
  35. * @var boolean
  36. */
  37. static protected $add_drop_table;
  38. /**
  39. * Defines the usage of DROP VIEW statment in SQL dumps.
  40. *
  41. * @access protected
  42. * @var boolean
  43. */
  44. static protected $add_drop_view;
  45. /**
  46. * Defines the usage of DROP DATABASE statment in SQL dumps.
  47. *
  48. * @access protected
  49. * @var boolean
  50. */
  51. static protected $add_drop_database;
  52. /**
  53. * Defines auto-creation of tracking versions.
  54. *
  55. * @var boolean
  56. */
  57. static protected $version_auto_create;
  58. /**
  59. * Defines the default set of tracked statements.
  60. *
  61. * @var string
  62. */
  63. static protected $default_tracking_set;
  64. /**
  65. * Flags copied from `tracking` column definition in `pma_tracking` table.
  66. * Used for column type conversion in Drizzle.
  67. *
  68. * @var array
  69. */
  70. static private $_tracking_set_flags = array(
  71. 'UPDATE','REPLACE','INSERT','DELETE','TRUNCATE','CREATE DATABASE',
  72. 'ALTER DATABASE','DROP DATABASE','CREATE TABLE','ALTER TABLE',
  73. 'RENAME TABLE','DROP TABLE','CREATE INDEX','DROP INDEX',
  74. 'CREATE VIEW','ALTER VIEW','DROP VIEW'
  75. );
  76. /**
  77. * Initializes settings.
  78. *
  79. * @static
  80. *
  81. * @return void
  82. */
  83. static protected function init()
  84. {
  85. self::$pma_table = PMA_Util::backquote($GLOBALS['cfg']['Server']['pmadb'])
  86. . '.' . PMA_Util::backquote($GLOBALS['cfg']['Server']['tracking']);
  87. self::$add_drop_table
  88. = $GLOBALS['cfg']['Server']['tracking_add_drop_table'];
  89. self::$add_drop_view
  90. = $GLOBALS['cfg']['Server']['tracking_add_drop_view'];
  91. self::$add_drop_database
  92. = $GLOBALS['cfg']['Server']['tracking_add_drop_database'];
  93. self::$default_tracking_set
  94. = $GLOBALS['cfg']['Server']['tracking_default_statements'];
  95. self::$version_auto_create
  96. = $GLOBALS['cfg']['Server']['tracking_version_auto_create'];
  97. }
  98. /**
  99. * Actually enables tracking. This needs to be done after all
  100. * underlaying code is initialized.
  101. *
  102. * @static
  103. *
  104. * @return void
  105. */
  106. static public function enable()
  107. {
  108. self::$enabled = true;
  109. }
  110. /**
  111. * Gets the on/off value of the Tracker module, starts initialization.
  112. *
  113. * @static
  114. *
  115. * @return boolean (true=on|false=off)
  116. */
  117. static public function isActive()
  118. {
  119. if (! self::$enabled) {
  120. return false;
  121. }
  122. /* We need to avoid attempt to track any queries
  123. * from PMA_getRelationsParam
  124. */
  125. self::$enabled = false;
  126. $cfgRelation = PMA_getRelationsParam();
  127. /* Restore original state */
  128. self::$enabled = true;
  129. if (! $cfgRelation['trackingwork']) {
  130. return false;
  131. }
  132. self::init();
  133. if (isset(self::$pma_table)) {
  134. return true;
  135. } else {
  136. return false;
  137. }
  138. }
  139. /**
  140. * Parses the name of a table from a SQL statement substring.
  141. *
  142. * @param string $string part of SQL statement
  143. *
  144. * @static
  145. *
  146. * @return string the name of table
  147. */
  148. static protected function getTableName($string)
  149. {
  150. if (strstr($string, '.')) {
  151. $temp = explode('.', $string);
  152. $tablename = $temp[1];
  153. } else {
  154. $tablename = $string;
  155. }
  156. $str = explode("\n", $tablename);
  157. $tablename = $str[0];
  158. $tablename = str_replace(';', '', $tablename);
  159. $tablename = str_replace('`', '', $tablename);
  160. $tablename = trim($tablename);
  161. return $tablename;
  162. }
  163. /**
  164. * Gets the tracking status of a table, is it active or deactive ?
  165. *
  166. * @param string $dbname name of database
  167. * @param string $tablename name of table
  168. *
  169. * @static
  170. *
  171. * @return boolean true or false
  172. */
  173. static public function isTracked($dbname, $tablename)
  174. {
  175. if (! self::$enabled) {
  176. return false;
  177. }
  178. /* We need to avoid attempt to track any queries
  179. * from PMA_getRelationsParam
  180. */
  181. self::$enabled = false;
  182. $cfgRelation = PMA_getRelationsParam();
  183. /* Restore original state */
  184. self::$enabled = true;
  185. if (! $cfgRelation['trackingwork']) {
  186. return false;
  187. }
  188. $sql_query = " SELECT tracking_active FROM " . self::$pma_table .
  189. " WHERE db_name = '" . PMA_Util::sqlAddSlashes($dbname) . "' " .
  190. " AND table_name = '" . PMA_Util::sqlAddSlashes($tablename) . "' " .
  191. " ORDER BY version DESC";
  192. $row = $GLOBALS['dbi']->fetchArray(PMA_queryAsControlUser($sql_query));
  193. if (isset($row['tracking_active']) && $row['tracking_active'] == 1) {
  194. return true;
  195. } else {
  196. return false;
  197. }
  198. }
  199. /**
  200. * Returns the comment line for the log.
  201. *
  202. * @return string Comment, contains date and username
  203. */
  204. static public function getLogComment()
  205. {
  206. $date = date('Y-m-d H:i:s');
  207. return "# log " . $date . " " . $GLOBALS['cfg']['Server']['user'] . "\n";
  208. }
  209. /**
  210. * Creates tracking version of a table / view
  211. * (in other words: create a job to track future changes on the table).
  212. *
  213. * @param string $dbname name of database
  214. * @param string $tablename name of table
  215. * @param string $version version
  216. * @param string $tracking_set set of tracking statements
  217. * @param bool $is_view if table is a view
  218. *
  219. * @static
  220. *
  221. * @return int result of version insertion
  222. */
  223. static public function createVersion($dbname, $tablename, $version,
  224. $tracking_set = '', $is_view = false
  225. ) {
  226. global $sql_backquotes, $export_type;
  227. if ($tracking_set == '') {
  228. $tracking_set = self::$default_tracking_set;
  229. }
  230. // get Export SQL instance
  231. include_once "libraries/plugin_interface.lib.php";
  232. $export_sql_plugin = PMA_getPlugin(
  233. "export",
  234. "sql",
  235. 'libraries/plugins/export/',
  236. array(
  237. 'export_type' => $export_type,
  238. 'single_table' => false,
  239. )
  240. );
  241. $sql_backquotes = true;
  242. $date = date('Y-m-d H:i:s');
  243. // Get data definition snapshot of table
  244. $columns = $GLOBALS['dbi']->getColumns($dbname, $tablename, null, true);
  245. // int indices to reduce size
  246. $columns = array_values($columns);
  247. // remove Privileges to reduce size
  248. for ($i = 0, $nb = count($columns); $i < $nb; $i++) {
  249. unset($columns[$i]['Privileges']);
  250. }
  251. $indexes = $GLOBALS['dbi']->getTableIndexes($dbname, $tablename);
  252. $snapshot = array('COLUMNS' => $columns, 'INDEXES' => $indexes);
  253. $snapshot = serialize($snapshot);
  254. // Get DROP TABLE / DROP VIEW and CREATE TABLE SQL statements
  255. $sql_backquotes = true;
  256. $create_sql = "";
  257. if (self::$add_drop_table == true && $is_view == false) {
  258. $create_sql .= self::getLogComment()
  259. . 'DROP TABLE IF EXISTS ' . PMA_Util::backquote($tablename) . ";\n";
  260. }
  261. if (self::$add_drop_view == true && $is_view == true) {
  262. $create_sql .= self::getLogComment()
  263. . 'DROP VIEW IF EXISTS ' . PMA_Util::backquote($tablename) . ";\n";
  264. }
  265. $create_sql .= self::getLogComment() .
  266. $export_sql_plugin->getTableDef($dbname, $tablename, "\n", "");
  267. // Save version
  268. $sql_query = "/*NOTRACK*/\n" .
  269. "INSERT INTO" . self::$pma_table . " (" .
  270. "db_name, " .
  271. "table_name, " .
  272. "version, " .
  273. "date_created, " .
  274. "date_updated, " .
  275. "schema_snapshot, " .
  276. "schema_sql, " .
  277. "data_sql, " .
  278. "tracking " .
  279. ") " .
  280. "values (
  281. '" . PMA_Util::sqlAddSlashes($dbname) . "',
  282. '" . PMA_Util::sqlAddSlashes($tablename) . "',
  283. '" . PMA_Util::sqlAddSlashes($version) . "',
  284. '" . PMA_Util::sqlAddSlashes($date) . "',
  285. '" . PMA_Util::sqlAddSlashes($date) . "',
  286. '" . PMA_Util::sqlAddSlashes($snapshot) . "',
  287. '" . PMA_Util::sqlAddSlashes($create_sql) . "',
  288. '" . PMA_Util::sqlAddSlashes("\n") . "',
  289. '" . PMA_Util::sqlAddSlashes(self::_transformTrackingSet($tracking_set))
  290. . "' )";
  291. $result = PMA_queryAsControlUser($sql_query);
  292. if ($result) {
  293. // Deactivate previous version
  294. self::deactivateTracking($dbname, $tablename, ($version - 1));
  295. }
  296. return $result;
  297. }
  298. /**
  299. * Removes all tracking data for a table
  300. *
  301. * @param string $dbname name of database
  302. * @param string $tablename name of table
  303. *
  304. * @static
  305. *
  306. * @return int result of version insertion
  307. */
  308. static public function deleteTracking($dbname, $tablename)
  309. {
  310. $sql_query = "/*NOTRACK*/\n"
  311. . "DELETE FROM " . self::$pma_table
  312. . " WHERE `db_name` = '"
  313. . PMA_Util::sqlAddSlashes($dbname) . "'"
  314. . " AND `table_name` = '"
  315. . PMA_Util::sqlAddSlashes($tablename) . "'";
  316. $result = PMA_queryAsControlUser($sql_query);
  317. return $result;
  318. }
  319. /**
  320. * Creates tracking version of a database
  321. * (in other words: create a job to track future changes on the database).
  322. *
  323. * @param string $dbname name of database
  324. * @param string $version version
  325. * @param string $query query
  326. * @param string $tracking_set set of tracking statements
  327. *
  328. * @static
  329. *
  330. * @return int result of version insertion
  331. */
  332. static public function createDatabaseVersion($dbname, $version, $query,
  333. $tracking_set = 'CREATE DATABASE,ALTER DATABASE,DROP DATABASE'
  334. ) {
  335. $date = date('Y-m-d H:i:s');
  336. if ($tracking_set == '') {
  337. $tracking_set = self::$default_tracking_set;
  338. }
  339. $create_sql = "";
  340. if (self::$add_drop_database == true) {
  341. $create_sql .= self::getLogComment()
  342. . 'DROP DATABASE IF EXISTS ' . PMA_Util::backquote($dbname) . ";\n";
  343. }
  344. $create_sql .= self::getLogComment() . $query;
  345. // Save version
  346. $sql_query = "/*NOTRACK*/\n" .
  347. "INSERT INTO" . self::$pma_table . " (" .
  348. "db_name, " .
  349. "table_name, " .
  350. "version, " .
  351. "date_created, " .
  352. "date_updated, " .
  353. "schema_snapshot, " .
  354. "schema_sql, " .
  355. "data_sql, " .
  356. "tracking " .
  357. ") " .
  358. "values (
  359. '" . PMA_Util::sqlAddSlashes($dbname) . "',
  360. '" . PMA_Util::sqlAddSlashes('') . "',
  361. '" . PMA_Util::sqlAddSlashes($version) . "',
  362. '" . PMA_Util::sqlAddSlashes($date) . "',
  363. '" . PMA_Util::sqlAddSlashes($date) . "',
  364. '" . PMA_Util::sqlAddSlashes('') . "',
  365. '" . PMA_Util::sqlAddSlashes($create_sql) . "',
  366. '" . PMA_Util::sqlAddSlashes("\n") . "',
  367. '" . PMA_Util::sqlAddSlashes(self::_transformTrackingSet($tracking_set))
  368. . "' )";
  369. $result = PMA_queryAsControlUser($sql_query);
  370. return $result;
  371. }
  372. /**
  373. * Changes tracking of a table.
  374. *
  375. * @param string $dbname name of database
  376. * @param string $tablename name of table
  377. * @param string $version version
  378. * @param integer $new_state the new state of tracking
  379. *
  380. * @static
  381. *
  382. * @return int result of SQL query
  383. */
  384. static private function _changeTracking($dbname, $tablename,
  385. $version, $new_state
  386. ) {
  387. $sql_query = " UPDATE " . self::$pma_table .
  388. " SET `tracking_active` = '" . $new_state . "' " .
  389. " WHERE `db_name` = '" . PMA_Util::sqlAddSlashes($dbname) . "' " .
  390. " AND `table_name` = '" . PMA_Util::sqlAddSlashes($tablename) . "' " .
  391. " AND `version` = '" . PMA_Util::sqlAddSlashes($version) . "' ";
  392. $result = PMA_queryAsControlUser($sql_query);
  393. return $result;
  394. }
  395. /**
  396. * Changes tracking data of a table.
  397. *
  398. * @param string $dbname name of database
  399. * @param string $tablename name of table
  400. * @param string $version version
  401. * @param string $type type of data(DDL || DML)
  402. * @param string|array $new_data the new tracking data
  403. *
  404. * @static
  405. *
  406. * @return bool result of change
  407. */
  408. static public function changeTrackingData($dbname, $tablename,
  409. $version, $type, $new_data
  410. ) {
  411. if ($type == 'DDL') {
  412. $save_to = 'schema_sql';
  413. } elseif ($type == 'DML') {
  414. $save_to = 'data_sql';
  415. } else {
  416. return false;
  417. }
  418. $date = date('Y-m-d H:i:s');
  419. $new_data_processed = '';
  420. if (is_array($new_data)) {
  421. foreach ($new_data as $data) {
  422. $new_data_processed .= '# log ' . $date . ' ' . $data['username']
  423. . PMA_Util::sqlAddSlashes($data['statement']) . "\n";
  424. }
  425. } else {
  426. $new_data_processed = $new_data;
  427. }
  428. $sql_query = " UPDATE " . self::$pma_table .
  429. " SET `" . $save_to . "` = '" . $new_data_processed . "' " .
  430. " WHERE `db_name` = '" . PMA_Util::sqlAddSlashes($dbname) . "' " .
  431. " AND `table_name` = '" . PMA_Util::sqlAddSlashes($tablename) . "' " .
  432. " AND `version` = '" . PMA_Util::sqlAddSlashes($version) . "' ";
  433. $result = PMA_queryAsControlUser($sql_query);
  434. return $result;
  435. }
  436. /**
  437. * Activates tracking of a table.
  438. *
  439. * @param string $dbname name of database
  440. * @param string $tablename name of table
  441. * @param string $version version
  442. *
  443. * @static
  444. *
  445. * @return int result of SQL query
  446. */
  447. static public function activateTracking($dbname, $tablename, $version)
  448. {
  449. return self::_changeTracking($dbname, $tablename, $version, 1);
  450. }
  451. /**
  452. * Deactivates tracking of a table.
  453. *
  454. * @param string $dbname name of database
  455. * @param string $tablename name of table
  456. * @param string $version version
  457. *
  458. * @static
  459. *
  460. * @return int result of SQL query
  461. */
  462. static public function deactivateTracking($dbname, $tablename, $version)
  463. {
  464. return self::_changeTracking($dbname, $tablename, $version, 0);
  465. }
  466. /**
  467. * Gets the newest version of a tracking job
  468. * (in other words: gets the HEAD version).
  469. *
  470. * @param string $dbname name of database
  471. * @param string $tablename name of table
  472. * @param string $statement tracked statement
  473. *
  474. * @static
  475. *
  476. * @return int (-1 if no version exists | > 0 if a version exists)
  477. */
  478. static public function getVersion($dbname, $tablename, $statement = null)
  479. {
  480. $sql_query = " SELECT MAX(version) FROM " . self::$pma_table .
  481. " WHERE `db_name` = '" . PMA_Util::sqlAddSlashes($dbname) . "' " .
  482. " AND `table_name` = '" . PMA_Util::sqlAddSlashes($tablename) . "' ";
  483. if ($statement != "") {
  484. if (PMA_DRIZZLE) {
  485. $sql_query .= ' AND tracking & '
  486. . self::_transformTrackingSet($statement) . ' <> 0';
  487. } else {
  488. $sql_query .= " AND FIND_IN_SET('"
  489. . $statement . "',tracking) > 0" ;
  490. }
  491. }
  492. $row = $GLOBALS['dbi']->fetchArray(PMA_queryAsControlUser($sql_query));
  493. return isset($row[0])
  494. ? $row[0]
  495. : -1;
  496. }
  497. /**
  498. * Gets the record of a tracking job.
  499. *
  500. * @param string $dbname name of database
  501. * @param string $tablename name of table
  502. * @param string $version version number
  503. *
  504. * @static
  505. *
  506. * @return mixed record DDM log, DDL log, structure snapshot, tracked
  507. * statements.
  508. */
  509. static public function getTrackedData($dbname, $tablename, $version)
  510. {
  511. if (! isset(self::$pma_table)) {
  512. self::init();
  513. }
  514. $sql_query = " SELECT * FROM " . self::$pma_table .
  515. " WHERE `db_name` = '" . PMA_Util::sqlAddSlashes($dbname) . "' ";
  516. if (! empty($tablename)) {
  517. $sql_query .= " AND `table_name` = '"
  518. . PMA_Util::sqlAddSlashes($tablename) . "' ";
  519. }
  520. $sql_query .= " AND `version` = '" . PMA_Util::sqlAddSlashes($version)
  521. . "' " . " ORDER BY `version` DESC LIMIT 1";
  522. $mixed = $GLOBALS['dbi']->fetchAssoc(PMA_queryAsControlUser($sql_query));
  523. // Parse log
  524. $log_schema_entries = explode('# log ', $mixed['schema_sql']);
  525. $log_data_entries = explode('# log ', $mixed['data_sql']);
  526. $ddl_date_from = $date = date('Y-m-d H:i:s');
  527. $ddlog = array();
  528. $i = 0;
  529. // Iterate tracked data definition statements
  530. // For each log entry we want to get date, username and statement
  531. foreach ($log_schema_entries as $log_entry) {
  532. if (trim($log_entry) != '') {
  533. $date = substr($log_entry, 0, 19);
  534. $username = substr($log_entry, 20, strpos($log_entry, "\n") - 20);
  535. if ($i == 0) {
  536. $ddl_date_from = $date;
  537. }
  538. $statement = rtrim(strstr($log_entry, "\n"));
  539. $ddlog[] = array( 'date' => $date,
  540. 'username'=> $username,
  541. 'statement' => $statement );
  542. $i++;
  543. }
  544. }
  545. $date_from = $ddl_date_from;
  546. $ddl_date_to = $date;
  547. $dml_date_from = $date_from;
  548. $dmlog = array();
  549. $i = 0;
  550. // Iterate tracked data manipulation statements
  551. // For each log entry we want to get date, username and statement
  552. foreach ($log_data_entries as $log_entry) {
  553. if (trim($log_entry) != '') {
  554. $date = substr($log_entry, 0, 19);
  555. $username = substr($log_entry, 20, strpos($log_entry, "\n") - 20);
  556. if ($i == 0) {
  557. $dml_date_from = $date;
  558. }
  559. $statement = rtrim(strstr($log_entry, "\n"));
  560. $dmlog[] = array( 'date' => $date,
  561. 'username' => $username,
  562. 'statement' => $statement );
  563. $i++;
  564. }
  565. }
  566. $dml_date_to = $date;
  567. // Define begin and end of date range for both logs
  568. $data = array();
  569. if (strtotime($ddl_date_from) <= strtotime($dml_date_from)) {
  570. $data['date_from'] = $ddl_date_from;
  571. } else {
  572. $data['date_from'] = $dml_date_from;
  573. }
  574. if (strtotime($ddl_date_to) >= strtotime($dml_date_to)) {
  575. $data['date_to'] = $ddl_date_to;
  576. } else {
  577. $data['date_to'] = $dml_date_to;
  578. }
  579. $data['ddlog'] = $ddlog;
  580. $data['dmlog'] = $dmlog;
  581. $data['tracking'] = self::_transformTrackingSet($mixed['tracking']);
  582. $data['schema_snapshot'] = $mixed['schema_snapshot'];
  583. return $data;
  584. }
  585. /**
  586. * Parses a query. Gets
  587. * - statement identifier (UPDATE, ALTER TABLE, ...)
  588. * - type of statement, is it part of DDL or DML ?
  589. * - tablename
  590. *
  591. * @param string $query query
  592. *
  593. * @static
  594. * @todo: using PMA SQL Parser when possible
  595. * @todo: support multi-table/view drops
  596. *
  597. * @return mixed Array containing identifier, type and tablename.
  598. *
  599. */
  600. static public function parseQuery($query)
  601. {
  602. // Usage of PMA_SQP does not work here
  603. //
  604. // require_once("libraries/sqlparser.lib.php");
  605. // $parsed_sql = PMA_SQP_parse($query);
  606. // $sql_info = PMA_SQP_analyze($parsed_sql);
  607. $query = str_replace("\n", " ", $query);
  608. $query = str_replace("\r", " ", $query);
  609. $query = trim($query);
  610. $query = trim($query, ' -');
  611. $tokens = explode(" ", $query);
  612. foreach ($tokens as $key => $value) {
  613. $tokens[$key] = strtoupper($value);
  614. }
  615. // Parse USE statement, need it for SQL dump imports
  616. if (substr($query, 0, 4) == 'USE ') {
  617. $prefix = explode('USE ', $query);
  618. $GLOBALS['db'] = self::getTableName($prefix[1]);
  619. }
  620. /*
  621. * DDL statements
  622. */
  623. $result = array();
  624. $result['type'] = 'DDL';
  625. // Parse CREATE VIEW statement
  626. if (in_array('CREATE', $tokens) == true
  627. && in_array('VIEW', $tokens) == true
  628. && in_array('AS', $tokens) == true
  629. ) {
  630. $result['identifier'] = 'CREATE VIEW';
  631. $index = array_search('VIEW', $tokens);
  632. $result['tablename'] = strtolower(
  633. self::getTableName($tokens[$index + 1])
  634. );
  635. }
  636. // Parse ALTER VIEW statement
  637. if (in_array('ALTER', $tokens) == true
  638. && in_array('VIEW', $tokens) == true
  639. && in_array('AS', $tokens) == true
  640. && ! isset($result['identifier'])
  641. ) {
  642. $result['identifier'] = 'ALTER VIEW';
  643. $index = array_search('VIEW', $tokens);
  644. $result['tablename'] = strtolower(
  645. self::getTableName($tokens[$index + 1])
  646. );
  647. }
  648. // Parse DROP VIEW statement
  649. if (! isset($result['identifier'])
  650. && substr($query, 0, 10) == 'DROP VIEW '
  651. ) {
  652. $result['identifier'] = 'DROP VIEW';
  653. $prefix = explode('DROP VIEW ', $query);
  654. $str = strstr($prefix[1], 'IF EXISTS');
  655. if ($str == false ) {
  656. $str = $prefix[1];
  657. }
  658. $result['tablename'] = self::getTableName($str);
  659. }
  660. // Parse CREATE DATABASE statement
  661. if (! isset($result['identifier'])
  662. && substr($query, 0, 15) == 'CREATE DATABASE'
  663. ) {
  664. $result['identifier'] = 'CREATE DATABASE';
  665. $str = str_replace('CREATE DATABASE', '', $query);
  666. $str = str_replace('IF NOT EXISTS', '', $str);
  667. $prefix = explode('DEFAULT ', $str);
  668. $result['tablename'] = '';
  669. $GLOBALS['db'] = self::getTableName($prefix[0]);
  670. }
  671. // Parse ALTER DATABASE statement
  672. if (! isset($result['identifier'])
  673. && substr($query, 0, 14) == 'ALTER DATABASE'
  674. ) {
  675. $result['identifier'] = 'ALTER DATABASE';
  676. $result['tablename'] = '';
  677. }
  678. // Parse DROP DATABASE statement
  679. if (! isset($result['identifier'])
  680. && substr($query, 0, 13) == 'DROP DATABASE'
  681. ) {
  682. $result['identifier'] = 'DROP DATABASE';
  683. $str = str_replace('DROP DATABASE', '', $query);
  684. $str = str_replace('IF EXISTS', '', $str);
  685. $GLOBALS['db'] = self::getTableName($str);
  686. $result['tablename'] = '';
  687. }
  688. // Parse CREATE TABLE statement
  689. if (! isset($result['identifier'])
  690. && substr($query, 0, 12) == 'CREATE TABLE'
  691. ) {
  692. $result['identifier'] = 'CREATE TABLE';
  693. $query = str_replace('IF NOT EXISTS', '', $query);
  694. $prefix = explode('CREATE TABLE ', $query);
  695. $suffix = explode('(', $prefix[1]);
  696. $result['tablename'] = self::getTableName($suffix[0]);
  697. }
  698. // Parse ALTER TABLE statement
  699. if (! isset($result['identifier'])
  700. && substr($query, 0, 12) == 'ALTER TABLE '
  701. ) {
  702. $result['identifier'] = 'ALTER TABLE';
  703. $prefix = explode('ALTER TABLE ', $query);
  704. $suffix = explode(' ', $prefix[1]);
  705. $result['tablename'] = self::getTableName($suffix[0]);
  706. }
  707. // Parse DROP TABLE statement
  708. if (! isset($result['identifier'])
  709. && substr($query, 0, 11) == 'DROP TABLE '
  710. ) {
  711. $result['identifier'] = 'DROP TABLE';
  712. $prefix = explode('DROP TABLE ', $query);
  713. $str = strstr($prefix[1], 'IF EXISTS');
  714. if ($str == false ) {
  715. $str = $prefix[1];
  716. }
  717. $result['tablename'] = self::getTableName($str);
  718. }
  719. // Parse CREATE INDEX statement
  720. if (! isset($result['identifier'])
  721. && (substr($query, 0, 12) == 'CREATE INDEX'
  722. || substr($query, 0, 19) == 'CREATE UNIQUE INDEX'
  723. || substr($query, 0, 20) == 'CREATE SPATIAL INDEX')
  724. ) {
  725. $result['identifier'] = 'CREATE INDEX';
  726. $prefix = explode('ON ', $query);
  727. $suffix = explode('(', $prefix[1]);
  728. $result['tablename'] = self::getTableName($suffix[0]);
  729. }
  730. // Parse DROP INDEX statement
  731. if (! isset($result['identifier'])
  732. && substr($query, 0, 10) == 'DROP INDEX'
  733. ) {
  734. $result['identifier'] = 'DROP INDEX';
  735. $prefix = explode('ON ', $query);
  736. $result['tablename'] = self::getTableName($prefix[1]);
  737. }
  738. // Parse RENAME TABLE statement
  739. if (! isset($result['identifier'])
  740. && substr($query, 0, 13) == 'RENAME TABLE '
  741. ) {
  742. $result['identifier'] = 'RENAME TABLE';
  743. $prefix = explode('RENAME TABLE ', $query);
  744. $names = explode(' TO ', $prefix[1]);
  745. $result['tablename'] = self::getTableName($names[0]);
  746. $result["tablename_after_rename"] = self::getTableName($names[1]);
  747. }
  748. /*
  749. * DML statements
  750. */
  751. if (! isset($result['identifier'])) {
  752. $result["type"] = 'DML';
  753. }
  754. // Parse UPDATE statement
  755. if (! isset($result['identifier'])
  756. && substr($query, 0, 6) == 'UPDATE'
  757. ) {
  758. $result['identifier'] = 'UPDATE';
  759. $prefix = explode('UPDATE ', $query);
  760. $suffix = explode(' ', $prefix[1]);
  761. $result['tablename'] = self::getTableName($suffix[0]);
  762. }
  763. // Parse INSERT INTO statement
  764. if (! isset($result['identifier'])
  765. && substr($query, 0, 11) == 'INSERT INTO'
  766. ) {
  767. $result['identifier'] = 'INSERT';
  768. $prefix = explode('INSERT INTO', $query);
  769. $suffix = explode('(', $prefix[1]);
  770. $result['tablename'] = self::getTableName($suffix[0]);
  771. }
  772. // Parse DELETE statement
  773. if (! isset($result['identifier'])
  774. && substr($query, 0, 6) == 'DELETE'
  775. ) {
  776. $result['identifier'] = 'DELETE';
  777. $prefix = explode('FROM ', $query);
  778. $suffix = explode(' ', $prefix[1]);
  779. $result['tablename'] = self::getTableName($suffix[0]);
  780. }
  781. // Parse TRUNCATE statement
  782. if (! isset($result['identifier'])
  783. && substr($query, 0, 8) == 'TRUNCATE'
  784. ) {
  785. $result['identifier'] = 'TRUNCATE';
  786. $prefix = explode('TRUNCATE', $query);
  787. $result['tablename'] = self::getTableName($prefix[1]);
  788. }
  789. return $result;
  790. }
  791. /**
  792. * Analyzes a given SQL statement and saves tracking data.
  793. *
  794. * @param string $query a SQL query
  795. *
  796. * @static
  797. *
  798. * @return void
  799. */
  800. static public function handleQuery($query)
  801. {
  802. // If query is marked as untouchable, leave
  803. if (strstr($query, "/*NOTRACK*/")) {
  804. return;
  805. }
  806. if (! (substr($query, -1) == ';')) {
  807. $query = $query . ";\n";
  808. }
  809. // Get some information about query
  810. $result = self::parseQuery($query);
  811. // Get database name
  812. $dbname = trim(isset($GLOBALS['db']) ? $GLOBALS['db'] : '', '`');
  813. // $dbname can be empty, for example when coming from Synchronize
  814. // and this is a query for the remote server
  815. if (empty($dbname)) {
  816. return;
  817. }
  818. // If we found a valid statement
  819. if (isset($result['identifier'])) {
  820. $version = self::getVersion(
  821. $dbname, $result['tablename'], $result['identifier']
  822. );
  823. // If version not exists and auto-creation is enabled
  824. if (self::$version_auto_create == true
  825. && self::isTracked($dbname, $result['tablename']) == false
  826. && $version == -1
  827. ) {
  828. // Create the version
  829. switch ($result['identifier']) {
  830. case 'CREATE TABLE':
  831. self::createVersion($dbname, $result['tablename'], '1');
  832. break;
  833. case 'CREATE VIEW':
  834. self::createVersion(
  835. $dbname, $result['tablename'], '1', '', true
  836. );
  837. break;
  838. case 'CREATE DATABASE':
  839. self::createDatabaseVersion($dbname, '1', $query);
  840. break;
  841. } // end switch
  842. }
  843. // If version exists
  844. if (self::isTracked($dbname, $result['tablename']) && $version != -1) {
  845. if ($result['type'] == 'DDL') {
  846. $save_to = 'schema_sql';
  847. } elseif ($result['type'] == 'DML') {
  848. $save_to = 'data_sql';
  849. } else {
  850. $save_to = '';
  851. }
  852. $date = date('Y-m-d H:i:s');
  853. // Cut off `dbname`. from query
  854. $query = preg_replace('/`' . preg_quote($dbname) . '`\s?\./', '', $query);
  855. // Add log information
  856. $query = self::getLogComment() . $query ;
  857. // Mark it as untouchable
  858. $sql_query = " /*NOTRACK*/\n"
  859. . " UPDATE " . self::$pma_table
  860. . " SET " . PMA_Util::backquote($save_to)
  861. . " = CONCAT( " . PMA_Util::backquote($save_to) . ",'\n"
  862. . PMA_Util::sqlAddSlashes($query) . "') ,"
  863. . " `date_updated` = '" . $date . "' ";
  864. // If table was renamed we have to change
  865. // the tablename attribute in pma_tracking too
  866. if ($result['identifier'] == 'RENAME TABLE') {
  867. $sql_query .= ', `table_name` = \''
  868. . PMA_Util::sqlAddSlashes($result['tablename_after_rename'])
  869. . '\' ';
  870. }
  871. // Save the tracking information only for
  872. // 1. the database
  873. // 2. the table / view
  874. // 3. the statements
  875. // we want to track
  876. $sql_query .=
  877. " WHERE FIND_IN_SET('" . $result['identifier'] . "',tracking) > 0" .
  878. " AND `db_name` = '" . PMA_Util::sqlAddSlashes($dbname) . "' " .
  879. " AND `table_name` = '"
  880. . PMA_Util::sqlAddSlashes($result['tablename']) . "' " .
  881. " AND `version` = '" . PMA_Util::sqlAddSlashes($version) . "' ";
  882. $result = PMA_queryAsControlUser($sql_query);
  883. }
  884. }
  885. }
  886. /**
  887. * Transforms tracking set for Drizzle, which has no SET type
  888. *
  889. * Converts int<>string for Drizzle, does nothing for MySQL
  890. *
  891. * @param int|string $tracking_set Set to convert
  892. *
  893. * @return int|string
  894. */
  895. static private function _transformTrackingSet($tracking_set)
  896. {
  897. if (!PMA_DRIZZLE) {
  898. return $tracking_set;
  899. }
  900. // init conversion array (key 3 doesn't exist in calculated array)
  901. if (isset(self::$_tracking_set_flags[3])) {
  902. // initialize flags
  903. $set = self::$_tracking_set_flags;
  904. $array = array();
  905. for ($i = 0, $nb = count($set); $i < $nb; $i++) {
  906. $flag = 1 << $i;
  907. $array[$flag] = $set[$i];
  908. $array[$set[$i]] = $flag;
  909. }
  910. self::$_tracking_set_flags = $array;
  911. }
  912. if (is_numeric($tracking_set)) {
  913. // int > string conversion
  914. $aflags = array();
  915. // count/2 - conversion table has both int > string
  916. // and string > int values
  917. for ($i = 0, $nb = count(self::$_tracking_set_flags)/2; $i < $nb; $i++) {
  918. $flag = 1 << $i;
  919. if ($tracking_set & $flag) {
  920. $aflags[] = self::$_tracking_set_flags[$flag];
  921. }
  922. }
  923. $flags = implode(',', $aflags);
  924. } else {
  925. // string > int conversion
  926. $flags = 0;
  927. foreach (explode(',', $tracking_set) as $strflag) {
  928. if ($strflag == '') {
  929. continue;
  930. }
  931. $flags |= self::$_tracking_set_flags[$strflag];
  932. }
  933. }
  934. return $flags;
  935. }
  936. }
  937. ?>