ImportSql.class.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463
  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 = $GLOBALS['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. $GLOBALS['dbi']->tryQuery(
  142. 'SET SQL_MODE="' . implode(',', $sql_modes) . '"'
  143. );
  144. }
  145. unset($sql_modes);
  146. /**
  147. * will be set in PMA_importGetNextChunk()
  148. *
  149. * @global boolean $GLOBALS['finished']
  150. */
  151. $GLOBALS['finished'] = false;
  152. while (! ($GLOBALS['finished'] && $i >= $len)
  153. && ! $error
  154. && ! $timeout_passed
  155. ) {
  156. $data = PMA_importGetNextChunk();
  157. if ($data === false) {
  158. // subtract data we didn't handle yet and stop processing
  159. $GLOBALS['offset'] -= strlen($buffer);
  160. break;
  161. } elseif ($data === true) {
  162. // Handle rest of buffer
  163. } else {
  164. // Append new data to buffer
  165. $buffer .= $data;
  166. // free memory
  167. unset($data);
  168. // Do not parse string when we're not at the end
  169. // and don't have ; inside
  170. if ((strpos($buffer, $sql_delimiter, $i) === false)
  171. && ! $GLOBALS['finished']
  172. ) {
  173. continue;
  174. }
  175. }
  176. // Convert CR (but not CRLF) to LF otherwise all queries
  177. // may not get executed on some platforms
  178. $buffer = preg_replace("/\r($|[^\n])/", "\n$1", $buffer);
  179. // Current length of our buffer
  180. $len = strlen($buffer);
  181. // Grab some SQL queries out of it
  182. while ($i < $len) {
  183. $found_delimiter = false;
  184. // Find first interesting character
  185. $old_i = $i;
  186. // this is about 7 times faster that looking for each sequence i
  187. // one by one with strpos()
  188. $match = preg_match(
  189. '/(\'|"|#|-- |\/\*|`|(?i)(?<![A-Z0-9_])'
  190. . $delimiter_keyword . ')/',
  191. $buffer,
  192. $matches,
  193. PREG_OFFSET_CAPTURE,
  194. $i
  195. );
  196. if ($match) {
  197. // in $matches, index 0 contains the match for the complete
  198. // expression but we don't use it
  199. $first_position = $matches[1][1];
  200. } else {
  201. $first_position = $big_value;
  202. }
  203. /**
  204. * @todo we should not look for a delimiter that might be
  205. * inside quotes (or even double-quotes)
  206. */
  207. // the cost of doing this one with preg_match() would be too high
  208. $first_sql_delimiter = strpos($buffer, $sql_delimiter, $i);
  209. if ($first_sql_delimiter === false) {
  210. $first_sql_delimiter = $big_value;
  211. } else {
  212. $found_delimiter = true;
  213. }
  214. // set $i to the position of the first quote,
  215. // comment.start or delimiter found
  216. $i = min($first_position, $first_sql_delimiter);
  217. if ($i == $big_value) {
  218. // none of the above was found in the string
  219. $i = $old_i;
  220. if (! $GLOBALS['finished']) {
  221. break;
  222. }
  223. // at the end there might be some whitespace...
  224. if (trim($buffer) == '') {
  225. $buffer = '';
  226. $len = 0;
  227. break;
  228. }
  229. // We hit end of query, go there!
  230. $i = strlen($buffer) - 1;
  231. }
  232. // Grab current character
  233. $ch = $buffer[$i];
  234. // Quotes
  235. if (strpos('\'"`', $ch) !== false) {
  236. $quote = $ch;
  237. $endq = false;
  238. while (! $endq) {
  239. // Find next quote
  240. $pos = strpos($buffer, $quote, $i + 1);
  241. /*
  242. * Behave same as MySQL and accept end of query as end
  243. * of backtick.
  244. * I know this is sick, but MySQL behaves like this:
  245. *
  246. * SELECT * FROM `table
  247. *
  248. * is treated like
  249. *
  250. * SELECT * FROM `table`
  251. */
  252. if ($pos === false && $quote == '`' && $found_delimiter) {
  253. $pos = $first_sql_delimiter - 1;
  254. } elseif ($pos === false) { // No quote? Too short string
  255. // We hit end of string => unclosed quote,
  256. // but we handle it as end of query
  257. list($endq, $i)
  258. = $this->getEndQuoteAndPos($len, $endq, $i);
  259. $found_delimiter = false;
  260. break;
  261. }
  262. // Was not the quote escaped?
  263. $j = $pos - 1;
  264. while ($buffer[$j] == '\\') {
  265. $j--;
  266. }
  267. // Even count means it was not escaped
  268. $endq = (((($pos - 1) - $j) % 2) == 0);
  269. // Skip the string
  270. $i = $pos;
  271. if ($first_sql_delimiter < $pos) {
  272. $found_delimiter = false;
  273. }
  274. }
  275. if (! $endq) {
  276. break;
  277. }
  278. $i++;
  279. // Aren't we at the end?
  280. if ($GLOBALS['finished'] && $i == $len) {
  281. $i--;
  282. } else {
  283. continue;
  284. }
  285. }
  286. // Not enough data to decide
  287. if ((($i == ($len - 1) && ($ch == '-' || $ch == '/'))
  288. || ($i == ($len - 2) && (($ch == '-' && $buffer[$i + 1] == '-')
  289. || ($ch == '/' && $buffer[$i + 1] == '*'))))
  290. && ! $GLOBALS['finished']
  291. ) {
  292. break;
  293. }
  294. // Comments
  295. if ($ch == '#'
  296. || ($i < ($len - 1) && $ch == '-' && $buffer[$i + 1] == '-'
  297. && (($i < ($len - 2) && $buffer[$i + 2] <= ' ')
  298. || ($i == ($len - 1) && $GLOBALS['finished'])))
  299. || ($i < ($len - 1) && $ch == '/' && $buffer[$i + 1] == '*')
  300. ) {
  301. // Copy current string to SQL
  302. if ($start_pos != $i) {
  303. $sql .= substr($buffer, $start_pos, $i - $start_pos);
  304. }
  305. // Skip the rest
  306. $start_of_comment = $i;
  307. // do not use PHP_EOL here instead of "\n", because the export
  308. // file might have been produced on a different system
  309. $i = strpos($buffer, $ch == '/' ? '*/' : "\n", $i);
  310. // didn't we hit end of string?
  311. if ($i === false) {
  312. if ($GLOBALS['finished']) {
  313. $i = $len - 1;
  314. } else {
  315. break;
  316. }
  317. }
  318. // Skip *
  319. if ($ch == '/') {
  320. $i++;
  321. }
  322. // Skip last char
  323. $i++;
  324. // We need to send the comment part in case we are defining
  325. // a procedure or function and comments in it are valuable
  326. $sql .= substr(
  327. $buffer,
  328. $start_of_comment,
  329. $i - $start_of_comment
  330. );
  331. // Next query part will start here
  332. $start_pos = $i;
  333. // Aren't we at the end?
  334. if ($i == $len) {
  335. $i--;
  336. } else {
  337. continue;
  338. }
  339. }
  340. // Change delimiter, if redefined, and skip it
  341. // (don't send to server!)
  342. if (($i + $length_of_delimiter_keyword < $len)
  343. && strtoupper(
  344. substr($buffer, $i, $length_of_delimiter_keyword)
  345. ) == $delimiter_keyword
  346. ) {
  347. // look for EOL on the character immediately after 'DELIMITER '
  348. // (see previous comment about PHP_EOL)
  349. $new_line_pos = strpos(
  350. $buffer,
  351. "\n",
  352. $i + $length_of_delimiter_keyword
  353. );
  354. // it might happen that there is no EOL
  355. if (false === $new_line_pos) {
  356. $new_line_pos = $len;
  357. }
  358. $sql_delimiter = substr(
  359. $buffer,
  360. $i + $length_of_delimiter_keyword,
  361. $new_line_pos - $i - $length_of_delimiter_keyword
  362. );
  363. $i = $new_line_pos + 1;
  364. // Next query part will start here
  365. $start_pos = $i;
  366. continue;
  367. }
  368. // End of SQL
  369. if ($found_delimiter
  370. || ($GLOBALS['finished']
  371. && ($i == $len - 1))
  372. ) {
  373. $tmp_sql = $sql;
  374. if ($start_pos < $len) {
  375. $length_to_grab = $i - $start_pos;
  376. if (! $found_delimiter) {
  377. $length_to_grab++;
  378. }
  379. $tmp_sql .= substr($buffer, $start_pos, $length_to_grab);
  380. unset($length_to_grab);
  381. }
  382. // Do not try to execute empty SQL
  383. if (! preg_match('/^([\s]*;)*$/', trim($tmp_sql))) {
  384. $sql = $tmp_sql;
  385. PMA_importRunQuery(
  386. $sql,
  387. substr($buffer, 0, $i + strlen($sql_delimiter)),
  388. false,
  389. $sql_data
  390. );
  391. $buffer = substr($buffer, $i + strlen($sql_delimiter));
  392. // Reset parser:
  393. $len = strlen($buffer);
  394. $sql = '';
  395. $i = 0;
  396. $start_pos = 0;
  397. // Any chance we will get a complete query?
  398. //if ((strpos($buffer, ';') === false)
  399. //&& ! $GLOBALS['finished']) {
  400. if (strpos($buffer, $sql_delimiter) === false
  401. && ! $GLOBALS['finished']
  402. ) {
  403. break;
  404. }
  405. } else {
  406. $i++;
  407. $start_pos = $i;
  408. }
  409. }
  410. } // End of parser loop
  411. } // End of import loop
  412. // Commit any possible data in buffers
  413. PMA_importRunQuery('', substr($buffer, 0, $len), false, $sql_data);
  414. PMA_importRunQuery('', '', false, $sql_data);
  415. }
  416. /**
  417. * Get end quote and position
  418. *
  419. * @param int $len Length
  420. * @param bool $endq End quote
  421. * @param int $position Position
  422. *
  423. * @return array End quote, position
  424. */
  425. protected function getEndQuoteAndPos($len, $endq, $position)
  426. {
  427. if ($GLOBALS['finished']) {
  428. $endq = true;
  429. $position = $len - 1;
  430. }
  431. return array($endq, $position);
  432. }
  433. }