Translated using Weblate (Finnish)
[phpmyadmin.git] / libraries / import.lib.php
blobcf6b5a763629c40192ec1ee4f910bdcc68e25f6d
1 <?php
2 /* vim: set expandtab sw=4 ts=4 sts=4: */
3 /**
4 * Library that provides common import functions that are used by import plugins
6 * @package PhpMyAdmin-Import
7 */
8 if (! defined('PHPMYADMIN')) {
9 exit;
12 /**
13 * We need to know something about user
15 require_once './libraries/check_user_privileges.lib.php';
17 /**
18 * We do this check, DROP DATABASE does not need to be confirmed elsewhere
20 define('PMA_CHK_DROP', 1);
22 /**
23 * Checks whether timeout is getting close
25 * @return boolean true if timeout is close
26 * @access public
28 function PMA_checkTimeout()
30 global $timestamp, $maximum_time, $timeout_passed;
31 if ($maximum_time == 0) {
32 return false;
33 } elseif ($timeout_passed) {
34 return true;
35 /* 5 in next row might be too much */
36 } elseif ((time() - $timestamp) > ($maximum_time - 5)) {
37 $timeout_passed = true;
38 return true;
39 } else {
40 return false;
44 /**
45 * Detects what compression the file uses
47 * @param string $filepath filename to check
49 * @return string MIME type of compression, none for none
50 * @access public
52 function PMA_detectCompression($filepath)
54 $file = @fopen($filepath, 'rb');
55 if (! $file) {
56 return false;
58 return PMA_Util::getCompressionMimeType($file);
61 /**
62 * Runs query inside import buffer. This is needed to allow displaying
63 * of last SELECT, SHOW or HANDLER results and similar nice stuff.
65 * @param string $sql query to run
66 * @param string $full query to display, this might be commented
67 * @param bool $controluser whether to use control user for queries
68 * @param array &$sql_data SQL parse data storage
70 * @return void
71 * @access public
73 function PMA_importRunQuery($sql = '', $full = '', $controluser = false,
74 &$sql_data = array()
75 ) {
76 global $import_run_buffer, $go_sql, $complete_query, $display_query,
77 $sql_query, $my_die, $error, $reload,
78 $last_query_with_results, $result, $msg,
79 $skip_queries, $executed_queries, $max_sql_len, $read_multiply,
80 $cfg, $sql_query_disabled, $db, $run_query, $is_superuser;
81 $read_multiply = 1;
82 if (!isset($import_run_buffer)) {
83 // Do we have something to push into buffer?
84 $import_run_buffer = PMA_ImportRunQuery_post(
85 $import_run_buffer, $sql, $full
87 return;
90 // Should we skip something?
91 if ($skip_queries > 0) {
92 $skip_queries--;
93 // Do we have something to push into buffer?
94 $import_run_buffer = PMA_ImportRunQuery_post(
95 $import_run_buffer, $sql, $full
97 return;
100 if (! empty($import_run_buffer['sql'])
101 && trim($import_run_buffer['sql']) != ''
104 // USE query changes the database, son need to track
105 // while running multiple queries
106 $is_use_query
107 = (/*overload*/mb_stripos($import_run_buffer['sql'], "use ") !== false)
108 ? true
109 : false;
111 $max_sql_len = max(
112 $max_sql_len,
113 /*overload*/mb_strlen($import_run_buffer['sql'])
115 if (! $sql_query_disabled) {
116 $sql_query .= $import_run_buffer['full'];
118 $pattern = '@^[[:space:]]*DROP[[:space:]]+(IF EXISTS[[:space:]]+)?'
119 . 'DATABASE @i';
120 if (! $cfg['AllowUserDropDatabase']
121 && ! $is_superuser
122 && preg_match($pattern, $import_run_buffer['sql'])
124 $GLOBALS['message'] = PMA_Message::error(
125 __('"DROP DATABASE" statements are disabled.')
127 $error = true;
128 } else {
129 $executed_queries++;
131 $pattern = '/^[\s]*(SELECT|SHOW|HANDLER)/i';
132 if ($run_query
133 && $GLOBALS['finished']
134 && empty($sql)
135 && ! $error
136 && ((! empty($import_run_buffer['sql'])
137 && preg_match($pattern, $import_run_buffer['sql']))
138 || ($executed_queries == 1))
140 $go_sql = true;
141 if (! $sql_query_disabled) {
142 $complete_query = $sql_query;
143 $display_query = $sql_query;
144 } else {
145 $complete_query = '';
146 $display_query = '';
148 $sql_query = $import_run_buffer['sql'];
149 $sql_data['valid_sql'][] = $import_run_buffer['sql'];
150 if (! isset($sql_data['valid_queries'])) {
151 $sql_data['valid_queries'] = 0;
153 $sql_data['valid_queries']++;
155 // If a 'USE <db>' SQL-clause was found,
156 // set our current $db to the new one
157 list($db, $reload) = PMA_lookForUse(
158 $import_run_buffer['sql'],
159 $db,
160 $reload
162 } elseif ($run_query) {
164 if ($controluser) {
165 $result = PMA_queryAsControlUser(
166 $import_run_buffer['sql']
168 } else {
169 $result = $GLOBALS['dbi']
170 ->tryQuery($import_run_buffer['sql']);
173 $msg = '# ';
174 if ($result === false) { // execution failed
175 if (! isset($my_die)) {
176 $my_die = array();
178 $my_die[] = array(
179 'sql' => $import_run_buffer['full'],
180 'error' => $GLOBALS['dbi']->getError()
183 $msg .= __('Error');
185 if (! $cfg['IgnoreMultiSubmitErrors']) {
186 $error = true;
187 return;
189 } else {
190 $a_num_rows = (int)@$GLOBALS['dbi']->numRows($result);
191 $a_aff_rows = (int)@$GLOBALS['dbi']->affectedRows();
192 if ($a_num_rows > 0) {
193 $msg .= __('Rows') . ': ' . $a_num_rows;
194 $last_query_with_results = $import_run_buffer['sql'];
195 } elseif ($a_aff_rows > 0) {
196 $message = PMA_Message::getMessageForAffectedRows(
197 $a_aff_rows
199 $msg .= $message->getMessage();
200 } else {
201 $msg .= __(
202 'MySQL returned an empty result set (i.e. zero '
203 . 'rows).'
207 $sql_data = updateSqlData(
208 $sql_data, $a_num_rows, $is_use_query, $import_run_buffer
211 if (! $sql_query_disabled) {
212 $sql_query .= $msg . "\n";
215 // If a 'USE <db>' SQL-clause was found and the query
216 // succeeded, set our current $db to the new one
217 if ($result != false) {
218 list($db, $reload) = PMA_lookForUse(
219 $import_run_buffer['sql'],
220 $db,
221 $reload
225 $pattern = '@^[\s]*(DROP|CREATE)[\s]+(IF EXISTS[[:space:]]+)'
226 . '?(TABLE|DATABASE)[[:space:]]+(.+)@im';
227 if ($result != false
228 && preg_match($pattern, $import_run_buffer['sql'])
230 $reload = true;
232 } // end run query
233 } // end if not DROP DATABASE
234 // end non empty query
235 } elseif (! empty($import_run_buffer['full'])) {
236 if ($go_sql) {
237 $complete_query .= $import_run_buffer['full'];
238 $display_query .= $import_run_buffer['full'];
239 } else {
240 if (! $sql_query_disabled) {
241 $sql_query .= $import_run_buffer['full'];
245 // check length of query unless we decided to pass it to sql.php
246 // (if $run_query is false, we are just displaying so show
247 // the complete query in the textarea)
248 if (! $go_sql && $run_query) {
249 if (! empty($sql_query)) {
250 if (/*overload*/mb_strlen($sql_query) > 50000
251 || $executed_queries > 50
252 || $max_sql_len > 1000
254 $sql_query = '';
255 $sql_query_disabled = true;
260 // Do we have something to push into buffer?
261 $import_run_buffer = PMA_ImportRunQuery_post($import_run_buffer, $sql, $full);
263 // In case of ROLLBACK, notify the user.
264 if (isset($_REQUEST['rollback_query'])) {
265 $msg .= __('[ROLLBACK occurred.]');
270 * Update $sql_data
272 * @param array $sql_data SQL data
273 * @param int $a_num_rows Number of rows
274 * @param bool $is_use_query Query is used
275 * @param array $import_run_buffer Import buffer
277 * @return array
279 function updateSqlData($sql_data, $a_num_rows, $is_use_query, $import_run_buffer)
281 if (($a_num_rows > 0) || $is_use_query) {
282 $sql_data['valid_sql'][] = $import_run_buffer['sql'];
283 if (!isset($sql_data['valid_queries'])) {
284 $sql_data['valid_queries'] = 0;
286 $sql_data['valid_queries']++;
288 return $sql_data;
292 * Return import run buffer
294 * @param array $import_run_buffer Buffer of queries for import
295 * @param string $sql SQL query
296 * @param string $full Query to display
298 * @return array Buffer of queries for import
300 function PMA_ImportRunQuery_post($import_run_buffer, $sql, $full)
302 if (!empty($sql) || !empty($full)) {
303 $import_run_buffer = array('sql' => $sql, 'full' => $full);
304 return $import_run_buffer;
305 } else {
306 unset($GLOBALS['import_run_buffer']);
307 return $import_run_buffer;
312 * Looks for the presence of USE to possibly change current db
314 * @param string $buffer buffer to examine
315 * @param string $db current db
316 * @param bool $reload reload
318 * @return array (current or new db, whether to reload)
319 * @access public
321 function PMA_lookForUse($buffer, $db, $reload)
323 if (preg_match('@^[\s]*USE[[:space:]]+([\S]+)@i', $buffer, $match)) {
324 $db = trim($match[1]);
325 $db = trim($db, ';'); // for example, USE abc;
327 // $db must not contain the escape characters generated by backquote()
328 // ( used in PMA_buildSQL() as: backquote($db_name), and then called
329 // in PMA_importRunQuery() which in turn calls PMA_lookForUse() )
330 $db = PMA_Util::unQuote($db);
332 $reload = true;
334 return(array($db, $reload));
339 * Returns next part of imported file/buffer
341 * @param int $size size of buffer to read
342 * (this is maximal size function will return)
344 * @return string part of file/buffer
345 * @access public
347 function PMA_importGetNextChunk($size = 32768)
349 global $compression, $import_handle, $charset_conversion, $charset_of_file,
350 $read_multiply;
352 // Add some progression while reading large amount of data
353 if ($read_multiply <= 8) {
354 $size *= $read_multiply;
355 } else {
356 $size *= 8;
358 $read_multiply++;
360 // We can not read too much
361 if ($size > $GLOBALS['read_limit']) {
362 $size = $GLOBALS['read_limit'];
365 if (PMA_checkTimeout()) {
366 return false;
368 if ($GLOBALS['finished']) {
369 return true;
372 if ($GLOBALS['import_file'] == 'none') {
373 // Well this is not yet supported and tested,
374 // but should return content of textarea
375 if (/*overload*/mb_strlen($GLOBALS['import_text']) < $size) {
376 $GLOBALS['finished'] = true;
377 return $GLOBALS['import_text'];
378 } else {
379 $r = /*overload*/mb_substr($GLOBALS['import_text'], 0, $size);
380 $GLOBALS['offset'] += $size;
381 $GLOBALS['import_text'] = /*overload*/
382 mb_substr($GLOBALS['import_text'], $size);
383 return $r;
387 switch ($compression) {
388 case 'application/bzip2':
389 $result = bzread($import_handle, $size);
390 $GLOBALS['finished'] = feof($import_handle);
391 break;
392 case 'application/gzip':
393 $result = gzread($import_handle, $size);
394 $GLOBALS['finished'] = feof($import_handle);
395 break;
396 case 'application/zip':
397 $result = /*overload*/mb_substr($GLOBALS['import_text'], 0, $size);
398 $GLOBALS['import_text'] = /*overload*/mb_substr(
399 $GLOBALS['import_text'],
400 $size
402 $GLOBALS['finished'] = empty($GLOBALS['import_text']);
403 break;
404 case 'none':
405 $result = fread($import_handle, $size);
406 $GLOBALS['finished'] = feof($import_handle);
407 break;
409 $GLOBALS['offset'] += $size;
411 if ($charset_conversion) {
412 return PMA_convertString($charset_of_file, 'utf-8', $result);
416 * Skip possible byte order marks (I do not think we need more
417 * charsets, but feel free to add more, you can use wikipedia for
418 * reference: <http://en.wikipedia.org/wiki/Byte_Order_Mark>)
420 * @todo BOM could be used for charset autodetection
422 if ($GLOBALS['offset'] == $size) {
423 // UTF-8
424 if (strncmp($result, "\xEF\xBB\xBF", 3) == 0) {
425 $result = /*overload*/mb_substr($result, 3);
426 // UTF-16 BE, LE
427 } elseif (strncmp($result, "\xFE\xFF", 2) == 0
428 || strncmp($result, "\xFF\xFE", 2) == 0
430 $result = /*overload*/mb_substr($result, 2);
433 return $result;
437 * Returns the "Excel" column name (i.e. 1 = "A", 26 = "Z", 27 = "AA", etc.)
439 * This functions uses recursion to build the Excel column name.
441 * The column number (1-26) is converted to the responding
442 * ASCII character (A-Z) and returned.
444 * If the column number is bigger than 26 (= num of letters in alphabet),
445 * an extra character needs to be added. To find this extra character,
446 * the number is divided by 26 and this value is passed to another instance
447 * of the same function (hence recursion). In that new instance the number is
448 * evaluated again, and if it is still bigger than 26, it is divided again
449 * and passed to another instance of the same function. This continues until
450 * the number is smaller than 26. Then the last called function returns
451 * the corresponding ASCII character to the function that called it.
452 * Each time a called function ends an extra character is added to the column name.
453 * When the first function is reached, the last character is added and the complete
454 * column name is returned.
456 * @param int $num the column number
458 * @return string The column's "Excel" name
459 * @access public
461 function PMA_getColumnAlphaName($num)
463 $A = 65; // ASCII value for capital "A"
464 $col_name = "";
466 if ($num > 26) {
467 $div = (int)($num / 26);
468 $remain = (int)($num % 26);
470 // subtract 1 of divided value in case the modulus is 0,
471 // this is necessary because A-Z has no 'zero'
472 if ($remain == 0) {
473 $div--;
476 // recursive function call
477 $col_name = PMA_getColumnAlphaName($div);
478 // use modulus as new column number
479 $num = $remain;
482 if ($num == 0) {
483 // use 'Z' if column number is 0,
484 // this is necessary because A-Z has no 'zero'
485 $col_name .= /*overload*/mb_chr(($A + 26) - 1);
486 } else {
487 // convert column number to ASCII character
488 $col_name .= /*overload*/mb_chr(($A + $num) - 1);
491 return $col_name;
495 * Returns the column number based on the Excel name.
496 * So "A" = 1, "Z" = 26, "AA" = 27, etc.
498 * Basically this is a base26 (A-Z) to base10 (0-9) conversion.
499 * It iterates through all characters in the column name and
500 * calculates the corresponding value, based on character value
501 * (A = 1, ..., Z = 26) and position in the string.
503 * @param string $name column name(i.e. "A", or "BC", etc.)
505 * @return int The column number
506 * @access public
508 function PMA_getColumnNumberFromName($name)
510 if (empty($name)) {
511 return 0;
514 $name = /*overload*/mb_strtoupper($name);
515 $num_chars = /*overload*/mb_strlen($name);
516 $column_number = 0;
517 for ($i = 0; $i < $num_chars; ++$i) {
518 // read string from back to front
519 $char_pos = ($num_chars - 1) - $i;
521 // convert capital character to ASCII value
522 // and subtract 64 to get corresponding decimal value
523 // ASCII value of "A" is 65, "B" is 66, etc.
524 // Decimal equivalent of "A" is 1, "B" is 2, etc.
525 $number = (int)(/*overload*/mb_ord($name[$char_pos]) - 64);
527 // base26 to base10 conversion : multiply each number
528 // with corresponding value of the position, in this case
529 // $i=0 : 1; $i=1 : 26; $i=2 : 676; ...
530 $column_number += $number * PMA_Util::pow(26, $i);
532 return $column_number;
536 * Constants definitions
539 /* MySQL type defs */
540 define("NONE", 0);
541 define("VARCHAR", 1);
542 define("INT", 2);
543 define("DECIMAL", 3);
544 define("BIGINT", 4);
545 define("GEOMETRY", 5);
547 /* Decimal size defs */
548 define("M", 0);
549 define("D", 1);
550 define("FULL", 2);
552 /* Table array defs */
553 define("TBL_NAME", 0);
554 define("COL_NAMES", 1);
555 define("ROWS", 2);
557 /* Analysis array defs */
558 define("TYPES", 0);
559 define("SIZES", 1);
560 define("FORMATTEDSQL", 2);
563 * Obtains the precision (total # of digits) from a size of type decimal
565 * @param string $last_cumulative_size Size of type decimal
567 * @return int Precision of the given decimal size notation
568 * @access public
570 function PMA_getDecimalPrecision($last_cumulative_size)
572 return (int)substr(
573 $last_cumulative_size,
575 strpos($last_cumulative_size, ",")
580 * Obtains the scale (# of digits to the right of the decimal point)
581 * from a size of type decimal
583 * @param string $last_cumulative_size Size of type decimal
585 * @return int Scale of the given decimal size notation
586 * @access public
588 function PMA_getDecimalScale($last_cumulative_size)
590 return (int)substr(
591 $last_cumulative_size,
592 (strpos($last_cumulative_size, ",") + 1),
593 (strlen($last_cumulative_size) - strpos($last_cumulative_size, ","))
598 * Obtains the decimal size of a given cell
600 * @param string $cell cell content
602 * @return array Contains the precision, scale, and full size
603 * representation of the given decimal cell
604 * @access public
606 function PMA_getDecimalSize($cell)
608 $curr_size = /*overload*/mb_strlen((string)$cell);
609 $decPos = /*overload*/mb_strpos($cell, ".");
610 $decPrecision = ($curr_size - 1) - $decPos;
612 $m = $curr_size - 1;
613 $d = $decPrecision;
615 return array($m, $d, ($m . "," . $d));
619 * Obtains the size of the given cell
621 * @param string $last_cumulative_size Last cumulative column size
622 * @param int $last_cumulative_type Last cumulative column type
623 * (NONE or VARCHAR or DECIMAL or INT or BIGINT)
624 * @param int $curr_type Type of the current cell
625 * (NONE or VARCHAR or DECIMAL or INT or BIGINT)
626 * @param string $cell The current cell
628 * @return string Size of the given cell in the type-appropriate format
629 * @access public
631 * @todo Handle the error cases more elegantly
633 function PMA_detectSize($last_cumulative_size, $last_cumulative_type,
634 $curr_type, $cell
636 $curr_size = /*overload*/mb_strlen((string)$cell);
639 * If the cell is NULL, don't treat it as a varchar
641 if (! strcmp('NULL', $cell)) {
642 return $last_cumulative_size;
643 } elseif ($curr_type == VARCHAR) {
645 * What to do if the current cell is of type VARCHAR
648 * The last cumulative type was VARCHAR
650 if ($last_cumulative_type == VARCHAR) {
651 if ($curr_size >= $last_cumulative_size) {
652 return $curr_size;
653 } else {
654 return $last_cumulative_size;
656 } elseif ($last_cumulative_type == DECIMAL) {
658 * The last cumulative type was DECIMAL
660 $oldM = PMA_getDecimalPrecision($last_cumulative_size);
662 if ($curr_size >= $oldM) {
663 return $curr_size;
664 } else {
665 return $oldM;
667 } elseif ($last_cumulative_type == BIGINT || $last_cumulative_type == INT) {
669 * The last cumulative type was BIGINT or INT
671 if ($curr_size >= $last_cumulative_size) {
672 return $curr_size;
673 } else {
674 return $last_cumulative_size;
676 } elseif (! isset($last_cumulative_type) || $last_cumulative_type == NONE) {
678 * This is the first row to be analyzed
680 return $curr_size;
681 } else {
683 * An error has DEFINITELY occurred
686 * TODO: Handle this MUCH more elegantly
689 return -1;
691 } elseif ($curr_type == DECIMAL) {
693 * What to do if the current cell is of type DECIMAL
696 * The last cumulative type was VARCHAR
698 if ($last_cumulative_type == VARCHAR) {
699 /* Convert $last_cumulative_size from varchar to decimal format */
700 $size = PMA_getDecimalSize($cell);
702 if ($size[M] >= $last_cumulative_size) {
703 return $size[M];
704 } else {
705 return $last_cumulative_size;
707 } elseif ($last_cumulative_type == DECIMAL) {
709 * The last cumulative type was DECIMAL
711 $size = PMA_getDecimalSize($cell);
713 $oldM = PMA_getDecimalPrecision($last_cumulative_size);
714 $oldD = PMA_getDecimalScale($last_cumulative_size);
716 /* New val if M or D is greater than current largest */
717 if ($size[M] > $oldM || $size[D] > $oldD) {
718 /* Take the largest of both types */
719 return (string) ((($size[M] > $oldM) ? $size[M] : $oldM)
720 . "," . (($size[D] > $oldD) ? $size[D] : $oldD));
721 } else {
722 return $last_cumulative_size;
724 } elseif ($last_cumulative_type == BIGINT || $last_cumulative_type == INT) {
726 * The last cumulative type was BIGINT or INT
728 /* Convert $last_cumulative_size from int to decimal format */
729 $size = PMA_getDecimalSize($cell);
731 if ($size[M] >= $last_cumulative_size) {
732 return $size[FULL];
733 } else {
734 return ($last_cumulative_size . "," . $size[D]);
736 } elseif (! isset($last_cumulative_type) || $last_cumulative_type == NONE) {
738 * This is the first row to be analyzed
740 /* First row of the column */
741 $size = PMA_getDecimalSize($cell);
743 return $size[FULL];
744 } else {
746 * An error has DEFINITELY occurred
749 * TODO: Handle this MUCH more elegantly
752 return -1;
754 } elseif ($curr_type == BIGINT || $curr_type == INT) {
756 * What to do if the current cell is of type BIGINT or INT
759 * The last cumulative type was VARCHAR
761 if ($last_cumulative_type == VARCHAR) {
762 if ($curr_size >= $last_cumulative_size) {
763 return $curr_size;
764 } else {
765 return $last_cumulative_size;
767 } elseif ($last_cumulative_type == DECIMAL) {
769 * The last cumulative type was DECIMAL
771 $oldM = PMA_getDecimalPrecision($last_cumulative_size);
772 $oldD = PMA_getDecimalScale($last_cumulative_size);
773 $oldInt = $oldM - $oldD;
774 $newInt = /*overload*/mb_strlen((string)$cell);
776 /* See which has the larger integer length */
777 if ($oldInt >= $newInt) {
778 /* Use old decimal size */
779 return $last_cumulative_size;
780 } else {
781 /* Use $newInt + $oldD as new M */
782 return (($newInt + $oldD) . "," . $oldD);
784 } elseif ($last_cumulative_type == BIGINT || $last_cumulative_type == INT) {
786 * The last cumulative type was BIGINT or INT
788 if ($curr_size >= $last_cumulative_size) {
789 return $curr_size;
790 } else {
791 return $last_cumulative_size;
793 } elseif (! isset($last_cumulative_type) || $last_cumulative_type == NONE) {
795 * This is the first row to be analyzed
797 return $curr_size;
798 } else {
800 * An error has DEFINITELY occurred
803 * TODO: Handle this MUCH more elegantly
806 return -1;
808 } else {
810 * An error has DEFINITELY occurred
813 * TODO: Handle this MUCH more elegantly
816 return -1;
821 * Determines what MySQL type a cell is
823 * @param int $last_cumulative_type Last cumulative column type
824 * (VARCHAR or INT or BIGINT or DECIMAL or NONE)
825 * @param string $cell String representation of the cell for which
826 * a best-fit type is to be determined
828 * @return int The MySQL type representation
829 * (VARCHAR or INT or BIGINT or DECIMAL or NONE)
830 * @access public
832 function PMA_detectType($last_cumulative_type, $cell)
835 * If numeric, determine if decimal, int or bigint
836 * Else, we call it varchar for simplicity
839 if (! strcmp('NULL', $cell)) {
840 if ($last_cumulative_type === null || $last_cumulative_type == NONE) {
841 return NONE;
844 return $last_cumulative_type;
847 if (!is_numeric($cell)) {
848 return VARCHAR;
851 if ($cell == (string)(float)$cell
852 && /*overload*/mb_strpos($cell, ".") !== false
853 && /*overload*/mb_substr_count($cell, ".") == 1
855 return DECIMAL;
858 if (abs($cell) > 2147483647) {
859 return BIGINT;
862 return INT;
866 * Determines if the column types are int, decimal, or string
868 * @param array &$table array(string $table_name, array $col_names, array $rows)
870 * @return array array(array $types, array $sizes)
871 * @access public
873 * @link http://wiki.phpmyadmin.net/pma/Import
875 * @todo Handle the error case more elegantly
877 function PMA_analyzeTable(&$table)
879 /* Get number of rows in table */
880 $numRows = count($table[ROWS]);
881 /* Get number of columns */
882 $numCols = count($table[COL_NAMES]);
883 /* Current type for each column */
884 $types = array();
885 $sizes = array();
887 /* Initialize $sizes to all 0's */
888 for ($i = 0; $i < $numCols; ++$i) {
889 $sizes[$i] = 0;
892 /* Initialize $types to NONE */
893 for ($i = 0; $i < $numCols; ++$i) {
894 $types[$i] = NONE;
897 /* If the passed array is not of the correct form, do not process it */
898 if (!is_array($table)
899 || is_array($table[TBL_NAME])
900 || !is_array($table[COL_NAMES])
901 || !is_array($table[ROWS])
904 * TODO: Handle this better
907 return false;
910 /* Analyze each column */
911 for ($i = 0; $i < $numCols; ++$i) {
912 /* Analyze the column in each row */
913 for ($j = 0; $j < $numRows; ++$j) {
914 /* Determine type of the current cell */
915 $curr_type = PMA_detectType($types[$i], $table[ROWS][$j][$i]);
916 /* Determine size of the current cell */
917 $sizes[$i] = PMA_detectSize(
918 $sizes[$i],
919 $types[$i],
920 $curr_type,
921 $table[ROWS][$j][$i]
925 * If a type for this column has already been declared,
926 * only alter it if it was a number and a varchar was found
928 if ($curr_type != NONE) {
929 if ($curr_type == VARCHAR) {
930 $types[$i] = VARCHAR;
931 } else if ($curr_type == DECIMAL) {
932 if ($types[$i] != VARCHAR) {
933 $types[$i] = DECIMAL;
935 } else if ($curr_type == BIGINT) {
936 if ($types[$i] != VARCHAR && $types[$i] != DECIMAL) {
937 $types[$i] = BIGINT;
939 } else if ($curr_type == INT) {
940 if ($types[$i] != VARCHAR
941 && $types[$i] != DECIMAL
942 && $types[$i] != BIGINT
944 $types[$i] = INT;
951 /* Check to ensure that all types are valid */
952 $len = count($types);
953 for ($n = 0; $n < $len; ++$n) {
954 if (! strcmp(NONE, $types[$n])) {
955 $types[$n] = VARCHAR;
956 $sizes[$n] = '10';
960 return array($types, $sizes);
963 /* Needed to quell the beast that is PMA_Message */
964 $import_notice = null;
967 * Builds and executes SQL statements to create the database and tables
968 * as necessary, as well as insert all the data.
970 * @param string $db_name Name of the database
971 * @param array &$tables Array of tables for the specified database
972 * @param array &$analyses Analyses of the tables
973 * @param array &$additional_sql Additional SQL statements to be executed
974 * @param array $options Associative array of options
976 * @return void
977 * @access public
979 * @link http://wiki.phpmyadmin.net/pma/Import
981 function PMA_buildSQL($db_name, &$tables, &$analyses = null,
982 &$additional_sql = null, $options = null
984 /* Take care of the options */
985 if (isset($options['db_collation'])&& ! is_null($options['db_collation'])) {
986 $collation = $options['db_collation'];
987 } else {
988 $collation = "utf8_general_ci";
991 if (isset($options['db_charset']) && ! is_null($options['db_charset'])) {
992 $charset = $options['db_charset'];
993 } else {
994 $charset = "utf8";
997 if (isset($options['create_db'])) {
998 $create_db = $options['create_db'];
999 } else {
1000 $create_db = true;
1003 /* Create SQL code to handle the database */
1004 $sql = array();
1006 if ($create_db) {
1007 if (PMA_DRIZZLE) {
1008 $sql[] = "CREATE DATABASE IF NOT EXISTS " . PMA_Util::backquote($db_name)
1009 . " COLLATE " . $collation;
1010 } else {
1011 $sql[] = "CREATE DATABASE IF NOT EXISTS " . PMA_Util::backquote($db_name)
1012 . " DEFAULT CHARACTER SET " . $charset . " COLLATE " . $collation;
1017 * The calling plug-in should include this statement,
1018 * if necessary, in the $additional_sql parameter
1020 * $sql[] = "USE " . backquote($db_name);
1023 /* Execute the SQL statements create above */
1024 $sql_len = count($sql);
1025 for ($i = 0; $i < $sql_len; ++$i) {
1026 PMA_importRunQuery($sql[$i], $sql[$i]);
1029 /* No longer needed */
1030 unset($sql);
1032 /* Run the $additional_sql statements supplied by the caller plug-in */
1033 if ($additional_sql != null) {
1034 /* Clean the SQL first */
1035 $additional_sql_len = count($additional_sql);
1038 * Only match tables for now, because CREATE IF NOT EXISTS
1039 * syntax is lacking or nonexisting for views, triggers,
1040 * functions, and procedures.
1042 * See: http://bugs.mysql.com/bug.php?id=15287
1044 * To the best of my knowledge this is still an issue.
1046 * $pattern = 'CREATE (TABLE|VIEW|TRIGGER|FUNCTION|PROCEDURE)';
1048 $pattern = '/CREATE [^`]*(TABLE)/';
1049 $replacement = 'CREATE \\1 IF NOT EXISTS';
1051 /* Change CREATE statements to CREATE IF NOT EXISTS to support
1052 * inserting into existing structures
1054 for ($i = 0; $i < $additional_sql_len; ++$i) {
1055 $additional_sql[$i] = preg_replace(
1056 $pattern,
1057 $replacement,
1058 $additional_sql[$i]
1060 /* Execute the resulting statements */
1061 PMA_importRunQuery($additional_sql[$i], $additional_sql[$i]);
1065 if ($analyses != null) {
1066 $type_array = array(
1067 NONE => "NULL",
1068 VARCHAR => "varchar",
1069 INT => "int",
1070 DECIMAL => "decimal",
1071 BIGINT => "bigint",
1072 GEOMETRY => 'geometry'
1075 /* TODO: Do more checking here to make sure they really are matched */
1076 if (count($tables) != count($analyses)) {
1077 exit();
1080 /* Create SQL code to create the tables */
1081 $num_tables = count($tables);
1082 for ($i = 0; $i < $num_tables; ++$i) {
1083 $num_cols = count($tables[$i][COL_NAMES]);
1084 $tempSQLStr = "CREATE TABLE IF NOT EXISTS "
1085 . PMA_Util::backquote($db_name)
1086 . '.' . PMA_Util::backquote($tables[$i][TBL_NAME]) . " (";
1087 for ($j = 0; $j < $num_cols; ++$j) {
1088 $size = $analyses[$i][SIZES][$j];
1089 if ((int)$size == 0) {
1090 $size = 10;
1093 $tempSQLStr .= PMA_Util::backquote($tables[$i][COL_NAMES][$j]) . " "
1094 . $type_array[$analyses[$i][TYPES][$j]];
1095 if ($analyses[$i][TYPES][$j] != GEOMETRY) {
1096 $tempSQLStr .= "(" . $size . ")";
1099 if ($j != (count($tables[$i][COL_NAMES]) - 1)) {
1100 $tempSQLStr .= ", ";
1103 $tempSQLStr .= ")"
1104 . (PMA_DRIZZLE ? "" : " DEFAULT CHARACTER SET " . $charset)
1105 . " COLLATE " . $collation . ";";
1108 * Each SQL statement is executed immediately
1109 * after it is formed so that we don't have
1110 * to store them in a (possibly large) buffer
1112 PMA_importRunQuery($tempSQLStr, $tempSQLStr);
1117 * Create the SQL statements to insert all the data
1119 * Only one insert query is formed for each table
1121 $tempSQLStr = "";
1122 $col_count = 0;
1123 $num_tables = count($tables);
1124 for ($i = 0; $i < $num_tables; ++$i) {
1125 $num_cols = count($tables[$i][COL_NAMES]);
1126 $num_rows = count($tables[$i][ROWS]);
1128 $tempSQLStr = "INSERT INTO " . PMA_Util::backquote($db_name) . '.'
1129 . PMA_Util::backquote($tables[$i][TBL_NAME]) . " (";
1131 for ($m = 0; $m < $num_cols; ++$m) {
1132 $tempSQLStr .= PMA_Util::backquote($tables[$i][COL_NAMES][$m]);
1134 if ($m != ($num_cols - 1)) {
1135 $tempSQLStr .= ", ";
1139 $tempSQLStr .= ") VALUES ";
1141 for ($j = 0; $j < $num_rows; ++$j) {
1142 $tempSQLStr .= "(";
1144 for ($k = 0; $k < $num_cols; ++$k) {
1145 // If fully formatted SQL, no need to enclose
1146 // with apostrophes, add slashes etc.
1147 if ($analyses != null
1148 && isset($analyses[$i][FORMATTEDSQL][$col_count])
1149 && $analyses[$i][FORMATTEDSQL][$col_count] == true
1151 $tempSQLStr .= (string) $tables[$i][ROWS][$j][$k];
1152 } else {
1153 if ($analyses != null) {
1154 $is_varchar = ($analyses[$i][TYPES][$col_count] === VARCHAR);
1155 } else {
1156 $is_varchar = ! is_numeric($tables[$i][ROWS][$j][$k]);
1159 /* Don't put quotes around NULL fields */
1160 if (! strcmp($tables[$i][ROWS][$j][$k], 'NULL')) {
1161 $is_varchar = false;
1164 $tempSQLStr .= (($is_varchar) ? "'" : "");
1165 $tempSQLStr .= PMA_Util::sqlAddSlashes(
1166 (string) $tables[$i][ROWS][$j][$k]
1168 $tempSQLStr .= (($is_varchar) ? "'" : "");
1171 if ($k != ($num_cols - 1)) {
1172 $tempSQLStr .= ", ";
1175 if ($col_count == ($num_cols - 1)) {
1176 $col_count = 0;
1177 } else {
1178 $col_count++;
1181 /* Delete the cell after we are done with it */
1182 unset($tables[$i][ROWS][$j][$k]);
1185 $tempSQLStr .= ")";
1187 if ($j != ($num_rows - 1)) {
1188 $tempSQLStr .= ",\n ";
1191 $col_count = 0;
1192 /* Delete the row after we are done with it */
1193 unset($tables[$i][ROWS][$j]);
1196 $tempSQLStr .= ";";
1199 * Each SQL statement is executed immediately
1200 * after it is formed so that we don't have
1201 * to store them in a (possibly large) buffer
1203 PMA_importRunQuery($tempSQLStr, $tempSQLStr);
1206 /* No longer needed */
1207 unset($tempSQLStr);
1210 * A work in progress
1213 /* Add the viewable structures from $additional_sql
1214 * to $tables so they are also displayed
1216 $view_pattern = '@VIEW `[^`]+`\.`([^`]+)@';
1217 $table_pattern = '@CREATE TABLE IF NOT EXISTS `([^`]+)`@';
1218 /* Check a third pattern to make sure its not a "USE `db_name`;" statement */
1220 $regs = array();
1222 $inTables = false;
1224 $additional_sql_len = count($additional_sql);
1225 for ($i = 0; $i < $additional_sql_len; ++$i) {
1226 preg_match($view_pattern, $additional_sql[$i], $regs);
1228 if (count($regs) == 0) {
1229 preg_match($table_pattern, $additional_sql[$i], $regs);
1232 if (count($regs)) {
1233 for ($n = 0; $n < $num_tables; ++$n) {
1234 if (! strcmp($regs[1], $tables[$n][TBL_NAME])) {
1235 $inTables = true;
1236 break;
1240 if (! $inTables) {
1241 $tables[] = array(TBL_NAME => $regs[1]);
1245 /* Reset the array */
1246 $regs = array();
1247 $inTables = false;
1250 $params = array('db' => (string)$db_name);
1251 $db_url = 'db_structure.php' . PMA_URL_getCommon($params);
1252 $db_ops_url = 'db_operations.php' . PMA_URL_getCommon($params);
1254 $message = '<br /><br />';
1255 $message .= '<strong>' . __('The following structures have either been created or altered. Here you can:') . '</strong><br />';
1256 $message .= '<ul><li>' . __("View a structure's contents by clicking on its name.") . '</li>';
1257 $message .= '<li>' . __('Change any of its settings by clicking the corresponding "Options" link.') . '</li>';
1258 $message .= '<li>' . __('Edit structure by following the "Structure" link.') . '</li>';
1259 $message .= sprintf(
1260 '<br /><li><a href="%s" title="%s">%s</a> (<a href="%s" title="%s">'
1261 . __('Options') . '</a>)</li>',
1262 $db_url,
1263 sprintf(
1264 __('Go to database: %s'),
1265 htmlspecialchars(PMA_Util::backquote($db_name))
1267 htmlspecialchars($db_name),
1268 $db_ops_url,
1269 sprintf(
1270 __('Edit settings for %s'),
1271 htmlspecialchars(PMA_Util::backquote($db_name))
1275 $message .= '<ul>';
1277 unset($params);
1279 $num_tables = count($tables);
1280 for ($i = 0; $i < $num_tables; ++$i) {
1281 $params = array(
1282 'db' => (string) $db_name,
1283 'table' => (string) $tables[$i][TBL_NAME]
1285 $tbl_url = 'sql.php' . PMA_URL_getCommon($params);
1286 $tbl_struct_url = 'tbl_structure.php' . PMA_URL_getCommon($params);
1287 $tbl_ops_url = 'tbl_operations.php' . PMA_URL_getCommon($params);
1289 unset($params);
1291 if (! PMA_Table::isView($db_name, $tables[$i][TBL_NAME])) {
1292 $message .= sprintf(
1293 '<li><a href="%s" title="%s">%s</a> (<a href="%s" title="%s">' . __('Structure') . '</a>) (<a href="%s" title="%s">' . __('Options') . '</a>)</li>',
1294 $tbl_url,
1295 sprintf(
1296 __('Go to table: %s'),
1297 htmlspecialchars(
1298 PMA_Util::backquote($tables[$i][TBL_NAME])
1301 htmlspecialchars($tables[$i][TBL_NAME]),
1302 $tbl_struct_url,
1303 sprintf(
1304 __('Structure of %s'),
1305 htmlspecialchars(
1306 PMA_Util::backquote($tables[$i][TBL_NAME])
1309 $tbl_ops_url,
1310 sprintf(
1311 __('Edit settings for %s'),
1312 htmlspecialchars(
1313 PMA_Util::backquote($tables[$i][TBL_NAME])
1317 } else {
1318 $message .= sprintf(
1319 '<li><a href="%s" title="%s">%s</a></li>',
1320 $tbl_url,
1321 sprintf(
1322 __('Go to view: %s'),
1323 htmlspecialchars(
1324 PMA_Util::backquote($tables[$i][TBL_NAME])
1327 htmlspecialchars($tables[$i][TBL_NAME])
1332 $message .= '</ul></ul>';
1334 global $import_notice;
1335 $import_notice = $message;
1337 unset($tables);
1342 * Stops the import on (mostly upload/file related) error
1344 * @param PMA_Message $error_message The error message
1346 * @return void
1347 * @access public
1350 function PMA_stopImport( PMA_Message $error_message )
1352 global $import_handle, $file_to_unlink;
1354 // Close open handles
1355 if ($import_handle !== false && $import_handle !== null) {
1356 fclose($import_handle);
1359 // Delete temporary file
1360 if ($file_to_unlink != '') {
1361 unlink($file_to_unlink);
1363 $msg = $error_message->getDisplay();
1364 $_SESSION['Import_message']['message'] = $msg;
1366 $response = PMA_Response::getInstance();
1367 $response->isSuccess(false);
1368 $response->addJSON('message', PMA_Message::error($msg));
1370 exit;
1374 * Handles request for Simulation of UPDATE/DELETE queries.
1376 * @return void
1378 function PMA_handleSimulateDMLRequest()
1380 $response = PMA_Response::getInstance();
1381 $error = false;
1382 $error_msg = __('Only single-table UPDATE and DELETE queries can be simulated.');
1383 $sql_delimiter = $_REQUEST['sql_delimiter'];
1384 $sql_data = array();
1385 $queries = explode($sql_delimiter, $GLOBALS['sql_query']);
1386 foreach ($queries as $sql_query) {
1387 if (empty($sql_query)) {
1388 continue;
1391 // Parse and Analyze the query.
1392 $parsed_sql = PMA_SQP_parse($sql_query);
1393 $analyzed_sql = PMA_SQP_analyze($parsed_sql);
1394 $analyzed_sql_results = array(
1395 'parsed_sql' => $parsed_sql,
1396 'analyzed_sql' => $analyzed_sql
1399 // Only UPDATE/DELETE queries accepted.
1400 $query_type = $analyzed_sql_results['analyzed_sql'][0]['querytype'];
1401 if ($query_type != 'UPDATE' && $query_type != 'DELETE') {
1402 $error = $error_msg;
1403 break;
1406 // Only single-table queries accepted.
1407 $table_references = PMA_getTableReferences($analyzed_sql_results);
1408 $table_references = $table_references ? $table_references : '';
1409 if (preg_match('/JOIN/i', $table_references)) {
1410 $error = $error_msg;
1411 break;
1412 } else {
1413 $tables = explode(',', $table_references);
1414 if (count($tables) > 1) {
1415 $error = $error_msg;
1416 break;
1420 // Get the matched rows for the query.
1421 $result = PMA_getMatchedRows($analyzed_sql_results);
1422 if (! $error = $GLOBALS['dbi']->getError()) {
1423 $sql_data[] = $result;
1424 } else {
1425 break;
1429 if ($error) {
1430 $message = PMA_Message::rawError($error);
1431 $response->addJSON('message', $message);
1432 $response->addJSON('sql_data', false);
1433 } else {
1434 $response->addJSON('sql_data', $sql_data);
1439 * Find the matching rows for UPDATE/DELETE query.
1441 * @param array $analyzed_sql_results Analyzed SQL results from parser.
1443 * @return mixed
1445 function PMA_getMatchedRows($analyzed_sql_results = array())
1447 // Get the query type.
1448 $query_type = (isset($analyzed_sql_results['analyzed_sql'][0]['querytype']))
1449 ? $analyzed_sql_results['analyzed_sql'][0]['querytype']
1450 : '';
1452 $matched_row_query = '';
1453 if ($query_type == 'DELETE') {
1454 $matched_row_query = PMA_getSimulatedDeleteQuery($analyzed_sql_results);
1455 } else if ($query_type == 'UPDATE') {
1456 $matched_row_query = PMA_getSimulatedUpdateQuery($analyzed_sql_results);
1459 // Execute the query and get the number of matched rows.
1460 $matched_rows = PMA_executeMatchedRowQuery($matched_row_query);
1461 // URL to matched rows.
1462 $_url_params = array(
1463 'db' => $GLOBALS['db'],
1464 'sql_query' => $matched_row_query
1466 $matched_rows_url = 'sql.php' . PMA_URL_getCommon($_url_params);
1468 return array(
1469 'sql_query' => PMA_Util::formatSql(
1470 $analyzed_sql_results['parsed_sql']['raw']
1472 'matched_rows' => $matched_rows,
1473 'matched_rows_url' => $matched_rows_url
1478 * Transforms a UPDATE query into SELECT statement.
1480 * @param array $analyzed_sql_results Analyzed SQL results from parser.
1482 * @return string SQL query
1484 function PMA_getSimulatedUpdateQuery($analyzed_sql_results)
1486 $where_clause = '';
1487 $extra_where_clause = '';
1488 $target_cols = array();
1490 $prev_term = '';
1491 $i = 0;
1492 foreach ($analyzed_sql_results['parsed_sql'] as $key => $term) {
1493 if (! isset($get_set_expr)
1494 && preg_match(
1495 '/\bSET\b/i',
1496 isset($term['data']) ? $term['data'] : ''
1499 $get_set_expr = true;
1500 continue;
1503 if (isset($get_set_expr)) {
1504 if (preg_match(
1505 '/\bWHERE\b|\bORDER BY\b|\bLIMIT\b/i',
1506 isset($term['data']) ? $term['data'] : ''
1509 break;
1512 if ($term['type'] == 'punct_listsep') {
1513 $extra_where_clause .= ' OR ';
1514 } else if ($term['type'] == 'punct') {
1515 $extra_where_clause .= ' <> ';
1516 } else {
1517 $extra_where_clause .= $term['data'];
1520 // Get columns in SET expression.
1521 if ($prev_term != 'punct') {
1522 if ($term['type'] != 'punct_listsep'
1523 && $term['type'] != 'punct'
1524 && isset($term['data'])
1526 if (isset($target_cols[$i])) {
1527 $target_cols[$i] .= $term['data'];
1528 } else {
1529 $target_cols[$i] = $term['data'];
1532 } else {
1533 $i++;
1536 $prev_term = $term['type'];
1537 continue;
1541 // Get table_references.
1542 $table_references = PMA_getTableReferences($analyzed_sql_results);
1543 $target_cols = implode(', ', $target_cols);
1545 // Get WHERE clause.
1546 $where_clause .= $analyzed_sql_results['analyzed_sql'][0]['where_clause'];
1547 if (empty($where_clause) && empty($extra_where_clause)) {
1548 $where_clause = '1';
1551 $matched_row_query = 'SELECT '
1552 . $target_cols
1553 . ' FROM '
1554 . $table_references
1555 . ' WHERE '
1556 . $where_clause;
1558 return $matched_row_query;
1562 * Transforms a DELETE query into SELECT statement.
1564 * @param array $analyzed_sql_results Analyzed SQL results from parser.
1566 * @return string SQL query
1568 function PMA_getSimulatedDeleteQuery($analyzed_sql_results)
1570 $where_clause = '';
1572 $where_clause .= $analyzed_sql_results['analyzed_sql'][0]['where_clause'];
1573 if (empty($where_clause) && empty($extra_where_clause)) {
1574 $where_clause = '1';
1577 // Get the table_references.
1578 $table_references = PMA_getTableReferences($analyzed_sql_results);
1580 $matched_row_query = 'SELECT * '
1581 . ' FROM '
1582 . $table_references
1583 . ' WHERE '
1584 . $where_clause;
1586 return $matched_row_query;
1590 * Finds table_references from a given query.
1591 * Queries Supported: INSERT, UPDATE, DELETE, REPLACE, ALTER, DROP, TRUNCATE
1592 * and RENAME.
1594 * @param array $analyzed_sql_results Analyzed SQL results from parser
1596 * @return string table_references
1598 function PMA_getTableReferences($analyzed_sql_results)
1600 $table_references = '';
1601 foreach ($analyzed_sql_results['parsed_sql'] as $key => $term) {
1602 // Skip first KeyWord and other invalid keys.
1603 if ($key == 0 || ! isset($term['data'])) {
1604 continue;
1607 // Get the query type.
1608 $query_type = (isset($analyzed_sql_results['analyzed_sql'][0]['querytype']))
1609 ? $analyzed_sql_results['analyzed_sql'][0]['querytype']
1610 : '';
1612 // Terms to 'ignore' from query for table_references.
1613 $ignore_re = '/';
1614 // Terminating condition for table_references.
1615 $terminate_re = '/';
1617 // Create relevant Regular Expressions.
1618 switch ($query_type) {
1619 case 'REPLACE':
1620 case 'INSERT':
1621 $ignore_re .= '\bINSERT\b|\bREPLACE\b|\bLOW_PRIORITY\b|\bDELAYED\b'
1622 . '|\bHIGH_PRIORITY\b|\bIGNORE\b|\bINTO\b';
1623 $terminate_re .= '\bPARTITION\b|\(|\bVALUE\b|\bVALUES\b|\bSELECT\b';
1624 break;
1625 case 'UPDATE':
1626 $ignore_re .= '\bUPDATE\b|\bLOW_PRIORITY\b|\bIGNORE\b';
1627 $terminate_re .= '\bSET\b|\bUSING\b';
1628 break;
1629 case 'DELETE':
1630 $ignore_re .= '\bDELETE\b|\bLOW_PRIORITY\b|\bQUICK\b|\bIGNORE\b'
1631 . '|\bFROM\b';
1632 $terminate_re .= '\bPARTITION\b|\bWHERE\b|\bORDER\b|\bLIMIT\b|\bUSING\b';
1633 break;
1634 case 'ALTER':
1635 $ignore_re .= '\bALTER\b|\bONLINE\b|\bOFFLINE\b|\bIGNORE\b|\bTABLE\b';
1636 $terminate_re .= '\bADD\b|\bALTER\b|\bCHANGE\b|\bMODIFY\b|\bDROP\b'
1637 . '|\bDISABLE\b|\bENABLE\b|\bRENAME\b|\bORDER\b|\bCONVERT\b'
1638 . '|\bDEFAULT\b|\bDISCARD\b|\bIMPORT\b|\bCOALESCE\b|\bREORGANIZE\b'
1639 . '|\bANALYZE\b|\bCHECK\b|\bOPTIMIZE\b|\bREBUILD\b|\bREPAIR\b'
1640 . '|\bPARTITION\b|\bREMOVE\b|\bCHARACTER\b';
1641 break;
1642 case 'DROP':
1643 $ignore_re .= '\bDROP\b|\bTEMPORARY\b|\bTABLE\b|\bIF\b|\bEXISTS\b';
1644 $terminate_re .= '\bRESTRICT\b|\bCASCADE\b';
1645 break;
1646 case 'TRUNCATE':
1647 $ignore_re .= '\bTRUNCATE\b|\bTABLE\b';
1648 $terminate_re .= '';
1649 break;
1650 case 'RENAME':
1651 $ignore_re .= '\bRENAME\b|\bTABLE\b';
1652 $terminate_re .= '\bTO\b';
1653 break;
1654 default:
1655 return false;
1658 // Ignore 'case' in RegEx.
1659 $ignore_re .= '/i';
1660 $terminate_re .= '/i';
1662 if ($query_type != 'TRUNCATE'
1663 && preg_match($terminate_re, $term['data'])
1665 break;
1668 if (preg_match($ignore_re, $term['data'])
1669 || ! is_numeric($key)
1670 || $key == 0
1672 continue;
1675 $table_references .= ' ' . $term['data'];
1678 return $table_references;
1682 * Executes the matched_row_query and returns the resultant row count.
1684 * @param string $matched_row_query SQL query
1686 * @return integer Number of rows returned
1688 function PMA_executeMatchedRowQuery($matched_row_query)
1690 $GLOBALS['dbi']->selectDb($GLOBALS['db']);
1691 // Execute the query.
1692 $result = $GLOBALS['dbi']->tryQuery($matched_row_query);
1693 // Count the number of rows in the result set.
1694 $result = $GLOBALS['dbi']->numRows($result);
1696 return $result;
1700 * Extracts unique table names from table_references.
1702 * @param string $table_references table_references
1704 * @return array $table_names
1706 function PMA_getTableNamesFromTableReferences($table_references)
1708 $table_names = array();
1709 $parsed_data = PMA_SQP_parse($table_references);
1711 $prev_term = array(
1712 'data' => '',
1713 'type' => ''
1715 $on_encountered = false;
1716 $qualifier_encountered = false;
1717 $i = 0;
1718 foreach ($parsed_data as $key => $term) {
1719 // To skip first 'raw' key and other invalid keys.
1720 if (! is_numeric($key)
1721 || ! isset($term['data'])
1722 || ! isset($term['type'])
1724 continue;
1727 $add_to_table_names = true;
1729 // Un-quote the data, if any.
1730 if ($term['type'] == 'quote_backtick') {
1731 $term['data'] = PMA_Util::unQuote($term['data']);
1732 $term['type'] = 'alpha_identifier';
1735 // New table name expected after 'JOIN' keyword.
1736 if (preg_match('/\bJOIN\b/i', $term['data'])) {
1737 $on_encountered = false;
1740 // If term is a qualifier, set flag.
1741 if ($term['type'] == 'punct_qualifier') {
1742 $qualifier_encountered = true;
1745 // Skip the JOIN conditions after 'ON' keyword.
1746 if (preg_match('/\bON\b/i', $term['data'])) {
1747 $on_encountered = true;
1750 // If the word is not an 'identifier', skip it.
1751 if ($term['type'] != 'alpha_identifier') {
1752 $add_to_table_names = false;
1755 // Skip table 'alias'.
1756 if (preg_match('/\bAS\b/i', $prev_term['data'])
1757 || $prev_term['type'] == 'alpha_identifier'
1759 $add_to_table_names = false;
1762 // Everything fine up to now, add name to list if 'unique'.
1763 if ($add_to_table_names
1764 && ! $on_encountered
1765 && ! in_array($term['data'], $table_names)
1767 if (! $qualifier_encountered) {
1768 $table_names[] = PMA_Util::backquote($term['data']);
1769 $i++;
1770 } else {
1771 // If qualifier encountered, concatenate DB name and table name.
1772 $table_names[$i-1] = $table_names[$i-1]
1773 . '.'
1774 . PMA_Util::backquote($term['data']);
1775 $qualifier_encountered = false;
1779 // Update previous term.
1780 $prev_term = $term;
1783 return $table_names;
1787 * Handles request for ROLLBACK.
1789 * @param string $sql_query SQL query(s)
1791 * @return void
1793 function PMA_handleRollbackRequest($sql_query)
1795 $sql_delimiter = $_REQUEST['sql_delimiter'];
1796 $queries = explode($sql_delimiter, $sql_query);
1797 $error = false;
1798 $error_msg = __(
1799 'Only INSERT, UPDATE, DELETE and REPLACE '
1800 . 'SQL queries containing transactional engine tables can be rolled back.'
1802 foreach ($queries as $sql_query) {
1803 if (empty($sql_query)) {
1804 continue;
1807 // Check each query for ROLLBACK support.
1808 if (! PMA_checkIfRollbackPossible($sql_query)) {
1809 $global_error = $GLOBALS['dbi']->getError();
1810 if ($global_error) {
1811 $error = $global_error;
1812 } else {
1813 $error = $error_msg;
1815 break;
1819 if ($error) {
1820 unset($_REQUEST['rollback_query']);
1821 $response = PMA_Response::getInstance();
1822 $message = PMA_Message::rawError($error);
1823 $response->addJSON('message', $message);
1824 exit;
1825 } else {
1826 // If everything fine, START a transaction.
1827 $GLOBALS['dbi']->query('START TRANSACTION');
1832 * Checks if ROLLBACK is possible for a SQL query or not.
1834 * @param string $sql_query SQL query
1836 * @return bool
1838 function PMA_checkIfRollbackPossible($sql_query)
1840 // Supported queries.
1841 $supported_queries = array(
1842 'INSERT',
1843 'UPDATE',
1844 'DELETE',
1845 'REPLACE'
1848 // Parse and Analyze the query.
1849 $parsed_sql = PMA_SQP_parse($sql_query);
1850 $analyzed_sql = PMA_SQP_analyze($parsed_sql);
1851 $analyzed_sql_results = array(
1852 'parsed_sql' => $parsed_sql,
1853 'analyzed_sql' => $analyzed_sql
1856 // Get the query type.
1857 $query_type = (isset($analyzed_sql_results['analyzed_sql'][0]['querytype']))
1858 ? $analyzed_sql_results['analyzed_sql'][0]['querytype']
1859 : '';
1861 // Check if query is supported.
1862 if (! in_array($query_type, $supported_queries)) {
1863 return false;
1866 // Get table_references from the query.
1867 $table_references = PMA_getTableReferences($analyzed_sql_results);
1868 $table_references = $table_references ? $table_references : '';
1869 // Get table names from table_references.
1870 $tables = PMA_getTableNamesFromTableReferences($table_references);
1872 // Check if each table is 'InnoDB'.
1873 foreach ($tables as $table) {
1874 if (! PMA_isTableTransactional($table)) {
1875 return false;
1879 return true;
1883 * Checks if a table is 'InnoDB' or not.
1885 * @param string $table Table details
1887 * @return bool
1889 function PMA_isTableTransactional($table)
1891 $table = explode('.', $table);
1892 if (count($table) == 2) {
1893 $db = PMA_Util::unQuote($table[0]);
1894 $table = PMA_Util::unQuote($table[1]);
1895 } else {
1896 $db = $GLOBALS['db'];
1897 $table = PMA_Util::unQuote($table[0]);
1900 // Query to check if table exists.
1901 $check_table_query = 'SELECT * FROM ' . PMA_Util::backquote($db)
1902 . '.' . PMA_Util::backquote($table) . ' '
1903 . 'LIMIT 1';
1905 $result = $GLOBALS['dbi']->tryQuery($check_table_query);
1907 if (! $result) {
1908 return false;
1911 // List of Transactional Engines.
1912 $transactional_engines = array(
1913 'INNODB',
1914 'FALCON',
1915 'NDB',
1916 'INFINIDB',
1917 'TOKUDB',
1918 'XTRADB',
1919 'SEQUENCE',
1920 'BDB'
1923 // Query to check if table is 'Transactional'.
1924 $check_query = 'SELECT `ENGINE` FROM `information_schema`.`tables` '
1925 . 'WHERE `table_name` = "' . $table . '" '
1926 . 'AND `table_schema` = "' . $db . '" '
1927 . 'AND UPPER(`engine`) IN ("'
1928 . implode('", "', $transactional_engines)
1929 . '")';
1931 $result = $GLOBALS['dbi']->tryQuery($check_query);
1933 if ($GLOBALS['dbi']->numRows($result) == 1) {
1934 return true;
1935 } else {
1936 return false;