MySqlSchemaState.php 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. <?php
  2. namespace Illuminate\Database\Schema;
  3. use Exception;
  4. use Illuminate\Database\Connection;
  5. use Illuminate\Support\Str;
  6. use Symfony\Component\Process\Process;
  7. class MySqlSchemaState extends SchemaState
  8. {
  9. /**
  10. * Dump the database's schema into a file.
  11. *
  12. * @param \Illuminate\Database\Connection $connection
  13. * @param string $path
  14. * @return void
  15. */
  16. public function dump(Connection $connection, $path)
  17. {
  18. $this->executeDumpProcess($this->makeProcess(
  19. $this->baseDumpCommand().' --routines --result-file="${:LARAVEL_LOAD_PATH}" --no-data'
  20. ), $this->output, array_merge($this->baseVariables($this->connection->getConfig()), [
  21. 'LARAVEL_LOAD_PATH' => $path,
  22. ]));
  23. $this->removeAutoIncrementingState($path);
  24. if ($this->hasMigrationTable()) {
  25. $this->appendMigrationData($path);
  26. }
  27. }
  28. /**
  29. * Remove the auto-incrementing state from the given schema dump.
  30. *
  31. * @param string $path
  32. * @return void
  33. */
  34. protected function removeAutoIncrementingState(string $path)
  35. {
  36. $this->files->put($path, preg_replace(
  37. '/\s+AUTO_INCREMENT=[0-9]+/iu',
  38. '',
  39. $this->files->get($path)
  40. ));
  41. }
  42. /**
  43. * Append the migration data to the schema dump.
  44. *
  45. * @param string $path
  46. * @return void
  47. */
  48. protected function appendMigrationData(string $path)
  49. {
  50. $process = $this->executeDumpProcess($this->makeProcess(
  51. $this->baseDumpCommand().' '.$this->migrationTable.' --no-create-info --skip-extended-insert --skip-routines --compact --complete-insert'
  52. ), null, array_merge($this->baseVariables($this->connection->getConfig()), [
  53. //
  54. ]));
  55. $this->files->append($path, $process->getOutput());
  56. }
  57. /**
  58. * Load the given schema file into the database.
  59. *
  60. * @param string $path
  61. * @return void
  62. */
  63. public function load($path)
  64. {
  65. $command = 'mysql '.$this->connectionString().' --database="${:LARAVEL_LOAD_DATABASE}" < "${:LARAVEL_LOAD_PATH}"';
  66. $process = $this->makeProcess($command)->setTimeout(null);
  67. $process->mustRun(null, array_merge($this->baseVariables($this->connection->getConfig()), [
  68. 'LARAVEL_LOAD_PATH' => $path,
  69. ]));
  70. }
  71. /**
  72. * Get the base dump command arguments for MySQL as a string.
  73. *
  74. * @return string
  75. */
  76. protected function baseDumpCommand()
  77. {
  78. $command = 'mysqldump '.$this->connectionString().' --no-tablespaces --skip-add-locks --skip-comments --skip-set-charset --tz-utc --column-statistics=0';
  79. if (! $this->connection->isMaria()) {
  80. $command .= ' --set-gtid-purged=OFF';
  81. }
  82. return $command.' "${:LARAVEL_LOAD_DATABASE}"';
  83. }
  84. /**
  85. * Generate a basic connection string (--socket, --host, --port, --user, --password) for the database.
  86. *
  87. * @return string
  88. */
  89. protected function connectionString()
  90. {
  91. $value = ' --user="${:LARAVEL_LOAD_USER}" --password="${:LARAVEL_LOAD_PASSWORD}"';
  92. $config = $this->connection->getConfig();
  93. $value .= $config['unix_socket'] ?? false
  94. ? ' --socket="${:LARAVEL_LOAD_SOCKET}"'
  95. : ' --host="${:LARAVEL_LOAD_HOST}" --port="${:LARAVEL_LOAD_PORT}"';
  96. if (isset($config['options'][\PDO::MYSQL_ATTR_SSL_CA])) {
  97. $value .= ' --ssl-ca="${:LARAVEL_LOAD_SSL_CA}"';
  98. }
  99. return $value;
  100. }
  101. /**
  102. * Get the base variables for a dump / load command.
  103. *
  104. * @param array $config
  105. * @return array
  106. */
  107. protected function baseVariables(array $config)
  108. {
  109. $config['host'] ??= '';
  110. return [
  111. 'LARAVEL_LOAD_SOCKET' => $config['unix_socket'] ?? '',
  112. 'LARAVEL_LOAD_HOST' => is_array($config['host']) ? $config['host'][0] : $config['host'],
  113. 'LARAVEL_LOAD_PORT' => $config['port'] ?? '',
  114. 'LARAVEL_LOAD_USER' => $config['username'],
  115. 'LARAVEL_LOAD_PASSWORD' => $config['password'] ?? '',
  116. 'LARAVEL_LOAD_DATABASE' => $config['database'],
  117. 'LARAVEL_LOAD_SSL_CA' => $config['options'][\PDO::MYSQL_ATTR_SSL_CA] ?? '',
  118. ];
  119. }
  120. /**
  121. * Execute the given dump process.
  122. *
  123. * @param \Symfony\Component\Process\Process $process
  124. * @param callable $output
  125. * @param array $variables
  126. * @return \Symfony\Component\Process\Process
  127. */
  128. protected function executeDumpProcess(Process $process, $output, array $variables)
  129. {
  130. try {
  131. $process->setTimeout(null)->mustRun($output, $variables);
  132. } catch (Exception $e) {
  133. if (Str::contains($e->getMessage(), ['column-statistics', 'column_statistics'])) {
  134. return $this->executeDumpProcess(Process::fromShellCommandLine(
  135. str_replace(' --column-statistics=0', '', $process->getCommandLine())
  136. ), $output, $variables);
  137. }
  138. if (str_contains($e->getMessage(), 'set-gtid-purged')) {
  139. return $this->executeDumpProcess(Process::fromShellCommandLine(
  140. str_replace(' --set-gtid-purged=OFF', '', $process->getCommandLine())
  141. ), $output, $variables);
  142. }
  143. throw $e;
  144. }
  145. return $process;
  146. }
  147. }