ImportSql.class.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440
  1. <?php
  2. /* vim: set expandtab sw=4 ts=4 sts=4: */
  3. /**
  4. * SQL import plugin for phpMyAdmin
  5. *
  6. * @package PhpMyAdmin-Import
  7. * @subpackage SQL
  8. */
  9. if (! defined('PHPMYADMIN')) {
  10. exit;
  11. }
  12. /* Get the import interface */
  13. require_once 'libraries/plugins/ImportPlugin.class.php';
  14. /**
  15. * Handles the import for the SQL format
  16. *
  17. * @package PhpMyAdmin-Import
  18. * @subpackage SQL
  19. */
  20. class ImportSql extends ImportPlugin
  21. {
  22. /**
  23. * Constructor
  24. */
  25. public function __construct()
  26. {
  27. $this->setProperties();
  28. }
  29. /**
  30. * Sets the import plugin properties.
  31. * Called in the constructor.
  32. *
  33. * @return void
  34. */
  35. protected function setProperties()
  36. {
  37. $props = 'libraries/properties/';
  38. include_once "$props/plugins/ImportPluginProperties.class.php";
  39. include_once "$props/options/groups/OptionsPropertyRootGroup.class.php";
  40. include_once "$props/options/groups/OptionsPropertyMainGroup.class.php";
  41. include_once "$props/options/items/SelectPropertyItem.class.php";
  42. include_once "$props/options/items/BoolPropertyItem.class.php";
  43. $importPluginProperties = new ImportPluginProperties();
  44. $importPluginProperties->setText('SQL');
  45. $importPluginProperties->setExtension('sql');
  46. $importPluginProperties->setOptionsText(__('Options'));
  47. $compats = PMA_DBI_getCompatibilities();
  48. if (count($compats) > 0) {
  49. $values = array();
  50. foreach ($compats as $val) {
  51. $values[$val] = $val;
  52. }
  53. // create the root group that will be the options field for
  54. // $importPluginProperties
  55. // this will be shown as "Format specific options"
  56. $importSpecificOptions = new OptionsPropertyRootGroup();
  57. $importSpecificOptions->setName("Format Specific Options");
  58. // general options main group
  59. $generalOptions = new OptionsPropertyMainGroup();
  60. $generalOptions->setName("general_opts");
  61. // create primary items and add them to the group
  62. $leaf = new SelectPropertyItem();
  63. $leaf->setName("compatibility");
  64. $leaf->setText(__('SQL compatibility mode:'));
  65. $leaf->setValues($values);
  66. $leaf->setDoc(
  67. array(
  68. 'manual_MySQL_Database_Administration',
  69. 'Server_SQL_mode',
  70. )
  71. );
  72. $generalOptions->addProperty($leaf);
  73. $leaf = new BoolPropertyItem();
  74. $leaf->setName("no_auto_value_on_zero");
  75. $leaf->setText(
  76. __('Do not use <code>AUTO_INCREMENT</code> for zero values')
  77. );
  78. $leaf->setDoc(
  79. array(
  80. 'manual_MySQL_Database_Administration',
  81. 'Server_SQL_mode',
  82. 'sqlmode_no_auto_value_on_zero'
  83. )
  84. );
  85. $generalOptions->addProperty($leaf);
  86. // add the main group to the root group
  87. $importSpecificOptions->addProperty($generalOptions);
  88. // set the options for the import plugin property item
  89. $importPluginProperties->setOptions($importSpecificOptions);
  90. }
  91. $this->properties = $importPluginProperties;
  92. }
  93. /**
  94. * This method is called when any PluginManager to which the observer
  95. * is attached calls PluginManager::notify()
  96. *
  97. * @param SplSubject $subject The PluginManager notifying the observer
  98. * of an update.
  99. *
  100. * @return void
  101. */
  102. public function update (SplSubject $subject)
  103. {
  104. }
  105. /**
  106. * Handles the whole import logic
  107. *
  108. * @param array &$sql_data 2-element array with sql data
  109. *
  110. * @return void
  111. */
  112. public function doImport(&$sql_data = array())
  113. {
  114. global $error, $timeout_passed;
  115. $buffer = '';
  116. // Defaults for parser
  117. $sql = '';
  118. $start_pos = 0;
  119. $i = 0;
  120. $len= 0;
  121. $big_value = 2147483647;
  122. // include the space because it's mandatory
  123. $delimiter_keyword = 'DELIMITER ';
  124. $length_of_delimiter_keyword = strlen($delimiter_keyword);
  125. if (isset($_POST['sql_delimiter'])) {
  126. $sql_delimiter = $_POST['sql_delimiter'];
  127. } else {
  128. $sql_delimiter = ';';
  129. }
  130. // Handle compatibility options
  131. $sql_modes = array();
  132. if (isset($_REQUEST['sql_compatibility'])
  133. && 'NONE' != $_REQUEST['sql_compatibility']
  134. ) {
  135. $sql_modes[] = $_REQUEST['sql_compatibility'];
  136. }
  137. if (isset($_REQUEST['sql_no_auto_value_on_zero'])) {
  138. $sql_modes[] = 'NO_AUTO_VALUE_ON_ZERO';
  139. }
  140. if (count($sql_modes) > 0) {
  141. PMA_DBI_try_query('SET SQL_MODE="' . implode(',', $sql_modes) . '"');
  142. }
  143. unset($sql_modes);
  144. /**
  145. * will be set in PMA_importGetNextChunk()
  146. *
  147. * @global boolean $GLOBALS['finished']
  148. */
  149. $GLOBALS['finished'] = false;
  150. while (! ($GLOBALS['finished'] && $i >= $len)
  151. && ! $error
  152. && ! $timeout_passed
  153. ) {
  154. $data = PMA_importGetNextChunk();
  155. if ($data === false) {
  156. // subtract data we didn't handle yet and stop processing
  157. $offset -= strlen($buffer);
  158. break;
  159. } elseif ($data === true) {
  160. // Handle rest of buffer
  161. } else {
  162. // Append new data to buffer
  163. $buffer .= $data;
  164. // free memory
  165. unset($data);
  166. // Do not parse string when we're not at the end
  167. // and don't have ; inside
  168. if ((strpos($buffer, $sql_delimiter, $i) === false)
  169. && ! $GLOBALS['finished']
  170. ) {
  171. continue;
  172. }
  173. }
  174. // Current length of our buffer
  175. $len = strlen($buffer);
  176. // Grab some SQL queries out of it
  177. while ($i < $len) {
  178. $found_delimiter = false;
  179. // Find first interesting character
  180. $old_i = $i;
  181. // this is about 7 times faster that looking for each sequence i
  182. // one by one with strpos()
  183. $match = preg_match(
  184. '/(\'|"|#|-- |\/\*|`|(?i)(?<![A-Z0-9_])'
  185. . $delimiter_keyword . ')/',
  186. $buffer,
  187. $matches,
  188. PREG_OFFSET_CAPTURE,
  189. $i
  190. );
  191. if ($match) {
  192. // in $matches, index 0 contains the match for the complete
  193. // expression but we don't use it
  194. $first_position = $matches[1][1];
  195. } else {
  196. $first_position = $big_value;
  197. }
  198. /**
  199. * @todo we should not look for a delimiter that might be
  200. * inside quotes (or even double-quotes)
  201. */
  202. // the cost of doing this one with preg_match() would be too high
  203. $first_sql_delimiter = strpos($buffer, $sql_delimiter, $i);
  204. if ($first_sql_delimiter === false) {
  205. $first_sql_delimiter = $big_value;
  206. } else {
  207. $found_delimiter = true;
  208. }
  209. // set $i to the position of the first quote,
  210. // comment.start or delimiter found
  211. $i = min($first_position, $first_sql_delimiter);
  212. if ($i == $big_value) {
  213. // none of the above was found in the string
  214. $i = $old_i;
  215. if (! $GLOBALS['finished']) {
  216. break;
  217. }
  218. // at the end there might be some whitespace...
  219. if (trim($buffer) == '') {
  220. $buffer = '';
  221. $len = 0;
  222. break;
  223. }
  224. // We hit end of query, go there!
  225. $i = strlen($buffer) - 1;
  226. }
  227. // Grab current character
  228. $ch = $buffer[$i];
  229. // Quotes
  230. if (strpos('\'"`', $ch) !== false) {
  231. $quote = $ch;
  232. $endq = false;
  233. while (! $endq) {
  234. // Find next quote
  235. $pos = strpos($buffer, $quote, $i + 1);
  236. /*
  237. * Behave same as MySQL and accept end of query as end
  238. * of backtick.
  239. * I know this is sick, but MySQL behaves like this:
  240. *
  241. * SELECT * FROM `table
  242. *
  243. * is treated like
  244. *
  245. * SELECT * FROM `table`
  246. */
  247. if ($pos === false && $quote == '`' && $found_delimiter) {
  248. $pos = $first_sql_delimiter - 1;
  249. } elseif ($pos === false) { // No quote? Too short string
  250. // We hit end of string => unclosed quote,
  251. // but we handle it as end of query
  252. if ($GLOBALS['finished']) {
  253. $endq = true;
  254. $i = $len - 1;
  255. }
  256. $found_delimiter = false;
  257. break;
  258. }
  259. // Was not the quote escaped?
  260. $j = $pos - 1;
  261. while ($buffer[$j] == '\\') {
  262. $j--;
  263. }
  264. // Even count means it was not escaped
  265. $endq = (((($pos - 1) - $j) % 2) == 0);
  266. // Skip the string
  267. $i = $pos;
  268. if ($first_sql_delimiter < $pos) {
  269. $found_delimiter = false;
  270. }
  271. }
  272. if (! $endq) {
  273. break;
  274. }
  275. $i++;
  276. // Aren't we at the end?
  277. if ($GLOBALS['finished'] && $i == $len) {
  278. $i--;
  279. } else {
  280. continue;
  281. }
  282. }
  283. // Not enough data to decide
  284. if ((($i == ($len - 1) && ($ch == '-' || $ch == '/'))
  285. || ($i == ($len - 2) && (($ch == '-' && $buffer[$i + 1] == '-')
  286. || ($ch == '/' && $buffer[$i + 1] == '*'))))
  287. && ! $GLOBALS['finished']
  288. ) {
  289. break;
  290. }
  291. // Comments
  292. if ($ch == '#'
  293. || ($i < ($len - 1) && $ch == '-' && $buffer[$i + 1] == '-'
  294. && (($i < ($len - 2) && $buffer[$i + 2] <= ' ')
  295. || ($i == ($len - 1) && $GLOBALS['finished'])))
  296. || ($i < ($len - 1) && $ch == '/' && $buffer[$i + 1] == '*')
  297. ) {
  298. // Copy current string to SQL
  299. if ($start_pos != $i) {
  300. $sql .= substr($buffer, $start_pos, $i - $start_pos);
  301. }
  302. // Skip the rest
  303. $start_of_comment = $i;
  304. // do not use PHP_EOL here instead of "\n", because the export
  305. // file might have been produced on a different system
  306. $i = strpos($buffer, $ch == '/' ? '*/' : "\n", $i);
  307. // didn't we hit end of string?
  308. if ($i === false) {
  309. if ($GLOBALS['finished']) {
  310. $i = $len - 1;
  311. } else {
  312. break;
  313. }
  314. }
  315. // Skip *
  316. if ($ch == '/') {
  317. $i++;
  318. }
  319. // Skip last char
  320. $i++;
  321. // We need to send the comment part in case we are defining
  322. // a procedure or function and comments in it are valuable
  323. $sql .= substr(
  324. $buffer,
  325. $start_of_comment,
  326. $i - $start_of_comment
  327. );
  328. // Next query part will start here
  329. $start_pos = $i;
  330. // Aren't we at the end?
  331. if ($i == $len) {
  332. $i--;
  333. } else {
  334. continue;
  335. }
  336. }
  337. // Change delimiter, if redefined, and skip it
  338. // (don't send to server!)
  339. if (($i + $length_of_delimiter_keyword < $len)
  340. && strtoupper(
  341. substr($buffer, $i, $length_of_delimiter_keyword)
  342. ) == $delimiter_keyword
  343. ) {
  344. // look for EOL on the character immediately after 'DELIMITER '
  345. // (see previous comment about PHP_EOL)
  346. $new_line_pos = strpos(
  347. $buffer,
  348. "\n",
  349. $i + $length_of_delimiter_keyword
  350. );
  351. // it might happen that there is no EOL
  352. if (false === $new_line_pos) {
  353. $new_line_pos = $len;
  354. }
  355. $sql_delimiter = substr(
  356. $buffer,
  357. $i + $length_of_delimiter_keyword,
  358. $new_line_pos - $i - $length_of_delimiter_keyword
  359. );
  360. $i = $new_line_pos + 1;
  361. // Next query part will start here
  362. $start_pos = $i;
  363. continue;
  364. }
  365. // End of SQL
  366. if ($found_delimiter
  367. || ($GLOBALS['finished']
  368. && ($i == $len - 1))
  369. ) {
  370. $tmp_sql = $sql;
  371. if ($start_pos < $len) {
  372. $length_to_grab = $i - $start_pos;
  373. if (! $found_delimiter) {
  374. $length_to_grab++;
  375. }
  376. $tmp_sql .= substr($buffer, $start_pos, $length_to_grab);
  377. unset($length_to_grab);
  378. }
  379. // Do not try to execute empty SQL
  380. if (! preg_match('/^([\s]*;)*$/', trim($tmp_sql))) {
  381. $sql = $tmp_sql;
  382. PMA_importRunQuery(
  383. $sql,
  384. substr($buffer, 0, $i + strlen($sql_delimiter)),
  385. false,
  386. $sql_data
  387. );
  388. $buffer = substr($buffer, $i + strlen($sql_delimiter));
  389. // Reset parser:
  390. $len = strlen($buffer);
  391. $sql = '';
  392. $i = 0;
  393. $start_pos = 0;
  394. // Any chance we will get a complete query?
  395. //if ((strpos($buffer, ';') === false)
  396. //&& ! $GLOBALS['finished']) {
  397. if (strpos($buffer, $sql_delimiter) === false
  398. && ! $GLOBALS['finished']
  399. ) {
  400. break;
  401. }
  402. } else {
  403. $i++;
  404. $start_pos = $i;
  405. }
  406. }
  407. } // End of parser loop
  408. } // End of import loop
  409. // Commit any possible data in buffers
  410. PMA_importRunQuery('', substr($buffer, 0, $len), false, $sql_data);
  411. PMA_importRunQuery('', '', false, $sql_data);
  412. }
  413. }