UPDATE 4.4.0.0
[phpmyadmin.git] / libraries / Tracker.class.php
blobe5d295647a3cc74941b2737f2966e99bf75dd77a
1 <?php
2 /* vim: set expandtab sw=4 ts=4 sts=4: */
3 /**
4 * Tracking changes on databases, tables and views
6 * @package PhpMyAdmin
7 */
8 if (! defined('PHPMYADMIN')) {
9 exit;
12 /**
13 * This class tracks changes on databases, tables and views.
15 * @package PhpMyAdmin
17 * @todo use stristr instead of strstr
19 class PMA_Tracker
21 /**
22 * Whether tracking is ready.
24 static protected $enabled = false;
26 /**
27 * Flags copied from `tracking` column definition in `pma_tracking` table.
28 * Used for column type conversion in Drizzle.
30 * @var array
32 static private $_tracking_set_flags = array(
33 'UPDATE','REPLACE','INSERT','DELETE','TRUNCATE','CREATE DATABASE',
34 'ALTER DATABASE','DROP DATABASE','CREATE TABLE','ALTER TABLE',
35 'RENAME TABLE','DROP TABLE','CREATE INDEX','DROP INDEX',
36 'CREATE VIEW','ALTER VIEW','DROP VIEW'
39 /**
40 * Actually enables tracking. This needs to be done after all
41 * underlaying code is initialized.
43 * @static
45 * @return void
47 static public function enable()
49 self::$enabled = true;
52 /**
53 * Gets the on/off value of the Tracker module, starts initialization.
55 * @static
57 * @return boolean (true=on|false=off)
59 static public function isActive()
61 if (! self::$enabled) {
62 return false;
64 /* We need to avoid attempt to track any queries
65 * from PMA_getRelationsParam
67 self::$enabled = false;
68 $cfgRelation = PMA_getRelationsParam();
69 /* Restore original state */
70 self::$enabled = true;
71 if (! $cfgRelation['trackingwork']) {
72 return false;
75 $pma_table = self::_getTrackingTable();
76 if (isset($pma_table)) {
77 return true;
78 } else {
79 return false;
83 /**
84 * Parses the name of a table from a SQL statement substring.
86 * @param string $string part of SQL statement
88 * @static
90 * @return string the name of table
92 static protected function getTableName($string)
94 if (/*overload*/mb_strstr($string, '.')) {
95 $temp = explode('.', $string);
96 $tablename = $temp[1];
97 } else {
98 $tablename = $string;
101 $str = explode("\n", $tablename);
102 $tablename = $str[0];
104 $tablename = str_replace(';', '', $tablename);
105 $tablename = str_replace('`', '', $tablename);
106 $tablename = trim($tablename);
108 return $tablename;
113 * Gets the tracking status of a table, is it active or deactive ?
115 * @param string $dbname name of database
116 * @param string $tablename name of table
118 * @static
120 * @return boolean true or false
122 static public function isTracked($dbname, $tablename)
124 if (! self::$enabled) {
125 return false;
127 /* We need to avoid attempt to track any queries
128 * from PMA_getRelationsParam
130 self::$enabled = false;
131 $cfgRelation = PMA_getRelationsParam();
132 /* Restore original state */
133 self::$enabled = true;
134 if (! $cfgRelation['trackingwork']) {
135 return false;
138 $sql_query = " SELECT tracking_active FROM " . self::_getTrackingTable() .
139 " WHERE db_name = '" . PMA_Util::sqlAddSlashes($dbname) . "' " .
140 " AND table_name = '" . PMA_Util::sqlAddSlashes($tablename) . "' " .
141 " ORDER BY version DESC";
143 $row = $GLOBALS['dbi']->fetchArray(PMA_queryAsControlUser($sql_query));
145 if (isset($row['tracking_active']) && $row['tracking_active'] == 1) {
146 return true;
147 } else {
148 return false;
153 * Returns the comment line for the log.
155 * @return string Comment, contains date and username
157 static public function getLogComment()
159 $date = date('Y-m-d H:i:s');
161 return "# log " . $date . " " . $GLOBALS['cfg']['Server']['user'] . "\n";
165 * Creates tracking version of a table / view
166 * (in other words: create a job to track future changes on the table).
168 * @param string $dbname name of database
169 * @param string $tablename name of table
170 * @param string $version version
171 * @param string $tracking_set set of tracking statements
172 * @param bool $is_view if table is a view
174 * @static
176 * @return int result of version insertion
178 static public function createVersion($dbname, $tablename, $version,
179 $tracking_set = '', $is_view = false
181 global $sql_backquotes, $export_type;
183 if ($tracking_set == '') {
184 $tracking_set
185 = $GLOBALS['cfg']['Server']['tracking_default_statements'];
188 // get Export SQL instance
189 include_once "libraries/plugin_interface.lib.php";
190 $export_sql_plugin = PMA_getPlugin(
191 "export",
192 "sql",
193 'libraries/plugins/export/',
194 array(
195 'export_type' => $export_type,
196 'single_table' => false,
200 $sql_backquotes = true;
202 $date = date('Y-m-d H:i:s');
204 // Get data definition snapshot of table
206 $columns = $GLOBALS['dbi']->getColumns($dbname, $tablename, null, true);
207 // int indices to reduce size
208 $columns = array_values($columns);
209 // remove Privileges to reduce size
210 for ($i = 0, $nb = count($columns); $i < $nb; $i++) {
211 unset($columns[$i]['Privileges']);
214 $indexes = $GLOBALS['dbi']->getTableIndexes($dbname, $tablename);
216 $snapshot = array('COLUMNS' => $columns, 'INDEXES' => $indexes);
217 $snapshot = serialize($snapshot);
219 // Get DROP TABLE / DROP VIEW and CREATE TABLE SQL statements
220 $sql_backquotes = true;
222 $create_sql = "";
224 if ($GLOBALS['cfg']['Server']['tracking_add_drop_table'] == true
225 && $is_view == false
227 $create_sql .= self::getLogComment()
228 . 'DROP TABLE IF EXISTS ' . PMA_Util::backquote($tablename) . ";\n";
232 if ($GLOBALS['cfg']['Server']['tracking_add_drop_view'] == true
233 && $is_view == true
235 $create_sql .= self::getLogComment()
236 . 'DROP VIEW IF EXISTS ' . PMA_Util::backquote($tablename) . ";\n";
239 $create_sql .= self::getLogComment() .
240 $export_sql_plugin->getTableDef($dbname, $tablename, "\n", "");
242 // Save version
244 $sql_query = "/*NOTRACK*/\n" .
245 "INSERT INTO " . self::_getTrackingTable() . " (" .
246 "db_name, " .
247 "table_name, " .
248 "version, " .
249 "date_created, " .
250 "date_updated, " .
251 "schema_snapshot, " .
252 "schema_sql, " .
253 "data_sql, " .
254 "tracking " .
255 ") " .
256 "values (
257 '" . PMA_Util::sqlAddSlashes($dbname) . "',
258 '" . PMA_Util::sqlAddSlashes($tablename) . "',
259 '" . PMA_Util::sqlAddSlashes($version) . "',
260 '" . PMA_Util::sqlAddSlashes($date) . "',
261 '" . PMA_Util::sqlAddSlashes($date) . "',
262 '" . PMA_Util::sqlAddSlashes($snapshot) . "',
263 '" . PMA_Util::sqlAddSlashes($create_sql) . "',
264 '" . PMA_Util::sqlAddSlashes("\n") . "',
265 '" . PMA_Util::sqlAddSlashes(self::_transformTrackingSet($tracking_set))
266 . "' )";
268 $result = PMA_queryAsControlUser($sql_query);
270 if ($result) {
271 // Deactivate previous version
272 self::deactivateTracking($dbname, $tablename, ($version - 1));
275 return $result;
280 * Removes all tracking data for a table or a version of a table
282 * @param string $dbname name of database
283 * @param string $tablename name of table
284 * @param string $version version
286 * @static
288 * @return int result of version insertion
290 static public function deleteTracking($dbname, $tablename, $version = '')
292 $sql_query = "/*NOTRACK*/\n"
293 . "DELETE FROM " . self::_getTrackingTable()
294 . " WHERE `db_name` = '"
295 . PMA_Util::sqlAddSlashes($dbname) . "'"
296 . " AND `table_name` = '"
297 . PMA_Util::sqlAddSlashes($tablename) . "'";
298 if ($version) {
299 $sql_query .= " AND `version` = '"
300 . PMA_Util::sqlAddSlashes($version) . "'";
302 $result = PMA_queryAsControlUser($sql_query);
304 return $result;
308 * Creates tracking version of a database
309 * (in other words: create a job to track future changes on the database).
311 * @param string $dbname name of database
312 * @param string $version version
313 * @param string $query query
314 * @param string $tracking_set set of tracking statements
316 * @static
318 * @return int result of version insertion
320 static public function createDatabaseVersion($dbname, $version, $query,
321 $tracking_set = 'CREATE DATABASE,ALTER DATABASE,DROP DATABASE'
323 $date = date('Y-m-d H:i:s');
325 if ($tracking_set == '') {
326 $tracking_set
327 = $GLOBALS['cfg']['Server']['tracking_default_statements'];
330 $create_sql = "";
332 if ($GLOBALS['cfg']['Server']['tracking_add_drop_database'] == true) {
333 $create_sql .= self::getLogComment()
334 . 'DROP DATABASE IF EXISTS ' . PMA_Util::backquote($dbname) . ";\n";
337 $create_sql .= self::getLogComment() . $query;
339 // Save version
340 $sql_query = "/*NOTRACK*/\n" .
341 "INSERT INTO " . self::_getTrackingTable() . " (" .
342 "db_name, " .
343 "table_name, " .
344 "version, " .
345 "date_created, " .
346 "date_updated, " .
347 "schema_snapshot, " .
348 "schema_sql, " .
349 "data_sql, " .
350 "tracking " .
351 ") " .
352 "values (
353 '" . PMA_Util::sqlAddSlashes($dbname) . "',
354 '" . PMA_Util::sqlAddSlashes('') . "',
355 '" . PMA_Util::sqlAddSlashes($version) . "',
356 '" . PMA_Util::sqlAddSlashes($date) . "',
357 '" . PMA_Util::sqlAddSlashes($date) . "',
358 '" . PMA_Util::sqlAddSlashes('') . "',
359 '" . PMA_Util::sqlAddSlashes($create_sql) . "',
360 '" . PMA_Util::sqlAddSlashes("\n") . "',
361 '" . PMA_Util::sqlAddSlashes(self::_transformTrackingSet($tracking_set))
362 . "' )";
364 $result = PMA_queryAsControlUser($sql_query);
366 return $result;
372 * Changes tracking of a table.
374 * @param string $dbname name of database
375 * @param string $tablename name of table
376 * @param string $version version
377 * @param integer $new_state the new state of tracking
379 * @static
381 * @return int result of SQL query
383 static private function _changeTracking($dbname, $tablename,
384 $version, $new_state
387 $sql_query = " UPDATE " . self::_getTrackingTable() .
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) . "' ";
393 $result = PMA_queryAsControlUser($sql_query);
395 return $result;
399 * Changes tracking data of a table.
401 * @param string $dbname name of database
402 * @param string $tablename name of table
403 * @param string $version version
404 * @param string $type type of data(DDL || DML)
405 * @param string|array $new_data the new tracking data
407 * @static
409 * @return bool result of change
411 static public function changeTrackingData($dbname, $tablename,
412 $version, $type, $new_data
414 if ($type == 'DDL') {
415 $save_to = 'schema_sql';
416 } elseif ($type == 'DML') {
417 $save_to = 'data_sql';
418 } else {
419 return false;
421 $date = date('Y-m-d H:i:s');
423 $new_data_processed = '';
424 if (is_array($new_data)) {
425 foreach ($new_data as $data) {
426 $new_data_processed .= '# log ' . $date . ' ' . $data['username']
427 . PMA_Util::sqlAddSlashes($data['statement']) . "\n";
429 } else {
430 $new_data_processed = $new_data;
433 $sql_query = " UPDATE " . self::_getTrackingTable() .
434 " SET `" . $save_to . "` = '" . $new_data_processed . "' " .
435 " WHERE `db_name` = '" . PMA_Util::sqlAddSlashes($dbname) . "' " .
436 " AND `table_name` = '" . PMA_Util::sqlAddSlashes($tablename) . "' " .
437 " AND `version` = '" . PMA_Util::sqlAddSlashes($version) . "' ";
439 $result = PMA_queryAsControlUser($sql_query);
441 return $result;
445 * Activates tracking of a table.
447 * @param string $dbname name of database
448 * @param string $tablename name of table
449 * @param string $version version
451 * @static
453 * @return int result of SQL query
455 static public function activateTracking($dbname, $tablename, $version)
457 return self::_changeTracking($dbname, $tablename, $version, 1);
462 * Deactivates tracking of a table.
464 * @param string $dbname name of database
465 * @param string $tablename name of table
466 * @param string $version version
468 * @static
470 * @return int result of SQL query
472 static public function deactivateTracking($dbname, $tablename, $version)
474 return self::_changeTracking($dbname, $tablename, $version, 0);
479 * Gets the newest version of a tracking job
480 * (in other words: gets the HEAD version).
482 * @param string $dbname name of database
483 * @param string $tablename name of table
484 * @param string $statement tracked statement
486 * @static
488 * @return int (-1 if no version exists | > 0 if a version exists)
490 static public function getVersion($dbname, $tablename, $statement = null)
492 $sql_query = " SELECT MAX(version) FROM " . self::_getTrackingTable() .
493 " WHERE `db_name` = '" . PMA_Util::sqlAddSlashes($dbname) . "' " .
494 " AND `table_name` = '" . PMA_Util::sqlAddSlashes($tablename) . "' ";
496 if ($statement != "") {
497 if (PMA_DRIZZLE) {
498 $sql_query .= ' AND tracking & '
499 . self::_transformTrackingSet($statement) . ' <> 0';
500 } else {
501 $sql_query .= " AND FIND_IN_SET('"
502 . $statement . "',tracking) > 0" ;
505 $row = $GLOBALS['dbi']->fetchArray(PMA_queryAsControlUser($sql_query));
506 return isset($row[0])
507 ? $row[0]
508 : -1;
513 * Gets the record of a tracking job.
515 * @param string $dbname name of database
516 * @param string $tablename name of table
517 * @param string $version version number
519 * @static
521 * @return mixed record DDM log, DDL log, structure snapshot, tracked
522 * statements.
524 static public function getTrackedData($dbname, $tablename, $version)
526 $sql_query = " SELECT * FROM " . self::_getTrackingTable() .
527 " WHERE `db_name` = '" . PMA_Util::sqlAddSlashes($dbname) . "' ";
528 if (! empty($tablename)) {
529 $sql_query .= " AND `table_name` = '"
530 . PMA_Util::sqlAddSlashes($tablename) . "' ";
532 $sql_query .= " AND `version` = '" . PMA_Util::sqlAddSlashes($version)
533 . "' " . " ORDER BY `version` DESC LIMIT 1";
535 $mixed = $GLOBALS['dbi']->fetchAssoc(PMA_queryAsControlUser($sql_query));
537 // Parse log
538 $log_schema_entries = explode('# log ', $mixed['schema_sql']);
539 $log_data_entries = explode('# log ', $mixed['data_sql']);
541 $ddl_date_from = $date = date('Y-m-d H:i:s');
543 $ddlog = array();
544 $i = 0;
546 // Iterate tracked data definition statements
547 // For each log entry we want to get date, username and statement
548 foreach ($log_schema_entries as $log_entry) {
549 if (trim($log_entry) != '') {
550 $date = /*overload*/mb_substr($log_entry, 0, 19);
551 $username = /*overload*/mb_substr(
552 $log_entry, 20, /*overload*/mb_strpos($log_entry, "\n") - 20
554 if ($i == 0) {
555 $ddl_date_from = $date;
557 $statement = rtrim(/*overload*/mb_strstr($log_entry, "\n"));
559 $ddlog[] = array( 'date' => $date,
560 'username'=> $username,
561 'statement' => $statement );
562 $i++;
566 $date_from = $ddl_date_from;
567 $ddl_date_to = $date;
569 $dml_date_from = $date_from;
571 $dmlog = array();
572 $i = 0;
574 // Iterate tracked data manipulation statements
575 // For each log entry we want to get date, username and statement
576 foreach ($log_data_entries as $log_entry) {
577 if (trim($log_entry) != '') {
578 $date = /*overload*/mb_substr($log_entry, 0, 19);
579 $username = /*overload*/mb_substr(
580 $log_entry, 20, /*overload*/mb_strpos($log_entry, "\n") - 20
582 if ($i == 0) {
583 $dml_date_from = $date;
585 $statement = rtrim(/*overload*/mb_strstr($log_entry, "\n"));
587 $dmlog[] = array( 'date' => $date,
588 'username' => $username,
589 'statement' => $statement );
590 $i++;
594 $dml_date_to = $date;
596 // Define begin and end of date range for both logs
597 $data = array();
598 if (strtotime($ddl_date_from) <= strtotime($dml_date_from)) {
599 $data['date_from'] = $ddl_date_from;
600 } else {
601 $data['date_from'] = $dml_date_from;
603 if (strtotime($ddl_date_to) >= strtotime($dml_date_to)) {
604 $data['date_to'] = $ddl_date_to;
605 } else {
606 $data['date_to'] = $dml_date_to;
608 $data['ddlog'] = $ddlog;
609 $data['dmlog'] = $dmlog;
610 $data['tracking'] = self::_transformTrackingSet($mixed['tracking']);
611 $data['schema_snapshot'] = $mixed['schema_snapshot'];
613 return $data;
618 * Parses a query. Gets
619 * - statement identifier (UPDATE, ALTER TABLE, ...)
620 * - type of statement, is it part of DDL or DML ?
621 * - tablename
623 * @param string $query query
625 * @static
626 * @todo: using PMA SQL Parser when possible
627 * @todo: support multi-table/view drops
629 * @return mixed Array containing identifier, type and tablename.
632 static public function parseQuery($query)
634 // Usage of PMA_SQP does not work here
636 // require_once("libraries/sqlparser.lib.php");
637 // $parsed_sql = PMA_SQP_parse($query);
638 // $sql_info = PMA_SQP_analyze($parsed_sql);
640 $query = str_replace("\n", " ", $query);
641 $query = str_replace("\r", " ", $query);
643 $query = trim($query);
644 $query = trim($query, ' -');
646 $tokens = explode(" ", $query);
647 foreach ($tokens as $key => $value) {
648 $tokens[$key] = /*overload*/mb_strtoupper($value);
651 // Parse USE statement, need it for SQL dump imports
652 if (/*overload*/mb_substr($query, 0, 4) == 'USE ') {
653 $prefix = explode('USE ', $query);
654 $GLOBALS['db'] = self::getTableName($prefix[1]);
658 * DDL statements
661 $result = array();
662 $result['type'] = 'DDL';
664 // Parse CREATE VIEW statement
665 if (in_array('CREATE', $tokens) == true
666 && in_array('VIEW', $tokens) == true
667 && in_array('AS', $tokens) == true
669 $result['identifier'] = 'CREATE VIEW';
671 $index = array_search('VIEW', $tokens);
673 $result['tablename'] = /*overload*/mb_strtolower(
674 self::getTableName($tokens[$index + 1])
678 // Parse ALTER VIEW statement
679 if (in_array('ALTER', $tokens) == true
680 && in_array('VIEW', $tokens) == true
681 && in_array('AS', $tokens) == true
682 && ! isset($result['identifier'])
684 $result['identifier'] = 'ALTER VIEW';
686 $index = array_search('VIEW', $tokens);
688 $result['tablename'] = /*overload*/mb_strtolower(
689 self::getTableName($tokens[$index + 1])
693 // Parse DROP VIEW statement
694 if (! isset($result['identifier'])
695 && substr($query, 0, 10) == 'DROP VIEW '
697 $result['identifier'] = 'DROP VIEW';
699 $prefix = explode('DROP VIEW ', $query);
700 $str = str_replace('IF EXISTS', '', $prefix[1]);
701 $result['tablename'] = self::getTableName($str);
704 // Parse CREATE DATABASE statement
705 if (! isset($result['identifier'])
706 && substr($query, 0, 15) == 'CREATE DATABASE'
708 $result['identifier'] = 'CREATE DATABASE';
709 $str = str_replace('CREATE DATABASE', '', $query);
710 $str = str_replace('IF NOT EXISTS', '', $str);
712 $prefix = explode('DEFAULT ', $str);
714 $result['tablename'] = '';
715 $GLOBALS['db'] = self::getTableName($prefix[0]);
718 // Parse ALTER DATABASE statement
719 if (! isset($result['identifier'])
720 && substr($query, 0, 14) == 'ALTER DATABASE'
722 $result['identifier'] = 'ALTER DATABASE';
723 $result['tablename'] = '';
726 // Parse DROP DATABASE statement
727 if (! isset($result['identifier'])
728 && substr($query, 0, 13) == 'DROP DATABASE'
730 $result['identifier'] = 'DROP DATABASE';
731 $str = str_replace('DROP DATABASE', '', $query);
732 $str = str_replace('IF EXISTS', '', $str);
733 $GLOBALS['db'] = self::getTableName($str);
734 $result['tablename'] = '';
737 // Parse CREATE TABLE statement
738 if (! isset($result['identifier'])
739 && substr($query, 0, 12) == 'CREATE TABLE'
741 $result['identifier'] = 'CREATE TABLE';
742 $query = str_replace('IF NOT EXISTS', '', $query);
743 $prefix = explode('CREATE TABLE ', $query);
744 $suffix = explode('(', $prefix[1]);
745 $result['tablename'] = self::getTableName($suffix[0]);
748 // Parse ALTER TABLE statement
749 if (! isset($result['identifier'])
750 && substr($query, 0, 12) == 'ALTER TABLE '
752 $result['identifier'] = 'ALTER TABLE';
754 $prefix = explode('ALTER TABLE ', $query);
755 $suffix = explode(' ', $prefix[1]);
756 $result['tablename'] = self::getTableName($suffix[0]);
759 // Parse DROP TABLE statement
760 if (! isset($result['identifier'])
761 && substr($query, 0, 11) == 'DROP TABLE '
763 $result['identifier'] = 'DROP TABLE';
765 $prefix = explode('DROP TABLE ', $query);
766 $str = str_replace('IF EXISTS', '', $prefix[1]);
767 $result['tablename'] = self::getTableName($str);
770 // Parse CREATE INDEX statement
771 if (! isset($result['identifier'])
772 && (substr($query, 0, 12) == 'CREATE INDEX'
773 || substr($query, 0, 19) == 'CREATE UNIQUE INDEX'
774 || substr($query, 0, 20) == 'CREATE SPATIAL INDEX')
776 $result['identifier'] = 'CREATE INDEX';
777 $prefix = explode('ON ', $query);
778 $suffix = explode('(', $prefix[1]);
779 $result['tablename'] = self::getTableName($suffix[0]);
782 // Parse DROP INDEX statement
783 if (! isset($result['identifier'])
784 && substr($query, 0, 10) == 'DROP INDEX'
786 $result['identifier'] = 'DROP INDEX';
787 $prefix = explode('ON ', $query);
788 $result['tablename'] = self::getTableName($prefix[1]);
791 // Parse RENAME TABLE statement
792 if (! isset($result['identifier'])
793 && substr($query, 0, 13) == 'RENAME TABLE '
795 $result['identifier'] = 'RENAME TABLE';
796 $prefix = explode('RENAME TABLE ', $query);
797 $names = explode(' TO ', $prefix[1]);
798 $result['tablename'] = self::getTableName($names[0]);
799 $result["tablename_after_rename"] = self::getTableName($names[1]);
803 * DML statements
806 if (! isset($result['identifier'])) {
807 $result["type"] = 'DML';
809 // Parse UPDATE statement
810 if (! isset($result['identifier'])
811 && substr($query, 0, 6) == 'UPDATE'
813 $result['identifier'] = 'UPDATE';
814 $prefix = explode('UPDATE ', $query);
815 $suffix = explode(' ', $prefix[1]);
816 $result['tablename'] = self::getTableName($suffix[0]);
819 // Parse INSERT INTO statement
820 if (! isset($result['identifier'])
821 && substr($query, 0, 11) == 'INSERT INTO'
823 $result['identifier'] = 'INSERT';
824 $prefix = explode('INSERT INTO', $query);
825 $suffix = explode('(', $prefix[1]);
826 $result['tablename'] = self::getTableName($suffix[0]);
829 // Parse DELETE statement
830 if (! isset($result['identifier'])
831 && substr($query, 0, 6) == 'DELETE'
833 $result['identifier'] = 'DELETE';
834 $prefix = explode('FROM ', $query);
835 $suffix = explode(' ', $prefix[1]);
836 $result['tablename'] = self::getTableName($suffix[0]);
839 // Parse TRUNCATE statement
840 if (! isset($result['identifier'])
841 && substr($query, 0, 8) == 'TRUNCATE'
843 $result['identifier'] = 'TRUNCATE';
844 $prefix = explode('TRUNCATE', $query);
845 $result['tablename'] = self::getTableName($prefix[1]);
848 return $result;
853 * Analyzes a given SQL statement and saves tracking data.
855 * @param string $query a SQL query
857 * @static
859 * @return void
861 static public function handleQuery($query)
863 // If query is marked as untouchable, leave
864 if (/*overload*/mb_strstr($query, "/*NOTRACK*/")) {
865 return;
868 if (! (substr($query, -1) == ';')) {
869 $query = $query . ";\n";
871 // Get some information about query
872 $result = self::parseQuery($query);
874 // Get database name
875 $dbname = trim(isset($GLOBALS['db']) ? $GLOBALS['db'] : '', '`');
876 // $dbname can be empty, for example when coming from Synchronize
877 // and this is a query for the remote server
878 if (empty($dbname)) {
879 return;
882 // If we found a valid statement
883 if (isset($result['identifier'])) {
884 $version = self::getVersion(
885 $dbname, $result['tablename'], $result['identifier']
888 // If version not exists and auto-creation is enabled
889 if ($GLOBALS['cfg']['Server']['tracking_version_auto_create'] == true
890 && self::isTracked($dbname, $result['tablename']) == false
891 && $version == -1
893 // Create the version
895 switch ($result['identifier']) {
896 case 'CREATE TABLE':
897 self::createVersion($dbname, $result['tablename'], '1');
898 break;
899 case 'CREATE VIEW':
900 self::createVersion(
901 $dbname, $result['tablename'], '1', '', true
903 break;
904 case 'CREATE DATABASE':
905 self::createDatabaseVersion($dbname, '1', $query);
906 break;
907 } // end switch
910 // If version exists
911 if (self::isTracked($dbname, $result['tablename']) && $version != -1) {
912 if ($result['type'] == 'DDL') {
913 $save_to = 'schema_sql';
914 } elseif ($result['type'] == 'DML') {
915 $save_to = 'data_sql';
916 } else {
917 $save_to = '';
919 $date = date('Y-m-d H:i:s');
921 // Cut off `dbname`. from query
922 $query = preg_replace(
923 '/`' . preg_quote($dbname) . '`\s?\./',
925 $query
928 // Add log information
929 $query = self::getLogComment() . $query ;
931 // Mark it as untouchable
932 $sql_query = " /*NOTRACK*/\n"
933 . " UPDATE " . self::_getTrackingTable()
934 . " SET " . PMA_Util::backquote($save_to)
935 . " = CONCAT( " . PMA_Util::backquote($save_to) . ",'\n"
936 . PMA_Util::sqlAddSlashes($query) . "') ,"
937 . " `date_updated` = '" . $date . "' ";
939 // If table was renamed we have to change
940 // the tablename attribute in pma_tracking too
941 if ($result['identifier'] == 'RENAME TABLE') {
942 $sql_query .= ', `table_name` = \''
943 . PMA_Util::sqlAddSlashes($result['tablename_after_rename'])
944 . '\' ';
947 // Save the tracking information only for
948 // 1. the database
949 // 2. the table / view
950 // 3. the statements
951 // we want to track
952 $sql_query .=
953 " WHERE FIND_IN_SET('" . $result['identifier'] . "',tracking) > 0" .
954 " AND `db_name` = '" . PMA_Util::sqlAddSlashes($dbname) . "' " .
955 " AND `table_name` = '"
956 . PMA_Util::sqlAddSlashes($result['tablename']) . "' " .
957 " AND `version` = '" . PMA_Util::sqlAddSlashes($version) . "' ";
959 PMA_queryAsControlUser($sql_query);
965 * Transforms tracking set for Drizzle, which has no SET type
967 * Converts int<>string for Drizzle, does nothing for MySQL
969 * @param int|string $tracking_set Set to convert
971 * @return int|string
973 static private function _transformTrackingSet($tracking_set)
975 if (!PMA_DRIZZLE) {
976 return $tracking_set;
979 // init conversion array (key 3 doesn't exist in calculated array)
980 if (isset(self::$_tracking_set_flags[3])) {
981 // initialize flags
982 $set = self::$_tracking_set_flags;
983 $array = array();
984 for ($i = 0, $nb = count($set); $i < $nb; $i++) {
985 $flag = 1 << $i;
986 $array[$flag] = $set[$i];
987 $array[$set[$i]] = $flag;
989 self::$_tracking_set_flags = $array;
992 if (is_numeric($tracking_set)) {
993 // int > string conversion
994 $aflags = array();
995 // count/2 - conversion table has both int > string
996 // and string > int values
997 for ($i = 0, $nb = count(self::$_tracking_set_flags)/2; $i < $nb; $i++) {
998 $flag = 1 << $i;
999 if ($tracking_set & $flag) {
1000 $aflags[] = self::$_tracking_set_flags[$flag];
1003 $flags = implode(',', $aflags);
1004 } else {
1005 // string > int conversion
1006 $flags = 0;
1007 foreach (explode(',', $tracking_set) as $strflag) {
1008 if ($strflag == '') {
1009 continue;
1011 $flags |= self::$_tracking_set_flags[$strflag];
1015 return $flags;
1019 * Returns the tracking table
1021 * @return string tracking table
1023 private static function _getTrackingTable()
1025 $cfgRelation = PMA_getRelationsParam();
1026 return PMA_Util::backquote($cfgRelation['db'])
1027 . '.' . PMA_Util::backquote($cfgRelation['tracking']);