ExportSql.php 103 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001
  1. <?php
  2. /**
  3. * Set of functions used to build SQL dumps of tables
  4. */
  5. declare(strict_types=1);
  6. namespace PhpMyAdmin\Plugins\Export;
  7. use PhpMyAdmin\Charsets;
  8. use PhpMyAdmin\DatabaseInterface;
  9. use PhpMyAdmin\Plugins\ExportPlugin;
  10. use PhpMyAdmin\Properties\Options\Groups\OptionsPropertyMainGroup;
  11. use PhpMyAdmin\Properties\Options\Groups\OptionsPropertyRootGroup;
  12. use PhpMyAdmin\Properties\Options\Groups\OptionsPropertySubgroup;
  13. use PhpMyAdmin\Properties\Options\Items\BoolPropertyItem;
  14. use PhpMyAdmin\Properties\Options\Items\MessageOnlyPropertyItem;
  15. use PhpMyAdmin\Properties\Options\Items\NumberPropertyItem;
  16. use PhpMyAdmin\Properties\Options\Items\RadioPropertyItem;
  17. use PhpMyAdmin\Properties\Options\Items\SelectPropertyItem;
  18. use PhpMyAdmin\Properties\Options\Items\TextPropertyItem;
  19. use PhpMyAdmin\Properties\Plugins\ExportPluginProperties;
  20. use PhpMyAdmin\SqlParser\Components\CreateDefinition;
  21. use PhpMyAdmin\SqlParser\Context;
  22. use PhpMyAdmin\SqlParser\Parser;
  23. use PhpMyAdmin\SqlParser\Statements\CreateStatement;
  24. use PhpMyAdmin\SqlParser\Token;
  25. use PhpMyAdmin\Util;
  26. use const E_USER_ERROR;
  27. use const PHP_VERSION;
  28. use function bin2hex;
  29. use function count;
  30. use function defined;
  31. use function explode;
  32. use function implode;
  33. use function in_array;
  34. use function intval;
  35. use function is_array;
  36. use function mb_strlen;
  37. use function mb_strpos;
  38. use function mb_substr;
  39. use function preg_quote;
  40. use function preg_replace;
  41. use function preg_split;
  42. use function sprintf;
  43. use function str_repeat;
  44. use function str_replace;
  45. use function stripos;
  46. use function strtotime;
  47. use function strtoupper;
  48. use function trigger_error;
  49. /**
  50. * Handles the export for the SQL class
  51. */
  52. class ExportSql extends ExportPlugin
  53. {
  54. /**
  55. * Whether charset header was sent.
  56. *
  57. * @var bool
  58. */
  59. private $sentCharset = false;
  60. public function __construct()
  61. {
  62. parent::__construct();
  63. $this->setProperties();
  64. // Avoids undefined variables, use NULL so isset() returns false
  65. if (isset($GLOBALS['sql_backquotes'])) {
  66. return;
  67. }
  68. $GLOBALS['sql_backquotes'] = null;
  69. }
  70. /**
  71. * Sets the export SQL properties
  72. *
  73. * @return void
  74. */
  75. protected function setProperties()
  76. {
  77. global $plugin_param, $dbi;
  78. $hide_sql = false;
  79. $hide_structure = false;
  80. if ($plugin_param['export_type'] === 'table'
  81. && ! $plugin_param['single_table']
  82. ) {
  83. $hide_structure = true;
  84. $hide_sql = true;
  85. }
  86. // In case we have `raw_query` parameter set,
  87. // we initialize SQL option
  88. if (isset($_REQUEST['raw_query'])) {
  89. $hide_structure = false;
  90. $hide_sql = false;
  91. }
  92. if ($hide_sql) {
  93. return;
  94. }
  95. $exportPluginProperties = new ExportPluginProperties();
  96. $exportPluginProperties->setText('SQL');
  97. $exportPluginProperties->setExtension('sql');
  98. $exportPluginProperties->setMimeType('text/x-sql');
  99. $exportPluginProperties->setOptionsText(__('Options'));
  100. // create the root group that will be the options field for
  101. // $exportPluginProperties
  102. // this will be shown as "Format specific options"
  103. $exportSpecificOptions = new OptionsPropertyRootGroup(
  104. 'Format Specific Options'
  105. );
  106. // general options main group
  107. $generalOptions = new OptionsPropertyMainGroup('general_opts');
  108. // comments
  109. $subgroup = new OptionsPropertySubgroup('include_comments');
  110. $leaf = new BoolPropertyItem(
  111. 'include_comments',
  112. __(
  113. 'Display comments <i>(includes info such as export'
  114. . ' timestamp, PHP version, and server version)</i>'
  115. )
  116. );
  117. $subgroup->setSubgroupHeader($leaf);
  118. $leaf = new TextPropertyItem(
  119. 'header_comment',
  120. __('Additional custom header comment (\n splits lines):')
  121. );
  122. $subgroup->addProperty($leaf);
  123. $leaf = new BoolPropertyItem(
  124. 'dates',
  125. __(
  126. 'Include a timestamp of when databases were created, last'
  127. . ' updated, and last checked'
  128. )
  129. );
  130. $subgroup->addProperty($leaf);
  131. if (! empty($GLOBALS['cfgRelation']['relation'])) {
  132. $leaf = new BoolPropertyItem(
  133. 'relation',
  134. __('Display foreign key relationships')
  135. );
  136. $subgroup->addProperty($leaf);
  137. }
  138. if (! empty($GLOBALS['cfgRelation']['mimework'])) {
  139. $leaf = new BoolPropertyItem(
  140. 'mime',
  141. __('Display media types')
  142. );
  143. $subgroup->addProperty($leaf);
  144. }
  145. $generalOptions->addProperty($subgroup);
  146. // enclose in a transaction
  147. $leaf = new BoolPropertyItem(
  148. 'use_transaction',
  149. __('Enclose export in a transaction')
  150. );
  151. $leaf->setDoc(
  152. [
  153. 'programs',
  154. 'mysqldump',
  155. 'option_mysqldump_single-transaction',
  156. ]
  157. );
  158. $generalOptions->addProperty($leaf);
  159. // disable foreign key checks
  160. $leaf = new BoolPropertyItem(
  161. 'disable_fk',
  162. __('Disable foreign key checks')
  163. );
  164. $leaf->setDoc(
  165. [
  166. 'manual_MySQL_Database_Administration',
  167. 'server-system-variables',
  168. 'sysvar_foreign_key_checks',
  169. ]
  170. );
  171. $generalOptions->addProperty($leaf);
  172. // export views as tables
  173. $leaf = new BoolPropertyItem(
  174. 'views_as_tables',
  175. __('Export views as tables')
  176. );
  177. $generalOptions->addProperty($leaf);
  178. // export metadata
  179. $leaf = new BoolPropertyItem(
  180. 'metadata',
  181. __('Export metadata')
  182. );
  183. $generalOptions->addProperty($leaf);
  184. // compatibility maximization
  185. $compats = $dbi->getCompatibilities();
  186. if (count($compats) > 0) {
  187. $values = [];
  188. foreach ($compats as $val) {
  189. $values[$val] = $val;
  190. }
  191. $leaf = new SelectPropertyItem(
  192. 'compatibility',
  193. __(
  194. 'Database system or older MySQL server to maximize output'
  195. . ' compatibility with:'
  196. )
  197. );
  198. $leaf->setValues($values);
  199. $leaf->setDoc(
  200. [
  201. 'manual_MySQL_Database_Administration',
  202. 'Server_SQL_mode',
  203. ]
  204. );
  205. $generalOptions->addProperty($leaf);
  206. unset($values);
  207. }
  208. // what to dump (structure/data/both)
  209. $subgroup = new OptionsPropertySubgroup(
  210. 'dump_table',
  211. __('Dump table')
  212. );
  213. $leaf = new RadioPropertyItem('structure_or_data');
  214. $leaf->setValues(
  215. [
  216. 'structure' => __('structure'),
  217. 'data' => __('data'),
  218. 'structure_and_data' => __('structure and data'),
  219. ]
  220. );
  221. $subgroup->setSubgroupHeader($leaf);
  222. $generalOptions->addProperty($subgroup);
  223. // add the main group to the root group
  224. $exportSpecificOptions->addProperty($generalOptions);
  225. // structure options main group
  226. if (! $hide_structure) {
  227. $structureOptions = new OptionsPropertyMainGroup(
  228. 'structure',
  229. __('Object creation options')
  230. );
  231. $structureOptions->setForce('data');
  232. // begin SQL Statements
  233. $subgroup = new OptionsPropertySubgroup();
  234. $leaf = new MessageOnlyPropertyItem(
  235. 'add_statements',
  236. __('Add statements:')
  237. );
  238. $subgroup->setSubgroupHeader($leaf);
  239. // server export options
  240. if ($plugin_param['export_type'] === 'server') {
  241. $leaf = new BoolPropertyItem(
  242. 'drop_database',
  243. sprintf(__('Add %s statement'), '<code>DROP DATABASE IF EXISTS</code>')
  244. );
  245. $subgroup->addProperty($leaf);
  246. }
  247. if ($plugin_param['export_type'] === 'database') {
  248. $create_clause = '<code>CREATE DATABASE / USE</code>';
  249. $leaf = new BoolPropertyItem(
  250. 'create_database',
  251. sprintf(__('Add %s statement'), $create_clause)
  252. );
  253. $subgroup->addProperty($leaf);
  254. }
  255. if ($plugin_param['export_type'] === 'table') {
  256. $drop_clause = $dbi->getTable(
  257. $GLOBALS['db'],
  258. $GLOBALS['table']
  259. )->isView()
  260. ? '<code>DROP VIEW</code>'
  261. : '<code>DROP TABLE</code>';
  262. } else {
  263. $drop_clause = '<code>DROP TABLE / VIEW / PROCEDURE'
  264. . ' / FUNCTION / EVENT</code>';
  265. }
  266. $drop_clause .= '<code> / TRIGGER</code>';
  267. $leaf = new BoolPropertyItem(
  268. 'drop_table',
  269. sprintf(__('Add %s statement'), $drop_clause)
  270. );
  271. $subgroup->addProperty($leaf);
  272. $subgroup_create_table = new OptionsPropertySubgroup();
  273. // Add table structure option
  274. $leaf = new BoolPropertyItem(
  275. 'create_table',
  276. sprintf(__('Add %s statement'), '<code>CREATE TABLE</code>')
  277. );
  278. $subgroup_create_table->setSubgroupHeader($leaf);
  279. $leaf = new BoolPropertyItem(
  280. 'if_not_exists',
  281. '<code>IF NOT EXISTS</code> ' . __(
  282. '(less efficient as indexes will be generated during table '
  283. . 'creation)'
  284. )
  285. );
  286. $subgroup_create_table->addProperty($leaf);
  287. $leaf = new BoolPropertyItem(
  288. 'auto_increment',
  289. sprintf(__('%s value'), '<code>AUTO_INCREMENT</code>')
  290. );
  291. $subgroup_create_table->addProperty($leaf);
  292. $subgroup->addProperty($subgroup_create_table);
  293. // Add view option
  294. $subgroup_create_view = new OptionsPropertySubgroup();
  295. $leaf = new BoolPropertyItem(
  296. 'create_view',
  297. sprintf(__('Add %s statement'), '<code>CREATE VIEW</code>')
  298. );
  299. $subgroup_create_view->setSubgroupHeader($leaf);
  300. $leaf = new BoolPropertyItem(
  301. 'simple_view_export',
  302. /* l10n: Allow simplifying exported view syntax to only "CREATE VIEW" */
  303. __('Use simple view export')
  304. );
  305. $subgroup_create_view->addProperty($leaf);
  306. $leaf = new BoolPropertyItem(
  307. 'view_current_user',
  308. __('Exclude definition of current user')
  309. );
  310. $subgroup_create_view->addProperty($leaf);
  311. $leaf = new BoolPropertyItem(
  312. 'or_replace_view',
  313. sprintf(__('%s view'), '<code>OR REPLACE</code>')
  314. );
  315. $subgroup_create_view->addProperty($leaf);
  316. $subgroup->addProperty($subgroup_create_view);
  317. $leaf = new BoolPropertyItem(
  318. 'procedure_function',
  319. sprintf(
  320. __('Add %s statement'),
  321. '<code>CREATE PROCEDURE / FUNCTION / EVENT</code>'
  322. )
  323. );
  324. $subgroup->addProperty($leaf);
  325. // Add triggers option
  326. $leaf = new BoolPropertyItem(
  327. 'create_trigger',
  328. sprintf(__('Add %s statement'), '<code>CREATE TRIGGER</code>')
  329. );
  330. $subgroup->addProperty($leaf);
  331. $structureOptions->addProperty($subgroup);
  332. $leaf = new BoolPropertyItem(
  333. 'backquotes',
  334. __(
  335. 'Enclose table and column names with backquotes '
  336. . '<i>(Protects column and table names formed with'
  337. . ' special characters or keywords)</i>'
  338. )
  339. );
  340. $structureOptions->addProperty($leaf);
  341. // add the main group to the root group
  342. $exportSpecificOptions->addProperty($structureOptions);
  343. }
  344. // begin Data options
  345. $dataOptions = new OptionsPropertyMainGroup(
  346. 'data',
  347. __('Data creation options')
  348. );
  349. $dataOptions->setForce('structure');
  350. $leaf = new BoolPropertyItem(
  351. 'truncate',
  352. __('Truncate table before insert')
  353. );
  354. $dataOptions->addProperty($leaf);
  355. // begin SQL Statements
  356. $subgroup = new OptionsPropertySubgroup();
  357. $leaf = new MessageOnlyPropertyItem(
  358. __('Instead of <code>INSERT</code> statements, use:')
  359. );
  360. $subgroup->setSubgroupHeader($leaf);
  361. $leaf = new BoolPropertyItem(
  362. 'delayed',
  363. __('<code>INSERT DELAYED</code> statements')
  364. );
  365. $leaf->setDoc(
  366. [
  367. 'manual_MySQL_Database_Administration',
  368. 'insert_delayed',
  369. ]
  370. );
  371. $subgroup->addProperty($leaf);
  372. $leaf = new BoolPropertyItem(
  373. 'ignore',
  374. __('<code>INSERT IGNORE</code> statements')
  375. );
  376. $leaf->setDoc(
  377. [
  378. 'manual_MySQL_Database_Administration',
  379. 'insert',
  380. ]
  381. );
  382. $subgroup->addProperty($leaf);
  383. $dataOptions->addProperty($subgroup);
  384. // Function to use when dumping dat
  385. $leaf = new SelectPropertyItem(
  386. 'type',
  387. __('Function to use when dumping data:')
  388. );
  389. $leaf->setValues(
  390. [
  391. 'INSERT' => 'INSERT',
  392. 'UPDATE' => 'UPDATE',
  393. 'REPLACE' => 'REPLACE',
  394. ]
  395. );
  396. $dataOptions->addProperty($leaf);
  397. /* Syntax to use when inserting data */
  398. $subgroup = new OptionsPropertySubgroup();
  399. $leaf = new MessageOnlyPropertyItem(
  400. null,
  401. __('Syntax to use when inserting data:')
  402. );
  403. $subgroup->setSubgroupHeader($leaf);
  404. $leaf = new RadioPropertyItem(
  405. 'insert_syntax',
  406. __('<code>INSERT IGNORE</code> statements')
  407. );
  408. $leaf->setValues(
  409. [
  410. 'complete' => __(
  411. 'include column names in every <code>INSERT</code> statement'
  412. . ' <br> &nbsp; &nbsp; &nbsp; Example: <code>INSERT INTO'
  413. . ' tbl_name (col_A,col_B,col_C) VALUES (1,2,3)</code>'
  414. ),
  415. 'extended' => __(
  416. 'insert multiple rows in every <code>INSERT</code> statement'
  417. . '<br> &nbsp; &nbsp; &nbsp; Example: <code>INSERT INTO'
  418. . ' tbl_name VALUES (1,2,3), (4,5,6), (7,8,9)</code>'
  419. ),
  420. 'both' => __(
  421. 'both of the above<br> &nbsp; &nbsp; &nbsp; Example:'
  422. . ' <code>INSERT INTO tbl_name (col_A,col_B,col_C) VALUES'
  423. . ' (1,2,3), (4,5,6), (7,8,9)</code>'
  424. ),
  425. 'none' => __(
  426. 'neither of the above<br> &nbsp; &nbsp; &nbsp; Example:'
  427. . ' <code>INSERT INTO tbl_name VALUES (1,2,3)</code>'
  428. ),
  429. ]
  430. );
  431. $subgroup->addProperty($leaf);
  432. $dataOptions->addProperty($subgroup);
  433. // Max length of query
  434. $leaf = new NumberPropertyItem(
  435. 'max_query_size',
  436. __('Maximal length of created query')
  437. );
  438. $dataOptions->addProperty($leaf);
  439. // Dump binary columns in hexadecimal
  440. $leaf = new BoolPropertyItem(
  441. 'hex_for_binary',
  442. __(
  443. 'Dump binary columns in hexadecimal notation'
  444. . ' <i>(for example, "abc" becomes 0x616263)</i>'
  445. )
  446. );
  447. $dataOptions->addProperty($leaf);
  448. // Dump time in UTC
  449. $leaf = new BoolPropertyItem(
  450. 'utc_time',
  451. __(
  452. 'Dump TIMESTAMP columns in UTC <i>(enables TIMESTAMP columns'
  453. . ' to be dumped and reloaded between servers in different'
  454. . ' time zones)</i>'
  455. )
  456. );
  457. $dataOptions->addProperty($leaf);
  458. // add the main group to the root group
  459. $exportSpecificOptions->addProperty($dataOptions);
  460. // set the options for the export plugin property item
  461. $exportPluginProperties->setOptions($exportSpecificOptions);
  462. $this->properties = $exportPluginProperties;
  463. }
  464. /**
  465. * Generates SQL for routines export
  466. *
  467. * @param string $db Database
  468. * @param array $aliases Aliases of db/table/columns
  469. * @param string $type Type of exported routine
  470. * @param string $name Verbose name of exported routine
  471. * @param array $routines List of routines to export
  472. * @param string $delimiter Delimiter to use in SQL
  473. *
  474. * @return string SQL query
  475. */
  476. protected function exportRoutineSQL(
  477. $db,
  478. array $aliases,
  479. $type,
  480. $name,
  481. array $routines,
  482. $delimiter
  483. ) {
  484. global $crlf, $dbi;
  485. $text = $this->exportComment()
  486. . $this->exportComment($name)
  487. . $this->exportComment();
  488. $used_alias = false;
  489. $proc_query = '';
  490. foreach ($routines as $routine) {
  491. if (! empty($GLOBALS['sql_drop_table'])) {
  492. $proc_query .= 'DROP ' . $type . ' IF EXISTS '
  493. . Util::backquote($routine)
  494. . $delimiter . $crlf;
  495. }
  496. $create_query = $this->replaceWithAliases(
  497. $dbi->getDefinition($db, $type, $routine),
  498. $aliases,
  499. $db,
  500. '',
  501. $flag
  502. );
  503. // One warning per database
  504. if ($flag) {
  505. $used_alias = true;
  506. }
  507. $proc_query .= $create_query . $delimiter . $crlf . $crlf;
  508. }
  509. if ($used_alias) {
  510. $text .= $this->exportComment(
  511. __('It appears your database uses routines;')
  512. )
  513. . $this->exportComment(
  514. __('alias export may not work reliably in all cases.')
  515. )
  516. . $this->exportComment();
  517. }
  518. $text .= $proc_query;
  519. return $text;
  520. }
  521. /**
  522. * Exports routines (procedures and functions)
  523. *
  524. * @param string $db Database
  525. * @param array $aliases Aliases of db/table/columns
  526. *
  527. * @return bool Whether it succeeded
  528. */
  529. public function exportRoutines($db, array $aliases = [])
  530. {
  531. global $crlf, $dbi;
  532. $db_alias = $db;
  533. $this->initAlias($aliases, $db_alias);
  534. $text = '';
  535. $delimiter = '$$';
  536. $procedure_names = $dbi
  537. ->getProceduresOrFunctions($db, 'PROCEDURE');
  538. $function_names = $dbi->getProceduresOrFunctions($db, 'FUNCTION');
  539. if ($procedure_names || $function_names) {
  540. $text .= $crlf
  541. . 'DELIMITER ' . $delimiter . $crlf;
  542. if ($procedure_names) {
  543. $text .= $this->exportRoutineSQL(
  544. $db,
  545. $aliases,
  546. 'PROCEDURE',
  547. __('Procedures'),
  548. $procedure_names,
  549. $delimiter
  550. );
  551. }
  552. if ($function_names) {
  553. $text .= $this->exportRoutineSQL(
  554. $db,
  555. $aliases,
  556. 'FUNCTION',
  557. __('Functions'),
  558. $function_names,
  559. $delimiter
  560. );
  561. }
  562. $text .= 'DELIMITER ;' . $crlf;
  563. }
  564. if (! empty($text)) {
  565. return $this->export->outputHandler($text);
  566. }
  567. return false;
  568. }
  569. /**
  570. * Possibly outputs comment
  571. *
  572. * @param string $text Text of comment
  573. *
  574. * @return string The formatted comment
  575. */
  576. private function exportComment($text = '')
  577. {
  578. if (isset($GLOBALS['sql_include_comments'])
  579. && $GLOBALS['sql_include_comments']
  580. ) {
  581. // see https://dev.mysql.com/doc/refman/5.0/en/ansi-diff-comments.html
  582. if (empty($text)) {
  583. return '--' . $GLOBALS['crlf'];
  584. }
  585. $lines = preg_split("/\\r\\n|\\r|\\n/", $text);
  586. $result = [];
  587. foreach ($lines as $line) {
  588. $result[] = '-- ' . $line . $GLOBALS['crlf'];
  589. }
  590. return implode('', $result);
  591. }
  592. return '';
  593. }
  594. /**
  595. * Possibly outputs CRLF
  596. *
  597. * @return string crlf or nothing
  598. */
  599. private function possibleCRLF()
  600. {
  601. if (isset($GLOBALS['sql_include_comments'])
  602. && $GLOBALS['sql_include_comments']
  603. ) {
  604. return $GLOBALS['crlf'];
  605. }
  606. return '';
  607. }
  608. /**
  609. * Outputs export footer
  610. *
  611. * @return bool Whether it succeeded
  612. */
  613. public function exportFooter()
  614. {
  615. global $crlf, $dbi;
  616. $foot = '';
  617. if (isset($GLOBALS['sql_disable_fk'])) {
  618. $foot .= 'SET FOREIGN_KEY_CHECKS=1;' . $crlf;
  619. }
  620. if (isset($GLOBALS['sql_use_transaction'])) {
  621. $foot .= 'COMMIT;' . $crlf;
  622. }
  623. // restore connection settings
  624. if ($this->sentCharset) {
  625. $foot .= $crlf
  626. . '/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;'
  627. . $crlf
  628. . '/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;'
  629. . $crlf
  630. . '/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;'
  631. . $crlf;
  632. $this->sentCharset = false;
  633. }
  634. /* Restore timezone */
  635. if (isset($GLOBALS['sql_utc_time']) && $GLOBALS['sql_utc_time']) {
  636. $dbi->query('SET time_zone = "' . $GLOBALS['old_tz'] . '"');
  637. }
  638. return $this->export->outputHandler($foot);
  639. }
  640. /**
  641. * Outputs export header. It is the first method to be called, so all
  642. * the required variables are initialized here.
  643. *
  644. * @return bool Whether it succeeded
  645. */
  646. public function exportHeader()
  647. {
  648. global $crlf, $cfg, $dbi;
  649. if (isset($GLOBALS['sql_compatibility'])) {
  650. $tmp_compat = $GLOBALS['sql_compatibility'];
  651. if ($tmp_compat === 'NONE') {
  652. $tmp_compat = '';
  653. }
  654. $dbi->tryQuery('SET SQL_MODE="' . $tmp_compat . '"');
  655. unset($tmp_compat);
  656. }
  657. $head = $this->exportComment('phpMyAdmin SQL Dump')
  658. . $this->exportComment('version ' . PMA_VERSION)
  659. . $this->exportComment('https://www.phpmyadmin.net/')
  660. . $this->exportComment();
  661. $host_string = __('Host:') . ' ' . $cfg['Server']['host'];
  662. if (! empty($cfg['Server']['port'])) {
  663. $host_string .= ':' . $cfg['Server']['port'];
  664. }
  665. $head .= $this->exportComment($host_string);
  666. $head .= $this->exportComment(
  667. __('Generation Time:') . ' '
  668. . Util::localisedDate()
  669. )
  670. . $this->exportComment(
  671. __('Server version:') . ' ' . $dbi->getVersionString()
  672. )
  673. . $this->exportComment(__('PHP Version:') . ' ' . PHP_VERSION)
  674. . $this->possibleCRLF();
  675. if (isset($GLOBALS['sql_header_comment'])
  676. && ! empty($GLOBALS['sql_header_comment'])
  677. ) {
  678. // '\n' is not a newline (like "\n" would be), it's the characters
  679. // backslash and n, as explained on the export interface
  680. $lines = explode('\n', $GLOBALS['sql_header_comment']);
  681. $head .= $this->exportComment();
  682. foreach ($lines as $one_line) {
  683. $head .= $this->exportComment($one_line);
  684. }
  685. $head .= $this->exportComment();
  686. }
  687. if (isset($GLOBALS['sql_disable_fk'])) {
  688. $head .= 'SET FOREIGN_KEY_CHECKS=0;' . $crlf;
  689. }
  690. // We want exported AUTO_INCREMENT columns to have still same value,
  691. // do this only for recent MySQL exports
  692. if (! isset($GLOBALS['sql_compatibility'])
  693. || $GLOBALS['sql_compatibility'] === 'NONE'
  694. ) {
  695. $head .= 'SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";' . $crlf;
  696. }
  697. if (isset($GLOBALS['sql_use_transaction'])) {
  698. $head .= 'START TRANSACTION;' . $crlf;
  699. }
  700. /* Change timezone if we should export timestamps in UTC */
  701. if (isset($GLOBALS['sql_utc_time']) && $GLOBALS['sql_utc_time']) {
  702. $head .= 'SET time_zone = "+00:00";' . $crlf;
  703. $GLOBALS['old_tz'] = $dbi
  704. ->fetchValue('SELECT @@session.time_zone');
  705. $dbi->query('SET time_zone = "+00:00"');
  706. }
  707. $head .= $this->possibleCRLF();
  708. if (! empty($GLOBALS['asfile'])) {
  709. // we are saving as file, therefore we provide charset information
  710. // so that a utility like the mysql client can interpret
  711. // the file correctly
  712. if (isset($GLOBALS['charset'], Charsets::$mysqlCharsetMap[$GLOBALS['charset']])) {
  713. // we got a charset from the export dialog
  714. $set_names = Charsets::$mysqlCharsetMap[$GLOBALS['charset']];
  715. } else {
  716. // by default we use the connection charset
  717. $set_names = Charsets::$mysqlCharsetMap['utf-8'];
  718. }
  719. if ($set_names === 'utf8' && $dbi->getVersion() > 50503) {
  720. $set_names = 'utf8mb4';
  721. }
  722. $head .= $crlf
  723. . '/*!40101 SET @OLD_CHARACTER_SET_CLIENT='
  724. . '@@CHARACTER_SET_CLIENT */;' . $crlf
  725. . '/*!40101 SET @OLD_CHARACTER_SET_RESULTS='
  726. . '@@CHARACTER_SET_RESULTS */;' . $crlf
  727. . '/*!40101 SET @OLD_COLLATION_CONNECTION='
  728. . '@@COLLATION_CONNECTION */;' . $crlf
  729. . '/*!40101 SET NAMES ' . $set_names . ' */;' . $crlf . $crlf;
  730. $this->sentCharset = true;
  731. }
  732. return $this->export->outputHandler($head);
  733. }
  734. /**
  735. * Outputs CREATE DATABASE statement
  736. *
  737. * @param string $db Database name
  738. * @param string $export_type 'server', 'database', 'table'
  739. * @param string $db_alias Aliases of db
  740. *
  741. * @return bool Whether it succeeded
  742. */
  743. public function exportDBCreate($db, $export_type, $db_alias = '')
  744. {
  745. global $crlf, $dbi;
  746. if (empty($db_alias)) {
  747. $db_alias = $db;
  748. }
  749. if (isset($GLOBALS['sql_compatibility'])) {
  750. $compat = $GLOBALS['sql_compatibility'];
  751. } else {
  752. $compat = 'NONE';
  753. }
  754. if (isset($GLOBALS['sql_drop_database'])) {
  755. if (! $this->export->outputHandler(
  756. 'DROP DATABASE IF EXISTS '
  757. . Util::backquoteCompat(
  758. $db_alias,
  759. $compat,
  760. isset($GLOBALS['sql_backquotes'])
  761. )
  762. . ';' . $crlf
  763. )
  764. ) {
  765. return false;
  766. }
  767. }
  768. if ($export_type === 'database' && ! isset($GLOBALS['sql_create_database'])) {
  769. return true;
  770. }
  771. $create_query = 'CREATE DATABASE IF NOT EXISTS '
  772. . Util::backquoteCompat(
  773. $db_alias,
  774. $compat,
  775. isset($GLOBALS['sql_backquotes'])
  776. );
  777. $collation = $dbi->getDbCollation($db);
  778. if (mb_strpos($collation, '_')) {
  779. $create_query .= ' DEFAULT CHARACTER SET '
  780. . mb_substr(
  781. $collation,
  782. 0,
  783. (int) mb_strpos($collation, '_')
  784. )
  785. . ' COLLATE ' . $collation;
  786. } else {
  787. $create_query .= ' DEFAULT CHARACTER SET ' . $collation;
  788. }
  789. $create_query .= ';' . $crlf;
  790. if (! $this->export->outputHandler($create_query)) {
  791. return false;
  792. }
  793. return $this->exportUseStatement($db_alias, $compat);
  794. }
  795. /**
  796. * Outputs USE statement
  797. *
  798. * @param string $db db to use
  799. * @param string $compat sql compatibility
  800. *
  801. * @return bool Whether it succeeded
  802. */
  803. private function exportUseStatement($db, $compat)
  804. {
  805. global $crlf;
  806. if (isset($GLOBALS['sql_compatibility'])
  807. && $GLOBALS['sql_compatibility'] === 'NONE'
  808. ) {
  809. $result = $this->export->outputHandler(
  810. 'USE '
  811. . Util::backquoteCompat(
  812. $db,
  813. $compat,
  814. isset($GLOBALS['sql_backquotes'])
  815. )
  816. . ';' . $crlf
  817. );
  818. } else {
  819. $result = $this->export->outputHandler('USE ' . $db . ';' . $crlf);
  820. }
  821. return $result;
  822. }
  823. /**
  824. * Outputs database header
  825. *
  826. * @param string $db Database name
  827. * @param string $db_alias Alias of db
  828. *
  829. * @return bool Whether it succeeded
  830. */
  831. public function exportDBHeader($db, $db_alias = '')
  832. {
  833. if (empty($db_alias)) {
  834. $db_alias = $db;
  835. }
  836. if (isset($GLOBALS['sql_compatibility'])) {
  837. $compat = $GLOBALS['sql_compatibility'];
  838. } else {
  839. $compat = 'NONE';
  840. }
  841. $head = $this->exportComment()
  842. . $this->exportComment(
  843. __('Database:') . ' '
  844. . Util::backquoteCompat(
  845. $db_alias,
  846. $compat,
  847. isset($GLOBALS['sql_backquotes'])
  848. )
  849. )
  850. . $this->exportComment();
  851. return $this->export->outputHandler($head);
  852. }
  853. /**
  854. * Outputs database footer
  855. *
  856. * @param string $db Database name
  857. *
  858. * @return bool Whether it succeeded
  859. */
  860. public function exportDBFooter($db)
  861. {
  862. global $crlf;
  863. $result = true;
  864. //add indexes to the sql dump file
  865. if (isset($GLOBALS['sql_indexes'])) {
  866. $result = $this->export->outputHandler($GLOBALS['sql_indexes']);
  867. unset($GLOBALS['sql_indexes']);
  868. }
  869. //add auto increments to the sql dump file
  870. if (isset($GLOBALS['sql_auto_increments'])) {
  871. $result = $this->export->outputHandler($GLOBALS['sql_auto_increments']);
  872. unset($GLOBALS['sql_auto_increments']);
  873. }
  874. //add constraints to the sql dump file
  875. if (isset($GLOBALS['sql_constraints'])) {
  876. $result = $this->export->outputHandler($GLOBALS['sql_constraints']);
  877. unset($GLOBALS['sql_constraints']);
  878. }
  879. return $result;
  880. }
  881. /**
  882. * Exports events
  883. *
  884. * @param string $db Database
  885. *
  886. * @return bool Whether it succeeded
  887. */
  888. public function exportEvents($db)
  889. {
  890. global $crlf, $dbi;
  891. $text = '';
  892. $delimiter = '$$';
  893. $event_names = $dbi->fetchResult(
  894. 'SELECT EVENT_NAME FROM information_schema.EVENTS WHERE'
  895. . " EVENT_SCHEMA= '" . $dbi->escapeString($db)
  896. . "';"
  897. );
  898. if ($event_names) {
  899. $text .= $crlf
  900. . 'DELIMITER ' . $delimiter . $crlf;
  901. $text .= $this->exportComment()
  902. . $this->exportComment(__('Events'))
  903. . $this->exportComment();
  904. foreach ($event_names as $event_name) {
  905. if (! empty($GLOBALS['sql_drop_table'])) {
  906. $text .= 'DROP EVENT IF EXISTS '
  907. . Util::backquote($event_name)
  908. . $delimiter . $crlf;
  909. }
  910. $text .= $dbi->getDefinition($db, 'EVENT', $event_name)
  911. . $delimiter . $crlf . $crlf;
  912. }
  913. $text .= 'DELIMITER ;' . $crlf;
  914. }
  915. if (! empty($text)) {
  916. return $this->export->outputHandler($text);
  917. }
  918. return false;
  919. }
  920. /**
  921. * Exports metadata from Configuration Storage
  922. *
  923. * @param string $db database being exported
  924. * @param string|array $tables table(s) being exported
  925. * @param array $metadataTypes types of metadata to export
  926. *
  927. * @return bool Whether it succeeded
  928. */
  929. public function exportMetadata(
  930. $db,
  931. $tables,
  932. array $metadataTypes
  933. ) {
  934. $cfgRelation = $this->relation->getRelationsParam();
  935. if (! isset($cfgRelation['db'])) {
  936. return true;
  937. }
  938. $comment = $this->possibleCRLF()
  939. . $this->possibleCRLF()
  940. . $this->exportComment()
  941. . $this->exportComment(__('Metadata'))
  942. . $this->exportComment();
  943. if (! $this->export->outputHandler($comment)) {
  944. return false;
  945. }
  946. if (! $this->exportUseStatement(
  947. $cfgRelation['db'],
  948. $GLOBALS['sql_compatibility']
  949. )
  950. ) {
  951. return false;
  952. }
  953. $r = true;
  954. if (is_array($tables)) {
  955. // export metadata for each table
  956. foreach ($tables as $table) {
  957. $r &= $this->exportConfigurationMetadata($db, $table, $metadataTypes);
  958. }
  959. // export metadata for the database
  960. $r &= $this->exportConfigurationMetadata($db, null, $metadataTypes);
  961. } else {
  962. // export metadata for single table
  963. $r &= $this->exportConfigurationMetadata($db, $tables, $metadataTypes);
  964. }
  965. return (bool) $r;
  966. }
  967. /**
  968. * Exports metadata from Configuration Storage
  969. *
  970. * @param string $db database being exported
  971. * @param string|null $table table being exported
  972. * @param array $metadataTypes types of metadata to export
  973. *
  974. * @return bool Whether it succeeded
  975. */
  976. private function exportConfigurationMetadata(
  977. $db,
  978. $table,
  979. array $metadataTypes
  980. ) {
  981. global $dbi;
  982. $cfgRelation = $this->relation->getRelationsParam();
  983. if (isset($table)) {
  984. $types = [
  985. 'column_info' => 'db_name',
  986. 'table_uiprefs' => 'db_name',
  987. 'tracking' => 'db_name',
  988. ];
  989. } else {
  990. $types = [
  991. 'bookmark' => 'dbase',
  992. 'relation' => 'master_db',
  993. 'pdf_pages' => 'db_name',
  994. 'savedsearches' => 'db_name',
  995. 'central_columns' => 'db_name',
  996. ];
  997. }
  998. $aliases = [];
  999. $comment = $this->possibleCRLF()
  1000. . $this->exportComment();
  1001. if (isset($table)) {
  1002. $comment .= $this->exportComment(
  1003. sprintf(
  1004. __('Metadata for table %s'),
  1005. $table
  1006. )
  1007. );
  1008. } else {
  1009. $comment .= $this->exportComment(
  1010. sprintf(
  1011. __('Metadata for database %s'),
  1012. $db
  1013. )
  1014. );
  1015. }
  1016. $comment .= $this->exportComment();
  1017. if (! $this->export->outputHandler($comment)) {
  1018. return false;
  1019. }
  1020. foreach ($types as $type => $dbNameColumn) {
  1021. if (! in_array($type, $metadataTypes) || ! isset($cfgRelation[$type])) {
  1022. continue;
  1023. }
  1024. // special case, designer pages and their coordinates
  1025. if ($type === 'pdf_pages') {
  1026. $sql_query = 'SELECT `page_nr`, `page_descr` FROM '
  1027. . Util::backquote($cfgRelation['db'])
  1028. . '.' . Util::backquote($cfgRelation[$type])
  1029. . ' WHERE ' . Util::backquote($dbNameColumn)
  1030. . " = '" . $dbi->escapeString($db) . "'";
  1031. $result = $dbi->fetchResult(
  1032. $sql_query,
  1033. 'page_nr',
  1034. 'page_descr'
  1035. );
  1036. foreach ($result as $page => $name) {
  1037. // insert row for pdf_page
  1038. $sql_query_row = 'SELECT `db_name`, `page_descr` FROM '
  1039. . Util::backquote($cfgRelation['db'])
  1040. . '.' . Util::backquote(
  1041. $cfgRelation[$type]
  1042. )
  1043. . ' WHERE ' . Util::backquote(
  1044. $dbNameColumn
  1045. )
  1046. . " = '" . $dbi->escapeString($db) . "'"
  1047. . " AND `page_nr` = '" . intval($page) . "'";
  1048. if (! $this->exportData(
  1049. $cfgRelation['db'],
  1050. $cfgRelation[$type],
  1051. $GLOBALS['crlf'],
  1052. '',
  1053. $sql_query_row,
  1054. $aliases
  1055. )
  1056. ) {
  1057. return false;
  1058. }
  1059. $lastPage = $GLOBALS['crlf']
  1060. . 'SET @LAST_PAGE = LAST_INSERT_ID();'
  1061. . $GLOBALS['crlf'];
  1062. if (! $this->export->outputHandler($lastPage)) {
  1063. return false;
  1064. }
  1065. $sql_query_coords = 'SELECT `db_name`, `table_name`, '
  1066. . "'@LAST_PAGE' AS `pdf_page_number`, `x`, `y` FROM "
  1067. . Util::backquote($cfgRelation['db'])
  1068. . '.' . Util::backquote(
  1069. $cfgRelation['table_coords']
  1070. )
  1071. . " WHERE `pdf_page_number` = '" . $page . "'";
  1072. $GLOBALS['exporting_metadata'] = true;
  1073. if (! $this->exportData(
  1074. $cfgRelation['db'],
  1075. $cfgRelation['table_coords'],
  1076. $GLOBALS['crlf'],
  1077. '',
  1078. $sql_query_coords,
  1079. $aliases
  1080. )
  1081. ) {
  1082. $GLOBALS['exporting_metadata'] = false;
  1083. return false;
  1084. }
  1085. $GLOBALS['exporting_metadata'] = false;
  1086. }
  1087. continue;
  1088. }
  1089. // remove auto_incrementing id field for some tables
  1090. if ($type === 'bookmark') {
  1091. $sql_query = 'SELECT `dbase`, `user`, `label`, `query` FROM ';
  1092. } elseif ($type === 'column_info') {
  1093. $sql_query = 'SELECT `db_name`, `table_name`, `column_name`,'
  1094. . ' `comment`, `mimetype`, `transformation`,'
  1095. . ' `transformation_options`, `input_transformation`,'
  1096. . ' `input_transformation_options` FROM';
  1097. } elseif ($type === 'savedsearches') {
  1098. $sql_query = 'SELECT `username`, `db_name`, `search_name`,'
  1099. . ' `search_data` FROM';
  1100. } else {
  1101. $sql_query = 'SELECT * FROM ';
  1102. }
  1103. $sql_query .= Util::backquote($cfgRelation['db'])
  1104. . '.' . Util::backquote($cfgRelation[$type])
  1105. . ' WHERE ' . Util::backquote($dbNameColumn)
  1106. . " = '" . $dbi->escapeString($db) . "'";
  1107. if (isset($table)) {
  1108. $sql_query .= " AND `table_name` = '"
  1109. . $dbi->escapeString($table) . "'";
  1110. }
  1111. if (! $this->exportData(
  1112. $cfgRelation['db'],
  1113. $cfgRelation[$type],
  1114. $GLOBALS['crlf'],
  1115. '',
  1116. $sql_query,
  1117. $aliases
  1118. )
  1119. ) {
  1120. return false;
  1121. }
  1122. }
  1123. return true;
  1124. }
  1125. /**
  1126. * Returns a stand-in CREATE definition to resolve view dependencies
  1127. *
  1128. * @param string $db the database name
  1129. * @param string $view the view name
  1130. * @param string $crlf the end of line sequence
  1131. * @param array $aliases Aliases of db/table/columns
  1132. *
  1133. * @return string resulting definition
  1134. */
  1135. public function getTableDefStandIn($db, $view, $crlf, $aliases = [])
  1136. {
  1137. global $dbi;
  1138. $db_alias = $db;
  1139. $view_alias = $view;
  1140. $this->initAlias($aliases, $db_alias, $view_alias);
  1141. $create_query = '';
  1142. if (! empty($GLOBALS['sql_drop_table'])) {
  1143. $create_query .= 'DROP VIEW IF EXISTS '
  1144. . Util::backquote($view_alias)
  1145. . ';' . $crlf;
  1146. }
  1147. $create_query .= 'CREATE TABLE ';
  1148. if (isset($GLOBALS['sql_if_not_exists'])
  1149. && $GLOBALS['sql_if_not_exists']
  1150. ) {
  1151. $create_query .= 'IF NOT EXISTS ';
  1152. }
  1153. $create_query .= Util::backquote($view_alias) . ' (' . $crlf;
  1154. $tmp = [];
  1155. $columns = $dbi->getColumnsFull($db, $view);
  1156. foreach ($columns as $column_name => $definition) {
  1157. $col_alias = $column_name;
  1158. if (! empty($aliases[$db]['tables'][$view]['columns'][$col_alias])) {
  1159. $col_alias = $aliases[$db]['tables'][$view]['columns'][$col_alias];
  1160. }
  1161. $tmp[] = Util::backquote($col_alias) . ' ' .
  1162. $definition['Type'] . $crlf;
  1163. }
  1164. return $create_query . implode(',', $tmp) . ');' . $crlf;
  1165. }
  1166. /**
  1167. * Returns CREATE definition that matches $view's structure
  1168. *
  1169. * @param string $db the database name
  1170. * @param string $view the view name
  1171. * @param string $crlf the end of line sequence
  1172. * @param bool $add_semicolon whether to add semicolon and end-of-line at
  1173. * the end
  1174. * @param array $aliases Aliases of db/table/columns
  1175. *
  1176. * @return string resulting schema
  1177. */
  1178. private function getTableDefForView(
  1179. $db,
  1180. $view,
  1181. $crlf,
  1182. $add_semicolon = true,
  1183. array $aliases = []
  1184. ) {
  1185. global $dbi;
  1186. $db_alias = $db;
  1187. $view_alias = $view;
  1188. $this->initAlias($aliases, $db_alias, $view_alias);
  1189. $create_query = 'CREATE TABLE';
  1190. if (isset($GLOBALS['sql_if_not_exists'])) {
  1191. $create_query .= ' IF NOT EXISTS ';
  1192. }
  1193. $create_query .= Util::backquote($view_alias) . '(' . $crlf;
  1194. $columns = $dbi->getColumns($db, $view, null, true);
  1195. $firstCol = true;
  1196. foreach ($columns as $column) {
  1197. $col_alias = $column['Field'];
  1198. if (! empty($aliases[$db]['tables'][$view]['columns'][$col_alias])) {
  1199. $col_alias = $aliases[$db]['tables'][$view]['columns'][$col_alias];
  1200. }
  1201. $extracted_columnspec = Util::extractColumnSpec(
  1202. $column['Type']
  1203. );
  1204. if (! $firstCol) {
  1205. $create_query .= ',' . $crlf;
  1206. }
  1207. $create_query .= ' ' . Util::backquote($col_alias);
  1208. $create_query .= ' ' . $column['Type'];
  1209. if ($extracted_columnspec['can_contain_collation']
  1210. && ! empty($column['Collation'])
  1211. ) {
  1212. $create_query .= ' COLLATE ' . $column['Collation'];
  1213. }
  1214. if ($column['Null'] === 'NO') {
  1215. $create_query .= ' NOT NULL';
  1216. }
  1217. if (isset($column['Default'])) {
  1218. $create_query .= " DEFAULT '"
  1219. . $dbi->escapeString($column['Default']) . "'";
  1220. } else {
  1221. if ($column['Null'] === 'YES') {
  1222. $create_query .= ' DEFAULT NULL';
  1223. }
  1224. }
  1225. if (! empty($column['Comment'])) {
  1226. $create_query .= " COMMENT '"
  1227. . $dbi->escapeString($column['Comment']) . "'";
  1228. }
  1229. $firstCol = false;
  1230. }
  1231. $create_query .= $crlf . ')' . ($add_semicolon ? ';' : '') . $crlf;
  1232. if (isset($GLOBALS['sql_compatibility'])) {
  1233. $compat = $GLOBALS['sql_compatibility'];
  1234. } else {
  1235. $compat = 'NONE';
  1236. }
  1237. if ($compat === 'MSSQL') {
  1238. $create_query = $this->makeCreateTableMSSQLCompatible(
  1239. $create_query
  1240. );
  1241. }
  1242. return $create_query;
  1243. }
  1244. /**
  1245. * Returns $table's CREATE definition
  1246. *
  1247. * @param string $db the database name
  1248. * @param string $table the table name
  1249. * @param string $crlf the end of line sequence
  1250. * @param string $error_url the url to go back in case
  1251. * of error
  1252. * @param bool $show_dates whether to include creation/
  1253. * update/check dates
  1254. * @param bool $add_semicolon whether to add semicolon and
  1255. * end-of-line at the end
  1256. * @param bool $view whether we're handling a view
  1257. * @param bool $update_indexes_increments whether we need to update
  1258. * two global variables
  1259. * @param array $aliases Aliases of db/table/columns
  1260. *
  1261. * @return string resulting schema
  1262. */
  1263. public function getTableDef(
  1264. $db,
  1265. $table,
  1266. $crlf,
  1267. $error_url,
  1268. $show_dates = false,
  1269. $add_semicolon = true,
  1270. $view = false,
  1271. $update_indexes_increments = true,
  1272. array $aliases = []
  1273. ) {
  1274. global $sql_drop_table, $sql_backquotes, $sql_constraints,
  1275. $sql_constraints_query, $sql_indexes, $sql_indexes_query,
  1276. $sql_auto_increments, $sql_drop_foreign_keys, $dbi;
  1277. $db_alias = $db;
  1278. $table_alias = $table;
  1279. $this->initAlias($aliases, $db_alias, $table_alias);
  1280. $schema_create = '';
  1281. $auto_increment = '';
  1282. $new_crlf = $crlf;
  1283. if (isset($GLOBALS['sql_compatibility'])) {
  1284. $compat = $GLOBALS['sql_compatibility'];
  1285. } else {
  1286. $compat = 'NONE';
  1287. }
  1288. // need to use PhpMyAdmin\DatabaseInterface::QUERY_STORE
  1289. // with $dbi->numRows() in mysqli
  1290. $result = $dbi->tryQuery(
  1291. 'SHOW TABLE STATUS FROM ' . Util::backquote($db)
  1292. . ' WHERE Name = \'' . $dbi->escapeString((string) $table) . '\'',
  1293. DatabaseInterface::CONNECT_USER,
  1294. DatabaseInterface::QUERY_STORE
  1295. );
  1296. if ($result != false) {
  1297. if ($dbi->numRows($result) > 0) {
  1298. $tmpres = $dbi->fetchAssoc($result);
  1299. // Here we optionally add the AUTO_INCREMENT next value,
  1300. // but starting with MySQL 5.0.24, the clause is already included
  1301. // in SHOW CREATE TABLE so we'll remove it below
  1302. if (isset($GLOBALS['sql_auto_increment'])
  1303. && ! empty($tmpres['Auto_increment'])
  1304. ) {
  1305. $auto_increment .= ' AUTO_INCREMENT='
  1306. . $tmpres['Auto_increment'] . ' ';
  1307. }
  1308. if ($show_dates
  1309. && isset($tmpres['Create_time'])
  1310. && ! empty($tmpres['Create_time'])
  1311. ) {
  1312. $schema_create .= $this->exportComment(
  1313. __('Creation:') . ' '
  1314. . Util::localisedDate(
  1315. strtotime($tmpres['Create_time'])
  1316. )
  1317. );
  1318. $new_crlf = $this->exportComment() . $crlf;
  1319. }
  1320. if ($show_dates
  1321. && isset($tmpres['Update_time'])
  1322. && ! empty($tmpres['Update_time'])
  1323. ) {
  1324. $schema_create .= $this->exportComment(
  1325. __('Last update:') . ' '
  1326. . Util::localisedDate(
  1327. strtotime($tmpres['Update_time'])
  1328. )
  1329. );
  1330. $new_crlf = $this->exportComment() . $crlf;
  1331. }
  1332. if ($show_dates
  1333. && isset($tmpres['Check_time'])
  1334. && ! empty($tmpres['Check_time'])
  1335. ) {
  1336. $schema_create .= $this->exportComment(
  1337. __('Last check:') . ' '
  1338. . Util::localisedDate(
  1339. strtotime($tmpres['Check_time'])
  1340. )
  1341. );
  1342. $new_crlf = $this->exportComment() . $crlf;
  1343. }
  1344. }
  1345. $dbi->freeResult($result);
  1346. }
  1347. $schema_create .= $new_crlf;
  1348. if (! empty($sql_drop_table)
  1349. && $dbi->getTable($db, $table)->isView()
  1350. ) {
  1351. $schema_create .= 'DROP VIEW IF EXISTS '
  1352. . Util::backquote($table_alias, $sql_backquotes) . ';'
  1353. . $crlf;
  1354. }
  1355. // no need to generate a DROP VIEW here, it was done earlier
  1356. if (! empty($sql_drop_table)
  1357. && ! $dbi->getTable($db, $table)->isView()
  1358. ) {
  1359. $schema_create .= 'DROP TABLE IF EXISTS '
  1360. . Util::backquote($table_alias, $sql_backquotes) . ';'
  1361. . $crlf;
  1362. }
  1363. // Complete table dump,
  1364. // Whether to quote table and column names or not
  1365. if ($sql_backquotes) {
  1366. $dbi->query('SET SQL_QUOTE_SHOW_CREATE = 1');
  1367. } else {
  1368. $dbi->query('SET SQL_QUOTE_SHOW_CREATE = 0');
  1369. }
  1370. // I don't see the reason why this unbuffered query could cause problems,
  1371. // because SHOW CREATE TABLE returns only one row, and we free the
  1372. // results below. Nonetheless, we got 2 user reports about this
  1373. // (see bug 1562533) so I removed the unbuffered mode.
  1374. // $result = $dbi->query('SHOW CREATE TABLE ' . backquote($db)
  1375. // . '.' . backquote($table), null, DatabaseInterface::QUERY_UNBUFFERED);
  1376. //
  1377. // Note: SHOW CREATE TABLE, at least in MySQL 5.1.23, does not
  1378. // produce a displayable result for the default value of a BIT
  1379. // column, nor does the mysqldump command. See MySQL bug 35796
  1380. $dbi->tryQuery('USE ' . Util::backquote($db));
  1381. $result = $dbi->tryQuery(
  1382. 'SHOW CREATE TABLE ' . Util::backquote($db) . '.'
  1383. . Util::backquote($table)
  1384. );
  1385. // an error can happen, for example the table is crashed
  1386. $tmp_error = $dbi->getError();
  1387. if ($tmp_error) {
  1388. $message = sprintf(__('Error reading structure for table %s:'), $db . '.' . $table);
  1389. $message .= ' ' . $tmp_error;
  1390. if (! defined('TESTSUITE')) {
  1391. trigger_error($message, E_USER_ERROR);
  1392. }
  1393. return $this->exportComment($message);
  1394. }
  1395. // Old mode is stored so it can be restored once exporting is done.
  1396. $old_mode = Context::$MODE;
  1397. $warning = '';
  1398. $row = null;
  1399. if ($result !== false) {
  1400. $row = $dbi->fetchRow($result);
  1401. }
  1402. if ($row) {
  1403. $create_query = $row[1];
  1404. unset($row);
  1405. // Convert end of line chars to one that we want (note that MySQL
  1406. // doesn't return query it will accept in all cases)
  1407. if (mb_strpos($create_query, "(\r\n ")) {
  1408. $create_query = str_replace("\r\n", $crlf, $create_query);
  1409. } elseif (mb_strpos($create_query, "(\n ")) {
  1410. $create_query = str_replace("\n", $crlf, $create_query);
  1411. } elseif (mb_strpos($create_query, "(\r ")) {
  1412. $create_query = str_replace("\r", $crlf, $create_query);
  1413. }
  1414. /*
  1415. * Drop database name from VIEW creation.
  1416. *
  1417. * This is a bit tricky, but we need to issue SHOW CREATE TABLE with
  1418. * database name, but we don't want name to show up in CREATE VIEW
  1419. * statement.
  1420. */
  1421. if ($view) {
  1422. //TODO: use parser
  1423. $create_query = preg_replace(
  1424. '/' . preg_quote(Util::backquote($db), '/') . '\./',
  1425. '',
  1426. $create_query
  1427. );
  1428. $parser = new Parser($create_query);
  1429. /**
  1430. * `CREATE TABLE` statement.
  1431. *
  1432. * @var CreateStatement
  1433. */
  1434. $statement = $parser->statements[0];
  1435. // exclude definition of current user
  1436. if (isset($GLOBALS['sql_view_current_user'])) {
  1437. $statement->options->remove('DEFINER');
  1438. }
  1439. if (isset($GLOBALS['sql_simple_view_export'])) {
  1440. $statement->options->remove('SQL SECURITY');
  1441. $statement->options->remove('INVOKER');
  1442. $statement->options->remove('ALGORITHM');
  1443. $statement->options->remove('DEFINER');
  1444. }
  1445. $create_query = $statement->build();
  1446. // whether to replace existing view or not
  1447. if (isset($GLOBALS['sql_or_replace_view'])) {
  1448. $create_query = preg_replace(
  1449. '/^CREATE/',
  1450. 'CREATE OR REPLACE',
  1451. $create_query
  1452. );
  1453. }
  1454. }
  1455. // Substitute aliases in `CREATE` query.
  1456. $create_query = $this->replaceWithAliases(
  1457. $create_query,
  1458. $aliases,
  1459. $db,
  1460. $table,
  1461. $flag
  1462. );
  1463. // One warning per view.
  1464. if ($flag && $view) {
  1465. $warning = $this->exportComment()
  1466. . $this->exportComment(
  1467. __('It appears your database uses views;')
  1468. )
  1469. . $this->exportComment(
  1470. __('alias export may not work reliably in all cases.')
  1471. )
  1472. . $this->exportComment();
  1473. }
  1474. // Adding IF NOT EXISTS, if required.
  1475. if (isset($GLOBALS['sql_if_not_exists'])) {
  1476. $create_query = preg_replace(
  1477. '/^CREATE TABLE/',
  1478. 'CREATE TABLE IF NOT EXISTS',
  1479. $create_query
  1480. );
  1481. }
  1482. // Making the query MSSQL compatible.
  1483. if ($compat === 'MSSQL') {
  1484. $create_query = $this->makeCreateTableMSSQLCompatible(
  1485. $create_query
  1486. );
  1487. }
  1488. // Views have no constraints, indexes, etc. They do not require any
  1489. // analysis.
  1490. if (! $view) {
  1491. if (empty($sql_backquotes)) {
  1492. // Option "Enclose table and column names with backquotes"
  1493. // was checked.
  1494. Context::$MODE |= Context::SQL_MODE_NO_ENCLOSING_QUOTES;
  1495. }
  1496. // Using appropriate quotes.
  1497. if (($compat === 'MSSQL') || ($sql_backquotes === '"')) {
  1498. Context::$MODE |= Context::SQL_MODE_ANSI_QUOTES;
  1499. }
  1500. }
  1501. /**
  1502. * Parser used for analysis.
  1503. *
  1504. * @var Parser
  1505. */
  1506. $parser = new Parser($create_query);
  1507. /**
  1508. * `CREATE TABLE` statement.
  1509. *
  1510. * @var CreateStatement
  1511. */
  1512. $statement = $parser->statements[0];
  1513. if (! empty($statement->entityOptions)) {
  1514. $engine = $statement->entityOptions->has('ENGINE');
  1515. } else {
  1516. $engine = '';
  1517. }
  1518. /* Avoid operation on ARCHIVE tables as those can not be altered */
  1519. if (! empty($statement->fields) && (empty($engine) || strtoupper($engine) !== 'ARCHIVE')) {
  1520. /**
  1521. * Fragments containing definition of each constraint.
  1522. *
  1523. * @var array
  1524. */
  1525. $constraints = [];
  1526. /**
  1527. * Fragments containing definition of each index.
  1528. *
  1529. * @var array
  1530. */
  1531. $indexes = [];
  1532. /**
  1533. * Fragments containing definition of each FULLTEXT index.
  1534. *
  1535. * @var array
  1536. */
  1537. $indexes_fulltext = [];
  1538. /**
  1539. * Fragments containing definition of each foreign key that will
  1540. * be dropped.
  1541. *
  1542. * @var array
  1543. */
  1544. $dropped = [];
  1545. /**
  1546. * Fragment containing definition of the `AUTO_INCREMENT`.
  1547. *
  1548. * @var array
  1549. */
  1550. $auto_increment = [];
  1551. // Scanning each field of the `CREATE` statement to fill the arrays
  1552. // above.
  1553. // If the field is used in any of the arrays above, it is removed
  1554. // from the original definition.
  1555. // Also, AUTO_INCREMENT attribute is removed.
  1556. /** @var CreateDefinition $field */
  1557. foreach ($statement->fields as $key => $field) {
  1558. if ($field->isConstraint) {
  1559. // Creating the parts that add constraints.
  1560. $constraints[] = $field::build($field);
  1561. unset($statement->fields[$key]);
  1562. } elseif (! empty($field->key)) {
  1563. // Creating the parts that add indexes (must not be
  1564. // constraints).
  1565. if ($field->key->type === 'FULLTEXT KEY') {
  1566. $indexes_fulltext[] = $field::build($field);
  1567. unset($statement->fields[$key]);
  1568. } else {
  1569. if (empty($GLOBALS['sql_if_not_exists'])) {
  1570. $indexes[] = str_replace(
  1571. 'COMMENT=\'',
  1572. 'COMMENT \'',
  1573. $field::build($field)
  1574. );
  1575. unset($statement->fields[$key]);
  1576. }
  1577. }
  1578. }
  1579. // Creating the parts that drop foreign keys.
  1580. if (! empty($field->key)) {
  1581. if ($field->key->type === 'FOREIGN KEY') {
  1582. $dropped[] = 'FOREIGN KEY ' . Context::escape(
  1583. $field->name
  1584. );
  1585. unset($statement->fields[$key]);
  1586. }
  1587. }
  1588. // Dropping AUTO_INCREMENT.
  1589. if (empty($field->options)) {
  1590. continue;
  1591. }
  1592. if (! $field->options->has('AUTO_INCREMENT')
  1593. || ! empty($GLOBALS['sql_if_not_exists'])
  1594. ) {
  1595. continue;
  1596. }
  1597. $auto_increment[] = $field::build($field);
  1598. $field->options->remove('AUTO_INCREMENT');
  1599. }
  1600. /**
  1601. * The header of the `ALTER` statement (`ALTER TABLE tbl`).
  1602. *
  1603. * @var string
  1604. */
  1605. $alter_header = 'ALTER TABLE ' .
  1606. Util::backquoteCompat(
  1607. $table_alias,
  1608. $compat,
  1609. $sql_backquotes
  1610. );
  1611. /**
  1612. * The footer of the `ALTER` statement (usually ';')
  1613. *
  1614. * @var string
  1615. */
  1616. $alter_footer = ';' . $crlf;
  1617. // Generating constraints-related query.
  1618. if (! empty($constraints)) {
  1619. $sql_constraints_query = $alter_header . $crlf . ' ADD '
  1620. . implode(',' . $crlf . ' ADD ', $constraints)
  1621. . $alter_footer;
  1622. $sql_constraints = $this->generateComment(
  1623. $crlf,
  1624. $sql_constraints,
  1625. __('Constraints for dumped tables'),
  1626. __('Constraints for table'),
  1627. $table_alias,
  1628. $compat
  1629. ) . $sql_constraints_query;
  1630. }
  1631. // Generating indexes-related query.
  1632. $sql_indexes_query = '';
  1633. if (! empty($indexes)) {
  1634. $sql_indexes_query .= $alter_header . $crlf . ' ADD '
  1635. . implode(',' . $crlf . ' ADD ', $indexes)
  1636. . $alter_footer;
  1637. }
  1638. if (! empty($indexes_fulltext)) {
  1639. // InnoDB supports one FULLTEXT index creation at a time.
  1640. // So FULLTEXT indexes are created one-by-one after other
  1641. // indexes where created.
  1642. $sql_indexes_query .= $alter_header .
  1643. ' ADD ' . implode(
  1644. $alter_footer . $alter_header . ' ADD ',
  1645. $indexes_fulltext
  1646. ) . $alter_footer;
  1647. }
  1648. if (! empty($indexes) || ! empty($indexes_fulltext)) {
  1649. $sql_indexes = $this->generateComment(
  1650. $crlf,
  1651. $sql_indexes,
  1652. __('Indexes for dumped tables'),
  1653. __('Indexes for table'),
  1654. $table_alias,
  1655. $compat
  1656. ) . $sql_indexes_query;
  1657. }
  1658. // Generating drop foreign keys-related query.
  1659. if (! empty($dropped)) {
  1660. $sql_drop_foreign_keys = $alter_header . $crlf . ' DROP '
  1661. . implode(',' . $crlf . ' DROP ', $dropped)
  1662. . $alter_footer;
  1663. }
  1664. // Generating auto-increment-related query.
  1665. if (! empty($auto_increment) && $update_indexes_increments) {
  1666. $sql_auto_increments_query = $alter_header . $crlf . ' MODIFY '
  1667. . implode(',' . $crlf . ' MODIFY ', $auto_increment);
  1668. if (isset($GLOBALS['sql_auto_increment'])
  1669. && ($statement->entityOptions->has('AUTO_INCREMENT') !== false)
  1670. ) {
  1671. if (! isset($GLOBALS['table_data'])
  1672. || (isset($GLOBALS['table_data'])
  1673. && in_array($table, $GLOBALS['table_data']))
  1674. ) {
  1675. $sql_auto_increments_query .= ', AUTO_INCREMENT='
  1676. . $statement->entityOptions->has('AUTO_INCREMENT');
  1677. }
  1678. }
  1679. $sql_auto_increments_query .= ';' . $crlf;
  1680. $sql_auto_increments = $this->generateComment(
  1681. $crlf,
  1682. $sql_auto_increments,
  1683. __('AUTO_INCREMENT for dumped tables'),
  1684. __('AUTO_INCREMENT for table'),
  1685. $table_alias,
  1686. $compat
  1687. ) . $sql_auto_increments_query;
  1688. }
  1689. // Removing the `AUTO_INCREMENT` attribute from the `CREATE TABLE`
  1690. // too.
  1691. if (! empty($statement->entityOptions)
  1692. && (empty($GLOBALS['sql_if_not_exists'])
  1693. || empty($GLOBALS['sql_auto_increment']))
  1694. ) {
  1695. $statement->entityOptions->remove('AUTO_INCREMENT');
  1696. }
  1697. // Rebuilding the query.
  1698. $create_query = $statement->build();
  1699. }
  1700. $schema_create .= $create_query;
  1701. }
  1702. $dbi->freeResult($result);
  1703. // Restoring old mode.
  1704. Context::$MODE = $old_mode;
  1705. return $warning . $schema_create . ($add_semicolon ? ';' . $crlf : '');
  1706. }
  1707. /**
  1708. * Returns $table's comments, relations etc.
  1709. *
  1710. * @param string $db database name
  1711. * @param string $table table name
  1712. * @param string $crlf end of line sequence
  1713. * @param bool $do_relation whether to include relation comments
  1714. * @param bool $do_mime whether to include mime comments
  1715. * @param array $aliases Aliases of db/table/columns
  1716. *
  1717. * @return string resulting comments
  1718. */
  1719. private function getTableComments(
  1720. $db,
  1721. $table,
  1722. $crlf,
  1723. $do_relation = false,
  1724. $do_mime = false,
  1725. array $aliases = []
  1726. ) {
  1727. global $cfgRelation, $sql_backquotes;
  1728. $db_alias = $db;
  1729. $table_alias = $table;
  1730. $this->initAlias($aliases, $db_alias, $table_alias);
  1731. $schema_create = '';
  1732. // Check if we can use Relations
  1733. [$res_rel, $have_rel] = $this->relation->getRelationsAndStatus(
  1734. $do_relation && ! empty($cfgRelation['relation']),
  1735. $db,
  1736. $table
  1737. );
  1738. if ($do_mime && $cfgRelation['mimework']) {
  1739. $mime_map = $this->transformations->getMime($db, $table, true);
  1740. if ($mime_map === null) {
  1741. unset($mime_map);
  1742. }
  1743. }
  1744. if (isset($mime_map) && count($mime_map) > 0) {
  1745. $schema_create .= $this->possibleCRLF()
  1746. . $this->exportComment()
  1747. . $this->exportComment(
  1748. __('MEDIA TYPES FOR TABLE') . ' '
  1749. . Util::backquote($table, $sql_backquotes) . ':'
  1750. );
  1751. foreach ($mime_map as $mime_field => $mime) {
  1752. $schema_create .= $this->exportComment(
  1753. ' '
  1754. . Util::backquote($mime_field, $sql_backquotes)
  1755. )
  1756. . $this->exportComment(
  1757. ' '
  1758. . Util::backquote(
  1759. $mime['mimetype'],
  1760. $sql_backquotes
  1761. )
  1762. );
  1763. }
  1764. $schema_create .= $this->exportComment();
  1765. }
  1766. if ($have_rel) {
  1767. $schema_create .= $this->possibleCRLF()
  1768. . $this->exportComment()
  1769. . $this->exportComment(
  1770. __('RELATIONSHIPS FOR TABLE') . ' '
  1771. . Util::backquote($table_alias, $sql_backquotes)
  1772. . ':'
  1773. );
  1774. foreach ($res_rel as $rel_field => $rel) {
  1775. if ($rel_field !== 'foreign_keys_data') {
  1776. $rel_field_alias = ! empty(
  1777. $aliases[$db]['tables'][$table]['columns'][$rel_field]
  1778. ) ? $aliases[$db]['tables'][$table]['columns'][$rel_field]
  1779. : $rel_field;
  1780. $schema_create .= $this->exportComment(
  1781. ' '
  1782. . Util::backquote(
  1783. $rel_field_alias,
  1784. $sql_backquotes
  1785. )
  1786. )
  1787. . $this->exportComment(
  1788. ' '
  1789. . Util::backquote(
  1790. $rel['foreign_table'],
  1791. $sql_backquotes
  1792. )
  1793. . ' -> '
  1794. . Util::backquote(
  1795. $rel['foreign_field'],
  1796. $sql_backquotes
  1797. )
  1798. );
  1799. } else {
  1800. foreach ($rel as $one_key) {
  1801. foreach ($one_key['index_list'] as $index => $field) {
  1802. $rel_field_alias = ! empty(
  1803. $aliases[$db]['tables'][$table]['columns'][$field]
  1804. ) ? $aliases[$db]['tables'][$table]['columns'][$field]
  1805. : $field;
  1806. $schema_create .= $this->exportComment(
  1807. ' '
  1808. . Util::backquote(
  1809. $rel_field_alias,
  1810. $sql_backquotes
  1811. )
  1812. )
  1813. . $this->exportComment(
  1814. ' '
  1815. . Util::backquote(
  1816. $one_key['ref_table_name'],
  1817. $sql_backquotes
  1818. )
  1819. . ' -> '
  1820. . Util::backquote(
  1821. $one_key['ref_index_list'][$index],
  1822. $sql_backquotes
  1823. )
  1824. );
  1825. }
  1826. }
  1827. }
  1828. }
  1829. $schema_create .= $this->exportComment();
  1830. }
  1831. return $schema_create;
  1832. }
  1833. /**
  1834. * Outputs a raw query
  1835. *
  1836. * @param string $err_url the url to go back in case of error
  1837. * @param string $sql_query the rawquery to output
  1838. * @param string $crlf the seperator for a file
  1839. *
  1840. * @return bool if succeeded
  1841. */
  1842. public function exportRawQuery(string $err_url, string $sql_query, string $crlf): bool
  1843. {
  1844. return $this->export->outputHandler($sql_query);
  1845. }
  1846. /**
  1847. * Outputs table's structure
  1848. *
  1849. * @param string $db database name
  1850. * @param string $table table name
  1851. * @param string $crlf the end of line sequence
  1852. * @param string $error_url the url to go back in case of error
  1853. * @param string $export_mode 'create_table','triggers','create_view',
  1854. * 'stand_in'
  1855. * @param string $export_type 'server', 'database', 'table'
  1856. * @param bool $relation whether to include relation comments
  1857. * @param bool $comments whether to include the pmadb-style column
  1858. * comments as comments in the structure; this is
  1859. * deprecated but the parameter is left here
  1860. * because /export calls exportStructure()
  1861. * also for other export types which use this
  1862. * parameter
  1863. * @param bool $mime whether to include mime comments
  1864. * @param bool $dates whether to include creation/update/check dates
  1865. * @param array $aliases Aliases of db/table/columns
  1866. *
  1867. * @return bool Whether it succeeded
  1868. */
  1869. public function exportStructure(
  1870. $db,
  1871. $table,
  1872. $crlf,
  1873. $error_url,
  1874. $export_mode,
  1875. $export_type,
  1876. $relation = false,
  1877. $comments = false,
  1878. $mime = false,
  1879. $dates = false,
  1880. array $aliases = []
  1881. ) {
  1882. global $dbi;
  1883. $db_alias = $db;
  1884. $table_alias = $table;
  1885. $this->initAlias($aliases, $db_alias, $table_alias);
  1886. if (isset($GLOBALS['sql_compatibility'])) {
  1887. $compat = $GLOBALS['sql_compatibility'];
  1888. } else {
  1889. $compat = 'NONE';
  1890. }
  1891. $formatted_table_name = Util::backquoteCompat(
  1892. $table_alias,
  1893. $compat,
  1894. isset($GLOBALS['sql_backquotes'])
  1895. );
  1896. $dump = $this->possibleCRLF()
  1897. . $this->exportComment(str_repeat('-', 56))
  1898. . $this->possibleCRLF()
  1899. . $this->exportComment();
  1900. switch ($export_mode) {
  1901. case 'create_table':
  1902. $dump .= $this->exportComment(
  1903. __('Table structure for table') . ' ' . $formatted_table_name
  1904. );
  1905. $dump .= $this->exportComment();
  1906. $dump .= $this->getTableDef(
  1907. $db,
  1908. $table,
  1909. $crlf,
  1910. $error_url,
  1911. $dates,
  1912. true,
  1913. false,
  1914. true,
  1915. $aliases
  1916. );
  1917. $dump .= $this->getTableComments(
  1918. $db,
  1919. $table,
  1920. $crlf,
  1921. $relation,
  1922. $mime,
  1923. $aliases
  1924. );
  1925. break;
  1926. case 'triggers':
  1927. $dump = '';
  1928. $delimiter = '$$';
  1929. $triggers = $dbi->getTriggers($db, $table, $delimiter);
  1930. if ($triggers) {
  1931. $dump .= $this->possibleCRLF()
  1932. . $this->exportComment()
  1933. . $this->exportComment(
  1934. __('Triggers') . ' ' . $formatted_table_name
  1935. )
  1936. . $this->exportComment();
  1937. $used_alias = false;
  1938. $trigger_query = '';
  1939. foreach ($triggers as $trigger) {
  1940. if (! empty($GLOBALS['sql_drop_table'])) {
  1941. $trigger_query .= $trigger['drop'] . ';' . $crlf;
  1942. }
  1943. $trigger_query .= 'DELIMITER ' . $delimiter . $crlf;
  1944. $trigger_query .= $this->replaceWithAliases(
  1945. $trigger['create'],
  1946. $aliases,
  1947. $db,
  1948. $table,
  1949. $flag
  1950. );
  1951. if ($flag) {
  1952. $used_alias = true;
  1953. }
  1954. $trigger_query .= 'DELIMITER ;' . $crlf;
  1955. }
  1956. // One warning per table.
  1957. if ($used_alias) {
  1958. $dump .= $this->exportComment(
  1959. __('It appears your table uses triggers;')
  1960. )
  1961. . $this->exportComment(
  1962. __('alias export may not work reliably in all cases.')
  1963. )
  1964. . $this->exportComment();
  1965. }
  1966. $dump .= $trigger_query;
  1967. }
  1968. break;
  1969. case 'create_view':
  1970. if (empty($GLOBALS['sql_views_as_tables'])) {
  1971. $dump .= $this->exportComment(
  1972. __('Structure for view')
  1973. . ' '
  1974. . $formatted_table_name
  1975. )
  1976. . $this->exportComment();
  1977. // delete the stand-in table previously created (if any)
  1978. if ($export_type !== 'table') {
  1979. $dump .= 'DROP TABLE IF EXISTS '
  1980. . Util::backquote($table_alias) . ';' . $crlf;
  1981. }
  1982. $dump .= $this->getTableDef(
  1983. $db,
  1984. $table,
  1985. $crlf,
  1986. $error_url,
  1987. $dates,
  1988. true,
  1989. true,
  1990. true,
  1991. $aliases
  1992. );
  1993. } else {
  1994. $dump .= $this->exportComment(
  1995. sprintf(
  1996. __('Structure for view %s exported as a table'),
  1997. $formatted_table_name
  1998. )
  1999. )
  2000. . $this->exportComment();
  2001. // delete the stand-in table previously created (if any)
  2002. if ($export_type !== 'table') {
  2003. $dump .= 'DROP TABLE IF EXISTS '
  2004. . Util::backquote($table_alias) . ';' . $crlf;
  2005. }
  2006. $dump .= $this->getTableDefForView(
  2007. $db,
  2008. $table,
  2009. $crlf,
  2010. true,
  2011. $aliases
  2012. );
  2013. }
  2014. break;
  2015. case 'stand_in':
  2016. $dump .= $this->exportComment(
  2017. __('Stand-in structure for view') . ' ' . $formatted_table_name
  2018. )
  2019. . $this->exportComment(
  2020. __('(See below for the actual view)')
  2021. )
  2022. . $this->exportComment();
  2023. // export a stand-in definition to resolve view dependencies
  2024. $dump .= $this->getTableDefStandIn($db, $table, $crlf, $aliases);
  2025. }
  2026. // this one is built by getTableDef() to use in table copy/move
  2027. // but not in the case of export
  2028. unset($GLOBALS['sql_constraints_query']);
  2029. return $this->export->outputHandler($dump);
  2030. }
  2031. /**
  2032. * Outputs the content of a table in SQL format
  2033. *
  2034. * @param string $db database name
  2035. * @param string $table table name
  2036. * @param string $crlf the end of line sequence
  2037. * @param string $error_url the url to go back in case of error
  2038. * @param string $sql_query SQL query for obtaining data
  2039. * @param array $aliases Aliases of db/table/columns
  2040. *
  2041. * @return bool Whether it succeeded
  2042. */
  2043. public function exportData(
  2044. $db,
  2045. $table,
  2046. $crlf,
  2047. $error_url,
  2048. $sql_query,
  2049. array $aliases = []
  2050. ) {
  2051. global $current_row, $sql_backquotes, $dbi;
  2052. // Do not export data for merge tables
  2053. if ($dbi->getTable($db, $table)->isMerge()) {
  2054. return true;
  2055. }
  2056. $db_alias = $db;
  2057. $table_alias = $table;
  2058. $this->initAlias($aliases, $db_alias, $table_alias);
  2059. if (isset($GLOBALS['sql_compatibility'])) {
  2060. $compat = $GLOBALS['sql_compatibility'];
  2061. } else {
  2062. $compat = 'NONE';
  2063. }
  2064. $formatted_table_name = Util::backquoteCompat(
  2065. $table_alias,
  2066. $compat,
  2067. $sql_backquotes
  2068. );
  2069. // Do not export data for a VIEW, unless asked to export the view as a table
  2070. // (For a VIEW, this is called only when exporting a single VIEW)
  2071. if ($dbi->getTable($db, $table)->isView()
  2072. && empty($GLOBALS['sql_views_as_tables'])
  2073. ) {
  2074. $head = $this->possibleCRLF()
  2075. . $this->exportComment()
  2076. . $this->exportComment('VIEW ' . $formatted_table_name)
  2077. . $this->exportComment(__('Data:') . ' ' . __('None'))
  2078. . $this->exportComment()
  2079. . $this->possibleCRLF();
  2080. return $this->export->outputHandler($head);
  2081. }
  2082. $result = $dbi->tryQuery(
  2083. $sql_query,
  2084. DatabaseInterface::CONNECT_USER,
  2085. DatabaseInterface::QUERY_UNBUFFERED
  2086. );
  2087. // a possible error: the table has crashed
  2088. $tmp_error = $dbi->getError();
  2089. if ($tmp_error) {
  2090. $message = sprintf(__('Error reading data for table %s:'), $db . '.' . $table);
  2091. $message .= ' ' . $tmp_error;
  2092. if (! defined('TESTSUITE')) {
  2093. trigger_error($message, E_USER_ERROR);
  2094. }
  2095. return $this->export->outputHandler(
  2096. $this->exportComment($message)
  2097. );
  2098. }
  2099. if ($result == false) {
  2100. $dbi->freeResult($result);
  2101. return true;
  2102. }
  2103. $fields_cnt = $dbi->numFields($result);
  2104. // Get field information
  2105. $fields_meta = $dbi->getFieldsMeta($result);
  2106. $field_flags = [];
  2107. for ($j = 0; $j < $fields_cnt; $j++) {
  2108. $field_flags[$j] = $dbi->fieldFlags($result, $j);
  2109. }
  2110. $field_set = [];
  2111. for ($j = 0; $j < $fields_cnt; $j++) {
  2112. $col_as = $fields_meta[$j]->name;
  2113. if (! empty($aliases[$db]['tables'][$table]['columns'][$col_as])) {
  2114. $col_as = $aliases[$db]['tables'][$table]['columns'][$col_as];
  2115. }
  2116. $field_set[$j] = Util::backquoteCompat(
  2117. $col_as,
  2118. $compat,
  2119. $sql_backquotes
  2120. );
  2121. }
  2122. if (isset($GLOBALS['sql_type'])
  2123. && $GLOBALS['sql_type'] === 'UPDATE'
  2124. ) {
  2125. // update
  2126. $schema_insert = 'UPDATE ';
  2127. if (isset($GLOBALS['sql_ignore'])) {
  2128. $schema_insert .= 'IGNORE ';
  2129. }
  2130. // avoid EOL blank
  2131. $schema_insert .= Util::backquoteCompat(
  2132. $table_alias,
  2133. $compat,
  2134. $sql_backquotes
  2135. ) . ' SET';
  2136. } else {
  2137. // insert or replace
  2138. if (isset($GLOBALS['sql_type'])
  2139. && $GLOBALS['sql_type'] === 'REPLACE'
  2140. ) {
  2141. $sql_command = 'REPLACE';
  2142. } else {
  2143. $sql_command = 'INSERT';
  2144. }
  2145. // delayed inserts?
  2146. if (isset($GLOBALS['sql_delayed'])) {
  2147. $insert_delayed = ' DELAYED';
  2148. } else {
  2149. $insert_delayed = '';
  2150. }
  2151. // insert ignore?
  2152. if (isset($GLOBALS['sql_type'], $GLOBALS['sql_ignore']) && $GLOBALS['sql_type'] === 'INSERT') {
  2153. $insert_delayed .= ' IGNORE';
  2154. }
  2155. //truncate table before insert
  2156. if (isset($GLOBALS['sql_truncate'])
  2157. && $GLOBALS['sql_truncate']
  2158. && $sql_command === 'INSERT'
  2159. ) {
  2160. $truncate = 'TRUNCATE TABLE '
  2161. . Util::backquoteCompat(
  2162. $table_alias,
  2163. $compat,
  2164. $sql_backquotes
  2165. ) . ';';
  2166. $truncatehead = $this->possibleCRLF()
  2167. . $this->exportComment()
  2168. . $this->exportComment(
  2169. __('Truncate table before insert') . ' '
  2170. . $formatted_table_name
  2171. )
  2172. . $this->exportComment()
  2173. . $crlf;
  2174. $this->export->outputHandler($truncatehead);
  2175. $this->export->outputHandler($truncate);
  2176. }
  2177. // scheme for inserting fields
  2178. if ($GLOBALS['sql_insert_syntax'] === 'complete'
  2179. || $GLOBALS['sql_insert_syntax'] === 'both'
  2180. ) {
  2181. $fields = implode(', ', $field_set);
  2182. $schema_insert = $sql_command . $insert_delayed . ' INTO '
  2183. . Util::backquoteCompat(
  2184. $table_alias,
  2185. $compat,
  2186. $sql_backquotes
  2187. )
  2188. // avoid EOL blank
  2189. . ' (' . $fields . ') VALUES';
  2190. } else {
  2191. $schema_insert = $sql_command . $insert_delayed . ' INTO '
  2192. . Util::backquoteCompat(
  2193. $table_alias,
  2194. $compat,
  2195. $sql_backquotes
  2196. )
  2197. . ' VALUES';
  2198. }
  2199. }
  2200. //\x08\\x09, not required
  2201. $current_row = 0;
  2202. $query_size = 0;
  2203. if (($GLOBALS['sql_insert_syntax'] === 'extended'
  2204. || $GLOBALS['sql_insert_syntax'] === 'both')
  2205. && (! isset($GLOBALS['sql_type'])
  2206. || $GLOBALS['sql_type'] !== 'UPDATE')
  2207. ) {
  2208. $separator = ',';
  2209. $schema_insert .= $crlf;
  2210. } else {
  2211. $separator = ';';
  2212. }
  2213. while ($row = $dbi->fetchRow($result)) {
  2214. if ($current_row == 0) {
  2215. $head = $this->possibleCRLF()
  2216. . $this->exportComment()
  2217. . $this->exportComment(
  2218. __('Dumping data for table') . ' '
  2219. . $formatted_table_name
  2220. )
  2221. . $this->exportComment()
  2222. . $crlf;
  2223. if (! $this->export->outputHandler($head)) {
  2224. return false;
  2225. }
  2226. }
  2227. // We need to SET IDENTITY_INSERT ON for MSSQL
  2228. if (isset($GLOBALS['sql_compatibility'])
  2229. && $GLOBALS['sql_compatibility'] === 'MSSQL'
  2230. && $current_row == 0
  2231. ) {
  2232. if (! $this->export->outputHandler(
  2233. 'SET IDENTITY_INSERT '
  2234. . Util::backquoteCompat(
  2235. $table_alias,
  2236. $compat,
  2237. $sql_backquotes
  2238. )
  2239. . ' ON ;' . $crlf
  2240. )
  2241. ) {
  2242. return false;
  2243. }
  2244. }
  2245. $current_row++;
  2246. $values = [];
  2247. for ($j = 0; $j < $fields_cnt; $j++) {
  2248. // NULL
  2249. if (! isset($row[$j]) || $row[$j] === null) {
  2250. $values[] = 'NULL';
  2251. } elseif ($fields_meta[$j]->numeric
  2252. && $fields_meta[$j]->type !== 'timestamp'
  2253. && ! $fields_meta[$j]->blob
  2254. ) {
  2255. // a number
  2256. // timestamp is numeric on some MySQL 4.1, BLOBs are
  2257. // sometimes numeric
  2258. $values[] = $row[$j];
  2259. } elseif (stripos($field_flags[$j], 'BINARY') !== false
  2260. && isset($GLOBALS['sql_hex_for_binary'])
  2261. ) {
  2262. // a true BLOB
  2263. // - mysqldump only generates hex data when the --hex-blob
  2264. // option is used, for fields having the binary attribute
  2265. // no hex is generated
  2266. // - a TEXT field returns type blob but a real blob
  2267. // returns also the 'binary' flag
  2268. // empty blobs need to be different, but '0' is also empty
  2269. // :-(
  2270. if (empty($row[$j]) && $row[$j] != '0') {
  2271. $values[] = '\'\'';
  2272. } else {
  2273. $values[] = '0x' . bin2hex($row[$j]);
  2274. }
  2275. } elseif ($fields_meta[$j]->type === 'bit') {
  2276. // detection of 'bit' works only on mysqli extension
  2277. $values[] = "b'" . $dbi->escapeString(
  2278. Util::printableBitValue(
  2279. (int) $row[$j],
  2280. (int) $fields_meta[$j]->length
  2281. )
  2282. )
  2283. . "'";
  2284. } elseif ($fields_meta[$j]->type === 'geometry') {
  2285. // export GIS types as hex
  2286. $values[] = '0x' . bin2hex($row[$j]);
  2287. } elseif (! empty($GLOBALS['exporting_metadata'])
  2288. && $row[$j] === '@LAST_PAGE'
  2289. ) {
  2290. $values[] = '@LAST_PAGE';
  2291. } else {
  2292. // something else -> treat as a string
  2293. $values[] = '\''
  2294. . $dbi->escapeString($row[$j])
  2295. . '\'';
  2296. }
  2297. }
  2298. // should we make update?
  2299. if (isset($GLOBALS['sql_type'])
  2300. && $GLOBALS['sql_type'] === 'UPDATE'
  2301. ) {
  2302. $insert_line = $schema_insert;
  2303. for ($i = 0; $i < $fields_cnt; $i++) {
  2304. if ($i == 0) {
  2305. $insert_line .= ' ';
  2306. }
  2307. if ($i > 0) {
  2308. // avoid EOL blank
  2309. $insert_line .= ',';
  2310. }
  2311. $insert_line .= $field_set[$i] . ' = ' . $values[$i];
  2312. }
  2313. [$tmp_unique_condition, $tmp_clause_is_unique] = Util::getUniqueCondition(
  2314. $result,
  2315. $fields_cnt,
  2316. $fields_meta,
  2317. $row
  2318. );
  2319. $insert_line .= ' WHERE ' . $tmp_unique_condition;
  2320. unset($tmp_unique_condition, $tmp_clause_is_unique);
  2321. } else {
  2322. // Extended inserts case
  2323. if ($GLOBALS['sql_insert_syntax'] === 'extended'
  2324. || $GLOBALS['sql_insert_syntax'] === 'both'
  2325. ) {
  2326. if ($current_row == 1) {
  2327. $insert_line = $schema_insert . '('
  2328. . implode(', ', $values) . ')';
  2329. } else {
  2330. $insert_line = '(' . implode(', ', $values) . ')';
  2331. $insertLineSize = mb_strlen($insert_line);
  2332. $sql_max_size = $GLOBALS['sql_max_query_size'];
  2333. if (isset($sql_max_size)
  2334. && $sql_max_size > 0
  2335. && $query_size + $insertLineSize > $sql_max_size
  2336. ) {
  2337. if (! $this->export->outputHandler(';' . $crlf)) {
  2338. return false;
  2339. }
  2340. $query_size = 0;
  2341. $current_row = 1;
  2342. $insert_line = $schema_insert . $insert_line;
  2343. }
  2344. }
  2345. $query_size += mb_strlen($insert_line);
  2346. // Other inserts case
  2347. } else {
  2348. $insert_line = $schema_insert
  2349. . '(' . implode(', ', $values) . ')';
  2350. }
  2351. }
  2352. unset($values);
  2353. if (! $this->export->outputHandler(
  2354. ($current_row == 1 ? '' : $separator . $crlf)
  2355. . $insert_line
  2356. )
  2357. ) {
  2358. return false;
  2359. }
  2360. }
  2361. if ($current_row > 0) {
  2362. if (! $this->export->outputHandler(';' . $crlf)) {
  2363. return false;
  2364. }
  2365. }
  2366. // We need to SET IDENTITY_INSERT OFF for MSSQL
  2367. if (isset($GLOBALS['sql_compatibility'])
  2368. && $GLOBALS['sql_compatibility'] === 'MSSQL'
  2369. && $current_row > 0
  2370. ) {
  2371. $outputSucceeded = $this->export->outputHandler(
  2372. $crlf . 'SET IDENTITY_INSERT '
  2373. . Util::backquoteCompat(
  2374. $table_alias,
  2375. $compat,
  2376. $sql_backquotes
  2377. )
  2378. . ' OFF;' . $crlf
  2379. );
  2380. if (! $outputSucceeded) {
  2381. return false;
  2382. }
  2383. }
  2384. $dbi->freeResult($result);
  2385. return true;
  2386. }
  2387. /**
  2388. * Make a create table statement compatible with MSSQL
  2389. *
  2390. * @param string $create_query MySQL create table statement
  2391. *
  2392. * @return string MSSQL compatible create table statement
  2393. */
  2394. private function makeCreateTableMSSQLCompatible($create_query)
  2395. {
  2396. // In MSSQL
  2397. // 1. No 'IF NOT EXISTS' in CREATE TABLE
  2398. // 2. DATE field doesn't exists, we will use DATETIME instead
  2399. // 3. UNSIGNED attribute doesn't exist
  2400. // 4. No length on INT, TINYINT, SMALLINT, BIGINT and no precision on
  2401. // FLOAT fields
  2402. // 5. No KEY and INDEX inside CREATE TABLE
  2403. // 6. DOUBLE field doesn't exists, we will use FLOAT instead
  2404. $create_query = (string) preg_replace(
  2405. '/^CREATE TABLE IF NOT EXISTS/',
  2406. 'CREATE TABLE',
  2407. (string) $create_query
  2408. );
  2409. // first we need to replace all lines ended with '" DATE ...,\n'
  2410. // last preg_replace preserve us from situation with date text
  2411. // inside DEFAULT field value
  2412. $create_query = (string) preg_replace(
  2413. "/\" date DEFAULT NULL(,)?\n/",
  2414. '" datetime DEFAULT NULL$1' . "\n",
  2415. $create_query
  2416. );
  2417. $create_query = (string) preg_replace(
  2418. "/\" date NOT NULL(,)?\n/",
  2419. '" datetime NOT NULL$1' . "\n",
  2420. $create_query
  2421. );
  2422. $create_query = (string) preg_replace(
  2423. '/" date NOT NULL DEFAULT \'([^\'])/',
  2424. '" datetime NOT NULL DEFAULT \'$1',
  2425. $create_query
  2426. );
  2427. // next we need to replace all lines ended with ') UNSIGNED ...,'
  2428. // last preg_replace preserve us from situation with unsigned text
  2429. // inside DEFAULT field value
  2430. $create_query = (string) preg_replace(
  2431. "/\) unsigned NOT NULL(,)?\n/",
  2432. ') NOT NULL$1' . "\n",
  2433. $create_query
  2434. );
  2435. $create_query = (string) preg_replace(
  2436. "/\) unsigned DEFAULT NULL(,)?\n/",
  2437. ') DEFAULT NULL$1' . "\n",
  2438. $create_query
  2439. );
  2440. $create_query = (string) preg_replace(
  2441. '/\) unsigned NOT NULL DEFAULT \'([^\'])/',
  2442. ') NOT NULL DEFAULT \'$1',
  2443. $create_query
  2444. );
  2445. // we need to replace all lines ended with
  2446. // '" INT|TINYINT([0-9]{1,}) ...,' last preg_replace preserve us
  2447. // from situation with int([0-9]{1,}) text inside DEFAULT field
  2448. // value
  2449. $create_query = (string) preg_replace(
  2450. '/" (int|tinyint|smallint|bigint)\([0-9]+\) DEFAULT NULL(,)?\n/',
  2451. '" $1 DEFAULT NULL$2' . "\n",
  2452. $create_query
  2453. );
  2454. $create_query = (string) preg_replace(
  2455. '/" (int|tinyint|smallint|bigint)\([0-9]+\) NOT NULL(,)?\n/',
  2456. '" $1 NOT NULL$2' . "\n",
  2457. $create_query
  2458. );
  2459. $create_query = (string) preg_replace(
  2460. '/" (int|tinyint|smallint|bigint)\([0-9]+\) NOT NULL DEFAULT \'([^\'])/',
  2461. '" $1 NOT NULL DEFAULT \'$2',
  2462. $create_query
  2463. );
  2464. // we need to replace all lines ended with
  2465. // '" FLOAT|DOUBLE([0-9,]{1,}) ...,'
  2466. // last preg_replace preserve us from situation with
  2467. // float([0-9,]{1,}) text inside DEFAULT field value
  2468. $create_query = (string) preg_replace(
  2469. '/" (float|double)(\([0-9]+,[0-9,]+\))? DEFAULT NULL(,)?\n/',
  2470. '" float DEFAULT NULL$3' . "\n",
  2471. $create_query
  2472. );
  2473. $create_query = (string) preg_replace(
  2474. '/" (float|double)(\([0-9,]+,[0-9,]+\))? NOT NULL(,)?\n/',
  2475. '" float NOT NULL$3' . "\n",
  2476. $create_query
  2477. );
  2478. return (string) preg_replace(
  2479. '/" (float|double)(\([0-9,]+,[0-9,]+\))? NOT NULL DEFAULT \'([^\'])/',
  2480. '" float NOT NULL DEFAULT \'$3',
  2481. $create_query
  2482. );
  2483. // @todo remove indexes from CREATE TABLE
  2484. }
  2485. /**
  2486. * replaces db/table/column names with their aliases
  2487. *
  2488. * @param string $sql_query SQL query in which aliases are to be substituted
  2489. * @param array $aliases Alias information for db/table/column
  2490. * @param string $db the database name
  2491. * @param string $table the tablename
  2492. * @param string $flag the flag denoting whether any replacement was done
  2493. *
  2494. * @return string query replaced with aliases
  2495. */
  2496. public function replaceWithAliases(
  2497. $sql_query,
  2498. array $aliases,
  2499. $db,
  2500. $table = '',
  2501. &$flag = null
  2502. ) {
  2503. $flag = false;
  2504. /**
  2505. * The parser of this query.
  2506. *
  2507. * @var Parser $parser
  2508. */
  2509. $parser = new Parser($sql_query);
  2510. if (empty($parser->statements[0])) {
  2511. return $sql_query;
  2512. }
  2513. /**
  2514. * The statement that represents the query.
  2515. *
  2516. * @var CreateStatement $statement
  2517. */
  2518. $statement = $parser->statements[0];
  2519. /**
  2520. * Old database name.
  2521. *
  2522. * @var string $old_database
  2523. */
  2524. $old_database = $db;
  2525. // Replacing aliases in `CREATE TABLE` statement.
  2526. if ($statement->options->has('TABLE')) {
  2527. // Extracting the name of the old database and table from the
  2528. // statement to make sure the parameters are correct.
  2529. if (! empty($statement->name->database)) {
  2530. $old_database = $statement->name->database;
  2531. }
  2532. /**
  2533. * Old table name.
  2534. *
  2535. * @var string $old_table
  2536. */
  2537. $old_table = $statement->name->table;
  2538. // Finding the aliased database name.
  2539. // The database might be empty so we have to add a few checks.
  2540. $new_database = null;
  2541. if (! empty($statement->name->database)) {
  2542. $new_database = $statement->name->database;
  2543. if (! empty($aliases[$old_database]['alias'])) {
  2544. $new_database = $aliases[$old_database]['alias'];
  2545. }
  2546. }
  2547. // Finding the aliases table name.
  2548. $new_table = $old_table;
  2549. if (! empty($aliases[$old_database]['tables'][$old_table]['alias'])) {
  2550. $new_table = $aliases[$old_database]['tables'][$old_table]['alias'];
  2551. }
  2552. // Replacing new values.
  2553. if (($statement->name->database !== $new_database)
  2554. || ($statement->name->table !== $new_table)
  2555. ) {
  2556. $statement->name->database = $new_database;
  2557. $statement->name->table = $new_table;
  2558. $statement->name->expr = ''; // Force rebuild.
  2559. $flag = true;
  2560. }
  2561. /** @var CreateDefinition $field */
  2562. foreach ($statement->fields as $field) {
  2563. // Column name.
  2564. if (! empty($field->type)) {
  2565. if (! empty($aliases[$old_database]['tables'][$old_table]['columns'][$field->name])) {
  2566. $field->name = $aliases[$old_database]['tables'][$old_table]['columns'][$field->name];
  2567. $flag = true;
  2568. }
  2569. }
  2570. // Key's columns.
  2571. if (! empty($field->key)) {
  2572. foreach ($field->key->columns as $key => $column) {
  2573. if (empty($aliases[$old_database]['tables'][$old_table]['columns'][$column['name']])) {
  2574. continue;
  2575. }
  2576. $columnAliases = $aliases[$old_database]['tables'][$old_table]['columns'];
  2577. $field->key->columns[$key]['name'] = $columnAliases[$column['name']];
  2578. $flag = true;
  2579. }
  2580. }
  2581. // References.
  2582. if (empty($field->references)) {
  2583. continue;
  2584. }
  2585. $ref_table = $field->references->table->table;
  2586. // Replacing table.
  2587. if (! empty($aliases[$old_database]['tables'][$ref_table]['alias'])) {
  2588. $field->references->table->table
  2589. = $aliases[$old_database]['tables'][$ref_table]['alias'];
  2590. $field->references->table->expr = '';
  2591. $flag = true;
  2592. }
  2593. // Replacing column names.
  2594. foreach ($field->references->columns as $key => $column) {
  2595. if (empty($aliases[$old_database]['tables'][$ref_table]['columns'][$column])) {
  2596. continue;
  2597. }
  2598. $field->references->columns[$key]
  2599. = $aliases[$old_database]['tables'][$ref_table]['columns'][$column];
  2600. $flag = true;
  2601. }
  2602. }
  2603. } elseif ($statement->options->has('TRIGGER')) {
  2604. // Extracting the name of the old database and table from the
  2605. // statement to make sure the parameters are correct.
  2606. if (! empty($statement->table->database)) {
  2607. $old_database = $statement->table->database;
  2608. }
  2609. /**
  2610. * Old table name.
  2611. *
  2612. * @var string $old_table
  2613. */
  2614. $old_table = $statement->table->table;
  2615. if (! empty($aliases[$old_database]['tables'][$old_table]['alias'])) {
  2616. $statement->table->table
  2617. = $aliases[$old_database]['tables'][$old_table]['alias'];
  2618. $statement->table->expr = ''; // Force rebuild.
  2619. $flag = true;
  2620. }
  2621. }
  2622. if ($statement->options->has('TRIGGER')
  2623. || $statement->options->has('PROCEDURE')
  2624. || $statement->options->has('FUNCTION')
  2625. || $statement->options->has('VIEW')
  2626. ) {
  2627. // Replacing the body.
  2628. for ($i = 0, $count = count((array) $statement->body); $i < $count; ++$i) {
  2629. /**
  2630. * Token parsed at this moment.
  2631. *
  2632. * @var Token $token
  2633. */
  2634. $token = $statement->body[$i];
  2635. // Replacing only symbols (that are not variables) and unknown
  2636. // identifiers.
  2637. $isSymbol = $token->type === Token::TYPE_SYMBOL;
  2638. $isKeyword = $token->type === Token::TYPE_KEYWORD;
  2639. $isNone = $token->type === Token::TYPE_NONE;
  2640. $replaceToken = $isSymbol
  2641. && (! ($token->flags & Token::FLAG_SYMBOL_VARIABLE))
  2642. || ($isKeyword
  2643. && (! ($token->flags & Token::FLAG_KEYWORD_RESERVED))
  2644. || $isNone);
  2645. if (! $replaceToken) {
  2646. continue;
  2647. }
  2648. $alias = $this->getAlias($aliases, $token->value);
  2649. if (empty($alias)) {
  2650. continue;
  2651. }
  2652. // Replacing the token.
  2653. $token->token = Context::escape($alias);
  2654. $flag = true;
  2655. }
  2656. }
  2657. return $statement->build();
  2658. }
  2659. /**
  2660. * Generate comment
  2661. *
  2662. * @param string $crlf Carriage return character
  2663. * @param string|null $sql_statement SQL statement
  2664. * @param string $comment1 Comment for dumped table
  2665. * @param string $comment2 Comment for current table
  2666. * @param string $table_alias Table alias
  2667. * @param string $compat Compatibility mode
  2668. *
  2669. * @return string
  2670. */
  2671. protected function generateComment(
  2672. $crlf,
  2673. ?string $sql_statement,
  2674. $comment1,
  2675. $comment2,
  2676. $table_alias,
  2677. $compat
  2678. ) {
  2679. if (! isset($sql_statement)) {
  2680. if (isset($GLOBALS['no_constraints_comments'])) {
  2681. $sql_statement = '';
  2682. } else {
  2683. $sql_statement = $crlf
  2684. . $this->exportComment()
  2685. . $this->exportComment($comment1)
  2686. . $this->exportComment();
  2687. }
  2688. }
  2689. // comments for current table
  2690. if (! isset($GLOBALS['no_constraints_comments'])) {
  2691. $sql_statement .= $crlf
  2692. . $this->exportComment()
  2693. . $this->exportComment(
  2694. $comment2 . ' ' . Util::backquoteCompat(
  2695. $table_alias,
  2696. $compat,
  2697. isset($GLOBALS['sql_backquotes'])
  2698. )
  2699. )
  2700. . $this->exportComment();
  2701. }
  2702. return $sql_statement;
  2703. }
  2704. }