MariaDBPlatform.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. <?php
  2. namespace PhpDevCommunity\PaperORM\Platform;
  3. use LogicException;
  4. use PhpDevCommunity\PaperORM\Mapping\Column\AnyColumn;
  5. use PhpDevCommunity\PaperORM\Mapping\Column\AutoIncrementColumn;
  6. use PhpDevCommunity\PaperORM\Mapping\Column\BinaryColumn;
  7. use PhpDevCommunity\PaperORM\Mapping\Column\BoolColumn;
  8. use PhpDevCommunity\PaperORM\Mapping\Column\Column;
  9. use PhpDevCommunity\PaperORM\Mapping\Column\DateColumn;
  10. use PhpDevCommunity\PaperORM\Mapping\Column\DateTimeColumn;
  11. use PhpDevCommunity\PaperORM\Mapping\Column\DecimalColumn;
  12. use PhpDevCommunity\PaperORM\Mapping\Column\FloatColumn;
  13. use PhpDevCommunity\PaperORM\Mapping\Column\IntColumn;
  14. use PhpDevCommunity\PaperORM\Mapping\Column\JoinColumn;
  15. use PhpDevCommunity\PaperORM\Mapping\Column\JsonColumn;
  16. use PhpDevCommunity\PaperORM\Mapping\Column\PrimaryKeyColumn;
  17. use PhpDevCommunity\PaperORM\Mapping\Column\SlugColumn;
  18. use PhpDevCommunity\PaperORM\Mapping\Column\StringColumn;
  19. use PhpDevCommunity\PaperORM\Mapping\Column\TextColumn;
  20. use PhpDevCommunity\PaperORM\Mapping\Column\TimestampColumn;
  21. use PhpDevCommunity\PaperORM\Mapping\Column\TokenColumn;
  22. use PhpDevCommunity\PaperORM\Mapping\Column\UuidColumn;
  23. use PhpDevCommunity\PaperORM\Metadata\ColumnMetadata;
  24. use PhpDevCommunity\PaperORM\Metadata\ForeignKeyMetadata;
  25. use PhpDevCommunity\PaperORM\Metadata\IndexMetadata;
  26. use PhpDevCommunity\PaperORM\PaperConnection;
  27. use PhpDevCommunity\PaperORM\Parser\SQLTypeParser;
  28. use PhpDevCommunity\PaperORM\Schema\MariaDBSchema;
  29. use PhpDevCommunity\PaperORM\Schema\SchemaInterface;
  30. class MariaDBPlatform extends AbstractPlatform
  31. {
  32. private PaperConnection $connection;
  33. private MariaDBSchema $schema;
  34. public function __construct(PaperConnection $connection, MariaDBSchema $schema)
  35. {
  36. $this->connection = $connection;
  37. $this->schema = $schema;
  38. }
  39. public function getDatabaseName(): string
  40. {
  41. return $this->connection->getParams()['path'] ?? '';
  42. }
  43. public function listTables(): array
  44. {
  45. $rows = $this->connection->fetchAll($this->schema->showTables());
  46. $tables = [];
  47. foreach ($rows as $table) {
  48. $table = array_values($table);
  49. $tables[] = $table[0];
  50. }
  51. rsort($tables, SORT_STRING);
  52. return $tables;
  53. }
  54. /**
  55. * @param string $tableName
  56. * @return array<ColumnMetadata>
  57. */
  58. public function listTableColumns(string $tableName): array
  59. {
  60. $tables = $this->listTables();
  61. if (!in_array($tableName, $tables)) {
  62. return [];
  63. }
  64. $rows = $this->connection->fetchAll($this->schema->showTableColumns($tableName));
  65. $foreignKeys = $this->connection->fetchAll($this->schema->showForeignKeys($tableName));
  66. $columns = [];
  67. foreach ($rows as $row) {
  68. $foreignKeyMetadata = null;
  69. foreach ($foreignKeys as $foreignKey) {
  70. if ($row['Field'] == $foreignKey['COLUMN_NAME']) {
  71. $foreignKeyMetadata = ForeignKeyMetadata::fromArray([
  72. 'name' => $foreignKey['CONSTRAINT_NAME'],
  73. 'columns' => [$row['Field']],
  74. 'referenceTable' => $foreignKey['REFERENCED_TABLE_NAME'],
  75. 'referenceColumns' => [$foreignKey['REFERENCED_COLUMN_NAME']],
  76. 'onDelete' => $this->convertForeignKeyRuleStringToCode($foreignKey['DELETE_RULE']),
  77. 'onUpdate' => $this->convertForeignKeyRuleStringToCode($foreignKey['UPDATE_RULE']),
  78. ]);
  79. break;
  80. }
  81. }
  82. $columnMetadata = ColumnMetadata::fromArray([
  83. 'name' => $row['Field'],
  84. 'type' => SQLTypeParser::getBaseType($row['Type']),
  85. 'primary' => ($row['Key'] === 'PRI'),
  86. 'foreignKeyMetadata' => $foreignKeyMetadata,
  87. 'null' => ($row['Null'] === 'YES'),
  88. 'default' => $row['Default'] ?? null,
  89. 'comment' => $row['comment'] ?? null,
  90. 'attributes' => SQLTypeParser::extractTypedParameters($row['Type']),
  91. ]);
  92. $columns[] = $columnMetadata;
  93. }
  94. return $columns;
  95. }
  96. /**
  97. * @param string $tableName
  98. * @return array<IndexMetadata>
  99. */
  100. public function listTableIndexes(string $tableName): array
  101. {
  102. $tables = $this->listTables();
  103. if (!in_array($tableName, $tables)) {
  104. return [];
  105. }
  106. $indexes = $this->connection->fetchAll($this->schema->showTableIndexes($tableName));
  107. $indexByColumns = [];
  108. foreach ($indexes as $index) {
  109. $indexName = $index['Key_name'];
  110. if (isset($indexByColumns[$indexName])) {
  111. $indexByColumns[$indexName]['columns'][] = $index['Column_name'];
  112. continue;
  113. }
  114. if ($indexName === 'PRIMARY') {
  115. continue;
  116. }
  117. $indexByColumns[$indexName] = [
  118. 'tableName' => $index['Table'],
  119. 'name' => $indexName,
  120. 'columns' => [$index['Column_name']],
  121. 'unique' => ((int)$index['Non_unique'] === 0),
  122. ];
  123. }
  124. $indexesFormatted = [];
  125. foreach ($indexByColumns as $idx) {
  126. $indexesFormatted[] = IndexMetadata::fromArray($idx);
  127. }
  128. return $indexesFormatted;
  129. }
  130. public function listDatabases(): array
  131. {
  132. throw new LogicException(sprintf("The method '%s' is not supported by the platform interface.", __METHOD__));
  133. }
  134. public function createDatabase(): void
  135. {
  136. $connection = $this->connection->cloneConnectionWithoutDbname();
  137. $connection->executeStatement($this->schema->createDatabase($this->getDatabaseName()));
  138. }
  139. public function createDatabaseIfNotExists(): void
  140. {
  141. $connection = $this->connection->cloneConnectionWithoutDbname();
  142. $connection->executeStatement($this->schema->createDatabaseIfNotExists($this->getDatabaseName()));
  143. }
  144. public function dropDatabase(): void
  145. {
  146. $connection = $this->connection->cloneConnectionWithoutDbname();
  147. $database = $this->getDatabaseName();
  148. $connection->executeStatement($this->schema->dropDatabase($database));
  149. }
  150. public function createTable(string $tableName, array $columns): int
  151. {
  152. return $this->executeStatement($this->schema->createTable($tableName, $this->mapColumnsToMetadata($tableName, $columns)));
  153. }
  154. public function createTableIfNotExists(string $tableName, array $columns, array $options = []): int
  155. {
  156. return $this->connection->executeStatement($this->schema->createTableIfNotExists($tableName, $this->mapColumnsToMetadata($tableName, $columns)));
  157. }
  158. public function dropTable(string $tableName): int
  159. {
  160. return $this->connection->executeStatement($this->schema->dropTable($tableName));
  161. }
  162. public function addColumn(string $tableName, Column $column): int
  163. {
  164. return $this->connection->executeStatement($this->schema->addColumn($tableName, $this->mapColumnToMetadata($tableName, $column)));
  165. }
  166. public function dropColumn(string $tableName, Column $column): int
  167. {
  168. return $this->connection->executeStatement($this->schema->dropColumn($tableName, $this->mapColumnToMetadata($tableName, $column)));
  169. }
  170. public function renameColumn(string $tableName, string $oldColumnName, string $newColumnName): int
  171. {
  172. return $this->connection->executeStatement($this->schema->renameColumn($tableName, $oldColumnName, $newColumnName));
  173. }
  174. public function createIndex(IndexMetadata $indexMetadata): int
  175. {
  176. return $this->connection->executeStatement($this->schema->createIndex($indexMetadata));
  177. }
  178. public function dropIndex(IndexMetadata $indexMetadata): int
  179. {
  180. return $this->connection->executeStatement($this->schema->dropIndex($indexMetadata));
  181. }
  182. public function createForeignKeyConstraint(string $tableName, ForeignKeyMetadata $foreignKey): int
  183. {
  184. return $this->executeStatement($this->schema->createForeignKeyConstraint($tableName, $foreignKey));
  185. }
  186. public function dropForeignKeyConstraints(string $tableName, string $foreignKeyName): int
  187. {
  188. return $this->executeStatement($this->schema->dropForeignKeyConstraints($tableName, $foreignKeyName));
  189. }
  190. public function getMaxLength(): int
  191. {
  192. return 30;
  193. }
  194. public function getPrefixIndexName(): string
  195. {
  196. return 'ix_';
  197. }
  198. public function getPrefixUniqIndexName(): string
  199. {
  200. return 'uniq_';
  201. }
  202. public function getPrefixForeignKeyName(): string
  203. {
  204. return 'fk_';
  205. }
  206. public function getColumnTypeMappings(): array
  207. {
  208. return [
  209. PrimaryKeyColumn::class => [
  210. 'type' => 'INT',
  211. 'args' => [11]
  212. ],
  213. IntColumn::class => [
  214. 'type' => 'INT',
  215. 'args' => [11]
  216. ],
  217. JoinColumn::class => [
  218. 'type' => 'INT',
  219. 'args' => [11]
  220. ],
  221. DecimalColumn::class => [
  222. 'type' => 'DECIMAL',
  223. 'args' => [10, 5]
  224. ],
  225. FloatColumn::class => [
  226. 'type' => 'FLOAT',
  227. 'args' => []
  228. ],
  229. DateColumn::class => [
  230. 'type' => 'DATE',
  231. 'args' => []
  232. ],
  233. DateTimeColumn::class => [
  234. 'type' => 'DATETIME',
  235. 'args' => []
  236. ],
  237. TimestampColumn::class => [
  238. 'type' => 'DATETIME',
  239. 'args' => [],
  240. ],
  241. BoolColumn::class => [
  242. 'type' => 'TINYINT',
  243. 'args' => [1]
  244. ],
  245. TextColumn::class => [
  246. 'type' => 'TEXT',
  247. 'args' => []
  248. ],
  249. JsonColumn::class => [
  250. 'type' => 'LONGTEXT',
  251. 'args' => []
  252. ],
  253. StringColumn::class => [
  254. 'type' => 'VARCHAR',
  255. 'args' => [255]
  256. ],
  257. SlugColumn::class => [
  258. 'type' => 'VARCHAR',
  259. 'args' => [128]
  260. ],
  261. BinaryColumn::class => [
  262. 'type' => 'BLOB',
  263. 'args' => []
  264. ],
  265. AnyColumn::class => [
  266. 'type' => 'VARCHAR',
  267. 'args' => [150],
  268. ],
  269. UuidColumn::class => [
  270. 'type' => 'VARCHAR',
  271. 'args' => [36],
  272. ],
  273. AutoIncrementColumn::class => [
  274. 'type' => 'VARCHAR',
  275. 'args' => [150],
  276. ],
  277. TokenColumn::class => [
  278. 'type' => 'VARCHAR',
  279. 'args' => [128],
  280. ]
  281. ];
  282. }
  283. public function convertForeignKeyRuleStringToCode(?string $rule): int
  284. {
  285. $rule = strtoupper($rule);
  286. switch ($rule) {
  287. case 'CASCADE':
  288. return ForeignKeyMetadata::CASCADE;
  289. case 'SET NULL':
  290. return ForeignKeyMetadata::SET_NULL;
  291. case 'RESTRICT':
  292. return ForeignKeyMetadata::RESTRICT;
  293. case 'NO ACTION': /* fall-through */
  294. default:
  295. return ForeignKeyMetadata::NO_ACTION;
  296. }
  297. }
  298. public function getSchema(): SchemaInterface
  299. {
  300. return $this->schema;
  301. }
  302. public function supportsTransactionalDDL(): bool
  303. {
  304. return false;
  305. }
  306. public function getConnection(): PaperConnection
  307. {
  308. return $this->connection;
  309. }
  310. public function executeStatement(string $sql): int
  311. {
  312. $result = 0;
  313. foreach (explode(';', $sql) as $stmt) {
  314. $stmt = trim($stmt);
  315. if (!empty($stmt)) {
  316. $result += $this->getConnection()->executeStatement($stmt);
  317. }
  318. }
  319. return $result;
  320. }
  321. public function autoCreateIndexJoinColumns(): bool
  322. {
  323. return true;
  324. }
  325. public function autoCreateIndexPrimaryKeys(): bool
  326. {
  327. return false;
  328. }
  329. public function autoCreateIndexUniqueColumns(): bool
  330. {
  331. return true;
  332. }
  333. }