Skip to content

Commit

Permalink
Merge pull request #796 from cakephp/mysql-schema
Browse files Browse the repository at this point in the history
Use cakephp/database for mysql schema
  • Loading branch information
markstory authored Jan 3, 2025
2 parents 59b672f + 611b1b8 commit d9b6140
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 107 deletions.
143 changes: 59 additions & 84 deletions src/Db/Adapter/MysqlAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

use Cake\Core\Configure;
use Cake\Database\Connection;
use Cake\Database\Exception\QueryException;
use Cake\Database\Schema\SchemaDialect;
use InvalidArgumentException;
use Migrations\Db\AlterInstructions;
use Migrations\Db\Literal;
Expand Down Expand Up @@ -157,15 +159,31 @@ public function rollbackTransaction(): void
*/
public function quoteTableName(string $tableName): string
{
return str_replace('.', '`.`', $this->quoteColumnName($tableName));
$driver = $this->getConnection()->getDriver();

return $driver->quoteIdentifier($tableName);
}

/**
* @inheritDoc
*/
public function quoteColumnName(string $columnName): string
{
return '`' . str_replace('`', '``', $columnName) . '`';
$driver = $this->getConnection()->getDriver();

return $driver->quoteIdentifier($columnName);
}

/**
* Get the schema dialect for this adapter.
*
* @return \Cake\Database\Schema\SchemaDialect
*/
protected function getSchemaDialect(): SchemaDialect
{
$driver = $this->getConnection()->getDriver();

return $driver->schemaDialect();
}

/**
Expand All @@ -186,9 +204,9 @@ public function hasTable(string $tableName): bool
}
}

$options = $this->getOptions();
$database = (string)$this->getOption('database');

return $this->hasTableWithSchema($options['database'], $tableName);
return $this->hasTableWithSchema($database, $tableName);
}

/**
Expand All @@ -198,15 +216,20 @@ public function hasTable(string $tableName): bool
*/
protected function hasTableWithSchema(string $schema, string $tableName): bool
{
$connection = $this->getConnection();
$result = $connection->execute(
"SELECT TABLE_NAME
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?",
[$schema, $tableName]
);
$dialect = $this->getSchemaDialect();
[$query, $params] = $dialect->listTablesSql(['database' => $schema]);

return $result->rowCount() === 1;
try {
$statement = $this->query($query, $params);
} catch (QueryException $e) {
return false;
}
$tables = [];
foreach ($statement->fetchAll() as $row) {
$tables[] = $row[0];
}

return in_array($tableName, $tables, true);
}

/**
Expand Down Expand Up @@ -293,7 +316,7 @@ public function createTable(Table $table, array $columns = [], array $indexes =
if (is_string($primaryKey)) { // handle primary_key => 'id'
$sql .= $this->quoteColumnName($primaryKey);
} elseif (is_array($primaryKey)) { // handle primary_key => array('tag_id', 'resource_id')
$sql .= implode(',', array_map([$this, 'quoteColumnName'], $primaryKey));
$sql .= implode(',', array_map($this->quoteColumnName(...), $primaryKey));
}
$sql .= ')';
} else {
Expand Down Expand Up @@ -335,7 +358,7 @@ protected function getChangePrimaryKeyInstructions(Table $table, $newColumns): A
if (is_string($newColumns)) { // handle primary_key => 'id'
$sql .= $this->quoteColumnName($newColumns);
} elseif (is_array($newColumns)) { // handle primary_key => array('tag_id', 'resource_id')
$sql .= implode(',', array_map([$this, 'quoteColumnName'], $newColumns));
$sql .= implode(',', array_map($this->quoteColumnName(...), $newColumns));
} else {
throw new InvalidArgumentException(sprintf(
'Invalid value for primary key: %s',
Expand Down Expand Up @@ -408,8 +431,11 @@ public function truncateTable(string $tableName): void
*/
public function getColumns(string $tableName): array
{
$dialect = $this->getSchemaDialect();
[$query, $params] = $dialect->describeColumnSql($tableName, []);
$rows = $this->query($query, $params)->fetchAll('assoc');

$columns = [];
$rows = $this->fetchAll(sprintf('SHOW FULL COLUMNS FROM %s', $this->quoteTableName($tableName)));
foreach ($rows as $columnInfo) {
$phinxType = $this->getPhinxType($columnInfo['Type']);

Expand Down Expand Up @@ -589,8 +615,10 @@ protected function getDropColumnInstructions(string $tableName, string $columnNa
*/
protected function getIndexes(string $tableName): array
{
$dialect = $this->getSchemaDialect();
[$query, $params] = $dialect->describeIndexSql($tableName, []);
$rows = $this->query($query, $params)->fetchAll('assoc');
$indexes = [];
$rows = $this->fetchAll(sprintf('SHOW INDEXES FROM %s', $this->quoteTableName($tableName)));
foreach ($rows as $row) {
if (!isset($indexes[$row['Key_name']])) {
$indexes[$row['Key_name']] = ['columns' => []];
Expand Down Expand Up @@ -751,30 +779,14 @@ public function hasPrimaryKey(string $tableName, $columns, ?string $constraint =
*/
public function getPrimaryKey(string $tableName): array
{
$options = $this->getOptions();
$params = [
$options['database'],
$tableName,
];
$rows = $this->query(
"SELECT
k.CONSTRAINT_NAME,
k.COLUMN_NAME
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS t
JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE k
USING(CONSTRAINT_NAME,TABLE_SCHEMA,TABLE_NAME)
WHERE t.CONSTRAINT_TYPE = 'PRIMARY KEY'
AND t.TABLE_SCHEMA = ?
AND t.TABLE_NAME = ?",
$params
)->fetchAll('assoc');

$indexes = $this->getIndexes($tableName);
$primaryKey = [
'constraint' => '',
'columns' => [],
];
foreach ($rows as $row) {
$primaryKey['constraint'] = $row['CONSTRAINT_NAME'];
$primaryKey['columns'][] = $row['COLUMN_NAME'];
foreach ($indexes as $name => $row) {
$primaryKey['constraint'] = $name;
$primaryKey['columns'] = (array)$row['columns'];
}

return $primaryKey;
Expand Down Expand Up @@ -813,33 +825,17 @@ public function hasForeignKey(string $tableName, $columns, ?string $constraint =
*/
protected function getForeignKeys(string $tableName): array
{
$schema = null;
$dialect = $this->getSchemaDialect();
$schema = (string)$this->getOption('database');
if (strpos($tableName, '.') !== false) {
[$schema, $tableName] = explode('.', $tableName);
}
$config = ['database' => $schema];

$params = [];
$query = "SELECT
CONSTRAINT_NAME,
CONCAT(TABLE_SCHEMA, '.', TABLE_NAME) AS TABLE_NAME,
COLUMN_NAME,
CONCAT(REFERENCED_TABLE_SCHEMA, '.', REFERENCED_TABLE_NAME) AS REFERENCED_TABLE_NAME,
REFERENCED_COLUMN_NAME
FROM information_schema.KEY_COLUMN_USAGE
WHERE REFERENCED_TABLE_NAME IS NOT NULL";

if ($schema) {
$query .= ' AND TABLE_SCHEMA = ?';
$params[] = $schema;
} else {
$query .= ' AND TABLE_SCHEMA = DATABASE()';
}

$query .= ' AND TABLE_NAME = ? ORDER BY POSITION_IN_UNIQUE_CONSTRAINT';
$params[] = $tableName;
[$query, $params] = $dialect->describeForeignKeySql($tableName, $config);
$rows = $this->query($query, $params)->fetchAll('assoc');

$foreignKeys = [];
$rows = $this->query($query, $params)->fetchAll('assoc');
foreach ($rows as $row) {
$foreignKeys[$row['CONSTRAINT_NAME']]['table'] = $row['TABLE_NAME'];
$foreignKeys[$row['CONSTRAINT_NAME']]['columns'][] = $row['COLUMN_NAME'];
Expand Down Expand Up @@ -1412,14 +1408,15 @@ protected function getIndexSqlDefinition(Index $index): string

$def .= ' KEY';

if (is_string($index->getName())) {
$def .= ' `' . $index->getName() . '`';
$name = $index->getName();
if (is_string($name)) {
$def .= ' ' . $this->quoteColumnName($name);
}

$columnNames = (array)$index->getColumns();
$order = $index->getOrder() ?? [];
$columnNames = array_map(function ($columnName) use ($order) {
$ret = '`' . $columnName . '`';
$ret = $this->quoteColumnName($columnName);
if (isset($order[$columnName])) {
$ret .= ' ' . $order[$columnName];
}
Expand All @@ -1439,7 +1436,7 @@ protected function getIndexSqlDefinition(Index $index): string
foreach ($columns as $column) {
$limit = !isset($limits[$column]) || $limits[$column] <= 0 ? '' : '(' . $limits[$column] . ')';
$columnSort = $order[$column] ?? '';
$def .= '`' . $column . '`' . $limit . ' ' . $columnSort . ', ';
$def .= $this->quoteColumnName($column) . $limit . ' ' . $columnSort . ', ';
}
$def = rtrim($def, ', ');
$def .= ' )';
Expand Down Expand Up @@ -1480,28 +1477,6 @@ protected function getForeignKeySqlDefinition(ForeignKey $foreignKey): string
return $def;
}

/**
* Describes a database table. This is a MySQL adapter specific method.
*
* @param string $tableName Table name
* @return array
*/
public function describeTable(string $tableName): array
{
$options = $this->getOptions();

// mysql specific
$sql = "SELECT *
FROM information_schema.tables
WHERE table_schema = ?
AND table_name = ?";
$params = [$options['database'], $tableName];

$table = $this->query($sql, $params)->fetch('assoc');

return $table !== false ? $table : [];
}

/**
* Returns MySQL column types (inherited and MySQL specified).
*
Expand Down
23 changes: 0 additions & 23 deletions tests/TestCase/Db/Adapter/MysqlAdapterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -146,15 +146,6 @@ public function testHasTableUnderstandsSchemaNotation()
$this->assertFalse($this->adapter->hasTable('unknown_schema.phinxlog'));
}

public function testHasTableRespectsDotInTableName()
{
$sql = "CREATE TABLE `discouraged.naming.convention`
(id INT(11) NOT NULL)
ENGINE = InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci";
$this->adapter->execute($sql);
$this->assertTrue($this->adapter->hasTable('discouraged.naming.convention'));
}

public function testCreateTable()
{
$table = new Table('ntable', [], $this->adapter);
Expand Down Expand Up @@ -1371,20 +1362,6 @@ public function testGetColumnsInteger()
$this->assertEquals($this->usingMysql8() ? null : 10, $columns[1]->getLimit());
}

public function testDescribeTable()
{
$table = new Table('t', [], $this->adapter);
$table->addColumn('column1', 'string');
$table->save();

$described = $this->adapter->describeTable('t');

$this->assertContains($described['TABLE_TYPE'], ['VIEW', 'BASE TABLE']);
$this->assertEquals($described['TABLE_NAME'], 't');
$this->assertEquals($described['TABLE_SCHEMA'], $this->config['database']);
$this->assertEquals($described['TABLE_ROWS'], 0);
}

public function testGetColumnsReservedTableName()
{
$table = new Table('group', [], $this->adapter);
Expand Down

0 comments on commit d9b6140

Please sign in to comment.