您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

240 行
6.7KB

  1. <?php
  2. namespace Framework\Database\Migration;
  3. use Framework\Database\Connection\SqliteConnection;
  4. use Framework\Database\Exception\MigrationException;
  5. use Framework\Database\Migration\Field\Field;
  6. use Framework\Database\Migration\Field\BoolField;
  7. use Framework\Database\Migration\Field\DateTimeField;
  8. use Framework\Database\Migration\Field\FloatField;
  9. use Framework\Database\Migration\Field\IdField;
  10. use Framework\Database\Migration\Field\IntField;
  11. use Framework\Database\Migration\Field\StringField;
  12. use Framework\Database\Migration\Field\TextField;
  13. class SqliteMigration extends Migration
  14. {
  15. protected SqliteConnection $connection;
  16. protected string $table;
  17. protected string $type;
  18. protected array $drops = [];
  19. public function __construct(SqliteConnection $connection, string $table, string $type)
  20. {
  21. $this->connection = $connection;
  22. $this->table = $table;
  23. $this->type = $type;
  24. }
  25. public function execute()
  26. {
  27. if ($this->type === 'create') {
  28. $this->executeCreate();
  29. return;
  30. }
  31. $hasAlteredField = array_reduce($this->fields, fn($carry, $field) => $carry || $field->alter, false);
  32. if (count($this->drops) > 0 || $hasAlteredField) {
  33. $this->executeRebuild();
  34. return;
  35. }
  36. $this->executeAdd();
  37. }
  38. private function executeCreate()
  39. {
  40. $fields = join(',' . PHP_EOL, array_map(fn($field) => $this->columnDefinition($field), $this->fields));
  41. $query = "
  42. CREATE TABLE \"{$this->table}\" (
  43. {$fields}
  44. );
  45. ";
  46. $this->connection->pdo()->prepare($query)->execute();
  47. }
  48. private function executeAdd()
  49. {
  50. $fields = join(';' . PHP_EOL, array_map(
  51. fn($field) => "ADD COLUMN {$this->columnDefinition($field)}",
  52. $this->fields
  53. ));
  54. $query = "
  55. ALTER TABLE \"{$this->table}\"
  56. {$fields};
  57. ";
  58. $this->connection->pdo()->prepare($query)->execute();
  59. }
  60. /**
  61. * SQLite can't alter or drop columns in place, so the standard approach is to
  62. * rebuild the table: create a new table with the desired schema, copy the
  63. * surviving data across, then swap it in for the original.
  64. */
  65. private function executeRebuild()
  66. {
  67. $pdo = $this->connection->pdo();
  68. $existingColumns = $pdo->query("PRAGMA table_info(\"{$this->table}\")")->fetchAll();
  69. $alteredFields = [];
  70. foreach ($this->fields as $field) {
  71. if ($field->alter) {
  72. $alteredFields[$field->name] = $field;
  73. }
  74. }
  75. $newFields = array_filter($this->fields, fn($field) => !$field->alter);
  76. $keptColumnNames = [];
  77. $definitions = [];
  78. foreach ($existingColumns as $column) {
  79. $name = $column['name'];
  80. if (in_array($name, $this->drops)) {
  81. continue;
  82. }
  83. $keptColumnNames[] = $name;
  84. $definitions[] = isset($alteredFields[$name])
  85. ? $this->columnDefinition($alteredFields[$name])
  86. : $this->existingColumnDefinition($column);
  87. }
  88. foreach ($newFields as $field) {
  89. $definitions[] = $this->columnDefinition($field);
  90. }
  91. $tempTable = "{$this->table}_migration_tmp";
  92. $definitionList = join(',' . PHP_EOL, $definitions);
  93. $columnList = join(', ', array_map(fn($name) => "\"{$name}\"", $keptColumnNames));
  94. $pdo->beginTransaction();
  95. $pdo->exec("CREATE TABLE \"{$tempTable}\" ({$definitionList})");
  96. $pdo->exec("INSERT INTO \"{$tempTable}\" ({$columnList}) SELECT {$columnList} FROM \"{$this->table}\"");
  97. $pdo->exec("DROP TABLE \"{$this->table}\"");
  98. $pdo->exec("ALTER TABLE \"{$tempTable}\" RENAME TO \"{$this->table}\"");
  99. $pdo->commit();
  100. }
  101. private function existingColumnDefinition(array $column): string
  102. {
  103. $name = $column['name'];
  104. $type = $column['type'];
  105. if ($column['pk'] == 1 && $type === 'INTEGER') {
  106. return "\"{$name}\" INTEGER PRIMARY KEY AUTOINCREMENT";
  107. }
  108. $definition = "\"{$name}\" {$type}";
  109. if ($column['notnull'] == 1) {
  110. $definition .= " NOT NULL";
  111. }
  112. if ($column['dflt_value'] !== null) {
  113. $definition .= " DEFAULT {$column['dflt_value']}";
  114. }
  115. return $definition;
  116. }
  117. private function columnDefinition(Field $field): string
  118. {
  119. if ($field instanceof BoolField) {
  120. $template = "\"{$field->name}\" INTEGER";
  121. if (!$field->nullable) {
  122. $template .= " NOT NULL";
  123. }
  124. if ($field->default !== null) {
  125. $default = (int) $field->default;
  126. $template .= " DEFAULT {$default}";
  127. }
  128. return $template;
  129. }
  130. if ($field instanceof DateTimeField) {
  131. $template = "\"{$field->name}\" TEXT";
  132. if (!$field->nullable) {
  133. $template .= " NOT NULL";
  134. }
  135. if ($field->default === 'CURRENT_TIMESTAMP') {
  136. $template .= " DEFAULT CURRENT_TIMESTAMP";
  137. } else if ($field->default !== null) {
  138. $template .= " DEFAULT '{$field->default}'";
  139. }
  140. return $template;
  141. }
  142. if ($field instanceof FloatField) {
  143. $template = "\"{$field->name}\" REAL";
  144. if (!$field->nullable) {
  145. $template .= " NOT NULL";
  146. }
  147. if ($field->default !== null) {
  148. $template .= " DEFAULT {$field->default}";
  149. }
  150. return $template;
  151. }
  152. if ($field instanceof IdField) {
  153. return "\"{$field->name}\" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE";
  154. }
  155. if ($field instanceof IntField) {
  156. $template = "\"{$field->name}\" INTEGER";
  157. if (!$field->nullable) {
  158. $template .= " NOT NULL";
  159. }
  160. if ($field->default !== null) {
  161. $template .= " DEFAULT {$field->default}";
  162. }
  163. return $template;
  164. }
  165. if ($field instanceof StringField || $field instanceof TextField) {
  166. $template = "\"{$field->name}\" TEXT";
  167. if (!$field->nullable) {
  168. $template .= " NOT NULL";
  169. }
  170. if ($field->default !== null) {
  171. $template .= " DEFAULT '{$field->default}'";
  172. }
  173. return $template;
  174. }
  175. throw new MigrationException("Unrecognised field type for {$field->name}");
  176. }
  177. public function dropColumn(string $name): static
  178. {
  179. $this->drops[] = $name;
  180. return $this;
  181. }
  182. }

Powered by TurnKey Linux.