Translated using Weblate (Finnish)
[phpmyadmin.git] / libraries / Tracker.class.php
blob7efc3fa69c44d08c16ef6d8ff898de2d114ad5a8
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 = /*overload*/mb_strstr($prefix[1], 'IF EXISTS');
702 if ($str == false ) {
703 $str = $prefix[1];
705 $result['tablename'] = self::getTableName($str);
708 // Parse CREATE DATABASE statement
709 if (! isset($result['identifier'])
710 && substr($query, 0, 15) == 'CREATE DATABASE'
712 $result['identifier'] = 'CREATE DATABASE';
713 $str = str_replace('CREATE DATABASE', '', $query);
714 $str = str_replace('IF NOT EXISTS', '', $str);
716 $prefix = explode('DEFAULT ', $str);
718 $result['tablename'] = '';
719 $GLOBALS['db'] = self::getTableName($prefix[0]);
722 // Parse ALTER DATABASE statement
723 if (! isset($result['identifier'])
724 && substr($query, 0, 14) == 'ALTER DATABASE'
726 $result['identifier'] = 'ALTER DATABASE';
727 $result['tablename'] = '';
730 // Parse DROP DATABASE statement
731 if (! isset($result['identifier'])
732 && substr($query, 0, 13) == 'DROP DATABASE'
734 $result['identifier'] = 'DROP DATABASE';
735 $str = str_replace('DROP DATABASE', '', $query);
736 $str = str_replace('IF EXISTS', '', $str);
737 $GLOBALS['db'] = self::getTableName($str);
738 $result['tablename'] = '';
741 // Parse CREATE TABLE statement
742 if (! isset($result['identifier'])
743 && substr($query, 0, 12) == 'CREATE TABLE'
745 $result['identifier'] = 'CREATE TABLE';
746 $query = str_replace('IF NOT EXISTS', '', $query);
747 $prefix = explode('CREATE TABLE ', $query);
748 $suffix = explode('(', $prefix[1]);
749 $result['tablename'] = self::getTableName($suffix[0]);
752 // Parse ALTER TABLE statement
753 if (! isset($result['identifier'])
754 && substr($query, 0, 12) == 'ALTER TABLE '
756 $result['identifier'] = 'ALTER TABLE';
758 $prefix = explode('ALTER TABLE ', $query);
759 $suffix = explode(' ', $prefix[1]);
760 $result['tablename'] = self::getTableName($suffix[0]);
763 // Parse DROP TABLE statement
764 if (! isset($result['identifier'])
765 && substr($query, 0, 11) == 'DROP TABLE '
767 $result['identifier'] = 'DROP TABLE';
769 $prefix = explode('DROP TABLE ', $query);
770 $str = /*overload*/mb_strstr($prefix[1], 'IF EXISTS');
772 if ($str == false ) {
773 $str = $prefix[1];
775 $result['tablename'] = self::getTableName($str);
778 // Parse CREATE INDEX statement
779 if (! isset($result['identifier'])
780 && (substr($query, 0, 12) == 'CREATE INDEX'
781 || substr($query, 0, 19) == 'CREATE UNIQUE INDEX'
782 || substr($query, 0, 20) == 'CREATE SPATIAL INDEX')
784 $result['identifier'] = 'CREATE INDEX';
785 $prefix = explode('ON ', $query);
786 $suffix = explode('(', $prefix[1]);
787 $result['tablename'] = self::getTableName($suffix[0]);
790 // Parse DROP INDEX statement
791 if (! isset($result['identifier'])
792 && substr($query, 0, 10) == 'DROP INDEX'
794 $result['identifier'] = 'DROP INDEX';
795 $prefix = explode('ON ', $query);
796 $result['tablename'] = self::getTableName($prefix[1]);
799 // Parse RENAME TABLE statement
800 if (! isset($result['identifier'])
801 && substr($query, 0, 13) == 'RENAME TABLE '
803 $result['identifier'] = 'RENAME TABLE';
804 $prefix = explode('RENAME TABLE ', $query);
805 $names = explode(' TO ', $prefix[1]);
806 $result['tablename'] = self::getTableName($names[0]);
807 $result["tablename_after_rename"] = self::getTableName($names[1]);
811 * DML statements
814 if (! isset($result['identifier'])) {
815 $result["type"] = 'DML';
817 // Parse UPDATE statement
818 if (! isset($result['identifier'])
819 && substr($query, 0, 6) == 'UPDATE'
821 $result['identifier'] = 'UPDATE';
822 $prefix = explode('UPDATE ', $query);
823 $suffix = explode(' ', $prefix[1]);
824 $result['tablename'] = self::getTableName($suffix[0]);
827 // Parse INSERT INTO statement
828 if (! isset($result['identifier'])
829 && substr($query, 0, 11) == 'INSERT INTO'
831 $result['identifier'] = 'INSERT';
832 $prefix = explode('INSERT INTO', $query);
833 $suffix = explode('(', $prefix[1]);
834 $result['tablename'] = self::getTableName($suffix[0]);
837 // Parse DELETE statement
838 if (! isset($result['identifier'])
839 && substr($query, 0, 6) == 'DELETE'
841 $result['identifier'] = 'DELETE';
842 $prefix = explode('FROM ', $query);
843 $suffix = explode(' ', $prefix[1]);
844 $result['tablename'] = self::getTableName($suffix[0]);
847 // Parse TRUNCATE statement
848 if (! isset($result['identifier'])
849 && substr($query, 0, 8) == 'TRUNCATE'
851 $result['identifier'] = 'TRUNCATE';
852 $prefix = explode('TRUNCATE', $query);
853 $result['tablename'] = self::getTableName($prefix[1]);
856 return $result;
861 * Analyzes a given SQL statement and saves tracking data.
863 * @param string $query a SQL query
865 * @static
867 * @return void
869 static public function handleQuery($query)
871 // If query is marked as untouchable, leave
872 if (/*overload*/mb_strstr($query, "/*NOTRACK*/")) {
873 return;
876 if (! (substr($query, -1) == ';')) {
877 $query = $query . ";\n";
879 // Get some information about query
880 $result = self::parseQuery($query);
882 // Get database name
883 $dbname = trim(isset($GLOBALS['db']) ? $GLOBALS['db'] : '', '`');
884 // $dbname can be empty, for example when coming from Synchronize
885 // and this is a query for the remote server
886 if (empty($dbname)) {
887 return;
890 // If we found a valid statement
891 if (isset($result['identifier'])) {
892 $version = self::getVersion(
893 $dbname, $result['tablename'], $result['identifier']
896 // If version not exists and auto-creation is enabled
897 if ($GLOBALS['cfg']['Server']['tracking_version_auto_create'] == true
898 && self::isTracked($dbname, $result['tablename']) == false
899 && $version == -1
901 // Create the version
903 switch ($result['identifier']) {
904 case 'CREATE TABLE':
905 self::createVersion($dbname, $result['tablename'], '1');
906 break;
907 case 'CREATE VIEW':
908 self::createVersion(
909 $dbname, $result['tablename'], '1', '', true
911 break;
912 case 'CREATE DATABASE':
913 self::createDatabaseVersion($dbname, '1', $query);
914 break;
915 } // end switch
918 // If version exists
919 if (self::isTracked($dbname, $result['tablename']) && $version != -1) {
920 if ($result['type'] == 'DDL') {
921 $save_to = 'schema_sql';
922 } elseif ($result['type'] == 'DML') {
923 $save_to = 'data_sql';
924 } else {
925 $save_to = '';
927 $date = date('Y-m-d H:i:s');
929 // Cut off `dbname`. from query
930 $query = preg_replace(
931 '/`' . preg_quote($dbname) . '`\s?\./',
933 $query
936 // Add log information
937 $query = self::getLogComment() . $query ;
939 // Mark it as untouchable
940 $sql_query = " /*NOTRACK*/\n"
941 . " UPDATE " . self::_getTrackingTable()
942 . " SET " . PMA_Util::backquote($save_to)
943 . " = CONCAT( " . PMA_Util::backquote($save_to) . ",'\n"
944 . PMA_Util::sqlAddSlashes($query) . "') ,"
945 . " `date_updated` = '" . $date . "' ";
947 // If table was renamed we have to change
948 // the tablename attribute in pma_tracking too
949 if ($result['identifier'] == 'RENAME TABLE') {
950 $sql_query .= ', `table_name` = \''
951 . PMA_Util::sqlAddSlashes($result['tablename_after_rename'])
952 . '\' ';
955 // Save the tracking information only for
956 // 1. the database
957 // 2. the table / view
958 // 3. the statements
959 // we want to track
960 $sql_query .=
961 " WHERE FIND_IN_SET('" . $result['identifier'] . "',tracking) > 0" .
962 " AND `db_name` = '" . PMA_Util::sqlAddSlashes($dbname) . "' " .
963 " AND `table_name` = '"
964 . PMA_Util::sqlAddSlashes($result['tablename']) . "' " .
965 " AND `version` = '" . PMA_Util::sqlAddSlashes($version) . "' ";
967 PMA_queryAsControlUser($sql_query);
973 * Transforms tracking set for Drizzle, which has no SET type
975 * Converts int<>string for Drizzle, does nothing for MySQL
977 * @param int|string $tracking_set Set to convert
979 * @return int|string
981 static private function _transformTrackingSet($tracking_set)
983 if (!PMA_DRIZZLE) {
984 return $tracking_set;
987 // init conversion array (key 3 doesn't exist in calculated array)
988 if (isset(self::$_tracking_set_flags[3])) {
989 // initialize flags
990 $set = self::$_tracking_set_flags;
991 $array = array();
992 for ($i = 0, $nb = count($set); $i < $nb; $i++) {
993 $flag = 1 << $i;
994 $array[$flag] = $set[$i];
995 $array[$set[$i]] = $flag;
997 self::$_tracking_set_flags = $array;
1000 if (is_numeric($tracking_set)) {
1001 // int > string conversion
1002 $aflags = array();
1003 // count/2 - conversion table has both int > string
1004 // and string > int values
1005 for ($i = 0, $nb = count(self::$_tracking_set_flags)/2; $i < $nb; $i++) {
1006 $flag = 1 << $i;
1007 if ($tracking_set & $flag) {
1008 $aflags[] = self::$_tracking_set_flags[$flag];
1011 $flags = implode(',', $aflags);
1012 } else {
1013 // string > int conversion
1014 $flags = 0;
1015 foreach (explode(',', $tracking_set) as $strflag) {
1016 if ($strflag == '') {
1017 continue;
1019 $flags |= self::$_tracking_set_flags[$strflag];
1023 return $flags;
1027 * Returns the tracking table
1029 * @return string tracking table
1031 private static function _getTrackingTable()
1033 $cfgRelation = PMA_getRelationsParam();
1034 return PMA_Util::backquote($cfgRelation['db'])
1035 . '.' . PMA_Util::backquote($cfgRelation['tracking']);