Translated using Weblate (Estonian)
[phpmyadmin.git] / libraries / Tracker.class.php
blobd4dab00ee2af15dd88f47b19a692c616149c2400
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 * Defines the internal PMA table which contains tracking data.
29 * @access protected
30 * @var string
32 static protected $pma_table;
34 /**
35 * Defines the usage of DROP TABLE statment in SQL dumps.
37 * @access protected
38 * @var boolean
40 static protected $add_drop_table;
42 /**
43 * Defines the usage of DROP VIEW statment in SQL dumps.
45 * @access protected
46 * @var boolean
48 static protected $add_drop_view;
50 /**
51 * Defines the usage of DROP DATABASE statment in SQL dumps.
53 * @access protected
54 * @var boolean
56 static protected $add_drop_database;
58 /**
59 * Defines auto-creation of tracking versions.
61 * @var boolean
63 static protected $version_auto_create;
65 /**
66 * Defines the default set of tracked statements.
68 * @var string
70 static protected $default_tracking_set;
72 /**
73 * Flags copied from `tracking` column definition in `pma_tracking` table.
74 * Used for column type conversion in Drizzle.
76 * @var array
78 static private $_tracking_set_flags = array(
79 'UPDATE','REPLACE','INSERT','DELETE','TRUNCATE','CREATE DATABASE',
80 'ALTER DATABASE','DROP DATABASE','CREATE TABLE','ALTER TABLE',
81 'RENAME TABLE','DROP TABLE','CREATE INDEX','DROP INDEX',
82 'CREATE VIEW','ALTER VIEW','DROP VIEW'
86 /**
87 * Initializes settings.
89 * @static
91 * @return void
93 static protected function init()
95 self::$pma_table = PMA_Util::backquote($GLOBALS['cfg']['Server']['pmadb'])
96 . '.' . PMA_Util::backquote($GLOBALS['cfg']['Server']['tracking']);
98 self::$add_drop_table
99 = $GLOBALS['cfg']['Server']['tracking_add_drop_table'];
101 self::$add_drop_view
102 = $GLOBALS['cfg']['Server']['tracking_add_drop_view'];
104 self::$add_drop_database
105 = $GLOBALS['cfg']['Server']['tracking_add_drop_database'];
107 self::$default_tracking_set
108 = $GLOBALS['cfg']['Server']['tracking_default_statements'];
110 self::$version_auto_create
111 = $GLOBALS['cfg']['Server']['tracking_version_auto_create'];
115 * Actually enables tracking. This needs to be done after all
116 * underlaying code is initialized.
118 * @static
120 * @return void
122 static public function enable()
124 self::$enabled = true;
128 * Gets the on/off value of the Tracker module, starts initialization.
130 * @static
132 * @return boolean (true=on|false=off)
134 static public function isActive()
136 if (! self::$enabled) {
137 return false;
139 /* We need to avoid attempt to track any queries
140 * from PMA_getRelationsParam
142 self::$enabled = false;
143 $cfgRelation = PMA_getRelationsParam();
144 /* Restore original state */
145 self::$enabled = true;
146 if (! $cfgRelation['trackingwork']) {
147 return false;
149 self::init();
151 if (isset(self::$pma_table)) {
152 return true;
153 } else {
154 return false;
159 * Parses the name of a table from a SQL statement substring.
161 * @param string $string part of SQL statement
163 * @static
165 * @return string the name of table
167 static protected function getTableName($string)
169 if (strstr($string, '.')) {
170 $temp = explode('.', $string);
171 $tablename = $temp[1];
172 } else {
173 $tablename = $string;
176 $str = explode("\n", $tablename);
177 $tablename = $str[0];
179 $tablename = str_replace(';', '', $tablename);
180 $tablename = str_replace('`', '', $tablename);
181 $tablename = trim($tablename);
183 return $tablename;
188 * Gets the tracking status of a table, is it active or deactive ?
190 * @param string $dbname name of database
191 * @param string $tablename name of table
193 * @static
195 * @return boolean true or false
197 static public function isTracked($dbname, $tablename)
199 if (! self::$enabled) {
200 return false;
202 /* We need to avoid attempt to track any queries
203 * from PMA_getRelationsParam
205 self::$enabled = false;
206 $cfgRelation = PMA_getRelationsParam();
207 /* Restore original state */
208 self::$enabled = true;
209 if (! $cfgRelation['trackingwork']) {
210 return false;
213 $sql_query = " SELECT tracking_active FROM " . self::$pma_table .
214 " WHERE db_name = '" . PMA_Util::sqlAddSlashes($dbname) . "' " .
215 " AND table_name = '" . PMA_Util::sqlAddSlashes($tablename) . "' " .
216 " ORDER BY version DESC";
218 $row = $GLOBALS['dbi']->fetchArray(PMA_queryAsControlUser($sql_query));
220 if (isset($row['tracking_active']) && $row['tracking_active'] == 1) {
221 return true;
222 } else {
223 return false;
228 * Returns the comment line for the log.
230 * @return string Comment, contains date and username
232 static public function getLogComment()
234 $date = date('Y-m-d H:i:s');
236 return "# log " . $date . " " . $GLOBALS['cfg']['Server']['user'] . "\n";
240 * Creates tracking version of a table / view
241 * (in other words: create a job to track future changes on the table).
243 * @param string $dbname name of database
244 * @param string $tablename name of table
245 * @param string $version version
246 * @param string $tracking_set set of tracking statements
247 * @param bool $is_view if table is a view
249 * @static
251 * @return int result of version insertion
253 static public function createVersion($dbname, $tablename, $version,
254 $tracking_set = '', $is_view = false
256 global $sql_backquotes, $export_type;
258 if ($tracking_set == '') {
259 $tracking_set = self::$default_tracking_set;
262 // get Export SQL instance
263 include_once "libraries/plugin_interface.lib.php";
264 $export_sql_plugin = PMA_getPlugin(
265 "export",
266 "sql",
267 'libraries/plugins/export/',
268 array(
269 'export_type' => $export_type,
270 'single_table' => false,
274 $sql_backquotes = true;
276 $date = date('Y-m-d H:i:s');
278 // Get data definition snapshot of table
280 $columns = $GLOBALS['dbi']->getColumns($dbname, $tablename, null, true);
281 // int indices to reduce size
282 $columns = array_values($columns);
283 // remove Privileges to reduce size
284 for ($i = 0; $i < count($columns); $i++) {
285 unset($columns[$i]['Privileges']);
288 $indexes = $GLOBALS['dbi']->getTableIndexes($dbname, $tablename);
290 $snapshot = array('COLUMNS' => $columns, 'INDEXES' => $indexes);
291 $snapshot = serialize($snapshot);
293 // Get DROP TABLE / DROP VIEW and CREATE TABLE SQL statements
294 $sql_backquotes = true;
296 $create_sql = "";
298 if (self::$add_drop_table == true && $is_view == false) {
299 $create_sql .= self::getLogComment()
300 . 'DROP TABLE IF EXISTS ' . PMA_Util::backquote($tablename) . ";\n";
304 if (self::$add_drop_view == true && $is_view == true) {
305 $create_sql .= self::getLogComment()
306 . 'DROP VIEW IF EXISTS ' . PMA_Util::backquote($tablename) . ";\n";
309 $create_sql .= self::getLogComment() .
310 $export_sql_plugin->getTableDef($dbname, $tablename, "\n", "");
312 // Save version
314 $sql_query = "/*NOTRACK*/\n" .
315 "INSERT INTO" . self::$pma_table . " (" .
316 "db_name, " .
317 "table_name, " .
318 "version, " .
319 "date_created, " .
320 "date_updated, " .
321 "schema_snapshot, " .
322 "schema_sql, " .
323 "data_sql, " .
324 "tracking " .
325 ") " .
326 "values (
327 '" . PMA_Util::sqlAddSlashes($dbname) . "',
328 '" . PMA_Util::sqlAddSlashes($tablename) . "',
329 '" . PMA_Util::sqlAddSlashes($version) . "',
330 '" . PMA_Util::sqlAddSlashes($date) . "',
331 '" . PMA_Util::sqlAddSlashes($date) . "',
332 '" . PMA_Util::sqlAddSlashes($snapshot) . "',
333 '" . PMA_Util::sqlAddSlashes($create_sql) . "',
334 '" . PMA_Util::sqlAddSlashes("\n") . "',
335 '" . PMA_Util::sqlAddSlashes(self::_transformTrackingSet($tracking_set))
336 . "' )";
338 $result = PMA_queryAsControlUser($sql_query);
340 if ($result) {
341 // Deactivate previous version
342 self::deactivateTracking($dbname, $tablename, ($version - 1));
345 return $result;
350 * Removes all tracking data for a table
352 * @param string $dbname name of database
353 * @param string $tablename name of table
355 * @static
357 * @return int result of version insertion
359 static public function deleteTracking($dbname, $tablename)
361 $sql_query = "/*NOTRACK*/\n"
362 . "DELETE FROM " . self::$pma_table
363 . " WHERE `db_name` = '"
364 . PMA_Util::sqlAddSlashes($dbname) . "'"
365 . " AND `table_name` = '"
366 . PMA_Util::sqlAddSlashes($tablename) . "'";
367 $result = PMA_queryAsControlUser($sql_query);
369 return $result;
373 * Creates tracking version of a database
374 * (in other words: create a job to track future changes on the database).
376 * @param string $dbname name of database
377 * @param string $version version
378 * @param string $query query
379 * @param string $tracking_set set of tracking statements
381 * @static
383 * @return int result of version insertion
385 static public function createDatabaseVersion($dbname, $version, $query,
386 $tracking_set = 'CREATE DATABASE,ALTER DATABASE,DROP DATABASE'
388 $date = date('Y-m-d H:i:s');
390 if ($tracking_set == '') {
391 $tracking_set = self::$default_tracking_set;
394 $create_sql = "";
396 if (self::$add_drop_database == true) {
397 $create_sql .= self::getLogComment()
398 . 'DROP DATABASE IF EXISTS ' . PMA_Util::backquote($dbname) . ";\n";
401 $create_sql .= self::getLogComment() . $query;
403 // Save version
404 $sql_query = "/*NOTRACK*/\n" .
405 "INSERT INTO" . self::$pma_table . " (" .
406 "db_name, " .
407 "table_name, " .
408 "version, " .
409 "date_created, " .
410 "date_updated, " .
411 "schema_snapshot, " .
412 "schema_sql, " .
413 "data_sql, " .
414 "tracking " .
415 ") " .
416 "values (
417 '" . PMA_Util::sqlAddSlashes($dbname) . "',
418 '" . PMA_Util::sqlAddSlashes('') . "',
419 '" . PMA_Util::sqlAddSlashes($version) . "',
420 '" . PMA_Util::sqlAddSlashes($date) . "',
421 '" . PMA_Util::sqlAddSlashes($date) . "',
422 '" . PMA_Util::sqlAddSlashes('') . "',
423 '" . PMA_Util::sqlAddSlashes($create_sql) . "',
424 '" . PMA_Util::sqlAddSlashes("\n") . "',
425 '" . PMA_Util::sqlAddSlashes(self::_transformTrackingSet($tracking_set))
426 . "' )";
428 $result = PMA_queryAsControlUser($sql_query);
430 return $result;
436 * Changes tracking of a table.
438 * @param string $dbname name of database
439 * @param string $tablename name of table
440 * @param string $version version
441 * @param integer $new_state the new state of tracking
443 * @static
445 * @return int result of SQL query
447 static private function _changeTracking($dbname, $tablename,
448 $version, $new_state
451 $sql_query = " UPDATE " . self::$pma_table .
452 " SET `tracking_active` = '" . $new_state . "' " .
453 " WHERE `db_name` = '" . PMA_Util::sqlAddSlashes($dbname) . "' " .
454 " AND `table_name` = '" . PMA_Util::sqlAddSlashes($tablename) . "' " .
455 " AND `version` = '" . PMA_Util::sqlAddSlashes($version) . "' ";
457 $result = PMA_queryAsControlUser($sql_query);
459 return $result;
463 * Changes tracking data of a table.
465 * @param string $dbname name of database
466 * @param string $tablename name of table
467 * @param string $version version
468 * @param string $type type of data(DDL || DML)
469 * @param string|array $new_data the new tracking data
471 * @static
473 * @return bool result of change
475 static public function changeTrackingData($dbname, $tablename,
476 $version, $type, $new_data
478 if ($type == 'DDL') {
479 $save_to = 'schema_sql';
480 } elseif ($type == 'DML') {
481 $save_to = 'data_sql';
482 } else {
483 return false;
485 $date = date('Y-m-d H:i:s');
487 $new_data_processed = '';
488 if (is_array($new_data)) {
489 foreach ($new_data as $data) {
490 $new_data_processed .= '# log ' . $date . ' ' . $data['username']
491 . PMA_Util::sqlAddSlashes($data['statement']) . "\n";
493 } else {
494 $new_data_processed = $new_data;
497 $sql_query = " UPDATE " . self::$pma_table .
498 " SET `" . $save_to . "` = '" . $new_data_processed . "' " .
499 " WHERE `db_name` = '" . PMA_Util::sqlAddSlashes($dbname) . "' " .
500 " AND `table_name` = '" . PMA_Util::sqlAddSlashes($tablename) . "' " .
501 " AND `version` = '" . PMA_Util::sqlAddSlashes($version) . "' ";
503 $result = PMA_queryAsControlUser($sql_query);
505 return $result;
509 * Activates tracking of a table.
511 * @param string $dbname name of database
512 * @param string $tablename name of table
513 * @param string $version version
515 * @static
517 * @return int result of SQL query
519 static public function activateTracking($dbname, $tablename, $version)
521 return self::_changeTracking($dbname, $tablename, $version, 1);
526 * Deactivates tracking of a table.
528 * @param string $dbname name of database
529 * @param string $tablename name of table
530 * @param string $version version
532 * @static
534 * @return int result of SQL query
536 static public function deactivateTracking($dbname, $tablename, $version)
538 return self::_changeTracking($dbname, $tablename, $version, 0);
543 * Gets the newest version of a tracking job
544 * (in other words: gets the HEAD version).
546 * @param string $dbname name of database
547 * @param string $tablename name of table
548 * @param string $statement tracked statement
550 * @static
552 * @return int (-1 if no version exists | > 0 if a version exists)
554 static public function getVersion($dbname, $tablename, $statement = null)
556 $sql_query = " SELECT MAX(version) FROM " . self::$pma_table .
557 " WHERE `db_name` = '" . PMA_Util::sqlAddSlashes($dbname) . "' " .
558 " AND `table_name` = '" . PMA_Util::sqlAddSlashes($tablename) . "' ";
560 if ($statement != "") {
561 if (PMA_DRIZZLE) {
562 $sql_query .= ' AND tracking & '
563 . self::_transformTrackingSet($statement) . ' <> 0';
564 } else {
565 $sql_query .= " AND FIND_IN_SET('"
566 . $statement . "',tracking) > 0" ;
569 $row = $GLOBALS['dbi']->fetchArray(PMA_queryAsControlUser($sql_query));
570 return isset($row[0])
571 ? $row[0]
572 : -1;
577 * Gets the record of a tracking job.
579 * @param string $dbname name of database
580 * @param string $tablename name of table
581 * @param string $version version number
583 * @static
585 * @return mixed record DDM log, DDL log, structure snapshot, tracked
586 * statements.
588 static public function getTrackedData($dbname, $tablename, $version)
590 if (! isset(self::$pma_table)) {
591 self::init();
593 $sql_query = " SELECT * FROM " . self::$pma_table .
594 " WHERE `db_name` = '" . PMA_Util::sqlAddSlashes($dbname) . "' ";
595 if (! empty($tablename)) {
596 $sql_query .= " AND `table_name` = '"
597 . PMA_Util::sqlAddSlashes($tablename) ."' ";
599 $sql_query .= " AND `version` = '" . PMA_Util::sqlAddSlashes($version)
600 . "' " . " ORDER BY `version` DESC LIMIT 1";
602 $mixed = $GLOBALS['dbi']->fetchAssoc(PMA_queryAsControlUser($sql_query));
604 // Parse log
605 $log_schema_entries = explode('# log ', $mixed['schema_sql']);
606 $log_data_entries = explode('# log ', $mixed['data_sql']);
608 $ddl_date_from = $date = date('Y-m-d H:i:s');
610 $ddlog = array();
611 $i = 0;
613 // Iterate tracked data definition statements
614 // For each log entry we want to get date, username and statement
615 foreach ($log_schema_entries as $log_entry) {
616 if (trim($log_entry) != '') {
617 $date = substr($log_entry, 0, 19);
618 $username = substr($log_entry, 20, strpos($log_entry, "\n") - 20);
619 if ($i == 0) {
620 $ddl_date_from = $date;
622 $statement = rtrim(strstr($log_entry, "\n"));
624 $ddlog[] = array( 'date' => $date,
625 'username'=> $username,
626 'statement' => $statement );
627 $i++;
631 $date_from = $ddl_date_from;
632 $ddl_date_to = $date;
634 $dml_date_from = $date_from;
636 $dmlog = array();
637 $i = 0;
639 // Iterate tracked data manipulation statements
640 // For each log entry we want to get date, username and statement
641 foreach ($log_data_entries as $log_entry) {
642 if (trim($log_entry) != '') {
643 $date = substr($log_entry, 0, 19);
644 $username = substr($log_entry, 20, strpos($log_entry, "\n") - 20);
645 if ($i == 0) {
646 $dml_date_from = $date;
648 $statement = rtrim(strstr($log_entry, "\n"));
650 $dmlog[] = array( 'date' => $date,
651 'username' => $username,
652 'statement' => $statement );
653 $i++;
657 $dml_date_to = $date;
659 // Define begin and end of date range for both logs
660 if (strtotime($ddl_date_from) <= strtotime($dml_date_from)) {
661 $data['date_from'] = $ddl_date_from;
662 } else {
663 $data['date_from'] = $dml_date_from;
665 if (strtotime($ddl_date_to) >= strtotime($dml_date_to)) {
666 $data['date_to'] = $ddl_date_to;
667 } else {
668 $data['date_to'] = $dml_date_to;
670 $data['ddlog'] = $ddlog;
671 $data['dmlog'] = $dmlog;
672 $data['tracking'] = self::_transformTrackingSet($mixed['tracking']);
673 $data['schema_snapshot'] = $mixed['schema_snapshot'];
675 return $data;
680 * Parses a query. Gets
681 * - statement identifier (UPDATE, ALTER TABLE, ...)
682 * - type of statement, is it part of DDL or DML ?
683 * - tablename
685 * @param string $query query
687 * @static
688 * @todo: using PMA SQL Parser when possible
689 * @todo: support multi-table/view drops
691 * @return mixed Array containing identifier, type and tablename.
694 static public function parseQuery($query)
697 // Usage of PMA_SQP does not work here
699 // require_once("libraries/sqlparser.lib.php");
700 // $parsed_sql = PMA_SQP_parse($query);
701 // $sql_info = PMA_SQP_analyze($parsed_sql);
703 $query = str_replace("\n", " ", $query);
704 $query = str_replace("\r", " ", $query);
706 $query = trim($query);
707 $query = trim($query, ' -');
709 $tokens = explode(" ", $query);
710 foreach ($tokens as $key => $value) {
711 $tokens[$key] = strtoupper($value);
714 // Parse USE statement, need it for SQL dump imports
715 if (substr($query, 0, 4) == 'USE ') {
716 $prefix = explode('USE ', $query);
717 $GLOBALS['db'] = self::getTableName($prefix[1]);
721 * DDL statements
724 $result['type'] = 'DDL';
726 // Parse CREATE VIEW statement
727 if (in_array('CREATE', $tokens) == true
728 && in_array('VIEW', $tokens) == true
729 && in_array('AS', $tokens) == true
731 $result['identifier'] = 'CREATE VIEW';
733 $index = array_search('VIEW', $tokens);
735 $result['tablename'] = strtolower(
736 self::getTableName($tokens[$index + 1])
740 // Parse ALTER VIEW statement
741 if (in_array('ALTER', $tokens) == true
742 && in_array('VIEW', $tokens) == true
743 && in_array('AS', $tokens) == true
744 && ! isset($result['identifier'])
746 $result['identifier'] = 'ALTER VIEW';
748 $index = array_search('VIEW', $tokens);
750 $result['tablename'] = strtolower(
751 self::getTableName($tokens[$index + 1])
755 // Parse DROP VIEW statement
756 if (! isset($result['identifier'])
757 && substr($query, 0, 10) == 'DROP VIEW '
759 $result['identifier'] = 'DROP VIEW';
761 $prefix = explode('DROP VIEW ', $query);
762 $str = strstr($prefix[1], 'IF EXISTS');
764 if ($str == false ) {
765 $str = $prefix[1];
767 $result['tablename'] = self::getTableName($str);
770 // Parse CREATE DATABASE statement
771 if (! isset($result['identifier'])
772 && substr($query, 0, 15) == 'CREATE DATABASE'
774 $result['identifier'] = 'CREATE DATABASE';
775 $str = str_replace('CREATE DATABASE', '', $query);
776 $str = str_replace('IF NOT EXISTS', '', $str);
778 $prefix = explode('DEFAULT ', $str);
780 $result['tablename'] = '';
781 $GLOBALS['db'] = self::getTableName($prefix[0]);
784 // Parse ALTER DATABASE statement
785 if (! isset($result['identifier'])
786 && substr($query, 0, 14) == 'ALTER DATABASE'
788 $result['identifier'] = 'ALTER DATABASE';
789 $result['tablename'] = '';
792 // Parse DROP DATABASE statement
793 if (! isset($result['identifier'])
794 && substr($query, 0, 13) == 'DROP DATABASE'
796 $result['identifier'] = 'DROP DATABASE';
797 $str = str_replace('DROP DATABASE', '', $query);
798 $str = str_replace('IF EXISTS', '', $str);
799 $GLOBALS['db'] = self::getTableName($str);
800 $result['tablename'] = '';
803 // Parse CREATE TABLE statement
804 if (! isset($result['identifier'])
805 && substr($query, 0, 12) == 'CREATE TABLE'
807 $result['identifier'] = 'CREATE TABLE';
808 $query = str_replace('IF NOT EXISTS', '', $query);
809 $prefix = explode('CREATE TABLE ', $query);
810 $suffix = explode('(', $prefix[1]);
811 $result['tablename'] = self::getTableName($suffix[0]);
814 // Parse ALTER TABLE statement
815 if (! isset($result['identifier'])
816 && substr($query, 0, 12) == 'ALTER TABLE '
818 $result['identifier'] = 'ALTER TABLE';
820 $prefix = explode('ALTER TABLE ', $query);
821 $suffix = explode(' ', $prefix[1]);
822 $result['tablename'] = self::getTableName($suffix[0]);
825 // Parse DROP TABLE statement
826 if (! isset($result['identifier'])
827 && substr($query, 0, 11) == 'DROP TABLE '
829 $result['identifier'] = 'DROP TABLE';
831 $prefix = explode('DROP TABLE ', $query);
832 $str = strstr($prefix[1], 'IF EXISTS');
834 if ($str == false ) {
835 $str = $prefix[1];
837 $result['tablename'] = self::getTableName($str);
840 // Parse CREATE INDEX statement
841 if (! isset($result['identifier'])
842 && (substr($query, 0, 12) == 'CREATE INDEX'
843 || substr($query, 0, 19) == 'CREATE UNIQUE INDEX'
844 || substr($query, 0, 20) == 'CREATE SPATIAL INDEX')
846 $result['identifier'] = 'CREATE INDEX';
847 $prefix = explode('ON ', $query);
848 $suffix = explode('(', $prefix[1]);
849 $result['tablename'] = self::getTableName($suffix[0]);
852 // Parse DROP INDEX statement
853 if (! isset($result['identifier'])
854 && substr($query, 0, 10) == 'DROP INDEX'
856 $result['identifier'] = 'DROP INDEX';
857 $prefix = explode('ON ', $query);
858 $result['tablename'] = self::getTableName($prefix[1]);
861 // Parse RENAME TABLE statement
862 if (! isset($result['identifier'])
863 && substr($query, 0, 13) == 'RENAME TABLE '
865 $result['identifier'] = 'RENAME TABLE';
866 $prefix = explode('RENAME TABLE ', $query);
867 $names = explode(' TO ', $prefix[1]);
868 $result['tablename'] = self::getTableName($names[0]);
869 $result["tablename_after_rename"] = self::getTableName($names[1]);
873 * DML statements
876 if (! isset($result['identifier'])) {
877 $result["type"] = 'DML';
879 // Parse UPDATE statement
880 if (! isset($result['identifier'])
881 && substr($query, 0, 6) == 'UPDATE'
883 $result['identifier'] = 'UPDATE';
884 $prefix = explode('UPDATE ', $query);
885 $suffix = explode(' ', $prefix[1]);
886 $result['tablename'] = self::getTableName($suffix[0]);
889 // Parse INSERT INTO statement
890 if (! isset($result['identifier'])
891 && substr($query, 0, 11) == 'INSERT INTO'
893 $result['identifier'] = 'INSERT';
894 $prefix = explode('INSERT INTO', $query);
895 $suffix = explode('(', $prefix[1]);
896 $result['tablename'] = self::getTableName($suffix[0]);
899 // Parse DELETE statement
900 if (! isset($result['identifier'])
901 && substr($query, 0, 6) == 'DELETE'
903 $result['identifier'] = 'DELETE';
904 $prefix = explode('FROM ', $query);
905 $suffix = explode(' ', $prefix[1]);
906 $result['tablename'] = self::getTableName($suffix[0]);
909 // Parse TRUNCATE statement
910 if (! isset($result['identifier'])
911 && substr($query, 0, 8) == 'TRUNCATE'
913 $result['identifier'] = 'TRUNCATE';
914 $prefix = explode('TRUNCATE', $query);
915 $result['tablename'] = self::getTableName($prefix[1]);
918 return $result;
923 * Analyzes a given SQL statement and saves tracking data.
925 * @param string $query a SQL query
927 * @static
929 * @return void
931 static public function handleQuery($query)
933 // If query is marked as untouchable, leave
934 if (strstr($query, "/*NOTRACK*/")) {
935 return;
938 if (! (substr($query, -1) == ';')) {
939 $query = $query . ";\n";
941 // Get some information about query
942 $result = self::parseQuery($query);
944 // Get database name
945 $dbname = trim(isset($GLOBALS['db']) ? $GLOBALS['db'] : '', '`');
946 // $dbname can be empty, for example when coming from Synchronize
947 // and this is a query for the remote server
948 if (empty($dbname)) {
949 return;
951 // Remove null bytes (preg_replace() is vulnerable in some
952 // PHP versions)
953 $dbname = str_replace("\0", "", $dbname);
955 // If we found a valid statement
956 if (isset($result['identifier'])) {
957 $version = self::getVersion(
958 $dbname, $result['tablename'], $result['identifier']
961 // If version not exists and auto-creation is enabled
962 if (self::$version_auto_create == true
963 && self::isTracked($dbname, $result['tablename']) == false
964 && $version == -1
966 // Create the version
968 switch ($result['identifier']) {
969 case 'CREATE TABLE':
970 self::createVersion($dbname, $result['tablename'], '1');
971 break;
972 case 'CREATE VIEW':
973 self::createVersion(
974 $dbname, $result['tablename'], '1', '', true
976 break;
977 case 'CREATE DATABASE':
978 self::createDatabaseVersion($dbname, '1', $query);
979 break;
980 } // end switch
983 // If version exists
984 if (self::isTracked($dbname, $result['tablename']) && $version != -1) {
985 if ($result['type'] == 'DDL') {
986 $save_to = 'schema_sql';
987 } elseif ($result['type'] == 'DML') {
988 $save_to = 'data_sql';
989 } else {
990 $save_to = '';
992 $date = date('Y-m-d H:i:s');
994 // Cut off `dbname`. from query
995 $query = preg_replace('/`' . $dbname . '`\s?\./', '', $query);
997 // Add log information
998 $query = self::getLogComment() . $query ;
1000 // Mark it as untouchable
1001 $sql_query = " /*NOTRACK*/\n"
1002 . " UPDATE " . self::$pma_table
1003 . " SET " . PMA_Util::backquote($save_to)
1004 . " = CONCAT( " . PMA_Util::backquote($save_to) . ",'\n"
1005 . PMA_Util::sqlAddSlashes($query) . "') ,"
1006 . " `date_updated` = '" . $date . "' ";
1008 // If table was renamed we have to change
1009 // the tablename attribute in pma_tracking too
1010 if ($result['identifier'] == 'RENAME TABLE') {
1011 $sql_query .= ', `table_name` = \''
1012 . PMA_Util::sqlAddSlashes($result['tablename_after_rename'])
1013 . '\' ';
1016 // Save the tracking information only for
1017 // 1. the database
1018 // 2. the table / view
1019 // 3. the statements
1020 // we want to track
1021 $sql_query .=
1022 " WHERE FIND_IN_SET('" . $result['identifier'] . "',tracking) > 0" .
1023 " AND `db_name` = '" . PMA_Util::sqlAddSlashes($dbname) . "' " .
1024 " AND `table_name` = '"
1025 . PMA_Util::sqlAddSlashes($result['tablename']) . "' " .
1026 " AND `version` = '" . PMA_Util::sqlAddSlashes($version) . "' ";
1028 $result = PMA_queryAsControlUser($sql_query);
1034 * Transforms tracking set for Drizzle, which has no SET type
1036 * Converts int<>string for Drizzle, does nothing for MySQL
1038 * @param int|string $tracking_set Set to convert
1040 * @return int|string
1042 static private function _transformTrackingSet($tracking_set)
1044 if (!PMA_DRIZZLE) {
1045 return $tracking_set;
1048 // init conversion array (key 3 doesn't exist in calculated array)
1049 if (isset(self::$_tracking_set_flags[3])) {
1050 // initialize flags
1051 $set = self::$_tracking_set_flags;
1052 $array = array();
1053 for ($i = 0; $i < count($set); $i++) {
1054 $flag = 1 << $i;
1055 $array[$flag] = $set[$i];
1056 $array[$set[$i]] = $flag;
1058 self::$_tracking_set_flags = $array;
1061 if (is_numeric($tracking_set)) {
1062 // int > string conversion
1063 $aflags = array();
1064 // count/2 - conversion table has both int > string
1065 // and string > int values
1066 for ($i = 0; $i < count(self::$_tracking_set_flags)/2; $i++) {
1067 $flag = 1 << $i;
1068 if ($tracking_set & $flag) {
1069 $aflags[] = self::$_tracking_set_flags[$flag];
1072 $flags = implode(',', $aflags);
1073 } else {
1074 // string > int conversion
1075 $flags = 0;
1076 foreach (explode(',', $tracking_set) as $strflag) {
1077 if ($strflag == '') {
1078 continue;
1080 $flags |= self::$_tracking_set_flags[$strflag];
1084 return $flags;